Loading media/java/android/media/MediaTranscodeManager.java 0 → 100644 +403 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.Uri; import android.util.Log; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.concurrent.locks.ReentrantLock; /** * MediaTranscodeManager provides an interface to the system's media transcode service. * Transcode requests are put in a queue and processed in order. When a transcode operation is * completed the caller is notified via its OnTranscodingFinishedListener. In the meantime the * caller may use the returned TranscodingJob object to cancel or check the status of a specific * transcode operation. * The currently supported media types are video and still images. * * TODO(lnilsson): Add sample code when API is settled. * * @hide */ public final class MediaTranscodeManager { private static final String TAG = "MediaTranscodeManager"; // Invalid ID passed from native means the request was never enqueued. private static final long ID_INVALID = -1; // Events passed from native. private static final int EVENT_JOB_STARTED = 1; private static final int EVENT_JOB_PROGRESSED = 2; private static final int EVENT_JOB_FINISHED = 3; @IntDef(prefix = { "EVENT_" }, value = { EVENT_JOB_STARTED, EVENT_JOB_PROGRESSED, EVENT_JOB_FINISHED, }) @Retention(RetentionPolicy.SOURCE) public @interface Event {} private static MediaTranscodeManager sMediaTranscodeManager; private final ConcurrentMap<Long, TranscodingJob> mPendingTranscodingJobs = new ConcurrentHashMap<>(); private final Context mContext; /** * 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. */ @FunctionalInterface public interface OnTranscodingFinishedListener { /** * Called when the transcoding operation has finished. The receiver may use the * TranscodingJob to check the result, i.e. whether the operation succeeded, was canceled or * if an error occurred. * @param transcodingJob The TranscodingJob instance for the finished transcoding operation. */ void onTranscodingFinished(@NonNull TranscodingJob transcodingJob); } /** * Class describing a transcode operation to be performed. The caller uses this class to * configure a transcoding operation that can then be enqueued using MediaTranscodeManager. */ public static final class TranscodingRequest { private Uri mSrcUri; private Uri mDstUri; private MediaFormat mDstFormat; private TranscodingRequest(Builder b) { mSrcUri = b.mSrcUri; mDstUri = b.mDstUri; mDstFormat = b.mDstFormat; } /** TranscodingRequest builder class. */ public static class Builder { private Uri mSrcUri; private Uri mDstUri; private MediaFormat mDstFormat; /** * Specifies the source media file. * @param uri Content uri for the source media file. * @return The builder instance. */ public Builder setSourceUri(Uri uri) { mSrcUri = uri; return this; } /** * Specifies the destination media file. * @param uri Content uri for the destination media file. * @return The builder instance. */ public Builder setDestinationUri(Uri uri) { mDstUri = uri; return this; } /** * Specifies the media format of the transcoded media file. * @param dstFormat MediaFormat containing the desired destination format. * @return The builder instance. */ public Builder setDestinationFormat(MediaFormat dstFormat) { mDstFormat = dstFormat; return this; } /** * Builds a new TranscodingRequest with the configuration set on this builder. * @return A new TranscodingRequest. */ public TranscodingRequest build() { return new TranscodingRequest(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. */ public static final class TranscodingJob { /** The job is enqueued but not yet running. */ public static final int STATUS_PENDING = 1; /** The job is currently running. */ public static final int STATUS_RUNNING = 2; /** The job is finished. */ public static final int STATUS_FINISHED = 3; @IntDef(prefix = { "STATUS_" }, value = { STATUS_PENDING, STATUS_RUNNING, STATUS_FINISHED, }) @Retention(RetentionPolicy.SOURCE) public @interface Status {} /** The job does not have a result yet. */ public static final int RESULT_NONE = 1; /** The job completed successfully. */ public static final int RESULT_SUCCESS = 2; /** The job encountered an error while running. */ public static final int RESULT_ERROR = 3; /** The job was canceled by the caller. */ public static final int RESULT_CANCELED = 4; @IntDef(prefix = { "RESULT_" }, value = { RESULT_NONE, RESULT_SUCCESS, RESULT_ERROR, RESULT_CANCELED, }) @Retention(RetentionPolicy.SOURCE) public @interface Result {} /** Listener that gets notified when the progress changes. */ @FunctionalInterface public interface OnProgressChangedListener { /** * Called when the progress changes. The progress is between 0 and 1, where 0 means * that the job has not yet started and 1 means that it has finished. * @param progress The new progress. */ void onProgressChanged(float progress); } private final Executor mExecutor; private final OnTranscodingFinishedListener mListener; private final ReentrantLock mStatusChangeLock = new ReentrantLock(); private Executor mProgressChangedExecutor; private OnProgressChangedListener mProgressChangedListener; private long mID; private float mProgress = 0.0f; private @Status int mStatus = STATUS_PENDING; private @Result int mResult = RESULT_NONE; private TranscodingJob(long id, @NonNull @CallbackExecutor Executor executor, @NonNull OnTranscodingFinishedListener listener) { mID = id; mExecutor = executor; mListener = listener; } /** * Set a progress listener. * @param listener The progress listener. */ public void setOnProgressChangedListener(@NonNull @CallbackExecutor Executor executor, @Nullable OnProgressChangedListener listener) { mProgressChangedExecutor = executor; mProgressChangedListener = listener; } /** * Cancels the transcoding job and notify the listener. If the job happened to finish before * being canceled this call is effectively a no-op and will not update the result in that * case. */ public void cancel() { setJobFinished(RESULT_CANCELED); sMediaTranscodeManager.native_cancelTranscodingRequest(mID); } /** * Gets the progress of the transcoding job. The progress is between 0 and 1, where 0 means * that the job has not yet started and 1 means that it is finished. * @return The progress. */ public float getProgress() { return mProgress; } /** * Gets the status of the transcoding job. * @return The status. */ public @Status int getStatus() { return mStatus; } /** * Gets the result of the transcoding job. * @return The result. */ public @Result int getResult() { return mResult; } private void setJobStarted() { mStatus = STATUS_RUNNING; } private void setJobProgress(float newProgress) { mProgress = newProgress; // Notify listener. OnProgressChangedListener onProgressChangedListener = mProgressChangedListener; if (onProgressChangedListener != null) { mProgressChangedExecutor.execute( () -> onProgressChangedListener.onProgressChanged(mProgress)); } } private void setJobFinished(int result) { boolean doNotifyListener = false; // Prevent conflicting simultaneous status updates from native (finished) and from the // caller (cancel). try { mStatusChangeLock.lock(); if (mStatus != STATUS_FINISHED) { mStatus = STATUS_FINISHED; mResult = result; doNotifyListener = true; } } finally { mStatusChangeLock.unlock(); } if (doNotifyListener) { mExecutor.execute(() -> mListener.onTranscodingFinished(this)); } } private void processJobEvent(@Event int event, int arg) { switch (event) { case EVENT_JOB_STARTED: setJobStarted(); break; case EVENT_JOB_PROGRESSED: setJobProgress((float) arg / 100); break; case EVENT_JOB_FINISHED: setJobFinished(arg); break; default: Log.e(TAG, "Unsupported event: " + event); break; } } } // Initializes the native library. private static native void native_init(); // Requests a new job ID from the native service. private native long native_requestUniqueJobID(); // Enqueues a transcoding request to the native service. private native boolean native_enqueueTranscodingRequest( long id, @NonNull TranscodingRequest transcodingRequest, @NonNull Context context); // Cancels an enqueued transcoding request. private native void native_cancelTranscodingRequest(long id); // Private constructor. private MediaTranscodeManager(@NonNull Context context) { mContext = context; } // Events posted from the native service. @SuppressWarnings("unused") private void postEventFromNative(@Event int event, long id, int arg) { Log.d(TAG, String.format("postEventFromNative. Event %d, ID %d, arg %d", event, id, arg)); TranscodingJob transcodingJob = mPendingTranscodingJobs.get(id); // Job IDs are added to the tracking set before the job is enqueued so it should never // be null unless the service misbehaves. if (transcodingJob == null) { Log.e(TAG, "No matching transcode job found for id " + id); return; } transcodingJob.processJobEvent(event, arg); } /** * Gets the MediaTranscodeManager singleton instance. * @param context The application context. * @return the {@link MediaTranscodeManager} singleton instance. */ public static MediaTranscodeManager getInstance(@NonNull Context context) { Preconditions.checkNotNull(context); synchronized (MediaTranscodeManager.class) { if (sMediaTranscodeManager == null) { sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext()); } return sMediaTranscodeManager; } } /** * Enqueues a TranscodingRequest for execution. * @param transcodingRequest The TranscodingRequest to enqueue. * @param listenerExecutor Executor on which the listener is notified. * @param listener Listener to get notified when the transcoding job is finished. * @return A TranscodingJob for this operation. */ public @Nullable TranscodingJob enqueueTranscodingRequest( @NonNull TranscodingRequest transcodingRequest, @NonNull @CallbackExecutor Executor listenerExecutor, @NonNull OnTranscodingFinishedListener listener) { Log.i(TAG, "enqueueTranscodingRequest called."); Preconditions.checkNotNull(transcodingRequest); Preconditions.checkNotNull(listenerExecutor); Preconditions.checkNotNull(listener); // Reserve a job ID. long jobID = native_requestUniqueJobID(); if (jobID == ID_INVALID) { return null; } // Add the job to the tracking set. TranscodingJob transcodingJob = new TranscodingJob(jobID, listenerExecutor, listener); mPendingTranscodingJobs.put(jobID, transcodingJob); // Enqueue the request with the native service. boolean enqueued = native_enqueueTranscodingRequest(jobID, transcodingRequest, mContext); if (!enqueued) { mPendingTranscodingJobs.remove(jobID); return null; } return transcodingJob; } static { System.loadLibrary("media_jni"); native_init(); } } media/jni/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ cc_library_shared { "android_media_MediaProfiles.cpp", "android_media_MediaRecorder.cpp", "android_media_MediaSync.cpp", "android_media_MediaTranscodeManager.cpp", "android_media_ResampleInputStream.cpp", "android_media_Streams.cpp", "android_media_SyncParams.cpp", Loading media/jni/android_media_MediaPlayer.cpp +6 −0 Original line number Diff line number Diff line Loading @@ -1453,6 +1453,7 @@ extern int register_android_media_MediaProfiles(JNIEnv *env); extern int register_android_mtp_MtpDatabase(JNIEnv *env); extern int register_android_mtp_MtpDevice(JNIEnv *env); extern int register_android_mtp_MtpServer(JNIEnv *env); extern int register_android_media_MediaTranscodeManager(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { Loading Loading @@ -1565,6 +1566,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) goto bail; } if (register_android_media_MediaTranscodeManager(env) < 0) { ALOGE("ERROR: MediaTranscodeManager native registration failed"); goto bail; } /* success -- return valid version number */ result = JNI_VERSION_1_4; Loading media/jni/android_media_MediaTranscodeManager.cpp 0 → 100644 +102 −0 Original line number Diff line number Diff line /* * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaTranscodeManager_JNI" #include "android_runtime/AndroidRuntime.h" #include "jni.h" #include <nativehelper/JNIHelp.h> #include <utils/Log.h> namespace { // NOTE: Keep these enums in sync with their equivalents in MediaTranscodeManager.java. enum { ID_INVALID = -1 }; enum { EVENT_JOB_STARTED = 1, EVENT_JOB_PROGRESSED = 2, EVENT_JOB_FINISHED = 3, }; enum { RESULT_NONE = 1, RESULT_SUCCESS = 2, RESULT_ERROR = 3, RESULT_CANCELED = 4, }; struct { jmethodID postEventFromNative; } gMediaTranscodeManagerClassInfo; using namespace android; void android_media_MediaTranscodeManager_native_init(JNIEnv *env, jclass clazz) { ALOGV("android_media_MediaTranscodeManager_native_init"); gMediaTranscodeManagerClassInfo.postEventFromNative = env->GetMethodID( clazz, "postEventFromNative", "(IJI)V"); LOG_ALWAYS_FATAL_IF(gMediaTranscodeManagerClassInfo.postEventFromNative == NULL, "can't find android/media/MediaTranscodeManager.postEventFromNative"); } jlong android_media_MediaTranscodeManager_requestUniqueJobID( JNIEnv *env __unused, jobject thiz __unused) { ALOGV("android_media_MediaTranscodeManager_reserveUniqueJobID"); static std::atomic_int32_t sJobIDCounter{0}; jlong id = (jlong)++sJobIDCounter; return id; } jboolean android_media_MediaTranscodeManager_enqueueTranscodingRequest( JNIEnv *env, jobject thiz, jlong id, jobject request, jobject context __unused) { ALOGV("android_media_MediaTranscodeManager_enqueueTranscodingRequest"); if (!request) { return ID_INVALID; } env->CallVoidMethod(thiz, gMediaTranscodeManagerClassInfo.postEventFromNative, EVENT_JOB_FINISHED, id, RESULT_ERROR); return true; } void android_media_MediaTranscodeManager_cancelTranscodingRequest( JNIEnv *env __unused, jobject thiz __unused, jlong jobID __unused) { ALOGV("android_media_MediaTranscodeManager_cancelTranscodingRequest"); } const JNINativeMethod gMethods[] = { { "native_init", "()V", (void *)android_media_MediaTranscodeManager_native_init }, { "native_requestUniqueJobID", "()J", (void *)android_media_MediaTranscodeManager_requestUniqueJobID }, { "native_enqueueTranscodingRequest", "(JLandroid/media/MediaTranscodeManager$TranscodingRequest;Landroid/content/Context;)Z", (void *)android_media_MediaTranscodeManager_enqueueTranscodingRequest }, { "native_cancelTranscodingRequest", "(J)V", (void *)android_media_MediaTranscodeManager_cancelTranscodingRequest }, }; } // namespace anonymous int register_android_media_MediaTranscodeManager(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaTranscodeManager", gMethods, NELEM(gMethods)); } media/tests/MediaFrameworkTest/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ android_test { ], static_libs: [ "mockito-target-minus-junit4", "androidx.test.ext.junit", "androidx.test.rules", "android-ex-camera2", ], Loading Loading
media/java/android/media/MediaTranscodeManager.java 0 → 100644 +403 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.Uri; import android.util.Log; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.concurrent.locks.ReentrantLock; /** * MediaTranscodeManager provides an interface to the system's media transcode service. * Transcode requests are put in a queue and processed in order. When a transcode operation is * completed the caller is notified via its OnTranscodingFinishedListener. In the meantime the * caller may use the returned TranscodingJob object to cancel or check the status of a specific * transcode operation. * The currently supported media types are video and still images. * * TODO(lnilsson): Add sample code when API is settled. * * @hide */ public final class MediaTranscodeManager { private static final String TAG = "MediaTranscodeManager"; // Invalid ID passed from native means the request was never enqueued. private static final long ID_INVALID = -1; // Events passed from native. private static final int EVENT_JOB_STARTED = 1; private static final int EVENT_JOB_PROGRESSED = 2; private static final int EVENT_JOB_FINISHED = 3; @IntDef(prefix = { "EVENT_" }, value = { EVENT_JOB_STARTED, EVENT_JOB_PROGRESSED, EVENT_JOB_FINISHED, }) @Retention(RetentionPolicy.SOURCE) public @interface Event {} private static MediaTranscodeManager sMediaTranscodeManager; private final ConcurrentMap<Long, TranscodingJob> mPendingTranscodingJobs = new ConcurrentHashMap<>(); private final Context mContext; /** * 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. */ @FunctionalInterface public interface OnTranscodingFinishedListener { /** * Called when the transcoding operation has finished. The receiver may use the * TranscodingJob to check the result, i.e. whether the operation succeeded, was canceled or * if an error occurred. * @param transcodingJob The TranscodingJob instance for the finished transcoding operation. */ void onTranscodingFinished(@NonNull TranscodingJob transcodingJob); } /** * Class describing a transcode operation to be performed. The caller uses this class to * configure a transcoding operation that can then be enqueued using MediaTranscodeManager. */ public static final class TranscodingRequest { private Uri mSrcUri; private Uri mDstUri; private MediaFormat mDstFormat; private TranscodingRequest(Builder b) { mSrcUri = b.mSrcUri; mDstUri = b.mDstUri; mDstFormat = b.mDstFormat; } /** TranscodingRequest builder class. */ public static class Builder { private Uri mSrcUri; private Uri mDstUri; private MediaFormat mDstFormat; /** * Specifies the source media file. * @param uri Content uri for the source media file. * @return The builder instance. */ public Builder setSourceUri(Uri uri) { mSrcUri = uri; return this; } /** * Specifies the destination media file. * @param uri Content uri for the destination media file. * @return The builder instance. */ public Builder setDestinationUri(Uri uri) { mDstUri = uri; return this; } /** * Specifies the media format of the transcoded media file. * @param dstFormat MediaFormat containing the desired destination format. * @return The builder instance. */ public Builder setDestinationFormat(MediaFormat dstFormat) { mDstFormat = dstFormat; return this; } /** * Builds a new TranscodingRequest with the configuration set on this builder. * @return A new TranscodingRequest. */ public TranscodingRequest build() { return new TranscodingRequest(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. */ public static final class TranscodingJob { /** The job is enqueued but not yet running. */ public static final int STATUS_PENDING = 1; /** The job is currently running. */ public static final int STATUS_RUNNING = 2; /** The job is finished. */ public static final int STATUS_FINISHED = 3; @IntDef(prefix = { "STATUS_" }, value = { STATUS_PENDING, STATUS_RUNNING, STATUS_FINISHED, }) @Retention(RetentionPolicy.SOURCE) public @interface Status {} /** The job does not have a result yet. */ public static final int RESULT_NONE = 1; /** The job completed successfully. */ public static final int RESULT_SUCCESS = 2; /** The job encountered an error while running. */ public static final int RESULT_ERROR = 3; /** The job was canceled by the caller. */ public static final int RESULT_CANCELED = 4; @IntDef(prefix = { "RESULT_" }, value = { RESULT_NONE, RESULT_SUCCESS, RESULT_ERROR, RESULT_CANCELED, }) @Retention(RetentionPolicy.SOURCE) public @interface Result {} /** Listener that gets notified when the progress changes. */ @FunctionalInterface public interface OnProgressChangedListener { /** * Called when the progress changes. The progress is between 0 and 1, where 0 means * that the job has not yet started and 1 means that it has finished. * @param progress The new progress. */ void onProgressChanged(float progress); } private final Executor mExecutor; private final OnTranscodingFinishedListener mListener; private final ReentrantLock mStatusChangeLock = new ReentrantLock(); private Executor mProgressChangedExecutor; private OnProgressChangedListener mProgressChangedListener; private long mID; private float mProgress = 0.0f; private @Status int mStatus = STATUS_PENDING; private @Result int mResult = RESULT_NONE; private TranscodingJob(long id, @NonNull @CallbackExecutor Executor executor, @NonNull OnTranscodingFinishedListener listener) { mID = id; mExecutor = executor; mListener = listener; } /** * Set a progress listener. * @param listener The progress listener. */ public void setOnProgressChangedListener(@NonNull @CallbackExecutor Executor executor, @Nullable OnProgressChangedListener listener) { mProgressChangedExecutor = executor; mProgressChangedListener = listener; } /** * Cancels the transcoding job and notify the listener. If the job happened to finish before * being canceled this call is effectively a no-op and will not update the result in that * case. */ public void cancel() { setJobFinished(RESULT_CANCELED); sMediaTranscodeManager.native_cancelTranscodingRequest(mID); } /** * Gets the progress of the transcoding job. The progress is between 0 and 1, where 0 means * that the job has not yet started and 1 means that it is finished. * @return The progress. */ public float getProgress() { return mProgress; } /** * Gets the status of the transcoding job. * @return The status. */ public @Status int getStatus() { return mStatus; } /** * Gets the result of the transcoding job. * @return The result. */ public @Result int getResult() { return mResult; } private void setJobStarted() { mStatus = STATUS_RUNNING; } private void setJobProgress(float newProgress) { mProgress = newProgress; // Notify listener. OnProgressChangedListener onProgressChangedListener = mProgressChangedListener; if (onProgressChangedListener != null) { mProgressChangedExecutor.execute( () -> onProgressChangedListener.onProgressChanged(mProgress)); } } private void setJobFinished(int result) { boolean doNotifyListener = false; // Prevent conflicting simultaneous status updates from native (finished) and from the // caller (cancel). try { mStatusChangeLock.lock(); if (mStatus != STATUS_FINISHED) { mStatus = STATUS_FINISHED; mResult = result; doNotifyListener = true; } } finally { mStatusChangeLock.unlock(); } if (doNotifyListener) { mExecutor.execute(() -> mListener.onTranscodingFinished(this)); } } private void processJobEvent(@Event int event, int arg) { switch (event) { case EVENT_JOB_STARTED: setJobStarted(); break; case EVENT_JOB_PROGRESSED: setJobProgress((float) arg / 100); break; case EVENT_JOB_FINISHED: setJobFinished(arg); break; default: Log.e(TAG, "Unsupported event: " + event); break; } } } // Initializes the native library. private static native void native_init(); // Requests a new job ID from the native service. private native long native_requestUniqueJobID(); // Enqueues a transcoding request to the native service. private native boolean native_enqueueTranscodingRequest( long id, @NonNull TranscodingRequest transcodingRequest, @NonNull Context context); // Cancels an enqueued transcoding request. private native void native_cancelTranscodingRequest(long id); // Private constructor. private MediaTranscodeManager(@NonNull Context context) { mContext = context; } // Events posted from the native service. @SuppressWarnings("unused") private void postEventFromNative(@Event int event, long id, int arg) { Log.d(TAG, String.format("postEventFromNative. Event %d, ID %d, arg %d", event, id, arg)); TranscodingJob transcodingJob = mPendingTranscodingJobs.get(id); // Job IDs are added to the tracking set before the job is enqueued so it should never // be null unless the service misbehaves. if (transcodingJob == null) { Log.e(TAG, "No matching transcode job found for id " + id); return; } transcodingJob.processJobEvent(event, arg); } /** * Gets the MediaTranscodeManager singleton instance. * @param context The application context. * @return the {@link MediaTranscodeManager} singleton instance. */ public static MediaTranscodeManager getInstance(@NonNull Context context) { Preconditions.checkNotNull(context); synchronized (MediaTranscodeManager.class) { if (sMediaTranscodeManager == null) { sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext()); } return sMediaTranscodeManager; } } /** * Enqueues a TranscodingRequest for execution. * @param transcodingRequest The TranscodingRequest to enqueue. * @param listenerExecutor Executor on which the listener is notified. * @param listener Listener to get notified when the transcoding job is finished. * @return A TranscodingJob for this operation. */ public @Nullable TranscodingJob enqueueTranscodingRequest( @NonNull TranscodingRequest transcodingRequest, @NonNull @CallbackExecutor Executor listenerExecutor, @NonNull OnTranscodingFinishedListener listener) { Log.i(TAG, "enqueueTranscodingRequest called."); Preconditions.checkNotNull(transcodingRequest); Preconditions.checkNotNull(listenerExecutor); Preconditions.checkNotNull(listener); // Reserve a job ID. long jobID = native_requestUniqueJobID(); if (jobID == ID_INVALID) { return null; } // Add the job to the tracking set. TranscodingJob transcodingJob = new TranscodingJob(jobID, listenerExecutor, listener); mPendingTranscodingJobs.put(jobID, transcodingJob); // Enqueue the request with the native service. boolean enqueued = native_enqueueTranscodingRequest(jobID, transcodingRequest, mContext); if (!enqueued) { mPendingTranscodingJobs.remove(jobID); return null; } return transcodingJob; } static { System.loadLibrary("media_jni"); native_init(); } }
media/jni/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ cc_library_shared { "android_media_MediaProfiles.cpp", "android_media_MediaRecorder.cpp", "android_media_MediaSync.cpp", "android_media_MediaTranscodeManager.cpp", "android_media_ResampleInputStream.cpp", "android_media_Streams.cpp", "android_media_SyncParams.cpp", Loading
media/jni/android_media_MediaPlayer.cpp +6 −0 Original line number Diff line number Diff line Loading @@ -1453,6 +1453,7 @@ extern int register_android_media_MediaProfiles(JNIEnv *env); extern int register_android_mtp_MtpDatabase(JNIEnv *env); extern int register_android_mtp_MtpDevice(JNIEnv *env); extern int register_android_mtp_MtpServer(JNIEnv *env); extern int register_android_media_MediaTranscodeManager(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { Loading Loading @@ -1565,6 +1566,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) goto bail; } if (register_android_media_MediaTranscodeManager(env) < 0) { ALOGE("ERROR: MediaTranscodeManager native registration failed"); goto bail; } /* success -- return valid version number */ result = JNI_VERSION_1_4; Loading
media/jni/android_media_MediaTranscodeManager.cpp 0 → 100644 +102 −0 Original line number Diff line number Diff line /* * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaTranscodeManager_JNI" #include "android_runtime/AndroidRuntime.h" #include "jni.h" #include <nativehelper/JNIHelp.h> #include <utils/Log.h> namespace { // NOTE: Keep these enums in sync with their equivalents in MediaTranscodeManager.java. enum { ID_INVALID = -1 }; enum { EVENT_JOB_STARTED = 1, EVENT_JOB_PROGRESSED = 2, EVENT_JOB_FINISHED = 3, }; enum { RESULT_NONE = 1, RESULT_SUCCESS = 2, RESULT_ERROR = 3, RESULT_CANCELED = 4, }; struct { jmethodID postEventFromNative; } gMediaTranscodeManagerClassInfo; using namespace android; void android_media_MediaTranscodeManager_native_init(JNIEnv *env, jclass clazz) { ALOGV("android_media_MediaTranscodeManager_native_init"); gMediaTranscodeManagerClassInfo.postEventFromNative = env->GetMethodID( clazz, "postEventFromNative", "(IJI)V"); LOG_ALWAYS_FATAL_IF(gMediaTranscodeManagerClassInfo.postEventFromNative == NULL, "can't find android/media/MediaTranscodeManager.postEventFromNative"); } jlong android_media_MediaTranscodeManager_requestUniqueJobID( JNIEnv *env __unused, jobject thiz __unused) { ALOGV("android_media_MediaTranscodeManager_reserveUniqueJobID"); static std::atomic_int32_t sJobIDCounter{0}; jlong id = (jlong)++sJobIDCounter; return id; } jboolean android_media_MediaTranscodeManager_enqueueTranscodingRequest( JNIEnv *env, jobject thiz, jlong id, jobject request, jobject context __unused) { ALOGV("android_media_MediaTranscodeManager_enqueueTranscodingRequest"); if (!request) { return ID_INVALID; } env->CallVoidMethod(thiz, gMediaTranscodeManagerClassInfo.postEventFromNative, EVENT_JOB_FINISHED, id, RESULT_ERROR); return true; } void android_media_MediaTranscodeManager_cancelTranscodingRequest( JNIEnv *env __unused, jobject thiz __unused, jlong jobID __unused) { ALOGV("android_media_MediaTranscodeManager_cancelTranscodingRequest"); } const JNINativeMethod gMethods[] = { { "native_init", "()V", (void *)android_media_MediaTranscodeManager_native_init }, { "native_requestUniqueJobID", "()J", (void *)android_media_MediaTranscodeManager_requestUniqueJobID }, { "native_enqueueTranscodingRequest", "(JLandroid/media/MediaTranscodeManager$TranscodingRequest;Landroid/content/Context;)Z", (void *)android_media_MediaTranscodeManager_enqueueTranscodingRequest }, { "native_cancelTranscodingRequest", "(J)V", (void *)android_media_MediaTranscodeManager_cancelTranscodingRequest }, }; } // namespace anonymous int register_android_media_MediaTranscodeManager(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaTranscodeManager", gMethods, NELEM(gMethods)); }
media/tests/MediaFrameworkTest/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ android_test { ], static_libs: [ "mockito-target-minus-junit4", "androidx.test.ext.junit", "androidx.test.rules", "android-ex-camera2", ], Loading