DynamicRangeUtil.java

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.video.internal.utils;

import static android.media.EncoderProfiles.VideoProfile.HDR_DOLBY_VISION;
import static android.media.EncoderProfiles.VideoProfile.HDR_HDR10;
import static android.media.EncoderProfiles.VideoProfile.HDR_HDR10PLUS;
import static android.media.EncoderProfiles.VideoProfile.HDR_HLG;
import static android.media.EncoderProfiles.VideoProfile.HDR_NONE;
import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10;
import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10;
import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10Plus;
import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8;
import static android.media.MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe;
import static android.media.MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt;
import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain;
import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10;
import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10;
import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus;
import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile0;
import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2;
import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR;
import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus;

import static androidx.camera.core.DynamicRange.BIT_DEPTH_10_BIT;
import static androidx.camera.core.DynamicRange.BIT_DEPTH_8_BIT;
import static androidx.camera.core.DynamicRange.BIT_DEPTH_UNSPECIFIED;
import static androidx.camera.core.DynamicRange.DOLBY_VISION_10_BIT;
import static androidx.camera.core.DynamicRange.DOLBY_VISION_8_BIT;
import static androidx.camera.core.DynamicRange.ENCODING_DOLBY_VISION;
import static androidx.camera.core.DynamicRange.ENCODING_HDR10;
import static androidx.camera.core.DynamicRange.ENCODING_HDR10_PLUS;
import static androidx.camera.core.DynamicRange.ENCODING_HDR_UNSPECIFIED;
import static androidx.camera.core.DynamicRange.ENCODING_HLG;
import static androidx.camera.core.DynamicRange.ENCODING_SDR;
import static androidx.camera.core.DynamicRange.ENCODING_UNSPECIFIED;
import static androidx.camera.core.DynamicRange.HDR10_10_BIT;
import static androidx.camera.core.DynamicRange.HDR10_PLUS_10_BIT;
import static androidx.camera.core.DynamicRange.HLG_10_BIT;
import static androidx.camera.core.DynamicRange.SDR;
import static androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_10;
import static androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy.BIT_DEPTH_8;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

