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

Commit 14fee7b0 authored by Mikhail Naganov's avatar Mikhail Naganov
Browse files

audio: Add stub default MMAP implementation

Add support for MMAP simulation to the default
implementation. Since there is no support for MMAP audio I/O on
CVD, the implementation is a stub.

The implementation supports 'createMmapBuffer' operation via
vendor property 'aosp.createMmapBuffer'. VTS tests for MMAP
streams are enabled back for this case (and for upcoming API
V4). VTS calls 'createMmapBuffer' after the stream has
transitioned out from the 'STANDBY' state.

Also, VTS for MMAP updated to wait for the burst size duration
after each 'burst' command in order to simulate audio I/O flow
and ensure that the stream positions get eventually updated.

Note that even with 'createMmapBuffer', the HAL implementation
must still provide some valid shared memory FD when opening an MMAP
stream. This is a limitation due to the requirement of the HAL
API on the descriptor being "non-null". The VTS and the framework
will not try to 'mmap' that initial FD.

Bug: 274456992
Test: atest VtsHalAudioCoreTargetTest
Change-Id: I6d5783b3108886ff5c1328ddc60c623d4290d3d3
parent b0a3695b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ cc_library {
        "stub/ApeHeader.cpp",
        "stub/DriverStubImpl.cpp",
        "stub/ModuleStub.cpp",
        "stub/StreamMmapStub.cpp",
        "stub/StreamOffloadStub.cpp",
        "stub/StreamStub.cpp",
        "usb/ModuleUsb.cpp",
+47 −9
Original line number Diff line number Diff line
@@ -21,18 +21,23 @@
#include <android-base/logging.h>

#include "core-impl/ModulePrimary.h"
#include "core-impl/StreamMmapStub.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::hasMmapFlag;
using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::hardware::audio::core::StreamDescriptor;
using aidl::android::media::audio::common::AudioInputFlags;
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::AudioPortExt;
using aidl::android::media::audio::common::MicrophoneInfo;

namespace aidl::android::hardware::audio::core {
@@ -62,6 +67,11 @@ ndk::ScopedAStatus ModulePrimary::createInputStream(StreamContext&& context,
                                                    const SinkMetadata& sinkMetadata,
                                                    const std::vector<MicrophoneInfo>& microphones,
                                                    std::shared_ptr<StreamIn>* result) {
    if (context.isMmap()) {
        // "Stub" is used because there is no support for MMAP audio I/O on CVD.
        return createStreamInstance<StreamInMmapStub>(result, std::move(context), sinkMetadata,
                                                      microphones);
    }
    return createStreamInstance<StreamInPrimary>(result, std::move(context), sinkMetadata,
                                                 microphones);
}
@@ -69,26 +79,54 @@ 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(
    if (context.isMmap()) {
        // "Stub" is used because there is no support for MMAP audio I/O on CVD.
        return createStreamInstance<StreamOutMmapStub>(result, std::move(context), sourceMetadata,
                                                       offloadInfo);
    } else 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);
    }
    return createStreamInstance<StreamOutPrimary>(result, std::move(context), sourceMetadata,
                                                  offloadInfo);
}

ndk::ScopedAStatus ModulePrimary::createMmapBuffer(const AudioPortConfig& portConfig,
                                                   int32_t bufferSizeFrames, int32_t frameSizeBytes,
                                                   MmapBufferDescriptor* desc) {
    const size_t bufferSizeBytes = static_cast<size_t>(bufferSizeFrames) * frameSizeBytes;
    // The actual mmap buffer for I/O is created after the stream exits standby, via
    // 'IStreamCommon.createMmapBuffer'. But we must return a valid file descriptor here because
    // 'MmapBufferDescriptor' can not contain a "null" fd.
    const std::string regionName =
            std::string("mmap-sim-o-") +
            std::to_string(portConfig.ext.get<AudioPortExt::Tag::mix>().handle);
    int fd = ashmem_create_region(regionName.c_str(), bufferSizeBytes);
    if (fd < 0) {
        PLOG(ERROR) << __func__ << ": failed to create shared memory region of " << bufferSizeBytes
                    << " bytes";
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
    }
    desc->sharedMemory.fd = ndk::ScopedFileDescriptor(fd);
    desc->sharedMemory.size = bufferSizeBytes;
    desc->burstSizeFrames = bufferSizeFrames / 2;
    desc->flags = 0;
    LOG(DEBUG) << __func__ << ": " << desc->toString();
    return ndk::ScopedAStatus::ok();
}

