Merge pull request #7 from JoshuaBatty/timestamp_coreaudio_fixes

Timestamp coreaudio fixes
This commit is contained in:
mitchmindtree 2020-05-01 15:24:15 +02:00 committed by GitHub
commit 63db35ed97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 6 deletions

View File

@ -34,6 +34,7 @@ libc = "0.2.65"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
coreaudio-rs = { version = "0.9.1", default-features = false, features = ["audio_unit", "core_audio"] } coreaudio-rs = { version = "0.9.1", default-features = false, features = ["audio_unit", "core_audio"] }
core-foundation-sys = "0.6.2" # For linking to CoreFoundation.framework and handling device name `CFString`s. core-foundation-sys = "0.6.2" # For linking to CoreFoundation.framework and handling device name `CFString`s.
mach = "0.3" # For access to mach_timebase type.
[target.'cfg(target_os = "emscripten")'.dependencies] [target.'cfg(target_os = "emscripten")'.dependencies]
stdweb = { version = "0.1.3", default-features = false } stdweb = { version = "0.1.3", default-features = false }

View File

@ -483,7 +483,7 @@ impl Device {
config: &StreamConfig, config: &StreamConfig,
sample_format: SampleFormat, sample_format: SampleFormat,
mut data_callback: D, mut data_callback: D,
_error_callback: E, mut error_callback: E,
) -> Result<Stream, BuildStreamError> ) -> Result<Stream, BuildStreamError>
where where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static, D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
@ -637,6 +637,7 @@ impl Device {
// Register the callback that is being called by coreaudio whenever it needs data to be // Register the callback that is being called by coreaudio whenever it needs data to be
// fed to the audio buffer. // fed to the audio buffer.
let bytes_per_channel = sample_format.sample_size(); let bytes_per_channel = sample_format.sample_size();
let sample_rate = config.sample_rate;
type Args = render_callback::Args<data::Raw>; type Args = render_callback::Args<data::Raw>;
audio_unit.set_input_callback(move |args: Args| unsafe { audio_unit.set_input_callback(move |args: Args| unsafe {
let ptr = (*args.data.data).mBuffers.as_ptr() as *const AudioBuffer; let ptr = (*args.data.data).mBuffers.as_ptr() as *const AudioBuffer;
@ -645,7 +646,7 @@ impl Device {
// TODO: Perhaps loop over all buffers instead? // TODO: Perhaps loop over all buffers instead?
let AudioBuffer { let AudioBuffer {
mNumberChannels: _num_channels, mNumberChannels: channels,
mDataByteSize: data_byte_size, mDataByteSize: data_byte_size,
mData: data, mData: data,
} = buffers[0]; } = buffers[0];
@ -653,7 +654,23 @@ impl Device {
let data = data as *mut (); let data = data as *mut ();
let len = (data_byte_size as usize / bytes_per_channel) as usize; let len = (data_byte_size as usize / bytes_per_channel) as usize;
let data = Data::from_parts(data, len, sample_format); let data = Data::from_parts(data, len, sample_format);
let info = InputCallbackInfo {};
// TODO: Need a better way to get delay, for now we assume a double-buffer offset.
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
error_callback(err.into());
return Err(());
}
Ok(cb) => cb,
};
let buffer_frames = len / channels as usize;
let delay = frames_to_duration(buffer_frames, sample_rate);
let capture = callback
.sub(delay)
.expect("`capture` occurs before origin of alsa `StreamInstant`");
let timestamp = crate::InputStreamTimestamp { callback, capture };
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info); data_callback(&data, &info);
Ok(()) Ok(())
})?; })?;
@ -672,7 +689,7 @@ impl Device {
config: &StreamConfig, config: &StreamConfig,
sample_format: SampleFormat, sample_format: SampleFormat,
mut data_callback: D, mut data_callback: D,
_error_callback: E, mut error_callback: E,
) -> Result<Stream, BuildStreamError> ) -> Result<Stream, BuildStreamError>
where where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
@ -691,13 +708,14 @@ impl Device {
// Register the callback that is being called by coreaudio whenever it needs data to be // Register the callback that is being called by coreaudio whenever it needs data to be
// fed to the audio buffer. // fed to the audio buffer.
let bytes_per_channel = sample_format.sample_size(); let bytes_per_channel = sample_format.sample_size();
let sample_rate = config.sample_rate;
type Args = render_callback::Args<data::Raw>; type Args = render_callback::Args<data::Raw>;
audio_unit.set_render_callback(move |args: Args| unsafe { audio_unit.set_render_callback(move |args: Args| unsafe {
// If `run()` is currently running, then a callback will be available from this list. // If `run()` is currently running, then a callback will be available from this list.
// Otherwise, we just fill the buffer with zeroes and return. // Otherwise, we just fill the buffer with zeroes and return.
let AudioBuffer { let AudioBuffer {
mNumberChannels: _num_channels, mNumberChannels: channels,
mDataByteSize: data_byte_size, mDataByteSize: data_byte_size,
mData: data, mData: data,
} = (*args.data.data).mBuffers[0]; } = (*args.data.data).mBuffers[0];
@ -705,7 +723,23 @@ impl Device {
let data = data as *mut (); let data = data as *mut ();
let len = (data_byte_size as usize / bytes_per_channel) as usize; let len = (data_byte_size as usize / bytes_per_channel) as usize;
let mut data = Data::from_parts(data, len, sample_format); let mut data = Data::from_parts(data, len, sample_format);
let info = OutputCallbackInfo {};
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
error_callback(err.into());
return Err(());
}
Ok(cb) => cb,
};
// TODO: Need a better way to get delay, for now we assume a double-buffer offset.
let buffer_frames = len / channels as usize;
let delay = frames_to_duration(buffer_frames, sample_rate);
let playback = callback
.add(delay)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info); data_callback(&mut data, &info);
Ok(()) Ok(())
})?; })?;
@ -720,6 +754,26 @@ impl Device {
} }
} }
fn host_time_to_stream_instant(
m_host_time: u64,
) -> Result<crate::StreamInstant, BackendSpecificError> {
let mut info: mach::mach_time::mach_timebase_info = Default::default();
let res = unsafe { mach::mach_time::mach_timebase_info(&mut info) };
check_os_status(res)?;
let nanos = m_host_time * info.numer as u64 / info.denom as u64;
let secs = nanos / 1_000_000_000;
let subsec_nanos = nanos - secs * 1_000_000_000;
Ok(crate::StreamInstant::new(secs as i64, subsec_nanos as u32))
}
// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
let secsf = frames as f64 / rate.0 as f64;
let secs = secsf as u64;
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
std::time::Duration::new(secs, nanos)
}
pub struct Stream { pub struct Stream {
inner: RefCell<StreamInner>, inner: RefCell<StreamInner>,
} }