libcm is a C development framework with an emphasis on audio signal processing applications.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cmAudioFileDev.c 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. //| Copyright: (C) 2009-2020 Kevin Larke <contact AT larke DOT org>
  2. //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
  3. #include "cmGlobal.h"
  4. #include "cmFloatTypes.h"
  5. #include "cmRpt.h"
  6. #include "cmErr.h"
  7. #include "cmCtx.h"
  8. #include "cmMem.h"
  9. #include "cmMallocDebug.h"
  10. #include "cmAudioFile.h"
  11. #include "cmThread.h"
  12. #include "cmTime.h"
  13. #include "cmAudioPort.h"
  14. #include "cmAudioFileDev.h"
  15. #include "cmTime.h"
  16. cmAfdH_t cmAfdNullHandle = cmSTATIC_NULL_HANDLE;
  17. #define cmAfd_Billion (1000000000)
  18. #define cmAfd_Million (1000000)
  19. typedef struct
  20. {
  21. cmErr_t err; // error object
  22. cmApCallbackPtr_t callbackPtr; // client callback function
  23. void* cbDataPtr; // argument to be passed with the client callback
  24. unsigned devIdx;
  25. cmChar_t* label;
  26. cmChar_t* oFn;
  27. unsigned oBits;
  28. unsigned oChCnt;
  29. cmAudioFileH_t iAfH; // audio input file handle
  30. cmAudioFileH_t oAfH; // audio output file handle
  31. cmThreadH_t tH; // thread handle
  32. double srate; // file device sample rate
  33. unsigned framesPerCycle; // count of samples sent/recv'd from the client on each callback
  34. cmApAudioPacket_t iPkt; // audio packet used sent to the client via callbackPtr.
  35. cmApAudioPacket_t oPkt; //
  36. cmApSample_t** iChArray; // audio buffer channel arrays used with cmAudioFile
  37. cmApSample_t** oChArray; //
  38. bool runFl; // set to true as long as the thread should continue looping
  39. bool rewindFl; // set to true when the input file should rewind
  40. unsigned readErrCnt; // count of read errors from the input file
  41. bool eofFl; // set to true when the input file reaches the EOF
  42. unsigned writeErrCnt; // count of write errors from the output file
  43. long nanosPerCycle; // nano-seconds per cycle
  44. struct timespec baseTime;
  45. struct timespec nextTime; // next execution time
  46. unsigned cycleCnt; // count of cycles completed
  47. } cmAfd_t;
  48. cmAfd_t* _cmAfdHandleToPtr( cmAfdH_t h )
  49. {
  50. cmAfd_t* p = (cmAfd_t*)h.h;
  51. assert(p != NULL );
  52. return p;
  53. }
  54. //
  55. void _cmAudioFileDevExec( cmAfd_t* p )
  56. {
  57. unsigned iPktCnt = 0;
  58. unsigned oPktCnt = p->oPkt.chCnt!=0;
  59. // if the input device is enabled
  60. if( p->iPkt.chCnt )
  61. {
  62. unsigned actualFrmCnt = p->framesPerCycle;
  63. // if the input file has reached EOF - zero the input buffer
  64. if( p->eofFl )
  65. memset(p->iPkt.audioBytesPtr,0,p->framesPerCycle*sizeof(cmApSample_t));
  66. else
  67. {
  68. // otherwise fill the input buffer from the input file
  69. if( cmAudioFileReadSample(p->iAfH, p->framesPerCycle, p->iPkt.begChIdx, p->iPkt.chCnt, p->iChArray, &actualFrmCnt) != kOkAfRC )
  70. ++p->readErrCnt;
  71. // if the input file reachged EOF the set p->eofFl
  72. if( (actualFrmCnt < p->framesPerCycle) && cmAudioFileIsEOF(p->iAfH) )
  73. p->eofFl = true;
  74. }
  75. iPktCnt = actualFrmCnt>0;
  76. }
  77. // callback to the client to provde incoming samples and receive outgoing samples
  78. p->callbackPtr(iPktCnt ? &p->iPkt : NULL, iPktCnt, oPktCnt ? &p->oPkt : NULL, oPktCnt );
  79. // if the output device is enabled
  80. if( p->oPkt.chCnt )
  81. {
  82. // write the output samples
  83. if( cmAudioFileWriteSample( p->oAfH, p->framesPerCycle, p->oPkt.chCnt, p->oChArray ) != kOkAfRC )
  84. ++p->writeErrCnt;
  85. }
  86. ++p->cycleCnt;
  87. }
  88. // incrment p->nextTime to the next execution time
  89. void _cmAfdIncrNextTime( cmAfd_t* p )
  90. {
  91. long nsec = p->nextTime.tv_nsec + p->nanosPerCycle;
  92. if( nsec < cmAfd_Billion )
  93. p->nextTime.tv_nsec = nsec;
  94. else
  95. {
  96. p->nextTime.tv_sec += 1;
  97. p->nextTime.tv_nsec = nsec - cmAfd_Billion;
  98. }
  99. }
  100. // calc the time between t1 and t0 - t1 is assummed to come after t0 in order to produce a positive result
  101. long _cmAfdDiffMicros( const struct timespec* t0, const struct timespec* t1 )
  102. {
  103. long u0 = t0->tv_sec * cmAfd_Million;
  104. long u1 = t1->tv_sec * cmAfd_Million;
  105. u0 += t0->tv_nsec / 1000;
  106. u1 += t1->tv_nsec / 1000;
  107. return u1 - u0;
  108. }
  109. // thread callback function
  110. bool _cmAudioDevThreadFunc(void* param)
  111. {
  112. cmAfd_t* p = (cmAfd_t*)param;
  113. struct timespec t0;
  114. // if this is the first time this callback has been called after a call to cmAudioFileDevStart().
  115. if( p->cycleCnt == 0 )
  116. {
  117. // get the baseTime - all other times will be relative to this time
  118. cmTimeGet(&p->baseTime);
  119. p->nextTime = p->baseTime;
  120. p->nextTime.tv_sec = 0;
  121. _cmAfdIncrNextTime(p);
  122. }
  123. // if the thread has not been requested to stop
  124. if( p->runFl )
  125. {
  126. // get the current time as an offset from baseTime.
  127. cmTimeGet(&t0);
  128. t0.tv_sec -= p->baseTime.tv_sec;
  129. // get length of time to next exec point
  130. long dusec = _cmAfdDiffMicros(&t0, &p->nextTime);
  131. // if the execution time has not yet arrived
  132. if( dusec > 0 )
  133. {
  134. cmSleepUs(dusec);
  135. }
  136. // if the thread is still running
  137. if( p->runFl )
  138. {
  139. // read/callback/write
  140. _cmAudioFileDevExec(p);
  141. // calc the next exec time
  142. _cmAfdIncrNextTime(p);
  143. }
  144. }
  145. return p->runFl;
  146. }
  147. cmAfdRC_t cmAudioFileDevInitialize(
  148. cmAfdH_t* hp,
  149. const cmChar_t* label,
  150. unsigned devIdx,
  151. const cmChar_t* iFn,
  152. const cmChar_t* oFn,
  153. unsigned oBits,
  154. unsigned oChCnt,
  155. cmRpt_t* rpt )
  156. {
  157. cmAfdRC_t rc;
  158. cmRC_t afRC;
  159. if((rc = cmAudioFileDevFinalize(hp)) != kOkAfdRC )
  160. return rc;
  161. // allocate the object
  162. cmAfd_t* p = cmMemAllocZ(cmAfd_t,1);
  163. hp->h = p;
  164. cmErrSetup(&p->err,rpt,"AudioFileDevice");
  165. // create the input audio file handle
  166. if( iFn != NULL )
  167. {
  168. cmAudioFileInfo_t afInfo;
  169. // open the input file
  170. if(cmAudioFileIsValid( p->iAfH = cmAudioFileNewOpen(iFn,&afInfo,&afRC,rpt)) == false )
  171. {
  172. rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"The audio input file '%s' could not be opened.", iFn);
  173. goto errLabel;
  174. }
  175. p->iPkt.devIdx = devIdx;
  176. p->iPkt.begChIdx = 0;
  177. p->iPkt.chCnt = afInfo.chCnt; // setting iPkt.chCnt to a non-zero value marks the input file as active
  178. p->iPkt.audioFramesCnt = 0;
  179. p->iPkt.bitsPerSample = afInfo.bits;
  180. p->iPkt.flags = kFloatApFl;
  181. p->iPkt.audioBytesPtr = NULL;
  182. p->iChArray = cmMemResizeZ( cmApSample_t*, p->iChArray, afInfo.chCnt );
  183. p->readErrCnt = 0;
  184. p->eofFl = false;
  185. }
  186. // create the output audio file handle
  187. if(cmAudioFileIsValid( p->oAfH = cmAudioFileNewOpen(NULL,NULL,NULL,rpt)) == false )
  188. {
  189. rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"The audio output file object allocation failed.");
  190. goto errLabel;
  191. }
  192. // create the driver thread
  193. if( cmThreadCreate(&p->tH, _cmAudioDevThreadFunc, p, rpt ) != kOkThRC )
  194. {
  195. rc = cmErrMsg(&p->err,kThreadFailAfdRC,"The internal thread could not be created.");
  196. goto errLabel;
  197. }
  198. p->runFl = true;
  199. p->devIdx = devIdx;
  200. p->label = cmMemAllocStr(label);
  201. p->oFn = cmMemAllocStr(oFn);
  202. p->oBits = oBits;
  203. p->oChCnt = oChCnt;
  204. errLabel:
  205. if( rc != kOkAfdRC )
  206. cmAudioFileDevFinalize(hp);
  207. return rc;
  208. }
  209. cmAfdRC_t cmAudioFileDevFinalize( cmAfdH_t* hp )
  210. {
  211. if( hp == NULL || cmAudioFileDevIsValid(*hp) == false )
  212. return kOkAfdRC;
  213. cmAfd_t* p = _cmAfdHandleToPtr(*hp);
  214. p->runFl = false;
  215. if( cmThreadIsValid(p->tH) )
  216. cmThreadDestroy(&p->tH);
  217. cmAudioFileDelete(&p->iAfH);
  218. cmAudioFileDelete(&p->oAfH);
  219. cmMemPtrFree(&p->label);
  220. cmMemPtrFree(&p->oFn);
  221. cmMemPtrFree(&p->iPkt.audioBytesPtr);
  222. cmMemPtrFree(&p->oPkt.audioBytesPtr);
  223. cmMemPtrFree(&p->iChArray);
  224. cmMemPtrFree(&p->oChArray);
  225. cmMemPtrFree(&p);
  226. hp->h = NULL;
  227. return kOkAfdRC;
  228. }
  229. bool cmAudioFileDevIsValid( cmAfdH_t h )
  230. { return h.h != NULL; }
  231. cmAfdRC_t cmAudioFileDevSetup(
  232. cmAfdH_t h,
  233. unsigned baseDevIdx,
  234. double srate,
  235. unsigned framesPerCycle,
  236. cmApCallbackPtr_t callbackPtr,
  237. void* cbDataPtr )
  238. {
  239. cmAfdRC_t rc = kOkAfdRC;
  240. bool restartFl = false;
  241. unsigned i;
  242. if( cmAudioFileDevIsStarted(h) )
  243. {
  244. if((rc = cmAudioFileDevStop(h)) != kOkAfdRC )
  245. return rc;
  246. restartFl = true;
  247. }
  248. cmAfd_t* p = _cmAfdHandleToPtr(h);
  249. /*
  250. // close the existing input file
  251. if(cmAudioFileClose(&p->iAfH) != kOkAfRC )
  252. rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file close failed on input audio file.");
  253. p->iPkt.chCnt = 0; // mark the input file as inactive
  254. */
  255. if( cmAudioFileIsValid( p->iAfH ) )
  256. if( cmAudioFileSeek( p->iAfH, 0 ) != kOkAfRC )
  257. rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file device rewind failed.");
  258. // close the existing output file
  259. if(cmAudioFileClose(&p->oAfH) != kOkAfRC )
  260. rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file close failed on output audio file.");
  261. p->oPkt.chCnt = 0; // mark the output file as inactive
  262. // if an output audio file was given ...
  263. if( p->oFn != NULL )
  264. {
  265. // ... then open it
  266. if( cmAudioFileCreate( p->oAfH, p->oFn, srate, p->oBits, p->oChCnt ) != kOkAfRC )
  267. {
  268. rc = cmErrMsg(&p->err,kAudioFileFailAfdRC, "The audio output file '%s' could not be created.",p->oFn);
  269. goto errLabel;
  270. }
  271. cmApSample_t* bp = (cmApSample_t*)p->oPkt.audioBytesPtr;
  272. p->oPkt.devIdx = p->devIdx + baseDevIdx;
  273. p->oPkt.begChIdx = 0;
  274. p->oPkt.chCnt = p->oChCnt;
  275. p->oPkt.audioFramesCnt = framesPerCycle;
  276. p->oPkt.bitsPerSample = p->oBits;
  277. p->oPkt.flags = kFloatApFl;
  278. p->oPkt.audioBytesPtr = bp = cmMemResizeZ( cmApSample_t, bp, framesPerCycle*p->oChCnt );
  279. p->oPkt.userCbPtr = cbDataPtr;
  280. p->oChArray = cmMemResizeZ( cmApSample_t*, p->oChArray, p->oChCnt );
  281. for(i=0; i<p->oChCnt; ++i)
  282. p->oChArray[i] = bp + (i*framesPerCycle);
  283. }
  284. if( cmAudioFileIsValid( p->iAfH) )
  285. {
  286. cmApSample_t* bp = (cmApSample_t*)p->iPkt.audioBytesPtr;
  287. p->iPkt.devIdx = p->devIdx + baseDevIdx;
  288. p->iPkt.audioFramesCnt = framesPerCycle;
  289. p->iPkt.audioBytesPtr = bp = cmMemResizeZ( cmApSample_t, bp, framesPerCycle*p->iPkt.chCnt ); ;
  290. p->iPkt.userCbPtr = cbDataPtr;
  291. for(i=0; i<p->iPkt.chCnt; ++i)
  292. p->iChArray[i] = bp + (i*framesPerCycle);
  293. }
  294. p->callbackPtr = callbackPtr;
  295. p->cbDataPtr = cbDataPtr;
  296. p->framesPerCycle = framesPerCycle;
  297. p->srate = srate;
  298. p->cycleCnt = 0;
  299. p->nanosPerCycle = floor((double)framesPerCycle / srate * cmAfd_Billion );
  300. if( restartFl )
  301. {
  302. if((rc = cmAudioFileDevStart(h)) != kOkAfdRC )
  303. {
  304. rc = cmErrMsg(&p->err,kRestartFailAfdRC,"The audio file device could not be restarted.");
  305. }
  306. }
  307. errLabel:
  308. return rc;
  309. }
  310. const char* cmAudioFileDevLabel( cmAfdH_t h )
  311. {
  312. cmAfd_t* p = _cmAfdHandleToPtr(h);
  313. return p->label;
  314. }
  315. unsigned cmAudioFileDevChannelCount( cmAfdH_t h, bool inputFl )
  316. {
  317. cmAfd_t* p = _cmAfdHandleToPtr(h);
  318. return inputFl ? p->iPkt.chCnt : p->oPkt.chCnt;
  319. }
  320. double cmAudioFileDevSampleRate( cmAfdH_t h )
  321. {
  322. cmAfd_t* p = _cmAfdHandleToPtr(h);
  323. return p->srate;
  324. }
  325. unsigned cmAudioFileDevFramesPerCycle( cmAfdH_t h, bool inputFl )
  326. {
  327. cmAfd_t* p = _cmAfdHandleToPtr(h);
  328. return inputFl ? p->iPkt.audioFramesCnt : p->oPkt.audioFramesCnt;
  329. }
  330. cmAfdRC_t cmAudioFileDevRewind( cmAfdH_t h )
  331. {
  332. cmAfd_t* p = _cmAfdHandleToPtr(h);
  333. p->rewindFl = true;
  334. return kOkAfdRC;
  335. }
  336. cmAfdRC_t cmAudioFileDevStart( cmAfdH_t h )
  337. {
  338. cmAfdRC_t rc = kOkAfdRC;
  339. cmAfd_t* p = _cmAfdHandleToPtr(h);
  340. p->cycleCnt = 0;
  341. if( cmThreadPause( p->tH, 0 ) != kOkThRC )
  342. {
  343. rc = cmErrMsg(&p->err,kThreadFailAfdRC,"Thread start failed.");
  344. goto errLabel;
  345. }
  346. fputs("Start\n",stderr);
  347. errLabel:
  348. return rc;
  349. }
  350. cmAfdRC_t cmAudioFileDevStop( cmAfdH_t h )
  351. {
  352. cmAfdRC_t rc = kOkAfdRC;
  353. cmAfd_t* p = _cmAfdHandleToPtr(h);
  354. if( cmThreadPause( p->tH, kPauseThFl | kWaitThFl ) != kOkThRC )
  355. {
  356. rc = cmErrMsg(&p->err,kThreadFailAfdRC,"Thread stop failed.");
  357. goto errLabel;
  358. }
  359. fputs("Stop\n",stderr);
  360. errLabel:
  361. return rc;
  362. }
  363. bool cmAudioFileDevIsStarted( cmAfdH_t h )
  364. {
  365. cmAfd_t* p = _cmAfdHandleToPtr(h);
  366. return cmThreadState(p->tH) == kRunningThId;
  367. }
  368. void cmAudioFileDevReport( cmAfdH_t h, cmRpt_t* rpt )
  369. {
  370. cmAfd_t* p = _cmAfdHandleToPtr(h);
  371. cmRptPrintf(rpt,"label:%s thr state:%i srate:%f\n",p->label,cmThreadState(p->tH),p->srate);
  372. cmRptPrintf(rpt, "in chs:%i %s\n",p->iPkt.chCnt,cmAudioFileName(p->iAfH));
  373. cmRptPrintf(rpt, "out chs:%i %s\n",p->oPkt.chCnt,p->oFn);
  374. }
  375. // device callback function used with cmAudioFileDevTest() note that this assumes
  376. // that the packet buffer contain non-interleaved data.
  377. void _cmAfdCallback(
  378. cmApAudioPacket_t* inPktArray,
  379. unsigned inPktCnt,
  380. cmApAudioPacket_t* outPktArray,
  381. unsigned outPktCnt )
  382. {
  383. cmApAudioPacket_t* ip = inPktArray;
  384. cmApAudioPacket_t* op = outPktArray;
  385. unsigned opi = 0;
  386. unsigned ipi = 0;
  387. unsigned oci = 0;
  388. unsigned ici = 0;
  389. while(1)
  390. {
  391. if( ici == ip->chCnt)
  392. {
  393. ici = 0;
  394. if( ++ipi >= inPktCnt )
  395. break;
  396. ip = inPktArray + ipi;
  397. }
  398. if( oci == op->chCnt )
  399. {
  400. oci = 0;
  401. if( ++opi >= outPktCnt )
  402. break;
  403. ip = outPktArray + opi;
  404. }
  405. assert( ip->audioFramesCnt == op->audioFramesCnt );
  406. assert( cmIsFlag(ip->flags,kInterleavedApFl)==false && cmIsFlag(ip->flags,kInterleavedApFl)==false );
  407. cmApSample_t* ibp = ((cmApSample_t*)ip->audioBytesPtr) + (ip->audioFramesCnt*ici);
  408. cmApSample_t* obp = ((cmApSample_t*)op->audioBytesPtr) + (op->audioFramesCnt*oci);
  409. memcpy(obp,ibp,ip->audioFramesCnt*sizeof(cmApSample_t));
  410. ++ici;
  411. ++oci;
  412. }
  413. }
  414. void cmAudioFileDevTest( cmRpt_t* rpt )
  415. {
  416. cmAfdH_t afdH = cmAfdNullHandle;
  417. double srate = 44100;
  418. unsigned framesPerCycle = 512;
  419. void* cbDataPtr = NULL;
  420. unsigned devIdx = 0;
  421. const cmChar_t* iFn = "/home/kevin/media/audio/McGill-1/1 Audio Track.aiff";
  422. const cmChar_t* oFn = "/home/kevin/temp/afd0.aif";
  423. unsigned oBits = 16;
  424. unsigned oChCnt = 2;
  425. if( cmAudioFileDevInitialize(&afdH,"file",devIdx,iFn,oFn,oBits,oChCnt,rpt) != kOkAfdRC )
  426. goto errLabel;
  427. if( cmAudioFileDevSetup(afdH,0,srate,framesPerCycle,_cmAfdCallback,cbDataPtr) != kOkAfdRC )
  428. goto errLabel;
  429. char c;
  430. fputs("q=quit 1=start 0=stop\n",stderr);
  431. fflush(stderr);
  432. while((c=getchar()) != 'q')
  433. {
  434. switch(c)
  435. {
  436. case '1': cmAudioFileDevStart(afdH); break;
  437. case '0': cmAudioFileDevStop(afdH); break;
  438. }
  439. c = 0;
  440. fflush(stdin);
  441. }
  442. errLabel:
  443. cmAudioFileDevFinalize(&afdH);
  444. }