1 /* 2 * Copyright 2023 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.mediaediting.cts; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import android.content.Context; 22 import android.net.Uri; 23 import android.platform.test.annotations.AppModeFull; 24 25 import androidx.media3.common.Effect; 26 import androidx.media3.common.Format; 27 import androidx.media3.common.MediaItem; 28 import androidx.media3.common.MimeTypes; 29 import androidx.media3.effect.Presentation; 30 import androidx.media3.transformer.EditedMediaItem; 31 import androidx.media3.transformer.Effects; 32 import androidx.media3.transformer.TransformationRequest; 33 import androidx.media3.transformer.Transformer; 34 import androidx.test.core.app.ApplicationProvider; 35 36 import com.android.compatibility.common.util.Preconditions; 37 38 import com.google.common.collect.ImmutableList; 39 40 import org.junit.Assume; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 import org.junit.runners.Parameterized; 44 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collection; 48 import java.util.List; 49 50 /** 51 * Instrumentation tests for checking transform aspects ratio for given inputs. 52 */ 53 @AppModeFull(reason = "Instant apps cannot access the SD card") 54 @RunWith(Parameterized.class) 55 public final class TransformVideoAspectRatio { 56 57 private static final String MEDIA_DIR = WorkDir.getMediaDirString(); 58 59 private final String mediaType; 60 private final int width; 61 private final int height; 62 private final float requestedAspectRatio; 63 private final String testFile; 64 private final String testId; 65 TransformVideoAspectRatio(String mediaType, int width, int height, float requestedAspectRatio, String testFile, String testId)66 public TransformVideoAspectRatio(String mediaType, int width, int height, 67 float requestedAspectRatio, String testFile, String testId) { 68 this.mediaType = mediaType; 69 this.width = width; 70 this.height = height; 71 this.requestedAspectRatio = requestedAspectRatio; 72 this.testFile = testFile; 73 this.testId = testId; 74 } 75 76 @Parameterized.Parameters(name = "{index}_{5}") input()77 public static Collection<Object[]> input() { 78 // mediaType, width, height, aspectRatio, clip 79 final List<Object[]> exhaustiveArgsList = new ArrayList<>(Arrays.asList(new Object[][] { 80 // H264 81 {MimeTypes.VIDEO_H264, 1920, 1080, (float) 1 / 2, 82 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_1920W_1080H_1S_URI_STRING}, 83 {MimeTypes.VIDEO_H264, 1920, 1080, (float) 4 / 3, 84 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_1920W_1080H_1S_URI_STRING}, 85 {MimeTypes.VIDEO_H264, 1920, 1080, (float) 3 / 2, 86 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_1920W_1080H_1S_URI_STRING}, 87 {MimeTypes.VIDEO_H264, 320, 240, (float) 1 / 4, 88 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_320W_240H_5S_URI_STRING}, 89 {MimeTypes.VIDEO_H264, 320, 240, (float) 1 / 3, 90 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_320W_240H_5S_URI_STRING}, 91 {MimeTypes.VIDEO_H264, 320, 240, (float) 5 / 6, 92 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_320W_240H_5S_URI_STRING}, 93 {MimeTypes.VIDEO_H264, 642, 642, (float) 1 / 4, 94 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_642W_642H_3S_URI_STRING}, 95 {MimeTypes.VIDEO_H264, 642, 642, (float) 1 / 3, 96 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_642W_642H_3S_URI_STRING}, 97 {MimeTypes.VIDEO_H264, 642, 642, (float) 3 / 4, 98 MediaEditingUtil.MP4_ASSET_H264_WITH_INCREASING_TIMESTAMPS_642W_642H_3S_URI_STRING}, 99 // Hevc 100 {MimeTypes.VIDEO_H265, 1920, 1080, (float) 1 / 2, 101 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_1920_1080_1S_URI_STRING}, 102 {MimeTypes.VIDEO_H265, 1920, 1080, (float) 5 / 6, 103 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_1920_1080_1S_URI_STRING}, 104 {MimeTypes.VIDEO_H265, 1920, 1080, (float) 4 / 3, 105 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_1920_1080_1S_URI_STRING}, 106 {MimeTypes.VIDEO_H265, 1920, 1080, (float) 3 / 2, 107 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_1920_1080_1S_URI_STRING}, 108 {MimeTypes.VIDEO_H265, 720, 480, (float) 1 / 4, 109 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_720W_480H_1S_URI_STRING}, 110 {MimeTypes.VIDEO_H265, 720, 480, (float) 1 / 2, 111 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_720W_480H_1S_URI_STRING}, 112 {MimeTypes.VIDEO_H265, 720, 480, (float) 5 / 6, 113 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_720W_480H_1S_URI_STRING}, 114 {MimeTypes.VIDEO_H265, 642, 642, (float) 3 / 4, 115 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_642W_642H_3S_URI_STRING}, 116 {MimeTypes.VIDEO_H265, 642, 642, (float) 5 / 6, 117 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_642W_642H_3S_URI_STRING}, 118 {MimeTypes.VIDEO_H265, 642, 642, (float) 4 / 3, 119 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_642W_642H_3S_URI_STRING}, 120 {MimeTypes.VIDEO_H265, 608, 1080, (float) 1 / 2, 121 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_608W_1080H_4S_URI_STRING}, 122 {MimeTypes.VIDEO_H265, 608, 1080, (float) 3 / 4, 123 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_608W_1080H_4S_URI_STRING}, 124 {MimeTypes.VIDEO_H265, 608, 1080, (float) 5 / 6, 125 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_608W_1080H_4S_URI_STRING}, 126 {MimeTypes.VIDEO_H265, 608, 1080, (float) 3 / 2, 127 MediaEditingUtil.MP4_ASSET_HEVC_WITH_INCREASING_TIMESTAMPS_608W_1080H_4S_URI_STRING}, 128 })); 129 return prepareParamList(exhaustiveArgsList); 130 } 131 prepareParamList(List<Object[]> exhaustiveArgsList)132 public static List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) { 133 List<Object[]> argsList = new ArrayList<>(); 134 int argLength = exhaustiveArgsList.get(0).length; 135 136 for (Object[] arg : exhaustiveArgsList) { 137 String from = arg[0].toString(); 138 // Trim the mime baseType with slash. 139 int lastIndex = from.lastIndexOf('/'); 140 if (lastIndex != -1) { 141 from = from.substring(lastIndex + 1); 142 } 143 144 String testId = String.format("transform_%s_%dx%d_To_aspectRatio_%f", from, (int) arg[1], 145 (int) arg[2], (float) arg[3]); 146 Object[] argUpdate = Arrays.copyOf(arg, argLength + 1); 147 argUpdate[argLength] = testId; 148 argsList.add(argUpdate); 149 } 150 return argsList; 151 } 152 createDecFormat()153 private Format createDecFormat() { 154 return new Format.Builder() 155 .setSampleMimeType(mediaType) 156 .setWidth(width) 157 .setHeight(height) 158 .build(); 159 } 160 createEncFormat()161 private Format createEncFormat() { 162 float requestedWidth, requestedHeight; 163 float inputAspectRatio = (float) width / height; 164 if (requestedAspectRatio > 1) { 165 if (requestedAspectRatio >= inputAspectRatio) { 166 requestedWidth = height * requestedAspectRatio; 167 requestedHeight = height; 168 } else { 169 requestedWidth = width; 170 requestedHeight = width / requestedAspectRatio; 171 } 172 } else { 173 if (requestedAspectRatio >= inputAspectRatio) { 174 requestedWidth = height; 175 requestedHeight = height * requestedAspectRatio; 176 } else { 177 requestedWidth = width / requestedAspectRatio; 178 requestedHeight = width; 179 } 180 } 181 return new Format.Builder() 182 .setSampleMimeType(mediaType) 183 .setWidth(Math.round(requestedWidth)) 184 .setHeight(Math.round(requestedHeight)) 185 .build(); 186 } 187 createTransformer(Context context, String toMediaType)188 private static Transformer createTransformer(Context context, String toMediaType) { 189 return new Transformer.Builder(context).setTransformationRequest( 190 new TransformationRequest.Builder().setVideoMimeType(toMediaType).build()).build(); 191 } 192 193 @Test transcodeTest()194 public void transcodeTest() throws Exception { 195 Preconditions.assertTestFileExists(MEDIA_DIR + testFile); 196 Context context = ApplicationProvider.getApplicationContext(); 197 Assume.assumeTrue("Skipping transcodeTest for" + testId, 198 !AndroidTestUtil.skipAndLogIfFormatsUnsupported( 199 context, testId, createDecFormat(), createEncFormat())); 200 201 Transformer transformer = createTransformer(context, mediaType); 202 MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile)); 203 ImmutableList<Effect> videoEffects = ImmutableList.of( 204 Presentation.createForAspectRatio(requestedAspectRatio, 0 /* LAYOUT_SCALE_TO_FIT */)); 205 EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem) 206 .setEffects(new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects)) 207 .setRemoveAudio(true) 208 .build(); 209 ExportTestResult result = new TransformerAndroidTestRunner.Builder(context, transformer).build() 210 .run(testId, editedMediaItem); 211 212 float inputAspectRatio = (float) width / height; 213 Format muxedOutputFormat = MediaEditingUtil.getMuxedWidthHeight(result.filePath); 214 if (requestedAspectRatio > 1) { 215 if (requestedAspectRatio >= inputAspectRatio) { 216 assertThat(muxedOutputFormat.width).isEqualTo( 217 Math.round(height * requestedAspectRatio)); 218 assertThat(muxedOutputFormat.height).isEqualTo(height); 219 } else { 220 assertThat(muxedOutputFormat.width).isEqualTo(width); 221 assertThat(muxedOutputFormat.height).isEqualTo( 222 Math.round(width / requestedAspectRatio)); 223 } 224 } else { 225 // Encoders commonly support higher maximum widths than maximum heights. 226 // VideoTranscodingSamplePipeline#getSurfaceInfo may rotate frame before encoding, so the 227 // encoded frame's width >= height, and sets rotationDegrees in the output Format to ensure 228 // the frame is displayed in the correct orientation. 229 assertThat(muxedOutputFormat.rotationDegrees).isEqualTo(90); 230 if (requestedAspectRatio >= inputAspectRatio) { 231 assertThat(muxedOutputFormat.width).isEqualTo(height); 232 assertThat(muxedOutputFormat.height).isEqualTo( 233 Math.round(height * requestedAspectRatio)); 234 } else { 235 assertThat(muxedOutputFormat.width).isEqualTo(Math.round(width / requestedAspectRatio)); 236 assertThat(muxedOutputFormat.height).isEqualTo(width); 237 } 238 } 239 } 240 } 241