From 163c67992ab9cc945b85d7dc41da2766076b2414 Mon Sep 17 00:00:00 2001 From: Paul Brossier Date: Fri, 22 Mar 2013 01:07:09 -0500 Subject: [PATCH] src/io/audio_unit.c: added ios driver --- src/aubio.h | 1 + src/io/audio_unit.c | 777 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/io/audio_unit.h | 61 +++++ 3 files changed, 839 insertions(+) create mode 100644 src/io/audio_unit.c create mode 100644 src/io/audio_unit.h diff --git a/src/aubio.h b/src/aubio.h index 0e23419f..226c59b8 100644 --- a/src/aubio.h +++ b/src/aubio.h @@ -184,6 +184,7 @@ extern "C" #include "tempo/tempo.h" #include "io/source.h" #include "io/sink.h" +#include "io/audio_unit.h" #if AUBIO_UNSTABLE #include "mathutils.h" diff --git a/src/io/audio_unit.c b/src/io/audio_unit.c new file mode 100644 index 00000000..fc1d2541 --- /dev/null +++ b/src/io/audio_unit.c @@ -0,0 +1,777 @@ +/* + Copyright (C) 2013 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#include "config.h" +#ifdef TARGET_OS_IPHONE +#include "aubio_priv.h" + +#include "fvec.h" +#include "fmat.h" +#include "io/audio_unit.h" + +#include + +#define AU_IOS_MAX_OUT 64 +#define AU_IOS_MAX_FRAMES AU_IOS_MAX_OUT * 16 * 2 +#define PREFERRED_LATENCY 0.010 +#define MAX_FPS 4096 + +#define INT_TO_FLOAT 3.0517578125e-05 // 1. / 32768. + +struct _aubio_audio_unit_t { + AudioUnit audio_unit; + uint_t samplerate; + uint_t blocksize; + uint_t sw_input_channels; + uint_t sw_output_channels; + uint_t hw_output_channels; + uint_t hw_input_channels; + Float32 latency; + sint_t total_frames; + fmat_t *input_frames; + fmat_t *output_frames; + SInt16 *au_ios_inbuf; + SInt16 *au_ios_outbuf; + aubio_device_callback_t callback; + void *callback_closure; + int dio_error; // flag to check if we had a read error + bool input_enabled; + bool prevent_feedback; + bool verbose; + int au_ios_start; + int au_ios_end; + AURenderCallbackStruct au_ios_cb_struct; +}; + + +static OSStatus +aubio_audio_unit_process(void *closure, AudioUnitRenderActionFlags * action_flags, + const AudioTimeStamp * time_stamp, UInt32 bus_number, UInt32 inNumber_frames, + AudioBufferList * input_output); + +static int aubio_audio_unit_blocking(aubio_audio_unit_t *o); + +static void audio_unit_check_audio_route(aubio_audio_unit_t *o); + +static void audio_unit_interruption_listener(void *closure, UInt32 inInterruptionState); +static void audio_unit_route_change_listener(void *closure, AudioSessionPropertyID + inID, UInt32 dataSize, const void *inData); +static OSStatus audio_unit_set_audio_session_category(bool has_input, bool verbose); +static UInt32 audio_unit_get_audio_session_category (); + +aubio_audio_unit_t * new_aubio_audio_unit(uint_t samplerate, + uint_t sw_input_channels, uint_t sw_output_channels, + uint_t blocksize) +{ + aubio_audio_unit_t * o = AUBIO_NEW(aubio_audio_unit_t); + o->hw_output_channels = 2; + o->hw_input_channels = 2; + o->sw_output_channels = sw_output_channels; + o->sw_input_channels = sw_input_channels; + o->samplerate = samplerate; + o->latency = PREFERRED_LATENCY; + o->blocksize = blocksize; + + o->au_ios_start = 0; + o->au_ios_end = 0; + + o->verbose = 0; + o->input_enabled = true; + o->prevent_feedback = 1; + o->dio_error = 0; + + o->total_frames = 0; + + /* the integers coming from and to the audio unit */ + o->au_ios_outbuf = AUBIO_ARRAY(SInt16, AU_IOS_MAX_FRAMES * o->hw_output_channels); + o->au_ios_inbuf = AUBIO_ARRAY(SInt16, AU_IOS_MAX_FRAMES * o->hw_input_channels); + + /* the floats coming from and to the device callback */ + o->output_frames = new_fmat(blocksize, sw_output_channels); + o->input_frames = new_fmat(blocksize, sw_input_channels); + + /* check for some sizes */ + if ( o->hw_output_channels != o->output_frames->height ) { + AUBIO_ERR ("got hw_output_channels = %d, but output_frames has %d rows", + o->hw_output_channels, o->output_frames->height); + } + if ( o->blocksize != o->output_frames->length ) { + AUBIO_ERR ("got blocksize = %d, but output_frames has length %d", + o->blocksize, o->output_frames->length); + } + if ( o->hw_input_channels != o->input_frames->height ) { + AUBIO_ERR ("got hw_input_channels = %d, but input_frames has %d rows", + o->hw_input_channels, o->input_frames->height); + } + if ( o->blocksize != o->input_frames->length ) { + AUBIO_ERR ("got blocksize = %d, but input_frames has length %d", + o->blocksize, o->input_frames->length); + } + + return o; +} + +sint_t aubio_audio_unit_set_preferred_latency (aubio_audio_unit_t *o, smpl_t latency) +{ + o->latency = latency; + return 0; +} + +sint_t aubio_audio_unit_set_prevent_feedback (aubio_audio_unit_t *o, uint_t prevent_feedback) +{ + o->prevent_feedback = prevent_feedback; + return 0; +} + +sint_t aubio_audio_unit_set_verbose (aubio_audio_unit_t *o, uint_t verbose) +{ + o->verbose = verbose; + return 0; +} + + +sint_t aubio_audio_unit_init (aubio_audio_unit_t *o) +{ + OSStatus err = noErr; + Float32 latency = o->latency; + Float64 samplerate = (Float64)o->samplerate; + + o->au_ios_cb_struct.inputProc = aubio_audio_unit_process; + o->au_ios_cb_struct.inputProcRefCon = o; + + /* setting up audio session with interruption listener */ + err = AudioSessionInitialize(NULL, NULL, audio_unit_interruption_listener, o); + if (err) { AUBIO_ERR("audio_unit: could not initialize audio session (%ld)", err); goto fail; } + + audio_unit_set_audio_session_category(o->input_enabled, o->verbose); + audio_unit_check_audio_route(o); + + /* add route change listener */ + err = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, + audio_unit_route_change_listener, o); + if (err) { AUBIO_ERR("audio_unit: could not set route change listener (%ld)", err); goto fail; } + + /* set latency */ + err = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, + sizeof(latency), &latency); + if (err) { AUBIO_ERR("audio_unit: could not set preferred latency (%ld)", err); goto fail; } + +#if 0 // only for iphone OS >= 3.1 + UInt32 val = 1; // set to 0 (default) to use ear speaker in voice application + err = AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, + sizeof(UInt32), &val); + if (err) { AUBIO_ERR("audio_unit: could not set session property to default to speaker"); } +#endif + + /* setting up audio unit */ + AudioComponentDescription desc; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentType = kAudioUnitType_Output; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + AudioStreamBasicDescription audioFormat; + + /* look for a component that match the description */ + AudioComponent comp = AudioComponentFindNext(NULL, &desc); + + /* create the audio component */ + AudioUnit *audio_unit = &(o->audio_unit); + + err = AudioComponentInstanceNew(comp, &(o->audio_unit)); + if (err) { AUBIO_ERR("audio_unit: failed creating the audio unit"); goto fail; } + + /* enable IO */ + UInt32 enabled = 1; + err = AudioUnitSetProperty (*audio_unit, kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, 1, &enabled, sizeof(enabled)); + if (err) { + AUBIO_ERR("audio_unit: failed enabling input of audio unit"); + goto fail; + } + + /* set max fps */ + UInt32 max_fps = MIN(o->blocksize, MAX_FPS); + err = AudioUnitSetProperty (*audio_unit, kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, 0, &max_fps, sizeof(max_fps)); + if (err) { + AUBIO_ERR("audio_unit: could not set maximum frames per slice property (%ld)", err); + goto fail; + } + + AudioUnitSetProperty (*audio_unit, kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, &(o->au_ios_cb_struct), sizeof(o->au_ios_cb_struct)); + if (err) { AUBIO_ERR("audio_unit: failed setting audio unit render callback"); goto fail; } + +#if 0 + err = AudioUnitSetProperty (*audio_unit, kAudioUnitProperty_SampleRate, + kAudioUnitScope_Input, 0, &samplerate, sizeof(Float64)); + if (err) { AUBIO_ERR("audio_unit: could not set audio input sample rate"); goto fail; } + err = AudioUnitSetProperty (*audio_unit, kAudioUnitProperty_SampleRate, + kAudioUnitScope_Output, 1, &samplerate, sizeof(Float64)); + if (err) { AUBIO_ERR("audio_unit: could not set audio input sample rate"); goto fail; } +#endif + + audioFormat.mSampleRate = (Float64)samplerate; + audioFormat.mChannelsPerFrame = 2; + audioFormat.mFormatID = kAudioFormatLinearPCM; + audioFormat.mFormatFlags = kAudioFormatFlagsCanonical; + audioFormat.mFramesPerPacket = 1; + audioFormat.mBitsPerChannel = 8 * sizeof(AudioSampleType); +#if 1 // interleaving + audioFormat.mBytesPerFrame = 2 * sizeof(AudioSampleType); + audioFormat.mBytesPerPacket = 2 * sizeof(AudioSampleType); +#else + audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame = sizeof(AudioUnitSampleType); + audioFormat.mFormatFlags |= kAudioFormatFlagIsNonInterleaved; +#endif + + err = AudioUnitSetProperty (*audio_unit, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat)); + if (err) { AUBIO_ERR("audio_unit: could not set audio output format"); goto fail; } + err = AudioUnitSetProperty (*audio_unit, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, 1, &audioFormat, sizeof(audioFormat)); + if (err) { AUBIO_ERR("audio_unit: could not set audio input format"); goto fail; } + +#if 0 + AudioStreamBasicDescription thruFormat; + thissize = sizeof(thruFormat); + err = AudioUnitGetProperty (*audio_unit, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, &thruFormat, &thissize); + if (err) { AUBIO_ERR("audio_unit: could not get speaker output format, err: %d", (int)err); goto fail; } + err = AudioUnitSetProperty (*audio_unit, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, 1, &thruFormat, sizeof(thruFormat)); + if (err) { AUBIO_ERR("audio_unit: could not set input audio format, err: %d", (int)err); goto fail; } +#endif + + /* time to initialize the unit */ + err = AudioUnitInitialize(*audio_unit); + if (err) { AUBIO_ERR("audio_unit: failed initializing audio, err: %d", (int)err); goto fail; } + + return 0; + +fail: + return err; +} + +/* perform function */ +OSStatus +aubio_audio_unit_process(void *closure, AudioUnitRenderActionFlags * action_flags, + const AudioTimeStamp * time_stamp, UNUSED UInt32 bus_number, UInt32 inNumber_frames, + AudioBufferList * input_output) +{ + UInt32 b; int err = 0; + aubio_audio_unit_t *o = (aubio_audio_unit_t *)closure; + AudioUnit thisUnit = o->audio_unit; + + if (o->input_enabled) { + err = AudioUnitRender(thisUnit, action_flags, time_stamp, 1, + inNumber_frames, input_output); + if (err) { + AUBIO_ERR("audio_unit: error performing AudioUnitRender (%d)", err); + return err; + } + } + + // get the number of frames from the audio buffer list, NOT inNumber_frames + UInt32 number_frames = input_output->mBuffers[0].mDataByteSize/ sizeof(SInt16) / 2; + + // FIXME find out why this happens + if (number_frames < 10) { + AUBIO_ERR("audio_unit: got number_frames %d", (int)number_frames); + return -1; + } + + if (o->total_frames >= (signed)number_frames) { + + SInt16 *data; + if (o->au_ios_start + number_frames > AU_IOS_MAX_FRAMES) { + // start reminder samples writing at reminder + int reminder = AU_IOS_MAX_FRAMES - o->au_ios_start; + int starter = (o->au_ios_start + number_frames) - AU_IOS_MAX_FRAMES; + for (b = 0; b < input_output->mNumberBuffers; b++) { + data = (SInt16 *)(input_output->mBuffers[b].mData); + /* copy microphone output to input buffer */ + memcpy (o->au_ios_inbuf + o->au_ios_start * 2, data, reminder * 2 * sizeof(SInt16)); + memcpy (o->au_ios_inbuf, data + reminder * 2, starter * 2 * sizeof(SInt16)); + /* silence data before copying from output */ + //memset (data, 0, input_output->mBuffers[b].mDataByteSize); + /* copy output buffer to speakers */ + memcpy (data, o->au_ios_outbuf + o->au_ios_start * 2, reminder * 2 * sizeof(SInt16)); + memcpy (data + reminder * 2, o->au_ios_outbuf, starter * 2 * sizeof(SInt16)); + } + } else { + for (b = 0; b < input_output->mNumberBuffers; b++) { + data = (SInt16 *)(input_output->mBuffers[b].mData); + /* copy microphone samples to au_ios_inbuf */ + memcpy(o->au_ios_inbuf + o->au_ios_start * 2, data, number_frames * 2 * sizeof(SInt16)); + /* silence data before copying from output */ + //memset (data, 0, input_output->mBuffers[b].mDataByteSize); + /* copy output buffer to speakers */ + memcpy(data, o->au_ios_outbuf + o->au_ios_start * 2, number_frames * 2 * sizeof(SInt16)); + } + } + o->au_ios_start += number_frames; + o->au_ios_start %= AU_IOS_MAX_FRAMES; + o->total_frames -= number_frames; + +#if 1 + } else { + if (o->total_frames > 0) o->dio_error = 1; + for (b = 0; b < input_output->mNumberBuffers; b++) { + memset (input_output->mBuffers[b].mData, 0, + input_output->mBuffers[b].mDataByteSize); + } + //total_frames = 0; +#endif + } + + // now call callback + while ( o->total_frames < (signed)number_frames ) { + //AUBIO_DBG ("audio_unit: total_frames = %d, number_frames = %d, o->au_ios_start = %d, o->au_ios_end = %d", + // o->total_frames, number_frames, o->au_ios_start, o->au_ios_end); + aubio_audio_unit_blocking(o); + } + + return err; +} + +int aubio_audio_unit_blocking(aubio_audio_unit_t *o) +{ + uint_t sw_output_channels, sw_input_channels, + hw_output_channels, hw_input_channels, + i, j, blocksize; + if (! o->callback) return -1; + + smpl_t ** tbuf; + + sw_output_channels = o->sw_output_channels; + sw_input_channels = o->sw_input_channels; + hw_output_channels = o->hw_output_channels; + hw_input_channels = o->hw_input_channels; + blocksize = o->blocksize; + + if (!sw_input_channels && !sw_output_channels) goto fail; + + if (o->dio_error) { + AUBIO_WRN("audio_unit: dio error %d", o->total_frames); + o->dio_error = 0; + } + + if (o->au_ios_inbuf) { + /* copy samples from input buffer */ + tbuf = o->input_frames->data; + if (o->input_enabled) { + for (j = 0; j < blocksize;j++) { + for (i = 0; i < sw_input_channels && i < hw_input_channels; i++) { + //tbuf[i][j] = + // (smpl_t)(o->au_ios_inbuf[i + (j + o->au_ios_end) * sw_input_channels] / 32768.); + // on iphone, input is mono, copy right to left channel + tbuf[i][j] = + (smpl_t) o->au_ios_inbuf[0 + (j + o->au_ios_end) * hw_input_channels] + * INT_TO_FLOAT; + } + } + } else { + // input is disabled, fill with zeroes + for (j = 0; j < blocksize; j++) { + for (i = 0; i < sw_input_channels && i < hw_input_channels; i++) { + tbuf[i][j] = 0; + } + } + } + } + + o->callback(o->callback_closure, o->input_frames, o->output_frames); + + /* copy samples to output buffer */ + tbuf = o->output_frames->data; + for (i = 0; i < o->output_frames->height; i++) { + for (j = 0; j < o->output_frames->length; j++) { + smpl_t val = tbuf[i][j]; + if (val < -1.0) val = -1.0; + if (val > 1.0) val = 1.0; + o->au_ios_outbuf[i + (j + o->au_ios_end) * hw_output_channels ] = (SInt16)(val * 32767); + } + } + + o->au_ios_end += blocksize; + o->au_ios_end %= AU_IOS_MAX_FRAMES; + o->total_frames += blocksize; + + return 0; + +fail: + AUBIO_ERR("audio_unit: callback() failed"); + o->total_frames += AU_IOS_MAX_FRAMES; + return 1; +} + +sint_t aubio_audio_unit_get_info (aubio_audio_unit_t *o) +{ + UInt32 thissize, input_hw_channels, output_hw_channels, max_fps; + Float32 latency, input_latency, output_latency, input_hw_volume, output_hw_volume; + Float64 samplerate; + OSStatus err = 0; + + // Show some info about the opened unit + + /* get sampling rate */ + thissize = sizeof(samplerate); + err = AudioUnitGetProperty (o->audio_unit, kAudioUnitProperty_SampleRate, + kAudioUnitScope_Output, 1, &samplerate, &thissize); + if (err) { AUBIO_ERR("audio_unit: could not get audio unit sample rate (%ld)", + err); goto fail; } + + /* get hardware input channels */ + thissize = sizeof(input_hw_channels); + err = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels, + &thissize, &input_hw_channels); + if (err) { AUBIO_ERR("audio_unit: could not get hardware input channels (%ld)", + err); goto fail; } + + /* get hardware output channels */ + thissize = sizeof(output_hw_channels); + err = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputNumberChannels, + &thissize, &output_hw_channels); + if (err) { AUBIO_ERR("audio_unit: could not get hardware output channels (%ld)", + err); goto fail; } + + /* get hardware input volume */ + thissize = sizeof(input_hw_volume); + err = AudioSessionGetProperty(kAudioSessionProperty_InputGainScalar, + &thissize, &input_hw_volume); + if (err) { AUBIO_ERR("audio_unit: could not get hardware input volume (%ld)", + err); goto fail; } + + /* get hardware output volume */ + thissize = sizeof(output_hw_volume); + err = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputVolume, + &thissize, &output_hw_volume); + if (err) { AUBIO_ERR("audio_unit: could not get hardware output volume (%ld)", + err); goto fail; } + + AUBIO_MSG("audio_unit: opened at %.0fHz, sw channels %din/%dout, hw channels %ldin/%ldout, hw vol %.2fin/%.2fout", + samplerate, + o->sw_input_channels, o->sw_output_channels, + input_hw_channels, output_hw_channels, + input_hw_volume, output_hw_volume); + + /* get max frames per slice */ + thissize = sizeof(max_fps); + err = AudioUnitGetProperty (o->audio_unit, kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, 0, &max_fps, &thissize); + if (err) { AUBIO_ERR("audio_unit: could not get maximum frames per slice property %ld", + err); goto fail; } + + /* get hardware latency */ + thissize = sizeof(latency); + err = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareIOBufferDuration, + &thissize, &latency); + if (err) { AUBIO_ERR("audio_unit: could not get hardware latency %ld", + err); goto fail; } + + /* get input latency */ + thissize = sizeof(input_latency); + err = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputLatency, + &thissize, &input_latency); + if (err) { AUBIO_ERR("audio_unit: could not get input latency %ld", + err); goto fail; } + + /* get output harlatency */ + thissize = sizeof(output_latency); + err = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputLatency, + &thissize, &output_latency); + if (err) { AUBIO_ERR("audio_unit: could not get output latency %ld", + err); goto fail; } + + AUBIO_MSG("audio_unit: I/O latency: %.2fms, %d frames, (%.2fms, %d frames in, %.2fms %d frames out)", + latency*1000., (sint_t)round(latency*samplerate), + input_latency*1000., (sint_t)ROUND(input_latency*samplerate), + output_latency*1000., (sint_t)ROUND(output_latency*samplerate)); + +fail: + return err; +} + +sint_t aubio_audio_unit_start(aubio_audio_unit_t *o) { + OSStatus err = 0; + + if (o->verbose) { + // print some info about the current settings + aubio_audio_unit_get_info (o); + } + + /* time to start the unit */ + err = AudioOutputUnitStart (o->audio_unit); + if (err) { AUBIO_ERR("audio_unit: could not start unit (%ld)", err); } + return err; +} + +sint_t aubio_audio_unit_stop(aubio_audio_unit_t *o) +{ + if (o->audio_unit == NULL) return -1; + OSStatus err = AudioOutputUnitStop (o->audio_unit); + if (err) { AUBIO_WRN("audio_unit: failed stopping audio unit (%ld)", err); } + err = AudioUnitUninitialize (o->audio_unit); + if (err) { AUBIO_WRN("audio_unit: failed unitializing audio unit (%ld)", err); } + err = AudioSessionSetActive(false); + if (err) { AUBIO_WRN("audio_unit: failed stopping audio session (%ld)", err); } + return err; +} + +uint_t aubio_audio_unit_set_callback(aubio_audio_unit_t *o, + aubio_device_callback_t callback, void *closure) { + o->callback = callback; + o->callback_closure = closure; + return 0; +} + +/* interruption listeners */ +void audio_unit_interruption_listener(void *closure, UInt32 inInterruptionState) +{ + OSStatus err = 0; + aubio_audio_unit_t *o = (aubio_audio_unit_t *) closure; + AudioUnit this_unit = o->audio_unit; + + if (inInterruptionState == kAudioSessionEndInterruption) { + AUBIO_WRN("audio_unit: session interruption ended"); + err = AudioSessionSetActive(true); + if (err) { + AUBIO_ERR("audio_unit: could not make session active after interruption (%ld)", err); + goto fail; + } + err = AudioOutputUnitStart(this_unit); + if (err) { + AUBIO_ERR("audio_unit: failed starting unit (%ld)", err); + goto fail; + } + } + if (inInterruptionState == kAudioSessionBeginInterruption) { + AUBIO_WRN("audio_unit: session interruption started"); + err = AudioOutputUnitStop(this_unit); + if (err) { + AUBIO_ERR("audio_unit: could not stop unit at interruption (%ld)", err); + goto fail; + } + err = AudioSessionSetActive(false); + if (err) { + AUBIO_ERR("audio_unit: could not make session inactive after interruption (%ld)", err); + goto fail; + } + } +fail: + return; +} + +UInt32 audio_unit_get_audio_session_category () { + UInt32 category, thissize; + thissize = sizeof(category); + OSStatus err = AudioSessionGetProperty(kAudioSessionProperty_AudioCategory, + &thissize, &category); + if (err) { + AUBIO_ERR("audio_unit: could not get audio category (%ld)", err); + return err; + } + if (category == kAudioSessionCategory_AmbientSound) { + AUBIO_MSG("audio_unit: session category is AmbiantSound"); + } else if (category == kAudioSessionCategory_SoloAmbientSound) { + AUBIO_MSG("audio_unit: session category is SoloAmbiantSound"); + } else if (category == kAudioSessionCategory_MediaPlayback) { + AUBIO_MSG("audio_unit: session category is MediaPlayback"); + } else if (category == kAudioSessionCategory_RecordAudio) { + AUBIO_MSG("audio_unit: session category is RecordAudio"); + } else if (category == kAudioSessionCategory_PlayAndRecord) { + AUBIO_MSG("audio_unit: session category is PlayAndRecord"); + } else if (category == kAudioSessionCategory_AudioProcessing) { + AUBIO_MSG("audio_unit: session category is AudioProcessing"); + } + return category; +} + +OSStatus audio_unit_set_audio_session_category(bool has_input, bool verbose) +{ + //if we have input, set the session category accordingly + OSStatus err = 0; + UInt32 category; + if (has_input) { + category = kAudioSessionCategory_PlayAndRecord; + if (verbose) AUBIO_MSG("audio_unit: setting category to PlayAndRecord"); + } else { + category = kAudioSessionCategory_MediaPlayback; + if (verbose) AUBIO_MSG("audio_unit: setting category to MediaPlayback"); + } + err = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, + sizeof(category), &category); + if (err) { + AUBIO_ERR("audio_unit: could not set audio category"); + } + + // Audiob.us style + UInt32 allowMixing = 1; + AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers, + sizeof (allowMixing), &allowMixing); + if (err) { + AUBIO_ERR("audio_unit: could not set audio session to mix with others"); + } + + return err; +} + +void audio_unit_check_audio_route(aubio_audio_unit_t *o) { + CFStringRef currentRoute; + UInt32 val, thissize = sizeof(currentRoute); + OSStatus err = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &thissize, ¤tRoute); + if (err) { AUBIO_ERR("audio_unit: could not get current route"); goto fail; } + else { + char *route = (char *)CFStringGetCStringPtr ( currentRoute, kCFStringEncodingUTF8); + if (route == NULL) { + int bufferSize = 25; + route = calloc(bufferSize, sizeof(char)); + CFStringGetCString ( currentRoute, route, bufferSize, + kCFStringEncodingUTF8); + } + if (o->verbose) { + AUBIO_MSG ("audio_unit: current route is %s", route); + } + free(route); + } + if( currentRoute ) { + if( CFStringCompare( currentRoute, CFSTR("Headset"), 0 ) == kCFCompareEqualTo ) { + val = kAudioSessionOverrideAudioRoute_None; + } else if( CFStringCompare( currentRoute, CFSTR("Receiver" ), 0 ) == kCFCompareEqualTo ) { + val = kAudioSessionOverrideAudioRoute_Speaker; + } else if( CFStringCompare( currentRoute, CFSTR("ReceiverAndMicrophone" ), 0 ) == kCFCompareEqualTo ) { + val = kAudioSessionOverrideAudioRoute_Speaker; + } else if( CFStringCompare( currentRoute, CFSTR("SpeakerAndMicrophone" ), 0 ) == kCFCompareEqualTo ) { + val = kAudioSessionOverrideAudioRoute_Speaker; + } else if( CFStringCompare( currentRoute, CFSTR("HeadphonesAndMicrophone" ), 0 ) == kCFCompareEqualTo ) { + val = kAudioSessionOverrideAudioRoute_None; + } else if( CFStringCompare( currentRoute, CFSTR("HeadsetInOut" ), 0 ) == kCFCompareEqualTo ) { + val = kAudioSessionOverrideAudioRoute_None; + } else { + val = kAudioSessionOverrideAudioRoute_None; + } + + o->input_enabled = true; + if (val == kAudioSessionOverrideAudioRoute_Speaker) { + if (o->prevent_feedback) { + o->input_enabled = false; + if (o->verbose) { + AUBIO_MSG ("audio_unit: disabling input to avoid feedback"); + } + } else { + AUBIO_WRN ("audio_unit: input not disabled as prevent_feedback set to 0, risking feedback"); + } + } + + err = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, + sizeof(UInt32), &val); + if (err) { AUBIO_ERR("audio_unit: could not set session OverrideAudioRoute to Speaker"); } + + } + +fail: + if ( currentRoute ) free((void*)currentRoute); + return; + +} + +SInt32 +audio_unit_get_route_change_reason(CFDictionaryRef routeChangeDic) { + CFNumberRef routeChangeReasonRef = (CFNumberRef)CFDictionaryGetValue(routeChangeDic, + CFSTR(kAudioSession_AudioRouteChangeKey_Reason)); + SInt32 change_reason_number; + CFNumberGetValue ( routeChangeReasonRef, kCFNumberSInt32Type, &change_reason_number); + switch (change_reason_number) { + case kAudioSessionRouteChangeReason_NewDeviceAvailable: + AUBIO_MSG("audio_unit: route changed to NewDeviceAvailable"); + break; + case kAudioSessionRouteChangeReason_OldDeviceUnavailable: + AUBIO_MSG("audio_unit: route changed to OldDeviceUnavailable"); + break; + case kAudioSessionRouteChangeReason_CategoryChange: + AUBIO_MSG("audio_unit: route changed to CategoryChange"); + audio_unit_get_audio_session_category(); + break; + case kAudioSessionRouteChangeReason_Override: + AUBIO_MSG("audio_unit: route changed to Override"); + break; + case kAudioSessionRouteChangeReason_WakeFromSleep: + AUBIO_MSG("audio_unit: route changed to WakeFromSleep"); + break; + case kAudioSessionRouteChangeReason_NoSuitableRouteForCategory: + AUBIO_MSG("audio_unit: route changed to NoSuitableRouteForCategory"); + break; + case kAudioSessionRouteChangeReason_Unknown: + default: + AUBIO_ERR("audio_unit: route changed for an unknown reason!?"); + break; + } + return change_reason_number; +} + +/* route change listeners */ +void +audio_unit_route_change_listener(void *closure, AudioSessionPropertyID inID, + UInt32 dataSize, const void *inData) +{ + + UNUSED aubio_audio_unit_t *o = (aubio_audio_unit_t *)closure; + UNUSED UInt32 size = dataSize; + if (inID == kAudioSessionProperty_AudioRouteChange) { + + // OSStatus err = 0; + //AudioUnit audio_unit = o->audio_unit; + + if (o->verbose) { + // show route change reason + audio_unit_get_route_change_reason((CFDictionaryRef)inData); + } + + // check current audio route, changing it to prevent feedback as needed + audio_unit_check_audio_route(o); + + if (o->verbose) { + // print some info about the current settings + aubio_audio_unit_get_info(o); + } + + } + +} + +/* delete object */ +uint_t del_aubio_audio_unit(aubio_audio_unit_t *o) +{ + int err = 0; + err = aubio_audio_unit_stop(o); + if (o->au_ios_inbuf) free(o->au_ios_inbuf); + o->au_ios_inbuf = NULL; + if (o->au_ios_outbuf) free(o->au_ios_outbuf); + o->au_ios_outbuf = NULL; + del_fmat (o->input_frames); + del_fmat (o->output_frames); + o->audio_unit = NULL; + return (int)err; +} + +#endif /* TARGET_OS_IPHONE */ diff --git a/src/io/audio_unit.h b/src/io/audio_unit.h new file mode 100644 index 00000000..c518bc64 --- /dev/null +++ b/src/io/audio_unit.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2013 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#ifndef _AUBIO_AUDIO_UNIT_H +#define _AUBIO_AUDIO_UNIT_H + +/** \file + +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _aubio_audio_unit_t aubio_audio_unit_t; + +aubio_audio_unit_t * new_aubio_audio_unit(uint_t samplerate, uint_t inchannels, + uint_t outchannels, uint_t blocksize); + +typedef uint_t (*aubio_device_callback_t) (void * closure, fmat_t *ibuf, fmat_t *obuf); + +uint_t aubio_audio_unit_set_callback(aubio_audio_unit_t *o, + aubio_device_callback_t callback, void *closure); + +sint_t aubio_audio_unit_set_verbose (aubio_audio_unit_t *o, uint_t verbose); +sint_t aubio_audio_unit_set_preferred_latency (aubio_audio_unit_t *o, smpl_t + latency); +sint_t aubio_audio_unit_set_prevent_feedback (aubio_audio_unit_t *o, uint_t + prevent_feedback); + +sint_t aubio_audio_unit_get_info (aubio_audio_unit_t *o); + +sint_t aubio_audio_unit_init (aubio_audio_unit_t *o); + +sint_t aubio_audio_unit_start (aubio_audio_unit_t *o); +sint_t aubio_audio_unit_stop (aubio_audio_unit_t *o); + +uint_t del_aubio_audio_unit(aubio_audio_unit_t *o); + +#ifdef __cplusplus +} +#endif + +#endif /* _AUBIO_AUDIO_UNIT_H */ -- 2.11.0