1 /*
2  * Copyright (C) 2002 2003 2004 2005 2006 2008 2010, 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 
22 #if defined(HAVE_SYS_SOUNDCARD_H)
23 #include <sys/soundcard.h>
24 #elif defined(HAVE_SOUNDCARD_H)
25 #include <soundcard.h>
26 #else
27 #warning "Header file not found for OSS driver!"
28 #endif
29 
30 #include <stdlib.h>
31 #include <string.h>
32 #include <fcntl.h>
33 #include <errno.h>
34 #include <unistd.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <sys/time.h>
38 #include <sys/types.h>
39 
40 #include <gtk/gtk.h>
41 
42 #include "ringbuf.h"
43 #include "um.h"
44 #include "main.h"
45 #include "gettext.h"
46 #include "mainloop.h"
47 
48 #define OSS_PCMFILE_PLAYBACK "OSSdevice"
49 #define OSS_PCMFILE_RECORD "OSSRecDevice"
50 
51 #ifdef __OpenBSD__
52 #define OSS_PCMFILE_DEFAULT "/dev/audio"
53 #else
54 #define OSS_PCMFILE_DEFAULT "/dev/dsp"
55 #endif
56 
57 #define OSS_NOSELECT "OSSAvoidSelect"
58 #define OSS_NOSELECT_DEFAULT FALSE
59 
60 static Ringbuf *oss_output_buffer;
61 static int oss_fd;
62 static int oss_format;
63 static int oss_samplesize;
64 static int oss_samplerate;
65 static int oss_channels;
66 static int oss_samplebytes_in,oss_samplebytes_out;
67 static gboolean oss_noselect = FALSE;
68 static gpointer play_source;
69 
70 static int oss_output_flush(gpointer tsource, GTimeVal *tv, gpointer user_data);
71 
oss_init(gboolean silent)72 static gboolean oss_init(gboolean silent)
73 {
74      oss_output_buffer = ringbuf_new(
75 	  inifile_get_guint32(INI_SETTING_SOUNDBUFSIZE,
76 			      INI_SETTING_SOUNDBUFSIZE_DEFAULT));
77      oss_noselect = inifile_get_gboolean(OSS_NOSELECT,OSS_NOSELECT_DEFAULT);
78      play_source = mainloop_time_source_add(NULL, oss_output_flush, NULL);
79      /* When autodetecting, this is the last tested driver before the dummy,
80       * so we might as well return TRUE */
81      return TRUE;
82 }
83 
oss_quit(void)84 static void oss_quit(void)
85 {
86      mainloop_time_source_free(play_source);
87      ringbuf_free(oss_output_buffer);
88 }
89 
oss_errdlg_open(char * filename,int flags,gboolean silent)90 static int oss_errdlg_open(char *filename, int flags, gboolean silent)
91 {
92      int fd;
93      gchar *c;
94      fd = open(filename,flags);
95      if (fd == -1 && !silent) {
96 	  c = g_strdup_printf(_("Could not open '%s': %s"),filename,strerror(errno));
97 	  user_error(c);
98 	  g_free(c);
99      }
100      return fd;
101 }
102 
oss_errdlg_ioctl(int filedes,int cmd,void * arg,gboolean silent)103 static int oss_errdlg_ioctl(int filedes, int cmd, void *arg, gboolean silent)
104 {
105      int i;
106      gchar *c;
107      i = ioctl(filedes,cmd,arg);
108      if (i == -1 && errno != EPIPE && !silent) {
109 	  c = g_strdup_printf(_("Error in sound driver: ioctl failed: %s"),strerror(errno));
110 	  user_error(c);
111 	  g_free(c);
112      }
113      return i;
114 }
115 
oss_try_format(Dataformat * format,gboolean input,gboolean silent)116 static gint oss_try_format(Dataformat *format, gboolean input, gboolean silent)
117 {
118      gchar *fname;
119      int i,j;
120      /* For now, we just refuse floating-point format. We should probably
121       * use a fallback format instead */
122      if (format->type == DATAFORMAT_FLOAT) return -1;
123      /* Determine the right format */
124      oss_samplesize = format->samplesize;
125      switch (oss_samplesize) {
126      case 1:
127 	  oss_format = (format->sign) ? AFMT_S8 : AFMT_U8;
128 	  break;
129      case 2:
130 	  if (!format->bigendian)
131 	       oss_format = (format->sign) ? AFMT_S16_LE : AFMT_U16_LE;
132 	  else
133 	       oss_format = (format->sign) ? AFMT_S16_BE : AFMT_U16_BE;
134 	  break;
135      case 3:
136      case 4:
137 	  if (!format->sign) return -1;
138 	  if (format->packing == 2) return -1;
139 	  /* This is really hairy, but the AFMT_S32-constants don't seem to be
140 	   * defined in all soundcard.h files */
141 	  if (format->bigendian) {
142 #ifdef AFMT_S32_BE
143 	       oss_format = AFMT_S32_BE;
144 #else
145 	       return -1;
146 #endif
147 	  } else {
148 #ifdef AFMT_S32_LE
149 	  oss_format = AFMT_S32_LE;
150 #else
151 	  return -1;
152 #endif
153 	  }
154 	  break;
155      default:
156 	  g_assert_not_reached();
157      }
158      /* Open the file */
159      fname = inifile_get(OSS_PCMFILE_PLAYBACK,OSS_PCMFILE_DEFAULT);
160      if (input)
161 	  fname = inifile_get(OSS_PCMFILE_RECORD, fname);
162      oss_fd = oss_errdlg_open(fname, input ? O_RDONLY : O_WRONLY, silent);
163      if (oss_fd == -1) return silent?-1:+1;
164      /* Try to set the format */
165      j = oss_format;
166      i = oss_errdlg_ioctl(oss_fd, SNDCTL_DSP_SETFMT, &j, silent);
167      if (i == -1 || j != oss_format) {
168 	  close(oss_fd);
169 	  oss_fd=-1;
170 	  return (i != -1 || silent)?-1:+1;
171      }
172      /* Try to set the number of channels */
173      j = oss_channels = format->channels;
174      i = oss_errdlg_ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &j, silent);
175      if (i==-1 || j != oss_channels) {
176 	  close(oss_fd);
177 	  oss_fd=-1;
178 	  return (i != -1 || silent)?-1:+1;
179      }
180      /* Try to set the sample rate */
181      j = oss_samplerate = format->samplerate;
182      i = oss_errdlg_ioctl(oss_fd, SNDCTL_DSP_SPEED, &j, silent);
183      /* FIXME: Variable tolerance (esp. for input) */
184      /* Currently tolerates 5% difference between requested/received samplerate
185       */
186      if (i==-1 || abs(j-oss_samplerate) > oss_samplerate/20) {
187 	  close(oss_fd);
188 	  oss_fd=-1;
189 	  return (i != -1 || silent)?-1:+1;
190      }
191      /* Everything went well! */
192      oss_samplebytes_in = oss_samplebytes_out = oss_channels * oss_samplesize;
193      if (oss_samplesize == 3)
194 	  oss_samplebytes_out += oss_channels;
195      return 0;
196 }
197 
oss_output_select_format(Dataformat * format,gboolean silent,GVoidFunc ready_func)198 static gint oss_output_select_format(Dataformat *format, gboolean silent,
199 				     GVoidFunc ready_func)
200 {
201      return oss_try_format(format,FALSE,silent);
202 }
203 
oss_input_select_format(Dataformat * format,gboolean silent,GVoidFunc ready_func)204 static gint oss_input_select_format(Dataformat *format, gboolean silent,
205 				    GVoidFunc ready_func)
206 {
207      return oss_try_format(format,TRUE,silent);
208 }
209 
oss_errdlg_write(int fd,gchar * buffer,size_t size)210 static int oss_errdlg_write(int fd, gchar *buffer, size_t size)
211 {
212      ssize_t s;
213      gchar *c;
214      s = write(fd,buffer,size);
215      if (s == -1) {
216 	  c = g_strdup_printf(_("Error in sound driver: write failed: %s"),strerror(errno));
217 	  user_error(c);
218 	  g_free(c);
219      }
220      return s;
221 }
222 
oss_errdlg_read(int fd,gchar * buffer,size_t size)223 static int oss_errdlg_read(int fd, gchar *buffer, size_t size)
224 {
225      ssize_t s;
226      gchar *c;
227      s = read(fd,buffer,size);
228      if (s == -1) {
229 	  c = g_strdup_printf(_("Error in sound driver: read failed: %s"),strerror(errno));
230 	  user_error(c);
231 	  g_free(c);
232      }
233      return s;
234 }
235 
oss_output_flush(gpointer ts,GTimeVal * tv,gpointer ud)236 static int oss_output_flush(gpointer ts, GTimeVal *tv, gpointer ud)
237 {
238      fd_set a;
239      struct timeval t = { 0 };
240      int i;
241      guint u;
242      gchar *c;
243      audio_buf_info info;
244      /* printf("oss_output_flush: ringbuf_available: %d\n",ringbuf_available(oss_output_buffer)); */
245      /* Do we have anything to write at all? */
246      if (ringbuf_available(oss_output_buffer) == 0) return 0;
247      /* Do a select call and see if it's ready for writing */
248      if (!oss_noselect) {
249 	  FD_ZERO(&a);
250 	  FD_SET(oss_fd, &a);
251 	  i = select(FD_SETSIZE, NULL, &a, NULL, &t);
252 	  if (i==-1) {
253 	       c = g_strdup_printf(_("Error in sound driver: select failed: %s"),strerror(errno));
254 	       user_error(c);
255 	       return 2000;
256 	  }
257 	  if (i==0) return 50;
258      }
259 #ifdef SNDCTL_DSP_GETOSPACE
260      /* Now do an ioctl call to check the number of writable bytes */
261      i = oss_errdlg_ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &info, TRUE);
262      if (i == -1 && errno == EPIPE) {
263 	  /* This is a workaround for a bug in ALSA 0.9.6 OSS emulation, where
264 	   * this call can return "broken pipe", especially after a
265 	   * SNDCTL_DSP_RESET call. */
266 	  info.fragments = 1024;
267 	  info.fragsize = 1*2*3*4;
268      } else if (i == -1) return 0;
269 #else
270      /* Fill in dummy values */
271      info.fragments = 1024;
272      info.fragsize = 1*2*3*4;
273 #endif
274      /* Calculate the number of bytes to write (into u) */
275      u = ringbuf_available(oss_output_buffer);
276      if (u > info.fragsize)
277 	  u -= u % info.fragsize;
278      u = MIN(u, info.fragments*info.fragsize);
279      if (u == 0) return 50;
280      /* Write it out! */
281      c = g_malloc(u);
282      ringbuf_dequeue(oss_output_buffer, c, u);
283      i = oss_errdlg_write(oss_fd, c, u);
284      g_free(c);
285      return 10;
286 }
287 
oss_output_want_data(void)288 static gboolean oss_output_want_data(void)
289 {
290      /* oss_output_flush(); */
291      return (ringbuf_freespace(oss_output_buffer) >= oss_samplebytes_out);
292 }
293 
oss_output_play(gchar * buffer,guint bufsize)294 static guint oss_output_play(gchar *buffer, guint bufsize)
295 {
296      guint u=0;
297      GTimeVal t;
298      guint bs0 = bufsize;
299      /* 24-bit samples need padding */
300      if (oss_samplesize == 3) {
301 	  while (bufsize >= 3 && ringbuf_freespace(oss_output_buffer)>=oss_samplebytes_out) {
302 	       for (u=0; u<oss_channels; u++) {
303 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
304 		    ringbuf_enqueue(oss_output_buffer,"",1);
305 		    ringbuf_enqueue(oss_output_buffer,buffer,3);
306 #else
307 		    ringbuf_enqueue(oss_output_buffer,buffer,3);
308 		    ringbuf_enqueue(oss_output_buffer,"",1);
309 #endif
310 		    buffer += 3;
311 		    bufsize -= 3;
312 		    u += 3;
313 	       }
314 	  }
315      }
316      /* Normal operation */
317      else if (bufsize) {
318 	  u = ringbuf_freespace(oss_output_buffer);
319 	  if (u > bufsize) u = bufsize;
320 	  /* Make sure we store whole samples */
321 	  u -= u%(oss_samplebytes_out);
322 	  if (u>0)
323 	       u = ringbuf_enqueue(oss_output_buffer, buffer, u);
324 	  buffer += u;
325 	  bufsize -= u;
326      }
327      if (ringbuf_freespace(oss_output_buffer) < oss_samplebytes_out &&
328 	 !mainloop_time_source_enabled(play_source)) {
329 	  puts("Starting time source");
330 	  g_get_current_time(&t);
331 	  mainloop_time_source_restart(play_source,&t);
332      }
333      g_assert(bufsize == 0 || bufsize >= oss_samplebytes_in);
334      printf("oss_output_play, bs0: %d, u: %d\n",bs0,u);
335      /* oss_output_flush(); */
336      return bs0 ? u : ringbuf_available(oss_output_buffer);
337 }
338 
oss_output_clear_buffers(void)339 static void oss_output_clear_buffers(void)
340 {
341 #ifdef SNDCTL_DSP_RESET
342      ioctl(oss_fd, SNDCTL_DSP_RESET);
343 #endif
344      ringbuf_drain(oss_output_buffer);
345 }
346 
347 
oss_output_stop(gboolean must_flush)348 static gboolean oss_output_stop(gboolean must_flush)
349 {
350      if (oss_fd == -1) return TRUE;
351      if (must_flush)
352 	  while (ringbuf_available(oss_output_buffer)>0) {
353 	       oss_output_flush(NULL,NULL,NULL);
354 	       do_yield(TRUE);
355 	  }
356      else
357 	  oss_output_clear_buffers();
358      close(oss_fd);
359      oss_fd = -1;
360      /* printf("oss_stop: oss_delay_time = %f\n",oss_delay_time); */
361      ringbuf_drain(oss_output_buffer);
362      return must_flush;
363 }
364 
oss_input_stop(void)365 static void oss_input_stop(void)
366 {
367      if (oss_fd >= 0) close(oss_fd);
368      oss_fd = -1;
369 }
370 
oss_input_store(Ringbuf * buffer)371 static void oss_input_store(Ringbuf *buffer)
372 {
373      fd_set a;
374      struct timeval t = { 0 };
375      int i;
376      guint u;
377      gchar *c;
378      audio_buf_info info;
379      /* Do a select call to see if data is available */
380      if (!oss_noselect) {
381 	  FD_ZERO(&a);
382 	  FD_SET(oss_fd, &a);
383 	  i = select(FD_SETSIZE, &a, NULL, NULL, &t);
384 	  if (i==-1) {
385 	       c = g_strdup_printf(_("Error in sound driver: select failed: %s"),strerror(errno));
386 	       user_error(c);
387 	       return;
388 	  }
389 	  if (i==0) return;
390      }
391 
392      /* Calculate the number of bytes to read (into buffer) */
393      /* If we don't know how much data is available, don't read more than 0.1
394       * seconds to avoid GUI latency... */
395      u = ringbuf_freespace(buffer);
396 #ifdef SNDCTL_DSP_GETISPACE
397      /* Now do an ioctl call to check the number of readable bytes */
398      /* Note: It seems as ALSA 0.9's OSS emulation returns 0 for this call... */
399      i = oss_errdlg_ioctl(oss_fd, SNDCTL_DSP_GETISPACE, &info, TRUE);
400      if (i > 0 && i < u) u=i;
401      else u = MIN(u,oss_samplesize*oss_channels*oss_samplerate/50);
402 #else
403      u = MIN(u,oss_samplesize*oss_channels*oss_samplerate/50);
404 #endif
405      if (u == 0) return;
406      /* Read it! */
407      c = g_malloc(u);
408      i = oss_errdlg_read(oss_fd, c, u);
409      if (i != -1)
410 	  ringbuf_enqueue(buffer, c, i);
411      g_free(c);
412 }
413 
414 struct oss_prefdlg {
415      GtkWindow *wnd;
416      GtkEntry *pcmdev_playback;
417      GtkEntry *pcmdev_record;
418      GtkToggleButton *noselect;
419 };
420 
oss_preferences_ok(GtkButton * button,struct oss_prefdlg * pd)421 static void oss_preferences_ok(GtkButton *button, struct oss_prefdlg *pd)
422 {
423      inifile_set(OSS_PCMFILE_PLAYBACK,
424 		 (char *)gtk_entry_get_text(pd->pcmdev_playback));
425      inifile_set(OSS_PCMFILE_RECORD,
426 		 (char *)gtk_entry_get_text(pd->pcmdev_record));
427      oss_noselect = gtk_toggle_button_get_active(pd->noselect);
428      inifile_set_gboolean(OSS_NOSELECT,oss_noselect);
429      gtk_widget_destroy(GTK_WIDGET(pd->wnd));
430 }
431 
oss_preferences(void)432 static void oss_preferences(void)
433 {
434      GtkWidget *a,*b,*c,*d;
435      struct oss_prefdlg *pd;
436      gchar *q;
437      pd = g_malloc(sizeof(struct oss_prefdlg));
438      a = gtk_window_new(GTK_WINDOW_DIALOG);
439      gtk_window_set_modal(GTK_WINDOW(a),TRUE);
440      gtk_window_set_title(GTK_WINDOW(a),_("OSS preferences"));
441      gtk_window_set_position(GTK_WINDOW(a),GTK_WIN_POS_CENTER);
442      gtk_container_set_border_width(GTK_CONTAINER(a),5);
443      gtk_signal_connect_object(GTK_OBJECT(a),"destroy",GTK_SIGNAL_FUNC(g_free),
444 			       (GtkObject *)pd);
445      pd->wnd = GTK_WINDOW(a);
446      b = gtk_vbox_new(FALSE,5);
447      gtk_container_add(GTK_CONTAINER(a),b);
448      c = gtk_hbox_new(FALSE,3);
449      gtk_container_add(GTK_CONTAINER(b),c);
450      d = gtk_label_new(_("Playback device file:"));
451      gtk_container_add(GTK_CONTAINER(c),d);
452      d = gtk_entry_new();
453      q = inifile_get(OSS_PCMFILE_PLAYBACK,OSS_PCMFILE_DEFAULT);
454      gtk_entry_set_text(GTK_ENTRY(d),q);
455      gtk_container_add(GTK_CONTAINER(c),d);
456      pd->pcmdev_playback = GTK_ENTRY(d);
457      c = gtk_hbox_new(FALSE,3);
458      gtk_container_add(GTK_CONTAINER(b),c);
459      d = gtk_label_new(_("Recording device file:"));
460      gtk_container_add(GTK_CONTAINER(c),d);
461      d = gtk_entry_new();
462      gtk_entry_set_text(GTK_ENTRY(d),inifile_get(OSS_PCMFILE_RECORD,q));
463      gtk_container_add(GTK_CONTAINER(c),d);
464      pd->pcmdev_record = GTK_ENTRY(d);
465      c = gtk_check_button_new_with_label(_("Avoid select calls (try this if "
466 					 "recording locks up)"));
467      gtk_container_add(GTK_CONTAINER(b),c);
468      oss_noselect = inifile_get_gboolean(OSS_NOSELECT,OSS_NOSELECT_DEFAULT);
469      pd->noselect = GTK_TOGGLE_BUTTON(c);
470      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c),oss_noselect);
471      c = gtk_hseparator_new();
472      gtk_container_add(GTK_CONTAINER(b),c);
473      c = gtk_hbutton_box_new();
474      gtk_container_add(GTK_CONTAINER(b),c);
475      d = gtk_button_new_with_label(_("OK"));
476      gtk_signal_connect(GTK_OBJECT(d),"clicked",
477 			GTK_SIGNAL_FUNC(oss_preferences_ok),pd);
478      gtk_container_add(GTK_CONTAINER(c),d);
479      d = gtk_button_new_with_label(_("Close"));
480      gtk_signal_connect_object(GTK_OBJECT(d),"clicked",
481 			       GTK_SIGNAL_FUNC(gtk_widget_destroy),
482 			       GTK_OBJECT(a));
483      gtk_container_add(GTK_CONTAINER(c),d);
484      gtk_widget_show_all(a);
485 }
486 
487