android13/device/google/atv/audio_proxy/service/StreamOutImpl.cpp

568 lines
15 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.
#include "StreamOutImpl.h"
#include <android-base/logging.h>
#include <inttypes.h>
#include <math.h>
#include <system/audio-hal-enums.h>
#include <time.h>
#include <utils/Log.h>
#include <cstring>
#include "AidlTypes.h"
#include "BusOutputStream.h"
#include "WriteThread.h"
using android::status_t;
namespace audio_proxy::service {
namespace {
// 1GB
constexpr uint32_t kMaxBufferSize = 1 << 30;
constexpr int64_t kOneSecInNs = 1'000'000'000;
void deleteEventFlag(EventFlag* obj) {
if (!obj) {
return;
}
status_t status = EventFlag::deleteEventFlag(&obj);
if (status) {
LOG(ERROR) << "Write MQ event flag deletion error: " << strerror(-status);
}
}
uint64_t estimatePlayedFramesSince(const TimeSpec& timestamp,
uint32_t sampleRateHz) {
timespec now = {0, 0};
clock_gettime(CLOCK_MONOTONIC, &now);
int64_t deltaSec = 0;
int64_t deltaNSec = 0;
if (now.tv_nsec >= timestamp.tvNSec) {
deltaSec = now.tv_sec - timestamp.tvSec;
deltaNSec = now.tv_nsec - timestamp.tvNSec;
} else {
deltaSec = now.tv_sec - timestamp.tvSec - 1;
deltaNSec = kOneSecInNs + now.tv_nsec - timestamp.tvNSec;
}
if (deltaSec < 0 || deltaNSec < 0) {
return 0;
}
return deltaSec * sampleRateHz + deltaNSec * sampleRateHz / kOneSecInNs;
}
} // namespace
StreamOutImpl::StreamOutImpl(std::shared_ptr<BusOutputStream> stream,
const StreamOutConfig& config,
uint32_t bufferSizeMs, uint32_t latencyMs)
: mStream(std::move(stream)),
mConfig(config),
mBufferSizeMs(bufferSizeMs),
mLatencyMs(latencyMs),
mEventFlag(nullptr, deleteEventFlag) {}
StreamOutImpl::~StreamOutImpl() {
if (mWriteThread) {
mWriteThread->stop();
status_t status = mWriteThread->join();
if (status) {
LOG(ERROR) << "write thread exit error " << strerror(-status);
}
}
mEventFlag.reset();
}
Return<uint64_t> StreamOutImpl::getFrameSize() {
return mStream->getFrameSize();
}
Return<uint64_t> StreamOutImpl::getFrameCount() {
return mBufferSizeMs * mConfig.sampleRateHz / 1000;
}
Return<uint64_t> StreamOutImpl::getBufferSize() {
return mBufferSizeMs * mConfig.sampleRateHz * mStream->getFrameSize() / 1000;
}
#if MAJOR_VERSION >= 7
Return<void> StreamOutImpl::getSupportedProfiles(
getSupportedProfiles_cb _hidl_cb) {
// For devices with fixed configuration, this method can return NOT_SUPPORTED.
_hidl_cb(Result::NOT_SUPPORTED, {});
return Void();
}
Return<void> StreamOutImpl::getAudioProperties(getAudioProperties_cb _hidl_cb) {
_hidl_cb(Result::OK, mConfig);
return Void();
}
Return<Result> StreamOutImpl::setAudioProperties(
const AudioConfigBaseOptional& config) {
return Result::NOT_SUPPORTED;
}
#else
Return<uint32_t> StreamOutImpl::getSampleRate() { return mConfig.sampleRateHz; }
Return<void> StreamOutImpl::getSupportedSampleRates(
AudioFormat format, getSupportedSampleRates_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, {});
return Void();
}
Return<void> StreamOutImpl::getSupportedChannelMasks(
AudioFormat format, getSupportedChannelMasks_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, {});
return Void();
}
Return<Result> StreamOutImpl::setSampleRate(uint32_t sampleRateHz) {
return Result::NOT_SUPPORTED;
}
Return<hidl_bitfield<AudioChannelMask>> StreamOutImpl::getChannelMask() {
return mConfig.channelMask;
}
Return<Result> StreamOutImpl::setChannelMask(
hidl_bitfield<AudioChannelMask> mask) {
return Result::NOT_SUPPORTED;
}
Return<AudioFormat> StreamOutImpl::getFormat() { return mConfig.format; }
Return<void> StreamOutImpl::getSupportedFormats(
getSupportedFormats_cb _hidl_cb) {
#if MAJOR_VERSION >= 6
_hidl_cb(Result::NOT_SUPPORTED, {});
#else
_hidl_cb({});
#endif
return Void();
}
Return<Result> StreamOutImpl::setFormat(AudioFormat format) {
return Result::NOT_SUPPORTED;
}
Return<void> StreamOutImpl::getAudioProperties(getAudioProperties_cb _hidl_cb) {
_hidl_cb(mConfig.sampleRateHz, mConfig.channelMask, mConfig.format);
return Void();
}
#endif
// We don't support effects. So any effectId is invalid.
Return<Result> StreamOutImpl::addEffect(uint64_t effectId) {
return Result::INVALID_ARGUMENTS;
}
Return<Result> StreamOutImpl::removeEffect(uint64_t effectId) {
return Result::INVALID_ARGUMENTS;
}
Return<Result> StreamOutImpl::standby() {
bool success = mStream->standby();
if (!success) {
return Result::INVALID_STATE;
}
mTotalPlayedFramesSinceStandby = estimateTotalPlayedFrames();
return Result::OK;
}
Return<void> StreamOutImpl::getDevices(getDevices_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, {});
return Void();
}
Return<Result> StreamOutImpl::setDevices(
const hidl_vec<DeviceAddress>& devices) {
return Result::NOT_SUPPORTED;
}
Return<void> StreamOutImpl::getParameters(
const hidl_vec<ParameterValue>& context, const hidl_vec<hidl_string>& keys,
getParameters_cb _hidl_cb) {
_hidl_cb(keys.size() > 0 ? Result::NOT_SUPPORTED : Result::OK, {});
return Void();
}
Return<Result> StreamOutImpl::setParameters(
const hidl_vec<ParameterValue>& context,
const hidl_vec<ParameterValue>& parameters) {
return Result::OK;
}
Return<Result> StreamOutImpl::setHwAvSync(uint32_t hwAvSync) {
return Result::NOT_SUPPORTED;
}
Return<Result> StreamOutImpl::close() {
if (!mStream) {
return Result::INVALID_STATE;
}
if (mWriteThread) {
mWriteThread->stop();
}
if (!mStream->close()) {
LOG(WARNING) << "Failed to close stream.";
}
mStream = nullptr;
return Result::OK;
}
Return<uint32_t> StreamOutImpl::getLatency() { return mLatencyMs; }
Return<Result> StreamOutImpl::setVolume(float left, float right) {
if (isnan(left) || left < 0.f || left > 1.f || isnan(right) || right < 0.f ||
right > 1.f) {
return Result::INVALID_ARGUMENTS;
}
return mStream->setVolume(left, right) ? Result::OK : Result::INVALID_STATE;
}
Return<void> StreamOutImpl::prepareForWriting(uint32_t frameSize,
uint32_t framesCount,
prepareForWriting_cb _hidl_cb) {
#if MAJOR_VERSION >= 7
int32_t threadInfo = 0;
#else
ThreadInfo threadInfo = {0, 0};
#endif
// Wrap the _hidl_cb to return an error
auto sendError = [&threadInfo, &_hidl_cb](Result result) -> Return<void> {
_hidl_cb(result, CommandMQ::Descriptor(), DataMQ::Descriptor(),
StatusMQ::Descriptor(), threadInfo);
return Void();
};
if (mDataMQ) {
LOG(ERROR) << "The client attempted to call prepareForWriting twice";
return sendError(Result::INVALID_STATE);
}
if (frameSize == 0 || framesCount == 0) {
LOG(ERROR) << "Invalid frameSize (" << frameSize << ") or framesCount ("
<< framesCount << ")";
return sendError(Result::INVALID_ARGUMENTS);
}
if (frameSize > kMaxBufferSize / framesCount) {
LOG(ERROR) << "Buffer too big: " << frameSize << "*" << framesCount
<< " bytes > MAX_BUFFER_SIZE (" << kMaxBufferSize << ")";
return sendError(Result::INVALID_ARGUMENTS);
}
auto commandMQ = std::make_unique<CommandMQ>(1);
if (!commandMQ->isValid()) {
LOG(ERROR) << "Command MQ is invalid";
return sendError(Result::INVALID_ARGUMENTS);
}
auto dataMQ =
std::make_unique<DataMQ>(frameSize * framesCount, true /* EventFlag */);
if (!dataMQ->isValid()) {
LOG(ERROR) << "Data MQ is invalid";
return sendError(Result::INVALID_ARGUMENTS);
}
auto statusMQ = std::make_unique<StatusMQ>(1);
if (!statusMQ->isValid()) {
LOG(ERROR) << "Status MQ is invalid";
return sendError(Result::INVALID_ARGUMENTS);
}
EventFlag* rawEventFlag = nullptr;
status_t status =
EventFlag::createEventFlag(dataMQ->getEventFlagWord(), &rawEventFlag);
std::unique_ptr<EventFlag, EventFlagDeleter> eventFlag(rawEventFlag,
deleteEventFlag);
if (status != ::android::OK || !eventFlag) {
LOG(ERROR) << "Failed creating event flag for data MQ: "
<< strerror(-status);
return sendError(Result::INVALID_ARGUMENTS);
}
if (!mStream->prepareForWriting(frameSize, framesCount)) {
LOG(ERROR) << "Failed to prepare writing channel.";
return sendError(Result::INVALID_ARGUMENTS);
}
sp<WriteThread> writeThread =
sp<WriteThread>::make(mStream, commandMQ.get(), dataMQ.get(),
statusMQ.get(), eventFlag.get(), mLatencyMs);
status = writeThread->run("writer", ::android::PRIORITY_URGENT_AUDIO);
if (status != ::android::OK) {
LOG(ERROR) << "Failed to start writer thread: " << strerror(-status);
return sendError(Result::INVALID_ARGUMENTS);
}
mCommandMQ = std::move(commandMQ);
mDataMQ = std::move(dataMQ);
mStatusMQ = std::move(statusMQ);
mEventFlag = std::move(eventFlag);
mWriteThread = std::move(writeThread);
#if MAJOR_VERSION >= 7
threadInfo = mWriteThread->getTid();
#else
threadInfo.pid = getpid();
threadInfo.tid = mWriteThread->getTid();
#endif
_hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(),
*mStatusMQ->getDesc(), threadInfo);
return Void();
}
Return<void> StreamOutImpl::getRenderPosition(getRenderPosition_cb _hidl_cb) {
uint64_t totalPlayedFrames = estimateTotalPlayedFrames();
if (totalPlayedFrames == 0) {
_hidl_cb(Result::OK, 0);
return Void();
}
// getRenderPosition returns the number of frames played since the output has
// exited standby.
DCHECK_GE(totalPlayedFrames, mTotalPlayedFramesSinceStandby);
uint64_t position = totalPlayedFrames - mTotalPlayedFramesSinceStandby;
if (position > std::numeric_limits<uint32_t>::max()) {
_hidl_cb(Result::INVALID_STATE, 0);
return Void();
}
_hidl_cb(Result::OK, position);
return Void();
}
Return<void> StreamOutImpl::getNextWriteTimestamp(
getNextWriteTimestamp_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, 0);
return Void();
}
Return<Result> StreamOutImpl::setCallback(
const sp<IStreamOutCallback>& callback) {
return Result::NOT_SUPPORTED;
}
Return<Result> StreamOutImpl::clearCallback() { return Result::NOT_SUPPORTED; }
Return<void> StreamOutImpl::supportsPauseAndResume(
supportsPauseAndResume_cb _hidl_cb) {
_hidl_cb(true, true);
return Void();
}
// pause should not be called before starting the playback.
Return<Result> StreamOutImpl::pause() {
if (!mWriteThread) {
return Result::INVALID_STATE;
}
if (!mStream->pause()) {
return Result::INVALID_STATE;
}
mIsPaused = true;
return Result::OK;
}
// Resume should onl be called after pause.
Return<Result> StreamOutImpl::resume() {
if (!mIsPaused) {
return Result::INVALID_STATE;
}
if (!mStream->resume()) {
return Result::INVALID_STATE;
}
mIsPaused = false;
return Result::OK;
}
// Drain and flush should always succeed if supported.
Return<bool> StreamOutImpl::supportsDrain() { return true; }
Return<Result> StreamOutImpl::drain(AudioDrain type) {
if (!mStream->drain(static_cast<AidlAudioDrain>(type))) {
LOG(WARNING) << "Failed to drain the stream.";
}
return Result::OK;
}
Return<Result> StreamOutImpl::flush() {
if (!mStream->flush()) {
LOG(WARNING) << "Failed to flush the stream.";
}
return Result::OK;
}
Return<void> StreamOutImpl::getPresentationPosition(
getPresentationPosition_cb _hidl_cb) {
if (!mWriteThread) {
_hidl_cb(Result::INVALID_STATE, 0, {});
return Void();
}
auto [frames, timestamp] = mWriteThread->getPresentationPosition();
_hidl_cb(Result::OK, frames, timestamp);
return Void();
}
Return<Result> StreamOutImpl::start() { return Result::NOT_SUPPORTED; }
Return<Result> StreamOutImpl::stop() { return Result::NOT_SUPPORTED; }
Return<void> StreamOutImpl::createMmapBuffer(int32_t minSizeFrames,
createMmapBuffer_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, MmapBufferInfo());
return Void();
}
Return<void> StreamOutImpl::getMmapPosition(getMmapPosition_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, MmapPosition());
return Void();
}
#if MAJOR_VERSION >= 7
Return<Result> StreamOutImpl::updateSourceMetadata(
const SourceMetadata& sourceMetadata) {
return Result::NOT_SUPPORTED;
}
#else
Return<void> StreamOutImpl::updateSourceMetadata(
const SourceMetadata& sourceMetadata) {
return Void();
}
#endif
Return<Result> StreamOutImpl::selectPresentation(int32_t presentationId,
int32_t programId) {
return Result::NOT_SUPPORTED;
}
std::shared_ptr<BusOutputStream> StreamOutImpl::getOutputStream() {
return mStream;
}
void StreamOutImpl::updateOutputStream(
std::shared_ptr<BusOutputStream> stream) {
DCHECK(stream);
DCHECK(mStream);
if (stream->getConfig() != mStream->getConfig()) {
LOG(ERROR) << "New stream's config doesn't match the old stream's config.";
return;
}
if (mWriteThread) {
if (!stream->prepareForWriting(mStream->getWritingFrameSize(),
mStream->getWritingFrameCount())) {
LOG(ERROR) << "Failed to prepare writing channel.";
return;
}
mWriteThread->updateOutputStream(stream);
}
mStream = std::move(stream);
}
uint64_t StreamOutImpl::estimateTotalPlayedFrames() const {
if (!mWriteThread) {
return 0;
}
auto [frames, timestamp] = mWriteThread->getPresentationPosition();
return frames + estimatePlayedFramesSince(timestamp, mConfig.sampleRateHz);
}
#if MAJOR_VERSION >= 6
Return<Result> StreamOutImpl::setEventCallback(
const sp<IStreamOutEventCallback>& callback) {
return Result::NOT_SUPPORTED;
}
Return<void> StreamOutImpl::getDualMonoMode(getDualMonoMode_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, DualMonoMode::OFF);
return Void();
}
Return<Result> StreamOutImpl::setDualMonoMode(DualMonoMode mode) {
return Result::NOT_SUPPORTED;
}
Return<void> StreamOutImpl::getAudioDescriptionMixLevel(
getAudioDescriptionMixLevel_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, 0.f);
return Void();
}
Return<Result> StreamOutImpl::setAudioDescriptionMixLevel(float leveldB) {
return Result::NOT_SUPPORTED;
}
Return<void> StreamOutImpl::getPlaybackRateParameters(
getPlaybackRateParameters_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, {});
return Void();
}
Return<Result> StreamOutImpl::setPlaybackRateParameters(
const PlaybackRate& playbackRate) {
return Result::NOT_SUPPORTED;
}
#endif
#if MAJOR_VERSION == 7 && MINOR_VERSION == 1
Return<Result> StreamOutImpl::setLatencyMode(
android::hardware::audio::V7_1::LatencyMode mode) {
return Result::NOT_SUPPORTED;
}
Return<void> StreamOutImpl::getRecommendedLatencyModes(
getRecommendedLatencyModes_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, {});
return Void();
}
Return<Result> StreamOutImpl::setLatencyModeCallback(
const sp<android::hardware::audio::V7_1::IStreamOutLatencyModeCallback>&
cb) {
return Result::NOT_SUPPORTED;
}
#endif
} // namespace audio_proxy::service