diff --git a/core/api/current.txt b/core/api/current.txt index 001e960d2867f..ec08af2d6c04c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -21325,6 +21325,7 @@ package android.media { method public void setCallback(@Nullable android.media.MediaCodec.Callback, @Nullable android.os.Handler); method public void setCallback(@Nullable android.media.MediaCodec.Callback); method public void setInputSurface(@NonNull android.view.Surface); + method public void setOnFirstTunnelFrameReadyListener(@Nullable android.os.Handler, @Nullable android.media.MediaCodec.OnFirstTunnelFrameReadyListener); method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler); method public void setOutputSurface(@NonNull android.view.Surface); method public void setParameters(@Nullable android.os.Bundle); @@ -21352,6 +21353,7 @@ package android.media { field public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; field public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames"; field public static final String PARAMETER_KEY_SUSPEND_TIME = "drop-start-time-us"; + field public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek"; field public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate"; field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1 field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2 @@ -21442,6 +21444,10 @@ package android.media { field public static final String WIDTH = "android.media.mediacodec.width"; } + public static interface MediaCodec.OnFirstTunnelFrameReadyListener { + method public void onFirstTunnelFrameReady(@NonNull android.media.MediaCodec); + } + public static interface MediaCodec.OnFrameRenderedListener { method public void onFrameRendered(@NonNull android.media.MediaCodec, long, long); } diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index cf31e4141a6dd..a7e2b65ebb8ff 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -1674,9 +1674,11 @@ final public class MediaCodec implements PlaybackComponent { public @interface BufferFlag {} private EventHandler mEventHandler; + private EventHandler mOnFirstTunnelFrameReadyHandler; private EventHandler mOnFrameRenderedHandler; private EventHandler mCallbackHandler; private Callback mCallback; + private OnFirstTunnelFrameReadyListener mOnFirstTunnelFrameReadyListener; private OnFrameRenderedListener mOnFrameRenderedListener; private final Object mListenerLock = new Object(); private MediaCodecInfo mCodecInfo; @@ -1687,6 +1689,7 @@ final public class MediaCodec implements PlaybackComponent { 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 EVENT_FIRST_TUNNEL_FRAME_READY = 4; private static final int CB_INPUT_AVAILABLE = 1; private static final int CB_OUTPUT_AVAILABLE = 2; @@ -1748,6 +1751,16 @@ final public class MediaCodec implements PlaybackComponent { mCodec, (long)mediaTimeUs, (long)systemNano); } break; + case EVENT_FIRST_TUNNEL_FRAME_READY: + OnFirstTunnelFrameReadyListener onFirstTunnelFrameReadyListener; + synchronized (mListenerLock) { + onFirstTunnelFrameReadyListener = mOnFirstTunnelFrameReadyListener; + } + if (onFirstTunnelFrameReadyListener == null) { + break; + } + onFirstTunnelFrameReadyListener.onFirstTunnelFrameReady(mCodec); + break; default: { break; @@ -1923,6 +1936,7 @@ final public class MediaCodec implements PlaybackComponent { mEventHandler = null; } mCallbackHandler = mEventHandler; + mOnFirstTunnelFrameReadyHandler = mEventHandler; mOnFrameRenderedHandler = mEventHandler; mBufferLock = new Object(); @@ -2277,6 +2291,9 @@ final public class MediaCodec implements PlaybackComponent { mCallbackHandler.removeMessages(EVENT_SET_CALLBACK); mCallbackHandler.removeMessages(EVENT_CALLBACK); } + if (mOnFirstTunnelFrameReadyHandler != null) { + mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY); + } if (mOnFrameRenderedHandler != null) { mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED); } @@ -4446,6 +4463,41 @@ final public class MediaCodec implements PlaybackComponent { public static final String PARAMETER_KEY_LOW_LATENCY = MediaFormat.KEY_LOW_LATENCY; + /** + * Control video peek of the first frame when a codec is configured for tunnel mode with + * {@link MediaFormat#KEY_AUDIO_SESSION_ID} while the {@link AudioTrack} is paused. + *
+ * When disabled (1) after a {@link #flush} or {@link #start}, (2) while the corresponding + * {@link AudioTrack} is paused and (3) before any buffers are queued, the first frame is not to + * be rendered until either this parameter is enabled or the corresponding {@link AudioTrack} + * has begun playback. Once the frame is decoded and ready to be rendered, + * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called but the frame is + * not rendered. The surface continues to show the previously-rendered content, or black if the + * surface is new. A subsequent call to {@link AudioTrack#play} renders this frame and triggers + * a callback to {@link OnFrameRenderedListener#onFrameRendered}, and video playback begins. + *
+ * Note: To clear any previously rendered content and show black, configure the + * MediaCodec with {@code KEY_PUSH_BLANK_BUFFERS_ON_STOP(1)}, and call {@link #stop} before + * pushing new video frames to the codec. + *
+ * When enabled (1) after a {@link #flush} or {@link #start} and (2) while the corresponding + * {@link AudioTrack} is paused, the first frame is rendered as soon as it is decoded, or + * immediately, if it has already been decoded. If not already decoded, when the frame is + * decoded and ready to be rendered, + * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called. The frame is then + * immediately rendered and {@link OnFrameRenderedListener#onFrameRendered} is subsequently + * called. + *
+ * The value is an Integer object containing the value 1 to enable or the value 0 to disable. + *
+ * The default for this parameter is enabled. Once a frame has been rendered, changing
+ * this parameter has no effect until a subsequent {@link #flush} or
+ * {@link #stop}/{@link #start}.
+ *
+ * @see #setParameters(Bundle)
+ */
+ public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek";
+
/**
* Communicate additional parameter changes to the component instance.
* Note: Some of these parameter changes may silently fail to apply.
@@ -4544,6 +4596,55 @@ final public class MediaCodec implements PlaybackComponent {
setCallback(cb, null /* handler */);
}
+ /**
+ * Listener to be called when the first output frame has been decoded
+ * and is ready to be rendered for a codec configured for tunnel mode with
+ * {@code KEY_AUDIO_SESSION_ID}.
+ *
+ * @see MediaCodec#setOnFirstTunnelFrameReadyListener
+ */
+ public interface OnFirstTunnelFrameReadyListener {
+
+ /**
+ * Called when the first output frame has been decoded and is ready to be
+ * rendered.
+ */
+ void onFirstTunnelFrameReady(@NonNull MediaCodec codec);
+ }
+
+ /**
+ * Registers a callback to be invoked when the first output frame has been decoded
+ * and is ready to be rendered on a codec configured for tunnel mode with {@code
+ * KEY_AUDIO_SESSION_ID}.
+ *
+ * @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.
+ *
+ * @param listener the callback that will be run. If {@code null}, clears any registered
+ * listener.
+ */
+ public void setOnFirstTunnelFrameReadyListener(
+ @Nullable Handler handler, @Nullable OnFirstTunnelFrameReadyListener listener) {
+ synchronized (mListenerLock) {
+ mOnFirstTunnelFrameReadyListener = listener;
+ if (listener != null) {
+ EventHandler newHandler = getEventHandlerOn(
+ handler,
+ mOnFirstTunnelFrameReadyHandler);
+ if (newHandler != mOnFirstTunnelFrameReadyHandler) {
+ mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+ }
+ mOnFirstTunnelFrameReadyHandler = newHandler;
+ } else if (mOnFirstTunnelFrameReadyHandler != null) {
+ mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+ }
+ native_enableOnFirstTunnelFrameReadyListener(listener != null);
+ }
+ }
+
+ private native void native_enableOnFirstTunnelFrameReadyListener(boolean enable);
+
/**
* Listener to be called when an output frame has rendered on the output surface
*
@@ -4667,6 +4768,8 @@ final public class MediaCodec implements PlaybackComponent {
EventHandler handler = mEventHandler;
if (what == EVENT_CALLBACK) {
handler = mCallbackHandler;
+ } else if (what == EVENT_FIRST_TUNNEL_FRAME_READY) {
+ handler = mOnFirstTunnelFrameReadyHandler;
} else if (what == EVENT_FRAME_RENDERED) {
handler = mOnFrameRenderedHandler;
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 7f5dd5d15dbec..ded2e1b4e9390 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -81,6 +81,7 @@ enum {
EVENT_CALLBACK = 1,
EVENT_SET_CALLBACK = 2,
EVENT_FRAME_RENDERED = 3,
+ EVENT_FIRST_TUNNEL_FRAME_READY = 4,
};
static struct CryptoErrorCodes {
@@ -269,6 +270,18 @@ JMediaCodec::~JMediaCodec() {
mClass = NULL;
}
+status_t JMediaCodec::enableOnFirstTunnelFrameReadyListener(jboolean enable) {
+ if (enable) {
+ if (mOnFirstTunnelFrameReadyNotification == NULL) {
+ mOnFirstTunnelFrameReadyNotification = new AMessage(kWhatFirstTunnelFrameReady, this);
+ }
+ } else {
+ mOnFirstTunnelFrameReadyNotification.clear();
+ }
+
+ return mCodec->setOnFirstTunnelFrameReadyNotification(mOnFirstTunnelFrameReadyNotification);
+}
+
status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) {
if (enable) {
if (mOnFrameRenderedNotification == NULL) {
@@ -1058,6 +1071,27 @@ void JMediaCodec::handleCallback(const sp