#include "cmPrefix.h" #include "cmGlobal.h" #include "cmRpt.h" #include "cmErr.h" #include "cmMem.h" #include "cmMallocDebug.h" #include "cmAudioPort.h" #include "cmApBuf.h" #include "cmThread.h" /* This API is in general called by two types of threads: audio devices threads and the client thread. There may be multiple devie threads however there is only one client thread. The audio device threads only call cmApBufUpdate(). cmApBufUpdate() is never called by any other threads. A call from the audio update threads targets specific channels (cmApCh records). The variables within each channels that it modifies are confined to: on input channels: increments ii and increments fn (data is entering the ch. buffers) on output channels: increments oi and decrements fn (data is leaving the ch. buffers) The client picks up incoming audio and provides outgoing audio via cmApBufGet(). It then informs the cmApBuf() that it has completed the audio data transfer by calling cmApBufAdvance(). cmApBufAdvance() modifies the following internal variables: on input channels: increments oi and decrements fn (data has left the ch buffer) on output channels: increments ii and increments fn (data has enterned the ch. buffer) Based on the above scenario the channel ii and oi variables are always thread-safe because they are only changed by a single thread. ii oi fn ------ ----- ---- input ch: audio client both output ch: client audio both The fn variable however is not thread-safe and therefore care must be taken as to how it is read and updated. */ enum { kInApIdx=0, kOutApIdx=1, kIoApCnt=2 }; typedef struct { unsigned fl; // kChApFl|kToneApFl|kMeterApFl ... cmApSample_t* b; // b[n] unsigned ii; // next in unsigned oi; // next out unsigned fn; // full cnt - count of samples currently in the buffer - incr'd by incoming, decr'd by outgoing unsigned phs; // tone phase double hz; // tone frequency double gain; // channel gain cmApSample_t* m; // m[mn] meter sample sum unsigned mn; // length of m[] unsigned mi; // next ele of m[] to rcv sum } cmApCh; typedef struct { unsigned chCnt; cmApCh* chArray; unsigned n; // length of b[] (multiple of dspFrameCnt) bufCnt*framesPerCycle double srate; // device sample rate; unsigned faultCnt; unsigned framesPerCycle; unsigned dspFrameCnt; } cmApIO; typedef struct { // ioArray[] always contains 2 elements - one for input the other for output. cmApIO ioArray[kIoApCnt]; } cmApDev; typedef struct { cmApDev* devArray; unsigned devCnt; unsigned meterMs; cmApSample_t* zeroBuf; // buffer of zeros unsigned zeroBufCnt; // max of all dspFrameCnt for all devices. } cmApBuf; cmApBuf _cmApBuf; cmApSample_t _cmApMeterValue( const cmApCh* cp ) { double sum = 0; unsigned i; for(i=0; imn; ++i) sum += cp->m[i]; return (cmApSample_t)sqrt(sum); } void _cmApSine( cmApCh* cp, cmApSample_t* b0, unsigned n0, cmApSample_t* b1, unsigned n1, unsigned stride, float srate ) { unsigned i; for(i=0; iphs) b0[i*stride] = (float)(cp->gain * sin( 2.0 * M_PI * cp->hz * cp->phs / srate )); for(i=0; iphs) b1[i*stride] = (float)(cp->gain * sin( 2.0 * M_PI * cp->hz * cp->phs / srate )); } cmApSample_t _cmApMeter( const cmApSample_t* b, unsigned bn, unsigned stride ) { const cmApSample_t* ep = b + bn; cmApSample_t sum = 0; for(; bb ); cmMemPtrFree( &chPtr->m ); } // n=buf sample cnt mn=meter buf smp cnt void _cmApChInitialize( cmApCh* chPtr, unsigned n, unsigned mn ) { _cmApChFinalize(chPtr); chPtr->b = n==0 ? NULL : cmMemAllocZ( cmApSample_t, n ); chPtr->ii = 0; chPtr->oi = 0; chPtr->fn = 0; chPtr->fl = (n!=0 ? kChApFl : 0); chPtr->hz = 1000; chPtr->gain = 1.0; chPtr->mn = mn; chPtr->m = cmMemAllocZ(cmApSample_t,mn); chPtr->mi = 0; } void _cmApIoFinalize( cmApIO* ioPtr ) { unsigned i; for(i=0; ichCnt; ++i) _cmApChFinalize( ioPtr->chArray + i ); cmMemPtrFree(&ioPtr->chArray); ioPtr->chCnt = 0; ioPtr->n = 0; } void _cmApIoInitialize( cmApIO* ioPtr, double srate, unsigned framesPerCycle, unsigned chCnt, unsigned n, unsigned meterBufN, unsigned dspFrameCnt ) { unsigned i; _cmApIoFinalize(ioPtr); n += (n % dspFrameCnt); // force buffer size to be a multiple of dspFrameCnt ioPtr->chArray = chCnt==0 ? NULL : cmMemAllocZ( cmApCh, chCnt ); ioPtr->chCnt = chCnt; ioPtr->n = n; ioPtr->faultCnt = 0; ioPtr->framesPerCycle = framesPerCycle; ioPtr->srate = srate; ioPtr->dspFrameCnt = dspFrameCnt; for(i=0; ichArray + i, n, meterBufN ); } void _cmApDevFinalize( cmApDev* dp ) { unsigned i; for(i=0; iioArray+i); } void _cmApDevInitialize( cmApDev* dp, double srate, unsigned iFpC, unsigned iChCnt, unsigned iBufN, unsigned oFpC, unsigned oChCnt, unsigned oBufN, unsigned meterBufN, unsigned dspFrameCnt ) { unsigned i; _cmApDevFinalize(dp); for(i=0; iioArray+i, srate, fpc, chCnt, bufN, meterBufN, dspFrameCnt ); } } cmAbRC_t cmApBufInitialize( unsigned devCnt, unsigned meterMs ) { cmAbRC_t rc; if((rc = cmApBufFinalize()) != kOkAbRC ) return rc; _cmApBuf.devArray = cmMemAllocZ( cmApDev, devCnt ); _cmApBuf.devCnt = devCnt; _cmApBuf.meterMs = meterMs; return kOkAbRC; } cmAbRC_t cmApBufFinalize() { unsigned i; for(i=0; i<_cmApBuf.devCnt; ++i) _cmApDevFinalize(_cmApBuf.devArray + i); cmMemPtrFree( &_cmApBuf.devArray ); cmMemPtrFree( &_cmApBuf.zeroBuf ); _cmApBuf.devCnt = 0; return kOkAbRC; } cmAbRC_t cmApBufSetup( unsigned devIdx, double srate, unsigned dspFrameCnt, unsigned bufCnt, unsigned inChCnt, unsigned inFramesPerCycle, unsigned outChCnt, unsigned outFramesPerCycle) { cmApDev* devPtr = _cmApBuf.devArray + devIdx; unsigned iBufN = bufCnt * inFramesPerCycle; unsigned oBufN = bufCnt * outFramesPerCycle; unsigned meterBufN = cmMax(1,floor(srate * _cmApBuf.meterMs / (1000.0 * outFramesPerCycle))); _cmApDevInitialize( devPtr, srate, inFramesPerCycle, inChCnt, iBufN, outFramesPerCycle, outChCnt, oBufN, meterBufN, dspFrameCnt ); if( inFramesPerCycle > _cmApBuf.zeroBufCnt || outFramesPerCycle > _cmApBuf.zeroBufCnt ) { _cmApBuf.zeroBufCnt = cmMax(inFramesPerCycle,outFramesPerCycle); _cmApBuf.zeroBuf = cmMemResizeZ(cmApSample_t,_cmApBuf.zeroBuf,_cmApBuf.zeroBufCnt); } return kOkAbRC; } cmAbRC_t cmApBufPrimeOutput( unsigned devIdx, unsigned audioCycleCnt ) { cmApIO* iop = _cmApBuf.devArray[devIdx].ioArray + kOutApIdx; unsigned i; for(i=0; ichCnt; ++i) { cmApCh* cp = iop->chArray + i; unsigned bn = iop->n * sizeof(cmApSample_t); memset(cp->b,0,bn); cp->oi = 0; cp->ii = iop->framesPerCycle * audioCycleCnt; cp->fn = iop->framesPerCycle * audioCycleCnt; } return kOkAbRC; } cmAbRC_t cmApBufUpdate( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) { unsigned i,j; // copy samples from the packet to the buffer if( inPktArray != NULL ) { for(i=0; idevIdx].ioArray + kInApIdx; // dest io recd // for each source packet channel and enabled dest channel for(j=0; jchCnt; ++j) { cmApCh* cp = ip->chArray + pp->begChIdx +j; // dest ch unsigned n0 = ip->n - cp->ii; // first dest segment unsigned n1 = 0; // second dest segment assert(pp->begChIdx + j < ip->chCnt ); // if the incoming samples would overflow the buffer then ignore them if( cp->fn + pp->audioFramesCnt > ip->n ) { ++ip->faultCnt; // record input overflow continue; } // if the incoming samples would go off the end of the buffer then // copy in the samples in two segments (one at the end and another at begin of dest channel) if( n0 < pp->audioFramesCnt ) n1 = pp->audioFramesCnt-n0; else n0 = pp->audioFramesCnt; bool enaFl = cmIsFlag(cp->fl,kChApFl) && cmIsFlag(cp->fl,kMuteApFl)==false; const cmApSample_t* sp = enaFl ? ((cmApSample_t*)pp->audioBytesPtr) + j : _cmApBuf.zeroBuf; unsigned ssn = enaFl ? pp->chCnt : 1; // stride (packet samples are interleaved) cmApSample_t* dp = cp->b + cp->ii; const cmApSample_t* ep = dp + n0; // update the meter if( cmIsFlag(cp->fl,kMeterApFl) ) { cp->m[cp->mi] = _cmApMeter(sp,pp->audioFramesCnt,pp->chCnt); cp->mi = (cp->mi + 1) % cp->mn; } // if the test tone is enabled on this input channel if( enaFl && cmIsFlag(cp->fl,kToneApFl) ) { _cmApSine(cp, dp, n0, cp->b, n1, 1, ip->srate ); } else // otherwise copy samples from the packet to the buffer { // copy the first segment for(; dp < ep; sp += ssn ) *dp++ = cp->gain * *sp; // if there is a second segment if( n1 > 0 ) { // copy the second segment dp = cp->b; ep = dp + n1; for(; dpgain * *sp; } } // advance the input channel buffer cp->ii = n1>0 ? n1 : cp->ii + n0; //cp->fn += pp->audioFramesCnt; cmThUIntIncr(&cp->fn,pp->audioFramesCnt); } } } // copy samples from the buffer to the packet if( outPktArray != NULL ) { for(i=0; idevIdx].ioArray + kOutApIdx; // dest io recd // for each dest packet channel and enabled source channel for(j=0; jchCnt; ++j) { cmApCh* cp = op->chArray + pp->begChIdx + j; // dest ch unsigned n0 = op->n - cp->oi; // first src segment unsigned n1 = 0; // second src segment volatile unsigned fn = cp->fn; // store fn because it may be changed by the client thread // if the outgoing samples will underflow the buffer if( pp->audioFramesCnt > fn ) { ++op->faultCnt; // record an output underflow // if the buffer is empty - zero the packet and return if( fn == 0 ) { memset( pp->audioBytesPtr, 0, pp->audioFramesCnt*sizeof(cmApSample_t)); continue; } // ... otherwise decrease the count of returned samples pp->audioFramesCnt = fn; } // if the outgong segments would go off the end of the buffer then // arrange to wrap to the begining of the buffer if( n0 < pp->audioFramesCnt ) n1 = pp->audioFramesCnt-n0; else n0 = pp->audioFramesCnt; cmApSample_t* dp = ((cmApSample_t*)pp->audioBytesPtr) + j; bool enaFl = cmIsFlag(cp->fl,kChApFl) && cmIsFlag(cp->fl,kMuteApFl)==false; // if the tone is enabled on this channel if( enaFl && cmIsFlag(cp->fl,kToneApFl) ) { _cmApSine(cp, dp, n0, dp + n0*pp->chCnt, n1, pp->chCnt, op->srate ); } else // otherwise copy samples from the output buffer to the packet { const cmApSample_t* sp = enaFl ? cp->b + cp->oi : _cmApBuf.zeroBuf; const cmApSample_t* ep = sp + n0; // copy the first segment for(; sp < ep; dp += pp->chCnt ) *dp = cp->gain * *sp++; // if there is a second segment if( n1 > 0 ) { // copy the second segment sp = enaFl ? cp->b : _cmApBuf.zeroBuf; ep = sp + n1; for(; spchCnt ) *dp = cp->gain * *sp++; } } // update the meter if( cmIsFlag(cp->fl,kMeterApFl) ) { cp->m[cp->mi] = _cmApMeter(((cmApSample_t*)pp->audioBytesPtr)+j,pp->audioFramesCnt,pp->chCnt); cp->mi = (cp->mi + 1) % cp->mn; } // advance the output channel buffer cp->oi = n1>0 ? n1 : cp->oi + n0; //cp->fn -= pp->audioFramesCnt; cmThUIntDecr(&cp->fn,pp->audioFramesCnt); } } } return kOkAbRC; } unsigned cmApBufMeterMs() { return _cmApBuf.meterMs; } unsigned cmApBufChannelCount( unsigned devIdx, unsigned flags ) { if( devIdx == cmInvalidIdx ) return 0; unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; return _cmApBuf.devArray[devIdx].ioArray[ idx ].chCnt; } void cmApBufSetFlag( unsigned devIdx, unsigned chIdx, unsigned flags ) { if( devIdx == cmInvalidIdx ) return; unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; bool enableFl = flags & kEnableApFl ? true : false; unsigned i = chIdx != -1 ? chIdx : 0; unsigned n = chIdx != -1 ? chIdx+1 : _cmApBuf.devArray[devIdx].ioArray[idx].chCnt; for(; ifl = cmEnaFlag(cp->fl, flags & (kChApFl|kToneApFl|kMeterApFl|kMuteApFl|kPassApFl), enableFl ); } } bool cmApBufIsFlag( unsigned devIdx, unsigned chIdx, unsigned flags ) { if( devIdx == cmInvalidIdx ) return false; unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; return cmIsFlag(_cmApBuf.devArray[devIdx].ioArray[idx].chArray[chIdx].fl,flags); } void cmApBufEnableChannel( unsigned devIdx, unsigned chIdx, unsigned flags ) { cmApBufSetFlag(devIdx,chIdx,flags | kChApFl); } bool cmApBufIsChannelEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) { return cmApBufIsFlag(devIdx, chIdx, flags | kChApFl); } void cmApBufEnableTone( unsigned devIdx, unsigned chIdx, unsigned flags ) { cmApBufSetFlag(devIdx,chIdx,flags | kToneApFl); } bool cmApBufIsToneEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) { return cmApBufIsFlag(devIdx,chIdx,flags | kToneApFl); } void cmApBufEnableMute( unsigned devIdx, unsigned chIdx, unsigned flags ) { cmApBufSetFlag(devIdx,chIdx,flags | kMuteApFl); } bool cmApBufIsMuteEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) { return cmApBufIsFlag(devIdx,chIdx,flags | kMuteApFl); } void cmApBufEnablePass( unsigned devIdx, unsigned chIdx, unsigned flags ) { cmApBufSetFlag(devIdx,chIdx,flags | kPassApFl); } bool cmApBufIsPassEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) { return cmApBufIsFlag(devIdx,chIdx,flags | kPassApFl); } void cmApBufEnableMeter( unsigned devIdx, unsigned chIdx, unsigned flags ) { cmApBufSetFlag(devIdx,chIdx,flags | kMeterApFl); } bool cmApBufIsMeterEnabled(unsigned devIdx, unsigned chIdx, unsigned flags ) { return cmApBufIsFlag(devIdx,chIdx,flags | kMeterApFl); } cmApSample_t cmApBufMeter(unsigned devIdx, unsigned chIdx, unsigned flags ) { if( devIdx == cmInvalidIdx ) return 0; unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; const cmApCh* cp = _cmApBuf.devArray[devIdx].ioArray[idx].chArray + chIdx; return _cmApMeterValue(cp); } void cmApBufSetGain( unsigned devIdx, unsigned chIdx, unsigned flags, double gain ) { if( devIdx == cmInvalidIdx ) return; unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; unsigned i = chIdx != -1 ? chIdx : 0; unsigned n = i + (chIdx != -1 ? 1 : _cmApBuf.devArray[devIdx].ioArray[idx].chCnt); for(; ichCnt, meterCnt ); unsigned i; if( faultCntPtr != NULL ) *faultCntPtr = iop->faultCnt; for(i=0; ichArray + i); return chCnt; } bool cmApBufIsDeviceReady( unsigned devIdx, unsigned flags ) { //bool iFl = true; //bool oFl = true; unsigned i = 0; if( devIdx == cmInvalidIdx ) return false; if( flags & kInApFl ) { const cmApIO* ioPtr = _cmApBuf.devArray[devIdx].ioArray + kInApIdx; for(i=0; ichCnt; ++i) if( ioPtr->chArray[i].fn < ioPtr->dspFrameCnt ) return false; //iFl = ioPtr->fn > ioPtr->dspFrameCnt; } if( flags & kOutApFl ) { const cmApIO* ioPtr = _cmApBuf.devArray[devIdx].ioArray + kOutApIdx; for(i=0; ichCnt; ++i) if( (ioPtr->n - ioPtr->chArray[i].fn) < ioPtr->dspFrameCnt ) return false; //oFl = (ioPtr->n - ioPtr->fn) > ioPtr->dspFrameCnt; } return true; //return iFl & oFl; } // Note that his function returns audio samples but does NOT // change any internal states. void cmApBufGet( unsigned devIdx, unsigned flags, cmApSample_t* bufArray[], unsigned bufChCnt ) { unsigned i; if( devIdx == cmInvalidIdx ) { for(i=0; ichCnt ? bufChCnt : ioPtr->chCnt; //unsigned offs = flags & kInApFl ? ioPtr->oi : ioPtr->ii; cmApCh* cp = ioPtr->chArray; for(i=0; ioi : cp->ii; bufArray[i] = cmIsFlag(cp->fl,kChApFl) ? cp->b + offs : NULL; } } void cmApBufGetIO( unsigned iDevIdx, cmApSample_t* iBufArray[], unsigned iBufChCnt, unsigned oDevIdx, cmApSample_t* oBufArray[], unsigned oBufChCnt ) { cmApBufGet( iDevIdx, kInApFl, iBufArray, iBufChCnt ); cmApBufGet( oDevIdx, kOutApFl,oBufArray, oBufChCnt ); unsigned i = 0; if( iDevIdx != cmInvalidIdx && oDevIdx != cmInvalidIdx ) { const cmApIO* ip = _cmApBuf.devArray[iDevIdx].ioArray + kInApIdx; const cmApIO* op = _cmApBuf.devArray[oDevIdx].ioArray + kOutApIdx; unsigned minChCnt = cmMin(iBufChCnt,oBufChCnt); unsigned frmCnt = cmMin(ip->dspFrameCnt,op->dspFrameCnt); unsigned byteCnt = frmCnt * sizeof(cmApSample_t); for(i=0; ichArray + i; cmApCh* icp = ip->chArray + i; if( oBufArray[i] != NULL ) { // if either the input or output channel is marked for pass-through if( cmAllFlags(ocp->fl,kPassApFl) || cmAllFlags(icp->fl,kPassApFl) ) { memcpy( oBufArray[i], iBufArray[i], byteCnt ); // set the output buffer to NULL to prevent it being over written by the client oBufArray[i] = NULL; } else { // zero the output buffer memset(oBufArray[i],0,byteCnt); } } } } if( oDevIdx != cmInvalidIdx ) { const cmApIO* op = _cmApBuf.devArray[oDevIdx].ioArray + kOutApIdx; unsigned byteCnt = op->dspFrameCnt * sizeof(cmApSample_t); for(; ichCnt; ++i) { cmApCh* cp = ioPtr->chArray + i; cp->oi = (cp->oi + ioPtr->dspFrameCnt) % ioPtr->n; //cp->fn -= ioPtr->dspFrameCnt; cmThUIntDecr(&cp->fn,ioPtr->dspFrameCnt); } //ioPtr->oi = (ioPtr->oi + ioPtr->dspFrameCnt) % ioPtr->n; //ioPtr->fn -= ioPtr->dspFrameCnt; } if( flags & kOutApFl ) { cmApIO* ioPtr = _cmApBuf.devArray[devIdx].ioArray + kOutApIdx; for(i=0; ichCnt; ++i) { cmApCh* cp = ioPtr->chArray + i; cp->ii = (cp->ii + ioPtr->dspFrameCnt) % ioPtr->n; //cp->fn += ioPtr->dspFrameCnt; cmThUIntIncr(&cp->fn,ioPtr->dspFrameCnt); } //ioPtr->ii = (ioPtr->ii + ioPtr->dspFrameCnt) % ioPtr->n; //ioPtr->fn += ioPtr->dspFrameCnt; } } void cmApBufInputToOutput( unsigned iDevIdx, unsigned oDevIdx ) { if( iDevIdx == cmInvalidIdx || oDevIdx == cmInvalidIdx ) return; unsigned iChCnt = cmApBufChannelCount( iDevIdx, kInApFl ); unsigned oChCnt = cmApBufChannelCount( oDevIdx, kOutApFl ); unsigned chCnt = iChCnt < oChCnt ? iChCnt : oChCnt; unsigned i; cmApSample_t* iBufPtrArray[ iChCnt ]; cmApSample_t* oBufPtrArray[ oChCnt ]; while( cmApBufIsDeviceReady( iDevIdx, kInApFl ) && cmApBufIsDeviceReady( oDevIdx, kOutApFl ) ) { cmApBufGet( iDevIdx, kInApFl, iBufPtrArray, iChCnt ); cmApBufGet( oDevIdx, kOutApFl, oBufPtrArray, oChCnt ); // Warning: buffer pointers to disabled channels will be set to NULL for(i=0; idspFrameCnt == op->dspFrameCnt ); unsigned byteCnt = ip->dspFrameCnt * sizeof(cmApSample_t); if( oBufPtrArray[i] != NULL ) { // the input channel is not disabled if( iBufPtrArray[i]!=NULL ) memcpy(oBufPtrArray[i],iBufPtrArray[i],byteCnt); else // the input channel is disabled but the output is not - so fill the output with zeros memset(oBufPtrArray[i],0,byteCnt); } } cmApBufAdvance( iDevIdx, kInApFl ); cmApBufAdvance( oDevIdx, kOutApFl ); } } void cmApBufReport( cmRpt_t* rpt ) { unsigned i,j,k; for(i=0; i<_cmApBuf.devCnt; ++i) { cmRptPrintf(rpt,"%i ",i); for(j=0; jchCnt; ++k) { cmApCh* cp = ip->chArray + i; ii += cp->ii; oi += cp->oi; fn += cp->fn; } cmRptPrintf(rpt,"%s - i:%7i o:%7i f:%7i n:%7i err %s:%7i ", j==0?"IN":"OUT", ii,oi,fn,ip->n, (j==0?"over":"under"), ip->faultCnt); } cmRptPrintf(rpt,"\n"); } } /// [cmApBufExample] void cmApBufTest( cmRpt_t* rpt ) { unsigned devIdx = 0; unsigned devCnt = 1 ; unsigned dspFrameCnt = 10; unsigned cycleCnt = 3; unsigned framesPerCycle = 25; unsigned inChCnt = 2; unsigned outChCnt = inChCnt; unsigned sigN = cycleCnt*framesPerCycle*inChCnt; double srate = 44100.0; unsigned meterMs = 50; unsigned bufChCnt= inChCnt; cmApSample_t* inBufArray[ bufChCnt ]; cmApSample_t* outBufArray[ bufChCnt ]; cmApSample_t iSig[ sigN ]; cmApSample_t oSig[ sigN ]; cmApSample_t* os = oSig; cmApAudioPacket_t pkt; int i,j; // create a simulated signal for(i=0; i