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 androidx.camera.video.internal.utils;
18 
19 import static android.media.EncoderProfiles.VideoProfile.HDR_DOLBY_VISION;
20 import static android.media.EncoderProfiles.VideoProfile.HDR_HDR10;
21 import static android.media.EncoderProfiles.VideoProfile.HDR_HDR10PLUS;
22 import static android.media.EncoderProfiles.VideoProfile.HDR_HLG;
23 import static android.media.EncoderProfiles.VideoProfile.HDR_NONE;
24 import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10;
25 import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10;
26 import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10Plus;
27 import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8;
28 import static android.media.MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe;
29 import static android.media.MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt;
30 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain;
31 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10;
32 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10;
33 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus;
34 import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile0;
35 import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2;
36 import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR;
37 import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus;
38 
39 import static androidx.camera.core.DynamicRange.BIT_DEPTH_10_BIT;
40 import static androidx.camera.core.DynamicRange.BIT_DEPTH_8_BIT;
41 import static androidx.camera.core.DynamicRange.BIT_DEPTH_UNSPECIFIED;
42 import static androidx.camera.core.DynamicRange.DOLBY_VISION_10_BIT;
43 import static androidx.camera.core.DynamicRange.DOLBY_VISION_8_BIT;
44 import static androidx.camera.core.DynamicRange.ENCODING_DOLBY_VISION;
45 import static androidx.camera.core.DynamicRange.ENCODING_HDR10;
46 import static androidx.camera.core.DynamicRange.ENCODING_HDR10_PLUS;
47 import static androidx.camera.core.DynamicRange.ENCODING_HDR_UNSPECIFIED;
48 import static androidx.camera.core.DynamicRange.ENCODING_HLG;
49 import static androidx.camera.core.DynamicRange.ENCODING_SDR;
50 import static androidx.camera.core.DynamicRange.ENCODING_UNSPECIFIED;
51 import static androidx.camera.core.DynamicRange.HDR10_10_BIT;
52 import static androidx.camera.core.DynamicRange.HDR10_PLUS_10_BIT;
53 import static androidx.camera.core.DynamicRange.HLG_10_BIT;
54 import static androidx.camera.core.DynamicRange.SDR;
55 import static androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_10;
56 import static androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_8;
57 import static androidx.core.util.Preconditions.checkArgument;
58 
59 import static java.util.Arrays.asList;
60 import static java.util.Collections.singletonList;
61 import static java.util.Objects.requireNonNull;
62 
63 import android.media.MediaFormat;
64 
65 import androidx.camera.core.DynamicRange;
66 import androidx.camera.core.impl.EncoderProfilesProxy;
67 
68 import org.jspecify.annotations.NonNull;
69 
70 import java.util.Collections;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Map;
74 import java.util.Set;
75 
76 /**
77  * Utility class for dynamic range related operations.
78  */
79 public class DynamicRangeUtil {
80     public static final Map<Integer, Set<Integer>> DR_TO_VP_BIT_DEPTH_MAP = new HashMap<>();
81     public static final Map<Integer, Set<Integer>> DR_TO_VP_FORMAT_MAP = new HashMap<>();
82     public static final Map<Integer, Integer> VP_TO_DR_BIT_DEPTH = new HashMap<>();
83     public static final Map<Integer, Integer> VP_TO_DR_FORMAT_MAP = new HashMap<>();
84     private static final Map<String, Map<DynamicRange, Integer>> MIME_TO_DEFAULT_PROFILE_LEVEL_MAP =
85             new HashMap<>();
86 
DynamicRangeUtil()87     private DynamicRangeUtil() {
88     }
89 
90     static {
91         // DynamicRange bit depth to VideoProfile bit depth.
DR_TO_VP_BIT_DEPTH_MAP.put(BIT_DEPTH_8_BIT, new HashSet<>(singletonList(BIT_DEPTH_8)))92         DR_TO_VP_BIT_DEPTH_MAP.put(BIT_DEPTH_8_BIT, new HashSet<>(singletonList(BIT_DEPTH_8)));
DR_TO_VP_BIT_DEPTH_MAP.put(BIT_DEPTH_10_BIT, new HashSet<>(singletonList(BIT_DEPTH_10)))93         DR_TO_VP_BIT_DEPTH_MAP.put(BIT_DEPTH_10_BIT, new HashSet<>(singletonList(BIT_DEPTH_10)));
DR_TO_VP_BIT_DEPTH_MAP.put(BIT_DEPTH_UNSPECIFIED, new HashSet<>(asList(BIT_DEPTH_8, BIT_DEPTH_10)))94         DR_TO_VP_BIT_DEPTH_MAP.put(BIT_DEPTH_UNSPECIFIED,
95                 new HashSet<>(asList(BIT_DEPTH_8, BIT_DEPTH_10)));
96 
97         // DynamicRange encoding to VideoProfile HDR format.
DR_TO_VP_FORMAT_MAP.put(ENCODING_UNSPECIFIED, new HashSet<>(asList(HDR_NONE, HDR_HLG, HDR_HDR10, HDR_HDR10PLUS, HDR_DOLBY_VISION)))98         DR_TO_VP_FORMAT_MAP.put(ENCODING_UNSPECIFIED, new HashSet<>(asList(HDR_NONE, HDR_HLG,
99                 HDR_HDR10, HDR_HDR10PLUS, HDR_DOLBY_VISION)));
DR_TO_VP_FORMAT_MAP.put(ENCODING_SDR, new HashSet<>(singletonList(HDR_NONE)))100         DR_TO_VP_FORMAT_MAP.put(ENCODING_SDR, new HashSet<>(singletonList(HDR_NONE)));
DR_TO_VP_FORMAT_MAP.put(ENCODING_HDR_UNSPECIFIED, new HashSet<>(asList(HDR_HLG, HDR_HDR10, HDR_HDR10PLUS, HDR_DOLBY_VISION)))101         DR_TO_VP_FORMAT_MAP.put(ENCODING_HDR_UNSPECIFIED,
102                 new HashSet<>(asList(HDR_HLG, HDR_HDR10, HDR_HDR10PLUS, HDR_DOLBY_VISION)));
DR_TO_VP_FORMAT_MAP.put(ENCODING_HLG, new HashSet<>(singletonList(HDR_HLG)))103         DR_TO_VP_FORMAT_MAP.put(ENCODING_HLG, new HashSet<>(singletonList(HDR_HLG)));
DR_TO_VP_FORMAT_MAP.put(ENCODING_HDR10, new HashSet<>(singletonList(HDR_HDR10)))104         DR_TO_VP_FORMAT_MAP.put(ENCODING_HDR10, new HashSet<>(singletonList(HDR_HDR10)));
DR_TO_VP_FORMAT_MAP.put(ENCODING_HDR10_PLUS, new HashSet<>(singletonList(HDR_HDR10PLUS)))105         DR_TO_VP_FORMAT_MAP.put(ENCODING_HDR10_PLUS, new HashSet<>(singletonList(HDR_HDR10PLUS)));
DR_TO_VP_FORMAT_MAP.put(ENCODING_DOLBY_VISION, new HashSet<>(singletonList(HDR_DOLBY_VISION)))106         DR_TO_VP_FORMAT_MAP.put(ENCODING_DOLBY_VISION,
107                 new HashSet<>(singletonList(HDR_DOLBY_VISION)));
108 
109         // VideoProfile bit depth to DynamicRange bit depth.
VP_TO_DR_BIT_DEPTH.put(BIT_DEPTH_8, BIT_DEPTH_8_BIT)110         VP_TO_DR_BIT_DEPTH.put(BIT_DEPTH_8, BIT_DEPTH_8_BIT);
VP_TO_DR_BIT_DEPTH.put(BIT_DEPTH_10, BIT_DEPTH_10_BIT)111         VP_TO_DR_BIT_DEPTH.put(BIT_DEPTH_10, BIT_DEPTH_10_BIT);
112 
113         // VideoProfile HDR format to DynamicRange encoding.
VP_TO_DR_FORMAT_MAP.put(HDR_NONE, ENCODING_SDR)114         VP_TO_DR_FORMAT_MAP.put(HDR_NONE, ENCODING_SDR);
VP_TO_DR_FORMAT_MAP.put(HDR_HLG, ENCODING_HLG)115         VP_TO_DR_FORMAT_MAP.put(HDR_HLG, ENCODING_HLG);
VP_TO_DR_FORMAT_MAP.put(HDR_HDR10, ENCODING_HDR10)116         VP_TO_DR_FORMAT_MAP.put(HDR_HDR10, ENCODING_HDR10);
VP_TO_DR_FORMAT_MAP.put(HDR_HDR10PLUS, ENCODING_HDR10_PLUS)117         VP_TO_DR_FORMAT_MAP.put(HDR_HDR10PLUS, ENCODING_HDR10_PLUS);
VP_TO_DR_FORMAT_MAP.put(HDR_DOLBY_VISION, ENCODING_DOLBY_VISION)118         VP_TO_DR_FORMAT_MAP.put(HDR_DOLBY_VISION, ENCODING_DOLBY_VISION);
119 
120         //--------------------------------------------------------------------------------------//
121         // Default CodecProfileLevel mappings from                                              //
122         // frameworks/av/media/codec2/sfplugin/utils/Codec2Mapper.cpp                           //
123         //--------------------------------------------------------------------------------------//
124         // DynamicRange encodings to HEVC profiles
125         Map<DynamicRange, Integer> hevcMap = new HashMap<>();
hevcMap.put(SDR, HEVCProfileMain)126         hevcMap.put(SDR, HEVCProfileMain);
hevcMap.put(HLG_10_BIT, HEVCProfileMain10)127         hevcMap.put(HLG_10_BIT, HEVCProfileMain10);
hevcMap.put(HDR10_10_BIT, HEVCProfileMain10HDR10)128         hevcMap.put(HDR10_10_BIT, HEVCProfileMain10HDR10);
hevcMap.put(HDR10_PLUS_10_BIT, HEVCProfileMain10HDR10Plus)129         hevcMap.put(HDR10_PLUS_10_BIT, HEVCProfileMain10HDR10Plus);
130 
131         // DynamicRange encodings to AV1 profiles for YUV 4:2:0 chroma subsampling
132         Map<DynamicRange, Integer> av1420Map = new HashMap<>();
av1420Map.put(SDR, AV1ProfileMain8)133         av1420Map.put(SDR, AV1ProfileMain8);
av1420Map.put(HLG_10_BIT, AV1ProfileMain10)134         av1420Map.put(HLG_10_BIT, AV1ProfileMain10);
av1420Map.put(HDR10_10_BIT, AV1ProfileMain10HDR10)135         av1420Map.put(HDR10_10_BIT, AV1ProfileMain10HDR10);
av1420Map.put(HDR10_PLUS_10_BIT, AV1ProfileMain10HDR10Plus)136         av1420Map.put(HDR10_PLUS_10_BIT, AV1ProfileMain10HDR10Plus);
137 
138         // DynamicRange encodings to VP9 profile for YUV 4:2:0 chroma subsampling
139         Map<DynamicRange, Integer> vp9420Map = new HashMap<>();
vp9420Map.put(SDR, VP9Profile0)140         vp9420Map.put(SDR, VP9Profile0);
vp9420Map.put(HLG_10_BIT, VP9Profile2)141         vp9420Map.put(HLG_10_BIT, VP9Profile2);
vp9420Map.put(HDR10_10_BIT, VP9Profile2HDR)142         vp9420Map.put(HDR10_10_BIT, VP9Profile2HDR);
vp9420Map.put(HDR10_PLUS_10_BIT, VP9Profile2HDR10Plus)143         vp9420Map.put(HDR10_PLUS_10_BIT, VP9Profile2HDR10Plus);
144 
145         // Dolby vision encodings to dolby vision profiles
146         Map<DynamicRange, Integer> dvMap = new HashMap<>();
147         // Taken from the (now deprecated) Dolby Vision Profile Specification 1.3.3
148         // DV Profile 8 (10-bit HEVC)
dvMap.put(DOLBY_VISION_10_BIT, DolbyVisionProfileDvheSt)149         dvMap.put(DOLBY_VISION_10_BIT, DolbyVisionProfileDvheSt);
150         // DV Profile 9 (8-bit AVC)
dvMap.put(DOLBY_VISION_8_BIT, DolbyVisionProfileDvavSe)151         dvMap.put(DOLBY_VISION_8_BIT, DolbyVisionProfileDvavSe);
152 
153         // Combine all mime type maps
MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, hevcMap)154         MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, hevcMap);
MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, av1420Map)155         MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, av1420Map);
MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, vp9420Map)156         MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, vp9420Map);
MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION, dvMap)157         MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION, dvMap);
158         //--------------------------------------------------------------------------------------//
159     }
160 
161     /**
162      * Returns a set of possible HDR formats for the given {@link DynamicRange}.
163      *
164      * <p>The returned HDR formats are those defined in
165      * {@link android.media.EncoderProfiles.VideoProfile} prefixed with {@code HDR_}, such as
166      * {@link android.media.EncoderProfiles.VideoProfile#HDR_HLG}.
167      *
168      * <p>Returns an empty set if no HDR formats are supported for the provided dynamic range.
169      */
dynamicRangeToVideoProfileHdrFormats( @onNull DynamicRange dynamicRange)170     public static @NonNull Set<Integer> dynamicRangeToVideoProfileHdrFormats(
171             @NonNull DynamicRange dynamicRange) {
172         Set<Integer> hdrFormats = DR_TO_VP_FORMAT_MAP.get(dynamicRange.getEncoding());
173         if (hdrFormats == null) {
174             hdrFormats = Collections.emptySet();
175         }
176         return hdrFormats;
177     }
178 
179     /**
180      * Returns a set of possible bit depths for the given {@link DynamicRange}.
181      *
182      * <p>The returned bit depths are the defined in
183      * {@link androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy} prefixed with {@code
184      * BIT_DEPTH_}, such as
185      * {@link androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy#BIT_DEPTH_10}.
186      *
187      * <p>Returns an empty set if no bit depths are supported for the provided dynamic range.
188      */
dynamicRangeToVideoProfileBitDepth( @onNull DynamicRange dynamicRange)189     public static @NonNull Set<Integer> dynamicRangeToVideoProfileBitDepth(
190             @NonNull DynamicRange dynamicRange) {
191         Set<Integer> bitDepths = DR_TO_VP_BIT_DEPTH_MAP.get(dynamicRange.getBitDepth());
192         if (bitDepths == null) {
193             bitDepths = Collections.emptySet();
194         }
195         return bitDepths;
196     }
197 
198     /**
199      * Returns a codec profile level for a given mime type and dynamic range.
200      *
201      * <p>If the mime type or dynamic range is not supported, returns
202      * {@link EncoderProfilesProxy#CODEC_PROFILE_NONE}.
203      *
204      * <p>Only fully-specified dynamic ranges are supported. All other dynamic ranges will return
205      * {@link EncoderProfilesProxy#CODEC_PROFILE_NONE}.
206      */
dynamicRangeToCodecProfileLevelForMime(@onNull String mimeType, @NonNull DynamicRange dynamicRange)207     public static int dynamicRangeToCodecProfileLevelForMime(@NonNull String mimeType,
208             @NonNull DynamicRange dynamicRange) {
209         Map<DynamicRange, Integer> hdrToProfile = MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.get(mimeType);
210         if (hdrToProfile != null) {
211             Integer profile = hdrToProfile.get(dynamicRange);
212             if (profile != null) {
213                 return profile;
214             }
215         }
216 
217         return EncoderProfilesProxy.CODEC_PROFILE_NONE;
218     }
219 
220     /**
221      * Returns the encoding of {@link DynamicRange} for the given HDR format of
222      * {@link androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy}.
223      *
224      * @throws IllegalArgumentException if the input HDR format is not defined in VideoProfileProxy.
225      */
videoProfileHdrFormatsToDynamicRangeEncoding(int hdrFormat)226     public static int videoProfileHdrFormatsToDynamicRangeEncoding(int hdrFormat) {
227         checkArgument(VP_TO_DR_FORMAT_MAP.containsKey(hdrFormat));
228         return requireNonNull(VP_TO_DR_FORMAT_MAP.get(hdrFormat));
229     }
230 
231     /**
232      * Returns the bit depth of {@link DynamicRange} for the given bit depth of
233      * {@link androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy}.
234      *
235      * @throws IllegalArgumentException if the input bit depth is not defined in VideoProfileProxy.
236      */
videoProfileBitDepthToDynamicRangeBitDepth(int vpBitDepth)237     public static int videoProfileBitDepthToDynamicRangeBitDepth(int vpBitDepth) {
238         checkArgument(VP_TO_DR_BIT_DEPTH.containsKey(vpBitDepth));
239         return requireNonNull(VP_TO_DR_BIT_DEPTH.get(vpBitDepth));
240     }
241 
242     /**
243      * Checks if the HDR settings match between a {@link EncoderProfilesProxy.VideoProfileProxy}
244      * and a {@link DynamicRange}.
245      *
246      * <p>HDR settings includes bit depth and encoding.
247      */
isHdrSettingsMatched( EncoderProfilesProxy.@onNull VideoProfileProxy videoProfile, @NonNull DynamicRange dynamicRange)248     public static boolean isHdrSettingsMatched(
249             EncoderProfilesProxy.@NonNull VideoProfileProxy videoProfile,
250             @NonNull DynamicRange dynamicRange) {
251         return isBitDepthMatched(videoProfile.getBitDepth(), dynamicRange)
252                 && isHdrEncodingMatched(videoProfile.getHdrFormat(), dynamicRange);
253     }
254 
isBitDepthMatched(int bitDepth, @NonNull DynamicRange dynamicRange)255     private static boolean isBitDepthMatched(int bitDepth, @NonNull DynamicRange dynamicRange) {
256         Set<Integer> matchedBitDepths = DR_TO_VP_BIT_DEPTH_MAP.get(dynamicRange.getBitDepth());
257         return matchedBitDepths != null && matchedBitDepths.contains(bitDepth);
258     }
259 
isHdrEncodingMatched(int hdrFormat, @NonNull DynamicRange dynamicRange)260     private static boolean isHdrEncodingMatched(int hdrFormat, @NonNull DynamicRange dynamicRange) {
261         Set<Integer> matchedHdrEncodings = DR_TO_VP_FORMAT_MAP.get(dynamicRange.getEncoding());
262         return matchedHdrEncodings != null && matchedHdrEncodings.contains(hdrFormat);
263     }
264 }
265