diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs')
-rw-r--r-- | third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs new file mode 100644 index 0000000000..16063d0011 --- /dev/null +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs @@ -0,0 +1,572 @@ +use super::utils::{ + noop_data_callback, test_audiounit_get_buffer_frame_size, test_get_default_audiounit, + test_get_default_device, test_ops_context_operation, PropertyScope, Scope, +}; +use super::*; +use std::thread; + +// Ignore the test by default to avoid overwritting the buffer frame size of the device that is +// currently used by other streams in other tests. +#[ignore] +#[test] +fn test_parallel_ops_init_streams_in_parallel_input() { + const THREADS: u32 = 50; + create_streams_by_ops_in_parallel_with_different_latency( + THREADS, + StreamType::Input, + |streams| { + // All the latency frames should be the same value as the first stream's one, since the + // latency frames cannot be changed if another stream is operating in parallel. + let mut latency_frames = vec![]; + let mut in_buffer_frame_sizes = vec![]; + + for stream in streams { + latency_frames.push(stream.latency_frames); + + assert!(!stream.core_stream_data.input_unit.is_null()); + let in_buffer_frame_size = test_audiounit_get_buffer_frame_size( + stream.core_stream_data.input_unit, + Scope::Input, + PropertyScope::Output, + ) + .unwrap(); + in_buffer_frame_sizes.push(in_buffer_frame_size); + + assert!(stream.core_stream_data.output_unit.is_null()); + } + + // Make sure all the latency frames are same as the first stream's one. + for i in 0..latency_frames.len() - 1 { + assert_eq!(latency_frames[i], latency_frames[i + 1]); + } + + // Make sure all the buffer frame sizes on output scope of the input audiounit are same + // as the defined latency of the first initial stream. + for i in 0..in_buffer_frame_sizes.len() - 1 { + assert_eq!(in_buffer_frame_sizes[i], in_buffer_frame_sizes[i + 1]); + } + }, + ); +} + +// Ignore the test by default to avoid overwritting the buffer frame size of the device that is +// currently used by other streams in other tests. +#[ignore] +#[test] +fn test_parallel_ops_init_streams_in_parallel_output() { + const THREADS: u32 = 50; + create_streams_by_ops_in_parallel_with_different_latency( + THREADS, + StreamType::Output, + |streams| { + // All the latency frames should be the same value as the first stream's one, since the + // latency frames cannot be changed if another stream is operating in parallel. + let mut latency_frames = vec![]; + let mut out_buffer_frame_sizes = vec![]; + + for stream in streams { + latency_frames.push(stream.latency_frames); + + assert!(stream.core_stream_data.input_unit.is_null()); + + assert!(!stream.core_stream_data.output_unit.is_null()); + let out_buffer_frame_size = test_audiounit_get_buffer_frame_size( + stream.core_stream_data.output_unit, + Scope::Output, + PropertyScope::Input, + ) + .unwrap(); + out_buffer_frame_sizes.push(out_buffer_frame_size); + } + + // Make sure all the latency frames are same as the first stream's one. + for i in 0..latency_frames.len() - 1 { + assert_eq!(latency_frames[i], latency_frames[i + 1]); + } + + // Make sure all the buffer frame sizes on input scope of the output audiounit are same + // as the defined latency of the first initial stream. + for i in 0..out_buffer_frame_sizes.len() - 1 { + assert_eq!(out_buffer_frame_sizes[i], out_buffer_frame_sizes[i + 1]); + } + }, + ); +} + +// Ignore the test by default to avoid overwritting the buffer frame size of the device that is +// currently used by other streams in other tests. +#[ignore] +#[test] +fn test_parallel_ops_init_streams_in_parallel_duplex() { + const THREADS: u32 = 50; + create_streams_by_ops_in_parallel_with_different_latency( + THREADS, + StreamType::Duplex, + |streams| { + // All the latency frames should be the same value as the first stream's one, since the + // latency frames cannot be changed if another stream is operating in parallel. + let mut latency_frames = vec![]; + let mut in_buffer_frame_sizes = vec![]; + let mut out_buffer_frame_sizes = vec![]; + + for stream in streams { + latency_frames.push(stream.latency_frames); + + assert!(!stream.core_stream_data.input_unit.is_null()); + let in_buffer_frame_size = test_audiounit_get_buffer_frame_size( + stream.core_stream_data.input_unit, + Scope::Input, + PropertyScope::Output, + ) + .unwrap(); + in_buffer_frame_sizes.push(in_buffer_frame_size); + + assert!(!stream.core_stream_data.output_unit.is_null()); + let out_buffer_frame_size = test_audiounit_get_buffer_frame_size( + stream.core_stream_data.output_unit, + Scope::Output, + PropertyScope::Input, + ) + .unwrap(); + out_buffer_frame_sizes.push(out_buffer_frame_size); + } + + // Make sure all the latency frames are same as the first stream's one. + for i in 0..latency_frames.len() - 1 { + assert_eq!(latency_frames[i], latency_frames[i + 1]); + } + + // Make sure all the buffer frame sizes on output scope of the input audiounit are same + // as the defined latency of the first initial stream. + for i in 0..in_buffer_frame_sizes.len() - 1 { + assert_eq!(in_buffer_frame_sizes[i], in_buffer_frame_sizes[i + 1]); + } + + // Make sure all the buffer frame sizes on input scope of the output audiounit are same + // as the defined latency of the first initial stream. + for i in 0..out_buffer_frame_sizes.len() - 1 { + assert_eq!(out_buffer_frame_sizes[i], out_buffer_frame_sizes[i + 1]); + } + }, + ); +} + +fn create_streams_by_ops_in_parallel_with_different_latency<F>( + amount: u32, + stm_type: StreamType, + callback: F, +) where + F: FnOnce(Vec<&AudioUnitStream>), +{ + let default_input = test_get_default_device(Scope::Input); + let default_output = test_get_default_device(Scope::Output); + + let has_input = stm_type == StreamType::Input || stm_type == StreamType::Duplex; + let has_output = stm_type == StreamType::Output || stm_type == StreamType::Duplex; + + if has_input && default_input.is_none() { + println!("No input device to perform the test."); + return; + } + + if has_output && default_output.is_none() { + println!("No output device to perform the test."); + return; + } + + test_ops_context_operation("context: init and destroy", |context_ptr| { + let context_ptr_value = context_ptr as usize; + + let mut join_handles = vec![]; + for i in 0..amount { + // Make sure the parameters meet the requirements of AudioUnitContext::stream_init + // (in the comments). + let mut input_params = ffi::cubeb_stream_params::default(); + input_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE; + input_params.rate = 48_000; + input_params.channels = 1; + input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; + input_params.prefs = ffi::CUBEB_STREAM_PREF_NONE; + + let mut output_params = ffi::cubeb_stream_params::default(); + output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE; + output_params.rate = 44100; + output_params.channels = 2; + output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; + output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE; + + // Latency cannot be changed if another stream is operating in parallel. All the latecy + // should be set to the same latency value of the first stream that is operating in the + // context. + let latency_frames = SAFE_MIN_LATENCY_FRAMES + i; + assert!(latency_frames < SAFE_MAX_LATENCY_FRAMES); + + // Create many streams within the same context. The order of the stream creation + // is random (The order of execution of the spawned threads is random.).assert! + // It's super dangerous to pass `context_ptr_value` across threads and convert it back + // to a pointer. However, it's the cheapest way to make sure the inside mutex works. + let thread_name = format!("stream {} @ context {:?}", i, context_ptr); + join_handles.push( + thread::Builder::new() + .name(thread_name) + .spawn(move || { + let context_ptr = context_ptr_value as *mut ffi::cubeb; + let mut stream: *mut ffi::cubeb_stream = ptr::null_mut(); + let stream_name = CString::new(format!("stream {}", i)).unwrap(); + assert_eq!( + unsafe { + OPS.stream_init.unwrap()( + context_ptr, + &mut stream, + stream_name.as_ptr(), + ptr::null_mut(), // Use default input device. + if has_input { + &mut input_params + } else { + ptr::null_mut() + }, + ptr::null_mut(), // Use default output device. + if has_output { + &mut output_params + } else { + ptr::null_mut() + }, + latency_frames, + Some(noop_data_callback), + None, // No state callback. + ptr::null_mut(), // No user data pointer. + ) + }, + ffi::CUBEB_OK + ); + assert!(!stream.is_null()); + stream as usize + }) + .unwrap(), + ); + } + + let mut streams = vec![]; + // Wait for finishing the tasks on the different threads. + for handle in join_handles { + let stream_ptr_value = handle.join().unwrap(); + let stream = unsafe { Box::from_raw(stream_ptr_value as *mut AudioUnitStream) }; + streams.push(stream); + } + + let stream_refs: Vec<&AudioUnitStream> = streams.iter().map(|stm| stm.as_ref()).collect(); + callback(stream_refs); + }); +} + +// Ignore the test by default to avoid overwritting the buffer frame size of the device that is +// currently used by other streams in other tests. +#[ignore] +#[test] +fn test_parallel_init_streams_in_parallel_input() { + const THREADS: u32 = 10; + create_streams_in_parallel_with_different_latency(THREADS, StreamType::Input, |streams| { + // All the latency frames should be the same value as the first stream's one, since the + // latency frames cannot be changed if another stream is operating in parallel. + let mut latency_frames = vec![]; + let mut in_buffer_frame_sizes = vec![]; + + for stream in streams { + latency_frames.push(stream.latency_frames); + + assert!(!stream.core_stream_data.input_unit.is_null()); + let in_buffer_frame_size = test_audiounit_get_buffer_frame_size( + stream.core_stream_data.input_unit, + Scope::Input, + PropertyScope::Output, + ) + .unwrap(); + in_buffer_frame_sizes.push(in_buffer_frame_size); + + assert!(stream.core_stream_data.output_unit.is_null()); + } + + // Make sure all the latency frames are same as the first stream's one. + for i in 0..latency_frames.len() - 1 { + assert_eq!(latency_frames[i], latency_frames[i + 1]); + } + + // Make sure all the buffer frame sizes on output scope of the input audiounit are same + // as the defined latency of the first initial stream. + for i in 0..in_buffer_frame_sizes.len() - 1 { + assert_eq!(in_buffer_frame_sizes[i], in_buffer_frame_sizes[i + 1]); + } + }); +} + +// Ignore the test by default to avoid overwritting the buffer frame size of the device that is +// currently used by other streams in other tests. +#[ignore] +#[test] +fn test_parallel_init_streams_in_parallel_output() { + const THREADS: u32 = 10; + create_streams_in_parallel_with_different_latency(THREADS, StreamType::Output, |streams| { + // All the latency frames should be the same value as the first stream's one, since the + // latency frames cannot be changed if another stream is operating in parallel. + let mut latency_frames = vec![]; + let mut out_buffer_frame_sizes = vec![]; + + for stream in streams { + latency_frames.push(stream.latency_frames); + + assert!(stream.core_stream_data.input_unit.is_null()); + + assert!(!stream.core_stream_data.output_unit.is_null()); + let out_buffer_frame_size = test_audiounit_get_buffer_frame_size( + stream.core_stream_data.output_unit, + Scope::Output, + PropertyScope::Input, + ) + .unwrap(); + out_buffer_frame_sizes.push(out_buffer_frame_size); + } + + // Make sure all the latency frames are same as the first stream's one. + for i in 0..latency_frames.len() - 1 { + assert_eq!(latency_frames[i], latency_frames[i + 1]); + } + + // Make sure all the buffer frame sizes on input scope of the output audiounit are same + // as the defined latency of the first initial stream. + for i in 0..out_buffer_frame_sizes.len() - 1 { + assert_eq!(out_buffer_frame_sizes[i], out_buffer_frame_sizes[i + 1]); + } + }); +} + +// Ignore the test by default to avoid overwritting the buffer frame size of the device that is +// currently used by other streams in other tests. +#[ignore] +#[test] +fn test_parallel_init_streams_in_parallel_duplex() { + const THREADS: u32 = 10; + create_streams_in_parallel_with_different_latency(THREADS, StreamType::Duplex, |streams| { + // All the latency frames should be the same value as the first stream's one, since the + // latency frames cannot be changed if another stream is operating in parallel. + let mut latency_frames = vec![]; + let mut in_buffer_frame_sizes = vec![]; + let mut out_buffer_frame_sizes = vec![]; + + for stream in streams { + latency_frames.push(stream.latency_frames); + + assert!(!stream.core_stream_data.input_unit.is_null()); + let in_buffer_frame_size = test_audiounit_get_buffer_frame_size( + stream.core_stream_data.input_unit, + Scope::Input, + PropertyScope::Output, + ) + .unwrap(); + in_buffer_frame_sizes.push(in_buffer_frame_size); + + assert!(!stream.core_stream_data.output_unit.is_null()); + let out_buffer_frame_size = test_audiounit_get_buffer_frame_size( + stream.core_stream_data.output_unit, + Scope::Output, + PropertyScope::Input, + ) + .unwrap(); + out_buffer_frame_sizes.push(out_buffer_frame_size); + } + + // Make sure all the latency frames are same as the first stream's one. + for i in 0..latency_frames.len() - 1 { + assert_eq!(latency_frames[i], latency_frames[i + 1]); + } + + // Make sure all the buffer frame sizes on output scope of the input audiounit are same + // as the defined latency of the first initial stream. + for i in 0..in_buffer_frame_sizes.len() - 1 { + assert_eq!(in_buffer_frame_sizes[i], in_buffer_frame_sizes[i + 1]); + } + + // Make sure all the buffer frame sizes on input scope of the output audiounit are same + // as the defined latency of the first initial stream. + for i in 0..out_buffer_frame_sizes.len() - 1 { + assert_eq!(out_buffer_frame_sizes[i], out_buffer_frame_sizes[i + 1]); + } + }); +} + +fn create_streams_in_parallel_with_different_latency<F>( + amount: u32, + stm_type: StreamType, + callback: F, +) where + F: FnOnce(Vec<&AudioUnitStream>), +{ + let default_input = test_get_default_device(Scope::Input); + let default_output = test_get_default_device(Scope::Output); + + let has_input = stm_type == StreamType::Input || stm_type == StreamType::Duplex; + let has_output = stm_type == StreamType::Output || stm_type == StreamType::Duplex; + + if has_input && default_input.is_none() { + println!("No input device to perform the test."); + return; + } + + if has_output && default_output.is_none() { + println!("No output device to perform the test."); + return; + } + + let mut context = AudioUnitContext::new(); + + let context_ptr_value = &mut context as *mut AudioUnitContext as usize; + + let mut join_handles = vec![]; + for i in 0..amount { + // Make sure the parameters meet the requirements of AudioUnitContext::stream_init + // (in the comments). + let mut input_params = ffi::cubeb_stream_params::default(); + input_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE; + input_params.rate = 48_000; + input_params.channels = 1; + input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; + input_params.prefs = ffi::CUBEB_STREAM_PREF_NONE; + + let mut output_params = ffi::cubeb_stream_params::default(); + output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE; + output_params.rate = 44100; + output_params.channels = 2; + output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; + output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE; + + // Latency cannot be changed if another stream is operating in parallel. All the latecy + // should be set to the same latency value of the first stream that is operating in the + // context. + let latency_frames = SAFE_MIN_LATENCY_FRAMES + i; + assert!(latency_frames < SAFE_MAX_LATENCY_FRAMES); + + // Create many streams within the same context. The order of the stream creation + // is random. (The order of execution of the spawned threads is random.) + // It's super dangerous to pass `context_ptr_value` across threads and convert it back + // to a reference. However, it's the cheapest way to make sure the inside mutex works. + let thread_name = format!("stream {} @ context {:?}", i, context_ptr_value); + join_handles.push( + thread::Builder::new() + .name(thread_name) + .spawn(move || { + let context = unsafe { &mut *(context_ptr_value as *mut AudioUnitContext) }; + let input_params = unsafe { StreamParamsRef::from_ptr(&mut input_params) }; + let output_params = unsafe { StreamParamsRef::from_ptr(&mut output_params) }; + let stream = context + .stream_init( + None, + ptr::null_mut(), // Use default input device. + if has_input { Some(input_params) } else { None }, + ptr::null_mut(), // Use default output device. + if has_output { + Some(output_params) + } else { + None + }, + latency_frames, + Some(noop_data_callback), + None, // No state callback. + ptr::null_mut(), // No user data pointer. + ) + .unwrap(); + assert!(!stream.as_ptr().is_null()); + let stream_ptr_value = stream.as_ptr() as usize; + // Prevent the stream from being destroyed by leaking this stream. + mem::forget(stream); + stream_ptr_value + }) + .unwrap(), + ); + } + + let mut streams = vec![]; + // Wait for finishing the tasks on the different threads. + for handle in join_handles { + let stream_ptr_value = handle.join().unwrap(); + // Retake the leaked stream. + let stream = unsafe { Box::from_raw(stream_ptr_value as *mut AudioUnitStream) }; + streams.push(stream); + } + + let stream_refs: Vec<&AudioUnitStream> = streams.iter().map(|stm| stm.as_ref()).collect(); + callback(stream_refs); +} + +#[derive(Debug, PartialEq)] +enum StreamType { + Input, + Output, + Duplex, +} + +// This is used to interfere other active streams. +// From this testing, it's ok to set the buffer frame size of a device that is currently used by +// other tests. It works on OSX 10.13, not sure if it works on other versions. +// However, other tests may check the buffer frame size they set at the same time, +// so we ignore this by default incase those checks fail. +#[ignore] +#[test] +fn test_set_buffer_frame_size_in_parallel() { + test_set_buffer_frame_size_in_parallel_in_scope(Scope::Input); + test_set_buffer_frame_size_in_parallel_in_scope(Scope::Output); +} + +fn test_set_buffer_frame_size_in_parallel_in_scope(scope: Scope) { + const THREADS: u32 = 100; + + let unit = test_get_default_audiounit(scope.clone()); + if unit.is_none() { + println!("No unit for {:?}", scope); + return; + } + + let (unit_scope, unit_element, prop_scope) = match scope { + Scope::Input => (kAudioUnitScope_Output, AU_IN_BUS, PropertyScope::Output), + Scope::Output => (kAudioUnitScope_Input, AU_OUT_BUS, PropertyScope::Input), + }; + + let mut units = vec![]; + let mut join_handles = vec![]; + for i in 0..THREADS { + let latency_frames = SAFE_MIN_LATENCY_FRAMES + i; + assert!(latency_frames < SAFE_MAX_LATENCY_FRAMES); + units.push(test_get_default_audiounit(scope.clone()).unwrap()); + let unit_value = units.last().unwrap().get_inner() as usize; + join_handles.push(thread::spawn(move || { + let status = audio_unit_set_property( + unit_value as AudioUnit, + kAudioDevicePropertyBufferFrameSize, + unit_scope, + unit_element, + &latency_frames, + mem::size_of::<u32>(), + ); + (latency_frames, status) + })); + } + + let mut latencies = vec![]; + let mut statuses = vec![]; + for handle in join_handles { + let (latency, status) = handle.join().unwrap(); + latencies.push(latency); + statuses.push(status); + } + + let mut buffer_frames_list = vec![]; + for unit in units.iter() { + buffer_frames_list.push(unit.get_buffer_frame_size(scope.clone(), prop_scope.clone())); + } + + for status in statuses { + assert_eq!(status, NO_ERR); + } + + for i in 0..buffer_frames_list.len() - 1 { + assert_eq!(buffer_frames_list[i], buffer_frames_list[i + 1]); + } +} |