C++程序  |  525行  |  15.47 KB

/*
**
** Copyright 2011, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

#define LOG_TAG "AudioHAL:AudioOutput"
// #define LOG_NDEBUG 0

#include <utils/Log.h>

#include <assert.h>
#include <limits.h>
#include <semaphore.h>
#include <sys/ioctl.h>

#include <audio_utils/primitives.h>
#include <common_time/local_clock.h>

#define __DO_FUNCTION_IMPL__
#include "alsa_utils.h"
#undef __DO_FUNCTION_IMPL__
#include "AudioOutput.h"

// TODO: Consider using system/media/alsa_utils for the future.

namespace android {

const uint32_t AudioOutput::kMaxDelayCompensationMSec = 300;
const uint32_t AudioOutput::kPrimeTimeoutChunks = 10; // 100ms

AudioOutput::AudioOutput(const char* alsa_name,
                         enum pcm_format alsa_pcm_format)
        : mState(OUT_OF_SYNC)
        , mFramesPerChunk(0)
        , mFramesPerSec(0)
        , mBufferChunks(0)
        , mChannelCnt(0)
        , mALSAName(alsa_name)
        , mALSAFormat(alsa_pcm_format)
        , mBytesPerSample(0)
        , mBytesPerFrame(0)
        , mBytesPerChunk(0)
        , mStagingSize(0)
        , mStagingBuf(NULL)
        , mSilenceSize(0)
        , mSilenceBuf(NULL)
        , mPrimeTimeoutChunks(0)
        , mReportedWriteFail(false)
        , mVolume(0.0)
        , mFixedLvl(0.0)
        , mMute(false)
        , mOutputFixed(false)
        , mVolParamsDirty(true)
{
    mLastNextWriteTimeValid = false;

    mMaxDelayCompFrames = 0;
    mExternalDelayUSec = 0;

    mDevice = NULL;
    mDeviceExtFd = -1;
    mALSACardID = -1;
    mFramesQueuedToDriver = 0;
}

AudioOutput::~AudioOutput() {
    cleanupResources();
    free(mStagingBuf);
    free(mSilenceBuf);
}

status_t AudioOutput::initCheck() {
    if (!mDevice) {
        ALOGE("Unable to open PCM device for %s output.", getOutputName());
        return NO_INIT;
    }
    if (!pcm_is_ready(mDevice)) {
        ALOGE("PCM device %s is not ready.", getOutputName());
        ALOGE("PCM error: %s", pcm_get_error(mDevice));
        return NO_INIT;
    }

    return OK;
}

void AudioOutput::setupInternal() {
    LocalClock lc;

    mMaxDelayCompFrames = kMaxDelayCompensationMSec * mFramesPerSec / 1000;

    switch (mALSAFormat) {
    case PCM_FORMAT_S16_LE:
        mBytesPerSample = 2;
        break;
    case PCM_FORMAT_S24_3LE:
        mBytesPerSample = 3;
        break;
    case PCM_FORMAT_S24_LE: // fall through
    case PCM_FORMAT_S32_LE:
        mBytesPerSample = 4;
        break;
    default:
        LOG_ALWAYS_FATAL("Unexpected alsa format %d", mALSAFormat);
        break;
    }

    mBytesPerFrame = mBytesPerSample * mChannelCnt;
    mBytesPerChunk = mBytesPerFrame * mFramesPerChunk;

    memset(&mFramesToLocalTime, 0, sizeof(mFramesToLocalTime));
    mFramesToLocalTime.a_to_b_numer = lc.getLocalFreq();
    mFramesToLocalTime.a_to_b_denom = mFramesPerSec ? mFramesPerSec : 1;
    LinearTransform::reduce(
            &mFramesToLocalTime.a_to_b_numer,
            &mFramesToLocalTime.a_to_b_denom);

    openPCMDevice();
}

void AudioOutput::primeOutput(bool hasActiveOutputs) {
    ALOGI("primeOutput %s", getOutputName());

    if (hasFatalError())
        return;

    // See comments in AudioStreamOut::write for the reasons behind the
    // different priming levels.
    uint32_t primeAmt = mFramesPerChunk * mBufferChunks;
    if (hasActiveOutputs)
        primeAmt /= 2;

    pushSilence(primeAmt);
    mPrimeTimeoutChunks = 0;
    mState = PRIMED;
}

void AudioOutput::adjustDelay(int32_t nFrames) {
    if (hasFatalError())
        return;

    if (nFrames >= 0) {
        ALOGI("adjustDelay %s %d", getOutputName(), nFrames);
        pushSilence(nFrames);
        mState = ACTIVE;
    } else {
        ALOGW("adjustDelay %s %d, ignoring negative adjustment",
              getOutputName(), nFrames);
    }
}

void AudioOutput::pushSilence(uint32_t nFrames)
{
    if (nFrames == 0 || hasFatalError())
        return;
    // choose 8_24_BIT instead of 16_BIT as it is native to Fugu
    const audio_format_t format = AUDIO_FORMAT_PCM_8_24_BIT;
    const size_t frameSize = audio_bytes_per_sample(format) * mChannelCnt;
    const size_t writeSize = nFrames * frameSize;
    if (mSilenceSize < writeSize) {
        // for zero initialized memory calloc is much faster than malloc or realloc.
        void *sbuf = calloc(nFrames, frameSize);
        if (sbuf == NULL) return;
        free(mSilenceBuf);
        mSilenceBuf = sbuf;
        mSilenceSize = writeSize;
    }
    doPCMWrite((const uint8_t*)mSilenceBuf, writeSize, format);
    mFramesQueuedToDriver += nFrames;
}

void AudioOutput::cleanupResources() {

    Mutex::Autolock _l(mDeviceLock);

    if (NULL != mDevice)
        pcm_close(mDevice);

    mDevice = NULL;
    mDeviceExtFd = -1;
    mALSACardID = -1;
}

void AudioOutput::openPCMDevice() {

    Mutex::Autolock _l(mDeviceLock);
    if (NULL == mDevice) {
        struct pcm_config config;
        int dev_id = 0;
        int retry = 0;
        static const int MAX_RETRY_COUNT = 3;

        mALSACardID = find_alsa_card_by_name(mALSAName);
        if (mALSACardID < 0)
            return;

        memset(&config, 0, sizeof(config));
        config.channels        = mChannelCnt;
        config.rate            = mFramesPerSec;
        config.period_size     = mFramesPerChunk;
        config.period_count    = mBufferChunks;
        config.format          = mALSAFormat;
        // start_threshold is in audio frames. The default behavior
        // is to fill period_size*period_count frames before outputing
        // audio. Setting to 1 will start the DMA immediately. Our first
        // write is a full chunk, so we have 10ms to get back with the next
        // chunk before we underflow. This number could be increased if
        // problems arise.
        config.start_threshold = 1;

        ALOGI("calling pcm_open() for output, mALSACardID = %d, dev_id %d, rate = %u, "
            "%d channels, framesPerChunk = %d, alsaFormat = %d",
              mALSACardID, dev_id, config.rate, config.channels, config.period_size, config.format);
        while (1) {
            // Use PCM_MONOTONIC clock for get_presentation_position.
            mDevice = pcm_open(mALSACardID, dev_id,
                    PCM_OUT | PCM_NORESTART | PCM_MONOTONIC, &config);
            if (initCheck() == OK)
                break;
            if (retry++ >= MAX_RETRY_COUNT) {
                ALOGI("out of retries, giving up");
                break;
            }
            /* try again after a delay.  on hotplug, there appears to
             * be a race where the pcm device node isn't available on
             * first open try.
             */
            pcm_close(mDevice);
            mDevice = NULL;
            sleep(1);
            ALOGI("retrying pcm_open() after delay");
        }
        mDeviceExtFd = mDevice
                        ? *(reinterpret_cast<int*>(mDevice))
                        : -1;
        mState = OUT_OF_SYNC;
    }
}

