/* * 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 #include #define LOG_TAG "AHAL_MmapStream" #include #include #include #include #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(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(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(::mmap( nullptr, mState.bufferSizeBytes, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); if (sharedMemory == reinterpret_cast(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& in_ids, std::vector* _aidl_return) { std::vector 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& in_parameters, bool in_async) { std::vector 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(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& microphones) : StreamIn(std::move(context), microphones), StreamMmapStub(&mContextInstance, sinkMetadata) {} StreamOutMmapStub::StreamOutMmapStub(StreamContext&& context, const SourceMetadata& sourceMetadata, const std::optional& offloadInfo) : StreamOut(std::move(context), offloadInfo), StreamMmapStub(&mContextInstance, sourceMetadata) {} } // namespace aidl::android::hardware::audio::core