Skip to content
Merged
2 changes: 2 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fn main() {
println!("cargo:rerun-if-changed={NATIVE_DIR}/toc_reader.c");
println!("cargo:rerun-if-changed={NATIVE_DIR}/track_information.c");
println!("cargo:rerun-if-changed={NATIVE_DIR}/read_cd.c");
println!("cargo:rerun-if-changed={NATIVE_DIR}/request_read_speed.c");

println!("cargo:rustc-link-lib=framework=IOKit");
println!("cargo:rustc-link-lib=framework=CoreFoundation");
Expand All @@ -22,6 +23,7 @@ fn main() {
.file(format!("{NATIVE_DIR}/toc_reader.c"))
.file(format!("{NATIVE_DIR}/track_information.c"))
.file(format!("{NATIVE_DIR}/read_cd.c"))
.file(format!("{NATIVE_DIR}/request_read_speed.c"))
.include(NATIVE_DIR)
// force C compilation
.flag("-x")
Expand Down
50 changes: 50 additions & 0 deletions examples/read_speed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// Read the first audio track at 10x speed, and read the second audio track at `Optimal`
/// speed.
mod common;

use cd_da_reader::{CdReader, ReadOptions, ReadSpeed, Track};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let output_dir = common::fresh_output_dir("read_speed")?;
let reader = CdReader::open_default()?;
let toc = reader.read_toc()?;

let audio_tracks: Vec<&Track> = toc.tracks.iter().filter(|t| t.is_audio).collect();

if audio_tracks.len() < 2 {
panic!("This example requires at least two audio tracks");
}

let first_track = audio_tracks[0];
let second_track = audio_tracks[1];

// Read the first track with 10x speed
{
let options_10x = ReadOptions::default().with_read_speed(ReadSpeed::CustomMultiplier(10));

println!("Reading track {} with 10x speed...", first_track.number);
let data = reader.read_track_with_options(&toc, first_track.number, &options_10x)?;

let wav = CdReader::create_wav(data);
let output_path = output_dir.join(format!("track{:02}.wav", first_track.number));
std::fs::write(&output_path, wav)?;
println!("Saved {}", output_path.display());
}

// Read the second track with "optimal" speed
{
let options_optimal = ReadOptions::default().with_read_speed(ReadSpeed::Optimal);
println!(
"Reading track {} with optimal speed...",
second_track.number
);
let data = reader.read_track_with_options(&toc, second_track.number, &options_optimal)?;

let wav = CdReader::create_wav(data);
let output_path = output_dir.join(format!("track{:02}.wav", second_track.number));
std::fs::write(&output_path, wav)?;
println!("Saved {}", output_path.display());
}

Ok(())
}
18 changes: 18 additions & 0 deletions src/data_reader/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod detect;
mod raw_sector;
mod read_speed;
mod sector_read_format;
pub(crate) mod track_information;

pub use read_speed::ReadSpeed;
pub use sector_read_format::SectorReadFormat;

use crate::retry::RetryConfig;
Expand All @@ -16,6 +18,7 @@ use crate::{CdReaderError, Track};
pub struct ReadOptions {
format: SectorReadFormat,
retry: RetryConfig,
read_speed: ReadSpeed,
}

impl ReadOptions {
Expand All @@ -31,20 +34,35 @@ impl ReadOptions {
self
}

/// Set the read speed to request from the drive. See [`ReadSpeed`] for
/// details.
///
/// # Note
/// For simplicity, this crate doesn't restore the previous speed setting.
pub fn with_read_speed(mut self, read_speed: ReadSpeed) -> Self {
self.read_speed = read_speed;
self
}

pub(crate) fn format(&self) -> SectorReadFormat {
self.format
}

pub(crate) fn retry(&self) -> &RetryConfig {
&self.retry
}

pub(crate) fn read_speed(&self) -> ReadSpeed {
self.read_speed
}
}

impl Default for ReadOptions {
fn default() -> Self {
Self {
format: SectorReadFormat::Audio,
retry: RetryConfig::default(),
read_speed: ReadSpeed::Unchanged,
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/data_reader/read_speed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// Representation of read speed requested by the `SET CD SPEED` (0xBB) command.
///
/// # Note
///
/// According to the MMC-3 specification, the requested speed doesn't necessarily
/// match the actual read speed. The drive may select the specified read speed
/// or any higher rate.
///
/// Therefore, this enum represents a requested speed, not a guaranteed actual
/// read speed. The actual behaviour is drive-dependent.
#[derive(Debug, Clone, Copy)]
pub enum ReadSpeed {
/// Don't change the speed
///
/// The speed depends on the OS, previous configuration, and other factors.
Unchanged,

/// Request the drive-selected/optimal speed.
///
/// According to the MMC-3 specification, the drive can select its optimal
/// speed when the `SET CD SPEED` command is executed with the read
/// speed (KB/s) set to 0xFFFF.
///
/// On macOS, this variant requests `SET CD SPEED` with 0xFFFF.
///
/// On Linux, the read speed is selected by the `CDROM_SELECT_SPEED`
/// ioctl with speed = 0. It requests automatic speed selection.
Optimal,

/// Use a custom speed with the specified multiplier.
///
/// Although the CD-DA read speed should be requested in KB/s according to
/// the specification, this variant uses a multiplier for simplicity.
///
/// For example, `ReadSpeed::CustomMultiplier(1)` represents the nominal
/// CD-DA 1x read rate (176.4 KB/s). The exact conversion is
/// platform-dependent.
///
/// Another example: `ReadSpeed::CustomMultiplier(10)` represents 10x speed.
///
/// `ReadSpeed::CustomMultiplier(0)` is equivalent to `ReadSpeed::Optimal`.
/// The value 0 is used as a sentinel.
CustomMultiplier(u8),
}
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ pub use backend::{
AudioSectorReader, AudioTrackStream, TrackBounds, open_track_stream, open_track_stream_at,
open_track_stream_with_bounds, read_track, read_track_with_bounds,
};
pub use data_reader::{ReadOptions, SectorReadFormat};
pub use data_reader::{ReadOptions, ReadSpeed, SectorReadFormat};
pub use discovery::DriveInfo;
pub use errors::{CdReaderError, ScsiError, ScsiOp};
pub use retry::RetryConfig;
Expand Down Expand Up @@ -319,6 +319,7 @@ impl CdReader {
options: &ReadOptions,
) -> Result<Vec<u8>, CdReaderError> {
let format = options.format();
self.drive.request_read_speed(options.read_speed())?;
read_loop::read_sectors_chunked(
start_lba,
sectors,
Expand Down
8 changes: 8 additions & 0 deletions src/platform/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod device;
mod read_cd;
mod sg_io;
mod speed;
mod toc;
mod track_information;

Expand Down Expand Up @@ -28,4 +29,11 @@ impl Drive {
) -> Result<Vec<u8>, CdReaderError> {
read_cd::read_cd_chunk(self, lba, sectors, format)
}

pub(crate) fn request_read_speed(
&self,
read_speed: crate::data_reader::ReadSpeed,
) -> Result<(), CdReaderError> {
speed::request_read_speed(self, read_speed)
}
}
29 changes: 29 additions & 0 deletions src/platform/linux/speed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use super::device::Drive;
use crate::CdReaderError;
use crate::data_reader::ReadSpeed;
use std::os::fd::RawFd;

// From linux/include/uapi/linux/cdrom.h
const CDROM_SELECT_SPEED: libc::c_ulong = 0x5322;

pub(super) fn request_read_speed(
drive: &Drive,
target_read_speed: ReadSpeed,
) -> Result<(), CdReaderError> {
let multiplier = match target_read_speed {
ReadSpeed::Unchanged => return Ok(()),
ReadSpeed::Optimal => 0,
ReadSpeed::CustomMultiplier(x) => x,
};

execute_request_read_speed(drive.fd(), multiplier)
}

fn execute_request_read_speed(fd: RawFd, multiplier: u8) -> Result<(), CdReaderError> {
let result = unsafe { libc::ioctl(fd, CDROM_SELECT_SPEED, multiplier as libc::c_ulong) };
if result < 0 {
Err(CdReaderError::Io(std::io::Error::last_os_error()))
} else {
Ok(())
}
}
1 change: 1 addition & 0 deletions src/platform/macos/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ unsafe extern "C" {
out_len: *mut u32,
out_err: *mut MacScsiError,
) -> bool;
pub(super) fn request_cd_read_speed(fd: libc::c_int, multiplier: libc::c_ushort) -> bool;
pub(super) fn cd_free(pointer: *mut libc::c_void);
pub(super) fn list_cd_drives(out_drives: *mut *mut MacDriveInfo, out_count: *mut u32) -> bool;
pub(super) fn open_cd_raw_device(bsd_name: *const libc::c_char) -> libc::c_int;
Expand Down
8 changes: 8 additions & 0 deletions src/platform/macos/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod device;
mod ffi;
mod read_cd;
mod speed;
mod toc;
mod track_information;

Expand Down Expand Up @@ -28,4 +29,11 @@ impl Drive {
) -> Result<Vec<u8>, CdReaderError> {
read_cd::read_cd_chunk(self, lba, sectors, format)
}

pub(crate) fn request_read_speed(
&self,
read_speed: crate::data_reader::ReadSpeed,
) -> Result<(), CdReaderError> {
speed::request_read_speed(self, read_speed)
}
}
12 changes: 12 additions & 0 deletions src/platform/macos/native/request_read_speed.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include "shim_common.h"
#include <IOKit/storage/IOCDMediaBSDClient.h>

Boolean request_cd_read_speed(int fd, uint16_t target_speed_kbs) {
int ret = ioctl(fd, DKIOCCDSETSPEED, &target_speed_kbs);
if (ret < 0) {
fprintf(stderr, "[SPEED] DKIOCCDSETSPEED failed (errno=%d)\n", errno);
return false;
}

return true;
}
27 changes: 27 additions & 0 deletions src/platform/macos/speed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use super::device::Drive;
use crate::CdReaderError;
use crate::data_reader::ReadSpeed;

pub(super) fn request_read_speed(
drive: &Drive,
target_read_speed: ReadSpeed,
) -> Result<(), CdReaderError> {
let multiplier = match target_read_speed {
ReadSpeed::Unchanged => return Ok(()),
ReadSpeed::Optimal => 0,
ReadSpeed::CustomMultiplier(x) => x as u32,
};
let target_speed_kbs = if multiplier == 0 {
0xffff
} else {
multiplier * 176400 / 1000
Comment thread
Bloomca marked this conversation as resolved.
};
let response =
unsafe { super::ffi::request_cd_read_speed(drive.fd(), target_speed_kbs as u16) };

if !response {
return Err(CdReaderError::Io(std::io::Error::last_os_error()));
}

Ok(())
}
8 changes: 8 additions & 0 deletions src/platform/windows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod device;
mod read_cd;
mod speed;
mod spti;
mod toc;
mod track_information;
Expand Down Expand Up @@ -28,4 +29,11 @@ impl Drive {
) -> Result<Vec<u8>, CdReaderError> {
read_cd::read_cd_chunk(self, lba, sectors, format)
}

pub(crate) fn request_read_speed(
&self,
read_speed: crate::data_reader::ReadSpeed,
) -> Result<(), CdReaderError> {
speed::request_read_speed(self, read_speed)
}
}
18 changes: 18 additions & 0 deletions src/platform/windows/speed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use super::device::Drive;
use crate::CdReaderError;
use crate::data_reader::ReadSpeed;

pub(super) fn request_read_speed(
drive: &Drive,
target_read_speed: ReadSpeed,
) -> Result<(), CdReaderError> {
// stub
/*
let multiplier = match target_read_speed {
ReadSpeed::Unchanged => return Ok(()),
ReadSpeed::Optimal => 0,
ReadSpeed::CustomMultiplier(x) => x,
};
*/
Ok(())
}
Loading