Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit efc2b326 authored by Mikhail Naganov's avatar Mikhail Naganov
Browse files

audio: Implement compressed offload simulation

Add 'StreamOffloadStub' to 'ModulePrimary' which simulates
offloaded playback ('COMPRESS_OFFLOAD') and can send asynchronous
callbacks. The simulation only keeps track of the playback
time, it does not actually decode compressed audio. This
approach was chosen to avoid linking against any decoder
implementation.

For the same reason, the only supported format is 'APE' ("Monkey
audio")  which was chosen due to the simplicity of its header
structure.

As 'StreamOffloadStub' provides a proper implementation of
'EARLY_NOTIFY' drain callbacks, there is no more need to use
'aosp.forceDrainToDraining' property in VTS, and support for
it has been removed.

Bug: 373872271
Bug: 384431822
Test: atest CtsMediaAudioTestCases
Test: atest VtsHalAudioCoreTargetTest
Merged-In: I74ae867227c768d4afe9314ed93168da58362131
Change-Id: I74ae867227c768d4afe9314ed93168da58362131
parent 17b3fa3c
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -184,6 +184,12 @@ constexpr U makeBitPositionFlagMask(std::initializer_list<E> flags) {
    return result;
}

template <typename E, typename U = std::underlying_type_t<E>,
          typename = std::enable_if_t<is_bit_position_enum<E>::value>>
constexpr bool areAllBitPositionFlagsSet(U mask, std::initializer_list<E> flags) {
    return (mask & makeBitPositionFlagMask<E>(flags)) == makeBitPositionFlagMask<E>(flags);
}

template <typename E, typename U = std::underlying_type_t<E>,
          typename = std::enable_if_t<is_bit_position_enum<E>::value>>
constexpr bool isAnyBitPositionFlagSet(U mask, std::initializer_list<E> flags) {
+2 −0
Original line number Diff line number Diff line
@@ -77,8 +77,10 @@ cc_library {
        "r_submix/ModuleRemoteSubmix.cpp",
        "r_submix/SubmixRoute.cpp",
        "r_submix/StreamRemoteSubmix.cpp",
        "stub/ApeHeader.cpp",
        "stub/DriverStubImpl.cpp",
        "stub/ModuleStub.cpp",
        "stub/StreamOffloadStub.cpp",
        "stub/StreamStub.cpp",
        "usb/ModuleUsb.cpp",
        "usb/StreamUsb.cpp",
+3 −12
Original line number Diff line number Diff line
@@ -211,9 +211,9 @@ ndk::ScopedAStatus Module::createStreamContext(
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
    }
    const auto& flags = portConfigIt->flags.value();
    StreamContext::DebugParameters params{
            mDebug.streamTransientStateDelayMs, mVendorDebug.forceTransientBurst,
            mVendorDebug.forceSynchronousDrain, mVendorDebug.forceDrainToDraining};
    StreamContext::DebugParameters params{mDebug.streamTransientStateDelayMs,
                                          mVendorDebug.forceTransientBurst,
                                          mVendorDebug.forceSynchronousDrain};
    std::unique_ptr<StreamContext::DataMQ> dataMQ = nullptr;
    std::shared_ptr<IStreamCallback> streamAsyncCallback = nullptr;
    std::shared_ptr<ISoundDose> soundDose;
@@ -1546,7 +1546,6 @@ ndk::ScopedAStatus Module::generateHwAvSyncId(int32_t* _aidl_return) {

const std::string Module::VendorDebug::kForceTransientBurstName = "aosp.forceTransientBurst";
const std::string Module::VendorDebug::kForceSynchronousDrainName = "aosp.forceSynchronousDrain";
const std::string Module::VendorDebug::kForceDrainToDrainingName = "aosp.forceDrainToDraining";

ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& in_ids,
                                               std::vector<VendorParameter>* _aidl_return) {
@@ -1561,10 +1560,6 @@ ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& i
            VendorParameter forceSynchronousDrain{.id = id};
            forceSynchronousDrain.ext.setParcelable(Boolean{mVendorDebug.forceSynchronousDrain});
            _aidl_return->push_back(std::move(forceSynchronousDrain));
        } else if (id == VendorDebug::kForceDrainToDrainingName) {
            VendorParameter forceDrainToDraining{.id = id};
            forceDrainToDraining.ext.setParcelable(Boolean{mVendorDebug.forceDrainToDraining});
            _aidl_return->push_back(std::move(forceDrainToDraining));
        } else {
            allParametersKnown = false;
            LOG(VERBOSE) << __func__ << ": " << mType << ": unrecognized parameter \"" << id << "\"";
@@ -1605,10 +1600,6 @@ ndk::ScopedAStatus Module::setVendorParameters(const std::vector<VendorParameter
            if (!extractParameter<Boolean>(p, &mVendorDebug.forceSynchronousDrain)) {
                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
            }
        } else if (p.id == VendorDebug::kForceDrainToDrainingName) {
            if (!extractParameter<Boolean>(p, &mVendorDebug.forceDrainToDraining)) {
                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
            }
        } else {
            allParametersKnown = false;
            LOG(VERBOSE) << __func__ << ": " << mType << ": unrecognized parameter \"" << p.id
+27 −2
Original line number Diff line number Diff line
@@ -21,12 +21,16 @@
#include <android-base/logging.h>

#include "core-impl/ModulePrimary.h"
#include "core-impl/StreamOffloadStub.h"
#include "core-impl/StreamPrimary.h"
#include "core-impl/Telephony.h"

using aidl::android::hardware::audio::common::areAllBitPositionFlagsSet;
using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::media::audio::common::AudioIoFlags;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::AudioOutputFlags;
using aidl::android::media::audio::common::AudioPort;
using aidl::android::media::audio::common::AudioPortConfig;
using aidl::android::media::audio::common::MicrophoneInfo;
@@ -43,6 +47,17 @@ ndk::ScopedAStatus ModulePrimary::getTelephony(std::shared_ptr<ITelephony>* _aid
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus ModulePrimary::calculateBufferSizeFrames(
        const ::aidl::android::media::audio::common::AudioFormatDescription& format,
        int32_t latencyMs, int32_t sampleRateHz, int32_t* bufferSizeFrames) {
    if (format.type != ::aidl::android::media::audio::common::AudioFormatType::PCM &&
        StreamOffloadStub::getSupportedEncodings().count(format.encoding)) {
        *bufferSizeFrames = sampleRateHz / 2;  // 1/2 of a second.
        return ndk::ScopedAStatus::ok();
    }
    return Module::calculateBufferSizeFrames(format, latencyMs, sampleRateHz, bufferSizeFrames);
}

ndk::ScopedAStatus ModulePrimary::createInputStream(StreamContext&& context,
                                                    const SinkMetadata& sinkMetadata,
                                                    const std::vector<MicrophoneInfo>& microphones,
@@ -54,8 +69,18 @@ ndk::ScopedAStatus ModulePrimary::createInputStream(StreamContext&& context,
ndk::ScopedAStatus ModulePrimary::createOutputStream(
        StreamContext&& context, const SourceMetadata& sourceMetadata,
        const std::optional<AudioOffloadInfo>& offloadInfo, std::shared_ptr<StreamOut>* result) {
    if (!areAllBitPositionFlagsSet(
                context.getFlags().get<AudioIoFlags::output>(),
                {AudioOutputFlags::COMPRESS_OFFLOAD, AudioOutputFlags::NON_BLOCKING})) {
        return createStreamInstance<StreamOutPrimary>(result, std::move(context), sourceMetadata,
                                                      offloadInfo);
    } else {
        // "Stub" is used because there is no actual decoder. The stream just
        // extracts the clip duration from the media file header and simulates
        // playback over time.
        return createStreamInstance<StreamOutOffloadStub>(result, std::move(context),
                                                          sourceMetadata, offloadInfo);
    }
}

int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig&) {
+52 −37
Original line number Diff line number Diff line
@@ -142,12 +142,16 @@ std::string StreamWorkerCommonLogic::init() {
                   ", size in bytes: " + std::to_string(mDataBufferSize);
        }
    }
    if (::android::status_t status = mDriver->init(); status != STATUS_OK) {
    if (::android::status_t status = mDriver->init(this /*DriverCallbackInterface*/);
        status != STATUS_OK) {
        return "Failed to initialize the driver: " + std::to_string(status);
    }
    return "";
}

void StreamWorkerCommonLogic::onBufferStateChange(size_t /*bufferFramesLeft*/) {}
void StreamWorkerCommonLogic::onClipStateChange(size_t /*clipFramesLeft*/, bool /*hasNextClip*/) {}

void StreamWorkerCommonLogic::populateReply(StreamDescriptor::Reply* reply,
                                            bool isConnected) const {
    static const StreamDescriptor::Position kUnknownPosition = {
@@ -381,48 +385,60 @@ bool StreamInWorkerLogic::read(size_t clientSize, StreamDescriptor::Reply* reply

const std::string StreamOutWorkerLogic::kThreadName = "writer";

StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
    if (mState == StreamDescriptor::State::DRAINING && mContext->getForceDrainToDraining() &&
        mOnDrainReadyStatus == OnDrainReadyStatus::UNSENT) {
void StreamOutWorkerLogic::onBufferStateChange(size_t bufferFramesLeft) {
    const StreamDescriptor::State state = mState;
    LOG(DEBUG) << __func__ << ": state: " << toString(state)
               << ", bufferFramesLeft: " << bufferFramesLeft;
    if (state == StreamDescriptor::State::TRANSFERRING) {
        mState = StreamDescriptor::State::ACTIVE;
        std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
        if (asyncCallback != nullptr) {
            ndk::ScopedAStatus status = asyncCallback->onDrainReady();
            ndk::ScopedAStatus status = asyncCallback->onTransferReady();
            if (!status.isOk()) {
                LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
                LOG(ERROR) << __func__ << ": error from onTransferReady: " << status;
            }
            // This sets the timeout for moving into IDLE on next iterations.
            switchToTransientState(StreamDescriptor::State::DRAINING);
            mOnDrainReadyStatus = OnDrainReadyStatus::SENT;
        }
    } else if (mState == StreamDescriptor::State::DRAINING ||
               mState == StreamDescriptor::State::TRANSFERRING) {
        if (auto stateDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
                    std::chrono::steady_clock::now() - mTransientStateStart);
            stateDurationMs >= mTransientStateDelayMs) {
    }
}

void StreamOutWorkerLogic::onClipStateChange(size_t clipFramesLeft, bool hasNextClip) {
    const DrainState drainState = mDrainState;
    std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
            if (asyncCallback == nullptr) {
                // In blocking mode, mState can only be DRAINING.
                mState = StreamDescriptor::State::IDLE;
            } else {
                // In a real implementation, the driver should notify the HAL about
                // drain or transfer completion. In the stub, we switch unconditionally.
                if (mState == StreamDescriptor::State::DRAINING) {
                    mState = StreamDescriptor::State::IDLE;
                    if (mOnDrainReadyStatus != OnDrainReadyStatus::SENT) {
    LOG(DEBUG) << __func__ << ": drainState: " << drainState << "; clipFramesLeft "
               << clipFramesLeft << "; hasNextClip? " << hasNextClip << "; asyncCallback? "
               << (asyncCallback != nullptr);
    if (drainState != DrainState::NONE && clipFramesLeft == 0) {
        mState =
                hasNextClip ? StreamDescriptor::State::TRANSFERRING : StreamDescriptor::State::IDLE;
        mDrainState = DrainState::NONE;
        if (drainState == DrainState::ALL && asyncCallback != nullptr) {
            LOG(DEBUG) << __func__ << ": sending onDrainReady";
            ndk::ScopedAStatus status = asyncCallback->onDrainReady();
            if (!status.isOk()) {
                LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
            }
                        mOnDrainReadyStatus = OnDrainReadyStatus::SENT;
        }
                } else {
                    mState = StreamDescriptor::State::ACTIVE;
                    ndk::ScopedAStatus status = asyncCallback->onTransferReady();
    } else if (drainState == DrainState::EN && clipFramesLeft > 0) {
        // The stream state does not change, it is still draining.
        mDrainState = DrainState::EN_SENT;
        if (asyncCallback != nullptr) {
            LOG(DEBUG) << __func__ << ": sending onDrainReady";
            ndk::ScopedAStatus status = asyncCallback->onDrainReady();
            if (!status.isOk()) {
                        LOG(ERROR) << __func__ << ": error from onTransferReady: " << status;
                LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
            }
        }
    }
}

StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
    // Non-blocking mode is handled within 'onClipStateChange'
    if (std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
        mState == StreamDescriptor::State::DRAINING && asyncCallback == nullptr) {
        if (auto stateDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
                    std::chrono::steady_clock::now() - mTransientStateStart);
            stateDurationMs >= mTransientStateDelayMs) {
            mState = StreamDescriptor::State::IDLE;
            if (mTransientStateDelayMs.count() != 0) {
                LOG(DEBUG) << __func__ << ": switched to state " << toString(mState)
                           << " after a timeout";
@@ -552,10 +568,9 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
                            mState = StreamDescriptor::State::IDLE;
                        } else {
                            switchToTransientState(StreamDescriptor::State::DRAINING);
                            mOnDrainReadyStatus =
                                    mode == StreamDescriptor::DrainMode::DRAIN_EARLY_NOTIFY
                                            ? OnDrainReadyStatus::UNSENT
                                            : OnDrainReadyStatus::IGNORE;
                            mDrainState = mode == StreamDescriptor::DrainMode::DRAIN_EARLY_NOTIFY
                                                  ? DrainState::EN
                                                  : DrainState::ALL;
                        }
                    } else {
                        LOG(ERROR) << __func__ << ": drain failed: " << status;
Loading