1 /*****************************************************************************
2 * mmal.c: MMAL-based vout plugin for Raspberry Pi
3 *****************************************************************************
4 * Copyright © 2014 jusst technologies GmbH
6 * Authors: Dennis Hamester <dennis.hamester@gmail.com>
7 * Julian Scheel <julian@jusst.de>
8 * John Cox <jc@kynesim.co.uk>
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 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_vout_display.h>
33 #include "mmal_picture.h"
36 #include <interface/mmal/mmal.h>
37 #include <interface/mmal/util/mmal_util.h>
38 #include <interface/mmal/util/mmal_default_components.h>
42 #define MAX_BUFFERS_IN_TRANSIT 1
43 #define VC_TV_MAX_MODE_IDS 127
45 #define MMAL_LAYER_NAME "mmal-layer"
46 #define MMAL_LAYER_TEXT N_("VideoCore layer where the video is displayed.")
47 #define MMAL_LAYER_LONGTEXT N_("VideoCore layer where the video is displayed. Subpictures are displayed directly above and a black background directly below.")
49 #define MMAL_DISPLAY_NAME "mmal-display"
50 #define MMAL_DISPLAY_TEXT N_("Output device for Rpi fullscreen.")
51 #define MMAL_DISPLAY_LONGTEXT N_("Output device for Rpi fullscreen. " \
52 "Valid values are HDMI-1,HDMI-2. By default HDMI-1.")
54 #define MMAL_ADJUST_REFRESHRATE_NAME "mmal-adjust-refreshrate"
55 #define MMAL_ADJUST_REFRESHRATE_TEXT N_("Adjust HDMI refresh rate to the video.")
56 #define MMAL_ADJUST_REFRESHRATE_LONGTEXT N_("Adjust HDMI refresh rate to the video.")
58 #define MMAL_NATIVE_INTERLACED "mmal-native-interlaced"
59 #define MMAL_NATIVE_INTERLACE_TEXT N_("Force interlaced HDMI mode.")
60 #define MMAL_NATIVE_INTERLACE_LONGTEXT N_("Force the HDMI output into an " \
61 "interlaced video mode for interlaced video content.")
63 /* Ideal rendering phase target is at rough 25% of frame duration */
64 #define PHASE_OFFSET_TARGET ((double)0.25)
65 #define PHASE_CHECK_INTERVAL 100
67 static int OpenMmalVout(vout_display_t
*, const vout_display_cfg_t
*,
68 video_format_t
*, vlc_video_context
*);
74 set_shortname(N_("MMAL vout"))
75 set_description(N_("MMAL-based vout plugin for Raspberry Pi"))
76 add_shortcut("mmal_vout")
77 set_category( CAT_VIDEO
)
78 set_subcategory( SUBCAT_VIDEO_VOUT
)
80 add_integer(MMAL_LAYER_NAME
, 1, MMAL_LAYER_TEXT
, MMAL_LAYER_LONGTEXT
, false)
81 add_bool(MMAL_ADJUST_REFRESHRATE_NAME
, false, MMAL_ADJUST_REFRESHRATE_TEXT
,
82 MMAL_ADJUST_REFRESHRATE_LONGTEXT
, false)
83 add_bool(MMAL_NATIVE_INTERLACED
, false, MMAL_NATIVE_INTERLACE_TEXT
,
84 MMAL_NATIVE_INTERLACE_LONGTEXT
, false)
85 add_string(MMAL_DISPLAY_NAME
, "auto", MMAL_DISPLAY_TEXT
,
86 MMAL_DISPLAY_LONGTEXT
, false)
87 set_callback_display(OpenMmalVout
, 16) // 1 point better than ASCII art
90 typedef struct vout_subpic_s
{
91 MMAL_COMPONENT_T
*component
;
92 subpic_reg_stash_t sub
;
95 struct vout_display_sys_t
{
96 vlc_mutex_t manage_mutex
;
98 vlc_decoder_device
*dec_dev
;
99 MMAL_COMPONENT_T
*component
;
101 MMAL_POOL_T
*pool
; /* mmal buffer headers, used for pushing pictures to component*/
102 int i_planes
; /* Number of actually used planes, 1 for opaque, 3 for i420 */
104 int buffers_in_transit
; /* number of buffers currently pushed to mmal component */
105 unsigned num_buffers
; /* number of buffers allocated at mmal port */
108 unsigned display_width
;
109 unsigned display_height
;
111 MMAL_RECT_T dest_rect
; // Output rectangle in display coords
113 unsigned int i_frame_rate_base
; /* cached framerate to detect changes for rate adjustment */
114 unsigned int i_frame_rate
;
116 int next_phase_check
; /* lowpass for phase check frequency */
117 int phase_offset
; /* currently applied offset to presentation time in ns */
118 int layer
; /* the dispman layer (z-index) used for video rendering */
120 bool need_configure_display
; /* indicates a required display reconfigure to main thread */
121 bool adjust_refresh_rate
;
122 bool native_interlaced
;
123 bool b_top_field_first
; /* cached interlaced settings to detect changes for native mode */
127 vout_subpic_t subs
[SUBS_MAX
];
128 // Stash for subpics derived from the passed subpicture rather than
129 // included with the main pic
130 MMAL_BUFFER_HEADER_T
* subpic_bufs
[SUBS_MAX
];
132 picture_pool_t
* pic_pool
;
134 struct vout_isp_conf_s
{
135 MMAL_COMPONENT_T
*component
;
137 MMAL_PORT_T
* output
;
138 MMAL_QUEUE_T
* out_q
;
139 MMAL_POOL_T
* in_pool
;
140 MMAL_POOL_T
* out_pool
;
144 MMAL_POOL_T
* copy_pool
;
145 MMAL_BUFFER_HEADER_T
* copy_buf
;
147 // Subpic blend if we have to do it here
148 vzc_pool_ctl_t
* vzc
;
154 static bool want_copy(const video_format_t
* const fmt
)
156 return (fmt
->i_chroma
== VLC_CODEC_I420
|| fmt
->i_chroma
== VLC_CODEC_I420_10L
);
159 static vlc_fourcc_t
req_chroma(const video_format_t
* const fmt
)
161 return hw_mmal_chroma_is_mmal(fmt
->i_chroma
) || want_copy(fmt
) ?
162 fmt
->i_chroma
: VLC_CODEC_I420
;
165 static MMAL_FOURCC_T
vout_vlc_to_mmal_pic_fourcc(const unsigned int fcc
)
168 case VLC_CODEC_MMAL_OPAQUE
:
169 return MMAL_ENCODING_OPAQUE
;
171 return MMAL_ENCODING_I420
;
175 return MMAL_ENCODING_I420
;
178 static void display_set_format(const vout_display_t
* const vd
, MMAL_ES_FORMAT_T
*const es_fmt
, const bool is_intermediate
)
180 const unsigned int w
= is_intermediate
? vd
->fmt
.i_visible_width
: vd
->fmt
.i_width
;
181 const unsigned int h
= is_intermediate
? vd
->fmt
.i_visible_height
: vd
->fmt
.i_height
;
182 MMAL_VIDEO_FORMAT_T
* const v_fmt
= &es_fmt
->es
->video
;
184 es_fmt
->type
= MMAL_ES_TYPE_VIDEO
;
185 es_fmt
->encoding
= is_intermediate
? MMAL_ENCODING_I420
: vout_vlc_to_mmal_pic_fourcc(vd
->fmt
.i_chroma
);
186 es_fmt
->encoding_variant
= 0;
188 v_fmt
->width
= (w
+ 31) & ~31;
189 v_fmt
->height
= (h
+ 15) & ~15;
192 v_fmt
->crop
.width
= w
;
193 v_fmt
->crop
.height
= h
;
194 if (vd
->fmt
.i_sar_num
== 0 || vd
->fmt
.i_sar_den
== 0) {
198 v_fmt
->par
.num
= vd
->fmt
.i_sar_num
;
199 v_fmt
->par
.den
= vd
->fmt
.i_sar_den
;
201 v_fmt
->frame_rate
.num
= vd
->fmt
.i_frame_rate
;
202 v_fmt
->frame_rate
.den
= vd
->fmt
.i_frame_rate_base
;
203 v_fmt
->color_space
= vlc_to_mmal_color_space(vd
->fmt
.space
);
206 static void display_src_rect(const vout_display_t
* const vd
, MMAL_RECT_T
*const rect
)
208 const bool wants_isp
= false;
209 rect
->x
= wants_isp
? 0 : vd
->fmt
.i_x_offset
;
210 rect
->y
= wants_isp
? 0 : vd
->fmt
.i_y_offset
;
211 rect
->width
= vd
->fmt
.i_visible_width
;
212 rect
->height
= vd
->fmt
.i_visible_height
;
215 static void isp_input_cb(MMAL_PORT_T
*port
, MMAL_BUFFER_HEADER_T
*buf
)
219 mmal_buffer_header_release(buf
);
222 static void isp_control_port_cb(MMAL_PORT_T
*port
, MMAL_BUFFER_HEADER_T
*buffer
)
224 vout_display_t
*vd
= (vout_display_t
*)port
->userdata
;
225 MMAL_STATUS_T status
;
227 if (buffer
->cmd
== MMAL_EVENT_ERROR
) {
228 status
= *(uint32_t *)buffer
->data
;
229 msg_Err(vd
, "MMAL error %"PRIx32
" \"%s\"", status
, mmal_status_to_string(status
));
232 mmal_buffer_header_release(buffer
);
235 static void isp_output_cb(MMAL_PORT_T
*port
, MMAL_BUFFER_HEADER_T
*buf
)
237 if (buf
->cmd
== 0 && buf
->length
!= 0)
239 // The filter structure etc. should always exist if we have contents
240 // but might not on later flushes as we shut down
241 vout_display_t
* const vd
= (vout_display_t
*)port
->userdata
;
242 struct vout_isp_conf_s
*const isp
= &vd
->sys
->isp
;
244 mmal_queue_put(isp
->out_q
, buf
);
248 mmal_buffer_header_reset(buf
);
249 mmal_buffer_header_release(buf
);
253 static void isp_empty_out_q(struct vout_isp_conf_s
* const isp
)
255 MMAL_BUFFER_HEADER_T
* buf
;
256 // We can be called as part of error recovery so allow for missing Q
257 if (isp
->out_q
== NULL
)
260 while ((buf
= mmal_queue_get(isp
->out_q
)) != NULL
)
261 mmal_buffer_header_release(buf
);
264 static void isp_flush(struct vout_isp_conf_s
* const isp
)
266 if (!isp
->input
->is_enabled
)
267 mmal_port_disable(isp
->input
);
269 if (isp
->output
->is_enabled
)
270 mmal_port_disable(isp
->output
);
272 isp_empty_out_q(isp
);
273 isp
->pending
= false;
276 static MMAL_STATUS_T
isp_prepare(vout_display_t
* const vd
, struct vout_isp_conf_s
* const isp
)
279 MMAL_BUFFER_HEADER_T
* buf
;
281 if (!isp
->output
->is_enabled
) {
282 if ((err
= mmal_port_enable(isp
->output
, isp_output_cb
)) != MMAL_SUCCESS
)
284 msg_Err(vd
, "ISP output port enable failed");
289 while ((buf
= mmal_queue_get(isp
->out_pool
->queue
)) != NULL
) {
290 if ((err
= mmal_port_send_buffer(isp
->output
, buf
)) != MMAL_SUCCESS
)
292 msg_Err(vd
, "ISP output port stuff failed");
297 if (!isp
->input
->is_enabled
) {
298 if ((err
= mmal_port_enable(isp
->input
, isp_input_cb
)) != MMAL_SUCCESS
)
300 msg_Err(vd
, "ISP input port enable failed");
307 static void isp_close(vout_display_t
* const vd
, vout_display_sys_t
* const vd_sys
)
309 struct vout_isp_conf_s
* const isp
= &vd_sys
->isp
;
312 if (isp
->component
== NULL
)
317 if (isp
->component
->control
->is_enabled
)
318 mmal_port_disable(isp
->component
->control
);
320 if (isp
->out_q
!= NULL
) {
321 // 1st junk anything lying around
322 isp_empty_out_q(isp
);
324 mmal_queue_destroy(isp
->out_q
);
328 if (isp
->out_pool
!= NULL
) {
329 mmal_port_pool_destroy(isp
->output
, isp
->out_pool
);
330 isp
->out_pool
= NULL
;
336 mmal_component_release(isp
->component
);
337 isp
->component
= NULL
;
342 // Restuff into output rather than return to pool is we can
343 static MMAL_BOOL_T
isp_out_pool_cb(MMAL_POOL_T
*pool
, MMAL_BUFFER_HEADER_T
*buffer
, void *userdata
)
345 struct vout_isp_conf_s
* const isp
= userdata
;
347 if (isp
->output
->is_enabled
) {
348 mmal_buffer_header_reset(buffer
);
349 if (mmal_port_send_buffer(isp
->output
, buffer
) == MMAL_SUCCESS
)
355 static MMAL_STATUS_T
isp_setup(vout_display_t
* const vd
, vout_display_sys_t
* const vd_sys
)
357 struct vout_isp_conf_s
* const isp
= &vd_sys
->isp
;
360 if ((err
= mmal_component_create(MMAL_COMPONENT_ISP_RESIZER
, &isp
->component
)) != MMAL_SUCCESS
) {
361 msg_Err(vd
, "Cannot create ISP component");
364 isp
->input
= isp
->component
->input
[0];
365 isp
->output
= isp
->component
->output
[0];
367 isp
->component
->control
->userdata
= (void *)vd
;
368 if ((err
= mmal_port_enable(isp
->component
->control
, isp_control_port_cb
)) != MMAL_SUCCESS
) {
369 msg_Err(vd
, "Failed to enable ISP control port");
373 isp
->input
->userdata
= (void *)vd
;
374 display_set_format(vd
, isp
->input
->format
, false);
376 if ((err
= port_parameter_set_bool(isp
->input
, MMAL_PARAMETER_ZERO_COPY
, true)) != MMAL_SUCCESS
)
379 if ((err
= mmal_port_format_commit(isp
->input
)) != MMAL_SUCCESS
) {
380 msg_Err(vd
, "Failed to set ISP input format");
384 isp
->input
->buffer_size
= isp
->input
->buffer_size_recommended
;
385 isp
->input
->buffer_num
= 30;
387 if ((isp
->in_pool
= mmal_pool_create(isp
->input
->buffer_num
, 0)) == NULL
)
389 msg_Err(vd
, "Failed to create input pool");
393 if ((isp
->out_q
= mmal_queue_create()) == NULL
)
399 display_set_format(vd
, isp
->output
->format
, true);
401 if ((err
= port_parameter_set_bool(isp
->output
, MMAL_PARAMETER_ZERO_COPY
, true)) != MMAL_SUCCESS
)
404 if ((err
= mmal_port_format_commit(isp
->output
)) != MMAL_SUCCESS
) {
405 msg_Err(vd
, "Failed to set ISP input format");
409 isp
->output
->buffer_size
= isp
->output
->buffer_size_recommended
;
410 isp
->output
->buffer_num
= 2;
411 isp
->output
->userdata
= (void *)vd
;
413 if ((isp
->out_pool
= mmal_port_pool_create(isp
->output
, isp
->output
->buffer_num
, isp
->output
->buffer_size
)) == NULL
)
415 msg_Err(vd
, "Failed to make ISP port pool");
419 mmal_pool_callback_set(isp
->out_pool
, isp_out_pool_cb
, isp
);
421 if ((err
= isp_prepare(vd
, isp
)) != MMAL_SUCCESS
)
427 isp_close(vd
, vd_sys
);
431 static MMAL_STATUS_T
isp_check(vout_display_t
* const vd
, vout_display_sys_t
* const vd_sys
)
433 struct vout_isp_conf_s
*const isp
= &vd_sys
->isp
;
434 const bool has_isp
= (isp
->component
!= NULL
);
435 const bool wants_isp
= false;
437 if (has_isp
== wants_isp
)
439 // All OK - do nothing
443 // ISP active but we don't want it
446 // Check we have everything back and then kill it
447 if (mmal_queue_length(isp
->out_pool
->queue
) == isp
->output
->buffer_num
)
448 isp_close(vd
, vd_sys
);
452 // ISP closed but we want it
453 return isp_setup(vd
, vd_sys
);
460 static void tvservice_cb(void *callback_data
, uint32_t reason
, uint32_t param1
,
462 static void adjust_refresh_rate(vout_display_t
*vd
, const video_format_t
*fmt
);
463 static int set_latency_target(vout_display_t
*vd
, bool enable
);
466 static void maintain_phase_sync(vout_display_t
*vd
);
470 static void vd_input_port_cb(MMAL_PORT_T
*port
, MMAL_BUFFER_HEADER_T
*buf
)
474 mmal_buffer_header_release(buf
);
477 static int query_resolution(vout_display_t
*vd
, const int display_id
, unsigned *width
, unsigned *height
)
479 TV_DISPLAY_STATE_T display_state
= {0};
482 if (vc_tv_get_display_state_id(display_id
, &display_state
) == 0) {
483 msg_Dbg(vd
, "State=%#x", display_state
.state
);
484 if (display_state
.state
& 0xFF) {
485 msg_Dbg(vd
, "HDMI: %dx%d", display_state
.display
.hdmi
.width
, display_state
.display
.hdmi
.height
);
486 *width
= display_state
.display
.hdmi
.width
;
487 *height
= display_state
.display
.hdmi
.height
;
488 } else if (display_state
.state
& 0xFF00) {
489 msg_Dbg(vd
, "SDTV: %dx%d", display_state
.display
.sdtv
.width
, display_state
.display
.sdtv
.height
);
490 *width
= display_state
.display
.sdtv
.width
;
491 *height
= display_state
.display
.sdtv
.height
;
493 msg_Warn(vd
, "Invalid display state %"PRIx32
, display_state
.state
);
497 msg_Warn(vd
, "Failed to query display resolution");
505 place_to_mmal_rect(const vout_display_place_t place
)
507 return (MMAL_RECT_T
){
510 .width
= place
.width
,
511 .height
= place
.height
516 place_dest(vout_display_sys_t
* const sys
,
517 const vout_display_cfg_t
* const cfg
, const video_format_t
* fmt
)
521 // Fix SAR if unknown
522 if (fmt
->i_sar_den
== 0 || fmt
->i_sar_num
== 0) {
529 // Ignore what VLC thinks might be going on with display size
530 vout_display_cfg_t tcfg
= *cfg
;
531 vout_display_place_t place
;
532 tcfg
.display
.width
= sys
->display_width
;
533 tcfg
.display
.height
= sys
->display_height
;
534 tcfg
.is_display_filled
= true;
535 vout_display_PlacePicture(&place
, fmt
, &tcfg
);
537 sys
->dest_rect
= place_to_mmal_rect(place
);
542 static int configure_display(vout_display_t
*vd
, const vout_display_cfg_t
*cfg
,
543 const video_format_t
*fmt
)
545 vout_display_sys_t
* const sys
= vd
->sys
;
546 MMAL_DISPLAYREGION_T display_region
;
547 MMAL_STATUS_T status
;
551 msg_Err(vd
, "Missing cfg & fmt");
558 sys
->input
->format
->es
->video
.par
.num
= fmt
->i_sar_num
;
559 sys
->input
->format
->es
->video
.par
.den
= fmt
->i_sar_den
;
561 status
= mmal_port_format_commit(sys
->input
);
562 if (status
!= MMAL_SUCCESS
) {
563 msg_Err(vd
, "Failed to commit format for input port %s (status=%"PRIx32
" %s)",
564 sys
->input
->name
, status
, mmal_status_to_string(status
));
574 place_dest(sys
, cfg
, fmt
);
576 display_region
.hdr
.id
= MMAL_PARAMETER_DISPLAYREGION
;
577 display_region
.hdr
.size
= sizeof(MMAL_DISPLAYREGION_T
);
578 display_region
.fullscreen
= MMAL_FALSE
;
579 display_src_rect(vd
, &display_region
.src_rect
);
580 display_region
.dest_rect
= sys
->dest_rect
;
581 display_region
.layer
= sys
->layer
;
582 display_region
.alpha
= 0xff | (1 << 29);
583 display_region
.set
= MMAL_DISPLAY_SET_FULLSCREEN
| MMAL_DISPLAY_SET_SRC_RECT
|
584 MMAL_DISPLAY_SET_DEST_RECT
| MMAL_DISPLAY_SET_LAYER
| MMAL_DISPLAY_SET_ALPHA
;
585 status
= mmal_port_parameter_set(sys
->input
, &display_region
.hdr
);
586 if (status
!= MMAL_SUCCESS
) {
587 msg_Err(vd
, "Failed to set display region (status=%"PRIx32
" %s)",
588 status
, mmal_status_to_string(status
));
592 sys
->adjust_refresh_rate
= var_InheritBool(vd
, MMAL_ADJUST_REFRESHRATE_NAME
);
593 sys
->native_interlaced
= var_InheritBool(vd
, MMAL_NATIVE_INTERLACED
);
594 if (sys
->adjust_refresh_rate
) {
595 adjust_refresh_rate(vd
, fmt
);
596 set_latency_target(vd
, true);
602 static void kill_pool(vout_display_sys_t
* const sys
)
604 if (sys
->pic_pool
!= NULL
) {
605 picture_pool_Release(sys
->pic_pool
);
606 sys
->pic_pool
= NULL
;
610 static void vd_display(vout_display_t
*vd
, picture_t
*p_pic
)
612 vout_display_sys_t
* const sys
= vd
->sys
;
615 if (!sys
->input
->is_enabled
&&
616 (err
= mmal_port_enable(sys
->input
, vd_input_port_cb
)) != MMAL_SUCCESS
)
618 msg_Err(vd
, "Input port enable failed");
622 // We assume the BH is already set up with values reflecting pic date etc.
623 if (sys
->copy_buf
!= NULL
) {
624 MMAL_BUFFER_HEADER_T
*const buf
= sys
->copy_buf
;
625 sys
->copy_buf
= NULL
;
626 if (mmal_port_send_buffer(sys
->input
, buf
) != MMAL_SUCCESS
)
628 mmal_buffer_header_release(buf
);
629 msg_Err(vd
, "Send copy buffer to render input failed");
633 else if (sys
->isp
.pending
) {
634 MMAL_BUFFER_HEADER_T
*const buf
= mmal_queue_wait(sys
->isp
.out_q
);
635 sys
->isp
.pending
= false;
636 if (mmal_port_send_buffer(sys
->input
, buf
) != MMAL_SUCCESS
)
638 mmal_buffer_header_release(buf
);
639 msg_Err(vd
, "Send ISP buffer to render input failed");
645 MMAL_BUFFER_HEADER_T
*const pic_buf
= hw_mmal_pic_buf_replicated(p_pic
, sys
->pool
);
648 msg_Err(vd
, "Replicated buffer get fail");
653 // If dimensions have chnaged then fix that
654 if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys
->input
->format
, p_pic
))
656 msg_Dbg(vd
, "Reset port format");
658 // HVS can deal with on-line dimension changes
659 if (mmal_port_format_commit(sys
->input
) != MMAL_SUCCESS
)
660 msg_Warn(vd
, "Input format commit failed");
663 if ((err
= mmal_port_send_buffer(sys
->input
, pic_buf
)) != MMAL_SUCCESS
)
665 mmal_buffer_header_release(pic_buf
);
666 msg_Err(vd
, "Send buffer to input failed");
672 unsigned int sub_no
= 0;
673 MMAL_BUFFER_HEADER_T
**psub_bufs2
= sys
->subpic_bufs
;
674 const bool is_mmal_pic
= hw_mmal_chroma_is_mmal(p_pic
->format
.i_chroma
);
676 for (sub_no
= 0; sub_no
!= SUBS_MAX
; ++sub_no
) {
678 MMAL_BUFFER_HEADER_T
* const sub_buf
= !is_mmal_pic
? NULL
:
679 hw_mmal_pic_sub_buf_get(p_pic
, sub_no
);
681 if ((rv
= hw_mmal_subpic_update(VLC_OBJECT(vd
),
682 sub_buf
!= NULL
? sub_buf
: *psub_bufs2
++,
683 &sys
->subs
[sub_no
].sub
,
694 for (unsigned int i
= 0; i
!= SUBS_MAX
&& sys
->subpic_bufs
[i
] != NULL
; ++i
) {
695 mmal_buffer_header_release(sys
->subpic_bufs
[i
]);
696 sys
->subpic_bufs
[i
] = NULL
;
699 if (sys
->next_phase_check
== 0 && sys
->adjust_refresh_rate
)
700 maintain_phase_sync(vd
);
701 sys
->next_phase_check
= (sys
->next_phase_check
+ 1) % PHASE_CHECK_INTERVAL
;
704 static int vd_control(vout_display_t
*vd
, int query
, va_list args
)
706 vout_display_sys_t
* const sys
= vd
->sys
;
707 int ret
= VLC_EGENERIC
;
711 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE
:
713 const vout_display_cfg_t
*cfg
= va_arg(args
, const vout_display_cfg_t
*);
714 if (configure_display(vd
, cfg
, NULL
) >= 0)
719 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
:
720 case VOUT_DISPLAY_CHANGE_SOURCE_CROP
:
721 if (configure_display(vd
, NULL
, &vd
->source
) >= 0)
725 case VOUT_DISPLAY_RESET_PICTURES
:
726 msg_Warn(vd
, "Reset Pictures");
728 vd
->fmt
= vd
->source
; // Take (nearly) whatever source wants to give us
729 vd
->fmt
.i_chroma
= req_chroma(&vd
->fmt
); // Adjust chroma to something we can actually deal with
733 case VOUT_DISPLAY_CHANGE_ZOOM
:
734 msg_Warn(vd
, "Unsupported control query %d", query
);
739 msg_Warn(vd
, "Unknown control query %d", query
);
746 static void vd_manage(vout_display_t
*vd
)
748 vout_display_sys_t
*sys
= vd
->sys
;
749 unsigned width
, height
;
751 vlc_mutex_lock(&sys
->manage_mutex
);
753 if (sys
->need_configure_display
) {
754 if (query_resolution(vd
, sys
->display_id
, &width
, &height
) >= 0) {
755 sys
->display_width
= width
;
756 sys
->display_height
= height
;
757 // vout_window_ReportSize(vd->cfg->window, width, height);
760 sys
->need_configure_display
= false;
763 vlc_mutex_unlock(&sys
->manage_mutex
);
767 static int attach_subpics(vout_display_t
* const vd
, vout_display_sys_t
* const sys
,
768 subpicture_t
* const subpicture
)
772 if (sys
->vzc
== NULL
) {
773 mmal_decoder_device_t
*devsys
= GetMMALDeviceOpaque(sys
->dec_dev
);
774 if ((sys
->vzc
= hw_mmal_vzc_pool_new(devsys
->is_cma
)) == NULL
)
776 msg_Err(vd
, "Failed to allocate VZC");
781 // Attempt to import the subpics
782 for (subpicture_t
* spic
= subpicture
; spic
!= NULL
; spic
= spic
->p_next
)
784 for (subpicture_region_t
*sreg
= spic
->p_region
; sreg
!= NULL
; sreg
= sreg
->p_next
) {
785 picture_t
*const src
= sreg
->p_picture
;
787 // At this point I think the subtitles are being placed in the
788 // coord space of the cfg rectangle
789 if ((sys
->subpic_bufs
[n
] = hw_mmal_vzc_buf_from_pic(sys
->vzc
,
791 (MMAL_RECT_T
){.width
= vd
->cfg
->display
.width
, .height
=vd
->cfg
->display
.height
},
792 sreg
->i_x
, sreg
->i_y
,
796 msg_Err(vd
, "Failed to allocate vzc buffer for subpic");
808 static void vd_prepare(vout_display_t
*vd
, picture_t
*p_pic
,
809 subpicture_t
*subpicture
, vlc_tick_t date
)
812 vout_display_sys_t
* const sys
= vd
->sys
;
816 if (sys
->force_config
||
817 p_pic
->format
.i_frame_rate
!= sys
->i_frame_rate
||
818 p_pic
->format
.i_frame_rate_base
!= sys
->i_frame_rate_base
||
819 p_pic
->b_progressive
!= sys
->b_progressive
||
820 p_pic
->b_top_field_first
!= sys
->b_top_field_first
)
822 sys
->force_config
= false;
823 sys
->b_top_field_first
= p_pic
->b_top_field_first
;
824 sys
->b_progressive
= p_pic
->b_progressive
;
825 sys
->i_frame_rate
= p_pic
->format
.i_frame_rate
;
826 sys
->i_frame_rate_base
= p_pic
->format
.i_frame_rate_base
;
827 configure_display(vd
, NULL
, &p_pic
->format
);
830 // Subpics can either turn up attached to the main pic or in the
831 // subpic list here - if they turn up here then process into temp
833 if (subpicture
!= NULL
) {
834 attach_subpics(vd
, sys
, subpicture
);
838 if (want_copy(&vd
->fmt
)) {
839 if (sys
->copy_buf
!= NULL
) {
840 msg_Err(vd
, "Copy buf not NULL");
841 mmal_buffer_header_release(sys
->copy_buf
);
842 sys
->copy_buf
= NULL
;
845 MMAL_BUFFER_HEADER_T
* const buf
= mmal_queue_wait(sys
->copy_pool
->queue
);
847 mmal_decoder_device_t
*devsys
= GetMMALDeviceOpaque(sys
->dec_dev
);
848 hw_mmal_copy_pic_to_buf(buf
->data
, &buf
->length
, sys
->input
->format
, p_pic
,
850 buf
->flags
= MMAL_BUFFER_HEADER_FLAG_FRAME_END
;
855 if (isp_check(vd
, sys
) != MMAL_SUCCESS
) {
861 static void vd_control_port_cb(MMAL_PORT_T
*port
, MMAL_BUFFER_HEADER_T
*buffer
)
863 vout_display_t
*vd
= (vout_display_t
*)port
->userdata
;
864 MMAL_STATUS_T status
;
866 if (buffer
->cmd
== MMAL_EVENT_ERROR
) {
867 status
= *(uint32_t *)buffer
->data
;
868 msg_Err(vd
, "MMAL error %"PRIx32
" \"%s\"", status
, mmal_status_to_string(status
));
871 mmal_buffer_header_release(buffer
);
874 static void tvservice_cb(void *callback_data
, uint32_t reason
, uint32_t param1
, uint32_t param2
)
880 vout_display_t
*vd
= (vout_display_t
*)callback_data
;
881 vout_display_sys_t
*sys
= vd
->sys
;
883 vlc_mutex_lock(&sys
->manage_mutex
);
884 sys
->need_configure_display
= true;
885 vlc_mutex_unlock(&sys
->manage_mutex
);
888 static int set_latency_target(vout_display_t
*vd
, bool enable
)
890 vout_display_sys_t
*sys
= vd
->sys
;
891 MMAL_STATUS_T status
;
893 MMAL_PARAMETER_AUDIO_LATENCY_TARGET_T latency_target
= {
894 .hdr
= { MMAL_PARAMETER_AUDIO_LATENCY_TARGET
, sizeof(latency_target
) },
895 .enable
= enable
? MMAL_TRUE
: MMAL_FALSE
,
899 .speed_factor
= -135,
904 status
= mmal_port_parameter_set(sys
->input
, &latency_target
.hdr
);
905 if (status
!= MMAL_SUCCESS
) {
906 msg_Err(vd
, "Failed to configure latency target on input port %s (status=%"PRIx32
" %s)",
907 sys
->input
->name
, status
, mmal_status_to_string(status
));
914 static void adjust_refresh_rate(vout_display_t
*vd
, const video_format_t
*fmt
)
916 vout_display_sys_t
*sys
= vd
->sys
;
917 TV_DISPLAY_STATE_T display_state
;
918 TV_SUPPORTED_MODE_NEW_T supported_modes
[VC_TV_MAX_MODE_IDS
];
919 char response
[20]; /* answer is hvs_update_fields=%1d */
921 double frame_rate
= (double)fmt
->i_frame_rate
/ fmt
->i_frame_rate_base
;
923 double best_score
, score
;
926 vc_tv_get_display_state_id(sys
->display_id
, &display_state
);
927 if(display_state
.display
.hdmi
.mode
!= HDMI_MODE_OFF
) {
928 num_modes
= vc_tv_hdmi_get_supported_modes_new_id(sys
->display_id
, display_state
.display
.hdmi
.group
,
929 supported_modes
, VC_TV_MAX_MODE_IDS
, NULL
, NULL
);
931 for (i
= 0; i
< num_modes
; ++i
) {
932 TV_SUPPORTED_MODE_NEW_T
*mode
= &supported_modes
[i
];
933 if (!sys
->native_interlaced
) {
934 if (mode
->width
!= display_state
.display
.hdmi
.width
||
935 mode
->height
!= display_state
.display
.hdmi
.height
||
936 mode
->scan_mode
== HDMI_INTERLACED
)
939 if (mode
->width
!= vd
->fmt
.i_visible_width
||
940 mode
->height
!= vd
->fmt
.i_visible_height
)
942 if (mode
->scan_mode
!= sys
->b_progressive
? HDMI_NONINTERLACED
: HDMI_INTERLACED
)
946 score
= fmod(supported_modes
[i
].frame_rate
, frame_rate
);
947 if((best_id
< 0) || (score
< best_score
)) {
953 if((best_id
>= 0) && (display_state
.display
.hdmi
.mode
!= supported_modes
[best_id
].code
)) {
954 msg_Info(vd
, "Setting HDMI refresh rate to %"PRIu32
,
955 supported_modes
[best_id
].frame_rate
);
956 vc_tv_hdmi_power_on_explicit_new_id(sys
->display_id
, HDMI_MODE_HDMI
,
957 supported_modes
[best_id
].group
,
958 supported_modes
[best_id
].code
);
961 if (sys
->native_interlaced
&&
962 supported_modes
[best_id
].scan_mode
== HDMI_INTERLACED
) {
963 char hvs_mode
= sys
->b_top_field_first
? '1' : '2';
964 if (vc_gencmd(response
, sizeof(response
), "hvs_update_fields %c",
965 hvs_mode
) != 0 || response
[18] != hvs_mode
)
966 msg_Warn(vd
, "Could not set hvs field mode");
968 msg_Info(vd
, "Configured hvs field mode for interlaced %s playback",
969 sys
->b_top_field_first
? "tff" : "bff");
974 static void maintain_phase_sync(vout_display_t
*vd
)
976 MMAL_PARAMETER_VIDEO_RENDER_STATS_T render_stats
= {
977 .hdr
= { MMAL_PARAMETER_VIDEO_RENDER_STATS
, sizeof(render_stats
) },
979 int32_t frame_duration
= CLOCK_FREQ
/
980 ((double)vd
->sys
->i_frame_rate
/
981 vd
->sys
->i_frame_rate_base
);
982 vout_display_sys_t
*sys
= vd
->sys
;
983 int32_t phase_offset
;
984 MMAL_STATUS_T status
;
986 status
= mmal_port_parameter_get(sys
->input
, &render_stats
.hdr
);
987 if (status
!= MMAL_SUCCESS
) {
988 msg_Err(vd
, "Failed to read render stats on control port %s (status=%"PRIx32
" %s)",
989 sys
->input
->name
, status
, mmal_status_to_string(status
));
993 if (render_stats
.valid
) {
995 msg_Dbg(vd
, "render_stats: match: %u, period: %u ms, phase: %u ms, hvs: %u",
996 render_stats
.match
, render_stats
.period
/ 1000, render_stats
.phase
/ 1000,
997 render_stats
.hvs_status
);
1000 if (render_stats
.phase
> 0.1 * frame_duration
&&
1001 render_stats
.phase
< 0.75 * frame_duration
)
1004 phase_offset
= frame_duration
* PHASE_OFFSET_TARGET
- render_stats
.phase
;
1005 if (phase_offset
< 0)
1006 phase_offset
+= frame_duration
;
1008 phase_offset
%= frame_duration
;
1010 sys
->phase_offset
+= phase_offset
;
1011 sys
->phase_offset
%= frame_duration
;
1012 msg_Dbg(vd
, "Apply phase offset of %"PRId32
" ms (total offset %"PRId32
" ms)",
1013 phase_offset
/ 1000, sys
->phase_offset
/ 1000);
1015 /* Reset the latency target, so that it does not get confused
1016 * by the jump in the offset */
1017 set_latency_target(vd
, false);
1018 set_latency_target(vd
, true);
1022 static void CloseMmalVout(vout_display_t
* vd
)
1024 vout_display_sys_t
* const sys
= vd
->sys
;
1025 char response
[20]; /* answer is hvs_update_fields=%1d */
1029 vc_tv_unregister_callback_full(tvservice_cb
, vd
);
1031 // Shouldn't be anything here - but just in case
1032 for (unsigned int i
= 0; i
!= SUBS_MAX
; ++i
)
1033 if (sys
->subpic_bufs
[i
] != NULL
)
1034 mmal_buffer_header_release(sys
->subpic_bufs
[i
]);
1036 for (unsigned int i
= 0; i
!= SUBS_MAX
; ++i
) {
1037 vout_subpic_t
* const sub
= sys
->subs
+ i
;
1038 if (sub
->component
!= NULL
) {
1039 hw_mmal_subpic_close(&sub
->sub
);
1040 if (sub
->component
->control
->is_enabled
)
1041 mmal_port_disable(sub
->component
->control
);
1042 if (sub
->component
->is_enabled
)
1043 mmal_component_disable(sub
->component
);
1044 mmal_component_release(sub
->component
);
1045 sub
->component
= NULL
;
1049 if (sys
->input
&& sys
->input
->is_enabled
)
1050 mmal_port_disable(sys
->input
);
1052 if (sys
->component
&& sys
->component
->control
->is_enabled
)
1053 mmal_port_disable(sys
->component
->control
);
1055 if (sys
->copy_buf
!= NULL
)
1056 mmal_buffer_header_release(sys
->copy_buf
);
1058 if (sys
->input
!= NULL
&& sys
->copy_pool
!= NULL
)
1059 mmal_port_pool_destroy(sys
->input
, sys
->copy_pool
);
1061 if (sys
->component
&& sys
->component
->is_enabled
)
1062 mmal_component_disable(sys
->component
);
1065 mmal_pool_destroy(sys
->pool
);
1068 mmal_component_release(sys
->component
);
1072 hw_mmal_vzc_pool_release(sys
->vzc
);
1074 if (sys
->native_interlaced
) {
1075 if (vc_gencmd(response
, sizeof(response
), "hvs_update_fields 0") < 0 ||
1076 response
[18] != '0')
1077 msg_Warn(vd
, "Could not reset hvs field mode");
1081 vlc_decoder_device_Release(sys
->dec_dev
);
1087 static const struct {
1090 } display_name_to_num
[] = {
1092 {"hdmi-1", DISPMANX_ID_HDMI0
},
1093 {"hdmi-2", DISPMANX_ID_HDMI1
},
1097 static int find_display_num(const char * name
)
1100 for (i
= 0; display_name_to_num
[i
].name
!= NULL
&& strcasecmp(display_name_to_num
[i
].name
, name
) != 0; ++i
)
1102 return display_name_to_num
[i
].num
;
1105 static int OpenMmalVout(vout_display_t
*vd
, const vout_display_cfg_t
*cfg
,
1106 video_format_t
*fmtp
, vlc_video_context
*vctx
)
1108 vout_display_sys_t
*sys
;
1109 MMAL_DISPLAYREGION_T display_region
;
1110 MMAL_STATUS_T status
;
1111 int ret
= VLC_EGENERIC
;
1112 // At the moment all copy is via I420
1113 const bool needs_copy
= !hw_mmal_chroma_is_mmal(vd
->fmt
.i_chroma
);
1114 const MMAL_FOURCC_T enc_in
= needs_copy
? MMAL_ENCODING_I420
:
1115 vout_vlc_to_mmal_pic_fourcc(vd
->fmt
.i_chroma
);
1117 sys
= calloc(1, sizeof(struct vout_display_sys_t
));
1124 sys
->dec_dev
= vlc_video_context_HoldDevice(vctx
);
1125 if (sys
->dec_dev
&& sys
->dec_dev
->type
!= VLC_DECODER_DEVICE_MMAL
)
1127 vlc_decoder_device_Release(sys
->dec_dev
);
1128 sys
->dec_dev
= NULL
;
1132 if (sys
->dec_dev
== NULL
)
1133 sys
->dec_dev
= vlc_decoder_device_Create(VLC_OBJECT(vd
), cfg
->window
);
1134 if (sys
->dec_dev
== NULL
|| sys
->dec_dev
->type
!= VLC_DECODER_DEVICE_MMAL
)
1136 msg_Err(vd
, "Missing decoder device");
1140 vlc_mutex_init(&sys
->manage_mutex
);
1142 vc_tv_register_callback(tvservice_cb
, vd
);
1144 sys
->layer
= var_InheritInteger(vd
, MMAL_LAYER_NAME
);
1147 const char *display_name
= var_InheritString(vd
, MMAL_DISPLAY_NAME
);
1149 int display_id
= find_display_num(display_name
);
1150 // sys->display_id = display_id < 0 ? vc_tv_get_default_display_id() : display_id;
1151 sys
->display_id
= display_id
>= 0 ? display_id
: DISPMANX_ID_HDMI
;
1152 if (display_id
< -1)
1153 msg_Warn(vd
, "Unknown display device: '%s'", display_name
);
1155 msg_Dbg(vd
, "Display device: %s, qt=%d id=%d display=%d", display_name
,
1156 qt_num
, display_id
, sys
->display_id
);
1159 status
= mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER
, &sys
->component
);
1160 if (status
!= MMAL_SUCCESS
) {
1161 msg_Err(vd
, "Failed to create MMAL component %s (status=%"PRIx32
" %s)",
1162 MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER
, status
, mmal_status_to_string(status
));
1166 sys
->component
->control
->userdata
= (struct MMAL_PORT_USERDATA_T
*)vd
;
1167 status
= mmal_port_enable(sys
->component
->control
, vd_control_port_cb
);
1168 if (status
!= MMAL_SUCCESS
) {
1169 msg_Err(vd
, "Failed to enable control port %s (status=%"PRIx32
" %s)",
1170 sys
->component
->control
->name
, status
, mmal_status_to_string(status
));
1174 sys
->input
= sys
->component
->input
[0];
1175 sys
->input
->userdata
= (struct MMAL_PORT_USERDATA_T
*)vd
;
1177 sys
->input
->format
->encoding
= enc_in
;
1178 sys
->input
->format
->encoding_variant
= 0;
1181 display_set_format(vd
, sys
->input
->format
, false);
1183 status
= port_parameter_set_bool(sys
->input
, MMAL_PARAMETER_ZERO_COPY
, true);
1184 if (status
!= MMAL_SUCCESS
) {
1185 msg_Err(vd
, "Failed to set zero copy on port %s (status=%"PRIx32
" %s)",
1186 sys
->input
->name
, status
, mmal_status_to_string(status
));
1190 status
= mmal_port_format_commit(sys
->input
);
1191 if (status
!= MMAL_SUCCESS
) {
1192 msg_Err(vd
, "Failed to commit format for input port %s (status=%"PRIx32
" %s)",
1193 sys
->input
->name
, status
, mmal_status_to_string(status
));
1197 sys
->input
->buffer_size
= sys
->input
->buffer_size_recommended
;
1200 sys
->input
->buffer_num
= 30;
1203 sys
->input
->buffer_num
= 2;
1204 if ((sys
->copy_pool
= mmal_port_pool_create(sys
->input
, 2, sys
->input
->buffer_size
)) == NULL
)
1206 msg_Err(vd
, "Cannot create copy pool");
1211 if (query_resolution(vd
, sys
->display_id
, &sys
->display_width
, &sys
->display_height
) < 0)
1213 sys
->display_width
= vd
->cfg
->display
.width
;
1214 sys
->display_height
= vd
->cfg
->display
.height
;
1217 place_dest(sys
, vd
->cfg
, &vd
->source
); // Sets sys->dest_rect
1219 display_region
.hdr
.id
= MMAL_PARAMETER_DISPLAYREGION
;
1220 display_region
.hdr
.size
= sizeof(MMAL_DISPLAYREGION_T
);
1221 display_region
.display_num
= sys
->display_id
;
1222 display_region
.fullscreen
= MMAL_FALSE
;
1223 display_src_rect(vd
, &display_region
.src_rect
);
1224 display_region
.dest_rect
= sys
->dest_rect
;
1225 display_region
.layer
= sys
->layer
;
1226 display_region
.set
=
1227 MMAL_DISPLAY_SET_NUM
|
1228 MMAL_DISPLAY_SET_FULLSCREEN
| MMAL_DISPLAY_SET_SRC_RECT
|
1229 MMAL_DISPLAY_SET_DEST_RECT
| MMAL_DISPLAY_SET_LAYER
;
1230 status
= mmal_port_parameter_set(sys
->input
, &display_region
.hdr
);
1231 if (status
!= MMAL_SUCCESS
) {
1232 msg_Err(vd
, "Failed to set display region (status=%"PRIx32
" %s)",
1233 status
, mmal_status_to_string(status
));
1237 status
= mmal_port_enable(sys
->input
, vd_input_port_cb
);
1238 if (status
!= MMAL_SUCCESS
) {
1239 msg_Err(vd
, "Failed to enable input port %s (status=%"PRIx32
" %s)",
1240 sys
->input
->name
, status
, mmal_status_to_string(status
));
1244 status
= mmal_component_enable(sys
->component
);
1245 if (status
!= MMAL_SUCCESS
) {
1246 msg_Err(vd
, "Failed to enable component %s (status=%"PRIx32
" %s)",
1247 sys
->component
->name
, status
, mmal_status_to_string(status
));
1251 if ((sys
->pool
= mmal_pool_create(sys
->input
->buffer_num
, 0)) == NULL
)
1253 msg_Err(vd
, "Failed to create input pool");
1257 for (unsigned int i
= 0; i
!= SUBS_MAX
; ++i
) {
1258 vout_subpic_t
* const sub
= sys
->subs
+ i
;
1259 if ((status
= mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER
, &sub
->component
)) != MMAL_SUCCESS
)
1261 msg_Dbg(vd
, "Failed to create subpic component %d", i
);
1264 sub
->component
->control
->userdata
= (struct MMAL_PORT_USERDATA_T
*)vd
;
1265 if ((status
= mmal_port_enable(sub
->component
->control
, vd_control_port_cb
)) != MMAL_SUCCESS
) {
1266 msg_Err(vd
, "Failed to enable control port %s on sub %d (status=%"PRIx32
" %s)",
1267 sys
->component
->control
->name
, i
, status
, mmal_status_to_string(status
));
1270 if ((status
= hw_mmal_subpic_open(VLC_OBJECT(vd
), &sub
->sub
, sub
->component
->input
[0],
1271 sys
->display_id
, sys
->layer
+ i
+ 1)) != MMAL_SUCCESS
) {
1272 msg_Dbg(vd
, "Failed to open subpic %d", i
);
1275 if ((status
= mmal_component_enable(sub
->component
)) != MMAL_SUCCESS
)
1277 msg_Dbg(vd
, "Failed to enable subpic component %d", i
);
1282 // If we can't deal with it directly ask for I420
1283 fmtp
->i_chroma
= req_chroma(fmtp
);
1285 vd
->info
= (vout_display_info_t
){
1286 .subpicture_chromas
= hw_mmal_vzc_subpicture_chromas
1289 vd
->prepare
= vd_prepare
;
1290 vd
->display
= vd_display
;
1291 vd
->control
= vd_control
;
1292 vd
->close
= CloseMmalVout
;