status_t AudioOutput::getNextWriteTimestamp(int64_t* timestamp,
                                            bool* discon) {
    int64_t  dma_start_time;
    int64_t  frames_queued_to_driver;
    status_t ret;

    *discon = false;
    if (hasFatalError())
        return UNKNOWN_ERROR;

    ret = getDMAStartData(&dma_start_time,
                          &frames_queued_to_driver);
    if (OK != ret) {
        if (mLastNextWriteTimeValid) {
            if (!hasFatalError())
                ALOGE("Underflow detected for output \"%s\"", getOutputName());
            *discon = true;
        }

        goto bailout;
    }

    if (mLastNextWriteTimeValid && (mLastDMAStartTime != dma_start_time)) {
        *discon = true;
        ret = UNKNOWN_ERROR;

        ALOGE("Discontinuous DMA start time detected for output \"%s\"."
              "DMA start time is %lld, but last DMA start time was %lld.",
              getOutputName(), dma_start_time, mLastDMAStartTime);

        goto bailout;
    }

    mLastDMAStartTime = dma_start_time;

    mFramesToLocalTime.a_zero = 0;
    mFramesToLocalTime.b_zero = dma_start_time;

    if (!mFramesToLocalTime.doForwardTransform(frames_queued_to_driver,
                                               timestamp)) {
        ALOGE("Overflow when attempting to compute next write time for output"
              " \"%s\".  Frames Queued To Driver = %lld, DMA Start Time = %lld",
              getOutputName(), frames_queued_to_driver, dma_start_time);
        ret = UNKNOWN_ERROR;
        goto bailout;
    }

    mLastNextWriteTime = *timestamp;
    mLastNextWriteTimeValid = true;

    // If we have a valuid timestamp, DMA has started so advance the state.
    if (mState == PRIMED)
        mState = DMA_START;

    return OK;

bailout:
    mLastNextWriteTimeValid = false;
    // If we underflow, reset this output now.
    if (mState > PRIMED) {
        reset();
    }

    return ret;
}

