audio: Add a helper class to simplify legacy HALs migration am: 43a85cfb2b

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2674340

Change-Id: Id2572acb414548928dc3ee086d569d6882313254
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Mikhail Naganov
2023-07-25 18:27:28 +00:00
committed by Automerger Merge Worker
8 changed files with 444 additions and 15 deletions

View File

@@ -76,6 +76,7 @@ cc_library {
"ModulePrimary.cpp",
"SoundDose.cpp",
"Stream.cpp",
"StreamSwitcher.cpp",
"Telephony.cpp",
"alsa/Mixer.cpp",
"alsa/ModuleAlsa.cpp",

View File

@@ -670,8 +670,7 @@ ndk::ScopedAStatus StreamCommonImpl::close() {
LOG(DEBUG) << __func__ << ": joining the worker thread...";
mWorker->stop();
LOG(DEBUG) << __func__ << ": worker thread joined";
onClose();
mWorker->setClosed();
onClose(mWorker->setClosed());
return ndk::ScopedAStatus::ok();
} else {
LOG(ERROR) << __func__ << ": stream was already closed";

View File

@@ -0,0 +1,230 @@
/*
* Copyright (C) 2023 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 <limits>
#define LOG_TAG "AHAL_StreamSwitcher"
#include <Utils.h>
#include <android-base/logging.h>
#include <error/expected_utils.h>
#include "core-impl/StreamStub.h"
#include "core-impl/StreamSwitcher.h"
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::media::audio::common::AudioDevice;
namespace aidl::android::hardware::audio::core {
StreamSwitcher::StreamSwitcher(StreamContext* context, const Metadata& metadata)
: mMetadata(metadata), mStream(new InnerStreamWrapper<StreamStub>(context, mMetadata)) {}
ndk::ScopedAStatus StreamSwitcher::closeCurrentStream(bool validateStreamState) {
if (!mStream) return ndk::ScopedAStatus::ok();
RETURN_STATUS_IF_ERROR(mStream->prepareToClose());
RETURN_STATUS_IF_ERROR(mStream->close());
if (validateStreamState && !isValidClosingStreamState(mStream->getStatePriorToClosing())) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
mStream.reset();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamSwitcher::close() {
if (mStream != nullptr) {
auto status = closeCurrentStream(false /*validateStreamState*/);
// The actual state is irrelevant since only StreamSwitcher cares about it.
onClose(StreamDescriptor::State::STANDBY);
return status;
}
LOG(ERROR) << __func__ << ": stream was already closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
ndk::ScopedAStatus StreamSwitcher::prepareToClose() {
if (mStream != nullptr) {
return mStream->prepareToClose();
}
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
ndk::ScopedAStatus StreamSwitcher::updateHwAvSyncId(int32_t in_hwAvSyncId) {
if (mStream == nullptr) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
RETURN_STATUS_IF_ERROR(mStream->updateHwAvSyncId(in_hwAvSyncId));
mHwAvSyncId = in_hwAvSyncId;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamSwitcher::getVendorParameters(const std::vector<std::string>& in_ids,
std::vector<VendorParameter>* _aidl_return) {
if (mStream == nullptr) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (mIsStubStream) {
LOG(ERROR) << __func__ << ": the stream is not connected";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
return mStream->getVendorParameters(in_ids, _aidl_return);
}
ndk::ScopedAStatus StreamSwitcher::setVendorParameters(
const std::vector<VendorParameter>& in_parameters, bool in_async) {
if (mStream == nullptr) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (mIsStubStream) {
mMissedParameters.emplace_back(in_parameters, in_async);
return ndk::ScopedAStatus::ok();
}
return mStream->setVendorParameters(in_parameters, in_async);
}
ndk::ScopedAStatus StreamSwitcher::addEffect(const std::shared_ptr<IEffect>& in_effect) {
if (mStream == nullptr) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (!mIsStubStream) {
RETURN_STATUS_IF_ERROR(mStream->addEffect(in_effect));
}
mEffects.push_back(in_effect);
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamSwitcher::removeEffect(const std::shared_ptr<IEffect>& in_effect) {
if (mStream == nullptr) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
for (auto it = mEffects.begin(); it != mEffects.end();) {
if ((*it)->asBinder() == in_effect->asBinder()) {
it = mEffects.erase(it);
} else {
++it;
}
}
return !mIsStubStream ? mStream->removeEffect(in_effect) : ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamSwitcher::getStreamCommonCommon(
std::shared_ptr<IStreamCommon>* _aidl_return) {
if (!mCommon) {
LOG(FATAL) << __func__ << ": the common interface was not created";
}
*_aidl_return = mCommon.getInstance();
LOG(DEBUG) << __func__ << ": returning " << _aidl_return->get()->asBinder().get();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamSwitcher::updateMetadataCommon(const Metadata& metadata) {
if (mStream == nullptr) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
mMetadata = metadata;
return !mIsStubStream ? mStream->updateMetadataCommon(metadata) : ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamSwitcher::initInstance(
const std::shared_ptr<StreamCommonInterface>& delegate) {
mCommon = ndk::SharedRefBase::make<StreamCommonDelegator>(delegate);
// The delegate is null because StreamSwitcher handles IStreamCommon methods by itself.
return mStream->initInstance(nullptr);
}
const StreamContext& StreamSwitcher::getContext() const {
return *mContext;
}
bool StreamSwitcher::isClosed() const {
return mStream == nullptr || mStream->isClosed();
}
const StreamCommonInterface::ConnectedDevices& StreamSwitcher::getConnectedDevices() const {
return mStream->getConnectedDevices();
}
ndk::ScopedAStatus StreamSwitcher::setConnectedDevices(const std::vector<AudioDevice>& devices) {
LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(devices);
if (mStream->getConnectedDevices() == devices) return ndk::ScopedAStatus::ok();
const DeviceSwitchBehavior behavior = switchCurrentStream(devices);
if (behavior == DeviceSwitchBehavior::UNSUPPORTED_DEVICES) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
} else if (behavior == DeviceSwitchBehavior::SWITCH_TO_STUB_STREAM && !devices.empty()) {
// This is an error in the extending class.
LOG(FATAL) << __func__
<< ": switching to stub stream with connected devices is not allowed";
}
if (behavior == USE_CURRENT_STREAM) {
mIsStubStream = false;
} else {
LOG(DEBUG) << __func__ << ": connected devices changed, switching stream";
// Two streams can't be opened for the same context, thus we always need to close
// the current one before creating a new one.
RETURN_STATUS_IF_ERROR(closeCurrentStream(true /*validateStreamState*/));
if (behavior == CREATE_NEW_STREAM) {
mStream = createNewStream(devices, mContext, mMetadata);
mIsStubStream = false;
} else { // SWITCH_TO_STUB_STREAM
mStream.reset(new InnerStreamWrapper<StreamStub>(mContext, mMetadata));
mIsStubStream = true;
}
// The delegate is null because StreamSwitcher handles IStreamCommon methods by itself.
if (ndk::ScopedAStatus status = mStream->initInstance(nullptr); !status.isOk()) {
// Need to close the current failed stream, and report an error.
// Since we can't operate without a stream implementation, put a stub in.
RETURN_STATUS_IF_ERROR(closeCurrentStream(false /*validateStreamState*/));
mStream.reset(new InnerStreamWrapper<StreamStub>(mContext, mMetadata));
(void)mStream->initInstance(nullptr);
(void)mStream->setConnectedDevices(devices);
return status;
}
}
RETURN_STATUS_IF_ERROR(mStream->setConnectedDevices(devices));
if (behavior == CREATE_NEW_STREAM) {
// These updates are less critical, only log warning on failure.
if (mHwAvSyncId.has_value()) {
if (auto status = mStream->updateHwAvSyncId(*mHwAvSyncId); !status.isOk()) {
LOG(WARNING) << __func__ << ": could not update HW AV Sync for a new stream: "
<< status.getDescription();
}
}
for (const auto& vndParam : mMissedParameters) {
if (auto status = mStream->setVendorParameters(vndParam.first, vndParam.second);
!status.isOk()) {
LOG(WARNING) << __func__ << ": error while setting parameters for a new stream: "
<< status.getDescription();
}
}
mMissedParameters.clear();
for (const auto& effect : mEffects) {
if (auto status = mStream->addEffect(effect); !status.isOk()) {
LOG(WARNING) << __func__ << ": error while adding effect for a new stream: "
<< status.getDescription();
}
}
}
return ndk::ScopedAStatus::ok();
}
} // namespace aidl::android::hardware::audio::core

View File

@@ -66,7 +66,8 @@ class StreamContext {
DataMQ;
// Ensure that this value is not used by any of StreamDescriptor.State enums
static constexpr int32_t STATE_CLOSED = -1;
static constexpr StreamDescriptor::State STATE_CLOSED =
static_cast<StreamDescriptor::State>(-1);
struct DebugParameters {
// An extra delay for transient states, in ms.
@@ -205,10 +206,14 @@ struct DriverInterface {
class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic {
public:
bool isClosed() const {
return static_cast<int32_t>(mState.load()) == StreamContext::STATE_CLOSED;
bool isClosed() const { return mState == StreamContext::STATE_CLOSED; }
StreamDescriptor::State setClosed() {
auto prevState = mState.exchange(StreamContext::STATE_CLOSED);
if (prevState != StreamContext::STATE_CLOSED) {
mStatePriorToClosing = prevState;
}
return mStatePriorToClosing;
}
void setClosed() { mState = static_cast<StreamDescriptor::State>(StreamContext::STATE_CLOSED); }
void setIsConnected(bool connected) { mIsConnected = connected; }
protected:
@@ -231,6 +236,9 @@ class StreamWorkerCommonLogic : public ::android::hardware::audio::common::Strea
// which happens on the worker thread only.
StreamContext* const mContext;
DriverInterface* const mDriver;
// This is the state the stream was in before being closed. It is retrieved by the main
// thread after joining the worker thread.
StreamDescriptor::State mStatePriorToClosing = StreamDescriptor::State::STANDBY;
// Atomic fields are used both by the main and worker threads.
std::atomic<bool> mIsConnected = false;
static_assert(std::atomic<StreamDescriptor::State>::is_always_lock_free);
@@ -252,7 +260,7 @@ struct StreamWorkerInterface {
virtual ~StreamWorkerInterface() = default;
virtual bool isClosed() const = 0;
virtual void setIsConnected(bool isConnected) = 0;
virtual void setClosed() = 0;
virtual StreamDescriptor::State setClosed() = 0;
virtual bool start() = 0;
virtual void stop() = 0;
};
@@ -267,7 +275,7 @@ class StreamWorkerImpl : public StreamWorkerInterface,
: WorkerImpl(context, driver) {}
bool isClosed() const override { return WorkerImpl::isClosed(); }
void setIsConnected(bool isConnected) override { WorkerImpl::setIsConnected(isConnected); }
void setClosed() override { WorkerImpl::setClosed(); }
StreamDescriptor::State setClosed() override { return WorkerImpl::setClosed(); }
bool start() override {
return WorkerImpl::start(WorkerImpl::kThreadName, ANDROID_PRIORITY_AUDIO);
}
@@ -459,7 +467,7 @@ class StreamCommonImpl : virtual public StreamCommonInterface, virtual public Dr
};
}
virtual void onClose() = 0;
virtual void onClose(StreamDescriptor::State statePriorToClosing) = 0;
void stopWorker();
const StreamContext& mContext;

View File

@@ -81,7 +81,7 @@ class StreamInRemoteSubmix final : public StreamIn, public StreamRemoteSubmix {
const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
private:
void onClose() override { defaultOnClose(); }
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
ndk::ScopedAStatus getActiveMicrophones(
std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return)
override;
@@ -97,7 +97,7 @@ class StreamOutRemoteSubmix final : public StreamOut, public StreamRemoteSubmix
offloadInfo);
private:
void onClose() override { defaultOnClose(); }
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
};
} // namespace aidl::android::hardware::audio::core

View File

@@ -52,7 +52,7 @@ class StreamInStub final : public StreamIn, public StreamStub {
const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
private:
void onClose() override { defaultOnClose(); }
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
};
class StreamOutStub final : public StreamOut, public StreamStub {
@@ -64,7 +64,7 @@ class StreamOutStub final : public StreamOut, public StreamStub {
offloadInfo);
private:
void onClose() override { defaultOnClose(); }
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
};
} // namespace aidl::android::hardware::audio::core

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2023 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 "Stream.h"
namespace aidl::android::hardware::audio::core {
// 'StreamSwitcher' is implementation of 'StreamCommonInterface' which allows
// dynamically switching the underlying stream implementation based on currently
// connected devices. This is achieved by replacing inheritance from
// 'StreamCommonImpl' with owning an instance of it. StreamSwitcher must be
// extended in order to supply the logic for choosing the stream
// implementation. When there are no connected devices, for instance, upon the
// creation, the StreamSwitcher engages an instance of a stub stream in order to
// keep serving requests coming via 'StreamDescriptor'.
//
// StreamSwitcher implements the 'IStreamCommon' interface directly, with
// necessary delegation to the current stream implementation. While the stub
// stream is engaged, any requests made via 'IStreamCommon' (parameters, effects
// setting, etc) are postponed and only delivered on device connection change
// to the "real" stream implementation provided by the extending class. This is why
// the behavior of StreamSwitcher in the "stub" state is not identical to behavior
// of 'StreamStub'. It can become a full substitute for 'StreamStub' once
// device connection change event occurs and the extending class returns
// 'LEAVE_CURRENT_STREAM' from 'switchCurrentStream' method.
//
// There is a natural limitation that the current stream implementation may only
// be switched when the stream is in the 'STANDBY' state. Thus, when the event
// to switch the stream occurs, the current stream is stopped and joined, and
// its last state is validated. Since the change of the set of connected devices
// normally occurs on patch updates, if the stream was not in standby, this is
// reported to the caller of 'IModule.setAudioPatch' as the 'EX_ILLEGAL_STATE'
// error.
//
// The simplest use case, when the implementor just needs to emulate the legacy HAL API
// behavior of receiving the connected devices upon stream creation, the implementation
// of the extending class can look as follows. We assume that 'StreamLegacy' implementation
// is the one requiring to know connected devices on creation:
//
// class StreamLegacy : public StreamCommonImpl {
// public:
// StreamLegacy(StreamContext* context, const Metadata& metadata,
// const std::vector<AudioDevice>& devices);
// };
//
// class StreamOutLegacy final : public StreamOut, public StreamSwitcher {
// public:
// StreamOutLegacy(StreamContext&& context, metatadata etc.)
// private:
// DeviceSwitchBehavior switchCurrentStream(const std::vector<AudioDevice>&) override {
// // This implementation effectively postpones stream creation until
// // receiving the first call to 'setConnectedDevices' with a non-empty list.
// return isStubStream() ? DeviceSwitchBehavior::CREATE_NEW_STREAM :
// DeviceSwitchBehavior::USE_CURRENT_STREAM;
// }
// std::unique_ptr<StreamCommonInterfaceEx> createNewStream(
// const std::vector<AudioDevice>& devices,
// StreamContext* context, const Metadata& metadata) override {
// return std::unique_ptr<StreamCommonInterfaceEx>(new InnerStreamWrapper<StreamLegacy>(
// context, metadata, devices));
// }
// void onClose(StreamDescriptor::State) override { defaultOnClose(); }
// }
//
class StreamCommonInterfaceEx : virtual public StreamCommonInterface {
public:
virtual StreamDescriptor::State getStatePriorToClosing() const = 0;
};
template <typename T>
class InnerStreamWrapper : public T, public StreamCommonInterfaceEx {
public:
InnerStreamWrapper(StreamContext* context, const Metadata& metadata) : T(context, metadata) {}
StreamDescriptor::State getStatePriorToClosing() const override { return mStatePriorToClosing; }
private:
// Do not need to do anything on close notification from the inner stream
// because StreamSwitcher handles IStreamCommon::close by itself.
void onClose(StreamDescriptor::State statePriorToClosing) override {
mStatePriorToClosing = statePriorToClosing;
}
StreamDescriptor::State mStatePriorToClosing = StreamDescriptor::State::STANDBY;
};
class StreamSwitcher : virtual public StreamCommonInterface {
public:
StreamSwitcher(StreamContext* context, const Metadata& metadata);
ndk::ScopedAStatus close() override;
ndk::ScopedAStatus prepareToClose() override;
ndk::ScopedAStatus updateHwAvSyncId(int32_t in_hwAvSyncId) override;
ndk::ScopedAStatus getVendorParameters(const std::vector<std::string>& in_ids,
std::vector<VendorParameter>* _aidl_return) override;
ndk::ScopedAStatus setVendorParameters(const std::vector<VendorParameter>& in_parameters,
bool in_async) override;
ndk::ScopedAStatus addEffect(
const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect)
override;
ndk::ScopedAStatus removeEffect(
const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect)
override;
ndk::ScopedAStatus getStreamCommonCommon(std::shared_ptr<IStreamCommon>* _aidl_return) override;
ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override;
ndk::ScopedAStatus initInstance(
const std::shared_ptr<StreamCommonInterface>& delegate) override;
const StreamContext& getContext() const override;
bool isClosed() const override;
const ConnectedDevices& getConnectedDevices() const override;
ndk::ScopedAStatus setConnectedDevices(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices)
override;
protected:
// Since switching a stream requires closing down the current stream, StreamSwitcher
// asks the extending class its intent on the connected devices change.
enum DeviceSwitchBehavior {
// Continue using the current stream implementation. If it's the stub implementation,
// StreamSwitcher starts treating the stub stream as a "real" implementation,
// without effectively closing it and starting again.
USE_CURRENT_STREAM,
// This is the normal case when the extending class provides a "real" implementation
// which is not a stub implementation.
CREATE_NEW_STREAM,
// This is the case when the extending class wants to revert back to the initial
// condition of using a stub stream provided by the StreamSwitcher. This behavior
// is only allowed when the list of connected devices is empty.
SWITCH_TO_STUB_STREAM,
// Use when the set of devices is not supported by the extending class. This returns
// 'EX_UNSUPPORTED_OPERATION' from 'setConnectedDevices'.
UNSUPPORTED_DEVICES,
};
// StreamSwitcher will call these methods from 'setConnectedDevices'. If the switch behavior
// is 'CREATE_NEW_STREAM', the 'createwNewStream' function will be called (with the same
// device vector) for obtaining a new stream implementation, assuming that closing
// the current stream was a success.
virtual DeviceSwitchBehavior switchCurrentStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) = 0;
virtual std::unique_ptr<StreamCommonInterfaceEx> createNewStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices,
StreamContext* context, const Metadata& metadata) = 0;
virtual void onClose(StreamDescriptor::State streamPriorToClosing) = 0;
bool isStubStream() const { return mIsStubStream; }
StreamCommonInterfaceEx* getCurrentStream() const { return mStream.get(); }
private:
using VndParam = std::pair<std::vector<VendorParameter>, bool /*isAsync*/>;
static constexpr bool isValidClosingStreamState(StreamDescriptor::State state) {
return state == StreamDescriptor::State::STANDBY || state == StreamDescriptor::State::ERROR;
}
ndk::ScopedAStatus closeCurrentStream(bool validateStreamState);
// StreamSwitcher does not own the context.
StreamContext* mContext;
Metadata mMetadata;
ChildInterface<StreamCommonDelegator> mCommon;
// The current stream.
std::unique_ptr<StreamCommonInterfaceEx> mStream;
// Indicates whether 'mCurrentStream' is a stub stream implementation
// maintained by StreamSwitcher until the extending class provides a "real"
// implementation. The invariant of this state is that there are no connected
// devices.
bool mIsStubStream = true;
// Storage for the data from commands received via 'IStreamCommon'.
std::optional<int32_t> mHwAvSyncId;
std::vector<VndParam> mMissedParameters;
std::vector<std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>> mEffects;
};
} // namespace aidl::android::hardware::audio::core

View File

@@ -53,7 +53,7 @@ class StreamInUsb final : public StreamIn, public StreamUsb {
const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
private:
void onClose() override { defaultOnClose(); }
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
ndk::ScopedAStatus getActiveMicrophones(
std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return)
override;
@@ -68,7 +68,7 @@ class StreamOutUsb final : public StreamOut, public StreamUsb {
offloadInfo);
private:
void onClose() override { defaultOnClose(); }
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
ndk::ScopedAStatus getHwVolume(std::vector<float>* _aidl_return) override;
ndk::ScopedAStatus setHwVolume(const std::vector<float>& in_channelVolumes) override;