| /* |
| * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code 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 |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| #define USE_ERROR |
| #define USE_TRACE |
| |
| #include "PLATFORM_API_LinuxOS_ALSA_PCMUtils.h" |
| #include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h" |
| #include "DirectAudio.h" |
| |
| #if USE_DAUDIO == TRUE |
| |
| // GetPosition method 1: based on how many bytes are passed to the kernel driver |
| // + does not need much processor resources |
| // - not very exact, "jumps" |
| // GetPosition method 2: ask kernel about actual position of playback. |
| // - very exact |
| // - switch to kernel layer for each call |
| // GetPosition method 3: use snd_pcm_avail() call - not yet in official ALSA |
| // quick tests on a Pentium 200MMX showed max. 1.5% processor usage |
| // for playing back a CD-quality file and printing 20x per second a line |
| // on the console with the current time. So I guess performance is not such a |
| // factor here. |
| //#define GET_POSITION_METHOD1 |
| #define GET_POSITION_METHOD2 |
| |
| |
| // The default time for a period in microseconds. |
| // For very small buffers, only 2 periods are used. |
| #define DEFAULT_PERIOD_TIME 20000 /* 20ms */ |
| |
| ///// implemented functions of DirectAudio.h |
| |
| INT32 DAUDIO_GetDirectAudioDeviceCount() { |
| return (INT32) getAudioDeviceCount(); |
| } |
| |
| |
| INT32 DAUDIO_GetDirectAudioDeviceDescription(INT32 mixerIndex, DirectAudioDeviceDescription* description) { |
| ALSA_AudioDeviceDescription adesc; |
| |
| adesc.index = (int) mixerIndex; |
| adesc.strLen = DAUDIO_STRING_LENGTH; |
| |
| adesc.maxSimultaneousLines = (int*) (&(description->maxSimulLines)); |
| adesc.deviceID = &(description->deviceID); |
| adesc.name = description->name; |
| adesc.vendor = description->vendor; |
| adesc.description = description->description; |
| adesc.version = description->version; |
| |
| return getAudioDeviceDescriptionByIndex(&adesc); |
| } |
| |
| #define MAX_BIT_INDEX 6 |
| // returns |
| // 6: for anything above 24-bit |
| // 5: for 4 bytes sample size, 24-bit |
| // 4: for 3 bytes sample size, 24-bit |
| // 3: for 3 bytes sample size, 20-bit |
| // 2: for 2 bytes sample size, 16-bit |
| // 1: for 1 byte sample size, 8-bit |
| // 0: for anything else |
| int getBitIndex(int sampleSizeInBytes, int significantBits) { |
| if (significantBits > 24) return 6; |
| if (sampleSizeInBytes == 4 && significantBits == 24) return 5; |
| if (sampleSizeInBytes == 3) { |
| if (significantBits == 24) return 4; |
| if (significantBits == 20) return 3; |
| } |
| if (sampleSizeInBytes == 2 && significantBits == 16) return 2; |
| if (sampleSizeInBytes == 1 && significantBits == 8) return 1; |
| return 0; |
| } |
| |
| int getSampleSizeInBytes(int bitIndex, int sampleSizeInBytes) { |
| switch(bitIndex) { |
| case 1: return 1; |
| case 2: return 2; |
| case 3: /* fall through */ |
| case 4: return 3; |
| case 5: return 4; |
| } |
| return sampleSizeInBytes; |
| } |
| |
| int getSignificantBits(int bitIndex, int significantBits) { |
| switch(bitIndex) { |
| case 1: return 8; |
| case 2: return 16; |
| case 3: return 20; |
| case 4: /* fall through */ |
| case 5: return 24; |
| } |
| return significantBits; |
| } |
| |
| void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* creator) { |
| snd_pcm_t* handle; |
| snd_pcm_format_mask_t* formatMask; |
| snd_pcm_format_t format; |
| snd_pcm_hw_params_t* hwParams; |
| int handledBits[MAX_BIT_INDEX+1]; |
| |
| int ret; |
| int sampleSizeInBytes, significantBits, isSigned, isBigEndian, enc; |
| int origSampleSizeInBytes, origSignificantBits; |
| unsigned int channels, minChannels, maxChannels; |
| int rate, bitIndex; |
| |
| for (bitIndex = 0; bitIndex <= MAX_BIT_INDEX; bitIndex++) handledBits[bitIndex] = FALSE; |
| if (openPCMfromDeviceID(deviceID, &handle, isSource, TRUE /*query hardware*/) < 0) { |
| return; |
| } |
| ret = snd_pcm_format_mask_malloc(&formatMask); |
| if (ret != 0) { |
| ERROR1("snd_pcm_format_mask_malloc returned error %d\n", ret); |
| } else { |
| ret = snd_pcm_hw_params_malloc(&hwParams); |
| if (ret != 0) { |
| ERROR1("snd_pcm_hw_params_malloc returned error %d\n", ret); |
| } else { |
| ret = snd_pcm_hw_params_any(handle, hwParams); |
| /* snd_pcm_hw_params_any can return a positive value on success too */ |
| if (ret < 0) { |
| ERROR1("snd_pcm_hw_params_any returned error %d\n", ret); |
| } else { |
| /* for the logic following this code, set ret to 0 to indicate success */ |
| ret = 0; |
| } |
| } |
| snd_pcm_hw_params_get_format_mask(hwParams, formatMask); |
| if (ret == 0) { |
| ret = snd_pcm_hw_params_get_channels_min(hwParams, &minChannels); |
| if (ret != 0) { |
| ERROR1("snd_pcm_hw_params_get_channels_min returned error %d\n", ret); |
| } |
| } |
| if (ret == 0) { |
| ret = snd_pcm_hw_params_get_channels_max(hwParams, &maxChannels); |
| if (ret != 0) { |
| ERROR1("snd_pcm_hw_params_get_channels_max returned error %d\n", ret); |
| } |
| } |
| |
| // since we queried the hw: device, for many soundcards, it will only |
| // report the maximum number of channels (which is the only way to talk |
| // to the hw: device). Since we will, however, open the plughw: device |
| // when opening the Source/TargetDataLine, we can safely assume that |
| // also the channels 1..maxChannels are available. |
| #ifdef ALSA_PCM_USE_PLUGHW |
| minChannels = 1; |
| #endif |
| if (ret == 0) { |
| // plughw: supports any sample rate |
| rate = -1; |
| for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) { |
| if (snd_pcm_format_mask_test(formatMask, format)) { |
| // format exists |
| if (getFormatFromAlsaFormat(format, &origSampleSizeInBytes, |
| &origSignificantBits, |
| &isSigned, &isBigEndian, &enc)) { |
| // now if we use plughw:, we can use any bit size below the |
| // natively supported ones. Some ALSA drivers only support the maximum |
| // bit size, so we add any sample rates below the reported one. |
| // E.g. this iteration reports support for 16-bit. |
| // getBitIndex will return 2, so it will add entries for |
| // 16-bit (bitIndex=2) and in the next do-while loop iteration, |
| // it will decrease bitIndex and will therefore add 8-bit support. |
| bitIndex = getBitIndex(origSampleSizeInBytes, origSignificantBits); |
| do { |
| if (bitIndex == 0 |
| || bitIndex == MAX_BIT_INDEX |
| || !handledBits[bitIndex]) { |
| handledBits[bitIndex] = TRUE; |
| sampleSizeInBytes = getSampleSizeInBytes(bitIndex, origSampleSizeInBytes); |
| significantBits = getSignificantBits(bitIndex, origSignificantBits); |
| if (maxChannels - minChannels > MAXIMUM_LISTED_CHANNELS) { |
| // avoid too many channels explicitly listed |
| // just add -1, min, and max |
| DAUDIO_AddAudioFormat(creator, significantBits, |
| -1, -1, rate, |
| enc, isSigned, isBigEndian); |
| DAUDIO_AddAudioFormat(creator, significantBits, |
| sampleSizeInBytes * minChannels, |
| minChannels, rate, |
| enc, isSigned, isBigEndian); |
| DAUDIO_AddAudioFormat(creator, significantBits, |
| sampleSizeInBytes * maxChannels, |
| maxChannels, rate, |
| enc, isSigned, isBigEndian); |
| } else { |
| for (channels = minChannels; channels <= maxChannels; channels++) { |
| DAUDIO_AddAudioFormat(creator, significantBits, |
| sampleSizeInBytes * channels, |
| channels, rate, |
| enc, isSigned, isBigEndian); |
| } |
| } |
| } |
| #ifndef ALSA_PCM_USE_PLUGHW |
| // without plugin, do not add fake formats |
| break; |
| #endif |
| } while (--bitIndex > 0); |
| } else { |
| TRACE1("could not get format from alsa for format %d\n", format); |
| } |
| } else { |
| //TRACE1("Format %d not supported\n", format); |
| } |
| } // for loop |
| snd_pcm_hw_params_free(hwParams); |
| } |
| snd_pcm_format_mask_free(formatMask); |
| } |
| snd_pcm_close(handle); |
| } |
| |
| /** Workaround for cr 7033899, 7030629: |
| * dmix plugin doesn't like flush (snd_pcm_drop) when the buffer is empty |
| * (just opened, underruned or already flushed). |
| * Sometimes it causes PCM falls to -EBADFD error, |
| * sometimes causes bufferSize change. |
| * To prevent unnecessary flushes AlsaPcmInfo::isRunning & isFlushed are used. |
| */ |
| /* ******* ALSA PCM INFO ******************** */ |
| typedef struct tag_AlsaPcmInfo { |
| snd_pcm_t* handle; |
| snd_pcm_hw_params_t* hwParams; |
| snd_pcm_sw_params_t* swParams; |
| int bufferSizeInBytes; |
| int frameSize; // storage size in Bytes |
| unsigned int periods; |
| snd_pcm_uframes_t periodSize; |
| short int isRunning; // see comment above |
| short int isFlushed; // see comment above |
| #ifdef GET_POSITION_METHOD2 |
| // to be used exclusively by getBytePosition! |
| snd_pcm_status_t* positionStatus; |
| #endif |
| } AlsaPcmInfo; |
| |
| |
| int setStartThresholdNoCommit(AlsaPcmInfo* info, int useThreshold) { |
| int ret; |
| int threshold; |
| |
| if (useThreshold) { |
| // start device whenever anything is written to the buffer |
| threshold = 1; |
| } else { |
| // never start the device automatically |
| threshold = 2000000000; /* near UINT_MAX */ |
| } |
| ret = snd_pcm_sw_params_set_start_threshold(info->handle, info->swParams, threshold); |
| if (ret < 0) { |
| ERROR1("Unable to set start threshold mode: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| int setStartThreshold(AlsaPcmInfo* info, int useThreshold) { |
| int ret = 0; |
| |
| if (!setStartThresholdNoCommit(info, useThreshold)) { |
| ret = -1; |
| } |
| if (ret == 0) { |
| // commit it |
| ret = snd_pcm_sw_params(info->handle, info->swParams); |
| if (ret < 0) { |
| ERROR1("Unable to set sw params: %s\n", snd_strerror(ret)); |
| } |
| } |
| return (ret == 0)?TRUE:FALSE; |
| } |
| |
| |
| // returns TRUE if successful |
| int setHWParams(AlsaPcmInfo* info, |
| float sampleRate, |
| int channels, |
| int bufferSizeInFrames, |
| snd_pcm_format_t format) { |
| unsigned int rrate, periodTime, periods; |
| int ret, dir; |
| snd_pcm_uframes_t alsaBufferSizeInFrames = (snd_pcm_uframes_t) bufferSizeInFrames; |
| |
| /* choose all parameters */ |
| ret = snd_pcm_hw_params_any(info->handle, info->hwParams); |
| if (ret < 0) { |
| ERROR1("Broken configuration: no configurations available: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| /* set the interleaved read/write format */ |
| ret = snd_pcm_hw_params_set_access(info->handle, info->hwParams, SND_PCM_ACCESS_RW_INTERLEAVED); |
| if (ret < 0) { |
| ERROR1("SND_PCM_ACCESS_RW_INTERLEAVED access type not available: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| /* set the sample format */ |
| ret = snd_pcm_hw_params_set_format(info->handle, info->hwParams, format); |
| if (ret < 0) { |
| ERROR1("Sample format not available: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| /* set the count of channels */ |
| ret = snd_pcm_hw_params_set_channels(info->handle, info->hwParams, channels); |
| if (ret < 0) { |
| ERROR2("Channels count (%d) not available: %s\n", channels, snd_strerror(ret)); |
| return FALSE; |
| } |
| /* set the stream rate */ |
| rrate = (int) (sampleRate + 0.5f); |
| dir = 0; |
| ret = snd_pcm_hw_params_set_rate_near(info->handle, info->hwParams, &rrate, &dir); |
| if (ret < 0) { |
| ERROR2("Rate %dHz not available for playback: %s\n", (int) (sampleRate+0.5f), snd_strerror(ret)); |
| return FALSE; |
| } |
| if ((rrate-sampleRate > 2) || (rrate-sampleRate < - 2)) { |
| ERROR2("Rate doesn't match (requested %2.2fHz, got %dHz)\n", sampleRate, rrate); |
| return FALSE; |
| } |
| /* set the buffer time */ |
| ret = snd_pcm_hw_params_set_buffer_size_near(info->handle, info->hwParams, &alsaBufferSizeInFrames); |
| if (ret < 0) { |
| ERROR2("Unable to set buffer size to %d frames: %s\n", |
| (int) alsaBufferSizeInFrames, snd_strerror(ret)); |
| return FALSE; |
| } |
| bufferSizeInFrames = (int) alsaBufferSizeInFrames; |
| /* set the period time */ |
| if (bufferSizeInFrames > 1024) { |
| dir = 0; |
| periodTime = DEFAULT_PERIOD_TIME; |
| ret = snd_pcm_hw_params_set_period_time_near(info->handle, info->hwParams, &periodTime, &dir); |
| if (ret < 0) { |
| ERROR2("Unable to set period time to %d: %s\n", DEFAULT_PERIOD_TIME, snd_strerror(ret)); |
| return FALSE; |
| } |
| } else { |
| /* set the period count for very small buffer sizes to 2 */ |
| dir = 0; |
| periods = 2; |
| ret = snd_pcm_hw_params_set_periods_near(info->handle, info->hwParams, &periods, &dir); |
| if (ret < 0) { |
| ERROR2("Unable to set period count to %d: %s\n", /*periods*/ 2, snd_strerror(ret)); |
| return FALSE; |
| } |
| } |
| /* write the parameters to device */ |
| ret = snd_pcm_hw_params(info->handle, info->hwParams); |
| if (ret < 0) { |
| ERROR1("Unable to set hw params: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| // returns 1 if successful |
| int setSWParams(AlsaPcmInfo* info) { |
| int ret; |
| |
| /* get the current swparams */ |
| ret = snd_pcm_sw_params_current(info->handle, info->swParams); |
| if (ret < 0) { |
| ERROR1("Unable to determine current swparams: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| /* never start the transfer automatically */ |
| if (!setStartThresholdNoCommit(info, FALSE /* don't use threshold */)) { |
| return FALSE; |
| } |
| |
| /* allow the transfer when at least period_size samples can be processed */ |
| ret = snd_pcm_sw_params_set_avail_min(info->handle, info->swParams, info->periodSize); |
| if (ret < 0) { |
| ERROR1("Unable to set avail min for playback: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| /* write the parameters to the playback device */ |
| ret = snd_pcm_sw_params(info->handle, info->swParams); |
| if (ret < 0) { |
| ERROR1("Unable to set sw params: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| static snd_output_t* ALSA_OUTPUT = NULL; |
| |
| void* DAUDIO_Open(INT32 mixerIndex, INT32 deviceID, int isSource, |
| int encoding, float sampleRate, int sampleSizeInBits, |
| int frameSize, int channels, |
| int isSigned, int isBigEndian, int bufferSizeInBytes) { |
| snd_pcm_format_mask_t* formatMask; |
| snd_pcm_format_t format; |
| int dir; |
| int ret = 0; |
| AlsaPcmInfo* info = NULL; |
| /* snd_pcm_uframes_t is 64 bit on 64-bit systems */ |
| snd_pcm_uframes_t alsaBufferSizeInFrames = 0; |
| |
| |
| TRACE0("> DAUDIO_Open\n"); |
| #ifdef USE_TRACE |
| // for using ALSA debug dump methods |
| if (ALSA_OUTPUT == NULL) { |
| snd_output_stdio_attach(&ALSA_OUTPUT, stdout, 0); |
| } |
| #endif |
| if (channels <= 0) { |
| ERROR1("ERROR: Invalid number of channels=%d!\n", channels); |
| return NULL; |
| } |
| info = (AlsaPcmInfo*) malloc(sizeof(AlsaPcmInfo)); |
| if (!info) { |
| ERROR0("Out of memory\n"); |
| return NULL; |
| } |
| memset(info, 0, sizeof(AlsaPcmInfo)); |
| // initial values are: stopped, flushed |
| info->isRunning = 0; |
| info->isFlushed = 1; |
| |
| ret = openPCMfromDeviceID(deviceID, &(info->handle), isSource, FALSE /* do open device*/); |
| if (ret == 0) { |
| // set to blocking mode |
| snd_pcm_nonblock(info->handle, 0); |
| ret = snd_pcm_hw_params_malloc(&(info->hwParams)); |
| if (ret != 0) { |
| ERROR1(" snd_pcm_hw_params_malloc returned error %d\n", ret); |
| } else { |
| ret = -1; |
| if (getAlsaFormatFromFormat(&format, frameSize / channels, sampleSizeInBits, |
| isSigned, isBigEndian, encoding)) { |
| if (setHWParams(info, |
| sampleRate, |
| channels, |
| bufferSizeInBytes / frameSize, |
| format)) { |
| info->frameSize = frameSize; |
| ret = snd_pcm_hw_params_get_period_size(info->hwParams, &info->periodSize, &dir); |
| if (ret < 0) { |
| ERROR1("ERROR: snd_pcm_hw_params_get_period: %s\n", snd_strerror(ret)); |
| } |
| snd_pcm_hw_params_get_periods(info->hwParams, &(info->periods), &dir); |
| snd_pcm_hw_params_get_buffer_size(info->hwParams, &alsaBufferSizeInFrames); |
| info->bufferSizeInBytes = (int) alsaBufferSizeInFrames * frameSize; |
| TRACE3(" DAUDIO_Open: period size = %d frames, periods = %d. Buffer size: %d bytes.\n", |
| (int) info->periodSize, info->periods, info->bufferSizeInBytes); |
| } |
| } |
| } |
| if (ret == 0) { |
| // set software parameters |
| ret = snd_pcm_sw_params_malloc(&(info->swParams)); |
| if (ret != 0) { |
| ERROR1("snd_pcm_hw_params_malloc returned error %d\n", ret); |
| } else { |
| if (!setSWParams(info)) { |
| ret = -1; |
| } |
| } |
| } |
| if (ret == 0) { |
| // prepare device |
| ret = snd_pcm_prepare(info->handle); |
| if (ret < 0) { |
| ERROR1("ERROR: snd_pcm_prepare: %s\n", snd_strerror(ret)); |
| } |
| } |
| |
| #ifdef GET_POSITION_METHOD2 |
| if (ret == 0) { |
| ret = snd_pcm_status_malloc(&(info->positionStatus)); |
| if (ret != 0) { |
| ERROR1("ERROR in snd_pcm_status_malloc: %s\n", snd_strerror(ret)); |
| } |
| } |
| #endif |
| } |
| if (ret != 0) { |
| DAUDIO_Close((void*) info, isSource); |
| info = NULL; |
| } else { |
| // set to non-blocking mode |
| snd_pcm_nonblock(info->handle, 1); |
| TRACE1("< DAUDIO_Open: Opened device successfully. Handle=%p\n", |
| (void*) info->handle); |
| } |
| return (void*) info; |
| } |
| |
| #ifdef USE_TRACE |
| void printState(snd_pcm_state_t state) { |
| if (state == SND_PCM_STATE_OPEN) { |
| TRACE0("State: SND_PCM_STATE_OPEN\n"); |
| } |
| else if (state == SND_PCM_STATE_SETUP) { |
| TRACE0("State: SND_PCM_STATE_SETUP\n"); |
| } |
| else if (state == SND_PCM_STATE_PREPARED) { |
| TRACE0("State: SND_PCM_STATE_PREPARED\n"); |
| } |
| else if (state == SND_PCM_STATE_RUNNING) { |
| TRACE0("State: SND_PCM_STATE_RUNNING\n"); |
| } |
| else if (state == SND_PCM_STATE_XRUN) { |
| TRACE0("State: SND_PCM_STATE_XRUN\n"); |
| } |
| else if (state == SND_PCM_STATE_DRAINING) { |
| TRACE0("State: SND_PCM_STATE_DRAINING\n"); |
| } |
| else if (state == SND_PCM_STATE_PAUSED) { |
| TRACE0("State: SND_PCM_STATE_PAUSED\n"); |
| } |
| else if (state == SND_PCM_STATE_SUSPENDED) { |
| TRACE0("State: SND_PCM_STATE_SUSPENDED\n"); |
| } |
| } |
| #endif |
| |
| int DAUDIO_Start(void* id, int isSource) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| int ret; |
| snd_pcm_state_t state; |
| |
| TRACE0("> DAUDIO_Start\n"); |
| // set to blocking mode |
| snd_pcm_nonblock(info->handle, 0); |
| // set start mode so that it always starts as soon as data is there |
| setStartThreshold(info, TRUE /* use threshold */); |
| state = snd_pcm_state(info->handle); |
| if (state == SND_PCM_STATE_PAUSED) { |
| // in case it was stopped previously |
| TRACE0(" Un-pausing...\n"); |
| ret = snd_pcm_pause(info->handle, FALSE); |
| if (ret != 0) { |
| ERROR2(" NOTE: error in snd_pcm_pause:%d: %s\n", ret, snd_strerror(ret)); |
| } |
| } |
| if (state == SND_PCM_STATE_SUSPENDED) { |
| TRACE0(" Resuming...\n"); |
| ret = snd_pcm_resume(info->handle); |
| if (ret < 0) { |
| if ((ret != -EAGAIN) && (ret != -ENOSYS)) { |
| ERROR2(" ERROR: error in snd_pcm_resume:%d: %s\n", ret, snd_strerror(ret)); |
| } |
| } |
| } |
| if (state == SND_PCM_STATE_SETUP) { |
| TRACE0("need to call prepare again...\n"); |
| // prepare device |
| ret = snd_pcm_prepare(info->handle); |
| if (ret < 0) { |
| ERROR1("ERROR: snd_pcm_prepare: %s\n", snd_strerror(ret)); |
| } |
| } |
| // in case there is still data in the buffers |
| ret = snd_pcm_start(info->handle); |
| if (ret != 0) { |
| if (ret != -EPIPE) { |
| ERROR2(" NOTE: error in snd_pcm_start: %d: %s\n", ret, snd_strerror(ret)); |
| } |
| } |
| // set to non-blocking mode |
| ret = snd_pcm_nonblock(info->handle, 1); |
| if (ret != 0) { |
| ERROR1(" ERROR in snd_pcm_nonblock: %s\n", snd_strerror(ret)); |
| } |
| state = snd_pcm_state(info->handle); |
| #ifdef USE_TRACE |
| printState(state); |
| #endif |
| ret = (state == SND_PCM_STATE_PREPARED) |
| || (state == SND_PCM_STATE_RUNNING) |
| || (state == SND_PCM_STATE_XRUN) |
| || (state == SND_PCM_STATE_SUSPENDED); |
| if (ret) { |
| info->isRunning = 1; |
| // source line should keep isFlushed value until Write() is called; |
| // for target data line reset it right now. |
| if (!isSource) { |
| info->isFlushed = 0; |
| } |
| } |
| TRACE1("< DAUDIO_Start %s\n", ret?"success":"error"); |
| return ret?TRUE:FALSE; |
| } |
| |
| int DAUDIO_Stop(void* id, int isSource) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| int ret; |
| |
| TRACE0("> DAUDIO_Stop\n"); |
| // set to blocking mode |
| snd_pcm_nonblock(info->handle, 0); |
| setStartThreshold(info, FALSE /* don't use threshold */); // device will not start after buffer xrun |
| ret = snd_pcm_pause(info->handle, 1); |
| // set to non-blocking mode |
| snd_pcm_nonblock(info->handle, 1); |
| if (ret != 0) { |
| ERROR1("ERROR in snd_pcm_pause: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| info->isRunning = 0; |
| TRACE0("< DAUDIO_Stop success\n"); |
| return TRUE; |
| } |
| |
| void DAUDIO_Close(void* id, int isSource) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| |
| TRACE0("DAUDIO_Close\n"); |
| if (info != NULL) { |
| if (info->handle != NULL) { |
| snd_pcm_close(info->handle); |
| } |
| if (info->hwParams) { |
| snd_pcm_hw_params_free(info->hwParams); |
| } |
| if (info->swParams) { |
| snd_pcm_sw_params_free(info->swParams); |
| } |
| #ifdef GET_POSITION_METHOD2 |
| if (info->positionStatus) { |
| snd_pcm_status_free(info->positionStatus); |
| } |
| #endif |
| free(info); |
| } |
| } |
| |
| /* |
| * Underrun and suspend recovery |
| * returns |
| * 0: exit native and return 0 |
| * 1: try again to write/read |
| * -1: error - exit native with return value -1 |
| */ |
| int xrun_recovery(AlsaPcmInfo* info, int err) { |
| int ret; |
| |
| if (err == -EPIPE) { /* underrun / overflow */ |
| TRACE0("xrun_recovery: underrun/overflow.\n"); |
| ret = snd_pcm_prepare(info->handle); |
| if (ret < 0) { |
| ERROR1("Can't recover from underrun/overflow, prepare failed: %s\n", snd_strerror(ret)); |
| return -1; |
| } |
| return 1; |
| } else if (err == -ESTRPIPE) { |
| TRACE0("xrun_recovery: suspended.\n"); |
| ret = snd_pcm_resume(info->handle); |
| if (ret < 0) { |
| if (ret == -EAGAIN) { |
| return 0; /* wait until the suspend flag is released */ |
| } |
| return -1; |
| } |
| ret = snd_pcm_prepare(info->handle); |
| if (ret < 0) { |
| ERROR1("Can't recover from underrun/overflow, prepare failed: %s\n", snd_strerror(ret)); |
| return -1; |
| } |
| return 1; |
| } else if (err == -EAGAIN) { |
| TRACE0("xrun_recovery: EAGAIN try again flag.\n"); |
| return 0; |
| } |
| |
| TRACE2("xrun_recovery: unexpected error %d: %s\n", err, snd_strerror(err)); |
| return -1; |
| } |
| |
| // returns -1 on error |
| int DAUDIO_Write(void* id, char* data, int byteSize) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| int ret, count; |
| snd_pcm_sframes_t frameSize, writtenFrames; |
| |
| TRACE1("> DAUDIO_Write %d bytes\n", byteSize); |
| |
| /* sanity */ |
| if (byteSize <= 0 || info->frameSize <= 0) { |
| ERROR2(" DAUDIO_Write: byteSize=%d, frameSize=%d!\n", |
| (int) byteSize, (int) info->frameSize); |
| TRACE0("< DAUDIO_Write returning -1\n"); |
| return -1; |
| } |
| |
| count = 2; // maximum number of trials to recover from underrun |
| //frameSize = snd_pcm_bytes_to_frames(info->handle, byteSize); |
| frameSize = (snd_pcm_sframes_t) (byteSize / info->frameSize); |
| do { |
| writtenFrames = snd_pcm_writei(info->handle, (const void*) data, (snd_pcm_uframes_t) frameSize); |
| |
| if (writtenFrames < 0) { |
| ret = xrun_recovery(info, (int) writtenFrames); |
| if (ret <= 0) { |
| TRACE1("DAUDIO_Write: xrun recovery returned %d -> return.\n", ret); |
| return ret; |
| } |
| if (count-- <= 0) { |
| ERROR0("DAUDIO_Write: too many attempts to recover from xrun/suspend\n"); |
| return -1; |
| } |
| } else { |
| break; |
| } |
| } while (TRUE); |
| //ret = snd_pcm_frames_to_bytes(info->handle, writtenFrames); |
| |
| if (writtenFrames > 0) { |
| // reset "flushed" flag |
| info->isFlushed = 0; |
| } |
| |
| ret = (int) (writtenFrames * info->frameSize); |
| TRACE1("< DAUDIO_Write: returning %d bytes.\n", ret); |
| return ret; |
| } |
| |
| // returns -1 on error |
| int DAUDIO_Read(void* id, char* data, int byteSize) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| int ret, count; |
| snd_pcm_sframes_t frameSize, readFrames; |
| |
| TRACE1("> DAUDIO_Read %d bytes\n", byteSize); |
| /*TRACE3(" info=%p, data=%p, byteSize=%d\n", |
| (void*) info, (void*) data, (int) byteSize); |
| TRACE2(" info->frameSize=%d, info->handle=%p\n", |
| (int) info->frameSize, (void*) info->handle); |
| */ |
| /* sanity */ |
| if (byteSize <= 0 || info->frameSize <= 0) { |
| ERROR2(" DAUDIO_Read: byteSize=%d, frameSize=%d!\n", |
| (int) byteSize, (int) info->frameSize); |
| TRACE0("< DAUDIO_Read returning -1\n"); |
| return -1; |
| } |
| if (!info->isRunning && info->isFlushed) { |
| // PCM has nothing to read |
| return 0; |
| } |
| |
| count = 2; // maximum number of trials to recover from error |
| //frameSize = snd_pcm_bytes_to_frames(info->handle, byteSize); |
| frameSize = (snd_pcm_sframes_t) (byteSize / info->frameSize); |
| do { |
| readFrames = snd_pcm_readi(info->handle, (void*) data, (snd_pcm_uframes_t) frameSize); |
| if (readFrames < 0) { |
| ret = xrun_recovery(info, (int) readFrames); |
| if (ret <= 0) { |
| TRACE1("DAUDIO_Read: xrun recovery returned %d -> return.\n", ret); |
| return ret; |
| } |
| if (count-- <= 0) { |
| ERROR0("DAUDIO_Read: too many attempts to recover from xrun/suspend\n"); |
| return -1; |
| } |
| } else { |
| break; |
| } |
| } while (TRUE); |
| //ret = snd_pcm_frames_to_bytes(info->handle, readFrames); |
| ret = (int) (readFrames * info->frameSize); |
| TRACE1("< DAUDIO_Read: returning %d bytes.\n", ret); |
| return ret; |
| } |
| |
| |
| int DAUDIO_GetBufferSize(void* id, int isSource) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| |
| return info->bufferSizeInBytes; |
| } |
| |
| int DAUDIO_StillDraining(void* id, int isSource) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| snd_pcm_state_t state; |
| |
| state = snd_pcm_state(info->handle); |
| //printState(state); |
| //TRACE1("Still draining: %s\n", (state != SND_PCM_STATE_XRUN)?"TRUE":"FALSE"); |
| return (state == SND_PCM_STATE_RUNNING)?TRUE:FALSE; |
| } |
| |
| |
| int DAUDIO_Flush(void* id, int isSource) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| int ret; |
| |
| TRACE0("DAUDIO_Flush\n"); |
| |
| if (info->isFlushed) { |
| // nothing to drop |
| return 1; |
| } |
| |
| ret = snd_pcm_drop(info->handle); |
| if (ret != 0) { |
| ERROR1("ERROR in snd_pcm_drop: %s\n", snd_strerror(ret)); |
| return FALSE; |
| } |
| |
| info->isFlushed = 1; |
| if (info->isRunning) { |
| ret = DAUDIO_Start(id, isSource); |
| } |
| return ret; |
| } |
| |
| int DAUDIO_GetAvailable(void* id, int isSource) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| snd_pcm_sframes_t availableInFrames; |
| snd_pcm_state_t state; |
| int ret; |
| |
| state = snd_pcm_state(info->handle); |
| if (info->isFlushed || state == SND_PCM_STATE_XRUN) { |
| // if in xrun state then we have the entire buffer available, |
| // not 0 as alsa reports |
| ret = info->bufferSizeInBytes; |
| } else { |
| availableInFrames = snd_pcm_avail_update(info->handle); |
| if (availableInFrames < 0) { |
| ret = 0; |
| } else { |
| //ret = snd_pcm_frames_to_bytes(info->handle, availableInFrames); |
| ret = (int) (availableInFrames * info->frameSize); |
| } |
| } |
| TRACE1("DAUDIO_GetAvailable returns %d bytes\n", ret); |
| return ret; |
| } |
| |
| INT64 estimatePositionFromAvail(AlsaPcmInfo* info, int isSource, INT64 javaBytePos, int availInBytes) { |
| // estimate the current position with the buffer size and |
| // the available bytes to read or write in the buffer. |
| // not an elegant solution - bytePos will stop on xruns, |
| // and in race conditions it may jump backwards |
| // Advantage is that it is indeed based on the samples that go through |
| // the system (rather than time-based methods) |
| if (isSource) { |
| // javaBytePos is the position that is reached when the current |
| // buffer is played completely |
| return (INT64) (javaBytePos - info->bufferSizeInBytes + availInBytes); |
| } else { |
| // javaBytePos is the position that was when the current buffer was empty |
| return (INT64) (javaBytePos + availInBytes); |
| } |
| } |
| |
| INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) { |
| AlsaPcmInfo* info = (AlsaPcmInfo*) id; |
| int ret; |
| INT64 result = javaBytePos; |
| snd_pcm_state_t state; |
| state = snd_pcm_state(info->handle); |
| |
| if (!info->isFlushed && state != SND_PCM_STATE_XRUN) { |
| #ifdef GET_POSITION_METHOD2 |
| snd_timestamp_t* ts; |
| snd_pcm_uframes_t framesAvail; |
| |
| // note: slight race condition if this is called simultaneously from 2 threads |
| ret = snd_pcm_status(info->handle, info->positionStatus); |
| if (ret != 0) { |
| ERROR1("ERROR in snd_pcm_status: %s\n", snd_strerror(ret)); |
| result = javaBytePos; |
| } else { |
| // calculate from time value, or from available bytes |
| framesAvail = snd_pcm_status_get_avail(info->positionStatus); |
| result = estimatePositionFromAvail(info, isSource, javaBytePos, framesAvail * info->frameSize); |
| } |
| #endif |
| #ifdef GET_POSITION_METHOD3 |
| snd_pcm_uframes_t framesAvail; |
| ret = snd_pcm_avail(info->handle, &framesAvail); |
| if (ret != 0) { |
| ERROR1("ERROR in snd_pcm_avail: %s\n", snd_strerror(ret)); |
| result = javaBytePos; |
| } else { |
| result = estimatePositionFromAvail(info, isSource, javaBytePos, framesAvail * info->frameSize); |
| } |
| #endif |
| #ifdef GET_POSITION_METHOD1 |
| result = estimatePositionFromAvail(info, isSource, javaBytePos, DAUDIO_GetAvailable(id, isSource)); |
| #endif |
| } |
| //printf("getbyteposition: javaBytePos=%d , return=%d\n", (int) javaBytePos, (int) result); |
| return result; |
| } |
| |
| |
| |
| void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) { |
| /* save to ignore, since GetBytePosition |
| * takes the javaBytePos param into account |
| */ |
| } |
| |
| int DAUDIO_RequiresServicing(void* id, int isSource) { |
| // never need servicing on Linux |
| return FALSE; |
| } |
| |
| void DAUDIO_Service(void* id, int isSource) { |
| // never need servicing on Linux |
| } |
| |
| |
| #endif // USE_DAUDIO |