From 713d2c55908aa6bd22481163509c4543a9c6d1bf Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Tue, 28 Jun 2022 01:46:21 +0000 Subject: [PATCH 1/2] audio VTS: Refactor test parameter generation for I/O streams Two changes that are necessary to test offloaded output: 1. Pass all mix port flags specified in the config to HAL when opening an output stream. Previously the generator was omitting the 'NON_BLOCKING' flag, however it is necessary for offloaded output to work. This also now passes the 'GAPLESS_OFFLOAD' flag to the stream opening call site. 2. Provide the DeviceAddress of the attached source/sink device to tests that use mix ports. Some tests were looking up the device address anyway. Also, HAL implementations seem to prefer to have the actual output device instead of 'DEFAULT' when opening offload streams. Bug: 219767875 Test: atest VtsHalAudioV7_0TargetTest Test: atest VtsHalAudioV7_1TargetTest Test: atest HalAudioV7_0GeneratorTest Change-Id: I0482376ecc7d6964f45f508a80716ffab18044b4 --- .../7.0/AudioPrimaryHidlHalTest.cpp | 50 ++----------- .../vts/functional/7.0/Generators.cpp | 71 ++++++++++--------- .../vts/functional/7.0/PolicyConfig.h | 14 +++- .../vts/functional/AudioPrimaryHidlHalTest.h | 17 +++-- .../vts/functional/AudioTestDefinitions.h | 3 +- 5 files changed, 68 insertions(+), 87 deletions(-) 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 dfc238623c..be90b2180c 100644 --- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp @@ -517,20 +517,10 @@ class PcmOnlyConfigOutputStreamTest : public OutputStreamTest { } bool canQueryPresentationPosition() const { - auto maybeSinkAddress = - getCachedPolicyConfig().getSinkDeviceForMixPort(getDeviceName(), getMixPortName()); - // Returning 'true' when no sink is found so the test can fail later with a more clear - // problem description. - return !maybeSinkAddress.has_value() || - !xsd::isTelephonyDevice(maybeSinkAddress.value().deviceType); + return !xsd::isTelephonyDevice(address.deviceType); } void createPatchIfNeeded() { - auto maybeSinkAddress = - getCachedPolicyConfig().getSinkDeviceForMixPort(getDeviceName(), getMixPortName()); - ASSERT_TRUE(maybeSinkAddress.has_value()) - << "No sink device found for mix port " << getMixPortName() << " (module " - << getDeviceName() << ")"; if (areAudioPatchesSupported()) { AudioPortConfig source; source.base.format.value(getConfig().base.format); @@ -540,13 +530,13 @@ class PcmOnlyConfigOutputStreamTest : public OutputStreamTest { source.ext.mix().ioHandle = helper.getIoHandle(); source.ext.mix().useCase.stream({}); AudioPortConfig sink; - sink.ext.device(maybeSinkAddress.value()); + sink.ext.device(address); EXPECT_OK(getDevice()->createAudioPatch(hidl_vec{source}, hidl_vec{sink}, returnIn(res, mPatchHandle))); mHasPatch = res == Result::OK; } else { - EXPECT_OK(stream->setDevices({maybeSinkAddress.value()})); + EXPECT_OK(stream->setDevices({address})); } } @@ -556,10 +546,6 @@ class PcmOnlyConfigOutputStreamTest : public OutputStreamTest { EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle)); mHasPatch = false; } - } else { - if (stream) { - EXPECT_OK(stream->setDevices({address})); - } } } @@ -691,24 +677,12 @@ class PcmOnlyConfigInputStreamTest : public InputStreamTest { InputStreamTest::TearDown(); } - bool canQueryCapturePosition() const { - auto maybeSourceAddress = getCachedPolicyConfig().getSourceDeviceForMixPort( - getDeviceName(), getMixPortName()); - // Returning 'true' when no source is found so the test can fail later with a more clear - // problem description. - return !maybeSourceAddress.has_value() || - !xsd::isTelephonyDevice(maybeSourceAddress.value().deviceType); - } + bool canQueryCapturePosition() const { return !xsd::isTelephonyDevice(address.deviceType); } void createPatchIfNeeded() { - auto maybeSourceAddress = getCachedPolicyConfig().getSourceDeviceForMixPort( - getDeviceName(), getMixPortName()); - ASSERT_TRUE(maybeSourceAddress.has_value()) - << "No source device found for mix port " << getMixPortName() << " (module " - << getDeviceName() << ")"; if (areAudioPatchesSupported()) { AudioPortConfig source; - source.ext.device(maybeSourceAddress.value()); + source.ext.device(address); AudioPortConfig sink; sink.base.format.value(getConfig().base.format); sink.base.sampleRateHz.value(getConfig().base.sampleRateHz); @@ -721,7 +695,7 @@ class PcmOnlyConfigInputStreamTest : public InputStreamTest { returnIn(res, mPatchHandle))); mHasPatch = res == Result::OK; } else { - EXPECT_OK(stream->setDevices({maybeSourceAddress.value()})); + EXPECT_OK(stream->setDevices({address})); } } @@ -731,10 +705,6 @@ class PcmOnlyConfigInputStreamTest : public InputStreamTest { EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle)); mHasPatch = false; } - } else { - if (stream) { - EXPECT_OK(stream->setDevices({address})); - } } } @@ -864,14 +834,8 @@ TEST_P(MicrophoneInfoInputStreamTest, GetActiveMicrophones) { } ASSERT_OK(res); - auto maybeSourceAddress = - getCachedPolicyConfig().getSourceDeviceForMixPort(getDeviceName(), getMixPortName()); - ASSERT_TRUE(maybeSourceAddress.has_value()) - << "No source device found for mix port " << getMixPortName() << " (module " - << getDeviceName() << ")"; - for (auto microphone : microphones) { - if (microphone.deviceAddress == maybeSourceAddress.value()) { + if (microphone.deviceAddress == address) { StreamReader reader(stream.get(), stream->getBufferSize()); ASSERT_TRUE(reader.start()); reader.pause(); // This ensures that at least one read has happened. 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 f936d0afbf..cd84c41308 100644 --- a/audio/core/all-versions/vts/functional/7.0/Generators.cpp +++ b/audio/core/all-versions/vts/functional/7.0/Generators.cpp @@ -57,9 +57,6 @@ static std::vector combineAudioConfig(std::vector, bool> generateOutFlags( const xsd::MixPorts::MixPort& mixPort) { - static const std::vector offloadFlags = { - toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD), - toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)}; std::vector flags; bool isOffload = false; if (mixPort.hasFlags()) { @@ -67,14 +64,10 @@ static std::tuple, bool> generateOutFlags( isOffload = std::find(xsdFlags.begin(), xsdFlags.end(), xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != xsdFlags.end(); - if (!isOffload) { - for (auto flag : xsdFlags) { - if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) { - flags.push_back(toString(flag)); - } + for (auto flag : xsdFlags) { + if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) { + flags.push_back(toString(flag)); } - } else { - flags = offloadFlags; } } return {flags, isOffload}; @@ -100,11 +93,10 @@ std::vector generateOutputDeviceConfigParameters(bool one if (!module || !module->getFirstMixPorts()) break; for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) { if (mixPort.getRole() != xsd::Role::source) continue; // not an output profile - if (getCachedPolicyConfig() - .getAttachedSinkDeviceForMixPort(moduleName, mixPort.getName()) - .empty()) { - continue; // no attached device - } + const auto attachedDeviceAddress = + getCachedPolicyConfig().getDeviceAddressOfSinkDeviceAttachedToMixPort( + moduleName, mixPort.getName()); + if (!attachedDeviceAddress.has_value()) continue; auto [flags, isOffload] = generateOutFlags(mixPort); for (const auto& profile : mixPort.getProfile()) { if (!profile.hasFormat() || !profile.hasSamplingRates() || @@ -118,7 +110,8 @@ std::vector generateOutputDeviceConfigParameters(bool one if (isOffload) { config.offloadInfo.info(generateOffloadInfo(config.base)); } - result.emplace_back(device, mixPort.getName(), config, flags); + result.emplace_back(device, mixPort.getName(), attachedDeviceAddress.value(), + config, flags); if (oneProfilePerDevice) break; } if (oneProfilePerDevice) break; @@ -162,13 +155,16 @@ const std::vector& getOutputDeviceInvalidConfigParameters profile.getFormat(), static_cast(profile.getSamplingRates()[0]), toString(profile.getChannelMasks()[0])}; + DeviceAddress defaultDevice = { + toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT), {}}; { AudioConfig config{.base = validBase}; config.base.channelMask = "random_string"; if (isOffload) { config.offloadInfo.info(generateOffloadInfo(validBase)); } - result.emplace_back(device, mixPort.getName(), config, validFlags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + validFlags); } { AudioConfig config{.base = validBase}; @@ -176,7 +172,8 @@ const std::vector& getOutputDeviceInvalidConfigParameters if (isOffload) { config.offloadInfo.info(generateOffloadInfo(validBase)); } - result.emplace_back(device, mixPort.getName(), config, validFlags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + validFlags); } if (generateInvalidFlags) { AudioConfig config{.base = validBase}; @@ -184,32 +181,37 @@ const std::vector& getOutputDeviceInvalidConfigParameters config.offloadInfo.info(generateOffloadInfo(validBase)); } std::vector flags = {"random_string", ""}; - result.emplace_back(device, mixPort.getName(), config, flags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + flags); } if (isOffload) { { AudioConfig config{.base = validBase}; config.offloadInfo.info(generateOffloadInfo(validBase)); config.offloadInfo.info().base.channelMask = "random_string"; - result.emplace_back(device, mixPort.getName(), config, validFlags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + validFlags); } { AudioConfig config{.base = validBase}; config.offloadInfo.info(generateOffloadInfo(validBase)); config.offloadInfo.info().base.format = "random_string"; - result.emplace_back(device, mixPort.getName(), config, validFlags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + validFlags); } { AudioConfig config{.base = validBase}; config.offloadInfo.info(generateOffloadInfo(validBase)); config.offloadInfo.info().streamType = "random_string"; - result.emplace_back(device, mixPort.getName(), config, validFlags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + validFlags); } { AudioConfig config{.base = validBase}; config.offloadInfo.info(generateOffloadInfo(validBase)); config.offloadInfo.info().usage = "random_string"; - result.emplace_back(device, mixPort.getName(), config, validFlags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + validFlags); } hasOffloadConfig = true; } else { @@ -233,11 +235,10 @@ std::vector generateInputDeviceConfigParameters(bool oneP if (!module || !module->getFirstMixPorts()) break; for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) { if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile - if (getCachedPolicyConfig() - .getAttachedSourceDeviceForMixPort(moduleName, mixPort.getName()) - .empty()) { - continue; // no attached device - } + const auto attachedDeviceAddress = + getCachedPolicyConfig().getDeviceAddressOfSourceDeviceAttachedToMixPort( + moduleName, mixPort.getName()); + if (!attachedDeviceAddress.has_value()) continue; std::vector flags; if (mixPort.hasFlags()) { std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(), @@ -250,7 +251,8 @@ std::vector generateInputDeviceConfigParameters(bool oneP auto configs = combineAudioConfig(profile.getChannelMasks(), profile.getSamplingRates(), profile.getFormat()); for (const auto& config : configs) { - result.emplace_back(device, mixPort.getName(), config, flags); + result.emplace_back(device, mixPort.getName(), attachedDeviceAddress.value(), + config, flags); if (oneProfilePerDevice) break; } if (oneProfilePerDevice) break; @@ -298,20 +300,25 @@ const std::vector& getInputDeviceInvalidConfigParameters( profile.getFormat(), static_cast(profile.getSamplingRates()[0]), toString(profile.getChannelMasks()[0])}; + DeviceAddress defaultDevice = { + toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT), {}}; { AudioConfig config{.base = validBase}; config.base.channelMask = "random_string"; - result.emplace_back(device, mixPort.getName(), config, validFlags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + validFlags); } { AudioConfig config{.base = validBase}; config.base.format = "random_string"; - result.emplace_back(device, mixPort.getName(), config, validFlags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + validFlags); } if (generateInvalidFlags) { AudioConfig config{.base = validBase}; std::vector flags = {"random_string", ""}; - result.emplace_back(device, mixPort.getName(), config, flags); + result.emplace_back(device, mixPort.getName(), defaultDevice, config, + flags); } hasConfig = true; break; diff --git a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h index 4aea503938..c1d5669775 100644 --- a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h +++ b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h @@ -61,6 +61,18 @@ class PolicyConfig { const std::set& getModulesWithDevicesNames() const { return mModulesWithDevicesNames; } + std::optional getDeviceAddressOfSinkDeviceAttachedToMixPort( + const std::string& moduleName, const std::string& mixPortName) const { + const auto attachedDevicePort = getAttachedSinkDeviceForMixPort(moduleName, mixPortName); + if (attachedDevicePort.empty()) return {}; + return getDeviceAddressOfDevicePort(moduleName, attachedDevicePort); + } + std::optional getDeviceAddressOfSourceDeviceAttachedToMixPort( + const std::string& moduleName, const std::string& mixPortName) const { + const auto attachedDevicePort = getAttachedSourceDeviceForMixPort(moduleName, mixPortName); + if (attachedDevicePort.empty()) return {}; + return getDeviceAddressOfDevicePort(moduleName, attachedDevicePort); + } std::string getAttachedSinkDeviceForMixPort(const std::string& moduleName, const std::string& mixPortName) const { return findAttachedDevice(getAttachedDevices(moduleName), @@ -84,8 +96,6 @@ class PolicyConfig { const std::vector& getAttachedDevices(const std::string& moduleName) const; std::optional getDeviceAddressOfDevicePort( const std::string& moduleName, const std::string& devicePortName) const; - std::string getDevicePortTagNameFromType(const std::string& moduleName, - const AudioDevice& deviceType) const; std::set getSinkDevicesForMixPort(const std::string& moduleName, const std::string& mixPortName) const; std::set getSourceDevicesForMixPort(const std::string& moduleName, diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h index 38e9e5f467..afc25f0a1e 100644 --- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h +++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h @@ -617,7 +617,8 @@ static std::string DeviceConfigParameterToString( std::get(info.param))); #elif MAJOR_VERSION >= 7 const auto configPart = - std::to_string(config.base.sampleRateHz) + "_" + + ::testing::PrintToString(std::get(info.param).deviceType) + + "_" + std::to_string(config.base.sampleRateHz) + "_" + // The channel masks and flags are vectors of strings, just need to sanitize them. SanitizeStringForGTestName(::testing::PrintToString(config.base.channelMask)) + "_" + SanitizeStringForGTestName(::testing::PrintToString(std::get(info.param))); @@ -658,6 +659,9 @@ class AudioHidlTestWithDeviceConfigParameter std::get(std::get(GetParam()))); } #elif MAJOR_VERSION >= 7 + DeviceAddress getAttachedDeviceAddress() const { + return std::get(GetParam()); + } hidl_vec getInputFlags() const { return std::get(GetParam()); } hidl_vec getOutputFlags() const { return std::get(GetParam()); } #endif @@ -1047,7 +1051,7 @@ class OutputStreamTest #if MAJOR_VERSION <= 6 address.device = AudioDevice::OUT_DEFAULT; #elif MAJOR_VERSION >= 7 - address.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT); + address = getAttachedDeviceAddress(); #endif const AudioConfig& config = getConfig(); auto flags = getOutputFlags(); @@ -1243,16 +1247,11 @@ class InputStreamTest #if MAJOR_VERSION <= 6 address.device = AudioDevice::IN_DEFAULT; #elif MAJOR_VERSION >= 7 - auto maybeSourceAddress = getCachedPolicyConfig().getSourceDeviceForMixPort( - getDeviceName(), getMixPortName()); + address = getAttachedDeviceAddress(); auto& metadata = initMetadata.tracks[0]; - if (maybeSourceAddress.has_value() && - !xsd::isTelephonyDevice(maybeSourceAddress.value().deviceType)) { - address = maybeSourceAddress.value(); + if (!xsd::isTelephonyDevice(address.deviceType)) { metadata.source = toString(xsd::AudioSource::AUDIO_SOURCE_UNPROCESSED); metadata.channelMask = getConfig().base.channelMask; - } else { - address.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT); } #if MAJOR_VERSION == 7 && MINOR_VERSION >= 1 auto flagsIt = std::find(flags.begin(), flags.end(), diff --git a/audio/core/all-versions/vts/functional/AudioTestDefinitions.h b/audio/core/all-versions/vts/functional/AudioTestDefinitions.h index 802b87bc36..3de06c35ac 100644 --- a/audio/core/all-versions/vts/functional/AudioTestDefinitions.h +++ b/audio/core/all-versions/vts/functional/AudioTestDefinitions.h @@ -39,9 +39,10 @@ using DeviceConfigParameter = std::tuple< std::variant>; #elif MAJOR_VERSION >= 7 -enum { PARAM_DEVICE, PARAM_PORT_NAME, PARAM_CONFIG, PARAM_FLAGS }; +enum { PARAM_DEVICE, PARAM_PORT_NAME, PARAM_ATTACHED_DEV_ADDR, PARAM_CONFIG, PARAM_FLAGS }; using DeviceConfigParameter = std::tuple>; #endif From 158a2ddb2fafa01e2e52a4c9033b81c29d05dfdd Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Mon, 27 Jun 2022 21:34:09 +0000 Subject: [PATCH 2/2] audio VTS: add CompressedOffloadOutputStream test CompressedOffloadOutputStreamTest#Mp3FormatGaplessOffload verifies that gapless offload is supported by compressed offload mix ports. Bug: 219767875 Test: atest VtsHalAudioV7_0TargetTest Test: atest VtsHalAudioV7_1TargetTest Change-Id: I7d42f8a714da2923b8775445ba301938ca90b885 --- .../7.0/AudioPrimaryHidlHalTest.cpp | 134 ++++++++++++++++-- .../vts/functional/7.0/Generators.cpp | 4 +- .../all-versions/vts/functional/Android.bp | 2 + .../vts/functional/AudioPrimaryHidlHalTest.h | 17 ++- .../functional/VtsHalAudioV7_0TargetTest.xml | 1 + .../functional/VtsHalAudioV7_1TargetTest.xml | 2 + .../vts/functional/data/sine882hz3s.mp3 | Bin 0 -> 19117 bytes 7 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 audio/core/all-versions/vts/functional/data/sine882hz3s.mp3 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 be90b2180c..44d3cbd14a 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 +#include + #include #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::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::max(); ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer, &frames)); EXPECT_GT(frames, framesInitial); @@ -700,11 +709,9 @@ class PcmOnlyConfigInputStreamTest : public InputStreamTest { } void releasePatchIfNeeded() { - if (getDevice()) { - if (areAudioPatchesSupported() && mHasPatch) { - EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle)); - mHasPatch = false; - } + if (getDevice() && areAudioPatchesSupported() && mHasPatch) { + EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle)); + mHasPatch = false; } } @@ -853,3 +860,100 @@ INSTANTIATE_TEST_CASE_P(MicrophoneInfoInputStream, MicrophoneInfoInputStreamTest ::testing::ValuesIn(getBuiltinMicConfigParameters()), &DeviceConfigParameterToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MicrophoneInfoInputStreamTest); + +static const std::vector& getOutputDeviceCompressedConfigParameters( + const AudioConfigBase& configToMatch) { + static const std::vector parameters = [&] { + auto allParams = getOutputDeviceConfigParameters(); + std::vector compressedParams; + std::copy_if(allParams.begin(), allParams.end(), std::back_inserter(compressedParams), + [&](auto cfg) { + if (std::get(cfg).base != configToMatch) return false; + const auto& flags = std::get(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* 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(is), + std::istreambuf_iterator()); + 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 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 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()); + } + 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 cd84c41308..8b955b6c84 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 @@ 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 }; } diff --git a/audio/core/all-versions/vts/functional/Android.bp b/audio/core/all-versions/vts/functional/Android.bp index c757032e0f..5b0a7f28f9 100644 --- a/audio/core/all-versions/vts/functional/Android.bp +++ b/audio/core/all-versions/vts/functional/Android.bp @@ -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. diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h index afc25f0a1e..6c5584d427 100644 --- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h +++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h @@ -937,6 +937,14 @@ 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) + : 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 { 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(MessageQueueFlagBits::NOT_EMPTY)); uint32_t efState = 0; @@ -1030,6 +1040,7 @@ class StreamWriter : public StreamWorker { ALOGE("bad wait status: %d", ret); success = false; } + if (success && mDataPosition == 0) mOnDataWrap(); return success; } @@ -1037,6 +1048,8 @@ class StreamWriter : public StreamWorker { IStreamOut* const mStream; const size_t mBufferSize; std::vector mData; + std::function mOnDataWrap = []() {}; + size_t mDataPosition = 0; std::unique_ptr mCommandMQ; std::unique_ptr mDataMQ; std::unique_ptr mStatusMQ; diff --git a/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml b/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml index f0e26958b9..8da5744089 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 @@