diff options
Diffstat (limited to 'services/audiopolicy/common/managerdefinitions/src')
13 files changed, 3524 insertions, 0 deletions
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioGain.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioGain.cpp new file mode 100644 index 0000000..fc7b0cc --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/AudioGain.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2015 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 "APM::AudioGain" +//#define LOG_NDEBUG 0 + +//#define VERY_VERBOSE_LOGGING +#ifdef VERY_VERBOSE_LOGGING +#define ALOGVV ALOGV +#else +#define ALOGVV(a...) do { } while(0) +#endif + +#include "AudioGain.h" +#include "StreamDescriptor.h" +#include <utils/Log.h> +#include <utils/String8.h> + +#include <math.h> + +namespace android { + +AudioGain::AudioGain(int index, bool useInChannelMask) +{ + mIndex = index; + mUseInChannelMask = useInChannelMask; + memset(&mGain, 0, sizeof(struct audio_gain)); +} + +void AudioGain::getDefaultConfig(struct audio_gain_config *config) +{ + config->index = mIndex; + config->mode = mGain.mode; + config->channel_mask = mGain.channel_mask; + if ((mGain.mode & AUDIO_GAIN_MODE_JOINT) == AUDIO_GAIN_MODE_JOINT) { + config->values[0] = mGain.default_value; + } else { + uint32_t numValues; + if (mUseInChannelMask) { + numValues = audio_channel_count_from_in_mask(mGain.channel_mask); + } else { + numValues = audio_channel_count_from_out_mask(mGain.channel_mask); + } + for (size_t i = 0; i < numValues; i++) { + config->values[i] = mGain.default_value; + } + } + if ((mGain.mode & AUDIO_GAIN_MODE_RAMP) == AUDIO_GAIN_MODE_RAMP) { + config->ramp_duration_ms = mGain.min_ramp_ms; + } +} + +status_t AudioGain::checkConfig(const struct audio_gain_config *config) +{ + if ((config->mode & ~mGain.mode) != 0) { + return BAD_VALUE; + } + if ((config->mode & AUDIO_GAIN_MODE_JOINT) == AUDIO_GAIN_MODE_JOINT) { + if ((config->values[0] < mGain.min_value) || + (config->values[0] > mGain.max_value)) { + return BAD_VALUE; + } + } else { + if ((config->channel_mask & ~mGain.channel_mask) != 0) { + return BAD_VALUE; + } + uint32_t numValues; + if (mUseInChannelMask) { + numValues = audio_channel_count_from_in_mask(config->channel_mask); + } else { + numValues = audio_channel_count_from_out_mask(config->channel_mask); + } + for (size_t i = 0; i < numValues; i++) { + if ((config->values[i] < mGain.min_value) || + (config->values[i] > mGain.max_value)) { + return BAD_VALUE; + } + } + } + if ((config->mode & AUDIO_GAIN_MODE_RAMP) == AUDIO_GAIN_MODE_RAMP) { + if ((config->ramp_duration_ms < mGain.min_ramp_ms) || + (config->ramp_duration_ms > mGain.max_ramp_ms)) { + return BAD_VALUE; + } + } + return NO_ERROR; +} + +void AudioGain::dump(int fd, int spaces, int index) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "%*sGain %d:\n", spaces, "", index+1); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- mode: %08x\n", spaces, "", mGain.mode); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- channel_mask: %08x\n", spaces, "", mGain.channel_mask); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- min_value: %d mB\n", spaces, "", mGain.min_value); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- max_value: %d mB\n", spaces, "", mGain.max_value); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- default_value: %d mB\n", spaces, "", mGain.default_value); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- step_value: %d mB\n", spaces, "", mGain.step_value); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- min_ramp_ms: %d ms\n", spaces, "", mGain.min_ramp_ms); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- max_ramp_ms: %d ms\n", spaces, "", mGain.max_ramp_ms); + result.append(buffer); + + write(fd, result.string(), result.size()); +} + +}; // namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioInputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioInputDescriptor.cpp new file mode 100644 index 0000000..937160b --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/AudioInputDescriptor.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2015 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 "APM::AudioInputDescriptor" +//#define LOG_NDEBUG 0 + +#include "AudioInputDescriptor.h" +#include "IOProfile.h" +#include "AudioGain.h" +#include "HwModule.h" +#include <media/AudioPolicy.h> +#include <policy.h> + +namespace android { + +AudioInputDescriptor::AudioInputDescriptor(const sp<IOProfile>& profile) + : mIoHandle(0), + mDevice(AUDIO_DEVICE_NONE), mPolicyMix(NULL), mPatchHandle(0), mRefCount(0), + mInputSource(AUDIO_SOURCE_DEFAULT), mProfile(profile), mIsSoundTrigger(false), mId(0) +{ + if (profile != NULL) { + mSamplingRate = profile->pickSamplingRate(); + mFormat = profile->pickFormat(); + mChannelMask = profile->pickChannelMask(); + if (profile->mGains.size() > 0) { + profile->mGains[0]->getDefaultConfig(&mGain); + } + } +} + +void AudioInputDescriptor::setIoHandle(audio_io_handle_t ioHandle) +{ + mId = AudioPort::getNextUniqueId(); + mIoHandle = ioHandle; +} + +audio_module_handle_t AudioInputDescriptor::getModuleHandle() const +{ + if (mProfile == 0) { + return 0; + } + return mProfile->getModuleHandle(); +} + +audio_port_handle_t AudioInputDescriptor::getId() const +{ + return mId; +} + +void AudioInputDescriptor::toAudioPortConfig(struct audio_port_config *dstConfig, + const struct audio_port_config *srcConfig) const +{ + ALOG_ASSERT(mProfile != 0, + "toAudioPortConfig() called on input with null profile %d", mIoHandle); + dstConfig->config_mask = AUDIO_PORT_CONFIG_SAMPLE_RATE|AUDIO_PORT_CONFIG_CHANNEL_MASK| + AUDIO_PORT_CONFIG_FORMAT|AUDIO_PORT_CONFIG_GAIN; + if (srcConfig != NULL) { + dstConfig->config_mask |= srcConfig->config_mask; + } + + AudioPortConfig::toAudioPortConfig(dstConfig, srcConfig); + + dstConfig->id = mId; + dstConfig->role = AUDIO_PORT_ROLE_SINK; + dstConfig->type = AUDIO_PORT_TYPE_MIX; + dstConfig->ext.mix.hw_module = getModuleHandle(); + dstConfig->ext.mix.handle = mIoHandle; + dstConfig->ext.mix.usecase.source = mInputSource; +} + +void AudioInputDescriptor::toAudioPort(struct audio_port *port) const +{ + ALOG_ASSERT(mProfile != 0, "toAudioPort() called on input with null profile %d", mIoHandle); + + mProfile->toAudioPort(port); + port->id = mId; + toAudioPortConfig(&port->active_config); + port->ext.mix.hw_module = getModuleHandle(); + port->ext.mix.handle = mIoHandle; + port->ext.mix.latency_class = AUDIO_LATENCY_NORMAL; +} + +status_t AudioInputDescriptor::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, " ID: %d\n", getId()); + result.append(buffer); + snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate); + result.append(buffer); + snprintf(buffer, SIZE, " Format: %d\n", mFormat); + result.append(buffer); + snprintf(buffer, SIZE, " Channels: %08x\n", mChannelMask); + result.append(buffer); + snprintf(buffer, SIZE, " Devices %08x\n", mDevice); + result.append(buffer); + snprintf(buffer, SIZE, " Ref Count %d\n", mRefCount); + result.append(buffer); + snprintf(buffer, SIZE, " Open Ref Count %d\n", mOpenRefCount); + result.append(buffer); + + write(fd, result.string(), result.size()); + + return NO_ERROR; +} + +bool AudioInputCollection::isSourceActive(audio_source_t source) const +{ + for (size_t i = 0; i < size(); i++) { + const sp<AudioInputDescriptor> inputDescriptor = valueAt(i); + if (inputDescriptor->mRefCount == 0) { + continue; + } + if (inputDescriptor->mInputSource == (int)source) { + return true; + } + } + return false; +} + +sp<AudioInputDescriptor> AudioInputCollection::getInputFromId(audio_port_handle_t id) const +{ + sp<AudioInputDescriptor> inputDesc = NULL; + for (size_t i = 0; i < size(); i++) { + inputDesc = valueAt(i); + if (inputDesc->getId() == id) { + break; + } + } + return inputDesc; +} + +uint32_t AudioInputCollection::activeInputsCount() const +{ + uint32_t count = 0; + for (size_t i = 0; i < size(); i++) { + const sp<AudioInputDescriptor> desc = valueAt(i); + if (desc->mRefCount > 0) { + count++; + } + } + return count; +} + +audio_io_handle_t AudioInputCollection::getActiveInput(bool ignoreVirtualInputs) +{ + for (size_t i = 0; i < size(); i++) { + const sp<AudioInputDescriptor> input_descriptor = valueAt(i); + if ((input_descriptor->mRefCount > 0) + && (!ignoreVirtualInputs || !is_virtual_input_device(input_descriptor->mDevice))) { + return keyAt(i); + } + } + return 0; +} + +audio_devices_t AudioInputCollection::getSupportedDevices(audio_io_handle_t handle) const +{ + sp<AudioInputDescriptor> inputDesc = valueFor(handle); + audio_devices_t devices = inputDesc->mProfile->mSupportedDevices.types(); + return devices; +} + +status_t AudioInputCollection::dump(int fd) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "\nInputs dump:\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < size(); i++) { + snprintf(buffer, SIZE, "- Input %d dump:\n", keyAt(i)); + write(fd, buffer, strlen(buffer)); + valueAt(i)->dump(fd); + } + + return NO_ERROR; +} + +}; //namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp new file mode 100644 index 0000000..596aa1d --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2015 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 "APM::AudioOutputDescriptor" +//#define LOG_NDEBUG 0 + +#include <AudioPolicyInterface.h> +#include "AudioOutputDescriptor.h" +#include "IOProfile.h" +#include "AudioGain.h" +#include "Volume.h" +#include "HwModule.h" +#include <media/AudioPolicy.h> + +// A device mask for all audio output devices that are considered "remote" when evaluating +// active output devices in isStreamActiveRemotely() +#define APM_AUDIO_OUT_DEVICE_REMOTE_ALL AUDIO_DEVICE_OUT_REMOTE_SUBMIX + +namespace android { + +AudioOutputDescriptor::AudioOutputDescriptor(const sp<AudioPort>& port, + AudioPolicyClientInterface *clientInterface) + : mPort(port), mDevice(AUDIO_DEVICE_NONE), + mPatchHandle(0), mClientInterface(clientInterface), mId(0) +{ + // clear usage count for all stream types + for (int i = 0; i < AUDIO_STREAM_CNT; i++) { + mRefCount[i] = 0; + mCurVolume[i] = -1.0; + mMuteCount[i] = 0; + mStopTime[i] = 0; + } + for (int i = 0; i < NUM_STRATEGIES; i++) { + mStrategyMutedByDevice[i] = false; + } + if (port != NULL) { + mSamplingRate = port->pickSamplingRate(); + mFormat = port->pickFormat(); + mChannelMask = port->pickChannelMask(); + if (port->mGains.size() > 0) { + port->mGains[0]->getDefaultConfig(&mGain); + } + } +} + +audio_module_handle_t AudioOutputDescriptor::getModuleHandle() const +{ + return mPort->getModuleHandle(); +} + +audio_port_handle_t AudioOutputDescriptor::getId() const +{ + return mId; +} + +audio_devices_t AudioOutputDescriptor::device() const +{ + return mDevice; +} + +audio_devices_t AudioOutputDescriptor::supportedDevices() +{ + return mDevice; +} + +bool AudioOutputDescriptor::sharesHwModuleWith( + const sp<AudioOutputDescriptor> outputDesc) +{ + if (outputDesc->isDuplicated()) { + return sharesHwModuleWith(outputDesc->subOutput1()) || + sharesHwModuleWith(outputDesc->subOutput2()); + } else { + return (getModuleHandle() == outputDesc->getModuleHandle()); + } +} + +void AudioOutputDescriptor::changeRefCount(audio_stream_type_t stream, + int delta) +{ + if ((delta + (int)mRefCount[stream]) < 0) { + ALOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", + delta, stream, mRefCount[stream]); + mRefCount[stream] = 0; + return; + } + mRefCount[stream] += delta; + ALOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]); +} + +bool AudioOutputDescriptor::isActive(uint32_t inPastMs) const +{ + nsecs_t sysTime = 0; + if (inPastMs != 0) { + sysTime = systemTime(); + } + for (int i = 0; i < (int)AUDIO_STREAM_CNT; i++) { + if (i == AUDIO_STREAM_PATCH) { + continue; + } + if (isStreamActive((audio_stream_type_t)i, inPastMs, sysTime)) { + return true; + } + } + return false; +} + +bool AudioOutputDescriptor::isStreamActive(audio_stream_type_t stream, + uint32_t inPastMs, + nsecs_t sysTime) const +{ + if (mRefCount[stream] != 0) { + return true; + } + if (inPastMs == 0) { + return false; + } + if (sysTime == 0) { + sysTime = systemTime(); + } + if (ns2ms(sysTime - mStopTime[stream]) < inPastMs) { + return true; + } + return false; +} + + +bool AudioOutputDescriptor::isFixedVolume(audio_devices_t device __unused) +{ + return false; +} + +bool AudioOutputDescriptor::setVolume(float volume, + audio_stream_type_t stream, + audio_devices_t device __unused, + uint32_t delayMs, + bool force) +{ + // We actually change the volume if: + // - the float value returned by computeVolume() changed + // - the force flag is set + if (volume != mCurVolume[stream] || force) { + ALOGV("setVolume() for stream %d, volume %f, delay %d", stream, volume, delayMs); + mCurVolume[stream] = volume; + return true; + } + return false; +} + +void AudioOutputDescriptor::toAudioPortConfig( + struct audio_port_config *dstConfig, + const struct audio_port_config *srcConfig) const +{ + dstConfig->config_mask = AUDIO_PORT_CONFIG_SAMPLE_RATE|AUDIO_PORT_CONFIG_CHANNEL_MASK| + AUDIO_PORT_CONFIG_FORMAT|AUDIO_PORT_CONFIG_GAIN; + if (srcConfig != NULL) { + dstConfig->config_mask |= srcConfig->config_mask; + } + AudioPortConfig::toAudioPortConfig(dstConfig, srcConfig); + + dstConfig->id = mId; + dstConfig->role = AUDIO_PORT_ROLE_SOURCE; + dstConfig->type = AUDIO_PORT_TYPE_MIX; + dstConfig->ext.mix.hw_module = getModuleHandle(); + dstConfig->ext.mix.usecase.stream = AUDIO_STREAM_DEFAULT; +} + +void AudioOutputDescriptor::toAudioPort( + struct audio_port *port) const +{ + mPort->toAudioPort(port); + port->id = mId; + port->ext.mix.hw_module = getModuleHandle(); +} + +status_t AudioOutputDescriptor::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, " ID: %d\n", mId); + result.append(buffer); + snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate); + result.append(buffer); + snprintf(buffer, SIZE, " Format: %08x\n", mFormat); + result.append(buffer); + snprintf(buffer, SIZE, " Channels: %08x\n", mChannelMask); + result.append(buffer); + snprintf(buffer, SIZE, " Devices %08x\n", device()); + result.append(buffer); + snprintf(buffer, SIZE, " Stream volume refCount muteCount\n"); + result.append(buffer); + for (int i = 0; i < (int)AUDIO_STREAM_CNT; i++) { + snprintf(buffer, SIZE, " %02d %.03f %02d %02d\n", + i, mCurVolume[i], mRefCount[i], mMuteCount[i]); + result.append(buffer); + } + write(fd, result.string(), result.size()); + + return NO_ERROR; +} + +void AudioOutputDescriptor::log(const char* indent) +{ + ALOGI("%sID: %d,0x%X, [rt:%d fmt:0x%X ch:0x%X]", + indent, mId, mId, mSamplingRate, mFormat, mChannelMask); +} + +// SwAudioOutputDescriptor implementation +SwAudioOutputDescriptor::SwAudioOutputDescriptor( + const sp<IOProfile>& profile, AudioPolicyClientInterface *clientInterface) + : AudioOutputDescriptor(profile, clientInterface), + mProfile(profile), mIoHandle(0), mLatency(0), + mFlags((audio_output_flags_t)0), mPolicyMix(NULL), + mOutput1(0), mOutput2(0), mDirectOpenCount(0) +{ + if (profile != NULL) { + mFlags = (audio_output_flags_t)profile->mFlags; + } +} + +void SwAudioOutputDescriptor::setIoHandle(audio_io_handle_t ioHandle) +{ + mId = AudioPort::getNextUniqueId(); + mIoHandle = ioHandle; +} + + +status_t SwAudioOutputDescriptor::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, " Latency: %d\n", mLatency); + result.append(buffer); + snprintf(buffer, SIZE, " Flags %08x\n", mFlags); + result.append(buffer); + write(fd, result.string(), result.size()); + + AudioOutputDescriptor::dump(fd); + + return NO_ERROR; +} + +audio_devices_t SwAudioOutputDescriptor::device() const +{ + if (isDuplicated()) { + return (audio_devices_t)(mOutput1->mDevice | mOutput2->mDevice); + } else { + return mDevice; + } +} + +bool SwAudioOutputDescriptor::sharesHwModuleWith( + const sp<AudioOutputDescriptor> outputDesc) +{ + if (isDuplicated()) { + return mOutput1->sharesHwModuleWith(outputDesc) || mOutput2->sharesHwModuleWith(outputDesc); + } else if (outputDesc->isDuplicated()){ + return sharesHwModuleWith(outputDesc->subOutput1()) || + sharesHwModuleWith(outputDesc->subOutput2()); + } else { + return AudioOutputDescriptor::sharesHwModuleWith(outputDesc); + } +} + +audio_devices_t SwAudioOutputDescriptor::supportedDevices() +{ + if (isDuplicated()) { + return (audio_devices_t)(mOutput1->supportedDevices() | mOutput2->supportedDevices()); + } else { + return mProfile->mSupportedDevices.types() ; + } +} + +uint32_t SwAudioOutputDescriptor::latency() +{ + if (isDuplicated()) { + return (mOutput1->mLatency > mOutput2->mLatency) ? mOutput1->mLatency : mOutput2->mLatency; + } else { + return mLatency; + } +} + +void SwAudioOutputDescriptor::changeRefCount(audio_stream_type_t stream, + int delta) +{ + // forward usage count change to attached outputs + if (isDuplicated()) { + mOutput1->changeRefCount(stream, delta); + mOutput2->changeRefCount(stream, delta); + } + AudioOutputDescriptor::changeRefCount(stream, delta); +} + + +bool SwAudioOutputDescriptor::isFixedVolume(audio_devices_t device) +{ + // unit gain if rerouting to external policy + if (device == AUDIO_DEVICE_OUT_REMOTE_SUBMIX) { + if (mPolicyMix != NULL) { + ALOGV("max gain when rerouting for output=%d", mIoHandle); + return true; + } + } + return false; +} + +void SwAudioOutputDescriptor::toAudioPortConfig( + struct audio_port_config *dstConfig, + const struct audio_port_config *srcConfig) const +{ + + ALOG_ASSERT(!isDuplicated(), "toAudioPortConfig() called on duplicated output %d", mIoHandle); + AudioOutputDescriptor::toAudioPortConfig(dstConfig, srcConfig); + + dstConfig->ext.mix.handle = mIoHandle; +} + +void SwAudioOutputDescriptor::toAudioPort( + struct audio_port *port) const +{ + ALOG_ASSERT(!isDuplicated(), "toAudioPort() called on duplicated output %d", mIoHandle); + + AudioOutputDescriptor::toAudioPort(port); + + toAudioPortConfig(&port->active_config); + port->ext.mix.handle = mIoHandle; + port->ext.mix.latency_class = + mFlags & AUDIO_OUTPUT_FLAG_FAST ? AUDIO_LATENCY_LOW : AUDIO_LATENCY_NORMAL; +} + +bool SwAudioOutputDescriptor::setVolume(float volume, + audio_stream_type_t stream, + audio_devices_t device, + uint32_t delayMs, + bool force) +{ + bool changed = AudioOutputDescriptor::setVolume(volume, stream, device, delayMs, force); + + if (changed) { + // Force VOICE_CALL to track BLUETOOTH_SCO stream volume when bluetooth audio is + // enabled + float volume = Volume::DbToAmpl(mCurVolume[stream]); + if (stream == AUDIO_STREAM_BLUETOOTH_SCO) { + mClientInterface->setStreamVolume( + AUDIO_STREAM_VOICE_CALL, volume, mIoHandle, delayMs); + } + mClientInterface->setStreamVolume(stream, volume, mIoHandle, delayMs); + } + return changed; +} + +// SwAudioOutputCollection implementation + +bool SwAudioOutputCollection::isStreamActive(audio_stream_type_t stream, uint32_t inPastMs) const +{ + nsecs_t sysTime = systemTime(); + for (size_t i = 0; i < this->size(); i++) { + const sp<SwAudioOutputDescriptor> outputDesc = this->valueAt(i); + if (outputDesc->isStreamActive(stream, inPastMs, sysTime)) { + return true; + } + } + return false; +} + +bool SwAudioOutputCollection::isStreamActiveRemotely(audio_stream_type_t stream, + uint32_t inPastMs) const +{ + nsecs_t sysTime = systemTime(); + for (size_t i = 0; i < size(); i++) { + const sp<SwAudioOutputDescriptor> outputDesc = valueAt(i); + if (((outputDesc->device() & APM_AUDIO_OUT_DEVICE_REMOTE_ALL) != 0) && + outputDesc->isStreamActive(stream, inPastMs, sysTime)) { + // do not consider re routing (when the output is going to a dynamic policy) + // as "remote playback" + if (outputDesc->mPolicyMix == NULL) { + return true; + } + } + } + return false; +} + +audio_io_handle_t SwAudioOutputCollection::getA2dpOutput() const +{ + for (size_t i = 0; i < size(); i++) { + sp<SwAudioOutputDescriptor> outputDesc = valueAt(i); + if (!outputDesc->isDuplicated() && outputDesc->device() & AUDIO_DEVICE_OUT_ALL_A2DP) { + return this->keyAt(i); + } + } + return 0; +} + +sp<SwAudioOutputDescriptor> SwAudioOutputCollection::getPrimaryOutput() const +{ + for (size_t i = 0; i < size(); i++) { + const sp<SwAudioOutputDescriptor> outputDesc = valueAt(i); + if (outputDesc->mFlags & AUDIO_OUTPUT_FLAG_PRIMARY) { + return outputDesc; + } + } + return NULL; +} + +sp<SwAudioOutputDescriptor> SwAudioOutputCollection::getOutputFromId(audio_port_handle_t id) const +{ + sp<SwAudioOutputDescriptor> outputDesc = NULL; + for (size_t i = 0; i < size(); i++) { + outputDesc = valueAt(i); + if (outputDesc->getId() == id) { + break; + } + } + return outputDesc; +} + +bool SwAudioOutputCollection::isAnyOutputActive(audio_stream_type_t streamToIgnore) const +{ + for (size_t s = 0 ; s < AUDIO_STREAM_CNT ; s++) { + if (s == (size_t) streamToIgnore) { + continue; + } + for (size_t i = 0; i < size(); i++) { + const sp<SwAudioOutputDescriptor> outputDesc = valueAt(i); + if (outputDesc->mRefCount[s] != 0) { + return true; + } + } + } + return false; +} + +audio_devices_t SwAudioOutputCollection::getSupportedDevices(audio_io_handle_t handle) const +{ + sp<SwAudioOutputDescriptor> outputDesc = valueFor(handle); + audio_devices_t devices = outputDesc->mProfile->mSupportedDevices.types(); + return devices; +} + + +status_t SwAudioOutputCollection::dump(int fd) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "\nOutputs dump:\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < size(); i++) { + snprintf(buffer, SIZE, "- Output %d dump:\n", keyAt(i)); + write(fd, buffer, strlen(buffer)); + valueAt(i)->dump(fd); + } + + return NO_ERROR; +} + +}; //namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPatch.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPatch.cpp new file mode 100644 index 0000000..a06d867 --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/AudioPatch.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2015 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 "APM::AudioPatch" +//#define LOG_NDEBUG 0 + +#include "AudioPatch.h" +#include "AudioGain.h" +#include "ConfigParsingUtils.h" +#include <cutils/log.h> +#include <utils/String8.h> + +namespace android { + +int32_t volatile AudioPatch::mNextUniqueId = 1; + +AudioPatch::AudioPatch(const struct audio_patch *patch, uid_t uid) : + mHandle(static_cast<audio_patch_handle_t>(android_atomic_inc(&mNextUniqueId))), + mPatch(*patch), + mUid(uid), + mAfPatchHandle(0) +{ +} + +status_t AudioPatch::dump(int fd, int spaces, int index) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "%*sAudio patch %d:\n", spaces, "", index+1); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- handle: %2d\n", spaces, "", mHandle); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- audio flinger handle: %2d\n", spaces, "", mAfPatchHandle); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- owner uid: %2d\n", spaces, "", mUid); + result.append(buffer); + snprintf(buffer, SIZE, "%*s- %d sources:\n", spaces, "", mPatch.num_sources); + result.append(buffer); + for (size_t i = 0; i < mPatch.num_sources; i++) { + if (mPatch.sources[i].type == AUDIO_PORT_TYPE_DEVICE) { + snprintf(buffer, SIZE, "%*s- Device ID %d %s\n", spaces + 2, "", + mPatch.sources[i].id, ConfigParsingUtils::enumToString(sDeviceTypeToEnumTable, + ARRAY_SIZE(sDeviceTypeToEnumTable), + mPatch.sources[i].ext.device.type)); + } else { + snprintf(buffer, SIZE, "%*s- Mix ID %d I/O handle %d\n", spaces + 2, "", + mPatch.sources[i].id, mPatch.sources[i].ext.mix.handle); + } + result.append(buffer); + } + snprintf(buffer, SIZE, "%*s- %d sinks:\n", spaces, "", mPatch.num_sinks); + result.append(buffer); + for (size_t i = 0; i < mPatch.num_sinks; i++) { + if (mPatch.sinks[i].type == AUDIO_PORT_TYPE_DEVICE) { + snprintf(buffer, SIZE, "%*s- Device ID %d %s\n", spaces + 2, "", + mPatch.sinks[i].id, ConfigParsingUtils::enumToString(sDeviceTypeToEnumTable, + ARRAY_SIZE(sDeviceTypeToEnumTable), + mPatch.sinks[i].ext.device.type)); + } else { + snprintf(buffer, SIZE, "%*s- Mix ID %d I/O handle %d\n", spaces + 2, "", + mPatch.sinks[i].id, mPatch.sinks[i].ext.mix.handle); + } + result.append(buffer); + } + + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioPatchCollection::addAudioPatch(audio_patch_handle_t handle, + const sp<AudioPatch>& patch) +{ + ssize_t index = indexOfKey(handle); + + if (index >= 0) { + ALOGW("addAudioPatch() patch %d already in", handle); + return ALREADY_EXISTS; + } + add(handle, patch); + ALOGV("addAudioPatch() handle %d af handle %d num_sources %d num_sinks %d source handle %d" + "sink handle %d", + handle, patch->mAfPatchHandle, patch->mPatch.num_sources, patch->mPatch.num_sinks, + patch->mPatch.sources[0].id, patch->mPatch.sinks[0].id); + return NO_ERROR; +} + +status_t AudioPatchCollection::removeAudioPatch(audio_patch_handle_t handle) +{ + ssize_t index = indexOfKey(handle); + + if (index < 0) { + ALOGW("removeAudioPatch() patch %d not in", handle); + return ALREADY_EXISTS; + } + ALOGV("removeAudioPatch() handle %d af handle %d", handle, valueAt(index)->mAfPatchHandle); + removeItemsAt(index); + return NO_ERROR; +} + +status_t AudioPatchCollection::listAudioPatches(unsigned int *num_patches, + struct audio_patch *patches) const +{ + if (num_patches == NULL || (*num_patches != 0 && patches == NULL)) { + return BAD_VALUE; + } + ALOGV("listAudioPatches() num_patches %d patches %p available patches %zu", + *num_patches, patches, size()); + if (patches == NULL) { + *num_patches = 0; + } + + size_t patchesWritten = 0; + size_t patchesMax = *num_patches; + for (size_t i = 0; i < size() && patchesWritten < patchesMax; i++) { + const sp<AudioPatch> patch = valueAt(i); + patches[patchesWritten] = patch->mPatch; + patches[patchesWritten++].id = patch->mHandle; + ALOGV("listAudioPatches() patch %zu num_sources %d num_sinks %d", + i, patch->mPatch.num_sources, patch->mPatch.num_sinks); + } + *num_patches = size(); + + ALOGV("listAudioPatches() got %zu patches needed %d", patchesWritten, *num_patches); + return NO_ERROR; +} + +status_t AudioPatchCollection::dump(int fd) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + snprintf(buffer, SIZE, "\nAudio Patches:\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < size(); i++) { + valueAt(i)->dump(fd, 2, i); + } + return NO_ERROR; +} + +}; // namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp new file mode 100644 index 0000000..77fc0b9 --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2015 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 "APM::AudioPolicyMix" +//#define LOG_NDEBUG 0 + +#include "AudioPolicyMix.h" +#include "HwModule.h" +#include "AudioPort.h" +#include "IOProfile.h" +#include "AudioGain.h" +#include <AudioOutputDescriptor.h> + +namespace android { + +void AudioPolicyMix::setOutput(sp<SwAudioOutputDescriptor> &output) +{ + mOutput = output; +} + +const sp<SwAudioOutputDescriptor> &AudioPolicyMix::getOutput() const +{ + return mOutput; +} + +void AudioPolicyMix::clearOutput() +{ + mOutput.clear(); +} + +void AudioPolicyMix::setMix(AudioMix &mix) +{ + mMix = mix; +} + +android::AudioMix *AudioPolicyMix::getMix() +{ + return &mMix; +} + +status_t AudioPolicyMixCollection::registerMix(String8 address, AudioMix mix) +{ + ssize_t index = indexOfKey(address); + if (index >= 0) { + ALOGE("registerPolicyMixes(): mix for address %s already registered", address.string()); + return BAD_VALUE; + } + sp<AudioPolicyMix> policyMix = new AudioPolicyMix(); + policyMix->setMix(mix); + add(address, policyMix); + return NO_ERROR; +} + +status_t AudioPolicyMixCollection::unregisterMix(String8 address) +{ + ssize_t index = indexOfKey(address); + if (index < 0) { + ALOGE("unregisterPolicyMixes(): mix for address %s not registered", address.string()); + return BAD_VALUE; + } + + removeItemsAt(index); + return NO_ERROR; +} + +status_t AudioPolicyMixCollection::getAudioPolicyMix(String8 address, + sp<AudioPolicyMix> &policyMix) const +{ + ssize_t index = indexOfKey(address); + if (index < 0) { + ALOGE("unregisterPolicyMixes(): mix for address %s not registered", address.string()); + return BAD_VALUE; + } + policyMix = valueAt(index); + return NO_ERROR; +} + +void AudioPolicyMixCollection::closeOutput(sp<SwAudioOutputDescriptor> &desc) +{ + for (size_t i = 0; i < size(); i++) { + sp<AudioPolicyMix> policyMix = valueAt(i); + if (policyMix->getOutput() == desc) { + policyMix->clearOutput(); + } + } +} + +status_t AudioPolicyMixCollection::getOutputForAttr(audio_attributes_t attributes, + sp<SwAudioOutputDescriptor> &desc) +{ + for (size_t i = 0; i < size(); i++) { + sp<AudioPolicyMix> policyMix = valueAt(i); + AudioMix *mix = policyMix->getMix(); + + if (mix->mMixType == MIX_TYPE_PLAYERS) { + for (size_t j = 0; j < mix->mCriteria.size(); j++) { + if ((RULE_MATCH_ATTRIBUTE_USAGE == mix->mCriteria[j].mRule && + mix->mCriteria[j].mAttr.mUsage == attributes.usage) || + (RULE_EXCLUDE_ATTRIBUTE_USAGE == mix->mCriteria[j].mRule && + mix->mCriteria[j].mAttr.mUsage != attributes.usage)) { + desc = policyMix->getOutput(); + break; + } + if (strncmp(attributes.tags, "addr=", strlen("addr=")) == 0 && + strncmp(attributes.tags + strlen("addr="), + mix->mRegistrationId.string(), + AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - strlen("addr=") - 1) == 0) { + desc = policyMix->getOutput(); + break; + } + } + } else if (mix->mMixType == MIX_TYPE_RECORDERS) { + if (attributes.usage == AUDIO_USAGE_VIRTUAL_SOURCE && + strncmp(attributes.tags, "addr=", strlen("addr=")) == 0 && + strncmp(attributes.tags + strlen("addr="), + mix->mRegistrationId.string(), + AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - strlen("addr=") - 1) == 0) { + desc = policyMix->getOutput(); + } + } + if (desc != 0) { + desc->mPolicyMix = mix; + return NO_ERROR; + } + } + return BAD_VALUE; +} + +audio_devices_t AudioPolicyMixCollection::getDeviceAndMixForInputSource(audio_source_t inputSource, + audio_devices_t availDevices, + AudioMix **policyMix) +{ + for (size_t i = 0; i < size(); i++) { + AudioMix *mix = valueAt(i)->getMix(); + + if (mix->mMixType != MIX_TYPE_RECORDERS) { + continue; + } + for (size_t j = 0; j < mix->mCriteria.size(); j++) { + if ((RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET == mix->mCriteria[j].mRule && + mix->mCriteria[j].mAttr.mSource == inputSource) || + (RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET == mix->mCriteria[j].mRule && + mix->mCriteria[j].mAttr.mSource != inputSource)) { + if (availDevices & AUDIO_DEVICE_IN_REMOTE_SUBMIX) { + if (policyMix != NULL) { + *policyMix = mix; + } + return AUDIO_DEVICE_IN_REMOTE_SUBMIX; + } + break; + } + } + } + return AUDIO_DEVICE_NONE; +} + +status_t AudioPolicyMixCollection::getInputMixForAttr(audio_attributes_t attr, AudioMix **policyMix) +{ + if (strncmp(attr.tags, "addr=", strlen("addr=")) != 0) { + return BAD_VALUE; + } + String8 address(attr.tags + strlen("addr=")); + + ssize_t index = indexOfKey(address); + if (index < 0) { + ALOGW("getInputForAttr() no policy for address %s", address.string()); + return BAD_VALUE; + } + sp<AudioPolicyMix> audioPolicyMix = valueAt(index); + AudioMix *mix = audioPolicyMix->getMix(); + + if (mix->mMixType != MIX_TYPE_PLAYERS) { + ALOGW("getInputForAttr() bad policy mix type for address %s", address.string()); + return BAD_VALUE; + } + *policyMix = mix; + return NO_ERROR; +} + +}; //namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp new file mode 100644 index 0000000..e8191dd --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2015 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 "APM::AudioPort" +//#define LOG_NDEBUG 0 + +#include "AudioPort.h" +#include "HwModule.h" +#include "AudioGain.h" +#include "ConfigParsingUtils.h" +#include "audio_policy_conf.h" +#include <policy.h> + +namespace android { + +int32_t volatile AudioPort::mNextUniqueId = 1; + +// --- AudioPort class implementation + +AudioPort::AudioPort(const String8& name, audio_port_type_t type, + audio_port_role_t role) : + mName(name), mType(type), mRole(role), mFlags(0) +{ + mUseInChannelMask = ((type == AUDIO_PORT_TYPE_DEVICE) && (role == AUDIO_PORT_ROLE_SOURCE)) || + ((type == AUDIO_PORT_TYPE_MIX) && (role == AUDIO_PORT_ROLE_SINK)); +} + +void AudioPort::attach(const sp<HwModule>& module) +{ + mModule = module; +} + +audio_port_handle_t AudioPort::getNextUniqueId() +{ + return static_cast<audio_port_handle_t>(android_atomic_inc(&mNextUniqueId)); +} + +audio_module_handle_t AudioPort::getModuleHandle() const +{ + if (mModule == 0) { + return 0; + } + return mModule->mHandle; +} + +uint32_t AudioPort::getModuleVersion() const +{ + if (mModule == 0) { + return 0; + } + return mModule->mHalVersion; +} + +const char *AudioPort::getModuleName() const +{ + if (mModule == 0) { + return ""; + } + return mModule->mName; +} + +void AudioPort::toAudioPort(struct audio_port *port) const +{ + port->role = mRole; + port->type = mType; + strlcpy(port->name, mName, AUDIO_PORT_MAX_NAME_LEN); + unsigned int i; + for (i = 0; i < mSamplingRates.size() && i < AUDIO_PORT_MAX_SAMPLING_RATES; i++) { + if (mSamplingRates[i] != 0) { + port->sample_rates[i] = mSamplingRates[i]; + } + } + port->num_sample_rates = i; + for (i = 0; i < mChannelMasks.size() && i < AUDIO_PORT_MAX_CHANNEL_MASKS; i++) { + if (mChannelMasks[i] != 0) { + port->channel_masks[i] = mChannelMasks[i]; + } + } + port->num_channel_masks = i; + for (i = 0; i < mFormats.size() && i < AUDIO_PORT_MAX_FORMATS; i++) { + if (mFormats[i] != 0) { + port->formats[i] = mFormats[i]; + } + } + port->num_formats = i; + + ALOGV("AudioPort::toAudioPort() num gains %zu", mGains.size()); + + for (i = 0; i < mGains.size() && i < AUDIO_PORT_MAX_GAINS; i++) { + port->gains[i] = mGains[i]->mGain; + } + port->num_gains = i; +} + +void AudioPort::importAudioPort(const sp<AudioPort> port) { + for (size_t k = 0 ; k < port->mSamplingRates.size() ; k++) { + const uint32_t rate = port->mSamplingRates.itemAt(k); + if (rate != 0) { // skip "dynamic" rates + bool hasRate = false; + for (size_t l = 0 ; l < mSamplingRates.size() ; l++) { + if (rate == mSamplingRates.itemAt(l)) { + hasRate = true; + break; + } + } + if (!hasRate) { // never import a sampling rate twice + mSamplingRates.add(rate); + } + } + } + for (size_t k = 0 ; k < port->mChannelMasks.size() ; k++) { + const audio_channel_mask_t mask = port->mChannelMasks.itemAt(k); + if (mask != 0) { // skip "dynamic" masks + bool hasMask = false; + for (size_t l = 0 ; l < mChannelMasks.size() ; l++) { + if (mask == mChannelMasks.itemAt(l)) { + hasMask = true; + break; + } + } + if (!hasMask) { // never import a channel mask twice + mChannelMasks.add(mask); + } + } + } + for (size_t k = 0 ; k < port->mFormats.size() ; k++) { + const audio_format_t format = port->mFormats.itemAt(k); + if (format != 0) { // skip "dynamic" formats + bool hasFormat = false; + for (size_t l = 0 ; l < mFormats.size() ; l++) { + if (format == mFormats.itemAt(l)) { + hasFormat = true; + break; + } + } + if (!hasFormat) { // never import a channel mask twice + mFormats.add(format); + } + } + } + for (size_t k = 0 ; k < port->mGains.size() ; k++) { + sp<AudioGain> gain = port->mGains.itemAt(k); + if (gain != 0) { + bool hasGain = false; + for (size_t l = 0 ; l < mGains.size() ; l++) { + if (gain == mGains.itemAt(l)) { + hasGain = true; + break; + } + } + if (!hasGain) { // never import a gain twice + mGains.add(gain); + } + } + } +} + +void AudioPort::clearCapabilities() { + mChannelMasks.clear(); + mFormats.clear(); + mSamplingRates.clear(); + mGains.clear(); +} + +void AudioPort::loadSamplingRates(char *name) +{ + char *str = strtok(name, "|"); + + // by convention, "0' in the first entry in mSamplingRates indicates the supported sampling + // rates should be read from the output stream after it is opened for the first time + if (str != NULL && strcmp(str, DYNAMIC_VALUE_TAG) == 0) { + mSamplingRates.add(0); + return; + } + + while (str != NULL) { + uint32_t rate = atoi(str); + if (rate != 0) { + ALOGV("loadSamplingRates() adding rate %d", rate); + mSamplingRates.add(rate); + } + str = strtok(NULL, "|"); + } +} + +void AudioPort::loadFormats(char *name) +{ + char *str = strtok(name, "|"); + + // by convention, "0' in the first entry in mFormats indicates the supported formats + // should be read from the output stream after it is opened for the first time + if (str != NULL && strcmp(str, DYNAMIC_VALUE_TAG) == 0) { + mFormats.add(AUDIO_FORMAT_DEFAULT); + return; + } + + while (str != NULL) { + audio_format_t format = (audio_format_t)ConfigParsingUtils::stringToEnum(sFormatNameToEnumTable, + ARRAY_SIZE(sFormatNameToEnumTable), + str); + if (format != AUDIO_FORMAT_DEFAULT) { + mFormats.add(format); + } + str = strtok(NULL, "|"); + } +} + +void AudioPort::loadInChannels(char *name) +{ + const char *str = strtok(name, "|"); + + ALOGV("loadInChannels() %s", name); + + if (str != NULL && strcmp(str, DYNAMIC_VALUE_TAG) == 0) { + mChannelMasks.add(0); + return; + } + + while (str != NULL) { + audio_channel_mask_t channelMask = + (audio_channel_mask_t)ConfigParsingUtils::stringToEnum(sInChannelsNameToEnumTable, + ARRAY_SIZE(sInChannelsNameToEnumTable), + str); + if (channelMask != 0) { + ALOGV("loadInChannels() adding channelMask %04x", channelMask); + mChannelMasks.add(channelMask); + } + str = strtok(NULL, "|"); + } +} + +void AudioPort::loadOutChannels(char *name) +{ + const char *str = strtok(name, "|"); + + ALOGV("loadOutChannels() %s", name); + + // by convention, "0' in the first entry in mChannelMasks indicates the supported channel + // masks should be read from the output stream after it is opened for the first time + if (str != NULL && strcmp(str, DYNAMIC_VALUE_TAG) == 0) { + mChannelMasks.add(0); + return; + } + + while (str != NULL) { + audio_channel_mask_t channelMask = + (audio_channel_mask_t)ConfigParsingUtils::stringToEnum(sOutChannelsNameToEnumTable, + ARRAY_SIZE(sOutChannelsNameToEnumTable), + str); + if (channelMask != 0) { + mChannelMasks.add(channelMask); + } + str = strtok(NULL, "|"); + } + return; +} + +audio_gain_mode_t AudioPort::loadGainMode(char *name) +{ + const char *str = strtok(name, "|"); + + ALOGV("loadGainMode() %s", name); + audio_gain_mode_t mode = 0; + while (str != NULL) { + mode |= (audio_gain_mode_t)ConfigParsingUtils::stringToEnum(sGainModeNameToEnumTable, + ARRAY_SIZE(sGainModeNameToEnumTable), + str); + str = strtok(NULL, "|"); + } + return mode; +} + +void AudioPort::loadGain(cnode *root, int index) +{ + cnode *node = root->first_child; + + sp<AudioGain> gain = new AudioGain(index, mUseInChannelMask); + + while (node) { + if (strcmp(node->name, GAIN_MODE) == 0) { + gain->mGain.mode = loadGainMode((char *)node->value); + } else if (strcmp(node->name, GAIN_CHANNELS) == 0) { + if (mUseInChannelMask) { + gain->mGain.channel_mask = + (audio_channel_mask_t)ConfigParsingUtils::stringToEnum(sInChannelsNameToEnumTable, + ARRAY_SIZE(sInChannelsNameToEnumTable), + (char *)node->value); + } else { + gain->mGain.channel_mask = + (audio_channel_mask_t)ConfigParsingUtils::stringToEnum(sOutChannelsNameToEnumTable, + ARRAY_SIZE(sOutChannelsNameToEnumTable), + (char *)node->value); + } + } else if (strcmp(node->name, GAIN_MIN_VALUE) == 0) { + gain->mGain.min_value = atoi((char *)node->value); + } else if (strcmp(node->name, GAIN_MAX_VALUE) == 0) { + gain->mGain.max_value = atoi((char *)node->value); + } else if (strcmp(node->name, GAIN_DEFAULT_VALUE) == 0) { + gain->mGain.default_value = atoi((char *)node->value); + } else if (strcmp(node->name, GAIN_STEP_VALUE) == 0) { + gain->mGain.step_value = atoi((char *)node->value); + } else if (strcmp(node->name, GAIN_MIN_RAMP_MS) == 0) { + gain->mGain.min_ramp_ms = atoi((char *)node->value); + } else if (strcmp(node->name, GAIN_MAX_RAMP_MS) == 0) { + gain->mGain.max_ramp_ms = atoi((char *)node->value); + } + node = node->next; + } + + ALOGV("loadGain() adding new gain mode %08x channel mask %08x min mB %d max mB %d", + gain->mGain.mode, gain->mGain.channel_mask, gain->mGain.min_value, gain->mGain.max_value); + + if (gain->mGain.mode == 0) { + return; + } + mGains.add(gain); +} + +void AudioPort::loadGains(cnode *root) +{ + cnode *node = root->first_child; + int index = 0; + while (node) { + ALOGV("loadGains() loading gain %s", node->name); + loadGain(node, index++); + node = node->next; + } +} + +status_t AudioPort::checkExactSamplingRate(uint32_t samplingRate) const +{ + if (mSamplingRates.isEmpty()) { + return NO_ERROR; + } + + for (size_t i = 0; i < mSamplingRates.size(); i ++) { + if (mSamplingRates[i] == samplingRate) { + return NO_ERROR; + } + } + return BAD_VALUE; +} + +status_t AudioPort::checkCompatibleSamplingRate(uint32_t samplingRate, + uint32_t *updatedSamplingRate) const +{ + if (mSamplingRates.isEmpty()) { + return NO_ERROR; + } + + // Search for the closest supported sampling rate that is above (preferred) + // or below (acceptable) the desired sampling rate, within a permitted ratio. + // The sampling rates do not need to be sorted in ascending order. + ssize_t maxBelow = -1; + ssize_t minAbove = -1; + uint32_t candidate; + for (size_t i = 0; i < mSamplingRates.size(); i++) { + candidate = mSamplingRates[i]; + if (candidate == samplingRate) { + if (updatedSamplingRate != NULL) { + *updatedSamplingRate = candidate; + } + return NO_ERROR; + } + // candidate < desired + if (candidate < samplingRate) { + if (maxBelow < 0 || candidate > mSamplingRates[maxBelow]) { + maxBelow = i; + } + // candidate > desired + } else { + if (minAbove < 0 || candidate < mSamplingRates[minAbove]) { + minAbove = i; + } + } + } + // This uses hard-coded knowledge about AudioFlinger resampling ratios. + // TODO Move these assumptions out. + static const uint32_t kMaxDownSampleRatio = 6; // beyond this aliasing occurs + static const uint32_t kMaxUpSampleRatio = 256; // beyond this sample rate inaccuracies occur + // due to approximation by an int32_t of the + // phase increments + // Prefer to down-sample from a higher sampling rate, as we get the desired frequency spectrum. + if (minAbove >= 0) { + candidate = mSamplingRates[minAbove]; + if (candidate / kMaxDownSampleRatio <= samplingRate) { + if (updatedSamplingRate != NULL) { + *updatedSamplingRate = candidate; + } + return NO_ERROR; + } + } + // But if we have to up-sample from a lower sampling rate, that's OK. + if (maxBelow >= 0) { + candidate = mSamplingRates[maxBelow]; + if (candidate * kMaxUpSampleRatio >= samplingRate) { + if (updatedSamplingRate != NULL) { + *updatedSamplingRate = candidate; + } + return NO_ERROR; + } + } + // leave updatedSamplingRate unmodified + return BAD_VALUE; +} + +status_t AudioPort::checkExactChannelMask(audio_channel_mask_t channelMask) const +{ + if (mChannelMasks.isEmpty()) { + return NO_ERROR; + } + + for (size_t i = 0; i < mChannelMasks.size(); i++) { + if (mChannelMasks[i] == channelMask) { + return NO_ERROR; + } + } + return BAD_VALUE; +} + +status_t AudioPort::checkCompatibleChannelMask(audio_channel_mask_t channelMask) + const +{ + if (mChannelMasks.isEmpty()) { + return NO_ERROR; + } + + const bool isRecordThread = mType == AUDIO_PORT_TYPE_MIX && mRole == AUDIO_PORT_ROLE_SINK; + for (size_t i = 0; i < mChannelMasks.size(); i ++) { + // FIXME Does not handle multi-channel automatic conversions yet + audio_channel_mask_t supported = mChannelMasks[i]; + if (supported == channelMask) { + return NO_ERROR; + } + if (isRecordThread) { + // This uses hard-coded knowledge that AudioFlinger can silently down-mix and up-mix. + // FIXME Abstract this out to a table. + if (((supported == AUDIO_CHANNEL_IN_FRONT_BACK || supported == AUDIO_CHANNEL_IN_STEREO) + && channelMask == AUDIO_CHANNEL_IN_MONO) || + (supported == AUDIO_CHANNEL_IN_MONO && (channelMask == AUDIO_CHANNEL_IN_FRONT_BACK + || channelMask == AUDIO_CHANNEL_IN_STEREO))) { + return NO_ERROR; + } + } + } + return BAD_VALUE; +} + +status_t AudioPort::checkFormat(audio_format_t format) const +{ + if (mFormats.isEmpty()) { + return NO_ERROR; + } + + for (size_t i = 0; i < mFormats.size(); i ++) { + if (mFormats[i] == format) { + return NO_ERROR; + } + } + return BAD_VALUE; +} + + +uint32_t AudioPort::pickSamplingRate() const +{ + // special case for uninitialized dynamic profile + if (mSamplingRates.size() == 1 && mSamplingRates[0] == 0) { + return 0; + } + + // For direct outputs, pick minimum sampling rate: this helps ensuring that the + // channel count / sampling rate combination chosen will be supported by the connected + // sink + if ((mType == AUDIO_PORT_TYPE_MIX) && (mRole == AUDIO_PORT_ROLE_SOURCE) && + (mFlags & (AUDIO_OUTPUT_FLAG_DIRECT | AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD))) { + uint32_t samplingRate = UINT_MAX; + for (size_t i = 0; i < mSamplingRates.size(); i ++) { + if ((mSamplingRates[i] < samplingRate) && (mSamplingRates[i] > 0)) { + samplingRate = mSamplingRates[i]; + } + } + return (samplingRate == UINT_MAX) ? 0 : samplingRate; + } + + uint32_t samplingRate = 0; + uint32_t maxRate = MAX_MIXER_SAMPLING_RATE; + + // For mixed output and inputs, use max mixer sampling rates. Do not + // limit sampling rate otherwise + if (mType != AUDIO_PORT_TYPE_MIX) { + maxRate = UINT_MAX; + } + for (size_t i = 0; i < mSamplingRates.size(); i ++) { + if ((mSamplingRates[i] > samplingRate) && (mSamplingRates[i] <= maxRate)) { + samplingRate = mSamplingRates[i]; + } + } + return samplingRate; +} + +audio_channel_mask_t AudioPort::pickChannelMask() const +{ + // special case for uninitialized dynamic profile + if (mChannelMasks.size() == 1 && mChannelMasks[0] == 0) { + return AUDIO_CHANNEL_NONE; + } + audio_channel_mask_t channelMask = AUDIO_CHANNEL_NONE; + + // For direct outputs, pick minimum channel count: this helps ensuring that the + // channel count / sampling rate combination chosen will be supported by the connected + // sink + if ((mType == AUDIO_PORT_TYPE_MIX) && (mRole == AUDIO_PORT_ROLE_SOURCE) && + (mFlags & (AUDIO_OUTPUT_FLAG_DIRECT | AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD))) { + uint32_t channelCount = UINT_MAX; + for (size_t i = 0; i < mChannelMasks.size(); i ++) { + uint32_t cnlCount; + if (mUseInChannelMask) { + cnlCount = audio_channel_count_from_in_mask(mChannelMasks[i]); + } else { + cnlCount = audio_channel_count_from_out_mask(mChannelMasks[i]); + } + if ((cnlCount < channelCount) && (cnlCount > 0)) { + channelMask = mChannelMasks[i]; + channelCount = cnlCount; + } + } + return channelMask; + } + + uint32_t channelCount = 0; + uint32_t maxCount = MAX_MIXER_CHANNEL_COUNT; + + // For mixed output and inputs, use max mixer channel count. Do not + // limit channel count otherwise + if (mType != AUDIO_PORT_TYPE_MIX) { + maxCount = UINT_MAX; + } + for (size_t i = 0; i < mChannelMasks.size(); i ++) { + uint32_t cnlCount; + if (mUseInChannelMask) { + cnlCount = audio_channel_count_from_in_mask(mChannelMasks[i]); + } else { + cnlCount = audio_channel_count_from_out_mask(mChannelMasks[i]); + } + if ((cnlCount > channelCount) && (cnlCount <= maxCount)) { + channelMask = mChannelMasks[i]; + channelCount = cnlCount; + } + } + return channelMask; +} + +/* format in order of increasing preference */ +const audio_format_t AudioPort::sPcmFormatCompareTable[] = { + AUDIO_FORMAT_DEFAULT, + AUDIO_FORMAT_PCM_16_BIT, + AUDIO_FORMAT_PCM_8_24_BIT, + AUDIO_FORMAT_PCM_24_BIT_PACKED, + AUDIO_FORMAT_PCM_32_BIT, + AUDIO_FORMAT_PCM_FLOAT, +}; + +int AudioPort::compareFormats(audio_format_t format1, + audio_format_t format2) +{ + // NOTE: AUDIO_FORMAT_INVALID is also considered not PCM and will be compared equal to any + // compressed format and better than any PCM format. This is by design of pickFormat() + if (!audio_is_linear_pcm(format1)) { + if (!audio_is_linear_pcm(format2)) { + return 0; + } + return 1; + } + if (!audio_is_linear_pcm(format2)) { + return -1; + } + + int index1 = -1, index2 = -1; + for (size_t i = 0; + (i < ARRAY_SIZE(sPcmFormatCompareTable)) && ((index1 == -1) || (index2 == -1)); + i ++) { + if (sPcmFormatCompareTable[i] == format1) { + index1 = i; + } + if (sPcmFormatCompareTable[i] == format2) { + index2 = i; + } + } + // format1 not found => index1 < 0 => format2 > format1 + // format2 not found => index2 < 0 => format2 < format1 + return index1 - index2; +} + +audio_format_t AudioPort::pickFormat() const +{ + // special case for uninitialized dynamic profile + if (mFormats.size() == 1 && mFormats[0] == 0) { + return AUDIO_FORMAT_DEFAULT; + } + + audio_format_t format = AUDIO_FORMAT_DEFAULT; + audio_format_t bestFormat = + AudioPort::sPcmFormatCompareTable[ + ARRAY_SIZE(AudioPort::sPcmFormatCompareTable) - 1]; + // For mixed output and inputs, use best mixer output format. Do not + // limit format otherwise + if ((mType != AUDIO_PORT_TYPE_MIX) || + ((mRole == AUDIO_PORT_ROLE_SOURCE) && + (((mFlags & (AUDIO_OUTPUT_FLAG_DIRECT | AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD)) != 0)))) { + bestFormat = AUDIO_FORMAT_INVALID; + } + + for (size_t i = 0; i < mFormats.size(); i ++) { + if ((compareFormats(mFormats[i], format) > 0) && + (compareFormats(mFormats[i], bestFormat) <= 0)) { + format = mFormats[i]; + } + } + return format; +} + +status_t AudioPort::checkGain(const struct audio_gain_config *gainConfig, + int index) const +{ + if (index < 0 || (size_t)index >= mGains.size()) { + return BAD_VALUE; + } + return mGains[index]->checkConfig(gainConfig); +} + +void AudioPort::dump(int fd, int spaces) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + if (mName.length() != 0) { + snprintf(buffer, SIZE, "%*s- name: %s\n", spaces, "", mName.string()); + result.append(buffer); + } + + if (mSamplingRates.size() != 0) { + snprintf(buffer, SIZE, "%*s- sampling rates: ", spaces, ""); + result.append(buffer); + for (size_t i = 0; i < mSamplingRates.size(); i++) { + if (i == 0 && mSamplingRates[i] == 0) { + snprintf(buffer, SIZE, "Dynamic"); + } else { + snprintf(buffer, SIZE, "%d", mSamplingRates[i]); + } + result.append(buffer); + result.append(i == (mSamplingRates.size() - 1) ? "" : ", "); + } + result.append("\n"); + } + + if (mChannelMasks.size() != 0) { + snprintf(buffer, SIZE, "%*s- channel masks: ", spaces, ""); + result.append(buffer); + for (size_t i = 0; i < mChannelMasks.size(); i++) { + ALOGV("AudioPort::dump mChannelMasks %zu %08x", i, mChannelMasks[i]); + + if (i == 0 && mChannelMasks[i] == 0) { + snprintf(buffer, SIZE, "Dynamic"); + } else { + snprintf(buffer, SIZE, "0x%04x", mChannelMasks[i]); + } + result.append(buffer); + result.append(i == (mChannelMasks.size() - 1) ? "" : ", "); + } + result.append("\n"); + } + + if (mFormats.size() != 0) { + snprintf(buffer, SIZE, "%*s- formats: ", spaces, ""); + result.append(buffer); + for (size_t i = 0; i < mFormats.size(); i++) { + const char *formatStr = ConfigParsingUtils::enumToString(sFormatNameToEnumTable, + ARRAY_SIZE(sFormatNameToEnumTable), + mFormats[i]); + if (i == 0 && strcmp(formatStr, "") == 0) { + snprintf(buffer, SIZE, "Dynamic"); + } else { + snprintf(buffer, SIZE, "%s", formatStr); + } + result.append(buffer); + result.append(i == (mFormats.size() - 1) ? "" : ", "); + } + result.append("\n"); + } + write(fd, result.string(), result.size()); + if (mGains.size() != 0) { + snprintf(buffer, SIZE, "%*s- gains:\n", spaces, ""); + write(fd, buffer, strlen(buffer) + 1); + for (size_t i = 0; i < mGains.size(); i++) { + mGains[i]->dump(fd, spaces + 2, i); + } + } +} + +void AudioPort::log(const char* indent) const +{ + ALOGI("%s Port[nm:%s, type:%d, role:%d]", indent, mName.string(), mType, mRole); +} + +// --- AudioPortConfig class implementation + +AudioPortConfig::AudioPortConfig() +{ + mSamplingRate = 0; + mChannelMask = AUDIO_CHANNEL_NONE; + mFormat = AUDIO_FORMAT_INVALID; + mGain.index = -1; +} + +status_t AudioPortConfig::applyAudioPortConfig( + const struct audio_port_config *config, + struct audio_port_config *backupConfig) +{ + struct audio_port_config localBackupConfig; + status_t status = NO_ERROR; + + localBackupConfig.config_mask = config->config_mask; + toAudioPortConfig(&localBackupConfig); + + sp<AudioPort> audioport = getAudioPort(); + if (audioport == 0) { + status = NO_INIT; + goto exit; + } + if (config->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) { + status = audioport->checkExactSamplingRate(config->sample_rate); + if (status != NO_ERROR) { + goto exit; + } + mSamplingRate = config->sample_rate; + } + if (config->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) { + status = audioport->checkExactChannelMask(config->channel_mask); + if (status != NO_ERROR) { + goto exit; + } + mChannelMask = config->channel_mask; + } + if (config->config_mask & AUDIO_PORT_CONFIG_FORMAT) { + status = audioport->checkFormat(config->format); + if (status != NO_ERROR) { + goto exit; + } + mFormat = config->format; + } + if (config->config_mask & AUDIO_PORT_CONFIG_GAIN) { + status = audioport->checkGain(&config->gain, config->gain.index); + if (status != NO_ERROR) { + goto exit; + } + mGain = config->gain; + } + +exit: + if (status != NO_ERROR) { + applyAudioPortConfig(&localBackupConfig); + } + if (backupConfig != NULL) { + *backupConfig = localBackupConfig; + } + return status; +} + +void AudioPortConfig::toAudioPortConfig(struct audio_port_config *dstConfig, + const struct audio_port_config *srcConfig) const +{ + if (dstConfig->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) { + dstConfig->sample_rate = mSamplingRate; + if ((srcConfig != NULL) && (srcConfig->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE)) { + dstConfig->sample_rate = srcConfig->sample_rate; + } + } else { + dstConfig->sample_rate = 0; + } + if (dstConfig->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) { + dstConfig->channel_mask = mChannelMask; + if ((srcConfig != NULL) && (srcConfig->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK)) { + dstConfig->channel_mask = srcConfig->channel_mask; + } + } else { + dstConfig->channel_mask = AUDIO_CHANNEL_NONE; + } + if (dstConfig->config_mask & AUDIO_PORT_CONFIG_FORMAT) { + dstConfig->format = mFormat; + if ((srcConfig != NULL) && (srcConfig->config_mask & AUDIO_PORT_CONFIG_FORMAT)) { + dstConfig->format = srcConfig->format; + } + } else { + dstConfig->format = AUDIO_FORMAT_INVALID; + } + if (dstConfig->config_mask & AUDIO_PORT_CONFIG_GAIN) { + dstConfig->gain = mGain; + if ((srcConfig != NULL) && (srcConfig->config_mask & AUDIO_PORT_CONFIG_GAIN)) { + dstConfig->gain = srcConfig->gain; + } + } else { + dstConfig->gain.index = -1; + } + if (dstConfig->gain.index != -1) { + dstConfig->config_mask |= AUDIO_PORT_CONFIG_GAIN; + } else { + dstConfig->config_mask &= ~AUDIO_PORT_CONFIG_GAIN; + } +} + +}; // namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/ConfigParsingUtils.cpp b/services/audiopolicy/common/managerdefinitions/src/ConfigParsingUtils.cpp new file mode 100644 index 0000000..9ab1d61 --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/ConfigParsingUtils.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2015 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 "APM::ConfigParsingUtils" +//#define LOG_NDEBUG 0 + +#include "ConfigParsingUtils.h" +#include "AudioGain.h" +#include <hardware/audio.h> +#include <utils/Log.h> +#include <cutils/misc.h> + +namespace android { + +//static +uint32_t ConfigParsingUtils::stringToEnum(const struct StringToEnum *table, + size_t size, + const char *name) +{ + for (size_t i = 0; i < size; i++) { + if (strcmp(table[i].name, name) == 0) { + ALOGV("stringToEnum() found %s", table[i].name); + return table[i].value; + } + } + return 0; +} + +//static +const char *ConfigParsingUtils::enumToString(const struct StringToEnum *table, + size_t size, + uint32_t value) +{ + for (size_t i = 0; i < size; i++) { + if (table[i].value == value) { + return table[i].name; + } + } + return ""; +} + +//static +bool ConfigParsingUtils::stringToBool(const char *value) +{ + return ((strcasecmp("true", value) == 0) || (strcmp("1", value) == 0)); +} + + +// --- audio_policy.conf file parsing +//static +uint32_t ConfigParsingUtils::parseOutputFlagNames(char *name) +{ + uint32_t flag = 0; + + // it is OK to cast name to non const here as we are not going to use it after + // strtok() modifies it + char *flagName = strtok(name, "|"); + while (flagName != NULL) { + if (strlen(flagName) != 0) { + flag |= ConfigParsingUtils::stringToEnum(sOutputFlagNameToEnumTable, + ARRAY_SIZE(sOutputFlagNameToEnumTable), + flagName); + } + flagName = strtok(NULL, "|"); + } + //force direct flag if offload flag is set: offloading implies a direct output stream + // and all common behaviors are driven by checking only the direct flag + // this should normally be set appropriately in the policy configuration file + if ((flag & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0) { + flag |= AUDIO_OUTPUT_FLAG_DIRECT; + } + + return flag; +} + +//static +uint32_t ConfigParsingUtils::parseInputFlagNames(char *name) +{ + uint32_t flag = 0; + + // it is OK to cast name to non const here as we are not going to use it after + // strtok() modifies it + char *flagName = strtok(name, "|"); + while (flagName != NULL) { + if (strlen(flagName) != 0) { + flag |= stringToEnum(sInputFlagNameToEnumTable, + ARRAY_SIZE(sInputFlagNameToEnumTable), + flagName); + } + flagName = strtok(NULL, "|"); + } + return flag; +} + +//static +audio_devices_t ConfigParsingUtils::parseDeviceNames(char *name) +{ + uint32_t device = 0; + + char *devName = strtok(name, "|"); + while (devName != NULL) { + if (strlen(devName) != 0) { + device |= stringToEnum(sDeviceTypeToEnumTable, + ARRAY_SIZE(sDeviceTypeToEnumTable), + devName); + } + devName = strtok(NULL, "|"); + } + return device; +} + +//static +void ConfigParsingUtils::loadHwModule(cnode *root, HwModuleCollection &hwModules, + DeviceVector &availableInputDevices, + DeviceVector &availableOutputDevices, + sp<DeviceDescriptor> &defaultOutputDevices, + bool &isSpeakerDrcEnable) +{ + status_t status = NAME_NOT_FOUND; + cnode *node; + sp<HwModule> module = new HwModule(root->name); + + node = config_find(root, DEVICES_TAG); + if (node != NULL) { + node = node->first_child; + while (node) { + ALOGV("loadHwModule() loading device %s", node->name); + status_t tmpStatus = module->loadDevice(node); + if (status == NAME_NOT_FOUND || status == NO_ERROR) { + status = tmpStatus; + } + node = node->next; + } + } + node = config_find(root, OUTPUTS_TAG); + if (node != NULL) { + node = node->first_child; + while (node) { + ALOGV("loadHwModule() loading output %s", node->name); + status_t tmpStatus = module->loadOutput(node); + if (status == NAME_NOT_FOUND || status == NO_ERROR) { + status = tmpStatus; + } + node = node->next; + } + } + node = config_find(root, INPUTS_TAG); + if (node != NULL) { + node = node->first_child; + while (node) { + ALOGV("loadHwModule() loading input %s", node->name); + status_t tmpStatus = module->loadInput(node); + if (status == NAME_NOT_FOUND || status == NO_ERROR) { + status = tmpStatus; + } + node = node->next; + } + } + loadGlobalConfig(root, module, availableInputDevices, availableOutputDevices, + defaultOutputDevices, isSpeakerDrcEnable); + + if (status == NO_ERROR) { + hwModules.add(module); + } +} + +//static +void ConfigParsingUtils::loadHwModules(cnode *root, HwModuleCollection &hwModules, + DeviceVector &availableInputDevices, + DeviceVector &availableOutputDevices, + sp<DeviceDescriptor> &defaultOutputDevices, + bool &isSpeakerDrcEnabled) +{ + cnode *node = config_find(root, AUDIO_HW_MODULE_TAG); + if (node == NULL) { + return; + } + + node = node->first_child; + while (node) { + ALOGV("loadHwModules() loading module %s", node->name); + loadHwModule(node, hwModules, availableInputDevices, availableOutputDevices, + defaultOutputDevices, isSpeakerDrcEnabled); + node = node->next; + } +} + +//static +void ConfigParsingUtils::loadGlobalConfig(cnode *root, const sp<HwModule>& module, + DeviceVector &availableInputDevices, + DeviceVector &availableOutputDevices, + sp<DeviceDescriptor> &defaultOutputDevice, + bool &speakerDrcEnabled) +{ + cnode *node = config_find(root, GLOBAL_CONFIG_TAG); + + if (node == NULL) { + return; + } + DeviceVector declaredDevices; + if (module != NULL) { + declaredDevices = module->mDeclaredDevices; + } + + node = node->first_child; + while (node) { + if (strcmp(ATTACHED_OUTPUT_DEVICES_TAG, node->name) == 0) { + availableOutputDevices.loadDevicesFromName((char *)node->value, + declaredDevices); + ALOGV("loadGlobalConfig() Attached Output Devices %08x", + availableOutputDevices.types()); + } else if (strcmp(DEFAULT_OUTPUT_DEVICE_TAG, node->name) == 0) { + audio_devices_t device = (audio_devices_t)stringToEnum( + sDeviceTypeToEnumTable, + ARRAY_SIZE(sDeviceTypeToEnumTable), + (char *)node->value); + if (device != AUDIO_DEVICE_NONE) { + defaultOutputDevice = new DeviceDescriptor(String8("default-output"), device); + } else { + ALOGW("loadGlobalConfig() default device not specified"); + } + ALOGV("loadGlobalConfig() mDefaultOutputDevice %08x", defaultOutputDevice->type()); + } else if (strcmp(ATTACHED_INPUT_DEVICES_TAG, node->name) == 0) { + availableInputDevices.loadDevicesFromName((char *)node->value, + declaredDevices); + ALOGV("loadGlobalConfig() Available InputDevices %08x", availableInputDevices.types()); + } else if (strcmp(SPEAKER_DRC_ENABLED_TAG, node->name) == 0) { + speakerDrcEnabled = stringToBool((char *)node->value); + ALOGV("loadGlobalConfig() mSpeakerDrcEnabled = %d", speakerDrcEnabled); + } else if (strcmp(AUDIO_HAL_VERSION_TAG, node->name) == 0) { + uint32_t major, minor; + sscanf((char *)node->value, "%u.%u", &major, &minor); + module->mHalVersion = HARDWARE_DEVICE_API_VERSION(major, minor); + ALOGV("loadGlobalConfig() mHalVersion = %04x major %u minor %u", + module->mHalVersion, major, minor); + } + node = node->next; + } +} + +//static +status_t ConfigParsingUtils::loadAudioPolicyConfig(const char *path, + HwModuleCollection &hwModules, + DeviceVector &availableInputDevices, + DeviceVector &availableOutputDevices, + sp<DeviceDescriptor> &defaultOutputDevices, + bool &isSpeakerDrcEnabled) +{ + cnode *root; + char *data; + + data = (char *)load_file(path, NULL); + if (data == NULL) { + return -ENODEV; + } + root = config_node("", ""); + config_load(root, data); + + loadHwModules(root, hwModules, + availableInputDevices, availableOutputDevices, + defaultOutputDevices, isSpeakerDrcEnabled); + // legacy audio_policy.conf files have one global_configuration section + loadGlobalConfig(root, hwModules.getModuleFromName(AUDIO_HARDWARE_MODULE_ID_PRIMARY), + availableInputDevices, availableOutputDevices, + defaultOutputDevices, isSpeakerDrcEnabled); + config_free(root); + free(root); + free(data); + + ALOGI("loadAudioPolicyConfig() loaded %s\n", path); + + return NO_ERROR; +} + +}; // namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/DeviceDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/DeviceDescriptor.cpp new file mode 100644 index 0000000..9573583 --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/DeviceDescriptor.cpp @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2015 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 "APM::Devices" +//#define LOG_NDEBUG 0 + +#include "DeviceDescriptor.h" +#include "AudioGain.h" +#include "HwModule.h" +#include "ConfigParsingUtils.h" + +namespace android { + +String8 DeviceDescriptor::emptyNameStr = String8(""); + +DeviceDescriptor::DeviceDescriptor(const String8& name, audio_devices_t type) : + AudioPort(name, AUDIO_PORT_TYPE_DEVICE, + audio_is_output_device(type) ? AUDIO_PORT_ROLE_SINK : + AUDIO_PORT_ROLE_SOURCE), + mAddress(""), mDeviceType(type), mId(0) +{ + +} + +audio_port_handle_t DeviceDescriptor::getId() const +{ + return mId; +} + +void DeviceDescriptor::attach(const sp<HwModule>& module) +{ + AudioPort::attach(module); + mId = getNextUniqueId(); +} + +bool DeviceDescriptor::equals(const sp<DeviceDescriptor>& other) const +{ + // Devices are considered equal if they: + // - are of the same type (a device type cannot be AUDIO_DEVICE_NONE) + // - have the same address or one device does not specify the address + // - have the same channel mask or one device does not specify the channel mask + return (mDeviceType == other->mDeviceType) && + (mAddress == "" || other->mAddress == "" || mAddress == other->mAddress) && + (mChannelMask == 0 || other->mChannelMask == 0 || + mChannelMask == other->mChannelMask); +} + +void DeviceDescriptor::loadGains(cnode *root) +{ + AudioPort::loadGains(root); + if (mGains.size() > 0) { + mGains[0]->getDefaultConfig(&mGain); + } +} + +void DeviceVector::refreshTypes() +{ + mDeviceTypes = AUDIO_DEVICE_NONE; + for(size_t i = 0; i < size(); i++) { + mDeviceTypes |= itemAt(i)->type(); + } + ALOGV("DeviceVector::refreshTypes() mDeviceTypes %08x", mDeviceTypes); +} + +ssize_t DeviceVector::indexOf(const sp<DeviceDescriptor>& item) const +{ + for(size_t i = 0; i < size(); i++) { + if (item->equals(itemAt(i))) { + return i; + } + } + return -1; +} + +ssize_t DeviceVector::add(const sp<DeviceDescriptor>& item) +{ + ssize_t ret = indexOf(item); + + if (ret < 0) { + ret = SortedVector::add(item); + if (ret >= 0) { + refreshTypes(); + } + } else { + ALOGW("DeviceVector::add device %08x already in", item->type()); + ret = -1; + } + return ret; +} + +ssize_t DeviceVector::remove(const sp<DeviceDescriptor>& item) +{ + size_t i; + ssize_t ret = indexOf(item); + + if (ret < 0) { + ALOGW("DeviceVector::remove device %08x not in", item->type()); + } else { + ret = SortedVector::removeAt(ret); + if (ret >= 0) { + refreshTypes(); + } + } + return ret; +} + +audio_devices_t DeviceVector::getDevicesFromHwModule(audio_module_handle_t moduleHandle) const +{ + audio_devices_t devices = AUDIO_DEVICE_NONE; + for (size_t i = 0; i < size(); i++) { + if (itemAt(i)->getModuleHandle() == moduleHandle) { + devices |= itemAt(i)->type(); + } + } + return devices; +} + +void DeviceVector::loadDevicesFromType(audio_devices_t types) +{ + DeviceVector deviceList; + + uint32_t role_bit = AUDIO_DEVICE_BIT_IN & types; + types &= ~role_bit; + + while (types) { + uint32_t i = 31 - __builtin_clz(types); + uint32_t type = 1 << i; + types &= ~type; + add(new DeviceDescriptor(String8("device_type"), type | role_bit)); + } +} + +void DeviceVector::loadDevicesFromName(char *name, + const DeviceVector& declaredDevices) +{ + char *devName = strtok(name, "|"); + while (devName != NULL) { + if (strlen(devName) != 0) { + audio_devices_t type = ConfigParsingUtils::stringToEnum(sDeviceTypeToEnumTable, + ARRAY_SIZE(sDeviceTypeToEnumTable), + devName); + if (type != AUDIO_DEVICE_NONE) { + devName = (char *)ConfigParsingUtils::enumToString(sDeviceNameToEnumTable, + ARRAY_SIZE(sDeviceNameToEnumTable), + type); + sp<DeviceDescriptor> dev = new DeviceDescriptor(String8(devName), type); + if (type == AUDIO_DEVICE_IN_REMOTE_SUBMIX || + type == AUDIO_DEVICE_OUT_REMOTE_SUBMIX ) { + dev->mAddress = String8("0"); + } + add(dev); + } else { + sp<DeviceDescriptor> deviceDesc = + declaredDevices.getDeviceFromName(String8(devName)); + if (deviceDesc != 0) { + add(deviceDesc); + } + } + } + devName = strtok(NULL, "|"); + } +} + +sp<DeviceDescriptor> DeviceVector::getDevice(audio_devices_t type, String8 address) const +{ + sp<DeviceDescriptor> device; + for (size_t i = 0; i < size(); i++) { + if (itemAt(i)->type() == type) { + if (address == "" || itemAt(i)->mAddress == address) { + device = itemAt(i); + if (itemAt(i)->mAddress == address) { + break; + } + } + } + } + ALOGV("DeviceVector::getDevice() for type %08x address %s found %p", + type, address.string(), device.get()); + return device; +} + +sp<DeviceDescriptor> DeviceVector::getDeviceFromId(audio_port_handle_t id) const +{ + sp<DeviceDescriptor> device; + for (size_t i = 0; i < size(); i++) { + if (itemAt(i)->getId() == id) { + device = itemAt(i); + break; + } + } + return device; +} + +DeviceVector DeviceVector::getDevicesFromType(audio_devices_t type) const +{ + DeviceVector devices; + bool isOutput = audio_is_output_devices(type); + type &= ~AUDIO_DEVICE_BIT_IN; + for (size_t i = 0; (i < size()) && (type != AUDIO_DEVICE_NONE); i++) { + bool curIsOutput = audio_is_output_devices(itemAt(i)->mDeviceType); + audio_devices_t curType = itemAt(i)->mDeviceType & ~AUDIO_DEVICE_BIT_IN; + if ((isOutput == curIsOutput) && ((type & curType) != 0)) { + devices.add(itemAt(i)); + type &= ~curType; + ALOGV("DeviceVector::getDevicesFromType() for type %x found %p", + itemAt(i)->type(), itemAt(i).get()); + } + } + return devices; +} + +DeviceVector DeviceVector::getDevicesFromTypeAddr( + audio_devices_t type, String8 address) const +{ + DeviceVector devices; + for (size_t i = 0; i < size(); i++) { + if (itemAt(i)->type() == type) { + if (itemAt(i)->mAddress == address) { + devices.add(itemAt(i)); + } + } + } + return devices; +} + +sp<DeviceDescriptor> DeviceVector::getDeviceFromName(const String8& name) const +{ + sp<DeviceDescriptor> device; + for (size_t i = 0; i < size(); i++) { + if (itemAt(i)->mName == name) { + device = itemAt(i); + break; + } + } + return device; +} + + +status_t DeviceVector::dump(int fd, const String8 &direction) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "\n Available %s devices:\n", direction.string()); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < size(); i++) { + itemAt(i)->dump(fd, 2, i); + } + return NO_ERROR; +} + +audio_policy_dev_state_t DeviceVector::getDeviceConnectionState(const sp<DeviceDescriptor> &devDesc) const +{ + ssize_t index = indexOf(devDesc); + return index >= 0 ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE : AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE; +} + +void DeviceDescriptor::toAudioPortConfig(struct audio_port_config *dstConfig, + const struct audio_port_config *srcConfig) const +{ + dstConfig->config_mask = AUDIO_PORT_CONFIG_CHANNEL_MASK|AUDIO_PORT_CONFIG_GAIN; + if (srcConfig != NULL) { + dstConfig->config_mask |= srcConfig->config_mask; + } + + AudioPortConfig::toAudioPortConfig(dstConfig, srcConfig); + + dstConfig->id = mId; + dstConfig->role = audio_is_output_device(mDeviceType) ? + AUDIO_PORT_ROLE_SINK : AUDIO_PORT_ROLE_SOURCE; + dstConfig->type = AUDIO_PORT_TYPE_DEVICE; + dstConfig->ext.device.type = mDeviceType; + + //TODO Understand why this test is necessary. i.e. why at boot time does it crash + // without the test? + // This has been demonstrated to NOT be true (at start up) + // ALOG_ASSERT(mModule != NULL); + dstConfig->ext.device.hw_module = mModule != 0 ? mModule->mHandle : AUDIO_IO_HANDLE_NONE; + strncpy(dstConfig->ext.device.address, mAddress.string(), AUDIO_DEVICE_MAX_ADDRESS_LEN); +} + +void DeviceDescriptor::toAudioPort(struct audio_port *port) const +{ + ALOGV("DeviceDescriptor::toAudioPort() handle %d type %x", mId, mDeviceType); + AudioPort::toAudioPort(port); + port->id = mId; + toAudioPortConfig(&port->active_config); + port->ext.device.type = mDeviceType; + port->ext.device.hw_module = mModule->mHandle; + strncpy(port->ext.device.address, mAddress.string(), AUDIO_DEVICE_MAX_ADDRESS_LEN); +} + +status_t DeviceDescriptor::dump(int fd, int spaces, int index) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "%*sDevice %d:\n", spaces, "", index+1); + result.append(buffer); + if (mId != 0) { + snprintf(buffer, SIZE, "%*s- id: %2d\n", spaces, "", mId); + result.append(buffer); + } + snprintf(buffer, SIZE, "%*s- type: %-48s\n", spaces, "", + ConfigParsingUtils::enumToString(sDeviceTypeToEnumTable, + ARRAY_SIZE(sDeviceTypeToEnumTable), + mDeviceType)); + result.append(buffer); + if (mAddress.size() != 0) { + snprintf(buffer, SIZE, "%*s- address: %-32s\n", spaces, "", mAddress.string()); + result.append(buffer); + } + write(fd, result.string(), result.size()); + AudioPort::dump(fd, spaces); + + return NO_ERROR; +} + +void DeviceDescriptor::log() const +{ + ALOGI("Device id:%d type:0x%X:%s, addr:%s", + mId, + mDeviceType, + ConfigParsingUtils::enumToString( + sDeviceNameToEnumTable, ARRAY_SIZE(sDeviceNameToEnumTable), mDeviceType), + mAddress.string()); + + AudioPort::log(" "); +} + +}; // namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/EffectDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/EffectDescriptor.cpp new file mode 100644 index 0000000..33d838d --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/EffectDescriptor.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2015 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 "APM::EffectDescriptor" +//#define LOG_NDEBUG 0 + +#include "EffectDescriptor.h" +#include <utils/String8.h> + +namespace android { + +status_t EffectDescriptor::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, " I/O: %d\n", mIo); + result.append(buffer); + snprintf(buffer, SIZE, " Strategy: %d\n", mStrategy); + result.append(buffer); + snprintf(buffer, SIZE, " Session: %d\n", mSession); + result.append(buffer); + snprintf(buffer, SIZE, " Name: %s\n", mDesc.name); + result.append(buffer); + snprintf(buffer, SIZE, " %s\n", mEnabled ? "Enabled" : "Disabled"); + result.append(buffer); + write(fd, result.string(), result.size()); + + return NO_ERROR; +} + +EffectDescriptorCollection::EffectDescriptorCollection() : + mTotalEffectsCpuLoad(0), + mTotalEffectsMemory(0) +{ + +} + +status_t EffectDescriptorCollection::registerEffect(const effect_descriptor_t *desc, + audio_io_handle_t io, + uint32_t strategy, + int session, + int id) +{ + if (mTotalEffectsMemory + desc->memoryUsage > getMaxEffectsMemory()) { + ALOGW("registerEffect() memory limit exceeded for Fx %s, Memory %d KB", + desc->name, desc->memoryUsage); + return INVALID_OPERATION; + } + mTotalEffectsMemory += desc->memoryUsage; + ALOGV("registerEffect() effect %s, io %d, strategy %d session %d id %d", + desc->name, io, strategy, session, id); + ALOGV("registerEffect() memory %d, total memory %d", desc->memoryUsage, mTotalEffectsMemory); + + sp<EffectDescriptor> effectDesc = new EffectDescriptor(); + memcpy (&effectDesc->mDesc, desc, sizeof(effect_descriptor_t)); + effectDesc->mIo = io; + effectDesc->mStrategy = static_cast<routing_strategy>(strategy); + effectDesc->mSession = session; + effectDesc->mEnabled = false; + + add(id, effectDesc); + + return NO_ERROR; +} + +status_t EffectDescriptorCollection::unregisterEffect(int id) +{ + ssize_t index = indexOfKey(id); + if (index < 0) { + ALOGW("unregisterEffect() unknown effect ID %d", id); + return INVALID_OPERATION; + } + + sp<EffectDescriptor> effectDesc = valueAt(index); + + setEffectEnabled(effectDesc, false); + + if (mTotalEffectsMemory < effectDesc->mDesc.memoryUsage) { + ALOGW("unregisterEffect() memory %d too big for total %d", + effectDesc->mDesc.memoryUsage, mTotalEffectsMemory); + effectDesc->mDesc.memoryUsage = mTotalEffectsMemory; + } + mTotalEffectsMemory -= effectDesc->mDesc.memoryUsage; + ALOGV("unregisterEffect() effect %s, ID %d, memory %d total memory %d", + effectDesc->mDesc.name, id, effectDesc->mDesc.memoryUsage, mTotalEffectsMemory); + + removeItem(id); + + return NO_ERROR; +} + +status_t EffectDescriptorCollection::setEffectEnabled(int id, bool enabled) +{ + ssize_t index = indexOfKey(id); + if (index < 0) { + ALOGW("unregisterEffect() unknown effect ID %d", id); + return INVALID_OPERATION; + } + + return setEffectEnabled(valueAt(index), enabled); +} + + +status_t EffectDescriptorCollection::setEffectEnabled(const sp<EffectDescriptor> &effectDesc, + bool enabled) +{ + if (enabled == effectDesc->mEnabled) { + ALOGV("setEffectEnabled(%s) effect already %s", + enabled?"true":"false", enabled?"enabled":"disabled"); + return INVALID_OPERATION; + } + + if (enabled) { + if (mTotalEffectsCpuLoad + effectDesc->mDesc.cpuLoad > getMaxEffectsCpuLoad()) { + ALOGW("setEffectEnabled(true) CPU Load limit exceeded for Fx %s, CPU %f MIPS", + effectDesc->mDesc.name, (float)effectDesc->mDesc.cpuLoad/10); + return INVALID_OPERATION; + } + mTotalEffectsCpuLoad += effectDesc->mDesc.cpuLoad; + ALOGV("setEffectEnabled(true) total CPU %d", mTotalEffectsCpuLoad); + } else { + if (mTotalEffectsCpuLoad < effectDesc->mDesc.cpuLoad) { + ALOGW("setEffectEnabled(false) CPU load %d too high for total %d", + effectDesc->mDesc.cpuLoad, mTotalEffectsCpuLoad); + effectDesc->mDesc.cpuLoad = mTotalEffectsCpuLoad; + } + mTotalEffectsCpuLoad -= effectDesc->mDesc.cpuLoad; + ALOGV("setEffectEnabled(false) total CPU %d", mTotalEffectsCpuLoad); + } + effectDesc->mEnabled = enabled; + return NO_ERROR; +} + +bool EffectDescriptorCollection::isNonOffloadableEffectEnabled() +{ + for (size_t i = 0; i < size(); i++) { + sp<EffectDescriptor> effectDesc = valueAt(i); + if (effectDesc->mEnabled && (effectDesc->mStrategy == STRATEGY_MEDIA) && + ((effectDesc->mDesc.flags & EFFECT_FLAG_OFFLOAD_SUPPORTED) == 0)) { + ALOGV("isNonOffloadableEffectEnabled() non offloadable effect %s enabled on session %d", + effectDesc->mDesc.name, effectDesc->mSession); + return true; + } + } + return false; +} + +uint32_t EffectDescriptorCollection::getMaxEffectsCpuLoad() const +{ + return MAX_EFFECTS_CPU_LOAD; +} + +uint32_t EffectDescriptorCollection::getMaxEffectsMemory() const +{ + return MAX_EFFECTS_MEMORY; +} + +status_t EffectDescriptorCollection::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "\nTotal Effects CPU: %f MIPS, Total Effects memory: %d KB\n", + (float)mTotalEffectsCpuLoad/10, mTotalEffectsMemory); + write(fd, buffer, strlen(buffer)); + + snprintf(buffer, SIZE, "Registered effects:\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < size(); i++) { + snprintf(buffer, SIZE, "- Effect %d dump:\n", keyAt(i)); + write(fd, buffer, strlen(buffer)); + valueAt(i)->dump(fd); + } + return NO_ERROR; +} + +}; //namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp b/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp new file mode 100644 index 0000000..e955447 --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2015 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 "APM::HwModule" +//#define LOG_NDEBUG 0 + +#include "HwModule.h" +#include "IOProfile.h" +#include "AudioGain.h" +#include "ConfigParsingUtils.h" +#include "audio_policy_conf.h" +#include <hardware/audio.h> +#include <policy.h> + +namespace android { + +HwModule::HwModule(const char *name) + : mName(strndup(name, AUDIO_HARDWARE_MODULE_ID_MAX_LEN)), + mHalVersion(AUDIO_DEVICE_API_VERSION_MIN), mHandle(0) +{ +} + +HwModule::~HwModule() +{ + for (size_t i = 0; i < mOutputProfiles.size(); i++) { + mOutputProfiles[i]->mSupportedDevices.clear(); + } + for (size_t i = 0; i < mInputProfiles.size(); i++) { + mInputProfiles[i]->mSupportedDevices.clear(); + } + free((void *)mName); +} + +status_t HwModule::loadInput(cnode *root) +{ + cnode *node = root->first_child; + + sp<IOProfile> profile = new IOProfile(String8(root->name), AUDIO_PORT_ROLE_SINK); + + while (node) { + if (strcmp(node->name, SAMPLING_RATES_TAG) == 0) { + profile->loadSamplingRates((char *)node->value); + } else if (strcmp(node->name, FORMATS_TAG) == 0) { + profile->loadFormats((char *)node->value); + } else if (strcmp(node->name, CHANNELS_TAG) == 0) { + profile->loadInChannels((char *)node->value); + } else if (strcmp(node->name, DEVICES_TAG) == 0) { + profile->mSupportedDevices.loadDevicesFromName((char *)node->value, + mDeclaredDevices); + } else if (strcmp(node->name, FLAGS_TAG) == 0) { + profile->mFlags = ConfigParsingUtils::parseInputFlagNames((char *)node->value); + } else if (strcmp(node->name, GAINS_TAG) == 0) { + profile->loadGains(node); + } + node = node->next; + } + ALOGW_IF(profile->mSupportedDevices.isEmpty(), + "loadInput() invalid supported devices"); + ALOGW_IF(profile->mChannelMasks.size() == 0, + "loadInput() invalid supported channel masks"); + ALOGW_IF(profile->mSamplingRates.size() == 0, + "loadInput() invalid supported sampling rates"); + ALOGW_IF(profile->mFormats.size() == 0, + "loadInput() invalid supported formats"); + if (!profile->mSupportedDevices.isEmpty() && + (profile->mChannelMasks.size() != 0) && + (profile->mSamplingRates.size() != 0) && + (profile->mFormats.size() != 0)) { + + ALOGV("loadInput() adding input Supported Devices %04x", + profile->mSupportedDevices.types()); + + profile->attach(this); + mInputProfiles.add(profile); + return NO_ERROR; + } else { + return BAD_VALUE; + } +} + +status_t HwModule::loadOutput(cnode *root) +{ + cnode *node = root->first_child; + + sp<IOProfile> profile = new IOProfile(String8(root->name), AUDIO_PORT_ROLE_SOURCE); + + while (node) { + if (strcmp(node->name, SAMPLING_RATES_TAG) == 0) { + profile->loadSamplingRates((char *)node->value); + } else if (strcmp(node->name, FORMATS_TAG) == 0) { + profile->loadFormats((char *)node->value); + } else if (strcmp(node->name, CHANNELS_TAG) == 0) { + profile->loadOutChannels((char *)node->value); + } else if (strcmp(node->name, DEVICES_TAG) == 0) { + profile->mSupportedDevices.loadDevicesFromName((char *)node->value, + mDeclaredDevices); + } else if (strcmp(node->name, FLAGS_TAG) == 0) { + profile->mFlags = ConfigParsingUtils::parseOutputFlagNames((char *)node->value); + } else if (strcmp(node->name, GAINS_TAG) == 0) { + profile->loadGains(node); + } + node = node->next; + } + ALOGW_IF(profile->mSupportedDevices.isEmpty(), + "loadOutput() invalid supported devices"); + ALOGW_IF(profile->mChannelMasks.size() == 0, + "loadOutput() invalid supported channel masks"); + ALOGW_IF(profile->mSamplingRates.size() == 0, + "loadOutput() invalid supported sampling rates"); + ALOGW_IF(profile->mFormats.size() == 0, + "loadOutput() invalid supported formats"); + if (!profile->mSupportedDevices.isEmpty() && + (profile->mChannelMasks.size() != 0) && + (profile->mSamplingRates.size() != 0) && + (profile->mFormats.size() != 0)) { + + ALOGV("loadOutput() adding output Supported Devices %04x, mFlags %04x", + profile->mSupportedDevices.types(), profile->mFlags); + profile->attach(this); + mOutputProfiles.add(profile); + return NO_ERROR; + } else { + return BAD_VALUE; + } +} + +status_t HwModule::loadDevice(cnode *root) +{ + cnode *node = root->first_child; + + audio_devices_t type = AUDIO_DEVICE_NONE; + while (node) { + if (strcmp(node->name, APM_DEVICE_TYPE) == 0) { + type = ConfigParsingUtils::parseDeviceNames((char *)node->value); + break; + } + node = node->next; + } + if (type == AUDIO_DEVICE_NONE || + (!audio_is_input_device(type) && !audio_is_output_device(type))) { + ALOGW("loadDevice() bad type %08x", type); + return BAD_VALUE; + } + sp<DeviceDescriptor> deviceDesc = new DeviceDescriptor(String8(root->name), type); + + node = root->first_child; + while (node) { + if (strcmp(node->name, APM_DEVICE_ADDRESS) == 0) { + deviceDesc->mAddress = String8((char *)node->value); + } else if (strcmp(node->name, CHANNELS_TAG) == 0) { + if (audio_is_input_device(type)) { + deviceDesc->loadInChannels((char *)node->value); + } else { + deviceDesc->loadOutChannels((char *)node->value); + } + } else if (strcmp(node->name, GAINS_TAG) == 0) { + deviceDesc->loadGains(node); + } + node = node->next; + } + + ALOGV("loadDevice() adding device name %s type %08x address %s", + deviceDesc->mName.string(), type, deviceDesc->mAddress.string()); + + mDeclaredDevices.add(deviceDesc); + + return NO_ERROR; +} + +status_t HwModule::addOutputProfile(String8 name, const audio_config_t *config, + audio_devices_t device, String8 address) +{ + sp<IOProfile> profile = new IOProfile(name, AUDIO_PORT_ROLE_SOURCE); + + profile->mSamplingRates.add(config->sample_rate); + profile->mChannelMasks.add(config->channel_mask); + profile->mFormats.add(config->format); + + sp<DeviceDescriptor> devDesc = new DeviceDescriptor(name, device); + devDesc->mAddress = address; + profile->mSupportedDevices.add(devDesc); + + profile->attach(this); + mOutputProfiles.add(profile); + + return NO_ERROR; +} + +status_t HwModule::removeOutputProfile(String8 name) +{ + for (size_t i = 0; i < mOutputProfiles.size(); i++) { + if (mOutputProfiles[i]->mName == name) { + mOutputProfiles.removeAt(i); + break; + } + } + + return NO_ERROR; +} + +status_t HwModule::addInputProfile(String8 name, const audio_config_t *config, + audio_devices_t device, String8 address) +{ + sp<IOProfile> profile = new IOProfile(name, AUDIO_PORT_ROLE_SINK); + + profile->mSamplingRates.add(config->sample_rate); + profile->mChannelMasks.add(config->channel_mask); + profile->mFormats.add(config->format); + + sp<DeviceDescriptor> devDesc = new DeviceDescriptor(name, device); + devDesc->mAddress = address; + profile->mSupportedDevices.add(devDesc); + + ALOGV("addInputProfile() name %s rate %d mask 0x08", name.string(), config->sample_rate, config->channel_mask); + + profile->attach(this); + mInputProfiles.add(profile); + + return NO_ERROR; +} + +status_t HwModule::removeInputProfile(String8 name) +{ + for (size_t i = 0; i < mInputProfiles.size(); i++) { + if (mInputProfiles[i]->mName == name) { + mInputProfiles.removeAt(i); + break; + } + } + + return NO_ERROR; +} + + +void HwModule::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, " - name: %s\n", mName); + result.append(buffer); + snprintf(buffer, SIZE, " - handle: %d\n", mHandle); + result.append(buffer); + snprintf(buffer, SIZE, " - version: %u.%u\n", mHalVersion >> 8, mHalVersion & 0xFF); + result.append(buffer); + write(fd, result.string(), result.size()); + if (mOutputProfiles.size()) { + write(fd, " - outputs:\n", strlen(" - outputs:\n")); + for (size_t i = 0; i < mOutputProfiles.size(); i++) { + snprintf(buffer, SIZE, " output %zu:\n", i); + write(fd, buffer, strlen(buffer)); + mOutputProfiles[i]->dump(fd); + } + } + if (mInputProfiles.size()) { + write(fd, " - inputs:\n", strlen(" - inputs:\n")); + for (size_t i = 0; i < mInputProfiles.size(); i++) { + snprintf(buffer, SIZE, " input %zu:\n", i); + write(fd, buffer, strlen(buffer)); + mInputProfiles[i]->dump(fd); + } + } + if (mDeclaredDevices.size()) { + write(fd, " - devices:\n", strlen(" - devices:\n")); + for (size_t i = 0; i < mDeclaredDevices.size(); i++) { + mDeclaredDevices[i]->dump(fd, 4, i); + } + } +} + +sp <HwModule> HwModuleCollection::getModuleFromName(const char *name) const +{ + sp <HwModule> module; + + for (size_t i = 0; i < size(); i++) + { + if (strcmp(itemAt(i)->mName, name) == 0) { + return itemAt(i); + } + } + return module; +} + + +sp <HwModule> HwModuleCollection::getModuleForDevice(audio_devices_t device) const +{ + sp <HwModule> module; + + for (size_t i = 0; i < size(); i++) { + if (itemAt(i)->mHandle == 0) { + continue; + } + if (audio_is_output_device(device)) { + for (size_t j = 0; j < itemAt(i)->mOutputProfiles.size(); j++) + { + if (itemAt(i)->mOutputProfiles[j]->mSupportedDevices.types() & device) { + return itemAt(i); + } + } + } else { + for (size_t j = 0; j < itemAt(i)->mInputProfiles.size(); j++) { + if (itemAt(i)->mInputProfiles[j]->mSupportedDevices.types() & + device & ~AUDIO_DEVICE_BIT_IN) { + return itemAt(i); + } + } + } + } + return module; +} + +sp<DeviceDescriptor> HwModuleCollection::getDeviceDescriptor(const audio_devices_t device, + const char *device_address, + const char *device_name) const +{ + String8 address = (device_address == NULL) ? String8("") : String8(device_address); + // handle legacy remote submix case where the address was not always specified + if (device_distinguishes_on_address(device) && (address.length() == 0)) { + address = String8("0"); + } + + for (size_t i = 0; i < size(); i++) { + const sp<HwModule> hwModule = itemAt(i); + if (hwModule->mHandle == 0) { + continue; + } + DeviceVector deviceList = + hwModule->mDeclaredDevices.getDevicesFromTypeAddr(device, address); + if (!deviceList.isEmpty()) { + return deviceList.itemAt(0); + } + deviceList = hwModule->mDeclaredDevices.getDevicesFromType(device); + if (!deviceList.isEmpty()) { + return deviceList.itemAt(0); + } + } + + sp<DeviceDescriptor> devDesc = + new DeviceDescriptor(String8(device_name != NULL ? device_name : ""), device); + devDesc->mAddress = address; + return devDesc; +} + +status_t HwModuleCollection::dump(int fd) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "\nHW Modules dump:\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < size(); i++) { + snprintf(buffer, SIZE, "- HW Module %zu:\n", i + 1); + write(fd, buffer, strlen(buffer)); + itemAt(i)->dump(fd); + } + return NO_ERROR; +} + +} //namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp b/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp new file mode 100644 index 0000000..de6539c --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2015 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 "APM::IOProfile" +//#define LOG_NDEBUG 0 + +#include "IOProfile.h" +#include "HwModule.h" +#include "AudioGain.h" + +namespace android { + +IOProfile::IOProfile(const String8& name, audio_port_role_t role) + : AudioPort(name, AUDIO_PORT_TYPE_MIX, role) +{ +} + +IOProfile::~IOProfile() +{ +} + +// checks if the IO profile is compatible with specified parameters. +// Sampling rate, format and channel mask must be specified in order to +// get a valid a match +bool IOProfile::isCompatibleProfile(audio_devices_t device, + String8 address, + uint32_t samplingRate, + uint32_t *updatedSamplingRate, + audio_format_t format, + audio_channel_mask_t channelMask, + uint32_t flags) const +{ + const bool isPlaybackThread = mType == AUDIO_PORT_TYPE_MIX && mRole == AUDIO_PORT_ROLE_SOURCE; + const bool isRecordThread = mType == AUDIO_PORT_TYPE_MIX && mRole == AUDIO_PORT_ROLE_SINK; + ALOG_ASSERT(isPlaybackThread != isRecordThread); + + + if (device != AUDIO_DEVICE_NONE) { + // just check types if multiple devices are selected + if (popcount(device & ~AUDIO_DEVICE_BIT_IN) > 1) { + if ((mSupportedDevices.types() & device) != device) { + return false; + } + } else if (mSupportedDevices.getDevice(device, address) == 0) { + return false; + } + } + + if (samplingRate == 0) { + return false; + } + uint32_t myUpdatedSamplingRate = samplingRate; + if (isPlaybackThread && checkExactSamplingRate(samplingRate) != NO_ERROR) { + return false; + } + if (isRecordThread && checkCompatibleSamplingRate(samplingRate, &myUpdatedSamplingRate) != + NO_ERROR) { + return false; + } + + if (!audio_is_valid_format(format) || checkFormat(format) != NO_ERROR) { + return false; + } + + if (isPlaybackThread && (!audio_is_output_channel(channelMask) || + checkExactChannelMask(channelMask) != NO_ERROR)) { + return false; + } + if (isRecordThread && (!audio_is_input_channel(channelMask) || + checkCompatibleChannelMask(channelMask) != NO_ERROR)) { + return false; + } + + if (isPlaybackThread && (mFlags & flags) != flags) { + return false; + } + // The only input flag that is allowed to be different is the fast flag. + // An existing fast stream is compatible with a normal track request. + // An existing normal stream is compatible with a fast track request, + // but the fast request will be denied by AudioFlinger and converted to normal track. + if (isRecordThread && ((mFlags ^ flags) & + ~AUDIO_INPUT_FLAG_FAST)) { + return false; + } + + if (updatedSamplingRate != NULL) { + *updatedSamplingRate = myUpdatedSamplingRate; + } + return true; +} + +void IOProfile::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + AudioPort::dump(fd, 4); + + snprintf(buffer, SIZE, " - flags: 0x%04x\n", mFlags); + result.append(buffer); + snprintf(buffer, SIZE, " - devices:\n"); + result.append(buffer); + write(fd, result.string(), result.size()); + for (size_t i = 0; i < mSupportedDevices.size(); i++) { + mSupportedDevices[i]->dump(fd, 6, i); + } +} + +void IOProfile::log() +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + ALOGV(" - sampling rates: "); + for (size_t i = 0; i < mSamplingRates.size(); i++) { + ALOGV(" %d", mSamplingRates[i]); + } + + ALOGV(" - channel masks: "); + for (size_t i = 0; i < mChannelMasks.size(); i++) { + ALOGV(" 0x%04x", mChannelMasks[i]); + } + + ALOGV(" - formats: "); + for (size_t i = 0; i < mFormats.size(); i++) { + ALOGV(" 0x%08x", mFormats[i]); + } + + ALOGV(" - devices: 0x%04x\n", mSupportedDevices.types()); + ALOGV(" - flags: 0x%04x\n", mFlags); +} + +}; // namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/SoundTriggerSession.cpp b/services/audiopolicy/common/managerdefinitions/src/SoundTriggerSession.cpp new file mode 100644 index 0000000..8ca3ae0 --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/SoundTriggerSession.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 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 "APM::SoundTriggerSession" +//#define LOG_NDEBUG 0 + +#include "SoundTriggerSession.h" + + +namespace android { + +status_t SoundTriggerSessionCollection::acquireSession(audio_session_t session, + audio_io_handle_t ioHandle) +{ + add(session, ioHandle); + + return NO_ERROR; +} + +status_t SoundTriggerSessionCollection::releaseSession(audio_session_t session) +{ + ssize_t index = indexOfKey(session); + if (index < 0) { + ALOGW("acquireSoundTriggerSession() session %d not registered", session); + return BAD_VALUE; + } + + removeItem(session); + return NO_ERROR; +} + +}; //namespace android diff --git a/services/audiopolicy/common/managerdefinitions/src/StreamDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/StreamDescriptor.cpp new file mode 100644 index 0000000..b682e2c --- /dev/null +++ b/services/audiopolicy/common/managerdefinitions/src/StreamDescriptor.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 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 "APM::Volumes" +//#define LOG_NDEBUG 0 + +//#define VERY_VERBOSE_LOGGING +#ifdef VERY_VERBOSE_LOGGING +#define ALOGVV ALOGV +#else +#define ALOGVV(a...) do { } while(0) +#endif + +#include "StreamDescriptor.h" +#include <utils/Log.h> +#include <utils/String8.h> + +namespace android { + +// --- StreamDescriptor class implementation + +StreamDescriptor::StreamDescriptor() + : mIndexMin(0), mIndexMax(1), mCanBeMuted(true) +{ + mIndexCur.add(AUDIO_DEVICE_OUT_DEFAULT, 0); +} + +int StreamDescriptor::getVolumeIndex(audio_devices_t device) const +{ + device = Volume::getDeviceForVolume(device); + // there is always a valid entry for AUDIO_DEVICE_OUT_DEFAULT + if (mIndexCur.indexOfKey(device) < 0) { + device = AUDIO_DEVICE_OUT_DEFAULT; + } + return mIndexCur.valueFor(device); +} + +void StreamDescriptor::clearCurrentVolumeIndex() +{ + mIndexCur.clear(); +} + +void StreamDescriptor::addCurrentVolumeIndex(audio_devices_t device, int index) +{ + mIndexCur.add(device, index); +} + +void StreamDescriptor::setVolumeIndexMin(int volIndexMin) +{ + mIndexMin = volIndexMin; +} + +void StreamDescriptor::setVolumeIndexMax(int volIndexMax) +{ + mIndexMax = volIndexMax; +} + +void StreamDescriptor::setVolumeCurvePoint(Volume::device_category deviceCategory, + const VolumeCurvePoint *point) +{ + mVolumeCurve[deviceCategory] = point; +} + +void StreamDescriptor::dump(int fd) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "%s %02d %02d ", + mCanBeMuted ? "true " : "false", mIndexMin, mIndexMax); + result.append(buffer); + for (size_t i = 0; i < mIndexCur.size(); i++) { + snprintf(buffer, SIZE, "%04x : %02d, ", + mIndexCur.keyAt(i), + mIndexCur.valueAt(i)); + result.append(buffer); + } + result.append("\n"); + + write(fd, result.string(), result.size()); +} + +StreamDescriptorCollection::StreamDescriptorCollection() +{ + for (size_t stream = 0 ; stream < AUDIO_STREAM_CNT; stream++) { + add(static_cast<audio_stream_type_t>(stream), StreamDescriptor()); + } +} + +bool StreamDescriptorCollection::canBeMuted(audio_stream_type_t stream) +{ + return valueAt(stream).canBeMuted(); +} + +void StreamDescriptorCollection::clearCurrentVolumeIndex(audio_stream_type_t stream) +{ + editValueAt(stream).clearCurrentVolumeIndex(); +} + +void StreamDescriptorCollection::addCurrentVolumeIndex(audio_stream_type_t stream, + audio_devices_t device, int index) +{ + editValueAt(stream).addCurrentVolumeIndex(device, index); +} + +void StreamDescriptorCollection::setVolumeCurvePoint(audio_stream_type_t stream, + Volume::device_category deviceCategory, + const VolumeCurvePoint *point) +{ + editValueAt(stream).setVolumeCurvePoint(deviceCategory, point); +} + +const VolumeCurvePoint *StreamDescriptorCollection::getVolumeCurvePoint(audio_stream_type_t stream, + Volume::device_category deviceCategory) const +{ + return valueAt(stream).getVolumeCurvePoint(deviceCategory); +} + +void StreamDescriptorCollection::setVolumeIndexMin(audio_stream_type_t stream,int volIndexMin) +{ + return editValueAt(stream).setVolumeIndexMin(volIndexMin); +} + +void StreamDescriptorCollection::setVolumeIndexMax(audio_stream_type_t stream,int volIndexMax) +{ + return editValueAt(stream).setVolumeIndexMax(volIndexMax); +} + +status_t StreamDescriptorCollection::dump(int fd) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "\nStreams dump:\n"); + write(fd, buffer, strlen(buffer)); + snprintf(buffer, SIZE, + " Stream Can be muted Index Min Index Max Index Cur [device : index]...\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < size(); i++) { + snprintf(buffer, SIZE, " %02zu ", i); + write(fd, buffer, strlen(buffer)); + valueAt(i).dump(fd); + } + + return NO_ERROR; +} + +}; // namespace android |