1 /*****************************************************************************
2 * mmal.c: MMAL-based vout plugin for Raspberry Pi
3 *****************************************************************************
4 * Copyright © 2014 jusst technologies GmbH
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 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_threads.h>
34 #include <vlc_vout_display.h>
36 #include "mmal_picture.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>
45 #define MAX_BUFFERS_IN_TRANSIT 2
46 #define VC_TV_MAX_MODE_IDS 127
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.")
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.")
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.")
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
65 static int Open(vlc_object_t *);
66 static void Close(vlc_object_t *);
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)
82 struct dmx_region_t *next;
87 VC_DISPMANX_ALPHA_T alpha;
88 DISPMANX_ELEMENT_HANDLE_T element;
89 DISPMANX_RESOURCE_HANDLE_T resource;
94 struct vout_display_sys_t {
95 vlc_cond_t buffer_cond;
96 vlc_mutex_t buffer_mutex;
97 vlc_mutex_t manage_mutex;
100 picture_t **pictures;
101 picture_pool_t *picture_pool;
103 MMAL_COMPONENT_T *component;
106 struct dmx_region_t *dmx_region;
109 uint32_t buffer_size;
110 int buffers_in_transit;
111 unsigned num_buffers;
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;
119 int i_frame_rate_base;
122 int next_phase_check;
126 bool need_configure_display;
127 bool adjust_refresh_rate;
128 bool native_interlaced;
129 bool b_top_field_first;
134 static const vlc_fourcc_t subpicture_chromas[] = {
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);
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);
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);
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,
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);
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);
176 static int Open(vlc_object_t *object)
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;
184 MMAL_STATUS_T status;
185 int ret = VLC_SUCCESS;
188 sys = calloc(1, sizeof(struct vout_display_sys_t));
193 sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME);
196 vd->info.has_hide_mouse = true;
197 sys->opaque = vd->fmt.i_chroma == VLC_CODEC_MMAL_OPAQUE;
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));
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));
216 sys->input = sys->component->input[0];
217 sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
220 sys->input->format->encoding = MMAL_ENCODING_OPAQUE;
222 sys->buffer_size = sys->input->buffer_size_recommended;
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);
229 sys->buffer_size = 3 * buffer_pitch * buffer_height / 2;
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;
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));
248 sys->input->buffer_size = sys->input->buffer_size_recommended;
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));
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;
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;
288 sys->planes[i].p_pixels = (uint8_t *)offsets[i];
291 vlc_mutex_init(&sys->buffer_mutex);
292 vlc_cond_init(&sys->buffer_cond);
293 vlc_mutex_init(&sys->manage_mutex);
296 vd->prepare = vd_prepare;
297 vd->display = vd_display;
298 vd->control = vd_control;
299 vd->manage = vd_manage;
301 vc_tv_register_callback(tvservice_cb, vd);
303 if (query_resolution(vd, &sys->display_width, &sys->display_height) >= 0) {
304 vout_display_SendEventDisplaySize(vd, sys->display_width, sys->display_height);
306 sys->display_width = vd->cfg->display.width;
307 sys->display_height = vd->cfg->display.height;
310 sys->dmx_handle = vc_dispmanx_display_open(0);
311 vd->info.subpicture_chromas = subpicture_chromas;
314 if (ret != VLC_SUCCESS)
320 static void Close(vlc_object_t *object)
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 */
327 vc_tv_unregister_callback_full(tvservice_cb, vd);
332 if (sys->component && sys->component->control->is_enabled)
333 mmal_port_disable(sys->component->control);
335 if (sys->input && sys->input->is_enabled)
336 mmal_port_disable(sys->input);
338 if (sys->component && sys->component->is_enabled)
339 mmal_component_disable(sys->component);
342 mmal_pool_destroy(sys->pool);
345 mmal_component_release(sys->component);
347 if (sys->picture_pool)
348 picture_pool_Delete(sys->picture_pool);
350 for (i = 0; i < sys->num_buffers; ++i)
351 if (sys->pictures[i])
352 picture_Release(sys->pictures[i]);
354 vlc_mutex_destroy(&sys->buffer_mutex);
355 vlc_cond_destroy(&sys->buffer_cond);
356 vlc_mutex_destroy(&sys->manage_mutex);
358 if (sys->native_interlaced) {
359 if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 ||
361 msg_Warn(vd, "Could not reset hvs field mode");
370 static inline uint32_t align(uint32_t x, uint32_t y) {
371 uint32_t mod = x % y;
378 static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
379 const video_format_t *fmt)
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;
390 sys->input->format->es->video.par.num = fmt->i_sar_num;
391 sys->input->format->es->video.par.den = fmt->i_sar_den;
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));
406 vout_display_PlacePicture(&place, fmt, cfg, false);
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));
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);
440 static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count)
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;
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);
458 if (count <= NUM_ACTUAL_OPAQUE_BUFFERS)
459 count = NUM_ACTUAL_OPAQUE_BUFFERS;
462 if (count < sys->input->buffer_num_recommended)
463 count = sys->input->buffer_num_recommended;
466 msg_Dbg(vd, "Creating picture pool with %u pictures", count);
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));
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));
484 sys->num_buffers = count;
485 sys->pool = mmal_pool_create(sys->num_buffers, sys->input->buffer_size);
487 msg_Err(vd, "Failed to create MMAL pool for %u buffers of size %"PRIu32,
488 count, sys->input->buffer_size);
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;
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);
506 sys->pictures[i]->i_planes = sys->i_planes;
507 memcpy(sys->pictures[i]->p, sys->planes, sys->i_planes * sizeof(plane_t));
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;
516 sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg);
517 if (!sys->picture_pool) {
518 msg_Err(vd, "Failed to create picture pool");
523 return sys->picture_pool;
526 static void vd_prepare(vout_display_t *vd, picture_t *picture,
527 subpicture_t *subpicture)
529 vout_display_sys_t *sys = vd->sys;
530 picture_sys_t *pic_sys = picture->p_sys;
532 if (!sys->adjust_refresh_rate || pic_sys->displayed)
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;
540 static void vd_display(vout_display_t *vd, picture_t *picture,
541 subpicture_t *subpicture)
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;
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);
559 if (!pic_sys->displayed || !sys->opaque) {
561 buffer->length = sys->input->buffer_size;
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);
567 status = mmal_port_send_buffer(sys->input, buffer);
568 if (status == MMAL_SUCCESS)
569 ++sys->buffers_in_transit;
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);
577 pic_sys->displayed = true;
579 picture_Release(picture);
582 display_subpicture(vd, subpicture);
585 subpicture_Delete(subpicture);
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;
592 static int vd_control(vout_display_t *vd, int query, va_list args)
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;
601 case VOUT_DISPLAY_HIDE_MOUSE:
602 case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
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);
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) {
618 cfg.display.width = sys->display_width;
619 cfg.display.height = sys->display_height;
620 if (configure_display(vd, &cfg, NULL) >= 0)
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)
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);
639 msg_Warn(vd, "Unknown control query %d", query);
646 static void vd_manage(vout_display_t *vd)
648 vout_display_sys_t *sys = vd->sys;
649 unsigned width, height;
651 vlc_mutex_lock(&sys->manage_mutex);
653 if (sys->need_configure_display) {
655 sys->dmx_handle = vc_dispmanx_display_open(0);
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);
663 sys->need_configure_display = false;
666 vlc_mutex_unlock(&sys->manage_mutex);
669 static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
671 vout_display_t *vd = (vout_display_t *)port->userdata;
672 MMAL_STATUS_T status;
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));
679 mmal_buffer_header_release(buffer);
682 static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
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;
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);
694 picture_Release(picture);
697 static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height)
699 TV_DISPLAY_STATE_T display_state;
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;
710 msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state);
714 msg_Warn(vd, "Failed to query display resolution");
721 static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, uint32_t param2)
727 vout_display_t *vd = (vout_display_t *)callback_data;
728 vout_display_sys_t *sys = vd->sys;
730 vlc_mutex_lock(&sys->manage_mutex);
731 sys->need_configure_display = true;
732 vlc_mutex_unlock(&sys->manage_mutex);
735 static int set_latency_target(vout_display_t *vd, bool enable)
737 vout_display_sys_t *sys = vd->sys;
738 MMAL_STATUS_T status;
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,
746 .speed_factor = -135,
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));
761 static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt)
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 */
768 double frame_rate = (double)fmt->i_frame_rate / fmt->i_frame_rate_base;
770 double best_score, score;
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);
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)
786 if (mode->width != vd->fmt.i_visible_width ||
787 mode->height != vd->fmt.i_visible_height)
789 if (mode->scan_mode != sys->b_progressive ? HDMI_NONINTERLACED : HDMI_INTERLACED)
793 score = fmod(supported_modes[i].frame_rate, frame_rate);
794 if((best_id < 0) || (score < best_score)) {
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);
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");
815 msg_Info(vd, "Configured hvs field mode for interlaced %s playback",
816 sys->b_top_field_first ? "tff" : "bff");
821 static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture)
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;
829 struct dmx_region_t *dmx_region_next;
832 subpicture_region_t *region = subpicture->p_region;
834 picture = region->p_picture;
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;
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) {
854 update = vc_dispmanx_update_start(10);
855 dmx_region_update(*dmx_region, update, picture);
858 dmx_region = &(*dmx_region)->next;
859 region = region->p_next;
863 /* Remove remaining regions */
864 unused_dmx_region = *dmx_region;
865 while(unused_dmx_region) {
866 dmx_region_next = unused_dmx_region->next;
868 update = vc_dispmanx_update_start(10);
869 dmx_region_delete(unused_dmx_region, update);
870 unused_dmx_region = dmx_region_next;
875 vc_dispmanx_update_submit_sync(update);
878 static void close_dmx(vout_display_t *vd)
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;
886 dmx_region_next = dmx_region->next;
887 dmx_region_delete(dmx_region, update);
888 dmx_region = dmx_region_next;
891 vc_dispmanx_update_submit_sync(update);
892 sys->dmx_region = NULL;
894 show_background(vd, false);
896 vc_dispmanx_display_close(sys->dmx_handle);
897 sys->dmx_handle = DISPMANX_NO_HANDLE;
900 static struct dmx_region_t *dmx_region_new(vout_display_t *vd,
901 DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region)
903 vout_display_sys_t *sys = vd->sys;
904 video_format_t *fmt = ®ion->fmt;
905 struct dmx_region_t *dmx_region = malloc(sizeof(struct dmx_region_t));
906 uint32_t image_handle;
908 dmx_region->pos_x = region->i_x;
909 dmx_region->pos_y = region->i_y;
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);
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),
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);
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);
934 dmx_region->next = NULL;
935 dmx_region->picture = region->p_picture;
940 static void dmx_region_update(struct dmx_region_t *dmx_region,
941 DISPMANX_UPDATE_HANDLE_T update, picture_t *picture)
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;
949 static void dmx_region_delete(struct dmx_region_t *dmx_region,
950 DISPMANX_UPDATE_HANDLE_T update)
952 vc_dispmanx_element_remove(update, dmx_region->element);
953 vc_dispmanx_resource_delete(dmx_region->resource);
957 static void maintain_phase_sync(vout_display_t *vd)
959 MMAL_PARAMETER_VIDEO_RENDER_STATS_T render_stats = {
960 .hdr = { MMAL_PARAMETER_VIDEO_RENDER_STATS, sizeof(render_stats) },
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;
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));
976 if (render_stats.valid) {
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);
983 if (render_stats.phase > 0.1 * frame_duration &&
984 render_stats.phase < 0.75 * frame_duration)
987 phase_offset = frame_duration * PHASE_OFFSET_TARGET - render_stats.phase;
988 if (phase_offset < 0)
989 phase_offset += frame_duration;
991 phase_offset %= frame_duration;
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);
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);
1005 static void show_background(vout_display_t *vd, bool enable)
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;
1012 if (enable && !sys->bkg_element) {
1013 sys->bkg_resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32, 1, 1,
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;