import android.media.MediaFormat;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.core.DynamicRange;
import androidx.camera.core.impl.EncoderProfilesProxy;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Utility class for dynamic range related operations.
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class DynamicRangeUtil {
    public static final Map<Integer, Set<Integer>> DR_TO_VP_BIT_DEPTH_MAP = new HashMap<>();
    public static final Map<Integer, Set<Integer>> DR_TO_VP_FORMAT_MAP = new HashMap<>();
    public static final Map<Integer, Integer> VP_TO_DR_FORMAT_MAP = new HashMap<>();
    private static final Map<String, Map<DynamicRange, Integer>> MIME_TO_DEFAULT_PROFILE_LEVEL_MAP =
            new HashMap<>();

    private DynamicRangeUtil() {
    }

    static {
        // DynamicRange bit depth to VideoProfile bit depth.
        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)));
        DR_TO_VP_BIT_DEPTH_MAP.put(BIT_DEPTH_UNSPECIFIED,
                new HashSet<>(asList(BIT_DEPTH_8, BIT_DEPTH_10)));

        // 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)));
        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)));
        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)));
        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)));

        // VideoProfile HDR format to DynamicRange encoding.
        VP_TO_DR_FORMAT_MAP.put(HDR_NONE, ENCODING_SDR);
        VP_TO_DR_FORMAT_MAP.put(HDR_HLG, ENCODING_HLG);
        VP_TO_DR_FORMAT_MAP.put(HDR_HDR10, ENCODING_HDR10);
        VP_TO_DR_FORMAT_MAP.put(HDR_HDR10PLUS, ENCODING_HDR10_PLUS);
        VP_TO_DR_FORMAT_MAP.put(HDR_DOLBY_VISION, ENCODING_DOLBY_VISION);

        //--------------------------------------------------------------------------------------//
        // Default CodecProfileLevel mappings from                                              //
        // frameworks/av/media/codec2/sfplugin/utils/Codec2Mapper.cpp                           //
        //--------------------------------------------------------------------------------------//
        // DynamicRange encodings to HEVC profiles
        Map<DynamicRange, Integer> hevcMap = new HashMap<>();
        hevcMap.put(SDR, HEVCProfileMain);
        hevcMap.put(HLG_10_BIT, HEVCProfileMain10);
        hevcMap.put(HDR10_10_BIT, HEVCProfileMain10HDR10);
        hevcMap.put(HDR10_PLUS_10_BIT, HEVCProfileMain10HDR10Plus);

        // DynamicRange encodings to AV1 profiles for YUV 4:2:0 chroma subsampling
        Map<DynamicRange, Integer> av1420Map = new HashMap<>();
        av1420Map.put(SDR, AV1ProfileMain8);
        av1420Map.put(HLG_10_BIT, AV1ProfileMain10);
        av1420Map.put(HDR10_10_BIT, AV1ProfileMain10HDR10);
        av1420Map.put(HDR10_PLUS_10_BIT, AV1ProfileMain10HDR10Plus);

        // DynamicRange encodings to VP9 profile for YUV 4:2:0 chroma subsampling
        Map<DynamicRange, Integer> vp9420Map = new HashMap<>();
        vp9420Map.put(SDR, VP9Profile0);
        vp9420Map.put(HLG_10_BIT, VP9Profile2);
        vp9420Map.put(HDR10_10_BIT, VP9Profile2HDR);
        vp9420Map.put(HDR10_PLUS_10_BIT, VP9Profile2HDR10Plus);

        // Dolby vision encodings to dolby vision profiles
        Map<DynamicRange, Integer> dvMap = new HashMap<>();
        // Taken from the (now deprecated) Dolby Vision Profile Specification 1.3.3
        // DV Profile 8 (10-bit HEVC)
        dvMap.put(DOLBY_VISION_10_BIT, DolbyVisionProfileDvheSt);
        // DV Profile 9 (8-bit AVC)
        dvMap.put(DOLBY_VISION_8_BIT, DolbyVisionProfileDvavSe);

        // Combine all mime type maps
        MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, hevcMap);
        MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, av1420Map);
        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);
        //--------------------------------------------------------------------------------------//
    }

    /**
     * Returns a set of possible HDR formats for the given {@link DynamicRange}.
     *
     * <p>The returned HDR formats are those defined in
     * {@link android.media.EncoderProfiles.VideoProfile} prefixed with {@code HDR_}, such as
     * {@link android.media.EncoderProfiles.VideoProfile#HDR_HLG}.
     *
     * <p>Returns an empty set if no HDR formats are supported for the provided dynamic range.
     */
    @NonNull
    public static Set<Integer> dynamicRangeToVideoProfileHdrFormats(
            @NonNull DynamicRange dynamicRange) {
        Set<Integer> hdrFormats = DR_TO_VP_FORMAT_MAP.get(dynamicRange.getEncoding());
        if (hdrFormats == null) {
            hdrFormats = Collections.emptySet();
        }
        return hdrFormats;
    }

    /**
     * Returns a set of possible bit depths for the given {@link DynamicRange}.
     *
     * <p>The returned bit depths are the defined in
     * {@link androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy} prefixed with {@code
     * BIT_DEPTH_}, such as
     * {@link androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy#BIT_DEPTH_10}.
     *
     * <p>Returns an empty set if no bit depths are supported for the provided dynamic range.
     */
    @NonNull
    public static Set<Integer> dynamicRangeToVideoProfileBitDepth(
            @NonNull DynamicRange dynamicRange) {
        Set<Integer> bitDepths = DR_TO_VP_BIT_DEPTH_MAP.get(dynamicRange.getBitDepth());
        if (bitDepths == null) {
            bitDepths = Collections.emptySet();
        }
        return bitDepths;
    }

    /**
     * Returns a codec profile level for a given mime type and dynamic range.
     *
     * <p>If the mime type or dynamic range is not supported, returns
     * {@link EncoderProfilesProxy#CODEC_PROFILE_NONE}.
     *
     * <p>Only fully-specified dynamic ranges are supported. All other dynamic ranges will return
     * {@link EncoderProfilesProxy#CODEC_PROFILE_NONE}.
     */
    public static int dynamicRangeToCodecProfileLevelForMime(@NonNull String mimeType,
            @NonNull DynamicRange dynamicRange) {
        Map<DynamicRange, Integer> hdrToProfile = MIME_TO_DEFAULT_PROFILE_LEVEL_MAP.get(mimeType);
        if (hdrToProfile != null) {
            Integer profile = hdrToProfile.get(dynamicRange);
            if (profile != null) {
                return profile;
            }
        }

        return EncoderProfilesProxy.CODEC_PROFILE_NONE;
    }
}