summaryrefslogtreecommitdiff
path: root/audio/core/all-versions/default/StreamOut.cpp
diff options
context:
space:
mode:
authorKevin Rocard <krocard@google.com>2018-11-14 16:22:07 -0800
committerKevin Rocard <krocard@google.com>2018-11-28 20:29:52 -0800
commit96d2cd9d5c87d8bf9766a7190cf358adccab349a (patch)
treeeb67244d62b8278f994da71e40a4e49cfc2b2788 /audio/core/all-versions/default/StreamOut.cpp
parent6166ef40f9e24436e1d727050c99fc5b185a9e17 (diff)
Audio HAL: Factorize V2 and V4 like libaudiohal
Use the same technique developed for the simpler libaudiohal to factorize the audio HAL impl. Generated with script: set -euo pipefail cd hardware/interfaces/audio # Make 2.0 and 4.0 files mostly identical by removing all 2.0, V4.0... # and replacing it by CPP_VERSION and FILE_VERSION # This allow both implementation to be mostly identical, except for code difference # Also remove IncludeGuard.h as it will be included from compiler flag # Also update license to 2018 find -name *.cpp -o -name *h | grep -v -e service.cpp | xargs sed -Ei \ -e 's/(#.*)V[42]_0_(.*_H_?)$/\1\2/' \ -e 's/(LOG_TAG.*)[24]_0/\1/' \ -e '/#ifndef AUDIO_HAL_VERSION/,/#endif/d' \ -e '/^#.*def.*AUDIO_HAL_VERSION\b/d' \ -e's/V[42]_0/CPP_VERSION/' \ -e 's/(#include ).(android.*)[24].0(.*).$/\1PATH(\2FILE_VERSION\3)/' \ -e 's|(#include) .(.*)[24].0/(default/.*).$|\1 "\2\3"|' \ -e 's/\bAUDIO_HAL_VERSION\b/CPP_VERSION/' \ -e '/IncludeGuard.h/d' \ -e 's/(Copyright .C. 201)./\18/' # set MAJOR_VERSION instead of having a different macro for each version # this allow to do preprocessor arithmetic (eg: MAJOR_VERSION >= 4) for v in 2 4; do find -ipath '*/'$v'.0/*/Android.bp' | xargs sed -i -e '/cflags:/,/^$/d' -e '/vndk/{:a;/}/!{N;ba};d}' -e '$i\ cflags: [\ "-DMAJOR_VERSION='$v'",\ "-DMINOR_VERSION=0",\ "-include common/all-versions/VersionMacro.h",\ ]'; done # replace# ifdef VERSION_X by #if MAJOR_VERSION == X find -path *all-versions/*.h | xargs sed -Ei -e 's/def.*AUDIO_HAL_VERSION_([24])_0.*/ MAJOR_VERSION == \1/' \ -e 'T;s/ +/ /' # copy all-versions impl in each version impl find -path '*/[24].0/*.[hc]*' | grep -ve all-versions -e test | xargs -P99 -n4 sed -i -Ee '/include <.*all-versions.default/!b' \ -e 's#.*<#find -path */#' -e 's/>$/|xargs tail -n +16/' -e e # remove all-versions impl rm -r {core,effect}/all-versions/ # merge version impl into a single all-version for dir in core/*.0/vts/functional/*.0/ ;do dest=$(echo $dir | sed 's#/..0/#/all-versions/#') mkdir -p $dest mv -T $dir $dest done find -mindepth 3 -path '*/2.0/*' -a \( -name '*cpp' -o -name '*h' \) | grep -v 'all-versions' | sed -E 'h;s/2/4/g;H;s/4.0/all-versions/;s/4.0//;H;g;s/\n/ /g;'| xargs -P99 -L1 sh -c ' set -euo pipefail mkdir -p $(dirname $2); diff --old-group-format="#if MAJOR_VERSION == 2 %<#endif " \ --new-group-format="#if MAJOR_VERSION == 4 %>#endif " $0 $1 > $2 || true; rm $0 $1' # merge the X.0 Android.bp & OWNERS in the all-versions for dir in common effect core; do for sub in default vts/functional; do test -f $dir/2.0/$sub/Android.bp || continue awk 1 $dir/*.0/$sub/Android.bp >> $dir/all-versions/$sub/Android.bp # delete licenses except for the first one and add whitelines sed -i -e '/^}$/{N;/^}\n$/!s/\n/&\n/}' $dir/all-versions/$sub/Android.bp sed -i -e '1,17b;/^\/\//,/^$/{d}' $dir/all-versions/$sub/Android.bp done for sub in default vts; do test -d $dir/2.0/$sub || continue test -f $dir/2.0/$sub/OWNERS && awk 1 $dir/*.0/$sub/OWNERS | sort -u > $dir/all-versions/$sub/OWNERS || true rm -r $dir/*.0/$sub done done # delete all-versions-impl dependencies find -name 'Android.bp' | xargs sed -i -e '/all-versions-impl/d' # cleanup unused files rm common/all-versions/default/include/common/all-versions/default/HidlUtils* rm common/all-versions/util/include/common/all-versions/IncludeGuard.h find -depth -type d -empty -delete # Clamp consecutive number of empty lines to 2 find -name *.cpp -o -name *h | xargs sed -Ei ':a;/^\n*$/{N;ba};s/\n\n+/\n\n/' # transform #endif\n#if to #elif find -name *.cpp -o -name *h | xargs sed -i '/^#endif/{N;s/.*\n#if/#elif/}' # remove leftover include guard in cpp find -name *.cpp |xargs sed -Ei '/^#.*_H_?$/d' # apply clang-format find -name *cpp -o -name *h | xargs ../../../prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format --style file -i # clang format breaks PATH(a/b) to PATH(a / b), remove the space surrounding / find -name *cpp -o -name *h | xargs sed -i "/#include PATH/s# / #/#g" Test: compile Bug: 118203066 Change-Id: I3692a444307afc5f71064fe0b9e6b8af3c9ff1dd Signed-off-by: Kevin Rocard <krocard@google.com>
Diffstat (limited to 'audio/core/all-versions/default/StreamOut.cpp')
-rw-r--r--audio/core/all-versions/default/StreamOut.cpp580
1 files changed, 580 insertions, 0 deletions
diff --git a/audio/core/all-versions/default/StreamOut.cpp b/audio/core/all-versions/default/StreamOut.cpp
new file mode 100644
index 0000000000..4ae0fb19c1
--- /dev/null
+++ b/audio/core/all-versions/default/StreamOut.cpp
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2018 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_TAG "StreamOutHAL"
+
+#include "core/default/StreamOut.h"
+#include "core/default/Util.h"
+
+//#define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_AUDIO
+
+#include <memory>
+
+#include <android/log.h>
+#include <hardware/audio.h>
+#include <utils/Trace.h>
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace CPP_VERSION {
+namespace implementation {
+
+using ::android::hardware::audio::common::CPP_VERSION::ThreadInfo;
+
+namespace {
+
+class WriteThread : public Thread {
+ public:
+ // WriteThread's lifespan never exceeds StreamOut's lifespan.
+ WriteThread(std::atomic<bool>* stop, audio_stream_out_t* stream,
+ StreamOut::CommandMQ* commandMQ, StreamOut::DataMQ* dataMQ,
+ StreamOut::StatusMQ* statusMQ, EventFlag* efGroup)
+ : Thread(false /*canCallJava*/),
+ mStop(stop),
+ mStream(stream),
+ mCommandMQ(commandMQ),
+ mDataMQ(dataMQ),
+ mStatusMQ(statusMQ),
+ mEfGroup(efGroup),
+ mBuffer(nullptr) {}
+ bool init() {
+ mBuffer.reset(new (std::nothrow) uint8_t[mDataMQ->getQuantumCount()]);
+ return mBuffer != nullptr;
+ }
+ virtual ~WriteThread() {}
+
+ private:
+ std::atomic<bool>* mStop;
+ audio_stream_out_t* mStream;
+ StreamOut::CommandMQ* mCommandMQ;
+ StreamOut::DataMQ* mDataMQ;
+ StreamOut::StatusMQ* mStatusMQ;
+ EventFlag* mEfGroup;
+ std::unique_ptr<uint8_t[]> mBuffer;
+ IStreamOut::WriteStatus mStatus;
+
+ bool threadLoop() override;
+
+ void doGetLatency();
+ void doGetPresentationPosition();
+ void doWrite();
+};
+
+void WriteThread::doWrite() {
+ const size_t availToRead = mDataMQ->availableToRead();
+ mStatus.retval = Result::OK;
+ mStatus.reply.written = 0;
+ if (mDataMQ->read(&mBuffer[0], availToRead)) {
+ ssize_t writeResult = mStream->write(mStream, &mBuffer[0], availToRead);
+ if (writeResult >= 0) {
+ mStatus.reply.written = writeResult;
+ } else {
+ mStatus.retval = Stream::analyzeStatus("write", writeResult);
+ }
+ }
+}
+
+void WriteThread::doGetPresentationPosition() {
+ mStatus.retval =
+ StreamOut::getPresentationPositionImpl(mStream, &mStatus.reply.presentationPosition.frames,
+ &mStatus.reply.presentationPosition.timeStamp);
+}
+
+void WriteThread::doGetLatency() {
+ mStatus.retval = Result::OK;
+ mStatus.reply.latencyMs = mStream->get_latency(mStream);
+}
+
+bool WriteThread::threadLoop() {
+ // This implementation doesn't return control back to the Thread until it
+ // decides to stop,
+ // as the Thread uses mutexes, and this can lead to priority inversion.
+ while (!std::atomic_load_explicit(mStop, std::memory_order_acquire)) {
+ uint32_t efState = 0;
+ mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState);
+ if (!(efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY))) {
+ continue; // Nothing to do.
+ }
+ if (!mCommandMQ->read(&mStatus.replyTo)) {
+ continue; // Nothing to do.
+ }
+ switch (mStatus.replyTo) {
+ case IStreamOut::WriteCommand::WRITE:
+ doWrite();
+ break;
+ case IStreamOut::WriteCommand::GET_PRESENTATION_POSITION:
+ doGetPresentationPosition();
+ break;
+ case IStreamOut::WriteCommand::GET_LATENCY:
+ doGetLatency();
+ break;
+ default:
+ ALOGE("Unknown write thread command code %d", mStatus.replyTo);
+ mStatus.retval = Result::NOT_SUPPORTED;
+ break;
+ }
+ if (!mStatusMQ->write(&mStatus)) {
+ ALOGE("status message queue write failed");
+ }
+ mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
+ }
+
+ return false;
+}
+
+} // namespace
+
+StreamOut::StreamOut(const sp<Device>& device, audio_stream_out_t* stream)
+ : mIsClosed(false),
+ mDevice(device),
+ mStream(stream),
+ mStreamCommon(new Stream(&stream->common)),
+ mStreamMmap(new StreamMmap<audio_stream_out_t>(stream)),
+ mEfGroup(nullptr),
+ mStopWriteThread(false) {}
+
+StreamOut::~StreamOut() {
+ ATRACE_CALL();
+ close();
+ if (mWriteThread.get()) {
+ ATRACE_NAME("mWriteThread->join");
+ status_t status = mWriteThread->join();
+ ALOGE_IF(status, "write thread exit error: %s", strerror(-status));
+ }
+ if (mEfGroup) {
+ status_t status = EventFlag::deleteEventFlag(&mEfGroup);
+ ALOGE_IF(status, "write MQ event flag deletion error: %s", strerror(-status));
+ }
+ mCallback.clear();
+ mDevice->closeOutputStream(mStream);
+ // Closing the output stream in the HAL waits for the callback to finish,
+ // and joins the callback thread. Thus is it guaranteed that the callback
+ // thread will not be accessing our object anymore.
+ mStream = nullptr;
+}
+
+// Methods from ::android::hardware::audio::CPP_VERSION::IStream follow.
+Return<uint64_t> StreamOut::getFrameSize() {
+ return audio_stream_out_frame_size(mStream);
+}
+
+Return<uint64_t> StreamOut::getFrameCount() {
+ return mStreamCommon->getFrameCount();
+}
+
+Return<uint64_t> StreamOut::getBufferSize() {
+ return mStreamCommon->getBufferSize();
+}
+
+Return<uint32_t> StreamOut::getSampleRate() {
+ return mStreamCommon->getSampleRate();
+}
+
+#if MAJOR_VERSION == 2
+Return<void> StreamOut::getSupportedChannelMasks(getSupportedChannelMasks_cb _hidl_cb) {
+ return mStreamCommon->getSupportedChannelMasks(_hidl_cb);
+}
+Return<void> StreamOut::getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) {
+ return mStreamCommon->getSupportedSampleRates(_hidl_cb);
+}
+#endif
+
+Return<void> StreamOut::getSupportedChannelMasks(AudioFormat format,
+ getSupportedChannelMasks_cb _hidl_cb) {
+ return mStreamCommon->getSupportedChannelMasks(format, _hidl_cb);
+}
+Return<void> StreamOut::getSupportedSampleRates(AudioFormat format,
+ getSupportedSampleRates_cb _hidl_cb) {
+ return mStreamCommon->getSupportedSampleRates(format, _hidl_cb);
+}
+
+Return<Result> StreamOut::setSampleRate(uint32_t sampleRateHz) {
+ return mStreamCommon->setSampleRate(sampleRateHz);
+}
+
+Return<AudioChannelBitfield> StreamOut::getChannelMask() {
+ return mStreamCommon->getChannelMask();
+}
+
+Return<Result> StreamOut::setChannelMask(AudioChannelBitfield mask) {
+ return mStreamCommon->setChannelMask(mask);
+}
+
+Return<AudioFormat> StreamOut::getFormat() {
+ return mStreamCommon->getFormat();
+}
+
+Return<void> StreamOut::getSupportedFormats(getSupportedFormats_cb _hidl_cb) {
+ return mStreamCommon->getSupportedFormats(_hidl_cb);
+}
+
+Return<Result> StreamOut::setFormat(AudioFormat format) {
+ return mStreamCommon->setFormat(format);
+}
+
+Return<void> StreamOut::getAudioProperties(getAudioProperties_cb _hidl_cb) {
+ return mStreamCommon->getAudioProperties(_hidl_cb);
+}
+
+Return<Result> StreamOut::addEffect(uint64_t effectId) {
+ return mStreamCommon->addEffect(effectId);
+}
+
+Return<Result> StreamOut::removeEffect(uint64_t effectId) {
+ return mStreamCommon->removeEffect(effectId);
+}
+
+Return<Result> StreamOut::standby() {
+ return mStreamCommon->standby();
+}
+
+Return<Result> StreamOut::setHwAvSync(uint32_t hwAvSync) {
+ return mStreamCommon->setHwAvSync(hwAvSync);
+}
+
+#if MAJOR_VERSION == 2
+Return<Result> StreamOut::setConnectedState(const DeviceAddress& address, bool connected) {
+ return mStreamCommon->setConnectedState(address, connected);
+}
+
+Return<AudioDevice> StreamOut::getDevice() {
+ return mStreamCommon->getDevice();
+}
+
+Return<Result> StreamOut::setDevice(const DeviceAddress& address) {
+ return mStreamCommon->setDevice(address);
+}
+
+Return<void> StreamOut::getParameters(const hidl_vec<hidl_string>& keys,
+ getParameters_cb _hidl_cb) {
+ return mStreamCommon->getParameters(keys, _hidl_cb);
+}
+
+Return<Result> StreamOut::setParameters(const hidl_vec<ParameterValue>& parameters) {
+ return mStreamCommon->setParameters(parameters);
+}
+
+Return<void> StreamOut::debugDump(const hidl_handle& fd) {
+ return mStreamCommon->debugDump(fd);
+}
+#elif MAJOR_VERSION == 4
+Return<void> StreamOut::getDevices(getDevices_cb _hidl_cb) {
+ return mStreamCommon->getDevices(_hidl_cb);
+}
+
+Return<Result> StreamOut::setDevices(const hidl_vec<DeviceAddress>& devices) {
+ return mStreamCommon->setDevices(devices);
+}
+Return<void> StreamOut::getParameters(const hidl_vec<ParameterValue>& context,
+ const hidl_vec<hidl_string>& keys,
+ getParameters_cb _hidl_cb) {
+ return mStreamCommon->getParameters(context, keys, _hidl_cb);
+}
+
+Return<Result> StreamOut::setParameters(const hidl_vec<ParameterValue>& context,
+ const hidl_vec<ParameterValue>& parameters) {
+ return mStreamCommon->setParameters(context, parameters);
+}
+#endif
+
+Return<Result> StreamOut::close() {
+ if (mIsClosed) return Result::INVALID_STATE;
+ mIsClosed = true;
+ if (mWriteThread.get()) {
+ mStopWriteThread.store(true, std::memory_order_release);
+ }
+ if (mEfGroup) {
+ mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));
+ }
+ return Result::OK;
+}
+
+// Methods from ::android::hardware::audio::CPP_VERSION::IStreamOut follow.
+Return<uint32_t> StreamOut::getLatency() {
+ return mStream->get_latency(mStream);
+}
+
+Return<Result> StreamOut::setVolume(float left, float right) {
+ if (mStream->set_volume == NULL) {
+ return Result::NOT_SUPPORTED;
+ }
+ if (!isGainNormalized(left)) {
+ ALOGW("Can not set a stream output volume {%f, %f} outside [0,1]", left, right);
+ return Result::INVALID_ARGUMENTS;
+ }
+ return Stream::analyzeStatus("set_volume", mStream->set_volume(mStream, left, right));
+}
+
+Return<void> StreamOut::prepareForWriting(uint32_t frameSize, uint32_t framesCount,
+ prepareForWriting_cb _hidl_cb) {
+ status_t status;
+ ThreadInfo threadInfo = {0, 0};
+
+ // Wrap the _hidl_cb to return an error
+ auto sendError = [&threadInfo, &_hidl_cb](Result result) {
+ _hidl_cb(result, CommandMQ::Descriptor(), DataMQ::Descriptor(), StatusMQ::Descriptor(),
+ threadInfo);
+ };
+
+ // Create message queues.
+ if (mDataMQ) {
+ ALOGE("the client attempts to call prepareForWriting twice");
+ sendError(Result::INVALID_STATE);
+ return Void();
+ }
+ std::unique_ptr<CommandMQ> tempCommandMQ(new CommandMQ(1));
+
+ // Check frameSize and framesCount
+ if (frameSize == 0 || framesCount == 0) {
+ ALOGE("Null frameSize (%u) or framesCount (%u)", frameSize, framesCount);
+ sendError(Result::INVALID_ARGUMENTS);
+ return Void();
+ }
+ if (frameSize > Stream::MAX_BUFFER_SIZE / framesCount) {
+ ALOGE("Buffer too big: %u*%u bytes > MAX_BUFFER_SIZE (%u)", frameSize, framesCount,
+ Stream::MAX_BUFFER_SIZE);
+ sendError(Result::INVALID_ARGUMENTS);
+ return Void();
+ }
+ std::unique_ptr<DataMQ> tempDataMQ(new DataMQ(frameSize * framesCount, true /* EventFlag */));
+
+ std::unique_ptr<StatusMQ> tempStatusMQ(new StatusMQ(1));
+ if (!tempCommandMQ->isValid() || !tempDataMQ->isValid() || !tempStatusMQ->isValid()) {
+ ALOGE_IF(!tempCommandMQ->isValid(), "command MQ is invalid");
+ ALOGE_IF(!tempDataMQ->isValid(), "data MQ is invalid");
+ ALOGE_IF(!tempStatusMQ->isValid(), "status MQ is invalid");
+ sendError(Result::INVALID_ARGUMENTS);
+ return Void();
+ }
+ EventFlag* tempRawEfGroup{};
+ status = EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &tempRawEfGroup);
+ std::unique_ptr<EventFlag, void (*)(EventFlag*)> tempElfGroup(
+ tempRawEfGroup, [](auto* ef) { EventFlag::deleteEventFlag(&ef); });
+ if (status != OK || !tempElfGroup) {
+ ALOGE("failed creating event flag for data MQ: %s", strerror(-status));
+ sendError(Result::INVALID_ARGUMENTS);
+ return Void();
+ }
+
+ // Create and launch the thread.
+ auto tempWriteThread =
+ std::make_unique<WriteThread>(&mStopWriteThread, mStream, tempCommandMQ.get(),
+ tempDataMQ.get(), tempStatusMQ.get(), tempElfGroup.get());
+ if (!tempWriteThread->init()) {
+ ALOGW("failed to start writer thread: %s", strerror(-status));
+ sendError(Result::INVALID_ARGUMENTS);
+ return Void();
+ }
+ status = tempWriteThread->run("writer", PRIORITY_URGENT_AUDIO);
+ if (status != OK) {
+ ALOGW("failed to start writer thread: %s", strerror(-status));
+ sendError(Result::INVALID_ARGUMENTS);
+ return Void();
+ }
+
+ mCommandMQ = std::move(tempCommandMQ);
+ mDataMQ = std::move(tempDataMQ);
+ mStatusMQ = std::move(tempStatusMQ);
+ mWriteThread = tempWriteThread.release();
+ mEfGroup = tempElfGroup.release();
+ threadInfo.pid = getpid();
+ threadInfo.tid = mWriteThread->getTid();
+ _hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(), *mStatusMQ->getDesc(),
+ threadInfo);
+ return Void();
+}
+
+Return<void> StreamOut::getRenderPosition(getRenderPosition_cb _hidl_cb) {
+ uint32_t halDspFrames;
+ Result retval = Stream::analyzeStatus("get_render_position",
+ mStream->get_render_position(mStream, &halDspFrames));
+ _hidl_cb(retval, halDspFrames);
+ return Void();
+}
+
+Return<void> StreamOut::getNextWriteTimestamp(getNextWriteTimestamp_cb _hidl_cb) {
+ Result retval(Result::NOT_SUPPORTED);
+ int64_t timestampUs = 0;
+ if (mStream->get_next_write_timestamp != NULL) {
+ retval = Stream::analyzeStatus("get_next_write_timestamp",
+ mStream->get_next_write_timestamp(mStream, &timestampUs));
+ }
+ _hidl_cb(retval, timestampUs);
+ return Void();
+}
+
+Return<Result> StreamOut::setCallback(const sp<IStreamOutCallback>& callback) {
+ if (mStream->set_callback == NULL) return Result::NOT_SUPPORTED;
+ // Safe to pass 'this' because it is guaranteed that the callback thread
+ // is joined prior to exit from StreamOut's destructor.
+ int result = mStream->set_callback(mStream, StreamOut::asyncCallback, this);
+ if (result == 0) {
+ mCallback = callback;
+ }
+ return Stream::analyzeStatus("set_callback", result);
+}
+
+Return<Result> StreamOut::clearCallback() {
+ if (mStream->set_callback == NULL) return Result::NOT_SUPPORTED;
+ mCallback.clear();
+ return Result::OK;
+}
+
+// static
+int StreamOut::asyncCallback(stream_callback_event_t event, void*, void* cookie) {
+ // It is guaranteed that the callback thread is joined prior
+ // to exiting from StreamOut's destructor. Must *not* use sp<StreamOut>
+ // here because it can make this code the last owner of StreamOut,
+ // and an attempt to run the destructor on the callback thread
+ // will cause a deadlock in the legacy HAL code.
+ StreamOut* self = reinterpret_cast<StreamOut*>(cookie);
+ // It's correct to hold an sp<> to callback because the reference
+ // in the StreamOut instance can be cleared in the meantime. There is
+ // no difference on which thread to run IStreamOutCallback's destructor.
+ sp<IStreamOutCallback> callback = self->mCallback;
+ if (callback.get() == nullptr) return 0;
+ ALOGV("asyncCallback() event %d", event);
+ switch (event) {
+ case STREAM_CBK_EVENT_WRITE_READY:
+ callback->onWriteReady();
+ break;
+ case STREAM_CBK_EVENT_DRAIN_READY:
+ callback->onDrainReady();
+ break;
+ case STREAM_CBK_EVENT_ERROR:
+ callback->onError();
+ break;
+ default:
+ ALOGW("asyncCallback() unknown event %d", event);
+ break;
+ }
+ return 0;
+}
+
+Return<void> StreamOut::supportsPauseAndResume(supportsPauseAndResume_cb _hidl_cb) {
+ _hidl_cb(mStream->pause != NULL, mStream->resume != NULL);
+ return Void();
+}
+
+Return<Result> StreamOut::pause() {
+ return mStream->pause != NULL ? Stream::analyzeStatus("pause", mStream->pause(mStream))
+ : Result::NOT_SUPPORTED;
+}
+
+Return<Result> StreamOut::resume() {
+ return mStream->resume != NULL ? Stream::analyzeStatus("resume", mStream->resume(mStream))
+ : Result::NOT_SUPPORTED;
+}
+
+Return<bool> StreamOut::supportsDrain() {
+ return mStream->drain != NULL;
+}
+
+Return<Result> StreamOut::drain(AudioDrain type) {
+ return mStream->drain != NULL
+ ? Stream::analyzeStatus(
+ "drain", mStream->drain(mStream, static_cast<audio_drain_type_t>(type)))
+ : Result::NOT_SUPPORTED;
+}
+
+Return<Result> StreamOut::flush() {
+ return mStream->flush != NULL ? Stream::analyzeStatus("flush", mStream->flush(mStream))
+ : Result::NOT_SUPPORTED;
+}
+
+// static
+Result StreamOut::getPresentationPositionImpl(audio_stream_out_t* stream, uint64_t* frames,
+ TimeSpec* timeStamp) {
+ // Don't logspam on EINVAL--it's normal for get_presentation_position
+ // to return it sometimes. EAGAIN may be returned by A2DP audio HAL
+ // implementation. ENODATA can also be reported while the writer is
+ // continuously querying it, but the stream has been stopped.
+ static const std::vector<int> ignoredErrors{EINVAL, EAGAIN, ENODATA};
+ Result retval(Result::NOT_SUPPORTED);
+ if (stream->get_presentation_position == NULL) return retval;
+ struct timespec halTimeStamp;
+ retval = Stream::analyzeStatus("get_presentation_position",
+ stream->get_presentation_position(stream, frames, &halTimeStamp),
+ ignoredErrors);
+ if (retval == Result::OK) {
+ timeStamp->tvSec = halTimeStamp.tv_sec;
+ timeStamp->tvNSec = halTimeStamp.tv_nsec;
+ }
+ return retval;
+}
+
+Return<void> StreamOut::getPresentationPosition(getPresentationPosition_cb _hidl_cb) {
+ uint64_t frames = 0;
+ TimeSpec timeStamp = {0, 0};
+ Result retval = getPresentationPositionImpl(mStream, &frames, &timeStamp);
+ _hidl_cb(retval, frames, timeStamp);
+ return Void();
+}
+
+Return<Result> StreamOut::start() {
+ return mStreamMmap->start();
+}
+
+Return<Result> StreamOut::stop() {
+ return mStreamMmap->stop();
+}
+
+Return<void> StreamOut::createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) {
+ return mStreamMmap->createMmapBuffer(minSizeFrames, audio_stream_out_frame_size(mStream),
+ _hidl_cb);
+}
+
+Return<void> StreamOut::getMmapPosition(getMmapPosition_cb _hidl_cb) {
+ return mStreamMmap->getMmapPosition(_hidl_cb);
+}
+
+Return<void> StreamOut::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) {
+ return mStreamCommon->debug(fd, options);
+}
+
+#if MAJOR_VERSION == 4
+Return<void> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
+ if (mStream->update_source_metadata == nullptr) {
+ return Void(); // not supported by the HAL
+ }
+ std::vector<playback_track_metadata> halTracks;
+ halTracks.reserve(sourceMetadata.tracks.size());
+ for (auto& metadata : sourceMetadata.tracks) {
+ halTracks.push_back({
+ .usage = static_cast<audio_usage_t>(metadata.usage),
+ .content_type = static_cast<audio_content_type_t>(metadata.contentType),
+ .gain = metadata.gain,
+ });
+ }
+ const source_metadata_t halMetadata = {
+ .track_count = halTracks.size(),
+ .tracks = halTracks.data(),
+ };
+ mStream->update_source_metadata(mStream, &halMetadata);
+ return Void();
+}
+Return<Result> StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t /*programId*/) {
+ return Result::NOT_SUPPORTED; // TODO: propagate to legacy
+}
+#endif
+
+} // namespace implementation
+} // namespace CPP_VERSION
+} // namespace audio
+} // namespace hardware
+} // namespace android