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.
- 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.
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
+ * 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 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 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 TranscodingRequest instance.
+ * Builder to build a {@link TranscodingRequest} object.
+ *
+ * @param 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.
+ * 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