blob: 4e253134c4fa03816bb7e3ab9bf595f99a1a9e8f [file] [log] [blame]
/*
* Copyright (C) 2017 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.
*/
#define LOG_TAG "media_omx_hidl_master_test"
#ifdef __LP64__
#define OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS
#endif
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <android/hardware/media/omx/1.0/IOmx.h>
#include <android/hardware/media/omx/1.0/IOmxNode.h>
#include <android/hardware/media/omx/1.0/IOmxObserver.h>
#include <android/hardware/media/omx/1.0/IOmxStore.h>
#include <android/hardware/media/omx/1.0/types.h>
#include <android/hidl/allocator/1.0/IAllocator.h>
#include <android/hidl/memory/1.0/IMapper.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
#include <media/stagefright/omx/OMXUtils.h>
using ::android::sp;
using ::android::base::Join;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::hardware::media::omx::V1_0::CodecBuffer;
using ::android::hardware::media::omx::V1_0::IOmx;
using ::android::hardware::media::omx::V1_0::IOmxNode;
using ::android::hardware::media::omx::V1_0::IOmxObserver;
using ::android::hardware::media::omx::V1_0::IOmxStore;
using ::android::hardware::media::omx::V1_0::Message;
using ::android::hardware::media::omx::V1_0::PortMode;
using ::android::hidl::allocator::V1_0::IAllocator;
using ::android::hidl::memory::V1_0::IMapper;
using ::android::hidl::memory::V1_0::IMemory;
#include <getopt.h>
#include <media_hidl_test_common.h>
class MasterHidlTest : public ::testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
omxStore = IOmxStore::getService(GetParam());
ASSERT_NE(omxStore, nullptr);
omx = IOmx::getService(GetParam());
ASSERT_NE(omx, nullptr);
}
sp<IOmxStore> omxStore;
sp<IOmx> omx;
protected:
static void description(const std::string& description) {
RecordProperty("description", description);
}
};
struct AttributePattern {
const testing::internal::RE key;
const testing::internal::RE value;
};
void displayComponentInfo(hidl_vec<IOmx::ComponentInfo>& nodeList) {
for (size_t i = 0; i < nodeList.size(); i++) {
printf("%s | ", nodeList[i].mName.c_str());
for (size_t j = 0; j < ((nodeList[i]).mRoles).size(); j++) {
printf("%s ", nodeList[i].mRoles[j].c_str());
}
printf("\n");
}
}
void validateAttributes(
const std::map<const std::string, const testing::internal::RE>& knownPatterns,
const std::vector<const struct AttributePattern>& unknownPatterns,
hidl_vec<IOmxStore::Attribute> attributes) {
std::set<const std::string> attributeKeys;
for (const auto& attr : attributes) {
// Make sure there are no duplicates
const auto [nodeIter, inserted] = attributeKeys.insert(attr.key);
EXPECT_EQ(inserted, true) << "Attribute \"" << attr.key << "\" has duplicates.";
// Check the value against the corresponding regular
// expression.
const auto knownPattern = knownPatterns.find(attr.key);
if (knownPattern != knownPatterns.end()) {
EXPECT_EQ(testing::internal::RE::FullMatch(attr.value, knownPattern->second), true)
<< "Attribute \"" << attr.key << "\" has invalid value \"" << attr.value << ".";
;
} else {
// Failed to find exact attribute, check against
// possible patterns.
bool keyFound = false;
for (const auto& unknownPattern : unknownPatterns) {
if (testing::internal::RE::PartialMatch(attr.key, unknownPattern.key)) {
keyFound = true;
EXPECT_EQ(testing::internal::RE::FullMatch(attr.value, unknownPattern.value),
true)
<< "Attribute \"" << attr.key << "\" has invalid value \"" << attr.value
<< ".";
}
}
if (!keyFound) {
std::cout << "Warning, Unrecognized attribute \"" << attr.key << "\" with value \""
<< attr.value << "\"." << std::endl;
}
}
}
}
// Make sure IOmx and IOmxStore have the same set of instances.
TEST(MasterHidlTest, instanceMatchValidation) {
auto omxInstances = android::hardware::getAllHalInstanceNames(IOmx::descriptor);
auto omxStoreInstances = android::hardware::getAllHalInstanceNames(IOmxStore::descriptor);
ASSERT_EQ(omxInstances.size(), omxInstances.size());
for (const std::string& omxInstance : omxInstances) {
EXPECT_TRUE(std::find(omxStoreInstances.begin(), omxStoreInstances.end(), omxInstance) !=
omxStoreInstances.end());
}
}
// list service attributes and verify expected formats
TEST_P(MasterHidlTest, ListServiceAttr) {
description("list service attributes");
android::hardware::media::omx::V1_0::Status status;
hidl_vec<IOmxStore::Attribute> attributes;
EXPECT_TRUE(omxStore
->listServiceAttributes([&status, &attributes](
android::hardware::media::omx::V1_0::Status _s,
hidl_vec<IOmxStore::Attribute> const& _nl) {
status = _s;
attributes = _nl;
})
.isOk());
ASSERT_EQ(status, android::hardware::media::omx::V1_0::Status::OK);
if (attributes.size() == 0) {
std::cout << "Warning, Attribute list empty" << std::endl;
} else {
/*
* knownPatterns is a map whose keys are the known "key" for a service
* attribute pair (see IOmxStore::Attribute), and whose values are the
* corresponding regular expressions that will have to match with the
* "value" of the attribute pair. If listServiceAttributes() returns an
* attribute that has a matching key but an unmatched value, the test
* will fail.
*/
const std::map<const std::string, const testing::internal::RE> knownPatterns = {
{"max-video-encoder-input-buffers", "0|[1-9][0-9]*"},
{"supports-multiple-secure-codecs", "0|1"},
{"supports-secure-with-non-secure-codec", "0|1"},
};
/*
* unknownPatterns is a vector of pairs of regular expressions.
* For each attribute whose key is not known (i.e., does not match any
* of the keys in the "knownPatterns" variable defined above), that key will be
* tried for a match with the first element of each pair of the variable
* "unknownPatterns". If a match occurs, the value of that same attribute will be
* tried for a match with the second element of the pair. If this second
* match fails, the test will fail.
*/
const std::vector<const struct AttributePattern> unknownPatterns = {
{"supports-[a-z0-9-]*", "0|1"}};
validateAttributes(knownPatterns, unknownPatterns, attributes);
}
}
// get node prefix
TEST_P(MasterHidlTest, getNodePrefix) {
description("get node prefix");
hidl_string prefix;
omxStore->getNodePrefix(
[&prefix](hidl_string const& _nl) { prefix = _nl; });
if (prefix.empty()) std::cout << "Warning, Node Prefix empty" << std::endl;
}
// list roles and validate all RoleInfo objects
TEST_P(MasterHidlTest, ListRoles) {
description("list roles");
hidl_vec<IOmxStore::RoleInfo> roleList;
omxStore->listRoles([&roleList](hidl_vec<IOmxStore::RoleInfo> const& _nl) {
roleList = _nl;
});
if (roleList.size() == 0) {
GTEST_SKIP() << "Warning, RoleInfo list empty";
return;
}
// Basic patterns for matching
const std::string toggle = "(0|1)";
const std::string string = "(.*)";
const std::string num = "(0|([1-9][0-9]*))";
const std::string size = "(" + num + "x" + num + ")";
const std::string ratio = "(" + num + ":" + num + ")";
const std::string range_num = "((" + num + "-" + num + ")|" + num + ")";
const std::string range_size = "((" + size + "-" + size + ")|" + size + ")";
const std::string range_ratio = "((" + ratio + "-" + ratio + ")|" + ratio + ")";
const std::string list_range_num = "(" + range_num + "(," + range_num + ")*)";
// Matching rules for node attributes with fixed keys
const std::map<const std::string, const testing::internal::RE> knownPatterns = {
{"alignment", size},
{"bitrate-range", range_num},
{"block-aspect-ratio-range", range_ratio},
{"block-count-range", range_num},
{"block-size", size},
{"blocks-per-second-range", range_num},
{"complexity-default", num},
{"complexity-range", range_num},
{"feature-adaptive-playback", toggle},
{"feature-bitrate-control", "(VBR|CBR|CQ)[,(VBR|CBR|CQ)]*"},
{"feature-can-swap-width-height", toggle},
{"feature-intra-refresh", toggle},
{"feature-partial-frame", toggle},
{"feature-secure-playback", toggle},
{"feature-tunneled-playback", toggle},
{"frame-rate-range", range_num},
{"max-channel-count", num},
{"max-concurrent-instances", num},
{"max-supported-instances", num},
{"pixel-aspect-ratio-range", range_ratio},
{"quality-default", num},
{"quality-range", range_num},
{"quality-scale", string},
{"sample-rate-ranges", list_range_num},
{"size-range", range_size},
};
// Strings for matching rules for node attributes with key patterns
const std::vector<const struct AttributePattern> unknownPatterns = {
{"measured-frame-rate-" + size + "-range", range_num},
{"feature-[a-zA-Z0-9_-]+", string},
};
// Matching rules for node names and owners
const testing::internal::RE nodeNamePattern = "[a-zA-Z0-9.-]+";
const testing::internal::RE nodeOwnerPattern = "[a-zA-Z0-9._-]+";
std::set<const std::string> roleKeys;
std::map<const std::string, std::set<const std::string>> nodeToRoles;
std::map<const std::string, std::set<const std::string>> ownerToNodes;
for (const IOmxStore::RoleInfo& role : roleList) {
// Make sure there are no duplicates
const auto [roleIter, inserted] = roleKeys.insert(role.role);
EXPECT_EQ(inserted, true) << "Role \"" << role.role << "\" has duplicates.";
// Make sure role name follows expected format based on type and
// isEncoder
const std::string role_name(
::android::GetComponentRole(role.isEncoder, role.type.c_str()));
EXPECT_EQ(role_name, role.role) << "Role \"" << role.role << "\" does not match "
<< (role.isEncoder ? "an encoder " : "a decoder ")
<< "for mime type \"" << role.type << ".";
// Check the nodes for this role
std::set<const std::string> nodeKeys;
for (const IOmxStore::NodeInfo& node : role.nodes) {
// Make sure there are no duplicates
const auto [nodeIter, inserted] = nodeKeys.insert(node.name);
EXPECT_EQ(inserted, true) << "Node \"" << node.name << "\" has duplicates.";
// Check the format of node name
EXPECT_EQ(testing::internal::RE::FullMatch(node.name, nodeNamePattern), true)
<< "Node name \"" << node.name << " is invalid.";
// Check the format of node owner
EXPECT_EQ(testing::internal::RE::FullMatch(node.owner, nodeOwnerPattern), true)
<< "Node owner \"" << node.owner << " is invalid.";
validateAttributes(knownPatterns, unknownPatterns, node.attributes);
ownerToNodes[node.owner].insert(node.name);
nodeToRoles[node.name].insert(role.role);
}
}
// Verify the information with IOmx::listNodes().
// IOmxStore::listRoles() and IOmx::listNodes() should give consistent
// information about nodes and roles.
for (const auto& [owner, nodes] : ownerToNodes) {
// Obtain the IOmx instance for each "owner"
const sp<IOmx> omx = omxStore->getOmx(owner);
EXPECT_NE(nullptr, omx);
// Invoke IOmx::listNodes()
android::hardware::media::omx::V1_0::Status status;
hidl_vec<IOmx::ComponentInfo> nodeList;
EXPECT_TRUE(
omx->listNodes([&status, &nodeList](android::hardware::media::omx::V1_0::Status _s,
hidl_vec<IOmx::ComponentInfo> const& _nl) {
status = _s;
nodeList = _nl;
}).isOk());
ASSERT_EQ(status, android::hardware::media::omx::V1_0::Status::OK);
// Verify that roles for each node match with the information from
// IOmxStore::listRoles().
std::set<const std::string> nodeKeys;
for (IOmx::ComponentInfo node : nodeList) {
// Make sure there are no duplicates
const auto [nodeIter, inserted] = nodeKeys.insert(node.mName);
EXPECT_EQ(inserted, true)
<< "IOmx::listNodes() lists duplicate nodes \"" << node.mName << "\".";
// Skip "hidden" nodes, i.e. those that are not advertised by
// IOmxStore::listRoles().
if (nodes.find(node.mName) == nodes.end()) {
std::cout << "Warning, IOmx::listNodes() lists unknown node \"" << node.mName
<< "\" for IOmx instance \"" << owner << "\"." << std::endl;
continue;
}
// All the roles advertised by IOmxStore::listRoles() for this
// node must be included in roleKeys.
std::set<const std::string> difference;
std::set_difference(nodeToRoles[node.mName].begin(), nodeToRoles[node.mName].end(),
roleKeys.begin(), roleKeys.end(),
std::inserter(difference, difference.begin()));
EXPECT_EQ(difference.empty(), true) << "IOmx::listNodes() for IOmx "
"instance \""
<< owner
<< "\" does not report some "
"expected nodes: "
<< android::base::Join(difference, ", ") << ".";
}
// Check that all nodes obtained from IOmxStore::listRoles() are
// supported by the their corresponding IOmx instances.
std::set<const std::string> difference;
std::set_difference(nodes.begin(), nodes.end(), nodeKeys.begin(), nodeKeys.end(),
std::inserter(difference, difference.begin()));
EXPECT_EQ(difference.empty(), true) << "IOmx::listNodes() for IOmx "
"instance \""
<< owner
<< "\" does not report some "
"expected nodes: "
<< android::base::Join(difference, ", ") << ".";
}
if (!nodeToRoles.empty()) {
// Check that the prefix is a sensible string.
hidl_string prefix;
omxStore->getNodePrefix([&prefix](hidl_string const& _nl) { prefix = _nl; });
EXPECT_EQ(testing::internal::RE::PartialMatch(prefix, nodeNamePattern), true)
<< "\"" << prefix << "\" is not a valid prefix for node names.";
// Check that all node names have the said prefix.
for (const auto& node : nodeToRoles) {
EXPECT_NE(node.first.rfind(prefix, 0), std::string::npos)
<< "Node \"" << node.first << "\" does not start with prefix \"" << prefix
<< "\".";
}
}
}
// list components and roles.
TEST_P(MasterHidlTest, ListNodes) {
description("enumerate component and roles");
android::hardware::media::omx::V1_0::Status status;
hidl_vec<IOmx::ComponentInfo> nodeList;
bool isPass = true;
EXPECT_TRUE(
omx->listNodes([&status, &nodeList](
android::hardware::media::omx::V1_0::Status _s,
hidl_vec<IOmx::ComponentInfo> const& _nl) {
status = _s;
nodeList = _nl;
})
.isOk());
ASSERT_EQ(status, android::hardware::media::omx::V1_0::Status::OK);
if (nodeList.size() == 0)
std::cout << "Warning, ComponentInfo list empty" << std::endl;
else {
// displayComponentInfo(nodeList);
for (size_t i = 0; i < nodeList.size(); i++) {
sp<CodecObserver> observer = nullptr;
sp<IOmxNode> omxNode = nullptr;
observer = new CodecObserver(nullptr);
ASSERT_NE(observer, nullptr);
EXPECT_TRUE(
omx->allocateNode(
nodeList[i].mName, observer,
[&](android::hardware::media::omx::V1_0::Status _s,
sp<IOmxNode> const& _nl) {
status = _s;
omxNode = _nl;
})
.isOk());
ASSERT_EQ(status, android::hardware::media::omx::V1_0::Status::OK);
if (omxNode == nullptr) {
isPass = false;
std::cerr << "[ !OK ] " << nodeList[i].mName.c_str()
<< "\n";
} else {
EXPECT_TRUE((omxNode->freeNode()).isOk());
omxNode = nullptr;
// std::cout << "[ OK ] " << nodeList[i].mName.c_str() <<
// "\n";
}
}
}
EXPECT_TRUE(isPass);
}
INSTANTIATE_TEST_CASE_P(
PerInstance, MasterHidlTest,
testing::ValuesIn(android::hardware::getAllHalInstanceNames(IOmxStore::descriptor)),
android::hardware::PrintInstanceNameToString);