Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc7bab29 authored by Biswarup Pal's avatar Biswarup Pal
Browse files

Improve virtual camera config API

- Add sensor orientation to VirtualCameraStreamConfig
- Add frame rate to VirtualCameraStreamConfig
- Fixed bounds for dimensions in VirtualCameraStreamConfig
- Refactor VirtualCameraCallback to include minimum
information (width, height, format) regarding the input
surface in onStreamConfigured.

Test: atest CtsVirtualDevicesCameraTestCases, VirtualCameraControllerTest
Bug: 310857519
Change-Id: I69ffb487fabd72c4dbf7e610fcf2845cbb6ece11
parent 8fa40be2
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -3356,30 +3356,36 @@ package android.companion.virtual.camera {
  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback {
    method public default void onProcessCaptureRequest(int, long);
    method public void onStreamClosed(int);
    method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig);
    method public void onStreamConfigured(int, @NonNull android.view.Surface, @IntRange(from=1) int, @IntRange(from=1) int, int);
  }
  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
    method public int describeContents();
    method @NonNull public String getName();
    method public int getSensorOrientation();
    method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
    field public static final int SENSOR_ORIENTATION_0 = 0; // 0x0
    field public static final int SENSOR_ORIENTATION_180 = 180; // 0xb4
    field public static final int SENSOR_ORIENTATION_270 = 270; // 0x10e
    field public static final int SENSOR_ORIENTATION_90 = 90; // 0x5a
  }
  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder {
    ctor public VirtualCameraConfig.Builder();
    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int);
    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setSensorOrientation(int);
    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
  }
  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable {
    ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int);
    method public int describeContents();
    method public int getFormat();
    method @IntRange(from=1) public int getHeight();
    method @IntRange(from=1) public int getMaximumFramesPerSecond();
    method @IntRange(from=1) public int getWidth();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraStreamConfig> CREATOR;
+15 −17
Original line number Diff line number Diff line
@@ -13,9 +13,9 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.companion.virtual.camera;

import android.companion.virtual.camera.VirtualCameraStreamConfig;
import android.view.Surface;

/**
@@ -32,36 +32,34 @@ interface IVirtualCameraCallback {
     *
     * @param streamId The id of the configured stream
     * @param surface The surface to write data into for this stream
     * @param streamConfig The image data configuration for this stream
     * @param width The width of the surface
     * @param height The height of the surface
     * @param format The pixel format of the surface
     */
    oneway void onStreamConfigured(
            int streamId,
            in Surface surface,
            in VirtualCameraStreamConfig streamConfig);
    oneway void onStreamConfigured(int streamId, in Surface surface, int width, int height,
            int format);

    /**
     * The client application is requesting a camera frame for the given streamId and frameId.
     *
     * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
     * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
     * VirtualCameraStreamConfig)} call.
     * this stream that was provided during the
     * {@link #onStreamConfigured(int, Surface, int, int, int)} call.
     *
     * @param streamId The streamId for which the frame is requested. This corresponds to the
     *     streamId that was given in {@link #onStreamConfigured(int, Surface,
     *     VirtualCameraStreamConfig)}
     *     streamId that was given in {@link #onStreamConfigured(int, Surface, int, int, int)}
     * @param frameId The frameId that is being requested. Each request will have a different
     *     frameId, that will be increasing for each call with a particular streamId.
     */
    oneway void onProcessCaptureRequest(int streamId, long frameId);

    /**
     * The stream previously configured when {@link #onStreamConfigured(int, Surface,
     * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
     * freed. The Surface was disposed on the client side and should not be used anymore by the
     * virtual camera owner.
     * The stream previously configured when
     * {@link #onStreamConfigured(int, Surface, int, int, int)} was called is now being closed and
     * associated resources can be freed. The Surface was disposed on the client side and should not
     * be used anymore by the virtual camera owner.
     *
     * @param streamId The id of the stream that was closed.
     */
    oneway void onStreamClosed(int streamId);

}
 No newline at end of file
+15 −13
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@
package android.companion.virtual.camera;

import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
import android.graphics.ImageFormat;
import android.view.Surface;

