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