libcm is a C development framework with an emphasis on audio signal processing applications.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

cmAudioPortAlsa.c 51KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749
  1. #include "cmPrefix.h"
  2. #include "cmGlobal.h"
  3. #include "cmRpt.h"
  4. #include "cmTime.h"
  5. #include "cmAudioPort.h"
  6. #include "cmMem.h"
  7. #include "cmTime.h"
  8. #include "cmMallocDebug.h"
  9. #include "cmAudioPort.h"
  10. #include "cmAudioPortAlsa.h"
  11. #include "cmThread.h"
  12. #include "alsa/asoundlib.h"
  13. #include <unistd.h> // usleep
  14. #define NAME_CHAR_CNT (255)
  15. #define DESC_CHAR_CNT (255)
  16. #define INIT_DEV_CNT (5)
  17. //#define IMPULSE_FN "/home/kevin/temp/recd0.txt"
  18. enum { kDfltPeriodsPerBuf = 2, kPollfdsArrayCnt=2 };
  19. enum { kInFl=0x01, kOutFl=0x02 };
  20. struct cmApRoot_str;
  21. typedef struct devRecd_str
  22. {
  23. struct cmApRoot_str* rootPtr;
  24. unsigned devIdx;
  25. cmChar_t nameStr[ NAME_CHAR_CNT ];
  26. cmChar_t descStr[ DESC_CHAR_CNT ];
  27. unsigned flags;
  28. unsigned framesPerCycle; // samples per sub-buffer
  29. unsigned periodsPerBuf; // sub-buffers per buffer
  30. snd_async_handler_t* ahandler;
  31. unsigned srate; // device sample rate
  32. unsigned iChCnt; // ch count
  33. unsigned oChCnt;
  34. unsigned iBits; // bits per sample
  35. unsigned oBits;
  36. unsigned iSigBits; // significant bits in each sample beginning
  37. unsigned oSigBits; // with the most sig. bit.
  38. cmApSample_t* iBuf; // iBuf[ iFpc * iChCnt ]
  39. cmApSample_t* oBuf; // oBuf[ oFpc * oChCnt ]
  40. unsigned oBufCnt; // count of buffers written
  41. #ifdef IMPULSE_FN
  42. int* recdBuf;
  43. unsigned recdN;
  44. unsigned recdIdx;
  45. #endif
  46. unsigned iFpC; // buffer frames per cycle (in ALSA this is call period_size)
  47. unsigned oFpC;
  48. snd_pcm_t* iPcmH; // device handle
  49. snd_pcm_t* oPcmH;
  50. unsigned iCbCnt; // callback count
  51. unsigned oCbCnt;
  52. unsigned iErrCnt; // error count
  53. unsigned oErrCnt;
  54. cmApCallbackPtr_t cbPtr; // user callback
  55. void* userCbPtr;
  56. } cmApDevRecd_t;
  57. typedef struct cmApPoll_str
  58. {
  59. cmApDevRecd_t* devPtr;
  60. bool inputFl;
  61. unsigned fdsCnt;
  62. } cmApPollfdsDesc_t;
  63. typedef struct cmApRoot_str
  64. {
  65. cmRpt_t* rpt; //
  66. cmApDevRecd_t* devArray; // array of device records
  67. unsigned devCnt; // count of actual dev recds in devArray[]
  68. unsigned devAllocCnt; // count of dev recds allocated in devArray[]
  69. bool asyncFl; // true=use async callback false=use polling thread
  70. cmThreadH_t thH; // polling thread
  71. unsigned pollfdsAllocCnt; // 2*devCnt (max possible in+out handles)
  72. struct pollfd* pollfds; // pollfds[ pollfdsAllocCnt ]
  73. cmApPollfdsDesc_t *pollfdsDesc; // pollfdsDesc[ pollfdsAllocCnt ]
  74. unsigned pollfdsCnt; // count of active recds in pollfds[] and pollfdsDesc[]
  75. } cmApRoot_t;
  76. cmApRoot_t _cmApRoot = { NULL, NULL, 0, 0 };
  77. //===============================================================================================
  78. enum
  79. {
  80. kReadErrRId,
  81. kWriteErrRId
  82. };
  83. #undef cmALSA_RECD
  84. #ifdef cmALSA_RECD
  85. enum
  86. {
  87. kNotUsedRId,
  88. kStartRId,
  89. kCbRId,
  90. kSysErrRId,
  91. kAppErrRId,
  92. kRecoverRId
  93. };
  94. typedef struct
  95. {
  96. int code;
  97. char* label;
  98. } recdErrMap_t;
  99. typedef struct
  100. {
  101. unsigned devIdx;
  102. unsigned typeId;
  103. cmTimeSpec_t t;
  104. bool inputFl;
  105. unsigned arg;
  106. unsigned arg1;
  107. } recd;
  108. recd* recdArray = NULL;
  109. unsigned recdCnt = 0;
  110. unsigned recdIdx = 0;
  111. cmTimeSpec_t recdTime;
  112. recdErrMap_t recdSysErrMap[] =
  113. {
  114. { -EPIPE, "EPIPE" },
  115. { -ESTRPIPE, "ESTRPIPE" },
  116. { -EBADFD, "EBADFD" },
  117. { 0, NULL }
  118. };
  119. recdErrMap_t recdAppErrMap[] =
  120. {
  121. { kReadErrRId, "Read Error"},
  122. { kWriteErrRId, "Write Error"},
  123. { 0, NULL }
  124. };
  125. const char* _recdSysErrToLabel( int err )
  126. {
  127. unsigned i;
  128. for(i=0; recdSysErrMap[i].label != NULL; ++i)
  129. if( recdSysErrMap[i].code == err )
  130. return recdSysErrMap[i].label;
  131. return "<Unknown sys error>";
  132. }
  133. const char* _recdAppErrToLabel( int err )
  134. {
  135. unsigned i;
  136. for(i=0; recdAppErrMap[i].label != NULL; ++i)
  137. if( recdAppErrMap[i].code == err )
  138. return recdAppErrMap[i].label;
  139. return "<Unknown app error>";
  140. }
  141. void recdSetup()
  142. {
  143. recdCnt = 100;
  144. recdIdx = 0;
  145. clock_gettime(CLOCK_REALTIME,&recdTime);
  146. recdArray = cmMemAllocZ(recd,recdCnt);
  147. }
  148. void recdPush( unsigned typeId, unsigned devIdx, bool inputFl, unsigned arg, unsigned arg1 )
  149. {
  150. //if( recdIdx == recdCnt )
  151. // return;
  152. if( recdIdx == recdCnt )
  153. recdIdx = 0;
  154. recd* r = recdArray + recdIdx;
  155. r->typeId = typeId;
  156. r->devIdx = devIdx;
  157. r->inputFl = inputFl;
  158. r->arg = arg;
  159. r->arg1 = arg1;
  160. clock_gettime(CLOCK_REALTIME,&r->t);
  161. ++recdIdx;
  162. }
  163. void recdStart( const cmApDevRecd_t* drp, bool inputFl )
  164. { recdPush(kStartRId,drp->devIdx,inputFl,0,0); }
  165. void recdCb( const cmApDevRecd_t* drp, bool inputFl, unsigned frmCnt )
  166. { recdPush(kCbRId,drp->devIdx,inputFl, inputFl ? drp->iCbCnt : drp->oCbCnt, 0 ); }
  167. void recdSysErr( bool inputFl, int err )
  168. { recdPush(kSysErrRId,cmInvalidIdx,inputFl,err,0); }
  169. void recdAppErr( const cmApDevRecd_t* drp, bool inputFl, int app, int err )
  170. { recdPush(kAppErrRId,drp->devIdx,inputFl,app,err); }
  171. void recdRecover( const cmApDevRecd_t* drp, bool inputFl, int err, int line )
  172. { recdPush(kRecoverRId,drp->devIdx,inputFl,err,line); }
  173. void recdPrint()
  174. {
  175. unsigned i;
  176. cmTimeSpec_t t0 = recdTime;
  177. unsigned j = recdIdx;
  178. for(i=0; i<recdCnt; ++i)
  179. {
  180. recd* r = recdArray + j;
  181. ++j;
  182. if( j == recdCnt )
  183. j = 0;
  184. double ms = cmTimeElapsedMicros(&recdTime,&r->t)/1000.0;
  185. double dms = cmTimeElapsedMicros(&t0,&r->t)/1000.0;
  186. t0 = r->t;
  187. printf("%5i %8.3f %8.3f %i %s: ",i,ms,dms,r->devIdx,r->inputFl ? "in ":"out");
  188. switch(r->typeId)
  189. {
  190. case kStartRId:
  191. printf("start");
  192. break;
  193. case kCbRId:
  194. printf("callback %i",r->arg );
  195. break;
  196. case kSysErrRId:
  197. printf("sys err %s ",_recdSysErrToLabel(r->arg));
  198. break;
  199. case kAppErrRId:
  200. printf("app err %s %s",_recdAppErrToLabel(r->arg),_recdSysErrToLabel(r->arg1));
  201. break;
  202. case kRecoverRId:
  203. printf("Recover %s %i",_recdSysErrToLabel(r->arg),r->arg1);
  204. break;
  205. default:
  206. printf("Unknown recd type id.\n");
  207. break;
  208. }
  209. printf("\n");
  210. }
  211. }
  212. void recdFree()
  213. {
  214. recdPrint();
  215. cmMemFree(recdArray);
  216. }
  217. #else
  218. void recdSetup(){}
  219. void recdPush( unsigned typeId, unsigned devIdx, bool inputFl, unsigned arg, unsigned arg1 ){}
  220. void recdStart( const cmApDevRecd_t* drp, bool inputFl ){}
  221. void recdCb( const cmApDevRecd_t* drp, bool inputFl, unsigned frmCnt ){}
  222. void recdSysErr( bool inputFl, int err ){}
  223. void recdAppErr( const cmApDevRecd_t* drp, bool inputFl, int app, int err ){}
  224. void recdRecover( const cmApDevRecd_t* drp, bool inputFl, int err, int line ){}
  225. void recdPrint(){}
  226. void recdFree(){}
  227. #endif
  228. //===================================================================================================
  229. cmApRC_t _cmApOsError( cmApRoot_t* p, int err, const char* fmt, ... )
  230. {
  231. va_list vl;
  232. va_start(vl,fmt);
  233. int msgN = 255;
  234. char msg[ msgN+1];
  235. vsnprintf(msg,msgN,fmt,vl);
  236. if( err )
  237. cmRptPrintf(p->rpt,"%s ALSA Error:%s. ",msg,snd_strerror(err));
  238. else
  239. cmRptPrintf(p->rpt,"%s ",msg);
  240. cmRptPrintf(p->rpt,"\n");
  241. va_end(vl);
  242. return kSysErrApRC;
  243. }
  244. bool _cmApDevSetupError( cmApRoot_t* p, int err, bool inputFl, const cmApDevRecd_t* drp, const char* fmt, ... )
  245. {
  246. va_list vl;
  247. int msgN = 255;
  248. char msg[ msgN + 1];
  249. va_start(vl,fmt);
  250. vsnprintf(msg,msgN,fmt,vl);
  251. _cmApOsError(p,err,"%s for %s '%s' : '%s'.",msg,inputFl ? "INPUT" : "OUTPUT", drp->nameStr, drp->descStr);
  252. va_end(vl);
  253. return false;
  254. }
  255. const char* _cmApPcmStateToString( snd_pcm_state_t state )
  256. {
  257. switch( state )
  258. {
  259. case SND_PCM_STATE_OPEN: return "open";
  260. case SND_PCM_STATE_SETUP: return "setup";
  261. case SND_PCM_STATE_PREPARED: return "prepared";
  262. case SND_PCM_STATE_RUNNING: return "running";
  263. case SND_PCM_STATE_XRUN: return "xrun";
  264. case SND_PCM_STATE_DRAINING: return "draining";
  265. case SND_PCM_STATE_PAUSED: return "paused";
  266. case SND_PCM_STATE_SUSPENDED: return "suspended";
  267. case SND_PCM_STATE_DISCONNECTED: return "disconnected";
  268. }
  269. return "<invalid>";
  270. }
  271. void _cmApDevRtReport( cmRpt_t* rpt, cmApDevRecd_t* drp )
  272. {
  273. cmRptPrintf(rpt,"cb i:%i o:%i err i:%i o:%i",drp->iCbCnt,drp->oCbCnt,drp->iErrCnt,drp->oErrCnt);
  274. if( drp->iPcmH != NULL )
  275. cmRptPrintf(rpt," state i:%s",_cmApPcmStateToString(snd_pcm_state(drp->iPcmH)));
  276. if( drp->oPcmH != NULL )
  277. cmRptPrintf(rpt," o:%s",_cmApPcmStateToString(snd_pcm_state(drp->oPcmH)));
  278. cmRptPrint(rpt,"\n ");
  279. }
  280. void _cmApDevReport( cmRpt_t* rpt, cmApDevRecd_t* drp )
  281. {
  282. bool inputFl = true;
  283. snd_pcm_t* pcmH;
  284. int err;
  285. unsigned i;
  286. cmApRoot_t* p = drp->rootPtr;
  287. cmRptPrintf(rpt,"%s %s Device:%s Desc:%s\n", drp->flags & kInFl ? "IN ":"", drp->flags & kOutFl ? "OUT ":"", drp->nameStr, drp->descStr);
  288. for(i=0; i<2; i++,inputFl=!inputFl)
  289. {
  290. if( ((inputFl==true) && (drp->flags&kInFl)) || (((inputFl==false) && (drp->flags&kOutFl))))
  291. {
  292. const char* ioLabel = inputFl ? "In" : "Out";
  293. // attempt to open the sub-device
  294. if((err = snd_pcm_open(&pcmH,drp->nameStr,inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,0)) < 0 )
  295. _cmApDevSetupError(p,err,inputFl,drp,"Attempt to open the PCM handle failed");
  296. else
  297. {
  298. snd_pcm_hw_params_t* hwParams;
  299. snd_pcm_hw_params_alloca(&hwParams);
  300. memset(hwParams,0,snd_pcm_hw_params_sizeof());
  301. // load the parameter record
  302. if((err = snd_pcm_hw_params_any(pcmH,hwParams)) < 0 )
  303. _cmApDevSetupError(p,err,inputFl,drp,"Error obtaining hw param record");
  304. else
  305. {
  306. unsigned minChCnt=0,maxChCnt=0,minSrate=0,maxSrate=0;
  307. snd_pcm_uframes_t minPeriodFrmCnt=0,maxPeriodFrmCnt=0,minBufFrmCnt=0,maxBufFrmCnt=0;
  308. int dir;
  309. // extract the min channel count
  310. if((err = snd_pcm_hw_params_get_channels_min(hwParams, &minChCnt )) < 0 )
  311. _cmApDevSetupError(p,err,inputFl,drp,"Error getting min. channel count.");
  312. // extract the max channel count
  313. if((err = snd_pcm_hw_params_get_channels_max(hwParams, &maxChCnt )) < 0 )
  314. _cmApDevSetupError(p,err,inputFl,drp,"Error getting max. channel count.");
  315. // extract the min srate
  316. if((err = snd_pcm_hw_params_get_rate_min(hwParams, &minSrate,&dir )) < 0 )
  317. _cmApDevSetupError(p,err,inputFl,drp,"Error getting min. sample rate.");
  318. // extract the max srate
  319. if((err = snd_pcm_hw_params_get_rate_max(hwParams, &maxSrate,&dir )) < 0 )
  320. _cmApDevSetupError(p,err,inputFl,drp,"Error getting max. sample rate.");
  321. // extract the min period
  322. if((err = snd_pcm_hw_params_get_period_size_min(hwParams, &minPeriodFrmCnt,&dir )) < 0 )
  323. _cmApDevSetupError(p,err,inputFl,drp,"Error getting min. period frame count.");
  324. // extract the max period
  325. if((err = snd_pcm_hw_params_get_period_size_max(hwParams, &maxPeriodFrmCnt,&dir )) < 0 )
  326. _cmApDevSetupError(p,err,inputFl,drp,"Error getting max. period frame count.");
  327. // extract the min buf
  328. if((err = snd_pcm_hw_params_get_buffer_size_min(hwParams, &minBufFrmCnt )) < 0 )
  329. _cmApDevSetupError(p,err,inputFl,drp,"Error getting min. period frame count.");
  330. // extract the max buffer
  331. if((err = snd_pcm_hw_params_get_buffer_size_max(hwParams, &maxBufFrmCnt )) < 0 )
  332. _cmApDevSetupError(p,err,inputFl,drp,"Error getting max. period frame count.");
  333. cmRptPrintf(rpt,"%s chs:%i - %i srate:%i - %i period:%i - %i buf:%i - %i half duplex only:%s joint duplex:%s\n",
  334. ioLabel,minChCnt,maxChCnt,minSrate,maxSrate,minPeriodFrmCnt,maxPeriodFrmCnt,minBufFrmCnt,maxBufFrmCnt,
  335. (snd_pcm_hw_params_is_half_duplex(hwParams) ? "yes" : "no"),
  336. (snd_pcm_hw_params_is_joint_duplex(hwParams) ? "yes" : "no"));
  337. }
  338. if((err = snd_pcm_close(pcmH)) < 0)
  339. _cmApDevSetupError(p,err,inputFl,drp,"Error closing PCM handle");
  340. }
  341. }
  342. }
  343. }
  344. // Called by cmApInitialize() to append a cmApDevRecd to the _cmApRoot.devArray[].
  345. void _cmApDevAppend( cmApRoot_t* p, cmApDevRecd_t* drp )
  346. {
  347. if( p->devCnt == p->devAllocCnt )
  348. {
  349. p->devArray = cmMemResizePZ( cmApDevRecd_t, p->devArray, p->devAllocCnt + INIT_DEV_CNT );
  350. p->devAllocCnt += INIT_DEV_CNT;
  351. }
  352. drp->devIdx = p->devCnt; // set the device index
  353. drp->rootPtr = p; // set the pointer back to the root
  354. #ifdef IMPULSE_FN
  355. drp->recdN = 44100*2*2;
  356. drp->recdBuf = cmMemAllocZ(int,drp->recdN);
  357. drp->recdIdx = 0;
  358. #endif
  359. memcpy(p->devArray + p->devCnt, drp, sizeof(cmApDevRecd_t));
  360. ++p->devCnt;
  361. }
  362. cmApRC_t _cmApDevShutdown( cmApRoot_t* p, cmApDevRecd_t* drp, bool inputFl )
  363. {
  364. int err;
  365. snd_pcm_t** pcmH = inputFl ? &drp->iPcmH : &drp->oPcmH;
  366. if( *pcmH != NULL )
  367. {
  368. if((err = snd_pcm_close(*pcmH)) < 0 )
  369. {
  370. _cmApDevSetupError(p,err,inputFl,drp,"Error closing device handle.");
  371. return kSysErrApRC;
  372. }
  373. *pcmH = NULL;
  374. }
  375. return kOkApRC;
  376. }
  377. int _cmApDevOpen( snd_pcm_t** pcmHPtr, const char* devNameStr, bool inputFl )
  378. {
  379. int cnt = 0;
  380. int err;
  381. do
  382. {
  383. if((err = snd_pcm_open(pcmHPtr,devNameStr,inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,0)) < 0 )
  384. {
  385. cnt++;
  386. usleep(10000); // sleep for 10 milliseconds
  387. }
  388. }while(cnt<100 && err == -EBUSY );
  389. return err;
  390. }
  391. void _cmApXrun_recover( snd_pcm_t* pcmH, int err, cmApDevRecd_t* drp, bool inputFl, int line )
  392. {
  393. char dirCh = inputFl ? 'I' : 'O';
  394. inputFl ? drp->iErrCnt++ : drp->oErrCnt++;
  395. recdRecover(drp,inputFl,err,line);
  396. // -EPIPE signals and over/underrun (see pcm.c example xrun_recovery())
  397. switch( err )
  398. {
  399. case -EPIPE:
  400. {
  401. int silentFl = 1;
  402. if((err = snd_pcm_recover( pcmH, err, silentFl )) < 0 )
  403. _cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"recover failed.");
  404. if( inputFl )
  405. {
  406. if((err= snd_pcm_prepare(pcmH)) < 0 )
  407. _cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"re-prepare failed.");
  408. else
  409. if((err = snd_pcm_start(pcmH)) < 0 )
  410. _cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"restart failed.");
  411. }
  412. else
  413. {
  414. /*
  415. _cmApWriteBuf(pcmH, NULL, drp->oChCnt, drp->oFpC );
  416. _cmApWriteBuf(pcmH, NULL, drp->oChCnt, drp->oFpC );
  417. if((err = snd_pcm_prepare(pcmH))<0)
  418. _cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"Recovery failed.\n");
  419. else
  420. if((err = snd_pcm_resume(pcmH))<0)
  421. _cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"Resume failed.\n");
  422. */
  423. }
  424. printf("EPIPE %c %i %i %i\n",dirCh,drp->devIdx,drp->oCbCnt,line);
  425. //if((err = snd_pcm_prepare(pcmH))<0)
  426. // _devSetupError(err,inputFl,*drp,"Recovery failed.\n");
  427. //else
  428. // if((err = snd_pcm_resume(pcmH))<0)
  429. // _devSetupError(err,inputFl,*drp,"Resume failed.\n");
  430. break;
  431. }
  432. case -ESTRPIPE:
  433. {
  434. int silentFl = 1;
  435. if((err = snd_pcm_recover( pcmH, err, silentFl )) < 0 )
  436. _cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"recover failed.");
  437. printf("audio port impl ESTRPIPE:%c\n",dirCh);
  438. break;
  439. }
  440. case -EBADFD:
  441. {
  442. _cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"%s failed.",inputFl ? "Read" : "Write" );
  443. break;
  444. }
  445. default:
  446. _cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"Unknown rd/wr error.\n");
  447. } // switch
  448. }
  449. void _cmApStateRecover( snd_pcm_t* pcmH, cmApDevRecd_t* drp, bool inputFl )
  450. {
  451. int err = 0;
  452. switch( snd_pcm_state(pcmH))
  453. {
  454. case SND_PCM_STATE_XRUN:
  455. err = -EPIPE;
  456. break;
  457. case SND_PCM_STATE_SUSPENDED:
  458. err = -ESTRPIPE;
  459. break;
  460. case SND_PCM_STATE_OPEN:
  461. case SND_PCM_STATE_SETUP:
  462. case SND_PCM_STATE_PREPARED:
  463. case SND_PCM_STATE_RUNNING:
  464. case SND_PCM_STATE_DRAINING:
  465. case SND_PCM_STATE_PAUSED:
  466. case SND_PCM_STATE_DISCONNECTED:
  467. //case SND_PCM_STATE_LAST:
  468. break;
  469. }
  470. if( err < 0 )
  471. _cmApXrun_recover( pcmH, err, drp, inputFl, __LINE__ );
  472. }
  473. // Returns count of frames written on success or < 0 on error;
  474. // set smpPtr to NULL to write a buffer of silence
  475. int _cmApWriteBuf( cmApDevRecd_t* drp, snd_pcm_t* pcmH, const cmApSample_t* sp, unsigned chCnt, unsigned frmCnt, unsigned bits, unsigned sigBits )
  476. {
  477. int err = 0;
  478. unsigned bytesPerSmp = (bits==24 ? 32 : bits)/8;
  479. unsigned smpCnt = chCnt * frmCnt;
  480. unsigned byteCnt = bytesPerSmp * smpCnt;
  481. const cmApSample_t* ep = sp + smpCnt;
  482. char obuf[ byteCnt ];
  483. // if no output was given then fill the device buffer with zeros
  484. if( sp == NULL )
  485. memset(obuf,0,byteCnt);
  486. else
  487. {
  488. // otherwise convert the floating point samples to integers
  489. switch( bits )
  490. {
  491. case 8:
  492. {
  493. char* dp = (char*)obuf;
  494. while( sp < ep )
  495. *dp++ = (char)(*sp++ * 0x7f);
  496. }
  497. break;
  498. case 16:
  499. {
  500. short* dp = (short*)obuf;
  501. while( sp < ep )
  502. *dp++ = (short)(*sp++ * 0x7fff);
  503. }
  504. break;
  505. case 24:
  506. {
  507. int* dp = (int*)obuf;
  508. while( sp < ep )
  509. *dp++ = (int)(*sp++ * 0x7fffff);
  510. }
  511. break;
  512. case 32:
  513. {
  514. int* dp = (int*)obuf;
  515. while( sp < ep )
  516. *dp++ = (int)(*sp++ * 0x7fffffff);
  517. #ifdef IMPLUSE_FN
  518. int* tmp = (int*)obuf;
  519. unsigned ii = 0;
  520. if( drp->oBufCnt < 3 )
  521. for(; ii<32; ++ii)
  522. tmp[ii] = 0x7fffffff;
  523. #endif
  524. }
  525. break;
  526. }
  527. }
  528. // send the bytes to the device
  529. err = snd_pcm_writei( pcmH, obuf, frmCnt );
  530. ++drp->oBufCnt;
  531. if( err < 0 )
  532. {
  533. recdAppErr(drp,false,kWriteErrRId,err);
  534. _cmApDevSetupError(drp->rootPtr, err, false, drp, "ALSA write error" );
  535. }
  536. else
  537. if( err > 0 && err != frmCnt )
  538. {
  539. _cmApDevSetupError(drp->rootPtr, 0, false, drp, "Actual count of bytes written did not match the count provided." );
  540. }
  541. return err;
  542. }
  543. // Returns frames read on success or < 0 on error.
  544. // Set smpPtr to NULL to read the incoming buffer and discard it
  545. int _cmApReadBuf( cmApDevRecd_t* drp, snd_pcm_t* pcmH, cmApSample_t* smpPtr, unsigned chCnt, unsigned frmCnt, unsigned bits, unsigned sigBits )
  546. {
  547. int err = 0;
  548. unsigned bytesPerSmp = (bits==24 ? 32 : bits)/8;
  549. unsigned smpCnt = chCnt * frmCnt;
  550. unsigned byteCnt = smpCnt * bytesPerSmp;
  551. char buf[ byteCnt ];
  552. // get the incoming samples into buf[] ...
  553. err = snd_pcm_readi(pcmH,buf,frmCnt);
  554. // if a read error occurred
  555. if( err < 0 )
  556. {
  557. recdAppErr(drp,true,kReadErrRId,err);
  558. _cmApDevSetupError(drp->rootPtr, err, false, drp, "ALSA read error" );
  559. }
  560. else
  561. if( err > 0 && err != frmCnt )
  562. {
  563. _cmApDevSetupError(drp->rootPtr, 0, false, drp, "Actual count of bytes read did not match the count requested." );
  564. }
  565. // if no buffer was given then there is nothing else to do
  566. if( smpPtr == NULL )
  567. return err;
  568. // setup the return buffer
  569. cmApSample_t* dp = smpPtr;
  570. cmApSample_t* ep = dp + cmMin(smpCnt,err*chCnt);
  571. switch(bits)
  572. {
  573. case 8:
  574. {
  575. char* sp = buf;
  576. while(dp < ep)
  577. *dp++ = ((cmApSample_t)*sp++) / 0x7f;
  578. }
  579. break;
  580. case 16:
  581. {
  582. short* sp = (short*)buf;
  583. while(dp < ep)
  584. *dp++ = ((cmApSample_t)*sp++) / 0x7fff;
  585. }
  586. break;
  587. case 24:
  588. {
  589. int* sp = (int*)buf;
  590. while(dp < ep)
  591. *dp++ = ((cmApSample_t)*sp++) / 0x7fffff;
  592. }
  593. break;
  594. case 32:
  595. {
  596. int* sp = (int*)buf;
  597. // The delta1010 (ICE1712) uses only the 24 highest bits according to
  598. //
  599. // http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html
  600. // <snip> The example: ICE1712 chips support 32-bit sample processing,
  601. // but low byte is ignored (playback) or zero (capture).
  602. //
  603. int mv = sigBits==24 ? 0x7fffff00 : 0x7fffffff;
  604. while(dp < ep)
  605. *dp++ = ((cmApSample_t)*sp++) / mv;
  606. #ifdef IMPULSE_FN
  607. sp = (int*)buf;
  608. int* esp = sp + smpCnt;
  609. for(; sp<esp && drp->recdIdx < drp->recdN; ++drp->recdIdx)
  610. drp->recdBuf[drp->recdIdx] = *sp++;
  611. #endif
  612. }
  613. break;
  614. default:
  615. { assert(0); }
  616. }
  617. return err;
  618. }
  619. void _cmApStaticAsyncHandler( snd_async_handler_t* ahandler )
  620. {
  621. int err;
  622. snd_pcm_sframes_t avail;
  623. cmApDevRecd_t* drp = (cmApDevRecd_t*)snd_async_handler_get_callback_private(ahandler);
  624. snd_pcm_t* pcmH = snd_async_handler_get_pcm(ahandler);
  625. bool inputFl = snd_pcm_stream(pcmH) == SND_PCM_STREAM_CAPTURE;
  626. cmApSample_t* b = inputFl ? drp->iBuf : drp->oBuf;
  627. unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
  628. unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC;
  629. cmApAudioPacket_t pkt;
  630. inputFl ? drp->iCbCnt++ : drp->oCbCnt++;
  631. pkt.devIdx = drp->devIdx;
  632. pkt.begChIdx = 0;
  633. pkt.chCnt = chCnt;
  634. pkt.audioFramesCnt = frmCnt;
  635. pkt.bitsPerSample = 32;
  636. pkt.flags = kInterleavedApFl | kFloatApFl;
  637. pkt.audioBytesPtr = b;
  638. pkt.userCbPtr = drp->userCbPtr;
  639. recdCb(drp,inputFl,0);
  640. _cmApStateRecover( pcmH, drp, inputFl );
  641. while( (avail = snd_pcm_avail_update(pcmH)) >= (snd_pcm_sframes_t)frmCnt )
  642. {
  643. // Handle inpuut
  644. if( inputFl )
  645. {
  646. // read samples from the device
  647. if((err = _cmApReadBuf(drp,pcmH,drp->iBuf,chCnt,frmCnt,drp->iBits,drp->oBits)) > 0 )
  648. {
  649. pkt.audioFramesCnt = err;
  650. drp->cbPtr(&pkt,1,NULL,0 ); // send the samples to the application
  651. }
  652. }
  653. // Handle output
  654. else
  655. {
  656. // callback to fill the buffer
  657. drp->cbPtr(NULL,0,&pkt,1);
  658. // note that the application may return fewer samples than were requested
  659. err = _cmApWriteBuf(drp, pcmH, pkt.audioFramesCnt < frmCnt ? NULL : drp->oBuf,chCnt,frmCnt,drp->oBits,drp->oSigBits);
  660. }
  661. // Handle read/write errors
  662. if( err < 0 )
  663. {
  664. inputFl ? drp->iErrCnt++ : drp->oErrCnt++;
  665. _cmApXrun_recover( pcmH, err, drp, inputFl, __LINE__ );
  666. }
  667. else
  668. {
  669. // _cmApStateRecover( pcmH, drp, inputFl );
  670. }
  671. } // while
  672. }
  673. bool _cmApThreadFunc(void* param)
  674. {
  675. cmApRoot_t* p = (cmApRoot_t*)param;
  676. int result;
  677. bool retFl = true;
  678. switch( result = poll(p->pollfds, p->pollfdsCnt, 250) )
  679. {
  680. case 0:
  681. // time out
  682. break;
  683. case -1:
  684. _cmApOsError(p,errno,"Poll fail.");
  685. break;
  686. default:
  687. {
  688. assert( result > 0 );
  689. unsigned i;
  690. // for each i/o stream
  691. for(i=0; i<p->pollfdsCnt; ++i)
  692. {
  693. cmApDevRecd_t* drp = p->pollfdsDesc[i].devPtr;
  694. bool inputFl = p->pollfdsDesc[i].inputFl;
  695. snd_pcm_t* pcmH = inputFl ? drp->iPcmH : drp->oPcmH;
  696. unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
  697. unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC;
  698. cmApSample_t* b = inputFl ? drp->iBuf : drp->oBuf;
  699. unsigned short revents = 0;
  700. int err;
  701. cmApAudioPacket_t pkt;
  702. snd_pcm_uframes_t avail_frames;
  703. inputFl ? drp->iCbCnt++ : drp->oCbCnt++;
  704. pkt.devIdx = drp->devIdx;
  705. pkt.begChIdx = 0;
  706. pkt.chCnt = chCnt;
  707. pkt.audioFramesCnt = frmCnt;
  708. pkt.bitsPerSample = 32;
  709. pkt.flags = kInterleavedApFl | kFloatApFl;
  710. pkt.audioBytesPtr = b;
  711. pkt.userCbPtr = drp->userCbPtr;
  712. inputFl ? drp->iCbCnt++ : drp->oCbCnt++;
  713. // get the timestamp for this buffer
  714. if((err = snd_pcm_htimestamp(pcmH,&avail_frames,&pkt.timeStamp)) != 0 )
  715. {
  716. _cmApDevSetupError(p, err, p->pollfdsDesc[i].inputFl, drp, "Get timestamp error.");
  717. pkt.timeStamp.tv_sec = 0;
  718. pkt.timeStamp.tv_nsec = 0;
  719. }
  720. // Note that based on experimenting with the timestamp and the current
  721. // clock_gettime(CLOCK_MONOTONIC) time it appears that the time stamp
  722. // marks the end of the current buffer - so in fact the time stamp should
  723. // be backed up by the availble sample count period to get the time of the
  724. // first sample in the buffer
  725. /*
  726. unsigned avail_nano_secs = (unsigned)(avail_frames * (1000000000.0/drp->srate));
  727. if( pkt.timeStamp.tv_nsec > avail_nano_secs )
  728. pkt.timeStamp.tv_nsec -= avail_nano_secs;
  729. else
  730. {
  731. pkt.timeStamp.tv_sec -= 1;
  732. pkt.timeStamp.tv_nsec = 1000000000 - avail_nano_secs;
  733. }
  734. */
  735. //printf("AUDI: %ld %ld\n",pkt.timeStamp.tv_sec,pkt.timeStamp.tv_nsec);
  736. //cmTimeSpec_t t;
  737. //clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&t);
  738. //printf("AUDI: %ld %ld\n",t.tv_sec,t.tv_nsec);
  739. switch( snd_pcm_state(pcmH) )
  740. {
  741. case SND_PCM_STATE_OPEN:
  742. case SND_PCM_STATE_SETUP:
  743. case SND_PCM_STATE_PREPARED:
  744. case SND_PCM_STATE_DRAINING:
  745. case SND_PCM_STATE_PAUSED:
  746. case SND_PCM_STATE_DISCONNECTED:
  747. continue;
  748. case SND_PCM_STATE_RUNNING:
  749. case SND_PCM_STATE_XRUN:
  750. case SND_PCM_STATE_SUSPENDED:
  751. break;
  752. }
  753. if(( err = snd_pcm_poll_descriptors_revents(pcmH, p->pollfds + i, 1 , &revents)) != 0 )
  754. {
  755. _cmApDevSetupError(p, err, p->pollfdsDesc[i].inputFl, drp, "Return poll events failed.");
  756. retFl = false;
  757. goto errLabel;
  758. }
  759. if(revents & POLLERR)
  760. {
  761. _cmApDevSetupError(p, err, p->pollfdsDesc[i].inputFl, drp, "Poll error.");
  762. _cmApStateRecover( pcmH, drp, inputFl );
  763. //goto errLabel;
  764. }
  765. if( inputFl && (revents & POLLIN) )
  766. {
  767. if((err = _cmApReadBuf(drp,pcmH,drp->iBuf,chCnt,frmCnt,drp->iBits,drp->oBits)) > 0 )
  768. {
  769. pkt.audioFramesCnt = err;
  770. drp->cbPtr(&pkt,1,NULL,0 ); // send the samples to the application
  771. }
  772. }
  773. if( !inputFl && (revents & POLLOUT) )
  774. {
  775. /*
  776. unsigned srate = 96;
  777. cmTimeSpec_t t1;
  778. static cmTimeSpec_t t0 = {0,0};
  779. clock_gettime(CLOCK_MONOTONIC,&t1);
  780. // time since the time-stamp was generated
  781. unsigned smp = (srate * (t1.tv_nsec - pkt.timeStamp.tv_nsec)) / 1000000;
  782. // time since the last output buffer was sent
  783. unsigned dsmp = (srate * (t1.tv_nsec - t0.tv_nsec)) / 1000000;
  784. printf("%i %ld %i : %ld %ld -> %ld %ld\n",smp,avail_frames,dsmp,pkt.timeStamp.tv_sec,pkt.timeStamp.tv_nsec,t1.tv_sec,t1.tv_nsec);
  785. t0 = t1;
  786. */
  787. // callback to fill the buffer
  788. drp->cbPtr(NULL,0,&pkt,1);
  789. // note that the application may return fewer samples than were requested
  790. err = _cmApWriteBuf(drp, pcmH, pkt.audioFramesCnt < frmCnt ? NULL : drp->oBuf,chCnt,frmCnt,drp->oBits,drp->oSigBits);
  791. }
  792. }
  793. }
  794. }
  795. errLabel:
  796. return retFl;
  797. }
  798. bool _cmApDevSetup( cmApDevRecd_t *drp, unsigned srate, unsigned framesPerCycle, unsigned periodsPerBuf )
  799. {
  800. int err;
  801. int dir;
  802. unsigned i;
  803. bool retFl = true;
  804. bool inputFl = true;
  805. snd_pcm_uframes_t periodFrameCnt = framesPerCycle;
  806. snd_pcm_uframes_t bufferFrameCnt;
  807. unsigned bits = 0;
  808. int sig_bits = 0;
  809. cmApRoot_t* p = drp->rootPtr;
  810. // setup input, then output device
  811. for(i=0; i<2; i++,inputFl=!inputFl)
  812. {
  813. unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
  814. snd_pcm_uframes_t actFpC = 0;
  815. // if this is the in/out pass and the in/out flag is set
  816. if( ((inputFl==true) && (drp->flags & kInFl)) || ((inputFl==false) && (drp->flags & kOutFl)) )
  817. {
  818. snd_pcm_t* pcmH = NULL;
  819. if( _cmApDevShutdown(p, drp, inputFl ) != kOkApRC )
  820. retFl = false;
  821. // attempt to open the sub-device
  822. if((err = snd_pcm_open(&pcmH,drp->nameStr, inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0)) < 0 )
  823. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Unable to open the PCM handle");
  824. else
  825. {
  826. snd_pcm_hw_params_t* hwParams;
  827. snd_pcm_sw_params_t* swParams;
  828. // prepare the hwParam recd
  829. snd_pcm_hw_params_alloca(&hwParams);
  830. memset(hwParams,0,snd_pcm_hw_params_sizeof());
  831. // load the hw parameter record
  832. if((err = snd_pcm_hw_params_any(pcmH,hwParams)) < 0 )
  833. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error obtaining hw param record");
  834. else
  835. {
  836. if((err = snd_pcm_hw_params_set_rate_resample(pcmH,hwParams,0)) < 0 )
  837. retFl = _cmApDevSetupError(p,err,inputFl, drp,"Unable to disable the ALSA sample rate converter.");
  838. if((err = snd_pcm_hw_params_set_channels(pcmH,hwParams,chCnt)) < 0 )
  839. retFl = _cmApDevSetupError(p,err,inputFl, drp,"Unable to set channel count to: %i",chCnt);
  840. if((err = snd_pcm_hw_params_set_rate(pcmH,hwParams,srate,0)) < 0 )
  841. retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to set sample rate to: %i",srate);
  842. if((err = snd_pcm_hw_params_set_access(pcmH,hwParams,SND_PCM_ACCESS_RW_INTERLEAVED )) < 0 )
  843. retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to set access to: RW Interleaved");
  844. // select the widest possible sample width
  845. if((err = snd_pcm_hw_params_set_format(pcmH,hwParams,SND_PCM_FORMAT_S32)) >= 0 )
  846. bits = 32;
  847. else
  848. {
  849. if((err = snd_pcm_hw_params_set_format(pcmH,hwParams,SND_PCM_FORMAT_S24)) >= 0 )
  850. bits = 24;
  851. else
  852. {
  853. if((err = snd_pcm_hw_params_set_format(pcmH,hwParams,SND_PCM_FORMAT_S16)) >= 0 )
  854. bits = 16;
  855. else
  856. retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to set format to: S16");
  857. }
  858. }
  859. sig_bits = snd_pcm_hw_params_get_sbits(hwParams);
  860. snd_pcm_uframes_t ps_min,ps_max;
  861. if((err = snd_pcm_hw_params_get_period_size_min(hwParams,&ps_min,NULL)) < 0 )
  862. retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to get the minimum period size.");
  863. if((err = snd_pcm_hw_params_get_period_size_max(hwParams,&ps_max,NULL)) < 0 )
  864. retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to get the maximum period size.");
  865. if( periodFrameCnt < ps_min )
  866. periodFrameCnt = ps_min;
  867. else
  868. if( periodFrameCnt > ps_max )
  869. periodFrameCnt = ps_max;
  870. if((err = snd_pcm_hw_params_set_period_size_near(pcmH,hwParams,&periodFrameCnt,NULL)) < 0 )
  871. retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to set period to %i.",periodFrameCnt);
  872. bufferFrameCnt = periodFrameCnt * periodsPerBuf + 1;
  873. if((err = snd_pcm_hw_params_set_buffer_size_near(pcmH,hwParams,&bufferFrameCnt)) < 0 )
  874. retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to set buffer to %i.",bufferFrameCnt);
  875. // Note: snd_pcm_hw_params() automatically calls snd_pcm_prepare()
  876. if((err = snd_pcm_hw_params(pcmH,hwParams)) < 0 )
  877. retFl = _cmApDevSetupError(p,err,inputFl, drp, "Parameter application failed.");
  878. //_reportActualParams( hwParams, inputFl, dr, srate, periodFrameCnt, bufferFrameCnt );
  879. }
  880. // prepare the sw param recd
  881. snd_pcm_sw_params_alloca(&swParams);
  882. memset(swParams,0,snd_pcm_sw_params_sizeof());
  883. // load the sw param recd
  884. if((err = snd_pcm_sw_params_current(pcmH,swParams)) < 0 )
  885. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error obtaining sw param record.");
  886. else
  887. {
  888. if((err = snd_pcm_sw_params_set_start_threshold(pcmH,swParams, inputFl ? 0x7fffffff : periodFrameCnt)) < 0 )
  889. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error seting the start threshold.");
  890. // setting the stop-threshold to twice the buffer frame count is intended to stop spurious
  891. // XRUN states - it will also mean that there will have no direct way of knowing about a
  892. // in/out buffer over/under run.
  893. if((err = snd_pcm_sw_params_set_stop_threshold(pcmH,swParams,bufferFrameCnt*2)) < 0 )
  894. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error setting the stop threshold.");
  895. if((err = snd_pcm_sw_params_set_avail_min(pcmH,swParams,periodFrameCnt)) < 0 )
  896. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error setting the avail. min. setting.");
  897. if((err = snd_pcm_sw_params_set_tstamp_mode(pcmH,swParams,SND_PCM_TSTAMP_MMAP)) < 0 )
  898. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error setting the time samp mode.");
  899. if((err = snd_pcm_sw_params(pcmH,swParams)) < 0 )
  900. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error applying sw params.");
  901. }
  902. // setup the callback
  903. if( p->asyncFl )
  904. if((err = snd_async_add_pcm_handler(&drp->ahandler,pcmH,_cmApStaticAsyncHandler, drp )) < 0 )
  905. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error assigning callback handler.");
  906. // get the actual frames per cycle
  907. if((err = snd_pcm_hw_params_get_period_size(hwParams,&actFpC,&dir)) < 0 )
  908. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Unable to get the actual period.");
  909. // store the device handle
  910. if( inputFl )
  911. {
  912. drp->iBits = bits;
  913. drp->iSigBits = sig_bits;
  914. drp->iPcmH = pcmH;
  915. drp->iBuf = cmMemResizeZ( cmApSample_t, drp->iBuf, actFpC * drp->iChCnt );
  916. drp->iFpC = actFpC;
  917. }
  918. else
  919. {
  920. drp->oBits = bits;
  921. drp->oSigBits = sig_bits;
  922. drp->oPcmH = pcmH;
  923. drp->oBuf = cmMemResizeZ( cmApSample_t, drp->oBuf, actFpC * drp->oChCnt );
  924. drp->oFpC = actFpC;
  925. }
  926. if( p->asyncFl == false )
  927. {
  928. assert( p->pollfdsCnt < p->pollfdsAllocCnt );
  929. unsigned incrFdsCnt = 0;
  930. unsigned fdsCnt = 0;
  931. // locate the pollfd associated with this device/direction
  932. unsigned j;
  933. for(j=0; j<p->pollfdsCnt; j+=p->pollfdsDesc[j].fdsCnt)
  934. if( p->pollfdsDesc[j].devPtr == drp && inputFl == p->pollfdsDesc[j].inputFl )
  935. break;
  936. // get the count of descriptors for this device/direction
  937. fdsCnt = snd_pcm_poll_descriptors_count(pcmH);
  938. // if the device was not found
  939. if( j == p->pollfdsCnt )
  940. {
  941. j = p->pollfdsCnt;
  942. incrFdsCnt = fdsCnt;
  943. // if the pollfds[] needs more memroy
  944. if( p->pollfdsCnt + fdsCnt > p->pollfdsAllocCnt )
  945. {
  946. p->pollfds = cmMemResizePZ(struct pollfd, p->pollfds, p->pollfdsCnt + fdsCnt );
  947. p->pollfdsDesc = cmMemResizePZ(cmApPollfdsDesc_t, p->pollfdsDesc, p->pollfdsCnt + fdsCnt );
  948. p->pollfdsAllocCnt += fdsCnt;
  949. }
  950. }
  951. // get the poll descriptors for this device/dir
  952. if( snd_pcm_poll_descriptors(pcmH,p->pollfds + j,fdsCnt) != 1 )
  953. retFl = _cmApDevSetupError(p,0,inputFl,drp,"Poll descriptor assignment failed.");
  954. else
  955. {
  956. // store the desc. record assicoated with the poll descriptor
  957. p->pollfdsDesc[ j ].fdsCnt = fdsCnt;
  958. p->pollfdsDesc[ j ].devPtr = drp;
  959. p->pollfdsDesc[ j ].inputFl = inputFl;
  960. }
  961. p->pollfdsCnt += incrFdsCnt;
  962. }
  963. printf("%s %s period:%i %i buffer:%i bits:%i sig_bits:%i\n",inputFl?"in ":"out",drp->nameStr,(unsigned)periodFrameCnt,(unsigned)actFpC,(unsigned)bufferFrameCnt,bits,sig_bits);
  964. }
  965. //_dumpAlsaDevice(pcmH);
  966. } // end if
  967. } // end for
  968. return retFl;
  969. }
  970. #ifdef NOTDEF
  971. #define TRY(e) while(e<0){ printf("LINE:%i ALSA ERROR:%s\n",__LINE__,snd_strerror(e)); exit(EXIT_FAILURE); }
  972. void open_device( const char* device_name, bool inputFl )
  973. {
  974. snd_pcm_t *pcm_handle = NULL;
  975. snd_pcm_hw_params_t *hw_params;
  976. snd_pcm_uframes_t bs_min,bs_max,ps_min,ps_max;
  977. unsigned rt_min,rt_max,ch_min,ch_max;
  978. const char* ioLabel = inputFl ? "in" : "out";
  979. // Open the device
  980. TRY( snd_pcm_open (&pcm_handle, device_name, inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0));
  981. TRY( snd_pcm_hw_params_malloc (&hw_params) );
  982. TRY( snd_pcm_hw_params_any (pcm_handle, hw_params));
  983. TRY( snd_pcm_hw_params_test_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE));
  984. // get the sample rate range
  985. TRY(snd_pcm_hw_params_get_rate_min(hw_params,&rt_min,NULL));
  986. TRY(snd_pcm_hw_params_get_rate_max(hw_params,&rt_max,NULL));
  987. TRY(snd_pcm_hw_params_get_channels_min(hw_params,&ch_min));
  988. TRY(snd_pcm_hw_params_get_channels_max(hw_params,&ch_max));
  989. // set the basic device format - setting the format may influence the size of the possible
  990. // buffer and period size
  991. //TRY( snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED));
  992. //TRY( snd_pcm_hw_params_set_format (pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE));
  993. //TRY( snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &srate, NULL));
  994. //TRY( snd_pcm_hw_params_set_channels(pcm_handle, hw_params, ch_cnt));
  995. // get the range of possible buffer and period sizes
  996. TRY(snd_pcm_hw_params_get_buffer_size_min(hw_params,&bs_min));
  997. TRY(snd_pcm_hw_params_get_buffer_size_max(hw_params,&bs_max));
  998. TRY(snd_pcm_hw_params_get_period_size_min(hw_params,&ps_min,NULL));
  999. TRY(snd_pcm_hw_params_get_period_size_max(hw_params,&ps_max,NULL));
  1000. //printf("%s %s bs min:%u max:%u ps min:%u max:%u rate min:%u max:%u ch min:%u max:%u\n",device_name,ioLabel,bs_min,bs_max,ps_min,ps_max,rt_min,rt_max,ch_min,ch_max);
  1001. printf("%s %s rate min:%u max:%u ch min:%u max:%u\n",device_name,ioLabel,rt_min,rt_max,ch_min,ch_max);
  1002. snd_pcm_hw_params_free(hw_params);
  1003. snd_pcm_close(pcm_handle);
  1004. }
  1005. #endif
  1006. cmApRC_t cmApAlsaInitialize( cmRpt_t* rpt, unsigned baseApDevIdx )
  1007. {
  1008. cmApRC_t rc = kOkApRC;
  1009. int err;
  1010. int cardNum = -1;
  1011. if((rc = cmApAlsaFinalize()) != kOkApRC )
  1012. return rc;
  1013. recdSetup();
  1014. cmApRoot_t* p = &_cmApRoot;
  1015. memset(p,0,sizeof(cmApRoot_t));
  1016. p->rpt = rpt;
  1017. p->asyncFl = false;
  1018. // for each sound card
  1019. while(1)
  1020. {
  1021. snd_ctl_t* cardH;
  1022. char* cardNamePtr = NULL;
  1023. char* cardLongNamePtr = NULL;
  1024. int devNum = -1;
  1025. int devStrN = 31;
  1026. char devStr[devStrN+1];
  1027. // get the next card handle
  1028. if((err = snd_card_next(&cardNum)) < 0 )
  1029. {
  1030. _cmApOsError(p,err,"Error getting sound card handle");
  1031. return kSysErrApRC;
  1032. }
  1033. // if no more card's to get
  1034. if( cardNum < 0 )
  1035. break;
  1036. // get the card short name
  1037. if(((err = snd_card_get_name(cardNum,&cardNamePtr)) < 0) || (cardNamePtr == NULL))
  1038. {
  1039. _cmApOsError(p,err,"Unable to get card name for card number %i\n",cardNum);
  1040. goto releaseCard;
  1041. }
  1042. // get the card long name
  1043. if((err = snd_card_get_longname(cardNum,&cardLongNamePtr)) < 0 || cardLongNamePtr == NULL )
  1044. {
  1045. _cmApOsError(p,err,"Unable to get long card name for card number %i\n",cardNum);
  1046. goto releaseCard;
  1047. }
  1048. // form the device name for this card
  1049. if(snprintf(devStr,devStrN,"hw:%i",cardNum) > devStrN )
  1050. {
  1051. _cmApOsError(p,0,"Device name is too long for buffer.");
  1052. goto releaseCard;
  1053. }
  1054. // open the card device driver
  1055. if((err = snd_ctl_open(&cardH, devStr, 0)) < 0 )
  1056. {
  1057. _cmApOsError(p,err,"Error opening sound card %i.",cardNum);
  1058. goto releaseCard;
  1059. }
  1060. // for each device on this card
  1061. while(1)
  1062. {
  1063. snd_pcm_info_t* info;
  1064. int subDevCnt = 1;
  1065. int i,j;
  1066. // get the next device on this card
  1067. if((err = snd_ctl_pcm_next_device(cardH,&devNum)) < 0 )
  1068. {
  1069. _cmApOsError(p,err,"Error gettign next device on card %i",cardNum);
  1070. break;
  1071. }
  1072. // if no more devices to get
  1073. if( devNum < 0 )
  1074. break;
  1075. // allocate a pcmInfo record
  1076. snd_pcm_info_alloca(&info);
  1077. memset(info, 0, snd_pcm_info_sizeof());
  1078. // set the device to query
  1079. snd_pcm_info_set_device(info, devNum );
  1080. for(i=0; i<subDevCnt; i++)
  1081. {
  1082. cmApDevRecd_t dr;
  1083. bool inputFl = false;
  1084. memset(&dr,0,sizeof(dr));
  1085. for(j=0; j<2; j++,inputFl=!inputFl)
  1086. {
  1087. snd_pcm_t* pcmH = NULL;
  1088. dr.devIdx = -1;
  1089. // set the subdevice and I/O direction to query
  1090. snd_pcm_info_set_subdevice(info,i);
  1091. snd_pcm_info_set_stream(info,inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
  1092. // if this device does not use this sub-device
  1093. if((err = snd_ctl_pcm_info(cardH,info)) < 0 )
  1094. continue;
  1095. // get the count of subdevices this device uses
  1096. if(i == 0 )
  1097. subDevCnt = snd_pcm_info_get_subdevices_count(info);
  1098. // if this device has no sub-devices
  1099. if(subDevCnt == 0 )
  1100. continue;
  1101. // form the device name and desc. string
  1102. if(strlen(dr.nameStr) == 0)
  1103. snprintf(dr.nameStr,NAME_CHAR_CNT,"hw:%i,%i,%i",cardNum,devNum,i);
  1104. if(strlen(dr.descStr) == 0)
  1105. {
  1106. snprintf(dr.descStr,DESC_CHAR_CNT,"%s %s",cardNamePtr,snd_pcm_info_get_name(info));
  1107. //snprintf(dr.descStr,DESC_CHAR_CNT,"Name:%s Card:[%s] [%s] Device:%s Subdevice:%s",dr.nameStr, cardNamePtr,cardLongNamePtr,snd_pcm_info_get_id(info),snd_pcm_info_get_name(info));
  1108. }
  1109. // attempt to open the sub-device
  1110. if((err = _cmApDevOpen(&pcmH,dr.nameStr,inputFl)) < 0 )
  1111. _cmApDevSetupError(p,err,inputFl,&dr,"Unable to open the PCM handle");
  1112. else
  1113. {
  1114. snd_pcm_hw_params_t* hwParams;
  1115. // allocate the parameter record
  1116. snd_pcm_hw_params_alloca(&hwParams);
  1117. memset( hwParams,0,snd_pcm_hw_params_sizeof());
  1118. // load the parameter record
  1119. if((err = snd_pcm_hw_params_any(pcmH,hwParams)) < 0 )
  1120. _cmApDevSetupError(p,err,inputFl,&dr,"Error obtaining hw param record");
  1121. else
  1122. {
  1123. unsigned* chCntPtr = inputFl ? &dr.iChCnt : &dr.oChCnt;
  1124. unsigned rate;
  1125. snd_pcm_hw_params_get_rate_max(hwParams,&rate,NULL);
  1126. // extract the channel count
  1127. if((err = snd_pcm_hw_params_get_channels_max(hwParams, chCntPtr )) < 0 )
  1128. _cmApDevSetupError(p,err,inputFl,&dr,"Error getting channel count.");
  1129. else
  1130. // this device uses this subdevice in the current direction
  1131. dr.flags += inputFl ? kInFl : kOutFl;
  1132. printf("%s in:%i chs:%i rate:%i\n",dr.nameStr,inputFl,*chCntPtr,rate);
  1133. }
  1134. // close the sub-device
  1135. snd_pcm_close(pcmH);
  1136. }
  1137. } // in/out loop
  1138. // insert the device in the device array
  1139. if( dr.flags != 0 )
  1140. _cmApDevAppend(p,&dr);
  1141. } // sub-dev loop
  1142. } // device loop
  1143. releaseCard:
  1144. snd_ctl_close(cardH);
  1145. } // card loop
  1146. if( rc == kOkApRC && p->asyncFl==false )
  1147. {
  1148. p->pollfdsCnt = 0;
  1149. p->pollfdsAllocCnt = 2*p->devCnt;
  1150. p->pollfds = cmMemAllocZ(struct pollfd, p->pollfdsAllocCnt );
  1151. p->pollfdsDesc = cmMemAllocZ(cmApPollfdsDesc_t, p->pollfdsAllocCnt );
  1152. if( cmThreadCreate(&p->thH,_cmApThreadFunc,p,rpt) != kOkThRC )
  1153. {
  1154. _cmApOsError(p,0,"Thread create failed.");
  1155. rc = kThreadFailApRC;
  1156. }
  1157. }
  1158. return rc;
  1159. }
  1160. cmApRC_t cmApAlsaFinalize()
  1161. {
  1162. cmApRoot_t* p = &_cmApRoot;
  1163. int i;
  1164. cmApRC_t rc = kOkApRC;
  1165. if( p->asyncFl==false && cmThreadIsValid(p->thH) )
  1166. if( cmThreadDestroy(&p->thH) != kOkThRC )
  1167. {
  1168. _cmApOsError(p,0,"Thread destroy failed.");
  1169. rc = kThreadFailApRC;
  1170. }
  1171. for(i=0; i<p->devCnt; ++i)
  1172. {
  1173. _cmApDevShutdown(p,p->devArray+i,true);
  1174. _cmApDevShutdown(p,p->devArray+i,false);
  1175. #ifdef IMPULSE_FN
  1176. if( p->devArray[i].recdIdx > 0 )
  1177. {
  1178. const char* fn = "/home/kevin/temp/recd0.txt";
  1179. FILE* fp = fopen(fn,"wt");
  1180. if( fp != NULL )
  1181. {
  1182. unsigned j;
  1183. for(j=0; j<p->devArray[i].recdIdx; ++j)
  1184. fprintf(fp,"%i\n",p->devArray[i].recdBuf[j]);
  1185. fclose(fp);
  1186. }
  1187. }
  1188. cmMemFree(p->devArray[i].recdBuf);
  1189. #endif
  1190. cmMemPtrFree(&p->devArray[i].iBuf);
  1191. cmMemPtrFree(&p->devArray[i].oBuf);
  1192. }
  1193. cmMemPtrFree(&p->pollfds);
  1194. cmMemPtrFree(&p->pollfdsDesc);
  1195. cmMemPtrFree(&p->devArray);
  1196. p->devAllocCnt = 0;
  1197. p->devCnt = 0;
  1198. recdFree();
  1199. //write_rec(2);
  1200. return rc;
  1201. }
  1202. cmApRC_t cmApAlsaDeviceCount()
  1203. { return _cmApRoot.devCnt; }
  1204. const char* cmApAlsaDeviceLabel( unsigned devIdx )
  1205. {
  1206. assert(devIdx < cmApAlsaDeviceCount());
  1207. return _cmApRoot.devArray[devIdx].descStr;
  1208. }
  1209. unsigned cmApAlsaDeviceChannelCount( unsigned devIdx, bool inputFl )
  1210. {
  1211. assert(devIdx < cmApAlsaDeviceCount());
  1212. return inputFl ? _cmApRoot.devArray[devIdx].iChCnt : _cmApRoot.devArray[devIdx].oChCnt;
  1213. }
  1214. double cmApAlsaDeviceSampleRate( unsigned devIdx )
  1215. {
  1216. assert(devIdx < cmApAlsaDeviceCount());
  1217. return (double)_cmApRoot.devArray[devIdx].srate;
  1218. }
  1219. unsigned cmApAlsaDeviceFramesPerCycle( unsigned devIdx, bool inputFl )
  1220. {
  1221. assert(devIdx < cmApAlsaDeviceCount());
  1222. return _cmApRoot.devArray[devIdx].framesPerCycle;
  1223. }
  1224. cmApRC_t cmApAlsaDeviceSetup(
  1225. unsigned devIdx,
  1226. double srate,
  1227. unsigned framesPerCycle,
  1228. cmApCallbackPtr_t callbackPtr,
  1229. void* userCbPtr )
  1230. {
  1231. assert( devIdx < cmApAlsaDeviceCount());
  1232. cmApRoot_t* p = &_cmApRoot;
  1233. cmApDevRecd_t* drp = _cmApRoot.devArray + devIdx;
  1234. unsigned periodsPerBuf = kDfltPeriodsPerBuf;
  1235. if( p->asyncFl == false )
  1236. if( cmThreadPause(p->thH,kWaitThFl | kPauseThFl) != kOkThRC )
  1237. {
  1238. _cmApOsError(p,0,"Audio thread pause failed.");
  1239. return kThreadFailApRC;
  1240. }
  1241. if( _cmApDevSetup(drp, srate, framesPerCycle, periodsPerBuf ) )
  1242. {
  1243. drp->srate = srate;
  1244. drp->framesPerCycle = framesPerCycle;
  1245. drp->periodsPerBuf = periodsPerBuf;
  1246. drp->cbPtr = callbackPtr;
  1247. drp->userCbPtr = userCbPtr;
  1248. return kOkApRC;
  1249. }
  1250. return kSysErrApRC;
  1251. }
  1252. cmApRC_t cmApAlsaDeviceStart( unsigned devIdx )
  1253. {
  1254. assert( devIdx < cmApAlsaDeviceCount());
  1255. int err;
  1256. cmApRoot_t* p = &_cmApRoot;
  1257. cmApDevRecd_t* drp = p->devArray + devIdx;
  1258. bool retFl = true;
  1259. bool inputFl = true;
  1260. unsigned i;
  1261. for(i=0; i<2; ++i,inputFl=!inputFl)
  1262. {
  1263. snd_pcm_t* pcmH = inputFl ? drp->iPcmH : drp->oPcmH;
  1264. if( pcmH != NULL )
  1265. {
  1266. snd_pcm_state_t state = snd_pcm_state(pcmH);
  1267. if( state != SND_PCM_STATE_RUNNING )
  1268. {
  1269. unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
  1270. unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC;
  1271. const char* ioLabel = inputFl ? "Input" : "Output";
  1272. //printf("%i %s state:%s %i %i\n",drp->devIdx, ioLabel,_pcmStateToString(snd_pcm_state(pcmH)),chCnt,frmCnt);
  1273. // preparing may not always be necessary because the earlier call to snd_pcm_hw_params()
  1274. // may have left the device prepared - the redundant call however doesn't seem to hurt
  1275. if((err= snd_pcm_prepare(pcmH)) < 0 )
  1276. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error preparing the %i device.",ioLabel);
  1277. else
  1278. {
  1279. recdStart(drp,inputFl);
  1280. if( inputFl == false )
  1281. {
  1282. int j;
  1283. for(j=0; j<1; ++j)
  1284. if((err = _cmApWriteBuf( drp, pcmH, NULL, chCnt, frmCnt, drp->oBits, drp->oSigBits )) < 0 )
  1285. {
  1286. retFl = _cmApDevSetupError(p,err,inputFl,drp,"Write before start failed.");
  1287. break;
  1288. }
  1289. }
  1290. else
  1291. {
  1292. if((err = snd_pcm_start(pcmH)) < 0 )
  1293. retFl = _cmApDevSetupError(p,err,inputFl,drp,"'%s' start failed.",ioLabel);
  1294. }
  1295. // wait 500 microseconds between starting and stopping - this prevents
  1296. // input and output and other device callbacks from landing on top of
  1297. // each other - when this happens callbacks are dropped.
  1298. if( p->asyncFl )
  1299. usleep(500);
  1300. }
  1301. //printf("%i %s state:%s %i %i\n",drp->devIdx, ioLabel,_cmApPcmStateToString(snd_pcm_state(pcmH)),chCnt,frmCnt);
  1302. }
  1303. }
  1304. }
  1305. if( p->asyncFl == false )
  1306. if( cmThreadPause(p->thH,0) != kOkThRC )
  1307. {
  1308. _cmApOsError(p,0,"Audio thread start failed.");
  1309. retFl = false;
  1310. }
  1311. return retFl ? kOkApRC : kSysErrApRC;
  1312. }
  1313. cmApRC_t cmApAlsaDeviceStop( unsigned devIdx )
  1314. {
  1315. int err;
  1316. bool retFl = true;
  1317. cmApRoot_t* p = &_cmApRoot;
  1318. cmApDevRecd_t* drp = p->devArray + devIdx;
  1319. if( drp->iPcmH != NULL )
  1320. if((err = snd_pcm_drop(drp->iPcmH)) < 0 )
  1321. retFl = _cmApDevSetupError(p,err,true,drp,"Input stop failed.");
  1322. if( drp->oPcmH != NULL )
  1323. if((err = snd_pcm_drop(drp->oPcmH)) < 0 )
  1324. retFl = _cmApDevSetupError(p,err,false,drp,"Output stop failed.");
  1325. if( p->asyncFl == false )
  1326. if( cmThreadPause(p->thH,kPauseThFl) != kOkThRC )
  1327. {
  1328. _cmApOsError(p,0,"Audio thread pause failed.");
  1329. retFl = false;
  1330. }
  1331. return retFl ? kOkApRC : kSysErrApRC;
  1332. }
  1333. bool cmApAlsaDeviceIsStarted( unsigned devIdx )
  1334. {
  1335. assert( devIdx < cmApAlsaDeviceCount());
  1336. bool iFl = false;
  1337. bool oFl = false;
  1338. const cmApDevRecd_t* drp = _cmApRoot.devArray + devIdx;
  1339. if( drp->iPcmH != NULL )
  1340. iFl = snd_pcm_state(drp->iPcmH) == SND_PCM_STATE_RUNNING;
  1341. if( drp->oPcmH != NULL )
  1342. oFl = snd_pcm_state(drp->oPcmH) == SND_PCM_STATE_RUNNING;
  1343. return iFl || oFl;
  1344. }
  1345. //{ { label:alsaDevRpt }
  1346. //(
  1347. // Here's an example of generating a report of available
  1348. // ALSA devices.
  1349. //)
  1350. //[
  1351. void cmApAlsaDeviceReport( cmRpt_t* rpt )
  1352. {
  1353. unsigned i;
  1354. for(i=0; i<_cmApRoot.devCnt; i++)
  1355. {
  1356. cmRptPrintf(rpt,"%i : ",i);
  1357. _cmApDevReport(rpt,_cmApRoot.devArray + i );
  1358. }
  1359. }
  1360. //]
  1361. //}
  1362. void cmApAlsaDeviceRtReport( cmRpt_t* rpt, unsigned devIdx )
  1363. {
  1364. _cmApDevRtReport(rpt, _cmApRoot.devArray + devIdx );
  1365. }