/* * 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 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, const sp& module) : mName(name), mType(type), mRole(role), mModule(module), mFlags(0), mId(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& module) { mId = getNextUniqueId(); mModule = module; } audio_port_handle_t AudioPort::getNextUniqueId() { return static_cast(android_atomic_inc(&mNextUniqueId)); } audio_module_handle_t AudioPort::getModuleHandle() const { return mModule->mHandle; } 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 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 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 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.size() != 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); result.append(buffer); 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 = 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