#include "cmGlobal.h" #include "cmFloatTypes.h" #include "cmRpt.h" #include "cmErr.h" #include "cmCtx.h" #include "cmMem.h" #include "cmMallocDebug.h" #include "cmAudioFile.h" #include "cmThread.h" #include "cmTime.h" #include "cmAudioPort.h" #include "cmAudioFileDev.h" #include "cmTime.h" cmAfdH_t cmAfdNullHandle = cmSTATIC_NULL_HANDLE; #define cmAfd_Billion (1000000000) #define cmAfd_Million (1000000) typedef struct { cmErr_t err; // error object cmApCallbackPtr_t callbackPtr; // client callback function void* cbDataPtr; // argument to be passed with the client callback unsigned devIdx; cmChar_t* label; cmChar_t* oFn; unsigned oBits; unsigned oChCnt; cmAudioFileH_t iAfH; // audio input file handle cmAudioFileH_t oAfH; // audio output file handle cmThreadH_t tH; // thread handle double srate; // file device sample rate unsigned framesPerCycle; // count of samples sent/recv'd from the client on each callback cmApAudioPacket_t iPkt; // audio packet used sent to the client via callbackPtr. cmApAudioPacket_t oPkt; // cmApSample_t** iChArray; // audio buffer channel arrays used with cmAudioFile cmApSample_t** oChArray; // bool runFl; // set to true as long as the thread should continue looping bool rewindFl; // set to true when the input file should rewind unsigned readErrCnt; // count of read errors from the input file bool eofFl; // set to true when the input file reaches the EOF unsigned writeErrCnt; // count of write errors from the output file long nanosPerCycle; // nano-seconds per cycle struct timespec baseTime; struct timespec nextTime; // next execution time unsigned cycleCnt; // count of cycles completed } cmAfd_t; cmAfd_t* _cmAfdHandleToPtr( cmAfdH_t h ) { cmAfd_t* p = (cmAfd_t*)h.h; assert(p != NULL ); return p; } // void _cmAudioFileDevExec( cmAfd_t* p ) { unsigned iPktCnt = 0; unsigned oPktCnt = p->oPkt.chCnt!=0; // if the input device is enabled if( p->iPkt.chCnt ) { unsigned actualFrmCnt = p->framesPerCycle; // if the input file has reached EOF - zero the input buffer if( p->eofFl ) memset(p->iPkt.audioBytesPtr,0,p->framesPerCycle*sizeof(cmApSample_t)); else { // otherwise fill the input buffer from the input file if( cmAudioFileReadSample(p->iAfH, p->framesPerCycle, p->iPkt.begChIdx, p->iPkt.chCnt, p->iChArray, &actualFrmCnt) != kOkAfRC ) ++p->readErrCnt; // if the input file reachged EOF the set p->eofFl if( (actualFrmCnt < p->framesPerCycle) && cmAudioFileIsEOF(p->iAfH) ) p->eofFl = true; } iPktCnt = actualFrmCnt>0; } // callback to the client to provde incoming samples and receive outgoing samples p->callbackPtr(iPktCnt ? &p->iPkt : NULL, iPktCnt, oPktCnt ? &p->oPkt : NULL, oPktCnt ); // if the output device is enabled if( p->oPkt.chCnt ) { // write the output samples if( cmAudioFileWriteSample( p->oAfH, p->framesPerCycle, p->oPkt.chCnt, p->oChArray ) != kOkAfRC ) ++p->writeErrCnt; } ++p->cycleCnt; } // incrment p->nextTime to the next execution time void _cmAfdIncrNextTime( cmAfd_t* p ) { long nsec = p->nextTime.tv_nsec + p->nanosPerCycle; if( nsec < cmAfd_Billion ) p->nextTime.tv_nsec = nsec; else { p->nextTime.tv_sec += 1; p->nextTime.tv_nsec = nsec - cmAfd_Billion; } } // calc the time between t1 and t0 - t1 is assummed to come after t0 in order to produce a positive result long _cmAfdDiffMicros( const struct timespec* t0, const struct timespec* t1 ) { long u0 = t0->tv_sec * cmAfd_Million; long u1 = t1->tv_sec * cmAfd_Million; u0 += t0->tv_nsec / 1000; u1 += t1->tv_nsec / 1000; return u1 - u0; } // thread callback function bool _cmAudioDevThreadFunc(void* param) { cmAfd_t* p = (cmAfd_t*)param; struct timespec t0; // if this is the first time this callback has been called after a call to cmAudioFileDevStart(). if( p->cycleCnt == 0 ) { // get the baseTime - all other times will be relative to this time cmTimeGet(&p->baseTime); p->nextTime = p->baseTime; p->nextTime.tv_sec = 0; _cmAfdIncrNextTime(p); } // if the thread has not been requested to stop if( p->runFl ) { // get the current time as an offset from baseTime. cmTimeGet(&t0); t0.tv_sec -= p->baseTime.tv_sec; // get length of time to next exec point long dusec = _cmAfdDiffMicros(&t0, &p->nextTime); // if the execution time has not yet arrived if( dusec > 0 ) { cmSleepUs(dusec); } // if the thread is still running if( p->runFl ) { // read/callback/write _cmAudioFileDevExec(p); // calc the next exec time _cmAfdIncrNextTime(p); } } return p->runFl; } cmAfdRC_t cmAudioFileDevInitialize( cmAfdH_t* hp, const cmChar_t* label, unsigned devIdx, const cmChar_t* iFn, const cmChar_t* oFn, unsigned oBits, unsigned oChCnt, cmRpt_t* rpt ) { cmAfdRC_t rc; cmRC_t afRC; if((rc = cmAudioFileDevFinalize(hp)) != kOkAfdRC ) return rc; // allocate the object cmAfd_t* p = cmMemAllocZ(cmAfd_t,1); hp->h = p; cmErrSetup(&p->err,rpt,"AudioFileDevice"); // create the input audio file handle if( iFn != NULL ) { cmAudioFileInfo_t afInfo; // open the input file if(cmAudioFileIsValid( p->iAfH = cmAudioFileNewOpen(iFn,&afInfo,&afRC,rpt)) == false ) { rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"The audio input file '%s' could not be opened.", iFn); goto errLabel; } p->iPkt.devIdx = devIdx; p->iPkt.begChIdx = 0; p->iPkt.chCnt = afInfo.chCnt; // setting iPkt.chCnt to a non-zero value marks the input file as active p->iPkt.audioFramesCnt = 0; p->iPkt.bitsPerSample = afInfo.bits; p->iPkt.flags = kFloatApFl; p->iPkt.audioBytesPtr = NULL; p->iChArray = cmMemResizeZ( cmApSample_t*, p->iChArray, afInfo.chCnt ); p->readErrCnt = 0; p->eofFl = false; } // create the output audio file handle if(cmAudioFileIsValid( p->oAfH = cmAudioFileNewOpen(NULL,NULL,NULL,rpt)) == false ) { rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"The audio output file object allocation failed."); goto errLabel; } // create the driver thread if( cmThreadCreate(&p->tH, _cmAudioDevThreadFunc, p, rpt ) != kOkThRC ) { rc = cmErrMsg(&p->err,kThreadFailAfdRC,"The internal thread could not be created."); goto errLabel; } p->runFl = true; p->devIdx = devIdx; p->label = cmMemAllocStr(label); p->oFn = cmMemAllocStr(oFn); p->oBits = oBits; p->oChCnt = oChCnt; errLabel: if( rc != kOkAfdRC ) cmAudioFileDevFinalize(hp); return rc; } cmAfdRC_t cmAudioFileDevFinalize( cmAfdH_t* hp ) { if( hp == NULL || cmAudioFileDevIsValid(*hp) == false ) return kOkAfdRC; cmAfd_t* p = _cmAfdHandleToPtr(*hp); p->runFl = false; if( cmThreadIsValid(p->tH) ) cmThreadDestroy(&p->tH); cmAudioFileDelete(&p->iAfH); cmAudioFileDelete(&p->oAfH); cmMemPtrFree(&p->label); cmMemPtrFree(&p->oFn); cmMemPtrFree(&p->iPkt.audioBytesPtr); cmMemPtrFree(&p->oPkt.audioBytesPtr); cmMemPtrFree(&p->iChArray); cmMemPtrFree(&p->oChArray); cmMemPtrFree(&p); hp->h = NULL; return kOkAfdRC; } bool cmAudioFileDevIsValid( cmAfdH_t h ) { return h.h != NULL; } cmAfdRC_t cmAudioFileDevSetup( cmAfdH_t h, unsigned baseDevIdx, double srate, unsigned framesPerCycle, cmApCallbackPtr_t callbackPtr, void* cbDataPtr ) { cmAfdRC_t rc = kOkAfdRC; bool restartFl = false; unsigned i; if( cmAudioFileDevIsStarted(h) ) { if((rc = cmAudioFileDevStop(h)) != kOkAfdRC ) return rc; restartFl = true; } cmAfd_t* p = _cmAfdHandleToPtr(h); /* // close the existing input file if(cmAudioFileClose(&p->iAfH) != kOkAfRC ) rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file close failed on input audio file."); p->iPkt.chCnt = 0; // mark the input file as inactive */ if( cmAudioFileIsValid( p->iAfH ) ) if( cmAudioFileSeek( p->iAfH, 0 ) != kOkAfRC ) rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file device rewind failed."); // close the existing output file if(cmAudioFileClose(&p->oAfH) != kOkAfRC ) rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file close failed on output audio file."); p->oPkt.chCnt = 0; // mark the output file as inactive // if an output audio file was given ... if( p->oFn != NULL ) { // ... then open it if( cmAudioFileCreate( p->oAfH, p->oFn, srate, p->oBits, p->oChCnt ) != kOkAfRC ) { rc = cmErrMsg(&p->err,kAudioFileFailAfdRC, "The audio output file '%s' could not be created.",p->oFn); goto errLabel; } cmApSample_t* bp = (cmApSample_t*)p->oPkt.audioBytesPtr; p->oPkt.devIdx = p->devIdx + baseDevIdx; p->oPkt.begChIdx = 0; p->oPkt.chCnt = p->oChCnt; p->oPkt.audioFramesCnt = framesPerCycle; p->oPkt.bitsPerSample = p->oBits; p->oPkt.flags = kFloatApFl; p->oPkt.audioBytesPtr = bp = cmMemResizeZ( cmApSample_t, bp, framesPerCycle*p->oChCnt ); p->oPkt.userCbPtr = cbDataPtr; p->oChArray = cmMemResizeZ( cmApSample_t*, p->oChArray, p->oChCnt ); for(i=0; ioChCnt; ++i) p->oChArray[i] = bp + (i*framesPerCycle); } if( cmAudioFileIsValid( p->iAfH) ) { cmApSample_t* bp = (cmApSample_t*)p->iPkt.audioBytesPtr; p->iPkt.devIdx = p->devIdx + baseDevIdx; p->iPkt.audioFramesCnt = framesPerCycle; p->iPkt.audioBytesPtr = bp = cmMemResizeZ( cmApSample_t, bp, framesPerCycle*p->iPkt.chCnt ); ; p->iPkt.userCbPtr = cbDataPtr; for(i=0; iiPkt.chCnt; ++i) p->iChArray[i] = bp + (i*framesPerCycle); } p->callbackPtr = callbackPtr; p->cbDataPtr = cbDataPtr; p->framesPerCycle = framesPerCycle; p->srate = srate; p->cycleCnt = 0; p->nanosPerCycle = floor((double)framesPerCycle / srate * cmAfd_Billion ); if( restartFl ) { if((rc = cmAudioFileDevStart(h)) != kOkAfdRC ) { rc = cmErrMsg(&p->err,kRestartFailAfdRC,"The audio file device could not be restarted."); } } errLabel: return rc; } const char* cmAudioFileDevLabel( cmAfdH_t h ) { cmAfd_t* p = _cmAfdHandleToPtr(h); return p->label; } unsigned cmAudioFileDevChannelCount( cmAfdH_t h, bool inputFl ) { cmAfd_t* p = _cmAfdHandleToPtr(h); return inputFl ? p->iPkt.chCnt : p->oPkt.chCnt; } double cmAudioFileDevSampleRate( cmAfdH_t h ) { cmAfd_t* p = _cmAfdHandleToPtr(h); return p->srate; } unsigned cmAudioFileDevFramesPerCycle( cmAfdH_t h, bool inputFl ) { cmAfd_t* p = _cmAfdHandleToPtr(h); return inputFl ? p->iPkt.audioFramesCnt : p->oPkt.audioFramesCnt; } cmAfdRC_t cmAudioFileDevRewind( cmAfdH_t h ) { cmAfd_t* p = _cmAfdHandleToPtr(h); p->rewindFl = true; return kOkAfdRC; } cmAfdRC_t cmAudioFileDevStart( cmAfdH_t h ) { cmAfdRC_t rc = kOkAfdRC; cmAfd_t* p = _cmAfdHandleToPtr(h); p->cycleCnt = 0; if( cmThreadPause( p->tH, 0 ) != kOkThRC ) { rc = cmErrMsg(&p->err,kThreadFailAfdRC,"Thread start failed."); goto errLabel; } fputs("Start\n",stderr); errLabel: return rc; } cmAfdRC_t cmAudioFileDevStop( cmAfdH_t h ) { cmAfdRC_t rc = kOkAfdRC; cmAfd_t* p = _cmAfdHandleToPtr(h); if( cmThreadPause( p->tH, kPauseThFl | kWaitThFl ) != kOkThRC ) { rc = cmErrMsg(&p->err,kThreadFailAfdRC,"Thread stop failed."); goto errLabel; } fputs("Stop\n",stderr); errLabel: return rc; } bool cmAudioFileDevIsStarted( cmAfdH_t h ) { cmAfd_t* p = _cmAfdHandleToPtr(h); return cmThreadState(p->tH) == kRunningThId; } void cmAudioFileDevReport( cmAfdH_t h, cmRpt_t* rpt ) { cmAfd_t* p = _cmAfdHandleToPtr(h); cmRptPrintf(rpt,"label:%s thr state:%i srate:%f\n",p->label,cmThreadState(p->tH),p->srate); cmRptPrintf(rpt, "in chs:%i %s\n",p->iPkt.chCnt,cmAudioFileName(p->iAfH)); cmRptPrintf(rpt, "out chs:%i %s\n",p->oPkt.chCnt,p->oFn); } // device callback function used with cmAudioFileDevTest() note that this assumes // that the packet buffer contain non-interleaved data. void _cmAfdCallback( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) { cmApAudioPacket_t* ip = inPktArray; cmApAudioPacket_t* op = outPktArray; unsigned opi = 0; unsigned ipi = 0; unsigned oci = 0; unsigned ici = 0; while(1) { if( ici == ip->chCnt) { ici = 0; if( ++ipi >= inPktCnt ) break; ip = inPktArray + ipi; } if( oci == op->chCnt ) { oci = 0; if( ++opi >= outPktCnt ) break; ip = outPktArray + opi; } assert( ip->audioFramesCnt == op->audioFramesCnt ); assert( cmIsFlag(ip->flags,kInterleavedApFl)==false && cmIsFlag(ip->flags,kInterleavedApFl)==false ); cmApSample_t* ibp = ((cmApSample_t*)ip->audioBytesPtr) + (ip->audioFramesCnt*ici); cmApSample_t* obp = ((cmApSample_t*)op->audioBytesPtr) + (op->audioFramesCnt*oci); memcpy(obp,ibp,ip->audioFramesCnt*sizeof(cmApSample_t)); ++ici; ++oci; } } void cmAudioFileDevTest( cmRpt_t* rpt ) { cmAfdH_t afdH = cmAfdNullHandle; double srate = 44100; unsigned framesPerCycle = 512; void* cbDataPtr = NULL; unsigned devIdx = 0; const cmChar_t* iFn = "/home/kevin/media/audio/McGill-1/1 Audio Track.aiff"; const cmChar_t* oFn = "/home/kevin/temp/afd0.aif"; unsigned oBits = 16; unsigned oChCnt = 2; if( cmAudioFileDevInitialize(&afdH,"file",devIdx,iFn,oFn,oBits,oChCnt,rpt) != kOkAfdRC ) goto errLabel; if( cmAudioFileDevSetup(afdH,0,srate,framesPerCycle,_cmAfdCallback,cbDataPtr) != kOkAfdRC ) goto errLabel; char c; fputs("q=quit 1=start 0=stop\n",stderr); fflush(stderr); while((c=getchar()) != 'q') { switch(c) { case '1': cmAudioFileDevStart(afdH); break; case '0': cmAudioFileDevStop(afdH); break; } c = 0; fflush(stdin); } errLabel: cmAudioFileDevFinalize(&afdH); }