Add output file caching and use of kUseInternalClockFl.

This commit is contained in:
kevin 2023-02-26 13:11:07 -05:00
parent 03061f42b7
commit 41bac85d94
2 changed files with 180 additions and 59 deletions

View File

@ -27,6 +27,15 @@ namespace cw
kOutFl = 0x02 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 typedef struct dev_str
{ {
char* label; char* label;
@ -73,6 +82,11 @@ namespace cw
unsigned oErrCnt; // count of errors unsigned oErrCnt; // count of errors
unsigned oFrmCnt; // count of frames written 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; struct dev_str* link;
} dev_t; } dev_t;
@ -84,7 +98,6 @@ namespace cw
unsigned threadCbCnt; unsigned threadCbCnt;
thread::handle_t threadH; thread::handle_t threadH;
mutex::handle_t mutexH; mutex::handle_t mutexH;
} dev_mgr_t; } dev_mgr_t;
dev_mgr_t* _handleToPtr( handle_t h ) dev_mgr_t* _handleToPtr( handle_t h )
@ -93,6 +106,9 @@ namespace cw
dev_mgr_t* _driverToPtr( driver_t* drvr ) dev_mgr_t* _driverToPtr( driver_t* drvr )
{ return (dev_mgr_t*)drvr->drvArg; } { 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* _labelToDev( dev_mgr_t* p, const char* label )
{ {
dev_t* d = p->list; dev_t* d = p->list;
@ -189,7 +205,7 @@ namespace cw
if( n > 0 ) if( n > 0 )
{ {
memcpy( d->iPktAudioBuf, d->iCacheBuf + (d->iCacheIdx * d->iChCnt), d->framesPerCycle * d->iChCnt * sizeof(sample_t)); memcpy( d->iPktAudioBuf, d->iCacheBuf + (d->iCacheIdx * d->iChCnt), n * d->iChCnt * sizeof(sample_t));
d->iCacheIdx += n; d->iCacheIdx += n;
// set m to the count of frames to zero at the end of the pkt // set m to the count of frames to zero at the end of the pkt
@ -211,9 +227,7 @@ namespace cw
unsigned actualFrameCnt = 0; unsigned actualFrameCnt = 0;
// read the file // read the file
if((rc = readFloat(d->iFileH, d->framesPerCycle, 0, d->iChCnt, d->iChArray, &actualFrameCnt)) == kOkRC ) if((rc = readFloat(d->iFileH, d->framesPerCycle, 0, d->iChCnt, d->iChArray, &actualFrameCnt)) != kOkRC )
d->iFrmCnt += actualFrameCnt;
else
{ {
rc = cwLogError(rc,"File read failed on audio device file: %s.",cwStringNullGuard(d->label)); rc = cwLogError(rc,"File read failed on audio device file: %s.",cwStringNullGuard(d->label));
d->iErrCnt += 1; d->iErrCnt += 1;
@ -309,7 +323,7 @@ namespace cw
d->iPkt.begChIdx = 0; d->iPkt.begChIdx = 0;
d->iPkt.chCnt = info.chCnt; d->iPkt.chCnt = info.chCnt;
d->iPkt.audioFramesCnt = d->framesPerCycle; d->iPkt.audioFramesCnt = d->framesPerCycle;
d->iPkt.bitsPerSample = sizeof(sample_t); d->iPkt.bitsPerSample = 0;
d->iPkt.flags = kInterleavedApFl | kFloatApFl; d->iPkt.flags = kInterleavedApFl | kFloatApFl;
d->iPkt.audioBytesPtr = d->iPktAudioBuf; d->iPkt.audioBytesPtr = d->iPktAudioBuf;
d->iPkt.cbArg = d->cbArg; d->iPkt.cbArg = d->cbArg;
@ -341,33 +355,82 @@ namespace cw
rc = _fill_input_packet_from_cache(d); rc = _fill_input_packet_from_cache(d);
else else
rc = _fill_input_packet_from_file(d); rc = _fill_input_packet_from_file(d);
if( rc == kOkRC )
d->iFrmCnt += d->framesPerCycle;
} }
return rc; 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 ) void _close_output( dev_t* d )
{ {
if( d->oFileH.isValid() ) if( d->oFileH.isValid() )
{ {
_write_cache_to_file(d);
close(d->oFileH); close(d->oFileH);
d->oCbCnt = 0; d->oCbCnt = 0;
mem::release(d->oFname); mem::release(d->oFname);
mem::release(d->oPktAudioBuf); mem::release(d->oPktAudioBuf);
mem::release(d->oChArray); mem::release(d->oChArray);
mem::release(d->oChSmpBuf); 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<uint8_t>( 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 _open_output( dev_t* d, unsigned devIdx )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
audiofile::handle_t oFileH; audiofile::handle_t oFileH;
unsigned floatBitsPerSample = 0; // set bits per sample to 0 to indicate a floating point file
if( d->oFname == nullptr ) if( d->oFname == nullptr )
return rc; return rc;
// open the requested audio output flie // open the requested audio output flie
if((rc = create( oFileH, d->oFname, d->srate, d->oBitsPerSample, d->oChCnt )) != kOkRC )
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)); rc = cwLogError(rc,"Audio file device open failed on '%s'.",cwStringNullGuard(d->oFname));
goto errLabel; goto errLabel;
@ -384,11 +447,22 @@ namespace cw
d->oPkt.begChIdx = 0; d->oPkt.begChIdx = 0;
d->oPkt.chCnt = d->oChCnt; d->oPkt.chCnt = d->oChCnt;
d->oPkt.audioFramesCnt = d->framesPerCycle; d->oPkt.audioFramesCnt = d->framesPerCycle;
d->oPkt.bitsPerSample = 0; d->oPkt.bitsPerSample = 0; // 0==floating point sample format
d->oPkt.flags = kInterleavedApFl | kFloatApFl; d->oPkt.flags = kInterleavedApFl | kFloatApFl;
d->oPkt.audioBytesPtr = d->oPktAudioBuf; d->oPkt.audioBytesPtr = d->oPktAudioBuf;
d->oPkt.cbArg = d->cbArg; 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; i<d->oChCnt; ++i) for(unsigned i=0; i<d->oChCnt; ++i)
d->oChArray[i] = d->oChSmpBuf + i*d->framesPerCycle; d->oChArray[i] = d->oChSmpBuf + i*d->framesPerCycle;
@ -396,35 +470,58 @@ namespace cw
return rc; return rc;
} }
rc_t _write_cache_output( dev_t* d )
{
rc_t rc = kOkRC;
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;
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 _write_output( dev_t* d )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
if( d->oFileH.isValid() && d->oEnableFl ) if( d->oFileH.isValid() && d->oEnableFl )
{ {
/* if( cwIsFlag(d->oFlags,kCacheFl) )
// deinterleave the audio into d->oChArray[] rc = _write_cache_output(d);
vop::deinterleave( d->oChSmpBuf, d->oPktAudioBuf, d->framesPerCycle, d->oChCnt );
// write the audio
if((rc = writeFloat( d->oFileH, d->framesPerCycle, d->oChCnt, d->oChArray )) == kOkRC )
*/
if((rc = writeFloatInterleaved( d->oFileH, d->framesPerCycle, d->oChCnt, d->oPktAudioBuf )) == kOkRC )
d->oFrmCnt += d->framesPerCycle;
else else
{ rc = _write_file_output(d);
rc = cwLogError(rc,"Audio device file write failed on device '%s'.",d->label);
d->oErrCnt += 1; if( rc != kOkRC )
goto errLabel; d->oFrmCnt += d->framesPerCycle;
}
} }
errLabel:
return rc; return rc;
} }
rc_t _update_device( dev_t* d ) rc_t _update_device( dev_t* d, bool inThreadFl )
{ {
rc_t rc0 = kOkRC; rc_t rc0 = kOkRC;
rc_t rc1 = kOkRC; rc_t rc1 = kOkRC;
@ -434,7 +531,7 @@ namespace cw
audioPacket_t* oPkt = nullptr; audioPacket_t* oPkt = nullptr;
unsigned oPktN = 0; unsigned oPktN = 0;
if( d->iFileH.isValid() ) if( d->iFileH.isValid() && inThreadFl == cwIsFlag(d->iFlags,kUseInternalClockFl) )
{ {
iPkt = &d->iPkt; iPkt = &d->iPkt;
iPktN = 1; iPktN = 1;
@ -442,7 +539,7 @@ namespace cw
rc0 = _read_input( d ); rc0 = _read_input( d );
} }
if( d->oFileH.isValid() ) if( d->oFileH.isValid() && inThreadFl == cwIsFlag(d->iFlags,kUseInternalClockFl) )
{ {
oPkt = &d->oPkt; oPkt = &d->oPkt;
oPktN = 1; oPktN = 1;
@ -465,7 +562,7 @@ namespace cw
dev_t* d = nullptr; dev_t* d = nullptr;
// block on the cond. var - unlock the mutex // block on the cond. var - unlock the mutex
if((rc = mutex::waitOnCondVar(p->mutexH,false,p->threadTimeOutMs/2)) != kOkRC ) if((rc = mutex::waitOnCondVar(p->mutexH,false,p->threadTimeOutMs)) != kOkRC )
{ {
if( rc != kTimeOutRC ) if( rc != kTimeOutRC )
{ {
@ -488,7 +585,7 @@ namespace cw
// atomic incr - note that the ordering doesn't matter because the update does not control access to any other variables from another thread // 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 std::atomic_store_explicit(&d->readyCnt, d->readyCnt-1, std::memory_order_relaxed); // decrement
if((rc = _update_device(d)) != kOkRC ) if((rc = _update_device(d,true)) != kOkRC )
cwLogError(rc,"The update of audio device file %s failed.",cwStringNullGuard(d->label)); cwLogError(rc,"The update of audio device file %s failed.",cwStringNullGuard(d->label));
} }
@ -613,6 +710,7 @@ cw::rc_t cw::audio::device::file::start( handle_t h )
// if there are any audio device files // if there are any audio device files
if( p->list != nullptr ) if( p->list != nullptr )
{ {
if( _is_thread_active(p) )
if((rc = thread::unpause(p->threadH)) != kOkRC ) if((rc = thread::unpause(p->threadH)) != kOkRC )
{ {
rc = cwLogError(rc,"Audio file device thread start failed."); rc = cwLogError(rc,"Audio file device thread start failed.");
@ -628,6 +726,7 @@ cw::rc_t cw::audio::device::file::stop( handle_t h )
rc_t rc = kOkRC; rc_t rc = kOkRC;
dev_mgr_t* p = _handleToPtr(h); dev_mgr_t* p = _handleToPtr(h);
if( _is_thread_active(p) )
if((rc = thread::pause(p->threadH)) != kOkRC ) if((rc = thread::pause(p->threadH)) != kOkRC )
{ {
rc = cwLogError(rc,"Audio file device thread stop failed."); rc = cwLogError(rc,"Audio file device thread stop failed.");
@ -719,12 +818,15 @@ cw::rc_t cw::audio::device::file::deviceSetup( struct driver_str* drv, unsign
d->cbArg = cbArg; d->cbArg = cbArg;
d->cbDevIdx = cbDevIdx; d->cbDevIdx = cbDevIdx;
if( cwIsFlag(d->iFlags,kUseInternalClockFl) || cwIsFlag(d->oFlags,kUseInternalClockFl) )
{
unsigned ms = (frmPerCycle * 1000) / srate; unsigned ms = (frmPerCycle * 1000) / srate;
if( p->threadTimeOutMs==0 || ms < p->threadTimeOutMs ) if( p->threadTimeOutMs==0 || ms < p->threadTimeOutMs )
{ {
p->threadTimeOutMs = ms; p->threadTimeOutMs = ms;
cwLogInfo("Audio device file time out %i ms.",p->threadTimeOutMs); cwLogInfo("Audio device file time out %i ms.",p->threadTimeOutMs);
} }
}
rc0 = _open_input(d, devIdx); rc0 = _open_input(d, devIdx);
rc1 = _open_output(d, devIdx); rc1 = _open_output(d, devIdx);
@ -746,7 +848,6 @@ cw::rc_t cw::audio::device::file::deviceStart( struct driver_str* dr
if((rc0 = _indexToDev( p, devIdx, d )) == kOkRC ) if((rc0 = _indexToDev( p, devIdx, d )) == kOkRC )
{ {
/*
if( d->iFileH.isValid() && cwIsFlag(d->iFlags,kRewindOnStartFl) ) if( d->iFileH.isValid() && cwIsFlag(d->iFlags,kRewindOnStartFl) )
if((rc0 = seek(d->iFileH,0)) != kOkRC ) if((rc0 = seek(d->iFileH,0)) != kOkRC )
rc0 = cwLogError(rc0,"Rewind on start failed on the audio device file input file."); rc0 = cwLogError(rc0,"Rewind on start failed on the audio device file input file.");
@ -754,7 +855,6 @@ cw::rc_t cw::audio::device::file::deviceStart( struct driver_str* dr
if( d->oFileH.isValid() && cwIsFlag(d->oFlags,kRewindOnStartFl) ) if( d->oFileH.isValid() && cwIsFlag(d->oFlags,kRewindOnStartFl) )
if((rc1 = seek(d->oFileH,0)) != kOkRC ) if((rc1 = seek(d->oFileH,0)) != kOkRC )
rc1 = cwLogError(rc1,"Rewind on start failed on the audio device file output file."); rc1 = cwLogError(rc1,"Rewind on start failed on the audio device file output file.");
*/
} }
if((rc0 = rcSelect(rc0,rc1)) == kOkRC ) if((rc0 = rcSelect(rc0,rc1)) == kOkRC )
@ -800,11 +900,20 @@ cw::rc_t cw::audio::device::file::deviceExecute( struct driver_str* dr
dev_t* d = nullptr; dev_t* d = nullptr;
if((rc = _indexToDev( p, devIdx, d )) != kOkRC ) if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
{
goto errLabel; goto errLabel;
}
std::atomic_store_explicit(&d->readyCnt, d->readyCnt+1, std::memory_order_relaxed); // atomic incr std::atomic_store_explicit(&d->readyCnt, d->readyCnt+1, std::memory_order_relaxed); // atomic incr
if( cwIsFlag(d->iFlags,kUseInternalClockFl) )
{
mutex::signalCondVar(p->mutexH); mutex::signalCondVar(p->mutexH);
}
else
{
rc = _update_device(d,false);
}
errLabel: errLabel:
return rc; return rc;
@ -840,9 +949,18 @@ cw::rc_t cw::audio::device::file::deviceSeek( struct driver_str* drv, unsigne
if( inputFl ) if( inputFl )
{ {
if( d->iFileH.isValid() ) if( d->iFileH.isValid() )
{
if( d->iCacheFrameN )
{
d->iCacheIdx = std::min( frameOffset, d->iCacheFrameN );
}
else
{
if((rc = seek(d->iFileH,frameOffset)) != kOkRC ) if((rc = seek(d->iFileH,frameOffset)) != kOkRC )
rc = cwLogError(rc,"Seek failed on the audio device file input file."); rc = cwLogError(rc,"Seek failed on the audio device file input file.");
} }
}
}
else else
{ {
if( d->oFileH.isValid()) if( d->oFileH.isValid())
@ -889,7 +1007,7 @@ cw::rc_t cw::audio::device::file::createInDevice( handle_t& h, const char* l
return rc; 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 ) 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; rc_t rc = kOkRC;
dev_t* d; dev_t* d;
@ -906,6 +1024,7 @@ cw::rc_t cw::audio::device::file::createOutDevice( handle_t& h, const char* l
d->oFlags = flags; d->oFlags = flags;
d->oChCnt = chCnt; d->oChCnt = chCnt;
d->oBitsPerSample = bitsPerSample; d->oBitsPerSample = bitsPerSample;
d->oCacheBlockSec = cacheBlkSec;
errLabel: errLabel:
@ -1014,14 +1133,14 @@ cw::rc_t cw::audio::device::file::test( const object_t* cfg)
} }
// create an input audio file device // create an input audio file device
if((rc = createInDevice( h, devLabel, ifname, kRewindOnStartFl )) != kOkRC ) 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)); rc = cwLogError(rc,"Error creating the audio device file input device from the file:%s.",cwStringNullGuard(ifname));
goto errLabel; goto errLabel;
} }
// create an output audio file device // create an output audio file device
if((rc = createOutDevice( h, devLabel, ofname, kRewindOnStartFl, info.chCnt, bitsPerSample )) != kOkRC ) 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)); rc = cwLogError(rc,"Error creating the audio device file output device from the file:%s.",cwStringNullGuard(ofname));
goto errLabel; goto errLabel;

View File

@ -12,7 +12,7 @@ namespace cw
{ {
typedef handle<struct dev_mgr_str> handle_t; typedef handle<struct dev_mgr_str> handle_t;
rc_t create( handle_t& hRef, struct driver_str*& drvRef ); rc_t create( handle_t& hRef, struct driver_str*& drvRef);
rc_t destroy( handle_t& hRef ); rc_t destroy( handle_t& hRef );
rc_t start( handle_t h ); rc_t start( handle_t h );
rc_t stop( handle_t h ); rc_t stop( handle_t h );
@ -32,8 +32,10 @@ namespace cw
void deviceRealTimeReport( struct driver_str* drv, unsigned devIdx ); void deviceRealTimeReport( struct driver_str* drv, unsigned devIdx );
enum { enum {
kRewindOnStartFl = 0x01, kRewindOnStartFl = 0x01, // rewind the in/out file deviceStart()
kCacheFl = 0x02 kCacheFl = 0x02, // if set then cache in/out audio in memory, otherwise access disk file
kUseInternalClockFl = 0x04, // if set then callback from internal thread, else callback from deviceExecute()
}; };
// A device may have an input, an output or both. // A device may have an input, an output or both.
@ -41,7 +43,7 @@ namespace cw
rc_t createInDevice( handle_t& h, const char* label, const char* audioInFName, unsigned flags ); rc_t createInDevice( handle_t& h, const char* label, const char* audioInFName, unsigned flags );
// Set bitsPerSample to 0 to write in single prec. float. // Set bitsPerSample to 0 to write in single prec. float.
rc_t createOutDevice( handle_t& h, const char* label, const char* audioOutFName, unsigned flags, unsigned chCnt, unsigned bitsPerSample ); rc_t createOutDevice( handle_t& h, const char* label, const char* audioOutFName, unsigned flags, unsigned chCnt, unsigned bitsPerSample, unsigned cacheBlockSec=10 );
// Generate an audio callback on the specified device. // Generate an audio callback on the specified device.
rc_t deviceExec( handle_t& h, unsigned devIdx ); rc_t deviceExec( handle_t& h, unsigned devIdx );