blob: 8dc56e9cb6faddc71362c47b6599f4ba370a379b [file] [log] [blame]
#!/usr/bin/env python
#
# 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.
#
"""This module is for VTS test cases involving IOmxStore and IOmx::listNodes().
VtsHalMediaOmxStoreV1_0Host derives from base_test.BaseTestClass. It contains
two independent tests: testListServiceAttributes() and
testQueryCodecInformation(). The first one tests
IOmxStore::listServiceAttributes() while the second one test multiple functions
in IOmxStore as well as check the consistency of the return values with
IOmx::listNodes().
"""
import logging
import re
from vts.runners.host import asserts
from vts.runners.host import test_runner
from vts.testcases.template.hal_hidl_host_test import hal_hidl_host_test
OMXSTORE_V1_0_HAL = "android.hardware.media.omx@1.0::IOmxStore"
class VtsHalMediaOmxStoreV1_0Host(hal_hidl_host_test.HalHidlHostTest):
"""Host test class to run the Media_OmxStore HAL."""
TEST_HAL_SERVICES = {OMXSTORE_V1_0_HAL}
def setUpClass(self):
super(VtsHalMediaOmxStoreV1_0Host, self).setUpClass()
self.dut.hal.InitHidlHal(
target_type='media_omx',
target_basepaths=self.dut.libPaths,
target_version=1.0,
target_package='android.hardware.media.omx',
target_component_name='IOmxStore',
hw_binder_service_name=self.getHalServiceName(OMXSTORE_V1_0_HAL),
bits=int(self.abi_bitness))
self.omxstore = self.dut.hal.media_omx
self.vtypes = self.omxstore.GetHidlTypeInterface('types')
def testListServiceAttributes(self):
"""Test IOmxStore::listServiceAttributes().
Tests that IOmxStore::listServiceAttributes() can be called
successfully and returns sensible attributes.
An attribute has a name (key) and a value. Known attributes (represented
by variable "known" below) have certain specifications for valid values.
Unknown attributes that start with 'supports-' should only have '0' or
'1' as their value. Other unknown attributes do not cause the test to
fail, but are reported as warnings in the host log.
"""
status, attributes = self.omxstore.listServiceAttributes()
asserts.assertEqual(self.vtypes.Status.OK, status,
'listServiceAttributes() fails.')
# known is a dictionary 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.
known = {
'max-video-encoder-input-buffers': re.compile('0|[1-9][0-9]*'),
'supports-multiple-secure-codecs': re.compile('0|1'),
'supports-secure-with-non-secure-codec': re.compile('0|1'),
}
# unknown is a list of pairs of regular expressions. For each attribute
# whose key is not known (i.e., does not match any of the keys in the
# "known" variable defined above), that key will be tried for a match
# with the first element of each pair of the variable "unknown". 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.
unknown = [
(re.compile(r'supports-[a-z0-9\-]*'), re.compile('0|1')),
]
# key_set is used to verify that listServiceAttributes() does not return
# duplicate attribute names.
key_set = set()
for attr in attributes:
attr_key = attr['key']
attr_value = attr['value']
# attr_key must not have been seen before.
asserts.assertTrue(
attr_key not in key_set,
'Service attribute "' + attr_key + '" has duplicates.')
key_set.add(attr_key)
if attr_key in known:
asserts.assertTrue(
known[attr_key].match(attr_value),
'Service attribute "' + attr_key + '" has ' +
'invalid value "' + attr_value + '".')
else:
matched = False
for key_re, value_re in unknown:
if key_re.match(attr_key):
asserts.assertTrue(
value_re.match(attr_value),
'Service attribute "' + attr_key + '" has ' +
'invalid value "' + attr_value + '".')
matched = True
if not matched:
logging.warning(
'Unrecognized service attribute "' + attr_key + '" ' +
'with value "' + attr_value + '".')
def testQueryCodecInformation(self):
"""Query and verify information from IOmxStore and IOmx::listNodes().
This function performs three main checks:
1. Information about roles and nodes returned from
IOmxStore::listRoles() conforms to the specifications in
IOmxStore.hal.
2. Each node present in the information returned from
IOmxStore::listRoles() must be supported by its owner. A node is
considered "supported" by its owner if the IOmx instance
corresponding to that owner returns that node and all associated
roles when IOmx::listNodes() is called.
3. The prefix string obtained form IOmxStore::getNodePrefix() must be
sensible, and is indeed a prefix of all the node names.
In step 1, node attributes are validated in the same manner as how
service attributes are validated in testListServiceAttributes().
Role names and mime types must be recognized by the function get_role()
defined below.
"""
# Basic patterns for matching
class Pattern(object):
toggle = '(0|1)'
string = '(.*)'
num = '(0|([1-9][0-9]*))'
size = '(' + num + 'x' + num + ')'
ratio = '(' + num + ':' + num + ')'
range_num = '((' + num + '-' + num + ')|' + num + ')'
range_size = '((' + size + '-' + size + ')|' + size + ')'
range_ratio = '((' + ratio + '-' + ratio + ')|' + ratio + ')'
list_range_num = '(' + range_num + '(,' + range_num + ')*)'
# Matching rules for node attributes with fixed keys
attr_re = {
'alignment' : Pattern.size,
'bitrate-range' : Pattern.range_num,
'block-aspect-ratio-range' : Pattern.range_ratio,
'block-count-range' : Pattern.range_num,
'block-size' : Pattern.size,
'blocks-per-second-range' : Pattern.range_num,
'complexity-default' : Pattern.num,
'complexity-range' : Pattern.range_num,
'feature-adaptive-playback' : Pattern.toggle,
'feature-bitrate-control' : '(VBR|CBR|CQ)[,(VBR|CBR|CQ)]*',
'feature-can-swap-width-height' : Pattern.toggle,
'feature-intra-refresh' : Pattern.toggle,
'feature-partial-frame' : Pattern.toggle,
'feature-secure-playback' : Pattern.toggle,
'feature-tunneled-playback' : Pattern.toggle,
'frame-rate-range' : Pattern.range_num,
'max-channel-count' : Pattern.num,
'max-concurrent-instances' : Pattern.num,
'max-supported-instances' : Pattern.num,
'pixel-aspect-ratio-range' : Pattern.range_ratio,
'quality-default' : Pattern.num,
'quality-range' : Pattern.range_num,
'quality-scale' : Pattern.string,
'sample-rate-ranges' : Pattern.list_range_num,
'size-range' : Pattern.range_size,
}
# Matching rules for node attributes with key patterns
attr_pattern_re = [
('measured-frame-rate-' + Pattern.size +
'-range', Pattern.range_num),
(r'feature-[a-zA-Z0-9_\-]+', Pattern.string),
]
# Matching rules for node names and owners
node_name_re = r'[a-zA-Z0-9.\-]+'
node_owner_re = r'[a-zA-Z0-9._\-]+'
# Compile all regular expressions
for key in attr_re:
attr_re[key] = re.compile(attr_re[key])
for index, value in enumerate(attr_pattern_re):
attr_pattern_re[index] = (re.compile(value[0]),
re.compile(value[1]))
node_name_re = re.compile(node_name_re)
node_owner_re = re.compile(node_owner_re)
# Mapping from mime types to roles.
# These values come from MediaDefs.cpp and OMXUtils.cpp
audio_mime_to_role = {
'3gpp' : 'amrnb',
'ac3' : 'ac3',
'amr-wb' : 'amrwb',
'eac3' : 'eac3',
'flac' : 'flac',
'g711-alaw' : 'g711alaw',
'g711-mlaw' : 'g711mlaw',
'gsm' : 'gsm',
'mp4a-latm' : 'aac',
'mpeg' : 'mp3',
'mpeg-L1' : 'mp1',
'mpeg-L2' : 'mp2',
'opus' : 'opus',
'raw' : 'raw',
'vorbis' : 'vorbis',
}
video_mime_to_role = {
'3gpp' : 'h263',
'avc' : 'avc',
'dolby-vision' : 'dolby-vision',
'hevc' : 'hevc',
'mp4v-es' : 'mpeg4',
'mpeg2' : 'mpeg2',
'x-vnd.on2.vp8' : 'vp8',
'x-vnd.on2.vp9' : 'vp9',
}
image_mime_to_role = {
'vnd.android.heic' : 'heic',
}
def get_role(is_encoder, mime):
"""Returns the role based on is_encoder and mime.
The mapping from a pair (is_encoder, mime) to a role string is
defined in frameworks/av/media/libmedia/MediaDefs.cpp and
frameworks/av/media/libstagefright/omx/OMXUtils.cpp. This function
does essentially the same work as GetComponentRole() in
OMXUtils.cpp.
Args:
is_encoder: A boolean indicating whether the role is for an
encoder or a decoder.
mime: A string of the desired mime type.
Returns:
A string for the requested role name, or None if mime is not
recognized.
"""
mime_suffix = mime[6:]
middle = 'encoder.' if is_encoder else 'decoder.'
if mime.startswith('audio/'):
if mime_suffix not in audio_mime_to_role:
return None
prefix = 'audio_'
suffix = audio_mime_to_role[mime_suffix]
elif mime.startswith('video/'):
if mime_suffix not in video_mime_to_role:
return None
prefix = 'video_'
suffix = video_mime_to_role[mime_suffix]
elif mime.startswith('image/'):
if mime_suffix not in image_mime_to_role:
return None
prefix = 'image_'
suffix = image_mime_to_role[mime_suffix]
else:
return None
return prefix + middle + suffix
# The test code starts here.
roles = self.omxstore.listRoles()
if len(roles) == 0:
logging.warning('IOmxStore has an empty implementation. Skipping...')
return
# A map from a node name to a set of roles.
node2roles = {}
# A map from an owner to a set of node names.
owner2nodes = {}
logging.info('Testing IOmxStore::listRoles()...')
# role_set is used for checking if there are duplicate roles.
role_set = set()
for role in roles:
role_name = role['role']
mime_type = role['type']
is_encoder = role['isEncoder']
nodes = role['nodes']
# The role name must not have duplicates.
asserts.assertFalse(
role_name in role_set,
'Role "' + role_name + '" has duplicates.')
queried_role = get_role(is_encoder, mime_type)
# If mime_type is not recognized, skip it.
if queried_role is None:
logging.info(
'Unrecognized mime type "' +
mime_type + '", skipping.')
continue
# Otherwise, type and isEncoder must be consistent with role.
asserts.assertEqual(
role_name, queried_role,
'Role "' + role_name + '" does not match ' +
('an encoder ' if is_encoder else 'a decoder ') +
'for mime type "' + mime_type + '"')
# Save the role name to check for duplicates.
role_set.add(role_name)
# Ignore role.preferPlatformNodes for now.
# node_set is used for checking if there are duplicate node names
# for each role.
node_set = set()
for node in nodes:
node_name = node['name']
owner = node['owner']
attributes = node['attributes']
# For each role, the node name must not have duplicates.
asserts.assertFalse(
node_name in node_set,
'Node "' + node_name + '" has duplicates for the same ' +
'role "' + queried_role + '".')
# Check the format of node name
asserts.assertTrue(
node_name_re.match(node_name),
'Node name "' + node_name + '" is invalid.')
# Check the format of node owner
asserts.assertTrue(
node_owner_re.match(owner),
'Node owner "' + owner + '" is invalid.')
attr_map = {}
for attr in attributes:
attr_key = attr['key']
attr_value = attr['value']
# For each node and each role, the attribute key must not
# have duplicates.
asserts.assertFalse(
attr_key in attr_map,
'Attribute "' + attr_key +
'" for node "' + node_name +
'"has duplicates.')
# Check the value against the corresponding regular
# expression.
if attr_key in attr_re:
asserts.assertTrue(
attr_re[attr_key].match(attr_value),
'Attribute "' + attr_key + '" has ' +
'invalid value "' + attr_value + '".')
else:
key_found = False
for pattern_key, pattern_value in attr_pattern_re:
if pattern_key.match(attr_key):
asserts.assertTrue(
pattern_value.match(attr_value),
'Attribute "' + attr_key + '" has ' +
'invalid value "' + attr_value + '".')
key_found = True
break
if not key_found:
logging.warning(
'Unknown attribute "' +
attr_key + '" with value "' +
attr_value + '".')
# Store the key-value pair
attr_map[attr_key] = attr_value
if node_name not in node2roles:
node2roles[node_name] = {queried_role,}
if owner not in owner2nodes:
owner2nodes[owner] = {node_name,}
else:
owner2nodes[owner].add(node_name)
else:
node2roles[node_name].add(queried_role)
# Verify the information with IOmx::listNodes().
# IOmxStore::listRoles() and IOmx::listNodes() should give consistent
# information about nodes and roles.
logging.info('Verifying with IOmx::listNodes()...')
for owner in owner2nodes:
# Obtain the IOmx instance for each "owner"
omx = self.omxstore.getOmx(owner)
asserts.assertTrue(
omx,
'Cannot obtain IOmx instance "' + owner + '".')
# Invoke IOmx::listNodes()
status, node_info_list = omx.listNodes()
asserts.assertEqual(
self.vtypes.Status.OK, status,
'IOmx::listNodes() fails for IOmx instance "' + owner + '".')
# Verify that roles for each node match with the information from
# IOmxStore::listRoles().
node_set = set()
for node_info in node_info_list:
node = node_info['mName']
roles = node_info['mRoles']
# IOmx::listNodes() should not list duplicate node names.
asserts.assertFalse(
node in node_set,
'IOmx::listNodes() lists duplicate nodes "' + node + '".')
node_set.add(node)
# Skip "hidden" nodes, i.e. those that are not advertised by
# IOmxStore::listRoles().
if node not in owner2nodes[owner]:
logging.warning(
'IOmx::listNodes() lists unknown node "' + node +
'" for IOmx instance "' + owner + '".')
continue
# All the roles advertised by IOmxStore::listRoles() for this
# node must be included in role_set.
role_set = set(roles)
asserts.assertTrue(
node2roles[node] <= role_set,
'IOmx::listNodes() for IOmx instance "' + owner + '" ' +
'does not report some roles for node "' + node + '": ' +
', '.join(node2roles[node] - role_set))
# Try creating the node.
status, omxNode = omx.allocateNode(node, None)
asserts.assertEqual(
self.vtypes.Status.OK, status,
'IOmx::allocateNode() for IOmx instance "' + owner + '" ' +
'fails to allocate node "' + node +'".')
status = omxNode.freeNode()
# Check that all nodes obtained from IOmxStore::listRoles() are
# supported by the their corresponding IOmx instances.
node_set_diff = owner2nodes[owner] - node_set
asserts.assertFalse(
node_set_diff,
'IOmx::listNodes() for IOmx instance "' + owner + '" ' +
'does not report some expected nodes: ' +
', '.join(node_set_diff) + '.')
# Check that the prefix is a sensible string.
if node2roles:
# Call IOmxStore::getNodePrefix().
prefix = self.omxstore.getNodePrefix()
logging.info('Checking node prefix: ' +
'IOmxStore::getNodePrefix() returns "' + prefix + '".')
asserts.assertTrue(
node_name_re.match(prefix),
'"' + prefix + '" is not a valid prefix for node names.')
# Check that all node names have the said prefix.
for node in node2roles:
asserts.assertTrue(
node.startswith(prefix),
'Node "' + node + '" does not start with ' +
'prefix "' + prefix + '".')
if __name__ == '__main__':
test_runner.main()