bool AudioOutput::getLastNextWriteTSValid() const {
    return mLastNextWriteTimeValid;
}

int64_t AudioOutput::getLastNextWriteTS() const {
    return mLastNextWriteTime;
}

uint32_t AudioOutput::getExternalDelay_uSec() const {
    return mExternalDelayUSec;
}

void AudioOutput::setExternalDelay_uSec(uint32_t delay_usec) {
    mExternalDelayUSec = delay_usec;
}

void AudioOutput::reset() {
    if (hasFatalError())
        return;

    // Flush the driver level.
    cleanupResources();
    openPCMDevice();
    mFramesQueuedToDriver = 0;
    mLastNextWriteTimeValid = false;

    if (OK == initCheck()) {
        ALOGE("Reset %s", mALSAName);
    } else {
        ALOGE("Reset for %s failed, device is a zombie pending cleanup.", mALSAName);
        cleanupResources();
        mState = FATAL;
    }
}

status_t AudioOutput::getDMAStartData(
        int64_t* dma_start_time,
        int64_t* frames_queued_to_driver) {
    int ret;
#if 1 /* not implemented in driver yet, just fake it */
    *dma_start_time = mLastDMAStartTime;
    ret = 0;
#endif

    // If the get start time ioctl fails with an error of EBADFD, then our
    // underlying audio device is in the DISCONNECTED state.  The only reason
    // this should happen is that HDMI was unplugged while we were running, and
    // the audio driver needed to immediately shut down the driver without
    // involving the application level.  We should enter the fatal state, and
    // wait until the app level catches up to our view of the world (at which
    // point in time we will go through a plug/unplug cycle which should clean
    // things up).
    if (ret < 0) {
        if (EBADFD == errno) {
            ALOGI("Failed to ioctl to %s, output is probably disconnected."
                  " Going into zombie state to await cleanup.", mALSAName);
            cleanupResources();
            mState = FATAL;
        }

        return UNKNOWN_ERROR;
    }

    *frames_queued_to_driver = mFramesQueuedToDriver;
    return OK;
}

void AudioOutput::processOneChunk(const uint8_t* data, size_t len,
                                  bool hasActiveOutputs, audio_format_t format) {
    switch (mState) {
    case OUT_OF_SYNC:
        primeOutput(hasActiveOutputs);
        break;
    case PRIMED:
        if (mPrimeTimeoutChunks < kPrimeTimeoutChunks)
            mPrimeTimeoutChunks++;
        else
            // Uh-oh, DMA didn't start. Reset and try again.
            reset();

        break;
    case DMA_START:
        // Don't push data when primed and waiting for buffer alignment.
        // We need to align the ALSA buffers first.
        break;
    case ACTIVE: {
        doPCMWrite(data, len, format);
        // we use input frame size here (mBytesPerFrame is alsa device frame size)
        const size_t frameSize = mChannelCnt * audio_bytes_per_sample(format);
        mFramesQueuedToDriver += len / frameSize;
        } break;
    default:
        // Do nothing.
        break;
    }

}

