preparser: art: use new md5 API
[vlc.git] / modules / hw / mmal / vout.c
blobee48a6a552dbc0c158e85b1b4e6f43b0ede4139c
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 *****************************************************************************/
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_vout_display.h>
33 #include "mmal_picture.h"
35 #include <bcm_host.h>
36 #include <interface/mmal/mmal.h>
37 #include <interface/mmal/util/mmal_util.h>
38 #include <interface/mmal/util/mmal_default_components.h>
40 #include "subpic.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 *);
70 #define SUBS_MAX 4
73 vlc_module_begin()
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
88 vlc_module_end()
90 typedef struct vout_subpic_s {
91 MMAL_COMPONENT_T *component;
92 subpic_reg_stash_t sub;
93 } vout_subpic_t;
95 struct vout_display_sys_t {
96 vlc_mutex_t manage_mutex;
98 vlc_decoder_device *dec_dev;
99 MMAL_COMPONENT_T *component;
100 MMAL_PORT_T *input;
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 */
107 int display_id;
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 */
124 bool b_progressive;
125 bool force_config;
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;
136 MMAL_PORT_T * input;
137 MMAL_PORT_T * output;
138 MMAL_QUEUE_T * out_q;
139 MMAL_POOL_T * in_pool;
140 MMAL_POOL_T * out_pool;
141 bool pending;
142 } isp;
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;
152 // ISP setup
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)
167 switch (fcc){
168 case VLC_CODEC_MMAL_OPAQUE:
169 return MMAL_ENCODING_OPAQUE;
170 case VLC_CODEC_I420:
171 return MMAL_ENCODING_I420;
172 default:
173 break;
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;
190 v_fmt->crop.x = 0;
191 v_fmt->crop.y = 0;
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) {
195 v_fmt->par.num = 1;
196 v_fmt->par.den = 1;
197 } else {
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)
217 VLC_UNUSED(port);
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);
246 else
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)
258 return;
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)
278 MMAL_STATUS_T err;
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");
285 return err;
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");
293 return err;
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");
301 return err;
304 return MMAL_SUCCESS;
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;
310 VLC_UNUSED(vd);
312 if (isp->component == NULL)
313 return;
315 isp_flush(isp);
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);
325 isp->out_q = NULL;
328 if (isp->out_pool != NULL) {
329 mmal_port_pool_destroy(isp->output, isp->out_pool);
330 isp->out_pool = NULL;
333 isp->input = NULL;
334 isp->output = NULL;
336 mmal_component_release(isp->component);
337 isp->component = NULL;
339 return;
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;
346 VLC_UNUSED(pool);
347 if (isp->output->is_enabled) {
348 mmal_buffer_header_reset(buffer);
349 if (mmal_port_send_buffer(isp->output, buffer) == MMAL_SUCCESS)
350 return MMAL_FALSE;
352 return MMAL_TRUE;
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;
358 MMAL_STATUS_T err;
360 if ((err = mmal_component_create(MMAL_COMPONENT_ISP_RESIZER, &isp->component)) != MMAL_SUCCESS) {
361 msg_Err(vd, "Cannot create ISP component");
362 return err;
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");
370 goto fail;
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)
377 goto fail;
379 if ((err = mmal_port_format_commit(isp->input)) != MMAL_SUCCESS) {
380 msg_Err(vd, "Failed to set ISP input format");
381 goto fail;
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");
390 goto fail;
393 if ((isp->out_q = mmal_queue_create()) == NULL)
395 err = MMAL_ENOMEM;
396 goto fail;
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)
402 goto fail;
404 if ((err = mmal_port_format_commit(isp->output)) != MMAL_SUCCESS) {
405 msg_Err(vd, "Failed to set ISP input format");
406 goto fail;
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");
416 goto fail;
419 mmal_pool_callback_set(isp->out_pool, isp_out_pool_cb, isp);
421 if ((err = isp_prepare(vd, isp)) != MMAL_SUCCESS)
422 goto fail;
424 return MMAL_SUCCESS;
426 fail:
427 isp_close(vd, vd_sys);
428 return err;
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
441 else if (has_isp)
443 // ISP active but we don't want it
444 isp_flush(isp);
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);
450 else
452 // ISP closed but we want it
453 return isp_setup(vd, vd_sys);
456 return MMAL_SUCCESS;
459 /* TV service */
460 static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1,
461 uint32_t param2);
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);
465 // Mmal
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)
472 VLC_UNUSED(port);
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};
480 int ret = 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;
492 } else {
493 msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state);
494 ret = -1;
496 } else {
497 msg_Warn(vd, "Failed to query display resolution");
498 ret = -1;
501 return ret;
504 static MMAL_RECT_T
505 place_to_mmal_rect(const vout_display_place_t place)
507 return (MMAL_RECT_T){
508 .x = place.x,
509 .y = place.y,
510 .width = place.width,
511 .height = place.height
515 static void
516 place_dest(vout_display_sys_t * const sys,
517 const vout_display_cfg_t * const cfg, const video_format_t * fmt)
519 video_format_t tfmt;
521 // Fix SAR if unknown
522 if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) {
523 tfmt = *fmt;
524 tfmt.i_sar_den = 1;
525 tfmt.i_sar_num = 1;
526 fmt = &tfmt;
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;
549 if (!cfg && !fmt)
551 msg_Err(vd, "Missing cfg & fmt");
552 return -EINVAL;
555 isp_check(vd, sys);
557 if (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));
565 return -EINVAL;
567 } else {
568 fmt = &vd->source;
571 if (!cfg)
572 cfg = vd->cfg;
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));
589 return -EINVAL;
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);
599 return 0;
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;
613 MMAL_STATUS_T err;
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");
619 goto fail;
621 // Stuff into input
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");
630 goto fail;
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");
640 goto fail;
643 else
645 MMAL_BUFFER_HEADER_T *const pic_buf = hw_mmal_pic_buf_replicated(p_pic, sys->pool);
646 if (pic_buf == NULL)
648 msg_Err(vd, "Replicated buffer get fail");
649 goto 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");
667 goto fail;
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) {
677 int rv;
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,
684 &p_pic->format,
685 &sys->dest_rect,
686 p_pic->date)) == 0)
687 break;
688 else if (rv < 0)
689 goto fail;
693 fail:
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;
708 VLC_UNUSED(args);
710 switch (query) {
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)
715 ret = VLC_SUCCESS;
716 break;
719 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
720 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
721 if (configure_display(vd, NULL, &vd->source) >= 0)
722 ret = VLC_SUCCESS;
723 break;
725 case VOUT_DISPLAY_RESET_PICTURES:
726 msg_Warn(vd, "Reset Pictures");
727 kill_pool(sys);
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
730 ret = VLC_SUCCESS;
731 break;
733 case VOUT_DISPLAY_CHANGE_ZOOM:
734 msg_Warn(vd, "Unsupported control query %d", query);
735 ret = VLC_SUCCESS;
736 break;
738 default:
739 msg_Warn(vd, "Unknown control query %d", query);
740 break;
743 return ret;
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)
770 unsigned int n = 0;
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");
777 return VLC_ENOMEM;
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,
790 src,
791 (MMAL_RECT_T){.width = vd->cfg->display.width, .height=vd->cfg->display.height},
792 sreg->i_x, sreg->i_y,
793 sreg->i_alpha,
794 n == 0)) == NULL)
796 msg_Err(vd, "Failed to allocate vzc buffer for subpic");
797 return VLC_ENOMEM;
800 if (++n == SUBS_MAX)
801 return VLC_SUCCESS;
804 return VLC_SUCCESS;
808 static void vd_prepare(vout_display_t *vd, picture_t *p_pic,
809 subpicture_t *subpicture, vlc_tick_t date)
811 VLC_UNUSED(date);
812 vout_display_sys_t * const sys = vd->sys;
814 vd_manage(vd);
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
832 // buffers
833 if (subpicture != NULL) {
834 attach_subpics(vd, sys, subpicture);
837 // *****
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);
846 // Copy 2d
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,
849 devsys->is_cma);
850 buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END;
852 sys->copy_buf = buf;
855 if (isp_check(vd, sys) != MMAL_SUCCESS) {
856 return;
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)
876 VLC_UNUSED(reason);
877 VLC_UNUSED(param1);
878 VLC_UNUSED(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,
896 .filter = 2,
897 .target = 4000,
898 .shift = 3,
899 .speed_factor = -135,
900 .inter_factor = 500,
901 .adj_cap = 20
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));
908 return VLC_EGENERIC;
911 return VLC_SUCCESS;
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 */
920 int num_modes;
921 double frame_rate = (double)fmt->i_frame_rate / fmt->i_frame_rate_base;
922 int best_id = -1;
923 double best_score, score;
924 int i;
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)
937 continue;
938 } else {
939 if (mode->width != vd->fmt.i_visible_width ||
940 mode->height != vd->fmt.i_visible_height)
941 continue;
942 if (mode->scan_mode != sys->b_progressive ? HDMI_NONINTERLACED : HDMI_INTERLACED)
943 continue;
946 score = fmod(supported_modes[i].frame_rate, frame_rate);
947 if((best_id < 0) || (score < best_score)) {
948 best_id = i;
949 best_score = 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");
967 else
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));
990 return;
993 if (render_stats.valid) {
994 #ifndef NDEBUG
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);
998 #endif
1000 if (render_stats.phase > 0.1 * frame_duration &&
1001 render_stats.phase < 0.75 * frame_duration)
1002 return;
1004 phase_offset = frame_duration * PHASE_OFFSET_TARGET - render_stats.phase;
1005 if (phase_offset < 0)
1006 phase_offset += frame_duration;
1007 else
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 */
1027 kill_pool(sys);
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);
1064 if (sys->pool)
1065 mmal_pool_destroy(sys->pool);
1067 if (sys->component)
1068 mmal_component_release(sys->component);
1070 isp_close(vd, sys);
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");
1080 if (sys->dec_dev)
1081 vlc_decoder_device_Release(sys->dec_dev);
1083 free(sys);
1087 static const struct {
1088 const char * name;
1089 int num;
1090 } display_name_to_num[] = {
1091 {"auto", -1},
1092 {"hdmi-1", DISPMANX_ID_HDMI0},
1093 {"hdmi-2", DISPMANX_ID_HDMI1},
1094 {NULL, -2}
1097 static int find_display_num(const char * name)
1099 unsigned int i;
1100 for (i = 0; display_name_to_num[i].name != NULL && strcasecmp(display_name_to_num[i].name, name) != 0; ++i)
1101 /* Loop */;
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));
1118 if (!sys)
1119 return VLC_ENOMEM;
1120 vd->sys = sys;
1122 if (vctx)
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");
1137 goto fail;
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);
1148 int qt_num = -1;
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);
1154 else
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));
1163 goto fail;
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));
1171 goto fail;
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;
1179 sys->i_planes = 1;
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));
1187 goto fail;
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));
1194 goto fail;
1197 sys->input->buffer_size = sys->input->buffer_size_recommended;
1199 if (!needs_copy) {
1200 sys->input->buffer_num = 30;
1202 else {
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");
1207 goto fail;
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));
1234 goto fail;
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));
1241 goto fail;
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));
1248 goto fail;
1251 if ((sys->pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL)
1253 msg_Err(vd, "Failed to create input pool");
1254 goto fail;
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);
1262 goto fail;
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));
1268 goto fail;
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);
1273 goto fail;
1275 if ((status = mmal_component_enable(sub->component)) != MMAL_SUCCESS)
1277 msg_Dbg(vd, "Failed to enable subpic component %d", i);
1278 goto fail;
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;
1295 return VLC_SUCCESS;
1297 fail:
1298 CloseMmalVout(vd);
1300 return ret;