DynamicRangesCompat.java

/*
 * Copyright 2022 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.camera2.internal.compat.params;

import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.params.DynamicRangeProfiles;
import android.os.Build;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.core.DynamicRange;
import androidx.core.util.Preconditions;

import java.util.Set;

/**
 * Helper for accessing features in DynamicRangeProfiles in a backwards compatible fashion.
 */
@RequiresApi(21)
public final class DynamicRangesCompat {

    private final DynamicRangeProfilesCompatImpl mImpl;

    DynamicRangesCompat(@NonNull DynamicRangeProfilesCompatImpl impl) {
        mImpl = impl;
    }

    /**
     * Returns a set of supported {@link DynamicRange} that can be referenced in a single
     * capture request.
     *
     * <p>For example if a particular 10-bit output capable device returns (STANDARD,
     * HLG10, HDR10) as result from calling {@link #getSupportedDynamicRanges()} and
     * getProfileCaptureRequestConstraints(long) returns (STANDARD, HLG10) when given an argument
     * of STANDARD. This means that the corresponding camera device will only accept and process
     * capture requests that reference outputs configured using HDR10 dynamic range or
     * alternatively some combination of STANDARD and HLG10. However trying to queue capture
     * requests to outputs that reference both HDR10 and STANDARD/HLG10 will result in
     * IllegalArgumentException.
     *
     * <p>The list will be empty in case there are no constraints for the given dynamic range.
     *
     * @param dynamicRange The dynamic range that will be checked for constraints
     * @return non-modifiable set of dynamic ranges
     * @throws IllegalArgumentException If the dynamic range argument is not within the set
     * returned by {@link #getSupportedDynamicRanges()}.
     */
    @NonNull
    public Set<DynamicRange> getDynamicRangeCaptureRequestConstraints(
            @NonNull DynamicRange dynamicRange) {
        return mImpl.getDynamicRangeCaptureRequestConstraints(dynamicRange);
    }

    /**
     * Returns a set of supported dynamic ranges.
     *
     * @return a non-modifiable set of dynamic ranges.
     */
    @NonNull
    public Set<DynamicRange> getSupportedDynamicRanges() {
        return mImpl.getSupportedDynamicRanges();
    }

    /**
     * Checks whether a given dynamic range is suitable for latency sensitive use cases.
     *
     * <p>Due to internal lookahead logic, camera outputs configured with some dynamic range
     * profiles may experience additional latency greater than 3 buffers. Using camera outputs
     * with such dynamic ranges for latency sensitive use cases such as camera preview is not
     * recommended. Dynamic ranges that have such extra streaming delay are typically utilized for
     * scenarios such as offscreen video recording.
     *
     * @param dynamicRange The dynamic range to check for extra latency
     * @return {@code true} if the given profile is not suitable for latency sensitive use cases,
     * {@code false} otherwise.
     * @throws IllegalArgumentException If the dynamic range argument is not within the set
     * returned by {@link #getSupportedDynamicRanges()}.
     */
    public boolean isExtraLatencyPresent(@NonNull DynamicRange dynamicRange) {
        return mImpl.isExtraLatencyPresent(dynamicRange);
    }

    /**
     * Returns a {@link DynamicRangesCompat} using the capabilities derived from the provided
     * characteristics.
     *
     * @param characteristics the characteristics used to derive dynamic range information.
     * @return a {@link DynamicRangesCompat} object.
     */
    @NonNull
    public static DynamicRangesCompat fromCameraCharacteristics(
            @NonNull CameraCharacteristicsCompat characteristics) {
        DynamicRangesCompat rangesCompat = null;
        if (Build.VERSION.SDK_INT >= 33) {
            rangesCompat = toDynamicRangesCompat(characteristics.get(
                    CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES));
        }

        return (rangesCompat == null) ? DynamicRangesCompatBaseImpl.COMPAT_INSTANCE : rangesCompat;
    }

    /**
     * Creates an instance from a framework android.hardware.camera2.params.DynamicRangeProfiles
     * object.
     *
     * @param dynamicRangeProfiles a {@link android.hardware.camera2.params.DynamicRangeProfiles}.
     * @return an equivalent {@link DynamicRangesCompat} object.
     */
    @Nullable
    @RequiresApi(33)
    public static DynamicRangesCompat toDynamicRangesCompat(
            @Nullable DynamicRangeProfiles dynamicRangeProfiles) {
        if (dynamicRangeProfiles == null) {
            return null;
        }

        Preconditions.checkState(Build.VERSION.SDK_INT >= 33, "DynamicRangeProfiles can only be "
                + "converted to DynamicRangesCompat on API 33 or higher.");

        return new DynamicRangesCompat(new DynamicRangesCompatApi33Impl(dynamicRangeProfiles));
    }

    /**
     * Returns the underlying framework
     * {@link android.hardware.camera2.params.DynamicRangeProfiles}.
     *
     * @return the underlying {@link android.hardware.camera2.params.DynamicRangeProfiles} or
     * {@code null} if the device doesn't support 10 bit dynamic range.
     */
    @Nullable
    @RequiresApi(33)
    public DynamicRangeProfiles toDynamicRangeProfiles() {
        Preconditions.checkState(Build.VERSION.SDK_INT >= 33, "DynamicRangesCompat can only be "
                + "converted to DynamicRangeProfiles on API 33 or higher.");
        return mImpl.unwrap();
    }

    interface DynamicRangeProfilesCompatImpl {
        @NonNull
        Set<DynamicRange> getDynamicRangeCaptureRequestConstraints(
                @NonNull DynamicRange dynamicRange);

        @NonNull
        Set<DynamicRange> getSupportedDynamicRanges();

        boolean isExtraLatencyPresent(@NonNull DynamicRange dynamicRange);

        @Nullable
        DynamicRangeProfiles unwrap();
    }
}