diff options
Diffstat (limited to 'media/java/android/media/MediaCodec.java')
| -rw-r--r-- | media/java/android/media/MediaCodec.java | 268 |
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); + } } } |
