vout: change "vout display" activate/deactivate callbacks.
[vlc.git] / modules / video_output / android / display.c
blob2cb5b0d6ca85b542fa4ba4a97865eba67c6b2b2c
1 /*****************************************************************************
2 * display.c: Android video output module
3 *****************************************************************************
4 * Copyright (C) 2014 VLC authors and VideoLAN
6 * Authors: Thomas Guillem <thomas@gllm.fr>
7 * Felix Abecassis <felix.abecassis@gmail.com>
8 * Ming Hu <tewilove@gmail.com>
9 * Ludovic Fauvet <etix@l0cal.com>
10 * Sébastien Toque <xilasz@gmail.com>
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU Lesser General Public License as published by
14 * the Free Software Foundation; either version 2.1 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public License
23 * along with this program; if not, write to the Free Software Foundation,
24 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_vout_display.h>
34 #include <vlc_picture_pool.h>
35 #include <vlc_filter.h>
37 #include <vlc_opengl.h> /* for ClearSurface */
38 #include <GLES2/gl2.h> /* for ClearSurface */
40 #include <dlfcn.h>
42 #include "display.h"
43 #include "utils.h"
45 /*****************************************************************************
46 * Module descriptor
47 *****************************************************************************/
48 #define USE_ANWP
49 #define CHROMA_TEXT "Chroma used"
50 #define CHROMA_LONGTEXT \
51 "Force use of a specific chroma for output. Default is RGB32."
53 #define CFG_PREFIX "android-display-"
54 static int Open(vout_display_t *vd, const vout_display_cfg_t *cfg,
55 video_format_t *fmtp, vlc_video_context *context);
56 static int OpenOpaque(vout_display_t *vd, const vout_display_cfg_t *cfg,
57 video_format_t *fmtp, vlc_video_context *context);
58 static void Close(vout_display_t *vd);
59 static void SubpicturePrepare(vout_display_t *vd, subpicture_t *subpicture);
61 vlc_module_begin()
62 set_category(CAT_VIDEO)
63 set_subcategory(SUBCAT_VIDEO_VOUT)
64 set_description("Android video output")
65 set_capability("vout display", 260)
66 add_shortcut("android-display")
67 add_string(CFG_PREFIX "chroma", NULL, CHROMA_TEXT, CHROMA_LONGTEXT, true)
68 set_callbacks(Open, Close)
69 add_submodule ()
70 set_description("Android opaque video output")
71 set_capability("vout display", 280)
72 add_shortcut("android-opaque")
73 set_callbacks(OpenOpaque, Close)
74 vlc_module_end()
76 /*****************************************************************************
77 * Local prototypes
78 *****************************************************************************/
80 static const vlc_fourcc_t subpicture_chromas[] =
82 VLC_CODEC_RGBA,
86 static picture_pool_t *Pool (vout_display_t *, unsigned);
87 static void Prepare(vout_display_t *, picture_t *, subpicture_t *, vlc_tick_t);
88 static void Display(vout_display_t *, picture_t *);
89 static int Control(vout_display_t *, int, va_list);
91 typedef struct android_window android_window;
92 struct android_window
94 video_format_t fmt;
95 int i_android_hal;
96 unsigned int i_angle;
97 unsigned int i_pic_count;
98 unsigned int i_min_undequeued;
99 bool b_opaque;
101 enum AWindow_ID id;
102 ANativeWindow *p_surface;
103 jobject *p_jsurface;
106 typedef struct buffer_bounds buffer_bounds;
107 struct buffer_bounds
109 uint8_t *p_pixels;
110 ARect bounds;
113 struct vout_display_sys_t
115 vout_window_t *embed;
116 picture_pool_t *pool;
118 int i_display_width;
119 int i_display_height;
121 AWindowHandler *p_awh;
122 native_window_api_t *anw;
124 android_window *p_window;
125 android_window *p_sub_window;
127 bool b_displayed;
128 bool b_sub_invalid;
129 filter_t *p_spu_blend;
130 picture_t *p_sub_pic;
131 buffer_bounds *p_sub_buffer_bounds;
132 int64_t i_sub_last_order;
133 ARect sub_last_region;
135 bool b_has_subpictures;
138 #define PRIV_WINDOW_FORMAT_YV12 0x32315659
140 static inline int ChromaToAndroidHal(vlc_fourcc_t i_chroma)
142 switch (i_chroma) {
143 case VLC_CODEC_YV12:
144 case VLC_CODEC_I420:
145 return PRIV_WINDOW_FORMAT_YV12;
146 case VLC_CODEC_RGB16:
147 return WINDOW_FORMAT_RGB_565;
148 case VLC_CODEC_RGB32:
149 return WINDOW_FORMAT_RGBX_8888;
150 case VLC_CODEC_RGBA:
151 return WINDOW_FORMAT_RGBA_8888;
152 default:
153 return -1;
157 static int UpdateVideoSize(vout_display_sys_t *sys, video_format_t *p_fmt)
159 unsigned int i_width, i_height;
160 unsigned int i_sar_num = 1, i_sar_den = 1;
161 video_format_t rot_fmt;
163 video_format_ApplyRotation(&rot_fmt, p_fmt);
165 if (rot_fmt.i_sar_num != 0 && rot_fmt.i_sar_den != 0) {
166 i_sar_num = rot_fmt.i_sar_num;
167 i_sar_den = rot_fmt.i_sar_den;
169 i_width = rot_fmt.i_width;
170 i_height = rot_fmt.i_height;
172 AWindowHandler_setVideoLayout(sys->p_awh, i_width, i_height,
173 rot_fmt.i_visible_width,
174 rot_fmt.i_visible_height,
175 i_sar_num, i_sar_den);
176 return 0;
179 static picture_t *PictureAlloc(vout_display_sys_t *sys, video_format_t *fmt,
180 bool b_opaque)
182 picture_t *p_pic;
183 picture_resource_t rsc;
184 picture_sys_t *p_picsys = calloc(1, sizeof(*p_picsys));
186 if (unlikely(p_picsys == NULL))
187 return NULL;
190 memset(&rsc, 0, sizeof(picture_resource_t));
191 rsc.p_sys = p_picsys;
193 if (b_opaque)
195 p_picsys->hw.b_vd_ref = true;
196 p_picsys->hw.p_surface = sys->p_window->p_surface;
197 p_picsys->hw.p_jsurface = sys->p_window->p_jsurface;
198 p_picsys->hw.i_index = -1;
199 vlc_mutex_init(&p_picsys->hw.lock);
200 rsc.pf_destroy = AndroidOpaquePicture_DetachVout;
202 else
203 p_picsys->sw.p_vd_sys = sys;
205 p_pic = picture_NewFromResource(fmt, &rsc);
206 if (!p_pic)
208 free(p_picsys);
209 return NULL;
211 return p_pic;
214 static void FixSubtitleFormat(vout_display_sys_t *sys)
216 video_format_t *p_subfmt;
217 video_format_t fmt;
218 int i_width, i_height;
219 int i_video_width, i_video_height;
220 int i_display_width, i_display_height;
221 double aspect;
223 if (!sys->p_sub_window)
224 return;
225 p_subfmt = &sys->p_sub_window->fmt;
227 video_format_ApplyRotation(&fmt, &sys->p_window->fmt);
229 if (fmt.i_visible_width == 0 || fmt.i_visible_height == 0) {
230 i_video_width = fmt.i_width;
231 i_video_height = fmt.i_height;
232 } else {
233 i_video_width = fmt.i_visible_width;
234 i_video_height = fmt.i_visible_height;
237 if (fmt.i_sar_num > 0 && fmt.i_sar_den > 0) {
238 if (fmt.i_sar_num >= fmt.i_sar_den)
239 i_video_width = i_video_width * fmt.i_sar_num / fmt.i_sar_den;
240 else
241 i_video_height = i_video_height * fmt.i_sar_den / fmt.i_sar_num;
244 if (sys->p_window->i_angle == 90 || sys->p_window->i_angle == 180) {
245 i_display_width = sys->i_display_height;
246 i_display_height = sys->i_display_width;
247 aspect = i_video_height / (double) i_video_width;
248 } else {
249 i_display_width = sys->i_display_width;
250 i_display_height = sys->i_display_height;
251 aspect = i_video_width / (double) i_video_height;
254 if (i_display_width / aspect < i_display_height) {
255 i_width = i_display_width;
256 i_height = i_display_width / aspect;
257 } else {
258 i_width = i_display_height * aspect;
259 i_height = i_display_height;
262 // Use the biggest size available
263 if (i_width * i_height < i_video_width * i_video_height) {
264 i_width = i_video_width;
265 i_height = i_video_height;
268 p_subfmt->i_width =
269 p_subfmt->i_visible_width = i_width;
270 p_subfmt->i_height =
271 p_subfmt->i_visible_height = i_height;
272 p_subfmt->i_x_offset = 0;
273 p_subfmt->i_y_offset = 0;
274 p_subfmt->i_sar_num = 1;
275 p_subfmt->i_sar_den = 1;
276 sys->b_sub_invalid = true;
279 #define ALIGN_16_PIXELS( x ) ( ( ( x ) + 15 ) / 16 * 16 )
280 static void SetupPictureYV12(picture_t *p_picture, uint32_t i_in_stride)
282 /* according to document of android.graphics.ImageFormat.YV12 */
283 int i_stride = ALIGN_16_PIXELS(i_in_stride);
284 int i_c_stride = ALIGN_16_PIXELS(i_stride / 2);
286 p_picture->p->i_pitch = i_stride;
288 /* Fill chroma planes for planar YUV */
289 for (int n = 1; n < p_picture->i_planes; n++)
291 const plane_t *o = &p_picture->p[n-1];
292 plane_t *p = &p_picture->p[n];
294 p->p_pixels = o->p_pixels + o->i_lines * o->i_pitch;
295 p->i_pitch = i_c_stride;
296 p->i_lines = p_picture->format.i_height / 2;
298 Explicitly set the padding lines of the picture to black (127 for YUV)
299 since they might be used by Android during rescaling.
301 int visible_lines = p_picture->format.i_visible_height / 2;
302 if (visible_lines < p->i_lines)
303 memset(&p->p_pixels[visible_lines * p->i_pitch], 127, (p->i_lines - visible_lines) * p->i_pitch);
306 if (vlc_fourcc_AreUVPlanesSwapped(p_picture->format.i_chroma,
307 VLC_CODEC_YV12))
308 picture_SwapUV( p_picture );
311 static void AndroidWindow_DisconnectSurface(vout_display_sys_t *sys,
312 android_window *p_window)
314 if (p_window->p_surface) {
315 AWindowHandler_releaseANativeWindow(sys->p_awh, p_window->id);
316 p_window->p_surface = NULL;
320 static int AndroidWindow_ConnectSurface(vout_display_sys_t *sys,
321 android_window *p_window)
323 if (!p_window->p_surface) {
324 p_window->p_surface = AWindowHandler_getANativeWindow(sys->p_awh,
325 p_window->id);
326 if (!p_window->p_surface)
327 return -1;
328 if (p_window->b_opaque)
329 p_window->p_jsurface = AWindowHandler_getSurface(sys->p_awh,
330 p_window->id);
333 return 0;
336 static android_window *AndroidWindow_New(vout_display_t *vd,
337 const video_format_t *p_fmt,
338 enum AWindow_ID id)
340 vout_display_sys_t *sys = vd->sys;
341 android_window *p_window = NULL;
343 p_window = calloc(1, sizeof(android_window));
344 if (!p_window)
345 goto error;
347 p_window->id = id;
348 p_window->b_opaque = p_fmt->i_chroma == VLC_CODEC_ANDROID_OPAQUE;
349 if (!p_window->b_opaque) {
350 p_window->i_android_hal = ChromaToAndroidHal(p_fmt->i_chroma);
351 if (p_window->i_android_hal == -1)
352 goto error;
355 switch (p_fmt->orientation)
357 case ORIENT_ROTATED_90:
358 p_window->i_angle = 90;
359 break;
360 case ORIENT_ROTATED_180:
361 p_window->i_angle = 180;
362 break;
363 case ORIENT_ROTATED_270:
364 p_window->i_angle = 270;
365 break;
366 default:
367 p_window->i_angle = 0;
369 video_format_ApplyRotation(&p_window->fmt, p_fmt);
370 p_window->i_pic_count = 1;
372 if (AndroidWindow_ConnectSurface(sys, p_window) != 0)
374 if (id == AWindow_Video)
375 msg_Err(vd, "can't get Video Surface");
376 else if (id == AWindow_Subtitles)
377 msg_Err(vd, "can't get Subtitles Surface");
378 goto error;
381 return p_window;
382 error:
383 free(p_window);
384 return NULL;
387 static void AndroidWindow_Destroy(vout_display_t *vd,
388 android_window *p_window)
390 AndroidWindow_DisconnectSurface(vd->sys, p_window);
391 free(p_window);
394 static int AndroidWindow_SetupANW(vout_display_sys_t *sys,
395 android_window *p_window,
396 bool b_java_configured)
398 p_window->i_pic_count = 1;
399 p_window->i_min_undequeued = 0;
401 if (!b_java_configured && sys->anw->setBuffersGeometry)
402 return sys->anw->setBuffersGeometry(p_window->p_surface,
403 p_window->fmt.i_width,
404 p_window->fmt.i_height,
405 p_window->i_android_hal);
406 else
407 return 0;
410 static int AndroidWindow_Setup(vout_display_sys_t *sys,
411 android_window *p_window,
412 unsigned int i_pic_count)
414 bool b_java_configured = false;
416 if (i_pic_count != 0)
417 p_window->i_pic_count = i_pic_count;
419 if (!p_window->b_opaque) {
420 int align_pixels;
421 picture_t *p_pic = PictureAlloc(sys, &p_window->fmt, false);
423 // For RGB (32 or 16) we need to align on 8 or 4 pixels, 16 pixels for YUV
424 align_pixels = (16 / p_pic->p[0].i_pixel_pitch) - 1;
425 p_window->fmt.i_height = p_pic->format.i_height;
426 p_window->fmt.i_width = (p_pic->format.i_width + align_pixels) & ~align_pixels;
427 picture_Release(p_pic);
429 if (AndroidWindow_SetupANW(sys, p_window, b_java_configured) != 0)
430 return -1;
431 } else {
432 sys->p_window->i_pic_count = 31; // TODO
433 sys->p_window->i_min_undequeued = 0;
436 return 0;
439 static void AndroidWindow_UnlockPicture(vout_display_sys_t *sys,
440 android_window *p_window,
441 picture_t *p_pic)
443 picture_sys_t *p_picsys = p_pic->p_sys;
445 if (!p_picsys->b_locked)
446 return;
448 sys->anw->unlockAndPost(p_window->p_surface);
450 p_picsys->b_locked = false;
453 static int AndroidWindow_LockPicture(vout_display_sys_t *sys,
454 android_window *p_window,
455 picture_t *p_pic)
457 picture_sys_t *p_picsys = p_pic->p_sys;
459 if (p_picsys->b_locked)
460 return -1;
462 if (sys->anw->winLock(p_window->p_surface,
463 &p_picsys->sw.buf, NULL) != 0)
464 return -1;
466 if (p_picsys->sw.buf.width < 0 ||
467 p_picsys->sw.buf.height < 0 ||
468 (unsigned)p_picsys->sw.buf.width < p_window->fmt.i_width ||
469 (unsigned)p_picsys->sw.buf.height < p_window->fmt.i_height)
471 p_picsys->b_locked = true;
472 AndroidWindow_UnlockPicture(sys, p_window, p_pic);
473 return -1;
476 p_pic->p[0].p_pixels = p_picsys->sw.buf.bits;
477 p_pic->p[0].i_lines = p_picsys->sw.buf.height;
478 p_pic->p[0].i_pitch = p_pic->p[0].i_pixel_pitch * p_picsys->sw.buf.stride;
480 if (p_picsys->sw.buf.format == PRIV_WINDOW_FORMAT_YV12)
481 SetupPictureYV12(p_pic, p_picsys->sw.buf.stride);
483 p_picsys->b_locked = true;
484 return 0;
487 static void SetRGBMask(video_format_t *p_fmt)
489 switch(p_fmt->i_chroma) {
490 case VLC_CODEC_RGB16:
491 p_fmt->i_bmask = 0x0000001f;
492 p_fmt->i_gmask = 0x000007e0;
493 p_fmt->i_rmask = 0x0000f800;
494 break;
496 case VLC_CODEC_RGB32:
497 case VLC_CODEC_RGBA:
498 p_fmt->i_rmask = 0x000000ff;
499 p_fmt->i_gmask = 0x0000ff00;
500 p_fmt->i_bmask = 0x00ff0000;
501 break;
505 static int OpenCommon(vout_display_t *vd, const vout_display_cfg_t *cfg,
506 video_format_t *fmtp)
508 vout_display_sys_t *sys;
509 video_format_t fmt, sub_fmt;
511 vout_window_t *embed = cfg->window;
512 if (embed->type != VOUT_WINDOW_TYPE_ANDROID_NATIVE)
513 return VLC_EGENERIC;
515 fmt = *fmtp;
517 if (embed == NULL)
518 return VLC_EGENERIC;
519 assert(embed->handle.anativewindow);
520 AWindowHandler *p_awh = embed->handle.anativewindow;
522 if (!AWindowHandler_canSetVideoLayout(p_awh))
524 /* It's better to use gles2 if we are not able to change the video
525 * layout */
526 return VLC_EGENERIC;
529 /* Allocate structure */
530 vd->sys = sys = (struct vout_display_sys_t*)calloc(1, sizeof(*sys));
531 if (!sys)
532 return VLC_ENOMEM;
534 sys->embed = embed;
535 sys->p_awh = p_awh;
536 sys->anw = AWindowHandler_getANativeWindowAPI(sys->p_awh);
538 sys->i_display_width = cfg->display.width;
539 sys->i_display_height = cfg->display.height;
541 if (fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) {
542 /* Setup chroma */
543 char *psz_fcc = var_InheritString(vd, CFG_PREFIX "chroma");
544 if (psz_fcc) {
545 fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, psz_fcc);
546 free(psz_fcc);
547 } else
548 fmt.i_chroma = VLC_CODEC_RGB32;
550 switch(fmt.i_chroma) {
551 case VLC_CODEC_YV12:
552 /* avoid swscale usage by asking for I420 instead since the
553 * vout already has code to swap the buffers */
554 fmt.i_chroma = VLC_CODEC_I420;
555 case VLC_CODEC_I420:
556 break;
557 case VLC_CODEC_RGB16:
558 case VLC_CODEC_RGB32:
559 case VLC_CODEC_RGBA:
560 SetRGBMask(&fmt);
561 video_format_FixRgb(&fmt);
562 break;
563 default:
564 goto error;
568 sys->p_window = AndroidWindow_New(vd, &fmt, AWindow_Video);
569 if (!sys->p_window)
570 goto error;
572 if (AndroidWindow_Setup(sys, sys->p_window, 0) != 0)
573 goto error;
575 /* use software rotation if we don't do opaque */
576 if (!sys->p_window->b_opaque)
577 video_format_TransformTo(&fmt, ORIENT_NORMAL);
579 msg_Dbg(vd, "using %s", sys->p_window->b_opaque ? "opaque" : "ANW");
581 video_format_ApplyRotation(&sub_fmt, &fmt);
582 sub_fmt.i_chroma = subpicture_chromas[0];
583 SetRGBMask(&sub_fmt);
584 video_format_FixRgb(&sub_fmt);
585 sys->p_sub_window = AndroidWindow_New(vd, &sub_fmt, AWindow_Subtitles);
586 if (sys->p_sub_window) {
588 FixSubtitleFormat(sys);
589 sys->i_sub_last_order = -1;
591 /* Export the subpicture capability of this vout. */
592 vd->info.subpicture_chromas = subpicture_chromas;
594 else if (!vd->obj.force && sys->p_window->b_opaque)
596 msg_Warn(vd, "cannot blend subtitles with an opaque surface, "
597 "trying next vout");
598 goto error;
601 *fmtp = fmt;
602 /* Setup vout_display */
603 vd->pool = Pool;
604 vd->prepare = Prepare;
605 vd->display = Display;
606 vd->control = Control;
607 vd->info.is_slow = !sys->p_window->b_opaque;
609 return VLC_SUCCESS;
611 error:
612 Close(vd);
613 return VLC_EGENERIC;
616 static int Open(vout_display_t *vd, const vout_display_cfg_t *cfg,
617 video_format_t *fmtp, vlc_video_context *context)
619 if (fmtp->i_chroma == VLC_CODEC_ANDROID_OPAQUE)
620 return VLC_EGENERIC;
622 /* There are two cases:
623 * 1. the projection_mode is PROJECTION_MODE_RECTANGULAR
624 * 2. gles2 vout failed */
625 fmtp->projection_mode = PROJECTION_MODE_RECTANGULAR;
627 (void) context;
628 return OpenCommon(vd, cfg, fmtp);
631 static int OpenOpaque(vout_display_t *vd, const vout_display_cfg_t *cfg,
632 video_format_t *fmtp, vlc_video_context *context)
634 if (fmtp->i_chroma != VLC_CODEC_ANDROID_OPAQUE)
635 return VLC_EGENERIC;
637 if (!vd->obj.force
638 && (fmtp->projection_mode != PROJECTION_MODE_RECTANGULAR
639 || fmtp->orientation != ORIENT_NORMAL))
641 /* Let the gles2 vout handle orientation and projection */
642 return VLC_EGENERIC;
645 (void) context;
646 return OpenCommon(vd, cfg, fmtp);
649 static void ClearSurface(vout_display_t *vd)
651 vout_display_sys_t *sys = vd->sys;
653 if (sys->p_window->b_opaque)
655 /* Clear the surface to black with OpenGL ES 2 */
656 char *modlist = var_InheritString(sys->embed, "gles2");
657 vlc_gl_t *gl = vlc_gl_Create(sys->embed, VLC_OPENGL_ES2, modlist);
658 free(modlist);
659 if (gl == NULL)
660 return;
662 if (vlc_gl_MakeCurrent(gl))
663 goto end;
665 vlc_gl_Resize(gl, 1, 1);
666 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
667 glClear(GL_COLOR_BUFFER_BIT);
668 vlc_gl_Swap(gl);
670 vlc_gl_ReleaseCurrent(gl);
672 end:
673 vlc_gl_Release(gl);
675 else
677 android_window *p_window = sys->p_window;
678 ANativeWindow_Buffer buf;
680 if (sys->anw->setBuffersGeometry(p_window->p_surface, 1, 1,
681 WINDOW_FORMAT_RGB_565) == 0
682 && sys->anw->winLock(p_window->p_surface, &buf, NULL) == 0)
684 uint16_t *p_bit = buf.bits;
685 p_bit[0] = 0x0000;
686 sys->anw->unlockAndPost(p_window->p_surface);
691 static void Close(vout_display_t *vd)
693 vout_display_sys_t *sys = vd->sys;
695 /* Check if SPU regions have been properly cleared, and clear them if they
696 * were not. */
697 if (sys->b_has_subpictures)
699 SubpicturePrepare(vd, NULL);
700 AndroidWindow_UnlockPicture(sys, sys->p_sub_window, sys->p_sub_pic);
703 if (sys->pool)
704 picture_pool_Release(sys->pool);
706 if (sys->p_window)
708 if (sys->b_displayed)
709 ClearSurface(vd);
710 AndroidWindow_Destroy(vd, sys->p_window);
713 if (sys->p_sub_pic)
714 picture_Release(sys->p_sub_pic);
715 if (sys->p_spu_blend)
716 filter_DeleteBlend(sys->p_spu_blend);
717 free(sys->p_sub_buffer_bounds);
718 if (sys->p_sub_window)
719 AndroidWindow_Destroy(vd, sys->p_sub_window);
721 if (sys->embed)
722 AWindowHandler_setVideoLayout(sys->p_awh, 0, 0, 0, 0, 0, 0);
724 free(sys);
727 static int PoolLockPicture(picture_t *p_pic)
729 picture_sys_t *p_picsys = p_pic->p_sys;
730 vout_display_sys_t *sys = p_picsys->sw.p_vd_sys;
732 if (AndroidWindow_LockPicture(sys, sys->p_window, p_pic) != 0)
733 return -1;
735 return 0;
738 static void PoolUnlockPicture(picture_t *p_pic)
740 picture_sys_t *p_picsys = p_pic->p_sys;
741 vout_display_sys_t *sys = p_picsys->sw.p_vd_sys;
743 AndroidWindow_UnlockPicture(sys, sys->p_window, p_pic);
746 static int PoolLockOpaquePicture(picture_t *p_pic)
748 picture_sys_t *p_picsys = p_pic->p_sys;
750 p_picsys->b_locked = true;
751 return 0;
754 static void PoolUnlockOpaquePicture(picture_t *p_pic)
756 picture_sys_t *p_picsys = p_pic->p_sys;
758 AndroidOpaquePicture_Release(p_picsys, false);
761 static picture_pool_t *PoolAlloc(vout_display_t *vd, unsigned requested_count)
763 vout_display_sys_t *sys = vd->sys;
764 picture_pool_t *pool = NULL;
765 picture_t **pp_pics = NULL;
766 unsigned int i = 0;
768 msg_Dbg(vd, "PoolAlloc: request %d frames", requested_count);
769 if (AndroidWindow_Setup(sys, sys->p_window, requested_count) != 0)
770 goto error;
772 requested_count = sys->p_window->i_pic_count;
773 msg_Dbg(vd, "PoolAlloc: got %d frames", requested_count);
775 UpdateVideoSize(sys, &sys->p_window->fmt);
777 pp_pics = calloc(requested_count, sizeof(picture_t));
779 for (i = 0; i < requested_count; i++)
781 picture_t *p_pic = PictureAlloc(sys, &sys->p_window->fmt,
782 sys->p_window->b_opaque);
783 if (!p_pic)
784 goto error;
786 pp_pics[i] = p_pic;
789 picture_pool_configuration_t pool_cfg;
790 memset(&pool_cfg, 0, sizeof(pool_cfg));
791 pool_cfg.picture_count = requested_count;
792 pool_cfg.picture = pp_pics;
793 if (sys->p_window->b_opaque)
795 pool_cfg.lock = PoolLockOpaquePicture;
796 pool_cfg.unlock = PoolUnlockOpaquePicture;
798 else
800 pool_cfg.lock = PoolLockPicture;
801 pool_cfg.unlock = PoolUnlockPicture;
803 pool = picture_pool_NewExtended(&pool_cfg);
805 error:
806 if (!pool && pp_pics) {
807 for (unsigned j = 0; j < i; j++)
808 picture_Release(pp_pics[j]);
810 free(pp_pics);
811 return pool;
814 static void SubtitleRegionToBounds(subpicture_t *subpicture,
815 ARect *p_out_bounds)
817 if (subpicture) {
818 for (subpicture_region_t *r = subpicture->p_region; r != NULL; r = r->p_next) {
819 ARect new_bounds;
821 new_bounds.left = r->i_x;
822 new_bounds.top = r->i_y;
823 if (new_bounds.left < 0)
824 new_bounds.left = 0;
825 if (new_bounds.top < 0)
826 new_bounds.top = 0;
827 new_bounds.right = r->fmt.i_visible_width + r->i_x;
828 new_bounds.bottom = r->fmt.i_visible_height + r->i_y;
829 if (r == &subpicture->p_region[0])
830 *p_out_bounds = new_bounds;
831 else {
832 if (p_out_bounds->left > new_bounds.left)
833 p_out_bounds->left = new_bounds.left;
834 if (p_out_bounds->right < new_bounds.right)
835 p_out_bounds->right = new_bounds.right;
836 if (p_out_bounds->top > new_bounds.top)
837 p_out_bounds->top = new_bounds.top;
838 if (p_out_bounds->bottom < new_bounds.bottom)
839 p_out_bounds->bottom = new_bounds.bottom;
842 } else {
843 p_out_bounds->left = p_out_bounds->top = 0;
844 p_out_bounds->right = p_out_bounds->bottom = 0;
848 static void SubtitleGetDirtyBounds(vout_display_t *vd,
849 subpicture_t *subpicture,
850 ARect *p_out_bounds)
852 vout_display_sys_t *sys = vd->sys;
853 int i = 0;
854 bool b_found = false;
856 /* Try to find last bounds set by current locked buffer.
857 * Indeed, even if we can lock only one buffer at a time, differents
858 * buffers can be locked. This functions will find the last bounds set by
859 * the current buffer. */
860 if (sys->p_sub_buffer_bounds) {
861 for (; sys->p_sub_buffer_bounds[i].p_pixels != NULL; ++i) {
862 buffer_bounds *p_bb = &sys->p_sub_buffer_bounds[i];
863 if (p_bb->p_pixels == sys->p_sub_pic->p[0].p_pixels) {
864 *p_out_bounds = p_bb->bounds;
865 b_found = true;
866 break;
871 if (!b_found
872 || p_out_bounds->left < 0
873 || p_out_bounds->right < 0
874 || (unsigned int) p_out_bounds->right > sys->p_sub_pic->format.i_width
875 || p_out_bounds->bottom < 0
876 || p_out_bounds->top < 0
877 || (unsigned int) p_out_bounds->top > sys->p_sub_pic->format.i_height)
879 /* default is full picture */
880 p_out_bounds->left = 0;
881 p_out_bounds->top = 0;
882 p_out_bounds->right = sys->p_sub_pic->format.i_width;
883 p_out_bounds->bottom = sys->p_sub_pic->format.i_height;
886 /* buffer not found, add it to the array */
887 if (!sys->p_sub_buffer_bounds
888 || sys->p_sub_buffer_bounds[i].p_pixels == NULL) {
889 buffer_bounds *p_bb = realloc(sys->p_sub_buffer_bounds,
890 (i + 2) * sizeof(buffer_bounds));
891 if (p_bb) {
892 sys->p_sub_buffer_bounds = p_bb;
893 sys->p_sub_buffer_bounds[i].p_pixels = sys->p_sub_pic->p[0].p_pixels;
894 sys->p_sub_buffer_bounds[i+1].p_pixels = NULL;
898 /* set buffer bounds */
899 if (sys->p_sub_buffer_bounds
900 && sys->p_sub_buffer_bounds[i].p_pixels != NULL)
901 SubtitleRegionToBounds(subpicture, &sys->p_sub_buffer_bounds[i].bounds);
904 static void SubpicturePrepare(vout_display_t *vd, subpicture_t *subpicture)
906 vout_display_sys_t *sys = vd->sys;
907 ARect memset_bounds;
909 SubtitleRegionToBounds(subpicture, &memset_bounds);
911 if( subpicture )
913 if( subpicture->i_order == sys->i_sub_last_order
914 && memcmp( &memset_bounds, &sys->sub_last_region, sizeof(ARect) ) == 0 )
915 return;
917 sys->i_sub_last_order = subpicture->i_order;
918 sys->sub_last_region = memset_bounds;
921 if (AndroidWindow_LockPicture(sys, sys->p_sub_window, sys->p_sub_pic) != 0)
922 return;
924 /* Clear the subtitles surface. */
925 SubtitleGetDirtyBounds(vd, subpicture, &memset_bounds);
926 const int x_pixels_offset = memset_bounds.left
927 * sys->p_sub_pic->p[0].i_pixel_pitch;
928 const int i_line_size = (memset_bounds.right - memset_bounds.left)
929 * sys->p_sub_pic->p->i_pixel_pitch;
930 for (int y = memset_bounds.top; y < memset_bounds.bottom; y++)
931 memset(&sys->p_sub_pic->p[0].p_pixels[y * sys->p_sub_pic->p[0].i_pitch
932 + x_pixels_offset], 0, i_line_size);
934 if (subpicture)
935 picture_BlendSubpicture(sys->p_sub_pic, sys->p_spu_blend, subpicture);
938 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
940 vout_display_sys_t *sys = vd->sys;
942 if (sys->pool == NULL)
943 sys->pool = PoolAlloc(vd, requested_count);
944 return sys->pool;
947 static void Prepare(vout_display_t *vd, picture_t *picture,
948 subpicture_t *subpicture, vlc_tick_t date)
950 vout_display_sys_t *sys = vd->sys;
951 VLC_UNUSED(picture);
953 if (subpicture && sys->p_sub_window) {
954 if (sys->b_sub_invalid) {
955 sys->b_sub_invalid = false;
956 if (sys->p_sub_pic) {
957 picture_Release(sys->p_sub_pic);
958 sys->p_sub_pic = NULL;
960 if (sys->p_spu_blend) {
961 filter_DeleteBlend(sys->p_spu_blend);
962 sys->p_spu_blend = NULL;
964 free(sys->p_sub_buffer_bounds);
965 sys->p_sub_buffer_bounds = NULL;
968 if (!sys->p_sub_pic
969 && AndroidWindow_Setup(sys, sys->p_sub_window, 1) == 0)
970 sys->p_sub_pic = PictureAlloc(sys, &sys->p_sub_window->fmt, false);
971 if (!sys->p_spu_blend && sys->p_sub_pic)
972 sys->p_spu_blend = filter_NewBlend(VLC_OBJECT(vd),
973 &sys->p_sub_pic->format);
975 if (sys->p_sub_pic && sys->p_spu_blend)
976 sys->b_has_subpictures = true;
978 /* As long as no subpicture was received, do not call
979 SubpictureDisplay since JNI calls and clearing the subtitles
980 surface are expensive operations. */
981 if (sys->b_has_subpictures)
983 SubpicturePrepare(vd, subpicture);
984 if (!subpicture)
986 /* The surface has been cleared and there is no new
987 subpicture to upload, do not clear again until a new
988 subpicture is received. */
989 sys->b_has_subpictures = false;
992 if (sys->p_window->b_opaque
993 && AndroidOpaquePicture_CanReleaseAtTime(picture->p_sys))
995 vlc_tick_t now = vlc_tick_now();
996 if (date > now)
998 if (date - now <= VLC_TICK_FROM_SEC(1))
999 AndroidOpaquePicture_ReleaseAtTime(picture->p_sys, date);
1000 else /* The picture will be displayed from the Display callback */
1001 msg_Warn(vd, "picture way too early to release at time");
1006 static void Display(vout_display_t *vd, picture_t *picture)
1008 vout_display_sys_t *sys = vd->sys;
1009 VLC_UNUSED(picture);
1011 if (sys->p_window->b_opaque)
1012 AndroidOpaquePicture_Release(picture->p_sys, true);
1013 else
1014 AndroidWindow_UnlockPicture(sys, sys->p_window, picture);
1016 if (sys->p_sub_pic)
1017 AndroidWindow_UnlockPicture(sys, sys->p_sub_window, sys->p_sub_pic);
1019 sys->b_displayed = true;
1022 static void CopySourceAspect(video_format_t *p_dest,
1023 const video_format_t *p_src)
1025 p_dest->i_sar_num = p_src->i_sar_num;
1026 p_dest->i_sar_den = p_src->i_sar_den;
1029 static int Control(vout_display_t *vd, int query, va_list args)
1031 vout_display_sys_t *sys = vd->sys;
1033 switch (query) {
1034 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
1035 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
1037 msg_Dbg(vd, "change source crop/aspect");
1039 if (query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
1040 video_format_CopyCrop(&sys->p_window->fmt, &vd->source);
1041 } else
1042 CopySourceAspect(&sys->p_window->fmt, &vd->source);
1044 UpdateVideoSize(sys, &sys->p_window->fmt);
1045 FixSubtitleFormat(sys);
1046 return VLC_SUCCESS;
1048 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
1050 const vout_display_cfg_t *cfg = va_arg(args, const vout_display_cfg_t *);
1052 sys->i_display_width = cfg->display.width;
1053 sys->i_display_height = cfg->display.height;
1054 msg_Dbg(vd, "change display size: %dx%d", sys->i_display_width,
1055 sys->i_display_height);
1056 FixSubtitleFormat(sys);
1057 return VLC_SUCCESS;
1059 case VOUT_DISPLAY_RESET_PICTURES:
1060 vlc_assert_unreachable();
1061 default:
1062 msg_Warn(vd, "Unknown request in android-display: %d", query);
1063 case VOUT_DISPLAY_CHANGE_ZOOM:
1064 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
1065 return VLC_EGENERIC;