diff options
author | yucliu <yucliu@google.com> | 2020-05-15 14:13:39 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-05-15 14:13:39 +0000 |
commit | 8ef72a433f74c03cef1574ddec3e79033ebb151d (patch) | |
tree | 2f7825610428d120693baab97755b211c24b346a | |
parent | 6c605adb5a129611b67d2a6d8ade943625300e16 (diff) | |
parent | 77f096abc28402ea6dd1ac3dd1e925135d78eaa5 (diff) | |
download | atv-8ef72a433f74c03cef1574ddec3e79033ebb151d.tar.gz |
[AudioProxy] client library -- HAL interfaces am: 77f096abc2
Change-Id: I022432692d97e9b54314acb1739b165c2e792110
-rw-r--r-- | audio_proxy/Android.bp | 3 | ||||
-rw-r--r-- | audio_proxy/AudioProxyDevice.cpp | 61 | ||||
-rw-r--r-- | audio_proxy/AudioProxyDevice.h | 16 | ||||
-rw-r--r-- | audio_proxy/AudioProxyManager.cpp | 3 | ||||
-rw-r--r-- | audio_proxy/AudioProxyStreamOut.cpp | 248 | ||||
-rw-r--r-- | audio_proxy/AudioProxyStreamOut.h | 90 | ||||
-rw-r--r-- | audio_proxy/BusDeviceImpl.cpp | 13 | ||||
-rw-r--r-- | audio_proxy/HidlTypeUtil.cpp | 57 | ||||
-rw-r--r-- | audio_proxy/HidlTypeUtil.h | 38 | ||||
-rw-r--r-- | audio_proxy/StreamOutImpl.cpp | 460 | ||||
-rw-r--r-- | audio_proxy/StreamOutImpl.h | 128 | ||||
-rw-r--r-- | audio_proxy/public/audio_proxy.h | 172 |
12 files changed, 1288 insertions, 1 deletions
diff --git a/audio_proxy/Android.bp b/audio_proxy/Android.bp index ed82228..3c926bd 100644 --- a/audio_proxy/Android.bp +++ b/audio_proxy/Android.bp @@ -34,7 +34,10 @@ cc_defaults { srcs: [ "AudioProxyDevice.cpp", "AudioProxyManager.cpp", + "AudioProxyStreamOut.cpp", "BusDeviceImpl.cpp", + "HidlTypeUtil.cpp", + "StreamOutImpl.cpp", ], header_libs: [ diff --git a/audio_proxy/AudioProxyDevice.cpp b/audio_proxy/AudioProxyDevice.cpp index 657073e..16cdcf4 100644 --- a/audio_proxy/AudioProxyDevice.cpp +++ b/audio_proxy/AudioProxyDevice.cpp @@ -18,8 +18,42 @@ #include <utils/Log.h> +#include "AudioProxyStreamOut.h" +#include "HidlTypeUtil.h" + +#define CHECK_API(func) \ + do { \ + if (!stream->func) { \ + ALOGD("Undefined API %s", #func); \ + return false; \ + } \ + } while (0) + namespace audio_proxy { namespace CPP_VERSION { +namespace { +bool isValidStreamOut(const audio_proxy_stream_out_t* stream) { + CHECK_API(get_buffer_size); + CHECK_API(get_frame_count); + CHECK_API(get_supported_sample_rates); + CHECK_API(get_sample_rate); + CHECK_API(get_supported_channel_masks); + CHECK_API(get_channel_mask); + CHECK_API(get_supported_formats); + CHECK_API(get_format); + CHECK_API(get_latency); + CHECK_API(standby); + CHECK_API(pause); + CHECK_API(resume); + CHECK_API(flush); + CHECK_API(write); + CHECK_API(get_presentation_position); + CHECK_API(set_parameters); + CHECK_API(get_parameters); + + return true; +} +} // namespace AudioProxyDevice::AudioProxyDevice(audio_proxy_device_t* device) : mDevice(device) {} @@ -30,5 +64,32 @@ const char* AudioProxyDevice::getAddress() { return mDevice->get_address(mDevice); } +Result AudioProxyDevice::openOutputStream( + hidl_bitfield<AudioOutputFlag> flags, const AudioConfig& hidlConfig, + std::unique_ptr<AudioProxyStreamOut>* streamOut, + AudioConfig* hidlConfigOut) { + audio_proxy_config_t config = toAudioProxyConfig(hidlConfig); + + audio_proxy_stream_out_t* stream = nullptr; + int ret = mDevice->open_output_stream( + mDevice, static_cast<audio_proxy_output_flags_t>(flags), &config, + &stream); + + if (stream) { + if (!isValidStreamOut(stream)) { + mDevice->close_output_stream(mDevice, stream); + return Result::NOT_SUPPORTED; + } + + *streamOut = std::make_unique<AudioProxyStreamOut>(stream, mDevice); + } + + // Pass the config out even if open_output_stream returns error, as the + // suggested config, so that audio service can re-open the stream with + // suggested config. + *hidlConfigOut = toHidlAudioConfig(config); + return toResult(ret); +} + } // namespace CPP_VERSION } // namespace audio_proxy diff --git a/audio_proxy/AudioProxyDevice.h b/audio_proxy/AudioProxyDevice.h index 010335b..d801fc9 100644 --- a/audio_proxy/AudioProxyDevice.h +++ b/audio_proxy/AudioProxyDevice.h @@ -18,9 +18,20 @@ #include "public/audio_proxy.h" +// clang-format off +#include PATH(android/hardware/audio/FILE_VERSION/types.h) +#include PATH(android/hardware/audio/common/FILE_VERSION/types.h) +// clang-format on + namespace audio_proxy { namespace CPP_VERSION { +using ::android::hardware::hidl_bitfield; +using namespace ::android::hardware::audio::CPP_VERSION; +using namespace ::android::hardware::audio::common::CPP_VERSION; + +class AudioProxyStreamOut; + // C++ friendly wrapper of audio_proxy_device. class AudioProxyDevice final { public: @@ -29,6 +40,11 @@ class AudioProxyDevice final { const char* getAddress(); + Result openOutputStream(hidl_bitfield<AudioOutputFlag> flags, + const AudioConfig& config, + std::unique_ptr<AudioProxyStreamOut>* streamOut, + AudioConfig* configOut); + private: audio_proxy_device_t* const mDevice; }; diff --git a/audio_proxy/AudioProxyManager.cpp b/audio_proxy/AudioProxyManager.cpp index b507046..b84a206 100644 --- a/audio_proxy/AudioProxyManager.cpp +++ b/audio_proxy/AudioProxyManager.cpp @@ -43,7 +43,8 @@ namespace CPP_VERSION { namespace { bool checkDevice(audio_proxy_device_t* device) { - return device && device->get_address; + return device && device->get_address && device->open_output_stream && + device->close_output_stream; } class DeathRecipient; diff --git a/audio_proxy/AudioProxyStreamOut.cpp b/audio_proxy/AudioProxyStreamOut.cpp new file mode 100644 index 0000000..97a40dd --- /dev/null +++ b/audio_proxy/AudioProxyStreamOut.cpp @@ -0,0 +1,248 @@ +// Copyright (C) 2020 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 "AudioProxyStreamOut.h" + +#include "HidlTypeUtil.h" + +#define CHECK_OPT_API(func) \ + do { \ + if (!mStream->func) return Result::NOT_SUPPORTED; \ + } while (0) + +namespace audio_proxy { +namespace CPP_VERSION { +namespace { + +template <typename T> +size_t getArraySize(const T* const arr, T terminator) { + if (!arr) { + return 0; + } + + const T* ptr = arr; + while (*ptr != terminator) { + ptr++; + } + + return ptr - arr; +} + +template <typename T, typename H> +hidl_vec<H> convertToHidlVec(const T* const arr, T terminator) { + const size_t size = getArraySize(arr, terminator); + hidl_vec<H> vec(size); + + for (size_t i = 0; i < size; i++) { + vec[i] = H(arr[i]); + } + + return vec; +} + +std::vector<audio_proxy_key_val_t> buildKeyValVec( + const hidl_vec<ParameterValue>& parameters) { + std::vector<audio_proxy_key_val_t> kvVec(parameters.size() + 1); + + for (size_t i = 0; i < parameters.size(); i++) { + kvVec[i] = {parameters[i].key.c_str(), parameters[i].value.c_str()}; + } + + // Terminator. + kvVec.back() = {}; + return kvVec; +} + +std::vector<const char*> buildKeyVec(const hidl_vec<hidl_string>& keys) { + std::vector<const char*> keyVec(keys.size() + 1); + for (size_t i = 0; i < keys.size(); i++) { + keyVec[i] = keys[i].c_str(); + } + + // Terminator. + keyVec.back() = nullptr; + return keyVec; +} + +void onParametersAvailable(void* obj, const audio_proxy_key_val_t* params) { + std::vector<ParameterValue>* results = + static_cast<std::vector<ParameterValue>*>(obj); + while (params->key != nullptr) { + ParameterValue result; + result.key = params->key; + result.value = params->val; + results->push_back(result); + params++; + } +} +} // namespace + +AudioProxyStreamOut::AudioProxyStreamOut(audio_proxy_stream_out_t* stream, + audio_proxy_device_t* device) + : mStream(stream), mDevice(device) {} + +AudioProxyStreamOut::~AudioProxyStreamOut() { + mDevice->close_output_stream(mDevice, mStream); +} + +uint64_t AudioProxyStreamOut::getFrameCount() const { + return mStream->get_frame_count(mStream); +} + +uint32_t AudioProxyStreamOut::getSampleRate() const { + return mStream->get_sample_rate(mStream); +} + +Result AudioProxyStreamOut::setSampleRate(uint32_t rate) { + CHECK_OPT_API(set_sample_rate); + return toResult(mStream->set_sample_rate(mStream, rate)); +} + +hidl_vec<uint32_t> AudioProxyStreamOut::getSupportedSampleRates( + AudioFormat format) const { + return convertToHidlVec<uint32_t, uint32_t>( + mStream->get_supported_sample_rates( + mStream, static_cast<audio_proxy_format_t>(format)), + 0); +} + +size_t AudioProxyStreamOut::getBufferSize() const { + return mStream->get_buffer_size(mStream); +} + +hidl_bitfield<AudioChannelMask> AudioProxyStreamOut::getChannelMask() const { + return hidl_bitfield<AudioChannelMask>(mStream->get_channel_mask(mStream)); +} + +Result AudioProxyStreamOut::setChannelMask( + hidl_bitfield<AudioChannelMask> mask) { + CHECK_OPT_API(set_channel_mask); + return toResult(mStream->set_channel_mask( + mStream, static_cast<audio_proxy_channel_mask_t>(mask))); +} + +hidl_vec<hidl_bitfield<AudioChannelMask>> +AudioProxyStreamOut::getSupportedChannelMasks(AudioFormat format) const { + const audio_proxy_channel_mask_t* channelMasks = + mStream->get_supported_channel_masks( + mStream, static_cast<audio_proxy_format_t>(format)); + + return convertToHidlVec<audio_proxy_channel_mask_t, + hidl_bitfield<AudioChannelMask>>( + channelMasks, AUDIO_PROXY_CHANNEL_INVALID); +} + +AudioFormat AudioProxyStreamOut::getFormat() const { + return AudioFormat(mStream->get_format(mStream)); +} + +hidl_vec<AudioFormat> AudioProxyStreamOut::getSupportedFormats() const { + return convertToHidlVec<audio_proxy_format_t, AudioFormat>( + mStream->get_supported_formats(mStream), AUDIO_PROXY_FORMAT_INVALID); +} + +Result AudioProxyStreamOut::setFormat(AudioFormat format) { + CHECK_OPT_API(set_format); + return toResult( + mStream->set_format(mStream, static_cast<audio_proxy_format_t>(format))); +} + +Result AudioProxyStreamOut::standby() { + return toResult(mStream->standby(mStream)); +} + +Result AudioProxyStreamOut::setParameters( + const hidl_vec<ParameterValue>& context, + const hidl_vec<ParameterValue>& parameters) { + std::vector<audio_proxy_key_val_t> contextKvVec = buildKeyValVec(context); + std::vector<audio_proxy_key_val_t> parameterKvVec = + buildKeyValVec(parameters); + return toResult(mStream->set_parameters(mStream, contextKvVec.data(), + parameterKvVec.data())); +} + +hidl_vec<ParameterValue> AudioProxyStreamOut::getParameters( + const hidl_vec<ParameterValue>& context, + const hidl_vec<hidl_string>& keys) const { + std::vector<audio_proxy_key_val_t> contextKvVec = buildKeyValVec(context); + std::vector<const char*> keyVec = buildKeyVec(keys); + + std::vector<ParameterValue> results; + results.reserve(keys.size()); + + mStream->get_parameters(mStream, contextKvVec.data(), keyVec.data(), + onParametersAvailable, &results); + + return hidl_vec<ParameterValue>(results); +} + +ssize_t AudioProxyStreamOut::write(const void* buffer, size_t bytes) { + return mStream->write(mStream, buffer, bytes); +} + +uint32_t AudioProxyStreamOut::getLatency() const { + return mStream->get_latency(mStream); +} + +Result AudioProxyStreamOut::getRenderPosition(uint32_t* dsp_frames) const { + CHECK_OPT_API(get_render_position); + return toResult(mStream->get_render_position(mStream, dsp_frames)); +} + +Result AudioProxyStreamOut::getNextWriteTimestamp(int64_t* timestamp) const { + CHECK_OPT_API(get_next_write_timestamp); + return toResult(mStream->get_next_write_timestamp(mStream, timestamp)); +} + +Result AudioProxyStreamOut::getPresentationPosition(uint64_t* frames, + TimeSpec* timestamp) const { + struct timespec ts; + int ret = mStream->get_presentation_position(mStream, frames, &ts); + if (ret != 0) { + return toResult(ret); + } + + timestamp->tvSec = ts.tv_sec; + timestamp->tvNSec = ts.tv_nsec; + return Result::OK; +} + +Result AudioProxyStreamOut::pause() { + return toResult(mStream->pause(mStream)); +} + +Result AudioProxyStreamOut::resume() { + return toResult(mStream->resume(mStream)); +} + +bool AudioProxyStreamOut::supportsDrain() const { + return mStream->drain != nullptr; +} + +Result AudioProxyStreamOut::drain(AudioDrain type) { + return toResult( + mStream->drain(mStream, static_cast<audio_proxy_drain_type_t>(type))); +} + +Result AudioProxyStreamOut::flush() { + return toResult(mStream->flush(mStream)); +} + +Result AudioProxyStreamOut::setVolume(float left, float right) { + CHECK_OPT_API(set_volume); + return toResult(mStream->set_volume(mStream, left, right)); +} + +} // namespace CPP_VERSION +} // namespace audio_proxy diff --git a/audio_proxy/AudioProxyStreamOut.h b/audio_proxy/AudioProxyStreamOut.h new file mode 100644 index 0000000..56ad6a2 --- /dev/null +++ b/audio_proxy/AudioProxyStreamOut.h @@ -0,0 +1,90 @@ +// Copyright (C) 2020 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. + +#pragma once + +#include <memory> + +// clang-format off +#include PATH(android/hardware/audio/FILE_VERSION/types.h) +#include PATH(android/hardware/audio/common/FILE_VERSION/types.h) +// clang-format on + +#include "public/audio_proxy.h" + +namespace audio_proxy { +namespace CPP_VERSION { + +using ::android::hardware::hidl_bitfield; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; + +using namespace ::android::hardware::audio::common::CPP_VERSION; +using namespace ::android::hardware::audio::CPP_VERSION; + +// C++ friendly wrapper of audio_proxy_stream_out. It handles type conversion +// between C type and hidl type. +class AudioProxyStreamOut final { + public: + AudioProxyStreamOut(audio_proxy_stream_out_t* stream, + audio_proxy_device_t* device); + ~AudioProxyStreamOut(); + + size_t getBufferSize() const; + uint64_t getFrameCount() const; + + hidl_vec<uint32_t> getSupportedSampleRates(AudioFormat format) const; + uint32_t getSampleRate() const; + Result setSampleRate(uint32_t rate); + + hidl_vec<hidl_bitfield<AudioChannelMask>> getSupportedChannelMasks( + AudioFormat format) const; + hidl_bitfield<AudioChannelMask> getChannelMask() const; + Result setChannelMask(hidl_bitfield<AudioChannelMask> mask); + + hidl_vec<AudioFormat> getSupportedFormats() const; + AudioFormat getFormat() const; + Result setFormat(AudioFormat format); + + Result standby(); + + Result setParameters(const hidl_vec<ParameterValue>& context, + const hidl_vec<ParameterValue>& parameters); + hidl_vec<ParameterValue> getParameters( + const hidl_vec<ParameterValue>& context, + const hidl_vec<hidl_string>& keys) const; + + uint32_t getLatency() const; + ssize_t write(const void* buffer, size_t bytes); + Result getRenderPosition(uint32_t* dsp_frames) const; + Result getNextWriteTimestamp(int64_t* timestamp) const; + Result getPresentationPosition(uint64_t* frames, TimeSpec* timestamp) const; + + Result pause(); + Result resume(); + + bool supportsDrain() const; + Result drain(AudioDrain type); + + Result flush(); + + Result setVolume(float left, float right); + + private: + audio_proxy_stream_out_t* const mStream; + audio_proxy_device_t* const mDevice; +}; + +} // namespace CPP_VERSION +} // namespace audio_proxy diff --git a/audio_proxy/BusDeviceImpl.cpp b/audio_proxy/BusDeviceImpl.cpp index a3b13a9..ee53baf 100644 --- a/audio_proxy/BusDeviceImpl.cpp +++ b/audio_proxy/BusDeviceImpl.cpp @@ -19,6 +19,8 @@ #include <utils/Log.h> #include "AudioProxyDevice.h" +#include "AudioProxyStreamOut.h" +#include "StreamOutImpl.h" using ::android::hardware::Void; @@ -32,6 +34,17 @@ Return<void> BusDeviceImpl::openOutputStream( int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config, hidl_bitfield<AudioOutputFlag> flags, const SourceMetadata& sourceMetadata, openOutputStream_cb _hidl_cb) { + std::unique_ptr<AudioProxyStreamOut> stream; + AudioConfig suggestedConfig; + + Result res = + mDevice->openOutputStream(flags, config, &stream, &suggestedConfig); + ALOGE_IF(res != Result::OK, "Open output stream error."); + + // Still pass `suggestedConfig` back when `openOutputStream` returns error, + // so that audio service can re-open the stream with a new config. + _hidl_cb(res, stream ? new StreamOutImpl(std::move(stream)) : nullptr, + suggestedConfig); return Void(); } diff --git a/audio_proxy/HidlTypeUtil.cpp b/audio_proxy/HidlTypeUtil.cpp new file mode 100644 index 0000000..3eb3dcb --- /dev/null +++ b/audio_proxy/HidlTypeUtil.cpp @@ -0,0 +1,57 @@ +// Copyright (C) 2020 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 "HidlTypeUtil.h" + +#include <error.h> + +using ::android::hardware::hidl_bitfield; + +namespace audio_proxy { +namespace CPP_VERSION { + +Result toResult(int res) { + switch (res) { + case 0: + return Result::OK; + case EINVAL: + return Result::INVALID_ARGUMENTS; + case ENOSYS: + return Result::NOT_SUPPORTED; + default: + return Result::INVALID_STATE; + } +} + +AudioConfig toHidlAudioConfig(const audio_proxy_config_t& config) { + AudioConfig hidlConfig; + hidlConfig.sampleRateHz = config.sample_rate; + hidlConfig.channelMask = hidl_bitfield<AudioChannelMask>(config.channel_mask); + hidlConfig.format = AudioFormat(config.format); + hidlConfig.frameCount = config.frame_count; + return hidlConfig; +} + +audio_proxy_config_t toAudioProxyConfig(const AudioConfig& hidlConfig) { + audio_proxy_config_t config = {}; + config.sample_rate = hidlConfig.sampleRateHz; + config.channel_mask = + static_cast<audio_proxy_channel_mask_t>(hidlConfig.channelMask); + config.format = static_cast<audio_proxy_format_t>(hidlConfig.format); + config.frame_count = hidlConfig.frameCount; + return config; +} + +} // namespace CPP_VERSION +} // namespace audio_proxy
\ No newline at end of file diff --git a/audio_proxy/HidlTypeUtil.h b/audio_proxy/HidlTypeUtil.h new file mode 100644 index 0000000..138fd5d --- /dev/null +++ b/audio_proxy/HidlTypeUtil.h @@ -0,0 +1,38 @@ +// Copyright (C) 2020 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. + +#pragma once + +#include "public/audio_proxy.h" + +// clang-format off +#include PATH(android/hardware/audio/FILE_VERSION/types.h) +#include PATH(android/hardware/audio/common/FILE_VERSION/types.h) +// clang-format on + +namespace audio_proxy { +namespace CPP_VERSION { + +using namespace ::android::hardware::audio::CPP_VERSION; +using namespace ::android::hardware::audio::common::CPP_VERSION; + +// Convert from C type to HIDL type. +Result toResult(int res); +AudioConfig toHidlAudioConfig(const audio_proxy_config_t& config); + +// Convert from HIDL type to C type. +audio_proxy_config_t toAudioProxyConfig(const AudioConfig& hidlConfig); + +} // namespace CPP_VERSION +} // namespace audio_proxy
\ No newline at end of file diff --git a/audio_proxy/StreamOutImpl.cpp b/audio_proxy/StreamOutImpl.cpp new file mode 100644 index 0000000..eabc1e3 --- /dev/null +++ b/audio_proxy/StreamOutImpl.cpp @@ -0,0 +1,460 @@ +// Copyright (C) 2020 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 "audio_proxy_client" + +#include "StreamOutImpl.h" + +#include <system/audio.h> +#include <time.h> +#include <utils/Log.h> + +#include <cstring> + +#include "AudioProxyStreamOut.h" + +using ::android::status_t; + +namespace audio_proxy { +namespace CPP_VERSION { +namespace { +// 1GB +constexpr uint32_t kMaxBufferSize = 1 << 30; + +void deleteEventFlag(EventFlag* obj) { + if (!obj) { + return; + } + + status_t status = EventFlag::deleteEventFlag(&obj); + ALOGE_IF(status, "write MQ event flag deletion error: %s", strerror(-status)); +} + +class WriteThread : public Thread { + public: + // WriteThread's lifespan never exceeds StreamOut's lifespan. + WriteThread(std::atomic<bool>* stop, AudioProxyStreamOut* stream, + StreamOutImpl::CommandMQ* commandMQ, + StreamOutImpl::DataMQ* dataMQ, StreamOutImpl::StatusMQ* statusMQ, + EventFlag* eventFlag); + + ~WriteThread() override; + + private: + bool threadLoop() override; + + IStreamOut::WriteStatus doGetLatency(); + IStreamOut::WriteStatus doGetPresentationPosition(); + IStreamOut::WriteStatus doWrite(); + + std::atomic<bool>* const mStop; + AudioProxyStreamOut* mStream; + StreamOutImpl::CommandMQ* const mCommandMQ; + StreamOutImpl::DataMQ* const mDataMQ; + StreamOutImpl::StatusMQ* const mStatusMQ; + EventFlag* const mEventFlag; + const std::unique_ptr<uint8_t[]> mBuffer; +}; + +WriteThread::WriteThread(std::atomic<bool>* stop, AudioProxyStreamOut* stream, + StreamOutImpl::CommandMQ* commandMQ, + StreamOutImpl::DataMQ* dataMQ, + StreamOutImpl::StatusMQ* statusMQ, + EventFlag* eventFlag) + : Thread(false /*canCallJava*/), + mStop(stop), + mStream(stream), + mCommandMQ(commandMQ), + mDataMQ(dataMQ), + mStatusMQ(statusMQ), + mEventFlag(eventFlag), + mBuffer(new uint8_t[mDataMQ->getQuantumCount()]) {} + +WriteThread::~WriteThread() = default; + +IStreamOut::WriteStatus WriteThread::doWrite() { + const size_t availToRead = mDataMQ->availableToRead(); + IStreamOut::WriteStatus status; + status.replyTo = IStreamOut::WriteCommand::WRITE; + status.retval = Result::OK; + status.reply.written = 0; + if (mDataMQ->read(&mBuffer[0], availToRead)) { + status.reply.written = availToRead; + ssize_t writeResult = mStream->write(&mBuffer[0], availToRead); + if (writeResult >= 0) { + status.reply.written = writeResult; + ALOGW_IF(writeResult < availToRead, + "Stream doesn't write all the bytes. Drop the unwritten bytes."); + } else { + status.retval = Result::INVALID_STATE; + } + } + + return status; +} + +IStreamOut::WriteStatus WriteThread::doGetPresentationPosition() { + IStreamOut::WriteStatus status; + status.replyTo = IStreamOut::WriteCommand::GET_PRESENTATION_POSITION; + status.retval = mStream->getPresentationPosition( + &status.reply.presentationPosition.frames, + &status.reply.presentationPosition.timeStamp); + return status; +} + +IStreamOut::WriteStatus WriteThread::doGetLatency() { + IStreamOut::WriteStatus status; + status.replyTo = IStreamOut::WriteCommand::GET_LATENCY; + status.retval = Result::OK; + status.reply.latencyMs = mStream->getLatency(); + return status; +} + +bool WriteThread::threadLoop() { + // This implementation doesn't return control back to the Thread until the + // parent thread 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; + mEventFlag->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), + &efState); + if (!(efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY))) { + continue; // Nothing to do. + } + + IStreamOut::WriteCommand replyTo; + if (!mCommandMQ->read(&replyTo)) { + continue; // Nothing to do. + } + + IStreamOut::WriteStatus status; + switch (replyTo) { + case IStreamOut::WriteCommand::WRITE: + status = doWrite(); + break; + case IStreamOut::WriteCommand::GET_PRESENTATION_POSITION: + status = doGetPresentationPosition(); + break; + case IStreamOut::WriteCommand::GET_LATENCY: + status = doGetLatency(); + break; + default: + ALOGE("Unknown write thread command code %d", replyTo); + status.retval = Result::NOT_SUPPORTED; + break; + } + if (!mStatusMQ->write(&status)) { + ALOGE("status message queue write failed"); + } + mEventFlag->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)); + } + + return false; +} + +} // namespace + +StreamOutImpl::StreamOutImpl(std::unique_ptr<AudioProxyStreamOut> stream) + : mStream(std::move(stream)), mEventFlag(nullptr, deleteEventFlag) {} + +StreamOutImpl::~StreamOutImpl() { + closeImpl(); + + if (mWriteThread) { + status_t status = mWriteThread->join(); + ALOGE_IF(status, "write thread exit error: %s", strerror(-status)); + } + + mEventFlag.reset(); +} + +Return<uint64_t> StreamOutImpl::getFrameSize() { + audio_format_t format = static_cast<audio_format_t>(mStream->getFormat()); + + if (!audio_has_proportional_frames(format)) { + return sizeof(int8_t); + } + + size_t channel_sample_size = audio_bytes_per_sample(format); + return audio_channel_count_from_out_mask( + static_cast<audio_channel_mask_t>(mStream->getChannelMask())) * + channel_sample_size; +} + +Return<uint64_t> StreamOutImpl::getFrameCount() { + return mStream->getFrameCount(); +} + +Return<uint64_t> StreamOutImpl::getBufferSize() { + return mStream->getBufferSize(); +} + +Return<uint32_t> StreamOutImpl::getSampleRate() { + return mStream->getSampleRate(); +} + +Return<void> StreamOutImpl::getSupportedSampleRates( + AudioFormat format, getSupportedSampleRates_cb _hidl_cb) { + _hidl_cb(Result::OK, mStream->getSupportedSampleRates(format)); + return Void(); +} + +Return<void> StreamOutImpl::getSupportedChannelMasks( + AudioFormat format, getSupportedChannelMasks_cb _hidl_cb) { + _hidl_cb(Result::OK, mStream->getSupportedChannelMasks(format)); + return Void(); +} + +Return<Result> StreamOutImpl::setSampleRate(uint32_t sampleRateHz) { + return mStream->setSampleRate(sampleRateHz); +} + +Return<hidl_bitfield<AudioChannelMask>> StreamOutImpl::getChannelMask() { + return hidl_bitfield<AudioChannelMask>(mStream->getChannelMask()); +} + +Return<Result> StreamOutImpl::setChannelMask( + hidl_bitfield<AudioChannelMask> mask) { + return mStream->setChannelMask(mask); +} + +Return<AudioFormat> StreamOutImpl::getFormat() { return mStream->getFormat(); } + +Return<void> StreamOutImpl::getSupportedFormats( + getSupportedFormats_cb _hidl_cb) { + _hidl_cb(mStream->getSupportedFormats()); + return Void(); +} + +Return<Result> StreamOutImpl::setFormat(AudioFormat format) { + return mStream->setFormat(format); +} + +Return<void> StreamOutImpl::getAudioProperties(getAudioProperties_cb _hidl_cb) { + _hidl_cb(mStream->getSampleRate(), mStream->getChannelMask(), + mStream->getFormat()); + return Void(); +} + +Return<Result> StreamOutImpl::addEffect(uint64_t effectId) { + return Result::NOT_SUPPORTED; +} + +Return<Result> StreamOutImpl::removeEffect(uint64_t effectId) { + return Result::NOT_SUPPORTED; +} + +Return<Result> StreamOutImpl::standby() { return mStream->standby(); } + +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(Result::OK, mStream->getParameters(context, keys)); + return Void(); +} + +Return<Result> StreamOutImpl::setParameters( + const hidl_vec<ParameterValue>& context, + const hidl_vec<ParameterValue>& parameters) { + return mStream->setParameters(context, parameters); +} + +Return<Result> StreamOutImpl::setHwAvSync(uint32_t hwAvSync) { + return Result::NOT_SUPPORTED; +} + +Return<Result> StreamOutImpl::close() { return closeImpl(); } + +Result StreamOutImpl::closeImpl() { + if (mStopWriteThread.load( + std::memory_order_relaxed)) { // only this thread writes + return Result::INVALID_STATE; + } + mStopWriteThread.store(true, std::memory_order_release); + if (mEventFlag) { + mEventFlag->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)); + } + return Result::OK; +} + +Return<uint32_t> StreamOutImpl::getLatency() { return mStream->getLatency(); } + +Return<Result> StreamOutImpl::setVolume(float left, float right) { + return mStream->setVolume(left, right); +} + +Return<void> StreamOutImpl::prepareForWriting(uint32_t frameSize, + uint32_t framesCount, + prepareForWriting_cb _hidl_cb) { + ThreadInfo threadInfo = {0, 0}; + + // 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) { + ALOGE("the client attempted to call prepareForWriting twice"); + return sendError(Result::INVALID_STATE); + } + + if (frameSize == 0 || framesCount == 0) { + ALOGE("Invalid frameSize (%u) or framesCount (%u)", frameSize, framesCount); + return sendError(Result::INVALID_ARGUMENTS); + } + + if (frameSize > kMaxBufferSize / framesCount) { + ALOGE("Buffer too big: %u*%u bytes > MAX_BUFFER_SIZE (%u)", frameSize, + framesCount, kMaxBufferSize); + return sendError(Result::INVALID_ARGUMENTS); + } + + auto commandMQ = std::make_unique<CommandMQ>(1); + if (!commandMQ->isValid()) { + ALOGE("command MQ is invalid"); + return sendError(Result::INVALID_ARGUMENTS); + } + + auto dataMQ = + std::make_unique<DataMQ>(frameSize * framesCount, true /* EventFlag */); + if (!dataMQ->isValid()) { + ALOGE("data MQ is invalid"); + return sendError(Result::INVALID_ARGUMENTS); + } + + auto statusMQ = std::make_unique<StatusMQ>(1); + if (!statusMQ->isValid()) { + ALOGE("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) { + ALOGE("failed creating event flag for data MQ: %s", strerror(-status)); + return sendError(Result::INVALID_ARGUMENTS); + } + + sp<WriteThread> writeThread = + new WriteThread(&mStopWriteThread, mStream.get(), commandMQ.get(), + dataMQ.get(), statusMQ.get(), eventFlag.get()); + status = writeThread->run("writer", ::android::PRIORITY_URGENT_AUDIO); + if (status != ::android::OK) { + ALOGW("failed to start writer thread: %s", 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); + threadInfo.pid = getpid(); + threadInfo.tid = mWriteThread->getTid(); + _hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(), + *mStatusMQ->getDesc(), threadInfo); + return Void(); +} + +Return<void> StreamOutImpl::getRenderPosition(getRenderPosition_cb _hidl_cb) { + uint32_t dspFrames = 0; + Result res = mStream->getRenderPosition(&dspFrames); + _hidl_cb(res, dspFrames); + return Void(); +} + +Return<void> StreamOutImpl::getNextWriteTimestamp( + getNextWriteTimestamp_cb _hidl_cb) { + int64_t timestamp = 0; + Result res = mStream->getNextWriteTimestamp(×tamp); + _hidl_cb(res, timestamp); + 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(); +} + +Return<Result> StreamOutImpl::pause() { return mStream->pause(); } + +Return<Result> StreamOutImpl::resume() { return mStream->resume(); } + +Return<bool> StreamOutImpl::supportsDrain() { return mStream->supportsDrain(); } + +Return<Result> StreamOutImpl::drain(AudioDrain type) { + return mStream->drain(type); +} + +Return<Result> StreamOutImpl::flush() { return mStream->flush(); } + +Return<void> StreamOutImpl::getPresentationPosition( + getPresentationPosition_cb _hidl_cb) { + uint64_t frames = 0; + TimeSpec ts = {0, 0}; + Result result = mStream->getPresentationPosition(&frames, &ts); + _hidl_cb(result, frames, ts); + 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(); +} + +Return<void> StreamOutImpl::updateSourceMetadata( + const SourceMetadata& sourceMetadata) { + return Void(); +} + +Return<Result> StreamOutImpl::selectPresentation(int32_t presentationId, + int32_t programId) { + return Result::NOT_SUPPORTED; +} + +} // namespace CPP_VERSION +} // namespace audio_proxy diff --git a/audio_proxy/StreamOutImpl.h b/audio_proxy/StreamOutImpl.h new file mode 100644 index 0000000..435e0f8 --- /dev/null +++ b/audio_proxy/StreamOutImpl.h @@ -0,0 +1,128 @@ +// Copyright (C) 2020 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. + +#pragma once + +// clang-format off +#include PATH(android/hardware/audio/FILE_VERSION/IStreamOut.h) +// clang-format on + +#include <fmq/EventFlag.h> +#include <fmq/MessageQueue.h> +#include <hidl/MQDescriptor.h> +#include <hidl/Status.h> +#include <utils/Thread.h> + +using ::android::sp; +using ::android::Thread; +using ::android::hardware::EventFlag; +using ::android::hardware::hidl_bitfield; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::MessageQueue; +using ::android::hardware::Return; +using ::android::hardware::Void; +using namespace ::android::hardware::audio::common::CPP_VERSION; +using namespace ::android::hardware::audio::CPP_VERSION; + +namespace audio_proxy { +namespace CPP_VERSION { +class AudioProxyStreamOut; + +class StreamOutImpl : public IStreamOut { + public: + using CommandMQ = MessageQueue<WriteCommand, kSynchronizedReadWrite>; + using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>; + using StatusMQ = MessageQueue<WriteStatus, kSynchronizedReadWrite>; + + explicit StreamOutImpl(std::unique_ptr<AudioProxyStreamOut> stream); + ~StreamOutImpl() override; + + // Methods from ::android::hardware::audio::CPP_VERSION::IStream follow. + Return<uint64_t> getFrameSize() override; + Return<uint64_t> getFrameCount() override; + Return<uint64_t> getBufferSize() override; + Return<uint32_t> getSampleRate() override; + Return<void> getSupportedSampleRates( + AudioFormat format, getSupportedSampleRates_cb _hidl_cb) override; + Return<void> getSupportedChannelMasks( + AudioFormat format, getSupportedChannelMasks_cb _hidl_cb) override; + Return<Result> setSampleRate(uint32_t sampleRateHz) override; + Return<hidl_bitfield<AudioChannelMask>> getChannelMask() override; + Return<Result> setChannelMask(hidl_bitfield<AudioChannelMask> mask) override; + Return<AudioFormat> getFormat() override; + Return<void> getSupportedFormats(getSupportedFormats_cb _hidl_cb) override; + Return<Result> setFormat(AudioFormat format) override; + Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override; + Return<Result> addEffect(uint64_t effectId) override; + Return<Result> removeEffect(uint64_t effectId) override; + Return<Result> standby() override; + Return<void> getDevices(getDevices_cb _hidl_cb) override; + Return<Result> setDevices(const hidl_vec<DeviceAddress>& devices) override; + Return<void> getParameters(const hidl_vec<ParameterValue>& context, + const hidl_vec<hidl_string>& keys, + getParameters_cb _hidl_cb) override; + Return<Result> setParameters( + const hidl_vec<ParameterValue>& context, + const hidl_vec<ParameterValue>& parameters) override; + Return<Result> setHwAvSync(uint32_t hwAvSync) override; + Return<Result> close() override; + + // Methods from ::android::hardware::audio::CPP_VERSION::IStreamOut follow. + Return<uint32_t> getLatency() override; + Return<Result> setVolume(float left, float right) override; + Return<void> prepareForWriting(uint32_t frameSize, uint32_t framesCount, + prepareForWriting_cb _hidl_cb) override; + Return<void> getRenderPosition(getRenderPosition_cb _hidl_cb) override; + Return<void> getNextWriteTimestamp( + getNextWriteTimestamp_cb _hidl_cb) override; + Return<Result> setCallback(const sp<IStreamOutCallback>& callback) override; + Return<Result> clearCallback() override; + Return<void> supportsPauseAndResume( + supportsPauseAndResume_cb _hidl_cb) override; + Return<Result> pause() override; + Return<Result> resume() override; + Return<bool> supportsDrain() override; + Return<Result> drain(AudioDrain type) override; + Return<Result> flush() override; + Return<void> getPresentationPosition( + getPresentationPosition_cb _hidl_cb) override; + Return<Result> start() override; + Return<Result> stop() override; + Return<void> createMmapBuffer(int32_t minSizeFrames, + createMmapBuffer_cb _hidl_cb) override; + Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override; + Return<void> updateSourceMetadata( + const SourceMetadata& sourceMetadata) override; + Return<Result> selectPresentation(int32_t presentationId, + int32_t programId) override; + + private: + typedef void (*EventFlagDeleter)(EventFlag*); + + Result closeImpl(); + + std::unique_ptr<AudioProxyStreamOut> mStream; + + std::unique_ptr<CommandMQ> mCommandMQ; + std::unique_ptr<DataMQ> mDataMQ; + std::unique_ptr<StatusMQ> mStatusMQ; + std::unique_ptr<EventFlag, EventFlagDeleter> mEventFlag; + std::atomic<bool> mStopWriteThread = false; + sp<Thread> mWriteThread; +}; + +} // namespace CPP_VERSION +} // namespace audio_proxy diff --git a/audio_proxy/public/audio_proxy.h b/audio_proxy/public/audio_proxy.h index ae79381..6ffca0d 100644 --- a/audio_proxy/public/audio_proxy.h +++ b/audio_proxy/public/audio_proxy.h @@ -34,11 +34,183 @@ extern "C" { // Most of the struct/functions just converts the HIDL definitions into C // definitions. +// The following enum and typedef are subset of those defined in +// hardware/interfaces/audio/common/$VERSION/types.hal, or +// hardware/interfaces/audio/$VERSION/types.hal. +// The selected subsets are those commonly supported by a normal audio HAL. The +// library won't check the validation of these enums. In other words, Audio +// server can still pass value not defined here to the application. + +// AudioFormat +enum { + AUDIO_PROXY_FORMAT_INVALID = 0xFFFFFFFFu, + AUDIO_PROXY_FORMAT_PCM_16_BIT = 0x1u, + AUDIO_PROXY_FORMAT_PCM_8_BIT = 0x2u, + AUDIO_PROXY_FORMAT_PCM_FLOAT = 0x5u, +}; +typedef uint32_t audio_proxy_format_t; + +// AudioChannelMask +enum { + AUDIO_PROXY_CHANNEL_INVALID = 0xC0000000u, + AUDIO_PROXY_CHANNEL_OUT_MONO = 0x1u, + AUDIO_PROXY_CHANNEL_OUT_STEREO = 0x3u, +}; +typedef uint32_t audio_proxy_channel_mask_t; + +// AudioDrain +enum { + AUDIO_PROXY_DRAIN_ALL, + AUDIO_PROXY_DRAIN_EARLY_NOTIFY, +}; +typedef int32_t audio_proxy_drain_type_t; + +// AudioOutputFlag +enum { + AUDIO_PROXY_OUTPUT_FLAG_NONE = 0x0, + AUDIO_PROXY_OUTPUT_FLAG_DIRECT = 0x1, +}; +typedef int32_t audio_proxy_output_flags_t; + +// AudioConfig +typedef struct { + uint32_t sample_rate; + audio_proxy_channel_mask_t channel_mask; + audio_proxy_format_t format; + uint32_t frame_count; + + // Points to extra fields defined in the future versions. + void* extension; +} audio_proxy_config_t; + +// Util structure for key value pair. +typedef struct { + const char* key; + const char* val; +} audio_proxy_key_val_t; + +typedef void (*audio_proxy_get_parameters_callback_t)( + void*, const audio_proxy_key_val_t*); + +// The following struct/functions mirror those definitions in audio HAL. They +// should have the same requirement as audio HAL interfaces, unless specified. + +// IStreamOut. +struct audio_proxy_stream_out { + size_t (*get_buffer_size)(const struct audio_proxy_stream_out* stream); + uint64_t (*get_frame_count)(const struct audio_proxy_stream_out* stream); + + // Gets all the sample rate supported by the stream. The list is terminated + // by 0. The returned list should have the same life cycle of |stream|. + const uint32_t* (*get_supported_sample_rates)( + const struct audio_proxy_stream_out* stream, audio_proxy_format_t format); + uint32_t (*get_sample_rate)(const struct audio_proxy_stream_out* stream); + + // optional. + int (*set_sample_rate)(struct audio_proxy_stream_out* stream, uint32_t rate); + + // Gets all the channel mask supported by the stream. The list is terminated + // by AUDIO_PROXY_CHANNEL_INVALID. The returned list should have the same life + // cycle of |stream|. + const audio_proxy_channel_mask_t* (*get_supported_channel_masks)( + const struct audio_proxy_stream_out* stream, audio_proxy_format_t format); + audio_proxy_channel_mask_t (*get_channel_mask)( + const struct audio_proxy_stream_out* stream); + + // optional. + int (*set_channel_mask)(struct audio_proxy_stream_out* stream, + audio_proxy_channel_mask_t mask); + + // Gets all the audio formats supported by the stream. The list is terminated + // by AUDIO_PROXY_FORMAT_INVALID. The returned list should have the same life + // cycle of |stream|. + const audio_proxy_format_t* (*get_supported_formats)( + const struct audio_proxy_stream_out* stream); + audio_proxy_format_t (*get_format)( + const struct audio_proxy_stream_out* stream); + + // optional. + int (*set_format)(struct audio_proxy_stream_out* stream, + audio_proxy_format_t format); + + uint32_t (*get_latency)(const struct audio_proxy_stream_out* stream); + + int (*standby)(struct audio_proxy_stream_out* stream); + + int (*pause)(struct audio_proxy_stream_out* stream); + int (*resume)(struct audio_proxy_stream_out* stream); + + // optional. + int (*drain)(struct audio_proxy_stream_out* stream, + audio_proxy_drain_type_t type); + + int (*flush)(struct audio_proxy_stream_out* stream); + + // Writes |buffer| into |stream|. This is called on an internal thread of this + // library. + ssize_t (*write)(struct audio_proxy_stream_out* self, const void* buffer, + size_t bytes); + + // optional. + int (*get_render_position)(const struct audio_proxy_stream_out* stream, + uint32_t* dsp_frames); + + // optional. + int (*get_next_write_timestamp)(const struct audio_proxy_stream_out* stream, + int64_t* timestamp); + + int (*get_presentation_position)(const struct audio_proxy_stream_out* stream, + uint64_t* frames, + struct timespec* timestamp); + + // opional. + int (*set_volume)(struct audio_proxy_stream_out* stream, float left, + float right); + + // Sets parameters on |stream|. Both |context| and |param| are terminated + // by key_val_t whose key is null. They are only valid during the function + // call. + int (*set_parameters)(struct audio_proxy_stream_out* stream, + const audio_proxy_key_val_t* context, + const audio_proxy_key_val_t* param); + + // Gets parameters from |stream|. + // |context| is key val pairs array terminated by null key + // audio_proxy_key_val_t. |keys| is C string array, terminated by nullptr. + // |on_result| is the callback to deliver the result. It must be called before + // this function returns, with |obj| as the first argument, and the list of + // caller owned list of key value pairs as the second argument. + // |obj| opaque object. Implementation should not touch it. + void (*get_parameters)(const struct audio_proxy_stream_out* stream, + const audio_proxy_key_val_t* context, + const char** keys, + audio_proxy_get_parameters_callback_t on_result, + void* obj); + + // optional. + int (*dump)(const struct audio_proxy_stream_out* stream, int fd); + + // Pointer to the next version structure, for compatibility. + void* extension; +}; + +typedef struct audio_proxy_stream_out audio_proxy_stream_out_t; + // Represents an audio HAL bus device. struct audio_proxy_device { // Returns the unique address of this device. const char* (*get_address)(struct audio_proxy_device* device); + // Similar to IDevice::openOutputStream. + int (*open_output_stream)(struct audio_proxy_device* device, + audio_proxy_output_flags_t flags, + audio_proxy_config_t* config, + audio_proxy_stream_out_t** stream_out); + + // Close |stream|. No more methods will be called on |stream| after this. + void (*close_output_stream)(struct audio_proxy_device* device, + struct audio_proxy_stream_out* stream); + // Points to next version's struct. Implementation should set this field to // null if next version struct is not available. // This allows library to work with applications integrated with older version |