PatchPanel.cpp revision 054d9d3dea1390294650ac704acb4aa0a0731217
1/* 2** 3** Copyright 2014, The Android Open Source Project 4** 5** Licensed under the Apache License, Version 2.0 (the "License"); 6** you may not use this file except in compliance with the License. 7** You may obtain a copy of the License at 8** 9** http://www.apache.org/licenses/LICENSE-2.0 10** 11** Unless required by applicable law or agreed to in writing, software 12** distributed under the License is distributed on an "AS IS" BASIS, 13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14** See the License for the specific language governing permissions and 15** limitations under the License. 16*/ 17 18 19#define LOG_TAG "AudioFlinger::PatchPanel" 20//#define LOG_NDEBUG 0 21 22#include "Configuration.h" 23#include <utils/Log.h> 24#include <audio_utils/primitives.h> 25 26#include "AudioFlinger.h" 27#include "ServiceUtilities.h" 28#include <media/AudioParameter.h> 29 30// ---------------------------------------------------------------------------- 31 32// Note: the following macro is used for extremely verbose logging message. In 33// order to run with ALOG_ASSERT turned on, we need to have LOG_NDEBUG set to 34// 0; but one side effect of this is to turn all LOGV's as well. Some messages 35// are so verbose that we want to suppress them even when we have ALOG_ASSERT 36// turned on. Do not uncomment the #def below unless you really know what you 37// are doing and want to see all of the extremely verbose messages. 38//#define VERY_VERY_VERBOSE_LOGGING 39#ifdef VERY_VERY_VERBOSE_LOGGING 40#define ALOGVV ALOGV 41#else 42#define ALOGVV(a...) do { } while(0) 43#endif 44 45namespace android { 46 47/* List connected audio ports and their attributes */ 48status_t AudioFlinger::listAudioPorts(unsigned int *num_ports, 49 struct audio_port *ports) 50{ 51 Mutex::Autolock _l(mLock); 52 if (mPatchPanel != 0) { 53 return mPatchPanel->listAudioPorts(num_ports, ports); 54 } 55 return NO_INIT; 56} 57 58/* Get supported attributes for a given audio port */ 59status_t AudioFlinger::getAudioPort(struct audio_port *port) 60{ 61 Mutex::Autolock _l(mLock); 62 if (mPatchPanel != 0) { 63 return mPatchPanel->getAudioPort(port); 64 } 65 return NO_INIT; 66} 67 68 69/* Connect a patch between several source and sink ports */ 70status_t AudioFlinger::createAudioPatch(const struct audio_patch *patch, 71 audio_patch_handle_t *handle) 72{ 73 Mutex::Autolock _l(mLock); 74 if (mPatchPanel != 0) { 75 return mPatchPanel->createAudioPatch(patch, handle); 76 } 77 return NO_INIT; 78} 79 80/* Disconnect a patch */ 81status_t AudioFlinger::releaseAudioPatch(audio_patch_handle_t handle) 82{ 83 Mutex::Autolock _l(mLock); 84 if (mPatchPanel != 0) { 85 return mPatchPanel->releaseAudioPatch(handle); 86 } 87 return NO_INIT; 88} 89 90 91/* List connected audio ports and they attributes */ 92status_t AudioFlinger::listAudioPatches(unsigned int *num_patches, 93 struct audio_patch *patches) 94{ 95 Mutex::Autolock _l(mLock); 96 if (mPatchPanel != 0) { 97 return mPatchPanel->listAudioPatches(num_patches, patches); 98 } 99 return NO_INIT; 100} 101 102/* Set audio port configuration */ 103status_t AudioFlinger::setAudioPortConfig(const struct audio_port_config *config) 104{ 105 Mutex::Autolock _l(mLock); 106 if (mPatchPanel != 0) { 107 return mPatchPanel->setAudioPortConfig(config); 108 } 109 return NO_INIT; 110} 111 112 113AudioFlinger::PatchPanel::PatchPanel(const sp<AudioFlinger>& audioFlinger) 114 : mAudioFlinger(audioFlinger) 115{ 116} 117 118AudioFlinger::PatchPanel::~PatchPanel() 119{ 120} 121 122/* List connected audio ports and their attributes */ 123status_t AudioFlinger::PatchPanel::listAudioPorts(unsigned int *num_ports __unused, 124 struct audio_port *ports __unused) 125{ 126 ALOGV("listAudioPorts"); 127 return NO_ERROR; 128} 129 130/* Get supported attributes for a given audio port */ 131status_t AudioFlinger::PatchPanel::getAudioPort(struct audio_port *port __unused) 132{ 133 ALOGV("getAudioPort"); 134 return NO_ERROR; 135} 136 137 138/* Connect a patch between several source and sink ports */ 139status_t AudioFlinger::PatchPanel::createAudioPatch(const struct audio_patch *patch, 140 audio_patch_handle_t *handle) 141{ 142 ALOGV("createAudioPatch() num_sources %d num_sinks %d handle %d", 143 patch->num_sources, patch->num_sinks, *handle); 144 status_t status = NO_ERROR; 145 audio_patch_handle_t halHandle = AUDIO_PATCH_HANDLE_NONE; 146 sp<AudioFlinger> audioflinger = mAudioFlinger.promote(); 147 if (audioflinger == 0) { 148 return NO_INIT; 149 } 150 151 if (handle == NULL || patch == NULL) { 152 return BAD_VALUE; 153 } 154 if (patch->num_sources == 0 || patch->num_sources > AUDIO_PATCH_PORTS_MAX || 155 patch->num_sinks == 0 || patch->num_sinks > AUDIO_PATCH_PORTS_MAX) { 156 return BAD_VALUE; 157 } 158 // limit number of sources to 1 for now or 2 sources for special cross hw module case. 159 // only the audio policy manager can request a patch creation with 2 sources. 160 if (patch->num_sources > 2) { 161 return INVALID_OPERATION; 162 } 163 164 if (*handle != AUDIO_PATCH_HANDLE_NONE) { 165 for (size_t index = 0; *handle != 0 && index < mPatches.size(); index++) { 166 if (*handle == mPatches[index]->mHandle) { 167 ALOGV("createAudioPatch() removing patch handle %d", *handle); 168 halHandle = mPatches[index]->mHalHandle; 169 Patch *removedPatch = mPatches[index]; 170 mPatches.removeAt(index); 171 delete removedPatch; 172 break; 173 } 174 } 175 } 176 177 Patch *newPatch = new Patch(patch); 178 179 switch (patch->sources[0].type) { 180 case AUDIO_PORT_TYPE_DEVICE: { 181 audio_module_handle_t srcModule = patch->sources[0].ext.device.hw_module; 182 ssize_t index = audioflinger->mAudioHwDevs.indexOfKey(srcModule); 183 if (index < 0) { 184 ALOGW("createAudioPatch() bad src hw module %d", srcModule); 185 status = BAD_VALUE; 186 goto exit; 187 } 188 AudioHwDevice *audioHwDevice = audioflinger->mAudioHwDevs.valueAt(index); 189 for (unsigned int i = 0; i < patch->num_sinks; i++) { 190 // support only one sink if connection to a mix or across HW modules 191 if ((patch->sinks[i].type == AUDIO_PORT_TYPE_MIX || 192 patch->sinks[i].ext.mix.hw_module != srcModule) && 193 patch->num_sinks > 1) { 194 status = INVALID_OPERATION; 195 goto exit; 196 } 197 // reject connection to different sink types 198 if (patch->sinks[i].type != patch->sinks[0].type) { 199 ALOGW("createAudioPatch() different sink types in same patch not supported"); 200 status = BAD_VALUE; 201 goto exit; 202 } 203 } 204 205 // manage patches requiring a software bridge 206 // - Device to device AND 207 // - source HW module != destination HW module OR 208 // - audio HAL version < 3.0 209 // - special patch request with 2 sources (reuse one existing output mix) 210 if ((patch->sinks[0].type == AUDIO_PORT_TYPE_DEVICE) && 211 ((patch->sinks[0].ext.device.hw_module != srcModule) || 212 (audioHwDevice->version() < AUDIO_DEVICE_API_VERSION_3_0) || 213 (patch->num_sources == 2))) { 214 if (patch->num_sources == 2) { 215 if (patch->sources[1].type != AUDIO_PORT_TYPE_MIX || 216 patch->sinks[0].ext.device.hw_module != 217 patch->sources[1].ext.mix.hw_module) { 218 ALOGW("createAudioPatch() invalid source combination"); 219 status = INVALID_OPERATION; 220 goto exit; 221 } 222 223 sp<ThreadBase> thread = 224 audioflinger->checkPlaybackThread_l(patch->sources[1].ext.mix.handle); 225 newPatch->mPlaybackThread = (MixerThread *)thread.get(); 226 if (thread == 0) { 227 ALOGW("createAudioPatch() cannot get playback thread"); 228 status = INVALID_OPERATION; 229 goto exit; 230 } 231 } else { 232 audio_config_t config = AUDIO_CONFIG_INITIALIZER; 233 audio_devices_t device = patch->sinks[0].ext.device.type; 234 String8 address = String8(patch->sinks[0].ext.device.address); 235 audio_io_handle_t output = AUDIO_IO_HANDLE_NONE; 236 newPatch->mPlaybackThread = audioflinger->openOutput_l( 237 patch->sinks[0].ext.device.hw_module, 238 &output, 239 &config, 240 device, 241 address, 242 AUDIO_OUTPUT_FLAG_NONE); 243 ALOGV("audioflinger->openOutput_l() returned %p", 244 newPatch->mPlaybackThread.get()); 245 if (newPatch->mPlaybackThread == 0) { 246 status = NO_MEMORY; 247 goto exit; 248 } 249 } 250 uint32_t channelCount = newPatch->mPlaybackThread->channelCount(); 251 audio_devices_t device = patch->sources[0].ext.device.type; 252 String8 address = String8(patch->sources[0].ext.device.address); 253 audio_config_t config = AUDIO_CONFIG_INITIALIZER; 254 audio_channel_mask_t inChannelMask = audio_channel_in_mask_from_count(channelCount); 255 config.sample_rate = newPatch->mPlaybackThread->sampleRate(); 256 config.channel_mask = inChannelMask; 257 config.format = newPatch->mPlaybackThread->format(); 258 audio_io_handle_t input = AUDIO_IO_HANDLE_NONE; 259 newPatch->mRecordThread = audioflinger->openInput_l(srcModule, 260 &input, 261 &config, 262 device, 263 address, 264 AUDIO_SOURCE_MIC, 265 AUDIO_INPUT_FLAG_NONE); 266 ALOGV("audioflinger->openInput_l() returned %p inChannelMask %08x", 267 newPatch->mRecordThread.get(), inChannelMask); 268 if (newPatch->mRecordThread == 0) { 269 status = NO_MEMORY; 270 goto exit; 271 } 272 status = createPatchConnections(newPatch, patch); 273 if (status != NO_ERROR) { 274 goto exit; 275 } 276 } else { 277 if (patch->sinks[0].type == AUDIO_PORT_TYPE_MIX) { 278 sp<ThreadBase> thread = audioflinger->checkRecordThread_l( 279 patch->sinks[0].ext.mix.handle); 280 if (thread == 0) { 281 ALOGW("createAudioPatch() bad capture I/O handle %d", 282 patch->sinks[0].ext.mix.handle); 283 status = BAD_VALUE; 284 goto exit; 285 } 286 status = thread->sendCreateAudioPatchConfigEvent(patch, &halHandle); 287 } else { 288 if (audioHwDevice->version() < AUDIO_DEVICE_API_VERSION_3_0) { 289 status = INVALID_OPERATION; 290 goto exit; 291 } 292 293 audio_hw_device_t *hwDevice = audioHwDevice->hwDevice(); 294 status = hwDevice->create_audio_patch(hwDevice, 295 patch->num_sources, 296 patch->sources, 297 patch->num_sinks, 298 patch->sinks, 299 &halHandle); 300 } 301 } 302 } break; 303 case AUDIO_PORT_TYPE_MIX: { 304 audio_module_handle_t srcModule = patch->sources[0].ext.mix.hw_module; 305 ssize_t index = audioflinger->mAudioHwDevs.indexOfKey(srcModule); 306 if (index < 0) { 307 ALOGW("createAudioPatch() bad src hw module %d", srcModule); 308 status = BAD_VALUE; 309 goto exit; 310 } 311 // limit to connections between devices and output streams 312 audio_devices_t type = AUDIO_DEVICE_NONE; 313 for (unsigned int i = 0; i < patch->num_sinks; i++) { 314 if (patch->sinks[i].type != AUDIO_PORT_TYPE_DEVICE) { 315 ALOGW("createAudioPatch() invalid sink type %d for mix source", 316 patch->sinks[i].type); 317 status = BAD_VALUE; 318 goto exit; 319 } 320 // limit to connections between sinks and sources on same HW module 321 if (patch->sinks[i].ext.device.hw_module != srcModule) { 322 status = BAD_VALUE; 323 goto exit; 324 } 325 type |= patch->sinks[i].ext.device.type; 326 } 327 sp<ThreadBase> thread = 328 audioflinger->checkPlaybackThread_l(patch->sources[0].ext.mix.handle); 329 if (thread == 0) { 330 ALOGW("createAudioPatch() bad playback I/O handle %d", 331 patch->sources[0].ext.mix.handle); 332 status = BAD_VALUE; 333 goto exit; 334 } 335 if (thread == audioflinger->primaryPlaybackThread_l()) { 336 AudioParameter param = AudioParameter(); 337 param.addInt(String8(AUDIO_PARAMETER_STREAM_ROUTING), (int)type); 338 339 audioflinger->broacastParametersToRecordThreads_l(param.toString()); 340 } 341 342 status = thread->sendCreateAudioPatchConfigEvent(patch, &halHandle); 343 } break; 344 default: 345 status = BAD_VALUE; 346 goto exit; 347 } 348exit: 349 ALOGV("createAudioPatch() status %d", status); 350 if (status == NO_ERROR) { 351 *handle = audioflinger->nextUniqueId(); 352 newPatch->mHandle = *handle; 353 newPatch->mHalHandle = halHandle; 354 mPatches.add(newPatch); 355 ALOGV("createAudioPatch() added new patch handle %d halHandle %d", *handle, halHandle); 356 } else { 357 clearPatchConnections(newPatch); 358 delete newPatch; 359 } 360 return status; 361} 362 363status_t AudioFlinger::PatchPanel::createPatchConnections(Patch *patch, 364 const struct audio_patch *audioPatch) 365{ 366 // create patch from source device to record thread input 367 struct audio_patch subPatch; 368 subPatch.num_sources = 1; 369 subPatch.sources[0] = audioPatch->sources[0]; 370 subPatch.num_sinks = 1; 371 372 patch->mRecordThread->getAudioPortConfig(&subPatch.sinks[0]); 373 subPatch.sinks[0].ext.mix.usecase.source = AUDIO_SOURCE_MIC; 374 375 status_t status = createAudioPatch(&subPatch, &patch->mRecordPatchHandle); 376 if (status != NO_ERROR) { 377 patch->mRecordPatchHandle = AUDIO_PATCH_HANDLE_NONE; 378 return status; 379 } 380 381 // create patch from playback thread output to sink device 382 patch->mPlaybackThread->getAudioPortConfig(&subPatch.sources[0]); 383 subPatch.sinks[0] = audioPatch->sinks[0]; 384 status = createAudioPatch(&subPatch, &patch->mPlaybackPatchHandle); 385 if (status != NO_ERROR) { 386 patch->mPlaybackPatchHandle = AUDIO_PATCH_HANDLE_NONE; 387 return status; 388 } 389 390 // use a pseudo LCM between input and output framecount 391 size_t playbackFrameCount = patch->mPlaybackThread->frameCount(); 392 int playbackShift = __builtin_ctz(playbackFrameCount); 393 size_t recordFramecount = patch->mRecordThread->frameCount(); 394 int shift = __builtin_ctz(recordFramecount); 395 if (playbackShift < shift) { 396 shift = playbackShift; 397 } 398 size_t frameCount = (playbackFrameCount * recordFramecount) >> shift; 399 ALOGV("createPatchConnections() playframeCount %d recordFramecount %d frameCount %d ", 400 playbackFrameCount, recordFramecount, frameCount); 401 402 // create a special record track to capture from record thread 403 uint32_t channelCount = patch->mPlaybackThread->channelCount(); 404 audio_channel_mask_t inChannelMask = audio_channel_in_mask_from_count(channelCount); 405 audio_channel_mask_t outChannelMask = patch->mPlaybackThread->channelMask(); 406 uint32_t sampleRate = patch->mPlaybackThread->sampleRate(); 407 audio_format_t format = patch->mPlaybackThread->format(); 408 409 patch->mPatchRecord = new RecordThread::PatchRecord( 410 patch->mRecordThread.get(), 411 sampleRate, 412 inChannelMask, 413 format, 414 frameCount, 415 NULL, 416 IAudioFlinger::TRACK_DEFAULT); 417 if (patch->mPatchRecord == 0) { 418 return NO_MEMORY; 419 } 420 status = patch->mPatchRecord->initCheck(); 421 if (status != NO_ERROR) { 422 return status; 423 } 424 patch->mRecordThread->addPatchRecord(patch->mPatchRecord); 425 426 // create a special playback track to render to playback thread. 427 // this track is given the same buffer as the PatchRecord buffer 428 patch->mPatchTrack = new PlaybackThread::PatchTrack( 429 patch->mPlaybackThread.get(), 430 audioPatch->sources[1].ext.mix.usecase.stream, 431 sampleRate, 432 outChannelMask, 433 format, 434 frameCount, 435 patch->mPatchRecord->buffer(), 436 IAudioFlinger::TRACK_DEFAULT); 437 if (patch->mPatchTrack == 0) { 438 return NO_MEMORY; 439 } 440 status = patch->mPatchTrack->initCheck(); 441 if (status != NO_ERROR) { 442 return status; 443 } 444 patch->mPlaybackThread->addPatchTrack(patch->mPatchTrack); 445 446 // tie playback and record tracks together 447 patch->mPatchRecord->setPeerProxy(patch->mPatchTrack.get()); 448 patch->mPatchTrack->setPeerProxy(patch->mPatchRecord.get()); 449 450 // start capture and playback 451 patch->mPatchRecord->start(AudioSystem::SYNC_EVENT_NONE, 0); 452 patch->mPatchTrack->start(); 453 454 return status; 455} 456 457void AudioFlinger::PatchPanel::clearPatchConnections(Patch *patch) 458{ 459 sp<AudioFlinger> audioflinger = mAudioFlinger.promote(); 460 if (audioflinger == 0) { 461 return; 462 } 463 464 ALOGV("clearPatchConnections() patch->mRecordPatchHandle %d patch->mPlaybackPatchHandle %d", 465 patch->mRecordPatchHandle, patch->mPlaybackPatchHandle); 466 467 if (patch->mPatchRecord != 0) { 468 patch->mPatchRecord->stop(); 469 } 470 if (patch->mPatchTrack != 0) { 471 patch->mPatchTrack->stop(); 472 } 473 if (patch->mRecordPatchHandle != AUDIO_PATCH_HANDLE_NONE) { 474 releaseAudioPatch(patch->mRecordPatchHandle); 475 patch->mRecordPatchHandle = AUDIO_PATCH_HANDLE_NONE; 476 } 477 if (patch->mPlaybackPatchHandle != AUDIO_PATCH_HANDLE_NONE) { 478 releaseAudioPatch(patch->mPlaybackPatchHandle); 479 patch->mPlaybackPatchHandle = AUDIO_PATCH_HANDLE_NONE; 480 } 481 if (patch->mRecordThread != 0) { 482 if (patch->mPatchRecord != 0) { 483 patch->mRecordThread->deletePatchRecord(patch->mPatchRecord); 484 patch->mPatchRecord.clear(); 485 } 486 audioflinger->closeInputInternal_l(patch->mRecordThread); 487 patch->mRecordThread.clear(); 488 } 489 if (patch->mPlaybackThread != 0) { 490 if (patch->mPatchTrack != 0) { 491 patch->mPlaybackThread->deletePatchTrack(patch->mPatchTrack); 492 patch->mPatchTrack.clear(); 493 } 494 // if num sources == 2 we are reusing an existing playback thread so we do not close it 495 if (patch->mAudioPatch.num_sources != 2) { 496 audioflinger->closeOutputInternal_l(patch->mPlaybackThread); 497 } 498 patch->mPlaybackThread.clear(); 499 } 500} 501 502/* Disconnect a patch */ 503status_t AudioFlinger::PatchPanel::releaseAudioPatch(audio_patch_handle_t handle) 504{ 505 ALOGV("releaseAudioPatch handle %d", handle); 506 status_t status = NO_ERROR; 507 size_t index; 508 509 sp<AudioFlinger> audioflinger = mAudioFlinger.promote(); 510 if (audioflinger == 0) { 511 return NO_INIT; 512 } 513 514 for (index = 0; index < mPatches.size(); index++) { 515 if (handle == mPatches[index]->mHandle) { 516 break; 517 } 518 } 519 if (index == mPatches.size()) { 520 return BAD_VALUE; 521 } 522 Patch *removedPatch = mPatches[index]; 523 mPatches.removeAt(index); 524 525 struct audio_patch *patch = &removedPatch->mAudioPatch; 526 527 switch (patch->sources[0].type) { 528 case AUDIO_PORT_TYPE_DEVICE: { 529 audio_module_handle_t srcModule = patch->sources[0].ext.device.hw_module; 530 ssize_t index = audioflinger->mAudioHwDevs.indexOfKey(srcModule); 531 if (index < 0) { 532 ALOGW("releaseAudioPatch() bad src hw module %d", srcModule); 533 status = BAD_VALUE; 534 break; 535 } 536 537 if (removedPatch->mRecordPatchHandle != AUDIO_PATCH_HANDLE_NONE || 538 removedPatch->mPlaybackPatchHandle != AUDIO_PATCH_HANDLE_NONE) { 539 clearPatchConnections(removedPatch); 540 break; 541 } 542 543 if (patch->sinks[0].type == AUDIO_PORT_TYPE_MIX) { 544 sp<ThreadBase> thread = audioflinger->checkRecordThread_l( 545 patch->sinks[0].ext.mix.handle); 546 if (thread == 0) { 547 ALOGW("releaseAudioPatch() bad capture I/O handle %d", 548 patch->sinks[0].ext.mix.handle); 549 status = BAD_VALUE; 550 break; 551 } 552 status = thread->sendReleaseAudioPatchConfigEvent(removedPatch->mHalHandle); 553 } else { 554 AudioHwDevice *audioHwDevice = audioflinger->mAudioHwDevs.valueAt(index); 555 if (audioHwDevice->version() < AUDIO_DEVICE_API_VERSION_3_0) { 556 status = INVALID_OPERATION; 557 break; 558 } 559 audio_hw_device_t *hwDevice = audioHwDevice->hwDevice(); 560 status = hwDevice->release_audio_patch(hwDevice, removedPatch->mHalHandle); 561 } 562 } break; 563 case AUDIO_PORT_TYPE_MIX: { 564 audio_module_handle_t srcModule = patch->sources[0].ext.mix.hw_module; 565 ssize_t index = audioflinger->mAudioHwDevs.indexOfKey(srcModule); 566 if (index < 0) { 567 ALOGW("releaseAudioPatch() bad src hw module %d", srcModule); 568 status = BAD_VALUE; 569 break; 570 } 571 sp<ThreadBase> thread = 572 audioflinger->checkPlaybackThread_l(patch->sources[0].ext.mix.handle); 573 if (thread == 0) { 574 ALOGW("releaseAudioPatch() bad playback I/O handle %d", 575 patch->sources[0].ext.mix.handle); 576 status = BAD_VALUE; 577 break; 578 } 579 status = thread->sendReleaseAudioPatchConfigEvent(removedPatch->mHalHandle); 580 } break; 581 default: 582 status = BAD_VALUE; 583 break; 584 } 585 586 delete removedPatch; 587 return status; 588} 589 590 591/* List connected audio ports and they attributes */ 592status_t AudioFlinger::PatchPanel::listAudioPatches(unsigned int *num_patches __unused, 593 struct audio_patch *patches __unused) 594{ 595 ALOGV("listAudioPatches"); 596 return NO_ERROR; 597} 598 599/* Set audio port configuration */ 600status_t AudioFlinger::PatchPanel::setAudioPortConfig(const struct audio_port_config *config) 601{ 602 ALOGV("setAudioPortConfig"); 603 status_t status = NO_ERROR; 604 605 sp<AudioFlinger> audioflinger = mAudioFlinger.promote(); 606 if (audioflinger == 0) { 607 return NO_INIT; 608 } 609 610 audio_module_handle_t module; 611 if (config->type == AUDIO_PORT_TYPE_DEVICE) { 612 module = config->ext.device.hw_module; 613 } else { 614 module = config->ext.mix.hw_module; 615 } 616 617 ssize_t index = audioflinger->mAudioHwDevs.indexOfKey(module); 618 if (index < 0) { 619 ALOGW("setAudioPortConfig() bad hw module %d", module); 620 return BAD_VALUE; 621 } 622 623 AudioHwDevice *audioHwDevice = audioflinger->mAudioHwDevs.valueAt(index); 624 if (audioHwDevice->version() >= AUDIO_DEVICE_API_VERSION_3_0) { 625 audio_hw_device_t *hwDevice = audioHwDevice->hwDevice(); 626 return hwDevice->set_audio_port_config(hwDevice, config); 627 } else { 628 return INVALID_OPERATION; 629 } 630 return NO_ERROR; 631} 632 633} // namespace android 634