1 /*
2  * Copyright (C) 2004 2005 2006 2008 2010 2011 2012, Magnus Hjorth
3  *
4  * This file is part of mhWaveEdit.
5  *
6  * mhWaveEdit 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 2 of the License, or
9  * (at your option) any later version.
10  *
11  * mhWaveEdit 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 mhWaveEdit; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <alsa/asoundlib.h>
22 #include "gettext.h"
23 
24 /* #define ALSADEBUG */
25 
26 static struct {
27      snd_pcm_t *whand,*rhand;
28      Dataformat wfmt,rfmt;
29      gboolean draining,draining_done;
30      int overrun_count;
31      gpointer iogroup;
32      gpointer csource;
33      gboolean eventdriv;
34      gboolean inside_ready_func;
35      int rw_call_count;
36      GVoidFunc ready_func;
37 } alsa_data = { 0 };
38 
39 static gboolean alsa_output_want_data(void);
40 
alsa_init(gboolean silent)41 static gboolean alsa_init(gboolean silent)
42 {
43      alsa_data.eventdriv = inifile_get_gboolean("ALSAEventDriven",TRUE);
44      /* We assume that if you have alsa-lib, you're using the ALSA kernel
45       * drivers. */
46      return TRUE;
47 }
48 
alsa_quit(void)49 static void alsa_quit(void)
50 {
51 }
52 
alsa_prefs_ok(GtkButton * button,gpointer user_data)53 static void alsa_prefs_ok(GtkButton *button, gpointer user_data)
54 {
55      GtkWidget *w = GTK_WIDGET(user_data);
56      GtkWidget *ep,*er;
57      GtkToggleButton *tb;
58      ep = gtk_object_get_data(GTK_OBJECT(w),"playentry");
59      er = gtk_object_get_data(GTK_OBJECT(w),"recentry");
60      tb = gtk_object_get_data(GTK_OBJECT(w),"eventtb");
61      alsa_data.eventdriv = gtk_toggle_button_get_active(tb);
62      inifile_set("ALSAPlayDevice",(gchar *)gtk_entry_get_text(GTK_ENTRY(ep)));
63      inifile_set("ALSARecDevice",(gchar *)gtk_entry_get_text(GTK_ENTRY(er)));
64      inifile_set_gboolean("ALSAEventDriven",alsa_data.eventdriv);
65      gtk_widget_destroy(w);
66 }
67 
alsa_show_preferences(void)68 static void alsa_show_preferences(void)
69 {
70      GtkWidget *a,*b,*c,*d;
71      alsa_init(TRUE); /* Read config */
72      a = gtk_window_new(GTK_WINDOW_DIALOG);
73      gtk_window_set_title(GTK_WINDOW(a),_("ALSA Preferences"));
74      gtk_window_set_modal(GTK_WINDOW(a),TRUE);
75      gtk_window_set_position(GTK_WINDOW(a),GTK_WIN_POS_MOUSE);
76      gtk_container_set_border_width(GTK_CONTAINER(a),5);
77      gtk_signal_connect(GTK_OBJECT(a),"delete_event",
78 			GTK_SIGNAL_FUNC(gtk_false),NULL);
79      b = gtk_table_new(4,2,FALSE);
80      gtk_container_add(GTK_CONTAINER(a),b);
81      attach_label(_("Playback device: "),b,0,0);
82      c = gtk_entry_new();
83      gtk_entry_set_text(GTK_ENTRY(c),inifile_get("ALSAPlayDevice","default"));
84      gtk_table_attach(GTK_TABLE(b),c,1,2,0,1,GTK_EXPAND|GTK_FILL,0,5,5);
85      attach_label(_("Recording device: "),b,1,0);
86      gtk_object_set_data(GTK_OBJECT(a),"playentry",c);
87      c = gtk_entry_new();
88      gtk_entry_set_text(GTK_ENTRY(c),inifile_get("ALSARecDevice","hw"));
89      gtk_table_attach(GTK_TABLE(b),c,1,2,1,2,GTK_EXPAND|GTK_FILL,0,5,5);
90      gtk_object_set_data(GTK_OBJECT(a),"recentry",c);
91      c = gtk_check_button_new_with_label(_("Event driven I/O"));
92      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c),alsa_data.eventdriv);
93      gtk_table_attach(GTK_TABLE(b),c,0,2,2,3,GTK_EXPAND|GTK_FILL,0,5,5);
94      gtk_object_set_data(GTK_OBJECT(a),"eventtb",c);
95      c = gtk_hbutton_box_new();
96      gtk_table_attach(GTK_TABLE(b),c,0,2,3,4,GTK_EXPAND|GTK_FILL,
97 		      GTK_EXPAND|GTK_FILL,0,5);
98      d = gtk_button_new_with_label(_("OK"));
99      gtk_signal_connect(GTK_OBJECT(d),"clicked",GTK_SIGNAL_FUNC(alsa_prefs_ok),
100 			a);
101      gtk_container_add(GTK_CONTAINER(c),d);
102      d = gtk_button_new_with_label(_("Cancel"));
103      gtk_signal_connect_object(GTK_OBJECT(d),"clicked",
104 			       GTK_SIGNAL_FUNC(gtk_widget_destroy),
105 			       GTK_OBJECT(a));
106      gtk_container_add(GTK_CONTAINER(c),d);
107      gtk_widget_show_all(a);
108 }
109 
alsa_open_rw(snd_pcm_t ** handp,Dataformat * fmtp,gchar * devini,snd_pcm_stream_t str,gboolean silent)110 static gboolean alsa_open_rw(snd_pcm_t **handp, Dataformat *fmtp,
111 			     gchar *devini, snd_pcm_stream_t str,
112 			     gboolean silent)
113 {
114      int i;
115      gchar *c,*devname;
116      if (*handp == NULL) {
117 	  devname = inifile_get(devini,"default");
118 	  i = snd_pcm_open(handp,devname,str,
119 			   SND_PCM_NONBLOCK);
120 	  if (i<0) {
121 	       if (!silent) {
122 		    c = g_strdup_printf((str == SND_PCM_STREAM_CAPTURE)?
123 					_("Error opening ALSA device '%s' "
124 					  "for recording: %s"):
125 					_("Error opening ALSA device '%s' "
126 					  "for playback: %s"), devname,
127 					snd_strerror(i));
128 		    user_error(c);
129 		    g_free(c);
130 	       }
131 	       return TRUE;
132 	  }
133 	  memset(fmtp,0,sizeof(*fmtp));
134      }
135      return FALSE;
136 }
137 
alsa_open_write(gboolean silent)138 static gboolean alsa_open_write(gboolean silent)
139 {
140      return alsa_open_rw(&(alsa_data.whand),&(alsa_data.wfmt),
141 			 "ALSAPlayDevice",SND_PCM_STREAM_PLAYBACK,silent);
142 }
143 
alsa_open_read(gboolean silent)144 static gboolean alsa_open_read(gboolean silent)
145 {
146      return alsa_open_rw(&(alsa_data.rhand),&(alsa_data.rfmt),
147 			 "ALSARecDevice",SND_PCM_STREAM_CAPTURE,silent);
148 }
149 
alsa_get_format(Dataformat * format)150 static snd_pcm_format_t alsa_get_format(Dataformat *format)
151 {
152      if (format->type == DATAFORMAT_FLOAT) {
153 	  if (format->samplesize == sizeof(float))
154 	       return format->bigendian ? SND_PCM_FORMAT_FLOAT_BE :
155 		    SND_PCM_FORMAT_FLOAT_LE;
156 	  else
157 	       return format->bigendian ? SND_PCM_FORMAT_FLOAT64_BE :
158 		    SND_PCM_FORMAT_FLOAT64_LE;
159      }
160      switch (format->samplesize) {
161      case 1:
162 	  if (format->sign)
163 	       return SND_PCM_FORMAT_S8;
164 	  else
165 	       return SND_PCM_FORMAT_U8;
166      case 2:
167 	  if (format->sign)
168 	       return format->bigendian?SND_PCM_FORMAT_S16_BE:
169 		    SND_PCM_FORMAT_S16_LE;
170 	  else
171 	       return format->bigendian ? SND_PCM_FORMAT_U16_BE :
172 		    SND_PCM_FORMAT_U16_LE;
173      case 3:
174 	  if (format->sign)
175 	       return format->bigendian ? SND_PCM_FORMAT_S24_3BE :
176 		    SND_PCM_FORMAT_S24_3LE;
177 	  else
178 	       return format->bigendian ? SND_PCM_FORMAT_U24_3BE :
179 		    SND_PCM_FORMAT_U24_3LE;
180      case 4:
181 	  switch (format->packing) {
182 	  case 0:
183 	  case 1: /* Use 32-bit for 24-in-32 with data in MSB */
184 	       if (format->sign)
185 		    return format->bigendian?
186 			 SND_PCM_FORMAT_S32_BE:SND_PCM_FORMAT_S32_LE;
187 	       else
188 		    return format->bigendian?
189 			 SND_PCM_FORMAT_U32_BE:SND_PCM_FORMAT_U32_LE;
190 	  case 2:
191 	       if (format->sign)
192 		    return format->bigendian?
193 			 SND_PCM_FORMAT_S24_BE:SND_PCM_FORMAT_S24_LE;
194 	       else
195 		    return format->bigendian?
196 			 SND_PCM_FORMAT_U24_BE:SND_PCM_FORMAT_S24_LE;
197 	  }
198      }
199      g_assert_not_reached();
200      return SND_PCM_FORMAT_UNKNOWN;
201 }
202 
csource_func(gpointer csource,gpointer user_data)203 static int csource_func(gpointer csource, gpointer user_data)
204 {
205      int i;
206 #ifdef ALSADEBUG
207      puts("csource_func");
208 #endif
209      i = alsa_data.rw_call_count;
210      alsa_data.inside_ready_func = TRUE;
211      alsa_data.ready_func();
212      alsa_data.inside_ready_func = FALSE;
213      if (alsa_data.rw_call_count == i) {
214 	  mainloop_constant_source_enable(csource,FALSE);
215 	  return -1;
216      }
217      if (alsa_output_want_data()) {
218 	  return 0;
219      }
220      mainloop_constant_source_enable(csource,FALSE);
221      mainloop_io_group_enable(alsa_data.iogroup,TRUE);
222      return -1;
223 }
224 
iogroup_ready_func(gpointer iogroup,int fd,gushort revents,gpointer user_data)225 static int iogroup_ready_func(gpointer iogroup, int fd, gushort revents,
226 			       gpointer user_data)
227 {
228      int i;
229 #ifdef ALSADEBUG
230      GTimeVal tv;
231      g_get_current_time(&tv);
232      printf("iogroup_ready_func: time=%3d.%06d, fd=%d, revents=%d\n",
233 	    (int)tv.tv_sec,(int)tv.tv_usec,(int)fd,(int)revents);
234 #endif
235      if (alsa_data.whand!=NULL && !alsa_output_want_data()) {
236 #ifdef ALSADEBUG
237 	  printf("Alsa woke us up but didn't want data\n");
238 #endif
239 	  return 0;
240      }
241      i = alsa_data.rw_call_count;
242      alsa_data.inside_ready_func = TRUE;
243      alsa_data.ready_func();
244      alsa_data.inside_ready_func = FALSE;
245      if (alsa_data.rw_call_count == i) return -1;
246      if (!alsa_data.eventdriv && alsa_data.whand!=NULL && alsa_output_want_data()) {
247 	  if (alsa_data.csource == NULL)
248 	       alsa_data.csource = mainloop_constant_source_add(csource_func,NULL,FALSE);
249 	  mainloop_constant_source_enable(alsa_data.csource,TRUE);
250 	  return -1;
251      }
252      return 1;
253 }
254 
alsa_set_format(Dataformat * format,Dataformat * fmtp,snd_pcm_t ** handp,gboolean playback,GVoidFunc ready_func)255 static gboolean alsa_set_format(Dataformat *format,Dataformat *fmtp,
256 				snd_pcm_t **handp,gboolean playback,
257 				GVoidFunc ready_func)
258 {
259      snd_pcm_hw_params_t *par;
260      int i;
261      snd_pcm_uframes_t bufsize;
262      unsigned int pertime,wdtime;
263      int perdir;
264      int fd_count;
265      struct pollfd *fds;
266      GPollFD *pfds;
267      if (dataformat_equal(fmtp,format)) return FALSE;
268      snd_pcm_hw_params_malloc(&par);
269      snd_pcm_hw_params_any(*handp,par);
270      i = snd_pcm_hw_params_set_access(*handp,par,
271 				      SND_PCM_ACCESS_RW_INTERLEAVED);
272      if (i) {
273 	  console_message(_("snd_pcm_hw_params_set_access failed"));
274 	  return TRUE;
275      }
276      i = snd_pcm_hw_params_set_format(*handp,par,alsa_get_format(format));
277      if (i) {
278 	  console_message(_("snd_pcm_hw_params_set_format failed"));
279 	  return TRUE;
280      }
281      i = snd_pcm_hw_params_set_channels(*handp,par,format->channels);
282      if (i) {
283 	  console_message(_("snd_pcm_hw_params_set_channels failed"));
284 	  return TRUE;
285      }
286      i = snd_pcm_hw_params_set_rate(*handp,par,format->samplerate,0);
287      if (i) {
288 	  console_message(_("snd_pcm_hw_params_set_rate failed"));
289 	  return TRUE;
290      }
291      i = snd_pcm_hw_params_set_buffer_size_last(*handp,par,&bufsize);
292      if (i) {
293 	  console_message(_("snd_pcm_hw_params_set_buffer_size_last failed"));
294 	  return TRUE;
295      }
296 #ifdef ALSADEBUG
297      printf("Buffer size: %d\n",(int)bufsize);
298 #endif
299      pertime = 100001; /* 0.1 sec */
300      perdir = -1;
301      if (playback) {
302 	  i = snd_pcm_hw_params_set_period_time_near(*handp,par,&pertime,&perdir);
303 	  if (i) {
304 	       console_message("snd_pcm_hw_params_set_period_time_near failed");
305 	       return TRUE;
306 	  }
307 #ifdef ALSADEBUG
308 	  printf("Period time: %d us\n",pertime);
309 #endif
310      }
311      i = snd_pcm_hw_params(*handp,par);
312      snd_pcm_hw_params_free(par);
313      if (i<0) {
314 	  printf("snd_pcm_hw_params: %s\n",snd_strerror(i));
315 	  memset(fmtp,0,sizeof(*fmtp));
316 	  return TRUE;
317      }
318 
319      memcpy(fmtp,format,sizeof(*fmtp));
320      alsa_data.ready_func = ready_func;
321 
322      fd_count = snd_pcm_poll_descriptors_count(*handp);
323      fds = g_malloc(fd_count*sizeof(*fds));
324      fd_count = snd_pcm_poll_descriptors(*handp,fds,fd_count*sizeof(*fds));
325 
326      pfds = g_malloc(fd_count*sizeof(GPollFD));
327      for (i=0; i<fd_count; i++) {
328 	  pfds[i].fd = fds[i].fd;
329 	  pfds[i].events = fds[i].events;
330      }
331 
332      g_assert(alsa_data.iogroup == NULL);
333      if (alsa_data.eventdriv)
334 	  wdtime = pertime + 50000;
335      else {
336 	  wdtime = pertime;
337 	  fd_count = 0;
338      }
339      alsa_data.iogroup = mainloop_io_group_add(fd_count,pfds,wdtime/1000,
340 					       iogroup_ready_func,NULL);
341      alsa_data.rw_call_count = 0;
342      g_free(pfds);
343      g_free(fds);
344 
345      return FALSE;
346 }
347 
alsa_needs_polling(void)348 static gboolean alsa_needs_polling(void)
349 {
350      return FALSE;
351 }
352 
alsa_set_write_format(Dataformat * format,GVoidFunc ready_func)353 static gboolean alsa_set_write_format(Dataformat *format, GVoidFunc ready_func)
354 {
355      return alsa_set_format(format,&(alsa_data.wfmt),&(alsa_data.whand),TRUE,
356 			    ready_func);
357 }
358 
alsa_set_read_format(Dataformat * format,GVoidFunc ready_func)359 static gboolean alsa_set_read_format(Dataformat *format, GVoidFunc ready_func)
360 {
361      return alsa_set_format(format,&(alsa_data.rfmt),&(alsa_data.rhand),FALSE,
362 			    ready_func);
363 }
364 
alsa_output_select_format(Dataformat * format,gboolean silent,GVoidFunc ready_func)365 static gint alsa_output_select_format(Dataformat *format, gboolean silent,
366 				      GVoidFunc ready_func)
367 {
368      /* signal(SIGPIPE,SIG_IGN); */
369      if (alsa_open_write(silent)) return silent?-1:+1;
370      if (alsa_set_write_format(format,ready_func)) {
371 	  snd_pcm_close(alsa_data.whand);
372 	  alsa_data.whand = NULL;
373 	  return -1;
374      }
375      return 0;
376 }
377 
alsa_input_select_format(Dataformat * format,gboolean silent,GVoidFunc ready_func)378 static gint alsa_input_select_format(Dataformat *format, gboolean silent,
379 				     GVoidFunc ready_func)
380 {
381      if (alsa_open_read(silent)) return silent?-1:+1;
382      if (alsa_set_read_format(format,ready_func)) {
383 	  snd_pcm_close(alsa_data.rhand);
384 	  alsa_data.rhand = NULL;
385 	  return -1;
386      }
387      alsa_data.draining = alsa_data.draining_done = FALSE;
388      return 0;
389 }
390 
alsa_stop_iosource(void)391 static void alsa_stop_iosource(void)
392 {
393      if (alsa_data.iogroup != NULL) {
394 	  mainloop_io_group_free(alsa_data.iogroup);
395 	  alsa_data.iogroup = NULL;
396      }
397 }
398 
alsa_output_stop(gboolean must_flush)399 static gboolean alsa_output_stop(gboolean must_flush)
400 {
401      alsa_stop_iosource();
402      if (alsa_data.whand != NULL) {
403 	  if (must_flush)
404 	       snd_pcm_drain(alsa_data.whand);
405 	  else
406 	       snd_pcm_drop(alsa_data.whand);
407 	  snd_pcm_close(alsa_data.whand);
408 	  alsa_data.whand = NULL;
409      }
410      return must_flush;
411 }
412 
alsa_input_stop(void)413 static void alsa_input_stop(void)
414 {
415      alsa_stop_iosource();
416      if (alsa_data.rhand != NULL) {
417 	  snd_pcm_close(alsa_data.rhand);
418 	  alsa_data.rhand = NULL;
419      }
420 }
421 
alsa_input_stop_hint(void)422 static void alsa_input_stop_hint(void)
423 {
424      if (!alsa_data.draining) {
425 	  snd_pcm_drain(alsa_data.rhand);
426 	  alsa_data.draining = TRUE;
427      }
428 }
429 
alsa_output_want_data(void)430 static gboolean alsa_output_want_data(void)
431 {
432      snd_pcm_sframes_t s;
433      /* signal(SIGPIPE,SIG_IGN); */
434      s = snd_pcm_avail_update(alsa_data.whand);
435      if (s == -EPIPE) {
436 	  puts(_("<ALSA playback buffer underrun>"));
437 	  snd_pcm_prepare(alsa_data.whand);
438 	  return TRUE;
439      } else if (s<0) {
440 	  printf("snd_pcm_avail_update: %s\n",snd_strerror(s));
441 	  return FALSE;
442      }
443      /* return (s > alsa_data.wfmt.samplerate / 250); */
444      return (s > 0);
445 }
446 
alsa_output_play(gchar * buffer,guint bufsize)447 static guint alsa_output_play(gchar *buffer, guint bufsize)
448 {
449      snd_pcm_sframes_t r,s;
450      guint u;
451 #ifdef ALSADEBUG
452      GTimeVal tv;
453 #endif
454      /* signal(SIGPIPE,SIG_IGN); */
455      if (bufsize == 0) return 0;
456      if (!alsa_data.inside_ready_func)
457 	  mainloop_io_group_enable(alsa_data.iogroup,TRUE);
458      alsa_data.rw_call_count ++;
459 #ifdef ALSADEBUG
460      g_get_current_time(&tv);
461      if (!alsa_data.inside_ready_func)
462 	  printf("called output_play, %d bytes, re-enabled events, "
463 		 "time=%3d.%06d\n",
464 		 (int)bufsize,(int)tv.tv_sec,(int)tv.tv_usec);
465      else
466 	  printf("called output_play from ready-func, %d bytes, "
467 		 "time=%3d.%06d\n",
468 		 (int)bufsize,(int)tv.tv_sec,(int)tv.tv_usec);
469 #endif
470      s = bufsize/alsa_data.wfmt.samplebytes;
471      while (1) {
472 	  r = snd_pcm_writei(alsa_data.whand,buffer,s);
473 	  if (r == -EAGAIN || r == 0) {
474 #ifdef ALSADEBUG
475 	       puts("snd_pcm_writei: EAGAIN!");
476 #endif
477 	       r = snd_pcm_avail_update(alsa_data.whand);
478 	       if (r > 0 && r<s) {
479 		    s = r;
480 		    continue;
481 	       }
482 
483 	       if (snd_pcm_state(alsa_data.whand) == SND_PCM_STATE_PREPARED) {
484 #ifdef ALSADEBUG
485 		    puts("Starting stream");
486 #endif
487 		    snd_pcm_start(alsa_data.whand);
488 	       }
489 	       return 0;
490 	  }
491 	  if (r == -EPIPE) {
492 	       puts(_("<ALSA playback buffer underrun>"));
493 	       snd_pcm_prepare(alsa_data.whand);
494 	       continue;
495 	  }
496 	  if (r < 0) {
497 	       printf("snd_pcm_writei: %s\n",snd_strerror(r));
498 	       return 0;
499 	  }
500 	  break;
501      }
502      u = r*alsa_data.wfmt.samplebytes;
503 #ifdef ALSADEBUG
504      g_get_current_time(&tv);
505      printf("played %d samples time=%3d.%06d\n",(int)r,(int)tv.tv_sec,
506 	    (int)tv.tv_usec);
507 #endif
508      return u;
509 }
510 
alsa_output_clear_buffers(void)511 static void alsa_output_clear_buffers(void)
512 {
513      /* signal(SIGPIPE,SIG_IGN); */
514      snd_pcm_drop(alsa_data.whand);
515      snd_pcm_prepare(alsa_data.whand);
516 }
517 
alsa_input_store(Ringbuf * buffer)518 static void alsa_input_store(Ringbuf *buffer)
519 {
520      gchar buf[4096];
521      snd_pcm_sframes_t x;
522      if (alsa_data.draining_done) return;
523      alsa_data.rw_call_count ++;
524      while (1) {
525 	  x = snd_pcm_readi(alsa_data.rhand,buf,
526 			    MIN(sizeof(buf),ringbuf_freespace(buffer))/
527 			    alsa_data.rfmt.samplebytes);
528 	  if (x > 0)
529 	       ringbuf_enqueue(buffer,buf,x*alsa_data.rfmt.samplebytes);
530 	  else if (alsa_data.draining) {
531 	       alsa_data.draining_done = TRUE;
532 	       return;
533 	  } else if (x == 0 || x == -EAGAIN)
534 	       return;
535 	  else if (x == -EPIPE) {
536 	       puts(_("<ALSA recording buffer overrun>"));
537 	       alsa_data.overrun_count ++;
538 	       snd_pcm_prepare(alsa_data.rhand);
539 	  } else {
540 	       printf("snd_pcm_readi: %s\n",snd_strerror(x));
541 	       snd_pcm_prepare(alsa_data.rhand);
542 	       return;
543 	  }
544      }
545      mainloop_io_group_enable(alsa_data.iogroup,TRUE);
546 }
547 
alsa_input_overrun_count(void)548 int alsa_input_overrun_count(void)
549 {
550      return alsa_data.overrun_count;
551 }
552