mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 02:02:52 +00:00
win_audio: Upstream Window's audio crate
This crate has been copied upstream verbatim. This crate is used as an audio backend for playback (capture has not been implemented yet). The next CLs are will upstream the usage of win_audio. Bug: 232462411 Test: built and presubmits (although this crate won't be built locally or presubmits yet) Change-Id: Iae374b2b575fbd19b016cae403a00024896cba56 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3694097 Commit-Queue: Richard Zhang <rizhang@google.com> Reviewed-by: Noah Gold <nkgold@google.com> Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
parent
7aac156d53
commit
2efb19303d
8 changed files with 3099 additions and 0 deletions
17
win_audio/Cargo.toml
Normal file
17
win_audio/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "win_audio"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
audio_streams = { path = "../common/audio_streams"}
|
||||
base = { path = "../base" }
|
||||
libc = "*"
|
||||
win_util = { path = "../win_util" }
|
||||
winapi = "*"
|
||||
wio = "*"
|
||||
sync = { path = "../common/sync" }
|
||||
thiserror = "*"
|
||||
metrics = { path = "../metrics"}
|
||||
once_cell = "1.7.2"
|
38
win_audio/build.rs
Normal file
38
win_audio/build.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
fn main() {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::env;
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
println!("cargo:rustc-link-lib=static=r8brain");
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let dll_dir = format!(
|
||||
r#"{}\..\..\..\third_party\r8brain\r8brain\x64\Debug\"#,
|
||||
manifest_dir
|
||||
);
|
||||
println!(r#"cargo:rustc-link-search={}"#, dll_dir);
|
||||
println!(
|
||||
r#"cargo:rustc-env=PATH={};{}"#,
|
||||
env::var("PATH").unwrap(),
|
||||
dll_dir
|
||||
);
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
let dll_dir = format!(
|
||||
r#"{}\..\..\..\third_party\r8brain\r8brain\x64\Release\"#,
|
||||
manifest_dir
|
||||
);
|
||||
println!(r#"cargo:rustc-link-search={}"#, dll_dir);
|
||||
println!(
|
||||
r#"cargo:rustc-env=PATH={};{}"#,
|
||||
env::var("PATH").unwrap(),
|
||||
dll_dir
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
406
win_audio/src/intermediate_resampler_buffer.rs
Normal file
406
win_audio/src/intermediate_resampler_buffer.rs
Normal file
|
@ -0,0 +1,406 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::r8b_create;
|
||||
use crate::r8b_delete;
|
||||
use crate::r8b_process;
|
||||
use crate::win_audio_impl;
|
||||
use crate::CR8BResampler;
|
||||
use crate::ER8BResamplerRes_r8brr24;
|
||||
use audio_streams::BoxError;
|
||||
use base::{info, warn};
|
||||
use std::collections::VecDeque;
|
||||
use winapi::shared::mmreg::{SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT};
|
||||
|
||||
// Increasing this constant won't do much now. In the future, we may want to read from the shm
|
||||
// buffer mulitple times in a row to prevent the chance of us running out of audio frames to write
|
||||
// to the Windows audio engine buffer.
|
||||
const PERIOD_COUNT: usize = 4;
|
||||
pub const STEREO_CHANNEL_COUNT: usize = win_audio_impl::STEREO_CHANNEL_COUNT as usize;
|
||||
const MONO_CHANNEL_COUNT: usize = win_audio_impl::MONO_CHANNEL_COUNT as usize;
|
||||
|
||||
/// Provides a ring buffer to hold audio samples coming from the guest. Also responsible for sample
|
||||
/// rate conversion (src) if needed. We are assuming the guest's sample format is ALWAYS 16bit
|
||||
/// ints, 48kHz, and 2 channels because this is defined in Kiwi's Android Audio HAL, which
|
||||
/// we control. We are also assuming that the audio engine will always take 32bit
|
||||
/// floats if we ask for the shared format through `GetMixFormat` since it will convert
|
||||
/// to 32bit floats if it's not anyways.
|
||||
pub struct IntermediateResamplerBuffer {
|
||||
left_resampler: CR8BResampler,
|
||||
right_resampler: CR8BResampler,
|
||||
pub ring_buf: VecDeque<f32>,
|
||||
pub shared_audio_engine_period_in_frames: usize,
|
||||
// The guest period in frames when converted to the audio engine's sample rate.
|
||||
pub guest_period_in_target_sample_rate_frames: usize,
|
||||
resampled_output_buffer: Vec<u8>,
|
||||
num_channels: usize,
|
||||
}
|
||||
|
||||
impl IntermediateResamplerBuffer {
|
||||
pub fn new(
|
||||
from_sample_rate: usize,
|
||||
to_sample_rate: usize,
|
||||
guest_period_in_frames: usize,
|
||||
shared_audio_engine_period_in_frames: usize,
|
||||
num_channels: usize,
|
||||
channel_mask: Option<u32>,
|
||||
) -> Result<Self, BoxError> {
|
||||
// Convert the period to milliseconds. Even though rounding happens, it shouldn't distort
|
||||
// the result.
|
||||
// Unit would look like: (frames * 1000(milliseconds/second) / (frames/second))
|
||||
// so end units is in milliseconds.
|
||||
if (shared_audio_engine_period_in_frames * 1000) / to_sample_rate < 10 {
|
||||
warn!("Windows Audio Engine period is less than 10ms");
|
||||
}
|
||||
// Divide by 100 because we want to get the # of frames in 10ms since that is the guest's
|
||||
// period.
|
||||
let guest_period_in_target_sample_rate_frames = to_sample_rate / 100;
|
||||
|
||||
if let Some(channel_mask) = channel_mask {
|
||||
if channel_mask & SPEAKER_FRONT_LEFT == 0 || channel_mask & SPEAKER_FRONT_RIGHT == 0 {
|
||||
warn!(
|
||||
"channel_mask: {} does not have both front left and front right channels set. \
|
||||
Will proceed to populate the first 2 channels anyways.",
|
||||
channel_mask
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IntermediateResamplerBuffer {
|
||||
// If the from and to sample rate is the same, there will be a no-op.
|
||||
left_resampler: unsafe {
|
||||
r8b_create(
|
||||
from_sample_rate as f64,
|
||||
to_sample_rate as f64,
|
||||
guest_period_in_frames as i32,
|
||||
/* ReqTransBand= */ 2.0,
|
||||
ER8BResamplerRes_r8brr24,
|
||||
)
|
||||
},
|
||||
right_resampler: unsafe {
|
||||
r8b_create(
|
||||
from_sample_rate as f64,
|
||||
to_sample_rate as f64,
|
||||
guest_period_in_frames as i32,
|
||||
/* ReqTransBand= */ 2.0,
|
||||
ER8BResamplerRes_r8brr24,
|
||||
)
|
||||
},
|
||||
// Size chosen since it's a power of 2 minus 1. This is the max capacity I've
|
||||
// seen the VecDeque reach.
|
||||
ring_buf: VecDeque::with_capacity(shared_audio_engine_period_in_frames * PERIOD_COUNT),
|
||||
shared_audio_engine_period_in_frames,
|
||||
guest_period_in_target_sample_rate_frames,
|
||||
// Each frame will have 64 bits, or 8 bytes.
|
||||
resampled_output_buffer: Vec::<u8>::with_capacity(
|
||||
shared_audio_engine_period_in_frames * 8,
|
||||
),
|
||||
num_channels,
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts the 16 bit int samples to the target sample rate and also add to the
|
||||
/// intermediate `ring_buf` if needed.
|
||||
pub fn convert_and_add(&mut self, input_buffer: &[u8]) {
|
||||
if input_buffer.len() % 4 != 0 {
|
||||
warn!("input buffer len {} not divisible by 4", input_buffer.len());
|
||||
}
|
||||
let mut left_channel = vec![0.0; input_buffer.len() / 4];
|
||||
let mut right_channel = vec![0.0; input_buffer.len() / 4];
|
||||
self.copy_every_other_and_convert_to_float(input_buffer, &mut left_channel, 0);
|
||||
self.copy_every_other_and_convert_to_float(input_buffer, &mut right_channel, 2);
|
||||
|
||||
let mut left_converted_buffer_raw: *mut f64 = std::ptr::null_mut();
|
||||
let mut right_converted_buffer_raw: *mut f64 = std::ptr::null_mut();
|
||||
// Safe because the only part unsafe in calling `r8b_process` which is a FFI that
|
||||
// should be binded correctly.
|
||||
let (left_samples_available, right_samples_available) = unsafe {
|
||||
let left_samples_available = r8b_process(
|
||||
self.left_resampler,
|
||||
left_channel.as_mut_ptr(),
|
||||
left_channel.len() as i32,
|
||||
&mut left_converted_buffer_raw,
|
||||
);
|
||||
let right_samples_available = r8b_process(
|
||||
self.right_resampler,
|
||||
right_channel.as_mut_ptr(),
|
||||
right_channel.len() as i32,
|
||||
&mut right_converted_buffer_raw,
|
||||
);
|
||||
(left_samples_available, right_samples_available)
|
||||
};
|
||||
|
||||
if left_samples_available != right_samples_available {
|
||||
warn!(
|
||||
"left_samples_avialable: {}, does not match right_samples_avaiable: {}",
|
||||
left_samples_available, right_samples_available,
|
||||
);
|
||||
}
|
||||
// `r8b_process` will need multiple guest periods before it will
|
||||
// return a converted buffer of audio samples.
|
||||
if left_samples_available != 0 && right_samples_available != 0 {
|
||||
let left_channel_converted = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
left_converted_buffer_raw,
|
||||
left_samples_available as usize,
|
||||
)
|
||||
};
|
||||
let right_channel_converted = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
right_converted_buffer_raw,
|
||||
right_samples_available as usize,
|
||||
)
|
||||
};
|
||||
|
||||
// As mentioned above, we are assuming that guest's format is 16bits int. A 16 bit int
|
||||
// format gives a range from −32,768 to 32,767. To convert audio samples from int to float,
|
||||
// we need to convert it to a range from -1.0 to 1.0, hence dividing by 32767 (2^15 - 1).
|
||||
for (left_sample, right_sample) in left_channel_converted
|
||||
.iter()
|
||||
.zip(right_channel_converted.iter())
|
||||
{
|
||||
let left_normalized_sample = *left_sample as f32 / i16::MAX as f32;
|
||||
let right_normalized_sample = *right_sample as f32 / i16::MAX as f32;
|
||||
|
||||
self.perform_channel_conversion(left_normalized_sample, right_normalized_sample);
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
"Skipping adding samples to ring buffer because left samples available: {} and \
|
||||
right samples available: {}",
|
||||
left_samples_available, right_samples_available
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_channel_conversion(
|
||||
&mut self,
|
||||
left_normalized_sample: f32,
|
||||
right_normalized_sample: f32,
|
||||
) {
|
||||
match self.num_channels {
|
||||
STEREO_CHANNEL_COUNT => {
|
||||
self.ring_buf.push_back(left_normalized_sample);
|
||||
self.ring_buf.push_back(right_normalized_sample);
|
||||
}
|
||||
MONO_CHANNEL_COUNT => {
|
||||
self.ring_buf
|
||||
.push_back((left_normalized_sample + right_normalized_sample) / 2.0);
|
||||
}
|
||||
_ => {
|
||||
// This will put the `left_normalized_sample` in SPEAKER_FRONT_LEFT and the
|
||||
// `right_normalized_sample` in SPEAKER_FRONT_RIGHT and then zero out the rest.
|
||||
self.ring_buf.push_back(left_normalized_sample);
|
||||
self.ring_buf.push_back(right_normalized_sample);
|
||||
for _ in 0..self.num_channels - 2 {
|
||||
self.ring_buf.push_back(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_next_period(&mut self) -> Option<&Vec<u8>> {
|
||||
self.resampled_output_buffer.clear();
|
||||
// This value is equal to one full audio engine period of audio frames.
|
||||
let sample_threshold = self.shared_audio_engine_period_in_frames * self.num_channels;
|
||||
|
||||
if self.ring_buf.len() >= sample_threshold {
|
||||
for current_sample in self.ring_buf.drain(..sample_threshold) {
|
||||
self.resampled_output_buffer
|
||||
.extend_from_slice(¤t_sample.to_le_bytes());
|
||||
}
|
||||
return Some(&self.resampled_output_buffer);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Seperates the audio samples by channels
|
||||
///
|
||||
/// Audio samples coming from the guest are formatted similarly to how WAV files are formatted:
|
||||
/// http://soundfile.sapp.org/doc/WaveFormat/
|
||||
///
|
||||
/// Audio samples from the guest are coming in as little endian format. Example:
|
||||
/// Channel: [ L ] [ R ] [ L ] [ R ]
|
||||
/// [u8]: [14, 51, 45, 0, 23, 234, 123, 15]
|
||||
/// [i16]: [13070] [ 45 ] [-5609] [ 3963 ]
|
||||
///
|
||||
/// Sample rate conversion samples as floats.
|
||||
fn copy_every_other_and_convert_to_float(&self, source: &[u8], dest: &mut [f64], start: usize) {
|
||||
for (dest_index, x) in (start..source.len()).step_by(4).enumerate() {
|
||||
let sample_value = source[x] as i16 + ((source[x + 1] as i16) << 8);
|
||||
dest[dest_index] = sample_value.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IntermediateResamplerBuffer {
|
||||
fn drop(&mut self) {
|
||||
// Safe because this is calling to a FFI that was binded properly. Also
|
||||
// `left_resampler` and `right_resampler` are instantiated in the contructor.
|
||||
unsafe {
|
||||
r8b_delete(self.left_resampler);
|
||||
r8b_delete(self.right_resampler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use winapi::shared::mmreg::{
|
||||
SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY,
|
||||
SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_copy_every_other_and_convert_to_float() {
|
||||
let intermediate_src_buffer = IntermediateResamplerBuffer::new(
|
||||
48000, 44100, 480, 448, /* num_channel */ 2, /* channel_mask */ None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut left_channel_bytes: Vec<u8> = [25u16, 256, 1000, 2400]
|
||||
.iter()
|
||||
.map(|x| x.to_le_bytes())
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
let mut result = vec![0.0; 2];
|
||||
intermediate_src_buffer.copy_every_other_and_convert_to_float(
|
||||
&mut left_channel_bytes,
|
||||
&mut result,
|
||||
0,
|
||||
);
|
||||
assert_vec_float_eq(result, [25.0, 1000.0].to_vec());
|
||||
|
||||
let mut result2 = vec![0.0; 2];
|
||||
intermediate_src_buffer.copy_every_other_and_convert_to_float(
|
||||
&mut left_channel_bytes,
|
||||
&mut result2,
|
||||
2,
|
||||
);
|
||||
assert_vec_float_eq(result2, [256.0, 2400.0].to_vec());
|
||||
}
|
||||
|
||||
fn assert_vec_float_eq(vec1: Vec<f64>, vec2: Vec<f64>) {
|
||||
assert_eq!(vec1.len(), vec2.len());
|
||||
for (i, val) in vec1.into_iter().enumerate() {
|
||||
assert!((val - vec2[i]).abs() < f64::EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_next_period() {
|
||||
// Create an intermediate buffer that won't require resampling
|
||||
let mut intermediate_src_buffer = IntermediateResamplerBuffer::new(
|
||||
48000, 48000, 480, 513, /* num_channel */ 2, /* channel_mask */ None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(intermediate_src_buffer.get_next_period().is_none());
|
||||
|
||||
// 480 frames * 2 sample/frames * 2 bytes/sample = 1920 bytes
|
||||
let bytes_in_16bit_48k_hz = 1920;
|
||||
let buffer: Vec<u8> = vec![0; bytes_in_16bit_48k_hz];
|
||||
intermediate_src_buffer.convert_and_add(&buffer);
|
||||
|
||||
assert!(intermediate_src_buffer.get_next_period().is_none());
|
||||
|
||||
let buffer: Vec<u8> = vec![0; bytes_in_16bit_48k_hz];
|
||||
intermediate_src_buffer.convert_and_add(&buffer);
|
||||
|
||||
assert!(intermediate_src_buffer.get_next_period().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_perform_channel_conversion_mono() {
|
||||
let mut intermediate_src_buffer = IntermediateResamplerBuffer::new(
|
||||
/* from_sample_rate */ 48000, /* to_sample_rate */ 48000,
|
||||
/* guest_period_in_frames */ 480,
|
||||
/* shared_audio_engine_period_in_frames */ 513, /* num_channel */ 1,
|
||||
/* channel_mask */ None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let two_channel_samples = vec![5.0, 5.0, 2.0, 8.0];
|
||||
|
||||
for x in (0..two_channel_samples.len()).step_by(2) {
|
||||
let left = two_channel_samples[x];
|
||||
let right = two_channel_samples[x + 1];
|
||||
intermediate_src_buffer.perform_channel_conversion(left, right);
|
||||
}
|
||||
|
||||
assert_eq!(intermediate_src_buffer.ring_buf.len(), 2);
|
||||
assert_eq!(intermediate_src_buffer.ring_buf, vec![5.0, 5.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upmix_5_1() {
|
||||
let channel_mask = SPEAKER_FRONT_LEFT
|
||||
| SPEAKER_FRONT_RIGHT
|
||||
| SPEAKER_FRONT_CENTER
|
||||
| SPEAKER_LOW_FREQUENCY
|
||||
| SPEAKER_BACK_LEFT
|
||||
| SPEAKER_BACK_RIGHT;
|
||||
let mut intermediate_src_buffer = IntermediateResamplerBuffer::new(
|
||||
48000,
|
||||
44100,
|
||||
480,
|
||||
448,
|
||||
/* num_channel */ 6,
|
||||
/* channel_mask */ Some(channel_mask),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let two_channel_samples = vec![5.0, 5.0, 2.0, 8.0];
|
||||
for x in (0..two_channel_samples.len()).step_by(2) {
|
||||
let left = two_channel_samples[x];
|
||||
let right = two_channel_samples[x + 1];
|
||||
intermediate_src_buffer.perform_channel_conversion(left, right);
|
||||
}
|
||||
|
||||
assert_eq!(intermediate_src_buffer.ring_buf.len(), 12);
|
||||
// Only populate FL and FR channels and zero out the rest.
|
||||
assert_eq!(
|
||||
intermediate_src_buffer.ring_buf,
|
||||
vec![5.0, 5.0, 0.0, 0.0, 0.0, 0.0, 2.0, 8.0, 0.0, 0.0, 0.0, 0.0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upmix_7_1() {
|
||||
let channel_mask = SPEAKER_FRONT_LEFT
|
||||
| SPEAKER_FRONT_RIGHT
|
||||
| SPEAKER_FRONT_CENTER
|
||||
| SPEAKER_LOW_FREQUENCY
|
||||
| SPEAKER_BACK_LEFT
|
||||
| SPEAKER_BACK_RIGHT
|
||||
| SPEAKER_SIDE_LEFT
|
||||
| SPEAKER_SIDE_RIGHT;
|
||||
let mut intermediate_src_buffer = IntermediateResamplerBuffer::new(
|
||||
48000,
|
||||
44100,
|
||||
480,
|
||||
448,
|
||||
/* num_channel */ 8,
|
||||
/* channel_mask */ Some(channel_mask),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let two_channel_samples = vec![5.0, 5.0, 2.0, 8.0];
|
||||
for x in (0..two_channel_samples.len()).step_by(2) {
|
||||
let left = two_channel_samples[x];
|
||||
let right = two_channel_samples[x + 1];
|
||||
intermediate_src_buffer.perform_channel_conversion(left, right);
|
||||
}
|
||||
|
||||
assert_eq!(intermediate_src_buffer.ring_buf.len(), 16);
|
||||
// Only populate FL and FR channels and zero out the rest.
|
||||
assert_eq!(
|
||||
intermediate_src_buffer.ring_buf,
|
||||
vec![5.0, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
);
|
||||
}
|
||||
}
|
182
win_audio/src/lib.rs
Normal file
182
win_audio/src/lib.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
include!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/r8brain_sys/bindings.rs"
|
||||
));
|
||||
|
||||
macro_rules! check_hresult {
|
||||
($hr: expr, $error: expr, $msg: expr) => {
|
||||
if winapi::shared::winerror::FAILED($hr) {
|
||||
base::warn!("{}", $msg);
|
||||
Err($error)
|
||||
} else {
|
||||
Ok($hr)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod intermediate_resampler_buffer;
|
||||
mod win_audio_impl;
|
||||
use std::error;
|
||||
use win_audio_impl::*;
|
||||
|
||||
use audio_streams::{
|
||||
NoopStream, NoopStreamSource, PlaybackBufferStream, SampleFormat, StreamSource,
|
||||
};
|
||||
use base::{info, warn};
|
||||
use std::sync::Arc;
|
||||
use sync::Mutex;
|
||||
|
||||
pub type BoxError = Box<dyn error::Error + Send + Sync>;
|
||||
|
||||
/// Contains information about the audio engine's properties, such as its audio sample format
|
||||
/// and its period in frames.
|
||||
///
|
||||
/// This does exclude whether the bit depth is in the form of floats or ints. The bit depth form
|
||||
/// isn't used for sample rate conversion so it's excluded.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AudioSharedFormat {
|
||||
pub bit_depth: usize,
|
||||
pub frame_rate: usize,
|
||||
pub shared_audio_engine_period_in_frames: usize,
|
||||
pub channels: usize,
|
||||
// Only available for WAVEFORMATEXTENSIBLE
|
||||
pub channel_mask: Option<u32>,
|
||||
}
|
||||
|
||||
/// Implementation of StreamSource which will create the playback stream for the Windows
|
||||
/// audio engine.
|
||||
///
|
||||
/// Extending the StreamSource trait will allow us to make necessary changes without modifying
|
||||
/// the third party audiostream library.
|
||||
pub trait WinAudioServer: StreamSource {
|
||||
fn new_playback_stream_and_get_shared_format(
|
||||
&mut self,
|
||||
num_channels: usize,
|
||||
format: SampleFormat,
|
||||
frame_rate: usize,
|
||||
buffer_size: usize,
|
||||
) -> Result<(Arc<Mutex<Box<dyn PlaybackBufferStream>>>, AudioSharedFormat), BoxError>;
|
||||
|
||||
/// Evict the playback stream cache so that the audio device can be released, thus allowing
|
||||
/// for machines to go to sleep.
|
||||
fn evict_playback_stream_cache(&mut self);
|
||||
|
||||
/// Returns true if audio server is a noop stream. This determine if evicting a cache is worth
|
||||
/// doing
|
||||
fn is_noop_stream(&mut self) -> bool;
|
||||
}
|
||||
|
||||
impl WinAudioServer for WinAudio {
|
||||
fn new_playback_stream_and_get_shared_format(
|
||||
&mut self,
|
||||
num_channels: usize,
|
||||
_format: SampleFormat,
|
||||
frame_rate: usize,
|
||||
buffer_size: usize,
|
||||
) -> Result<(Arc<Mutex<Box<dyn PlaybackBufferStream>>>, AudioSharedFormat), BoxError> {
|
||||
let hr = WinAudio::co_init_once_per_thread();
|
||||
let _ = check_hresult!(hr, RenderError::from(hr), "Co Initialized failed");
|
||||
|
||||
// Return the existing stream if we have one.
|
||||
// This is mainly to reduce audio skips caused by a buffer underrun on the guest. An
|
||||
// underrun causes the guest to stop the audio stream, but then start it back up when the
|
||||
// guest buffer is filled again.
|
||||
if let Some((playback_buffer_stream, audio_format)) =
|
||||
self.cached_playback_buffer_stream.as_ref()
|
||||
{
|
||||
info!("Reusing playback_buffer_stream.");
|
||||
return Ok((playback_buffer_stream.clone(), audio_format.clone()));
|
||||
}
|
||||
|
||||
let (playback_buffer_stream, audio_shared_format): (
|
||||
Arc<Mutex<Box<dyn PlaybackBufferStream>>>,
|
||||
AudioSharedFormat,
|
||||
) = match win_audio_impl::WinAudioRenderer::new(
|
||||
num_channels,
|
||||
frame_rate as u32,
|
||||
buffer_size,
|
||||
) {
|
||||
Ok(renderer) => {
|
||||
let audio_shared_format = renderer.device.audio_shared_format.clone();
|
||||
let renderer_arc = Arc::new(Mutex::new(
|
||||
Box::new(renderer) as Box<dyn PlaybackBufferStream>
|
||||
));
|
||||
self.cached_playback_buffer_stream =
|
||||
Some((renderer_arc.clone(), audio_shared_format.clone()));
|
||||
(renderer_arc, audio_shared_format)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to create WinAudioRenderer. Fallback to NoopStream with error: {}",
|
||||
e
|
||||
);
|
||||
(
|
||||
Arc::new(Mutex::new(Box::new(NoopStream::new(
|
||||
num_channels,
|
||||
SampleFormat::S16LE,
|
||||
frame_rate as u32,
|
||||
buffer_size,
|
||||
)))),
|
||||
AudioSharedFormat {
|
||||
bit_depth: 16,
|
||||
frame_rate,
|
||||
channels: 2,
|
||||
shared_audio_engine_period_in_frames: frame_rate / 100,
|
||||
channel_mask: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Ok((playback_buffer_stream, audio_shared_format))
|
||||
}
|
||||
|
||||
fn evict_playback_stream_cache(&mut self) {
|
||||
self.cached_playback_buffer_stream = None;
|
||||
}
|
||||
|
||||
fn is_noop_stream(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl WinAudioServer for NoopStreamSource {
|
||||
fn new_playback_stream_and_get_shared_format(
|
||||
&mut self,
|
||||
num_channels: usize,
|
||||
format: SampleFormat,
|
||||
frame_rate: usize,
|
||||
buffer_size: usize,
|
||||
) -> Result<(Arc<Mutex<Box<dyn PlaybackBufferStream>>>, AudioSharedFormat), BoxError> {
|
||||
let (_, playback_buffer_stream) = self
|
||||
.new_playback_stream(num_channels, format, frame_rate as u32, buffer_size)
|
||||
.unwrap();
|
||||
Ok((
|
||||
Arc::new(Mutex::new(playback_buffer_stream)),
|
||||
AudioSharedFormat {
|
||||
bit_depth: 16,
|
||||
frame_rate,
|
||||
channels: 2,
|
||||
shared_audio_engine_period_in_frames: frame_rate / 100,
|
||||
channel_mask: None,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn evict_playback_stream_cache(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_noop_stream(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_win_audio_device() -> Result<WinAudio, BoxError> {
|
||||
WinAudio::new()
|
||||
}
|
101
win_audio/src/r8brain_sys/bindings.rs
Normal file
101
win_audio/src/r8brain_sys/bindings.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
/* automatically generated by rust-bindgen
|
||||
See instructions from: https://rust-lang.github.io/rust-bindgen/print.html
|
||||
|
||||
Original library: https://github.com/avaneev/r8brain-free-src
|
||||
|
||||
This library should be included in the third_party folder when running
|
||||
`repo sync`. Otherwise the library can be cloned with:
|
||||
|
||||
git clone "sso://play-internal/battlestar/kiwivm/thirdparty/r8brain""
|
||||
|
||||
To generate bindings:
|
||||
1. go to the r8brain-free-src location. In my case it'll be:
|
||||
|
||||
cd C:\src\kiwivm\third_party\r8brain\
|
||||
|
||||
2. Run bindgen and put the binding file in the win_audio r8brain_sys directory"
|
||||
|
||||
bindgen .\DLL\r8bsrc.h -o ..\..\platform\crosvm\win_audio\src\r8brain_sys\binding.rs -- -x c++
|
||||
*/
|
||||
|
||||
#[doc = " Resampler object handle."]
|
||||
pub type CR8BResampler = *mut ::std::os::raw::c_void;
|
||||
#[doc = "< 16-bit precision resampler."]
|
||||
#[doc = "<"]
|
||||
pub const ER8BResamplerRes_r8brr16: ER8BResamplerRes = 0;
|
||||
#[doc = "< 16-bit precision resampler for impulse responses."]
|
||||
#[doc = "<"]
|
||||
pub const ER8BResamplerRes_r8brr16IR: ER8BResamplerRes = 1;
|
||||
#[doc = "< 24-bit precision resampler (including 32-bit floating"]
|
||||
#[doc = "< point)."]
|
||||
#[doc = "<"]
|
||||
pub const ER8BResamplerRes_r8brr24: ER8BResamplerRes = 2;
|
||||
#[doc = " Possible resampler object resolutions."]
|
||||
pub type ER8BResamplerRes = i32;
|
||||
extern "C" {
|
||||
#[doc = " Function creates a new linear-phase resampler object."]
|
||||
#[doc = ""]
|
||||
#[doc = " @param SrcSampleRate Source signal sample rate. Both sample rates can"]
|
||||
#[doc = " be specified as a ratio, e.g. SrcSampleRate = 1.0, DstSampleRate = 2.0."]
|
||||
#[doc = " @param DstSampleRate Destination signal sample rate."]
|
||||
#[doc = " @param MaxInLen The maximal planned length of the input buffer (in samples)"]
|
||||
#[doc = " that will be passed to the resampler. The resampler relies on this value as"]
|
||||
#[doc = " it allocates intermediate buffers. Input buffers longer than this value"]
|
||||
#[doc = " should never be supplied to the resampler. Note that the resampler may use"]
|
||||
#[doc = " the input buffer itself for intermediate sample data storage."]
|
||||
#[doc = " @param Res Resampler's required resolution."]
|
||||
pub fn r8b_create(
|
||||
SrcSampleRate: f64,
|
||||
DstSampleRate: f64,
|
||||
MaxInLen: ::std::os::raw::c_int,
|
||||
ReqTransBand: f64,
|
||||
Res: ER8BResamplerRes,
|
||||
) -> CR8BResampler;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Function deletes a resampler previously created via the r8b_create()"]
|
||||
#[doc = " function."]
|
||||
#[doc = ""]
|
||||
#[doc = " @param rs Resampler object to delete."]
|
||||
pub fn r8b_delete(rs: CR8BResampler);
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Function clears (resets) the state of the resampler object and returns it"]
|
||||
#[doc = " to the state after construction. All input data accumulated in the"]
|
||||
#[doc = " internal buffer of this resampler object so far will be discarded."]
|
||||
#[doc = ""]
|
||||
#[doc = " @param rs Resampler object to clear."]
|
||||
pub fn r8b_clear(rs: CR8BResampler);
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Function performs sample rate conversion."]
|
||||
#[doc = ""]
|
||||
#[doc = " If the source and destination sample rates are equal, the resampler will do"]
|
||||
#[doc = " nothing and will simply return the input buffer unchanged."]
|
||||
#[doc = ""]
|
||||
#[doc = " You do not need to allocate an intermediate output buffer for use with this"]
|
||||
#[doc = " function. If required, the resampler will allocate a suitable intermediate"]
|
||||
#[doc = " output buffer itself."]
|
||||
#[doc = ""]
|
||||
#[doc = " @param rs Resampler object that performs processing."]
|
||||
#[doc = " @param ip0 Input buffer. This buffer may be used as output buffer by this"]
|
||||
#[doc = " function."]
|
||||
#[doc = " @param l The number of samples available in the input buffer."]
|
||||
#[doc = " @param[out] op0 This variable receives the pointer to the resampled data."]
|
||||
#[doc = " This pointer may point to the address within the \"ip0\" input buffer, or to"]
|
||||
#[doc = " *this object's internal buffer. In real-time applications it is suggested"]
|
||||
#[doc = " to pass this pointer to the next output audio block and consume any data"]
|
||||
#[doc = " left from the previous output audio block first before calling the"]
|
||||
#[doc = " r8b_process() function again. The buffer pointed to by the \"op0\" on return"]
|
||||
#[doc = " may be owned by the resampler, so it should not be freed by the caller."]
|
||||
#[doc = " @return The number of samples available in the \"op0\" output buffer. If the"]
|
||||
#[doc = " data from the output buffer \"op0\" is going to be written to a bigger output"]
|
||||
#[doc = " buffer, it is suggested to check the returned number of samples so that no"]
|
||||
#[doc = " overflow of the bigger output buffer happens."]
|
||||
pub fn r8b_process(
|
||||
rs: CR8BResampler,
|
||||
ip0: *mut f64,
|
||||
l: ::std::os::raw::c_int,
|
||||
op0: *mut *mut f64,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
290
win_audio/src/win_audio_impl/completion_handler.rs
Normal file
290
win_audio/src/win_audio_impl/completion_handler.rs
Normal file
|
@ -0,0 +1,290 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use base::{error, info, Event, EventExt};
|
||||
use libc::c_void;
|
||||
use std::sync::atomic::{AtomicU32, Ordering::SeqCst};
|
||||
use winapi::shared::guiddef::{IsEqualGUID, REFIID};
|
||||
use winapi::shared::minwindef::ULONG;
|
||||
use winapi::shared::winerror::{E_INVALIDARG, E_NOINTERFACE, NOERROR, S_OK};
|
||||
use winapi::um::mmdeviceapi::*;
|
||||
use winapi::um::objidlbase::IAgileObject;
|
||||
use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl};
|
||||
use winapi::um::winnt::HRESULT;
|
||||
use winapi::Interface;
|
||||
use wio::com::ComPtr;
|
||||
|
||||
pub const ACTIVATE_AUDIO_INTERFACE_COMPLETION_EVENT: &str = "ActivateAudioCompletionEvent";
|
||||
|
||||
/// This struct is used to create the completion handler `IActivateAudioInterfaceCompletionHandler`
|
||||
/// that is passed into `ActivateAudioInterfaceAsync`. In other words, the first field in the struct
|
||||
/// must be `IActivateAudioInterfaceCompletionHandlerVtbl`.
|
||||
///
|
||||
/// This struct matches the `IActivateAudioInterfaceCompletionHandler` struct with the addition of
|
||||
/// the `ref_count` below `lp_vtbl` which is used to keep a reference count to the completion
|
||||
/// handler.
|
||||
#[repr(C)]
|
||||
pub struct WinAudioActivateAudioInterfaceCompletionHandler {
|
||||
pub lp_vtbl: &'static IActivateAudioInterfaceCompletionHandlerVtbl,
|
||||
ref_count: AtomicU32,
|
||||
}
|
||||
|
||||
impl WinAudioActivateAudioInterfaceCompletionHandler {
|
||||
/// The ComPtr is a `WinAudioActivateAudioInterfaceCompletionHandler` casted as an
|
||||
/// `IActivateAudioInterfaceCompletionHandler`.
|
||||
pub fn create_com_ptr() -> ComPtr<IActivateAudioInterfaceCompletionHandler> {
|
||||
let win_completion_handler = Box::new(WinAudioActivateAudioInterfaceCompletionHandler {
|
||||
lp_vtbl: IWIN_AUDIO_COMPLETION_HANDLER_VTBL,
|
||||
ref_count: AtomicU32::new(1),
|
||||
});
|
||||
|
||||
// This is safe if the value passed into `from_raw` is structured in a way where it can
|
||||
// match `IActivateAudioInterfaceCompletionHandler`.
|
||||
// Since `win_completion_handler.cast_to_com_ptr()` does, this is safe.
|
||||
// Safe because we are passing in a valid COM object that implements `IUnknown` into
|
||||
// `from_raw`.
|
||||
unsafe {
|
||||
ComPtr::from_raw(Box::into_raw(win_completion_handler)
|
||||
as *mut IActivateAudioInterfaceCompletionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unsafe if `thing` cannot because casted to
|
||||
/// `WinAudioActivateAudioInterfaceCompletionHandler`. This is safe because `thing` is
|
||||
/// originally a `WinAudioActivateAudioInterfaceCompletionHandler.
|
||||
unsafe fn increment_counter(&self) -> ULONG {
|
||||
self.ref_count.fetch_add(1, SeqCst) + 1
|
||||
}
|
||||
|
||||
fn decrement_counter(&mut self) -> ULONG {
|
||||
let old_val = self.ref_count.fetch_sub(1, SeqCst);
|
||||
if old_val == 0 {
|
||||
panic!("Attempted to decrement WinAudioActivateInterfaceCompletionHandler ref count when it is already 0.");
|
||||
}
|
||||
old_val - 1
|
||||
}
|
||||
|
||||
fn activate_completed() {
|
||||
info!("Activate Completed handler called from ActiviateAudioInterfaceAsync.");
|
||||
match Event::open(ACTIVATE_AUDIO_INTERFACE_COMPLETION_EVENT) {
|
||||
Ok(event) => {
|
||||
event.write(1).expect("Failed to notify audioclientevent");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Activate audio event cannot be opened: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinAudioActivateAudioInterfaceCompletionHandler {
|
||||
fn drop(&mut self) {
|
||||
info!("IActivateAudioInterfaceCompletionHandler is dropped.");
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the callback when `ActivateAudioInterfaceAsync` is completed. When this is callback is
|
||||
/// triggered, the IAudioClient will be available.
|
||||
/// More info: https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-iactivateaudiointerfacecompletionhandler-activatecompleted
|
||||
///
|
||||
/// Safe because all that happens is an event gets called.
|
||||
unsafe extern "system" fn activate_completed(
|
||||
_completion_handler: *mut IActivateAudioInterfaceCompletionHandler,
|
||||
_activate_operation: *mut IActivateAudioInterfaceAsyncOperation,
|
||||
) -> HRESULT {
|
||||
WinAudioActivateAudioInterfaceCompletionHandler::activate_completed();
|
||||
S_OK
|
||||
}
|
||||
|
||||
const IWIN_AUDIO_COMPLETION_HANDLER_VTBL: &IActivateAudioInterfaceCompletionHandlerVtbl =
|
||||
// Implementation based on
|
||||
// https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus
|
||||
&IActivateAudioInterfaceCompletionHandlerVtbl {
|
||||
parent: IUnknownVtbl {
|
||||
QueryInterface: {
|
||||
/// Safe because if `this` is not implemented (fails the RIID check) this function
|
||||
/// will just return. If it valid, it should be able to safely increment the ref
|
||||
/// counter and set the pointer `ppv_object`.
|
||||
unsafe extern "system" fn query_interface(
|
||||
this: *mut IUnknown,
|
||||
riid: REFIID,
|
||||
ppv_object: *mut *mut c_void,
|
||||
) -> HRESULT {
|
||||
if ppv_object.is_null() {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
*ppv_object = std::ptr::null_mut();
|
||||
|
||||
// Check for valid RIID's
|
||||
if IsEqualGUID(&*riid, &IUnknown::uuidof())
|
||||
|| IsEqualGUID(
|
||||
&*riid,
|
||||
&IActivateAudioInterfaceCompletionHandler::uuidof(),
|
||||
)
|
||||
|| IsEqualGUID(&*riid, &IAgileObject::uuidof())
|
||||
{
|
||||
*ppv_object = this as *mut c_void;
|
||||
(*this).AddRef();
|
||||
return NOERROR;
|
||||
}
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
query_interface
|
||||
},
|
||||
AddRef: {
|
||||
/// Unsafe if `this` cannot because casted to
|
||||
/// `WinAudioActivateAudioInterfaceCompletionHandler`.
|
||||
///
|
||||
/// This is safe because `this` is
|
||||
/// originally a `WinAudioActivateAudioInterfaceCompletionHandler.
|
||||
unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG {
|
||||
info!("Adding ref in IActivateAudioInterfaceCompletionHandler.");
|
||||
let win_audio_completion_handler =
|
||||
this as *mut WinAudioActivateAudioInterfaceCompletionHandler;
|
||||
(*win_audio_completion_handler).increment_counter()
|
||||
}
|
||||
add_ref
|
||||
},
|
||||
Release: {
|
||||
/// Unsafe if `this` cannot because casted to
|
||||
/// `WinAudioActivateAudioInterfaceCompletionHandler`. Also would be unsafe
|
||||
/// if `release` is called more than `add_ref`.
|
||||
///
|
||||
/// This is safe because `this` is
|
||||
/// originally a `WinAudioActivateAudioInterfaceCompletionHandler and isn't called
|
||||
/// more than `add_ref`.
|
||||
unsafe extern "system" fn release(this: *mut IUnknown) -> ULONG {
|
||||
info!("Releasing ref in IActivateAudioInterfaceCompletionHandler.");
|
||||
// Decrementing will free the `this` pointer if it's ref_count becomes 0.
|
||||
let win_audio_completion_handler =
|
||||
this as *mut WinAudioActivateAudioInterfaceCompletionHandler;
|
||||
let ref_count = (*win_audio_completion_handler).decrement_counter();
|
||||
if ref_count == 0 {
|
||||
// Delete the pointer
|
||||
Box::from_raw(
|
||||
this as *mut WinAudioActivateAudioInterfaceCompletionHandler,
|
||||
);
|
||||
}
|
||||
ref_count
|
||||
}
|
||||
release
|
||||
},
|
||||
},
|
||||
ActivateCompleted: activate_completed,
|
||||
};
|
||||
|
||||
/// `ActivateAudioInterfaceAsync` requires that `IActivateAudioCompletionHandler` to implement
|
||||
/// `IAgileObject`, which means it is free threaded and can be called from any apartment. These
|
||||
/// traits should allow it to do that.
|
||||
unsafe impl Send for WinAudioActivateAudioInterfaceCompletionHandler {}
|
||||
unsafe impl Sync for WinAudioActivateAudioInterfaceCompletionHandler {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_query_interface_valid() {
|
||||
let completion_handler = WinAudioActivateAudioInterfaceCompletionHandler::create_com_ptr();
|
||||
let invalid_ref_iid = IUnknown::uuidof();
|
||||
let mut null_value = std::ptr::null_mut();
|
||||
let ppv_object: *mut *mut c_void = &mut null_value;
|
||||
|
||||
// Calling `QueryInterface`
|
||||
let res = unsafe {
|
||||
((*(*completion_handler).lpVtbl).parent.QueryInterface)(
|
||||
completion_handler.as_raw() as *mut IUnknown,
|
||||
&invalid_ref_iid,
|
||||
ppv_object,
|
||||
)
|
||||
};
|
||||
assert_eq!(res, NOERROR);
|
||||
|
||||
// Release the reference from `QueryInteface` by calling `Release`
|
||||
release(&completion_handler);
|
||||
|
||||
let invalid_ref_iid = IActivateAudioInterfaceCompletionHandler::uuidof();
|
||||
let res = unsafe {
|
||||
((*(*completion_handler).lpVtbl).parent.QueryInterface)(
|
||||
completion_handler.as_raw() as *mut IUnknown,
|
||||
&invalid_ref_iid,
|
||||
ppv_object,
|
||||
)
|
||||
};
|
||||
assert_eq!(res, NOERROR);
|
||||
|
||||
release(&completion_handler);
|
||||
|
||||
let invalid_ref_iid = IAgileObject::uuidof();
|
||||
let res = unsafe {
|
||||
((*(*completion_handler).lpVtbl).parent.QueryInterface)(
|
||||
completion_handler.as_raw() as *mut IUnknown,
|
||||
&invalid_ref_iid,
|
||||
ppv_object,
|
||||
)
|
||||
};
|
||||
release(&completion_handler);
|
||||
assert_eq!(res, NOERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_interface_invalid() {
|
||||
let completion_handler = WinAudioActivateAudioInterfaceCompletionHandler::create_com_ptr();
|
||||
let invalid_ref_iid = IMMDeviceCollection::uuidof();
|
||||
let mut null_value = std::ptr::null_mut();
|
||||
let ppv_object: *mut *mut c_void = &mut null_value;
|
||||
|
||||
// Call `QueryInterface`
|
||||
let res = unsafe {
|
||||
((*(*completion_handler).lpVtbl).parent.QueryInterface)(
|
||||
completion_handler.as_raw() as *mut IUnknown,
|
||||
&invalid_ref_iid,
|
||||
ppv_object,
|
||||
)
|
||||
};
|
||||
assert_eq!(res, E_NOINTERFACE)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_ref() {
|
||||
// ref_count = 1
|
||||
let completion_handler = WinAudioActivateAudioInterfaceCompletionHandler::create_com_ptr();
|
||||
// ref_count = 2
|
||||
let ref_count = add_ref(&completion_handler);
|
||||
assert_eq!(ref_count, 2);
|
||||
// ref_count = 1
|
||||
release(&completion_handler);
|
||||
// ref_count = 0 since ComPtr drops
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_release() {
|
||||
// ref_count = 1
|
||||
let completion_handler = WinAudioActivateAudioInterfaceCompletionHandler::create_com_ptr();
|
||||
// ref_count = 2
|
||||
let ref_count = add_ref(&completion_handler);
|
||||
assert_eq!(ref_count, 2);
|
||||
// ref_count = 1
|
||||
let ref_count = release(&completion_handler);
|
||||
assert_eq!(ref_count, 1);
|
||||
// ref_count = 0 since ComPtr drops
|
||||
}
|
||||
|
||||
fn release(completion_handler: &ComPtr<IActivateAudioInterfaceCompletionHandler>) -> ULONG {
|
||||
unsafe {
|
||||
((*(*completion_handler).lpVtbl).parent.Release)(
|
||||
completion_handler.as_raw() as *mut IUnknown
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_ref(completion_handler: &ComPtr<IActivateAudioInterfaceCompletionHandler>) -> ULONG {
|
||||
unsafe {
|
||||
((*(*completion_handler).lpVtbl).parent.AddRef)(
|
||||
completion_handler.as_raw() as *mut IUnknown
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
1008
win_audio/src/win_audio_impl/mod.rs
Normal file
1008
win_audio/src/win_audio_impl/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
1057
win_audio/src/win_audio_impl/wave_format.rs
Normal file
1057
win_audio/src/win_audio_impl/wave_format.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue