diff --git a/lib/driver/image.h b/lib/driver/image.h index 3c987b30..36b8a9e5 100644 --- a/lib/driver/image.h +++ b/lib/driver/image.h @@ -39,6 +39,17 @@ #include "cdio_private.h" #include +/** + Sub-channel data stored after each sector in a disc image. +*/ +typedef enum { + IMAGE_SUBCHANNEL_NONE = 0, + IMAGE_SUBCHANNEL_RW, /**< 96 bytes of packed R-W (cdrdao RW) */ + IMAGE_SUBCHANNEL_RW_RAW /**< 96 bytes of raw interleaved sub-channel + data (cdrdao RW_RAW). cdrdao read-cd + stores the drive's raw P-W data. */ +} image_subchannel_t; + /** The universal format for information about a track for CD image readers It may be that some fields can be derived from other fields. @@ -78,6 +89,9 @@ typedef struct { This stuff may have error correction (EDC, or ECC).*/ uint16_t blocksize; /**< total block size = start + size + end */ + image_subchannel_t subchannel; /**< sub-channel data in endsize */ + lba_t data_lba; /**< LBA of the sector stored at offset in + data_source, 0 if unknown */ } track_info_t; diff --git a/lib/driver/image/cdrdao.c b/lib/driver/image/cdrdao.c index 4437cf6a..3e0cd3d9 100644 --- a/lib/driver/image/cdrdao.c +++ b/lib/driver/image/cdrdao.c @@ -636,13 +636,24 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) goto err_exit; } if (NULL != (psz_field = strtok_r(NULL, " \t\n\r", &saveptr))) { - /* \todo: set sub-channel-mode */ -#ifdef TODO + /* : 96 bytes follow each sector in the file */ + image_subchannel_t subchannel; if (0 == strcmp ("RW", psz_field)) - ; + subchannel = IMAGE_SUBCHANNEL_RW; else if (0 == strcmp ("RW_RAW", psz_field)) - ; -#endif + subchannel = IMAGE_SUBCHANNEL_RW_RAW; + else { + cdio_log(log_level, "%s line %d after TRACK:", + psz_cue_name, i_line); + cdio_log(log_level, "'%s' not a valid sub-channel mode.", + psz_field); + goto err_exit; + } + if (NULL != cd) { + cd->tocent[i_track].subchannel = subchannel; + cd->tocent[i_track].endsize += CDIO_CD_FRAMESIZE_SUB; + cd->tocent[i_track].blocksize += CDIO_CD_FRAMESIZE_SUB; + } } if (NULL != (psz_field = strtok_r(NULL, " \t\n\r", &saveptr))) { goto format_error; @@ -717,30 +728,31 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) if (0 <= i_track) { if (NULL != (psz_field = strtok_r(NULL, "\"\t\n\r", &saveptr))) { /* Handle "" */ + char *psz_dirname = cdio_dirname(psz_cue_name); + char *psz_filename = cdio_abspath(psz_dirname, psz_field); + free(psz_dirname); if (cd) { - char *psz_dirname = cdio_dirname(psz_cue_name); - char *psz_filename = cdio_abspath(psz_dirname, psz_field); cd->tocent[i_track].filename = strdup (psz_filename); - free(psz_filename); - free(psz_dirname); /* To do: do something about reusing existing files. */ - if (!(cd->tocent[i_track].data_source = cdio_stdio_new (psz_field))) { + if (!(cd->tocent[i_track].data_source = cdio_stdio_new (psz_filename))) { cdio_log (log_level, "%s line %d: can't open file `%s' for reading", psz_cue_name, i_line, psz_field); + free(psz_filename); goto err_exit; } } else { - CdioDataSource_t *s = cdio_stdio_new (psz_field); + CdioDataSource_t *s = cdio_stdio_new (psz_filename); if (!s) { cdio_log (log_level, "%s line %d: can't open file `%s' for reading", psz_cue_name, i_line, psz_field); - cdio_stdio_destroy (s); + free(psz_filename); goto err_exit; } cdio_stdio_destroy (s); } + free(psz_filename); } if (NULL != (psz_field = strtok_r(NULL, " \t\n\r", &saveptr))) { @@ -788,7 +800,7 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) goto not_in_global_section; } - /* DATAFILE "" #byte-offset */ + /* DATAFILE "" [#byte-offset] [] */ } else if (0 == strcmp ("DATAFILE", psz_keyword)) { if (0 <= i_track) { if (NULL != (psz_field = strtok_r(NULL, "\"\t\n\r", &saveptr))) { @@ -798,7 +810,7 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) if (cd) { cd->tocent[i_track].filename = strdup(psz_filename); /* To do: do something about reusing existing files. */ - if (!(cd->tocent[i_track].data_source = cdio_stdio_new (psz_field))) { + if (!(cd->tocent[i_track].data_source = cdio_stdio_new (psz_filename))) { cdio_log (log_level, "%s line %d: can't open file `%s' for reading", psz_cue_name, i_line, psz_field); @@ -845,37 +857,38 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) } } if (psz_field) { - /* Handle start-msf */ + /* Handle */ lba_t lba = cdio_mmssff_to_lba (psz_field); if (CDIO_INVALID_LBA == lba) { cdio_log(log_level, "%s line %d: invalid MSF string %s", psz_cue_name, i_line, psz_field); goto err_exit; } - if (cd) { - cd->tocent[i_track].start_lba = lba; - cdio_lba_to_msf(cd->tocent[i_track].start_lba, - &(cd->tocent[i_track].start_msf)); - } - } else { - /* No start-msf. */ - if (cd) { - if (i_track) { - uint16_t i_blocksize = cd->tocent[i_track-1].blocksize; - off_t i_size = - cdio_stream_stat(cd->tocent[i_track-1].data_source); - - check_track_is_blocksize_multiple(cd->tocent[i_track-1].filename, - i_track-1, i_size, i_blocksize); - /* Append size of previous datafile. */ - cd->tocent[i_track].start_lba = (lba_t) (cd->tocent[i_track-1].start_lba + - (i_size / i_blocksize)); - } - cd->tocent[i_track].offset = 0; - cd->tocent[i_track].start_lba += CDIO_PREGAP_SECTORS; - cdio_lba_to_msf(cd->tocent[i_track].start_lba, - &(cd->tocent[i_track].start_msf)); + if (cd) + cd->tocent[i_track].sec_count = lba; + } + if (cd) { + /* The track starts where the previous one ends */ + if (0 == i_track) { + cd->tocent[i_track].start_lba = CDIO_PREGAP_SECTORS; + } else if (cd->tocent[i_track-1].sec_count) { + cd->tocent[i_track].start_lba = cd->tocent[i_track-1].start_lba + + cd->tocent[i_track-1].sec_count; + } else { + /* No length: the previous track takes up the rest of its file */ + const track_info_t *prev = &cd->tocent[i_track-1]; + uint16_t i_blocksize = prev->blocksize; + off_t i_size = cdio_stream_stat(prev->data_source) - prev->offset; + + check_track_is_blocksize_multiple(prev->filename, + i_track-1, i_size, i_blocksize); + cd->tocent[i_track].start_lba = (lba_t) + ((prev->data_lba ? prev->data_lba : prev->start_lba) + + (i_size / i_blocksize)); } + cd->tocent[i_track].data_lba = cd->tocent[i_track].start_lba; + cdio_lba_to_msf(cd->tocent[i_track].start_lba, + &(cd->tocent[i_track].start_msf)); } } else { @@ -896,7 +909,9 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) cd->tocent[i_track].start_lba += cdio_mmssff_to_lba (psz_field); cdio_lba_to_msf(cd->tocent[i_track].start_lba, &(cd->tocent[i_track].start_msf)); - cd->tocent[i_track].sec_count -= cdio_mmssff_to_lba (psz_field); + /* Unknown lengths stay unknown */ + if (cd->tocent[i_track].sec_count) + cd->tocent[i_track].sec_count -= cdio_mmssff_to_lba (psz_field); } } @@ -1032,6 +1047,42 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) return false; } +/** + Returns the DATAFILE track that holds lsn, or NULL if there is none. + Pregap sectors set with START belong to the track that follows them. + */ +static const track_info_t * +_get_data_track_cdrdao (const _img_private_t *env, lsn_t lsn) +{ + const lba_t lba = cdio_lsn_to_lba(lsn); + int i; + + for (i = env->gen.i_tracks - 1; i >= 0; i--) { + const track_info_t *this_track = &env->tocent[i]; + if (this_track->data_lba && this_track->data_lba <= lba) + return this_track; + } + return NULL; +} + +/** + Reads the whole block of the sector at lsn, blocksize bytes, from + this_track's data file into buf. + */ +static driver_return_code_t +_read_block_cdrdao (const track_info_t *this_track, uint8_t *buf, lsn_t lsn) +{ + const off_t pos = this_track->offset + (off_t) + (cdio_lsn_to_lba(lsn) - this_track->data_lba) * this_track->blocksize; + + if (0 != cdio_stream_seek (this_track->data_source, pos, SEEK_SET)) + return DRIVER_OP_ERROR; + if (this_track->blocksize != + cdio_stream_read (this_track->data_source, buf, this_track->blocksize, 1)) + return DRIVER_OP_ERROR; + return DRIVER_OP_SUCCESS; +} + /** Reads a single audio sector from CD device into data starting from lsn. Returns 0 if no error. @@ -1043,6 +1094,26 @@ _read_audio_sectors_cdrdao (void *user_data, void *data, lsn_t lsn, _img_private_t *env = user_data; int ret; + if (_get_data_track_cdrdao(env, lsn)) { + uint8_t buf[CDIO_CD_FRAMESIZE_RAW + CDIO_CD_FRAMESIZE_SUB]; + uint8_t *p = data; + unsigned int i; + + for (i = 0; i < nblocks; i++) { + const track_info_t *this_track = _get_data_track_cdrdao(env, lsn + i); + if (!this_track || this_track->blocksize < CDIO_CD_FRAMESIZE_RAW || + this_track->blocksize > sizeof(buf)) + return DRIVER_OP_ERROR; + ret = _read_block_cdrdao(this_track, buf, lsn + i); + if (ret) + return ret; + memcpy(p, buf, CDIO_CD_FRAMESIZE_RAW); + p += CDIO_CD_FRAMESIZE_RAW; + } + return DRIVER_OP_SUCCESS; + } + + /* FILE and AUDIOFILE tracks */ ret = cdio_stream_seek (env->tocent[0].data_source, lsn * CDIO_CD_FRAMESIZE_RAW, SEEK_SET); if (ret!=0) return ret; diff --git a/test/check_fuzzyiso.sh b/test/check_fuzzyiso.sh index 71069da2..22a26f78 100755 --- a/test/check_fuzzyiso.sh +++ b/test/check_fuzzyiso.sh @@ -22,7 +22,7 @@ fi cd $srcdir; src_dir=`pwd` for file in $src_dir/data/*.bin $src_dir/data/*.iso $src_dir/data/*.nrg ; do case "$file" in - $src_dir/data/p1.nrg | $src_dir/data/p1.bin | $src_dir/data/cdda.bin | $src_dir/data/cdda_4_5.bin | $src_dir/data/cdtextfile.bin | $src_dir/data/cdda-mcn.nrg | $src_dir/data/svcdgs.nrg | $src_dir/data/cdtext.bin | $src_dir/data/udf102.iso | $src_dir/data/too-many-tracks.nrg ) + $src_dir/data/p1.nrg | $src_dir/data/p1.bin | $src_dir/data/cdda.bin | $src_dir/data/cdda-rw-raw.bin | $src_dir/data/cdda_4_5.bin | $src_dir/data/cdtextfile.bin | $src_dir/data/cdda-mcn.nrg | $src_dir/data/svcdgs.nrg | $src_dir/data/cdtext.bin | $src_dir/data/udf102.iso | $src_dir/data/too-many-tracks.nrg ) good_rc=1 ;; *) diff --git a/test/data/Makefile.am b/test/data/Makefile.am index 710ab51b..dd44d58e 100644 --- a/test/data/Makefile.am +++ b/test/data/Makefile.am @@ -20,8 +20,11 @@ check_DATA = \ bad-tracknum-exceeded.cue \ bad-tracknum-exceeded.toc \ bad-tracknum-overflow.toc \ + cdda-rw-raw.bin \ + cdda-rw-raw.toc \ cdda.bin \ cdda.cue \ + cdda.toc \ cdda_4_5.bin \ cdda_4_5.cue \ cdimage-u2.iso \ diff --git a/test/data/cdda-rw-raw.bin b/test/data/cdda-rw-raw.bin new file mode 100644 index 00000000..19ca591d Binary files /dev/null and b/test/data/cdda-rw-raw.bin differ diff --git a/test/data/cdda-rw-raw.toc b/test/data/cdda-rw-raw.toc new file mode 100644 index 00000000..f709c660 --- /dev/null +++ b/test/data/cdda-rw-raw.toc @@ -0,0 +1,20 @@ +// Two audio tracks with raw P-W sub-channel data after each sector, as +// written by cdrdao read-cd --read-subchan rw_raw. Track 2 has a two +// sector pregap. Sectors hold (lsn * 31 + byte) & 0xff, the Q sub-channel +// gives each sector's track, index and position. +CD_DA + +// Track 1 +TRACK AUDIO RW_RAW +NO COPY +NO PRE_EMPHASIS +TWO_CHANNEL_AUDIO +DATAFILE "cdda-rw-raw.bin" 00:00:06 // length in bytes: 14688 + +// Track 2 +TRACK AUDIO RW_RAW +NO COPY +NO PRE_EMPHASIS +TWO_CHANNEL_AUDIO +DATAFILE "cdda-rw-raw.bin" #14688 00:00:08 // length in bytes: 19584 +START 00:00:02 diff --git a/test/driver/cdrdao.c b/test/driver/cdrdao.c index e9c29560..e2f4ef7c 100644 --- a/test/driver/cdrdao.c +++ b/test/driver/cdrdao.c @@ -51,6 +51,66 @@ #define NUM_GOOD_TOCS 17 #define NUM_BAD_TOCS 10 +#define RW_RAW_SECTORS 14 +#define RW_RAW_BLOCKSIZE (CDIO_CD_FRAMESIZE_RAW + CDIO_CD_FRAMESIZE_SUB) + +/* Read the image of cdda-rw-raw.toc, opened from outside its directory */ +static int +check_rw_raw(void) +{ + const char *toc_name = DATA_DIR "/cdda-rw-raw.toc"; + static uint8_t image[RW_RAW_SECTORS * RW_RAW_BLOCKSIZE]; + static uint8_t buf[RW_RAW_SECTORS * RW_RAW_BLOCKSIZE]; + FILE *bin = fopen(DATA_DIR "/cdda-rw-raw.bin", "rb"); + CdIo_t *p_cdio; + lsn_t lsn; + int ret = 0; + + if (!bin || sizeof(image) != fread(image, 1, sizeof(image), bin)) { + fprintf(stderr, "Unable to read cdda-rw-raw.bin\n"); + if (bin) + fclose(bin); + return 1; + } + fclose(bin); + + p_cdio = cdio_open_cdrdao(toc_name); + if (!p_cdio) { + fprintf(stderr, "Incorrect: %s doesn't open.\n", toc_name); + return 1; + } + + if (2 != cdio_get_num_tracks(p_cdio) || + 0 != cdio_get_track_lsn(p_cdio, 1) || + 6 != cdio_get_track_pregap_lsn(p_cdio, 2) || + 8 != cdio_get_track_lsn(p_cdio, 2) || + RW_RAW_SECTORS != cdio_get_track_lsn(p_cdio, CDIO_CDROM_LEADOUT_TRACK)) { + fprintf(stderr, "Incorrect: %s track positions.\n", toc_name); + ret = 1; + goto out; + } + + /* Audio sectors leave out the sub-channel data */ + if (DRIVER_OP_SUCCESS != + cdio_read_audio_sectors(p_cdio, buf, 0, RW_RAW_SECTORS)) { + fprintf(stderr, "Incorrect: %s audio read failed.\n", toc_name); + ret = 1; + goto out; + } + for (lsn = 0; lsn < RW_RAW_SECTORS; lsn++) { + if (memcmp(buf + lsn * CDIO_CD_FRAMESIZE_RAW, + image + lsn * RW_RAW_BLOCKSIZE, CDIO_CD_FRAMESIZE_RAW)) { + fprintf(stderr, "Incorrect: %s audio of LSN %d.\n", toc_name, lsn); + ret = 1; + goto out; + } + } + + out: + cdio_destroy(p_cdio); + return ret; +} + static int check_no_tracks(void) { @@ -139,7 +199,7 @@ main(int argc, const char *argv[]) char psz_tocfile[500]; unsigned int verbose = (argc > 1); - if (check_no_tracks() || check_track_without_mode()) + if (check_no_tracks() || check_track_without_mode() || check_rw_raw()) ret = 1; #ifdef HAVE_CHDIR