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