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