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