transcoding: Address API council comments Part1
1. Create a base class TranscodingRequest for VideoTranscodingRequest
and upcoming ImageTranscodingRequest in Androd T. Also have builder in each type
with VideoTranscodingRequest.builder inherit TranscodingRequest.builder.
2. Hiding the PRIORITY_REALTIME and setPrority api for Android S.
3. Hiding TRANSCODING_TYPE_VIDEO as it is only for internal bookkeeping
4. Move TRANSCODING_TYPE_* and PRIORITY_* to inside TranscodingRequest.
5. Remove the setSrcUri, setDstUri, setVideoTrackFormat and set them in constructor.
Bug: 181551684
Test: atest CtsMediaTranscodingTestCases:MediaTranscodeManagerTest
Change-Id: Ib6fead5e9fcb48bfd1db35f9f35402d6fa1ed73d
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 3d129d8..20ce133 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -135,6 +135,10 @@
":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 6158e2e..1d912eb 100644
--- a/apex/media/framework/api/system-current.txt
+++ b/apex/media/framework/api/system-current.txt
@@ -3,38 +3,19 @@
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 @@
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 30d1896..af2aa6b 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.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
+ <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding">
+ Compatible media transcoding</a>. 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.
<h3>Transcoding Types</h3>
<h4>Video Transcoding</h4>
- When transcoding a video file, the video file could be of any of the following types:
- <ul>
- <li> Video file with single video track. </li>
- <li> Video file with multiple video track. </li>
- <li> Video file with multiple video tracks and audio tracks. </li>
- <li> Video file with video/audio tracks and metadata track. Note that metadata track will be passed
- through only if it could be recognized by {@link MediaExtractor}.
- TODO(hkuang): Finalize the metadata track behavior. </li>
- </ul>
+ 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.
<p class=note>
- 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.
<h3>Transcoding Request</h3>
<p>
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 @@
Here is an example where <code>Builder</code> is used to specify all parameters
<pre class=prettyprint>
- 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();
}</pre>
-
- 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
@@ -113,68 +95,6 @@
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.
- * <p>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.
- * <p> 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.
- * <p>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
* listener implementation to check the result and take appropriate action.
@@ -500,7 +420,79 @@
}
}
- public static final class TranscodingRequest {
+ /**
+ * Abstract base class for all the TranscodingRequest.
+ * <p> 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.
+ * <p>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.
+ * <p> 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.
+ * <p>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;
@@ -534,22 +526,6 @@
private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN;
/**
- * Desired output video format of the destination file.
- * <p> If this is null, source file's video track will be passed through and copied to the
- * destination file.
- * <p>
- */
- private @Nullable MediaFormat mVideoTrackFormat = null;
-
- /**
- * Desired output audio format of the destination file.
- * <p> 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.
* <p> If this is null, source file's image track will be passed through and copied to the
* destination file.
@@ -560,6 +536,12 @@
@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 @@
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,22 +603,16 @@
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 @@
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 @@
}
parcel.clientPackageName = packageName;
}
- parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat);
+ writeFormatToParcel(parcel);
if (mTestConfig != null) {
parcel.isForTesting = true;
parcel.testConfig = mTestConfig;
@@ -676,71 +654,12 @@
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 <code>TranscodingRequest</code> instance.
+ * Builder to build a {@link TranscodingRequest} object.
+ *
+ * @param <T> The subclass to be built.
*/
- public static final class Builder {
+ abstract static class Builder<T extends Builder<T>> {
private @NonNull Uri mSourceUri;
private @NonNull Uri mDestinationUri;
private @Nullable ParcelFileDescriptor mSourceFileDescriptor = null;
@@ -749,80 +668,68 @@
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 @@
* @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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 @@
* @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.
- * <p> 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.
- * <p>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.
- * <p>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 @@
*/
@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 <code>Builder</code>.
- * @throws UnsupportedOperationException if the parameters set on the
- * <code>Builder</code> 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();
}
}
@@ -1198,6 +1029,206 @@
}
/**
+ * VideoTranscodingRequest encapsulates the configuration for transcoding a video.
+ */
+ public static final class VideoTranscodingRequest extends TranscodingRequest {
+ /**
+ * Desired output video format of the destination file.
+ * <p> 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.
+ * <p> 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<VideoTranscodingRequest.Builder> {
+ /**
+ * Desired output video format of the destination file.
+ * <p> 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.
+ * <p> 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.
+ *
+ * <p> 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.
+ * <p>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 <code>Builder</code>.
+ * @throws UnsupportedOperationException if the parameters set on the
+ * <code>Builder</code> 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
* progress, and to get the result once the operation has completed.