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
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 36fd0d0..a8e8c50 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -80,6 +80,7 @@
"stub/ApeHeader.cpp",
"stub/DriverStubImpl.cpp",
"stub/ModuleStub.cpp",
+ "stub/StreamMmapStub.cpp",
"stub/StreamOffloadStub.cpp",
"stub/StreamStub.cpp",
"usb/ModuleUsb.cpp",
diff --git a/audio/aidl/default/ModulePrimary.cpp b/audio/aidl/default/ModulePrimary.cpp
index 2a1dba9..6cb9251 100644
--- a/audio/aidl/default/ModulePrimary.cpp
+++ b/audio/aidl/default/ModulePrimary.cpp
@@ -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 @@
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::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 {
+ 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})) {
// "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);
}
-int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig&) {
+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& 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
diff --git a/audio/aidl/default/include/core-impl/ModulePrimary.h b/audio/aidl/default/include/core-impl/ModulePrimary.h
index a657dc5..c93deed 100644
--- a/audio/aidl/default/include/core-impl/ModulePrimary.h
+++ b/audio/aidl/default/include/core-impl/ModulePrimary.h
@@ -42,6 +42,9 @@
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;
diff --git a/audio/aidl/default/include/core-impl/StreamMmapStub.h b/audio/aidl/default/include/core-impl/StreamMmapStub.h
new file mode 100644
index 0000000..0332007
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/StreamMmapStub.h
@@ -0,0 +1,132 @@
+/*
+ * 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
diff --git a/audio/aidl/default/include/core-impl/StreamOffloadStub.h b/audio/aidl/default/include/core-impl/StreamOffloadStub.h
index 24e98c2..09b88aa 100644
--- a/audio/aidl/default/include/core-impl/StreamOffloadStub.h
+++ b/audio/aidl/default/include/core-impl/StreamOffloadStub.h
@@ -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 @@
: ::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 @@
::android::status_t startWorkerIfNeeded();
const int64_t mBufferNotifyFrames;
- DspSimulatorState mState;
- DspSimulatorWorker mDspWorker;
+ offload::DspSimulatorState mState;
+ offload::DspSimulatorWorker mDspWorker;
bool mDspWorkerStarted = false;
};
diff --git a/audio/aidl/default/stub/DriverStubImpl.cpp b/audio/aidl/default/stub/DriverStubImpl.cpp
index 0d129e6..cb8ee70 100644
--- a/audio/aidl/default/stub/DriverStubImpl.cpp
+++ b/audio/aidl/default/stub/DriverStubImpl.cpp
@@ -15,6 +15,7 @@
*/
#include <cmath>
+#include <cstdlib>
#define LOG_TAG "AHAL_Stream"
#include <android-base/logging.h>
diff --git a/audio/aidl/default/stub/StreamMmapStub.cpp b/audio/aidl/default/stub/StreamMmapStub.cpp
new file mode 100644
index 0000000..f48aea4
--- /dev/null
+++ b/audio/aidl/default/stub/StreamMmapStub.cpp
@@ -0,0 +1,286 @@
+/*
+ * 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.
+ */
+
+#include <unistd.h>
+#include <cstdlib>
+
+#define LOG_TAG "AHAL_MmapStream"
+#include <android-base/logging.h>
+#include <audio_utils/clock.h>
+#include <error/Result.h>
+#include <utils/SystemClock.h>
+
+#include "core-impl/StreamMmapStub.h"
+
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::MicrophoneInfo;
+
+namespace aidl::android::hardware::audio::core {
+
+namespace mmap {
+
+std::string DspSimulatorLogic::init() {
+ {
+ std::lock_guard l(mSharedState.lock);
+ mSharedState.mmapPos.timeNs = StreamDescriptor::Position::UNKNOWN;
+ mSharedState.mmapPos.frames = StreamDescriptor::Position::UNKNOWN;
+ }
+ // Progress in buffer size chunks to make sure that VTS tolerates infrequent position updates
+ // (see b/350998390).
+ mCycleDurationUs = (mSharedState.bufferSizeBytes / mSharedState.frameSizeBytes) *
+ MICROS_PER_SECOND / mSharedState.sampleRate;
+ return "";
+}
+
+DspSimulatorLogic::Status DspSimulatorLogic::cycle() {
+ // Simulate DSP moving along in real time.
+ const int64_t timeBeginNs = ::android::uptimeNanos();
+ usleep(mCycleDurationUs);
+ int64_t newFrames;
+ std::lock_guard l(mSharedState.lock);
+ if (mMemBegin != mSharedState.sharedMemory) {
+ mMemBegin = mSharedState.sharedMemory;
+ if (mMemBegin != nullptr) mMemPos = mMemBegin;
+ }
+ if (mMemBegin != nullptr) {
+ mSharedState.mmapPos.timeNs = ::android::uptimeNanos();
+ newFrames = (mSharedState.mmapPos.timeNs - timeBeginNs) * mSharedState.sampleRate /
+ NANOS_PER_SECOND;
+ // Restore the reported frames position to ensure continuity.
+ if (mSharedState.mmapPos.frames == StreamDescriptor::Position::UNKNOWN) {
+ mSharedState.mmapPos.frames = mLastFrames;
+ }
+ mSharedState.mmapPos.frames += newFrames;
+ mLastFrames = mSharedState.mmapPos.frames;
+ if (mSharedState.isInput) {
+ for (size_t i = 0; i < static_cast<size_t>(newFrames) * mSharedState.frameSizeBytes;
+ ++i) {
+ *mMemPos++ = std::rand() % 255;
+ if (mMemPos >= mMemBegin + mSharedState.bufferSizeBytes) mMemPos = mMemBegin;
+ }
+ }
+ } else {
+ LOG(WARNING) << "No shared memory but the DSP is active";
+ mSharedState.mmapPos.timeNs = StreamDescriptor::Position::UNKNOWN;
+ mSharedState.mmapPos.frames = StreamDescriptor::Position::UNKNOWN;
+ }
+ return Status::CONTINUE;
+}
+
+} // namespace mmap
+
+using mmap::DspSimulatorState;
+
+DriverMmapStubImpl::DriverMmapStubImpl(const StreamContext& context)
+ : DriverStubImpl(context, 0 /*asyncSleepTimeUs*/),
+ mState{mIsInput, mSampleRate, static_cast<int>(mFrameSizeBytes),
+ mBufferSizeFrames * mFrameSizeBytes},
+ mDspWorker(mState) {
+ LOG_IF(FATAL, !context.isMmap()) << "The steam must be used in MMAP mode";
+}
+
+::android::status_t DriverMmapStubImpl::init(DriverCallbackInterface* callback) {
+ RETURN_STATUS_IF_ERROR(DriverStubImpl::init(callback));
+ return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::drain(StreamDescriptor::DrainMode drainMode) {
+ RETURN_STATUS_IF_ERROR(DriverStubImpl::drain(drainMode));
+ mDspWorker.pause();
+ return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::pause() {
+ RETURN_STATUS_IF_ERROR(DriverStubImpl::pause());
+ mDspWorker.pause();
+ return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::start() {
+ RETURN_STATUS_IF_ERROR(DriverStubImpl::start());
+ RETURN_STATUS_IF_ERROR(startWorkerIfNeeded());
+ mDspWorker.resume();
+ return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::transfer(void*, size_t, size_t*, int32_t*) {
+ // Do not call into DriverStubImpl::transfer
+ if (!mIsInitialized) {
+ LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver";
+ }
+ if (mIsStandby) {
+ LOG(FATAL) << __func__ << ": must not happen while in standby";
+ }
+ RETURN_STATUS_IF_ERROR(startWorkerIfNeeded());
+ mDspWorker.resume();
+ return ::android::OK;
+}
+
+void DriverMmapStubImpl::shutdown() {
+ LOG(DEBUG) << __func__ << ": stopping the DSP simulator worker";
+ mDspWorker.stop();
+ std::lock_guard l(mState.lock);
+ releaseSharedMemory();
+ DriverStubImpl::shutdown();
+}
+
+::android::status_t DriverMmapStubImpl::initSharedMemory(int ashmemFd) {
+ {
+ std::lock_guard l(mState.lock);
+ if (ashmemFd == -1) {
+ mState.sharedMemory = nullptr;
+ return ::android::BAD_VALUE;
+ }
+ RETURN_STATUS_IF_ERROR(releaseSharedMemory());
+ }
+ uint8_t* sharedMemory = static_cast<uint8_t*>(::mmap(
+ nullptr, mState.bufferSizeBytes, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0));
+ if (sharedMemory == reinterpret_cast<uint8_t*>(MAP_FAILED) || sharedMemory == nullptr) {
+ PLOG(ERROR) << "mmap failed for size " << mState.bufferSizeBytes << ", fd " << ashmemFd;
+ return ::android::NO_INIT;
+ }
+ std::lock_guard l(mState.lock);
+ mState.sharedMemory = sharedMemory;
+ return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::releaseSharedMemory() {
+ if (mState.sharedMemory != nullptr) {
+ LOG(DEBUG) << __func__ << ": unmapping shared memory";
+ if (munmap(mState.sharedMemory, mState.bufferSizeBytes) != 0) {
+ PLOG(ERROR) << "munmap failed for size " << mState.bufferSizeBytes;
+ return ::android::INVALID_OPERATION;
+ }
+ mState.sharedMemory = nullptr;
+ }
+ return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::startWorkerIfNeeded() {
+ if (!mDspWorkerStarted) {
+ // This is an "audio service thread," must have elevated priority.
+ if (!mDspWorker.start("dsp_sim", ANDROID_PRIORITY_URGENT_AUDIO)) {
+ return ::android::NO_INIT;
+ }
+ mDspWorkerStarted = true;
+ }
+ return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::refinePosition(StreamDescriptor::Position* position) {
+ std::lock_guard l(mState.lock);
+ *position = mState.mmapPos;
+ return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::getMmapPositionAndLatency(
+ StreamDescriptor::Position* position, int32_t* latencyMs) {
+ {
+ std::lock_guard l(mState.lock);
+ *position = mState.mmapPos;
+ }
+ const size_t latencyFrames = mBufferSizeFrames / 2;
+ if (position->frames != StreamDescriptor::Position::UNKNOWN) {
+ position->frames += latencyFrames;
+ }
+ *latencyMs = latencyFrames * MILLIS_PER_SECOND / mSampleRate;
+ return ::android::OK;
+}
+
+const std::string StreamMmapStub::kCreateMmapBufferName = "aosp.createMmapBuffer";
+
+StreamMmapStub::StreamMmapStub(StreamContext* context, const Metadata& metadata)
+ : StreamCommonImpl(context, metadata), DriverMmapStubImpl(getContext()) {}
+
+StreamMmapStub::~StreamMmapStub() {
+ cleanupWorker();
+}
+
+ndk::ScopedAStatus StreamMmapStub::getVendorParameters(const std::vector<std::string>& in_ids,
+ std::vector<VendorParameter>* _aidl_return) {
+ std::vector<std::string> unprocessedIds;
+ for (const auto& id : in_ids) {
+ if (id == kCreateMmapBufferName) {
+ LOG(DEBUG) << __func__ << ": " << id;
+ MmapBufferDescriptor mmapDesc;
+ RETURN_STATUS_IF_ERROR(createMmapBuffer(&mmapDesc));
+ VendorParameter createMmapBuffer{.id = id};
+ createMmapBuffer.ext.setParcelable(mmapDesc);
+ LOG(DEBUG) << __func__ << ": returning " << mmapDesc.toString();
+ _aidl_return->push_back(std::move(createMmapBuffer));
+ } else {
+ unprocessedIds.push_back(id);
+ }
+ }
+ if (!unprocessedIds.empty()) {
+ return StreamCommonImpl::getVendorParameters(unprocessedIds, _aidl_return);
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamMmapStub::setVendorParameters(
+ const std::vector<VendorParameter>& in_parameters, bool in_async) {
+ std::vector<VendorParameter> unprocessedParameters;
+ for (const auto& param : in_parameters) {
+ if (param.id == kCreateMmapBufferName) {
+ LOG(DEBUG) << __func__ << ": " << param.id;
+ // The value is irrelevant. The fact that this parameter can be "set" is an
+ // indication that the method can be used by the client via 'getVendorParameters'.
+ } else {
+ unprocessedParameters.push_back(param);
+ }
+ }
+ if (!unprocessedParameters.empty()) {
+ return StreamCommonImpl::setVendorParameters(unprocessedParameters, in_async);
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamMmapStub::createMmapBuffer(MmapBufferDescriptor* desc) {
+ const size_t bufferSizeFrames = mContext.getBufferSizeInFrames();
+ const size_t bufferSizeBytes = static_cast<size_t>(bufferSizeFrames) * mContext.getFrameSize();
+ const std::string regionName =
+ std::string("mmap-sim-") + std::to_string(mContext.getMixPortHandle());
+ 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);
+ }
+ mSharedMemoryFd = ndk::ScopedFileDescriptor(fd);
+ if (initSharedMemory(mSharedMemoryFd.get()) != ::android::OK) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ desc->sharedMemory.fd = mSharedMemoryFd.dup();
+ desc->sharedMemory.size = bufferSizeBytes;
+ desc->burstSizeFrames = bufferSizeFrames / 2;
+ desc->flags = 0;
+ LOG(DEBUG) << __func__ << ": " << desc->toString();
+ return ndk::ScopedAStatus::ok();
+}
+
+StreamInMmapStub::StreamInMmapStub(StreamContext&& context, const SinkMetadata& sinkMetadata,
+ const std::vector<MicrophoneInfo>& microphones)
+ : StreamIn(std::move(context), microphones), StreamMmapStub(&mContextInstance, sinkMetadata) {}
+
+StreamOutMmapStub::StreamOutMmapStub(StreamContext&& context, const SourceMetadata& sourceMetadata,
+ const std::optional<AudioOffloadInfo>& offloadInfo)
+ : StreamOut(std::move(context), offloadInfo),
+ StreamMmapStub(&mContextInstance, sourceMetadata) {}
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/stub/StreamOffloadStub.cpp b/audio/aidl/default/stub/StreamOffloadStub.cpp
index 155f76d..5f5f741 100644
--- a/audio/aidl/default/stub/StreamOffloadStub.cpp
+++ b/audio/aidl/default/stub/StreamOffloadStub.cpp
@@ -30,6 +30,8 @@
namespace aidl::android::hardware::audio::core {
+namespace offload {
+
std::string DspSimulatorLogic::init() {
return "";
}
@@ -90,6 +92,10 @@
return Status::CONTINUE;
}
+} // namespace offload
+
+using offload::DspSimulatorState;
+
DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
: DriverStubImpl(context, 0 /*asyncSleepTimeUs*/),
mBufferNotifyFrames(static_cast<int64_t>(context.getBufferSizeInFrames()) / 2),
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 806c93f..2c692f5 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -27,6 +27,7 @@
#include <set>
#include <string>
#include <string_view>
+#include <thread>
#include <variant>
#include <vector>
@@ -76,6 +77,7 @@
using aidl::android::hardware::audio::core::IStreamIn;
using aidl::android::hardware::audio::core::IStreamOut;
using aidl::android::hardware::audio::core::ITelephony;
+using aidl::android::hardware::audio::core::MmapBufferDescriptor;
using aidl::android::hardware::audio::core::ModuleDebug;
using aidl::android::hardware::audio::core::StreamDescriptor;
using aidl::android::hardware::audio::core::VendorParameter;
@@ -720,21 +722,8 @@
mFlags(flags),
mDataMQ(maybeCreateDataMQ(descriptor)),
mIsMmapped(isMmapped(descriptor)),
- mSharedMemoryFd(maybeGetMmapFd(descriptor)) {
- if (isMmapped()) {
- mSharedMemory = (int8_t*)mmap(nullptr, getBufferSizeBytes(), PROT_READ | PROT_WRITE,
- MAP_SHARED, mSharedMemoryFd, 0);
- if (mSharedMemory == MAP_FAILED) {
- PLOG(ERROR) << __func__ << ": mmap() failed.";
- mSharedMemory = nullptr;
- }
- }
- }
- ~StreamContext() {
- if (mSharedMemory != nullptr) {
- munmap(mSharedMemory, getBufferSizeBytes());
- }
- }
+ mMmapBurstSizeFrames(getMmapBurstSizeFrames(descriptor)),
+ mSharedMemoryFd(maybeGetMmapFd(descriptor)) {}
void checkIsValid() const {
EXPECT_NE(0UL, mFrameSizeBytes);
ASSERT_NE(nullptr, mCommandMQ);
@@ -742,15 +731,14 @@
ASSERT_NE(nullptr, mReplyMQ);
EXPECT_TRUE(mReplyMQ->isValid());
if (isMmapped()) {
- ASSERT_NE(nullptr, mSharedMemory);
+ EXPECT_NE(0, mMmapBurstSizeFrames) << "MMAP burst size must not be zero";
} else {
- if (mDataMQ != nullptr) {
- EXPECT_TRUE(mDataMQ->isValid());
- EXPECT_GE(mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize(),
- mFrameSizeBytes * mBufferSizeFrames)
- << "Data MQ actual buffer size is "
- "less than the buffer size as specified by the descriptor";
- }
+ ASSERT_NE(nullptr, mDataMQ);
+ EXPECT_TRUE(mDataMQ->isValid());
+ EXPECT_GE(mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize(),
+ mFrameSizeBytes * mBufferSizeFrames)
+ << "Data MQ actual buffer size is "
+ "less than the buffer size as specified by the descriptor";
}
}
size_t getBufferSizeBytes() const { return mFrameSizeBytes * mBufferSizeFrames; }
@@ -763,7 +751,8 @@
ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
int getSampleRate() const { return mConfig.sampleRate; }
bool isMmapped() const { return mIsMmapped; }
- int8_t* getMmapMemory() const { return mSharedMemory; }
+ int32_t getMmapBurstSizeFrames() const { return mMmapBurstSizeFrames; }
+ int getMmapFd() const { return mSharedMemoryFd; }
private:
static std::unique_ptr<DataMQ> maybeCreateDataMQ(const StreamDescriptor& descriptor) {
@@ -773,6 +762,13 @@
}
return nullptr;
}
+ static int32_t getMmapBurstSizeFrames(const StreamDescriptor& descriptor) {
+ using Tag = StreamDescriptor::AudioBuffer::Tag;
+ if (descriptor.audio.getTag() == Tag::mmap) {
+ return descriptor.audio.get<Tag::mmap>().burstSizeFrames;
+ }
+ return -1;
+ }
static bool isMmapped(const StreamDescriptor& descriptor) {
using Tag = StreamDescriptor::AudioBuffer::Tag;
return descriptor.audio.getTag() == Tag::mmap;
@@ -793,8 +789,75 @@
const AudioIoFlags mFlags;
std::unique_ptr<DataMQ> mDataMQ;
const bool mIsMmapped;
- const int32_t mSharedMemoryFd;
- int8_t* mSharedMemory = nullptr;
+ const int32_t mMmapBurstSizeFrames;
+ const int32_t mSharedMemoryFd; // owned by StreamDescriptor
+};
+
+struct StreamWorkerMethods {
+ virtual ~StreamWorkerMethods() = default;
+ virtual bool createMmapBuffer(MmapBufferDescriptor* desc) = 0;
+ virtual bool supportsCreateMmapBuffer() = 0;
+};
+
+class MmapSharedMemory {
+ public:
+ explicit MmapSharedMemory(const StreamContext& context, StreamWorkerMethods* stream)
+ : mStream(stream),
+ mBufferSizeBytes(context.getBufferSizeBytes()),
+ mSharedMemoryFd(::dup(context.getMmapFd())) {}
+ ~MmapSharedMemory() { releaseSharedMemory(); }
+
+ int8_t* getMmapMemory() {
+ if (mSharedMemory != nullptr) return mSharedMemory;
+ if (mSharedMemoryFd.get() != -1) {
+ int8_t* sharedMemory = (int8_t*)mmap(nullptr, mBufferSizeBytes, PROT_READ | PROT_WRITE,
+ MAP_SHARED, mSharedMemoryFd.get(), 0);
+ if (sharedMemory != MAP_FAILED && sharedMemory != nullptr) {
+ mSharedMemory = sharedMemory;
+ } else {
+ PLOG(ERROR) << __func__ << ": mmap() failed, fd " << mSharedMemoryFd.get()
+ << ", size " << mBufferSizeBytes;
+ }
+ } else {
+ LOG(WARNING) << __func__ << ": shared memory FD has not been set yet";
+ }
+ return mSharedMemory;
+ }
+ bool updateMmapSharedMemoryIfNeeded(StreamDescriptor::State state) {
+ if (mPreviousState == StreamDescriptor::State::STANDBY &&
+ state != StreamDescriptor::State::STANDBY && state != StreamDescriptor::State::ERROR) {
+ LOG(INFO) << "Mmap stream exited standby, update Mmap buffer";
+ MmapBufferDescriptor desc;
+ if (!mStream->createMmapBuffer(&desc)) return false;
+ updateMmapSharedMemoryFd(desc);
+ }
+ mPreviousState = state;
+ return true;
+ }
+
+ private:
+ static ndk::ScopedFileDescriptor getMmapFd(const MmapBufferDescriptor& desc) {
+ return desc.sharedMemory.fd.get() != -1 ? desc.sharedMemory.fd.dup()
+ : ndk::ScopedFileDescriptor{};
+ }
+ void releaseSharedMemory() {
+ if (mSharedMemory != nullptr) {
+ munmap(mSharedMemory, mBufferSizeBytes);
+ }
+ mSharedMemory = nullptr;
+ }
+ void updateMmapSharedMemoryFd(const MmapBufferDescriptor& desc) {
+ mSharedMemoryFd = getMmapFd(desc);
+ releaseSharedMemory();
+ }
+
+ StreamWorkerMethods* const mStream;
+ const size_t mBufferSizeBytes;
+ ndk::ScopedFileDescriptor mSharedMemoryFd;
+ // Maps on the worker thread, may unmap in the destructor on the main thread.
+ std::atomic<int8_t*> mSharedMemory = nullptr;
+ // 'STANDBY' is always the starting state for a stream.
+ StreamDescriptor::State mPreviousState = StreamDescriptor::State::STANDBY;
};
struct StreamEventReceiver {
@@ -984,15 +1047,18 @@
class StreamCommonLogic : public StreamLogic {
protected:
StreamCommonLogic(const StreamContext& context, StreamLogicDriver* driver,
- StreamEventReceiver* eventReceiver)
+ StreamWorkerMethods* stream, StreamEventReceiver* eventReceiver)
: mCommandMQ(context.getCommandMQ()),
mReplyMQ(context.getReplyMQ()),
mDataMQ(context.getDataMQ()),
+ mMmap(context, stream),
mData(context.getBufferSizeBytes()),
mDriver(driver),
mEventReceiver(eventReceiver),
mIsMmapped(context.isMmapped()),
- mSharedMemory(context.getMmapMemory()),
+ mMmapBurstSleep(mIsMmapped ? static_cast<double>(context.getMmapBurstSizeFrames()) /
+ context.getSampleRate()
+ : 0.0),
mIsCompressOffload(context.getFlags().getTag() == AudioIoFlags::output &&
isBitPositionFlagSet(context.getFlags().get<AudioIoFlags::output>(),
AudioOutputFlags::COMPRESS_OFFLOAD)),
@@ -1008,7 +1074,9 @@
bool isMmapped() const { return mIsMmapped; }
std::string init() override {
- LOG(DEBUG) << __func__;
+ LOG(DEBUG) << __func__ << ": isMmapped? " << mIsMmapped << ", MmapBurstSleep "
+ << mMmapBurstSleep << ", isCompressOffload? " << mIsCompressOffload << ", "
+ << mConfig.toString();
return "";
}
const std::vector<int8_t>& getData() const { return mData; }
@@ -1054,32 +1122,40 @@
return false;
}
bool readDataFromMmap(size_t readCount) {
- if (mSharedMemory != nullptr) {
- std::memcpy(mData.data(), mSharedMemory, readCount);
+ if (auto memory = mMmap.getMmapMemory(); memory != nullptr) {
+ std::memcpy(mData.data(), memory, readCount);
+ // Since MMap `burst` does not block, need to sleep here to get an updated position.
+ std::this_thread::sleep_for(mMmapBurstSleep);
return true;
}
- LOG(ERROR) << __func__ << ": reading of " << readCount << " bytes from mmap failed";
+ LOG(ERROR) << __func__ << ": reading of " << readCount << " bytes from MMap failed";
return false;
}
bool writeDataToMmap() {
- if (mSharedMemory != nullptr) {
- std::memcpy(mSharedMemory, mData.data(), mData.size());
+ if (auto memory = mMmap.getMmapMemory(); memory != nullptr) {
+ std::memcpy(memory, mData.data(), mData.size());
+ // Since MMap `burst` does not block, need to sleep here to get an updated position.
+ std::this_thread::sleep_for(mMmapBurstSleep);
return true;
}
- LOG(ERROR) << __func__ << ": writing of " << mData.size() << " bytes to mmap failed";
+ LOG(ERROR) << __func__ << ": writing of " << mData.size() << " bytes to MMap failed";
return false;
}
+ bool updateMmapSharedMemoryIfNeeded(StreamDescriptor::State state) {
+ return isMmapped() ? mMmap.updateMmapSharedMemoryIfNeeded(state) : true;
+ }
private:
StreamContext::CommandMQ* mCommandMQ;
StreamContext::ReplyMQ* mReplyMQ;
StreamContext::DataMQ* mDataMQ;
+ MmapSharedMemory mMmap;
std::vector<int8_t> mData;
StreamLogicDriver* const mDriver;
StreamEventReceiver* const mEventReceiver;
int mLastEventSeq = StreamEventReceiver::kEventSeqInit;
const bool mIsMmapped;
- int8_t* mSharedMemory = nullptr;
+ const std::chrono::duration<double> mMmapBurstSleep;
const bool mIsCompressOffload;
const AudioConfigBase mConfig;
};
@@ -1087,8 +1163,9 @@
class StreamReaderLogic : public StreamCommonLogic {
public:
StreamReaderLogic(const StreamContext& context, StreamLogicDriver* driver,
- StreamEventReceiver* eventReceiver)
- : StreamCommonLogic(context, driver, eventReceiver) {}
+ StreamWorkerMethods* stream, StreamEventReceiver* eventReceiver)
+ : StreamCommonLogic(context, driver, stream, eventReceiver),
+ mMmapBurstSizeFrames(context.getMmapBurstSizeFrames()) {}
// Should only be called after the worker has joined.
const std::vector<int8_t>& getData() const { return StreamCommonLogic::getData(); }
@@ -1154,30 +1231,35 @@
}
const bool acceptedReply = getDriver()->processValidReply(reply);
if (const size_t readCount =
- !isMmapped() ? getDataMQ()->availableToRead() : reply.fmqByteCount;
+ !isMmapped() ? getDataMQ()->availableToRead()
+ : (command.getTag() == StreamDescriptor::Command::Tag::burst
+ ? mMmapBurstSizeFrames
+ : 0);
readCount > 0) {
fillData(-1);
if (isMmapped() ? readDataFromMmap(readCount) : readDataFromMQ(readCount)) {
goto checkAcceptedReply;
}
- LOG(ERROR) << __func__ << ": reading of " << readCount << " data bytes from MQ failed";
+ LOG(ERROR) << __func__ << ": reading of " << readCount << " data bytes failed";
return Status::ABORT;
} // readCount == 0
checkAcceptedReply:
if (acceptedReply) {
- return Status::CONTINUE;
+ return updateMmapSharedMemoryIfNeeded(reply.state) ? Status::CONTINUE : Status::ABORT;
}
LOG(ERROR) << __func__ << ": unacceptable reply: " << reply.toString();
return Status::ABORT;
}
+
+ const int32_t mMmapBurstSizeFrames;
};
using StreamReader = StreamWorker<StreamReaderLogic>;
class StreamWriterLogic : public StreamCommonLogic {
public:
StreamWriterLogic(const StreamContext& context, StreamLogicDriver* driver,
- StreamEventReceiver* eventReceiver)
- : StreamCommonLogic(context, driver, eventReceiver) {}
+ StreamWorkerMethods* stream, StreamEventReceiver* eventReceiver)
+ : StreamCommonLogic(context, driver, stream, eventReceiver) {}
// Should only be called after the worker has joined.
const std::vector<int8_t>& getData() const { return StreamCommonLogic::getData(); }
@@ -1293,7 +1375,7 @@
return Status::ABORT;
}
if (getDriver()->processValidReply(reply)) {
- return Status::CONTINUE;
+ return updateMmapSharedMemoryIfNeeded(reply.state) ? Status::CONTINUE : Status::ABORT;
}
LOG(ERROR) << __func__ << ": unacceptable reply: " << reply.toString();
return Status::ABORT;
@@ -1381,7 +1463,7 @@
};
template <typename Stream>
-class WithStream {
+class WithStream : public StreamWorkerMethods {
public:
static ndk::ScopedAStatus callClose(std::shared_ptr<Stream> stream) {
std::shared_ptr<IStreamCommon> common;
@@ -1421,6 +1503,7 @@
const AudioConfigBase cfg{config.sampleRate->value, *config.channelMask, *config.format};
mContext.emplace(mDescriptor, cfg, config.flags.value());
ASSERT_NO_FATAL_FAILURE(mContext.value().checkIsValid());
+ ASSERT_IS_OK(mStream->getInterfaceVersion(&mInterfaceVersion));
}
void SetUp(IModule* module, long bufferSizeFrames) {
ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
@@ -1432,13 +1515,66 @@
std::shared_ptr<Stream> getSharedPointer() const { return mStream; }
const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); }
int32_t getPortId() const { return mPortConfig.getId(); }
+ // StreamWorkerMethods
+ bool createMmapBuffer(MmapBufferDescriptor* desc) override {
+ std::shared_ptr<IStreamCommon> common;
+ ndk::ScopedAStatus status = mStream->getStreamCommon(&common);
+ if (!status.isOk()) {
+ LOG(ERROR) << __func__ << ": getStreamCommon failed: " << status.getMessage();
+ return false;
+ }
+ if (mInterfaceVersion <= kAidlVersion3) {
+ std::vector<VendorParameter> parameters;
+ ScopedAStatus result = common->getVendorParameters({kCreateMmapBuffer}, ¶meters);
+ if (result.isOk() && parameters.size() == 1) {
+ std::optional<MmapBufferDescriptor> result;
+ binder_status_t status = parameters[0].ext.getParcelable(&result);
+ if (status == ::android::OK) {
+ *desc = std::move(*result);
+ return true;
+ } else {
+ LOG(ERROR) << __func__ << ": failed to extract parcelable: " << status;
+ }
+ } else {
+ LOG(ERROR) << __func__
+ << ": failed to call 'createMmapBuffer' via 'getVendorParameter': "
+ << result.getMessage();
+ }
+ } else {
+ // TODO: Use common->createMmapBuffer after interface update.
+ }
+ return false;
+ }
+ bool supportsCreateMmapBuffer() override {
+ if (!mHasCreateMmapBuffer.has_value()) {
+ if (mInterfaceVersion > kAidlVersion3) {
+ mHasCreateMmapBuffer = true;
+ } else {
+ std::shared_ptr<IStreamCommon> common;
+ ndk::ScopedAStatus status = mStream->getStreamCommon(&common);
+ if (status.isOk()) {
+ VendorParameter createMmapBuffer{.id = kCreateMmapBuffer};
+ mHasCreateMmapBuffer =
+ common->setVendorParameters({createMmapBuffer}, false).isOk();
+ } else {
+ LOG(ERROR) << __func__ << ": getStreamCommon failed: " << status.getMessage();
+ return false;
+ }
+ }
+ }
+ return mHasCreateMmapBuffer.value();
+ }
private:
+ static constexpr const char* kCreateMmapBuffer = "aosp.createMmapBuffer";
+
WithAudioPortConfig mPortConfig;
std::shared_ptr<Stream> mStream;
StreamDescriptor mDescriptor;
std::optional<StreamContext> mContext;
std::shared_ptr<DefaultStreamCallback> mStreamCallback;
+ int32_t mInterfaceVersion = -1;
+ std::optional<bool> mHasCreateMmapBuffer;
};
SinkMetadata GenerateSinkMetadata(const AudioPortConfig& portConfig) {
@@ -3103,6 +3239,7 @@
const StreamContext* getStreamContext() const { return mStream->getContext(); }
StreamEventReceiver* getStreamEventReceiver() { return mStream->getEventReceiver(); }
std::shared_ptr<Stream> getStreamSharedPointer() const { return mStream->getSharedPointer(); }
+ StreamWorkerMethods* getStreamWorkerMethods() const { return mStream.get(); }
const std::string& skipTestReason() const { return mSkipTestReason; }
private:
@@ -3315,12 +3452,11 @@
static bool skipStreamIoTestForMixPortConfig(const AudioPortConfig& portConfig) {
return (portConfig.flags.value().getTag() == AudioIoFlags::input &&
isAnyBitPositionFlagSet(portConfig.flags.value().template get<AudioIoFlags::input>(),
- {AudioInputFlags::MMAP_NOIRQ, AudioInputFlags::VOIP_TX,
- AudioInputFlags::HW_HOTWORD, AudioInputFlags::HOTWORD_TAP})) ||
+ {AudioInputFlags::VOIP_TX, AudioInputFlags::HW_HOTWORD,
+ AudioInputFlags::HOTWORD_TAP})) ||
(portConfig.flags.value().getTag() == AudioIoFlags::output &&
(isAnyBitPositionFlagSet(portConfig.flags.value().template get<AudioIoFlags::output>(),
- {AudioOutputFlags::MMAP_NOIRQ, AudioOutputFlags::VOIP_RX,
- AudioOutputFlags::INCALL_MUSIC}) ||
+ {AudioOutputFlags::VOIP_RX, AudioOutputFlags::INCALL_MUSIC}) ||
(isBitPositionFlagSet(portConfig.flags.value().template get<AudioIoFlags::output>(),
AudioOutputFlags::COMPRESS_OFFLOAD) &&
!getMediaFileInfoForConfig(portConfig))));
@@ -3331,6 +3467,12 @@
return device.type.type == AudioDeviceType::IN_ECHO_REFERENCE;
}
+// MMap implementation on the HAL version <= 3 was not test compliant,
+// unless the stream provides 'createMmapBuffer'
+static bool skipStreamIoTestForStream(const StreamContext* context, StreamWorkerMethods* stream) {
+ return context->isMmapped() && !stream->supportsCreateMmapBuffer();
+}
+
template <typename Stream>
class StreamFixtureWithWorker {
public:
@@ -3376,7 +3518,8 @@
makeBurstCommands(mIsSync, burstCount, standbyInputWhenDone),
context->getFrameSizeBytes(), context->isMmapped());
mWorker = std::make_unique<typename IOTraits<Stream>::Worker>(
- *context, mWorkerDriver.get(), mStream->getStreamEventReceiver());
+ *context, mWorkerDriver.get(), mStream->getStreamWorkerMethods(),
+ mStream->getStreamEventReceiver());
LOG(DEBUG) << __func__ << ": starting " << IOTraits<Stream>::directionStr << " worker...";
ASSERT_TRUE(mWorker->start());
}
@@ -3419,6 +3562,10 @@
if (skipStreamIoTestForMixPortConfig(mStream->getPortConfig())) {
mSkipTestReason = "Mix port config is not supported for stream I/O tests";
}
+ if (skipStreamIoTestForStream(mStream->getStreamContext(),
+ mStream->getStreamWorkerMethods())) {
+ mSkipTestReason = "Stream can not be used in I/O tests";
+ }
}
const bool mIsSync;
@@ -3774,6 +3921,7 @@
ASSERT_EQ("", stream.skipTestReason());
StreamLogicDriverInvalidCommand driver(seq.second);
typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+ stream.getStreamWorkerMethods(),
stream.getStreamEventReceiver());
LOG(DEBUG) << __func__ << ": starting worker...";
ASSERT_TRUE(worker.start());
@@ -4373,11 +4521,15 @@
ASSERT_NO_FATAL_FAILURE(
stream.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
if (skipStreamIoTestForDevice(stream.getDevice())) return;
+ if (skipStreamIoTestForStream(stream.getStreamContext(), stream.getStreamWorkerMethods())) {
+ return;
+ }
ASSERT_EQ("", stream.skipTestReason());
StreamLogicDefaultDriver driver(commandsAndStates,
stream.getStreamContext()->getFrameSizeBytes(),
stream.getStreamContext()->isMmapped());
typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+ stream.getStreamWorkerMethods(),
stream.getStreamEventReceiver());
LOG(DEBUG) << __func__ << ": starting worker...";
@@ -4407,10 +4559,14 @@
if (skipStreamIoTestForDevice(stream.getDevice())) return;
ASSERT_EQ("", stream.skipTestReason());
ASSERT_NO_FATAL_FAILURE(stream.TeardownPatchSetUpStream(module.get()));
+ if (skipStreamIoTestForStream(stream.getStreamContext(), stream.getStreamWorkerMethods())) {
+ return;
+ }
StreamLogicDefaultDriver driver(commandsAndStates,
stream.getStreamContext()->getFrameSizeBytes(),
stream.getStreamContext()->isMmapped());
typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+ stream.getStreamWorkerMethods(),
stream.getStreamEventReceiver());
ASSERT_NO_FATAL_FAILURE(stream.ReconnectPatch(module.get()));
@@ -4788,7 +4944,7 @@
// Allow optional routing via the TRANSFERRING state on bursts.
StateDag::Node makeAsyncBurstCommands(StateDag* d, size_t burstCount, StateDag::Node last) {
using State = StreamDescriptor::State;
- std::reference_wrapper<std::remove_reference_t<StateDag::Node>> prev = last;
+ std::reference_wrapper<StateDag::value_type> prev = last;
for (size_t i = 0; i < burstCount; ++i) {
StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, prev);
active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, prev));