cpvc 0.6.0

A simple crate + wrapper for controlling system audio cross platform
Documentation
// TODO Create CoreAudioExpanded struct for non essential features
pub mod device;

// #[cfg(not(target_os="macos"))] // Should be disabled, for testing
#[cfg(target_os="macos")]
pub mod coreaudio {
    
    use objc2_core_audio::kAudioHardwarePropertyDeviceForUID;

    use crate::{DeviceType, coreaudio::device, debug_eprintln, device::DeviceTrait, error::Error};

    #[cfg(target_os="macos")]
    use {
        std::ffi::c_void,
        std::ptr::{null, null_mut},
        std::mem::{size_of},
        std::ptr::NonNull,
        core_foundation::{base::TCFType, string::{CFString, CFStringRef}},
        objc2_core_audio_types::{AudioStreamBasicDescription},
        objc2_core_audio::{
            AudioObjectGetPropertyData, AudioObjectSetPropertyData, AudioObjectGetPropertyDataSize,
            AudioObjectID, AudioObjectPropertyAddress,
            kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectSystemObject,
            kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain,
            kAudioDevicePropertyScopeOutput, kAudioDevicePropertyMute,
            kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyDeviceNameCFString,
            kAudioDevicePropertyStreamFormat, kAudioObjectPropertyScopeOutput,
            kAudioHardwarePropertyDevices, kAudioDevicePropertyStreams,
            kAudioObjectPropertyScopeInput,
        },
    };

    pub(super) fn get_device_identifiers() -> Result<Vec<(u32, String)>, Error> {
        let mut devices:Vec<(u32, String)> = Vec::new();
        let audio_devices_count_address =  AudioObjectPropertyAddress {
            mSelector: kAudioHardwarePropertyDevices,
            mScope: kAudioObjectPropertyScopeGlobal,
            mElement: kAudioObjectPropertyElementMain
        };

        let mut device_count: u32 = 0;
        let mut success = false;
        let capture_count_status;
        unsafe {
            capture_count_status = AudioObjectGetPropertyDataSize(
                kAudioObjectSystemObject as AudioObjectID,
                NonNull::new_unchecked(&audio_devices_count_address as *const _ as *mut _),
                0,
                null(),
                NonNull::new_unchecked(&mut device_count as *mut _));
            if capture_count_status == 0 {
                success = true;
            }
        }

        if success {
            let mut device_details: Vec<AudioObjectID> = Vec::with_capacity(device_count as usize);
            unsafe {
                let capture_id_status = AudioObjectGetPropertyData(
                    kAudioObjectSystemObject as AudioObjectID,
                    NonNull::new_unchecked(&audio_devices_count_address as *const _ as * mut _),
                    0,
                    null(),
                    NonNull::new_unchecked(&device_count as *const _ as *mut _),
                    NonNull::new_unchecked(device_details.as_mut_ptr() as *mut c_void));
                if capture_id_status == 0 {
                    device_details.set_len(device_count as usize);
                } else {
                    return Err(Error::DeviceEnumerationFailed(format!("Failed to capture device ids with status {}", capture_id_status)))
                }
            }
            for device in &device_details {
                if *device != 0 {
                    if let Ok(name) = get_device_name(*device) {
                        match check_device_type(*device) {
                            DeviceType::Input => {
                                // May Add Future Functionality
                            },
                            DeviceType::Output => {
                                devices.push((*device, name));
                            },
                            DeviceType::None => {}
                        }
                    } else {
                        // ! Silent Failures may occur if there is an error when the name certain device ids are requested
                        // ! From testing, these errors are usually safe to ignore
                        
                        debug_eprintln(&format!("Silent Name Capture Failure on device with id: {}", *device));
                        // println!("Silent Name Capture Failure on device with id: {}", *device);
                    }
                }
            }
            Ok(devices)
        } else {
            Err(Error::DeviceEnumerationFailed(format!("Failed to capture device count with status {}", capture_count_status)))
        }
        
    }

