diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 3d129d8548eb0..20ce13322fd26 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -135,6 +135,10 @@ java_sdk_library { ":updatable-media-srcs", ], + api_lint: { + enabled: false, + }, + libs: [ "framework_media_annotation", ], diff --git a/apex/media/framework/api/system-current.txt b/apex/media/framework/api/system-current.txt index 6158e2ece55f1..1d912ebc71fa3 100644 --- a/apex/media/framework/api/system-current.txt +++ b/apex/media/framework/api/system-current.txt @@ -3,38 +3,19 @@ package android.media { public final class MediaTranscodeManager { method @Nullable public android.media.MediaTranscodeManager.TranscodingSession enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener); - field public static final int PRIORITY_REALTIME = 1; // 0x1 - field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1 } @java.lang.FunctionalInterface public static interface MediaTranscodeManager.OnTranscodingFinishedListener { method public void onTranscodingFinished(@NonNull android.media.MediaTranscodeManager.TranscodingSession); } - public static final class MediaTranscodeManager.TranscodingRequest { + public abstract static class MediaTranscodeManager.TranscodingRequest { method public int getClientPid(); method public int getClientUid(); method @Nullable public android.os.ParcelFileDescriptor getDestinationFileDescriptor(); method @NonNull public android.net.Uri getDestinationUri(); - method public int getPriority(); method @Nullable public android.os.ParcelFileDescriptor getSourceFileDescriptor(); method @NonNull public android.net.Uri getSourceUri(); - method public int getType(); - method @Nullable public android.media.MediaFormat getVideoTrackFormat(); - } - - public static final class MediaTranscodeManager.TranscodingRequest.Builder { - ctor public MediaTranscodeManager.TranscodingRequest.Builder(); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build(); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationFileDescriptor(@NonNull android.os.ParcelFileDescriptor); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceFileDescriptor(@NonNull android.os.ParcelFileDescriptor); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setType(int); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat); } public static class MediaTranscodeManager.TranscodingRequest.MediaFormatResolver { @@ -71,5 +52,18 @@ package android.media { method public void onProgressUpdate(@NonNull android.media.MediaTranscodeManager.TranscodingSession, @IntRange(from=0, to=100) int); } + public static final class MediaTranscodeManager.VideoTranscodingRequest extends android.media.MediaTranscodeManager.TranscodingRequest { + method @NonNull public android.media.MediaFormat getVideoTrackFormat(); + } + + public static final class MediaTranscodeManager.VideoTranscodingRequest.Builder { + ctor public MediaTranscodeManager.VideoTranscodingRequest.Builder(@NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.media.MediaFormat); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest build(); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setClientPid(int); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setClientUid(int); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setDestinationFileDescriptor(android.os.ParcelFileDescriptor); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setSourceFileDescriptor(android.os.ParcelFileDescriptor); + } + } diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java index 30d1896a23ac2..af2aa6bcbc63b 100644 --- a/apex/media/framework/java/android/media/MediaTranscodeManager.java +++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java @@ -48,27 +48,23 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** - MediaTranscodeManager provides an interface to the system's media transcoding service and can be - used to transcode media files, e.g. transcoding a video from HEVC to AVC. + Android 12 introduces Compatible media transcoding feature. See + + Compatible media transcoding. MediaTranscodeManager provides an interface to the system's media + transcoding service and can be used to transcode media files, e.g. transcoding a video from HEVC to + AVC.

Transcoding Types

Video Transcoding

- When transcoding a video file, the video file could be of any of the following types: - + When transcoding a video file, the video track will be transcoded based on the desired track format + and the audio track will be pass through without any modification.

- Note that currently only support transcoding video file in mp4 format. + Note that currently only support transcoding video file in mp4 format and with single video track.

Transcoding Request

To transcode a media file, first create a {@link TranscodingRequest} through its builder class - {@link TranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through + {@link VideoTranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through {@link MediaTranscodeManager#enqueueRequest( TranscodingRequest, Executor, OnTranscodingFinishedListener)} TranscodeRequest are processed based on client process's priority and request priority. When a @@ -80,23 +76,9 @@ import java.util.concurrent.Executors; Here is an example where Builder is used to specify all parameters

- TranscodingRequest request =
-    new TranscodingRequest.Builder()
-        .setSourceUri(srcUri)
-        .setDestinationUri(dstUri)
-        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-        .setPriority(REALTIME)
-        .setVideoTrackFormat(videoFormat)
-        .build();
+ VideoTranscodingRequest request =
+    new VideoTranscodingRequest.Builder(srcUri, dstUri, videoFormat).build();
  }
- - TODO(hkuang): Add architecture diagram showing the transcoding service and api. - TODO(hkuang): Add sample code when API is settled. - TODO(hkuang): Clarify whether multiple video tracks is supported or not. - TODO(hkuang): Clarify whether image/audio transcoding is supported or not. - TODO(hkuang): Clarify what will happen if there is unrecognized track in the source. - TODO(hkuang): Clarify whether supports scaling. - TODO(hkuang): Clarify whether supports framerate conversion. @hide */ @SystemApi @@ -112,68 +94,6 @@ public final class MediaTranscodeManager { /** Default bpp(bits-per-pixel) to use for calculating default bitrate. */ private static final float BPP = 0.25f; - /** - * Default transcoding type. - * @hide - */ - public static final int TRANSCODING_TYPE_UNKNOWN = 0; - - /** - * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video file. - *

Note that currently only support transcoding video file in mp4 format. - */ - public static final int TRANSCODING_TYPE_VIDEO = 1; - - /** - * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image file. - * @hide - */ - public static final int TRANSCODING_TYPE_IMAGE = 2; - - /** @hide */ - @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = { - TRANSCODING_TYPE_UNKNOWN, - TRANSCODING_TYPE_VIDEO, - TRANSCODING_TYPE_IMAGE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TranscodingType {} - - /** - * Default value. - * @hide - */ - public static final int PRIORITY_UNKNOWN = 0; - /** - * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the - * client wants the transcoding result as soon as possible. - *

Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve - * performance penalty due to resource reallocation to prioritize the sessions with higher - * priority. - * TODO(hkuang): Add more description of this when priority is finalized. - */ - public static final int PRIORITY_REALTIME = 1; - - /** - * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not need - * the transcoding result as soon as possible. - *

Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set to - * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept - * delay of the transcoding result. - * @hide - * TODO(hkuang): Add more description of this when priority is finalized. - */ - public static final int PRIORITY_OFFLINE = 2; - - /** @hide */ - @IntDef(prefix = {"PRIORITY_"}, value = { - PRIORITY_UNKNOWN, - PRIORITY_REALTIME, - PRIORITY_OFFLINE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TranscodingPriority {} - /** * Listener that gets notified when a transcoding operation has finished. * This listener gets notified regardless of how the operation finished. It is up to the @@ -500,7 +420,79 @@ public final class MediaTranscodeManager { } } - public static final class TranscodingRequest { + /** + * Abstract base class for all the TranscodingRequest. + *

TranscodingRequest encapsulates the desired configuration for the transcoding. + */ + public abstract static class TranscodingRequest { + /** + * + * Default transcoding type. + * @hide + */ + public static final int TRANSCODING_TYPE_UNKNOWN = 0; + + /** + * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video. + *

Note that currently only support transcoding video file in mp4 format. + * @hide + */ + public static final int TRANSCODING_TYPE_VIDEO = 1; + + /** + * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image. + * @hide + */ + public static final int TRANSCODING_TYPE_IMAGE = 2; + + /** @hide */ + @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = { + TRANSCODING_TYPE_UNKNOWN, + TRANSCODING_TYPE_VIDEO, + TRANSCODING_TYPE_IMAGE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TranscodingType {} + + /** + * Default value. + * + * @hide + */ + public static final int PRIORITY_UNKNOWN = 0; + /** + * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the + * client wants the transcoding result as soon as possible. + *

Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve + * performance penalty due to resource reallocation to prioritize the sessions with higher + * priority. + * + * @hide + */ + public static final int PRIORITY_REALTIME = 1; + + /** + * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not + * need the transcoding result as soon as possible. + *

Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set + * to + * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept + * delay of the transcoding result. + * + * @hide + * + */ + public static final int PRIORITY_OFFLINE = 2; + + /** @hide */ + @IntDef(prefix = {"PRIORITY_"}, value = { + PRIORITY_UNKNOWN, + PRIORITY_REALTIME, + PRIORITY_OFFLINE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TranscodingPriority {} + /** Uri of the source media file. */ private @NonNull Uri mSourceUri; @@ -533,22 +525,6 @@ public final class MediaTranscodeManager { /** Priority of the transcoding. */ private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN; - /** - * Desired output video format of the destination file. - *

If this is null, source file's video track will be passed through and copied to the - * destination file. - *

- */ - private @Nullable MediaFormat mVideoTrackFormat = null; - - /** - * Desired output audio format of the destination file. - *

If this is null, source file's audio track will be passed through and copied to the - * destination file. - * @hide - */ - private @Nullable MediaFormat mAudioTrackFormat = null; - /** * Desired image format for the destination file. *

If this is null, source file's image track will be passed through and copied to the @@ -560,6 +536,12 @@ public final class MediaTranscodeManager { @VisibleForTesting private TranscodingTestConfig mTestConfig = null; + /** + * Prevent public constructor access. + */ + /* package private */ TranscodingRequest() { + } + private TranscodingRequest(Builder b) { mSourceUri = b.mSourceUri; mSourceFileDescriptor = b.mSourceFileDescriptor; @@ -569,13 +551,13 @@ public final class MediaTranscodeManager { mClientPid = b.mClientPid; mPriority = b.mPriority; mType = b.mType; - mVideoTrackFormat = b.mVideoTrackFormat; - mAudioTrackFormat = b.mAudioTrackFormat; - mImageFormat = b.mImageFormat; mTestConfig = b.mTestConfig; } - /** Return the type of the transcoding. */ + /** + * Return the type of the transcoding. + * @hide + */ @TranscodingType public int getType() { return mType; @@ -621,21 +603,15 @@ public final class MediaTranscodeManager { return mDestinationFileDescriptor; } - /** Return priority of the transcoding. */ + /** + * Return priority of the transcoding. + * @hide + */ @TranscodingPriority public int getPriority() { return mPriority; } - /** - * Return the video track format of the transcoding. - * This will be null is the transcoding is not for video transcoding. - */ - @Nullable - public MediaFormat getVideoTrackFormat() { - return mVideoTrackFormat; - } - /** * Return TestConfig of the transcoding. * @hide @@ -645,6 +621,8 @@ public final class MediaTranscodeManager { return mTestConfig; } + abstract void writeFormatToParcel(TranscodingRequestParcel parcel); + /* Writes the TranscodingRequest to a parcel. */ private TranscodingRequestParcel writeToParcel(@NonNull Context context) { TranscodingRequestParcel parcel = new TranscodingRequestParcel(); @@ -668,7 +646,7 @@ public final class MediaTranscodeManager { } parcel.clientPackageName = packageName; } - parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat); + writeFormatToParcel(parcel); if (mTestConfig != null) { parcel.isForTesting = true; parcel.testConfig = mTestConfig; @@ -676,71 +654,12 @@ public final class MediaTranscodeManager { return parcel; } - /* Converts the MediaFormat to TranscodingVideoTrackFormat. */ - private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) { - if (format == null) { - throw new IllegalArgumentException("Invalid MediaFormat"); - } - - TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat(); - - if (format.containsKey(MediaFormat.KEY_MIME)) { - String mime = format.getString(MediaFormat.KEY_MIME); - if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) { - trackFormat.codecType = TranscodingVideoCodecType.kAvc; - } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) { - trackFormat.codecType = TranscodingVideoCodecType.kHevc; - } else { - throw new UnsupportedOperationException("Only support transcode to avc/hevc"); - } - } - - if (format.containsKey(MediaFormat.KEY_BIT_RATE)) { - int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE); - if (bitrateBps <= 0) { - throw new IllegalArgumentException("Bitrate must be larger than 0"); - } - trackFormat.bitrateBps = bitrateBps; - } - - if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey( - MediaFormat.KEY_HEIGHT)) { - int width = format.getInteger(MediaFormat.KEY_WIDTH); - int height = format.getInteger(MediaFormat.KEY_HEIGHT); - if (width <= 0 || height <= 0) { - throw new IllegalArgumentException("Width and height must be larger than 0"); - } - // TODO(hkuang): Validate the aspect ratio after adding scaling. - trackFormat.width = width; - trackFormat.height = height; - } - - if (format.containsKey(MediaFormat.KEY_PROFILE)) { - int profile = format.getInteger(MediaFormat.KEY_PROFILE); - if (profile <= 0) { - throw new IllegalArgumentException("Invalid codec profile"); - } - // TODO(hkuang): Validate the profile according to codec type. - trackFormat.profile = profile; - } - - if (format.containsKey(MediaFormat.KEY_LEVEL)) { - int level = format.getInteger(MediaFormat.KEY_LEVEL); - if (level <= 0) { - throw new IllegalArgumentException("Invalid codec level"); - } - // TODO(hkuang): Validate the level according to codec type. - trackFormat.level = level; - } - - return trackFormat; - } - /** - * Builder class for {@link TranscodingRequest} objects. - * Use this class to configure and create a TranscodingRequest instance. + * Builder to build a {@link TranscodingRequest} object. + * + * @param The subclass to be built. */ - public static final class Builder { + abstract static class Builder> { private @NonNull Uri mSourceUri; private @NonNull Uri mDestinationUri; private @Nullable ParcelFileDescriptor mSourceFileDescriptor = null; @@ -749,80 +668,68 @@ public final class MediaTranscodeManager { private int mClientPid = -1; private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN; private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN; - private @Nullable MediaFormat mVideoTrackFormat; - private @Nullable MediaFormat mAudioTrackFormat; - private @Nullable MediaFormat mImageFormat; private TranscodingTestConfig mTestConfig; + abstract T self(); + /** - * Specifies the uri of source media file. + * Creates a builder for building {@link TranscodingRequest}s. * * Client must set the source Uri. If client also provides the source fileDescriptor * through is provided by {@link #setSourceFileDescriptor(ParcelFileDescriptor)}, * TranscodingSession will use the fd instead of calling back to the client to open the * sourceUri. + * + * + * @param type The transcoding type. * @param sourceUri Content uri for the source media file. - * @return The same builder instance. - * @throws IllegalArgumentException if Uri is null or empty. + * @param destinationUri Content uri for the destination media file. + * */ - @NonNull - public Builder setSourceUri(@NonNull Uri sourceUri) { + private Builder(@TranscodingType int type, @NonNull Uri sourceUri, + @NonNull Uri destinationUri) { + mType = type; + if (sourceUri == null || Uri.EMPTY.equals(sourceUri)) { throw new IllegalArgumentException( "You must specify a non-empty source Uri."); } mSourceUri = sourceUri; - return this; + + if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) { + throw new IllegalArgumentException( + "You must specify a non-empty destination Uri."); + } + mDestinationUri = destinationUri; } /** * Specifies the fileDescriptor opened from the source media file. * * This call is optional. If the source fileDescriptor is provided, TranscodingSession - * will use it directly instead of opening the uri from {@link #setSourceUri(Uri)}. It - * is client's responsibility to make sure the fileDescriptor is opened from the source - * uri. + * will use it directly instead of opening the uri from {@link #Builder(int, Uri, Uri)}. + * It is client's responsibility to make sure the fileDescriptor is opened from the + * source uri. * @param fileDescriptor a {@link ParcelFileDescriptor} opened from source media file. * @return The same builder instance. * @throws IllegalArgumentException if fileDescriptor is invalid. */ @NonNull - public Builder setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) { + public T setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) { if (fileDescriptor == null || fileDescriptor.getFd() < 0) { throw new IllegalArgumentException( "Invalid source descriptor."); } mSourceFileDescriptor = fileDescriptor; - return this; - } - - /** - * Specifies the uri of the destination media file. - * - * Client must set the destination Uri. If client also provides the destination - * fileDescriptor through {@link #setDestinationFileDescriptor(ParcelFileDescriptor)}, - * TranscodingSession will use the fd instead of calling back to the client to open the - * destinationUri. - * @param destinationUri Content uri for the destination media file. - * @return The same builder instance. - * @throws IllegalArgumentException if Uri is null or empty. - */ - @NonNull - public Builder setDestinationUri(@NonNull Uri destinationUri) { - if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) { - throw new IllegalArgumentException( - "You must specify a non-empty destination Uri."); - } - mDestinationUri = destinationUri; - return this; + return self(); } /** * Specifies the fileDescriptor opened from the destination media file. * * This call is optional. If the destination fileDescriptor is provided, - * TranscodingSession will use it directly instead of opening the uri from - * {@link #setDestinationUri(Uri)} upon transcoding starts. It is client's + * TranscodingSession will use it directly instead of opening the source uri from + * {@link #Builder(int, Uri, Uri)} upon transcoding starts. It is client's * responsibility to make sure the fileDescriptor is opened from the destination uri. * @param fileDescriptor a {@link ParcelFileDescriptor} opened from destination media * file. @@ -830,46 +737,54 @@ public final class MediaTranscodeManager { * @throws IllegalArgumentException if fileDescriptor is invalid. */ @NonNull - public Builder setDestinationFileDescriptor( + public T setDestinationFileDescriptor( @NonNull ParcelFileDescriptor fileDescriptor) { if (fileDescriptor == null || fileDescriptor.getFd() < 0) { throw new IllegalArgumentException( "Invalid destination descriptor."); } mDestinationFileDescriptor = fileDescriptor; - return this; + return self(); } /** * Specify the UID of the client that this request is for. + *

+ * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the + * pid. Note that the permission check happens on the service side upon starting the + * transcoding. If the client does not have the permission, the transcoding will fail. + * * @param uid client Uid. * @return The same builder instance. * @throws IllegalArgumentException if uid is invalid. - * TODO(hkuang): Check the permission if it is allowed. */ @NonNull - public Builder setClientUid(int uid) { + public T setClientUid(int uid) { if (uid < 0) { throw new IllegalArgumentException("Invalid Uid"); } mClientUid = uid; - return this; + return self(); } /** - * Specify the PID of the client that this request is for. + * Specify the pid of the client that this request is for. + *

+ * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the + * pid. Note that the permission check happens on the service side upon starting the + * transcoding. If the client does not have the permission, the transcoding will fail. + * * @param pid client Pid. * @return The same builder instance. * @throws IllegalArgumentException if pid is invalid. - * TODO(hkuang): Check the permission if it is allowed. */ @NonNull - public Builder setClientPid(int pid) { + public T setClientPid(int pid) { if (pid < 0) { throw new IllegalArgumentException("Invalid pid"); } mClientPid = pid; - return this; + return self(); } /** @@ -878,64 +793,15 @@ public final class MediaTranscodeManager { * @param priority Must be one of the {@code PRIORITY_*} * @return The same builder instance. * @throws IllegalArgumentException if flags is invalid. + * @hide */ @NonNull - public Builder setPriority(@TranscodingPriority int priority) { + public T setPriority(@TranscodingPriority int priority) { if (priority != PRIORITY_OFFLINE && priority != PRIORITY_REALTIME) { throw new IllegalArgumentException("Invalid priority: " + priority); } mPriority = priority; - return this; - } - - /** - * Specifies the type of transcoding. - *

Clients must provide the source and destination that corresponds to the - * transcoding type. - * - * @param type Must be one of the {@code TRANSCODING_TYPE_*} - * @return The same builder instance. - * @throws IllegalArgumentException if flags is invalid. - */ - @NonNull - public Builder setType(@TranscodingType int type) { - if (type != TRANSCODING_TYPE_VIDEO && type != TRANSCODING_TYPE_IMAGE) { - throw new IllegalArgumentException("Invalid transcoding type"); - } - mType = type; - return this; - } - - /** - * Specifies the desired video track format in the destination media file. - *

Client could only specify the settings that matters to them, e.g. codec format or - * bitrate. And by default, transcoding will preserve the original video's - * settings(bitrate, framerate, resolution) if not provided. - *

Note that some settings may silently fail to apply if the device does not - * support them. - * TODO(hkuang): Add MediaTranscodeUtil to help client generate transcoding setting. - * TODO(hkuang): Add MediaTranscodeUtil to check if the setting is valid. - * - * @param videoFormat MediaFormat containing the settings that client wants override in - * the original video's video track. - * @return The same builder instance. - * @throws IllegalArgumentException if videoFormat is invalid. - */ - @NonNull - public Builder setVideoTrackFormat(@NonNull MediaFormat videoFormat) { - if (videoFormat == null) { - throw new IllegalArgumentException("videoFormat must not be null"); - } - - // Check if the MediaFormat is for video by looking at the MIME type. - String mime = videoFormat.containsKey(MediaFormat.KEY_MIME) - ? videoFormat.getString(MediaFormat.KEY_MIME) : null; - if (mime == null || !mime.startsWith("video/")) { - throw new IllegalArgumentException("Invalid video format: wrong mime type"); - } - - mVideoTrackFormat = videoFormat; - return this; + return self(); } /** @@ -946,44 +812,9 @@ public final class MediaTranscodeManager { */ @VisibleForTesting @NonNull - public Builder setTestConfig(@NonNull TranscodingTestConfig config) { + public T setTestConfig(@NonNull TranscodingTestConfig config) { mTestConfig = config; - return this; - } - - /** - * @return a new {@link TranscodingRequest} instance successfully initialized with all - * the parameters set on this Builder. - * @throws UnsupportedOperationException if the parameters set on the - * Builder were incompatible, or if they are not supported by the - * device. - */ - @NonNull - public TranscodingRequest build() { - if (mSourceUri == null) { - throw new UnsupportedOperationException("Source URI must not be null"); - } - - if (mDestinationUri == null) { - throw new UnsupportedOperationException("Destination URI must not be null"); - } - - if (mPriority == PRIORITY_UNKNOWN) { - throw new UnsupportedOperationException("Must specify transcoding priority"); - } - - // Only support video transcoding now. - if (mType != TRANSCODING_TYPE_VIDEO) { - throw new UnsupportedOperationException("Only supports video transcoding now"); - } - - // Must provide video track format for video transcoding. - if (mType == TRANSCODING_TYPE_VIDEO && mVideoTrackFormat == null) { - throw new UnsupportedOperationException( - "Must provide video track format for video transcoding"); - } - - return new TranscodingRequest(this); + return self(); } } @@ -1197,6 +1028,206 @@ public final class MediaTranscodeManager { } } + /** + * VideoTranscodingRequest encapsulates the configuration for transcoding a video. + */ + public static final class VideoTranscodingRequest extends TranscodingRequest { + /** + * Desired output video format of the destination file. + *

If this is null, source file's video track will be passed through and copied to the + * destination file. + */ + private @Nullable MediaFormat mVideoTrackFormat = null; + + /** + * Desired output audio format of the destination file. + *

If this is null, source file's audio track will be passed through and copied to the + * destination file. + */ + private @Nullable MediaFormat mAudioTrackFormat = null; + + private VideoTranscodingRequest(VideoTranscodingRequest.Builder builder) { + super(builder); + mVideoTrackFormat = builder.mVideoTrackFormat; + mAudioTrackFormat = builder.mAudioTrackFormat; + } + + /** + * Return the video track format of the transcoding. + * This will be null if client has not specified the video track format. + */ + @NonNull + public MediaFormat getVideoTrackFormat() { + return mVideoTrackFormat; + } + + @Override + void writeFormatToParcel(TranscodingRequestParcel parcel) { + parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat); + } + + /* Converts the MediaFormat to TranscodingVideoTrackFormat. */ + private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) { + if (format == null) { + throw new IllegalArgumentException("Invalid MediaFormat"); + } + + TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat(); + + if (format.containsKey(MediaFormat.KEY_MIME)) { + String mime = format.getString(MediaFormat.KEY_MIME); + if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) { + trackFormat.codecType = TranscodingVideoCodecType.kAvc; + } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) { + trackFormat.codecType = TranscodingVideoCodecType.kHevc; + } else { + throw new UnsupportedOperationException("Only support transcode to avc/hevc"); + } + } + + if (format.containsKey(MediaFormat.KEY_BIT_RATE)) { + int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE); + if (bitrateBps <= 0) { + throw new IllegalArgumentException("Bitrate must be larger than 0"); + } + trackFormat.bitrateBps = bitrateBps; + } + + if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey( + MediaFormat.KEY_HEIGHT)) { + int width = format.getInteger(MediaFormat.KEY_WIDTH); + int height = format.getInteger(MediaFormat.KEY_HEIGHT); + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Width and height must be larger than 0"); + } + // TODO: Validate the aspect ratio after adding scaling. + trackFormat.width = width; + trackFormat.height = height; + } + + if (format.containsKey(MediaFormat.KEY_PROFILE)) { + int profile = format.getInteger(MediaFormat.KEY_PROFILE); + if (profile <= 0) { + throw new IllegalArgumentException("Invalid codec profile"); + } + // TODO: Validate the profile according to codec type. + trackFormat.profile = profile; + } + + if (format.containsKey(MediaFormat.KEY_LEVEL)) { + int level = format.getInteger(MediaFormat.KEY_LEVEL); + if (level <= 0) { + throw new IllegalArgumentException("Invalid codec level"); + } + // TODO: Validate the level according to codec type. + trackFormat.level = level; + } + + return trackFormat; + } + + /** + * Builder class for {@link VideoTranscodingRequest}. + */ + public static final class Builder extends + TranscodingRequest.Builder { + /** + * Desired output video format of the destination file. + *

If this is null, source file's video track will be passed through and + * copied to the destination file. + */ + private @Nullable MediaFormat mVideoTrackFormat = null; + + /** + * Desired output audio format of the destination file. + *

If this is null, source file's audio track will be passed through and copied + * to the destination file. + */ + private @Nullable MediaFormat mAudioTrackFormat = null; + + /** + * Creates a builder for building {@link VideoTranscodingRequest}s. + * + *

Client could only specify the settings that matters to them, e.g. codec format or + * bitrate. And by default, transcoding will preserve the original video's settings + * (bitrate, framerate, resolution) if not provided. + *

Note that some settings may silently fail to apply if the device does not support + * them. + * @param sourceUri Content uri for the source media file. + * @param destinationUri Content uri for the destination media file. + * @param videoFormat MediaFormat containing the settings that client wants override in + * the original video's video track. + * @throws IllegalArgumentException if videoFormat is invalid. + */ + public Builder(@NonNull Uri sourceUri, @NonNull Uri destinationUri, + @NonNull MediaFormat videoFormat) { + super(TRANSCODING_TYPE_VIDEO, sourceUri, destinationUri); + setVideoTrackFormat(videoFormat); + } + + @Override + @NonNull + public Builder setClientUid(int uid) { + super.setClientUid(uid); + return self(); + } + + @Override + @NonNull + public Builder setClientPid(int pid) { + super.setClientPid(pid); + return self(); + } + + @Override + @NonNull + public Builder setSourceFileDescriptor(ParcelFileDescriptor fd) { + super.setSourceFileDescriptor(fd); + return self(); + } + + @Override + @NonNull + public Builder setDestinationFileDescriptor(ParcelFileDescriptor fd) { + super.setDestinationFileDescriptor(fd); + return self(); + } + + private void setVideoTrackFormat(@NonNull MediaFormat videoFormat) { + if (videoFormat == null) { + throw new IllegalArgumentException("videoFormat must not be null"); + } + + // Check if the MediaFormat is for video by looking at the MIME type. + String mime = videoFormat.containsKey(MediaFormat.KEY_MIME) + ? videoFormat.getString(MediaFormat.KEY_MIME) : null; + if (mime == null || !mime.startsWith("video/")) { + throw new IllegalArgumentException("Invalid video format: wrong mime type"); + } + + mVideoTrackFormat = videoFormat; + } + + /** + * @return a new {@link TranscodingRequest} instance successfully initialized + * with all the parameters set on this Builder. + * @throws UnsupportedOperationException if the parameters set on the + * Builder were incompatible, or + * if they are not supported by the + * device. + */ + @NonNull + public VideoTranscodingRequest build() { + return new VideoTranscodingRequest(this); + } + + @Override + VideoTranscodingRequest.Builder self() { + return this; + } + } + } + /** * Handle to an enqueued transcoding operation. An instance of this class represents a single * enqueued transcoding operation. The caller can use that instance to query the status or