Home | History | Annotate | Download | only in host_omxstore
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2017 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 #
     17 """This module is for VTS test cases involving IOmxStore and IOmx::listNodes().
     18 
     19 VtsHalMediaOmxStoreV1_0Host derives from base_test.BaseTestClass. It contains
     20 two independent tests: testListServiceAttributes() and
     21 testQueryCodecInformation(). The first one tests
     22 IOmxStore::listServiceAttributes() while the second one test multiple functions
     23 in IOmxStore as well as check the consistency of the return values with
     24 IOmx::listNodes().
     25 
     26 """
     27 
     28 import logging
     29 import re
     30 
     31 from vts.runners.host import asserts
     32 from vts.runners.host import test_runner
     33 from vts.testcases.template.hal_hidl_host_test import hal_hidl_host_test
     34 
     35 OMXSTORE_V1_0_HAL = "android.hardware.media.omx (at] 1.0::IOmxStore"
     36 
     37 class VtsHalMediaOmxStoreV1_0Host(hal_hidl_host_test.HalHidlHostTest):
     38     """Host test class to run the Media_OmxStore HAL."""
     39 
     40     TEST_HAL_SERVICES = {OMXSTORE_V1_0_HAL}
     41 
     42     def setUpClass(self):
     43         super(VtsHalMediaOmxStoreV1_0Host, self).setUpClass()
     44 
     45         self.dut.hal.InitHidlHal(
     46             target_type='media_omx',
     47             target_basepaths=self.dut.libPaths,
     48             target_version=1.0,
     49             target_package='android.hardware.media.omx',
     50             target_component_name='IOmxStore',
     51             hw_binder_service_name=self.getHalServiceName(OMXSTORE_V1_0_HAL),
     52             bits=int(self.abi_bitness))
     53 
     54         self.omxstore = self.dut.hal.media_omx
     55         self.vtypes = self.omxstore.GetHidlTypeInterface('types')
     56 
     57     def testListServiceAttributes(self):
     58         """Test IOmxStore::listServiceAttributes().
     59 
     60         Tests that IOmxStore::listServiceAttributes() can be called
     61         successfully and returns sensible attributes.
     62 
     63         An attribute has a name (key) and a value. Known attributes (represented
     64         by variable "known" below) have certain specifications for valid values.
     65         Unknown attributes that start with 'supports-' should only have '0' or
     66         '1' as their value. Other unknown attributes do not cause the test to
     67         fail, but are reported as warnings in the host log.
     68 
     69         """
     70 
     71         status, attributes = self.omxstore.listServiceAttributes()
     72         asserts.assertEqual(self.vtypes.Status.OK, status,
     73                             'listServiceAttributes() fails.')
     74 
     75         # known is a dictionary whose keys are the known "key" for a service
     76         # attribute pair (see IOmxStore::Attribute), and whose values are the
     77         # corresponding regular expressions that will have to match with the
     78         # "value" of the attribute pair. If listServiceAttributes() returns an
     79         # attribute that has a matching key but an unmatched value, the test
     80         # will fail.
     81         known = {
     82             'max-video-encoder-input-buffers': re.compile('0|[1-9][0-9]*'),
     83             'supports-multiple-secure-codecs': re.compile('0|1'),
     84             'supports-secure-with-non-secure-codec': re.compile('0|1'),
     85         }
     86         # unknown is a list of pairs of regular expressions. For each attribute
     87         # whose key is not known (i.e., does not match any of the keys in the
     88         # "known" variable defined above), that key will be tried for a match
     89         # with the first element of each pair of the variable "unknown". If a
     90         # match occurs, the value of that same attribute will be tried for a
     91         # match with the second element of the pair. If this second match fails,
     92         # the test will fail.
     93         unknown = [
     94             (re.compile(r'supports-[a-z0-9\-]*'), re.compile('0|1')),
     95         ]
     96 
     97         # key_set is used to verify that listServiceAttributes() does not return
     98         # duplicate attribute names.
     99         key_set = set()
    100         for attr in attributes:
    101             attr_key = attr['key']
    102             attr_value = attr['value']
    103 
    104             # attr_key must not have been seen before.
    105             assert(
    106                 attr_key not in key_set,
    107                 'Service attribute "' + attr_key + '" has duplicates.')
    108             key_set.add(attr_key)
    109 
    110             if attr_key in known:
    111                 asserts.assertTrue(
    112                     known[attr_key].match(attr_value),
    113                     'Service attribute "' + attr_key + '" has ' +
    114                     'invalid value "' + attr_value + '".')
    115             else:
    116                 matched = False
    117                 for key_re, value_re in unknown:
    118                     if key_re.match(attr_key):
    119                         asserts.assertTrue(
    120                             value_re.match(attr_value),
    121                             'Service attribute "' + attr_key + '" has ' +
    122                             'invalid value "' + attr_value + '".')
    123                         matched = True
    124                 if not matched:
    125                     logging.warning(
    126                         'Unrecognized service attribute "' + attr_key + '" ' +
    127                         'with value "' + attr_value + '".')
    128 
    129     def testQueryCodecInformation(self):
    130         """Query and verify information from IOmxStore and IOmx::listNodes().
    131 
    132         This function performs three main checks:
    133          1. Information about roles and nodes returned from
    134             IOmxStore::listRoles() conforms to the specifications in
    135             IOmxStore.hal.
    136          2. Each node present in the information returned from
    137             IOmxStore::listRoles() must be supported by its owner. A node is
    138             considered "supported" by its owner if the IOmx instance
    139             corresponding to that owner returns that node and all associated
    140             roles when IOmx::listNodes() is called.
    141          3. The prefix string obtained form IOmxStore::getNodePrefix() must be
    142             sensible, and is indeed a prefix of all the node names.
    143 
    144         In step 1, node attributes are validated in the same manner as how
    145         service attributes are validated in testListServiceAttributes().
    146         Role names and mime types must be recognized by the function get_role()
    147         defined below.
    148 
    149         """
    150 
    151         # Basic patterns for matching
    152         class Pattern(object):
    153             toggle = '(0|1)'
    154             string = '(.*)'
    155             num = '(0|([1-9][0-9]*))'
    156             size = '(' + num + 'x' + num + ')'
    157             ratio = '(' + num + ':' + num + ')'
    158             range_num = '((' + num + '-' + num + ')|' + num + ')'
    159             range_size = '((' + size + '-' + size + ')|' + size + ')'
    160             range_ratio = '((' + ratio + '-' + ratio + ')|' + ratio + ')'
    161             list_range_num = '(' + range_num + '(,' + range_num + ')*)'
    162 
    163         # Matching rules for node attributes with fixed keys
    164         attr_re = {
    165             'alignment'                     : Pattern.size,
    166             'bitrate-range'                 : Pattern.range_num,
    167             'block-aspect-ratio-range'      : Pattern.range_ratio,
    168             'block-count-range'             : Pattern.range_num,
    169             'block-size'                    : Pattern.size,
    170             'blocks-per-second-range'       : Pattern.range_num,
    171             'complexity-default'            : Pattern.num,
    172             'complexity-range'              : Pattern.range_num,
    173             'feature-adaptive-playback'     : Pattern.toggle,
    174             'feature-bitrate-control'       : '(VBR|CBR|CQ)[,(VBR|CBR|CQ)]*',
    175             'feature-can-swap-width-height' : Pattern.toggle,
    176             'feature-intra-refresh'         : Pattern.toggle,
    177             'feature-partial-frame'         : Pattern.toggle,
    178             'feature-secure-playback'       : Pattern.toggle,
    179             'feature-tunneled-playback'     : Pattern.toggle,
    180             'frame-rate-range'              : Pattern.range_num,
    181             'max-channel-count'             : Pattern.num,
    182             'max-concurrent-instances'      : Pattern.num,
    183             'max-supported-instances'       : Pattern.num,
    184             'pixel-aspect-ratio-range'      : Pattern.range_ratio,
    185             'quality-default'               : Pattern.num,
    186             'quality-range'                 : Pattern.range_num,
    187             'quality-scale'                 : Pattern.string,
    188             'sample-rate-ranges'            : Pattern.list_range_num,
    189             'size-range'                    : Pattern.range_size,
    190         }
    191 
    192         # Matching rules for node attributes with key patterns
    193         attr_pattern_re = [
    194             ('measured-frame-rate-' + Pattern.size +
    195              '-range', Pattern.range_num),
    196             (r'feature-[a-zA-Z0-9_\-]+', Pattern.string),
    197         ]
    198 
    199         # Matching rules for node names and owners
    200         node_name_re = r'[a-zA-Z0-9.\-]+'
    201         node_owner_re = r'[a-zA-Z0-9._\-]+'
    202 
    203         # Compile all regular expressions
    204         for key in attr_re:
    205             attr_re[key] = re.compile(attr_re[key])
    206         for index, value in enumerate(attr_pattern_re):
    207             attr_pattern_re[index] = (re.compile(value[0]),
    208                                       re.compile(value[1]))
    209         node_name_re = re.compile(node_name_re)
    210         node_owner_re = re.compile(node_owner_re)
    211 
    212         # Mapping from mime types to roles.
    213         # These values come from MediaDefs.cpp and OMXUtils.cpp
    214         audio_mime_to_role = {
    215             '3gpp'          : 'amrnb',
    216             'ac3'           : 'ac3',
    217             'amr-wb'        : 'amrwb',
    218             'eac3'          : 'eac3',
    219             'flac'          : 'flac',
    220             'g711-alaw'     : 'g711alaw',
    221             'g711-mlaw'     : 'g711mlaw',
    222             'gsm'           : 'gsm',
    223             'mp4a-latm'     : 'aac',
    224             'mpeg'          : 'mp3',
    225             'mpeg-L1'       : 'mp1',
    226             'mpeg-L2'       : 'mp2',
    227             'opus'          : 'opus',
    228             'raw'           : 'raw',
    229             'vorbis'        : 'vorbis',
    230         }
    231         video_mime_to_role = {
    232             '3gpp'          : 'h263',
    233             'avc'           : 'avc',
    234             'dolby-vision'  : 'dolby-vision',
    235             'hevc'          : 'hevc',
    236             'mp4v-es'       : 'mpeg4',
    237             'mpeg2'         : 'mpeg2',
    238             'x-vnd.on2.vp8' : 'vp8',
    239             'x-vnd.on2.vp9' : 'vp9',
    240         }
    241         def get_role(is_encoder, mime):
    242             """Returns the role based on is_encoder and mime.
    243 
    244             The mapping from a pair (is_encoder, mime) to a role string is
    245             defined in frameworks/av/media/libmedia/MediaDefs.cpp and
    246             frameworks/av/media/libstagefright/omx/OMXUtils.cpp. This function
    247             does essentially the same work as GetComponentRole() in
    248             OMXUtils.cpp.
    249 
    250             Args:
    251               is_encoder: A boolean indicating whether the role is for an
    252                   encoder or a decoder.
    253               mime: A string of the desired mime type.
    254 
    255             Returns:
    256               A string for the requested role name, or None if mime is not
    257               recognized.
    258             """
    259             mime_suffix = mime[6:]
    260             middle = 'encoder.' if is_encoder else 'decoder.'
    261             if mime.startswith('audio/'):
    262                 if mime_suffix not in audio_mime_to_role:
    263                     return None
    264                 prefix = 'audio_'
    265                 suffix = audio_mime_to_role[mime_suffix]
    266             elif mime.startswith('video/'):
    267                 if mime_suffix not in video_mime_to_role:
    268                     return None
    269                 prefix = 'video_'
    270                 suffix = video_mime_to_role[mime_suffix]
    271             else:
    272                 return None
    273             return prefix + middle + suffix
    274 
    275         # The test code starts here.
    276         roles = self.omxstore.listRoles()
    277         if len(roles) == 0:
    278             logging.warning('IOmxStore has an empty implementation. Skipping...')
    279             return
    280 
    281         # A map from a node name to a set of roles.
    282         node2roles = {}
    283 
    284         # A map from an owner to a set of node names.
    285         owner2nodes = {}
    286 
    287         logging.info('Testing IOmxStore::listRoles()...')
    288         # role_set is used for checking if there are duplicate roles.
    289         role_set = set()
    290         for role in roles:
    291             role_name = role['role']
    292             mime_type = role['type']
    293             is_encoder = role['isEncoder']
    294             nodes = role['nodes']
    295 
    296             # The role name must not have duplicates.
    297             asserts.assertFalse(
    298                 role_name in role_set,
    299                 'Role "' + role_name + '" has duplicates.')
    300 
    301             queried_role = get_role(is_encoder, mime_type)
    302             # type and isEncoder must be known.
    303             asserts.assertTrue(
    304                 queried_role,
    305                 'Invalid mime type "' + mime_type + '" for ' +
    306                 ('an encoder.' if is_encoder else 'a decoder.'))
    307             # type and isEncoder must be consistent with role.
    308             asserts.assertEqual(
    309                 role_name, queried_role,
    310                 'Role "' + role_name + '" does not match ' +
    311                 ('an encoder ' if is_encoder else 'a decoder ') +
    312                 'for mime type "' + mime_type + '"')
    313 
    314             # Save the role name to check for duplicates.
    315             role_set.add(role_name)
    316 
    317             # Ignore role.preferPlatformNodes for now.
    318 
    319             # node_set is used for checking if there are duplicate node names
    320             # for each role.
    321             node_set = set()
    322             for node in nodes:
    323                 node_name = node['name']
    324                 owner = node['owner']
    325                 attributes = node['attributes']
    326 
    327                 # For each role, the node name must not have duplicates.
    328                 asserts.assertFalse(
    329                     node_name in node_set,
    330                     'Node "' + node_name + '" has duplicates for the same ' +
    331                     'role "' + queried_role + '".')
    332 
    333                 # Check the format of node name
    334                 asserts.assertTrue(
    335                     node_name_re.match(node_name),
    336                     'Node name "' + node_name + '" is invalid.')
    337                 # Check the format of node owner
    338                 asserts.assertTrue(
    339                     node_owner_re.match(owner),
    340                     'Node owner "' + owner + '" is invalid.')
    341 
    342                 attr_map = {}
    343                 for attr in attributes:
    344                     attr_key = attr['key']
    345                     attr_value = attr['value']
    346 
    347                     # For each node and each role, the attribute key must not
    348                     # have duplicates.
    349                     asserts.assertFalse(
    350                         attr_key in attr_map,
    351                         'Attribute "' + attr_key +
    352                         '" for node "' + node_name +
    353                         '"has duplicates.')
    354 
    355                     # Check the value against the corresponding regular
    356                     # expression.
    357                     if attr_key in attr_re:
    358                         asserts.assertTrue(
    359                             attr_re[attr_key].match(attr_value),
    360                             'Attribute "' + attr_key + '" has ' +
    361                             'invalid value "' + attr_value + '".')
    362                     else:
    363                         key_found = False
    364                         for pattern_key, pattern_value in attr_pattern_re:
    365                             if pattern_key.match(attr_key):
    366                                 asserts.assertTrue(
    367                                     pattern_value.match(attr_value),
    368                                     'Attribute "' + attr_key + '" has ' +
    369                                     'invalid value "' + attr_value + '".')
    370                                 key_found = True
    371                                 break
    372                         if not key_found:
    373                             logging.warning(
    374                                 'Unknown attribute "' +
    375                                 attr_key + '" with value "' +
    376                                 attr_value + '".')
    377 
    378                     # Store the key-value pair
    379                     attr_map[attr_key] = attr_value
    380 
    381                 if node_name not in node2roles:
    382                     node2roles[node_name] = {queried_role,}
    383                     if owner not in owner2nodes:
    384                         owner2nodes[owner] = {node_name,}
    385                     else:
    386                         owner2nodes[owner].add(node_name)
    387                 else:
    388                     node2roles[node_name].add(queried_role)
    389 
    390         # Verify the information with IOmx::listNodes().
    391         # IOmxStore::listRoles() and IOmx::listNodes() should give consistent
    392         # information about nodes and roles.
    393         logging.info('Verifying with IOmx::listNodes()...')
    394         for owner in owner2nodes:
    395             # Obtain the IOmx instance for each "owner"
    396             omx = self.omxstore.getOmx(owner)
    397             asserts.assertTrue(
    398                 omx,
    399                 'Cannot obtain IOmx instance "' + owner + '".')
    400 
    401             # Invoke IOmx::listNodes()
    402             status, node_info_list = omx.listNodes()
    403             asserts.assertEqual(
    404                 self.vtypes.Status.OK, status,
    405                 'IOmx::listNodes() fails for IOmx instance "' + owner + '".')
    406 
    407             # Verify that roles for each node match with the information from
    408             # IOmxStore::listRoles().
    409             node_set = set()
    410             for node_info in node_info_list:
    411                 node = node_info['mName']
    412                 roles = node_info['mRoles']
    413 
    414                 # IOmx::listNodes() should not list duplicate node names.
    415                 asserts.assertFalse(
    416                     node in node_set,
    417                     'IOmx::listNodes() lists duplicate nodes "' + node + '".')
    418                 node_set.add(node)
    419 
    420                 # Skip "hidden" nodes, i.e. those that are not advertised by
    421                 # IOmxStore::listRoles().
    422                 if node not in owner2nodes[owner]:
    423                     logging.warning(
    424                         'IOmx::listNodes() lists unknown node "' + node +
    425                         '" for IOmx instance "' + owner + '".')
    426                     continue
    427 
    428                 # All the roles advertised by IOmxStore::listRoles() for this
    429                 # node must be included in role_set.
    430                 role_set = set(roles)
    431                 asserts.assertTrue(
    432                     node2roles[node] <= role_set,
    433                     'IOmx::listNodes() for IOmx instance "' + owner + '" ' +
    434                     'does not report some roles for node "' + node + '": ' +
    435                     ', '.join(node2roles[node] - role_set))
    436 
    437                 # Try creating the node.
    438                 status, omxNode = omx.allocateNode(node, None)
    439                 asserts.assertEqual(
    440                     self.vtypes.Status.OK, status,
    441                     'IOmx::allocateNode() for IOmx instance "' + owner + '" ' +
    442                     'fails to allocate node "' + node +'".')
    443 
    444 
    445             # Check that all nodes obtained from IOmxStore::listRoles() are
    446             # supported by the their corresponding IOmx instances.
    447             node_set_diff = owner2nodes[owner] - node_set
    448             asserts.assertFalse(
    449                 node_set_diff,
    450                 'IOmx::listNodes() for IOmx instance "' + owner + '" ' +
    451                 'does not report some expected nodes: ' +
    452                 ', '.join(node_set_diff) + '.')
    453 
    454         # Call IOmxStore::getNodePrefix().
    455         prefix = self.omxstore.getNodePrefix()
    456         logging.info('Checking node prefix: ' +
    457                      'IOmxStore::getNodePrefix() returns "' + prefix + '".')
    458 
    459         # Check that the prefix is a sensible string.
    460         asserts.assertTrue(
    461             node_name_re.match(prefix),
    462             '"' + prefix + '" is not a valid prefix for node names.')
    463 
    464         # Check that all node names have the said prefix.
    465         for node in node2roles:
    466             asserts.assertTrue(
    467                 node.startswith(prefix),
    468                 'Node "' + node + '" does not start with ' +
    469                 'prefix "' + prefix + '".')
    470 
    471 if __name__ == '__main__':
    472     test_runner.main()
    473