/* * 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