modules: add android_window vout
authorThomas Guillem <thomas@gllm.fr>
Fri, 14 Nov 2014 16:09:59 +0000 (17:09 +0100)
committerJean-Baptiste Kempf <jb@videolan.org>
Sat, 15 Nov 2014 11:25:21 +0000 (12:25 +0100)
 - used for direct and non direct rendering (replace opaque.c)

 - use nativewindowpriv: more control than the public api, since you can set
   orientation, crop, cancel a buffer without displaying it and allocate more
   than one buffers.

 - fallback to nativewindow if nativewindowpriv fails (with only one buffer in
   the pool then).

 - Only one way to display subtitles: use a seperate android surface.

 - Fix subtiles display in case or source aspect != 1.

Signed-off-by: Jean-Baptiste Kempf <jb@videolan.org>
configure.ac
modules/MODULES_LIST
modules/codec/omxil/android_mediacodec.c
modules/codec/omxil/android_opaque.h
modules/codec/omxil/omxil.c
modules/video_output/Makefile.am
modules/video_output/android/android_window.c [new file with mode: 0644]
modules/video_output/android/android_window.h [new file with mode: 0644]
modules/video_output/android/opaque.c [deleted file]

index 985c891..7babe95 100644 (file)
@@ -3364,7 +3364,7 @@ AC_ARG_ENABLE(android-surface,
   [  --enable-android-surface   Android Surface video output module (default disabled)])
 if test "${enable_android_surface}" = "yes"; then
      VLC_ADD_PLUGIN([android_surface])
-     VLC_ADD_PLUGIN([android_opaque])
+     VLC_ADD_PLUGIN([android_window])
 fi
 
 dnl
index 2cd3f61..6d93386 100644 (file)
@@ -33,8 +33,8 @@ $Id$
  * anaglyph: anaglyph 3d video filter
  * android_audiotrack: audio output for Android, based on AudioTrack
  * android_native_window: Android native window provider module
- * android_opaque: Android direct GPU rendering video output
  * android_surface: video output for Android, based on Surface
+ * android_window: Android direct/undirect rendering video output
  * antiflicker: anti-flicker video filter
  * araw: Pseudo audio decoder for raw PCM
  * aribcam: ARIB STD-B25 decoder/virtual CAM
index 1eeee69..d248c08 100644 (file)
@@ -42,6 +42,7 @@
 #include <OMX_Component.h>
 #include "omxil_utils.h"
 #include "android_opaque.h"
+#include "../../video_output/android/android_window.h"
 
 #define INFO_OUTPUT_BUFFERS_CHANGED -3
 #define INFO_OUTPUT_FORMAT_CHANGED  -2
@@ -617,23 +618,25 @@ static void CloseDecoder(vlc_object_t *p_this)
 /*****************************************************************************
  * vout callbacks
  *****************************************************************************/
-static void DisplayBuffer(picture_sys_t* p_picsys, bool b_render)
+static void UnlockPicture(picture_t* p_pic)
 {
-    decoder_t *p_dec = p_picsys->p_dec;
+    picture_sys_t *p_picsys = p_pic->p_sys;
+    decoder_t *p_dec = p_picsys->priv.hw.p_dec;
     decoder_sys_t *p_sys = p_dec->p_sys;
 
-    if (!p_picsys->b_valid)
+    if (!p_picsys->priv.hw.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) {
+    if (!p_picsys->priv.hw.b_valid) {
         vlc_mutex_unlock(get_android_opaque_mutex());
         return;
     }
 
-    uint32_t i_index = p_picsys->i_index;
+    uint32_t i_index = p_picsys->priv.hw.i_index;
+    bool b_render = p_picsys->b_render;
     p_sys->inflight_picture[i_index] = NULL;
 
     /* Release the MediaCodec buffer. */
@@ -646,21 +649,11 @@ static void DisplayBuffer(picture_sys_t* p_picsys, bool b_render)
     }
 
     jni_detach_thread();
-    p_picsys->b_valid = false;
+    p_picsys->priv.hw.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;
@@ -669,7 +662,7 @@ static void InvalidateAllPictures(decoder_t *p_dec)
     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_pic->p_sys->priv.hw.b_valid = false;
             p_sys->inflight_picture[i] = NULL;
         }
     }
@@ -707,7 +700,7 @@ static void GetOutput(decoder_t *p_dec, JNIEnv *env, picture_t **pp_pic, jlong t
             } 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;
