• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.media.decoder.cts;
18 
19 import static android.media.MediaCodecInfo.CodecProfileLevel.*;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 import static org.junit.Assume.assumeFalse;
26 import static org.junit.Assume.assumeTrue;
27 
28 import android.content.Context;
29 import android.content.res.AssetFileDescriptor;
30 import android.hardware.camera2.CameraAccessException;
31 import android.hardware.camera2.CameraCharacteristics;
32 import android.hardware.camera2.CameraManager;
33 import android.hardware.camera2.CameraMetadata;
34 import android.hardware.camera2.params.DynamicRangeProfiles;
35 import android.media.MediaCodec;
36 import android.media.MediaCodec.BufferInfo;
37 import android.media.MediaCodecInfo;
38 import android.media.MediaCodecList;
39 import android.media.MediaExtractor;
40 import android.media.MediaFormat;
41 import android.media.cts.MediaHeavyPresubmitTest;
42 import android.media.cts.TestUtils;
43 import android.os.Bundle;
44 import android.platform.test.annotations.AppModeFull;
45 import android.util.Log;
46 import android.view.Surface;
47 
48 import androidx.test.platform.app.InstrumentationRegistry;
49 
50 import com.android.compatibility.common.util.ApiTest;
51 import com.android.compatibility.common.util.CddTest;
52 import com.android.compatibility.common.util.MediaUtils;
53 import com.android.compatibility.common.util.Preconditions;
54 
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 import org.junit.runners.Parameterized;
58 
59 import java.nio.ByteBuffer;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.Collection;
63 import java.util.HashMap;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.concurrent.CountDownLatch;
68 import java.util.concurrent.TimeUnit;
69 import java.util.stream.IntStream;
70 
71 @MediaHeavyPresubmitTest
72 @AppModeFull(reason = "There should be no instant apps specific behavior related to decoders")
73 @RunWith(Parameterized.class)
74 public class HdrToSdrDecoderTest extends HDRDecoderTestBase{
75     private static final String TAG = "HdrToSdrDecoderTest";
76 
77     private static boolean DEBUG_HDR_TO_SDR_PLAY_VIDEO = false;
78     private static final String INVALID_HDR_STATIC_INFO =
79             "00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00" +
80             "00 00 00 00 00 00 00 00  00                     " ;
81 
82     public static final Map<String, Map<Integer, Long>> PROFILE_HDR_MAP = new HashMap<>();
83 
84     static {
85         Map<Integer, Long> AV1_PROFILE_MAP = new HashMap<>();
AV1_PROFILE_MAP.put(AV1ProfileMain10, DynamicRangeProfiles.HLG10)86         AV1_PROFILE_MAP.put(AV1ProfileMain10, DynamicRangeProfiles.HLG10);
AV1_PROFILE_MAP.put(AV1ProfileMain10HDR10, DynamicRangeProfiles.HDR10)87         AV1_PROFILE_MAP.put(AV1ProfileMain10HDR10, DynamicRangeProfiles.HDR10);
AV1_PROFILE_MAP.put(AV1ProfileMain10HDR10Plus, DynamicRangeProfiles.HDR10_PLUS)88         AV1_PROFILE_MAP.put(AV1ProfileMain10HDR10Plus, DynamicRangeProfiles.HDR10_PLUS);
89 
90         Map<Integer, Long> HEVC_PROFILE_MAP = new HashMap<>();
HEVC_PROFILE_MAP.put(HEVCProfileMain10, DynamicRangeProfiles.HLG10)91         HEVC_PROFILE_MAP.put(HEVCProfileMain10, DynamicRangeProfiles.HLG10);
HEVC_PROFILE_MAP.put(HEVCProfileMain10HDR10, DynamicRangeProfiles.HDR10)92         HEVC_PROFILE_MAP.put(HEVCProfileMain10HDR10, DynamicRangeProfiles.HDR10);
HEVC_PROFILE_MAP.put(HEVCProfileMain10HDR10Plus, DynamicRangeProfiles.HDR10_PLUS)93         HEVC_PROFILE_MAP.put(HEVCProfileMain10HDR10Plus, DynamicRangeProfiles.HDR10_PLUS);
94 
95         Map<Integer, Long> VP9_PROFILE_MAP = new HashMap<>();
VP9_PROFILE_MAP.put(VP9Profile2, DynamicRangeProfiles.HLG10)96         VP9_PROFILE_MAP.put(VP9Profile2, DynamicRangeProfiles.HLG10);
VP9_PROFILE_MAP.put(VP9Profile2HDR, DynamicRangeProfiles.HDR10)97         VP9_PROFILE_MAP.put(VP9Profile2HDR, DynamicRangeProfiles.HDR10);
VP9_PROFILE_MAP.put(VP9Profile2HDR10Plus, DynamicRangeProfiles.HDR10_PLUS)98         VP9_PROFILE_MAP.put(VP9Profile2HDR10Plus, DynamicRangeProfiles.HDR10_PLUS);
99 
PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_PROFILE_MAP)100         PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_PROFILE_MAP);
PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_PROFILE_MAP)101         PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_PROFILE_MAP);
PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_PROFILE_MAP)102         PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_PROFILE_MAP);
103     }
104 
105     @Parameterized.Parameter(0)
106     public String mCodecName;
107 
108     @Parameterized.Parameter(1)
109     public String mMediaType;
110 
111     @Parameterized.Parameter(2)
112     public String mInputFile;
113 
114     @Parameterized.Parameter(3)
115     public String mHdrStaticInfo;
116 
117     @Parameterized.Parameter(4)
118     public String[] mHdrDynamicInfo;
119 
120     @Parameterized.Parameter(5)
121     public boolean mMetaDataInContainer;
122 
123     @Parameterized.Parameter(6)
124     public int mProfile;
125 
prepareParamList(List<Object[]> exhaustiveArgsList)126     private static List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
127         final List<Object[]> argsList = new ArrayList<>();
128         int argLength = exhaustiveArgsList.get(0).length;
129         for (Object[] arg : exhaustiveArgsList) {
130             String mediaType = (String) arg[0];
131             String[] decoderNames = MediaUtils.getDecoderNamesForMime(mediaType);
132 
133             for (String decoder : decoderNames) {
134                 if (TestUtils.isMainlineCodec(decoder)) {
135                     if (!TestUtils.isTestingModules()) {
136                         Log.i(TAG, "not testing modules, skip module codec " + decoder);
137                         continue;
138                     }
139                 } else {
140                     if (TestUtils.isTestingModules()) {
141                         Log.i(TAG, "testing modules, skip non-module codec " + decoder);
142                         continue;
143                     }
144                 }
145                 Object[] testArgs = new Object[argLength + 1];
146                 testArgs[0] = decoder;
147                 System.arraycopy(arg, 0, testArgs, 1, argLength);
148                 argsList.add(testArgs);
149             }
150         }
151         return argsList;
152     }
153 
154     @Parameterized.Parameters(name = "{index}_{0}_{1}_{2}")
input()155     public static Collection<Object[]> input() {
156         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
157                 {MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HDR_RES, AV1_HDR_STATIC_INFO, null, false,
158                         AV1ProfileMain10HDR10},
159                 {MediaFormat.MIMETYPE_VIDEO_HEVC, H265_HDR10_RES, H265_HDR10_STATIC_INFO, null,
160                         false, HEVCProfileMain10HDR10},
161                 {MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR_RES, VP9_HDR_STATIC_INFO, null, true,
162                         VP9Profile2HDR},
163                 {MediaFormat.MIMETYPE_VIDEO_HEVC, H265_HDR10PLUS_RES, H265_HDR10PLUS_STATIC_INFO,
164                         H265_HDR10PLUS_DYNAMIC_INFO, false, HEVCProfileMain10HDR10},
165                 {MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR10PLUS_RES, VP9_HDR10PLUS_STATIC_INFO,
166                         VP9_HDR10PLUS_DYNAMIC_INFO, true, VP9Profile2HDR},
167                 {MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HLG_RES, null, null, false,
168                         AV1ProfileMain10},
169                 {MediaFormat.MIMETYPE_VIDEO_HEVC, H265_HLG_RES, null, null, false,
170                         HEVCProfileMain10},
171                 {MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HLG_RES, null, null, false,
172                         VP9Profile2},
173         });
174 
175         return prepareParamList(exhaustiveArgsList);
176     }
177 
getAvailableHDRCaptureProfiles()178     private static Set<Long> getAvailableHDRCaptureProfiles() throws CameraAccessException {
179         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
180         CameraManager cm = context.getSystemService(CameraManager.class);
181         String[] cameraIdList = cm.getCameraIdList();
182         for (String cameraId : cameraIdList) {
183             CameraCharacteristics ch = cm.getCameraCharacteristics(cameraId);
184             int[] caps = ch.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
185             if (IntStream.of(caps).anyMatch(x -> x
186                     == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) {
187                 Set<Long> profiles =
188                         ch.get(CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES)
189                                 .getSupportedProfiles();
190                 return profiles;
191             }
192         }
193         return null;
194     }
195 
isHardwareAcceleratedCodec(String codecName)196     private static boolean isHardwareAcceleratedCodec(String codecName) {
197         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
198         for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
199             if (codecName.equals(codecInfo.getName())) {
200                 return codecInfo.isHardwareAccelerated();
201             }
202         }
203         return false;
204     }
205 
206     @CddTest(requirements = {"5.12/C-6-6"})
207     @Test
208     @ApiTest(apis = {"android.media.MediaFormat#KEY_COLOR_TRANSFER_REQUEST"})
testHdrToSdr()209     public void testHdrToSdr() throws Exception {
210         AssetFileDescriptor infd = null;
211         final boolean dynamic = mHdrDynamicInfo != null;
212 
213         Preconditions.assertTestFileExists(MEDIA_DIR + mInputFile);
214         mExtractor = new MediaExtractor();
215         mExtractor.setDataSource(MEDIA_DIR + mInputFile);
216 
217         MediaFormat format = null;
218         int trackIndex = -1;
219         for (int i = 0; i < mExtractor.getTrackCount(); i++) {
220             format = mExtractor.getTrackFormat(i);
221             if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
222                 trackIndex = i;
223                 break;
224             }
225         }
226         assumeTrue("Media format of input file is not supported.",
227                 MediaUtils.supports(mCodecName, format));
228 
229         long requiredHdrProfile = PROFILE_HDR_MAP.get(mMediaType).get(mProfile);
230         Set<Long> availableProfiles = getAvailableHDRCaptureProfiles();
231         boolean isProfileSupported =
232                 availableProfiles != null && availableProfiles.contains(requiredHdrProfile);
233         assumeTrue("HDR capture is not supported for input file profile.", isProfileSupported);
234 
235         // If extractor returns profile, ensure it is as expected.
236         int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1);
237         if (profile != -1) {
238             assertEquals("profile returned by the extractor is invalid.",
239                          mProfile, profile);
240         }
241 
242         mExtractor.selectTrack(trackIndex);
243         Log.v(TAG, "format " + format);
244 
245         String mime = format.getString(MediaFormat.KEY_MIME);
246         format.setInteger(
247                 MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
248 
249         final Surface surface = getActivity().getSurfaceHolder().getSurface();
250 
251         Log.d(TAG, "Testing candicate decoder " + mCodecName);
252         CountDownLatch latch = new CountDownLatch(1);
253         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
254 
255         mDecoder = MediaCodec.createByCodecName(mCodecName);
256         mDecoder.setCallback(new MediaCodec.Callback() {
257             boolean mInputEOS;
258             boolean mOutputReceived;
259             int mInputCount;
260             int mOutputCount;
261 
262             @Override
263             public void onOutputBufferAvailable(
264                     MediaCodec codec, int index, BufferInfo info) {
265                 if (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO) {
266                     return;
267                 }
268 
269                 MediaFormat bufferFormat = codec.getOutputFormat(index);
270                 Log.i(TAG, "got output buffer: format " + bufferFormat);
271 
272                 assertEquals("unexpected color transfer for the buffer",
273                         MediaFormat.COLOR_TRANSFER_SDR_VIDEO,
274                         bufferFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER, 0));
275                 ByteBuffer staticInfo = bufferFormat.getByteBuffer(
276                         MediaFormat.KEY_HDR_STATIC_INFO, null);
277                 if (staticInfo != null) {
278                     assertTrue(
279                             "Buffer should not have a valid static HDR metadata present",
280                             Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO),
281                                     staticInfo.array()));
282                 }
283                 ByteBuffer hdr10PlusInfo = bufferFormat.getByteBuffer(
284                         MediaFormat.KEY_HDR10_PLUS_INFO, null);
285                 if (hdr10PlusInfo != null) {
286                     assertEquals(
287                             "Buffer should not have a valid dynamic HDR metadata present",
288                             0, hdr10PlusInfo.remaining());
289                 }
290 
291                 if (!dynamic) {
292                     codec.releaseOutputBuffer(index,  true);
293                     mOutputReceived = true;
294                     latch.countDown();
295                 } else {
296                     codec.releaseOutputBuffer(index,  true);
297                     mOutputCount++;
298                     if (mOutputCount >= mHdrDynamicInfo.length) {
299                         mOutputReceived = true;
300                         latch.countDown();
301                     }
302                 }
303             }
304 
305             @Override
306             public void onInputBufferAvailable(MediaCodec codec, int index) {
307                 // keep queuing until input EOS, or first output buffer received.
308                 if (mInputEOS || (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO)) {
309                     return;
310                 }
311 
312                 ByteBuffer inputBuffer = codec.getInputBuffer(index);
313 
314                 if (mExtractor.getSampleTrackIndex() == -1) {
315                     codec.queueInputBuffer(
316                             index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
317                     mInputEOS = true;
318                 } else {
319                     int size = mExtractor.readSampleData(inputBuffer, 0);
320                     long timestamp = mExtractor.getSampleTime();
321                     mExtractor.advance();
322 
323                     if (dynamic && mMetaDataInContainer) {
324                         final Bundle params = new Bundle();
325                         // TODO: extractor currently doesn't extract the dynamic metadata.
326                         // Send in the test pattern for now to test the metadata propagation.
327                         byte[] info = loadByteArrayFromString(mHdrDynamicInfo[mInputCount]);
328                         params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info);
329                         codec.setParameters(params);
330                         mInputCount++;
331                         if (mInputCount >= mHdrDynamicInfo.length) {
332                             mInputEOS = true;
333                         }
334                     }
335                     codec.queueInputBuffer(index, 0, size, timestamp, 0);
336                 }
337             }
338 
339             @Override
340             public void onError(MediaCodec codec, MediaCodec.CodecException e) {
341                 Log.e(TAG, "got codec exception", e);
342             }
343 
344             @Override
345             public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
346                 Log.i(TAG, "got output format: " + format);
347                 ByteBuffer staticInfo = format.getByteBuffer(
348                         MediaFormat.KEY_HDR_STATIC_INFO, null);
349                 if (staticInfo != null) {
350                     assertTrue(
351                             "output format should not have a valid " +
352                                     "static HDR metadata present",
353                             Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO),
354                                     staticInfo.array()));
355                 }
356             }
357         });
358         mDecoder.configure(format, surface, null/*crypto*/, 0/*flags*/);
359         int transferRequest = mDecoder.getInputFormat().getInteger(
360                 MediaFormat.KEY_COLOR_TRANSFER_REQUEST, 0);
361         if (DecoderTest.isDefaultCodec(mCodecName, mMediaType) && isHardwareAcceleratedCodec(
362                 mCodecName)) {
363             assertFalse(mCodecName + " does not support HDR to SDR tone mapping",
364                     transferRequest == 0);
365         } else {
366             assumeFalse(mCodecName + " does not support HDR to SDR tone mapping",
367                     transferRequest == 0);
368         }
369         assertEquals("unexpected color transfer request value from input format",
370                 MediaFormat.COLOR_TRANSFER_SDR_VIDEO, transferRequest);
371         mDecoder.start();
372         try {
373             assertTrue(latch.await(2000, TimeUnit.MILLISECONDS));
374         } catch (InterruptedException e) {
375             fail("playback interrupted");
376         }
377         if (DEBUG_HDR_TO_SDR_PLAY_VIDEO) {
378             Thread.sleep(5000);
379         }
380         mDecoder.stop();
381     }
382 }
383