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