--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_PCM.c Fri Mar 23 09:51:02 2018 +0100
@@ -0,0 +1,941 @@
+/*
+ * 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