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