Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2017 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.bitstreams.app;
     18 
     19 import android.app.Instrumentation;
     20 import android.content.Context;
     21 import android.content.SharedPreferences;
     22 import android.content.SharedPreferences.Editor;
     23 import android.media.MediaCodec;
     24 import android.media.MediaExtractor;
     25 import android.media.MediaFormat;
     26 import android.media.cts.bitstreams.MediaBitstreams;
     27 import android.os.Bundle;
     28 import android.os.Debug;
     29 import android.util.Xml;
     30 
     31 import androidx.test.InstrumentationRegistry;
     32 
     33 import com.android.compatibility.common.util.DynamicConfigDeviceSide;
     34 import com.android.compatibility.common.util.MediaUtils;
     35 
     36 import org.junit.BeforeClass;
     37 import org.junit.Test;
     38 import org.junit.runner.RunWith;
     39 import org.junit.runners.JUnit4;
     40 import org.xmlpull.v1.XmlSerializer;
     41 
     42 import java.io.ByteArrayOutputStream;
     43 import java.io.File;
     44 import java.io.FileNotFoundException;
     45 import java.io.FileOutputStream;
     46 import java.io.IOException;
     47 import java.io.OutputStream;
     48 import java.io.PrintStream;
     49 import java.nio.file.Files;
     50 import java.util.ArrayList;
     51 import java.util.List;
     52 import java.util.Scanner;
     53 import java.util.concurrent.Callable;
     54 import java.util.concurrent.ExecutorService;
     55 import java.util.concurrent.Executors;
     56 import java.util.concurrent.Future;
     57 import java.util.concurrent.TimeUnit;
     58 
     59 /**
     60  * Test class that uses device-side media APIs to determine up to which resolution MediaPreparer
     61  * should copy media files for CtsMediaStressTestCases.
     62  */
     63 @RunWith(JUnit4.class)
     64 public class MediaBitstreamsDeviceSideTest {
     65 
     66     private static final String KEY_SIZE = "size";
     67     private static final String UTF_8 = "utf-8";
     68     /** Instrumentation status code used to write resolution to metrics */
     69     private static final int INST_STATUS_IN_PROGRESS = 2;
     70 
     71     private static File mAppCache = InstrumentationRegistry.getContext().getExternalCacheDir();
     72     private static String mDeviceBitstreamsPath = InstrumentationRegistry.getArguments().getString(
     73             MediaBitstreams.OPT_DEVICE_BITSTREAMS_PATH,
     74             MediaBitstreams.DEFAULT_DEVICE_BITSTEAMS_PATH);
     75 
     76     @BeforeClass
     77     public static void setUp() {
     78         Bundle args = InstrumentationRegistry.getArguments();
     79         String debugStr = args.getString(MediaBitstreams.OPT_DEBUG_TARGET_DEVICE, "false");
     80         boolean debug = Boolean.parseBoolean(debugStr);
     81         if (debug && !Debug.isDebuggerConnected()) {
     82             Debug.waitForDebugger();
     83         }
     84     }
     85 
     86     static interface ReportCallback {
     87         void run(OutputStream out) throws Exception;
     88     }
     89 
     90     static class GenerateBitstreamsFormatsXml implements ReportCallback {
     91         @Override
     92         public void run(OutputStream out) throws Exception {
     93 
     94             String[] keys = new String[] {
     95                     MediaFormat.KEY_WIDTH,
     96                     MediaFormat.KEY_HEIGHT,
     97                     MediaFormat.KEY_FRAME_RATE,
     98                     MediaFormat.KEY_PROFILE,
     99                     MediaFormat.KEY_LEVEL,
    100                     MediaFormat.KEY_BIT_RATE};
    101 
    102             XmlSerializer formats = Xml.newSerializer();
    103             formats.setOutput(out, UTF_8);
    104             formats.startDocument(UTF_8, true);
    105             formats.startTag(null, MediaBitstreams.DYNAMIC_CONFIG);
    106 
    107             DynamicConfigDeviceSide config = new DynamicConfigDeviceSide(MediaBitstreams.K_MODULE);
    108             for (String path : config.keySet()) {
    109 
    110                 formats.startTag(null, MediaBitstreams.DYNAMIC_CONFIG_ENTRY);
    111                 formats.attribute(null, MediaBitstreams.DYNAMIC_CONFIG_KEY, path);
    112                 formats.startTag(null, MediaBitstreams.DYNAMIC_CONFIG_VALUE);
    113 
    114                 String formatStr = config.getValue(path);
    115                 if (formatStr != null && !formatStr.isEmpty()) {
    116                     formats.text(formatStr);
    117                 } else {
    118                     File media = new File(mDeviceBitstreamsPath, path);
    119                     String fullPath = media.getPath();
    120                     MediaFormat format = MediaUtils.getTrackFormatForPath(null, fullPath, "video");
    121                     StringBuilder formatStringBuilder = new StringBuilder(MediaFormat.KEY_MIME);
    122                     formatStringBuilder.append('=').append(format.getString(MediaFormat.KEY_MIME));
    123                     formatStringBuilder.append(',').append(KEY_SIZE)
    124                             .append('=').append(media.length());
    125                     for (String key : keys) {
    126                         formatStringBuilder.append(',').append(key).append('=');
    127                         if (format.containsKey(key)) {
    128                             formatStringBuilder.append(format.getInteger(key));
    129                         }
    130                     }
    131                     formats.text(formatStringBuilder.toString());
    132                 }
    133 
    134                 formats.endTag(null, MediaBitstreams.DYNAMIC_CONFIG_VALUE);
    135                 formats.endTag(null, MediaBitstreams.DYNAMIC_CONFIG_ENTRY);
    136 
    137             }
    138 
    139             formats.endTag(null, MediaBitstreams.DYNAMIC_CONFIG);
    140             formats.endDocument();
    141 
    142         }
    143     }
    144 
    145     static class GenerateSupportedBitstreamsFormatsTxt implements ReportCallback {
    146 
    147         @Override
    148         public void run(OutputStream out) throws Exception {
    149 
    150             PrintStream ps = new PrintStream(out);
    151             Bundle args = InstrumentationRegistry.getArguments();
    152             String prefix = args.getString(MediaBitstreams.OPT_BITSTREAMS_PREFIX, "");
    153             DynamicConfigDeviceSide config = new DynamicConfigDeviceSide(MediaBitstreams.K_MODULE);
    154 
    155             for (String path : config.keySet()) {
    156 
    157                 if (!path.startsWith(prefix)) {
    158                     continue;
    159                 }
    160 
    161                 String formatStr = config.getValue(path);
    162                 if (formatStr == null || formatStr.isEmpty()) {
    163                     continue;
    164                 }
    165 
    166                 MediaFormat format = parseTrackFormat(formatStr);
    167                 String mime = format.getString(MediaFormat.KEY_MIME);
    168                 String[] decoders = MediaUtils.getDecoderNamesForMime(mime);
    169 
    170                 ps.println(path);
    171                 ps.println(decoders.length);
    172                 for (String name : decoders) {
    173                     ps.println(name);
    174                     ps.println(MediaUtils.supports(name, format));
    175                 }
    176 
    177             }
    178 
    179             ps.flush();
    180         }
    181     }
    182 
    183     static class TestBitstreamsConformance implements ReportCallback {
    184 
    185         ExecutorService mExecutorService;
    186 
    187         private SharedPreferences getSettings() {
    188             Context ctx = InstrumentationRegistry.getContext();
    189             SharedPreferences settings = ctx.getSharedPreferences(MediaBitstreams.K_MODULE, 0);
    190             return settings;
    191         }
    192 
    193         private void setup() {
    194             Bundle args = InstrumentationRegistry.getArguments();
    195             String lastCrash = args.getString(MediaBitstreams.OPT_LAST_CRASH);
    196             if (lastCrash != null) {
    197                 SharedPreferences settings = getSettings();
    198                 int n = settings.getInt(lastCrash, 0);
    199                 Editor editor = settings.edit();
    200                 editor.putInt(lastCrash, n + 1);
    201                 editor.commit();
    202             }
    203         }
    204 
    205         @Override
    206         public void run(OutputStream out) throws Exception {
    207             setup();
    208             mExecutorService = Executors.newFixedThreadPool(3);
    209             try (
    210                 Scanner sc = new Scanner(
    211                         new File(mDeviceBitstreamsPath, MediaBitstreams.K_BITSTREAMS_LIST_TXT));
    212                 PrintStream ps = new PrintStream(out, true)
    213             ) {
    214                 while (sc.hasNextLine()) {
    215                     verifyBitstream(ps, sc.nextLine());
    216                 }
    217             } finally {
    218                 mExecutorService.shutdown();
    219             }
    220         }
    221 
    222         private List<String> getDecodersForPath(String path) throws IOException {
    223             List<String> decoders = new ArrayList<>();
    224             MediaExtractor ex = new MediaExtractor();
    225             try {
    226                 ex.setDataSource(path);
    227                 MediaFormat format = ex.getTrackFormat(0);
    228                 boolean[] vendors = new boolean[] {false, true};
    229                 for (boolean v : vendors) {
    230                     for (String name : MediaUtils.getDecoderNames(v, format)) {
    231                         decoders.add(name);
    232                     }
    233                 }
    234             } finally {
    235                 ex.release();
    236             }
    237             return decoders;
    238         }
    239 
    240         private List<String> getFrameChecksumsForPath(String path) throws IOException {
    241             String md5Path = MediaBitstreams.getMd5Path(path);
    242             List<String> frameMD5Sums = Files.readAllLines(
    243                     new File(mDeviceBitstreamsPath, md5Path).toPath());
    244             for (int i = 0; i < frameMD5Sums.size(); i++) {
    245                 String line = frameMD5Sums.get(i);
    246                 frameMD5Sums.set(i, line.split(" ")[0]);
    247             }
    248             return frameMD5Sums;
    249         }
    250 
    251         private void verifyBitstream(PrintStream ps, String relativePath) {
    252             ps.println(relativePath);
    253 
    254             List<String> decoders = new ArrayList<>();
    255             List<String> frameChecksums = new ArrayList<>();
    256             SharedPreferences settings = getSettings();
    257             String fullPath = new File(mDeviceBitstreamsPath, relativePath).toString();
    258             try {
    259                 String lastCrash = MediaBitstreams.generateCrashSignature(relativePath, "");
    260                 if (settings.getInt(lastCrash, 0) >= 3) {
    261                     ps.println(MediaBitstreams.K_NATIVE_CRASH);
    262                     return;
    263                 }
    264                 decoders = getDecodersForPath(fullPath);
    265                 frameChecksums = getFrameChecksumsForPath(relativePath);
    266                 ps.println(false);
    267             } catch (Exception e) {
    268                 ps.println(true);
    269                 ps.println(e.toString());
    270                 return;
    271             }
    272 
    273             ps.println(decoders.size());
    274             for (String name : decoders) {
    275                 ps.println(name);
    276                 String lastCrash = MediaBitstreams.generateCrashSignature(relativePath, name);
    277                 if (settings.getInt(lastCrash, 0) >= 3) {
    278                     ps.println(MediaBitstreams.K_NATIVE_CRASH);
    279                 } else {
    280                     ps.println(verifyBitstream(fullPath, name, frameChecksums));
    281                 }
    282             }
    283 
    284         }
    285 
    286         private String verifyBitstream(String path, String name, List<String> frameChecksums)  {
    287             MediaExtractor ex = new MediaExtractor();
    288             MediaCodec d = null;
    289             try {
    290                 Future<MediaCodec> dec = mExecutorService.submit(new Callable<MediaCodec>() {
    291                     @Override
    292                     public MediaCodec call() throws Exception {
    293                         return MediaCodec.createByCodecName(name);
    294                     }
    295                 });
    296                 MediaCodec decoder = d = dec.get(1, TimeUnit.SECONDS);
    297                 Future<Boolean> conform = mExecutorService.submit(new Callable<Boolean>() {
    298                     @Override
    299                     public Boolean call() throws Exception {
    300                         ex.setDataSource(path);
    301                         ex.selectTrack(0);
    302                         ex.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
    303                         return MediaUtils.verifyDecoder(decoder, ex, frameChecksums);
    304                     }
    305                 });
    306                 return conform.get(15, TimeUnit.SECONDS).toString();
    307             } catch (Exception e) {
    308                 return e.toString();
    309             } finally {
    310                 ex.release();
    311                 if (d != null) {
    312                     d.release();
    313                 }
    314             }
    315         }
    316 
    317     }
    318 
    319     private void generateReportFile(String suffix, String reportKey, ReportCallback callback)
    320             throws IOException, FileNotFoundException, Exception {
    321 
    322         OutputStream out = new ByteArrayOutputStream(0);
    323 
    324         try {
    325 
    326             File tmpf = File.createTempFile(getClass().getSimpleName(), suffix, mAppCache);
    327             Instrumentation inst = InstrumentationRegistry.getInstrumentation();
    328             Bundle bundle = new Bundle();
    329             bundle.putString(MediaBitstreams.KEY_APP_CACHE_DIR, mAppCache.getCanonicalPath());
    330             bundle.putString(reportKey, tmpf.getCanonicalPath());
    331             inst.sendStatus(INST_STATUS_IN_PROGRESS, bundle);
    332 
    333             out = new FileOutputStream(tmpf);
    334             callback.run(out);
    335             out.flush();
    336 
    337         } finally {
    338 
    339             out.close();
    340 
    341         }
    342     }
    343 
    344     @Test
    345     public void testGetBitstreamsFormats() throws Exception {
    346         generateReportFile(".xml",
    347                 MediaBitstreams.KEY_BITSTREAMS_FORMATS_XML,
    348                 new GenerateBitstreamsFormatsXml());
    349     }
    350 
    351     @Test
    352     public void testGetSupportedBitstreams() throws Exception {
    353         generateReportFile(".txt",
    354                 MediaBitstreams.KEY_SUPPORTED_BITSTREAMS_TXT,
    355                 new GenerateSupportedBitstreamsFormatsTxt());
    356     }
    357 
    358     @Test
    359     public void testBitstreamsConformance() throws Exception {
    360         generateReportFile(".txt",
    361                 MediaBitstreams.KEY_BITSTREAMS_VALIDATION_TXT,
    362                 new TestBitstreamsConformance());
    363     }
    364 
    365     /**
    366      * Converts a single media track format string into a MediaFormat object
    367      *
    368      * @param trackFormatString a string representation of the format of one media track
    369      * @return a MediaFormat
    370      */
    371     private static MediaFormat parseTrackFormat(String trackFormatString) {
    372         MediaFormat format = new MediaFormat();
    373         format.setString(MediaFormat.KEY_MIME, "");
    374         for (String entry : trackFormatString.split(",")) {
    375             String[] kv = entry.split("=");
    376             if (kv.length < 2 || kv[1].isEmpty()) {
    377                 continue;
    378             }
    379             String k = kv[0];
    380             String v = kv[1];
    381             try {
    382                 format.setInteger(k, Integer.parseInt(v));
    383             } catch (NumberFormatException e) {
    384                 format.setString(k, v);
    385             }
    386         }
    387         return format;
    388     }
    389 }
    390