+                int i_prev_index = p_picsys->priv.hw.i_index;
                 (*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, i_prev_index, false);
                 if ((*env)->ExceptionOccurred(env)) {
                     msg_Err(p_dec, "Exception in MediaCodec.releaseOutputBuffer " \
@@ -738,11 +731,11 @@ static void GetOutput(decoder_t *p_dec, JNIEnv *env, picture_t **pp_pic, jlong t
 
                 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_picsys->pf_lock_pic = NULL;
+                    p_picsys->pf_unlock_pic = UnlockPicture;
+                    p_picsys->priv.hw.p_dec = p_dec;
+                    p_picsys->priv.hw.i_index = index;
+                    p_picsys->priv.hw.b_valid = true;
 
                     p_sys->inflight_picture[index] = p_pic;
                 } else {
@@ -974,11 +967,11 @@ static picture_t *DecodeVideo(decoder_t *p_dec, block_t **pp_block)
                 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;
+                    p_picsys->pf_lock_pic = NULL;
+                    p_picsys->pf_unlock_pic = NULL;
+                    p_picsys->priv.hw.p_dec = NULL;
+                    p_picsys->priv.hw.i_index = -1;
+                    p_picsys->priv.hw.b_valid = false;
                 }
                 else {
                     /* If we cannot return a picture we must free the
index 30b145f..5d8b1e6 100644 (file)
 
 #include <vlc_common.h>
 
-struct picture_sys_t
-{
-    void (*pf_display_callback)(picture_sys_t*);
-    void (*pf_unlock_callback)(picture_sys_t*);
-    decoder_t *p_dec;
-    uint32_t i_index;
-    int b_valid;
-};
-
 vlc_mutex_t* get_android_opaque_mutex(void);
 
 #endif
index 19bf3f7..95e5d0a 100644 (file)
@@ -46,6 +46,7 @@
 #include <dlfcn.h>
 #include <jni.h>
 #include "android_opaque.h"
+#include "../../video_output/android/android_window.h"
 #endif
 
 #ifndef NDEBUG
@@ -96,8 +97,7 @@ static OMX_ERRORTYPE OmxFillBufferDone( OMX_HANDLETYPE, OMX_PTR,
 
 #if defined(USE_IOMX)
 static void *DequeueThread( void *data );
-static void DisplayCallback( picture_sys_t* p_picsys );
-static void UnlockCallback( picture_sys_t* p_picsys );
+static void UnlockPicture( picture_t* p_pic );
 static void HwBuffer_Init( decoder_t *p_dec, OmxPort *p_port );
 static void HwBuffer_Destroy( decoder_t *p_dec, OmxPort *p_port );
 static int  HwBuffer_AllocateBuffers( decoder_t *p_dec, OmxPort *p_port );
@@ -1635,11 +1635,11 @@ static picture_t *DecodeVideo( decoder_t *p_dec, block_t **pp_block )
             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;
+                p_picsys->pf_lock_pic = NULL;
+                p_picsys->pf_unlock_pic = NULL;
+                p_picsys->priv.hw.p_dec = NULL;
+                p_picsys->priv.hw.i_index = -1;
+                p_picsys->priv.hw.b_valid = false;
             } else {
                 /* If we cannot return a picture we must free the
                    block since the decoder will proceed with the
@@ -2418,14 +2418,14 @@ static int HwBuffer_Stop( decoder_t *p_dec, OmxPort *p_port )
             if( p_pic ) {
                 picture_sys_t *p_picsys = p_pic->p_sys;
                 if( p_picsys ) {
-                    void *p_handle = p_port->pp_buffers[p_picsys->i_index]->pBuffer;
+                    void *p_handle = p_port->pp_buffers[p_picsys->priv.hw.i_index]->pBuffer;
                     if( p_handle )
                     {
                         p_port->p_hwbuf->anwpriv.cancel( p_port->p_hwbuf->window_priv, p_handle );
-                        HwBuffer_ChangeState( p_dec, p_port, p_picsys->i_index,
+                        HwBuffer_ChangeState( p_dec, p_port, p_picsys->priv.hw.i_index,
                                               BUF_STATE_NOT_OWNED );
                     }
-                    p_picsys->b_valid = false;
+                    p_picsys->priv.hw.b_valid = false;
                 }
                 p_port->p_hwbuf->inflight_picture[i] = NULL;
             }
@@ -2493,11 +2493,11 @@ static int HwBuffer_GetPic( decoder_t *p_dec, OmxPort *p_port,
     p_pic->date = FromOmxTicks( p_header->nTimeStamp );
 
     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 = i_index;
-    p_picsys->b_valid = true;
+    p_picsys->pf_lock_pic = NULL;
+    p_picsys->pf_unlock_pic = UnlockPicture;
+    p_picsys->priv.hw.p_dec = p_dec;
+    p_picsys->priv.hw.i_index = i_index;
+    p_picsys->priv.hw.b_valid = true;
 
     HWBUFFER_LOCK();
     p_port->p_hwbuf->inflight_picture[i_index] = p_pic;
@@ -2600,27 +2600,28 @@ static void *DequeueThread( void *data )
 /*****************************************************************************
  * vout callbacks
  *****************************************************************************/
-static void DisplayBuffer( picture_sys_t* p_picsys, bool b_render )
+static void UnlockPicture( picture_t* p_pic )
 {
-    decoder_t *p_dec = p_picsys->p_dec;
+    picture_sys_t *p_picsys = p_pic->p_sys;
+    decoder_t *p_dec = p_picsys->priv.hw.p_dec;
     decoder_sys_t *p_sys = p_dec->p_sys;
     OmxPort *p_port = &p_sys->out;
     void *p_handle;
 
-    if( !p_picsys->b_valid ) return;
+    if( !p_picsys->priv.hw.b_valid ) return;
 
     HWBUFFER_LOCK();
 
     /* Picture might have been invalidated while waiting on the mutex. */
-    if (!p_picsys->b_valid) {
+    if (!p_picsys->priv.hw.b_valid) {
         HWBUFFER_UNLOCK();
         return;
     }
 
-    p_handle = p_port->pp_buffers[p_picsys->i_index]->pBuffer;
+    p_handle = p_port->pp_buffers[p_picsys->priv.hw.i_index]->pBuffer;
 
     OMX_DBG( "DisplayBuffer: %s %p",
-             b_render ? "render" : "cancel", p_handle );
+             p_picsys->b_render ? "render" : "cancel", p_handle );
 
     if( !p_handle )
     {
@@ -2628,31 +2629,21 @@ static void DisplayBuffer( picture_sys_t* p_picsys, bool b_render )
         goto end;
     }
 
-    if( b_render )
+    if( p_picsys->b_render )
         p_port->p_hwbuf->anwpriv.queue( p_port->p_hwbuf->window_priv, p_handle );
     else
         p_port->p_hwbuf->anwpriv.cancel( p_port->p_hwbuf->window_priv, p_handle );
 
-    HwBuffer_ChangeState( p_dec, p_port, p_picsys->i_index, BUF_STATE_NOT_OWNED );
+    HwBuffer_ChangeState( p_dec, p_port, p_picsys->priv.hw.i_index, BUF_STATE_NOT_OWNED );
     HWBUFFER_BROADCAST( p_port );
 
-    p_port->p_hwbuf->inflight_picture[p_picsys->i_index] = NULL;
+    p_port->p_hwbuf->inflight_picture[p_picsys->priv.hw.i_index] = NULL;
 
 end:
-    p_picsys->b_valid = false;
-    p_picsys->i_index = -1;
+    p_picsys->priv.hw.b_valid = false;
+    p_picsys->priv.hw.i_index = -1;
 
     HWBUFFER_UNLOCK();
 }
 
-static void UnlockCallback( picture_sys_t* p_picsys )
-{
-    DisplayBuffer( p_picsys, false );
-}
-
-static void DisplayCallback( picture_sys_t* p_picsys )
-{
-    DisplayBuffer( p_picsys, true );
-}
-
 #endif // USE_IOMX
index dccc3fa..b8586db 100644 (file)
@@ -250,11 +250,11 @@ libandroid_native_window_plugin_la_SOURCES = video_output/android/nativewindow.c
 libandroid_native_window_plugin_la_CFLAGS = $(AM_CFLAGS)
 libandroid_native_window_plugin_la_LIBADD = $(LIBDL)
 
-libandroid_opaque_plugin_la_SOURCES = video_output/android/opaque.c video_output/android/utils.c video_output/android/utils.h
-libandroid_opaque_plugin_la_CFLAGS = $(AM_CFLAGS)
-libandroid_opaque_plugin_la_LIBADD = $(LIBDL)
-libandroid_opaque_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)'
-EXTRA_LTLIBRARIES += libandroid_opaque_plugin.la
+libandroid_window_plugin_la_SOURCES = video_output/android/android_window.c video_output/android/android_window.h video_output/android/utils.c video_output/android/utils.h
+libandroid_window_plugin_la_CFLAGS = $(AM_CFLAGS)
+libandroid_window_plugin_la_LIBADD = $(LIBDL)
+libandroid_window_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)'
+EXTRA_LTLIBRARIES += libandroid_window_plugin.la
 
 libandroid_surface_plugin_la_SOURCES = video_output/android/surface.c video_output/android/utils.c video_output/android/utils.h
 libandroid_surface_plugin_la_CFLAGS = $(AM_CFLAGS)
@@ -264,7 +264,7 @@ EXTRA_LTLIBRARIES += libandroid_surface_plugin.la
 
 if HAVE_ANDROID
 vout_LTLIBRARIES += libandroid_native_window_plugin.la
-vout_LTLIBRARIES += $(LTLIBandroid_opaque)
+vout_LTLIBRARIES += $(LTLIBandroid_window)
 vout_LTLIBRARIES += $(LTLIBandroid_surface)
 if HAVE_EGL
 vout_LTLIBRARIES += libegl_android_plugin.la
diff --git a/modules/video_output/android/android_window.c b/modules/video_output/android/android_window.c
new file mode 100644 (file)
index 0000000..8dc8c18
--- /dev/null
@@ -0,0 +1,925 @@
+/*****************************************************************************
+ * android_window.c: Android video output module
+ *****************************************************************************
+ * Copyright (C) 2014 VLC authors and VideoLAN
+ *
+ * Authors: Thomas Guillem <thomas@gllm.fr>
+ *          Felix Abecassis <felix.abecassis@gmail.com>
+ *          Ming Hu <tewilove@gmail.com>
+ *          Ludovic Fauvet <etix@l0cal.com>
+ *          S├ębastien Toque <xilasz@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 <vlc_filter.h>
+#include <vlc_md5.h>
+
+#include <dlfcn.h>
+
+#include "android_window.h"
+#include "utils.h"
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+#define PRIV_WINDOW_MAX_BUFFER_COUNT 32
+#define PRIV_WINDOW_MIN_BUFFER_COUNT 2
+
+#define USE_ANWP
+#define CHROMA_TEXT N_("Chroma used")
+#define CHROMA_LONGTEXT N_(\
+    "Force use of a specific chroma for output. Default is RGB32.")
+
+#define CFG_PREFIX "androidsurface-"
+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("android_window")
+    set_description(N_("Android video output"))
+    set_capability("vout display", 200)
+    add_shortcut("androidwindow", "android")
+    set_callbacks(Open, Close)
+vlc_module_end()
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+
+#define THREAD_NAME "android_window"
+extern int jni_attach_thread(JNIEnv **env, const char *thread_name);
+extern void jni_detach_thread();
+
+extern jobject jni_LockAndGetAndroidJavaSurface();
+extern jobject jni_LockAndGetSubtitlesSurface();
+extern void  jni_UnlockAndroidSurface();
+
+extern void  jni_SetSurfaceLayout(int width, int height, int visible_width, int visible_height, int sar_num, int sar_den);
+extern int jni_ConfigureSurface(jobject jsurf, int width, int height, int hal, bool *configured);
+
+static const vlc_fourcc_t subpicture_chromas[] =
+{
+    VLC_CODEC_RGBA,
+    0
+};
+
+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);
+
+typedef struct android_window android_window;
+struct android_window
+{
+    video_format_t fmt;
+    int i_android_hal;
+    unsigned int i_angle;
+    unsigned int i_pic_count;
+    unsigned int i_min_undequeued;
+    bool b_use_priv;
+
+    jobject jsurf;
+    ANativeWindow *p_handle;
+    native_window_priv *p_handle_priv;
+};
+
+struct vout_display_sys_t
+{
+    picture_pool_t *pool;
+
+    void *p_library;
+    native_window_api_t anw;
+    native_window_priv_api_t anwp;
+    bool b_has_anwp;
+
+    android_window *p_window;
+    android_window *p_sub_window;
+
+    filter_t *p_spu_blend;
+    picture_t *p_sub_pic;
+
+    bool b_has_subpictures;
+
+    uint8_t hash[16];
+};
+
+static int UpdateWindowSize(video_format_t *p_fmt, bool b_cropped)
+{
+    unsigned int i_width, i_height;
+    unsigned int i_sar_num = 1, i_sar_den = 1;
+    video_format_t rot_fmt;
+
+    video_format_ApplyRotation(&rot_fmt, p_fmt);
+
+    if (rot_fmt.i_sar_num != 0 && rot_fmt.i_sar_den != 0) {
+        i_sar_num = rot_fmt.i_sar_num;
+        i_sar_den = rot_fmt.i_sar_den;
+    }
+    if (b_cropped) {
+        i_width = rot_fmt.i_visible_width;
+        i_height = rot_fmt.i_visible_height;
+    } else {
+        i_width = rot_fmt.i_width;
+        i_height = rot_fmt.i_height;
+    }
+
+    jni_SetSurfaceLayout(i_width, i_height,
+                         rot_fmt.i_visible_width,
+                         rot_fmt.i_visible_height,
+                         i_sar_num,
+                         i_sar_den);
+    return 0;
+}
+
+static picture_t *PictureAlloc(vout_display_sys_t *sys, video_format_t *fmt)
+{
+    picture_t *p_pic;
+    picture_resource_t rsc;
+    picture_sys_t *p_picsys = calloc(1, sizeof(*p_picsys));
+
+    if (unlikely(p_picsys == NULL))
+        return NULL;
+
+    p_picsys->p_vd_sys = sys;
+
+    memset(&rsc, 0, sizeof(picture_resource_t));
+    rsc.p_sys = p_picsys,
+
+    p_pic = picture_NewFromResource(fmt, &rsc);
+    if (!p_pic)
+    {
+        free(p_picsys);
+        return NULL;
+    }
+    return p_pic;
+}
+
+static void FixSubtitleFormat(vout_display_sys_t *sys)
+{
+    video_format_t *p_fmt = &sys->p_sub_window->fmt;
+
+    if (p_fmt->i_visible_width == 0 || p_fmt->i_visible_height == 0) {
+        p_fmt->i_visible_width = p_fmt->i_width;
+        p_fmt->i_visible_height = p_fmt->i_height;
+    }
+    if (p_fmt->i_sar_num > 0 && p_fmt->i_sar_den > 0) {
+        if (p_fmt->i_sar_num >= p_fmt->i_sar_den)
+            p_fmt->i_width = (int64_t)p_fmt->i_visible_width * p_fmt->i_sar_num / p_fmt->i_sar_den;
+        else
+            p_fmt->i_height = (int64_t)p_fmt->i_visible_height * p_fmt->i_sar_den / p_fmt->i_sar_num;
+        p_fmt->i_sar_num = 1;
+        p_fmt->i_sar_den = 1;
+    } else {
+        p_fmt->i_width = p_fmt->i_visible_width;
+        p_fmt->i_height = p_fmt->i_visible_height;
+    }
+    p_fmt->i_x_offset = 0;
+    p_fmt->i_y_offset = 0;
+}
+
+#define ALIGN_16_PIXELS( x ) ( ( ( x ) + 15 ) / 16 * 16 )
+static void SetupPictureYV12(picture_t *p_picture, uint32_t i_in_stride)
+{
+    /* according to document of android.graphics.ImageFormat.YV12 */
+    int i_stride = ALIGN_16_PIXELS(i_in_stride);
+    int i_c_stride = ALIGN_16_PIXELS(i_stride / 2);
+
+    p_picture->p->i_pitch = i_stride;
+
+    /* Fill chroma planes for planar YUV */
+    for (int n = 1; n < p_picture->i_planes; n++)
+    {
+        const plane_t *o = &p_picture->p[n-1];
+        plane_t *p = &p_picture->p[n];
+
+        p->p_pixels = o->p_pixels + o->i_lines * o->i_pitch;
+        p->i_pitch  = i_c_stride;
+        p->i_lines  = p_picture->format.i_height / 2;
+        /*
+          Explicitly set the padding lines of the picture to black (127 for YUV)
+          since they might be used by Android during rescaling.
+        */
+        int visible_lines = p_picture->format.i_visible_height / 2;
+        if (visible_lines < p->i_lines)
+            memset(&p->p_pixels[visible_lines * p->i_pitch], 127, (p->i_lines - visible_lines) * p->i_pitch);
+    }
+
+    if (vlc_fourcc_AreUVPlanesSwapped(p_picture->format.i_chroma,
+                                      VLC_CODEC_YV12)) {
+        uint8_t *p_tmp = p_picture->p[1].p_pixels;
+        p_picture->p[1].p_pixels = p_picture->p[2].p_pixels;
+        p_picture->p[2].p_pixels = p_tmp;
+    }
+}
+
+static android_window *AndroidWindow_New(vout_display_sys_t *sys,
+                                         video_format_t *p_fmt,
+                                         bool b_use_priv)
+{
+    android_window *p_window = calloc(1, sizeof(android_window));
+
+    if (!p_window)
+        return NULL;
+
+    if (p_fmt->i_chroma != VLC_CODEC_ANDROID_OPAQUE) {
+        p_window->b_use_priv = sys->b_has_anwp && b_use_priv;
+
+        p_window->i_android_hal = ChromaToAndroidHal(p_fmt->i_chroma);
+        if (p_window->i_android_hal == -1) {
+            free(p_window);
+            return NULL;
+        }
+    }
+
+    if (p_window->b_use_priv) {
+        switch (p_fmt->orientation)
+        {
+            case ORIENT_ROTATED_90:
+                p_window->i_angle = 90;
+                break;
+            case ORIENT_ROTATED_180:
+                p_window->i_angle = 180;
+                break;
+            case ORIENT_ROTATED_270:
+                p_window->i_angle = 270;
+                break;
+            default:
+                p_window->i_angle = 0;
+        }
+        p_window->fmt = *p_fmt;
+    } else {
+        video_format_ApplyRotation(&p_window->fmt, p_fmt);
+    }
+    p_window->i_pic_count = 1;
+    return p_window;
+}
+
+static void AndroidWindow_Destroy(vout_display_sys_t *sys,
+                                  android_window *p_window)
+{
+    if (p_window->p_handle_priv)
+        sys->anwp.disconnect(p_window->p_handle_priv);
+    if (p_window->p_handle)
+        sys->anw.winRelease(p_window->p_handle);
+    free(p_window);
+}
+
+static int AndroidWindow_UpdateCrop(vout_display_sys_t *sys,
+                                    android_window *p_window)
+{
+    if (!p_window->p_handle_priv)
+        return -1;
+
+    return sys->anwp.setCrop(p_window->p_handle_priv,
+                             p_window->fmt.i_x_offset,
+                             p_window->fmt.i_y_offset,
+                             p_window->fmt.i_visible_width,
+                             p_window->fmt.i_visible_height);
+}
+
+static unsigned int AndroidWindow_GetPicCount(vout_display_sys_t *sys,
+                                              android_window *p_window)
+{
+    VLC_UNUSED(sys);
+    return p_window->i_min_undequeued + p_window->i_pic_count;
+}
+
+static int AndroidWindow_SetSurface(vout_display_sys_t *sys,
+                                    android_window *p_window,
+                                    jobject jsurf)
+{
+    if (p_window->p_handle && jsurf != p_window->jsurf) {
+        if (p_window->p_handle_priv) {
+            sys->anwp.disconnect(p_window->p_handle_priv);
+            p_window->p_handle_priv = NULL;
+        }
+        sys->anw.winRelease(p_window->p_handle);
+        p_window->p_handle = NULL;
+    }
+
+    p_window->jsurf = jsurf;
+    if (!p_window->p_handle) {
+        JNIEnv *p_env;
+
+        jni_attach_thread(&p_env, THREAD_NAME);
+        if (!p_env)
+            return -1;
+        p_window->p_handle = sys->anw.winFromSurface(p_env, p_window->jsurf);
+        jni_detach_thread();
+        if (!p_window->p_handle)
+            return -1;
+    }
+
+    return 0;
+}
+
+static int AndroidWindow_SetupANWP(vout_display_sys_t *sys,
+                                   android_window *p_window)
+{
+    if (!p_window->p_handle_priv)
+        p_window->p_handle_priv = sys->anwp.connect(p_window->p_handle);
+
+    if (!p_window->p_handle_priv)
+        goto error;
+
+    if (sys->anwp.setup(p_window->p_handle_priv,
+                        p_window->fmt.i_width, p_window->fmt.i_height,
+                        p_window->i_android_hal,
+                        false, 0) != 0)
+        goto error;
+
+    sys->anwp.getMinUndequeued(p_window->p_handle_priv,
+                               &p_window->i_min_undequeued);
+
+    if ((p_window->i_min_undequeued + p_window->i_pic_count) >
+         PRIV_WINDOW_MAX_BUFFER_COUNT)
+        p_window->i_pic_count = PRIV_WINDOW_MAX_BUFFER_COUNT
+                                - p_window->i_min_undequeued;
+
+    if (sys->anwp.setBufferCount(p_window->p_handle_priv,
+                                 AndroidWindow_GetPicCount(sys, p_window)) != 0)
+        goto error;
+
+    if (sys->anwp.setOrientation(p_window->p_handle_priv,
+                                 p_window->i_angle) != 0)
+        goto error;
+
+    AndroidWindow_UpdateCrop(sys, p_window);
+
+    return 0;
+error:
+    if (p_window->p_handle_priv) {
+        sys->anwp.disconnect(p_window->p_handle_priv);
+        p_window->p_handle_priv = NULL;
+    }
+    p_window->b_use_priv = false;
+    if (p_window->i_angle != 0)
+        video_format_ApplyRotation(&p_window->fmt, &p_window->fmt);
+    p_window->i_angle = 0;
+    return -1;
+}
+
+static int AndroidWindow_SetupANW(vout_display_sys_t *sys,
+                                  android_window *p_window)
+{
+    int err;
+    bool configured;
+
+    p_window->i_pic_count = 1;
+    p_window->i_min_undequeued = 0;
+
+    /*
+     * anw.setBuffersGeometry is broken in gingerbread.
+     * use jni_ConfigureSurface to configure the surface on the java side
+     * synchronsouly.
+     * jni_ConfigureSurface return -1 when you don't need to call it (ie, after
+     * gingerbread).
+     * if jni_ConfigureSurface succeed, you need to get a new surface handle.
+     * That's why AndroidWindow_SetSurface is called again here.
+     */
+    err = jni_ConfigureSurface(p_window->jsurf,
+                               p_window->fmt.i_width,
+                               p_window->fmt.i_height,
+                               p_window->i_android_hal,
+                               &configured);
+    if (err == 0) {
+        if (configured) {
+            jobject jsurf = p_window->jsurf;
+            p_window->jsurf = NULL;
+            if (AndroidWindow_SetSurface(sys, p_window, jsurf) != 0)
+                return -1;
+        } else
+            return -1;
+    } else {
+        err = sys->anw.setBuffersGeometry(p_window->p_handle,
+                                          p_window->fmt.i_width,
+                                          p_window->fmt.i_height,
+                                          p_window->i_android_hal);
+    }
+    return err;
+}
+
+static int AndroidWindow_Setup(vout_display_sys_t *sys,
+                                    android_window *p_window,
+                                    unsigned int i_pic_count)
+{
+    int align_pixels;
+    picture_t *p_pic;
+
+    if (i_pic_count != 0)
+        p_window->i_pic_count = i_pic_count;
+
+    p_pic = PictureAlloc(sys, &p_window->fmt);
+    // For RGB (32 or 16) we need to align on 8 or 4 pixels, 16 pixels for YUV
+    align_pixels = (16 / p_pic->p[0].i_pixel_pitch) - 1;
+    p_window->fmt.i_height = p_pic->format.i_height;
+    p_window->fmt.i_width = (p_pic->format.i_width + align_pixels) & ~align_pixels;
+    picture_Release(p_pic);
+
+    if (!p_window->b_use_priv
+        || AndroidWindow_SetupANWP(sys, p_window) != 0) {
+        if (AndroidWindow_SetupANW(sys, p_window) != 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+static void AndroidWindow_UnlockPicture(vout_display_sys_t *sys,
+                                        android_window *p_window,
+                                        picture_t *p_pic)
+{
+    picture_sys_t *p_picsys = p_pic->p_sys;
+
+    if (p_window->b_use_priv) {
+        int err = 0;
+        void *p_handle = p_picsys->priv.sw.p_handle;
+
+        if (p_handle == NULL)
+            return;
+
+        err = sys->anwp.unlockData(p_window->p_handle_priv, p_handle);
+
+        if (err == 0) {
+            if (p_picsys->b_render)
+                err = sys->anwp.queue(p_window->p_handle_priv, p_handle);
+            else
+                err = sys->anwp.cancel(p_window->p_handle_priv, p_handle);
+        }
+    } else
+        sys->anw.unlockAndPost(p_window->p_handle);
+}
+
+static int AndroidWindow_LockPicture(vout_display_sys_t *sys,
+                                     android_window *p_window,
+                                     picture_t *p_pic)
+{
+    picture_sys_t *p_picsys = p_pic->p_sys;
+
+    if (p_window->b_use_priv) {
+        void *p_handle;
+        int err;
+
+        err = sys->anwp.dequeue(p_window->p_handle_priv, &p_handle);
+        err = err == 0 ? sys->anwp.lock(p_window->p_handle_priv, p_handle) : err;
+        err = err == 0 ? sys->anwp.lockData(p_window->p_handle_priv,
+                                            p_handle,
+                                            &p_picsys->priv.sw.buf) : err;
+        if (err != 0)
+            return -1;
+        p_picsys->priv.sw.p_handle = p_handle;
+    } else {
+        if (sys->anw.winLock(p_window->p_handle,
+                             &p_picsys->priv.sw.buf, NULL) != 0)
+            return -1;
+    }
+    if (p_picsys->priv.sw.buf.width < 0 ||
+        p_picsys->priv.sw.buf.height < 0 ||
+        (unsigned)p_picsys->priv.sw.buf.width < p_window->fmt.i_width ||
+        (unsigned)p_picsys->priv.sw.buf.height < p_window->fmt.i_height) {
+        AndroidWindow_UnlockPicture(sys, p_window, p_pic);
+        return -1;
+    }
+
+    p_pic->p[0].p_pixels = p_picsys->priv.sw.buf.bits;
+    p_pic->p[0].i_lines = p_picsys->priv.sw.buf.height;
+    p_pic->p[0].i_pitch = p_pic->p[0].i_pixel_pitch * p_picsys->priv.sw.buf.stride;
+
+    if (p_picsys->priv.sw.buf.format == PRIV_WINDOW_FORMAT_YV12)
+        SetupPictureYV12(p_pic, p_picsys->priv.sw.buf.stride);
+
+    return 0;
+}
+
+static int SetupWindowSurface(vout_display_sys_t *sys, unsigned i_pic_count)
+{
+    int err;
+    jobject jsurf = jni_LockAndGetAndroidJavaSurface();
+    err = AndroidWindow_SetSurface(sys, sys->p_window, jsurf);
+    jni_UnlockAndroidSurface();
+    err = err == 0 ? AndroidWindow_Setup(sys, sys->p_window, i_pic_count) : err;
+    return err;
+}
+
+static int SetupWindowSubtitleSurface(vout_display_sys_t *sys)
+{
+    int err;
+    jobject jsurf = jni_LockAndGetSubtitlesSurface();
+    err = AndroidWindow_SetSurface(sys, sys->p_sub_window, jsurf);
+    jni_UnlockAndroidSurface();
+    err = err == 0 ? AndroidWindow_Setup(sys, sys->p_sub_window, 1) : err;
+    return err;
+}
+
+static void SetRGBMask(video_format_t *p_fmt)
+{
+    switch(p_fmt->i_chroma) {
+        case VLC_CODEC_RGB16:
+            p_fmt->i_bmask = 0x0000001f;
+            p_fmt->i_gmask = 0x000007e0;
+            p_fmt->i_rmask = 0x0000f800;
+            break;
+
+        case VLC_CODEC_RGB32:
+        case VLC_CODEC_RGBA:
+            p_fmt->i_rmask = 0x000000ff;
+            p_fmt->i_gmask = 0x0000ff00;
+            p_fmt->i_bmask = 0x00ff0000;
+            break;
+    }
+}
+
+static int Open(vlc_object_t *p_this)
+{
+    vout_display_t *vd = (vout_display_t*)p_this;
+    vout_display_sys_t *sys;
+    video_format_t sub_fmt;
+
+    if (vout_display_IsWindowed(vd))
+        return VLC_EGENERIC;
+
+    /* Allocate structure */
+    vd->sys = sys = (struct vout_display_sys_t*)calloc(1, sizeof(*sys));
+    if (!sys)
+        return VLC_ENOMEM;
+
+    sys->p_library = LoadNativeWindowAPI(&sys->anw);
+    if (!sys->p_library) {
+        msg_Err(vd, "Could not initialize NativeWindow API.");
+        goto error;
+    }
+
+#ifdef USE_ANWP
+    if (LoadNativeWindowPrivAPI(&sys->anwp) == 0)
+        sys->b_has_anwp = true;
+    else
+        msg_Warn(vd, "Could not initialize NativeWindow Priv API.");
+#endif
+
+    if (vd->fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) {
+        /* Setup chroma */
+        char *psz_fcc = var_InheritString(vd, CFG_PREFIX "chroma");
+        if (psz_fcc) {
+            vd->fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, psz_fcc);
+            free(psz_fcc);
+        } else
+            vd->fmt.i_chroma = VLC_CODEC_RGB32;
+
+        switch(vd->fmt.i_chroma) {
+            case VLC_CODEC_YV12:
+                /* avoid swscale usage by asking for I420 instead since the
+                 * vout already has code to swap the buffers */
+                vd->fmt.i_chroma = VLC_CODEC_I420;
+            case VLC_CODEC_I420:
+                break;
+            case VLC_CODEC_RGB16:
+            case VLC_CODEC_RGB32:
+            case VLC_CODEC_RGBA:
+                SetRGBMask(&vd->fmt);
+                video_format_FixRgb(&vd->fmt);
+                break;
+            default:
+                goto error;
+        }
+
+        sys->p_window = AndroidWindow_New(sys, &vd->fmt, true);
+        if (!sys->p_window)
+            goto error;
+
+        if (SetupWindowSurface(sys, 0) != 0)
+            goto error;
+
+        /* use software rotation if we don't use private anw */
+        if (!sys->p_window->b_use_priv)
+            video_format_ApplyRotation(&vd->fmt, &vd->fmt);
+
+        msg_Dbg(vd, "using %s", sys->p_window->b_use_priv ? "ANWP" : "ANW");
+    } else {
+        /* vd->fmt.i_chroma == VLC_CODEC_ANDROID_OPAQUE */
+        sys->p_window = AndroidWindow_New(sys, &vd->fmt, false);
+        if (!sys->p_window)
+            goto error;
+        msg_Dbg(vd, "using opaque");
+    }
+
+    video_format_ApplyRotation(&sub_fmt, &vd->fmt);
+    sub_fmt.i_chroma = subpicture_chromas[0];
+    SetRGBMask(&sub_fmt);
+    video_format_FixRgb(&sub_fmt);
+    sys->p_sub_window = AndroidWindow_New(sys, &sub_fmt, false);
+    if (!sys->p_sub_window)
+        goto error;
+    FixSubtitleFormat(sys);
+
+    /* Export the subpicture capability of this vout. */
+    vd->info.subpicture_chromas = subpicture_chromas;
+
+    /* Setup vout_display */
+    vd->pool    = Pool;
+    vd->prepare = NULL;
+    vd->display = Display;
+    vd->control = Control;
+    vd->manage  = Manage;
+
+    /* Fix initial state */
+    vout_display_SendEventFullscreen(vd, false);
+
+    return VLC_SUCCESS;
+
+error:
+    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;
+
+    if (!sys)
+        return;
+
+    if (sys->pool)
+        picture_pool_Release(sys->pool);
+    if (sys->p_window)
+        AndroidWindow_Destroy(sys, sys->p_window);
+
+    if (sys->p_sub_pic)
+        picture_Release(sys->p_sub_pic);
+    if (sys->p_spu_blend)
+        filter_DeleteBlend(sys->p_spu_blend);
+    if (sys->p_sub_window)
+        AndroidWindow_Destroy(sys, sys->p_sub_window);
+
+    if (sys->p_library)
+        dlclose(sys->p_library);
+
+    free(sys);
+}
+
+static int DefaultLockPicture(picture_t *p_pic)
+{
+    picture_sys_t *p_picsys = p_pic->p_sys;
+    vout_display_sys_t *sys = p_picsys->p_vd_sys;
+
+    return AndroidWindow_LockPicture(sys, sys->p_window, p_pic);
+}
+
+static void DefaultUnlockPicture(picture_t *p_pic)
+{
+    picture_sys_t *p_picsys = p_pic->p_sys;
+    vout_display_sys_t *sys = p_picsys->p_vd_sys;
+
+    AndroidWindow_UnlockPicture(sys, sys->p_window, p_pic);
+}
+
+static int LockPicture(picture_t *p_pic)
+{
+    picture_sys_t *p_picsys = p_pic->p_sys;
+
+    p_picsys->b_render = false;
+    if (p_picsys->pf_lock_pic)
+        return p_picsys->pf_lock_pic(p_pic);
+    return 0;
+}
+
+static void UnlockPicture(picture_t *p_pic)
+{
+    picture_sys_t *p_picsys = p_pic->p_sys;
+
+    if (p_picsys->pf_unlock_pic)
+        p_picsys->pf_unlock_pic(p_pic);
+}
+
+static picture_pool_t *PoolAlloc(vout_display_t *vd, unsigned requested_count)
+{
+    vout_display_sys_t *sys = vd->sys;
+    picture_pool_t *pool = NULL;
+    picture_t **pp_pics = NULL;
+    unsigned int i = 0;
+
+    if (vd->fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) {
+        if (SetupWindowSurface(sys, requested_count) != 0)
+            goto error;
+
+        requested_count = AndroidWindow_GetPicCount(sys, sys->p_window);
+        msg_Dbg(vd, "PoolAlloc: request %d frames", requested_count);
+    } else {
+        requested_count = 31; // TODO:
+    }
+    UpdateWindowSize(&sys->p_window->fmt, sys->p_window->b_use_priv);
+
+    pp_pics = calloc(requested_count, sizeof(picture_t));
+
+    for (i = 0; i < requested_count; i++)
+    {
+        picture_t *p_pic = PictureAlloc(sys, &sys->p_window->fmt);
+        if (!p_pic)
+            goto error;
+        if (vd->fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) {
+            p_pic->p_sys->pf_lock_pic = DefaultLockPicture;
+            p_pic->p_sys->pf_unlock_pic = DefaultUnlockPicture;
+        }
+
+        pp_pics[i] = p_pic;
+    }
+
+    picture_pool_configuration_t pool_cfg;
+    memset(&pool_cfg, 0, sizeof(pool_cfg));
+    pool_cfg.picture_count = requested_count;
+    pool_cfg.picture       = pp_pics;
+    pool_cfg.lock          = LockPicture;
+    pool_cfg.unlock        = UnlockPicture;
+    pool = picture_pool_NewExtended(&pool_cfg);
+
+error:
+    if (!pool && pp_pics) {
+        for (unsigned j = 0; j < i; j++)
+            picture_Release(pp_pics[j]);
+    }
+    free(pp_pics);
+    return pool;
+}
+
+static void SubpictureDisplay(vout_display_t *vd, subpicture_t *subpicture)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    struct md5_s hash;
+    InitMD5(&hash);
+    if (subpicture) {
+        for (subpicture_region_t *r = subpicture->p_region; r != NULL; r = r->p_next) {
+            AddMD5(&hash, &r->i_x, sizeof(r->i_x));
+            AddMD5(&hash, &r->i_y, sizeof(r->i_y));
+            AddMD5(&hash, &r->fmt.i_visible_width, sizeof(r->fmt.i_visible_width));
+            AddMD5(&hash, &r->fmt.i_visible_height, sizeof(r->fmt.i_visible_height));
+            AddMD5(&hash, &r->fmt.i_x_offset, sizeof(r->fmt.i_x_offset));
+            AddMD5(&hash, &r->fmt.i_y_offset, sizeof(r->fmt.i_y_offset));
+            const int pixels_offset = r->fmt.i_y_offset * r->p_picture->p->i_pitch +
+                                      r->fmt.i_x_offset * r->p_picture->p->i_pixel_pitch;
+
+            for (unsigned int y = 0; y < r->fmt.i_visible_height; y++)
+                AddMD5(&hash, &r->p_picture->p->p_pixels[pixels_offset + y*r->p_picture->p->i_pitch], r->fmt.i_visible_width);
+        }
+    }
+    EndMD5(&hash);
+    if (!memcmp(hash.buf, sys->hash, 16))
+        return;
+    memcpy(sys->hash, hash.buf, 16);
+
+    if (AndroidWindow_LockPicture(sys, sys->p_sub_window, sys->p_sub_pic) != 0)
+        return;
+
+    /* Clear the subtitles surface. */
+    memset(sys->p_sub_pic->p[0].p_pixels, 0,
+           sys->p_sub_pic->p[0].i_pitch * sys->p_sub_pic->p[0].i_lines);
+
+    if (subpicture)
+    {
+        /* Allocate a blending filter if needed. */
+        if (unlikely(!sys->p_spu_blend))
+            sys->p_spu_blend = filter_NewBlend(VLC_OBJECT(vd),
+                                               &sys->p_sub_pic->format);
+        picture_BlendSubpicture(sys->p_sub_pic, sys->p_spu_blend, subpicture);
+    }
+    AndroidWindow_UnlockPicture(sys, sys->p_sub_window, sys->p_sub_pic);
+}
+
+static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    if (sys->pool == NULL)
+        sys->pool = PoolAlloc(vd, requested_count);
+    return sys->pool;
+}
+
+static void Display(vout_display_t *vd, picture_t *picture,
+                    subpicture_t *subpicture)
+{
+    vout_display_sys_t *sys = vd->sys;
+    picture_sys_t *p_picsys = picture->p_sys;
+
+    /* refcount lowers to 0, and pool_cfg.unlock is called */
+    p_picsys->b_render = true;
+    picture_Release(picture);
+
+    if (subpicture) {
+        if (!sys->p_sub_pic && SetupWindowSubtitleSurface(sys) == 0)
+            sys->p_sub_pic = PictureAlloc(sys, &sys->p_sub_window->fmt);
+
+        if (sys->p_sub_pic)
+            sys->b_has_subpictures = true;
+    }
+    /* As long as no subpicture was received, do not call
+       SubpictureDisplay since JNI calls and clearing the subtitles
+       surface are expensive operations. */
+    if (sys->b_has_subpictures)
+    {
+        SubpictureDisplay(vd, subpicture);
+        if (!subpicture)
+        {
+            /* The surface has been cleared and there is no new
+               subpicture to upload, do not clear again until a new
+               subpicture is received. */
+            sys->b_has_subpictures = false;
+        }
+    }
+
+    if (subpicture)
+        subpicture_Delete(subpicture);
+}
+
+static void CopySourceAspect(video_format_t *p_dest,
+                             const video_format_t *p_src)
+{
+    p_dest->i_sar_num = p_src->i_sar_num;
+    p_dest->i_sar_den = p_src->i_sar_den;
+}
+
+static int Control(vout_display_t *vd, int query, va_list args)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    switch (query) {
+    case VOUT_DISPLAY_HIDE_MOUSE:
+        return VLC_SUCCESS;
+    case VOUT_DISPLAY_RESET_PICTURES:
+    {
+        if (sys->p_window->fmt.i_chroma == VLC_CODEC_ANDROID_OPAQUE)
+            return VLC_EGENERIC;
+
+        msg_Dbg(vd, "resetting pictures");
+
+        if (sys->pool != NULL)
+        {
+            picture_pool_Release(sys->pool);
+            sys->pool = NULL;
+        }
+        return VLC_SUCCESS;
+    }
+    case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+    case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+    {
+        const video_format_t *source;
+        video_format_t sub_fmt;
+
+        msg_Dbg(vd, "change source crop/aspect");
+
+        source = va_arg(args, const video_format_t *);
+        video_format_ApplyRotation(&sub_fmt, source);
+
+        if (query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
+            video_format_CopyCrop(&sys->p_window->fmt, source);
+            AndroidWindow_UpdateCrop(sys, sys->p_window);
+
+            video_format_CopyCrop(&sys->p_sub_window->fmt, &sub_fmt);
+        } else {
+            CopySourceAspect(&sys->p_window->fmt, source);
+
+            CopySourceAspect(&sys->p_sub_window->fmt, &sub_fmt);
+        }
+        UpdateWindowSize(&sys->p_window->fmt, sys->p_window->b_use_priv);
+        FixSubtitleFormat(sys);
+
+        if (sys->p_sub_pic) {
+            picture_Release(sys->p_sub_pic);
+            sys->p_sub_pic = NULL;
+        }
+        if (sys->p_spu_blend) {
+            filter_DeleteBlend(sys->p_spu_blend);
+            sys->p_spu_blend = NULL;
+        }
+
+        return VLC_SUCCESS;
+    }
+    default:
+        msg_Warn(vd, "Unknown request in android_window");
+    case VOUT_DISPLAY_CHANGE_ZOOM:
+    case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+    case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+        return VLC_EGENERIC;
+    }
+}
diff --git a/modules/video_output/android/android_window.h b/modules/video_output/android/android_window.h
new file mode 100644 (file)
index 0000000..68101c8
--- /dev/null
@@ -0,0 +1,59 @@
+/*****************************************************************************
+ * android_window.c: Android video output module
+ *****************************************************************************
+ * Copyright (C) 2014 VLC authors and VideoLAN
+ *
+ * Authors: Thomas Guillem <thomas@gllm.fr>
+ *          Felix Abecassis <felix.abecassis@gmail.com>
+ *          Ming Hu <tewilove@gmail.com>
+ *          Ludovic Fauvet <etix@l0cal.com>
+ *          S├ębastien Toque <xilasz@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.
+ *****************************************************************************/
+
+#ifndef ANDROID_WINDOW_H_
+#define ANDROID_WINDOW_H_
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_vout_display.h>
+#include <android/native_window.h>
+
+struct picture_sys_t
+{
+    vout_display_sys_t *p_vd_sys;
+
+    int (*pf_lock_pic)(picture_t *);
+    void (*pf_unlock_pic)(picture_t *);
+
+    union {
+        struct {
+            decoder_t *p_dec;
+            uint32_t i_index;
+            bool b_valid;
+        } hw;
+        struct {
+            void *p_handle;
+            ANativeWindow_Buffer buf;
+        } sw;
+    } priv;
+    bool b_render;
+};
+
+#endif
diff --git a/modules/video_output/android/opaque.c b/modules/video_output/android/opaque.c
deleted file mode 100644 (file)
index b06feb4..0000000
+++ /dev/null
@@ -1,353 +0,0 @@
-/*****************************************************************************
- * 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 <vlc_filter.h>
-#include <vlc_md5.h>
-
-#include <dlfcn.h>
-
-#include "../codec/omxil/android_opaque.h"
-#include "utils.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()
-
-#define THREAD_NAME "vout_mediacodec"
-extern int jni_attach_thread(JNIEnv **env, const char *thread_name);
-extern void jni_detach_thread();
-extern jobject jni_LockAndGetSubtitlesSurface();
-extern void  jni_UnlockAndroidSurface();
-
-static const vlc_fourcc_t subpicture_chromas[] =
-{
-    VLC_CODEC_RGBA,
-    0
-};
-
-/*****************************************************************************
- * 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;
-
-    void *p_library;
-    native_window_api_t native_window;
-
-    jobject jsurf;
-    ANativeWindow *window;
-
-    video_format_t fmt;
-
-    filter_t *p_spu_blend;
-    picture_t *subtitles_picture;
-
-    bool b_has_subpictures;
-
-    uint8_t hash[16];
-};
-
-static void DisplaySubpicture(vout_display_t *vd, subpicture_t *subpicture)
-{
-    vout_display_sys_t *sys = vd->sys;
-
-    struct md5_s hash;
-    InitMD5(&hash);
-    if (subpicture) {
-        for (subpicture_region_t *r = subpicture->p_region; r != NULL; r = r->p_next) {
-            AddMD5(&hash, &r->i_x, sizeof(r->i_x));
-            AddMD5(&hash, &r->i_y, sizeof(r->i_y));
-            AddMD5(&hash, &r->fmt.i_visible_width, sizeof(r->fmt.i_visible_width));
-            AddMD5(&hash, &r->fmt.i_visible_height, sizeof(r->fmt.i_visible_height));
-            AddMD5(&hash, &r->fmt.i_x_offset, sizeof(r->fmt.i_x_offset));
-            AddMD5(&hash, &r->fmt.i_y_offset, sizeof(r->fmt.i_y_offset));
-            const int pixels_offset = r->fmt.i_y_offset * r->p_picture->p->i_pitch +
-                                      r->fmt.i_x_offset * r->p_picture->p->i_pixel_pitch;
-
-            for (int y = 0; y < r->fmt.i_visible_height; y++)
-                AddMD5(&hash, &r->p_picture->p->p_pixels[pixels_offset + y*r->p_picture->p->i_pitch], r->fmt.i_visible_width);
-        }
-    }
-    EndMD5(&hash);
-    if (!memcmp(hash.buf, sys->hash, 16))
-        return;
-    memcpy(sys->hash, hash.buf, 16);
-
-    jobject jsurf = jni_LockAndGetSubtitlesSurface();
-    if (sys->window && jsurf != sys->jsurf)
-    {
-        sys->native_window.winRelease(sys->window);
-        sys->window = NULL;
-    }
-    sys->jsurf = jsurf;
-    if (!sys->window)
-    {
-        JNIEnv *p_env;
-        jni_attach_thread(&p_env, THREAD_NAME);
-        sys->window = sys->native_window.winFromSurface(p_env, jsurf);
-        jni_detach_thread();
-    }
-
-    ANativeWindow_Buffer buf = { 0 };
-    int32_t err = sys->native_window.winLock(sys->window, &buf, NULL);
-    if (err) {
-        jni_UnlockAndroidSurface();
-        return;
-    }
-
-    if (buf.width >= sys->fmt.i_width && buf.height >= sys->fmt.i_height)
-    {
-        /* Wrap the NativeWindow corresponding to the subtitles surface in a picture_t */
-        picture_t *picture = sys->subtitles_picture;
-        picture->p[0].p_pixels = (uint8_t*)buf.bits;
-        picture->p[0].i_lines = buf.height;
-        picture->p[0].i_pitch = picture->p[0].i_pixel_pitch * buf.stride;
-        /* Clear the subtitles surface. */
-        memset(picture->p[0].p_pixels, 0, picture->p[0].i_pitch * picture->p[0].i_lines);
-        if (subpicture)
-        {
-            /* Allocate a blending filter if needed. */
-            if (unlikely(!sys->p_spu_blend))
-                sys->p_spu_blend = filter_NewBlend(VLC_OBJECT(vd), &picture->format);
-            picture_BlendSubpicture(picture, sys->p_spu_blend, subpicture);
-        }
-    }
-
-    sys->native_window.unlockAndPost(sys->window);
-    jni_UnlockAndroidSurface();
-}
-
-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;
-    if (vout_display_IsWindowed(vd))
-        return VLC_EGENERIC;
-
-    /* Allocate structure */
-    vout_display_sys_t *sys = (struct vout_display_sys_t*)calloc(1, sizeof(*sys));
-    if (!sys)
-        return VLC_ENOMEM;
-
-    sys->p_library = LoadNativeWindowAPI(&sys->native_window);
-    if (!sys->p_library)
-    {
-        free(sys);
-        msg_Err(vd, "Could not initialize NativeWindow API.");
-        return VLC_EGENERIC;
-    }
-    sys->fmt = fmt;
-    video_format_t subpicture_format = sys->fmt;
-    subpicture_format.i_chroma = VLC_CODEC_RGBA;
-    /* Create a RGBA picture for rendering subtitles. */
-    picture_resource_t rsc;
-    memset(&rsc, 0, sizeof(rsc));
-    sys->subtitles_picture = picture_NewFromResource(&subpicture_format, &rsc);
-
-    /* Export the subpicture capability of this vout. */
-    vd->info.subpicture_chromas = subpicture_chromas;
-
-    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  = Manage;
-
-    /* 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_Release(sys->pool);
-    if (sys->window)
-        sys->native_window.winRelease(sys->window);
-    dlclose(sys->p_library);
-    if (sys->subtitles_picture)
-        picture_Release(sys->subtitles_picture);
-    if (sys->p_spu_blend)
-        filter_DeleteBlend(sys->p_spu_blend);
-    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;
-    vout_display_sys_t *sys = vd->sys;
-    void (*display_callback)(picture_sys_t*) = p_picsys->pf_display_callback;
-    if (display_callback)
-        display_callback(p_picsys);
-
-    if (subpicture && sys->subtitles_picture)
-        sys->b_has_subpictures = true;
-    /* As long as no subpicture was received, do not call
-       DisplaySubpicture since JNI calls and clearing the subtitles
-       surface are expensive operations. */
-    if (sys->b_has_subpictures)
-    {
-        DisplaySubpicture(vd, subpicture);
-        if (!subpicture)
-        {
-            /* The surface has been cleared and there is no new
-               subpicture to upload, do not clear again until a new
-               subpicture is received. */
-            sys->b_has_subpictures = false;
-        }
-    }
-
-    /* refcount lowers to 0, and pool_cfg.unlock is called */
-    picture_Release(picture);
-    if (subpicture)
-        subpicture_Delete(subpicture);
-}
-
-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:
-        return VLC_EGENERIC;
-    }
-}