void AudioOutput::doPCMWrite(const uint8_t* data, size_t len, audio_format_t format) {
    if (len == 0 || hasFatalError())
        return;

    // If write fails with an error of EBADFD, then our underlying audio
    // device is in a pretty bad state.  This common cause of this is
    // that HDMI was unplugged while we were running, and the audio
    // driver needed to immediately shut down the driver without
    // involving the application level.  When this happens, the HDMI
    // audio device is put into the DISCONNECTED state, and calls to
    // write will return EBADFD.
#if 1
    /* Intel HDMI appears to be locked at 24bit PCM, but Android
     * will send data in the format specified in adev_open_output_stream().
     */
    LOG_ALWAYS_FATAL_IF(mALSAFormat != PCM_FORMAT_S24_LE,
            "Fugu alsa device format(%d) must be PCM_FORMAT_S24_LE", mALSAFormat);

    int err = BAD_VALUE;
    switch(format) {
    case AUDIO_FORMAT_IEC61937:
    case AUDIO_FORMAT_PCM_16_BIT: {
        const size_t outputSize = len * 2;
        if (outputSize > mStagingSize) {
            void *buf = realloc(mStagingBuf, outputSize);
            if (buf == NULL) {
                ALOGE("%s: memory allocation for conversion buffer failed", __func__);
                return;
            }
            mStagingBuf = buf;
            mStagingSize = outputSize;
        }
        memcpy_to_q8_23_from_i16((int32_t*)mStagingBuf, (const int16_t*)data, len >> 1);
        err = pcm_write(mDevice, mStagingBuf, outputSize);
    } break;
    case AUDIO_FORMAT_PCM_8_24_BIT:
        err = pcm_write(mDevice, data, len);
        break;
    default:
        LOG_ALWAYS_FATAL("Fugu input format(%#x) should be 16 bit or 8_24 bit pcm", format);
        break;
    }

#else

    int err = pcm_write(mDevice, data, len);
#endif
    if ((err < 0) && (EBADFD == errno)) {
        ALOGI("Failed to write to %s, output is probably disconnected."
              " Going into zombie state to await cleanup.", mALSAName);
        cleanupResources();
        mState = FATAL;
    }
    else if (err < 0) {
        ALOGW_IF(!mReportedWriteFail, "pcm_write failed err %d", err);
        mReportedWriteFail = true;
    }
    else {
        mReportedWriteFail = false;
#if 1 /* not implemented in driver yet, just fake it */
        LocalClock lc;
        mLastDMAStartTime = lc.getLocalTime();
#endif
    }
}

void AudioOutput::setVolume(float vol) {
    Mutex::Autolock _l(mVolumeLock);
    if (mVolume != vol) {
        mVolume = vol;
        mVolParamsDirty = true;
    }
}

void AudioOutput::setMute(bool mute) {
    Mutex::Autolock _l(mVolumeLock);
    if (mMute != mute) {
        mMute = mute;
        mVolParamsDirty = true;
    }
}

void AudioOutput::setOutputIsFixed(bool fixed) {
    Mutex::Autolock _l(mVolumeLock);
    if (mOutputFixed != fixed) {
        mOutputFixed = fixed;
        mVolParamsDirty = true;
    }
}

void AudioOutput::setFixedOutputLevel(float level) {
    Mutex::Autolock _l(mVolumeLock);
    if (mFixedLvl != level) {
        mFixedLvl = level;
        mVolParamsDirty = true;
    }
}

int  AudioOutput::getHardwareTimestamp(size_t *pAvail,
                            struct timespec *pTimestamp)
{
    Mutex::Autolock _l(mDeviceLock);
    if (!mDevice) {
       ALOGW("pcm device unavailable - reinitialize  timestamp");
       return -1;
    }
    return pcm_get_htimestamp(mDevice, pAvail, pTimestamp);
}

}  // namespace android