911 lines
36 KiB
C++
911 lines
36 KiB
C++
/*
|
|
* 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;
|
|
}
|