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