1 /*******************************************************************************
2 * Goggles Audio Player Library *
3 ********************************************************************************
4 * Copyright (C) 2010-2021 by Sander Jansen. All Rights Reserved *
5 * --- *
6 * This program is free software: you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation, either version 3 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program. If not, see http://www.gnu.org/licenses. *
18 ********************************************************************************/
19 #include "ap_defs.h"
20 #include "ap_output_plugin.h"
21
22 #include <alsa/asoundlib.h>
23
24
25 #define ALSA_VERSION(major,minor,patch) ((major<<16)|(minor<<8)|patch)
26
27 //#define DEBUG 1
28
29 using namespace ap;
30
31 namespace ap {
32
33
34 class AlsaMixer;
35
36 class AlsaOutput : public OutputPlugin {
37 protected:
38 snd_pcm_t* handle;
39 snd_pcm_uframes_t period_size;
40 snd_pcm_uframes_t period_written;
41 FXuchar* silence;
42
43
44 AlsaMixer * mixer;
45 protected:
46 AlsaConfig config;
47 FXbool can_pause;
48 FXbool can_resume;
49 protected:
50 FXbool open();
51 public:
52 AlsaOutput(OutputContext*);
53
54 /// Configure
55 FXbool configure(const AudioFormat &);
56
57 /// Write frames to playback buffer
58 FXbool write(const void*, FXuint);
59
60 /// Return delay in no. of frames
61 FXint delay();
62
63 /// Empty Playback Buffer Immediately
64 void drop();
65
66 /// Wait until playback buffer is emtpy.
67 void drain();
68
69 /// Pause Playback
70 void pause(FXbool t);
71
72 /// Change Volume
73 void volume(FXfloat);
74
75 /// Close Output
76 void close();
77
78 /// Get Device Type
type() const79 FXchar type() const { return DeviceAlsa; }
80
81 /// Set Device Configuration
82 FXbool setOutputConfig(const OutputConfig &);
83
84 /// Destructor
85 virtual ~AlsaOutput();
86 };
87
88
89
90
91
92
to_alsa_format(const AudioFormat & af,snd_pcm_format_t & alsa_format)93 static FXbool to_alsa_format(const AudioFormat & af,snd_pcm_format_t & alsa_format) {
94 switch(af.format){
95 case AP_FORMAT_S8 : alsa_format=SND_PCM_FORMAT_S8; break;
96 case AP_FORMAT_U8 : alsa_format=SND_PCM_FORMAT_U8; break;
97 case AP_FORMAT_S16_LE : alsa_format=SND_PCM_FORMAT_S16_LE; break;
98 case AP_FORMAT_S16_BE : alsa_format=SND_PCM_FORMAT_S16_BE; break;
99 case AP_FORMAT_S24_LE : alsa_format=SND_PCM_FORMAT_S24_LE; break;
100 case AP_FORMAT_S24_BE : alsa_format=SND_PCM_FORMAT_S24_BE; break;
101 case AP_FORMAT_S24_3LE : alsa_format=SND_PCM_FORMAT_S24_3LE; break;
102 case AP_FORMAT_S24_3BE : alsa_format=SND_PCM_FORMAT_S24_3BE; break;
103 case AP_FORMAT_S32_LE : alsa_format=SND_PCM_FORMAT_S32_LE; break;
104 case AP_FORMAT_S32_BE : alsa_format=SND_PCM_FORMAT_S32_BE; break;
105 case AP_FORMAT_FLOAT_LE : alsa_format=SND_PCM_FORMAT_FLOAT_LE; break;
106 case AP_FORMAT_FLOAT_BE : alsa_format=SND_PCM_FORMAT_FLOAT_BE; break;
107 default : GM_DEBUG_PRINT("[alsa] No alsa format specified for %s\n",af.debug_format().text());
108 return false;
109 break;
110 }
111 return true;
112 }
113
114
115 #ifdef DEBUG
116
117
118 #define DISPLAY_DIR(d) ((d==0) ? '=' : (d==-1) ? '<' : '>')
119 #define DISPLAY_YESNO(d) ((d==0) ? "no" : "yes")
120 #define DISPLAY_MINMAX_U(dir,value) ((dir==0) ? fxmessage("%u",value) : fxmessage("%c%u",(dir==-1) ? '<' : '>',value))
121 #define DISPLAY_MINMAX_LU(dir,value) ((dir==0) ? fxmessage("%lu",value) : fxmessage("%c%lu",(dir==-1) ? '<' : '>',value))
122
debug_hw_check_rate(snd_pcm_t * pcm,snd_pcm_hw_params_t * hw,unsigned int rate)123 static void debug_hw_check_rate(snd_pcm_t * pcm,snd_pcm_hw_params_t * hw,unsigned int rate)
124 {
125 if (snd_pcm_hw_params_test_rate(pcm,hw,rate,0)==0)
126 fxmessage("%u ",rate);
127 }
128
129
debug_hw_minmax(int s1,int s2,int mindir,int maxdir,unsigned int min,unsigned int max)130 static void debug_hw_minmax(int s1,int s2,int mindir,int maxdir,unsigned int min,unsigned int max){
131 if (s1==0 && s2==0) {
132 if (min!=max){
133 DISPLAY_MINMAX_U(mindir,min);
134 fxmessage(" - ");
135 DISPLAY_MINMAX_U(maxdir,max);
136 }
137 else {
138 DISPLAY_MINMAX_U(mindir,min);
139 }
140 }
141 else if (s1==0) {
142 DISPLAY_MINMAX_U(mindir,min);
143 }
144 else if (s2==0) {
145 DISPLAY_MINMAX_U(maxdir,max);
146 }
147 else {
148 fxmessage(" - ");
149 }
150 fxmessage("\n");
151 }
152
153
debug_hw_minmax(int s1,int s2,int mindir,int maxdir,snd_pcm_uframes_t min,snd_pcm_uframes_t max)154 static void debug_hw_minmax(int s1,int s2,int mindir,int maxdir,snd_pcm_uframes_t min,snd_pcm_uframes_t max){
155 if (s1==0 && s2==0) {
156 if (min!=max){
157 DISPLAY_MINMAX_LU(mindir,min);
158 fxmessage(" - ");
159 DISPLAY_MINMAX_LU(maxdir,max);
160 }
161 else {
162 DISPLAY_MINMAX_LU(mindir,min);
163 }
164 }
165 else if (s1==0) {
166 DISPLAY_MINMAX_LU(mindir,min);
167 }
168 else if (s2==0) {
169 DISPLAY_MINMAX_LU(maxdir,max);
170 }
171 else {
172 fxmessage(" - ");
173 }
174 fxmessage("\n");
175 }
176
177 #endif
178
179 class AlsaSetup {
180 protected:
181 snd_pcm_t * pcm = nullptr;
182 snd_pcm_hw_params_t * hw = nullptr;
183 snd_pcm_sw_params_t * sw = nullptr;
184 snd_pcm_format_t format = {};
185 snd_pcm_uframes_t buffer_size = 0;
186 snd_pcm_uframes_t period_size = 0;
187 unsigned int channels = 0;
188 unsigned int rate = 0;
189 protected:
190
debug_hw_caps()191 void debug_hw_caps(){
192 #ifdef DEBUG
193 int s1,s2,mindir,maxdir;
194 unsigned int minus,maxus;
195 snd_pcm_uframes_t minframes,maxframes;
196
197 fxmessage("[alsa] Hardware Caps\n");
198
199 fxmessage("\tsample formats ");
200 for (int i=0;i<=(int)SND_PCM_FORMAT_LAST;i++){
201 if (snd_pcm_hw_params_test_format(pcm,hw,(snd_pcm_format_t)i)==0)
202 fxmessage("%s ",snd_pcm_format_name((snd_pcm_format_t)i));
203 }
204 fxmessage("\n");
205
206 fxmessage("\tsample rates ");
207 debug_hw_check_rate(pcm,hw,44100);
208 debug_hw_check_rate(pcm,hw,48000);
209 debug_hw_check_rate(pcm,hw,96000);
210 fxmessage("\n");
211
212 fxmessage("\tsample rates (all) ");
213 s1=snd_pcm_hw_params_get_rate_min(hw,&minus,&mindir);
214 s2=snd_pcm_hw_params_get_rate_max(hw,&maxus,&maxdir);
215 debug_hw_minmax(s1,s2,mindir,maxdir,minus,maxus);
216
217 fxmessage("\tchannels ");
218 s1=snd_pcm_hw_params_get_channels_min(hw,&minus);
219 s2=snd_pcm_hw_params_get_channels_max(hw,&maxus);
220 debug_hw_minmax(s1,s2,0,0,minus,maxus);
221
222 fxmessage("\tbuffer size ");
223 mindir=snd_pcm_hw_params_get_buffer_size_min(hw,&minframes);
224 maxdir=snd_pcm_hw_params_get_buffer_size_max(hw,&maxframes);
225 debug_hw_minmax(0,0,mindir,maxdir,minframes,maxframes);
226
227 fxmessage("\tbuffer time ");
228 s1=snd_pcm_hw_params_get_buffer_time_min(hw,&minus,&mindir);
229 s2=snd_pcm_hw_params_get_buffer_time_max(hw,&maxus,&maxdir);
230 debug_hw_minmax(s1,s2,mindir,maxdir,minus,maxus);
231
232 fxmessage("\tperiod size ");
233 s1=snd_pcm_hw_params_get_period_size_min(hw,&minframes,&mindir);
234 s2=snd_pcm_hw_params_get_period_size_max(hw,&maxframes,&maxdir);
235 debug_hw_minmax(s1,s2,mindir,maxdir,minframes,maxframes);
236
237 fxmessage("\tperiod time ");
238 s1=snd_pcm_hw_params_get_period_time_min(hw,&minus,&mindir);
239 s2=snd_pcm_hw_params_get_period_time_max(hw,&maxus,&maxdir);
240 debug_hw_minmax(s1,s2,mindir,maxdir,minus,maxus);
241
242 fxmessage("\tperiod count ");
243 s1=snd_pcm_hw_params_get_periods_min(hw,&minus,&mindir);
244 s2=snd_pcm_hw_params_get_periods_max(hw,&maxus,&maxdir);
245 debug_hw_minmax(s1,s2,0,0,minus,maxus);
246 #endif
247 }
248
debug_hw_parameters()249 void debug_hw_parameters(){
250 #ifdef DEBUG
251 int dir;
252 snd_pcm_uframes_t frames;
253 unsigned int us;
254
255 fxmessage("[alsa] Hardware Parameters\n");
256
257 dir = snd_pcm_hw_params_can_mmap_sample_resolution(hw);
258 fxmessage("\tcan mmap sample resolution %s\n",DISPLAY_YESNO(dir));
259
260 dir = snd_pcm_hw_params_can_overrange(hw);
261 fxmessage("\thas overrange detection %s\n",DISPLAY_YESNO(dir));
262
263 dir = snd_pcm_hw_params_can_pause(hw);
264 fxmessage("\tcan pause %s\n",DISPLAY_YESNO(dir));
265
266 dir = snd_pcm_hw_params_can_resume(hw);
267 fxmessage("\tcan resume %s\n",DISPLAY_YESNO(dir));
268
269 dir = snd_pcm_hw_params_can_sync_start(hw);
270 fxmessage("\tcan sync start %s\n",DISPLAY_YESNO(dir));
271
272 dir = snd_pcm_hw_params_is_batch(hw);
273 fxmessage("\tis batch transfer %s\n",DISPLAY_YESNO(dir));
274
275 dir = snd_pcm_hw_params_is_block_transfer(hw);
276 fxmessage("\tis block transfer %s\n",DISPLAY_YESNO(dir));
277
278 dir = snd_pcm_hw_params_is_double(hw);
279 fxmessage("\tis double buffer %s\n",DISPLAY_YESNO(dir));
280
281 dir = snd_pcm_hw_params_is_half_duplex(hw);
282 fxmessage("\tis half duplex %s\n",DISPLAY_YESNO(dir));
283
284 dir = snd_pcm_hw_params_is_joint_duplex(hw);
285 fxmessage("\tis joint duplex %s\n",DISPLAY_YESNO(dir));
286
287 dir = snd_pcm_hw_params_is_monotonic(hw);
288 fxmessage("\tmonotonic timestamps %s\n",DISPLAY_YESNO(dir));
289
290 if (snd_pcm_hw_params_get_channels(hw,&us)==0)
291 fxmessage("\tchannel count = %u\n",us);
292
293 if (snd_pcm_hw_params_get_buffer_size(hw,&frames)==0)
294 fxmessage("\tbuffer size %lu frames\n",frames);
295
296 if (snd_pcm_hw_params_get_buffer_time(hw,&us,&dir)==0)
297 fxmessage("\tbuffer time %c %u us\n",DISPLAY_DIR(dir),us);
298
299 dir = snd_pcm_hw_params_get_fifo_size(hw);
300 fxmessage("\tfifo size = %d frames\n",dir);
301
302 if (snd_pcm_hw_params_get_min_align(hw,&frames)==0)
303 fxmessage("\tmin transfer align = %lu samples\n",frames);
304
305 if (snd_pcm_hw_params_get_period_size(hw,&frames,&dir)==0)
306 fxmessage("\tperiod size %c %lu frames\n",DISPLAY_DIR(dir),frames);
307
308 if (snd_pcm_hw_params_get_period_time(hw,&us,&dir)==0)
309 fxmessage("\tperiod time %c %u us\n",DISPLAY_DIR(dir),us);
310
311 if (snd_pcm_hw_params_get_periods(hw,&us,&dir)==0)
312 fxmessage("\tperiod count %c %u\n",DISPLAY_DIR(dir),us);
313
314 if (snd_pcm_hw_params_get_period_wakeup(pcm,hw,&us)==0)
315 fxmessage("\tperiod wakeup %s\n",DISPLAY_YESNO(us));
316
317 if (snd_pcm_hw_params_get_rate(hw,&us,&dir)==0)
318 fxmessage("\tsample rate %c %u\n",DISPLAY_DIR(dir),us);
319
320 if (snd_pcm_hw_params_get_rate_resample(pcm,hw,&us)==0)
321 fxmessage("\tsample rate resample %s\n",DISPLAY_YESNO(us));
322 #endif
323 }
324
debug_sw_parameters()325 void debug_sw_parameters(){
326 #ifdef DEBUG
327 snd_pcm_uframes_t frames;
328 int dir;
329
330 fxmessage("[alsa] Software Parameters\n");
331
332 if (snd_pcm_sw_params_get_avail_min(sw,&frames)==0)
333 fxmessage("\tmin available %lu frames\n",frames);
334
335 if (snd_pcm_sw_params_get_boundary(sw,&frames)==0)
336 fxmessage("\tboundary %lu frames\n",frames);
337
338 if (snd_pcm_sw_params_get_period_event(sw,&dir)==0)
339 fxmessage("\tperiod event %d\n",dir);
340
341 if (snd_pcm_sw_params_get_silence_size(sw,&frames)==0)
342 fxmessage("\tsilence size %lu frames\n",frames);
343
344 if (snd_pcm_sw_params_get_silence_threshold(sw,&frames)==0)
345 fxmessage("\tsilence threshold %lu frames\n",frames);
346
347 if (snd_pcm_sw_params_get_start_threshold(sw,&frames)==0)
348 fxmessage("\tstart threshold %lu frames\n",frames);
349
350 if (snd_pcm_sw_params_get_stop_threshold(sw,&frames)==0)
351 fxmessage("\tstop threshold %lu frames\n",frames);
352 #endif
353 }
354
355 protected:
AlsaSetup(snd_pcm_t * p)356 AlsaSetup(snd_pcm_t*p) : pcm(p) {
357 }
358
~AlsaSetup()359 ~AlsaSetup() {
360 snd_pcm_hw_params_free(hw);
361 snd_pcm_sw_params_free(sw);
362 }
363
init()364 FXbool init() {
365 int result;
366 snd_pcm_hw_params_malloc(&hw);
367 snd_pcm_sw_params_malloc(&sw);
368
369 /// blocking while configuring
370 if ((result=snd_pcm_nonblock(pcm,0))<0) {
371 GM_DEBUG_PRINT("[alsa] failed to set blocking mode. Reason: %s\n",snd_strerror(result));
372 return false;
373 }
374
375 /// Get all configurations
376 if ((result=snd_pcm_hw_params_any(pcm,hw))<0){
377 GM_DEBUG_PRINT("[alsa] failed to query hardware parameters. Reason: %s\n",snd_strerror(result));
378 return false;
379 }
380
381 debug_hw_caps();
382 return true;
383 }
384
385
matchFormat(const AudioFormat & in,AudioFormat & out,AlsaConfig & config)386 FXbool matchFormat(const AudioFormat & in,AudioFormat & out,AlsaConfig & config) {
387 int result;
388 int dir = 0;
389 out = in;
390 channels = out.channels;
391 rate = out.rate;
392
393 if (!to_alsa_format(out,format)){
394 GM_DEBUG_PRINT("[alsa] failed to find format %s\n",in.debug_format().text());
395 return false;
396 }
397
398 GM_DEBUG_PRINT("[alsa] check format %s\n",snd_pcm_format_name(format));
399
400 /// Find closest matching format based on what we can handle and what alsa offers
401 while(snd_pcm_hw_params_test_format(pcm,hw,format)<0) {
402
403 // Try a simple swap
404 if (out.swap()) {
405 if (to_alsa_format(out,format)) {
406 GM_DEBUG_PRINT("[alsa] check swapped format %s\n",snd_pcm_format_name(format));
407 if (snd_pcm_hw_params_test_format(pcm,hw,format)==0)
408 break;
409 }
410 out.swap();
411 }
412
413 // Try a compatible format.
414 if (!out.compatible() || !to_alsa_format(out,format)) {
415 GM_DEBUG_PRINT("[alsa] failed to find format %s\n",in.debug_format().text());
416 return false;
417 }
418 GM_DEBUG_PRINT("[alsa] check compatible format %s\n",snd_pcm_format_name(format));
419 }
420
421 if ((result=snd_pcm_hw_params_set_format(pcm,hw,format))<0) {
422 GM_DEBUG_PRINT("[alsa] failed to set format %s. Reason: %s\n",snd_pcm_format_name(format),snd_strerror(result));
423 return false;
424 }
425
426 if ((result=snd_pcm_hw_params_set_channels_near(pcm,hw,&channels))<0){
427 GM_DEBUG_PRINT("[alsa] failed to set channels %d. Reason: %s\n",channels,snd_strerror(result));
428 return false;
429 }
430
431 if (config.flags&AlsaConfig::DeviceNoResample) {
432 GM_DEBUG_PRINT("[alsa] disable rate resampling\n");
433 if ((result=snd_pcm_hw_params_set_rate_resample(pcm,hw,0))<0){
434 GM_DEBUG_PRINT("[alsa] failed to disable rate resample. Reason: %s\n",snd_strerror(result));
435 return false;
436 }
437 }
438 else {
439 GM_DEBUG_PRINT("[alsa] enable rate resampling\n");
440 if ((result=snd_pcm_hw_params_set_rate_resample(pcm,hw,1))<0){
441 GM_DEBUG_PRINT("[alsa] failed to enable rate resample. Reason: %s\n",snd_strerror(result));
442 return false;
443 }
444 }
445
446 if ((result=snd_pcm_hw_params_set_rate_near(pcm,hw,&rate,&dir))<0){
447 GM_DEBUG_PRINT("[alsa] failed to set rate %d. Reason: %s\n",rate,snd_strerror(result));
448 return false;
449 }
450
451 if (config.flags&AlsaConfig::DeviceMMap) {
452 if ((result=snd_pcm_hw_params_set_access(pcm,hw,SND_PCM_ACCESS_MMAP_INTERLEAVED))<0) {
453 GM_DEBUG_PRINT("[alsa] failed to set access MMAP_RW_INTERLEAVED. Reason: %s\n",snd_strerror(result));
454
455 if ((result=snd_pcm_hw_params_set_access(pcm,hw,SND_PCM_ACCESS_RW_INTERLEAVED))<0){
456 GM_DEBUG_PRINT("[alsa] failed to set access RW_INTERLEAVED. Reason: %s\n",snd_strerror(result));
457 return false;
458 }
459
460 config.flags&=~AlsaConfig::DeviceMMap;
461 }
462 }
463 else {
464 if ((result=snd_pcm_hw_params_set_access(pcm,hw,SND_PCM_ACCESS_RW_INTERLEAVED))<0) {
465 GM_DEBUG_PRINT("[alsa] failed to set access RW_INTERLEAVED. Reason: %s\n",snd_strerror(result));
466 return false;
467 }
468 }
469 return true;
470 }
471
472
finish(AudioFormat & af,FXbool & can_pause,FXbool & can_resume,snd_pcm_uframes_t & period_frames)473 FXbool finish(AudioFormat & af,FXbool & can_pause,FXbool & can_resume,snd_pcm_uframes_t & period_frames) {
474 int result;
475
476 af.rate = rate;
477 af.channels = channels;
478 can_pause = snd_pcm_hw_params_can_pause(hw);
479 can_resume = snd_pcm_hw_params_can_resume(hw);
480 period_frames = period_size;
481
482 debug_hw_parameters();
483 debug_sw_parameters();
484
485
486 if ((result=snd_pcm_nonblock(pcm,1))<0) {
487 GM_DEBUG_PRINT("[alsa] failed to set nonblock mode. Reason: %s\n",snd_strerror(result));
488 return false;
489 }
490
491 return true;
492 }
493
494 #if 0
495 static void print_channels(const snd_pcm_chmap_t *map)
496 {
497 char tmp[128];
498 if (snd_pcm_chmap_print(map, sizeof(tmp), tmp) > 0)
499 printf(" %s\n", tmp);
500 }
501
502 static int query_chmaps(snd_pcm_t *pcm)
503 {
504 snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps(pcm);
505 snd_pcm_chmap_query_t **p, *v;
506
507 if (!maps) {
508 printf("Cannot query maps %d\n",snd_pcm_state(pcm)==SND_PCM_STATE_PREPARED);
509 return 1;
510 }
511 for (p = maps; (v = *p) != nullptr; p++) {
512 printf("Type = %s, Channels = %d\n",
513 snd_pcm_chmap_type_name(v->type),
514 v->map.channels);
515 print_channels(&v->map);
516 }
517 snd_pcm_free_chmaps(maps);
518 return 0;
519 }
520
521 #endif
522
setupHardware()523 FXbool setupHardware() {
524 int result;
525 /*
526 int result;
527 unsigned int buffer_time = 1000000; // 1 sec
528 unsigned int periods = 5; // periods every 200ms
529 int dir=0;
530
531 if (snd_pcm_hw_params_set_buffer_time_near(pcm,hw,&buffer_time,&dir)<0)
532 return false;
533
534 if (snd_pcm_hw_params_set_periods_near(pcm,hw,&periods,&dir)<0)
535 return false;
536 */
537 if ((result=snd_pcm_hw_params(pcm,hw))<0) {
538 GM_DEBUG_PRINT("[alsa] failed to set hardware paramaters. Reason: %s\n",snd_strerror(result));
539 return false;
540 }
541
542 if (snd_pcm_hw_params_current(pcm,hw)<0){
543 GM_DEBUG_PRINT("[alsa] failed to retrieve hardware paramaters. Reason: %s\n",snd_strerror(result));
544 return false;
545 }
546 return getHardware();
547 }
548
549
550
setupChannelMap(const AudioFormat & af)551 FXbool setupChannelMap(const AudioFormat & af) {
552 if (af.channels) {
553 snd_pcm_chmap_t * map = nullptr;
554
555 if (!fxmalloc((void**)&map,sizeof(snd_pcm_chmap_t) + af.channels*sizeof(unsigned int)))
556 return false;
557
558 map->channels = af.channels;
559
560 for (FXint i=0;i<af.channels;i++) {
561 switch(af.channeltype(i)) {
562 case Channel::None : map->pos[i] = SND_CHMAP_NA; break;
563 case Channel::Mono : map->pos[i] = SND_CHMAP_MONO; break;
564 case Channel::FrontLeft : map->pos[i] = SND_CHMAP_FL; break;
565 case Channel::FrontRight : map->pos[i] = SND_CHMAP_FR; break;
566 case Channel::FrontCenter : map->pos[i] = SND_CHMAP_FC; break;
567 case Channel::BackLeft : map->pos[i] = SND_CHMAP_RL; break;
568 case Channel::BackRight : map->pos[i] = SND_CHMAP_RR; break;
569 case Channel::BackCenter : map->pos[i] = SND_CHMAP_RC; break;
570 case Channel::SideLeft : map->pos[i] = SND_CHMAP_SL; break;
571 case Channel::SideRight : map->pos[i] = SND_CHMAP_SR; break;
572 case Channel::LFE : map->pos[i] = SND_CHMAP_LFE; break;
573 default: return false;
574 }
575 }
576 if (snd_pcm_set_chmap(pcm,map)==0) {
577 fxfree((void**)&map);
578 return true;
579 }
580 else {
581 fxfree((void**)&map);
582 return false;
583 }
584 }
585 return false;
586 }
587
588
getHardware()589 FXbool getHardware() {
590 int dir=0;
591 int result;
592
593 if ((result=snd_pcm_hw_params_get_rate(hw,&rate,&dir))<0){
594 GM_DEBUG_PRINT("[alsa] failed to retrieve rate. Reason: %s\n",snd_strerror(result));
595 return false;
596 }
597
598 if ((result=snd_pcm_hw_params_get_channels(hw,&channels))<0) {
599 GM_DEBUG_PRINT("[alsa] failed to retrieve channels. Reason: %s\n",snd_strerror(result));
600 return false;
601 }
602
603 if ((result=snd_pcm_hw_params_get_period_size(hw,&period_size,&dir))<0){
604 GM_DEBUG_PRINT("[alsa] failed to retrieve period size. Reason: %s\n",snd_strerror(result));
605 return false;
606 }
607
608 if ((result=snd_pcm_hw_params_get_buffer_size(hw,&buffer_size))<0){
609 GM_DEBUG_PRINT("[alsa] failed to retrieve buffer size. Reason: %s\n",snd_strerror(result));
610 return false;
611 }
612
613 return true;
614 }
615
setupSoftware()616 FXbool setupSoftware() {
617 int result;
618
619 if ((result=snd_pcm_sw_params_set_avail_min(pcm,sw,period_size))<0){
620 GM_DEBUG_PRINT("[alsa] failed to set avail_min to %lu. Reason: %s\n",period_size,snd_strerror(result));
621 return false;
622 }
623
624 if ((result=snd_pcm_sw_params_set_start_threshold(pcm,sw,period_size))<0){
625 GM_DEBUG_PRINT("[alsa] failed to set start threshold to %lu. Reason: %s\n",period_size,snd_strerror(result));
626 return false;
627 }
628
629 if ((result=snd_pcm_sw_params_set_stop_threshold(pcm,sw,buffer_size))<0){
630 GM_DEBUG_PRINT("[alsa] failed to set stop threshold to %lu. Reason: %s\n",buffer_size,snd_strerror(result));
631 return false;
632 }
633
634 #if SND_LIB_VERSION < ALSA_VERSION(1,0,16)
635 if ((result=snd_pcm_sw_params_set_xfer_align(pcm,sw,1))<0){
636 GM_DEBUG_PRINT("[alsa] failed to set xfer align to 1. Reason: %s\n",snd_strerror(result));
637 return false;
638 }
639 #endif
640
641 return true;
642 }
643
644 public:
645
configure(snd_pcm_t * pcm,AlsaConfig & config,const AudioFormat & in,AudioFormat & out,FXbool & can_pause,FXbool & can_resume,snd_pcm_uframes_t & period_frames)646 static FXbool configure(snd_pcm_t * pcm,AlsaConfig & config,const AudioFormat & in,AudioFormat & out,FXbool & can_pause,FXbool & can_resume,snd_pcm_uframes_t & period_frames) {
647 AlsaSetup alsa(pcm);
648
649 // Init structures
650 if (!alsa.init())
651 return true;
652
653 /// Match Format
654 if (!alsa.matchFormat(in,out,config))
655 return false;
656
657 /// Configure Device
658 if (!alsa.setupHardware())
659 return false;
660
661 /// Configure the channel map
662 alsa.setupChannelMap(in);
663
664 /// Set the software parameters
665 if (!alsa.setupSoftware())
666 return false;
667
668 /// Finish up and get the Configured Format
669 if (!alsa.finish(out,can_pause,can_resume,period_frames))
670 return false;
671
672 return true;
673 }
674
675 };
676
677
678
679 class AlsaMixer : public Reactor::Native {
680 private:
681 OutputContext * context;
682 snd_mixer_t * mixer;
683 snd_mixer_elem_t * element;
684 FXint nhandles;
685 protected:
AlsaMixer(OutputContext * ctx,snd_mixer_t * m,snd_mixer_elem_t * e)686 AlsaMixer(OutputContext * ctx,snd_mixer_t * m,snd_mixer_elem_t * e) : context(ctx),mixer(m),element(e) {
687 updateVolume();
688 nhandles=snd_mixer_poll_descriptors_count(mixer);
689 }
690 public:
updateVolume()691 void updateVolume() {
692 FXfloat vol=0.0f;
693 long min,max;
694 long value;
695 int nvalues=0;
696
697 if (snd_mixer_selem_get_playback_volume_range(element,&min,&max)<0)
698 return;
699
700 GM_DEBUG_PRINT("Volume for channels:\n");
701 for (int c = SND_MIXER_SCHN_FRONT_LEFT;c<SND_MIXER_SCHN_LAST;c++){
702 if (snd_mixer_selem_has_playback_channel(element,(snd_mixer_selem_channel_id_t)c)==1){
703 if (snd_mixer_selem_get_playback_volume (element,(snd_mixer_selem_channel_id_t)c,&value)==0) {
704 GM_DEBUG_PRINT("\tchannel %d volume %ld\n",c,value);
705 nvalues++;
706 vol+=value;
707 }
708 }
709 }
710 context->notify_volume(vol/(nvalues*(max-min)));
711 }
712
713
volume(FXfloat v)714 void volume(FXfloat v) {
715 long min,max;
716 snd_mixer_selem_get_playback_volume_range(element,&min,&max);
717 long value = FXLERP(min,max,v);
718 snd_mixer_selem_set_playback_volume_all(element,value);
719 }
720
721
no()722 virtual FXint no() { return nhandles; }
723
prepare(struct pollfd * pfds)724 virtual void prepare(struct pollfd * pfds){
725 snd_mixer_poll_descriptors(mixer,pfds,nhandles);
726 }
727
dispatch(struct pollfd *)728 virtual void dispatch(struct pollfd*) {
729 if (snd_mixer_handle_events(mixer)>0) {
730 updateVolume();
731 }
732 }
733
~AlsaMixer()734 ~AlsaMixer() {
735 snd_mixer_close(mixer);
736 }
737
738 protected:
find_mixer_element_by_name(snd_mixer_t * mixer,const FXchar * name)739 static snd_mixer_elem_t * find_mixer_element_by_name(snd_mixer_t * mixer,const FXchar * name){
740 long volume;
741 for (snd_mixer_elem_t * element = snd_mixer_first_elem(mixer);element;element=snd_mixer_elem_next(element)){
742
743 /* Filter out the obvious ones */
744 if (!snd_mixer_selem_is_active(element) ||
745 snd_mixer_elem_get_type(element)!=SND_MIXER_ELEM_SIMPLE ||
746 !snd_mixer_selem_has_playback_volume(element))
747 continue;
748
749 /* Check if we can query the volume */
750 if (snd_mixer_selem_get_playback_volume(element,SND_MIXER_SCHN_FRONT_LEFT,&volume)<0 ||
751 snd_mixer_selem_get_playback_volume(element,SND_MIXER_SCHN_FRONT_RIGHT,&volume)<0 ){
752 continue;
753 }
754
755 /* If we don't know what we're looking for, return first one found */
756 if (name==nullptr)
757 return element;
758
759 /* Check if this is the one we want */
760 if (comparecase(snd_mixer_selem_get_name(element),name)==0)
761 return element;
762
763 }
764 return nullptr;
765 }
766
767
768 public:
open(OutputContext * context,snd_pcm_t * handle)769 static AlsaMixer * open(OutputContext * context,snd_pcm_t * handle) {
770 FXString device;
771 snd_mixer_t* mixer = nullptr;
772 snd_mixer_elem_t* element = nullptr;
773 snd_pcm_info_t* info = nullptr;
774 FXint result;
775
776 snd_pcm_info_alloca(&info);
777
778 if (snd_pcm_info(handle,info)<0)
779 return nullptr;
780
781 if (snd_mixer_open(&mixer,0)<0)
782 return nullptr;
783
784 device = snd_pcm_name(handle);
785
786 if ((result=snd_mixer_attach(mixer,device.text()))<0) {
787 GM_DEBUG_PRINT("Unable to attach mixer: %s\n",snd_strerror(result));
788
789 // get card info
790 if ((result=snd_pcm_info_get_card(info))<0) {
791 GM_DEBUG_PRINT("Unable to query card: %s\n",snd_strerror(result));
792 goto fail;
793 }
794
795 // try with hw name
796 device.format("hw:%d",snd_pcm_info_get_card(info));
797 if ((result=snd_mixer_attach(mixer,device.text()))<0) {
798 GM_DEBUG_PRINT("Unable to attach mixer: %s\n",snd_strerror(result));
799 goto fail;
800 }
801 }
802
803 // register mixer
804 if ((result=snd_mixer_selem_register(mixer,nullptr,nullptr))<0){
805 GM_DEBUG_PRINT("Unable to register simple mixer: %s\n",snd_strerror(result));
806 goto fail;
807 }
808
809 // load mixer
810 if ((result=snd_mixer_load(mixer))<0) {
811 GM_DEBUG_PRINT("Unable to load mixer: %s\n",snd_strerror(result));
812 goto fail;
813 }
814
815 /* Yay... let's guess what mixer we want */
816 element = find_mixer_element_by_name(mixer,"PCM");
817 if (element==nullptr) {
818 element = find_mixer_element_by_name(mixer,"MASTER");
819 if (element==nullptr) {
820 element = find_mixer_element_by_name(mixer,nullptr);
821 }
822 }
823
824 // If we found an element
825 if (element) {
826 return new AlsaMixer(context,mixer,element);
827 }
828 fail:
829 context->notify_disable_volume();
830 if (mixer) snd_mixer_close(mixer);
831 return nullptr;
832 }
833
834
835 };
836
837
838
839
AlsaOutput(OutputContext * ctx)840 AlsaOutput::AlsaOutput(OutputContext * ctx) : OutputPlugin(ctx), handle(nullptr),period_size(0),period_written(0),silence(nullptr),mixer(nullptr),can_pause(false),can_resume(false) {
841 }
842
~AlsaOutput()843 AlsaOutput::~AlsaOutput() {
844 close();
845 freeElms(silence);
846 }
847
open()848 FXbool AlsaOutput::open() {
849 FXint result;
850 if (handle==nullptr) {
851
852 if ((result=snd_pcm_open(&handle,config.device.text(),SND_PCM_STREAM_PLAYBACK,0))<0) {
853 GM_DEBUG_PRINT("[alsa] Unable to open device \"%s\": %s\n",config.device.text(),snd_strerror(result));
854 return false;
855 }
856
857 GM_DEBUG_PRINT("[alsa] opened device \"%s\"\n",config.device.text());
858 mixer = AlsaMixer::open(context,handle);
859 if (mixer) context->getReactor().addNative(mixer);
860 }
861 return true;
862 }
863
close()864 void AlsaOutput::close() {
865 GM_DEBUG_PRINT("[alsa] closing device\n");
866 if (handle) {
867 snd_pcm_drop(handle);
868
869 if (mixer) {
870 context->getReactor().removeNative(mixer);
871 delete mixer;
872 mixer=nullptr;
873 }
874
875 snd_pcm_close(handle);
876 handle=nullptr;
877 }
878 af.reset();
879 }
880
setOutputConfig(const OutputConfig & c)881 FXbool AlsaOutput::setOutputConfig(const OutputConfig & c) {
882 config=c.alsa;
883 return true;
884 }
885
volume(FXfloat v)886 void AlsaOutput::volume(FXfloat v) {
887 if (mixer) mixer->volume(v);
888 }
889
delay()890 FXint AlsaOutput::delay() {
891 int result;
892 snd_pcm_sframes_t nframes=0;
893 if (handle) {
894 if ((result=snd_pcm_delay(handle,&nframes))!=0){
895 GM_DEBUG_PRINT("[alsa] failed to get delay %s\n",snd_strerror(result));
896 return 0;
897 }
898 if (nframes<0) {
899 GM_DEBUG_PRINT("[alsa] delay was negative\n");
900 return 0;
901 }
902 }
903 return nframes;
904 }
905
906
drop()907 void AlsaOutput::drop() {
908 int result;
909 if (__likely(handle)) {
910
911 if ((result=snd_pcm_reset(handle))<0){
912 GM_DEBUG_PRINT("[alsa] failed to reset. Reason: %s\n",snd_strerror(result));
913 }
914
915 if ((result=snd_pcm_drop(handle))<0){
916 GM_DEBUG_PRINT("[alsa] failed to drop. Reason: %s\n",snd_strerror(result));
917 }
918
919 period_written = 0;
920 }
921 }
922
drain()923 void AlsaOutput::drain() {
924 if (__likely(handle)) {
925 int result;
926 if (snd_pcm_state(handle)==SND_PCM_STATE_RUNNING) {
927
928 // snd_pcm_drain works with periods, not samples. So
929 // make sure we have at least period_size of data.
930 // pad with silence if needed.
931 if (period_written) {
932 write(silence,period_size-period_written);
933 }
934
935 // Turn on blocking
936 if ((result=snd_pcm_nonblock(handle,0))<0) {
937 GM_DEBUG_PRINT("[alsa] failed to set blocking mode. Reason: %s\n",snd_strerror(result));
938 }
939
940 // Drain
941 GM_DEBUG_PRINT("[alsa] dispatch drain\n");
942 result=snd_pcm_drain(handle);
943
944 if (result==-EAGAIN) { // Handle non-blocking
945 GM_DEBUG_PRINT("[alsa] waiting for drain\n");
946 while(snd_pcm_state(handle)==SND_PCM_STATE_DRAINING){
947 FXThread::sleep(500000000); // 50ms
948 }
949 GM_DEBUG_PRINT("[alsa] drain complete. State: %s\n",snd_pcm_state_name(snd_pcm_state(handle)));
950 }
951 else if (result<0) { // Some other error
952 GM_DEBUG_PRINT("[alsa] drain failed. Reason: %s\n",snd_strerror(result));
953 }
954 else { // Success
955 GM_DEBUG_PRINT("[alsa] drain complete\n");
956 }
957
958 // Turn off blocking
959 if ((result=snd_pcm_nonblock(handle,1))<0) {
960 GM_DEBUG_PRINT("[alsa] failed to set blocking mode. Reason: %s\n",snd_strerror(result));
961 }
962 }
963 }
964 }
965
pause(FXbool p)966 void AlsaOutput::pause(FXbool p) {
967 FXint result=-1;
968 if (__likely(handle)) {
969 if (can_pause) {
970 result = snd_pcm_pause(handle,p?1:0);
971 if (result==-1 && p==true)
972 snd_pcm_drain(handle);
973 }
974 else {
975 if (p) snd_pcm_drain(handle);
976 }
977 }
978 }
979
980
configure(const AudioFormat & fmt)981 FXbool AlsaOutput::configure(const AudioFormat & fmt){
982 if (__unlikely(handle==nullptr)) {
983 if (!open()) {
984 return false;
985 }
986 }
987
988 if (fmt==af) {
989 return true;
990 }
991
992 if (!AlsaSetup::configure(handle,config,fmt,af,can_pause,can_resume,period_size)) {
993 GM_DEBUG_PRINT("[alsa] error configuring device\n");
994 af.reset();
995 return false;
996 }
997
998 if (silence)
999 resizeElms(silence,period_size*af.framesize());
1000 else
1001 allocElms(silence,period_size*af.framesize());
1002
1003 // this should never fail.
1004 snd_pcm_format_t format;
1005 if (__unlikely(!to_alsa_format(af,format)))
1006 return false;
1007
1008 snd_pcm_format_set_silence(format,silence,period_size*af.channels);
1009 return true;
1010 }
1011
1012
write(const void * buffer,FXuint nframes)1013 FXbool AlsaOutput::write(const void * buffer,FXuint nframes){
1014 int result;
1015 snd_pcm_sframes_t navailable;
1016 snd_pcm_sframes_t nwritten;
1017 snd_pcm_state_t state;
1018 const FXchar * buf = (const FXchar*)buffer;
1019
1020 if (__unlikely(handle==nullptr))
1021 return false;
1022
1023 while(nframes>0) {
1024 state=snd_pcm_state(handle);
1025 switch(state) {
1026 /// Failed States
1027 case SND_PCM_STATE_DRAINING :
1028 case SND_PCM_STATE_DISCONNECTED :
1029 case SND_PCM_STATE_OPEN : GM_DEBUG_PRINT("[alsa] state is open, draining or disconnected\n");
1030 return false;
1031 break;
1032
1033 case SND_PCM_STATE_PAUSED : GM_DEBUG_PRINT("[alsa] state is paused while write is called\n");
1034 return false;
1035 break;
1036
1037 /// Recoverable States
1038 case SND_PCM_STATE_XRUN :
1039 {
1040 GM_DEBUG_PRINT("[alsa] xrun\n");
1041 result = snd_pcm_prepare(handle);
1042 if (result<0) {
1043 GM_DEBUG_PRINT("[alsa] %s",snd_strerror(result));
1044 return false;
1045 }
1046 } break;
1047
1048 case SND_PCM_STATE_SETUP :
1049 {
1050 result = snd_pcm_prepare(handle);
1051 if (result<0) {
1052 GM_DEBUG_PRINT("[alsa] %s",snd_strerror(result));
1053 return false;
1054 }
1055
1056 } break;
1057
1058 case SND_PCM_STATE_SUSPENDED:
1059 {
1060 GM_DEBUG_PRINT("[alsa] suspended\n");
1061 result=-1;
1062
1063 if (can_resume) {
1064 while((result=snd_pcm_resume(handle))==-EAGAIN)
1065 FXThread::sleep(10000000);
1066 }
1067
1068 /// If the hardware cannot resume, we need to call prepare
1069 if (result!=0)
1070 result = snd_pcm_prepare(handle);
1071
1072 if (result!=0) {
1073 GM_DEBUG_PRINT("[alsa] %s",snd_strerror(result));
1074 return false;
1075 }
1076
1077 } break;
1078
1079 case SND_PCM_STATE_PREPARED :
1080 case SND_PCM_STATE_RUNNING :
1081 {
1082 navailable = snd_pcm_avail_update(handle);
1083 if (navailable<nframes /*&& navailable<(snd_pcm_sframes_t)periodsize*/) {
1084 result = snd_pcm_wait(handle,500);
1085 if (result<0) {
1086 /// Underrun / Suspended
1087 if (result==-EPIPE || result==-ESTRPIPE) {
1088 GM_DEBUG_PRINT("[alsa] %s\n",snd_strerror(result));
1089 continue;
1090 }
1091 return false;
1092 }
1093 navailable = snd_pcm_avail_update(handle);
1094 }
1095
1096 } // fallthrough - intentionally no break
1097 default :
1098 {
1099 if ((config.flags&AlsaConfig::DeviceMMap))
1100 nwritten = snd_pcm_mmap_writei(handle,buf,nframes);
1101 else
1102 nwritten = snd_pcm_writei(handle,buf,nframes);
1103
1104 if (nwritten==-EAGAIN || nwritten==-EINTR)
1105 continue;
1106
1107 if (nwritten<0) {
1108 GM_DEBUG_PRINT("[alsa] xrun or suspend: %s\n",snd_strerror(nwritten));
1109 nwritten = snd_pcm_recover(handle,nwritten,1);
1110 if (nwritten<0) {
1111 if (nwritten!=-EAGAIN) {
1112 GM_DEBUG_PRINT("[alsa] fatal write error %ld: %s\n",nwritten,snd_strerror(nwritten));
1113 return false;
1114 }
1115 }
1116 }
1117 if (nwritten>0) {
1118 period_written = (period_written + nwritten) % period_size;
1119 buf+=(nwritten*af.framesize());
1120 nframes-=nwritten;
1121 }
1122 } break;
1123 }
1124 }
1125 return true;
1126 }
1127
1128 }
1129
1130 AP_IMPLEMENT_PLUGIN(AlsaOutput);
1131
1132