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 13068dce94..505c54c1df 100644 --- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp @@ -898,10 +898,49 @@ class CompressedOffloadOutputStreamTest : public PcmOnlyConfigOutputStreamTest { } }; +class OffloadCallbacks : public IStreamOutCallback { + public: + Return onDrainReady() override { + ALOGI("onDrainReady"); + { + std::lock_guard lg(mLock); + mOnDrainReady = true; + } + mCondVar.notify_one(); + return {}; + } + Return onWriteReady() override { return {}; } + Return onError() override { + ALOGW("onError"); + { + std::lock_guard lg(mLock); + mOnError = true; + } + mCondVar.notify_one(); + return {}; + } + bool waitForDrainReadyOrError() { + std::unique_lock l(mLock); + if (!mOnDrainReady && !mOnError) { + mCondVar.wait(l, [&]() { return mOnDrainReady || mOnError; }); + } + const bool success = !mOnError; + mOnDrainReady = mOnError = false; + return success; + } + + private: + std::mutex mLock; + bool mOnDrainReady = false; + bool mOnError = false; + std::condition_variable mCondVar; +}; + TEST_P(CompressedOffloadOutputStreamTest, Mp3FormatGaplessOffload) { doc::test("Check that compressed offload mix ports for MP3 implement gapless offload"); const auto& flags = getOutputFlags(); const bool isNewDeviceLaunchingOnTPlus = property_get_int32("ro.vendor.api_level", 0) >= 33; + // See test instantiation, only offload MP3 mix ports are used. if (std::find_if(flags.begin(), flags.end(), [](const auto& flag) { return flag == toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_GAPLESS_OFFLOAD); }) == flags.end()) { @@ -912,8 +951,7 @@ TEST_P(CompressedOffloadOutputStreamTest, Mp3FormatGaplessOffload) { 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 offloadData(stream->getBufferSize()); + std::vector offloadData; ASSERT_NO_FATAL_FAILURE(loadData("/data/local/tmp/sine882hz3s.mp3", &offloadData)); ASSERT_FALSE(offloadData.empty()); ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded()); @@ -923,38 +961,70 @@ TEST_P(CompressedOffloadOutputStreamTest, Mp3FormatGaplessOffload) { 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. + auto start = std::chrono::steady_clock::now(); + auto callbacks = sp::make(); + std::mutex presentationEndLock; + std::vector presentationEndTimes; + // 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 'onDataStart' + // each time it starts writing the buffer from the beginning, and '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)}, + stream.get(), stream->getBufferSize(), std::move(offloadData), + [&]() /* onDataStart */ { start = std::chrono::steady_clock::now(); }, + [&]() /* onDataWrap */ { + Return ret(Result::OK); + // Decrease the volume since the test plays a loud sine wave. + ret = stream->setVolume(0.1, 0.1); + if (!ret.isOk() || ret != Result::OK) { + ALOGE("%s: setVolume failed: %s", __func__, toString(ret).c_str()); + return false; + } + ret = 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); + if (!ret.isOk() || ret != Result::OK) { + ALOGE("%s: setParameters failed: %s", __func__, toString(ret).c_str()); + return false; + } + ret = stream->drain(AudioDrain::EARLY_NOTIFY); + if (!ret.isOk() || ret != Result::OK) { + ALOGE("%s: drain failed: %s", __func__, toString(ret).c_str()); + return false; + } + // FIXME: On some SoCs intermittent errors are possible, ignore them. + if (callbacks->waitForDrainReadyOrError()) { + const float duration = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start) + .count(); + std::lock_guard lg(presentationEndLock); + presentationEndTimes.push_back(duration); + } + return true; }); + ASSERT_OK(stream->setCallback(callbacks)); 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 presentationEndTimes; - uint64_t previousPosition = std::numeric_limits::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::steady_clock::now() - start) - .count()); + for (bool done = false; !done;) { + usleep(presentationeEndPrecisionMs * 1000); + { + std::lock_guard lg(presentationEndLock); + done = presentationEndTimes.size() >= playbackNumber; + } + ASSERT_FALSE(writer.hasError()) << "Recent write or drain operation has failed"; } const float avgDuration = std::accumulate(presentationEndTimes.begin(), presentationEndTimes.end(), 0.0) / presentationEndTimes.size(); - EXPECT_NEAR(durationMs, avgDuration, presentationeEndPrecisionMs * 0.1); + std::stringstream observedEndTimes; + std::copy(presentationEndTimes.begin(), presentationEndTimes.end(), + std::ostream_iterator(observedEndTimes, ", ")); + EXPECT_NEAR(durationMs, avgDuration, presentationeEndPrecisionMs * 0.1) + << "Observed durations: " << observedEndTimes.str(); writer.stop(); + EXPECT_OK(stream->clearCallback()); releasePatchIfNeeded(); } diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h index 6c5584d427..e46e5b4f52 100644 --- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h +++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h @@ -938,12 +938,13 @@ class StreamWriter : public StreamWorker { StreamWriter(IStreamOut* stream, size_t bufferSize) : mStream(stream), mBufferSize(bufferSize), mData(mBufferSize) {} StreamWriter(IStreamOut* stream, size_t bufferSize, std::vector&& data, - std::function onDataWrap) + std::function onDataStart, std::function onDataWrap) : mStream(stream), mBufferSize(bufferSize), mData(std::move(data)), + mOnDataStart(onDataStart), mOnDataWrap(onDataWrap) { - ALOGW("StreamWriter data size: %d", (int)mData.size()); + ALOGI("StreamWriter data size: %d", (int)mData.size()); } ~StreamWriter() { stop(); @@ -1010,6 +1011,7 @@ class StreamWriter : public StreamWorker { ALOGE("command message queue write failed"); return false; } + if (mDataPosition == 0) mOnDataStart(); 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"); @@ -1040,7 +1042,9 @@ class StreamWriter : public StreamWorker { ALOGE("bad wait status: %d", ret); success = false; } - if (success && mDataPosition == 0) mOnDataWrap(); + if (success && mDataPosition == 0) { + success = mOnDataWrap(); + } return success; } @@ -1048,7 +1052,8 @@ class StreamWriter : public StreamWorker { IStreamOut* const mStream; const size_t mBufferSize; std::vector mData; - std::function mOnDataWrap = []() {}; + std::function mOnDataStart = []() {}; + std::function mOnDataWrap = []() { return true; }; size_t mDataPosition = 0; std::unique_ptr mCommandMQ; std::unique_ptr mDataMQ;