summaryrefslogtreecommitdiffstats
path: root/media/java/android/media/MediaCodec.java
diff options
context:
space:
mode:
Diffstat (limited to 'media/java/android/media/MediaCodec.java')
-rw-r--r--media/java/android/media/MediaCodec.java268
1 files changed, 255 insertions, 13 deletions
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index b0cd3e4..1f00c7b 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -300,6 +300,14 @@ final public class MediaCodec {
*/
@BufferFlag
public int flags;
+
+ /** @hide */
+ @NonNull
+ public BufferInfo dup() {
+ BufferInfo copy = new BufferInfo();
+ copy.set(offset, size, presentationTimeUs, flags);
+ return copy;
+ }
};
// The follow flag constants MUST stay in sync with their equivalents
@@ -343,11 +351,25 @@ final public class MediaCodec {
@Retention(RetentionPolicy.SOURCE)
public @interface BufferFlag {}
+ private static class FrameRenderedInfo {
+ public long mPresentationTimeUs;
+ public long mNanoTime;
+ public FrameRenderedInfo(long presentationTimeUs, long nanoTime) {
+ mPresentationTimeUs = presentationTimeUs;
+ mNanoTime = nanoTime;
+ }
+ }
+
private EventHandler mEventHandler;
+ private EventHandler mOnFrameRenderedHandler;
+ private EventHandler mCallbackHandler;
private Callback mCallback;
+ private OnFrameRenderedListener mOnFrameRenderedListener;
+ private Object mListenerLock = new Object();
private static final int EVENT_CALLBACK = 1;
private static final int EVENT_SET_CALLBACK = 2;
+ private static final int EVENT_FRAME_RENDERED = 3;
private static final int CB_INPUT_AVAILABLE = 1;
private static final int CB_OUTPUT_AVAILABLE = 2;
@@ -375,6 +397,15 @@ final public class MediaCodec {
mCallback = (MediaCodec.Callback) msg.obj;
break;
}
+ case EVENT_FRAME_RENDERED:
+ synchronized (mListenerLock) {
+ FrameRenderedInfo info = (FrameRenderedInfo)msg.obj;
+ if (mOnFrameRenderedListener != null) {
+ mOnFrameRenderedListener.onFrameRendered(
+ mCodec, info.mPresentationTimeUs, info.mNanoTime);
+ }
+ break;
+ }
default:
{
break;
@@ -431,6 +462,8 @@ final public class MediaCodec {
}
}
+ private boolean mHasSurface = false;
+
/**
* Instantiate a decoder supporting input data of the given mime type.
*
@@ -501,6 +534,9 @@ final public class MediaCodec {
} else {
mEventHandler = null;
}
+ mCallbackHandler = mEventHandler;
+ mOnFrameRenderedHandler = mEventHandler;
+
mBufferLock = new Object();
native_setup(name, nameIsType, encoder);
@@ -607,9 +643,66 @@ final public class MediaCodec {
}
}
+ mHasSurface = surface != null;
+
native_configure(keys, values, surface, crypto, flags);
}
+ /**
+ * Dynamically sets the output surface of a codec.
+ * <p>
+ * This can only be used if the codec was configured with an output surface. The
+ * new output surface should have a compatible usage type to the original output surface.
+ * E.g. codecs may not support switching from a SurfaceTexture (GPU readable) output
+ * to ImageReader (software readable) output.
+ * @param surface the output surface to use. It must not be {@code null}.
+ * @throws IllegalStateException if the codec does not support setting the output
+ * surface in the current state.
+ * @throws IllegalArgumentException if the new surface is not of a suitable type for the codec.
+ */
+ public void setSurface(@NonNull Surface surface) {
+ if (!mHasSurface) {
+ throw new IllegalStateException("codec was not configured for an output surface");
+ }
+
+ // TODO implement this
+ throw new IllegalArgumentException("codec does not support this surface");
+ }
+
+ /**
+ * Create a persistent input surface that can be used with codecs that normally have an input
+ * surface, such as video encoders. A persistent input can be reused by subsequent
+ * {@link MediaCodec} or {@link MediaRecorder} instances, but can only be used by at
+ * most one codec or recorder instance concurrently.
+ * <p>
+ * The application is responsible for calling release() on the Surface when done.
+ *
+ * @return an input surface that can be used with {@link #usePersistentInputSurface}.
+ */
+ @NonNull
+ public static Surface createPersistentInputSurface() {
+ // TODO implement this
+ return new PersistentSurface();
+ }
+
+ static class PersistentSurface extends Surface {
+ PersistentSurface() {}
+ };
+
+ /**
+ * Configures the codec (e.g. encoder) to use a persistent input surface in place of input
+ * buffers. This may only be called after {@link #configure} and before {@link #start}, in
+ * lieu of {@link #createInputSurface}.
+ * @param surface a persistent input surface created by {@link #createPersistentInputSurface}
+ * @throws IllegalStateException if not in the Configured state or does not require an input
+ * surface.
+ * @throws IllegalArgumentException if the surface was not created by
+ * {@link #createPersistentInputSurface}.
+ */
+ public void usePersistentInputSurface(@NonNull Surface surface) {
+ throw new IllegalArgumentException("not implemented");
+ }
+
private native final void native_setCallback(@Nullable Callback cb);
private native final void native_configure(
@@ -662,9 +755,14 @@ final public class MediaCodec {
native_stop();
freeAllTrackedBuffers();
- if (mEventHandler != null) {
- mEventHandler.removeMessages(EVENT_CALLBACK);
- mEventHandler.removeMessages(EVENT_SET_CALLBACK);
+ synchronized (mListenerLock) {
+ if (mCallbackHandler != null) {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
+ if (mOnFrameRenderedHandler != null) {
+ mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED);
+ }
}
}
@@ -1124,6 +1222,9 @@ final public class MediaCodec {
cacheBuffers(false /* input */);
} else if (res >= 0) {
validateOutputByteBuffer(mCachedOutputBuffers, res, info);
+ if (mHasSurface) {
+ mDequeuedOutputInfos.put(res, info.dup());
+ }
}
}
return res;
@@ -1150,13 +1251,34 @@ final public class MediaCodec {
* @throws MediaCodec.CodecException upon codec error.
*/
public final void releaseOutputBuffer(int index, boolean render) {
+ BufferInfo info = null;
synchronized(mBufferLock) {
invalidateByteBuffer(mCachedOutputBuffers, index);
mDequeuedOutputBuffers.remove(index);
+ if (mHasSurface) {
+ info = mDequeuedOutputInfos.remove(index);
+ }
}
+ // TODO
+ // until codec and libgui supports callback, assume frame is rendered within 50 ms
+ postRenderedCallback(render, info, 50 /* delayMs */);
releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
}
+ private void postRenderedCallback(boolean render, @Nullable BufferInfo info, long delayMs) {
+ if (render && info != null) {
+ synchronized (mListenerLock) {
+ if (mOnFrameRenderedListener != null) {
+ FrameRenderedInfo obj = new FrameRenderedInfo(
+ info.presentationTimeUs, System.nanoTime() + delayMs * 1000000);
+ Message msg = mOnFrameRenderedHandler.obtainMessage(
+ EVENT_FRAME_RENDERED, obj);
+ mOnFrameRenderedHandler.sendMessageDelayed(msg, delayMs);
+ }
+ }
+ }
+ }
+
/**
* If you are done with a buffer, use this call to update its surface timestamp
* and return it to the codec to render it on the output surface. If you
@@ -1207,10 +1329,20 @@ final public class MediaCodec {
* @throws MediaCodec.CodecException upon codec error.
*/
public final void releaseOutputBuffer(int index, long renderTimestampNs) {
+ BufferInfo info = null;
synchronized(mBufferLock) {
invalidateByteBuffer(mCachedOutputBuffers, index);
mDequeuedOutputBuffers.remove(index);
+ if (mHasSurface) {
+ info = mDequeuedOutputInfos.remove(index);
+ }
}
+ // TODO
+ // until codec and libgui supports callback, assume frame is rendered at the
+ // render time or 16 ms from now, whichever is later.
+ postRenderedCallback(
+ true /* render */, info,
+ Math.max(renderTimestampNs - System.nanoTime(), 16666666) / 1000000);
releaseOutputBuffer(
index, true /* render */, true /* updatePTS */, renderTimestampNs);
}
@@ -1350,6 +1482,8 @@ final public class MediaCodec {
private ByteBuffer[] mCachedOutputBuffers;
private final BufferMap mDequeuedInputBuffers = new BufferMap();
private final BufferMap mDequeuedOutputBuffers = new BufferMap();
+ private final Map<Integer, BufferInfo> mDequeuedOutputInfos =
+ new HashMap<Integer, BufferInfo>();
final private Object mBufferLock;
private final void invalidateByteBuffer(
@@ -1715,21 +1849,121 @@ final public class MediaCodec {
* @param cb The callback that will run. Use {@code null} to clear a previously
* set callback (before {@link #configure configure} is called and run
* in synchronous mode).
+ * @param handler Callbacks will happen on the handler's thread. If {@code null},
+ * callbacks are done on the default thread (the caller's thread or the
+ * main thread.)
*/
- public void setCallback(@Nullable /* MediaCodec. */ Callback cb) {
- if (mEventHandler != null) {
- // set java callback on handler
- Message msg = mEventHandler.obtainMessage(EVENT_SET_CALLBACK, 0, 0, cb);
- mEventHandler.sendMessage(msg);
+ public void setCallback(@Nullable /* MediaCodec. */ Callback cb, @Nullable Handler handler) {
+ if (cb != null) {
+ synchronized (mListenerLock) {
+ EventHandler newHandler = getEventHandlerOn(handler, mCallbackHandler);
+ // NOTE: there are no callbacks on the handler at this time, but check anyways
+ // even if we were to extend this to be callable dynamically, it must
+ // be called when codec is flushed, so no messages are pending.
+ if (newHandler != mCallbackHandler) {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ mCallbackHandler = newHandler;
+ }
+ }
+ } else if (mCallbackHandler != null) {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
+
+ if (mCallbackHandler != null) {
+ // set java callback on main handler
+ Message msg = mCallbackHandler.obtainMessage(EVENT_SET_CALLBACK, 0, 0, cb);
+ mCallbackHandler.sendMessage(msg);
// set native handler here, don't post to handler because
- // it may cause the callback to be delayed and set in a wrong state,
- // and MediaCodec is already doing it on looper.
+ // it may cause the callback to be delayed and set in a wrong state.
+ // Note that native codec may start sending events to the callback
+ // handler after this returns.
native_setCallback(cb);
}
}
/**
+ * Sets an asynchronous callback for actionable MediaCodec events on the default
+ * looper.
+ * <p>
+ * Same as {@link #setCallback(Callback, Handler)} with handler set to null.
+ * @param cb The callback that will run. Use {@code null} to clear a previously
+ * set callback (before {@link #configure configure} is called and run
+ * in synchronous mode).
+ * @see #setCallback(Callback, Handler)
+ */
+ public void setCallback(@Nullable /* MediaCodec. */ Callback cb) {
+ setCallback(cb, null /* handler */);
+ }
+
+ /**
+ * Listener to be called when an output frame has rendered on the output surface
+ *
+ * @see MediaCodec#setOnFrameRenderedListener
+ */
+ public interface OnFrameRenderedListener {
+
+ /**
+ * Called when an output frame has rendered on the output surface.
+ *
+ * @param codec the MediaCodec instance
+ * @param presentationTimeUs the presentation time (media time) of the frame rendered.
+ * This is usually the same as specified in {@link #queueInputBuffer}; however,
+ * some codecs may alter the media time by applying some time-based transformation,
+ * such as frame rate conversion. In that case, presentation time corresponds
+ * to the actual output frame rendered.
+ * @param nanoTime The system time when the frame was rendered.
+ *
+ * @see System#nanoTime
+ */
+ public void onFrameRendered(
+ @NonNull MediaCodec codec, long presentationTimeUs, long nanoTime);
+ }
+
+ /**
+ * Register a callback to be invoked when an output frame is rendered on the output surface.
+ * <p>
+ * This method can be called in any codec state, but will only have an effect in the
+ * Executing state for codecs that render buffers to the output surface.
+ *
+ * @param listener the callback that will be run
+ * @param handler the callback will be run on the handler's thread. If {@code null},
+ * the callback will be run on the default thread, which is the looper
+ * from which the codec was created, or a new thread if there was none.
+ */
+ public void setOnFrameRenderedListener(
+ @Nullable OnFrameRenderedListener listener, @Nullable Handler handler) {
+ synchronized (mListenerLock) {
+ mOnFrameRenderedListener = listener;
+ if (listener != null) {
+ EventHandler newHandler = getEventHandlerOn(handler, mOnFrameRenderedHandler);
+ if (newHandler != mOnFrameRenderedHandler) {
+ mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED);
+ }
+ mOnFrameRenderedHandler = newHandler;
+ } else if (mOnFrameRenderedHandler != null) {
+ mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED);
+ }
+ }
+ }
+
+ private EventHandler getEventHandlerOn(
+ @Nullable Handler handler, @NonNull EventHandler lastHandler) {
+ if (handler == null) {
+ return mEventHandler;
+ } else {
+ Looper looper = handler.getLooper();
+ if (lastHandler.getLooper() == looper) {
+ return lastHandler;
+ } else {
+ return new EventHandler(this, looper);
+ }
+ }
+ }
+
+ /**
* MediaCodec callback interface. Used to notify the user asynchronously
* of various MediaCodec events.
*/
@@ -1772,9 +2006,17 @@ final public class MediaCodec {
private void postEventFromNative(
int what, int arg1, int arg2, @Nullable Object obj) {
- if (mEventHandler != null) {
- Message msg = mEventHandler.obtainMessage(what, arg1, arg2, obj);
- mEventHandler.sendMessage(msg);
+ synchronized (mListenerLock) {
+ EventHandler handler = mEventHandler;
+ if (what == EVENT_CALLBACK) {
+ handler = mCallbackHandler;
+ } else if (what == EVENT_FRAME_RENDERED) {
+ handler = mOnFrameRenderedHandler;
+ }
+ if (handler != null) {
+ Message msg = handler.obtainMessage(what, arg1, arg2, obj);
+ handler.sendMessage(msg);
+ }
}
}