blob: 0ef9cadc3b3af0fa384ee316a90965e8cb80fba6 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::convert::TryInto;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Formatter;
use base::warn;
use metrics::event_details_proto::WaveFormat;
use metrics::event_details_proto::WaveFormatDetails;
use metrics::event_details_proto::WaveFormat_WaveFormatSubFormat;
use winapi::shared::guiddef::IsEqualGUID;
use winapi::shared::guiddef::GUID;
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_ADPCM;
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_ALAW;
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_ANALOG;
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_DRM;
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_MPEG;
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_MULAW;
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_PCM;
use winapi::shared::mmreg::SPEAKER_FRONT_CENTER;
use winapi::shared::mmreg::SPEAKER_FRONT_LEFT;
use winapi::shared::mmreg::SPEAKER_FRONT_RIGHT;
use winapi::shared::mmreg::WAVEFORMATEX;
use winapi::shared::mmreg::WAVEFORMATEXTENSIBLE;
use winapi::shared::mmreg::WAVE_FORMAT_EXTENSIBLE;
use winapi::shared::mmreg::WAVE_FORMAT_IEEE_FLOAT;
#[cfg(not(test))]
use winapi::um::combaseapi::CoTaskMemFree;
use crate::AudioSharedFormat;
use crate::MONO_CHANNEL_COUNT;
use crate::STEREO_CHANNEL_COUNT;
pub type WaveFormatDetailsProto = WaveFormatDetails;
pub type WaveFormatProto = WaveFormat;
pub type SubFormatProto = WaveFormat_WaveFormatSubFormat;
/// Wrapper around `WAVEFORMATEX` and `WAVEFORMATEXTENSIBLE` to hide some of the unsafe calls
/// that could be made.
pub enum WaveAudioFormat {
/// Format where channels are capped at 2.
WaveFormat(WAVEFORMATEX),
/// Format where channels can be >2. (It can still have <=2 channels)
WaveFormatExtensible(WAVEFORMATEXTENSIBLE),
}
impl WaveAudioFormat {
/// Wraps a WAVEFORMATEX pointer to make it's use more safe.
///
/// # Safety
/// Unsafe if `wave_format_ptr` is pointing to null. This function will assume it's not null
/// and dereference it.
/// Also `format_ptr` will be deallocated after this function completes, so it cannot be used.
#[allow(clippy::let_and_return)]
pub unsafe fn new(format_ptr: *mut WAVEFORMATEX) -> Self {
let format_tag = { (*format_ptr).wFormatTag };
let result = if format_tag != WAVE_FORMAT_EXTENSIBLE {
warn!(
"Default Mix Format does not have format_tag WAVE_FORMAT_EXTENSIBLE. It is: {}",
format_tag
);
WaveAudioFormat::WaveFormat(*format_ptr)
} else {
WaveAudioFormat::WaveFormatExtensible(*(format_ptr as *const WAVEFORMATEXTENSIBLE))
};
// WAVEFORMATEX and WAVEFORMATEXTENSIBLE both implement the Copy trait, so they have been
// copied to the WaveAudioFormat enum. Therefore, it is safe to free the memory
// `format_ptr` is pointing to.
// In a test, WAVEFORMATEX is initiated by us, not by Windows, so calling this function
// could cause a STATUS_HEAP_CORRUPTION exception.
#[cfg(not(test))]
CoTaskMemFree(format_ptr as *mut std::ffi::c_void);
result
}
pub fn get_num_channels(&self) -> u16 {
match self {
WaveAudioFormat::WaveFormat(wave_format) => wave_format.nChannels,
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => {
wave_format_extensible.Format.nChannels
}
}
}
// Modifies `WAVEFORMATEXTENSIBLE` to have the values passed into the function params.
// Currently it should only modify the bit_depth if it's != 32 and the data format if it's not
// float.
pub fn modify_mix_format(&mut self, target_bit_depth: usize, ks_data_format: GUID) {
let default_num_channels = self.get_num_channels();
fn calc_avg_bytes_per_sec(num_channels: u16, bit_depth: u16, samples_per_sec: u32) -> u32 {
num_channels as u32 * (bit_depth as u32 / 8) * samples_per_sec
}
fn calc_block_align(num_channels: u16, bit_depth: u16) -> u16 {
(bit_depth / 8) * num_channels
}
match self {
WaveAudioFormat::WaveFormat(wave_format) => {
if default_num_channels > STEREO_CHANNEL_COUNT {
warn!("WAVEFORMATEX shouldn't have >2 channels.");
}
// Force the format to be the only supported format (32 bit float)
if wave_format.wBitsPerSample != target_bit_depth as u16
|| wave_format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT
{
wave_format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
wave_format.nChannels =
std::cmp::min(STEREO_CHANNEL_COUNT as u16, default_num_channels);
wave_format.wBitsPerSample = target_bit_depth as u16;
wave_format.nAvgBytesPerSec = calc_avg_bytes_per_sec(
wave_format.nChannels,
wave_format.wBitsPerSample,
wave_format.nSamplesPerSec,
);
wave_format.nBlockAlign =
calc_block_align(wave_format.nChannels, wave_format.wBitsPerSample);
}
}
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => {
// WAVE_FORMAT_EXTENSIBLE uses the #[repr(packed)] flag so the compiler might
// unalign the fields. Thus, the fields will be copied to a local variable to
// prevent segfaults. For more information:
// https://github.com/rust-lang/rust/issues/46043
let sub_format = wave_format_extensible.SubFormat;
if wave_format_extensible.Format.wBitsPerSample != target_bit_depth as u16
|| !IsEqualGUID(&sub_format, &ks_data_format)
{
// wFormatTag won't be changed
wave_format_extensible.Format.nChannels = default_num_channels;
wave_format_extensible.Format.wBitsPerSample = target_bit_depth as u16;
// nSamplesPerSec should stay the same
// Calculated with a bit depth of 32bits
wave_format_extensible.Format.nAvgBytesPerSec = calc_avg_bytes_per_sec(
wave_format_extensible.Format.nChannels,
wave_format_extensible.Format.wBitsPerSample,
wave_format_extensible.Format.nSamplesPerSec,
);
wave_format_extensible.Format.nBlockAlign = calc_block_align(
wave_format_extensible.Format.nChannels,
wave_format_extensible.Format.wBitsPerSample,
);
// 22 is the size typically used when the format tag is WAVE_FORMAT_EXTENSIBLE.
// Since the `Initialize` syscall takes in a WAVEFORMATEX, this tells Windows
// how many bytes are left after the `Format` field
// (ie. Samples, dwChannelMask, SubFormat) so that it can cast to
// WAVEFORMATEXTENSIBLE safely.
wave_format_extensible.Format.cbSize = 22;
wave_format_extensible.Samples = target_bit_depth as u16;
let n_channels = wave_format_extensible.Format.nChannels;
// The channel masks are defined here:
// https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible#remarks
wave_format_extensible.dwChannelMask = match n_channels {
STEREO_CHANNEL_COUNT => SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
MONO_CHANNEL_COUNT => SPEAKER_FRONT_CENTER,
_ => {
// Don't change channel mask if it's >2 channels.
wave_format_extensible.dwChannelMask
}
};
wave_format_extensible.SubFormat = ks_data_format;
}
}
}
}
pub fn as_ptr(&self) -> *const WAVEFORMATEX {
match self {
WaveAudioFormat::WaveFormat(wave_format) => wave_format as *const WAVEFORMATEX,
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => {
wave_format_extensible as *const _ as *const WAVEFORMATEX
}
}
}
pub fn get_shared_audio_engine_period_in_frames(
&self,
shared_default_size_in_100nanoseconds: f64,
) -> usize {
// a 100 nanosecond unit is 1 * 10^-7 seconds
//
// To convert a 100nanoseconds value to # of frames in a period, we multiple by the
// frame rate (nSamplesPerSec. Sample rate == Frame rate) and then divide by 10000000
// in order to convert 100nanoseconds to seconds.
let samples_per_sec = match self {
WaveAudioFormat::WaveFormat(wave_format) => wave_format.nSamplesPerSec,
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => {
wave_format_extensible.Format.nSamplesPerSec
}
};
((samples_per_sec as f64 * shared_default_size_in_100nanoseconds as f64) / 10000000.0)
.ceil() as usize
}
pub fn create_audio_shared_format(
&self,
shared_audio_engine_period_in_frames: usize,
) -> AudioSharedFormat {
match self {
WaveAudioFormat::WaveFormat(wave_format) => AudioSharedFormat {
bit_depth: wave_format.wBitsPerSample as usize,
frame_rate: wave_format.nSamplesPerSec as usize,
shared_audio_engine_period_in_frames,
channels: wave_format.nChannels as usize,
channel_mask: None,
},
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => AudioSharedFormat {
bit_depth: wave_format_extensible.Format.wBitsPerSample as usize,
frame_rate: wave_format_extensible.Format.nSamplesPerSec as usize,
shared_audio_engine_period_in_frames,
channels: wave_format_extensible.Format.nChannels as usize,
channel_mask: Some(wave_format_extensible.dwChannelMask),
},
}
}
#[cfg(test)]
fn take_waveformatex(self) -> WAVEFORMATEX {
match self {
WaveAudioFormat::WaveFormat(wave_format) => wave_format,
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => {
// Safe because `wave_format_extensible` can't be a null pointer, otherwise the
// constructor would've failed. This will also give the caller ownership of this
// struct.
unsafe { *(&wave_format_extensible as *const _ as *const WAVEFORMATEX) }
}
}
}
#[cfg(test)]
fn take_waveformatextensible(self) -> WAVEFORMATEXTENSIBLE {
match self {
WaveAudioFormat::WaveFormat(_wave_format) => {
panic!("Format is WAVEFORMATEX. Can't convert to WAVEFORMATEXTENSBILE.")
}
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => wave_format_extensible,
}
}
}
impl Debug for WaveAudioFormat {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let res = match self {
WaveAudioFormat::WaveFormat(wave_format) => {
format!(
"wFormatTag: {}, \nnChannels: {}, \nnSamplesPerSec: {}, \nnAvgBytesPerSec: \
{}, \nnBlockAlign: {}, \nwBitsPerSample: {}, \ncbSize: {}",
{ wave_format.wFormatTag },
{ wave_format.nChannels },
{ wave_format.nSamplesPerSec },
{ wave_format.nAvgBytesPerSec },
{ wave_format.nBlockAlign },
{ wave_format.wBitsPerSample },
{ wave_format.cbSize },
)
}
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => {
let audio_engine_format = format!(
"wFormatTag: {}, \nnChannels: {}, \nnSamplesPerSec: {}, \nnAvgBytesPerSec: \
{}, \nnBlockAlign: {}, \nwBitsPerSample: {}, \ncbSize: {}",
{ wave_format_extensible.Format.wFormatTag },
{ wave_format_extensible.Format.nChannels },
{ wave_format_extensible.Format.nSamplesPerSec },
{ wave_format_extensible.Format.nAvgBytesPerSec },
{ wave_format_extensible.Format.nBlockAlign },
{ wave_format_extensible.Format.wBitsPerSample },
{ wave_format_extensible.Format.cbSize },
);
let subformat = wave_format_extensible.SubFormat;
// TODO(b/240186720): Passing in `KSDATAFORMAT_SUBTYPE_PCM` will cause a
// freeze. IsEqualGUID is unsafe even though it isn't marked as such. Look into
// fixing or possibily write our own, that works.
//
// This check would be a nice to have, but not necessary. Right now, the subformat
// used will always be `IEEE_FLOAT`, so this check will be useless if nothing
// changes.
//
// if !IsEqualGUID(
// &{ wave_format_extensible.SubFormat },
// &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
// ) {
// warn!("Audio Engine format is NOT IEEE FLOAT");
// }
let audio_engine_extensible_format = format!(
"\nSamples: {}, \ndwChannelMask: {}, \nSubFormat: {}-{}-{}-{:?}",
{ wave_format_extensible.Samples },
{ wave_format_extensible.dwChannelMask },
subformat.Data1,
subformat.Data2,
subformat.Data3,
subformat.Data4,
);
format!("{}{}", audio_engine_format, audio_engine_extensible_format)
}
};
write!(f, "{}", res)
}
}
#[cfg(test)]
impl PartialEq for WaveAudioFormat {
fn eq(&self, other: &Self) -> bool {
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return false;
}
fn are_formats_same(
wave_format_pointer: *const u8,
other_format_pointer: *const u8,
cb_size: usize,
) -> bool {
let wave_format_bytes: &[u8] = unsafe {
std::slice::from_raw_parts(
wave_format_pointer,
std::mem::size_of::<WAVEFORMATEX>() + cb_size,
)
};
let other_bytes: &[u8] = unsafe {
std::slice::from_raw_parts(
other_format_pointer,
std::mem::size_of::<WAVEFORMATEX>() + cb_size,
)
};
!wave_format_bytes
.iter()
.zip(other_bytes)
.map(|(x, y)| x.cmp(y))
.any(|ord| ord != std::cmp::Ordering::Equal)
}
match self {
WaveAudioFormat::WaveFormat(wave_format) => match other {
WaveAudioFormat::WaveFormat(other_wave_format) => {
if wave_format.cbSize != other_wave_format.cbSize {
return false;
}
are_formats_same(
wave_format as *const _ as *const u8,
other_wave_format as *const _ as *const u8,
wave_format.cbSize as usize,
)
}
WaveAudioFormat::WaveFormatExtensible(_) => unreachable!(),
},
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => match other {
WaveAudioFormat::WaveFormatExtensible(other_wave_format_extensible) => {
if wave_format_extensible.Format.cbSize
!= other_wave_format_extensible.Format.cbSize
{
return false;
}
are_formats_same(
wave_format_extensible as *const _ as *const u8,
other_wave_format_extensible as *const _ as *const u8,
wave_format_extensible.Format.cbSize as usize,
)
}
WaveAudioFormat::WaveFormat(_) => unreachable!(),
},
}
}
}
impl From<&WaveAudioFormat> for WaveFormatProto {
fn from(format: &WaveAudioFormat) -> WaveFormatProto {
let mut wave_format_proto = WaveFormatProto::new();
match format {
WaveAudioFormat::WaveFormat(wave_format) => {
wave_format_proto.set_format_tag(wave_format.wFormatTag.into());
wave_format_proto.set_channels(wave_format.nChannels.into());
wave_format_proto.set_samples_per_sec(
wave_format
.nSamplesPerSec
.try_into()
.expect("Failed to cast nSamplesPerSec to i32"),
);
wave_format_proto.set_avg_bytes_per_sec(
wave_format
.nAvgBytesPerSec
.try_into()
.expect("Failed to cast nAvgBytesPerSec"),
);
wave_format_proto.set_block_align(wave_format.nBlockAlign.into());
wave_format_proto.set_bits_per_sample(wave_format.wBitsPerSample.into());
wave_format_proto.set_size_bytes(wave_format.cbSize.into());
}
WaveAudioFormat::WaveFormatExtensible(wave_format_extensible) => {
wave_format_proto.set_format_tag(wave_format_extensible.Format.wFormatTag.into());
wave_format_proto.set_channels(wave_format_extensible.Format.nChannels.into());
wave_format_proto.set_samples_per_sec(
wave_format_extensible
.Format
.nSamplesPerSec
.try_into()
.expect("Failed to cast nSamplesPerSec to i32"),
);
wave_format_proto.set_avg_bytes_per_sec(
wave_format_extensible
.Format
.nAvgBytesPerSec
.try_into()
.expect("Failed to cast nAvgBytesPerSec"),
);
wave_format_proto.set_block_align(wave_format_extensible.Format.nBlockAlign.into());
wave_format_proto
.set_bits_per_sample(wave_format_extensible.Format.wBitsPerSample.into());
wave_format_proto.set_size_bytes(wave_format_extensible.Format.cbSize.into());
wave_format_proto.set_samples(wave_format_extensible.Samples.into());
wave_format_proto.set_channel_mask(wave_format_extensible.dwChannelMask.into());
let sub_format = wave_format_extensible.SubFormat;
wave_format_proto.set_sub_format(GuidWrapper(&sub_format).into());
}
}
wave_format_proto
}
}
struct GuidWrapper<'a>(&'a GUID);
impl<'a> From<GuidWrapper<'a>> for SubFormatProto {
fn from(guid: GuidWrapper) -> SubFormatProto {
let guid = guid.0;
if IsEqualGUID(guid, &KSDATAFORMAT_SUBTYPE_ANALOG) {
SubFormatProto::KSDATAFORMAT_SUBTYPE_ANALOG
} else if IsEqualGUID(guid, &KSDATAFORMAT_SUBTYPE_PCM) {
SubFormatProto::KSDATAFORMAT_SUBTYPE_PCM
} else if IsEqualGUID(guid, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) {
SubFormatProto::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
} else if IsEqualGUID(guid, &KSDATAFORMAT_SUBTYPE_DRM) {
SubFormatProto::KSDATAFORMAT_SUBTYPE_DRM
} else if IsEqualGUID(guid, &KSDATAFORMAT_SUBTYPE_ALAW) {
SubFormatProto::KSDATAFORMAT_SUBTYPE_ALAW
} else if IsEqualGUID(guid, &KSDATAFORMAT_SUBTYPE_MULAW) {
SubFormatProto::KSDATAFORMAT_SUBTYPE_MULAW
} else if IsEqualGUID(guid, &KSDATAFORMAT_SUBTYPE_ADPCM) {
SubFormatProto::KSDATAFORMAT_SUBTYPE_ADPCM
} else if IsEqualGUID(guid, &KSDATAFORMAT_SUBTYPE_MPEG) {
SubFormatProto::KSDATAFORMAT_SUBTYPE_MPEG
} else {
SubFormatProto::KSDATAFORMAT_SUBTYPE_INVALID
}
}
}
#[cfg(test)]
mod tests {
use winapi::shared::ksmedia::KSDATAFORMAT_SUBTYPE_PCM;
use winapi::shared::mmreg::SPEAKER_BACK_LEFT;
use winapi::shared::mmreg::SPEAKER_BACK_RIGHT;
use winapi::shared::mmreg::SPEAKER_LOW_FREQUENCY;
use winapi::shared::mmreg::SPEAKER_SIDE_LEFT;
use winapi::shared::mmreg::SPEAKER_SIDE_RIGHT;
use winapi::shared::mmreg::WAVE_FORMAT_PCM;
use super::*;
#[test]
fn test_modify_mix_format() {
// A typical 7.1 surround sound channel mask.
const channel_mask_7_1: u32 = 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 surround_sound_format = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 8,
nSamplesPerSec: 44100,
nAvgBytesPerSec: 1411200,
nBlockAlign: 32,
wBitsPerSample: 32,
cbSize: 22,
},
Samples: 32,
dwChannelMask: channel_mask_7_1,
SubFormat: KSDATAFORMAT_SUBTYPE_PCM,
};
// Safe because `GetMixFormat` casts `WAVEFORMATEXTENSIBLE` into a `WAVEFORMATEX` like so.
// Also this is casting from a bigger to a smaller struct, so it shouldn't be possible for
// this contructor to access memory it shouldn't.
let mut format = unsafe {
WaveAudioFormat::new((&surround_sound_format) as *const _ as *mut WAVEFORMATEX)
};
format.modify_mix_format(
/* bit_depth= */ 32,
/* ks_data_format= */ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
);
// Safe because we know the format is originally a `WAVEFORMATEXTENSIBLE`.
let surround_sound_format = format.take_waveformatextensible();
// WAVE_FORMAT_EXTENSIBLE uses the #[repr(packed)] flag so the compiler might unalign
// the fields. Thus, the fields will be copied to a local variable to prevent segfaults.
// For more information: https://github.com/rust-lang/rust/issues/46043
let format_tag = surround_sound_format.Format.wFormatTag;
// We expect `SubFormat` to be IEEE float instead of PCM.
// Everything else should remain the same.
assert_eq!(format_tag, WAVE_FORMAT_EXTENSIBLE);
let channels = surround_sound_format.Format.nChannels;
assert_eq!(channels, 8);
let samples_per_sec = surround_sound_format.Format.nSamplesPerSec;
assert_eq!(samples_per_sec, 44100);
let avg_bytes_per_sec = surround_sound_format.Format.nAvgBytesPerSec;
assert_eq!(avg_bytes_per_sec, 1411200);
let block_align = surround_sound_format.Format.nBlockAlign;
assert_eq!(block_align, 32);
let bits_per_samples = surround_sound_format.Format.wBitsPerSample;
assert_eq!(bits_per_samples, 32);
let size = surround_sound_format.Format.cbSize;
assert_eq!(size, 22);
let samples = surround_sound_format.Samples;
assert_eq!(samples, 32);
let channel_mask = surround_sound_format.dwChannelMask;
assert_eq!(channel_mask, channel_mask_7_1);
let sub_format = surround_sound_format.SubFormat;
assert!(IsEqualGUID(&sub_format, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT));
}
#[test]
fn test_waveformatex_ieee_modify_same_format() {
let format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_IEEE_FLOAT,
nChannels: 2,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 384000,
nBlockAlign: 8,
wBitsPerSample: 32,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let mut format =
unsafe { WaveAudioFormat::new((&format) as *const WAVEFORMATEX as *mut WAVEFORMATEX) };
format.modify_mix_format(
/* bit_depth= */ 32,
/* ks_data_format= */ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
);
let result_format = format.take_waveformatex();
assert_waveformatex_ieee(&result_format);
}
#[test]
fn test_waveformatex_ieee_modify_different_format() {
// I don't expect this format to show up ever, but it's possible so it's good to test.
let format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_IEEE_FLOAT,
nChannels: 2,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 192000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let mut format =
unsafe { WaveAudioFormat::new((&format) as *const WAVEFORMATEX as *mut WAVEFORMATEX) };
format.modify_mix_format(
/* bit_depth= */ 32,
/* ks_data_format= */ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
);
let result_format = format.take_waveformatex();
assert_waveformatex_ieee(&result_format);
}
#[test]
fn test_format_comparison_waveformatex_pass() {
let format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let format =
unsafe { WaveAudioFormat::new((&format) as *const WAVEFORMATEX as *mut WAVEFORMATEX) };
let expected = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
let expected = unsafe {
WaveAudioFormat::new((&expected) as *const WAVEFORMATEX as *mut WAVEFORMATEX)
};
assert_eq!(expected, format);
}
#[test]
fn test_format_comparison_waveformatextensible_pass() {
let format = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 22,
},
Samples: 16,
dwChannelMask: SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let format = unsafe {
WaveAudioFormat::new((&format) as *const WAVEFORMATEXTENSIBLE as *mut WAVEFORMATEX)
};
let expected = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 22,
},
Samples: 16,
dwChannelMask: SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
};
let expected = unsafe {
WaveAudioFormat::new((&expected) as *const WAVEFORMATEXTENSIBLE as *mut WAVEFORMATEX)
};
assert_eq!(expected, format);
}
#[test]
fn test_format_comparison_waveformatex_fail() {
let format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let format =
unsafe { WaveAudioFormat::new((&format) as *const WAVEFORMATEX as *mut WAVEFORMATEX) };
let expected = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
// The field below is the difference
nChannels: 6,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
let expected = unsafe {
WaveAudioFormat::new((&expected) as *const WAVEFORMATEX as *mut WAVEFORMATEX)
};
assert_ne!(expected, format);
}
#[test]
fn test_format_comparison_waveformatextensible_fail() {
let format = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 22,
},
Samples: 16,
dwChannelMask: SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let format = unsafe {
WaveAudioFormat::new((&format) as *const WAVEFORMATEXTENSIBLE as *mut WAVEFORMATEX)
};
let expected = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 22,
},
Samples: 16,
// The field below is the difference.
dwChannelMask: SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT,
SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
};
let expected = unsafe {
WaveAudioFormat::new((&expected) as *const WAVEFORMATEXTENSIBLE as *mut WAVEFORMATEX)
};
assert_ne!(expected, format);
}
#[test]
fn test_modify_mix_mono_channel_different_bit_depth_wave_format_extensible() {
// Start with a mono channel and 16 bit depth format.
let format = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 2 * 48000,
nBlockAlign: 2,
wBitsPerSample: 16,
cbSize: 22,
},
Samples: 16,
// Probably will never see a mask like this for two channels, but this is just testing
// that it will get changed.
dwChannelMask: SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let mut format = unsafe {
WaveAudioFormat::new((&format) as *const WAVEFORMATEXTENSIBLE as *mut WAVEFORMATEX)
};
format.modify_mix_format(
/* bit_depth= */ 32,
/* ks_data_format= */ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
);
// The format should be converted to 32 bit depth and retain mono channel.
let expected = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000, // Changed
nBlockAlign: 4, // Changed
wBitsPerSample: 32, // Changed
cbSize: 22,
},
Samples: 32,
dwChannelMask: SPEAKER_FRONT_CENTER, // Changed
SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
};
let expected = unsafe {
WaveAudioFormat::new((&expected) as *const WAVEFORMATEXTENSIBLE as *mut WAVEFORMATEX)
};
assert_eq!(format, expected);
}
#[test]
fn test_modify_mix_mono_channel_different_bit_depth_wave_format() {
// Start with a mono channel and 16 bit depth format.
let format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 2 * 48000,
nBlockAlign: 2,
wBitsPerSample: 16,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let mut format =
unsafe { WaveAudioFormat::new((&format) as *const WAVEFORMATEX as *mut WAVEFORMATEX) };
format.modify_mix_format(
/* bit_depth= */ 32,
/* ks_data_format= */ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
);
// The format should be converted to 32 bit depth and retain mono channel.
let expected = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_IEEE_FLOAT, // Changed
nChannels: 1,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 4 * 48000, // Changed
nBlockAlign: 4, // Changed
wBitsPerSample: 32, // Changed
cbSize: 0,
};
let expected = unsafe {
WaveAudioFormat::new((&expected) as *const WAVEFORMATEX as *mut WAVEFORMATEX)
};
assert_eq!(format, expected);
}
#[test]
fn test_waveformatex_non_ieee_modify_format() {
let format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 2,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 192000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let mut format =
unsafe { WaveAudioFormat::new((&format) as *const WAVEFORMATEX as *mut WAVEFORMATEX) };
format.modify_mix_format(
/* bit_depth= */ 32,
/* ks_data_format= */ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
);
let result_format = format.take_waveformatex();
assert_waveformatex_ieee(&result_format);
}
#[test]
fn test_waveformatex_non_ieee_32_bit_modify_format() {
let format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 2,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 384000,
nBlockAlign: 8,
wBitsPerSample: 32,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let mut format =
unsafe { WaveAudioFormat::new((&format) as *const WAVEFORMATEX as *mut WAVEFORMATEX) };
format.modify_mix_format(
/* bit_depth= */ 32,
/* ks_data_format= */ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
);
let result_format = format.take_waveformatex();
assert_waveformatex_ieee(&result_format);
}
fn assert_waveformatex_ieee(result_format: &WAVEFORMATEX) {
let format_tag = result_format.wFormatTag;
assert_eq!(format_tag, WAVE_FORMAT_IEEE_FLOAT);
let channels = result_format.nChannels;
assert_eq!(channels, 2);
let samples_per_sec = result_format.nSamplesPerSec;
assert_eq!(samples_per_sec, 48000);
let avg_bytes_per_sec = result_format.nAvgBytesPerSec;
assert_eq!(avg_bytes_per_sec, 384000);
let block_align = result_format.nBlockAlign;
assert_eq!(block_align, 8);
let bits_per_samples = result_format.wBitsPerSample;
assert_eq!(bits_per_samples, 32);
let size = result_format.cbSize;
assert_eq!(size, 0);
}
#[test]
fn test_create_audio_shared_format_wave_format_ex() {
let wave_format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 2,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 192000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let format = unsafe {
WaveAudioFormat::new((&wave_format) as *const WAVEFORMATEX as *mut WAVEFORMATEX)
};
// The period will most likely never be 123, but this is ok for testing.
let audio_shared_format =
format.create_audio_shared_format(/* shared_audio_engine_period_in_frames= */ 123);
assert_eq!(
audio_shared_format.bit_depth,
wave_format.wBitsPerSample as usize
);
assert_eq!(audio_shared_format.channels, wave_format.nChannels as usize);
assert_eq!(
audio_shared_format.frame_rate,
wave_format.nSamplesPerSec as usize
);
assert_eq!(
audio_shared_format.shared_audio_engine_period_in_frames,
123
);
}
#[test]
fn test_create_audio_shared_format_wave_format_extensible() {
let wave_format_extensible = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 2,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 8 * 48000,
nBlockAlign: 8,
wBitsPerSample: 32,
cbSize: 22,
},
Samples: 32,
dwChannelMask: SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let format = unsafe {
WaveAudioFormat::new((&wave_format_extensible) as *const _ as *mut WAVEFORMATEX)
};
// The period will most likely never be 123, but this is ok for testing.
let audio_shared_format =
format.create_audio_shared_format(/* shared_audio_engine_period_in_frames= */ 123);
assert_eq!(
audio_shared_format.bit_depth,
wave_format_extensible.Format.wBitsPerSample as usize
);
assert_eq!(
audio_shared_format.channels,
wave_format_extensible.Format.nChannels as usize
);
assert_eq!(
audio_shared_format.frame_rate,
wave_format_extensible.Format.nSamplesPerSec as usize
);
assert_eq!(
audio_shared_format.shared_audio_engine_period_in_frames,
123
);
assert_eq!(
audio_shared_format.channel_mask,
Some(SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
);
}
#[test]
fn test_wave_format_to_proto_convertion() {
let wave_format = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 2,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 192000,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let wave_audio_format =
unsafe { WaveAudioFormat::new((&wave_format) as *const _ as *mut WAVEFORMATEX) };
// Testing the `into`.
let wave_format_proto = WaveFormatProto::from(&wave_audio_format);
let mut expected = WaveFormatProto::new();
expected.set_format_tag(WAVE_FORMAT_PCM.into());
expected.set_channels(2);
expected.set_samples_per_sec(48000);
expected.set_avg_bytes_per_sec(192000);
expected.set_block_align(4);
expected.set_bits_per_sample(16);
expected.set_size_bytes(0);
assert_eq!(wave_format_proto, expected);
}
#[test]
fn test_wave_format_extensible_to_proto_convertion() {
let wave_format_extensible = WAVEFORMATEXTENSIBLE {
Format: WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 2,
nSamplesPerSec: 48000,
nAvgBytesPerSec: 8 * 48000,
nBlockAlign: 8,
wBitsPerSample: 32,
cbSize: 22,
},
Samples: 32,
dwChannelMask: SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
};
// Safe because we can convert a struct to a pointer declared above. Also that means the
// pointer can be safely deferenced.
let wave_audio_format = unsafe {
WaveAudioFormat::new((&wave_format_extensible) as *const _ as *mut WAVEFORMATEX)
};
// Testing the `into`.
let wave_format_proto = WaveFormatProto::from(&wave_audio_format);
let mut expected = WaveFormatProto::new();
expected.set_format_tag(WAVE_FORMAT_EXTENSIBLE.into());
expected.set_channels(2);
expected.set_samples_per_sec(48000);
expected.set_avg_bytes_per_sec(8 * 48000);
expected.set_block_align(8);
expected.set_bits_per_sample(32);
expected.set_size_bytes(22);
expected.set_samples(32);
expected.set_channel_mask((SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) as i64);
expected.set_sub_format(GuidWrapper(&KSDATAFORMAT_SUBTYPE_IEEE_FLOAT).into());
assert_eq!(wave_format_proto, expected);
}
}