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