audio VTS: add CompressedOffloadOutputStream test am: 56f1666feb

Original change: https://googleplex-android-review.googlesource.com/c/platform/hardware/interfaces/+/19345352

Change-Id: Id24534dd0c3c977e1b775229734bcadb11a42ad5
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
index be90b21..37992ae 100644
--- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <fstream>
+#include <numeric>
+
 #include <android-base/chrono_utils.h>
 
 #include "Generators.h"
@@ -561,16 +564,22 @@
         // Sometimes HAL doesn't have enough information until the audio data actually gets
         // consumed by the hardware.
         bool timedOut = false;
-        res = Result::INVALID_STATE;
-        for (android::base::Timer elapsed;
-             res != Result::OK && !writer.hasError() &&
-             !(timedOut = (elapsed.duration() >= kPositionChangeTimeout));) {
-            usleep(kWriteDurationUs);
-            ASSERT_OK(stream->getPresentationPosition(returnIn(res, framesInitial, ts)));
-            ASSERT_RESULT(okOrInvalidState, res);
+        if (!firstPosition || *firstPosition == std::numeric_limits<uint64_t>::max()) {
+            res = Result::INVALID_STATE;
+            for (android::base::Timer elapsed;
+                 res != Result::OK && !writer.hasError() &&
+                 !(timedOut = (elapsed.duration() >= kPositionChangeTimeout));) {
+                usleep(kWriteDurationUs);
+                ASSERT_OK(stream->getPresentationPosition(returnIn(res, framesInitial, ts)));
+                ASSERT_RESULT(okOrInvalidState, res);
+            }
+            ASSERT_FALSE(writer.hasError());
+            ASSERT_FALSE(timedOut);
+        } else {
+            // Use `firstPosition` instead of querying it from the HAL. This is used when
+            // `waitForPresentationPositionAdvance` is called in a loop.
+            framesInitial = *firstPosition;
         }
-        ASSERT_FALSE(writer.hasError());
-        ASSERT_FALSE(timedOut);
 
         uint64_t frames = framesInitial;
         for (android::base::Timer elapsed;
@@ -632,7 +641,7 @@
     ASSERT_OK(stream->standby());
     writer.resume();
 
-    uint64_t frames;
+    uint64_t frames = std::numeric_limits<uint64_t>::max();
     ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer, &frames));
     EXPECT_GT(frames, framesInitial);
 
@@ -853,3 +862,100 @@
                         ::testing::ValuesIn(getBuiltinMicConfigParameters()),
                         &DeviceConfigParameterToString);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MicrophoneInfoInputStreamTest);
