-
Notifications
You must be signed in to change notification settings - Fork 7
/
audio_alsa.c
360 lines (289 loc) · 10.6 KB
/
audio_alsa.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
/*****************************************************************************
* Gnome Wave Cleaner Version 0.19
* Copyright (C) 2003 Jeffrey J. Welty
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*******************************************************************************/
/* alsa interface impl. ...frank 12.09.03 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef ALSA_IN_SYS
#include <sys/asoundlib.h>
#else
#include <alsa/asoundlib.h>
#endif
#include "audio_device.h"
#include "gwc.h"
static snd_pcm_t *handle = NULL;
static snd_pcm_uframes_t written_frames = 0;
static long drain_delta = 0 ;
static long last_processed_bytes0 = -1 ;
static long last_processed_bytes = -1 ;
snd_pcm_uframes_t buffer_total_frames; /* number of frames in alsa device buffer */
static void snd_perr(char *text, int err)
{
fprintf(stderr, "##########################################################\n");
fprintf(stderr, "%s\n", text);
fprintf(stderr, "%s\n", snd_strerror(err));
warning(text) ;
}
int audio_device_open(char *output_device)
{
int err = snd_pcm_open(&handle, output_device, /*"default",*/
SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0) {
snd_perr("ALSA audio_device_open: snd_pcm_open", err);
return -1;
}
written_frames = 0;
drain_delta=0 ;
last_processed_bytes0 = -1 ;
last_processed_bytes = -1 ;
return 0;
}
int audio_device_set_params(AUDIO_FORMAT *format, int *channels, int *rate)
{
unsigned int utmp ;
int err;
snd_pcm_format_t alsa_format;
snd_pcm_hw_params_t *params;
snd_pcm_sw_params_t *swparams;
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_sw_params_alloca(&swparams);
err = snd_pcm_hw_params_any(handle, params);
if (err < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_hw_params_any", err);
return -1;
}
err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_hw_params_set_access", err);
return -1;
}
switch (*format)
{
case GWC_U8: alsa_format = SND_PCM_FORMAT_U8; break;
case GWC_S8: alsa_format = SND_PCM_FORMAT_S8; break;
case GWC_S16_BE: alsa_format = SND_PCM_FORMAT_S16_BE; break;
default:
case GWC_S16_LE: alsa_format = SND_PCM_FORMAT_S16_LE; break;
}
if (snd_pcm_hw_params_set_format(handle, params, alsa_format) < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_hw_params_set_format", err);
return -1;
}
if (snd_pcm_hw_params_get_format(params, &alsa_format) < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_hw_params_get_format", err);
return -1;
}
switch (alsa_format)
{
case SND_PCM_FORMAT_U8: *format = GWC_U8; break;
case SND_PCM_FORMAT_S8 : *format = GWC_S8; break;
case SND_PCM_FORMAT_S16_BE: *format = GWC_S16_BE; break;
case SND_PCM_FORMAT_S16_LE: *format = GWC_S16_LE; break;
default: *format = GWC_UNKNOWN; break;
}
err = snd_pcm_hw_params_set_channels(handle, params, *channels);
if (err < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_hw_params_set_channels", err);
return -1;
}
utmp = (unsigned int)*channels ;
if (snd_pcm_hw_params_get_channels(params, &utmp) < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_hw_params_get_channels", err);
return -1;
}
*channels = (int)utmp ;
utmp = (unsigned int)*rate ;
err = snd_pcm_hw_params_set_rate_near(handle, params, &utmp, 0);
if (err < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_hw_params_set_rate_near", err);
return -1;
}
*rate = (int)utmp ;
err = snd_pcm_hw_params(handle, params);
if (err < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_hw_params", err);
return -1;
}
err = snd_pcm_prepare(handle);
if (err < 0) {
snd_perr("ALSA audio_device_set_params: snd_pcm_prepare", err);
return -1;
}
fprintf(stderr, "audio_device_handle %d\n",(int)handle);
return 0;
}
int audio_device_read(unsigned char *buffer, int buffersize)
{
/* not implemented */
return -1;
}
/* recover underrun and suspend */
static int recover_snd_handle(int err)
{
if (err == -EPIPE) { /* underrun */
fprintf(stderr, "recover_snd_handle: err == -EPIPE\n");
err = snd_pcm_prepare(handle);
if (err < 0)
snd_perr("ALSA recover_snd_handle: can't recover underrun, prepare failed", err);
return 0;
}
else if (err == -ESTRPIPE) { /* suspend */
fprintf(stderr, "recover_snd_handle: err == -ESTRPIPE\n");
while ((err = snd_pcm_resume(handle)) == -EAGAIN)
sleep(1);
if (err < 0) {
err = snd_pcm_prepare(handle);
if (err < 0)
snd_perr("ALSA recover_snd_handle: can't recover suspend, prepare failed", err);
}
return 0;
}
return err;
}
int audio_device_write(unsigned char *data, int count)
{
snd_pcm_sframes_t err;
snd_pcm_uframes_t result_frames = 0;
snd_pcm_uframes_t count_frames = snd_pcm_bytes_to_frames(handle, count);
while (count_frames > 0) {
err = snd_pcm_writei(handle, data, count_frames);
if (err > 0) {
result_frames += err;
count_frames -= err;
data += snd_pcm_frames_to_bytes(handle, err);
} else if (err == -EAGAIN) {
snd_pcm_wait(handle, 1000);
} else if (err < 0) {
if(err == -EINVAL) {
fprintf(stderr, "snd_pcm_writei invalid argument: %d %d %d\n",(int)handle,(int)data,(int)count_frames);
exit(1) ;
} else if (recover_snd_handle(err) < 0) {
fprintf(stderr, "audio_device_write %d %d %d\n",(int)handle,(int)data,(int)count_frames);
snd_perr("ALSA audio_device_write: snd_pcm_writei", err);
exit(1) ;
return -1;
}
}
}
if(count_frames > 0) {
fprintf(stderr, "ALSA device DID NOT WRITE ALL FRAMES (want,got)=%ld,%ld\n", (long)(result_frames+count_frames), (long)result_frames) ;
}
written_frames += result_frames;
return snd_pcm_frames_to_bytes(handle, result_frames);
}
/* Number of bytes processed since opening the device. */
long query_processed_bytes(void)
{
if(handle != NULL) {
// both avail_update() and avail() seem to give same result in terms
// of puting the playback cursor on the screen. Since the avail_update()
// version is lighter weight according to the documentation, stay with
// that.
// snd_pcm_sframes_t avail_frames_in_buf = snd_pcm_avail_update(handle);
snd_pcm_sframes_t avail_frames_in_buf = snd_pcm_avail(handle);
if (avail_frames_in_buf < 0) {
// error occurred
//snd_perr("ALSA snd_pcm_avail", avail_frames_in_buf);
return snd_pcm_frames_to_bytes(handle,written_frames) ;
}
//fprintf(stderr, "ALSA device written frames:%ld\n", (long)written_frames) ;
//fprintf(stderr, "ALSA device buffer frames:%ld\n", (long)buffer_total_frames) ;
//fprintf(stderr, "ALSA device avail frames:%ld\n", (long)avail_frames_in_buf) ;
//fprintf(stderr, "ALSA device buf-avl frames:%ld\n", (long)(buffer_total_frames-avail_frames_in_buf)) ;
//long playback_frame = written_frames - (buffer_total_frames - avail_frames_in_buf) ;
//fprintf(stderr, "ALSA device playback frame:%ld\n\n", playback_frame) ;
return snd_pcm_frames_to_bytes(handle, (written_frames - (buffer_total_frames - avail_frames_in_buf)));
}
return 0 ;
}
long _audio_device_processed_bytes = 0 ;
/* Number of bytes processed since opening the device. */
long audio_device_processed_bytes(void)
{
if(handle != NULL)
_audio_device_processed_bytes = query_processed_bytes() ;
else
fprintf(stderr, "ALSA device handle is NULL\n") ;
return _audio_device_processed_bytes ;
}
void audio_device_close(int drain)
{
if (handle != NULL) {
int err;
printf("Closing the ALSA audio device\n") ;
_audio_device_processed_bytes = query_processed_bytes() ;
if(drain)
err = snd_pcm_drain(handle);
err = snd_pcm_drop(handle);
if (err < 0) {
snd_perr("ALSA audio_device_close: snd_pcm_drop", err);
}
err = snd_pcm_close(handle);
if (err < 0) {
snd_perr("ALSA audio_device_close: snd_pcm_close", err);
}
handle = NULL;
}
drain_delta=0 ;
}
int audio_device_best_buffer_size(int playback_bytes_per_block)
{
int err;
snd_pcm_status_t *status;
int frame_size ;
snd_pcm_status_alloca(&status);
err = snd_pcm_status(handle, status);
if (err < 0) {
snd_perr("ALSA audio_device_best_buffer_size: snd_pcm_status", err);
return 0;
}
buffer_total_frames = snd_pcm_status_get_avail(status);
frame_size = snd_pcm_frames_to_bytes(handle, buffer_total_frames);
if(frame_size < 4096 && frame_size > 0) {
int s = frame_size ;
while(frame_size < 4096) frame_size += s ;
printf("ALSA audio_device_adjusted_buffer_size:%d\n", frame_size) ;
}
if(frame_size == 0) {
warning("Your ALSA audio device driver gives invalid information for its buffer size, defaulting to 4K bytes, this may produce strange playback results") ;
frame_size = 4096 ;
}
return frame_size ;
}
int audio_device_nonblocking_write_buffer_size(int maxbufsize,
int playback_bytes_remaining)
{
int len = 0;
snd_pcm_sframes_t frames = snd_pcm_avail_update(handle);
if (frames < 0) {
snd_perr("audio_device_nonblocking_write_buffer_size: snd_pcm_avail_update",
frames);
if (recover_snd_handle(frames) < 0) {
fprintf(stderr, "audio_device_nonblocking_write_buffer_size: could not recover handle\n");
return -1 ;
}
}
len = snd_pcm_frames_to_bytes(handle, frames);
if (len > maxbufsize)
len = maxbufsize;
if (len > playback_bytes_remaining)
len = playback_bytes_remaining;
return len;
}