    pub fn check_device_type(device_id: u32) -> DeviceType {
        let mut dev_type_address = AudioObjectPropertyAddress {
            mSelector: kAudioDevicePropertyStreams,
            mScope: kAudioObjectPropertyScopeOutput,
            mElement: kAudioObjectPropertyElementMain,
        };

        let mut stream_count: u32 = 0;
        let mut count_size = size_of::<u32>() as u32;
        let capture_type_status;
        unsafe {
            capture_type_status = AudioObjectGetPropertyData(
                device_id,
                NonNull::new_unchecked(&mut dev_type_address),
                0,
                null(),
                NonNull::new_unchecked(&mut count_size),
                NonNull::new_unchecked(&mut stream_count as *mut _ as *mut c_void));
        }
        if capture_type_status == 0 {
            if stream_count > 0 {
                DeviceType::Output
            } else {
                let input_type_address = AudioObjectPropertyAddress {
                        mSelector: kAudioDevicePropertyStreams,
                        mScope: kAudioObjectPropertyScopeInput,
                        mElement: kAudioObjectPropertyElementMain,
                    };
                let mut in_stream_count: u32 = 0;
                let in_count_size = size_of::<u32>() as u32;
                let capture_in_type_status;
                unsafe {
                    capture_in_type_status = AudioObjectGetPropertyData(
                        device_id,
                        NonNull::new_unchecked(&input_type_address as *const _ as *mut _),
                        0,
                        null(),
                        NonNull::new_unchecked(&in_count_size as *const _ as *mut _),
                        NonNull::new_unchecked(&mut in_stream_count as *mut _ as *mut c_void)
                    );
                }
                if capture_in_type_status == 0 {
                    DeviceType::Input
                } else {
                    DeviceType::None
                }
            }
        } else {
            DeviceType::None
        }
    }

    pub fn get_device_name(device_id: u32) -> Result<String, Error> {
        let mut property_address = AudioObjectPropertyAddress {
            mSelector: kAudioDevicePropertyDeviceNameCFString,
            mScope: kAudioObjectPropertyScopeGlobal,
            mElement: kAudioObjectPropertyElementMain,
        };
        let mut name: CFStringRef = null_mut();
        let mut data_size = size_of::<CFStringRef>() as u32;
        unsafe {
            let status = AudioObjectGetPropertyData(
                    device_id,
                    NonNull::new_unchecked(&mut property_address),
                    0,
                    null(),
                    NonNull::new_unchecked(&mut data_size),
                    NonNull::new_unchecked(&mut name as *mut _ as *mut _),
                );
            if status == 0 {
                Ok(CFString::wrap_under_get_rule(name).to_string())
            } else {
                debug_eprintln(&format!("Failed to get device name. Status: {}", status));
                Err(Error::DeviceEnumerationFailed("Name Capture failed CoreAudio backend error".to_string()))
            }
        }
    }

    pub(super) fn get_output_device_details(device_id: u32) -> Result<AudioStreamBasicDescription, Error> {
        let mut property_address = AudioObjectPropertyAddress{
            mSelector: kAudioDevicePropertyStreamFormat,
            mScope: kAudioObjectPropertyScopeOutput,
            mElement: kAudioObjectPropertyElementMain,
        };
        
        let mut details: AudioStreamBasicDescription = AudioStreamBasicDescription {
            mSampleRate: 0.0,
            mFormatID: 0,
            mFormatFlags: 0,
            mBytesPerPacket: 0,
            mFramesPerPacket: 0,
            mBytesPerFrame: 0,
            mChannelsPerFrame: 0,
            mBitsPerChannel: 0,
            mReserved: 0 };
        let mut data_size: u32 = size_of::<AudioStreamBasicDescription>() as u32;

        unsafe {
            let detail_capture_status = AudioObjectGetPropertyData(device_id,
                NonNull::new_unchecked(&mut property_address),
                0,
                null(),
                NonNull::new_unchecked(&mut data_size),
                NonNull::new_unchecked(&mut details as *mut _ as *mut c_void));
            if detail_capture_status == 0 {
                Ok(details)
            } else {
                Err(Error::DeviceEnumerationFailed("CoreAudio backend error.".to_string()))
            }
        }
    }

    pub(super) fn uid_to_hw_id(uid: String) -> Result<u32, Error> {
        let id_property_address = AudioObjectPropertyAddress {
            mSelector: kAudioHardwarePropertyDeviceForUID,
            mScope: kAudioDevicePropertyScopeOutput,
            mElement: kAudioObjectPropertyElementMain,
        };

        let cf_uid = CFString::new(&uid);

        let mut hw_id: u32 = 0;
        let mut data_size = size_of::<u32>() as u32;
        unsafe {
            use std::ptr::{NonNull};
            let hw_id_status = AudioObjectGetPropertyData(kAudioObjectSystemObject as u32,
                NonNull::new_unchecked(&id_property_address as *const _ as *mut _),
                size_of::<String>() as u32, cf_uid.as_concrete_TypeRef() as *const c_void,
                NonNull::new_unchecked(&mut data_size as *mut _ ), NonNull::new_unchecked(&hw_id as *const _ as *mut _));
            if hw_id_status != 0 {
                return Err(Error::Placeholder);
            }
        }
        Ok(hw_id)
    }

