]> git.sesse.net Git - vlc/blob - modules/hw/mmal/vout.c
vout: remove no longer used display size event parameter
[vlc] / modules / hw / mmal / vout.c
1 /*****************************************************************************
2  * mmal.c: MMAL-based vout plugin for Raspberry Pi
3  *****************************************************************************
4  * Copyright © 2014 jusst technologies GmbH
5  * $Id$
6  *
7  * Authors: Dennis Hamester <dennis.hamester@gmail.com>
8  *          Julian Scheel <julian@jusst.de>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include <math.h>
30
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_threads.h>
34 #include <vlc_vout_display.h>
35
36 #include "mmal_picture.h"
37
38 #include <bcm_host.h>
39 #include <interface/mmal/mmal.h>
40 #include <interface/mmal/util/mmal_util.h>
41 #include <interface/mmal/util/mmal_default_components.h>
42 #include <interface/vmcs_host/vc_tvservice.h>
43 #include <interface/vmcs_host/vc_dispmanx.h>
44
45 #define MAX_BUFFERS_IN_TRANSIT 2
46 #define VC_TV_MAX_MODE_IDS 127
47
48 #define MMAL_LAYER_NAME "mmal-layer"
49 #define MMAL_LAYER_TEXT N_("VideoCore layer where the video is displayed.")
50 #define MMAL_LAYER_LONGTEXT N_("VideoCore layer where the video is displayed. Subpictures are displayed directly above and a black background directly below.")
51
52 #define MMAL_ADJUST_REFRESHRATE_NAME "mmal-adjust-refreshrate"
53 #define MMAL_ADJUST_REFRESHRATE_TEXT N_("Adjust HDMI refresh rate to the video.")
54 #define MMAL_ADJUST_REFRESHRATE_LONGTEXT N_("Adjust HDMI refresh rate to the video.")
55
56 #define MMAL_NATIVE_INTERLACED "mmal-native-interlaced"
57 #define MMAL_NATIVE_INTERLACE_TEXT N_("Force interlaced video mode.")
58 #define MMAL_NATIVE_INTERLACE_LONGTEXT N_("Force the HDMI output into an " \
59         "interlaced video mode for interlaced video content.")
60
61 /* Ideal rendering phase target is at rough 25% of frame duration */
62 #define PHASE_OFFSET_TARGET ((double)0.25)
63 #define PHASE_CHECK_INTERVAL 100
64
65 static int Open(vlc_object_t *);
66 static void Close(vlc_object_t *);
67
68 vlc_module_begin()
69     set_shortname(N_("MMAL vout"))
70     set_description(N_("MMAL-based vout plugin for Raspberry Pi"))
71     set_capability("vout display", 90)
72     add_shortcut("mmal_vout")
73     add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false)
74     add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT,
75                     MMAL_ADJUST_REFRESHRATE_LONGTEXT, false)
76     add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT,
77                     MMAL_NATIVE_INTERLACE_LONGTEXT, false)
78     set_callbacks(Open, Close)
79 vlc_module_end()
80
81 struct dmx_region_t {
82     struct dmx_region_t *next;
83     picture_t *picture;
84     VC_RECT_T bmp_rect;
85     VC_RECT_T src_rect;
86     VC_RECT_T dst_rect;
87     VC_DISPMANX_ALPHA_T alpha;
88     DISPMANX_ELEMENT_HANDLE_T element;
89     DISPMANX_RESOURCE_HANDLE_T resource;
90     int32_t pos_x;
91     int32_t pos_y;
92 };
93
94 struct vout_display_sys_t {
95     vlc_cond_t buffer_cond;
96     vlc_mutex_t buffer_mutex;
97     vlc_mutex_t manage_mutex;
98
99     plane_t planes[3];
100     picture_t **pictures;
101     picture_pool_t *picture_pool;
102
103     MMAL_COMPONENT_T *component;
104     MMAL_PORT_T *input;
105     MMAL_POOL_T *pool;
106     struct dmx_region_t *dmx_region;
107     int i_planes;
108
109     uint32_t buffer_size;
110     int buffers_in_transit;
111     unsigned num_buffers;
112
113     DISPMANX_DISPLAY_HANDLE_T dmx_handle;
114     DISPMANX_ELEMENT_HANDLE_T bkg_element;
115     DISPMANX_RESOURCE_HANDLE_T bkg_resource;
116     unsigned display_width;
117     unsigned display_height;
118
119     int i_frame_rate_base;
120     int i_frame_rate;
121
122     int next_phase_check;
123     int phase_offset;
124     int layer;
125
126     bool need_configure_display;
127     bool adjust_refresh_rate;
128     bool native_interlaced;
129     bool b_top_field_first;
130     bool b_progressive;
131     bool opaque;
132 };
133
134 static const vlc_fourcc_t subpicture_chromas[] = {
135     VLC_CODEC_RGBA,
136     0
137 };
138
139 /* Utility functions */
140 static inline uint32_t align(uint32_t x, uint32_t y);
141 static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
142                 const video_format_t *fmt);
143
144 /* VLC vout display callbacks */
145 static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count);
146 static void vd_prepare(vout_display_t *vd, picture_t *picture,
147                 subpicture_t *subpicture);
148 static void vd_display(vout_display_t *vd, picture_t *picture,
149                 subpicture_t *subpicture);
150 static int vd_control(vout_display_t *vd, int query, va_list args);
151 static void vd_manage(vout_display_t *vd);
152
153 /* MMAL callbacks */
154 static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
155 static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
156
157 /* TV service */
158 static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height);
159 static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1,
160                 uint32_t param2);
161 static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt);
162 static int set_latency_target(vout_display_t *vd, bool enable);
163
164 /* DispManX */
165 static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture);
166 static void close_dmx(vout_display_t *vd);
167 static struct dmx_region_t *dmx_region_new(vout_display_t *vd,
168                 DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region);
169 static void dmx_region_update(struct dmx_region_t *dmx_region,
170                 DISPMANX_UPDATE_HANDLE_T update, picture_t *picture);
171 static void dmx_region_delete(struct dmx_region_t *dmx_region,
172                 DISPMANX_UPDATE_HANDLE_T update);
173 static void show_background(vout_display_t *vd, bool enable);
174 static void maintain_phase_sync(vout_display_t *vd);
175
176 static int Open(vlc_object_t *object)
177 {
178     vout_display_t *vd = (vout_display_t *)object;
179     vout_display_sys_t *sys;
180     uint32_t buffer_pitch, buffer_height;
181     vout_display_place_t place;
182     MMAL_DISPLAYREGION_T display_region;
183     uint32_t offsets[3];
184     MMAL_STATUS_T status;
185     int ret = VLC_SUCCESS;
186     unsigned i;
187
188     sys = calloc(1, sizeof(struct vout_display_sys_t));
189     if (!sys)
190         return VLC_ENOMEM;
191     vd->sys = sys;
192
193     sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME);
194     bcm_host_init();
195
196     vd->info.has_hide_mouse = true;
197     sys->opaque = vd->fmt.i_chroma == VLC_CODEC_MMAL_OPAQUE;
198
199     status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component);
200     if (status != MMAL_SUCCESS) {
201         msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
202                         MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status));
203         ret = VLC_EGENERIC;
204         goto out;
205     }
206
207     sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
208     status = mmal_port_enable(sys->component->control, control_port_cb);
209     if (status != MMAL_SUCCESS) {
210         msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)",
211                         sys->component->control->name, status, mmal_status_to_string(status));
212         ret = VLC_EGENERIC;
213         goto out;
214     }
215
216     sys->input = sys->component->input[0];
217     sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
218
219     if (sys->opaque) {
220         sys->input->format->encoding = MMAL_ENCODING_OPAQUE;
221         sys->i_planes = 1;
222         sys->buffer_size = sys->input->buffer_size_recommended;
223     } else {
224         sys->input->format->encoding = MMAL_ENCODING_I420;
225         vd->fmt.i_chroma = VLC_CODEC_I420;
226         buffer_pitch = align(vd->fmt.i_width, 32);
227         buffer_height = align(vd->fmt.i_height, 16);
228         sys->i_planes = 3;
229         sys->buffer_size = 3 * buffer_pitch * buffer_height / 2;
230     }
231
232     sys->input->format->es->video.width = vd->fmt.i_width;
233     sys->input->format->es->video.height = vd->fmt.i_height;
234     sys->input->format->es->video.crop.x = 0;
235     sys->input->format->es->video.crop.y = 0;
236     sys->input->format->es->video.crop.width = vd->fmt.i_width;
237     sys->input->format->es->video.crop.height = vd->fmt.i_height;
238     sys->input->format->es->video.par.num = vd->source.i_sar_num;
239     sys->input->format->es->video.par.den = vd->source.i_sar_den;
240
241     status = mmal_port_format_commit(sys->input);
242     if (status != MMAL_SUCCESS) {
243         msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
244                         sys->input->name, status, mmal_status_to_string(status));
245         ret = VLC_EGENERIC;
246         goto out;
247     }
248     sys->input->buffer_size = sys->input->buffer_size_recommended;
249
250     vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
251     display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
252     display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
253     display_region.fullscreen = MMAL_FALSE;
254     display_region.src_rect.x = vd->fmt.i_x_offset;
255     display_region.src_rect.y = vd->fmt.i_y_offset;
256     display_region.src_rect.width = vd->fmt.i_visible_width;
257     display_region.src_rect.height = vd->fmt.i_visible_height;
258     display_region.dest_rect.x = place.x;
259     display_region.dest_rect.y = place.y;
260     display_region.dest_rect.width = place.width;
261     display_region.dest_rect.height = place.height;
262     display_region.layer = sys->layer;
263     display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT |
264             MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER;
265     status = mmal_port_parameter_set(sys->input, &display_region.hdr);
266     if (status != MMAL_SUCCESS) {
267         msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)",
268                         status, mmal_status_to_string(status));
269         ret = VLC_EGENERIC;
270         goto out;
271     }
272
273     offsets[0] = 0;
274     for (i = 0; i < sys->i_planes; ++i) {
275         sys->planes[i].i_lines = buffer_height;
276         sys->planes[i].i_pitch = buffer_pitch;
277         sys->planes[i].i_visible_lines = vd->fmt.i_visible_height;
278         sys->planes[i].i_visible_pitch = vd->fmt.i_visible_width;
279
280         if (i > 0) {
281             offsets[i] = offsets[i - 1] + sys->planes[i - 1].i_pitch * sys->planes[i - 1].i_lines;
282             sys->planes[i].i_lines /= 2;
283             sys->planes[i].i_pitch /= 2;
284             sys->planes[i].i_visible_lines /= 2;
285             sys->planes[i].i_visible_pitch /= 2;
286         }
287
288         sys->planes[i].p_pixels = (uint8_t *)offsets[i];
289     }
290
291     vlc_mutex_init(&sys->buffer_mutex);
292     vlc_cond_init(&sys->buffer_cond);
293     vlc_mutex_init(&sys->manage_mutex);
294
295     vd->pool = vd_pool;
296     vd->prepare = vd_prepare;
297     vd->display = vd_display;
298     vd->control = vd_control;
299     vd->manage = vd_manage;
300
301     vc_tv_register_callback(tvservice_cb, vd);
302
303     if (query_resolution(vd, &sys->display_width, &sys->display_height) >= 0) {
304         vout_display_SendEventDisplaySize(vd, sys->display_width, sys->display_height);
305     } else {
306         sys->display_width = vd->cfg->display.width;
307         sys->display_height = vd->cfg->display.height;
308     }
309
310     sys->dmx_handle = vc_dispmanx_display_open(0);
311     vd->info.subpicture_chromas = subpicture_chromas;
312
313 out:
314     if (ret != VLC_SUCCESS)
315         Close(object);
316
317     return ret;
318 }
319
320 static void Close(vlc_object_t *object)
321 {
322     vout_display_t *vd = (vout_display_t *)object;
323     vout_display_sys_t *sys = vd->sys;
324     char response[20]; /* answer is hvs_update_fields=%1d */
325     unsigned i;
326
327     vc_tv_unregister_callback_full(tvservice_cb, vd);
328
329     if (sys->dmx_handle)
330         close_dmx(vd);
331
332     if (sys->component && sys->component->control->is_enabled)
333         mmal_port_disable(sys->component->control);
334
335     if (sys->input && sys->input->is_enabled)
336         mmal_port_disable(sys->input);
337
338     if (sys->component && sys->component->is_enabled)
339         mmal_component_disable(sys->component);
340
341     if (sys->pool)
342         mmal_pool_destroy(sys->pool);
343
344     if (sys->component)
345         mmal_component_release(sys->component);
346
347     if (sys->picture_pool)
348         picture_pool_Delete(sys->picture_pool);
349     else
350         for (i = 0; i < sys->num_buffers; ++i)
351             if (sys->pictures[i])
352                 picture_Release(sys->pictures[i]);
353
354     vlc_mutex_destroy(&sys->buffer_mutex);
355     vlc_cond_destroy(&sys->buffer_cond);
356     vlc_mutex_destroy(&sys->manage_mutex);
357
358     if (sys->native_interlaced) {
359         if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 ||
360                 response[18] != '0')
361             msg_Warn(vd, "Could not reset hvs field mode");
362     }
363
364     free(sys->pictures);
365     free(sys);
366
367     bcm_host_deinit();
368 }
369
370 static inline uint32_t align(uint32_t x, uint32_t y) {
371     uint32_t mod = x % y;
372     if (mod == 0)
373         return x;
374     else
375         return x + y - mod;
376 }
377
378 static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
379                 const video_format_t *fmt)
380 {
381     vout_display_sys_t *sys = vd->sys;
382     vout_display_place_t place;
383     MMAL_DISPLAYREGION_T display_region;
384     MMAL_STATUS_T status;
385
386     if (!cfg && !fmt)
387         return -EINVAL;
388
389     if (fmt) {
390         sys->input->format->es->video.par.num = fmt->i_sar_num;
391         sys->input->format->es->video.par.den = fmt->i_sar_den;
392
393         status = mmal_port_format_commit(sys->input);
394         if (status != MMAL_SUCCESS) {
395             msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
396                             sys->input->name, status, mmal_status_to_string(status));
397             return -EINVAL;
398         }
399     } else {
400         fmt = &vd->source;
401     }
402
403     if (!cfg)
404         cfg = vd->cfg;
405
406     vout_display_PlacePicture(&place, fmt, cfg, false);
407
408     display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
409     display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
410     display_region.fullscreen = MMAL_FALSE;
411     display_region.src_rect.x = fmt->i_x_offset;
412     display_region.src_rect.y = fmt->i_y_offset;
413     display_region.src_rect.width = fmt->i_visible_width;
414     display_region.src_rect.height = fmt->i_visible_height;
415     display_region.dest_rect.x = place.x;
416     display_region.dest_rect.y = place.y;
417     display_region.dest_rect.width = place.width;
418     display_region.dest_rect.height = place.height;
419     display_region.layer = sys->layer;
420     display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT |
421             MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER;
422     status = mmal_port_parameter_set(sys->input, &display_region.hdr);
423     if (status != MMAL_SUCCESS) {
424         msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)",
425                         status, mmal_status_to_string(status));
426         return -EINVAL;
427     }
428
429     show_background(vd, cfg->is_fullscreen);
430     sys->adjust_refresh_rate = var_InheritBool(vd, MMAL_ADJUST_REFRESHRATE_NAME);
431     sys->native_interlaced = var_InheritBool(vd, MMAL_NATIVE_INTERLACED);
432     if (sys->adjust_refresh_rate) {
433         adjust_refresh_rate(vd, fmt);
434         set_latency_target(vd, true);
435     }
436
437     return 0;
438 }
439
440 static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count)
441 {
442     vout_display_sys_t *sys = vd->sys;
443     picture_resource_t picture_res;
444     picture_pool_configuration_t picture_pool_cfg;
445     video_format_t fmt = vd->fmt;
446     MMAL_STATUS_T status;
447     unsigned i;
448
449     if (sys->picture_pool) {
450         if (sys->num_buffers < count)
451             msg_Warn(vd, "Picture pool with %u pictures requested, but we already have one with %u pictures",
452                             count, sys->num_buffers);
453
454         goto out;
455     }
456
457     if (sys->opaque) {
458         if (count <= NUM_ACTUAL_OPAQUE_BUFFERS)
459             count = NUM_ACTUAL_OPAQUE_BUFFERS;
460     }
461
462     if (count < sys->input->buffer_num_recommended)
463         count = sys->input->buffer_num_recommended;
464
465 #ifndef NDEBUG
466     msg_Dbg(vd, "Creating picture pool with %u pictures", count);
467 #endif
468
469     sys->input->buffer_num = count;
470     status = mmal_port_enable(sys->input, input_port_cb);
471     if (status != MMAL_SUCCESS) {
472         msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)",
473                         sys->input->name, status, mmal_status_to_string(status));
474         goto out;
475     }
476
477     status = mmal_component_enable(sys->component);
478     if (status != MMAL_SUCCESS) {
479         msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)",
480                         sys->component->name, status, mmal_status_to_string(status));
481         goto out;
482     }
483
484     sys->num_buffers = count;
485     sys->pool = mmal_pool_create(sys->num_buffers, sys->input->buffer_size);
486     if (!sys->pool) {
487         msg_Err(vd, "Failed to create MMAL pool for %u buffers of size %"PRIu32,
488                         count, sys->input->buffer_size);
489         goto out;
490     }
491
492     memset(&picture_res, 0, sizeof(picture_resource_t));
493     sys->pictures = calloc(sys->num_buffers, sizeof(picture_t *));
494     for (i = 0; i < sys->num_buffers; ++i) {
495         picture_res.p_sys = calloc(1, sizeof(picture_sys_t));
496         picture_res.p_sys->owner = (vlc_object_t *)vd;
497         picture_res.p_sys->queue = sys->pool->queue;
498
499         sys->pictures[i] = picture_NewFromResource(&fmt, &picture_res);
500         if (!sys->pictures[i]) {
501             msg_Err(vd, "Failed to create picture");
502             free(picture_res.p_sys);
503             goto out;
504         }
505
506         sys->pictures[i]->i_planes = sys->i_planes;
507         memcpy(sys->pictures[i]->p, sys->planes, sys->i_planes * sizeof(plane_t));
508     }
509
510     memset(&picture_pool_cfg, 0, sizeof(picture_pool_configuration_t));
511     picture_pool_cfg.picture_count = sys->num_buffers;
512     picture_pool_cfg.picture = sys->pictures;
513     picture_pool_cfg.lock = mmal_picture_lock;
514     picture_pool_cfg.unlock = mmal_picture_unlock;
515
516     sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg);
517     if (!sys->picture_pool) {
518         msg_Err(vd, "Failed to create picture pool");
519         goto out;
520     }
521
522 out:
523     return sys->picture_pool;
524 }
525
526 static void vd_prepare(vout_display_t *vd, picture_t *picture,
527                 subpicture_t *subpicture)
528 {
529     vout_display_sys_t *sys = vd->sys;
530     picture_sys_t *pic_sys = picture->p_sys;
531
532     if (!sys->adjust_refresh_rate || pic_sys->displayed)
533         return;
534
535     /* Apply the required phase_offset to the picture, so that vd_display()
536      * will be called at the corrected time from the core */
537     picture->date += sys->phase_offset;
538 }
539
540 static void vd_display(vout_display_t *vd, picture_t *picture,
541                 subpicture_t *subpicture)
542 {
543     vout_display_sys_t *sys = vd->sys;
544     picture_sys_t *pic_sys = picture->p_sys;
545     MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer;
546     MMAL_STATUS_T status;
547
548     if (picture->format.i_frame_rate != sys->i_frame_rate ||
549         picture->format.i_frame_rate_base != sys->i_frame_rate_base ||
550         picture->b_progressive != sys->b_progressive ||
551         picture->b_top_field_first != sys->b_top_field_first) {
552         sys->b_top_field_first = picture->b_top_field_first;
553         sys->b_progressive = picture->b_progressive;
554         sys->i_frame_rate = picture->format.i_frame_rate;
555         sys->i_frame_rate_base = picture->format.i_frame_rate_base;
556         configure_display(vd, NULL, &picture->format);
557     }
558
559     if (!pic_sys->displayed || !sys->opaque) {
560         buffer->cmd = 0;
561         buffer->length = sys->input->buffer_size;
562
563         vlc_mutex_lock(&sys->buffer_mutex);
564         while (sys->buffers_in_transit >= MAX_BUFFERS_IN_TRANSIT)
565             vlc_cond_wait(&sys->buffer_cond, &sys->buffer_mutex);
566
567         status = mmal_port_send_buffer(sys->input, buffer);
568         if (status == MMAL_SUCCESS)
569             ++sys->buffers_in_transit;
570
571         vlc_mutex_unlock(&sys->buffer_mutex);
572         if (status != MMAL_SUCCESS) {
573             msg_Err(vd, "Failed to send buffer to input port. Frame dropped");
574             picture_Release(picture);
575         }
576
577         pic_sys->displayed = true;
578     } else {
579         picture_Release(picture);
580     }
581
582     display_subpicture(vd, subpicture);
583
584     if (subpicture)
585         subpicture_Delete(subpicture);
586
587     if (sys->next_phase_check == 0 && sys->adjust_refresh_rate)
588         maintain_phase_sync(vd);
589     sys->next_phase_check = (sys->next_phase_check + 1) % PHASE_CHECK_INTERVAL;
590 }
591
592 static int vd_control(vout_display_t *vd, int query, va_list args)
593 {
594     vout_display_sys_t *sys = vd->sys;
595     vout_display_cfg_t cfg;
596     const vout_display_cfg_t *tmp_cfg;
597     const video_format_t *tmp_fmt;
598     int ret = VLC_EGENERIC;
599
600     switch (query) {
601         case VOUT_DISPLAY_HIDE_MOUSE:
602         case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
603             ret = VLC_SUCCESS;
604             break;
605
606         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
607             tmp_cfg = va_arg(args, const vout_display_cfg_t *);
608             vout_display_SendEventDisplaySize(vd, sys->display_width,
609                             sys->display_height);
610             ret = VLC_SUCCESS;
611             break;
612
613         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
614             tmp_cfg = va_arg(args, const vout_display_cfg_t *);
615             if (tmp_cfg->display.width == sys->display_width &&
616                             tmp_cfg->display.height == sys->display_height) {
617                 cfg = *vd->cfg;
618                 cfg.display.width = sys->display_width;
619                 cfg.display.height = sys->display_height;
620                 if (configure_display(vd, &cfg, NULL) >= 0)
621                     ret = VLC_SUCCESS;
622             }
623             break;
624
625         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
626         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
627             tmp_fmt = va_arg(args, const video_format_t *);
628             if (configure_display(vd, NULL, tmp_fmt) >= 0)
629                 ret = VLC_SUCCESS;
630             break;
631
632         case VOUT_DISPLAY_CHANGE_ZOOM:
633         case VOUT_DISPLAY_RESET_PICTURES:
634         case VOUT_DISPLAY_GET_OPENGL:
635             msg_Warn(vd, "Unsupported control query %d", query);
636             break;
637
638         default:
639             msg_Warn(vd, "Unknown control query %d", query);
640             break;
641     }
642
643     return ret;
644 }
645
646 static void vd_manage(vout_display_t *vd)
647 {
648     vout_display_sys_t *sys = vd->sys;
649     unsigned width, height;
650
651     vlc_mutex_lock(&sys->manage_mutex);
652
653     if (sys->need_configure_display) {
654         close_dmx(vd);
655         sys->dmx_handle = vc_dispmanx_display_open(0);
656
657         if (query_resolution(vd, &width, &height) >= 0) {
658             sys->display_width = width;
659             sys->display_height = height;
660             vout_display_SendEventDisplaySize(vd, width, height);
661         }
662
663         sys->need_configure_display = false;
664     }
665
666     vlc_mutex_unlock(&sys->manage_mutex);
667 }
668
669 static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
670 {
671     vout_display_t *vd = (vout_display_t *)port->userdata;
672     MMAL_STATUS_T status;
673
674     if (buffer->cmd == MMAL_EVENT_ERROR) {
675         status = *(uint32_t *)buffer->data;
676         msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status));
677     }
678
679     mmal_buffer_header_release(buffer);
680 }
681
682 static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
683 {
684     vout_display_t *vd = (vout_display_t *)port->userdata;
685     vout_display_sys_t *sys = vd->sys;
686     picture_t *picture = (picture_t *)buffer->user_data;
687
688     vlc_mutex_lock(&sys->buffer_mutex);
689     --sys->buffers_in_transit;
690     vlc_cond_signal(&sys->buffer_cond);
691     vlc_mutex_unlock(&sys->buffer_mutex);
692
693     if (picture)
694         picture_Release(picture);
695 }
696
697 static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height)
698 {
699     TV_DISPLAY_STATE_T display_state;
700     int ret = 0;
701
702     if (vc_tv_get_display_state(&display_state) == 0) {
703         if (display_state.state & 0xFF) {
704             *width = display_state.display.hdmi.width;
705             *height = display_state.display.hdmi.height;
706         } else if (display_state.state & 0xFF00) {
707             *width = display_state.display.sdtv.width;
708             *height = display_state.display.sdtv.height;
709         } else {
710             msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state);
711             ret = -1;
712         }
713     } else {
714         msg_Warn(vd, "Failed to query display resolution");
715         ret = -1;
716     }
717
718     return ret;
719 }
720
721 static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, uint32_t param2)
722 {
723     VLC_UNUSED(reason);
724     VLC_UNUSED(param1);
725     VLC_UNUSED(param2);
726
727     vout_display_t *vd = (vout_display_t *)callback_data;
728     vout_display_sys_t *sys = vd->sys;
729
730     vlc_mutex_lock(&sys->manage_mutex);
731     sys->need_configure_display = true;
732     vlc_mutex_unlock(&sys->manage_mutex);
733 }
734
735 static int set_latency_target(vout_display_t *vd, bool enable)
736 {
737     vout_display_sys_t *sys = vd->sys;
738     MMAL_STATUS_T status;
739
740     MMAL_PARAMETER_AUDIO_LATENCY_TARGET_T latency_target = {
741         .hdr = { MMAL_PARAMETER_AUDIO_LATENCY_TARGET, sizeof(latency_target) },
742         .enable = enable ? MMAL_TRUE : MMAL_FALSE,
743         .filter = 2,
744         .target = 4000,
745         .shift = 3,
746         .speed_factor = -135,
747         .inter_factor = 500,
748         .adj_cap = 20
749     };
750
751     status = mmal_port_parameter_set(sys->input, &latency_target.hdr);
752     if (status != MMAL_SUCCESS) {
753         msg_Err(vd, "Failed to configure latency target on input port %s (status=%"PRIx32" %s)",
754                         sys->input->name, status, mmal_status_to_string(status));
755         return VLC_EGENERIC;
756     }
757
758     return VLC_SUCCESS;
759 }
760
761 static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt)
762 {
763     vout_display_sys_t *sys = vd->sys;
764     TV_DISPLAY_STATE_T display_state;
765     TV_SUPPORTED_MODE_NEW_T supported_modes[VC_TV_MAX_MODE_IDS];
766     char response[20]; /* answer is hvs_update_fields=%1d */
767     int num_modes;
768     double frame_rate = (double)fmt->i_frame_rate / fmt->i_frame_rate_base;
769     int best_id = -1;
770     double best_score, score;
771     int i;
772
773     vc_tv_get_display_state(&display_state);
774     if(display_state.display.hdmi.mode != HDMI_MODE_OFF) {
775         num_modes = vc_tv_hdmi_get_supported_modes_new(display_state.display.hdmi.group,
776                         supported_modes, VC_TV_MAX_MODE_IDS, NULL, NULL);
777
778         for (i = 0; i < num_modes; ++i) {
779             TV_SUPPORTED_MODE_NEW_T *mode = &supported_modes[i];
780             if (!sys->native_interlaced) {
781                 if (mode->width != display_state.display.hdmi.width ||
782                                 mode->height != display_state.display.hdmi.height ||
783                                 mode->scan_mode == HDMI_INTERLACED)
784                     continue;
785             } else {
786                 if (mode->width != vd->fmt.i_visible_width ||
787                         mode->height != vd->fmt.i_visible_height)
788                     continue;
789                 if (mode->scan_mode != sys->b_progressive ? HDMI_NONINTERLACED : HDMI_INTERLACED)
790                     continue;
791             }
792
793             score = fmod(supported_modes[i].frame_rate, frame_rate);
794             if((best_id < 0) || (score < best_score)) {
795                 best_id = i;
796                 best_score = score;
797             }
798         }
799
800         if((best_id >= 0) && (display_state.display.hdmi.mode != supported_modes[best_id].code)) {
801             msg_Info(vd, "Setting HDMI refresh rate to %"PRIu32,
802                             supported_modes[best_id].frame_rate);
803             vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_HDMI,
804                             supported_modes[best_id].group,
805                             supported_modes[best_id].code);
806         }
807
808         if (sys->native_interlaced &&
809                 supported_modes[best_id].scan_mode == HDMI_INTERLACED) {
810             char hvs_mode = sys->b_top_field_first ? '1' : '2';
811             if (vc_gencmd(response, sizeof(response), "hvs_update_fields %c",
812                     hvs_mode) != 0 || response[18] != hvs_mode)
813                 msg_Warn(vd, "Could not set hvs field mode");
814             else
815                 msg_Info(vd, "Configured hvs field mode for interlaced %s playback",
816                         sys->b_top_field_first ? "tff" : "bff");
817         }
818     }
819 }
820
821 static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture)
822 {
823     vout_display_sys_t *sys = vd->sys;
824     struct dmx_region_t **dmx_region = &sys->dmx_region;
825     struct dmx_region_t *unused_dmx_region;
826     DISPMANX_UPDATE_HANDLE_T update = 0;
827     picture_t *picture;
828     video_format_t *fmt;
829     struct dmx_region_t *dmx_region_next;
830
831     if(subpicture) {
832         subpicture_region_t *region = subpicture->p_region;
833         while(region) {
834             picture = region->p_picture;
835             fmt = &region->fmt;
836
837             if(!*dmx_region) {
838                 if(!update)
839                     update = vc_dispmanx_update_start(10);
840                 *dmx_region = dmx_region_new(vd, update, region);
841             } else if(((*dmx_region)->bmp_rect.width != (int32_t)fmt->i_visible_width) ||
842                     ((*dmx_region)->bmp_rect.height != (int32_t)fmt->i_visible_height) ||
843                     ((*dmx_region)->pos_x != region->i_x) ||
844                     ((*dmx_region)->pos_y != region->i_y) ||
845                     ((*dmx_region)->alpha.opacity != (uint32_t)region->i_alpha)) {
846                 dmx_region_next = (*dmx_region)->next;
847                 if(!update)
848                     update = vc_dispmanx_update_start(10);
849                 dmx_region_delete(*dmx_region, update);
850                 *dmx_region = dmx_region_new(vd, update, region);
851                 (*dmx_region)->next = dmx_region_next;
852             } else if((*dmx_region)->picture != picture) {
853                 if(!update)
854                     update = vc_dispmanx_update_start(10);
855                 dmx_region_update(*dmx_region, update, picture);
856             }
857
858             dmx_region = &(*dmx_region)->next;
859             region = region->p_next;
860         }
861     }
862
863     /* Remove remaining regions */
864     unused_dmx_region = *dmx_region;
865     while(unused_dmx_region) {
866         dmx_region_next = unused_dmx_region->next;
867         if(!update)
868             update = vc_dispmanx_update_start(10);
869         dmx_region_delete(unused_dmx_region, update);
870         unused_dmx_region = dmx_region_next;
871     }
872     *dmx_region = NULL;
873
874     if(update)
875         vc_dispmanx_update_submit_sync(update);
876 }
877
878 static void close_dmx(vout_display_t *vd)
879 {
880     vout_display_sys_t *sys = vd->sys;
881     DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(10);
882     struct dmx_region_t *dmx_region = sys->dmx_region;
883     struct dmx_region_t *dmx_region_next;
884
885     while(dmx_region) {
886         dmx_region_next = dmx_region->next;
887         dmx_region_delete(dmx_region, update);
888         dmx_region = dmx_region_next;
889     }
890
891     vc_dispmanx_update_submit_sync(update);
892     sys->dmx_region = NULL;
893
894     show_background(vd, false);
895
896     vc_dispmanx_display_close(sys->dmx_handle);
897     sys->dmx_handle = DISPMANX_NO_HANDLE;
898 }
899
900 static struct dmx_region_t *dmx_region_new(vout_display_t *vd,
901                 DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region)
902 {
903     vout_display_sys_t *sys = vd->sys;
904     video_format_t *fmt = &region->fmt;
905     struct dmx_region_t *dmx_region = malloc(sizeof(struct dmx_region_t));
906     uint32_t image_handle;
907
908     dmx_region->pos_x = region->i_x;
909     dmx_region->pos_y = region->i_y;
910
911     vc_dispmanx_rect_set(&dmx_region->bmp_rect, 0, 0, fmt->i_visible_width,
912                     fmt->i_visible_height);
913     vc_dispmanx_rect_set(&dmx_region->src_rect, 0, 0, fmt->i_visible_width << 16,
914                     fmt->i_visible_height << 16);
915     vc_dispmanx_rect_set(&dmx_region->dst_rect, region->i_x, region->i_y,
916                     fmt->i_visible_width, fmt->i_visible_height);
917
918     dmx_region->resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32,
919                     dmx_region->bmp_rect.width | (region->p_picture->p[0].i_pitch << 16),
920                     dmx_region->bmp_rect.height | (dmx_region->bmp_rect.height << 16),
921                     &image_handle);
922     vc_dispmanx_resource_write_data(dmx_region->resource, VC_IMAGE_RGBA32,
923                     region->p_picture->p[0].i_pitch,
924                     region->p_picture->p[0].p_pixels, &dmx_region->bmp_rect);
925
926     dmx_region->alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_MIX;
927     dmx_region->alpha.opacity = region->i_alpha;
928     dmx_region->alpha.mask = DISPMANX_NO_HANDLE;
929     dmx_region->element = vc_dispmanx_element_add(update, sys->dmx_handle,
930                     sys->layer + 1, &dmx_region->dst_rect, dmx_region->resource,
931                     &dmx_region->src_rect, DISPMANX_PROTECTION_NONE,
932                     &dmx_region->alpha, NULL, VC_IMAGE_ROT0);
933
934     dmx_region->next = NULL;
935     dmx_region->picture = region->p_picture;
936
937     return dmx_region;
938 }
939
940 static void dmx_region_update(struct dmx_region_t *dmx_region,
941                 DISPMANX_UPDATE_HANDLE_T update, picture_t *picture)
942 {
943     vc_dispmanx_resource_write_data(dmx_region->resource, VC_IMAGE_RGBA32,
944                     picture->p[0].i_pitch, picture->p[0].p_pixels, &dmx_region->bmp_rect);
945     vc_dispmanx_element_change_source(update, dmx_region->element, dmx_region->resource);
946     dmx_region->picture = picture;
947 }
948
949 static void dmx_region_delete(struct dmx_region_t *dmx_region,
950                 DISPMANX_UPDATE_HANDLE_T update)
951 {
952     vc_dispmanx_element_remove(update, dmx_region->element);
953     vc_dispmanx_resource_delete(dmx_region->resource);
954     free(dmx_region);
955 }
956
957 static void maintain_phase_sync(vout_display_t *vd)
958 {
959     MMAL_PARAMETER_VIDEO_RENDER_STATS_T render_stats = {
960         .hdr = { MMAL_PARAMETER_VIDEO_RENDER_STATS, sizeof(render_stats) },
961     };
962     int32_t frame_duration = 1000000 /
963         ((double)vd->source.i_frame_rate /
964         vd->source.i_frame_rate_base);
965     vout_display_sys_t *sys = vd->sys;
966     int32_t phase_offset;
967     MMAL_STATUS_T status;
968
969     status = mmal_port_parameter_get(sys->input, &render_stats.hdr);
970     if (status != MMAL_SUCCESS) {
971         msg_Err(vd, "Failed to read render stats on control port %s (status=%"PRIx32" %s)",
972                         sys->input->name, status, mmal_status_to_string(status));
973         return;
974     }
975
976     if (render_stats.valid) {
977 #ifndef NDEBUG
978         msg_Dbg(vd, "render_stats: match: %u, period: %u ms, phase: %u ms, hvs: %u",
979                 render_stats.match, render_stats.period / 1000, render_stats.phase / 1000,
980                 render_stats.hvs_status);
981 #endif
982
983         if (render_stats.phase > 0.1 * frame_duration &&
984                 render_stats.phase < 0.75 * frame_duration)
985             return;
986
987         phase_offset = frame_duration * PHASE_OFFSET_TARGET - render_stats.phase;
988         if (phase_offset < 0)
989             phase_offset += frame_duration;
990         else
991             phase_offset %= frame_duration;
992
993         sys->phase_offset += phase_offset;
994         sys->phase_offset %= frame_duration;
995         msg_Dbg(vd, "Apply phase offset of %"PRId32" ms (total offset %"PRId32" ms)",
996                 phase_offset / 1000, sys->phase_offset / 1000);
997
998         /* Reset the latency target, so that it does not get confused
999          * by the jump in the offset */
1000         set_latency_target(vd, false);
1001         set_latency_target(vd, true);
1002     }
1003 }
1004
1005 static void show_background(vout_display_t *vd, bool enable)
1006 {
1007     vout_display_sys_t *sys = vd->sys;
1008     uint32_t image_ptr, color = 0xFF000000;
1009     VC_RECT_T dst_rect, src_rect;
1010     DISPMANX_UPDATE_HANDLE_T update;
1011
1012     if (enable && !sys->bkg_element) {
1013         sys->bkg_resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32, 1, 1,
1014                         &image_ptr);
1015         vc_dispmanx_rect_set(&dst_rect, 0, 0, 1, 1);
1016         vc_dispmanx_resource_write_data(sys->bkg_resource, VC_IMAGE_RGBA32,
1017                         sizeof(color), &color, &dst_rect);
1018         vc_dispmanx_rect_set(&src_rect, 0, 0, 1 << 16, 1 << 16);
1019         vc_dispmanx_rect_set(&dst_rect, 0, 0, 0, 0);
1020         update = vc_dispmanx_update_start(0);
1021         sys->bkg_element = vc_dispmanx_element_add(update, sys->dmx_handle,
1022                         sys->layer - 1, &dst_rect, sys->bkg_resource, &src_rect,
1023                         DISPMANX_PROTECTION_NONE, NULL, NULL, VC_IMAGE_ROT0);
1024         vc_dispmanx_update_submit_sync(update);
1025     } else if (!enable && sys->bkg_element) {
1026         update = vc_dispmanx_update_start(0);
1027         vc_dispmanx_element_remove(update, sys->bkg_element);
1028         vc_dispmanx_resource_delete(sys->bkg_resource);
1029         vc_dispmanx_update_submit_sync(update);
1030         sys->bkg_element = DISPMANX_NO_HANDLE;
1031         sys->bkg_resource = DISPMANX_NO_HANDLE;
1032     }
1033 }