use crate::error::OsStatus;
use crate::format::StreamFormat;
use crate::io::Timestamp;
use crate::property::{
PropertyAddress, PropertyElement, PropertyScope, PropertySelector, PropertyValue, ValueRange,
};
use crate::raw::abi::{
AudioObjectPropertyAddress, AudioStreamBasicDescription, AudioTimeStamp, AudioValueRange,
};
#[must_use]
pub fn address_from_raw(raw: &AudioObjectPropertyAddress) -> PropertyAddress {
PropertyAddress::new(
PropertySelector::from(crate::fourcc::FourCharCode::from_u32(raw.mSelector)),
PropertyScope::from(crate::fourcc::FourCharCode::from_u32(raw.mScope)),
PropertyElement::from(raw.mElement),
)
}
#[must_use]
pub fn address_to_raw(address: &PropertyAddress) -> AudioObjectPropertyAddress {
AudioObjectPropertyAddress {
mSelector: address.selector.code().as_u32(),
mScope: address.scope.code().as_u32(),
mElement: address.element.as_u32(),
}
}
#[must_use]
pub fn format_from_asbd(asbd: &AudioStreamBasicDescription) -> StreamFormat {
StreamFormat::from_raw(
asbd.mSampleRate,
asbd.mFormatID,
asbd.mFormatFlags,
asbd.mBytesPerPacket,
asbd.mFramesPerPacket,
asbd.mBytesPerFrame,
asbd.mChannelsPerFrame,
asbd.mBitsPerChannel,
)
}
#[must_use]
pub fn format_to_asbd(format: &StreamFormat) -> AudioStreamBasicDescription {
AudioStreamBasicDescription {
mSampleRate: format.sample_rate(),
mFormatID: format.format_id(),
mFormatFlags: format.format_flags(),
mBytesPerPacket: format.bytes_per_packet(),
mFramesPerPacket: format.frames_per_packet(),
mBytesPerFrame: format.bytes_per_frame(),
mChannelsPerFrame: format.channels(),
mBitsPerChannel: format.bits_per_channel(),
mReserved: 0,
}
}
#[must_use]
pub fn range_from_raw(raw: &AudioValueRange) -> ValueRange {
ValueRange::new(raw.mMinimum, raw.mMaximum)
}
#[must_use]
pub fn range_to_raw(range: &ValueRange) -> AudioValueRange {
AudioValueRange {
mMinimum: range.min,
mMaximum: range.max,
}
}
#[must_use]
pub fn timestamp_from_raw(raw: &AudioTimeStamp) -> Timestamp {
Timestamp::new(raw.mSampleTime, raw.mHostTime)
}
#[must_use]
pub fn timestamp_to_raw(timestamp: &Timestamp) -> AudioTimeStamp {
const SAMPLE_HOST_TIME_VALID: u32 = 0b11;
AudioTimeStamp {
mSampleTime: timestamp.sample_time,
mHostTime: timestamp.host_time,
mFlags: SAMPLE_HOST_TIME_VALID,
..AudioTimeStamp::default()
}
}
pub fn read_u32(buf: &[u8]) -> Result<u32, OsStatus> {
let bytes: [u8; 4] = buf
.get(..4)
.ok_or(OsStatus::BAD_PROPERTY_SIZE)?
.try_into()
.expect("slice of length 4 converts to [u8; 4]");
Ok(u32::from_ne_bytes(bytes))
}
pub fn read_f64(buf: &[u8]) -> Result<f64, OsStatus> {
let bytes: [u8; 8] = buf
.get(..8)
.ok_or(OsStatus::BAD_PROPERTY_SIZE)?
.try_into()
.expect("slice of length 8 converts to [u8; 8]");
Ok(f64::from_ne_bytes(bytes))
}
pub fn read_asbd(buf: &[u8]) -> Result<StreamFormat, OsStatus> {
if buf.len() < crate::format::ASBD_SIZE {
return Err(OsStatus::BAD_PROPERTY_SIZE);
}
let sample_rate = read_f64(&buf[0..8])?;
let mut u = [0u32; 8];
for (i, slot) in u.iter_mut().enumerate() {
let off = 8 + i * 4;
*slot = read_u32(&buf[off..off + 4])?;
}
Ok(StreamFormat::from_raw(
sample_rate,
u[0],
u[1],
u[2],
u[3],
u[4],
u[5],
u[6],
))
}
pub fn write_property_value(value: &PropertyValue, buf: &mut [u8]) -> Result<usize, OsStatus> {
let needed = value.byte_size();
if buf.len() < needed {
return Err(OsStatus::BAD_PROPERTY_SIZE);
}
match value {
PropertyValue::U32(v) => {
buf[..4].copy_from_slice(&v.to_ne_bytes());
}
PropertyValue::ObjectId(id) => {
buf[..4].copy_from_slice(&id.as_u32().to_ne_bytes());
}
PropertyValue::F64(v) => {
buf[..8].copy_from_slice(&v.to_ne_bytes());
}
PropertyValue::ObjectList(ids) => {
for (i, id) in ids.iter().enumerate() {
let off = i * 4;
buf[off..off + 4].copy_from_slice(&id.as_u32().to_ne_bytes());
}
}
PropertyValue::Format(format) => {
write_asbd(
&format_to_asbd(format),
&mut buf[..crate::format::ASBD_SIZE],
);
}
PropertyValue::RangeList(ranges) => {
for (i, range) in ranges.iter().enumerate() {
let off = i * ValueRange::SIZE;
buf[off..off + 8].copy_from_slice(&range.min.to_ne_bytes());
buf[off + 8..off + 16].copy_from_slice(&range.max.to_ne_bytes());
}
}
PropertyValue::Text(_) => return Err(OsStatus::UNSPECIFIED),
}
Ok(needed)
}
fn write_asbd(asbd: &AudioStreamBasicDescription, buf: &mut [u8]) {
debug_assert_eq!(buf.len(), crate::format::ASBD_SIZE);
buf[0..8].copy_from_slice(&asbd.mSampleRate.to_ne_bytes());
let u = [
asbd.mFormatID,
asbd.mFormatFlags,
asbd.mBytesPerPacket,
asbd.mFramesPerPacket,
asbd.mBytesPerFrame,
asbd.mChannelsPerFrame,
asbd.mBitsPerChannel,
asbd.mReserved,
];
for (i, value) in u.iter().enumerate() {
let off = 8 + i * 4;
buf[off..off + 4].copy_from_slice(&value.to_ne_bytes());
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use crate::object::AudioObjectId;
use alloc::vec;
#[test]
fn property_address_round_trips() {
let address = PropertyAddress::new(
PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE,
PropertyScope::INPUT,
PropertyElement::channel(2),
);
let raw = address_to_raw(&address);
assert_eq!(raw.mSelector, address.selector.code().as_u32());
assert_eq!(raw.mScope, PropertyScope::INPUT.code().as_u32());
assert_eq!(raw.mElement, 2);
assert_eq!(address_from_raw(&raw), address);
}
#[test]
fn stream_format_round_trips_through_asbd() {
for format in [
StreamFormat::float32(48_000.0, 2),
StreamFormat::int16(44_100.0, 1),
StreamFormat::int24(96_000.0, 6),
] {
let asbd = format_to_asbd(&format);
assert_eq!(asbd.mReserved, 0);
assert_eq!(asbd.mSampleRate, format.sample_rate());
assert_eq!(asbd.mChannelsPerFrame, format.channels());
assert_eq!(format_from_asbd(&asbd), format);
}
}
#[test]
fn value_range_round_trips() {
let range = ValueRange::new(44_100.0, 96_000.0);
let raw = range_to_raw(&range);
assert_eq!(raw.mMinimum, 44_100.0);
assert_eq!(raw.mMaximum, 96_000.0);
assert_eq!(range_from_raw(&raw), range);
}
#[test]
fn timestamp_extracts_sample_and_host_time() {
let raw = AudioTimeStamp {
mSampleTime: 128.0,
mHostTime: 999_999,
mRateScalar: 1.0,
..AudioTimeStamp::default()
};
let ts = timestamp_from_raw(&raw);
assert_eq!(ts.sample_time, 128.0);
assert_eq!(ts.host_time, 999_999);
}
#[test]
fn timestamp_to_raw_marks_the_two_fields_valid() {
let raw = timestamp_to_raw(&Timestamp::new(256.0, 42));
assert_eq!(raw.mSampleTime, 256.0);
assert_eq!(raw.mHostTime, 42);
assert_eq!(raw.mFlags, 0b11);
assert_eq!(raw.mRateScalar, 0.0);
}
#[test]
fn read_u32_and_f64_parse_native_endian() {
assert_eq!(read_u32(&1234u32.to_ne_bytes()), Ok(1234));
assert_eq!(read_f64(&48_000.0f64.to_ne_bytes()), Ok(48_000.0));
let mut padded = vec![0u8; 16];
padded[..4].copy_from_slice(&7u32.to_ne_bytes());
assert_eq!(read_u32(&padded), Ok(7));
}
#[test]
fn read_scalars_reject_short_buffers() {
assert_eq!(read_u32(&[0u8; 3]), Err(OsStatus::BAD_PROPERTY_SIZE));
assert_eq!(read_f64(&[0u8; 7]), Err(OsStatus::BAD_PROPERTY_SIZE));
assert_eq!(read_asbd(&[0u8; 39]), Err(OsStatus::BAD_PROPERTY_SIZE));
}
#[test]
fn asbd_round_trips_through_a_buffer() {
let format = StreamFormat::float32(48_000.0, 2);
let value = PropertyValue::Format(format);
let mut buf = vec![0u8; 64];
let written = write_property_value(&value, &mut buf).unwrap();
assert_eq!(written, 40);
assert_eq!(read_asbd(&buf).unwrap(), format);
}
#[test]
fn write_u32_value() {
let mut buf = [0u8; 4];
assert_eq!(
write_property_value(&PropertyValue::U32(0xABCD), &mut buf),
Ok(4)
);
assert_eq!(u32::from_ne_bytes(buf), 0xABCD);
}
#[test]
fn write_object_id_value() {
let mut buf = [0u8; 4];
let id = AudioObjectId::from_u32(7);
assert_eq!(
write_property_value(&PropertyValue::ObjectId(id), &mut buf),
Ok(4)
);
assert_eq!(u32::from_ne_bytes(buf), 7);
}
#[test]
fn write_f64_value() {
let mut buf = [0u8; 8];
assert_eq!(
write_property_value(&PropertyValue::F64(96_000.0), &mut buf),
Ok(8)
);
assert_eq!(f64::from_ne_bytes(buf), 96_000.0);
}
#[test]
fn write_object_list_value() {
let ids = vec![
AudioObjectId::from_u32(2),
AudioObjectId::from_u32(3),
AudioObjectId::from_u32(4),
];
let mut buf = vec![0u8; 12];
assert_eq!(
write_property_value(&PropertyValue::ObjectList(ids), &mut buf),
Ok(12)
);
assert_eq!(read_u32(&buf[0..4]), Ok(2));
assert_eq!(read_u32(&buf[4..8]), Ok(3));
assert_eq!(read_u32(&buf[8..12]), Ok(4));
}
#[test]
fn write_range_list_value() {
let ranges = vec![
ValueRange::point(48_000.0),
ValueRange::new(44_100.0, 96_000.0),
];
let mut buf = vec![0u8; 32];
assert_eq!(
write_property_value(&PropertyValue::RangeList(ranges), &mut buf),
Ok(32)
);
assert_eq!(read_f64(&buf[0..8]), Ok(48_000.0));
assert_eq!(read_f64(&buf[8..16]), Ok(48_000.0));
assert_eq!(read_f64(&buf[16..24]), Ok(44_100.0));
assert_eq!(read_f64(&buf[24..32]), Ok(96_000.0));
}
#[test]
fn write_rejects_an_undersized_buffer() {
let mut buf = [0u8; 3];
assert_eq!(
write_property_value(&PropertyValue::U32(1), &mut buf),
Err(OsStatus::BAD_PROPERTY_SIZE)
);
}
#[test]
fn write_rejects_text_values() {
let mut buf = vec![0u8; 64];
assert_eq!(
write_property_value(&PropertyValue::Text("Device".into()), &mut buf),
Err(OsStatus::UNSPECIFIED)
);
}
#[test]
fn write_accepts_an_oversized_buffer_and_reports_the_used_length() {
let mut buf = vec![0u8; 100];
assert_eq!(
write_property_value(&PropertyValue::F64(1.0), &mut buf),
Ok(8)
);
}
}