// // http://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/CACoreAudioReference // #include #include "cmPrefix.h" #include "cmGlobal.h" #include "cmRpt.h" #include "cmAudioPort.h" #include "cmMem.h" #include "cmMallocDebug.h" #include "cmAudioPortOsx.h" #include #include // usleap typedef struct { unsigned chIdx; unsigned chCnt; unsigned framesPerBuf; } cmApOsxBufRecd; typedef struct { AudioDeviceID devId; AudioDeviceIOProcID ioProcId; unsigned devIdx; cmApCallbackPtr_t callbackPtr; char* mfgStr; char* nameStr; double srate; unsigned inFramesPerCycle; unsigned inChCnt; unsigned inPktCnt; cmApAudioPacket_t* inPktArray; unsigned outFramesPerCycle; unsigned outChCnt; unsigned outPktCnt; cmApAudioPacket_t* outPktArray; unsigned timeOutMs; AudioDeviceID fmtChangeDevId; AudioStreamID fmtChangeStreamId; } cmApOsxDevRecd; typedef struct { cmRpt_t* rpt; cmApOsxDevRecd* devArray; unsigned devCnt; } cmApOsxRoot; cmApOsxRoot _cmApOsxRoot = { NULL, NULL, 0 }; const char* _cmApOsxOsStatusToText( OSStatus errCode ) { switch( errCode ) { case kAudioHardwareNoError: return "No Error"; case kAudioHardwareNotRunningError: return "Not runing error"; case kAudioHardwareUnspecifiedError: return "Unspecified error"; case kAudioHardwareUnknownPropertyError: return "Unknown property error"; case kAudioHardwareBadPropertySizeError: return "Bad property error"; case kAudioHardwareIllegalOperationError: return "Illegal operation error"; case kAudioHardwareBadDeviceError: return "Bad device error"; case kAudioHardwareBadStreamError: return "Bad stream error"; case kAudioHardwareUnsupportedOperationError: return "Unsupported operating error"; case kAudioDeviceUnsupportedFormatError: return "Unsupported format error"; case kAudioDevicePermissionsError: return "Permissions error"; } return "Unknown error code"; } cmApRC_t _cmApOsxOsErrorRpt( OSStatus err, cmApOsxRoot* r, const char* func, const char* file, int line ) { if( r->rpt != NULL ) { if( err != noErr ) cmRptErrorf(r->rpt,"Audio Port Error %s in %s line:%i %s\n",_cmApOsxOsStatusToText(err),func,line,file); else cmRptErrorf(r->rpt,"Audio Port Error: Unknown\n"); } return kSysErrApRC; } #define _cmApOsxOsError( err, root ) _cmApOsxOsErrorRpt( err, root, __FUNCTION__, __FILE__, __LINE__ ) OSStatus _cmApOsxAllocDeviceCFString( AudioDeviceID devId, UInt32 ch, Boolean inputFl, AudioDevicePropertyID devPropId, char** strPtrPtr ) { CFStringRef cfStr; //Boolean outWritableFl = false; UInt32 outByteCnt = sizeof(cfStr);; OSStatus err = noErr; if((err = AudioDeviceGetProperty( devId, ch, inputFl, devPropId, &outByteCnt, &cfStr )) != noErr ) return err; CFIndex cfLen = CFStringGetLength(cfStr) * 2; char* cStr = cmMemAllocZ( char, cfLen+1 ); cStr[0] = 0; if( CFStringGetCString( cfStr, cStr, cfLen, kCFStringEncodingUTF8 ) ) cStr[cfLen]=0; CFRelease(cfStr); *strPtrPtr = cStr; return noErr; } // Note: the streamIdArray* allocated by this function must be released by the calling function. OSStatus _cmApOsxAllocStreamIdArray( AudioDeviceID devId, Boolean inputFl, AudioStreamID** streamIdArrayPtr, unsigned* streamIdCntPtr ) { UInt32 byteCnt = 0; Boolean canWriteFl = false; OSStatus err = noErr; *streamIdArrayPtr = NULL; *streamIdCntPtr = 0; // get the length of the stream id array if((err = AudioDeviceGetPropertyInfo( devId, 0, inputFl, kAudioDevicePropertyStreams, &byteCnt, &canWriteFl )) != noErr ) return err; if( byteCnt <= 0 ) goto doneLabel; // get the count of streams *streamIdCntPtr = byteCnt / sizeof(AudioStreamID); // allocate the stream id array *streamIdArrayPtr = cmMemAllocZ( AudioStreamID, *streamIdCntPtr ); // verify that the size of the stream id array is an integer multiple of the sizeof(AudioStreamID) assert( *streamIdCntPtr * sizeof(AudioStreamID) == byteCnt ); // fill the stream id array if((err = AudioDeviceGetProperty( devId, 0, inputFl, kAudioDevicePropertyStreams, &byteCnt, *streamIdArrayPtr )) != noErr ) return err; doneLabel: return noErr; } OSStatus cmApOsxSystemStreamPropertyListenerProc( AudioStreamID inStream, UInt32 inChannel, AudioDevicePropertyID inPropertyID, void* inClientData) { cmApOsxDevRecd* drp = (cmApOsxDevRecd*)inClientData; drp->fmtChangeDevId = drp->devId; drp->fmtChangeStreamId = inStream; return noErr; } OSStatus _cmApOsxSetSampleRate( cmApOsxDevRecd* drp, bool inputFl, double newSampleRate ) { AudioStreamID* streamIdArray = NULL; unsigned streamCnt; OSStatus err = noErr; unsigned waitMs = 0; unsigned i; // allocate a stream id array if((err = _cmApOsxAllocStreamIdArray( drp->devId, inputFl, &streamIdArray, &streamCnt )) != noErr ) return _cmApOsxOsError(err,&_cmApOsxRoot); // for each stream on this device for(i=0; idevId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &canWriteFl )) != noErr ) { _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } assert( byteCnt == sizeof(sdr) ); // get the stream desc recd if((err = AudioDeviceGetProperty( drp->devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &sdr )) != noErr ) { _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } // if the format has not already been set if( sdr.mSampleRate != newSampleRate ) { // change the sample rate sdr.mSampleRate = newSampleRate; drp->fmtChangeDevId = -1; drp->fmtChangeStreamId = -1; // attempt to change the sample rate if((err = AudioStreamSetProperty(streamIdArray[i], NULL, 0, kAudioDevicePropertyStreamFormat, sizeof(sdr), &sdr)) != noErr ) { err = _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } // wait for confirmation waitMs = 0; while( drp->fmtChangeDevId != drp->devId && drp->fmtChangeStreamId != streamIdArray[i] && waitMs < drp->timeOutMs ) { const unsigned waitIncrMs = 20; usleep(waitIncrMs*1000); waitMs += waitIncrMs; } // wait timed out if( waitMs >= drp->timeOutMs ) { err = _cmApOsxOsError(kAudioHardwareUnspecifiedError,&_cmApOsxRoot); goto errLabel; } else { // read back the format to be really sure it changed if((err = AudioDeviceGetProperty( drp->devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &sdr )) != noErr ) { _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } assert( sdr.mSampleRate == newSampleRate ); } } } errLabel: cmMemPtrFree(&streamIdArray); return err; } OSStatus _cmApOsxSetFramesPerCycle( cmApOsxDevRecd* drp, Boolean inputFl, unsigned newFramesPerCycle ) { OSStatus err = noErr; AudioTimeStamp when; UInt32 ch = 0; UInt32 devPropId = kAudioDevicePropertyBufferFrameSize; UInt32 outByteCnt = sizeof(UInt32); UInt32 curFramesPerCycle = 0; // set time stamp to zero to force the change to be immediate when.mHostTime = 0; when.mFlags = kAudioTimeStampHostTimeValid; // get the cur value off the param. to change if((err = AudioDeviceGetProperty( drp->devId, ch, inputFl, devPropId, &outByteCnt, &curFramesPerCycle )) != noErr ) return _cmApOsxOsError(err,&_cmApOsxRoot); // if the cur value is the same as the new value then there is nothing to do if( curFramesPerCycle == newFramesPerCycle ) return noErr; // attempt to set the new value if((err = AudioDeviceSetProperty( drp->devId, &when, ch, inputFl, devPropId, sizeof(newFramesPerCycle), &newFramesPerCycle)) != noErr ) return _cmApOsxOsError(err,&_cmApOsxRoot); // wait for the value to actually change unsigned waitMs = 0; while( waitMs < drp->timeOutMs ) { const unsigned waitIncrMs = 20; usleep(waitIncrMs*1000); // read the parameter value if((err = AudioDeviceGetProperty( drp->devId, ch, inputFl, devPropId, &outByteCnt, &curFramesPerCycle )) != noErr ) return _cmApOsxOsError(err,&_cmApOsxRoot); // if the parameter value equals the new value then the change has taken effect if( curFramesPerCycle == newFramesPerCycle ) break; waitMs += waitIncrMs; } // wait timed out if( waitMs >= drp->timeOutMs ) return _cmApOsxOsError(kAudioHardwareUnspecifiedError,&_cmApOsxRoot); return noErr; } // Note: *bufArrayPtr must be deallocated by the caller. cmApRC_t _cmApOsxGetBufferCfg(unsigned devIdx, AudioDeviceID devId, Boolean inputFl, cmApOsxDevRecd* drp ) { cmApRC_t rc = kOkApRC; OSStatus err = noErr; UInt32 byteCnt = 0; Boolean canWriteFl = 0; AudioBufferList* ablp = NULL; unsigned streamCnt = 0; AudioStreamID* streamIdArray = NULL; unsigned i = 0; unsigned chIdx = 0; // get the size of stream cfg buffer if( (err = AudioDeviceGetPropertyInfo(devId,0,inputFl,kAudioDevicePropertyStreamConfiguration,&byteCnt,&canWriteFl)) != noErr ) return _cmApOsxOsError(err,&_cmApOsxRoot); // allocate memory to hold the AudioBufferList ablp = (AudioBufferList*)cmMemMallocZ(byteCnt); // get the audio buffer list array if((err = AudioDeviceGetProperty(devId,0,inputFl,kAudioDevicePropertyStreamConfiguration,&byteCnt,ablp)) != noErr ) { rc = _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } // allocate a stream id array if((err = _cmApOsxAllocStreamIdArray( devId, inputFl, &streamIdArray, &streamCnt )) != noErr ) { rc = _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } // the number of buffers and the number of frames must be the same assert( streamCnt == ablp->mNumberBuffers); cmApAudioPacket_t* pktArray = cmMemAllocZ(cmApAudioPacket_t,ablp->mNumberBuffers); if( inputFl ) { drp->inPktCnt = ablp->mNumberBuffers; drp->inPktArray = pktArray; } else { drp->outPktCnt = ablp->mNumberBuffers; drp->outPktArray = pktArray; } for(i=0; imNumberBuffers; ++i) { AudioStreamBasicDescription sdr; // get the size of the stream desc recd if((err = AudioDeviceGetPropertyInfo( devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &canWriteFl )) != noErr ) { _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } assert( byteCnt == sizeof(sdr) ); // get the stream desc recd if((err = AudioDeviceGetProperty( devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &sdr )) != noErr ) { _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } // assert that the data format is packed float32 native endian //assert( IsAudioFormatNativeEndian(sdr) ); // 0x6c70636d = lpcm //printf("%s %i dev:%i sr:%f fmtId:0x%lx fmtFl:0x%lx bytesPerPkt:%li frmsPerPkt:%li bytesPerFrm:%li chsPerFrm:%li bitsPerCh:%li\n", // inputFl?"i":"o",i,devIdx,sdr.mSampleRate,sdr.mFormatID,sdr.mFormatFlags,sdr.mBytesPerPacket,sdr.mFramesPerPacket,sdr.mBytesPerFrame,sdr.mChannelsPerFrame,sdr.mBitsPerChannel); // assert that all buffers have the sample rate of the device if( drp->srate == 0 ) drp->srate = sdr.mSampleRate; assert( drp->srate == sdr.mSampleRate ); // install a stream property listener AudioStreamAddPropertyListener( streamIdArray[i], kAudioPropertyWildcardChannel, kAudioPropertyWildcardPropertyID, cmApOsxSystemStreamPropertyListenerProc, drp ); pktArray[i].devIdx = devIdx; pktArray[i].begChIdx = chIdx; pktArray[i].chCnt = ablp->mBuffers[i].mNumberChannels; //sdr.mChannelsPerFrame; pktArray[i].audioFramesCnt = ablp->mBuffers[i].mDataByteSize / (sizeof(cmApSample_t) * ablp->mBuffers[i].mNumberChannels); //sdr.mFramesPerPacket; pktArray[i].flags = kInterleavedApFl | kFloatApFl; pktArray[i].bitsPerSample = sizeof(cmApSample_t) * 8; pktArray[i].audioBytesPtr = NULL; pktArray[i].userCbPtr = NULL; // the userCbPtr value isn't availabe until the cmApOsxDeviceSetup() // verify that all buffers on this device have the same size assert(i==0 || pktArray[i].audioFramesCnt == pktArray[i-1].audioFramesCnt ); chIdx += ablp->mBuffers[i].mNumberChannels; //sdr.mChannelsPerFrame; if( inputFl ) { // track the total number of input channels on this device drp->inChCnt = chIdx; // track the frames per cycle on this device if( drp->inFramesPerCycle == 0 ) drp->inFramesPerCycle = pktArray[i].audioFramesCnt; assert( drp->inFramesPerCycle == pktArray[i].audioFramesCnt ); } else { drp->outChCnt = chIdx; if( drp->outFramesPerCycle == 0 ) drp->outFramesPerCycle = pktArray[i].audioFramesCnt; assert( drp->outFramesPerCycle == pktArray[i].audioFramesCnt ); } } errLabel: cmMemPtrFree(&streamIdArray); cmMemPtrFree(&ablp); return rc; } OSStatus _cmApOsxSystemDeviceIOProc( AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* iabl, const AudioTimeStamp* inInputTime, AudioBufferList* oabl, const AudioTimeStamp* inOutputTime, void* inClientData) { cmApOsxDevRecd* drp = (cmApOsxDevRecd*)inClientData; if( iabl->mNumberBuffers!=0 && iabl->mNumberBuffers != drp->inPktCnt ) return noErr; if( oabl->mNumberBuffers!=0 && oabl->mNumberBuffers != drp->outPktCnt ) return noErr; //assert( iabl->mNumberBuffers==0 || iabl->mNumberBuffers == drp->inPktCnt ); //assert( oabl->mNumberBuffers==0 || oabl->mNumberBuffers == drp->outPktCnt ); // setup the incoming packets (ADC->app) AudioBuffer* bp = iabl->mBuffers; AudioBuffer* ep = bp + iabl->mNumberBuffers; cmApAudioPacket_t* pp = drp->inPktArray; unsigned chIdx = 0; for(; bpaudioBytesPtr = (float*)bp->mData; pp->audioFramesCnt = bp->mDataByteSize / (bp->mNumberChannels * sizeof(cmApSample_t)); pp->begChIdx = chIdx; pp->chCnt = bp->mNumberChannels; chIdx += pp->chCnt; } // setup the outgoing packets (app->DAC) bp = oabl->mBuffers; ep = bp + oabl->mNumberBuffers; pp = drp->outPktArray; for(chIdx=0; bpaudioBytesPtr = (float*)bp->mData; pp->audioFramesCnt = bp->mDataByteSize / (bp->mNumberChannels * sizeof(cmApSample_t)); pp->begChIdx = chIdx; pp->chCnt = bp->mNumberChannels; chIdx += pp->chCnt; } // call the app drp->callbackPtr( iabl->mNumberBuffers ? drp->inPktArray : NULL, iabl->mNumberBuffers, oabl->mNumberBuffers ? drp->outPktArray : NULL, oabl->mNumberBuffers ); return noErr; } cmApRC_t cmApOsxInitialize( cmRpt_t* rpt ) { cmApRC_t rc = kOkApRC; Boolean outWritableFl = false; UInt32 outByteCnt = 0; OSStatus err = noErr; AudioDeviceID* devIdArray = NULL; unsigned i = 0; cmApOsxDevRecd* devArray = NULL; if((rc = cmApOsxFinalize()) != kOkApRC ) return rc; _cmApOsxRoot.rpt = rpt; _cmApOsxRoot.devArray = NULL; // get the size of the device id array if((err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &outByteCnt, &outWritableFl )) != noErr ) return _cmApOsxOsError(err,&_cmApOsxRoot); assert( outByteCnt > 0 ); // calc. the device count _cmApOsxRoot.devCnt = outByteCnt / sizeof(AudioDeviceID); assert( _cmApOsxRoot.devCnt*sizeof(AudioDeviceID) == outByteCnt ); // allocate space for the device id array and the device array devIdArray = cmMemAllocZ( AudioDeviceID, _cmApOsxRoot.devCnt ); devArray = cmMemAllocZ( cmApOsxDevRecd, _cmApOsxRoot.devCnt ); _cmApOsxRoot.devArray = devArray; // get the device id array if((err = AudioHardwareGetProperty( kAudioHardwarePropertyDevices, &outByteCnt, devIdArray )) != noErr ) { rc = _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } // for each for(i=0; i<_cmApOsxRoot.devCnt; ++i) { // device name if((err = _cmApOsxAllocDeviceCFString(devIdArray[i], 0, false, kAudioDevicePropertyDeviceNameCFString, &devArray[i].nameStr )) != noErr ) { rc = _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } // device mfg if((err = _cmApOsxAllocDeviceCFString(devIdArray[i], 0, false, kAudioDevicePropertyDeviceManufacturerCFString, &devArray[i].mfgStr )) != noErr ) { rc = _cmApOsxOsError(err,&_cmApOsxRoot); goto errLabel; } // in buf array if((rc = _cmApOsxGetBufferCfg(i,devIdArray[i], true, devArray + i )) != kOkApRC ) goto errLabel; // out buf array if((rc = _cmApOsxGetBufferCfg(i,devIdArray[i], false, devArray + i)) != kOkApRC ) goto errLabel; devArray[i].devId = devIdArray[i]; devArray[i].devIdx = i; devArray[i].callbackPtr = NULL; devArray[i].timeOutMs = 1000; devArray[i].ioProcId = NULL; } errLabel: if( devIdArray != NULL ) cmMemFree(devIdArray); if( rc == kOkApRC ) return rc; cmApOsxFinalize(); return rc; } cmApRC_t cmApOsxFinalize() { cmApOsxDevRecd* devArray = _cmApOsxRoot.devArray; unsigned i = 0; OSStatus err; for(i=0; i<_cmApOsxRoot.devCnt; ++i) { unsigned j; for(j=0; j<2; ++j) { AudioStreamID* streamIdArray; unsigned streamCnt; unsigned k; if((err = _cmApOsxAllocStreamIdArray(devArray[i].devId, true,&streamIdArray,&streamCnt )) != noErr ) _cmApOsxOsError(err,&_cmApOsxRoot); for(k=0; ksrate != srate ) { for(j=0; j<2; ++j ) if((err = _cmApOsxSetSampleRate(drp, j==0, srate )) != noErr ) goto errLabel; drp->srate = srate; } // set the frames per cycle for(j=0; j<2; ++j) { unsigned* fpcPtr = j==0 ? &drp->inFramesPerCycle : &drp->outFramesPerCycle; if( framesPerCycle != (*fpcPtr) ) { if((err = _cmApOsxSetFramesPerCycle(drp, j==0, framesPerCycle )) == noErr ) *fpcPtr = framesPerCycle; else goto errLabel; } } // set the user callback data ptr in each packet on this device for(j=0; j<2; ++j) { unsigned k; cmApAudioPacket_t* packetArray = j==0 ? drp->inPktArray : drp->outPktArray; unsigned pktCnt = j==0 ? drp->inPktCnt : drp->outPktCnt; for(k=0; kcallbackPtr = callbackPtr; // if the io if( drp->ioProcId != NULL ) if((err = AudioDeviceDestroyIOProcID( drp->devId, drp->ioProcId )) != noErr ) _cmApOsxOsError(err,&_cmApOsxRoot); // set the io proc drp->ioProcId = NULL; //if( (err = AudioDeviceAddIOProc(drp->devId,_cmApOsxSystemDeviceIOProc,(void*)drp) ) != noErr ) if( (err = AudioDeviceCreateIOProcID(drp->devId,_cmApOsxSystemDeviceIOProc,(void*)drp,&drp->ioProcId) ) != noErr ) { _cmApOsxOsError(err,&_cmApOsxRoot); return kSysErrApRC; } return kOkApRC; errLabel: return kSysErrApRC; } cmApRC_t cmApOsxDeviceStart( unsigned devIdx ) { assert( devIdx < _cmApOsxRoot.devCnt ); OSStatus err; //if( (err = AudioDeviceStart(_cmApOsxRoot.devArray[devIdx].devId,_cmApOsxSystemDeviceIOProc)) != noErr ) if( (err = AudioDeviceStart(_cmApOsxRoot.devArray[devIdx].devId,_cmApOsxRoot.devArray[devIdx].ioProcId)) != noErr ) { _cmApOsxOsError(err,&_cmApOsxRoot); return kSysErrApRC; } return kOkApRC; } cmApRC_t cmApOsxDeviceStop( unsigned devIdx ) { assert( devIdx < _cmApOsxRoot.devCnt ); OSStatus err; //if( (err = AudioDeviceStop(_cmApOsxRoot.devArray[devIdx].devId,_cmApOsxSystemDeviceIOProc)) != noErr ) if( (err = AudioDeviceStop(_cmApOsxRoot.devArray[devIdx].devId,_cmApOsxRoot.devArray[devIdx].ioProcId)) != noErr ) { _cmApOsxOsError(err,&_cmApOsxRoot); return kSysErrApRC; } return kOkApRC; } bool cmApOsxDeviceIsStarted( unsigned devIdx ) { assert( devIdx < _cmApOsxRoot.devCnt ); return false; } /* void apReport( apPrintFunc_t printFunc ) { unsigned i; for(i=0; i<_cmApOsxRoot.devCnt; ++i) { cmApOsxDevRecd* drp = _cmApOsxRoot.devArray + i; printf("%i in:%i out:%i %s %s\n",i,drp->inChCnt,drp->outChCnt,drp->nameStr,drp->mfgStr); } } */