ofxiPhoneSoundStream.mm
Go to the documentation of this file.
00001 /***********************************************************************
00002  
00003  Copyright (c) 2009
00004  Memo Akten, http://www.memo.tv
00005  Marek Bareza http://mrkbrz.com/
00006  
00007  Updated 2012 by Dan Wilcox <danomatika@gmail.com>
00008  
00009  references:
00010     - http://michael.tyson.id.au/2008/11/04/using-remoteio-audio-unit/
00011  
00012  ***********************************************************************/
00013 
00014 #include "ofxiPhoneSoundStream.h"
00015 
00016 #ifdef OF_SOUNDSTREAM_IPHONE
00017 
00018 #include "ofSoundStream.h"
00019 #include "ofMath.h"
00020 #include "ofUtils.h"
00021 #import <AudioToolbox/AudioToolbox.h>
00022 #import "ofxiPhone.h"
00023 
00024 static bool                         isSetup         = false;
00025 static bool                         isRunning       = false;
00026 AudioStreamBasicDescription         format, audioFormat;
00027 AudioUnit                           audioUnit       = NULL;
00028 AudioBufferList                     inputBufferList;        // input buffer
00029 static ofBaseSoundInput *           soundInputPtr   = NULL;
00030 static ofBaseSoundOutput *          soundOutputPtr  = NULL;
00031 
00032 // intermediate buffer for sample scaling
00033 #define MAX_BUFFER_SIZE 4096
00034 float scaleBuffer[MAX_BUFFER_SIZE];
00035 
00036 #define kOutputBus  0
00037 #define kInputBus   1
00038 
00039 //------------------------------------------------------------------------------
00040 
00041 // returns true on error
00042 //
00043 // see general error codes here:
00044 // http://www.opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/MacErrors.h
00045 //
00046 // error string conversion from:
00047 // http://stackoverflow.com/questions/2196869/how-do-you-convert-an-iphone-osstatus-code-to-something-useful
00048 //
00049 bool checkStatus(OSStatus error) {
00050     if(error != noErr) {
00051         char* str = new char[32];
00052         // see if it appears to be a 4-char-code
00053         *(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error);
00054         if(isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) {
00055             str[0] = str[5] = '\'';
00056             str[6] = '\0';
00057         } else {
00058             // no, format it as an integer
00059             sprintf(str, "%d", (int) error);
00060         }
00061         ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: OS status error code %s", str);
00062         return true;
00063     }
00064     return false;
00065 }
00066 
00067 //------------------------------------------------------------------------------
00068 
00069 // called when the audio system is interrupted (backgrounded, etc)
00070 static void rioInterruptionListener(void *inClientData, UInt32 inInterruption) {
00071     if(inInterruption == kAudioSessionBeginInterruption)
00072         ofLog(OF_LOG_VERBOSE, "ofxiPhoneSoundStream: Audio session interrupted");
00073     else if(inInterruption == kAudioSessionEndInterruption)
00074         ofLog(OF_LOG_VERBOSE, "ofxiPhoneSoundStream: Audio session resumed");
00075 }
00076 
00077 static OSStatus playbackCallback(void *inRefCon, 
00078                                  AudioUnitRenderActionFlags *ioActionFlags, 
00079                                  const AudioTimeStamp *inTimeStamp, 
00080                                  UInt32 inBusNumber, 
00081                                  UInt32 inNumberFrames, 
00082                                  AudioBufferList *ioData) {
00083     if(soundInputPtr == NULL)
00084         return noErr;
00085     
00086     for(int i = 0; i < ioData->mNumberBuffers; i++) {
00087         
00088         short int *buffer = (short int *)ioData->mBuffers[i].mData;
00089         
00090         // check to see if our buffer is big enough to store the data:
00091         if(ioData->mBuffers[i].mDataByteSize > MAX_BUFFER_SIZE*2) {
00092             int len = ioData->mBuffers[i].mDataByteSize/2;
00093             for(int j = 0; j < len; j++) {
00094                 buffer[j] = 0;
00095             }
00096         }
00097         else {
00098             // get floats from app
00099             soundOutputPtr->audioOut(scaleBuffer,
00100                 ioData->mBuffers[i].mDataByteSize/(ioData->mBuffers[i].mNumberChannels*2),
00101                 ioData->mBuffers[i].mNumberChannels);
00102             
00103             // truncate to 16bit fixed point data
00104             int len = ioData->mBuffers[i].mDataByteSize/2;
00105             for(int j = 0; j < len; j++) {
00106                 buffer[j] = (int) (scaleBuffer[j] * 32767.f);
00107             }
00108         }
00109     }
00110     
00111     return noErr;
00112 }
00113 
00114 static OSStatus recordingCallback(void *inRefCon, 
00115                                   AudioUnitRenderActionFlags *ioActionFlags, 
00116                                   const AudioTimeStamp *inTimeStamp, 
00117                                   UInt32 inBusNumber, 
00118                                   UInt32 inNumberFrames, 
00119                                   AudioBufferList *ioData) {
00120     
00121     // set input buffer params
00122     inputBufferList.mBuffers[0].mDataByteSize = 2*inNumberFrames*inputBufferList.mBuffers[0].mNumberChannels;
00123     ioData = &inputBufferList;
00124     
00125     // obtain recorded samples
00126     OSStatus status = AudioUnitRender(audioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData);
00127     if(checkStatus(status)) {
00128         ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't render input audio samples");
00129         return status;
00130     }
00131     
00132     // send data to app
00133     if(soundInputPtr != NULL) {
00134         for(int i = 0; i < ioData->mNumberBuffers; ++i) {
00135             short int *buffer = (short int *) ioData->mBuffers[i].mData;
00136             for(int j = 0; j < ioData->mBuffers[i].mDataByteSize/2; ++j) {
00137                 scaleBuffer[j] = (float) buffer[j] / 32767.f;   // convert each sample into a float
00138             }
00139             soundInputPtr->audioIn(scaleBuffer,
00140                 ioData->mBuffers[i].mDataByteSize/(ioData->mBuffers[i].mNumberChannels*2),
00141                 ioData->mBuffers[i].mNumberChannels);
00142         }
00143     }
00144     return noErr;
00145 }
00146 
00147 //------------------------------------------------------------------------------
00148 ofxiPhoneSoundStream::ofxiPhoneSoundStream(){
00149 }
00150 
00151 //------------------------------------------------------------------------------
00152 ofxiPhoneSoundStream::~ofxiPhoneSoundStream(){
00153 }
00154 
00155 //------------------------------------------------------------------------------
00156 void ofxiPhoneSoundStream::listDevices(){
00157 }
00158 
00159 //------------------------------------------------------------------------------
00160 void ofxiPhoneSoundStream::setDeviceID(int _deviceID){
00161 }
00162 
00163 //------------------------------------------------------------------------------
00164 void ofxiPhoneSoundStream::setInput(ofBaseSoundInput * soundInput){
00165     soundInputPtr = soundInput;
00166 }
00167 
00168 //------------------------------------------------------------------------------
00169 void ofxiPhoneSoundStream::setOutput(ofBaseSoundOutput * soundOutput){
00170     soundOutputPtr = soundOutput;
00171 }
00172 
00173 //------------------------------------------------------------------------------
00174 bool ofxiPhoneSoundStream::setup(int outChannels, int inChannels, int _sampleRate, int bufferSize, int nBuffers){
00175     
00176     nInputChannels = inChannels;
00177     nOutputChannels = outChannels;
00178     tickCount = 0;
00179     sampleRate = _sampleRate;
00180     
00181     // nBuffers is always 1  (see CoreAudio AudioBuffer struct)
00182     // this may change in the future ...
00183     nBuffers = 1;
00184     
00185     if(isRunning) {
00186         stop();
00187         close();
00188     }
00189 
00190     OSStatus status;
00191     
00192     // initialize and configure the audio session
00193     status = AudioSessionInitialize(NULL, NULL, rioInterruptionListener, NULL);
00194     if(checkStatus(status)) {
00195         ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't initialize audio session");
00196     }
00197     status = AudioSessionSetActive(true);
00198     if(checkStatus(status)) {
00199         ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't set audio session active");
00200     }
00201     
00202     Float32 preferredBufferSize = (float) bufferSize/sampleRate; 
00203     
00204     
00205     status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(preferredBufferSize), &preferredBufferSize);
00206     if(checkStatus(status)) {
00207         ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't set i/o buffer duration");
00208     }
00209     
00210     
00211     // describe audio component
00212     AudioComponentDescription desc;
00213     desc.componentType = kAudioUnitType_Output; // this is for output, input, or input-output
00214     desc.componentSubType = kAudioUnitSubType_RemoteIO;
00215     desc.componentFlags = 0;
00216     desc.componentFlagsMask = 0;
00217     desc.componentManufacturer = kAudioUnitManufacturer_Apple;
00218     
00219     // get component
00220     AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
00221     
00222     // get audio units
00223     status = AudioComponentInstanceNew(inputComponent, &audioUnit);
00224     if(checkStatus(status)) {
00225         ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't create audio unit");
00226     }
00227     
00228     // this is supposed to make the audio come out of the speaker rather
00229     // than the receiver, but I don't think it works when using the microphone as well.
00230     //  UInt32 category = kAudioSessionOverrideAudioRoute_Speaker;
00231     //  AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(category), &category);
00232     
00233     UInt32 category = 1;
00234     status = AudioSessionSetProperty(kAudioSessionOverrideAudioRoute_Speaker, sizeof(category), &category);
00235     if(checkStatus(status)) {
00236         ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't set ignore speaker routing");
00237     }
00238     
00239     // describe format
00240     audioFormat.mSampleRate         = (double)sampleRate;
00241     audioFormat.mFormatID           = kAudioFormatLinearPCM;
00242     audioFormat.mFormatFlags        = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
00243     audioFormat.mFramesPerPacket    = 1;
00244     audioFormat.mBitsPerChannel     = 16;
00245     
00246     AURenderCallbackStruct callbackStruct;
00247     UInt32 flag = 1;
00248     
00249     if(outChannels > 0) {
00250     
00251         // enable IO for playback
00252         status = AudioUnitSetProperty(audioUnit, 
00253                                       kAudioOutputUnitProperty_EnableIO, 
00254                                       kAudioUnitScope_Output, 
00255                                       kOutputBus,
00256                                       &flag, 
00257                                       sizeof(flag));
00258         if(checkStatus(status)) {
00259             ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't enable audio output");
00260         }
00261         
00262         // set output format
00263         audioFormat.mChannelsPerFrame   = outChannels;
00264         audioFormat.mBytesPerPacket     = outChannels*2;
00265         audioFormat.mBytesPerFrame      = outChannels*2;
00266         
00267         status = AudioUnitSetProperty(audioUnit, 
00268                                       kAudioUnitProperty_StreamFormat, 
00269                                       kAudioUnitScope_Input, 
00270                                       kOutputBus, 
00271                                       &audioFormat, 
00272                                       sizeof(audioFormat));
00273         if(checkStatus(status)) {
00274             ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't set output format");
00275         }
00276         
00277         // set output callback
00278         callbackStruct.inputProc = playbackCallback;
00279         callbackStruct.inputProcRefCon = NULL;
00280         status = AudioUnitSetProperty(audioUnit, 
00281                                       kAudioUnitProperty_SetRenderCallback, 
00282                                       kAudioUnitScope_Global, 
00283                                       kOutputBus,
00284                                       &callbackStruct, 
00285                                       sizeof(callbackStruct));
00286         if(checkStatus(status)) {
00287             ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't set output callback");
00288         }
00289     }
00290     
00291     if(inChannels > 0) {
00292         
00293         // enable IO for recording
00294         status = AudioUnitSetProperty(audioUnit, 
00295                                       kAudioOutputUnitProperty_EnableIO, 
00296                                       kAudioUnitScope_Input, 
00297                                       kInputBus,
00298                                       &flag, 
00299                                       sizeof(flag));
00300         if(checkStatus(status)) {
00301             ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't enable audio input");
00302         }
00303         
00304         audioFormat.mChannelsPerFrame   = inChannels;
00305         audioFormat.mBytesPerPacket     = inChannels*2;
00306         audioFormat.mBytesPerFrame      = inChannels*2;
00307         
00308         // set input format
00309         status = AudioUnitSetProperty(audioUnit, 
00310                                       kAudioUnitProperty_StreamFormat, 
00311                                       kAudioUnitScope_Output, 
00312                                       kInputBus, 
00313                                       &audioFormat, 
00314                                       sizeof(audioFormat));
00315         if(checkStatus(status)) {
00316             ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't enable set input format");
00317         }
00318         
00319         // setup input buffer
00320         inputBufferList.mNumberBuffers = nBuffers;
00321         for(int i = 0; i < inputBufferList.mNumberBuffers; ++i) {
00322             inputBufferList.mBuffers[i].mData = (short int*) malloc(MAX_BUFFER_SIZE*2*inChannels);
00323             inputBufferList.mBuffers[i].mDataByteSize = audioFormat.mBytesPerFrame;
00324             inputBufferList.mBuffers[i].mNumberChannels = inChannels;
00325         }
00326         
00327         // set input callback
00328         callbackStruct.inputProc = recordingCallback;
00329         callbackStruct.inputProcRefCon = NULL;
00330         status = AudioUnitSetProperty(audioUnit, 
00331                                       kAudioOutputUnitProperty_SetInputCallback, 
00332                                       kAudioUnitScope_Global, 
00333                                       kInputBus, 
00334                                       &callbackStruct, 
00335                                       sizeof(callbackStruct));
00336         if(checkStatus(status)) {
00337             ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't set input callback");
00338         }
00339     }
00340     
00341     UInt32 shouldAllocateBuffer = 1;
00342     AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
00343                          kAudioUnitScope_Global, 1, &shouldAllocateBuffer,
00344                          sizeof(shouldAllocateBuffer));
00345     
00346     // Initialise
00347     status = AudioUnitInitialize(audioUnit);
00348     if(checkStatus(status)) {
00349         ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't initialize audio unit");
00350     }
00351     
00352     isSetup = true;
00353     ofSoundStreamStart();
00354 }
00355 
00356 //------------------------------------------------------------------------------
00357 bool ofxiPhoneSoundStream::setup(ofBaseApp * app, int outChannels, int inChannels, int sampleRate, int bufferSize, int nBuffers){
00358     setInput(app);
00359     setOutput(app);
00360     setup(outChannels, inChannels, sampleRate, bufferSize, nBuffers);
00361 }
00362 
00363 //------------------------------------------------------------------------------
00364 void ofxiPhoneSoundStream::start(){
00365     if(isRunning)
00366         ofSoundStreamStop();
00367     if(audioUnit != NULL) {
00368         OSStatus status = AudioOutputUnitStart(audioUnit);
00369         checkStatus(status);
00370         isRunning = true;
00371     }
00372 }
00373 
00374 //------------------------------------------------------------------------------
00375 void ofxiPhoneSoundStream::stop(){
00376     if(isRunning) {
00377         OSStatus status = AudioOutputUnitStop(audioUnit);
00378         checkStatus(status);
00379     }
00380     isRunning = false;  
00381 }
00382 
00383 //------------------------------------------------------------------------------
00384 void ofxiPhoneSoundStream::close(){
00385     if(audioUnit != NULL)
00386         AudioUnitUninitialize(audioUnit);
00387     
00388     // clear input buffer
00389     for(int i = 0; i < inputBufferList.mNumberBuffers; ++i) {
00390         if(inputBufferList.mBuffers[i].mData != NULL)
00391             free(inputBufferList.mBuffers[i].mData);
00392     }
00393 }
00394 
00395 //------------------------------------------------------------------------------
00396 long unsigned long ofxiPhoneSoundStream::getTickCount(){
00397     return tickCount;
00398 }
00399 
00400 //------------------------------------------------------------------------------
00401 int ofxiPhoneSoundStream::getNumOutputChannels(){
00402     return nOutputChannels;
00403 }
00404 
00405 //------------------------------------------------------------------------------
00406 int ofxiPhoneSoundStream::getNumInputChannels(){
00407     return nInputChannels;
00408 }
00409 
00410 #endif
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Defines