+
+static const std::vector<DeviceConfigParameter>& getOutputDeviceCompressedConfigParameters(
+        const AudioConfigBase& configToMatch) {
+    static const std::vector<DeviceConfigParameter> parameters = [&] {
+        auto allParams = getOutputDeviceConfigParameters();
+        std::vector<DeviceConfigParameter> compressedParams;
+        std::copy_if(allParams.begin(), allParams.end(), std::back_inserter(compressedParams),
+                     [&](auto cfg) {
+                         if (std::get<PARAM_CONFIG>(cfg).base != configToMatch) return false;
+                         const auto& flags = std::get<PARAM_FLAGS>(cfg);
+                         return std::find_if(flags.begin(), flags.end(), [](const auto& flag) {
+                                    return flag ==
+                                           toString(xsd::AudioInOutFlag::
+                                                            AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD);
+                                }) != flags.end();
+                     });
+        return compressedParams;
+    }();
+    return parameters;
+}
+
+class CompressedOffloadOutputStreamTest : public PcmOnlyConfigOutputStreamTest {
+  public:
+    void loadData(const std::string& fileName, std::vector<uint8_t>* data) {
+        std::ifstream is(fileName, std::ios::in | std::ios::binary);
+        ASSERT_TRUE(is.good()) << "Failed to open file " << fileName;
+        is.seekg(0, is.end);
+        data->reserve(data->size() + is.tellg());
+        is.seekg(0, is.beg);
+        data->insert(data->end(), std::istreambuf_iterator<char>(is),
+                     std::istreambuf_iterator<char>());
+        ASSERT_TRUE(!is.fail()) << "Failed to read from file " << fileName;
+    }
+};
+
+TEST_P(CompressedOffloadOutputStreamTest, Mp3FormatGaplessOffload) {
+    doc::test("Check that compressed offload mix ports for MP3 implement gapless offload");
+    const auto& flags = getOutputFlags();
+    if (std::find_if(flags.begin(), flags.end(), [](const auto& flag) {
+            return flag == toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_GAPLESS_OFFLOAD);
+        }) == flags.end()) {
+        GTEST_SKIP() << "Compressed offload mix port does not support gapless offload";
+    }
+    // FIXME: The presentation position is not updated if there is no zero padding in data.
+    std::vector<uint8_t> offloadData(stream->getBufferSize());
+    ASSERT_NO_FATAL_FAILURE(loadData("/data/local/tmp/sine882hz3s.mp3", &offloadData));
+    ASSERT_FALSE(offloadData.empty());
+    ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+    const int presentationeEndPrecisionMs = 1000;
+    const int sampleRate = 44100;
+    const int significantSampleNumber = (presentationeEndPrecisionMs * sampleRate) / 1000;
+    const int delay = 576 + 1000;
+    const int padding = 756 + 1000;
+    const int durationMs = 3000 - 44;
+    // StreamWriter plays 'offloadData' in a loop, possibly using multiple calls to 'write',
+    // this depends on the relative sizes of 'offloadData' and the HAL buffer. Writer calls
+    // 'onDataWrap' callback each time it wraps around the buffer.
+    StreamWriter writer(
+            stream.get(), stream->getBufferSize(), std::move(offloadData), [&]() /* onDataWrap */ {
+                Parameters::set(stream,
+                                {{AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES, std::to_string(delay)},
+                                 {AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES, std::to_string(padding)}});
+                stream->drain(AudioDrain::EARLY_NOTIFY);
+            });
+    ASSERT_TRUE(writer.start());
+    ASSERT_TRUE(writer.waitForAtLeastOneCycle());
+    // Decrease the volume since the test plays a loud sine wave.
+    ASSERT_OK(stream->setVolume(0.1, 0.1));
+    // How many times to loop the track so that the sum of gapless delay and padding from
+    // the first presentation end to the last is at least 'presentationeEndPrecisionMs'.
+    const int playbackNumber = (int)(significantSampleNumber / ((float)delay + padding) + 1);
+    std::vector<float> presentationEndTimes;
+    uint64_t previousPosition = std::numeric_limits<uint64_t>::max();
+    for (int i = 0; i < playbackNumber; ++i) {
+        const auto start = std::chrono::steady_clock::now();
+        ASSERT_NO_FATAL_FAILURE(
+                waitForPresentationPositionAdvance(writer, &previousPosition, &previousPosition));
+        presentationEndTimes.push_back(std::chrono::duration_cast<std::chrono::milliseconds>(
+                                               std::chrono::steady_clock::now() - start)
+                                               .count());
+    }
+    const float avgDuration =
+            std::accumulate(presentationEndTimes.begin(), presentationEndTimes.end(), 0.0) /
+            presentationEndTimes.size();
+    EXPECT_NEAR(durationMs, avgDuration, presentationeEndPrecisionMs * 0.1);
+    writer.stop();
+    releasePatchIfNeeded();
+}
+
+INSTANTIATE_TEST_CASE_P(
+        CompressedOffloadOutputStream, CompressedOffloadOutputStreamTest,
+        ::testing::ValuesIn(getOutputDeviceCompressedConfigParameters(AudioConfigBase{
+                .format = xsd::toString(xsd::AudioFormat::AUDIO_FORMAT_MP3),
+                .sampleRateHz = 44100,
+                .channelMask = xsd::toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO)})),
+        &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CompressedOffloadOutputStreamTest);
diff --git a/audio/core/all-versions/vts/functional/7.0/Generators.cpp b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
index cd84c41..8b955b6 100644
--- a/audio/core/all-versions/vts/functional/7.0/Generators.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
@@ -78,10 +78,10 @@
             .base = base,
             .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC),
             .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
-            .bitRatePerSecond = 320,
+            .bitRatePerSecond = 192,  // as in sine882hz3s.mp3
             .durationMicroseconds = -1,
             .bitWidth = 16,
-            .bufferSize = 256  // arbitrary value
+            .bufferSize = 72000  // 3 seconds at 192 kbps, as in sine882hz3s.mp3
     };
 }
 
