blob: 1e26ff60113480b87fef58d24cd190d5699a5d71 [file] [log] [blame]
/*
* 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 <android-base/file.h>
#include <system/audio_config.h>
#include <utils/Log.h>
#include "audio_test_utils.h"
#define WAIT_PERIOD_MS 10 // from AudioTrack.cpp
#define MAX_WAIT_TIME_MS 5000
template <class T>
constexpr void (*xmlDeleter)(T* t);
template <>
constexpr auto xmlDeleter<xmlDoc> = xmlFreeDoc;
template <>
constexpr auto xmlDeleter<xmlChar> = [](xmlChar* s) { xmlFree(s); };
/** @return a unique_ptr with the correct deleter for the libxml2 object. */
template <class T>
constexpr auto make_xmlUnique(T* t) {
// Wrap deleter in lambda to enable empty base optimization
auto deleter = [](T* t) { xmlDeleter<T>(t); };
return std::unique_ptr<T, decltype(deleter)>{t, deleter};
}
void OnAudioDeviceUpdateNotifier::onAudioDeviceUpdate(audio_io_handle_t audioIo,
audio_port_handle_t deviceId) {
std::unique_lock<std::mutex> 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<std::mutex> 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<BBinder>::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<AudioTrack::IAudioTrackCallback>::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<uint8_t*>(static_cast<void*>(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<AudioTrack> 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<std::mutex> 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<uint8_t*>(static_cast<void*>(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<std::mutex> 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<RawBuffer> 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<int32_t>(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<int32_t>(bufferSize)};
memcpy(audioBuffer.mData.get(), buffer.data(), bufferSize);
mNumFramesReceived += bufferSize / frameSize;
tmpQueue.push_back(std::move(audioBuffer));
}
if (tmpQueue.size() > 0) {
std::unique_lock<std::mutex> 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<BBinder>::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<AudioRecord> 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<int32_t>(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<std::mutex> 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<const char*>(static_cast<void*>(buffer.mData.get()));
write(mOutFileFd, ptr, buffer.mCapacity);
}
}
return OK;
}
status_t listAudioPorts(std::vector<audio_port_v7>& 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<struct audio_port_v7> 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<struct audio_port_v7> 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<struct audio_patch>& 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<struct audio_patch> 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<struct audio_patch> 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<const xmlChar*>(attribute)));
if (charPtr == NULL) {
return "";
}
std::string value(reinterpret_cast<const char*>(charPtr.get()));
return value;
}
status_t parse_audio_policy_configuration_xml(std::vector<std::string>& attachedDevices,
std::vector<MixPort>& mixPorts,
std::vector<Route>& 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<const xmlChar*>("audioPolicyConfiguration"))) {
std::string raw{getXmlAttribute(root, "version")};
for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) {
if (!xmlStrcmp(child->name, reinterpret_cast<const xmlChar*>("modules"))) {
xmlNode* root = child;
for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) {
if (!xmlStrcmp(child->name, reinterpret_cast<const xmlChar*>("module"))) {
xmlNode* root = child;
for (auto* child = root->xmlChildrenNode; child != nullptr;
child = child->next) {
if (!xmlStrcmp(child->name,
reinterpret_cast<const xmlChar*>("mixPorts"))) {
xmlNode* root = child;
for (auto* child = root->xmlChildrenNode; child != nullptr;
child = child->next) {
if (!xmlStrcmp(child->name,
reinterpret_cast<const xmlChar*>("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<const xmlChar*>(
"attachedDevices"))) {
xmlNode* root = child;
for (auto* child = root->xmlChildrenNode; child != nullptr;
child = child->next) {
if (!xmlStrcmp(child->name,
reinterpret_cast<const xmlChar*>("item"))) {
auto xmlValue = make_xmlUnique(xmlNodeListGetString(
child->doc, child->xmlChildrenNode, 1));
if (xmlValue == nullptr) {
raw = "";
} else {
raw = reinterpret_cast<const char*>(xmlValue.get());
}
std::string& value = raw;
attachedDevices.push_back(std::move(value));
}
}
} else if (!xmlStrcmp(child->name,
reinterpret_cast<const xmlChar*>("routes"))) {
xmlNode* root = child;
for (auto* child = root->xmlChildrenNode; child != nullptr;
child = child->next) {
if (!xmlStrcmp(child->name,
reinterpret_cast<const xmlChar*>("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;
}