audio VTS: add CompressedOffloadOutputStream test am: 56f1666feb
am: 96ddc8f20f
Original change: https://googleplex-android-review.googlesource.com/c/platform/hardware/interfaces/+/19345352 Change-Id: I245c82c92b0f1a0f6275d3b81c3679e77701856b Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
commit
1808705ea7
7 changed files with 138 additions and 14 deletions
|
@ -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 @@ class PcmOnlyConfigOutputStreamTest : public OutputStreamTest {
|
|||
// 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 @@ TEST_P(PcmOnlyConfigOutputStreamTest, PresentationPositionPreservedOnStandby) {
|
|||
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 @@ INSTANTIATE_TEST_CASE_P(MicrophoneInfoInputStream, MicrophoneInfoInputStreamTest
|
|||
::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);
|
||||
|
|
|
@ -78,10 +78,10 @@ static AudioOffloadInfo generateOffloadInfo(const AudioConfigBase& base) {
|
|||
.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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -190,6 +190,7 @@ cc_test {
|
|||
],
|
||||
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 @@ cc_test {
|
|||
],
|
||||
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.
|
||||
|
|
|
@ -937,6 +937,14 @@ class StreamWriter : public StreamWorker<StreamWriter> {
|
|||
|
||||
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 @@ class StreamWriter : public StreamWorker<StreamWriter> {
|
|||
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 @@ class StreamWriter : public StreamWorker<StreamWriter> {
|
|||
ALOGE("bad wait status: %d", ret);
|
||||
success = false;
|
||||
}
|
||||
if (success && mDataPosition == 0) mOnDataWrap();
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -1037,6 +1048,8 @@ class StreamWriter : public StreamWorker<StreamWriter> {
|
|||
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;
|
||||
|
|
|
@ -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" >
|
||||
|
|
|
@ -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" >
|
||||
|
|
BIN
audio/core/all-versions/vts/functional/data/sine882hz3s.mp3
Normal file
BIN
audio/core/all-versions/vts/functional/data/sine882hz3s.mp3
Normal file
Binary file not shown.
Loading…
Reference in a new issue