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