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