1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.cts; 18 19 import android.media.cts.R; 20 21 import android.content.pm.PackageManager; 22 import android.content.res.AssetFileDescriptor; 23 import android.content.res.Resources; 24 import android.media.MediaDataSource; 25 import android.media.MediaExtractor; 26 import android.media.MediaFormat; 27 import android.media.MediaMetadataRetriever; 28 import android.graphics.Bitmap; 29 import android.graphics.BitmapFactory; 30 import android.graphics.Color; 31 import android.graphics.Rect; 32 import android.platform.test.annotations.AppModeFull; 33 import android.support.test.filters.SmallTest; 34 import android.platform.test.annotations.RequiresDevice; 35 import android.test.AndroidTestCase; 36 import android.util.Log; 37 38 import com.android.compatibility.common.util.MediaUtils; 39 40 import static android.content.pm.PackageManager.FEATURE_WATCH; 41 import static android.media.MediaMetadataRetriever.OPTION_CLOSEST; 42 import static android.media.MediaMetadataRetriever.OPTION_CLOSEST_SYNC; 43 import static android.media.MediaMetadataRetriever.OPTION_NEXT_SYNC; 44 import static android.media.MediaMetadataRetriever.OPTION_PREVIOUS_SYNC; 45 46 import java.io.InputStream; 47 import java.io.IOException; 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.function.Function; 51 52 @SmallTest 53 @RequiresDevice 54 @AppModeFull(reason = "No interaction with system server") 55 public class MediaMetadataRetrieverTest extends AndroidTestCase { 56 private static final String TAG = "MediaMetadataRetrieverTest"; 57 private static final boolean SAVE_BITMAP_OUTPUT = false; 58 59 protected Resources mResources; 60 protected MediaMetadataRetriever mRetriever; 61 private PackageManager mPackageManager; 62 63 private static int BORDER_WIDTH = 16; 64 private static Color COLOR_BLOCK = 65 Color.valueOf(1.0f, 1.0f, 1.0f); 66 private static Color[] COLOR_BARS = { 67 Color.valueOf(0.0f, 0.0f, 0.0f), 68 Color.valueOf(0.0f, 0.0f, 0.64f), 69 Color.valueOf(0.0f, 0.64f, 0.0f), 70 Color.valueOf(0.0f, 0.64f, 0.64f), 71 Color.valueOf(0.64f, 0.0f, 0.0f), 72 Color.valueOf(0.64f, 0.0f, 0.64f), 73 Color.valueOf(0.64f, 0.64f, 0.0f), 74 }; 75 76 @Override setUp()77 protected void setUp() throws Exception { 78 super.setUp(); 79 mResources = getContext().getResources(); 80 mRetriever = new MediaMetadataRetriever(); 81 mPackageManager = getContext().getPackageManager(); 82 } 83 84 @Override tearDown()85 protected void tearDown() throws Exception { 86 super.tearDown(); 87 mRetriever.release(); 88 } 89 setDataSourceFd(int resid)90 protected void setDataSourceFd(int resid) { 91 try { 92 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 93 mRetriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 94 afd.close(); 95 } catch (Exception e) { 96 fail("Unable to open file"); 97 } 98 } 99 setDataSourceCallback(int resid)100 protected TestMediaDataSource setDataSourceCallback(int resid) { 101 TestMediaDataSource ds = null; 102 try { 103 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 104 ds = TestMediaDataSource.fromAssetFd(afd); 105 mRetriever.setDataSource(ds); 106 } catch (Exception e) { 107 fail("Unable to open file"); 108 } 109 return ds; 110 } 111 getFaultyDataSource(int resid, boolean throwing)112 protected TestMediaDataSource getFaultyDataSource(int resid, boolean throwing) { 113 TestMediaDataSource ds = null; 114 try { 115 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 116 ds = TestMediaDataSource.fromAssetFd(afd); 117 if (throwing) { 118 ds.throwFromReadAt(); 119 } else { 120 ds.returnFromReadAt(-2); 121 } 122 } catch (Exception e) { 123 fail("Unable to open file"); 124 } 125 return ds; 126 } 127 test3gppMetadata()128 public void test3gppMetadata() { 129 setDataSourceCallback(R.raw.testvideo); 130 131 assertEquals("Title was other than expected", 132 "Title", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)); 133 134 assertEquals("Artist was other than expected", 135 "UTF16LE エンディアン ", 136 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)); 137 138 assertEquals("Album was other than expected", 139 "Test album", 140 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM)); 141 142 assertEquals("Track number was other than expected", 143 "10", 144 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER)); 145 146 assertEquals("Year was other than expected", 147 "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR)); 148 149 assertEquals("Date was other than expected", 150 "19040101T000000.000Z", 151 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE)); 152 153 assertNull("Writer was unexpected present", 154 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER)); 155 } 156 testID3v2Metadata()157 public void testID3v2Metadata() { 158 setDataSourceFd(R.raw.video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz_id3v2); 159 160 assertEquals("Title was other than expected", 161 "Title", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)); 162 163 assertEquals("Artist was other than expected", 164 "UTF16LE エンディアン ", 165 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)); 166 167 assertEquals("Album was other than expected", 168 "Test album", 169 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM)); 170 171 assertEquals("Track number was other than expected", 172 "10", 173 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER)); 174 175 assertEquals("Year was other than expected", 176 "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR)); 177 178 assertEquals("Date was other than expected", 179 "19700101T000000.000Z", 180 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE)); 181 182 assertNull("Writer was unexpectedly present", 183 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER)); 184 } 185 testLargeAlbumArt()186 public void testLargeAlbumArt() { 187 setDataSourceFd(R.raw.largealbumart); 188 189 assertNotNull("couldn't retrieve album art", mRetriever.getEmbeddedPicture()); 190 } 191 testSetDataSourceNullPath()192 public void testSetDataSourceNullPath() { 193 try { 194 mRetriever.setDataSource((String)null); 195 fail("Expected IllegalArgumentException."); 196 } catch (IllegalArgumentException ex) { 197 // Expected, test passed. 198 } 199 } 200 testNullMediaDataSourceIsRejected()201 public void testNullMediaDataSourceIsRejected() { 202 try { 203 mRetriever.setDataSource((MediaDataSource)null); 204 fail("Expected IllegalArgumentException."); 205 } catch (IllegalArgumentException ex) { 206 // Expected, test passed. 207 } 208 } 209 testMediaDataSourceIsClosedOnRelease()210 public void testMediaDataSourceIsClosedOnRelease() throws Exception { 211 TestMediaDataSource dataSource = setDataSourceCallback(R.raw.testvideo); 212 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); 213 mRetriever.release(); 214 assertTrue(dataSource.isClosed()); 215 } 216 testRetrieveFailsIfMediaDataSourceThrows()217 public void testRetrieveFailsIfMediaDataSourceThrows() throws Exception { 218 TestMediaDataSource ds = getFaultyDataSource(R.raw.testvideo, true /* throwing */); 219 try { 220 mRetriever.setDataSource(ds); 221 fail("Failed to throw exceptions"); 222 } catch (RuntimeException e) { 223 assertTrue(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) == null); 224 } 225 } 226 testRetrieveFailsIfMediaDataSourceReturnsAnError()227 public void testRetrieveFailsIfMediaDataSourceReturnsAnError() throws Exception { 228 TestMediaDataSource ds = getFaultyDataSource(R.raw.testvideo, false /* throwing */); 229 try { 230 mRetriever.setDataSource(ds); 231 fail("Failed to throw exceptions"); 232 } catch (RuntimeException e) { 233 assertTrue(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) == null); 234 } 235 } 236 testThumbnail(int resId)237 private void testThumbnail(int resId) { 238 if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")) { 239 MediaUtils.skipTest("no video codecs for resource"); 240 return; 241 } 242 243 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 244 Resources resources = getContext().getResources(); 245 AssetFileDescriptor afd = resources.openRawResourceFd(resId); 246 247 retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 248 249 try { 250 afd.close(); 251 } catch (IOException e) { 252 fail("Unable to open file"); 253 } 254 255 assertNotNull(retriever.getFrameAtTime(-1 /* timeUs (any) */)); 256 } 257 testThumbnailH264()258 public void testThumbnailH264() { 259 testThumbnail(R.raw.bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz); 260 } 261 testThumbnailH263()262 public void testThumbnailH263() { 263 testThumbnail(R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz); 264 } 265 testThumbnailMPEG4()266 public void testThumbnailMPEG4() { 267 testThumbnail(R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz); 268 } 269 testThumbnailVP8()270 public void testThumbnailVP8() { 271 testThumbnail(R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz); 272 } 273 testThumbnailVP9()274 public void testThumbnailVP9() { 275 testThumbnail(R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz); 276 } 277 testThumbnailHEVC()278 public void testThumbnailHEVC() { 279 testThumbnail(R.raw.bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz); 280 } 281 282 283 /** 284 * The following tests verifies MediaMetadataRetriever.getFrameAtTime behavior. 285 * 286 * We use a simple stream with binary counter at the top to check which frame 287 * is actually captured. The stream is 30fps with 600 frames in total. It has 288 * I/P/B frames, with I interval of 30. Due to the encoding structure, pts starts 289 * at 66666 (instead of 0), so we have I frames at 66666, 1066666, ..., etc.. 290 * 291 * For each seek option, we check the following five cases: 292 * 1) frame time falls right on a sync frame 293 * 2) frame time is near the middle of two sync frames but closer to the previous one 294 * 3) frame time is near the middle of two sync frames but closer to the next one 295 * 4) frame time is shortly before a sync frame 296 * 5) frame time is shortly after a sync frame 297 */ testGetFrameAtTimePreviousSync()298 public void testGetFrameAtTimePreviousSync() { 299 int[][] testCases = { 300 { 2066666, 60 }, { 2500000, 60 }, { 2600000, 60 }, { 3000000, 60 }, { 3200000, 90}}; 301 testGetFrameAtTime(OPTION_PREVIOUS_SYNC, testCases); 302 } 303 testGetFrameAtTimeNextSync()304 public void testGetFrameAtTimeNextSync() { 305 int[][] testCases = { 306 { 2066666, 60 }, { 2500000, 90 }, { 2600000, 90 }, { 3000000, 90 }, { 3200000, 120}}; 307 testGetFrameAtTime(OPTION_NEXT_SYNC, testCases); 308 } 309 testGetFrameAtTimeClosestSync()310 public void testGetFrameAtTimeClosestSync() { 311 int[][] testCases = { 312 { 2066666, 60 }, { 2500000, 60 }, { 2600000, 90 }, { 3000000, 90 }, { 3200000, 90}}; 313 testGetFrameAtTime(OPTION_CLOSEST_SYNC, testCases); 314 } 315 testGetFrameAtTimeClosest()316 public void testGetFrameAtTimeClosest() { 317 int[][] testCases = { 318 { 2066666, 60 }, { 2500001, 73 }, { 2599999, 76 }, { 3016000, 88 }, { 3184000, 94}}; 319 testGetFrameAtTime(OPTION_CLOSEST, testCases); 320 } 321 testGetFrameAtTime(int option, int[][] testCases)322 private void testGetFrameAtTime(int option, int[][] testCases) { 323 testGetFrameAt(testCases, (r) -> { 324 List<Bitmap> bitmaps = new ArrayList<>(); 325 for (int i = 0; i < testCases.length; i++) { 326 bitmaps.add(r.getFrameAtTime(testCases[i][0], option)); 327 } 328 return bitmaps; 329 }); 330 } 331 testGetFrameAtIndex()332 public void testGetFrameAtIndex() { 333 int[][] testCases = { { 60, 60 }, { 73, 73 }, { 76, 76 }, { 88, 88 }, { 94, 94} }; 334 335 testGetFrameAt(testCases, (r) -> { 336 List<Bitmap> bitmaps = new ArrayList<>(); 337 for (int i = 0; i < testCases.length; i++) { 338 bitmaps.add(r.getFrameAtIndex(testCases[i][0])); 339 } 340 return bitmaps; 341 }); 342 343 MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams(); 344 params.setPreferredConfig(Bitmap.Config.RGB_565); 345 assertEquals("Failed to set preferred config", 346 Bitmap.Config.RGB_565, params.getPreferredConfig()); 347 348 testGetFrameAt(testCases, (r) -> { 349 List<Bitmap> bitmaps = new ArrayList<>(); 350 for (int i = 0; i < testCases.length; i++) { 351 Bitmap bitmap = r.getFrameAtIndex(testCases[i][0], params); 352 assertEquals(Bitmap.Config.RGB_565, params.getActualConfig()); 353 bitmaps.add(bitmap); 354 } 355 return bitmaps; 356 }); 357 } 358 testGetFramesAtIndex()359 public void testGetFramesAtIndex() { 360 int[][] testCases = { { 27, 27 }, { 28, 28 }, { 29, 29 }, { 30, 30 }, { 31, 31} }; 361 362 testGetFrameAt(testCases, (r) -> { 363 return r.getFramesAtIndex(testCases[0][0], testCases.length); 364 }); 365 366 MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams(); 367 params.setPreferredConfig(Bitmap.Config.RGB_565); 368 assertEquals("Failed to set preferred config", 369 Bitmap.Config.RGB_565, params.getPreferredConfig()); 370 371 testGetFrameAt(testCases, (r) -> { 372 List<Bitmap> bitmaps = r.getFramesAtIndex(testCases[0][0], testCases.length, params); 373 assertEquals(Bitmap.Config.RGB_565, params.getActualConfig()); 374 return bitmaps; 375 }); 376 } 377 testGetFrameAt(int[][] testCases, Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever)378 private void testGetFrameAt(int[][] testCases, 379 Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever) { 380 int resId = R.raw.binary_counter_320x240_30fps_600frames; 381 if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/") 382 && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) { 383 MediaUtils.skipTest("no video codecs for resource on watch"); 384 return; 385 } 386 387 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 388 Resources resources = getContext().getResources(); 389 AssetFileDescriptor afd = resources.openRawResourceFd(resId); 390 391 retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 392 try { 393 afd.close(); 394 } catch (IOException e) { 395 fail("Unable to close file"); 396 } 397 398 List<Bitmap> bitmaps = bitmapRetriever.apply(retriever); 399 400 for (int i = 0; i < testCases.length; i++) { 401 verifyVideoFrame(bitmaps.get(i), testCases[i]); 402 } 403 retriever.release(); 404 } 405 verifyVideoFrame(Bitmap bitmap, int[] testCase)406 private void verifyVideoFrame(Bitmap bitmap, int[] testCase) { 407 try { 408 assertTrue("Failed to get bitmap for " + testCase[0], bitmap != null); 409 assertEquals("Counter value incorrect for " + testCase[0], 410 testCase[1], CodecUtils.readBinaryCounterFromBitmap(bitmap)); 411 412 if (SAVE_BITMAP_OUTPUT) { 413 CodecUtils.saveBitmapToFile(bitmap, "test_" + testCase[0] + ".jpg"); 414 } 415 } catch (Exception e) { 416 fail("Exception getting bitmap: " + e); 417 } 418 } 419 420 /** 421 * The following tests verifies MediaMetadataRetriever.getScaledFrameAtTime behavior. 422 */ testGetScaledFrameAtTime()423 public void testGetScaledFrameAtTime() { 424 int resId = R.raw.binary_counter_320x240_30fps_600frames; 425 if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/") 426 && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) { 427 MediaUtils.skipTest("no video codecs for resource on watch"); 428 return; 429 } 430 431 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 432 Resources resources = getContext().getResources(); 433 AssetFileDescriptor afd = resources.openRawResourceFd(resId); 434 435 retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 436 try { 437 afd.close(); 438 } catch (IOException e) { 439 fail("Unable to close file"); 440 } 441 442 try { 443 Bitmap bitmap = retriever.getScaledFrameAtTime( 444 2066666 /*timeUs*/ , OPTION_CLOSEST, 0 /*width*/, 120 /*height*/); 445 fail("Failed to receive exception"); 446 } catch (IllegalArgumentException e) { 447 // Expect exception 448 } 449 450 try { 451 Bitmap bitmap = retriever.getScaledFrameAtTime( 452 2066666 /*timeUs*/ , OPTION_CLOSEST, -1 /*width*/, 0 /*height*/); 453 fail("Failed to receive exception"); 454 } catch (IllegalArgumentException e) { 455 // Expect exception 456 } 457 458 try { 459 Bitmap bitmap = retriever.getScaledFrameAtTime( 460 2066666 /*timeUs*/ , OPTION_CLOSEST, -1 /*width*/, 120 /*height*/); 461 fail("Failed to receive exception"); 462 } catch (IllegalArgumentException e) { 463 // Expect exception 464 } 465 466 try { 467 Bitmap bitmap = retriever.getScaledFrameAtTime( 468 2066666 /*timeUs */, OPTION_CLOSEST, 140 /*width*/, -1 /*height*/); 469 fail("Failed to receive exception"); 470 } catch (IllegalArgumentException e) { 471 // Expect exception 472 } 473 474 try { 475 Bitmap bitmap = retriever.getScaledFrameAtTime( 476 2066666 /*timeUs */, OPTION_CLOSEST, -1 /*width*/, -1 /*height*/); 477 fail("Failed to receive exception"); 478 } catch (IllegalArgumentException e) { 479 // Expect exception 480 } 481 482 // Test desided size of 160 x 120. Return should be 160 x 120 483 try { 484 Bitmap bitmap = retriever.getScaledFrameAtTime( 485 2066666 /*timeUs */, OPTION_CLOSEST, 160 /*width*/, 120 /*height*/); 486 if (bitmap == null) { 487 fail("Failed to get scaled bitmap"); 488 } 489 if (SAVE_BITMAP_OUTPUT) { 490 CodecUtils.saveBitmapToFile(bitmap, "test_160x120" + ".jpg"); 491 } 492 493 if (bitmap.getWidth() != 160 /* width */) { 494 fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160"); 495 } 496 if (bitmap.getHeight() != 120 /* height */) { 497 fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120"); 498 } 499 500 } catch (Exception e) { 501 fail("Exception getting bitmap: " + e); 502 } 503 504 // Test scaled up bitmap to 640 x 480. Return should be 640 x 480 505 try { 506 Bitmap bitmap = retriever.getScaledFrameAtTime( 507 2066666 /*timeUs */, OPTION_CLOSEST, 640 /*width*/, 480 /*height*/); 508 if (bitmap == null) { 509 fail("Failed to get scaled bitmap"); 510 } 511 if (SAVE_BITMAP_OUTPUT) { 512 CodecUtils.saveBitmapToFile(bitmap, "test_640x480" + ".jpg"); 513 } 514 515 if (bitmap.getWidth() != 640 /* width */) { 516 fail("Bitmap width is " + bitmap.getWidth() + "Expect: 640"); 517 } 518 if (bitmap.getHeight() != 480 /* height */) { 519 fail("Bitmap height is " + bitmap.getHeight() + "Expect: 480"); 520 } 521 522 } catch (Exception e) { 523 fail("Exception getting bitmap: " + e); 524 } 525 526 // Test scaled up bitmap to 320 x 120. Return should be 160 x 120 527 try { 528 Bitmap bitmap = retriever.getScaledFrameAtTime( 529 2066666 /*timeUs */, OPTION_CLOSEST, 320 /*width*/, 120 /*height*/); 530 if (bitmap == null) { 531 fail("Failed to get scaled bitmap"); 532 } 533 if (SAVE_BITMAP_OUTPUT) { 534 CodecUtils.saveBitmapToFile(bitmap, "test_320x120" + ".jpg"); 535 } 536 537 if (bitmap.getWidth() != 160 /* width */) { 538 fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160"); 539 } 540 if (bitmap.getHeight() != 120 /* height */) { 541 fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120"); 542 } 543 544 } catch (Exception e) { 545 fail("Exception getting bitmap: " + e); 546 } 547 548 // Test scaled up bitmap to 160 x 240. Return should be 160 x 120 549 try { 550 Bitmap bitmap = retriever.getScaledFrameAtTime( 551 2066666 /*timeUs */, OPTION_CLOSEST, 160 /*width*/, 240 /*height*/); 552 if (bitmap == null) { 553 fail("Failed to get scaled bitmap"); 554 } 555 if (SAVE_BITMAP_OUTPUT) { 556 CodecUtils.saveBitmapToFile(bitmap, "test_160x240" + ".jpg"); 557 } 558 559 if (bitmap.getWidth() != 160 /* width */) { 560 fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160"); 561 } 562 if (bitmap.getHeight() != 120 /* height */) { 563 fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120"); 564 } 565 566 } catch (Exception e) { 567 fail("Exception getting bitmap: " + e); 568 } 569 570 // Test scaled the video with aspect ratio 571 resId = R.raw.binary_counter_320x240_720x240_30fps_600frames; 572 afd = resources.openRawResourceFd(resId); 573 574 retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 575 try { 576 afd.close(); 577 } catch (IOException e) { 578 fail("Unable to close file"); 579 } 580 try { 581 Bitmap bitmap = retriever.getScaledFrameAtTime( 582 2066666 /*timeUs */, OPTION_CLOSEST, 330 /*width*/, 240 /*height*/); 583 if (bitmap == null) { 584 fail("Failed to get scaled bitmap"); 585 } 586 if (SAVE_BITMAP_OUTPUT) { 587 CodecUtils.saveBitmapToFile(bitmap, "test_330x240" + ".jpg"); 588 } 589 590 if (bitmap.getWidth() != 330 /* width */) { 591 fail("Bitmap width is " + bitmap.getWidth() + "Expect: 330"); 592 } 593 if (bitmap.getHeight() != 110 /* height */) { 594 fail("Bitmap height is " + bitmap.getHeight() + "Expect: 110"); 595 } 596 597 } catch (Exception e) { 598 fail("Exception getting bitmap: " + e); 599 } 600 } 601 testGetImageAtIndex()602 public void testGetImageAtIndex() throws Exception { 603 if (!MediaUtils.hasDecoder(MediaFormat.MIMETYPE_VIDEO_HEVC)) { 604 MediaUtils.skipTest("no video decoders for resource"); 605 return; 606 } 607 608 testGetImage(R.raw.heifwriter_input, 1920, 1080, 0 /*rotation*/, 609 4 /*imageCount*/, 3 /*primary*/, true /*useGrid*/, true /*checkColor*/); 610 } 611 612 /** 613 * Determines if two color values are approximately equal. 614 */ approxEquals(Color expected, Color actual)615 private static boolean approxEquals(Color expected, Color actual) { 616 final float MAX_DELTA = 0.025f; 617 return (Math.abs(expected.red() - actual.red()) <= MAX_DELTA) 618 && (Math.abs(expected.green() - actual.green()) <= MAX_DELTA) 619 && (Math.abs(expected.blue() - actual.blue()) <= MAX_DELTA); 620 } 621 getColorBarRect(int index, int width, int height)622 private static Rect getColorBarRect(int index, int width, int height) { 623 int barWidth = (width - BORDER_WIDTH * 2) / COLOR_BARS.length; 624 return new Rect(BORDER_WIDTH + barWidth * index, BORDER_WIDTH, 625 BORDER_WIDTH + barWidth * (index + 1), height - BORDER_WIDTH); 626 } 627 getColorBlockRect(int index, int width, int height)628 private static Rect getColorBlockRect(int index, int width, int height) { 629 int blockCenterX = (width / 5) * (index % 4 + 1); 630 return new Rect(blockCenterX - width / 10, height / 6, 631 blockCenterX + width / 10, height / 3); 632 } 633 testGetImage( int resId, int width, int height, int rotation, int imageCount, int primary, boolean useGrid, boolean checkColor)634 private void testGetImage( 635 int resId, int width, int height, int rotation, 636 int imageCount, int primary, boolean useGrid, boolean checkColor) 637 throws Exception { 638 MediaMetadataRetriever retriever = null; 639 MediaExtractor extractor = null; 640 AssetFileDescriptor afd = null; 641 InputStream inputStream = null; 642 643 try { 644 retriever = new MediaMetadataRetriever(); 645 646 Resources resources = getContext().getResources(); 647 afd = resources.openRawResourceFd(resId); 648 649 retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 650 651 // Verify image related meta keys. 652 String hasImage = retriever.extractMetadata( 653 MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE); 654 assertTrue("No images found in resId " + resId, "yes".equals(hasImage)); 655 assertEquals("Wrong width", width, 656 Integer.parseInt(retriever.extractMetadata( 657 MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH))); 658 assertEquals("Wrong height", height, 659 Integer.parseInt(retriever.extractMetadata( 660 MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT))); 661 assertEquals("Wrong rotation", rotation, 662 Integer.parseInt(retriever.extractMetadata( 663 MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION))); 664 assertEquals("Wrong image count", imageCount, 665 Integer.parseInt(retriever.extractMetadata( 666 MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT))); 667 assertEquals("Wrong primary index", primary, 668 Integer.parseInt(retriever.extractMetadata( 669 MediaMetadataRetriever.METADATA_KEY_IMAGE_PRIMARY))); 670 671 if (checkColor) { 672 Bitmap bitmap = null; 673 // For each image in the image collection, check the 7 color bars' color. 674 // Also check the position of the color block, which should move left-to-right 675 // with the index. 676 for (int imageIndex = 0; imageIndex < imageCount; imageIndex++) { 677 bitmap = retriever.getImageAtIndex(imageIndex); 678 679 for (int barIndex = 0; barIndex < COLOR_BARS.length; barIndex++) { 680 Rect r = getColorBarRect(barIndex, width, height); 681 assertTrue("Color bar " + barIndex + 682 " for image " + imageIndex + " doesn't match", 683 approxEquals(COLOR_BARS[barIndex], Color.valueOf( 684 bitmap.getPixel(r.centerX(), r.centerY())))); 685 } 686 687 Rect r = getColorBlockRect(imageIndex, width, height); 688 assertTrue("Color block for image " + imageIndex + " doesn't match", 689 approxEquals(COLOR_BLOCK, Color.valueOf( 690 bitmap.getPixel(r.centerX(), height - r.centerY())))); 691 bitmap.recycle(); 692 } 693 694 // Check the color block position on the primary image. 695 Rect r = getColorBlockRect(primary, width, height); 696 bitmap = retriever.getPrimaryImage(); 697 assertTrue("Color block for primary image doesn't match", 698 approxEquals(COLOR_BLOCK, Color.valueOf( 699 bitmap.getPixel(r.centerX(), height - r.centerY())))); 700 bitmap.recycle(); 701 702 // Check the color block position on the bitmap decoded by BitmapFactory. 703 // This should match the primary image as well. 704 inputStream = getContext().getResources().openRawResource(resId); 705 bitmap = BitmapFactory.decodeStream(inputStream); 706 assertTrue("Color block for bitmap decoding doesn't match", 707 approxEquals(COLOR_BLOCK, Color.valueOf( 708 bitmap.getPixel(r.centerX(), height - r.centerY())))); 709 bitmap.recycle(); 710 } 711 712 // Check the grid configuration related keys. 713 if (useGrid) { 714 extractor = new MediaExtractor(); 715 extractor.setDataSource( 716 afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 717 MediaFormat format = extractor.getTrackFormat(0); 718 int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH); 719 int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT); 720 int gridRows = format.getInteger(MediaFormat.KEY_GRID_ROWS); 721 int gridCols = format.getInteger(MediaFormat.KEY_GRID_COLUMNS); 722 assertTrue("Wrong tile width or grid cols", 723 ((width + tileWidth - 1) / tileWidth) == gridCols); 724 assertTrue("Wrong tile height or grid rows", 725 ((height + tileHeight - 1) / tileHeight) == gridRows); 726 } 727 } catch (IOException e) { 728 fail("Unable to open file"); 729 } finally { 730 if (retriever != null) { 731 retriever.release(); 732 } 733 if (extractor != null) { 734 extractor.release(); 735 } 736 if (afd != null) { 737 afd.close(); 738 } 739 if (inputStream != null) { 740 inputStream.close(); 741 } 742 } 743 } 744 } 745