/* * Copyright (C) 2021 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_NDEBUG 0 #define LOG_TAG "AudioTestUtils" #include #include #include #include "audio_test_utils.h" #define WAIT_PERIOD_MS 10 // from AudioTrack.cpp #define MAX_WAIT_TIME_MS 5000 template constexpr void (*xmlDeleter)(T* t); template <> constexpr auto xmlDeleter = xmlFreeDoc; template <> constexpr auto xmlDeleter = [](xmlChar* s) { xmlFree(s); }; /** @return a unique_ptr with the correct deleter for the libxml2 object. */ template constexpr auto make_xmlUnique(T* t) { // Wrap deleter in lambda to enable empty base optimization auto deleter = [](T* t) { xmlDeleter(t); }; return std::unique_ptr{t, deleter}; } void OnAudioDeviceUpdateNotifier::onAudioDeviceUpdate(audio_io_handle_t audioIo, audio_port_handle_t deviceId) { std::unique_lock lock{mMutex}; ALOGD("%s audioIo=%d deviceId=%d", __func__, audioIo, deviceId); mAudioIo = audioIo; mDeviceId = deviceId; mCondition.notify_all(); } status_t OnAudioDeviceUpdateNotifier::waitForAudioDeviceCb(audio_port_handle_t expDeviceId) { std::unique_lock lock{mMutex}; if (mAudioIo == AUDIO_IO_HANDLE_NONE || (expDeviceId != AUDIO_PORT_HANDLE_NONE && expDeviceId != mDeviceId)) { mCondition.wait_for(lock, std::chrono::milliseconds(500)); if (mAudioIo == AUDIO_IO_HANDLE_NONE || (expDeviceId != AUDIO_PORT_HANDLE_NONE && expDeviceId != mDeviceId)) return TIMED_OUT; } return OK; } AudioPlayback::AudioPlayback(uint32_t sampleRate, audio_format_t format, audio_channel_mask_t channelMask, audio_output_flags_t flags, audio_session_t sessionId, AudioTrack::transfer_type transferType, audio_attributes_t* attributes, audio_offload_info_t* info) : mSampleRate(sampleRate), mFormat(format), mChannelMask(channelMask), mFlags(flags), mSessionId(sessionId), mTransferType(transferType), mAttributes(attributes), mOffloadInfo(info) { mStopPlaying = false; mBytesUsedSoFar = 0; mState = PLAY_NO_INIT; mMemCapacity = 0; mMemoryDealer = nullptr; mMemory = nullptr; } AudioPlayback::~AudioPlayback() { stop(); } status_t AudioPlayback::create() { if (mState != PLAY_NO_INIT) return INVALID_OPERATION; std::string packageName{"AudioPlayback"}; AttributionSourceState attributionSource; attributionSource.packageName = packageName; attributionSource.uid = VALUE_OR_FATAL(legacy2aidl_uid_t_int32_t(getuid())); attributionSource.pid = VALUE_OR_FATAL(legacy2aidl_pid_t_int32_t(getpid())); attributionSource.token = sp::make(); if (mTransferType == AudioTrack::TRANSFER_OBTAIN) { mTrack = new AudioTrack(attributionSource); mTrack->set(AUDIO_STREAM_MUSIC, mSampleRate, mFormat, mChannelMask, 0 /* frameCount */, mFlags, nullptr /* callback */, 0 /* notificationFrames */, nullptr /* sharedBuffer */, false /*canCallJava */, mSessionId, mTransferType, mOffloadInfo, attributionSource, mAttributes); } else if (mTransferType == AudioTrack::TRANSFER_SHARED) { mTrack = new AudioTrack(AUDIO_STREAM_MUSIC, mSampleRate, mFormat, mChannelMask, mMemory, mFlags, wp::fromExisting(this), 0, mSessionId, mTransferType, nullptr, attributionSource, mAttributes); } else { ALOGE("Test application is not handling transfer type %s", AudioTrack::convertTransferToText(mTransferType)); return INVALID_OPERATION; } mTrack->setCallerName(packageName); status_t status = mTrack->initCheck(); if (NO_ERROR == status) mState = PLAY_READY; return status; } status_t AudioPlayback::loadResource(const char* name) { status_t status = OK; FILE* fp = fopen(name, "rbe"); struct stat buf {}; if (fp && !fstat(fileno(fp), &buf)) { mMemCapacity = buf.st_size; mMemoryDealer = new MemoryDealer(mMemCapacity, "AudioPlayback"); if (nullptr == mMemoryDealer.get()) { ALOGE("couldn't get MemoryDealer!"); fclose(fp); return NO_MEMORY; } mMemory = mMemoryDealer->allocate(mMemCapacity); if (nullptr == mMemory.get()) { ALOGE("couldn't get IMemory!"); fclose(fp); return NO_MEMORY; } uint8_t* ipBuffer = static_cast(static_cast(mMemory->unsecurePointer())); fread(ipBuffer, sizeof(uint8_t), mMemCapacity, fp); } else { ALOGE("unable to open input file %s", name); status = NAME_NOT_FOUND; } if (fp) fclose(fp); return status; } sp AudioPlayback::getAudioTrackHandle() { return (PLAY_NO_INIT != mState) ? mTrack : nullptr; } status_t AudioPlayback::start() { status_t status; if (PLAY_READY != mState) { return INVALID_OPERATION; } else { status = mTrack->start(); if (OK == status) { mState = PLAY_STARTED; LOG_FATAL_IF(false != mTrack->stopped()); } } return status; } void AudioPlayback::onBufferEnd() { std::unique_lock lock{mMutex}; mStopPlaying = true; mCondition.notify_all(); } status_t AudioPlayback::fillBuffer() { if (PLAY_STARTED != mState) return INVALID_OPERATION; const int maxTries = MAX_WAIT_TIME_MS / WAIT_PERIOD_MS; int counter = 0; uint8_t* ipBuffer = static_cast(static_cast(mMemory->unsecurePointer())); size_t nonContig = 0; size_t bytesAvailable = mMemCapacity - mBytesUsedSoFar; while (bytesAvailable > 0) { AudioTrack::Buffer trackBuffer; trackBuffer.frameCount = mTrack->frameCount() * 2; status_t status = mTrack->obtainBuffer(&trackBuffer, 1, &nonContig); if (OK == status) { size_t bytesToCopy = std::min(bytesAvailable, trackBuffer.size()); if (bytesToCopy > 0) { memcpy(trackBuffer.data(), ipBuffer + mBytesUsedSoFar, bytesToCopy); } mTrack->releaseBuffer(&trackBuffer); mBytesUsedSoFar += bytesToCopy; bytesAvailable = mMemCapacity - mBytesUsedSoFar; counter = 0; } else if (WOULD_BLOCK == status) { // if not received a buffer for MAX_WAIT_TIME_MS, something has gone wrong if (counter == maxTries) return TIMED_OUT; counter++; } } return OK; } status_t AudioPlayback::waitForConsumption(bool testSeek) { if (PLAY_STARTED != mState) return INVALID_OPERATION; const int maxTries = MAX_WAIT_TIME_MS / WAIT_PERIOD_MS; int counter = 0; size_t totalFrameCount = mMemCapacity / mTrack->frameSize(); while (!mStopPlaying && counter < maxTries) { uint32_t currPosition; mTrack->getPosition(&currPosition); if (currPosition >= totalFrameCount) counter++; if (testSeek && (currPosition > totalFrameCount * 0.6)) { testSeek = false; if (!mTrack->hasStarted()) return BAD_VALUE; mTrack->pauseAndWait(std::chrono::seconds(2)); if (mTrack->hasStarted()) return BAD_VALUE; mTrack->reload(); mTrack->getPosition(&currPosition); if (currPosition != 0) return BAD_VALUE; mTrack->start(); while (currPosition < totalFrameCount * 0.3) { mTrack->getPosition(&currPosition); } mTrack->pauseAndWait(std::chrono::seconds(2)); uint32_t setPosition = totalFrameCount * 0.9; mTrack->setPosition(setPosition); uint32_t bufferPosition; mTrack->getBufferPosition(&bufferPosition); if (bufferPosition != setPosition) return BAD_VALUE; mTrack->start(); } std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_PERIOD_MS)); } if (!mStopPlaying && counter == maxTries) return TIMED_OUT; return OK; } status_t AudioPlayback::onProcess(bool testSeek) { if (mTransferType == AudioTrack::TRANSFER_SHARED) return waitForConsumption(testSeek); else if (mTransferType == AudioTrack::TRANSFER_OBTAIN) return fillBuffer(); else return INVALID_OPERATION; } void AudioPlayback::stop() { std::unique_lock lock{mMutex}; mStopPlaying = true; if (mState != PLAY_STOPPED && mState != PLAY_NO_INIT) { int32_t msec = 0; (void)mTrack->pendingDuration(&msec); mTrack->stopAndJoinCallbacks(); LOG_FATAL_IF(true != mTrack->stopped()); mState = PLAY_STOPPED; if (msec > 0) { ALOGD("deleting recycled track, waiting for data drain (%d msec)", msec); usleep(msec * 1000LL); } } } // hold pcm data sent by AudioRecord RawBuffer::RawBuffer(int64_t ptsPipeline, int64_t ptsManual, int32_t capacity) : mData(capacity > 0 ? new uint8_t[capacity] : nullptr), mPtsPipeline(ptsPipeline), mPtsManual(ptsManual), mCapacity(capacity) {} // Simple AudioCapture size_t AudioCapture::onMoreData(const AudioRecord::Buffer& buffer) { if (mState != REC_STARTED) { ALOGE("Unexpected Callback from audiorecord, not reading data"); return 0; } // no more frames to read if (mNumFramesReceived >= mNumFramesToRecord || mStopRecording) { mStopRecording = true; return 0; } int64_t timeUs = 0, position = 0, timeNs = 0; ExtendedTimestamp ts; ExtendedTimestamp::Location location; const int32_t usPerSec = 1000000; if (mRecord->getTimestamp(&ts) == OK && ts.getBestTimestamp(&position, &timeNs, ExtendedTimestamp::TIMEBASE_MONOTONIC, &location) == OK) { // Use audio timestamp. timeUs = timeNs / 1000 - (position - mNumFramesReceived + mNumFramesLost) * usPerSec / mSampleRate; } else { // This should not happen in normal case. ALOGW("Failed to get audio timestamp, fallback to use systemclock"); timeUs = systemTime() / 1000LL; // Estimate the real sampling time of the 1st sample in this buffer // from AudioRecord's latency. (Apply this adjustment first so that // the start time logic is not affected.) timeUs -= mRecord->latency() * 1000LL; } ALOGV("dataCallbackTimestamp: %" PRId64 " us", timeUs); const size_t frameSize = mRecord->frameSize(); uint64_t numLostBytes = (uint64_t)mRecord->getInputFramesLost() * frameSize; if (numLostBytes > 0) { ALOGW("Lost audio record data: %" PRIu64 " bytes", numLostBytes); } std::deque tmpQueue; while (numLostBytes > 0) { uint64_t bufferSize = numLostBytes; if (numLostBytes > mMaxBytesPerCallback) { numLostBytes -= mMaxBytesPerCallback; bufferSize = mMaxBytesPerCallback; } else { numLostBytes = 0; } const int64_t timestampUs = ((1000000LL * mNumFramesReceived) + (mRecord->getSampleRate() >> 1)) / mRecord->getSampleRate(); RawBuffer emptyBuffer{timeUs, timestampUs, static_cast(bufferSize)}; memset(emptyBuffer.mData.get(), 0, bufferSize); mNumFramesLost += bufferSize / frameSize; mNumFramesReceived += bufferSize / frameSize; tmpQueue.push_back(std::move(emptyBuffer)); } if (buffer.size() == 0) { ALOGW("Nothing is available from AudioRecord callback buffer"); } else { const size_t bufferSize = buffer.size(); const int64_t timestampUs = ((1000000LL * mNumFramesReceived) + (mRecord->getSampleRate() >> 1)) / mRecord->getSampleRate(); RawBuffer audioBuffer{timeUs, timestampUs, static_cast(bufferSize)}; memcpy(audioBuffer.mData.get(), buffer.data(), bufferSize); mNumFramesReceived += bufferSize / frameSize; tmpQueue.push_back(std::move(audioBuffer)); } if (tmpQueue.size() > 0) { std::unique_lock lock{mMutex}; for (auto it = tmpQueue.begin(); it != tmpQueue.end(); it++) mBuffersReceived.push_back(std::move(*it)); mCondition.notify_all(); } return buffer.size(); } void AudioCapture::onOverrun() { ALOGV("received event overrun"); mBufferOverrun = true; } void AudioCapture::onMarker(uint32_t markerPosition) { ALOGV("received Callback at position %d", markerPosition); mReceivedCbMarkerAtPosition = markerPosition; } void AudioCapture::onNewPos(uint32_t markerPosition) { ALOGV("received Callback at position %d", markerPosition); mReceivedCbMarkerCount++; } void AudioCapture::onNewIAudioRecord() { ALOGV("IAudioRecord is re-created"); } AudioCapture::AudioCapture(audio_source_t inputSource, uint32_t sampleRate, audio_format_t format, audio_channel_mask_t channelMask, audio_input_flags_t flags, audio_session_t sessionId, AudioRecord::transfer_type transferType, const audio_attributes_t* attributes) : mInputSource(inputSource), mSampleRate(sampleRate), mFormat(format), mChannelMask(channelMask), mFlags(flags), mSessionId(sessionId), mTransferType(transferType), mAttributes(attributes) { mFrameCount = 0; mNotificationFrames = 0; mNumFramesToRecord = 0; mNumFramesReceived = 0; mNumFramesLost = 0; mBufferOverrun = false; mMarkerPosition = 0; mMarkerPeriod = 0; mReceivedCbMarkerAtPosition = -1; mReceivedCbMarkerCount = 0; mState = REC_NO_INIT; mStopRecording = false; } AudioCapture::~AudioCapture() { if (mOutFileFd > 0) close(mOutFileFd); stop(); } status_t AudioCapture::create() { if (mState != REC_NO_INIT) return INVALID_OPERATION; // get Min Frame Count size_t minFrameCount; status_t status = AudioRecord::getMinFrameCount(&minFrameCount, mSampleRate, mFormat, mChannelMask); if (NO_ERROR != status) return status; // Limit notificationFrames basing on client bufferSize const int samplesPerFrame = audio_channel_count_from_in_mask(mChannelMask); const int bytesPerSample = audio_bytes_per_sample(mFormat); mNotificationFrames = mMaxBytesPerCallback / (samplesPerFrame * bytesPerSample); // select frameCount to be at least minFrameCount mFrameCount = 2 * mNotificationFrames; while (mFrameCount < minFrameCount) { mFrameCount += mNotificationFrames; } if (mFlags & AUDIO_INPUT_FLAG_FAST) { ALOGW("Overriding all previous computations"); mFrameCount = 0; mNotificationFrames = 0; } mNumFramesToRecord = (mSampleRate * 0.25); // record .25 sec std::string packageName{"AudioCapture"}; AttributionSourceState attributionSource; attributionSource.packageName = packageName; attributionSource.uid = VALUE_OR_FATAL(legacy2aidl_uid_t_int32_t(getuid())); attributionSource.pid = VALUE_OR_FATAL(legacy2aidl_pid_t_int32_t(getpid())); attributionSource.token = sp::make(); if (mTransferType == AudioRecord::TRANSFER_OBTAIN) { if (mSampleRate == 48000) { // test all available constructors mRecord = new AudioRecord(mInputSource, mSampleRate, mFormat, mChannelMask, attributionSource, mFrameCount, nullptr /* callback */, mNotificationFrames, mSessionId, mTransferType, mFlags, mAttributes); } else { mRecord = new AudioRecord(attributionSource); status = mRecord->set(mInputSource, mSampleRate, mFormat, mChannelMask, mFrameCount, nullptr /* callback */, 0 /* notificationFrames */, false /* canCallJava */, mSessionId, mTransferType, mFlags, attributionSource.uid, attributionSource.pid, mAttributes); } if (NO_ERROR != status) return status; } else if (mTransferType == AudioRecord::TRANSFER_CALLBACK) { mRecord = new AudioRecord(mInputSource, mSampleRate, mFormat, mChannelMask, attributionSource, mFrameCount, this, mNotificationFrames, mSessionId, mTransferType, mFlags, mAttributes); } else { ALOGE("Test application is not handling transfer type %s", AudioRecord::convertTransferToText(mTransferType)); return NO_INIT; } mRecord->setCallerName(packageName); status = mRecord->initCheck(); if (NO_ERROR == status) mState = REC_READY; if (mFlags & AUDIO_INPUT_FLAG_FAST) { mFrameCount = mRecord->frameCount(); mNotificationFrames = mRecord->getNotificationPeriodInFrames(); mMaxBytesPerCallback = mNotificationFrames * samplesPerFrame * bytesPerSample; } return status; } status_t AudioCapture::setRecordDuration(float durationInSec) { if (REC_READY != mState) { return INVALID_OPERATION; } uint32_t sampleRate = mSampleRate == 0 ? mRecord->getSampleRate() : mSampleRate; mNumFramesToRecord = (sampleRate * durationInSec); return OK; } status_t AudioCapture::enableRecordDump() { if (mOutFileFd != -1) { return INVALID_OPERATION; } TemporaryFile tf("/data/local/tmp"); tf.DoNotRemove(); mOutFileFd = tf.release(); mFileName = std::string{tf.path}; return OK; } sp AudioCapture::getAudioRecordHandle() { return (REC_NO_INIT == mState) ? nullptr : mRecord; } status_t AudioCapture::start(AudioSystem::sync_event_t event, audio_session_t triggerSession) { status_t status; if (REC_READY != mState) { return INVALID_OPERATION; } else { status = mRecord->start(event, triggerSession); if (OK == status) { mState = REC_STARTED; LOG_FATAL_IF(false != mRecord->stopped()); } } return status; } status_t AudioCapture::stop() { status_t status = OK; mStopRecording = true; if (mState != REC_STOPPED && mState != REC_NO_INIT) { if (mInputSource != AUDIO_SOURCE_DEFAULT) { bool state = false; status = AudioSystem::isSourceActive(mInputSource, &state); if (status == OK && !state) status = BAD_VALUE; } mRecord->stopAndJoinCallbacks(); mState = REC_STOPPED; LOG_FATAL_IF(true != mRecord->stopped()); } return status; } status_t AudioCapture::obtainBuffer(RawBuffer& buffer) { if (REC_STARTED != mState) return INVALID_OPERATION; const int maxTries = MAX_WAIT_TIME_MS / WAIT_PERIOD_MS; int counter = 0; size_t nonContig = 0; while (mNumFramesReceived < mNumFramesToRecord) { AudioRecord::Buffer recordBuffer; recordBuffer.frameCount = mNotificationFrames; status_t status = mRecord->obtainBuffer(&recordBuffer, 1, &nonContig); if (OK == status) { const int64_t timestampUs = ((1000000LL * mNumFramesReceived) + (mRecord->getSampleRate() >> 1)) / mRecord->getSampleRate(); RawBuffer buff{-1, timestampUs, static_cast(recordBuffer.size())}; memcpy(buff.mData.get(), recordBuffer.data(), recordBuffer.size()); buffer = std::move(buff); mNumFramesReceived += recordBuffer.size() / mRecord->frameSize(); mRecord->releaseBuffer(&recordBuffer); counter = 0; } else if (WOULD_BLOCK == status) { // if not received a buffer for MAX_WAIT_TIME_MS, something has gone wrong if (counter == maxTries) return TIMED_OUT; counter++; } } return OK; } status_t AudioCapture::obtainBufferCb(RawBuffer& buffer) { if (REC_STARTED != mState) return INVALID_OPERATION; const int maxTries = MAX_WAIT_TIME_MS / WAIT_PERIOD_MS; int counter = 0; std::unique_lock lock{mMutex}; while (mBuffersReceived.empty() && !mStopRecording && counter < maxTries) { mCondition.wait_for(lock, std::chrono::milliseconds(WAIT_PERIOD_MS)); counter++; } if (!mBuffersReceived.empty()) { auto it = mBuffersReceived.begin(); buffer = std::move(*it); mBuffersReceived.erase(it); } else { if (!mStopRecording && counter == maxTries) return TIMED_OUT; } return OK; } status_t AudioCapture::audioProcess() { RawBuffer buffer; status_t status = OK; while (mNumFramesReceived < mNumFramesToRecord && status == OK) { if (mTransferType == AudioRecord::TRANSFER_CALLBACK) status = obtainBufferCb(buffer); else status = obtainBuffer(buffer); if (OK == status && mOutFileFd > 0) { const char* ptr = static_cast(static_cast(buffer.mData.get())); write(mOutFileFd, ptr, buffer.mCapacity); } } return OK; } status_t listAudioPorts(std::vector& portsVec) { int attempts = 5; status_t status; unsigned int generation1, generation; unsigned int numPorts = 0; do { if (attempts-- < 0) { status = TIMED_OUT; break; } status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, AUDIO_PORT_TYPE_NONE, &numPorts, nullptr, &generation1); if (status != NO_ERROR) { ALOGE("AudioSystem::listAudioPorts returned error %d", status); break; } portsVec.resize(numPorts); status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, AUDIO_PORT_TYPE_NONE, &numPorts, portsVec.data(), &generation); } while (generation1 != generation && status == NO_ERROR); if (status != NO_ERROR) { numPorts = 0; portsVec.clear(); } return status; } status_t getPortById(const audio_port_handle_t portId, audio_port_v7& port) { std::vector ports; status_t status = listAudioPorts(ports); if (status != OK) return status; for (auto i = 0; i < ports.size(); i++) { if (ports[i].id == portId) { port = ports[i]; return OK; } } return BAD_VALUE; } status_t getPortByAttributes(audio_port_role_t role, audio_port_type_t type, audio_devices_t deviceType, const std::string& address, audio_port_v7& port) { std::vector ports; status_t status = listAudioPorts(ports); if (status != OK) return status; for (auto i = 0; i < ports.size(); i++) { if (ports[i].role == role && ports[i].type == type && ports[i].ext.device.type == deviceType && !strncmp(ports[i].ext.device.address, address.c_str(), AUDIO_DEVICE_MAX_ADDRESS_LEN)) { port = ports[i]; return OK; } } return BAD_VALUE; } status_t listAudioPatches(std::vector& patchesVec) { int attempts = 5; status_t status; unsigned int generation1, generation; unsigned int numPatches = 0; do { if (attempts-- < 0) { status = TIMED_OUT; break; } status = AudioSystem::listAudioPatches(&numPatches, nullptr, &generation1); if (status != NO_ERROR) { ALOGE("AudioSystem::listAudioPatches returned error %d", status); break; } patchesVec.resize(numPatches); status = AudioSystem::listAudioPatches(&numPatches, patchesVec.data(), &generation); } while (generation1 != generation && status == NO_ERROR); if (status != NO_ERROR) { numPatches = 0; patchesVec.clear(); } return status; } status_t getPatchForOutputMix(audio_io_handle_t audioIo, audio_patch& patch) { std::vector patches; status_t status = listAudioPatches(patches); if (status != OK) return status; for (auto i = 0; i < patches.size(); i++) { for (auto j = 0; j < patches[i].num_sources; j++) { if (patches[i].sources[j].type == AUDIO_PORT_TYPE_MIX && patches[i].sources[j].ext.mix.handle == audioIo) { patch = patches[i]; return OK; } } } return BAD_VALUE; } status_t getPatchForInputMix(audio_io_handle_t audioIo, audio_patch& patch) { std::vector patches; status_t status = listAudioPatches(patches); if (status != OK) return status; for (auto i = 0; i < patches.size(); i++) { for (auto j = 0; j < patches[i].num_sinks; j++) { if (patches[i].sinks[j].type == AUDIO_PORT_TYPE_MIX && patches[i].sinks[j].ext.mix.handle == audioIo) { patch = patches[i]; return OK; } } } return BAD_VALUE; } bool patchContainsOutputDevice(audio_port_handle_t deviceId, audio_patch patch) { for (auto j = 0; j < patch.num_sinks; j++) { if (patch.sinks[j].type == AUDIO_PORT_TYPE_DEVICE && patch.sinks[j].id == deviceId) { return true; } } return false; } bool patchContainsInputDevice(audio_port_handle_t deviceId, audio_patch patch) { for (auto j = 0; j < patch.num_sources; j++) { if (patch.sources[j].type == AUDIO_PORT_TYPE_DEVICE && patch.sources[j].id == deviceId) { return true; } } return false; } bool checkPatchPlayback(audio_io_handle_t audioIo, audio_port_handle_t deviceId) { struct audio_patch patch; if (getPatchForOutputMix(audioIo, patch) == OK) { return patchContainsOutputDevice(deviceId, patch); } return false; } bool checkPatchCapture(audio_io_handle_t audioIo, audio_port_handle_t deviceId) { struct audio_patch patch; if (getPatchForInputMix(audioIo, patch) == OK) { return patchContainsInputDevice(deviceId, patch); } return false; } std::string dumpPortConfig(const audio_port_config& port) { std::ostringstream result; std::string deviceInfo; if (port.type == AUDIO_PORT_TYPE_DEVICE) { if (port.ext.device.type & AUDIO_DEVICE_BIT_IN) { InputDeviceConverter::maskToString(port.ext.device.type, deviceInfo); } else { OutputDeviceConverter::maskToString(port.ext.device.type, deviceInfo); } deviceInfo += std::string(", address = ") + port.ext.device.address; } result << "audio_port_handle_t = " << port.id << ", " << "Role = " << (port.role == AUDIO_PORT_ROLE_SOURCE ? "source" : "sink") << ", " << "Type = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix") << ", " << "deviceInfo = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? deviceInfo : "") << ", " << "config_mask = 0x" << std::hex << port.config_mask << std::dec << ", "; if (port.config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) { result << "sample rate = " << port.sample_rate << ", "; } if (port.config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) { result << "channel mask = " << port.channel_mask << ", "; } if (port.config_mask & AUDIO_PORT_CONFIG_FORMAT) { result << "format = " << port.format << ", "; } result << "input flags = " << port.flags.input << ", "; result << "output flags = " << port.flags.output << ", "; result << "mix io handle = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? 0 : port.ext.mix.handle) << "\n"; return result.str(); } std::string dumpPatch(const audio_patch& patch) { std::ostringstream result; result << "----------------- Dumping Patch ------------ \n"; result << "Patch Handle: " << patch.id << ", sources: " << patch.num_sources << ", sink: " << patch.num_sinks << "\n"; audio_port_v7 port; for (uint32_t i = 0; i < patch.num_sources; i++) { result << "----------------- Dumping Source Port Config @ index " << i << " ------------ \n"; result << dumpPortConfig(patch.sources[i]); result << "----------------- Dumping Source Port for id " << patch.sources[i].id << " ------------ \n"; getPortById(patch.sources[i].id, port); result << dumpPort(port); } for (uint32_t i = 0; i < patch.num_sinks; i++) { result << "----------------- Dumping Sink Port Config @ index " << i << " ------------ \n"; result << dumpPortConfig(patch.sinks[i]); result << "----------------- Dumping Sink Port for id " << patch.sinks[i].id << " ------------ \n"; getPortById(patch.sinks[i].id, port); result << dumpPort(port); } return result.str(); } std::string dumpPort(const audio_port_v7& port) { std::ostringstream result; std::string deviceInfo; if (port.type == AUDIO_PORT_TYPE_DEVICE) { if (port.ext.device.type & AUDIO_DEVICE_BIT_IN) { InputDeviceConverter::maskToString(port.ext.device.type, deviceInfo); } else { OutputDeviceConverter::maskToString(port.ext.device.type, deviceInfo); } deviceInfo += std::string(", address = ") + port.ext.device.address; } result << "audio_port_handle_t = " << port.id << ", " << "Role = " << (port.role == AUDIO_PORT_ROLE_SOURCE ? "source" : "sink") << ", " << "Type = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix") << ", " << "deviceInfo = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? deviceInfo : "") << ", " << "Name = " << port.name << ", " << "num profiles = " << port.num_audio_profiles << ", " << "mix io handle = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? 0 : port.ext.mix.handle) << ", "; for (int i = 0; i < port.num_audio_profiles; i++) { result << "AudioProfile = " << i << " {"; result << "format = " << port.audio_profiles[i].format << ", "; result << "samplerates = "; for (int j = 0; j < port.audio_profiles[i].num_sample_rates; j++) { result << port.audio_profiles[i].sample_rates[j] << ", "; } result << "channelmasks = "; for (int j = 0; j < port.audio_profiles[i].num_channel_masks; j++) { result << "0x" << std::hex << port.audio_profiles[i].channel_masks[j] << std::dec << ", "; } result << "} "; } result << dumpPortConfig(port.active_config); return result.str(); } std::string getXmlAttribute(const xmlNode* cur, const char* attribute) { auto charPtr = make_xmlUnique(xmlGetProp(cur, reinterpret_cast(attribute))); if (charPtr == NULL) { return ""; } std::string value(reinterpret_cast(charPtr.get())); return value; } status_t parse_audio_policy_configuration_xml(std::vector& attachedDevices, std::vector& mixPorts, std::vector& routes) { std::string path = audio_find_readable_configuration_file("audio_policy_configuration.xml"); if (path.length() == 0) return UNKNOWN_ERROR; auto doc = make_xmlUnique(xmlParseFile(path.c_str())); if (doc == nullptr) return UNKNOWN_ERROR; xmlNode* root = xmlDocGetRootElement(doc.get()); if (root == nullptr) return UNKNOWN_ERROR; if (xmlXIncludeProcess(doc.get()) < 0) return UNKNOWN_ERROR; mixPorts.clear(); if (!xmlStrcmp(root->name, reinterpret_cast("audioPolicyConfiguration"))) { std::string raw{getXmlAttribute(root, "version")}; for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) { if (!xmlStrcmp(child->name, reinterpret_cast("modules"))) { xmlNode* root = child; for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) { if (!xmlStrcmp(child->name, reinterpret_cast("module"))) { xmlNode* root = child; for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) { if (!xmlStrcmp(child->name, reinterpret_cast("mixPorts"))) { xmlNode* root = child; for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) { if (!xmlStrcmp(child->name, reinterpret_cast("mixPort"))) { MixPort mixPort; xmlNode* root = child; mixPort.name = getXmlAttribute(root, "name"); mixPort.role = getXmlAttribute(root, "role"); mixPort.flags = getXmlAttribute(root, "flags"); if (mixPort.role == "source") mixPorts.push_back(mixPort); } } } else if (!xmlStrcmp(child->name, reinterpret_cast( "attachedDevices"))) { xmlNode* root = child; for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) { if (!xmlStrcmp(child->name, reinterpret_cast("item"))) { auto xmlValue = make_xmlUnique(xmlNodeListGetString( child->doc, child->xmlChildrenNode, 1)); if (xmlValue == nullptr) { raw = ""; } else { raw = reinterpret_cast(xmlValue.get()); } std::string& value = raw; attachedDevices.push_back(std::move(value)); } } } else if (!xmlStrcmp(child->name, reinterpret_cast("routes"))) { xmlNode* root = child; for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) { if (!xmlStrcmp(child->name, reinterpret_cast("route"))) { Route route; xmlNode* root = child; route.name = getXmlAttribute(root, "name"); route.sources = getXmlAttribute(root, "sources"); route.sink = getXmlAttribute(root, "sink"); routes.push_back(route); } } } } } } } } } return OK; }