You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1160 lines
36 KiB

/*****************************************************************************
* 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 <dlfcn.h>
#include "android_window.h"
#include "utils.h"
/*****************************************************************************
* Module descriptor
*****************************************************************************/
#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 "androidwindow-"
static int Open (vlc_object_t *);
static void Close(vlc_object_t *);
static void SubpicturePrepare(vout_display_t *vd, subpicture_t *subpicture);
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", 260)
add_shortcut("androidwindow", "android")
add_string(CFG_PREFIX "chroma", NULL, CHROMA_TEXT, CHROMA_LONGTEXT, true)
set_callbacks(Open, Close)
vlc_module_end()
/*****************************************************************************
* Local prototypes
*****************************************************************************/
#define THREAD_NAME "android_window"
static const vlc_fourcc_t subpicture_chromas[] =
{
VLC_CODEC_RGBA,
0
};
static picture_pool_t *Pool (vout_display_t *, unsigned);
static void Prepare(vout_display_t *, picture_t *, subpicture_t *);
static void Display(vout_display_t *, picture_t *, subpicture_t *);
static void Manage(vout_display_t *vd);
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;
bool b_opaque;
enum AWindow_ID id;
ANativeWindow *p_handle;
native_window_priv *p_handle_priv;
};
typedef struct buffer_bounds buffer_bounds;
struct buffer_bounds
{
uint8_t *p_pixels;
ARect bounds;
};
struct vout_display_sys_t
{
picture_pool_t *pool;
int i_display_width;
int i_display_height;
AWindowHandler *p_awh;
native_window_api_t *anw;
native_window_priv_api_t *anwp;
android_window *p_window;
android_window *p_sub_window;
bool b_sub_invalid;
filter_t *p_spu_blend;
picture_t *p_sub_pic;
buffer_bounds *p_sub_buffer_bounds;
bool b_sub_pic_locked;
int64_t i_sub_last_order;
ARect sub_last_region;
bool b_has_subpictures;
uint8_t hash[16];
};
#define PRIV_WINDOW_FORMAT_YV12 0x32315659
static inline int ChromaToAndroidHal(vlc_fourcc_t i_chroma)
{
switch (i_chroma) {
case VLC_CODEC_YV12:
case VLC_CODEC_I420:
return PRIV_WINDOW_FORMAT_YV12;
case VLC_CODEC_RGB16:
return WINDOW_FORMAT_RGB_565;
case VLC_CODEC_RGB32:
return WINDOW_FORMAT_RGBX_8888;
case VLC_CODEC_RGBA:
return WINDOW_FORMAT_RGBA_8888;
default:
return -1;
}
}
static int UpdateWindowSize(vout_display_sys_t *sys, 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;
}
AWindowHandler_setWindowLayout(sys->p_awh, i_width, i_height,
rot_fmt.i_visible_width,
rot_fmt.i_visible_height,
i_sar_num, i_sar_den);
return 0;
}
static void AndroidOpaquePicture_DetachVout(picture_t *p_pic)
{
picture_sys_t *p_picsys = p_pic->p_sys;
vlc_mutex_lock(&p_picsys->priv.hw.lock);
p_pic->p_sys->p_vd_sys = NULL;
/* Release p_picsys if references from VOUT and from decoder are NULL */
if (!p_picsys->p_vd_sys && !p_picsys->priv.hw.p_dec)
{
vlc_mutex_unlock(&p_picsys->priv.hw.lock);
vlc_mutex_destroy(&p_picsys->priv.hw.lock);
free(p_picsys);
}
else
vlc_mutex_unlock(&p_picsys->priv.hw.lock);
free(p_pic);
}
static picture_t *PictureAlloc(vout_display_sys_t *sys, video_format_t *fmt,
bool b_opaque)
{
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;
if (b_opaque)
{
p_picsys->priv.hw.i_index = -1;
vlc_mutex_init(&p_picsys->priv.hw.lock);
rsc.pf_destroy = AndroidOpaquePicture_DetachVout;
}
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_subfmt;
video_format_t fmt;
int i_width, i_height;
int i_video_width, i_video_height;
int i_display_width, i_display_height;
double aspect;
if (!sys->p_sub_window)
return;
p_subfmt = &sys->p_sub_window->fmt;
video_format_ApplyRotation(&fmt, &sys->p_window->fmt);
if (fmt.i_visible_width == 0 || fmt.i_visible_height == 0) {
i_video_width = fmt.i_width;
i_video_height = fmt.i_height;
} else {
i_video_width = fmt.i_visible_width;
i_video_height = fmt.i_visible_height;
}
if (fmt.i_sar_num > 0 && fmt.i_sar_den > 0) {
if (fmt.i_sar_num >= fmt.i_sar_den)
i_video_width = i_video_width * fmt.i_sar_num / fmt.i_sar_den;
else
i_video_height = i_video_height * fmt.i_sar_den / fmt.i_sar_num;
}
if (sys->p_window->i_angle == 90 || sys->p_window->i_angle == 180) {
i_display_width = sys->i_display_height;
i_display_height = sys->i_display_width;
aspect = i_video_height / (double) i_video_width;
} else {
i_display_width = sys->i_display_width;
i_display_height = sys->i_display_height;
aspect = i_video_width / (double) i_video_height;
}
if (i_display_width / aspect < i_display_height) {
i_width = i_display_width;
i_height = i_display_width / aspect;
} else {
i_width = i_display_height * aspect;
i_height = i_display_height;
}
// Use the biggest size available
if (i_width * i_height < i_video_width * i_video_height) {
i_width = i_video_width;
i_height = i_video_height;
}
p_subfmt->i_width =
p_subfmt->i_visible_width = i_width;
p_subfmt->i_height =
p_subfmt->i_visible_height = i_height;
p_subfmt->i_x_offset = 0;
p_subfmt->i_y_offset = 0;
p_subfmt->i_sar_num = 1;
p_subfmt->i_sar_den = 1;
sys->b_sub_invalid = true;
}
#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 void AndroidWindow_DisconnectSurface(vout_display_sys_t *sys,
android_window *p_window,
bool b_clear)
{
if (p_window->p_handle_priv) {
sys->anwp->disconnect(p_window->p_handle_priv);
p_window->p_handle_priv = NULL;
}
if (p_window->p_handle) {
AWindowHandler_releaseANativeWindow(sys->p_awh, p_window->id, b_clear);
p_window->p_handle = NULL;
}
}
static int AndroidWindow_ConnectSurface(vout_display_sys_t *sys,
android_window *p_window)
{
if (!p_window->p_handle && !p_window->b_opaque) {
p_window->p_handle = AWindowHandler_getANativeWindow(sys->p_awh,
p_window->id);
if (!p_window->p_handle)
return -1;
}
return 0;
}
static android_window *AndroidWindow_New(vout_display_t *vd,
video_format_t *p_fmt,
enum AWindow_ID id,
bool b_use_priv)
{
vout_display_sys_t *sys = vd->sys;
android_window *p_window = NULL;
p_window = calloc(1, sizeof(android_window));
if (!p_window)
goto error;
p_window->id = id;
p_window->b_opaque = p_fmt->i_chroma == VLC_CODEC_ANDROID_OPAQUE;
if (!p_window->b_opaque) {
p_window->b_use_priv = sys->anwp && b_use_priv;
p_window->i_android_hal = ChromaToAndroidHal(p_fmt->i_chroma);
if (p_window->i_android_hal == -1)
goto error;
}
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;
}
if (p_window->b_use_priv)
p_window->fmt = *p_fmt;
else
video_format_ApplyRotation(&p_window->fmt, p_fmt);
p_window->i_pic_count = 1;
if (AndroidWindow_ConnectSurface(sys, p_window) != 0)
{
if (id == AWindow_Video)
msg_Err(vd, "can't get Video Surface");
else if (id == AWindow_Subtitles)
msg_Err(vd, "can't get Subtitles Surface");
goto error;
}
return p_window;
error:
free(p_window);
return NULL;
}
static void AndroidWindow_Destroy(vout_display_t *vd,
android_window *p_window, bool b_clear)
{
AndroidWindow_DisconnectSurface(vd->sys, p_window, b_clear);
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 int AndroidWindow_SetupANWP(vout_display_sys_t *sys,
android_window *p_window,
bool b_java_configured)
{
unsigned int i_max_buffer_count = 0;
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->setUsage(p_window->p_handle_priv, false, 0) != 0)
goto error;
if (!b_java_configured
&& sys->anwp->setBuffersGeometry(p_window->p_handle_priv,
p_window->fmt.i_width,
p_window->fmt.i_height,
p_window->i_android_hal) != 0)
goto error;
sys->anwp->getMinUndequeued(p_window->p_handle_priv,
&p_window->i_min_undequeued);
sys->anwp->getMaxBufferCount(p_window->p_handle_priv, &i_max_buffer_count);
if ((p_window->i_min_undequeued + p_window->i_pic_count) >
i_max_buffer_count)
p_window->i_pic_count = i_max_buffer_count - p_window->i_min_undequeued;
if (sys->anwp->setBufferCount(p_window->p_handle_priv,
p_window->i_pic_count +
p_window->i_min_undequeued) != 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);
return -1;
}
static int AndroidWindow_ConfigureJavaSurface(vout_display_sys_t *sys,
android_window *p_window,
bool *p_java_configured)
{
/* setBuffersGeometry is broken before ics. Use
* AJavaWindow_setBuffersGeometry to configure the surface on the java side
* synchronously. AJavaWindow_setBuffersGeometry return en error when you
* don't need to call it (ie, after ics). if this call succeed, you need to
* get a new surface handle. That's why AndroidWindow_DisconnectSurface is
* called here. */
if (AWindowHandler_setBuffersGeometry(sys->p_awh, p_window->id,
p_window->fmt.i_width,
p_window->fmt.i_height,
p_window->i_android_hal) == VLC_SUCCESS)
{
*p_java_configured = true;
AndroidWindow_DisconnectSurface(sys, p_window, false);
if (AndroidWindow_ConnectSurface(sys, p_window) != 0)
return -1;
} else
*p_java_configured = false;
return 0;
}
static int AndroidWindow_SetupANW(vout_display_sys_t *sys,
android_window *p_window,
bool b_java_configured)
{
p_window->i_pic_count = 1;
p_window->i_min_undequeued = 0;
if (!b_java_configured && sys->anw->setBuffersGeometry)
return sys->anw->setBuffersGeometry(p_window->p_handle,
p_window->fmt.i_width,
p_window->fmt.i_height,
p_window->i_android_hal);
else
return 0;
}
static int AndroidWindow_Setup(vout_display_sys_t *sys,
android_window *p_window,
unsigned int i_pic_count)
{
bool b_java_configured = false;
if (i_pic_count != 0)
p_window->i_pic_count = i_pic_count;
if (!p_window->b_opaque) {
int align_pixels;
picture_t *p_pic = PictureAlloc(sys, &p_window->fmt, false);
// 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 (AndroidWindow_ConfigureJavaSurface(sys, p_window,
&b_java_configured) != 0)
return -1;
if (!p_window->b_use_priv
|| AndroidWindow_SetupANWP(sys, p_window, b_java_configured) != 0) {
if (AndroidWindow_SetupANW(sys, p_window, b_java_configured) != 0)
return -1;
}
} else {
sys->p_window->i_pic_count = 31; // TODO
sys->p_window->i_min_undequeued = 0;
}
return 0;
}
static void AndroidWindow_UnlockPicture(vout_display_sys_t *sys,
android_window *p_window,
picture_t *p_pic,
bool b_render)
{
picture_sys_t *p_picsys = p_pic->p_sys;
if (p_window->b_use_priv) {
void *p_handle = p_picsys->priv.sw.p_handle;
if (p_handle == NULL)
return;
sys->anwp->unlockData(p_window->p_handle_priv, p_handle, b_render);
} 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->lockData(p_window->p_handle_priv,
&p_handle,
&p_picsys->priv.sw.buf);
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, false);
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 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 void SendEventDisplaySize(vout_display_t *vd)
{
vout_display_sys_t *sys = vd->sys;
int i_display_width, i_display_height;
if (AWindowHandler_getWindowSize(sys->p_awh, &i_display_width, &i_display_height)
&& i_display_width != 0 && i_display_height != 0
&& (i_display_width != sys->i_display_width
|| i_display_height != sys->i_display_height))
vout_display_SendEventDisplaySize(vd, i_display_width,
i_display_height);
}
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_awh = AWindowHandler_new(p_this);
if (!sys->p_awh)
{
free(sys);
msg_Err(vd, "AWindowHandler_new failed");
return VLC_EGENERIC;
}
sys->anw = AWindowHandler_getANativeWindowAPI(sys->p_awh);
#ifdef USE_ANWP
sys->anwp = AWindowHandler_getANativeWindowPrivAPI(sys->p_awh);
if (!sys->anwp)
msg_Warn(vd, "Could not initialize NativeWindow Priv API.");
#endif
sys->i_display_width = vd->cfg->display.width;
sys->i_display_height = vd->cfg->display.height;
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(vd, &vd->fmt, AWindow_Video, true);
if (!sys->p_window)
goto error;
if (AndroidWindow_Setup(sys, sys->p_window, 0) != 0)
goto error;
/* use software rotation if we don't use private anw */
if (!sys->p_window->b_opaque && !sys->p_window->b_use_priv)
video_format_ApplyRotation(&vd->fmt, &vd->fmt);
msg_Dbg(vd, "using %s", sys->p_window->b_opaque ? "opaque" :
(sys->p_window->b_use_priv ? "ANWP" : "ANW"));
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(vd, &sub_fmt, AWindow_Subtitles, false);
if (sys->p_sub_window) {
FixSubtitleFormat(sys);
sys->i_sub_last_order = -1;
/* Export the subpicture capability of this vout. */
vd->info.subpicture_chromas = subpicture_chromas;
}
/* Setup vout_display */
vd->pool = Pool;
vd->prepare = Prepare;
vd->display = Display;
vd->control = Control;
vd->manage = Manage;
/* Fix initial state */
vout_display_SendEventFullscreen(vd, true);
SendEventDisplaySize(vd);
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;
/* Check if SPU regions have been properly cleared, and clear them if they
* were not. */
if (sys->b_has_subpictures)
{
SubpicturePrepare(vd, NULL);
if (sys->b_sub_pic_locked)
AndroidWindow_UnlockPicture(sys, sys->p_sub_window, sys->p_sub_pic, true);
}
if (sys->pool)
picture_pool_Release(sys->pool);
if (sys->p_window)
AndroidWindow_Destroy(vd, sys->p_window, true);
if (sys->p_sub_pic)
picture_Release(sys->p_sub_pic);
if (sys->p_spu_blend)
filter_DeleteBlend(sys->p_spu_blend);
free(sys->p_sub_buffer_bounds);
if (sys->p_sub_window)
AndroidWindow_Destroy(vd, sys->p_sub_window, false);
if (sys->p_awh)
AWindowHandler_destroy(sys->p_awh);
free(sys);
}
static void UnlockPicture(picture_t *p_pic, bool b_render)
{
picture_sys_t *p_picsys = p_pic->p_sys;
vout_display_sys_t *sys = p_picsys->p_vd_sys;
if (p_picsys->b_locked)
{
if (sys->p_window->b_opaque)
AndroidOpaquePicture_Release(p_picsys, b_render);
else
AndroidWindow_UnlockPicture(sys, sys->p_window, p_pic, b_render);
p_picsys->b_locked = false;
}
}
static int PoolLockPicture(picture_t *p_pic)
{
picture_sys_t *p_picsys = p_pic->p_sys;
vout_display_sys_t *sys = p_picsys->p_vd_sys;
if (!sys->p_window->b_opaque
&& AndroidWindow_LockPicture(sys, sys->p_window, p_pic) != 0)
return -1;
p_picsys->b_locked = true;
return 0;
}
static void PoolUnlockPicture(picture_t *p_pic)
{
UnlockPicture(p_pic, false);
}
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;
msg_Dbg(vd, "PoolAlloc: request %d frames", requested_count);
if (AndroidWindow_Setup(sys, sys->p_window, requested_count) != 0)
goto error;
requested_count = sys->p_window->i_pic_count;
msg_Dbg(vd, "PoolAlloc: got %d frames", requested_count);
UpdateWindowSize(sys, &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,
sys->p_window->b_opaque);
if (!p_pic)
goto error;
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 = PoolLockPicture;
pool_cfg.unlock = PoolUnlockPicture;
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 SubtitleRegionToBounds(subpicture_t *subpicture,
ARect *p_out_bounds)
{
if (subpicture) {
for (subpicture_region_t *r = subpicture->p_region; r != NULL; r = r->p_next) {
ARect new_bounds;
new_bounds.left = r->i_x;
new_bounds.top = r->i_y;
new_bounds.right = r->fmt.i_visible_width + r->i_x;
new_bounds.bottom = r->fmt.i_visible_height + r->i_y;
if (r == &subpicture->p_region[0])
*p_out_bounds = new_bounds;
else {
if (p_out_bounds->left > new_bounds.left)
p_out_bounds->left = new_bounds.left;
if (p_out_bounds->right < new_bounds.right)
p_out_bounds->right = new_bounds.right;
if (p_out_bounds->top > new_bounds.top)
p_out_bounds->top = new_bounds.top;
if (p_out_bounds->bottom < new_bounds.bottom)
p_out_bounds->bottom = new_bounds.bottom;
}
}
} else {
p_out_bounds->left = p_out_bounds->top = 0;
p_out_bounds->right = p_out_bounds->bottom = 0;
}
}
static void SubtitleGetDirtyBounds(vout_display_t *vd,
subpicture_t *subpicture,
ARect *p_out_bounds)
{
vout_display_sys_t *sys = vd->sys;
int i = 0;
bool b_found = false;
/* Try to find last bounds set by current locked buffer.
* Indeed, even if we can lock only one buffer at a time, differents
* buffers can be locked. This functions will find the last bounds set by
* the current buffer. */
if (sys->p_sub_buffer_bounds) {
for (; sys->p_sub_buffer_bounds[i].p_pixels != NULL; ++i) {
buffer_bounds *p_bb = &sys->p_sub_buffer_bounds[i];
if (p_bb->p_pixels == sys->p_sub_pic->p[0].p_pixels) {
*p_out_bounds = p_bb->bounds;
b_found = true;
break;
}
}
}
if (!b_found
|| p_out_bounds->left < 0
|| p_out_bounds->right < 0
|| (unsigned int) p_out_bounds->right > sys->p_sub_pic->format.i_width
|| p_out_bounds->bottom < 0
|| p_out_bounds->top < 0
|| (unsigned int) p_out_bounds->top > sys->p_sub_pic->format.i_height)
{
/* default is full picture */
p_out_bounds->left = 0;
p_out_bounds->top = 0;
p_out_bounds->right = sys->p_sub_pic->format.i_width;
p_out_bounds->bottom = sys->p_sub_pic->format.i_height;
}
/* buffer not found, add it to the array */
if (!sys->p_sub_buffer_bounds
|| sys->p_sub_buffer_bounds[i].p_pixels == NULL) {
buffer_bounds *p_bb = realloc(sys->p_sub_buffer_bounds,
(i + 2) * sizeof(buffer_bounds));
if (p_bb) {
sys->p_sub_buffer_bounds = p_bb;
sys->p_sub_buffer_bounds[i].p_pixels = sys->p_sub_pic->p[0].p_pixels;
sys->p_sub_buffer_bounds[i+1].p_pixels = NULL;
}
}
/* set buffer bounds */
if (sys->p_sub_buffer_bounds
&& sys->p_sub_buffer_bounds[i].p_pixels != NULL)
SubtitleRegionToBounds(subpicture, &sys->p_sub_buffer_bounds[i].bounds);
}
static void SubpicturePrepare(vout_display_t *vd, subpicture_t *subpicture)
{
vout_display_sys_t *sys = vd->sys;
ARect memset_bounds;
SubtitleRegionToBounds(subpicture, &memset_bounds);
if( subpicture )
{
if( subpicture->i_order == sys->i_sub_last_order
&& memcmp( &memset_bounds, &sys->sub_last_region, sizeof(ARect) ) == 0 )
return;
sys->i_sub_last_order = subpicture->i_order;
sys->sub_last_region = memset_bounds;
}
if (AndroidWindow_LockPicture(sys, sys->p_sub_window, sys->p_sub_pic) != 0)
return;
sys->b_sub_pic_locked = true;
/* Clear the subtitles surface. */
SubtitleGetDirtyBounds(vd, subpicture, &memset_bounds);
const int x_pixels_offset = memset_bounds.left
* sys->p_sub_pic->p[0].i_pixel_pitch;
const int i_line_size = (memset_bounds.right - memset_bounds.left)
* sys->p_sub_pic->p->i_pixel_pitch;
for (int y = memset_bounds.top; y < memset_bounds.bottom; y++)
memset(&sys->p_sub_pic->p[0].p_pixels[y * sys->p_sub_pic->p[0].i_pitch
+ x_pixels_offset], 0, i_line_size);
if (subpicture)
picture_BlendSubpicture(sys->p_sub_pic, sys->p_spu_blend, subpicture);
}
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 Prepare(vout_display_t *vd, picture_t *picture,
subpicture_t *subpicture)
{
vout_display_sys_t *sys = vd->sys;
VLC_UNUSED(picture);
SendEventDisplaySize(vd);
if (subpicture && sys->p_sub_window) {
if (sys->b_sub_invalid) {
sys->b_sub_invalid = false;
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;
}
free(sys->p_sub_buffer_bounds);
sys->p_sub_buffer_bounds = NULL;
}
if (!sys->p_sub_pic
&& AndroidWindow_Setup(sys, sys->p_sub_window, 1) == 0)
sys->p_sub_pic = PictureAlloc(sys, &sys->p_sub_window->fmt, false);
if (!sys->p_spu_blend && sys->p_sub_pic)
sys->p_spu_blend = filter_NewBlend(VLC_OBJECT(vd),
&sys->p_sub_pic->format);
if (sys->p_sub_pic && sys->p_spu_blend)
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)
{
SubpicturePrepare(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;
}
}
}
static void Display(vout_display_t *vd, picture_t *picture,
subpicture_t *subpicture)
{
vout_display_sys_t *sys = vd->sys;
/* refcount lowers to 0, and pool_cfg.unlock is called */
UnlockPicture(picture, true);
picture_Release(picture);
if (sys->b_sub_pic_locked) {
sys->b_sub_pic_locked = false;
AndroidWindow_UnlockPicture(sys, sys->p_sub_window, sys->p_sub_pic, true);
}
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:
case VOUT_DISPLAY_CHANGE_FULLSCREEN:
return VLC_SUCCESS;
case VOUT_DISPLAY_RESET_PICTURES:
{
if (sys->p_window->b_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:
case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
{
if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
|| query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
const video_format_t *source;
msg_Dbg(vd, "change source crop/aspect");
source = va_arg(args, const video_format_t *);
if (query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
video_format_CopyCrop(&sys->p_window->fmt, source);
AndroidWindow_UpdateCrop(sys, sys->p_window);
} else
CopySourceAspect(&sys->p_window->fmt, source);
UpdateWindowSize(sys, &sys->p_window->fmt,
sys->p_window->b_use_priv);
} else {
const vout_display_cfg_t *cfg;
cfg = va_arg(args, const vout_display_cfg_t *);
sys->i_display_width = cfg->display.width;
sys->i_display_height = cfg->display.height;
msg_Dbg(vd, "change display size: %dx%d", sys->i_display_width,
sys->i_display_height);
}
FixSubtitleFormat(sys);
return VLC_SUCCESS;
}
default:
msg_Warn(vd, "Unknown request in android_window");
case VOUT_DISPLAY_CHANGE_ZOOM:
case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
return VLC_EGENERIC;
}
}
static void Manage(vout_display_t *vd)
{
vout_display_sys_t *sys = vd->sys;
int x, y, button, action;
if (AWindowHandler_getMouseCoordinates(sys->p_awh,
&action, &button, &x, &y))
{
switch( action )
{
case AMOTION_EVENT_ACTION_DOWN:
vout_display_SendEventMouseMoved(vd, x, y);
vout_display_SendEventMousePressed(vd, button); break;
case AMOTION_EVENT_ACTION_UP:
vout_display_SendEventMouseMoved(vd, x, y);
vout_display_SendEventMouseReleased(vd, button); break;
case AMOTION_EVENT_ACTION_MOVE:
vout_display_SendEventMouseMoved(vd, x, y); break;
}
}
}