Audio : Add remote submix stream implementation

Bug: 286914845
Test: atest VtsHalAudioCoreTargetTest
Change-Id: Ia477458193ade9068eaf56e953ab670fee53cc7d
diff --git a/audio/aidl/default/r_submix/SubmixRoute.cpp b/audio/aidl/default/r_submix/SubmixRoute.cpp
new file mode 100644
index 0000000..8f5b8cb
--- /dev/null
+++ b/audio/aidl/default/r_submix/SubmixRoute.cpp
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AHAL_SubmixRoute"
+#include <android-base/logging.h>
+#include <media/AidlConversionCppNdk.h>
+
+#include <Utils.h>
+
+#include "SubmixRoute.h"
+
+using aidl::android::hardware::audio::common::getChannelCount;
+
+namespace aidl::android::hardware::audio::core::r_submix {
+
+// Verify a submix input or output stream can be opened.
+bool SubmixRoute::isStreamConfigValid(bool isInput, const AudioConfig streamConfig) {
+    // If the stream is already open, don't open it again.
+    // ENABLE_LEGACY_INPUT_OPEN is default behaviour
+    if (!isInput && isStreamOutOpen()) {
+        LOG(ERROR) << __func__ << ": output stream already open.";
+        return false;
+    }
+    // If either stream is open, verify the existing pipe config matches the stream config.
+    if (hasAtleastOneStreamOpen() && !isStreamConfigCompatible(streamConfig)) {
+        return false;
+    }
+    return true;
+}
+
+// Compare this stream config with existing pipe config, returning false if they do *not*
+// match, true otherwise.
+bool SubmixRoute::isStreamConfigCompatible(const AudioConfig streamConfig) {
+    if (streamConfig.channelLayout != mPipeConfig.channelLayout) {
+        LOG(ERROR) << __func__ << ": channel count mismatch, stream channels = "
+                   << streamConfig.channelLayout.toString()
+                   << " pipe config channels = " << mPipeConfig.channelLayout.toString();
+        return false;
+    }
+    if (streamConfig.sampleRate != mPipeConfig.sampleRate) {
+        LOG(ERROR) << __func__
+                   << ": sample rate mismatch, stream sample rate = " << streamConfig.sampleRate
+                   << " pipe config sample rate = " << mPipeConfig.sampleRate;
+        return false;
+    }
+    if (streamConfig.format != mPipeConfig.format) {
+        LOG(ERROR) << __func__
+                   << ": format mismatch, stream format = " << streamConfig.format.toString()
+                   << " pipe config format = " << mPipeConfig.format.toString();
+        return false;
+    }
+    return true;
+}
+
+bool SubmixRoute::hasAtleastOneStreamOpen() {
+    std::lock_guard guard(mLock);
+    return (mStreamInOpen || mStreamOutOpen);
+}
+
+// We DO NOT block if:
+// - no peer input stream is present
+// - the peer input is in standby AFTER having been active.
+// We DO block if:
+// - the input was never activated to avoid discarding first frames in the pipe in case capture
+// start was delayed
+bool SubmixRoute::shouldBlockWrite() {
+    std::lock_guard guard(mLock);
+    return (mStreamInOpen || (mStreamInStandby && (mReadCounterFrames != 0)));
+}
+
+int SubmixRoute::notifyReadError() {
+    std::lock_guard guard(mLock);
+    return ++mReadErrorCount;
+}
+
+long SubmixRoute::updateReadCounterFrames(size_t frameCount) {
+    std::lock_guard guard(mLock);
+    mReadCounterFrames += frameCount;
+    return mReadCounterFrames;
+}
+
+void SubmixRoute::openStream(bool isInput) {
+    std::lock_guard guard(mLock);
+    if (isInput) {
+        if (mStreamInOpen) {
+            mInputRefCount++;
+        } else {
+            mInputRefCount = 1;
+            mStreamInOpen = true;
+        }
+        mStreamInStandby = true;
+        mReadCounterFrames = 0;
+        mReadErrorCount = 0;
+    } else {
+        mStreamOutOpen = true;
+    }
+}
+
+void SubmixRoute::closeStream(bool isInput) {
+    std::lock_guard guard(mLock);
+    if (isInput) {
+        mInputRefCount--;
+        if (mInputRefCount == 0) {
+            mStreamInOpen = false;
+            if (mSink != nullptr) {
+                mSink->shutdown(true);
+            }
+        }
+    } else {
+        mStreamOutOpen = false;
+    }
+}
+
+// If SubmixRoute doesn't exist for a port, create a pipe for the submix audio device of size
+// buffer_size_frames and store config of the submix audio device.
+::android::status_t SubmixRoute::createPipe(const AudioConfig streamConfig) {
+    const int channelCount = getChannelCount(streamConfig.channelLayout);
+    const audio_format_t audioFormat = VALUE_OR_RETURN_STATUS(
+            aidl2legacy_AudioFormatDescription_audio_format_t(streamConfig.format));
+    const ::android::NBAIO_Format format =
+            ::android::Format_from_SR_C(streamConfig.sampleRate, channelCount, audioFormat);
+    const ::android::NBAIO_Format offers[1] = {format};
+    size_t numCounterOffers = 0;
+
+    const size_t pipeSizeInFrames =
+            r_submix::kDefaultPipeSizeInFrames *
+            ((float)streamConfig.sampleRate / r_submix::kDefaultSampleRateHz);
+    LOG(VERBOSE) << __func__ << ": creating pipe, rate : " << streamConfig.sampleRate
+                 << ", pipe size : " << pipeSizeInFrames;
+
+    // Create a MonoPipe with optional blocking set to true.
+    sp<MonoPipe> sink = sp<MonoPipe>::make(pipeSizeInFrames, format, true /*writeCanBlock*/);
+    if (sink == nullptr) {
+        LOG(FATAL) << __func__ << ": sink is null";
+        return ::android::UNEXPECTED_NULL;
+    }
+
+    // Negotiation between the source and sink cannot fail as the device open operation
+    // creates both ends of the pipe using the same audio format.
+    ssize_t index = sink->negotiate(offers, 1, nullptr, numCounterOffers);
+    if (index != 0) {
+        LOG(FATAL) << __func__ << ": Negotiation for the sink failed, index = " << index;
+        return ::android::BAD_INDEX;
+    }
+    sp<MonoPipeReader> source = sp<MonoPipeReader>::make(sink.get());
+    if (source == nullptr) {
+        LOG(FATAL) << __func__ << ": source is null";
+        return ::android::UNEXPECTED_NULL;
+    }
+    numCounterOffers = 0;
+    index = source->negotiate(offers, 1, nullptr, numCounterOffers);
+    if (index != 0) {
+        LOG(FATAL) << __func__ << ": Negotiation for the source failed, index = " << index;
+        return ::android::BAD_INDEX;
+    }
+    LOG(VERBOSE) << __func__ << ": created pipe";
+
+    mPipeConfig = streamConfig;
+    mPipeConfig.frameCount = sink->maxFrames();
+
+    LOG(VERBOSE) << __func__ << ": Pipe frame size : " << mPipeConfig.frameSize
+                 << ", pipe frames : " << mPipeConfig.frameCount;
+
+    // Save references to the source and sink.
+    {
+        std::lock_guard guard(mLock);
+        mSink = std::move(sink);
+        mSource = std::move(source);
+    }
+
+    return ::android::OK;
+}
+
+// Release references to the sink and source.
+void SubmixRoute::releasePipe() {
+    std::lock_guard guard(mLock);
+    mSink.clear();
+    mSource.clear();
+}
+
+::android::status_t SubmixRoute::resetPipe() {
+    releasePipe();
+    return createPipe(mPipeConfig);
+}
+
+void SubmixRoute::standby(bool isInput) {
+    std::lock_guard guard(mLock);
+
+    if (isInput) {
+        mStreamInStandby = true;
+    } else {
+        mStreamOutStandby = true;
+        mStreamOutStandbyTransition = !mStreamOutStandbyTransition;
+    }
+}
+
+void SubmixRoute::exitStandby(bool isInput) {
+    std::lock_guard guard(mLock);
+
+    if (isInput) {
+        if (mStreamInStandby || mStreamOutStandbyTransition) {
+            mStreamInStandby = false;
+            mStreamOutStandbyTransition = false;
+            // keep track of when we exit input standby (== first read == start "real recording")
+            // or when we start recording silence, and reset projected time
+            mRecordStartTime = std::chrono::steady_clock::now();
+            mReadCounterFrames = 0;
+        }
+    } else {
+        if (mStreamOutStandby) {
+            mStreamOutStandby = false;
+            mStreamOutStandbyTransition = true;
+        }
+    }
+}
+
+}  // namespace aidl::android::hardware::audio::core::r_submix