    pub(super) fn get_hw_name(device_id: u32) -> Result<String, Error> {
        #[cfg(target_os = "macos")]
        {
            use objc2_core_audio::kAudioDevicePropertyDeviceName;

            let mut property_address = AudioObjectPropertyAddress {
                mSelector: kAudioDevicePropertyDeviceName,
                mScope: kAudioObjectPropertyScopeGlobal,
                mElement: kAudioObjectPropertyElementMain,
            };

            let mut data_size = size_of::<u32>() as u32;
            unsafe{
                
                
                let size_status = AudioObjectGetPropertyDataSize(
                        device_id,
                        NonNull::new_unchecked(&mut property_address),
                        0,
                        null(),
                        NonNull::new_unchecked(&mut data_size),
                );
                if size_status == 0 {
                    let mut hw_name= Vec::new();
                    hw_name.resize(data_size as usize, 0);
                    let status = AudioObjectGetPropertyData(
                            device_id,
                            NonNull::new_unchecked(&mut property_address),
                            0,
                            null(),
                            NonNull::new_unchecked(&mut data_size),
                            NonNull::new_unchecked(hw_name.as_mut_ptr() as *mut _),
                        );
                    if status == 0 {
                        match String::from_utf8(hw_name) {
                            Ok(name) => {
                                dbg!(name.clone());
                                Ok(name)
                            },
                            Err(e) => {
                                debug_eprintln(&format!("Failed to get device name. Error: {}", e));
                                Err(Error::DeviceAccessFailed(e.to_string()))
                            }
                        }
                    } else {
                        debug_eprintln(&format!("Failed to get device name. Status: {}", status));
                        Err(Error::DeviceAccessFailed("CoreAudio backend error".to_string()))
                    }
                } else {
                    Err(Error::DeviceAccessFailed("CoreAudio backend error".to_string()))
                }
            }
        }
    }

    // Attempt to Capture Device ID of Default Audio Output Device
    pub(super) fn capture_output_device() -> Result<device::CoreAudioDevice, Error> {
        let mut output_device_address = AudioObjectPropertyAddress {
                mSelector: kAudioHardwarePropertyDefaultOutputDevice,
                mScope: kAudioObjectPropertyScopeGlobal,
                mElement: kAudioObjectPropertyElementMain,
            };
        let mut device_id: AudioObjectID = 0;
        let mut data_size = size_of::<AudioObjectID>() as u32;
        unsafe {
            let capture_output_status = AudioObjectGetPropertyData(
                kAudioObjectSystemObject as u32,
                NonNull::new_unchecked(&mut output_device_address),
                0,
                null(),
                NonNull::new_unchecked(&mut data_size),
                NonNull::new_unchecked(&mut device_id as *mut _ as *mut c_void),
            );

            if capture_output_status == 0 {
                Ok(device::CoreAudioDevice::from_hw_id(device_id))?
            } else {
                Err(Error::DeviceEnumerationFailed("CoreAudio backend error".to_string()))
            }
        }

    }

    // Volume Controls 
    pub fn get_sound_devices() -> Result<Vec<String>, Error> {
        if let Ok(identifiers) = get_device_identifiers() {
            Ok(identifiers.into_iter().map(|(_id, name)| name).collect())
        } else {
            Err(Error::Placeholder)
        }
    }

    pub fn get_vol() -> Result<f32, Error> {
        let output_dev = capture_output_device()?;
        output_dev.get_vol()
    }

    pub fn set_vol(vol: f32) -> Result<(), Error> {
        let output_dev = capture_output_device()?;
        output_dev.set_vol(vol)
    }

    pub fn get_mute() -> Result<bool, Error> {
        let output_dev = capture_output_device()?;
        output_dev.get_mute()
    }

    pub fn set_mute(mute: bool) -> Result<(), Error> {
        let output_dev = capture_output_device()?;
        output_dev.set_mute(mute)
    }

}

#[cfg(not(target_os="macos"))] 
// #[cfg(target_os="macos")] // Should be disabled, for testing
pub mod coreaudio {
    use crate::{DeviceType, error::Error};

    pub fn check_device_type(device_id: u32) -> DeviceType {
        DeviceType::None
    }

    pub fn get_device_name(device_id: u32) -> Result<String, Error> {
        Err(Error::PlatformUnsupported)
    }

    // Volume Controls 
    pub fn get_sound_devices() -> Result<Vec<String>, Error> {
        Err(Error::PlatformUnsupported)
    }

    pub fn get_vol() -> Result<f32, Error> {
        Err(Error::PlatformUnsupported)
    }

    pub fn set_vol(vol: f32) -> Result<(), Error> {
        Err(Error::PlatformUnsupported)
    }

    pub fn get_mute() -> Result<bool, Error> {
        Err(Error::PlatformUnsupported)
    }

    pub fn set_mute(mute: bool) -> Result<(), Error> {
        Err(Error::PlatformUnsupported)
    }

}


pub(crate) use coreaudio::*;