vout: mmal: use vout_display_cfg_IsWindowed()
[vlc.git] / modules / hw / mmal / vout.c
blob2adeca698a07dc2e38c8ce0e3b6fdfc91ee74b3f
1 /*****************************************************************************
2 * mmal.c: MMAL-based vout plugin for Raspberry Pi
3 *****************************************************************************
4 * Copyright © 2014 jusst technologies GmbH
5 * $Id$
7 * Authors: Dennis Hamester <dennis.hamester@gmail.com>
8 * Julian Scheel <julian@jusst.de>
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.
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.
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 *****************************************************************************/
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <math.h>
30 #include <stdatomic.h>
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_threads.h>
35 #include <vlc_vout_display.h>
37 #include "mmal_picture.h"
39 #include <bcm_host.h>
40 #include <interface/mmal/mmal.h>
41 #include <interface/mmal/util/mmal_util.h>
42 #include <interface/mmal/util/mmal_default_components.h>
43 #include <interface/vmcs_host/vc_tvservice.h>
44 #include <interface/vmcs_host/vc_dispmanx.h>
46 #define MAX_BUFFERS_IN_TRANSIT 1
47 #define VC_TV_MAX_MODE_IDS 127
49 #define MMAL_LAYER_NAME "mmal-layer"
50 #define MMAL_LAYER_TEXT N_("VideoCore layer where the video is displayed.")
51 #define MMAL_LAYER_LONGTEXT N_("VideoCore layer where the video is displayed. Subpictures are displayed directly above and a black background directly below.")
53 #define MMAL_BLANK_BACKGROUND_NAME "mmal-blank-background"
54 #define MMAL_BLANK_BACKGROUND_TEXT N_("Blank screen below video.")
55 #define MMAL_BLANK_BACKGROUND_LONGTEXT N_("Render blank screen below video. " \
56 "Increases VideoCore load.")
58 #define MMAL_ADJUST_REFRESHRATE_NAME "mmal-adjust-refreshrate"
59 #define MMAL_ADJUST_REFRESHRATE_TEXT N_("Adjust HDMI refresh rate to the video.")
60 #define MMAL_ADJUST_REFRESHRATE_LONGTEXT N_("Adjust HDMI refresh rate to the video.")
62 #define MMAL_NATIVE_INTERLACED "mmal-native-interlaced"
63 #define MMAL_NATIVE_INTERLACE_TEXT N_("Force interlaced video mode.")
64 #define MMAL_NATIVE_INTERLACE_LONGTEXT N_("Force the HDMI output into an " \
65 "interlaced video mode for interlaced video content.")
67 /* Ideal rendering phase target is at rough 25% of frame duration */
68 #define PHASE_OFFSET_TARGET ((double)0.25)
69 #define PHASE_CHECK_INTERVAL 100
71 static int Open(vlc_object_t *);
72 static void Close(vlc_object_t *);
74 vlc_module_begin()
75 set_shortname(N_("MMAL vout"))
76 set_description(N_("MMAL-based vout plugin for Raspberry Pi"))
77 set_capability("vout display", 90)
78 add_shortcut("mmal_vout")
79 add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false)
80 add_bool(MMAL_BLANK_BACKGROUND_NAME, true, MMAL_BLANK_BACKGROUND_TEXT,
81 MMAL_BLANK_BACKGROUND_LONGTEXT, true);
82 add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT,
83 MMAL_ADJUST_REFRESHRATE_LONGTEXT, false)
84 add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT,
85 MMAL_NATIVE_INTERLACE_LONGTEXT, false)
86 set_callbacks(Open, Close)
87 vlc_module_end()
89 struct dmx_region_t {
90 struct dmx_region_t *next;
91 picture_t *picture;
92 VC_RECT_T bmp_rect;
93 VC_RECT_T src_rect;
94 VC_RECT_T dst_rect;
95 VC_DISPMANX_ALPHA_T alpha;
96 DISPMANX_ELEMENT_HANDLE_T element;
97 DISPMANX_RESOURCE_HANDLE_T resource;
98 int32_t pos_x;
99 int32_t pos_y;
102 struct vout_display_sys_t {
103 vlc_cond_t buffer_cond;
104 vlc_mutex_t buffer_mutex;
105 vlc_mutex_t manage_mutex;
107 plane_t planes[3]; /* Depending on video format up to 3 planes are used */
108 picture_t **pictures; /* Actual list of alloced pictures passed into picture_pool */
109 picture_pool_t *picture_pool;
110 vout_display_cfg_t last_cfg;
112 MMAL_COMPONENT_T *component;
113 MMAL_PORT_T *input;
114 MMAL_POOL_T *pool; /* mmal buffer headers, used for pushing pictures to component*/
115 struct dmx_region_t *dmx_region;
116 int i_planes; /* Number of actually used planes, 1 for opaque, 3 for i420 */
118 uint32_t buffer_size; /* size of actual mmal buffers */
119 int buffers_in_transit; /* number of buffers currently pushed to mmal component */
120 unsigned num_buffers; /* number of buffers allocated at mmal port */
122 DISPMANX_DISPLAY_HANDLE_T dmx_handle;
123 DISPMANX_ELEMENT_HANDLE_T bkg_element;
124 DISPMANX_RESOURCE_HANDLE_T bkg_resource;
125 unsigned display_width;
126 unsigned display_height;
128 int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */
129 int i_frame_rate;
131 int next_phase_check; /* lowpass for phase check frequency */
132 int phase_offset; /* currently applied offset to presentation time in ns */
133 int layer; /* the dispman layer (z-index) used for video rendering */
135 bool need_configure_display; /* indicates a required display reconfigure to main thread */
136 bool adjust_refresh_rate;
137 bool native_interlaced;
138 bool b_top_field_first; /* cached interlaced settings to detect changes for native mode */
139 bool b_progressive;
140 bool opaque; /* indicated use of opaque picture format (zerocopy) */
143 static const vlc_fourcc_t subpicture_chromas[] = {
144 VLC_CODEC_RGBA,
148 /* Utility functions */
149 static inline uint32_t align(uint32_t x, uint32_t y);
150 static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
151 const video_format_t *fmt);
153 /* VLC vout display callbacks */
154 static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count);
155 static void vd_prepare(vout_display_t *vd, picture_t *picture,
156 subpicture_t *subpicture);
157 static void vd_display(vout_display_t *vd, picture_t *picture);
158 static int vd_control(vout_display_t *vd, int query, va_list args);
159 static void vd_manage(vout_display_t *vd);
161 /* MMAL callbacks */
162 static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
163 static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
165 /* TV service */
166 static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height);
167 static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1,
168 uint32_t param2);
169 static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt);
170 static int set_latency_target(vout_display_t *vd, bool enable);
172 /* DispManX */
173 static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture);
174 static void close_dmx(vout_display_t *vd);
175 static struct dmx_region_t *dmx_region_new(vout_display_t *vd,
176 DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region);
177 static void dmx_region_update(struct dmx_region_t *dmx_region,
178 DISPMANX_UPDATE_HANDLE_T update, picture_t *picture);
179 static void dmx_region_delete(struct dmx_region_t *dmx_region,
180 DISPMANX_UPDATE_HANDLE_T update);
181 static void show_background(vout_display_t *vd, bool enable);
182 static void maintain_phase_sync(vout_display_t *vd);
184 static int Open(vlc_object_t *object)
186 vout_display_t *vd = (vout_display_t *)object;
187 const vout_display_cfg_t *cfg = vd->cfg;
188 vout_display_sys_t *sys;
189 uint32_t buffer_pitch, buffer_height;
190 vout_display_place_t place;
191 MMAL_DISPLAYREGION_T display_region;
192 MMAL_STATUS_T status;
193 int ret = VLC_SUCCESS;
194 unsigned i;
196 if (vout_display_cfg_IsWindowed(cfg))
197 return VLC_EGENERIC;
199 sys = calloc(1, sizeof(struct vout_display_sys_t));
200 if (!sys)
201 return VLC_ENOMEM;
202 vd->sys = sys;
204 sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME);
205 bcm_host_init();
207 sys->opaque = fmt->i_chroma == VLC_CODEC_MMAL_OPAQUE;
209 status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component);
210 if (status != MMAL_SUCCESS) {
211 msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
212 MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status));
213 ret = VLC_EGENERIC;
214 goto out;
217 sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
218 status = mmal_port_enable(sys->component->control, control_port_cb);
219 if (status != MMAL_SUCCESS) {
220 msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)",
221 sys->component->control->name, status, mmal_status_to_string(status));
222 ret = VLC_EGENERIC;
223 goto out;
226 sys->input = sys->component->input[0];
227 sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
229 if (sys->opaque) {
230 sys->input->format->encoding = MMAL_ENCODING_OPAQUE;
231 sys->i_planes = 1;
232 sys->buffer_size = sys->input->buffer_size_recommended;
233 } else {
234 sys->input->format->encoding = MMAL_ENCODING_I420;
235 fmt->i_chroma = VLC_CODEC_I420;
236 buffer_pitch = align(fmt->i_width, 32);
237 buffer_height = align(fmt->i_height, 16);
238 sys->i_planes = 3;
239 sys->buffer_size = 3 * buffer_pitch * buffer_height / 2;
242 sys->input->format->es->video.width = fmt->i_width;
243 sys->input->format->es->video.height = fmt->i_height;
244 sys->input->format->es->video.crop.x = 0;
245 sys->input->format->es->video.crop.y = 0;
246 sys->input->format->es->video.crop.width = fmt->i_width;
247 sys->input->format->es->video.crop.height = fmt->i_height;
248 sys->input->format->es->video.par.num = vd->source.i_sar_num;
249 sys->input->format->es->video.par.den = vd->source.i_sar_den;
250 sys->last_cfg = *cfg;
252 status = mmal_port_format_commit(sys->input);
253 if (status != MMAL_SUCCESS) {
254 msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
255 sys->input->name, status, mmal_status_to_string(status));
256 ret = VLC_EGENERIC;
257 goto out;
259 sys->input->buffer_size = sys->input->buffer_size_recommended;
261 vout_display_PlacePicture(&place, &vd->source, cfg, false);
262 display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
263 display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
264 display_region.fullscreen = MMAL_FALSE;
265 display_region.src_rect.x = fmt->i_x_offset;
266 display_region.src_rect.y = fmt->i_y_offset;
267 display_region.src_rect.width = fmt->i_visible_width;
268 display_region.src_rect.height = fmt->i_visible_height;
269 display_region.dest_rect.x = place.x;
270 display_region.dest_rect.y = place.y;
271 display_region.dest_rect.width = place.width;
272 display_region.dest_rect.height = place.height;
273 display_region.layer = sys->layer;
274 display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT |
275 MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER;
276 status = mmal_port_parameter_set(sys->input, &display_region.hdr);
277 if (status != MMAL_SUCCESS) {
278 msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)",
279 status, mmal_status_to_string(status));
280 ret = VLC_EGENERIC;
281 goto out;
284 for (i = 0; i < sys->i_planes; ++i) {
285 sys->planes[i].i_lines = buffer_height;
286 sys->planes[i].i_pitch = buffer_pitch;
287 sys->planes[i].i_visible_lines = fmt->i_visible_height;
288 sys->planes[i].i_visible_pitch = fmt->i_visible_width;
290 if (i > 0) {
291 sys->planes[i].i_lines /= 2;
292 sys->planes[i].i_pitch /= 2;
293 sys->planes[i].i_visible_lines /= 2;
294 sys->planes[i].i_visible_pitch /= 2;
298 vlc_mutex_init(&sys->buffer_mutex);
299 vlc_cond_init(&sys->buffer_cond);
300 vlc_mutex_init(&sys->manage_mutex);
302 vd->pool = vd_pool;
303 vd->prepare = vd_prepare;
304 vd->display = vd_display;
305 vd->control = vd_control;
307 vc_tv_register_callback(tvservice_cb, vd);
309 if (query_resolution(vd, &sys->display_width, &sys->display_height) >= 0) {
310 vout_window_ReportSize(cfg->window,
311 sys->display_width, sys->display_height);
312 } else {
313 sys->display_width = cfg->display.width;
314 sys->display_height = cfg->display.height;
317 sys->dmx_handle = vc_dispmanx_display_open(0);
318 vd->info.subpicture_chromas = subpicture_chromas;
320 out:
321 if (ret != VLC_SUCCESS)
322 Close(object);
324 return ret;
327 static void Close(vlc_object_t *object)
329 vout_display_t *vd = (vout_display_t *)object;
330 vout_display_sys_t *sys = vd->sys;
331 char response[20]; /* answer is hvs_update_fields=%1d */
332 unsigned i;
334 vc_tv_unregister_callback_full(tvservice_cb, vd);
336 if (sys->dmx_handle)
337 close_dmx(vd);
339 if (sys->component && sys->component->control->is_enabled)
340 mmal_port_disable(sys->component->control);
342 if (sys->input && sys->input->is_enabled)
343 mmal_port_disable(sys->input);
345 if (sys->component && sys->component->is_enabled)
346 mmal_component_disable(sys->component);
348 if (sys->pool)
349 mmal_port_pool_destroy(sys->input, sys->pool);
351 if (sys->component)
352 mmal_component_release(sys->component);
354 if (sys->picture_pool)
355 picture_pool_Release(sys->picture_pool);
356 else
357 for (i = 0; i < sys->num_buffers; ++i)
358 if (sys->pictures[i]) {
359 mmal_buffer_header_release(sys->pictures[i]->p_sys->buffer);
360 picture_Release(sys->pictures[i]);
363 vlc_mutex_destroy(&sys->buffer_mutex);
364 vlc_cond_destroy(&sys->buffer_cond);
365 vlc_mutex_destroy(&sys->manage_mutex);
367 if (sys->native_interlaced) {
368 if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 ||
369 response[18] != '0')
370 msg_Warn(vd, "Could not reset hvs field mode");
373 free(sys->pictures);
374 free(sys);
376 bcm_host_deinit();
379 static inline uint32_t align(uint32_t x, uint32_t y) {
380 uint32_t mod = x % y;
381 if (mod == 0)
382 return x;
383 else
384 return x + y - mod;
387 static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
388 const video_format_t *fmt)
390 vout_display_sys_t *sys = vd->sys;
391 vout_display_place_t place;
392 MMAL_DISPLAYREGION_T display_region;
393 MMAL_STATUS_T status;
395 if (!cfg && !fmt)
396 return -EINVAL;
398 if (fmt) {
399 sys->input->format->es->video.par.num = fmt->i_sar_num;
400 sys->input->format->es->video.par.den = fmt->i_sar_den;
402 status = mmal_port_format_commit(sys->input);
403 if (status != MMAL_SUCCESS) {
404 msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
405 sys->input->name, status, mmal_status_to_string(status));
406 return -EINVAL;
408 } else {
409 fmt = &vd->source;
412 if (!cfg)
413 cfg = &sys->last_cfg;
415 vout_display_PlacePicture(&place, fmt, cfg, false);
417 display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
418 display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
419 display_region.fullscreen = MMAL_FALSE;
420 display_region.src_rect.x = fmt->i_x_offset;
421 display_region.src_rect.y = fmt->i_y_offset;
422 display_region.src_rect.width = fmt->i_visible_width;
423 display_region.src_rect.height = fmt->i_visible_height;
424 display_region.dest_rect.x = place.x;
425 display_region.dest_rect.y = place.y;
426 display_region.dest_rect.width = place.width;
427 display_region.dest_rect.height = place.height;
428 display_region.layer = sys->layer;
429 display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT |
430 MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER;
431 status = mmal_port_parameter_set(sys->input, &display_region.hdr);
432 if (status != MMAL_SUCCESS) {
433 msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)",
434 status, mmal_status_to_string(status));
435 return -EINVAL;
438 show_background(vd, var_InheritBool(vd, MMAL_BLANK_BACKGROUND_NAME));
439 sys->adjust_refresh_rate = var_InheritBool(vd, MMAL_ADJUST_REFRESHRATE_NAME);
440 sys->native_interlaced = var_InheritBool(vd, MMAL_NATIVE_INTERLACED);
441 if (sys->adjust_refresh_rate) {
442 adjust_refresh_rate(vd, fmt);
443 set_latency_target(vd, true);
446 return 0;
449 static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count)
451 vout_display_sys_t *sys = vd->sys;
452 picture_resource_t picture_res;
453 picture_pool_configuration_t picture_pool_cfg;
454 MMAL_STATUS_T status;
455 unsigned i;
457 if (sys->picture_pool) {
458 if (sys->num_buffers < count)
459 msg_Warn(vd, "Picture pool with %u pictures requested, but we already have one with %u pictures",
460 count, sys->num_buffers);
462 goto out;
465 if (sys->opaque) {
466 if (count <= NUM_ACTUAL_OPAQUE_BUFFERS)
467 count = NUM_ACTUAL_OPAQUE_BUFFERS;
469 MMAL_PARAMETER_BOOLEAN_T zero_copy = {
470 { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) },
474 status = mmal_port_parameter_set(sys->input, &zero_copy.hdr);
475 if (status != MMAL_SUCCESS) {
476 msg_Err(vd, "Failed to set zero copy on port %s (status=%"PRIx32" %s)",
477 sys->input->name, status, mmal_status_to_string(status));
478 goto out;
482 if (count < sys->input->buffer_num_recommended)
483 count = sys->input->buffer_num_recommended;
485 #ifndef NDEBUG
486 msg_Dbg(vd, "Creating picture pool with %u pictures", count);
487 #endif
489 sys->input->buffer_num = count;
490 status = mmal_port_enable(sys->input, input_port_cb);
491 if (status != MMAL_SUCCESS) {
492 msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)",
493 sys->input->name, status, mmal_status_to_string(status));
494 goto out;
497 status = mmal_component_enable(sys->component);
498 if (status != MMAL_SUCCESS) {
499 msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)",
500 sys->component->name, status, mmal_status_to_string(status));
501 goto out;
504 sys->num_buffers = count;
505 sys->pool = mmal_port_pool_create(sys->input, sys->num_buffers,
506 sys->input->buffer_size);
507 if (!sys->pool) {
508 msg_Err(vd, "Failed to create MMAL pool for %u buffers of size %"PRIu32,
509 count, sys->input->buffer_size);
510 goto out;
513 memset(&picture_res, 0, sizeof(picture_resource_t));
514 sys->pictures = calloc(sys->num_buffers, sizeof(picture_t *));
515 for (i = 0; i < sys->num_buffers; ++i) {
516 picture_res.p_sys = calloc(1, sizeof(picture_sys_t));
517 picture_res.p_sys->owner = (vlc_object_t *)vd;
518 picture_res.p_sys->buffer = mmal_queue_get(sys->pool->queue);
520 sys->pictures[i] = picture_NewFromResource(&vd->fmt, &picture_res);
521 if (!sys->pictures[i]) {
522 msg_Err(vd, "Failed to create picture");
523 free(picture_res.p_sys);
524 goto out;
527 sys->pictures[i]->i_planes = sys->i_planes;
528 memcpy(sys->pictures[i]->p, sys->planes, sys->i_planes * sizeof(plane_t));
531 memset(&picture_pool_cfg, 0, sizeof(picture_pool_configuration_t));
532 picture_pool_cfg.picture_count = sys->num_buffers;
533 picture_pool_cfg.picture = sys->pictures;
534 picture_pool_cfg.lock = mmal_picture_lock;
536 sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg);
537 if (!sys->picture_pool) {
538 msg_Err(vd, "Failed to create picture pool");
539 goto out;
542 out:
543 return sys->picture_pool;
546 static void vd_prepare(vout_display_t *vd, picture_t *picture,
547 subpicture_t *subpicture, vlc_tick_t date)
549 vd_manage(vd);
550 VLC_UNUSED(date);
551 vout_display_sys_t *sys = vd->sys;
552 picture_sys_t *pic_sys = picture->p_sys;
554 if (!sys->adjust_refresh_rate || pic_sys->displayed)
555 return;
557 /* Apply the required phase_offset to the picture, so that vd_display()
558 * will be called at the corrected time from the core */
559 picture->date += sys->phase_offset;
562 static void vd_display(vout_display_t *vd, picture_t *picture)
564 vout_display_sys_t *sys = vd->sys;
565 picture_sys_t *pic_sys = picture->p_sys;
566 MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer;
567 MMAL_STATUS_T status;
569 if (picture->format.i_frame_rate != sys->i_frame_rate ||
570 picture->format.i_frame_rate_base != sys->i_frame_rate_base ||
571 picture->b_progressive != sys->b_progressive ||
572 picture->b_top_field_first != sys->b_top_field_first) {
573 sys->b_top_field_first = picture->b_top_field_first;
574 sys->b_progressive = picture->b_progressive;
575 sys->i_frame_rate = picture->format.i_frame_rate;
576 sys->i_frame_rate_base = picture->format.i_frame_rate_base;
577 configure_display(vd, NULL, &picture->format);
580 if (!pic_sys->displayed || !sys->opaque) {
581 buffer->cmd = 0;
582 buffer->length = sys->input->buffer_size;
583 buffer->user_data = picture_Hold(picture);
585 status = mmal_port_send_buffer(sys->input, buffer);
586 if (status == MMAL_SUCCESS)
587 atomic_fetch_add(&sys->buffers_in_transit, 1);
589 if (status != MMAL_SUCCESS) {
590 msg_Err(vd, "Failed to send buffer to input port. Frame dropped");
591 picture_Release(picture);
594 pic_sys->displayed = true;
597 display_subpicture(vd, subpicture);
599 if (sys->next_phase_check == 0 && sys->adjust_refresh_rate)
600 maintain_phase_sync(vd);
601 sys->next_phase_check = (sys->next_phase_check + 1) % PHASE_CHECK_INTERVAL;
603 if (sys->opaque) {
604 vlc_mutex_lock(&sys->buffer_mutex);
605 while (atomic_load(&sys->buffers_in_transit) >= MAX_BUFFERS_IN_TRANSIT)
606 vlc_cond_wait(&sys->buffer_cond, &sys->buffer_mutex);
607 vlc_mutex_unlock(&sys->buffer_mutex);
611 static int vd_control(vout_display_t *vd, int query, va_list args)
613 vout_display_sys_t *sys = vd->sys;
614 vout_display_cfg_t cfg;
615 const vout_display_cfg_t *tmp_cfg;
616 int ret = VLC_EGENERIC;
618 switch (query) {
619 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
620 tmp_cfg = va_arg(args, const vout_display_cfg_t *);
621 if (tmp_cfg->display.width == sys->display_width &&
622 tmp_cfg->display.height == sys->display_height) {
623 cfg = sys->last_cfg;
624 cfg.display.width = sys->display_width;
625 cfg.display.height = sys->display_height;
626 if (configure_display(vd, &cfg, NULL) >= 0)
627 ret = VLC_SUCCESS;
629 break;
631 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
632 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
633 sys->last_cfg = *va_arg(args, const vout_display_cfg_t *);
634 if (configure_display(vd, NULL, &vd->source) >= 0)
635 ret = VLC_SUCCESS;
636 break;
638 case VOUT_DISPLAY_RESET_PICTURES:
639 vlc_assert_unreachable();
640 case VOUT_DISPLAY_CHANGE_ZOOM:
641 msg_Warn(vd, "Unsupported control query %d", query);
642 break;
644 default:
645 msg_Warn(vd, "Unknown control query %d", query);
646 break;
649 return ret;
652 static void vd_manage(vout_display_t *vd)
654 vout_display_sys_t *sys = vd->sys;
655 unsigned width, height;
657 vlc_mutex_lock(&sys->manage_mutex);
659 if (sys->need_configure_display) {
660 close_dmx(vd);
661 sys->dmx_handle = vc_dispmanx_display_open(0);
663 if (query_resolution(vd, &width, &height) >= 0) {
664 sys->display_width = width;
665 sys->display_height = height;
666 vout_window_ReportSize(sys->last_cfg->window, width, height);
669 sys->need_configure_display = false;
672 vlc_mutex_unlock(&sys->manage_mutex);
675 static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
677 vout_display_t *vd = (vout_display_t *)port->userdata;
678 MMAL_STATUS_T status;
680 if (buffer->cmd == MMAL_EVENT_ERROR) {
681 status = *(uint32_t *)buffer->data;
682 msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status));
685 mmal_buffer_header_release(buffer);
688 static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
690 vout_display_t *vd = (vout_display_t *)port->userdata;
691 vout_display_sys_t *sys = vd->sys;
692 picture_t *picture = (picture_t *)buffer->user_data;
694 if (picture)
695 picture_Release(picture);
697 vlc_mutex_lock(&sys->buffer_mutex);
698 atomic_fetch_sub(&sys->buffers_in_transit, 1);
699 vlc_cond_signal(&sys->buffer_cond);
700 vlc_mutex_unlock(&sys->buffer_mutex);
703 static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height)
705 TV_DISPLAY_STATE_T display_state;
706 int ret = 0;
708 if (vc_tv_get_display_state(&display_state) == 0) {
709 if (display_state.state & 0xFF) {
710 *width = display_state.display.hdmi.width;
711 *height = display_state.display.hdmi.height;
712 } else if (display_state.state & 0xFF00) {
713 *width = display_state.display.sdtv.width;
714 *height = display_state.display.sdtv.height;
715 } else {
716 msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state);
717 ret = -1;
719 } else {
720 msg_Warn(vd, "Failed to query display resolution");
721 ret = -1;
724 return ret;
727 static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, uint32_t param2)
729 VLC_UNUSED(reason);
730 VLC_UNUSED(param1);
731 VLC_UNUSED(param2);
733 vout_display_t *vd = (vout_display_t *)callback_data;
734 vout_display_sys_t *sys = vd->sys;
736 vlc_mutex_lock(&sys->manage_mutex);
737 sys->need_configure_display = true;
738 vlc_mutex_unlock(&sys->manage_mutex);
741 static int set_latency_target(vout_display_t *vd, bool enable)
743 vout_display_sys_t *sys = vd->sys;
744 MMAL_STATUS_T status;
746 MMAL_PARAMETER_AUDIO_LATENCY_TARGET_T latency_target = {
747 .hdr = { MMAL_PARAMETER_AUDIO_LATENCY_TARGET, sizeof(latency_target) },
748 .enable = enable ? MMAL_TRUE : MMAL_FALSE,
749 .filter = 2,
750 .target = 4000,
751 .shift = 3,
752 .speed_factor = -135,
753 .inter_factor = 500,
754 .adj_cap = 20
757 status = mmal_port_parameter_set(sys->input, &latency_target.hdr);
758 if (status != MMAL_SUCCESS) {
759 msg_Err(vd, "Failed to configure latency target on input port %s (status=%"PRIx32" %s)",
760 sys->input->name, status, mmal_status_to_string(status));
761 return VLC_EGENERIC;
764 return VLC_SUCCESS;
767 static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt)
769 vout_display_sys_t *sys = vd->sys;
770 TV_DISPLAY_STATE_T display_state;
771 TV_SUPPORTED_MODE_NEW_T supported_modes[VC_TV_MAX_MODE_IDS];
772 char response[20]; /* answer is hvs_update_fields=%1d */
773 int num_modes;
774 double frame_rate = (double)fmt->i_frame_rate / fmt->i_frame_rate_base;
775 int best_id = -1;
776 double best_score, score;
777 int i;
779 vc_tv_get_display_state(&display_state);
780 if(display_state.display.hdmi.mode != HDMI_MODE_OFF) {
781 num_modes = vc_tv_hdmi_get_supported_modes_new(display_state.display.hdmi.group,
782 supported_modes, VC_TV_MAX_MODE_IDS, NULL, NULL);
784 for (i = 0; i < num_modes; ++i) {
785 TV_SUPPORTED_MODE_NEW_T *mode = &supported_modes[i];
786 if (!sys->native_interlaced) {
787 if (mode->width != display_state.display.hdmi.width ||
788 mode->height != display_state.display.hdmi.height ||
789 mode->scan_mode == HDMI_INTERLACED)
790 continue;
791 } else {
792 if (mode->width != fmt->i_visible_width ||
793 mode->height != fmt->i_visible_height)
794 continue;
795 if (mode->scan_mode != sys->b_progressive ? HDMI_NONINTERLACED : HDMI_INTERLACED)
796 continue;
799 score = fmod(supported_modes[i].frame_rate, frame_rate);
800 if((best_id < 0) || (score < best_score)) {
801 best_id = i;
802 best_score = score;
806 if((best_id >= 0) && (display_state.display.hdmi.mode != supported_modes[best_id].code)) {
807 msg_Info(vd, "Setting HDMI refresh rate to %"PRIu32,
808 supported_modes[best_id].frame_rate);
809 vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_HDMI,
810 supported_modes[best_id].group,
811 supported_modes[best_id].code);
814 if (sys->native_interlaced &&
815 supported_modes[best_id].scan_mode == HDMI_INTERLACED) {
816 char hvs_mode = sys->b_top_field_first ? '1' : '2';
817 if (vc_gencmd(response, sizeof(response), "hvs_update_fields %c",
818 hvs_mode) != 0 || response[18] != hvs_mode)
819 msg_Warn(vd, "Could not set hvs field mode");
820 else
821 msg_Info(vd, "Configured hvs field mode for interlaced %s playback",
822 sys->b_top_field_first ? "tff" : "bff");
827 static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture)
829 vout_display_sys_t *sys = vd->sys;
830 struct dmx_region_t **dmx_region = &sys->dmx_region;
831 struct dmx_region_t *unused_dmx_region;
832 DISPMANX_UPDATE_HANDLE_T update = 0;
833 picture_t *picture;
834 video_format_t *fmt;
835 struct dmx_region_t *dmx_region_next;
837 if(subpicture) {
838 subpicture_region_t *region = subpicture->p_region;
839 while(region) {
840 picture = region->p_picture;
841 fmt = &region->fmt;
843 if(!*dmx_region) {
844 if(!update)
845 update = vc_dispmanx_update_start(10);
846 *dmx_region = dmx_region_new(vd, update, region);
847 } else if(((*dmx_region)->bmp_rect.width != (int32_t)fmt->i_visible_width) ||
848 ((*dmx_region)->bmp_rect.height != (int32_t)fmt->i_visible_height) ||
849 ((*dmx_region)->pos_x != region->i_x) ||
850 ((*dmx_region)->pos_y != region->i_y) ||
851 ((*dmx_region)->alpha.opacity != (uint32_t)region->i_alpha)) {
852 dmx_region_next = (*dmx_region)->next;
853 if(!update)
854 update = vc_dispmanx_update_start(10);
855 dmx_region_delete(*dmx_region, update);
856 *dmx_region = dmx_region_new(vd, update, region);
857 (*dmx_region)->next = dmx_region_next;
858 } else if((*dmx_region)->picture != picture) {
859 if(!update)
860 update = vc_dispmanx_update_start(10);
861 dmx_region_update(*dmx_region, update, picture);
864 dmx_region = &(*dmx_region)->next;
865 region = region->p_next;
869 /* Remove remaining regions */
870 unused_dmx_region = *dmx_region;
871 while(unused_dmx_region) {
872 dmx_region_next = unused_dmx_region->next;
873 if(!update)
874 update = vc_dispmanx_update_start(10);
875 dmx_region_delete(unused_dmx_region, update);
876 unused_dmx_region = dmx_region_next;
878 *dmx_region = NULL;
880 if(update)
881 vc_dispmanx_update_submit_sync(update);
884 static void close_dmx(vout_display_t *vd)
886 vout_display_sys_t *sys = vd->sys;
887 DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(10);
888 struct dmx_region_t *dmx_region = sys->dmx_region;
889 struct dmx_region_t *dmx_region_next;
891 while(dmx_region) {
892 dmx_region_next = dmx_region->next;
893 dmx_region_delete(dmx_region, update);
894 dmx_region = dmx_region_next;
897 vc_dispmanx_update_submit_sync(update);
898 sys->dmx_region = NULL;
900 show_background(vd, false);
902 vc_dispmanx_display_close(sys->dmx_handle);
903 sys->dmx_handle = DISPMANX_NO_HANDLE;
906 static struct dmx_region_t *dmx_region_new(vout_display_t *vd,
907 DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region)
909 vout_display_sys_t *sys = vd->sys;
910 video_format_t *fmt = &region->fmt;
911 struct dmx_region_t *dmx_region = malloc(sizeof(struct dmx_region_t));
912 uint32_t image_handle;
914 dmx_region->pos_x = region->i_x;
915 dmx_region->pos_y = region->i_y;
917 vc_dispmanx_rect_set(&dmx_region->bmp_rect, 0, 0, fmt->i_visible_width,
918 fmt->i_visible_height);
919 vc_dispmanx_rect_set(&dmx_region->src_rect, 0, 0, fmt->i_visible_width << 16,
920 fmt->i_visible_height << 16);
921 vc_dispmanx_rect_set(&dmx_region->dst_rect, region->i_x, region->i_y,
922 fmt->i_visible_width, fmt->i_visible_height);
924 dmx_region->resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32,
925 dmx_region->bmp_rect.width | (region->p_picture->p[0].i_pitch << 16),
926 dmx_region->bmp_rect.height | (dmx_region->bmp_rect.height << 16),
927 &image_handle);
928 vc_dispmanx_resource_write_data(dmx_region->resource, VC_IMAGE_RGBA32,
929 region->p_picture->p[0].i_pitch,
930 region->p_picture->p[0].p_pixels, &dmx_region->bmp_rect);
932 dmx_region->alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_MIX;
933 dmx_region->alpha.opacity = region->i_alpha;
934 dmx_region->alpha.mask = DISPMANX_NO_HANDLE;
935 dmx_region->element = vc_dispmanx_element_add(update, sys->dmx_handle,
936 sys->layer + 1, &dmx_region->dst_rect, dmx_region->resource,
937 &dmx_region->src_rect, DISPMANX_PROTECTION_NONE,
938 &dmx_region->alpha, NULL, VC_IMAGE_ROT0);
940 dmx_region->next = NULL;
941 dmx_region->picture = region->p_picture;
943 return dmx_region;
946 static void dmx_region_update(struct dmx_region_t *dmx_region,
947 DISPMANX_UPDATE_HANDLE_T update, picture_t *picture)
949 vc_dispmanx_resource_write_data(dmx_region->resource, VC_IMAGE_RGBA32,
950 picture->p[0].i_pitch, picture->p[0].p_pixels, &dmx_region->bmp_rect);
951 vc_dispmanx_element_change_source(update, dmx_region->element, dmx_region->resource);
952 dmx_region->picture = picture;
955 static void dmx_region_delete(struct dmx_region_t *dmx_region,
956 DISPMANX_UPDATE_HANDLE_T update)
958 vc_dispmanx_element_remove(update, dmx_region->element);
959 vc_dispmanx_resource_delete(dmx_region->resource);
960 free(dmx_region);
963 static void maintain_phase_sync(vout_display_t *vd)
965 MMAL_PARAMETER_VIDEO_RENDER_STATS_T render_stats = {
966 .hdr = { MMAL_PARAMETER_VIDEO_RENDER_STATS, sizeof(render_stats) },
968 int32_t frame_duration = CLOCK_FREQ /
969 ((double)vd->sys->i_frame_rate /
970 vd->sys->i_frame_rate_base);
971 vout_display_sys_t *sys = vd->sys;
972 int32_t phase_offset;
973 MMAL_STATUS_T status;
975 status = mmal_port_parameter_get(sys->input, &render_stats.hdr);
976 if (status != MMAL_SUCCESS) {
977 msg_Err(vd, "Failed to read render stats on control port %s (status=%"PRIx32" %s)",
978 sys->input->name, status, mmal_status_to_string(status));
979 return;
982 if (render_stats.valid) {
983 #ifndef NDEBUG
984 msg_Dbg(vd, "render_stats: match: %u, period: %u ms, phase: %u ms, hvs: %u",
985 render_stats.match, render_stats.period / 1000, render_stats.phase / 1000,
986 render_stats.hvs_status);
987 #endif
989 if (render_stats.phase > 0.1 * frame_duration &&
990 render_stats.phase < 0.75 * frame_duration)
991 return;
993 phase_offset = frame_duration * PHASE_OFFSET_TARGET - render_stats.phase;
994 if (phase_offset < 0)
995 phase_offset += frame_duration;
996 else
997 phase_offset %= frame_duration;
999 sys->phase_offset += phase_offset;
1000 sys->phase_offset %= frame_duration;
1001 msg_Dbg(vd, "Apply phase offset of %"PRId32" ms (total offset %"PRId32" ms)",
1002 phase_offset / 1000, sys->phase_offset / 1000);
1004 /* Reset the latency target, so that it does not get confused
1005 * by the jump in the offset */
1006 set_latency_target(vd, false);
1007 set_latency_target(vd, true);
1011 static void show_background(vout_display_t *vd, bool enable)
1013 vout_display_sys_t *sys = vd->sys;
1014 uint32_t image_ptr, color = 0xFF000000;
1015 VC_RECT_T dst_rect, src_rect;
1016 DISPMANX_UPDATE_HANDLE_T update;
1018 if (enable && !sys->bkg_element) {
1019 sys->bkg_resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32, 1, 1,
1020 &image_ptr);
1021 vc_dispmanx_rect_set(&dst_rect, 0, 0, 1, 1);
1022 vc_dispmanx_resource_write_data(sys->bkg_resource, VC_IMAGE_RGBA32,
1023 sizeof(color), &color, &dst_rect);
1024 vc_dispmanx_rect_set(&src_rect, 0, 0, 1 << 16, 1 << 16);
1025 vc_dispmanx_rect_set(&dst_rect, 0, 0, 0, 0);
1026 update = vc_dispmanx_update_start(0);
1027 sys->bkg_element = vc_dispmanx_element_add(update, sys->dmx_handle,
1028 sys->layer - 1, &dst_rect, sys->bkg_resource, &src_rect,
1029 DISPMANX_PROTECTION_NONE, NULL, NULL, VC_IMAGE_ROT0);
1030 vc_dispmanx_update_submit_sync(update);
1031 } else if (!enable && sys->bkg_element) {
1032 update = vc_dispmanx_update_start(0);
1033 vc_dispmanx_element_remove(update, sys->bkg_element);
1034 vc_dispmanx_resource_delete(sys->bkg_resource);
1035 vc_dispmanx_update_submit_sync(update);
1036 sys->bkg_element = DISPMANX_NO_HANDLE;
1037 sys->bkg_resource = DISPMANX_NO_HANDLE;