From 4812b0c44ac90144fd5758af64304d3ff64a8898 Mon Sep 17 00:00:00 2001 From: Michael Hills Date: Mon, 28 Sep 2020 07:47:19 +1000 Subject: [PATCH] Fix iOS compilation error and add macOS + iOS feedback example applications --- .travis.yml | 4 + examples/feedback.rs | 214 +++++++++ examples/ios/build_rust_deps.sh | 12 + .../project.pbxproj | 429 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/xcschememanagement.plist | 19 + examples/ios/ios-src/AppDelegate.h | 15 + examples/ios/ios-src/AppDelegate.m | 48 ++ .../Base.lproj/LaunchScreen.storyboard | 25 + .../ios/ios-src/Base.lproj/Main.storyboard | 24 + examples/ios/ios-src/Info.plist | 49 ++ examples/ios/ios-src/ViewController.h | 14 + examples/ios/ios-src/ViewController.m | 22 + examples/ios/ios-src/main.m | 18 + examples/ios/src/feedback.rs | 155 +++++++ examples/ios/src/lib.rs | 6 + src/audio_unit/mod.rs | 48 ++ src/audio_unit/render_callback.rs | 22 +- 19 files changed, 1134 insertions(+), 5 deletions(-) create mode 100644 examples/feedback.rs create mode 100755 examples/ios/build_rust_deps.sh create mode 100644 examples/ios/coreaudio-ios-example.xcodeproj/project.pbxproj create mode 100644 examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/ios/coreaudio-ios-example.xcodeproj/xcuserdata/mikeh.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 examples/ios/ios-src/AppDelegate.h create mode 100644 examples/ios/ios-src/AppDelegate.m create mode 100644 examples/ios/ios-src/Base.lproj/LaunchScreen.storyboard create mode 100644 examples/ios/ios-src/Base.lproj/Main.storyboard create mode 100644 examples/ios/ios-src/Info.plist create mode 100644 examples/ios/ios-src/ViewController.h create mode 100644 examples/ios/ios-src/ViewController.m create mode 100644 examples/ios/ios-src/main.m create mode 100644 examples/ios/src/feedback.rs create mode 100644 examples/ios/src/lib.rs diff --git a/.travis.yml b/.travis.yml index 27d5f0e88..e2c80ecd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ env: - LD_LIBRARY_PATH: /usr/local/lib global: - secure: URNNlOqxnfwWDC/w4PDTCswqe9ccnkIqDwfQecppfEui4KhpYXfrG9gx3xupWzWMfX+NPEcXtiyWA4CuUYaHbWWINLeLQUk650AFEqaRSiTpeh45Y9nh3dHT4fFDz4OeNfayNBBKL0kx5YwrugoeQggIgnF2KEcIHMF0+BbTtAM= +install: +- rustup target add aarch64-apple-ios x86_64-apple-ios before_script: - rustc --version - cargo --version @@ -20,6 +22,8 @@ script: - cargo build --verbose - cargo test --verbose - cargo doc --verbose +- cargo build --verbose --target aarch64-apple-ios +- cargo build --verbose --target x86_64-apple-ios after_success: | [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ] && diff --git a/examples/feedback.rs b/examples/feedback.rs new file mode 100644 index 000000000..07646dd37 --- /dev/null +++ b/examples/feedback.rs @@ -0,0 +1,214 @@ +//! A basic input + output stream example, copying the mic input stream to the default output stream + +extern crate coreaudio; + +use std::collections::VecDeque; +use std::mem; +use std::ptr::null; +use std::sync::{Arc, Mutex}; + +use coreaudio::audio_unit::audio_format::LinearPcmFlags; +use coreaudio::audio_unit::render_callback::{self, data}; +use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; +use coreaudio::sys::*; + +const SAMPLE_RATE: f64 = 44100.0; + +type S = f32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; +// type S = i32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I32; +// type S = i16; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I16; +// type S = i8; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I8; + +fn main() -> Result<(), coreaudio::Error> { + let mut input_audio_unit = audio_unit_from_device(default_input_device().unwrap(), true)?; + let mut output_audio_unit = audio_unit_from_device(default_output_device().unwrap(), false)?; + + let format_flag = match SAMPLE_FORMAT { + SampleFormat::F32 => LinearPcmFlags::IS_FLOAT, + SampleFormat::I32 | SampleFormat::I16 | SampleFormat::I8 => LinearPcmFlags::IS_SIGNED_INTEGER, + }; + + // Using IS_NON_INTERLEAVED everywhere because data::Interleaved is commented out / not implemented + let in_stream_format = StreamFormat { + sample_rate: SAMPLE_RATE, + sample_format: SAMPLE_FORMAT, + flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED, + // audio_unit.set_input_callback is hardcoded to 1 buffer, and when using non_interleaved + // we are forced to 1 channel + channels_per_frame: 1, + }; + + let out_stream_format = StreamFormat { + sample_rate: SAMPLE_RATE, + sample_format: SAMPLE_FORMAT, + flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED, + // you can change this to 1 + channels_per_frame: 2, + }; + + println!("input={:#?}", &in_stream_format); + println!("output={:#?}", &out_stream_format); + println!("input_asbd={:#?}", &in_stream_format.to_asbd()); + println!("output_asbd={:#?}", &out_stream_format.to_asbd()); + + let id = kAudioUnitProperty_StreamFormat; + let asbd = in_stream_format.to_asbd(); + input_audio_unit.set_property(id, Scope::Output, Element::Input, Some(&asbd))?; + + let asbd = out_stream_format.to_asbd(); + output_audio_unit.set_property(id, Scope::Input, Element::Output, Some(&asbd))?; + + let buffer_left = Arc::new(Mutex::new(VecDeque::::new())); + let producer_left = buffer_left.clone(); + let consumer_left = buffer_left.clone(); + let buffer_right = Arc::new(Mutex::new(VecDeque::::new())); + let producer_right = buffer_right.clone(); + let consumer_right = buffer_right.clone(); + + // seed roughly 1 second of data to create a delay in the feedback loop for easier testing + for buffer in vec![buffer_left, buffer_right] { + let mut buffer = buffer.lock().unwrap(); + for _ in 0..(out_stream_format.sample_rate as i32) { + buffer.push_back(0 as S); + } + } + + type Args = render_callback::Args>; + + input_audio_unit.set_input_callback(move |args| { + let Args { + num_frames, + mut data, + .. + } = args; + let buffer_left = producer_left.lock().unwrap(); + let buffer_right = producer_right.lock().unwrap(); + let mut buffers = vec![buffer_left, buffer_right]; + for i in 0..num_frames { + for (ch, channel) in data.channels_mut().enumerate() { + let value: S = channel[i]; + buffers[ch].push_back(value); + } + } + Ok(()) + })?; + input_audio_unit.start()?; + + output_audio_unit.set_render_callback(move |args: Args| { + let Args { + num_frames, + mut data, + .. + } = args; + + let buffer_left = consumer_left.lock().unwrap(); + let buffer_right = consumer_right.lock().unwrap(); + let mut buffers = vec![buffer_left, buffer_right]; + for i in 0..num_frames { + // Default other channels to copy value from first channel as a fallback + let zero: S = 0 as S; + let f: S = *buffers[0].front().unwrap_or(&zero); + for (ch, channel) in data.channels_mut().enumerate() { + let sample: S = buffers[ch].pop_front().unwrap_or(f); + channel[i] = sample; + } + } + Ok(()) + })?; + output_audio_unit.start()?; + + std::thread::sleep(std::time::Duration::from_millis(100000)); + + Ok(()) +} + +/// Copied from cpal +pub fn default_input_device() -> Option { + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioHardwarePropertyDefaultInputDevice, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; + + let audio_device_id: AudioDeviceID = 0; + let data_size = mem::size_of::(); + let status = unsafe { + AudioObjectGetPropertyData( + kAudioObjectSystemObject, + &property_address as *const _, + 0, + null(), + &data_size as *const _ as *mut _, + &audio_device_id as *const _ as *mut _, + ) + }; + if status != kAudioHardwareNoError as i32 { + return None; + } + + Some(audio_device_id) +} + +/// Copied from cpal +pub fn default_output_device() -> Option { + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioHardwarePropertyDefaultOutputDevice, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; + + let audio_device_id: AudioDeviceID = 0; + let data_size = mem::size_of::(); + let status = unsafe { + AudioObjectGetPropertyData( + kAudioObjectSystemObject, + &property_address as *const _, + 0, + null(), + &data_size as *const _ as *mut _, + &audio_device_id as *const _ as *mut _, + ) + }; + if status != kAudioHardwareNoError as i32 { + return None; + } + + Some(audio_device_id) +} + +/// Copied from cpal +fn audio_unit_from_device( + device_id: AudioDeviceID, + input: bool, +) -> Result { + let mut audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::HalOutput)?; + + if input { + // Enable input processing. + let enable_input = 1u32; + audio_unit.set_property( + kAudioOutputUnitProperty_EnableIO, + Scope::Input, + Element::Input, + Some(&enable_input), + )?; + + // Disable output processing. + let disable_output = 0u32; + audio_unit.set_property( + kAudioOutputUnitProperty_EnableIO, + Scope::Output, + Element::Output, + Some(&disable_output), + )?; + } + + audio_unit.set_property( + kAudioOutputUnitProperty_CurrentDevice, + Scope::Global, + Element::Output, + Some(&device_id), + )?; + + Ok(audio_unit) +} diff --git a/examples/ios/build_rust_deps.sh b/examples/ios/build_rust_deps.sh new file mode 100755 index 000000000..aa5ee3134 --- /dev/null +++ b/examples/ios/build_rust_deps.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +PATH=$PATH:$HOME/.cargo/bin + +# If you want your build to run faster, add a "--targets x86_64-apple-ios" for just using the ios simulator. +if [ -n ${IOS_TARGETS} ]; then + cargo lipo --targets ${IOS_TARGETS} +else + cargo lipo +fi diff --git a/examples/ios/coreaudio-ios-example.xcodeproj/project.pbxproj b/examples/ios/coreaudio-ios-example.xcodeproj/project.pbxproj new file mode 100644 index 000000000..a706f377e --- /dev/null +++ b/examples/ios/coreaudio-ios-example.xcodeproj/project.pbxproj @@ -0,0 +1,429 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 57AB5AF3252767460040DE8C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57AB5AF2252767460040DE8C /* AVFoundation.framework */; }; + 57AB5B07252769700040DE8C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AB5AFE252769700040DE8C /* ViewController.m */; }; + 57AB5B08252769700040DE8C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 57AB5AFF252769700040DE8C /* LaunchScreen.storyboard */; }; + 57AB5B09252769700040DE8C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 57AB5B01252769700040DE8C /* Main.storyboard */; }; + 57AB5B0A252769700040DE8C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AB5B03252769700040DE8C /* main.m */; }; + 57AB5B0B252769700040DE8C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AB5B04252769700040DE8C /* AppDelegate.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 57AB5AEE252766820040DE8C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 57AB5AC2252762C00040DE8C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57AB5AE9252766240040DE8C; + remoteInfo = cargo_ios; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 57AB5ACA252762C10040DE8C /* coreaudio-ios-example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "coreaudio-ios-example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 57AB5AF2252767460040DE8C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 57AB5AFD252769700040DE8C /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 57AB5AFE252769700040DE8C /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 57AB5B00252769700040DE8C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 57AB5B02252769700040DE8C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 57AB5B03252769700040DE8C /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 57AB5B04252769700040DE8C /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 57AB5B05252769700040DE8C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 57AB5B06252769700040DE8C /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 57AB5AC7252762C10040DE8C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 57AB5AF3252767460040DE8C /* AVFoundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 57AB5AC1252762C00040DE8C = { + isa = PBXGroup; + children = ( + 57AB5AFC252769700040DE8C /* ios-src */, + 57AB5ACB252762C10040DE8C /* Products */, + 57AB5AF1252767460040DE8C /* Frameworks */, + ); + sourceTree = ""; + }; + 57AB5ACB252762C10040DE8C /* Products */ = { + isa = PBXGroup; + children = ( + 57AB5ACA252762C10040DE8C /* coreaudio-ios-example.app */, + ); + name = Products; + sourceTree = ""; + }; + 57AB5AF1252767460040DE8C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 57AB5AF2252767460040DE8C /* AVFoundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 57AB5AFC252769700040DE8C /* ios-src */ = { + isa = PBXGroup; + children = ( + 57AB5AFD252769700040DE8C /* AppDelegate.h */, + 57AB5B04252769700040DE8C /* AppDelegate.m */, + 57AB5B06252769700040DE8C /* ViewController.h */, + 57AB5AFE252769700040DE8C /* ViewController.m */, + 57AB5AFF252769700040DE8C /* LaunchScreen.storyboard */, + 57AB5B01252769700040DE8C /* Main.storyboard */, + 57AB5B05252769700040DE8C /* Info.plist */, + 57AB5B03252769700040DE8C /* main.m */, + ); + path = "ios-src"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXLegacyTarget section */ + 57AB5AE9252766240040DE8C /* cargo_ios */ = { + isa = PBXLegacyTarget; + buildArgumentsString = build_rust_deps.sh; + buildConfigurationList = 57AB5AEA252766240040DE8C /* Build configuration list for PBXLegacyTarget "cargo_ios" */; + buildPhases = ( + ); + buildToolPath = /bin/sh; + buildWorkingDirectory = .; + dependencies = ( + ); + name = cargo_ios; + passBuildSettingsInEnvironment = 1; + productName = cargo_ios; + }; +/* End PBXLegacyTarget section */ + +/* Begin PBXNativeTarget section */ + 57AB5AC9252762C10040DE8C /* coreaudio-ios-example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57AB5AE3252762C30040DE8C /* Build configuration list for PBXNativeTarget "coreaudio-ios-example" */; + buildPhases = ( + 57AB5AC6252762C10040DE8C /* Sources */, + 57AB5AC7252762C10040DE8C /* Frameworks */, + 57AB5AC8252762C10040DE8C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 57AB5AEF252766820040DE8C /* PBXTargetDependency */, + ); + name = "coreaudio-ios-example"; + productName = "coreaudio-ios-example"; + productReference = 57AB5ACA252762C10040DE8C /* coreaudio-ios-example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 57AB5AC2252762C00040DE8C /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1200; + TargetAttributes = { + 57AB5AC9252762C10040DE8C = { + CreatedOnToolsVersion = 12.0.1; + }; + 57AB5AE9252766240040DE8C = { + CreatedOnToolsVersion = 12.0.1; + }; + }; + }; + buildConfigurationList = 57AB5AC5252762C00040DE8C /* Build configuration list for PBXProject "coreaudio-ios-example" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 57AB5AC1252762C00040DE8C; + productRefGroup = 57AB5ACB252762C10040DE8C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 57AB5AC9252762C10040DE8C /* coreaudio-ios-example */, + 57AB5AE9252766240040DE8C /* cargo_ios */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 57AB5AC8252762C10040DE8C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 57AB5B09252769700040DE8C /* Main.storyboard in Resources */, + 57AB5B08252769700040DE8C /* LaunchScreen.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 57AB5AC6252762C10040DE8C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 57AB5B0A252769700040DE8C /* main.m in Sources */, + 57AB5B0B252769700040DE8C /* AppDelegate.m in Sources */, + 57AB5B07252769700040DE8C /* ViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 57AB5AEF252766820040DE8C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 57AB5AE9252766240040DE8C /* cargo_ios */; + targetProxy = 57AB5AEE252766820040DE8C /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 57AB5AFF252769700040DE8C /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 57AB5B00252769700040DE8C /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 57AB5B01252769700040DE8C /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 57AB5B02252769700040DE8C /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 57AB5AE1252762C30040DE8C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 57AB5AE2252762C30040DE8C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 57AB5AE4252762C30040DE8C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "ios-src/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = target/universal/debug; + OTHER_LDFLAGS = "-lcoreaudio_ios_example"; + OTHER_LIBTOOLFLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = "coreaudio-rs.coreaudio-ios-example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 57AB5AE5252762C30040DE8C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "ios-src/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = target/universal/release; + OTHER_LDFLAGS = "-lcoreaudio_ios_example"; + OTHER_LIBTOOLFLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = "coreaudio-rs.coreaudio-ios-example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 57AB5AEB252766240040DE8C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEBUGGING_SYMBOLS = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 57AB5AEC252766240040DE8C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 57AB5AC5252762C00040DE8C /* Build configuration list for PBXProject "coreaudio-ios-example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57AB5AE1252762C30040DE8C /* Debug */, + 57AB5AE2252762C30040DE8C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 57AB5AE3252762C30040DE8C /* Build configuration list for PBXNativeTarget "coreaudio-ios-example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57AB5AE4252762C30040DE8C /* Debug */, + 57AB5AE5252762C30040DE8C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 57AB5AEA252766240040DE8C /* Build configuration list for PBXLegacyTarget "cargo_ios" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57AB5AEB252766240040DE8C /* Debug */, + 57AB5AEC252766240040DE8C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 57AB5AC2252762C00040DE8C /* Project object */; +} diff --git a/examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/ios/coreaudio-ios-example.xcodeproj/xcuserdata/mikeh.xcuserdatad/xcschemes/xcschememanagement.plist b/examples/ios/coreaudio-ios-example.xcodeproj/xcuserdata/mikeh.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..5d5ebeb57 --- /dev/null +++ b/examples/ios/coreaudio-ios-example.xcodeproj/xcuserdata/mikeh.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + cargo_ios.xcscheme_^#shared#^_ + + orderHint + 1 + + coreaudio-ios-example.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/examples/ios/ios-src/AppDelegate.h b/examples/ios/ios-src/AppDelegate.h new file mode 100644 index 000000000..596b56f03 --- /dev/null +++ b/examples/ios/ios-src/AppDelegate.h @@ -0,0 +1,15 @@ +// +// AppDelegate.h +// coreaudio-ios-example +// +// Created by Michael Hills on 2/10/20. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end + diff --git a/examples/ios/ios-src/AppDelegate.m b/examples/ios/ios-src/AppDelegate.m new file mode 100644 index 000000000..d215a9f4d --- /dev/null +++ b/examples/ios/ios-src/AppDelegate.m @@ -0,0 +1,48 @@ +// +// AppDelegate.m +// coreaudio-ios-example +// +// Created by Michael Hills on 2/10/20. +// + +#import "AppDelegate.h" +@import AVFoundation; + +void rust_ios_main(void); + + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + + NSError *error; + BOOL success; + + // It is necessary to access the sharedInstance so that calls to AudioSessionGetProperty + // will work. + AVAudioSession *session = AVAudioSession.sharedInstance; + // Setting up the category is not necessary, but generally advised. + // Since this demo records and plays, lets use AVAudioSessionCategoryPlayAndRecord. + // Also default to speaker as defaulting to the phone earpiece would be unusual. + success = [session setCategory:AVAudioSessionCategoryPlayAndRecord + withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker + error:&error]; + + if (success) { + NSLog(@"Calling rust_ios_main()"); + rust_ios_main(); + } else { + NSLog(@"Failed to configure audio session category"); + } + + return YES; +} + +@end diff --git a/examples/ios/ios-src/Base.lproj/LaunchScreen.storyboard b/examples/ios/ios-src/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..865e9329f --- /dev/null +++ b/examples/ios/ios-src/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ios/ios-src/Base.lproj/Main.storyboard b/examples/ios/ios-src/Base.lproj/Main.storyboard new file mode 100644 index 000000000..808a21ce7 --- /dev/null +++ b/examples/ios/ios-src/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ios/ios-src/Info.plist b/examples/ios/ios-src/Info.plist new file mode 100644 index 000000000..462567911 --- /dev/null +++ b/examples/ios/ios-src/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSMicrophoneUsageDescription + coreaudio-rs feedback demo + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ios/ios-src/ViewController.h b/examples/ios/ios-src/ViewController.h new file mode 100644 index 000000000..098223ebd --- /dev/null +++ b/examples/ios/ios-src/ViewController.h @@ -0,0 +1,14 @@ +// +// ViewController.h +// coreaudio-ios-example +// +// Created by Michael Hills on 2/10/20. +// + +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/examples/ios/ios-src/ViewController.m b/examples/ios/ios-src/ViewController.m new file mode 100644 index 000000000..2e74e8b4d --- /dev/null +++ b/examples/ios/ios-src/ViewController.m @@ -0,0 +1,22 @@ +// +// ViewController.m +// coreaudio-ios-example +// +// Created by Michael Hills on 2/10/20. +// + +#import "ViewController.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + + +@end diff --git a/examples/ios/ios-src/main.m b/examples/ios/ios-src/main.m new file mode 100644 index 000000000..4c9d95504 --- /dev/null +++ b/examples/ios/ios-src/main.m @@ -0,0 +1,18 @@ +// +// main.m +// coreaudio-ios-example +// +// Created by Michael Hills on 2/10/20. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + NSString * appDelegateClassName; + @autoreleasepool { + // Setup code that might create autoreleased objects goes here. + appDelegateClassName = NSStringFromClass([AppDelegate class]); + } + return UIApplicationMain(argc, argv, nil, appDelegateClassName); +} diff --git a/examples/ios/src/feedback.rs b/examples/ios/src/feedback.rs new file mode 100644 index 000000000..cd3fcbc44 --- /dev/null +++ b/examples/ios/src/feedback.rs @@ -0,0 +1,155 @@ +//! A basic input + output stream example, copying the mic input stream to the default output stream + +extern crate coreaudio; + +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; + +use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; +use coreaudio::audio_unit::audio_format::LinearPcmFlags; +use coreaudio::audio_unit::render_callback::{self, data}; +use coreaudio::sys::*; + +const SAMPLE_RATE: f64 = 44100.0; + +type S = f32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; +// type S = i32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I32; +// type S = i16; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I16; +// type S = i8; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I8; + +pub fn run_example() -> Result<(), coreaudio::Error> { + let mut input_audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::RemoteIO)?; + let mut output_audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::RemoteIO)?; + + // iOS doesn't let you reconfigure an "initialized" audio unit, so uninitialize them + input_audio_unit.uninitialize()?; + output_audio_unit.uninitialize()?; + + configure_for_recording(&mut input_audio_unit)?; + + let format_flag = match SAMPLE_FORMAT { + SampleFormat::F32 => LinearPcmFlags::IS_FLOAT, + SampleFormat::I32 | SampleFormat::I16 | SampleFormat::I8 => LinearPcmFlags::IS_SIGNED_INTEGER, + }; + + // Using IS_NON_INTERLEAVED everywhere because data::Interleaved is commented out / not implemented + let in_stream_format = StreamFormat { + sample_rate: SAMPLE_RATE, + sample_format: SAMPLE_FORMAT, + flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED, + // audio_unit.set_input_callback is hardcoded to 1 buffer, and when using non_interleaved + // we are forced to 1 channel + channels_per_frame: 1, + }; + + let out_stream_format = StreamFormat { + sample_rate: SAMPLE_RATE, + sample_format: SAMPLE_FORMAT, + flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED, + // you can change this to 1 + channels_per_frame: 2, + }; + + println!("input={:#?}", &in_stream_format); + println!("output={:#?}", &out_stream_format); + println!("input_asbd={:#?}", &in_stream_format.to_asbd()); + println!("output_asbd={:#?}", &out_stream_format.to_asbd()); + + let id = kAudioUnitProperty_StreamFormat; + input_audio_unit.set_property(id, Scope::Output, Element::Input, Some(&in_stream_format.to_asbd()))?; + output_audio_unit.set_property(id, Scope::Input, Element::Output, Some(&out_stream_format.to_asbd()))?; + + let buffer_left = Arc::new(Mutex::new(VecDeque::::new())); + let producer_left = buffer_left.clone(); + let consumer_left = buffer_left.clone(); + let buffer_right = Arc::new(Mutex::new(VecDeque::::new())); + let producer_right = buffer_right.clone(); + let consumer_right = buffer_right.clone(); + + // seed roughly 1 second of data to create a delay in the feedback loop for easier testing + for buffer in vec![buffer_left, buffer_right] { + let mut buffer = buffer.lock().unwrap(); + for _ in 0..(out_stream_format.sample_rate as i32) { + buffer.push_back(0 as S); + } + } + + type Args = render_callback::Args>; + + println!("set_input_callback"); + input_audio_unit.set_input_callback(move |args| { + let Args { + num_frames, + mut data, + .. + } = args; + let buffer_left = producer_left.lock().unwrap(); + let buffer_right = producer_right.lock().unwrap(); + let mut buffers = vec![buffer_left, buffer_right]; + for i in 0..num_frames { + for (ch, channel) in data.channels_mut().enumerate() { + let value: S = channel[i]; + buffers[ch].push_back(value); + } + } + Ok(()) + })?; + input_audio_unit.initialize()?; + input_audio_unit.start()?; + + println!("set_render_callback"); + output_audio_unit.set_render_callback(move |args: Args| { + let Args { + num_frames, + mut data, + .. + } = args; + + let buffer_left = consumer_left.lock().unwrap(); + let buffer_right = consumer_right.lock().unwrap(); + let mut buffers = vec![buffer_left, buffer_right]; + for i in 0..num_frames { + // Default other channels to copy value from first channel as a fallback + let zero: S = 0 as S; + let f: S = *buffers[0].front().unwrap_or(&zero); + for (ch, channel) in data.channels_mut().enumerate() { + let sample: S = buffers[ch].pop_front().unwrap_or(f); + channel[i] = sample; + } + } + Ok(()) + })?; + output_audio_unit.initialize()?; + output_audio_unit.start()?; + + // for the purposes of this demo, leak these so that after returning the audio units will + // keep running + std::mem::forget(input_audio_unit); + std::mem::forget(output_audio_unit); + + Ok(()) +} + +fn configure_for_recording(audio_unit: &mut AudioUnit) -> Result<(), coreaudio::Error> { + println!("Configure audio unit for recording"); + + // Enable mic recording + let enable_input = 1u32; + audio_unit.set_property( + kAudioOutputUnitProperty_EnableIO, + Scope::Input, + Element::Input, + Some(&enable_input), + )?; + + // Disable output + let disable_output = 0u32; + audio_unit.set_property( + kAudioOutputUnitProperty_EnableIO, + Scope::Output, + Element::Output, + Some(&disable_output), + )?; + + Ok(()) +} diff --git a/examples/ios/src/lib.rs b/examples/ios/src/lib.rs new file mode 100644 index 000000000..286636116 --- /dev/null +++ b/examples/ios/src/lib.rs @@ -0,0 +1,6 @@ +mod feedback; + +#[no_mangle] +pub extern "C" fn rust_ios_main() { + feedback::run_example().unwrap(); +} diff --git a/src/audio_unit/mod.rs b/src/audio_unit/mod.rs index 9e4459bbe..9fe3fae7d 100644 --- a/src/audio_unit/mod.rs +++ b/src/audio_unit/mod.rs @@ -171,6 +171,29 @@ impl AudioUnit { } } + /// On successful initialization, the audio formats for input and output are valid + /// and the audio unit is ready to render. During initialization, an audio unit + /// allocates memory according to the maximum number of audio frames it can produce + /// in response to a single render call. + /// + /// Usually, the state of an audio unit (such as its I/O formats and memory allocations) + /// cannot be changed while an audio unit is initialized. + pub fn initialize(&mut self) -> Result<(), Error> { + unsafe { try_os_status!(sys::AudioUnitInitialize(self.instance)); } + Ok(()) + } + + /// Before you change an initialize audio unit’s processing characteristics, + /// such as its input or output audio data format or its sample rate, you must + /// first uninitialize it. Calling this function deallocates the audio unit’s resources. + /// + /// After calling this function, you can reconfigure the audio unit and then call + /// AudioUnitInitialize to reinitialize it. + pub fn uninitialize(&mut self) -> Result<(), Error> { + unsafe { try_os_status!(sys::AudioUnitUninitialize(self.instance)); } + Ok(()) + } + /// Sets the value for some property of the **AudioUnit**. /// /// To clear an audio unit property value, set the data paramater with `None::<()>`. @@ -375,3 +398,28 @@ pub fn get_property( Ok(data) } } + +/// Gets the value of a specified audio session property. +/// +/// **Available** in iOS 2.0 and later. +/// +/// Parameters +/// ---------- +/// +/// - **id**: The identifier of the property. +#[cfg(target_os = "ios")] +pub fn audio_session_get_property( + id: u32, +) -> Result +{ + let mut size = ::std::mem::size_of::() as u32; + unsafe { + let mut data: T = ::std::mem::uninitialized(); + let data_ptr = &mut data as *mut _ as *mut c_void; + let size_ptr = &mut size as *mut _; + try_os_status!( + sys::AudioSessionGetProperty(id, size_ptr, data_ptr) + ); + Ok(data) + } +} diff --git a/src/audio_unit/render_callback.rs b/src/audio_unit/render_callback.rs index e0c5668a7..4dcbbdc13 100644 --- a/src/audio_unit/render_callback.rs +++ b/src/audio_unit/render_callback.rs @@ -398,7 +398,7 @@ impl AudioUnit { // First, we'll retrieve the stream format so that we can ensure that the given callback // format matches the audio unit's format. let id = sys::kAudioUnitProperty_StreamFormat; - let asbd = try!(self.get_property(id, Scope::Output, Element::Output)); + let asbd = try!(self.get_property(id, Scope::Input, Element::Output)); let stream_format = super::StreamFormat::from_asbd(asbd)?; // If the stream format does not match, return an error indicating this. @@ -471,7 +471,7 @@ impl AudioUnit { // First, we'll retrieve the stream format so that we can ensure that the given callback // format matches the audio unit's format. let id = sys::kAudioUnitProperty_StreamFormat; - let asbd = self.get_property(id, Scope::Input, Element::Input)?; + let asbd = self.get_property(id, Scope::Output, Element::Input)?; let stream_format = super::StreamFormat::from_asbd(asbd)?; // If the stream format does not match, return an error indicating this. @@ -482,8 +482,20 @@ impl AudioUnit { // Pre-allocate a buffer list for input stream. // // First, get the current buffer size for pre-allocating the `AudioBuffer`s. - let id = sys::kAudioDevicePropertyBufferFrameSize; - let mut buffer_frame_size: u32 = self.get_property(id, Scope::Global, Element::Output)?; + #[cfg(target_os = "macos")] + let mut buffer_frame_size: u32 = { + let id = sys::kAudioDevicePropertyBufferFrameSize; + let buffer_frame_size: u32 = self.get_property(id, Scope::Global, Element::Output)?; + buffer_frame_size + }; + #[cfg(target_os = "ios")] + let mut buffer_frame_size: u32 = { + let id = sys::kAudioSessionProperty_CurrentHardwareIOBufferDuration; + let seconds: f32 = super::audio_session_get_property(id)?; + let id = sys::kAudioSessionProperty_CurrentHardwareSampleRate; + let sample_rate: f64 = super::audio_session_get_property(id)?; + (sample_rate * seconds as f64).round() as u32 + }; let mut data: Vec = vec![]; let sample_bytes = stream_format.sample_format.size_in_bytes(); let n_channels = stream_format.channels_per_frame; @@ -525,7 +537,7 @@ impl AudioUnit { unsafe { // Retrieve the up-to-date stream format. let id = sys::kAudioUnitProperty_StreamFormat; - let asbd = match super::get_property(audio_unit, id, Scope::Input, Element::Output) { + let asbd = match super::get_property(audio_unit, id, Scope::Output, Element::Input) { Err(err) => return err.to_os_status(), Ok(asbd) => asbd, };