Merge changes from topic "nnapi-aidl-burst"
* changes:
Add Burst tests to NN AIDL HAL VTS
Add IBurst to NN AIDL HAL -- hal
Format NN AIDL HAL
diff --git a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
index fe8eee1..88dd12e 100644
--- a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
+++ b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
@@ -212,6 +212,15 @@
return isOutputDevice(stringToAudioDevice(device));
}
+static inline bool isTelephonyDevice(AudioDevice device) {
+ return device == AudioDevice::AUDIO_DEVICE_OUT_TELEPHONY_TX ||
+ device == AudioDevice::AUDIO_DEVICE_IN_TELEPHONY_RX;
+}
+
+static inline bool isTelephonyDevice(const std::string& device) {
+ return isTelephonyDevice(stringToAudioDevice(device));
+}
+
static inline bool maybeVendorExtension(const std::string& s) {
// Only checks whether the string starts with the "vendor prefix".
static const std::string vendorPrefix = "VX_";
@@ -260,6 +269,24 @@
return stringToAudioUsage(usage) == AudioUsage::UNKNOWN;
}
+static inline bool isLinearPcm(AudioFormat format) {
+ switch (format) {
+ case AudioFormat::AUDIO_FORMAT_PCM_16_BIT:
+ case AudioFormat::AUDIO_FORMAT_PCM_8_BIT:
+ case AudioFormat::AUDIO_FORMAT_PCM_32_BIT:
+ case AudioFormat::AUDIO_FORMAT_PCM_8_24_BIT:
+ case AudioFormat::AUDIO_FORMAT_PCM_FLOAT:
+ case AudioFormat::AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static inline bool isLinearPcm(const std::string& format) {
+ return isLinearPcm(stringToAudioFormat(format));
+}
+
} // namespace android::audio::policy::configuration::V7_0
#endif // ANDROID_AUDIO_POLICY_CONFIGURATION_V7_0__ENUMS_H
diff --git a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
index f87e5ed..b96cc83 100644
--- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
@@ -77,7 +77,6 @@
.tags = {},
.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
#endif
- EventFlag* efGroup;
for (auto microphone : microphones) {
#if MAJOR_VERSION <= 6
if (microphone.deviceAddress.device != AudioDevice::IN_BUILTIN_MIC) {
@@ -96,44 +95,15 @@
config, flags, initMetadata, cb);
},
config, &res, &suggestedConfig));
+ StreamReader reader(stream.get(), stream->getBufferSize());
+ ASSERT_TRUE(reader.start());
+ reader.pause(); // This ensures that at least one read has happened.
+ EXPECT_FALSE(reader.hasError());
+
hidl_vec<MicrophoneInfo> activeMicrophones;
- Result readRes;
- typedef MessageQueue<IStreamIn::ReadParameters, kSynchronizedReadWrite> CommandMQ;
- typedef MessageQueue<uint8_t, kSynchronizedReadWrite> DataMQ;
- std::unique_ptr<CommandMQ> commandMQ;
- std::unique_ptr<DataMQ> dataMQ;
- size_t frameSize = stream->getFrameSize();
- size_t frameCount = stream->getBufferSize() / frameSize;
- ASSERT_OK(stream->prepareForReading(
- frameSize, frameCount, [&](auto r, auto& c, auto& d, auto&, auto) {
- readRes = r;
- if (readRes == Result::OK) {
- commandMQ.reset(new CommandMQ(c));
- dataMQ.reset(new DataMQ(d));
- if (dataMQ->isValid() && dataMQ->getEventFlagWord()) {
- EventFlag::createEventFlag(dataMQ->getEventFlagWord(), &efGroup);
- }
- }
- }));
- ASSERT_OK(readRes);
- IStreamIn::ReadParameters params;
- params.command = IStreamIn::ReadCommand::READ;
- ASSERT_TRUE(commandMQ != nullptr);
- ASSERT_TRUE(commandMQ->isValid());
- ASSERT_TRUE(commandMQ->write(¶ms));
- efGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
- uint32_t efState = 0;
- efGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState);
- if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) {
- ASSERT_OK(stream->getActiveMicrophones(returnIn(res, activeMicrophones)));
- ASSERT_OK(res);
- ASSERT_NE(0U, activeMicrophones.size());
- }
- helper.close(true /*clear*/, &res);
+ ASSERT_OK(stream->getActiveMicrophones(returnIn(res, activeMicrophones)));
ASSERT_OK(res);
- if (efGroup) {
- EventFlag::deleteEventFlag(&efGroup);
- }
+ EXPECT_NE(0U, activeMicrophones.size());
}
}
}
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 c1923f1..657b42d 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,8 @@
* limitations under the License.
*/
+#include <android-base/chrono_utils.h>
+
#include "Generators.h"
// pull in all the <= 6.0 tests
@@ -487,3 +489,305 @@
<< ::testing::PrintToString(metadata);
}
}
+
+static const std::vector<DeviceConfigParameter>& getOutputDevicePcmOnlyConfigParameters() {
+ static const std::vector<DeviceConfigParameter> parameters = [] {
+ auto allParams = getOutputDeviceConfigParameters();
+ std::vector<DeviceConfigParameter> pcmParams;
+ std::copy_if(allParams.begin(), allParams.end(), std::back_inserter(pcmParams), [](auto cfg) {
+ const auto& flags = std::get<PARAM_FLAGS>(cfg);
+ return xsd::isLinearPcm(std::get<PARAM_CONFIG>(cfg).base.format)
+ // MMAP NOIRQ and HW A/V Sync profiles use special writing protocols.
+ &&
+ std::find_if(flags.begin(), flags.end(),
+ [](const auto& flag) {
+ return flag == toString(xsd::AudioInOutFlag::
+ AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) ||
+ flag == toString(xsd::AudioInOutFlag::
+ AUDIO_OUTPUT_FLAG_HW_AV_SYNC);
+ }) == flags.end() &&
+ !getCachedPolicyConfig()
+ .getAttachedSinkDeviceForMixPort(
+ std::get<PARAM_DEVICE_NAME>(std::get<PARAM_DEVICE>(cfg)),
+ std::get<PARAM_PORT_NAME>(cfg))
+ .empty();
+ });
+ return pcmParams;
+ }();
+ return parameters;
+}
+
+class PcmOnlyConfigOutputStreamTest : public OutputStreamTest {
+ public:
+ void TearDown() override {
+ releasePatchIfNeeded();
+ OutputStreamTest::TearDown();
+ }
+
+ 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);
+ }
+
+ 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);
+ source.base.sampleRateHz.value(getConfig().base.sampleRateHz);
+ source.base.channelMask.value(getConfig().base.channelMask);
+ source.ext.mix({});
+ source.ext.mix().ioHandle = helper.getIoHandle();
+ source.ext.mix().useCase.stream({});
+ AudioPortConfig sink;
+ sink.ext.device(maybeSinkAddress.value());
+ EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{source},
+ hidl_vec<AudioPortConfig>{sink},
+ returnIn(res, mPatchHandle)));
+ mHasPatch = res == Result::OK;
+ } else {
+ EXPECT_OK(stream->setDevices({maybeSinkAddress.value()}));
+ }
+ }
+
+ void releasePatchIfNeeded() {
+ if (areAudioPatchesSupported()) {
+ if (mHasPatch) {
+ EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle));
+ mHasPatch = false;
+ }
+ } else {
+ EXPECT_OK(stream->setDevices({address}));
+ }
+ }
+
+ const std::string& getMixPortName() const { return std::get<PARAM_PORT_NAME>(GetParam()); }
+
+ void waitForPresentationPositionAdvance(StreamWriter& writer, uint64_t* firstPosition = nullptr,
+ uint64_t* lastPosition = nullptr) {
+ static constexpr int kWriteDurationUs = 50 * 1000;
+ static constexpr std::chrono::milliseconds kPositionChangeTimeout{10000};
+ uint64_t framesInitial;
+ TimeSpec ts;
+ // Starting / resuming of streams is asynchronous at HAL level.
+ // Sometimes HAL doesn't have enough information until the audio data actually gets
+ // consumed by the hardware.
+ do {
+ ASSERT_OK(stream->getPresentationPosition(returnIn(res, framesInitial, ts)));
+ ASSERT_RESULT(okOrInvalidState, res);
+ } while (res != Result::OK);
+ uint64_t frames = framesInitial;
+ bool timedOut = false;
+ for (android::base::Timer elapsed;
+ frames <= framesInitial && !writer.hasError() &&
+ !(timedOut = (elapsed.duration() >= kPositionChangeTimeout));) {
+ usleep(kWriteDurationUs);
+ ASSERT_OK(stream->getPresentationPosition(returnIn(res, frames, ts)));
+ ASSERT_RESULT(Result::OK, res);
+ }
+ EXPECT_FALSE(timedOut);
+ EXPECT_FALSE(writer.hasError());
+ EXPECT_GT(frames, framesInitial);
+ if (firstPosition) *firstPosition = framesInitial;
+ if (lastPosition) *lastPosition = frames;
+ }
+
+ private:
+ AudioPatchHandle mPatchHandle = {};
+ bool mHasPatch = false;
+};
+
+TEST_P(PcmOnlyConfigOutputStreamTest, Write) {
+ doc::test("Check that output streams opened for PCM output accepts audio data");
+ StreamWriter writer(stream.get(), stream->getBufferSize());
+ ASSERT_TRUE(writer.start());
+ EXPECT_TRUE(writer.waitForAtLeastOneCycle());
+}
+
+TEST_P(PcmOnlyConfigOutputStreamTest, PresentationPositionAdvancesWithWrites) {
+ doc::test("Check that the presentation position advances with writes");
+ if (!canQueryPresentationPosition()) {
+ GTEST_SKIP() << "Presentation position retrieval is not possible";
+ }
+
+ ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+ StreamWriter writer(stream.get(), stream->getBufferSize());
+ ASSERT_TRUE(writer.start());
+ ASSERT_TRUE(writer.waitForAtLeastOneCycle());
+ ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer));
+
+ writer.stop();
+ releasePatchIfNeeded();
+}
+
+TEST_P(PcmOnlyConfigOutputStreamTest, PresentationPositionPreservedOnStandby) {
+ doc::test("Check that the presentation position does not reset on standby");
+ if (!canQueryPresentationPosition()) {
+ GTEST_SKIP() << "Presentation position retrieval is not possible";
+ }
+
+ ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+ StreamWriter writer(stream.get(), stream->getBufferSize());
+ ASSERT_TRUE(writer.start());
+ ASSERT_TRUE(writer.waitForAtLeastOneCycle());
+
+ uint64_t framesInitial;
+ ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer, nullptr, &framesInitial));
+ writer.pause();
+ ASSERT_OK(stream->standby());
+ writer.resume();
+
+ uint64_t frames;
+ ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer, &frames));
+ EXPECT_GT(frames, framesInitial);
+
+ writer.stop();
+ releasePatchIfNeeded();
+}
+
+INSTANTIATE_TEST_CASE_P(PcmOnlyConfigOutputStream, PcmOnlyConfigOutputStreamTest,
+ ::testing::ValuesIn(getOutputDevicePcmOnlyConfigParameters()),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PcmOnlyConfigOutputStreamTest);
+
+static const std::vector<DeviceConfigParameter>& getInputDevicePcmOnlyConfigParameters() {
+ static const std::vector<DeviceConfigParameter> parameters = [] {
+ auto allParams = getInputDeviceConfigParameters();
+ std::vector<DeviceConfigParameter> pcmParams;
+ std::copy_if(
+ allParams.begin(), allParams.end(), std::back_inserter(pcmParams), [](auto cfg) {
+ const auto& flags = std::get<PARAM_FLAGS>(cfg);
+ return xsd::isLinearPcm(std::get<PARAM_CONFIG>(cfg).base.format)
+ // MMAP NOIRQ profiles use different reading protocol.
+ &&
+ std::find(flags.begin(), flags.end(),
+ toString(xsd::AudioInOutFlag::AUDIO_INPUT_FLAG_MMAP_NOIRQ)) ==
+ flags.end() &&
+ !getCachedPolicyConfig()
+ .getAttachedSourceDeviceForMixPort(
+ std::get<PARAM_DEVICE_NAME>(
+ std::get<PARAM_DEVICE>(cfg)),
+ std::get<PARAM_PORT_NAME>(cfg))
+ .empty();
+ });
+ return pcmParams;
+ }();
+ return parameters;
+}
+
+class PcmOnlyConfigInputStreamTest : public InputStreamTest {
+ public:
+ void TearDown() override {
+ releasePatchIfNeeded();
+ InputStreamTest::TearDown();
+ }
+
+ 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());
+ AudioPortConfig sink;
+ sink.base.format.value(getConfig().base.format);
+ sink.base.sampleRateHz.value(getConfig().base.sampleRateHz);
+ sink.base.channelMask.value(getConfig().base.channelMask);
+ sink.ext.mix({});
+ sink.ext.mix().ioHandle = helper.getIoHandle();
+ sink.ext.mix().useCase.source(toString(xsd::AudioSource::AUDIO_SOURCE_MIC));
+ EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{source},
+ hidl_vec<AudioPortConfig>{sink},
+ returnIn(res, mPatchHandle)));
+ mHasPatch = res == Result::OK;
+ } else {
+ EXPECT_OK(stream->setDevices({maybeSourceAddress.value()}));
+ }
+ }
+ void releasePatchIfNeeded() {
+ if (areAudioPatchesSupported()) {
+ if (mHasPatch) {
+ EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle));
+ mHasPatch = false;
+ }
+ } else {
+ EXPECT_OK(stream->setDevices({address}));
+ }
+ }
+ const std::string& getMixPortName() const { return std::get<PARAM_PORT_NAME>(GetParam()); }
+
+ private:
+ AudioPatchHandle mPatchHandle = {};
+ bool mHasPatch = false;
+};
+
+TEST_P(PcmOnlyConfigInputStreamTest, Read) {
+ doc::test("Check that input streams opened for PCM input retrieve audio data");
+ StreamReader reader(stream.get(), stream->getBufferSize());
+ ASSERT_TRUE(reader.start());
+ EXPECT_TRUE(reader.waitForAtLeastOneCycle());
+}
+
+TEST_P(PcmOnlyConfigInputStreamTest, CapturePositionAdvancesWithReads) {
+ doc::test("Check that the capture position advances with reads");
+
+ ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+ StreamReader reader(stream.get(), stream->getBufferSize());
+ ASSERT_TRUE(reader.start());
+ EXPECT_TRUE(reader.waitForAtLeastOneCycle());
+
+ uint64_t framesInitial, ts;
+ ASSERT_OK(stream->getCapturePosition(returnIn(res, framesInitial, ts)));
+ ASSERT_RESULT(Result::OK, res);
+
+ EXPECT_TRUE(reader.waitForAtLeastOneCycle());
+
+ uint64_t frames;
+ ASSERT_OK(stream->getCapturePosition(returnIn(res, frames, ts)));
+ ASSERT_RESULT(Result::OK, res);
+ EXPECT_GT(frames, framesInitial);
+
+ reader.stop();
+ releasePatchIfNeeded();
+}
+
+TEST_P(PcmOnlyConfigInputStreamTest, CapturePositionPreservedOnStandby) {
+ doc::test("Check that the capture position does not reset on standby");
+
+ ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+ StreamReader reader(stream.get(), stream->getBufferSize());
+ ASSERT_TRUE(reader.start());
+ EXPECT_TRUE(reader.waitForAtLeastOneCycle());
+
+ uint64_t framesInitial, ts;
+ ASSERT_OK(stream->getCapturePosition(returnIn(res, framesInitial, ts)));
+ ASSERT_RESULT(Result::OK, res);
+
+ reader.pause();
+ ASSERT_OK(stream->standby());
+ reader.resume();
+ EXPECT_FALSE(reader.hasError());
+
+ uint64_t frames;
+ ASSERT_OK(stream->getCapturePosition(returnIn(res, frames, ts)));
+ ASSERT_RESULT(Result::OK, res);
+ EXPECT_GT(frames, framesInitial);
+
+ reader.stop();
+ releasePatchIfNeeded();
+}
+
+INSTANTIATE_TEST_CASE_P(PcmOnlyConfigInputStream, PcmOnlyConfigInputStreamTest,
+ ::testing::ValuesIn(getInputDevicePcmOnlyConfigParameters()),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PcmOnlyConfigInputStreamTest);
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 eafc813..d2ba339 100644
--- a/audio/core/all-versions/vts/functional/7.0/Generators.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
@@ -110,7 +110,7 @@
if (isOffload) {
config.offloadInfo.info(generateOffloadInfo(config.base));
}
- result.emplace_back(device, config, flags);
+ result.emplace_back(device, mixPort.getName(), config, flags);
if (oneProfilePerDevice) break;
}
if (oneProfilePerDevice) break;
@@ -160,7 +160,7 @@
if (isOffload) {
config.offloadInfo.info(generateOffloadInfo(validBase));
}
- result.emplace_back(device, config, validFlags);
+ result.emplace_back(device, mixPort.getName(), config, validFlags);
}
{
AudioConfig config{.base = validBase};
@@ -168,7 +168,7 @@
if (isOffload) {
config.offloadInfo.info(generateOffloadInfo(validBase));
}
- result.emplace_back(device, config, validFlags);
+ result.emplace_back(device, mixPort.getName(), config, validFlags);
}
if (generateInvalidFlags) {
AudioConfig config{.base = validBase};
@@ -176,32 +176,32 @@
config.offloadInfo.info(generateOffloadInfo(validBase));
}
std::vector<AudioInOutFlag> flags = {"random_string", ""};
- result.emplace_back(device, config, flags);
+ result.emplace_back(device, mixPort.getName(), config, flags);
}
if (isOffload) {
{
AudioConfig config{.base = validBase};
config.offloadInfo.info(generateOffloadInfo(validBase));
config.offloadInfo.info().base.channelMask = "random_string";
- result.emplace_back(device, config, validFlags);
+ result.emplace_back(device, mixPort.getName(), config, validFlags);
}
{
AudioConfig config{.base = validBase};
config.offloadInfo.info(generateOffloadInfo(validBase));
config.offloadInfo.info().base.format = "random_string";
- result.emplace_back(device, config, validFlags);
+ result.emplace_back(device, mixPort.getName(), config, validFlags);
}
{
AudioConfig config{.base = validBase};
config.offloadInfo.info(generateOffloadInfo(validBase));
config.offloadInfo.info().streamType = "random_string";
- result.emplace_back(device, config, validFlags);
+ result.emplace_back(device, mixPort.getName(), config, validFlags);
}
{
AudioConfig config{.base = validBase};
config.offloadInfo.info(generateOffloadInfo(validBase));
config.offloadInfo.info().usage = "random_string";
- result.emplace_back(device, config, validFlags);
+ result.emplace_back(device, mixPort.getName(), config, validFlags);
}
hasOffloadConfig = true;
} else {
@@ -234,7 +234,7 @@
auto configs = combineAudioConfig(profile.getChannelMasks(),
profile.getSamplingRates(), profile.getFormat());
for (const auto& config : configs) {
- result.emplace_back(device, config, flags);
+ result.emplace_back(device, mixPort.getName(), config, flags);
if (oneProfilePerDevice) break;
}
if (oneProfilePerDevice) break;
@@ -285,17 +285,17 @@
{
AudioConfig config{.base = validBase};
config.base.channelMask = "random_string";
- result.emplace_back(device, config, validFlags);
+ result.emplace_back(device, mixPort.getName(), config, validFlags);
}
{
AudioConfig config{.base = validBase};
config.base.format = "random_string";
- result.emplace_back(device, config, validFlags);
+ result.emplace_back(device, mixPort.getName(), config, validFlags);
}
if (generateInvalidFlags) {
AudioConfig config{.base = validBase};
std::vector<AudioInOutFlag> flags = {"random_string", ""};
- result.emplace_back(device, config, flags);
+ result.emplace_back(device, mixPort.getName(), config, flags);
}
hasConfig = true;
break;
diff --git a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.cpp b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.cpp
new file mode 100644
index 0000000..2988207
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include <HidlUtils.h>
+#include <system/audio.h>
+#include <system/audio_config.h>
+
+#include "DeviceManager.h"
+#include "PolicyConfig.h"
+#include "common/all-versions/HidlSupport.h"
+
+using ::android::NO_ERROR;
+using ::android::OK;
+
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using namespace ::android::hardware::audio::CPP_VERSION;
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+using ::android::hardware::audio::common::utils::splitString;
+namespace xsd {
+using namespace ::android::audio::policy::configuration::CPP_VERSION;
+using Module = Modules::Module;
+} // namespace xsd
+
+std::string PolicyConfig::getError() const {
+ if (mFilePath.empty()) {
+ return "Could not find " + mConfigFileName +
+ " file in: " + testing::PrintToString(android::audio_get_configuration_paths());
+ } else {
+ return "Invalid config file: " + mFilePath;
+ }
+}
+
+const xsd::Module* PolicyConfig::getModuleFromName(const std::string& name) const {
+ if (mConfig && mConfig->getFirstModules()) {
+ for (const auto& module : mConfig->getFirstModules()->get_module()) {
+ if (module.getName() == name) return &module;
+ }
+ }
+ return nullptr;
+}
+
+std::optional<DeviceAddress> PolicyConfig::getSinkDeviceForMixPort(
+ const std::string& moduleName, const std::string& mixPortName) const {
+ std::string device;
+ if (auto module = getModuleFromName(moduleName); module) {
+ auto possibleDevices = getSinkDevicesForMixPort(moduleName, mixPortName);
+ if (module->hasDefaultOutputDevice() &&
+ possibleDevices.count(module->getDefaultOutputDevice())) {
+ device = module->getDefaultOutputDevice();
+ } else {
+ device = getAttachedSinkDeviceForMixPort(moduleName, mixPortName);
+ }
+ }
+ if (!device.empty()) {
+ return getDeviceAddressOfDevicePort(moduleName, device);
+ }
+ ALOGE("Could not find a route for the mix port \"%s\" in module \"%s\"", mixPortName.c_str(),
+ moduleName.c_str());
+ return std::optional<DeviceAddress>{};
+}
+
+std::optional<DeviceAddress> PolicyConfig::getSourceDeviceForMixPort(
+ const std::string& moduleName, const std::string& mixPortName) const {
+ const std::string device = getAttachedSourceDeviceForMixPort(moduleName, mixPortName);
+ if (!device.empty()) {
+ return getDeviceAddressOfDevicePort(moduleName, device);
+ }
+ ALOGE("Could not find a route for the mix port \"%s\" in module \"%s\"", mixPortName.c_str(),
+ moduleName.c_str());
+ return std::optional<DeviceAddress>{};
+}
+
+bool PolicyConfig::haveInputProfilesInModule(const std::string& name) const {
+ auto module = getModuleFromName(name);
+ if (module && module->getFirstMixPorts()) {
+ for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+ if (mixPort.getRole() == xsd::Role::sink) return true;
+ }
+ }
+ return false;
+}
+
+// static
+std::string PolicyConfig::findExistingConfigurationFile(const std::string& fileName) {
+ for (const auto& location : android::audio_get_configuration_paths()) {
+ std::string path = location + '/' + fileName;
+ if (access(path.c_str(), F_OK) == 0) {
+ return path;
+ }
+ }
+ return {};
+}
+
+std::string PolicyConfig::findAttachedDevice(const std::vector<std::string>& attachedDevices,
+ const std::set<std::string>& possibleDevices) const {
+ for (const auto& device : attachedDevices) {
+ if (possibleDevices.count(device)) return device;
+ }
+ return {};
+}
+
+const std::vector<std::string>& PolicyConfig::getAttachedDevices(
+ const std::string& moduleName) const {
+ static const std::vector<std::string> empty;
+ auto module = getModuleFromName(moduleName);
+ if (module && module->getFirstAttachedDevices()) {
+ return module->getFirstAttachedDevices()->getItem();
+ }
+ return empty;
+}
+
+std::optional<DeviceAddress> PolicyConfig::getDeviceAddressOfDevicePort(
+ const std::string& moduleName, const std::string& devicePortName) const {
+ auto module = getModuleFromName(moduleName);
+ if (module->getFirstDevicePorts()) {
+ const auto& devicePorts = module->getFirstDevicePorts()->getDevicePort();
+ const auto& devicePort = std::find_if(
+ devicePorts.begin(), devicePorts.end(),
+ [&devicePortName](auto dp) { return dp.getTagName() == devicePortName; });
+ if (devicePort != devicePorts.end()) {
+ audio_devices_t halDeviceType;
+ if (HidlUtils::audioDeviceTypeToHal(devicePort->getType(), &halDeviceType) ==
+ NO_ERROR) {
+ // For AOSP device types use the standard parser for the device address.
+ const std::string address =
+ devicePort->hasAddress() ? devicePort->getAddress() : "";
+ DeviceAddress result;
+ if (HidlUtils::deviceAddressFromHal(halDeviceType, address.c_str(), &result) ==
+ NO_ERROR) {
+ return result;
+ }
+ } else if (xsd::isVendorExtension(devicePort->getType())) {
+ DeviceAddress result;
+ result.deviceType = devicePort->getType();
+ if (devicePort->hasAddress()) {
+ result.address.id(devicePort->getAddress());
+ }
+ return result;
+ }
+ } else {
+ ALOGE("Device port \"%s\" not found in module \"%s\"", devicePortName.c_str(),
+ moduleName.c_str());
+ }
+ } else {
+ ALOGE("Module \"%s\" has no device ports", moduleName.c_str());
+ }
+ return std::optional<DeviceAddress>{};
+}
+
+std::set<std::string> PolicyConfig::getSinkDevicesForMixPort(const std::string& moduleName,
+ const std::string& mixPortName) const {
+ std::set<std::string> result;
+ auto module = getModuleFromName(moduleName);
+ if (module && module->getFirstRoutes()) {
+ for (const auto& route : module->getFirstRoutes()->getRoute()) {
+ const auto sources = splitString(route.getSources(), ',');
+ if (std::find(sources.begin(), sources.end(), mixPortName) != sources.end()) {
+ result.insert(route.getSink());
+ }
+ }
+ }
+ return result;
+}
+
+std::set<std::string> PolicyConfig::getSourceDevicesForMixPort(
+ const std::string& moduleName, const std::string& mixPortName) const {
+ std::set<std::string> result;
+ auto module = getModuleFromName(moduleName);
+ if (module && module->getFirstRoutes()) {
+ const auto& routes = module->getFirstRoutes()->getRoute();
+ const auto route = std::find_if(routes.begin(), routes.end(), [&mixPortName](auto rte) {
+ return rte.getSink() == mixPortName;
+ });
+ if (route != routes.end()) {
+ const auto sources = splitString(route->getSources(), ',');
+ std::copy(sources.begin(), sources.end(), std::inserter(result, result.end()));
+ }
+ }
+ return result;
+}
+
+void PolicyConfig::init() {
+ if (mConfig) {
+ mStatus = OK;
+ mPrimaryModule = getModuleFromName(DeviceManager::kPrimaryDevice);
+ if (mConfig->getFirstModules()) {
+ for (const auto& module : mConfig->getFirstModules()->get_module()) {
+ if (module.getFirstAttachedDevices()) {
+ auto attachedDevices = module.getFirstAttachedDevices()->getItem();
+ if (!attachedDevices.empty()) {
+ mModulesWithDevicesNames.insert(module.getName());
+ }
+ }
+ }
+ }
+ }
+}
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 feb4d4b..f798839 100644
--- a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
+++ b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
@@ -16,15 +16,12 @@
#pragma once
-#include <fcntl.h>
-#include <unistd.h>
-
#include <optional>
#include <set>
#include <string>
+#include <vector>
#include <gtest/gtest.h>
-#include <system/audio_config.h>
#include <utils/Errors.h>
// clang-format off
@@ -35,12 +32,6 @@
#include <android_audio_policy_configuration_V7_0-enums.h>
#include <android_audio_policy_configuration_V7_0.h>
-#include "DeviceManager.h"
-
-using ::android::NO_INIT;
-using ::android::OK;
-using ::android::status_t;
-
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::CPP_VERSION;
namespace xsd {
@@ -62,69 +53,49 @@
mConfig{xsd::read(mFilePath.c_str())} {
init();
}
- status_t getStatus() const { return mStatus; }
- std::string getError() const {
- if (mFilePath.empty()) {
- return std::string{"Could not find "} + mConfigFileName +
- " file in: " + testing::PrintToString(android::audio_get_configuration_paths());
- } else {
- return "Invalid config file: " + mFilePath;
- }
- }
+ android::status_t getStatus() const { return mStatus; }
+ std::string getError() const;
const std::string& getFilePath() const { return mFilePath; }
- const xsd::Module* getModuleFromName(const std::string& name) const {
- if (mConfig && mConfig->getFirstModules()) {
- for (const auto& module : mConfig->getFirstModules()->get_module()) {
- if (module.getName() == name) return &module;
- }
- }
- return nullptr;
- }
+ const xsd::Module* getModuleFromName(const std::string& name) const;
const xsd::Module* getPrimaryModule() const { return mPrimaryModule; }
const std::set<std::string>& getModulesWithDevicesNames() const {
return mModulesWithDevicesNames;
}
- bool haveInputProfilesInModule(const std::string& name) const {
- auto module = getModuleFromName(name);
- if (module && module->getFirstMixPorts()) {
- for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
- if (mixPort.getRole() == xsd::Role::sink) return true;
- }
- }
- return false;
+ std::string getAttachedSinkDeviceForMixPort(const std::string& moduleName,
+ const std::string& mixPortName) const {
+ return findAttachedDevice(getAttachedDevices(moduleName),
+ getSinkDevicesForMixPort(moduleName, mixPortName));
}
+ std::string getAttachedSourceDeviceForMixPort(const std::string& moduleName,
+ const std::string& mixPortName) const {
+ return findAttachedDevice(getAttachedDevices(moduleName),
+ getSourceDevicesForMixPort(moduleName, mixPortName));
+ }
+ std::optional<DeviceAddress> getSinkDeviceForMixPort(const std::string& moduleName,
+ const std::string& mixPortName) const;
+ std::optional<DeviceAddress> getSourceDeviceForMixPort(const std::string& moduleName,
+ const std::string& mixPortName) const;
+ bool haveInputProfilesInModule(const std::string& name) const;
private:
- static std::string findExistingConfigurationFile(const std::string& fileName) {
- for (const auto& location : android::audio_get_configuration_paths()) {
- std::string path = location + '/' + fileName;
- if (access(path.c_str(), F_OK) == 0) {
- return path;
- }
- }
- return std::string{};
- }
- void init() {
- if (mConfig) {
- mStatus = OK;
- mPrimaryModule = getModuleFromName(DeviceManager::kPrimaryDevice);
- if (mConfig->getFirstModules()) {
- for (const auto& module : mConfig->getFirstModules()->get_module()) {
- if (module.getFirstAttachedDevices()) {
- auto attachedDevices = module.getFirstAttachedDevices()->getItem();
- if (!attachedDevices.empty()) {
- mModulesWithDevicesNames.insert(module.getName());
- }
- }
- }
- }
- }
- }
+ static std::string findExistingConfigurationFile(const std::string& fileName);
+ std::string findAttachedDevice(const std::vector<std::string>& attachedDevices,
+ const std::set<std::string>& possibleDevices) const;
+ const std::vector<std::string>& getAttachedDevices(const std::string& moduleName) const;
+ std::optional<DeviceAddress> getDeviceAddressOfDevicePort(
+ const std::string& moduleName, const std::string& devicePortName) const;
+ std::string getDevicePortTagNameFromType(const std::string& moduleName,
+ const AudioDevice& deviceType) const;
+ std::set<std::string> getSinkDevicesForMixPort(const std::string& moduleName,
+ const std::string& mixPortName) const;
+ std::set<std::string> getSourceDevicesForMixPort(const std::string& moduleName,
+ const std::string& mixPortName) const;
+ void init();
const std::string mConfigFileName;
const std::string mFilePath;
std::optional<xsd::AudioPolicyConfiguration> mConfig;
- status_t mStatus = NO_INIT;
+ android::status_t mStatus = android::NO_INIT;
const xsd::Module* mPrimaryModule;
std::set<std::string> mModulesWithDevicesNames;
};
diff --git a/audio/core/all-versions/vts/functional/Android.bp b/audio/core/all-versions/vts/functional/Android.bp
index 91c54dc..9183191 100644
--- a/audio/core/all-versions/vts/functional/Android.bp
+++ b/audio/core/all-versions/vts/functional/Android.bp
@@ -154,6 +154,7 @@
srcs: [
"7.0/AudioPrimaryHidlHalTest.cpp",
"7.0/Generators.cpp",
+ "7.0/PolicyConfig.cpp",
],
generated_headers: ["audio_policy_configuration_V7_0_parser"],
generated_sources: ["audio_policy_configuration_V7_0_parser"],
@@ -161,6 +162,7 @@
"android.hardware.audio@7.0",
"android.hardware.audio.common@7.0",
"android.hardware.audio.common@7.0-enums",
+ "android.hardware.audio.common@7.0-util",
],
cflags: [
"-DMAJOR_VERSION=7",
@@ -176,7 +178,15 @@
}
// Note: the following aren't VTS tests, but rather unit tests
-// to verify correctness of test parameter generator utilities.
+// to verify correctness of test utilities.
+cc_test {
+ name: "HalAudioStreamWorkerTest",
+ host_supported: true,
+ srcs: [
+ "tests/streamworker_tests.cpp",
+ ],
+}
+
cc_test {
name: "HalAudioV6_0GeneratorTest",
defaults: ["VtsHalAudioTargetTest_defaults"],
@@ -208,6 +218,7 @@
defaults: ["VtsHalAudioTargetTest_defaults"],
srcs: [
"7.0/Generators.cpp",
+ "7.0/PolicyConfig.cpp",
"tests/generators_tests.cpp",
],
generated_headers: ["audio_policy_configuration_V7_0_parser"],
@@ -216,6 +227,7 @@
"android.hardware.audio@7.0",
"android.hardware.audio.common@7.0",
"android.hardware.audio.common@7.0-enums",
+ "android.hardware.audio.common@7.0-util",
],
cflags: [
"-DMAJOR_VERSION=7",
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 56939fe..ae1467d 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -89,6 +89,10 @@
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::common::test::utility;
using namespace ::android::hardware::audio::CPP_VERSION;
+using ReadParameters = ::android::hardware::audio::CPP_VERSION::IStreamIn::ReadParameters;
+using ReadStatus = ::android::hardware::audio::CPP_VERSION::IStreamIn::ReadStatus;
+using WriteCommand = ::android::hardware::audio::CPP_VERSION::IStreamOut::WriteCommand;
+using WriteStatus = ::android::hardware::audio::CPP_VERSION::IStreamOut::WriteStatus;
#if MAJOR_VERSION >= 7
// Make an alias for enumerations generated from the APM config XSD.
namespace xsd {
@@ -100,6 +104,7 @@
static auto okOrNotSupported = {Result::OK, Result::NOT_SUPPORTED};
static auto okOrNotSupportedOrInvalidArgs = {Result::OK, Result::NOT_SUPPORTED,
Result::INVALID_ARGUMENTS};
+static auto okOrInvalidState = {Result::OK, Result::INVALID_STATE};
static auto okOrInvalidStateOrNotSupported = {Result::OK, Result::INVALID_STATE,
Result::NOT_SUPPORTED};
static auto invalidArgsOrNotSupported = {Result::INVALID_ARGUMENTS, Result::NOT_SUPPORTED};
@@ -115,6 +120,7 @@
#include "7.0/Generators.h"
#include "7.0/PolicyConfig.h"
#endif
+#include "StreamWorker.h"
class HidlTest : public ::testing::Test {
public:
@@ -778,6 +784,11 @@
////////////////////////// open{Output,Input}Stream //////////////////////////
//////////////////////////////////////////////////////////////////////////////
+static inline AudioIoHandle getNextIoHandle() {
+ static AudioIoHandle lastHandle{};
+ return ++lastHandle;
+}
+
// This class is also used by some device tests.
template <class Stream>
class StreamHelper {
@@ -787,16 +798,13 @@
template <class Open>
void open(Open openStream, const AudioConfig& config, Result* res,
AudioConfig* suggestedConfigPtr) {
- // FIXME: Open a stream without an IOHandle
- // This is not required to be accepted by hal implementations
- AudioIoHandle ioHandle{};
AudioConfig suggestedConfig{};
bool retryWithSuggestedConfig = true;
if (suggestedConfigPtr == nullptr) {
suggestedConfigPtr = &suggestedConfig;
retryWithSuggestedConfig = false;
}
- ASSERT_OK(openStream(ioHandle, config, returnIn(*res, mStream, *suggestedConfigPtr)));
+ ASSERT_OK(openStream(mIoHandle, config, returnIn(*res, mStream, *suggestedConfigPtr)));
switch (*res) {
case Result::OK:
ASSERT_TRUE(mStream != nullptr);
@@ -806,7 +814,7 @@
ASSERT_TRUE(mStream == nullptr);
if (retryWithSuggestedConfig) {
AudioConfig suggestedConfigRetry;
- ASSERT_OK(openStream(ioHandle, *suggestedConfigPtr,
+ ASSERT_OK(openStream(mIoHandle, *suggestedConfigPtr,
returnIn(*res, mStream, suggestedConfigRetry)));
ASSERT_OK(*res);
ASSERT_TRUE(mStream != nullptr);
@@ -834,8 +842,10 @@
#endif
}
}
+ AudioIoHandle getIoHandle() const { return mIoHandle; }
private:
+ const AudioIoHandle mIoHandle = getNextIoHandle();
sp<Stream>& mStream;
};
@@ -861,7 +871,6 @@
return res;
}
- private:
void TearDown() override {
if (open) {
ASSERT_OK(closeStream());
@@ -879,6 +888,116 @@
////////////////////////////// openOutputStream //////////////////////////////
+class StreamWriter : public StreamWorker<StreamWriter> {
+ public:
+ StreamWriter(IStreamOut* stream, size_t bufferSize)
+ : mStream(stream), mBufferSize(bufferSize), mData(mBufferSize) {}
+ ~StreamWriter() {
+ stop();
+ if (mEfGroup) {
+ EventFlag::deleteEventFlag(&mEfGroup);
+ }
+ }
+
+ typedef MessageQueue<WriteCommand, ::android::hardware::kSynchronizedReadWrite> CommandMQ;
+ typedef MessageQueue<uint8_t, ::android::hardware::kSynchronizedReadWrite> DataMQ;
+ typedef MessageQueue<WriteStatus, ::android::hardware::kSynchronizedReadWrite> StatusMQ;
+
+ bool workerInit() {
+ std::unique_ptr<CommandMQ> tempCommandMQ;
+ std::unique_ptr<DataMQ> tempDataMQ;
+ std::unique_ptr<StatusMQ> tempStatusMQ;
+ Result retval;
+ Return<void> ret = mStream->prepareForWriting(
+ 1, mBufferSize,
+ [&](Result r, const CommandMQ::Descriptor& commandMQ,
+ const DataMQ::Descriptor& dataMQ, const StatusMQ::Descriptor& statusMQ,
+ const auto& /*halThreadInfo*/) {
+ retval = r;
+ if (retval == Result::OK) {
+ tempCommandMQ.reset(new CommandMQ(commandMQ));
+ tempDataMQ.reset(new DataMQ(dataMQ));
+ tempStatusMQ.reset(new StatusMQ(statusMQ));
+ if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
+ EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
+ }
+ }
+ });
+ if (!ret.isOk()) {
+ ALOGE("Transport error while calling prepareForWriting: %s", ret.description().c_str());
+ return false;
+ }
+ if (retval != Result::OK) {
+ ALOGE("Error from prepareForWriting: %d", retval);
+ return false;
+ }
+ if (!tempCommandMQ || !tempCommandMQ->isValid() || !tempDataMQ || !tempDataMQ->isValid() ||
+ !tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) {
+ ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing");
+ ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
+ "Command message queue for writing is invalid");
+ ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for writing");
+ ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(),
+ "Data message queue for writing is invalid");
+ ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for writing");
+ ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+ "Status message queue for writing is invalid");
+ ALOGE_IF(!mEfGroup, "Event flag creation for writing failed");
+ return false;
+ }
+ mCommandMQ = std::move(tempCommandMQ);
+ mDataMQ = std::move(tempDataMQ);
+ mStatusMQ = std::move(tempStatusMQ);
+ return true;
+ }
+
+ bool workerCycle() {
+ WriteCommand cmd = WriteCommand::WRITE;
+ if (!mCommandMQ->write(&cmd)) {
+ 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);
+ ALOGE_IF(!success, "data message queue write failed");
+ mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));
+
+ uint32_t efState = 0;
+ retry:
+ status_t ret =
+ mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL), &efState);
+ if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)) {
+ WriteStatus writeStatus;
+ writeStatus.retval = Result::NOT_INITIALIZED;
+ if (!mStatusMQ->read(&writeStatus)) {
+ ALOGE("status message read failed");
+ success = false;
+ }
+ if (writeStatus.retval != Result::OK) {
+ ALOGE("bad write status: %d", writeStatus.retval);
+ success = false;
+ }
+ }
+ if (ret == -EAGAIN || ret == -EINTR) {
+ // Spurious wakeup. This normally retries no more than once.
+ goto retry;
+ } else if (ret) {
+ ALOGE("bad wait status: %d", ret);
+ success = false;
+ }
+ return success;
+ }
+
+ private:
+ IStreamOut* const mStream;
+ const size_t mBufferSize;
+ std::vector<uint8_t> mData;
+ std::unique_ptr<CommandMQ> mCommandMQ;
+ std::unique_ptr<DataMQ> mDataMQ;
+ std::unique_ptr<StatusMQ> mStatusMQ;
+ EventFlag* mEfGroup = nullptr;
+};
+
class OutputStreamTest : public OpenStreamTest<IStreamOut> {
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base
@@ -954,6 +1073,121 @@
////////////////////////////// openInputStream //////////////////////////////
+class StreamReader : public StreamWorker<StreamReader> {
+ public:
+ StreamReader(IStreamIn* stream, size_t bufferSize)
+ : mStream(stream), mBufferSize(bufferSize), mData(mBufferSize) {}
+ ~StreamReader() {
+ stop();
+ if (mEfGroup) {
+ EventFlag::deleteEventFlag(&mEfGroup);
+ }
+ }
+
+ typedef MessageQueue<ReadParameters, ::android::hardware::kSynchronizedReadWrite> CommandMQ;
+ typedef MessageQueue<uint8_t, ::android::hardware::kSynchronizedReadWrite> DataMQ;
+ typedef MessageQueue<ReadStatus, ::android::hardware::kSynchronizedReadWrite> StatusMQ;
+
+ bool workerInit() {
+ std::unique_ptr<CommandMQ> tempCommandMQ;
+ std::unique_ptr<DataMQ> tempDataMQ;
+ std::unique_ptr<StatusMQ> tempStatusMQ;
+ Result retval;
+ Return<void> ret = mStream->prepareForReading(
+ 1, mBufferSize,
+ [&](Result r, const CommandMQ::Descriptor& commandMQ,
+ const DataMQ::Descriptor& dataMQ, const StatusMQ::Descriptor& statusMQ,
+ const auto& /*halThreadInfo*/) {
+ retval = r;
+ if (retval == Result::OK) {
+ tempCommandMQ.reset(new CommandMQ(commandMQ));
+ tempDataMQ.reset(new DataMQ(dataMQ));
+ tempStatusMQ.reset(new StatusMQ(statusMQ));
+ if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
+ EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
+ }
+ }
+ });
+ if (!ret.isOk()) {
+ ALOGE("Transport error while calling prepareForReading: %s", ret.description().c_str());
+ return false;
+ }
+ if (retval != Result::OK) {
+ ALOGE("Error from prepareForReading: %d", retval);
+ return false;
+ }
+ if (!tempCommandMQ || !tempCommandMQ->isValid() || !tempDataMQ || !tempDataMQ->isValid() ||
+ !tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) {
+ ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for reading");
+ ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
+ "Command message queue for reading is invalid");
+ ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for reading");
+ ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(),
+ "Data message queue for reading is invalid");
+ ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for reading");
+ ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+ "Status message queue for reading is invalid");
+ ALOGE_IF(!mEfGroup, "Event flag creation for reading failed");
+ return false;
+ }
+ mCommandMQ = std::move(tempCommandMQ);
+ mDataMQ = std::move(tempDataMQ);
+ mStatusMQ = std::move(tempStatusMQ);
+ return true;
+ }
+
+ bool workerCycle() {
+ ReadParameters params;
+ params.command = IStreamIn::ReadCommand::READ;
+ params.params.read = mBufferSize;
+ if (!mCommandMQ->write(¶ms)) {
+ ALOGE("command message queue write failed");
+ return false;
+ }
+ mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
+
+ uint32_t efState = 0;
+ bool success = true;
+ retry:
+ status_t ret =
+ mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState);
+ if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) {
+ ReadStatus readStatus;
+ readStatus.retval = Result::NOT_INITIALIZED;
+ if (!mStatusMQ->read(&readStatus)) {
+ ALOGE("status message read failed");
+ success = false;
+ }
+ if (readStatus.retval != Result::OK) {
+ ALOGE("bad read status: %d", readStatus.retval);
+ success = false;
+ }
+ const size_t dataSize = std::min(mData.size(), mDataMQ->availableToRead());
+ if (!mDataMQ->read(mData.data(), dataSize)) {
+ ALOGE("data message queue read failed");
+ success = false;
+ }
+ }
+ if (ret == -EAGAIN || ret == -EINTR) {
+ // Spurious wakeup. This normally retries no more than once.
+ goto retry;
+ } else if (ret) {
+ ALOGE("bad wait status: %d", ret);
+ success = false;
+ }
+ return success;
+ }
+
+ private:
+ IStreamIn* const mStream;
+ const size_t mBufferSize;
+ std::vector<uint8_t> mData;
+ std::unique_ptr<CommandMQ> mCommandMQ;
+ std::unique_ptr<DataMQ> mDataMQ;
+ std::unique_ptr<StatusMQ> mStatusMQ;
+ EventFlag* mEfGroup = nullptr;
+};
+
class InputStreamTest : public OpenStreamTest<IStreamIn> {
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base
@@ -1377,6 +1611,12 @@
uint64_t frames;
uint64_t time;
ASSERT_OK(stream->getCapturePosition(returnIn(res, frames, time)));
+ // Although 'getCapturePosition' is mandatory in V7, legacy implementations
+ // may return -ENOSYS (which is translated to NOT_SUPPORTED) in cases when
+ // the capture position can't be retrieved, e.g. when the stream isn't
+ // running. Because of this, we don't fail when getting NOT_SUPPORTED
+ // in this test. Behavior of 'getCapturePosition' for running streams is
+ // tested in 'PcmOnlyConfigInputStreamTest' for V7.
ASSERT_RESULT(okOrInvalidStateOrNotSupported, res);
if (res == Result::OK) {
ASSERT_EQ(0U, frames);
@@ -1560,15 +1800,19 @@
"If supported, a stream should always succeed to retrieve the "
"presentation position");
uint64_t frames;
- TimeSpec mesureTS;
- ASSERT_OK(stream->getPresentationPosition(returnIn(res, frames, mesureTS)));
+ TimeSpec measureTS;
+ ASSERT_OK(stream->getPresentationPosition(returnIn(res, frames, measureTS)));
+#if MAJOR_VERSION <= 6
if (res == Result::NOT_SUPPORTED) {
- doc::partialTest("getpresentationPosition is not supported");
+ doc::partialTest("getPresentationPosition is not supported");
return;
}
+#else
+ ASSERT_NE(Result::NOT_SUPPORTED, res) << "getPresentationPosition is mandatory in V7";
+#endif
ASSERT_EQ(0U, frames);
- if (mesureTS.tvNSec == 0 && mesureTS.tvSec == 0) {
+ if (measureTS.tvNSec == 0 && measureTS.tvSec == 0) {
// As the stream has never written a frame yet,
// the timestamp does not really have a meaning, allow to return 0
return;
@@ -1580,8 +1824,8 @@
auto toMicroSec = [](uint64_t sec, auto nsec) { return sec * 1e+6 + nsec / 1e+3; };
auto currentTime = toMicroSec(currentTS.tv_sec, currentTS.tv_nsec);
- auto mesureTime = toMicroSec(mesureTS.tvSec, mesureTS.tvNSec);
- ASSERT_PRED2([](auto c, auto m) { return c - m < 1e+6; }, currentTime, mesureTime);
+ auto measureTime = toMicroSec(measureTS.tvSec, measureTS.tvNSec);
+ ASSERT_PRED2([](auto c, auto m) { return c - m < 1e+6; }, currentTime, measureTime);
}
//////////////////////////////////////////////////////////////////////////////
diff --git a/audio/core/all-versions/vts/functional/AudioTestDefinitions.h b/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
index 5b14a21..aa67630 100644
--- a/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
+++ b/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
@@ -31,15 +31,17 @@
// Nesting a tuple in another tuple allows to use GTest Combine function to generate
// all combinations of devices and configs.
-enum { PARAM_DEVICE, PARAM_CONFIG, PARAM_FLAGS };
#if MAJOR_VERSION <= 6
+enum { PARAM_DEVICE, PARAM_CONFIG, PARAM_FLAGS };
enum { INDEX_INPUT, INDEX_OUTPUT };
using DeviceConfigParameter =
std::tuple<DeviceParameter, android::hardware::audio::common::CPP_VERSION::AudioConfig,
std::variant<android::hardware::audio::common::CPP_VERSION::AudioInputFlag,
android::hardware::audio::common::CPP_VERSION::AudioOutputFlag>>;
#elif MAJOR_VERSION >= 7
+enum { PARAM_DEVICE, PARAM_PORT_NAME, PARAM_CONFIG, PARAM_FLAGS };
using DeviceConfigParameter =
- std::tuple<DeviceParameter, android::hardware::audio::common::CPP_VERSION::AudioConfig,
+ std::tuple<DeviceParameter, std::string,
+ android::hardware::audio::common::CPP_VERSION::AudioConfig,
std::vector<android::hardware::audio::CPP_VERSION::AudioInOutFlag>>;
#endif
diff --git a/audio/core/all-versions/vts/functional/StreamWorker.h b/audio/core/all-versions/vts/functional/StreamWorker.h
new file mode 100644
index 0000000..68a8024
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/StreamWorker.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sched.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+template <typename Impl>
+class StreamWorker {
+ enum class WorkerState { STOPPED, RUNNING, PAUSE_REQUESTED, PAUSED, RESUME_REQUESTED, ERROR };
+
+ public:
+ StreamWorker() = default;
+ ~StreamWorker() { stop(); }
+ bool start() {
+ mWorker = std::thread(&StreamWorker::workerThread, this);
+ std::unique_lock<std::mutex> lock(mWorkerLock);
+ mWorkerCv.wait(lock, [&] { return mWorkerState != WorkerState::STOPPED; });
+ return mWorkerState == WorkerState::RUNNING;
+ }
+ void pause() { switchWorkerStateSync(WorkerState::RUNNING, WorkerState::PAUSE_REQUESTED); }
+ void resume() { switchWorkerStateSync(WorkerState::PAUSED, WorkerState::RESUME_REQUESTED); }
+ bool hasError() {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ return mWorkerState == WorkerState::ERROR;
+ }
+ void stop() {
+ {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ if (mWorkerState == WorkerState::STOPPED) return;
+ mWorkerState = WorkerState::STOPPED;
+ }
+ if (mWorker.joinable()) {
+ mWorker.join();
+ }
+ }
+ bool waitForAtLeastOneCycle() {
+ WorkerState newState;
+ switchWorkerStateSync(WorkerState::RUNNING, WorkerState::PAUSE_REQUESTED, &newState);
+ if (newState != WorkerState::PAUSED) return false;
+ switchWorkerStateSync(newState, WorkerState::RESUME_REQUESTED, &newState);
+ return newState == WorkerState::RUNNING;
+ }
+
+ // Methods that need to be provided by subclasses:
+ //
+ // Called once at the beginning of the thread loop. Must return
+ // 'true' to enter the thread loop, otherwise the thread loop
+ // exits and the worker switches into the 'error' state.
+ // bool workerInit();
+ //
+ // Called for each thread loop unless the thread is in 'paused' state.
+ // Must return 'true' to continue running, otherwise the thread loop
+ // exits and the worker switches into the 'error' state.
+ // bool workerCycle();
+
+ private:
+ void switchWorkerStateSync(WorkerState oldState, WorkerState newState,
+ WorkerState* finalState = nullptr) {
+ std::unique_lock<std::mutex> lock(mWorkerLock);
+ if (mWorkerState != oldState) {
+ if (finalState) *finalState = mWorkerState;
+ return;
+ }
+ mWorkerState = newState;
+ mWorkerCv.wait(lock, [&] { return mWorkerState != newState; });
+ if (finalState) *finalState = mWorkerState;
+ }
+ void workerThread() {
+ bool success = static_cast<Impl*>(this)->workerInit();
+ {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ mWorkerState = success ? WorkerState::RUNNING : WorkerState::ERROR;
+ }
+ mWorkerCv.notify_one();
+ if (!success) return;
+
+ for (WorkerState state = WorkerState::RUNNING; state != WorkerState::STOPPED;) {
+ bool needToNotify = false;
+ if (state != WorkerState::PAUSED ? static_cast<Impl*>(this)->workerCycle()
+ : (sched_yield(), true)) {
+ //
+ // Pause and resume are synchronous. One worker cycle must complete
+ // before the worker indicates a state change. This is how 'mWorkerState' and
+ // 'state' interact:
+ //
+ // mWorkerState == RUNNING
+ // client sets mWorkerState := PAUSE_REQUESTED
+ // last workerCycle gets executed, state := mWorkerState := PAUSED by us
+ // (or the workers enters the 'error' state if workerCycle fails)
+ // client gets notified about state change in any case
+ // thread is doing a busy wait while 'state == PAUSED'
+ // client sets mWorkerState := RESUME_REQUESTED
+ // state := mWorkerState (RESUME_REQUESTED)
+ // mWorkerState := RUNNING, but we don't notify the client yet
+ // first workerCycle gets executed, the code below triggers a client notification
+ // (or if workerCycle fails, worker enters 'error' state and also notifies)
+ // state := mWorkerState (RUNNING)
+ if (state == WorkerState::RESUME_REQUESTED) {
+ needToNotify = true;
+ }
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ state = mWorkerState;
+ if (mWorkerState == WorkerState::PAUSE_REQUESTED) {
+ state = mWorkerState = WorkerState::PAUSED;
+ needToNotify = true;
+ } else if (mWorkerState == WorkerState::RESUME_REQUESTED) {
+ mWorkerState = WorkerState::RUNNING;
+ }
+ } else {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ if (state == WorkerState::RESUME_REQUESTED ||
+ mWorkerState == WorkerState::PAUSE_REQUESTED) {
+ needToNotify = true;
+ }
+ mWorkerState = WorkerState::ERROR;
+ state = WorkerState::STOPPED;
+ }
+ if (needToNotify) {
+ mWorkerCv.notify_one();
+ }
+ }
+ }
+
+ std::thread mWorker;
+ std::mutex mWorkerLock;
+ std::condition_variable mWorkerCv;
+ WorkerState mWorkerState = WorkerState::STOPPED; // GUARDED_BY(mWorkerLock);
+};
diff --git a/audio/core/all-versions/vts/functional/tests/streamworker_tests.cpp b/audio/core/all-versions/vts/functional/tests/streamworker_tests.cpp
new file mode 100644
index 0000000..75116af
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/tests/streamworker_tests.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "StreamWorker.h"
+
+#include <sched.h>
+#include <unistd.h>
+#include <atomic>
+
+#include <gtest/gtest.h>
+#define LOG_TAG "StreamWorker_Test"
+#include <log/log.h>
+
+struct TestStream {
+ std::atomic<bool> error = false;
+};
+
+class TestWorker : public StreamWorker<TestWorker> {
+ public:
+ // Use nullptr to test error reporting from the worker thread.
+ explicit TestWorker(TestStream* stream) : mStream(stream) {}
+
+ void ensureWorkerCycled() {
+ const size_t cyclesBefore = mWorkerCycles;
+ while (mWorkerCycles == cyclesBefore && !hasError()) {
+ sched_yield();
+ }
+ }
+ size_t getWorkerCycles() const { return mWorkerCycles; }
+ bool hasWorkerCycleCalled() const { return mWorkerCycles != 0; }
+ bool hasNoWorkerCycleCalled(useconds_t usec) {
+ const size_t cyclesBefore = mWorkerCycles;
+ usleep(usec);
+ return mWorkerCycles == cyclesBefore;
+ }
+
+ bool workerInit() { return mStream; }
+ bool workerCycle() {
+ do {
+ mWorkerCycles++;
+ } while (mWorkerCycles == 0);
+ return !mStream->error;
+ }
+
+ private:
+ TestStream* const mStream;
+ std::atomic<size_t> mWorkerCycles = 0;
+};
+
+// The parameter specifies whether an extra call to 'stop' is made at the end.
+class StreamWorkerInvalidTest : public testing::TestWithParam<bool> {
+ public:
+ StreamWorkerInvalidTest() : StreamWorkerInvalidTest(nullptr) {}
+ void TearDown() override {
+ if (GetParam()) {
+ worker.stop();
+ }
+ }
+
+ protected:
+ StreamWorkerInvalidTest(TestStream* stream) : testing::TestWithParam<bool>(), worker(stream) {}
+ TestWorker worker;
+};
+
+TEST_P(StreamWorkerInvalidTest, Uninitialized) {
+ EXPECT_FALSE(worker.hasWorkerCycleCalled());
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, UninitializedPauseIgnored) {
+ EXPECT_FALSE(worker.hasError());
+ worker.pause();
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, UninitializedResumeIgnored) {
+ EXPECT_FALSE(worker.hasError());
+ worker.resume();
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, Start) {
+ EXPECT_FALSE(worker.start());
+ EXPECT_FALSE(worker.hasWorkerCycleCalled());
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, PauseIgnored) {
+ EXPECT_FALSE(worker.start());
+ EXPECT_TRUE(worker.hasError());
+ worker.pause();
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, ResumeIgnored) {
+ EXPECT_FALSE(worker.start());
+ EXPECT_TRUE(worker.hasError());
+ worker.resume();
+ EXPECT_TRUE(worker.hasError());
+}
+
+INSTANTIATE_TEST_SUITE_P(StreamWorkerInvalid, StreamWorkerInvalidTest, testing::Bool());
+
+class StreamWorkerTest : public StreamWorkerInvalidTest {
+ public:
+ StreamWorkerTest() : StreamWorkerInvalidTest(&stream) {}
+
+ protected:
+ TestStream stream;
+};
+
+static constexpr unsigned kWorkerIdleCheckTime = 50 * 1000;
+
+TEST_P(StreamWorkerTest, Uninitialized) {
+ EXPECT_FALSE(worker.hasWorkerCycleCalled());
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, Start) {
+ ASSERT_TRUE(worker.start());
+ worker.ensureWorkerCycled();
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, WorkerError) {
+ ASSERT_TRUE(worker.start());
+ stream.error = true;
+ worker.ensureWorkerCycled();
+ EXPECT_TRUE(worker.hasError());
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+}
+
+TEST_P(StreamWorkerTest, PauseResume) {
+ ASSERT_TRUE(worker.start());
+ worker.ensureWorkerCycled();
+ EXPECT_FALSE(worker.hasError());
+ worker.pause();
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+ EXPECT_FALSE(worker.hasError());
+ const size_t workerCyclesBefore = worker.getWorkerCycles();
+ worker.resume();
+ // 'resume' is synchronous and returns after the worker has looped at least once.
+ EXPECT_GT(worker.getWorkerCycles(), workerCyclesBefore);
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, StopPaused) {
+ ASSERT_TRUE(worker.start());
+ worker.ensureWorkerCycled();
+ EXPECT_FALSE(worker.hasError());
+ worker.pause();
+ worker.stop();
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, PauseAfterErrorIgnored) {
+ ASSERT_TRUE(worker.start());
+ stream.error = true;
+ worker.ensureWorkerCycled();
+ EXPECT_TRUE(worker.hasError());
+ worker.pause();
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, ResumeAfterErrorIgnored) {
+ ASSERT_TRUE(worker.start());
+ stream.error = true;
+ worker.ensureWorkerCycled();
+ EXPECT_TRUE(worker.hasError());
+ worker.resume();
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, WorkerErrorOnResume) {
+ ASSERT_TRUE(worker.start());
+ worker.ensureWorkerCycled();
+ EXPECT_FALSE(worker.hasError());
+ worker.pause();
+ EXPECT_FALSE(worker.hasError());
+ stream.error = true;
+ EXPECT_FALSE(worker.hasError());
+ worker.resume();
+ worker.ensureWorkerCycled();
+ EXPECT_TRUE(worker.hasError());
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+}
+
+TEST_P(StreamWorkerTest, WaitForAtLeastOneCycle) {
+ ASSERT_TRUE(worker.start());
+ const size_t workerCyclesBefore = worker.getWorkerCycles();
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_GT(worker.getWorkerCycles(), workerCyclesBefore);
+}
+
+TEST_P(StreamWorkerTest, WaitForAtLeastOneCycleError) {
+ ASSERT_TRUE(worker.start());
+ stream.error = true;
+ EXPECT_FALSE(worker.waitForAtLeastOneCycle());
+}
+
+INSTANTIATE_TEST_SUITE_P(StreamWorker, StreamWorkerTest, testing::Bool());
diff --git a/neuralnetworks/1.0/types.t b/neuralnetworks/1.0/types.t
index d7b26aa..be1ee07 100644
--- a/neuralnetworks/1.0/types.t
+++ b/neuralnetworks/1.0/types.t
@@ -63,361 +63,25 @@
RELU6 = 3,
};
-/**
- * How an operand is used.
- */
-enum OperandLifeTime : int32_t {
- /**
- * The operand is internal to the model. It's created by an operation and
- * consumed by other operations. It must be an output operand of
- * exactly one operation.
- */
- TEMPORARY_VARIABLE,
+%insert OperandLifeTime
- /**
- * The operand is an input of the model. It must not be an output
- * operand of any operation.
- *
- * An operand can't be both input and output of a model.
- */
- MODEL_INPUT,
+%insert DeviceStatus
- /**
- * The operand is an output of the model. It must be an output
- * operand of exactly one operation.
- *
- * An operand can't be both input and output of a model.
- */
- MODEL_OUTPUT,
+%insert PerformanceInfo
- /**
- * The operand is a constant found in Model.operandValues. It must
- * not be an output operand of any operation.
- */
- CONSTANT_COPY,
+%insert Capabilities
- /**
- * The operand is a constant that was specified via a Memory
- * object. It must not be an output operand of any operation.
- */
- CONSTANT_REFERENCE,
+%insert DataLocation
- /**
- * The operand does not have a value. This is valid only for optional
- * arguments of operations.
- */
- NO_VALUE,
-};
+%insert Operand
-/**
- * Status of a device.
- */
-enum DeviceStatus : int32_t {
- AVAILABLE,
- BUSY,
- OFFLINE,
- UNKNOWN,
-};
+%insert Operation
-/**
- * Performance information for the reference workload.
- *
- * Used by a driver to report its performance characteristics.
- */
-struct PerformanceInfo {
- /**
- * Ratio of the time taken by the driver to execute the
- * workload compared to the time the CPU would take for the
- * same workload. A lower number is better.
- */
- float execTime;
+%insert Model
- /**
- * Ratio of the energy used by the driver compared to what
- * the CPU would use for doing the same workload. A lower number
- * is better.
- */
- float powerUsage;
-};
+%insert RequestArgument
-/**
- * The capabilities of a driver.
- */
-struct Capabilities {
- /**
- * Driver performance when operating on float32 data.
- */
- PerformanceInfo float32Performance;
-
- /**
- * Driver performance when operating on asymmetric 8-bit quantized data.
- */
- PerformanceInfo quantized8Performance;
-};
-
-/**
- * Describes the location of a data object.
- */
-struct DataLocation {
- /**
- * The index of the memory pool where this location is found.
- */
- uint32_t poolIndex;
-
- /**
- * Offset in bytes from the start of the pool.
- */
- uint32_t offset;
-
- /**
- * The length of the data in bytes.
- */
- uint32_t length;
-};
-
-/**
- * Describes one operand of the model's graph.
- */
-struct Operand {
- /**
- * Data type of the operand.
- */
- OperandType type;
-
- /**
- * Dimensions of the operand.
- *
- * For a scalar operand, dimensions.size() must be 0.
- *
- * For a tensor operand, dimensions.size() must be at least 1;
- * however, any of the dimensions may be unspecified.
- *
- * A tensor operand with all dimensions specified has "fully
- * specified" dimensions. Whenever possible (i.e., whenever the
- * dimensions are known at model construction time), a tensor
- * operand should have (but is not required to have) fully
- * specified dimensions, in order to enable the best possible
- * performance.
- *
- * If a tensor operand's dimensions are not fully specified, the
- * dimensions of the operand are deduced from the operand
- * dimensions and values of the operation for which that operand
- * is an output.
- *
- * In the following situations, a tensor operand's dimensions must
- * be fully specified:
- *
- * . The operand has lifetime CONSTANT_COPY or
- * CONSTANT_REFERENCE.
- *
- * . The operand has lifetime MODEL_INPUT or MODEL_OUTPUT. Fully
- * specified dimensions must either be present in the
- * Operand or they must be provided in the corresponding
- * RequestArgument.
- * EXCEPTION: If the input or output is optional and omitted
- * (by setting the hasNoValue field of the corresponding
- * RequestArgument to true) then it need not have fully
- * specified dimensions.
- *
- * A tensor operand with some number of unspecified dimensions is
- * represented by setting each unspecified dimension to 0.
- */
- vec<uint32_t> dimensions;
-
- /**
- * The number of times this operand appears as an operation input.
- *
- * (For example, if this operand appears once in one operation's
- * input list, and three times in another operation's input list,
- * then numberOfConsumers = 4.)
- */
- uint32_t numberOfConsumers;
-
- /**
- * Quantized scale of the operand.
- *
- * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or
- * TENSOR_INT32.
- */
- float scale;
-
- /**
- * Quantized zero-point offset of the operand.
- *
- * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM.
- */
- int32_t zeroPoint;
-
- /**
- * How the operand is used.
- */
- OperandLifeTime lifetime;
-
- /**
- * Where to find the data for this operand.
- * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_OUTPUT, or
- * NO_VALUE:
- * - All the fields must be 0.
- * If the lifetime is CONSTANT_COPY:
- * - location.poolIndex is 0.
- * - location.offset is the offset in bytes into Model.operandValues.
- * - location.length is set.
- * If the lifetime is CONSTANT_REFERENCE:
- * - location.poolIndex is set.
- * - location.offset is the offset in bytes into the specified pool.
- * - location.length is set.
- */
- DataLocation location;
-};
-
-/**
- * Describes one operation of the model's graph.
- */
-struct Operation {
- /**
- * The operation type.
- */
- OperationType type;
-
- /**
- * Describes the table that contains the indexes of the inputs of the
- * operation. The offset is the index in the operandIndexes table.
- */
- vec<uint32_t> inputs;
-
- /**
- * Describes the table that contains the indexes of the outputs of the
- * operation. The offset is the index in the operandIndexes table.
- */
- vec<uint32_t> outputs;
-};
-
-/**
- * A Neural Network Model.
- *
- * This includes not only the execution graph, but also constant data such as
- * weights or scalars added at construction time. The only information that
- * might not be known is the shape of the input tensors.
- */
-struct Model {
- /**
- * All operands included in the model.
- */
- vec<Operand> operands;
-
- /**
- * All operations included in the model.
- *
- * The operations are sorted into execution order. Every operand
- * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be
- * written before it is read.
- */
- vec<Operation> operations;
-
- /**
- * Input indexes of the model. There must be at least one.
- *
- * Each value corresponds to the index of the operand in "operands".
- */
- vec<uint32_t> inputIndexes;
-
- /**
- * Output indexes of the model. There must be at least one.
- *
- * Each value corresponds to the index of the operand in "operands".
- */
- vec<uint32_t> outputIndexes;
-
- /**
- * A byte buffer containing operand data that were copied into the model.
- *
- * An operand's value must be located here if and only if Operand::lifetime
- * equals OperandLifeTime::CONSTANT_COPY.
- */
- vec<uint8_t> operandValues;
-
- /**
- * A collection of shared memory pools containing operand values.
- *
- * An operand's value must be located here if and only if Operand::lifetime
- * equals OperandLifeTime::CONSTANT_REFERENCE.
- */
- vec<memory> pools;
-};
-
-/**
- * Metadata information specifying the location of the input or output data and
- * any updates to the input or output operand.
- */
-struct RequestArgument {
- /**
- * If true, the argument does not have a value. This can be used for
- * operations that take optional arguments. If true, the fields of location
- * are set to 0 and the dimensions vector is left empty.
- */
- bool hasNoValue;
-
- /**
- * The location within one of the memory pools passed in the Request.
- */
- DataLocation location;
-
- /**
- * Updated dimension information.
- *
- * If dimensions.size() > 0, dimension information was provided
- * along with the argument. This can be the case for models that
- * accept inputs of varying size. This can't change the rank, just
- * the value of the dimensions that were unspecified in the
- * model. If dimensions.size() > 0, then all dimensions must be
- * specified here; and any dimension that was specified in the
- * model must have the same value here.
- *
- * If the dimensions in the model are not fully specified, then
- * they must be fully specified here, unless hasNoValue is set to
- * true. If the dimensions in the model are fully specified, then
- * either dimensions.size() may be 0, or the dimensions in the
- * model must be identical to the dimensions here.
- */
- vec<uint32_t> dimensions;
-};
-
-/**
- * Inputs to be sent to and outputs to be retrieved from a prepared model.
- *
- * A Request serves two primary tasks:
- * 1) Provides the input and output data to be used when executing the model.
- * 2) Specifies any updates to the input operand metadata that were left
- * unspecified at model preparation time.
- *
- * An output must not overlap with any other output, with an input, or
- * with an operand of lifetime CONSTANT_REFERENCE.
- */
-struct Request {
- /**
- * Input data and information to be used in the execution of a prepared
- * model.
- *
- * The index of the input corresponds to the index in Model.inputIndexes.
- * E.g., input[i] corresponds to Model.inputIndexes[i].
- */
- vec<RequestArgument> inputs;
-
- /**
- * Output data and information to be used in the execution of a prepared
- * model.
- *
- * The index of the output corresponds to the index in Model.outputIndexes.
- * E.g., output[i] corresponds to Model.outputIndexes[i].
- */
- vec<RequestArgument> outputs;
-
- /**
- * A collection of shared memory pools containing operand data for both the
- * inputs and the outputs to a model.
- */
- vec<memory> pools;
-};
+%insert Request
/**
* Return status of a function.
diff --git a/neuralnetworks/1.1/types.t b/neuralnetworks/1.1/types.t
index 75ac2e7..8c22b30 100644
--- a/neuralnetworks/1.1/types.t
+++ b/neuralnetworks/1.1/types.t
@@ -31,128 +31,10 @@
%insert Operation_1.1
};
-/**
- * The capabilities of a driver.
- */
-struct Capabilities {
- /**
- * Driver performance when operating on float32 data.
- */
- PerformanceInfo float32Performance;
+%insert Capabilities
- /**
- * Driver performance when operating on asymmetric 8-bit quantized data.
- */
- PerformanceInfo quantized8Performance;
+%insert Operation
- /**
- * Driver performance when operating on float32 data but performing
- * calculations with range and/or precision as low as that of the IEEE
- * 754 16-bit floating-point format.
- */
- PerformanceInfo relaxedFloat32toFloat16Performance;
-};
+%insert Model
-/**
- * Describes one operation of the model's graph.
- */
-struct Operation {
- /**
- * The operation type.
- */
- OperationType type;
-
- /**
- * Describes the table that contains the indexes of the inputs of the
- * operation. The offset is the index in the operandIndexes table.
- */
- vec<uint32_t> inputs;
-
- /**
- * Describes the table that contains the indexes of the outputs of the
- * operation. The offset is the index in the operandIndexes table.
- */
- vec<uint32_t> outputs;
-};
-
-/**
- * A Neural Network Model.
- *
- * This includes not only the execution graph, but also constant data such as
- * weights or scalars added at construction time. The only information that
- * may not be known is the shape of the input tensors.
- */
-struct Model {
- /**
- * All operands included in the model.
- */
- vec<Operand> operands;
-
- /**
- * All operations included in the model.
- *
- * The operations are sorted into execution order. Every operand
- * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be
- * written before it is read.
- */
- vec<Operation> operations;
-
- /**
- * Input indexes of the model. There must be at least one.
- *
- * Each value corresponds to the index of the operand in "operands".
- */
- vec<uint32_t> inputIndexes;
-
- /**
- * Output indexes of the model. There must be at least one.
- *
- * Each value corresponds to the index of the operand in "operands".
- */
- vec<uint32_t> outputIndexes;
-
- /**
- * A byte buffer containing operand data that were copied into the model.
- *
- * An operand's value must be located here if and only if Operand::lifetime
- * equals OperandLifeTime::CONSTANT_COPY.
- */
- vec<uint8_t> operandValues;
-
- /**
- * A collection of shared memory pools containing operand values.
- *
- * An operand's value must be located here if and only if Operand::lifetime
- * equals OperandLifeTime::CONSTANT_REFERENCE.
- */
- vec<memory> pools;
-
- /**
- * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or
- * precision as low as that of the IEEE 754 16-bit floating-point format.
- * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the
- * range and precision of the IEEE 754 32-bit floating-point format.
- */
- bool relaxComputationFloat32toFloat16;
-};
-
-/**
- * Execution preferences.
- */
-enum ExecutionPreference : int32_t {
- /**
- * Prefer executing in a way that minimizes battery drain.
- * This is desirable for compilations that will be executed often.
- */
- LOW_POWER = 0,
- /**
- * Prefer returning a single answer as fast as possible, even if this causes
- * more power consumption.
- */
- FAST_SINGLE_ANSWER = 1,
- /**
- * Prefer maximizing the throughput of successive frames, for example when
- * processing successive frames coming from the camera.
- */
- SUSTAINED_SPEED = 2,
-};
+%insert ExecutionPreference
diff --git a/neuralnetworks/1.2/types.t b/neuralnetworks/1.2/types.t
index 4c9fd02..b490f7f 100644
--- a/neuralnetworks/1.2/types.t
+++ b/neuralnetworks/1.2/types.t
@@ -97,379 +97,23 @@
BASE_MAX = 0xFFFF,
};
-/**
- * Device types.
- *
- * The type of NNAPI device.
- */
-enum DeviceType : int32_t {
- // Leaving 0 unused as it means unknown type in NDK NNAPI. There is no
- // HAL equivalent of unknown type and a 1.2 HAL implementation must belong
- // to one of the categories below.
- /** The device does not fall into any category below. */
- OTHER = 1,
- /** The device runs NNAPI models on single or multi-core CPU. */
- CPU = 2,
- /** The device can run NNAPI models and also accelerate graphics APIs such
- * as OpenGL ES and Vulkan. */
- GPU = 3,
- /** Dedicated accelerator for Machine Learning workloads. */
- ACCELERATOR = 4,
-};
+%insert DeviceType
-/**
- * The capabilities of a driver.
- *
- * Performance of an operation comes from the type of its first operand.
- * This represents performance for non extension operand types.
- */
-struct Capabilities {
- /**
- * Driver performance when operating on float32 data but performing
- * calculations with range and/or precision as low as that of the IEEE
- * 754 16-bit floating-point format.
- */
- PerformanceInfo relaxedFloat32toFloat16PerformanceScalar;
- PerformanceInfo relaxedFloat32toFloat16PerformanceTensor;
+%insert Capabilities
- /**
- * Driver performance when operating on a particular data type.
- * In the case of float32 data, this is used when the calculations
- * are not relaxed.
- */
- struct OperandPerformance {
- OperandType type;
- PerformanceInfo info;
- };
+%insert Operation
- /**
- * Performance by operand type. Must be sorted by OperandType.
- * If a particular OperandType is not present in operandPerformance,
- * its performance is treated as { .execTime = FLT_MAX, .powerUsage = FLT_MAX }.
- */
- vec<OperandPerformance> operandPerformance;
-};
+%insert SymmPerChannelQuantParams
-/**
- * Describes one operation of the model's graph.
- */
-struct Operation {
- /**
- * The operation type.
- *
- * Besides the values listed in {@link OperationType}, any value above
- * {@link OperationTypeRange::BASE_MAX} is possible and should be interpreted
- * as an extension type according to {@link Model::extensionNameToPrefix}.
- */
- OperationType type;
+%insert Operand
- /**
- * Describes the table that contains the indexes of the inputs of the
- * operation. The offset is the index in the operandIndexes table.
- */
- vec<uint32_t> inputs;
+%insert Model
- /**
- * Describes the table that contains the indexes of the outputs of the
- * operation. The offset is the index in the operandIndexes table.
- */
- vec<uint32_t> outputs;
-};
+%insert OutputShape
-/**
- * Parameters for TENSOR_QUANT8_SYMM_PER_CHANNEL operand.
- */
-struct SymmPerChannelQuantParams {
- /** Array of scaling values for each channel. Each value must be greater than zero. */
- vec<float> scales;
- /** Index of the channel dimension */
- uint32_t channelDim;
-};
+%insert MeasureTiming
-/**
- * Describes one operand of the model's graph.
- */
-struct Operand {
- /**
- * The data type.
- *
- * Besides the values listed in {@link OperandType}, any value above
- * {@link OperandTypeRange::BASE_MAX} is possible and should be interpreted
- * as an extension type according to {@link Model::extensionNameToPrefix}.
- */
- OperandType type;
-
- /**
- * Dimensions of the operand.
- *
- * For a scalar operand, dimensions.size() must be 0.
- *
- * A tensor operand with all dimensions specified has "fully
- * specified" dimensions. Whenever possible (i.e., whenever the
- * dimensions are known at model construction time), a tensor
- * operand should have (but is not required to have) fully
- * specified dimensions, in order to enable the best possible
- * performance.
- *
- * If a tensor operand's dimensions are not fully specified, the
- * dimensions of the operand are deduced from the operand
- * dimensions and values of the operation for which that operand
- * is an output.
- *
- * In the following situations, a tensor operand's dimensions must
- * be fully specified:
- *
- * . The operand has lifetime CONSTANT_COPY or
- * CONSTANT_REFERENCE.
- *
- * . The operand has lifetime MODEL_INPUT. Fully
- * specified dimensions must either be present in the
- * Operand or they must be provided in the corresponding
- * RequestArgument.
- * EXCEPTION: If the input is optional and omitted
- * (by setting the hasNoValue field of the corresponding
- * RequestArgument to true) then it need not have fully
- * specified dimensions.
- *
- * A tensor operand with some number of unspecified dimensions is
- * represented by setting each unspecified dimension to 0.
- *
- * A tensor operand with unspecified rank is represented by providing
- * an empty dimensions vector.
- */
- vec<uint32_t> dimensions;
-
- /**
- * The number of times this operand appears as an operation input.
- *
- * (For example, if this operand appears once in one operation's
- * input list, and three times in another operation's input list,
- * then numberOfConsumers = 4.)
- */
- uint32_t numberOfConsumers;
-
- /**
- * Quantized scale of the operand.
- *
- * Must be 0 when not applicable to an operand type.
- *
- * See {@link OperandType}.
- */
- float scale;
-
- /**
- * Quantized zero-point offset of the operand.
- *
- * Must be 0 when not applicable to an operand type.
- *
- * See {@link OperandType}.
- */
- int32_t zeroPoint;
-
- /**
- * How the operand is used.
- */
- OperandLifeTime lifetime;
-
- /**
- * Where to find the data for this operand.
- * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_OUTPUT, or
- * NO_VALUE:
- * - All the fields must be 0.
- * If the lifetime is CONSTANT_COPY:
- * - location.poolIndex is 0.
- * - location.offset is the offset in bytes into Model.operandValues.
- * - location.length is set.
- * If the lifetime is CONSTANT_REFERENCE:
- * - location.poolIndex is set.
- * - location.offset is the offset in bytes into the specified pool.
- * - location.length is set.
- */
- DataLocation location;
-
- /**
- * Additional parameters specific to a particular operand type.
- */
- safe_union ExtraParams {
- /**
- * No additional parameters.
- */
- Monostate none;
-
- /**
- * Symmetric per-channel quantization parameters.
- *
- * Only applicable to operands of type TENSOR_QUANT8_SYMM_PER_CHANNEL.
- */
- SymmPerChannelQuantParams channelQuant;
-
- /**
- * Extension operand parameters.
- *
- * The framework treats this as an opaque data blob.
- * The format is up to individual extensions.
- */
- vec<uint8_t> extension;
- } extraParams;
-};
-
-/**
- * A Neural Network Model.
- *
- * This includes not only the execution graph, but also constant data such as
- * weights or scalars added at construction time. The only information that
- * may not be known is the shape of the input tensors.
- */
-struct Model {
- /**
- * All operands included in the model.
- */
- vec<Operand> operands;
-
- /**
- * All operations included in the model.
- *
- * The operations are sorted into execution order. Every operand
- * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be
- * written before it is read.
- */
- vec<Operation> operations;
-
- /**
- * Input indexes of the model. There must be at least one.
- *
- * Each value corresponds to the index of the operand in "operands".
- */
- vec<uint32_t> inputIndexes;
-
- /**
- * Output indexes of the model. There must be at least one.
- *
- * Each value corresponds to the index of the operand in "operands".
- */
- vec<uint32_t> outputIndexes;
-
- /**
- * A byte buffer containing operand data that were copied into the model.
- *
- * An operand's value must be located here if and only if Operand::lifetime
- * equals OperandLifeTime::CONSTANT_COPY.
- */
- vec<uint8_t> operandValues;
-
- /**
- * A collection of shared memory pools containing operand values.
- *
- * An operand's value must be located here if and only if Operand::lifetime
- * equals OperandLifeTime::CONSTANT_REFERENCE.
- */
- vec<memory> pools;
-
- /**
- * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or
- * precision as low as that of the IEEE 754 16-bit floating-point format.
- * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the
- * range and precision of the IEEE 754 32-bit floating-point format.
- */
- bool relaxComputationFloat32toFloat16;
-
- /**
- * The mapping between extension names and prefixes of operand and
- * operation type values.
- *
- * An operand or operation whose numeric type value is above
- * {@link OperandTypeRange::BASE_MAX} or
- * {@link OperationTypeRange::BASE_MAX} respectively should be interpreted
- * as an extension operand. The low
- * {@link Model::ExtensionTypeEncoding::LOW_BITS_TYPE} bits of the value
- * correspond to the type ID within the extension and the high
- * {@link Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX} bits encode
- * the "prefix", which maps uniquely to the extension name.
- *
- * For example, if a model contains an operation whose value is
- * 0xAAAABBBB and extensionNameToPrefix contains an entry with
- * prefix=0xAAAA and name="vendor.test.test_extension", then
- * the operation should be interpreted as the operation 0xBBBB
- * of the extension named vendor.test.test_extension.
- *
- * This is a one-to-one correspondence. That is, there must be at most one
- * prefix corresponding to each extension name and at most one extension
- * name corresponding to each prefix.
- */
- vec<ExtensionNameAndPrefix> extensionNameToPrefix;
-
- /**
- * A correspondence between an extension name and a prefix of operand and
- * operation type values.
- */
- struct ExtensionNameAndPrefix {
- /**
- * The extension name.
- *
- * See {@link Extension::name} for the format specification.
- */
- string name;
-
- /**
- * The unique extension identifier within the model.
- *
- * See {@link Model::extensionNameToPrefix}.
- */
- uint16_t prefix;
- };
-
- /**
- * Numeric values of extension operand and operation types have the
- * following structure:
- * - 16 high bits represent the "prefix", which corresponds uniquely to the
- * extension name.
- * - 16 low bits represent the type ID within the extension.
- */
- enum ExtensionTypeEncoding : uint8_t {
- HIGH_BITS_PREFIX = 16,
- LOW_BITS_TYPE = 16,
- };
-};
-
-/**
- * Describes the shape information of an output operand after execution.
- */
-struct OutputShape {
- /**
- * Dimensions of the operand.
- */
- vec<uint32_t> dimensions;
-
- /**
- * Whether the provided buffer size is sufficient for the output.
- */
- bool isSufficient;
-};
-
-/**
- * Specifies whether or not to measure timing information during execution.
- */
-enum MeasureTiming : int32_t {
- NO = 0,
- YES = 1,
-};
-
-/**
-
- * Timing information measured during execution. Each time is a duration from
- * the beginning of some task to the end of that task, including time when that
- * task is not active (for example, preempted by some other task, or
- * waiting for some resource to become available).
- *
- * Times are measured in microseconds.
- * When a time is not available, it must be reported as UINT64_MAX.
- */
-struct Timing {
- /** Execution time on device (not driver, which runs on host processor). */
- uint64_t timeOnDevice;
- /** Execution time in driver (including time on device). */
- uint64_t timeInDriver;
-};
+%insert Timing
/**
* FmqRequestDatum is a single element of a serialized representation of an
@@ -683,46 +327,4 @@
Timing executionTiming;
};
-/**
- * Information about an extension.
- */
-struct Extension {
- /**
- * The extension name.
- *
- * The name must consist of lowercase latin letters, numbers, periods, and
- * underscore signs. The name must contain at least one period.
- *
- * The name must start with the reverse domain name of the vendor.
- *
- * Example: com.google.test_extension
- */
- string name;
-
- /**
- * Information about an extension operand type.
- */
- struct OperandTypeInformation {
- /**
- * The extension operand type.
- */
- uint16_t type;
-
- /**
- * Indicates whether the extension operand type represents a tensor or
- * a scalar.
- */
- bool isTensor;
-
- /**
- * The byte size of the operand (if scalar) or of a single element (if
- * tensor).
- */
- uint32_t byteSize;
- };
-
- /**
- * Information about operand types defined by the extension.
- */
- vec<OperandTypeInformation> operandTypes;
-};
+%insert Extension
diff --git a/neuralnetworks/1.3/types.t b/neuralnetworks/1.3/types.t
index 9f69c9e..96d1a1b 100644
--- a/neuralnetworks/1.3/types.t
+++ b/neuralnetworks/1.3/types.t
@@ -90,459 +90,25 @@
BASE_MAX = 0xFFFF,
};
-/**
- * Priority given to a prepared model for execution.
- */
-enum Priority : int32_t {
- LOW,
- MEDIUM,
- HIGH,
-};
+%insert Priority
-/**
- * The capabilities of a driver.
- *
- * This represents performance of non-extension operations.
- *
- * Performance of an operation other than {@link OperationType::IF} and
- * {@link OperationType::WHILE} comes from the type of its first operand.
- */
-struct Capabilities {
- /**
- * Driver performance when operating on float32 data but performing
- * calculations with range and/or precision as low as that of the IEEE
- * 754 16-bit floating-point format.
- */
- PerformanceInfo relaxedFloat32toFloat16PerformanceScalar;
- PerformanceInfo relaxedFloat32toFloat16PerformanceTensor;
+%insert Capabilities
- /**
- * Driver performance when operating on a particular data type.
- * In the case of float32 data, this is used when the calculations
- * are not relaxed.
- */
- struct OperandPerformance {
- OperandType type;
- PerformanceInfo info;
- };
+%insert Operation
- /**
- * Performance by operand type. Must be sorted by OperandType.
- *
- * If a particular {@link OperandType} is not present in operandPerformance,
- * its performance is treated as
- * { .execTime = FLT_MAX, .powerUsage = FLT_MAX }.
- *
- * Performance does not apply to {@link OperandType::SUBGRAPH}, and a driver
- * must not report operand performance for {@link OperandType::SUBGRAPH}.
- */
- vec<OperandPerformance> operandPerformance;
+%insert OperandLifeTime
- /**
- * Performance of an {@link OperationType::IF} operation is the sum of
- * {@link Capabilities::ifPerformance} and the mean of performance for the
- * two branch subgraphs, where performance for a subgraph is the sum of the
- * performance of all operations within the subgraph.
- */
- PerformanceInfo ifPerformance;
+%insert Operand
- /**
- * Performance of a {@link OperationType::WHILE} operation is the sum of
- * {@link Capabilities::whilePerformance}, performance for the condition
- * subgraph and performance for the body subgraph, where performance for a
- * subgraph is the sum of the performance of all operations within the
- * subgraph.
- */
- PerformanceInfo whilePerformance;
-};
+%insert Model
-/**
- * Describes one operation of the model's graph.
- */
-struct Operation {
- /**
- * The operation type.
- *
- * Besides the values listed in {@link OperationType}, any value above
- * {@link OperationTypeRange::BASE_MAX} is possible and should be interpreted
- * as an extension type according to {@link Model::extensionNameToPrefix}.
- */
- OperationType type;
+%insert Subgraph
- /**
- * Describes the table that contains the indexes of the inputs of the
- * operation. The offset is the index in the operandIndexes table.
- */
- vec<uint32_t> inputs;
+%insert BufferDesc
- /**
- * Describes the table that contains the indexes of the outputs of the
- * operation. The offset is the index in the operandIndexes table.
- */
- vec<uint32_t> outputs;
-};
+%insert BufferRole
-/**
- * How an operand is used.
- */
-enum OperandLifeTime : int32_t {
- /**
- * The operand is internal to the model. It's created by an operation and
- * consumed by other operations. It must be an output operand of
- * exactly one operation.
- */
- TEMPORARY_VARIABLE,
-
- /**
- * The operand is an input of a subgraph. It must not be an output
- * operand of any operation.
- *
- * An operand can't be both input and output of a subgraph.
- */
- SUBGRAPH_INPUT,
-
- /**
- * The operand is an output of a subgraph. It must be an output
- * operand of exactly one operation.
- *
- * An operand can't be both input and output of a subgraph.
- */
- SUBGRAPH_OUTPUT,
-
- /**
- * The operand is a constant found in Model.operandValues. It must
- * not be an output operand of any operation.
- */
- CONSTANT_COPY,
-
- /**
- * The operand is a constant that was specified via a Memory
- * object. It must not be an output operand of any operation.
- */
- CONSTANT_REFERENCE,
-
- /**
- * The operand does not have a value. This is valid only for optional
- * arguments of operations.
- */
- NO_VALUE,
-
- /**
- * The operand is a reference to a subgraph. It must be an input to one
- * or more {@link OperationType::IF} or {@link OperationType::WHILE}
- * operations.
- */
- SUBGRAPH,
-};
-
-/**
- * Describes one operand of the model's graph.
- */
-struct Operand {
- /**
- * The data type.
- *
- * Besides the values listed in {@link OperandType}, any value above
- * {@link OperandTypeRange::BASE_MAX} is possible and should be interpreted
- * as an extension type according to {@link Model::extensionNameToPrefix}.
- */
- OperandType type;
-
- /**
- * Dimensions of the operand.
- *
- * For a scalar operand, dimensions.size() must be 0.
- *
- * A tensor operand with all dimensions specified has "fully
- * specified" dimensions. Whenever possible (i.e., whenever the
- * dimensions are known at model construction time), a tensor
- * operand should have (but is not required to have) fully
- * specified dimensions, in order to enable the best possible
- * performance.
- *
- * If a tensor operand's dimensions are not fully specified, the
- * dimensions of the operand are deduced from the operand
- * dimensions and values of the operation for which that operand
- * is an output or from the corresponding {@link OperationType::IF} or
- * {@link OperationType::WHILE} operation input operand dimensions in the
- * case of referenced subgraph input operands.
- *
- * In the following situations, a tensor operand's dimensions must
- * be fully specified:
- *
- * . The operand has lifetime CONSTANT_COPY or
- * CONSTANT_REFERENCE.
- *
- * . The operand has lifetime SUBGRAPH_INPUT and belongs to the main
- * subgraph. Fully specified dimensions must either be present in the
- * Operand or they must be provided in the corresponding
- * RequestArgument.
- * EXCEPTION: If the input is optional and omitted
- * (by setting the hasNoValue field of the corresponding
- * RequestArgument to true) then it need not have fully
- * specified dimensions.
- *
- * A tensor operand with some number of unspecified dimensions is
- * represented by setting each unspecified dimension to 0.
- *
- * A tensor operand with unspecified rank is represented by providing
- * an empty dimensions vector.
- */
- vec<uint32_t> dimensions;
-
- /**
- * The number of times this operand appears as an operation input.
- *
- * (For example, if this operand appears once in one operation's
- * input list, and three times in another operation's input list,
- * then numberOfConsumers = 4.)
- */
- uint32_t numberOfConsumers;
-
- /**
- * Quantized scale of the operand.
- *
- * Must be 0 when not applicable to an operand type.
- *
- * See {@link OperandType}.
- */
- float scale;
-
- /**
- * Quantized zero-point offset of the operand.
- *
- * Must be 0 when not applicable to an operand type.
- *
- * See {@link OperandType}.
- */
- int32_t zeroPoint;
-
- /**
- * How the operand is used.
- */
- OperandLifeTime lifetime;
-
- /**
- * Where to find the data for this operand.
- * If the lifetime is TEMPORARY_VARIABLE, SUBGRAPH_INPUT, SUBGRAPH_OUTPUT,
- * or NO_VALUE:
- * - All the fields must be 0.
- * If the lifetime is CONSTANT_COPY:
- * - location.poolIndex is 0.
- * - location.offset is the offset in bytes into Model.operandValues.
- * - location.length is set.
- * If the lifetime is CONSTANT_REFERENCE:
- * - location.poolIndex is set.
- * - location.offset is the offset in bytes into the specified pool.
- * - location.length is set.
- * If the lifetime is SUBGRAPH:
- * - location.poolIndex is 0.
- * - location.offset is the index of the referenced subgraph in
- * {@link Model::referenced}.
- * - location.length is 0.
- */
- DataLocation location;
-
- /**
- * Additional parameters specific to a particular operand type.
- */
- @1.2::Operand.ExtraParams extraParams;
-};
-
-/**
- * A Neural Network Model.
- *
- * This includes not only the execution graph, but also constant data such as
- * weights or scalars added at construction time. The only information that
- * may not be known is the shape of the input tensors.
- */
-struct Model {
- /**
- * The top-level subgraph.
- */
- Subgraph main;
-
- /**
- * Referenced subgraphs.
- *
- * Each subgraph is referenced by the main subgraph or at least one other
- * referenced subgraph.
- *
- * There must be no reference cycles.
- */
- vec<Subgraph> referenced;
-
- /**
- * A byte buffer containing operand data that were copied into the model.
- *
- * An operand's value must be located here if and only if Operand::lifetime
- * equals OperandLifeTime::CONSTANT_COPY.
- */
- vec<uint8_t> operandValues;
-
- /**
- * A collection of shared memory pools containing operand values.
- *
- * An operand's value must be located here if and only if Operand::lifetime
- * equals OperandLifeTime::CONSTANT_REFERENCE.
- */
- vec<memory> pools;
-
- /**
- * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or
- * precision as low as that of the IEEE 754 16-bit floating-point format.
- * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the
- * range and precision of the IEEE 754 32-bit floating-point format.
- */
- bool relaxComputationFloat32toFloat16;
-
- /**
- * The mapping between extension names and prefixes of operand and
- * operation type values.
- *
- * An operand or operation whose numeric type value is above
- * {@link OperandTypeRange::BASE_MAX} or
- * {@link OperationTypeRange::BASE_MAX} respectively should be interpreted
- * as an extension operand. The low
- * {@link @1.2::Model::ExtensionTypeEncoding::LOW_BITS_TYPE} bits of the
- * value correspond to the type ID within the extension and the high
- * {@link @1.2::Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX} bits encode
- * the "prefix", which maps uniquely to the extension name.
- *
- * For example, if a model contains an operation whose value is
- * 0xAAAABBBB and extensionNameToPrefix contains an entry with
- * prefix=0xAAAA and name="vendor.test.test_extension", then
- * the operation should be interpreted as the operation 0xBBBB
- * of the extension named vendor.test.test_extension.
- *
- * This is a one-to-one correspondence. That is, there must be at most one
- * prefix corresponding to each extension name and at most one extension
- * name corresponding to each prefix.
- */
- vec<@1.2::Model.ExtensionNameAndPrefix> extensionNameToPrefix;
-};
-
-/**
- * An excerpt of the execution graph.
- */
-struct Subgraph {
- /**
- * All operands included in the subgraph.
- */
- vec<Operand> operands;
-
- /**
- * All operations included in the subgraph.
- *
- * The operations are sorted into execution order. Every operand
- * with lifetime SUBGRAPH_OUTPUT or TEMPORARY_VARIABLE must be
- * written before it is read.
- */
- vec<Operation> operations;
-
- /**
- * Input indexes of the subgraph. There must be at least one.
- *
- * Each value corresponds to the index of the operand in "operands".
- */
- vec<uint32_t> inputIndexes;
-
- /**
- * Output indexes of the subgraph. There must be at least one.
- *
- * Each value corresponds to the index of the operand in "operands".
- */
- vec<uint32_t> outputIndexes;
-};
-
-/**
- * A buffer descriptor. Describes the properties of a buffer.
- */
-struct BufferDesc {
- /**
- * Dimensions of the buffer. May have unknown dimensions or rank. A buffer with some number
- * of unspecified dimensions is represented by setting each unspecified dimension to 0. A
- * buffer with unspecified rank is represented by providing an empty dimensions vector.
- */
- vec<uint32_t> dimensions;
-};
-
-/**
- * Describes a role of an input or output to a prepared model.
- */
-struct BufferRole {
- /**
- * The index of the IPreparedModel within the "preparedModel" argument passed in
- * IDevice::allocate.
- */
- uint32_t modelIndex;
-
- /**
- * The index of the input or output operand.
- */
- uint32_t ioIndex;
-
- /**
- * A floating-point value within the range (0.0, 1.0]. Describes how likely the
- * buffer is to be used in the specified role. This is provided as a hint to
- * optimize the case when multiple roles prefer different buffer locations or data
- * layouts.
- */
- float frequency;
-};
-
-/**
- * Inputs to be sent to and outputs to be retrieved from a prepared model.
- *
- * A Request serves two primary tasks:
- * 1) Provides the input and output data to be used when executing the model.
- * 2) Specifies any updates to the input operand metadata that were left
- * unspecified at model preparation time.
- *
- * An output must not overlap with any other output, with an input, or
- * with an operand of lifetime CONSTANT_REFERENCE.
- */
-struct Request {
- /**
- * Input data and information to be used in the execution of a prepared
- * model.
- *
- * The index of the input corresponds to the index in Model.main.inputIndexes.
- * E.g., input[i] corresponds to Model.main.inputIndexes[i].
- */
- vec<RequestArgument> inputs;
-
- /**
- * Output data and information to be used in the execution of a prepared
- * model.
- *
- * The index of the output corresponds to the index in Model.main.outputIndexes.
- * E.g., output[i] corresponds to Model.main.outputIndexes[i].
- */
- vec<RequestArgument> outputs;
-
- /**
- * A memory pool.
- */
- safe_union MemoryPool {
- /**
- * Specifies a client-managed shared memory pool.
- */
- memory hidlMemory;
-
- /**
- * Specifies a driver-managed buffer. It is the token returned from IDevice::allocate,
- * and is specific to the IDevice object.
- */
- uint32_t token;
- };
-
- /**
- * A collection of memory pools containing operand data for both the
- * inputs and the outputs to a model.
- */
- vec<MemoryPool> pools;
-};
+%insert Request
/**
* Optional time point of the steady clock (as from std::chrono::steady_clock)
diff --git a/radio/1.6/IRadioResponse.hal b/radio/1.6/IRadioResponse.hal
index d65c6d0..883711c 100644
--- a/radio/1.6/IRadioResponse.hal
+++ b/radio/1.6/IRadioResponse.hal
@@ -342,6 +342,7 @@
* RadioError:RADIO_NOT_AVAILABLE
* RadioError:MODEM_ERR
* RadioError:INVALID_ARGUMENTS
+ * RadioError:REQUEST_NOT_SUPPORTED
*/
oneway setDataThrottlingResponse(RadioResponseInfo info);
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
index 91d98cb..7fde18e 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
@@ -420,13 +420,18 @@
EXPECT_EQ(std::cv_status::no_timeout, wait());
EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type);
EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial);
- ASSERT_TRUE(
- CheckAnyOfErrors(radioRsp_v1_6->rspInfo.error,
- {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
- ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
- ::android::hardware::radio::V1_6::RadioError::NONE,
- ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
-
+ if (getRadioHalCapabilities().modemReducedFeatureSet1) {
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_v1_6->rspInfo.error,
+ {::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED}));
+ } else {
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_v1_6->rspInfo.error,
+ {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
+ ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
+ ::android::hardware::radio::V1_6::RadioError::NONE,
+ ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+ }
serial = GetRandomSerialNumber();
res = radio_v1_6->setDataThrottling(serial, DataThrottlingAction::THROTTLE_ANCHOR_CARRIER,
@@ -435,13 +440,18 @@
EXPECT_EQ(std::cv_status::no_timeout, wait());
EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type);
EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial);
- ASSERT_TRUE(
- CheckAnyOfErrors(radioRsp_v1_6->rspInfo.error,
- {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
- ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
- ::android::hardware::radio::V1_6::RadioError::NONE,
- ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
-
+ if (getRadioHalCapabilities().modemReducedFeatureSet1) {
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_v1_6->rspInfo.error,
+ {::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED}));
+ } else {
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_v1_6->rspInfo.error,
+ {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
+ ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
+ ::android::hardware::radio::V1_6::RadioError::NONE,
+ ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+ }
serial = GetRandomSerialNumber();
res = radio_v1_6->setDataThrottling(serial, DataThrottlingAction::HOLD, 60000);
@@ -450,13 +460,18 @@
EXPECT_EQ(std::cv_status::no_timeout, wait());
EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type);
EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial);
- ASSERT_TRUE(
- CheckAnyOfErrors(radioRsp_v1_6->rspInfo.error,
- {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
- ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
- ::android::hardware::radio::V1_6::RadioError::NONE,
- ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
-
+ if (getRadioHalCapabilities().modemReducedFeatureSet1) {
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_v1_6->rspInfo.error,
+ {::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED}));
+ } else {
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_v1_6->rspInfo.error,
+ {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
+ ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
+ ::android::hardware::radio::V1_6::RadioError::NONE,
+ ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+ }
serial = GetRandomSerialNumber();
res = radio_v1_6->setDataThrottling(serial, DataThrottlingAction::NO_DATA_THROTTLING, 60000);
@@ -464,12 +479,18 @@
EXPECT_EQ(std::cv_status::no_timeout, wait());
EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type);
EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial);
- ASSERT_TRUE(
- CheckAnyOfErrors(radioRsp_v1_6->rspInfo.error,
- {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
- ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
- ::android::hardware::radio::V1_6::RadioError::NONE,
- ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+ if (getRadioHalCapabilities().modemReducedFeatureSet1) {
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_v1_6->rspInfo.error,
+ {::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED}));
+ } else {
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_v1_6->rspInfo.error,
+ {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
+ ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
+ ::android::hardware::radio::V1_6::RadioError::NONE,
+ ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+ }
}
/*
diff --git a/radio/config/1.3/types.hal b/radio/config/1.3/types.hal
index 117abf3..2b6c9f0 100644
--- a/radio/config/1.3/types.hal
+++ b/radio/config/1.3/types.hal
@@ -31,6 +31,8 @@
* <li> calling android.hardware.radio@1.6::IRadio.setNrDualConnectivityState
* or querying android.hardware.radio@1.6::IRadio.isNrDualConnectivityEnabled
* </li>
+ * <li>Requesting android.hardware.radio@1.6::IRadio.setDataThrottling()
+ * </li>
* </ul>
*/
bool modemReducedFeatureSet1;