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