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 *****************************************************************************/
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"
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
*);
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
)
90 struct dmx_region_t
*next
;
95 VC_DISPMANX_ALPHA_T alpha
;
96 DISPMANX_ELEMENT_HANDLE_T element
;
97 DISPMANX_RESOURCE_HANDLE_T resource
;
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
;
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 */
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 */
140 bool opaque
; /* indicated use of opaque picture format (zerocopy) */
143 static const vlc_fourcc_t subpicture_chromas
[] = {
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
);
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
);
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
,
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
);
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
;
196 if (vout_display_cfg_IsWindowed(cfg
))
199 sys
= calloc(1, sizeof(struct vout_display_sys_t
));
204 sys
->layer
= var_InheritInteger(vd
, MMAL_LAYER_NAME
);
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
));
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
));
226 sys
->input
= sys
->component
->input
[0];
227 sys
->input
->userdata
= (struct MMAL_PORT_USERDATA_T
*)vd
;
230 sys
->input
->format
->encoding
= MMAL_ENCODING_OPAQUE
;
232 sys
->buffer_size
= sys
->input
->buffer_size_recommended
;
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);
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
));
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
));
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
;
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
);
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
);
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
;
321 if (ret
!= VLC_SUCCESS
)
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 */
334 vc_tv_unregister_callback_full(tvservice_cb
, 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
);
349 mmal_port_pool_destroy(sys
->input
, sys
->pool
);
352 mmal_component_release(sys
->component
);
354 if (sys
->picture_pool
)
355 picture_pool_Release(sys
->picture_pool
);
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 ||
370 msg_Warn(vd
, "Could not reset hvs field mode");
379 static inline uint32_t align(uint32_t x
, uint32_t y
) {
380 uint32_t mod
= x
% y
;
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
;
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
));
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
));
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);
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
;
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
);
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
));
482 if (count
< sys
->input
->buffer_num_recommended
)
483 count
= sys
->input
->buffer_num_recommended
;
486 msg_Dbg(vd
, "Creating picture pool with %u pictures", count
);
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
));
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
));
504 sys
->num_buffers
= count
;
505 sys
->pool
= mmal_port_pool_create(sys
->input
, sys
->num_buffers
,
506 sys
->input
->buffer_size
);
508 msg_Err(vd
, "Failed to create MMAL pool for %u buffers of size %"PRIu32
,
509 count
, sys
->input
->buffer_size
);
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
);
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");
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
)
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
)
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
) {
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
;
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
;
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
) {
624 cfg
.display
.width
= sys
->display_width
;
625 cfg
.display
.height
= sys
->display_height
;
626 if (configure_display(vd
, &cfg
, NULL
) >= 0)
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)
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
);
645 msg_Warn(vd
, "Unknown control query %d", query
);
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
) {
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
;
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
;
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
;
716 msg_Warn(vd
, "Invalid display state %"PRIx32
, display_state
.state
);
720 msg_Warn(vd
, "Failed to query display resolution");
727 static void tvservice_cb(void *callback_data
, uint32_t reason
, uint32_t param1
, uint32_t 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
,
752 .speed_factor
= -135,
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
));
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 */
774 double frame_rate
= (double)fmt
->i_frame_rate
/ fmt
->i_frame_rate_base
;
776 double best_score
, score
;
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
)
792 if (mode
->width
!= fmt
->i_visible_width
||
793 mode
->height
!= fmt
->i_visible_height
)
795 if (mode
->scan_mode
!= sys
->b_progressive
? HDMI_NONINTERLACED
: HDMI_INTERLACED
)
799 score
= fmod(supported_modes
[i
].frame_rate
, frame_rate
);
800 if((best_id
< 0) || (score
< best_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");
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;
835 struct dmx_region_t
*dmx_region_next
;
838 subpicture_region_t
*region
= subpicture
->p_region
;
840 picture
= region
->p_picture
;
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
;
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
) {
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
;
874 update
= vc_dispmanx_update_start(10);
875 dmx_region_delete(unused_dmx_region
, update
);
876 unused_dmx_region
= dmx_region_next
;
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
;
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
= ®ion
->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),
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
;
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
);
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
));
982 if (render_stats
.valid
) {
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
);
989 if (render_stats
.phase
> 0.1 * frame_duration
&&
990 render_stats
.phase
< 0.75 * frame_duration
)
993 phase_offset
= frame_duration
* PHASE_OFFSET_TARGET
- render_stats
.phase
;
994 if (phase_offset
< 0)
995 phase_offset
+= frame_duration
;
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,
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
;