Skip to content

cdrdao: read images with RW_RAW sub-channel data - #106

Draft
nicosp wants to merge 7 commits into
libcdio:mainfrom
nicosp:cdrdao-subchannel
Draft

nicosp wants to merge 7 commits into
libcdio:mainfrom
nicosp:cdrdao-subchannel

Conversation

@nicosp

@nicosp nicosp commented Oct 22, 2026 •

Copy link
Copy Markdown

Problem

The cdrdao driver can't open the TOC written by cdrdao read-cd --read-raw --read-subchan rw_raw:

  • RW_RAW is ignored, so every sector is read at the wrong offset.
  • The DATAFILE length is read as a start position. This also breaks existing images: the test suite's cdda.toc and vcd2.toc open with negative track LSNs.
  • Data files are opened relative to the current directory instead of the TOC.

Changes

  • Fix those three problems.
  • Read each track's audio from its own file, offset and block size.
  • Return the stored sub-channel data through an emulated READ CD: none, raw P-W or formatted Q.
  • Implement cdio_audio_read_subchannel(): the Q position of the last audio sector read, as READ SUB-CHANNEL gives on a drive. This doesn't use MMC.
  • Add a test with a small synthetic RW_RAW image.

Why this is a draft

This enables MMC commands on disc images: the cdrdao driver now implements run_mmc_cmd, so mmc_read_cd() works on an image the way it does on a drive. Only READ CD is emulated, and mmc-supported? stays false. If an image driver shouldn't answer MMC commands, I can switch to a separate API for reading a sector's sub-channel data instead.

Testing

  • make check passes at each commit.
  • Programs that check pre-emphasis with cdio_audio_read_subchannel(), such as cyanrip, no longer fail on every track of an RW_RAW image.
  • On a 55-track enhanced CD image, cyanrip read the disc ID and pregaps from the sub-channel data. The tracks ripped from the image matched an AccurateRip-verified rip from the drive.

Not handled

PREGAP length/silence, SILENCE/ZERO, and packed RW sub-channel data.

🤖 Generated with Claude Code

nicosp and others added 6 commits October 22, 2026 15:53
FILE, AUDIOFILE and DATAFILE names were opened relative to the
current directory, although the path relative to the TOC file's
directory was already computed and stored as the track's filename.
TOC files only opened when the program ran from the TOC file's
directory, which is how the cdrdao driver test runs.

Open the computed path, as the bincue driver does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cdrdao's TOC format is DATAFILE "<filename>" [#byte-offset] [<length>]
(see cdrdao(1)), and cdrdao read-cd and read-toc write the length of
each track there. The parser took it as the track's start instead, so
every track started at its own length; test/data/cdda.toc and vcd2.toc
opened with negative LSNs:

  cdda.toc   before: track 1 LSN -125, lead-out 177
             after:  track 1 LSN 0, lead-out 25
  vcd2.toc   before: tracks at LSN -150, -147, -125, lead-out 177
             after:  tracks at LSN 0, 302, 305, lead-out 330

Store the length as the track's sector count and start each track
where the previous one ends: after its length, or after the rest of
its data file when no length is given. That fallback used to add the
150 sector lead-in pregap on every track, and dropped any byte offset.

Keep the LBA of the sector at the track's byte offset in data_lba, as
START moves start_lba past the pregap, and leave unknown lengths at 0
on START instead of making them negative.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Audio sectors were read from the first track's data file at
lsn * 2352, ignoring each track's file, byte offset and block size.
That only works for a single file that starts at the first sector.

For DATAFILE tracks, find the track holding each sector and read it
from that track's file, at its byte offset plus the sector's position
times the block size. FILE and AUDIOFILE tracks are read as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TRACK <track-mode> [<sub-channel-mode>] was parsed, but the
sub-channel mode was ignored. With RW or RW_RAW, 96 bytes of
sub-channel data follow each sector in the data file (see cdrdao(1)),
as written by cdrdao read-cd --read-subchan rw or rw_raw. Their
sectors were read 2352 bytes apart instead of 2448, so all audio
after the first sector was misaligned.

