J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2002-2007 Sun Microsystems, Inc. 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. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any 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 | 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 | if (ret != 0) { |
| 147 | ERROR1("snd_pcm_hw_params_any returned error %d\n", ret); |
| 148 | } |
| 149 | } |
| 150 | snd_pcm_hw_params_get_format_mask(hwParams, formatMask); |
| 151 | #ifdef ALSA_PCM_NEW_HW_PARAMS_API |
| 152 | if (ret == 0) { |
| 153 | ret = snd_pcm_hw_params_get_channels_min(hwParams, &minChannels); |
| 154 | if (ret != 0) { |
| 155 | ERROR1("snd_pcm_hw_params_get_channels_min returned error %d\n", ret); |
| 156 | } |
| 157 | } |
| 158 | if (ret == 0) { |
| 159 | ret = snd_pcm_hw_params_get_channels_max(hwParams, &maxChannels); |
| 160 | if (ret != 0) { |
| 161 | ERROR1("snd_pcm_hw_params_get_channels_max returned error %d\n", ret); |
| 162 | } |
| 163 | } |
| 164 | #else |
| 165 | minChannels = snd_pcm_hw_params_get_channels_min(hwParams); |
| 166 | maxChannels = snd_pcm_hw_params_get_channels_max(hwParams); |
| 167 | if (minChannels > maxChannels) { |
| 168 | ERROR2("MinChannels=%d, maxChannels=%d\n", minChannels, maxChannels); |
| 169 | } |
| 170 | #endif |
| 171 | |
| 172 | // since we queried the hw: device, for many soundcards, it will only |
| 173 | // report the maximum number of channels (which is the only way to talk |
| 174 | // to the hw: device). Since we will, however, open the plughw: device |
| 175 | // when opening the Source/TargetDataLine, we can safely assume that |
| 176 | // also the channels 1..maxChannels are available. |
| 177 | #ifdef ALSA_PCM_USE_PLUGHW |
| 178 | minChannels = 1; |
| 179 | #endif |
| 180 | if (ret == 0) { |
| 181 | // plughw: supports any sample rate |
| 182 | rate = -1; |
| 183 | for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) { |
| 184 | if (snd_pcm_format_mask_test(formatMask, format)) { |
| 185 | // format exists |
| 186 | if (getFormatFromAlsaFormat(format, &origSampleSizeInBytes, |
| 187 | &origSignificantBits, |
| 188 | &isSigned, &isBigEndian, &enc)) { |
| 189 | // now if we use plughw:, we can use any bit size below the |
| 190 | // natively supported ones. Some ALSA drivers only support the maximum |
| 191 | // bit size, so we add any sample rates below the reported one. |
| 192 | // E.g. this iteration reports support for 16-bit. |
| 193 | // getBitIndex will return 2, so it will add entries for |
| 194 | // 16-bit (bitIndex=2) and in the next do-while loop iteration, |
| 195 | // it will decrease bitIndex and will therefore add 8-bit support. |
| 196 | bitIndex = getBitIndex(origSampleSizeInBytes, origSignificantBits); |
| 197 | do { |
| 198 | if (bitIndex == 0 |
| 199 | || bitIndex == MAX_BIT_INDEX |
| 200 | || !handledBits[bitIndex]) { |
| 201 | handledBits[bitIndex] = TRUE; |
| 202 | sampleSizeInBytes = getSampleSizeInBytes(bitIndex, origSampleSizeInBytes); |
| 203 | significantBits = getSignificantBits(bitIndex, origSignificantBits); |
| 204 | if (maxChannels - minChannels > MAXIMUM_LISTED_CHANNELS) { |
| 205 | // avoid too many channels explicitly listed |
| 206 | // just add -1, min, and max |
| 207 | DAUDIO_AddAudioFormat(creator, significantBits, |
| 208 | -1, -1, rate, |
| 209 | enc, isSigned, isBigEndian); |
| 210 | DAUDIO_AddAudioFormat(creator, significantBits, |
| 211 | sampleSizeInBytes * minChannels, |
| 212 | minChannels, rate, |
| 213 | enc, isSigned, isBigEndian); |
| 214 | DAUDIO_AddAudioFormat(creator, significantBits, |
| 215 | sampleSizeInBytes * maxChannels, |
| 216 | maxChannels, rate, |
| 217 | enc, isSigned, isBigEndian); |
| 218 | } else { |
| 219 | for (channels = minChannels; channels <= maxChannels; channels++) { |
| 220 | DAUDIO_AddAudioFormat(creator, significantBits, |
| 221 | (channels < 0)?-1:(sampleSizeInBytes * channels), |
| 222 | channels, rate, |
| 223 | enc, isSigned, isBigEndian); |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | #ifndef ALSA_PCM_USE_PLUGHW |
| 228 | // without plugin, do not add fake formats |
| 229 | break; |
| 230 | #endif |
| 231 | } while (--bitIndex > 0); |
| 232 | } else { |
| 233 | TRACE1("could not get format from alsa for format %d\n", format); |
| 234 | } |
| 235 | } else { |
| 236 | //TRACE1("Format %d not supported\n", format); |
| 237 | } |
| 238 | } // for loop |
| 239 | snd_pcm_hw_params_free(hwParams); |
| 240 | } |
| 241 | snd_pcm_format_mask_free(formatMask); |
| 242 | } |
| 243 | snd_pcm_close(handle); |
| 244 | } |
| 245 | |
| 246 | /* ******* ALSA PCM INFO ******************** */ |
| 247 | typedef struct tag_AlsaPcmInfo { |
| 248 | snd_pcm_t* handle; |
| 249 | snd_pcm_hw_params_t* hwParams; |
| 250 | snd_pcm_sw_params_t* swParams; |
| 251 | int bufferSizeInBytes; |
| 252 | int frameSize; // storage size in Bytes |
| 253 | int periods; |
| 254 | snd_pcm_uframes_t periodSize; |
| 255 | #ifdef GET_POSITION_METHOD2 |
| 256 | // to be used exclusively by getBytePosition! |
| 257 | snd_pcm_status_t* positionStatus; |
| 258 | #endif |
| 259 | } AlsaPcmInfo; |
| 260 | |
| 261 | |
| 262 | int setStartThresholdNoCommit(AlsaPcmInfo* info, int useThreshold) { |
| 263 | int ret; |
| 264 | int threshold; |
| 265 | |
| 266 | if (useThreshold) { |
| 267 | // start device whenever anything is written to the buffer |
| 268 | threshold = 1; |
| 269 | } else { |
| 270 | // never start the device automatically |
| 271 | threshold = 2000000000; /* near UINT_MAX */ |
| 272 | } |
| 273 | ret = snd_pcm_sw_params_set_start_threshold(info->handle, info->swParams, threshold); |
| 274 | if (ret < 0) { |
| 275 | ERROR1("Unable to set start threshold mode: %s\n", snd_strerror(ret)); |
| 276 | return FALSE; |
| 277 | } |
| 278 | return TRUE; |
| 279 | } |
| 280 | |
| 281 | int setStartThreshold(AlsaPcmInfo* info, int useThreshold) { |
| 282 | int ret = 0; |
| 283 | |
| 284 | if (!setStartThresholdNoCommit(info, useThreshold)) { |
| 285 | ret = -1; |
| 286 | } |
| 287 | if (ret == 0) { |
| 288 | // commit it |
| 289 | ret = snd_pcm_sw_params(info->handle, info->swParams); |
| 290 | if (ret < 0) { |
| 291 | ERROR1("Unable to set sw params: %s\n", snd_strerror(ret)); |
| 292 | } |
| 293 | } |
| 294 | return (ret == 0)?TRUE:FALSE; |
| 295 | } |
| 296 | |
| 297 | |
| 298 | // returns TRUE if successful |
| 299 | int setHWParams(AlsaPcmInfo* info, |
| 300 | float sampleRate, |
| 301 | int channels, |
| 302 | int bufferSizeInFrames, |
| 303 | snd_pcm_format_t format) { |
| 304 | unsigned int rrate; |
| 305 | int ret, dir, periods, periodTime; |
| 306 | snd_pcm_uframes_t alsaBufferSizeInFrames = (snd_pcm_uframes_t) bufferSizeInFrames; |
| 307 | |
| 308 | /* choose all parameters */ |
| 309 | ret = snd_pcm_hw_params_any(info->handle, info->hwParams); |
| 310 | if (ret < 0) { |
| 311 | ERROR1("Broken configuration: no configurations available: %s\n", snd_strerror(ret)); |
| 312 | return FALSE; |
| 313 | } |
| 314 | /* set the interleaved read/write format */ |
| 315 | ret = snd_pcm_hw_params_set_access(info->handle, info->hwParams, SND_PCM_ACCESS_RW_INTERLEAVED); |
| 316 | if (ret < 0) { |
| 317 | ERROR1("SND_PCM_ACCESS_RW_INTERLEAVED access type not available: %s\n", snd_strerror(ret)); |
| 318 | return FALSE; |
| 319 | } |
| 320 | /* set the sample format */ |
| 321 | ret = snd_pcm_hw_params_set_format(info->handle, info->hwParams, format); |
| 322 | if (ret < 0) { |
| 323 | ERROR1("Sample format not available: %s\n", snd_strerror(ret)); |
| 324 | return FALSE; |
| 325 | } |
| 326 | /* set the count of channels */ |
| 327 | ret = snd_pcm_hw_params_set_channels(info->handle, info->hwParams, channels); |
| 328 | if (ret < 0) { |
| 329 | ERROR2("Channels count (%d) not available: %s\n", channels, snd_strerror(ret)); |
| 330 | return FALSE; |
| 331 | } |
| 332 | /* set the stream rate */ |
| 333 | rrate = (int) (sampleRate + 0.5f); |
| 334 | #ifdef ALSA_PCM_NEW_HW_PARAMS_API |
| 335 | dir = 0; |
| 336 | ret = snd_pcm_hw_params_set_rate_near(info->handle, info->hwParams, &rrate, &dir); |
| 337 | #else |
| 338 | ret = snd_pcm_hw_params_set_rate_near(info->handle, info->hwParams, rrate, 0); |
| 339 | #endif |
| 340 | if (ret < 0) { |
| 341 | ERROR2("Rate %dHz not available for playback: %s\n", (int) (sampleRate+0.5f), snd_strerror(ret)); |
| 342 | return FALSE; |
| 343 | } |
| 344 | if ((rrate-sampleRate > 2) || (rrate-sampleRate < - 2)) { |
| 345 | ERROR2("Rate doesn't match (requested %2.2fHz, got %dHz)\n", sampleRate, rrate); |
| 346 | return FALSE; |
| 347 | } |
| 348 | /* set the buffer time */ |
| 349 | #ifdef ALSA_PCM_NEW_HW_PARAMS_API |
| 350 | |
| 351 | ret = snd_pcm_hw_params_set_buffer_size_near(info->handle, info->hwParams, &alsaBufferSizeInFrames); |
| 352 | #else |
| 353 | ret = snd_pcm_hw_params_set_buffer_size_near(info->handle, info->hwParams, alsaBufferSizeInFrames); |
| 354 | #endif |
| 355 | if (ret < 0) { |
| 356 | ERROR2("Unable to set buffer size to %d frames: %s\n", |
| 357 | (int) alsaBufferSizeInFrames, snd_strerror(ret)); |
| 358 | return FALSE; |
| 359 | } |
| 360 | bufferSizeInFrames = (int) alsaBufferSizeInFrames; |
| 361 | /* set the period time */ |
| 362 | if (bufferSizeInFrames > 1024) { |
| 363 | dir = 0; |
| 364 | periodTime = DEFAULT_PERIOD_TIME; |
| 365 | #ifdef ALSA_PCM_NEW_HW_PARAMS_API |
| 366 | ret = snd_pcm_hw_params_set_period_time_near(info->handle, info->hwParams, &periodTime, &dir); |
| 367 | #else |
| 368 | periodTime = snd_pcm_hw_params_set_period_time_near(info->handle, info->hwParams, periodTime, &dir); |
| 369 | ret = periodTime; |
| 370 | #endif |
| 371 | if (ret < 0) { |
| 372 | ERROR2("Unable to set period time to %d: %s\n", DEFAULT_PERIOD_TIME, snd_strerror(ret)); |
| 373 | return FALSE; |
| 374 | } |
| 375 | } else { |
| 376 | /* set the period count for very small buffer sizes to 2 */ |
| 377 | dir = 0; |
| 378 | periods = 2; |
| 379 | #ifdef ALSA_PCM_NEW_HW_PARAMS_API |
| 380 | ret = snd_pcm_hw_params_set_periods_near(info->handle, info->hwParams, &periods, &dir); |
| 381 | #else |
| 382 | periods = snd_pcm_hw_params_set_periods_near(info->handle, info->hwParams, periods, &dir); |
| 383 | ret = periods; |
| 384 | #endif |
| 385 | if (ret < 0) { |
| 386 | ERROR2("Unable to set period count to %d: %s\n", /*periods*/ 2, snd_strerror(ret)); |
| 387 | return FALSE; |
| 388 | } |
| 389 | } |
| 390 | /* write the parameters to device */ |
| 391 | ret = snd_pcm_hw_params(info->handle, info->hwParams); |
| 392 | if (ret < 0) { |
| 393 | ERROR1("Unable to set hw params: %s\n", snd_strerror(ret)); |
| 394 | return FALSE; |
| 395 | } |
| 396 | return TRUE; |
| 397 | } |
| 398 | |
| 399 | // returns 1 if successful |
| 400 | int setSWParams(AlsaPcmInfo* info) { |
| 401 | int ret; |
| 402 | |
| 403 | /* get the current swparams */ |
| 404 | ret = snd_pcm_sw_params_current(info->handle, info->swParams); |
| 405 | if (ret < 0) { |
| 406 | ERROR1("Unable to determine current swparams: %s\n", snd_strerror(ret)); |
| 407 | return FALSE; |
| 408 | } |
| 409 | /* never start the transfer automatically */ |
| 410 | if (!setStartThresholdNoCommit(info, FALSE /* don't use threshold */)) { |
| 411 | return FALSE; |
| 412 | } |
| 413 | |
| 414 | /* allow the transfer when at least period_size samples can be processed */ |
| 415 | ret = snd_pcm_sw_params_set_avail_min(info->handle, info->swParams, info->periodSize); |
| 416 | if (ret < 0) { |
| 417 | ERROR1("Unable to set avail min for playback: %s\n", snd_strerror(ret)); |
| 418 | return FALSE; |
| 419 | } |
| 420 | /* align all transfers to 1 sample */ |
| 421 | ret = snd_pcm_sw_params_set_xfer_align(info->handle, info->swParams, 1); |
| 422 | if (ret < 0) { |
| 423 | ERROR1("Unable to set transfer align: %s\n", snd_strerror(ret)); |
| 424 | return FALSE; |
| 425 | } |
| 426 | /* write the parameters to the playback device */ |
| 427 | ret = snd_pcm_sw_params(info->handle, info->swParams); |
| 428 | if (ret < 0) { |
| 429 | ERROR1("Unable to set sw params: %s\n", snd_strerror(ret)); |
| 430 | return FALSE; |
| 431 | } |
| 432 | return TRUE; |
| 433 | } |
| 434 | |
| 435 | static snd_output_t* ALSA_OUTPUT = NULL; |
| 436 | |
| 437 | void* DAUDIO_Open(INT32 mixerIndex, INT32 deviceID, int isSource, |
| 438 | int encoding, float sampleRate, int sampleSizeInBits, |
| 439 | int frameSize, int channels, |
| 440 | int isSigned, int isBigEndian, int bufferSizeInBytes) { |
| 441 | snd_pcm_format_mask_t* formatMask; |
| 442 | snd_pcm_format_t format; |
| 443 | int dir; |
| 444 | int ret = 0; |
| 445 | AlsaPcmInfo* info = NULL; |
| 446 | /* snd_pcm_uframes_t is 64 bit on 64-bit systems */ |
| 447 | snd_pcm_uframes_t alsaPeriodSize = 0; |
| 448 | snd_pcm_uframes_t alsaBufferSizeInFrames = 0; |
| 449 | |
| 450 | |
| 451 | TRACE0("> DAUDIO_Open\n"); |
| 452 | #ifdef USE_TRACE |
| 453 | // for using ALSA debug dump methods |
| 454 | if (ALSA_OUTPUT == NULL) { |
| 455 | snd_output_stdio_attach(&ALSA_OUTPUT, stdout, 0); |
| 456 | } |
| 457 | #endif |
| 458 | |
| 459 | info = (AlsaPcmInfo*) malloc(sizeof(AlsaPcmInfo)); |
| 460 | if (!info) { |
| 461 | ERROR0("Out of memory\n"); |
| 462 | return NULL; |
| 463 | } |
| 464 | memset(info, 0, sizeof(AlsaPcmInfo)); |
| 465 | |
| 466 | ret = openPCMfromDeviceID(deviceID, &(info->handle), isSource, FALSE /* do open device*/); |
| 467 | if (ret == 0) { |
| 468 | // set to blocking mode |
| 469 | snd_pcm_nonblock(info->handle, 0); |
| 470 | ret = snd_pcm_hw_params_malloc(&(info->hwParams)); |
| 471 | if (ret != 0) { |
| 472 | ERROR1(" snd_pcm_hw_params_malloc returned error %d\n", ret); |
| 473 | } else { |
| 474 | ret = -1; |
| 475 | if (getAlsaFormatFromFormat(&format, frameSize / channels, sampleSizeInBits, |
| 476 | isSigned, isBigEndian, encoding)) { |
| 477 | if (setHWParams(info, |
| 478 | sampleRate, |
| 479 | channels, |
| 480 | bufferSizeInBytes / frameSize, |
| 481 | format)) { |
| 482 | info->frameSize = frameSize; |
| 483 | #ifdef ALSA_PCM_NEW_HW_PARAMS_API |
| 484 | ret = snd_pcm_hw_params_get_period_size(info->hwParams, &alsaPeriodSize, &dir); |
| 485 | info->periodSize = (int) alsaPeriodSize; |
| 486 | if (ret < 0) { |
| 487 | ERROR1("ERROR: snd_pcm_hw_params_get_period: %s\n", snd_strerror(ret)); |
| 488 | } |
| 489 | snd_pcm_hw_params_get_periods(info->hwParams, &(info->periods), &dir); |
| 490 | snd_pcm_hw_params_get_buffer_size(info->hwParams, &alsaBufferSizeInFrames); |
| 491 | info->bufferSizeInBytes = (int) alsaBufferSizeInFrames * frameSize; |
| 492 | #else |
| 493 | info->periodSize = snd_pcm_hw_params_get_period_size(info->hwParams, &dir); |
| 494 | info->periods = snd_pcm_hw_params_get_periods(info->hwParams, &dir); |
| 495 | info->bufferSizeInBytes = snd_pcm_hw_params_get_buffer_size(info->hwParams) * frameSize; |
| 496 | ret = 0; |
| 497 | #endif |
| 498 | TRACE3(" DAUDIO_Open: period size = %d frames, periods = %d. Buffer size: %d bytes.\n", |
| 499 | (int) info->periodSize, info->periods, info->bufferSizeInBytes); |
| 500 | } |
| 501 | } |
| 502 | } |
| 503 | if (ret == 0) { |
| 504 | // set software parameters |
| 505 | ret = snd_pcm_sw_params_malloc(&(info->swParams)); |
| 506 | if (ret != 0) { |
| 507 | ERROR1("snd_pcm_hw_params_malloc returned error %d\n", ret); |
| 508 | } else { |
| 509 | if (!setSWParams(info)) { |
| 510 | ret = -1; |
| 511 | } |
| 512 | } |
| 513 | } |
| 514 | if (ret == 0) { |
| 515 | // prepare device |
| 516 | ret = snd_pcm_prepare(info->handle); |
| 517 | if (ret < 0) { |
| 518 | ERROR1("ERROR: snd_pcm_prepare: %s\n", snd_strerror(ret)); |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | #ifdef GET_POSITION_METHOD2 |
| 523 | if (ret == 0) { |
| 524 | ret = snd_pcm_status_malloc(&(info->positionStatus)); |
| 525 | if (ret != 0) { |
| 526 | ERROR1("ERROR in snd_pcm_status_malloc: %s\n", snd_strerror(ret)); |
| 527 | } |
| 528 | } |
| 529 | #endif |
| 530 | } |
| 531 | if (ret != 0) { |
| 532 | DAUDIO_Close((void*) info, isSource); |
| 533 | info = NULL; |
| 534 | } else { |
| 535 | // set to non-blocking mode |
| 536 | snd_pcm_nonblock(info->handle, 1); |
| 537 | TRACE1("< DAUDIO_Open: Opened device successfully. Handle=%p\n", |
| 538 | (void*) info->handle); |
| 539 | } |
| 540 | return (void*) info; |
| 541 | } |
| 542 | |
| 543 | #ifdef USE_TRACE |
| 544 | void printState(snd_pcm_state_t state) { |
| 545 | if (state == SND_PCM_STATE_OPEN) { |
| 546 | TRACE0("State: SND_PCM_STATE_OPEN\n"); |
| 547 | } |
| 548 | else if (state == SND_PCM_STATE_SETUP) { |
| 549 | TRACE0("State: SND_PCM_STATE_SETUP\n"); |
| 550 | } |
| 551 | else if (state == SND_PCM_STATE_PREPARED) { |
| 552 | TRACE0("State: SND_PCM_STATE_PREPARED\n"); |
| 553 | } |
| 554 | else if (state == SND_PCM_STATE_RUNNING) { |
| 555 | TRACE0("State: SND_PCM_STATE_RUNNING\n"); |
| 556 | } |
| 557 | else if (state == SND_PCM_STATE_XRUN) { |
| 558 | TRACE0("State: SND_PCM_STATE_XRUN\n"); |
| 559 | } |
| 560 | else if (state == SND_PCM_STATE_DRAINING) { |
| 561 | TRACE0("State: SND_PCM_STATE_DRAINING\n"); |
| 562 | } |
| 563 | else if (state == SND_PCM_STATE_PAUSED) { |
| 564 | TRACE0("State: SND_PCM_STATE_PAUSED\n"); |
| 565 | } |
| 566 | else if (state == SND_PCM_STATE_SUSPENDED) { |
| 567 | TRACE0("State: SND_PCM_STATE_SUSPENDED\n"); |
| 568 | } |
| 569 | } |
| 570 | #endif |
| 571 | |
| 572 | int DAUDIO_Start(void* id, int isSource) { |
| 573 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 574 | int ret; |
| 575 | snd_pcm_state_t state; |
| 576 | |
| 577 | TRACE0("> DAUDIO_Start\n"); |
| 578 | // set to blocking mode |
| 579 | snd_pcm_nonblock(info->handle, 0); |
| 580 | // set start mode so that it always starts as soon as data is there |
| 581 | setStartThreshold(info, TRUE /* use threshold */); |
| 582 | state = snd_pcm_state(info->handle); |
| 583 | if (state == SND_PCM_STATE_PAUSED) { |
| 584 | // in case it was stopped previously |
| 585 | TRACE0(" Un-pausing...\n"); |
| 586 | ret = snd_pcm_pause(info->handle, FALSE); |
| 587 | if (ret != 0) { |
| 588 | ERROR2(" NOTE: error in snd_pcm_pause:%d: %s\n", ret, snd_strerror(ret)); |
| 589 | } |
| 590 | } |
| 591 | if (state == SND_PCM_STATE_SUSPENDED) { |
| 592 | TRACE0(" Resuming...\n"); |
| 593 | ret = snd_pcm_resume(info->handle); |
| 594 | if (ret < 0) { |
| 595 | if ((ret != -EAGAIN) && (ret != -ENOSYS)) { |
| 596 | ERROR2(" ERROR: error in snd_pcm_resume:%d: %s\n", ret, snd_strerror(ret)); |
| 597 | } |
| 598 | } |
| 599 | } |
| 600 | if (state == SND_PCM_STATE_SETUP) { |
| 601 | TRACE0("need to call prepare again...\n"); |
| 602 | // prepare device |
| 603 | ret = snd_pcm_prepare(info->handle); |
| 604 | if (ret < 0) { |
| 605 | ERROR1("ERROR: snd_pcm_prepare: %s\n", snd_strerror(ret)); |
| 606 | } |
| 607 | } |
| 608 | // in case there is still data in the buffers |
| 609 | ret = snd_pcm_start(info->handle); |
| 610 | if (ret != 0) { |
| 611 | if (ret != -EPIPE) { |
| 612 | ERROR2(" NOTE: error in snd_pcm_start: %d: %s\n", ret, snd_strerror(ret)); |
| 613 | } |
| 614 | } |
| 615 | // set to non-blocking mode |
| 616 | ret = snd_pcm_nonblock(info->handle, 1); |
| 617 | if (ret != 0) { |
| 618 | ERROR1(" ERROR in snd_pcm_nonblock: %s\n", snd_strerror(ret)); |
| 619 | } |
| 620 | state = snd_pcm_state(info->handle); |
| 621 | #ifdef USE_TRACE |
| 622 | printState(state); |
| 623 | #endif |
| 624 | ret = (state == SND_PCM_STATE_PREPARED) |
| 625 | || (state == SND_PCM_STATE_RUNNING) |
| 626 | || (state == SND_PCM_STATE_XRUN) |
| 627 | || (state == SND_PCM_STATE_SUSPENDED); |
| 628 | TRACE1("< DAUDIO_Start %s\n", ret?"success":"error"); |
| 629 | return ret?TRUE:FALSE; |
| 630 | } |
| 631 | |
| 632 | int DAUDIO_Stop(void* id, int isSource) { |
| 633 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 634 | int ret; |
| 635 | |
| 636 | TRACE0("> DAUDIO_Stop\n"); |
| 637 | // set to blocking mode |
| 638 | snd_pcm_nonblock(info->handle, 0); |
| 639 | setStartThreshold(info, FALSE /* don't use threshold */); // device will not start after buffer xrun |
| 640 | ret = snd_pcm_pause(info->handle, 1); |
| 641 | // set to non-blocking mode |
| 642 | snd_pcm_nonblock(info->handle, 1); |
| 643 | if (ret != 0) { |
| 644 | ERROR1("ERROR in snd_pcm_pause: %s\n", snd_strerror(ret)); |
| 645 | return FALSE; |
| 646 | } |
| 647 | TRACE0("< DAUDIO_Stop success\n"); |
| 648 | return TRUE; |
| 649 | } |
| 650 | |
| 651 | void DAUDIO_Close(void* id, int isSource) { |
| 652 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 653 | |
| 654 | TRACE0("DAUDIO_Close\n"); |
| 655 | if (info != NULL) { |
| 656 | if (info->handle != NULL) { |
| 657 | snd_pcm_close(info->handle); |
| 658 | } |
| 659 | if (info->hwParams) { |
| 660 | snd_pcm_hw_params_free(info->hwParams); |
| 661 | } |
| 662 | if (info->swParams) { |
| 663 | snd_pcm_sw_params_free(info->swParams); |
| 664 | } |
| 665 | #ifdef GET_POSITION_METHOD2 |
| 666 | if (info->positionStatus) { |
| 667 | snd_pcm_status_free(info->positionStatus); |
| 668 | } |
| 669 | #endif |
| 670 | free(info); |
| 671 | } |
| 672 | } |
| 673 | |
| 674 | /* |
| 675 | * Underrun and suspend recovery |
| 676 | * returns |
| 677 | * 0: exit native and return 0 |
| 678 | * 1: try again to write/read |
| 679 | * -1: error - exit native with return value -1 |
| 680 | */ |
| 681 | int xrun_recovery(AlsaPcmInfo* info, int err) { |
| 682 | int ret; |
| 683 | |
| 684 | if (err == -EPIPE) { /* underrun / overflow */ |
| 685 | TRACE0("xrun_recovery: underrun/overflow.\n"); |
| 686 | ret = snd_pcm_prepare(info->handle); |
| 687 | if (ret < 0) { |
| 688 | ERROR1("Can't recover from underrun/overflow, prepare failed: %s\n", snd_strerror(ret)); |
| 689 | return -1; |
| 690 | } |
| 691 | return 1; |
| 692 | } |
| 693 | else if (err == -ESTRPIPE) { |
| 694 | TRACE0("xrun_recovery: suspended.\n"); |
| 695 | ret = snd_pcm_resume(info->handle); |
| 696 | if (ret < 0) { |
| 697 | if (ret == -EAGAIN) { |
| 698 | return 0; /* wait until the suspend flag is released */ |
| 699 | } |
| 700 | return -1; |
| 701 | } |
| 702 | ret = snd_pcm_prepare(info->handle); |
| 703 | if (ret < 0) { |
| 704 | ERROR1("Can't recover from underrun/overflow, prepare failed: %s\n", snd_strerror(ret)); |
| 705 | return -1; |
| 706 | } |
| 707 | return 1; |
| 708 | } |
| 709 | else if (err == -EAGAIN) { |
| 710 | TRACE0("xrun_recovery: EAGAIN try again flag.\n"); |
| 711 | return 0; |
| 712 | } |
| 713 | TRACE2("xrun_recovery: unexpected error %d: %s\n", err, snd_strerror(err)); |
| 714 | return -1; |
| 715 | } |
| 716 | |
| 717 | // returns -1 on error |
| 718 | int DAUDIO_Write(void* id, char* data, int byteSize) { |
| 719 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 720 | int ret, count; |
| 721 | snd_pcm_sframes_t frameSize, writtenFrames; |
| 722 | |
| 723 | TRACE1("> DAUDIO_Write %d bytes\n", byteSize); |
| 724 | |
| 725 | /* sanity */ |
| 726 | if (byteSize <= 0 || info->frameSize <= 0) { |
| 727 | ERROR2(" DAUDIO_Write: byteSize=%d, frameSize=%d!\n", |
| 728 | (int) byteSize, (int) info->frameSize); |
| 729 | TRACE0("< DAUDIO_Write returning -1\n"); |
| 730 | return -1; |
| 731 | } |
| 732 | count = 2; // maximum number of trials to recover from underrun |
| 733 | //frameSize = snd_pcm_bytes_to_frames(info->handle, byteSize); |
| 734 | frameSize = (snd_pcm_sframes_t) (byteSize / info->frameSize); |
| 735 | do { |
| 736 | writtenFrames = snd_pcm_writei(info->handle, (const void*) data, (snd_pcm_uframes_t) frameSize); |
| 737 | |
| 738 | if (writtenFrames < 0) { |
| 739 | ret = xrun_recovery(info, (int) writtenFrames); |
| 740 | if (ret <= 0) { |
| 741 | TRACE1("DAUDIO_Write: xrun recovery returned %d -> return.\n", ret); |
| 742 | return ret; |
| 743 | } |
| 744 | if (count-- <= 0) { |
| 745 | ERROR0("DAUDIO_Write: too many attempts to recover from xrun/suspend\n"); |
| 746 | return -1; |
| 747 | } |
| 748 | } else { |
| 749 | break; |
| 750 | } |
| 751 | } while (TRUE); |
| 752 | //ret = snd_pcm_frames_to_bytes(info->handle, writtenFrames); |
| 753 | ret = (int) (writtenFrames * info->frameSize); |
| 754 | TRACE1("< DAUDIO_Write: returning %d bytes.\n", ret); |
| 755 | return ret; |
| 756 | } |
| 757 | |
| 758 | // returns -1 on error |
| 759 | int DAUDIO_Read(void* id, char* data, int byteSize) { |
| 760 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 761 | int ret, count; |
| 762 | snd_pcm_sframes_t frameSize, readFrames; |
| 763 | |
| 764 | TRACE1("> DAUDIO_Read %d bytes\n", byteSize); |
| 765 | /*TRACE3(" info=%p, data=%p, byteSize=%d\n", |
| 766 | (void*) info, (void*) data, (int) byteSize); |
| 767 | TRACE2(" info->frameSize=%d, info->handle=%p\n", |
| 768 | (int) info->frameSize, (void*) info->handle); |
| 769 | */ |
| 770 | /* sanity */ |
| 771 | if (byteSize <= 0 || info->frameSize <= 0) { |
| 772 | ERROR2(" DAUDIO_Read: byteSize=%d, frameSize=%d!\n", |
| 773 | (int) byteSize, (int) info->frameSize); |
| 774 | TRACE0("< DAUDIO_Read returning -1\n"); |
| 775 | return -1; |
| 776 | } |
| 777 | count = 2; // maximum number of trials to recover from error |
| 778 | //frameSize = snd_pcm_bytes_to_frames(info->handle, byteSize); |
| 779 | frameSize = (snd_pcm_sframes_t) (byteSize / info->frameSize); |
| 780 | do { |
| 781 | readFrames = snd_pcm_readi(info->handle, (void*) data, (snd_pcm_uframes_t) frameSize); |
| 782 | if (readFrames < 0) { |
| 783 | ret = xrun_recovery(info, (int) readFrames); |
| 784 | if (ret <= 0) { |
| 785 | TRACE1("DAUDIO_Read: xrun recovery returned %d -> return.\n", ret); |
| 786 | return ret; |
| 787 | } |
| 788 | if (count-- <= 0) { |
| 789 | ERROR0("DAUDIO_Read: too many attempts to recover from xrun/suspend\n"); |
| 790 | return -1; |
| 791 | } |
| 792 | } else { |
| 793 | break; |
| 794 | } |
| 795 | } while (TRUE); |
| 796 | //ret = snd_pcm_frames_to_bytes(info->handle, readFrames); |
| 797 | ret = (int) (readFrames * info->frameSize); |
| 798 | TRACE1("< DAUDIO_Read: returning %d bytes.\n", ret); |
| 799 | return ret; |
| 800 | } |
| 801 | |
| 802 | |
| 803 | int DAUDIO_GetBufferSize(void* id, int isSource) { |
| 804 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 805 | |
| 806 | return info->bufferSizeInBytes; |
| 807 | } |
| 808 | |
| 809 | int DAUDIO_StillDraining(void* id, int isSource) { |
| 810 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 811 | snd_pcm_state_t state; |
| 812 | |
| 813 | state = snd_pcm_state(info->handle); |
| 814 | //printState(state); |
| 815 | //TRACE1("Still draining: %s\n", (state != SND_PCM_STATE_XRUN)?"TRUE":"FALSE"); |
| 816 | return (state == SND_PCM_STATE_RUNNING)?TRUE:FALSE; |
| 817 | } |
| 818 | |
| 819 | |
| 820 | int DAUDIO_Flush(void* id, int isSource) { |
| 821 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 822 | int ret; |
| 823 | |
| 824 | TRACE0("DAUDIO_Flush\n"); |
| 825 | ret = snd_pcm_drop(info->handle); |
| 826 | if (ret != 0) { |
| 827 | ERROR1("ERROR in snd_pcm_drop: %s\n", snd_strerror(ret)); |
| 828 | return FALSE; |
| 829 | } |
| 830 | ret = DAUDIO_Start(id, isSource); |
| 831 | return ret; |
| 832 | } |
| 833 | |
| 834 | int DAUDIO_GetAvailable(void* id, int isSource) { |
| 835 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 836 | snd_pcm_sframes_t availableInFrames; |
| 837 | snd_pcm_state_t state; |
| 838 | int ret; |
| 839 | |
| 840 | state = snd_pcm_state(info->handle); |
| 841 | if (state == SND_PCM_STATE_XRUN) { |
| 842 | // if in xrun state then we have the entire buffer available, |
| 843 | // not 0 as alsa reports |
| 844 | ret = info->bufferSizeInBytes; |
| 845 | } else { |
| 846 | availableInFrames = snd_pcm_avail_update(info->handle); |
| 847 | if (availableInFrames < 0) { |
| 848 | ret = 0; |
| 849 | } else { |
| 850 | //ret = snd_pcm_frames_to_bytes(info->handle, availableInFrames); |
| 851 | ret = (int) (availableInFrames * info->frameSize); |
| 852 | } |
| 853 | } |
| 854 | TRACE1("DAUDIO_GetAvailable returns %d bytes\n", ret); |
| 855 | return ret; |
| 856 | } |
| 857 | |
| 858 | INT64 estimatePositionFromAvail(AlsaPcmInfo* info, int isSource, INT64 javaBytePos, int availInBytes) { |
| 859 | // estimate the current position with the buffer size and |
| 860 | // the available bytes to read or write in the buffer. |
| 861 | // not an elegant solution - bytePos will stop on xruns, |
| 862 | // and in race conditions it may jump backwards |
| 863 | // Advantage is that it is indeed based on the samples that go through |
| 864 | // the system (rather than time-based methods) |
| 865 | if (isSource) { |
| 866 | // javaBytePos is the position that is reached when the current |
| 867 | // buffer is played completely |
| 868 | return (INT64) (javaBytePos - info->bufferSizeInBytes + availInBytes); |
| 869 | } else { |
| 870 | // javaBytePos is the position that was when the current buffer was empty |
| 871 | return (INT64) (javaBytePos + availInBytes); |
| 872 | } |
| 873 | } |
| 874 | |
| 875 | INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) { |
| 876 | AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| 877 | int ret; |
| 878 | INT64 result = javaBytePos; |
| 879 | snd_pcm_state_t state; |
| 880 | state = snd_pcm_state(info->handle); |
| 881 | |
| 882 | if (state != SND_PCM_STATE_XRUN) { |
| 883 | #ifdef GET_POSITION_METHOD2 |
| 884 | snd_timestamp_t* ts; |
| 885 | snd_pcm_uframes_t framesAvail; |
| 886 | |
| 887 | // note: slight race condition if this is called simultaneously from 2 threads |
| 888 | ret = snd_pcm_status(info->handle, info->positionStatus); |
| 889 | if (ret != 0) { |
| 890 | ERROR1("ERROR in snd_pcm_status: %s\n", snd_strerror(ret)); |
| 891 | result = javaBytePos; |
| 892 | } else { |
| 893 | // calculate from time value, or from available bytes |
| 894 | framesAvail = snd_pcm_status_get_avail(info->positionStatus); |
| 895 | result = estimatePositionFromAvail(info, isSource, javaBytePos, framesAvail * info->frameSize); |
| 896 | } |
| 897 | #endif |
| 898 | #ifdef GET_POSITION_METHOD3 |
| 899 | snd_pcm_uframes_t framesAvail; |
| 900 | ret = snd_pcm_avail(info->handle, &framesAvail); |
| 901 | if (ret != 0) { |
| 902 | ERROR1("ERROR in snd_pcm_avail: %s\n", snd_strerror(ret)); |
| 903 | result = javaBytePos; |
| 904 | } else { |
| 905 | result = estimatePositionFromAvail(info, isSource, javaBytePos, framesAvail * info->frameSize); |
| 906 | } |
| 907 | #endif |
| 908 | #ifdef GET_POSITION_METHOD1 |
| 909 | result = estimatePositionFromAvail(info, isSource, javaBytePos, DAUDIO_GetAvailable(id, isSource)); |
| 910 | #endif |
| 911 | } |
| 912 | //printf("getbyteposition: javaBytePos=%d , return=%d\n", (int) javaBytePos, (int) result); |
| 913 | return result; |
| 914 | } |
| 915 | |
| 916 | |
| 917 | |
| 918 | void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) { |
| 919 | /* save to ignore, since GetBytePosition |
| 920 | * takes the javaBytePos param into account |
| 921 | */ |
| 922 | } |
| 923 | |
| 924 | int DAUDIO_RequiresServicing(void* id, int isSource) { |
| 925 | // never need servicing on Linux |
| 926 | return FALSE; |
| 927 | } |
| 928 | |
| 929 | void DAUDIO_Service(void* id, int isSource) { |
| 930 | // never need servicing on Linux |
| 931 | } |
| 932 | |
| 933 | |
| 934 | #endif // USE_DAUDIO |