1 /*
2  * Sweep, a sound wave editor.
3  *
4  * Copyright (C) 2000 Conrad Parker
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 2 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 /*
22  * ALSA 0.6 support by Paul Davis
23  * ALSA 0.9 updates by Zenaan Harkness
24  * ALSA 1.0 updates by Daniel Dreschers
25  */
26 
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38 #include <fcntl.h>
39 #include <math.h>
40 #include <sys/ioctl.h>
41 #include <pthread.h>
42 
43 #include <sweep/sweep_types.h>
44 #include <sweep/sweep_sample.h>
45 
46 #include "driver.h"
47 #include "pcmio.h"
48 #include "question_dialogs.h"
49 
50 #ifdef DRIVER_ALSA
51 
52 #include <alsa/asoundlib.h>
53 
54 // shamelessly ripped from alsaplayer alsa-final driver:
55 #ifndef timersub
56 #define timersub(a, b, result) \
57 do { \
58 	(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
59   (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
60   if ((result)->tv_usec < 0) { \
61 		--(result)->tv_sec; \
62 		(result)->tv_usec += 1000000; \
63 	} \
64 } while (0)
65 #endif
66 
print_pcm_state(snd_pcm_t * pcm)67 void print_pcm_state (snd_pcm_t * pcm)
68 {
69   switch (snd_pcm_state(pcm)) {
70     case SND_PCM_STATE_OPEN:
71       fprintf (stderr, "sweep: print_pcm_state: state is OPEN\n");
72       break;
73     case SND_PCM_STATE_SETUP:
74       fprintf (stderr, "sweep: print_pcm_state: state is SETUP\n");
75       break;
76     case SND_PCM_STATE_PREPARED:
77       fprintf (stderr, "sweep: print_pcm_state: state is PREPARED\n");
78       break;
79     case SND_PCM_STATE_RUNNING:
80       fprintf (stderr, "sweep: print_pcm_state: state is RUNNING\n");
81       break;
82     case SND_PCM_STATE_XRUN:
83       fprintf (stderr, "sweep: print_pcm_state: state is XRUN\n");
84       break;
85     case SND_PCM_STATE_DRAINING:
86       fprintf (stderr, "sweep: print_pcm_state: state is DRAINING\n");
87       break;
88     case SND_PCM_STATE_PAUSED:
89       fprintf (stderr, "sweep: print_pcm_state: state is PAUSED\n");
90       break;
91     case SND_PCM_STATE_SUSPENDED:
92       fprintf (stderr, "sweep: print_pcm_state: state is SUSPENDED\n");
93       break;
94     default:
95       fprintf (stderr, "sweep: print_pcm_state: state is unknown! THIS SHOULD NEVER HAPPEN!\n");
96   }
97 }
98 
99 static GList *
alsa_get_names(void)100 alsa_get_names (void)
101 {
102   GList * names = NULL;
103   char * name;
104 
105   if ((name = getenv ("SWEEP_ALSA_PCM")) != 0) {
106     names = g_list_append (names, name);
107   }
108 
109   /* The standard command line options for this are -D or --device.
110    * The default fallback should be plughw.
111    */
112   names = g_list_append (names, "plughw:0,0");
113   names = g_list_append (names, "plughw:0,1");
114   names = g_list_append (names, "plughw:1,0");
115   names = g_list_append (names, "plughw:1,1");
116 
117   return names;
118 }
119 
120 static sw_handle *
alsa_device_open(int monitoring,int flags)121 alsa_device_open (int monitoring, int flags)
122 {
123   int err;
124   char * alsa_pcm_name;
125   snd_pcm_t * pcm_handle;
126   sw_handle * handle;
127   snd_pcm_stream_t stream;
128 
129   if (monitoring) {
130     if (pcmio_get_use_monitor())
131       alsa_pcm_name = pcmio_get_monitor_dev ();
132     else
133       return NULL;
134   } else {
135     alsa_pcm_name = pcmio_get_main_dev ();
136   }
137 
138   if (flags == O_RDONLY) {
139     stream = SND_PCM_STREAM_CAPTURE;
140   } else if (flags == O_WRONLY) {
141     stream = SND_PCM_STREAM_PLAYBACK;
142   } else {
143     return NULL;
144   }
145 
146   if ((err = snd_pcm_open(&pcm_handle, alsa_pcm_name, stream, 0)) < 0) {
147     sweep_perror (errno,
148 		  "Error opening ALSA device %s",
149 		  alsa_pcm_name /*, snd_strerror (err)*/);
150     return NULL;
151   }
152 
153   handle = g_malloc0 (sizeof (sw_handle));
154 
155   handle->driver_flags = flags;
156   handle->custom_data = pcm_handle;
157 
158   return handle;
159 }
160 
161   // /src/alsa/alsaplayer-0.99.72/output/alsa-final/alsa.c
162   // /src/alsa/alsa-lib-0.9.0rc3/test/pcm.c
163 static void
alsa_device_setup(sw_handle * handle,sw_format * format)164 alsa_device_setup (sw_handle * handle, sw_format * format)
165 {
166   int err;
167   snd_pcm_t * pcm_handle = (snd_pcm_t *)handle->custom_data;
168   snd_pcm_hw_params_t * hwparams;
169   int dir;
170   unsigned int rate = format->rate;
171   unsigned int channels = format->channels;
172   unsigned int periods;
173   snd_pcm_uframes_t period_size = PBUF_SIZE/format->channels;
174 
175 #if 1
176   if (handle->driver_flags == O_RDONLY) {
177     dir = (int)SND_PCM_STREAM_CAPTURE;
178   } else if (handle->driver_flags == O_WRONLY) {
179     dir = (int)SND_PCM_STREAM_PLAYBACK;
180   } else {
181     return;
182   }
183 #else
184   dir = 0;
185 #endif
186 
187   snd_pcm_hw_params_alloca (&hwparams);
188 
189   if ((err = snd_pcm_hw_params_any (pcm_handle, hwparams)) < 0) {
190     fprintf(stderr,
191 	    "sweep: alsa_setup: can't get PCM hw params (%s)\n",
192 	    snd_strerror(err));
193     return;
194   }
195 
196   if ((err = snd_pcm_hw_params_set_access
197        (pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
198     fprintf(stderr,
199 	    "sweep: alsa_setup: can't set interleaved access (%s)\n",
200 	    snd_strerror(err));
201     return;
202   }
203 
204   if ((err = snd_pcm_hw_params_set_format
205        (pcm_handle, hwparams, SND_PCM_FORMAT_FLOAT)) < 0) {
206     fprintf (stderr,
207 	     "sweep: alsa_setup: audio interface does not support "
208 	     "host endian 32 bit float samples (%s)\n",
209 	     snd_strerror(err));
210     return;
211   }
212 
213   if ((err = snd_pcm_hw_params_set_rate_near
214        (pcm_handle, hwparams, &rate, 0 /* dir */)) < 0) {
215     fprintf (stderr,
216 	     "sweep: alsa_setup: audio interface does not support "
217 	     "sample rate of %d (%s)\n",
218 	     format->rate, snd_strerror (err));
219     /*return;*/
220   }
221 
222   if ((err = snd_pcm_hw_params_set_channels_near
223        (pcm_handle, hwparams, &channels)) < 0) {
224     fprintf (stderr,
225 	     "sweep: alsa_setup: audio interface does not support "
226 	     "%d channels (%s)\n",
227 	     format->channels, snd_strerror (err));
228     /*return;*/
229   }
230 
231   if ((err = snd_pcm_hw_params_set_period_size_near
232        (pcm_handle, hwparams, &period_size, 0)) < 0) {
233     fprintf (stderr,
234 	     "sweep: alsa_setup: audio interface does not support "
235 	     "period size of %ld (%s)\n", period_size, snd_strerror (err));
236     return;
237   }
238 
239   periods = LOGFRAGS_TO_FRAGS(pcmio_get_log_frags());
240 
241   if ((err = snd_pcm_hw_params_set_periods_near
242        (pcm_handle, hwparams, &periods, 0)) < 0) {
243     fprintf (stderr,
244 	     "sweep: alsa_setup: audio interface does not support "
245 	     "period size of %d (%s) - suprising that we get this err!\n",
246 	     periods, snd_strerror (err));
247     return;
248   }
249 
250   // see alsa-lib html docs (may have to build them) for methods doco
251   // The following is old alsa 0.6 code, which may need including somehow:
252   //params.ready_mode = SND_PCM_READY_FRAGMENT;
253   //params.start_mode = SND_PCM_START_DATA;
254   //params.xrun_mode = SND_PCM_XRUN_FRAGMENT;
255   //params.frag_size = PBUF_SIZE / params.format.channels;
256   //params.avail_min = params.frag_size;
257   // params.buffer_size = 3 * params.frag_size;
258 
259   if ((err = snd_pcm_hw_params (pcm_handle, hwparams)) < 0) {
260     fprintf (stderr,
261 	     "sweep: alsa_setup: audio interface could not be configured "
262 	     "with specified parameters\n");
263     return;
264   }
265   //printf ("sweep: alsa_setup 9\n");
266 
267   {
268     unsigned int c, r;
269     int dir = 0;
270 
271     if ((err = snd_pcm_hw_params_get_rate (hwparams, &r, &dir)) < 0) {
272       fprintf (stderr,
273 	       "sweep: alsa_setup: error getting PCM rate (%s)\n",
274 	       snd_strerror (err));
275     }
276 
277     if ((err = snd_pcm_hw_params_get_channels (hwparams, &c)) < 0) {
278       fprintf (stderr,
279 	       "sweep: alsa_setup: error getting PCM channels (%s)\n",
280 	       snd_strerror (err));
281     }
282 
283 #ifdef DEBUG
284     fprintf (stderr, "alsa got rate %i, channels %i, dir %d\n", r, c, dir);
285 #endif
286 
287     handle->driver_rate = r;
288     handle->driver_channels = c;
289 
290     if (c < 1) {
291       fprintf (stderr, "sweep: alsa_setup: alsa says channels == %i\n", c);
292       return;
293     }
294   }
295 
296   if (snd_pcm_prepare (pcm_handle) < 0) {
297     fprintf (stderr, "audio interface could not be prepared for playback\n");
298     return;
299   }
300 }
301 
302 static int
alsa_device_wait(sw_handle * handle)303 alsa_device_wait (sw_handle * handle)
304 {
305   snd_pcm_t * pcm_handle = (snd_pcm_t *)handle->custom_data;
306 
307   if (snd_pcm_wait (pcm_handle, 1000) < 0) {
308     fprintf (stderr, "poll failed (%s)\n", strerror (errno));
309   }
310 
311   return 0;
312 }
313 
314 #define PLAYBACK_SCALE (32768 / SW_AUDIO_T_MAX)
315 
316 static ssize_t
alsa_device_read(sw_handle * handle,sw_audio_t * buf,size_t count)317 alsa_device_read (sw_handle * handle, sw_audio_t * buf, size_t count)
318 {
319   snd_pcm_t * pcm_handle = (snd_pcm_t *)handle->custom_data;
320   snd_pcm_uframes_t uframes;
321   int err;
322 
323   uframes = handle->driver_channels > 0 ? count / handle->driver_channels : 0;
324 
325   err = snd_pcm_readi (pcm_handle, buf, uframes);
326 
327   return err;
328 }
329 
330 static ssize_t
alsa_device_write(sw_handle * handle,sw_audio_t * buf,size_t count,sw_framecount_t offset)331 alsa_device_write (sw_handle * handle, sw_audio_t * buf, size_t count,
332     sw_framecount_t offset)
333 {
334   snd_pcm_t * pcm_handle = (snd_pcm_t *)handle->custom_data;
335   snd_pcm_uframes_t uframes;
336   snd_pcm_status_t * status;
337   int err;
338 
339 #if 0
340   gint16 * bbuf;
341   size_t byte_count;
342   ssize_t bytes_written;
343   int need_bswap;
344   int i;
345 
346   if (handle == NULL) {
347 #ifdef DEBUG
348     g_print ("handle NULL in write()\n");
349 #endif
350     return -1;
351   }
352 
353   byte_count = count * sizeof (gint16);
354   bbuf = alloca (byte_count);
355 
356   for (i = 0; i < count; i++) {
357     bbuf[i] = (gint16)(PLAYBACK_SCALE * buf[i]);
358   }
359 
360   err = snd_pcm_writei(pcm_handle, bbuf, uframes);
361 #else
362   /*printf ("sweep: alsa_write \n");*/
363 
364   uframes = handle->driver_channels > 0 ? count / handle->driver_channels : 0;
365   //printf ("sweep: alsa_write 1\n");
366 
367   // this basicaly ripped straight out of alsaplayer alsa-final driver:
368   err = snd_pcm_writei(pcm_handle, buf, uframes);
369 #endif
370 
371   if (err == -EPIPE) {
372     snd_pcm_status_alloca(&status);
373     if ((err = snd_pcm_status(pcm_handle, status))<0) {
374       fprintf(stderr, "sweep: alsa_write: xrun. can't determine length\n");
375     } else {
376       if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
377         struct timeval now, diff, tstamp;
378         gettimeofday(&now, 0);
379         snd_pcm_status_get_trigger_tstamp(status, &tstamp);
380         timersub(&now, &tstamp, &diff);
381         fprintf(stderr, "sweep: alsa_write: xrun of at least %.3f msecs. "
382 	    "resetting stream\n", diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
383       } else {
384         fprintf(stderr, "sweep: alsa_write: xrun. can't determine length\n");
385       }
386     }
387     snd_pcm_prepare(pcm_handle);
388     err = snd_pcm_writei(pcm_handle, buf, uframes);
389     if (err != uframes) {
390       fprintf(stderr, "sweep: alsa_write: %s\n", snd_strerror(err));
391       return 0;
392     } else if (err < 0) {
393       fprintf(stderr, "sweep: alsa_write: %s\n", snd_strerror(err));
394       return 0;
395     }
396   }
397 
398   return 1;
399 }
400 
401 sw_framecount_t
alsa_device_offset(sw_handle * handle)402 alsa_device_offset (sw_handle * handle)
403 {
404   /*printf ("sweep: alsa_offset\n");*/
405   return -1;
406 }
407 
408 static void
alsa_device_reset(sw_handle * handle)409 alsa_device_reset (sw_handle * handle)
410 {
411   /*printf ("sweep: alsa_reset\n");*/
412 }
413 
414 static void
alsa_device_flush(sw_handle * handle)415 alsa_device_flush (sw_handle * handle)
416 {
417   /*printf ("sweep: alsa_flush\n");*/
418 }
419 
420 /*
421  * alsa lib provides:
422  * int snd_pcm_drop (snd_pcm_t *pcm) // Stop a PCM dropping pending frames.
423  * int snd_pcm_drain (snd_pcm_t *pcm) // Stop a PCM preserving pending frames.
424  */
425 static void
alsa_device_drain(sw_handle * handle)426 alsa_device_drain (sw_handle * handle)
427 {
428   snd_pcm_t * pcm_handle = (snd_pcm_t *)handle->custom_data;
429 
430   if (snd_pcm_drop (pcm_handle) < 0) {
431         fprintf (stderr, "audio interface could not be stopped\n");
432         return;
433   }
434   if (snd_pcm_prepare (pcm_handle) < 0) {
435         fprintf (stderr, "audio interface could not be re-prepared\n");
436         return;
437   }
438 }
439 
440 static void
alsa_device_close(sw_handle * handle)441 alsa_device_close (sw_handle * handle)
442 {
443   snd_pcm_t * pcm_handle = (snd_pcm_t *)handle->custom_data;
444 
445   snd_pcm_close (pcm_handle);
446 }
447 
448 static sw_driver _driver_alsa = {
449   alsa_get_names,
450   alsa_device_open,
451   alsa_device_setup,
452   alsa_device_wait,
453   alsa_device_read,
454   alsa_device_write,
455   alsa_device_offset,
456   alsa_device_reset,
457   alsa_device_flush,
458   alsa_device_drain,
459   alsa_device_close,
460   "alsa_primary_device",
461   "alsa_monitor_device",
462   "alsa_log_frags"
463 };
464 
465 #else
466 
467 static sw_driver _driver_alsa = {
468   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
469 };
470 
471 #endif
472 
473 sw_driver * driver_alsa = &_driver_alsa;
474