SizeCannotEncodeVideoQuirk.java

/*
 * Copyright 2024 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.compat.quirk;

import static androidx.camera.core.impl.utils.TransformUtils.rectToSize;
import static androidx.camera.core.impl.utils.TransformUtils.rotateSize;

import static java.util.Collections.singletonList;

import android.graphics.Rect;
import android.os.Build;
import android.util.Size;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.impl.Quirk;
import androidx.camera.video.internal.encoder.VideoEncoderInfo;

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

/**
 * <p>QuirkSummary
 *     Bug Id: 318036483
 *     Description: On MotoC, resolution 720x1280 will crash the media server while encoding. The
 *                  workaround is to adjust the resolution according to encoder's alignment.
 *                  E.g. the workaround will adjust 720x1280 to 720x1264 when the height
 *                  alignment is 16.
 *     Device(s): MotoC
 */
@RequiresApi(21)
public class SizeCannotEncodeVideoQuirk implements Quirk {

    static boolean load() {
        return isMotoC();
    }

    private static boolean isMotoC() {
        return "motorola".equalsIgnoreCase(Build.BRAND) && "moto c".equalsIgnoreCase(Build.MODEL);
    }

    @NonNull
    private static Set<Size> getProblematicSizes() {
        if (isMotoC()) {
            return new HashSet<>(singletonList(new Size(720, 1280)));
        }
        return Collections.emptySet();
    }

    /** Checks if the size is the problematic size for encoding. */
    public boolean isProblematicEncodeSize(@NonNull Size size) {
        return getProblematicSizes().contains(size);
    }

    /**
     * Adjusts the input crop rect if its size is the problematic size for encoding. Otherwise
     * returns the original crop rect.
     *
     * @param cropRectWithoutRotation the input crop rect without rotation involved.
     * @param rotationDegrees         the rotation degrees that should apply to the input crop rect.
     * @param videoEncoderInfo        the video encoder info.
     * @return the adjusted crop rect.
     */
    @NonNull
    public Rect adjustCropRectForProblematicEncodeSize(@NonNull Rect cropRectWithoutRotation,
            int rotationDegrees, @Nullable VideoEncoderInfo videoEncoderInfo) {
        Size sizeToEncode = rotateSize(rectToSize(cropRectWithoutRotation), rotationDegrees);
        if (!isProblematicEncodeSize(sizeToEncode)) {
            return cropRectWithoutRotation;
        }
        int halfAlignment =
                videoEncoderInfo != null ? videoEncoderInfo.getHeightAlignment() / 2 : 8;
        Rect rectToAdjust = new Rect(cropRectWithoutRotation);
        // Adjust the rect from the center of the height side and keep the same orientation.
        // E.g. On MotoC, l:0,t:0,r:1280,b:720 -> l:8,t:0,r:1272,b:720
        if (cropRectWithoutRotation.width() == sizeToEncode.getHeight()) {
            rectToAdjust.left += halfAlignment;
            rectToAdjust.right -= halfAlignment;
        } else {
            rectToAdjust.top += halfAlignment;
            rectToAdjust.bottom -= halfAlignment;
        }
        return rectToAdjust;
    }
}