#include "cmGlobal.h" #include "cmRpt.h" #include "cmErr.h" #include "cmCtx.h" #include "cmMem.h" #include "cmMallocDebug.h" #include "cmTime.h" #include "cmAudioPort.h" #include "cmAudioPortFile.h" #include "cmAudioNrtDev.h" #include "cmAudioAggDev.h" #include "cmThread.h" // cmThUIntIncr() #include "cmApBuf.h" // only needed for cmApBufTest(). enum { kBufArrayCnt = 2 }; struct cmApAgg_str; typedef struct { unsigned physDevIdx; struct cmApAgg_str* ap; unsigned iChIdx; unsigned iChCnt; unsigned oChIdx; unsigned oChCnt; } cmApAggDev_t; typedef struct cmApAgg_str { cmChar_t* label; // agg. device label unsigned aggDevIdx; // agg. device index unsigned sysDevIdx; // system device index unsigned devCnt; // count of phys devices cmApAggDev_t* devArray; // devArray[ devCnt ] - physical device array unsigned iChCnt; // sum of phys device input channels unsigned oChCnt; // sum of phys device output channels double srate; // agg. dev sample rate unsigned framesPerCycle; // agg. dev frames per cycle unsigned flags; // kAgInFl | kAgOutFl cmApCallbackPtr_t cbFunc; // client supplied callback func void* cbArg; // client supplied callback func arg. bool startedFl; // true if the agg. device is started struct cmApAgg_str* link; // _cmAg.list link } cmApAgg_t; typedef struct { cmErr_t err; cmApAgg_t* list; } cmApAggMain_t; cmApAggMain_t _cmAg; void _cmApAggCb( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) { unsigned i; cmApAudioPacket_t pkt; for(i=0; iap->sysDevIdx; pkt.begChIdx = dp->iChIdx; pkt.userCbPtr = dp->ap->cbArg; dp->ap->cbFunc( &pkt, 1, NULL, 0 ); } for(i=0; iap->sysDevIdx; pkt.begChIdx = dp->oChIdx; pkt.userCbPtr = dp->ap->cbArg; dp->ap->cbFunc( NULL, 0, &pkt, 1 ); } } void _cmApAgDeleteAggDev( cmApAgg_t* ap ) { cmApAgg_t* cp = _cmAg.list; cmApAgg_t* pp = NULL; while( cp != NULL ) { if( cp == ap ) { if( pp == NULL ) _cmAg.list = cp->link; else pp->link = cp->link; cmMemFree(ap->label); cmMemFree(ap->devArray); cmMemFree(ap); return; } pp = cp; cp = cp->link; } } cmAgRC_t cmApAggAllocate( cmRpt_t* rpt ) { cmAgRC_t rc = kOkAgRC; cmErrSetup(&_cmAg.err,rpt,"cmAudioAggDev"); _cmAg.list = NULL; return rc; } cmAgRC_t cmApAggFree() { cmAgRC_t rc = kOkAgRC; while( _cmAg.list != NULL ) _cmApAgDeleteAggDev(_cmAg.list ); return rc; } cmAgRC_t cmApAggInitialize( cmRpt_t* rpt, unsigned baseApDevIdx ) { cmApAgg_t* ap = _cmAg.list; unsigned i; assert( baseApDevIdx == cmApDeviceCount() ); for(i=0; ap!=NULL; ap=ap->link,++i) { ap->sysDevIdx = cmApDeviceCount() + i; ap->iChCnt = 0; ap->oChCnt = 0; unsigned i; for(i=0; idevCnt; ++i) { ap->devArray[i].iChIdx = ap->iChCnt; ap->devArray[i].oChIdx = ap->oChCnt; ap->devArray[i].iChCnt = cmApDeviceChannelCount(ap->devArray[i].physDevIdx,true); ap->devArray[i].oChCnt = cmApDeviceChannelCount(ap->devArray[i].physDevIdx,false); ap->iChCnt += ap->devArray[i].iChCnt; ap->oChCnt += ap->devArray[i].oChCnt; } } return kOkAgRC; } cmAgRC_t cmApAggFinalize() { return kOkAgRC; } cmAgRC_t cmApAggCreateDevice( const cmChar_t* label, unsigned devCnt, const unsigned physDevIdxArray[], unsigned flags ) { cmAgRC_t rc = kOkAgRC; unsigned i; if( devCnt < 2 ) return cmErrMsg(&_cmAg.err,kMustAggTwoAgRC,"Cannot aggregate less than two devices."); /* for(i=0; ilabel = cmMemAllocStr(label==NULL?"Aggregated Device":label); ap->devArray = cmMemAllocZ(cmApAggDev_t,devCnt); ap->aggDevIdx = cmApAggDeviceCount(); ap->sysDevIdx = cmInvalidIdx; ap->devCnt = devCnt; ap->iChCnt = 0; ap->oChCnt = 0; for(i=0; idevArray[i].ap = ap; ap->devArray[i].physDevIdx = physDevIdxArray[i]; } ap->link = _cmAg.list; _cmAg.list = ap; return rc; } cmApAgg_t* _cmApAggDevIdxToPtr( unsigned aggDevIdx ) { cmApAgg_t* ap = _cmAg.list; unsigned i = 0; for(; ap!=NULL; ap=ap->link,++i) if( ap->aggDevIdx == aggDevIdx ) return ap; return NULL; } cmAgRC_t _cmApAggGetAgg( unsigned aggDevIdx, cmApAgg_t** retPtrPtr ) { if((*retPtrPtr = _cmApAggDevIdxToPtr(aggDevIdx)) == NULL ) return cmErrMsg(&_cmAg.err,kInvalidDevIdxAgRC,"The aggregate system device index '%i' is invalid."); return kOkAgRC; } bool cmApAggIsDeviceAggregated( unsigned physDevIdx ) { cmApAgg_t* ap = _cmAg.list; for(; ap!=NULL; ap=ap->link) { unsigned i; for(i=0; idevCnt; ++i) if( ap->devArray[i].physDevIdx == physDevIdx ) return true; } return false; } cmAgRC_t cmApAggDeviceCount() { unsigned devCnt=0; cmApAgg_t* ap = _cmAg.list; for(; ap!=NULL; ap=ap->link) ++devCnt; return devCnt; } const char* cmApAggDeviceLabel( unsigned aggDevIdx ) { cmApAgg_t* ap; cmAgRC_t rc; if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) == kOkAgRC ) return ap->label; return NULL; } unsigned cmApAggDeviceChannelCount( unsigned aggDevIdx, bool inputFl ) { cmApAgg_t* ap; cmAgRC_t rc; if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) == kOkAgRC ) return inputFl ? ap->iChCnt : ap->oChCnt; return 0; } double cmApAggDeviceSampleRate( unsigned aggDevIdx ) { cmApAgg_t* ap; cmAgRC_t rc; if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) == kOkAgRC ) return ap->srate; return 0; } unsigned cmApAggDeviceFramesPerCycle( unsigned aggDevIdx, bool inputFl ) { cmApAgg_t* ap; cmAgRC_t rc; if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) == kOkAgRC ) return ap->framesPerCycle; return 0; } cmAgRC_t cmApAggDeviceSetup( unsigned aggDevIdx, double srate, unsigned framesPerCycle, cmApCallbackPtr_t callbackPtr, void* userCbPtr ) { cmApAgg_t* ap; cmAgRC_t rc; unsigned i; if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) != kOkAgRC ) return rc; if((rc = cmApAggDeviceStop(aggDevIdx)) != kOkAgRC ) return rc; for(i=0; idevCnt; ++i) { unsigned physDevIdx = ap->devArray[i].physDevIdx; cmApAggDev_t* devPtr = ap->devArray + i; if( cmApDeviceSetup( physDevIdx, srate, framesPerCycle, _cmApAggCb, devPtr ) != kOkApRC ) rc = cmErrMsg(&_cmAg.err,kPhysDevSetupFailAgRC,"The physical device (index:%i '%s') setup failed for sample rate:%f frames-per-cycle:%i.",physDevIdx,cmStringNullGuard(cmApDeviceLabel(physDevIdx)),srate,framesPerCycle); } if( rc == kOkAgRC ) { ap->cbFunc = callbackPtr; ap->cbArg = userCbPtr; } return rc; } cmAgRC_t cmApAggDeviceStart( unsigned aggDevIdx ) { cmAgRC_t rc = kOkAgRC; cmApAgg_t* ap; unsigned i; if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) != kOkAgRC ) return rc; for(i=0; idevCnt; ++i) { unsigned physDevIdx = ap->devArray[i].physDevIdx; if( cmApDeviceStart( physDevIdx ) != kOkApRC ) return cmErrMsg(&_cmAg.err,kPhysDevStartFailAgRC,"The physical device (index:%i '%s') start failed.",physDevIdx,cmStringNullGuard(cmApDeviceLabel(physDevIdx))); } ap->startedFl = true; return rc; } cmAgRC_t cmApAggDeviceStop( unsigned aggDevIdx ) { cmAgRC_t rc = kOkAgRC; cmApAgg_t* ap; unsigned i; if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) != kOkAgRC ) return rc; for(i=0; idevCnt; ++i) { unsigned physDevIdx = ap->devArray[i].physDevIdx; if( cmApDeviceStop( physDevIdx ) != kOkApRC ) return cmErrMsg(&_cmAg.err,kPhysDevStartFailAgRC,"The physical device (index:%i '%s') start failed.",physDevIdx,cmStringNullGuard(cmApDeviceLabel(physDevIdx))); } ap->startedFl = false; return rc; } bool cmApAggDeviceIsStarted( unsigned aggDevIdx ) { cmApAgg_t* ap; if(_cmApAggGetAgg(aggDevIdx, &ap ) != kOkAgRC ) return false; return ap->startedFl; } typedef struct { unsigned bufCnt; // 2=double buffering 3=triple buffering unsigned chIdx; // first test channel unsigned chCnt; // count of channels to test unsigned framesPerCycle; // DSP frames per cycle unsigned bufFrmCnt; // count of DSP frames used by the audio buffer (bufCnt * framesPerCycle) unsigned bufSmpCnt; // count of samples used by the audio buffer (chCnt * bufFrmCnt) unsigned inDevIdx; // input device index unsigned outDevIdx; // output device index double srate; // audio sample rate unsigned meterMs; // audio meter buffer length // param's and state for cmApSynthSine() bool synthFl; unsigned phase; // sine synth phase double frqHz; // sine synth frequency in Hz // buffer and state for cmApCopyIn/Out() cmApSample_t* buf; // buf[bufSmpCnt] - circular interleaved audio buffer unsigned bufInIdx; // next input buffer index unsigned bufOutIdx; // next output buffer index unsigned bufFullCnt; // count of full samples unsigned cbCnt; // count the callback unsigned underunCnt; // unsigned overunCnt; double* iMeter; // iMeter[ chCnt ] FILE* ifp; FILE* ofp; } cmApAggPortTestRecd; // The application can request any block of channels from the device. The packets are provided with the starting // device channel and channel count. This function converts device channels and channel counts to buffer // channel indexes and counts. // // Example: // input output // i,n i n // App: 0,4 0 1 2 3 -> 2 2 // Pkt 2,8 2 3 4 5 6 7 8 -> 0 2 // // The return value is the count of application requested channels located in this packet. // // input: *appChIdxPtr and appChCnt describe a block of device channels requested by the application. // *pktChIdxPtr and pktChCnt describe a block of device channels provided to the application // // output:*appChIdxPtr and describe a block of app buffer channels which will send/recv samples. // *pktChIdxPtr and describe a block of pkt buffer channels which will send/recv samples // unsigned _cmApAggDeviceToBuffer( unsigned* appChIdxPtr, unsigned appChCnt, unsigned* pktChIdxPtr, unsigned pktChCnt ) { unsigned abi = *appChIdxPtr; unsigned aei = abi+appChCnt-1; unsigned pbi = *pktChIdxPtr; unsigned pei = pbi+pktChCnt-1; // if the ch's rqstd by the app do not overlap with this packet - return false. if( aei < pbi || abi > pei ) return 0; // if the ch's rqstd by the app overlap with the beginning of the pkt channel block if( abi < pbi ) { appChCnt -= pbi - abi; *appChIdxPtr = pbi - abi; *pktChIdxPtr = 0; } else { // the rqstd ch's begin inside the pkt channel block pktChCnt -= abi - pbi; *pktChIdxPtr = abi - pbi; *appChIdxPtr = 0; } // if the pkt channels extend beyond the rqstd ch block if( aei < pei ) pktChCnt -= pei - aei; else appChCnt -= aei - pei; // the rqstd ch's extend beyond or coincide with the pkt block // the returned channel count must always be the same for both the rqstd and pkt return cmMin(appChCnt,pktChCnt); } // synthesize a sine signal into an interleaved audio buffer unsigned _cmApAggSynthSine( cmApAggPortTestRecd* r, float* p, unsigned chIdx, unsigned chCnt, unsigned frmCnt, unsigned phs, double hz ) { long ph = 0; unsigned i; unsigned bufIdx = r->chIdx; unsigned bufChCnt; if( (bufChCnt = _cmApAggDeviceToBuffer( &bufIdx, r->chCnt, &chIdx, chCnt )) == 0) return phs; //if( r->cbCnt < 50 ) // printf("ch:%i cnt:%i ch:%i cnt:%i bi:%i bcn:%i\n",r->chIdx,r->chCnt,chIdx,chCnt,bufIdx,bufChCnt); for(i=bufIdx; israte )); } } return ph; } // Copy the audio samples in the interleaved audio buffer sp[srcChCnt*srcFrameCnt] // to the internal record buffer. void _cmApAggCopyIn( cmApAggPortTestRecd* r, const cmApSample_t* sp, unsigned srcChIdx, unsigned srcChCnt, unsigned srcFrameCnt ) { unsigned i,j; unsigned chCnt = cmMin(r->chCnt,srcChCnt); // write the incoming sample to an output file for debugging if( r->ifp != NULL ) if( fwrite(sp,sizeof(cmApSample_t),srcChCnt*srcFrameCnt,r->ifp) != srcChCnt*srcFrameCnt ) printf("file write fail.\n"); // zero the input meter array for(i=0; ichCnt; ++i) r->iMeter[i] = 0; for(i=0; ibuf[ r->bufInIdx + j ] = sp[ (i*srcChCnt) + srcChIdx + j ]; // record the max value in the input meter array if( r->buf[ r->bufInIdx + j ] > r->iMeter[j] ) r->iMeter[j] = r->buf[ r->bufInIdx + j ]; } // zero channels that are not used in the buffer for(; jchCnt; ++j) r->buf[ r->bufInIdx + j ] = 0; // advance to the next frame r->bufInIdx = (r->bufInIdx+r->chCnt) % r->bufFrmCnt; } //r->bufFullCnt = (r->bufFullCnt + srcFrameCnt) % r->bufFrmCnt; cmThUIntIncr(&r->bufFullCnt,srcFrameCnt); if( r->bufFullCnt > r->bufFrmCnt ) { //printf("Input buffer overrun.\n"); ++r->overunCnt; r->bufFullCnt = 0; } } // Copy audio samples out of the internal record buffer into dp[dstChCnt*dstFrameCnt]. void _cmApAggCopyOut( cmApAggPortTestRecd* r, cmApSample_t* dp, unsigned dstChIdx, unsigned dstChCnt, unsigned dstFrameCnt ) { // if there are not enough samples available to fill the destination // buffer then zero the dst buf. if( r->bufFullCnt < dstFrameCnt ) { //printf("Empty Output Buffer %i < %i\n",r->bufFullCnt,dstFrameCnt); memset( dp, 0, dstFrameCnt*dstChCnt*sizeof(cmApSample_t) ); ++r->underunCnt; } else { unsigned i,j; unsigned chCnt = cmMin(dstChCnt,r->chCnt); for(i=0; ibuf[ r->bufOutIdx + j ]; // zero unset channels in the dst buffer for(; jbufOutIdx = (r->bufOutIdx + r->chCnt) % r->bufFrmCnt; } cmThUIntDecr(&r->bufFullCnt,dstFrameCnt); } if( r->ofp != NULL ) fwrite(dp,sizeof(cmApSample_t),dstChCnt*dstFrameCnt,r->ofp); } // Audio port callback function called from the audio device thread. void _cmApAggPortCb( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) { unsigned i; // for each incoming audio packet for(i=0; isynthFl==false && inPktArray[i].devIdx == r->inDevIdx ) { // copy the incoming audio into an internal buffer where it can be picked up by _cpApCopyOut(). _cmApAggCopyIn( r, (cmApSample_t*)inPktArray[i].audioBytesPtr, inPktArray[i].begChIdx, inPktArray[i].chCnt, inPktArray[i].audioFramesCnt ); } ++r->cbCnt; //printf("i %4i in:%4i out:%4i\n",r->bufFullCnt,r->bufInIdx,r->bufOutIdx); } unsigned hold_phase = 0; // for each outgoing audio packet for(i=0; ioutDevIdx ) { // zero the output buffer memset(outPktArray[i].audioBytesPtr,0,outPktArray[i].chCnt * outPktArray[i].audioFramesCnt * sizeof(cmApSample_t) ); // if the synth is enabled if( r->synthFl ) { unsigned tmp_phase = _cmApAggSynthSine( r, outPktArray[i].audioBytesPtr, outPktArray[i].begChIdx, outPktArray[i].chCnt, outPktArray[i].audioFramesCnt, r->phase, r->frqHz ); // the phase will only change on packets that are actually used if( tmp_phase != r->phase ) hold_phase = tmp_phase; } else { // copy the any audio in the internal record buffer to the playback device _cmApAggCopyOut( r, (cmApSample_t*)outPktArray[i].audioBytesPtr, outPktArray[i].begChIdx, outPktArray[i].chCnt, outPktArray[i].audioFramesCnt ); } } r->phase = hold_phase; //printf("o %4i in:%4i out:%4i\n",r->bufFullCnt,r->bufInIdx,r->bufOutIdx); // count callbacks ++r->cbCnt; } } // print the usage message for cmAudioPortTest.c void _cmApAggPrintUsage( cmRpt_t* rpt ) { char msg[] = "cmApAggPortTest() command switches\n" "-r -c -b -f -i -o -t -p -h \n" "\n" "-r = sample rate\n" "-a = first channel\n" "-c = audio channels\n" "-b = count of buffers\n" "-f = count of samples per buffer\n" "-i = input device index\n" "-o = output device index\n" "-p = print report but do not start audio devices\n" "-h = print this usage message\n"; cmRptPrintf(rpt,msg); } // Get a command line option. int _cmApAggGetOpt( int argc, const char* argv[], const char* label, int defaultVal, bool boolFl ) { int i = 0; for(; icbCnt += 1; } if( outPktCnt ) { cmApAggPortTestRecd* r = (cmApAggPortTestRecd*)outPktArray[0].userCbPtr; r->cbCnt += 1; } cmApBufInputToOutput( _cmAggGlobalInDevIdx, _cmAggGlobalOutDevIdx ); cmApBufUpdate( inPktArray, inPktCnt, outPktArray, outPktCnt ); } //void recdPrint(); // Audio Port testing function int cmApAggTest( bool runFl, cmCtx_t* ctx, int argc, const char* argv[] ) { cmApAggPortTestRecd r; unsigned i; cmRpt_t* rpt = &ctx->rpt; int srateMult = 1; if( _cmApAggGetOpt(argc,argv,"-h",0,true) ) _cmApAggPrintUsage(rpt); runFl = _cmApAggGetOpt(argc,argv,"-p",!runFl,true)?false:true; r.chIdx = _cmApAggGetOpt(argc,argv,"-a",0,false); r.chCnt = _cmApAggGetOpt(argc,argv,"-c",2,false); r.bufCnt = _cmApAggGetOpt(argc,argv,"-b",3,false); r.framesPerCycle = _cmApAggGetOpt(argc,argv,"-f",512,false); r.bufFrmCnt = (r.bufCnt*r.framesPerCycle); r.bufSmpCnt = (r.chCnt * r.bufFrmCnt); r.synthFl = false; r.meterMs = 50; cmApSample_t buf[r.bufSmpCnt]; double imeter[r.chCnt]; r.iMeter = imeter; r.inDevIdx = _cmAggGlobalInDevIdx = _cmApAggGetOpt(argc,argv,"-i",0,false); r.outDevIdx = _cmAggGlobalOutDevIdx = _cmApAggGetOpt(argc,argv,"-o",2,false); r.phase = 0; r.frqHz = 2000; r.srate = 96000; r.bufInIdx = 0; r.bufOutIdx = 0; r.bufFullCnt = 0; r.buf = buf; r.cbCnt = 0; r.underunCnt = 0; r.overunCnt = 0; r.ifp = NULL; r.ofp = NULL; if(0) { if((r.ifp = fopen("/home/kevin/temp/itemp0.bin","wb")) == NULL ) cmRptPrintf(rpt,"File open failed.\n"); if((r.ofp = fopen("/home/kevin/temp/otemp0.bin","wb")) == NULL ) cmRptPrintf(rpt,"File open failed.\n"); } cmRptPrintf(rpt,"%s in:%i out:%i chidx:%i chs:%i bufs=%i frm=%i rate=%f\n",runFl?"exec":"rpt",r.inDevIdx,r.outDevIdx,r.chIdx,r.chCnt,r.bufCnt,r.framesPerCycle,r.srate); // allocate the aggregate device system if( cmApAggAllocate(rpt) != kOkAgRC ) { cmRptPrintf(rpt,"The aggregate device system allocation failed.\n"); return 1; } // allocate the audio file device system if( cmApFileAllocate( rpt ) != kOkApRC ) { cmRptPrintf(rpt,"The audio file device system allocation failed.\n"); goto doneLabel; } // allocate the NRT device system if( cmApNrtAllocate(rpt) != kOkApRC ) { cmRptPrintf(rpt,"The NRT audio device system allocation failed.\n"); goto doneLabel; } unsigned physDevIdxArray[] = { 2, 4 }; unsigned physDevCnt = sizeof(physDevIdxArray)/sizeof(physDevIdxArray[0]); if( cmApAggCreateDevice("aggdev",physDevCnt,physDevIdxArray,kInAggFl | kOutAggFl) != kOkAgRC ) { cmRptPrintf(rpt,"The aggregate device creation failed.n"); goto doneLabel; } // initialize the audio device interface if( cmApInitialize(rpt) != kOkApRC ) { cmRptPrintf(rpt,"Initialize failed.\n"); goto doneLabel; } // report the current audio device configuration for(i=0; i