diff --git a/audio/core/all-versions/vts/functional/Android.bp b/audio/core/all-versions/vts/functional/Android.bp
index c757032..5b0a7f2 100644
--- a/audio/core/all-versions/vts/functional/Android.bp
+++ b/audio/core/all-versions/vts/functional/Android.bp
@@ -190,6 +190,7 @@
     ],
     data: [
         ":audio_policy_configuration_V7_0",
+        "data/sine882hz3s.mp3",
     ],
     // Use test_config for vts suite.
     // TODO(b/146104851): Add auto-gen rules and remove it.
@@ -223,6 +224,7 @@
     ],
     data: [
         ":audio_policy_configuration_V7_1",
+        "data/sine882hz3s.mp3",
     ],
     // Use test_config for vts suite.
     // TODO(b/146104851): Add auto-gen rules and remove it.
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index afc25f0..6c5584d 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -937,6 +937,14 @@
 
     StreamWriter(IStreamOut* stream, size_t bufferSize)
         : mStream(stream), mBufferSize(bufferSize), mData(mBufferSize) {}
+    StreamWriter(IStreamOut* stream, size_t bufferSize, std::vector<uint8_t>&& data,
+                 std::function<void()> onDataWrap)
+        : mStream(stream),
+          mBufferSize(bufferSize),
+          mData(std::move(data)),
+          mOnDataWrap(onDataWrap) {
+        ALOGW("StreamWriter data size: %d", (int)mData.size());
+    }
     ~StreamWriter() {
         stop();
         if (mEfGroup) {
@@ -1002,9 +1010,11 @@
             ALOGE("command message queue write failed");
             return false;
         }
-        const size_t dataSize = std::min(mData.size(), mDataMQ->availableToWrite());
-        bool success = mDataMQ->write(mData.data(), dataSize);
+        const size_t dataSize = std::min(mData.size() - mDataPosition, mDataMQ->availableToWrite());
+        bool success = mDataMQ->write(mData.data() + mDataPosition, dataSize);
         ALOGE_IF(!success, "data message queue write failed");
+        mDataPosition += dataSize;
+        if (mDataPosition >= mData.size()) mDataPosition = 0;
         mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));
 
         uint32_t efState = 0;
@@ -1030,6 +1040,7 @@
             ALOGE("bad wait status: %d", ret);
             success = false;
         }
+        if (success && mDataPosition == 0) mOnDataWrap();
         return success;
     }
 
@@ -1037,6 +1048,8 @@
     IStreamOut* const mStream;
     const size_t mBufferSize;
     std::vector<uint8_t> mData;
+    std::function<void()> mOnDataWrap = []() {};
+    size_t mDataPosition = 0;
     std::unique_ptr<CommandMQ> mCommandMQ;
     std::unique_ptr<DataMQ> mDataMQ;
     std::unique_ptr<StatusMQ> mStatusMQ;
diff --git a/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml b/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml
index f0e2695..8da5744 100644
--- a/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml
+++ b/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml
@@ -29,6 +29,7 @@
         <option name="cleanup" value="true" />
         <option name="push" value="VtsHalAudioV7_0TargetTest->/data/local/tmp/VtsHalAudioV7_0TargetTest" />
         <option name="push" value="audio_policy_configuration_V7_0.xsd->/data/local/tmp/audio_policy_configuration_V7_0.xsd" />
+        <option name="push" value="sine882hz3s.mp3->/data/local/tmp/sine882hz3s.mp3" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
diff --git a/audio/core/all-versions/vts/functional/VtsHalAudioV7_1TargetTest.xml b/audio/core/all-versions/vts/functional/VtsHalAudioV7_1TargetTest.xml
index 7ce1477..227df18 100644
--- a/audio/core/all-versions/vts/functional/VtsHalAudioV7_1TargetTest.xml
+++ b/audio/core/all-versions/vts/functional/VtsHalAudioV7_1TargetTest.xml
@@ -29,6 +29,8 @@
         <option name="cleanup" value="true" />
         <option name="push" value="VtsHalAudioV7_1TargetTest->/data/local/tmp/VtsHalAudioV7_1TargetTest" />
         <option name="push" value="audio_policy_configuration_V7_1.xsd->/data/local/tmp/audio_policy_configuration_V7_1.xsd" />
+        <option name="push" value="sine882hz3s.mp3->/data/local/tmp/sine882hz3s.mp3" />
+
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
diff --git a/audio/core/all-versions/vts/functional/data/sine882hz3s.mp3 b/audio/core/all-versions/vts/functional/data/sine882hz3s.mp3
new file mode 100644
index 0000000..0604f9b
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/data/sine882hz3s.mp3
Binary files differ