int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig&) {
int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig& portConfig) {
    static constexpr int32_t kLowLatencyMs = 5;
    // 85 ms is chosen considering 4096 frames @ 48 kHz. This is the value which allows
    // the virtual Android device implementation to pass CTS. Hardware implementations
    // should have significantly lower latency.
    static constexpr int32_t kLatencyMs = 85;
    return kLatencyMs;
    static constexpr int32_t kStandardLatencyMs = 85;
    return hasMmapFlag(portConfig.flags.value()) ? kLowLatencyMs : kStandardLatencyMs;
}

}  // namespace aidl::android::hardware::audio::core
+3 −0
Original line number Diff line number Diff line
@@ -42,6 +42,9 @@ class ModulePrimary final : public Module {
            const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
                    offloadInfo,
            std::shared_ptr<StreamOut>* result) override;
    ndk::ScopedAStatus createMmapBuffer(
            const ::aidl::android::media::audio::common::AudioPortConfig& portConfig,
            int32_t bufferSizeFrames, int32_t frameSizeBytes, MmapBufferDescriptor* desc) override;
    int32_t getNominalLatencyMs(
            const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override;

+132 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 <mutex>
#include <string>

#include "core-impl/DriverStubImpl.h"
#include "core-impl/Stream.h"

namespace aidl::android::hardware::audio::core {

namespace mmap {

struct DspSimulatorState {
    const bool isInput;
    const int sampleRate;
    const int frameSizeBytes;
    const size_t bufferSizeBytes;
    std::mutex lock;
    // The lock is also used to prevent un-mapping while the memory is in use.
    uint8_t* sharedMemory GUARDED_BY(lock) = nullptr;
    StreamDescriptor::Position mmapPos GUARDED_BY(lock);
};

class DspSimulatorLogic : public ::android::hardware::audio::common::StreamLogic {
  protected:
    explicit DspSimulatorLogic(DspSimulatorState& sharedState) : mSharedState(sharedState) {}
    std::string init() override;
    Status cycle() override;

  private:
    DspSimulatorState& mSharedState;
    uint32_t mCycleDurationUs = 0;
    uint8_t* mMemBegin = nullptr;
    uint8_t* mMemPos = nullptr;
    int64_t mLastFrames = 0;
};

class DspSimulatorWorker
    : public ::android::hardware::audio::common::StreamWorker<DspSimulatorLogic> {
  public:
    explicit DspSimulatorWorker(DspSimulatorState& sharedState)
        : ::android::hardware::audio::common::StreamWorker<DspSimulatorLogic>(sharedState) {}
};

}  // namespace mmap

class DriverMmapStubImpl : public DriverStubImpl {
  public:
    explicit DriverMmapStubImpl(const StreamContext& context);
    ::android::status_t init(DriverCallbackInterface* callback) override;
    ::android::status_t drain(StreamDescriptor::DrainMode drainMode) override;
    ::android::status_t pause() override;
    ::android::status_t start() override;
    ::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
                                 int32_t* latencyMs) override;
    void shutdown() override;
    ::android::status_t refinePosition(StreamDescriptor::Position* position) override;
    ::android::status_t getMmapPositionAndLatency(StreamDescriptor::Position* position,
                                                  int32_t* latency) override;

  protected:
    ::android::status_t initSharedMemory(int ashmemFd);

  private:
    ::android::status_t releaseSharedMemory() REQUIRES(mState.lock);
    ::android::status_t startWorkerIfNeeded();

    mmap::DspSimulatorState mState;
    mmap::DspSimulatorWorker mDspWorker;
    bool mDspWorkerStarted = false;
};

class StreamMmapStub : public StreamCommonImpl, public DriverMmapStubImpl {
  public:
    static const std::string kCreateMmapBufferName;

    StreamMmapStub(StreamContext* context, const Metadata& metadata);
    ~StreamMmapStub();

    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;

  private:
    ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* desc);

    ndk::ScopedFileDescriptor mSharedMemoryFd;
};

class StreamInMmapStub final : public StreamIn, public StreamMmapStub {
  public:
    friend class ndk::SharedRefBase;
    StreamInMmapStub(
            StreamContext&& context,
            const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
            const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);

  private:
    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
};

class StreamOutMmapStub final : public StreamOut, public StreamMmapStub {
  public:
    friend class ndk::SharedRefBase;
    StreamOutMmapStub(
            StreamContext&& context,
            const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
            const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
                    offloadInfo);

  private:
    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
};

}  // namespace aidl::android::hardware::audio::core
+8 −3
Original line number Diff line number Diff line
@@ -19,12 +19,15 @@
#include <mutex>
#include <set>
#include <string>
#include <vector>

#include "core-impl/DriverStubImpl.h"
#include "core-impl/Stream.h"

namespace aidl::android::hardware::audio::core {

namespace offload {

struct DspSimulatorState {
    static constexpr int64_t kSkipBufferNotifyFrames = -1;

@@ -55,9 +58,11 @@ class DspSimulatorWorker
        : ::android::hardware::audio::common::StreamWorker<DspSimulatorLogic>(sharedState) {}
};

}  // namespace offload

class DriverOffloadStubImpl : public DriverStubImpl {
  public:
    DriverOffloadStubImpl(const StreamContext& context);
    explicit DriverOffloadStubImpl(const StreamContext& context);
    ::android::status_t init(DriverCallbackInterface* callback) override;
    ::android::status_t drain(StreamDescriptor::DrainMode drainMode) override;
    ::android::status_t flush() override;
@@ -71,8 +76,8 @@ class DriverOffloadStubImpl : public DriverStubImpl {
    ::android::status_t startWorkerIfNeeded();

    const int64_t mBufferNotifyFrames;
    DspSimulatorState mState;
    DspSimulatorWorker mDspWorker;
    offload::DspSimulatorState mState;
    offload::DspSimulatorWorker mDspWorker;
    bool mDspWorkerStarted = false;
};

Loading