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
This commit is contained in:
@@ -135,6 +135,10 @@ java_sdk_library {
|
||||
":updatable-media-srcs",
|
||||
],
|
||||
|
||||
api_lint: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
libs: [
|
||||
"framework_media_annotation",
|
||||
],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
<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 @@ import java.util.concurrent.Executors;
|
||||
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
|
||||
@@ -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.
|
||||
* <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
|
||||
@@ -500,7 +420,79 @@ public final class MediaTranscodeManager {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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.
|
||||
* <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
|
||||
@@ -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 <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 @@ 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.
|
||||
* <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 @@ 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.
|
||||
* <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 @@ 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 <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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* <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
|
||||
|
||||
Reference in New Issue
Block a user