Commit 8a709642 authored by Thomas Guillem's avatar Thomas Guillem Committed by Jean-Baptiste Kempf

modules: add android_window vout

 - 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: default avatarJean-Baptiste Kempf <jb@videolan.org>
parent 646639aa
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -30,15 +30,6 @@
#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
......@@ -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
......@@ -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
......
/*****************************************************************************
* 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;
}
}
/*****************************************************************************
* 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
/*****************************************************************************
* 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;
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment