#include <OMX_Core.h>
#include <OMX_Component.h>
#include "omxil_utils.h"
+#include "android_opaque.h"
#define INFO_OUTPUT_BUFFERS_CHANGED -3
#define INFO_OUTPUT_FORMAT_CHANGED -2
#define INFO_TRY_AGAIN_LATER -1
extern JavaVM *myVm;
+/* JNI functions to get/set an Android Surface object. */
+extern jobject jni_LockAndGetAndroidJavaSurface();
+extern void jni_UnlockAndroidSurface();
+extern void jni_SetAndroidSurfaceSizeEnv(JNIEnv *p_env, int width, int height, int visible_width, int visible_height, int sar_num, int sar_den);
struct decoder_sys_t
{
bool decoded;
ArchitectureSpecificCopyData architecture_specific_data;
+
+ /* Direct rendering members. */
+ bool direct_rendering;
+ int i_output_buffers; /**< number of MediaCodec output buffers */
+ picture_t** inflight_picture; /**< stores the inflight picture for each output buffer or NULL */
};
enum Types
static picture_t *DecodeVideo(decoder_t *, block_t **);
+static void InvalidateAllPictures(decoder_t *);
+
/*****************************************************************************
* Module descriptor
*****************************************************************************/
+#define DIRECTRENDERING_TEXT N_("Android direct rendering")
+#define DIRECTRENDERING_LONGTEXT N_(\
+ "Enable Android direct rendering using opaque buffers.")
+
+#define CFG_PREFIX "mediacodec-"
+
vlc_module_begin ()
set_description( N_("Video decoder using Android MediaCodec") )
set_category( CAT_INPUT )
set_subcategory( SUBCAT_INPUT_VCODEC )
set_section( N_("Decoding") , NULL )
set_capability( "decoder", 0 ) /* Only enabled via commandline arguments */
+ add_bool(CFG_PREFIX "dr", true,
+ DIRECTRENDERING_TEXT, DIRECTRENDERING_LONGTEXT, true)
set_callbacks( OpenDecoder, CloseDecoder )
vlc_module_end ()
(*env)->DeleteLocalRef(env, bytebuf);
}
- (*env)->CallVoidMethod(env, p_sys->codec, p_sys->configure, format, NULL, NULL, 0);
- if ((*env)->ExceptionOccurred(env)) {
- msg_Warn(p_dec, "Exception occurred in MediaCodec.configure");
- (*env)->ExceptionClear(env);
- goto error;
+ p_sys->direct_rendering = var_InheritBool(p_dec, CFG_PREFIX "dr");
+ if (p_sys->direct_rendering) {
+ jobject surf = jni_LockAndGetAndroidJavaSurface();
+ if (surf) {
+ // Configure MediaCodec with the Android surface.
+ (*env)->CallVoidMethod(env, p_sys->codec, p_sys->configure, format, surf, NULL, 0);
+ if ((*env)->ExceptionOccurred(env)) {
+ msg_Warn(p_dec, "Exception occurred in MediaCodec.configure");
+ (*env)->ExceptionClear(env);
+ goto error;
+ }
+ p_dec->fmt_out.i_codec = VLC_CODEC_ANDROID_OPAQUE;
+ } else {
+ msg_Warn(p_dec, "Failed to get the Android Surface, disabling direct rendering.");
+ p_sys->direct_rendering = false;
+ }
+ jni_UnlockAndroidSurface();
}
+ if (!p_sys->direct_rendering) {
+ (*env)->CallVoidMethod(env, p_sys->codec, p_sys->configure, format, NULL, NULL, 0);
+ if ((*env)->ExceptionOccurred(env)) {
+ msg_Warn(p_dec, "Exception occurred in MediaCodec.configure");
+ (*env)->ExceptionClear(env);
+ goto error;
+ }
+ }
+
(*env)->CallVoidMethod(env, p_sys->codec, p_sys->start);
if ((*env)->ExceptionOccurred(env)) {
msg_Warn(p_dec, "Exception occurred in MediaCodec.start");
p_sys->input_buffers = (*env)->NewGlobalRef(env, p_sys->input_buffers);
p_sys->output_buffers = (*env)->NewGlobalRef(env, p_sys->output_buffers);
p_sys->buffer_info = (*env)->NewGlobalRef(env, p_sys->buffer_info);
+ p_sys->i_output_buffers = (*env)->GetArrayLength(env, p_sys->output_buffers);
+ p_sys->inflight_picture = calloc(1, sizeof(picture_t*) * p_sys->i_output_buffers);
+ if (!p_sys->inflight_picture)
+ goto error;
(*env)->DeleteLocalRef(env, format);
(*myVm)->DetachCurrentThread(myVm);
if (!p_sys)
return;
+ /* Invalidate all pictures that are currently in flight in order
+ * to prevent the vout from using destroyed output buffers. */
+ if (p_sys->direct_rendering)
+ InvalidateAllPictures(p_dec);
(*myVm)->AttachCurrentThread(myVm, &env, NULL);
if (p_sys->input_buffers)
(*env)->DeleteGlobalRef(env, p_sys->input_buffers);
free(p_sys->name);
ArchitectureSpecificCopyHooksDestroy(p_sys->pixel_format, &p_sys->architecture_specific_data);
+ free(p_sys->inflight_picture);
free(p_sys);
}
+/*****************************************************************************
+ * vout callbacks
+ *****************************************************************************/
+static void DisplayBuffer(picture_sys_t* p_picsys, bool b_render)
+{
+ decoder_t *p_dec = p_picsys->p_dec;
+ decoder_sys_t *p_sys = p_dec->p_sys;
+
+ if (!p_picsys->b_valid)
+ return;
+
+ vlc_mutex_lock(get_android_opaque_mutex());
+
+ /* Picture might have been invalidated while waiting on the mutex. */
+ if (!p_picsys->b_valid)
+ {
+ vlc_mutex_unlock(get_android_opaque_mutex());
+ return;
+ }
+
+ uint32_t i_index = p_picsys->i_index;
+ p_sys->inflight_picture[i_index] = NULL;
+
+ /* Release the MediaCodec buffer. */
+ JNIEnv *env = NULL;
+ (*myVm)->AttachCurrentThread(myVm, &env, NULL);
+ (*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, i_index, b_render);
+ if ((*env)->ExceptionOccurred(env)) {
+ msg_Err(p_dec, "Exception in MediaCodec.releaseOutputBuffer (DisplayBuffer)");
+ (*env)->ExceptionClear(env);
+ }
+
+ (*myVm)->DetachCurrentThread(myVm);
+ p_picsys->b_valid = false;
+
+ vlc_mutex_unlock(get_android_opaque_mutex());
+}
+
+static void UnlockCallback(picture_sys_t* p_picsys)
+{
+ DisplayBuffer(p_picsys, false);
+}
+
+static void DisplayCallback(picture_sys_t* p_picsys)
+{
+ DisplayBuffer(p_picsys, true);
+}
+
+static void InvalidateAllPictures(decoder_t *p_dec)
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+
+ vlc_mutex_lock(get_android_opaque_mutex());
+ for (int i = 0; i < p_sys->i_output_buffers; ++i)
+ {
+ picture_t *p_pic = p_sys->inflight_picture[i];
+ if (p_pic) {
+ p_pic->p_sys->b_valid = false;
+ p_sys->inflight_picture[i] = NULL;
+ }
+ }
+ vlc_mutex_unlock(get_android_opaque_mutex());
+}
+
static void GetOutput(decoder_t *p_dec, JNIEnv *env, picture_t **pp_pic)
{
decoder_sys_t *p_sys = p_dec->p_sys;
(*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, index, false);
continue;
}
- jobject buf = (*env)->GetObjectArrayElement(env, p_sys->output_buffers, index);
- jsize buf_size = (*env)->GetDirectBufferCapacity(env, buf);
- uint8_t *ptr = (*env)->GetDirectBufferAddress(env, buf);
- if (!*pp_pic)
+
+ if (!*pp_pic) {
*pp_pic = decoder_NewPicture(p_dec);
+ } else if (p_sys->direct_rendering) {
+ picture_t *p_pic = *pp_pic;
+ picture_sys_t *p_picsys = p_pic->p_sys;
+ int i_prev_index = p_picsys->i_index;
+ (*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, i_prev_index, false);
+
+ // No need to lock here since the previous picture was not sent.
+ p_sys->inflight_picture[i_prev_index] = NULL;
+ }
if (*pp_pic) {
+
picture_t *p_pic = *pp_pic;
- int size = (*env)->GetIntField(env, p_sys->buffer_info, p_sys->size_field);
- int offset = (*env)->GetIntField(env, p_sys->buffer_info, p_sys->offset_field);
- ptr += offset; // Check the size parameter as well
// TODO: Use crop_top/crop_left as well? Or is that already taken into account?
// On OMX_TI_COLOR_FormatYUV420PackedSemiPlanar the offset already incldues
// the cropping, so the top/left cropping params should just be ignored.
- unsigned int chroma_div;
p_pic->date = (*env)->GetLongField(env, p_sys->buffer_info, p_sys->pts_field);
- GetVlcChromaSizes(p_dec->fmt_out.i_codec, p_dec->fmt_out.video.i_width,
- p_dec->fmt_out.video.i_height, NULL, NULL, &chroma_div);
- CopyOmxPicture(p_sys->pixel_format, p_pic, p_sys->slice_height, p_sys->stride,
- ptr, chroma_div, &p_sys->architecture_specific_data);
- }
- (*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, index, false);
- jthrowable exception = (*env)->ExceptionOccurred(env);
- if(exception != NULL) {
- jclass illegalStateException = (*env)->FindClass(env, "java/lang/IllegalStateException");
- if((*env)->IsInstanceOf(env, exception, illegalStateException)) {
- msg_Err(p_dec, "Codec error (IllegalStateException) in MediaCodec.releaseOutputBuffer");
- (*env)->ExceptionClear(env);
- (*env)->DeleteLocalRef(env, illegalStateException);
+ if (p_sys->direct_rendering) {
+ picture_sys_t *p_picsys = p_pic->p_sys;
+ p_picsys->pf_display_callback = DisplayCallback;
+ p_picsys->pf_unlock_callback = UnlockCallback;
+ p_picsys->p_dec = p_dec;
+ p_picsys->i_index = index;
+ p_picsys->b_valid = true;
+
+ p_sys->inflight_picture[index] = p_pic;
+ } else {
+ jobject buf = (*env)->GetObjectArrayElement(env, p_sys->output_buffers, index);
+ jsize buf_size = (*env)->GetDirectBufferCapacity(env, buf);
+ uint8_t *ptr = (*env)->GetDirectBufferAddress(env, buf);
+
+ int size = (*env)->GetIntField(env, p_sys->buffer_info, p_sys->size_field);
+ int offset = (*env)->GetIntField(env, p_sys->buffer_info, p_sys->offset_field);
+ ptr += offset; // Check the size parameter as well
+
+ unsigned int chroma_div;
+ GetVlcChromaSizes(p_dec->fmt_out.i_codec, p_dec->fmt_out.video.i_width,
+ p_dec->fmt_out.video.i_height, NULL, NULL, &chroma_div);
+ CopyOmxPicture(p_sys->pixel_format, p_pic, p_sys->slice_height, p_sys->stride,
+ ptr, chroma_div, &p_sys->architecture_specific_data);
+ (*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, index, false);
+
+ jthrowable exception = (*env)->ExceptionOccurred(env);
+ if(exception != NULL) {
+ jclass illegalStateException = (*env)->FindClass(env, "java/lang/IllegalStateException");
+ if((*env)->IsInstanceOf(env, exception, illegalStateException)) {
+ msg_Err(p_dec, "Codec error (IllegalStateException) in MediaCodec.releaseOutputBuffer");
+ (*env)->ExceptionClear(env);
+ (*env)->DeleteLocalRef(env, illegalStateException);
+ }
+ }
+ (*env)->DeleteLocalRef(env, buf);
}
+ } else {
+ msg_Warn(p_dec, "NewPicture failed");
+ (*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, index, false);
}
- (*env)->DeleteLocalRef(env, buf);
+
return;
} else if (index == INFO_OUTPUT_BUFFERS_CHANGED) {
msg_Dbg(p_dec, "output buffers changed");
(*env)->DeleteGlobalRef(env, p_sys->output_buffers);
+
p_sys->output_buffers = (*env)->CallObjectMethod(env, p_sys->codec,
p_sys->get_output_buffers);
p_sys->output_buffers = (*env)->NewGlobalRef(env, p_sys->output_buffers);
+
+ vlc_mutex_lock(get_android_opaque_mutex());
+ free(p_sys->inflight_picture);
+ p_sys->i_output_buffers = (*env)->GetArrayLength(env, p_sys->output_buffers);
+ p_sys->inflight_picture = calloc(1, sizeof(picture_t*) * p_sys->i_output_buffers);
+ vlc_mutex_unlock(get_android_opaque_mutex());
+
} else if (index == INFO_OUTPUT_FORMAT_CHANGED) {
jobject format = (*env)->CallObjectMethod(env, p_sys->codec, p_sys->get_output_format);
int crop_bottom = GET_INTEGER(format, "crop-bottom");
const char *name = "unknown";
- GetVlcChromaFormat(p_sys->pixel_format, &p_dec->fmt_out.i_codec, &name);
+ if (p_sys->direct_rendering)
+ jni_SetAndroidSurfaceSizeEnv(env, width, height, width, height, 1, 1);
+ else
+ GetVlcChromaFormat(p_sys->pixel_format, &p_dec->fmt_out.i_codec, &name);
+
msg_Dbg(p_dec, "output: %d %s, %dx%d stride %d %d, crop %d %d %d %d",
p_sys->pixel_format, name, width, height, p_sys->stride, p_sys->slice_height,
p_sys->crop_left, p_sys->crop_top, crop_right, crop_bottom);
if (p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED)) {
block_Release(p_block);
if (p_sys->decoded) {
+ /* Invalidate all pictures that are currently in flight
+ * since flushing make all previous indices returned by
+ * MediaCodec invalid. */
+ if (p_sys->direct_rendering)
+ InvalidateAllPictures(p_dec);
+
(*env)->CallVoidMethod(env, p_sys->codec, p_sys->flush);
if ((*env)->ExceptionOccurred(env)) {
msg_Warn(p_dec, "Exception occurred in MediaCodec.flush");
}
jlong timeout = 0;
+ const int max_polling_attempts = 50;
+ int attempts = 0;
while (true) {
int index = (*env)->CallIntMethod(env, p_sys->codec, p_sys->dequeue_input_buffer, timeout);
if (index < 0) {
return p_pic;
}
timeout = 30*1000;
+ ++attempts;
+ /* With opaque DR the output buffers are released by the
+ vout therefore we implement a timeout for polling in
+ order to avoid being indefinitely stalled in this loop. */
+ if (p_sys->direct_rendering && attempts == max_polling_attempts) {
+ picture_t *invalid_picture = decoder_NewPicture(p_dec);
+ if (invalid_picture) {
+ invalid_picture->date = VLC_TS_INVALID;
+ picture_sys_t *p_picsys = invalid_picture->p_sys;
+ p_picsys->pf_display_callback = NULL;
+ p_picsys->pf_unlock_callback = NULL;
+ p_picsys->p_dec = NULL;
+ p_picsys->i_index = -1;
+ p_picsys->b_valid = false;
+ }
+ else {
+ /* If we cannot return a picture we must free the
+ block since the decoder will proceed with the
+ next block. */
+ block_Release(p_block);
+ *pp_block = NULL;
+ }
+ (*myVm)->DetachCurrentThread(myVm);
+ return invalid_picture;
+ }
continue;
}
jobject buf = (*env)->GetObjectArrayElement(env, p_sys->input_buffers, index);
--- /dev/null
+/*****************************************************************************
+ * opaque.c: Android video output module using direct rendering with
+ * opaque buffers
+ *****************************************************************************
+ * Copyright (C) 2013 Felix Abecassis
+ *
+ * Authors: Felix Abecassis <felix.abecassis@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_vout_display.h>
+#include <vlc_picture_pool.h>
+#include "../codec/omxil/android_opaque.h"
+
+static int Open (vlc_object_t *);
+static void Close(vlc_object_t *);
+
+vlc_module_begin()
+ set_category(CAT_VIDEO)
+ set_subcategory(SUBCAT_VIDEO_VOUT)
+ set_shortname("vout_mediacodec")
+ set_description(N_("Android MediaCodec direct rendering video output"))
+ set_capability("vout display", 200)
+ add_shortcut("androidsurface", "android")
+ set_callbacks(Open, Close)
+vlc_module_end()
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+
+static picture_pool_t *Pool (vout_display_t *, unsigned);
+static void Display(vout_display_t *, picture_t *, subpicture_t *);
+static int Control(vout_display_t *, int, va_list);
+
+struct vout_display_sys_t
+{
+ picture_pool_t *pool;
+};
+
+static int LockSurface(picture_t *);
+static void UnlockSurface(picture_t *);
+
+/* We need to allocate a picture pool of more than 30 buffers in order
+ * to be connected directly to the decoder without any intermediate
+ * buffer pool. */
+#define POOL_SIZE 31
+
+static int Open(vlc_object_t *p_this)
+{
+ vout_display_t *vd = (vout_display_t*)p_this;
+
+ video_format_t fmt = vd->fmt;
+
+ if (fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE)
+ return VLC_EGENERIC;
+
+ /* Allocate structure */
+ vout_display_sys_t *sys = (struct vout_display_sys_t*)calloc(1, sizeof(*sys));
+ if (!sys)
+ return VLC_ENOMEM;
+
+ int i_pictures = POOL_SIZE;
+ picture_t** pictures = calloc(sizeof(*pictures), i_pictures);
+ if (!pictures)
+ goto error;
+ for (int i = 0; i < i_pictures; i++)
+ {
+ picture_sys_t *p_picsys = calloc(1, sizeof(*p_picsys));
+ if (unlikely(p_picsys == NULL))
+ goto error;
+
+ picture_resource_t resource = { .p_sys = p_picsys };
+ picture_t *picture = picture_NewFromResource(&fmt, &resource);
+ if (!picture)
+ {
+ free(p_picsys);
+ goto error;
+ }
+ pictures[i] = picture;
+ }
+
+ /* Wrap it into a picture pool */
+ picture_pool_configuration_t pool_cfg;
+ memset(&pool_cfg, 0, sizeof(pool_cfg));
+ pool_cfg.picture_count = i_pictures;
+ pool_cfg.picture = pictures;
+ pool_cfg.lock = LockSurface;
+ pool_cfg.unlock = UnlockSurface;
+
+ sys->pool = picture_pool_NewExtended(&pool_cfg);
+ if (!sys->pool)
+ {
+ for (int i = 0; i < i_pictures; i++)
+ picture_Release(pictures[i]);
+ goto error;
+ }
+
+ /* Setup vout_display */
+ vd->sys = sys;
+ vd->fmt = fmt;
+ vd->pool = Pool;
+ vd->display = Display;
+ vd->control = Control;
+ vd->prepare = NULL;
+ vd->manage = NULL;
+
+ /* Fix initial state */
+ vout_display_SendEventFullscreen(vd, false);
+
+ return VLC_SUCCESS;
+
+error:
+ free(pictures);
+ Close(p_this);
+ return VLC_ENOMEM;
+}
+
+static void Close(vlc_object_t *p_this)
+{
+ vout_display_t *vd = (vout_display_t *)p_this;
+ vout_display_sys_t *sys = vd->sys;
+
+ picture_pool_Delete(sys->pool);
+ free(sys);
+}
+
+static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
+{
+ VLC_UNUSED(count);
+
+ return vd->sys->pool;
+}
+
+static int LockSurface(picture_t *picture)
+{
+ VLC_UNUSED(picture);
+
+ return VLC_SUCCESS;
+}
+
+static void UnlockSurface(picture_t *picture)
+{
+ picture_sys_t *p_picsys = picture->p_sys;
+ void (*unlock_callback)(picture_sys_t*) = p_picsys->pf_unlock_callback;
+ if (unlock_callback)
+ unlock_callback(p_picsys);
+}
+
+static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
+{
+ VLC_UNUSED(vd);
+ VLC_UNUSED(subpicture);
+
+ picture_sys_t *p_picsys = picture->p_sys;
+ void (*display_callback)(picture_sys_t*) = p_picsys->pf_display_callback;
+ if (display_callback)
+ display_callback(p_picsys);
+
+ /* refcount lowers to 0, and pool_cfg.unlock is called */
+ picture_Release(picture);
+}
+
+static int Control(vout_display_t *vd, int query, va_list args)
+{
+ VLC_UNUSED(args);
+
+ switch (query) {
+ case VOUT_DISPLAY_HIDE_MOUSE:
+ return VLC_SUCCESS;
+
+ default:
+ msg_Err(vd, "Unknown request in vout mediacodec display");
+
+ case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+ case VOUT_DISPLAY_CHANGE_FULLSCREEN:
+ case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
+ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+ case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+ case VOUT_DISPLAY_CHANGE_ZOOM:
+ case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+ case VOUT_DISPLAY_GET_OPENGL:
+ return VLC_EGENERIC;
+ }
+}