/* * 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 an 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& devices); // }; // // class StreamOutLegacy final : public StreamOut, public StreamSwitcher { // public: // StreamOutLegacy(StreamContext&& context, metatadata etc.) // private: // DeviceSwitchBehavior switchCurrentStream(const std::vector&) 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 createNewStream( // const std::vector& devices, // StreamContext* context, const Metadata& metadata) override { // return std::unique_ptr(new InnerStreamWrapper( // context, metadata, devices)); // } // void onClose(StreamDescriptor::State) override { defaultOnClose(); } // } // class StreamCommonInterfaceEx : virtual public StreamCommonInterface { public: virtual StreamDescriptor::State getStatePriorToClosing() const = 0; }; template class InnerStreamWrapper : public T, public StreamCommonInterfaceEx { public: template InnerStreamWrapper(Args&&... args) : T(std::forward(args)...) {} 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& in_ids, std::vector* _aidl_return) override; ndk::ScopedAStatus setVendorParameters(const std::vector& 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* _aidl_return) override; ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override; ndk::ScopedAStatus initInstance( const std::shared_ptr& 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; ndk::ScopedAStatus bluetoothParametersUpdated() 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 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, 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 mCommon; // The current stream. std::unique_ptr 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 mHwAvSyncId; std::vector mMissedParameters; std::vector> mEffects; bool mBluetoothParametersUpdated = false; }; } // namespace aidl::android::hardware::audio::core