1 /* 2 * Copyright (C) 2024 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.videoencodingmin.app; 18 19 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; 20 import static android.mediav2.common.cts.CodecTestBase.CONTEXT; 21 import static android.mediav2.common.cts.CodecTestBase.MEDIA_CODEC_LIST_REGULAR; 22 import static android.mediav2.common.cts.CodecTestBase.selectCodecs; 23 import static android.mediav2.common.cts.DecodeStreamToYuv.getFormatInStream; 24 import static android.os.Environment.buildPath; 25 26 import android.Manifest; 27 import android.content.pm.PackageManager; 28 import android.media.MediaFormat; 29 import android.mediav2.common.cts.CodecEncoderSurfaceTestBase; 30 import android.mediav2.common.cts.CodecTestBase; 31 import android.mediav2.common.cts.EncoderConfigParams; 32 import android.mediav2.common.cts.OutputManager; 33 import android.os.Environment; 34 35 import androidx.test.ext.junit.runners.AndroidJUnit4; 36 import androidx.test.filters.LargeTest; 37 import androidx.test.platform.app.InstrumentationRegistry; 38 39 import com.android.compatibility.common.util.Preconditions; 40 41 import org.json.JSONArray; 42 import org.json.JSONException; 43 import org.json.JSONObject; 44 import org.junit.Assert; 45 import org.junit.Assume; 46 import org.junit.Test; 47 import org.junit.runner.RunWith; 48 49 import java.io.File; 50 import java.io.FileOutputStream; 51 import java.io.IOException; 52 import java.nio.charset.StandardCharsets; 53 import java.nio.file.Files; 54 import java.nio.file.Paths; 55 import java.util.ArrayList; 56 57 /** 58 * Test transcoding using media codec api. 59 * <p> 60 * The test decodes an input clip to surface. This decoded output is fed as input to encoder. 61 * Assuming no frame drops, the test expects, 62 * <ul> 63 * <li>The number of encoded frames to be identical to number of frames present in input clip 64 * .</li> 65 * <li>The encoder output timestamps list should be identical to decoder input timestamp list 66 * .</li> 67 * </ul> 68 * <p> 69 * The test has provision to validate the encoded output by computing PSNR against input. This is 70 * however disabled as VMAF is chosen for analysis. This analysis is done on host side. 71 */ 72 @RunWith(AndroidJUnit4.class) 73 public class VideoTranscoderTest { 74 private static final String SDCARD_MOUNT_POINT = 75 Environment.getExternalStorageDirectory().getAbsolutePath(); 76 private static final String MEDIA_DIR = SDCARD_MOUNT_POINT + "/vqf/input/"; 77 public static final String ENC_CONFIG_JSON = "conf-json"; 78 private static final String ENC_CONFIG_FILE; 79 private static String PATH_PREFIX; 80 public static final int DEFAULT_TEST_TIMEOUT_MS = 360000; 81 82 private String mEncoderName; 83 private String mEncMediaType; 84 private String mDecoderName; 85 private String mTestFileMediaType; 86 private String mTestFile; 87 private EncoderConfigParams[] mEncCfgParams; 88 private String[] mOutputFileNames; 89 private int mDecColorFormat; 90 91 static { 92 android.os.Bundle args = InstrumentationRegistry.getArguments(); 93 ENC_CONFIG_FILE = args.getString(ENC_CONFIG_JSON); 94 } 95 96 static class TestTranscode extends CodecEncoderSurfaceTestBase { 97 private final String mOutputFileName; 98 TestTranscode(String encoder, String mediaType, String decoder, String testFileMediaType, String testFile, EncoderConfigParams encCfgParams, String outputFileName, int decColorFormat, boolean isOutputToneMapped, boolean usePersistentSurface, String allTestParams)99 TestTranscode(String encoder, String mediaType, String decoder, String testFileMediaType, 100 String testFile, EncoderConfigParams encCfgParams, String outputFileName, 101 int decColorFormat, boolean isOutputToneMapped, boolean usePersistentSurface, 102 String allTestParams) { 103 super(encoder, mediaType, decoder, testFileMediaType, testFile, encCfgParams, 104 decColorFormat, isOutputToneMapped, usePersistentSurface, allTestParams); 105 mOutputFileName = outputFileName; 106 } 107 108 @Override setUpCodecEncoderSurfaceTestBase()109 public void setUpCodecEncoderSurfaceTestBase() 110 throws IOException, CloneNotSupportedException { 111 super.setUpCodecEncoderSurfaceTestBase(); 112 mEncoderFormat = mEncCfgParams.getFormat(); 113 } 114 getTempFilePath(String infix)115 private String getTempFilePath(String infix) throws IOException { 116 String totalPath = PATH_PREFIX + infix + ".mp4"; 117 new FileOutputStream(totalPath).close(); 118 return totalPath; 119 } 120 doTranscode()121 public void doTranscode() 122 throws IOException, InterruptedException, CloneNotSupportedException { 123 try { 124 setUpCodecEncoderSurfaceTestBase(); 125 encodeToMemory(false, false, false, new OutputManager(), true, 126 getTempFilePath(mOutputFileName)); 127 } finally { 128 tearDownCodecEncoderSurfaceTestBase(); 129 } 130 } 131 } 132 parseEncoderConfigurationFile(String jsonPath)133 private void parseEncoderConfigurationFile(String jsonPath) throws JSONException, IOException { 134 Preconditions.assertTestFileExists(jsonPath); 135 String jsonString = 136 new String(Files.readAllBytes(Paths.get(jsonPath)), StandardCharsets.UTF_8); 137 JSONArray jsonArray = new JSONArray(jsonString); 138 JSONObject obj = jsonArray.getJSONObject(0); 139 mTestFile = MEDIA_DIR + "samples/" + obj.getString("RefFileName"); 140 mTestFileMediaType = obj.getString("RefMediaType"); 141 mEncMediaType = obj.getString("TestMediaType"); 142 int width = obj.getInt("Width"); 143 int height = obj.getInt("Height"); 144 String componentType = obj.getString("EncoderType"); 145 CodecTestBase.ComponentClass cType = CodecTestBase.ComponentClass.ALL; 146 if (componentType.equals("hw")) { 147 cType = CodecTestBase.ComponentClass.HARDWARE; 148 } else if (componentType.equals("sw")) { 149 cType = CodecTestBase.ComponentClass.SOFTWARE; 150 } 151 mDecColorFormat = COLOR_FormatSurface; 152 JSONArray codecConfigs = obj.getJSONArray("CodecConfigs"); 153 mEncCfgParams = new EncoderConfigParams[codecConfigs.length()]; 154 mOutputFileNames = new String[codecConfigs.length()]; 155 for (int i = 0; i < codecConfigs.length(); i++) { 156 JSONObject codecConfig = codecConfigs.getJSONObject(i); 157 mEncCfgParams[i] = new EncoderConfigParams.Builder(mEncMediaType) 158 .setWidth(width) 159 .setHeight(height) 160 .setKeyFrameInterval(codecConfig.getInt("KeyFrameInterval")) 161 .setMaxBFrames(codecConfig.getInt("MaxBFrames")) 162 .setBitRate(codecConfig.getInt("BitRate")) 163 .setProfile(codecConfig.getInt("Profile")) 164 .setLevel(codecConfig.getInt("Level")) 165 .setColorFormat(COLOR_FormatSurface) 166 .build(); 167 String outFileName = codecConfig.getString("EncodedFileName"); 168 mOutputFileNames[i] = outFileName.substring(0, outFileName.lastIndexOf('.')); 169 } 170 MediaFormat format = getFormatInStream(mTestFileMediaType, mTestFile); 171 mDecoderName = MEDIA_CODEC_LIST_REGULAR.findDecoderForFormat(format); 172 ArrayList<MediaFormat> formats = new ArrayList<>(); 173 for (EncoderConfigParams param : mEncCfgParams) { 174 formats.add(param.getFormat()); 175 } 176 ArrayList<String> codecs = selectCodecs(mEncMediaType, formats, null, true, cType); 177 if (!codecs.isEmpty()) mEncoderName = codecs.get(0); 178 } 179 180 @LargeTest 181 @Test(timeout = DEFAULT_TEST_TIMEOUT_MS) testTranscode()182 public void testTranscode() throws IOException, InterruptedException, 183 JSONException, CloneNotSupportedException { 184 Assume.assumeTrue("Test did not receive config file for encoding", ENC_CONFIG_FILE != null); 185 parseEncoderConfigurationFile(MEDIA_DIR + "json/" + ENC_CONFIG_FILE); 186 Assume.assumeTrue("Found no encoder supporting the config file", mEncoderName != null); 187 Assume.assumeTrue("Found no decoder supporting the config file", mDecoderName != null); 188 Assert.assertEquals("Apk does not have permissions to write to external storage", 189 PackageManager.PERMISSION_GRANTED, 190 CONTEXT.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)); 191 File pub = new File(SDCARD_MOUNT_POINT + "/vqf/output/"); 192 File dir = buildPath(pub, 193 "output_" + ENC_CONFIG_FILE.substring(0, ENC_CONFIG_FILE.lastIndexOf('.'))); 194 if (!dir.exists()) { 195 Assert.assertTrue("Unable to create dir " + dir.getAbsolutePath(), dir.mkdirs()); 196 } 197 PATH_PREFIX = dir.getAbsolutePath() + File.separator; 198 for (int i = 0; i < mEncCfgParams.length; i++) { 199 TestTranscode ep = new TestTranscode(mEncoderName, mEncMediaType, 200 mDecoderName, mTestFileMediaType, mTestFile, mEncCfgParams[i], 201 mOutputFileNames[i], mDecColorFormat, false, false, ""); 202 ep.doTranscode(); 203 } 204 } 205 } 206