mirror of
https://github.com/Dangoware/cd_reader.git
synced 2025-04-19 09:52:54 -05:00
Added reasonable CD interface
This commit is contained in:
parent
20965bbafc
commit
4cc6149ebd
5 changed files with 523 additions and 70 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -18,9 +18,12 @@ checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||||
name = "cd_read"
|
name = "cd_read"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"hound",
|
||||||
|
"md5",
|
||||||
"nix",
|
"nix",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -35,12 +38,24 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hound"
|
||||||
|
version = "3.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.169"
|
version = "0.2.169"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md5"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
|
@ -102,6 +117,26 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
|
|
@ -4,6 +4,15 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
hound = "3.5.1"
|
||||||
|
md5 = "0.7.0"
|
||||||
nix = { version = "0.29.0", features = ["ioctl"] }
|
nix = { version = "0.29.0", features = ["ioctl"] }
|
||||||
num-derive = "0.4.2"
|
num-derive = "0.4.2"
|
||||||
num-traits = "0.2.19"
|
num-traits = "0.2.19"
|
||||||
|
thiserror = "2.0.11"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true # Automatically strip symbols from the binary.
|
||||||
|
opt-level = "z" # Optimize for size.
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
|
101
src/constants.rs
101
src/constants.rs
|
@ -3,13 +3,15 @@
|
||||||
|
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
|
|
||||||
|
pub const EDRIVE_CANT_DO_THIS: i32 = nix::errno::Errno::EOPNOTSUPP as i32;
|
||||||
|
|
||||||
/// CDROM ioctl byte, from <linux/cdrom.h>
|
/// CDROM ioctl byte, from <linux/cdrom.h>
|
||||||
pub const IOC_BYTE: u8 = 0x53;
|
pub const IOC_BYTE: u8 = 0x53;
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(FromPrimitive, ToPrimitive)]
|
#[derive(FromPrimitive, ToPrimitive)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Operations {
|
pub enum Operation {
|
||||||
/// Pause Audio Operation
|
/// Pause Audio Operation
|
||||||
Pause = 0x01,
|
Pause = 0x01,
|
||||||
/// Resume paused Audio Operation
|
/// Resume paused Audio Operation
|
||||||
|
@ -136,18 +138,16 @@ pub enum Operations {
|
||||||
TimedMediaChange = 0x96,
|
TimedMediaChange = 0x96,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Operations {
|
/// Turns the byte into its full IOCTL representation
|
||||||
/// Turns the byte into its full IOCTL representation
|
pub fn op_to_ioctl(op: Operation) -> u64 {
|
||||||
pub fn to_full(&self) -> u64 {
|
let value = op.to_u8().unwrap();
|
||||||
let value = self.to_u8().unwrap();
|
|
||||||
|
|
||||||
((IOC_BYTE as u64) << 8) + value as u64
|
((IOC_BYTE as u64) << 8) + value as u64
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drive status possibilities returned by CDROM_DRIVE_STATUS ioctl
|
/// Drive status possibilities returned by CDROM_DRIVE_STATUS ioctl
|
||||||
#[derive(FromPrimitive, ToPrimitive)]
|
#[derive(FromPrimitive, ToPrimitive)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
NoInfo = 0,
|
NoInfo = 0,
|
||||||
NoDisc = 1,
|
NoDisc = 1,
|
||||||
|
@ -155,3 +155,88 @@ pub enum Status {
|
||||||
DriveNotReady = 3,
|
DriveNotReady = 3,
|
||||||
DiscOK = 4
|
DiscOK = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disc status possibilities returned by CDROM_DISC_STATUS ioctl
|
||||||
|
#[derive(FromPrimitive, ToPrimitive)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum DiscStatus {
|
||||||
|
NoInfo = 0,
|
||||||
|
Audio = 100,
|
||||||
|
Data1 = 101,
|
||||||
|
Data2 = 102,
|
||||||
|
XA21 = 103,
|
||||||
|
XA22 = 104,
|
||||||
|
Mixed = 105,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CD_MINS: i32 = 74;
|
||||||
|
pub const CD_SECS: i32 = 60;
|
||||||
|
pub const CD_FRAMES: i32 = 75;
|
||||||
|
pub const CD_SYNC_SIZE: i32 = 12;
|
||||||
|
pub const CD_MSF_OFFSET: i32 = 150;
|
||||||
|
pub const CD_CHUNK_SIZE: i32 = 24;
|
||||||
|
pub const CD_NUM_OF_CHUNKS: i32 = 98;
|
||||||
|
pub const CD_FRAMESIZE_SUB: i32 = 96;
|
||||||
|
pub const CD_HEAD_SIZE: i32 = 4;
|
||||||
|
pub const CD_SUBHEAD_SIZE: i32 = 8;
|
||||||
|
pub const CD_EDC_SIZE: i32 = 4;
|
||||||
|
pub const CD_ZERO_SIZE: i32 = 8;
|
||||||
|
pub const CD_ECC_SIZE: i32 = 276;
|
||||||
|
pub const CD_FRAMESIZE: i32 = 2048;
|
||||||
|
pub const CD_FRAMESIZE_RAW: i32 = 2352;
|
||||||
|
pub const CD_FRAMESIZE_RAWER: i32 = 2646;
|
||||||
|
pub const CD_FRAMESIZE_RAW1: i32 = CD_FRAMESIZE_RAW - CD_SYNC_SIZE;
|
||||||
|
pub const CD_FRAMESIZE_RAW0: i32 = CD_FRAMESIZE_RAW - CD_SYNC_SIZE - CD_HEAD_SIZE;
|
||||||
|
|
||||||
|
pub const CD_XA_HEAD: i32 = CD_HEAD_SIZE + CD_SUBHEAD_SIZE;
|
||||||
|
pub const CD_XA_TAIL: i32 = CD_EDC_SIZE + CD_ECC_SIZE;
|
||||||
|
pub const CD_XA_SYNC_HEAD: i32 = CD_SYNC_SIZE + CD_XA_HEAD;
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(FromPrimitive, ToPrimitive)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AddressType {
|
||||||
|
Lba = 0x01,
|
||||||
|
Msf = 0x02,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromPrimitive, ToPrimitive)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AudioStates {
|
||||||
|
Invalid = 0x00,
|
||||||
|
Play = 0x11,
|
||||||
|
Paused = 0x12,
|
||||||
|
Completed = 0x13,
|
||||||
|
Error = 0x14,
|
||||||
|
NoStatus = 0x15,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Capability {
|
||||||
|
CloseTray = 0x01,
|
||||||
|
OpenTray = 0x02,
|
||||||
|
Lock = 0x04,
|
||||||
|
SelectSpeed = 0x08,
|
||||||
|
SelectDisc = 0x10,
|
||||||
|
MultiSession = 0x20,
|
||||||
|
Mcn = 0x40,
|
||||||
|
MediaChanged = 0x80,
|
||||||
|
PlayAudio = 0x100,
|
||||||
|
Reset = 0x200,
|
||||||
|
DriveStatus = 0x800,
|
||||||
|
GenericPacket = 0x1000,
|
||||||
|
CdR = 0x2000,
|
||||||
|
CdRW = 0x4000,
|
||||||
|
Dvd = 0x8000,
|
||||||
|
DvdR = 0x10000,
|
||||||
|
DvdRam = 0x20000,
|
||||||
|
MODrive = 0x40000,
|
||||||
|
Mrw = 0x80000,
|
||||||
|
MrwW = 0x1000000,
|
||||||
|
Ram = 0x2000000,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Capability {
|
||||||
|
pub fn compare() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
349
src/main.rs
349
src/main.rs
|
@ -1,65 +1,336 @@
|
||||||
mod constants;
|
mod constants;
|
||||||
mod structures;
|
mod structures;
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::fd::RawFd;
|
||||||
use std::os::{fd::IntoRawFd, unix::fs::OpenOptionsExt};
|
use std::os::{fd::IntoRawFd, unix::fs::OpenOptionsExt};
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
|
use std::ptr::addr_of_mut;
|
||||||
|
|
||||||
use constants::{Operations, IOC_BYTE};
|
use constants::{op_to_ioctl, AddressType, DiscStatus, Operation, Status};
|
||||||
use nix::{ioctl_none, ioctl_none_bad, ioctl_readwrite_bad, libc};
|
use nix::errno::Errno;
|
||||||
|
use nix::{ioctl_none_bad, ioctl_read_bad, ioctl_readwrite_bad, ioctl_write_int_bad, libc};
|
||||||
|
|
||||||
use nix::libc::c_int;
|
|
||||||
use num_traits::FromPrimitive as _;
|
use num_traits::FromPrimitive as _;
|
||||||
use structures::{Addr, Msf0, ReadAudio};
|
use structures::{Addr, AddrUnion, Msf, MsfLong, RawResult, ReadAudio, TocEntry, TocHeader, _TocEntry};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate num_derive;
|
extern crate num_derive;
|
||||||
|
|
||||||
ioctl_none_bad!(cdrom_stop, Operations::to_full(&Operations::Stop));
|
|
||||||
ioctl_none_bad!(cdrom_start, Operations::to_full(&Operations::Start));
|
|
||||||
ioctl_none_bad!(cdrom_eject, Operations::to_full(&Operations::Eject));
|
|
||||||
ioctl_none_bad!(cdrom_close_tray, Operations::to_full(&Operations::CloseTray));
|
|
||||||
ioctl_none_bad!(cdrom_status, Operations::to_full(&Operations::DriveStatus));
|
|
||||||
ioctl_readwrite_bad!(cdrom_read_audio, Operations::to_full(&Operations::ReadAudio), structures::ReadAudio);
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
const FRAMES: i32 = 75;
|
let mut cd_rom = CDRom::new().unwrap();
|
||||||
const BYTES_PER_FRAME: i32 = 2352;
|
|
||||||
|
|
||||||
let time = Msf0 {
|
let status = cd_rom.status().unwrap();
|
||||||
minute: 2,
|
println!("Drive status: {:?}", status);
|
||||||
second: 45,
|
cd_rom.close().unwrap();
|
||||||
frame: 0,
|
|
||||||
|
println!("Disc status: {:?}", cd_rom.disc_type());
|
||||||
|
|
||||||
|
let header = cd_rom.toc_header().unwrap();
|
||||||
|
for i in header.first_track..header.last_track {
|
||||||
|
let entry = cd_rom.toc_entry(i);
|
||||||
|
println!("{:?}", entry.addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
cd_rom.set_lock(false).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rip_cd() {
|
||||||
|
let mut cd_rom = CDRom::new().unwrap();
|
||||||
|
|
||||||
|
println!("Drive status: {:?}", cd_rom.status().unwrap());
|
||||||
|
println!("Disc status: {:?}", cd_rom.disc_type().unwrap());
|
||||||
|
|
||||||
|
let spec = hound::WavSpec {
|
||||||
|
channels: 2,
|
||||||
|
sample_rate: 44100,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
sample_format: hound::SampleFormat::Int,
|
||||||
|
};
|
||||||
|
let mut writer = hound::WavWriter::create("cd_audio.wav", spec).unwrap();
|
||||||
|
let mut buf = vec![0i16; (75 * constants::CD_FRAMESIZE_RAW as usize) / 2];
|
||||||
|
|
||||||
|
// Checksum calculator
|
||||||
|
let mut context = md5::Context::new();
|
||||||
|
|
||||||
|
println!("Begin ripping\n");
|
||||||
|
for i in 0..4500u32 {
|
||||||
|
let minute = ((i + 2) / 60) as u8;
|
||||||
|
let second = ((i + 2) % 60) as u8;
|
||||||
|
print!("Reading {minute:02}:{second:02}\r");
|
||||||
|
std::io::stdout().flush().unwrap();
|
||||||
|
|
||||||
|
match cd_rom.read_audio_into(
|
||||||
|
Addr::Msf(Msf { minute, second, frame: 0 }),
|
||||||
|
constants::CD_FRAMES as usize,
|
||||||
|
&mut buf,
|
||||||
|
) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
let address = Addr {
|
let audio_u8: Vec<u8> = buf.iter().flat_map(|s| s.to_le_bytes()).collect();
|
||||||
msf: time,
|
context.consume(audio_u8);
|
||||||
};
|
|
||||||
|
|
||||||
let mut buff = [0u8; FRAMES as usize * BYTES_PER_FRAME as usize]; //Frames per second (75) * bytes per frame (2352)
|
let mut writer = writer.get_i16_writer(buf.len() as u32);
|
||||||
|
for sample in &buf {
|
||||||
|
writer.write_sample(*sample);
|
||||||
|
}
|
||||||
|
writer.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let mut ra = ReadAudio {
|
let sum = context.compute();
|
||||||
addr: address,
|
let mut string_sum = String::new();
|
||||||
addr_format: 0x02,
|
sum.iter().for_each(|b| string_sum.push_str(format!("{:0x}", b).as_str()));
|
||||||
nframes: FRAMES,
|
println!("\nFinished!\n\n\tChecksum: {}", string_sum);
|
||||||
buf: buff.as_mut_ptr()
|
|
||||||
};
|
|
||||||
|
|
||||||
let cdrom = OpenOptions::new()
|
writer.flush().unwrap();
|
||||||
|
writer.finalize().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access to a CD-ROM drive on the system.
|
||||||
|
pub struct CDRom {
|
||||||
|
drive_fd: RawFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Clone)]
|
||||||
|
pub enum CDRomError {
|
||||||
|
#[error("internal system error")]
|
||||||
|
Errno(#[from] nix::errno::Errno),
|
||||||
|
|
||||||
|
#[error("no disc in drive to read")]
|
||||||
|
NoDisc,
|
||||||
|
|
||||||
|
#[error("the CD does not contain cd-audio")]
|
||||||
|
NotAudioCD,
|
||||||
|
|
||||||
|
#[error("the drive's door is locked for some reason")]
|
||||||
|
DoorLocked,
|
||||||
|
|
||||||
|
#[error("this drive does not support the function")]
|
||||||
|
Unsupported,
|
||||||
|
|
||||||
|
#[error("the drive is in use by another user")]
|
||||||
|
Busy,
|
||||||
|
}
|
||||||
|
|
||||||
|
ioctl_none_bad!(cdrom_stop, op_to_ioctl(Operation::Stop));
|
||||||
|
ioctl_none_bad!(cdrom_start, op_to_ioctl(Operation::Start));
|
||||||
|
ioctl_none_bad!(cdrom_eject, op_to_ioctl(Operation::Eject));
|
||||||
|
ioctl_write_int_bad!(cdrom_lock_door, op_to_ioctl(Operation::LockDoor));
|
||||||
|
ioctl_none_bad!(cdrom_close_tray, op_to_ioctl(Operation::CloseTray));
|
||||||
|
ioctl_none_bad!(cdrom_status, op_to_ioctl(Operation::DriveStatus));
|
||||||
|
ioctl_none_bad!(cdrom_disc_status, op_to_ioctl(Operation::DiscStatus));
|
||||||
|
ioctl_readwrite_bad!(cdrom_read_audio, op_to_ioctl(Operation::ReadAudio), structures::ReadAudio);
|
||||||
|
ioctl_readwrite_bad!(cdrom_read_raw, op_to_ioctl(Operation::ReadRaw), structures::RawResult);
|
||||||
|
ioctl_read_bad!(cdrom_get_mcn, op_to_ioctl(Operation::GetMcn), [u8; 14]);
|
||||||
|
ioctl_read_bad!(cdrom_read_toc_header, op_to_ioctl(Operation::ReadTocHeader), structures::TocHeader);
|
||||||
|
ioctl_read_bad!(cdrom_read_toc_entry, op_to_ioctl(Operation::ReadTocEntry), structures::_TocEntry);
|
||||||
|
|
||||||
|
impl CDRom {
|
||||||
|
/// Creates a new interface to a system CD-ROM drive.
|
||||||
|
pub fn new() -> Option<Self> {
|
||||||
|
let drive_file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.custom_flags(libc::O_NONBLOCK | libc::O_RDONLY)
|
.custom_flags(libc::O_NONBLOCK | libc::O_RDONLY)
|
||||||
.open("/dev/sr0")
|
.open("/dev/sr0")
|
||||||
.unwrap();
|
.ok()?;
|
||||||
let cdrom_fd: c_int = cdrom.into_raw_fd();
|
|
||||||
|
|
||||||
unsafe {
|
Some(Self {
|
||||||
let result = constants::Status::from_i32(cdrom_status(cdrom_fd).unwrap()).unwrap();
|
drive_fd: drive_file.into_raw_fd(),
|
||||||
dbg!(result);
|
})
|
||||||
//dbg!(cdrom_start(cdrom_fd).unwrap_or_default());
|
|
||||||
|
|
||||||
//std::thread::sleep(std::time::Duration::from_secs(1));
|
|
||||||
|
|
||||||
dbg!(cdrom_read_audio(cdrom_fd, std::ptr::addr_of_mut!(ra)).unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{:02X?}", &buff[0..10]);
|
/// Get the currently reported status of the drive.
|
||||||
|
pub fn status(&mut self) -> Option<Status> {
|
||||||
|
let status = unsafe {
|
||||||
|
cdrom_status(self.drive_fd).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
Status::from_i32(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the type of disc currently in the drive
|
||||||
|
pub fn disc_type(&mut self) -> Option<DiscStatus> {
|
||||||
|
let status = unsafe {
|
||||||
|
cdrom_disc_status(self.drive_fd).ok()?
|
||||||
|
};
|
||||||
|
|
||||||
|
DiscStatus::from_i32(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the Media Catalog Number of the current disc.
|
||||||
|
///
|
||||||
|
/// Many discs do not contain this information.
|
||||||
|
pub fn mcn(&mut self) -> Option<String> {
|
||||||
|
let mut buffer = [0u8; 14];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
cdrom_get_mcn(self.drive_fd, addr_of_mut!(buffer)).ok()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = String::from_utf8_lossy(&buffer[..buffer.len() - 1]).into_owned();
|
||||||
|
Some(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toc_header(&mut self) -> Result<TocHeader, CDRomError> {
|
||||||
|
let mut header = TocHeader::default();
|
||||||
|
|
||||||
|
if unsafe {
|
||||||
|
cdrom_read_toc_header(self.drive_fd, addr_of_mut!(header))
|
||||||
|
}.is_err_and(|e| e == Errno::ENOMEDIUM) {
|
||||||
|
return Err(CDRomError::NoDisc)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toc_entry(&mut self, index: u8) -> TocEntry {
|
||||||
|
let mut header = _TocEntry::default();
|
||||||
|
header.track = index;
|
||||||
|
header.format = AddressType::Lba as u8;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
cdrom_read_toc_entry(self.drive_fd, addr_of_mut!(header)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = TocEntry {
|
||||||
|
track: header.track,
|
||||||
|
adr: header.adr_ctrl >> 4,
|
||||||
|
ctrl: header.adr_ctrl & 0x0F,
|
||||||
|
addr: unsafe {
|
||||||
|
match header.format {
|
||||||
|
d if d == AddressType::Lba as u8 => Addr::Lba(header.addr.lba),
|
||||||
|
d if d == AddressType::Msf as u8 => Addr::Msf(header.addr.msf),
|
||||||
|
_ => panic!("Impossible value returned!")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
datamode: header.datamode,
|
||||||
|
};
|
||||||
|
|
||||||
|
entry
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lock(&mut self, locked: bool) -> Result<(), CDRomError> {
|
||||||
|
let result = match unsafe {
|
||||||
|
cdrom_lock_door(self.drive_fd, locked as i32)
|
||||||
|
} {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => match e {
|
||||||
|
Errno::EBUSY => return Err(CDRomError::Busy),
|
||||||
|
_ => return Err(CDRomError::Errno(e)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
constants::EDRIVE_CANT_DO_THIS => Err(CDRomError::Unsupported),
|
||||||
|
_ => Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eject(&mut self) -> Result<(), CDRomError> {
|
||||||
|
let status = unsafe {
|
||||||
|
cdrom_eject(self.drive_fd).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
if status == 2 {
|
||||||
|
return Err(CDRomError::DoorLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&mut self) -> Result<(), CDRomError> {
|
||||||
|
let status = unsafe {
|
||||||
|
cdrom_close_tray(self.drive_fd).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
match status {
|
||||||
|
d if d == Errno::ENOSYS as i32 => Err(CDRomError::Unsupported),
|
||||||
|
libc::EBUSY => Err(CDRomError::DoorLocked),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read audio from the CD.
|
||||||
|
///
|
||||||
|
/// The buffer will be constructed automatically.
|
||||||
|
pub fn read_audio(&mut self, address: Addr, frames: usize) -> Result<Vec<i16>, CDRomError> {
|
||||||
|
let mut buf = vec![0i16; (frames * constants::CD_FRAMESIZE_RAW as usize) / 2];
|
||||||
|
|
||||||
|
self.read_audio_into(address, frames, &mut buf)?;
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read audio from the CD into a preallocated buffer.
|
||||||
|
///
|
||||||
|
/// The buffer must be large enough to hold the audio for all the frames you want to read.
|
||||||
|
/// Since the values are [`i16`]s, the equation for the buffer size is `(n_frames * 2352) / 2`
|
||||||
|
pub fn read_audio_into(&mut self, address: Addr, frames: usize, buf: &mut [i16]) -> Result<(), CDRomError> {
|
||||||
|
let (addr, addr_format) = match address {
|
||||||
|
Addr::Lba(lba) => (AddrUnion { lba }, AddressType::Lba),
|
||||||
|
Addr::Msf(msf) => {
|
||||||
|
if msf.minute == 0 && msf.second < 2 {
|
||||||
|
panic!("MSF second cannot be less than 2!")
|
||||||
|
}
|
||||||
|
|
||||||
|
(AddrUnion { msf }, AddressType::Msf)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if frames < 1 || frames > 75 {
|
||||||
|
panic!("Invalid number of frames!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.len() < (frames * constants::CD_FRAMESIZE_RAW as usize) / 2 {
|
||||||
|
panic!("Buffer is too small!")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ra = ReadAudio {
|
||||||
|
addr,
|
||||||
|
addr_format,
|
||||||
|
nframes: frames as i32,
|
||||||
|
buf: buf.as_mut_ptr()
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = unsafe {
|
||||||
|
cdrom_read_audio(self.drive_fd, addr_of_mut!(ra))
|
||||||
|
}?;
|
||||||
|
|
||||||
|
if status != 0 {
|
||||||
|
return Err(Errno::from_raw(status).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_raw(&mut self, address: Msf) -> Box<[u8; 2352]> {
|
||||||
|
// TODO: Make this take LBA values too
|
||||||
|
// MSF values are converted to LBA values via this formula:
|
||||||
|
// lba = (((m * CD_SECS) + s) * CD_FRAMES + f) - CD_MSF_OFFSET;
|
||||||
|
// <https://docs.kernel.org/userspace-api/ioctl/cdrom.html>
|
||||||
|
|
||||||
|
if address.minute == 0 && address.second < 2 {
|
||||||
|
panic!("MSF second cannot be less than 2!")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut argument = RawResult {
|
||||||
|
cdrom_msf: MsfLong {
|
||||||
|
min0: address.minute,
|
||||||
|
sec0: address.second,
|
||||||
|
frame0: address.frame,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = unsafe {
|
||||||
|
cdrom_read_raw(self.drive_fd, addr_of_mut!(argument)).unwrap();
|
||||||
|
|
||||||
|
argument.buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::new(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use std::ffi::c_int;
|
use std::{ffi::c_int, mem};
|
||||||
|
|
||||||
|
use crate::constants::AddressType;
|
||||||
|
|
||||||
/// Address in MSF format
|
/// Address in MSF format
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Msf0 {
|
pub struct Msf {
|
||||||
pub minute: u8,
|
pub minute: u8,
|
||||||
pub second: u8,
|
pub second: u8,
|
||||||
pub frame: u8,
|
pub frame: u8,
|
||||||
|
@ -12,15 +14,21 @@ pub struct Msf0 {
|
||||||
/// Address in either MSF or logical format
|
/// Address in either MSF or logical format
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub union Addr {
|
pub union AddrUnion {
|
||||||
pub lba: c_int,
|
pub lba: c_int,
|
||||||
pub msf: Msf0,
|
pub msf: Msf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Addr {
|
||||||
|
Lba(i32),
|
||||||
|
Msf(Msf),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct is used by [`crate::constants::PLAY_MSF`]
|
/// This struct is used by [`crate::constants::PLAY_MSF`]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct Msf {
|
pub struct MsfLong {
|
||||||
/// Start minute
|
/// Start minute
|
||||||
pub min0: u8,
|
pub min0: u8,
|
||||||
/// Start second
|
/// Start second
|
||||||
|
@ -37,11 +45,12 @@ pub struct Msf {
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub union RawResult {
|
pub union RawResult {
|
||||||
pub cdrom_msf: Msf,
|
pub cdrom_msf: MsfLong,
|
||||||
pub buffer: [u8; 2646],
|
pub buffer: [u8; 2352],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct is used by [`crate::constants::PLAY_TRACK_INDEX`]
|
/// This struct is used by [`crate::constants::PLAY_TRACK_INDEX`]
|
||||||
|
#[repr(C)]
|
||||||
struct TrackIndex {
|
struct TrackIndex {
|
||||||
/// Start track
|
/// Start track
|
||||||
trk0: u8,
|
trk0: u8,
|
||||||
|
@ -54,9 +63,45 @@ struct TrackIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct is used by [`crate::constants::READ_TOC_HEADER`]
|
/// This struct is used by [`crate::constants::READ_TOC_HEADER`]
|
||||||
struct TocHeader {
|
#[repr(C)]
|
||||||
trk0: u8,
|
#[derive(Default, Debug)]
|
||||||
trk1: u8,
|
pub struct TocHeader {
|
||||||
|
pub first_track: u8,
|
||||||
|
pub last_track: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This struct is used by the [`crate::constants::READTOCENTRY`] ioctl
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct _TocEntry {
|
||||||
|
pub track: u8,
|
||||||
|
pub adr_ctrl: u8,
|
||||||
|
pub format: u8,
|
||||||
|
pub addr: AddrUnion,
|
||||||
|
pub datamode: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for _TocEntry {
|
||||||
|
fn default() -> Self {
|
||||||
|
unsafe {
|
||||||
|
Self {
|
||||||
|
track: 0,
|
||||||
|
adr_ctrl: 0,
|
||||||
|
format: AddressType::Msf as u8,
|
||||||
|
addr: mem::zeroed(),
|
||||||
|
datamode: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually public version of [`_TocEntry`].
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TocEntry {
|
||||||
|
pub track: u8,
|
||||||
|
pub adr: u8,
|
||||||
|
pub ctrl: u8,
|
||||||
|
pub addr: Addr,
|
||||||
|
pub datamode: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VolCtl {
|
struct VolCtl {
|
||||||
|
@ -67,11 +112,19 @@ struct VolCtl {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct ReadAudio {
|
pub struct ReadAudio {
|
||||||
/// Frame address
|
/// Frame address
|
||||||
pub addr: Addr,
|
pub addr: AddrUnion,
|
||||||
/// CDROM_LBA or CDROM_MSF
|
/// CDROM_LBA or CDROM_MSF
|
||||||
pub addr_format: u8,
|
pub addr_format: AddressType,
|
||||||
/// Number of 2352-byte-frames to read at once
|
/// Number of 2352-byte-frames to read at once
|
||||||
pub nframes: i32,
|
pub nframes: i32,
|
||||||
/// Pointer to frame buffer (size: nframes*2352 bytes)
|
/// Pointer to frame buffer (size: nframes*2352 bytes)
|
||||||
pub buf: *mut u8,
|
pub buf: *mut i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct is used with the CDROM_GET_MCN ioctl.
|
||||||
|
/// Very few audio discs actually have Universal Product Code information,
|
||||||
|
/// which should just be the Medium Catalog Number on the box. Also note
|
||||||
|
/// that the way the codeis written on CD is _not_ uniform across all discs!
|
||||||
|
struct Mcn {
|
||||||
|
medium_catalog_number: [u8; 14]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue