#![allow(unsafe_code)]
use std::{ffi::c_void, mem::size_of, ptr};
use tracing::debug;
use super::{
consts::Consts,
ffi::{
AudioBufferList, AudioConverterGetProperty, AudioConverterPrimeInfo, AudioConverterRef,
AudioStreamPacketDescription, OSStatus, UInt32,
},
};
use crate::GaplessInfo;
pub(crate) fn prime_info_from_converter(
converter: AudioConverterRef,
) -> Option<AudioConverterPrimeInfo> {
if converter.is_null() {
return None;
}
let mut info = AudioConverterPrimeInfo::default();
#[expect(
clippy::cast_possible_truncation,
reason = "size_of result fits in u32"
)]
let mut size = size_of::<AudioConverterPrimeInfo>() as UInt32;
let status = unsafe {
AudioConverterGetProperty(
converter,
Consts::kAudioConverterPrimeInfo,
&mut size,
&mut info as *mut _ as *mut c_void,
)
};
if status == Consts::noErr {
Some(info)
} else {
None
}
}
pub(crate) fn gapless_info_from_prime_info(info: AudioConverterPrimeInfo) -> Option<GaplessInfo> {
if info.leading_frames == 0 && info.trailing_frames == 0 {
return None;
}
Some(GaplessInfo {
leading_frames: u64::from(info.leading_frames),
trailing_frames: u64::from(info.trailing_frames),
})
}
pub(crate) fn log_gapless_prime_info(
stage: &'static str,
prime_info: Option<AudioConverterPrimeInfo>,
gapless: Option<GaplessInfo>,
) {
match (prime_info, gapless) {
(_, Some(info)) => debug!(
target: "kithara::gapless",
source = "apple_prime_info",
stage,
leading_frames = info.leading_frames,
trailing_frames = info.trailing_frames,
"captured gapless metadata from Apple PrimeInfo"
),
(Some(info), None) => debug!(
target: "kithara::gapless",
source = "apple_prime_info",
stage,
leading_frames = info.leading_frames,
trailing_frames = info.trailing_frames,
"Apple PrimeInfo reported no gapless trim"
),
(None, None) => debug!(
target: "kithara::gapless",
source = "apple_prime_info",
stage,
"Apple PrimeInfo not available"
),
}
}
#[derive(Default)]
pub(crate) struct ConverterInputState {
pub(crate) packet_ptr: *const u8,
pub(crate) packet_desc: AudioStreamPacketDescription,
pub(crate) packet_len: UInt32,
pub(crate) has_packet: bool,
}
impl ConverterInputState {
pub(crate) fn clear(&mut self) {
self.packet_ptr = ptr::null();
self.packet_len = 0;
self.has_packet = false;
}
pub(crate) fn set(&mut self, data: &[u8], description: AudioStreamPacketDescription) {
#[expect(clippy::cast_possible_truncation, reason = "packet size fits in u32")]
let len = data.len() as UInt32;
self.packet_ptr = data.as_ptr();
self.packet_len = len;
self.packet_desc = AudioStreamPacketDescription {
mStartOffset: 0,
mVariableFramesInPacket: description.mVariableFramesInPacket,
mDataByteSize: len,
};
self.has_packet = true;
}
}
pub(crate) extern "C" fn converter_input_callback(
_converter: AudioConverterRef,
io_num_packets: *mut UInt32,
io_data: *mut AudioBufferList,
out_packet_desc: *mut *mut AudioStreamPacketDescription,
user_data: *mut c_void,
) -> OSStatus {
let state = unsafe { &mut *(user_data as *mut ConverterInputState) };
if !state.has_packet {
unsafe {
*io_num_packets = 0;
}
return Consts::kAudioConverterErr_NoDataNow;
}
unsafe {
(*io_data).mBuffers[0].mDataByteSize = state.packet_len;
(*io_data).mBuffers[0].mData = state.packet_ptr as *mut c_void;
*io_num_packets = 1;
if !out_packet_desc.is_null() {
*out_packet_desc = &mut state.packet_desc;
}
}
state.has_packet = false;
Consts::noErr
}