extern crate alloc;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Media::Audio::Apo::{
IAudioMediaType, APO_CONNECTION_DESCRIPTOR, APO_CONNECTION_PROPERTY, APO_REG_PROPERTIES,
AUDIO_SYSTEMEFFECT, AUDIO_SYSTEMEFFECT_STATE, AUDIO_SYSTEMEFFECT_STATE_OFF,
AUDIO_SYSTEMEFFECT_STATE_ON, BUFFER_SILENT,
};
use windows_core::{Ref, BOOL, GUID, HRESULT};
use crate::apo::{ProcessInput, SystemEffectState};
use crate::buffer::{BufferFlags, CONNECTION_PROPERTY_SIGNATURE};
use crate::error::HResult;
use crate::format::Format;
use crate::instance::AnyApoInstance;
use crate::raw::media_type::NegotiationDirection;
#[cfg(feature = "aec")]
use crate::raw::media_type::{format_from_media_type, media_type_from_format};
use crate::raw::reg_properties::build_registration_properties;
use crate::realtime::RealtimeContext;
pub(crate) fn initialize(instance: &dyn AnyApoInstance) -> windows_core::Result<()> {
instance.initialize().map_err(|e| {
windows_core::Error::new(HRESULT::from(e), "ProcessingObject::initialize failed")
})
}
pub(crate) fn get_registration_properties_siso(
instance: &dyn AnyApoInstance,
) -> windows_core::Result<*mut APO_REG_PROPERTIES> {
build_registration_properties(instance)
}
pub(crate) fn negotiate_format(
instance: &dyn AnyApoInstance,
requested: Ref<'_, IAudioMediaType>,
direction: NegotiationDirection,
) -> windows_core::Result<IAudioMediaType> {
crate::raw::media_type::negotiate_format(instance, requested, direction)
}
pub(crate) fn get_input_channel_count(instance: &dyn AnyApoInstance) -> windows_core::Result<u32> {
instance
.locked_formats()
.map(|f| u32::from(f.input.channels()))
.ok_or_else(|| {
windows_core::Error::new(
HRESULT::from(HResult::APOERR_NOT_LOCKED),
"GetInputChannelCount called outside of the Locked state",
)
})
}
pub(crate) unsafe fn lock_for_process(
instance: &dyn AnyApoInstance,
u32numinputconnections: u32,
ppinputconnections: *const *const APO_CONNECTION_DESCRIPTOR,
u32numoutputconnections: u32,
ppoutputconnections: *const *const APO_CONNECTION_DESCRIPTOR,
) -> windows_core::Result<()> {
if u32numinputconnections != 1 || u32numoutputconnections != 1 {
return Err(windows_core::Error::new(
HRESULT::from(HResult::APOERR_NUM_CONNECTIONS_INVALID),
"framework supports exactly one input and one output connection",
));
}
if ppinputconnections.is_null() || ppoutputconnections.is_null() {
return Err(windows_core::Error::new(
HRESULT::from(HResult::E_POINTER),
"connection-descriptor array pointer was null",
));
}
let input_desc = unsafe { *ppinputconnections };
let output_desc = unsafe { *ppoutputconnections };
let input_format = unsafe { format_from_descriptor(input_desc) }?;
let output_format = unsafe { format_from_descriptor(output_desc) }?;
instance
.lock_for_process(&input_format, &output_format)
.map_err(|e| {
windows_core::Error::new(
HRESULT::from(e),
"ProcessingObject::lock_for_process failed",
)
})
}
pub(crate) fn unlock_for_process(instance: &dyn AnyApoInstance) -> windows_core::Result<()> {
instance.unlock_for_process().map_err(|e| {
windows_core::Error::new(
HRESULT::from(e),
"ProcessingObject::unlock_for_process failed",
)
})
}
pub(crate) unsafe fn apo_process(
instance: &dyn AnyApoInstance,
u32numinputconnections: u32,
ppinputconnections: *const *const APO_CONNECTION_PROPERTY,
u32numoutputconnections: u32,
ppoutputconnections: *mut *mut APO_CONNECTION_PROPERTY,
) {
let mark_output_silent = |frame_count: u32| {
if u32numoutputconnections == 1 && !ppoutputconnections.is_null() {
let out_ptr = unsafe { *ppoutputconnections };
if !out_ptr.is_null() {
unsafe {
(*out_ptr).u32ValidFrameCount = frame_count;
(*out_ptr).u32BufferFlags = BUFFER_SILENT;
}
}
}
};
if u32numinputconnections != 1 || u32numoutputconnections != 1 {
mark_output_silent(0);
return;
}
if ppinputconnections.is_null() || ppoutputconnections.is_null() {
mark_output_silent(0);
return;
}
let in_ptr = unsafe { *ppinputconnections };
let out_ptr = unsafe { *ppoutputconnections };
if in_ptr.is_null() || out_ptr.is_null() {
mark_output_silent(0);
return;
}
let in_prop = unsafe { &*in_ptr };
let out_prop = unsafe { &mut *out_ptr };
if in_prop.u32Signature != CONNECTION_PROPERTY_SIGNATURE
|| out_prop.u32Signature != CONNECTION_PROPERTY_SIGNATURE
{
mark_output_silent(0);
return;
}
let Some(formats) = instance.locked_formats() else {
mark_output_silent(0);
return;
};
let channels = formats.input.channels() as usize;
if channels == 0 || !formats.input.is_float() || formats.input.bits_per_sample() != 32 {
mark_output_silent(0);
return;
}
let frames = in_prop.u32ValidFrameCount as usize;
let sample_count = match frames.checked_mul(channels) {
Some(n) => n,
None => {
mark_output_silent(0);
return;
}
};
let input_slice =
unsafe { core::slice::from_raw_parts(in_prop.pBuffer as *const f32, sample_count) };
let output_slice =
unsafe { core::slice::from_raw_parts_mut(out_prop.pBuffer as *mut f32, sample_count) };
let in_flags: BufferFlags = in_prop.u32BufferFlags.into();
let rt = unsafe { RealtimeContext::new_unchecked() };
let out_flags =
match instance.process(&rt, ProcessInput::new(input_slice, in_flags), output_slice) {
Ok(f) => f,
Err(_) => {
mark_output_silent(in_prop.u32ValidFrameCount);
return;
}
};
out_prop.u32ValidFrameCount = in_prop.u32ValidFrameCount;
out_prop.u32BufferFlags = out_flags.into();
}
pub(crate) fn get_effects_list(
instance: &dyn AnyApoInstance,
ppeffectsids: *mut *mut GUID,
pceffects: *mut u32,
_event: HANDLE,
) -> windows_core::Result<()> {
if ppeffectsids.is_null() || pceffects.is_null() {
return Err(windows_core::Error::new(
HRESULT::from(HResult::E_POINTER),
"GetEffectsList output pointers were null",
));
}
let effects = instance.system_effects();
let count = effects.len();
if count == 0 {
unsafe {
*ppeffectsids = core::ptr::null_mut();
*pceffects = 0;
}
return Ok(());
}
let total_bytes = count
.checked_mul(core::mem::size_of::<GUID>())
.ok_or_else(|| {
windows_core::Error::new(
HRESULT::from(HResult::E_OUTOFMEMORY),
"GetEffectsList size calculation overflowed",
)
})?;
let raw = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(total_bytes) };
if raw.is_null() {
return Err(windows_core::Error::new(
HRESULT::from(HResult::E_OUTOFMEMORY),
"CoTaskMemAlloc returned null for GetEffectsList",
));
}
let list = raw.cast::<GUID>();
unsafe {
for (i, effect) in effects.iter().enumerate() {
core::ptr::write(list.add(i), effect.id.into());
}
*ppeffectsids = list;
*pceffects = count as u32;
}
Ok(())
}
pub(crate) fn get_controllable_system_effects_list(
instance: &dyn AnyApoInstance,
effects_out: *mut *mut AUDIO_SYSTEMEFFECT,
numeffects: *mut u32,
_event: HANDLE,
) -> windows_core::Result<()> {
if effects_out.is_null() || numeffects.is_null() {
return Err(windows_core::Error::new(
HRESULT::from(HResult::E_POINTER),
"GetControllableSystemEffectsList output pointers were null",
));
}
let user_effects = instance.system_effects();
let count = user_effects.len();
if count == 0 {
unsafe {
*effects_out = core::ptr::null_mut();
*numeffects = 0;
}
return Ok(());
}
let total_bytes = count
.checked_mul(core::mem::size_of::<AUDIO_SYSTEMEFFECT>())
.ok_or_else(|| {
windows_core::Error::new(
HRESULT::from(HResult::E_OUTOFMEMORY),
"GetControllableSystemEffectsList size calculation overflowed",
)
})?;
let raw = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(total_bytes) };
if raw.is_null() {
return Err(windows_core::Error::new(
HRESULT::from(HResult::E_OUTOFMEMORY),
"CoTaskMemAlloc returned null for GetControllableSystemEffectsList",
));
}
let list = raw.cast::<AUDIO_SYSTEMEFFECT>();
unsafe {
for (i, eff) in user_effects.iter().enumerate() {
core::ptr::write(
list.add(i),
AUDIO_SYSTEMEFFECT {
id: eff.id.into(),
canSetState: BOOL::from(eff.controllable),
state: match eff.state {
SystemEffectState::Off => AUDIO_SYSTEMEFFECT_STATE_OFF,
SystemEffectState::On => AUDIO_SYSTEMEFFECT_STATE_ON,
},
},
);
}
*effects_out = list;
*numeffects = count as u32;
}
Ok(())
}
pub(crate) fn set_audio_system_effect_state(
instance: &dyn AnyApoInstance,
effectid: &GUID,
state: AUDIO_SYSTEMEFFECT_STATE,
) -> windows_core::Result<()> {
let requested = crate::clsid::Clsid::from(*effectid);
if !instance.system_effects().iter().any(|e| e.id == requested) {
return Err(windows_core::Error::new(
HRESULT::from(HResult::E_INVALIDARG),
"SetAudioSystemEffectState: unknown effect id",
));
}
let cross = match state {
AUDIO_SYSTEMEFFECT_STATE_OFF => SystemEffectState::Off,
AUDIO_SYSTEMEFFECT_STATE_ON => SystemEffectState::On,
other => {
return Err(windows_core::Error::new(
HRESULT::from(HResult::E_INVALIDARG),
alloc::format!("SetAudioSystemEffectState: unknown state value {}", other.0),
));
}
};
instance.set_system_effect_state(&requested, cross);
Ok(())
}
#[allow(dead_code)]
pub(crate) fn iaudiomediatype_isequal_notimpl() -> windows_core::Result<u32> {
Err(windows_core::Error::new(
HRESULT::from(HResult::E_NOTIMPL),
"IsEqual not implemented",
))
}
pub(crate) unsafe fn format_from_descriptor(
descriptor: *const APO_CONNECTION_DESCRIPTOR,
) -> windows_core::Result<Format> {
if descriptor.is_null() {
return Err(windows_core::Error::new(
HRESULT::from(HResult::E_POINTER),
"APO_CONNECTION_DESCRIPTOR pointer was null",
));
}
let descriptor = unsafe { &*descriptor };
if descriptor.u32Signature != CONNECTION_PROPERTY_SIGNATURE {
return Err(windows_core::Error::new(
HRESULT::from(HResult::APOERR_INVALID_INPUT_DATA),
"APO_CONNECTION_DESCRIPTOR.u32Signature did not match 'APOC'",
));
}
let Some(media_type) = descriptor.pFormat.as_ref() else {
return Err(windows_core::Error::new(
HRESULT::from(HResult::APOERR_FORMAT_NOT_SUPPORTED),
"APO_CONNECTION_DESCRIPTOR.pFormat was None",
));
};
let wf_ptr = unsafe { media_type.GetAudioFormat() };
if wf_ptr.is_null() {
return Err(windows_core::Error::new(
HRESULT::from(HResult::APOERR_FORMAT_NOT_SUPPORTED),
"IAudioMediaType::GetAudioFormat returned null",
));
}
Ok(unsafe { Format::from_waveformatex_ptr(wf_ptr) })
}
#[cfg(feature = "aec")]
pub(crate) unsafe fn aec_add_auxiliary_input(
instance: &dyn crate::aec::AnyAecApoInstance,
dwinputid: u32,
cbdatasize: u32,
pbydata: *const u8,
pinputconnection: *const APO_CONNECTION_DESCRIPTOR,
) -> windows_core::Result<()> {
let format = unsafe { format_from_descriptor(pinputconnection) }?;
let init_data: &[u8] = if cbdatasize == 0 || pbydata.is_null() {
&[]
} else {
unsafe { core::slice::from_raw_parts(pbydata, cbdatasize as usize) }
};
instance
.add_aux_input(dwinputid, &format, init_data)
.map_err(|e| {
windows_core::Error::new(
HRESULT::from(e),
"AecProcessingObject::add_aux_input failed",
)
})
}
#[cfg(feature = "aec")]
pub(crate) fn aec_is_aux_format_supported(
instance: &dyn crate::aec::AnyAecApoInstance,
prequestedinputformat: Ref<'_, IAudioMediaType>,
) -> windows_core::Result<IAudioMediaType> {
let requested_format = format_from_media_type(prequestedinputformat)?;
let decision = instance.is_aux_format_supported(&requested_format);
match decision {
crate::format::FormatNegotiation::Accept => Ok(media_type_from_format(&requested_format)),
crate::format::FormatNegotiation::Suggest(alt) => Ok(media_type_from_format(&alt)),
crate::format::FormatNegotiation::Reject => Err(windows_core::Error::new(
HRESULT::from(HResult::APOERR_FORMAT_NOT_SUPPORTED),
"AecProcessingObject rejected the requested aux format",
)),
}
}
#[cfg(feature = "aec")]
pub(crate) unsafe fn aec_accept_input(
instance: &dyn crate::aec::AnyAecApoInstance,
dwinputid: u32,
pinputconnection: *const APO_CONNECTION_PROPERTY,
) {
if pinputconnection.is_null() {
return;
}
let prop = unsafe { &*pinputconnection };
if prop.u32Signature != CONNECTION_PROPERTY_SIGNATURE {
return;
}
let Some(formats) = instance.locked_formats() else {
return;
};
let channels = formats.input.channels() as usize;
if channels == 0 || !formats.input.is_float() || formats.input.bits_per_sample() != 32 {
return;
}
let frames = prop.u32ValidFrameCount as usize;
let Some(sample_count) = frames.checked_mul(channels) else {
return;
};
let samples = unsafe { core::slice::from_raw_parts(prop.pBuffer as *const f32, sample_count) };
let flags: BufferFlags = prop.u32BufferFlags.into();
let rt = unsafe { RealtimeContext::new_unchecked() };
instance.accept_aux_input(
&rt,
crate::aec::AuxiliaryInputBuffer {
id: dwinputid,
samples,
flags,
},
);
}