1 | /* |
2 | * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved. |
3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 | * |
5 | * This code is free software; you can redistribute it and/or modify it |
6 | * under the terms of the GNU General Public License version 2 only, as |
7 | * published by the Free Software Foundation. Oracle designates this |
8 | * particular file as subject to the "Classpath" exception as provided |
9 | * by Oracle in the LICENSE file that accompanied this code. |
10 | * |
11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
14 | * version 2 for more details (a copy is included in the LICENSE file that |
15 | * accompanied this code). |
16 | * |
17 | * You should have received a copy of the GNU General Public License version |
18 | * 2 along with this work; if not, write to the Free Software Foundation, |
19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
20 | * |
21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
22 | * or visit www.oracle.com if you need additional information or have any |
23 | * questions. |
24 | */ |
25 | |
26 | #define USE_ERROR |
27 | #define USE_TRACE |
28 | |
29 | #include "PLATFORM_API_LinuxOS_ALSA_PCMUtils.h" |
30 | #include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h" |
31 | #include "DirectAudio.h" |
32 | |
33 | #if USE_DAUDIO == TRUE |
34 | |
35 | // GetPosition method 1: based on how many bytes are passed to the kernel driver |
36 | // + does not need much processor resources |
37 | // - not very exact, "jumps" |
38 | // GetPosition method 2: ask kernel about actual position of playback. |
39 | // - very exact |
40 | // - switch to kernel layer for each call |
41 | // GetPosition method 3: use snd_pcm_avail() call - not yet in official ALSA |
42 | // quick tests on a Pentium 200MMX showed max. 1.5% processor usage |
43 | // for playing back a CD-quality file and printing 20x per second a line |
44 | // on the console with the current time. So I guess performance is not such a |
45 | // factor here. |
46 | //#define GET_POSITION_METHOD1 |
47 | #define GET_POSITION_METHOD2 |
48 | |
49 | |
50 | // The default time for a period in microseconds. |
51 | // For very small buffers, only 2 periods are used. |
52 | #define DEFAULT_PERIOD_TIME 20000 /* 20ms */ |
53 | |
54 | ///// implemented functions of DirectAudio.h |
55 | |
56 | INT32 DAUDIO_GetDirectAudioDeviceCount() { |
57 | return (INT32) getAudioDeviceCount(); |
58 | } |
59 | |
60 | |
61 | INT32 DAUDIO_GetDirectAudioDeviceDescription(INT32 mixerIndex, DirectAudioDeviceDescription* description) { |
62 | ALSA_AudioDeviceDescription adesc; |
63 | |
64 | adesc.index = (int) mixerIndex; |
65 | adesc.strLen = DAUDIO_STRING_LENGTH; |
66 | |
67 | adesc.maxSimultaneousLines = (int*) (&(description->maxSimulLines)); |
68 | adesc.deviceID = &(description->deviceID); |
69 | adesc.name = description->name; |
70 | adesc.vendor = description->vendor; |
71 | adesc.description = description->description; |
72 | adesc.version = description->version; |
73 | |
74 | return getAudioDeviceDescriptionByIndex(&adesc); |
75 | } |
76 | |
77 | #define MAX_BIT_INDEX 6 |
78 | // returns |
79 | // 6: for anything above 24-bit |
80 | // 5: for 4 bytes sample size, 24-bit |
81 | // 4: for 3 bytes sample size, 24-bit |
82 | // 3: for 3 bytes sample size, 20-bit |
83 | // 2: for 2 bytes sample size, 16-bit |
84 | // 1: for 1 byte sample size, 8-bit |
85 | // 0: for anything else |
86 | int getBitIndex(int sampleSizeInBytes, int significantBits) { |
87 | if (significantBits > 24) return 6; |
88 | if (sampleSizeInBytes == 4 && significantBits == 24) return 5; |
89 | if (sampleSizeInBytes == 3) { |
90 | if (significantBits == 24) return 4; |
91 | if (significantBits == 20) return 3; |
92 | } |
93 | if (sampleSizeInBytes == 2 && significantBits == 16) return 2; |
94 | if (sampleSizeInBytes == 1 && significantBits == 8) return 1; |
95 | return 0; |
96 | } |
97 | |
98 | int getSampleSizeInBytes(int bitIndex, int sampleSizeInBytes) { |
99 | switch(bitIndex) { |
100 | case 1: return 1; |
101 | case 2: return 2; |
102 | case 3: /* fall through */ |
103 | case 4: return 3; |
104 | case 5: return 4; |
105 | } |
106 | return sampleSizeInBytes; |
107 | } |
108 | |
109 | int getSignificantBits(int bitIndex, int significantBits) { |
110 | switch(bitIndex) { |
111 | case 1: return 8; |
112 | case 2: return 16; |
113 | case 3: return 20; |
114 | case 4: /* fall through */ |
115 | case 5: return 24; |
116 | } |
117 | return significantBits; |
118 | } |
119 | |
120 | void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* creator) { |
121 | snd_pcm_t* handle; |
122 | snd_pcm_format_mask_t* formatMask; |
123 | snd_pcm_format_t format; |
124 | snd_pcm_hw_params_t* hwParams; |
125 | int handledBits[MAX_BIT_INDEX+1]; |
126 | |
127 | int ret; |
128 | int sampleSizeInBytes, significantBits, isSigned, isBigEndian, enc; |
129 | int origSampleSizeInBytes, origSignificantBits; |
130 | unsigned int channels, minChannels, maxChannels; |
131 | int rate, bitIndex; |
132 | |
133 | for (bitIndex = 0; bitIndex <= MAX_BIT_INDEX; bitIndex++) handledBits[bitIndex] = FALSE; |
134 | if (openPCMfromDeviceID(deviceID, &handle, isSource, TRUE /*query hardware*/) < 0) { |
135 | return; |
136 | } |
137 | ret = snd_pcm_format_mask_malloc(&formatMask); |
138 | if (ret != 0) { |
139 | ERROR1("snd_pcm_format_mask_malloc returned error %d\n" , ret); |
140 | } else { |
141 | ret = snd_pcm_hw_params_malloc(&hwParams); |
142 | if (ret != 0) { |
143 | ERROR1("snd_pcm_hw_params_malloc returned error %d\n" , ret); |
144 | } else { |
145 | ret = snd_pcm_hw_params_any(handle, hwParams); |
146 | /* snd_pcm_hw_params_any can return a positive value on success too */ |
147 | if (ret < 0) { |
148 | ERROR1("snd_pcm_hw_params_any returned error %d\n" , ret); |
149 | } else { |
150 | /* for the logic following this code, set ret to 0 to indicate success */ |
151 | ret = 0; |
152 | } |
153 | } |
154 | snd_pcm_hw_params_get_format_mask(hwParams, formatMask); |
155 | if (ret == 0) { |
156 | ret = snd_pcm_hw_params_get_channels_min(hwParams, &minChannels); |
157 | if (ret != 0) { |
158 | ERROR1("snd_pcm_hw_params_get_channels_min returned error %d\n" , ret); |
159 | } |
160 | } |
161 | if (ret == 0) { |
162 | ret = snd_pcm_hw_params_get_channels_max(hwParams, &maxChannels); |
163 | if (ret != 0) { |
164 | ERROR1("snd_pcm_hw_params_get_channels_max returned error %d\n" , ret); |
165 | } |
166 | } |
167 | |
168 | // since we queried the hw: device, for many soundcards, it will only |
169 | // report the maximum number of channels (which is the only way to talk |
170 | // to the hw: device). Since we will, however, open the plughw: device |
171 | // when opening the Source/TargetDataLine, we can safely assume that |
172 | // also the channels 1..maxChannels are available. |
173 | #ifdef ALSA_PCM_USE_PLUGHW |
174 | minChannels = 1; |
175 | #endif |
176 | if (ret == 0) { |
177 | // plughw: supports any sample rate |
178 | rate = -1; |
179 | for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) { |
180 | if (snd_pcm_format_mask_test(formatMask, format)) { |
181 | // format exists |
182 | if (getFormatFromAlsaFormat(format, &origSampleSizeInBytes, |
183 | &origSignificantBits, |
184 | &isSigned, &isBigEndian, &enc)) { |
185 | // now if we use plughw:, we can use any bit size below the |
186 | // natively supported ones. Some ALSA drivers only support the maximum |
187 | // bit size, so we add any sample rates below the reported one. |
188 | // E.g. this iteration reports support for 16-bit. |
189 | // getBitIndex will return 2, so it will add entries for |
190 | // 16-bit (bitIndex=2) and in the next do-while loop iteration, |
191 | // it will decrease bitIndex and will therefore add 8-bit support. |
192 | bitIndex = getBitIndex(origSampleSizeInBytes, origSignificantBits); |
193 | do { |
194 | if (bitIndex == 0 |
195 | || bitIndex == MAX_BIT_INDEX |
196 | || !handledBits[bitIndex]) { |
197 | handledBits[bitIndex] = TRUE; |
198 | sampleSizeInBytes = getSampleSizeInBytes(bitIndex, origSampleSizeInBytes); |
199 | significantBits = getSignificantBits(bitIndex, origSignificantBits); |
200 | if (maxChannels - minChannels > MAXIMUM_LISTED_CHANNELS) { |
201 | // avoid too many channels explicitly listed |
202 | // just add -1, min, and max |
203 | DAUDIO_AddAudioFormat(creator, significantBits, |
204 | -1, -1, rate, |
205 | enc, isSigned, isBigEndian); |
206 | DAUDIO_AddAudioFormat(creator, significantBits, |
207 | sampleSizeInBytes * minChannels, |
208 | minChannels, rate, |
209 | enc, isSigned, isBigEndian); |
210 | DAUDIO_AddAudioFormat(creator, significantBits, |
211 | sampleSizeInBytes * maxChannels, |
212 | maxChannels, rate, |
213 | enc, isSigned, isBigEndian); |
214 | } else { |
215 | for (channels = minChannels; channels <= maxChannels; channels++) { |
216 | DAUDIO_AddAudioFormat(creator, significantBits, |
217 | sampleSizeInBytes * channels, |
218 | channels, rate, |
219 | enc, isSigned, isBigEndian); |
220 | } |
221 | } |
222 | } |
223 | #ifndef ALSA_PCM_USE_PLUGHW |
224 | // without plugin, do not add fake formats |
225 | break; |
226 | #endif |
227 | } while (--bitIndex > 0); |
228 | } else { |
229 | TRACE1("could not get format from alsa for format %d\n" , format); |
230 | } |
231 | } else { |
232 | //TRACE1("Format %d not supported\n", format); |
233 | } |
234 | } // for loop |
235 | snd_pcm_hw_params_free(hwParams); |
236 | } |
237 | snd_pcm_format_mask_free(formatMask); |
238 | } |
239 | snd_pcm_close(handle); |
240 | } |
241 | |
242 | /** Workaround for cr 7033899, 7030629: |
243 | * dmix plugin doesn't like flush (snd_pcm_drop) when the buffer is empty |
244 | * (just opened, underruned or already flushed). |
245 | * Sometimes it causes PCM falls to -EBADFD error, |
246 | * sometimes causes bufferSize change. |
247 | * To prevent unnecessary flushes AlsaPcmInfo::isRunning & isFlushed are used. |
248 | */ |
249 | /* ******* ALSA PCM INFO ******************** */ |
250 | typedef struct tag_AlsaPcmInfo { |
251 | snd_pcm_t* handle; |
252 | snd_pcm_hw_params_t* hwParams; |
253 | snd_pcm_sw_params_t* swParams; |
254 | int bufferSizeInBytes; |
255 | int frameSize; // storage size in Bytes |
256 | unsigned int periods; |
257 | snd_pcm_uframes_t periodSize; |
258 | short int isRunning; // see comment above |
259 | short int isFlushed; // see comment above |
260 | #ifdef GET_POSITION_METHOD2 |
261 | // to be used exclusively by getBytePosition! |
262 | snd_pcm_status_t* positionStatus; |
263 | #endif |
264 | } AlsaPcmInfo; |
265 | |
266 | |
267 | int setStartThresholdNoCommit(AlsaPcmInfo* info, int useThreshold) { |
268 | int ret; |
269 | int threshold; |
270 | |
271 | if (useThreshold) { |
272 | // start device whenever anything is written to the buffer |
273 | threshold = 1; |
274 | } else { |
275 | // never start the device automatically |
276 | threshold = 2000000000; /* near UINT_MAX */ |
277 | } |
278 | ret = snd_pcm_sw_params_set_start_threshold(info->handle, info->swParams, threshold); |
279 | if (ret < 0) { |
280 | ERROR1("Unable to set start threshold mode: %s\n" , snd_strerror(ret)); |
281 | return FALSE; |
282 | } |
283 | return TRUE; |
284 | } |
285 | |
286 | int setStartThreshold(AlsaPcmInfo* info, int useThreshold) { |
287 | int ret = 0; |
288 | |
289 | if (!setStartThresholdNoCommit(info, useThreshold)) { |
290 | ret = -1; |
291 | } |
292 | if (ret == 0) { |
293 | // commit it |
294 | ret = snd_pcm_sw_params(info->handle, info->swParams); |
295 | if (ret < 0) { |
296 | ERROR1("Unable to set sw params: %s\n" , snd_strerror(ret)); |
297 | } |
298 | } |
299 | return (ret == 0)?TRUE:FALSE; |
300 | } |
301 | |
302 | |
303 | // returns TRUE if successful |
304 | int setHWParams(AlsaPcmInfo* info, |
305 | float sampleRate, |
306 | int channels, |
307 | int bufferSizeInFrames, |
308 | snd_pcm_format_t format) { |
309 | unsigned int rrate, periodTime, periods; |
310 | int ret, dir; |
311 | snd_pcm_uframes_t alsaBufferSizeInFrames = (snd_pcm_uframes_t) bufferSizeInFrames; |
312 | |
313 | /* choose all parameters */ |
314 | ret = snd_pcm_hw_params_any(info->handle, info->hwParams); |
315 | if (ret < 0) { |
316 | ERROR1("Broken configuration: no configurations available: %s\n" , snd_strerror(ret)); |
317 | return FALSE; |
318 | } |
319 | /* set the interleaved read/write format */ |
320 | ret = snd_pcm_hw_params_set_access(info->handle, info->hwParams, SND_PCM_ACCESS_RW_INTERLEAVED); |
321 | if (ret < 0) { |
322 | ERROR1("SND_PCM_ACCESS_RW_INTERLEAVED access type not available: %s\n" , snd_strerror(ret)); |
323 | return FALSE; |
324 | } |
325 | /* set the sample format */ |
326 | ret = snd_pcm_hw_params_set_format(info->handle, info->hwParams, format); |
327 | if (ret < 0) { |
328 | ERROR1("Sample format not available: %s\n" , snd_strerror(ret)); |
329 | return FALSE; |
330 | } |
331 | /* set the count of channels */ |
332 | ret = snd_pcm_hw_params_set_channels(info->handle, info->hwParams, channels); |
333 | if (ret < 0) { |
334 | ERROR2("Channels count (%d) not available: %s\n" , channels, snd_strerror(ret)); |
335 | return FALSE; |
336 | } |
337 | /* set the stream rate */ |
338 | rrate = (int) (sampleRate + 0.5f); |
339 | dir = 0; |
340 | ret = snd_pcm_hw_params_set_rate_near(info->handle, info->hwParams, &rrate, &dir); |
341 | if (ret < 0) { |
342 | ERROR2("Rate %dHz not available for playback: %s\n" , (int) (sampleRate+0.5f), snd_strerror(ret)); |
343 | return FALSE; |
344 | } |
345 | if ((rrate-sampleRate > 2) || (rrate-sampleRate < - 2)) { |
346 | ERROR2("Rate doesn't match (requested %2.2fHz, got %dHz)\n" , sampleRate, rrate); |
347 | return FALSE; |
348 | } |
349 | /* set the buffer time */ |
350 | ret = snd_pcm_hw_params_set_buffer_size_near(info->handle, info->hwParams, &alsaBufferSizeInFrames); |
351 | if (ret < 0) { |
352 | ERROR2("Unable to set buffer size to %d frames: %s\n" , |
353 | (int) alsaBufferSizeInFrames, snd_strerror(ret)); |
354 | return FALSE; |
355 | } |
356 | bufferSizeInFrames = (int) alsaBufferSizeInFrames; |
357 | /* set the period time */ |
358 | if (bufferSizeInFrames > 1024) { |
359 | dir = 0; |
360 | periodTime = DEFAULT_PERIOD_TIME; |
361 | ret = snd_pcm_hw_params_set_period_time_near(info->handle, info->hwParams, &periodTime, &dir); |
362 | if (ret < 0) { |
363 | ERROR2("Unable to set period time to %d: %s\n" , DEFAULT_PERIOD_TIME, snd_strerror(ret)); |
364 | return FALSE; |
365 | } |
366 | } else { |
367 | /* set the period count for very small buffer sizes to 2 */ |
368 | dir = 0; |
369 | periods = 2; |
370 | ret = snd_pcm_hw_params_set_periods_near(info->handle, info->hwParams, &periods, &dir); |
371 | if (ret < 0) { |
372 | ERROR2("Unable to set period count to %d: %s\n" , /*periods*/ 2, snd_strerror(ret)); |
373 | return FALSE; |
374 | } |
375 | } |
376 | /* write the parameters to device */ |
377 | ret = snd_pcm_hw_params(info->handle, info->hwParams); |
378 | if (ret < 0) { |
379 | ERROR1("Unable to set hw params: %s\n" , snd_strerror(ret)); |
380 | return FALSE; |
381 | } |
382 | return TRUE; |
383 | } |
384 | |
385 | // returns 1 if successful |
386 | int setSWParams(AlsaPcmInfo* info) { |
387 | int ret; |
388 | |
389 | /* get the current swparams */ |
390 | ret = snd_pcm_sw_params_current(info->handle, info->swParams); |
391 | if (ret < 0) { |
392 | ERROR1("Unable to determine current swparams: %s\n" , snd_strerror(ret)); |
393 | return FALSE; |
394 | } |
395 | /* never start the transfer automatically */ |
396 | if (!setStartThresholdNoCommit(info, FALSE /* don't use threshold */)) { |
397 | return FALSE; |
398 | } |
399 | |
400 | /* allow the transfer when at least period_size samples can be processed */ |
401 | ret = snd_pcm_sw_params_set_avail_min(info->handle, info->swParams, info->periodSize); |
402 | if (ret < 0) { |
403 | ERROR1("Unable to set avail min for playback: %s\n" , snd_strerror(ret)); |
404 | return FALSE; |
405 | } |
406 | /* write the parameters to the playback device */ |
407 | ret = snd_pcm_sw_params(info->handle, info->swParams); |
408 | if (ret < 0) { |
409 | ERROR1("Unable to set sw params: %s\n" , snd_strerror(ret)); |
410 | return FALSE; |
411 | } |
412 | return TRUE; |
413 | } |
414 | |
415 | static snd_output_t* ALSA_OUTPUT = NULL; |
416 | |
417 | void* DAUDIO_Open(INT32 mixerIndex, INT32 deviceID, int isSource, |
418 | int encoding, float sampleRate, int sampleSizeInBits, |
419 | int frameSize, int channels, |
420 | int isSigned, int isBigEndian, int bufferSizeInBytes) { |
421 | snd_pcm_format_mask_t* formatMask; |
422 | snd_pcm_format_t format; |
423 | int dir; |
424 | int ret = 0; |
425 | AlsaPcmInfo* info = NULL; |
426 | /* snd_pcm_uframes_t is 64 bit on 64-bit systems */ |
427 | snd_pcm_uframes_t alsaBufferSizeInFrames = 0; |
428 | |
429 | |
430 | TRACE0("> DAUDIO_Open\n" ); |
431 | #ifdef USE_TRACE |
432 | // for using ALSA debug dump methods |
433 | if (ALSA_OUTPUT == NULL) { |
434 | snd_output_stdio_attach(&ALSA_OUTPUT, stdout, 0); |
435 | } |
436 | #endif |
437 | if (channels <= 0) { |
438 | ERROR1("ERROR: Invalid number of channels=%d!\n" , channels); |
439 | return NULL; |
440 | } |
441 | info = (AlsaPcmInfo*) malloc(sizeof(AlsaPcmInfo)); |
442 | if (!info) { |
443 | ERROR0("Out of memory\n" ); |
444 | return NULL; |
445 | } |
446 | memset(info, 0, sizeof(AlsaPcmInfo)); |
447 | // initial values are: stopped, flushed |
448 | info->isRunning = 0; |
449 | info->isFlushed = 1; |
450 | |
451 | ret = openPCMfromDeviceID(deviceID, &(info->handle), isSource, FALSE /* do open device*/); |
452 | if (ret == 0) { |
453 | // set to blocking mode |
454 | snd_pcm_nonblock(info->handle, 0); |
455 | ret = snd_pcm_hw_params_malloc(&(info->hwParams)); |
456 | if (ret != 0) { |
457 | ERROR1(" snd_pcm_hw_params_malloc returned error %d\n" , ret); |
458 | } else { |
459 | ret = -1; |
460 | if (getAlsaFormatFromFormat(&format, frameSize / channels, sampleSizeInBits, |
461 | isSigned, isBigEndian, encoding)) { |
462 | if (setHWParams(info, |
463 | sampleRate, |
464 | channels, |
465 | bufferSizeInBytes / frameSize, |
466 | format)) { |
467 | info->frameSize = frameSize; |
468 | ret = snd_pcm_hw_params_get_period_size(info->hwParams, &info->periodSize, &dir); |
469 | if (ret < 0) { |
470 | ERROR1("ERROR: snd_pcm_hw_params_get_period: %s\n" , snd_strerror(ret)); |
471 | } |
472 | snd_pcm_hw_params_get_periods(info->hwParams, &(info->periods), &dir); |
473 | snd_pcm_hw_params_get_buffer_size(info->hwParams, &alsaBufferSizeInFrames); |
474 | info->bufferSizeInBytes = (int) alsaBufferSizeInFrames * frameSize; |
475 | TRACE3(" DAUDIO_Open: period size = %d frames, periods = %d. Buffer size: %d bytes.\n" , |
476 | (int) info->periodSize, info->periods, info->bufferSizeInBytes); |
477 | } |
478 | } |
479 | } |
480 | if (ret == 0) { |
481 | // set software parameters |
482 | ret = snd_pcm_sw_params_malloc(&(info->swParams)); |
483 | if (ret != 0) { |
484 | ERROR1("snd_pcm_hw_params_malloc returned error %d\n" , ret); |
485 | } else { |
486 | if (!setSWParams(info)) { |
487 | ret = -1; |
488 | } |
489 | } |
490 | } |
491 | if (ret == 0) { |
492 | // prepare device |
493 | ret = snd_pcm_prepare(info->handle); |
494 | if (ret < 0) { |
495 | ERROR1("ERROR: snd_pcm_prepare: %s\n" , snd_strerror(ret)); |
496 | } |
497 | } |
498 | |
499 | #ifdef GET_POSITION_METHOD2 |
500 | if (ret == 0) { |
501 | ret = snd_pcm_status_malloc(&(info->positionStatus)); |
502 | if (ret != 0) { |
503 | ERROR1("ERROR in snd_pcm_status_malloc: %s\n" , snd_strerror(ret)); |
504 | } |
505 | } |
506 | #endif |
507 | } |
508 | if (ret != 0) { |
509 | DAUDIO_Close((void*) info, isSource); |
510 | info = NULL; |
511 | } else { |
512 | // set to non-blocking mode |
513 | snd_pcm_nonblock(info->handle, 1); |
514 | TRACE1("< DAUDIO_Open: Opened device successfully. Handle=%p\n" , |
515 | (void*) info->handle); |
516 | } |
517 | return (void*) info; |
518 | } |
519 | |
520 | #ifdef USE_TRACE |
521 | void printState(snd_pcm_state_t state) { |
522 | if (state == SND_PCM_STATE_OPEN) { |
523 | TRACE0("State: SND_PCM_STATE_OPEN\n" ); |
524 | } |
525 | else if (state == SND_PCM_STATE_SETUP) { |
526 | TRACE0("State: SND_PCM_STATE_SETUP\n" ); |
527 | } |
528 | else if (state == SND_PCM_STATE_PREPARED) { |
529 | TRACE0("State: SND_PCM_STATE_PREPARED\n" ); |
530 | } |
531 | else if (state == SND_PCM_STATE_RUNNING) { |
532 | TRACE0("State: SND_PCM_STATE_RUNNING\n" ); |
533 | } |
534 | else if (state == SND_PCM_STATE_XRUN) { |
535 | TRACE0("State: SND_PCM_STATE_XRUN\n" ); |
536 | } |
537 | else if (state == SND_PCM_STATE_DRAINING) { |
538 | TRACE0("State: SND_PCM_STATE_DRAINING\n" ); |
539 | } |
540 | else if (state == SND_PCM_STATE_PAUSED) { |
541 | TRACE0("State: SND_PCM_STATE_PAUSED\n" ); |
542 | } |
543 | else if (state == SND_PCM_STATE_SUSPENDED) { |
544 | TRACE0("State: SND_PCM_STATE_SUSPENDED\n" ); |
545 | } |
546 | } |
547 | #endif |
548 | |
549 | int DAUDIO_Start(void* id, int isSource) { |
550 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
551 | int ret; |
552 | snd_pcm_state_t state; |
553 | |
554 | TRACE0("> DAUDIO_Start\n" ); |
555 | // set to blocking mode |
556 | snd_pcm_nonblock(info->handle, 0); |
557 | // set start mode so that it always starts as soon as data is there |
558 | setStartThreshold(info, TRUE /* use threshold */); |
559 | state = snd_pcm_state(info->handle); |
560 | if (state == SND_PCM_STATE_PAUSED) { |
561 | // in case it was stopped previously |
562 | TRACE0(" Un-pausing...\n" ); |
563 | ret = snd_pcm_pause(info->handle, FALSE); |
564 | if (ret != 0) { |
565 | ERROR2(" NOTE: error in snd_pcm_pause:%d: %s\n" , ret, snd_strerror(ret)); |
566 | } |
567 | } |
568 | if (state == SND_PCM_STATE_SUSPENDED) { |
569 | TRACE0(" Resuming...\n" ); |
570 | ret = snd_pcm_resume(info->handle); |
571 | if (ret < 0) { |
572 | if ((ret != -EAGAIN) && (ret != -ENOSYS)) { |
573 | ERROR2(" ERROR: error in snd_pcm_resume:%d: %s\n" , ret, snd_strerror(ret)); |
574 | } |
575 | } |
576 | } |
577 | if (state == SND_PCM_STATE_SETUP) { |
578 | TRACE0("need to call prepare again...\n" ); |
579 | // prepare device |
580 | ret = snd_pcm_prepare(info->handle); |
581 | if (ret < 0) { |
582 | ERROR1("ERROR: snd_pcm_prepare: %s\n" , snd_strerror(ret)); |
583 | } |
584 | } |
585 | // in case there is still data in the buffers |
586 | ret = snd_pcm_start(info->handle); |
587 | if (ret != 0) { |
588 | if (ret != -EPIPE) { |
589 | ERROR2(" NOTE: error in snd_pcm_start: %d: %s\n" , ret, snd_strerror(ret)); |
590 | } |
591 | } |
592 | // set to non-blocking mode |
593 | ret = snd_pcm_nonblock(info->handle, 1); |
594 | if (ret != 0) { |
595 | ERROR1(" ERROR in snd_pcm_nonblock: %s\n" , snd_strerror(ret)); |
596 | } |
597 | state = snd_pcm_state(info->handle); |
598 | #ifdef USE_TRACE |
599 | printState(state); |
600 | #endif |
601 | ret = (state == SND_PCM_STATE_PREPARED) |
602 | || (state == SND_PCM_STATE_RUNNING) |
603 | || (state == SND_PCM_STATE_XRUN) |
604 | || (state == SND_PCM_STATE_SUSPENDED); |
605 | if (ret) { |
606 | info->isRunning = 1; |
607 | // source line should keep isFlushed value until Write() is called; |
608 | // for target data line reset it right now. |
609 | if (!isSource) { |
610 | info->isFlushed = 0; |
611 | } |
612 | } |
613 | TRACE1("< DAUDIO_Start %s\n" , ret?"success" :"error" ); |
614 | return ret?TRUE:FALSE; |
615 | } |
616 | |
617 | int DAUDIO_Stop(void* id, int isSource) { |
618 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
619 | int ret; |
620 | |
621 | TRACE0("> DAUDIO_Stop\n" ); |
622 | // set to blocking mode |
623 | snd_pcm_nonblock(info->handle, 0); |
624 | setStartThreshold(info, FALSE /* don't use threshold */); // device will not start after buffer xrun |
625 | ret = snd_pcm_pause(info->handle, 1); |
626 | // set to non-blocking mode |
627 | snd_pcm_nonblock(info->handle, 1); |
628 | if (ret != 0) { |
629 | ERROR1("ERROR in snd_pcm_pause: %s\n" , snd_strerror(ret)); |
630 | return FALSE; |
631 | } |
632 | info->isRunning = 0; |
633 | TRACE0("< DAUDIO_Stop success\n" ); |
634 | return TRUE; |
635 | } |
636 | |
637 | void DAUDIO_Close(void* id, int isSource) { |
638 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
639 | |
640 | TRACE0("DAUDIO_Close\n" ); |
641 | if (info != NULL) { |
642 | if (info->handle != NULL) { |
643 | snd_pcm_close(info->handle); |
644 | } |
645 | if (info->hwParams) { |
646 | snd_pcm_hw_params_free(info->hwParams); |
647 | } |
648 | if (info->swParams) { |
649 | snd_pcm_sw_params_free(info->swParams); |
650 | } |
651 | #ifdef GET_POSITION_METHOD2 |
652 | if (info->positionStatus) { |
653 | snd_pcm_status_free(info->positionStatus); |
654 | } |
655 | #endif |
656 | free(info); |
657 | } |
658 | } |
659 | |
660 | /* |
661 | * Underrun and suspend recovery |
662 | * returns |
663 | * 0: exit native and return 0 |
664 | * 1: try again to write/read |
665 | * -1: error - exit native with return value -1 |
666 | */ |
667 | int xrun_recovery(AlsaPcmInfo* info, int err) { |
668 | int ret; |
669 | |
670 | if (err == -EPIPE) { /* underrun / overflow */ |
671 | TRACE0("xrun_recovery: underrun/overflow.\n" ); |
672 | ret = snd_pcm_prepare(info->handle); |
673 | if (ret < 0) { |
674 | ERROR1("Can't recover from underrun/overflow, prepare failed: %s\n" , snd_strerror(ret)); |
675 | return -1; |
676 | } |
677 | return 1; |
678 | } else if (err == -ESTRPIPE) { |
679 | TRACE0("xrun_recovery: suspended.\n" ); |
680 | ret = snd_pcm_resume(info->handle); |
681 | if (ret < 0) { |
682 | if (ret == -EAGAIN) { |
683 | return 0; /* wait until the suspend flag is released */ |
684 | } |
685 | return -1; |
686 | } |
687 | ret = snd_pcm_prepare(info->handle); |
688 | if (ret < 0) { |
689 | ERROR1("Can't recover from underrun/overflow, prepare failed: %s\n" , snd_strerror(ret)); |
690 | return -1; |
691 | } |
692 | return 1; |
693 | } else if (err == -EAGAIN) { |
694 | TRACE0("xrun_recovery: EAGAIN try again flag.\n" ); |
695 | return 0; |
696 | } |
697 | |
698 | TRACE2("xrun_recovery: unexpected error %d: %s\n" , err, snd_strerror(err)); |
699 | return -1; |
700 | } |
701 | |
702 | // returns -1 on error |
703 | int DAUDIO_Write(void* id, char* data, int byteSize) { |
704 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
705 | int ret, count; |
706 | snd_pcm_sframes_t frameSize, writtenFrames; |
707 | |
708 | TRACE1("> DAUDIO_Write %d bytes\n" , byteSize); |
709 | |
710 | /* sanity */ |
711 | if (byteSize <= 0 || info->frameSize <= 0) { |
712 | ERROR2(" DAUDIO_Write: byteSize=%d, frameSize=%d!\n" , |
713 | (int) byteSize, (int) info->frameSize); |
714 | TRACE0("< DAUDIO_Write returning -1\n" ); |
715 | return -1; |
716 | } |
717 | |
718 | count = 2; // maximum number of trials to recover from underrun |
719 | //frameSize = snd_pcm_bytes_to_frames(info->handle, byteSize); |
720 | frameSize = (snd_pcm_sframes_t) (byteSize / info->frameSize); |
721 | do { |
722 | writtenFrames = snd_pcm_writei(info->handle, (const void*) data, (snd_pcm_uframes_t) frameSize); |
723 | |
724 | if (writtenFrames < 0) { |
725 | ret = xrun_recovery(info, (int) writtenFrames); |
726 | if (ret <= 0) { |
727 | TRACE1("DAUDIO_Write: xrun recovery returned %d -> return.\n" , ret); |
728 | return ret; |
729 | } |
730 | if (count-- <= 0) { |
731 | ERROR0("DAUDIO_Write: too many attempts to recover from xrun/suspend\n" ); |
732 | return -1; |
733 | } |
734 | } else { |
735 | break; |
736 | } |
737 | } while (TRUE); |
738 | //ret = snd_pcm_frames_to_bytes(info->handle, writtenFrames); |
739 | |
740 | if (writtenFrames > 0) { |
741 | // reset "flushed" flag |
742 | info->isFlushed = 0; |
743 | } |
744 | |
745 | ret = (int) (writtenFrames * info->frameSize); |
746 | TRACE1("< DAUDIO_Write: returning %d bytes.\n" , ret); |
747 | return ret; |
748 | } |
749 | |
750 | // returns -1 on error |
751 | int DAUDIO_Read(void* id, char* data, int byteSize) { |
752 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
753 | int ret, count; |
754 | snd_pcm_sframes_t frameSize, readFrames; |
755 | |
756 | TRACE1("> DAUDIO_Read %d bytes\n" , byteSize); |
757 | /*TRACE3(" info=%p, data=%p, byteSize=%d\n", |
758 | (void*) info, (void*) data, (int) byteSize); |
759 | TRACE2(" info->frameSize=%d, info->handle=%p\n", |
760 | (int) info->frameSize, (void*) info->handle); |
761 | */ |
762 | /* sanity */ |
763 | if (byteSize <= 0 || info->frameSize <= 0) { |
764 | ERROR2(" DAUDIO_Read: byteSize=%d, frameSize=%d!\n" , |
765 | (int) byteSize, (int) info->frameSize); |
766 | TRACE0("< DAUDIO_Read returning -1\n" ); |
767 | return -1; |
768 | } |
769 | if (!info->isRunning && info->isFlushed) { |
770 | // PCM has nothing to read |
771 | return 0; |
772 | } |
773 | |
774 | count = 2; // maximum number of trials to recover from error |
775 | //frameSize = snd_pcm_bytes_to_frames(info->handle, byteSize); |
776 | frameSize = (snd_pcm_sframes_t) (byteSize / info->frameSize); |
777 | do { |
778 | readFrames = snd_pcm_readi(info->handle, (void*) data, (snd_pcm_uframes_t) frameSize); |
779 | if (readFrames < 0) { |
780 | ret = xrun_recovery(info, (int) readFrames); |
781 | if (ret <= 0) { |
782 | TRACE1("DAUDIO_Read: xrun recovery returned %d -> return.\n" , ret); |
783 | return ret; |
784 | } |
785 | if (count-- <= 0) { |
786 | ERROR0("DAUDIO_Read: too many attempts to recover from xrun/suspend\n" ); |
787 | return -1; |
788 | } |
789 | } else { |
790 | break; |
791 | } |
792 | } while (TRUE); |
793 | //ret = snd_pcm_frames_to_bytes(info->handle, readFrames); |
794 | ret = (int) (readFrames * info->frameSize); |
795 | TRACE1("< DAUDIO_Read: returning %d bytes.\n" , ret); |
796 | return ret; |
797 | } |
798 | |
799 | |
800 | int DAUDIO_GetBufferSize(void* id, int isSource) { |
801 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
802 | |
803 | return info->bufferSizeInBytes; |
804 | } |
805 | |
806 | int DAUDIO_StillDraining(void* id, int isSource) { |
807 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
808 | snd_pcm_state_t state; |
809 | |
810 | state = snd_pcm_state(info->handle); |
811 | //printState(state); |
812 | //TRACE1("Still draining: %s\n", (state != SND_PCM_STATE_XRUN)?"TRUE":"FALSE"); |
813 | return (state == SND_PCM_STATE_RUNNING)?TRUE:FALSE; |
814 | } |
815 | |
816 | |
817 | int DAUDIO_Flush(void* id, int isSource) { |
818 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
819 | int ret; |
820 | |
821 | TRACE0("DAUDIO_Flush\n" ); |
822 | |
823 | if (info->isFlushed) { |
824 | // nothing to drop |
825 | return 1; |
826 | } |
827 | |
828 | ret = snd_pcm_drop(info->handle); |
829 | if (ret != 0) { |
830 | ERROR1("ERROR in snd_pcm_drop: %s\n" , snd_strerror(ret)); |
831 | return FALSE; |
832 | } |
833 | |
834 | info->isFlushed = 1; |
835 | if (info->isRunning) { |
836 | ret = DAUDIO_Start(id, isSource); |
837 | } |
838 | return ret; |
839 | } |
840 | |
841 | int DAUDIO_GetAvailable(void* id, int isSource) { |
842 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
843 | snd_pcm_sframes_t availableInFrames; |
844 | snd_pcm_state_t state; |
845 | int ret; |
846 | |
847 | state = snd_pcm_state(info->handle); |
848 | if (info->isFlushed || state == SND_PCM_STATE_XRUN) { |
849 | // if in xrun state then we have the entire buffer available, |
850 | // not 0 as alsa reports |
851 | ret = info->bufferSizeInBytes; |
852 | } else { |
853 | availableInFrames = snd_pcm_avail_update(info->handle); |
854 | if (availableInFrames < 0) { |
855 | ret = 0; |
856 | } else { |
857 | //ret = snd_pcm_frames_to_bytes(info->handle, availableInFrames); |
858 | ret = (int) (availableInFrames * info->frameSize); |
859 | } |
860 | } |
861 | TRACE1("DAUDIO_GetAvailable returns %d bytes\n" , ret); |
862 | return ret; |
863 | } |
864 | |
865 | INT64 estimatePositionFromAvail(AlsaPcmInfo* info, int isSource, INT64 javaBytePos, int availInBytes) { |
866 | // estimate the current position with the buffer size and |
867 | // the available bytes to read or write in the buffer. |
868 | // not an elegant solution - bytePos will stop on xruns, |
869 | // and in race conditions it may jump backwards |
870 | // Advantage is that it is indeed based on the samples that go through |
871 | // the system (rather than time-based methods) |
872 | if (isSource) { |
873 | // javaBytePos is the position that is reached when the current |
874 | // buffer is played completely |
875 | return (INT64) (javaBytePos - info->bufferSizeInBytes + availInBytes); |
876 | } else { |
877 | // javaBytePos is the position that was when the current buffer was empty |
878 | return (INT64) (javaBytePos + availInBytes); |
879 | } |
880 | } |
881 | |
882 | INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) { |
883 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
884 | int ret; |
885 | INT64 result = javaBytePos; |
886 | snd_pcm_state_t state; |
887 | state = snd_pcm_state(info->handle); |
888 | |
889 | if (!info->isFlushed && state != SND_PCM_STATE_XRUN) { |
890 | #ifdef GET_POSITION_METHOD2 |
891 | snd_timestamp_t* ts; |
892 | snd_pcm_uframes_t framesAvail; |
893 | |
894 | // note: slight race condition if this is called simultaneously from 2 threads |
895 | ret = snd_pcm_status(info->handle, info->positionStatus); |
896 | if (ret != 0) { |
897 | ERROR1("ERROR in snd_pcm_status: %s\n" , snd_strerror(ret)); |
898 | result = javaBytePos; |
899 | } else { |
900 | // calculate from time value, or from available bytes |
901 | framesAvail = snd_pcm_status_get_avail(info->positionStatus); |
902 | result = estimatePositionFromAvail(info, isSource, javaBytePos, framesAvail * info->frameSize); |
903 | } |
904 | #endif |
905 | #ifdef GET_POSITION_METHOD3 |
906 | snd_pcm_uframes_t framesAvail; |
907 | ret = snd_pcm_avail(info->handle, &framesAvail); |
908 | if (ret != 0) { |
909 | ERROR1("ERROR in snd_pcm_avail: %s\n" , snd_strerror(ret)); |
910 | result = javaBytePos; |
911 | } else { |
912 | result = estimatePositionFromAvail(info, isSource, javaBytePos, framesAvail * info->frameSize); |
913 | } |
914 | #endif |
915 | #ifdef GET_POSITION_METHOD1 |
916 | result = estimatePositionFromAvail(info, isSource, javaBytePos, DAUDIO_GetAvailable(id, isSource)); |
917 | #endif |
918 | } |
919 | //printf("getbyteposition: javaBytePos=%d , return=%d\n", (int) javaBytePos, (int) result); |
920 | return result; |
921 | } |
922 | |
923 | |
924 | |
925 | void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) { |
926 | /* save to ignore, since GetBytePosition |
927 | * takes the javaBytePos param into account |
928 | */ |
929 | } |
930 | |
931 | int DAUDIO_RequiresServicing(void* id, int isSource) { |
932 | // never need servicing on Linux |
933 | return FALSE; |
934 | } |
935 | |
936 | void DAUDIO_Service(void* id, int isSource) { |
937 | // never need servicing on Linux |
938 | } |
939 | |
940 | |
941 | #endif // USE_DAUDIO |
942 | |