| /* |
| ** |
| ** 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" |
| |
| #include <utils/Log.h> |
| |
| #include <assert.h> |
| #include <limits.h> |
| #include <semaphore.h> |
| #include <sys/ioctl.h> |
| |
| #include <common_time/local_clock.h> |
| |
| #define __DO_FUNCTION_IMPL__ |
| #include "alsa_utils.h" |
| #undef __DO_FUNCTION_IMPL__ |
| #include "AudioOutput.h" |
| |
| 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) |
| , mBytesPerFrame(0) |
| , mBytesPerChunk(0) |
| , mStagingBuf(NULL) |
| , mPrimeTimeoutChunks(0) |
| , 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(); |
| delete[] mStagingBuf; |
| } |
| |
| 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; |
| |
| #if 0 |
| mBytesPerSample = ((mALSAFormat == PCM_FORMAT_S32_LE) ? 4 : 2); |
| #else |
| switch (mALSAFormat) { |
| case PCM_FORMAT_S16_LE: |
| mBytesPerSample = 2; |
| break; |
| case PCM_FORMAT_S24_LE: |
| mBytesPerSample = 3; |
| break; |
| case PCM_FORMAT_S32_LE: |
| mBytesPerSample = 4; |
| break; |
| default: |
| ALOGE("Unexpected alsa format 0x%x, setting mBytesPerSample to 3", mALSAFormat); |
| mBytesPerSample = 3; |
| break; |
| } |
| #endif |
| mBytesPerFrame = mBytesPerSample * mChannelCnt; |
| mBytesPerChunk = mBytesPerFrame * mFramesPerChunk; |
| mStagingBuf = new uint8_t[mBytesPerChunk]; |
| |
| 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 (hasFatalError()) |
| return; |
| |
| uint8_t sbuf[mBytesPerChunk]; |
| uint32_t primeAmount = mBytesPerFrame*nFrames; |
| uint32_t zeroAmount = primeAmount < sizeof(sbuf) |
| ? primeAmount |
| : sizeof(sbuf); |
| |
| // Dispatch full buffer at a time if possible. |
| memset(sbuf, 0, zeroAmount); |
| while (primeAmount && !hasFatalError()) { |
| uint32_t amt = (primeAmount < mBytesPerChunk) ? |
| primeAmount : mBytesPerChunk; |
| doPCMWrite(sbuf, amt); |
| primeAmount -= amt; |
| } |
| |
| mFramesQueuedToDriver += nFrames; |
| } |
| |
| void AudioOutput::stageChunk(const uint8_t* chunkData, |
| uint8_t* sbuf, |
| uint32_t inBytesPerSample, |
| uint32_t nSamples) |
| { |
| memcpy(sbuf, chunkData, inBytesPerSample * nSamples); |
| } |
| |
| 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) { |
| 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); |
| mFramesQueuedToDriver += len / mBytesPerFrame; |
| break; |
| default: |
| // Do nothing. |
| break; |
| } |
| |
| } |
| |
| static int convert_16PCM_to_24PCM(const void* input, void *output, int ipbytes) |
| { |
| int i = 0,outbytes = 0; |
| const int *src = (const int*)input; |
| int *dst = (int*)output; |
| |
| ALOGV("convert 16 to 24 bits for %d",ipbytes); |
| /*convert 16 bit input to 24 bit output |
| in a 32 bit sample*/ |
| if(0 == ipbytes) |
| return outbytes; |
| |
| for(i = 0; i < (ipbytes/4); i++){ |
| int x = (int)((int*)src)[i]; |
| dst[i*2] = ((int)( x & 0x0000FFFF)) << 8; |
| // trying to sign extend |
| dst[i*2] = dst[i*2] << 8; |
| dst[i*2] = dst[i*2] >> 8; |
| //shift to middle |
| dst[i*2 + 1] = (int)(( x & 0xFFFF0000) >> 8); |
| dst[i*2 + 1] = dst[i*2 + 1] << 8; |
| dst[i*2 + 1] = dst[i*2 + 1] >> 8; |
| } |
| outbytes= ipbytes * 2; |
| return outbytes; |
| } |
| |
| void AudioOutput::doPCMWrite(const uint8_t* data, size_t len) { |
| if (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 |
| * only supports 16 or 32bit, so we have to convert to 24-bit |
| * over 32 bit data type. |
| */ |
| int32_t *dstbuff = (int32_t*)malloc(len * 2); |
| if (!dstbuff) { |
| ALOGE("%s: memory allocation for conversion buffer failed", __func__); |
| return; |
| } |
| memset(dstbuff, 0, len*2); |
| len = convert_16PCM_to_24PCM(data, dstbuff, len); |
| int err = pcm_write(mDevice, dstbuff, len); |
| free(dstbuff); |
| #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("pcm_write failed err %d", err); |
| } |
| |
| #if 1 /* not implemented in driver yet, just fake it */ |
| else { |
| 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 |