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