From b29438ef70549a331d11c0384c53cf1dc6e7f0be Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Thu, 22 Dec 2016 09:21:34 -0800 Subject: [PATCH] audiohal: Re-implement stream read and write using FMQ Result: no hwbinder calls due read / write session. Added IStream.close method for explicitly freeing up of resources consumed by the stream before automatic server objects reaping gets to it. Test: make, perform Loopback RTT, check traces Bug: 30222631 Change-Id: I678559f6ef30026685df787cd2ba7c2ee449ed27 --- audio/2.0/IStream.hal | 12 +++ audio/2.0/IStreamIn.hal | 45 +++++++++-- audio/2.0/IStreamOut.hal | 59 +++++++++----- audio/2.0/default/Android.mk | 9 ++- audio/2.0/default/Stream.cpp | 4 + audio/2.0/default/Stream.h | 1 + audio/2.0/default/StreamIn.cpp | 170 ++++++++++++++++++++++++++++++++++---- audio/2.0/default/StreamIn.h | 25 +++++- audio/2.0/default/StreamOut.cpp | 175 ++++++++++++++++++++++++++++++++++++---- audio/2.0/default/StreamOut.h | 24 +++++- audio/2.0/types.hal | 18 +++++ 11 files changed, 474 insertions(+), 68 deletions(-) diff --git a/audio/2.0/IStream.hal b/audio/2.0/IStream.hal index 5c88a693..8de78515 100644 --- a/audio/2.0/IStream.hal +++ b/audio/2.0/IStream.hal @@ -279,4 +279,16 @@ interface IStream { */ getMmapPosition() generates (Result retval, MmapPosition position); + + /* + * Called by the framework to deinitialize the stream and free up + * all the currently allocated resources. It is recommended to close + * the stream on the client side as soon as it is becomes unused. + * + * @return retval OK in case the success. + * NOT_SUPPORTED if called on IStream instead of input or + * output stream interface. + * INVALID_STATE if the stream was already closed. + */ + close() generates (Result retval); }; diff --git a/audio/2.0/IStreamIn.hal b/audio/2.0/IStreamIn.hal index 6cf74259..9a96f71d 100644 --- a/audio/2.0/IStreamIn.hal +++ b/audio/2.0/IStreamIn.hal @@ -41,16 +41,45 @@ interface IStreamIn extends IStream { setGain(float gain) generates (Result retval); /* - * Read audio buffer in from driver. If at least one frame was read prior to - * the error, 'read' must return that byte count and then return an error - * in the subsequent call. + * Data structure passed back to the client via status message queue + * of 'read' operation. * - * @param size maximum amount of bytes to read. - * @return retval operation completion status. - * @return data audio data. + * Possible values of 'retval' field: + * - OK, read operation was successful; + * - INVALID_ARGUMENTS, stream was not configured properly; + * - INVALID_STATE, stream is in a state that doesn't allow reads. + */ + struct ReadStatus { + Result retval; + uint64_t read; + }; + + /* + * Set up required transports for receiving audio buffers from the driver. + * + * The transport consists of two message queues: one is used for passing + * audio data from the driver to the client, another is used for reporting + * read operation status (amount of bytes actually read or error code), + * see ReadStatus structure definition. + * + * @param frameSize the size of a single frame, in bytes. + * @param framesCount the number of frames in a buffer. + * @param threadPriority priority of the thread that performs reads. + * @return retval OK if both message queues were created successfully. + * INVALID_STATE if the method was already called. + * INVALID_ARGUMENTS if there was a problem setting up + * the queues. + * @return dataMQ a message queue used for passing audio data in the format + * specified at the stream opening. + * @return statusMQ a message queue used for passing status from the driver + * using ReadStatus structures. */ - // TODO(mnaganov): Replace with FMQ version. - read(uint64_t size) generates (Result retval, vec data); + prepareForReading( + uint32_t frameSize, uint32_t framesCount, + ThreadPriority threadPriority) + generates ( + Result retval, + fmq_sync dataMQ, fmq_sync statusMQ); /* * Return the amount of input frames lost in the audio driver since the last diff --git a/audio/2.0/IStreamOut.hal b/audio/2.0/IStreamOut.hal index 4ba3b2f1..155e3292 100644 --- a/audio/2.0/IStreamOut.hal +++ b/audio/2.0/IStreamOut.hal @@ -44,25 +44,48 @@ interface IStreamOut extends IStream { setVolume(float left, float right) generates (Result retval); /* - * Write audio buffer to driver. On success, sets 'retval' to 'OK', and - * returns number of bytes written. If at least one frame was written - * successfully prior to the error, it is suggested that the driver return - * that successful (short) byte count and then return an error in the - * subsequent call. - * - * If 'setCallback' has previously been called to enable non-blocking mode - * then 'write' is not allowed to block. It must write only the number of - * bytes that currently fit in the driver/hardware buffer and then return - * this byte count. If this is less than the requested write size the - * callback function must be called when more space is available in the - * driver/hardware buffer. - * - * @param data audio data. - * @return retval operation completion status. - * @return written number of bytes written. + * Data structure passed back to the client via status message queue + * of 'write' operation. + * + * Possible values of 'retval' field: + * - OK, write operation was successful; + * - INVALID_ARGUMENTS, stream was not configured properly; + * - INVALID_STATE, stream is in a state that doesn't allow writes. + */ + struct WriteStatus { + Result retval; + uint64_t written; + uint64_t frames; // presentation position + TimeSpec timeStamp; // presentation position + }; + + /* + * Set up required transports for passing audio buffers to the driver. + * + * The transport consists of two message queues: one is used for passing + * audio data from the client to the driver, another is used for reporting + * write operation status (amount of bytes actually written or error code), + * and the presentation position immediately after the write, see + * WriteStatus structure definition. + * + * @param frameSize the size of a single frame, in bytes. + * @param framesCount the number of frames in a buffer. + * @param threadPriority priority of the thread that performs writes. + * @return retval OK if both message queues were created successfully. + * INVALID_STATE if the method was already called. + * INVALID_ARGUMENTS if there was a problem setting up + * the queues. + * @return dataMQ a message queue used for passing audio data in the format + * specified at the stream opening. + * @return statusMQ a message queue used for passing status from the driver + * using WriteStatus structures. */ - // TODO(mnaganov): Replace with FMQ version. - write(vec data) generates (Result retval, uint64_t written); + prepareForWriting( + uint32_t frameSize, uint32_t framesCount, + ThreadPriority threadPriority) + generates ( + Result retval, + fmq_sync dataMQ, fmq_sync statusMQ); /* * Return the number of audio frames written by the audio DSP to DAC since diff --git a/audio/2.0/default/Android.mk b/audio/2.0/default/Android.mk index c3cfd699..eeea92c2 100644 --- a/audio/2.0/default/Android.mk +++ b/audio/2.0/default/Android.mk @@ -30,13 +30,16 @@ LOCAL_SRC_FILES := \ StreamOut.cpp \ LOCAL_SHARED_LIBRARIES := \ + libbase \ + libcutils \ + libfmq \ + libhardware \ libhidlbase \ libhidltransport \ libhwbinder \ - libcutils \ - libutils \ - libhardware \ liblog \ + libmediautils \ + libutils \ android.hardware.audio@2.0 \ android.hardware.audio.common@2.0 \ android.hardware.audio.common@2.0-util \ diff --git a/audio/2.0/default/Stream.cpp b/audio/2.0/default/Stream.cpp index f214eed9..62b34a3a 100644 --- a/audio/2.0/default/Stream.cpp +++ b/audio/2.0/default/Stream.cpp @@ -253,6 +253,10 @@ Return Stream::getMmapPosition(getMmapPosition_cb _hidl_cb) { return Void(); } +Return Stream::close() { + return Result::NOT_SUPPORTED; +} + } // namespace implementation } // namespace V2_0 } // namespace audio diff --git a/audio/2.0/default/Stream.h b/audio/2.0/default/Stream.h index 819bbf7c..0bbd8033 100644 --- a/audio/2.0/default/Stream.h +++ b/audio/2.0/default/Stream.h @@ -76,6 +76,7 @@ struct Stream : public IStream, public ParametersUtil { Return stop() override; Return createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override; Return getMmapPosition(getMmapPosition_cb _hidl_cb) override; + Return close() override; // Utility methods for extending interfaces. static Result analyzeStatus(const char* funcName, int status, int ignoreError = OK); diff --git a/audio/2.0/default/StreamIn.cpp b/audio/2.0/default/StreamIn.cpp index 1441e74c..51c2cc72 100644 --- a/audio/2.0/default/StreamIn.cpp +++ b/audio/2.0/default/StreamIn.cpp @@ -15,26 +15,112 @@ */ #define LOG_TAG "StreamInHAL" +//#define LOG_NDEBUG 0 -#include #include +#include +#include #include "StreamIn.h" +using ::android::hardware::audio::V2_0::MessageQueueFlagBits; + namespace android { namespace hardware { namespace audio { namespace V2_0 { namespace implementation { +namespace { + +class ReadThread : public Thread { + public: + // ReadThread's lifespan never exceeds StreamIn's lifespan. + ReadThread(std::atomic* stop, + audio_stream_in_t* stream, + StreamIn::DataMQ* dataMQ, + StreamIn::StatusMQ* statusMQ, + EventFlag* efGroup, + ThreadPriority threadPriority) + : Thread(false /*canCallJava*/), + mStop(stop), + mStream(stream), + mDataMQ(dataMQ), + mStatusMQ(statusMQ), + mEfGroup(efGroup), + mThreadPriority(threadPriority), + mBuffer(new uint8_t[dataMQ->getQuantumCount()]) { + } + virtual ~ReadThread() {} + + status_t readyToRun() override; + + private: + std::atomic* mStop; + audio_stream_in_t* mStream; + StreamIn::DataMQ* mDataMQ; + StreamIn::StatusMQ* mStatusMQ; + EventFlag* mEfGroup; + ThreadPriority mThreadPriority; + std::unique_ptr mBuffer; + + bool threadLoop() override; +}; + +status_t ReadThread::readyToRun() { + if (mThreadPriority != ThreadPriority::NORMAL) { + int err = requestPriority( + getpid(), getTid(), static_cast(mThreadPriority), true /*asynchronous*/); + ALOGW_IF(err, "failed to set priority %d for pid %d tid %d; error %d", + static_cast(mThreadPriority), getpid(), getTid(), err); + } + return OK; +} + +bool ReadThread::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)) { + // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422 + uint32_t efState = 0; + mEfGroup->wait(static_cast(MessageQueueFlagBits::NOT_FULL), &efState, NS_PER_SEC); + if (!(efState & static_cast(MessageQueueFlagBits::NOT_FULL))) { + continue; // Nothing to do. + } + + const size_t availToWrite = mDataMQ->availableToWrite(); + ssize_t readResult = mStream->read(mStream, &mBuffer[0], availToWrite); + Result retval = Result::OK; + uint64_t read = 0; + if (readResult >= 0) { + read = readResult; + if (!mDataMQ->write(&mBuffer[0], readResult)) { + ALOGW("data message queue write failed"); + } + } else { + retval = Stream::analyzeStatus("read", readResult); + } + IStreamIn::ReadStatus status = { retval, read }; + if (!mStatusMQ->write(&status)) { + ALOGW("status message queue write failed"); + } + mEfGroup->wake(static_cast(MessageQueueFlagBits::NOT_EMPTY)); + } + + return false; +} + +} // namespace + StreamIn::StreamIn(audio_hw_device_t* device, audio_stream_in_t* stream) - : mDevice(device), mStream(stream), + : mIsClosed(false), mDevice(device), mStream(stream), mStreamCommon(new Stream(&stream->common)), - mStreamMmap(new StreamMmap(stream)) { + mStreamMmap(new StreamMmap(stream)), + mEfGroup(nullptr), mStopReadThread(false) { } StreamIn::~StreamIn() { - mDevice->close_input_stream(mDevice, mStream); + close(); mStream = nullptr; mDevice = nullptr; } @@ -149,6 +235,22 @@ Return StreamIn::getMmapPosition(getMmapPosition_cb _hidl_cb) { return mStreamMmap->getMmapPosition(_hidl_cb); } +Return StreamIn::close() { + if (mIsClosed) return Result::INVALID_STATE; + mIsClosed = true; + if (mReadThread.get()) { + mStopReadThread.store(true, std::memory_order_release); + status_t status = mReadThread->requestExitAndWait(); + ALOGE_IF(status, "read thread exit error: %s", strerror(-status)); + } + if (mEfGroup) { + status_t status = EventFlag::deleteEventFlag(&mEfGroup); + ALOGE_IF(status, "read MQ event flag deletion error: %s", strerror(-status)); + } + mDevice->close_input_stream(mDevice, mStream); + return Result::OK; +} + // Methods from ::android::hardware::audio::V2_0::IStreamIn follow. Return StreamIn::getAudioSource(getAudioSource_cb _hidl_cb) { int halSource; @@ -165,19 +267,55 @@ Return StreamIn::setGain(float gain) { return Stream::analyzeStatus("set_gain", mStream->set_gain(mStream, gain)); } -Return StreamIn::read(uint64_t size, read_cb _hidl_cb) { - // TODO(mnaganov): Replace with FMQ version. - hidl_vec data; - data.resize(size); - Result retval(Result::OK); - ssize_t readResult = mStream->read(mStream, &data[0], data.size()); - if (readResult >= 0 && static_cast(readResult) != data.size()) { - data.resize(readResult); - } else if (readResult < 0) { - data.resize(0); - retval = Stream::analyzeStatus("read", readResult); +Return StreamIn::prepareForReading( + uint32_t frameSize, uint32_t framesCount, ThreadPriority threadPriority, + prepareForReading_cb _hidl_cb) { + status_t status; + // Create message queues. + if (mDataMQ) { + ALOGE("the client attempts to call prepareForReading twice"); + _hidl_cb(Result::INVALID_STATE, + MQDescriptorSync(), MQDescriptorSync()); + return Void(); + } + std::unique_ptr tempDataMQ( + new DataMQ(frameSize * framesCount, true /* EventFlag */)); + std::unique_ptr tempStatusMQ(new StatusMQ(1)); + if (!tempDataMQ->isValid() || !tempStatusMQ->isValid()) { + ALOGE_IF(!tempDataMQ->isValid(), "data MQ is invalid"); + ALOGE_IF(!tempStatusMQ->isValid(), "status MQ is invalid"); + _hidl_cb(Result::INVALID_ARGUMENTS, + MQDescriptorSync(), MQDescriptorSync()); + return Void(); + } + // TODO: Remove event flag management once blocking MQ is implemented. b/33815422 + status = EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup); + if (status != OK || !mEfGroup) { + ALOGE("failed creating event flag for data MQ: %s", strerror(-status)); + _hidl_cb(Result::INVALID_ARGUMENTS, + MQDescriptorSync(), MQDescriptorSync()); + return Void(); + } + + // Create and launch the thread. + mReadThread = new ReadThread( + &mStopReadThread, + mStream, + tempDataMQ.get(), + tempStatusMQ.get(), + mEfGroup, + threadPriority); + status = mReadThread->run("reader", PRIORITY_URGENT_AUDIO); + if (status != OK) { + ALOGW("failed to start reader thread: %s", strerror(-status)); + _hidl_cb(Result::INVALID_ARGUMENTS, + MQDescriptorSync(), MQDescriptorSync()); + return Void(); } - _hidl_cb(retval, data); + + mDataMQ = std::move(tempDataMQ); + mStatusMQ = std::move(tempStatusMQ); + _hidl_cb(Result::OK, *mDataMQ->getDesc(), *mStatusMQ->getDesc()); return Void(); } diff --git a/audio/2.0/default/StreamIn.h b/audio/2.0/default/StreamIn.h index 65e94bbf..fc813d95 100644 --- a/audio/2.0/default/StreamIn.h +++ b/audio/2.0/default/StreamIn.h @@ -17,10 +17,15 @@ #ifndef ANDROID_HARDWARE_AUDIO_V2_0_STREAMIN_H #define ANDROID_HARDWARE_AUDIO_V2_0_STREAMIN_H -#include -#include +#include +#include +#include #include +#include +#include +#include +#include #include "Stream.h" @@ -39,6 +44,7 @@ using ::android::hardware::audio::V2_0::IStream; using ::android::hardware::audio::V2_0::IStreamIn; using ::android::hardware::audio::V2_0::ParameterValue; using ::android::hardware::audio::V2_0::Result; +using ::android::hardware::audio::V2_0::ThreadPriority; using ::android::hardware::Return; using ::android::hardware::Void; using ::android::hardware::hidl_vec; @@ -46,6 +52,9 @@ using ::android::hardware::hidl_string; using ::android::sp; struct StreamIn : public IStreamIn { + typedef MessageQueue DataMQ; + typedef MessageQueue StatusMQ; + StreamIn(audio_hw_device_t* device, audio_stream_in_t* stream); // Methods from ::android::hardware::audio::V2_0::IStream follow. @@ -73,11 +82,14 @@ struct StreamIn : public IStreamIn { const hidl_vec& keys, getParameters_cb _hidl_cb) override; Return setParameters(const hidl_vec& parameters) override; Return debugDump(const hidl_handle& fd) override; + Return close() override; // Methods from ::android::hardware::audio::V2_0::IStreamIn follow. Return getAudioSource(getAudioSource_cb _hidl_cb) override; Return setGain(float gain) override; - Return read(uint64_t size, read_cb _hidl_cb) override; + Return prepareForReading( + uint32_t frameSize, uint32_t framesCount, ThreadPriority threadPriority, + prepareForReading_cb _hidl_cb) override; Return getInputFramesLost() override; Return getCapturePosition(getCapturePosition_cb _hidl_cb) override; Return start() override; @@ -86,11 +98,16 @@ struct StreamIn : public IStreamIn { Return getMmapPosition(getMmapPosition_cb _hidl_cb) override; private: + bool mIsClosed; audio_hw_device_t *mDevice; audio_stream_in_t *mStream; sp mStreamCommon; sp> mStreamMmap; - + std::unique_ptr mDataMQ; + std::unique_ptr mStatusMQ; + EventFlag* mEfGroup; + std::atomic mStopReadThread; + sp mReadThread; virtual ~StreamIn(); }; diff --git a/audio/2.0/default/StreamOut.cpp b/audio/2.0/default/StreamOut.cpp index 3d20d11d..4bb22742 100644 --- a/audio/2.0/default/StreamOut.cpp +++ b/audio/2.0/default/StreamOut.cpp @@ -17,8 +17,9 @@ #define LOG_TAG "StreamOutHAL" //#define LOG_NDEBUG 0 -#include #include +#include +#include #include "StreamOut.h" @@ -28,15 +29,103 @@ namespace audio { namespace V2_0 { namespace implementation { +namespace { + +class WriteThread : public Thread { + public: + // WriteThread's lifespan never exceeds StreamOut's lifespan. + WriteThread(std::atomic* stop, + audio_stream_out_t* stream, + StreamOut::DataMQ* dataMQ, + StreamOut::StatusMQ* statusMQ, + EventFlag* efGroup, + ThreadPriority threadPriority) + : Thread(false /*canCallJava*/), + mStop(stop), + mStream(stream), + mDataMQ(dataMQ), + mStatusMQ(statusMQ), + mEfGroup(efGroup), + mThreadPriority(threadPriority), + mBuffer(new uint8_t[dataMQ->getQuantumCount()]) { + } + virtual ~WriteThread() {} + + status_t readyToRun() override; + + private: + std::atomic* mStop; + audio_stream_out_t* mStream; + StreamOut::DataMQ* mDataMQ; + StreamOut::StatusMQ* mStatusMQ; + EventFlag* mEfGroup; + ThreadPriority mThreadPriority; + std::unique_ptr mBuffer; + + bool threadLoop() override; +}; + +status_t WriteThread::readyToRun() { + if (mThreadPriority != ThreadPriority::NORMAL) { + int err = requestPriority( + getpid(), getTid(), static_cast(mThreadPriority), true /*asynchronous*/); + ALOGW_IF(err, "failed to set priority %d for pid %d tid %d; error %d", + static_cast(mThreadPriority), getpid(), getTid(), err); + } + return OK; +} + +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)) { + // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422 + uint32_t efState = 0; + mEfGroup->wait( + static_cast(MessageQueueFlagBits::NOT_EMPTY), &efState, NS_PER_SEC); + if (!(efState & static_cast(MessageQueueFlagBits::NOT_EMPTY))) { + continue; // Nothing to do. + } + + const size_t availToRead = mDataMQ->availableToRead(); + Result retval = Result::OK; + uint64_t written = 0; + if (mDataMQ->read(&mBuffer[0], availToRead)) { + ssize_t writeResult = mStream->write(mStream, &mBuffer[0], availToRead); + if (writeResult >= 0) { + written = writeResult; + } else { + retval = Stream::analyzeStatus("write", writeResult); + } + } + uint64_t frames = 0; + struct timespec halTimeStamp = { 0, 0 }; + if (retval == Result::OK && mStream->get_presentation_position != NULL) { + mStream->get_presentation_position(mStream, &frames, &halTimeStamp); + } + IStreamOut::WriteStatus status = { retval, written, frames, + { static_cast(halTimeStamp.tv_sec), + static_cast(halTimeStamp.tv_nsec) } }; + if (!mStatusMQ->write(&status)) { + ALOGW("status message queue write failed"); + } + mEfGroup->wake(static_cast(MessageQueueFlagBits::NOT_FULL)); + } + + return false; +} + +} // namespace + StreamOut::StreamOut(audio_hw_device_t* device, audio_stream_out_t* stream) - : mDevice(device), mStream(stream), + : mIsClosed(false), mDevice(device), mStream(stream), mStreamCommon(new Stream(&stream->common)), - mStreamMmap(new StreamMmap(stream)) { + mStreamMmap(new StreamMmap(stream)), + mEfGroup(nullptr), mStopWriteThread(false) { } StreamOut::~StreamOut() { - mCallback.clear(); - mDevice->close_output_stream(mDevice, mStream); + close(); mStream = nullptr; mDevice = nullptr; } @@ -135,6 +224,23 @@ Return StreamOut::debugDump(const hidl_handle& fd) { return mStreamCommon->debugDump(fd); } +Return StreamOut::close() { + if (mIsClosed) return Result::INVALID_STATE; + mIsClosed = true; + if (mWriteThread.get()) { + mStopWriteThread.store(true, std::memory_order_release); + status_t status = mWriteThread->requestExitAndWait(); + 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->close_output_stream(mDevice, mStream); + return Result::OK; +} + // Methods from ::android::hardware::audio::V2_0::IStreamOut follow. Return StreamOut::getLatency() { return mStream->get_latency(mStream); @@ -149,18 +255,55 @@ Return StreamOut::setVolume(float left, float right) { return retval; } -Return StreamOut::write(const hidl_vec& data, write_cb _hidl_cb) { - // TODO(mnaganov): Replace with FMQ version. - Result retval(Result::OK); - uint64_t written = 0; - ssize_t writeResult = mStream->write(mStream, &data[0], data.size()); - if (writeResult >= 0) { - written = writeResult; - } else { - retval = Stream::analyzeStatus("write", writeResult); - written = 0; +Return StreamOut::prepareForWriting( + uint32_t frameSize, uint32_t framesCount, ThreadPriority threadPriority, + prepareForWriting_cb _hidl_cb) { + status_t status; + // Create message queues. + if (mDataMQ) { + ALOGE("the client attempts to call prepareForWriting twice"); + _hidl_cb(Result::INVALID_STATE, + MQDescriptorSync(), MQDescriptorSync()); + return Void(); + } + std::unique_ptr tempDataMQ( + new DataMQ(frameSize * framesCount, true /* EventFlag */)); + std::unique_ptr tempStatusMQ(new StatusMQ(1)); + if (!tempDataMQ->isValid() || !tempStatusMQ->isValid()) { + ALOGE_IF(!tempDataMQ->isValid(), "data MQ is invalid"); + ALOGE_IF(!tempStatusMQ->isValid(), "status MQ is invalid"); + _hidl_cb(Result::INVALID_ARGUMENTS, + MQDescriptorSync(), MQDescriptorSync()); + return Void(); + } + // TODO: Remove event flag management once blocking MQ is implemented. b/33815422 + status = EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup); + if (status != OK || !mEfGroup) { + ALOGE("failed creating event flag for data MQ: %s", strerror(-status)); + _hidl_cb(Result::INVALID_ARGUMENTS, + MQDescriptorSync(), MQDescriptorSync()); + return Void(); } - _hidl_cb(retval, written); + + // Create and launch the thread. + mWriteThread = new WriteThread( + &mStopWriteThread, + mStream, + tempDataMQ.get(), + tempStatusMQ.get(), + mEfGroup, + threadPriority); + status = mWriteThread->run("writer", PRIORITY_URGENT_AUDIO); + if (status != OK) { + ALOGW("failed to start writer thread: %s", strerror(-status)); + _hidl_cb(Result::INVALID_ARGUMENTS, + MQDescriptorSync(), MQDescriptorSync()); + return Void(); + } + + mDataMQ = std::move(tempDataMQ); + mStatusMQ = std::move(tempStatusMQ); + _hidl_cb(Result::OK, *mDataMQ->getDesc(), *mStatusMQ->getDesc()); return Void(); } diff --git a/audio/2.0/default/StreamOut.h b/audio/2.0/default/StreamOut.h index 9b7f9f83..83f44476 100644 --- a/audio/2.0/default/StreamOut.h +++ b/audio/2.0/default/StreamOut.h @@ -17,10 +17,15 @@ #ifndef ANDROID_HARDWARE_AUDIO_V2_0_STREAMOUT_H #define ANDROID_HARDWARE_AUDIO_V2_0_STREAMOUT_H -#include -#include +#include +#include +#include #include +#include +#include +#include +#include #include "Stream.h" @@ -40,6 +45,7 @@ using ::android::hardware::audio::V2_0::IStreamOut; using ::android::hardware::audio::V2_0::IStreamOutCallback; using ::android::hardware::audio::V2_0::ParameterValue; using ::android::hardware::audio::V2_0::Result; +using ::android::hardware::audio::V2_0::ThreadPriority; using ::android::hardware::audio::V2_0::TimeSpec; using ::android::hardware::Return; using ::android::hardware::Void; @@ -48,6 +54,9 @@ using ::android::hardware::hidl_string; using ::android::sp; struct StreamOut : public IStreamOut { + typedef MessageQueue DataMQ; + typedef MessageQueue StatusMQ; + StreamOut(audio_hw_device_t* device, audio_stream_out_t* stream); // Methods from ::android::hardware::audio::V2_0::IStream follow. @@ -75,11 +84,14 @@ struct StreamOut : public IStreamOut { const hidl_vec& keys, getParameters_cb _hidl_cb) override; Return setParameters(const hidl_vec& parameters) override; Return debugDump(const hidl_handle& fd) override; + Return close() override; // Methods from ::android::hardware::audio::V2_0::IStreamOut follow. Return getLatency() override; Return setVolume(float left, float right) override; - Return write(const hidl_vec& data, write_cb _hidl_cb) override; + Return prepareForWriting( + uint32_t frameSize, uint32_t framesCount, ThreadPriority threadPriority, + prepareForWriting_cb _hidl_cb) override; Return getRenderPosition(getRenderPosition_cb _hidl_cb) override; Return getNextWriteTimestamp(getNextWriteTimestamp_cb _hidl_cb) override; Return setCallback(const sp& callback) override; @@ -97,11 +109,17 @@ struct StreamOut : public IStreamOut { Return getMmapPosition(getMmapPosition_cb _hidl_cb) override; private: + bool mIsClosed; audio_hw_device_t *mDevice; audio_stream_out_t *mStream; sp mStreamCommon; sp> mStreamMmap; sp mCallback; + std::unique_ptr mDataMQ; + std::unique_ptr mStatusMQ; + EventFlag* mEfGroup; + std::atomic mStopWriteThread; + sp mWriteThread; virtual ~StreamOut(); diff --git a/audio/2.0/types.hal b/audio/2.0/types.hal index 37c39e4d..8fc43140 100644 --- a/audio/2.0/types.hal +++ b/audio/2.0/types.hal @@ -89,3 +89,21 @@ struct MmapPosition { int64_t timeNanoseconds; // time stamp in ns, CLOCK_MONOTONIC int32_t positionFrames; // increasing 32 bit frame count reset when IStream.stop() is called }; + +/* + * The message queue flags used to synchronize reads and writes from + * message queues used by StreamIn and StreamOut. + */ +enum MessageQueueFlagBits : uint32_t { + NOT_EMPTY = 1 << 0, + NOT_FULL = 1 << 1 +}; + +/* + * The priority of threads executing reads and writes of audio data. + */ +enum ThreadPriority : int32_t { + NORMAL = 0, + FAST_CAPTURE = 3, + FAST_MIXER = 3 +}; -- 2.11.0