import java.util.concurrent.Executor;
@@ -41,33 +43,33 @@ public interface VirtualCameraCallback {
     *
     * @param streamId The id of the configured stream
     * @param surface The surface to write data into for this stream
     * @param streamConfig The image data configuration for this stream
     * @param width The width of the surface
     * @param height The height of the surface
     * @param format The {@link ImageFormat} of the surface
     */
    void onStreamConfigured(
            int streamId,
            @NonNull Surface surface,
            @NonNull VirtualCameraStreamConfig streamConfig);
    void onStreamConfigured(int streamId, @NonNull Surface surface,
            @IntRange(from = 1) int width, @IntRange(from = 1) int height,
            @ImageFormat.Format int format);

    /**
     * The client application is requesting a camera frame for the given streamId and frameId.
     *
     * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
     * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
     * VirtualCameraStreamConfig)} call.
     * this stream that was provided during the
     * {@link #onStreamConfigured(int, Surface, int, int, int)} call.
     *
     * @param streamId The streamId for which the frame is requested. This corresponds to the
     *     streamId that was given in {@link #onStreamConfigured(int, Surface,
     *     VirtualCameraStreamConfig)}
     *     streamId that was given in {@link #onStreamConfigured(int, Surface, int, int, int)}
     * @param frameId The frameId that is being requested. Each request will have a different
     *     frameId, that will be increasing for each call with a particular streamId.
     */
    default void onProcessCaptureRequest(int streamId, long frameId) {}

    /**
     * The stream previously configured when {@link #onStreamConfigured(int, Surface,
     * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
     * freed. The Surface corresponding to that streamId was disposed on the client side and should
     * not be used anymore by the virtual camera owner
     * The stream previously configured when
     * {@link #onStreamConfigured(int, Surface, int, int, int)} was called is now being closed and
     * associated resources can be freed. The Surface corresponding to that streamId was disposed on
     * the client side and should not be used anymore by the virtual camera owner.
     *
     * @param streamId The id of the stream that was closed.
     */
+2 −1
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 * 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.
@@ -13,6 +13,7 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.companion.virtual.camera;

/** @hide */
+123 −15
Original line number Diff line number Diff line
@@ -19,16 +19,21 @@ package android.companion.virtual.camera;
import static java.util.Objects.requireNonNull;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import android.view.Surface;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;
import java.util.concurrent.Executor;

@@ -43,15 +48,48 @@ import java.util.concurrent.Executor;
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public final class VirtualCameraConfig implements Parcelable {

    /**
     * Sensor orientation of {@code 0} degrees.
     * @see #getSensorOrientation
     */
    public static final int SENSOR_ORIENTATION_0 = 0;
    /**
     * Sensor orientation of {@code 90} degrees.
     * @see #getSensorOrientation
     */
    public static final int SENSOR_ORIENTATION_90 = 90;
    /**
     * Sensor orientation of {@code 180} degrees.
     * @see #getSensorOrientation
     */
    public static final int SENSOR_ORIENTATION_180 = 180;
    /**
     * Sensor orientation of {@code 270} degrees.
     * @see #getSensorOrientation
     */
    public static final int SENSOR_ORIENTATION_270 = 270;
    /** @hide */
    @IntDef(prefix = {"SENSOR_ORIENTATION_"}, value = {
            SENSOR_ORIENTATION_0,
            SENSOR_ORIENTATION_90,
            SENSOR_ORIENTATION_180,
            SENSOR_ORIENTATION_270
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface SensorOrientation {}

    private final String mName;
    private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
    private final IVirtualCameraCallback mCallback;
    @SensorOrientation
    private final int mSensorOrientation;

    private VirtualCameraConfig(
            @NonNull String name,
            @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
            @NonNull Executor executor,
            @NonNull VirtualCameraCallback callback) {
            @NonNull VirtualCameraCallback callback,
            @SensorOrientation int sensorOrientation) {
        mName = requireNonNull(name, "Missing name");
        mStreamConfigurations =
                Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
@@ -63,6 +101,7 @@ public final class VirtualCameraConfig implements Parcelable {
                new VirtualCameraCallbackInternal(
                        requireNonNull(callback, "Missing callback"),
                        requireNonNull(executor, "Missing callback executor"));
        mSensorOrientation = sensorOrientation;
    }

    private VirtualCameraConfig(@NonNull Parcel in) {
@@ -73,6 +112,7 @@ public final class VirtualCameraConfig implements Parcelable {
                        in.readParcelableArray(
                                VirtualCameraStreamConfig.class.getClassLoader(),
                                VirtualCameraStreamConfig.class));
        mSensorOrientation = in.readInt();
    }

    @Override
@@ -86,6 +126,7 @@ public final class VirtualCameraConfig implements Parcelable {
        dest.writeStrongInterface(mCallback);
        dest.writeParcelableArray(
                mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
        dest.writeInt(mSensorOrientation);
    }

    /**
@@ -100,7 +141,7 @@ public final class VirtualCameraConfig implements Parcelable {
     * Returns an unmodifiable set of the stream configurations added to this {@link
     * VirtualCameraConfig}.
     *
     * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int)
     * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int, int)
     */
    @NonNull
    public Set<VirtualCameraStreamConfig> getStreamConfigs() {
@@ -117,11 +158,21 @@ public final class VirtualCameraConfig implements Parcelable {
        return mCallback;
    }

    /**
     * Returns the sensor orientation of this stream, which represents the clockwise angle (in
     * degrees) through which the output image needs to be rotated to be upright on the device
     * screen in its native orientation. Returns {@link #SENSOR_ORIENTATION_0} if omitted.
     */
    @SensorOrientation
    public int getSensorOrientation() {
        return mSensorOrientation;
    }

    /**
     * Builder for {@link VirtualCameraConfig}.
     *
     * <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met:
     * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
     * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int, int)}.
     * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
     *     VirtualCameraCallback)}
     * <li>A camera name must be set with {@link #setName(String)}
@@ -133,6 +184,7 @@ public final class VirtualCameraConfig implements Parcelable {
        private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
        private Executor mCallbackExecutor;
        private VirtualCameraCallback mCallback;
        private int mSensorOrientation = SENSOR_ORIENTATION_0;

        /**
         * Set the name of the virtual camera instance.
@@ -150,19 +202,67 @@ public final class VirtualCameraConfig implements Parcelable {
         *
         * @param width The width of the stream.
         * @param height The height of the stream.
         * @param format The {@link ImageFormat} of the stream.
         * @param format The input format of the stream. Supported formats are
         *               {@link ImageFormat#YUV_420_888} and {@link PixelFormat#RGBA_8888}.
         * @param maximumFramesPerSecond The maximum frame rate (in frames per second) for the
         *                               stream.
         *
         * @throws IllegalArgumentException if invalid format or dimensions are passed.
         * @throws IllegalArgumentException if invalid dimensions, format or frame rate are passed.
         */
        @NonNull
        public Builder addStreamConfig(int width, int height, @ImageFormat.Format int format) {
            if (width <= 0 || height <= 0) {
                throw new IllegalArgumentException("Invalid dimensions passed for stream config");
        public Builder addStreamConfig(
                @IntRange(from = 1) int width,
                @IntRange(from = 1) int height,
                @ImageFormat.Format int format,
                @IntRange(from = 1) int maximumFramesPerSecond) {
            // TODO(b/310857519): Check dimension upper limits based on the maximum texture size
            // supported by the current device, instead of hardcoded limits.
            if (width <= 0 || width > VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT) {
                throw new IllegalArgumentException(
                        "Invalid width passed for stream config: " + width
                                + ", must be between 1 and "
                                + VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT);
            }
            if (!ImageFormat.isPublicFormat(format)) {
                throw new IllegalArgumentException("Invalid format passed for stream config");
            if (height <= 0 || height > VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT) {
                throw new IllegalArgumentException(
                        "Invalid height passed for stream config: " + height
                                + ", must be between 1 and "
                                + VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT);
            }
            mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format));
            if (!isFormatSupported(format)) {
                throw new IllegalArgumentException(
                        "Invalid format passed for stream config: " + format);
            }
            if (maximumFramesPerSecond <= 0
                    || maximumFramesPerSecond > VirtualCameraStreamConfig.MAX_FPS_UPPER_LIMIT) {
                throw new IllegalArgumentException(
                        "Invalid maximumFramesPerSecond, must be greater than 0 and less than "
                                + VirtualCameraStreamConfig.MAX_FPS_UPPER_LIMIT);
            }
            mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format,
                    maximumFramesPerSecond));
            return this;
        }

        /**
         * Sets the sensor orientation of the virtual camera. This field is optional and can be
         * omitted (defaults to {@link #SENSOR_ORIENTATION_0}).
         *
         * @param sensorOrientation The sensor orientation of the camera, which represents the
         *                          clockwise angle (in degrees) through which the output image
         *                          needs to be rotated to be upright on the device screen in its
         *                          native orientation.
         */
        @NonNull
        public Builder setSensorOrientation(@SensorOrientation int sensorOrientation) {
            if (sensorOrientation != SENSOR_ORIENTATION_0
                    && sensorOrientation != SENSOR_ORIENTATION_90
                    && sensorOrientation != SENSOR_ORIENTATION_180
                    && sensorOrientation != SENSOR_ORIENTATION_270) {
                throw new IllegalArgumentException(
                        "Invalid sensor orientation: " + sensorOrientation);
            }
            mSensorOrientation = sensorOrientation;
            return this;
        }

@@ -193,7 +293,7 @@ public final class VirtualCameraConfig implements Parcelable {
        @NonNull
        public VirtualCameraConfig build() {
            return new VirtualCameraConfig(
                    mName, mStreamConfigurations, mCallbackExecutor, mCallback);
                    mName, mStreamConfigurations, mCallbackExecutor, mCallback, mSensorOrientation);
        }
    }

@@ -208,9 +308,10 @@ public final class VirtualCameraConfig implements Parcelable {
        }

        @Override
        public void onStreamConfigured(
                int streamId, Surface surface, VirtualCameraStreamConfig streamConfig) {
            mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, streamConfig));
        public void onStreamConfigured(int streamId, Surface surface, int width, int height,
                int format) {
            mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, width, height,
                    format));
        }

        @Override
@@ -237,4 +338,11 @@ public final class VirtualCameraConfig implements Parcelable {
                    return new VirtualCameraConfig[size];
                }
            };

    private static boolean isFormatSupported(@ImageFormat.Format int format) {
        return switch (format) {
            case ImageFormat.YUV_420_888, PixelFormat.RGBA_8888 -> true;
            default -> false;
        };
    }
}
Loading