1
/* sqUnixSoundALSA.c -- cheap and cheerful sound for Advanced Linux Sound Architecture
3
* Author: Ian.Piumarta@squeakland.org
5
* Last edited: 2010-04-01 13:48:37 by piumarta on emilia-2.local
7
* Copyright (C) 2006 by Ian Piumarta
10
* This file is part of Unix Squeak.
12
* Permission is hereby granted, free of charge, to any person obtaining a copy
13
* of this software and associated documentation files (the "Software"), to deal
14
* in the Software without restriction, including without limitation the rights
15
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
* copies of the Software, and to permit persons to whom the Software is
17
* furnished to do so, subject to the following conditions:
19
* The above copyright notice and this permission notice shall be included in
20
* all copies or substantial portions of the Software.
22
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
#include <alsa/asoundlib.h>
37
#define DEVICE_NAME_LEN 128
39
static char sound_device[DEVICE_NAME_LEN]; /* = "default"; */
40
static char sound_playback[DEVICE_NAME_LEN]; /* = "Master"; */
41
static char sound_capture[DEVICE_NAME_LEN]; /* = "Capture"; */
50
#define snd(expr, what) \
51
if ((err= snd_##expr) < 0) \
53
fprintf(stderr, "%s: %s\n", what, snd_strerror(err)); \
58
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
59
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
61
static void sigio_save(void);
62
static void sigio_restore(void);
68
#define SQ_SND_PLAY_START_THRESHOLD 7/8
69
#define SQ_SND_PLAY_AVAIL_MIN 4/8
71
static snd_pcm_t *output_handle= 0;
72
static snd_async_handler_t *output_handler= 0;
73
static int output_semaphore= 0;
74
static int output_channels= 0;
75
static int output_buffer_frames_available= 0;
76
static snd_pcm_uframes_t output_buffer_period_size= 0;
77
static snd_pcm_uframes_t output_buffer_size= 0;
78
static double max_delay_frames= 0;
80
static void output_callback(snd_async_handler_t *handler)
82
signalSemaphoreWithIndex(output_semaphore);
83
output_buffer_frames_available= 1;
86
static sqInt sound_Stop(void)
90
snd_pcm_close(output_handle);
97
static sqInt sound_Start(sqInt frameCount, sqInt samplesPerSec, sqInt stereo, sqInt semaIndex)
100
snd_pcm_hw_params_t *hwparams;
101
snd_pcm_sw_params_t *swparams;
105
if (output_handle) sound_Stop();
107
output_semaphore= semaIndex;
108
output_channels= stereo ? 2 : 1;
109
snd(pcm_open(&output_handle, sound_device, SND_PCM_STREAM_PLAYBACK, 0), "startSound: snd_pcm_open");
111
snd_pcm_hw_params_alloca(&hwparams);
112
snd_pcm_hw_params_any(output_handle, hwparams);
113
snd_pcm_hw_params_set_access(output_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
114
snd_pcm_hw_params_set_format(output_handle, hwparams, SND_PCM_FORMAT_S16_LE);
115
snd_pcm_hw_params_set_channels(output_handle, hwparams, output_channels);
117
snd_pcm_hw_params_set_rate_near(output_handle, hwparams, &uval, &dir);
118
output_buffer_period_size= frameCount;
119
snd_pcm_hw_params_set_period_size_near(output_handle, hwparams, &output_buffer_period_size, &dir);
120
snd(pcm_hw_params(output_handle, hwparams), "sound_Start: snd_pcm_hw_params");
122
snd_pcm_sw_params_alloca(&swparams);
123
snd(pcm_sw_params_current(output_handle, swparams), "sound_Start: snd_pcm_sw_params_current");
124
snd(pcm_sw_params_set_start_threshold(output_handle, swparams, frameCount * SQ_SND_PLAY_START_THRESHOLD), "sound_Start: snd_pcm_sw_params_set_start_threshold");
125
snd(pcm_sw_params_set_avail_min(output_handle, swparams, frameCount * SQ_SND_PLAY_AVAIL_MIN), "sound_Start: snd_pcm_sw_parama_set_avail_min");
126
snd(pcm_sw_params_set_xfer_align(output_handle, swparams, 1), "sound_Start: snd_pcm_sw_params_set_xfer_align");
127
snd(pcm_sw_params(output_handle, swparams), "sound_Start: snd_pcm_sw_params");
128
snd(pcm_hw_params_get_buffer_size(hwparams, &output_buffer_size), "sound_Start: pcm_hw_params_get_buffer_size");
129
output_buffer_frames_available= 1;
130
max_delay_frames= output_buffer_period_size * 2; /* set initial delay frames */
132
snd(pcm_nonblock(output_handle, 1), "sound_Start: snd_pcm_nonblock");
133
snd(async_add_pcm_handler(&output_handler, output_handle, output_callback, 0), "soundStart: snd_add_pcm_handler");
135
if ((err= snd_pcm_start(output_handle)) < 0)
139
fprintf(stderr, "snd_pcm_start(1): %s\n", snd_strerror(err));
145
if ((err= snd_pcm_prepare(output_handle)) < 0)
146
fprintf(stderr, "snd_pcm_prepare: %s\n", snd_strerror(err));
148
if ((err= snd_pcm_start(output_handle)) < 0)
152
fprintf(stderr, "snd_pcm_start(2): %s\n", snd_strerror(err));
161
static sqInt sound_AvailableSpace(void)
164
snd_pcm_sframes_t delay; /* distance to playback point (in frames) */
165
snd_pcm_state_t state; /* current state of the stream */
166
sqInt avail= 0; /* available space for the answer (in bytes) */
168
if (!output_handle) return 0;
170
snd_pcm_delay(output_handle, &delay);
171
snd_pcm_avail_update(output_handle);
172
state= snd_pcm_state(output_handle);
174
/* if underrun causes, max delay is loosened */
175
if (state == SND_PCM_STATE_XRUN)
176
max_delay_frames= MIN(max_delay_frames * 1.5, output_buffer_size - output_buffer_period_size);
178
/* if the state is not running, new sound is needed because nobody can signal the semaphore */
179
if (delay <= max_delay_frames || state != SND_PCM_STATE_RUNNING)
181
avail= output_buffer_period_size;
182
max_delay_frames= MAX(max_delay_frames * 0.9995, output_buffer_period_size);
184
/*fprintf(stderr, "delay=%i, ans_avail=%i, state=%i, real_delay=%.1fms\n", (int) delay, avail, state, 1000 * max_delay_frames / 22050);*/
185
return avail * output_channels * 2; /* bytes */
189
int count= snd_pcm_avail_update(output_handle);
192
fprintf(stderr, "sound_AvailableSpace: snd_pcm_avail_update: %s\n", snd_strerror(count));
193
snd_pcm_prepare(output_handle);
199
static sqInt sound_InsertSamplesFromLeadTime(sqInt frameCount, void *srcBufPtr, sqInt samplesOfLeadTime) FAIL(frameCount)
201
static sqInt sound_PlaySamplesFromAtLength(sqInt frameCount, void *srcBufPtr, sqInt startIndex)
205
void *samples= srcBufPtr + startIndex * output_channels * 2;
206
int count= snd_pcm_writei(output_handle, samples, frameCount);
207
if (count < frameCount / 2)
209
output_buffer_frames_available= 0;
213
if (count == -EPIPE) /* underrun */
216
snd(pcm_prepare(output_handle), "sound_PlaySamples: snd_pcm_prepare");
219
fprintf(stderr, "snd_pcm_writei returned %i\n", count);
228
static sqInt sound_PlaySilence(void) FAIL(8192)
234
#define SQ_SND_REC_START_THRESHOLD 4/8
235
#define SQ_SND_REC_AVAIL_MIN 4/8
237
static snd_pcm_t *input_handle= 0;
238
static snd_async_handler_t *input_handler= 0;
239
static int input_semaphore= 0;
240
static int input_channels= 0;
241
static unsigned int input_rate= 0;
243
static void input_callback(snd_async_handler_t *handler)
245
signalSemaphoreWithIndex(input_semaphore);
248
static sqInt sound_StopRecording(void)
252
snd_pcm_close(input_handle);
259
static sqInt sound_StartRecording(sqInt desiredSamplesPerSec, sqInt stereo, sqInt semaIndex)
262
snd_pcm_hw_params_t *hwparams;
263
snd_pcm_sw_params_t *swparams;
264
snd_pcm_uframes_t frames;
267
if (input_handle) sound_StopRecording();
269
input_semaphore= semaIndex;
270
input_channels= stereo ? 2 : 1;
271
snd(pcm_open(&input_handle, sound_device, SND_PCM_STREAM_CAPTURE, 0), "start_SoundRecording: snd_pcm_open");
273
snd_pcm_hw_params_alloca(&hwparams);
274
snd_pcm_hw_params_any(input_handle, hwparams);
275
snd_pcm_hw_params_set_access(input_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
276
snd_pcm_hw_params_set_format(input_handle, hwparams, SND_PCM_FORMAT_S16_LE);
277
snd_pcm_hw_params_set_channels(input_handle, hwparams, input_channels);
278
input_rate= desiredSamplesPerSec;
279
snd_pcm_hw_params_set_rate_near(input_handle, hwparams, &input_rate, &dir);
281
snd_pcm_hw_params_set_period_size_near(input_handle, hwparams, &frames, &dir);
282
snd(pcm_hw_params(input_handle, hwparams), "sound_StartRecording: snd_pcm_hw_params");
284
snd_pcm_sw_params_alloca(&swparams);
285
snd(pcm_sw_params_current(input_handle, swparams), "sound_StartRecording: snd_pcm_sw_params_current");
286
snd(pcm_sw_params_set_start_threshold(input_handle, swparams, frames * SQ_SND_REC_START_THRESHOLD), "sound_StartRecording: snd_pcm_sw_params_set_start_threshold");
287
snd(pcm_sw_params_set_avail_min(input_handle, swparams, frames * SQ_SND_REC_AVAIL_MIN), "sound_StartRecording: snd_pcm_sw_parama_set_avail_min");
288
snd(pcm_sw_params_set_xfer_align(input_handle, swparams, 1), "sound_StartRecording: snd_pcm_sw_params_set_xfer_align");
289
snd(pcm_sw_params(input_handle, swparams), "sound_StartRecording: snd_pcm_sw_params");
291
snd(pcm_nonblock(input_handle, 1), "sound_StartRecording: snd_pcm_nonblock");
292
snd(async_add_pcm_handler(&input_handler, input_handle, input_callback, 0), "sound_StartRecording: snd_add_pcm_handler");
293
snd(pcm_start(input_handle), "sound_StartRecording: snd_pcm_start");
297
static double sound_GetRecordingSampleRate(void)
299
return (double)input_rate;
302
static sqInt sound_RecordSamplesIntoAtLength(void *buf, sqInt startSliceIndex, sqInt bufferSizeInBytes)
306
void *samples= buf + (startSliceIndex * 2);
307
int frameCount= ((bufferSizeInBytes / 2) - startSliceIndex) / input_channels;
308
int count= snd_pcm_readi(input_handle, samples, frameCount);
312
snd_pcm_prepare(input_handle);
313
else if (count != -EAGAIN)
314
fprintf(stderr, "snd_pcm_readi returned %i\n", count);
317
return count * input_channels;
327
static int sound_nomixer = 0;
328
static snd_mixer_t *mixer_handle = 0;
329
static snd_mixer_elem_t *mixer_element = 0;
332
static int mixer_open(char *name)
334
struct snd_mixer_selem_regopt smixer_options;
336
snd_mixer_selem_id_t *sid;
338
if (sound_nomixer) return -EACCES;
340
smixer_options.device= sound_device;
341
snd_mixer_selem_id_alloca(&sid);
342
snd_mixer_selem_id_set_name(sid, name);
343
snd(mixer_open(&mixer_handle, 0), "snd_mixer_open");
344
snd(mixer_attach(mixer_handle, sound_device), "snd_mixer_attach");
345
snd(mixer_selem_register(mixer_handle, NULL, NULL), "snd_selem_register");
346
snd(mixer_load(mixer_handle), "snd_mixer_load");
348
mixer_element= snd_mixer_find_selem(mixer_handle, sid);
352
fprintf(stderr, "unable to find control %s, %i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
359
static void mixer_close(void)
361
snd_mixer_close(mixer_handle);
366
static inline void mixer_getVolume(char *name, int captureFlag, double *leftLevel, double *rightLevel)
368
if (mixer_open(name))
374
if (!(captureFlag ? snd_mixer_selem_has_capture_volume : snd_mixer_selem_has_playback_volume)(mixer_element))
375
fprintf(stderr, "%s: no %s volume\n", name, captureFlag ? "capture" : "playback");
380
(captureFlag ? snd_mixer_selem_get_capture_volume_range : snd_mixer_selem_get_playback_volume_range)(mixer_element, &vmin, &vmax);
381
fprintf(stderr, "%s range %li - %li\n", captureFlag ? "capture" : "playback", vmin, vmax);
382
while (++channel <= SND_MIXER_SCHN_LAST)
383
if ((captureFlag ? snd_mixer_selem_has_capture_channel : snd_mixer_selem_has_playback_channel)(mixer_element, channel))
386
(captureFlag ? snd_mixer_selem_get_capture_volume : snd_mixer_selem_get_playback_volume)(mixer_element, channel, &vol);
387
*leftLevel= *rightLevel= (double)(vol - vmin) / (double)(vmax - vmin);
390
while (++channel <= SND_MIXER_SCHN_LAST)
391
if ((captureFlag ? snd_mixer_selem_has_capture_channel : snd_mixer_selem_has_playback_channel)(mixer_element, channel))
394
(captureFlag ? snd_mixer_selem_get_capture_volume : snd_mixer_selem_get_playback_volume)(mixer_element, channel, &vol);
395
*rightLevel= (double)(vol - vmin) / (double)(vmax - vmin);
404
static inline void mixer_setVolume(char *name, int captureFlag, double leftLevel, double rightLevel)
406
if (mixer_open(name))
412
if (!(captureFlag ? snd_mixer_selem_has_capture_volume : snd_mixer_selem_has_playback_volume)(mixer_element))
413
fprintf(stderr, "%s: no %s volume\n", name, captureFlag ? "capture" : "playback");
418
(captureFlag ? snd_mixer_selem_get_capture_volume_range : snd_mixer_selem_get_playback_volume_range)(mixer_element, &vmin, &vmax);
419
fprintf(stderr, "playback range %li - %li\n", vmin, vmax);
420
while (++channel <= SND_MIXER_SCHN_LAST)
421
if ((captureFlag ? snd_mixer_selem_has_capture_channel : snd_mixer_selem_has_playback_channel)(mixer_element, channel))
423
long vol= vmin + (double)(vmax - vmin) * leftLevel;
424
(captureFlag ? snd_mixer_selem_set_capture_volume : snd_mixer_selem_set_playback_volume)(mixer_element, channel, vol);
425
(captureFlag ? snd_mixer_selem_set_capture_switch : snd_mixer_selem_set_playback_switch)(mixer_element, channel, 1);
428
while (++channel <= SND_MIXER_SCHN_LAST)
429
if ((captureFlag ? snd_mixer_selem_has_capture_channel : snd_mixer_selem_has_playback_channel)(mixer_element, channel))
431
long vol= vmin + (double)(vmax - vmin) * rightLevel;
432
(captureFlag ? snd_mixer_selem_set_capture_volume : snd_mixer_selem_set_playback_volume)(mixer_element, channel, vol);
433
(captureFlag ? snd_mixer_selem_set_capture_switch : snd_mixer_selem_set_playback_switch)(mixer_element, channel, 1);
441
static int mixer_setSwitch(char *name, int captureFlag, int parameter)
444
if (mixer_open(name))
450
if (!(captureFlag ? snd_mixer_selem_has_capture_switch : snd_mixer_selem_has_playback_switch)(mixer_element))
456
for (chn= 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
458
if (!(captureFlag ? snd_mixer_selem_has_capture_channel : snd_mixer_selem_has_playback_channel)(mixer_element, chn))
461
if ((captureFlag ? snd_mixer_selem_set_capture_switch : snd_mixer_selem_set_playback_switch)(mixer_element, chn, parameter) < 0)
469
static int mixer_getSwitch(char *name, int captureFlag, int channel)
472
if (channel < 0 || channel > SND_MIXER_SCHN_LAST)
477
if (mixer_open(name))
483
if (!(captureFlag ? snd_mixer_selem_has_capture_switch : snd_mixer_selem_has_playback_switch)(mixer_element))
489
if (!(captureFlag ? snd_mixer_selem_has_capture_channel : snd_mixer_selem_has_playback_channel)(mixer_element, channel))
495
if ((captureFlag ? snd_mixer_selem_get_capture_switch : snd_mixer_selem_get_playback_switch)(mixer_element, channel, &ival) < 0)
502
static void sound_Volume(double *left, double *right)
504
mixer_getVolume(sound_playback, 0, left, right);
507
static void sound_SetVolume(double left, double right)
509
mixer_setVolume(sound_playback, 0, left, right);
510
if (strcmp("Master", sound_playback))
512
/* unmute the master volume */
513
mixer_getVolume("Master", 0, &left, &right);
514
mixer_setVolume("Master", 0, left, right);
518
static void sound_SetRecordLevel(sqInt level)
520
mixer_setVolume(sound_capture, 1, (double)level / 100.0, (double)level / 100.0);
523
static sqInt sound_SetDevice(sqInt id, char *arg)
540
dest= sound_playback;
553
strncpy(dest, arg, DEVICE_NAME_LEN-1);
559
static sqInt sound_GetSwitch(sqInt id, sqInt captureFlag, sqInt channel)
563
return mixer_getSwitch(sound_playback, captureFlag, channel);
567
return mixer_getSwitch(sound_capture, captureFlag, channel);
572
static sqInt sound_SetSwitch(sqInt id, sqInt captureFlag, sqInt parameter)
576
return mixer_setSwitch(sound_playback, captureFlag, parameter);
580
return mixer_setSwitch(sound_capture, captureFlag, parameter);
589
static void *sigio_handler= 0;
591
static void sigio_save(void)
595
sigio_handler= signal(SIGIO, SIG_IGN);
596
signal(SIGIO, sigio_handler);
600
static void sigio_restore(void)
602
if (sigio_handler && !output_handle && !input_handle)
603
signal(SIGIO, sigio_handler);
614
#include "SqModule.h"
616
static void sound_parseEnvironment(void)
620
sound_SetDevice(0, NULL);
621
sound_SetDevice(1, NULL);
622
sound_SetDevice(2, NULL);
624
if ( getenv("SQUEAK_NOMIXER" )) sound_nomixer= 1;
625
if ((ev= getenv("SQUEAK_SOUNDCARD"))) sound_SetDevice(0, ev);
626
if ((ev= getenv("SQUEAK_PLAYBACK" ))) sound_SetDevice(1, ev);
627
if ((ev= getenv("SQUEAK_CAPTURE" ))) sound_SetDevice(2, ev);
630
static int sound_parseArgument(int argc, char **argv)
632
if (!strcmp(argv[0], "-nomixer" )) { sound_nomixer= 1; return 1; }
635
if (!strcmp(argv[0], "-soundcard")) { sound_SetDevice(0, argv[1]); return 2; }
636
if (!strcmp(argv[0], "-playback" )) { sound_SetDevice(1, argv[1]); return 2; }
637
if (!strcmp(argv[0], "-capture" )) { sound_SetDevice(2, argv[1]); return 2; }
642
static void sound_printUsage(void)
644
printf("\nALSA <option>s:\n");
645
printf(" -nomixer disable mixer (volume) adjustment\n");
646
printf(" -soundcard <name> open the named sound card (default: %s)\n", sound_device);
647
printf(" -playback <name> play to the named sound device (default: %s)\n", sound_playback);
648
printf(" -capture <name> record from the named sound device (default: %s)\n", sound_capture);
651
static void sound_printUsageNotes(void) {}
653
static void *sound_makeInterface(void)
656
return &sound_ALSA_itf;
659
SqModuleDefine(sound, ALSA);