use crate::cd_ecc::{ecc_clear, ecc_generate, ecc_verify, CD_SYNC_HEADER};
use crate::error::ChdError;
use crate::{CD_FRAME_SIZE, CD_SECTOR_SIZE, CD_SUBCODE_SIZE};
#[inline]
pub fn flac_block_size(bytes: u32) -> u32 {
let mut hunkbytes = bytes / 4;
while hunkbytes > 2048 {
hunkbytes /= 2;
}
hunkbytes
}
#[inline]
pub fn cdfl_block_size(bytes: u32) -> u32 {
let mut hunkbytes = bytes / 4;
while hunkbytes > 2352 {
hunkbytes /= 2;
}
hunkbytes
}
#[cfg(feature = "zlib")]
thread_local! {
static ZLIB_DECOMPRESSOR: std::cell::RefCell<flate2::Decompress> =
std::cell::RefCell::new(flate2::Decompress::new(false));
}
#[cfg(feature = "zlib")]
pub fn decompress_zlib(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
ZLIB_DECOMPRESSOR.with(|d| {
let mut decompressor = d.borrow_mut();
decompressor.reset(false);
match decompressor.decompress(src, dest, flate2::FlushDecompress::Finish) {
Ok(flate2::Status::StreamEnd) => {
if decompressor.total_out() as usize == dest.len() {
Ok(())
} else {
Err(ChdError::Codec(format!(
"zlib decompressed size mismatch: expected {}, got {}",
dest.len(),
decompressor.total_out()
)))
}
}
Ok(flate2::Status::Ok | flate2::Status::BufError) => Err(ChdError::Codec(
"zlib stream did not finish or buffer error".into(),
)),
Err(e) => Err(ChdError::Codec(format!("zlib decompression error: {}", e))),
}
})
}
#[cfg(feature = "lzma")]
thread_local! {
static LZMA_DECOMP_SCRATCH: std::cell::RefCell<Vec<u8>> =
std::cell::RefCell::new(Vec::with_capacity(32768));
}
#[cfg(feature = "lzma")]
pub fn decompress_lzma(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
let mut header = [0u8; 13];
header[0] = 0x5d; let dict_size = dest.len() as u32;
header[1..5].copy_from_slice(&dict_size.to_le_bytes());
let uncomp_size = dest.len() as u64;
header[5..13].copy_from_slice(&uncomp_size.to_le_bytes());
LZMA_DECOMP_SCRATCH.with(|scratch_cell| -> Result<(), ChdError> {
let mut input_buf = scratch_cell.borrow_mut();
input_buf.clear();
input_buf.reserve(13 + src.len());
input_buf.extend_from_slice(&header);
input_buf.extend_from_slice(src);
let mut input_cursor = std::io::Cursor::new(input_buf.as_slice());
let mut dest_cursor = std::io::Cursor::new(dest);
lzma_rs::lzma_decompress(&mut input_cursor, &mut dest_cursor)
.map_err(|e| ChdError::Codec(format!("lzma decompression error: {:?}", e)))?;
Ok(())
})
}
pub fn decompress_huffman(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
let mut bitstream = chdlady_huffman::BitReader::new(src);
let mut decoder = chdlady_huffman::HuffmanDecoder::<256, 16>::new();
decoder.import_tree_huffman(&mut bitstream)?;
for byte in dest.iter_mut() {
*byte = decoder.decode_one(&mut bitstream) as u8;
}
bitstream.flush();
if bitstream.overflow() {
return Err(ChdError::Codec("huffman bitstream overflow".into()));
}
Ok(())
}
#[cfg(feature = "zstd")]
pub fn decompress_zstd(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
let bytes = zstd::bulk::decompress_to_buffer(src, dest)
.map_err(|e| ChdError::Codec(format!("zstd decompression error: {}", e)))?;
if bytes != dest.len() {
return Err(ChdError::Codec(format!(
"zstd decompressed size mismatch: expected {}, got {}",
dest.len(),
bytes
)));
}
Ok(())
}
struct CdDecompScratch {
base: Vec<u8>,
subcode: Vec<u8>,
}
thread_local! {
static CD_DECOMP_SCRATCH: std::cell::RefCell<CdDecompScratch> =
std::cell::RefCell::new(CdDecompScratch {
base: Vec::with_capacity(8 * CD_SECTOR_SIZE),
subcode: Vec::with_capacity(8 * CD_SUBCODE_SIZE),
});
}
pub fn decompress_cd_with_subcode<F1, F2>(
src: &[u8],
dest: &mut [u8],
mut base_decompress: F1,
mut subcode_decompress: F2,
) -> Result<(), ChdError>
where
F1: FnMut(&[u8], &mut [u8]) -> Result<(), ChdError>,
F2: FnMut(&[u8], &mut [u8]) -> Result<(), ChdError>,
{
if !dest.len().is_multiple_of(CD_FRAME_SIZE) {
return Err(ChdError::Codec(
"destination length must be a multiple of CD_FRAME_SIZE".into(),
));
}
let frames = dest.len() / CD_FRAME_SIZE;
let complen_bytes = if dest.len() < 65536 { 2 } else { 3 };
let ecc_bytes = frames.div_ceil(8);
let header_bytes = ecc_bytes + complen_bytes;
if src.len() < header_bytes {
return Err(ChdError::Codec(
"source buffer too small for CD header".into(),
));
}
let complen_base = if complen_bytes > 2 {
((src[ecc_bytes] as usize) << 16)
| ((src[ecc_bytes + 1] as usize) << 8)
| (src[ecc_bytes + 2] as usize)
} else {
((src[ecc_bytes] as usize) << 8) | (src[ecc_bytes + 1] as usize)
};
if header_bytes + complen_base > src.len() {
return Err(ChdError::Codec(
"base compressed length exceeds source buffer".into(),
));
}
CD_DECOMP_SCRATCH.with(|scratch_cell| -> Result<(), ChdError> {
let mut scratch = scratch_cell.borrow_mut();
let CdDecompScratch { base, subcode } = &mut *scratch;
let base_len = frames * CD_SECTOR_SIZE;
if base.len() < base_len {
base.resize(base_len, 0);
}
let subcode_len = frames * CD_SUBCODE_SIZE;
if subcode.len() < subcode_len {
subcode.resize(subcode_len, 0);
}
base_decompress(
&src[header_bytes..header_bytes + complen_base],
&mut base[..base_len],
)?;
subcode_decompress(
&src[header_bytes + complen_base..],
&mut subcode[..subcode_len],
)?;
for framenum in 0..frames {
let dest_offset = framenum * CD_FRAME_SIZE;
let base_offset = framenum * CD_SECTOR_SIZE;
let subcode_offset = framenum * CD_SUBCODE_SIZE;
dest[dest_offset..dest_offset + CD_SECTOR_SIZE]
.copy_from_slice(&base[base_offset..base_offset + CD_SECTOR_SIZE]);
dest[dest_offset + CD_SECTOR_SIZE..dest_offset + CD_FRAME_SIZE]
.copy_from_slice(&subcode[subcode_offset..subcode_offset + CD_SUBCODE_SIZE]);
let ecc_flag = (src[framenum / 8] & (1 << (framenum % 8))) != 0;
if ecc_flag {
let sector = &mut dest[dest_offset..dest_offset + CD_SECTOR_SIZE];
sector[0..12].copy_from_slice(&CD_SYNC_HEADER);
ecc_generate(sector);
}
}
Ok(())
})
}
#[cfg(feature = "zlib")]
pub fn decompress_cd<F>(src: &[u8], dest: &mut [u8], base_decompress: F) -> Result<(), ChdError>
where
F: FnMut(&[u8], &mut [u8]) -> Result<(), ChdError>,
{
decompress_cd_with_subcode(src, dest, base_decompress, decompress_zlib)
}
#[cfg(feature = "zlib")]
pub fn decompress_cdzl(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
decompress_cd(src, dest, decompress_zlib)
}
#[cfg(all(feature = "lzma", feature = "zlib"))]
pub fn decompress_cdlz(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
decompress_cd(src, dest, decompress_lzma)
}
#[cfg(feature = "zstd")]
pub fn decompress_cdzs(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
decompress_cd_with_subcode(src, dest, decompress_zstd, decompress_zstd)
}
#[cfg(feature = "flac-read")]
pub fn decompress_flac(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
if src.is_empty() {
return Err(ChdError::Codec("empty FLAC source buffer".into()));
}
let is_little_endian = match src[0] {
b'L' => true,
b'B' => false,
_ => return Err(ChdError::Codec("invalid FLAC endian prefix".into())),
};
if !dest.len().is_multiple_of(4) {
return Err(ChdError::Codec(
"FLAC destination length must be multiple of 4 (16-bit stereo)".into(),
));
}
let total_samples = dest.len() / 4;
let mut reader = claxon::frame::FrameReader::new(std::io::Cursor::new(&src[1..]));
let mut buffer = Vec::new();
let mut samples_decoded = 0;
while samples_decoded < total_samples {
let block = reader
.read_next_or_eof(buffer)
.map_err(|e| ChdError::Codec(format!("FLAC decode error: {:?}", e)))?
.ok_or_else(|| ChdError::Codec("unexpected EOF in FLAC stream".into()))?;
for (l, r) in block.stereo_samples() {
if samples_decoded >= total_samples {
break;
}
let offset = samples_decoded * 4;
if is_little_endian {
dest[offset..offset + 2].copy_from_slice(&(l as i16).to_le_bytes());
dest[offset + 2..offset + 4].copy_from_slice(&(r as i16).to_le_bytes());
} else {
dest[offset..offset + 2].copy_from_slice(&(l as i16).to_be_bytes());
dest[offset + 2..offset + 4].copy_from_slice(&(r as i16).to_be_bytes());
}
samples_decoded += 1;
}
buffer = block.into_buffer();
}
Ok(())
}
#[cfg(all(feature = "flac-read", feature = "zlib"))]
pub fn decompress_cdfl(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
if !dest.len().is_multiple_of(CD_FRAME_SIZE) {
return Err(ChdError::Codec(
"destination length must be a multiple of CD_FRAME_SIZE".into(),
));
}
let frames = dest.len() / CD_FRAME_SIZE;
let total_samples = frames * (CD_SECTOR_SIZE / 4);
let mut reader = claxon::frame::FrameReader::new(std::io::Cursor::new(src));
let mut buffer = Vec::new();
let mut samples_decoded = 0;
while samples_decoded < total_samples {
let block = reader
.read_next_or_eof(buffer)
.map_err(|e| ChdError::Codec(format!("CD FLAC decode error: {:?}", e)))?
.ok_or_else(|| ChdError::Codec("unexpected EOF in CD FLAC stream".into()))?;
for (l, r) in block.stereo_samples() {
if samples_decoded >= total_samples {
break;
}
let framenum = samples_decoded / (CD_SECTOR_SIZE / 4);
let sample_in_frame = samples_decoded % (CD_SECTOR_SIZE / 4);
let offset = framenum * CD_FRAME_SIZE + sample_in_frame * 4;
dest[offset..offset + 2].copy_from_slice(&(l as i16).to_be_bytes());
dest[offset + 2..offset + 4].copy_from_slice(&(r as i16).to_be_bytes());
samples_decoded += 1;
}
buffer = block.into_buffer();
}
let cursor = reader.into_inner();
let flac_consumed = cursor.position() as usize;
if flac_consumed > src.len() {
return Err(ChdError::Codec(
"FLAC consumed more bytes than source".into(),
));
}
let mut subcode_buffer = vec![0u8; frames * CD_SUBCODE_SIZE];
decompress_zlib(&src[flac_consumed..], &mut subcode_buffer)?;
for framenum in 0..frames {
let dest_offset = framenum * CD_FRAME_SIZE + CD_SECTOR_SIZE;
let subcode_offset = framenum * CD_SUBCODE_SIZE;
dest[dest_offset..dest_offset + CD_SUBCODE_SIZE]
.copy_from_slice(&subcode_buffer[subcode_offset..subcode_offset + CD_SUBCODE_SIZE]);
}
Ok(())
}
pub fn decompress_hunk(codec: u32, src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
match &codec.to_be_bytes() {
#[cfg(feature = "zlib")]
b"zlib" => decompress_zlib(src, dest),
#[cfg(feature = "lzma")]
b"lzma" => decompress_lzma(src, dest),
#[cfg(feature = "zstd")]
b"zstd" => decompress_zstd(src, dest),
b"huff" => decompress_huffman(src, dest),
#[cfg(feature = "flac-read")]
b"flac" => decompress_flac(src, dest),
#[cfg(feature = "zlib")]
b"cdzl" => decompress_cdzl(src, dest),
#[cfg(all(feature = "lzma", feature = "zlib"))]
b"cdlz" => decompress_cdlz(src, dest),
#[cfg(feature = "zstd")]
b"cdzs" => decompress_cdzs(src, dest),
#[cfg(all(feature = "flac-read", feature = "zlib"))]
b"cdfl" => decompress_cdfl(src, dest),
#[cfg(feature = "avhu")]
b"avhu" => decompress_avhu(src, dest),
fourcc => Err(ChdError::Codec(format!(
"unsupported or uncompiled compressor: {:?}",
std::str::from_utf8(fourcc).unwrap_or("????")
))),
}
}
#[cfg(feature = "avhu")]
pub fn decompress_avhu(src: &[u8], dest: &mut [u8]) -> Result<(), ChdError> {
chdlady_av::avhuff::decompress(src, dest)
.map_err(|e| ChdError::Codec(format!("avhu decompression error: {}", e)))
}
#[cfg(feature = "zlib")]
thread_local! {
static ZLIB_COMPRESSOR: std::cell::RefCell<flate2::Compress> =
std::cell::RefCell::new(flate2::Compress::new(flate2::Compression::best(), false));
}
#[cfg(feature = "zlib")]
pub fn compress_zlib(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
dest.clear();
dest.reserve(src.len());
ZLIB_COMPRESSOR.with(|c| {
let mut compressor = c.borrow_mut();
compressor.reset();
let mut input_consumed = 0;
loop {
let before_in = compressor.total_in();
let status = compressor
.compress_vec(&src[input_consumed..], dest, flate2::FlushCompress::Finish)
.map_err(|e| ChdError::Codec(format!("zlib compress error: {}", e)))?;
let after_in = compressor.total_in();
input_consumed += (after_in - before_in) as usize;
match status {
flate2::Status::StreamEnd => break,
flate2::Status::Ok => {
dest.reserve(4096);
}
flate2::Status::BufError => {
dest.reserve(4096);
}
}
}
Ok(())
})
}
#[cfg(feature = "lzma")]
thread_local! {
static LZMA_COMP_SCRATCH: std::cell::RefCell<Vec<u8>> =
std::cell::RefCell::new(Vec::with_capacity(32768));
}
#[cfg(feature = "lzma")]
pub fn compress_lzma(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
let options = lzma_rs::compress::Options {
unpacked_size: lzma_rs::compress::UnpackedSize::SkipWritingToHeader,
};
LZMA_COMP_SCRATCH.with(|scratch_cell| -> Result<(), ChdError> {
let mut temp = scratch_cell.borrow_mut();
temp.clear();
let mut cursor = std::io::Cursor::new(src);
lzma_rs::lzma_compress_with_options(&mut cursor, &mut *temp, &options)
.map_err(|e| ChdError::Codec(format!("lzma compress error: {:?}", e)))?;
if temp.len() < 5 {
return Err(ChdError::Codec("lzma output too short".into()));
}
dest.clear();
dest.extend_from_slice(&temp[5..]);
Ok(())
})
}
#[cfg(feature = "zstd")]
pub fn compress_zstd(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
let compressed = zstd::bulk::compress(src, 19)
.map_err(|e| ChdError::Codec(format!("zstd compress error: {}", e)))?;
*dest = compressed;
Ok(())
}
pub fn compress_huffman(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
let compressed = chdlady_huffman::compress_huffman_8bit(src)
.map_err(|e| ChdError::Codec(format!("huffman compress error: {:?}", e)))?;
*dest = compressed;
Ok(())
}
struct CdCompScratch {
base_buffer: Vec<u8>,
subcode_buffer: Vec<u8>,
ecc_flags: Vec<u8>,
base_comp: Vec<u8>,
subcode_comp: Vec<u8>,
}
thread_local! {
static CD_COMP_SCRATCH: std::cell::RefCell<CdCompScratch> =
std::cell::RefCell::new(CdCompScratch {
base_buffer: Vec::with_capacity(8 * CD_SECTOR_SIZE),
subcode_buffer: Vec::with_capacity(8 * CD_SUBCODE_SIZE),
ecc_flags: Vec::with_capacity(1),
base_comp: Vec::with_capacity(8 * CD_SECTOR_SIZE),
subcode_comp: Vec::with_capacity(8 * CD_SUBCODE_SIZE),
});
}
pub fn compress_cd_with_subcode<F1, F2>(
src: &[u8],
dest: &mut Vec<u8>,
mut base_compress: F1,
mut subcode_compress: F2,
) -> Result<(), ChdError>
where
F1: FnMut(&[u8], &mut Vec<u8>) -> Result<(), ChdError>,
F2: FnMut(&[u8], &mut Vec<u8>) -> Result<(), ChdError>,
{
if !src.len().is_multiple_of(CD_FRAME_SIZE) {
return Err(ChdError::Codec(
"source length must be a multiple of CD_FRAME_SIZE".into(),
));
}
let frames = src.len() / CD_FRAME_SIZE;
let complen_bytes = if src.len() < 65536 { 2 } else { 3 };
let ecc_bytes = frames.div_ceil(8);
let header_bytes = ecc_bytes + complen_bytes;
CD_COMP_SCRATCH.with(|scratch_cell| -> Result<(), ChdError> {
let mut scratch = scratch_cell.borrow_mut();
let CdCompScratch {
base_buffer,
subcode_buffer,
ecc_flags,
base_comp,
subcode_comp,
} = &mut *scratch;
let base_len = frames * CD_SECTOR_SIZE;
if base_buffer.len() != base_len {
base_buffer.resize(base_len, 0);
}
let subcode_len = frames * CD_SUBCODE_SIZE;
if subcode_buffer.len() != subcode_len {
subcode_buffer.resize(subcode_len, 0);
}
if ecc_flags.len() != ecc_bytes {
ecc_flags.resize(ecc_bytes, 0);
} else {
ecc_flags.fill(0);
}
for framenum in 0..frames {
let frame_src = &src[framenum * CD_FRAME_SIZE..framenum * CD_FRAME_SIZE + CD_FRAME_SIZE];
let sector =
&mut base_buffer[framenum * CD_SECTOR_SIZE..framenum * CD_SECTOR_SIZE + CD_SECTOR_SIZE];
let subcode = &mut subcode_buffer
[framenum * CD_SUBCODE_SIZE..framenum * CD_SUBCODE_SIZE + CD_SUBCODE_SIZE];
sector.copy_from_slice(&frame_src[..CD_SECTOR_SIZE]);
subcode.copy_from_slice(&frame_src[CD_SECTOR_SIZE..]);
if sector.starts_with(&CD_SYNC_HEADER) && ecc_verify(sector) {
ecc_flags[framenum / 8] |= 1 << (framenum % 8);
sector[0..12].fill(0);
ecc_clear(sector);
}
}
base_comp.clear();
base_compress(base_buffer, base_comp)?;
if base_comp.len() >= src.len() {
return Err(ChdError::Codec(
"base compression exceeded source size".into(),
));
}
subcode_comp.clear();
subcode_compress(subcode_buffer, subcode_comp)?;
dest.clear();
dest.reserve(header_bytes + base_comp.len() + subcode_comp.len());
dest.extend_from_slice(ecc_flags);
if complen_bytes > 2 {
dest.push((base_comp.len() >> 16) as u8);
dest.push((base_comp.len() >> 8) as u8);
dest.push(base_comp.len() as u8);
} else {
dest.push((base_comp.len() >> 8) as u8);
dest.push(base_comp.len() as u8);
}
dest.extend_from_slice(base_comp);
dest.extend_from_slice(subcode_comp);
Ok(())
})
}
#[cfg(feature = "zlib")]
pub fn compress_cd<F>(src: &[u8], dest: &mut Vec<u8>, base_compress: F) -> Result<(), ChdError>
where
F: FnMut(&[u8], &mut Vec<u8>) -> Result<(), ChdError>,
{
compress_cd_with_subcode(src, dest, base_compress, compress_zlib)
}
#[cfg(feature = "zlib")]
pub fn compress_cdzl(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
compress_cd(src, dest, compress_zlib)
}
#[cfg(all(feature = "lzma", feature = "zlib"))]
pub fn compress_cdlz(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
compress_cd(src, dest, compress_lzma)
}
#[cfg(feature = "zstd")]
pub fn compress_cdzs(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
compress_cd_with_subcode(src, dest, compress_zstd, compress_zstd)
}
#[cfg(feature = "flac-write")]
static FLAC_ENCODER_CONFIG: std::sync::LazyLock<flacenc::error::Verified<flacenc::config::Encoder>> =
std::sync::LazyLock::new(|| {
use flacenc::error::Verify;
let mut cfg = flacenc::config::Encoder::default();
cfg.stereo_coding.use_leftside = false;
cfg.stereo_coding.use_rightside = false;
cfg.stereo_coding.use_midside = true;
cfg.subframe_coding.use_lpc = false;
cfg.into_verified().expect("valid flac encoder config")
});
#[cfg(feature = "flac-write")]
static FLAC_STREAM_INFO_CD: std::sync::LazyLock<flacenc::component::StreamInfo> =
std::sync::LazyLock::new(|| {
flacenc::component::StreamInfo::new(44100, 2, 16).expect("valid cd flac stream info")
});
#[cfg(feature = "flac-write")]
struct FlacScratch {
samples: Vec<i32>,
#[cfg(feature = "zlib")]
subcode: Vec<u8>,
#[cfg(feature = "zlib")]
subcode_comp: Vec<u8>,
fb: flacenc::source::FrameBuf,
sink: flacenc::bitsink::ByteSink,
}
#[cfg(feature = "flac-write")]
thread_local! {
static FLAC_SCRATCH: std::cell::RefCell<FlacScratch> = std::cell::RefCell::new(FlacScratch {
samples: Vec::with_capacity(4704 * 2),
#[cfg(feature = "zlib")]
subcode: Vec::with_capacity(8 * CD_SUBCODE_SIZE),
#[cfg(feature = "zlib")]
subcode_comp: Vec::with_capacity(1024),
fb: flacenc::source::FrameBuf::with_size(2, 2352).expect("valid framebuf"),
sink: flacenc::bitsink::ByteSink::with_capacity(2352 * 4 * 8),
});
}
#[cfg(feature = "flac-write")]
pub fn compress_flac(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
use flacenc::component::BitRepr;
use flacenc::source::Fill;
if src.is_empty() || !src.len().is_multiple_of(4) {
return Err(ChdError::Codec(
"FLAC source length must be a non-zero multiple of 4 (16-bit stereo)".into(),
));
}
let num_stereo_samples = src.len() / 4;
let block_size = flac_block_size(src.len() as u32) as usize;
dest.clear();
dest.reserve(src.len());
dest.push(b'B');
FLAC_SCRATCH.with(|scratch_cell| -> Result<(), ChdError> {
let mut scratch = scratch_cell.borrow_mut();
let FlacScratch {
samples,
fb,
sink,
..
} = &mut *scratch;
samples.clear();
samples.reserve(num_stereo_samples * 2);
for chunk in src.as_chunks::<4>().0 {
samples.push(i16::from_be_bytes([chunk[0], chunk[1]]) as i32);
samples.push(i16::from_be_bytes([chunk[2], chunk[3]]) as i32);
}
let mut samples_left = num_stereo_samples;
let mut sample_offset = 0;
let mut frame_idx = 0;
while samples_left > 0 {
let cur_block = block_size.min(samples_left);
if fb.size() != cur_block {
fb.resize(cur_block);
}
fb.fill_interleaved(&samples[sample_offset * 2..(sample_offset + cur_block) * 2])
.map_err(|e| ChdError::Codec(format!("FLAC fill error: {:?}", e)))?;
let flac_frame = flacenc::encode_fixed_size_frame(
&FLAC_ENCODER_CONFIG,
fb,
frame_idx,
&FLAC_STREAM_INFO_CD,
)
.map_err(|e| ChdError::Codec(format!("FLAC encode frame error: {:?}", e)))?;
sink.clear();
flac_frame
.write(sink)
.map_err(|e| ChdError::Codec(format!("FLAC sink write error: {:?}", e)))?;
dest.extend_from_slice(sink.as_slice());
sample_offset += cur_block;
samples_left -= cur_block;
frame_idx += 1;
}
Ok(())
})
}
#[cfg(all(feature = "flac-write", feature = "zlib"))]
pub fn compress_cdfl(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
use flacenc::component::BitRepr;
use flacenc::source::Fill;
if src.is_empty() || !src.len().is_multiple_of(CD_FRAME_SIZE) {
return Err(ChdError::Codec(
"source length must be a non-zero multiple of CD_FRAME_SIZE".into(),
));
}
let frames = src.len() / CD_FRAME_SIZE;
let num_stereo_samples = (frames * CD_SECTOR_SIZE) / 4;
let block_size = cdfl_block_size((frames * CD_SECTOR_SIZE) as u32) as usize;
dest.clear();
dest.reserve(src.len());
FLAC_SCRATCH.with(|scratch_cell| -> Result<(), ChdError> {
let mut scratch = scratch_cell.borrow_mut();
let FlacScratch {
samples,
subcode,
subcode_comp,
fb,
sink,
} = &mut *scratch;
samples.clear();
samples.reserve(num_stereo_samples * 2);
for framenum in 0..frames {
let frame_audio = &src[framenum * CD_FRAME_SIZE..framenum * CD_FRAME_SIZE + CD_SECTOR_SIZE];
for chunk in frame_audio.as_chunks::<4>().0 {
samples.push(i16::from_be_bytes([chunk[0], chunk[1]]) as i32);
samples.push(i16::from_be_bytes([chunk[2], chunk[3]]) as i32);
}
}
let mut samples_left = num_stereo_samples;
let mut sample_offset = 0;
let mut frame_idx = 0;
while samples_left > 0 {
let cur_block = block_size.min(samples_left);
if fb.size() != cur_block {
fb.resize(cur_block);
}
fb.fill_interleaved(&samples[sample_offset * 2..(sample_offset + cur_block) * 2])
.map_err(|e| ChdError::Codec(format!("FLAC fill error: {:?}", e)))?;
let flac_frame = flacenc::encode_fixed_size_frame(
&FLAC_ENCODER_CONFIG,
fb,
frame_idx,
&FLAC_STREAM_INFO_CD,
)
.map_err(|e| ChdError::Codec(format!("FLAC encode frame error: {:?}", e)))?;
sink.clear();
flac_frame
.write(sink)
.map_err(|e| ChdError::Codec(format!("FLAC sink write error: {:?}", e)))?;
dest.extend_from_slice(sink.as_slice());
sample_offset += cur_block;
samples_left -= cur_block;
frame_idx += 1;
}
subcode.clear();
subcode.reserve(frames * CD_SUBCODE_SIZE);
for framenum in 0..frames {
let sub = &src[framenum * CD_FRAME_SIZE + CD_SECTOR_SIZE..(framenum + 1) * CD_FRAME_SIZE];
subcode.extend_from_slice(sub);
}
subcode_comp.clear();
compress_zlib(subcode, subcode_comp)?;
dest.extend_from_slice(subcode_comp);
Ok(())
})
}
#[cfg(feature = "avhu")]
pub fn compress_avhu(src: &[u8], dest: &mut Vec<u8>) -> Result<(), ChdError> {
let compressed = chdlady_av::avhuff::compress(src)
.map_err(|e| ChdError::Codec(format!("avhu compression error: {}", e)))?;
*dest = compressed;
Ok(())
}
#[derive(Debug, Clone)]
pub struct CompressedHunk {
pub hunk_type: crate::map::HunkType,
pub data: Vec<u8>,
pub crc16: u16,
}
pub fn compress_hunk(compressors: &[u32; 4], src: &[u8]) -> CompressedHunk {
let raw_crc = crate::crc16::crc16(src, 0xffff);
let mut best_codec_idx = None;
let mut best_data = Vec::new();
let mut candidate = Vec::with_capacity(src.len());
for (idx, &codec) in compressors.iter().enumerate() {
if codec == 0 {
continue;
}
let codec_bytes = codec.to_be_bytes();
candidate.clear();
let success = match &codec_bytes {
#[cfg(feature = "zlib")]
b"zlib" => compress_zlib(src, &mut candidate).is_ok(),
#[cfg(feature = "lzma")]
b"lzma" => compress_lzma(src, &mut candidate).is_ok(),
#[cfg(feature = "zstd")]
b"zstd" => compress_zstd(src, &mut candidate).is_ok(),
b"huff" => compress_huffman(src, &mut candidate).is_ok(),
#[cfg(feature = "flac-write")]
b"flac" => compress_flac(src, &mut candidate).is_ok(),
#[cfg(feature = "zlib")]
b"cdzl" => compress_cdzl(src, &mut candidate).is_ok(),
#[cfg(all(feature = "lzma", feature = "zlib"))]
b"cdlz" => compress_cdlz(src, &mut candidate).is_ok(),
#[cfg(feature = "zstd")]
b"cdzs" => compress_cdzs(src, &mut candidate).is_ok(),
#[cfg(all(feature = "flac-write", feature = "zlib"))]
b"cdfl" => compress_cdfl(src, &mut candidate).is_ok(),
#[cfg(feature = "avhu")]
b"avhu" => compress_avhu(src, &mut candidate).is_ok(),
_ => false,
};
if success
&& candidate.len() < src.len()
&& (best_codec_idx.is_none() || candidate.len() < best_data.len())
{
best_codec_idx = Some(idx as u8);
std::mem::swap(&mut best_data, &mut candidate);
}
}
if let Some(idx) = best_codec_idx {
CompressedHunk {
hunk_type: crate::map::HunkType::Compressed(idx),
data: best_data,
crc16: raw_crc,
}
} else {
CompressedHunk {
hunk_type: crate::map::HunkType::Uncompressed,
data: src.to_vec(),
crc16: raw_crc,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_codecs_round_trip() {
let data: Vec<u8> = (0..4096).map(|i| ((i % 16) * 7) as u8).collect();
let mut zlib_comp = Vec::new();
compress_zlib(&data, &mut zlib_comp).expect("compress zlib");
let mut zlib_decomp = vec![0u8; data.len()];
decompress_zlib(&zlib_comp, &mut zlib_decomp).expect("decompress zlib");
assert_eq!(data, zlib_decomp);
let mut huff_comp = Vec::new();
compress_huffman(&data, &mut huff_comp).expect("compress huffman");
let mut huff_decomp = vec![0u8; data.len()];
decompress_huffman(&huff_comp, &mut huff_decomp).expect("decompress huffman");
assert_eq!(data, huff_decomp);
let mut lzma_comp = Vec::new();
compress_lzma(&data, &mut lzma_comp).expect("compress lzma");
let mut lzma_decomp = vec![0u8; data.len()];
decompress_lzma(&lzma_comp, &mut lzma_decomp).expect("decompress lzma");
assert_eq!(data, lzma_decomp);
#[cfg(feature = "zstd")]
{
let mut zstd_comp = Vec::new();
compress_zstd(&data, &mut zstd_comp).expect("compress zstd");
let mut zstd_decomp = vec![0u8; data.len()];
decompress_zstd(&zstd_comp, &mut zstd_decomp).expect("decompress zstd");
assert_eq!(data, zstd_decomp);
}
let mut cd_data = vec![0u8; 9792];
for (i, b) in cd_data.iter_mut().enumerate() {
*b = ((i % 23) * 11) as u8;
}
let mut cdzl_comp = Vec::new();
compress_cdzl(&cd_data, &mut cdzl_comp).expect("compress cdzl");
let mut cdzl_decomp = vec![0u8; cd_data.len()];
decompress_cdzl(&cdzl_comp, &mut cdzl_decomp).expect("decompress cdzl");
assert_eq!(cd_data, cdzl_decomp);
#[cfg(feature = "zstd")]
{
let mut cdzs_comp = Vec::new();
compress_cdzs(&cd_data, &mut cdzs_comp).expect("compress cdzs");
let mut cdzs_decomp = vec![0u8; cd_data.len()];
decompress_cdzs(&cdzs_comp, &mut cdzs_decomp).expect("decompress cdzs");
assert_eq!(cd_data, cdzs_decomp);
}
#[cfg(all(feature = "flac-write", feature = "flac-read", feature = "zlib"))]
{
let mut cd_audio = vec![0u8; 9792];
for framenum in 0..4 {
let frame_start = framenum * 2448;
for i in 0..2352 {
cd_audio[frame_start + i] = ((i * 13) % 256) as u8;
}
for i in 0..96 {
cd_audio[frame_start + 2352 + i] = (i % 256) as u8;
}
}
let mut cdfl_comp = Vec::new();
compress_cdfl(&cd_audio, &mut cdfl_comp).expect("compress cdfl");
let mut cdfl_decomp = vec![0u8; cd_audio.len()];
decompress_cdfl(&cdfl_comp, &mut cdfl_decomp).expect("decompress cdfl");
assert_eq!(cd_audio, cdfl_decomp);
}
#[cfg(all(feature = "flac-write", feature = "flac-read"))]
{
let mut raw_audio = vec![0u8; 4096];
for (i, b) in raw_audio.iter_mut().enumerate() {
*b = ((i * 17) % 256) as u8;
}
let mut flac_comp = Vec::new();
compress_flac(&raw_audio, &mut flac_comp).expect("compress flac");
let mut flac_decomp = vec![0u8; raw_audio.len()];
decompress_flac(&flac_comp, &mut flac_decomp).expect("decompress flac");
assert_eq!(raw_audio, flac_decomp);
}
}
}