//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwTest.h" #include "cwMem.h" #include "cwText.h" #include "cwFile.h" #include "cwThread.h" #include "cwMutex.h" #include "cwObject.h" #include "cwTime.h" #include "cwAudioFile.h" #include "cwAudioDeviceDecls.h" #include "cwAudioDevice.h" #include "cwAudioDeviceFile.h" #include "cwVectOps.h" namespace cw { namespace audio { namespace device { namespace file { enum { kInFl = 0x01, kOutFl = 0x02 }; typedef struct cache_block_str { sample_t* buf; sample_t* ebuf; unsigned frameN; // allocated frames unsigned frameIdx; // next frame to fill struct cache_block_str* link; } cache_block_t; typedef struct dev_str { char* label; std::atomic_uint readyCnt; unsigned framesPerCycle; double srate; cbFunc_t cbFunc; void* cbArg; unsigned cbDevIdx; bool isStartedFl; char* iFname; audiofile::handle_t iFileH; unsigned iFlags; unsigned iChCnt; bool iEnableFl; audioPacket_t iPkt; sample_t* iPktAudioBuf; unsigned iCbCnt; sample_t** iChArray; // iChArray[ iChCnt ] sample_t* iChSmpBuf; // unsigned iErrCnt; // count of errors unsigned iFrmCnt; // count of frames read sample_t* iCacheBuf; // iCacheFrameBuf[ iCacheFrameN * iChCnt ] (interleaved) unsigned iCacheFrameN; // count of frames in iCacheBuf unsigned iCacheIdx; // next frame to read (always at channel 0) char* oFname; audiofile::handle_t oFileH; unsigned oFlags; unsigned oChCnt; bool oEnableFl; unsigned oBitsPerSample; audioPacket_t oPkt; sample_t* oPktAudioBuf; unsigned oCbCnt; sample_t** oChArray; // oChArray[2] sample_t* oChSmpBuf; // unsigned oErrCnt; // count of errors unsigned oFrmCnt; // count of frames written cache_block_t* oCacheBeg; // Output cache output linked list cache_block_t* oCacheEnd; // Last output cache output node. unsigned oCacheBlockSec; // output cache block size in seconds unsigned oCacheBlockFrameN; // output cache block size in frames struct dev_str* link; } dev_t; typedef struct dev_mgr_str { driver_t driver; dev_t* list; unsigned threadTimeOutMs; unsigned threadCbCnt; thread::handle_t threadH; mutex::handle_t mutexH; } dev_mgr_t; dev_mgr_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } dev_mgr_t* _driverToPtr( driver_t* drvr ) { return (dev_mgr_t*)drvr->drvArg; } bool _is_thread_active( dev_mgr_t* p ) { return p->threadTimeOutMs > 0; } dev_t* _labelToDev( dev_mgr_t* p, const char* label ) { dev_t* d = p->list; for(; d!=nullptr; d=d->link) if( textCompare(d->label,label) == 0 ) return d; return nullptr; } dev_t* _indexToDev( dev_mgr_t* p, unsigned devIdx ) { unsigned i = 0; dev_t* d = p->list; for(; d!=nullptr; d=d->link) { if( i == devIdx ) return d; ++i; } return nullptr; } rc_t _indexToDev( dev_mgr_t* p, unsigned devIdx, dev_t*& devPtrRef ) { rc_t rc = kOkRC; if((devPtrRef = _indexToDev(p,devIdx)) == nullptr ) rc = cwLogError(kInvalidArgRC,"The audio file device index %i is invalid.",devIdx); return rc; } dev_t* _findOrCreateDev( dev_mgr_t* p, const char* label ) { dev_t* d; if((d = _labelToDev(p,label)) != nullptr ) return d; d = mem::allocZ(); d->label = mem::duplStr(label); if(p->list == nullptr) p->list = d; else { // set d0 to the last dev on the list dev_t* d0 = p->list; while( d0->link != nullptr ) d0 = d0->link; d0->link = d; } return d; } void _devReport( dev_t* d ) { cwLogInfo("%s : FpC:%i sr:%f",cwStringNullGuard(d->label),d->framesPerCycle,d->srate); if( d->iFileH.isValid() ) cwLogInfo(" in: flags:0x%x ch:%i sr:%f : %s", d->iFlags,channelCount(d->iFileH),sampleRate(d->iFileH),cwStringNullGuard(d->iFname)); if( d->oFileH.isValid() ) cwLogInfo(" out: flags:0x%x ch:%i sr:%f : %s", d->oFlags,d->oChCnt,d->srate,cwStringNullGuard(d->oFname)); } void _close_input( dev_t* d ) { if( d->iFileH.isValid() ) { close(d->iFileH); d->iCbCnt = 0; mem::release(d->iFname); mem::release(d->iPktAudioBuf); mem::release(d->iChArray); mem::release(d->iChSmpBuf); mem::release(d->iCacheBuf); } } rc_t _fill_input_packet_from_cache( dev_t* d ) { rc_t rc = kOkRC; unsigned n = d->framesPerCycle; unsigned m = d->framesPerCycle; // set n to the count of frames to copy from the cache into the pkt if( d->iCacheIdx + n > d->iCacheFrameN ) n = d->iCacheFrameN - d->iCacheIdx; if( n > 0 ) { memcpy( d->iPktAudioBuf, d->iCacheBuf + (d->iCacheIdx * d->iChCnt), n * d->iChCnt * sizeof(sample_t)); d->iCacheIdx += n; // set m to the count of frames to zero at the end of the pkt m = d->framesPerCycle - n; } if( m > 0 ) { memset( d->iPktAudioBuf + (n*d->iChCnt), 0, m * d->iChCnt * sizeof(sample_t)); d->iCacheIdx = d->iCacheFrameN; } return rc; } rc_t _fill_input_packet_from_file( dev_t* d ) { rc_t rc = kOkRC; unsigned actualFrameCnt = 0; // read the file if((rc = readFloat(d->iFileH, d->framesPerCycle, 0, d->iChCnt, d->iChArray, &actualFrameCnt)) != kOkRC ) { rc = cwLogError(rc,"File read failed on audio device file: %s.",cwStringNullGuard(d->label)); d->iErrCnt += 1; goto errLabel; } // interleave into the iPkt audio buffer vop::interleave( d->iPktAudioBuf, d->iChSmpBuf, actualFrameCnt, d->iChCnt ); if( actualFrameCnt < d->framesPerCycle ) { for(unsigned i=0; iiChCnt; ++i) vop::fill( d->iPktAudioBuf + (actualFrameCnt * d->iChCnt) + i, 0, d->framesPerCycle-actualFrameCnt, d->iChCnt ); } // d->iPktAudioBuf[] now contains d->framesPerCycle*d->iChCnt samples errLabel: return rc; } rc_t _cache_input( dev_t* d, unsigned frameN ) { rc_t rc = kOkRC; unsigned n = 0; d->iCacheFrameN = frameN; d->iCacheBuf = mem::resize(d->iCacheBuf, d->iCacheFrameN * d->iChCnt); d->iCacheIdx = 0; while(n < d->iCacheFrameN ) { if((rc = _fill_input_packet_from_file(d)) != kOkRC ) { rc = cwLogError(rc,"Read failed while reading the input cache."); d->iCacheFrameN = 0; // this will prevent the cache from being used goto errLabel; } unsigned copyFrameN = d->framesPerCycle; if( n + copyFrameN > d->iCacheFrameN ) copyFrameN = d->iCacheFrameN - n; memcpy( d->iCacheBuf + n * d->iChCnt, d->iPktAudioBuf, copyFrameN * d->iChCnt * sizeof(sample_t)); n += copyFrameN; } if( n % d->iChCnt != 0 ) { cwLogWarning("The cache buffer seems to have a size mismatch with the input audio file."); } errLabel: return rc; } rc_t _open_input( dev_t* d, unsigned devIdx ) { rc_t rc = kOkRC; audiofile::handle_t iFileH; audiofile::info_t info; if( d->iFname == nullptr ) return rc; // open the requested audio flie if((rc = open( iFileH, d->iFname, &info )) != kOkRC ) { rc = cwLogError(rc,"Audio file device open failed on '%s'.",cwStringNullGuard(d->iFname)); goto errLabel; } // if the device input file is already open - then close it if( d->iFileH.isValid() ) { cwLogWarning("The audio file device '%s' input file '%s' has been closed prior to opening '%s'.",name(d->iFileH),cwStringNullGuard(d->iFname)); _close_input(d); } if( info.srate != d->srate ) cwLogWarning("The audio file sample rate (%f) does not match device sample rate (%f).",info.srate,d->srate); d->iFileH = iFileH; d->iPktAudioBuf = mem::resize(d->iPktAudioBuf,d->framesPerCycle*info.chCnt); d->iChArray = mem::resize(d->iChArray,info.chCnt); d->iChSmpBuf = mem::resize(d->iChSmpBuf, d->framesPerCycle*info.chCnt ); d->iChCnt = info.chCnt; d->iEnableFl = true; d->iErrCnt = 0; d->iFrmCnt = 0; d->iPkt.devIdx = d->cbDevIdx; d->iPkt.begChIdx = 0; d->iPkt.chCnt = info.chCnt; d->iPkt.audioFramesCnt = d->framesPerCycle; d->iPkt.bitsPerSample = 0; d->iPkt.flags = kInterleavedApFl | kFloatApFl; d->iPkt.audioBytesPtr = d->iPktAudioBuf; d->iPkt.cbArg = d->cbArg; for(unsigned i=0; iiChCnt; ++i) d->iChArray[i] = d->iChSmpBuf + i*d->framesPerCycle; // if input caching was requested if( cwIsFlag(d->iFlags,kCacheFl) ) if((rc = _cache_input(d,info.frameCnt)) != kOkRC ) cwLogError(rc,"Cache load failed on '%s'.",cwStringNullGuard(d->iFname)); errLabel: return rc; } rc_t _read_input( dev_t* d ) { rc_t rc = kOkRC; if( !d->iEnableFl ) { memset(d->iPkt.audioBytesPtr,0,d->iChCnt*d->framesPerCycle*sizeof(sample_t)); } else { if( d->iCacheFrameN ) rc = _fill_input_packet_from_cache(d); else rc = _fill_input_packet_from_file(d); if( rc == kOkRC ) d->iFrmCnt += d->framesPerCycle; } return rc; } rc_t _write_cache_to_file( dev_t* d ) { rc_t rc = kOkRC; for(cache_block_t* b = d->oCacheBeg; b!=nullptr; b=b->link) if((rc = writeFloatInterleaved( d->oFileH, b->frameIdx, d->oChCnt, b->buf )) != kOkRC ) rc = cwLogError(rc,"Audio device file cache write failed on device '%s'.",d->label); return rc; } void _close_output( dev_t* d ) { if( d->oFileH.isValid() ) { _write_cache_to_file(d); close(d->oFileH); d->oCbCnt = 0; mem::release(d->oFname); mem::release(d->oPktAudioBuf); mem::release(d->oChArray); mem::release(d->oChSmpBuf); for(cache_block_t* b=d->oCacheBeg; b!=nullptr; ) { cache_block_t* b0 = b->link; mem::release(b); b = b0; } } } rc_t _alloc_ocache_block( dev_t* d ) { unsigned bufByteN = sizeof(cache_block_t) + d->oCacheBlockFrameN * d->oChCnt * sizeof(sample_t); uint8_t* bytes = mem::allocZ( bufByteN ); cache_block_t* block = (cache_block_t*)bytes; block->buf = (sample_t*)(block + 1); block->ebuf = (sample_t*)(bytes + bufByteN); block->frameN = d->oCacheBlockFrameN; block->frameIdx = 0; block->link = nullptr; if( d->oCacheEnd != nullptr ) d->oCacheEnd->link = block; d->oCacheEnd = block; if( d->oCacheBeg == nullptr ) d->oCacheBeg = block; return kOkRC; } rc_t _open_output( dev_t* d, unsigned devIdx ) { rc_t rc = kOkRC; audiofile::handle_t oFileH; unsigned floatBitsPerSample = 0; // set bits per sample to 0 to indicate a floating point file if( d->oFname == nullptr ) return rc; // open the requested audio output flie if((rc = create( oFileH, d->oFname, d->srate, floatBitsPerSample, d->oChCnt )) != kOkRC ) { rc = cwLogError(rc,"Audio file device open failed on '%s'.",cwStringNullGuard(d->oFname)); goto errLabel; } d->oFileH = oFileH; d->oPktAudioBuf = mem::resize(d->oPktAudioBuf,d->framesPerCycle*d->oChCnt); d->oChArray = mem::resize(d->oChArray,d->oChCnt); d->oChSmpBuf = mem::resize(d->oChSmpBuf, d->framesPerCycle*d->oChCnt ); d->oEnableFl = true; d->oFrmCnt = 0; d->oErrCnt = 0; d->oPkt.devIdx = d->cbDevIdx; d->oPkt.begChIdx = 0; d->oPkt.chCnt = d->oChCnt; d->oPkt.audioFramesCnt = d->framesPerCycle; d->oPkt.bitsPerSample = 0; // 0==floating point sample format d->oPkt.flags = kInterleavedApFl | kFloatApFl; d->oPkt.audioBytesPtr = d->oPktAudioBuf; d->oPkt.cbArg = d->cbArg; d->oCacheBlockFrameN = 0; if( cwIsFlag(d->oFlags,kCacheFl) ) { // note each cache block has a whole multiple of framesPerCycle d->oCacheBlockFrameN = ((unsigned)lround((d->oCacheBlockSec * d->srate) + d->framesPerCycle)/d->framesPerCycle) * d->framesPerCycle; assert( d->oCacheBlockFrameN >= d->framesPerCycle && d->oCacheBlockFrameN % d->framesPerCycle == 0 ); _alloc_ocache_block(d); } for(unsigned i=0; ioChCnt; ++i) d->oChArray[i] = d->oChSmpBuf + i*d->framesPerCycle; errLabel: return rc; } rc_t _write_cache_output( dev_t* d ) { rc_t rc = kOkRC; //sample_t s; assert( d->oCacheEnd != nullptr ); if( d->oCacheEnd->frameIdx + d->framesPerCycle > d->oCacheEnd->frameN ) if((rc = _alloc_ocache_block(d)) != kOkRC ) goto errLabel; assert( d->oCacheEnd->frameIdx + d->framesPerCycle <= d->oCacheEnd->frameN ); memcpy(d->oCacheEnd->buf + d->oCacheEnd->frameIdx * d->oChCnt, d->oPktAudioBuf, d->framesPerCycle * d->oChCnt * sizeof(sample_t)); d->oCacheEnd->frameIdx += d->framesPerCycle; //s = vop::sum(d->oPktAudioBuf,d->framesPerCycle * d->oChCnt); //printf("w:%i %i %f\n",d->oCacheEnd->frameIdx,d->framesPerCycle * d->oChCnt,s); errLabel: return rc; } rc_t _write_file_output( dev_t* d ) { rc_t rc; if((rc = writeFloatInterleaved( d->oFileH, d->framesPerCycle, d->oChCnt, d->oPktAudioBuf )) != kOkRC ) { rc = cwLogError(rc,"Audio device file write failed on device '%s'.",d->label); d->oErrCnt += 1; } return rc; } rc_t _write_output( dev_t* d ) { rc_t rc = kOkRC; if( d->oFileH.isValid() && d->oEnableFl ) { if( cwIsFlag(d->oFlags,kCacheFl) ) rc = _write_cache_output(d); else rc = _write_file_output(d); if( rc != kOkRC ) d->oFrmCnt += d->framesPerCycle; } return rc; } rc_t _update_device( dev_t* d, bool inThreadFl ) { rc_t rc0 = kOkRC; rc_t rc1 = kOkRC; audioPacket_t* iPkt = nullptr; unsigned iPktN = 0; audioPacket_t* oPkt = nullptr; unsigned oPktN = 0; if( d->iFileH.isValid() && inThreadFl == cwIsFlag(d->iFlags,kUseInternalClockFl) ) { iPkt = &d->iPkt; iPktN = 1; d->iCbCnt += 1; rc0 = _read_input( d ); } if( d->oFileH.isValid() && inThreadFl == cwIsFlag(d->iFlags,kUseInternalClockFl) ) { oPkt = &d->oPkt; oPktN = 1; d->oCbCnt += 1; vop::zero( d->oPktAudioBuf, d->oChCnt * d->framesPerCycle ); } d->cbFunc( d->cbArg, iPkt, iPktN, oPkt, oPktN ); if( d->oFileH.isValid() ) rc1 = _write_output( d ); return rcSelect(rc0,rc1); } bool _threadCbFunc( void* arg ) { rc_t rc = kOkRC; dev_mgr_t* p = (dev_mgr_t*)arg; dev_t* d = nullptr; // block on the cond. var - unlock the mutex if((rc = mutex::waitOnCondVar(p->mutexH,false,p->threadTimeOutMs)) != kOkRC ) { if( rc != kTimeOutRC ) { cwLogError(rc,"Audio device file thread Wait-on-condition-var failed."); return false; } } p->threadCbCnt += 1; // if the cond. var was signaled and p->mutexH is locked if( rc == kOkRC ) { // check for ready devices for(d=p->list; d!=nullptr; d=d->link) { // if this device is ready if( std::atomic_load_explicit(&d->readyCnt, std::memory_order_acquire) > 0 ) // ACQUIRE { // atomic incr - note that the ordering doesn't matter because the update does not control access to any other variables from another thread std::atomic_store_explicit(&d->readyCnt, d->readyCnt-1, std::memory_order_relaxed); // decrement if((rc = _update_device(d,true)) != kOkRC ) cwLogError(rc,"The update of audio device file %s failed.",cwStringNullGuard(d->label)); } } } return true; } rc_t _destroy( dev_mgr_t* p ) { rc_t rc = kOkRC; // destroy the thread if((rc = thread::destroy(p->threadH)) != kOkRC ) { rc = cwLogError(rc,"Audio file device thread destroy failed."); goto errLabel; } // destroy the mutex if((rc = mutex::destroy( p->mutexH )) != kOkRC ) { rc = cwLogError(rc,"Audio file device thread mutex destroy failed."); goto errLabel; } // destroy each device for(dev_t* d = p->list; d != nullptr; ) { dev_t* d0 = d->link; _close_input(d); _close_output(d); mem::release(d->label); mem::release(d); d = d0; } mem::release(p); errLabel: return rc; } } } } } cw::rc_t cw::audio::device::file::create( handle_t& hRef, struct driver_str*& drvRef ) { rc_t rc; if((rc = destroy(hRef)) != kOkRC ) return rc; dev_mgr_t* p = mem::allocZ(); p->driver.drvArg = p; p->driver.deviceCount = deviceCount; p->driver.deviceLabel = deviceLabel; p->driver.deviceChannelCount = deviceChannelCount; p->driver.deviceSampleRate = deviceSampleRate; p->driver.deviceFramesPerCycle = deviceFramesPerCycle; p->driver.deviceSetup = deviceSetup; p->driver.deviceStart = deviceStart; p->driver.deviceStop = deviceStop; p->driver.deviceIsStarted = deviceIsStarted; p->driver.deviceExecute = deviceExecute; p->driver.deviceEnable = deviceEnable; p->driver.deviceSeek = deviceSeek; p->driver.deviceRealTimeReport = deviceRealTimeReport; if((rc = create( p->threadH, _threadCbFunc, p, "audio_dev_test" )) != kOkRC ) { rc = cwLogError(rc,"Audio device file thread create failed."); goto errLabel; } // create the audio group thread mutex/cond var if((rc = mutex::create(p->mutexH)) != kOkRC ) { rc = cwLogError(rc,"Audio device file mutex create failed."); goto errLabel; } drvRef = &p->driver; hRef.set(p); errLabel: if( rc != kOkRC ) { _destroy(p); rc = cwLogError(rc,"Audio device file create failed."); } return rc; } cw::rc_t cw::audio::device::file::destroy( handle_t& hRef ) { rc_t rc = kOkRC; if( !hRef.isValid() ) return rc; dev_mgr_t* p = _handleToPtr(hRef); if((rc = _destroy(p)) != kOkRC ) return rc; hRef.clear(); return rc; } cw::rc_t cw::audio::device::file::start( handle_t h ) { rc_t rc = kOkRC; dev_mgr_t* p = _handleToPtr(h); // if there are any audio device files if( p->list != nullptr ) { if( _is_thread_active(p) ) if((rc = thread::unpause(p->threadH)) != kOkRC ) { rc = cwLogError(rc,"Audio file device thread start failed."); goto errLabel; } } errLabel: return rc; } cw::rc_t cw::audio::device::file::stop( handle_t h ) { rc_t rc = kOkRC; dev_mgr_t* p = _handleToPtr(h); if( _is_thread_active(p) ) if((rc = thread::pause(p->threadH)) != kOkRC ) { rc = cwLogError(rc,"Audio file device thread stop failed."); goto errLabel; } errLabel: return rc; } unsigned cw::audio::device::file::deviceCount( struct driver_str* drv) { dev_mgr_t* p = _driverToPtr(drv); dev_t* d = p->list; unsigned n = 0; for(; d!=nullptr; d=d->link) ++n; return n; } const char* cw::audio::device::file::deviceLabel( struct driver_str* drv, unsigned devIdx ) { rc_t rc= kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) return nullptr; return d->label; } unsigned cw::audio::device::file::deviceChannelCount( struct driver_str* drv, unsigned devIdx, bool inputFl ) { rc_t rc = kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) return 0; audiofile::handle_t fH = inputFl ? d->iFileH : d->oFileH; return fH.isValid() ? channelCount( fH ) : 0; } double cw::audio::device::file::deviceSampleRate( struct driver_str* drv, unsigned devIdx ) { rc_t rc= kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) return 0; return d->srate; } unsigned cw::audio::device::file::deviceFramesPerCycle( struct driver_str* drv, unsigned devIdx, bool inputFl ) { rc_t rc= kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) return 0; return d->framesPerCycle; } cw::rc_t cw::audio::device::file::deviceSetup( struct driver_str* drv, unsigned devIdx, double srate, unsigned frmPerCycle, cbFunc_t cbFunc, void* cbArg, unsigned cbDevIdx ) { rc_t rc0 = kOkRC; rc_t rc1 = kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc0 = _indexToDev( p, devIdx, d )) != kOkRC ) return rc0; _close_input(d); _close_output(d); d->framesPerCycle = frmPerCycle; d->srate = srate; d->cbFunc = cbFunc; d->cbArg = cbArg; d->cbDevIdx = cbDevIdx; if( cwIsFlag(d->iFlags,kUseInternalClockFl) || cwIsFlag(d->oFlags,kUseInternalClockFl) ) { unsigned ms = (frmPerCycle * 1000) / srate; if( p->threadTimeOutMs==0 || ms < p->threadTimeOutMs ) { p->threadTimeOutMs = ms; cwLogInfo("Audio device file time out %i ms.",p->threadTimeOutMs); } } rc0 = _open_input(d, devIdx); rc1 = _open_output(d, devIdx); rc0 = rcSelect(rc0,rc1); if(rc0 != kOkRC ) rc0 = cwLogError(rc0,"Audio device file '%s' setup failed.",cwStringNullGuard(d->label)); return rc0; } cw::rc_t cw::audio::device::file::deviceStart( struct driver_str* drv, unsigned devIdx ) { rc_t rc0 = kOkRC; rc_t rc1 = kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc0 = _indexToDev( p, devIdx, d )) == kOkRC ) { if( d->iFileH.isValid() && cwIsFlag(d->iFlags,kRewindOnStartFl) ) if((rc0 = seek(d->iFileH,0)) != kOkRC ) rc0 = cwLogError(rc0,"Rewind on start failed on the audio device file input file."); if( d->oFileH.isValid() && cwIsFlag(d->oFlags,kRewindOnStartFl) ) if((rc1 = seek(d->oFileH,0)) != kOkRC ) rc1 = cwLogError(rc1,"Rewind on start failed on the audio device file output file."); } if((rc0 = rcSelect(rc0,rc1)) == kOkRC ) d->isStartedFl = true; return rc0; } cw::rc_t cw::audio::device::file::deviceStop( struct driver_str* drv, unsigned devIdx ) { rc_t rc = kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc = _indexToDev( p, devIdx, d )) == kOkRC ) if( d->isStartedFl && d->oFileH.isValid() ) { // TODO: implement audiofile::flush() // if((rc = flush(d->ofileH)) != kOkRC ) // rc = cwLogError(rc,"Flush on stop failed on the audio device file output file."); d->isStartedFl = false; } return rc; } bool cw::audio::device::file::deviceIsStarted( struct driver_str* drv, unsigned devIdx ) { rc_t rc = kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc = _indexToDev( p, devIdx, d )) == kOkRC ) return d->isStartedFl; return false; } cw::rc_t cw::audio::device::file::deviceExecute( struct driver_str* drv, unsigned devIdx ) { rc_t rc = kOkRC; dev_mgr_t* p = _driverToPtr(drv); dev_t* d = nullptr; if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) { goto errLabel; } std::atomic_store_explicit(&d->readyCnt, d->readyCnt+1, std::memory_order_relaxed); // atomic incr if( cwIsFlag(d->iFlags,kUseInternalClockFl) ) { mutex::signalCondVar(p->mutexH); } else { rc = _update_device(d,false); } errLabel: return rc; } cw::rc_t cw::audio::device::file::deviceEnable( struct driver_str* drv, unsigned devIdx, bool inputFl, bool enableFl ) { rc_t rc = kOkRC; dev_mgr_t* p = _driverToPtr(drv); dev_t* d = nullptr; if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) goto errLabel; if( inputFl ) d->iEnableFl = enableFl; else d->oEnableFl = enableFl; errLabel: return rc; } cw::rc_t cw::audio::device::file::deviceSeek( struct driver_str* drv, unsigned devIdx, bool inputFl, unsigned frameOffset ) { rc_t rc = kOkRC; dev_mgr_t* p = _driverToPtr(drv); dev_t* d = nullptr; if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) goto errLabel; if( inputFl ) { if( d->iFileH.isValid() ) { if( d->iCacheFrameN ) { d->iCacheIdx = std::min( frameOffset, d->iCacheFrameN ); } else { if((rc = seek(d->iFileH,frameOffset)) != kOkRC ) rc = cwLogError(rc,"Seek failed on the audio device file input file."); } } } else { if( d->oFileH.isValid()) if((rc = seek(d->oFileH,frameOffset)) != kOkRC ) rc = cwLogError(rc,"Seek failed on the audio device file output file."); } errLabel: return rc; } void cw::audio::device::file::deviceRealTimeReport( struct driver_str* drv, unsigned devIdx ) { rc_t rc = kOkRC; dev_t* d = nullptr; dev_mgr_t* p = _driverToPtr(drv); if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) return; cwLogInfo("file cb i:%i o:%i err i:%i o:%i frames: i:%i o:%i : th:%i : %s",d->iCbCnt,d->oCbCnt,d->iErrCnt,d->oErrCnt,d->iFrmCnt,d->oFrmCnt,p->threadCbCnt,cwStringNullGuard(d->label)); } cw::rc_t cw::audio::device::file::createInDevice( handle_t& h, const char* label, const char* audioInFName, unsigned flags ) { rc_t rc = kOkRC; handle_t iFileH; dev_t* d; dev_mgr_t* p = _handleToPtr(h); // create or find a device if((d = _findOrCreateDev(p, label )) == nullptr ) { rc = cwLogError(kOpFailRC,"Audio file device create failed."); goto errLabel; } d->iFname = mem::duplStr(audioInFName); d->iFlags = flags; errLabel: return rc; } cw::rc_t cw::audio::device::file::createOutDevice( handle_t& h, const char* label, const char* audioOutFName, unsigned flags, unsigned chCnt, unsigned bitsPerSample, unsigned cacheBlkSec ) { rc_t rc = kOkRC; dev_t* d; dev_mgr_t* p = _handleToPtr(h); // create or find a device if((d = _findOrCreateDev(p, label )) == nullptr ) { rc = cwLogError(kOpFailRC,"Audio file device create failed."); goto errLabel; } d->oFname = mem::duplStr(audioOutFName); d->oFlags = flags; d->oChCnt = chCnt; d->oBitsPerSample = bitsPerSample; d->oCacheBlockSec = cacheBlkSec; errLabel: return rc; } cw::rc_t cw::audio::device::file::report(handle_t h ) { rc_t rc = kOkRC; dev_mgr_t* p = _handleToPtr(h); dev_t* d = p->list; for(; d!=nullptr; d=d->link) _devReport(d); return rc; } cw::rc_t cw::audio::device::file::report() { rc_t rc = kOkRC; return rc; } namespace cw { namespace audio { namespace device { namespace file { typedef struct cb_object_str { uint8_t* buf; unsigned byteCnt; std::atomic readyCnt; } cb_object_t; void driverCallback( void* cbArg, audioPacket_t* inPktArray, unsigned inPktCnt, audioPacket_t* outPktArray, unsigned outPktCnt ) { cb_object_t* p = (cb_object_t*)cbArg; if( inPktCnt ) { audioPacket_t* pkt = inPktArray; unsigned pktByteN = pkt->audioFramesCnt * pkt->chCnt * sizeof(sample_t); unsigned byteN = std::min(p->byteCnt,pktByteN); vop::copy(p->buf,(const uint8_t*)pkt->audioBytesPtr,byteN); p->readyCnt++; } if( outPktCnt && p->readyCnt.load() > 0) { audioPacket_t* pkt = outPktArray; unsigned pktByteN = pkt->audioFramesCnt * pkt->chCnt * sizeof(sample_t); unsigned byteN = std::min(p->byteCnt,pktByteN); vop::copy((uint8_t*)pkt->audioBytesPtr,p->buf,byteN); p->readyCnt--; } } } } } } cw::rc_t cw::audio::device::file::test( const object_t* cfg) { rc_t rc = kOkRC; rc_t rc1 = kOkRC; rc_t rc2 = kOkRC; const char* ifname = nullptr; const char* ofname = nullptr; unsigned cycleCnt = 0; struct driver_str driver = {}; struct driver_str* driver_ptr = &driver; unsigned bitsPerSample = 0; // zero indicates floating point sample format for output audio file unsigned sleepMicrosec = 0; const char* devLabel = "dev_file"; unsigned devIdx = 0; unsigned framesPerCycle = 0; cb_object_t obj = {}; void* cbArg = &obj; audiofile::info_t info; handle_t h; // parse the test args if((rc = cfg->getv("inAudioFname",ifname, "outAudioFname",ofname, "framesPerCycle",framesPerCycle, "cycleCnt",cycleCnt)) != kOkRC || ifname==nullptr || ofname==nullptr ) { rc = cwLogError(rc,"Parsing audiio device file test cfg. failed."); goto errLabel; } // create the audioDeviceFile mgr if((rc = create(h,driver_ptr)) != kOkRC ) { rc = cwLogError(rc,"Error creating audio device file mgr."); goto errLabel; } // get input file srate and channel count if((rc = getInfo( ifname, &info )) != kOkRC ) { rc = cwLogError(rc,"Error parsing input audio file '%s' header.",cwStringNullGuard(ifname)); goto errLabel; } // create an input audio file device if((rc = createInDevice( h, devLabel, ifname, kRewindOnStartFl | kCacheFl)) != kOkRC ) { rc = cwLogError(rc,"Error creating the audio device file input device from the file:%s.",cwStringNullGuard(ifname)); goto errLabel; } // create an output audio file device if((rc = createOutDevice( h, devLabel, ofname, kRewindOnStartFl | kCacheFl, info.chCnt, bitsPerSample )) != kOkRC ) { rc = cwLogError(rc,"Error creating the audio device file output device from the file:%s.",cwStringNullGuard(ofname)); goto errLabel; } // setup the audio device file device if((rc = deviceSetup( driver_ptr, devIdx, info.srate, framesPerCycle, driverCallback, cbArg, devIdx )) != kOkRC ) { rc = cwLogError(rc,"Error setting up the audio device file."); goto errLabel; } sleepMicrosec = (framesPerCycle * 1e6) / info.srate; report(h); obj.byteCnt = framesPerCycle * info.chCnt * sizeof(sample_t); obj.buf = mem::allocZ( obj.byteCnt ); // start the audio device file if((rc = start(h)) != kOkRC ) { rc = cwLogError(rc,"Audio device file start failed."); goto errLabel; } // run the audio device file for(unsigned i=0; i