//----------------------------------------------------------------------------- // name: mo_audio.cpp // desc: MoPhO audio layer // - adapted from the smule audio layer & library (SMALL) // (see original header below) // // authors: Ge Wang (ge@ccrma.stanford.edu | ge@smule.com) // Spencer Salazar (spencer@smule.com) // date: October 2009 // version: 0.2 //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // name: small.cpp (original) // desc: the smule audio layer & library (SMALL) // // created by Ge Wang on 6/27/2008 // re-implemented using Audio Unit Remote I/O on 8/12/2008 // updated for iPod Touch by Spencer Salazar and Ge wang on 8/10/2009 // // copyright 2008 Smule. All rights reserved. //----------------------------------------------------------------------------- #include "mo_audio.h" #include // static member initialization bool MoAudio::m_hasInit = false; bool MoAudio::m_isRunning = false; bool MoAudio::m_isMute = false; bool MoAudio::m_handleInput = false; Float64 MoAudio::m_srate = 44100.0; Float64 MoAudio::m_hwSampleRate = 44100.0; UInt32 MoAudio::m_frameSize = 0; UInt32 MoAudio::m_numChannels = 2; AudioUnit MoAudio::m_au; MoAudioUnitInfo * MoAudio::m_info = NULL; MoCallback MoAudio::m_callback = NULL; // Float32 * MoAudio::m_buffer = NULL; // UInt32 MoAudio::m_bufferFrames = 2048; AURenderCallbackStruct MoAudio::m_renderProc; void * MoAudio::m_bindle = NULL; // number of buffers #define MO_DEFAULT_NUM_BUFFERS 3 // prototypes bool setupRemoteIO( AudioUnit & inRemoteIOUnit, AURenderCallbackStruct inRenderProc, AudioStreamBasicDescription & outFormat); //----------------------------------------------------------------------------- // name: silenceData() // desc: zero out a buffer list of audio data //----------------------------------------------------------------------------- void silenceData( AudioBufferList * inData ) { for( UInt32 i = 0; i < inData->mNumberBuffers; i++ ) memset( inData->mBuffers[i].mData, 0, inData->mBuffers[i].mDataByteSize ); } //----------------------------------------------------------------------------- // name: convertToUser() // desc: convert to user data (stereo) //----------------------------------------------------------------------------- void convertToUser( AudioBufferList * inData, Float32 * buffy, UInt32 numFrames, UInt32 & actualFrames ) { // make sure there are exactly two channels assert( inData->mNumberBuffers == MoAudio::m_numChannels ); // get number of frames UInt32 inFrames = inData->mBuffers[0].mDataByteSize / sizeof(SInt32); // make sure enough space assert( inFrames <= numFrames ); // channels SInt32 * left = (SInt32 *)inData->mBuffers[0].mData; SInt32 * right = (SInt32 *)inData->mBuffers[1].mData; // fixed to float scaling factor Float32 factor = (Float32)(1 << 24); // interleave (AU is by default non interleaved) for( UInt32 i = 0; i < inFrames; i++ ) { // convert (AU is by default 8.24 fixed) buffy[2*i] = ((Float32)left[i]) / factor; buffy[2*i+1] = ((Float32)right[i]) / factor; } // return actualFrames = inFrames; } //----------------------------------------------------------------------------- // name: convertFromUser() // desc: convert from user data (stereo) //----------------------------------------------------------------------------- void convertFromUser( AudioBufferList * inData, Float32 * buffy, UInt32 numFrames ) { // make sure there are exactly two channels assert( inData->mNumberBuffers == MoAudio::m_numChannels ); // get number of frames UInt32 inFrames = inData->mBuffers[0].mDataByteSize / 4; // make sure enough space assert( inFrames <= numFrames ); // channels SInt32 * left = (SInt32 *)inData->mBuffers[0].mData; SInt32 * right = (SInt32 *)inData->mBuffers[1].mData; // fixed to float scaling factor Float32 factor = (Float32)(1 << 24); // interleave (AU is by default non interleaved) for( UInt32 i = 0; i < inFrames; i++ ) { // convert (AU is by default 8.24 fixed) left[i] = (SInt32)(buffy[2*i] * factor); right[i] = (SInt32)(buffy[2*i+1] * factor); } } //----------------------------------------------------------------------------- // name: SMALLRenderProc() // desc: callback procedure awaiting audio unit audio buffers //----------------------------------------------------------------------------- static OSStatus SMALLRenderProc( void * inRefCon, AudioUnitRenderActionFlags * ioActionFlags, const AudioTimeStamp * inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList * ioData ) { OSStatus err = noErr; // render if full-duplex available and enabled if( MoAudio::m_handleInput ) { err = AudioUnitRender( MoAudio::m_au, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData ); if( err ) { // print error printf( "MoAudio: render procedure encountered error %d\n", (int)err ); return err; } } // actual frames UInt32 actualFrames = 0; // convert convertToUser( ioData, MoAudio::m_info->m_ioBuffer, MoAudio::m_info->m_bufferSize, actualFrames ); // callback MoAudio::m_callback( MoAudio::m_info->m_ioBuffer, actualFrames, MoAudio::m_bindle ); // convert back convertFromUser( ioData, MoAudio::m_info->m_ioBuffer, MoAudio::m_info->m_bufferSize ); // mute? if( MoAudio::m_isMute ) { silenceData( ioData ); } return err; } //----------------------------------------------------------------------------- // name: rioInterruptionListener() // desc: handler for interruptions to start and end //----------------------------------------------------------------------------- static void rioInterruptionListener( void * inUserData, UInt32 inInterruption ) { // printf( "Session interrupted! --- %s ---", // inInterruption == kAudioSessionBeginInterruption // ? "Begin Interruption" : "End Interruption" ); AudioUnit * rio = (AudioUnit *)inUserData; // end if( inInterruption == kAudioSessionEndInterruption ) { // make sure we are again the active session AudioSessionSetActive( true ); AudioOutputUnitStart( *rio ); } // begin if( inInterruption == kAudioSessionBeginInterruption ) AudioOutputUnitStop( *rio ); } //----------------------------------------------------------------------------- // name: propListener() // desc: audio session property listener //----------------------------------------------------------------------------- static void propListener( void * inClientData, AudioSessionPropertyID inID, UInt32 inDataSize, const void * inData ) { // detect audio route change if( inID == kAudioSessionProperty_AudioRouteChange ) { // status code OSStatus err; // if there was a route change, we need to dispose the current rio unit and create a new one err = AudioComponentInstanceDispose( MoAudio::m_au ); if( err ) { // TODO: "couldn't dispose remote i/o unit" return; } // set up setupRemoteIO( MoAudio::m_au, MoAudio::m_renderProc, MoAudio::m_info->m_dataFormat ); UInt32 size = sizeof(MoAudio::m_hwSampleRate); // get sample rate err = AudioSessionGetProperty( kAudioSessionProperty_CurrentHardwareSampleRate, &size, &MoAudio::m_hwSampleRate ); if( err ) { // TODO: "couldn't get new sample rate" return; } // check input MoAudio::checkInput(); // start audio unit err = AudioOutputUnitStart( MoAudio::m_au ); if( err ) { // TODO: "couldn't start unit" return; } // get route CFStringRef newRoute; size = sizeof(CFStringRef); err = AudioSessionGetProperty( kAudioSessionProperty_AudioRoute, &size, &newRoute ); if( err ) { // TODO: "couldn't get new audio route" return; } // check route if( newRoute ) { // CFShow( newRoute ); if( CFStringCompare( newRoute, CFSTR("Headset"), NULL ) == kCFCompareEqualTo ) { } else if( CFStringCompare( newRoute, CFSTR("Receiver" ), NULL ) == kCFCompareEqualTo ) { } else // unknown { } } } } //----------------------------------------------------------------------------- // name: setupRemoteIO() // desc: setup Audio Unit Remote I/O //----------------------------------------------------------------------------- bool setupRemoteIO( AudioUnit & inRemoteIOUnit, AURenderCallbackStruct inRenderProc, AudioStreamBasicDescription & outFormat ) { // open the output unit AudioComponentDescription desc; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_RemoteIO; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; // find next component AudioComponent comp = AudioComponentFindNext( NULL, &desc ); // status code OSStatus err; // the stream description AudioStreamBasicDescription localFormat; // open remote I/O unit err = AudioComponentInstanceNew( comp, &inRemoteIOUnit ); if( err ) { // TODO: "couldn't open the remote I/O unit" return false; } UInt32 one = 1; // enable input err = AudioUnitSetProperty( inRemoteIOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof(one) ); if( err ) { // TODO: "couldn't enable input on the remote I/O unit" return false; } // set render proc err = AudioUnitSetProperty( inRemoteIOUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inRenderProc, sizeof(inRenderProc) ); if( err ) { // TODO: "couldn't set remote i/o render callback" return false; } UInt32 size = sizeof(localFormat); // get and set client format err = AudioUnitGetProperty( inRemoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &localFormat, &size ); if( err ) { // TODO: "couldn't get the remote I/O unit's output client format" return false; } localFormat.mSampleRate = outFormat.mSampleRate; localFormat.mChannelsPerFrame = outFormat.mChannelsPerFrame; localFormat.mFormatID = outFormat.mFormatID; localFormat.mSampleRate = outFormat.mSampleRate; localFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved | (24 << kLinearPCMFormatFlagsSampleFractionShift); localFormat.mChannelsPerFrame = outFormat.mChannelsPerFrame; localFormat.mBitsPerChannel = 32; localFormat.mFramesPerPacket = 1; localFormat.mBytesPerFrame = 4; localFormat.mBytesPerPacket = 4; // set stream property err = AudioUnitSetProperty( inRemoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &localFormat, sizeof(localFormat) ); if( err ) { // TODO: "couldn't set the remote I/O unit's input client format" return false; } size = sizeof(outFormat); // get it again err = AudioUnitGetProperty( inRemoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outFormat, &size ); if( err ) { // TODO: "couldn't get the remote I/O unit's output client format" return false; } err = AudioUnitSetProperty( inRemoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &outFormat, sizeof(outFormat) ); if( err ) { // TODO: "couldn't set the remote I/O unit's input client format" return false; } // print the format // printf( "format for remote i/o:\n" ); // outFormat.Print(); // initialize remote I/O unit err = AudioUnitInitialize( inRemoteIOUnit ); if( err ) { // TODO: "couldn't initialize the remote I/O unit" return false; } return true; } //----------------------------------------------------------------------------- // name: init() // desc: initialize the MoAudio //----------------------------------------------------------------------------- bool MoAudio::init( Float64 srate, UInt32 frameSize, UInt32 numChannels ) { // sanity check if( m_hasInit ) { // TODO: error message return false; } // TODO: fix this assert( numChannels == 2 ); // set audio unit callback m_renderProc.inputProc = SMALLRenderProc; // uh, this probably shouldn't be NULL m_renderProc.inputProcRefCon = NULL; // allocate info m_info = new MoAudioUnitInfo(); // set the desired data format m_info->m_dataFormat.mSampleRate = srate; m_info->m_dataFormat.mFormatID = kAudioFormatLinearPCM; m_info->m_dataFormat.mChannelsPerFrame = numChannels; m_info->m_dataFormat.mBitsPerChannel = 32; m_info->m_dataFormat.mBytesPerPacket = m_info->m_dataFormat.mBytesPerFrame = m_info->m_dataFormat.mChannelsPerFrame * sizeof(SInt32); m_info->m_dataFormat.mFramesPerPacket = 1; m_info->m_dataFormat.mReserved = 0; m_info->m_dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; m_info->m_done = 0; // bound parameters if( frameSize > m_info->m_bufferSize ) frameSize = m_info->m_bufferSize; if( numChannels != 2 ) numChannels = 2; // copy parameters m_srate = srate; m_frameSize = frameSize; m_numChannels = numChannels; // return status code OSStatus err; // initialize and configure the audio session err = AudioSessionInitialize( NULL, NULL, rioInterruptionListener, m_au ); if( err ) { // TODO: "couldn't initialize audio session" return false; } // set audio session active err = AudioSessionSetActive( true ); if( err ) { // TODO: "couldn't set audio session active\n" return false; } UInt32 category = kAudioSessionCategory_PlayAndRecord; // set audio category err = AudioSessionSetProperty( kAudioSessionProperty_AudioCategory, sizeof(category), &category ); if( err ) { // TODO: "couldn't set audio category" return false; } // set property listener err = AudioSessionAddPropertyListener( kAudioSessionProperty_AudioRouteChange, propListener, NULL ); if( err ) { // TODO: "couldn't set property listener" return false; } // check for >= OS 2.1? { // check for headset // dont override if headset is plugged in // get route CFStringRef route; UInt32 size = sizeof(CFStringRef); err = AudioSessionGetProperty( kAudioSessionProperty_AudioRoute, &size, &route ); if( err ) { // TODO: "couldn't get new audio route\n" } UInt32 override; CFRange range = CFStringFind(route, CFSTR("Headset"), 0); if(range.location != kCFNotFound) { override = kAudioSessionOverrideAudioRoute_None; } else { range = CFStringFind(route, CFSTR("Headphone"), 0); if(range.location != kCFNotFound) { override = kAudioSessionOverrideAudioRoute_None; } else { override = kAudioSessionOverrideAudioRoute_Speaker; } } // set speaker override err = AudioSessionSetProperty( kAudioSessionProperty_OverrideAudioRoute, sizeof(override), &override ); if( err ) { // TODO: "couldn't get new audio route\n" return false; } } // compute durations Float32 preferredBufferSize = (Float32)(frameSize / srate); // .020; // set I/O buffer duration AudioSessionSetProperty( kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(preferredBufferSize), &preferredBufferSize ); if( err ) { // TODO: "couldn't set i/o buffer duration" return false; } Float64 preferredSampleRate = srate; // set sample rate AudioSessionSetProperty( kAudioSessionProperty_PreferredHardwareSampleRate, sizeof(preferredSampleRate), &preferredSampleRate ); if( err ) { // TODO: "couldn't set preferred sample rate" return false; } UInt32 size = sizeof(MoAudio::m_hwSampleRate); // get sample rate err = AudioSessionGetProperty( kAudioSessionProperty_CurrentHardwareSampleRate, &size, &MoAudio::m_hwSampleRate ); if ( err ) { // TODO: "couldn't get hw sample rate" return false; } // set up remote I/O if( !setupRemoteIO( m_au, m_renderProc, m_info->m_dataFormat ) ) { // TODO: "couldn't setup remote i/o unit" return false; } // initialize buffer m_info->m_ioBuffer = new Float32[m_info->m_bufferSize * m_numChannels]; // make sure if( !m_info->m_ioBuffer ) { // TODO: "couldn't allocate memory for I/O buffer" return false; } // check audio input checkInput(); // done with initialization m_hasInit = true; return true; } //----------------------------------------------------------------------------- // name: start() // desc: start the MoAudio //----------------------------------------------------------------------------- bool MoAudio::start( MoCallback callback, void * bindle ) { // assert assert( callback != NULL ); // sanity check if( !m_hasInit ) { // TODO: error message return false; } // sanity check 2 if( m_isRunning ) { // TODO: warning message return false; } // remember the callback m_callback = callback; // remember the bindle m_bindle = bindle; // status code OSStatus err; // start audio unit err = AudioOutputUnitStart( m_au ); if( err ) { // TODO: "couldn't start audio unit...\n" ); return false; } // started m_isRunning = true; return true; } //----------------------------------------------------------------------------- // name: stop() // desc: stop the MoAudio //----------------------------------------------------------------------------- void MoAudio::stop() { // sanity check if( !m_isRunning ) return; // status code OSStatus err; // stop audio unit err = AudioOutputUnitStop( m_au ); // flag m_isRunning = false; } //----------------------------------------------------------------------------- // name: shutdown() // desc: shutdown the MoAudio //----------------------------------------------------------------------------- void MoAudio::shutdown() { // sanity check if( !m_hasInit ) return; // stop stop(); // flag m_hasInit = false; // clear the callback m_callback = NULL; // clean up SAFE_DELETE( m_info ); } //----------------------------------------------------------------------------- // name: checkInput() // desc: check audio input, and sets appropriate flag //----------------------------------------------------------------------------- void MoAudio::checkInput() { // handle input in callback m_handleInput = true; UInt32 has_input; UInt32 size = sizeof(has_input); // get property OSStatus err = AudioSessionGetProperty( kAudioSessionProperty_AudioInputAvailable, &size, &has_input ); if( err ) { // TODO: "warning: unable to determine availability of audio input" } else if( !has_input ) { // TODO: "warning: full duplex enabled without available input" m_handleInput = false; } } //------------------------------------------------------------------------------ // name: vibrate() // desc: trigger vibration //------------------------------------------------------------------------------ void MoAudio::vibrate() { AudioServicesPlaySystemSound( kSystemSoundID_Vibrate ); }