MediaSpec.java

/*
 * Copyright 2021 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;

import android.annotation.SuppressLint;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.camera.video.internal.encoder.EncoderConfig;
import androidx.core.util.Consumer;

import com.google.auto.value.AutoValue;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * MediaSpec communicates the encoding type and encoder-specific options for both the
 * video and audio inputs to the VideoOutput.
 * @hide
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@RestrictTo(Scope.LIBRARY)
@AutoValue
public abstract class MediaSpec {

    private static final String AUDIO_ENCODER_MIME_MPEG4_DEFAULT = MediaFormat.MIMETYPE_AUDIO_AAC;
    private static final String AUDIO_ENCODER_MIME_WEBM_DEFAULT = MediaFormat.MIMETYPE_AUDIO_VORBIS;
    private static final String VIDEO_ENCODER_MIME_MPEG4_DEFAULT = MediaFormat.MIMETYPE_VIDEO_AVC;
    private static final String VIDEO_ENCODER_MIME_WEBM_DEFAULT = MediaFormat.MIMETYPE_VIDEO_VP8;

    private static final int AAC_DEFAULT_PROFILE = MediaCodecInfo.CodecProfileLevel.AACObjectLC;

    /** The output format representing no preference for output format. */
    public static final int OUTPUT_FORMAT_AUTO = -1;
    /** MPEG4 media file format. */
    public static final int OUTPUT_FORMAT_MPEG_4 = 0;
    /** VP8, VP9 media file format */
    public static final int OUTPUT_FORMAT_WEBM = 1;

    /** @hide */
    @IntDef({OUTPUT_FORMAT_AUTO, OUTPUT_FORMAT_MPEG_4, OUTPUT_FORMAT_WEBM})
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public @interface OutputFormat {
    }

    @NonNull
    static String outputFormatToAudioMime(@OutputFormat int outputFormat) {
        switch (outputFormat) {
            case MediaSpec.OUTPUT_FORMAT_WEBM:
                return AUDIO_ENCODER_MIME_WEBM_DEFAULT;
            case MediaSpec.OUTPUT_FORMAT_MPEG_4:
                // Fall-through
            case MediaSpec.OUTPUT_FORMAT_AUTO:
                // Fall-through
            default:
                return AUDIO_ENCODER_MIME_MPEG4_DEFAULT;
        }
    }

    static int outputFormatToAudioProfile(@OutputFormat int outputFormat) {
        String audioMime = outputFormatToAudioMime(outputFormat);
        if (Objects.equals(audioMime, MediaFormat.MIMETYPE_AUDIO_AAC)) {
            return AAC_DEFAULT_PROFILE;
        }

        return EncoderConfig.CODEC_PROFILE_NONE;
    }

    @NonNull
    static String outputFormatToVideoMime(@OutputFormat int outputFormat) {
        switch (outputFormat) {
            case MediaSpec.OUTPUT_FORMAT_WEBM:
                return VIDEO_ENCODER_MIME_WEBM_DEFAULT;
            case MediaSpec.OUTPUT_FORMAT_MPEG_4:
                // Fall-through
            case MediaSpec.OUTPUT_FORMAT_AUTO:
                // Fall-through
            default:
                return VIDEO_ENCODER_MIME_MPEG4_DEFAULT;
        }
    }

    static int outputFormatToMuxerFormat(@OutputFormat int outputFormat) {
        switch (outputFormat) {
            case MediaSpec.OUTPUT_FORMAT_WEBM:
                return MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM;
            case MediaSpec.OUTPUT_FORMAT_MPEG_4:
                // Fall-through
            case MediaSpec.OUTPUT_FORMAT_AUTO:
                // Fall-through
            default:
                return MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
        }
    }

    // Doesn't allow inheritance outside of package
    MediaSpec() {
    }

    /**
     * Returns a VideoSpec instance.
     */
    @NonNull
    public abstract VideoSpec getVideoSpec();

    /**
     * Returns an AudioSpec instance.
     */
    @NonNull
    public abstract AudioSpec getAudioSpec();

    /**
     * Returns the output file format.
     *
     * @return the file format
     */
    @OutputFormat
    public abstract int getOutputFormat();

    /** Creates a {@link Builder}. */
    @NonNull
    public static Builder builder() {
        return new AutoValue_MediaSpec.Builder()
                .setOutputFormat(OUTPUT_FORMAT_AUTO)
                .setAudioSpec(AudioSpec.builder().build())
                .setVideoSpec(VideoSpec.builder().build());
    }

    /**
     * Returns a {@link Builder} instance with the same property values as this instance.
     */
    @NonNull
    public abstract Builder toBuilder();

    /**
     * The builder for {@link MediaSpec}.
     * @hide
     */
    @RestrictTo(Scope.LIBRARY)
    @SuppressWarnings("StaticFinalBuilder")
    @AutoValue.Builder
    public abstract static class Builder {
        Builder() {
        }

        /**
         * Sets audio related configuration.
         *
         * <p>If not set, contains the default implementation of {@link AudioSpec} returned by
         * {@link AudioSpec.Builder#build()}.
         *
         * <p>The provided specification will override all audio related properties of
         * this media specification. If only some properties need to be modified, use
         * {@link #configureAudio(Consumer)}.
         *
         * <p>To request disabling audio, this should be set to {@link AudioSpec#NO_AUDIO}.
         */
        @NonNull
        public abstract Builder setAudioSpec(@NonNull AudioSpec audioSpec);

        /**
         * Configures the {@link AudioSpec} of this media specification with the given block.
         *
         * <p>This is a convenience method for in-line configuration of the {@link AudioSpec}
         * contained in this media spec. The {@link AudioSpec.Builder} provided to {@code
         * configBlock} is pre-populated with the current state of the internal audio spec.
         *
         * <p>Usage:
         * {@code
         * MediaSpec.Builder mediaSpecBuilder = ...;
         * MediaSpec mediaSpec = mediaSpecBuilder
         *     .configureAudio(audioSpecBuilder -> {
         *         audioSpecBuilder
         *             .setSource(...)
         *             .setSampleRate(...)
         *     })
         *     .setOutputFormat(...)
         *     .build();
         * }
         *
         * @param configBlock A consumer which provides the {@link AudioSpec.Builder} which will
         *                    configure the {@link AudioSpec} of this media specification.
         */
        @NonNull
        @SuppressWarnings("BuilderSetStyle")
        public Builder configureAudio(@NonNull Consumer<AudioSpec.Builder> configBlock) {
            AudioSpec.Builder audioSpecBuilder = getAudioSpec().toBuilder();
            configBlock.accept(audioSpecBuilder);
            setAudioSpec(audioSpecBuilder.build());
            return this;
        }

        /**
         * Gets the existing AudioSpec.
         */
        // This is not meant to be a public API. Only should be used internally within Builder.
        @SuppressLint("KotlinPropertyAccess")
        abstract AudioSpec getAudioSpec();

        /**
         * Sets video related configuration.
         *
         * <p>If not set, contains the default implementation of {@link VideoSpec} returned by
         * {@link VideoSpec.Builder#build()}.
         *
         * <p>The provided specification will override all video related properties of this media
         * specification. If only some properties need to be modified, use
         * {@link #configureVideo(Consumer)}.
         */
        @NonNull
        public abstract Builder setVideoSpec(@NonNull VideoSpec videoSpec);

        /**
         * Configures the {@link VideoSpec} of this media specification with the given block.
         *
         * <p>This is a convenience method for in-line configuration of the {@link VideoSpec}
         * contained in this media spec. The {@link VideoSpec.Builder} provided to {@code
         * configBlock} is pre-populated with the current state of the internal video spec.
         *
         * <p>Usage:
         * {@code
         * MediaSpec.Builder mediaSpecBuilder = ...;
         * MediaSpec mediaSpec = mediaSpecBuilder
         *     .configureVideo(videoSpecBuilder -> {
         *         videoSpecBuilder
         *             .setQualitySelector(...)
         *             .setBitrate(...)
         *     })
         *     .setOutputFormat(...)
         *     .build();
         * }
         *
         * @param configBlock A consumer which provides the {@link VideoSpec.Builder} which will
         *                    configure the {@link VideoSpec} of this media specification.
         */
        @NonNull
        @SuppressWarnings("BuilderSetStyle")
        public Builder configureVideo(@NonNull Consumer<VideoSpec.Builder> configBlock) {
            VideoSpec.Builder videoSpecBuilder = getVideoSpec().toBuilder();
            configBlock.accept(videoSpecBuilder);
            setVideoSpec(videoSpecBuilder.build());
            return this;
        }

        /**
         * Gets the existing VideoSpec.
         */
        // This is not meant to be a public API. Only should be used internally within Builder.
        @SuppressLint("KotlinPropertyAccess")
        abstract VideoSpec getVideoSpec();

        /**
         * Sets the video recording output format.
         *
         * <p>If not set, the default is {@link #OUTPUT_FORMAT_AUTO}.
         *
         * @param format The requested video format. Possible values are
         * {@link MediaSpec#OUTPUT_FORMAT_AUTO}, {@link MediaSpec#OUTPUT_FORMAT_MPEG_4} or
         * {@link MediaSpec#OUTPUT_FORMAT_WEBM}.
         */
        @NonNull
        public abstract Builder setOutputFormat(@OutputFormat int format);

        /** Build the {@link MediaSpec} from this builder. */
        @NonNull
        public abstract MediaSpec build();
    }
}