Record the mode and add the sub-channel data to the track's block
size. Reject unknown sub-channel modes, as is done for track modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cdrdao read-cd --read-subchan rw_raw stores the drive's raw P-W
sub-channel data with each sector, but libcdio had no way to return
it: images have no per-sector sub-channel read, and mmc_read_cd() is
unsupported without a run_mmc_cmd driver function. Programs that read
the Q sub-channel of a drive with mmc_read_cd(), for example to find
track pregaps, could not do so on such an image.

Give the cdrdao driver a run_mmc_cmd that emulates READ CD for CD-DA
user data, with no sub-channel, raw P-W as stored in RW_RAW tracks, or
formatted Q taken from bit 6 of the raw data. Sub-channel reads of
tracks without RW_RAW data, and every other command, stay
unsupported. mmc-supported? still answers false, as most commands are
not available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cdda-rw-raw.toc describes two audio tracks with raw P-W sub-channel
data after each sector, as cdrdao read-cd --read-subchan rw_raw writes
them: one data file with a byte offset and a length for each track,
and a two sector START pregap in track 2. The Q sub-channel gives each
sector's track, index and position.

Open it from outside its directory and check the track positions, the
audio data, and the raw P-W and formatted Q returned by mmc_read_cd().
Also check that READ CD of cdda.toc only works without sub-channel
data.

Add cdda.toc to check_DATA, as the test now reads it, and skip the new
audio-only image in check_fuzzyiso.sh like cdda.bin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rocky

rocky commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

I am okay with an image driver supporting MMC commands. @skr4n @pbatard: Your thoughts?

@pbatard

pbatard commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

I'll be blunt here: I do not care about any of the drivers, or cdrdao, since I do not use them in any of my projects, so you can really do whatever you wish here as far as I am concerned.

@rocky

rocky commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

I'll be blunt here: I do not care about any of the drivers, or cdrdao, since I do not use them in any of my projects, so you can really do whatever you wish here as far as I am concerned.

Ok. Thanks for the information. Sorry for the noise.

@skr4n

skr4n commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

I am okay with an image driver supporting MMC commands. @skr4n @pbatard: Your thoughts?

In my opinion, this is the continuation of libcdio's design mistake that I've observed when working on libcdio-rs. That being the tendency to overload or generalize things.

For example, the library is centered around CdIo_t, which is designed as a general object that can represent either an image or hardware.
The problem with this is that when CdIo_t is initialized as an image, all hardware routines are pointless (eject(), mmc()), and are made out to return NULL or a dummy response (such as with hardware_info() returning libcdio as the vendor).

If an image driver shouldn't answer MMC commands, I can switch to a separate API for reading a sector's sub-channel data instead.

Here, as you pointed out, an image driver is implementing an MMC routine.
It would indeed be better if a more appropriate routine is used, or created if one doesn't exist. (I'm not as familiar with the code, @rocky might guide here).

Existing code and design is indeed hard to change, given the prevalence of this library. However, if new code and interfaces diverge from this, it will be better long term.

cdio_audio_read_subchannel() had no image driver support, so it failed
on every image. For RW_RAW tracks, return the Q position of the last
audio sector read, the way READ SUB-CHANNEL does on a drive: the last
position (mode 1) frame, going back past MCN and ISRC frames. Tracks
without sub-channel data stay unsupported.

The Q extraction from raw P-W is shared with the READ CD emulation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nicosp

nicosp commented Oct 23, 2026

Copy link
Copy Markdown
Author

The alternative to MMC emulation is to introduce a new API:

typedef enum {
  CDIO_SUB_NONE   = 0,  /**< 2352 bytes, no sub-channel */
  CDIO_SUB_PW_RAW = 1,  /**< 2352 + 96 bytes of raw interleaved P-W */
  CDIO_SUB_Q      = 2,  /**< 2352 + 16 bytes: the 12 Q bytes with their
                             CRC, padded to 16 as MMC READ CD returns it */
} cdio_read_sub_t;

driver_return_code_t
cdio_read_audio_sectors_sub(const CdIo_t *p_cdio, void *p_buf, lsn_t i_lsn,
                            cdio_read_sub_t i_sub, uint32_t i_blocks);

This will be used in place of mmc_read_cd().

Physical drives will use mmc_read_cd() and as bonus this can also be implemented for macOS with ioctl.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants