Expand description
§CD-DA (audio CD) reading library
This library provides cross-platform audio CD reading capabilities (tested
on Windows, macOS and Linux). It was written to enable CD ripping, but it can
also be used to build a live audio CD player. The primary API reads physical
discs; to read from a file, image, or another custom source, implement
AudioSectorReader and provide a Toc.
Physical-disc access uses platform CD-drive APIs on macOS and direct SCSI commands on Windows and Linux. The library abstracts both access to the drive and reading the data, so callers do not interact with the hardware directly. It operates entirely in user space.
A typical drive-backed read happens in this order:
- Get a CD drive’s handle
- Read the ToC (table of contents) of the audio CD
- Read track data using ranges from the ToC
§CD access
The easiest way to open a drive is to use CdReader::open_default, which scans
all drives and opens the first one that contains an audio CD:
use cd_da_reader::CdReader;
let reader = CdReader::open_default()?;If you need to pick a specific drive, use CdReader::list_drives followed
by calling CdReader::open with the selected drive:
use cd_da_reader::CdReader;
let drives = CdReader::list_drives()?;
let selected = drives
.iter()
.find(|drive| drive.has_audio_cd) // we check for audio by checking ToC
.ok_or("no drive with an audio CD found")?;
let reader = CdReader::open(selected)?;If you already know the platform-specific device path, use
CdReader::open_path instead.
§Reading ToC
Each audio CD carries a Table of Contents with the block address of every track. You need to read it first before issuing any track read commands:
use cd_da_reader::CdReader;
let reader = CdReader::open_default()?;
let toc = reader.read_toc()?;The returned Toc contains a Vec<Track>. Each Track reports
its disc track number in Track::number and whether it contains audio in
Track::is_audio. Track numbers are not zero-based indices into
Toc::tracks and are not guaranteed to begin at 1 (but they usually do).
Each track also has two equivalent address fields:
start_lba– Logical Block Address, which is a sector index. LBA 0 is the first readable sector after the 2-second lead-in pre-gap. This is the format used internally for read commands.start_msf— Minutes/Seconds/Frames, a time-based address inherited from the physical disc layout. A “frame” is one sector; the spec defines 75 frames per second. MSF includes a fixed 2-second (150-frame) lead-in offset, so(0, 2, 0)corresponds to LBA 0. You can convert between them easily:LBA + 150 = total frames, then divide by 75 and 60 for M/S/F.
§Reading tracks
Pass the Toc and the track’s Track::number to
CdReader::read_track. The library calculates the sector boundaries
automatically. On CD-Extra discs
where the last audio track is followed only by data tracks, the trailing
audio/data session gap is excluded from the audio read – this is usually
what you want, and you can read custom range by using CdReader::read_sector_range.
use cd_da_reader::CdReader;
let reader = CdReader::open_default()?;
let toc = reader.read_toc()?;
// Track numbers come from the disc; do not assume the first audio track is #1.
let track = toc
.tracks
.iter()
.find(|track| track.is_audio)
.ok_or("no audio tracks found")?;
let data = reader.read_track(&toc, track.number)?;CdReader::read_track is a blocking call that buffers the complete track,
so it can take some time and use hundreds of megabytes of memory. The
streaming API instead returns sector-aligned chunks as they are read, which
keeps memory usage low and supports progress reporting or playback before the
complete track is available.
Streaming is still synchronous: each TrackStream::next_chunk call waits
for the drive to return the next chunk. This is often suitable for a CLI,
where the read loop can run on the main thread and report progress. A GUI
should run the loop on a worker thread so drive reads do not block its event
loop. Open a stream with CdReader::open_track_stream:
use cd_da_reader::CdReader;
let reader = CdReader::open_default()?;
let toc = reader.read_toc()?;
// Select by track metadata rather than assuming track #1 contains audio.
let track = toc
.tracks
.iter()
.find(|track| track.is_audio)
.ok_or("no audio tracks found")?;
let mut stream = reader.open_track_stream(&toc, track.number)?;
while let Some(chunk) = stream.next_chunk()? {
// process chunk — raw PCM, 2 352 bytes per sector
}§Audio track format
Audio track data is raw PCM, the same uncompressed sample representation used by PCM WAV files. Audio CDs use signed 16-bit little-endian stereo PCM sampled at 44,100 Hz:
44,100 sample frames * 2 channels * 2 bytes = 176,400 bytes/secondEach audio sector holds exactly 2,352 bytes (176,400 ÷ 75 = 2,352), which gives 75 sectors per second. A typical 3-minute track is about 31.8 MB (30.3 MiB). A 74-minute disc contains about 783 MB (747 MiB) of raw PCM; common 80-minute media contains about 847 MB (808 MiB).
Converting raw PCM to a playable WAV file only requires prepending a 44-byte
RIFF header — create_wav does exactly that:
use cd_da_reader::{CdReader, create_wav};
let reader = CdReader::open_default()?;
let toc = reader.read_toc()?;
let track = toc
.tracks
.iter()
.find(|track| track.is_audio)
.ok_or("no audio tracks found")?;
let data = reader.read_track(&toc, track.number)?;
let wav = create_wav(data);
let output = format!("track{:02}.wav", track.number);
std::fs::write(output, wav)?;§Read options
CdReader::read_track and CdReader::open_track_stream use the
ReadOptions defaults: CD-DA audio sectors, the default retry policy, and
no read-speed change. These settings are sufficient for most audio reads.
For more control, start with ReadOptions::default() and pass the configured
options to CdReader::read_track_with_options or CdReader::open_track_stream_with_options.
The configurable options are:
- Sector format:
SectorReadFormatcontrols the type and layout of sectors returned by the drive.SectorReadFormat::Audiois the default. For a data track,CdReader::detect_track_formatcan select an appropriate default format to pass toReadOptions::with_format. - Retry policy:
RetryConfigcontrols the number of attempts, retry delays, and adaptive reduction of the number of sectors requested after a failed read. Its defaults are suitable for most drives. - Read speed:
ReadSpeedrequests an automatic or custom drive speed. The default,ReadSpeed::Unchanged, issues no speed-change request. Requested speeds are not guaranteed, and this crate does not restore the previous drive setting afterward. Speed behavior depends on the OS and drive firmware.
use cd_da_reader::{CdReader, ReadOptions, ReadSpeed, RetryConfig, SectorReadFormat};
let reader = CdReader::open_default()?;
let toc = reader.read_toc()?;
let track = toc
.tracks
.iter()
.find(|track| track.is_audio)
.ok_or("no audio tracks found")?;
let options = ReadOptions::default()
.with_format(SectorReadFormat::Audio)
.with_retry(RetryConfig::default().with_max_attempts(6))
.with_read_speed(ReadSpeed::CustomMultiplier(4));
let data = reader.read_track_with_options(&toc, track.number, &options)?;§Metadata
Audio CDs carry almost no semantic metadata. CD-TEXT exists but is
unreliable and because of that is not provided by this library. The practical approach is to
calculate a Disc ID from the ToC and look it up on a service such as
MusicBrainz. The Toc struct exposes everything required for the
MusicBrainz disc ID algorithm.
Structs§
- Audio
Track Stream - A pull-based, sector-aligned stream of raw CD-DA PCM from an
AudioSectorReader. - CdReader
- Helper struct to interact with the audio CD. Internally it holds a platform-specific handle to the open CD drive to read from it and it is correctly closed when CDReader is dropped.
- Drive
Info - Information about an optical drive discovered by
CdReader::list_drives. - Read
Options - Sector format, retry policy, and read speed options for track, streaming, and sector-range reads.
- Retry
Config - Retry policy for failed drive reads.
- Scsi
Error - Structured SCSI failure context captured at the call site.
- Toc
- Table of Contents, read directly from the Audio CD. The most important part
is the
tracksvector, which allows you to read raw track data. - Track
- Representation of the track from ToC, purely in terms of data location on the CD.
- Track
Stream - Track-scoped streaming reader for audio or data sectors.
Enums§
- CdReader
Error - Top-level error type returned by
cd-da-reader. - Read
Speed - A platform-independent read-speed request for an optical drive.
- ScsiOp
- SCSI command groups issued by this library.
- Sector
Read Format - Selects the type and layout of sectors returned when reading from an optical drive.
- Track
Bounds - Policy for deriving a track’s half-open sector range from a
Toc.
Traits§
- Audio
Sector Reader - A source of raw CD-DA audio sectors.
Functions§
- create_
wav - Prepends a standard 44-byte RIFF/WAVE header to raw CD-DA PCM.
- lba_
to_ msf - Convert a Logical Block Address to its Minutes/Seconds/Frames address.
- open_
track_ stream - Open a streaming reader for a track assuming the TOC includes the inter-session
gap (
TrackBounds::SessionGap). SeeAudioTrackStream. - open_
track_ stream_ at - Open a streaming reader over an explicit absolute sector range
(
start_lba .. start_lba + sectors), bypassing TOC bounds resolution. - open_
track_ stream_ with_ bounds - Open a streaming reader for a track with an explicit
TrackBoundsgeometry. UseTrackBounds::Gaplessfor a contiguous, gap-stripped layout. - read_
track - Reads one complete audio track from an
AudioSectorReaderinto memory. - read_
track_ with_ bounds - Reads one complete audio track into memory using an explicit
TrackBoundspolicy.