alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (PLATFORM_API_LinuxOS_ALSA_PCM.c)

This example Java source code file (PLATFORM_API_LinuxOS_ALSA_PCM.c) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

alsapcminfo, daudio_read, daudio_write, error1, error2, false, get_position_method2, int64, null, state, trace0, trace1, true, unable

The PLATFORM_API_LinuxOS_ALSA_PCM.c Java example source code

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

    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

Other Java examples (source code examples)

Here is a short list of links related to this Java PLATFORM_API_LinuxOS_ALSA_PCM.c source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.