/*
**
** Copyright 2014, 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_AudioHardwareOutput"
#include <utils/Log.h>
#include <stdint.h>
#include <limits.h>
#include <math.h>
#include <common_time/local_clock.h>
#include <cutils/properties.h>
#include "AudioHardwareOutput.h"
#include "AudioStreamOut.h"
#include "HDMIAudioOutput.h"
namespace android {
// Global singleton.
AudioHardwareOutput gAudioHardwareOutput;
// HDMI options.
const String8 AudioHardwareOutput::kHDMIAllowedParamKey(
"atv.hdmi_audio.allowed");
const String8 AudioHardwareOutput::kHDMIDelayCompParamKey(
"atv.hdmi.audio_delay");
const String8 AudioHardwareOutput::kFixedHDMIOutputParamKey(
"atv.hdmi.fixed_volume");
const String8 AudioHardwareOutput::kFixedHDMIOutputLevelParamKey(
"atv.hdmi.fixed_level");
// Video delay comp hack options (not exposed to user level)
const String8 AudioHardwareOutput::kVideoDelayCompParamKey(
"atv.video.delay_comp");
// Defaults for settings.
void AudioHardwareOutput::OutputSettings::setDefaults()
{
allowed = true;
delayCompUsec = 0;
isFixed = false;
fixedLvl = 0.0f;
}
void AudioHardwareOutput::Settings::setDefaults() {
hdmi.setDefaults();
masterVolume = 0.60;
masterMute = false;
// Default this to 16mSec or so. Since audio start times are not sync'ed to
// to the VBI, there should be a +/-0.5 output frame rate error in the AV
// sync, even under the best of circumstances.
//
// In practice, the android core seems to have a hard time hitting its frame
// cadence consistently. Sometimes the frames are on time, and sometimes
// they are even a little early, but more often than not, the frames are
// late by about a full output frame time.
//
// ATV pretty much always uses a 60fps output rate, and the only thing
// consuming the latency estimate provided by the HAL is the path handling
// AV sync. For now, we can fudge this number to move things back in the
// direction of correct by providing a setting for video delay compensation
// which will be subtracted from the latency estimate and defaulting it to
// a reasonable middle gound (12mSec in this case).
videoDelayCompUsec = 12000;
}
AudioHardwareOutput::AudioHardwareOutput()
: mMainOutput(NULL)
, mMCOutput(NULL)
, mHDMIConnected(false)
, mMaxDelayCompUsec(0)
{
mSettings.setDefaults();
mHDMICardID = find_alsa_card_by_name(kHDMI_ALSADeviceName);
}
AudioHardwareOutput::~AudioHardwareOutput()
{
closeOutputStream(mMainOutput);
closeOutputStream(mMCOutput);
}
status_t AudioHardwareOutput::initCheck() {
return NO_ERROR;
}
AudioStreamOut* AudioHardwareOutput::openOutputStream(
uint32_t devices,
audio_format_t *format,
uint32_t *channels,
uint32_t *sampleRate,
audio_output_flags_t flags,
status_t *status) {
(void) devices;
AutoMutex lock(mStreamLock);
AudioStreamOut** pp_out;
AudioStreamOut* out;
bool isIec958NonAudio = (flags & AUDIO_OUTPUT_FLAG_IEC958_NONAUDIO) != 0;
if (!(flags & AUDIO_OUTPUT_FLAG_DIRECT)) {
pp_out = &mMainOutput;
out = new AudioStreamOut(*this, false, isIec958NonAudio);
} else {
pp_out = &mMCOutput;
out = new AudioStreamOut(*this, true, isIec958NonAudio);
}
if (out == NULL) {
*status = NO_MEMORY;
return NULL;
}
*status = out->set(format, channels, sampleRate);
if (*status == NO_ERROR) {
*pp_out = out;
updateTgtDevices_l();
} else {
delete out;
}
return *pp_out;
}
void AudioHardwareOutput::closeOutputStream(AudioStreamOut* out) {
if (out == NULL)
return;
// Putting the stream into "standby" should cause it to release all of its
// physical outputs.
out->standby();
{
Mutex::Autolock _l(mStreamLock);
if (mMainOutput && out == mMainOutput) {
delete mMainOutput;
mMainOutput = NULL;
} else if (mMCOutput && out == mMCOutput) {
delete mMCOutput;
mMCOutput = NULL;
}
updateTgtDevices_l();
}
}
status_t AudioHardwareOutput::setMasterVolume(float volume)
{
Mutex::Autolock _l1(mOutputLock);
Mutex::Autolock _l2(mSettingsLock);
mSettings.masterVolume = volume;
AudioOutputList::iterator I;
for (I = mPhysOutputs.begin(); I != mPhysOutputs.end(); ++I)
(*I)->setVolume(mSettings.masterVolume);
return NO_ERROR;
}
status_t AudioHardwareOutput::getMasterVolume(float* volume) {
if (NULL == volume)
return BAD_VALUE;
// Explicit scope for auto-lock pattern.
{
Mutex::Autolock _l(mSettingsLock);
*volume = mSettings.masterVolume;
}
return NO_ERROR;
}
status_t AudioHardwareOutput::setMasterMute(bool muted)
{
Mutex::Autolock _l1(mOutputLock);
Mutex::Autolock _l2(mSettingsLock);
mSettings.masterMute = muted;
AudioOutputList::iterator I;
for (I = mPhysOutputs.begin(); I != mPhysOutputs.end(); ++I)
(*I)->setMute(mSettings.masterMute);
return NO_ERROR;
}
status_t AudioHardwareOutput::getMasterMute(bool* muted) {
if (NULL == muted)
return BAD_VALUE;
// Explicit scope for auto-lock pattern.
{
Mutex::Autolock _l(mSettingsLock);
*muted = mSettings.masterMute;
}
return NO_ERROR;
}
status_t AudioHardwareOutput::setParameters(const char* kvpairs) {
AudioParameter param = AudioParameter(String8(kvpairs));
status_t status = NO_ERROR;
float floatVal;
int intVal;
Settings initial, s;
{
// Record the initial state of the settings from inside the lock. Then
// leave the lock in order to parse the changes to be made.
Mutex::Autolock _l(mSettingsLock);
initial = s = mSettings;
}
/***************************************************************
* HDMI Audio Options *
***************************************************************/
if (param.getInt(kHDMIAllowedParamKey, intVal) == NO_ERROR) {
s.hdmi.allowed = (intVal != 0);
param.remove(kHDMIAllowedParamKey);
}
if ((param.getFloat(kHDMIDelayCompParamKey, floatVal) == NO_ERROR) &&
(floatVal >= 0.0) &&
(floatVal <= AudioOutput::kMaxDelayCompensationMSec)) {
uint32_t delay_comp = static_cast<uint32_t>(floatVal * 1000.0);
s.hdmi.delayCompUsec = delay_comp;
param.remove(kHDMIDelayCompParamKey);
}
if (param.getInt(kFixedHDMIOutputParamKey, intVal) == NO_ERROR) {
s.hdmi.isFixed = (intVal != 0);
param.remove(kFixedHDMIOutputParamKey);
}
if ((param.getFloat(kFixedHDMIOutputLevelParamKey, floatVal) == NO_ERROR)
&& (floatVal <= 0.0)) {
s.hdmi.fixedLvl = floatVal;
param.remove(kFixedHDMIOutputLevelParamKey);
}
/***************************************************************
* Other Options *
***************************************************************/
if ((param.getFloat(kVideoDelayCompParamKey, floatVal) == NO_ERROR) &&
(floatVal >= 0.0) &&
(floatVal <= AudioOutput::kMaxDelayCompensationMSec)) {
s.videoDelayCompUsec = static_cast<uint32_t>(floatVal * 1000.0);
param.remove(kVideoDelayCompParamKey);
}
if (param.size())
status = BAD_VALUE;
// If there was a change made to settings, go ahead and apply it now.
bool allowedOutputsChanged = false;
if (memcmp(&initial, &s, sizeof(initial))) {
Mutex::Autolock _l1(mOutputLock);
Mutex::Autolock _l2(mSettingsLock);
if (memcmp(&initial.hdmi, &s.hdmi, sizeof(initial.hdmi)))
allowedOutputsChanged = allowedOutputsChanged ||
applyOutputSettings_l(initial.hdmi, s.hdmi, mSettings.hdmi,
HDMIAudioOutput::classDevMask());
if (initial.videoDelayCompUsec != s.videoDelayCompUsec)
mSettings.videoDelayCompUsec = s.videoDelayCompUsec;
uint32_t tmp = 0;
if (mSettings.hdmi.allowed && (tmp < mSettings.hdmi.delayCompUsec))
tmp = mSettings.hdmi.delayCompUsec;
if (mMaxDelayCompUsec != tmp)
mMaxDelayCompUsec = tmp;
}
if (allowedOutputsChanged) {
Mutex::Autolock _l(mStreamLock);
updateTgtDevices_l();
}
return status;
}
bool AudioHardwareOutput::applyOutputSettings_l(
const AudioHardwareOutput::OutputSettings& initial,
const AudioHardwareOutput::OutputSettings& current,
AudioHardwareOutput::OutputSettings& updateMe,
uint32_t outDevMask) {
// ASSERT(holding mOutputLock and mSettingsLock)
sp<AudioOutput> out;
// Check for a change in the allowed/not-allowed state. Update if needed
// and return true if there was a change made.
bool ret = false;
if (initial.allowed != current.allowed) {
updateMe.allowed = current.allowed;
ret = true;
}
// Look for an instance of the output to be updated in case other changes
// were made.
AudioOutputList::iterator I;
for (I = mPhysOutputs.begin(); I != mPhysOutputs.end(); ++I) {
if (outDevMask == (*I)->devMask()) {
out = (*I);
break;
}
}
// Update the other settings, if needed.
if (initial.delayCompUsec != current.delayCompUsec) {
updateMe.delayCompUsec = current.delayCompUsec;
if (out != NULL)
out->setExternalDelay_uSec(current.delayCompUsec);
}
if (initial.isFixed != current.isFixed) {
updateMe.isFixed = current.isFixed;
if (out != NULL)
out->setOutputIsFixed(current.isFixed);
}
if (initial.fixedLvl != current.fixedLvl) {
updateMe.fixedLvl = current.fixedLvl;
if (out != NULL)
out->setFixedOutputLevel(current.fixedLvl);
}
return ret;
}
char* AudioHardwareOutput::getParameters(const char* keys) {
Settings s;
// Explicit scope for auto-lock pattern.
{
// Snapshot the current settings so we don't have to hold the settings
// lock while formatting the results.
Mutex::Autolock _l(mSettingsLock);
s = mSettings;
}
AudioParameter param = AudioParameter(String8(keys));
String8 tmp;
/***************************************************************
* HDMI Audio Options *
***************************************************************/
if (param.get(kHDMIAllowedParamKey, tmp) == NO_ERROR)
param.addInt(kHDMIAllowedParamKey, s.hdmi.allowed ? 1 : 0);
if (param.get(kHDMIDelayCompParamKey, tmp) == NO_ERROR)
param.addFloat(kHDMIDelayCompParamKey,
static_cast<float>(s.hdmi.delayCompUsec) / 1000.0);
if (param.get(kFixedHDMIOutputParamKey, tmp) == NO_ERROR)
param.addInt(kFixedHDMIOutputParamKey, s.hdmi.isFixed ? 1 : 0);
if (param.get(kFixedHDMIOutputLevelParamKey, tmp) == NO_ERROR)
param.addFloat(kFixedHDMIOutputLevelParamKey, s.hdmi.fixedLvl);
/***************************************************************
* Other Options *
***************************************************************/
if (param.get(kVideoDelayCompParamKey, tmp) == NO_ERROR)
param.addFloat(kVideoDelayCompParamKey,
static_cast<float>(s.videoDelayCompUsec) / 1000.0);
return strdup(param.toString().string());
}
void AudioHardwareOutput::updateRouting(uint32_t devMask) {
Mutex::Autolock _l(mStreamLock);
bool hasHDMI = 0 != (devMask & HDMIAudioOutput::classDevMask());
ALOGI("%s: hasHDMI = %d, mHDMIConnected = %d", __func__, hasHDMI, mHDMIConnected);
if (mHDMIConnected != hasHDMI) {
mHDMIConnected = hasHDMI;
if (mHDMIConnected)
mHDMIAudioCaps.loadCaps(mHDMICardID);
else
mHDMIAudioCaps.reset();
updateTgtDevices_l();
}
}
status_t AudioHardwareOutput::obtainOutput(const AudioStreamOut& tgtStream,
uint32_t devMask,
sp<AudioOutput>* newOutput) {
Mutex::Autolock _l1(mOutputLock);
// Sanity check the device mask passed to us. There should exactly one bit
// set, no less, no more.
if (popcount(devMask) != 1) {
ALOGW("bad device mask in obtainOutput, %08x", devMask);
return INVALID_OPERATION;
}
// Start by checking to see if the requested output is currently busy.
AudioOutputList::iterator I;
for (I = mPhysOutputs.begin(); I != mPhysOutputs.end(); ++I)
if (devMask & (*I)->devMask())
return OK; // Yup; its busy.
// Looks like we don't currently have an output of the requested type.
// Figure out which type is being requested and try to construct one.
OutputSettings* S = NULL;
if (devMask & HDMIAudioOutput::classDevMask()) {
*newOutput = new HDMIAudioOutput();
S = &mSettings.hdmi;
}
else {
ALOGW("%s stream out requested output of unknown type %08x",
tgtStream.getName(), devMask);
return BAD_VALUE;
}
if (*newOutput == NULL)
return NO_MEMORY;
status_t res = (*newOutput)->setupForStream(tgtStream);
if (res != OK) {
ALOGE("%s setupForStream() returned %d",
tgtStream.getName(), res);
*newOutput = NULL;
} else {
ALOGI("%s stream out adding %s output.",
tgtStream.getName(), (*newOutput)->getOutputName());
mPhysOutputs.push_back(*newOutput);
{ // Apply current settings
Mutex::Autolock _l2(mSettingsLock);
(*newOutput)->setVolume(mSettings.masterVolume);
(*newOutput)->setMute(mSettings.masterMute);
(*newOutput)->setExternalDelay_uSec(S->delayCompUsec);
(*newOutput)->setOutputIsFixed(S->isFixed);
(*newOutput)->setFixedOutputLevel(S->fixedLvl);
}
}
return res;
}
void AudioHardwareOutput::releaseOutput(const AudioStreamOut& tgtStream,
const sp<AudioOutput>& releaseMe) {
Mutex::Autolock _l(mOutputLock);
ALOGI("%s stream out removing %s output.",
tgtStream.getName(), releaseMe->getOutputName());
// Immediately release any resources associated with this output (In
// particular, make sure to close any ALSA device driver handles ASAP)
releaseMe->cleanupResources();
// Now, clear our internal bookkeeping.
AudioOutputList::iterator I;
for (I = mPhysOutputs.begin(); I != mPhysOutputs.end(); ++I) {
if (releaseMe.get() == (*I).get()) {
mPhysOutputs.erase(I);
break;
}
}
}
void AudioHardwareOutput::updateTgtDevices_l() {
// ASSERT(holding mStreamLock)
uint32_t mcMask = 0;
uint32_t mainMask = 0;
{
Mutex::Autolock _l(mSettingsLock);
if (mSettings.hdmi.allowed && mHDMIConnected) {
if (NULL != mMCOutput)
mcMask |= HDMIAudioOutput::classDevMask();
else
mainMask |= HDMIAudioOutput::classDevMask();
}
}
if (NULL != mMainOutput)
mMainOutput->setTgtDevices(mainMask);
if (NULL != mMCOutput)
mMCOutput->setTgtDevices(mcMask);
}
void AudioHardwareOutput::standbyStatusUpdate(bool isInStandby, bool isMCStream) {
Mutex::Autolock _l1(mStreamLock);
bool hdmiAllowed;
{
Mutex::Autolock _l2(mSettingsLock);
hdmiAllowed = mSettings.hdmi.allowed;
}
// If there is no HDMI, do nothing
if (hdmiAllowed && mHDMIConnected) {
// If a multi-channel stream goes to standy state, we must switch
// to stereo stream. If MC comes out of standby, we must switch
// back to MC. No special processing needed for main stream.
// AudioStreamOut class handles that correctly
if (isMCStream) {
uint32_t mcMask;
uint32_t mainMask;
if (isInStandby) {
mainMask = HDMIAudioOutput::classDevMask();
mcMask = 0;
} else {
mainMask = 0;
mcMask = HDMIAudioOutput::classDevMask();
}
if (NULL != mMainOutput)
mMainOutput->setTgtDevices(mainMask);
if (NULL != mMCOutput)
mMCOutput->setTgtDevices(mcMask);
}
}
}
#define DUMP(a...) \
snprintf(buffer, SIZE, a); \
buffer[SIZE - 1] = 0; \
result.append(buffer);
#define B2STR(b) b ? "true" : "false"
status_t AudioHardwareOutput::dump(int fd)
{
const size_t SIZE = 256;
char buffer[SIZE];
String8 result;
Settings s;
// Explicit scope for auto-lock pattern.
{
// Snapshot the current settings so we don't have to hold the settings
// lock while formatting the results.
Mutex::Autolock _l(mSettingsLock);
s = mSettings;
}
DUMP("AudioHardwareOutput::dump\n");
DUMP("\tMaster Volume : %0.3f\n", s.masterVolume);
DUMP("\tMaster Mute : %s\n", B2STR(s.masterMute));
DUMP("\tHDMI Output Allowed : %s\n", B2STR(s.hdmi.allowed));
DUMP("\tHDMI Delay Comp : %u uSec\n", s.hdmi.delayCompUsec);
DUMP("\tHDMI Output Fixed : %s\n", B2STR(s.hdmi.isFixed));
DUMP("\tHDMI Fixed Level : %.1f dB\n", s.hdmi.fixedLvl);
DUMP("\tVideo Delay Comp : %u uSec\n", s.videoDelayCompUsec);
::write(fd, result.string(), result.size());
// Explicit scope for auto-lock pattern.
{
Mutex::Autolock _l(mOutputLock);
if (mMainOutput)
mMainOutput->dump(fd);
if (mMCOutput)
mMCOutput->dump(fd);
}
return NO_ERROR;
}
#undef B2STR
#undef DUMP
}; // namespace android