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.

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. }