diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0002-Revert-configure-Require-libmodplug-0.8.9.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0002-Revert-configure-Require-libmodplug-0.8.9.patch index 61807b3e1..636575556 100644 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0002-Revert-configure-Require-libmodplug-0.8.9.patch +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0002-Revert-configure-Require-libmodplug-0.8.9.patch @@ -1,5 +1,5 @@ From: Sebastian Ramacher -Date: Mon, 19 Aug 2019 21:08:26 +0200 +Date: Mon, 19 Aug 2019 21:04:56 +0200 Subject: Revert "configure: Require libmodplug >= 0.8.9" Upstream-Status: Inappropriate @@ -12,11 +12,9 @@ This reverts commit 48f014768dc22ecad23d0e9f53c38805a3aff832. configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) -diff --git a/configure.ac b/configure.ac -index df26367..b8580ec 100644 --- a/configure.ac +++ b/configure.ac -@@ -2207,7 +2207,7 @@ AC_ARG_ENABLE(mod, +@@ -2176,7 +2176,7 @@ [AS_HELP_STRING([--disable-mod], [do not use libmodplug (default auto)])]) if test "${enable_mod}" != "no" ; then diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0003-CVE-2022-41325.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0003-CVE-2022-41325.patch deleted file mode 100644 index 41f710967..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0003-CVE-2022-41325.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 4fcace61801f418786c42487c6b06b693ee87666 Mon Sep 17 00:00:00 2001 -From: Romain Vimont -Date: Mon, 19 Sep 2022 17:17:01 +0200 -Subject: [PATCH] vnc: fix possible buffer overflow - -Upstream-Status: Inappropriate - -RPI-Distro repo forks original vlc and applies patches -to enable raspiberry pi support. - -Thanks to 0xMitsurugi [1] from Synacktiv [2] for the bug report and fix. - -[1] https://twitter.com/0xMitsurugi -[2] https://www.synacktiv.com/ - -Fixes #27335 - -(cherry picked from commit 5eb783fd44ed6298db3e38f7765f21c42e4405f9) ---- - modules/access/vnc.c | 23 ++++++++++++++++------- - 1 file changed, 16 insertions(+), 7 deletions(-) - ---- a/modules/access/vnc.c -+++ b/modules/access/vnc.c -@@ -33,6 +33,7 @@ - #ifdef HAVE_CONFIG_H - # include "config.h" - #endif -+#include - - #include - #include -@@ -115,7 +116,7 @@ - int i_cancel_state; - - rfbClient* p_client; -- int i_framebuffersize; -+ size_t i_framebuffersize; - block_t *p_block; - - float f_fps; -@@ -143,11 +144,16 @@ - p_sys->es = NULL; - } - -- int i_width = p_client->width; -- int i_height = p_client->height; -- int i_depth = p_client->format.bitsPerPixel; -+ assert(!(p_client->width & ~0xffff)); // fits in 16 bits -+ uint16_t i_width = p_client->width; - -- switch( i_depth ) -+ assert(!(p_client->height & ~0xffff)); // fits in 16 bits -+ uint16_t i_height = p_client->height; -+ -+ uint8_t i_bits_per_pixel = p_client->format.bitsPerPixel; -+ assert((i_bits_per_pixel & 0x7) == 0); // multiple of 8 -+ -+ switch( i_bits_per_pixel ) - { - case 8: - i_chroma = VLC_CODEC_RGB8; -@@ -180,7 +186,10 @@ - } - - /* Set up framebuffer */ -- p_sys->i_framebuffersize = i_width * i_height * i_depth / 8; -+ if (mul_overflow(i_width, i_height * (i_bits_per_pixel / 8), &p_sys->i_framebuffersize)) { -+ msg_Err(p_demux, "VNC framebuffersize overflow"); -+ return FALSE; -+ } - - /* Reuse unsent block */ - if ( p_sys->p_block ) -@@ -211,7 +220,7 @@ - fmt.video.i_frame_rate_base = 1000; - fmt.video.i_frame_rate = 1000 * p_sys->f_fps; - -- fmt.video.i_bits_per_pixel = i_depth; -+ fmt.video.i_bits_per_pixel = i_bits_per_pixel; - fmt.video.i_rmask = p_client->format.redMax << p_client->format.redShift; - fmt.video.i_gmask = p_client->format.greenMax << p_client->format.greenShift; - fmt.video.i_bmask = p_client->format.blueMax << p_client->format.blueShift; diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0003-Do-not-generate-cache-during-build.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0003-Do-not-generate-cache-during-build.patch new file mode 100644 index 000000000..903b48958 --- /dev/null +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0003-Do-not-generate-cache-during-build.patch @@ -0,0 +1,77 @@ +From: Sebastian Ramacher +Date: Tue, 7 Jul 2020 00:18:39 +0200 +Subject: Do not generate cache during build + +Upstream-Status: Inappropriate + +RPI-Distro repo forks original vlc and applies patches +to enable raspiberry pi support. + +The generated cache is not used in the package. It causes spurious build +failures on the arm64 and ppc64el builds, that are not reproducible +elsewhere. +--- + Makefile.am | 16 ---------------- + bin/Makefile.am | 18 ------------------ + 2 files changed, 34 deletions(-) + +diff --git a/Makefile.am b/Makefile.am +index 5179198..08ad316 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -163,22 +163,6 @@ endif + TESTS = test/run_vlc.sh + dist_noinst_SCRIPTS += test/run_vlc.sh + +-if BUILD_VLC +-############################################################################### +-# Installing plugins cache +-############################################################################### +-install-exec-hook: +- if test "$(build)" = "$(host)"; then \ +- PATH="$(DESTDIR)$(bindir):$$PATH" \ +- LD_LIBRARY_PATH="$(DESTDIR)$(libdir):$$LD_LIBRARY_PATH" \ +- DYLD_LIBRARY_PATH="$(DESTDIR)$(libdir):$$DYLD_LIBRARY_PATH" \ +- "$(DESTDIR)$(vlclibdir)/vlc-cache-gen$(EXEEXT)" \ +- "$(DESTDIR)$(vlclibdir)/plugins" ; \ +- else \ +- echo "Cross-compilation: cache generation skipped!" ; \ +- fi +-endif +- + uninstall-hook: + rm -f -- "$(DESTDIR)$(vlclibdir)/plugins/plugins.dat" + +diff --git a/bin/Makefile.am b/bin/Makefile.am +index 4de299d..a2acc3c 100644 +--- a/bin/Makefile.am ++++ b/bin/Makefile.am +@@ -124,14 +124,6 @@ vlc_cache_gen_LDFLAGS = -Wc,-static + vlc_cache_gen_DEPENDENCIES = vlc_win32_rc.$(OBJEXT) + endif + +-# +-# Plug-ins cache +-# +-if HAVE_DYNAMIC_PLUGINS +-noinst_DATA = ../modules/plugins.dat +-endif +-MOSTLYCLEANFILES = $(noinst_DATA) +- + if HAVE_OSX + if BUILD_VLC + install-data-local: +@@ -139,13 +131,3 @@ install-data-local: + + endif + endif +- +-.PHONY: ../modules/plugins.dat +- +-../modules/plugins.dat: vlc-cache-gen$(EXEEXT) +- $(AM_V_at)rm -f ../modules/plugins.dat +- $(AM_V_GEN)if test "$(build)" = "$(host)"; then \ +- ./vlc-cache-gen$(EXEEXT) `realpath ../modules` ; \ +- else \ +- echo "Cross-compilation: cache generation skipped!" ; \ +- fi diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0004-mmal_20.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0004-mmal_35.patch similarity index 55% rename from dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0004-mmal_20.patch rename to dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0004-mmal_35.patch index ab317303d..6bb601756 100644 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0004-mmal_20.patch +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0004-mmal_35.patch @@ -5,7 +5,33 @@ to enable raspiberry pi support. --- a/configure.ac +++ b/configure.ac -@@ -3478,6 +3478,9 @@ dnl +@@ -3081,6 +3081,21 @@ dnl OpenGL ES 2: depends on EGL 1.1 + PKG_ENABLE_MODULES_VLC([GLES2], [], [glesv2], [OpenGL ES v2 support], [disabled]) + + dnl ++dnl DRM ++dnl ++AC_ARG_ENABLE(drm, ++ [ --enable-drm DRM output support (default enabled)],, [ ++ enable_drm="yes" ++]) ++ ++have_drm="no" ++AS_IF([test "${enable_drm}" != "no"], [ ++ PKG_CHECK_MODULES(DRM, [libdrm]) ++ have_drm="yes" ++]) ++AM_CONDITIONAL([HAVE_DRM], [test "${have_drm}" = "yes"]) ++ ++dnl + dnl Xlib + dnl + +@@ -3444,20 +3459,24 @@ AM_CONDITIONAL([HAVE_KVA], [test "${have + dnl + dnl MMAL plugin + dnl ++have_mmal="no" AC_ARG_ENABLE(mmal, AS_HELP_STRING([--enable-mmal], [Multi-Media Abstraction Layer (MMAL) hardware plugin (default enable)])) @@ -15,16 +41,21 @@ to enable raspiberry pi support. if test "${enable_mmal}" != "no"; then VLC_SAVE_FLAGS LDFLAGS="${LDFLAGS} -L/opt/vc/lib -lvchostif" -@@ -3488,7 +3491,7 @@ if test "${enable_mmal}" != "no"; then +- CPPFLAGS="${CPPFLAGS} -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux" ++ CPPFLAGS="${CPPFLAGS} -idirafter /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux" + AC_CHECK_HEADERS(interface/mmal/mmal.h, + [ AC_CHECK_LIB(bcm_host, vc_tv_unregister_callback_full, [ + have_mmal="yes" VLC_ADD_PLUGIN([mmal]) VLC_ADD_LDFLAGS([mmal],[ -L/opt/vc/lib ]) - VLC_ADD_CFLAGS([mmal],[ -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ]) +- VLC_ADD_CFLAGS([mmal],[ -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ]) - VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif ]) ], [ ++ VLC_ADD_CFLAGS([mmal],[ -idirafter /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ]) + VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif -lvchiq_arm -lvcsm ]) ], [ AS_IF([test "${enable_mmal}" = "yes"], [ AC_MSG_ERROR([Cannot find bcm library...]) ], [ AC_MSG_WARN([Cannot find bcm library...]) ]) -@@ -3500,6 +3503,7 @@ if test "${enable_mmal}" != "no"; then +@@ -3469,6 +3488,7 @@ if test "${enable_mmal}" != "no"; then VLC_RESTORE_FLAGS fi AM_CONDITIONAL([HAVE_MMAL], [test "${have_mmal}" = "yes"]) @@ -34,7 +65,7 @@ to enable raspiberry pi support. dnl evas plugin --- a/include/vlc_fourcc.h +++ b/include/vlc_fourcc.h -@@ -365,6 +365,11 @@ +@@ -374,6 +374,11 @@ /* Broadcom MMAL opaque buffer type */ #define VLC_CODEC_MMAL_OPAQUE VLC_FOURCC('M','M','A','L') @@ -46,12522 +77,14942 @@ to enable raspiberry pi support. /* DXVA2 opaque video surface for use with D3D9 */ #define VLC_CODEC_D3D9_OPAQUE VLC_FOURCC('D','X','A','9') /* 4:2:0 8 bpc */ ---- a/modules/hw/mmal/Makefile.am -+++ b/modules/hw/mmal/Makefile.am -@@ -1,23 +1,57 @@ - include $(top_srcdir)/modules/common.am - mmaldir = $(pluginsdir)/mmal - --AM_CFLAGS += $(CFLAGS_mmal) --AM_LDFLAGS += -rpath '$(mmaldir)' $(LDFLAGS_mmal) -+AM_CFLAGS += -pthread $(CFLAGS_mmal) -+AM_LDFLAGS += -pthread -rpath '$(mmaldir)' $(LDFLAGS_mmal) - --libmmal_vout_plugin_la_SOURCES = vout.c mmal_picture.c mmal_picture.h -+libmmal_vout_plugin_la_SOURCES = vout.c mmal_cma.c mmal_picture.c subpic.c\ -+ mmal_cma.h mmal_picture.h subpic.h transform_ops.h\ -+ mmal_piccpy_neon.S - libmmal_vout_plugin_la_CFLAGS = $(AM_CFLAGS) --libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm -+libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm -lX11 -lXrandr - libmmal_vout_plugin_la_LIBADD = $(LIBS_mmal) - mmal_LTLIBRARIES = libmmal_vout_plugin.la +@@ -383,6 +388,12 @@ + #define VLC_CODEC_D3D11_OPAQUE VLC_FOURCC('D','X','1','1') /* 4:2:0 8 bpc */ + #define VLC_CODEC_D3D11_OPAQUE_10B VLC_FOURCC('D','X','1','0') /* 4:2:0 10 bpc */ + ++/* DRM Prime */ ++#define VLC_CODEC_DRM_PRIME_I420 VLC_FOURCC('D','P','V','0') ++#define VLC_CODEC_DRM_PRIME_NV12 VLC_FOURCC('D','P','N','1') ++#define VLC_CODEC_DRM_PRIME_SAND8 VLC_FOURCC('D','P','S','8') ++#define VLC_CODEC_DRM_PRIME_SAND30 VLC_FOURCC('D','P','S','3') ++ + /* CVPixelBuffer opaque buffer type */ + #define VLC_CODEC_CVPX_NV12 VLC_FOURCC('C','V','P','N') + #define VLC_CODEC_CVPX_UYVY VLC_FOURCC('C','V','P','Y') +--- a/include/vlc_opengl.h ++++ b/include/vlc_opengl.h +@@ -68,6 +68,9 @@ struct vlc_gl_t + const int32_t *attrib_list); + /* call eglDestroyImageKHR() with current display, can be NULL */ + bool (*destroyImageKHR)(vlc_gl_t *, void *image); ++ bool (*queryDmaBufModifiersEXT)(vlc_gl_t *gl, uint32_t format, ++ unsigned int max_modifiers, uint64_t *modifiers, ++ unsigned int *external_only, int32_t *num_modifiers); + } egl; + /* if ext == VLC_GL_EXT_WGL */ + struct +--- a/modules/Makefile.am ++++ b/modules/Makefile.am +@@ -10,9 +10,7 @@ EXTRA_SUBDIRS = \ + + SUBDIRS = . + DIST_SUBDIRS = . $(EXTRA_SUBDIRS) +-if HAVE_MMAL + SUBDIRS += hw/mmal +-endif + + TESTS = + +@@ -34,6 +32,7 @@ include demux/Makefile.am + include gui/Makefile.am + include hw/d3d9/Makefile.am + include hw/d3d11/Makefile.am ++include hw/drm/Makefile.am + include hw/vaapi/Makefile.am + include hw/vdpau/Makefile.am + include keystore/Makefile.am +--- a/modules/audio_filter/converter/tospdif.c ++++ b/modules/audio_filter/converter/tospdif.c +@@ -484,6 +484,7 @@ static int write_buffer_dtshd( filter_t + p_in_buf->i_buffer ) != VLC_SUCCESS ) + return SPDIF_ERROR; + unsigned i_period = p_filter->fmt_out.audio.i_rate ++ * (p_filter->fmt_out.audio.i_bytes_per_frame / p_filter->fmt_out.audio.i_frame_length) / 4 + * core.i_frame_length / core.i_rate; + + int i_subtype = dtshd_get_subtype( i_period ); +--- a/modules/audio_output/alsa.c ++++ b/modules/audio_output/alsa.c +@@ -39,6 +39,8 @@ + #include + #include --libmmal_codec_plugin_la_SOURCES = codec.c -+libmmal_codec_plugin_la_SOURCES = codec.c mmal_cma.c mmal_picture.c subpic.c\ -+ mmal_cma.h mmal_picture.h subpic.h transform_ops.h\ -+ blend_rgba_neon.S mmal_piccpy_neon.S - libmmal_codec_plugin_la_CFLAGS = $(AM_CFLAGS) - libmmal_codec_plugin_la_LDFLAGS = $(AM_LDFLAGS) - libmmal_codec_plugin_la_LIBADD = $(LIBS_mmal) - mmal_LTLIBRARIES += libmmal_codec_plugin.la ++#define TRACE_ALL 0 ++ + /** Private data for an ALSA PCM playback stream */ + struct aout_sys_t + { +@@ -51,11 +53,19 @@ struct aout_sys_t + bool soft_mute; + float soft_gain; + char *device; ++ unsigned int pause_bytes; ++ ++ vlc_fourcc_t * passthrough_types; + }; + + #include "audio_output/volume.h" + +-#define A52_FRAME_NB 1536 ++enum { ++ PASSTHROUGH_UNSET = -1, ++ PASSTHROUGH_NONE = 0, ++ PASSTHROUGH_SPDIF, ++ PASSTHROUGH_HDMI, ++}; --libmmal_deinterlace_plugin_la_SOURCES = deinterlace.c mmal_picture.c -+libmmal_deinterlace_plugin_la_SOURCES = deinterlace.c mmal_picture.c mmal_cma.c\ -+ mmal_cma.h mmal_picture.h transform_ops.h\ -+ mmal_piccpy_neon.S - libmmal_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) - libmmal_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) - libmmal_deinterlace_plugin_la_LIBADD = $(LIBS_mmal) - mmal_LTLIBRARIES += libmmal_deinterlace_plugin.la + static int Open (vlc_object_t *); + static void Close (vlc_object_t *); +@@ -77,6 +87,22 @@ static const char *const channels_text[] + N_("Surround 5.0"), N_("Surround 5.1"), N_("Surround 7.1"), + }; + ++#define PASSTHROUGH_NAME "alsa-passthrough" ++#define PASSTHROUGH_TEXT N_("Audio passthrough mode") ++#define PASSTHROUGH_LONGTEXT N_("Audio passthrough mode. Defaults to 0 (none)") ++static const int passthrough_modes[] = { ++ PASSTHROUGH_UNSET, PASSTHROUGH_NONE, PASSTHROUGH_SPDIF, PASSTHROUGH_HDMI, ++}; ++static const char *const passthrough_modes_text[] = { ++ N_("unset"), N_("none"), N_("S/PDIF"), N_("HDMI"), ++}; + -+libmmal_xsplitter_plugin_la_SOURCES = xsplitter.c mmal_picture.c mmal_cma.c\ -+ mmal_cma.h mmal_picture.h transform_ops.h\ -+ mmal_piccpy_neon.S -+libmmal_xsplitter_plugin_la_CFLAGS = $(AM_CFLAGS) -+libmmal_xsplitter_plugin_la_LDFLAGS = $(AM_LDFLAGS) -+libmmal_xsplitter_plugin_la_LIBADD = $(LIBS_mmal) -+mmal_LTLIBRARIES += libmmal_xsplitter_plugin.la ++#define PASSTHROUGH_TYPES_NAME "alsa-passthrough-types" ++#define PASSTHROUGH_TYPES_TEXT "List of codecs to accept for passthrough" ++#define PASSTHROUGH_TYPES_LONGTEXT "List of codecs to accept for passthrough, comma separated. Default is to try everything."\ ++ "If this option is given then " PASSTHROUGH_NAME " defaults to HDMI" ++ ++ + vlc_module_begin () + set_shortname( "ALSA" ) + set_description( N_("ALSA audio output") ) +@@ -88,12 +114,85 @@ vlc_module_begin () + add_integer ("alsa-audio-channels", AOUT_CHANS_FRONT, + AUDIO_CHAN_TEXT, AUDIO_CHAN_LONGTEXT, false) + change_integer_list (channels, channels_text) ++ add_integer (PASSTHROUGH_NAME, PASSTHROUGH_UNSET, PASSTHROUGH_TEXT, ++ PASSTHROUGH_LONGTEXT, false) ++ change_integer_list (passthrough_modes, passthrough_modes_text) ++ add_string(PASSTHROUGH_TYPES_NAME, NULL, PASSTHROUGH_TYPES_TEXT, ++ PASSTHROUGH_TYPES_LONGTEXT, false) + add_sw_gain () + set_capability( "audio output", 150 ) + set_callbacks( Open, Close ) + vlc_module_end () + + ++static vlc_fourcc_t * parse_passthrough(audio_output_t * const aout, const char * const str) ++{ ++ const char * p = str; ++ size_t n = 2; ++ vlc_fourcc_t * rv = NULL; ++ vlc_fourcc_t * f; ++ ++ if (str == NULL) ++ return NULL; + -+libmmal_converter_plugin_la_SOURCES = converter_mmal.c mmal_cma.c mmal_picture.c\ -+ mmal_cma.h mmal_picture.h transform_ops.h\ -+ mmal_piccpy_neon.S -+libmmal_converter_plugin_la_CFLAGS = $(AM_CFLAGS) -+libmmal_converter_plugin_la_LDFLAGS = $(AM_LDFLAGS) -+libmmal_converter_plugin_la_LIBADD = $(LIBS_mmal) -+mmal_LTLIBRARIES += libmmal_converter_plugin.la ++ while (*p != '\0') ++ if (*p++ == ',') ++ ++n; + -+if HAVE_MMAL_AVCODEC -+libmmal_avcodec_plugin_la_SOURCES = mmal_avcodec.c mmal_cma.c mmal_picture.c\ -+ mmal_cma.h mmal_picture.h transform_ops.h\ -+ mmal_piccpy_neon.S -+libmmal_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) -+libmmal_avcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS) -+libmmal_avcodec_plugin_la_LIBADD = $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(LIBS_mmal) -+mmal_LTLIBRARIES += libmmal_avcodec_plugin.la -+endif ++ rv = malloc(sizeof(vlc_fourcc_t) * n); ++ if (rv == NULL) ++ return NULL; ++ f = rv; + ++ if (strcasecmp(str, "none") == 0) ++ goto done; + ---- /dev/null -+++ b/modules/hw/mmal/blend_rgba_neon.S -@@ -0,0 +1,197 @@ -+ .syntax unified -+ .arm -+// .thumb -+ .text -+ .align 16 -+ .arch armv7-a -+ .fpu neon-vfpv4 ++ for (p = str; *p != 0;) ++ { ++ unsigned int i; ++ const char *c = strchrnul(p, ','); ++ vlc_fourcc_t fcc = 0; ++ ++ static const struct { ++ const char * str; ++ vlc_fourcc_t val; ++ } codecs[] = { ++ {.str = "truehd", .val = VLC_CODEC_TRUEHD }, ++ {.str = "mlp", .val = VLC_CODEC_MLP }, ++ {.str = "dts", .val = VLC_CODEC_DTS }, ++ {.str = "dtshd", .val = VLC_CODEC_DTS }, ++ {.str = "ac3", .val = VLC_CODEC_A52 }, ++ {.str = "ac-3", .val = VLC_CODEC_A52 }, ++ {.str = "eac3", .val = VLC_CODEC_EAC3 }, ++ {.str = "eac-3", .val = VLC_CODEC_EAC3 }, ++ {.str = "all", .val = VLC_CODEC_UNKNOWN }, ++ }; + -+@ blend_rgbx_rgba_neon ++ for (i = 0; i != ARRAY_SIZE(codecs); ++i) ++ { ++ if (strncasecmp(p, codecs[i].str, c - p) == 0) ++ { ++ fcc = codecs[i].val; ++ break; ++ } ++ } + -+@ Implements /255 as ((x * 257) + 0x8000) >> 16 -+@ This generates something in the range [(x+126)/255, (x+127)/255] which is good enough ++ if (fcc != 0) ++ *f++ = fcc; ++ else ++ msg_Warn(aout, "Unknown codec type '%.*s'", (int)(c - p), p); + -+@ There is advantage to aligning src and/or dest - dest gives a bit more due to being used twice ++ if (*c == 0) ++ break; + ++ p = c + 1; ++ } + ++done: ++ *f = 0; ++ return rv; ++} + -+@ [r0] RGBx dest loaded into d20-d23 -+@ [r1] RGBA src merge loaded into d16-d19 -+@ r2 plane alpha -+@ r3 count (pixels) + /** Helper for ALSA -> VLC debugging output */ + static void Dump (vlc_object_t *obj, const char *msg, + int (*cb)(void *, snd_output_t *), void *p) +@@ -285,11 +384,20 @@ static int Start (audio_output_t *aout, + { + aout_sys_t *sys = aout->sys; + snd_pcm_format_t pcm_format; /* ALSA sample format */ +- bool spdif = false; ++ unsigned channels; ++ int passthrough = PASSTHROUGH_NONE; ++ snd_pcm_uframes_t periodSizeMax; ++ snd_pcm_uframes_t periodSize; ++ snd_pcm_uframes_t bufferSize; ++ unsigned int req_rate = fmt->i_rate; ++ vlc_fourcc_t req_format = fmt->i_format; ++ ++ msg_Dbg(aout, "Start: Format: %.4s, Chans: %d, Rate:%d", (char*)&fmt->i_format, aout_FormatNbChannels(fmt), fmt->i_rate); + +- if (aout_FormatNbChannels(fmt) == 0) ++ if (aout_FormatNbChannels(fmt) == 0 && AOUT_FMT_LINEAR(fmt)) + return VLC_EGENERIC; + ++ sys->pause_bytes = 0; + switch (fmt->i_format) + { + case VLC_CODEC_FL64: +@@ -308,36 +416,88 @@ static int Start (audio_output_t *aout, + pcm_format = SND_PCM_FORMAT_U8; + break; + default: +- if (AOUT_FMT_SPDIF(fmt)) +- spdif = var_InheritBool (aout, "spdif"); +- if (spdif) ++ if (AOUT_FMT_SPDIF(fmt) || AOUT_FMT_HDMI(fmt)) + { +- fmt->i_format = VLC_CODEC_SPDIFL; ++ if (sys->passthrough_types != NULL) ++ { ++ // VLC_CODEC_UNKNOWN used as explicit "all" ++ const vlc_fourcc_t *p; ++ for (p = sys->passthrough_types; *p != 0 || *p == VLC_CODEC_UNKNOWN; ++p) ++ if (*p == fmt->i_format) ++ break; ++ if (*p == 0) ++ { ++ msg_Dbg(aout, "Codec %.4s not in passthrough-types", (const char *)&fmt->i_format); ++ return VLC_EGENERIC; ++ } ++ } + -+.macro blend_main sR, sG, sB, sA, dR, dG, dB, dA ++ passthrough = var_InheritInteger(aout, PASSTHROUGH_NAME); ++ // Explicit passthrough will override spdif ++ if (passthrough == PASSTHROUGH_UNSET) ++ passthrough = ++ var_InheritBool(aout, "spdif") ? PASSTHROUGH_SPDIF : ++ sys->passthrough_types != NULL ? PASSTHROUGH_HDMI : PASSTHROUGH_NONE; ++ msg_Dbg(aout, "Passthrough %d for format %4.4s", passthrough, (const char *)&fmt->i_format); ++ } + -+ push { r4, lr } ++ if (passthrough != PASSTHROUGH_NONE) ++ { ++ req_format = VLC_CODEC_SPDIFL; + pcm_format = SND_PCM_FORMAT_S16; ++ sys->pause_bytes = 3 * 4; ++ channels = 2; ++ ++ switch (fmt->i_format) { ++ case VLC_CODEC_MLP: ++ case VLC_CODEC_TRUEHD: ++ sys->pause_bytes = 4 * 4; ++ req_rate = fmt->i_rate * 4; ++ channels = 8; ++ break; + -+ vdup.u8 d7, r2 ++ case VLC_CODEC_DTS: ++ { ++ if (passthrough == PASSTHROUGH_SPDIF) ++ break; ++ req_rate = 192000; ++ channels = 8; ++ break; ++ } + -+ subs r3, #8 -+ vmov.u8 d6, #0xff ++ case VLC_CODEC_EAC3: ++ sys->pause_bytes = 4 * 4; ++ req_rate = fmt->i_rate * 4; ++ break; + -+ blt 2f ++ default: ++ break; ++ } + } + else + if (HAVE_FPU) + { +- fmt->i_format = VLC_CODEC_FL32; ++ req_format = VLC_CODEC_FL32; + pcm_format = SND_PCM_FORMAT_FLOAT; + } + else + { +- fmt->i_format = VLC_CODEC_S16N; ++ req_format = VLC_CODEC_S16N; + pcm_format = SND_PCM_FORMAT_S16; + } ++ break; + } + + const char *device = sys->device; + + /* Choose the IEC device for S/PDIF output */ + char sep = '\0'; +- if (spdif) ++ if (passthrough != PASSTHROUGH_NONE) + { + const char *opt = NULL; + + if (!strcmp (device, "default")) +- device = "iec958"; /* TODO: hdmi */ ++ device = (passthrough == PASSTHROUGH_HDMI) ? "hdmi" : "iec958"; + + if (!strncmp (device, "iec958", 6)) + opt = device + 6; +@@ -364,8 +524,12 @@ static int Start (audio_output_t *aout, + FS( 44100) /* def. */ FS( 48000) FS( 32000) + FS( 22050) FS( 24000) + FS( 88200) FS(768000) FS( 96000) +- FS(176400) FS(192000) ++ FS(176400) + #undef FS ++ case 192000: ++ aes3 = (passthrough == PASSTHROUGH_HDMI && channels == 8) ? ++ IEC958_AES3_CON_FS_768000 : IEC958_AES3_CON_FS_192000; ++ break; + default: + aes3 = IEC958_AES3_CON_FS_NOTID; + break; +@@ -430,21 +594,27 @@ static int Start (audio_output_t *aout, + if (snd_pcm_hw_params_test_format (pcm, hw, pcm_format) == 0) + ; + else +- if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_FLOAT) == 0) ++ if (passthrough != PASSTHROUGH_NONE) + { +- fmt->i_format = VLC_CODEC_FL32; ++ msg_Warn(aout, "Failed to set required passthrough format"); ++ goto error; ++ } ++ else ++ if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT) == 0) ++ { ++ req_format = VLC_CODEC_FL32; + pcm_format = SND_PCM_FORMAT_FLOAT; + } + else + if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_S32) == 0) + { +- fmt->i_format = VLC_CODEC_S32N; ++ req_format = VLC_CODEC_S32N; + pcm_format = SND_PCM_FORMAT_S32; + } + else + if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_S16) == 0) + { +- fmt->i_format = VLC_CODEC_S16N; ++ req_format = VLC_CODEC_S16N; + pcm_format = SND_PCM_FORMAT_S16; + } + else +@@ -459,10 +629,10 @@ static int Start (audio_output_t *aout, + msg_Err (aout, "cannot set sample format: %s", snd_strerror (val)); + goto error; + } ++ sys->format = req_format; + + /* Set channels count */ +- unsigned channels; +- if (!spdif) ++ if (passthrough == PASSTHROUGH_NONE) + { + uint16_t map = var_InheritInteger (aout, "alsa-audio-channels"); + +@@ -474,7 +644,6 @@ static int Start (audio_output_t *aout, + else + { + sys->chans_to_reorder = 0; +- channels = 2; + } + + /* By default, ALSA plug will pad missing channels with zeroes, which is +@@ -490,14 +659,26 @@ static int Start (audio_output_t *aout, + } + + /* Set sample rate */ +- val = snd_pcm_hw_params_set_rate_near (pcm, hw, &fmt->i_rate, NULL); ++ sys->rate = req_rate; ++ val = snd_pcm_hw_params_set_rate_near (pcm, hw, &sys->rate, NULL); + if (val) + { + msg_Err (aout, "cannot set sample rate: %s", snd_strerror (val)); + goto error; + } +- sys->rate = fmt->i_rate; ++ if (passthrough != PASSTHROUGH_NONE && sys->rate != req_rate) ++ { ++ msg_Warn(aout, "Passthrough requires rate %d, got %d", req_rate, sys->rate); ++ goto error; ++ } + -+ @ If < 16 bytes to move then don't bother trying to align -+ @ (a) This means the the align doesn't need to worry about r3 underflow -+ @ (b) The overhead would be greater than any gain -+ cmp r3, #8 -+ mov r4, r3 -+ ble 1f ++ bufferSize = req_rate / 10; // 100ms - bigger than this & truehd goes unhappy? ++ periodSize = bufferSize / 4; ++ periodSizeMax = bufferSize / 3; ++ snd_pcm_hw_params_set_period_size_max(pcm, hw, &periodSizeMax, NULL); + ++ snd_pcm_hw_params_set_buffer_size_near(pcm, hw, &bufferSize); ++ snd_pcm_hw_params_set_period_size_near(pcm, hw, &periodSize, NULL); + #if 1 /* work-around for period-long latency outputs (e.g. PulseAudio): */ + param = AOUT_MIN_PREPARE_TIME; + val = snd_pcm_hw_params_set_period_time_near (pcm, hw, ¶m, NULL); +@@ -579,13 +760,12 @@ static int Start (audio_output_t *aout, + } + + /* Setup audio_output_t */ +- if (spdif) +- { +- fmt->i_bytes_per_frame = AOUT_SPDIF_SIZE; +- fmt->i_frame_length = A52_FRAME_NB; +- } ++ fmt->i_frame_length = 1; ++ fmt->i_bytes_per_frame = snd_pcm_frames_to_bytes(pcm, fmt->i_frame_length); ++ fmt->i_channels = channels; ++ fmt->i_rate = sys->rate; ++ fmt->i_format = sys->format; + fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP; +- sys->format = fmt->i_format; + + aout->time_get = TimeGet; + aout->play = Play; +@@ -616,7 +796,7 @@ static int TimeGet (audio_output_t *aout + msg_Err (aout, "cannot estimate delay: %s", snd_strerror (val)); + return -1; + } +- *delay = frames * CLOCK_FREQ / sys->rate; ++ *delay = (uint_fast64_t)frames * CLOCK_FREQ / sys->rate; + return 0; + } + +@@ -627,6 +807,29 @@ static void Play (audio_output_t *aout, + { + aout_sys_t *sys = aout->sys; + ++#if TRACE_ALL ++ static mtime_t last_pts = 0; ++ msg_Dbg(aout, "<<< %s: PTS: %"PRId64" samples: %u, bytes: %zu, delta: %"PRId64, __func__, ++ block->i_pts, block->i_nb_samples, block->i_buffer, ++ block->i_pts - last_pts); ++ last_pts = block->i_pts; ++#endif + -+ @ Align r1 on a 32 byte boundary -+ neg r3, r0 -+ ubfx r3, r3, #2, #3 ++ // S/pdif packets always start with sync so if no sync then this must ++ // be a padding buffer ++ if (sys->pause_bytes != 0 && block->p_buffer[0] == 0) ++ { ++ static const uint8_t pause_le[16] = {0x72, 0xf8, 0x1f, 0x4e, 3, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0}; ++ static const uint8_t pause_be[16] = {0xf8, 0x72, 0x4e, 0x1f, 0, 3, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0}; ++ const uint8_t *const pause = sys->format == VLC_CODEC_SPDIFB ? pause_be : pause_le; ++ size_t n = block->i_buffer / sys->pause_bytes; ++ size_t i; ++ ++ msg_Dbg(aout, "Silence detected"); ++ for (i = 0; i != n; ++i) ++ memcpy(block->p_buffer + i * sys->pause_bytes, pause, sys->pause_bytes); ++ } ++ + if (sys->chans_to_reorder != 0) + aout_ChannelReorder(block->p_buffer, block->i_buffer, + sys->chans_to_reorder, sys->chans_table, sys->format); +@@ -792,7 +995,7 @@ static int DeviceSelect (audio_output_t + static int Open(vlc_object_t *obj) + { + audio_output_t *aout = (audio_output_t *)obj; +- aout_sys_t *sys = malloc (sizeof (*sys)); ++ aout_sys_t *sys = calloc (1, sizeof (*sys)); + + if (unlikely(sys == NULL)) + return VLC_ENOMEM; +@@ -822,6 +1025,12 @@ static int Open(vlc_object_t *obj) + free (ids); + } + ++ { ++ const char *types = var_InheritString(aout, PASSTHROUGH_TYPES_NAME); ++ sys->passthrough_types = parse_passthrough(aout, types); ++ free((void *)types); ++ } + -+ cmp r3, #0 -+ blne 10f + return VLC_SUCCESS; + error: + free (sys); +@@ -833,6 +1042,7 @@ static void Close(vlc_object_t *obj) + audio_output_t *aout = (audio_output_t *)obj; + aout_sys_t *sys = aout->sys; + ++ free (sys->passthrough_types); + free (sys->device); + free (sys); + } +--- a/modules/codec/Makefile.am ++++ b/modules/codec/Makefile.am +@@ -373,6 +373,7 @@ libavcodec_plugin_la_SOURCES = \ + codec/avcodec/subtitle.c \ + codec/avcodec/audio.c \ + codec/avcodec/va.c codec/avcodec/va.h \ ++ codec/avcodec/drm_pic.c codec/avcodec/drm_pic.h \ + codec/avcodec/avcodec.c codec/avcodec/avcodec.h + if ENABLE_SOUT + libavcodec_plugin_la_SOURCES += codec/avcodec/encoder.c +--- a/modules/codec/avcodec/avcodec.c ++++ b/modules/codec/avcodec/avcodec.c +@@ -247,28 +247,51 @@ vlc_module_begin () + #endif + vlc_module_end () + +-AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec, +- const AVCodec **restrict codecp ) ++static const char * ++hw_v4l2m2m_dec_str(const unsigned i_codec_id) ++{ ++ switch( i_codec_id ) ++ { ++ case AV_CODEC_ID_MPEG2VIDEO: ++ return "mpeg2_v4l2m2m"; ++ case AV_CODEC_ID_H264: ++ return "h264_v4l2m2m"; ++ default: ++ break; ++ } ++ return NULL; ++} + -+ sub r3, r4, r3 ++AVCodecContext *ffmpeg_AllocContextHw( decoder_t *p_dec, ++ const AVCodec **restrict codecp, const int hw ) + { + unsigned i_codec_id; + const char *psz_namecodec; + const AVCodec *p_codec = NULL; ++ const char * hw_dec_name = NULL; ++ const char * const psz_decoder = var_InheritString( p_dec, "avcodec-codec" ); ++ ++ // If named decoder do not attempt hw override - wait for non-hw pass ++ if( hw != 0 && psz_decoder != NULL ) ++ return NULL; + + /* *** determine codec type *** */ + if( !GetFfmpegCodec( p_dec->fmt_in.i_cat, p_dec->fmt_in.i_codec, + &i_codec_id, &psz_namecodec ) ) + return NULL; + ++ if( hw != 0 && (hw_dec_name = hw_v4l2m2m_dec_str(i_codec_id)) == NULL ) ++ return NULL; + -+1: -+ vld4.8 {d16, d17, d18, d19}, [r1] + msg_Dbg( p_dec, "using %s %s", AVPROVIDER(LIBAVCODEC), LIBAVCODEC_IDENT ); + + /* Initialization must be done before avcodec_find_decoder() */ + vlc_init_avcodec(VLC_OBJECT(p_dec)); + + /* *** ask ffmpeg for a decoder *** */ +- char *psz_decoder = var_InheritString( p_dec, "avcodec-codec" ); + if( psz_decoder != NULL ) + { +- p_codec = avcodec_find_decoder_by_name( psz_decoder ); ++ p_codec = avcodec_find_decoder_by_name(psz_decoder); + if( !p_codec ) + msg_Err( p_dec, "Decoder `%s' not found", psz_decoder ); + else if( p_codec->id != i_codec_id ) +@@ -279,7 +302,9 @@ AVCodecContext *ffmpeg_AllocContext( dec + } + free( psz_decoder ); + } +- if( !p_codec ) ++ if( hw_dec_name != NULL ) ++ p_codec = avcodec_find_decoder_by_name(hw_dec_name); ++ else if( !p_codec ) + p_codec = avcodec_find_decoder( i_codec_id ); + if( !p_codec ) + { +@@ -299,6 +324,12 @@ AVCodecContext *ffmpeg_AllocContext( dec + return avctx; + } + ++AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec, ++ const AVCodec **restrict codecp ) ++{ ++ return ffmpeg_AllocContextHw(p_dec, codecp, 0); ++} + -+1: -+ vmull.u8 q15, \sA, d7 + /***************************************************************************** + * ffmpeg_OpenCodec: + *****************************************************************************/ +--- a/modules/codec/avcodec/avcodec.h ++++ b/modules/codec/avcodec/avcodec.h +@@ -47,6 +47,7 @@ int InitSubtitleDec( vlc_object_t * ); + void EndSubtitleDec( vlc_object_t * ); + + /* Initialize decoder */ ++AVCodecContext *ffmpeg_AllocContextHw( decoder_t *p_dec, const AVCodec **restrict codecp, const int hw ); + AVCodecContext *ffmpeg_AllocContext( decoder_t *, const AVCodec ** ); + int ffmpeg_OpenCodec( decoder_t *p_dec, AVCodecContext *, const AVCodec * ); + +--- /dev/null ++++ b/modules/codec/avcodec/drm_pic.c +@@ -0,0 +1,62 @@ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif + -+ vld4.8 {d20, d21, d22, d23}, [r0] ++#include ++#include + -+ vsra.u16 q15, q15, #8 -+ subs r3, #8 -+ vrshrn.u16 d31, q15, #8 -+ vsub.u8 d30, d6, d31 ++#include ++#include + -+ vmull.u8 q12, \sR, d31 -+ vmull.u8 q13, \sG, d31 -+ vmull.u8 q14, \sB, d31 -+ addge r1, #32 ++#include "drm_pic.h" + -+ vmlal.u8 q12, \dR, d30 -+ vmlal.u8 q13, \dG, d30 -+ vmlal.u8 q14, \dB, d30 -+ vld4.8 {d16, d17, d18, d19}, [r1] ++static picture_context_t * ++drm_prime_picture_context_new(AVBufferRef * const buf, const void * data, AVBufferRef * const hw_frames_ctx); + -+ vsra.u16 q12, q12, #8 @ * 257/256 -+ vsra.u16 q13, q13, #8 -+ vsra.u16 q14, q14, #8 ++static void ++drm_prime_pic_ctx_destroy(struct picture_context_t * ctx) ++{ ++ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)ctx; ++ av_buffer_unref(&pctx->buf); ++ av_buffer_unref(&pctx->hw_frames_ctx); ++ free(pctx); ++} + -+ vrshrn.u16 \dR, q12, #8 -+ vrshrn.u16 \dG, q13, #8 -+ vrshrn.u16 \dB, q14, #8 -+ vmov.u8 \dA, #0xff ++static picture_context_t * ++drm_prime_pic_ctx_copy(struct picture_context_t * src) ++{ ++ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)src; + -+ vst4.8 {d20, d21, d22, d23}, [r0]! -+ bge 1b -+ add r1, #32 ++ // We could ref count this structure but easier to just create a new one ++ // (which will ref the buf) ++ return drm_prime_picture_context_new(pctx->buf, pctx->desc, pctx->hw_frames_ctx); ++} + -+2: -+ cmp r3, #-8 -+ blgt 10f ++static picture_context_t * ++drm_prime_picture_context_new(AVBufferRef * const buf, const void * data, AVBufferRef * const hw_frames_ctx) ++{ ++ drm_prime_video_sys_t * const pctx = calloc(1, sizeof(*pctx)); + -+ pop { r4, pc } ++ if (pctx == NULL) ++ return NULL; + ++ pctx->cmn.copy = drm_prime_pic_ctx_copy; ++ pctx->cmn.destroy = drm_prime_pic_ctx_destroy; + -+// Partial version -+// Align @ start & deal with tail -+10: -+ lsls r2, r3, #30 @ b2 -> C, b1 -> N -+ mov r2, r0 -+ bcc 1f -+ vld4.8 {d16[0], d17[0], d18[0], d19[0]}, [r1]! -+ vld4.8 {d20[0], d21[0], d22[0], d23[0]}, [r2]! -+ vld4.8 {d16[1], d17[1], d18[1], d19[1]}, [r1]! -+ vld4.8 {d20[1], d21[1], d22[1], d23[1]}, [r2]! -+ vld4.8 {d16[2], d17[2], d18[2], d19[2]}, [r1]! -+ vld4.8 {d20[2], d21[2], d22[2], d23[2]}, [r2]! -+ vld4.8 {d16[3], d17[3], d18[3], d19[3]}, [r1]! -+ vld4.8 {d20[3], d21[3], d22[3], d23[3]}, [r2]! -+1: -+ bpl 1f -+ vld4.8 {d16[4], d17[4], d18[4], d19[4]}, [r1]! -+ vld4.8 {d20[4], d21[4], d22[4], d23[4]}, [r2]! -+ vld4.8 {d16[5], d17[5], d18[5], d19[5]}, [r1]! -+ vld4.8 {d20[5], d21[5], d22[5], d23[5]}, [r2]! -+1: -+ tst r3, #1 -+ beq 1f -+ vld4.8 {d16[6], d17[6], d18[6], d19[6]}, [r1]! -+ vld4.8 {d20[6], d21[6], d22[6], d23[6]}, [r2]! -+1: -+ @ Set conditions for later -+ lsls r2, r3, #30 @ b2 -> C, b1 -> N -+ -+ vmull.u8 q15, \sA, d7 -+ vsra.u16 q15, q15, #8 -+ vrshrn.u16 d31, q15, #8 -+ vsub.u8 d30, d6, d31 -+ -+ vmull.u8 q12, \sR, d31 -+ vmull.u8 q13, \sG, d31 -+ vmull.u8 q14, \sB, d31 -+ -+ vmlal.u8 q12, \dR, d30 -+ vmlal.u8 q13, \dG, d30 -+ vmlal.u8 q14, \dB, d30 -+ -+ vsra.u16 q12, q12, #8 -+ vsra.u16 q13, q13, #8 -+ vsra.u16 q14, q14, #8 ++ pctx->buf = av_buffer_ref(buf); ++ pctx->desc = data; ++ pctx->hw_frames_ctx = !hw_frames_ctx ? NULL : av_buffer_ref(hw_frames_ctx); ++ return &pctx->cmn; ++} + -+ vrshrn.u16 \dR, q12, #8 -+ vrshrn.u16 \dG, q13, #8 -+ vrshrn.u16 \dB, q14, #8 -+ vmov.u8 \dA, #0xff ++int drm_prime_attach_buf_to_pic(picture_t *pic, AVFrame *frame) ++{ ++ if (pic->context) ++ return VLC_EGENERIC; + -+ bcc 1f -+ vst4.8 {d20[0], d21[0], d22[0], d23[0]}, [r0]! -+ vst4.8 {d20[1], d21[1], d22[1], d23[1]}, [r0]! -+ vst4.8 {d20[2], d21[2], d22[2], d23[2]}, [r0]! -+ vst4.8 {d20[3], d21[3], d22[3], d23[3]}, [r0]! -+1: -+ bpl 1f -+ vst4.8 {d20[4], d21[4], d22[4], d23[4]}, [r0]! -+ vst4.8 {d20[5], d21[5], d22[5], d23[5]}, [r0]! -+1: -+ tst r3, #1 -+ bxeq lr -+ vst4.8 {d20[6], d21[6], d22[6], d23[6]}, [r0]! ++ pic->context = drm_prime_picture_context_new(frame->buf[0], frame->data[0], frame->hw_frames_ctx); ++ return VLC_SUCCESS; ++} + -+ bx lr + -+.endm + +--- /dev/null ++++ b/modules/codec/avcodec/drm_pic.h +@@ -0,0 +1,46 @@ ++#include ++#include + -+@ [r0] RGBx dest (Byte order: R, G, B, x) -+@ [r1] RGBA src merge (Byte order: R, G, B, A) -+@ r2 plane alpha -+@ r3 count (pixels) ++struct picture_t; ++struct AVFrame; ++struct AVBufferRef; ++struct AVDRMFrameDescriptor; + -+@ Whilst specified as RGBx+RGBA the only important part is the position of -+@ alpha, the other components are all treated the same ++typedef struct drm_prime_video_sys_s { ++ picture_context_t cmn; // PARENT: Common els at start + -+@ [r0] RGBx dest (Byte order: R, G, B, x) -+@ [r1] RGBA src merge (Byte order: R, G, B, A) - same as above -+@ r2 plane alpha -+@ r3 count (pixels) -+ .align 16 -+ .global blend_rgbx_rgba_neon -+#ifdef __ELF__ -+ .type blend_rgbx_rgba_neon, %function -+#endif -+blend_rgbx_rgba_neon: -+ blend_main d16, d17, d18, d19, d20, d21, d22, d23 ++ struct AVBufferRef * buf; ++ const struct AVDRMFrameDescriptor * desc; ++ struct AVBufferRef * hw_frames_ctx; ++} drm_prime_video_sys_t; + ++static inline const struct AVDRMFrameDescriptor * ++drm_prime_get_desc(picture_t *pic) ++{ ++ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)pic->context; + -+@ [r0] RGBx dest (Byte order: R, G, B, x) -+@ [r1] RGBA src merge (Byte order: B, G, R, A) - B / R swapped -+@ r2 plane alpha -+@ r3 count (pixels) -+ .align 16 -+ .global blend_bgrx_rgba_neon -+#ifdef __ELF__ -+ .type blend_bgrx_rgba_neon, %function -+#endif -+blend_bgrx_rgba_neon: -+ blend_main d18, d17, d16, d19, d20, d21, d22, d23 ++ return !pctx ? NULL : pctx->desc; ++} + ++static inline char safechar(unsigned int x) ++{ ++ const unsigned int c = x & 0xff; ++ return c > ' ' && c < 0x7f ? (char)c : '.'; ++} + ++static inline const char * ++str_fourcc(char buf[5], const uint32_t fcc) ++{ ++ buf[0] = safechar(fcc); ++ buf[1] = safechar(fcc >> 8); ++ buf[2] = safechar(fcc >> 16); ++ buf[3] = safechar(fcc >> 24); ++ buf[4] = 0; ++ return buf; ++} + ---- /dev/null -+++ b/modules/hw/mmal/blend_rgba_neon.h -@@ -0,0 +1,17 @@ -+#ifndef HW_MMAL_BLEND_RGBA_NEON_H -+#define HW_MMAL_BLEND_RGBA_NEON_H ++#define fourcc2str(fcc) \ ++ str_fourcc((char[5]){0}, fcc) + -+#ifdef __cplusplus -+extern "C" { -+#endif ++int drm_prime_attach_buf_to_pic(struct picture_t *pic, struct AVFrame *frame); + -+typedef void blend_neon_fn(void * dest, const void * src, int alpha, unsigned int n); -+extern blend_neon_fn blend_rgbx_rgba_neon; -+extern blend_neon_fn blend_bgrx_rgba_neon; +--- a/modules/codec/avcodec/va.c ++++ b/modules/codec/avcodec/va.c +@@ -57,6 +57,22 @@ vlc_fourcc_t vlc_va_GetChroma(enum Pixel + return VLC_CODEC_D3D9_OPAQUE; + } + break; ++ case AV_PIX_FMT_DRM_PRIME: ++ switch (swfmt) ++ { ++ case AV_PIX_FMT_YUV420P: ++ return VLC_CODEC_DRM_PRIME_I420; ++ case AV_PIX_FMT_NV12: ++ return VLC_CODEC_DRM_PRIME_NV12; ++ case AV_PIX_FMT_RPI4_8: ++ return VLC_CODEC_DRM_PRIME_SAND8; ++ case AV_PIX_FMT_RPI4_10: ++ case AV_PIX_FMT_YUV420P10LE: // When probing this is the swfmt given ++ return VLC_CODEC_DRM_PRIME_SAND30; ++ default: ++ return 0; ++ } ++ break; + + #if LIBAVUTIL_VERSION_CHECK(54, 13, 1, 24, 100) + case AV_PIX_FMT_D3D11VA_VLD: +--- a/modules/codec/avcodec/video.c ++++ b/modules/codec/avcodec/video.c +@@ -29,6 +29,8 @@ + # include "config.h" + #endif + ++#define OPT_RPI 1 + -+#ifdef __cplusplus -+} -+#endif + #include + #include + #include +@@ -46,11 +48,20 @@ + #include "avcodec.h" + #include "va.h" + ++#include "drm_pic.h" + -+#endif + #include "../codec/cc.h" + + /***************************************************************************** + * decoder_sys_t : decoder descriptor + *****************************************************************************/ + ---- /dev/null -+++ b/modules/hw/mmal/blend_test.c -@@ -0,0 +1,180 @@ -+#include -+#include -+#include ++struct hw_setup_params_s { ++ uint32_t i_codec; ++ unsigned int width; ++ unsigned int height; ++}; + -+#include "blend_rgba_neon.h" + struct decoder_sys_t + { + AVCodecContext *p_context; +@@ -59,6 +70,9 @@ struct decoder_sys_t + /* Video decoder specific part */ + date_t pts; + ++ mtime_t dts0; ++ bool dts0_used; ++ + /* Closed captions for decoders */ + cc_data_t cc; + +@@ -88,6 +102,7 @@ struct decoder_sys_t + /* VA API */ + vlc_va_t *p_va; + enum PixelFormat pix_fmt; ++ enum PixelFormat sw_pix_fmt; + int profile; + int level; + +@@ -356,6 +371,13 @@ static int lavc_CopyPicture(decoder_t *d + { + decoder_sys_t *sys = dec->p_sys; + ++ // DRM prime frames are alloced by the decoder ++ // To copy out just attach the buf to the pic ++ if (frame->format == AV_PIX_FMT_DRM_PRIME) ++ { ++ return drm_prime_attach_buf_to_pic(pic, frame); ++ } + -+#define RPI_PROFILE 1 -+#define RPI_PROC_ALLOC 1 -+#include "rpi_prof.h" + vlc_fourcc_t fourcc = FindVlcChroma(frame->format); + if (!fourcc) + { +@@ -417,6 +439,7 @@ static int OpenVideoCodec( decoder_t *p_ + + ctx->bits_per_coded_sample = p_dec->fmt_in.video.i_bits_per_pixel; + p_sys->pix_fmt = AV_PIX_FMT_NONE; ++ p_sys->sw_pix_fmt = AV_PIX_FMT_NONE; + p_sys->profile = -1; + p_sys->level = -1; + cc_Init( &p_sys->cc ); +@@ -458,17 +481,37 @@ static int OpenVideoCodec( decoder_t *p_ + return 0; + } + ++static es_format_t hw_fail; + -+static inline unsigned div255(unsigned v) ++static bool ++hw_check_bad(const es_format_t * const fmt) +{ -+ // This models what we we do in the asm for / 255 -+ // It generates something in the range [(i+126)/255, (i+127)/255] which is good enough -+ return ((v * 257) + 0x8000) >> 16; ++ if (hw_fail.i_codec == fmt->i_codec && ++ hw_fail.video.i_width == fmt->video.i_width && ++ hw_fail.video.i_height == fmt->video.i_height) ++ return true; ++ ++ return false; +} + -+static inline unsigned int a_merge(unsigned int dst, unsigned src, unsigned f) ++static void ++hw_set_bad(const es_format_t * const fmt) +{ -+ return div255((255 - f) * (dst) + src * f); ++ if (fmt->video.i_width != 0 && fmt->video.i_height != 0) ++ hw_fail = *fmt; +} + -+ -+static void merge_line(void * dest, const void * src, int alpha, unsigned int n) + /***************************************************************************** + * InitVideo: initialize the video decoder + ***************************************************************************** + * the ffmpeg codec will be opened, some memory allocated. The vout is not yet + * opened (done after the first decoded frame). + *****************************************************************************/ +-int InitVideoDec( vlc_object_t *obj ) ++static int InitVideoDec2( vlc_object_t *obj, const int hw ) + { + decoder_t *p_dec = (decoder_t *)obj; + const AVCodec *p_codec; +- AVCodecContext *p_context = ffmpeg_AllocContext( p_dec, &p_codec ); ++ AVCodecContext *p_context = ffmpeg_AllocContextHw( p_dec, &p_codec, hw ); + if( p_context == NULL ) + return VLC_EGENERIC; + +@@ -603,6 +646,8 @@ int InitVideoDec( vlc_object_t *obj ) + /* ***** misc init ***** */ + date_Init(&p_sys->pts, 1, 30001); + date_Set(&p_sys->pts, VLC_TS_INVALID); ++ p_sys->dts0 = VLC_TS_INVALID; ++ p_sys->dts0_used = false; + p_sys->b_first_frame = true; + p_sys->i_late_frames = 0; + p_sys->b_from_preroll = false; +@@ -649,6 +694,27 @@ int InitVideoDec( vlc_object_t *obj ) + return VLC_SUCCESS; + } + ++int InitVideoDec( vlc_object_t *obj ) +{ -+ unsigned int i; -+ const uint8_t * s_data = src; -+ uint8_t * d_data = dest; ++ decoder_t * const p_dec = (decoder_t *)obj; + -+ for (i = 0; i != n; ++i) { -+ const uint32_t s_pel = ((const uint32_t *)s_data)[i]; -+ const uint32_t d_pel = ((const uint32_t *)d_data)[i]; -+ const unsigned int a = div255(alpha * (s_pel >> 24)); -+ ((uint32_t *)d_data)[i] = 0xff000000 | -+ (a_merge((d_pel >> 16) & 0xff, (s_pel >> 16) & 0xff, a) << 16) | -+ (a_merge((d_pel >> 8) & 0xff, (s_pel >> 8) & 0xff, a) << 8 ) | -+ (a_merge((d_pel >> 0) & 0xff, (s_pel >> 0) & 0xff, a) << 0 ); ++ // Don't retry something we know failed ++ if (!hw_check_bad(&p_dec->fmt_in)) ++ { ++ if (InitVideoDec2(obj, 1) == 0) ++ return 0; ++ ++ hw_set_bad(&p_dec->fmt_in); ++ msg_Dbg(p_dec, "Set hw fail for %4.4s %dx%d", (char *)&p_dec->fmt_in.i_codec, p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height); + } ++ else ++ { ++ msg_Dbg(p_dec, "Avoid trying hw decoder for %4.4s %dx%d", (char *)&p_dec->fmt_in.i_codec, p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height); ++ } ++ ++ return InitVideoDec2(obj, 0); +} + + /***************************************************************************** + * Flush: + *****************************************************************************/ +@@ -658,6 +724,8 @@ static void Flush( decoder_t *p_dec ) + AVCodecContext *p_context = p_sys->p_context; + + date_Set(&p_sys->pts, VLC_TS_INVALID); /* To make sure we recover properly */ ++ p_sys->dts0 = VLC_TS_INVALID; ++ p_sys->dts0_used = false; + p_sys->i_late_frames = 0; + p_sys->b_draining = false; + cc_Flush( &p_sys->cc ); +@@ -685,6 +753,8 @@ static bool check_block_validity( decode + if( block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) ) + { + date_Set( &p_sys->pts, VLC_TS_INVALID ); /* To make sure we recover properly */ ++ p_sys->dts0 = VLC_TS_INVALID; ++ p_sys->dts0_used = false; + cc_Flush( &p_sys->cc ); + + p_sys->i_late_frames = 0; +@@ -1025,6 +1095,10 @@ static picture_t *DecodeBlock( decoder_t + } + if( b_has_data ) + { ++ /* Remember 1st DTS in case we need to invent a timebase */ ++ if (p_sys->dts0 <= VLC_TS_INVALID) ++ p_sys->dts0 = p_block->i_dts; ++ + pkt->data = p_block->p_buffer; + pkt->size = p_block->i_buffer; + pkt->pts = p_block->i_pts > VLC_TS_INVALID ? p_block->i_pts : AV_NOPTS_VALUE; +@@ -1131,6 +1205,17 @@ static picture_t *DecodeBlock( decoder_t + if( i_pts == AV_NOPTS_VALUE ) + i_pts = date_Get( &p_sys->pts ); + ++ /* VLC doesn't like having no pts - but a simple timestamp at the ++ * start of time is all that is needed to get it going - pick the ++ * first dts we saw as being in the right general area */ ++ if (i_pts > VLC_TS_INVALID) ++ p_sys->dts0_used = true; ++ else if (p_sys->dts0 > VLC_TS_INVALID && !p_sys->dts0_used) ++ { ++ i_pts = p_sys->dts0; ++ p_sys->dts0_used = true; ++ } + -+// Merge RGBA with BGRA -+static void merge_line2(void * dest, const void * src, int alpha, unsigned int n) -+{ -+ unsigned int i; -+ const uint8_t * s_data = src; -+ uint8_t * d_data = dest; + /* Interpolate the next PTS */ + if( i_pts > VLC_TS_INVALID ) + date_Set( &p_sys->pts, i_pts ); +@@ -1183,9 +1268,10 @@ static picture_t *DecodeBlock( decoder_t + { /* When direct rendering is not used, get_format() and get_buffer() + * might not be called. The output video format must be set here + * then picture buffer can be allocated. */ +- if (p_sys->p_va == NULL ++ if ((frame->format == AV_PIX_FMT_DRM_PRIME || ++ p_sys->p_va == NULL) + && lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, +- p_context->pix_fmt) == 0) ++ p_context->sw_pix_fmt) == 0) + p_pic = decoder_NewPicture(p_dec); + + if( !p_pic ) +@@ -1538,7 +1624,7 @@ static enum PixelFormat ffmpeg_GetFormat + video_format_t fmt; + + /* Enumerate available formats */ +- enum PixelFormat swfmt = avcodec_default_get_format(p_context, pi_fmt); ++ enum PixelFormat def_swfmt = avcodec_default_get_format(p_context, pi_fmt); + bool can_hwaccel = false; + + for (size_t i = 0; pi_fmt[i] != AV_PIX_FMT_NONE; i++) +@@ -1561,7 +1647,7 @@ static enum PixelFormat ffmpeg_GetFormat + * existing output format, and if present, hardware acceleration back-end. + * This avoids resetting the pipeline downstream. This also avoids + * needlessly probing for hardware acceleration support. */ +- if (lavc_GetVideoFormat(p_dec, &fmt, p_context, p_sys->pix_fmt, swfmt) != 0) ++ if (lavc_GetVideoFormat(p_dec, &fmt, p_context, p_sys->pix_fmt, p_sys->sw_pix_fmt) != 0) + { + msg_Dbg(p_dec, "get format failed"); + goto no_reuse; +@@ -1583,7 +1669,7 @@ static enum PixelFormat ffmpeg_GetFormat + for (size_t i = 0; pi_fmt[i] != AV_PIX_FMT_NONE; i++) + if (pi_fmt[i] == p_sys->pix_fmt) + { +- if (lavc_UpdateVideoFormat(p_dec, p_context, p_sys->pix_fmt, swfmt) == 0) ++ if (lavc_UpdateVideoFormat(p_dec, p_context, p_sys->pix_fmt, p_sys->sw_pix_fmt) == 0) + { + msg_Dbg(p_dec, "reusing decoder output format %d", pi_fmt[i]); + return p_sys->pix_fmt; +@@ -1602,7 +1688,7 @@ no_reuse: + p_sys->level = p_context->level; + + if (!can_hwaccel) +- return swfmt; ++ return def_swfmt; + + #if (LIBAVCODEC_VERSION_MICRO >= 100) \ + && (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 83, 101)) +@@ -1610,7 +1696,7 @@ no_reuse: + { + msg_Warn(p_dec, "thread type %d: disabling hardware acceleration", + p_context->active_thread_type); +- return swfmt; ++ return def_swfmt; + } + #endif + +@@ -1618,6 +1704,8 @@ no_reuse: + + static const enum PixelFormat hwfmts[] = + { ++ AV_PIX_FMT_DRM_PRIME, ++#if !OPT_RPI // RPI - ignore stuff we know isn't going to work + #ifdef _WIN32 + #if LIBAVUTIL_VERSION_CHECK(54, 13, 1, 24, 100) + AV_PIX_FMT_D3D11VA_VLD, +@@ -1628,12 +1716,14 @@ no_reuse: + #if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 4, 0)) + AV_PIX_FMT_VDPAU, + #endif ++#endif + AV_PIX_FMT_NONE, + }; + + for( size_t i = 0; hwfmts[i] != AV_PIX_FMT_NONE; i++ ) + { + enum PixelFormat hwfmt = AV_PIX_FMT_NONE; ++ enum PixelFormat swfmt = def_swfmt; + for( size_t j = 0; hwfmt == AV_PIX_FMT_NONE && pi_fmt[j] != AV_PIX_FMT_NONE; j++ ) + if( hwfmts[i] == pi_fmt[j] ) + hwfmt = hwfmts[i]; +@@ -1641,6 +1731,14 @@ no_reuse: + if( hwfmt == AV_PIX_FMT_NONE ) + continue; + ++#if OPT_RPI ++ // Kludge to what we know the swfmt is going to be ++ if (hwfmt == AV_PIX_FMT_DRM_PRIME && p_context->codec_id == AV_CODEC_ID_HEVC && def_swfmt == AV_PIX_FMT_YUV420P) ++ swfmt = AV_PIX_FMT_RPI4_8; ++ if (hwfmt == AV_PIX_FMT_DRM_PRIME && p_context->codec_id == AV_CODEC_ID_HEVC && def_swfmt == AV_PIX_FMT_YUV420P10LE) ++ swfmt = AV_PIX_FMT_RPI4_10; ++#endif + -+ for (i = 0; i != n; ++i) { -+ const uint32_t s_pel = ((const uint32_t *)s_data)[i]; -+ const uint32_t d_pel = ((const uint32_t *)d_data)[i]; -+ const unsigned int a = div255(alpha * (s_pel >> 24)); -+ ((uint32_t *)d_data)[i] = 0xff000000 | -+ (a_merge((d_pel >> 0) & 0xff, (s_pel >> 16) & 0xff, a) << 0 ) | -+ (a_merge((d_pel >> 8) & 0xff, (s_pel >> 8) & 0xff, a) << 8 ) | -+ (a_merge((d_pel >> 16) & 0xff, (s_pel >> 0) & 0xff, a) << 16); + p_dec->fmt_out.video.i_chroma = vlc_va_GetChroma(hwfmt, swfmt); + if (p_dec->fmt_out.video.i_chroma == 0) + continue; /* Unknown brand of hardware acceleration */ +@@ -1673,12 +1771,14 @@ no_reuse: + + p_sys->p_va = va; + p_sys->pix_fmt = hwfmt; ++ p_sys->sw_pix_fmt = swfmt; + p_context->draw_horiz_band = NULL; + return hwfmt; + } + + post_mt(p_sys); + /* Fallback to default behaviour */ +- p_sys->pix_fmt = swfmt; +- return swfmt; ++ p_sys->pix_fmt = def_swfmt; ++ p_sys->sw_pix_fmt = def_swfmt; ++ return p_sys->pix_fmt; + } +--- a/modules/gui/qt/qt.cpp ++++ b/modules/gui/qt/qt.cpp +@@ -31,6 +31,7 @@ + #include + #include + #include ++#include + + #include "qt.hpp" + +@@ -284,7 +285,7 @@ vlc_module_begin () + add_string( "qt-slider-colours", "153;210;153;20;210;20;255;199;15;245;39;29", + SLIDERCOL_TEXT, SLIDERCOL_LONGTEXT, false ) + +- add_bool( "qt-privacy-ask", true, PRIVACY_TEXT, PRIVACY_TEXT, ++ add_bool( "qt-privacy-ask", false, PRIVACY_TEXT, PRIVACY_TEXT, + false ) + change_private () + +@@ -529,6 +530,22 @@ static void *ThreadPlatform( void *obj, + QApplication::setAttribute( Qt::AA_UseHighDpiPixmaps ); + #endif + ++ bool do_disable = false; ++ { ++ QVLCApp app( argc, argv ); ++ const double dpr = app.devicePixelRatio(); ++ if( dpr > 2.1 ) ++ { ++ do_disable = true; ++ msg_Warn(p_intf, "devicePixelRatio (%g) > 2.1: assuming broken", dpr); ++ app.quit(); ++ } ++ } ++ if( do_disable ) ++ { ++ QApplication::setAttribute( Qt::AA_DisableHighDpiScaling ); + } -+} + -+#define BUF_SIZE 256 -+#define BUF_SLACK 16 -+#define BUF_ALIGN 64 -+#define BUF_ALLOC (BUF_SIZE + 2*BUF_SLACK + BUF_ALIGN) + /* Start the QApplication here */ + QVLCApp app( argc, argv ); + +--- /dev/null ++++ b/modules/hw/drm/Makefile.am +@@ -0,0 +1,26 @@ ++drmdir = $(pluginsdir)/drm ++ ++libdrm_avcodec_plugin_la_SOURCES = hw/drm/drm_avcodec.c ++libdrm_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) ++libdrm_avcodec_plugin_la_LIBADD = $(AVCODEC_LIBS) ++ ++libdrm_gl_conv_plugin_la_SOURCES = hw/drm/drm_gl_conv.c ++libdrm_gl_conv_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) ++ ++libdrm_conv_sand30_plugin_la_SOURCES = hw/drm/conv_sand30.c ++libdrm_conv_sand30_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) ++libdrm_conv_sand30_plugin_la_LIBADD = $(AVCODEC_LIBS) ++ ++libdrm_av_deinterlace_plugin_la_SOURCES = hw/drm/drm_av_deinterlace.c \ ++ codec/avcodec/drm_pic.c codec/avcodec/drm_pic.h ++libdrm_av_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) ++libdrm_av_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) $(AVCODEC_LDFLAGS) -lavfilter ++libdrm_av_deinterlace_plugin_la_LIBADD = $(AVCODEC_LIBS) ++ ++if HAVE_DRM ++drm_LTLIBRARIES = \ ++ libdrm_av_deinterlace_plugin.la \ ++ libdrm_avcodec_plugin.la \ ++ libdrm_conv_sand30_plugin.la \ ++ libdrm_gl_conv_plugin.la ++endif +--- /dev/null ++++ b/modules/hw/drm/conv_sand30.c +@@ -0,0 +1,212 @@ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif + -+static void test_line(const uint32_t * const dx, const unsigned int d_off, -+ const uint32_t * const sx, const unsigned int s_off, -+ const unsigned int alpha, const unsigned int len, const int prof_no) -+{ -+ uint32_t d0_buf[BUF_ALLOC]; -+ uint32_t d1_buf[BUF_ALLOC]; -+ const uint32_t * const s0 = sx + s_off; ++#include ++#include ++#include ++#include + -+ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; -+ uint32_t * const d1 = (uint32_t *)(((uintptr_t)d1_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; -+ unsigned int i; ++#include ++#include ++#include + -+ memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4); -+ memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4); ++#include "../../codec/avcodec/drm_pic.h" + -+ merge_line(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); ++#include + -+ PROFILE_START(); -+ blend_rgbx_rgba_neon(d1 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); -+ PROFILE_ACC_N(prof_no); ++#define TRACE_ALL 0 + -+ for (i = 0; i != BUF_SIZE + BUF_SLACK*2; ++i) { -+ if (d0[i] != d1[i]) { -+ printf("%3d: %08x + %08x * %02x: %08x / %08x: len=%d\n", (int)(i - BUF_SLACK), dx[i], s0[i], alpha, d0[i], d1[i], len); -+ } ++//---------------------------------------------------------------------------- ++// ++// Simple copy in to ZC ++ ++typedef struct to_nv12_sys_s { ++ int dummy; ++} to_nv12_sys_t; ++ ++static vlc_fourcc_t ++dst_fourcc_vlc_to_av(const vlc_fourcc_t av) ++{ ++ switch (av) { ++ case VLC_CODEC_I420: ++ return AV_PIX_FMT_YUV420P; ++ case VLC_CODEC_NV12: ++ return AV_PIX_FMT_NV12; ++ case VLC_CODEC_I420_10L: ++ return AV_PIX_FMT_YUV420P10LE; + } ++ return 0; +} + -+static void test_line2(const uint32_t * const dx, const unsigned int d_off, -+ const uint32_t * const sx, const unsigned int s_off, -+ const unsigned int alpha, const unsigned int len, const int prof_no) ++static void ++pic_buf_free(void *opaque, uint8_t *data) +{ -+ uint32_t d0_buf[BUF_ALLOC]; -+ uint32_t d1_buf[BUF_ALLOC]; -+ const uint32_t * const s0 = sx + s_off; ++ VLC_UNUSED(data); ++ picture_Release(opaque); ++} + -+ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; -+ uint32_t * const d1 = (uint32_t *)(((uintptr_t)d1_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; -+ unsigned int i; ++static AVBufferRef * ++mk_buf_from_pic(picture_t * const pic, uint8_t * const data, const size_t size) ++{ ++ return av_buffer_create(data, size, pic_buf_free, picture_Hold(pic), 0); ++} + -+ memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4); -+ memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4); ++static picture_t * ++to_nv12_filter(filter_t *p_filter, picture_t *in_pic) ++{ ++ to_nv12_sys_t * const sys = (to_nv12_sys_t *)p_filter->p_sys; ++#if TRACE_ALL ++ msg_Dbg(p_filter, "<<< %s", __func__); ++#endif ++ AVFrame * frame_in = av_frame_alloc(); ++ AVFrame * frame_out = av_frame_alloc(); ++ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)in_pic->context; ++ int rv; + -+ merge_line2(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); ++ VLC_UNUSED(sys); + -+ PROFILE_START(); -+ blend_bgrx_rgba_neon(d1 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); -+ PROFILE_ACC_N(prof_no); ++ if (!frame_in || !frame_out || !pctx) ++ goto fail0; + -+ for (i = 0; i != BUF_SIZE + BUF_SLACK*2; ++i) { -+ if (d0[i] != d1[i]) { -+ printf("%3d: %08x + %08x * %02x: %08x / %08x: len=%d\n", (int)(i - BUF_SLACK), dx[i], s0[i], alpha, d0[i], d1[i], len); ++ picture_t * const out_pic = filter_NewPicture(p_filter); ++ if (out_pic == NULL) ++ goto fail0; ++ ++ frame_in->format = AV_PIX_FMT_DRM_PRIME; ++ frame_in->buf[0] = av_buffer_ref(pctx->buf); ++ frame_in->data[0] = (uint8_t *)pctx->desc; ++ frame_in->hw_frames_ctx = av_buffer_ref(pctx->hw_frames_ctx); ++ frame_in->width = in_pic->format.i_width; ++ frame_in->height = in_pic->format.i_height; ++ frame_in->crop_left = in_pic->format.i_x_offset; ++ frame_in->crop_top = in_pic->format.i_y_offset; ++ frame_in->crop_right = frame_in->width - in_pic->format.i_visible_width - frame_in->crop_left; ++ frame_in->crop_bottom = frame_in->height - in_pic->format.i_visible_height - frame_in->crop_top; ++ ++ frame_out->format = dst_fourcc_vlc_to_av(p_filter->fmt_out.video.i_chroma); ++ frame_out->width = out_pic->format.i_width; ++ frame_out->height = out_pic->format.i_height; ++ for (int i = 0; i != out_pic->i_planes; ++i) { ++ frame_out->buf[i] = mk_buf_from_pic(out_pic, out_pic->p[i].p_pixels, out_pic->p[i].i_lines * out_pic->p[i].i_pitch); ++ if (!frame_out->buf[i]) { ++ msg_Err(p_filter, "Failed to make buf from pic"); ++ goto fail1; + } ++ frame_out->data[i] = out_pic->p[i].p_pixels; ++ frame_out->linesize[i] = out_pic->p[i].i_pitch; + } -+} + ++ if ((rv = av_hwframe_transfer_data(frame_out, frame_in, 0)) != 0) { ++ msg_Err(p_filter, "Failed to transfer data: %s", av_err2str(rv)); ++ goto fail1; ++ } ++ ++ av_frame_free(&frame_in); ++ av_frame_free(&frame_out); ++ picture_Release(in_pic); ++ return out_pic; + ++fail1: ++ picture_Release(out_pic); ++fail0: ++ av_frame_free(&frame_in); ++ av_frame_free(&frame_out); ++ picture_Release(in_pic); ++ return NULL; ++} + -+int main(int argc, char *argv[]) ++static void to_nv12_flush(filter_t * p_filter) +{ -+ unsigned int i, j; -+ uint32_t d0_buf[BUF_ALLOC]; -+ uint32_t s0_buf[BUF_ALLOC]; ++ VLC_UNUSED(p_filter); ++} + -+ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + 63) & ~63) + 0; -+ uint32_t * const s0 = (uint32_t *)(((uintptr_t)s0_buf + 63) & ~63) + 0; ++static void CloseConverterToNv12(vlc_object_t * obj) ++{ ++ filter_t * const p_filter = (filter_t *)obj; ++ to_nv12_sys_t * const sys = (to_nv12_sys_t *)p_filter->p_sys; + -+ PROFILE_INIT(); ++ if (sys == NULL) ++ return; + -+ for (i = 0; i != 255*255; ++i) { -+ unsigned int a = div255(i); -+ unsigned int b = (i + 127)/255; -+ unsigned int c = (i + 126)/255; -+ if (a != b && a != c) -+ printf("%d/255: %d != %d/%d\n", i, a, b, c); -+ } ++ p_filter->p_sys = NULL; + -+ for (i = 0; i != BUF_ALLOC; ++i) { -+ d0_buf[i] = 0xff00 | i; -+ s0_buf[i] = (i << 24) | 0x40ffc0; -+ } ++ free(sys); ++} + -+ for (i = 0; i != 256; ++i) { -+ test_line(d0, 0, s0, 0, i, 256, -1); -+ } -+ for (i = 0; i != 256; ++i) { -+ test_line(d0, 0, s0, 0, 128, i, -1); ++static bool to_nv12_validate_fmt(const video_format_t * const f_in, const video_format_t * const f_out) ++{ ++ if (f_in->i_height != f_out->i_height || ++ f_in->i_width != f_out->i_width) ++ { ++ return false; + } + -+ for (j = 0; j != 16; ++j) { -+ for (i = 0; i != 256; ++i) { -+ test_line(d0, j & 3, s0, j >> 2, i, 256, j); -+ } -+ PROFILE_PRINTF_N(j); -+ PROFILE_CLEAR_N(j); -+ } -+ printf("Done 1\n"); ++ if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_SAND8 && ++ (f_out->i_chroma == VLC_CODEC_I420 || f_out->i_chroma == VLC_CODEC_NV12)) ++ return true; + -+ for (i = 0; i != 256; ++i) { -+ test_line2(d0, 0, s0, 0, i, 256, -1); -+ } -+ for (i = 0; i != 256; ++i) { -+ test_line2(d0, 0, s0, 0, 128, i, -1); ++ if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_I420 && ++ f_out->i_chroma == VLC_CODEC_I420) ++ return true; ++ ++ if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_NV12 && ++ f_out->i_chroma == VLC_CODEC_NV12) ++ return true; ++ ++ if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_SAND30 && ++ (f_out->i_chroma == VLC_CODEC_I420_10L || f_out->i_chroma == VLC_CODEC_NV12)) ++ return true; ++ ++ return false; ++} ++ ++static int OpenConverterToNv12(vlc_object_t * obj) ++{ ++ int ret = VLC_EGENERIC; ++ filter_t * const p_filter = (filter_t *)obj; ++ ++ if (!to_nv12_validate_fmt(&p_filter->fmt_in.video, &p_filter->fmt_out.video)) ++ goto fail; ++ ++ { ++ msg_Dbg(p_filter, "%s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d", __func__, ++ fourcc2str(p_filter->fmt_in.video.i_chroma), ++ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, ++ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, ++ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, ++ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den, ++ fourcc2str(p_filter->fmt_out.video.i_chroma), ++ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, ++ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, ++ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height, ++ p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask, ++ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den); + } + -+ for (j = 0; j != 16; ++j) { -+ for (i = 0; i != 256; ++i) { -+ test_line2(d0, j & 3, s0, j >> 2, i, 256, j); -+ } -+ PROFILE_PRINTF_N(j); ++ to_nv12_sys_t * const sys = calloc(1, sizeof(*sys)); ++ if (!sys) { ++ ret = VLC_ENOMEM; ++ goto fail; + } -+ printf("Done 2\n"); ++ p_filter->p_sys = (filter_sys_t *)sys; + -+ return 0; ++ p_filter->pf_video_filter = to_nv12_filter; ++ p_filter->pf_flush = to_nv12_flush; ++ return VLC_SUCCESS; ++ ++fail: ++ CloseConverterToNv12(obj); ++ return ret; +} + ---- a/modules/hw/mmal/codec.c -+++ b/modules/hw/mmal/codec.c -@@ -26,267 +26,443 @@ - #include "config.h" - #endif - -+#include ++vlc_module_begin() ++ set_category( CAT_VIDEO ) ++ set_subcategory( SUBCAT_VIDEO_VFILTER ) ++ set_shortname(N_("DRMPRIME to s/w")) ++ set_description(N_("DRMPRIME-to software picture filter")) ++ add_shortcut("drmprime_to_sw") ++ set_capability( "video converter", 50 ) ++ set_callbacks(OpenConverterToNv12, CloseConverterToNv12) ++vlc_module_end() + - #include --#include - #include - #include +--- /dev/null ++++ b/modules/hw/drm/drm_av_deinterlace.c +@@ -0,0 +1,273 @@ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include +#include - #include - --#include - #include - #include - #include - -+#include ++#include ++#include + -+#include "mmal_cma.h" - #include "mmal_picture.h" - -+#include "subpic.h" -+#include "blend_rgba_neon.h" ++#include "../../codec/avcodec/drm_pic.h" + -+#define TRACE_ALL 0 ++#include ++#include ++#include ++#include ++#include ++#include ++#include + -+#define OPT_TO_FROM_ZC 0 ++#define TRACE_ALL 1 + - /* - * This seems to be a bit high, but reducing it causes instabilities - */ - #define NUM_EXTRA_BUFFERS 5 -+//#define NUM_EXTRA_BUFFERS 10 - #define NUM_DECODER_BUFFER_HEADERS 30 - --#define MIN_NUM_BUFFERS_IN_TRANSIT 2 -+#define CONVERTER_BUFFERS 4 // Buffers on the output of the converter ++typedef struct filter_sys_t { ++ AVFilterGraph *filter_graph; ++ AVFilterContext *buffersink_ctx; // Allocated within graph - no explicit free ++ AVFilterContext *buffersrc_ctx; // Allocated within graph - no explicit free ++ bool has_out; ++ AVFrame *out_frame; ++} filter_sys_t; + -+#define MMAL_SLICE_HEIGHT 16 -+#define MMAL_ALIGN_W 32 -+#define MMAL_ALIGN_H 16 - - #define MMAL_OPAQUE_NAME "mmal-opaque" - #define MMAL_OPAQUE_TEXT N_("Decode frames directly into RPI VideoCore instead of host memory.") - #define MMAL_OPAQUE_LONGTEXT N_("Decode frames directly into RPI VideoCore instead of host memory. This option must only be used with the MMAL video output plugin.") - --static int OpenDecoder(decoder_t *dec); --static void CloseDecoder(decoder_t *dec); -- --vlc_module_begin() -- set_shortname(N_("MMAL decoder")) -- set_description(N_("MMAL-based decoder plugin for Raspberry Pi")) -- set_capability("video decoder", 90) -- add_shortcut("mmal_decoder") -- add_bool(MMAL_OPAQUE_NAME, true, MMAL_OPAQUE_TEXT, MMAL_OPAQUE_LONGTEXT, false) -- set_callbacks(OpenDecoder, CloseDecoder) --vlc_module_end() -+#define MMAL_RESIZE_NAME "mmal-resize" -+#define MMAL_RESIZE_TEXT N_("Use mmal resizer rather than hvs.") -+#define MMAL_RESIZE_LONGTEXT N_("Use mmal resizer rather than isp. This uses less gpu memory than the ISP but is slower.") ++static void drmp_av_flush(filter_t * filter) ++{ ++ // Nothing to do ++ VLC_UNUSED(filter); + -+#define MMAL_ISP_NAME "mmal-isp" -+#define MMAL_ISP_TEXT N_("Use mmal isp rather than hvs.") -+#define MMAL_ISP_LONGTEXT N_("Use mmal isp rather than hvs. This may be faster but has no blend.") - --struct decoder_sys_t { -- bool opaque; -+typedef struct decoder_sys_t ++#if TRACE_ALL ++ msg_Dbg(filter, "<<< %s", __func__); ++#endif ++} ++ ++static picture_t * drmp_av_deinterlace(filter_t * filter, picture_t * in_pic) +{ - MMAL_COMPONENT_T *component; - MMAL_PORT_T *input; - MMAL_POOL_T *input_pool; - MMAL_PORT_T *output; -- MMAL_POOL_T *output_pool; /* only used for non-opaque mode */ -+ hw_mmal_port_pool_ref_t *ppr; - MMAL_ES_FORMAT_T *output_format; -- vlc_sem_t sem; - -+ MMAL_STATUS_T err_stream; - bool b_top_field_first; - bool b_progressive; - -+ bool b_flushed; ++ filter_sys_t *const sys = filter->p_sys; ++ AVFrame * frame = av_frame_alloc(); ++ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)in_pic->context; ++ picture_t * out_pic = NULL; ++ picture_t ** pp_pic = &out_pic; ++ int ret; + -+ vcsm_init_type_t vcsm_init_type; ++#if TRACE_ALL ++ msg_Dbg(filter, "<<< %s", __func__); ++#endif + -+ // Lock to avoid pic update & allocate happenening simultainiously -+ // * We should be able to arrange life s.t. this isn't needed -+ // but while we are confused apply belt & braces -+ vlc_mutex_t pic_lock; ++ if (!frame) { ++ msg_Err(filter, "Frame alloc failure"); ++ goto fail; ++ } + - /* statistics */ -- int output_in_transit; -- int input_in_transit; - atomic_bool started; --}; -+} decoder_sys_t; - --/* Utilities */ --static int change_output_format(decoder_t *dec); --static int send_output_buffer(decoder_t *dec); --static void fill_output_port(decoder_t *dec); -- --/* VLC decoder callback */ --static int decode(decoder_t *dec, block_t *block); --static void flush_decoder(decoder_t *dec); -- --/* MMAL callbacks */ --static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); --static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); --static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); - --static int OpenDecoder(decoder_t *dec) --{ -- int ret = VLC_SUCCESS; -- decoder_sys_t *sys; -- MMAL_PARAMETER_UINT32_T extra_buffers; -- MMAL_STATUS_T status; -+typedef struct supported_mmal_enc_s { -+ struct { -+ MMAL_PARAMETER_HEADER_T header; -+ MMAL_FOURCC_T encodings[64]; -+ } supported; -+ int n; -+} supported_mmal_enc_t; ++ frame->format = AV_PIX_FMT_DRM_PRIME; ++ frame->buf[0] = av_buffer_ref(pctx->buf); ++ frame->data[0] = (uint8_t *)pctx->desc; ++ frame->hw_frames_ctx = av_buffer_ref(pctx->hw_frames_ctx); ++ frame->width = in_pic->format.i_width; ++ frame->height = in_pic->format.i_height; ++ frame->crop_left = in_pic->format.i_x_offset; ++ frame->crop_top = in_pic->format.i_y_offset; ++ frame->crop_right = frame->width - in_pic->format.i_visible_width - frame->crop_left; ++ frame->crop_bottom = frame->height - in_pic->format.i_visible_height - frame->crop_top; ++ frame->interlaced_frame = !in_pic->b_progressive; ++ frame->top_field_first = in_pic->b_top_field_first; ++ frame->pts = (in_pic->date == VLC_TS_INVALID) ? AV_NOPTS_VALUE : in_pic->date; + -+#define SUPPORTED_MMAL_ENC_INIT \ -+{ \ -+ {{MMAL_PARAMETER_SUPPORTED_ENCODINGS, sizeof(((supported_mmal_enc_t *)0)->supported)}, {0}}, \ -+ -1 \ -+} - -- if (dec->fmt_in.i_codec != VLC_CODEC_MPGV && -- dec->fmt_in.i_codec != VLC_CODEC_H264) -- return VLC_EGENERIC; -+static supported_mmal_enc_t supported_decode_in_enc = SUPPORTED_MMAL_ENC_INIT; - -- sys = calloc(1, sizeof(decoder_sys_t)); -- if (!sys) { -- ret = VLC_ENOMEM; -- goto out; -+static bool is_enc_supported(supported_mmal_enc_t * const support, const MMAL_FOURCC_T fcc) -+{ -+ int i; ++ picture_Release(in_pic); ++ in_pic = NULL; + -+ if (fcc == 0) -+ return false; -+ if (support->n == -1) -+ return true; // Unknown - say OK -+ for (i = 0; i < support->n; ++i) { -+ if (support->supported.encodings[i] == fcc) -+ return true; - } -- dec->p_sys = sys; -+ return false; -+} - -- sys->opaque = var_InheritBool(dec, MMAL_OPAQUE_NAME); -- bcm_host_init(); -+static bool set_and_test_enc_supported(supported_mmal_enc_t * const support, MMAL_PORT_T * port, const MMAL_FOURCC_T fcc) -+{ -+ if (support->n >= 0) -+ /* already done */; -+ else if (mmal_port_parameter_get(port, (MMAL_PARAMETER_HEADER_T *)&support->supported) != MMAL_SUCCESS) -+ support->n = 0; -+ else -+ support->n = (support->supported.header.size - sizeof(support->supported.header)) / -+ sizeof(support->supported.encodings[0]); - -- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, &sys->component); -- if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to create MMAL component %s (status=%"PRIx32" %s)", -- MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -- } -+ return is_enc_supported(support, fcc); ++ if ((ret = av_buffersrc_add_frame_flags(sys->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { ++ msg_Err(filter, "Failed to feed filtergraph: %s", av_err2str(ret)); ++ goto fail; ++ } ++ av_frame_unref(frame); ++ ++ while (sys->has_out || (ret = av_buffersink_get_frame(sys->buffersink_ctx, sys->out_frame)) == 0) { ++ picture_t *const pic = filter_NewPicture(filter); ++ sys->has_out = true; ++ // Failure to get an output pic happens quite often, just keep the ++ // frame for next time ++ if (!pic) ++ break; ++ ++ if (drm_prime_attach_buf_to_pic(pic, sys->out_frame) != VLC_SUCCESS) { ++ msg_Err(filter, "Failed to attach frame to out pic"); ++ picture_Release(pic); ++ goto fail; ++ } ++ pic->date = sys->out_frame->pts == AV_NOPTS_VALUE ? VLC_TS_INVALID : sys->out_frame->pts; ++ av_frame_unref(sys->out_frame); ++ sys->has_out = false; ++ ++ *pp_pic = pic; ++ pp_pic = &pic->p_next; ++ } ++ ++ if (ret < 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) { ++ msg_Err(filter, "Failed to get frame: %s", av_err2str(ret)); ++ goto fail; ++ } ++ ++ // Even if we get an error we may have processed some pics and we need to return them ++fail: ++ if (in_pic) ++ picture_Release(in_pic); ++ av_frame_free(&frame); ++ ++#if TRACE_ALL ++ msg_Dbg(filter, ">>> %s: %p", __func__, out_pic); ++#endif ++ ++ return out_pic; +} - -- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)dec; -- status = mmal_port_enable(sys->component->control, control_port_cb); -- if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to enable control port %s (status=%"PRIx32" %s)", -- sys->component->control->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+static MMAL_FOURCC_T vlc_to_mmal_es_fourcc(const unsigned int fcc) ++ ++static void CloseDrmpAvDeinterlace(filter_t *filter) +{ -+ switch (fcc){ -+ case VLC_CODEC_MJPG: -+ return MMAL_ENCODING_MJPEG; -+ case VLC_CODEC_MP1V: -+ return MMAL_ENCODING_MP1V; -+ case VLC_CODEC_MPGV: -+ case VLC_CODEC_MP2V: -+ return MMAL_ENCODING_MP2V; -+ case VLC_CODEC_H263: -+ return MMAL_ENCODING_H263; -+ case VLC_CODEC_MP4V: -+ return MMAL_ENCODING_MP4V; -+ case VLC_CODEC_H264: -+ return MMAL_ENCODING_H264; -+ case VLC_CODEC_VP6: -+ return MMAL_ENCODING_VP6; -+ case VLC_CODEC_VP8: -+ return MMAL_ENCODING_VP8; -+ case VLC_CODEC_WMV1: -+ return MMAL_ENCODING_WMV1; -+ case VLC_CODEC_WMV2: -+ return MMAL_ENCODING_WMV2; -+ case VLC_CODEC_WMV3: -+ return MMAL_ENCODING_WMV3; -+ case VLC_CODEC_VC1: -+ return MMAL_ENCODING_WVC1; -+ case VLC_CODEC_THEORA: -+ return MMAL_ENCODING_THEORA; -+ default: -+ break; - } -+ return 0; ++ filter_sys_t * const sys = filter->p_sys; ++ ++#if TRACE_ALL ++ msg_Dbg(filter, "<<< %s", __func__); ++#endif ++ ++ if (sys == NULL) ++ return; ++ ++ av_frame_free(&sys->out_frame); ++ avfilter_graph_free(&sys->filter_graph); ++ free(sys); +} - -- sys->input = sys->component->input[0]; -- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)dec; -- if (dec->fmt_in.i_codec == VLC_CODEC_MPGV) -- sys->input->format->encoding = MMAL_ENCODING_MP2V; -- else -- sys->input->format->encoding = MMAL_ENCODING_H264; -+static MMAL_FOURCC_T pic_to_slice_mmal_fourcc(const MMAL_FOURCC_T fcc) ++ ++ ++// Copied almost directly from ffmpeg filtering_video.c example ++static int init_filters(filter_t * const filter, ++ const char * const filters_descr) +{ -+ switch (fcc){ -+ case MMAL_ENCODING_I420: -+ return MMAL_ENCODING_I420_SLICE; -+ case MMAL_ENCODING_I422: -+ return MMAL_ENCODING_I422_SLICE; -+ case MMAL_ENCODING_ARGB: -+ return MMAL_ENCODING_ARGB_SLICE; -+ case MMAL_ENCODING_RGBA: -+ return MMAL_ENCODING_RGBA_SLICE; -+ case MMAL_ENCODING_ABGR: -+ return MMAL_ENCODING_ABGR_SLICE; -+ case MMAL_ENCODING_BGRA: -+ return MMAL_ENCODING_BGRA_SLICE; -+ case MMAL_ENCODING_RGB16: -+ return MMAL_ENCODING_RGB16_SLICE; -+ case MMAL_ENCODING_RGB24: -+ return MMAL_ENCODING_RGB24_SLICE; -+ case MMAL_ENCODING_RGB32: -+ return MMAL_ENCODING_RGB32_SLICE; -+ case MMAL_ENCODING_BGR16: -+ return MMAL_ENCODING_BGR16_SLICE; -+ case MMAL_ENCODING_BGR24: -+ return MMAL_ENCODING_BGR24_SLICE; -+ case MMAL_ENCODING_BGR32: -+ return MMAL_ENCODING_BGR32_SLICE; -+ default: -+ break; ++ filter_sys_t *const sys = filter->p_sys; ++ const video_format_t * const fmt = &filter->fmt_in.video; ++ char args[512]; ++ int ret = 0; ++ const AVFilter *buffersrc = avfilter_get_by_name("buffer"); ++ const AVFilter *buffersink = avfilter_get_by_name("buffersink"); ++ AVFilterInOut *outputs = avfilter_inout_alloc(); ++ AVFilterInOut *inputs = avfilter_inout_alloc(); ++ enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_DRM_PRIME, AV_PIX_FMT_NONE }; ++ ++ sys->out_frame = av_frame_alloc(); ++ sys->filter_graph = avfilter_graph_alloc(); ++ if (!outputs || !inputs || !sys->filter_graph || !sys->out_frame) { ++ ret = AVERROR(ENOMEM); ++ goto end; + } -+ return 0; ++ ++ /* buffer video source: the decoded frames from the decoder will be inserted here. */ ++ snprintf(args, sizeof(args), ++ "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", ++ fmt->i_visible_width, fmt->i_visible_height, AV_PIX_FMT_DRM_PRIME, ++ 1, (int)CLOCK_FREQ, ++ fmt->i_sar_num, fmt->i_sar_den); ++ ++ ret = avfilter_graph_create_filter(&sys->buffersrc_ctx, buffersrc, "in", ++ args, NULL, sys->filter_graph); ++ if (ret < 0) { ++ msg_Err(filter, "Cannot create buffer source"); ++ goto end; ++ } ++ ++ /* buffer video sink: to terminate the filter chain. */ ++ ret = avfilter_graph_create_filter(&sys->buffersink_ctx, buffersink, "out", ++ NULL, NULL, sys->filter_graph); ++ if (ret < 0) { ++ msg_Err(filter, "Cannot create buffer sink"); ++ goto end; ++ } ++ ++ ret = av_opt_set_int_list(sys->buffersink_ctx, "pix_fmts", pix_fmts, ++ AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); ++ if (ret < 0) { ++ msg_Err(filter, "Cannot set output pixel format"); ++ goto end; ++ } ++ ++ /* ++ * Set the endpoints for the filter graph. The filter_graph will ++ * be linked to the graph described by filters_descr. ++ */ ++ ++ /* ++ * The buffer source output must be connected to the input pad of ++ * the first filter described by filters_descr; since the first ++ * filter input label is not specified, it is set to "in" by ++ * default. ++ */ ++ outputs->name = av_strdup("in"); ++ outputs->filter_ctx = sys->buffersrc_ctx; ++ outputs->pad_idx = 0; ++ outputs->next = NULL; ++ ++ /* ++ * The buffer sink input must be connected to the output pad of ++ * the last filter described by filters_descr; since the last ++ * filter output label is not specified, it is set to "out" by ++ * default. ++ */ ++ inputs->name = av_strdup("out"); ++ inputs->filter_ctx = sys->buffersink_ctx; ++ inputs->pad_idx = 0; ++ inputs->next = NULL; ++ ++ if ((ret = avfilter_graph_parse_ptr(sys->filter_graph, filters_descr, ++ &inputs, &outputs, NULL)) < 0) ++ goto end; ++ ++ if ((ret = avfilter_graph_config(sys->filter_graph, NULL)) < 0) ++ goto end; ++ ++end: ++ avfilter_inout_free(&inputs); ++ avfilter_inout_free(&outputs); ++ ++ return ret == 0 ? VLC_SUCCESS : ret == AVERROR(ENOMEM) ? VLC_ENOMEM : VLC_EGENERIC; +} - -- if (dec->fmt_in.i_codec == VLC_CODEC_H264) { -- if (dec->fmt_in.i_extra > 0) { -- status = mmal_format_extradata_alloc(sys->input->format, -- dec->fmt_in.i_extra); -- if (status == MMAL_SUCCESS) { -- memcpy(sys->input->format->extradata, dec->fmt_in.p_extra, -- dec->fmt_in.i_extra); -- sys->input->format->extradata_size = dec->fmt_in.i_extra; -- } else { -- msg_Err(dec, "Failed to allocate extra format data on input port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- } -+#define DEBUG_SQUARES 0 -+#if DEBUG_SQUARES -+static void draw_square(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t val) ++ ++static bool is_fmt_valid_in(const vlc_fourcc_t fmt) +{ -+ uint32_t * p = (uint32_t *)pic_buf + y * pic_stride + x; -+ unsigned int i; -+ for (i = 0; i != h; ++i) { -+ unsigned int j; -+ for (j = 0; j != w; ++j) { -+ p[j] = val; - } -+ p += pic_stride; - } ++ return fmt == VLC_CODEC_DRM_PRIME_I420 || ++ fmt == VLC_CODEC_DRM_PRIME_NV12 || ++ fmt == VLC_CODEC_DRM_PRIME_SAND8; +} -+#endif - -- status = mmal_port_format_commit(sys->input); -- if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+#if 0 -+static inline void draw_line(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int len, int inc) ++ ++static int OpenDrmpAvDeinterlace(filter_t *filter) +{ -+ uint32_t * p = (uint32_t *)pic_buf + y * pic_stride + x; -+ while (len-- != 0) { -+ *p = ~0U; -+ p += inc; - } -- sys->input->buffer_size = sys->input->buffer_size_recommended; -- sys->input->buffer_num = sys->input->buffer_num_recommended; -+} - -- status = mmal_port_enable(sys->input, input_port_cb); -- if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to enable input port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -- } - -- sys->output = sys->component->output[0]; -- sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)dec; -+static void draw_corners(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int w, unsigned int h) -+{ -+ const unsigned int len = 20; -+ draw_line(pic_buf, pic_stride, x, y, len, 1); -+ draw_line(pic_buf, pic_stride, x, y, len, pic_stride); -+ draw_line(pic_buf, pic_stride, x + w - 1, y, len, -1); -+ draw_line(pic_buf, pic_stride, x + w - 1, y, len, pic_stride); -+ draw_line(pic_buf, pic_stride, x + w - 1, y + h - 1, len, -1); -+ draw_line(pic_buf, pic_stride, x + w - 1, y + h - 1, len, -(int)pic_stride); -+ draw_line(pic_buf, pic_stride, x, y + h - 1, len, 1); -+ draw_line(pic_buf, pic_stride, x, y + h - 1, len, -(int)pic_stride); ++ filter_sys_t *sys; ++ int ret; ++ ++ msg_Dbg(filter, "<<< %s", __func__); ++ ++ if (!is_fmt_valid_in(filter->fmt_in.video.i_chroma) || ++ filter->fmt_out.video.i_chroma != filter->fmt_in.video.i_chroma) ++ return VLC_EGENERIC; ++ ++ sys = calloc(1, sizeof(filter_sys_t)); ++ if (!sys) ++ return VLC_ENOMEM; ++ filter->p_sys = sys; ++ ++ if ((ret = init_filters(filter, "deinterlace_v4l2m2m")) != 0) ++ goto fail; ++ ++ filter->pf_video_filter = drmp_av_deinterlace; ++ filter->pf_flush = drmp_av_flush; ++ ++ return VLC_SUCCESS; ++ ++fail: ++ CloseDrmpAvDeinterlace(filter); ++ return VLC_EGENERIC; +} ++ ++vlc_module_begin() ++ set_shortname(N_("DRM PRIME deinterlace")) ++ set_description(N_("libav-based DRM_PRIME deinterlace filter plugin")) ++ set_capability("video filter", 902) ++ set_category(CAT_VIDEO) ++ set_subcategory(SUBCAT_VIDEO_VFILTER) ++ set_callbacks(OpenDrmpAvDeinterlace, CloseDrmpAvDeinterlace) ++ add_shortcut("deinterlace") ++vlc_module_end() ++ +--- /dev/null ++++ b/modules/hw/drm/drm_avcodec.c +@@ -0,0 +1,94 @@ ++/***************************************************************************** ++ * avcodec.c: VDPAU decoder for libav ++ ***************************************************************************** ++ * Copyright (C) 2012-2013 Rémi Denis-Courmont ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" +#endif - -- if (sys->opaque) { -- extra_buffers.hdr.id = MMAL_PARAMETER_EXTRA_BUFFERS; -- extra_buffers.hdr.size = sizeof(MMAL_PARAMETER_UINT32_T); -- extra_buffers.value = NUM_EXTRA_BUFFERS; -- status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", -- status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -- } -+static MMAL_RATIONAL_T -+rationalize_sar(unsigned int num, unsigned int den) ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include "../../codec/avcodec/va.h" ++ ++// Dummy - not expected to be called ++static int drm_va_get(vlc_va_t *va, picture_t *pic, uint8_t **data) +{ -+ static const unsigned int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 0}; -+ const unsigned int * p = primes; - -- msg_Dbg(dec, "Activate zero-copy for output port"); -- MMAL_PARAMETER_BOOLEAN_T zero_copy = { -- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, -- 1 -- }; -+ // If either num or den is 0 then return a well formed "unknown" -+ if (num == 0 || den == 0) { -+ return (MMAL_RATIONAL_T){.num = 0, .den = 0}; -+ } - -- status = mmal_port_parameter_set(sys->output, &zero_copy.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", -- sys->output->name, status, mmal_status_to_string(status)); -- goto out; -+ while (*p != 0 && num >= *p && den >= *p) { -+ if (num % *p != 0 || den % *p != 0) -+ ++p; -+ else { -+ num /= *p; -+ den /= *p; - } - } -+ return (MMAL_RATIONAL_T){.num = num, .den = den}; ++ VLC_UNUSED(va); ++ VLC_UNUSED(pic); ++ VLC_UNUSED(data); ++ ++ return VLC_SUCCESS; +} - -- status = mmal_port_enable(sys->output, output_port_cb); -- if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to enable output port %s (status=%"PRIx32" %s)", -- sys->output->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -- } -+// Buffer either attached to pic or released -+static picture_t * alloc_opaque_pic(decoder_t * const dec, MMAL_BUFFER_HEADER_T * const buf) ++ ++static int Open(vlc_va_t *va, AVCodecContext *avctx, enum PixelFormat pix_fmt, ++ const es_format_t *fmt, picture_sys_t *p_sys) +{ -+ decoder_sys_t *const dec_sys = dec->p_sys; - -- status = mmal_component_enable(sys->component); -- if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to enable component %s (status=%"PRIx32" %s)", -- sys->component->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+ vlc_mutex_lock(&dec_sys->pic_lock); -+ picture_t * const pic = decoder_NewPicture(dec); -+ vlc_mutex_unlock(&dec_sys->pic_lock); ++ int err; ++ VLC_UNUSED(fmt); ++ VLC_UNUSED(p_sys); + -+ if (pic == NULL) -+ goto fail1; ++ msg_Dbg(va, "%s: pix_fmt=%d", __func__, pix_fmt); + -+ if (buf->length == 0) { -+ msg_Err(dec, "%s: Empty buffer", __func__); -+ goto fail2; - } - -- sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0); -+ if ((pic->context = hw_mmal_gen_context(buf, dec_sys->ppr)) == NULL) -+ goto fail2; - -- if (sys->opaque) { -- dec->fmt_out.i_codec = VLC_CODEC_MMAL_OPAQUE; -- dec->fmt_out.video.i_chroma = VLC_CODEC_MMAL_OPAQUE; -- } else { -- dec->fmt_out.i_codec = VLC_CODEC_I420; -- dec->fmt_out.video.i_chroma = VLC_CODEC_I420; -+ buf_to_pic_copy_props(pic, buf); ++ if (pix_fmt != AV_PIX_FMT_DRM_PRIME) ++ return VLC_EGENERIC; + -+#if TRACE_ALL -+ msg_Dbg(dec, "pic: prog=%d, tff=%d, date=%lld", pic->b_progressive, pic->b_top_field_first, (long long)pic->date); -+#endif ++ enum AVHWDeviceType devtype = av_hwdevice_find_type_by_name("drm"); ++ if (devtype == AV_HWDEVICE_TYPE_NONE) { ++ msg_Dbg(va, "No DRM device found in ffmpeg"); ++ return VLC_EGENERIC; ++ } + -+ return pic; ++ // ctx->hw_device_ctx gets freed when we call avcodec_free_context ++ if ((err = av_hwdevice_ctx_create(&avctx->hw_device_ctx, devtype, NULL, NULL, 0)) < 0) { ++ msg_Err(va, "Failed to create specified HW device: %s", av_err2str(err)); ++ goto error; ++ } + -+fail2: -+ picture_Release(pic); -+fail1: -+ // Recycle rather than release to avoid buffer starvation if NewPic fails -+ hw_mmal_port_pool_ref_recycle(dec_sys->ppr, buf); -+ return NULL; ++ // This gives us whatever the decode requires + 6 frames that will be ++ // alloced by ffmpeg before it blocks (at least for Pi HEVC) ++ avctx->extra_hw_frames = 6; ++ ++ va->description = "DRM Video Accel"; ++ va->get = drm_va_get; ++ return VLC_SUCCESS; ++ ++error: ++ return VLC_EGENERIC; +} + -+static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++static void Close(vlc_va_t *va, void **hwctx) +{ -+ decoder_t *dec = (decoder_t *)port->userdata; -+ MMAL_STATUS_T status; ++ VLC_UNUSED(hwctx); + -+#if TRACE_ALL -+ msg_Dbg(dec, "<<< %s: cmd=%d, data=%p", __func__, buffer->cmd, buffer->data); ++ msg_Dbg(va, "%s", __func__); ++} ++ ++vlc_module_begin() ++ set_description(N_("DRM video decoder")) ++ set_capability("hw decoder", 100) ++ set_category(CAT_INPUT) ++ set_subcategory(SUBCAT_INPUT_VCODEC) ++ set_callbacks(Open, Close) ++ add_shortcut("drm_prime") ++vlc_module_end() +--- /dev/null ++++ b/modules/hw/drm/drm_gl_conv.c +@@ -0,0 +1,367 @@ ++#ifdef HAVE_CONFIG_H ++# include "config.h" +#endif + -+ if (buffer->cmd == MMAL_EVENT_ERROR) { -+ status = *(uint32_t *)buffer->data; -+ dec->p_sys->err_stream = status; -+ msg_Err(dec, "MMAL error %"PRIx32" \"%s\"", status, -+ mmal_status_to_string(status)); - } - -- dec->pf_decode = decode; -- dec->pf_flush = flush_decoder; -+ mmal_buffer_header_release(buffer); -+} - -- vlc_sem_init(&sys->sem, 0); -+static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "../../video_output/opengl/converter.h" ++#include "../../codec/avcodec/drm_pic.h" ++ ++#include ++ ++#define TRACE_ALL 0 ++ ++#define ICACHE_SIZE 2 ++ ++typedef struct drm_gl_converter_s +{ -+ block_t * const block = (block_t *)buffer->user_data; - --out: -- if (ret != VLC_SUCCESS) -- CloseDecoder(dec); -+ (void)port; // Unused - -- return ret; -+#if TRACE_ALL -+ msg_Dbg((decoder_t *)port->userdata, "<<< %s: cmd=%d, data=%p, len=%d/%d, pts=%lld", __func__, -+ buffer->cmd, buffer->data, buffer->length, buffer->alloc_size, (long long)buffer->pts); -+#endif ++ EGLint drm_fourcc; + -+ mmal_buffer_header_reset(buffer); -+ mmal_buffer_header_release(buffer); ++ unsigned int icache_n; ++ struct icache_s { ++ EGLImageKHR last_image; ++ picture_context_t * last_ctx; ++ } icache[ICACHE_SIZE]; + -+ if (block != NULL) -+ block_Release(block); - } - --static void CloseDecoder(decoder_t *dec) -+static void decoder_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) - { -- decoder_sys_t *sys = dec->p_sys; -- MMAL_BUFFER_HEADER_T *buffer; -+ decoder_t * const dec = (decoder_t *)port->userdata; - -- if (!sys) -+ if (buffer->cmd == 0 && buffer->length != 0) ++ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; ++} drm_gl_converter_t; ++ ++ ++static void ++unset_icache_ent(const opengl_tex_converter_t * const tc, struct icache_s * const s) ++{ ++ if (s->last_image) + { -+#if TRACE_ALL -+ msg_Dbg(dec, "<<< %s: cmd=%d, data=%p, len=%d/%d, pts=%lld", __func__, -+ buffer->cmd, buffer->data, buffer->length, buffer->alloc_size, (long long)buffer->pts); -+#endif ++ tc->gl->egl.destroyImageKHR(tc->gl, s->last_image); ++ s->last_image = NULL; ++ } + -+ picture_t *pic = alloc_opaque_pic(dec, buffer); ++ if (s->last_ctx) ++ { ++ s->last_ctx->destroy(s->last_ctx); ++ s->last_ctx = NULL; ++ } ++} ++ ++static void ++update_icache(const opengl_tex_converter_t * const tc, EGLImageKHR image, picture_t * pic) ++{ ++ drm_gl_converter_t * const sys = tc->priv; ++ struct icache_s * const s = sys->icache + sys->icache_n; ++ ++ s->last_image = image; ++ // DRM buffer is held by the context, pictures can be in surprisingly ++ // small pools for filters so let go of the pic and keep a ref on the ++ // context ++ unset_icache_ent(tc, s); ++ s->last_ctx = pic->context->copy(pic->context); ++ sys->icache_n = sys->icache_n + 1 >= ICACHE_SIZE ? 0 : sys->icache_n + 1; ++} ++ ++static int ++tc_drm_update(const opengl_tex_converter_t *tc, GLuint *textures, ++ const GLsizei *tex_width, const GLsizei *tex_height, ++ picture_t *pic, const size_t *plane_offset) ++{ ++ drm_gl_converter_t * const sys = tc->priv; +#if TRACE_ALL -+ msg_Dbg(dec, "flags=%#x, video flags=%#x", buffer->flags, buffer->type->video.flags); -+#endif -+ if (pic == NULL) -+ msg_Err(dec, "Failed to allocate new picture"); -+ else -+ decoder_QueueVideo(dec, pic); -+ // Buffer released or attached to pic - do not release again - return; ++ { ++ char cbuf[5]; ++ msg_Dbg(tc, "%s: %s %d*%dx%d : %d*%dx%d", __func__, ++ str_fourcc(cbuf, pic->format.i_chroma), ++ tc->tex_count, tex_width[0], tex_height[0], pic->i_planes, pic->p[0].i_pitch, pic->p[0].i_lines); + } - -- if (sys->component && sys->component->control->is_enabled) -- mmal_port_disable(sys->component->control); -+ if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) ++#endif ++ VLC_UNUSED(tex_width); ++ VLC_UNUSED(tex_height); ++ VLC_UNUSED(plane_offset); ++ + { -+ decoder_sys_t * const sys = dec->p_sys; -+ MMAL_EVENT_FORMAT_CHANGED_T * const fmt = mmal_event_format_changed_get(buffer); -+ MMAL_ES_FORMAT_T * const format = mmal_format_alloc(); - -- if (sys->input && sys->input->is_enabled) -- mmal_port_disable(sys->input); -+ if (format == NULL) -+ msg_Err(dec, "Failed to allocate new format"); -+ else ++ const AVDRMFrameDescriptor * const desc = drm_prime_get_desc(pic); ++ EGLint attribs[64] = {0}; ++ ++ static const EGLint plane_exts[] = { ++ EGL_DMA_BUF_PLANE0_FD_EXT, ++ EGL_DMA_BUF_PLANE0_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE0_PITCH_EXT, ++ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, ++ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, ++ EGL_DMA_BUF_PLANE1_FD_EXT, ++ EGL_DMA_BUF_PLANE1_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE1_PITCH_EXT, ++ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, ++ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, ++ EGL_DMA_BUF_PLANE2_FD_EXT, ++ EGL_DMA_BUF_PLANE2_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE2_PITCH_EXT, ++ EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, ++ EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, ++ }; ++ ++ // msg_Info(o, "<<< %s", __func__); ++ ++ if (!desc) + { -+ mmal_format_full_copy(format, fmt->format); -+ format->encoding = MMAL_ENCODING_OPAQUE; - -- if (sys->output && sys->output->is_enabled) -- mmal_port_disable(sys->output); -+ // If no PAR in the stream - see if we've got one from the demux -+ if (format->es->video.par.den <= 0 || format->es->video.par.num <= 0) { -+ unsigned int n = dec->fmt_in.video.i_sar_num; -+ unsigned int d = dec->fmt_in.video.i_sar_den; ++ msg_Err(tc, "%s: No DRM Frame desriptor found", __func__); ++ return VLC_EGENERIC; ++ } + -+ if (n == 0 || d == 0) { -+ // Guesswork required -+ const unsigned int w = format->es->video.width; -+ const unsigned int h = format->es->video.height; -+ if ((w == 704 || w == 720) && (h == 480 || h == 576)) { -+ // Very likely SD 4:3 -+ n = w * 3; -+ d = h * 4; -+ } -+ else -+ { -+ // Otherwise guess SAR 1:1 -+ n = 1; -+ d = 1; -+ } ++ EGLint *a = attribs; ++ *a++ = EGL_WIDTH; ++ *a++ = tex_width[0]; ++ *a++ = EGL_HEIGHT; ++ *a++ = tex_height[0]; ++ *a++ = EGL_LINUX_DRM_FOURCC_EXT; ++ *a++ = desc->layers[0].format; ++ ++ const EGLint * ext = plane_exts; ++ ++ for (int i = 0; i < desc->nb_layers; ++i) ++ { ++ const AVDRMLayerDescriptor * const layer = desc->layers + i; ++ for (int j = 0; j != layer->nb_planes; ++j) ++ { ++ const AVDRMPlaneDescriptor * const plane = layer->planes + j; ++ const AVDRMObjectDescriptor * const obj = desc->objects + plane->object_index; ++ ++ *a++ = *ext++; // FD ++ *a++ = obj->fd; ++ *a++ = *ext++; // OFFSET ++ *a++ = plane->offset; ++ *a++ = *ext++; // PITCH ++ *a++ = plane->pitch; ++ if (!obj->format_modifier || obj->format_modifier == DRM_FORMAT_MOD_INVALID) ++ { ++ ext += 2; ++ } ++ else ++ { ++ *a++ = *ext++; // MODIFIER_LO ++ *a++ = (EGLint)(obj->format_modifier & 0xffffffff); ++ *a++ = *ext++; // MODIFIER_HI ++ *a++ = (EGLint)(obj->format_modifier >> 32); + } - -- if (sys->component && sys->component->is_enabled) -- mmal_component_disable(sys->component); -+ format->es->video.par = rationalize_sar(n, d); + } - -- if (sys->input_pool) -- mmal_pool_destroy(sys->input_pool); -+ if (sys->output_format != NULL) -+ mmal_format_free(sys->output_format); - -- if (sys->output_format) -- mmal_format_free(sys->output_format); -+ sys->output_format = format; + } ++ *a++ = EGL_NONE; ++ *a++ = 0; ++ ++ const EGLImageKHR image = tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); ++ if (!image) { ++ msg_Err(tc, "Failed to createImageKHR: Err=%#x", tc->vt->GetError()); ++ return VLC_EGENERIC; ++ } ++ ++ // *** MMAL ZC does this a little differently ++ // tc->tex_target == OES??? ++ ++ tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, textures[0]); ++ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); ++ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); ++ sys->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image); ++ ++ update_icache(tc, image, pic); + } -+ else if (buffer->cmd != 0) { -+ char buf0[5]; -+ msg_Warn(dec, "Unexpected output cb event: %s", str_fourcc(buf0, buffer->cmd)); -+ } - -- if (sys->output_pool) -- mmal_pool_destroy(sys->output_pool); -+ // If we get here then we were flushing (cmd == 0 && len == 0) or -+ // that was an EVENT - in either case we want to release the buffer -+ // back to its pool rather than recycle it. -+ mmal_buffer_header_reset(buffer); -+ buffer->user_data = NULL; -+ mmal_buffer_header_release(buffer); ++ ++ return VLC_SUCCESS; +} - -- if (sys->component) -- mmal_component_release(sys->component); - -- vlc_sem_destroy(&sys->sem); -- free(sys); - -- bcm_host_deinit(); -+static void fill_output_port(decoder_t *dec) ++ ++static int ++tc_drm_fetch_locations(opengl_tex_converter_t *tc, GLuint program) +{ -+ decoder_sys_t *sys = dec->p_sys; ++ tc->uloc.Texture[0] = tc->vt->GetUniformLocation(program, "Texture0"); ++ return tc->uloc.Texture[0] != -1 ? VLC_SUCCESS : VLC_EGENERIC; ++} ++ ++static void ++tc_drm_prepare_shader(const opengl_tex_converter_t *tc, ++ const GLsizei *tex_width, const GLsizei *tex_height, ++ float alpha) ++{ ++ (void) tex_width; (void) tex_height; (void) alpha; ++ VLC_UNUSED(tc); ++// tc->vt->Uniform1i(tc->uloc.Texture[0], 0); ++} ++ ++static GLuint ++tc_fragment_shader_init(opengl_tex_converter_t * const tc, const GLenum tex_target, ++ const vlc_fourcc_t chroma, const video_color_space_t yuv_space) ++{ ++ VLC_UNUSED(yuv_space); ++ ++ tc->tex_count = 1; ++ tc->tex_target = tex_target; ++ tc->texs[0] = (struct opengl_tex_cfg) { ++ { 1, 1 }, { 1, 1 }, GL_RGB, chroma, GL_UNSIGNED_SHORT //** ?? ++ }; ++ ++ tc->pf_fetch_locations = tc_drm_fetch_locations; ++ tc->pf_prepare_shader = tc_drm_prepare_shader; ++ ++ ++ const char fs[] = ++ "#extension GL_OES_EGL_image_external : enable\n" ++ "precision mediump float;\n" ++ "uniform samplerExternalOES Texture0;\n" ++ "varying vec2 TexCoord0;\n" ++ "void main() {\n" ++ " gl_FragColor = texture2D(Texture0, TexCoord0);\n" ++ "}\n"; ++ ++ ++ const char *code = fs; ++ ++ GLuint fragment_shader = tc->vt->CreateShader(GL_FRAGMENT_SHADER); ++ tc->vt->ShaderSource(fragment_shader, 1, &code, NULL); ++ tc->vt->CompileShader(fragment_shader); ++ return fragment_shader; ++} + -+ if (decoder_UpdateVideoFormat(dec) != 0) -+ { -+ // If we have a new format don't bother stuffing the buffer -+ // We should get a reset RSN -+#if TRACE_ALL -+ msg_Dbg(dec, "%s: Updated", __func__); -+#endif + ++static void ++CloseGLConverter(vlc_object_t *obj) ++{ ++ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; ++ drm_gl_converter_t * const sys = tc->priv; ++ unsigned int i; ++ ++ if (sys == NULL) + return; -+ } + -+ hw_mmal_port_pool_ref_fill(sys->ppr); -+ return; - } - - static int change_output_format(decoder_t *dec) - { - MMAL_PARAMETER_VIDEO_INTERLACE_TYPE_T interlace_type; -- decoder_sys_t *sys = dec->p_sys; -+ decoder_sys_t * const sys = dec->p_sys; - MMAL_STATUS_T status; -- int pool_size; - int ret = 0; - -+#if TRACE_ALL -+ msg_Dbg(dec, "%s: <<<", __func__); -+#endif ++ for (i = 0; i != ICACHE_SIZE; ++i) ++ unset_icache_ent(tc, sys->icache + i); ++ free(sys); ++} + - if (atomic_load(&sys->started)) { - mmal_format_full_copy(sys->output->format, sys->output_format); - status = mmal_port_format_commit(sys->output); -@@ -300,7 +476,9 @@ static int change_output_format(decoder_ - } - - port_reset: -+#if TRACE_ALL - msg_Dbg(dec, "%s: Do full port reset", __func__); ++#ifndef DRM_FORMAT_P030 ++#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') +#endif - status = mmal_port_disable(sys->output); - if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to disable output port (status=%"PRIx32" %s)", -@@ -310,6 +488,7 @@ port_reset: - } - - mmal_format_full_copy(sys->output->format, sys->output_format); + - status = mmal_port_format_commit(sys->output); - if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to commit output format (status=%"PRIx32" %s)", -@@ -318,18 +497,10 @@ port_reset: - goto out; - } - -- if (sys->opaque) { -- sys->output->buffer_num = NUM_DECODER_BUFFER_HEADERS; -- pool_size = NUM_DECODER_BUFFER_HEADERS; -- } else { -- sys->output->buffer_num = __MAX(sys->output->buffer_num_recommended, -- MIN_NUM_BUFFERS_IN_TRANSIT); -- pool_size = sys->output->buffer_num; -- } -- -+ sys->output->buffer_num = NUM_DECODER_BUFFER_HEADERS; - sys->output->buffer_size = sys->output->buffer_size_recommended; - -- status = mmal_port_enable(sys->output, output_port_cb); -+ status = mmal_port_enable(sys->output, decoder_output_cb); - if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to enable output port (status=%"PRIx32" %s)", - status, mmal_status_to_string(status)); -@@ -338,25 +509,14 @@ port_reset: - } - - if (!atomic_load(&sys->started)) { -- if (!sys->opaque) { -- sys->output_pool = mmal_port_pool_create(sys->output, pool_size, 0); -- msg_Dbg(dec, "Created output pool with %d pictures", sys->output_pool->headers_num); -- } -- - atomic_store(&sys->started, true); - - /* we need one picture from vout for each buffer header on the output - * port */ -- dec->i_extra_picture_buffers = pool_size; -- -- /* remove what VLC core reserves as it is part of the pool_size -- * already */ -- if (dec->fmt_in.i_codec == VLC_CODEC_H264) -- dec->i_extra_picture_buffers -= 19; -- else -- dec->i_extra_picture_buffers -= 3; -- -+ dec->i_extra_picture_buffers = 10; -+#if TRACE_ALL - msg_Dbg(dec, "Request %d extra pictures", dec->i_extra_picture_buffers); -+#endif - } - - apply_fmt: -@@ -366,8 +526,8 @@ apply_fmt: - dec->fmt_out.video.i_y_offset = sys->output->format->es->video.crop.y; - dec->fmt_out.video.i_visible_width = sys->output->format->es->video.crop.width; - dec->fmt_out.video.i_visible_height = sys->output->format->es->video.crop.height; -- dec->fmt_out.video.i_sar_num = sys->output->format->es->video.par.num; -- dec->fmt_out.video.i_sar_den = sys->output->format->es->video.par.den; -+ dec->fmt_out.video.i_sar_num = sys->output_format->es->video.par.num; // SAR can be killed by commit -+ dec->fmt_out.video.i_sar_den = sys->output_format->es->video.par.den; - dec->fmt_out.video.i_frame_rate = sys->output->format->es->video.frame_rate.num; - dec->fmt_out.video.i_frame_rate_base = sys->output->format->es->video.frame_rate.den; - -@@ -382,12 +542,19 @@ apply_fmt: - sys->b_progressive = (interlace_type.eMode == MMAL_InterlaceProgressive); - sys->b_top_field_first = sys->b_progressive ? true : - (interlace_type.eMode == MMAL_InterlaceFieldsInterleavedUpperFirst); -+#if TRACE_ALL - msg_Dbg(dec, "Detected %s%s video (%d)", - sys->b_progressive ? "progressive" : "interlaced", - sys->b_progressive ? "" : (sys->b_top_field_first ? " tff" : " bff"), - interlace_type.eMode); -+#endif - } - -+ // Tell the rest of the world we have changed format -+ vlc_mutex_lock(&sys->pic_lock); -+ ret = decoder_UpdateVideoFormat(dec); -+ vlc_mutex_unlock(&sys->pic_lock); ++static struct vlc_to_drm_mod_s { ++ vlc_fourcc_t chroma; ++ uint32_t drm_fmt; ++ uint64_t drm_mod; ++} vlc_to_drm_mods[] = { ++ {VLC_CODEC_DRM_PRIME_I420, DRM_FORMAT_YUV420, DRM_FORMAT_MOD_LINEAR}, ++ {VLC_CODEC_DRM_PRIME_NV12, DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR}, ++ {VLC_CODEC_DRM_PRIME_SAND8, DRM_FORMAT_NV12, DRM_FORMAT_MOD_BROADCOM_SAND128}, ++ {VLC_CODEC_DRM_PRIME_SAND30, DRM_FORMAT_P030, DRM_FORMAT_MOD_BROADCOM_SAND128}, ++}; + - out: - mmal_format_free(sys->output_format); - sys->output_format = NULL; -@@ -395,144 +562,85 @@ out: - return ret; - } - --static int send_output_buffer(decoder_t *dec) -+static MMAL_STATUS_T -+set_extradata_and_commit(decoder_t * const dec, decoder_sys_t * const sys) - { -- decoder_sys_t *sys = dec->p_sys; -- MMAL_BUFFER_HEADER_T *buffer; -- picture_sys_t *p_sys; -- picture_t *picture = NULL; - MMAL_STATUS_T status; -- unsigned buffer_size = 0; -- int ret = 0; - -- if (!sys->output->is_enabled) -- return VLC_EGENERIC; -- -- /* If local output pool is allocated, use it - this is only the case for -- * non-opaque modes */ -- if (sys->output_pool) { -- buffer = mmal_queue_get(sys->output_pool->queue); -- if (!buffer) { -- msg_Warn(dec, "Failed to get new buffer"); -- return VLC_EGENERIC; -- } -- } -- -- if (!decoder_UpdateVideoFormat(dec)) -- picture = decoder_NewPicture(dec); -- if (!picture) { -- msg_Warn(dec, "Failed to get new picture"); -- ret = -1; -- goto err; -- } -- -- p_sys = picture->p_sys; -- for (int i = 0; i < picture->i_planes; i++) -- buffer_size += picture->p[i].i_lines * picture->p[i].i_pitch; -- -- if (sys->output_pool) { -- mmal_buffer_header_reset(buffer); -- buffer->alloc_size = sys->output->buffer_size; -- if (buffer_size < sys->output->buffer_size) { -- msg_Err(dec, "Retrieved picture with too small data block (%d < %d)", -- buffer_size, sys->output->buffer_size); -- ret = VLC_EGENERIC; -- goto err; -- } -- -- if (!sys->opaque) -- buffer->data = picture->p[0].p_pixels; -- } else { -- buffer = p_sys->buffer; -- if (!buffer) { -- msg_Warn(dec, "Picture has no buffer attached"); -- picture_Release(picture); -- return VLC_EGENERIC; -- } -- buffer->data = p_sys->buffer->data; -- } -- buffer->user_data = picture; -- buffer->cmd = 0; -- -- status = mmal_port_send_buffer(sys->output, buffer); -+ status = mmal_port_format_commit(sys->input); - if (status != MMAL_SUCCESS) { -- msg_Err(dec, "Failed to send buffer to output port (status=%"PRIx32" %s)", -- status, mmal_status_to_string(status)); -- ret = -1; -- goto err; -- } -- atomic_fetch_add(&sys->output_in_transit, 1); -- -- return ret; -- --err: -- if (picture) -- picture_Release(picture); -- if (sys->output_pool && buffer) { -- buffer->data = NULL; -- mmal_buffer_header_release(buffer); -+ msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)", -+ sys->input->name, status, mmal_status_to_string(status)); - } -- return ret; -+ return status; - } - --static void fill_output_port(decoder_t *dec) -+static MMAL_STATUS_T decoder_send_extradata(decoder_t * const dec, decoder_sys_t *const sys) - { -- decoder_sys_t *sys = dec->p_sys; -- -- unsigned max_buffers_in_transit = 0; -- int buffers_available = 0; -- int buffers_to_send = 0; -- int i; -+ if (dec->fmt_in.i_codec == VLC_CODEC_H264 && -+ dec->fmt_in.i_extra > 0) -+ { -+ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_wait(sys->input_pool->queue); -+ MMAL_STATUS_T status; ++static bool check_chroma(opengl_tex_converter_t * const tc) ++{ ++ char fcc[5] = {0}; ++ vlc_fourcc_to_char(tc->fmt.i_chroma, fcc); ++ uint32_t fmt = 0; ++ uint64_t mod = DRM_FORMAT_MOD_INVALID; ++ uint64_t mods[16]; ++ int32_t mod_count = 0; + -+ mmal_buffer_header_reset(buf); -+ buf->cmd = 0; -+ buf->user_data = NULL; -+ buf->alloc_size = sys->input->buffer_size; -+ buf->length = dec->fmt_in.i_extra; -+ buf->data = dec->fmt_in.p_extra; -+ buf->flags = MMAL_BUFFER_HEADER_FLAG_CONFIG; - -- if (sys->output_pool) { -- max_buffers_in_transit = __MAX(sys->output_pool->headers_num, -- MIN_NUM_BUFFERS_IN_TRANSIT); -- buffers_available = mmal_queue_length(sys->output_pool->queue); -- } else { -- max_buffers_in_transit = NUM_DECODER_BUFFER_HEADERS; -- buffers_available = NUM_DECODER_BUFFER_HEADERS - atomic_load(&sys->output_in_transit); -+ status = mmal_port_send_buffer(sys->input, buf); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(dec, "Failed to send extradata buffer to input port (status=%"PRIx32" %s)", -+ status, mmal_status_to_string(status)); -+ return status; ++ for (unsigned int i = 0; i != ARRAY_SIZE(vlc_to_drm_mods); ++i) ++ { ++ if (tc->fmt.i_chroma == vlc_to_drm_mods[i].chroma) ++ { ++ fmt = vlc_to_drm_mods[i].drm_fmt; ++ mod = vlc_to_drm_mods[i].drm_mod; ++ break; + } - } -- buffers_to_send = max_buffers_in_transit - atomic_load(&sys->output_in_transit); - -- if (buffers_to_send > buffers_available) -- buffers_to_send = buffers_available; -- --#ifndef NDEBUG -- msg_Dbg(dec, "Send %d buffers to output port (available: %d, " -- "in_transit: %d, buffer_num: %d)", -- buffers_to_send, buffers_available, -- atomic_load(&sys->output_in_transit), -- sys->output->buffer_num); --#endif -- for (i = 0; i < buffers_to_send; ++i) -- if (send_output_buffer(dec) < 0) -- break; -+ return MMAL_SUCCESS; - } - - static void flush_decoder(decoder_t *dec) - { -- decoder_sys_t *sys = dec->p_sys; -- MMAL_BUFFER_HEADER_T *buffer; -- MMAL_STATUS_T status; -+ decoder_sys_t *const sys = dec->p_sys; - -- msg_Dbg(dec, "Flushing decoder ports..."); -- mmal_port_flush(sys->output); -- mmal_port_flush(sys->input); -- -- while (atomic_load(&sys->output_in_transit) || -- atomic_load(&sys->input_in_transit)) -- vlc_sem_wait(&sys->sem); -+#if TRACE_ALL -+ msg_Dbg(dec, "%s: <<<", __func__); -+#endif ++ } ++ if (!fmt) ++ return false; + -+ if (!sys->b_flushed) { -+ mmal_port_disable(sys->input); -+ mmal_port_disable(sys->output); -+ // We can leave the input disabled, but we want the output enabled -+ // in order to sink any buffers returning from other modules -+ mmal_port_enable(sys->output, decoder_output_cb); -+ sys->b_flushed = true; ++ if (!tc->gl->egl.queryDmaBufModifiersEXT) ++ { ++ msg_Dbg(tc, "No queryDmaBufModifiersEXT"); ++ return false; + } -+#if TRACE_ALL -+ msg_Dbg(dec, "%s: >>>", __func__); -+#endif - } - - static int decode(decoder_t *dec, block_t *block) - { - decoder_sys_t *sys = dec->p_sys; - MMAL_BUFFER_HEADER_T *buffer; -- bool need_flush = false; - uint32_t len; -- uint32_t flags = 0; -+ uint32_t flags = MMAL_BUFFER_HEADER_FLAG_FRAME_START; - MMAL_STATUS_T status; - -+#if TRACE_ALL -+ msg_Dbg(dec, "<<< %s: %lld/%lld", __func__, block == NULL ? -1LL : block->i_dts, block == NULL ? -1LL : block->i_pts); -+#endif + -+ if (sys->err_stream != MMAL_SUCCESS) { -+ msg_Err(dec, "MMAL error reported by ctrl"); -+ flush_decoder(dec); -+ return VLCDEC_ECRITICAL; /// I think they are all fatal ++ if (!tc->gl->egl.queryDmaBufModifiersEXT(tc->gl, fmt, 16, mods, NULL, &mod_count)) ++ { ++ msg_Dbg(tc, "queryDmaBufModifiersEXT Failed for %s", fcc); ++ return false; + } + - /* - * Configure output port if necessary - */ -@@ -541,18 +649,50 @@ static int decode(decoder_t *dec, block_ - msg_Err(dec, "Failed to change output port format"); - } - -- if (!block) -- goto out; -+ if (block == NULL) -+ return VLCDEC_SUCCESS; - - /* - * Check whether full flush is required - */ -- if (block && block->i_flags & BLOCK_FLAG_DISCONTINUITY) { -+ if (block->i_flags & BLOCK_FLAG_DISCONTINUITY) { -+#if TRACE_ALL -+ msg_Dbg(dec, "%s: >>> Discontinuity", __func__); -+#endif - flush_decoder(dec); ++ for (int32_t i = 0; i < mod_count; ++i) ++ { ++ if (mods[i] == mod) ++ return true; + } ++ msg_Dbg(tc, "Mod %" PRIx64 " not found for %s/%.4s in %d mods", mod, fcc, (char*)&fmt, mod_count); ++ return false; ++} ++ ++static int ++OpenGLConverter(vlc_object_t *obj) ++{ ++ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; ++ int rv = VLC_EGENERIC; ++ ++ // Do we know what to do with this? ++ if (!check_chroma(tc)) ++ return VLC_EGENERIC; + -+ if (block->i_buffer == 0) -+ { - block_Release(block); - return VLCDEC_SUCCESS; - } - -+ // Reenable stuff if the last thing we did was flush -+ if (!sys->output->is_enabled && -+ (status = mmal_port_enable(sys->output, decoder_output_cb)) != MMAL_SUCCESS) + { -+ msg_Err(dec, "Output port enable failed"); -+ goto fail; ++ msg_Dbg(tc, "<<< %s: %.4s %dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__, ++ (char *)&tc->fmt.i_chroma, ++ tc->fmt.i_width, tc->fmt.i_height, ++ tc->fmt.i_x_offset, tc->fmt.i_y_offset, ++ tc->fmt.i_visible_width, tc->fmt.i_visible_height, ++ tc->fmt.i_sar_num, tc->fmt.i_sar_den); + } + -+ if (!sys->input->is_enabled) ++ if (tc->gl->ext != VLC_GL_EXT_EGL || ++ !tc->gl->egl.createImageKHR || !tc->gl->egl.destroyImageKHR) + { -+ if ((status = set_extradata_and_commit(dec, sys)) != MMAL_SUCCESS) -+ goto fail; ++ // Missing an important callback ++ msg_Dbg(tc, "Missing EGL xxxImageKHR calls"); ++ return rv; ++ } + -+ if ((status = mmal_port_enable(sys->input, input_port_cb)) != MMAL_SUCCESS) -+ { -+ msg_Err(dec, "Input port enable failed"); -+ goto fail; -+ } ++ if ((tc->priv = calloc(1, sizeof(drm_gl_converter_t))) == NULL) ++ { ++ msg_Err(tc, "priv alloc failure"); ++ rv = VLC_ENOMEM; ++ goto fail; ++ } ++ drm_gl_converter_t * const sys = tc->priv; + -+ if ((status = decoder_send_extradata(dec, sys)) != MMAL_SUCCESS) -+ goto fail; ++ if ((sys->glEGLImageTargetTexture2DOES = vlc_gl_GetProcAddress(tc->gl, "glEGLImageTargetTexture2DOES")) == NULL) ++ { ++ msg_Err(tc, "Failed to bind GL fns"); ++ goto fail; + } + -+ // *** We cannot get a picture to put the result in 'till we have -+ // reported the size & the output stages have been set up - if (atomic_load(&sys->started)) - fill_output_port(dec); - -@@ -563,18 +703,21 @@ static int decode(decoder_t *dec, block_ - if (block->i_flags & BLOCK_FLAG_CORRUPTED) - flags |= MMAL_BUFFER_HEADER_FLAG_CORRUPTED; - -- while (block && block->i_buffer > 0) { -- buffer = mmal_queue_timedwait(sys->input_pool->queue, 100); -+ while (block != NULL) ++ if ((tc->fshader = tc_fragment_shader_init(tc, GL_TEXTURE_EXTERNAL_OES, tc->fmt.i_chroma, tc->fmt.space)) == 0) + { -+ buffer = mmal_queue_wait(sys->input_pool->queue); - if (!buffer) { - msg_Err(dec, "Failed to retrieve buffer header for input data"); -- need_flush = true; -- break; -+ goto fail; - } ++ msg_Err(tc, "Failed to make shader"); ++ goto fail; ++ } + - mmal_buffer_header_reset(buffer); - buffer->cmd = 0; -- buffer->pts = block->i_pts != 0 ? block->i_pts : block->i_dts; -+ buffer->pts = block->i_pts != VLC_TICK_INVALID ? block->i_pts : -+ block->i_dts != VLC_TICK_INVALID ? block->i_dts : MMAL_TIME_UNKNOWN; - buffer->dts = block->i_dts; - buffer->alloc_size = sys->input->buffer_size; -+ buffer->user_data = NULL; ++ tc->handle_texs_gen = true; // We manage the texs ++ tc->pf_update = tc_drm_update; ++ ++ return VLC_SUCCESS; ++ ++fail: ++ CloseGLConverter(obj); ++ return rv; ++} ++ ++vlc_module_begin () ++ set_description("DRM OpenGL surface converter") ++ set_shortname (N_("DRMGLConverter")) ++ set_capability("glconv", 900) ++ set_callbacks(OpenGLConverter, CloseGLConverter) ++ set_category(CAT_VIDEO) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ add_shortcut("drm_gl_converter") ++vlc_module_end () ++ +--- a/modules/hw/mmal/Makefile.am ++++ b/modules/hw/mmal/Makefile.am +@@ -1,23 +1,60 @@ + include $(top_srcdir)/modules/common.am + mmaldir = $(pluginsdir)/mmal - len = block->i_buffer; - if (len > buffer->alloc_size) -@@ -585,94 +728,1808 @@ static int decode(decoder_t *dec, block_ - block->i_buffer -= len; - buffer->length = len; - if (block->i_buffer == 0) { -+ flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END; -+ if (block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE) { -+ msg_Dbg(dec, "EOS sent"); -+ flags |= MMAL_BUFFER_HEADER_FLAG_EOS; -+ } - buffer->user_data = block; - block = NULL; - } - buffer->flags = flags; +-AM_CFLAGS += $(CFLAGS_mmal) +-AM_LDFLAGS += -rpath '$(mmaldir)' $(LDFLAGS_mmal) ++AM_CFLAGS += -pthread $(CFLAGS_mmal) ++AM_LDFLAGS += -pthread -rpath '$(mmaldir)' $(LDFLAGS_mmal) -+#if TRACE_ALL -+ msg_Dbg(dec, "%s: -- Send buffer: cmd=%d, data=%p, size=%d, len=%d, offset=%d, flags=%#x, pts=%lld, dts=%lld", __func__,\ -+ buffer->cmd, buffer->data, buffer->alloc_size, buffer->length, buffer->offset, -+ buffer->flags, (long long)buffer->pts, (long long)buffer->dts); -+#endif - status = mmal_port_send_buffer(sys->input, buffer); - if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to send buffer to input port (status=%"PRIx32" %s)", - status, mmal_status_to_string(status)); -- break; -+ goto fail; - } -- atomic_fetch_add(&sys->input_in_transit, 1); +-libmmal_vout_plugin_la_SOURCES = vout.c mmal_picture.c mmal_picture.h ++mmal_LTLIBRARIES = + -+ // Reset flushed flag once we have sent a buf -+ sys->b_flushed = false; -+ flags &= ~MMAL_BUFFER_HEADER_FLAG_FRAME_START; - } -+ return VLCDEC_SUCCESS; - --out: -- if (need_flush) -- flush_decoder(dec); -+fail: -+ flush_decoder(dec); -+ return VLCDEC_ECRITICAL; ++if HAVE_MMAL ++libmmal_vout_plugin_la_SOURCES = vout.c mmal_cma.c mmal_picture.c subpic.c\ ++ mmal_cma.h mmal_cma_int.h mmal_picture.h subpic.h transform_ops.h\ ++ mmal_piccpy_neon.S + libmmal_vout_plugin_la_CFLAGS = $(AM_CFLAGS) +-libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm ++libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm -lX11 -lXrandr + libmmal_vout_plugin_la_LIBADD = $(LIBS_mmal) +-mmal_LTLIBRARIES = libmmal_vout_plugin.la ++mmal_LTLIBRARIES += libmmal_vout_plugin.la -- return VLCDEC_SUCCESS; - } +-libmmal_codec_plugin_la_SOURCES = codec.c ++libmmal_codec_plugin_la_SOURCES = codec.c mmal_cma.c mmal_picture.c subpic.c\ ++ mmal_cma.h mmal_cma_int.h mmal_picture.h subpic.h transform_ops.h\ ++ blend_rgba_neon.S mmal_piccpy_neon.S + libmmal_codec_plugin_la_CFLAGS = $(AM_CFLAGS) + libmmal_codec_plugin_la_LDFLAGS = $(AM_LDFLAGS) + libmmal_codec_plugin_la_LIBADD = $(LIBS_mmal) + mmal_LTLIBRARIES += libmmal_codec_plugin.la --static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +-libmmal_deinterlace_plugin_la_SOURCES = deinterlace.c mmal_picture.c ++libmmal_deinterlace_plugin_la_SOURCES = deinterlace.c mmal_picture.c mmal_cma.c\ ++ mmal_cma.h mmal_cma_int.h mmal_picture.h transform_ops.h\ ++ mmal_piccpy_neon.S + libmmal_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) + libmmal_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) + libmmal_deinterlace_plugin_la_LIBADD = $(LIBS_mmal) + mmal_LTLIBRARIES += libmmal_deinterlace_plugin.la + -+static void CloseDecoder(decoder_t *dec) - { -- decoder_t *dec = (decoder_t *)port->userdata; -+ decoder_sys_t *sys = dec->p_sys; ++libmmal_converter_plugin_la_SOURCES = converter_mmal.c mmal_cma.c mmal_picture.c\ ++ mmal_cma.h mmal_cma_int.h mmal_picture.h transform_ops.h\ ++ mmal_piccpy_neon.S ++libmmal_converter_plugin_la_CFLAGS = $(AM_CFLAGS) ++libmmal_converter_plugin_la_LDFLAGS = $(AM_LDFLAGS) ++libmmal_converter_plugin_la_LIBADD = $(LIBS_mmal) ++mmal_LTLIBRARIES += libmmal_converter_plugin.la ++endif + -+#if TRACE_ALL -+ msg_Dbg(dec, "%s: <<<", __func__); -+#endif ++libmmal_xsplitter_plugin_la_SOURCES = xsplitter.c ++libmmal_xsplitter_plugin_la_CFLAGS = $(AM_CFLAGS) ++libmmal_xsplitter_plugin_la_LDFLAGS = $(AM_LDFLAGS) ++libmmal_xsplitter_plugin_la_LIBADD = $(LIBS_mmal) ++mmal_LTLIBRARIES += libmmal_xsplitter_plugin.la + -+ if (!sys) -+ return; + -+ if (sys->component != NULL) { -+ if (sys->input->is_enabled) -+ mmal_port_disable(sys->input); ++if HAVE_MMAL_AVCODEC ++libmmal_avcodec_plugin_la_SOURCES = mmal_avcodec.c mmal_cma_drmprime.c mmal_cma.c\ ++ mmal_picture.c\ ++ mmal_cma.h mmal_cma_int.h mmal_cma_drmprime.h mmal_picture.h transform_ops.h\ ++ mmal_piccpy_neon.S ++libmmal_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) ++libmmal_avcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS) ++libmmal_avcodec_plugin_la_LIBADD = $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(LIBS_mmal) ++mmal_LTLIBRARIES += libmmal_avcodec_plugin.la ++endif + -+ if (sys->output->is_enabled) -+ mmal_port_disable(sys->output); +--- /dev/null ++++ b/modules/hw/mmal/blend_rgba_neon.S +@@ -0,0 +1,189 @@ ++#include "../../arm_neon/asm.S" ++ .align 16 ++ .arch armv7-a ++ .syntax unified ++#if HAVE_AS_FPU_DIRECTIVE ++ .fpu neon-vfpv4 ++#endif + -+ if (sys->component->control->is_enabled) -+ mmal_port_disable(sys->component->control); ++@ blend_rgbx_rgba_neon + -+ if (sys->component->is_enabled) -+ mmal_component_disable(sys->component); ++@ Implements /255 as ((x * 257) + 0x8000) >> 16 ++@ This generates something in the range [(x+126)/255, (x+127)/255] which is good enough + -+ mmal_component_release(sys->component); -+ } ++@ There is advantage to aligning src and/or dest - dest gives a bit more due to being used twice + -+ if (sys->input_pool != NULL) -+ mmal_pool_destroy(sys->input_pool); + -+ if (sys->output_format != NULL) -+ mmal_format_free(sys->output_format); + -+ hw_mmal_port_pool_ref_release(sys->ppr, false); ++@ [r0] RGBx dest loaded into d20-d23 ++@ [r1] RGBA src merge loaded into d16-d19 ++@ r2 plane alpha ++@ r3 count (pixels) + -+ cma_vcsm_exit(sys->vcsm_init_type); ++.macro blend_main sR, sG, sB, sA, dR, dG, dB, dA + -+ vlc_mutex_destroy(&sys->pic_lock); -+ free(sys); -+} ++ push { r4, lr } + -+static int OpenDecoder(decoder_t *dec) -+{ -+ int ret = VLC_EGENERIC; -+ decoder_sys_t *sys; - MMAL_STATUS_T status; -+ const MMAL_FOURCC_T in_fcc = vlc_to_mmal_es_fourcc(dec->fmt_in.i_codec); ++ vdup.u8 d7, r2 + -+#if TRACE_ALL || 1 -+ { -+ char buf1[5], buf2[5], buf2a[5]; -+ char buf3[5], buf4[5]; -+ MMAL_RATIONAL_T r = rationalize_sar(dec->fmt_in.video.i_sar_num, dec->fmt_in.video.i_sar_den); ++ subs r3, #8 ++ vmov.u8 d6, #0xff + -+ msg_Dbg(dec, "%s: <<< (%s/%s)[%s] %dx%d %d/%d=%d/%d o:%#x -> (%s/%s) %dx%d %d/%d o:%#x", __func__, -+ str_fourcc(buf1, dec->fmt_in.i_codec), -+ str_fourcc(buf2, dec->fmt_in.video.i_chroma), -+ str_fourcc(buf2a, in_fcc), -+ dec->fmt_in.video.i_width, dec->fmt_in.video.i_height, -+ dec->fmt_in.video.i_sar_num, dec->fmt_in.video.i_sar_den, -+ r.num, r.den, -+ (int)dec->fmt_in.video.orientation, -+ str_fourcc(buf3, dec->fmt_out.i_codec), -+ str_fourcc(buf4, dec->fmt_out.video.i_chroma), -+ dec->fmt_out.video.i_width, dec->fmt_out.video.i_height, -+ dec->fmt_out.video.i_sar_num, dec->fmt_out.video.i_sar_den, -+ (int)dec->fmt_out.video.orientation); -+ } -+#endif -+ -+ if (!is_enc_supported(&supported_decode_in_enc, in_fcc)) -+ return VLC_EGENERIC; ++ blt 2f + -+ sys = calloc(1, sizeof(decoder_sys_t)); -+ if (!sys) { -+ ret = VLC_ENOMEM; -+ goto fail; -+ } -+ dec->p_sys = sys; -+ vlc_mutex_init(&sys->pic_lock); ++ @ If < 16 bytes to move then don't bother trying to align ++ @ (a) This means the the align doesn't need to worry about r3 underflow ++ @ (b) The overhead would be greater than any gain ++ cmp r3, #8 ++ mov r4, r3 ++ ble 1f + -+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { -+ msg_Err(dec, "VCSM init failed"); -+ goto fail; -+ } -+ msg_Info(dec, "VCSM init succeeded: %s", cma_vcsm_init_str(sys->vcsm_init_type)); ++ @ Align r1 on a 32 byte boundary ++ neg r3, r0 ++ ubfx r3, r3, #2, #3 + -+ sys->err_stream = MMAL_SUCCESS; ++ cmp r3, #0 ++ blne 10f + -+ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, &sys->component); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(dec, "Failed to create MMAL component %s (status=%"PRIx32" %s)", -+ MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status)); -+ goto fail; -+ } ++ sub r3, r4, r3 + -+ sys->input = sys->component->input[0]; -+ sys->output = sys->component->output[0]; ++1: ++ vld4.8 {d16, d17, d18, d19}, [r1] + -+ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)dec; -+ sys->input->format->encoding = in_fcc; ++1: ++ vmull.u8 q15, \sA, d7 + -+ if (!set_and_test_enc_supported(&supported_decode_in_enc, sys->input, in_fcc)) { -+#if TRACE_ALL -+ char cbuf[5]; -+ msg_Dbg(dec, "Format not supported: %s", str_fourcc(cbuf, in_fcc)); -+#endif -+ goto fail; -+ } ++ vld4.8 {d20, d21, d22, d23}, [r0] + -+ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)dec; -+ status = mmal_port_enable(sys->component->control, control_port_cb); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(dec, "Failed to enable control port %s (status=%"PRIx32" %s)", -+ sys->component->control->name, status, mmal_status_to_string(status)); -+ goto fail; -+ } ++ vsra.u16 q15, q15, #8 ++ subs r3, #8 ++ vrshrn.u16 d31, q15, #8 ++ vsub.u8 d30, d6, d31 + -+ if ((status = set_extradata_and_commit(dec, sys)) != MMAL_SUCCESS) -+ goto fail; ++ vmull.u8 q12, \sR, d31 ++ vmull.u8 q13, \sG, d31 ++ vmull.u8 q14, \sB, d31 ++ addge r1, #32 + -+ sys->input->buffer_size = sys->input->buffer_size_recommended; -+ sys->input->buffer_num = sys->input->buffer_num_recommended; ++ vmlal.u8 q12, \dR, d30 ++ vmlal.u8 q13, \dG, d30 ++ vmlal.u8 q14, \dB, d30 ++ vld4.8 {d16, d17, d18, d19}, [r1] + -+ status = mmal_port_enable(sys->input, input_port_cb); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(dec, "Failed to enable input port %s (status=%"PRIx32" %s)", -+ sys->input->name, status, mmal_status_to_string(status)); -+ goto fail; -+ } ++ vsra.u16 q12, q12, #8 @ * 257/256 ++ vsra.u16 q13, q13, #8 ++ vsra.u16 q14, q14, #8 + -+ // Set vanishingly unlikely shape (or at least crop) -+ // to ensure that we get a resolution changed event -+ // Small wxh are rejected (128x128 is rejected) so pick a -+ // plausible size. -+ // Crop doesn't seem to be checked for being constrained by wxh -+ // so we could place it outside the pic to be sure that it is -+ // never matched but stick with something legal in case it is ever -+ // actually checked -+ sys->output->format->es->video.height = 256; -+ sys->output->format->es->video.width = 256; -+ sys->output->format->es->video.crop.height = 4; -+ sys->output->format->es->video.crop.width = 2; -+ sys->output->format->es->video.crop.x = 66; -+ sys->output->format->es->video.crop.y = 88; ++ vrshrn.u16 \dR, q12, #8 ++ vrshrn.u16 \dG, q13, #8 ++ vrshrn.u16 \dB, q14, #8 ++ vmov.u8 \dA, #0xff + -+ if ((status = hw_mmal_opaque_output(VLC_OBJECT(dec), &sys->ppr, -+ sys->output, NUM_EXTRA_BUFFERS, decoder_output_cb)) != MMAL_SUCCESS) -+ goto fail; ++ vst4.8 {d20, d21, d22, d23}, [r0]! ++ bge 1b ++ add r1, #32 + -+ status = mmal_component_enable(sys->component); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(dec, "Failed to enable component %s (status=%"PRIx32" %s)", -+ sys->component->name, status, mmal_status_to_string(status)); -+ goto fail; -+ } ++2: ++ cmp r3, #-8 ++ blgt 10f + -+ if ((sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) -+ { -+ msg_Err(dec, "Failed to create input pool"); -+ goto fail; -+ } ++ pop { r4, pc } + -+ sys->b_flushed = true; + -+ if ((status = decoder_send_extradata(dec, sys)) != MMAL_SUCCESS) -+ goto fail; ++// Partial version ++// Align @ start & deal with tail ++10: ++ lsls r2, r3, #30 @ b2 -> C, b1 -> N ++ mov r2, r0 ++ bcc 1f ++ vld4.8 {d16[0], d17[0], d18[0], d19[0]}, [r1]! ++ vld4.8 {d20[0], d21[0], d22[0], d23[0]}, [r2]! ++ vld4.8 {d16[1], d17[1], d18[1], d19[1]}, [r1]! ++ vld4.8 {d20[1], d21[1], d22[1], d23[1]}, [r2]! ++ vld4.8 {d16[2], d17[2], d18[2], d19[2]}, [r1]! ++ vld4.8 {d20[2], d21[2], d22[2], d23[2]}, [r2]! ++ vld4.8 {d16[3], d17[3], d18[3], d19[3]}, [r1]! ++ vld4.8 {d20[3], d21[3], d22[3], d23[3]}, [r2]! ++1: ++ bpl 1f ++ vld4.8 {d16[4], d17[4], d18[4], d19[4]}, [r1]! ++ vld4.8 {d20[4], d21[4], d22[4], d23[4]}, [r2]! ++ vld4.8 {d16[5], d17[5], d18[5], d19[5]}, [r1]! ++ vld4.8 {d20[5], d21[5], d22[5], d23[5]}, [r2]! ++1: ++ tst r3, #1 ++ beq 1f ++ vld4.8 {d16[6], d17[6], d18[6], d19[6]}, [r1]! ++ vld4.8 {d20[6], d21[6], d22[6], d23[6]}, [r2]! ++1: ++ @ Set conditions for later ++ lsls r2, r3, #30 @ b2 -> C, b1 -> N + -+ // Given no better ideas at this point copy input format to output -+ // This also copies container stuff (such as orientation) that we do not -+ // decode from the ES but may be important to display -+ video_format_Copy(&dec->fmt_out.video, &dec->fmt_in.video); -+ dec->fmt_out.i_codec = VLC_CODEC_MMAL_OPAQUE; -+ dec->fmt_out.video.i_chroma = VLC_CODEC_MMAL_OPAQUE; ++ vmull.u8 q15, \sA, d7 ++ vsra.u16 q15, q15, #8 ++ vrshrn.u16 d31, q15, #8 ++ vsub.u8 d30, d6, d31 + ++ vmull.u8 q12, \sR, d31 ++ vmull.u8 q13, \sG, d31 ++ vmull.u8 q14, \sB, d31 + -+ dec->pf_decode = decode; -+ dec->pf_flush = flush_decoder; ++ vmlal.u8 q12, \dR, d30 ++ vmlal.u8 q13, \dG, d30 ++ vmlal.u8 q14, \dB, d30 + -+#if TRACE_ALL -+ msg_Dbg(dec, ">>> %s: ok", __func__); -+#endif -+ return 0; ++ vsra.u16 q12, q12, #8 ++ vsra.u16 q13, q13, #8 ++ vsra.u16 q14, q14, #8 + -+fail: -+ CloseDecoder(dec); -+#if TRACE_ALL -+msg_Dbg(dec, ">>> %s: FAIL: ret=%d", __func__, ret); -+#endif -+ return ret; -+} ++ vrshrn.u16 \dR, q12, #8 ++ vrshrn.u16 \dG, q13, #8 ++ vrshrn.u16 \dB, q14, #8 ++ vmov.u8 \dA, #0xff + -+// ---------------------------- ++ bcc 1f ++ vst4.8 {d20[0], d21[0], d22[0], d23[0]}, [r0]! ++ vst4.8 {d20[1], d21[1], d22[1], d23[1]}, [r0]! ++ vst4.8 {d20[2], d21[2], d22[2], d23[2]}, [r0]! ++ vst4.8 {d20[3], d21[3], d22[3], d23[3]}, [r0]! ++1: ++ bpl 1f ++ vst4.8 {d20[4], d21[4], d22[4], d23[4]}, [r0]! ++ vst4.8 {d20[5], d21[5], d22[5], d23[5]}, [r0]! ++1: ++ tst r3, #1 ++ bxeq lr ++ vst4.8 {d20[6], d21[6], d22[6], d23[6]}, [r0]! + -+#define CONV_MAX_LATENCY 1 // In frames ++ bx lr + -+typedef struct pic_fifo_s { -+ picture_t * head; -+ picture_t * tail; -+} pic_fifo_t; ++.endm + -+static inline picture_t * pic_fifo_get(pic_fifo_t * const pf) -+{ -+ picture_t * const pic = pf->head;; -+ if (pic != NULL) { -+ pf->head = pic->p_next; -+ pic->p_next = NULL; -+ } -+ return pic; -+} + -+static inline picture_t * pic_fifo_get_all(pic_fifo_t * const pf) -+{ -+ picture_t * const pic = pf->head;; -+ pf->head = NULL; -+ return pic; -+} ++@ [r0] RGBx dest (Byte order: R, G, B, x) ++@ [r1] RGBA src merge (Byte order: R, G, B, A) ++@ r2 plane alpha ++@ r3 count (pixels) + -+static inline void pic_fifo_release_all(pic_fifo_t * const pf) -+{ -+ picture_t * pic; -+ while ((pic = pic_fifo_get(pf)) != NULL) { -+ picture_Release(pic); -+ } -+} ++@ Whilst specified as RGBx+RGBA the only important part is the position of ++@ alpha, the other components are all treated the same + -+static inline void pic_fifo_init(pic_fifo_t * const pf) -+{ -+ pf->head = NULL; -+ pf->tail = NULL; // Not strictly needed -+} ++@ [r0] RGBx dest (Byte order: R, G, B, x) ++@ [r1] RGBA src merge (Byte order: R, G, B, A) - same as above ++@ r2 plane alpha ++@ r3 count (pixels) ++ .align 16 ++function blend_rgbx_rgba_neon ++ blend_main d16, d17, d18, d19, d20, d21, d22, d23 + -+static inline void pic_fifo_put(pic_fifo_t * const pf, picture_t * pic) -+{ -+ pic->p_next = NULL; -+ if (pf->head == NULL) -+ pf->head = pic; -+ else -+ pf->tail->p_next = pic; -+ pf->tail = pic; -+} + -+#define SUBS_MAX 3 ++@ [r0] RGBx dest (Byte order: R, G, B, x) ++@ [r1] RGBA src merge (Byte order: B, G, R, A) - B / R swapped ++@ r2 plane alpha ++@ r3 count (pixels) ++ .align 16 ++function blend_bgrx_rgba_neon ++ blend_main d18, d17, d16, d19, d20, d21, d22, d23 + -+typedef enum filter_resizer_e { -+ FILTER_RESIZER_RESIZER, -+ FILTER_RESIZER_ISP, -+ FILTER_RESIZER_HVS -+} filter_resizer_t; + -+typedef struct conv_frame_stash_s -+{ -+ mtime_t pts; -+ MMAL_BUFFER_HEADER_T * sub_bufs[SUBS_MAX]; -+} conv_frame_stash_t; + -+typedef struct filter_sys_t { -+ filter_resizer_t resizer_type; -+ MMAL_COMPONENT_T *component; -+ MMAL_PORT_T *input; -+ MMAL_PORT_T *output; -+ MMAL_POOL_T *out_pool; // Free output buffers -+ MMAL_POOL_T *in_pool; // Input pool to get BH for replication +--- /dev/null ++++ b/modules/hw/mmal/blend_rgba_neon.h +@@ -0,0 +1,17 @@ ++#ifndef HW_MMAL_BLEND_RGBA_NEON_H ++#define HW_MMAL_BLEND_RGBA_NEON_H + -+ cma_buf_pool_t * cma_in_pool; -+ cma_buf_pool_t * cma_out_pool; ++#ifdef __cplusplus ++extern "C" { ++#endif + -+ subpic_reg_stash_t subs[SUBS_MAX]; ++typedef void blend_neon_fn(void * dest, const void * src, int alpha, unsigned int n); ++extern blend_neon_fn blend_rgbx_rgba_neon; ++extern blend_neon_fn blend_bgrx_rgba_neon; + -+ pic_fifo_t ret_pics; ++#ifdef __cplusplus ++} ++#endif + -+ unsigned int pic_n; -+ vlc_sem_t sem; -+ vlc_mutex_t lock; ++#endif + -+ MMAL_STATUS_T err_stream; +--- /dev/null ++++ b/modules/hw/mmal/blend_test.c +@@ -0,0 +1,180 @@ ++#include ++#include ++#include + -+ bool needs_copy_in; -+ bool is_cma; -+ bool is_sliced; -+ bool out_fmt_set; -+ const char * component_name; -+ MMAL_PORT_BH_CB_T in_port_cb_fn; -+ MMAL_PORT_BH_CB_T out_port_cb_fn; ++#include "blend_rgba_neon.h" + -+ uint64_t frame_seq; -+ conv_frame_stash_t stash[16]; ++#define RPI_PROFILE 1 ++#define RPI_PROC_ALLOC 1 ++#include "rpi_prof.h" + -+ // Slice specific tracking stuff -+ struct { -+ pic_fifo_t pics; -+ unsigned int line; // Lines filled -+ } slice; ++static inline unsigned div255(unsigned v) ++{ ++ // This models what we we do in the asm for / 255 ++ // It generates something in the range [(i+126)/255, (i+127)/255] which is good enough ++ return ((v * 257) + 0x8000) >> 16; ++} + -+ vcsm_init_type_t vcsm_init_type; -+} filter_sys_t; ++static inline unsigned int a_merge(unsigned int dst, unsigned src, unsigned f) ++{ ++ return div255((255 - f) * (dst) + src * f); ++} + + -+static MMAL_STATUS_T pic_to_format(MMAL_ES_FORMAT_T * const es_fmt, const picture_t * const pic) ++static void merge_line(void * dest, const void * src, int alpha, unsigned int n) +{ -+ unsigned int bpp = (pic->format.i_bits_per_pixel + 7) >> 3; -+ MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video; -+ -+ es_fmt->type = MMAL_ES_TYPE_VIDEO; -+ es_fmt->encoding = vlc_to_mmal_video_fourcc(&pic->format); -+ es_fmt->encoding_variant = 0; ++ unsigned int i; ++ const uint8_t * s_data = src; ++ uint8_t * d_data = dest; + -+ // Fill in crop etc. -+ hw_mmal_vlc_fmt_to_mmal_fmt(es_fmt, &pic->format); -+ // Override width / height with strides if appropriate -+ if (bpp != 0) { -+ v_fmt->width = pic->p[0].i_pitch / bpp; -+ v_fmt->height = pic->p[0].i_lines; ++ for (i = 0; i != n; ++i) { ++ const uint32_t s_pel = ((const uint32_t *)s_data)[i]; ++ const uint32_t d_pel = ((const uint32_t *)d_data)[i]; ++ const unsigned int a = div255(alpha * (s_pel >> 24)); ++ ((uint32_t *)d_data)[i] = 0xff000000 | ++ (a_merge((d_pel >> 16) & 0xff, (s_pel >> 16) & 0xff, a) << 16) | ++ (a_merge((d_pel >> 8) & 0xff, (s_pel >> 8) & 0xff, a) << 8 ) | ++ (a_merge((d_pel >> 0) & 0xff, (s_pel >> 0) & 0xff, a) << 0 ); + } -+ return MMAL_SUCCESS; +} + + -+static MMAL_STATUS_T conv_enable_in(filter_t * const p_filter, filter_sys_t * const sys) ++// Merge RGBA with BGRA ++static void merge_line2(void * dest, const void * src, int alpha, unsigned int n) +{ -+ MMAL_STATUS_T err = MMAL_SUCCESS; ++ unsigned int i; ++ const uint8_t * s_data = src; ++ uint8_t * d_data = dest; + -+ if (!sys->input->is_enabled && -+ (err = mmal_port_enable(sys->input, sys->in_port_cb_fn)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Failed to enable input port %s (status=%"PRIx32" %s)", -+ sys->input->name, err, mmal_status_to_string(err)); ++ for (i = 0; i != n; ++i) { ++ const uint32_t s_pel = ((const uint32_t *)s_data)[i]; ++ const uint32_t d_pel = ((const uint32_t *)d_data)[i]; ++ const unsigned int a = div255(alpha * (s_pel >> 24)); ++ ((uint32_t *)d_data)[i] = 0xff000000 | ++ (a_merge((d_pel >> 0) & 0xff, (s_pel >> 16) & 0xff, a) << 0 ) | ++ (a_merge((d_pel >> 8) & 0xff, (s_pel >> 8) & 0xff, a) << 8 ) | ++ (a_merge((d_pel >> 16) & 0xff, (s_pel >> 0) & 0xff, a) << 16); + } -+ return err; +} + -+static MMAL_STATUS_T conv_enable_out(filter_t * const p_filter, filter_sys_t * const sys) ++#define BUF_SIZE 256 ++#define BUF_SLACK 16 ++#define BUF_ALIGN 64 ++#define BUF_ALLOC (BUF_SIZE + 2*BUF_SLACK + BUF_ALIGN) ++ ++static void test_line(const uint32_t * const dx, const unsigned int d_off, ++ const uint32_t * const sx, const unsigned int s_off, ++ const unsigned int alpha, const unsigned int len, const int prof_no) +{ -+ MMAL_STATUS_T err = MMAL_SUCCESS; ++ uint32_t d0_buf[BUF_ALLOC]; ++ uint32_t d1_buf[BUF_ALLOC]; ++ const uint32_t * const s0 = sx + s_off; + -+ if (sys->is_cma) -+ { -+ if (sys->cma_out_pool == NULL && -+ (sys->cma_out_pool = cma_buf_pool_new(CONVERTER_BUFFERS, CONVERTER_BUFFERS, true, "mmal_resizer")) == NULL) -+ { -+ msg_Err(p_filter, "Failed to alloc cma buf pool"); -+ return MMAL_ENOMEM; -+ } -+ } -+ else -+ { -+ cma_buf_pool_deletez(&sys->cma_out_pool); -+ } ++ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; ++ uint32_t * const d1 = (uint32_t *)(((uintptr_t)d1_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; ++ unsigned int i; + -+ if (!sys->output->is_enabled && -+ (err = mmal_port_enable(sys->output, sys->out_port_cb_fn)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Failed to enable output port %s (status=%"PRIx32" %s)", -+ sys->output->name, err, mmal_status_to_string(err)); ++ memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4); ++ memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4); ++ ++ merge_line(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); ++ ++ PROFILE_START(); ++ blend_rgbx_rgba_neon(d1 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); ++ PROFILE_ACC_N(prof_no); ++ ++ for (i = 0; i != BUF_SIZE + BUF_SLACK*2; ++i) { ++ if (d0[i] != d1[i]) { ++ printf("%3d: %08x + %08x * %02x: %08x / %08x: len=%d\n", (int)(i - BUF_SLACK), dx[i], s0[i], alpha, d0[i], d1[i], len); ++ } + } -+ return err; +} + -+static void conv_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++static void test_line2(const uint32_t * const dx, const unsigned int d_off, ++ const uint32_t * const sx, const unsigned int s_off, ++ const unsigned int alpha, const unsigned int len, const int prof_no) +{ -+ filter_t * const p_filter = (filter_t *)port->userdata; -+ -+#if TRACE_ALL -+ msg_Dbg(p_filter, "%s: <<< cmd=%d, data=%p, pic=%p", __func__, buffer->cmd, buffer->data, buffer->user_data); -+#endif - - if (buffer->cmd == MMAL_EVENT_ERROR) { -- status = *(uint32_t *)buffer->data; -- msg_Err(dec, "MMAL error %"PRIx32" \"%s\"", status, -+ MMAL_STATUS_T status = *(uint32_t *)buffer->data; ++ uint32_t d0_buf[BUF_ALLOC]; ++ uint32_t d1_buf[BUF_ALLOC]; ++ const uint32_t * const s0 = sx + s_off; + -+ p_filter->p_sys->err_stream = status; ++ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; ++ uint32_t * const d1 = (uint32_t *)(((uintptr_t)d1_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; ++ unsigned int i; + -+ msg_Err(p_filter, "MMAL error %"PRIx32" \"%s\"", status, - mmal_status_to_string(status)); - } - - mmal_buffer_header_release(buffer); - } - --static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -+static void conv_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) - { -- block_t *block = (block_t *)buffer->user_data; -- decoder_t *dec = (decoder_t *)port->userdata; -- decoder_sys_t *sys = dec->p_sys; -- buffer->user_data = NULL; -+#if TRACE_ALL -+ picture_context_t * ctx = buf->user_data; -+// filter_sys_t *const sys = ((filter_t *)port->userdata)->p_sys; ++ memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4); ++ memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4); + -+ msg_Dbg((filter_t *)port->userdata, "<<< %s cmd=%d, ctx=%p, buf=%p, flags=%#x, len=%d/%d, pts=%lld", -+ __func__, buf->cmd, ctx, buf, buf->flags, buf->length, buf->alloc_size, (long long)buf->pts); -+#else -+ VLC_UNUSED(port); -+#endif ++ merge_line2(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); + -+ mmal_buffer_header_release(buf); ++ PROFILE_START(); ++ blend_bgrx_rgba_neon(d1 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); ++ PROFILE_ACC_N(prof_no); + -+#if TRACE_ALL -+ msg_Dbg((filter_t *)port->userdata, ">>> %s", __func__); -+#endif ++ for (i = 0; i != BUF_SIZE + BUF_SLACK*2; ++i) { ++ if (d0[i] != d1[i]) { ++ printf("%3d: %08x + %08x * %02x: %08x / %08x: len=%d\n", (int)(i - BUF_SLACK), dx[i], s0[i], alpha, d0[i], d1[i], len); ++ } ++ } +} + -+static void conv_out_q_pic(filter_sys_t * const sys, picture_t * const pic) -+{ -+ pic->p_next = NULL; + -+ vlc_mutex_lock(&sys->lock); -+ pic_fifo_put(&sys->ret_pics, pic); -+ vlc_mutex_unlock(&sys->lock); - -- mmal_buffer_header_release(buffer); -- if (block) -- block_Release(block); -- atomic_fetch_sub(&sys->input_in_transit, 1); - vlc_sem_post(&sys->sem); - } - --static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -+static void conv_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) - { -- decoder_t *dec = (decoder_t *)port->userdata; -- decoder_sys_t *sys = dec->p_sys; -- picture_t *picture; -- MMAL_EVENT_FORMAT_CHANGED_T *fmt; -- MMAL_ES_FORMAT_T *format; -- -- if (buffer->cmd == 0) { -- picture = (picture_t *)buffer->user_data; -- if (buffer->length > 0) { -- picture->date = buffer->pts; -- picture->b_progressive = sys->b_progressive; -- picture->b_top_field_first = sys->b_top_field_first; -- decoder_QueueVideo(dec, picture); -- } else { -- picture_Release(picture); -- if (sys->output_pool) { -- buffer->user_data = NULL; -- buffer->alloc_size = 0; -- buffer->data = NULL; -- mmal_buffer_header_release(buffer); -- } -- } -- atomic_fetch_sub(&sys->output_in_transit, 1); -- vlc_sem_post(&sys->sem); -- } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) { -- fmt = mmal_event_format_changed_get(buffer); -+ filter_t * const p_filter = (filter_t *)port->userdata; -+ filter_sys_t * const sys = p_filter->p_sys; - -- format = mmal_format_alloc(); -- mmal_format_full_copy(format, fmt->format); -+#if TRACE_ALL -+ msg_Dbg(p_filter, "<<< %s: cmd=%d, flags=%#x, pic=%p, data=%p, len=%d/%d, pts=%lld/%lld", __func__, -+ buf->cmd, buf->flags, buf->user_data, buf->data, buf->length, buf->alloc_size, -+ (long long)buf->pts, (long long)sys->stash[(unsigned int)(buf->pts & 0xf)].pts); -+#endif -+ if (buf->cmd == 0) { -+ picture_t * const pic = (picture_t *)buf->user_data; - -- if (sys->opaque) -- format->encoding = MMAL_ENCODING_OPAQUE; -+ if (pic == NULL) { -+ msg_Err(p_filter, "%s: Buffer has no attached picture", __func__); -+ } -+ else if (buf->data == NULL || buf->length == 0) -+ { -+#if TRACE_ALL -+ msg_Dbg(p_filter, "%s: Buffer has no data", __func__); -+#endif -+ } -+ else -+ { -+ buf_to_pic_copy_props(pic, buf); -+ -+ // Set pic data pointers from buf aux info now it has it -+ if (sys->is_cma) { -+ if (cma_pic_set_data(pic, sys->output->format, buf) != VLC_SUCCESS) -+ msg_Err(p_filter, "Failed to set data"); -+ } -+ -+// draw_corners(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 0, 0, pic->p[0].i_visible_pitch / 4, pic->p[0].i_visible_lines); -+#if DEBUG_SQUARES -+ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 0, 0, 32, 32, 0xffff0000); -+ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 32, 0, 32, 32, 0xff00ff00); -+ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 64, 0, 32, 32, 0xff0000ff); -+#endif + -+ buf->user_data = NULL; // Responsability for this pic no longer with buffer -+ conv_out_q_pic(sys, pic); -+ } -+ } -+ -+ mmal_buffer_header_release(buf); -+} -+ -+ -+static void slice_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) ++int main(int argc, char *argv[]) +{ -+ filter_t * const p_filter = (filter_t *)port->userdata; -+ filter_sys_t * const sys = p_filter->p_sys; ++ unsigned int i, j; ++ uint32_t d0_buf[BUF_ALLOC]; ++ uint32_t s0_buf[BUF_ALLOC]; + -+#if TRACE_ALL -+ msg_Dbg(p_filter, "<<< %s: cmd=%d, flags=%#x, pic=%p, data=%p, len=%d/%d, pts=%lld", __func__, -+ buf->cmd, buf->flags, buf->user_data, buf->data, buf->length, buf->alloc_size, (long long)buf->pts); -+#endif ++ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + 63) & ~63) + 0; ++ uint32_t * const s0 = (uint32_t *)(((uintptr_t)s0_buf + 63) & ~63) + 0; + -+ if (buf->cmd != 0) -+ { -+ mmal_buffer_header_release(buf); -+ return; -+ } ++ PROFILE_INIT(); + -+ if (buf->data == NULL || buf->length == 0) -+ { -+#if TRACE_ALL -+ msg_Dbg(p_filter, "%s: Buffer has no data", __func__); -+#endif ++ for (i = 0; i != 255*255; ++i) { ++ unsigned int a = div255(i); ++ unsigned int b = (i + 127)/255; ++ unsigned int c = (i + 126)/255; ++ if (a != b && a != c) ++ printf("%d/255: %d != %d/%d\n", i, a, b, c); + } -+ else -+ { -+ // Got slice -+ picture_t *pic = sys->slice.pics.head; -+ const unsigned int scale_lines = sys->output->format->es->video.height; // Expected lines of callback -+ -+ if (pic == NULL) { -+ msg_Err(p_filter, "No output picture"); -+ goto fail; -+ } -+ -+ // Copy lines -+ // * single plane only - fix for I420 -+ { -+ const unsigned int scale_n = __MIN(scale_lines - sys->slice.line, MMAL_SLICE_HEIGHT); -+ const unsigned int pic_lines = pic->p[0].i_lines; -+ const unsigned int copy_n = sys->slice.line + scale_n <= pic_lines ? scale_n : -+ sys->slice.line >= pic_lines ? 0 : -+ pic_lines - sys->slice.line; -+ -+ const unsigned int src_stride = buf->type->video.pitch[0]; -+ const unsigned int dst_stride = pic->p[0].i_pitch; -+ uint8_t *dst = pic->p[0].p_pixels + sys->slice.line * dst_stride; -+ const uint8_t *src = buf->data + buf->type->video.offset[0]; -+ -+ if (src_stride == dst_stride) { -+ if (copy_n != 0) -+ memcpy(dst, src, src_stride * copy_n); -+ } -+ else { -+ unsigned int i; -+ for (i = 0; i != copy_n; ++i) { -+ memcpy(dst, src, __MIN(dst_stride, src_stride)); -+ dst += dst_stride; -+ src += src_stride; -+ } -+ } -+ sys->slice.line += scale_n; -+ } -+ -+ if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) != 0 || sys->slice.line >= scale_lines) { + -+ if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) == 0 || sys->slice.line != scale_lines) { -+ // Stuff doesn't add up... -+ msg_Err(p_filter, "Line count (%d/%d) & EOF disagree (flags=%#x)", sys->slice.line, scale_lines, buf->flags); -+ goto fail; -+ } -+ else { -+ sys->slice.line = 0; ++ for (i = 0; i != BUF_ALLOC; ++i) { ++ d0_buf[i] = 0xff00 | i; ++ s0_buf[i] = (i << 24) | 0x40ffc0; ++ } + -+ vlc_mutex_lock(&sys->lock); -+ pic_fifo_get(&sys->slice.pics); // Remove head from Q -+ vlc_mutex_unlock(&sys->lock); ++ for (i = 0; i != 256; ++i) { ++ test_line(d0, 0, s0, 0, i, 256, -1); ++ } ++ for (i = 0; i != 256; ++i) { ++ test_line(d0, 0, s0, 0, 128, i, -1); ++ } + -+ buf_to_pic_copy_props(pic, buf); -+ conv_out_q_pic(sys, pic); -+ } ++ for (j = 0; j != 16; ++j) { ++ for (i = 0; i != 256; ++i) { ++ test_line(d0, j & 3, s0, j >> 2, i, 256, j); + } ++ PROFILE_PRINTF_N(j); ++ PROFILE_CLEAR_N(j); + } ++ printf("Done 1\n"); + -+ // Put back -+ buf->user_data = NULL; // Zap here to make sure we can't reuse later -+ mmal_buffer_header_reset(buf); ++ for (i = 0; i != 256; ++i) { ++ test_line2(d0, 0, s0, 0, i, 256, -1); ++ } ++ for (i = 0; i != 256; ++i) { ++ test_line2(d0, 0, s0, 0, 128, i, -1); ++ } + -+ if (mmal_port_send_buffer(sys->output, buf) != MMAL_SUCCESS) { -+ mmal_buffer_header_release(buf); ++ for (j = 0; j != 16; ++j) { ++ for (i = 0; i != 256; ++i) { ++ test_line2(d0, j & 3, s0, j >> 2, i, 256, j); ++ } ++ PROFILE_PRINTF_N(j); + } -+ return; ++ printf("Done 2\n"); + -+fail: -+ sys->err_stream = MMAL_EIO; -+ vlc_sem_post(&sys->sem); // If we were waiting then break us out - the flush should fix sem values ++ return 0; +} + +--- a/modules/hw/mmal/codec.c ++++ b/modules/hw/mmal/codec.c +@@ -26,267 +26,448 @@ + #include "config.h" + #endif + ++#include + -+static void conv_flush(filter_t * p_filter) -+{ -+ filter_sys_t * const sys = p_filter->p_sys; -+ unsigned int i; -+ -+#if TRACE_ALL -+ msg_Dbg(p_filter, "<<< %s", __func__); -+#endif + #include +-#include ++#include + #include + #include ++#include + #include + +-#include + #include + #include + #include + ++#include + -+ if (sys->resizer_type == FILTER_RESIZER_HVS) -+ { -+ for (i = 0; i != SUBS_MAX; ++i) { -+ hw_mmal_subpic_flush(VLC_OBJECT(p_filter), sys->subs + i); -+ } -+ } ++#include "mmal_cma.h" + #include "mmal_picture.h" + ++#include "subpic.h" ++#include "blend_rgba_neon.h" + -+ if (sys->input != NULL && sys->input->is_enabled) -+ mmal_port_disable(sys->input); ++#define TRACE_ALL 0 + -+ if (sys->output != NULL && sys->output->is_enabled) -+ mmal_port_disable(sys->output); ++#define OPT_TO_FROM_ZC 0 + -+// cma_buf_pool_deletez(&sys->cma_out_pool); -+ -+ // Free up anything we may have already lying around -+ // Don't need lock as the above disables should have prevented anything -+ // happening in the background -+ -+ for (i = 0; i != 16; ++i) { -+ conv_frame_stash_t *const stash = sys->stash + i; -+ unsigned int sub_no; -+ -+ stash->pts = MMAL_TIME_UNKNOWN; -+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { -+ if (stash->sub_bufs[sub_no] != NULL) { -+ mmal_buffer_header_release(stash->sub_bufs[sub_no]); -+ stash->sub_bufs[sub_no] = NULL; -+ } -+ } -+ } -+ -+ pic_fifo_release_all(&sys->slice.pics); -+ pic_fifo_release_all(&sys->ret_pics); -+ -+ // Reset sem values - easiest & most reliable way is to just kill & re-init -+ vlc_sem_destroy(&sys->sem); -+ vlc_sem_init(&sys->sem, 0); -+ sys->pic_n = 0; + /* + * This seems to be a bit high, but reducing it causes instabilities + */ + #define NUM_EXTRA_BUFFERS 5 ++//#define NUM_EXTRA_BUFFERS 10 + #define NUM_DECODER_BUFFER_HEADERS 30 + +-#define MIN_NUM_BUFFERS_IN_TRANSIT 2 ++#define CONVERTER_BUFFERS 4 // Buffers on the output of the converter + -+ // Reset error status -+ sys->err_stream = MMAL_SUCCESS; ++#define MMAL_SLICE_HEIGHT 16 ++#define MMAL_ALIGN_W 32 ++#define MMAL_ALIGN_H 16 + + #define MMAL_OPAQUE_NAME "mmal-opaque" + #define MMAL_OPAQUE_TEXT N_("Decode frames directly into RPI VideoCore instead of host memory.") + #define MMAL_OPAQUE_LONGTEXT N_("Decode frames directly into RPI VideoCore instead of host memory. This option must only be used with the MMAL video output plugin.") + +-static int OpenDecoder(decoder_t *dec); +-static void CloseDecoder(decoder_t *dec); +- +-vlc_module_begin() +- set_shortname(N_("MMAL decoder")) +- set_description(N_("MMAL-based decoder plugin for Raspberry Pi")) +- set_capability("video decoder", 90) +- add_shortcut("mmal_decoder") +- add_bool(MMAL_OPAQUE_NAME, true, MMAL_OPAQUE_TEXT, MMAL_OPAQUE_LONGTEXT, false) +- set_callbacks(OpenDecoder, CloseDecoder) +-vlc_module_end() ++#define MMAL_RESIZE_NAME "mmal-resize" ++#define MMAL_RESIZE_TEXT N_("Use mmal resizer rather than hvs.") ++#define MMAL_RESIZE_LONGTEXT N_("Use mmal resizer rather than isp. This uses less gpu memory than the ISP but is slower.") + -+#if TRACE_ALL -+ msg_Dbg(p_filter, ">>> %s", __func__); -+#endif -+} ++#define MMAL_ISP_NAME "mmal-isp" ++#define MMAL_ISP_TEXT N_("Use mmal isp rather than hvs.") ++#define MMAL_ISP_LONGTEXT N_("Use mmal isp rather than hvs. This may be faster but has no blend.") + -+static void conv_stash_fixup(filter_t * const p_filter, filter_sys_t * const sys, picture_t * const p_pic) ++#define MMAL_DECODE_ENABLE_NAME "mmal-decode-enable" ++#define MMAL_DECODE_ENABLE_TEXT N_("Enable mmal decode even if normally disabled") ++#define MMAL_DECODE_ENABLE_LONGTEXT N_("Enable mmal decode even if normally disabled. MMAL decode is normally disabled on Pi4 or later.") + +-struct decoder_sys_t { +- bool opaque; ++typedef struct decoder_sys_t +{ -+ conv_frame_stash_t * const stash = sys->stash + (p_pic->date & 0xf); -+ unsigned int sub_no; -+ VLC_UNUSED(p_filter); -+ -+ p_pic->date = stash->pts; -+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { -+ if (stash->sub_bufs[sub_no] != NULL) { -+ // **** Do stashed blend -+ // **** Aaargh, bother... need to rescale subs too -+ -+ mmal_buffer_header_release(stash->sub_bufs[sub_no]); -+ stash->sub_bufs[sub_no] = NULL; -+ } -+ } -+} + MMAL_COMPONENT_T *component; + MMAL_PORT_T *input; + MMAL_POOL_T *input_pool; + MMAL_PORT_T *output; +- MMAL_POOL_T *output_pool; /* only used for non-opaque mode */ ++ hw_mmal_port_pool_ref_t *ppr; + MMAL_ES_FORMAT_T *output_format; +- vlc_sem_t sem; + ++ MMAL_STATUS_T err_stream; + bool b_top_field_first; + bool b_progressive; + ++ bool b_flushed; + -+// Output buffers may contain a pic ref on error or flush -+// Free it -+static MMAL_BOOL_T out_buffer_pre_release_cb(MMAL_BUFFER_HEADER_T *header, void *userdata) -+{ -+ VLC_UNUSED(userdata); ++ vcsm_init_type_t vcsm_init_type; + -+ picture_t * const pic = header->user_data; -+ header->user_data = NULL; ++ // Lock to avoid pic update & allocate happenening simultainiously ++ // * We should be able to arrange life s.t. this isn't needed ++ // but while we are confused apply belt & braces ++ vlc_mutex_t pic_lock; + -+ if (pic != NULL) -+ picture_Release(pic); + /* statistics */ +- int output_in_transit; +- int input_in_transit; + atomic_bool started; +-}; ++} decoder_sys_t; + +-/* Utilities */ +-static int change_output_format(decoder_t *dec); +-static int send_output_buffer(decoder_t *dec); +-static void fill_output_port(decoder_t *dec); +- +-/* VLC decoder callback */ +-static int decode(decoder_t *dec, block_t *block); +-static void flush_decoder(decoder_t *dec); +- +-/* MMAL callbacks */ +-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); +-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); +-static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); + +-static int OpenDecoder(decoder_t *dec) +-{ +- int ret = VLC_SUCCESS; +- decoder_sys_t *sys; +- MMAL_PARAMETER_UINT32_T extra_buffers; +- MMAL_STATUS_T status; ++typedef struct supported_mmal_enc_s { ++ struct { ++ MMAL_PARAMETER_HEADER_T header; ++ MMAL_FOURCC_T encodings[64]; ++ } supported; ++ int n; ++} supported_mmal_enc_t; + -+ return MMAL_FALSE; ++#define SUPPORTED_MMAL_ENC_INIT \ ++{ \ ++ {{MMAL_PARAMETER_SUPPORTED_ENCODINGS, sizeof(((supported_mmal_enc_t *)0)->supported)}, {0}}, \ ++ -1 \ +} -+ -+static MMAL_STATUS_T conv_set_output(filter_t * const p_filter, filter_sys_t * const sys, picture_t * const pic) + +- if (dec->fmt_in.i_codec != VLC_CODEC_MPGV && +- dec->fmt_in.i_codec != VLC_CODEC_H264) +- return VLC_EGENERIC; ++static supported_mmal_enc_t supported_decode_in_enc = SUPPORTED_MMAL_ENC_INIT; + +- sys = calloc(1, sizeof(decoder_sys_t)); +- if (!sys) { +- ret = VLC_ENOMEM; +- goto out; ++static bool is_enc_supported(supported_mmal_enc_t * const support, const MMAL_FOURCC_T fcc) +{ -+ MMAL_STATUS_T status; -+ -+ sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter; -+ sys->output->format->type = MMAL_ES_TYPE_VIDEO; -+ sys->output->format->encoding = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video); -+ sys->output->format->encoding_variant = 0; -+ hw_mmal_vlc_fmt_to_mmal_fmt(sys->output->format, &p_filter->fmt_out.video); -+ -+ if (pic != NULL) -+ { -+ // Override default format width/height if we have a pic we need to match -+ if ((status = pic_to_format(sys->output->format, pic)) != MMAL_SUCCESS) -+ { -+ char cbuf[5]; -+ msg_Err(p_filter, "Bad format desc: %s, pic=%p, bits=%d", str_fourcc(cbuf, pic->format.i_chroma), pic, pic->format.i_bits_per_pixel); -+ return status; -+ } -+ -+ MMAL_VIDEO_FORMAT_T *fmt = &sys->output->format->es->video; -+ msg_Dbg(p_filter, "%s: %dx%d [(0,0) %dx%d]", __func__, fmt->width, fmt->height, fmt->crop.width, fmt->crop.height); -+ } -+ -+ if (sys->is_sliced) { -+ // Override height for slice -+ sys->output->format->es->video.height = MMAL_SLICE_HEIGHT; -+ } -+ -+ mmal_log_dump_format(sys->output->format); -+ -+ status = mmal_port_format_commit(sys->output); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(p_filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)", -+ sys->output->name, status, mmal_status_to_string(status)); -+ return status; -+ } -+ -+ sys->output->buffer_num = __MAX(sys->is_sliced ? 16 : 2, sys->output->buffer_num_recommended); -+ sys->output->buffer_size = sys->output->buffer_size_recommended; -+ -+ if ((status = conv_enable_out(p_filter, sys)) != MMAL_SUCCESS) -+ return status; ++ int i; + -+ return MMAL_SUCCESS; ++ if (fcc == 0) ++ return false; ++ if (support->n == -1) ++ return true; // Unknown - say OK ++ for (i = 0; i < support->n; ++i) { ++ if (support->supported.encodings[i] == fcc) ++ return true; + } +- dec->p_sys = sys; ++ return false; +} -+ -+ -+static picture_t *conv_get_out_pics(filter_sys_t * const sys) + +- sys->opaque = var_InheritBool(dec, MMAL_OPAQUE_NAME); +- bcm_host_init(); ++static bool set_and_test_enc_supported(supported_mmal_enc_t * const support, MMAL_PORT_T * port, const MMAL_FOURCC_T fcc) +{ -+ picture_t * ret_pics; -+ -+ vlc_sem_wait(&sys->sem); -+ -+ // Return a single pending buffer -+ vlc_mutex_lock(&sys->lock); -+ ret_pics = pic_fifo_get(&sys->ret_pics); -+ vlc_mutex_unlock(&sys->lock); -+ -+ return ret_pics; ++ if (support->n >= 0) ++ /* already done */; ++ else if (mmal_port_parameter_get(port, (MMAL_PARAMETER_HEADER_T *)&support->supported) != MMAL_SUCCESS) ++ support->n = 0; ++ else ++ support->n = (support->supported.header.size - sizeof(support->supported.header)) / ++ sizeof(support->supported.encodings[0]); + +- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, &sys->component); +- if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to create MMAL component %s (status=%"PRIx32" %s)", +- MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; +- } ++ return is_enc_supported(support, fcc); +} -+ -+static picture_t *conv_filter(filter_t *p_filter, picture_t *p_pic) -+{ -+ filter_sys_t * const sys = p_filter->p_sys; -+ picture_t * ret_pics = NULL; -+ MMAL_STATUS_T err; -+ const uint64_t frame_seq = ++sys->frame_seq; -+ conv_frame_stash_t * const stash = sys->stash + (frame_seq & 0xf); -+ MMAL_BUFFER_HEADER_T * out_buf = NULL; -+ -+#if TRACE_ALL -+ { -+ char dbuf0[5], dbuf1[5]; -+ msg_Dbg(p_filter, "<<< %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] sar:%d/%d", __func__, -+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, -+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, -+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, -+ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den, -+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, -+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, -+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height, -+ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den); + +- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)dec; +- status = mmal_port_enable(sys->component->control, control_port_cb); +- if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to enable control port %s (status=%"PRIx32" %s)", +- sys->component->control->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++static MMAL_FOURCC_T vlc_to_mmal_es_fourcc(const unsigned int fcc) ++{ ++ switch (fcc){ ++ case VLC_CODEC_MJPG: ++ return MMAL_ENCODING_MJPEG; ++ case VLC_CODEC_MP1V: ++ return MMAL_ENCODING_MP1V; ++ case VLC_CODEC_MPGV: ++ case VLC_CODEC_MP2V: ++ return MMAL_ENCODING_MP2V; ++ case VLC_CODEC_H263: ++ return MMAL_ENCODING_H263; ++ case VLC_CODEC_MP4V: ++ return MMAL_ENCODING_MP4V; ++ case VLC_CODEC_H264: ++ return MMAL_ENCODING_H264; ++ case VLC_CODEC_VP6: ++ return MMAL_ENCODING_VP6; ++ case VLC_CODEC_VP8: ++ return MMAL_ENCODING_VP8; ++ case VLC_CODEC_WMV1: ++ return MMAL_ENCODING_WMV1; ++ case VLC_CODEC_WMV2: ++ return MMAL_ENCODING_WMV2; ++ case VLC_CODEC_WMV3: ++ return MMAL_ENCODING_WMV3; ++ case VLC_CODEC_VC1: ++ return MMAL_ENCODING_WVC1; ++ case VLC_CODEC_THEORA: ++ return MMAL_ENCODING_THEORA; ++ default: ++ break; + } ++ return 0; ++} + +- sys->input = sys->component->input[0]; +- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)dec; +- if (dec->fmt_in.i_codec == VLC_CODEC_MPGV) +- sys->input->format->encoding = MMAL_ENCODING_MP2V; +- else +- sys->input->format->encoding = MMAL_ENCODING_H264; ++static MMAL_FOURCC_T pic_to_slice_mmal_fourcc(const MMAL_FOURCC_T fcc) ++{ ++ switch (fcc){ ++ case MMAL_ENCODING_I420: ++ return MMAL_ENCODING_I420_SLICE; ++ case MMAL_ENCODING_I422: ++ return MMAL_ENCODING_I422_SLICE; ++ case MMAL_ENCODING_ARGB: ++ return MMAL_ENCODING_ARGB_SLICE; ++ case MMAL_ENCODING_RGBA: ++ return MMAL_ENCODING_RGBA_SLICE; ++ case MMAL_ENCODING_ABGR: ++ return MMAL_ENCODING_ABGR_SLICE; ++ case MMAL_ENCODING_BGRA: ++ return MMAL_ENCODING_BGRA_SLICE; ++ case MMAL_ENCODING_RGB16: ++ return MMAL_ENCODING_RGB16_SLICE; ++ case MMAL_ENCODING_RGB24: ++ return MMAL_ENCODING_RGB24_SLICE; ++ case MMAL_ENCODING_RGB32: ++ return MMAL_ENCODING_RGB32_SLICE; ++ case MMAL_ENCODING_BGR16: ++ return MMAL_ENCODING_BGR16_SLICE; ++ case MMAL_ENCODING_BGR24: ++ return MMAL_ENCODING_BGR24_SLICE; ++ case MMAL_ENCODING_BGR32: ++ return MMAL_ENCODING_BGR32_SLICE; ++ default: ++ break; + } ++ return 0; ++} + +- if (dec->fmt_in.i_codec == VLC_CODEC_H264) { +- if (dec->fmt_in.i_extra > 0) { +- status = mmal_format_extradata_alloc(sys->input->format, +- dec->fmt_in.i_extra); +- if (status == MMAL_SUCCESS) { +- memcpy(sys->input->format->extradata, dec->fmt_in.p_extra, +- dec->fmt_in.i_extra); +- sys->input->format->extradata_size = dec->fmt_in.i_extra; +- } else { +- msg_Err(dec, "Failed to allocate extra format data on input port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- } ++#define DEBUG_SQUARES 0 ++#if DEBUG_SQUARES ++static void draw_square(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t val) ++{ ++ uint32_t * p = (uint32_t *)pic_buf + y * pic_stride + x; ++ unsigned int i; ++ for (i = 0; i != h; ++i) { ++ unsigned int j; ++ for (j = 0; j != w; ++j) { ++ p[j] = val; + } ++ p += pic_stride; + } ++} +#endif -+ -+ if (sys->err_stream != MMAL_SUCCESS) { -+ goto stream_fail; + +- status = mmal_port_format_commit(sys->input); +- if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++#if 0 ++static inline void draw_line(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int len, int inc) ++{ ++ uint32_t * p = (uint32_t *)pic_buf + y * pic_stride + x; ++ while (len-- != 0) { ++ *p = ~0U; ++ p += inc; + } +- sys->input->buffer_size = sys->input->buffer_size_recommended; +- sys->input->buffer_num = sys->input->buffer_num_recommended; ++} + +- status = mmal_port_enable(sys->input, input_port_cb); +- if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to enable input port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; +- } + +- sys->output = sys->component->output[0]; +- sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)dec; ++static void draw_corners(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int w, unsigned int h) ++{ ++ const unsigned int len = 20; ++ draw_line(pic_buf, pic_stride, x, y, len, 1); ++ draw_line(pic_buf, pic_stride, x, y, len, pic_stride); ++ draw_line(pic_buf, pic_stride, x + w - 1, y, len, -1); ++ draw_line(pic_buf, pic_stride, x + w - 1, y, len, pic_stride); ++ draw_line(pic_buf, pic_stride, x + w - 1, y + h - 1, len, -1); ++ draw_line(pic_buf, pic_stride, x + w - 1, y + h - 1, len, -(int)pic_stride); ++ draw_line(pic_buf, pic_stride, x, y + h - 1, len, 1); ++ draw_line(pic_buf, pic_stride, x, y + h - 1, len, -(int)pic_stride); ++} ++#endif + +- if (sys->opaque) { +- extra_buffers.hdr.id = MMAL_PARAMETER_EXTRA_BUFFERS; +- extra_buffers.hdr.size = sizeof(MMAL_PARAMETER_UINT32_T); +- extra_buffers.value = NUM_EXTRA_BUFFERS; +- status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", +- status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; +- } ++static MMAL_RATIONAL_T ++rationalize_sar(unsigned int num, unsigned int den) ++{ ++ static const unsigned int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 0}; ++ const unsigned int * p = primes; + +- msg_Dbg(dec, "Activate zero-copy for output port"); +- MMAL_PARAMETER_BOOLEAN_T zero_copy = { +- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, +- 1 +- }; ++ // If either num or den is 0 then return a well formed "unknown" ++ if (num == 0 || den == 0) { ++ return (MMAL_RATIONAL_T){.num = 0, .den = 0}; + } + +- status = mmal_port_parameter_set(sys->output, &zero_copy.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", +- sys->output->name, status, mmal_status_to_string(status)); +- goto out; ++ while (*p != 0 && num >= *p && den >= *p) { ++ if (num % *p != 0 || den % *p != 0) ++ ++p; ++ else { ++ num /= *p; ++ den /= *p; + } + } ++ return (MMAL_RATIONAL_T){.num = num, .den = den}; ++} + +- status = mmal_port_enable(sys->output, output_port_cb); +- if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to enable output port %s (status=%"PRIx32" %s)", +- sys->output->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; +- } ++// Buffer either attached to pic or released ++static picture_t * alloc_opaque_pic(decoder_t * const dec, MMAL_BUFFER_HEADER_T * const buf) ++{ ++ decoder_sys_t *const dec_sys = dec->p_sys; + +- status = mmal_component_enable(sys->component); +- if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to enable component %s (status=%"PRIx32" %s)", +- sys->component->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++ vlc_mutex_lock(&dec_sys->pic_lock); ++ picture_t * const pic = decoder_NewPicture(dec); ++ vlc_mutex_unlock(&dec_sys->pic_lock); + -+ // Check pic fmt corresponds to what we have set up -+ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic)) -+ { -+ msg_Dbg(p_filter, "Reset input port format"); -+ -+ // HVS can take new formats without disable, others need it -+ if (sys->resizer_type != FILTER_RESIZER_HVS) { -+ // Extract any pending pic -+ if (sys->pic_n >= 2) { -+ ret_pics = conv_get_out_pics(sys); -+ // If pic_n == 1 then we return without trying to get stuff -+ sys->pic_n = 1; -+ } -+ if (sys->input->is_enabled) { -+ if ((err = mmal_port_disable(sys->input)) != MMAL_SUCCESS) -+ msg_Warn(p_filter, "Format update disable failed: %s", mmal_status_to_string(err)); -+ } -+ } ++ if (pic == NULL) ++ goto fail1; + -+// mmal_log_dump_port(sys->input); -+ if ((err = mmal_port_format_commit(sys->input)) != MMAL_SUCCESS) -+ msg_Warn(p_filter, "Format update commit failed: %s", mmal_status_to_string(err)); -+ -+ // (Re)enable if required will be done later -+ } -+ -+ if (p_pic->context == NULL) { -+ // Can't have stashed subpics if not one of our pics -+ if (!sys->needs_copy_in) -+ msg_Dbg(p_filter, "%s: No context", __func__); -+ } -+ else if (sys->resizer_type == FILTER_RESIZER_HVS) -+ { -+ unsigned int sub_no = 0; -+ -+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { -+ int rv; -+ if ((rv = hw_mmal_subpic_update(VLC_OBJECT(p_filter), -+ hw_mmal_pic_sub_buf_get(p_pic, sub_no), -+ sys->subs + sub_no, -+ &p_pic->format, -+ &sys->output->format->es->video.crop, -+ MMAL_DISPLAY_ROT0, -+ frame_seq)) == 0) -+ break; -+ else if (rv < 0) -+ goto fail; -+ } -+ } -+ else -+ { -+ unsigned int sub_no = 0; -+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { -+ if ((stash->sub_bufs[sub_no] = hw_mmal_pic_sub_buf_get(p_pic, sub_no)) != NULL) { -+ mmal_buffer_header_acquire(stash->sub_bufs[sub_no]); -+ } -+ } -+ } -+ -+ if (!sys->out_fmt_set) { -+ sys->out_fmt_set = true; -+ -+ if (sys->is_sliced) { -+ // If zc then we will do stride conversion when we copy to arm side -+ // so no need to worry about actual pic dimensions here -+ if ((err = conv_set_output(p_filter, sys, NULL)) != MMAL_SUCCESS) -+ goto fail; -+ -+ sys->out_pool = mmal_port_pool_create(sys->output, sys->output->buffer_num, sys->output->buffer_size); -+ } -+ else { -+ picture_t *pic = filter_NewPicture(p_filter); -+ err = conv_set_output(p_filter, sys, pic); -+ picture_Release(pic); -+ if (err != MMAL_SUCCESS) -+ goto fail; -+ -+ sys->out_pool = mmal_pool_create(sys->output->buffer_num, 0); -+ } -+ -+ if (sys->out_pool == NULL) { -+ msg_Err(p_filter, "Failed to create output pool"); -+ goto fail; -+ } -+ } -+ -+ // Reenable stuff if the last thing we did was flush -+ if ((err = conv_enable_out(p_filter, sys)) != MMAL_SUCCESS || -+ (err = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS) -+ goto fail; -+ -+ // We attach pic to buf before stuffing the output port -+ // We could attach the pic on output for cma, but it is a lot easier to keep -+ // the code common. -+ { -+ picture_t * const out_pic = filter_NewPicture(p_filter); -+ -+ if (out_pic == NULL) -+ { -+ msg_Err(p_filter, "Failed to alloc required filter output pic"); -+ goto fail; -+ } -+ -+ out_pic->format.i_sar_den = p_filter->fmt_out.video.i_sar_den; -+ out_pic->format.i_sar_num = p_filter->fmt_out.video.i_sar_num; -+ -+ if (sys->is_sliced) { -+ vlc_mutex_lock(&sys->lock); -+ pic_fifo_put(&sys->slice.pics, out_pic); -+ vlc_mutex_unlock(&sys->lock); -+ -+ // Poke any returned pic buffers into output -+ // In general this should only happen immediately after enable -+ while ((out_buf = mmal_queue_get(sys->out_pool->queue)) != NULL) -+ mmal_port_send_buffer(sys->output, out_buf); -+ } -+ else -+ { -+ // 1 in - 1 out -+ if ((out_buf = mmal_queue_wait(sys->out_pool->queue)) == NULL) -+ { -+ msg_Err(p_filter, "Failed to get output buffer"); -+ picture_Release(out_pic); -+ goto fail; -+ } -+ mmal_buffer_header_reset(out_buf); -+ -+ // Attach out_pic to the buffer & ensure it is freed when the buffer is released -+ // On a good send callback the pic will be extracted to avoid this -+ out_buf->user_data = out_pic; -+ mmal_buffer_header_pre_release_cb_set(out_buf, out_buffer_pre_release_cb, NULL); ++ if (buf->length == 0) { ++ msg_Err(dec, "%s: Empty buffer", __func__); ++ goto fail2; + } + +- sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0); ++ if ((pic->context = hw_mmal_gen_context(buf, dec_sys->ppr)) == NULL) ++ goto fail2; + +- if (sys->opaque) { +- dec->fmt_out.i_codec = VLC_CODEC_MMAL_OPAQUE; +- dec->fmt_out.video.i_chroma = VLC_CODEC_MMAL_OPAQUE; +- } else { +- dec->fmt_out.i_codec = VLC_CODEC_I420; +- dec->fmt_out.video.i_chroma = VLC_CODEC_I420; ++ buf_to_pic_copy_props(pic, buf); + -+#if 0 -+ { -+ char dbuf0[5]; -+ msg_Dbg(p_filter, "out_pic %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", -+ str_fourcc(dbuf0, out_pic->format.i_chroma), -+ out_pic->format.i_width, out_pic->format.i_height, -+ out_pic->format.i_x_offset, out_pic->format.i_y_offset, -+ out_pic->format.i_visible_width, out_pic->format.i_visible_height, -+ out_pic->format.i_sar_num, out_pic->format.i_sar_den); -+ } ++#if TRACE_ALL ++ msg_Dbg(dec, "pic: prog=%d, tff=%d, date=%lld", pic->b_progressive, pic->b_top_field_first, (long long)pic->date); +#endif + -+ if (sys->is_cma) { -+ int rv; ++ return pic; + -+ cma_buf_t * const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, sys->output->buffer_size); -+ if (cb == NULL) { -+ char dbuf0[5]; -+ msg_Err(p_filter, "Failed to alloc CMA buf: fmt=%s, size=%d", -+ str_fourcc(dbuf0, out_pic->format.i_chroma), -+ sys->output->buffer_size); -+ goto fail; -+ } -+ const unsigned int vc_h = cma_buf_vc_handle(cb); // Cannot coerce without going via variable -+ out_buf->data = (uint8_t *)vc_h; -+ out_buf->alloc_size = sys->output->buffer_size; ++fail2: ++ picture_Release(pic); ++fail1: ++ // Recycle rather than release to avoid buffer starvation if NewPic fails ++ hw_mmal_port_pool_ref_recycle(dec_sys->ppr, buf); ++ return NULL; ++} + -+ if ((rv = cma_buf_pic_attach(cb, out_pic)) != VLC_SUCCESS) -+ { -+ char dbuf0[5]; -+ msg_Err(p_filter, "Failed to attach CMA to pic: fmt=%s err=%d", -+ str_fourcc(dbuf0, out_pic->format.i_chroma), -+ rv); -+ cma_buf_unref(cb); -+ goto fail; -+ } -+ } -+ else { -+ out_buf->data = out_pic->p[0].p_pixels; -+ out_buf->alloc_size = out_pic->p[0].i_pitch * out_pic->p[0].i_lines; -+ //**** stride ???? -+ } ++static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++{ ++ decoder_t *dec = (decoder_t *)port->userdata; ++ MMAL_STATUS_T status; + +#if TRACE_ALL -+ msg_Dbg(p_filter, "Out buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d, pts=%lld", -+ p_pic, out_buf->data, out_buf->user_data, out_buf->flags, -+ out_buf->length, out_buf->alloc_size, (long long)out_buf->pts); ++ msg_Dbg(dec, "<<< %s: cmd=%d, data=%p", __func__, buffer->cmd, buffer->data); +#endif + -+ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Send buffer to output failed"); -+ goto fail; -+ } -+ out_buf = NULL; -+ } -+ } ++ if (buffer->cmd == MMAL_EVENT_ERROR) { ++ status = *(uint32_t *)buffer->data; ++ dec->p_sys->err_stream = status; ++ msg_Err(dec, "MMAL error %"PRIx32" \"%s\"", status, ++ mmal_status_to_string(status)); + } + +- dec->pf_decode = decode; +- dec->pf_flush = flush_decoder; ++ mmal_buffer_header_release(buffer); ++} + +- vlc_sem_init(&sys->sem, 0); ++static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++{ ++ block_t * const block = (block_t *)buffer->user_data; + +-out: +- if (ret != VLC_SUCCESS) +- CloseDecoder(dec); ++ (void)port; // Unused + +- return ret; ++#if TRACE_ALL ++ msg_Dbg((decoder_t *)port->userdata, "<<< %s: cmd=%d, data=%p, len=%d/%d, pts=%lld", __func__, ++ buffer->cmd, buffer->data, buffer->length, buffer->alloc_size, (long long)buffer->pts); ++#endif + ++ mmal_buffer_header_reset(buffer); ++ mmal_buffer_header_release(buffer); + -+ // Stuff into input -+ // We assume the BH is already set up with values reflecting pic date etc. -+ stash->pts = p_pic->date; ++ if (block != NULL) ++ block_Release(block); + } + +-static void CloseDecoder(decoder_t *dec) ++static void decoder_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) + { +- decoder_sys_t *sys = dec->p_sys; +- MMAL_BUFFER_HEADER_T *buffer; ++ decoder_t * const dec = (decoder_t *)port->userdata; + +- if (!sys) ++ if (buffer->cmd == 0 && buffer->length != 0) + { -+ MMAL_BUFFER_HEADER_T *const pic_buf = sys->needs_copy_in ? -+ hw_mmal_pic_buf_copied(p_pic, sys->in_pool, sys->input, sys->cma_in_pool) : -+ hw_mmal_pic_buf_replicated(p_pic, sys->in_pool); -+ -+ // Whether or not we extracted the pic_buf we are done with the picture -+ picture_Release(p_pic); -+ p_pic = NULL; -+ -+ if (pic_buf == NULL) { -+ msg_Err(p_filter, "Pic has no attached buffer"); -+ goto fail; -+ } -+ -+ pic_buf->pts = frame_seq; -+ +#if TRACE_ALL -+ msg_Dbg(p_filter, "In buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d/%d, pts=%lld", -+ p_pic, pic_buf->data, pic_buf->user_data, pic_buf->flags, -+ pic_buf->length, pic_buf->alloc_size, sys->input->buffer_size, (long long)pic_buf->pts); ++ msg_Dbg(dec, "<<< %s: cmd=%d, data=%p, len=%d/%d, pts=%lld", __func__, ++ buffer->cmd, buffer->data, buffer->length, buffer->alloc_size, (long long)buffer->pts); +#endif + -+ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Send buffer to input failed"); -+ mmal_buffer_header_release(pic_buf); -+ goto fail; -+ } -+ } -+ -+ // We have a 1 pic latency for everything except the 1st pic which we -+ // wait for. -+ // This means we get a single static pic out -+ if (sys->pic_n++ == 1) { ++ picture_t *pic = alloc_opaque_pic(dec, buffer); +#if TRACE_ALL -+ msg_Dbg(p_filter, ">>> %s: Pic1=%p", __func__, ret_pics); ++ msg_Dbg(dec, "flags=%#x, video flags=%#x", buffer->flags, buffer->type->video.flags); +#endif -+ return ret_pics; ++ if (pic == NULL) ++ msg_Err(dec, "Failed to allocate new picture"); ++ else ++ decoder_QueueVideo(dec, pic); ++ // Buffer released or attached to pic - do not release again + return; + } + +- if (sys->component && sys->component->control->is_enabled) +- mmal_port_disable(sys->component->control); ++ if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) ++ { ++ decoder_sys_t * const sys = dec->p_sys; ++ MMAL_EVENT_FORMAT_CHANGED_T * const fmt = mmal_event_format_changed_get(buffer); ++ MMAL_ES_FORMAT_T * const format = mmal_format_alloc(); + +- if (sys->input && sys->input->is_enabled) +- mmal_port_disable(sys->input); ++ if (format == NULL) ++ msg_Err(dec, "Failed to allocate new format"); ++ else ++ { ++ mmal_format_full_copy(format, fmt->format); ++ format->encoding = MMAL_ENCODING_OPAQUE; + +- if (sys->output && sys->output->is_enabled) +- mmal_port_disable(sys->output); ++ // If no PAR in the stream - see if we've got one from the demux ++ if (format->es->video.par.den <= 0 || format->es->video.par.num <= 0) { ++ unsigned int n = dec->fmt_in.video.i_sar_num; ++ unsigned int d = dec->fmt_in.video.i_sar_den; + -+ ret_pics = conv_get_out_pics(sys); -+ -+ if (sys->err_stream != MMAL_SUCCESS) -+ goto stream_fail; -+ -+ conv_stash_fixup(p_filter, sys, ret_pics); ++ if (n == 0 || d == 0) { ++ // Guesswork required ++ const unsigned int w = format->es->video.width; ++ const unsigned int h = format->es->video.height; ++ if ((w == 704 || w == 720) && (h == 480 || h == 576)) { ++ // Very likely SD 4:3 ++ n = w * 3; ++ d = h * 4; ++ } ++ else ++ { ++ // Otherwise guess SAR 1:1 ++ n = 1; ++ d = 1; ++ } ++ } + +- if (sys->component && sys->component->is_enabled) +- mmal_component_disable(sys->component); ++ format->es->video.par = rationalize_sar(n, d); ++ } + +- if (sys->input_pool) +- mmal_pool_destroy(sys->input_pool); ++ if (sys->output_format != NULL) ++ mmal_format_free(sys->output_format); + +- if (sys->output_format) +- mmal_format_free(sys->output_format); ++ sys->output_format = format; ++ } ++ } ++ else if (buffer->cmd != 0) { ++ char buf0[5]; ++ msg_Warn(dec, "Unexpected output cb event: %s", str_fourcc(buf0, buffer->cmd)); ++ } + +- if (sys->output_pool) +- mmal_pool_destroy(sys->output_pool); ++ // If we get here then we were flushing (cmd == 0 && len == 0) or ++ // that was an EVENT - in either case we want to release the buffer ++ // back to its pool rather than recycle it. ++ mmal_buffer_header_reset(buffer); ++ buffer->user_data = NULL; ++ mmal_buffer_header_release(buffer); ++} + +- if (sys->component) +- mmal_component_release(sys->component); + +- vlc_sem_destroy(&sys->sem); +- free(sys); + +- bcm_host_deinit(); ++static void fill_output_port(decoder_t *dec) ++{ ++ decoder_sys_t *sys = dec->p_sys; + ++ if (decoder_UpdateVideoFormat(dec) != 0) ++ { ++ // If we have a new format don't bother stuffing the buffer ++ // We should get a reset RSN +#if TRACE_ALL -+ msg_Dbg(p_filter, ">>> %s: pic=%p", __func__, ret_pics); ++ msg_Dbg(dec, "%s: Updated", __func__); +#endif + -+ return ret_pics; ++ return; ++ } + -+stream_fail: -+ msg_Err(p_filter, "MMAL error reported by callback"); -+fail: ++ hw_mmal_port_pool_ref_fill(sys->ppr); ++ return; + } + + static int change_output_format(decoder_t *dec) + { + MMAL_PARAMETER_VIDEO_INTERLACE_TYPE_T interlace_type; +- decoder_sys_t *sys = dec->p_sys; ++ decoder_sys_t * const sys = dec->p_sys; + MMAL_STATUS_T status; +- int pool_size; + int ret = 0; + +#if TRACE_ALL -+ msg_Err(p_filter, ">>> %s: FAIL", __func__); ++ msg_Dbg(dec, "%s: <<<", __func__); +#endif -+ if (ret_pics != NULL) -+ picture_Release(ret_pics); -+ if (out_buf != NULL) -+ mmal_buffer_header_release(out_buf); -+ if (p_pic != NULL) -+ picture_Release(p_pic); -+ conv_flush(p_filter); -+ return NULL; -+} -+ -+static void CloseConverter(vlc_object_t * obj) -+{ -+ filter_t * const p_filter = (filter_t *)obj; -+ filter_sys_t * const sys = p_filter->p_sys; -+ unsigned int i; + + if (atomic_load(&sys->started)) { + mmal_format_full_copy(sys->output->format, sys->output_format); + status = mmal_port_format_commit(sys->output); +@@ -300,7 +481,9 @@ static int change_output_format(decoder_ + } + + port_reset: +#if TRACE_ALL -+ msg_Dbg(obj, "<<< %s", __func__); + msg_Dbg(dec, "%s: Do full port reset", __func__); +#endif + status = mmal_port_disable(sys->output); + if (status != MMAL_SUCCESS) { + msg_Err(dec, "Failed to disable output port (status=%"PRIx32" %s)", +@@ -310,6 +493,7 @@ port_reset: + } + + mmal_format_full_copy(sys->output->format, sys->output_format); + -+ if (sys == NULL) -+ return; -+ -+ // Disables input & output ports -+ conv_flush(p_filter); -+ -+ cma_buf_pool_deletez(&sys->cma_in_pool); -+ cma_buf_pool_deletez(&sys->cma_out_pool); -+ -+ if (sys->component && sys->component->control->is_enabled) -+ mmal_port_disable(sys->component->control); -+ -+ if (sys->component && sys->component->is_enabled) -+ mmal_component_disable(sys->component); + status = mmal_port_format_commit(sys->output); + if (status != MMAL_SUCCESS) { + msg_Err(dec, "Failed to commit output format (status=%"PRIx32" %s)", +@@ -318,18 +502,10 @@ port_reset: + goto out; + } + +- if (sys->opaque) { +- sys->output->buffer_num = NUM_DECODER_BUFFER_HEADERS; +- pool_size = NUM_DECODER_BUFFER_HEADERS; +- } else { +- sys->output->buffer_num = __MAX(sys->output->buffer_num_recommended, +- MIN_NUM_BUFFERS_IN_TRANSIT); +- pool_size = sys->output->buffer_num; +- } +- ++ sys->output->buffer_num = NUM_DECODER_BUFFER_HEADERS; + sys->output->buffer_size = sys->output->buffer_size_recommended; + +- status = mmal_port_enable(sys->output, output_port_cb); ++ status = mmal_port_enable(sys->output, decoder_output_cb); + if (status != MMAL_SUCCESS) { + msg_Err(dec, "Failed to enable output port (status=%"PRIx32" %s)", + status, mmal_status_to_string(status)); +@@ -338,25 +514,14 @@ port_reset: + } + + if (!atomic_load(&sys->started)) { +- if (!sys->opaque) { +- sys->output_pool = mmal_port_pool_create(sys->output, pool_size, 0); +- msg_Dbg(dec, "Created output pool with %d pictures", sys->output_pool->headers_num); +- } +- + atomic_store(&sys->started, true); + + /* we need one picture from vout for each buffer header on the output + * port */ +- dec->i_extra_picture_buffers = pool_size; +- +- /* remove what VLC core reserves as it is part of the pool_size +- * already */ +- if (dec->fmt_in.i_codec == VLC_CODEC_H264) +- dec->i_extra_picture_buffers -= 19; +- else +- dec->i_extra_picture_buffers -= 3; +- ++ dec->i_extra_picture_buffers = 10; ++#if TRACE_ALL + msg_Dbg(dec, "Request %d extra pictures", dec->i_extra_picture_buffers); ++#endif + } + + apply_fmt: +@@ -366,8 +531,8 @@ apply_fmt: + dec->fmt_out.video.i_y_offset = sys->output->format->es->video.crop.y; + dec->fmt_out.video.i_visible_width = sys->output->format->es->video.crop.width; + dec->fmt_out.video.i_visible_height = sys->output->format->es->video.crop.height; +- dec->fmt_out.video.i_sar_num = sys->output->format->es->video.par.num; +- dec->fmt_out.video.i_sar_den = sys->output->format->es->video.par.den; ++ dec->fmt_out.video.i_sar_num = sys->output_format->es->video.par.num; // SAR can be killed by commit ++ dec->fmt_out.video.i_sar_den = sys->output_format->es->video.par.den; + dec->fmt_out.video.i_frame_rate = sys->output->format->es->video.frame_rate.num; + dec->fmt_out.video.i_frame_rate_base = sys->output->format->es->video.frame_rate.den; + +@@ -382,12 +547,19 @@ apply_fmt: + sys->b_progressive = (interlace_type.eMode == MMAL_InterlaceProgressive); + sys->b_top_field_first = sys->b_progressive ? true : + (interlace_type.eMode == MMAL_InterlaceFieldsInterleavedUpperFirst); ++#if TRACE_ALL + msg_Dbg(dec, "Detected %s%s video (%d)", + sys->b_progressive ? "progressive" : "interlaced", + sys->b_progressive ? "" : (sys->b_top_field_first ? " tff" : " bff"), + interlace_type.eMode); ++#endif + } + ++ // Tell the rest of the world we have changed format ++ vlc_mutex_lock(&sys->pic_lock); ++ ret = decoder_UpdateVideoFormat(dec); ++ vlc_mutex_unlock(&sys->pic_lock); + -+ if (sys->resizer_type == FILTER_RESIZER_HVS) -+ { -+ for (i = 0; i != SUBS_MAX; ++i) { -+ hw_mmal_subpic_close(VLC_OBJECT(p_filter), sys->subs + i); + out: + mmal_format_free(sys->output_format); + sys->output_format = NULL; +@@ -395,144 +567,85 @@ out: + return ret; + } + +-static int send_output_buffer(decoder_t *dec) ++static MMAL_STATUS_T ++set_extradata_and_commit(decoder_t * const dec, decoder_sys_t * const sys) + { +- decoder_sys_t *sys = dec->p_sys; +- MMAL_BUFFER_HEADER_T *buffer; +- picture_sys_t *p_sys; +- picture_t *picture = NULL; + MMAL_STATUS_T status; +- unsigned buffer_size = 0; +- int ret = 0; +- +- if (!sys->output->is_enabled) +- return VLC_EGENERIC; +- +- /* If local output pool is allocated, use it - this is only the case for +- * non-opaque modes */ +- if (sys->output_pool) { +- buffer = mmal_queue_get(sys->output_pool->queue); +- if (!buffer) { +- msg_Warn(dec, "Failed to get new buffer"); +- return VLC_EGENERIC; +- } +- } +- +- if (!decoder_UpdateVideoFormat(dec)) +- picture = decoder_NewPicture(dec); +- if (!picture) { +- msg_Warn(dec, "Failed to get new picture"); +- ret = -1; +- goto err; +- } + +- p_sys = picture->p_sys; +- for (int i = 0; i < picture->i_planes; i++) +- buffer_size += picture->p[i].i_lines * picture->p[i].i_pitch; +- +- if (sys->output_pool) { +- mmal_buffer_header_reset(buffer); +- buffer->alloc_size = sys->output->buffer_size; +- if (buffer_size < sys->output->buffer_size) { +- msg_Err(dec, "Retrieved picture with too small data block (%d < %d)", +- buffer_size, sys->output->buffer_size); +- ret = VLC_EGENERIC; +- goto err; +- } +- +- if (!sys->opaque) +- buffer->data = picture->p[0].p_pixels; +- } else { +- buffer = p_sys->buffer; +- if (!buffer) { +- msg_Warn(dec, "Picture has no buffer attached"); +- picture_Release(picture); +- return VLC_EGENERIC; +- } +- buffer->data = p_sys->buffer->data; +- } +- buffer->user_data = picture; +- buffer->cmd = 0; +- +- status = mmal_port_send_buffer(sys->output, buffer); ++ status = mmal_port_format_commit(sys->input); + if (status != MMAL_SUCCESS) { +- msg_Err(dec, "Failed to send buffer to output port (status=%"PRIx32" %s)", +- status, mmal_status_to_string(status)); +- ret = -1; +- goto err; +- } +- atomic_fetch_add(&sys->output_in_transit, 1); +- +- return ret; +- +-err: +- if (picture) +- picture_Release(picture); +- if (sys->output_pool && buffer) { +- buffer->data = NULL; +- mmal_buffer_header_release(buffer); ++ msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)", ++ sys->input->name, status, mmal_status_to_string(status)); + } +- return ret; ++ return status; + } + +-static void fill_output_port(decoder_t *dec) ++static MMAL_STATUS_T decoder_send_extradata(decoder_t * const dec, decoder_sys_t *const sys) + { +- decoder_sys_t *sys = dec->p_sys; ++ if (dec->fmt_in.i_codec == VLC_CODEC_H264 && ++ dec->fmt_in.i_extra > 0) ++ { ++ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_wait(sys->input_pool->queue); ++ MMAL_STATUS_T status; ++ ++ mmal_buffer_header_reset(buf); ++ buf->cmd = 0; ++ buf->user_data = NULL; ++ buf->alloc_size = sys->input->buffer_size; ++ buf->length = dec->fmt_in.i_extra; ++ buf->data = dec->fmt_in.p_extra; ++ buf->flags = MMAL_BUFFER_HEADER_FLAG_CONFIG; + +- unsigned max_buffers_in_transit = 0; +- int buffers_available = 0; +- int buffers_to_send = 0; +- int i; +- +- if (sys->output_pool) { +- max_buffers_in_transit = __MAX(sys->output_pool->headers_num, +- MIN_NUM_BUFFERS_IN_TRANSIT); +- buffers_available = mmal_queue_length(sys->output_pool->queue); +- } else { +- max_buffers_in_transit = NUM_DECODER_BUFFER_HEADERS; +- buffers_available = NUM_DECODER_BUFFER_HEADERS - atomic_load(&sys->output_in_transit); ++ status = mmal_port_send_buffer(sys->input, buf); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(dec, "Failed to send extradata buffer to input port (status=%"PRIx32" %s)", ++ status, mmal_status_to_string(status)); ++ return status; + } + } +- buffers_to_send = max_buffers_in_transit - atomic_load(&sys->output_in_transit); +- +- if (buffers_to_send > buffers_available) +- buffers_to_send = buffers_available; + +-#ifndef NDEBUG +- msg_Dbg(dec, "Send %d buffers to output port (available: %d, " +- "in_transit: %d, buffer_num: %d)", +- buffers_to_send, buffers_available, +- atomic_load(&sys->output_in_transit), +- sys->output->buffer_num); +-#endif +- for (i = 0; i < buffers_to_send; ++i) +- if (send_output_buffer(dec) < 0) +- break; ++ return MMAL_SUCCESS; + } + + static void flush_decoder(decoder_t *dec) + { +- decoder_sys_t *sys = dec->p_sys; +- MMAL_BUFFER_HEADER_T *buffer; +- MMAL_STATUS_T status; ++ decoder_sys_t *const sys = dec->p_sys; ++ ++#if TRACE_ALL ++ msg_Dbg(dec, "%s: <<<", __func__); ++#endif + +- msg_Dbg(dec, "Flushing decoder ports..."); +- mmal_port_flush(sys->output); +- mmal_port_flush(sys->input); +- +- while (atomic_load(&sys->output_in_transit) || +- atomic_load(&sys->input_in_transit)) +- vlc_sem_wait(&sys->sem); ++ if (!sys->b_flushed) { ++ mmal_port_disable(sys->input); ++ mmal_port_disable(sys->output); ++ // We can leave the input disabled, but we want the output enabled ++ // in order to sink any buffers returning from other modules ++ mmal_port_enable(sys->output, decoder_output_cb); ++ sys->b_flushed = true; + } ++#if TRACE_ALL ++ msg_Dbg(dec, "%s: >>>", __func__); ++#endif + } + + static int decode(decoder_t *dec, block_t *block) + { + decoder_sys_t *sys = dec->p_sys; + MMAL_BUFFER_HEADER_T *buffer; +- bool need_flush = false; + uint32_t len; +- uint32_t flags = 0; ++ uint32_t flags = MMAL_BUFFER_HEADER_FLAG_FRAME_START; + MMAL_STATUS_T status; + ++#if TRACE_ALL ++ msg_Dbg(dec, "<<< %s: %lld/%lld", __func__, block == NULL ? -1LL : block->i_dts, block == NULL ? -1LL : block->i_pts); ++#endif + -+ if (sys->out_pool) -+ { -+ if (sys->is_sliced) -+ mmal_port_pool_destroy(sys->output, sys->out_pool); -+ else -+ mmal_pool_destroy(sys->out_pool); ++ if (sys->err_stream != MMAL_SUCCESS) { ++ msg_Err(dec, "MMAL error reported by ctrl"); ++ flush_decoder(dec); ++ return VLCDEC_ECRITICAL; /// I think they are all fatal + } + -+ if (sys->in_pool != NULL) -+ mmal_pool_destroy(sys->in_pool); + /* + * Configure output port if necessary + */ +@@ -541,18 +654,50 @@ static int decode(decoder_t *dec, block_ + msg_Err(dec, "Failed to change output port format"); + } + +- if (!block) +- goto out; ++ if (block == NULL) ++ return VLCDEC_SUCCESS; + + /* + * Check whether full flush is required + */ +- if (block && block->i_flags & BLOCK_FLAG_DISCONTINUITY) { ++ if (block->i_flags & BLOCK_FLAG_DISCONTINUITY) { ++#if TRACE_ALL ++ msg_Dbg(dec, "%s: >>> Discontinuity", __func__); ++#endif + flush_decoder(dec); ++ } + -+ if (sys->component) -+ mmal_component_release(sys->component); ++ if (block->i_buffer == 0) ++ { + block_Release(block); + return VLCDEC_SUCCESS; + } + ++ // Reenable stuff if the last thing we did was flush ++ if (!sys->output->is_enabled && ++ (status = mmal_port_enable(sys->output, decoder_output_cb)) != MMAL_SUCCESS) ++ { ++ msg_Err(dec, "Output port enable failed"); ++ goto fail; ++ } + -+ cma_vcsm_exit(sys->vcsm_init_type); ++ if (!sys->input->is_enabled) ++ { ++ if ((status = set_extradata_and_commit(dec, sys)) != MMAL_SUCCESS) ++ goto fail; + -+ vlc_sem_destroy(&sys->sem); -+ vlc_mutex_destroy(&sys->lock); ++ if ((status = mmal_port_enable(sys->input, input_port_cb)) != MMAL_SUCCESS) ++ { ++ msg_Err(dec, "Input port enable failed"); ++ goto fail; ++ } + -+ p_filter->p_sys = NULL; -+ free(sys); -+} ++ if ((status = decoder_send_extradata(dec, sys)) != MMAL_SUCCESS) ++ goto fail; ++ } + ++ // *** We cannot get a picture to put the result in 'till we have ++ // reported the size & the output stages have been set up + if (atomic_load(&sys->started)) + fill_output_port(dec); + +@@ -563,18 +708,21 @@ static int decode(decoder_t *dec, block_ + if (block->i_flags & BLOCK_FLAG_CORRUPTED) + flags |= MMAL_BUFFER_HEADER_FLAG_CORRUPTED; + +- while (block && block->i_buffer > 0) { +- buffer = mmal_queue_timedwait(sys->input_pool->queue, 100); ++ while (block != NULL) ++ { ++ buffer = mmal_queue_wait(sys->input_pool->queue); + if (!buffer) { + msg_Err(dec, "Failed to retrieve buffer header for input data"); +- need_flush = true; +- break; ++ goto fail; + } + -+static inline MMAL_FOURCC_T filter_enc_in(const video_format_t * const fmt) -+{ -+ if (hw_mmal_chroma_is_mmal(fmt->i_chroma)) -+ return vlc_to_mmal_video_fourcc(fmt); + mmal_buffer_header_reset(buffer); + buffer->cmd = 0; +- buffer->pts = block->i_pts != 0 ? block->i_pts : block->i_dts; ++ buffer->pts = block->i_pts != VLC_TICK_INVALID ? block->i_pts : ++ block->i_dts != VLC_TICK_INVALID ? block->i_dts : MMAL_TIME_UNKNOWN; + buffer->dts = block->i_dts; + buffer->alloc_size = sys->input->buffer_size; ++ buffer->user_data = NULL; + + len = block->i_buffer; + if (len > buffer->alloc_size) +@@ -585,94 +733,1835 @@ static int decode(decoder_t *dec, block_ + block->i_buffer -= len; + buffer->length = len; + if (block->i_buffer == 0) { ++ flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END; ++ if (block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE) { ++ msg_Dbg(dec, "EOS sent"); ++ flags |= MMAL_BUFFER_HEADER_FLAG_EOS; ++ } + buffer->user_data = block; + block = NULL; + } + buffer->flags = flags; + ++#if TRACE_ALL ++ msg_Dbg(dec, "%s: -- Send buffer: cmd=%d, data=%p, size=%d, len=%d, offset=%d, flags=%#x, pts=%lld, dts=%lld", __func__,\ ++ buffer->cmd, buffer->data, buffer->alloc_size, buffer->length, buffer->offset, ++ buffer->flags, (long long)buffer->pts, (long long)buffer->dts); ++#endif + status = mmal_port_send_buffer(sys->input, buffer); + if (status != MMAL_SUCCESS) { + msg_Err(dec, "Failed to send buffer to input port (status=%"PRIx32" %s)", + status, mmal_status_to_string(status)); +- break; ++ goto fail; + } +- atomic_fetch_add(&sys->input_in_transit, 1); + -+ if (fmt->i_chroma == VLC_CODEC_I420 || -+ fmt->i_chroma == VLC_CODEC_I420_10L) -+ return MMAL_ENCODING_I420; ++ // Reset flushed flag once we have sent a buf ++ sys->b_flushed = false; ++ flags &= ~MMAL_BUFFER_HEADER_FLAG_FRAME_START; + } ++ return VLCDEC_SUCCESS; + +-out: +- if (need_flush) +- flush_decoder(dec); ++fail: ++ flush_decoder(dec); ++ return VLCDEC_ECRITICAL; + +- return VLCDEC_SUCCESS; + } + +-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) + -+ return 0; -+} ++static void CloseDecoder(decoder_t *dec) + { +- decoder_t *dec = (decoder_t *)port->userdata; ++ decoder_sys_t *sys = dec->p_sys; + -+static inline MMAL_FOURCC_T filter_enc_out(const video_format_t * const fmt) -+{ -+ const MMAL_FOURCC_T mmes = vlc_to_mmal_video_fourcc(fmt); -+ // Can only copy out single plane stuff currently - this could be fixed! -+ return hw_mmal_chroma_is_mmal(fmt->i_chroma) || mmes != MMAL_ENCODING_I420 ? mmes : 0; -+} ++#if TRACE_ALL ++ msg_Dbg(dec, "%s: <<<", __func__); ++#endif + ++ if (!sys) ++ return; + -+static int OpenConverter(vlc_object_t * obj) -+{ -+ filter_t * const p_filter = (filter_t *)obj; -+ int ret = VLC_EGENERIC; -+ filter_sys_t *sys; -+ MMAL_STATUS_T status; -+ MMAL_FOURCC_T enc_out = filter_enc_out(&p_filter->fmt_out.video); -+ const MMAL_FOURCC_T enc_in = filter_enc_in(&p_filter->fmt_in.video); -+ bool use_resizer; -+ bool use_isp; -+ int gpu_mem; ++ if (sys->component != NULL) { ++ if (sys->input->is_enabled) ++ mmal_port_disable(sys->input); + -+ // At least in principle we should deal with any mmal format as input -+ if (enc_in == 0 || enc_out == 0) -+ return VLC_EGENERIC; ++ if (sys->output->is_enabled) ++ mmal_port_disable(sys->output); + -+ // Can't transform -+ if (p_filter->fmt_in.video.orientation != p_filter->fmt_out.video.orientation) -+ return VLC_EGENERIC; ++ if (sys->component->control->is_enabled) ++ mmal_port_disable(sys->component->control); + -+ use_resizer = var_InheritBool(p_filter, MMAL_RESIZE_NAME); -+ use_isp = var_InheritBool(p_filter, MMAL_ISP_NAME); ++ if (sys->component->is_enabled) ++ mmal_component_disable(sys->component); + -+retry: -+ // ** Make more generic by checking supported encs -+ // -+ // Must use ISP - HVS can't do this, nor can resizer -+ if (enc_in == MMAL_ENCODING_YUVUV64_10) { -+ // If resizer selected then just give up -+ if (use_resizer) -+ return VLC_EGENERIC; -+ // otherwise downgrade HVS to ISP -+ use_isp = true; -+ } -+ // HVS can't do I420 -+ if (enc_out == MMAL_ENCODING_I420) { -+ use_isp = true; -+ } -+ // Only HVS can deal with SAND30 -+ if (enc_in == MMAL_ENCODING_YUV10_COL) { -+ if (use_isp || use_resizer) -+ return VLC_EGENERIC; -+ } - -- sys->output_format = format; - -- mmal_buffer_header_release(buffer); -+ if (use_resizer) { -+ // use resizer overrides use_isp -+ use_isp = false; ++ mmal_component_release(sys->component); + } + -+ // Check we have a sliced version of the fourcc if we want the resizer -+ if (use_resizer && -+ (enc_out = pic_to_slice_mmal_fourcc(enc_out)) == 0) { ++ if (sys->input_pool != NULL) ++ mmal_pool_destroy(sys->input_pool); ++ ++ if (sys->output_format != NULL) ++ mmal_format_free(sys->output_format); ++ ++ hw_mmal_port_pool_ref_release(sys->ppr, false); ++ ++ cma_vcsm_exit(sys->vcsm_init_type); ++ ++ vlc_mutex_destroy(&sys->pic_lock); ++ free(sys); ++} ++ ++static int OpenDecoder(decoder_t *dec) ++{ ++ int ret = VLC_EGENERIC; ++ decoder_sys_t *sys; + MMAL_STATUS_T status; ++ const MMAL_FOURCC_T in_fcc = vlc_to_mmal_es_fourcc(dec->fmt_in.i_codec); ++ static bool is_kms = false; ++ static bool kms_checked = false; ++ ++ if (!kms_checked) { ++ // This is true for Pi3 and earlier ++ // On Pi4 and later it is almost certainly better to be using V4L2 ++ is_kms = !rpi_use_qpu_deinterlace(); ++ kms_checked = true; ++ } ++ if (is_kms && !var_InheritBool(dec, MMAL_DECODE_ENABLE_NAME)) { ++#if TRACE_ALL ++ msg_Dbg(dec, "%s: <<< Disabled: Is KMS", __func__); ++#endif + return VLC_EGENERIC; + } + -+ gpu_mem = hw_mmal_get_gpu_mem(); -+ ++#if TRACE_ALL || 1 + { -+ char dbuf0[5], dbuf1[5], dbuf2[5], dbuf3[5]; -+ msg_Dbg(p_filter, "%s: (%s) %s/%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s/%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d (gpu=%d)", __func__, -+ use_resizer ? "resize" : use_isp ? "isp" : "hvs", -+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), str_fourcc(dbuf2, enc_in), -+ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, -+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, -+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, -+ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den, -+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), str_fourcc(dbuf3, enc_out), -+ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, -+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, -+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height, -+ p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask, -+ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den, -+ gpu_mem); ++ char buf1[5], buf2[5], buf2a[5]; ++ char buf3[5], buf4[5]; ++ MMAL_RATIONAL_T r = rationalize_sar(dec->fmt_in.video.i_sar_num, dec->fmt_in.video.i_sar_den); ++ ++ msg_Dbg(dec, "%s: <<< (%s/%s)[%s] %dx%d %d/%d=%d/%d o:%#x -> (%s/%s) %dx%d %d/%d o:%#x", __func__, ++ str_fourcc(buf1, dec->fmt_in.i_codec), ++ str_fourcc(buf2, dec->fmt_in.video.i_chroma), ++ str_fourcc(buf2a, in_fcc), ++ dec->fmt_in.video.i_width, dec->fmt_in.video.i_height, ++ dec->fmt_in.video.i_sar_num, dec->fmt_in.video.i_sar_den, ++ r.num, r.den, ++ (int)dec->fmt_in.video.orientation, ++ str_fourcc(buf3, dec->fmt_out.i_codec), ++ str_fourcc(buf4, dec->fmt_out.video.i_chroma), ++ dec->fmt_out.video.i_width, dec->fmt_out.video.i_height, ++ dec->fmt_out.video.i_sar_num, dec->fmt_out.video.i_sar_den, ++ (int)dec->fmt_out.video.orientation); + } ++#endif + -+ sys = calloc(1, sizeof(filter_sys_t)); ++ if (!is_enc_supported(&supported_decode_in_enc, in_fcc)) ++ return VLC_EGENERIC; ++ ++ sys = calloc(1, sizeof(decoder_sys_t)); + if (!sys) { + ret = VLC_ENOMEM; + goto fail; + } -+ p_filter->p_sys = sys; ++ dec->p_sys = sys; ++ vlc_mutex_init(&sys->pic_lock); ++ ++ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { ++ msg_Err(dec, "VCSM init failed"); ++ goto fail; ++ } ++ msg_Info(dec, "VCSM init succeeded: %s", cma_vcsm_init_str(sys->vcsm_init_type)); + -+ // Init stuff the we destroy unconditionaly in Close first -+ vlc_mutex_init(&sys->lock); -+ vlc_sem_init(&sys->sem, 0); + sys->err_stream = MMAL_SUCCESS; -+ pic_fifo_init(&sys->ret_pics); -+ pic_fifo_init(&sys->slice.pics); + -+ sys->needs_copy_in = !hw_mmal_chroma_is_mmal(p_filter->fmt_in.video.i_chroma); -+ sys->in_port_cb_fn = conv_input_port_cb; ++ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, &sys->component); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(dec, "Failed to create MMAL component %s (status=%"PRIx32" %s)", ++ MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status)); ++ goto fail; ++ } + -+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { -+ msg_Err(p_filter, "VCSM init failed"); ++ sys->input = sys->component->input[0]; ++ sys->output = sys->component->output[0]; ++ ++ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)dec; ++ sys->input->format->encoding = in_fcc; ++ ++ if (!set_and_test_enc_supported(&supported_decode_in_enc, sys->input, in_fcc)) { ++#if TRACE_ALL ++ char cbuf[5]; ++ msg_Dbg(dec, "Format not supported: %s", str_fourcc(cbuf, in_fcc)); ++#endif + goto fail; + } + -+ if (use_resizer) { -+ sys->resizer_type = FILTER_RESIZER_RESIZER; -+ sys->is_sliced = true; -+ sys->component_name = MMAL_COMPONENT_DEFAULT_RESIZER; -+ sys->out_port_cb_fn = slice_output_port_cb; -+ } -+ else if (use_isp) { -+ sys->resizer_type = FILTER_RESIZER_ISP; -+ sys->is_sliced = false; // Copy directly into filter picture -+ sys->component_name = MMAL_COMPONENT_ISP_RESIZER; -+ sys->out_port_cb_fn = conv_output_port_cb; - } else { -- mmal_buffer_header_release(buffer); -+ sys->resizer_type = FILTER_RESIZER_HVS; -+ sys->is_sliced = false; // Copy directly into filter picture -+ sys->component_name = MMAL_COMPONENT_HVS; -+ sys->out_port_cb_fn = conv_output_port_cb; -+ } -+ sys->is_cma = is_cma_buf_pic_chroma(p_filter->fmt_out.video.i_chroma); -+ -+ status = mmal_component_create(sys->component_name, &sys->component); -+ if (status != MMAL_SUCCESS) { -+ if (!use_isp && !use_resizer) { -+ msg_Warn(p_filter, "Failed to rcreate HVS resizer - retrying with ISP"); -+ CloseConverter(obj); -+ use_isp = true; -+ goto retry; -+ } -+ msg_Err(p_filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)", -+ MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status)); -+ goto fail; - } -+ sys->output = sys->component->output[0]; -+ sys->input = sys->component->input[0]; -+ -+ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter; -+ status = mmal_port_enable(sys->component->control, conv_control_port_cb); ++ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)dec; ++ status = mmal_port_enable(sys->component->control, control_port_cb); + if (status != MMAL_SUCCESS) { -+ msg_Err(p_filter, "Failed to enable control port %s (status=%"PRIx32" %s)", ++ msg_Err(dec, "Failed to enable control port %s (status=%"PRIx32" %s)", + sys->component->control->name, status, mmal_status_to_string(status)); + goto fail; + } + -+ if (sys->needs_copy_in && -+ (sys->cma_in_pool = cma_buf_pool_new(2, 2, true, "conv-copy-in")) == NULL) -+ { -+ msg_Err(p_filter, "Failed to allocate input CMA pool"); ++ if ((status = set_extradata_and_commit(dec, sys)) != MMAL_SUCCESS) + goto fail; -+ } -+ -+ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter; -+ sys->input->format->type = MMAL_ES_TYPE_VIDEO; -+ sys->input->format->encoding = enc_in; -+ sys->input->format->encoding_variant = MMAL_ENCODING_I420; -+ hw_mmal_vlc_fmt_to_mmal_fmt(sys->input->format, &p_filter->fmt_in.video); -+ port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, 1); + -+ mmal_log_dump_format(sys->input->format); ++ sys->input->buffer_size = sys->input->buffer_size_recommended; ++ sys->input->buffer_num = sys->input->buffer_num_recommended; + -+ status = mmal_port_format_commit(sys->input); ++ status = mmal_port_enable(sys->input, input_port_cb); + if (status != MMAL_SUCCESS) { -+ msg_Err(p_filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)", ++ msg_Err(dec, "Failed to enable input port %s (status=%"PRIx32" %s)", + sys->input->name, status, mmal_status_to_string(status)); + goto fail; + } -+ sys->input->buffer_size = sys->input->buffer_size_recommended; -+ sys->input->buffer_num = NUM_DECODER_BUFFER_HEADERS; + -+ if ((status = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS) -+ goto fail; ++ // Set vanishingly unlikely shape (or at least crop) ++ // to ensure that we get a resolution changed event ++ // Small wxh are rejected (128x128 is rejected) so pick a ++ // plausible size. ++ // Crop doesn't seem to be checked for being constrained by wxh ++ // so we could place it outside the pic to be sure that it is ++ // never matched but stick with something legal in case it is ever ++ // actually checked ++ sys->output->format->es->video.height = 256; ++ sys->output->format->es->video.width = 256; ++ sys->output->format->es->video.crop.height = 4; ++ sys->output->format->es->video.crop.width = 2; ++ sys->output->format->es->video.crop.x = 66; ++ sys->output->format->es->video.crop.y = 88; + -+ port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, sys->is_sliced || sys->is_cma); ++ if ((status = hw_mmal_opaque_output(VLC_OBJECT(dec), &sys->ppr, ++ sys->output, NUM_EXTRA_BUFFERS, decoder_output_cb)) != MMAL_SUCCESS) ++ goto fail; + + status = mmal_component_enable(sys->component); + if (status != MMAL_SUCCESS) { -+ msg_Err(p_filter, "Failed to enable component %s (status=%"PRIx32" %s)", ++ msg_Err(dec, "Failed to enable component %s (status=%"PRIx32" %s)", + sys->component->name, status, mmal_status_to_string(status)); + goto fail; + } + -+ if ((sys->in_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) ++ if ((sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) + { -+ msg_Err(p_filter, "Failed to create input pool"); ++ msg_Err(dec, "Failed to create input pool"); + goto fail; + } + -+ if (sys->resizer_type == FILTER_RESIZER_HVS) -+ { -+ unsigned int i; -+ for (i = 0; i != SUBS_MAX; ++i) { -+ if (hw_mmal_subpic_open(VLC_OBJECT(p_filter), sys->subs + i, sys->component->input[i + 1], -1, i + 1) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Failed to open subpic %d", i); -+ goto fail; -+ } -+ } -+ } ++ sys->b_flushed = true; + -+ p_filter->pf_video_filter = conv_filter; -+ p_filter->pf_flush = conv_flush; -+ // video_drain NIF in filter structure ++ if ((status = decoder_send_extradata(dec, sys)) != MMAL_SUCCESS) ++ goto fail; + -+#if TRACE_ALL -+ msg_Dbg(p_filter, ">>> %s: ok", __func__); -+#endif ++ // Given no better ideas at this point copy input format to output ++ // This also copies container stuff (such as orientation) that we do not ++ // decode from the ES but may be important to display ++ video_format_Copy(&dec->fmt_out.video, &dec->fmt_in.video); ++ dec->fmt_out.i_codec = VLC_CODEC_MMAL_OPAQUE; ++ dec->fmt_out.video.i_chroma = VLC_CODEC_MMAL_OPAQUE; + -+ return VLC_SUCCESS; + -+fail: -+ CloseConverter(obj); ++ dec->pf_decode = decode; ++ dec->pf_flush = flush_decoder; + -+ if (!use_resizer && status == MMAL_ENOMEM) { -+ use_resizer = true; -+ msg_Warn(p_filter, "Lack of memory to use HVS/ISP: trying resizer"); -+ goto retry; -+ } ++#if TRACE_ALL ++ msg_Dbg(dec, ">>> %s: ok", __func__); ++#endif ++ return 0; + ++fail: ++ CloseDecoder(dec); +#if TRACE_ALL -+ msg_Dbg(p_filter, ">>> %s: FAIL: %d", __func__, ret); ++msg_Dbg(dec, ">>> %s: FAIL: ret=%d", __func__, ret); +#endif + return ret; +} + -+#if OPT_TO_FROM_ZC -+//---------------------------------------------------------------------------- -+// -+// Simple copy in to ZC ++// ---------------------------- + -+typedef struct to_zc_sys_s { -+ vcsm_init_type_t vcsm_init_type; -+ cma_buf_pool_t * cma_out_pool; -+} to_zc_sys_t; ++#define CONV_MAX_LATENCY 1 // In frames + ++typedef struct pic_fifo_s { ++ picture_t * head; ++ picture_t * tail; ++} pic_fifo_t; + -+static size_t buf_alloc_size(const vlc_fourcc_t i_chroma, const unsigned int width, const unsigned int height) ++static inline picture_t * pic_fifo_get(pic_fifo_t * const pf) +{ -+ const unsigned int pels = width * height; -+ -+ switch (i_chroma) -+ { -+ case VLC_CODEC_MMAL_ZC_RGB32: -+ return pels * 4; -+ case VLC_CODEC_MMAL_ZC_I420: -+ return pels * 3 / 2; -+ default: -+ break; ++ picture_t * const pic = pf->head;; ++ if (pic != NULL) { ++ pf->head = pic->p_next; ++ pic->p_next = NULL; + } -+ return 0; ++ return pic; +} + ++static inline picture_t * pic_fifo_get_all(pic_fifo_t * const pf) ++{ ++ picture_t * const pic = pf->head;; ++ pf->head = NULL; ++ return pic; ++} + -+static picture_t * -+to_zc_filter(filter_t *p_filter, picture_t *in_pic) ++static inline void pic_fifo_release_all(pic_fifo_t * const pf) +{ -+ to_zc_sys_t * const sys = (to_zc_sys_t *)p_filter->p_sys; -+#if TRACE_ALL -+ msg_Dbg(p_filter, "<<< %s", __func__); -+#endif ++ picture_t * pic; ++ while ((pic = pic_fifo_get(pf)) != NULL) { ++ picture_Release(pic); ++ } ++} + -+ assert(p_filter->fmt_out.video.i_chroma == VLC_CODEC_MMAL_ZC_I420); ++static inline void pic_fifo_init(pic_fifo_t * const pf) ++{ ++ pf->head = NULL; ++ pf->tail = NULL; // Not strictly needed ++} + -+ picture_t * const out_pic = filter_NewPicture(p_filter); -+ if (out_pic == NULL) -+ goto fail0; ++static inline void pic_fifo_put(pic_fifo_t * const pf, picture_t * pic) ++{ ++ pic->p_next = NULL; ++ if (pf->head == NULL) ++ pf->head = pic; ++ else ++ pf->tail->p_next = pic; ++ pf->tail = pic; ++} + -+ MMAL_ES_SPECIFIC_FORMAT_T mm_vfmt = {.video={0}}; -+ MMAL_ES_FORMAT_T mm_esfmt = { -+ .encoding = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video), -+ .es = &mm_vfmt}; ++#define SUBS_MAX 3 + -+ hw_mmal_vlc_fmt_to_mmal_fmt(&mm_esfmt, &p_filter->fmt_out.video); ++typedef enum filter_resizer_e { ++ FILTER_RESIZER_RESIZER, ++ FILTER_RESIZER_ISP, ++ FILTER_RESIZER_HVS ++} filter_resizer_t; + -+ const size_t buf_alloc = buf_alloc_size(p_filter->fmt_out.video.i_chroma, -+ mm_vfmt.video.width, mm_vfmt.video.height); -+ if (buf_alloc == 0) -+ goto fail1; -+ cma_buf_t *const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, buf_alloc); -+ if (cb == NULL) -+ goto fail1; ++typedef struct conv_frame_stash_s ++{ ++ mtime_t pts; ++ MMAL_BUFFER_HEADER_T * sub_bufs[SUBS_MAX]; ++} conv_frame_stash_t; + -+ if (cma_buf_pic_attach(cb, out_pic) != VLC_SUCCESS) -+ goto fail2; -+ cma_pic_set_data(out_pic, &mm_esfmt, NULL); ++typedef struct filter_sys_t { ++ filter_resizer_t resizer_type; ++ MMAL_COMPONENT_T *component; ++ MMAL_PORT_T *input; ++ MMAL_PORT_T *output; ++ MMAL_POOL_T *out_pool; // Free output buffers ++ MMAL_POOL_T *in_pool; // Input pool to get BH for replication + -+ hw_mmal_copy_pic_to_buf(cma_buf_addr(cb), NULL, &mm_esfmt, in_pic); ++ cma_buf_pool_t * cma_in_pool; ++ cma_buf_pool_t * cma_out_pool; + -+ // Copy pic properties -+ out_pic->date = in_pic->date; -+ out_pic->b_force = in_pic->b_force; -+ out_pic->b_progressive = in_pic->b_progressive; -+ out_pic->b_top_field_first = in_pic->b_top_field_first; -+ out_pic->i_nb_fields = in_pic->i_nb_fields; ++ subpic_reg_stash_t subs[SUBS_MAX]; + -+ picture_Release(in_pic); ++ pic_fifo_t ret_pics; + -+ return out_pic; ++ unsigned int pic_n; ++ vlc_sem_t sem; ++ vlc_mutex_t lock; + -+fail2: -+ cma_buf_unref(cb); -+fail1: -+ picture_Release(out_pic); -+fail0: -+ picture_Release(in_pic); -+ return NULL; -+} ++ MMAL_STATUS_T err_stream; + -+static void to_zc_flush(filter_t * p_filter) -+{ -+ VLC_UNUSED(p_filter); - } ++ bool needs_copy_in; ++ bool is_cma; ++ bool is_sliced; ++ bool out_fmt_set; ++ const char * component_name; ++ MMAL_PORT_BH_CB_T in_port_cb_fn; ++ MMAL_PORT_BH_CB_T out_port_cb_fn; + -+static void CloseConverterToZc(vlc_object_t * obj) -+{ -+ filter_t * const p_filter = (filter_t *)obj; -+ to_zc_sys_t * const sys = (to_zc_sys_t *)p_filter->p_sys; ++ uint64_t frame_seq; ++ conv_frame_stash_t stash[16]; + -+ if (sys == NULL) -+ return; ++ // Slice specific tracking stuff ++ struct { ++ pic_fifo_t pics; ++ unsigned int line; // Lines filled ++ } slice; + -+ p_filter->p_sys = NULL; ++ vcsm_init_type_t vcsm_init_type; ++} filter_sys_t; + -+ cma_buf_pool_deletez(&sys->cma_out_pool); -+ cma_vcsm_exit(sys->vcsm_init_type); + -+ free(sys); ++static MMAL_STATUS_T pic_to_format(MMAL_ES_FORMAT_T * const es_fmt, const picture_t * const pic) ++{ ++ unsigned int bpp = (pic->format.i_bits_per_pixel + 7) >> 3; ++ MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video; ++ ++ es_fmt->type = MMAL_ES_TYPE_VIDEO; ++ es_fmt->encoding = vlc_to_mmal_video_fourcc(&pic->format); ++ es_fmt->encoding_variant = 0; ++ ++ // Fill in crop etc. ++ hw_mmal_vlc_fmt_to_mmal_fmt(es_fmt, &pic->format); ++ // Override width / height with strides if appropriate ++ if (bpp != 0) { ++ v_fmt->width = pic->p[0].i_pitch / bpp; ++ v_fmt->height = pic->p[0].i_lines; ++ } ++ return MMAL_SUCCESS; +} + -+static bool to_zc_validate_fmt(const video_format_t * const f_in, const video_format_t * const f_out) ++ ++static MMAL_STATUS_T conv_enable_in(filter_t * const p_filter, filter_sys_t * const sys) +{ -+ if (!((f_in->i_chroma == VLC_CODEC_I420 || f_in->i_chroma == VLC_CODEC_I420_10L) && -+ f_out->i_chroma == VLC_CODEC_MMAL_ZC_I420)) -+ { -+ return false; -+ } -+ if (f_in->i_height != f_out->i_height || -+ f_in->i_width != f_out->i_width) ++ MMAL_STATUS_T err = MMAL_SUCCESS; ++ ++ if (!sys->input->is_enabled && ++ (err = mmal_port_enable(sys->input, sys->in_port_cb_fn)) != MMAL_SUCCESS) + { -+ return false; ++ msg_Err(p_filter, "Failed to enable input port %s (status=%"PRIx32" %s)", ++ sys->input->name, err, mmal_status_to_string(err)); + } -+ -+ return true; ++ return err; +} + -+static int OpenConverterToZc(vlc_object_t * obj) ++static MMAL_STATUS_T conv_enable_out(filter_t * const p_filter, filter_sys_t * const sys) +{ -+ int ret = VLC_EGENERIC; -+ filter_t * const p_filter = (filter_t *)obj; -+ -+ if (!to_zc_validate_fmt(&p_filter->fmt_in.video, &p_filter->fmt_out.video)) -+ goto fail; ++ MMAL_STATUS_T err = MMAL_SUCCESS; + ++ if (sys->is_cma) + { -+ char dbuf0[5], dbuf1[5]; -+ msg_Dbg(p_filter, "%s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d", __func__, -+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), -+ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, -+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, -+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, -+ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den, -+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), -+ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, -+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, -+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height, -+ p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask, -+ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den); -+ } -+ -+ to_zc_sys_t * const sys = calloc(1, sizeof(*sys)); -+ if (!sys) { -+ ret = VLC_ENOMEM; -+ goto fail; ++ if (sys->cma_out_pool == NULL && ++ (sys->cma_out_pool = cma_buf_pool_new(CONVERTER_BUFFERS, CONVERTER_BUFFERS, true, "mmal_resizer")) == NULL) ++ { ++ msg_Err(p_filter, "Failed to alloc cma buf pool"); ++ return MMAL_ENOMEM; ++ } + } -+ p_filter->p_sys = (filter_sys_t *)sys; -+ -+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { -+ msg_Err(p_filter, "VCSM init failed"); -+ goto fail; ++ else ++ { ++ cma_buf_pool_deletez(&sys->cma_out_pool); + } + -+ if ((sys->cma_out_pool = cma_buf_pool_new(5, 5, true, "conv-to-zc")) == NULL) ++ if (!sys->output->is_enabled && ++ (err = mmal_port_enable(sys->output, sys->out_port_cb_fn)) != MMAL_SUCCESS) + { -+ msg_Err(p_filter, "Failed to allocate input CMA pool"); -+ goto fail; ++ msg_Err(p_filter, "Failed to enable output port %s (status=%"PRIx32" %s)", ++ sys->output->name, err, mmal_status_to_string(err)); + } -+ -+ p_filter->pf_video_filter = to_zc_filter; -+ p_filter->pf_flush = to_zc_flush; -+ return VLC_SUCCESS; -+ -+fail: -+ CloseConverterToZc(obj); -+ return ret; -+} -+ -+//---------------------------------------------------------------------------- -+// -+// Simple "copy" from ZC -+ -+static void CloseConverterFromZc(vlc_object_t * obj) -+{ -+ VLC_UNUSED(obj); ++ return err; +} + -+static int OpenConverterFromZc(vlc_object_t * obj) ++static void conv_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ -+ return VLC_EGENERIC; -+} -+#endif -+//---------------------------------------------------------------------------- -+ -+typedef struct blend_sys_s { -+ vzc_pool_ctl_t * vzc; -+ const picture_t * last_dst; // Not a ref, just a hint that we have a new pic -+ vcsm_init_type_t vcsm_init_type; -+} blend_sys_t; ++ filter_t * const p_filter = (filter_t *)port->userdata; + -+static void FilterBlendMmal(filter_t *p_filter, -+ picture_t *dst, const picture_t * src, -+ int x_offset, int y_offset, int alpha) -+{ -+ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys; +#if TRACE_ALL -+ msg_Dbg(p_filter, "%s (%d,%d:%d) pic=%p, pts=%lld, force=%d", __func__, x_offset, y_offset, alpha, src, src->date, src->b_force); ++ msg_Dbg(p_filter, "%s: <<< cmd=%d, data=%p, pic=%p", __func__, buffer->cmd, buffer->data, buffer->user_data); +#endif -+ // If nothing to do then do nothing -+ if (alpha == 0 || -+ src->format.i_visible_height == 0 || -+ src->format.i_visible_width == 0) -+ { -+ return; -+ } -+ -+ if (dst->context == NULL) -+ msg_Err(p_filter, "MMAL pic missing context"); -+ else -+ { -+ // cast away src const so we can ref it -+ MMAL_BUFFER_HEADER_T *buf = hw_mmal_vzc_buf_from_pic(sys->vzc, (picture_t *)src, -+ vis_mmal_rect(&dst->format), -+ x_offset, y_offset, -+ alpha, -+ dst != sys->last_dst || !hw_mmal_pic_has_sub_bufs(dst)); -+ if (buf == NULL) { -+ msg_Err(p_filter, "Failed to allocate vzc buffer for subpic"); -+ return; -+ } -+ -+ hw_mmal_pic_sub_buf_add(dst, buf); + + if (buffer->cmd == MMAL_EVENT_ERROR) { +- status = *(uint32_t *)buffer->data; +- msg_Err(dec, "MMAL error %"PRIx32" \"%s\"", status, ++ MMAL_STATUS_T status = *(uint32_t *)buffer->data; + -+ sys->last_dst = dst; -+ } -+} ++ p_filter->p_sys->err_stream = status; + -+static void FlushBlendMmal(filter_t * p_filter) -+{ -+ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys; -+ sys->last_dst = NULL; -+ hw_mmal_vzc_pool_flush(sys->vzc); -+} ++ msg_Err(p_filter, "MMAL error %"PRIx32" \"%s\"", status, + mmal_status_to_string(status)); + } + + mmal_buffer_header_release(buffer); + } + +-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++static void conv_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) + { +- block_t *block = (block_t *)buffer->user_data; +- decoder_t *dec = (decoder_t *)port->userdata; +- decoder_sys_t *sys = dec->p_sys; +- buffer->user_data = NULL; ++#if TRACE_ALL ++ picture_context_t * ctx = buf->user_data; ++// filter_sys_t *const sys = ((filter_t *)port->userdata)->p_sys; + -+static void CloseBlendMmal(vlc_object_t *object) -+{ -+ filter_t * const p_filter = (filter_t *)object; -+ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys; ++ msg_Dbg((filter_t *)port->userdata, "<<< %s cmd=%d, ctx=%p, buf=%p, flags=%#x, len=%d/%d, pts=%lld", ++ __func__, buf->cmd, ctx, buf, buf->flags, buf->length, buf->alloc_size, (long long)buf->pts); ++#else ++ VLC_UNUSED(port); ++#endif + -+ if (sys != NULL) { -+ p_filter->p_sys = NULL; ++ mmal_buffer_header_release(buf); + -+ hw_mmal_vzc_pool_release(sys->vzc); -+ cma_vcsm_exit(sys->vcsm_init_type); -+ free(sys); -+ } ++#if TRACE_ALL ++ msg_Dbg((filter_t *)port->userdata, ">>> %s", __func__); ++#endif +} + -+static int OpenBlendMmal(vlc_object_t *object) ++static void conv_out_q_pic(filter_sys_t * const sys, picture_t * const pic) +{ -+ filter_t * const p_filter = (filter_t *)object; -+ const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma; -+ -+ if (!hw_mmal_chroma_is_mmal(vfcc_dst) || -+ !hw_mmal_vzc_subpic_fmt_valid(&p_filter->fmt_in.video)) -+ { -+ return VLC_EGENERIC; -+ } -+ -+ { -+ char dbuf0[5], dbuf1[5]; -+ msg_Dbg(p_filter, "%s: (%s) %s,%dx%d [(%d,%d) %dx%d]->%s,%dx%d [(%d,%d) %dx%d]", __func__, -+ "blend", -+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, -+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, -+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, -+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, -+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, -+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height); -+ } ++ pic->p_next = NULL; + -+ { -+ blend_sys_t * const sys = calloc(1, sizeof (*sys)); -+ if (sys == NULL) -+ return VLC_ENOMEM; -+ -+ p_filter->p_sys = (filter_sys_t *)sys; ++ vlc_mutex_lock(&sys->lock); ++ pic_fifo_put(&sys->ret_pics, pic); ++ vlc_mutex_unlock(&sys->lock); + +- mmal_buffer_header_release(buffer); +- if (block) +- block_Release(block); +- atomic_fetch_sub(&sys->input_in_transit, 1); + vlc_sem_post(&sys->sem); + } + +-static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++static void conv_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) + { +- decoder_t *dec = (decoder_t *)port->userdata; +- decoder_sys_t *sys = dec->p_sys; +- picture_t *picture; +- MMAL_EVENT_FORMAT_CHANGED_T *fmt; +- MMAL_ES_FORMAT_T *format; +- +- if (buffer->cmd == 0) { +- picture = (picture_t *)buffer->user_data; +- if (buffer->length > 0) { +- picture->date = buffer->pts; +- picture->b_progressive = sys->b_progressive; +- picture->b_top_field_first = sys->b_top_field_first; +- decoder_QueueVideo(dec, picture); +- } else { +- picture_Release(picture); +- if (sys->output_pool) { +- buffer->user_data = NULL; +- buffer->alloc_size = 0; +- buffer->data = NULL; +- mmal_buffer_header_release(buffer); +- } +- } +- atomic_fetch_sub(&sys->output_in_transit, 1); +- vlc_sem_post(&sys->sem); +- } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) { +- fmt = mmal_event_format_changed_get(buffer); ++ filter_t * const p_filter = (filter_t *)port->userdata; ++ filter_sys_t * const sys = p_filter->p_sys; + -+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { -+ msg_Err(p_filter, "VCSM init failed"); -+ goto fail; ++#if TRACE_ALL ++ msg_Dbg(p_filter, "<<< %s: cmd=%d, flags=%#x, pic=%p, data=%p, len=%d/%d, pts=%lld/%lld", __func__, ++ buf->cmd, buf->flags, buf->user_data, buf->data, buf->length, buf->alloc_size, ++ (long long)buf->pts, (long long)sys->stash[(unsigned int)(buf->pts & 0xf)].pts); ++#endif ++ if (buf->cmd == 0) { ++ picture_t * const pic = (picture_t *)buf->user_data; + +- format = mmal_format_alloc(); +- mmal_format_full_copy(format, fmt->format); ++ if (pic == NULL) { ++ msg_Err(p_filter, "%s: Buffer has no attached picture", __func__); + } ++ else if (buf->data == NULL || buf->length == 0) ++ { ++#if TRACE_ALL ++ msg_Dbg(p_filter, "%s: Buffer has no data", __func__); ++#endif ++ } ++ else ++ { ++ buf_to_pic_copy_props(pic, buf); + +- if (sys->opaque) +- format->encoding = MMAL_ENCODING_OPAQUE; ++ // Set pic data pointers from buf aux info now it has it ++ if (sys->is_cma) { ++ if (cma_pic_set_data(pic, sys->output->format, buf) != VLC_SUCCESS) ++ msg_Err(p_filter, "Failed to set data"); ++ } + -+ if ((sys->vzc = hw_mmal_vzc_pool_new()) == NULL) -+ goto fail; -+ } -+ -+ p_filter->pf_video_blend = FilterBlendMmal; -+ p_filter->pf_flush = FlushBlendMmal; ++// draw_corners(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 0, 0, pic->p[0].i_visible_pitch / 4, pic->p[0].i_visible_lines); ++#if DEBUG_SQUARES ++ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 0, 0, 32, 32, 0xffff0000); ++ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 32, 0, 32, 32, 0xff00ff00); ++ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 64, 0, 32, 32, 0xff0000ff); ++#endif + -+ return VLC_SUCCESS; ++ buf->user_data = NULL; // Responsability for this pic no longer with buffer ++ conv_out_q_pic(sys, pic); ++ } ++ } + -+fail: -+ CloseBlendMmal(VLC_OBJECT(p_filter)); -+ return VLC_ENOMEM; ++ mmal_buffer_header_release(buf); +} + -+// --------------------------------------------------------------------------- + -+static void FilterBlendNeon(filter_t *p_filter, -+ picture_t *dst_pic, const picture_t * src_pic, -+ int x_offset, int y_offset, int alpha) ++static void slice_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) +{ -+ const uint8_t * s_data; -+ uint8_t * d_data; -+ int width = src_pic->format.i_visible_width; -+ int height = src_pic->format.i_visible_height; -+ blend_neon_fn *const blend_fn = (blend_neon_fn * )p_filter->p_sys; ++ filter_t * const p_filter = (filter_t *)port->userdata; ++ filter_sys_t * const sys = p_filter->p_sys; + +#if TRACE_ALL -+ msg_Dbg(p_filter, "%s (%d,%d:%d) pic=%p, pts=%lld, force=%d", __func__, x_offset, y_offset, alpha, src_pic, src_pic->date, src_pic->b_force); ++ msg_Dbg(p_filter, "<<< %s: cmd=%d, flags=%#x, pic=%p, data=%p, len=%d/%d, pts=%lld", __func__, ++ buf->cmd, buf->flags, buf->user_data, buf->data, buf->length, buf->alloc_size, (long long)buf->pts); +#endif + -+ if (alpha == 0 || -+ src_pic->format.i_visible_height == 0 || -+ src_pic->format.i_visible_width == 0) ++ if (buf->cmd != 0) + { ++ mmal_buffer_header_release(buf); + return; + } + -+ x_offset += dst_pic->format.i_x_offset; -+ y_offset += dst_pic->format.i_y_offset; -+ -+ // Deal with R/B overrun -+ if (x_offset + width >= (int)(dst_pic->format.i_x_offset + dst_pic->format.i_visible_width)) -+ width = dst_pic->format.i_x_offset + dst_pic->format.i_visible_width - x_offset; -+ if (y_offset + height >= (int)(dst_pic->format.i_y_offset + dst_pic->format.i_visible_height)) -+ height = dst_pic->format.i_y_offset + dst_pic->format.i_visible_height - y_offset; -+ -+ if (width <= 0 || height <= 0) { -+ return; ++ if (buf->data == NULL || buf->length == 0) ++ { ++#if TRACE_ALL ++ msg_Dbg(p_filter, "%s: Buffer has no data", __func__); ++#endif + } ++ else ++ { ++ // Got slice ++ picture_t *pic = sys->slice.pics.head; ++ const unsigned int scale_lines = sys->output->format->es->video.height; // Expected lines of callback + -+ // *** L/U overrun ++ if (pic == NULL) { ++ msg_Err(p_filter, "No output picture"); ++ goto fail; ++ } + -+ s_data = src_pic->p[0].p_pixels + -+ src_pic->p[0].i_pixel_pitch * src_pic->format.i_x_offset + -+ src_pic->p[0].i_pitch * src_pic->format.i_y_offset; -+ d_data = dst_pic->p[0].p_pixels + -+ dst_pic->p[0].i_pixel_pitch * x_offset + -+ dst_pic->p[0].i_pitch * y_offset; ++ // Copy lines ++ // * single plane only - fix for I420 ++ { ++ const unsigned int scale_n = __MIN(scale_lines - sys->slice.line, MMAL_SLICE_HEIGHT); ++ const unsigned int pic_lines = pic->p[0].i_lines; ++ const unsigned int copy_n = sys->slice.line + scale_n <= pic_lines ? scale_n : ++ sys->slice.line >= pic_lines ? 0 : ++ pic_lines - sys->slice.line; + ++ const unsigned int src_stride = buf->type->video.pitch[0]; ++ const unsigned int dst_stride = pic->p[0].i_pitch; ++ uint8_t *dst = pic->p[0].p_pixels + sys->slice.line * dst_stride; ++ const uint8_t *src = buf->data + buf->type->video.offset[0]; + -+ do { -+ blend_fn(d_data, s_data, alpha, width); -+ s_data += src_pic->p[0].i_pitch; -+ d_data += dst_pic->p[0].i_pitch; -+ } while (--height > 0); -+} ++ if (src_stride == dst_stride) { ++ if (copy_n != 0) ++ memcpy(dst, src, src_stride * copy_n); ++ } ++ else { ++ unsigned int i; ++ for (i = 0; i != copy_n; ++i) { ++ memcpy(dst, src, __MIN(dst_stride, src_stride)); ++ dst += dst_stride; ++ src += src_stride; ++ } ++ } ++ sys->slice.line += scale_n; ++ } + -+static void CloseBlendNeon(vlc_object_t *object) -+{ -+ VLC_UNUSED(object); -+} ++ if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) != 0 || sys->slice.line >= scale_lines) { + -+static int OpenBlendNeon(vlc_object_t *object) -+{ -+ filter_t * const p_filter = (filter_t *)object; -+ const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma; -+ MMAL_FOURCC_T mfcc_src = vlc_to_mmal_video_fourcc(&p_filter->fmt_in.video); -+ MMAL_FOURCC_T mfcc_dst = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video); -+ blend_neon_fn * blend_fn = (blend_neon_fn *)0; ++ if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) == 0 || sys->slice.line != scale_lines) { ++ // Stuff doesn't add up... ++ msg_Err(p_filter, "Line count (%d/%d) & EOF disagree (flags=%#x)", sys->slice.line, scale_lines, buf->flags); ++ goto fail; ++ } ++ else { ++ sys->slice.line = 0; + -+ // Non-alpha RGB only for dest -+ if (vfcc_dst != VLC_CODEC_RGB32) -+ return VLC_EGENERIC; ++ vlc_mutex_lock(&sys->lock); ++ pic_fifo_get(&sys->slice.pics); // Remove head from Q ++ vlc_mutex_unlock(&sys->lock); + -+ // Check we have appropriate blend fn (mmal doesn't have a non-alpha RGB32) -+ switch (mfcc_src) { -+ case MMAL_ENCODING_RGBA: -+ if (mfcc_dst == MMAL_ENCODING_RGBA) -+ blend_fn = blend_rgbx_rgba_neon; -+ else if (mfcc_dst == MMAL_ENCODING_BGRA) -+ blend_fn = blend_bgrx_rgba_neon; -+ break; ++ buf_to_pic_copy_props(pic, buf); ++ conv_out_q_pic(sys, pic); ++ } ++ } ++ } + -+ case MMAL_ENCODING_BGRA: -+ if (mfcc_dst == MMAL_ENCODING_BGRA) -+ blend_fn = blend_rgbx_rgba_neon; -+ else if (mfcc_dst == MMAL_ENCODING_RGBA) -+ blend_fn = blend_bgrx_rgba_neon; -+ break; ++ // Put back ++ buf->user_data = NULL; // Zap here to make sure we can't reuse later ++ mmal_buffer_header_reset(buf); + -+ default: -+ break; ++ if (mmal_port_send_buffer(sys->output, buf) != MMAL_SUCCESS) { ++ mmal_buffer_header_release(buf); + } ++ return; + -+ if (blend_fn == (blend_neon_fn *)0) -+ { -+ return VLC_EGENERIC; -+ } ++fail: ++ sys->err_stream = MMAL_EIO; ++ vlc_sem_post(&sys->sem); // If we were waiting then break us out - the flush should fix sem values ++} + -+ p_filter->p_sys = (void *)blend_fn; -+ p_filter->pf_video_blend = FilterBlendNeon; + -+ { -+ char dbuf0[5], dbuf1[5]; -+ char dbuf0a[5], dbuf1a[5]; -+ msg_Dbg(p_filter, "%s: (%s) %s/%s,%dx%d [(%d,%d) %dx%d]->%s/%s,%dx%d [(%d,%d) %dx%d]", __func__, -+ "blend", -+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), -+ str_fourcc(dbuf0a, mfcc_src), -+ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, -+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, -+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, -+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), -+ str_fourcc(dbuf1a, mfcc_dst), -+ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, -+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, -+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height); -+ } ++static void conv_flush(filter_t * p_filter) ++{ ++ filter_sys_t * const sys = p_filter->p_sys; ++ unsigned int i; + -+ return VLC_SUCCESS; -+} ++#if TRACE_ALL ++ msg_Dbg(p_filter, "<<< %s", __func__); ++#endif + -+vlc_module_begin() -+ set_category( CAT_INPUT ) -+ set_subcategory( SUBCAT_INPUT_VCODEC ) -+ set_shortname(N_("MMAL decoder")) -+ set_description(N_("MMAL-based decoder plugin for Raspberry Pi")) -+ set_capability("video decoder", 90) -+ add_shortcut("mmal_decoder") -+ add_bool(MMAL_OPAQUE_NAME, true, MMAL_OPAQUE_TEXT, MMAL_OPAQUE_LONGTEXT, false) -+ set_callbacks(OpenDecoder, CloseDecoder) ++ if (sys->resizer_type == FILTER_RESIZER_HVS) ++ { ++ for (i = 0; i != SUBS_MAX; ++i) { ++ hw_mmal_subpic_flush(VLC_OBJECT(p_filter), sys->subs + i); ++ } ++ } + -+ add_submodule() -+ set_category( CAT_VIDEO ) -+ set_subcategory( SUBCAT_VIDEO_VFILTER ) -+ set_shortname(N_("MMAL resizer")) -+ set_description(N_("MMAL resizing conversion filter")) -+ add_shortcut("mmal_converter") -+ set_capability( "video converter", 900 ) -+ add_bool(MMAL_RESIZE_NAME, /* default */ false, MMAL_RESIZE_TEXT, MMAL_RESIZE_LONGTEXT, /* advanced option */ false) -+ add_bool(MMAL_ISP_NAME, /* default */ false, MMAL_ISP_TEXT, MMAL_ISP_LONGTEXT, /* advanced option */ false) -+ set_callbacks(OpenConverter, CloseConverter) ++ if (sys->input != NULL && sys->input->is_enabled) ++ mmal_port_disable(sys->input); + -+#if OPT_TO_FROM_ZC -+ add_submodule() -+ set_category( CAT_VIDEO ) -+ set_subcategory( SUBCAT_VIDEO_VFILTER ) -+ set_shortname(N_("MMAL to ZC")) -+ set_description(N_("MMAL conversion to ZC filter")) -+ add_shortcut("mmal_to_zc") -+ set_capability( "video converter", 901 ) -+ set_callbacks(OpenConverterToZc, CloseConverterToZc) ++ if (sys->output != NULL && sys->output->is_enabled) ++ mmal_port_disable(sys->output); + -+ add_submodule() -+ set_category( CAT_VIDEO ) -+ set_subcategory( SUBCAT_VIDEO_VFILTER ) -+ set_shortname(N_("MMAL from ZC")) -+ set_description(N_("MMAL conversion from ZC filter")) -+ add_shortcut("mmal_from_zc") -+ set_capability( "video converter", 902 ) -+ set_callbacks(OpenConverterFromZc, CloseConverterFromZc) -+#endif ++// cma_buf_pool_deletez(&sys->cma_out_pool); + -+ add_submodule() -+ set_category( CAT_VIDEO ) -+ set_subcategory( SUBCAT_VIDEO_VFILTER ) -+ set_description(N_("Video pictures blending for MMAL")) -+ add_shortcut("mmal_blend") -+ set_capability("video blending", 120) -+ set_callbacks(OpenBlendMmal, CloseBlendMmal) ++ // Free up anything we may have already lying around ++ // Don't need lock as the above disables should have prevented anything ++ // happening in the background + -+ add_submodule() -+ set_category( CAT_VIDEO ) -+ set_subcategory( SUBCAT_VIDEO_VFILTER ) -+ set_description(N_("Video pictures blending for neon")) -+ add_shortcut("neon_blend") -+ set_capability("video blending", 110) -+ set_callbacks(OpenBlendNeon, CloseBlendNeon) ++ for (i = 0; i != 16; ++i) { ++ conv_frame_stash_t *const stash = sys->stash + i; ++ unsigned int sub_no; + -+vlc_module_end() ++ stash->pts = MMAL_TIME_UNKNOWN; ++ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { ++ if (stash->sub_bufs[sub_no] != NULL) { ++ mmal_buffer_header_release(stash->sub_bufs[sub_no]); ++ stash->sub_bufs[sub_no] = NULL; ++ } ++ } ++ } + ++ pic_fifo_release_all(&sys->slice.pics); ++ pic_fifo_release_all(&sys->ret_pics); + ---- /dev/null -+++ b/modules/hw/mmal/converter_mmal.c -@@ -0,0 +1,479 @@ -+#ifdef HAVE_CONFIG_H -+# include "config.h" -+#endif ++ // Reset sem values - easiest & most reliable way is to just kill & re-init ++ vlc_sem_destroy(&sys->sem); ++ vlc_sem_init(&sys->sem, 0); ++ sys->pic_n = 0; + -+#include -+#include -+#include -+#include ++ // Reset error status ++ sys->err_stream = MMAL_SUCCESS; + -+#include ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s", __func__); ++#endif ++} + -+#include -+#include ++static void conv_stash_fixup(filter_t * const p_filter, filter_sys_t * const sys, picture_t * const p_pic) ++{ ++ conv_frame_stash_t * const stash = sys->stash + (p_pic->date & 0xf); ++ unsigned int sub_no; ++ VLC_UNUSED(p_filter); + -+#include -+#include -+#include -+#include -+#include ++ p_pic->date = stash->pts; ++ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { ++ if (stash->sub_bufs[sub_no] != NULL) { ++ // **** Do stashed blend ++ // **** Aaargh, bother... need to rescale subs too + -+#include "mmal_cma.h" ++ mmal_buffer_header_release(stash->sub_bufs[sub_no]); ++ stash->sub_bufs[sub_no] = NULL; ++ } ++ } ++} + -+#include "../../video_output/opengl/converter.h" ++// Output buffers may contain a pic ref on error or flush ++// Free it ++static MMAL_BOOL_T out_buffer_pre_release_cb(MMAL_BUFFER_HEADER_T *header, void *userdata) ++{ ++ VLC_UNUSED(userdata); + -+#include "mmal_picture.h" ++ picture_t * const pic = header->user_data; ++ header->user_data = NULL; + -+#include ++ if (pic != NULL) ++ picture_Release(pic); + -+#define TRACE_ALL 0 ++ return MMAL_FALSE; ++} + -+typedef struct mmal_gl_converter_s ++static MMAL_STATUS_T conv_set_output(filter_t * const p_filter, filter_sys_t * const sys, picture_t * const pic) +{ -+ EGLint drm_fourcc; -+ vcsm_init_type_t vcsm_init_type; -+ cma_buf_t * last_cb; -+ -+ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; -+} mmal_gl_converter_t; ++ MMAL_STATUS_T status; + ++ sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter; ++ sys->output->format->type = MMAL_ES_TYPE_VIDEO; ++ sys->output->format->encoding = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video); ++ sys->output->format->encoding_variant = 0; ++ hw_mmal_vlc_fmt_to_mmal_fmt(sys->output->format, &p_filter->fmt_out.video); + -+static EGLint vlc_to_gl_fourcc(const video_format_t * const fmt) -+{ -+ // Converting to mmal selects the right RGB32 varient -+ switch(vlc_to_mmal_video_fourcc(fmt)) ++ if (pic != NULL) + { -+ case MMAL_ENCODING_I420: -+ return MMAL_FOURCC('Y','U','1','2'); -+ case MMAL_ENCODING_YV12: -+ return MMAL_FOURCC('Y','V','1','2'); -+ case MMAL_ENCODING_I422: -+ return MMAL_FOURCC('Y','U','1','6'); -+// case MMAL_ENCODING_YUVUV128: // Doesn't actually work yet -+ case MMAL_ENCODING_NV12: -+ return MMAL_FOURCC('N','V','1','2'); -+ case MMAL_ENCODING_NV21: -+ return MMAL_FOURCC('N','V','2','1'); -+ case MMAL_ENCODING_RGB16: -+ return MMAL_FOURCC('R','G','1','6'); -+ case MMAL_ENCODING_RGB24: -+ return MMAL_FOURCC('B','G','2','4'); -+ case MMAL_ENCODING_BGR24: -+ return MMAL_FOURCC('R','G','2','4'); -+ case MMAL_ENCODING_BGR32: -+ case MMAL_ENCODING_BGRA: -+ return MMAL_FOURCC('X','R','2','4'); -+ case MMAL_ENCODING_RGB32: -+ case MMAL_ENCODING_RGBA: -+ return MMAL_FOURCC('X','B','2','4'); -+ default: -+ break; ++ // Override default format width/height if we have a pic we need to match ++ if ((status = pic_to_format(sys->output->format, pic)) != MMAL_SUCCESS) ++ { ++ char cbuf[5]; ++ msg_Err(p_filter, "Bad format desc: %s, pic=%p, bits=%d", str_fourcc(cbuf, pic->format.i_chroma), pic, pic->format.i_bits_per_pixel); ++ return status; ++ } ++ ++ MMAL_VIDEO_FORMAT_T *fmt = &sys->output->format->es->video; ++ msg_Dbg(p_filter, "%s: %dx%d [(0,0) %dx%d]", __func__, fmt->width, fmt->height, fmt->crop.width, fmt->crop.height); + } -+ return 0; -+} + -+typedef struct tex_context_s { -+ picture_context_t cmn; -+ GLuint texture; ++ if (sys->is_sliced) { ++ // Override height for slice ++ sys->output->format->es->video.height = MMAL_SLICE_HEIGHT; ++ } + -+ PFNGLDELETETEXTURESPROC DeleteTextures; // Copy fn pointer so we don't need tc on delete -+} tex_context_t; ++ mmal_log_dump_format(sys->output->format); + -+static void tex_context_delete(tex_context_t * const tex) -+{ -+ tex->DeleteTextures(1, &tex->texture); -+ free(tex); -+} ++ status = mmal_port_format_commit(sys->output); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(p_filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)", ++ sys->output->name, status, mmal_status_to_string(status)); ++ return status; ++ } + -+static void tex_context_destroy(picture_context_t * pic_ctx) -+{ -+ tex_context_delete((tex_context_t *)pic_ctx); -+} ++ sys->output->buffer_num = __MAX(sys->is_sliced ? 16 : 2, sys->output->buffer_num_recommended); ++ sys->output->buffer_size = sys->output->buffer_size_recommended; + -+static picture_context_t * tex_context_copy(picture_context_t * pic_ctx) -+{ -+ return pic_ctx; ++ if ((status = conv_enable_out(p_filter, sys)) != MMAL_SUCCESS) ++ return status; ++ ++ return MMAL_SUCCESS; +} + -+static tex_context_t * get_tex_context(const opengl_tex_converter_t * const tc, picture_t * const pic, cma_buf_t * const cb) ++ ++static picture_t *conv_get_out_pics(filter_sys_t * const sys) +{ -+ mmal_gl_converter_t * const sys = tc->priv; -+ tex_context_t * tex = (tex_context_t *)cma_buf_context2(cb); -+ if (tex != NULL) -+ return tex; ++ picture_t * ret_pics; + -+ if ((tex = malloc(sizeof(*tex))) == NULL) -+ return NULL; ++ vlc_sem_wait(&sys->sem); + -+ *tex = (tex_context_t){ -+ .cmn = { -+ .destroy = tex_context_destroy, -+ .copy = tex_context_copy -+ }, -+ .texture = 0, -+ .DeleteTextures = tc->vt->DeleteTextures -+ }; ++ // Return a single pending buffer ++ vlc_mutex_lock(&sys->lock); ++ ret_pics = pic_fifo_get(&sys->ret_pics); ++ vlc_mutex_unlock(&sys->lock); + -+ { -+ EGLint attribs[30]; -+ EGLint * a = attribs; -+ const int fd = cma_buf_fd(cb); -+ uint8_t * base_addr = cma_buf_addr(cb); ++ return ret_pics; ++} + -+ if (pic->i_planes >= 4 || pic->i_planes <= 0) -+ { -+ msg_Err(tc, "%s: Bad planes: %d", __func__, pic->i_planes); -+ goto fail; -+ } -+ -+ *a++ = EGL_WIDTH; -+ *a++ = pic->format.i_visible_width; -+ *a++ = EGL_HEIGHT; -+ *a++ = pic->format.i_visible_height; -+ *a++ = EGL_LINUX_DRM_FOURCC_EXT; -+ *a++ = sys->drm_fourcc; ++static picture_t *conv_filter(filter_t *p_filter, picture_t *p_pic) ++{ ++ filter_sys_t * const sys = p_filter->p_sys; ++ picture_t * ret_pics = NULL; ++ MMAL_STATUS_T err; ++ const uint64_t frame_seq = ++sys->frame_seq; ++ conv_frame_stash_t * const stash = sys->stash + (frame_seq & 0xf); ++ MMAL_BUFFER_HEADER_T * out_buf = NULL; + -+ if (pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND8) -+ { -+ // Sand is its own very special bunny :-( -+ static const EGLint attnames[] = { -+ EGL_DMA_BUF_PLANE0_FD_EXT, -+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, -+ EGL_DMA_BUF_PLANE0_PITCH_EXT, -+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, -+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, -+ EGL_DMA_BUF_PLANE1_FD_EXT, -+ EGL_DMA_BUF_PLANE1_OFFSET_EXT, -+ EGL_DMA_BUF_PLANE1_PITCH_EXT, -+ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, -+ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT -+ }; ++#if TRACE_ALL ++ { ++ char dbuf0[5], dbuf1[5]; ++ msg_Dbg(p_filter, "<<< %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] sar:%d/%d", __func__, ++ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, ++ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, ++ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, ++ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den, ++ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, ++ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, ++ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height, ++ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den); ++ } ++#endif + -+ const EGLint * n = attnames; ++ if (sys->err_stream != MMAL_SUCCESS) { ++ goto stream_fail; ++ } + -+ for (int i = 0; i < pic->i_planes; ++i) -+ { -+ const uint64_t mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(pic->p[i].i_pitch >> 7); ++ // Check pic fmt corresponds to what we have set up ++ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic)) ++ { ++ msg_Dbg(p_filter, "Reset input port format"); + -+ *a++ = *n++; -+ *a++ = fd; -+ *a++ = *n++; -+ *a++ = pic->p[i].p_pixels - base_addr; -+ *a++ = *n++; -+ *a++ = pic->format.i_width; -+ *a++ = *n++; -+ *a++ = (EGLint)(mod >> 32); -+ *a++ = *n++; -+ *a++ = (EGLint)(mod & 0xffffffff); ++ // HVS can take new formats without disable, others need it ++ if (sys->resizer_type != FILTER_RESIZER_HVS) { ++ // Extract any pending pic ++ if (sys->pic_n >= 2) { ++ ret_pics = conv_get_out_pics(sys); ++ // If pic_n == 1 then we return without trying to get stuff ++ sys->pic_n = 1; + } -+ } -+ else -+ { -+ static const EGLint attnames[] = { -+ EGL_DMA_BUF_PLANE0_FD_EXT, -+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, -+ EGL_DMA_BUF_PLANE0_PITCH_EXT, -+ EGL_DMA_BUF_PLANE1_FD_EXT, -+ EGL_DMA_BUF_PLANE1_OFFSET_EXT, -+ EGL_DMA_BUF_PLANE1_PITCH_EXT, -+ EGL_DMA_BUF_PLANE2_FD_EXT, -+ EGL_DMA_BUF_PLANE2_OFFSET_EXT, -+ EGL_DMA_BUF_PLANE2_PITCH_EXT, -+ EGL_DMA_BUF_PLANE3_FD_EXT, -+ EGL_DMA_BUF_PLANE3_OFFSET_EXT, -+ EGL_DMA_BUF_PLANE3_PITCH_EXT -+ }; -+ -+ const EGLint * n = attnames; -+ -+ for (int i = 0; i < pic->i_planes; ++i) -+ { -+ *a++ = *n++; -+ *a++ = fd; -+ *a++ = *n++; -+ *a++ = pic->p[i].p_pixels - base_addr; -+ *a++ = *n++; -+ *a++ = pic->p[i].i_pitch; ++ if (sys->input->is_enabled) { ++ if ((err = mmal_port_disable(sys->input)) != MMAL_SUCCESS) ++ msg_Warn(p_filter, "Format update disable failed: %s", mmal_status_to_string(err)); + } + } + -+ *a = EGL_NONE; -+ -+ const EGLImage image = tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); -+ if (!image) { -+ msg_Err(tc, "Failed to import fd %d: Err=%#x", fd, tc->vt->GetError()); -+ goto fail; -+ } ++// mmal_log_dump_port(sys->input); ++ if ((err = mmal_port_format_commit(sys->input)) != MMAL_SUCCESS) ++ msg_Warn(p_filter, "Format update commit failed: %s", mmal_status_to_string(err)); + -+ // ** ?? tc->tex_target -+ tc->vt->GenTextures(1, &tex->texture); -+ tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, tex->texture); -+ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -+ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -+ sys->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image); ++ // (Re)enable if required will be done later ++ } + -+ tc->gl->egl.destroyImageKHR(tc->gl, image); ++ if (p_pic->context == NULL) { ++ // Can't have stashed subpics if not one of our pics ++ if (!sys->needs_copy_in) ++ msg_Dbg(p_filter, "%s: No context", __func__); + } ++ else if (sys->resizer_type == FILTER_RESIZER_HVS) ++ { ++ unsigned int sub_no = 0; + -+ if (cma_buf_add_context2(cb, &tex->cmn) != VLC_SUCCESS) ++ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { ++ int rv; ++ if ((rv = hw_mmal_subpic_update(VLC_OBJECT(p_filter), ++ hw_mmal_pic_sub_buf_get(p_pic, sub_no), ++ sys->subs + sub_no, ++ &p_pic->format, ++ &sys->output->format->es->video.crop, ++ MMAL_DISPLAY_ROT0, ++ frame_seq)) == 0) ++ break; ++ else if (rv < 0) ++ goto fail; ++ } ++ } ++ else + { -+ msg_Err(tc, "%s: add_context2 failed", __func__); -+ goto fail; ++ unsigned int sub_no = 0; ++ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { ++ if ((stash->sub_bufs[sub_no] = hw_mmal_pic_sub_buf_get(p_pic, sub_no)) != NULL) { ++ mmal_buffer_header_acquire(stash->sub_bufs[sub_no]); ++ } ++ } + } -+ return tex; + -+fail: -+ tex_context_delete(tex); -+ return NULL; -+} ++ if (!sys->out_fmt_set) { ++ sys->out_fmt_set = true; + ++ if (sys->is_sliced) { ++ // If zc then we will do stride conversion when we copy to arm side ++ // so no need to worry about actual pic dimensions here ++ if ((err = conv_set_output(p_filter, sys, NULL)) != MMAL_SUCCESS) ++ goto fail; + -+static int -+tc_mmal_update(const opengl_tex_converter_t *tc, GLuint *textures, -+ const GLsizei *tex_width, const GLsizei *tex_height, -+ picture_t *pic, const size_t *plane_offset) -+{ -+ mmal_gl_converter_t * const sys = tc->priv; -+#if TRACE_ALL -+ { -+ char cbuf[5]; -+ msg_Dbg(tc, "%s: %s %d*%dx%d : %d*%dx%d", __func__, -+ str_fourcc(cbuf, pic->format.i_chroma), -+ tc->tex_count, tex_width[0], tex_height[0], pic->i_planes, pic->p[0].i_pitch, pic->p[0].i_lines); -+ } -+#endif -+ VLC_UNUSED(tex_width); -+ VLC_UNUSED(tex_height); -+ VLC_UNUSED(plane_offset); ++ sys->out_pool = mmal_port_pool_create(sys->output, sys->output->buffer_num, sys->output->buffer_size); ++ } ++ else { ++ picture_t *pic = filter_NewPicture(p_filter); ++ err = conv_set_output(p_filter, sys, pic); ++ picture_Release(pic); ++ if (err != MMAL_SUCCESS) ++ goto fail; + -+ if (!is_cma_buf_pic_chroma(pic->format.i_chroma)) -+ { -+ char cbuf[5]; -+ msg_Err(tc, "Pic with unexpected chroma: %s", str_fourcc(cbuf, pic->format.i_chroma)); -+ return VLC_EGENERIC; -+ } ++ sys->out_pool = mmal_pool_create(sys->output->buffer_num, 0); ++ } + -+ cma_buf_t * const cb = cma_buf_pic_get(pic); -+ if (cb == NULL) -+ { -+ msg_Err(tc, "Pic missing cma buf"); -+ return VLC_EGENERIC; ++ if (sys->out_pool == NULL) { ++ msg_Err(p_filter, "Failed to create output pool"); ++ goto fail; ++ } + } + -+ tex_context_t * const tex = get_tex_context(tc, pic, cb); -+ if (tex == NULL) -+ return VLC_EGENERIC; -+ -+// tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, tex->texture); ++ // Reenable stuff if the last thing we did was flush ++ if ((err = conv_enable_out(p_filter, sys)) != MMAL_SUCCESS || ++ (err = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS) ++ goto fail; + -+ cma_buf_unref(sys->last_cb); -+ sys->last_cb = cma_buf_ref(cb); ++ // We attach pic to buf before stuffing the output port ++ // We could attach the pic on output for cma, but it is a lot easier to keep ++ // the code common. ++ { ++ picture_t * const out_pic = filter_NewPicture(p_filter); + -+ textures[0] = tex->texture; -+ return VLC_SUCCESS; -+} ++ if (out_pic == NULL) ++ { ++ msg_Err(p_filter, "Failed to alloc required filter output pic"); ++ goto fail; ++ } + -+static int -+tc_mmal_fetch_locations(opengl_tex_converter_t *tc, GLuint program) -+{ -+ tc->uloc.Texture[0] = tc->vt->GetUniformLocation(program, "Texture0"); -+ return tc->uloc.Texture[0] != -1 ? VLC_SUCCESS : VLC_EGENERIC; -+} ++ if (p_filter->fmt_out.video.i_sar_den == 0 || p_filter->fmt_out.video.i_sar_num == 0) { ++ out_pic->format.i_sar_den = 1; ++ out_pic->format.i_sar_num = 1; ++ } ++ else { ++ out_pic->format.i_sar_den = p_filter->fmt_out.video.i_sar_den; ++ out_pic->format.i_sar_num = p_filter->fmt_out.video.i_sar_num; ++ } + -+static void -+tc_mmal_prepare_shader(const opengl_tex_converter_t *tc, -+ const GLsizei *tex_width, const GLsizei *tex_height, -+ float alpha) -+{ -+ (void) tex_width; (void) tex_height; (void) alpha; -+ VLC_UNUSED(tc); -+// tc->vt->Uniform1i(tc->uloc.Texture[0], 0); -+} ++ if (sys->is_sliced) { ++ vlc_mutex_lock(&sys->lock); ++ pic_fifo_put(&sys->slice.pics, out_pic); ++ vlc_mutex_unlock(&sys->lock); + -+static GLuint -+tc_fragment_shader_init(opengl_tex_converter_t * const tc, const GLenum tex_target, -+ const vlc_fourcc_t chroma, const video_color_space_t yuv_space) -+{ -+ VLC_UNUSED(yuv_space); ++ // Poke any returned pic buffers into output ++ // In general this should only happen immediately after enable ++ while ((out_buf = mmal_queue_get(sys->out_pool->queue)) != NULL) ++ mmal_port_send_buffer(sys->output, out_buf); ++ } ++ else ++ { ++ // 1 in - 1 out ++ if ((out_buf = mmal_queue_wait(sys->out_pool->queue)) == NULL) ++ { ++ msg_Err(p_filter, "Failed to get output buffer"); ++ picture_Release(out_pic); ++ goto fail; ++ } ++ mmal_buffer_header_reset(out_buf); + -+ tc->tex_count = 1; -+ tc->tex_target = tex_target; -+ tc->texs[0] = (struct opengl_tex_cfg) { -+ { 1, 1 }, { 1, 1 }, GL_RGB, chroma, GL_UNSIGNED_SHORT //** ?? -+ }; ++ // Attach out_pic to the buffer & ensure it is freed when the buffer is released ++ // On a good send callback the pic will be extracted to avoid this ++ out_buf->user_data = out_pic; ++ mmal_buffer_header_pre_release_cb_set(out_buf, out_buffer_pre_release_cb, NULL); + -+ tc->pf_fetch_locations = tc_mmal_fetch_locations; -+ tc->pf_prepare_shader = tc_mmal_prepare_shader; ++#if 0 ++ { ++ char dbuf0[5]; ++ msg_Dbg(p_filter, "out_pic %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", ++ str_fourcc(dbuf0, out_pic->format.i_chroma), ++ out_pic->format.i_width, out_pic->format.i_height, ++ out_pic->format.i_x_offset, out_pic->format.i_y_offset, ++ out_pic->format.i_visible_width, out_pic->format.i_visible_height, ++ out_pic->format.i_sar_num, out_pic->format.i_sar_den); ++ } ++#endif + ++ if (sys->is_cma) { ++ int rv; + -+ const char fs[] = -+ "#extension GL_OES_EGL_image_external : enable\n" -+ "precision mediump float;\n" -+ "uniform samplerExternalOES Texture0;\n" -+ "varying vec2 TexCoord0;\n" -+ "void main() {\n" -+ " gl_FragColor = texture2D(Texture0, TexCoord0);\n" -+ "}\n"; ++ cma_buf_t * const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, sys->output->buffer_size); ++ if (cb == NULL) { ++ char dbuf0[5]; ++ msg_Err(p_filter, "Failed to alloc CMA buf: fmt=%s, size=%d", ++ str_fourcc(dbuf0, out_pic->format.i_chroma), ++ sys->output->buffer_size); ++ goto fail; ++ } ++ const unsigned int vc_h = cma_buf_vc_handle(cb); // Cannot coerce without going via variable ++ out_buf->data = (uint8_t *)vc_h; ++ out_buf->alloc_size = sys->output->buffer_size; + ++ if ((rv = cma_buf_pic_attach(cb, out_pic)) != VLC_SUCCESS) ++ { ++ char dbuf0[5]; ++ msg_Err(p_filter, "Failed to attach CMA to pic: fmt=%s err=%d", ++ str_fourcc(dbuf0, out_pic->format.i_chroma), ++ rv); ++ cma_buf_unref(cb); ++ goto fail; ++ } ++ } ++ else { ++ out_buf->data = out_pic->p[0].p_pixels; ++ out_buf->alloc_size = out_pic->p[0].i_pitch * out_pic->p[0].i_lines; ++ //**** stride ???? ++ } + -+ const char *code = fs; ++#if TRACE_ALL ++ msg_Dbg(p_filter, "Out buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d, pts=%lld", ++ p_pic, out_buf->data, out_buf->user_data, out_buf->flags, ++ out_buf->length, out_buf->alloc_size, (long long)out_buf->pts); ++#endif + -+ GLuint fragment_shader = tc->vt->CreateShader(GL_FRAGMENT_SHADER); -+ tc->vt->ShaderSource(fragment_shader, 1, &code, NULL); -+ tc->vt->CompileShader(fragment_shader); -+ return fragment_shader; -+} ++ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Send buffer to output failed"); ++ goto fail; ++ } ++ out_buf = NULL; ++ } ++ } + + -+static void -+CloseGLConverter(vlc_object_t *obj) -+{ -+ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; -+ mmal_gl_converter_t * const sys = tc->priv; ++ // Stuff into input ++ // We assume the BH is already set up with values reflecting pic date etc. ++ stash->pts = p_pic->date; ++ { ++ MMAL_BUFFER_HEADER_T *const pic_buf = sys->needs_copy_in ? ++ hw_mmal_pic_buf_copied(p_pic, sys->in_pool, sys->input, sys->cma_in_pool) : ++ hw_mmal_pic_buf_replicated(p_pic, sys->in_pool); + -+ if (sys == NULL) -+ return; ++ // Whether or not we extracted the pic_buf we are done with the picture ++ picture_Release(p_pic); ++ p_pic = NULL; + -+ cma_buf_unref(sys->last_cb); -+ cma_vcsm_exit(sys->vcsm_init_type); -+ free(sys); -+} ++ if (pic_buf == NULL) { ++ msg_Err(p_filter, "Pic has no attached buffer"); ++ goto fail; ++ } + ++ pic_buf->pts = frame_seq; + -+// Pick a chroma that we can convert to -+// Prefer I420 as smallest -+static vlc_fourcc_t chroma_in_out(const vlc_fourcc_t chroma_in) -+{ -+ switch (chroma_in) -+ { -+ case VLC_CODEC_MMAL_OPAQUE: -+ case VLC_CODEC_MMAL_ZC_I420: -+ case VLC_CODEC_MMAL_ZC_SAND8: -+ case VLC_CODEC_MMAL_ZC_SAND10: // ISP only -+ return VLC_CODEC_MMAL_ZC_I420; -+ case VLC_CODEC_MMAL_ZC_SAND30: // HVS only -+ case VLC_CODEC_MMAL_ZC_RGB32: -+ return VLC_CODEC_MMAL_ZC_RGB32; // HVS can't generate YUV of any sort -+ default: -+ break; ++#if TRACE_ALL ++ msg_Dbg(p_filter, "In buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d/%d, pts=%lld", ++ p_pic, pic_buf->data, pic_buf->user_data, pic_buf->flags, ++ pic_buf->length, pic_buf->alloc_size, sys->input->buffer_size, (long long)pic_buf->pts); ++#endif ++ ++ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Send buffer to input failed"); ++ mmal_buffer_header_release(pic_buf); ++ goto fail; ++ } ++ } ++ ++ // We have a 1 pic latency for everything except the 1st pic which we ++ // wait for. ++ // This means we get a single static pic out ++ if (sys->pic_n++ == 1) { ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s: Pic1=%p", __func__, ret_pics); ++#endif ++ return ret_pics; + } -+ return 0; -+} + ++ ret_pics = conv_get_out_pics(sys); + -+static int -+OpenGLConverter(vlc_object_t *obj) ++ if (sys->err_stream != MMAL_SUCCESS) ++ goto stream_fail; ++ ++ conv_stash_fixup(p_filter, sys, ret_pics); ++ ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s: pic=%p", __func__, ret_pics); ++#endif ++ ++ return ret_pics; ++ ++stream_fail: ++ msg_Err(p_filter, "MMAL error reported by callback"); ++fail: ++#if TRACE_ALL ++ msg_Err(p_filter, ">>> %s: FAIL", __func__); ++#endif ++ if (ret_pics != NULL) ++ picture_Release(ret_pics); ++ if (out_buf != NULL) ++ mmal_buffer_header_release(out_buf); ++ if (p_pic != NULL) ++ picture_Release(p_pic); ++ conv_flush(p_filter); ++ return NULL; ++} ++ ++static void CloseConverter(vlc_object_t * obj) +{ -+ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; -+ int rv = VLC_EGENERIC; -+ const EGLint eglfmt = vlc_to_gl_fourcc(&tc->fmt); -+ const vlc_fourcc_t chroma_out = chroma_in_out(tc->fmt.i_chroma); ++ filter_t * const p_filter = (filter_t *)obj; ++ filter_sys_t * const sys = p_filter->p_sys; ++ unsigned int i; + -+ // Do we know what to do with this? -+ if (chroma_out == 0) -+ return rv; ++#if TRACE_ALL ++ msg_Dbg(obj, "<<< %s", __func__); ++#endif + -+ { -+ char dbuf0[5], dbuf1[5], dbuf2[5]; -+ msg_Dbg(tc, "<<< %s: V:%s/E:%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %s", __func__, -+ str_fourcc(dbuf0, tc->fmt.i_chroma), -+ str_fourcc(dbuf1, eglfmt), -+ tc->fmt.i_width, tc->fmt.i_height, -+ tc->fmt.i_x_offset, tc->fmt.i_y_offset, -+ tc->fmt.i_visible_width, tc->fmt.i_visible_height, -+ tc->fmt.i_sar_num, tc->fmt.i_sar_den, -+ str_fourcc(dbuf2, chroma_out)); -+ } ++ if (sys == NULL) ++ return; + -+ if (tc->gl->ext != VLC_GL_EXT_EGL || -+ !tc->gl->egl.createImageKHR || !tc->gl->egl.destroyImageKHR) -+ { -+ // Missing an important callback -+ msg_Dbg(tc, "Missing EGL xxxImageKHR calls"); -+ return rv; -+ } ++ // Disables input & output ports ++ conv_flush(p_filter); + -+ if ((tc->priv = calloc(1, sizeof(mmal_gl_converter_t))) == NULL) -+ { -+ msg_Err(tc, "priv alloc failure"); -+ rv = VLC_ENOMEM; -+ goto fail; -+ } -+ mmal_gl_converter_t * const sys = tc->priv; ++ cma_buf_pool_deletez(&sys->cma_in_pool); ++ cma_buf_pool_deletez(&sys->cma_out_pool); + -+ sys->drm_fourcc = eglfmt; ++ if (sys->component && sys->component->control->is_enabled) ++ mmal_port_disable(sys->component->control); + -+ if ((sys->vcsm_init_type = cma_vcsm_init()) != VCSM_INIT_CMA) { -+ msg_Dbg(tc, "VCSM init failed"); -+ goto fail; -+ } ++ if (sys->component && sys->component->is_enabled) ++ mmal_component_disable(sys->component); + -+ if ((sys->glEGLImageTargetTexture2DOES = vlc_gl_GetProcAddress(tc->gl, "glEGLImageTargetTexture2DOES")) == NULL) ++ if (sys->resizer_type == FILTER_RESIZER_HVS) + { -+ msg_Err(tc, "Failed to bind GL fns"); -+ goto fail; ++ for (i = 0; i != SUBS_MAX; ++i) { ++ hw_mmal_subpic_close(VLC_OBJECT(p_filter), sys->subs + i); ++ } + } + -+ if ((tc->fshader = tc_fragment_shader_init(tc, GL_TEXTURE_EXTERNAL_OES, -+ eglfmt == 0 ? VLC_CODEC_RGB32 : tc->fmt.i_chroma, -+ eglfmt == 0 ? COLOR_SPACE_SRGB : tc->fmt.space)) == 0) -+ { -+ msg_Err(tc, "Failed to make shader"); -+ goto fail; -+ } -+ -+ if (eglfmt == 0) ++ if (sys->out_pool) + { -+ tc->fmt.i_chroma = chroma_out; -+ tc->fmt.i_bits_per_pixel = 8; -+ if (tc->fmt.i_chroma == VLC_CODEC_MMAL_ZC_RGB32) -+ { -+ tc->fmt.i_rmask = 0xff0000; -+ tc->fmt.i_gmask = 0xff00; -+ tc->fmt.i_bmask = 0xff; -+ tc->fmt.space = COLOR_SPACE_SRGB; -+ } ++ if (sys->is_sliced) ++ mmal_port_pool_destroy(sys->output, sys->out_pool); + else -+ { -+ tc->fmt.i_rmask = 0; -+ tc->fmt.i_gmask = 0; -+ tc->fmt.i_bmask = 0; -+ tc->fmt.space = COLOR_SPACE_UNDEF; -+ } -+ sys->drm_fourcc = vlc_to_gl_fourcc(&tc->fmt); -+ } -+ -+ tc->handle_texs_gen = true; // We manage the texs -+ tc->pf_update = tc_mmal_update; -+ -+#if TRACE_ALL -+ { -+ char dbuf0[5], dbuf1[5], dbuf2[5]; -+ msg_Dbg(tc, ">>> %s: V:%s/E:%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %s", __func__, -+ str_fourcc(dbuf0, tc->fmt.i_chroma), -+ str_fourcc(dbuf1, sys->drm_fourcc), -+ tc->fmt.i_width, tc->fmt.i_height, -+ tc->fmt.i_x_offset, tc->fmt.i_y_offset, -+ tc->fmt.i_visible_width, tc->fmt.i_visible_height, -+ tc->fmt.i_sar_num, tc->fmt.i_sar_den, -+ str_fourcc(dbuf2, chroma_out)); ++ mmal_pool_destroy(sys->out_pool); + } -+#endif + -+ return VLC_SUCCESS; ++ if (sys->in_pool != NULL) ++ mmal_pool_destroy(sys->in_pool); + -+fail: -+ CloseGLConverter(obj); -+ return rv; -+} ++ if (sys->component) ++ mmal_component_release(sys->component); + -+vlc_module_begin () -+ set_description("MMAL OpenGL surface converter") -+ set_shortname (N_("MMALGLConverter")) -+ set_capability("glconv", 900) -+ set_callbacks(OpenGLConverter, CloseGLConverter) -+ set_category(CAT_VIDEO) -+ set_subcategory(SUBCAT_VIDEO_VOUT) -+ add_shortcut("mmal_gl_converter") -+vlc_module_end () ++ cma_vcsm_exit(sys->vcsm_init_type); + ---- a/modules/hw/mmal/deinterlace.c -+++ b/modules/hw/mmal/deinterlace.c -@@ -26,11 +26,12 @@ - #include "config.h" - #endif - --#include -+#include ++ vlc_sem_destroy(&sys->sem); ++ vlc_mutex_destroy(&sys->lock); + - #include -+#include - #include - #include --#include - - #include "mmal_picture.h" - -@@ -39,468 +40,814 @@ - #include - #include - --#define MIN_NUM_BUFFERS_IN_TRANSIT 2 -+#define MMAL_DEINTERLACE_NO_QPU "mmal-deinterlace-no-qpu" -+#define MMAL_DEINTERLACE_NO_QPU_TEXT N_("Do not use QPUs for advanced HD deinterlacing.") -+#define MMAL_DEINTERLACE_NO_QPU_LONGTEXT N_("Do not make use of the QPUs to allow higher quality deinterlacing of HD content.") - --#define MMAL_DEINTERLACE_QPU "mmal-deinterlace-adv-qpu" --#define MMAL_DEINTERLACE_QPU_TEXT N_("Use QPUs for advanced HD deinterlacing.") --#define MMAL_DEINTERLACE_QPU_LONGTEXT N_("Make use of the QPUs to allow higher quality deinterlacing of HD content.") -+#define MMAL_DEINTERLACE_ADV "mmal-deinterlace-adv" -+#define MMAL_DEINTERLACE_ADV_TEXT N_("Force advanced deinterlace") -+#define MMAL_DEINTERLACE_ADV_LONGTEXT N_("Force advanced deinterlace") ++ p_filter->p_sys = NULL; ++ free(sys); ++} --static int Open(filter_t *filter); --static void Close(filter_t *filter); -+#define MMAL_DEINTERLACE_FAST "mmal-deinterlace-fast" -+#define MMAL_DEINTERLACE_FAST_TEXT N_("Force fast deinterlace") -+#define MMAL_DEINTERLACE_FAST_LONGTEXT N_("Force fast deinterlace") +- sys->output_format = format; --vlc_module_begin() -- set_shortname(N_("MMAL deinterlace")) -- set_description(N_("MMAL-based deinterlace filter plugin")) -- set_capability("video filter", 0) -- set_category(CAT_VIDEO) -- set_subcategory(SUBCAT_VIDEO_VFILTER) -- set_callbacks(Open, Close) -- add_shortcut("deinterlace") -- add_bool(MMAL_DEINTERLACE_QPU, false, MMAL_DEINTERLACE_QPU_TEXT, -- MMAL_DEINTERLACE_QPU_LONGTEXT, true); --vlc_module_end() -+#define MMAL_DEINTERLACE_NONE "mmal-deinterlace-none" -+#define MMAL_DEINTERLACE_NONE_TEXT N_("Force no deinterlace") -+#define MMAL_DEINTERLACE_NONE_LONGTEXT N_("Force no interlace. Simply strips off the interlace markers and passes the frame straight through. "\ -+ "This is the default for > SD if < 96M gpu-mem") +- mmal_buffer_header_release(buffer); ++static inline MMAL_FOURCC_T filter_enc_in(const video_format_t * const fmt) ++{ ++ if (hw_mmal_chroma_is_mmal(fmt->i_chroma)) ++ return vlc_to_mmal_video_fourcc(fmt); + -+#define MMAL_DEINTERLACE_HALF_RATE "mmal-deinterlace-half-rate" -+#define MMAL_DEINTERLACE_HALF_RATE_TEXT N_("Halve output framerate") -+#define MMAL_DEINTERLACE_HALF_RATE_LONGTEXT N_("Halve output framerate. 1 output frame for each pair of interlaced fields input") ++ if (fmt->i_chroma == VLC_CODEC_I420 || ++ fmt->i_chroma == VLC_CODEC_I420_10L) ++ return MMAL_ENCODING_I420; + -+#define MMAL_DEINTERLACE_FULL_RATE "mmal-deinterlace-full-rate" -+#define MMAL_DEINTERLACE_FULL_RATE_TEXT N_("Full output framerate") -+#define MMAL_DEINTERLACE_FULL_RATE_LONGTEXT N_("Full output framerate. 1 output frame for each interlaced field input") - --struct filter_sys_t { ++ return 0; ++} + -+typedef struct filter_sys_t ++static inline MMAL_FOURCC_T filter_enc_out(const video_format_t * const fmt) +{ - MMAL_COMPONENT_T *component; - MMAL_PORT_T *input; - MMAL_PORT_T *output; -+ MMAL_POOL_T *in_pool; ++ const MMAL_FOURCC_T mmes = vlc_to_mmal_video_fourcc(fmt); ++ // Can only copy out single plane stuff currently - this could be fixed! ++ return hw_mmal_chroma_is_mmal(fmt->i_chroma) || mmes != MMAL_ENCODING_I420 ? mmes : 0; ++} + -+ MMAL_QUEUE_T * out_q; + -+ // Bind this lot somehow into ppr???? -+ bool is_cma; -+ cma_buf_pool_t * cma_out_pool; -+ MMAL_POOL_T * out_pool; ++static int OpenConverter(vlc_object_t * obj) ++{ ++ filter_t * const p_filter = (filter_t *)obj; ++ int ret = VLC_EGENERIC; ++ filter_sys_t *sys; ++ MMAL_STATUS_T status = 0; ++ MMAL_FOURCC_T enc_out = filter_enc_out(&p_filter->fmt_out.video); ++ const MMAL_FOURCC_T enc_in = filter_enc_in(&p_filter->fmt_in.video); ++ bool use_resizer; ++ bool use_isp; ++ int gpu_mem; + -+ hw_mmal_port_pool_ref_t *out_ppr; ++ // At least in principle we should deal with any mmal format as input ++ if (enc_in == 0 || enc_out == 0) ++ return VLC_EGENERIC; + -+ bool half_rate; -+ bool use_qpu; -+ bool use_fast; -+ bool use_passthrough; -+ unsigned int seq_in; // Seq of next frame to submit (1-15) [Init=1] -+ unsigned int seq_out; // Seq of last frame received (1-15) [Init=15] - -- MMAL_QUEUE_T *filtered_pictures; -- vlc_sem_t sem; -+ vcsm_init_type_t vcsm_init_type; - -- atomic_bool started; -+} filter_sys_t; - -- /* statistics */ -- int output_in_transit; -- int input_in_transit; --}; -- --static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); --static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); --static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); --static picture_t *deinterlace(filter_t *filter, picture_t *picture); --static void flush(filter_t *filter); - - #define MMAL_COMPONENT_DEFAULT_DEINTERLACE "vc.ril.image_fx" - --static int Open(filter_t *filter) -+#define TRACE_ALL 0 ++ // Can't transform ++ if (p_filter->fmt_in.video.orientation != p_filter->fmt_out.video.orientation) ++ return VLC_EGENERIC; + ++ use_resizer = var_InheritBool(p_filter, MMAL_RESIZE_NAME); ++ use_isp = var_InheritBool(p_filter, MMAL_ISP_NAME); + ++retry: ++ // ** Make more generic by checking supported encs ++ // ++ // Must use ISP - HVS can't do this, nor can resizer ++ if (enc_in == MMAL_ENCODING_YUVUV64_10) { ++ // If resizer selected then just give up ++ if (use_resizer) ++ return VLC_EGENERIC; ++ // otherwise downgrade HVS to ISP ++ use_isp = true; ++ } ++ // HVS can't do I420 ++ if (enc_out == MMAL_ENCODING_I420) { ++ use_isp = true; ++ } ++ // Only HVS can deal with SAND30 ++ if (enc_in == MMAL_ENCODING_YUV10_COL) { ++ if (use_isp || use_resizer) ++ return VLC_EGENERIC; ++ } + -+// Buffer attached to pic on success, is still valid on failure -+static picture_t * di_alloc_opaque(filter_t * const p_filter, MMAL_BUFFER_HEADER_T * const buf) - { -- int32_t frame_duration = filter->fmt_in.video.i_frame_rate != 0 ? -- (int64_t)1000000 * filter->fmt_in.video.i_frame_rate_base / -- filter->fmt_in.video.i_frame_rate : 0; -- bool use_qpu = var_InheritBool(filter, MMAL_DEINTERLACE_QPU); -+ filter_sys_t *const filter_sys = p_filter->p_sys; -+ picture_t * const pic = filter_NewPicture(p_filter); - -- MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = { -- { MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, sizeof(imfx_param) }, -- MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV, -- 4, -- { 3, frame_duration, 0, use_qpu } -- }; -+ if (pic == NULL) -+ goto fail1; - -- int ret = VLC_SUCCESS; -- MMAL_STATUS_T status; -- filter_sys_t *sys; -+ if (buf->length == 0) { -+ msg_Err(p_filter, "%s: Empty buffer", __func__); -+ goto fail2; ++ ++ if (use_resizer) { ++ // use resizer overrides use_isp ++ use_isp = false; + } - -- msg_Dbg(filter, "Try to open mmal_deinterlace filter. frame_duration: %d, QPU %s!", -- frame_duration, use_qpu ? "used" : "unused"); -+ if ((pic->context = hw_mmal_gen_context(buf, filter_sys->out_ppr)) == NULL) -+ goto fail2; - -- if (filter->fmt_in.video.i_chroma != VLC_CODEC_MMAL_OPAQUE) -- return VLC_EGENERIC; -+ buf_to_pic_copy_props(pic, buf); - -- if (filter->fmt_out.video.i_chroma != VLC_CODEC_MMAL_OPAQUE) -- return VLC_EGENERIC; -+#if TRACE_ALL -+ msg_Dbg(p_filter, "pic: prog=%d, tff=%d, date=%lld", pic->b_progressive, pic->b_top_field_first, (long long)pic->date); -+#endif - -- sys = calloc(1, sizeof(filter_sys_t)); -- if (!sys) -- return VLC_ENOMEM; -- filter->p_sys = sys; -+ return pic; - -- bcm_host_init(); -+fail2: -+ picture_Release(pic); -+fail1: -+// mmal_buffer_header_release(buf); -+ return NULL; -+} - -- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_DEINTERLACE, &sys->component); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)", -- MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -- } -+static void di_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -+{ -+#if TRACE_ALL -+ pic_ctx_mmal_t * ctx = buffer->user_data; -+// filter_sys_t *const sys = ((filter_t *)port->userdata)->p_sys; + -+ msg_Dbg((filter_t *)port->userdata, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buffer->cmd, ctx, buffer, -+ buffer->flags, (long long)buffer->pts); -+#else -+ VLC_UNUSED(port); -+#endif - -- status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)", -- MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -- } -+ mmal_buffer_header_release(buffer); - -- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)filter; -- status = mmal_port_enable(sys->component->control, control_port_cb); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to enable control port %s (status=%"PRIx32" %s)", -- sys->component->control->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+#if TRACE_ALL -+ msg_Dbg((filter_t *)port->userdata, ">>> %s", __func__); -+#endif -+} ++ // Check we have a sliced version of the fourcc if we want the resizer ++ if (use_resizer && ++ (enc_out = pic_to_slice_mmal_fourcc(enc_out)) == 0) { ++ return VLC_EGENERIC; ++ } + -+static void di_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) -+{ -+ if (buf->cmd == 0 && buf->length != 0) -+ { -+ // The filter structure etc. should always exist if we have contents -+ // but might not on later flushes as we shut down -+ filter_t * const p_filter = (filter_t *)port->userdata; -+ filter_sys_t * const sys = p_filter->p_sys; ++ gpu_mem = hw_mmal_get_gpu_mem(); + -+#if TRACE_ALL -+ msg_Dbg(p_filter, "<<< %s: cmd=%d; flags=%#x, pts=%lld", __func__, buf->cmd, buf->flags, (long long) buf->pts); -+#endif -+ mmal_queue_put(sys->out_q, buf); -+#if TRACE_ALL -+ msg_Dbg(p_filter, ">>> %s: out Q len=%d", __func__, mmal_queue_length(sys->out_q)); -+#endif -+ return; - } - -- sys->input = sys->component->input[0]; -- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)filter; -- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) -- sys->input->format->encoding = MMAL_ENCODING_OPAQUE; -- sys->input->format->es->video.width = filter->fmt_in.video.i_width; -- sys->input->format->es->video.height = filter->fmt_in.video.i_height; -- sys->input->format->es->video.crop.x = 0; -- sys->input->format->es->video.crop.y = 0; -- sys->input->format->es->video.crop.width = filter->fmt_in.video.i_width; -- sys->input->format->es->video.crop.height = filter->fmt_in.video.i_height; -- sys->input->format->es->video.par.num = filter->fmt_in.video.i_sar_num; -- sys->input->format->es->video.par.den = filter->fmt_in.video.i_sar_den; -+ mmal_buffer_header_reset(buf); // User data stays intact so release will kill pic -+ mmal_buffer_header_release(buf); -+} - -- es_format_Copy(&filter->fmt_out, &filter->fmt_in); -- filter->fmt_out.video.i_frame_rate *= 2; - -- status = mmal_port_format_commit(sys->input); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -- } -- sys->input->buffer_size = sys->input->buffer_size_recommended; -- sys->input->buffer_num = sys->input->buffer_num_recommended; - -- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) { -- MMAL_PARAMETER_BOOLEAN_T zero_copy = { -- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, -- 1 -- }; -+static MMAL_STATUS_T fill_output_from_q(filter_t * const p_filter, filter_sys_t * const sys, MMAL_QUEUE_T * const q) -+{ -+ MMAL_BUFFER_HEADER_T * out_buf; - -- status = mmal_port_parameter_set(sys->input, &zero_copy.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- goto out; -+ while ((out_buf = mmal_queue_get(q)) != NULL) + { -+ MMAL_STATUS_T err; -+ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Send buffer to output failed"); -+ mmal_queue_put_back(q, out_buf); -+ return err; - } - } -+ return MMAL_SUCCESS; -+} - -- status = mmal_port_enable(sys->input, input_port_cb); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -- } -+// Output buffers may contain a pic ref on error or flush -+// Free it -+static MMAL_BOOL_T out_buffer_pre_release_cb(MMAL_BUFFER_HEADER_T *header, void *userdata) -+{ -+ VLC_UNUSED(userdata); - -- sys->output = sys->component->output[0]; -- sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)filter; -- mmal_format_full_copy(sys->output->format, sys->input->format); -+ cma_buf_t * const cb = header->user_data; -+ header->user_data = NULL; -+ cma_buf_unref(cb); // Copes fine with NULL - -- status = mmal_port_format_commit(sys->output); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+ return MMAL_FALSE; -+} -+ -+static inline unsigned int seq_inc(unsigned int x) -+{ -+ return x + 1 >= 16 ? 1 : x + 1; -+} -+ -+static inline unsigned int seq_delta(unsigned int sseq, unsigned int fseq) -+{ -+ return fseq == 0 ? 0 : fseq <= sseq ? sseq - fseq : 15 - (fseq - sseq); -+} -+ -+static picture_t *deinterlace(filter_t * p_filter, picture_t * p_pic) -+{ -+ filter_sys_t * const sys = p_filter->p_sys; -+ picture_t *ret_pics = NULL; -+ MMAL_STATUS_T err; -+ MMAL_BUFFER_HEADER_T * out_buf = NULL; -+ -+#if TRACE_ALL -+ msg_Dbg(p_filter, "<<< %s", __func__); -+#endif ++ char dbuf0[5], dbuf1[5], dbuf2[5], dbuf3[5]; ++ msg_Dbg(p_filter, "%s: (%s) %s/%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s/%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d (gpu=%d)", __func__, ++ use_resizer ? "resize" : use_isp ? "isp" : "hvs", ++ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), str_fourcc(dbuf2, enc_in), ++ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, ++ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, ++ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, ++ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den, ++ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), str_fourcc(dbuf3, enc_out), ++ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, ++ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, ++ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height, ++ p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask, ++ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den, ++ gpu_mem); ++ } + -+ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic)) -+ { -+ // ****** Breaks on opaque (at least) ++ sys = calloc(1, sizeof(filter_sys_t)); ++ if (!sys) { ++ ret = VLC_ENOMEM; ++ goto fail; ++ } ++ p_filter->p_sys = sys; + -+ if (sys->input->is_enabled) -+ mmal_port_disable(sys->input); -+#if 0 -+ if (sys->output->is_enabled) -+ mmal_port_disable(sys->output); ++ // Init stuff the we destroy unconditionaly in Close first ++ vlc_mutex_init(&sys->lock); ++ vlc_sem_init(&sys->sem, 0); ++ sys->err_stream = MMAL_SUCCESS; ++ pic_fifo_init(&sys->ret_pics); ++ pic_fifo_init(&sys->slice.pics); + -+ mmal_format_full_copy(sys->output->format, sys->input->format); -+ mmal_port_format_commit(sys->output); -+ sys->output->buffer_num = 30; -+ sys->output->buffer_size = sys->input->buffer_size_recommended; -+ mmal_port_enable(sys->output, di_output_port_cb); -+#endif -+ if (mmal_port_format_commit(sys->input) != MMAL_SUCCESS) -+ msg_Err(p_filter, "Failed to update pic format"); -+ sys->input->buffer_num = 30; -+ sys->input->buffer_size = sys->input->buffer_size_recommended; -+ mmal_log_dump_format(sys->input->format); -+ } ++ sys->needs_copy_in = !hw_mmal_chroma_is_mmal(p_filter->fmt_in.video.i_chroma); ++ sys->in_port_cb_fn = conv_input_port_cb; + -+ // Reenable stuff if the last thing we did was flush -+ // Output should always be enabled -+ if (!sys->input->is_enabled && -+ (err = mmal_port_enable(sys->input, di_input_port_cb)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Input port reenable failed"); ++ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { ++ msg_Err(p_filter, "VCSM init failed"); + goto fail; + } + -+ if (!sys->is_cma) -+ { -+ // Fill output from anything that has turned up in pool Q -+ if (hw_mmal_port_pool_ref_fill(sys->out_ppr) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Out port fill fail"); -+ goto fail; -+ } ++ if (use_resizer) { ++ sys->resizer_type = FILTER_RESIZER_RESIZER; ++ sys->is_sliced = true; ++ sys->component_name = MMAL_COMPONENT_DEFAULT_RESIZER; ++ sys->out_port_cb_fn = slice_output_port_cb; ++ } ++ else if (use_isp) { ++ sys->resizer_type = FILTER_RESIZER_ISP; ++ sys->is_sliced = false; // Copy directly into filter picture ++ sys->component_name = MMAL_COMPONENT_ISP_RESIZER; ++ sys->out_port_cb_fn = conv_output_port_cb; + } else { +- mmal_buffer_header_release(buffer); ++ sys->resizer_type = FILTER_RESIZER_HVS; ++ sys->is_sliced = false; // Copy directly into filter picture ++ sys->component_name = MMAL_COMPONENT_HVS; ++ sys->out_port_cb_fn = conv_output_port_cb; } -+ else -+ { -+ // We are expecting one in - one out so simply wedge a new bufer -+ // into the output port. Flow control will happen on cma alloc. -+ -+ if ((out_buf = mmal_queue_get(sys->out_pool->queue)) == NULL) -+ { -+ // Should never happen -+ msg_Err(p_filter, "Failed to get output buffer"); -+ goto fail; -+ } -+ mmal_buffer_header_reset(out_buf); - -- sys->output->buffer_num = 3; -+ // Attach cma_buf to the buffer & ensure it is freed when the buffer is released -+ // On a good send callback the pic will be extracted to avoid this -+ mmal_buffer_header_pre_release_cb_set(out_buf, out_buffer_pre_release_cb, p_filter); ++ sys->is_cma = is_cma_buf_pic_chroma(p_filter->fmt_out.video.i_chroma); + -+ cma_buf_t * const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, sys->output->buffer_size); -+ if ((out_buf->user_data = cb) == NULL) // Check & attach cb to buf -+ { -+ char dbuf0[5]; -+ msg_Err(p_filter, "Failed to alloc CMA buf: fmt=%s, size=%d", -+ str_fourcc(dbuf0, p_pic->format.i_chroma), -+ sys->output->buffer_size); -+ goto fail; ++ status = mmal_component_create(sys->component_name, &sys->component); ++ if (status != MMAL_SUCCESS) { ++ if (!use_isp && !use_resizer) { ++ msg_Warn(p_filter, "Failed to rcreate HVS resizer - retrying with ISP"); ++ CloseConverter(obj); ++ use_isp = true; ++ goto retry; + } -+ const unsigned int vc_h = cma_buf_vc_handle(cb); // Cannot coerce without going via variable -+ out_buf->data = (uint8_t *)vc_h; -+ out_buf->alloc_size = sys->output->buffer_size; ++ msg_Err(p_filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)", ++ MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ sys->output = sys->component->output[0]; ++ sys->input = sys->component->input[0]; + -+#if TRACE_ALL -+ msg_Dbg(p_filter, "Out buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d, pts=%lld", -+ p_pic, out_buf->data, out_buf->user_data, out_buf->flags, -+ out_buf->length, out_buf->alloc_size, (long long)out_buf->pts); -+#endif - -- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) { -- MMAL_PARAMETER_UINT32_T extra_buffers = { -- { MMAL_PARAMETER_EXTRA_BUFFERS, sizeof(MMAL_PARAMETER_UINT32_T) }, -- 5 -- }; -- status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", -- status, mmal_status_to_string(status)); -- goto out; -+ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Send buffer to output failed"); -+ goto fail; - } -+ out_buf = NULL; ++ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter; ++ status = mmal_port_enable(sys->component->control, conv_control_port_cb); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(p_filter, "Failed to enable control port %s (status=%"PRIx32" %s)", ++ sys->component->control->name, status, mmal_status_to_string(status)); ++ goto fail; + } - -- MMAL_PARAMETER_BOOLEAN_T zero_copy = { -- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, -- 1 -- }; -+ // Stuff into input -+ // We assume the BH is already set up with values reflecting pic date etc. ++ ++ if (sys->needs_copy_in && ++ (sys->cma_in_pool = cma_buf_pool_new(2, 2, true, "conv-copy-in")) == NULL) + { -+ MMAL_BUFFER_HEADER_T * const pic_buf = hw_mmal_pic_buf_replicated(p_pic, sys->in_pool); ++ msg_Err(p_filter, "Failed to allocate input CMA pool"); ++ goto fail; ++ } + -+ if (pic_buf == NULL) -+ { -+ msg_Err(p_filter, "Pic has not attached buffer"); -+ goto fail; -+ } - -- status = mmal_port_parameter_set(sys->output, &zero_copy.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", -- sys->output->name, status, mmal_status_to_string(status)); -- goto out; -+ picture_Release(p_pic); ++ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter; ++ sys->input->format->type = MMAL_ES_TYPE_VIDEO; ++ sys->input->format->encoding = enc_in; ++ sys->input->format->encoding_variant = MMAL_ENCODING_I420; ++ hw_mmal_vlc_fmt_to_mmal_fmt(sys->input->format, &p_filter->fmt_in.video); ++ port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, 1); + -+ // Add a sequence to the flags so we can track what we have actually -+ // deinterlaced -+ pic_buf->flags = (pic_buf->flags & ~(0xfU * MMAL_BUFFER_HEADER_FLAG_USER0)) | (sys->seq_in * (MMAL_BUFFER_HEADER_FLAG_USER0)); -+ sys->seq_in = seq_inc(sys->seq_in); ++ mmal_log_dump_format(sys->input->format); + -+ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Send buffer to input failed"); -+ mmal_buffer_header_release(pic_buf); -+ goto fail; - } - } - -- status = mmal_port_enable(sys->output, output_port_cb); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to enable output port %s (status=%"PRIx32" %s)", -- sys->output->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+ // Return anything that is in the out Q -+ { -+ picture_t ** pp_pic = &ret_pics; ++ status = mmal_port_format_commit(sys->input); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(p_filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)", ++ sys->input->name, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ sys->input->buffer_size = sys->input->buffer_size_recommended; ++ sys->input->buffer_num = NUM_DECODER_BUFFER_HEADERS; + -+ // Advanced di has a 3 frame latency, so if the seq delta is greater -+ // than that then we are expecting at least two frames of output. Wait -+ // for one of those. -+ // seq_in is seq of the next frame we are going to submit (1-15, no 0) -+ // seq_out is last frame we removed from Q -+ // So after 4 frames sent (1st time we want to wait), 0 rx seq_in=5, seq_out=15, delta=5 ++ if ((status = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS) ++ goto fail; + -+ while ((out_buf = (seq_delta(sys->seq_in, sys->seq_out) >= 5 ? mmal_queue_timedwait(sys->out_q, 1000) : mmal_queue_get(sys->out_q))) != NULL) -+ { -+ const unsigned int seq_out = (out_buf->flags / MMAL_BUFFER_HEADER_FLAG_USER0) & 0xf; -+ int rv; ++ port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, sys->is_sliced || sys->is_cma); + -+ picture_t * out_pic; ++ status = mmal_component_enable(sys->component); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(p_filter, "Failed to enable component %s (status=%"PRIx32" %s)", ++ sys->component->name, status, mmal_status_to_string(status)); ++ goto fail; ++ } + -+ if (sys->is_cma) ++ if ((sys->in_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) ++ { ++ msg_Err(p_filter, "Failed to create input pool"); ++ goto fail; ++ } ++ ++ if (sys->resizer_type == FILTER_RESIZER_HVS) ++ { ++ unsigned int i; ++ for (i = 0; i != SUBS_MAX; ++i) { ++ if (hw_mmal_subpic_open(VLC_OBJECT(p_filter), sys->subs + i, sys->component->input[i + 1], -1, i + 1) != MMAL_SUCCESS) + { -+ // Alloc pic -+ if ((out_pic = filter_NewPicture(p_filter)) == NULL) -+ { -+ // Can't alloc pic - just stop extraction -+ mmal_queue_put_back(sys->out_q, out_buf); -+ out_buf = NULL; -+ msg_Warn(p_filter, "Failed to alloc new filter output pic"); -+ break; -+ } ++ msg_Err(p_filter, "Failed to open subpic %d", i); ++ goto fail; ++ } ++ } ++ } + -+ // Extract cma_buf from buf & attach to pic -+ cma_buf_t * const cb = (cma_buf_t *)out_buf->user_data; -+ if ((rv = cma_buf_pic_attach(cb, out_pic)) != VLC_SUCCESS) -+ { -+ char dbuf0[5]; -+ msg_Err(p_filter, "Failed to attach CMA to pic: fmt=%s err=%d", -+ str_fourcc(dbuf0, out_pic->format.i_chroma), -+ rv); -+ // cb still attached to buffer and will be freed with it -+ goto fail; -+ } -+ out_buf->user_data = NULL; ++ p_filter->pf_video_filter = conv_filter; ++ p_filter->pf_flush = conv_flush; ++ // video_drain NIF in filter structure + -+ buf_to_pic_copy_props(out_pic, out_buf); ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s: ok", __func__); ++#endif + -+ // Set pic data pointers from buf aux info now it has it -+ if ((rv = cma_pic_set_data(out_pic, sys->output->format, out_buf)) != VLC_SUCCESS) -+ { -+ char dbuf0[5]; -+ msg_Err(p_filter, "Failed to set data: fmt=%s, rv=%d", -+ str_fourcc(dbuf0, sys->output->format->encoding), -+ rv); -+ } ++ return VLC_SUCCESS; + -+ out_buf->user_data = NULL; // Responsability for this pic no longer with buffer -+ mmal_buffer_header_release(out_buf); -+ } -+ else -+ { -+ out_pic = di_alloc_opaque(p_filter, out_buf); ++fail: ++ CloseConverter(obj); + -+ if (out_pic == NULL) { -+ msg_Warn(p_filter, "Failed to alloc new filter output pic"); -+ mmal_queue_put_back(sys->out_q, out_buf); // Wedge buf back into Q in the hope we can alloc a pic later -+ out_buf = NULL; -+ break; -+ } -+ } -+ out_buf = NULL; // Now attached to pic or recycled ++ if (!use_resizer && status == MMAL_ENOMEM) { ++ use_resizer = true; ++ msg_Warn(p_filter, "Lack of memory to use HVS/ISP: trying resizer"); ++ goto retry; ++ } + +#if TRACE_ALL -+ msg_Dbg(p_filter, "-- %s: Q pic=%p: seq_in=%d, seq_out=%d, delta=%d", __func__, out_pic, sys->seq_in, seq_out, seq_delta(sys->seq_in, seq_out)); ++ msg_Dbg(p_filter, ">>> %s: FAIL: %d", __func__, ret); +#endif ++ return ret; ++} + -+ *pp_pic = out_pic; -+ pp_pic = &out_pic->p_next; ++#if OPT_TO_FROM_ZC ++//---------------------------------------------------------------------------- ++// ++// Simple copy in to ZC + -+ // Ignore 0 seqs -+ // Don't think these should actually happen -+ if (seq_out != 0) -+ sys->seq_out = seq_out; -+ } ++typedef struct to_zc_sys_s { ++ vcsm_init_type_t vcsm_init_type; ++ cma_buf_pool_t * cma_out_pool; ++} to_zc_sys_t; + -+ // Crash on lockup -+ assert(ret_pics != NULL || seq_delta(sys->seq_in, sys->seq_out) < 5); - } - -- status = mmal_component_enable(sys->component); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to enable component %s (status=%"PRIx32" %s)", -- sys->component->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+#if TRACE_ALL -+ msg_Dbg(p_filter, ">>> %s: pic=%p", __func__, ret_pics); -+#endif + -+ return ret_pics; ++static size_t buf_alloc_size(const vlc_fourcc_t i_chroma, const unsigned int width, const unsigned int height) ++{ ++ const unsigned int pels = width * height; + -+fail: -+ if (out_buf != NULL) -+ mmal_buffer_header_release(out_buf); -+ picture_Release(p_pic); -+ return NULL; ++ switch (i_chroma) ++ { ++ case VLC_CODEC_MMAL_ZC_RGB32: ++ return pels * 4; ++ case VLC_CODEC_MMAL_ZC_I420: ++ return pels * 3 / 2; ++ default: ++ break; ++ } ++ return 0; +} + -+static void di_flush(filter_t *p_filter) -+{ -+ filter_sys_t * const sys = p_filter->p_sys; + ++static picture_t * ++to_zc_filter(filter_t *p_filter, picture_t *in_pic) ++{ ++ to_zc_sys_t * const sys = (to_zc_sys_t *)p_filter->p_sys; +#if TRACE_ALL + msg_Dbg(p_filter, "<<< %s", __func__); +#endif + -+ if (sys->input != NULL && sys->input->is_enabled) -+ mmal_port_disable(sys->input); ++ assert(p_filter->fmt_out.video.i_chroma == VLC_CODEC_MMAL_ZC_I420); + -+ if (sys->output != NULL && sys->output->is_enabled) -+ { -+ if (sys->is_cma) -+ { -+ MMAL_BUFFER_HEADER_T * buf; -+ mmal_port_disable(sys->output); -+ while ((buf = mmal_queue_get(sys->out_q)) != NULL) -+ mmal_buffer_header_release(buf); -+ } -+ else -+ { -+ // Wedge anything we've got into the output port as that will free the underlying buffers -+ fill_output_from_q(p_filter, sys, sys->out_q); ++ picture_t * const out_pic = filter_NewPicture(p_filter); ++ if (out_pic == NULL) ++ goto fail0; + -+ mmal_port_disable(sys->output); ++ MMAL_ES_SPECIFIC_FORMAT_T mm_vfmt = {.video={0}}; ++ MMAL_ES_FORMAT_T mm_esfmt = { ++ .encoding = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video), ++ .es = &mm_vfmt}; + -+ // If that dumped anything real into the out_q then have another go -+ if (mmal_queue_length(sys->out_q) != 0) -+ { -+ mmal_port_enable(sys->output, di_output_port_cb); -+ fill_output_from_q(p_filter, sys, sys->out_q); -+ mmal_port_disable(sys->output); -+ // Out q should now be empty & should remain so until the input is reenabled -+ } -+ } -+ mmal_port_enable(sys->output, di_output_port_cb); ++ hw_mmal_vlc_fmt_to_mmal_fmt(&mm_esfmt, &p_filter->fmt_out.video); + -+ // Leaving the input disabled is fine - but we want to leave the output enabled -+ // so we can retrieve buffers that are still bound to pictures - } - -- sys->filtered_pictures = mmal_queue_create(); -+ sys->seq_in = 1; -+ sys->seq_out = 15; - -- filter->pf_video_filter = deinterlace; -- filter->pf_flush = flush; -+#if TRACE_ALL -+ msg_Dbg(p_filter, ">>> %s", __func__); -+#endif -+} - -- vlc_sem_init(&sys->sem, 0); - --out: -- if (ret != VLC_SUCCESS) -- Close(filter); -+static void pass_flush(filter_t *p_filter) ++ const size_t buf_alloc = buf_alloc_size(p_filter->fmt_out.video.i_chroma, ++ mm_vfmt.video.width, mm_vfmt.video.height); ++ if (buf_alloc == 0) ++ goto fail1; ++ cma_buf_t *const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, buf_alloc); ++ if (cb == NULL) ++ goto fail1; ++ ++ if (cma_buf_pic_attach(cb, out_pic) != VLC_SUCCESS) ++ goto fail2; ++ cma_pic_set_data(out_pic, &mm_esfmt, NULL); ++ ++ hw_mmal_copy_pic_to_buf(cma_buf_addr(cb), NULL, &mm_esfmt, in_pic); ++ ++ // Copy pic properties ++ out_pic->date = in_pic->date; ++ out_pic->b_force = in_pic->b_force; ++ out_pic->b_progressive = in_pic->b_progressive; ++ out_pic->b_top_field_first = in_pic->b_top_field_first; ++ out_pic->i_nb_fields = in_pic->i_nb_fields; ++ ++ picture_Release(in_pic); ++ ++ return out_pic; ++ ++fail2: ++ cma_buf_unref(cb); ++fail1: ++ picture_Release(out_pic); ++fail0: ++ picture_Release(in_pic); ++ return NULL; + } ++ ++static void to_zc_flush(filter_t * p_filter) +{ -+ // Nothing to do + VLC_UNUSED(p_filter); +} - -- return ret; -+static picture_t * pass_deinterlace(filter_t * p_filter, picture_t * p_pic) ++ ++static void CloseConverterToZc(vlc_object_t * obj) +{ -+ VLC_UNUSED(p_filter); ++ filter_t * const p_filter = (filter_t *)obj; ++ to_zc_sys_t * const sys = (to_zc_sys_t *)p_filter->p_sys; + -+ p_pic->b_progressive = true; -+ return p_pic; - } - --static void Close(filter_t *filter) ++ if (sys == NULL) ++ return; + -+static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) - { -- filter_sys_t *sys = filter->p_sys; -- MMAL_BUFFER_HEADER_T *buffer; -+ filter_t *filter = (filter_t *)port->userdata; -+ MMAL_STATUS_T status; - -- if (!sys) -+ if (buffer->cmd == MMAL_EVENT_ERROR) { -+ status = *(uint32_t *)buffer->data; -+ msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status, -+ mmal_status_to_string(status)); ++ p_filter->p_sys = NULL; ++ ++ cma_buf_pool_deletez(&sys->cma_out_pool); ++ cma_vcsm_exit(sys->vcsm_init_type); ++ ++ free(sys); ++} ++ ++static bool to_zc_validate_fmt(const video_format_t * const f_in, const video_format_t * const f_out) ++{ ++ if (!((f_in->i_chroma == VLC_CODEC_I420 || f_in->i_chroma == VLC_CODEC_I420_10L) && ++ f_out->i_chroma == VLC_CODEC_MMAL_ZC_I420)) ++ { ++ return false; ++ } ++ if (f_in->i_height != f_out->i_height || ++ f_in->i_width != f_out->i_width) ++ { ++ return false; + } + -+ mmal_buffer_header_reset(buffer); -+ mmal_buffer_header_release(buffer); ++ return true; +} + -+static void CloseMmalDeinterlace(filter_t *filter) ++static int OpenConverterToZc(vlc_object_t * obj) +{ -+ filter_sys_t * const sys = filter->p_sys; ++ int ret = VLC_EGENERIC; ++ filter_t * const p_filter = (filter_t *)obj; + -+#if TRACE_ALL -+ msg_Dbg(filter, "<<< %s", __func__); -+#endif ++ if (!to_zc_validate_fmt(&p_filter->fmt_in.video, &p_filter->fmt_out.video)) ++ goto fail; + -+ if (sys == NULL) - return; - -- if (sys->component && sys->component->control->is_enabled) -- mmal_port_disable(sys->component->control); -+ if (sys->use_passthrough) + { -+ free(sys); -+ return; ++ char dbuf0[5], dbuf1[5]; ++ msg_Dbg(p_filter, "%s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d", __func__, ++ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), ++ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, ++ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, ++ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, ++ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den, ++ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), ++ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, ++ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, ++ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height, ++ p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask, ++ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den); + } - -- if (sys->input && sys->input->is_enabled) -- mmal_port_disable(sys->input); -+ di_flush(filter); - -- if (sys->output && sys->output->is_enabled) -- mmal_port_disable(sys->output); -+ if (sys->component && sys->component->control->is_enabled) -+ mmal_port_disable(sys->component->control); - - if (sys->component && sys->component->is_enabled) - mmal_component_disable(sys->component); - -- while ((buffer = mmal_queue_get(sys->filtered_pictures))) { -- picture_t *pic = (picture_t *)buffer->user_data; -- picture_Release(pic); -+ if (sys->in_pool != NULL) -+ mmal_pool_destroy(sys->in_pool); -+ -+ hw_mmal_port_pool_ref_release(sys->out_ppr, false); -+ // Once we exit filter & sys are invalid so mark as such -+ if (sys->output != NULL) -+ sys->output->userdata = NULL; + -+ if (sys->is_cma) -+ { -+ if (sys->output && sys->output->is_enabled) -+ mmal_port_disable(sys->output); ++ to_zc_sys_t * const sys = calloc(1, sizeof(*sys)); ++ if (!sys) { ++ ret = VLC_ENOMEM; ++ goto fail; ++ } ++ p_filter->p_sys = (filter_sys_t *)sys; + -+ cma_buf_pool_deletez(&sys->cma_out_pool); ++ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { ++ msg_Err(p_filter, "VCSM init failed"); ++ goto fail; ++ } + -+ if (sys->out_pool != NULL) -+ mmal_pool_destroy(sys->out_pool); - } - -- if (sys->filtered_pictures) -- mmal_queue_destroy(sys->filtered_pictures); -+ if (sys->out_q != NULL) -+ mmal_queue_destroy(sys->out_q); - - if (sys->component) - mmal_component_release(sys->component); - -- vlc_sem_destroy(&sys->sem); -+ cma_vcsm_exit(sys->vcsm_init_type); -+ - free(sys); -+} ++ if ((sys->cma_out_pool = cma_buf_pool_new(5, 5, true, "conv-to-zc")) == NULL) ++ { ++ msg_Err(p_filter, "Failed to allocate input CMA pool"); ++ goto fail; ++ } + - -- bcm_host_deinit(); -+static bool is_fmt_valid_in(const vlc_fourcc_t fmt) -+{ -+ return fmt == VLC_CODEC_MMAL_OPAQUE || -+ fmt == VLC_CODEC_MMAL_ZC_I420 || -+ fmt == VLC_CODEC_MMAL_ZC_SAND8; - } - --static int send_output_buffer(filter_t *filter) -+static int OpenMmalDeinterlace(filter_t *filter) - { -- filter_sys_t *sys = filter->p_sys; -- MMAL_BUFFER_HEADER_T *buffer; -+ int32_t frame_duration = filter->fmt_in.video.i_frame_rate != 0 ? -+ CLOCK_FREQ * filter->fmt_in.video.i_frame_rate_base / -+ filter->fmt_in.video.i_frame_rate : 0; ++ p_filter->pf_video_filter = to_zc_filter; ++ p_filter->pf_flush = to_zc_flush; ++ return VLC_SUCCESS; + -+ int ret = VLC_EGENERIC; - MMAL_STATUS_T status; -- picture_t *picture; -- int ret = 0; -+ filter_sys_t *sys; ++fail: ++ CloseConverterToZc(obj); ++ return ret; ++} + -+ msg_Dbg(filter, "<<< %s", __func__); ++//---------------------------------------------------------------------------- ++// ++// Simple "copy" from ZC + -+ if (!is_fmt_valid_in(filter->fmt_in.video.i_chroma) || -+ filter->fmt_out.video.i_chroma != filter->fmt_in.video.i_chroma) -+ return VLC_EGENERIC; - -- if (!sys->output->is_enabled) { -- ret = VLC_EGENERIC; -- goto out; -+ sys = calloc(1, sizeof(filter_sys_t)); -+ if (!sys) -+ return VLC_ENOMEM; -+ filter->p_sys = sys; ++static void CloseConverterFromZc(vlc_object_t * obj) ++{ ++ VLC_UNUSED(obj); ++} + -+ sys->seq_in = 1; -+ sys->seq_out = 15; -+ sys->is_cma = is_cma_buf_pic_chroma(filter->fmt_out.video.i_chroma); ++static int OpenConverterFromZc(vlc_object_t * obj) ++{ ++ return VLC_EGENERIC; ++} ++#endif ++//---------------------------------------------------------------------------- + -+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { -+ msg_Err(filter, "VCSM init failed"); -+ goto fail; -+ } ++typedef struct blend_sys_s { ++ vzc_pool_ctl_t * vzc; ++ const picture_t * last_dst; // Not a ref, just a hint that we have a new pic ++ vcsm_init_type_t vcsm_init_type; ++} blend_sys_t; + -+ if (rpi_is_model_pi4()) -+ { -+ sys->half_rate = true; -+ sys->use_qpu = false; -+ sys->use_fast = true; -+ } -+ else ++static void FilterBlendMmal(filter_t *p_filter, ++ picture_t *dst, const picture_t * src, ++ int x_offset, int y_offset, int alpha) ++{ ++ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys; ++#if TRACE_ALL ++ msg_Dbg(p_filter, "%s (%d,%d:%d) pic=%p, pts=%lld, force=%d", __func__, x_offset, y_offset, alpha, src, src->date, src->b_force); ++#endif ++ // If nothing to do then do nothing ++ if (alpha == 0 || ++ src->format.i_visible_height == 0 || ++ src->format.i_visible_width == 0) + { -+ sys->half_rate = false; -+ sys->use_qpu = true; -+ sys->use_fast = false; ++ return; + } -+ sys->use_passthrough = false; + -+ if (filter->fmt_in.video.i_width * filter->fmt_in.video.i_height > 768 * 576) ++ if (dst->context == NULL) ++ msg_Err(p_filter, "MMAL pic missing context"); ++ else + { -+ // We get stressed if we have to try too hard - so make life easier -+ sys->half_rate = true; -+ // Also check we actually have enough memory to do this -+ // Memory always comes from GPU if Opaque -+ // Assume we have plenty of memory if it comes from CMA -+ if ((!sys->is_cma || sys->vcsm_init_type == VCSM_INIT_LEGACY) && -+ hw_mmal_get_gpu_mem() < (96 << 20)) -+ { -+ sys->use_passthrough = true; -+ msg_Warn(filter, "Deinterlace bypassed due to lack of GPU memory"); ++ // cast away src const so we can ref it ++ MMAL_BUFFER_HEADER_T *buf = hw_mmal_vzc_buf_from_pic(sys->vzc, (picture_t *)src, ++ &p_filter->fmt_in.video, ++ vis_mmal_rect(&p_filter->fmt_out.video), ++ x_offset, y_offset, ++ alpha, ++ dst != sys->last_dst || !hw_mmal_pic_has_sub_bufs(dst)); ++ if (buf == NULL) { ++ msg_Err(p_filter, "Failed to allocate vzc buffer for subpic"); ++ return; + } - } - -- picture = filter_NewPicture(filter); -- if (!picture) { -- msg_Warn(filter, "Failed to get new picture"); -- ret = -1; -- goto out; -+ if (var_InheritBool(filter, MMAL_DEINTERLACE_NO_QPU)) -+ sys->use_qpu = false; -+ if (var_InheritBool(filter, MMAL_DEINTERLACE_ADV)) -+ { -+ sys->use_fast = false; -+ sys->use_passthrough = false; ++ ++ hw_mmal_pic_sub_buf_add(dst, buf); ++ ++ sys->last_dst = dst; + } -+ if (var_InheritBool(filter, MMAL_DEINTERLACE_FAST)) -+ { -+ sys->use_fast = true; -+ sys->use_passthrough = false; ++} ++ ++static void FlushBlendMmal(filter_t * p_filter) ++{ ++ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys; ++ sys->last_dst = NULL; ++ hw_mmal_vzc_pool_flush(sys->vzc); ++} ++ ++static void CloseBlendMmal(vlc_object_t *object) ++{ ++ filter_t * const p_filter = (filter_t *)object; ++ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys; ++ ++ if (sys != NULL) { ++ p_filter->p_sys = NULL; ++ ++ hw_mmal_vzc_pool_release(sys->vzc); ++ cma_vcsm_exit(sys->vcsm_init_type); ++ free(sys); + } -+ if (var_InheritBool(filter, MMAL_DEINTERLACE_NONE)) -+ sys->use_passthrough = true; -+ if (var_InheritBool(filter, MMAL_DEINTERLACE_FULL_RATE)) -+ sys->half_rate = false; -+ if (var_InheritBool(filter, MMAL_DEINTERLACE_HALF_RATE)) -+ sys->half_rate = true; ++} + -+ if (sys->use_passthrough) ++static int OpenBlendMmal(vlc_object_t *object) ++{ ++ filter_t * const p_filter = (filter_t *)object; ++ const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma; ++ ++ if (!hw_mmal_chroma_is_mmal(vfcc_dst) || ++ !hw_mmal_vzc_subpic_fmt_valid(&p_filter->fmt_in.video)) + { -+ filter->pf_video_filter = pass_deinterlace; -+ filter->pf_flush = pass_flush; -+ // Don't need VCSM - get rid of it now -+ cma_vcsm_exit(sys->vcsm_init_type); -+ sys->vcsm_init_type = VCSM_INIT_NONE; -+ return 0; ++ return VLC_EGENERIC; + } + + { + char dbuf0[5], dbuf1[5]; -+ msg_Dbg(filter, "%s: %s,%dx%d [(%d,%d) %d/%d] -> %s,%dx%d [(%d,%d) %dx%d]: %s %s %s", __func__, -+ str_fourcc(dbuf0, filter->fmt_in.video.i_chroma), -+ filter->fmt_in.video.i_width, filter->fmt_in.video.i_height, -+ filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset, -+ filter->fmt_in.video.i_visible_width, filter->fmt_in.video.i_visible_height, -+ str_fourcc(dbuf1, filter->fmt_out.video.i_chroma), -+ filter->fmt_out.video.i_width, filter->fmt_out.video.i_height, -+ filter->fmt_out.video.i_x_offset, filter->fmt_out.video.i_y_offset, -+ filter->fmt_out.video.i_visible_width, filter->fmt_out.video.i_visible_height, -+ sys->use_qpu ? "QPU" : "VPU", -+ sys->use_fast ? "FAST" : "ADV", -+ sys->use_passthrough ? "PASS" : sys->half_rate ? "HALF" : "FULL"); - } -- picture->format.i_frame_rate = filter->fmt_out.video.i_frame_rate; -- picture->format.i_frame_rate_base = filter->fmt_out.video.i_frame_rate_base; - -- buffer = picture->p_sys->buffer; -- buffer->user_data = picture; -- buffer->cmd = 0; -+ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_DEINTERLACE, &sys->component); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)", -+ MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); -+ goto fail; ++ msg_Dbg(p_filter, "%s: (%s) %s,%dx%d [(%d,%d) %dx%d]->%s,%dx%d [(%d,%d) %dx%d]", __func__, ++ "blend", ++ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, ++ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, ++ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, ++ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, ++ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, ++ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height); + } - -- mmal_picture_lock(picture); ++ + { -+ const MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = { -+ { MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, sizeof(imfx_param) }, -+ sys->use_fast ? -+ MMAL_PARAM_IMAGEFX_DEINTERLACE_FAST : -+ MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV, -+ 4, -+ { 5 /* Frame type: mixed */, frame_duration, sys->half_rate, sys->use_qpu } -+ }; - -- status = mmal_port_send_buffer(sys->output, buffer); -+ status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)", -+ MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); ++ blend_sys_t * const sys = calloc(1, sizeof (*sys)); ++ if (sys == NULL) ++ return VLC_ENOMEM; ++ ++ p_filter->p_sys = (filter_sys_t *)sys; ++ ++ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { ++ msg_Err(p_filter, "VCSM init failed"); + goto fail; + } ++ ++ if ((sys->vzc = hw_mmal_vzc_pool_new()) == NULL) ++ goto fail; + } + -+ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)filter; -+ status = mmal_port_enable(sys->component->control, control_port_cb); - if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to send buffer to output port (status=%"PRIx32" %s)", -- status, mmal_status_to_string(status)); -- mmal_buffer_header_release(buffer); -- picture_Release(picture); -- ret = -1; -- } else { -- atomic_fetch_add(&sys->output_in_transit, 1); -- vlc_sem_post(&sys->sem); -+ msg_Err(filter, "Failed to enable control port %s (status=%"PRIx32" %s)", -+ sys->component->control->name, status, mmal_status_to_string(status)); -+ goto fail; - } - --out: -- return ret; --} -+ sys->input = sys->component->input[0]; -+ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)filter; -+ sys->input->format->encoding = vlc_to_mmal_video_fourcc(&filter->fmt_in.video); -+ hw_mmal_vlc_fmt_to_mmal_fmt(sys->input->format, &filter->fmt_in.video); - --static void fill_output_port(filter_t *filter) --{ -- filter_sys_t *sys = filter->p_sys; -- /* allow at least 2 buffers in transit */ -- unsigned max_buffers_in_transit = __MAX(2, MIN_NUM_BUFFERS_IN_TRANSIT); -- int buffers_available = sys->output->buffer_num - -- atomic_load(&sys->output_in_transit) - -- mmal_queue_length(sys->filtered_pictures); -- int buffers_to_send = max_buffers_in_transit - sys->output_in_transit; -- int i; -+ es_format_Copy(&filter->fmt_out, &filter->fmt_in); -+ if (!sys->half_rate) -+ filter->fmt_out.video.i_frame_rate *= 2; - -- if (buffers_to_send > buffers_available) -- buffers_to_send = buffers_available; -+ status = mmal_port_format_commit(sys->input); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)", -+ sys->input->name, status, mmal_status_to_string(status)); -+ goto fail; ++ p_filter->pf_video_blend = FilterBlendMmal; ++ p_filter->pf_flush = FlushBlendMmal; ++ ++ return VLC_SUCCESS; ++ ++fail: ++ CloseBlendMmal(VLC_OBJECT(p_filter)); ++ return VLC_ENOMEM; ++} ++ ++// --------------------------------------------------------------------------- ++ ++static void FilterBlendNeon(filter_t *p_filter, ++ picture_t *dst_pic, const picture_t * src_pic, ++ int x_offset, int y_offset, int alpha) ++{ ++ const uint8_t * s_data; ++ uint8_t * d_data; ++ const video_format_t *const src_fmt = &p_filter->fmt_in.video; // Format here has different visible params to pic ++ const video_format_t *const dst_fmt = &p_filter->fmt_out.video; ++ int width = src_fmt->i_visible_width; ++ int height = src_fmt->i_visible_height; ++ blend_neon_fn *const blend_fn = (blend_neon_fn * )p_filter->p_sys; ++ ++#if TRACE_ALL ++ msg_Dbg(p_filter, "%s (%d,%d:%d) pic=%p (%dx%d/%dx%d), pts=%lld, force=%d, filter in %dx%d/%dx%d", __func__, x_offset, y_offset, alpha, ++ src_pic, src_pic->format.i_width, src_pic->format.i_height, src_pic->format.i_visible_width, src_pic->format.i_visible_height, ++ src_pic->date, src_pic->b_force, ++ src_fmt->i_width, src_fmt->i_height, src_fmt->i_visible_width, src_fmt->i_visible_height); ++#endif ++ ++ if (alpha == 0) ++ return; ++ ++ x_offset += dst_fmt->i_x_offset; ++ y_offset += dst_fmt->i_y_offset; ++ ++ // Deal with R/B overrun ++ if (x_offset + width >= (int)(dst_fmt->i_x_offset + dst_fmt->i_visible_width)) ++ width = dst_fmt->i_x_offset + dst_fmt->i_visible_width - x_offset; ++ if (y_offset + height >= (int)(dst_fmt->i_y_offset + dst_fmt->i_visible_height)) ++ height = dst_fmt->i_y_offset + dst_fmt->i_visible_height - y_offset; ++ ++ if (width <= 0 || height <= 0) ++ return; ++ ++ // *** L/U overrun ++ ++ s_data = src_pic->p[0].p_pixels + ++ src_pic->p[0].i_pixel_pitch * src_fmt->i_x_offset + ++ src_pic->p[0].i_pitch * src_fmt->i_y_offset; ++ d_data = dst_pic->p[0].p_pixels + ++ dst_pic->p[0].i_pixel_pitch * x_offset + ++ dst_pic->p[0].i_pitch * y_offset; ++ ++ ++ do { ++ blend_fn(d_data, s_data, alpha, width); ++ s_data += src_pic->p[0].i_pitch; ++ d_data += dst_pic->p[0].i_pitch; ++ } while (--height > 0); ++} ++ ++static void CloseBlendNeon(vlc_object_t *object) ++{ ++ VLC_UNUSED(object); ++} ++ ++static int OpenBlendNeon(vlc_object_t *object) ++{ ++ filter_t * const p_filter = (filter_t *)object; ++ const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma; ++ MMAL_FOURCC_T mfcc_src = vlc_to_mmal_video_fourcc(&p_filter->fmt_in.video); ++ MMAL_FOURCC_T mfcc_dst = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video); ++ blend_neon_fn * blend_fn = (blend_neon_fn *)0; ++ ++ // Obviously can't use this if we have no neon ++ if (!vlc_CPU_ARM_NEON()) ++ return VLC_EGENERIC; ++ ++ // Non-alpha RGB only for dest ++ if (vfcc_dst != VLC_CODEC_RGB32) ++ return VLC_EGENERIC; ++ ++ // Check we have appropriate blend fn (mmal doesn't have a non-alpha RGB32) ++ switch (mfcc_src) { ++ case MMAL_ENCODING_RGBA: ++ if (mfcc_dst == MMAL_ENCODING_RGBA) ++ blend_fn = blend_rgbx_rgba_neon; ++ else if (mfcc_dst == MMAL_ENCODING_BGRA) ++ blend_fn = blend_bgrx_rgba_neon; ++ break; ++ ++ case MMAL_ENCODING_BGRA: ++ if (mfcc_dst == MMAL_ENCODING_BGRA) ++ blend_fn = blend_rgbx_rgba_neon; ++ else if (mfcc_dst == MMAL_ENCODING_RGBA) ++ blend_fn = blend_bgrx_rgba_neon; ++ break; ++ ++ default: ++ break; + } -+ sys->input->buffer_size = sys->input->buffer_size_recommended; -+ sys->input->buffer_num = 30; -+// sys->input->buffer_num = sys->input->buffer_num_recommended; - --#ifndef NDEBUG -- msg_Dbg(filter, "Send %d buffers to output port (available: %d, in_transit: %d, buffer_num: %d)", -- buffers_to_send, buffers_available, sys->output_in_transit, -- sys->output->buffer_num); --#endif -- for (i = 0; i < buffers_to_send; ++i) { -- if (send_output_buffer(filter) < 0) -- break; -+ if ((sys->in_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) ++ ++ if (blend_fn == (blend_neon_fn *)0) + { -+ msg_Err(filter, "Failed to create input pool"); -+ goto fail; - } --} - --static picture_t *deinterlace(filter_t *filter, picture_t *picture) --{ -- filter_sys_t *sys = filter->p_sys; -- MMAL_BUFFER_HEADER_T *buffer; -- picture_t *out_picture = NULL; -- picture_t *ret = NULL; -- MMAL_STATUS_T status; -- unsigned i = 0; -+ status = port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, true); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", -+ sys->input->name, status, mmal_status_to_string(status)); -+ goto fail; -+ } - -- fill_output_port(filter); -+ status = mmal_port_enable(sys->input, di_input_port_cb); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)", -+ sys->input->name, status, mmal_status_to_string(status)); -+ goto fail; ++ return VLC_EGENERIC; + } - -- buffer = picture->p_sys->buffer; -- buffer->user_data = picture; -- buffer->pts = picture->date; -- buffer->cmd = 0; - -- if (!picture->p_sys->displayed) { -- status = mmal_port_send_buffer(sys->input, buffer); -- if (status != MMAL_SUCCESS) { -- msg_Err(filter, "Failed to send buffer to input port (status=%"PRIx32" %s)", -- status, mmal_status_to_string(status)); -- picture_Release(picture); -- } else { -- picture->p_sys->displayed = true; -- atomic_fetch_add(&sys->input_in_transit, 1); -- vlc_sem_post(&sys->sem); -- } -- } else { -- picture_Release(picture); -- } -- -- /* -- * Send output buffers -- */ -- while(atomic_load(&sys->started) && i < 2) { -- if (buffer = mmal_queue_timedwait(sys->filtered_pictures, 2000)) { -- i++; -- if (!out_picture) { -- out_picture = (picture_t *)buffer->user_data; -- ret = out_picture; -- } else { -- out_picture->p_next = (picture_t *)buffer->user_data; -- out_picture = out_picture->p_next; -- } -- out_picture->date = buffer->pts; -- } else { -- msg_Dbg(filter, "Failed waiting for filtered picture"); -- break; -- } -+ if ((sys->out_q = mmal_queue_create()) == NULL) -+ { -+ msg_Err(filter, "Failed to create out Q"); -+ goto fail; - } -- if (out_picture) -- out_picture->p_next = NULL; - -- return ret; --} -- --static void flush(filter_t *filter) --{ -- filter_sys_t *sys = filter->p_sys; -- MMAL_BUFFER_HEADER_T *buffer; -+ sys->output = sys->component->output[0]; -+ mmal_format_full_copy(sys->output->format, sys->input->format); - -- msg_Dbg(filter, "flush deinterlace filter"); -+ if (!sys->is_cma) ++ ++ p_filter->p_sys = (void *)blend_fn; ++ p_filter->pf_video_blend = FilterBlendNeon; ++ + { -+ if ((status = hw_mmal_opaque_output(VLC_OBJECT(filter), &sys->out_ppr, sys->output, 5, di_output_port_cb)) != MMAL_SUCCESS) -+ goto fail; ++ char dbuf0[5], dbuf1[5]; ++ char dbuf0a[5], dbuf1a[5]; ++ msg_Dbg(p_filter, "%s: (%s) %s/%s,%dx%d [(%d,%d) %dx%d]->%s/%s,%dx%d [(%d,%d) %dx%d]", __func__, ++ "blend", ++ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), ++ str_fourcc(dbuf0a, mfcc_src), ++ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, ++ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, ++ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, ++ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), ++ str_fourcc(dbuf1a, mfcc_dst), ++ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, ++ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, ++ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height); + } -+ else -+ { -+ // CMA stuff -+ sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)filter; + -+ if ((sys->cma_out_pool = cma_buf_pool_new(8, 8, true, "deinterlace")) == NULL) -+ { -+ msg_Err(filter, "Failed to alloc cma buf pool"); -+ goto fail; -+ } - -- msg_Dbg(filter, "flush: flush ports (input: %d, output: %d in transit)", -- sys->input_in_transit, sys->output_in_transit); -- mmal_port_flush(sys->output); -- mmal_port_flush(sys->input); -- -- msg_Dbg(filter, "flush: wait for all buffers to be returned"); -- while (atomic_load(&sys->input_in_transit) || -- atomic_load(&sys->output_in_transit)) -- vlc_sem_wait(&sys->sem); -- -- while ((buffer = mmal_queue_get(sys->filtered_pictures))) { -- picture_t *pic = (picture_t *)buffer->user_data; -- msg_Dbg(filter, "flush: release already filtered pic %p", -- (void *)pic); -- picture_Release(pic); -- } -- atomic_store(&sys->started, false); -- msg_Dbg(filter, "flush: done"); --} -+ // Rate control done by CMA in flight logic, so have "inexhaustable" pool here -+ if ((sys->out_pool = mmal_pool_create(30, 0)) == NULL) -+ { -+ msg_Err(filter, "Failed to alloc out pool"); -+ goto fail; -+ } - --static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) --{ -- filter_t *filter = (filter_t *)port->userdata; -- MMAL_STATUS_T status; -+ port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, true); - -- if (buffer->cmd == MMAL_EVENT_ERROR) { -- status = *(uint32_t *)buffer->data; -- msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status, -- mmal_status_to_string(status)); -- } -+ if ((status = mmal_port_format_commit(sys->output)) != MMAL_SUCCESS) -+ { -+ msg_Err(filter, "Output port format commit failed"); -+ goto fail; -+ } - -- mmal_buffer_header_release(buffer); --} -+ sys->output->buffer_num = 30; -+ sys->output->buffer_size = sys->output->buffer_size_recommended; - --static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) --{ -- picture_t *picture = (picture_t *)buffer->user_data; -- filter_t *filter = (filter_t *)port->userdata; -- filter_sys_t *sys = filter->p_sys; -+ // CB just drops all bufs into out_q -+ if ((status = mmal_port_enable(sys->output, di_output_port_cb)) != MMAL_SUCCESS) -+ { -+ msg_Err(filter, "Failed to enable output port %s (status=%"PRIx32" %s)", -+ sys->output->name, status, mmal_status_to_string(status)); -+ goto fail; -+ } -+ } - -- if (picture) { -- picture_Release(picture); -- } else { -- msg_Warn(filter, "Got buffer without picture on input port - OOOPS"); -- mmal_buffer_header_release(buffer); -+ status = mmal_component_enable(sys->component); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(filter, "Failed to enable component %s (status=%"PRIx32" %s)", -+ sys->component->name, status, mmal_status_to_string(status)); -+ goto fail; - } - -- atomic_fetch_sub(&sys->input_in_transit, 1); -- vlc_sem_post(&sys->sem); -+ filter->pf_video_filter = deinterlace; -+ filter->pf_flush = di_flush; -+ return 0; ++ return VLC_SUCCESS; ++} + -+fail: -+ CloseMmalDeinterlace(filter); -+ return ret; - } - --static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) --{ -- filter_t *filter = (filter_t *)port->userdata; -- filter_sys_t *sys = filter->p_sys; -- picture_t *picture; +vlc_module_begin() -+ set_shortname(N_("MMAL deinterlace")) -+ set_description(N_("MMAL-based deinterlace filter plugin")) -+ set_capability("video filter", 900) -+ set_category(CAT_VIDEO) -+ set_subcategory(SUBCAT_VIDEO_VFILTER) -+ set_callbacks(OpenMmalDeinterlace, CloseMmalDeinterlace) -+ add_shortcut("deinterlace") -+ add_bool(MMAL_DEINTERLACE_NO_QPU, false, MMAL_DEINTERLACE_NO_QPU_TEXT, -+ MMAL_DEINTERLACE_NO_QPU_LONGTEXT, true); -+ add_bool(MMAL_DEINTERLACE_ADV, false, MMAL_DEINTERLACE_ADV_TEXT, -+ MMAL_DEINTERLACE_ADV_LONGTEXT, true); -+ add_bool(MMAL_DEINTERLACE_FAST, false, MMAL_DEINTERLACE_FAST_TEXT, -+ MMAL_DEINTERLACE_FAST_LONGTEXT, true); -+ add_bool(MMAL_DEINTERLACE_NONE, false, MMAL_DEINTERLACE_NONE_TEXT, -+ MMAL_DEINTERLACE_NONE_LONGTEXT, true); -+ add_bool(MMAL_DEINTERLACE_HALF_RATE, false, MMAL_DEINTERLACE_HALF_RATE_TEXT, -+ MMAL_DEINTERLACE_HALF_RATE_LONGTEXT, true); -+ add_bool(MMAL_DEINTERLACE_FULL_RATE, false, MMAL_DEINTERLACE_FULL_RATE_TEXT, -+ MMAL_DEINTERLACE_FULL_RATE_LONGTEXT, true); ++ set_category( CAT_INPUT ) ++ set_subcategory( SUBCAT_INPUT_VCODEC ) ++ set_shortname(N_("MMAL decoder")) ++ set_description(N_("MMAL-based decoder plugin for Raspberry Pi")) ++ set_capability("video decoder", 90) ++ add_shortcut("mmal_decoder") ++ add_bool(MMAL_OPAQUE_NAME, true, MMAL_OPAQUE_TEXT, MMAL_OPAQUE_LONGTEXT, false) ++ add_bool(MMAL_DECODE_ENABLE_NAME, false, MMAL_DECODE_ENABLE_TEXT, MMAL_DECODE_ENABLE_LONGTEXT, true) ++ set_callbacks(OpenDecoder, CloseDecoder) ++ ++ add_submodule() ++ set_category( CAT_VIDEO ) ++ set_subcategory( SUBCAT_VIDEO_VFILTER ) ++ set_shortname(N_("MMAL resizer")) ++ set_description(N_("MMAL resizing conversion filter")) ++ add_shortcut("mmal_converter") ++ set_capability( "video converter", 900 ) ++ add_bool(MMAL_RESIZE_NAME, /* default */ false, MMAL_RESIZE_TEXT, MMAL_RESIZE_LONGTEXT, /* advanced option */ false) ++ add_bool(MMAL_ISP_NAME, /* default */ false, MMAL_ISP_TEXT, MMAL_ISP_LONGTEXT, /* advanced option */ false) ++ set_callbacks(OpenConverter, CloseConverter) ++ ++#if OPT_TO_FROM_ZC ++ add_submodule() ++ set_category( CAT_VIDEO ) ++ set_subcategory( SUBCAT_VIDEO_VFILTER ) ++ set_shortname(N_("MMAL to ZC")) ++ set_description(N_("MMAL conversion to ZC filter")) ++ add_shortcut("mmal_to_zc") ++ set_capability( "video converter", 901 ) ++ set_callbacks(OpenConverterToZc, CloseConverterToZc) ++ ++ add_submodule() ++ set_category( CAT_VIDEO ) ++ set_subcategory( SUBCAT_VIDEO_VFILTER ) ++ set_shortname(N_("MMAL from ZC")) ++ set_description(N_("MMAL conversion from ZC filter")) ++ add_shortcut("mmal_from_zc") ++ set_capability( "video converter", 902 ) ++ set_callbacks(OpenConverterFromZc, CloseConverterFromZc) ++#endif ++ ++ add_submodule() ++ set_category( CAT_VIDEO ) ++ set_subcategory( SUBCAT_VIDEO_VFILTER ) ++ set_description(N_("Video pictures blending for MMAL")) ++ add_shortcut("mmal_blend") ++ set_capability("video blending", 120) ++ set_callbacks(OpenBlendMmal, CloseBlendMmal) ++ ++ add_submodule() ++ set_category( CAT_VIDEO ) ++ set_subcategory( SUBCAT_VIDEO_VFILTER ) ++ set_description(N_("Video pictures blending for neon")) ++ add_shortcut("neon_blend") ++ set_capability("video blending", 110) ++ set_callbacks(OpenBlendNeon, CloseBlendNeon) + +vlc_module_end() + - -- if (buffer->cmd == 0) { -- if (buffer->length > 0) { -- atomic_store(&sys->started, true); -- mmal_queue_put(sys->filtered_pictures, buffer); -- picture = (picture_t *)buffer->user_data; -- } else { -- picture = (picture_t *)buffer->user_data; -- picture_Release(picture); -- } -- -- atomic_fetch_sub(&sys->output_in_transit, 1); -- vlc_sem_post(&sys->sem); -- } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) { -- msg_Warn(filter, "MMAL_EVENT_FORMAT_CHANGED seen but not handled"); -- mmal_buffer_header_release(buffer); -- } else { -- mmal_buffer_header_release(buffer); -- } --} ++ --- /dev/null -+++ b/modules/hw/mmal/mmal_avcodec.c -@@ -0,0 +1,2175 @@ -+/***************************************************************************** -+ * video.c: video decoder using the libavcodec library -+ ***************************************************************************** -+ * Copyright (C) 1999-2001 VLC authors and VideoLAN -+ * $Id$ -+ * -+ * Authors: Laurent Aimar -+ * Gildas Bazin -+ * -+ * This program is free software; you can redistribute it and/or modify it -+ * under the terms of the GNU Lesser General Public License as published by -+ * the Free Software Foundation; either version 2.1 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public License -+ * along with this program; if not, write to the Free Software Foundation, -+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. -+ *****************************************************************************/ ++++ b/modules/hw/mmal/converter_mmal.c +@@ -0,0 +1,480 @@ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif + -+/***************************************************************************** -+ * Preamble -+ *****************************************************************************/ -+#include "config.h" ++#include ++#include ++#include ++#include + -+#include -+#include -+#include -+#include -+#include -+#include ++#include + -+#include -+#include -+#include -+#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 16, 101 ) ) -+#include -+#endif ++#include ++#include + -+//#include "avcodec.h" -+//#include "va.h" ++#include ++#include ++#include ++#include ++#include + -+#include -+#include -+#include -+#include "../../codec/cc.h" -+#include "../../codec/avcodec/avcommon.h" // ??? Beware over inclusion +#include "mmal_cma.h" ++ ++#include "../../video_output/opengl/converter.h" ++ +#include "mmal_picture.h" + ++#include ++ +#define TRACE_ALL 0 + -+#define BUFFERS_IN_FLIGHT 5 // Default max value for in flight buffers -+#define BUFFERS_IN_FLIGHT_UHD 3 // Fewer if very big -+ -+#define MMAL_AVCODEC_BUFFERS "mmal-avcodec-buffers" -+#define MMAL_AVCODEC_BUFFERS_TEXT N_("In flight buffer count before blocking.") -+#define MMAL_AVCODEC_BUFFERS_LONGTEXT N_("In flight buffer count before blocking. " \ -+"Beware that incautious changing of this can lead to lockup. " \ -+"Zero will disable the module.") -+ -+ -+// Fwd declarations required due to wanting to avoid reworking the original -+// code too much -+static void MmalAvcodecCloseDecoder( vlc_object_t *obj ); -+ -+ -+/***************************************************************************** -+ * decoder_sys_t : decoder descriptor -+ *****************************************************************************/ -+struct decoder_sys_t ++typedef struct mmal_gl_converter_s +{ -+ AVCodecContext *p_context; -+ const AVCodec *p_codec; -+ -+ /* Video decoder specific part */ -+ date_t pts; -+ -+ /* Closed captions for decoders */ -+ cc_data_t cc; -+ -+ /* for frame skipping algo */ -+ bool b_hurry_up; -+ bool b_show_corrupted; -+ bool b_from_preroll; -+ enum AVDiscard i_skip_frame; -+ -+ /* how many decoded frames are late */ -+ int i_late_frames; -+ mtime_t i_late_frames_start; -+ mtime_t i_last_late_delay; -+ -+ /* for direct rendering */ -+ bool b_direct_rendering; -+ atomic_bool b_dr_failure; -+ -+ /* Hack to force display of still pictures */ -+ bool b_first_frame; -+ -+ -+ /* */ -+ bool palette_sent; -+ -+ /* VA API */ -+// vlc_va_t *p_va; -+ enum PixelFormat pix_fmt; -+ int profile; -+ int level; -+ -+ vlc_sem_t sem_mt; -+ -+ // Rpi vars -+ cma_buf_pool_t * cma_pool; -+ bool pool_alloc_1; ++ EGLint drm_fourcc; + vcsm_init_type_t vcsm_init_type; -+ int cma_in_flight_max; -+ // Debug -+ decoder_t * p_dec; -+}; -+ -+ -+static vlc_fourcc_t -+ZcFindVlcChroma(const int i_ffmpeg_chroma) -+{ -+ switch (i_ffmpeg_chroma) -+ { -+ // This is all we claim to deal with -+ // In theory RGB should be doable within our current framework -+ case AV_PIX_FMT_YUV420P: -+ return VLC_CODEC_MMAL_ZC_I420; -+ case AV_PIX_FMT_SAND128: -+ case AV_PIX_FMT_RPI4_8: -+ return VLC_CODEC_MMAL_ZC_SAND8; -+ case AV_PIX_FMT_SAND64_10: -+ return VLC_CODEC_MMAL_ZC_SAND10; -+ case AV_PIX_FMT_RPI4_10: -+ return VLC_CODEC_MMAL_ZC_SAND30; -+ default: -+ break; -+ } -+ return 0; -+} -+ -+// Pix Fmt conv for MMal -+// video_fromat from ffmpeg pic_fmt -+static int -+ZcGetVlcChroma( video_format_t *fmt, int i_ffmpeg_chroma ) -+{ -+ fmt->i_rmask = 0; -+ fmt->i_gmask = 0; -+ fmt->i_bmask = 0; -+ fmt->i_chroma = ZcFindVlcChroma(i_ffmpeg_chroma); ++ cma_buf_t * last_cb; + -+ return fmt->i_chroma == 0 ? -1 : 0; -+} ++ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; ++} mmal_gl_converter_t; + + -+// Format chooser is way simpler than vlc -+static enum PixelFormat -+ZcGetFormat(AVCodecContext *p_context, const enum PixelFormat *pi_fmt) ++static EGLint vlc_to_gl_fourcc(const video_format_t * const fmt) +{ -+ enum PixelFormat swfmt = avcodec_default_get_format(p_context, pi_fmt); -+ for (size_t i = 0; pi_fmt[i] != AV_PIX_FMT_NONE; i++) ++ // Converting to mmal selects the right RGB32 varient ++ switch(vlc_to_mmal_video_fourcc(fmt)) + { -+ if (ZcFindVlcChroma(pi_fmt[i]) != 0) -+ return pi_fmt[i]; ++ case MMAL_ENCODING_I420: ++ return MMAL_FOURCC('Y','U','1','2'); ++ case MMAL_ENCODING_YV12: ++ return MMAL_FOURCC('Y','V','1','2'); ++ case MMAL_ENCODING_I422: ++ return MMAL_FOURCC('Y','U','1','6'); ++ case MMAL_ENCODING_YUVUV128: // Doesn't actually work yet ++ case MMAL_ENCODING_NV12: ++ return MMAL_FOURCC('N','V','1','2'); ++ case MMAL_ENCODING_NV21: ++ return MMAL_FOURCC('N','V','2','1'); ++ case MMAL_ENCODING_RGB16: ++ return MMAL_FOURCC('R','G','1','6'); ++ case MMAL_ENCODING_RGB24: ++ return MMAL_FOURCC('B','G','2','4'); ++ case MMAL_ENCODING_BGR24: ++ return MMAL_FOURCC('R','G','2','4'); ++ case MMAL_ENCODING_BGR32: ++ case MMAL_ENCODING_BGRA: ++ return MMAL_FOURCC('X','R','2','4'); ++ case MMAL_ENCODING_RGB32: ++ case MMAL_ENCODING_RGBA: ++ return MMAL_FOURCC('X','B','2','4'); ++ default: ++ break; + } -+ return swfmt; ++ return 0; +} + ++typedef struct tex_context_s { ++ picture_context_t cmn; ++ GLuint texture; + -+static void cma_avbuf_pool_free(void * v) -+{ -+ cma_buf_unref(v); -+} -+ -+static unsigned int zc_buf_vcsm_handle(void * v) -+{ -+ return cma_buf_vcsm_handle(v); -+} ++ PFNGLDELETETEXTURESPROC DeleteTextures; // Copy fn pointer so we don't need tc on delete ++} tex_context_t; + -+static unsigned int zc_buf_vc_handle(void * v) ++static void tex_context_delete(tex_context_t * const tex) +{ -+ return cma_buf_vc_handle(v); ++ tex->DeleteTextures(1, &tex->texture); ++ free(tex); +} + -+static void * zc_buf_map_arm(void * v) ++static void tex_context_destroy(picture_context_t * pic_ctx) +{ -+ return cma_buf_addr(v); ++ tex_context_delete((tex_context_t *)pic_ctx); +} + -+static unsigned int zc_buf_map_vc(void * v) ++static picture_context_t * tex_context_copy(picture_context_t * pic_ctx) +{ -+ return cma_buf_vc_addr(v); ++ return pic_ctx; +} + -+ -+ -+static const av_rpi_zc_buf_fn_tab_t zc_buf_fn_tab = { -+ .free = cma_avbuf_pool_free, -+ -+ .vcsm_handle = zc_buf_vcsm_handle, -+ .vc_handle = zc_buf_vc_handle, -+ .map_arm = zc_buf_map_arm, -+ .map_vc = zc_buf_map_vc -+}; -+ -+ -+static AVBufferRef * -+zc_alloc_buf(void * v, size_t size, const AVRpiZcFrameGeometry * geo) ++static tex_context_t * get_tex_context(const opengl_tex_converter_t * const tc, picture_t * const pic, cma_buf_t * const cb) +{ -+ decoder_t * const dec = v; -+ decoder_sys_t * const sys = dec->p_sys; ++ mmal_gl_converter_t * const sys = tc->priv; ++ tex_context_t * tex = (tex_context_t *)cma_buf_context2(cb); ++ if (tex != NULL) ++ return tex; + -+ VLC_UNUSED(geo); ++ if ((tex = malloc(sizeof(*tex))) == NULL) ++ return NULL; + -+ assert(sys != NULL); ++ *tex = (tex_context_t){ ++ .cmn = { ++ .destroy = tex_context_destroy, ++ .copy = tex_context_copy ++ }, ++ .texture = 0, ++ .DeleteTextures = tc->vt->DeleteTextures ++ }; + -+ const unsigned int dec_pool_req = av_rpi_zc_get_decoder_pool_size(sys->p_context->opaque); -+ if (dec_pool_req != 0) + { -+ cma_buf_pool_resize(sys->cma_pool, dec_pool_req + sys->cma_in_flight_max, sys->cma_in_flight_max); ++ EGLint attribs[30]; ++ EGLint * a = attribs; ++ const int fd = cma_buf_fd(cb); ++ uint8_t * base_addr = cma_buf_addr(cb); + -+ if (!sys->pool_alloc_1) ++ if (pic->i_planes >= 4 || pic->i_planes <= 0) + { -+ sys->pool_alloc_1 = true; -+ msg_Dbg(dec, "Pool size: (%d+%d) * %zd", dec_pool_req, sys->cma_in_flight_max, size); -+ if (cma_buf_pool_fill(sys->cma_pool, size) != 0) -+ msg_Warn(dec, "Failed to preallocate decoder pool (%d+%d) * %zd", dec_pool_req, sys->cma_in_flight_max, size); ++ msg_Err(tc, "%s: Bad planes: %d", __func__, pic->i_planes); ++ goto fail; + } -+ } + -+ void * const cmabuf = cma_buf_pool_alloc_buf(sys->cma_pool, size); -+ -+ if (cmabuf == NULL) -+ { -+ msg_Err(dec, "CMA buf pool alloc buf failed"); -+ return NULL; -+ } ++ *a++ = EGL_WIDTH; ++ *a++ = pic->format.i_visible_width; ++ *a++ = EGL_HEIGHT; ++ *a++ = pic->format.i_visible_height; ++ *a++ = EGL_LINUX_DRM_FOURCC_EXT; ++ *a++ = sys->drm_fourcc; + -+ AVBufferRef *const avbuf = av_rpi_zc_buf(cma_buf_size(cmabuf), 0, cmabuf, &zc_buf_fn_tab); ++ if (pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND8) ++ { ++ // Sand is its own very special bunny :-( ++ static const EGLint attnames[] = { ++ EGL_DMA_BUF_PLANE0_FD_EXT, ++ EGL_DMA_BUF_PLANE0_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE0_PITCH_EXT, ++ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, ++ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, ++ EGL_DMA_BUF_PLANE1_FD_EXT, ++ EGL_DMA_BUF_PLANE1_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE1_PITCH_EXT, ++ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, ++ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT ++ }; + -+ if (avbuf == NULL) -+ { -+ msg_Err(dec, "av_rpi_zc_buf failed"); -+ cma_buf_unref(cmabuf); -+ return NULL; -+ } ++ const EGLint * n = attnames; + -+ return avbuf; -+} ++ for (int i = 0; i < pic->i_planes; ++i) ++ { ++ const uint64_t mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(pic->p[i].i_pitch); + -+static void -+zc_free_pool(void * v) -+{ -+ decoder_t * const dec = v; -+ cma_buf_pool_delete(dec->p_sys->cma_pool); -+} ++ *a++ = *n++; ++ *a++ = fd; ++ *a++ = *n++; ++ *a++ = pic->p[i].p_pixels - base_addr; ++ *a++ = *n++; ++ *a++ = pic->format.i_width; ++ *a++ = *n++; ++ *a++ = (EGLint)(mod >> 32); ++ *a++ = *n++; ++ *a++ = (EGLint)(mod & 0xffffffff); ++ } ++ } ++ else ++ { ++ static const EGLint attnames[] = { ++ EGL_DMA_BUF_PLANE0_FD_EXT, ++ EGL_DMA_BUF_PLANE0_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE0_PITCH_EXT, ++ EGL_DMA_BUF_PLANE1_FD_EXT, ++ EGL_DMA_BUF_PLANE1_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE1_PITCH_EXT, ++ EGL_DMA_BUF_PLANE2_FD_EXT, ++ EGL_DMA_BUF_PLANE2_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE2_PITCH_EXT, ++ EGL_DMA_BUF_PLANE3_FD_EXT, ++ EGL_DMA_BUF_PLANE3_OFFSET_EXT, ++ EGL_DMA_BUF_PLANE3_PITCH_EXT ++ }; + ++ const EGLint * n = attnames; + -+static const uint8_t shift_01[] = {0,1,1,1}; -+static const uint8_t pb_1[] = {1,1,1,1}; -+static const uint8_t pb_12[] = {1,2,2,2}; -+static const uint8_t pb_24[] = {2,4,4,4}; -+static const uint8_t pb_4[] = {4,4,4,4}; ++ for (int i = 0; i < pic->i_planes; ++i) ++ { ++ *a++ = *n++; ++ *a++ = fd; ++ *a++ = *n++; ++ *a++ = pic->p[i].p_pixels - base_addr; ++ *a++ = *n++; ++ *a++ = pic->p[i].i_pitch; ++ } ++ } + -+static int set_pic_from_frame(picture_t * const pic, const AVFrame * const frame) -+{ -+ const uint8_t * hs = shift_01; -+ const uint8_t * ws = shift_01; -+ const uint8_t * pb = pb_1; ++ *a = EGL_NONE; + -+ switch (pic->format.i_chroma) ++ const EGLImage image = tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); ++ if (!image) { ++ msg_Err(tc, "Failed to import fd %d: Err=%#x", fd, tc->vt->GetError()); ++ goto fail; ++ } ++ ++ // ** ?? tc->tex_target ++ tc->vt->GenTextures(1, &tex->texture); ++ tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, tex->texture); ++ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); ++ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); ++ sys->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image); ++ ++ tc->gl->egl.destroyImageKHR(tc->gl, image); ++ } ++ ++ if (cma_buf_add_context2(cb, &tex->cmn) != VLC_SUCCESS) + { -+ case VLC_CODEC_MMAL_ZC_RGB32: -+ pic->i_planes = 1; -+ pb = pb_4; -+ break; -+ case VLC_CODEC_MMAL_ZC_I420: -+ pic->i_planes = 3; -+ break; -+ case VLC_CODEC_MMAL_ZC_SAND8: -+ pic->i_planes = 2; -+ pb = pb_12; -+ break; -+ case VLC_CODEC_MMAL_ZC_SAND10: -+ case VLC_CODEC_MMAL_ZC_SAND30: // Lies: SAND30 is "special" -+ pic->i_planes = 2; -+ pb = pb_24; -+ break; -+ default: -+ return VLC_EGENERIC; ++ msg_Err(tc, "%s: add_context2 failed", __func__); ++ goto fail; + } ++ return tex; + -+ const cma_buf_t * const cb = cma_buf_pic_get(pic); -+ uint8_t * const data = cma_buf_addr(cb); -+ if (data == NULL) { -+ return VLC_ENOMEM; ++fail: ++ tex_context_delete(tex); ++ return NULL; ++} ++ ++ ++static int ++tc_mmal_update(const opengl_tex_converter_t *tc, GLuint *textures, ++ const GLsizei *tex_width, const GLsizei *tex_height, ++ picture_t *pic, const size_t *plane_offset) ++{ ++ mmal_gl_converter_t * const sys = tc->priv; ++#if TRACE_ALL ++ { ++ char cbuf[5]; ++ msg_Dbg(tc, "%s: %s %d*%dx%d : %d*%dx%d", __func__, ++ str_fourcc(cbuf, pic->format.i_chroma), ++ tc->tex_count, tex_width[0], tex_height[0], pic->i_planes, pic->p[0].i_pitch, pic->p[0].i_lines); + } ++#endif ++ VLC_UNUSED(tex_width); ++ VLC_UNUSED(tex_height); ++ VLC_UNUSED(plane_offset); + -+ uint8_t * frame_end = frame->data[0] + cma_buf_size(cb); -+ for (int i = 0; i != pic->i_planes; ++i) { -+ // Calculate lines from gap between planes -+ // This will give us an accurate "height" for later use by MMAL -+ const int lines = ((i + 1 == pic->i_planes ? frame_end : frame->data[i + 1]) - -+ frame->data[i]) / frame->linesize[i]; -+ pic->p[i] = (plane_t){ -+ .p_pixels = data + (frame->data[i] - frame->data[0]), -+ .i_lines = lines, -+ .i_pitch = frame->linesize[i], -+ .i_pixel_pitch = pb[i], -+ .i_visible_lines = av_frame_cropped_height(frame) >> hs[i], -+ .i_visible_pitch = av_frame_cropped_width(frame) >> ws[i] -+ }; ++ if (!is_cma_buf_pic_chroma(pic->format.i_chroma)) ++ { ++ char cbuf[5]; ++ msg_Err(tc, "Pic with unexpected chroma: %s", str_fourcc(cbuf, pic->format.i_chroma)); ++ return VLC_EGENERIC; + } -+ return 0; ++ ++ cma_buf_t * const cb = cma_buf_pic_get(pic); ++ if (cb == NULL) ++ { ++ msg_Err(tc, "Pic missing cma buf"); ++ return VLC_EGENERIC; ++ } ++ ++ tex_context_t * const tex = get_tex_context(tc, pic, cb); ++ if (tex == NULL) ++ return VLC_EGENERIC; ++ ++ tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, tex->texture); ++ ++ cma_buf_unref(sys->last_cb); ++ sys->last_cb = cma_buf_ref(cb); ++ ++ textures[0] = tex->texture; ++ return VLC_SUCCESS; +} + ++static int ++tc_mmal_fetch_locations(opengl_tex_converter_t *tc, GLuint program) ++{ ++ tc->uloc.Texture[0] = tc->vt->GetUniformLocation(program, "Texture0"); ++ return tc->uloc.Texture[0] != -1 ? VLC_SUCCESS : VLC_EGENERIC; ++} + -+//============================================================================ -+// -+// Nicked from avcodec/fourcc.c -+// -+// * Really we should probably use that directly ++static void ++tc_mmal_prepare_shader(const opengl_tex_converter_t *tc, ++ const GLsizei *tex_width, const GLsizei *tex_height, ++ float alpha) ++{ ++ (void) tex_width; (void) tex_height; (void) alpha; ++ VLC_UNUSED(tc); ++// tc->vt->Uniform1i(tc->uloc.Texture[0], 0); ++} + -+/* -+ * Video Codecs -+ */ ++static GLuint ++tc_fragment_shader_init(opengl_tex_converter_t * const tc, const GLenum tex_target, ++ const vlc_fourcc_t chroma, const video_color_space_t yuv_space) ++{ ++ VLC_UNUSED(yuv_space); + -+struct vlc_avcodec_fourcc ++ tc->tex_count = 1; ++ tc->tex_target = tex_target; ++ tc->texs[0] = (struct opengl_tex_cfg) { ++ { 1, 1 }, { 1, 1 }, GL_RGB, chroma, GL_UNSIGNED_SHORT //** ?? ++ }; ++ ++ tc->pf_fetch_locations = tc_mmal_fetch_locations; ++ tc->pf_prepare_shader = tc_mmal_prepare_shader; ++ ++ ++ const char fs[] = ++ "#extension GL_OES_EGL_image_external : enable\n" ++ "precision mediump float;\n" ++ "uniform samplerExternalOES Texture0;\n" ++ "varying vec2 TexCoord0;\n" ++ "void main() {\n" ++ " gl_FragColor = texture2D(Texture0, TexCoord0);\n" ++ "}\n"; ++ ++ ++ const char *code = fs; ++ ++ GLuint fragment_shader = tc->vt->CreateShader(GL_FRAGMENT_SHADER); ++ tc->vt->ShaderSource(fragment_shader, 1, &code, NULL); ++ tc->vt->CompileShader(fragment_shader); ++ return fragment_shader; ++} ++ ++ ++static void ++CloseGLConverter(vlc_object_t *obj) +{ -+ vlc_fourcc_t i_fourcc; -+ unsigned i_codec; -+}; ++ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; ++ mmal_gl_converter_t * const sys = tc->priv; + ++ if (sys == NULL) ++ return; + -+static const struct vlc_avcodec_fourcc video_codecs[] = ++ cma_buf_unref(sys->last_cb); ++ cma_vcsm_exit(sys->vcsm_init_type); ++ free(sys); ++} ++ ++ ++// Pick a chroma that we can convert to ++// Prefer I420 as smallest ++static vlc_fourcc_t chroma_in_out(const vlc_fourcc_t chroma_in) +{ -+ { VLC_CODEC_MP1V, AV_CODEC_ID_MPEG1VIDEO }, -+ { VLC_CODEC_MP2V, AV_CODEC_ID_MPEG2VIDEO }, /* prefer MPEG2 over MPEG1 */ -+ { VLC_CODEC_MPGV, AV_CODEC_ID_MPEG2VIDEO }, /* prefer MPEG2 over MPEG1 */ -+ /* AV_CODEC_ID_MPEG2VIDEO_XVMC */ -+ { VLC_CODEC_H261, AV_CODEC_ID_H261 }, -+ { VLC_CODEC_H263, AV_CODEC_ID_H263 }, -+ { VLC_CODEC_RV10, AV_CODEC_ID_RV10 }, -+ { VLC_CODEC_RV13, AV_CODEC_ID_RV10 }, -+ { VLC_CODEC_RV20, AV_CODEC_ID_RV20 }, -+ { VLC_CODEC_MJPG, AV_CODEC_ID_MJPEG }, -+ { VLC_CODEC_MJPGB, AV_CODEC_ID_MJPEGB }, -+ { VLC_CODEC_LJPG, AV_CODEC_ID_LJPEG }, -+ { VLC_CODEC_SP5X, AV_CODEC_ID_SP5X }, -+ { VLC_CODEC_JPEGLS, AV_CODEC_ID_JPEGLS }, -+ { VLC_CODEC_MP4V, AV_CODEC_ID_MPEG4 }, -+ /* AV_CODEC_ID_RAWVIDEO */ -+ { VLC_CODEC_DIV1, AV_CODEC_ID_MSMPEG4V1 }, -+ { VLC_CODEC_DIV2, AV_CODEC_ID_MSMPEG4V2 }, -+ { VLC_CODEC_DIV3, AV_CODEC_ID_MSMPEG4V3 }, -+ { VLC_CODEC_WMV1, AV_CODEC_ID_WMV1 }, -+ { VLC_CODEC_WMV2, AV_CODEC_ID_WMV2 }, -+ { VLC_CODEC_H263P, AV_CODEC_ID_H263P }, -+ { VLC_CODEC_H263I, AV_CODEC_ID_H263I }, -+ { VLC_CODEC_FLV1, AV_CODEC_ID_FLV1 }, -+ { VLC_CODEC_SVQ1, AV_CODEC_ID_SVQ1 }, -+ { VLC_CODEC_SVQ3, AV_CODEC_ID_SVQ3 }, -+ { VLC_CODEC_DV, AV_CODEC_ID_DVVIDEO }, -+ { VLC_CODEC_HUFFYUV, AV_CODEC_ID_HUFFYUV }, -+ { VLC_CODEC_CYUV, AV_CODEC_ID_CYUV }, -+ { VLC_CODEC_H264, AV_CODEC_ID_H264 }, -+ { VLC_CODEC_INDEO3, AV_CODEC_ID_INDEO3 }, -+ { VLC_CODEC_VP3, AV_CODEC_ID_VP3 }, -+ { VLC_CODEC_THEORA, AV_CODEC_ID_THEORA }, -+#if ( !defined( WORDS_BIGENDIAN ) ) -+ /* Asus Video (Another thing that doesn't work on PPC) */ -+ { VLC_CODEC_ASV1, AV_CODEC_ID_ASV1 }, -+ { VLC_CODEC_ASV2, AV_CODEC_ID_ASV2 }, -+#endif -+ { VLC_CODEC_FFV1, AV_CODEC_ID_FFV1 }, -+ { VLC_CODEC_4XM, AV_CODEC_ID_4XM }, -+ { VLC_CODEC_VCR1, AV_CODEC_ID_VCR1 }, -+ { VLC_CODEC_CLJR, AV_CODEC_ID_CLJR }, -+ { VLC_CODEC_MDEC, AV_CODEC_ID_MDEC }, -+ { VLC_CODEC_ROQ, AV_CODEC_ID_ROQ }, -+ { VLC_CODEC_INTERPLAY, AV_CODEC_ID_INTERPLAY_VIDEO }, -+ { VLC_CODEC_XAN_WC3, AV_CODEC_ID_XAN_WC3 }, -+ { VLC_CODEC_XAN_WC4, AV_CODEC_ID_XAN_WC4 }, -+ { VLC_CODEC_RPZA, AV_CODEC_ID_RPZA }, -+ { VLC_CODEC_CINEPAK, AV_CODEC_ID_CINEPAK }, -+ { VLC_CODEC_WS_VQA, AV_CODEC_ID_WS_VQA }, -+ { VLC_CODEC_MSRLE, AV_CODEC_ID_MSRLE }, -+ { VLC_CODEC_MSVIDEO1, AV_CODEC_ID_MSVIDEO1 }, -+ { VLC_CODEC_IDCIN, AV_CODEC_ID_IDCIN }, -+ { VLC_CODEC_8BPS, AV_CODEC_ID_8BPS }, -+ { VLC_CODEC_SMC, AV_CODEC_ID_SMC }, -+ { VLC_CODEC_FLIC, AV_CODEC_ID_FLIC }, -+ { VLC_CODEC_TRUEMOTION1, AV_CODEC_ID_TRUEMOTION1 }, -+ { VLC_CODEC_VMDVIDEO, AV_CODEC_ID_VMDVIDEO }, -+ { VLC_CODEC_LCL_MSZH, AV_CODEC_ID_MSZH }, -+ { VLC_CODEC_LCL_ZLIB, AV_CODEC_ID_ZLIB }, -+ { VLC_CODEC_QTRLE, AV_CODEC_ID_QTRLE }, -+ { VLC_CODEC_TSCC, AV_CODEC_ID_TSCC }, -+ { VLC_CODEC_ULTI, AV_CODEC_ID_ULTI }, -+ { VLC_CODEC_QDRAW, AV_CODEC_ID_QDRAW }, -+ { VLC_CODEC_VIXL, AV_CODEC_ID_VIXL }, -+ { VLC_CODEC_QPEG, AV_CODEC_ID_QPEG }, -+ { VLC_CODEC_PNG, AV_CODEC_ID_PNG }, -+ { VLC_CODEC_PPM, AV_CODEC_ID_PPM }, -+ /* AV_CODEC_ID_PBM */ -+ { VLC_CODEC_PGM, AV_CODEC_ID_PGM }, -+ { VLC_CODEC_PGMYUV, AV_CODEC_ID_PGMYUV }, -+ { VLC_CODEC_PAM, AV_CODEC_ID_PAM }, -+ { VLC_CODEC_FFVHUFF, AV_CODEC_ID_FFVHUFF }, -+ { VLC_CODEC_RV30, AV_CODEC_ID_RV30 }, -+ { VLC_CODEC_RV40, AV_CODEC_ID_RV40 }, -+ { VLC_CODEC_VC1, AV_CODEC_ID_VC1 }, -+ { VLC_CODEC_WMVA, AV_CODEC_ID_VC1 }, -+ { VLC_CODEC_WMV3, AV_CODEC_ID_WMV3 }, -+ { VLC_CODEC_WMVP, AV_CODEC_ID_WMV3 }, -+ { VLC_CODEC_LOCO, AV_CODEC_ID_LOCO }, -+ { VLC_CODEC_WNV1, AV_CODEC_ID_WNV1 }, -+ { VLC_CODEC_AASC, AV_CODEC_ID_AASC }, -+ { VLC_CODEC_INDEO2, AV_CODEC_ID_INDEO2 }, -+ { VLC_CODEC_FRAPS, AV_CODEC_ID_FRAPS }, -+ { VLC_CODEC_TRUEMOTION2, AV_CODEC_ID_TRUEMOTION2 }, -+ { VLC_CODEC_BMP, AV_CODEC_ID_BMP }, -+ { VLC_CODEC_CSCD, AV_CODEC_ID_CSCD }, -+ { VLC_CODEC_MMVIDEO, AV_CODEC_ID_MMVIDEO }, -+ { VLC_CODEC_ZMBV, AV_CODEC_ID_ZMBV }, -+ { VLC_CODEC_AVS, AV_CODEC_ID_AVS }, -+ { VLC_CODEC_SMACKVIDEO, AV_CODEC_ID_SMACKVIDEO }, -+ { VLC_CODEC_NUV, AV_CODEC_ID_NUV }, -+ { VLC_CODEC_KMVC, AV_CODEC_ID_KMVC }, -+ { VLC_CODEC_FLASHSV, AV_CODEC_ID_FLASHSV }, -+ { VLC_CODEC_CAVS, AV_CODEC_ID_CAVS }, -+ { VLC_CODEC_JPEG2000, AV_CODEC_ID_JPEG2000 }, -+ { VLC_CODEC_VMNC, AV_CODEC_ID_VMNC }, -+ { VLC_CODEC_VP5, AV_CODEC_ID_VP5 }, -+ { VLC_CODEC_VP6, AV_CODEC_ID_VP6 }, -+ { VLC_CODEC_VP6F, AV_CODEC_ID_VP6F }, -+ { VLC_CODEC_TARGA, AV_CODEC_ID_TARGA }, -+ { VLC_CODEC_DSICINVIDEO, AV_CODEC_ID_DSICINVIDEO }, -+ { VLC_CODEC_TIERTEXSEQVIDEO, AV_CODEC_ID_TIERTEXSEQVIDEO }, -+ { VLC_CODEC_TIFF, AV_CODEC_ID_TIFF }, -+ { VLC_CODEC_GIF, AV_CODEC_ID_GIF }, -+ { VLC_CODEC_DXA, AV_CODEC_ID_DXA }, -+ { VLC_CODEC_DNXHD, AV_CODEC_ID_DNXHD }, -+ { VLC_CODEC_THP, AV_CODEC_ID_THP }, -+ { VLC_CODEC_SGI, AV_CODEC_ID_SGI }, -+ { VLC_CODEC_C93, AV_CODEC_ID_C93 }, -+ { VLC_CODEC_BETHSOFTVID, AV_CODEC_ID_BETHSOFTVID }, -+ /* AV_CODEC_ID_PTX */ -+ { VLC_CODEC_TXD, AV_CODEC_ID_TXD }, -+ { VLC_CODEC_VP6A, AV_CODEC_ID_VP6A }, -+ { VLC_CODEC_AMV, AV_CODEC_ID_AMV }, -+ { VLC_CODEC_VB, AV_CODEC_ID_VB }, -+ { VLC_CODEC_PCX, AV_CODEC_ID_PCX }, -+ /* AV_CODEC_ID_SUNRAST */ -+ { VLC_CODEC_INDEO4, AV_CODEC_ID_INDEO4 }, -+ { VLC_CODEC_INDEO5, AV_CODEC_ID_INDEO5 }, -+ { VLC_CODEC_MIMIC, AV_CODEC_ID_MIMIC }, -+ { VLC_CODEC_RL2, AV_CODEC_ID_RL2 }, -+ { VLC_CODEC_ESCAPE124, AV_CODEC_ID_ESCAPE124 }, -+ { VLC_CODEC_DIRAC, AV_CODEC_ID_DIRAC }, -+ { VLC_CODEC_BFI, AV_CODEC_ID_BFI }, -+ { VLC_CODEC_CMV, AV_CODEC_ID_CMV }, -+ { VLC_CODEC_MOTIONPIXELS, AV_CODEC_ID_MOTIONPIXELS }, -+ { VLC_CODEC_TGV, AV_CODEC_ID_TGV }, -+ { VLC_CODEC_TGQ, AV_CODEC_ID_TGQ }, -+ { VLC_CODEC_TQI, AV_CODEC_ID_TQI }, -+ { VLC_CODEC_AURA, AV_CODEC_ID_AURA }, -+ /* AV_CODEC_ID_AURA2 */ -+ /* AV_CODEC_ID_V210X */ -+ { VLC_CODEC_TMV, AV_CODEC_ID_TMV }, -+ { VLC_CODEC_V210, AV_CODEC_ID_V210 }, -+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( 54, 50, 100 ) && LIBAVCODEC_VERSION_MICRO >= 100 -+ { VLC_CODEC_VUYA, AV_CODEC_ID_AYUV }, -+#endif -+ /* AV_CODEC_ID_DPX */ -+ { VLC_CODEC_MAD, AV_CODEC_ID_MAD }, -+ { VLC_CODEC_FRWU, AV_CODEC_ID_FRWU }, -+ { VLC_CODEC_FLASHSV2, AV_CODEC_ID_FLASHSV2 }, -+ /* AV_CODEC_ID_CDGRAPHICS */ -+ /* AV_CODEC_ID_R210 */ -+ { VLC_CODEC_ANM, AV_CODEC_ID_ANM }, -+ { VLC_CODEC_BINKVIDEO, AV_CODEC_ID_BINKVIDEO }, -+ /* AV_CODEC_ID_IFF_ILBM */ -+ /* AV_CODEC_ID_IFF_BYTERUN1 */ -+ { VLC_CODEC_KGV1, AV_CODEC_ID_KGV1 }, -+ { VLC_CODEC_YOP, AV_CODEC_ID_YOP }, -+ { VLC_CODEC_VP8, AV_CODEC_ID_VP8 }, -+ /* AV_CODEC_ID_PICTOR */ -+ /* AV_CODEC_ID_ANSI */ -+ /* AV_CODEC_ID_A64_MULTI */ -+ /* AV_CODEC_ID_A64_MULTI5 */ -+ /* AV_CODEC_ID_R10K */ -+ { VLC_CODEC_MXPEG, AV_CODEC_ID_MXPEG }, -+ { VLC_CODEC_LAGARITH, AV_CODEC_ID_LAGARITH }, -+ { VLC_CODEC_PRORES, AV_CODEC_ID_PRORES }, -+ { VLC_CODEC_JV, AV_CODEC_ID_JV }, -+ { VLC_CODEC_DFA, AV_CODEC_ID_DFA }, -+ { VLC_CODEC_WMVP, AV_CODEC_ID_WMV3IMAGE }, -+ { VLC_CODEC_WMVP2, AV_CODEC_ID_VC1IMAGE }, -+ { VLC_CODEC_UTVIDEO, AV_CODEC_ID_UTVIDEO }, -+ { VLC_CODEC_BMVVIDEO, AV_CODEC_ID_BMV_VIDEO }, -+ { VLC_CODEC_VBLE, AV_CODEC_ID_VBLE }, -+ { VLC_CODEC_DXTORY, AV_CODEC_ID_DXTORY }, -+ /* AV_CODEC_ID_V410 */ -+ /* AV_CODEC_ID_XWD */ -+ { VLC_CODEC_CDXL, AV_CODEC_ID_CDXL }, -+ /* AV_CODEC_ID_XBM */ -+ /* AV_CODEC_ID_ZEROCODEC */ -+ { VLC_CODEC_MSS1, AV_CODEC_ID_MSS1 }, -+ { VLC_CODEC_MSA1, AV_CODEC_ID_MSA1 }, -+ { VLC_CODEC_TSC2, AV_CODEC_ID_TSCC2 }, -+ { VLC_CODEC_MTS2, AV_CODEC_ID_MTS2 }, -+ { VLC_CODEC_CLLC, AV_CODEC_ID_CLLC }, -+ { VLC_CODEC_MSS2, AV_CODEC_ID_MSS2 }, -+ { VLC_CODEC_VP9, AV_CODEC_ID_VP9 }, -+#if LIBAVCODEC_VERSION_CHECK( 57, 26, 0, 83, 101 ) -+ { VLC_CODEC_AV1, AV_CODEC_ID_AV1 }, -+#endif -+ { VLC_CODEC_ICOD, AV_CODEC_ID_AIC }, -+ /* AV_CODEC_ID_ESCAPE130 */ -+ { VLC_CODEC_G2M4, AV_CODEC_ID_G2M }, -+ { VLC_CODEC_G2M2, AV_CODEC_ID_G2M }, -+ { VLC_CODEC_G2M3, AV_CODEC_ID_G2M }, -+ /* AV_CODEC_ID_WEBP */ -+ { VLC_CODEC_HNM4_VIDEO, AV_CODEC_ID_HNM4_VIDEO }, -+ { VLC_CODEC_HEVC, AV_CODEC_ID_HEVC }, -+ -+ { VLC_CODEC_FIC , AV_CODEC_ID_FIC }, -+ /* AV_CODEC_ID_ALIAS_PIX */ -+ /* AV_CODEC_ID_BRENDER_PIX */ -+ /* AV_CODEC_ID_PAF_VIDEO */ -+ /* AV_CODEC_ID_EXR */ ++ switch (chroma_in) ++ { ++ case VLC_CODEC_MMAL_OPAQUE: ++ case VLC_CODEC_MMAL_ZC_I420: ++ case VLC_CODEC_MMAL_ZC_SAND10: // ISP only ++ return VLC_CODEC_MMAL_ZC_I420; ++ case VLC_CODEC_MMAL_ZC_SAND30: // HVS only ++ case VLC_CODEC_MMAL_ZC_RGB32: ++ return VLC_CODEC_MMAL_ZC_RGB32; // HVS can't generate YUV of any sort ++ case VLC_CODEC_MMAL_ZC_SAND8: ++ return VLC_CODEC_MMAL_ZC_SAND8; ++ default: ++ break; ++ } ++ return 0; ++} + -+ { VLC_CODEC_VP7 , AV_CODEC_ID_VP7 }, -+ /* AV_CODEC_ID_SANM */ -+ /* AV_CODEC_ID_SGIRLE */ -+ /* AV_CODEC_ID_MVC1 */ -+ /* AV_CODEC_ID_MVC2 */ -+ { VLC_CODEC_HQX, AV_CODEC_ID_HQX }, + -+ { VLC_CODEC_TDSC, AV_CODEC_ID_TDSC }, ++static int ++OpenGLConverter(vlc_object_t *obj) ++{ ++ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; ++ int rv = VLC_EGENERIC; ++ const EGLint eglfmt = vlc_to_gl_fourcc(&tc->fmt); ++ const vlc_fourcc_t chroma_out = chroma_in_out(tc->fmt.i_chroma); + -+ { VLC_CODEC_HQ_HQA, AV_CODEC_ID_HQ_HQA }, ++ // Do we know what to do with this? ++ if (chroma_out == 0) ++ return rv; + -+ { VLC_CODEC_HAP, AV_CODEC_ID_HAP }, -+ /* AV_CODEC_ID_DDS */ ++ { ++ char dbuf0[5], dbuf1[5], dbuf2[5]; ++ msg_Dbg(tc, "<<< %s: V:%s/E:%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %s", __func__, ++ str_fourcc(dbuf0, tc->fmt.i_chroma), ++ str_fourcc(dbuf1, eglfmt), ++ tc->fmt.i_width, tc->fmt.i_height, ++ tc->fmt.i_x_offset, tc->fmt.i_y_offset, ++ tc->fmt.i_visible_width, tc->fmt.i_visible_height, ++ tc->fmt.i_sar_num, tc->fmt.i_sar_den, ++ str_fourcc(dbuf2, chroma_out)); ++ } + -+ { VLC_CODEC_DXV, AV_CODEC_ID_DXV }, ++ if (tc->gl->ext != VLC_GL_EXT_EGL || ++ !tc->gl->egl.createImageKHR || !tc->gl->egl.destroyImageKHR) ++ { ++ // Missing an important callback ++ msg_Dbg(tc, "Missing EGL xxxImageKHR calls"); ++ return rv; ++ } + -+ /* ffmpeg only: AV_CODEC_ID_BRENDER_PIX */ -+ /* ffmpeg only: AV_CODEC_ID_Y41P */ -+ /* ffmpeg only: AV_CODEC_ID_EXR */ -+ /* ffmpeg only: AV_CODEC_ID_AVRP */ -+ /* ffmpeg only: AV_CODEC_ID_012V */ -+ /* ffmpeg only: AV_CODEC_ID_AVUI */ -+ /* ffmpeg only: AV_CODEC_ID_TARGA_Y216 */ -+ /* ffmpeg only: AV_CODEC_ID_V308 */ -+ /* ffmpeg only: AV_CODEC_ID_V408 */ -+ /* ffmpeg only: AV_CODEC_ID_YUV4 */ -+ /* ffmpeg only: AV_CODEC_ID_SANM */ -+ /* ffmpeg only: AV_CODEC_ID_PAF_VIDEO */ -+ /* ffmpeg only: AV_CODEC_ID_AVRN */ -+ /* ffmpeg only: AV_CODEC_ID_CPIA */ -+ /* ffmpeg only: AV_CODEC_ID_XFACE */ -+ /* ffmpeg only: AV_CODEC_ID_SGIRLE */ -+ /* ffmpeg only: AV_CODEC_ID_MVC1 */ -+ /* ffmpeg only: AV_CODEC_ID_MVC2 */ -+ /* ffmpeg only: AV_CODEC_ID_SNOW */ -+ /* ffmpeg only: AV_CODEC_ID_SMVJPEG */ ++ if ((tc->priv = calloc(1, sizeof(mmal_gl_converter_t))) == NULL) ++ { ++ msg_Err(tc, "priv alloc failure"); ++ rv = VLC_ENOMEM; ++ goto fail; ++ } ++ mmal_gl_converter_t * const sys = tc->priv; + -+#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 24, 102 ) -+ { VLC_CODEC_CINEFORM, AV_CODEC_ID_CFHD }, -+#endif ++ sys->drm_fourcc = eglfmt; + -+#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 70, 100 ) -+ { VLC_CODEC_PIXLET, AV_CODEC_ID_PIXLET }, -+#endif ++ if ((sys->vcsm_init_type = cma_vcsm_init()) != VCSM_INIT_CMA) { ++ msg_Dbg(tc, "VCSM init failed"); ++ goto fail; ++ } + -+#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 71, 101 ) -+ { VLC_CODEC_SPEEDHQ, AV_CODEC_ID_SPEEDHQ }, -+#endif -+ -+#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 79, 100 ) -+ { VLC_CODEC_FMVC, AV_CODEC_ID_FMVC }, -+#endif -+}; -+ -+// *** Really we should probably use GetFfmpegCodec with a pre-kludge for the bits we care about -+static bool -+ZcGetFfmpegCodec( enum es_format_category_e cat, vlc_fourcc_t i_fourcc, -+ unsigned *pi_ffmpeg_codec, const char **ppsz_name ) -+{ -+ const struct vlc_avcodec_fourcc *base; -+ size_t count; -+ -+ base = video_codecs; -+ count = ARRAY_SIZE(video_codecs); -+ i_fourcc = vlc_fourcc_GetCodec( cat, i_fourcc ); -+ -+ for( size_t i = 0; i < count; i++ ) ++ if ((sys->glEGLImageTargetTexture2DOES = vlc_gl_GetProcAddress(tc->gl, "glEGLImageTargetTexture2DOES")) == NULL) + { -+ if( base[i].i_fourcc == i_fourcc ) -+ { -+ if( pi_ffmpeg_codec != NULL ) -+ *pi_ffmpeg_codec = base[i].i_codec; -+ if( ppsz_name ) -+ *ppsz_name = vlc_fourcc_GetDescription( cat, i_fourcc ); -+ return true; -+ } ++ msg_Err(tc, "Failed to bind GL fns"); ++ goto fail; + } -+ return false; -+} -+ -+ -+ -+//============================================================================ -+// Derived from codec/avcodec/avcodec.c -+ -+static AVCodecContext * -+ZcFfmpeg_AllocContext( decoder_t *p_dec, -+ const AVCodec **restrict codecp ) -+{ -+ unsigned i_codec_id; -+ const char *psz_namecodec; -+ const AVCodec *p_codec = NULL; -+ -+ /* *** determine codec type *** */ -+ if( !ZcGetFfmpegCodec( p_dec->fmt_in.i_cat, p_dec->fmt_in.i_codec, -+ &i_codec_id, &psz_namecodec ) ) -+ return NULL; + -+ msg_Dbg( p_dec, "using %s %s", AVPROVIDER(LIBAVCODEC), LIBAVCODEC_IDENT ); -+ -+ /* Initialization must be done before avcodec_find_decoder() */ -+ vlc_init_avcodec(VLC_OBJECT(p_dec)); ++ if ((tc->fshader = tc_fragment_shader_init(tc, GL_TEXTURE_EXTERNAL_OES, ++ eglfmt == 0 ? VLC_CODEC_RGB32 : tc->fmt.i_chroma, ++ eglfmt == 0 ? COLOR_SPACE_SRGB : tc->fmt.space)) == 0) ++ { ++ msg_Err(tc, "Failed to make shader"); ++ goto fail; ++ } + -+ /* *** ask ffmpeg for a decoder *** */ -+ char *psz_decoder = var_InheritString( p_dec, "avcodec-codec" ); -+ if( psz_decoder != NULL ) ++ if (eglfmt == 0) + { -+ p_codec = avcodec_find_decoder_by_name( psz_decoder ); -+ if( !p_codec ) -+ msg_Err( p_dec, "Decoder `%s' not found", psz_decoder ); -+ else if( p_codec->id != i_codec_id ) ++ tc->fmt.i_chroma = chroma_out; ++ tc->fmt.i_bits_per_pixel = 8; ++ if (tc->fmt.i_chroma == VLC_CODEC_MMAL_ZC_RGB32) + { -+ msg_Err( p_dec, "Decoder `%s' can't handle %4.4s", -+ psz_decoder, (char*)&p_dec->fmt_in.i_codec ); -+ p_codec = NULL; ++ tc->fmt.i_rmask = 0xff0000; ++ tc->fmt.i_gmask = 0xff00; ++ tc->fmt.i_bmask = 0xff; ++ tc->fmt.space = COLOR_SPACE_SRGB; + } -+ free( psz_decoder ); -+ } -+ if( !p_codec ) -+// p_codec = avcodec_find_decoder( i_codec_id ); -+ { -+ if( p_dec->fmt_in.i_codec != VLC_CODEC_HEVC ) -+ p_codec = avcodec_find_decoder(i_codec_id); + else + { -+ psz_namecodec = rpi_is_model_pi4() ? "hevc" : "hevc_rpi"; -+ msg_Info(p_dec, "Looking for HEVC decoder '%s'", psz_namecodec); -+ p_codec = avcodec_find_decoder_by_name(psz_namecodec); ++ tc->fmt.i_rmask = 0; ++ tc->fmt.i_gmask = 0; ++ tc->fmt.i_bmask = 0; ++ tc->fmt.space = COLOR_SPACE_UNDEF; + } ++ sys->drm_fourcc = vlc_to_gl_fourcc(&tc->fmt); + } + -+ if( !p_codec ) ++ tc->handle_texs_gen = true; // We manage the texs ++ tc->pf_update = tc_mmal_update; ++ ++#if TRACE_ALL + { -+ msg_Dbg( p_dec, "codec not found (%s)", psz_namecodec ); -+ return NULL; ++ char dbuf0[5], dbuf1[5], dbuf2[5]; ++ msg_Dbg(tc, ">>> %s: V:%s/E:%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %s", __func__, ++ str_fourcc(dbuf0, tc->fmt.i_chroma), ++ str_fourcc(dbuf1, sys->drm_fourcc), ++ tc->fmt.i_width, tc->fmt.i_height, ++ tc->fmt.i_x_offset, tc->fmt.i_y_offset, ++ tc->fmt.i_visible_width, tc->fmt.i_visible_height, ++ tc->fmt.i_sar_num, tc->fmt.i_sar_den, ++ str_fourcc(dbuf2, chroma_out)); + } ++#endif + -+ *codecp = p_codec; -+ -+ /* *** get a p_context *** */ -+ AVCodecContext *avctx = avcodec_alloc_context3(p_codec); -+ if( unlikely(avctx == NULL) ) -+ return NULL; ++ return VLC_SUCCESS; + -+ avctx->debug = var_InheritInteger( p_dec, "avcodec-debug" ); -+ avctx->opaque = p_dec; -+ return avctx; ++fail: ++ CloseGLConverter(obj); ++ return rv; +} + -+/***************************************************************************** -+ * ffmpeg_OpenCodec: -+ *****************************************************************************/ -+ -+static int -+ZcFfmpeg_OpenCodec( decoder_t *p_dec, AVCodecContext *ctx, -+ const AVCodec *codec ) -+{ -+ char *psz_opts = var_InheritString( p_dec, "avcodec-options" ); -+ AVDictionary *options = NULL; -+ int ret; ++vlc_module_begin () ++ set_description("MMAL OpenGL surface converter") ++ set_shortname (N_("MMALGLConverter")) ++ set_capability("glconv", 900) ++ set_callbacks(OpenGLConverter, CloseGLConverter) ++ set_category(CAT_VIDEO) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ add_shortcut("mmal_gl_converter") ++vlc_module_end () + -+ if (psz_opts) { -+ vlc_av_get_options(psz_opts, &options); -+ free(psz_opts); -+ } +--- a/modules/hw/mmal/deinterlace.c ++++ b/modules/hw/mmal/deinterlace.c +@@ -26,11 +26,12 @@ + #include "config.h" + #endif + +-#include ++#include + -+ if (av_rpi_zc_init2(ctx, p_dec, zc_alloc_buf, zc_free_pool) != 0) -+ { -+ msg_Err(p_dec, "Failed to init AV ZC"); -+ return VLC_EGENERIC; -+ } + #include ++#include + #include + #include +-#include + + #include "mmal_picture.h" + +@@ -39,468 +40,814 @@ + #include + #include + +-#define MIN_NUM_BUFFERS_IN_TRANSIT 2 ++#define MMAL_DEINTERLACE_NO_QPU "mmal-deinterlace-no-qpu" ++#define MMAL_DEINTERLACE_NO_QPU_TEXT N_("Do not use QPUs for advanced HD deinterlacing.") ++#define MMAL_DEINTERLACE_NO_QPU_LONGTEXT N_("Do not make use of the QPUs to allow higher quality deinterlacing of HD content.") + +-#define MMAL_DEINTERLACE_QPU "mmal-deinterlace-adv-qpu" +-#define MMAL_DEINTERLACE_QPU_TEXT N_("Use QPUs for advanced HD deinterlacing.") +-#define MMAL_DEINTERLACE_QPU_LONGTEXT N_("Make use of the QPUs to allow higher quality deinterlacing of HD content.") ++#define MMAL_DEINTERLACE_ADV "mmal-deinterlace-adv" ++#define MMAL_DEINTERLACE_ADV_TEXT N_("Force advanced deinterlace") ++#define MMAL_DEINTERLACE_ADV_LONGTEXT N_("Force advanced deinterlace") + +-static int Open(filter_t *filter); +-static void Close(filter_t *filter); ++#define MMAL_DEINTERLACE_FAST "mmal-deinterlace-fast" ++#define MMAL_DEINTERLACE_FAST_TEXT N_("Force fast deinterlace") ++#define MMAL_DEINTERLACE_FAST_LONGTEXT N_("Force fast deinterlace") + +-vlc_module_begin() +- set_shortname(N_("MMAL deinterlace")) +- set_description(N_("MMAL-based deinterlace filter plugin")) +- set_capability("video filter", 0) +- set_category(CAT_VIDEO) +- set_subcategory(SUBCAT_VIDEO_VFILTER) +- set_callbacks(Open, Close) +- add_shortcut("deinterlace") +- add_bool(MMAL_DEINTERLACE_QPU, false, MMAL_DEINTERLACE_QPU_TEXT, +- MMAL_DEINTERLACE_QPU_LONGTEXT, true); +-vlc_module_end() ++#define MMAL_DEINTERLACE_NONE "mmal-deinterlace-none" ++#define MMAL_DEINTERLACE_NONE_TEXT N_("Force no deinterlace") ++#define MMAL_DEINTERLACE_NONE_LONGTEXT N_("Force no interlace. Simply strips off the interlace markers and passes the frame straight through. "\ ++ "This is the default for > SD if < 96M gpu-mem") + -+ vlc_avcodec_lock(); -+ ret = avcodec_open2( ctx, codec, options ? &options : NULL ); -+ vlc_avcodec_unlock(); ++#define MMAL_DEINTERLACE_HALF_RATE "mmal-deinterlace-half-rate" ++#define MMAL_DEINTERLACE_HALF_RATE_TEXT N_("Halve output framerate") ++#define MMAL_DEINTERLACE_HALF_RATE_LONGTEXT N_("Halve output framerate. 1 output frame for each pair of interlaced fields input") + -+ AVDictionaryEntry *t = NULL; -+ while ((t = av_dict_get(options, "", t, AV_DICT_IGNORE_SUFFIX))) { -+ msg_Err( p_dec, "Unknown option \"%s\"", t->key ); -+ } -+ av_dict_free(&options); ++#define MMAL_DEINTERLACE_FULL_RATE "mmal-deinterlace-full-rate" ++#define MMAL_DEINTERLACE_FULL_RATE_TEXT N_("Full output framerate") ++#define MMAL_DEINTERLACE_FULL_RATE_LONGTEXT N_("Full output framerate. 1 output frame for each interlaced field input") + +-struct filter_sys_t { + -+ if( ret < 0 ) -+ { -+ msg_Err( p_dec, "cannot start codec (%s)", codec->name ); -+ return VLC_EGENERIC; -+ } ++typedef struct filter_sys_t ++{ + MMAL_COMPONENT_T *component; + MMAL_PORT_T *input; + MMAL_PORT_T *output; ++ MMAL_POOL_T *in_pool; + -+ msg_Dbg( p_dec, "codec (%s) started", codec->name ); -+ return VLC_SUCCESS; -+} ++ MMAL_QUEUE_T * out_q; + -+//============================================================================ -+// Derived from 3.0.7.1 codec/avcodec/video.c ++ // Bind this lot somehow into ppr???? ++ bool is_cma; ++ cma_buf_pool_t * cma_out_pool; ++ MMAL_POOL_T * out_pool; + -+static inline void wait_mt(decoder_sys_t *sys) -+{ -+#if 1 -+ // As we only ever update the output in our main thread this lock is -+ // redundant -+ VLC_UNUSED(sys); -+#else -+ vlc_sem_wait(&sys->sem_mt); -+#endif -+} ++ hw_mmal_port_pool_ref_t *out_ppr; + -+static inline void post_mt(decoder_sys_t *sys) -+{ -+#if 1 -+ // As we only ever update the output in our main thread this lock is -+ // redundant -+ VLC_UNUSED(sys); -+#else -+ vlc_sem_post(&sys->sem_mt); -+#endif -+} -+ -+/***************************************************************************** -+ * Local prototypes -+ *****************************************************************************/ -+static void ffmpeg_InitCodec ( decoder_t * ); -+static int DecodeVideo( decoder_t *, block_t * ); -+static void Flush( decoder_t * ); ++ bool half_rate; ++ bool use_qpu; ++ bool use_fast; ++ bool use_passthrough; ++ unsigned int seq_in; // Seq of next frame to submit (1-15) [Init=1] ++ unsigned int seq_out; // Seq of last frame received (1-15) [Init=15] + +- MMAL_QUEUE_T *filtered_pictures; +- vlc_sem_t sem; ++ vcsm_init_type_t vcsm_init_type; + +- atomic_bool started; ++} filter_sys_t; + +- /* statistics */ +- int output_in_transit; +- int input_in_transit; +-}; +- +-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); +-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); +-static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); +-static picture_t *deinterlace(filter_t *filter, picture_t *picture); +-static void flush(filter_t *filter); + + #define MMAL_COMPONENT_DEFAULT_DEINTERLACE "vc.ril.image_fx" + +-static int Open(filter_t *filter) ++#define TRACE_ALL 0 + -+static uint32_t ffmpeg_CodecTag( vlc_fourcc_t fcc ) -+{ -+ uint8_t *p = (uint8_t*)&fcc; -+ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); -+} + -+/***************************************************************************** -+ * Local Functions -+ *****************************************************************************/ + -+/** -+ * Sets the decoder output format. -+ */ -+static int lavc_GetVideoFormat(decoder_t *dec, video_format_t *restrict fmt, -+ AVCodecContext *ctx, enum AVPixelFormat pix_fmt, -+ enum AVPixelFormat sw_pix_fmt) ++// Buffer attached to pic on success, is still valid on failure ++static picture_t * di_alloc_opaque(filter_t * const p_filter, MMAL_BUFFER_HEADER_T * const buf) + { +- int32_t frame_duration = filter->fmt_in.video.i_frame_rate != 0 ? +- (int64_t)1000000 * filter->fmt_in.video.i_frame_rate_base / +- filter->fmt_in.video.i_frame_rate : 0; +- bool use_qpu = var_InheritBool(filter, MMAL_DEINTERLACE_QPU); ++ filter_sys_t *const filter_sys = p_filter->p_sys; ++ picture_t * const pic = filter_NewPicture(p_filter); + +- MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = { +- { MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, sizeof(imfx_param) }, +- MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV, +- 4, +- { 3, frame_duration, 0, use_qpu } +- }; ++ if (pic == NULL) ++ goto fail1; + +- int ret = VLC_SUCCESS; +- MMAL_STATUS_T status; +- filter_sys_t *sys; ++ if (buf->length == 0) { ++ msg_Err(p_filter, "%s: Empty buffer", __func__); ++ goto fail2; ++ } + +- msg_Dbg(filter, "Try to open mmal_deinterlace filter. frame_duration: %d, QPU %s!", +- frame_duration, use_qpu ? "used" : "unused"); ++ if ((pic->context = hw_mmal_gen_context(buf, filter_sys->out_ppr)) == NULL) ++ goto fail2; + +- if (filter->fmt_in.video.i_chroma != VLC_CODEC_MMAL_OPAQUE) +- return VLC_EGENERIC; ++ buf_to_pic_copy_props(pic, buf); + +- if (filter->fmt_out.video.i_chroma != VLC_CODEC_MMAL_OPAQUE) +- return VLC_EGENERIC; ++#if TRACE_ALL ++ msg_Dbg(p_filter, "pic: prog=%d, tff=%d, date=%lld", pic->b_progressive, pic->b_top_field_first, (long long)pic->date); ++#endif + +- sys = calloc(1, sizeof(filter_sys_t)); +- if (!sys) +- return VLC_ENOMEM; +- filter->p_sys = sys; ++ return pic; + +- bcm_host_init(); ++fail2: ++ picture_Release(pic); ++fail1: ++// mmal_buffer_header_release(buf); ++ return NULL; ++} + +- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_DEINTERLACE, &sys->component); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)", +- MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; +- } ++static void di_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ -+ int width = ctx->coded_width; -+ int height = ctx->coded_height; -+ -+ video_format_Init(fmt, 0); ++#if TRACE_ALL ++ pic_ctx_mmal_t * ctx = buffer->user_data; ++// filter_sys_t *const sys = ((filter_t *)port->userdata)->p_sys; + -+#if 1 -+ VLC_UNUSED(sw_pix_fmt); -+ if ((fmt->i_chroma = ZcFindVlcChroma(pix_fmt)) == 0) -+ return -1; ++ msg_Dbg((filter_t *)port->userdata, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buffer->cmd, ctx, buffer, ++ buffer->flags, (long long)buffer->pts); +#else -+ if (pix_fmt == sw_pix_fmt) -+ { /* software decoding */ -+ int aligns[AV_NUM_DATA_POINTERS]; -+ -+ if (GetVlcChroma(fmt, pix_fmt)) -+ return -1; -+ -+ /* The libavcodec palette can only be fetched when the first output -+ * frame is decoded. Assume that the current chroma is RGB32 while we -+ * are waiting for a valid palette. Indeed, fmt_out.video.p_palette -+ * doesn't trigger a new vout request, but a new chroma yes. */ -+ if (pix_fmt == AV_PIX_FMT_PAL8 && !dec->fmt_out.video.p_palette) -+ fmt->i_chroma = VLC_CODEC_RGB32; -+ -+ avcodec_align_dimensions2(ctx, &width, &height, aligns); -+ } -+ else /* hardware decoding */ -+ fmt->i_chroma = vlc_va_GetChroma(pix_fmt, sw_pix_fmt); ++ VLC_UNUSED(port); ++#endif + +- status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)", +- MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; +- } ++ mmal_buffer_header_release(buffer); + +- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)filter; +- status = mmal_port_enable(sys->component->control, control_port_cb); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to enable control port %s (status=%"PRIx32" %s)", +- sys->component->control->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++#if TRACE_ALL ++ msg_Dbg((filter_t *)port->userdata, ">>> %s", __func__); +#endif ++} + -+ if( width == 0 || height == 0 || width > 8192 || height > 8192 || -+ width < ctx->width || height < ctx->height ) ++static void di_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) ++{ ++ if (buf->cmd == 0 && buf->length != 0) + { -+ msg_Err(dec, "Invalid frame size %dx%d vsz %dx%d", -+ width, height, ctx->width, ctx->height ); -+ return -1; /* invalid display size */ -+ } -+ -+ fmt->i_width = width; -+ fmt->i_height = height; -+ fmt->i_visible_width = ctx->width; -+ fmt->i_visible_height = ctx->height; ++ // The filter structure etc. should always exist if we have contents ++ // but might not on later flushes as we shut down ++ filter_t * const p_filter = (filter_t *)port->userdata; ++ filter_sys_t * const sys = p_filter->p_sys; + -+ /* If an aspect-ratio was specified in the input format then force it */ -+ if (dec->fmt_in.video.i_sar_num > 0 && dec->fmt_in.video.i_sar_den > 0) -+ { -+ fmt->i_sar_num = dec->fmt_in.video.i_sar_num; -+ fmt->i_sar_den = dec->fmt_in.video.i_sar_den; -+ } -+ else ++#if TRACE_ALL ++ msg_Dbg(p_filter, "<<< %s: cmd=%d; flags=%#x, pts=%lld", __func__, buf->cmd, buf->flags, (long long) buf->pts); ++#endif ++ mmal_queue_put(sys->out_q, buf); ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s: out Q len=%d", __func__, mmal_queue_length(sys->out_q)); ++#endif ++ return; + } + +- sys->input = sys->component->input[0]; +- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)filter; +- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) +- sys->input->format->encoding = MMAL_ENCODING_OPAQUE; +- sys->input->format->es->video.width = filter->fmt_in.video.i_width; +- sys->input->format->es->video.height = filter->fmt_in.video.i_height; +- sys->input->format->es->video.crop.x = 0; +- sys->input->format->es->video.crop.y = 0; +- sys->input->format->es->video.crop.width = filter->fmt_in.video.i_width; +- sys->input->format->es->video.crop.height = filter->fmt_in.video.i_height; +- sys->input->format->es->video.par.num = filter->fmt_in.video.i_sar_num; +- sys->input->format->es->video.par.den = filter->fmt_in.video.i_sar_den; ++ mmal_buffer_header_reset(buf); // User data stays intact so release will kill pic ++ mmal_buffer_header_release(buf); ++} + +- es_format_Copy(&filter->fmt_out, &filter->fmt_in); +- filter->fmt_out.video.i_frame_rate *= 2; + +- status = mmal_port_format_commit(sys->input); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; +- } +- sys->input->buffer_size = sys->input->buffer_size_recommended; +- sys->input->buffer_num = sys->input->buffer_num_recommended; + +- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) { +- MMAL_PARAMETER_BOOLEAN_T zero_copy = { +- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, +- 1 +- }; ++static MMAL_STATUS_T fill_output_from_q(filter_t * const p_filter, filter_sys_t * const sys, MMAL_QUEUE_T * const q) ++{ ++ MMAL_BUFFER_HEADER_T * out_buf; + +- status = mmal_port_parameter_set(sys->input, &zero_copy.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- goto out; ++ while ((out_buf = mmal_queue_get(q)) != NULL) + { -+ fmt->i_sar_num = ctx->sample_aspect_ratio.num; -+ fmt->i_sar_den = ctx->sample_aspect_ratio.den; -+ -+ if (fmt->i_sar_num == 0 || fmt->i_sar_den == 0) -+ fmt->i_sar_num = fmt->i_sar_den = 1; -+ } ++ MMAL_STATUS_T err; ++ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Send buffer to output failed"); ++ mmal_queue_put_back(q, out_buf); ++ return err; + } + } ++ return MMAL_SUCCESS; ++} + +- status = mmal_port_enable(sys->input, input_port_cb); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; +- } ++// Output buffers may contain a pic ref on error or flush ++// Free it ++static MMAL_BOOL_T out_buffer_pre_release_cb(MMAL_BUFFER_HEADER_T *header, void *userdata) ++{ ++ VLC_UNUSED(userdata); + +- sys->output = sys->component->output[0]; +- sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)filter; +- mmal_format_full_copy(sys->output->format, sys->input->format); ++ cma_buf_t * const cb = header->user_data; ++ header->user_data = NULL; ++ cma_buf_unref(cb); // Copes fine with NULL + +- status = mmal_port_format_commit(sys->output); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++ return MMAL_FALSE; ++} + -+ if (dec->fmt_in.video.i_frame_rate > 0 -+ && dec->fmt_in.video.i_frame_rate_base > 0) -+ { -+ fmt->i_frame_rate = dec->fmt_in.video.i_frame_rate; -+ fmt->i_frame_rate_base = dec->fmt_in.video.i_frame_rate_base; -+ } -+ else if (ctx->framerate.num > 0 && ctx->framerate.den > 0) -+ { -+ fmt->i_frame_rate = ctx->framerate.num; -+ fmt->i_frame_rate_base = ctx->framerate.den; -+# if LIBAVCODEC_VERSION_MICRO < 100 -+ // for some reason libav don't thinkg framerate presents actually same thing as in ffmpeg -+ fmt->i_frame_rate_base *= __MAX(ctx->ticks_per_frame, 1); -+# endif -+ } -+ else if (ctx->time_base.num > 0 && ctx->time_base.den > 0) -+ { -+ fmt->i_frame_rate = ctx->time_base.den; -+ fmt->i_frame_rate_base = ctx->time_base.num -+ * __MAX(ctx->ticks_per_frame, 1); -+ } ++static inline unsigned int seq_inc(unsigned int x) ++{ ++ return x + 1 >= 16 ? 1 : x + 1; ++} + -+ /* FIXME we should only set the known values and let the core decide -+ * later of fallbacks, but we can't do that with a boolean */ -+ switch ( ctx->color_range ) -+ { -+ case AVCOL_RANGE_JPEG: -+ fmt->b_color_range_full = true; -+ break; -+ case AVCOL_RANGE_UNSPECIFIED: -+ fmt->b_color_range_full = !vlc_fourcc_IsYUV( fmt->i_chroma ); -+ break; -+ case AVCOL_RANGE_MPEG: -+ default: -+ fmt->b_color_range_full = false; -+ break; -+ } ++static inline unsigned int seq_delta(unsigned int sseq, unsigned int fseq) ++{ ++ return fseq == 0 ? 0 : fseq <= sseq ? sseq - fseq : 15 - (fseq - sseq); ++} + -+ switch( ctx->colorspace ) -+ { -+ case AVCOL_SPC_BT709: -+ fmt->space = COLOR_SPACE_BT709; -+ break; -+ case AVCOL_SPC_SMPTE170M: -+ case AVCOL_SPC_BT470BG: -+ fmt->space = COLOR_SPACE_BT601; -+ break; -+ case AVCOL_SPC_BT2020_NCL: -+ case AVCOL_SPC_BT2020_CL: -+ fmt->space = COLOR_SPACE_BT2020; -+ break; -+ default: -+ break; -+ } ++static picture_t *deinterlace(filter_t * p_filter, picture_t * p_pic) ++{ ++ filter_sys_t * const sys = p_filter->p_sys; ++ picture_t *ret_pics = NULL; ++ MMAL_STATUS_T err; ++ MMAL_BUFFER_HEADER_T * out_buf = NULL; + -+ switch( ctx->color_trc ) -+ { -+ case AVCOL_TRC_LINEAR: -+ fmt->transfer = TRANSFER_FUNC_LINEAR; -+ break; -+ case AVCOL_TRC_GAMMA22: -+ fmt->transfer = TRANSFER_FUNC_SRGB; -+ break; -+ case AVCOL_TRC_BT709: -+ fmt->transfer = TRANSFER_FUNC_BT709; -+ break; -+ case AVCOL_TRC_SMPTE170M: -+ case AVCOL_TRC_BT2020_10: -+ case AVCOL_TRC_BT2020_12: -+ fmt->transfer = TRANSFER_FUNC_BT2020; -+ break; -+#if LIBAVUTIL_VERSION_CHECK( 55, 14, 0, 31, 100) -+ case AVCOL_TRC_ARIB_STD_B67: -+ fmt->transfer = TRANSFER_FUNC_ARIB_B67; -+ break; -+#endif -+#if LIBAVUTIL_VERSION_CHECK( 55, 17, 0, 37, 100) -+ case AVCOL_TRC_SMPTE2084: -+ fmt->transfer = TRANSFER_FUNC_SMPTE_ST2084; -+ break; -+ case AVCOL_TRC_SMPTE240M: -+ fmt->transfer = TRANSFER_FUNC_SMPTE_240; -+ break; -+ case AVCOL_TRC_GAMMA28: -+ fmt->transfer = TRANSFER_FUNC_BT470_BG; -+ break; ++#if TRACE_ALL ++ msg_Dbg(p_filter, "<<< %s", __func__); +#endif -+ default: -+ break; -+ } -+ -+ switch( ctx->color_primaries ) -+ { -+ case AVCOL_PRI_BT709: -+ fmt->primaries = COLOR_PRIMARIES_BT709; -+ break; -+ case AVCOL_PRI_BT470BG: -+ fmt->primaries = COLOR_PRIMARIES_BT601_625; -+ break; -+ case AVCOL_PRI_SMPTE170M: -+ case AVCOL_PRI_SMPTE240M: -+ fmt->primaries = COLOR_PRIMARIES_BT601_525; -+ break; -+ case AVCOL_PRI_BT2020: -+ fmt->primaries = COLOR_PRIMARIES_BT2020; -+ break; -+ default: -+ break; -+ } + -+ switch( ctx->chroma_sample_location ) ++ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic)) + { -+ case AVCHROMA_LOC_LEFT: -+ fmt->chroma_location = CHROMA_LOCATION_LEFT; -+ break; -+ case AVCHROMA_LOC_CENTER: -+ fmt->chroma_location = CHROMA_LOCATION_CENTER; -+ break; -+ case AVCHROMA_LOC_TOPLEFT: -+ fmt->chroma_location = CHROMA_LOCATION_TOP_LEFT; -+ break; -+ default: -+ break; -+ } ++ // ****** Breaks on opaque (at least) + -+ return 0; -+} ++ if (sys->input->is_enabled) ++ mmal_port_disable(sys->input); ++#if 0 ++ if (sys->output->is_enabled) ++ mmal_port_disable(sys->output); + -+static int lavc_UpdateVideoFormat(decoder_t *dec, AVCodecContext *ctx, -+ enum AVPixelFormat fmt, -+ enum AVPixelFormat swfmt) -+{ -+ video_format_t fmt_out; -+ int val; -+#if TRACE_ALL -+ msg_Dbg(dec, "<<< %s", __func__); ++ mmal_format_full_copy(sys->output->format, sys->input->format); ++ mmal_port_format_commit(sys->output); ++ sys->output->buffer_num = 30; ++ sys->output->buffer_size = sys->input->buffer_size_recommended; ++ mmal_port_enable(sys->output, di_output_port_cb); +#endif -+ val = lavc_GetVideoFormat(dec, &fmt_out, ctx, fmt, swfmt); -+ if (val) ++ if (mmal_port_format_commit(sys->input) != MMAL_SUCCESS) ++ msg_Err(p_filter, "Failed to update pic format"); ++ sys->input->buffer_num = 30; ++ sys->input->buffer_size = sys->input->buffer_size_recommended; ++ mmal_log_dump_format(sys->input->format); ++ } ++ ++ // Reenable stuff if the last thing we did was flush ++ // Output should always be enabled ++ if (!sys->input->is_enabled && ++ (err = mmal_port_enable(sys->input, di_input_port_cb)) != MMAL_SUCCESS) + { -+ msg_Dbg(dec, "Failed to get format"); -+ return val; ++ msg_Err(p_filter, "Input port reenable failed"); ++ goto fail; + } + -+ /* always have date in fields/ticks units */ -+ if(dec->p_sys->pts.i_divider_num) -+ date_Change(&dec->p_sys->pts, fmt_out.i_frame_rate * -+ __MAX(ctx->ticks_per_frame, 1), -+ fmt_out.i_frame_rate_base); ++ if (!sys->is_cma) ++ { ++ // Fill output from anything that has turned up in pool Q ++ if (hw_mmal_port_pool_ref_fill(sys->out_ppr) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Out port fill fail"); ++ goto fail; ++ } + } + else -+ date_Init(&dec->p_sys->pts, fmt_out.i_frame_rate * -+ __MAX(ctx->ticks_per_frame, 1), -+ fmt_out.i_frame_rate_base); ++ { ++ // We are expecting one in - one out so simply wedge a new bufer ++ // into the output port. Flow control will happen on cma alloc. + -+ fmt_out.p_palette = dec-> fmt_out.video.p_palette; -+ dec->fmt_out.video.p_palette = NULL; ++ if ((out_buf = mmal_queue_get(sys->out_pool->queue)) == NULL) ++ { ++ // Should never happen ++ msg_Err(p_filter, "Failed to get output buffer"); ++ goto fail; ++ } ++ mmal_buffer_header_reset(out_buf); + +- sys->output->buffer_num = 3; ++ // Attach cma_buf to the buffer & ensure it is freed when the buffer is released ++ // On a good send callback the pic will be extracted to avoid this ++ mmal_buffer_header_pre_release_cb_set(out_buf, out_buffer_pre_release_cb, p_filter); + -+ es_format_Change(&dec->fmt_out, VIDEO_ES, fmt_out.i_chroma); -+ dec->fmt_out.video = fmt_out; -+ dec->fmt_out.video.orientation = dec->fmt_in.video.orientation; -+ dec->fmt_out.video.projection_mode = dec->fmt_in.video.projection_mode; -+ dec->fmt_out.video.multiview_mode = dec->fmt_in.video.multiview_mode; -+ dec->fmt_out.video.pose = dec->fmt_in.video.pose; -+ if ( dec->fmt_in.video.mastering.max_luminance ) -+ dec->fmt_out.video.mastering = dec->fmt_in.video.mastering; -+ dec->fmt_out.video.lighting = dec->fmt_in.video.lighting; ++ cma_buf_t * const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, sys->output->buffer_size); ++ if ((out_buf->user_data = cb) == NULL) // Check & attach cb to buf ++ { ++ char dbuf0[5]; ++ msg_Err(p_filter, "Failed to alloc CMA buf: fmt=%s, size=%d", ++ str_fourcc(dbuf0, p_pic->format.i_chroma), ++ sys->output->buffer_size); ++ goto fail; ++ } ++ const unsigned int vc_h = cma_buf_vc_handle(cb); // Cannot coerce without going via variable ++ out_buf->data = (uint8_t *)vc_h; ++ out_buf->alloc_size = sys->output->buffer_size; + -+ val = decoder_UpdateVideoFormat(dec); +#if TRACE_ALL -+ msg_Dbg(dec, ">>> %s: rv=%d", __func__, val); ++ msg_Dbg(p_filter, "Out buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d, pts=%lld", ++ p_pic, out_buf->data, out_buf->user_data, out_buf->flags, ++ out_buf->length, out_buf->alloc_size, (long long)out_buf->pts); +#endif -+ return val; -+} -+ -+static int OpenVideoCodec( decoder_t *p_dec ) -+{ -+ decoder_sys_t *p_sys = p_dec->p_sys; -+ AVCodecContext *ctx = p_sys->p_context; -+ const AVCodec *codec = p_sys->p_codec; -+ int ret; -+ -+ if( ctx->extradata_size <= 0 ) -+ { -+ if( codec->id == AV_CODEC_ID_VC1 || -+ codec->id == AV_CODEC_ID_THEORA ) + +- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) { +- MMAL_PARAMETER_UINT32_T extra_buffers = { +- { MMAL_PARAMETER_EXTRA_BUFFERS, sizeof(MMAL_PARAMETER_UINT32_T) }, +- 5 +- }; +- status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", +- status, mmal_status_to_string(status)); +- goto out; ++ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS) + { -+ msg_Warn( p_dec, "waiting for extra data for codec %s", -+ codec->name ); -+ return 1; -+ } ++ msg_Err(p_filter, "Send buffer to output failed"); ++ goto fail; + } ++ out_buf = NULL; + } + +- MMAL_PARAMETER_BOOLEAN_T zero_copy = { +- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, +- 1 +- }; ++ // Stuff into input ++ // We assume the BH is already set up with values reflecting pic date etc. ++ { ++ MMAL_BUFFER_HEADER_T * const pic_buf = hw_mmal_pic_buf_replicated(p_pic, sys->in_pool); + -+ ctx->width = p_dec->fmt_in.video.i_visible_width; -+ ctx->height = p_dec->fmt_in.video.i_visible_height; -+ -+ ctx->coded_width = p_dec->fmt_in.video.i_width; -+ ctx->coded_height = p_dec->fmt_in.video.i_height; ++ if (pic_buf == NULL) ++ { ++ msg_Err(p_filter, "Pic has not attached buffer"); ++ goto fail; ++ } + +- status = mmal_port_parameter_set(sys->output, &zero_copy.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", +- sys->output->name, status, mmal_status_to_string(status)); +- goto out; ++ picture_Release(p_pic); + -+ ctx->bits_per_coded_sample = p_dec->fmt_in.video.i_bits_per_pixel; -+ p_sys->pix_fmt = AV_PIX_FMT_NONE; -+ p_sys->profile = -1; -+ p_sys->level = -1; -+ cc_Init( &p_sys->cc ); ++ // Add a sequence to the flags so we can track what we have actually ++ // deinterlaced ++ pic_buf->flags = (pic_buf->flags & ~(0xfU * MMAL_BUFFER_HEADER_FLAG_USER0)) | (sys->seq_in * (MMAL_BUFFER_HEADER_FLAG_USER0)); ++ sys->seq_in = seq_inc(sys->seq_in); + -+ set_video_color_settings( &p_dec->fmt_in.video, ctx ); -+ if( p_dec->fmt_in.video.i_frame_rate_base && -+ p_dec->fmt_in.video.i_frame_rate && -+ (double) p_dec->fmt_in.video.i_frame_rate / -+ p_dec->fmt_in.video.i_frame_rate_base < 6 ) ++ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Send buffer to input failed"); ++ mmal_buffer_header_release(pic_buf); ++ goto fail; + } + } + +- status = mmal_port_enable(sys->output, output_port_cb); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to enable output port %s (status=%"PRIx32" %s)", +- sys->output->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++ // Return anything that is in the out Q + { -+ ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; -+ } ++ picture_t ** pp_pic = &ret_pics; + -+ post_mt( p_sys ); -+ ret = ZcFfmpeg_OpenCodec( p_dec, ctx, codec ); -+ wait_mt( p_sys ); -+ if( ret < 0 ) -+ return ret; ++ // Advanced di has a 3 frame latency, so if the seq delta is greater ++ // than that then we are expecting at least two frames of output. Wait ++ // for one of those. ++ // seq_in is seq of the next frame we are going to submit (1-15, no 0) ++ // seq_out is last frame we removed from Q ++ // So after 4 frames sent (1st time we want to wait), 0 rx seq_in=5, seq_out=15, delta=5 + -+ switch( ctx->active_thread_type ) -+ { -+ case FF_THREAD_FRAME: -+ msg_Dbg( p_dec, "using frame thread mode with %d threads", -+ ctx->thread_count ); -+ break; -+ case FF_THREAD_SLICE: -+ msg_Dbg( p_dec, "using slice thread mode with %d threads", -+ ctx->thread_count ); -+ break; -+ case 0: -+ if( ctx->thread_count > 1 ) -+ msg_Warn( p_dec, "failed to enable threaded decoding" ); -+ break; -+ default: -+ msg_Warn( p_dec, "using unknown thread mode with %d threads", -+ ctx->thread_count ); -+ break; -+ } -+ return 0; -+} ++ while ((out_buf = (seq_delta(sys->seq_in, sys->seq_out) >= 5 ? mmal_queue_timedwait(sys->out_q, 1000) : mmal_queue_get(sys->out_q))) != NULL) ++ { ++ const unsigned int seq_out = (out_buf->flags / MMAL_BUFFER_HEADER_FLAG_USER0) & 0xf; ++ int rv; + -+/***************************************************************************** -+ * InitVideo: initialize the video decoder -+ ***************************************************************************** -+ * the ffmpeg codec will be opened, some memory allocated. The vout is not yet -+ * opened (done after the first decoded frame). -+ *****************************************************************************/ -+static int MmalAvcodecOpenDecoder( vlc_object_t *obj ) -+{ -+ decoder_t *p_dec = (decoder_t *)obj; -+ const AVCodec *p_codec; ++ picture_t * out_pic; + -+ int extra_buffers = var_InheritInteger(p_dec, MMAL_AVCODEC_BUFFERS); ++ if (sys->is_cma) ++ { ++ // Alloc pic ++ if ((out_pic = filter_NewPicture(p_filter)) == NULL) ++ { ++ // Can't alloc pic - just stop extraction ++ mmal_queue_put_back(sys->out_q, out_buf); ++ out_buf = NULL; ++ msg_Warn(p_filter, "Failed to alloc new filter output pic"); ++ break; ++ } + -+ if (extra_buffers < 0) -+ { -+ extra_buffers = p_dec->fmt_in.video.i_height * p_dec->fmt_in.video.i_width >= 1920 * 1088 ? -+ BUFFERS_IN_FLIGHT_UHD : BUFFERS_IN_FLIGHT; -+ } ++ // Extract cma_buf from buf & attach to pic ++ cma_buf_t * const cb = (cma_buf_t *)out_buf->user_data; ++ if ((rv = cma_buf_pic_attach(cb, out_pic)) != VLC_SUCCESS) ++ { ++ char dbuf0[5]; ++ msg_Err(p_filter, "Failed to attach CMA to pic: fmt=%s err=%d", ++ str_fourcc(dbuf0, out_pic->format.i_chroma), ++ rv); ++ // cb still attached to buffer and will be freed with it ++ goto fail; ++ } ++ out_buf->user_data = NULL; + -+ if (extra_buffers <= 0) -+ { -+ msg_Dbg(p_dec, "%s: extra_buffers=%d - cannot use module", __func__, extra_buffers); -+ return VLC_EGENERIC; -+ } ++ buf_to_pic_copy_props(out_pic, out_buf); + -+ const vcsm_init_type_t vcsm_type = cma_vcsm_init(); -+ const int vcsm_size = -+ vcsm_type == VCSM_INIT_LEGACY ? hw_mmal_get_gpu_mem() : 512 << 20; ++ // Set pic data pointers from buf aux info now it has it ++ if ((rv = cma_pic_set_data(out_pic, sys->output->format, out_buf)) != VLC_SUCCESS) ++ { ++ char dbuf0[5]; ++ msg_Err(p_filter, "Failed to set data: fmt=%s, rv=%d", ++ str_fourcc(dbuf0, sys->output->format->encoding), ++ rv); ++ } + -+#if 1 -+ { -+ char buf1[5], buf2[5], buf2a[5]; -+ char buf3[5], buf4[5]; -+ uint32_t in_fcc = 0; -+ msg_Dbg(p_dec, "%s: <<< (%s/%s)[%s] %dx%d -> (%s/%s) %dx%d [%s/%d] xb:%d", __func__, -+ str_fourcc(buf1, p_dec->fmt_in.i_codec), -+ str_fourcc(buf2, p_dec->fmt_in.video.i_chroma), -+ str_fourcc(buf2a, in_fcc), -+ p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height, -+ str_fourcc(buf3, p_dec->fmt_out.i_codec), -+ str_fourcc(buf4, p_dec->fmt_out.video.i_chroma), -+ p_dec->fmt_out.video.i_width, p_dec->fmt_out.video.i_height, -+ cma_vcsm_init_str(vcsm_type), vcsm_size, extra_buffers); -+ } -+#endif ++ out_buf->user_data = NULL; // Responsability for this pic no longer with buffer ++ mmal_buffer_header_release(out_buf); ++ } ++ else ++ { ++ out_pic = di_alloc_opaque(p_filter, out_buf); + -+ if( vcsm_type == VCSM_INIT_NONE ) -+ return VLC_EGENERIC; -+#if 1 -+ if( (p_dec->fmt_in.i_codec != VLC_CODEC_HEVC && -+ (vcsm_type == VCSM_INIT_CMA || vcsm_size < (96 << 20))) || -+ (p_dec->fmt_in.i_codec == VLC_CODEC_HEVC && -+ vcsm_size < (128 << 20))) -+ { -+ cma_vcsm_exit(vcsm_type); -+ return VLC_EGENERIC; -+ } ++ if (out_pic == NULL) { ++ msg_Warn(p_filter, "Failed to alloc new filter output pic"); ++ mmal_queue_put_back(sys->out_q, out_buf); // Wedge buf back into Q in the hope we can alloc a pic later ++ out_buf = NULL; ++ break; ++ } ++ } ++ out_buf = NULL; // Now attached to pic or recycled ++ ++#if TRACE_ALL ++ msg_Dbg(p_filter, "-- %s: Q pic=%p: seq_in=%d, seq_out=%d, delta=%d", __func__, out_pic, sys->seq_in, seq_out, seq_delta(sys->seq_in, seq_out)); +#endif + -+ AVCodecContext *p_context = ZcFfmpeg_AllocContext( p_dec, &p_codec ); -+ if( p_context == NULL ) -+ { -+ cma_vcsm_exit(vcsm_type); -+ return VLC_EGENERIC; -+ } ++ *pp_pic = out_pic; ++ pp_pic = &out_pic->p_next; + -+ int i_val; ++ // Ignore 0 seqs ++ // Don't think these should actually happen ++ if (seq_out != 0) ++ sys->seq_out = seq_out; ++ } + -+ /* Allocate the memory needed to store the decoder's structure */ -+ decoder_sys_t *p_sys = calloc( 1, sizeof(*p_sys) ); -+ if( unlikely(p_sys == NULL) ) -+ { -+ avcodec_free_context( &p_context ); -+ cma_vcsm_exit(vcsm_type); -+ return VLC_ENOMEM; -+ } ++ // Crash on lockup ++ assert(ret_pics != NULL || seq_delta(sys->seq_in, sys->seq_out) < 5); + } + +- status = mmal_component_enable(sys->component); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to enable component %s (status=%"PRIx32" %s)", +- sys->component->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s: pic=%p", __func__, ret_pics); ++#endif + -+ p_dec->p_sys = p_sys; -+ p_sys->p_context = p_context; -+ p_sys->p_codec = p_codec; -+ p_sys->p_dec = p_dec; -+// p_sys->p_va = NULL; -+ p_sys->cma_in_flight_max = extra_buffers; -+ p_sys->vcsm_init_type = vcsm_type; -+ vlc_sem_init( &p_sys->sem_mt, 0 ); ++ return ret_pics; + -+ /* ***** Fill p_context with init values ***** */ -+ p_context->codec_tag = ffmpeg_CodecTag( p_dec->fmt_in.i_original_fourcc ? -+ p_dec->fmt_in.i_original_fourcc : p_dec->fmt_in.i_codec ); ++fail: ++ if (out_buf != NULL) ++ mmal_buffer_header_release(out_buf); ++ picture_Release(p_pic); ++ return NULL; ++} + -+ /* ***** Get configuration of ffmpeg plugin ***** */ -+ p_context->workaround_bugs = -+ var_InheritInteger( p_dec, "avcodec-workaround-bugs" ); -+ p_context->err_recognition = -+ var_InheritInteger( p_dec, "avcodec-error-resilience" ); ++static void di_flush(filter_t *p_filter) ++{ ++ filter_sys_t * const sys = p_filter->p_sys; + -+ if( var_CreateGetBool( p_dec, "grayscale" ) ) -+ p_context->flags |= AV_CODEC_FLAG_GRAY; ++#if TRACE_ALL ++ msg_Dbg(p_filter, "<<< %s", __func__); ++#endif + -+ /* ***** Output always the frames ***** */ -+ p_context->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT; ++ if (sys->input != NULL && sys->input->is_enabled) ++ mmal_port_disable(sys->input); + -+ i_val = var_CreateGetInteger( p_dec, "avcodec-skiploopfilter" ); -+ if( i_val >= 4 ) p_context->skip_loop_filter = AVDISCARD_ALL; -+ else if( i_val == 3 ) p_context->skip_loop_filter = AVDISCARD_NONKEY; -+ else if( i_val == 2 ) p_context->skip_loop_filter = AVDISCARD_BIDIR; -+ else if( i_val == 1 ) p_context->skip_loop_filter = AVDISCARD_NONREF; -+ else p_context->skip_loop_filter = AVDISCARD_DEFAULT; ++ if (sys->output != NULL && sys->output->is_enabled) ++ { ++ if (sys->is_cma) ++ { ++ MMAL_BUFFER_HEADER_T * buf; ++ mmal_port_disable(sys->output); ++ while ((buf = mmal_queue_get(sys->out_q)) != NULL) ++ mmal_buffer_header_release(buf); ++ } ++ else ++ { ++ // Wedge anything we've got into the output port as that will free the underlying buffers ++ fill_output_from_q(p_filter, sys, sys->out_q); + -+ if( var_CreateGetBool( p_dec, "avcodec-fast" ) ) -+ p_context->flags2 |= AV_CODEC_FLAG2_FAST; ++ mmal_port_disable(sys->output); + -+ /* ***** libavcodec frame skipping ***** */ -+ p_sys->b_hurry_up = var_CreateGetBool( p_dec, "avcodec-hurry-up" ); -+ p_sys->b_show_corrupted = var_CreateGetBool( p_dec, "avcodec-corrupted" ); ++ // If that dumped anything real into the out_q then have another go ++ if (mmal_queue_length(sys->out_q) != 0) ++ { ++ mmal_port_enable(sys->output, di_output_port_cb); ++ fill_output_from_q(p_filter, sys, sys->out_q); ++ mmal_port_disable(sys->output); ++ // Out q should now be empty & should remain so until the input is reenabled ++ } ++ } ++ mmal_port_enable(sys->output, di_output_port_cb); + -+ i_val = var_CreateGetInteger( p_dec, "avcodec-skip-frame" ); -+ if( i_val >= 4 ) p_sys->i_skip_frame = AVDISCARD_ALL; -+ else if( i_val == 3 ) p_sys->i_skip_frame = AVDISCARD_NONKEY; -+ else if( i_val == 2 ) p_sys->i_skip_frame = AVDISCARD_BIDIR; -+ else if( i_val == 1 ) p_sys->i_skip_frame = AVDISCARD_NONREF; -+ else if( i_val == -1 ) p_sys->i_skip_frame = AVDISCARD_NONE; -+ else p_sys->i_skip_frame = AVDISCARD_DEFAULT; -+ p_context->skip_frame = p_sys->i_skip_frame; ++ // Leaving the input disabled is fine - but we want to leave the output enabled ++ // so we can retrieve buffers that are still bound to pictures + } + +- sys->filtered_pictures = mmal_queue_create(); ++ sys->seq_in = 1; ++ sys->seq_out = 15; + +- filter->pf_video_filter = deinterlace; +- filter->pf_flush = flush; ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s", __func__); ++#endif ++} + +- vlc_sem_init(&sys->sem, 0); + +-out: +- if (ret != VLC_SUCCESS) +- Close(filter); ++static void pass_flush(filter_t *p_filter) ++{ ++ // Nothing to do ++ VLC_UNUSED(p_filter); ++} + +- return ret; ++static picture_t * pass_deinterlace(filter_t * p_filter, picture_t * p_pic) ++{ ++ VLC_UNUSED(p_filter); + -+ i_val = var_CreateGetInteger( p_dec, "avcodec-skip-idct" ); -+ if( i_val >= 4 ) p_context->skip_idct = AVDISCARD_ALL; -+ else if( i_val == 3 ) p_context->skip_idct = AVDISCARD_NONKEY; -+ else if( i_val == 2 ) p_context->skip_idct = AVDISCARD_BIDIR; -+ else if( i_val == 1 ) p_context->skip_idct = AVDISCARD_NONREF; -+ else if( i_val == -1 ) p_context->skip_idct = AVDISCARD_NONE; -+ else p_context->skip_idct = AVDISCARD_DEFAULT; ++ p_pic->b_progressive = true; ++ return p_pic; + } + +-static void Close(filter_t *filter) + -+ /* ***** libavcodec direct rendering ***** */ -+ p_sys->b_direct_rendering = false; -+ atomic_init(&p_sys->b_dr_failure, false); -+ if( var_CreateGetBool( p_dec, "avcodec-dr" ) && -+ (p_codec->capabilities & AV_CODEC_CAP_DR1) && -+ /* No idea why ... but this fixes flickering on some TSCC streams */ -+ p_sys->p_codec->id != AV_CODEC_ID_TSCC && -+ p_sys->p_codec->id != AV_CODEC_ID_CSCD && -+ p_sys->p_codec->id != AV_CODEC_ID_CINEPAK ) -+ { -+ /* Some codecs set pix_fmt only after the 1st frame has been decoded, -+ * so we need to do another check in ffmpeg_GetFrameBuf() */ -+ p_sys->b_direct_rendering = true; ++static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) + { +- filter_sys_t *sys = filter->p_sys; +- MMAL_BUFFER_HEADER_T *buffer; ++ filter_t *filter = (filter_t *)port->userdata; ++ MMAL_STATUS_T status; + +- if (!sys) ++ if (buffer->cmd == MMAL_EVENT_ERROR) { ++ status = *(uint32_t *)buffer->data; ++ msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status, ++ mmal_status_to_string(status)); + } + -+ p_context->get_format = ZcGetFormat; -+#if 0 -+ p_context->get_format = ffmpeg_GetFormat; -+ /* Always use our get_buffer wrapper so we can calculate the -+ * PTS correctly */ -+ p_context->get_buffer2 = lavc_GetFrame; -+ p_context->opaque = p_dec; -+#endif ++ mmal_buffer_header_reset(buffer); ++ mmal_buffer_header_release(buffer); ++} + -+ int i_thread_count = var_InheritInteger( p_dec, "avcodec-threads" ); -+ if( i_thread_count <= 0 ) -+#if 1 -+ { -+ // Pick 5 threads for everything on Pi except for HEVC where the h/w -+ // really limits the useful size to 3 -+ i_thread_count = p_codec->id == AV_CODEC_ID_HEVC ? 3 : 5; -+ } -+#else -+ { -+ i_thread_count = vlc_GetCPUCount(); -+ if( i_thread_count > 1 ) -+ i_thread_count++; ++static void CloseMmalDeinterlace(filter_t *filter) ++{ ++ filter_sys_t * const sys = filter->p_sys; + -+ //FIXME: take in count the decoding time -+#if VLC_WINSTORE_APP -+ i_thread_count = __MIN( i_thread_count, 6 ); -+#else -+ i_thread_count = __MIN( i_thread_count, p_codec->id == AV_CODEC_ID_HEVC ? 10 : 6 ); -+#endif -+ } -+ i_thread_count = __MIN( i_thread_count, p_codec->id == AV_CODEC_ID_HEVC ? 32 : 16 ); ++#if TRACE_ALL ++ msg_Dbg(filter, "<<< %s", __func__); +#endif -+ msg_Dbg( p_dec, "allowing %d thread(s) for decoding", i_thread_count ); -+ p_context->thread_count = i_thread_count; -+ p_context->thread_safe_callbacks = true; + -+ switch( p_codec->id ) ++ if (sys == NULL) + return; + +- if (sys->component && sys->component->control->is_enabled) +- mmal_port_disable(sys->component->control); ++ if (sys->use_passthrough) + { -+ case AV_CODEC_ID_MPEG4: -+ case AV_CODEC_ID_H263: -+ p_context->thread_type = 0; -+ break; -+ case AV_CODEC_ID_MPEG1VIDEO: -+ case AV_CODEC_ID_MPEG2VIDEO: -+ p_context->thread_type &= ~FF_THREAD_SLICE; -+ /* fall through */ -+# if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 1, 0)) -+ case AV_CODEC_ID_H264: -+ case AV_CODEC_ID_VC1: -+ case AV_CODEC_ID_WMV3: -+ p_context->thread_type &= ~FF_THREAD_FRAME; -+# endif -+ default: -+ break; ++ free(sys); ++ return; + } + +- if (sys->input && sys->input->is_enabled) +- mmal_port_disable(sys->input); ++ di_flush(filter); + +- if (sys->output && sys->output->is_enabled) +- mmal_port_disable(sys->output); ++ if (sys->component && sys->component->control->is_enabled) ++ mmal_port_disable(sys->component->control); + + if (sys->component && sys->component->is_enabled) + mmal_component_disable(sys->component); + +- while ((buffer = mmal_queue_get(sys->filtered_pictures))) { +- picture_t *pic = (picture_t *)buffer->user_data; +- picture_Release(pic); ++ if (sys->in_pool != NULL) ++ mmal_pool_destroy(sys->in_pool); + -+ if( p_context->thread_type & FF_THREAD_FRAME ) -+ p_dec->i_extra_picture_buffers = 2 * p_context->thread_count; ++ hw_mmal_port_pool_ref_release(sys->out_ppr, false); ++ // Once we exit filter & sys are invalid so mark as such ++ if (sys->output != NULL) ++ sys->output->userdata = NULL; + -+ /* ***** misc init ***** */ -+ date_Init(&p_sys->pts, 1, 30001); -+ date_Set(&p_sys->pts, VLC_TS_INVALID); -+ p_sys->b_first_frame = true; -+ p_sys->i_late_frames = 0; -+ p_sys->b_from_preroll = false; -+ -+ /* Set output properties */ -+ if( ZcGetVlcChroma( &p_dec->fmt_out.video, p_context->pix_fmt ) != VLC_SUCCESS ) -+ { -+ /* we are doomed. but not really, because most codecs set their pix_fmt later on */ -+// p_dec->fmt_out.i_codec = VLC_CODEC_I420; -+ p_dec->fmt_out.i_codec = VLC_CODEC_MMAL_ZC_I420; -+ } -+ p_dec->fmt_out.i_codec = p_dec->fmt_out.video.i_chroma; -+ -+ p_dec->fmt_out.video.orientation = p_dec->fmt_in.video.orientation; -+ -+ if( p_dec->fmt_in.video.p_palette ) { -+ p_sys->palette_sent = false; -+ p_dec->fmt_out.video.p_palette = malloc( sizeof(video_palette_t) ); -+ if( p_dec->fmt_out.video.p_palette ) -+ *p_dec->fmt_out.video.p_palette = *p_dec->fmt_in.video.p_palette; -+ } else -+ p_sys->palette_sent = true; -+ -+ if ((p_sys->cma_pool = cma_buf_pool_new(p_sys->cma_in_flight_max, p_sys->cma_in_flight_max, false, "mmal_avcodec")) == NULL) -+ { -+ msg_Err(p_dec, "CMA pool alloc failure"); -+ goto fail; -+ } -+ -+ /* ***** init this codec with special data ***** */ -+ ffmpeg_InitCodec( p_dec ); -+ -+ /* ***** Open the codec ***** */ -+ if( OpenVideoCodec( p_dec ) < 0 ) -+ { -+ vlc_sem_destroy( &p_sys->sem_mt ); -+ free( p_sys ); -+ avcodec_free_context( &p_context ); -+ return VLC_EGENERIC; -+ } -+ -+ p_dec->pf_decode = DecodeVideo; -+ p_dec->pf_flush = Flush; -+ -+ /* XXX: Writing input format makes little sense. */ -+ if( p_context->profile != FF_PROFILE_UNKNOWN ) -+ p_dec->fmt_in.i_profile = p_context->profile; -+ if( p_context->level != FF_LEVEL_UNKNOWN ) -+ p_dec->fmt_in.i_level = p_context->level; -+ -+#if 1 -+ // Most of the time we have nothing useful by way of a format here -+ // wait till we've decoded something -+#else -+ // Update output format -+ if (lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, -+ p_context->pix_fmt) != 0) ++ if (sys->is_cma) + { -+ msg_Err(p_dec, "Unable to update format: pix_fmt=%d", p_context->pix_fmt); -+// goto fail; -+ } -+#endif -+ -+#if TRACE_ALL -+ msg_Dbg(p_dec, "<<< %s: OK", __func__); -+#endif -+ return VLC_SUCCESS; ++ if (sys->output && sys->output->is_enabled) ++ mmal_port_disable(sys->output); + -+fail: -+ MmalAvcodecCloseDecoder(VLC_OBJECT(p_dec)); ++ cma_buf_pool_deletez(&sys->cma_out_pool); + -+#if TRACE_ALL -+ msg_Dbg(p_dec, "<<< %s: FAIL", __func__); -+#endif ++ if (sys->out_pool != NULL) ++ mmal_pool_destroy(sys->out_pool); + } + +- if (sys->filtered_pictures) +- mmal_queue_destroy(sys->filtered_pictures); ++ if (sys->out_q != NULL) ++ mmal_queue_destroy(sys->out_q); + + if (sys->component) + mmal_component_release(sys->component); + +- vlc_sem_destroy(&sys->sem); ++ cma_vcsm_exit(sys->vcsm_init_type); + -+ return VLC_EGENERIC; + free(sys); +} + -+/***************************************************************************** -+ * Flush: -+ *****************************************************************************/ -+static void Flush( decoder_t *p_dec ) + +- bcm_host_deinit(); ++static bool is_fmt_valid_in(const vlc_fourcc_t fmt) +{ -+ decoder_sys_t *p_sys = p_dec->p_sys; -+ AVCodecContext *p_context = p_sys->p_context; -+ -+#if TRACE_ALL -+ msg_Dbg(p_dec, "<<< %s", __func__); -+#endif -+ -+ date_Set(&p_sys->pts, VLC_TS_INVALID); /* To make sure we recover properly */ -+ p_sys->i_late_frames = 0; -+ cc_Flush( &p_sys->cc ); -+ -+ /* Abort pictures in order to unblock all avcodec workers threads waiting -+ * for a picture. This will avoid a deadlock between avcodec_flush_buffers -+ * and workers threads */ -+// It would probably be good to use AbortPicture but that often deadlocks on close -+// and given that we wait for pics in the main thread it should be unneeded (whereas -+// cma is alloced in the depths of ffmpeg on its own threads) -+// decoder_AbortPictures( p_dec, true ); -+ cma_buf_pool_cancel(p_sys->cma_pool); ++ return fmt == VLC_CODEC_MMAL_OPAQUE || ++ fmt == VLC_CODEC_MMAL_ZC_I420 || ++ fmt == VLC_CODEC_MMAL_ZC_SAND8; + } + +-static int send_output_buffer(filter_t *filter) ++static int OpenMmalDeinterlace(filter_t *filter) + { +- filter_sys_t *sys = filter->p_sys; +- MMAL_BUFFER_HEADER_T *buffer; ++ int32_t frame_duration = filter->fmt_in.video.i_frame_rate != 0 ? ++ CLOCK_FREQ * filter->fmt_in.video.i_frame_rate_base / ++ filter->fmt_in.video.i_frame_rate : 0; + -+ post_mt( p_sys ); -+ /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */ -+ if( avcodec_is_open( p_context ) ) -+ avcodec_flush_buffers( p_context ); -+ wait_mt( p_sys ); ++ int ret = VLC_EGENERIC; + MMAL_STATUS_T status; +- picture_t *picture; +- int ret = 0; ++ filter_sys_t *sys; + -+ /* Reset cancel state to false */ -+ cma_buf_pool_uncancel(p_sys->cma_pool); -+// decoder_AbortPictures( p_dec, false ); ++ msg_Dbg(filter, "<<< %s", __func__); + -+#if TRACE_ALL -+ msg_Dbg(p_dec, ">>> %s", __func__); -+#endif ++ if (!is_fmt_valid_in(filter->fmt_in.video.i_chroma) || ++ filter->fmt_out.video.i_chroma != filter->fmt_in.video.i_chroma) ++ return VLC_EGENERIC; + +- if (!sys->output->is_enabled) { +- ret = VLC_EGENERIC; +- goto out; ++ sys = calloc(1, sizeof(filter_sys_t)); ++ if (!sys) ++ return VLC_ENOMEM; ++ filter->p_sys = sys; + -+} ++ sys->seq_in = 1; ++ sys->seq_out = 15; ++ sys->is_cma = is_cma_buf_pic_chroma(filter->fmt_out.video.i_chroma); + -+static bool check_block_validity( decoder_sys_t *p_sys, block_t *block ) -+{ -+ if( !block) -+ return true; ++ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { ++ msg_Err(filter, "VCSM init failed"); ++ goto fail; ++ } + -+ if( block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) ) ++ if (!rpi_use_qpu_deinterlace()) + { -+ date_Set( &p_sys->pts, VLC_TS_INVALID ); /* To make sure we recover properly */ -+ cc_Flush( &p_sys->cc ); ++ sys->half_rate = true; ++ sys->use_qpu = false; ++ sys->use_fast = true; ++ } ++ else ++ { ++ sys->half_rate = false; ++ sys->use_qpu = true; ++ sys->use_fast = false; ++ } ++ sys->use_passthrough = false; + -+ p_sys->i_late_frames = 0; -+ if( block->i_flags & BLOCK_FLAG_CORRUPTED ) ++ if (filter->fmt_in.video.i_width * filter->fmt_in.video.i_height > 768 * 576) ++ { ++ // We get stressed if we have to try too hard - so make life easier ++ sys->half_rate = true; ++ // Also check we actually have enough memory to do this ++ // Memory always comes from GPU if Opaque ++ // Assume we have plenty of memory if it comes from CMA ++ if ((!sys->is_cma || sys->vcsm_init_type == VCSM_INIT_LEGACY) && ++ hw_mmal_get_gpu_mem() < (96 << 20)) + { -+ block_Release( block ); -+ return false; ++ sys->use_passthrough = true; ++ msg_Warn(filter, "Deinterlace bypassed due to lack of GPU memory"); + } + } + +- picture = filter_NewPicture(filter); +- if (!picture) { +- msg_Warn(filter, "Failed to get new picture"); +- ret = -1; +- goto out; ++ if (var_InheritBool(filter, MMAL_DEINTERLACE_NO_QPU)) ++ sys->use_qpu = false; ++ if (var_InheritBool(filter, MMAL_DEINTERLACE_ADV)) ++ { ++ sys->use_fast = false; ++ sys->use_passthrough = false; + } -+ return true; -+} -+ -+static bool check_block_being_late( decoder_sys_t *p_sys, block_t *block, mtime_t current_time) -+{ -+ if( !block ) -+ return false; -+ if( block->i_flags & BLOCK_FLAG_PREROLL ) ++ if (var_InheritBool(filter, MMAL_DEINTERLACE_FAST)) + { -+ /* Do not care about late frames when prerolling -+ * TODO avoid decoding of non reference frame -+ * (ie all B except for H264 where it depends only on nal_ref_idc) */ -+ p_sys->i_late_frames = 0; -+ p_sys->b_from_preroll = true; -+ p_sys->i_last_late_delay = INT64_MAX; ++ sys->use_fast = true; ++ sys->use_passthrough = false; + } ++ if (var_InheritBool(filter, MMAL_DEINTERLACE_NONE)) ++ sys->use_passthrough = true; ++ if (var_InheritBool(filter, MMAL_DEINTERLACE_FULL_RATE)) ++ sys->half_rate = false; ++ if (var_InheritBool(filter, MMAL_DEINTERLACE_HALF_RATE)) ++ sys->half_rate = true; + -+ if( p_sys->i_late_frames <= 0 ) -+ return false; -+ -+ if( current_time - p_sys->i_late_frames_start > (5*CLOCK_FREQ)) ++ if (sys->use_passthrough) + { -+ date_Set( &p_sys->pts, VLC_TS_INVALID ); /* To make sure we recover properly */ -+ block_Release( block ); -+ p_sys->i_late_frames--; -+ return true; ++ filter->pf_video_filter = pass_deinterlace; ++ filter->pf_flush = pass_flush; ++ // Don't need VCSM - get rid of it now ++ cma_vcsm_exit(sys->vcsm_init_type); ++ sys->vcsm_init_type = VCSM_INIT_NONE; ++ return 0; + } -+ return false; -+} -+ -+static bool check_frame_should_be_dropped( decoder_sys_t *p_sys, AVCodecContext *p_context, bool *b_need_output_picture ) -+{ -+ if( p_sys->i_late_frames <= 4) -+ return false; + -+ *b_need_output_picture = false; -+ if( p_sys->i_late_frames < 12 ) -+ { -+ p_context->skip_frame = -+ (p_sys->i_skip_frame <= AVDISCARD_NONREF) ? -+ AVDISCARD_NONREF : p_sys->i_skip_frame; -+ } -+ else -+ { -+ /* picture too late, won't decode -+ * but break picture until a new I, and for mpeg4 ...*/ -+ p_sys->i_late_frames--; /* needed else it will never be decrease */ -+ return true; -+ } -+ return false; -+} -+ -+static mtime_t interpolate_next_pts( decoder_t *p_dec, AVFrame *frame ) -+{ -+ decoder_sys_t *p_sys = p_dec->p_sys; -+ AVCodecContext *p_context = p_sys->p_context; -+ -+ if( date_Get( &p_sys->pts ) == VLC_TS_INVALID || -+ p_sys->pts.i_divider_num == 0 ) -+ return VLC_TS_INVALID; -+ -+ int i_tick = p_context->ticks_per_frame; -+ if( i_tick <= 0 ) -+ i_tick = 1; -+ -+ /* interpolate the next PTS */ -+ return date_Increment( &p_sys->pts, i_tick + frame->repeat_pict ); -+} -+ -+static void update_late_frame_count( decoder_t *p_dec, block_t *p_block, -+ mtime_t current_time, mtime_t i_pts, -+ mtime_t i_next_pts ) -+{ -+ decoder_sys_t *p_sys = p_dec->p_sys; -+ /* Update frame late count (except when doing preroll) */ -+ mtime_t i_display_date = VLC_TS_INVALID; -+ if( !p_block || !(p_block->i_flags & BLOCK_FLAG_PREROLL) ) -+ i_display_date = decoder_GetDisplayDate( p_dec, i_pts ); -+ -+ mtime_t i_threshold = i_next_pts != VLC_TS_INVALID ? (i_next_pts - i_pts) / 2 : 20000; -+ -+ if( i_display_date > VLC_TS_INVALID && i_display_date + i_threshold <= current_time ) -+ { -+ /* Out of preroll, consider only late frames on rising delay */ -+ if( p_sys->b_from_preroll ) -+ { -+ if( p_sys->i_last_late_delay > current_time - i_display_date ) -+ { -+ p_sys->i_last_late_delay = current_time - i_display_date; -+ return; -+ } -+ p_sys->b_from_preroll = false; -+ } -+ -+ p_sys->i_late_frames++; -+ if( p_sys->i_late_frames == 1 ) -+ p_sys->i_late_frames_start = current_time; -+ -+ } -+ else -+ { -+ p_sys->i_late_frames = 0; -+ } -+} -+ -+ -+static int DecodeSidedata( decoder_t *p_dec, const AVFrame *frame, picture_t *p_pic ) -+{ -+ decoder_sys_t *p_sys = p_dec->p_sys; -+ bool format_changed = false; -+ -+#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 16, 101 ) ) -+#define FROM_AVRAT(default_factor, avrat) \ -+(uint64_t)(default_factor) * (avrat).num / (avrat).den -+ const AVFrameSideData *metadata = -+ av_frame_get_side_data( frame, -+ AV_FRAME_DATA_MASTERING_DISPLAY_METADATA ); -+ if ( metadata ) + { -+ const AVMasteringDisplayMetadata *hdr_meta = -+ (const AVMasteringDisplayMetadata *) metadata->data; -+ if ( hdr_meta->has_luminance ) -+ { -+#define ST2086_LUMA_FACTOR 10000 -+ p_pic->format.mastering.max_luminance = -+ FROM_AVRAT(ST2086_LUMA_FACTOR, hdr_meta->max_luminance); -+ p_pic->format.mastering.min_luminance = -+ FROM_AVRAT(ST2086_LUMA_FACTOR, hdr_meta->min_luminance); -+ } -+ if ( hdr_meta->has_primaries ) -+ { -+#define ST2086_RED 2 -+#define ST2086_GREEN 0 -+#define ST2086_BLUE 1 -+#define LAV_RED 0 -+#define LAV_GREEN 1 -+#define LAV_BLUE 2 -+#define ST2086_PRIM_FACTOR 50000 -+ p_pic->format.mastering.primaries[ST2086_RED*2 + 0] = -+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_RED][0]); -+ p_pic->format.mastering.primaries[ST2086_RED*2 + 1] = -+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_RED][1]); -+ p_pic->format.mastering.primaries[ST2086_GREEN*2 + 0] = -+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_GREEN][0]); -+ p_pic->format.mastering.primaries[ST2086_GREEN*2 + 1] = -+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_GREEN][1]); -+ p_pic->format.mastering.primaries[ST2086_BLUE*2 + 0] = -+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_BLUE][0]); -+ p_pic->format.mastering.primaries[ST2086_BLUE*2 + 1] = -+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_BLUE][1]); -+ p_pic->format.mastering.white_point[0] = -+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->white_point[0]); -+ p_pic->format.mastering.white_point[1] = -+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->white_point[1]); -+ } -+ -+ if ( memcmp( &p_dec->fmt_out.video.mastering, -+ &p_pic->format.mastering, -+ sizeof(p_pic->format.mastering) ) ) -+ { -+ p_dec->fmt_out.video.mastering = p_pic->format.mastering; -+ format_changed = true; -+ } -+#undef FROM_AVRAT ++ char dbuf0[5], dbuf1[5]; ++ msg_Dbg(filter, "%s: %s,%dx%d [(%d,%d) %d/%d] -> %s,%dx%d [(%d,%d) %dx%d]: %s %s %s", __func__, ++ str_fourcc(dbuf0, filter->fmt_in.video.i_chroma), ++ filter->fmt_in.video.i_width, filter->fmt_in.video.i_height, ++ filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset, ++ filter->fmt_in.video.i_visible_width, filter->fmt_in.video.i_visible_height, ++ str_fourcc(dbuf1, filter->fmt_out.video.i_chroma), ++ filter->fmt_out.video.i_width, filter->fmt_out.video.i_height, ++ filter->fmt_out.video.i_x_offset, filter->fmt_out.video.i_y_offset, ++ filter->fmt_out.video.i_visible_width, filter->fmt_out.video.i_visible_height, ++ sys->use_qpu ? "QPU" : "VPU", ++ sys->use_fast ? "FAST" : "ADV", ++ sys->use_passthrough ? "PASS" : sys->half_rate ? "HALF" : "FULL"); + } +- picture->format.i_frame_rate = filter->fmt_out.video.i_frame_rate; +- picture->format.i_frame_rate_base = filter->fmt_out.video.i_frame_rate_base; + +- buffer = picture->p_sys->buffer; +- buffer->user_data = picture; +- buffer->cmd = 0; ++ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_DEINTERLACE, &sys->component); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)", ++ MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); ++ goto fail; + } -+#endif -+#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 60, 100 ) ) -+ const AVFrameSideData *metadata_lt = -+ av_frame_get_side_data( frame, -+ AV_FRAME_DATA_CONTENT_LIGHT_LEVEL ); -+ if ( metadata_lt ) + +- mmal_picture_lock(picture); + { -+ const AVContentLightMetadata *light_meta = -+ (const AVContentLightMetadata *) metadata_lt->data; -+ p_pic->format.lighting.MaxCLL = light_meta->MaxCLL; -+ p_pic->format.lighting.MaxFALL = light_meta->MaxFALL; -+ if ( memcmp( &p_dec->fmt_out.video.lighting, -+ &p_pic->format.lighting, -+ sizeof(p_pic->format.lighting) ) ) -+ { -+ p_dec->fmt_out.video.lighting = p_pic->format.lighting; -+ format_changed = true; ++ const MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = { ++ { MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, sizeof(imfx_param) }, ++ sys->use_fast ? ++ MMAL_PARAM_IMAGEFX_DEINTERLACE_FAST : ++ MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV, ++ 4, ++ { 5 /* Frame type: mixed */, frame_duration, sys->half_rate, sys->use_qpu } ++ }; + +- status = mmal_port_send_buffer(sys->output, buffer); ++ status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)", ++ MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); ++ goto fail; + } + } -+#endif -+ -+ if (format_changed && decoder_UpdateVideoFormat( p_dec )) -+ return -1; + -+ const AVFrameSideData *p_avcc = av_frame_get_side_data( frame, AV_FRAME_DATA_A53_CC ); -+ if( p_avcc ) -+ { -+ cc_Extract( &p_sys->cc, CC_PAYLOAD_RAW, true, p_avcc->data, p_avcc->size ); -+ if( p_sys->cc.b_reorder || p_sys->cc.i_data ) -+ { -+ block_t *p_cc = block_Alloc( p_sys->cc.i_data ); -+ if( p_cc ) -+ { -+ memcpy( p_cc->p_buffer, p_sys->cc.p_data, p_sys->cc.i_data ); -+ if( p_sys->cc.b_reorder ) -+ p_cc->i_dts = p_cc->i_pts = p_pic->date; -+ else -+ p_cc->i_pts = p_cc->i_dts; -+ decoder_cc_desc_t desc; -+ desc.i_608_channels = p_sys->cc.i_608channels; -+ desc.i_708_channels = p_sys->cc.i_708channels; -+ desc.i_reorder_depth = 4; -+ decoder_QueueCc( p_dec, p_cc, &desc ); -+ } -+ cc_Flush( &p_sys->cc ); -+ } ++ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)filter; ++ status = mmal_port_enable(sys->component->control, control_port_cb); + if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to send buffer to output port (status=%"PRIx32" %s)", +- status, mmal_status_to_string(status)); +- mmal_buffer_header_release(buffer); +- picture_Release(picture); +- ret = -1; +- } else { +- atomic_fetch_add(&sys->output_in_transit, 1); +- vlc_sem_post(&sys->sem); ++ msg_Err(filter, "Failed to enable control port %s (status=%"PRIx32" %s)", ++ sys->component->control->name, status, mmal_status_to_string(status)); ++ goto fail; + } + +-out: +- return ret; +-} ++ sys->input = sys->component->input[0]; ++ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)filter; ++ sys->input->format->encoding = vlc_to_mmal_video_fourcc(&filter->fmt_in.video); ++ hw_mmal_vlc_fmt_to_mmal_fmt(sys->input->format, &filter->fmt_in.video); + +-static void fill_output_port(filter_t *filter) +-{ +- filter_sys_t *sys = filter->p_sys; +- /* allow at least 2 buffers in transit */ +- unsigned max_buffers_in_transit = __MAX(2, MIN_NUM_BUFFERS_IN_TRANSIT); +- int buffers_available = sys->output->buffer_num - +- atomic_load(&sys->output_in_transit) - +- mmal_queue_length(sys->filtered_pictures); +- int buffers_to_send = max_buffers_in_transit - sys->output_in_transit; +- int i; ++ es_format_Copy(&filter->fmt_out, &filter->fmt_in); ++ if (!sys->half_rate) ++ filter->fmt_out.video.i_frame_rate *= 2; + +- if (buffers_to_send > buffers_available) +- buffers_to_send = buffers_available; ++ status = mmal_port_format_commit(sys->input); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)", ++ sys->input->name, status, mmal_status_to_string(status)); ++ goto fail; + } -+ return 0; -+} -+ -+/***************************************************************************** -+ * DecodeBlock: Called to decode one or more frames -+ *****************************************************************************/ -+ -+static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error ) -+{ -+ decoder_sys_t *p_sys = p_dec->p_sys; -+ AVCodecContext *p_context = p_sys->p_context; -+ /* Boolean if we assume that we should get valid pic as result */ -+ bool b_need_output_picture = true; -+ -+ /* Boolean for END_OF_SEQUENCE */ -+ bool eos_spotted = false; -+ -+#if TRACE_ALL -+ msg_Dbg(p_dec, "<<< %s: (buf_size=%d)", __func__, pp_block == NULL || *pp_block == NULL ? 0 : (*pp_block)->i_buffer); -+#endif -+ -+ block_t *p_block; -+ mtime_t current_time; -+ picture_t *p_pic = NULL; -+ AVFrame *frame = NULL; -+ -+ // By default we are OK -+ *error = false; -+ -+ if( !p_context->extradata_size && p_dec->fmt_in.i_extra ) ++ sys->input->buffer_size = sys->input->buffer_size_recommended; ++ sys->input->buffer_num = 30; ++// sys->input->buffer_num = sys->input->buffer_num_recommended; + +-#ifndef NDEBUG +- msg_Dbg(filter, "Send %d buffers to output port (available: %d, in_transit: %d, buffer_num: %d)", +- buffers_to_send, buffers_available, sys->output_in_transit, +- sys->output->buffer_num); +-#endif +- for (i = 0; i < buffers_to_send; ++i) { +- if (send_output_buffer(filter) < 0) +- break; ++ if ((sys->in_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) + { -+ ffmpeg_InitCodec( p_dec ); -+ if( !avcodec_is_open( p_context ) ) -+ OpenVideoCodec( p_dec ); ++ msg_Err(filter, "Failed to create input pool"); ++ goto fail; + } +-} + +-static picture_t *deinterlace(filter_t *filter, picture_t *picture) +-{ +- filter_sys_t *sys = filter->p_sys; +- MMAL_BUFFER_HEADER_T *buffer; +- picture_t *out_picture = NULL; +- picture_t *ret = NULL; +- MMAL_STATUS_T status; +- unsigned i = 0; ++ status = port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, true); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", ++ sys->input->name, status, mmal_status_to_string(status)); ++ goto fail; + } -+ -+ p_block = pp_block ? *pp_block : NULL; -+ if(!p_block && !(p_sys->p_codec->capabilities & AV_CODEC_CAP_DELAY) ) -+ return NULL; -+ -+ if( !avcodec_is_open( p_context ) ) -+ { -+ if( p_block ) -+ block_Release( p_block ); -+ return NULL; + +- fill_output_port(filter); ++ status = mmal_port_enable(sys->input, di_input_port_cb); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)", ++ sys->input->name, status, mmal_status_to_string(status)); ++ goto fail; + } -+ -+ if( !check_block_validity( p_sys, p_block ) ) -+ return NULL; -+ -+ current_time = mdate(); -+ if( p_dec->b_frame_drop_allowed && check_block_being_late( p_sys, p_block, current_time) ) + +- buffer = picture->p_sys->buffer; +- buffer->user_data = picture; +- buffer->pts = picture->date; +- buffer->cmd = 0; + +- if (!picture->p_sys->displayed) { +- status = mmal_port_send_buffer(sys->input, buffer); +- if (status != MMAL_SUCCESS) { +- msg_Err(filter, "Failed to send buffer to input port (status=%"PRIx32" %s)", +- status, mmal_status_to_string(status)); +- picture_Release(picture); +- } else { +- picture->p_sys->displayed = true; +- atomic_fetch_add(&sys->input_in_transit, 1); +- vlc_sem_post(&sys->sem); +- } +- } else { +- picture_Release(picture); +- } +- +- /* +- * Send output buffers +- */ +- while(atomic_load(&sys->started) && i < 2) { +- if (buffer = mmal_queue_timedwait(sys->filtered_pictures, 2000)) { +- i++; +- if (!out_picture) { +- out_picture = (picture_t *)buffer->user_data; +- ret = out_picture; +- } else { +- out_picture->p_next = (picture_t *)buffer->user_data; +- out_picture = out_picture->p_next; +- } +- out_picture->date = buffer->pts; +- } else { +- msg_Dbg(filter, "Failed waiting for filtered picture"); +- break; +- } ++ if ((sys->out_q = mmal_queue_create()) == NULL) + { -+ msg_Err( p_dec, "more than 5 seconds of late video -> " -+ "dropping frame (computer too slow ?)" ); -+ return NULL; ++ msg_Err(filter, "Failed to create out Q"); ++ goto fail; + } +- if (out_picture) +- out_picture->p_next = NULL; + +- return ret; +-} +- +-static void flush(filter_t *filter) +-{ +- filter_sys_t *sys = filter->p_sys; +- MMAL_BUFFER_HEADER_T *buffer; ++ sys->output = sys->component->output[0]; ++ mmal_format_full_copy(sys->output->format, sys->input->format); + +- msg_Dbg(filter, "flush deinterlace filter"); ++ if (!sys->is_cma) ++ { ++ if ((status = hw_mmal_opaque_output(VLC_OBJECT(filter), &sys->out_ppr, sys->output, 5, di_output_port_cb)) != MMAL_SUCCESS) ++ goto fail; + } -+ -+ -+ /* A good idea could be to decode all I pictures and see for the other */ -+ -+ /* Defaults that if we aren't in prerolling, we want output picture -+ same for if we are flushing (p_block==NULL) */ -+ if( !p_block || !(p_block->i_flags & BLOCK_FLAG_PREROLL) ) -+ b_need_output_picture = true; + else -+ b_need_output_picture = false; -+ -+ /* Change skip_frame config only if hurry_up is enabled */ -+ if( p_sys->b_hurry_up ) + { -+ p_context->skip_frame = p_sys->i_skip_frame; ++ // CMA stuff ++ sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)filter; + -+ /* Check also if we should/can drop the block and move to next block -+ as trying to catchup the speed*/ -+ if( p_dec->b_frame_drop_allowed && -+ check_frame_should_be_dropped( p_sys, p_context, &b_need_output_picture ) ) ++ if ((sys->cma_out_pool = cma_buf_pool_new(8, 8, true, "deinterlace")) == NULL) + { -+ if( p_block ) -+ block_Release( p_block ); -+ msg_Warn( p_dec, "More than 11 late frames, dropping frame" ); -+ return NULL; ++ msg_Err(filter, "Failed to alloc cma buf pool"); ++ goto fail; + } -+ } -+ if( !b_need_output_picture ) -+ { -+ p_context->skip_frame = __MAX( p_context->skip_frame, -+ AVDISCARD_NONREF ); -+ } -+ -+ /* -+ * Do the actual decoding now */ -+ -+ /* Don't forget that libavcodec requires a little more bytes -+ * that the real frame size */ -+ if( p_block && p_block->i_buffer > 0 ) -+ { -+ eos_spotted = ( p_block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE ) != 0; -+ -+ p_block = block_Realloc( p_block, 0, -+ p_block->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE ); -+ if( !p_block ) -+ return NULL; -+ p_block->i_buffer -= FF_INPUT_BUFFER_PADDING_SIZE; -+ *pp_block = p_block; -+ memset( p_block->p_buffer + p_block->i_buffer, 0, -+ FF_INPUT_BUFFER_PADDING_SIZE ); -+ } -+ -+ while( !p_block || p_block->i_buffer > 0 || eos_spotted ) -+ { -+ int i_used; -+ AVPacket pkt; -+ -+ post_mt( p_sys ); -+ -+ av_init_packet( &pkt ); -+ if( p_block && p_block->i_buffer > 0 ) + +- msg_Dbg(filter, "flush: flush ports (input: %d, output: %d in transit)", +- sys->input_in_transit, sys->output_in_transit); +- mmal_port_flush(sys->output); +- mmal_port_flush(sys->input); +- +- msg_Dbg(filter, "flush: wait for all buffers to be returned"); +- while (atomic_load(&sys->input_in_transit) || +- atomic_load(&sys->output_in_transit)) +- vlc_sem_wait(&sys->sem); +- +- while ((buffer = mmal_queue_get(sys->filtered_pictures))) { +- picture_t *pic = (picture_t *)buffer->user_data; +- msg_Dbg(filter, "flush: release already filtered pic %p", +- (void *)pic); +- picture_Release(pic); +- } +- atomic_store(&sys->started, false); +- msg_Dbg(filter, "flush: done"); +-} ++ // Rate control done by CMA in flight logic, so have "inexhaustable" pool here ++ if ((sys->out_pool = mmal_pool_create(30, 0)) == NULL) + { -+ pkt.data = p_block->p_buffer; -+ pkt.size = p_block->i_buffer; -+ pkt.pts = p_block->i_pts > VLC_TS_INVALID ? p_block->i_pts : AV_NOPTS_VALUE; -+ pkt.dts = p_block->i_dts > VLC_TS_INVALID ? p_block->i_dts : AV_NOPTS_VALUE; ++ msg_Err(filter, "Failed to alloc out pool"); ++ goto fail; + } -+ else + +-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +-{ +- filter_t *filter = (filter_t *)port->userdata; +- MMAL_STATUS_T status; ++ port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, true); + +- if (buffer->cmd == MMAL_EVENT_ERROR) { +- status = *(uint32_t *)buffer->data; +- msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status, +- mmal_status_to_string(status)); +- } ++ if ((status = mmal_port_format_commit(sys->output)) != MMAL_SUCCESS) + { -+ /* Return delayed frames if codec has CODEC_CAP_DELAY */ -+ pkt.data = NULL; -+ pkt.size = 0; ++ msg_Err(filter, "Output port format commit failed"); ++ goto fail; + } -+ -+ if( !p_sys->palette_sent ) + +- mmal_buffer_header_release(buffer); +-} ++ sys->output->buffer_num = 30; ++ sys->output->buffer_size = sys->output->buffer_size_recommended; + +-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +-{ +- picture_t *picture = (picture_t *)buffer->user_data; +- filter_t *filter = (filter_t *)port->userdata; +- filter_sys_t *sys = filter->p_sys; ++ // CB just drops all bufs into out_q ++ if ((status = mmal_port_enable(sys->output, di_output_port_cb)) != MMAL_SUCCESS) + { -+ uint8_t *pal = av_packet_new_side_data(&pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE); -+ if (pal) { -+ memcpy(pal, p_dec->fmt_in.video.p_palette->palette, AVPALETTE_SIZE); -+ p_sys->palette_sent = true; -+ } ++ msg_Err(filter, "Failed to enable output port %s (status=%"PRIx32" %s)", ++ sys->output->name, status, mmal_status_to_string(status)); ++ goto fail; + } ++ } + +- if (picture) { +- picture_Release(picture); +- } else { +- msg_Warn(filter, "Got buffer without picture on input port - OOOPS"); +- mmal_buffer_header_release(buffer); ++ status = mmal_component_enable(sys->component); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(filter, "Failed to enable component %s (status=%"PRIx32" %s)", ++ sys->component->name, status, mmal_status_to_string(status)); ++ goto fail; + } + +- atomic_fetch_sub(&sys->input_in_transit, 1); +- vlc_sem_post(&sys->sem); ++ filter->pf_video_filter = deinterlace; ++ filter->pf_flush = di_flush; ++ return 0; + -+ /* Make sure we don't reuse the same timestamps twice */ -+ if( p_block ) -+ { -+ p_block->i_pts = -+ p_block->i_dts = VLC_TS_INVALID; -+ } ++fail: ++ CloseMmalDeinterlace(filter); ++ return ret; + } + +-static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +-{ +- filter_t *filter = (filter_t *)port->userdata; +- filter_sys_t *sys = filter->p_sys; +- picture_t *picture; ++vlc_module_begin() ++ set_shortname(N_("MMAL deinterlace")) ++ set_description(N_("MMAL-based deinterlace filter plugin")) ++ set_capability("video filter", 900) ++ set_category(CAT_VIDEO) ++ set_subcategory(SUBCAT_VIDEO_VFILTER) ++ set_callbacks(OpenMmalDeinterlace, CloseMmalDeinterlace) ++ add_shortcut("deinterlace") ++ add_bool(MMAL_DEINTERLACE_NO_QPU, false, MMAL_DEINTERLACE_NO_QPU_TEXT, ++ MMAL_DEINTERLACE_NO_QPU_LONGTEXT, true); ++ add_bool(MMAL_DEINTERLACE_ADV, false, MMAL_DEINTERLACE_ADV_TEXT, ++ MMAL_DEINTERLACE_ADV_LONGTEXT, true); ++ add_bool(MMAL_DEINTERLACE_FAST, false, MMAL_DEINTERLACE_FAST_TEXT, ++ MMAL_DEINTERLACE_FAST_LONGTEXT, true); ++ add_bool(MMAL_DEINTERLACE_NONE, false, MMAL_DEINTERLACE_NONE_TEXT, ++ MMAL_DEINTERLACE_NONE_LONGTEXT, true); ++ add_bool(MMAL_DEINTERLACE_HALF_RATE, false, MMAL_DEINTERLACE_HALF_RATE_TEXT, ++ MMAL_DEINTERLACE_HALF_RATE_LONGTEXT, true); ++ add_bool(MMAL_DEINTERLACE_FULL_RATE, false, MMAL_DEINTERLACE_FULL_RATE_TEXT, ++ MMAL_DEINTERLACE_FULL_RATE_LONGTEXT, true); + -+ int ret = avcodec_send_packet(p_context, &pkt); -+ if( ret != 0 && ret != AVERROR(EAGAIN) ) -+ { -+ if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL)) -+ { -+ msg_Err(p_dec, "avcodec_send_packet critical error"); -+ *error = true; -+ } -+ av_packet_unref( &pkt ); -+ break; -+ } -+ i_used = ret != AVERROR(EAGAIN) ? pkt.size : 0; -+ av_packet_unref( &pkt ); ++vlc_module_end() + -+ frame = av_frame_alloc(); -+ if (unlikely(frame == NULL)) -+ { -+ *error = true; -+ break; -+ } + +- if (buffer->cmd == 0) { +- if (buffer->length > 0) { +- atomic_store(&sys->started, true); +- mmal_queue_put(sys->filtered_pictures, buffer); +- picture = (picture_t *)buffer->user_data; +- } else { +- picture = (picture_t *)buffer->user_data; +- picture_Release(picture); +- } +- +- atomic_fetch_sub(&sys->output_in_transit, 1); +- vlc_sem_post(&sys->sem); +- } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) { +- msg_Warn(filter, "MMAL_EVENT_FORMAT_CHANGED seen but not handled"); +- mmal_buffer_header_release(buffer); +- } else { +- mmal_buffer_header_release(buffer); +- } +-} +--- /dev/null ++++ b/modules/hw/mmal/mmal_avcodec.c +@@ -0,0 +1,2302 @@ ++/***************************************************************************** ++ * video.c: video decoder using the libavcodec library ++ ***************************************************************************** ++ * Copyright (C) 1999-2001 VLC authors and VideoLAN ++ * $Id$ ++ * ++ * Authors: Laurent Aimar ++ * Gildas Bazin ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ + -+ ret = avcodec_receive_frame(p_context, frame); -+ if( ret != 0 && ret != AVERROR(EAGAIN) ) -+ { -+ msg_Dbg(p_dec, "No receive"); -+ if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL)) -+ { -+ msg_Err(p_dec, "avcodec_receive_frame critical error"); -+ *error = true; -+ } -+ av_frame_free(&frame); -+ /* After draining, we need to reset decoder with a flush */ -+ if( ret == AVERROR_EOF ) -+ avcodec_flush_buffers( p_sys->p_context ); -+ break; -+ } -+ bool not_received_frame = ret; ++/***************************************************************************** ++ * Preamble ++ *****************************************************************************/ ++#include "config.h" + -+ wait_mt( p_sys ); ++#include ++#include ++#include ++#include ++#include + -+ if( eos_spotted ) -+ p_sys->b_first_frame = true; ++#include + -+ if( p_block ) -+ { -+ if( p_block->i_buffer <= 0 ) -+ eos_spotted = false; ++#include ++#include ++#include + -+ /* Consumed bytes */ -+ p_block->p_buffer += i_used; -+ p_block->i_buffer -= i_used; -+ } ++#include ++#include ++#include ++#include ++#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 16, 101 ) ) ++#include ++#endif + -+ /* Nothing to display */ -+ if( not_received_frame ) -+ { -+// msg_Dbg(p_dec, "No rx: used=%d", i_used); -+ av_frame_free(&frame); -+ if( i_used == 0 ) break; -+ continue; -+ } ++//#include "avcodec.h" ++//#include "va.h" + -+ /* Compute the PTS */ -+#ifdef FF_API_PKT_PTS -+ mtime_t i_pts = frame->pts; -+#else -+ mtime_t i_pts = frame->pkt_pts; -+#endif -+ if (i_pts == AV_NOPTS_VALUE ) -+ i_pts = frame->pkt_dts; ++#include ++#include ++#include ++#include "../../codec/cc.h" ++#include "../../codec/avcodec/avcommon.h" // ??? Beware over inclusion ++#include "mmal_cma.h" ++#include "mmal_cma_drmprime.h" ++#include "mmal_picture.h" + -+ if( i_pts == AV_NOPTS_VALUE ) -+ i_pts = date_Get( &p_sys->pts ); ++#include + -+ /* Interpolate the next PTS */ -+ if( i_pts > VLC_TS_INVALID ) -+ date_Set( &p_sys->pts, i_pts ); ++#define TRACE_ALL 0 + -+ const mtime_t i_next_pts = interpolate_next_pts(p_dec, frame); ++#define BUFFERS_IN_FLIGHT 5 // Default max value for in flight buffers ++#define BUFFERS_IN_FLIGHT_UHD 3 // Fewer if very big + -+ update_late_frame_count( p_dec, p_block, current_time, i_pts, i_next_pts); ++#define MMAL_AVCODEC_BUFFERS "mmal-avcodec-buffers" ++#define MMAL_AVCODEC_BUFFERS_TEXT N_("In flight buffer count before blocking.") ++#define MMAL_AVCODEC_BUFFERS_LONGTEXT N_("In flight buffer count before blocking. " \ ++"Beware that incautious changing of this can lead to lockup. " \ ++"Zero will disable the module.") + -+ if( !b_need_output_picture || -+// ( !p_sys->p_va && !frame->linesize[0] ) || -+ ( !frame->linesize[0] ) || -+ ( p_dec->b_frame_drop_allowed && (frame->flags & AV_FRAME_FLAG_CORRUPT) && -+ !p_sys->b_show_corrupted ) ) -+ { -+ av_frame_free(&frame); -+// msg_Dbg(p_dec, "Bad frame"); -+ continue; -+ } + -+ if( p_context->pix_fmt == AV_PIX_FMT_PAL8 -+ && !p_dec->fmt_out.video.p_palette ) -+ { -+ /* See AV_PIX_FMT_PAL8 comment in avc_GetVideoFormat(): update the -+ * fmt_out palette and change the fmt_out chroma to request a new -+ * vout */ -+ assert( p_dec->fmt_out.video.i_chroma != VLC_CODEC_RGBP ); ++// Fwd declarations required due to wanting to avoid reworking the original ++// code too much ++static void MmalAvcodecCloseDecoder( vlc_object_t *obj ); + -+ video_palette_t *p_palette; -+ p_palette = p_dec->fmt_out.video.p_palette -+ = malloc( sizeof(video_palette_t) ); -+ if( !p_palette ) -+ { -+ *error = true; -+ av_frame_free(&frame); -+ break; -+ } -+ static_assert( sizeof(p_palette->palette) == AVPALETTE_SIZE, -+ "Palette size mismatch between vlc and libavutil" ); -+ assert( frame->data[1] != NULL ); -+ memcpy( p_palette->palette, frame->data[1], AVPALETTE_SIZE ); -+ p_palette->i_entries = AVPALETTE_COUNT; -+ p_dec->fmt_out.video.i_chroma = VLC_CODEC_RGBP; -+ if( decoder_UpdateVideoFormat( p_dec ) ) -+ { -+ av_frame_free(&frame); -+ continue; -+ } -+ } -+ -+#if 1 -+ { -+ cma_buf_t * const cb = av_rpi_zc_buf_v(frame->buf[0]); + -+ if (cb == NULL) -+ { -+ msg_Err(p_dec, "Frame has no attached CMA buffer"); -+ goto fail; -+ } ++/***************************************************************************** ++ * decoder_sys_t : decoder descriptor ++ *****************************************************************************/ ++struct decoder_sys_t ++{ ++ AVCodecContext *p_context; ++ const AVCodec *p_codec; + -+ if (lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, -+ p_context->pix_fmt) != 0) -+ { -+ msg_Err(p_dec, "Failed to update format"); -+ goto fail; -+ } ++ /* Video decoder specific part */ ++ date_t pts; + -+ if ((p_pic = decoder_NewPicture(p_dec)) == NULL) -+ { -+ msg_Err(p_dec, "Failed to allocate pic"); -+ goto fail; -+ } ++ /* Closed captions for decoders */ ++ cc_data_t cc; + -+ if (cma_buf_pic_attach(cma_buf_ref(cb), p_pic) != 0) -+ { -+ cma_buf_unref(cb); // Undo the in_flight -+ char dbuf0[5]; -+ msg_Err(p_dec, "Failed to attach bufs to pic: fmt=%s", str_fourcc(dbuf0, p_pic->format.i_chroma)); -+ goto fail; -+ } ++ /* for frame skipping algo */ ++ bool b_hurry_up; ++ bool b_show_corrupted; ++ bool b_from_preroll; ++ enum AVDiscard i_skip_frame; + -+ // ****** Set planes etc. -+ set_pic_from_frame(p_pic, frame); -+ } -+#else -+ picture_t *p_pic = frame->opaque; -+ if( p_pic == NULL ) -+ { /* When direct rendering is not used, get_format() and get_buffer() -+ * might not be called. The output video format must be set here -+ * then picture buffer can be allocated. */ -+ if (p_sys->p_va == NULL -+ && lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, -+ p_context->pix_fmt) == 0) -+ p_pic = decoder_NewPicture(p_dec); ++ /* how many decoded frames are late */ ++ int i_late_frames; ++ mtime_t i_late_frames_start; ++ mtime_t i_last_late_delay; + -+ if( !p_pic ) -+ { -+ av_frame_free(&frame); -+ break; -+ } ++ /* for direct rendering */ ++ bool b_direct_rendering; ++ atomic_bool b_dr_failure; + -+ /* Fill picture_t from AVFrame */ -+ if( lavc_CopyPicture( p_dec, p_pic, frame ) != VLC_SUCCESS ) -+ { -+ av_frame_free(&frame); -+ picture_Release( p_pic ); -+ break; -+ } -+ } -+ else -+ { -+ /* Some codecs can return the same frame multiple times. By the -+ * time that the same frame is returned a second time, it will be -+ * too late to clone the underlying picture. So clone proactively. -+ * A single picture CANNOT be queued multiple times. -+ */ -+ p_pic = picture_Clone( p_pic ); -+ if( unlikely(p_pic == NULL) ) -+ { -+ av_frame_free(&frame); -+ break; -+ } -+ } -+#endif ++ /* Hack to force display of still pictures */ ++ bool b_first_frame; + -+ if( !p_dec->fmt_in.video.i_sar_num || !p_dec->fmt_in.video.i_sar_den ) -+ { -+ /* Fetch again the aspect ratio in case it changed */ -+ p_dec->fmt_out.video.i_sar_num -+ = p_context->sample_aspect_ratio.num; -+ p_dec->fmt_out.video.i_sar_den -+ = p_context->sample_aspect_ratio.den; + -+ if( !p_dec->fmt_out.video.i_sar_num || !p_dec->fmt_out.video.i_sar_den ) -+ { -+ p_dec->fmt_out.video.i_sar_num = 1; -+ p_dec->fmt_out.video.i_sar_den = 1; -+ } -+ } ++ /* */ ++ bool palette_sent; + -+ p_pic->date = i_pts; -+ /* Hack to force display of still pictures */ -+ p_pic->b_force = p_sys->b_first_frame; -+ p_pic->i_nb_fields = 2 + frame->repeat_pict; -+ p_pic->b_progressive = !frame->interlaced_frame; -+ p_pic->b_top_field_first = frame->top_field_first; ++ /* VA API */ ++// vlc_va_t *p_va; ++ enum PixelFormat pix_fmt; ++ int profile; ++ int level; + -+ if (DecodeSidedata(p_dec, frame, p_pic)) -+ i_pts = VLC_TS_INVALID; ++ vlc_sem_t sem_mt; + -+ av_frame_free(&frame); ++ // Rpi vars ++ cma_buf_pool_t * cma_pool; ++ bool pool_alloc_1; ++ vcsm_init_type_t vcsm_init_type; ++ int cma_in_flight_max; ++ bool use_drm; ++ // Debug ++ decoder_t * p_dec; ++}; + -+ /* Send decoded frame to vout */ -+ if (i_pts > VLC_TS_INVALID) -+ { -+ p_sys->b_first_frame = false; -+#if TRACE_ALL -+ msg_Dbg(p_dec, ">>> %s: Got pic", __func__); -+#endif -+ return p_pic; -+ } -+ else -+ picture_Release( p_pic ); -+ } + -+ if( p_block ) -+ block_Release( p_block ); ++static vlc_fourcc_t ++ZcFindVlcChroma(const int i_ffmpeg_chroma, const int i_ffmpeg_sw_chroma) ++{ ++ int c = i_ffmpeg_chroma; + -+#if TRACE_ALL -+ msg_Dbg(p_dec, ">>> %s: NULL", __func__); -+#endif -+ return NULL; ++ if (c == AV_PIX_FMT_DRM_PRIME) ++ c = i_ffmpeg_sw_chroma; + -+fail: -+#if TRACE_ALL -+ msg_Dbg(p_dec, ">>> %s: FAIL", __func__); -+#endif -+ av_frame_free(&frame); -+ if (p_pic != NULL) -+ picture_Release(p_pic); -+ if (p_block != NULL) -+ block_Release(p_block); -+ *error = true; -+ return NULL; ++ switch (c) ++ { ++ // This is all we claim to deal with ++ // In theory RGB should be doable within our current framework ++ case AV_PIX_FMT_YUV420P: ++ return VLC_CODEC_MMAL_ZC_I420; ++ case AV_PIX_FMT_SAND128: ++ case AV_PIX_FMT_RPI4_8: ++ return VLC_CODEC_MMAL_ZC_SAND8; ++ case AV_PIX_FMT_SAND64_10: ++ return VLC_CODEC_MMAL_ZC_SAND10; ++ case AV_PIX_FMT_RPI4_10: ++ return VLC_CODEC_MMAL_ZC_SAND30; ++ default: ++ break; ++ } ++ return 0; +} + -+static int DecodeVideo( decoder_t *p_dec, block_t *p_block ) ++// Pix Fmt conv for MMal ++// video_fromat from ffmpeg pic_fmt ++static int ++ZcGetVlcChroma( video_format_t *fmt, int i_ffmpeg_chroma, const int i_ffmpeg_sw_chroma) +{ -+ block_t **pp_block = p_block ? &p_block : NULL; -+ picture_t *p_pic; -+ bool error = false; -+ while( ( p_pic = DecodeBlock( p_dec, pp_block, &error ) ) != NULL ) -+ decoder_QueueVideo( p_dec, p_pic ); -+ return VLCDEC_SUCCESS; -+// Easiest to just ignore all errors - returning a real error seems to -+// kill output forever -+// return error ? VLCDEC_ECRITICAL : VLCDEC_SUCCESS; ++ fmt->i_rmask = 0; ++ fmt->i_gmask = 0; ++ fmt->i_bmask = 0; ++ fmt->i_chroma = ZcFindVlcChroma(i_ffmpeg_chroma, i_ffmpeg_sw_chroma); ++ ++ return fmt->i_chroma == 0 ? -1 : 0; +} + -+/***************************************************************************** -+ * EndVideo: decoder destruction -+ ***************************************************************************** -+ * This function is called when the thread ends after a successful -+ * initialization. -+ *****************************************************************************/ -+static void MmalAvcodecCloseDecoder( vlc_object_t *obj ) ++ ++// Format chooser is way simpler than vlc ++static enum PixelFormat ++ZcGetFormat(AVCodecContext *p_context, const enum PixelFormat *pi_fmt) +{ -+ decoder_t *p_dec = (decoder_t *)obj; -+ decoder_sys_t *p_sys = p_dec->p_sys; -+ AVCodecContext *ctx = p_sys->p_context; -+// void *hwaccel_context; ++ enum PixelFormat swfmt = avcodec_default_get_format(p_context, pi_fmt); ++ decoder_t * const p_dec = av_rpi_zc_in_use(p_context) ? NULL : p_context->opaque; ++ const bool use_drm = (p_dec != NULL) && p_dec->p_sys->use_drm; + -+ msg_Dbg(obj, "<<< %s", __func__); ++ for (size_t i = 0; pi_fmt[i] != AV_PIX_FMT_NONE; i++) ++ { ++ if (use_drm && pi_fmt[i] == AV_PIX_FMT_DRM_PRIME) ++ return pi_fmt[i]; + -+ post_mt( p_sys ); ++ if (!use_drm && pi_fmt[i] != AV_PIX_FMT_DRM_PRIME && ++ ZcFindVlcChroma(pi_fmt[i], p_context->sw_pix_fmt) != 0) ++ return pi_fmt[i]; ++ } ++ return swfmt; ++} + -+ cma_buf_pool_cancel(p_sys->cma_pool); // Abort any pending frame allocs + -+ /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */ -+ if( avcodec_is_open( ctx ) ) -+ avcodec_flush_buffers( ctx ); ++static void cma_avbuf_pool_free(void * v) ++{ ++ cma_buf_unref(v); ++} + -+ av_rpi_zc_uninit2(ctx); ++static unsigned int zc_buf_vcsm_handle(void * v) ++{ ++ return cma_buf_vcsm_handle(v); ++} + -+ wait_mt( p_sys ); ++static unsigned int zc_buf_vc_handle(void * v) ++{ ++ return cma_buf_vc_handle(v); ++} + -+ cc_Flush( &p_sys->cc ); ++static void * zc_buf_map_arm(void * v) ++{ ++ return cma_buf_addr(v); ++} + -+// hwaccel_context = ctx->hwaccel_context; -+ avcodec_free_context( &ctx ); ++static unsigned int zc_buf_map_vc(void * v) ++{ ++ return cma_buf_vc_addr(v); ++} + -+// if( p_sys->p_va ) -+// vlc_va_Delete( p_sys->p_va, &hwaccel_context ); + -+ cma_vcsm_exit(p_sys->vcsm_init_type); + -+ vlc_sem_destroy( &p_sys->sem_mt ); -+ free( p_sys ); -+} -+ -+/***************************************************************************** -+ * ffmpeg_InitCodec: setup codec extra initialization data for ffmpeg -+ *****************************************************************************/ -+static void ffmpeg_InitCodec( decoder_t *p_dec ) -+{ -+ decoder_sys_t *p_sys = p_dec->p_sys; -+ size_t i_size = p_dec->fmt_in.i_extra; -+ -+ if( !i_size ) return; ++static const av_rpi_zc_buf_fn_tab_t zc_buf_fn_tab = { ++ .free = cma_avbuf_pool_free, + -+ if( p_sys->p_codec->id == AV_CODEC_ID_SVQ3 ) -+ { -+ uint8_t *p; ++ .vcsm_handle = zc_buf_vcsm_handle, ++ .vc_handle = zc_buf_vc_handle, ++ .map_arm = zc_buf_map_arm, ++ .map_vc = zc_buf_map_vc ++}; + -+ p_sys->p_context->extradata_size = i_size + 12; -+ p = p_sys->p_context->extradata = -+ av_malloc( p_sys->p_context->extradata_size + -+ FF_INPUT_BUFFER_PADDING_SIZE ); -+ if( !p ) -+ return; + -+ memcpy( &p[0], "SVQ3", 4 ); -+ memset( &p[4], 0, 8 ); -+ memcpy( &p[12], p_dec->fmt_in.p_extra, i_size ); ++static AVBufferRef * ++zc_alloc_buf(void * v, size_t size, const AVRpiZcFrameGeometry * geo) ++{ ++ decoder_t * const dec = v; ++ decoder_sys_t * const sys = dec->p_sys; + -+ /* Now remove all atoms before the SMI one */ -+ if( p_sys->p_context->extradata_size > 0x5a && -+ strncmp( (char*)&p[0x56], "SMI ", 4 ) ) -+ { -+ uint8_t *psz = &p[0x52]; ++ VLC_UNUSED(geo); + -+ while( psz < &p[p_sys->p_context->extradata_size - 8] ) -+ { -+ uint_fast32_t atom_size = GetDWBE( psz ); -+ if( atom_size <= 1 ) -+ { -+ /* FIXME handle 1 as long size */ -+ break; -+ } -+ if( !strncmp( (char*)&psz[4], "SMI ", 4 ) ) -+ { -+ memmove( &p[0x52], psz, -+ &p[p_sys->p_context->extradata_size] - psz ); -+ break; -+ } ++ assert(sys != NULL); + -+ psz += atom_size; -+ } -+ } -+ } -+ else ++ const unsigned int dec_pool_req = av_rpi_zc_get_decoder_pool_size(sys->p_context->opaque); ++ if (dec_pool_req != 0) + { -+ p_sys->p_context->extradata_size = i_size; -+ p_sys->p_context->extradata = -+ av_malloc( i_size + FF_INPUT_BUFFER_PADDING_SIZE ); -+ if( p_sys->p_context->extradata ) ++ cma_buf_pool_resize(sys->cma_pool, dec_pool_req + sys->cma_in_flight_max, sys->cma_in_flight_max); ++ ++ if (!sys->pool_alloc_1) + { -+ memcpy( p_sys->p_context->extradata, -+ p_dec->fmt_in.p_extra, i_size ); -+ memset( p_sys->p_context->extradata + i_size, -+ 0, FF_INPUT_BUFFER_PADDING_SIZE ); ++ sys->pool_alloc_1 = true; ++ msg_Dbg(dec, "Pool size: (%d+%d) * %zd", dec_pool_req, sys->cma_in_flight_max, size); ++ if (cma_buf_pool_fill(sys->cma_pool, size) != 0) ++ msg_Warn(dec, "Failed to preallocate decoder pool (%d+%d) * %zd", dec_pool_req, sys->cma_in_flight_max, size); + } + } -+} -+ -+ -+vlc_module_begin() -+ set_category( CAT_INPUT ) -+ set_subcategory( SUBCAT_INPUT_VCODEC ) -+ set_shortname(N_("MMAL avcodec")) -+ set_description(N_("MMAL buffered avcodec ")) -+ set_capability("video decoder", 80) -+ add_shortcut("mmal_avcodec") -+ add_integer(MMAL_AVCODEC_BUFFERS, -1, MMAL_AVCODEC_BUFFERS_TEXT, -+ MMAL_AVCODEC_BUFFERS_LONGTEXT, true) -+ set_callbacks(MmalAvcodecOpenDecoder, MmalAvcodecCloseDecoder) -+vlc_module_end() -+ ---- /dev/null -+++ b/modules/hw/mmal/mmal_cma.c -@@ -0,0 +1,668 @@ -+#ifdef HAVE_CONFIG_H -+# include "config.h" -+#endif -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include -+#include -+ -+#include "mmal_cma.h" -+#include "mmal_picture.h" + -+#include ++ void * const cmabuf = cma_buf_pool_alloc_buf(sys->cma_pool, size); + -+#define TRACE_ALL 0 ++ if (cmabuf == NULL) ++ { ++ msg_Err(dec, "CMA buf pool alloc buf failed"); ++ return NULL; ++ } + -+//----------------------------------------------------------------------------- -+// -+// Generic pool functions -+// Knows nothing about pool entries ++ AVBufferRef *const avbuf = av_rpi_zc_buf(cma_buf_size(cmabuf), 0, cmabuf, &zc_buf_fn_tab); + -+typedef void * cma_pool_alloc_fn(void * v, size_t size); -+typedef void cma_pool_free_fn(void * v, void * el, size_t size); ++ if (avbuf == NULL) ++ { ++ msg_Err(dec, "av_rpi_zc_buf failed"); ++ cma_buf_unref(cmabuf); ++ return NULL; ++ } + -+#if TRACE_ALL -+static atomic_int pool_seq; -+#endif ++ return avbuf; ++} + -+// Pool structure -+// Ref count is held by pool owner and pool els that have been got -+// Els in the pool do not count towards its ref count -+struct cma_pool_fixed_s ++static void ++zc_free_pool(void * v) +{ -+ atomic_int ref_count; -+ -+ vlc_mutex_t lock; -+ unsigned int n_in; -+ unsigned int n_out; -+ unsigned int pool_size; -+ int flight_size; -+ size_t el_size; -+ void ** pool; -+ -+ bool cancel; -+ int in_flight; -+ vlc_cond_t flight_cond; ++ decoder_t * const dec = v; ++ cma_buf_pool_delete(dec->p_sys->cma_pool); ++} + -+ void * alloc_v; -+ cma_pool_alloc_fn * el_alloc_fn; -+ cma_pool_free_fn * el_free_fn; -+ cma_pool_on_delete_fn * on_delete_fn; + -+ const char * name; -+#if TRACE_ALL -+ int seq; -+#endif -+}; ++static const uint8_t shift_01[] = {0,1,1,1}; ++static const uint8_t pb_1[] = {1,1,1,1}; ++static const uint8_t pb_12[] = {1,2,2,2}; ++static const uint8_t pb_24[] = {2,4,4,4}; ++static const uint8_t pb_4[] = {4,4,4,4}; + -+static inline unsigned int inc_mod(const unsigned int n, const unsigned int m) ++static inline int pitch_from_mod(const uint64_t mod) +{ -+ return n + 1 >= m ? 0 : n + 1; ++ return fourcc_mod_broadcom_mod(mod) != DRM_FORMAT_MOD_BROADCOM_SAND128 ? 0 : ++ fourcc_mod_broadcom_param(mod); +} + -+static void free_pool(const cma_pool_fixed_t * const p, void ** const pool, -+ const unsigned int pool_size, const size_t el_size) ++static int set_pic_from_frame(picture_t * const pic, const AVFrame * const frame) +{ -+ if (pool == NULL) -+ return; ++ const uint8_t * hs = shift_01; ++ const uint8_t * ws = shift_01; ++ const uint8_t * pb = pb_1; + -+ for (unsigned int n = 0; n != pool_size; ++n) -+ if (pool[n] != NULL) -+ p->el_free_fn(p->alloc_v, pool[n], el_size); -+ free(pool); -+} ++ switch (pic->format.i_chroma) ++ { ++ case VLC_CODEC_MMAL_ZC_RGB32: ++ pic->i_planes = 1; ++ pb = pb_4; ++ break; ++ case VLC_CODEC_MMAL_ZC_I420: ++ pic->i_planes = 3; ++ break; ++ case VLC_CODEC_MMAL_ZC_SAND8: ++ pic->i_planes = 2; ++ pb = pb_12; ++ break; ++ case VLC_CODEC_MMAL_ZC_SAND10: ++ case VLC_CODEC_MMAL_ZC_SAND30: // Lies: SAND30 is "special" ++ pic->i_planes = 2; ++ pb = pb_24; ++ break; ++ default: ++ return VLC_EGENERIC; ++ } + -+// Just kill this - no checks -+static void cma_pool_fixed_delete(cma_pool_fixed_t * const p) -+{ -+ cma_pool_on_delete_fn *const on_delete_fn = p->on_delete_fn; -+ void *const v = p->alloc_v; ++ const cma_buf_t * const cb = cma_buf_pic_get(pic); ++ uint8_t * const data = cma_buf_addr(cb); ++ if (data == NULL) { ++ return VLC_ENOMEM; ++ } + -+ free_pool(p, p->pool, p->pool_size, p->el_size); ++ if (frame->format == AV_PIX_FMT_DRM_PRIME) ++ { ++ const AVDRMFrameDescriptor * const desc = (AVDRMFrameDescriptor*)frame->data[0]; ++ const AVDRMLayerDescriptor * layer = desc->layers + 0; ++ const AVDRMPlaneDescriptor * plane = layer->planes + 0; ++ const uint64_t mod = desc->objects[0].format_modifier; ++ const int set_pitch = pitch_from_mod(mod); ++ int nb_plane = 0; ++ ++ if (desc->nb_objects != 1) ++ return VLC_EGENERIC; + -+ if (p->name != NULL) -+ free((void *)p->name); // Discard const ++ for (int i = 0; i != pic->i_planes; ++i) ++ { ++ if (nb_plane >= layer->nb_planes) ++ { ++ ++layer; ++ plane = layer->planes + 0; ++ nb_plane = 0; ++ } + -+ vlc_cond_destroy(&p->flight_cond); -+ vlc_mutex_destroy(&p->lock); -+ free(p); ++ pic->p[i] = (plane_t){ ++ .p_pixels = data + plane->offset, ++ .i_lines = frame->height >> hs[i], ++ .i_pitch = set_pitch != 0 ? set_pitch : plane->pitch, ++ .i_pixel_pitch = pb[i], ++ .i_visible_lines = av_frame_cropped_height(frame) >> hs[i], ++ .i_visible_pitch = av_frame_cropped_width(frame) >> ws[i] ++ }; + -+ // Inform our container that we are dead (if it cares) -+ if (on_delete_fn) -+ on_delete_fn(v); -+} ++ ++plane; ++ ++nb_plane; ++ } + -+static void cma_pool_fixed_unref(cma_pool_fixed_t * const p) -+{ -+ if (atomic_fetch_sub(&p->ref_count, 1) <= 1) -+ cma_pool_fixed_delete(p); ++ // Calculate lines from gap between planes ++ // This will give us an accurate "height" for later use by MMAL ++ for (int i = 0; i + 1 < pic->i_planes; ++i) ++ pic->p[i].i_lines = (pic->p[i + 1].p_pixels - pic->p[i].p_pixels) / pic->p[i].i_pitch; ++ } ++ else ++ { ++ uint8_t * frame_end = frame->data[0] + cma_buf_size(cb); ++ for (int i = 0; i != pic->i_planes; ++i) { ++ // Calculate lines from gap between planes ++ // This will give us an accurate "height" for later use by MMAL ++ const int lines = ((i + 1 == pic->i_planes ? frame_end : frame->data[i + 1]) - ++ frame->data[i]) / frame->linesize[i]; ++ pic->p[i] = (plane_t){ ++ .p_pixels = data + (frame->data[i] - frame->data[0]), ++ .i_lines = lines, ++ .i_pitch = av_rpi_is_sand_frame(frame) ? av_rpi_sand_frame_stride2(frame) : frame->linesize[i], ++ .i_pixel_pitch = pb[i], ++ .i_visible_lines = av_frame_cropped_height(frame) >> hs[i], ++ .i_visible_pitch = av_frame_cropped_width(frame) >> ws[i] ++ }; ++ } ++ } ++ return 0; +} + -+static void cma_pool_fixed_ref(cma_pool_fixed_t * const p) -+{ -+ atomic_fetch_add(&p->ref_count, 1); -+} + -+static void cma_pool_fixed_inc_in_flight(cma_pool_fixed_t * const p) -+{ -+ vlc_mutex_lock(&p->lock); -+ ++p->in_flight; -+ vlc_mutex_unlock(&p->lock); -+} ++//============================================================================ ++// ++// Nicked from avcodec/fourcc.c ++// ++// * Really we should probably use that directly + -+static void cma_pool_fixed_dec_in_flight(cma_pool_fixed_t * const p) ++/* ++ * Video Codecs ++ */ ++ ++struct vlc_avcodec_fourcc +{ -+ vlc_mutex_lock(&p->lock); -+ if (--p->in_flight == 0) -+ vlc_cond_signal(&p->flight_cond); -+ vlc_mutex_unlock(&p->lock); -+} ++ vlc_fourcc_t i_fourcc; ++ unsigned i_codec; ++}; + -+static void * cma_pool_fixed_get(cma_pool_fixed_t * const p, const size_t req_el_size, const bool inc_flight, const bool no_pool) ++ ++static const struct vlc_avcodec_fourcc video_codecs[] = +{ -+ void * v = NULL; ++ { VLC_CODEC_MP1V, AV_CODEC_ID_MPEG1VIDEO }, ++ { VLC_CODEC_MP2V, AV_CODEC_ID_MPEG2VIDEO }, /* prefer MPEG2 over MPEG1 */ ++ { VLC_CODEC_MPGV, AV_CODEC_ID_MPEG2VIDEO }, /* prefer MPEG2 over MPEG1 */ ++ /* AV_CODEC_ID_MPEG2VIDEO_XVMC */ ++ { VLC_CODEC_H261, AV_CODEC_ID_H261 }, ++ { VLC_CODEC_H263, AV_CODEC_ID_H263 }, ++ { VLC_CODEC_RV10, AV_CODEC_ID_RV10 }, ++ { VLC_CODEC_RV13, AV_CODEC_ID_RV10 }, ++ { VLC_CODEC_RV20, AV_CODEC_ID_RV20 }, ++ { VLC_CODEC_MJPG, AV_CODEC_ID_MJPEG }, ++ { VLC_CODEC_MJPGB, AV_CODEC_ID_MJPEGB }, ++ { VLC_CODEC_LJPG, AV_CODEC_ID_LJPEG }, ++ { VLC_CODEC_SP5X, AV_CODEC_ID_SP5X }, ++ { VLC_CODEC_JPEGLS, AV_CODEC_ID_JPEGLS }, ++ { VLC_CODEC_MP4V, AV_CODEC_ID_MPEG4 }, ++ /* AV_CODEC_ID_RAWVIDEO */ ++ { VLC_CODEC_DIV1, AV_CODEC_ID_MSMPEG4V1 }, ++ { VLC_CODEC_DIV2, AV_CODEC_ID_MSMPEG4V2 }, ++ { VLC_CODEC_DIV3, AV_CODEC_ID_MSMPEG4V3 }, ++ { VLC_CODEC_WMV1, AV_CODEC_ID_WMV1 }, ++ { VLC_CODEC_WMV2, AV_CODEC_ID_WMV2 }, ++ { VLC_CODEC_H263P, AV_CODEC_ID_H263P }, ++ { VLC_CODEC_H263I, AV_CODEC_ID_H263I }, ++ { VLC_CODEC_FLV1, AV_CODEC_ID_FLV1 }, ++ { VLC_CODEC_SVQ1, AV_CODEC_ID_SVQ1 }, ++ { VLC_CODEC_SVQ3, AV_CODEC_ID_SVQ3 }, ++ { VLC_CODEC_DV, AV_CODEC_ID_DVVIDEO }, ++ { VLC_CODEC_HUFFYUV, AV_CODEC_ID_HUFFYUV }, ++ { VLC_CODEC_CYUV, AV_CODEC_ID_CYUV }, ++ { VLC_CODEC_H264, AV_CODEC_ID_H264 }, ++ { VLC_CODEC_INDEO3, AV_CODEC_ID_INDEO3 }, ++ { VLC_CODEC_VP3, AV_CODEC_ID_VP3 }, ++ { VLC_CODEC_THEORA, AV_CODEC_ID_THEORA }, ++#if ( !defined( WORDS_BIGENDIAN ) ) ++ /* Asus Video (Another thing that doesn't work on PPC) */ ++ { VLC_CODEC_ASV1, AV_CODEC_ID_ASV1 }, ++ { VLC_CODEC_ASV2, AV_CODEC_ID_ASV2 }, ++#endif ++ { VLC_CODEC_FFV1, AV_CODEC_ID_FFV1 }, ++ { VLC_CODEC_4XM, AV_CODEC_ID_4XM }, ++ { VLC_CODEC_VCR1, AV_CODEC_ID_VCR1 }, ++ { VLC_CODEC_CLJR, AV_CODEC_ID_CLJR }, ++ { VLC_CODEC_MDEC, AV_CODEC_ID_MDEC }, ++ { VLC_CODEC_ROQ, AV_CODEC_ID_ROQ }, ++ { VLC_CODEC_INTERPLAY, AV_CODEC_ID_INTERPLAY_VIDEO }, ++ { VLC_CODEC_XAN_WC3, AV_CODEC_ID_XAN_WC3 }, ++ { VLC_CODEC_XAN_WC4, AV_CODEC_ID_XAN_WC4 }, ++ { VLC_CODEC_RPZA, AV_CODEC_ID_RPZA }, ++ { VLC_CODEC_CINEPAK, AV_CODEC_ID_CINEPAK }, ++ { VLC_CODEC_WS_VQA, AV_CODEC_ID_WS_VQA }, ++ { VLC_CODEC_MSRLE, AV_CODEC_ID_MSRLE }, ++ { VLC_CODEC_MSVIDEO1, AV_CODEC_ID_MSVIDEO1 }, ++ { VLC_CODEC_IDCIN, AV_CODEC_ID_IDCIN }, ++ { VLC_CODEC_8BPS, AV_CODEC_ID_8BPS }, ++ { VLC_CODEC_SMC, AV_CODEC_ID_SMC }, ++ { VLC_CODEC_FLIC, AV_CODEC_ID_FLIC }, ++ { VLC_CODEC_TRUEMOTION1, AV_CODEC_ID_TRUEMOTION1 }, ++ { VLC_CODEC_VMDVIDEO, AV_CODEC_ID_VMDVIDEO }, ++ { VLC_CODEC_LCL_MSZH, AV_CODEC_ID_MSZH }, ++ { VLC_CODEC_LCL_ZLIB, AV_CODEC_ID_ZLIB }, ++ { VLC_CODEC_QTRLE, AV_CODEC_ID_QTRLE }, ++ { VLC_CODEC_TSCC, AV_CODEC_ID_TSCC }, ++ { VLC_CODEC_ULTI, AV_CODEC_ID_ULTI }, ++ { VLC_CODEC_QDRAW, AV_CODEC_ID_QDRAW }, ++ { VLC_CODEC_VIXL, AV_CODEC_ID_VIXL }, ++ { VLC_CODEC_QPEG, AV_CODEC_ID_QPEG }, ++ { VLC_CODEC_PNG, AV_CODEC_ID_PNG }, ++ { VLC_CODEC_PPM, AV_CODEC_ID_PPM }, ++ /* AV_CODEC_ID_PBM */ ++ { VLC_CODEC_PGM, AV_CODEC_ID_PGM }, ++ { VLC_CODEC_PGMYUV, AV_CODEC_ID_PGMYUV }, ++ { VLC_CODEC_PAM, AV_CODEC_ID_PAM }, ++ { VLC_CODEC_FFVHUFF, AV_CODEC_ID_FFVHUFF }, ++ { VLC_CODEC_RV30, AV_CODEC_ID_RV30 }, ++ { VLC_CODEC_RV40, AV_CODEC_ID_RV40 }, ++ { VLC_CODEC_VC1, AV_CODEC_ID_VC1 }, ++ { VLC_CODEC_WMVA, AV_CODEC_ID_VC1 }, ++ { VLC_CODEC_WMV3, AV_CODEC_ID_WMV3 }, ++ { VLC_CODEC_WMVP, AV_CODEC_ID_WMV3 }, ++ { VLC_CODEC_LOCO, AV_CODEC_ID_LOCO }, ++ { VLC_CODEC_WNV1, AV_CODEC_ID_WNV1 }, ++ { VLC_CODEC_AASC, AV_CODEC_ID_AASC }, ++ { VLC_CODEC_INDEO2, AV_CODEC_ID_INDEO2 }, ++ { VLC_CODEC_FRAPS, AV_CODEC_ID_FRAPS }, ++ { VLC_CODEC_TRUEMOTION2, AV_CODEC_ID_TRUEMOTION2 }, ++ { VLC_CODEC_BMP, AV_CODEC_ID_BMP }, ++ { VLC_CODEC_CSCD, AV_CODEC_ID_CSCD }, ++ { VLC_CODEC_MMVIDEO, AV_CODEC_ID_MMVIDEO }, ++ { VLC_CODEC_ZMBV, AV_CODEC_ID_ZMBV }, ++ { VLC_CODEC_AVS, AV_CODEC_ID_AVS }, ++ { VLC_CODEC_SMACKVIDEO, AV_CODEC_ID_SMACKVIDEO }, ++ { VLC_CODEC_NUV, AV_CODEC_ID_NUV }, ++ { VLC_CODEC_KMVC, AV_CODEC_ID_KMVC }, ++ { VLC_CODEC_FLASHSV, AV_CODEC_ID_FLASHSV }, ++ { VLC_CODEC_CAVS, AV_CODEC_ID_CAVS }, ++ { VLC_CODEC_JPEG2000, AV_CODEC_ID_JPEG2000 }, ++ { VLC_CODEC_VMNC, AV_CODEC_ID_VMNC }, ++ { VLC_CODEC_VP5, AV_CODEC_ID_VP5 }, ++ { VLC_CODEC_VP6, AV_CODEC_ID_VP6 }, ++ { VLC_CODEC_VP6F, AV_CODEC_ID_VP6F }, ++ { VLC_CODEC_TARGA, AV_CODEC_ID_TARGA }, ++ { VLC_CODEC_DSICINVIDEO, AV_CODEC_ID_DSICINVIDEO }, ++ { VLC_CODEC_TIERTEXSEQVIDEO, AV_CODEC_ID_TIERTEXSEQVIDEO }, ++ { VLC_CODEC_TIFF, AV_CODEC_ID_TIFF }, ++ { VLC_CODEC_GIF, AV_CODEC_ID_GIF }, ++ { VLC_CODEC_DXA, AV_CODEC_ID_DXA }, ++ { VLC_CODEC_DNXHD, AV_CODEC_ID_DNXHD }, ++ { VLC_CODEC_THP, AV_CODEC_ID_THP }, ++ { VLC_CODEC_SGI, AV_CODEC_ID_SGI }, ++ { VLC_CODEC_C93, AV_CODEC_ID_C93 }, ++ { VLC_CODEC_BETHSOFTVID, AV_CODEC_ID_BETHSOFTVID }, ++ /* AV_CODEC_ID_PTX */ ++ { VLC_CODEC_TXD, AV_CODEC_ID_TXD }, ++ { VLC_CODEC_VP6A, AV_CODEC_ID_VP6A }, ++ { VLC_CODEC_AMV, AV_CODEC_ID_AMV }, ++ { VLC_CODEC_VB, AV_CODEC_ID_VB }, ++ { VLC_CODEC_PCX, AV_CODEC_ID_PCX }, ++ /* AV_CODEC_ID_SUNRAST */ ++ { VLC_CODEC_INDEO4, AV_CODEC_ID_INDEO4 }, ++ { VLC_CODEC_INDEO5, AV_CODEC_ID_INDEO5 }, ++ { VLC_CODEC_MIMIC, AV_CODEC_ID_MIMIC }, ++ { VLC_CODEC_RL2, AV_CODEC_ID_RL2 }, ++ { VLC_CODEC_ESCAPE124, AV_CODEC_ID_ESCAPE124 }, ++ { VLC_CODEC_DIRAC, AV_CODEC_ID_DIRAC }, ++ { VLC_CODEC_BFI, AV_CODEC_ID_BFI }, ++ { VLC_CODEC_CMV, AV_CODEC_ID_CMV }, ++ { VLC_CODEC_MOTIONPIXELS, AV_CODEC_ID_MOTIONPIXELS }, ++ { VLC_CODEC_TGV, AV_CODEC_ID_TGV }, ++ { VLC_CODEC_TGQ, AV_CODEC_ID_TGQ }, ++ { VLC_CODEC_TQI, AV_CODEC_ID_TQI }, ++ { VLC_CODEC_AURA, AV_CODEC_ID_AURA }, ++ /* AV_CODEC_ID_AURA2 */ ++ /* AV_CODEC_ID_V210X */ ++ { VLC_CODEC_TMV, AV_CODEC_ID_TMV }, ++ { VLC_CODEC_V210, AV_CODEC_ID_V210 }, ++#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( 54, 50, 100 ) && LIBAVCODEC_VERSION_MICRO >= 100 ++ { VLC_CODEC_VUYA, AV_CODEC_ID_AYUV }, ++#endif ++ /* AV_CODEC_ID_DPX */ ++ { VLC_CODEC_MAD, AV_CODEC_ID_MAD }, ++ { VLC_CODEC_FRWU, AV_CODEC_ID_FRWU }, ++ { VLC_CODEC_FLASHSV2, AV_CODEC_ID_FLASHSV2 }, ++ /* AV_CODEC_ID_CDGRAPHICS */ ++ /* AV_CODEC_ID_R210 */ ++ { VLC_CODEC_ANM, AV_CODEC_ID_ANM }, ++ { VLC_CODEC_BINKVIDEO, AV_CODEC_ID_BINKVIDEO }, ++ /* AV_CODEC_ID_IFF_ILBM */ ++ /* AV_CODEC_ID_IFF_BYTERUN1 */ ++ { VLC_CODEC_KGV1, AV_CODEC_ID_KGV1 }, ++ { VLC_CODEC_YOP, AV_CODEC_ID_YOP }, ++ { VLC_CODEC_VP8, AV_CODEC_ID_VP8 }, ++ /* AV_CODEC_ID_PICTOR */ ++ /* AV_CODEC_ID_ANSI */ ++ /* AV_CODEC_ID_A64_MULTI */ ++ /* AV_CODEC_ID_A64_MULTI5 */ ++ /* AV_CODEC_ID_R10K */ ++ { VLC_CODEC_MXPEG, AV_CODEC_ID_MXPEG }, ++ { VLC_CODEC_LAGARITH, AV_CODEC_ID_LAGARITH }, ++ { VLC_CODEC_PRORES, AV_CODEC_ID_PRORES }, ++ { VLC_CODEC_JV, AV_CODEC_ID_JV }, ++ { VLC_CODEC_DFA, AV_CODEC_ID_DFA }, ++ { VLC_CODEC_WMVP, AV_CODEC_ID_WMV3IMAGE }, ++ { VLC_CODEC_WMVP2, AV_CODEC_ID_VC1IMAGE }, ++ { VLC_CODEC_UTVIDEO, AV_CODEC_ID_UTVIDEO }, ++ { VLC_CODEC_BMVVIDEO, AV_CODEC_ID_BMV_VIDEO }, ++ { VLC_CODEC_VBLE, AV_CODEC_ID_VBLE }, ++ { VLC_CODEC_DXTORY, AV_CODEC_ID_DXTORY }, ++ /* AV_CODEC_ID_V410 */ ++ /* AV_CODEC_ID_XWD */ ++ { VLC_CODEC_CDXL, AV_CODEC_ID_CDXL }, ++ /* AV_CODEC_ID_XBM */ ++ /* AV_CODEC_ID_ZEROCODEC */ ++ { VLC_CODEC_MSS1, AV_CODEC_ID_MSS1 }, ++ { VLC_CODEC_MSA1, AV_CODEC_ID_MSA1 }, ++ { VLC_CODEC_TSC2, AV_CODEC_ID_TSCC2 }, ++ { VLC_CODEC_MTS2, AV_CODEC_ID_MTS2 }, ++ { VLC_CODEC_CLLC, AV_CODEC_ID_CLLC }, ++ { VLC_CODEC_MSS2, AV_CODEC_ID_MSS2 }, ++ { VLC_CODEC_VP9, AV_CODEC_ID_VP9 }, ++#if LIBAVCODEC_VERSION_CHECK( 57, 26, 0, 83, 101 ) ++ { VLC_CODEC_AV1, AV_CODEC_ID_AV1 }, ++#endif ++ { VLC_CODEC_ICOD, AV_CODEC_ID_AIC }, ++ /* AV_CODEC_ID_ESCAPE130 */ ++ { VLC_CODEC_G2M4, AV_CODEC_ID_G2M }, ++ { VLC_CODEC_G2M2, AV_CODEC_ID_G2M }, ++ { VLC_CODEC_G2M3, AV_CODEC_ID_G2M }, ++ /* AV_CODEC_ID_WEBP */ ++ { VLC_CODEC_HNM4_VIDEO, AV_CODEC_ID_HNM4_VIDEO }, ++ { VLC_CODEC_HEVC, AV_CODEC_ID_HEVC }, + -+ vlc_mutex_lock(&p->lock); ++ { VLC_CODEC_FIC , AV_CODEC_ID_FIC }, ++ /* AV_CODEC_ID_ALIAS_PIX */ ++ /* AV_CODEC_ID_BRENDER_PIX */ ++ /* AV_CODEC_ID_PAF_VIDEO */ ++ /* AV_CODEC_ID_EXR */ + -+ for (;;) -+ { -+ if (req_el_size != p->el_size) -+ { -+ void ** const deadpool = p->pool; -+ const size_t dead_size = p->el_size; -+ const unsigned int dead_n = p->pool_size; ++ { VLC_CODEC_VP7 , AV_CODEC_ID_VP7 }, ++ /* AV_CODEC_ID_SANM */ ++ /* AV_CODEC_ID_SGIRLE */ ++ /* AV_CODEC_ID_MVC1 */ ++ /* AV_CODEC_ID_MVC2 */ ++ { VLC_CODEC_HQX, AV_CODEC_ID_HQX }, + -+ p->pool = NULL; -+ p->n_in = 0; -+ p->n_out = 0; -+ p->el_size = req_el_size; ++ { VLC_CODEC_TDSC, AV_CODEC_ID_TDSC }, + -+ if (deadpool != NULL) -+ { -+ vlc_mutex_unlock(&p->lock); -+ // Do the free old op outside the mutex in case the free is slow -+ free_pool(p, deadpool, dead_n, dead_size); -+ vlc_mutex_lock(&p->lock); -+ continue; -+ } -+ } ++ { VLC_CODEC_HQ_HQA, AV_CODEC_ID_HQ_HQA }, + -+ // Late abort if flush or cancel so we can still kill the pool -+ if (req_el_size == 0 || p->cancel) -+ { -+ vlc_mutex_unlock(&p->lock); -+ return NULL; -+ } -+ -+ if (p->pool != NULL && !no_pool) -+ { -+ v = p->pool[p->n_in]; -+ if (v != NULL) -+ { -+ p->pool[p->n_in] = NULL; -+ p->n_in = inc_mod(p->n_in, p->pool_size); -+ break; -+ } -+ } -+ -+ if (p->in_flight <= 0) -+ break; ++ { VLC_CODEC_HAP, AV_CODEC_ID_HAP }, ++ /* AV_CODEC_ID_DDS */ + -+ vlc_cond_wait(&p->flight_cond, &p->lock); -+ } ++ { VLC_CODEC_DXV, AV_CODEC_ID_DXV }, + -+ if (inc_flight) -+ ++p->in_flight; ++ /* ffmpeg only: AV_CODEC_ID_BRENDER_PIX */ ++ /* ffmpeg only: AV_CODEC_ID_Y41P */ ++ /* ffmpeg only: AV_CODEC_ID_EXR */ ++ /* ffmpeg only: AV_CODEC_ID_AVRP */ ++ /* ffmpeg only: AV_CODEC_ID_012V */ ++ /* ffmpeg only: AV_CODEC_ID_AVUI */ ++ /* ffmpeg only: AV_CODEC_ID_TARGA_Y216 */ ++ /* ffmpeg only: AV_CODEC_ID_V308 */ ++ /* ffmpeg only: AV_CODEC_ID_V408 */ ++ /* ffmpeg only: AV_CODEC_ID_YUV4 */ ++ /* ffmpeg only: AV_CODEC_ID_SANM */ ++ /* ffmpeg only: AV_CODEC_ID_PAF_VIDEO */ ++ /* ffmpeg only: AV_CODEC_ID_AVRN */ ++ /* ffmpeg only: AV_CODEC_ID_CPIA */ ++ /* ffmpeg only: AV_CODEC_ID_XFACE */ ++ /* ffmpeg only: AV_CODEC_ID_SGIRLE */ ++ /* ffmpeg only: AV_CODEC_ID_MVC1 */ ++ /* ffmpeg only: AV_CODEC_ID_MVC2 */ ++ /* ffmpeg only: AV_CODEC_ID_SNOW */ ++ /* ffmpeg only: AV_CODEC_ID_SMVJPEG */ + -+ vlc_mutex_unlock(&p->lock); ++#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 24, 102 ) ++ { VLC_CODEC_CINEFORM, AV_CODEC_ID_CFHD }, ++#endif + -+ if (v == NULL && req_el_size != 0) -+ v = p->el_alloc_fn(p->alloc_v, req_el_size); ++#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 70, 100 ) ++ { VLC_CODEC_PIXLET, AV_CODEC_ID_PIXLET }, ++#endif + -+ // Tag ref -+ if (v != NULL) -+ cma_pool_fixed_ref(p); -+ // Remove flight if we set it and error -+ else if (inc_flight) -+ cma_pool_fixed_dec_in_flight(p); ++#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 71, 101 ) ++ { VLC_CODEC_SPEEDHQ, AV_CODEC_ID_SPEEDHQ }, ++#endif + -+ return v; -+} ++#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 79, 100 ) ++ { VLC_CODEC_FMVC, AV_CODEC_ID_FMVC }, ++#endif ++}; + -+static void cma_pool_fixed_put(cma_pool_fixed_t * const p, void * v, const size_t el_size, const bool was_in_flight) ++// *** Really we should probably use GetFfmpegCodec with a pre-kludge for the bits we care about ++static bool ++ZcGetFfmpegCodec( enum es_format_category_e cat, vlc_fourcc_t i_fourcc, ++ unsigned *pi_ffmpeg_codec, const char **ppsz_name ) +{ -+ vlc_mutex_lock(&p->lock); ++ const struct vlc_avcodec_fourcc *base; ++ size_t count; + -+ if (el_size == p->el_size && (p->pool == NULL || p->pool[p->n_out] == NULL)) -+ { -+ if (p->pool == NULL) -+ p->pool = calloc(p->pool_size, sizeof(void*)); ++ base = video_codecs; ++ count = ARRAY_SIZE(video_codecs); ++ i_fourcc = vlc_fourcc_GetCodec( cat, i_fourcc ); + -+ p->pool[p->n_out] = v; -+ p->n_out = inc_mod(p->n_out, p->pool_size); -+ v = NULL; ++ for( size_t i = 0; i < count; i++ ) ++ { ++ if( base[i].i_fourcc == i_fourcc ) ++ { ++ if( pi_ffmpeg_codec != NULL ) ++ *pi_ffmpeg_codec = base[i].i_codec; ++ if( ppsz_name ) ++ *ppsz_name = vlc_fourcc_GetDescription( cat, i_fourcc ); ++ return true; ++ } + } ++ return false; ++} + -+ if (was_in_flight) -+ --p->in_flight; -+ -+ vlc_mutex_unlock(&p->lock); -+ -+ vlc_cond_signal(&p->flight_cond); + -+ if (v != NULL) -+ p->el_free_fn(p->alloc_v, v, el_size); + -+ cma_pool_fixed_unref(p); -+} ++//============================================================================ ++// Derived from codec/avcodec/avcodec.c + -+static int cma_pool_fixed_resize(cma_pool_fixed_t * const p, -+ const unsigned int new_pool_size, const int new_flight_size) ++static AVCodecContext * ++ZcFfmpeg_AllocContext( decoder_t *p_dec, ++ const AVCodec **restrict codecp ) +{ -+ void ** dead_pool = NULL; -+ size_t dead_size = 0; -+ unsigned int dead_n = 0; ++ unsigned i_codec_id; ++ const char *psz_namecodec; ++ const AVCodec *p_codec = NULL; + -+ // This makes this non-reentrant but saves us a lot of time in the normal -+ // "nothing happens" case -+ if (p->pool_size == new_pool_size && p->flight_size == new_flight_size) -+ return 0; ++ /* *** determine codec type *** */ ++ if( !ZcGetFfmpegCodec( p_dec->fmt_in.i_cat, p_dec->fmt_in.i_codec, ++ &i_codec_id, &psz_namecodec ) ) ++ return NULL; + -+ vlc_mutex_lock(&p->lock); ++ msg_Dbg( p_dec, "using %s %s", AVPROVIDER(LIBAVCODEC), LIBAVCODEC_IDENT ); + -+ if (p->pool != NULL && new_pool_size != p->pool_size) -+ { -+ void ** const new_pool = calloc(new_pool_size, sizeof(void*)); -+ unsigned int d, s; -+ dead_pool = p->pool; -+ dead_size = p->el_size; -+ dead_n = p->pool_size; ++ /* Initialization must be done before avcodec_find_decoder() */ ++ vlc_init_avcodec(VLC_OBJECT(p_dec)); + -+ if (new_pool == NULL) ++ /* *** ask ffmpeg for a decoder *** */ ++ char *psz_decoder = var_InheritString( p_dec, "avcodec-codec" ); ++ if( psz_decoder != NULL ) ++ { ++ p_codec = avcodec_find_decoder_by_name( psz_decoder ); ++ if( !p_codec ) ++ msg_Err( p_dec, "Decoder `%s' not found", psz_decoder ); ++ else if( p_codec->id != i_codec_id ) + { -+ vlc_mutex_unlock(&p->lock); -+ return -1; ++ msg_Err( p_dec, "Decoder `%s' can't handle %4.4s", ++ psz_decoder, (char*)&p_dec->fmt_in.i_codec ); ++ p_codec = NULL; + } ++ free( psz_decoder ); ++ } ++ if( !p_codec ) ++// p_codec = avcodec_find_decoder( i_codec_id ); ++ { ++ if( p_dec->fmt_in.i_codec != VLC_CODEC_HEVC ) ++ p_codec = avcodec_find_decoder(i_codec_id); ++ else ++ { ++ psz_namecodec = rpi_use_pi3_hevc() ? "hevc_rpi" : "hevc" ; ++ msg_Info(p_dec, "Looking for HEVC decoder '%s'", psz_namecodec); ++ p_codec = avcodec_find_decoder_by_name(psz_namecodec); ++ } ++ } + -+ for (d = 0, s = p->n_in; d != new_pool_size && (new_pool[d] = dead_pool[s]) != NULL; ++d, s = inc_mod(s, dead_n)) -+ dead_pool[s] = NULL; -+ -+ p->n_out = 0; -+ p->n_in = (d != new_pool_size) ? d : 0; -+ p->pool = new_pool; ++ if( !p_codec ) ++ { ++ msg_Dbg( p_dec, "codec not found (%s)", psz_namecodec ); ++ return NULL; + } + -+ p->pool_size = new_pool_size; -+ if (new_flight_size > p->flight_size) -+ vlc_cond_broadcast(&p->flight_cond); // Lock still active so nothing happens till we release it -+ p->in_flight += p->flight_size - new_flight_size; -+ p->flight_size = new_flight_size; ++ *codecp = p_codec; + -+ vlc_mutex_unlock(&p->lock); ++ /* *** get a p_context *** */ ++ AVCodecContext *avctx = avcodec_alloc_context3(p_codec); ++ if( unlikely(avctx == NULL) ) ++ return NULL; + -+ free_pool(p, dead_pool, dead_n, dead_size); -+ return 0; ++ avctx->debug = var_InheritInteger( p_dec, "avcodec-debug" ); ++ avctx->opaque = p_dec; ++ return avctx; +} + -+static int cma_pool_fixed_fill(cma_pool_fixed_t * const p, const size_t el_size) ++/***************************************************************************** ++ * ffmpeg_OpenCodec: ++ *****************************************************************************/ ++ ++static int ++ZcFfmpeg_OpenCodec( decoder_t *p_dec, AVCodecContext *ctx, ++ const AVCodec *codec ) +{ -+ for (;;) ++ char *psz_opts = var_InheritString( p_dec, "avcodec-options" ); ++ AVDictionary *options = NULL; ++ int ret; ++ ++ if (psz_opts) { ++ vlc_av_get_options(psz_opts, &options); ++ free(psz_opts); ++ } ++ ++ if (!p_dec->p_sys->use_drm && ++ av_rpi_zc_init2(ctx, p_dec, zc_alloc_buf, zc_free_pool) != 0) + { -+ vlc_mutex_lock(&p->lock); -+ bool done = el_size == p->el_size && p->pool != NULL && p->pool[p->n_out] != NULL; -+ vlc_mutex_unlock(&p->lock); -+ if (done) -+ break; -+ void * buf = cma_pool_fixed_get(p, el_size, false, true); -+ if (buf == NULL) -+ return -ENOMEM; -+ cma_pool_fixed_put(p, buf, el_size, false); ++ msg_Err(p_dec, "Failed to init AV ZC"); ++ return VLC_EGENERIC; + } -+ return 0; -+} + -+static void cma_pool_fixed_cancel(cma_pool_fixed_t * const p) -+{ -+ vlc_mutex_lock(&p->lock); -+ p->cancel = true; -+ vlc_cond_broadcast(&p->flight_cond); -+ vlc_mutex_unlock(&p->lock); -+} ++ vlc_avcodec_lock(); ++ ret = avcodec_open2( ctx, codec, options ? &options : NULL ); ++ vlc_avcodec_unlock(); + -+static void cma_pool_fixed_uncancel(cma_pool_fixed_t * const p) -+{ -+ vlc_mutex_lock(&p->lock); -+ p->cancel = false; -+ vlc_mutex_unlock(&p->lock); ++ AVDictionaryEntry *t = NULL; ++ while ((t = av_dict_get(options, "", t, AV_DICT_IGNORE_SUFFIX))) { ++ msg_Err( p_dec, "Unknown option \"%s\"", t->key ); ++ } ++ av_dict_free(&options); ++ ++ if( ret < 0 ) ++ { ++ msg_Err( p_dec, "cannot start codec (%s)", codec->name ); ++ return VLC_EGENERIC; ++ } ++ ++ msg_Dbg( p_dec, "codec (%s) started", codec->name ); ++ return VLC_SUCCESS; +} + ++//============================================================================ ++// Derived from 3.0.7.1 codec/avcodec/video.c + -+// Purge pool & unref -+static void cma_pool_fixed_kill(cma_pool_fixed_t * const p) ++static inline void wait_mt(decoder_sys_t *sys) +{ -+ if (p == NULL) -+ return; -+ -+ // This flush is not strictly needed but it reclaims what memory we can reclaim asap -+ cma_pool_fixed_get(p, 0, false, false); -+ cma_pool_fixed_unref(p); ++#if 1 ++ // As we only ever update the output in our main thread this lock is ++ // redundant ++ VLC_UNUSED(sys); ++#else ++ vlc_sem_wait(&sys->sem_mt); ++#endif +} + -+// Create a new pool -+static cma_pool_fixed_t* -+cma_pool_fixed_new(const unsigned int pool_size, -+ const int flight_size, -+ void * const alloc_v, -+ cma_pool_alloc_fn * const alloc_fn, cma_pool_free_fn * const free_fn, -+ cma_pool_on_delete_fn * const on_delete_fn, -+ const char * const name) ++static inline void post_mt(decoder_sys_t *sys) +{ -+ cma_pool_fixed_t* const p = calloc(1, sizeof(cma_pool_fixed_t)); -+ if (p == NULL) -+ return NULL; -+ -+ atomic_store(&p->ref_count, 1); -+ vlc_mutex_init(&p->lock); -+ vlc_cond_init(&p->flight_cond); -+ -+ p->pool_size = pool_size; -+ p->flight_size = flight_size; -+ p->in_flight = -flight_size; -+ -+ p->alloc_v = alloc_v; -+ p->el_alloc_fn = alloc_fn; -+ p->el_free_fn = free_fn; -+ p->on_delete_fn = on_delete_fn; -+ p->name = name == NULL ? NULL : strdup(name); -+#if TRACE_ALL -+ p->seq = atomic_fetch_add(&pool_seq, 1); ++#if 1 ++ // As we only ever update the output in our main thread this lock is ++ // redundant ++ VLC_UNUSED(sys); ++#else ++ vlc_sem_post(&sys->sem_mt); +#endif -+ -+ return p; +} + -+// --------------------------------------------------------------------------- -+// -+// CMA buffer functions - uses cma_pool_fixed for pooling -+ -+struct cma_buf_pool_s { -+ cma_pool_fixed_t * pool; -+ vcsm_init_type_t init_type; ++/***************************************************************************** ++ * Local prototypes ++ *****************************************************************************/ ++static void ffmpeg_InitCodec ( decoder_t * ); ++static int DecodeVideo( decoder_t *, block_t * ); ++static void Flush( decoder_t * ); + -+ bool all_in_flight; -+#if TRACE_ALL -+ size_t alloc_n; -+ size_t alloc_size; -+#endif -+}; ++static uint32_t ffmpeg_CodecTag( vlc_fourcc_t fcc ) ++{ ++ uint8_t *p = (uint8_t*)&fcc; ++ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); ++} + -+typedef struct cma_buf_s { -+ atomic_int ref_count; -+ cma_buf_pool_t * cbp; -+ bool in_flight; -+ size_t size; -+ unsigned int vcsm_h; // VCSM handle from initial alloc -+ unsigned int vc_h; // VC handle for ZC mmal buffers -+ unsigned int vc_addr; // VC addr - unused by us but wanted by FFmpeg -+ int fd; // dmabuf handle for GL -+ void * mmap; // ARM mapped address -+ picture_context_t *ctx2; -+} cma_buf_t; ++/***************************************************************************** ++ * Local Functions ++ *****************************************************************************/ + -+static void cma_pool_delete(cma_buf_t * const cb) ++/** ++ * Sets the decoder output format. ++ */ ++static int lavc_GetVideoFormat(decoder_t *dec, video_format_t *restrict fmt, ++ AVCodecContext *ctx, enum AVPixelFormat pix_fmt, ++ enum AVPixelFormat sw_pix_fmt) +{ -+ assert(atomic_load(&cb->ref_count) == 0); -+#if TRACE_ALL -+ cb->cbp->alloc_size -= cb->size; -+ --cb->cbp->alloc_n; -+ fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cb->cbp->pool->seq, cb->cbp->pool->name, cb->cbp->alloc_n, cb->cbp->alloc_size); -+#endif ++ int width = ctx->coded_width; ++ int height = ctx->coded_height; + -+ if (cb->ctx2 != NULL) -+ cb->ctx2->destroy(cb->ctx2); ++ video_format_Init(fmt, 0); + -+ if (cb->mmap != MAP_FAILED) -+ { -+ if (cb->cbp->init_type == VCSM_INIT_CMA) -+ munmap(cb->mmap, cb->size); -+ else -+ vcsm_unlock_hdl(cb->vcsm_h); ++#if 1 ++ if ((fmt->i_chroma = ZcFindVlcChroma(pix_fmt, sw_pix_fmt)) == 0) { ++ msg_Info(dec, "Find chroma fail"); ++ return -1; + } -+ if (cb->fd != -1) -+ close(cb->fd); -+ if (cb->vcsm_h != 0) -+ vcsm_free(cb->vcsm_h); -+ free(cb); -+} -+ -+static void cma_pool_free_cb(void * v, void * el, size_t size) -+{ -+ VLC_UNUSED(v); -+ VLC_UNUSED(size); -+ -+ cma_pool_delete(el); -+} ++#else ++ if (pix_fmt == sw_pix_fmt) ++ { /* software decoding */ ++ int aligns[AV_NUM_DATA_POINTERS]; + -+static void * cma_pool_alloc_cb(void * v, size_t size) -+{ -+ cma_buf_pool_t * const cbp = v; ++ if (GetVlcChroma(fmt, pix_fmt)) ++ return -1; + -+ cma_buf_t * const cb = malloc(sizeof(cma_buf_t)); -+ if (cb == NULL) -+ return NULL; ++ /* The libavcodec palette can only be fetched when the first output ++ * frame is decoded. Assume that the current chroma is RGB32 while we ++ * are waiting for a valid palette. Indeed, fmt_out.video.p_palette ++ * doesn't trigger a new vout request, but a new chroma yes. */ ++ if (pix_fmt == AV_PIX_FMT_PAL8 && !dec->fmt_out.video.p_palette) ++ fmt->i_chroma = VLC_CODEC_RGB32; + -+ *cb = (cma_buf_t){ -+ .ref_count = ATOMIC_VAR_INIT(0), -+ .cbp = cbp, -+ .in_flight = 0, -+ .size = size, -+ .vcsm_h = 0, -+ .vc_h = 0, -+ .fd = -1, -+ .mmap = MAP_FAILED, -+ .ctx2 = NULL -+ }; -+#if TRACE_ALL -+ cb->cbp->alloc_size += cb->size; -+ ++cb->cbp->alloc_n; -+ fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cbp->pool->seq, cbp->pool->name, cbp->alloc_n, cbp->alloc_size); ++ avcodec_align_dimensions2(ctx, &width, &height, aligns); ++ } ++ else /* hardware decoding */ ++ fmt->i_chroma = vlc_va_GetChroma(pix_fmt, sw_pix_fmt); +#endif + -+ // 0x80 is magic value to force full ARM-side mapping - otherwise -+ // cache requests can cause kernel crashes -+ if ((cb->vcsm_h = vcsm_malloc_cache(size, VCSM_CACHE_TYPE_HOST | 0x80, "VLC frame")) == 0) ++ if( width == 0 || height == 0 || width > 8192 || height > 8192 || ++ width < ctx->width || height < ctx->height ) + { -+#if TRACE_ALL -+ fprintf(stderr, "vcsm_malloc_cache fail\n"); -+#endif -+ goto fail; ++ msg_Err(dec, "Invalid frame size %dx%d vsz %dx%d", ++ width, height, ctx->width, ctx->height ); ++ return -1; /* invalid display size */ + } + -+ if ((cb->vc_h = vcsm_vc_hdl_from_hdl(cb->vcsm_h)) == 0) -+ { -+#if TRACE_ALL -+ fprintf(stderr, "vcsm_vc_hdl_from_hdl fail\n"); -+#endif -+ goto fail; -+ } ++ fmt->i_width = width; ++ fmt->i_height = height; ++ fmt->i_visible_width = ctx->width; ++ fmt->i_visible_height = ctx->height; + -+ if (cbp->init_type == VCSM_INIT_CMA) ++ /* If an aspect-ratio was specified in the input format then force it */ ++ if (dec->fmt_in.video.i_sar_num > 0 && dec->fmt_in.video.i_sar_den > 0) + { -+ if ((cb->fd = vcsm_export_dmabuf(cb->vcsm_h)) == -1) -+ { -+#if TRACE_ALL -+ fprintf(stderr, "vcsm_export_dmabuf fail\n"); -+#endif -+ goto fail; -+ } -+ -+ if ((cb->mmap = mmap(NULL, cb->size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, cb->fd, 0)) == MAP_FAILED) -+ goto fail; ++ fmt->i_sar_num = dec->fmt_in.video.i_sar_num; ++ fmt->i_sar_den = dec->fmt_in.video.i_sar_den; + } + else + { -+ void * arm_addr; -+ if ((arm_addr = vcsm_lock(cb->vcsm_h)) == NULL) -+ { -+#if TRACE_ALL -+ fprintf(stderr, "vcsm_lock fail\n"); -+#endif -+ goto fail; -+ } -+ cb->mmap = arm_addr; -+ } ++ fmt->i_sar_num = ctx->sample_aspect_ratio.num; ++ fmt->i_sar_den = ctx->sample_aspect_ratio.den; + -+ cb->vc_addr = vcsm_vc_addr_from_hdl(cb->vcsm_h); ++ if (fmt->i_sar_num == 0 || fmt->i_sar_den == 0) ++ fmt->i_sar_num = fmt->i_sar_den = 1; ++ } + -+ return cb; ++ if (dec->fmt_in.video.i_frame_rate > 0 ++ && dec->fmt_in.video.i_frame_rate_base > 0) ++ { ++ fmt->i_frame_rate = dec->fmt_in.video.i_frame_rate; ++ fmt->i_frame_rate_base = dec->fmt_in.video.i_frame_rate_base; ++ } ++ else if (ctx->framerate.num > 0 && ctx->framerate.den > 0) ++ { ++ fmt->i_frame_rate = ctx->framerate.num; ++ fmt->i_frame_rate_base = ctx->framerate.den; ++# if LIBAVCODEC_VERSION_MICRO < 100 ++ // for some reason libav don't thinkg framerate presents actually same thing as in ffmpeg ++ fmt->i_frame_rate_base *= __MAX(ctx->ticks_per_frame, 1); ++# endif ++ } ++ else if (ctx->time_base.num > 0 && ctx->time_base.den > 0) ++ { ++ fmt->i_frame_rate = ctx->time_base.den; ++ fmt->i_frame_rate_base = ctx->time_base.num ++ * __MAX(ctx->ticks_per_frame, 1); ++ } + -+fail: -+ cma_pool_delete(cb); -+ return NULL; -+} ++ /* FIXME we should only set the known values and let the core decide ++ * later of fallbacks, but we can't do that with a boolean */ ++ switch ( ctx->color_range ) ++ { ++ case AVCOL_RANGE_JPEG: ++ fmt->b_color_range_full = true; ++ break; ++ case AVCOL_RANGE_UNSPECIFIED: ++ fmt->b_color_range_full = !vlc_fourcc_IsYUV( fmt->i_chroma ); ++ break; ++ case AVCOL_RANGE_MPEG: ++ default: ++ fmt->b_color_range_full = false; ++ break; ++ } + -+// Pool has died - safe now to exit vcsm -+static void cma_buf_pool_on_delete_cb(void * v) -+{ -+ cma_buf_pool_t * const cbp = v; -+ -+ cma_vcsm_exit(cbp->init_type); -+ free(cbp); -+} ++ switch( ctx->colorspace ) ++ { ++ case AVCOL_SPC_BT709: ++ fmt->space = COLOR_SPACE_BT709; ++ break; ++ case AVCOL_SPC_SMPTE170M: ++ case AVCOL_SPC_BT470BG: ++ fmt->space = COLOR_SPACE_BT601; ++ break; ++ case AVCOL_SPC_BT2020_NCL: ++ case AVCOL_SPC_BT2020_CL: ++ fmt->space = COLOR_SPACE_BT2020; ++ break; ++ default: ++ break; ++ } + -+void cma_buf_pool_cancel(cma_buf_pool_t * const cbp) -+{ -+ if (cbp == NULL || cbp->pool == NULL) -+ return; ++ switch( ctx->color_trc ) ++ { ++ case AVCOL_TRC_LINEAR: ++ fmt->transfer = TRANSFER_FUNC_LINEAR; ++ break; ++ case AVCOL_TRC_GAMMA22: ++ fmt->transfer = TRANSFER_FUNC_SRGB; ++ break; ++ case AVCOL_TRC_BT709: ++ fmt->transfer = TRANSFER_FUNC_BT709; ++ break; ++ case AVCOL_TRC_SMPTE170M: ++ case AVCOL_TRC_BT2020_10: ++ case AVCOL_TRC_BT2020_12: ++ fmt->transfer = TRANSFER_FUNC_BT2020; ++ break; ++#if LIBAVUTIL_VERSION_CHECK( 55, 14, 0, 31, 100) ++ case AVCOL_TRC_ARIB_STD_B67: ++ fmt->transfer = TRANSFER_FUNC_ARIB_B67; ++ break; ++#endif ++#if LIBAVUTIL_VERSION_CHECK( 55, 17, 0, 37, 100) ++ case AVCOL_TRC_SMPTE2084: ++ fmt->transfer = TRANSFER_FUNC_SMPTE_ST2084; ++ break; ++ case AVCOL_TRC_SMPTE240M: ++ fmt->transfer = TRANSFER_FUNC_SMPTE_240; ++ break; ++ case AVCOL_TRC_GAMMA28: ++ fmt->transfer = TRANSFER_FUNC_BT470_BG; ++ break; ++#endif ++ default: ++ break; ++ } + -+ cma_pool_fixed_cancel(cbp->pool); -+} ++ switch( ctx->color_primaries ) ++ { ++ case AVCOL_PRI_BT709: ++ fmt->primaries = COLOR_PRIMARIES_BT709; ++ break; ++ case AVCOL_PRI_BT470BG: ++ fmt->primaries = COLOR_PRIMARIES_BT601_625; ++ break; ++ case AVCOL_PRI_SMPTE170M: ++ case AVCOL_PRI_SMPTE240M: ++ fmt->primaries = COLOR_PRIMARIES_BT601_525; ++ break; ++ case AVCOL_PRI_BT2020: ++ fmt->primaries = COLOR_PRIMARIES_BT2020; ++ break; ++ default: ++ break; ++ } + -+void cma_buf_pool_uncancel(cma_buf_pool_t * const cbp) -+{ -+ if (cbp == NULL || cbp->pool == NULL) -+ return; ++ switch( ctx->chroma_sample_location ) ++ { ++ case AVCHROMA_LOC_LEFT: ++ fmt->chroma_location = CHROMA_LOCATION_LEFT; ++ break; ++ case AVCHROMA_LOC_CENTER: ++ fmt->chroma_location = CHROMA_LOCATION_CENTER; ++ break; ++ case AVCHROMA_LOC_TOPLEFT: ++ fmt->chroma_location = CHROMA_LOCATION_TOP_LEFT; ++ break; ++ default: ++ break; ++ } + -+ cma_pool_fixed_uncancel(cbp->pool); ++ return 0; +} + -+// User finished with pool -+void cma_buf_pool_delete(cma_buf_pool_t * const cbp) ++static int lavc_UpdateVideoFormat(decoder_t *dec, AVCodecContext *ctx, ++ enum AVPixelFormat fmt, ++ enum AVPixelFormat swfmt) +{ -+ if (cbp == NULL) -+ return; -+ -+ if (cbp->pool != NULL) ++ video_format_t fmt_out; ++ int val; ++#if TRACE_ALL ++ msg_Dbg(dec, "<<< %s", __func__); ++#endif ++ val = lavc_GetVideoFormat(dec, &fmt_out, ctx, fmt, swfmt); ++ if (val) + { -+ // We will call cma_buf_pool_on_delete_cb when the pool finally dies -+ // (might be now) which will free up our env. -+ cma_pool_fixed_kill(cbp->pool); ++ msg_Dbg(dec, "Failed to get format"); ++ return val; + } ++ ++ /* always have date in fields/ticks units */ ++ if(dec->p_sys->pts.i_divider_num) ++ date_Change(&dec->p_sys->pts, fmt_out.i_frame_rate * ++ __MAX(ctx->ticks_per_frame, 1), ++ fmt_out.i_frame_rate_base); + else -+ { -+ // Had no pool for some reason (error) but must still finish cleanup -+ cma_buf_pool_on_delete_cb(cbp); -+ } -+} ++ date_Init(&dec->p_sys->pts, fmt_out.i_frame_rate * ++ __MAX(ctx->ticks_per_frame, 1), ++ fmt_out.i_frame_rate_base); + -+int cma_buf_pool_fill(cma_buf_pool_t * const cbp, const size_t el_size) -+{ -+ return cma_pool_fixed_fill(cbp->pool, el_size); -+} ++ fmt_out.p_palette = dec-> fmt_out.video.p_palette; ++ dec->fmt_out.video.p_palette = NULL; + -+int cma_buf_pool_resize(cma_buf_pool_t * const cbp, -+ const unsigned int new_pool_size, const int new_flight_size) -+{ -+ return cma_pool_fixed_resize(cbp->pool, new_pool_size, new_flight_size); ++ es_format_Change(&dec->fmt_out, VIDEO_ES, fmt_out.i_chroma); ++ dec->fmt_out.video = fmt_out; ++ dec->fmt_out.video.orientation = dec->fmt_in.video.orientation; ++ dec->fmt_out.video.projection_mode = dec->fmt_in.video.projection_mode; ++ dec->fmt_out.video.multiview_mode = dec->fmt_in.video.multiview_mode; ++ dec->fmt_out.video.pose = dec->fmt_in.video.pose; ++ if ( dec->fmt_in.video.mastering.max_luminance ) ++ dec->fmt_out.video.mastering = dec->fmt_in.video.mastering; ++ dec->fmt_out.video.lighting = dec->fmt_in.video.lighting; ++ ++ val = decoder_UpdateVideoFormat(dec); ++#if TRACE_ALL ++ msg_Dbg(dec, ">>> %s: rv=%d", __func__, val); ++#endif ++ return val; +} + -+cma_buf_pool_t * cma_buf_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name) ++static int OpenVideoCodec( decoder_t *p_dec ) +{ -+ vcsm_init_type_t const init_type = cma_vcsm_init(); -+ if (init_type == VCSM_INIT_NONE) -+ return NULL; ++ decoder_sys_t *p_sys = p_dec->p_sys; ++ AVCodecContext *ctx = p_sys->p_context; ++ const AVCodec *codec = p_sys->p_codec; ++ int ret; + -+ cma_buf_pool_t * const cbp = calloc(1, sizeof(cma_buf_pool_t)); -+ if (cbp == NULL) -+ return NULL; ++ if( ctx->extradata_size <= 0 ) ++ { ++ if( codec->id == AV_CODEC_ID_VC1 || ++ codec->id == AV_CODEC_ID_THEORA ) ++ { ++ msg_Warn( p_dec, "waiting for extra data for codec %s", ++ codec->name ); ++ return 1; ++ } ++ } + -+ cbp->init_type = init_type; -+ cbp->all_in_flight = all_in_flight; ++ ctx->width = p_dec->fmt_in.video.i_visible_width; ++ ctx->height = p_dec->fmt_in.video.i_visible_height; + -+ if ((cbp->pool = cma_pool_fixed_new(pool_size, flight_size, cbp, cma_pool_alloc_cb, cma_pool_free_cb, cma_buf_pool_on_delete_cb, name)) == NULL) -+ goto fail; -+ return cbp; ++ ctx->coded_width = p_dec->fmt_in.video.i_width; ++ ctx->coded_height = p_dec->fmt_in.video.i_height; + -+fail: -+ cma_buf_pool_delete(cbp); -+ return NULL; -+} ++ ctx->bits_per_coded_sample = p_dec->fmt_in.video.i_bits_per_pixel; ++ p_sys->pix_fmt = AV_PIX_FMT_NONE; ++ p_sys->profile = -1; ++ p_sys->level = -1; ++ cc_Init( &p_sys->cc ); ++ ++ set_video_color_settings( &p_dec->fmt_in.video, ctx ); ++ if( p_dec->fmt_in.video.i_frame_rate_base && ++ p_dec->fmt_in.video.i_frame_rate && ++ (double) p_dec->fmt_in.video.i_frame_rate / ++ p_dec->fmt_in.video.i_frame_rate_base < 6 ) ++ { ++ ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; ++ } + ++ post_mt( p_sys ); ++ ret = ZcFfmpeg_OpenCodec( p_dec, ctx, codec ); ++ wait_mt( p_sys ); ++ if( ret < 0 ) ++ return ret; + -+void cma_buf_in_flight(cma_buf_t * const cb) -+{ -+ if (!cb->cbp->all_in_flight) ++ switch( ctx->active_thread_type ) + { -+ assert(!cb->in_flight); -+ cb->in_flight = true; -+ cma_pool_fixed_inc_in_flight(cb->cbp->pool); ++ case FF_THREAD_FRAME: ++ msg_Dbg( p_dec, "using frame thread mode with %d threads", ++ ctx->thread_count ); ++ break; ++ case FF_THREAD_SLICE: ++ msg_Dbg( p_dec, "using slice thread mode with %d threads", ++ ctx->thread_count ); ++ break; ++ case 0: ++ if( ctx->thread_count > 1 ) ++ msg_Warn( p_dec, "failed to enable threaded decoding" ); ++ break; ++ default: ++ msg_Warn( p_dec, "using unknown thread mode with %d threads", ++ ctx->thread_count ); ++ break; + } ++ return 0; +} + -+void cma_buf_end_flight(cma_buf_t * const cb) ++/***************************************************************************** ++ * InitVideo: initialize the video decoder ++ ***************************************************************************** ++ * the ffmpeg codec will be opened, some memory allocated. The vout is not yet ++ * opened (done after the first decoded frame). ++ *****************************************************************************/ ++ ++static bool has_legacy_rpivid() +{ -+ if (cb != NULL && !cb->cbp->all_in_flight && cb->in_flight) ++ static int cached_state = 0; ++ ++ if (cached_state == 0) + { -+ cb->in_flight = false; -+ cma_pool_fixed_dec_in_flight(cb->cbp->pool); ++ struct stat buf; ++ cached_state = stat("/dev/rpivid-intcmem", &buf) != 0 ? -1 : 1; + } ++ return cached_state > 0; +} + + -+// Return vcsm handle -+unsigned int cma_buf_vcsm_handle(const cma_buf_t * const cb) ++static int MmalAvcodecOpenDecoder( vlc_object_t *obj ) +{ -+ return cb->vcsm_h; -+} -+ -+size_t cma_buf_size(const cma_buf_t * const cb) -+{ -+ return cb->size; -+} -+ -+int cma_buf_add_context2(cma_buf_t *const cb, picture_context_t * const ctx2) -+{ -+ if (cb->ctx2 != NULL) -+ return VLC_EGENERIC; -+ -+ cb->ctx2 = ctx2; -+ return VLC_SUCCESS; -+} -+ -+unsigned int cma_buf_vc_handle(const cma_buf_t *const cb) -+{ -+ return cb->vc_h; -+} -+ -+int cma_buf_fd(const cma_buf_t *const cb) -+{ -+ return cb->fd; -+} -+ -+void * cma_buf_addr(const cma_buf_t *const cb) -+{ -+ return cb->mmap; -+} -+ -+unsigned int cma_buf_vc_addr(const cma_buf_t *const cb) -+{ -+ return cb->vc_addr; -+} -+ -+ -+picture_context_t * cma_buf_context2(const cma_buf_t *const cb) -+{ -+ return cb->ctx2; -+} ++ decoder_t *p_dec = (decoder_t *)obj; ++ const AVCodec *p_codec; ++ bool use_zc; ++ bool use_drm; ++ AVBufferRef * hw_dev_ctx = NULL; + ++ int extra_buffers = var_InheritInteger(p_dec, MMAL_AVCODEC_BUFFERS); + -+void cma_buf_unref(cma_buf_t * const cb) -+{ -+ if (cb == NULL) -+ return; -+ if (atomic_fetch_sub(&cb->ref_count, 1) <= 1) ++ if (extra_buffers < 0) + { -+ const bool was_in_flight = cb->in_flight; -+ cb->in_flight = false; -+ cma_pool_fixed_put(cb->cbp->pool, cb, cb->size, was_in_flight); ++ extra_buffers = p_dec->fmt_in.video.i_height * p_dec->fmt_in.video.i_width >= 1920 * 1088 ? ++ BUFFERS_IN_FLIGHT_UHD : BUFFERS_IN_FLIGHT; + } -+} -+ -+cma_buf_t * cma_buf_ref(cma_buf_t * const cb) -+{ -+ if (cb == NULL) -+ return NULL; -+ atomic_fetch_add(&cb->ref_count, 1); -+ return cb; -+} + -+cma_buf_t * cma_buf_pool_alloc_buf(cma_buf_pool_t * const cbp, const size_t size) -+{ -+ cma_buf_t *const cb = cma_pool_fixed_get(cbp->pool, size, cbp->all_in_flight, false); ++ if (extra_buffers <= 0) ++ { ++ msg_Dbg(p_dec, "%s: extra_buffers=%d - cannot use module", __func__, extra_buffers); ++ return VLC_EGENERIC; ++ } + -+ if (cb == NULL) -+ return NULL; ++ use_zc = has_legacy_rpivid(); ++ use_drm = !use_zc; // ** At least for the moment ++ const vcsm_init_type_t vcsm_type = use_zc ? cma_vcsm_init() : VCSM_INIT_NONE; ++ const int vcsm_size = vcsm_type == VCSM_INIT_NONE ? 0 : ++ vcsm_type == VCSM_INIT_LEGACY ? hw_mmal_get_gpu_mem() : 512 << 20; + -+ cb->in_flight = cbp->all_in_flight; -+ // When 1st allocated or retrieved from the pool the block will have a -+ // ref count of 0 so ref here -+ return cma_buf_ref(cb); -+} ++#if 1 ++ { ++ char buf1[5], buf2[5], buf2a[5]; ++ char buf3[5], buf4[5]; ++ uint32_t in_fcc = 0; ++ msg_Dbg(p_dec, "%s: <<< (%s/%s)[%s] %dx%d -> (%s/%s) %dx%d [%s/%d] xb:%d", __func__, ++ str_fourcc(buf1, p_dec->fmt_in.i_codec), ++ str_fourcc(buf2, p_dec->fmt_in.video.i_chroma), ++ str_fourcc(buf2a, in_fcc), ++ p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height, ++ str_fourcc(buf3, p_dec->fmt_out.i_codec), ++ str_fourcc(buf4, p_dec->fmt_out.video.i_chroma), ++ p_dec->fmt_out.video.i_width, p_dec->fmt_out.video.i_height, ++ cma_vcsm_init_str(vcsm_type), vcsm_size, extra_buffers); ++ } ++#endif + ---- /dev/null -+++ b/modules/hw/mmal/mmal_cma.h -@@ -0,0 +1,71 @@ -+#ifndef VLC_MMAL_MMAL_CMA_H_ -+#define VLC_MMAL_MMAL_CMA_H_ ++ if (vcsm_type == VCSM_INIT_NONE && !use_drm) ++ return VLC_EGENERIC; + ++ if (use_drm) ++ { ++ int dev_type = av_hwdevice_find_type_by_name("drm"); + -+struct cma_pool_fixed_s; -+typedef struct cma_pool_fixed_s cma_pool_fixed_t; ++ if (dev_type == AV_HWDEVICE_TYPE_NONE || ++ av_hwdevice_ctx_create(&hw_dev_ctx, dev_type, NULL, NULL, 0)) ++ { ++ msg_Warn(p_dec, "Failed to create drm hw device context"); ++ use_drm = false; ++ } ++ } + -+typedef void * cma_pool_alloc_fn(void * v, size_t size); -+typedef void cma_pool_free_fn(void * v, void * el, size_t size); -+typedef void cma_pool_on_delete_fn(void * v); + -+#if 0 -+void cma_pool_fixed_unref(cma_pool_fixed_t * const p); -+void cma_pool_fixed_ref(cma_pool_fixed_t * const p); -+void * cma_pool_fixed_get(cma_pool_fixed_t * const p, const size_t req_el_size, const bool in_flight); -+void cma_pool_fixed_put(cma_pool_fixed_t * const p, void * v, const size_t el_size, const bool was_in_flight); -+void cma_pool_fixed_inc_in_flight(cma_pool_fixed_t * const p); -+void cma_pool_fixed_dec_in_flight(cma_pool_fixed_t * const p); -+void cma_pool_fixed_cancel(cma_pool_fixed_t * const p); -+void cma_pool_fixed_uncancel(cma_pool_fixed_t * const p); -+void cma_pool_fixed_kill(cma_pool_fixed_t * const p); -+int cma_pool_fixed_resize(cma_pool_fixed_t * const p, -+ const unsigned int new_pool_size, const int new_flight_size); -+cma_pool_fixed_t * cma_pool_fixed_new(const unsigned int pool_size, -+ const int flight_size, -+ void * const alloc_v, -+ cma_pool_alloc_fn * const alloc_fn, cma_pool_free_fn * const free_fn, -+ cma_pool_on_delete_fn * const on_delete_fn, -+ const char * const name); ++#if 1 ++ if (use_zc && ++ ((p_dec->fmt_in.i_codec != VLC_CODEC_HEVC && ++ (vcsm_type == VCSM_INIT_CMA || vcsm_size < (96 << 20))) || ++ (p_dec->fmt_in.i_codec == VLC_CODEC_HEVC && ++ vcsm_size < (128 << 20)))) ++ { ++ cma_vcsm_exit(vcsm_type); ++ return VLC_EGENERIC; ++ } +#endif + -+struct cma_buf_s; -+typedef struct cma_buf_s cma_buf_t; -+ -+void cma_buf_in_flight(cma_buf_t * const cb); -+void cma_buf_end_flight(cma_buf_t * const cb); -+unsigned int cma_buf_vcsm_handle(const cma_buf_t * const cb); -+size_t cma_buf_size(const cma_buf_t * const cb); -+int cma_buf_add_context2(cma_buf_t *const cb, picture_context_t * const ctx2); -+unsigned int cma_buf_vc_handle(const cma_buf_t *const cb); -+int cma_buf_fd(const cma_buf_t *const cb); -+void * cma_buf_addr(const cma_buf_t *const cb); -+unsigned int cma_buf_vc_addr(const cma_buf_t *const cb); -+picture_context_t * cma_buf_context2(const cma_buf_t *const cb); -+ -+void cma_buf_unref(cma_buf_t * const cb); -+cma_buf_t * cma_buf_ref(cma_buf_t * const cb); -+ -+struct cma_buf_pool_s; -+typedef struct cma_buf_pool_s cma_buf_pool_t; ++ AVCodecContext *p_context = ZcFfmpeg_AllocContext( p_dec, &p_codec ); ++ if( p_context == NULL ) ++ { ++ av_buffer_unref(&hw_dev_ctx); ++ cma_vcsm_exit(vcsm_type); ++ return VLC_EGENERIC; ++ } ++ p_context->hw_device_ctx = hw_dev_ctx; ++ hw_dev_ctx = NULL; + -+cma_buf_t * cma_buf_pool_alloc_buf(cma_buf_pool_t * const p, const size_t size); -+void cma_buf_pool_cancel(cma_buf_pool_t * const cbp); -+void cma_buf_pool_uncancel(cma_buf_pool_t * const cbp); -+void cma_buf_pool_delete(cma_buf_pool_t * const p); -+int cma_buf_pool_fill(cma_buf_pool_t * const cbp, const size_t el_size); -+int cma_buf_pool_resize(cma_buf_pool_t * const cbp, -+ const unsigned int new_pool_size, const int new_flight_size); -+cma_buf_pool_t * cma_buf_pool_new(const unsigned int pool_size, const unsigned int flight_size, -+ const bool all_in_flight, const char * const name); ++ int i_val; + -+static inline void cma_buf_pool_deletez(cma_buf_pool_t ** const pp) -+{ -+ cma_buf_pool_t * const p = *pp; -+ if (p != NULL) { -+ *pp = NULL; -+ cma_buf_pool_delete(p); ++ /* Allocate the memory needed to store the decoder's structure */ ++ decoder_sys_t *p_sys = calloc( 1, sizeof(*p_sys) ); ++ if( unlikely(p_sys == NULL) ) ++ { ++ avcodec_free_context( &p_context ); ++ cma_vcsm_exit(vcsm_type); ++ return VLC_ENOMEM; + } -+} -+ -+#endif // VLC_MMAL_MMAL_CMA_H_ ---- /dev/null -+++ b/modules/hw/mmal/mmal_gl.h -@@ -0,0 +1,45 @@ -+// Trim this include list! + -+#include -+#include -+#include -+//#include -+//#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include ++ p_dec->p_sys = p_sys; ++ p_sys->p_context = p_context; ++ p_sys->p_codec = p_codec; ++ p_sys->p_dec = p_dec; ++// p_sys->p_va = NULL; ++ p_sys->cma_in_flight_max = extra_buffers; ++ p_sys->vcsm_init_type = vcsm_type; ++ p_sys->use_drm = use_drm; ++ vlc_sem_init( &p_sys->sem_mt, 0 ); + -+struct mmal_gl_converter_s; ++ /* ***** Fill p_context with init values ***** */ ++ p_context->codec_tag = ffmpeg_CodecTag( p_dec->fmt_in.i_original_fourcc ? ++ p_dec->fmt_in.i_original_fourcc : p_dec->fmt_in.i_codec ); + -+typedef struct cma_buf_s { -+ struct mmal_gl_converter_s * sys; ++ /* ***** Get configuration of ffmpeg plugin ***** */ ++ p_context->workaround_bugs = ++ var_InheritInteger( p_dec, "avcodec-workaround-bugs" ); ++ p_context->err_recognition = ++ var_InheritInteger( p_dec, "avcodec-error-resilience" ); + -+ size_t size; -+ __u32 h_dumb; -+ int fd; -+ unsigned int h_vcsm; -+ void * mapped_addr; -+ GLuint texture; -+} cma_buf_t; ++ if( var_CreateGetBool( p_dec, "grayscale" ) ) ++ p_context->flags |= AV_CODEC_FLAG_GRAY; + -+typedef struct cma_pic_sys_s { -+ cma_buf_t * cmabuf; -+} cma_pic_sys_t; ++ /* ***** Output always the frames ***** */ ++ p_context->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT; + -+static inline unsigned int -+hw_mmal_h_vcsm(const picture_t * const pic) -+{ -+ const cma_pic_sys_t *const pic_sys = (cma_pic_sys_t *)pic->p_sys; ++ i_val = var_CreateGetInteger( p_dec, "avcodec-skiploopfilter" ); ++ if( i_val >= 4 ) p_context->skip_loop_filter = AVDISCARD_ALL; ++ else if( i_val == 3 ) p_context->skip_loop_filter = AVDISCARD_NONKEY; ++ else if( i_val == 2 ) p_context->skip_loop_filter = AVDISCARD_BIDIR; ++ else if( i_val == 1 ) p_context->skip_loop_filter = AVDISCARD_NONREF; ++ else p_context->skip_loop_filter = AVDISCARD_DEFAULT; + -+ if (pic->format.i_chroma != VLC_CODEC_MMAL_GL_RGB32 || -+ pic_sys == NULL || pic_sys->cmabuf == NULL) { -+ return 0; -+ } ++ if( var_CreateGetBool( p_dec, "avcodec-fast" ) ) ++ p_context->flags2 |= AV_CODEC_FLAG2_FAST; + -+ return pic_sys->cmabuf->h_vcsm; -+} ++ /* ***** libavcodec frame skipping ***** */ ++ p_sys->b_hurry_up = var_CreateGetBool( p_dec, "avcodec-hurry-up" ); ++ p_sys->b_show_corrupted = var_CreateGetBool( p_dec, "avcodec-corrupted" ); + ---- /dev/null -+++ b/modules/hw/mmal/mmal_piccpy_neon.S -@@ -0,0 +1,105 @@ -+// Copy pix ++ i_val = var_CreateGetInteger( p_dec, "avcodec-skip-frame" ); ++ if( i_val >= 4 ) p_sys->i_skip_frame = AVDISCARD_ALL; ++ else if( i_val == 3 ) p_sys->i_skip_frame = AVDISCARD_NONKEY; ++ else if( i_val == 2 ) p_sys->i_skip_frame = AVDISCARD_BIDIR; ++ else if( i_val == 1 ) p_sys->i_skip_frame = AVDISCARD_NONREF; ++ else if( i_val == -1 ) p_sys->i_skip_frame = AVDISCARD_NONE; ++ else p_sys->i_skip_frame = AVDISCARD_DEFAULT; ++ p_context->skip_frame = p_sys->i_skip_frame; + -+ .syntax unified -+ .arm -+// .thumb -+ .text -+ .align 16 -+ .arch armv7-a -+ .fpu neon-vfpv4 ++ i_val = var_CreateGetInteger( p_dec, "avcodec-skip-idct" ); ++ if( i_val >= 4 ) p_context->skip_idct = AVDISCARD_ALL; ++ else if( i_val == 3 ) p_context->skip_idct = AVDISCARD_NONKEY; ++ else if( i_val == 2 ) p_context->skip_idct = AVDISCARD_BIDIR; ++ else if( i_val == 1 ) p_context->skip_idct = AVDISCARD_NONREF; ++ else if( i_val == -1 ) p_context->skip_idct = AVDISCARD_NONE; ++ else p_context->skip_idct = AVDISCARD_DEFAULT; + ++ /* ***** libavcodec direct rendering ***** */ ++ p_sys->b_direct_rendering = false; ++ atomic_init(&p_sys->b_dr_failure, false); ++ if( var_CreateGetBool( p_dec, "avcodec-dr" ) && ++ (p_codec->capabilities & AV_CODEC_CAP_DR1) && ++ /* No idea why ... but this fixes flickering on some TSCC streams */ ++ p_sys->p_codec->id != AV_CODEC_ID_TSCC && ++ p_sys->p_codec->id != AV_CODEC_ID_CSCD && ++ p_sys->p_codec->id != AV_CODEC_ID_CINEPAK ) ++ { ++ /* Some codecs set pix_fmt only after the 1st frame has been decoded, ++ * so we need to do another check in ffmpeg_GetFrameBuf() */ ++ p_sys->b_direct_rendering = true; ++ } + -+.macro function name -+ .global \name -+#ifdef __ELF__ -+ .type \name, %function ++ p_context->get_format = ZcGetFormat; ++#if 0 ++ p_context->get_format = ffmpeg_GetFormat; ++ /* Always use our get_buffer wrapper so we can calculate the ++ * PTS correctly */ ++ p_context->get_buffer2 = lavc_GetFrame; ++ p_context->opaque = p_dec; +#endif -+\name: -+.endm + ++ int i_thread_count = var_InheritInteger( p_dec, "avcodec-threads" ); ++ if( i_thread_count <= 0 ) ++#if 1 ++ { ++ // Pick 5 threads for everything on Pi except for HEVC where the h/w ++ // really limits the useful size to 3 ++ i_thread_count = p_codec->id == AV_CODEC_ID_HEVC ? 3 : 5; ++ } ++#else ++ { ++ i_thread_count = vlc_GetCPUCount(); ++ if( i_thread_count > 1 ) ++ i_thread_count++; + -+.macro piccpy_to_8, bit_depth -+ subs r2, #128 -+ vpush {q4-q7} -+ blt 2f -+1: -+ vldm r1!, {q0-q7} -+ subs r2, #128 -+ vqrshrn.u16 d0, q0, #\bit_depth - 8 -+ vqrshrn.u16 d1, q1, #\bit_depth - 8 -+ vqrshrn.u16 d2, q2, #\bit_depth - 8 -+ vqrshrn.u16 d3, q3, #\bit_depth - 8 -+ vldm r1!, {q8-q15} -+ vqrshrn.u16 d4, q4, #\bit_depth - 8 -+ vqrshrn.u16 d5, q5, #\bit_depth - 8 -+ vqrshrn.u16 d6, q6, #\bit_depth - 8 -+ vqrshrn.u16 d7, q7, #\bit_depth - 8 -+ vqrshrn.u16 d8, q8, #\bit_depth - 8 -+ vqrshrn.u16 d9, q9, #\bit_depth - 8 -+ vqrshrn.u16 d10, q10, #\bit_depth - 8 -+ vqrshrn.u16 d11, q11, #\bit_depth - 8 -+ vqrshrn.u16 d12, q12, #\bit_depth - 8 -+ vqrshrn.u16 d13, q13, #\bit_depth - 8 -+ vqrshrn.u16 d14, q14, #\bit_depth - 8 -+ vqrshrn.u16 d15, q15, #\bit_depth - 8 -+ vstm r0!, {q0-q7} -+ bge 1b -+2: -+ adds r2, #64 -+ blt 1f -+ -+ vldm r1!, {q0-q7} -+ vqrshrn.u16 d0, q0, #\bit_depth - 8 -+ vqrshrn.u16 d1, q1, #\bit_depth - 8 -+ vqrshrn.u16 d2, q2, #\bit_depth - 8 -+ vqrshrn.u16 d3, q3, #\bit_depth - 8 -+ vqrshrn.u16 d4, q4, #\bit_depth - 8 -+ vqrshrn.u16 d5, q5, #\bit_depth - 8 -+ vqrshrn.u16 d6, q6, #\bit_depth - 8 -+ vqrshrn.u16 d7, q7, #\bit_depth - 8 -+ vstm r0!, {q0-q3} -+1: -+ adds r2, #32 -+ blt 1f -+ -+ vldm r1!, {q0-q3} -+ vqrshrn.u16 d0, q0, #\bit_depth - 8 -+ vqrshrn.u16 d1, q1, #\bit_depth - 8 -+ vqrshrn.u16 d2, q2, #\bit_depth - 8 -+ vqrshrn.u16 d3, q3, #\bit_depth - 8 -+ vstm r0!, {q0-q1} -+1: -+ adds r2, #16 -+ blt 1f ++ //FIXME: take in count the decoding time ++#if VLC_WINSTORE_APP ++ i_thread_count = __MIN( i_thread_count, 6 ); ++#else ++ i_thread_count = __MIN( i_thread_count, p_codec->id == AV_CODEC_ID_HEVC ? 10 : 6 ); ++#endif ++ } ++ i_thread_count = __MIN( i_thread_count, p_codec->id == AV_CODEC_ID_HEVC ? 32 : 16 ); ++#endif ++ msg_Dbg( p_dec, "allowing %d thread(s) for decoding", i_thread_count ); ++ p_context->thread_count = i_thread_count; ++ p_context->thread_safe_callbacks = true; + -+ vldm r1!, {q0-q1} -+ vqrshrn.u16 d0, q0, #\bit_depth - 8 -+ vqrshrn.u16 d1, q1, #\bit_depth - 8 -+ vstm r0!, {q0} -+1: -+ adds r2, #8 -+ blt 1f ++ switch( p_codec->id ) ++ { ++ case AV_CODEC_ID_MPEG4: ++ case AV_CODEC_ID_H263: ++ p_context->thread_type = 0; ++ break; ++ case AV_CODEC_ID_MPEG1VIDEO: ++ case AV_CODEC_ID_MPEG2VIDEO: ++ p_context->thread_type &= ~FF_THREAD_SLICE; ++ /* fall through */ ++# if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 1, 0)) ++ case AV_CODEC_ID_H264: ++ case AV_CODEC_ID_VC1: ++ case AV_CODEC_ID_WMV3: ++ p_context->thread_type &= ~FF_THREAD_FRAME; ++# endif ++ default: ++ break; ++ } + -+ vldm r1!, {q0} -+ vqrshrn.u16 d0, q0, #\bit_depth - 8 -+ vstr d0, [r0] -+ add r0, #8 -+1: -+ adds r2, #4 -+ blt 1f ++ if( p_context->thread_type & FF_THREAD_FRAME ) ++ p_dec->i_extra_picture_buffers = 2 * p_context->thread_count; + -+ vldr d0, [r1] -+ vqrshrn.u16 d0, q0, #\bit_depth - 8 -+ vstr s0, [r0] -+1: -+ vpop {q4-q7} -+ bx lr -+.endm ++ /* ***** misc init ***** */ ++ date_Init(&p_sys->pts, 1, 30001); ++ date_Set(&p_sys->pts, VLC_TS_INVALID); ++ p_sys->b_first_frame = true; ++ p_sys->i_late_frames = 0; ++ p_sys->b_from_preroll = false; + ++ /* Set output properties */ ++ if (ZcGetVlcChroma(&p_dec->fmt_out.video, p_context->pix_fmt, p_context->sw_pix_fmt) != VLC_SUCCESS) ++ { ++ /* we are doomed. but not really, because most codecs set their pix_fmt later on */ ++// p_dec->fmt_out.i_codec = VLC_CODEC_I420; ++ p_dec->fmt_out.i_codec = VLC_CODEC_MMAL_ZC_I420; ++ } ++ p_dec->fmt_out.i_codec = p_dec->fmt_out.video.i_chroma; + -+@ [r0] Dest -+@ [r1] Src -+@ r2 Pels -+function mmal_piccpy_10_to_8_neon -+ piccpy_to_8 10 ++ p_dec->fmt_out.video.orientation = p_dec->fmt_in.video.orientation; + ---- a/modules/hw/mmal/mmal_picture.c -+++ b/modules/hw/mmal/mmal_picture.c -@@ -21,25 +21,1542 @@ - * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. - *****************************************************************************/ - -+// We would really like to use vlc_thread.h but the detach thread stuff can't be -+// used here :-( -+#include ++ if( p_dec->fmt_in.video.p_palette ) { ++ p_sys->palette_sent = false; ++ p_dec->fmt_out.video.p_palette = malloc( sizeof(video_palette_t) ); ++ if( p_dec->fmt_out.video.p_palette ) ++ *p_dec->fmt_out.video.p_palette = *p_dec->fmt_in.video.p_palette; ++ } else ++ p_sys->palette_sent = true; + -+#include -+#include -+#include ++ if (use_drm) ++ p_sys->cma_pool = cma_drmprime_pool_new(p_sys->cma_in_flight_max, p_sys->cma_in_flight_max, false, "drm_avcodec"); ++ else if (use_zc) ++ p_sys->cma_pool = cma_buf_pool_new(p_sys->cma_in_flight_max, p_sys->cma_in_flight_max, false, "mmal_avcodec"); + - #include -+#include - #include ++ if (p_sys->cma_pool == NULL) ++ { ++ msg_Err(p_dec, "CMA pool alloc failure"); ++ goto fail; ++ } + -+#pragma GCC diagnostic push -+#pragma GCC diagnostic ignored "-Wbad-function-cast" -+#include -+#pragma GCC diagnostic pop - #include -+#include -+#include -+#include -+#include - -+#include "mmal_cma.h" - #include "mmal_picture.h" -+#include "transform_ops.h" ++ /* ***** init this codec with special data ***** */ ++ ffmpeg_InitCodec( p_dec ); + -+#define TRACE_TRANSFORMS 0 ++ /* ***** Open the codec ***** */ ++ if( OpenVideoCodec( p_dec ) < 0 ) ++ { ++ vlc_sem_destroy( &p_sys->sem_mt ); ++ free( p_sys ); ++ avcodec_free_context( &p_context ); ++ return VLC_EGENERIC; ++ } + -+#define UINT64_SIZE(s) (((s) + sizeof(uint64_t) - 1)/sizeof(uint64_t)) ++ p_dec->pf_decode = DecodeVideo; ++ p_dec->pf_flush = Flush; + -+static inline char safe_char(const unsigned int c0) -+{ -+ const unsigned int c = c0 & 0xff; -+ return c > ' ' && c < 0x7f ? c : '.'; -+} ++ /* XXX: Writing input format makes little sense. */ ++ if( p_context->profile != FF_PROFILE_UNKNOWN ) ++ p_dec->fmt_in.i_profile = p_context->profile; ++ if( p_context->level != FF_LEVEL_UNKNOWN ) ++ p_dec->fmt_in.i_level = p_context->level; + -+const char * str_fourcc(char * const buf, const unsigned int fcc) -+{ -+ if (fcc == 0) -+ return "----"; -+ buf[0] = safe_char(fcc >> 0); -+ buf[1] = safe_char(fcc >> 8); -+ buf[2] = safe_char(fcc >> 16); -+ buf[3] = safe_char(fcc >> 24); -+ buf[4] = 0; -+ return buf; -+} ++#if 1 ++ // Most of the time we have nothing useful by way of a format here ++ // wait till we've decoded something ++#else ++ // Update output format ++ if (lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, ++ p_context->pix_fmt) != 0) ++ { ++ msg_Err(p_dec, "Unable to update format: pix_fmt=%d", p_context->pix_fmt); ++// goto fail; ++ } ++#endif + -+// WB + Inv -+static inline void flush_range(void * const start, const size_t len) -+{ -+ uint64_t buf[UINT64_SIZE(sizeof(struct vcsm_user_clean_invalid2_s) + sizeof(struct vcsm_user_clean_invalid2_block_s))]; -+ struct vcsm_user_clean_invalid2_s * const b = (struct vcsm_user_clean_invalid2_s *)buf; ++#if TRACE_ALL ++ msg_Dbg(p_dec, "<<< %s: OK", __func__); ++#endif ++ return VLC_SUCCESS; + -+ *b = (struct vcsm_user_clean_invalid2_s){ -+ .op_count = 1 -+ }; ++fail: ++ MmalAvcodecCloseDecoder(VLC_OBJECT(p_dec)); + -+ b->s[0] = (struct vcsm_user_clean_invalid2_block_s){ -+ .invalidate_mode = 3, // wb + invalidate -+ .block_count = 1, -+ .start_address = start, // Rely on clean inv to fix up align & size boundries -+ .block_size = len, -+ .inter_block_stride = 0 -+ }; ++#if TRACE_ALL ++ msg_Dbg(p_dec, "<<< %s: FAIL", __func__); ++#endif + -+ vcsm_clean_invalid2(b); ++ return VLC_EGENERIC; +} + -+MMAL_FOURCC_T vlc_to_mmal_color_space(const video_color_space_t vlc_cs) ++/***************************************************************************** ++ * Flush: ++ *****************************************************************************/ ++static void Flush( decoder_t *p_dec ) +{ -+ switch (vlc_cs) -+ { -+ case COLOR_SPACE_BT601: -+ return MMAL_COLOR_SPACE_ITUR_BT601; -+ case COLOR_SPACE_BT709: -+ return MMAL_COLOR_SPACE_ITUR_BT709; -+ default: -+ break; -+ } -+ return MMAL_COLOR_SPACE_UNKNOWN; ++ decoder_sys_t *p_sys = p_dec->p_sys; ++ AVCodecContext *p_context = p_sys->p_context; ++ ++#if TRACE_ALL ++ msg_Dbg(p_dec, "<<< %s", __func__); ++#endif ++ ++ date_Set(&p_sys->pts, VLC_TS_INVALID); /* To make sure we recover properly */ ++ p_sys->i_late_frames = 0; ++ cc_Flush( &p_sys->cc ); ++ ++ /* Abort pictures in order to unblock all avcodec workers threads waiting ++ * for a picture. This will avoid a deadlock between avcodec_flush_buffers ++ * and workers threads */ ++// It would probably be good to use AbortPicture but that often deadlocks on close ++// and given that we wait for pics in the main thread it should be unneeded (whereas ++// cma is alloced in the depths of ffmpeg on its own threads) ++// decoder_AbortPictures( p_dec, true ); ++ cma_buf_pool_cancel(p_sys->cma_pool); ++ ++ post_mt( p_sys ); ++ /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */ ++ if( avcodec_is_open( p_context ) ) ++ avcodec_flush_buffers( p_context ); ++ wait_mt( p_sys ); ++ ++ /* Reset cancel state to false */ ++ cma_buf_pool_uncancel(p_sys->cma_pool); ++// decoder_AbortPictures( p_dec, false ); ++ ++#if TRACE_ALL ++ msg_Dbg(p_dec, ">>> %s", __func__); ++#endif ++ +} + -+MMAL_FOURCC_T vlc_to_mmal_video_fourcc(const video_frame_format_t * const vf_vlc) ++static bool check_block_validity( decoder_sys_t *p_sys, block_t *block ) +{ -+ switch (vf_vlc->i_chroma) { -+ case VLC_CODEC_MMAL_ZC_RGB32: -+ case VLC_CODEC_RGB32: -+ { -+ // VLC RGB32 aka RV32 means we have to look at the mask values -+ const uint32_t r = vf_vlc->i_rmask; -+ const uint32_t g = vf_vlc->i_gmask; -+ const uint32_t b = vf_vlc->i_bmask; -+ if (r == 0xff0000 && g == 0xff00 && b == 0xff) -+ return MMAL_ENCODING_BGRA; -+ if (r == 0xff && g == 0xff00 && b == 0xff0000) -+ return MMAL_ENCODING_RGBA; -+ if (r == 0xff000000 && g == 0xff0000 && b == 0xff00) -+ return MMAL_ENCODING_ABGR; -+ if (r == 0xff00 && g == 0xff0000 && b == 0xff000000) -+ return MMAL_ENCODING_ARGB; -+ break; -+ } -+ case VLC_CODEC_RGB16: ++ if( !block) ++ return true; ++ ++ if( block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) ) ++ { ++ date_Set( &p_sys->pts, VLC_TS_INVALID ); /* To make sure we recover properly */ ++ cc_Flush( &p_sys->cc ); ++ ++ p_sys->i_late_frames = 0; ++ if( block->i_flags & BLOCK_FLAG_CORRUPTED ) + { -+ // VLC RGB16 aka RV16 means we have to look at the mask values -+ const uint32_t r = vf_vlc->i_rmask; -+ const uint32_t g = vf_vlc->i_gmask; -+ const uint32_t b = vf_vlc->i_bmask; -+ if (r == 0xf800 && g == 0x7e0 && b == 0x1f) -+ return MMAL_ENCODING_RGB16; -+ break; ++ block_Release( block ); ++ return false; + } -+ case VLC_CODEC_I420: -+ case VLC_CODEC_MMAL_ZC_I420: -+ return MMAL_ENCODING_I420; -+ case VLC_CODEC_RGBA: -+ return MMAL_ENCODING_RGBA; -+ case VLC_CODEC_BGRA: -+ return MMAL_ENCODING_BGRA; -+ case VLC_CODEC_ARGB: -+ return MMAL_ENCODING_ARGB; -+ // VLC_CODEC_ABGR does not exist in VLC -+ case VLC_CODEC_MMAL_OPAQUE: -+ return MMAL_ENCODING_OPAQUE; -+ case VLC_CODEC_MMAL_ZC_SAND8: -+ return MMAL_ENCODING_YUVUV128; -+ case VLC_CODEC_MMAL_ZC_SAND10: -+ return MMAL_ENCODING_YUVUV64_10; -+ case VLC_CODEC_MMAL_ZC_SAND30: -+ return MMAL_ENCODING_YUV10_COL; -+ default: -+ break; + } -+ return 0; ++ return true; +} + -+static void vlc_fmt_to_video_format(MMAL_VIDEO_FORMAT_T *const vf_mmal, const video_frame_format_t * const vf_vlc) ++static bool check_block_being_late( decoder_sys_t *p_sys, block_t *block, mtime_t current_time) +{ -+ const unsigned int wmask = (vf_vlc->i_chroma == VLC_CODEC_MMAL_ZC_I420 || -+ vf_vlc->i_chroma == VLC_CODEC_I420) ? 31 : 15; -+ -+ vf_mmal->width = (vf_vlc->i_width + wmask) & ~wmask; -+ vf_mmal->height = (vf_vlc->i_height + 15) & ~15; -+ vf_mmal->crop.x = vf_vlc->i_x_offset; -+ vf_mmal->crop.y = vf_vlc->i_y_offset; -+ vf_mmal->crop.width = vf_vlc->i_visible_width; -+ vf_mmal->crop.height = vf_vlc->i_visible_height; -+ if (vf_vlc->i_sar_num == 0 || vf_vlc->i_sar_den == 0) { -+ vf_mmal->par.num = 1; -+ vf_mmal->par.den = 1; -+ } else { -+ vf_mmal->par.num = vf_vlc->i_sar_num; -+ vf_mmal->par.den = vf_vlc->i_sar_den; ++ if( !block ) ++ return false; ++ if( block->i_flags & BLOCK_FLAG_PREROLL ) ++ { ++ /* Do not care about late frames when prerolling ++ * TODO avoid decoding of non reference frame ++ * (ie all B except for H264 where it depends only on nal_ref_idc) */ ++ p_sys->i_late_frames = 0; ++ p_sys->b_from_preroll = true; ++ p_sys->i_last_late_delay = INT64_MAX; + } -+ vf_mmal->frame_rate.num = vf_vlc->i_frame_rate; -+ vf_mmal->frame_rate.den = vf_vlc->i_frame_rate_base; -+ vf_mmal->color_space = vlc_to_mmal_color_space(vf_vlc->space); -+} + ++ if( p_sys->i_late_frames <= 0 ) ++ return false; + -+void hw_mmal_vlc_fmt_to_mmal_fmt(MMAL_ES_FORMAT_T *const es_fmt, const video_frame_format_t * const vf_vlc) -+{ -+ vlc_fmt_to_video_format(&es_fmt->es->video, vf_vlc); ++ if( current_time - p_sys->i_late_frames_start > (5*CLOCK_FREQ)) ++ { ++ date_Set( &p_sys->pts, VLC_TS_INVALID ); /* To make sure we recover properly */ ++ block_Release( block ); ++ p_sys->i_late_frames--; ++ return true; ++ } ++ return false; +} + -+bool hw_mmal_vlc_pic_to_mmal_fmt_update(MMAL_ES_FORMAT_T *const es_fmt, const picture_t * const pic) ++static bool check_frame_should_be_dropped( decoder_sys_t *p_sys, AVCodecContext *p_context, bool *b_need_output_picture ) +{ -+ MMAL_VIDEO_FORMAT_T vf_new_ss; -+ MMAL_VIDEO_FORMAT_T *const vf_old = &es_fmt->es->video; -+ MMAL_VIDEO_FORMAT_T *const vf_new = &vf_new_ss; -+ -+ vlc_fmt_to_video_format(vf_new, &pic->format); ++ if( p_sys->i_late_frames <= 4) ++ return false; + -+ // If we have a format that might have come from ffmpeg then rework for -+ // a better guess as to layout. All sand stuff is "special" with regards to -+ // width/height vs real layout so leave as is if that -+ if ((pic->format.i_chroma == VLC_CODEC_MMAL_ZC_I420 || -+ pic->format.i_chroma == VLC_CODEC_MMAL_ZC_RGB32) && -+ pic->p[0].i_pixel_pitch != 0) ++ *b_need_output_picture = false; ++ if( p_sys->i_late_frames < 12 ) + { -+ // Now overwrite width/height with a better guess as to actual layout info -+ vf_new->height = pic->p[0].i_lines; -+ vf_new->width = pic->p[0].i_pitch / pic->p[0].i_pixel_pitch; ++ p_context->skip_frame = ++ (p_sys->i_skip_frame <= AVDISCARD_NONREF) ? ++ AVDISCARD_NONREF : p_sys->i_skip_frame; + } -+ -+ if ( -+ vf_new->width != vf_old->width || -+ vf_new->height != vf_old->height || -+ vf_new->crop.x != vf_old->crop.x || -+ vf_new->crop.y != vf_old->crop.y || -+ vf_new->crop.width != vf_old->crop.width || -+ vf_new->crop.height != vf_old->crop.height || -+ vf_new->par.num != vf_old->par.num || -+ vf_new->par.den != vf_old->par.den || -+ // Frame rate ignored -+ vf_new->color_space != vf_old->color_space) ++ else + { -+#if 0 -+ char dbuf0[5], dbuf1[5]; -+ printf("%dx%d (%d,%d %dx%d) par:%d/%d %s -> %dx%d (%d,%d %dx%d) par:%d/%d %s\n", -+ vf_old->width , -+ vf_old->height , -+ vf_old->crop.x , -+ vf_old->crop.y , -+ vf_old->crop.width , -+ vf_old->crop.height , -+ vf_old->par.num , -+ vf_old->par.den , -+ str_fourcc(dbuf0, vf_old->color_space) , -+ vf_new->width , -+ vf_new->height , -+ vf_new->crop.x , -+ vf_new->crop.y , -+ vf_new->crop.width , -+ vf_new->crop.height , -+ vf_new->par.num , -+ vf_new->par.den , -+ str_fourcc(dbuf1, vf_new->color_space) ); -+#endif -+ *vf_old = *vf_new; ++ /* picture too late, won't decode ++ * but break picture until a new I, and for mpeg4 ...*/ ++ p_sys->i_late_frames--; /* needed else it will never be decrease */ + return true; + } + return false; +} + -+ -+hw_mmal_port_pool_ref_t * hw_mmal_port_pool_ref_create(MMAL_PORT_T * const port, -+ const unsigned int headers, const uint32_t payload_size) ++static mtime_t interpolate_next_pts( decoder_t *p_dec, AVFrame *frame ) +{ -+ hw_mmal_port_pool_ref_t * ppr = calloc(1, sizeof(hw_mmal_port_pool_ref_t)); -+ if (ppr == NULL) -+ return NULL; ++ decoder_sys_t *p_sys = p_dec->p_sys; ++ AVCodecContext *p_context = p_sys->p_context; + -+ if ((ppr->pool = mmal_port_pool_create(port, headers, payload_size)) == NULL) -+ goto fail; ++ if( date_Get( &p_sys->pts ) == VLC_TS_INVALID || ++ p_sys->pts.i_divider_num == 0 ) ++ return VLC_TS_INVALID; + -+ ppr->port = port; -+ atomic_store(&ppr->refs, 1); -+ return ppr; ++ int i_tick = p_context->ticks_per_frame; ++ if( i_tick <= 0 ) ++ i_tick = 1; + -+fail: -+ free(ppr); -+ return NULL; ++ /* interpolate the next PTS */ ++ return date_Increment( &p_sys->pts, i_tick + frame->repeat_pict ); +} + -+static void do_detached(void *(*fn)(void *), void * v) ++static void update_late_frame_count( decoder_t *p_dec, block_t *p_block, ++ mtime_t current_time, mtime_t i_pts, ++ mtime_t i_next_pts ) +{ -+ pthread_t dothread; -+ pthread_create(&dothread, NULL, fn, v); -+ pthread_detach(dothread); -+} ++ decoder_sys_t *p_sys = p_dec->p_sys; ++ /* Update frame late count (except when doing preroll) */ ++ mtime_t i_display_date = VLC_TS_INVALID; ++ if( !p_block || !(p_block->i_flags & BLOCK_FLAG_PREROLL) ) ++ i_display_date = decoder_GetDisplayDate( p_dec, i_pts ); + -+// Destroy a ppr - aranged s.t. it has the correct prototype for a pthread -+static void * kill_ppr(void * v) -+{ -+ hw_mmal_port_pool_ref_t * const ppr = v; -+ if (ppr->port->is_enabled) -+ mmal_port_disable(ppr->port); // Avoid annoyed messages from MMAL when we kill the pool -+ mmal_port_pool_destroy(ppr->port, ppr->pool); -+ free(ppr); -+ return NULL; -+} ++ mtime_t i_threshold = i_next_pts != VLC_TS_INVALID ? (i_next_pts - i_pts) / 2 : 20000; + -+void hw_mmal_port_pool_ref_release(hw_mmal_port_pool_ref_t * const ppr, const bool in_cb) -+{ -+ if (ppr == NULL) -+ return; -+ if (atomic_fetch_sub(&ppr->refs, 1) != 1) -+ return; -+ if (in_cb) -+ do_detached(kill_ppr, ppr); -+ else -+ kill_ppr(ppr); -+} ++ if( i_display_date > VLC_TS_INVALID && i_display_date + i_threshold <= current_time ) ++ { ++ /* Out of preroll, consider only late frames on rising delay */ ++ if( p_sys->b_from_preroll ) ++ { ++ if( p_sys->i_last_late_delay > current_time - i_display_date ) ++ { ++ p_sys->i_last_late_delay = current_time - i_display_date; ++ return; ++ } ++ p_sys->b_from_preroll = false; ++ } + -+// Put buffer in port if possible - if not then release to pool -+// Returns true if sent, false if recycled -+bool hw_mmal_port_pool_ref_recycle(hw_mmal_port_pool_ref_t * const ppr, MMAL_BUFFER_HEADER_T * const buf) -+{ -+ mmal_buffer_header_reset(buf); -+ buf->user_data = NULL; ++ p_sys->i_late_frames++; ++ if( p_sys->i_late_frames == 1 ) ++ p_sys->i_late_frames_start = current_time; + -+ if (mmal_port_send_buffer(ppr->port, buf) == MMAL_SUCCESS) -+ return true; -+ mmal_buffer_header_release(buf); -+ return false; ++ } ++ else ++ { ++ p_sys->i_late_frames = 0; ++ } +} + -+MMAL_STATUS_T hw_mmal_port_pool_ref_fill(hw_mmal_port_pool_ref_t * const ppr) ++ ++static int DecodeSidedata( decoder_t *p_dec, const AVFrame *frame, picture_t *p_pic ) +{ -+ MMAL_BUFFER_HEADER_T * buf; -+ MMAL_STATUS_T err = MMAL_SUCCESS; ++ decoder_sys_t *p_sys = p_dec->p_sys; ++ bool format_changed = false; + -+ while ((buf = mmal_queue_get(ppr->pool->queue)) != NULL) { -+ if ((err = mmal_port_send_buffer(ppr->port, buf)) != MMAL_SUCCESS) ++#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 16, 101 ) ) ++#define FROM_AVRAT(default_factor, avrat) \ ++(uint64_t)(default_factor) * (avrat).num / (avrat).den ++ const AVFrameSideData *metadata = ++ av_frame_get_side_data( frame, ++ AV_FRAME_DATA_MASTERING_DISPLAY_METADATA ); ++ if ( metadata ) ++ { ++ const AVMasteringDisplayMetadata *hdr_meta = ++ (const AVMasteringDisplayMetadata *) metadata->data; ++ if ( hdr_meta->has_luminance ) + { -+ mmal_queue_put_back(ppr->pool->queue, buf); -+ break; ++#define ST2086_LUMA_FACTOR 10000 ++ p_pic->format.mastering.max_luminance = ++ FROM_AVRAT(ST2086_LUMA_FACTOR, hdr_meta->max_luminance); ++ p_pic->format.mastering.min_luminance = ++ FROM_AVRAT(ST2086_LUMA_FACTOR, hdr_meta->min_luminance); ++ } ++ if ( hdr_meta->has_primaries ) ++ { ++#define ST2086_RED 2 ++#define ST2086_GREEN 0 ++#define ST2086_BLUE 1 ++#define LAV_RED 0 ++#define LAV_GREEN 1 ++#define LAV_BLUE 2 ++#define ST2086_PRIM_FACTOR 50000 ++ p_pic->format.mastering.primaries[ST2086_RED*2 + 0] = ++ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_RED][0]); ++ p_pic->format.mastering.primaries[ST2086_RED*2 + 1] = ++ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_RED][1]); ++ p_pic->format.mastering.primaries[ST2086_GREEN*2 + 0] = ++ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_GREEN][0]); ++ p_pic->format.mastering.primaries[ST2086_GREEN*2 + 1] = ++ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_GREEN][1]); ++ p_pic->format.mastering.primaries[ST2086_BLUE*2 + 0] = ++ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_BLUE][0]); ++ p_pic->format.mastering.primaries[ST2086_BLUE*2 + 1] = ++ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_BLUE][1]); ++ p_pic->format.mastering.white_point[0] = ++ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->white_point[0]); ++ p_pic->format.mastering.white_point[1] = ++ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->white_point[1]); + } -+ } -+ return err; -+} -+ -+ -+MMAL_STATUS_T hw_mmal_opaque_output(vlc_object_t * const obj, -+ hw_mmal_port_pool_ref_t ** pppr, -+ MMAL_PORT_T * const port, -+ const unsigned int extra_buffers, MMAL_PORT_BH_CB_T callback) -+{ -+ MMAL_STATUS_T status; -+ -+ port->userdata = (struct MMAL_PORT_USERDATA_T *)obj; + -+ status = port_parameter_set_uint32(port, MMAL_PARAMETER_EXTRA_BUFFERS, extra_buffers); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(obj, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", -+ status, mmal_status_to_string(status)); -+ return status; ++ if ( memcmp( &p_dec->fmt_out.video.mastering, ++ &p_pic->format.mastering, ++ sizeof(p_pic->format.mastering) ) ) ++ { ++ p_dec->fmt_out.video.mastering = p_pic->format.mastering; ++ format_changed = true; ++ } ++#undef FROM_AVRAT + } -+ -+ status = port_parameter_set_bool(port, MMAL_PARAMETER_ZERO_COPY, 1); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(obj, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", -+ port->name, status, mmal_status_to_string(status)); -+ return status; ++#endif ++#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 60, 100 ) ) ++ const AVFrameSideData *metadata_lt = ++ av_frame_get_side_data( frame, ++ AV_FRAME_DATA_CONTENT_LIGHT_LEVEL ); ++ if ( metadata_lt ) ++ { ++ const AVContentLightMetadata *light_meta = ++ (const AVContentLightMetadata *) metadata_lt->data; ++ p_pic->format.lighting.MaxCLL = light_meta->MaxCLL; ++ p_pic->format.lighting.MaxFALL = light_meta->MaxFALL; ++ if ( memcmp( &p_dec->fmt_out.video.lighting, ++ &p_pic->format.lighting, ++ sizeof(p_pic->format.lighting) ) ) ++ { ++ p_dec->fmt_out.video.lighting = p_pic->format.lighting; ++ format_changed = true; ++ } + } ++#endif + -+ port->format->encoding = MMAL_ENCODING_OPAQUE; -+ port->format->encoding_variant = 0; -+ if ((status = mmal_port_format_commit(port)) != MMAL_SUCCESS) ++ if (format_changed && decoder_UpdateVideoFormat( p_dec )) ++ return -1; ++ ++ const AVFrameSideData *p_avcc = av_frame_get_side_data( frame, AV_FRAME_DATA_A53_CC ); ++ if( p_avcc ) + { -+ msg_Err(obj, "Failed to commit format on port %s (status=%"PRIx32" %s)", -+ port->name, status, mmal_status_to_string(status)); -+ return status; ++ cc_Extract( &p_sys->cc, CC_PAYLOAD_RAW, true, p_avcc->data, p_avcc->size ); ++ if( p_sys->cc.b_reorder || p_sys->cc.i_data ) ++ { ++ block_t *p_cc = block_Alloc( p_sys->cc.i_data ); ++ if( p_cc ) ++ { ++ memcpy( p_cc->p_buffer, p_sys->cc.p_data, p_sys->cc.i_data ); ++ if( p_sys->cc.b_reorder ) ++ p_cc->i_dts = p_cc->i_pts = p_pic->date; ++ else ++ p_cc->i_pts = p_cc->i_dts; ++ decoder_cc_desc_t desc; ++ desc.i_608_channels = p_sys->cc.i_608channels; ++ desc.i_708_channels = p_sys->cc.i_708channels; ++ desc.i_reorder_depth = 4; ++ decoder_QueueCc( p_dec, p_cc, &desc ); ++ } ++ cc_Flush( &p_sys->cc ); ++ } + } ++ return 0; ++} + -+ port->buffer_num = 30; -+ port->buffer_size = port->buffer_size_recommended; ++/***************************************************************************** ++ * DecodeBlock: Called to decode one or more frames ++ *****************************************************************************/ + -+ if ((*pppr = hw_mmal_port_pool_ref_create(port, port->buffer_num, port->buffer_size)) == NULL) { -+ msg_Err(obj, "Failed to create output pool"); -+ return status; -+ } ++static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error ) ++{ ++ decoder_sys_t *p_sys = p_dec->p_sys; ++ AVCodecContext *p_context = p_sys->p_context; ++ /* Boolean if we assume that we should get valid pic as result */ ++ bool b_need_output_picture = true; + -+ status = mmal_port_enable(port, callback); -+ if (status != MMAL_SUCCESS) { -+ hw_mmal_port_pool_ref_release(*pppr, false); -+ *pppr = NULL; -+ msg_Err(obj, "Failed to enable output port %s (status=%"PRIx32" %s)", -+ port->name, status, mmal_status_to_string(status)); -+ return status; -+ } ++ /* Boolean for END_OF_SEQUENCE */ ++ bool eos_spotted = false; + -+ return MMAL_SUCCESS; -+} ++#if TRACE_ALL ++ msg_Dbg(p_dec, "<<< %s: (buf_size=%d)", __func__, pp_block == NULL || *pp_block == NULL ? 0 : (*pp_block)->i_buffer); ++#endif + ++ block_t *p_block; ++ mtime_t current_time; ++ picture_t *p_pic = NULL; ++ AVFrame *frame = NULL; ++ cma_buf_t * cb = NULL; + -+void hw_mmal_pic_ctx_destroy(picture_context_t * pic_ctx_cmn) -+{ -+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic_ctx_cmn; -+ unsigned int i; ++ // By default we are OK ++ *error = false; + -+ for (i = 0; i != ctx->buf_count; ++i) { -+ if (ctx->bufs[i] != NULL) -+ mmal_buffer_header_release(ctx->bufs[i]); ++ if( !p_context->extradata_size && p_dec->fmt_in.i_extra ) ++ { ++ ffmpeg_InitCodec( p_dec ); ++ if( !avcodec_is_open( p_context ) ) ++ OpenVideoCodec( p_dec ); + } + -+ cma_buf_end_flight(ctx->cb); -+ cma_buf_unref(ctx->cb); ++ p_block = pp_block ? *pp_block : NULL; ++ if(!p_block && !(p_sys->p_codec->capabilities & AV_CODEC_CAP_DELAY) ) ++ return NULL; + -+ free(ctx); -+} ++ if( !avcodec_is_open( p_context ) ) ++ { ++ if( p_block ) ++ block_Release( p_block ); ++ return NULL; ++ } + -+picture_context_t * hw_mmal_pic_ctx_copy(picture_context_t * pic_ctx_cmn) -+{ -+ const pic_ctx_mmal_t * const src_ctx = (pic_ctx_mmal_t *)pic_ctx_cmn; -+ pic_ctx_mmal_t * const dst_ctx = calloc(1, sizeof(*dst_ctx)); -+ unsigned int i; ++ if( !check_block_validity( p_sys, p_block ) ) ++ return NULL; + -+ if (dst_ctx == NULL) ++ current_time = mdate(); ++ if( p_dec->b_frame_drop_allowed && check_block_being_late( p_sys, p_block, current_time) ) ++ { ++ msg_Err( p_dec, "more than 5 seconds of late video -> " ++ "dropping frame (computer too slow ?)" ); + return NULL; ++ } + -+ // Copy -+ dst_ctx->cmn = src_ctx->cmn; + -+ dst_ctx->cb = cma_buf_ref(src_ctx->cb); ++ /* A good idea could be to decode all I pictures and see for the other */ + -+ dst_ctx->buf_count = src_ctx->buf_count; -+ for (i = 0; i != src_ctx->buf_count; ++i) { -+ dst_ctx->bufs[i] = src_ctx->bufs[i]; -+ if (dst_ctx->bufs[i] != NULL) -+ mmal_buffer_header_acquire(dst_ctx->bufs[i]); ++ /* Defaults that if we aren't in prerolling, we want output picture ++ same for if we are flushing (p_block==NULL) */ ++ if( !p_block || !(p_block->i_flags & BLOCK_FLAG_PREROLL) ) ++ b_need_output_picture = true; ++ else ++ b_need_output_picture = false; ++ ++ /* Change skip_frame config only if hurry_up is enabled */ ++ if( p_sys->b_hurry_up ) ++ { ++ p_context->skip_frame = p_sys->i_skip_frame; ++ ++ /* Check also if we should/can drop the block and move to next block ++ as trying to catchup the speed*/ ++ if( p_dec->b_frame_drop_allowed && ++ check_frame_should_be_dropped( p_sys, p_context, &b_need_output_picture ) ) ++ { ++ if( p_block ) ++ block_Release( p_block ); ++ msg_Warn( p_dec, "More than 11 late frames, dropping frame" ); ++ return NULL; ++ } ++ } ++ if( !b_need_output_picture ) ++ { ++ p_context->skip_frame = __MAX( p_context->skip_frame, ++ AVDISCARD_NONREF ); + } + -+ return &dst_ctx->cmn; -+} ++ /* ++ * Do the actual decoding now */ + -+static MMAL_BOOL_T -+buf_pre_release_cb(MMAL_BUFFER_HEADER_T * buf, void *userdata) -+{ -+ hw_mmal_port_pool_ref_t * const ppr = userdata; ++ /* Don't forget that libavcodec requires a little more bytes ++ * that the real frame size */ ++ if( p_block && p_block->i_buffer > 0 ) ++ { ++ eos_spotted = ( p_block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE ) != 0; + -+ // Kill the callback - otherwise we will go in circles! -+ mmal_buffer_header_pre_release_cb_set(buf, (MMAL_BH_PRE_RELEASE_CB_T)0, NULL); -+ mmal_buffer_header_acquire(buf); // Ref it again ++ p_block = block_Realloc( p_block, 0, ++ p_block->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE ); ++ if( !p_block ) ++ return NULL; ++ p_block->i_buffer -= FF_INPUT_BUFFER_PADDING_SIZE; ++ *pp_block = p_block; ++ memset( p_block->p_buffer + p_block->i_buffer, 0, ++ FF_INPUT_BUFFER_PADDING_SIZE ); ++ } + -+ // As we have re-acquired the buffer we need a full release -+ // (not continue) to zap the ref count back to zero -+ // This is "safe" 'cos we have already reset the cb -+ hw_mmal_port_pool_ref_recycle(ppr, buf); -+ hw_mmal_port_pool_ref_release(ppr, true); // Assume in callback ++ while( !p_block || p_block->i_buffer > 0 || eos_spotted ) ++ { ++ int i_used; ++ AVPacket pkt; + -+ return MMAL_TRUE; -+} ++ post_mt( p_sys ); + -+// Buffer belongs to context on successful return from this fn -+// is still valid on failure -+picture_context_t * -+hw_mmal_gen_context(MMAL_BUFFER_HEADER_T * buf, hw_mmal_port_pool_ref_t * const ppr) -+{ -+ pic_ctx_mmal_t * const ctx = calloc(1, sizeof(pic_ctx_mmal_t)); ++ av_init_packet( &pkt ); ++ if( p_block && p_block->i_buffer > 0 ) ++ { ++ pkt.data = p_block->p_buffer; ++ pkt.size = p_block->i_buffer; ++ pkt.pts = p_block->i_pts > VLC_TS_INVALID ? p_block->i_pts : AV_NOPTS_VALUE; ++ pkt.dts = p_block->i_dts > VLC_TS_INVALID ? p_block->i_dts : AV_NOPTS_VALUE; ++ } ++ else ++ { ++ /* Return delayed frames if codec has CODEC_CAP_DELAY */ ++ pkt.data = NULL; ++ pkt.size = 0; ++ } + -+ if (ctx == NULL) -+ return NULL; ++ if( !p_sys->palette_sent ) ++ { ++ uint8_t *pal = av_packet_new_side_data(&pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE); ++ if (pal) { ++ memcpy(pal, p_dec->fmt_in.video.p_palette->palette, AVPALETTE_SIZE); ++ p_sys->palette_sent = true; ++ } ++ } + -+ // If we have an associated ppr then ref & set appropriate callbacks -+ if (ppr != NULL) { -+ hw_mmal_port_pool_ref_acquire(ppr); -+ mmal_buffer_header_pre_release_cb_set(buf, buf_pre_release_cb, ppr); -+ buf->user_data = NULL; -+ } ++ /* Make sure we don't reuse the same timestamps twice */ ++ if( p_block ) ++ { ++ p_block->i_pts = ++ p_block->i_dts = VLC_TS_INVALID; ++ } + -+ ctx->cmn.copy = hw_mmal_pic_ctx_copy; -+ ctx->cmn.destroy = hw_mmal_pic_ctx_destroy; ++ int ret = avcodec_send_packet(p_context, &pkt); ++ if( ret != 0 && ret != AVERROR(EAGAIN) ) ++ { ++ if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL)) ++ { ++ msg_Err(p_dec, "avcodec_send_packet critical error"); ++ *error = true; ++ } ++ av_packet_unref( &pkt ); ++ break; ++ } ++ i_used = ret != AVERROR(EAGAIN) ? pkt.size : 0; ++ av_packet_unref( &pkt ); + -+ ctx->buf_count = 1; -+ ctx->bufs[0] = buf; ++ frame = av_frame_alloc(); ++ if (unlikely(frame == NULL)) ++ { ++ *error = true; ++ break; ++ } + -+ return &ctx->cmn; -+} ++ ret = avcodec_receive_frame(p_context, frame); ++ if( ret != 0 && ret != AVERROR(EAGAIN) ) ++ { ++ msg_Dbg(p_dec, "No receive"); ++ if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL)) ++ { ++ msg_Err(p_dec, "avcodec_receive_frame critical error"); ++ *error = true; ++ } ++ av_frame_free(&frame); ++ /* After draining, we need to reset decoder with a flush */ ++ if( ret == AVERROR_EOF ) ++ avcodec_flush_buffers( p_sys->p_context ); ++ break; ++ } ++ bool not_received_frame = ret; + -+// n is els -+// * Make NEON! -+typedef void piccpy_fn(void * dest, const void * src, size_t n); ++ wait_mt( p_sys ); + -+extern piccpy_fn mmal_piccpy_10_to_8_neon; ++ if( eos_spotted ) ++ p_sys->b_first_frame = true; + -+static void piccpy_10_to_8_c(void * dest, const void * src, size_t n) -+{ -+ uint8_t * d = dest; -+ const uint16_t * s = src; -+ while (n-- != 0) -+ *d++ = *s++ >> 2; -+} ++ if( p_block ) ++ { ++ if( p_block->i_buffer <= 0 ) ++ eos_spotted = false; + -+// Do a stride converting copy - if the strides are the same and line_len is -+// close then do a single block copy - we don't expect to have to preserve -+// pixels in the output frame -+static void mem_copy_2d(uint8_t * d_ptr, const size_t d_stride, -+ const uint8_t * s_ptr, const size_t s_stride, -+ size_t lines, const size_t line_len) -+{ -+ if (s_stride == d_stride && d_stride < line_len + 32) -+ { -+ memcpy(d_ptr, s_ptr, d_stride * lines); -+ } -+ else -+ { -+ while (lines-- != 0) { -+ memcpy(d_ptr, s_ptr, line_len); -+ d_ptr += d_stride; -+ s_ptr += s_stride; ++ /* Consumed bytes */ ++ p_block->p_buffer += i_used; ++ p_block->i_buffer -= i_used; + } -+ } -+} + -+// line_len in D units -+static void mem_copy_2d_10_to_8(uint8_t * d_ptr, const size_t d_stride, -+ const uint8_t * s_ptr, const size_t s_stride, -+ size_t lines, const size_t line_len) -+{ -+ piccpy_fn * const docpy = vlc_CPU_ARM_NEON() ? mmal_piccpy_10_to_8_neon : piccpy_10_to_8_c; -+ if (s_stride == d_stride * 2 && d_stride < line_len + 32) -+ { -+ docpy(d_ptr, s_ptr, d_stride * lines); -+ } -+ else -+ { -+ while (lines-- != 0) { -+ docpy(d_ptr, s_ptr, line_len); -+ d_ptr += d_stride; -+ s_ptr += s_stride; ++ /* Nothing to display */ ++ if( not_received_frame ) ++ { ++// msg_Dbg(p_dec, "No rx: used=%d", i_used); ++ av_frame_free(&frame); ++ if( i_used == 0 ) break; ++ continue; + } -+ } -+} + ++ /* Compute the PTS */ ++#ifdef FF_API_PKT_PTS ++ mtime_t i_pts = frame->pts; ++#else ++ mtime_t i_pts = frame->pkt_pts; ++#endif ++ if (i_pts == AV_NOPTS_VALUE ) ++ i_pts = frame->pkt_dts; + -+int hw_mmal_copy_pic_to_buf(void * const buf_data, -+ uint32_t * const pLength, -+ const MMAL_ES_FORMAT_T * const fmt, -+ const picture_t * const pic) -+{ -+ const MMAL_VIDEO_FORMAT_T *const video = &fmt->es->video; -+ uint8_t * const dest = buf_data; -+ size_t length = 0; ++ if( i_pts == AV_NOPTS_VALUE ) ++ i_pts = date_Get( &p_sys->pts ); + -+ //**** Worry about x/y_offsets ++ /* Interpolate the next PTS */ ++ if( i_pts > VLC_TS_INVALID ) ++ date_Set( &p_sys->pts, i_pts ); + -+ assert(fmt->encoding == MMAL_ENCODING_I420); ++ const mtime_t i_next_pts = interpolate_next_pts(p_dec, frame); + -+ switch (pic->format.i_chroma) { -+ case VLC_CODEC_I420: ++ update_late_frame_count( p_dec, p_block, current_time, i_pts, i_next_pts); ++ ++ if( !b_need_output_picture || ++// ( !p_sys->p_va && !frame->linesize[0] ) || ++ (frame->format != AV_PIX_FMT_DRM_PRIME && !frame->linesize[0] ) || ++ ( p_dec->b_frame_drop_allowed && (frame->flags & AV_FRAME_FLAG_CORRUPT) && ++ !p_sys->b_show_corrupted ) ) + { -+ const size_t y_size = video->width * video->height; -+ mem_copy_2d(dest, video->width, -+ pic->p[0].p_pixels, pic->p[0].i_pitch, -+ video->crop.height, -+ video->crop.width); ++ av_frame_free(&frame); ++// msg_Dbg(p_dec, "Bad frame"); ++ continue; ++ } + -+ mem_copy_2d(dest + y_size, video->width / 2, -+ pic->p[1].p_pixels, pic->p[1].i_pitch, -+ video->crop.height / 2, -+ video->crop.width / 2); ++ if( p_context->pix_fmt == AV_PIX_FMT_PAL8 ++ && !p_dec->fmt_out.video.p_palette ) ++ { ++ /* See AV_PIX_FMT_PAL8 comment in avc_GetVideoFormat(): update the ++ * fmt_out palette and change the fmt_out chroma to request a new ++ * vout */ ++ assert( p_dec->fmt_out.video.i_chroma != VLC_CODEC_RGBP ); + -+ mem_copy_2d(dest + y_size + y_size / 4, video->width / 2, -+ pic->p[2].p_pixels, pic->p[2].i_pitch, -+ video->crop.height / 2, -+ video->crop.width / 2); ++ video_palette_t *p_palette; ++ p_palette = p_dec->fmt_out.video.p_palette ++ = malloc( sizeof(video_palette_t) ); ++ if( !p_palette ) ++ { ++ *error = true; ++ av_frame_free(&frame); ++ break; ++ } ++ static_assert( sizeof(p_palette->palette) == AVPALETTE_SIZE, ++ "Palette size mismatch between vlc and libavutil" ); ++ assert( frame->data[1] != NULL ); ++ memcpy( p_palette->palette, frame->data[1], AVPALETTE_SIZE ); ++ p_palette->i_entries = AVPALETTE_COUNT; ++ p_dec->fmt_out.video.i_chroma = VLC_CODEC_RGBP; ++ if( decoder_UpdateVideoFormat( p_dec ) ) ++ { ++ av_frame_free(&frame); ++ continue; ++ } ++ } + -+ // And make sure it is actually in memory -+ length = y_size + y_size / 2; -+ break; ++#if 1 ++ if (lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, ++ p_context->sw_pix_fmt) != 0) ++ { ++ msg_Err(p_dec, "Failed to update format"); ++ goto fail; + } + -+ case VLC_CODEC_I420_10L: ++ if ((p_pic = decoder_NewPicture(p_dec)) == NULL) + { -+ const size_t y_size = video->width * video->height; -+ mem_copy_2d_10_to_8(dest, video->width, -+ pic->p[0].p_pixels, pic->p[0].i_pitch, -+ video->crop.height, -+ video->crop.width); ++ msg_Err(p_dec, "Failed to allocate pic"); ++ goto fail; ++ } + -+ mem_copy_2d_10_to_8(dest + y_size, video->width / 2, -+ pic->p[1].p_pixels, pic->p[1].i_pitch, -+ video->crop.height / 2, -+ video->crop.width / 2); ++ if (p_sys->use_drm) ++ { ++ cb = cma_drmprime_pool_alloc_buf(p_sys->cma_pool, frame); ++ if (cb == NULL) ++ { ++ msg_Err(p_dec, "Failed to alloc CMA buf from DRM_PRIME"); ++ goto fail; ++ } ++ } ++ else ++ { ++ cb = cma_buf_ref(av_rpi_zc_buf_v(frame->buf[0])); ++ if (cb == NULL) ++ { ++ msg_Err(p_dec, "Frame has no attached CMA buffer"); ++ goto fail; ++ } ++ } + -+ mem_copy_2d_10_to_8(dest + y_size + y_size / 4, video->width / 2, -+ pic->p[2].p_pixels, pic->p[2].i_pitch, -+ video->crop.height / 2, -+ video->crop.width / 2); ++ if (cma_buf_pic_attach(cb, p_pic) != 0) ++ { ++ cma_buf_unref(cb); // Undo the in_flight ++ char dbuf0[5]; ++ msg_Err(p_dec, "Failed to attach bufs to pic: fmt=%s", str_fourcc(dbuf0, p_pic->format.i_chroma)); ++ goto fail; ++ } ++ cb = NULL; // Now attached to pic + -+ // And make sure it is actually in memory -+ length = y_size + y_size / 2; -+ break; ++ // ****** Set planes etc. ++ set_pic_from_frame(p_pic, frame); ++#else ++ picture_t *p_pic = frame->opaque; ++ if( p_pic == NULL ) ++ { /* When direct rendering is not used, get_format() and get_buffer() ++ * might not be called. The output video format must be set here ++ * then picture buffer can be allocated. */ ++ if (p_sys->p_va == NULL ++ && lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, ++ p_context->pix_fmt) == 0) ++ p_pic = decoder_NewPicture(p_dec); ++ ++ if( !p_pic ) ++ { ++ av_frame_free(&frame); ++ break; ++ } ++ ++ /* Fill picture_t from AVFrame */ ++ if( lavc_CopyPicture( p_dec, p_pic, frame ) != VLC_SUCCESS ) ++ { ++ av_frame_free(&frame); ++ picture_Release( p_pic ); ++ break; ++ } ++ } ++ else ++ { ++ /* Some codecs can return the same frame multiple times. By the ++ * time that the same frame is returned a second time, it will be ++ * too late to clone the underlying picture. So clone proactively. ++ * A single picture CANNOT be queued multiple times. ++ */ ++ p_pic = picture_Clone( p_pic ); ++ if( unlikely(p_pic == NULL) ) ++ { ++ av_frame_free(&frame); ++ break; ++ } + } ++#endif + -+ default: -+ if (pLength != NULL) -+ *pLength = 0; -+ return VLC_EBADVAR; -+ } ++ if( !p_dec->fmt_in.video.i_sar_num || !p_dec->fmt_in.video.i_sar_den ) ++ { ++ /* Fetch again the aspect ratio in case it changed */ ++ p_dec->fmt_out.video.i_sar_num ++ = p_context->sample_aspect_ratio.num; ++ p_dec->fmt_out.video.i_sar_den ++ = p_context->sample_aspect_ratio.den; + -+ if (cma_vcsm_type() == VCSM_INIT_LEGACY) { // ** CMA is currently always uncached -+ flush_range(dest, length); -+ } ++ if( !p_dec->fmt_out.video.i_sar_num || !p_dec->fmt_out.video.i_sar_den ) ++ { ++ p_dec->fmt_out.video.i_sar_num = 1; ++ p_dec->fmt_out.video.i_sar_den = 1; ++ } ++ } + -+ if (pLength != NULL) -+ *pLength = (uint32_t)length; ++ p_pic->date = i_pts; ++ /* Hack to force display of still pictures */ ++ p_pic->b_force = p_sys->b_first_frame; ++ p_pic->i_nb_fields = 2 + frame->repeat_pict; ++ p_pic->b_progressive = !frame->interlaced_frame; ++ p_pic->b_top_field_first = frame->top_field_first; + -+ return VLC_SUCCESS; -+} ++ if (DecodeSidedata(p_dec, frame, p_pic)) ++ i_pts = VLC_TS_INVALID; + ++ av_frame_free(&frame); + -+static MMAL_BOOL_T rep_buf_free_cb(MMAL_BUFFER_HEADER_T *header, void *userdata) -+{ -+ cma_buf_t * const cb = userdata; -+ VLC_UNUSED(header); ++ /* Send decoded frame to vout */ ++ if (i_pts > VLC_TS_INVALID) ++ { ++ p_sys->b_first_frame = false; ++#if TRACE_ALL ++ msg_Dbg(p_dec, ">>> %s: Got pic", __func__); ++#endif ++ return p_pic; ++ } ++ else ++ picture_Release( p_pic ); ++ } + -+ cma_buf_unref(cb); -+ return MMAL_FALSE; ++ if( p_block ) ++ block_Release( p_block ); ++ ++#if TRACE_ALL ++ msg_Dbg(p_dec, ">>> %s: NULL", __func__); ++#endif ++ return NULL; ++ ++fail: ++#if TRACE_ALL ++ msg_Dbg(p_dec, ">>> %s: FAIL", __func__); ++#endif ++ av_frame_free(&frame); ++ if (p_pic != NULL) ++ picture_Release(p_pic); ++ if (p_block != NULL) ++ block_Release(p_block); ++ *error = true; ++ return NULL; +} + -+static int cma_buf_buf_attach(MMAL_BUFFER_HEADER_T * const buf, cma_buf_t * const cb) ++static int DecodeVideo( decoder_t *p_dec, block_t *p_block ) +{ -+ // Just a CMA buffer - fill in new buffer -+ const uintptr_t vc_h = cma_buf_vc_handle(cb); -+ if (vc_h == 0) -+ return VLC_EGENERIC; -+ -+ mmal_buffer_header_reset(buf); -+ buf->data = (uint8_t *)vc_h; -+ buf->alloc_size = cma_buf_size(cb); -+ buf->length = buf->alloc_size; -+ // Ensure cb remains valid for the duration of this buffer -+ mmal_buffer_header_pre_release_cb_set(buf, rep_buf_free_cb, cma_buf_ref(cb)); -+ return VLC_SUCCESS; ++ block_t **pp_block = p_block ? &p_block : NULL; ++ picture_t *p_pic; ++ bool error = false; ++ while( ( p_pic = DecodeBlock( p_dec, pp_block, &error ) ) != NULL ) ++ decoder_QueueVideo( p_dec, p_pic ); ++ return VLCDEC_SUCCESS; ++// Easiest to just ignore all errors - returning a real error seems to ++// kill output forever ++// return error ? VLCDEC_ECRITICAL : VLCDEC_SUCCESS; +} + -+MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_copied(const picture_t *const pic, -+ MMAL_POOL_T * const rep_pool, -+ MMAL_PORT_T * const port, -+ cma_buf_pool_t * const cbp) ++/***************************************************************************** ++ * EndVideo: decoder destruction ++ ***************************************************************************** ++ * This function is called when the thread ends after a successful ++ * initialization. ++ *****************************************************************************/ ++static void MmalAvcodecCloseDecoder( vlc_object_t *obj ) +{ -+ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(rep_pool->queue); -+ if (buf == NULL) -+ goto fail0; ++ decoder_t *p_dec = (decoder_t *)obj; ++ decoder_sys_t *p_sys = p_dec->p_sys; ++ AVCodecContext *ctx = p_sys->p_context; ++// void *hwaccel_context; + -+ cma_buf_t * const cb = cma_buf_pool_alloc_buf(cbp, port->buffer_size); -+ if (cb == NULL) -+ goto fail1; ++ msg_Dbg(obj, "<<< %s", __func__); + -+ if (cma_buf_buf_attach(buf, cb) != VLC_SUCCESS) -+ goto fail2; ++ post_mt( p_sys ); + -+ pic_to_buf_copy_props(buf, pic); ++ cma_buf_pool_cancel(p_sys->cma_pool); // Abort any pending frame allocs + -+ if (hw_mmal_copy_pic_to_buf(cma_buf_addr(cb), &buf->length, port->format, pic) != VLC_SUCCESS) -+ goto fail2; -+ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END; ++ /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */ ++ if( avcodec_is_open( ctx ) ) ++ avcodec_flush_buffers( ctx ); + -+ cma_buf_unref(cb); -+ return buf; ++ if (!p_sys->use_drm) ++ av_rpi_zc_uninit2(ctx); + -+fail2: -+ cma_buf_unref(cb); -+fail1: -+ mmal_buffer_header_release(buf); -+fail0: -+ return NULL; -+} ++ wait_mt( p_sys ); + -+MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_replicated(const picture_t *const pic, MMAL_POOL_T * const rep_pool) -+{ -+ pic_ctx_mmal_t *const ctx = (pic_ctx_mmal_t *)pic->context; -+ MMAL_BUFFER_HEADER_T *const rep_buf = mmal_queue_wait(rep_pool->queue); ++ cc_Flush( &p_sys->cc ); + -+ if (rep_buf == NULL) -+ return NULL; ++// hwaccel_context = ctx->hwaccel_context; ++ avcodec_free_context( &ctx ); + -+ if (ctx->bufs[0] != NULL) -+ { -+ // Existing buffer - replicate it -+ if (mmal_buffer_header_replicate(rep_buf, ctx->bufs[0]) != MMAL_SUCCESS) -+ goto fail; -+ } -+ else if (ctx->cb != NULL) -+ { -+ // Just a CMA buffer - fill in new buffer -+ if (cma_buf_buf_attach(rep_buf, ctx->cb) != 0) -+ goto fail; -+ } -+ else -+ goto fail; ++// if( p_sys->p_va ) ++// vlc_va_Delete( p_sys->p_va, &hwaccel_context ); + -+ pic_to_buf_copy_props(rep_buf, pic); -+ return rep_buf; ++ cma_vcsm_exit(p_sys->vcsm_init_type); + -+fail: -+ mmal_buffer_header_release(rep_buf); -+ return NULL; ++ vlc_sem_destroy( &p_sys->sem_mt ); ++ free( p_sys ); +} + ++/***************************************************************************** ++ * ffmpeg_InitCodec: setup codec extra initialization data for ffmpeg ++ *****************************************************************************/ ++static void ffmpeg_InitCodec( decoder_t *p_dec ) ++{ ++ decoder_sys_t *p_sys = p_dec->p_sys; ++ size_t i_size = p_dec->fmt_in.i_extra; + ++ if( !i_size ) return; + ++ if( p_sys->p_codec->id == AV_CODEC_ID_SVQ3 ) ++ { ++ uint8_t *p; + -+int hw_mmal_get_gpu_mem(void) { -+ static int stashed_val = -2; -+ VCHI_INSTANCE_T vchi_instance; -+ VCHI_CONNECTION_T *vchi_connection = NULL; -+ char rbuf[1024] = { 0 }; ++ p_sys->p_context->extradata_size = i_size + 12; ++ p = p_sys->p_context->extradata = ++ av_malloc( p_sys->p_context->extradata_size + ++ FF_INPUT_BUFFER_PADDING_SIZE ); ++ if( !p ) ++ return; + -+ if (stashed_val >= -1) -+ return stashed_val; ++ memcpy( &p[0], "SVQ3", 4 ); ++ memset( &p[4], 0, 8 ); ++ memcpy( &p[12], p_dec->fmt_in.p_extra, i_size ); + -+ if (vchi_initialise(&vchi_instance) != 0) -+ goto fail0; ++ /* Now remove all atoms before the SMI one */ ++ if( p_sys->p_context->extradata_size > 0x5a && ++ strncmp( (char*)&p[0x56], "SMI ", 4 ) ) ++ { ++ uint8_t *psz = &p[0x52]; + -+ //create a vchi connection -+ if (vchi_connect(NULL, 0, vchi_instance) != 0) -+ goto fail0; ++ while( psz < &p[p_sys->p_context->extradata_size - 8] ) ++ { ++ uint_fast32_t atom_size = GetDWBE( psz ); ++ if( atom_size <= 1 ) ++ { ++ /* FIXME handle 1 as long size */ ++ break; ++ } ++ if( !strncmp( (char*)&psz[4], "SMI ", 4 ) ) ++ { ++ memmove( &p[0x52], psz, ++ &p[p_sys->p_context->extradata_size] - psz ); ++ break; ++ } + -+ vc_vchi_gencmd_init(vchi_instance, &vchi_connection, 1); ++ psz += atom_size; ++ } ++ } ++ } ++ else ++ { ++ p_sys->p_context->extradata_size = i_size; ++ p_sys->p_context->extradata = ++ av_malloc( i_size + FF_INPUT_BUFFER_PADDING_SIZE ); ++ if( p_sys->p_context->extradata ) ++ { ++ memcpy( p_sys->p_context->extradata, ++ p_dec->fmt_in.p_extra, i_size ); ++ memset( p_sys->p_context->extradata + i_size, ++ 0, FF_INPUT_BUFFER_PADDING_SIZE ); ++ } ++ } ++} + -+ //send the gencmd for the argument -+ if (vc_gencmd_send("get_mem gpu") != 0) -+ goto fail; + -+ if (vc_gencmd_read_response(rbuf, sizeof(rbuf) - 1) != 0) -+ goto fail; ++vlc_module_begin() ++ set_category( CAT_INPUT ) ++ set_subcategory( SUBCAT_INPUT_VCODEC ) ++ set_shortname(N_("MMAL avcodec")) ++ set_description(N_("MMAL buffered avcodec ")) ++ set_capability("video decoder", 80) ++ add_shortcut("mmal_avcodec") ++ add_integer(MMAL_AVCODEC_BUFFERS, -1, MMAL_AVCODEC_BUFFERS_TEXT, ++ MMAL_AVCODEC_BUFFERS_LONGTEXT, true) ++ set_callbacks(MmalAvcodecOpenDecoder, MmalAvcodecCloseDecoder) ++vlc_module_end() + -+ if (strncmp(rbuf, "gpu=", 4) != 0) -+ goto fail; +--- /dev/null ++++ b/modules/hw/mmal/mmal_cma.c +@@ -0,0 +1,672 @@ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif + -+ char *p; -+ unsigned long m = strtoul(rbuf + 4, &p, 10); ++#include ++#include ++#include ++#include ++#include + -+ if (p[0] != 'M' || p[1] != '\0') -+ stashed_val = -1; -+ else -+ stashed_val = (int)m << 20; ++#include + -+ vc_gencmd_stop(); ++#include ++#include + -+ //close the vchi connection -+ vchi_disconnect(vchi_instance); ++#include "mmal_cma.h" ++#include "mmal_cma_int.h" ++#include "mmal_picture.h" + -+ return stashed_val; ++#include + -+fail: -+ vc_gencmd_stop(); -+ vchi_disconnect(vchi_instance); -+fail0: -+ stashed_val = -1; -+ return -1; -+}; ++#define TRACE_ALL 0 + -+// =========================================================================== ++//----------------------------------------------------------------------------- ++// ++// Generic pool functions ++// Knows nothing about pool entries + -+typedef struct pool_ent_s -+{ -+ struct pool_ent_s * next; -+ struct pool_ent_s * prev; ++typedef void * cma_pool_alloc_fn(void * v, size_t size); ++typedef void cma_pool_free_fn(void * v, void * el, size_t size); + -+ atomic_int ref_count; -+ unsigned int seq; ++#if TRACE_ALL ++static atomic_int pool_seq; ++#endif + -+ size_t size; ++// Pool structure ++// Ref count is held by pool owner and pool els that have been got ++// Els in the pool do not count towards its ref count ++struct cma_pool_fixed_s ++{ ++ atomic_int ref_count; + -+ int vcsm_hdl; -+ int vc_hdl; -+ void * buf; ++ vlc_mutex_t lock; ++ unsigned int n_in; ++ unsigned int n_out; ++ unsigned int pool_size; ++ int flight_size; ++ size_t el_size; ++ void ** pool; + -+ unsigned int width; -+ unsigned int height; -+ MMAL_FOURCC_T enc_type; ++ bool cancel; ++ int in_flight; ++ vlc_cond_t flight_cond; + -+ picture_t * pic; -+} pool_ent_t; ++ void * alloc_v; ++ cma_pool_alloc_fn * el_alloc_fn; ++ cma_pool_free_fn * el_free_fn; ++ cma_pool_on_put_fn * on_put_fn; ++ cma_pool_on_delete_fn * on_delete_fn; + ++ const char * name; ++#if TRACE_ALL ++ int seq; ++#endif ++}; + -+typedef struct ent_list_hdr_s ++static void cma_pool_fixed_on_put_null_cb(void * v) +{ -+ pool_ent_t * ents; -+ pool_ent_t * tail; -+ unsigned int n; -+} ent_list_hdr_t; ++ VLC_UNUSED(v); ++} + -+#define ENT_LIST_HDR_INIT (ent_list_hdr_t){ \ -+ .ents = NULL, \ -+ .tail = NULL, \ -+ .n = 0 \ ++static inline unsigned int inc_mod(const unsigned int n, const unsigned int m) ++{ ++ return n + 1 >= m ? 0 : n + 1; +} + -+struct vzc_pool_ctl_s ++static void free_pool(const cma_pool_fixed_t * const p, void ** const pool, ++ const unsigned int pool_size, const size_t el_size) +{ -+ atomic_int ref_count; ++ if (pool == NULL) ++ return; + -+ ent_list_hdr_t ent_pool; -+ ent_list_hdr_t ents_cur; -+ ent_list_hdr_t ents_prev; ++ for (unsigned int n = 0; n != pool_size; ++n) ++ if (pool[n] != NULL) ++ p->el_free_fn(p->alloc_v, pool[n], el_size); ++ free(pool); ++} + -+ unsigned int max_n; -+ unsigned int seq; ++// Just kill this - no checks ++static void cma_pool_fixed_delete(cma_pool_fixed_t * const p) ++{ ++ cma_pool_on_delete_fn *const on_delete_fn = p->on_delete_fn; ++ void *const v = p->alloc_v; + -+ vlc_mutex_t lock; ++ free_pool(p, p->pool, p->pool_size, p->el_size); + -+ MMAL_POOL_T * buf_pool; ++ if (p->name != NULL) ++ free((void *)p->name); // Discard const + -+ vcsm_init_type_t vcsm_init_type; -+}; ++ vlc_cond_destroy(&p->flight_cond); ++ vlc_mutex_destroy(&p->lock); ++ free(p); + -+typedef struct vzc_subbuf_ent_s ++ // Inform our container that we are dead (if it cares) ++ if (on_delete_fn) ++ on_delete_fn(v); ++} ++ ++static void cma_pool_fixed_unref(cma_pool_fixed_t * const p) +{ -+ pool_ent_t * ent; -+ MMAL_RECT_T pic_rect; -+ MMAL_RECT_T orig_dest_rect; -+ MMAL_DISPLAYREGION_T dreg; -+} vzc_subbuf_ent_t; -+ ++ if (atomic_fetch_sub(&p->ref_count, 1) <= 1) ++ cma_pool_fixed_delete(p); ++} + -+static pool_ent_t * ent_extract(ent_list_hdr_t * const elh, pool_ent_t * const ent) ++static void cma_pool_fixed_ref(cma_pool_fixed_t * const p) +{ -+// printf("List %p [%d]: Ext %p\n", elh, elh->n, ent); -+ -+ if (ent == NULL) -+ return NULL; -+ -+ if (ent->next == NULL) -+ elh->tail = ent->prev; -+ else -+ ent->next->prev = ent->prev; -+ -+ if (ent->prev == NULL) -+ elh->ents = ent->next; -+ else -+ ent->prev->next = ent->next; -+ -+ ent->prev = ent->next = NULL; -+ -+ --elh->n; -+ -+ return ent; // For convienience ++ atomic_fetch_add(&p->ref_count, 1); +} + -+static inline pool_ent_t * ent_extract_tail(ent_list_hdr_t * const elh) ++static void cma_pool_fixed_inc_in_flight(cma_pool_fixed_t * const p) +{ -+ return ent_extract(elh, elh->tail); ++ vlc_mutex_lock(&p->lock); ++ ++p->in_flight; ++ vlc_mutex_unlock(&p->lock); +} + -+static void ent_add_head(ent_list_hdr_t * const elh, pool_ent_t * const ent) ++static void cma_pool_fixed_dec_in_flight(cma_pool_fixed_t * const p) +{ -+// printf("List %p [%d]: Add %p\n", elh, elh->n, ent); -+ -+ if ((ent->next = elh->ents) == NULL) -+ elh->tail = ent; -+ else -+ ent->next->prev = ent; -+ -+ ent->prev = NULL; -+ elh->ents = ent; -+ ++elh->n; ++ vlc_mutex_lock(&p->lock); ++ if (--p->in_flight == 0) ++ vlc_cond_signal(&p->flight_cond); ++ vlc_mutex_unlock(&p->lock); +} + -+static void ent_free(pool_ent_t * const ent) ++static void * cma_pool_fixed_get(cma_pool_fixed_t * const p, const size_t req_el_size, const bool inc_flight, const bool no_pool) +{ -+// printf("Free ent: %p\n", ent); -+ if (ent != NULL) { -+ // If we still have a ref to a pic - kill it now -+ if (ent->pic != NULL) -+ picture_Release(ent->pic); ++ void * v = NULL; + -+ // Free contents -+ vcsm_unlock_hdl(ent->vcsm_hdl); ++ vlc_mutex_lock(&p->lock); + -+ vcsm_free(ent->vcsm_hdl); ++ for (;;) ++ { ++ if (req_el_size != p->el_size) ++ { ++ void ** const deadpool = p->pool; ++ const size_t dead_size = p->el_size; ++ const unsigned int dead_n = p->pool_size; + -+ free(ent); -+ } -+} ++ p->pool = NULL; ++ p->n_in = 0; ++ p->n_out = 0; ++ p->el_size = req_el_size; + -+static void ent_free_list(ent_list_hdr_t * const elh) -+{ -+ pool_ent_t * ent = elh->ents; ++ if (deadpool != NULL) ++ { ++ vlc_mutex_unlock(&p->lock); ++ // Do the free old op outside the mutex in case the free is slow ++ free_pool(p, deadpool, dead_n, dead_size); ++ vlc_mutex_lock(&p->lock); ++ continue; ++ } ++ } + -+// printf("Free list: %p [%d]\n", elh, elh->n); ++ // Late abort if flush or cancel so we can still kill the pool ++ if (req_el_size == 0 || p->cancel) ++ { ++ vlc_mutex_unlock(&p->lock); ++ return NULL; ++ } + -+ *elh = ENT_LIST_HDR_INIT; ++ if (p->pool != NULL && !no_pool) ++ { ++ v = p->pool[p->n_in]; ++ if (v != NULL) ++ { ++ p->pool[p->n_in] = NULL; ++ p->n_in = inc_mod(p->n_in, p->pool_size); ++ break; ++ } ++ } + -+ while (ent != NULL) { -+ pool_ent_t * const t = ent; -+ ent = t->next; -+ ent_free(t); ++ if (p->in_flight <= 0) ++ break; ++ ++ vlc_cond_wait(&p->flight_cond, &p->lock); + } -+} + -+static void ent_list_move(ent_list_hdr_t * const dst, ent_list_hdr_t * const src) -+{ -+// printf("Move %p->%p\n", src, dst); ++ if (inc_flight) ++ ++p->in_flight; + -+ *dst = *src; -+ *src = ENT_LIST_HDR_INIT; ++ vlc_mutex_unlock(&p->lock); ++ ++ if (v == NULL && req_el_size != 0) ++ v = p->el_alloc_fn(p->alloc_v, req_el_size); ++ ++ // Tag ref ++ if (v != NULL) ++ cma_pool_fixed_ref(p); ++ // Remove flight if we set it and error ++ else if (inc_flight) ++ cma_pool_fixed_dec_in_flight(p); ++ ++ return v; +} + -+// Scans "backwards" as that should give us the fastest match if we are -+// presented with pics in the same order each time -+static pool_ent_t * ent_list_extract_pic_ent(ent_list_hdr_t * const elh, picture_t * const pic) ++static void cma_pool_fixed_put(cma_pool_fixed_t * const p, void * v, const size_t el_size, const bool was_in_flight) +{ -+ pool_ent_t *ent = elh->tail; ++ p->on_put_fn(v); + -+// printf("Find list: %p [%d]; pic:%p\n", elh, elh->n, pic); ++ vlc_mutex_lock(&p->lock); + -+ while (ent != NULL) { -+// printf("Check ent: %p, pic:%p\n", ent, ent->pic); ++ if (el_size == p->el_size && (p->pool == NULL || p->pool[p->n_out] == NULL)) ++ { ++ if (p->pool == NULL) ++ p->pool = calloc(p->pool_size, sizeof(void*)); + -+ if (ent->pic == pic) -+ return ent_extract(elh, ent); -+ ent = ent->prev; ++ p->pool[p->n_out] = v; ++ p->n_out = inc_mod(p->n_out, p->pool_size); ++ v = NULL; + } -+ return NULL; -+} + -+#define POOL_ENT_ALLOC_BLOCK 0x10000 -+ -+static pool_ent_t * pool_ent_alloc_new(size_t req_size) -+{ -+ pool_ent_t * ent = calloc(1, sizeof(*ent)); -+ const size_t alloc_size = (req_size + POOL_ENT_ALLOC_BLOCK - 1) & ~(POOL_ENT_ALLOC_BLOCK - 1); -+ -+ if (ent == NULL) -+ return NULL; ++ if (was_in_flight) ++ --p->in_flight; + -+ ent->next = ent->prev = NULL; ++ vlc_mutex_unlock(&p->lock); + -+ // Alloc from vcsm -+ if ((ent->vcsm_hdl = vcsm_malloc_cache(alloc_size, VCSM_CACHE_TYPE_HOST, (char *)"vlc-subpic")) == -1) -+ goto fail1; -+ if ((ent->vc_hdl = vcsm_vc_hdl_from_hdl(ent->vcsm_hdl)) == 0) -+ goto fail2; -+ if ((ent->buf = vcsm_lock(ent->vcsm_hdl)) == NULL) -+ goto fail2; ++ vlc_cond_signal(&p->flight_cond); + -+ ent->size = alloc_size; -+ return ent; ++ if (v != NULL) ++ p->el_free_fn(p->alloc_v, v, el_size); + -+fail2: -+ vcsm_free(ent->vcsm_hdl); -+fail1: -+ free(ent); -+ return NULL; ++ cma_pool_fixed_unref(p); +} + -+static inline pool_ent_t * pool_ent_ref(pool_ent_t * const ent) ++static int cma_pool_fixed_resize(cma_pool_fixed_t * const p, ++ const unsigned int new_pool_size, const int new_flight_size) +{ -+// int n = atomic_fetch_add(&ent->ref_count, 1) + 1; -+// printf("Ref: %p: %d\n", ent, n); -+ atomic_fetch_add(&ent->ref_count, 1); -+ return ent; -+} ++ void ** dead_pool = NULL; ++ size_t dead_size = 0; ++ unsigned int dead_n = 0; + -+static void pool_recycle(vzc_pool_ctl_t * const pc, pool_ent_t * const ent) -+{ -+ pool_ent_t * xs = NULL; -+ int n; ++ // This makes this non-reentrant but saves us a lot of time in the normal ++ // "nothing happens" case ++ if (p->pool_size == new_pool_size && p->flight_size == new_flight_size) ++ return 0; + -+ if (ent == NULL) -+ return; ++ vlc_mutex_lock(&p->lock); + -+ n = atomic_fetch_sub(&ent->ref_count, 1) - 1; ++ if (p->pool != NULL && new_pool_size != p->pool_size) ++ { ++ void ** const new_pool = calloc(new_pool_size, sizeof(void*)); ++ unsigned int d, s; ++ dead_pool = p->pool; ++ dead_size = p->el_size; ++ dead_n = p->pool_size; + -+// printf("%s: Pool: %p: Ent: %p: %d\n", __func__, &pc->ent_pool, ent, n); ++ if (new_pool == NULL) ++ { ++ vlc_mutex_unlock(&p->lock); ++ return -1; ++ } + -+ if (n != 0) -+ return; ++ for (d = 0, s = p->n_in; d != new_pool_size && (new_pool[d] = dead_pool[s]) != NULL; ++d, s = inc_mod(s, dead_n)) ++ dead_pool[s] = NULL; + -+ if (ent->pic != NULL) { -+ picture_Release(ent->pic); -+ ent->pic = NULL; ++ p->n_out = 0; ++ p->n_in = (d != new_pool_size) ? d : 0; ++ p->pool = new_pool; + } + -+ vlc_mutex_lock(&pc->lock); -+ -+ // If we have a full pool then extract the LRU and free it -+ // Free done outside mutex -+ if (pc->ent_pool.n >= pc->max_n) -+ xs = ent_extract_tail(&pc->ent_pool); -+ -+ ent_add_head(&pc->ent_pool, ent); ++ p->pool_size = new_pool_size; ++ if (new_flight_size > p->flight_size) ++ vlc_cond_broadcast(&p->flight_cond); // Lock still active so nothing happens till we release it ++ p->in_flight += p->flight_size - new_flight_size; ++ p->flight_size = new_flight_size; + -+ vlc_mutex_unlock(&pc->lock); ++ vlc_mutex_unlock(&p->lock); + -+ ent_free(xs); ++ free_pool(p, dead_pool, dead_n, dead_size); ++ return 0; +} + -+// * This could be made more efficient, but this is easy -+static void pool_recycle_list(vzc_pool_ctl_t * const pc, ent_list_hdr_t * const elh) -+{ -+ pool_ent_t * ent; -+ while ((ent = ent_extract_tail(elh)) != NULL) { -+ pool_recycle(pc, ent); -+ } -+} -+ -+static pool_ent_t * pool_best_fit(vzc_pool_ctl_t * const pc, size_t req_size) ++static int cma_pool_fixed_fill(cma_pool_fixed_t * const p, const size_t el_size) +{ -+ pool_ent_t * best = NULL; -+ -+ vlc_mutex_lock(&pc->lock); -+ ++ for (;;) + { -+ pool_ent_t * ent = pc->ent_pool.ents; -+ -+ // Simple scan -+ while (ent != NULL) { -+ if (ent->size >= req_size && ent->size <= req_size * 2 + POOL_ENT_ALLOC_BLOCK && -+ (best == NULL || best->size > ent->size)) -+ best = ent; -+ ent = ent->next; -+ } -+ -+ // extract best from chain if we've found it -+ ent_extract(&pc->ent_pool, best); ++ vlc_mutex_lock(&p->lock); ++ bool done = el_size == p->el_size && p->pool != NULL && p->pool[p->n_out] != NULL; ++ vlc_mutex_unlock(&p->lock); ++ if (done) ++ break; ++ void * buf = cma_pool_fixed_get(p, el_size, false, true); ++ if (buf == NULL) ++ return -ENOMEM; ++ cma_pool_fixed_put(p, buf, el_size, false); + } -+ -+ vlc_mutex_unlock(&pc->lock); -+ -+ if (best == NULL) -+ best = pool_ent_alloc_new(req_size); -+ -+ if ((best->seq = ++pc->seq) == 0) -+ best->seq = ++pc->seq; // Never allow to be zero -+ -+ atomic_store(&best->ref_count, 1); -+ return best; ++ return 0; +} + -+ -+const vlc_fourcc_t hw_mmal_vzc_subpicture_chromas[] = { VLC_CODEC_RGBA, VLC_CODEC_BGRA, VLC_CODEC_ARGB, 0 }; -+ -+void hw_mmal_vzc_buf_get_wh(MMAL_BUFFER_HEADER_T * const buf, int * const pW, int * const pH) ++static void cma_pool_fixed_cancel(cma_pool_fixed_t * const p) +{ -+ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent; -+ *pW = ent->width; -+ *pH = ent->height; ++ vlc_mutex_lock(&p->lock); ++ p->cancel = true; ++ vlc_cond_broadcast(&p->flight_cond); ++ vlc_mutex_unlock(&p->lock); +} + -+bool hw_mmal_vzc_buf_set_format(MMAL_BUFFER_HEADER_T * const buf, MMAL_ES_FORMAT_T * const es_fmt) ++static void cma_pool_fixed_uncancel(cma_pool_fixed_t * const p) +{ -+ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent; -+ MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video; ++ vlc_mutex_lock(&p->lock); ++ p->cancel = false; ++ vlc_mutex_unlock(&p->lock); ++} + -+ es_fmt->type = MMAL_ES_TYPE_VIDEO; -+ es_fmt->encoding = ent->enc_type; -+ es_fmt->encoding_variant = 0; + -+ v_fmt->width = ent->width; -+ v_fmt->height = ent->height; -+ v_fmt->crop.x = 0; -+ v_fmt->crop.y = 0; -+ v_fmt->crop.width = ent->width; -+ v_fmt->crop.height = ent->height; ++// Purge pool & unref ++static void cma_pool_fixed_kill(cma_pool_fixed_t * const p) ++{ ++ if (p == NULL) ++ return; + -+ return true; ++ // This flush is not strictly needed but it reclaims what memory we can reclaim asap ++ cma_pool_fixed_get(p, 0, false, false); ++ cma_pool_fixed_unref(p); +} + -+void hw_mmal_vzc_buf_frame_size(MMAL_BUFFER_HEADER_T * const buf, -+ uint32_t * const pWidth, uint32_t * const pHeight) ++// Create a new pool ++cma_pool_fixed_t* ++cma_pool_fixed_new(const unsigned int pool_size, ++ const int flight_size, ++ void * const alloc_v, ++ cma_pool_alloc_fn * const alloc_fn, cma_pool_free_fn * const free_fn, ++ cma_pool_on_put_fn * const on_put_fn, cma_pool_on_delete_fn * const on_delete_fn, ++ const char * const name) +{ -+ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent; -+ *pWidth = ent->width; -+ *pHeight = ent->height; -+} -+ ++ cma_pool_fixed_t* const p = calloc(1, sizeof(cma_pool_fixed_t)); ++ if (p == NULL) ++ return NULL; + -+MMAL_DISPLAYREGION_T * hw_mmal_vzc_buf_region(MMAL_BUFFER_HEADER_T * const buf) -+{ -+ vzc_subbuf_ent_t * sb = buf->user_data; -+ return &sb->dreg; -+} ++ atomic_store(&p->ref_count, 1); ++ vlc_mutex_init(&p->lock); ++ vlc_cond_init(&p->flight_cond); + -+static inline int rescale_x(int x, int mul, int div) -+{ -+ return div == 0 ? x * mul : (x * mul + div/2) / div; -+} ++ p->pool_size = pool_size; ++ p->flight_size = flight_size; ++ p->in_flight = -flight_size; + -+static void rescale_rect(MMAL_RECT_T * const d, const MMAL_RECT_T * const s, const MMAL_RECT_T * mul_rect, const MMAL_RECT_T * div_rect) -+{ -+ d->x = rescale_x(s->x - div_rect->x, mul_rect->width, div_rect->width) + mul_rect->x; -+ d->y = rescale_x(s->y - div_rect->y, mul_rect->height, div_rect->height) + mul_rect->y; -+ d->width = rescale_x(s->width, mul_rect->width, div_rect->width); -+ d->height = rescale_x(s->height, mul_rect->height, div_rect->height); -+#if TRACE_TRANSFORMS -+ fprintf(stderr, "(%d,%d %dx%d) * (%d,%d %dx%d) / (%d,%d %dx%d) -> (%d,%d %dx%d)\n", -+ s->x, s->y, s->width, s->height, -+ mul_rect->x, mul_rect->y, mul_rect->width, mul_rect->height, -+ div_rect->x, div_rect->y, div_rect->width, div_rect->height, -+ d->x, d->y, d->width, d->height); ++ p->alloc_v = alloc_v; ++ p->el_alloc_fn = alloc_fn; ++ p->el_free_fn = free_fn; ++ p->on_put_fn = on_put_fn; ++ p->on_delete_fn = on_delete_fn; ++ p->name = name == NULL ? NULL : strdup(name); ++#if TRACE_ALL ++ p->seq = atomic_fetch_add(&pool_seq, 1); +#endif ++ ++ return p; +} + -+static MMAL_RECT_T -+rect_untransform(MMAL_RECT_T s, const MMAL_RECT_T c, const MMAL_DISPLAYTRANSFORM_T t) ++// --------------------------------------------------------------------------- ++// ++// CMA buffer functions - uses cma_pool_fixed for pooling ++ ++void cma_pool_delete(cma_buf_t * const cb) +{ -+#if TRACE_TRANSFORMS -+ fprintf(stderr, "t=%d, s=%d,%d:%dx%d, c=%d,%d:%dx%d -> ", (int)t, -+ s.x,s.y,s.width,s.height, -+ c.x,c.y,c.width,c.height); -+#endif -+ if (is_transform_hflip(t)) -+ s = rect_hflip(s, c); -+ if (is_transform_vflip(t) != 0) -+ s = rect_vflip(s, c); -+ if (is_transform_transpose(t) != 0) -+ s = rect_transpose(s); -+#if TRACE_TRANSFORMS -+ fprintf(stderr, "s=%d,%d:%dx%d\n", -+ s.x,s.y,s.width,s.height); ++ assert(atomic_load(&cb->ref_count) == 0); ++#if TRACE_ALL ++ cb->cbp->alloc_size -= cb->size; ++ --cb->cbp->alloc_n; ++ fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cb->cbp->pool->seq, cb->cbp->pool->name, cb->cbp->alloc_n, cb->cbp->alloc_size); +#endif -+ return s; -+} + -+void hw_mmal_vzc_buf_scale_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const MMAL_RECT_T * const scale_rect, const MMAL_DISPLAYTRANSFORM_T scale_transform) -+{ -+ vzc_subbuf_ent_t * sb = buf->user_data; -+ if (scale_rect == NULL) { -+ sb->dreg.dest_rect = sb->orig_dest_rect; -+ sb->dreg.transform = MMAL_DISPLAY_ROT0; -+ } -+ else ++ if (cb->ctx2 != NULL) ++ cb->ctx2->destroy(cb->ctx2); ++ ++ if (cb->mmap != MAP_FAILED) + { -+ // The scale rect has been transposed if we have a transposing -+ // transform - untranspose so we are the same way up as the source -+ const MMAL_RECT_T c = (scale_transform & 4) == 0 ? *scale_rect : rect_transpose(*scale_rect); -+ rescale_rect(&sb->dreg.dest_rect, &sb->orig_dest_rect, -+ &c, &sb->pic_rect); -+ sb->dreg.dest_rect = rect_untransform(sb->dreg.dest_rect, c, scale_transform); -+ sb->dreg.transform = scale_transform; ++ if (cb->cbp->buf_type != CMA_BUF_TYPE_VCSM) ++ munmap(cb->mmap, cb->size); ++ else ++ vcsm_unlock_hdl(cb->vcsm_h); + } ++ if (cb->fd != -1) ++ close(cb->fd); ++ if (cb->vcsm_h != 0) ++ vcsm_free(cb->vcsm_h); ++ free(cb); +} + -+unsigned int hw_mmal_vzc_buf_seq(MMAL_BUFFER_HEADER_T * const buf) ++static void cma_pool_free_cb(void * v, void * el, size_t size) +{ -+ vzc_subbuf_ent_t * sb = buf->user_data; -+ return sb->ent->seq; -+} -+ -+ -+// The intent with the ents_cur & ents_last stuff is to remember the buffers -+// we used on the last frame and reuse them on the current one if they are the -+// same. Unfortunately detection of "is_first" is only a heuristic (there are -+// no rules governing the order in which things are blended) so we must deal -+// (fairly) gracefully with it never (or always) being set. ++ VLC_UNUSED(v); ++ VLC_UNUSED(size); + -+// dst_fmt gives the number space in which the destination pixels are specified ++ cma_pool_delete(el); ++} + -+MMAL_BUFFER_HEADER_T * hw_mmal_vzc_buf_from_pic(vzc_pool_ctl_t * const pc, -+ picture_t * const pic, -+ const MMAL_RECT_T dst_pic_rect, -+ const int x_offset, const int y_offset, -+ const unsigned int alpha, -+ const bool is_first) ++static void * cma_pool_alloc_cb(void * v, size_t size) +{ -+ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_get(pc->buf_pool->queue); -+ vzc_subbuf_ent_t * sb; ++ cma_buf_pool_t * const cbp = v; + -+ if (buf == NULL) ++ cma_buf_t * const cb = malloc(sizeof(cma_buf_t)); ++ if (cb == NULL) + return NULL; + -+ if ((sb = calloc(1, sizeof(*sb))) == NULL) -+ goto fail1; ++ *cb = (cma_buf_t){ ++ .ref_count = ATOMIC_VAR_INIT(0), ++ .cbp = cbp, ++ .in_flight = 0, ++ .size = size, ++ .vcsm_h = 0, ++ .vc_h = 0, ++ .fd = -1, ++ .mmap = MAP_FAILED, ++ .ctx2 = NULL ++ }; ++#if TRACE_ALL ++ cb->cbp->alloc_size += cb->size; ++ ++cb->cbp->alloc_n; ++ fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cbp->pool->seq, cbp->pool->name, cbp->alloc_n, cbp->alloc_size); ++#endif + -+ // If first or we've had a lot of stuff move everything to the last list -+ // (we could deal more gracefully with the "too many" case but it shouldn't -+ // really happen) -+ if (is_first || pc->ents_cur.n >= CTX_BUFS_MAX) { -+ pool_recycle_list(pc, &pc->ents_prev); -+ ent_list_move(&pc->ents_prev, &pc->ents_cur); ++ // 0x80 is magic value to force full ARM-side mapping - otherwise ++ // cache requests can cause kernel crashes ++ if ((cb->vcsm_h = vcsm_malloc_cache(size, VCSM_CACHE_TYPE_HOST | 0x80, "VLC frame")) == 0) ++ { ++#if TRACE_ALL ++ fprintf(stderr, "vcsm_malloc_cache fail\n"); ++#endif ++ goto fail; + } + -+ sb->dreg.hdr.id = MMAL_PARAMETER_DISPLAYREGION; -+ sb->dreg.hdr.size = sizeof(sb->dreg); -+ buf->user_data = sb; -+ ++ if ((cb->vc_h = vcsm_vc_hdl_from_hdl(cb->vcsm_h)) == 0) + { -+ // ?? Round start offset as well as length -+ const video_format_t *const fmt = &pic->format; -+ -+ const unsigned int bpp = (fmt->i_bits_per_pixel + 7) >> 3; -+ const unsigned int xl = (fmt->i_x_offset & ~15); -+ const unsigned int xr = (fmt->i_x_offset + fmt->i_visible_width + 15) & ~15; -+ const size_t dst_stride = (xr - xl) * bpp; -+ const size_t dst_lines = ((fmt->i_visible_height + 15) & ~15); -+ const size_t dst_size = dst_stride * dst_lines; -+ -+ pool_ent_t * ent = ent_list_extract_pic_ent(&pc->ents_prev, pic); -+ bool needs_copy = false; -+ -+ // If we didn't find ent in last then look in cur in case is_first -+ // isn't working -+ if (ent == NULL) -+ ent = ent_list_extract_pic_ent(&pc->ents_cur, pic); ++#if TRACE_ALL ++ fprintf(stderr, "vcsm_vc_hdl_from_hdl fail\n"); ++#endif ++ goto fail; ++ } + -+// printf("ent_found: %p\n", ent); - --int mmal_picture_lock(picture_t *picture) -+ if (ent == NULL) ++ if (cbp->buf_type == CMA_BUF_TYPE_CMA) ++ { ++ if ((cb->fd = vcsm_export_dmabuf(cb->vcsm_h)) == -1) + { -+ // Need a new ent -+ needs_copy = true; -+ -+ if ((ent = pool_best_fit(pc, dst_size)) == NULL) -+ goto fail2; -+ if ((ent->enc_type = vlc_to_mmal_video_fourcc(&pic->format)) == 0) -+ goto fail2; -+ -+ ent->pic = picture_Hold(pic); ++#if TRACE_ALL ++ fprintf(stderr, "vcsm_export_dmabuf fail\n"); ++#endif ++ goto fail; + } + -+ ent_add_head(&pc->ents_cur, ent); -+ -+ sb->ent = pool_ent_ref(ent); -+ hw_mmal_vzc_pool_ref(pc); -+ -+ // Copy data -+ buf->next = NULL; -+ buf->cmd = 0; -+ buf->data = (uint8_t *)(ent->vc_hdl); -+ buf->alloc_size = buf->length = dst_size; -+ buf->offset = 0; -+ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END; -+ buf->pts = buf->dts = pic->date != VLC_TICK_INVALID ? pic->date : MMAL_TIME_UNKNOWN; -+ buf->type->video = (MMAL_BUFFER_HEADER_VIDEO_SPECIFIC_T){ -+ .planes = 1, -+ .pitch = { dst_stride } -+ }; -+ -+ // Remember offsets -+ sb->dreg.set = MMAL_DISPLAY_SET_SRC_RECT | -+ MMAL_DISPLAY_SET_DEST_RECT | -+ MMAL_DISPLAY_SET_FULLSCREEN | -+ MMAL_DISPLAY_SET_TRANSFORM | -+ MMAL_DISPLAY_SET_ALPHA; -+ -+ sb->dreg.fullscreen = 0; -+ -+ // Will be set later - zero now to avoid any confusion -+ sb->dreg.transform = MMAL_DISPLAY_ROT0; -+ sb->dreg.dest_rect = (MMAL_RECT_T){0, 0, 0, 0}; -+ -+ sb->dreg.alpha = (uint32_t)(alpha & 0xff) | MMAL_DISPLAY_ALPHA_FLAGS_MIX; -+ -+// printf("+++ bpp:%d, vis:%dx%d wxh:%dx%d, d:%dx%d\n", bpp, fmt->i_visible_width, fmt->i_visible_height, fmt->i_width, fmt->i_height, dst_stride, dst_lines); -+ -+ sb->dreg.src_rect = (MMAL_RECT_T){ -+ .x = (fmt->i_x_offset - xl), -+ .y = 0, -+ .width = fmt->i_visible_width, -+ .height = fmt->i_visible_height -+ }; -+ -+ sb->pic_rect = dst_pic_rect; -+ -+ sb->orig_dest_rect = (MMAL_RECT_T){ -+ .x = x_offset, -+ .y = y_offset, -+ .width = fmt->i_visible_width, -+ .height = fmt->i_visible_height -+ }; -+ -+ if (needs_copy) ++ if ((cb->mmap = mmap(NULL, cb->size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, cb->fd, 0)) == MAP_FAILED) ++ goto fail; ++ } ++ else ++ { ++ void * arm_addr; ++ if ((arm_addr = vcsm_lock(cb->vcsm_h)) == NULL) + { -+ ent->width = dst_stride / bpp; -+ ent->height = dst_lines; -+ -+ // 2D copy -+ { -+ uint8_t *d = ent->buf; -+ const uint8_t *s = pic->p[0].p_pixels + xl * bpp + fmt->i_y_offset * pic->p[0].i_pitch; -+ -+ mem_copy_2d(d, dst_stride, s, pic->p[0].i_pitch, fmt->i_visible_height, dst_stride); -+ -+ // And make sure it is actually in memory -+ if (pc->vcsm_init_type != VCSM_INIT_CMA) { // ** CMA is currently always uncached -+ flush_range(ent->buf, dst_stride * fmt->i_visible_height); -+ } -+ } ++#if TRACE_ALL ++ fprintf(stderr, "vcsm_lock fail\n"); ++#endif ++ goto fail; + } ++ cb->mmap = arm_addr; + } + -+ return buf; ++ cb->vc_addr = vcsm_vc_addr_from_hdl(cb->vcsm_h); + -+fail2: -+ free(sb); -+fail1: -+ mmal_buffer_header_release(buf); ++ return cb; ++ ++fail: ++ cma_pool_delete(cb); + return NULL; +} + -+void hw_mmal_vzc_pool_flush(vzc_pool_ctl_t * const pc) ++// Pool has died - safe now to exit vcsm ++static void cma_buf_pool_on_delete_cb(void * v) +{ -+ pool_recycle_list(pc, &pc->ents_prev); -+ pool_recycle_list(pc, &pc->ents_cur); ++ cma_buf_pool_t * const cbp = v; ++ ++ switch (cbp->buf_type) ++ { ++ case CMA_BUF_TYPE_CMA: ++ cma_vcsm_exit(VCSM_INIT_CMA); ++ break; ++ case CMA_BUF_TYPE_VCSM: ++ cma_vcsm_exit(VCSM_INIT_LEGACY); ++ break; ++ default: ++ break; ++ } ++ free(cbp); +} + -+static void hw_mmal_vzc_pool_delete(vzc_pool_ctl_t * const pc) ++void cma_buf_pool_cancel(cma_buf_pool_t * const cbp) +{ ++ if (cbp == NULL || cbp->pool == NULL) ++ return; + -+// printf("<<< %s\n", __func__); -+ -+ hw_mmal_vzc_pool_flush(pc); -+ -+ ent_free_list(&pc->ent_pool); -+ -+ if (pc->buf_pool != NULL) -+ mmal_pool_destroy(pc->buf_pool); -+ -+ vlc_mutex_destroy(&pc->lock); -+ -+ cma_vcsm_exit(pc->vcsm_init_type); -+ -+// memset(pc, 0xba, sizeof(*pc)); // Zap for (hopefully) faster crash -+ free (pc); -+ -+ // printf(">>> %s\n", __func__); ++ cma_pool_fixed_cancel(cbp->pool); +} + -+void hw_mmal_vzc_pool_release(vzc_pool_ctl_t * const pc) ++void cma_buf_pool_uncancel(cma_buf_pool_t * const cbp) +{ -+ int n; -+ -+ if (pc == NULL) ++ if (cbp == NULL || cbp->pool == NULL) + return; + -+ n = atomic_fetch_sub(&pc->ref_count, 1) - 1; ++ cma_pool_fixed_uncancel(cbp->pool); ++} + -+ if (n != 0) ++// User finished with pool ++void cma_buf_pool_delete(cma_buf_pool_t * const cbp) ++{ ++ if (cbp == NULL) + return; + -+ hw_mmal_vzc_pool_delete(pc); ++ if (cbp->pool != NULL) ++ { ++ // We will call cma_buf_pool_on_delete_cb when the pool finally dies ++ // (might be now) which will free up our env. ++ cma_pool_fixed_kill(cbp->pool); ++ } ++ else ++ { ++ // Had no pool for some reason (error) but must still finish cleanup ++ cma_buf_pool_on_delete_cb(cbp); ++ } +} + -+void hw_mmal_vzc_pool_ref(vzc_pool_ctl_t * const pc) ++int cma_buf_pool_fill(cma_buf_pool_t * const cbp, const size_t el_size) +{ -+ atomic_fetch_add(&pc->ref_count, 1); ++ return cma_pool_fixed_fill(cbp->pool, el_size); +} + -+static MMAL_BOOL_T vcz_pool_release_cb(MMAL_POOL_T * buf_pool, MMAL_BUFFER_HEADER_T *buf, void *userdata) ++int cma_buf_pool_resize(cma_buf_pool_t * const cbp, ++ const unsigned int new_pool_size, const int new_flight_size) +{ -+ vzc_pool_ctl_t * const pc = userdata; -+ vzc_subbuf_ent_t * const sb = buf->user_data; ++ return cma_pool_fixed_resize(cbp->pool, new_pool_size, new_flight_size); ++} + -+ VLC_UNUSED(buf_pool); ++cma_buf_pool_t * cma_buf_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name) ++{ ++ vcsm_init_type_t const init_type = cma_vcsm_init(); ++ if (init_type == VCSM_INIT_NONE) ++ return NULL; + -+// printf("<<< %s\n", __func__); ++ cma_buf_pool_t * const cbp = calloc(1, sizeof(cma_buf_pool_t)); ++ if (cbp == NULL) ++ return NULL; + -+ if (sb != NULL) { -+ buf->user_data = NULL; -+ pool_recycle(pc, sb->ent); -+ hw_mmal_vzc_pool_release(pc); -+ free(sb); -+ } ++ cbp->buf_type = (init_type == VCSM_INIT_CMA) ? CMA_BUF_TYPE_CMA : CMA_BUF_TYPE_VCSM; ++ cbp->all_in_flight = all_in_flight; + -+// printf(">>> %s\n", __func__); ++ if ((cbp->pool = cma_pool_fixed_new(pool_size, flight_size, cbp, ++ cma_pool_alloc_cb, cma_pool_free_cb, ++ cma_pool_fixed_on_put_null_cb, cma_buf_pool_on_delete_cb, ++ name)) == NULL) ++ goto fail; ++ return cbp; + -+ return MMAL_TRUE; ++fail: ++ cma_buf_pool_delete(cbp); ++ return NULL; +} + -+vzc_pool_ctl_t * hw_mmal_vzc_pool_new() -+{ -+ vzc_pool_ctl_t * const pc = calloc(1, sizeof(*pc)); -+ -+ if (pc == NULL) -+ return NULL; + -+ if ((pc->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) ++void cma_buf_in_flight(cma_buf_t * const cb) ++{ ++ if (!cb->cbp->all_in_flight) + { -+ free(pc); -+ return NULL; ++ assert(!cb->in_flight); ++ cb->in_flight = true; ++ cma_pool_fixed_inc_in_flight(cb->cbp->pool); + } ++} + -+ pc->max_n = 8; -+ vlc_mutex_init(&pc->lock); // Must init before potential destruction -+ -+ if ((pc->buf_pool = mmal_pool_create(64, 0)) == NULL) ++void cma_buf_end_flight(cma_buf_t * const cb) ++{ ++ if (cb != NULL && !cb->cbp->all_in_flight && cb->in_flight) + { -+ hw_mmal_vzc_pool_delete(pc); -+ return NULL; ++ cb->in_flight = false; ++ cma_pool_fixed_dec_in_flight(cb->cbp->pool); + } ++} + -+ atomic_store(&pc->ref_count, 1); -+ -+ mmal_pool_callback_set(pc->buf_pool, vcz_pool_release_cb, pc); + -+ return pc; ++// Return vcsm handle ++unsigned int cma_buf_vcsm_handle(cma_buf_t * const cb) ++{ ++ if (cb->vcsm_h == 0 && cb->fd != -1) ++ cb->vcsm_h = vcsm_import_dmabuf(cb->fd, "vlc-drmprime"); ++ return cb->vcsm_h; +} + -+//---------------------------------------------------------------------------- ++size_t cma_buf_size(const cma_buf_t * const cb) ++{ ++ return cb->size; ++} + ++int cma_buf_add_context2(cma_buf_t *const cb, picture_context_t * const ctx2) ++{ ++ if (cb->ctx2 != NULL) ++ return VLC_EGENERIC; + -+static const uint8_t shift_00[] = {0,0,0,0}; -+static const uint8_t shift_01[] = {0,1,1,1}; ++ cb->ctx2 = ctx2; ++ return VLC_SUCCESS; ++} + -+int cma_pic_set_data(picture_t * const pic, -+ const MMAL_ES_FORMAT_T * const mm_esfmt, -+ const MMAL_BUFFER_HEADER_T * const buf) - { -- picture_sys_t *pic_sys = picture->p_sys; -- MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer; -+ const MMAL_VIDEO_FORMAT_T * const mm_fmt = &mm_esfmt->es->video; -+ const MMAL_BUFFER_HEADER_VIDEO_SPECIFIC_T *const buf_vid = (buf == NULL) ? NULL : &buf->type->video; -+ cma_buf_t *const cb = cma_buf_pic_get(pic); -+ unsigned int planes = 1; ++unsigned int cma_buf_vc_handle(cma_buf_t *const cb) ++{ ++ if (cb->vc_h == 0) ++ { ++ const int vcsm_h = cma_buf_vcsm_handle(cb); ++ if (vcsm_h != 0) ++ cb->vc_h = vcsm_vc_hdl_from_hdl(vcsm_h); ++ } ++ return cb->vc_h; ++} + -+ uint8_t * const data = cma_buf_addr(cb); -+ if (data == NULL) { -+ return VLC_ENOMEM; ++unsigned int cma_buf_vc_addr(cma_buf_t *const cb) ++{ ++ if (cb->vc_addr == 0) ++ { ++ const int vcsm_h = cma_buf_vcsm_handle(cb); ++ if (vcsm_h != 0) ++ cb->vc_addr = vcsm_vc_addr_from_hdl(vcsm_h); + } ++ return cb->vc_addr; ++} + -+ const uint8_t * ws = shift_00; -+ const uint8_t * hs = shift_00; -+ int pb = 1; + -+ switch (mm_esfmt->encoding) -+ { -+ case MMAL_ENCODING_ARGB: -+ case MMAL_ENCODING_ABGR: -+ case MMAL_ENCODING_RGBA: -+ case MMAL_ENCODING_BGRA: -+ case MMAL_ENCODING_RGB32: -+ case MMAL_ENCODING_BGR32: -+ pb = 4; -+ break; -+ case MMAL_ENCODING_RGB16: -+ pb = 2; -+ break; - -- int offset = 0; -- picture->p[0].p_pixels = buffer->data; -- for (int i = 1; i < picture->i_planes; i++) { -- offset = offset + picture->p[i - 1].i_pitch * picture->p[i - 1].i_lines; -- picture->p[i].p_pixels = (ptrdiff_t)buffer->data + offset; -+ case MMAL_ENCODING_I420: -+ ws = shift_01; -+ hs = shift_01; -+ planes = 3; -+ break; ++picture_context_t * cma_buf_context2(const cma_buf_t *const cb) ++{ ++ return cb->ctx2; ++} + -+ case MMAL_ENCODING_YUVUV128: -+ hs = shift_01; -+ planes = 2; -+ break; + -+ default: -+// msg_Err(p_filter, "%s: Unexpected format", __func__); -+ return VLC_EGENERIC; - } - -- pic_sys->displayed = false; -+ // Fix up SAR if unset -+ if (pic->format.i_sar_den == 0 || pic->format.i_sar_num == 0) { -+ pic->format.i_sar_den = mm_fmt->par.den; -+ pic->format.i_sar_num = mm_fmt->par.num; -+ } - -+ pic->i_planes = planes; -+ unsigned int offset = 0; -+ for (unsigned int i = 0; i != planes; ++i) { -+ pic->p[i] = (plane_t){ -+ .p_pixels = data + (buf_vid != NULL ? buf_vid->offset[i] : offset), -+ .i_lines = mm_fmt->height >> hs[i], -+ .i_pitch = buf_vid != NULL ? buf_vid->pitch[i] : mm_fmt->width * pb, -+ .i_pixel_pitch = pb, -+ .i_visible_lines = mm_fmt->crop.height >> hs[i], -+ .i_visible_pitch = mm_fmt->crop.width >> ws[i] -+ }; -+ offset += pic->p[i].i_pitch * pic->p[i].i_lines; ++void cma_buf_unref(cma_buf_t * const cb) ++{ ++ if (cb == NULL) ++ return; ++ if (atomic_fetch_sub(&cb->ref_count, 1) <= 1) ++ { ++ const bool was_in_flight = cb->in_flight; ++ cb->in_flight = false; ++ cma_pool_fixed_put(cb->cbp->pool, cb, cb->size, was_in_flight); + } - return VLC_SUCCESS; - } ++} + -+int cma_buf_pic_attach(cma_buf_t * const cb, picture_t * const pic) ++cma_buf_t * cma_buf_ref(cma_buf_t * const cb) +{ -+ if (!is_cma_buf_pic_chroma(pic->format.i_chroma)) -+ return VLC_EGENERIC; -+ if (pic->context != NULL) -+ return VLC_EBADVAR; ++ if (cb == NULL) ++ return NULL; ++ atomic_fetch_add(&cb->ref_count, 1); ++ return cb; ++} + -+ pic_ctx_mmal_t * const ctx = calloc(1, sizeof(pic_ctx_mmal_t)); ++cma_buf_t * cma_buf_pool_alloc_buf(cma_buf_pool_t * const cbp, const size_t size) ++{ ++ cma_buf_t *const cb = cma_pool_fixed_get(cbp->pool, size, cbp->all_in_flight, false); + -+ if (ctx == NULL) -+ return VLC_ENOMEM; ++ if (cb == NULL) ++ return NULL; + -+ ctx->cmn.copy = hw_mmal_pic_ctx_copy; -+ ctx->cmn.destroy = hw_mmal_pic_ctx_destroy; -+ ctx->buf_count = 1; // cb takes the place of the 1st buf -+ ctx->cb = cb; ++ cb->type = CMA_BUF_TYPE_CMA; ++ cb->in_flight = cbp->all_in_flight; ++ // When 1st allocated or retrieved from the pool the block will have a ++ // ref count of 0 so ref here ++ return cma_buf_ref(cb); ++} + -+ cma_buf_in_flight(cb); +--- /dev/null ++++ b/modules/hw/mmal/mmal_cma.h +@@ -0,0 +1,43 @@ ++#ifndef VLC_MMAL_MMAL_CMA_H_ ++#define VLC_MMAL_MMAL_CMA_H_ + -+ pic->context = &ctx->cmn; -+ return VLC_SUCCESS; -+} ++#include ++ ++struct cma_buf_s; ++typedef struct cma_buf_s cma_buf_t; ++ ++void cma_buf_in_flight(cma_buf_t * const cb); ++void cma_buf_end_flight(cma_buf_t * const cb); ++unsigned int cma_buf_vcsm_handle(cma_buf_t * const cb); ++size_t cma_buf_size(const cma_buf_t * const cb); ++int cma_buf_add_context2(cma_buf_t *const cb, picture_context_t * const ctx2); ++unsigned int cma_buf_vc_handle(cma_buf_t *const cb); ++unsigned int cma_buf_vc_addr(cma_buf_t *const cb); ++picture_context_t * cma_buf_context2(const cma_buf_t *const cb); ++ ++void cma_buf_unref(cma_buf_t * const cb); ++cma_buf_t * cma_buf_ref(cma_buf_t * const cb); ++ ++struct cma_buf_pool_s; ++typedef struct cma_buf_pool_s cma_buf_pool_t; ++ ++cma_buf_t * cma_buf_pool_alloc_buf(cma_buf_pool_t * const p, const size_t size); ++void cma_buf_pool_cancel(cma_buf_pool_t * const cbp); ++void cma_buf_pool_uncancel(cma_buf_pool_t * const cbp); ++void cma_buf_pool_delete(cma_buf_pool_t * const p); ++int cma_buf_pool_fill(cma_buf_pool_t * const cbp, const size_t el_size); ++int cma_buf_pool_resize(cma_buf_pool_t * const cbp, ++ const unsigned int new_pool_size, const int new_flight_size); ++cma_buf_pool_t * cma_buf_pool_new(const unsigned int pool_size, const unsigned int flight_size, ++ const bool all_in_flight, const char * const name); + -+cma_buf_t * cma_buf_pic_get(picture_t * const pic) ++static inline void cma_buf_pool_deletez(cma_buf_pool_t ** const pp) +{ -+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; -+ return !is_cma_buf_pic_chroma(pic->format.i_chroma) || ctx == NULL ? 0 : ctx->cb; ++ cma_buf_pool_t * const p = *pp; ++ if (p != NULL) { ++ *pp = NULL; ++ cma_buf_pool_delete(p); ++ } +} + ++#endif // VLC_MMAL_MMAL_CMA_H_ +--- /dev/null ++++ b/modules/hw/mmal/mmal_cma_drmprime.c +@@ -0,0 +1,120 @@ ++#include + -+//---------------------------------------------------------------------------- ++#include "mmal_cma.h" ++#include "mmal_cma_int.h" ++#include "mmal_cma_drmprime.h" + -+/* Returns the type of the Pi being used -+*/ -+bool rpi_is_model_pi4(void) { -+ return bcm_host_is_model_pi4(); -+} ++#include ++#include ++#include ++#include + -+// Preferred mode - none->cma on Pi4 otherwise legacy -+static volatile vcsm_init_type_t last_vcsm_type = VCSM_INIT_NONE; ++#define TRACE_ALL 0 + -+vcsm_init_type_t cma_vcsm_type(void) ++typedef struct cma_drmprime_buf_s +{ -+ return last_vcsm_type; -+} ++ cma_buf_t cb; ++ const AVDRMFrameDescriptor *desc; ++ AVBufferRef * avbuf; ++} cma_drmprime_buf_t; + -+vcsm_init_type_t cma_vcsm_init(void) ++static void drmprime_pool_free_cb(void * v, void * el, size_t size) +{ -+ vcsm_init_type_t rv = VCSM_INIT_NONE; -+ // We don't bother locking - taking a copy here should be good enough -+ vcsm_init_type_t try_type = last_vcsm_type; ++ VLC_UNUSED(v); ++ VLC_UNUSED(size); + -+ if (try_type == VCSM_INIT_NONE) { -+ if (bcm_host_is_fkms_active()) -+ try_type = VCSM_INIT_CMA; -+ else -+ try_type = VCSM_INIT_LEGACY; -+ } ++ cma_pool_delete(el); ++} + -+ if (try_type == VCSM_INIT_CMA) { -+ if (vcsm_init_ex(1, -1) == 0) -+ rv = VCSM_INIT_CMA; -+ else if (vcsm_init_ex(0, -1) == 0) -+ rv = VCSM_INIT_LEGACY; -+ } -+ else -+ { -+ if (vcsm_init_ex(0, -1) == 0) -+ rv = VCSM_INIT_LEGACY; -+ else if (vcsm_init_ex(1, -1) == 0) -+ rv = VCSM_INIT_CMA; -+ } ++static void * drmprime_pool_alloc_cb(void * v, size_t size) ++{ ++ cma_buf_pool_t * const cbp = v; + -+ // Just in case this affects vcsm init do after that -+ if (rv != VCSM_INIT_NONE) -+ bcm_host_init(); ++ cma_drmprime_buf_t * const cdb = malloc(sizeof(cma_drmprime_buf_t)); ++ if (cdb == NULL) ++ return NULL; + -+ last_vcsm_type = rv; -+ return rv; ++ *cdb = (cma_drmprime_buf_t){ ++ .cb = { ++ .ref_count = ATOMIC_VAR_INIT(0), ++ .cbp = cbp, ++ .in_flight = 0, ++ .size = size, ++ .vcsm_h = 0, ++ .vc_h = 0, ++ .fd = -1, ++ .mmap = MAP_FAILED, ++ .ctx2 = NULL ++ } ++ }; ++#if TRACE_ALL ++ cdb->cbp->alloc_size += cdb->size; ++ ++cdb->cbp->alloc_n; ++ fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cbp->pool->seq, cbp->pool->name, cbp->alloc_n, cbp->alloc_size); ++#endif ++ ++ return cdb; +} + -+void cma_vcsm_exit(const vcsm_init_type_t init_mode) ++// Buf being returned to pool ++static void drmprime_buf_pool_on_put_cb(void * v) +{ -+ if (init_mode != VCSM_INIT_NONE) -+ { -+ vcsm_exit(); -+ bcm_host_deinit(); // Does nothing but add in case it ever does ++ cma_drmprime_buf_t * const cdb = v; ++ cdb->cb.fd = -1; ++ if (cdb->cb.vcsm_h != 0) ++ vcsm_free(cdb->cb.vcsm_h); ++ cdb->cb.vcsm_h = 0; ++ cdb->cb.vc_h = 0; ++ cdb->cb.vc_addr = 0; ++ av_buffer_unref(&cdb->avbuf); ++ if (cdb->cb.ctx2) { ++ cdb->cb.ctx2->destroy(cdb->cb.ctx2); ++ cdb->cb.ctx2 = NULL; + } +} + -+const char * cma_vcsm_init_str(const vcsm_init_type_t init_mode) ++// Pool has died - safe now to exit vcsm ++static void drmprime_buf_pool_on_delete_cb(void * v) +{ -+ switch (init_mode) -+ { -+ case VCSM_INIT_CMA: -+ return "CMA"; -+ case VCSM_INIT_LEGACY: -+ return "Legacy"; -+ case VCSM_INIT_NONE: -+ return "none"; -+ default: -+ break; -+ } -+ return "???"; ++ cma_buf_pool_t * const cbp = v; ++ free(cbp); +} + ++cma_buf_pool_t * cma_drmprime_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name) ++{ ++ cma_buf_pool_t * const cbp = calloc(1, sizeof(cma_buf_pool_t)); ++ if (cbp == NULL) ++ return NULL; + ---- a/modules/hw/mmal/mmal_picture.h -+++ b/modules/hw/mmal/mmal_picture.h -@@ -24,19 +24,298 @@ - #ifndef VLC_MMAL_MMAL_PICTURE_H_ - #define VLC_MMAL_MMAL_PICTURE_H_ - -+#include ++ cbp->buf_type = CMA_BUF_TYPE_DRMPRIME; ++ cbp->all_in_flight = all_in_flight; + - #include - #include - -+#include "mmal_cma.h" ++ if ((cbp->pool = cma_pool_fixed_new(pool_size, flight_size, cbp, ++ drmprime_pool_alloc_cb, drmprime_pool_free_cb, ++ drmprime_buf_pool_on_put_cb, drmprime_buf_pool_on_delete_cb, ++ name)) == NULL) ++ goto fail; ++ return cbp; + - /* Think twice before changing this. Incorrect values cause havoc. */ - #define NUM_ACTUAL_OPAQUE_BUFFERS 30 - --struct picture_sys_t { -- vlc_object_t *owner; -+#ifndef VLC_TICK_INVALID -+#define VLC_TICK_INVALID VLC_TS_INVALID -+#define VLC_VER_3 1 -+#else -+#define VLC_VER_3 0 -+#endif ++fail: ++ cma_buf_pool_delete(cbp); ++ return NULL; ++} + -+typedef struct mmal_port_pool_ref_s ++cma_buf_t * cma_drmprime_pool_alloc_buf(cma_buf_pool_t * const cbp, struct AVFrame * frame) +{ -+ atomic_uint refs; -+ MMAL_POOL_T * pool; -+ MMAL_PORT_T * port; -+} hw_mmal_port_pool_ref_t; ++ const AVDRMFrameDescriptor* const desc = (AVDRMFrameDescriptor*)frame->data[0]; ++ cma_drmprime_buf_t *const cdb = (cma_drmprime_buf_t *)cma_buf_pool_alloc_buf(cbp, desc->objects[0].size); + -+typedef struct pic_ctx_subpic_s { -+ picture_t * subpic; -+ int x, y; -+ int alpha; -+} pic_ctx_subpic_t; -+ -+ -+#define CTX_BUFS_MAX 4 -+typedef struct pic_ctx_mmal_s { -+ picture_context_t cmn; // PARENT: Common els at start ++ if (cdb == NULL) ++ return NULL; + -+ cma_buf_t * cb; ++ cdb->cb.type = CMA_BUF_TYPE_DRMPRIME; ++ cdb->cb.fd = desc->objects[0].fd; + -+ unsigned int buf_count; -+ MMAL_BUFFER_HEADER_T * bufs[CTX_BUFS_MAX]; ++ cdb->desc = desc; ++ cdb->avbuf = av_buffer_ref(frame->buf[0]); ++ return &cdb->cb; ++} + -+} pic_ctx_mmal_t; + -+const char * str_fourcc(char * const buf, const unsigned int fcc); +--- /dev/null ++++ b/modules/hw/mmal/mmal_cma_drmprime.h +@@ -0,0 +1,9 @@ ++#ifndef VLC_HW_MMAL_MMAL_CMA_DRMPRIME_H_ ++#define VLC_HW_MMAL_MMAL_CMA_DRMPRIME_H_ + -+MMAL_FOURCC_T vlc_to_mmal_video_fourcc(const video_frame_format_t * const vf_vlc); -+MMAL_FOURCC_T vlc_to_mmal_color_space(const video_color_space_t vlc_cs); -+void hw_mmal_vlc_fmt_to_mmal_fmt(MMAL_ES_FORMAT_T *const es_fmt, const video_frame_format_t * const vf_vlc); -+// Returns true if fmt_changed -+// frame_rate ignored for compare, but is set if something else is updated -+bool hw_mmal_vlc_pic_to_mmal_fmt_update(MMAL_ES_FORMAT_T *const es_fmt, const picture_t * const pic); ++struct AVFrame; ++cma_buf_pool_t * cma_drmprime_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name); ++cma_buf_t * cma_drmprime_pool_alloc_buf(cma_buf_pool_t * const cbp, struct AVFrame * frame); + -+// Copy pic contents into an existing buffer -+int hw_mmal_copy_pic_to_buf(void * const buf_data, uint32_t * const pLength, -+ const MMAL_ES_FORMAT_T * const fmt, const picture_t * const pic); ++#endif // VLC_MMAL_MMAL_CMA_H_ + -+hw_mmal_port_pool_ref_t * hw_mmal_port_pool_ref_create(MMAL_PORT_T * const port, -+ const unsigned int headers, const uint32_t payload_size); -+void hw_mmal_port_pool_ref_release(hw_mmal_port_pool_ref_t * const ppr, const bool in_cb); -+bool hw_mmal_port_pool_ref_recycle(hw_mmal_port_pool_ref_t * const ppr, MMAL_BUFFER_HEADER_T * const buf); -+MMAL_STATUS_T hw_mmal_port_pool_ref_fill(hw_mmal_port_pool_ref_t * const ppr); -+static inline void hw_mmal_port_pool_ref_acquire(hw_mmal_port_pool_ref_t * const ppr) -+{ -+ atomic_fetch_add(&ppr->refs, 1); -+} -+MMAL_STATUS_T hw_mmal_opaque_output(vlc_object_t * const obj, -+ hw_mmal_port_pool_ref_t ** pppr, -+ MMAL_PORT_T * const port, -+ const unsigned int extra_buffers, MMAL_PORT_BH_CB_T callback); +--- /dev/null ++++ b/modules/hw/mmal/mmal_cma_int.h +@@ -0,0 +1,58 @@ ++#ifndef VLC_HW_MMAL_MMAL_CMA_INT_H_ ++#define VLC_HW_MMAL_MMAL_CMA_INT_H_ + -+static inline int hw_mmal_pic_has_sub_bufs(picture_t * const pic) -+{ -+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; -+ return ctx->buf_count > 1; -+} ++#include + -+static inline void hw_mmal_pic_sub_buf_add(picture_t * const pic, MMAL_BUFFER_HEADER_T * const sub) -+{ -+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; ++typedef void * cma_pool_alloc_fn(void * usr_v, size_t size); ++typedef void cma_pool_free_fn(void * usr_v, void * buffer_v, size_t size); ++typedef void cma_pool_on_delete_fn(void * usr_v); ++typedef void cma_pool_on_put_fn(void * buffer_v); + -+ if (ctx->buf_count >= CTX_BUFS_MAX) { -+ mmal_buffer_header_release(sub); -+ return; -+ } ++// Pool structure ++// Ref count is held by pool owner and pool els that have been got ++// Els in the pool do not count towards its ref count ++struct cma_pool_fixed_s; ++typedef struct cma_pool_fixed_s cma_pool_fixed_t; + -+ ctx->bufs[ctx->buf_count++] = sub; -+} ++cma_pool_fixed_t* ++cma_pool_fixed_new(const unsigned int pool_size, ++ const int flight_size, ++ void * const alloc_v, ++ cma_pool_alloc_fn * const alloc_fn, cma_pool_free_fn * const free_fn, ++ cma_pool_on_put_fn * const on_put_fn, cma_pool_on_delete_fn * const on_delete_fn, ++ const char * const name); + -+static inline MMAL_BUFFER_HEADER_T * hw_mmal_pic_sub_buf_get(picture_t * const pic, const unsigned int n) -+{ -+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; ++typedef enum cma_buf_type_e { ++ CMA_BUF_TYPE_NONE = 0, ++ CMA_BUF_TYPE_CMA, ++ CMA_BUF_TYPE_VCSM, ++ CMA_BUF_TYPE_DRMPRIME, ++} cma_buf_type_t; + -+ return n + 1 > ctx->buf_count ? NULL : ctx->bufs[n + 1]; -+} ++struct cma_buf_pool_s { ++ cma_pool_fixed_t * pool; ++ cma_buf_type_t buf_type; + -+static inline bool hw_mmal_chroma_is_mmal(const vlc_fourcc_t chroma) -+{ -+ return -+ chroma == VLC_CODEC_MMAL_OPAQUE || -+ chroma == VLC_CODEC_MMAL_ZC_SAND8 || -+ chroma == VLC_CODEC_MMAL_ZC_SAND10 || -+ chroma == VLC_CODEC_MMAL_ZC_SAND30 || -+ chroma == VLC_CODEC_MMAL_ZC_I420 || -+ chroma == VLC_CODEC_MMAL_ZC_RGB32; -+} ++ bool all_in_flight; ++ size_t alloc_n; ++ size_t alloc_size; ++}; + -+static inline bool hw_mmal_pic_is_mmal(const picture_t * const pic) -+{ -+ return hw_mmal_chroma_is_mmal(pic->format.i_chroma); -+} ++typedef struct cma_buf_s { ++ atomic_int ref_count; ++ cma_buf_type_t type; ++ struct cma_buf_pool_s * cbp; ++ bool in_flight; ++ size_t size; ++ unsigned int vcsm_h; // VCSM handle from initial alloc ++ unsigned int vc_h; // VC handle for ZC mmal buffers ++ unsigned int vc_addr; // VC addr - unused by us but wanted by FFmpeg ++ int fd; // dmabuf handle for GL ++ void * mmap; // ARM mapped address ++ picture_context_t *ctx2; ++} cma_buf_t; + -+picture_context_t * hw_mmal_pic_ctx_copy(picture_context_t * pic_ctx_cmn); -+void hw_mmal_pic_ctx_destroy(picture_context_t * pic_ctx_cmn); -+picture_context_t * hw_mmal_gen_context( -+ MMAL_BUFFER_HEADER_T * buf, hw_mmal_port_pool_ref_t * const ppr); ++void cma_pool_delete(cma_buf_t * const cb); + -+int hw_mmal_get_gpu_mem(void); ++#endif + +--- /dev/null ++++ b/modules/hw/mmal/mmal_cma_pic.h +@@ -0,0 +1,49 @@ ++#include ++#include ++#include "mmal_cma_int.h" + -+static inline MMAL_STATUS_T port_parameter_set_uint32(MMAL_PORT_T * port, uint32_t id, uint32_t val) ++static inline int ++cma_buf_fd(const cma_buf_t *const cb) +{ -+ const MMAL_PARAMETER_UINT32_T param = { -+ .hdr = {.id = id, .size = sizeof(MMAL_PARAMETER_UINT32_T)}, -+ .value = val -+ }; -+ return mmal_port_parameter_set(port, ¶m.hdr); ++ return cb->fd; +} + -+static inline MMAL_STATUS_T port_parameter_set_bool(MMAL_PORT_T * const port, const uint32_t id, const bool val) ++static inline void * ++cma_buf_addr(const cma_buf_t *const cb) +{ -+ const MMAL_PARAMETER_BOOLEAN_T param = { -+ .hdr = {.id = id, .size = sizeof(MMAL_PARAMETER_BOOLEAN_T)}, -+ .enable = val -+ }; -+ return mmal_port_parameter_set(port, ¶m.hdr); ++ return cb->mmap; +} + -+static inline MMAL_STATUS_T port_send_replicated(MMAL_PORT_T * const port, MMAL_POOL_T * const rep_pool, -+ MMAL_BUFFER_HEADER_T * const src_buf, -+ const uint64_t seq) -+{ -+ MMAL_STATUS_T err; -+ MMAL_BUFFER_HEADER_T *const rep_buf = mmal_queue_wait(rep_pool->queue); -+ -+ if (rep_buf == NULL) -+ return MMAL_ENOSPC; -+ -+ if ((err = mmal_buffer_header_replicate(rep_buf, src_buf)) != MMAL_SUCCESS) -+ return err; ++#define CTX_BUFS_MAX 4 ++struct MMAL_BUFFER_HEADER_T; + -+ rep_buf->pts = seq; ++typedef struct pic_ctx_mmal_s { ++ picture_context_t cmn; // PARENT: Common els at start + -+ if ((err = mmal_port_send_buffer(port, rep_buf)) != MMAL_SUCCESS) -+ { -+ mmal_buffer_header_release(rep_buf); -+ return err; -+ } ++ cma_buf_t * cb; + -+ return MMAL_SUCCESS; -+} ++ unsigned int buf_count; ++ struct MMAL_BUFFER_HEADER_T * bufs[CTX_BUFS_MAX]; + ++} pic_ctx_mmal_t; + -+static inline void pic_to_buf_copy_props(MMAL_BUFFER_HEADER_T * const buf, const picture_t * const pic) ++static inline bool ++is_cma_buf_pic_chroma(const uint32_t chroma) +{ -+ if (!pic->b_progressive) -+ { -+ buf->flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; -+ buf->type->video.flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; -+ } -+ else -+ { -+ buf->flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; -+ buf->type->video.flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; -+ } -+ if (pic->b_top_field_first) -+ { -+ buf->flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; -+ buf->type->video.flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; -+ } -+ else -+ { -+ buf->flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; -+ buf->type->video.flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; -+ } -+ buf->pts = pic->date != VLC_TICK_INVALID ? pic->date : MMAL_TIME_UNKNOWN; -+ buf->dts = buf->pts; ++ return chroma == VLC_CODEC_MMAL_ZC_RGB32 || ++ chroma == VLC_CODEC_MMAL_ZC_SAND8 || ++ chroma == VLC_CODEC_MMAL_ZC_SAND10 || ++ chroma == VLC_CODEC_MMAL_ZC_SAND30 || ++ chroma == VLC_CODEC_MMAL_ZC_I420; +} + -+static inline void buf_to_pic_copy_props(picture_t * const pic, const MMAL_BUFFER_HEADER_T * const buf) ++// Returns a pointer to the cma_buf attached to the pic ++// Just a pointer - doesn't add a ref ++static inline cma_buf_t * ++cma_buf_pic_get(picture_t * const pic) +{ -+ // Contrary to docn the interlace & tff flags turn up in the header flags rather than the -+ // video specific flags (which appear to be currently unused). -+ pic->b_progressive = (buf->flags & MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED) == 0; -+ pic->b_top_field_first = (buf->flags & MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST) != 0; -+ -+ pic->date = buf->pts != MMAL_TIME_UNKNOWN ? buf->pts : -+ buf->dts != MMAL_TIME_UNKNOWN ? buf->dts : -+ VLC_TICK_INVALID; ++ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; ++ return !is_cma_buf_pic_chroma(pic->format.i_chroma) || ctx == NULL ? 0 : ctx->cb; +} + -+MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_copied(const picture_t *const pic, -+ MMAL_POOL_T * const rep_pool, -+ MMAL_PORT_T * const port, -+ cma_buf_pool_t * const cbp); + -+MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_replicated(const picture_t *const pic, MMAL_POOL_T * const rep_pool); +--- /dev/null ++++ b/modules/hw/mmal/mmal_piccpy_neon.S +@@ -0,0 +1,95 @@ ++#include "../../arm_neon/asm.S" ++ .align 16 ++ .arch armv7-a ++ .syntax unified ++#if HAVE_AS_FPU_DIRECTIVE ++ .fpu neon-vfpv4 ++#endif + -+struct vzc_pool_ctl_s; -+typedef struct vzc_pool_ctl_s vzc_pool_ctl_t; ++// Copy pix + -+// At the moment we cope with any mono-planar RGBA thing -+// We could cope with many other things but they currently don't occur -+extern const vlc_fourcc_t hw_mmal_vzc_subpicture_chromas[]; -+static inline bool hw_mmal_vzc_subpic_fmt_valid(const video_frame_format_t * const vf_vlc) -+{ -+ const vlc_fourcc_t vfcc_src = vf_vlc->i_chroma; -+ for (const vlc_fourcc_t * p = hw_mmal_vzc_subpicture_chromas; *p != 0; ++p) -+ if (*p == vfcc_src) -+ return true; -+ -+ return false; -+} -+ -+bool hw_mmal_vzc_buf_set_format(MMAL_BUFFER_HEADER_T * const buf, MMAL_ES_FORMAT_T * const es_fmt); -+MMAL_DISPLAYREGION_T * hw_mmal_vzc_buf_region(MMAL_BUFFER_HEADER_T * const buf); -+void hw_mmal_vzc_buf_scale_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const MMAL_RECT_T * const scale_rect, const MMAL_DISPLAYTRANSFORM_T scale_transform); -+void hw_mmal_vzc_buf_get_wh(MMAL_BUFFER_HEADER_T * const buf, int * const pW, int * const pH); -+unsigned int hw_mmal_vzc_buf_seq(MMAL_BUFFER_HEADER_T * const buf); -+MMAL_BUFFER_HEADER_T * hw_mmal_vzc_buf_from_pic(vzc_pool_ctl_t * const pc, picture_t * const pic, -+ const MMAL_RECT_T dst_pic_rect, -+ const int x_offset, const int y_offset, -+ const unsigned int alpha, const bool is_first); -+void hw_mmal_vzc_buf_frame_size(MMAL_BUFFER_HEADER_T * const buf, -+ uint32_t * const pWidth, uint32_t * const pHeight); ++.macro piccpy_to_8, bit_depth ++ subs r2, #128 ++ vpush {q4-q7} ++ blt 2f ++1: ++ vldm r1!, {q0-q7} ++ subs r2, #128 ++ vqrshrn.u16 d0, q0, #\bit_depth - 8 ++ vqrshrn.u16 d1, q1, #\bit_depth - 8 ++ vqrshrn.u16 d2, q2, #\bit_depth - 8 ++ vqrshrn.u16 d3, q3, #\bit_depth - 8 ++ vldm r1!, {q8-q15} ++ vqrshrn.u16 d4, q4, #\bit_depth - 8 ++ vqrshrn.u16 d5, q5, #\bit_depth - 8 ++ vqrshrn.u16 d6, q6, #\bit_depth - 8 ++ vqrshrn.u16 d7, q7, #\bit_depth - 8 ++ vqrshrn.u16 d8, q8, #\bit_depth - 8 ++ vqrshrn.u16 d9, q9, #\bit_depth - 8 ++ vqrshrn.u16 d10, q10, #\bit_depth - 8 ++ vqrshrn.u16 d11, q11, #\bit_depth - 8 ++ vqrshrn.u16 d12, q12, #\bit_depth - 8 ++ vqrshrn.u16 d13, q13, #\bit_depth - 8 ++ vqrshrn.u16 d14, q14, #\bit_depth - 8 ++ vqrshrn.u16 d15, q15, #\bit_depth - 8 ++ vstm r0!, {q0-q7} ++ bge 1b ++2: ++ adds r2, #64 ++ blt 1f + -+void hw_mmal_vzc_pool_flush(vzc_pool_ctl_t * const pc); -+void hw_mmal_vzc_pool_release(vzc_pool_ctl_t * const pc); -+void hw_mmal_vzc_pool_ref(vzc_pool_ctl_t * const pc); -+vzc_pool_ctl_t * hw_mmal_vzc_pool_new(void); ++ vldm r1!, {q0-q7} ++ vqrshrn.u16 d0, q0, #\bit_depth - 8 ++ vqrshrn.u16 d1, q1, #\bit_depth - 8 ++ vqrshrn.u16 d2, q2, #\bit_depth - 8 ++ vqrshrn.u16 d3, q3, #\bit_depth - 8 ++ vqrshrn.u16 d4, q4, #\bit_depth - 8 ++ vqrshrn.u16 d5, q5, #\bit_depth - 8 ++ vqrshrn.u16 d6, q6, #\bit_depth - 8 ++ vqrshrn.u16 d7, q7, #\bit_depth - 8 ++ vstm r0!, {q0-q3} ++1: ++ adds r2, #32 ++ blt 1f + ++ vldm r1!, {q0-q3} ++ vqrshrn.u16 d0, q0, #\bit_depth - 8 ++ vqrshrn.u16 d1, q1, #\bit_depth - 8 ++ vqrshrn.u16 d2, q2, #\bit_depth - 8 ++ vqrshrn.u16 d3, q3, #\bit_depth - 8 ++ vstm r0!, {q0-q1} ++1: ++ adds r2, #16 ++ blt 1f + -+static inline MMAL_RECT_T vis_mmal_rect(const video_format_t * const fmt) -+{ -+ return (MMAL_RECT_T){ -+ .x = fmt->i_x_offset, -+ .y = fmt->i_y_offset, -+ .width = fmt->i_visible_width, -+ .height = fmt->i_visible_height -+ }; -+} ++ vldm r1!, {q0-q1} ++ vqrshrn.u16 d0, q0, #\bit_depth - 8 ++ vqrshrn.u16 d1, q1, #\bit_depth - 8 ++ vstm r0!, {q0} ++1: ++ adds r2, #8 ++ blt 1f + -+int cma_pic_set_data(picture_t * const pic, -+ const MMAL_ES_FORMAT_T * const mm_esfmt, -+ const MMAL_BUFFER_HEADER_T * const buf); ++ vldm r1!, {q0} ++ vqrshrn.u16 d0, q0, #\bit_depth - 8 ++ vstr d0, [r0] ++ add r0, #8 ++1: ++ adds r2, #4 ++ blt 1f + -+// Attaches cma buf to pic -+// Marks in_flight if not all_in_flight anyway -+int cma_buf_pic_attach(cma_buf_t * const cb, picture_t * const pic); -+// Returns a pointer to the cma_buf attached to the pic -+// Just a pointer - doesn't add a ref -+cma_buf_t * cma_buf_pic_get(picture_t * const pic); ++ vldr d0, [r1] ++ vqrshrn.u16 d0, q0, #\bit_depth - 8 ++ vstr s0, [r0] ++1: ++ vpop {q4-q7} ++ bx lr ++.endm + -+static inline bool is_cma_buf_pic_chroma(const uint32_t chroma) -+{ -+ return chroma == VLC_CODEC_MMAL_ZC_RGB32 || -+ chroma == VLC_CODEC_MMAL_ZC_SAND8 || -+ chroma == VLC_CODEC_MMAL_ZC_SAND10 || -+ chroma == VLC_CODEC_MMAL_ZC_SAND30 || -+ chroma == VLC_CODEC_MMAL_ZC_I420; -+} + ++@ [r0] Dest ++@ [r1] Src ++@ r2 Pels ++function mmal_piccpy_10_to_8_neon ++ piccpy_to_8 10 + -+int rpi_get_model_type(void); -+bool rpi_is_model_pi4(void); -+bool rpi_is_fkms_active(void); +--- a/modules/hw/mmal/mmal_picture.c ++++ b/modules/hw/mmal/mmal_picture.c +@@ -21,25 +21,1574 @@ + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + ++// We would really like to use vlc_thread.h but the detach thread stuff can't be ++// used here :-( ++#include + -+typedef enum vcsm_init_type_e { -+ VCSM_INIT_NONE = 0, -+ VCSM_INIT_LEGACY, -+ VCSM_INIT_CMA -+} vcsm_init_type_t; ++#include ++#include ++#include + -+vcsm_init_type_t cma_vcsm_init(void); -+void cma_vcsm_exit(const vcsm_init_type_t init_mode); -+vcsm_init_type_t cma_vcsm_type(void); -+const char * cma_vcsm_init_str(const vcsm_init_type_t init_mode); + #include ++#include + #include + ++#pragma GCC diagnostic push ++#pragma GCC diagnostic ignored "-Wbad-function-cast" ++#include ++#pragma GCC diagnostic pop + #include ++#include ++#include ++#include ++#include -- MMAL_BUFFER_HEADER_T *buffer; -- bool displayed; --}; -+#define VOUT_DISPLAY_CHANGE_MMAL_BASE 1024 -+#define VOUT_DISPLAY_CHANGE_MMAL_HIDE (VOUT_DISPLAY_CHANGE_MMAL_BASE + 0) - --int mmal_picture_lock(picture_t *picture); -+#define MMAL_COMPONENT_DEFAULT_RESIZER "vc.ril.resize" -+#define MMAL_COMPONENT_ISP_RESIZER "vc.ril.isp" -+#define MMAL_COMPONENT_HVS "vc.ril.hvs" - - #endif ---- /dev/null -+++ b/modules/hw/mmal/rpi_prof.h -@@ -0,0 +1,110 @@ -+#ifndef RPI_PROFILE_H -+#define RPI_PROFILE_H -+ -+#include -+#include -+ -+#ifndef RPI_PROFILE -+#define RPI_PROFILE 0 -+#endif ++#include "mmal_cma.h" + #include "mmal_picture.h" ++#include "transform_ops.h" + -+#if RPI_PROFILE ++#define TRACE_TRANSFORMS 0 + -+#include "v7_pmu.h" ++#define UINT64_SIZE(s) (((s) + sizeof(uint64_t) - 1)/sizeof(uint64_t)) + -+#ifdef RPI_PROC_ALLOC -+#define X volatile -+#define Z =0 -+#else -+#define X extern volatile -+#define Z -+#endif ++static inline char safe_char(const unsigned int c0) ++{ ++ const unsigned int c = c0 & 0xff; ++ return c > ' ' && c < 0x7f ? c : '.'; ++} + -+X uint64_t av_rpi_prof0_cycles Z; -+X unsigned int av_rpi_prof0_cnt Z; -+#define RPI_prof0_MAX_DURATION 100000 ++const char * str_fourcc(char * const buf, const unsigned int fcc) ++{ ++ if (fcc == 0) ++ return "----"; ++ buf[0] = safe_char(fcc >> 0); ++ buf[1] = safe_char(fcc >> 8); ++ buf[2] = safe_char(fcc >> 16); ++ buf[3] = safe_char(fcc >> 24); ++ buf[4] = 0; ++ return buf; ++} + -+X uint64_t av_rpi_prof1_cycles Z; -+X unsigned int av_rpi_prof1_cnt Z; -+#define RPI_prof1_MAX_DURATION 100000 ++// WB + Inv ++static inline void flush_range(void * const start, const size_t len) ++{ ++ uint64_t buf[UINT64_SIZE(sizeof(struct vcsm_user_clean_invalid2_s) + sizeof(struct vcsm_user_clean_invalid2_block_s))]; ++ struct vcsm_user_clean_invalid2_s * const b = (struct vcsm_user_clean_invalid2_s *)buf; + -+X uint64_t av_rpi_prof2_cycles Z; -+X unsigned int av_rpi_prof2_cnt Z; -+#define RPI_prof2_MAX_DURATION 10000 ++ *b = (struct vcsm_user_clean_invalid2_s){ ++ .op_count = 1 ++ }; + -+X uint64_t av_rpi_prof_n_cycles[128]; -+X unsigned int av_rpi_prof_n_cnt[128]; -+#define RPI_prof_n_MAX_DURATION 10000 ++ b->s[0] = (struct vcsm_user_clean_invalid2_block_s){ ++ .invalidate_mode = 3, // wb + invalidate ++ .block_count = 1, ++ .start_address = start, // Rely on clean inv to fix up align & size boundries ++ .block_size = len, ++ .inter_block_stride = 0 ++ }; + ++ vcsm_clean_invalid2(b); ++} + -+#undef X -+#undef Z ++MMAL_FOURCC_T vlc_to_mmal_color_space(const video_color_space_t vlc_cs) ++{ ++ switch (vlc_cs) ++ { ++ case COLOR_SPACE_BT601: ++ return MMAL_COLOR_SPACE_ITUR_BT601; ++ case COLOR_SPACE_BT709: ++ return MMAL_COLOR_SPACE_ITUR_BT709; ++ default: ++ break; ++ } ++ return MMAL_COLOR_SPACE_UNKNOWN; ++} + -+#define PROFILE_INIT()\ -+do {\ -+ enable_pmu();\ -+ enable_ccnt();\ -+} while (0) -+ -+#define PROFILE_START()\ -+do {\ -+ volatile uint32_t perf_1 = read_ccnt();\ -+ volatile uint32_t perf_2 -+ -+ -+#define PROFILE_ACC(x)\ -+ perf_2 = read_ccnt();\ -+ {\ -+ const uint32_t duration = perf_2 - perf_1;\ -+ if (duration < RPI_##x##_MAX_DURATION)\ -+ {\ -+ av_rpi_##x##_cycles += duration;\ -+ av_rpi_##x##_cnt += 1;\ -+ }\ -+ }\ -+} while(0) -+ -+ -+#define PROFILE_ACC_N(n)\ -+ if ((n) >= 0) {\ -+ perf_2 = read_ccnt();\ -+ {\ -+ const uint32_t duration = perf_2 - perf_1;\ -+ if (duration < RPI_prof_n_MAX_DURATION)\ -+ {\ -+ av_rpi_prof_n_cycles[n] += duration;\ -+ av_rpi_prof_n_cnt[n] += 1;\ -+ }\ -+ }\ -+ }\ -+} while(0) -+ -+#define PROFILE_PRINTF(x)\ -+ printf("%-20s cycles=%14" PRIu64 "; cnt=%8u; avg=%5" PRIu64 "\n", #x, av_rpi_##x##_cycles, av_rpi_##x##_cnt,\ -+ av_rpi_##x##_cnt == 0 ? (uint64_t)0 : av_rpi_##x##_cycles / (uint64_t)av_rpi_##x##_cnt) ++MMAL_FOURCC_T vlc_to_mmal_video_fourcc(const video_frame_format_t * const vf_vlc) ++{ ++ switch (vf_vlc->i_chroma) { ++ case VLC_CODEC_MMAL_ZC_RGB32: ++ case VLC_CODEC_RGB32: ++ { ++ // VLC RGB32 aka RV32 means we have to look at the mask values ++ const uint32_t r = vf_vlc->i_rmask; ++ const uint32_t g = vf_vlc->i_gmask; ++ const uint32_t b = vf_vlc->i_bmask; ++ if (r == 0xff0000 && g == 0xff00 && b == 0xff) ++ return MMAL_ENCODING_BGRA; ++ if (r == 0xff && g == 0xff00 && b == 0xff0000) ++ return MMAL_ENCODING_RGBA; ++ if (r == 0xff000000 && g == 0xff0000 && b == 0xff00) ++ return MMAL_ENCODING_ABGR; ++ if (r == 0xff00 && g == 0xff0000 && b == 0xff000000) ++ return MMAL_ENCODING_ARGB; ++ break; ++ } ++ case VLC_CODEC_RGB16: ++ { ++ // VLC RGB16 aka RV16 means we have to look at the mask values ++ const uint32_t r = vf_vlc->i_rmask; ++ const uint32_t g = vf_vlc->i_gmask; ++ const uint32_t b = vf_vlc->i_bmask; ++ if (r == 0xf800 && g == 0x7e0 && b == 0x1f) ++ return MMAL_ENCODING_RGB16; ++ break; ++ } ++ case VLC_CODEC_I420: ++ case VLC_CODEC_MMAL_ZC_I420: ++ return MMAL_ENCODING_I420; ++ case VLC_CODEC_RGBA: ++ return MMAL_ENCODING_RGBA; ++ case VLC_CODEC_BGRA: ++ return MMAL_ENCODING_BGRA; ++ case VLC_CODEC_ARGB: ++ return MMAL_ENCODING_ARGB; ++ // VLC_CODEC_ABGR does not exist in VLC ++ case VLC_CODEC_MMAL_OPAQUE: ++ return MMAL_ENCODING_OPAQUE; ++ case VLC_CODEC_MMAL_ZC_SAND8: ++ return MMAL_ENCODING_YUVUV128; ++ case VLC_CODEC_MMAL_ZC_SAND10: ++ return MMAL_ENCODING_YUVUV64_10; ++ case VLC_CODEC_MMAL_ZC_SAND30: ++ return MMAL_ENCODING_YUV10_COL; ++ default: ++ break; ++ } ++ return 0; ++} + -+#define PROFILE_PRINTF_N(n)\ -+ printf("prof[%d] cycles=%14" PRIu64 "; cnt=%8u; avg=%5" PRIu64 "\n", (n), av_rpi_prof_n_cycles[n], av_rpi_prof_n_cnt[n],\ -+ av_rpi_prof_n_cnt[n] == 0 ? (uint64_t)0 : av_rpi_prof_n_cycles[n] / (uint64_t)av_rpi_prof_n_cnt[n]) ++static void vlc_fmt_to_video_format(MMAL_VIDEO_FORMAT_T *const vf_mmal, const video_frame_format_t * const vf_vlc) ++{ ++ const unsigned int wmask = (vf_vlc->i_chroma == VLC_CODEC_MMAL_ZC_I420 || ++ vf_vlc->i_chroma == VLC_CODEC_I420) ? 31 : 15; + -+#define PROFILE_CLEAR_N(n) \ -+do {\ -+ av_rpi_prof_n_cycles[n] = 0;\ -+ av_rpi_prof_n_cnt[n] = 0;\ -+} while(0) ++ vf_mmal->width = (vf_vlc->i_width + wmask) & ~wmask; ++ vf_mmal->height = (vf_vlc->i_height + 15) & ~15; ++ vf_mmal->crop.x = vf_vlc->i_x_offset; ++ vf_mmal->crop.y = vf_vlc->i_y_offset; ++ vf_mmal->crop.width = vf_vlc->i_visible_width; ++ vf_mmal->crop.height = vf_vlc->i_visible_height; ++ if (vf_vlc->i_sar_num == 0 || vf_vlc->i_sar_den == 0) { ++ vf_mmal->par.num = 1; ++ vf_mmal->par.den = 1; ++ } else { ++ vf_mmal->par.num = vf_vlc->i_sar_num; ++ vf_mmal->par.den = vf_vlc->i_sar_den; ++ } ++ vf_mmal->frame_rate.num = vf_vlc->i_frame_rate; ++ vf_mmal->frame_rate.den = vf_vlc->i_frame_rate_base; ++ vf_mmal->color_space = vlc_to_mmal_color_space(vf_vlc->space); ++} + -+#else + -+// No profile -+#define PROFILE_INIT() -+#define PROFILE_START() -+#define PROFILE_ACC(x) -+#define PROFILE_ACC_N(x) -+#define PROFILE_PRINTF(x) -+#define PROFILE_PRINTF_N(x) -+#define PROFILE_CLEAR_N(n) ++void hw_mmal_vlc_fmt_to_mmal_fmt(MMAL_ES_FORMAT_T *const es_fmt, const video_frame_format_t * const vf_vlc) ++{ ++ vlc_fmt_to_video_format(&es_fmt->es->video, vf_vlc); ++} + -+#endif ++bool hw_mmal_vlc_pic_to_mmal_fmt_update(MMAL_ES_FORMAT_T *const es_fmt, const picture_t * const pic) ++{ ++ MMAL_VIDEO_FORMAT_T vf_new_ss; ++ MMAL_VIDEO_FORMAT_T *const vf_old = &es_fmt->es->video; ++ MMAL_VIDEO_FORMAT_T *const vf_new = &vf_new_ss; + -+#endif ++ vlc_fmt_to_video_format(vf_new, &pic->format); + ---- /dev/null -+++ b/modules/hw/mmal/subpic.c -@@ -0,0 +1,257 @@ -+/***************************************************************************** -+ * mmal.c: MMAL-based decoder plugin for Raspberry Pi -+ ***************************************************************************** -+ * Authors: jc@kynesim.co.uk -+ * -+ * This program is free software; you can redistribute it and/or modify it -+ * under the terms of the GNU Lesser General Public License as published by -+ * the Free Software Foundation; either version 2.1 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public License -+ * along with this program; if not, write to the Free Software Foundation, -+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. -+ *****************************************************************************/ ++ // If we have a format that might have come from ffmpeg then rework for ++ // a better guess as to layout. All sand stuff is "special" with regards to ++ // width/height vs real layout so leave as is if that ++ if ((pic->format.i_chroma == VLC_CODEC_MMAL_ZC_I420 || ++ pic->format.i_chroma == VLC_CODEC_MMAL_ZC_RGB32) && ++ pic->p[0].i_pixel_pitch != 0) ++ { ++ // Now overwrite width/height with a better guess as to actual layout info ++ vf_new->height = pic->p[0].i_lines; ++ vf_new->width = pic->p[0].i_pitch / pic->p[0].i_pixel_pitch; ++ } + -+#ifdef HAVE_CONFIG_H -+#include "config.h" ++ if ( ++ vf_new->width != vf_old->width || ++ vf_new->height != vf_old->height || ++ vf_new->crop.x != vf_old->crop.x || ++ vf_new->crop.y != vf_old->crop.y || ++ vf_new->crop.width != vf_old->crop.width || ++ vf_new->crop.height != vf_old->crop.height || ++ vf_new->par.num != vf_old->par.num || ++ vf_new->par.den != vf_old->par.den || ++ // Frame rate ignored ++ vf_new->color_space != vf_old->color_space) ++ { ++#if 0 ++ char dbuf0[5], dbuf1[5]; ++ printf("%dx%d (%d,%d %dx%d) par:%d/%d %s -> %dx%d (%d,%d %dx%d) par:%d/%d %s\n", ++ vf_old->width , ++ vf_old->height , ++ vf_old->crop.x , ++ vf_old->crop.y , ++ vf_old->crop.width , ++ vf_old->crop.height , ++ vf_old->par.num , ++ vf_old->par.den , ++ str_fourcc(dbuf0, vf_old->color_space) , ++ vf_new->width , ++ vf_new->height , ++ vf_new->crop.x , ++ vf_new->crop.y , ++ vf_new->crop.width , ++ vf_new->crop.height , ++ vf_new->par.num , ++ vf_new->par.den , ++ str_fourcc(dbuf1, vf_new->color_space) ); +#endif ++ *vf_old = *vf_new; ++ return true; ++ } ++ return false; ++} + -+#include -+ -+#include -+#include -+#include -+#include -+#include + -+#include -+#include -+#include -+#include ++hw_mmal_port_pool_ref_t * hw_mmal_port_pool_ref_create(MMAL_PORT_T * const port, ++ const unsigned int headers, const uint32_t payload_size) ++{ ++ hw_mmal_port_pool_ref_t * ppr = calloc(1, sizeof(hw_mmal_port_pool_ref_t)); ++ if (ppr == NULL) ++ return NULL; + -+#include "mmal_picture.h" -+#include "subpic.h" ++ if ((ppr->pool = mmal_port_pool_create(port, headers, payload_size)) == NULL) ++ goto fail; + ++ ppr->port = port; ++ atomic_store(&ppr->refs, 1); ++ return ppr; + -+#define TRACE_ALL 0 ++fail: ++ free(ppr); ++ return NULL; ++} + -+static inline bool cmp_rect(const MMAL_RECT_T * const a, const MMAL_RECT_T * const b) ++static void do_detached(void *(*fn)(void *), void * v) +{ -+ return a->x == b->x && a->y == b->y && a->width == b->width && a->height == b->height; ++ pthread_t dothread; ++ pthread_create(&dothread, NULL, fn, v); ++ pthread_detach(dothread); +} + -+void hw_mmal_subpic_flush(vlc_object_t * const p_filter, subpic_reg_stash_t * const sub) ++// Destroy a ppr - aranged s.t. it has the correct prototype for a pthread ++static void * kill_ppr(void * v) +{ -+ VLC_UNUSED(p_filter); -+ if (sub->port != NULL && sub->port->is_enabled) -+ mmal_port_disable(sub->port); -+ sub->seq = 0; ++ hw_mmal_port_pool_ref_t * const ppr = v; ++ if (ppr->port->is_enabled) ++ mmal_port_disable(ppr->port); // Avoid annoyed messages from MMAL when we kill the pool ++ mmal_port_pool_destroy(ppr->port, ppr->pool); ++ free(ppr); ++ return NULL; +} + -+void hw_mmal_subpic_close(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe) ++void hw_mmal_port_pool_ref_release(hw_mmal_port_pool_ref_t * const ppr, const bool in_cb) +{ -+ hw_mmal_subpic_flush(p_filter, spe); ++ if (ppr == NULL) ++ return; ++ if (atomic_fetch_sub(&ppr->refs, 1) != 1) ++ return; ++ if (in_cb) ++ do_detached(kill_ppr, ppr); ++ else ++ kill_ppr(ppr); ++} + -+ if (spe->pool != NULL) -+ mmal_pool_destroy(spe->pool); ++// Put buffer in port if possible - if not then release to pool ++// Returns true if sent, false if recycled ++bool hw_mmal_port_pool_ref_recycle(hw_mmal_port_pool_ref_t * const ppr, MMAL_BUFFER_HEADER_T * const buf) ++{ ++ mmal_buffer_header_reset(buf); ++ buf->user_data = NULL; + +-int mmal_picture_lock(picture_t *picture) ++ if (mmal_port_send_buffer(ppr->port, buf) == MMAL_SUCCESS) ++ return true; ++ mmal_buffer_header_release(buf); ++ return false; ++} + -+ // Zap to avoid any accidental reuse -+ *spe = (subpic_reg_stash_t){NULL}; ++MMAL_STATUS_T hw_mmal_port_pool_ref_fill(hw_mmal_port_pool_ref_t * const ppr) ++{ ++ MMAL_BUFFER_HEADER_T * buf; ++ MMAL_STATUS_T err = MMAL_SUCCESS; ++ ++ while ((buf = mmal_queue_get(ppr->pool->queue)) != NULL) { ++ if ((err = mmal_port_send_buffer(ppr->port, buf)) != MMAL_SUCCESS) ++ { ++ mmal_queue_put_back(ppr->pool->queue, buf); ++ break; ++ } ++ } ++ return err; +} + -+MMAL_STATUS_T hw_mmal_subpic_open(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, MMAL_PORT_T * const port, -+ const int display_id, const unsigned int layer) ++ ++MMAL_STATUS_T hw_mmal_opaque_output(vlc_object_t * const obj, ++ hw_mmal_port_pool_ref_t ** pppr, ++ MMAL_PORT_T * const port, ++ const unsigned int extra_buffers, MMAL_PORT_BH_CB_T callback) +{ -+ MMAL_STATUS_T err; ++ MMAL_STATUS_T status; + -+ // Start by zapping all to zero -+ *spe = (subpic_reg_stash_t){NULL}; ++ port->userdata = (struct MMAL_PORT_USERDATA_T *)obj; + -+ if ((err = port_parameter_set_bool(port, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Failed to set sub port zero copy"); -+ return err; ++ status = port_parameter_set_uint32(port, MMAL_PARAMETER_EXTRA_BUFFERS, extra_buffers); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(obj, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", ++ status, mmal_status_to_string(status)); ++ return status; + } + -+ if ((spe->pool = mmal_pool_create(30, 0)) == NULL) ++ status = port_parameter_set_bool(port, MMAL_PARAMETER_ZERO_COPY, 1); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(obj, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", ++ port->name, status, mmal_status_to_string(status)); ++ return status; ++ } ++ ++ port->format->encoding = MMAL_ENCODING_OPAQUE; ++ port->format->encoding_variant = 0; ++ if ((status = mmal_port_format_commit(port)) != MMAL_SUCCESS) + { -+ msg_Err(p_filter, "Failed to create sub pool"); -+ return MMAL_ENOMEM; ++ msg_Err(obj, "Failed to commit format on port %s (status=%"PRIx32" %s)", ++ port->name, status, mmal_status_to_string(status)); ++ return status; + } + -+ port->userdata = (void *)p_filter; -+ spe->port = port; -+ spe->display_id = display_id; -+ spe->layer = layer; ++ port->buffer_num = 30; ++ port->buffer_size = port->buffer_size_recommended; + -+ return MMAL_SUCCESS; -+} ++ if ((*pppr = hw_mmal_port_pool_ref_create(port, port->buffer_num, port->buffer_size)) == NULL) { ++ msg_Err(obj, "Failed to create output pool"); ++ return status; ++ } + -+static void conv_subpic_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) -+{ -+#if TRACE_ALL -+ msg_Dbg((filter_t *)port->userdata, "<<< %s cmd=%d, user=%p, buf=%p, flags=%#x, len=%d/%d, pts=%lld", -+ __func__, buf->cmd, buf->user_data, buf, buf->flags, buf->length, buf->alloc_size, (long long)buf->pts); -+#else -+ VLC_UNUSED(port); -+#endif ++ status = mmal_port_enable(port, callback); ++ if (status != MMAL_SUCCESS) { ++ hw_mmal_port_pool_ref_release(*pppr, false); ++ *pppr = NULL; ++ msg_Err(obj, "Failed to enable output port %s (status=%"PRIx32" %s)", ++ port->name, status, mmal_status_to_string(status)); ++ return status; ++ } + -+ mmal_buffer_header_release(buf); // Will extract & release pic in pool callback ++ return MMAL_SUCCESS; +} + -+static int -+subpic_send_empty(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, const uint64_t pts) ++ ++void hw_mmal_pic_ctx_destroy(picture_context_t * pic_ctx_cmn) +{ -+ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(spe->pool->queue); -+ MMAL_STATUS_T err; ++ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic_ctx_cmn; ++ unsigned int i; + -+ if (buf == NULL) { -+ msg_Err(p_filter, "Buffer get for subpic failed"); -+ return -1; ++ for (i = 0; i != ctx->buf_count; ++i) { ++ if (ctx->bufs[i] != NULL) ++ mmal_buffer_header_release(ctx->bufs[i]); + } -+#if TRACE_ALL -+ msg_Dbg(p_filter, "Remove pic for sub %d", spe->seq); -+#endif -+ buf->cmd = 0; -+ buf->data = NULL; -+ buf->alloc_size = 0; -+ buf->offset = 0; -+ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END; -+ buf->pts = pts; -+ buf->dts = MMAL_TIME_UNKNOWN; -+ buf->user_data = NULL; + -+ if ((err = mmal_port_send_buffer(spe->port, buf)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Send buffer to subput failed"); -+ mmal_buffer_header_release(buf); -+ return -1; -+ } -+ return 0; -+} ++ cma_buf_end_flight(ctx->cb); ++ cma_buf_unref(ctx->cb); + -+// < 0 Error -+// 0 Done & stop -+// 1 Done & continue ++ free(ctx); ++} + -+int hw_mmal_subpic_update(vlc_object_t * const p_filter, -+ MMAL_BUFFER_HEADER_T * const sub_buf, -+ subpic_reg_stash_t * const spe, -+ const video_format_t * const fmt, -+ const MMAL_RECT_T * const scale_out, -+ const MMAL_DISPLAYTRANSFORM_T transform_out, -+ const uint64_t pts) ++picture_context_t * hw_mmal_pic_ctx_copy(picture_context_t * pic_ctx_cmn) +{ -+ MMAL_STATUS_T err; -+ -+ if (sub_buf == NULL) -+ { -+ if (spe->port->is_enabled && spe->seq != 0) -+ { -+ subpic_send_empty(p_filter, spe, pts); -+ spe->seq = 0; -+ } -+ } -+ else -+ { -+ const unsigned int seq = hw_mmal_vzc_buf_seq(sub_buf); -+ bool needs_update = (spe->seq != seq); ++ const pic_ctx_mmal_t * const src_ctx = (pic_ctx_mmal_t *)pic_ctx_cmn; ++ pic_ctx_mmal_t * const dst_ctx = calloc(1, sizeof(*dst_ctx)); ++ unsigned int i; + -+ hw_mmal_vzc_buf_scale_dest_rect(sub_buf, scale_out, transform_out); ++ if (dst_ctx == NULL) ++ return NULL; + -+ if (hw_mmal_vzc_buf_set_format(sub_buf, spe->port->format)) -+ { -+ MMAL_DISPLAYREGION_T * const dreg = hw_mmal_vzc_buf_region(sub_buf); -+ MMAL_VIDEO_FORMAT_T *const v_fmt = &spe->port->format->es->video; ++ // Copy ++ dst_ctx->cmn = src_ctx->cmn; + -+ v_fmt->frame_rate.den = fmt->i_frame_rate_base; -+ v_fmt->frame_rate.num = fmt->i_frame_rate; -+ v_fmt->par.den = fmt->i_sar_den; -+ v_fmt->par.num = fmt->i_sar_num; -+ v_fmt->color_space = MMAL_COLOR_SPACE_UNKNOWN; ++ dst_ctx->cb = cma_buf_ref(src_ctx->cb); + -+ if (needs_update || dreg->alpha != spe->alpha || !cmp_rect(&dreg->dest_rect, &spe->dest_rect)) { ++ dst_ctx->buf_count = src_ctx->buf_count; ++ for (i = 0; i != src_ctx->buf_count; ++i) { ++ dst_ctx->bufs[i] = src_ctx->bufs[i]; ++ if (dst_ctx->bufs[i] != NULL) ++ mmal_buffer_header_acquire(dst_ctx->bufs[i]); ++ } + -+ spe->alpha = dreg->alpha; -+ spe->dest_rect = dreg->dest_rect; -+ needs_update = true; ++ return &dst_ctx->cmn; ++} + -+ if (spe->display_id >= 0) -+ { -+ dreg->display_num = spe->display_id; -+ dreg->set |= MMAL_DISPLAY_SET_NUM; -+ } -+ dreg->layer = spe->layer; -+ dreg->set |= MMAL_DISPLAY_SET_LAYER; ++static MMAL_BOOL_T ++buf_pre_release_cb(MMAL_BUFFER_HEADER_T * buf, void *userdata) ++{ ++ hw_mmal_port_pool_ref_t * const ppr = userdata; + -+#if TRACE_ALL -+ msg_Dbg(p_filter, "%s: Update region: Set=%x, dest=%dx%d @ (%d,%d), src=%dx%d @ (%d,%d), layer=%d, alpha=%#x", -+ __func__, dreg->set, -+ dreg->dest_rect.width, dreg->dest_rect.height, dreg->dest_rect.x, dreg->dest_rect.y, -+ dreg->src_rect.width, dreg->src_rect.height, dreg->src_rect.x, dreg->src_rect.y, -+ dreg->layer, dreg->alpha); -+#endif ++ // Kill the callback - otherwise we will go in circles! ++ mmal_buffer_header_pre_release_cb_set(buf, (MMAL_BH_PRE_RELEASE_CB_T)0, NULL); ++ mmal_buffer_header_acquire(buf); // Ref it again + -+ // If now completely offscreen just flush this & return -+ // We only do -ve as (a) that is easy and (b) it seems to be -+ // something that can confuse mmal -+ if (dreg->dest_rect.y + dreg->dest_rect.height <= 0 || -+ dreg->dest_rect.x + dreg->dest_rect.width <= 0) -+ { -+ if (spe->port->is_enabled) -+ subpic_send_empty(p_filter, spe, pts); -+ spe->seq = seq; -+ return 1; -+ } ++ // As we have re-acquired the buffer we need a full release ++ // (not continue) to zap the ref count back to zero ++ // This is "safe" 'cos we have already reset the cb ++ hw_mmal_port_pool_ref_recycle(ppr, buf); ++ hw_mmal_port_pool_ref_release(ppr, true); // Assume in callback + -+ if ((err = mmal_port_parameter_set(spe->port, &dreg->hdr)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Set display region on subput failed"); -+ return -1; -+ } ++ return MMAL_TRUE; ++} + -+ if ((err = mmal_port_format_commit(spe->port)) != MMAL_SUCCESS) -+ { -+ msg_Dbg(p_filter, "%s: Subpic commit fail: %d", __func__, err); -+ return -1; -+ } -+ } -+ } ++// Buffer belongs to context on successful return from this fn ++// is still valid on failure ++picture_context_t * ++hw_mmal_gen_context(MMAL_BUFFER_HEADER_T * buf, hw_mmal_port_pool_ref_t * const ppr) + { +- picture_sys_t *pic_sys = picture->p_sys; +- MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer; ++ pic_ctx_mmal_t * const ctx = calloc(1, sizeof(pic_ctx_mmal_t)); + +- int offset = 0; +- picture->p[0].p_pixels = buffer->data; +- for (int i = 1; i < picture->i_planes; i++) { +- offset = offset + picture->p[i - 1].i_pitch * picture->p[i - 1].i_lines; +- picture->p[i].p_pixels = (ptrdiff_t)buffer->data + offset; ++ if (ctx == NULL) ++ return NULL; + -+ if (!spe->port->is_enabled) -+ { -+ spe->port->buffer_num = 30; -+ spe->port->buffer_size = spe->port->buffer_size_recommended; // Not used but shuts up the error checking ++ // If we have an associated ppr then ref & set appropriate callbacks ++ if (ppr != NULL) { ++ hw_mmal_port_pool_ref_acquire(ppr); ++ mmal_buffer_header_pre_release_cb_set(buf, buf_pre_release_cb, ppr); ++ buf->user_data = NULL; + } + +- pic_sys->displayed = false; ++ ctx->cmn.copy = hw_mmal_pic_ctx_copy; ++ ctx->cmn.destroy = hw_mmal_pic_ctx_destroy; + -+ if ((err = mmal_port_enable(spe->port, conv_subpic_cb)) != MMAL_SUCCESS) -+ { -+ msg_Dbg(p_filter, "%s: Subpic enable fail: %d", __func__, err); -+ return -1; -+ } -+ } ++ ctx->buf_count = 1; ++ ctx->bufs[0] = buf; + -+ if (needs_update) -+ { -+#if TRACE_ALL -+ msg_Dbg(p_filter, "Update pic for sub %d", spe->seq); -+#endif -+ if ((err = port_send_replicated(spe->port, spe->pool, sub_buf, pts)) != MMAL_SUCCESS) -+ { -+ msg_Err(p_filter, "Send buffer to subput failed"); -+ return -1; -+ } ++ return &ctx->cmn; ++} + -+ spe->seq = seq; ++// n is els ++// * Make NEON! ++typedef void piccpy_fn(void * dest, const void * src, size_t n); ++ ++extern piccpy_fn mmal_piccpy_10_to_8_neon; + ++static void piccpy_10_to_8_c(void * dest, const void * src, size_t n) ++{ ++ uint8_t * d = dest; ++ const uint16_t * s = src; ++ while (n-- != 0) ++ *d++ = *s++ >> 2; ++} ++ ++// Do a stride converting copy - if the strides are the same and line_len is ++// close then do a single block copy - we don't expect to have to preserve ++// pixels in the output frame ++static void mem_copy_2d(uint8_t * d_ptr, const size_t d_stride, ++ const uint8_t * s_ptr, const size_t s_stride, ++ size_t lines, const size_t line_len) ++{ ++ if (s_stride == d_stride && d_stride < line_len + 32) ++ { ++ memcpy(d_ptr, s_ptr, d_stride * lines); ++ } ++ else ++ { ++ while (lines-- != 0) { ++ memcpy(d_ptr, s_ptr, line_len); ++ d_ptr += d_stride; ++ s_ptr += s_stride; + } + } -+ return 1; +} + ++// line_len in D units ++static void mem_copy_2d_10_to_8(uint8_t * d_ptr, const size_t d_stride, ++ const uint8_t * s_ptr, const size_t s_stride, ++ size_t lines, const size_t line_len) ++{ ++ piccpy_fn * const docpy = vlc_CPU_ARM_NEON() ? mmal_piccpy_10_to_8_neon : piccpy_10_to_8_c; ++ if (s_stride == d_stride * 2 && d_stride < line_len + 32) ++ { ++ docpy(d_ptr, s_ptr, d_stride * lines); ++ } ++ else ++ { ++ while (lines-- != 0) { ++ docpy(d_ptr, s_ptr, line_len); ++ d_ptr += d_stride; ++ s_ptr += s_stride; ++ } ++ } ++} + + ---- /dev/null -+++ b/modules/hw/mmal/subpic.h -@@ -0,0 +1,33 @@ -+#ifndef VLC_HW_MMAL_SUBPIC_H_ -+#define VLC_HW_MMAL_SUBPIC_H_ -+ -+typedef struct subpic_reg_stash_s ++int hw_mmal_copy_pic_to_buf(void * const buf_data, ++ uint32_t * const pLength, ++ const MMAL_ES_FORMAT_T * const fmt, ++ const picture_t * const pic) +{ -+ MMAL_PORT_T * port; -+ MMAL_POOL_T * pool; -+ int display_id; // -1 => do not set -+ unsigned int layer; -+ // Shadow vars so we can tell if stuff has changed -+ MMAL_RECT_T dest_rect; -+ unsigned int alpha; -+ unsigned int seq; -+} subpic_reg_stash_t; ++ const MMAL_VIDEO_FORMAT_T *const video = &fmt->es->video; ++ uint8_t * const dest = buf_data; ++ size_t length = 0; + -+int hw_mmal_subpic_update(vlc_object_t * const p_filter, -+ MMAL_BUFFER_HEADER_T * const sub_buf, -+ subpic_reg_stash_t * const spe, -+ const video_format_t * const fmt, -+ const MMAL_RECT_T * const scale_out, -+ const MMAL_DISPLAYTRANSFORM_T transform_out, -+ const uint64_t pts); ++ //**** Worry about x/y_offsets + -+void hw_mmal_subpic_flush(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe); ++ assert(fmt->encoding == MMAL_ENCODING_I420); + -+void hw_mmal_subpic_close(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe); ++ switch (pic->format.i_chroma) { ++ case VLC_CODEC_I420: ++ { ++ const size_t y_size = video->width * video->height; ++ mem_copy_2d(dest, video->width, ++ pic->p[0].p_pixels, pic->p[0].i_pitch, ++ video->crop.height, ++ video->crop.width); + -+// If display id is -1 it will be unset -+MMAL_STATUS_T hw_mmal_subpic_open(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, MMAL_PORT_T * const port, -+ const int display_id, const unsigned int layer); ++ mem_copy_2d(dest + y_size, video->width / 2, ++ pic->p[1].p_pixels, pic->p[1].i_pitch, ++ video->crop.height / 2, ++ video->crop.width / 2); + -+#endif ++ mem_copy_2d(dest + y_size + y_size / 4, video->width / 2, ++ pic->p[2].p_pixels, pic->p[2].i_pitch, ++ video->crop.height / 2, ++ video->crop.width / 2); + ---- /dev/null -+++ b/modules/hw/mmal/transform_ops.h -@@ -0,0 +1,99 @@ -+#ifndef VLC_MMAL_TRANSFORM_OPS_H -+#define VLC_MMAL_TRANSFORM_OPS_H ++ // And make sure it is actually in memory ++ length = y_size + y_size / 2; ++ break; ++ } + -+#include -+#include -+#include ++ case VLC_CODEC_I420_10L: ++ { ++ const size_t y_size = video->width * video->height; ++ mem_copy_2d_10_to_8(dest, video->width, ++ pic->p[0].p_pixels, pic->p[0].i_pitch, ++ video->crop.height, ++ video->crop.width); + ++ mem_copy_2d_10_to_8(dest + y_size, video->width / 2, ++ pic->p[1].p_pixels, pic->p[1].i_pitch, ++ video->crop.height / 2, ++ video->crop.width / 2); + -+// These are enums with the same order so simply coerce -+static inline MMAL_DISPLAYTRANSFORM_T vlc_to_mmal_transform(const video_orientation_t orientation){ -+ return (MMAL_DISPLAYTRANSFORM_T)orientation; -+} ++ mem_copy_2d_10_to_8(dest + y_size + y_size / 4, video->width / 2, ++ pic->p[2].p_pixels, pic->p[2].i_pitch, ++ video->crop.height / 2, ++ video->crop.width / 2); + -+// MMAL headers comment these (getting 2 a bit wrong) but do not give -+// defines -+#define XFORM_H_SHIFT 0 // Hflip -+#define XFORM_V_SHIFT 1 // Vflip -+#define XFORM_T_SHIFT 2 // Transpose -+#define XFORM_H_BIT (1 << XFORM_H_SHIFT) -+#define XFORM_V_BIT (1 << XFORM_V_SHIFT) -+#define XFORM_T_BIT (1 << XFORM_T_SHIFT) ++ // And make sure it is actually in memory ++ length = y_size + y_size / 2; ++ break; ++ } + -+static inline bool -+is_transform_transpose(const MMAL_DISPLAYTRANSFORM_T t) -+{ -+ return ((unsigned int)t & XFORM_T_BIT) != 0; -+} ++ default: ++ if (pLength != NULL) ++ *pLength = 0; ++ return VLC_EBADVAR; ++ } + -+static inline bool -+is_transform_hflip(const MMAL_DISPLAYTRANSFORM_T t) -+{ -+ return ((unsigned int)t & XFORM_H_BIT) != 0; -+} ++ if (cma_vcsm_type() == VCSM_INIT_LEGACY) { // ** CMA is currently always uncached ++ flush_range(dest, length); ++ } + -+static inline bool -+is_transform_vflip(const MMAL_DISPLAYTRANSFORM_T t) -+{ -+ return ((unsigned int)t & XFORM_V_BIT) != 0; -+} ++ if (pLength != NULL) ++ *pLength = (uint32_t)length; + -+static inline MMAL_DISPLAYTRANSFORM_T -+swap_transform_hv(const MMAL_DISPLAYTRANSFORM_T x) -+{ -+ return (((x >> XFORM_H_SHIFT) & 1) << XFORM_V_SHIFT) | -+ (((x >> XFORM_V_SHIFT) & 1) << XFORM_H_SHIFT) | -+ (x & XFORM_T_BIT); ++ return VLC_SUCCESS; +} + -+static inline MMAL_DISPLAYTRANSFORM_T -+transform_inverse(const MMAL_DISPLAYTRANSFORM_T x) -+{ -+ return is_transform_transpose(x) ? swap_transform_hv(x) : x; -+} + -+// Transform generated by A then B -+// All ops are self inverse so can simply be XORed on their own -+// H & V flips after a transpose need to be swapped -+static inline MMAL_DISPLAYTRANSFORM_T -+combine_transform(const MMAL_DISPLAYTRANSFORM_T a, const MMAL_DISPLAYTRANSFORM_T b) ++static MMAL_BOOL_T rep_buf_free_cb(MMAL_BUFFER_HEADER_T *header, void *userdata) +{ -+ return a ^ (is_transform_transpose(a) ? swap_transform_hv(b) : b); ++ cma_buf_t * const cb = userdata; ++ VLC_UNUSED(header); ++ ++ cma_buf_unref(cb); ++ return MMAL_FALSE; +} + -+static inline MMAL_RECT_T -+rect_transpose(const MMAL_RECT_T s) ++static int cma_buf_buf_attach(MMAL_BUFFER_HEADER_T * const buf, cma_buf_t * const cb) +{ -+ return (MMAL_RECT_T){ -+ .x = s.y, -+ .y = s.x, -+ .width = s.height, -+ .height = s.width -+ }; -+} ++ // Just a CMA buffer - fill in new buffer ++ const uintptr_t vc_h = cma_buf_vc_handle(cb); ++ if (vc_h == 0) ++ return VLC_EGENERIC; + -+// hflip s in c -+static inline MMAL_RECT_T rect_hflip(const MMAL_RECT_T s, const MMAL_RECT_T c) -+{ -+ return (MMAL_RECT_T){ -+ .x = c.x + (c.x + c.width) - (s.x + s.width), -+ .y = s.y, -+ .width = s.width, -+ .height = s.height -+ }; -+} ++ mmal_buffer_header_reset(buf); ++ buf->data = (uint8_t *)vc_h; ++ buf->alloc_size = cma_buf_size(cb); ++ buf->length = buf->alloc_size; ++ // Ensure cb remains valid for the duration of this buffer ++ mmal_buffer_header_pre_release_cb_set(buf, rep_buf_free_cb, cma_buf_ref(cb)); + return VLC_SUCCESS; + } + -+// vflip s in c -+static inline MMAL_RECT_T rect_vflip(const MMAL_RECT_T s, const MMAL_RECT_T c) ++MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_copied(const picture_t *const pic, ++ MMAL_POOL_T * const rep_pool, ++ MMAL_PORT_T * const port, ++ cma_buf_pool_t * const cbp) +{ -+ return (MMAL_RECT_T){ -+ .x = s.x, -+ .y = (c.y + c.height) - (s.y - c.y) - s.height, -+ .width = s.width, -+ .height = s.height -+ }; -+} ++ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(rep_pool->queue); ++ if (buf == NULL) ++ goto fail0; + ++ cma_buf_t * const cb = cma_buf_pool_alloc_buf(cbp, port->buffer_size); ++ if (cb == NULL) ++ goto fail1; + -+#endif ++ if (cma_buf_buf_attach(buf, cb) != VLC_SUCCESS) ++ goto fail2; + ---- /dev/null -+++ b/modules/hw/mmal/v7_pmu.S -@@ -0,0 +1,263 @@ -+/*------------------------------------------------------------ -+Performance Monitor Block -+------------------------------------------------------------*/ -+ .arm @ Make sure we are in ARM mode. -+ .text -+ .align 2 -+ .global getPMN @ export this function for the linker ++ pic_to_buf_copy_props(buf, pic); + -+/* Returns the number of progammable counters uint32_t getPMN(void) */ ++ if (hw_mmal_copy_pic_to_buf(cma_buf_addr(cb), &buf->length, port->format, pic) != VLC_SUCCESS) ++ goto fail2; ++ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END; + -+getPMN: -+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC Register */ -+ MOV r0, r0, LSR #11 /* Shift N field down to bit 0 */ -+ AND r0, r0, #0x1F /* Mask to leave just the 5 N bits */ -+ BX lr ++ cma_buf_unref(cb); ++ return buf; + ++fail2: ++ cma_buf_unref(cb); ++fail1: ++ mmal_buffer_header_release(buf); ++fail0: ++ return NULL; ++} + ++MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_replicated(const picture_t *const pic, MMAL_POOL_T * const rep_pool) ++{ ++ pic_ctx_mmal_t *const ctx = (pic_ctx_mmal_t *)pic->context; ++ MMAL_BUFFER_HEADER_T *const rep_buf = mmal_queue_wait(rep_pool->queue); + -+ .global pmn_config @ export this function for the linker -+ /* Sets the event for a programmable counter to record */ -+ /* void pmn_config(unsigned counter, uint32_t event) */ -+ /* counter = r0 = Which counter to program (e.g. 0 for PMN0, 1 for PMN1 */ -+ /* event = r1 = The event code */ -+pmn_config: -+ AND r0, r0, #0x1F /* Mask to leave only bits 4:0 */ -+ MCR p15, 0, r0, c9, c12, 5 /* Write PMNXSEL Register */ -+ MCR p15, 0, r1, c9, c13, 1 /* Write EVTSELx Register */ -+ BX lr ++ if (rep_buf == NULL) ++ return NULL; + ++ if (ctx->bufs[0] != NULL) ++ { ++ // Existing buffer - replicate it ++ if (mmal_buffer_header_replicate(rep_buf, ctx->bufs[0]) != MMAL_SUCCESS) ++ goto fail; ++ } ++ else if (ctx->cb != NULL) ++ { ++ // Just a CMA buffer - fill in new buffer ++ if (cma_buf_buf_attach(rep_buf, ctx->cb) != 0) ++ goto fail; ++ } ++ else ++ goto fail; + ++ pic_to_buf_copy_props(rep_buf, pic); ++ return rep_buf; + -+ .global ccnt_divider @ export this function for the linker -+ /* Enables/disables the divider (1/64) on CCNT */ -+ /* void ccnt_divider(int divider) */ -+ /* divider = r0 = If 0 disable divider, else enable dvider */ -+ccnt_divider: -+ MRC p15, 0, r1, c9, c12, 0 /* Read PMNC */ ++fail: ++ mmal_buffer_header_release(rep_buf); ++ return NULL; ++} + -+ CMP r0, #0x0 /* IF (r0 == 0) */ -+ BICEQ r1, r1, #0x08 /* THEN: Clear the D bit (disables the */ -+ ORRNE r1, r1, #0x08 /* ELSE: Set the D bit (enables the di */ + -+ MCR p15, 0, r1, c9, c12, 0 /* Write PMNC */ -+ BX lr + + -+ /* --------------------------------------------------------------- */ -+ /* Enable/Disable */ -+ /* --------------------------------------------------------------- */ ++int hw_mmal_get_gpu_mem(void) { ++ static int stashed_val = -2; ++ VCHI_INSTANCE_T vchi_instance; ++ VCHI_CONNECTION_T *vchi_connection = NULL; ++ char rbuf[1024] = { 0 }; + -+ .global enable_pmu @ export this function for the linker -+ /* Global PMU enable */ -+ /* void enable_pmu(void) */ -+enable_pmu: -+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ -+ ORR r0, r0, #0x01 /* Set E bit */ -+ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ -+ BX lr ++ if (stashed_val >= -1) ++ return stashed_val; + ++ if (vchi_initialise(&vchi_instance) != 0) ++ goto fail0; + ++ //create a vchi connection ++ if (vchi_connect(NULL, 0, vchi_instance) != 0) ++ goto fail0; + -+ .global disable_pmu @ export this function for the linker -+ /* Global PMU disable */ -+ /* void disable_pmu(void) */ -+disable_pmu: -+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ -+ BIC r0, r0, #0x01 /* Clear E bit */ -+ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ -+ BX lr ++ vc_vchi_gencmd_init(vchi_instance, &vchi_connection, 1); + ++ //send the gencmd for the argument ++ if (vc_gencmd_send("get_mem gpu") != 0) ++ goto fail; + ++ if (vc_gencmd_read_response(rbuf, sizeof(rbuf) - 1) != 0) ++ goto fail; + -+ .global enable_ccnt @ export this function for the linker -+ /* Enable the CCNT */ -+ /* void enable_ccnt(void) */ -+enable_ccnt: -+ MOV r0, #0x80000000 /* Set C bit */ -+ MCR p15, 0, r0, c9, c12, 1 /* Write CNTENS Register */ -+ BX lr ++ if (strncmp(rbuf, "gpu=", 4) != 0) ++ goto fail; + ++ char *p; ++ unsigned long m = strtoul(rbuf + 4, &p, 10); + ++ if (p[0] != 'M' || p[1] != '\0') ++ stashed_val = -1; ++ else ++ stashed_val = (int)m << 20; + -+ .global disable_ccnt @ export this function for the linker -+ /* Disable the CCNT */ -+ /* void disable_ccnt(void) */ -+disable_ccnt: -+ MOV r0, #0x80000000 /* Clear C bit */ -+ MCR p15, 0, r0, c9, c12, 2 /* Write CNTENC Register */ -+ BX lr ++ vc_gencmd_stop(); + ++ //close the vchi connection ++ vchi_disconnect(vchi_instance); + ++ return stashed_val; + -+ .global enable_pmn @ export this function for the linker -+ /* Enable PMN{n} */ -+ /* void enable_pmn(uint32_t counter) */ -+ /* counter = r0 = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) -+enable_pmn: */ -+ MOV r1, #0x1 /* Use arg (r0) to set which counter t */ -+ MOV r1, r1, LSL r0 ++fail: ++ vc_gencmd_stop(); ++ vchi_disconnect(vchi_instance); ++fail0: ++ stashed_val = -1; ++ return -1; ++}; + -+ MCR p15, 0, r1, c9, c12, 1 /* Write CNTENS Register */ -+ BX lr ++// =========================================================================== + ++typedef struct pool_ent_s ++{ ++ struct pool_ent_s * next; ++ struct pool_ent_s * prev; + ++ atomic_int ref_count; ++ unsigned int seq; + -+ .global disable_pmn @ export this function for the linker -+ /* Enable PMN{n} */ -+ /* void disable_pmn(uint32_t counter) */ -+ /* counter = r0 = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) -+disable_pmn: */ -+ MOV r1, #0x1 /* Use arg (r0) to set which counter t */ -+ MOV r1, r1, LSL r0 ++ size_t size; + -+ MCR p15, 0, r1, c9, c12, 1 /* Write CNTENS Register */ -+ BX lr ++ int vcsm_hdl; ++ int vc_hdl; ++ void * buf; + ++ unsigned int width; ++ unsigned int height; ++ MMAL_FOURCC_T enc_type; + ++ picture_t * pic; ++} pool_ent_t; + -+ .global enable_pmu_user_access @ export this function for the linker -+ /* Enables User mode access to the PMU (must be called in a priviledge */ -+ /* void enable_pmu_user_access(void) */ -+enable_pmu_user_access: -+ MRC p15, 0, r0, c9, c14, 0 /* Read PMUSERENR Register */ -+ ORR r0, r0, #0x01 /* Set EN bit (bit 0) */ -+ MCR p15, 0, r0, c9, c14, 0 /* Write PMUSERENR Register */ -+ BX lr + ++typedef struct ent_list_hdr_s ++{ ++ pool_ent_t * ents; ++ pool_ent_t * tail; ++ unsigned int n; ++} ent_list_hdr_t; + ++#define ENT_LIST_HDR_INIT (ent_list_hdr_t){ \ ++ .ents = NULL, \ ++ .tail = NULL, \ ++ .n = 0 \ ++} + -+ .global disable_pmu_user_access @ export this function for the linke -+ /* Disables User mode access to the PMU (must be called in a priviledg */ -+ /* void disable_pmu_user_access(void) */ -+disable_pmu_user_access: -+ MRC p15, 0, r0, c9, c14, 0 /* Read PMUSERENR Register */ -+ BIC r0, r0, #0x01 /* Clear EN bit (bit 0) */ -+ MCR p15, 0, r0, c9, c14, 0 /* Write PMUSERENR Register */ -+ BX lr -+ ++struct vzc_pool_ctl_s ++{ ++ atomic_int ref_count; + -+ /* --------------------------------------------------------------- */ -+ /* Counter read registers */ -+ /* --------------------------------------------------------------- */ ++ ent_list_hdr_t ent_pool; ++ ent_list_hdr_t ents_cur; ++ ent_list_hdr_t ents_prev; + -+ .global read_ccnt @ export this function for the linker -+ /* Returns the value of CCNT */ -+ /* uint32_t read_ccnt(void) */ -+read_ccnt: -+ MRC p15, 0, r0, c9, c13, 0 /* Read CCNT Register */ -+ BX lr ++ unsigned int max_n; ++ unsigned int seq; + ++ vlc_mutex_t lock; + -+ .global read_pmn @ export this function for the linker -+ /* Returns the value of PMN{n} */ -+ /* uint32_t read_pmn(uint32_t counter) */ -+ /* counter = r0 = The counter to read (e.g. 0 for PMN0, 1 for PMN1) * -+read_pmn: */ -+ AND r0, r0, #0x1F /* Mask to leave only bits 4:0 */ -+ MCR p15, 0, r0, c9, c12, 5 /* Write PMNXSEL Register */ -+ MRC p15, 0, r0, c9, c13, 2 /* Read current PMNx Register */ -+ BX lr ++ MMAL_POOL_T * buf_pool; + ++ vcsm_init_type_t vcsm_init_type; ++}; + -+ /* --------------------------------------------------------------- */ -+ /* Software Increment */ -+ /* --------------------------------------------------------------- */ ++typedef struct vzc_subbuf_ent_s ++{ ++ pool_ent_t * ent; ++ MMAL_RECT_T pic_rect; ++ MMAL_RECT_T orig_dest_rect; ++ MMAL_DISPLAYREGION_T dreg; ++} vzc_subbuf_ent_t; + -+ .global pmu_software_increment @ export this function for the linker -+ /* Writes to software increment register */ -+ /* void pmu_software_increment(uint32_t counter) */ -+ /* counter = r0 = The counter to increment (e.g. 0 for PMN0, 1 for PMN -+pmu_software_increment: */ -+ MOV r1, #0x01 -+ MOV r1, r1, LSL r0 -+ MCR p15, 0, r1, c9, c12, 4 /* Write SWINCR Register */ -+ BX lr + -+ /* --------------------------------------------------------------- */ -+ /* Overflow & Interrupt Generation */ -+ /* --------------------------------------------------------------- */ ++static pool_ent_t * ent_extract(ent_list_hdr_t * const elh, pool_ent_t * const ent) ++{ ++// printf("List %p [%d]: Ext %p\n", elh, elh->n, ent); + -+ .global read_flags @ export this function for the linker -+ /* Returns the value of the overflow flags */ -+ /* uint32_t read_flags(void) */ -+read_flags: -+ MRC p15, 0, r0, c9, c12, 3 /* Read FLAG Register */ -+ BX lr ++ if (ent == NULL) ++ return NULL; + ++ if (ent->next == NULL) ++ elh->tail = ent->prev; ++ else ++ ent->next->prev = ent->prev; + -+ .global write_flags @ export this function for the linker -+ /* Writes the overflow flags */ -+ /* void write_flags(uint32_t flags) */ -+write_flags: -+ MCR p15, 0, r0, c9, c12, 3 /* Write FLAG Register */ -+ BX lr ++ if (ent->prev == NULL) ++ elh->ents = ent->next; ++ else ++ ent->prev->next = ent->next; + ++ ent->prev = ent->next = NULL; + -+ .global enable_ccnt_irq @ export this function for the linker -+ /* Enables interrupt generation on overflow of the CCNT */ -+ /* void enable_ccnt_irq(void) */ -+enable_ccnt_irq: -+ MOV r0, #0x80000000 -+ MCR p15, 0, r0, c9, c14, 1 /* Write INTENS Register */ -+ BX lr ++ --elh->n; + -+ .global disable_ccnt_irq @ export this function for the linker -+ /* Disables interrupt generation on overflow of the CCNT */ -+ /* void disable_ccnt_irq(void) */ -+disable_ccnt_irq: -+ MOV r0, #0x80000000 -+ MCR p15, 0, r0, c9, c14, 2 /* Write INTENC Register */ -+ BX lr ++ return ent; // For convienience ++} + ++static inline pool_ent_t * ent_extract_tail(ent_list_hdr_t * const elh) ++{ ++ return ent_extract(elh, elh->tail); ++} + -+ .global enable_pmn_irq @ export this function for the linker -+ /* Enables interrupt generation on overflow of PMN{x} */ -+ /* void enable_pmn_irq(uint32_t counter) */ -+ /* counter = r0 = The counter to enable the interrupt for (e.g. 0 for -+enable_pmn_irq: */ -+ MOV r1, #0x1 /* Use arg (r0) to set which counter */ -+ MOV r0, r1, LSL r0 -+ MCR p15, 0, r0, c9, c14, 1 /* Write INTENS Register */ -+ BX lr ++static void ent_add_head(ent_list_hdr_t * const elh, pool_ent_t * const ent) ++{ ++// printf("List %p [%d]: Add %p\n", elh, elh->n, ent); + -+ .global disable_pmn_irq @ export this function for the linker -+ /* Disables interrupt generation on overflow of PMN{x} */ -+ /* void disable_pmn_irq(uint32_t counter) */ -+ /* counter = r0 = The counter to disable the interrupt for (e.g. 0 fo -+disable_pmn_irq: */ -+ MOV r1, #0x1 /* Use arg (r0) to set which counter t */ -+ MOV r0, r1, LSL r0 -+ MCR p15, 0, r0, c9, c14, 2 /* Write INTENC Register */ -+ BX lr ++ if ((ent->next = elh->ents) == NULL) ++ elh->tail = ent; ++ else ++ ent->next->prev = ent; + -+ /* --------------------------------------------------------------- */ -+ /* Reset Functions */ -+ /* --------------------------------------------------------------- */ ++ ent->prev = NULL; ++ elh->ents = ent; ++ ++elh->n; ++} + -+ .global reset_pmn @ export this function for the linker -+ /* Resets the programmable counters */ -+ /* void reset_pmn(void) */ -+reset_pmn: -+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ -+ ORR r0, r0, #0x02 /* Set P bit (Event Counter Reset) */ -+ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ -+ BX lr ++static void ent_free(pool_ent_t * const ent) ++{ ++// printf("Free ent: %p\n", ent); ++ if (ent != NULL) { ++ // If we still have a ref to a pic - kill it now ++ if (ent->pic != NULL) ++ picture_Release(ent->pic); + ++ // Free contents ++ vcsm_unlock_hdl(ent->vcsm_hdl); + -+ .global reset_ccnt @ export this function for the linker -+ /* Resets the CCNT */ -+ /* void reset_ccnt(void) */ -+reset_ccnt: -+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ -+ ORR r0, r0, #0x04 /* Set C bit (Event Counter Reset) */ -+ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ -+ BX lr ++ vcsm_free(ent->vcsm_hdl); + ++ free(ent); ++ } ++} + -+ .end @end of code, this line is optional. -+/* ------------------------------------------------------------ */ -+/* End of v7_pmu.s */ -+/* ------------------------------------------------------------ */ ++static void ent_free_list(ent_list_hdr_t * const elh) ++{ ++ pool_ent_t * ent = elh->ents; + ++// printf("Free list: %p [%d]\n", elh, elh->n); + ---- /dev/null -+++ b/modules/hw/mmal/v7_pmu.h -@@ -0,0 +1,113 @@ -+// ------------------------------------------------------------ -+// PMU for Cortex-A/R (v7-A/R) -+// ------------------------------------------------------------ ++ *elh = ENT_LIST_HDR_INIT; + -+#ifndef _V7_PMU_H -+#define _V7_PMU_H ++ while (ent != NULL) { ++ pool_ent_t * const t = ent; ++ ent = t->next; ++ ent_free(t); ++ } ++} + -+// Returns the number of progammable counters -+unsigned int getPMN(void); ++static void ent_list_move(ent_list_hdr_t * const dst, ent_list_hdr_t * const src) ++{ ++// printf("Move %p->%p\n", src, dst); + -+// Sets the event for a programmable counter to record -+// counter = r0 = Which counter to program (e.g. 0 for PMN0, 1 for PMN1) -+// event = r1 = The event code (from appropiate TRM or ARM Architecture Reference Manual) -+void pmn_config(unsigned int counter, unsigned int event); ++ *dst = *src; ++ *src = ENT_LIST_HDR_INIT; ++} + -+// Enables/disables the divider (1/64) on CCNT -+// divider = r0 = If 0 disable divider, else enable dvider -+void ccnt_divider(int divider); ++// Scans "backwards" as that should give us the fastest match if we are ++// presented with pics in the same order each time ++static pool_ent_t * ent_list_extract_pic_ent(ent_list_hdr_t * const elh, picture_t * const pic) ++{ ++ pool_ent_t *ent = elh->tail; + -+// -+// Enables and disables -+// ++// printf("Find list: %p [%d]; pic:%p\n", elh, elh->n, pic); + -+// Global PMU enable -+// On ARM11 this enables the PMU, and the counters start immediately -+// On Cortex this enables the PMU, there are individual enables for the counters -+void enable_pmu(void); ++ while (ent != NULL) { ++// printf("Check ent: %p, pic:%p\n", ent, ent->pic); + -+// Global PMU disable -+// On Cortex, this overrides the enable state of the individual counters -+void disable_pmu(void); ++ if (ent->pic == pic) ++ return ent_extract(elh, ent); ++ ent = ent->prev; ++ } ++ return NULL; ++} + -+// Enable the CCNT -+void enable_ccnt(void); ++#define POOL_ENT_ALLOC_BLOCK 0x10000 + -+// Disable the CCNT -+void disable_ccnt(void); ++static pool_ent_t * pool_ent_alloc_new(size_t req_size) ++{ ++ pool_ent_t * ent = calloc(1, sizeof(*ent)); ++ const size_t alloc_size = (req_size + POOL_ENT_ALLOC_BLOCK - 1) & ~(POOL_ENT_ALLOC_BLOCK - 1); + -+// Enable PMN{n} -+// counter = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) -+void enable_pmn(unsigned int counter); ++ if (ent == NULL) ++ return NULL; + -+// Enable PMN{n} -+// counter = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) -+void disable_pmn(unsigned int counter); ++ ent->next = ent->prev = NULL; + -+// -+// Read counter values -+// ++ // Alloc from vcsm ++ if ((ent->vcsm_hdl = vcsm_malloc_cache(alloc_size, VCSM_CACHE_TYPE_HOST, (char *)"vlc-subpic")) == -1) ++ goto fail1; ++ if ((ent->vc_hdl = vcsm_vc_hdl_from_hdl(ent->vcsm_hdl)) == 0) ++ goto fail2; ++ if ((ent->buf = vcsm_lock(ent->vcsm_hdl)) == NULL) ++ goto fail2; + -+// Returns the value of CCNT -+unsigned int read_ccnt(void); ++ ent->size = alloc_size; ++ return ent; + -+// Returns the value of PMN{n} -+// counter = The counter to read (e.g. 0 for PMN0, 1 for PMN1) -+unsigned int read_pmn(unsigned int counter); ++fail2: ++ vcsm_free(ent->vcsm_hdl); ++fail1: ++ free(ent); ++ return NULL; ++} + -+// -+// Overflow and interrupts -+// ++static inline pool_ent_t * pool_ent_ref(pool_ent_t * const ent) ++{ ++// int n = atomic_fetch_add(&ent->ref_count, 1) + 1; ++// printf("Ref: %p: %d\n", ent, n); ++ atomic_fetch_add(&ent->ref_count, 1); ++ return ent; ++} + -+// Returns the value of the overflow flags -+unsigned int read_flags(void); ++static void pool_recycle(vzc_pool_ctl_t * const pc, pool_ent_t * const ent) ++{ ++ pool_ent_t * xs = NULL; ++ int n; + -+// Writes the overflow flags -+void write_flags(unsigned int flags); ++ if (ent == NULL) ++ return; + -+// Enables interrupt generation on overflow of the CCNT -+void enable_ccnt_irq(void); ++ n = atomic_fetch_sub(&ent->ref_count, 1) - 1; + -+// Disables interrupt generation on overflow of the CCNT -+void disable_ccnt_irq(void); ++// printf("%s: Pool: %p: Ent: %p: %d\n", __func__, &pc->ent_pool, ent, n); + -+// Enables interrupt generation on overflow of PMN{x} -+// counter = The counter to enable the interrupt for (e.g. 0 for PMN0, 1 for PMN1) -+void enable_pmn_irq(unsigned int counter); ++ if (n != 0) ++ return; + -+// Disables interrupt generation on overflow of PMN{x} -+// counter = r0 = The counter to disable the interrupt for (e.g. 0 for PMN0, 1 for PMN1) -+void disable_pmn_irq(unsigned int counter); ++ if (ent->pic != NULL) { ++ picture_Release(ent->pic); ++ ent->pic = NULL; ++ } + -+// -+// Counter reset functions -+// ++ vlc_mutex_lock(&pc->lock); + -+// Resets the programmable counters -+void reset_pmn(void); ++ // If we have a full pool then extract the LRU and free it ++ // Free done outside mutex ++ if (pc->ent_pool.n >= pc->max_n) ++ xs = ent_extract_tail(&pc->ent_pool); + -+// Resets the CCNT -+void reset_ccnt(void); ++ ent_add_head(&pc->ent_pool, ent); + -+// -+// Software Increment ++ vlc_mutex_unlock(&pc->lock); + -+// Writes to software increment register -+// counter = The counter to increment (e.g. 0 for PMN0, 1 for PMN1) -+void pmu_software_increment(unsigned int counter); ++ ent_free(xs); ++} + -+// -+// User mode access -+// ++// * This could be made more efficient, but this is easy ++static void pool_recycle_list(vzc_pool_ctl_t * const pc, ent_list_hdr_t * const elh) ++{ ++ pool_ent_t * ent; ++ while ((ent = ent_extract_tail(elh)) != NULL) { ++ pool_recycle(pc, ent); ++ } ++} + -+// Enables User mode access to the PMU (must be called in a priviledged mode) -+void enable_pmu_user_access(void); ++static pool_ent_t * pool_best_fit(vzc_pool_ctl_t * const pc, size_t req_size) ++{ ++ pool_ent_t * best = NULL; + -+// Disables User mode access to the PMU (must be called in a priviledged mode) -+void disable_pmu_user_access(void); ++ vlc_mutex_lock(&pc->lock); + -+#endif -+// ------------------------------------------------------------ -+// End of v7_pmu.h -+// ------------------------------------------------------------ ++ { ++ pool_ent_t * ent = pc->ent_pool.ents; + ---- a/modules/hw/mmal/vout.c -+++ b/modules/hw/mmal/vout.c -@@ -27,21 +27,28 @@ - #endif - - #include -+#include - - #include --#include - #include - #include - #include -+#include - --#include "mmal_picture.h" -- -+#pragma GCC diagnostic push -+#pragma GCC diagnostic ignored "-Wbad-function-cast" - #include -+#pragma GCC diagnostic pop - #include - #include - #include - #include --#include ++ // Simple scan ++ while (ent != NULL) { ++ if (ent->size >= req_size && ent->size <= req_size * 2 + POOL_ENT_ALLOC_BLOCK && ++ (best == NULL || best->size > ent->size)) ++ best = ent; ++ ent = ent->next; ++ } + -+#include "mmal_picture.h" -+#include "subpic.h" -+#include "transform_ops.h" ++ // extract best from chain if we've found it ++ ent_extract(&pc->ent_pool, best); ++ } + -+#define TRACE_ALL 0 - - #define MAX_BUFFERS_IN_TRANSIT 1 - #define VC_TV_MAX_MODE_IDS 127 -@@ -50,10 +57,28 @@ - #define MMAL_LAYER_TEXT N_("VideoCore layer where the video is displayed.") - #define MMAL_LAYER_LONGTEXT N_("VideoCore layer where the video is displayed. Subpictures are displayed directly above and a black background directly below.") - --#define MMAL_BLANK_BACKGROUND_NAME "mmal-blank-background" --#define MMAL_BLANK_BACKGROUND_TEXT N_("Blank screen below video.") --#define MMAL_BLANK_BACKGROUND_LONGTEXT N_("Render blank screen below video. " \ -- "Increases VideoCore load.") -+#define MMAL_DISPLAY_NAME "mmal-display" -+#define MMAL_DISPLAY_TEXT N_("Output device for Rpi fullscreen.") -+#define MMAL_DISPLAY_LONGTEXT N_("Output device for Rpi fullscreen. " \ -+"Valid values are HDMI-1,HDMI-2. By default if qt-fullscreen-screennumber " \ -+"is specified (or set by Fullscreen Output Device in Preferences) " \ -+"HDMI- will be used, otherwise HDMI-1.") ++ vlc_mutex_unlock(&pc->lock); + -+#define MMAL_VOUT_TRANSFORM_NAME "mmal-vout-transform" -+#define MMAL_VOUT_TRANSFORM_TEXT N_("Video transform for Rpi fullscreen.") -+#define MMAL_VOUT_TRANSFORM_LONGTEXT N_("Video transform for Rpi fullscreen."\ -+"Transforms availible: auto, 0, 90, 180, 270, hflip, vflip, transpose, antitranspose") ++ if (best == NULL) ++ best = pool_ent_alloc_new(req_size); + -+#define MMAL_VOUT_WINDOW_NAME "mmal-vout-window" -+#define MMAL_VOUT_WINDOW_TEXT N_("Display window for Rpi fullscreen") -+#define MMAL_VOUT_WINDOW_LONGTEXT N_("Display window for Rpi fullscreen."\ -+"fullscreen|x++") ++ if ((best->seq = ++pc->seq) == 0) ++ best->seq = ++pc->seq; // Never allow to be zero + -+#define MMAL_VOUT_TRANSPARENT_NAME "mmal-vout-transparent" -+#define MMAL_VOUT_TRANSPARENT_TEXT N_("Enable layers beneeth the vodeo layer.") -+#define MMAL_VOUT_TRANSPARENT_LONGTEXT N_("Enable layers beneath the video layer."\ -+" By default these are disabled."\ -+" Having the lower layers enabled can impact video performance") - - #define MMAL_ADJUST_REFRESHRATE_NAME "mmal-adjust-refreshrate" - #define MMAL_ADJUST_REFRESHRATE_TEXT N_("Adjust HDMI refresh rate to the video.") -@@ -68,332 +93,628 @@ - #define PHASE_OFFSET_TARGET ((double)0.25) - #define PHASE_CHECK_INTERVAL 100 - --static int Open(vlc_object_t *); --static void Close(vlc_object_t *); -- --vlc_module_begin() -- set_shortname(N_("MMAL vout")) -- set_description(N_("MMAL-based vout plugin for Raspberry Pi")) -- set_capability("vout display", 90) -- add_shortcut("mmal_vout") -- add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false) -- add_bool(MMAL_BLANK_BACKGROUND_NAME, true, MMAL_BLANK_BACKGROUND_TEXT, -- MMAL_BLANK_BACKGROUND_LONGTEXT, true); -- add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT, -- MMAL_ADJUST_REFRESHRATE_LONGTEXT, false) -- add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT, -- MMAL_NATIVE_INTERLACE_LONGTEXT, false) -- set_callbacks(Open, Close) --vlc_module_end() -+#define SUBS_MAX 4 - --struct dmx_region_t { -- struct dmx_region_t *next; -- picture_t *picture; -- VC_RECT_T bmp_rect; -- VC_RECT_T src_rect; -- VC_RECT_T dst_rect; -- VC_DISPMANX_ALPHA_T alpha; -- DISPMANX_ELEMENT_HANDLE_T element; -- DISPMANX_RESOURCE_HANDLE_T resource; -- int32_t pos_x; -- int32_t pos_y; --}; -+typedef struct vout_subpic_s { -+ MMAL_COMPONENT_T *component; -+ subpic_reg_stash_t sub; -+} vout_subpic_t; - - struct vout_display_sys_t { -- vlc_cond_t buffer_cond; -- vlc_mutex_t buffer_mutex; - vlc_mutex_t manage_mutex; - -- plane_t planes[3]; /* Depending on video format up to 3 planes are used */ -- picture_t **pictures; /* Actual list of alloced pictures passed into picture_pool */ -- picture_pool_t *picture_pool; -- -+ vcsm_init_type_t init_type; - MMAL_COMPONENT_T *component; - MMAL_PORT_T *input; - MMAL_POOL_T *pool; /* mmal buffer headers, used for pushing pictures to component*/ -- struct dmx_region_t *dmx_region; - int i_planes; /* Number of actually used planes, 1 for opaque, 3 for i420 */ - -- uint32_t buffer_size; /* size of actual mmal buffers */ - int buffers_in_transit; /* number of buffers currently pushed to mmal component */ - unsigned num_buffers; /* number of buffers allocated at mmal port */ - -- DISPMANX_DISPLAY_HANDLE_T dmx_handle; -- DISPMANX_ELEMENT_HANDLE_T bkg_element; -- DISPMANX_RESOURCE_HANDLE_T bkg_resource; -- unsigned display_width; -- unsigned display_height; -+ int display_id; -+ MMAL_RECT_T win_rect; // Window rect after transform(s) -+ MMAL_RECT_T display_rect; // Actual shape of display (x, y always 0) -+ MMAL_RECT_T req_win; // User requested window (w=0 => fullscreen) ++ atomic_store(&best->ref_count, 1); ++ return best; ++} + -+ MMAL_RECT_T spu_rect; // Output rectangle in cfg coords (for subpic placement) -+ MMAL_RECT_T dest_rect; // Output rectangle in display coords -+ MMAL_DISPLAYTRANSFORM_T dest_transform; // Dest window coord transform -+ MMAL_DISPLAYTRANSFORM_T display_transform; // "Native" display transform -+ MMAL_DISPLAYTRANSFORM_T video_transform; // Combined config+native transform - -- int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */ -- int i_frame_rate; -+ unsigned int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */ -+ unsigned int i_frame_rate; - - int next_phase_check; /* lowpass for phase check frequency */ - int phase_offset; /* currently applied offset to presentation time in ns */ - int layer; /* the dispman layer (z-index) used for video rendering */ -+ bool transparent; // Do not disable layers beneath ours - - bool need_configure_display; /* indicates a required display reconfigure to main thread */ - bool adjust_refresh_rate; - bool native_interlaced; - bool b_top_field_first; /* cached interlaced settings to detect changes for native mode */ - bool b_progressive; -- bool opaque; /* indicated use of opaque picture format (zerocopy) */ --}; -+ bool force_config; - --static const vlc_fourcc_t subpicture_chromas[] = { -- VLC_CODEC_RGBA, -- 0 --}; -+ vout_subpic_t subs[SUBS_MAX]; -+ // Stash for subpics derived from the passed subpicture rather than -+ // included with the main pic -+ MMAL_BUFFER_HEADER_T * subpic_bufs[SUBS_MAX]; + -+ picture_pool_t * pic_pool; ++const vlc_fourcc_t hw_mmal_vzc_subpicture_chromas[] = { VLC_CODEC_RGBA, VLC_CODEC_BGRA, VLC_CODEC_ARGB, 0 }; + -+ struct vout_isp_conf_s { -+ MMAL_COMPONENT_T *component; -+ MMAL_PORT_T * input; -+ MMAL_PORT_T * output; -+ MMAL_QUEUE_T * out_q; -+ MMAL_POOL_T * in_pool; -+ MMAL_POOL_T * out_pool; -+ bool pending; -+ } isp; - --/* Utility functions */ --static inline uint32_t align(uint32_t x, uint32_t y); --static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg, -- const video_format_t *fmt); -+ MMAL_POOL_T * copy_pool; -+ MMAL_BUFFER_HEADER_T * copy_buf; - --/* VLC vout display callbacks */ --static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count); --static void vd_prepare(vout_display_t *vd, picture_t *picture, -- subpicture_t *subpicture); --static void vd_display(vout_display_t *vd, picture_t *picture, -- subpicture_t *subpicture); --static int vd_control(vout_display_t *vd, int query, va_list args); --static void vd_manage(vout_display_t *vd); -- --/* MMAL callbacks */ --static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); --static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); -+ // Subpic blend if we have to do it here -+ vzc_pool_ctl_t * vzc; -+}; - --/* TV service */ --static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height); --static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, -- uint32_t param2); --static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt); --static int set_latency_target(vout_display_t *vd, bool enable); - --/* DispManX */ --static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture); --static void close_dmx(vout_display_t *vd); --static struct dmx_region_t *dmx_region_new(vout_display_t *vd, -- DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region); --static void dmx_region_update(struct dmx_region_t *dmx_region, -- DISPMANX_UPDATE_HANDLE_T update, picture_t *picture); --static void dmx_region_delete(struct dmx_region_t *dmx_region, -- DISPMANX_UPDATE_HANDLE_T update); --static void show_background(vout_display_t *vd, bool enable); --static void maintain_phase_sync(vout_display_t *vd); -+// ISP setup - --static int Open(vlc_object_t *object) -+static inline bool want_isp(const vout_display_t * const vd) - { -- vout_display_t *vd = (vout_display_t *)object; -- vout_display_sys_t *sys; -- uint32_t buffer_pitch, buffer_height; -- vout_display_place_t place; -- MMAL_DISPLAYREGION_T display_region; -- MMAL_STATUS_T status; -- int ret = VLC_SUCCESS; -- unsigned i; -+ return (vd->fmt.i_chroma == VLC_CODEC_MMAL_ZC_SAND10); -+} - -- if (vout_display_IsWindowed(vd)) -- return VLC_EGENERIC; -+static inline bool want_copy(const vout_display_t * const vd) -+{ -+ return (vd->fmt.i_chroma == VLC_CODEC_I420 || vd->fmt.i_chroma == VLC_CODEC_I420_10L); -+} - -- sys = calloc(1, sizeof(struct vout_display_sys_t)); -- if (!sys) -- return VLC_ENOMEM; -- vd->sys = sys; -+static inline vlc_fourcc_t req_chroma(const vout_display_t * const vd) -+{ -+ return !hw_mmal_chroma_is_mmal(vd->fmt.i_chroma) && !want_copy(vd) ? -+ VLC_CODEC_I420 : -+ vd->fmt.i_chroma; -+} - -- sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME); -- bcm_host_init(); -+static MMAL_FOURCC_T vout_vlc_to_mmal_pic_fourcc(const unsigned int fcc) ++void hw_mmal_vzc_buf_get_wh(MMAL_BUFFER_HEADER_T * const buf, int * const pW, int * const pH) +{ -+ switch (fcc){ -+ case VLC_CODEC_MMAL_OPAQUE: -+ return MMAL_ENCODING_OPAQUE; -+ case VLC_CODEC_MMAL_ZC_SAND8: -+ return MMAL_ENCODING_YUVUV128; -+ case VLC_CODEC_MMAL_ZC_SAND10: -+ return MMAL_ENCODING_YUVUV64_10; -+ case VLC_CODEC_MMAL_ZC_SAND30: -+ return MMAL_ENCODING_YUV10_COL; -+ case VLC_CODEC_MMAL_ZC_I420: -+ case VLC_CODEC_I420: -+ return MMAL_ENCODING_I420; -+ default: -+ break; -+ } -+ return MMAL_ENCODING_I420; ++ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent; ++ *pW = ent->width; ++ *pH = ent->height; +} - -- sys->opaque = vd->fmt.i_chroma == VLC_CODEC_MMAL_OPAQUE; -+static void display_set_format(const vout_display_t * const vd, MMAL_ES_FORMAT_T *const es_fmt, const bool is_intermediate) ++ ++bool hw_mmal_vzc_buf_set_format(MMAL_BUFFER_HEADER_T * const buf, MMAL_ES_FORMAT_T * const es_fmt) +{ -+ const unsigned int w = is_intermediate ? vd->fmt.i_visible_width : vd->fmt.i_width ; -+ const unsigned int h = is_intermediate ? vd->fmt.i_visible_height : vd->fmt.i_height; ++ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent; + MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video; - -- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component); -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)", -- MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; ++ + es_fmt->type = MMAL_ES_TYPE_VIDEO; -+ es_fmt->encoding = is_intermediate ? MMAL_ENCODING_I420 : vout_vlc_to_mmal_pic_fourcc(vd->fmt.i_chroma); ++ es_fmt->encoding = ent->enc_type; + es_fmt->encoding_variant = 0; + -+ v_fmt->width = (w + 31) & ~31; -+ v_fmt->height = (h + 15) & ~15; ++ v_fmt->width = ent->width; ++ v_fmt->height = ent->height; + v_fmt->crop.x = 0; + v_fmt->crop.y = 0; -+ v_fmt->crop.width = w; -+ v_fmt->crop.height = h; -+ if (vd->fmt.i_sar_num == 0 || vd->fmt.i_sar_den == 0) { -+ v_fmt->par.num = 1; -+ v_fmt->par.den = 1; -+ } else { -+ v_fmt->par.num = vd->fmt.i_sar_num; -+ v_fmt->par.den = vd->fmt.i_sar_den; - } -+ v_fmt->frame_rate.num = vd->fmt.i_frame_rate; -+ v_fmt->frame_rate.den = vd->fmt.i_frame_rate_base; -+ v_fmt->color_space = vlc_to_mmal_color_space(vd->fmt.space); - -- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd; -- status = mmal_port_enable(sys->component->control, control_port_cb); -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)", -- sys->component->control->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+ msg_Dbg(vd, "WxH: %dx%d, Crop: %dx%d", v_fmt->width, v_fmt->height, v_fmt->crop.width, v_fmt->crop.height); ++ v_fmt->crop.width = ent->width; ++ v_fmt->crop.height = ent->height; ++ ++ return true; +} + -+static MMAL_RECT_T -+display_src_rect(const vout_display_t * const vd, const video_format_t * const src) ++void hw_mmal_vzc_buf_frame_size(MMAL_BUFFER_HEADER_T * const buf, ++ uint32_t * const pWidth, uint32_t * const pHeight) +{ -+ const bool wants_isp = want_isp(vd); -+ -+ // Scale source derived cropping to actual picture shape -+ return (MMAL_RECT_T){ -+ .x = wants_isp ? 0 : src->i_x_offset * vd->fmt.i_width / src->i_width, -+ .y = wants_isp ? 0 : src->i_y_offset * vd->fmt.i_height / src->i_height, -+ .width = src->i_visible_width * vd->fmt.i_width / src->i_width, -+ .height = src->i_visible_height * vd->fmt.i_height / src->i_height -+ }; ++ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent; ++ *pWidth = ent->width; ++ *pHeight = ent->height; +} + -+static void isp_input_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) ++ ++MMAL_DISPLAYREGION_T * hw_mmal_vzc_buf_region(MMAL_BUFFER_HEADER_T * const buf) +{ -+#if TRACE_ALL -+ vout_display_t * const vd = (vout_display_t *)port->userdata; -+ pic_ctx_mmal_t * ctx = buf->user_data; -+ msg_Dbg(vd, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buf->cmd, ctx, buf, -+ buf->flags, (long long)buf->pts); -+#else -+ VLC_UNUSED(port); -+#endif ++ vzc_subbuf_ent_t * sb = buf->user_data; ++ return &sb->dreg; ++} + -+ mmal_buffer_header_release(buf); ++static inline int rescale_x(int x, int mul, int div) ++{ ++ return div == 0 ? x * mul : (x * mul + div/2) / div; ++} + -+#if TRACE_ALL -+ msg_Dbg(vd, ">>> %s", __func__); ++static void rescale_rect(MMAL_RECT_T * const d, const MMAL_RECT_T * const s, const MMAL_RECT_T * mul_rect, const MMAL_RECT_T * div_rect) ++{ ++ d->x = rescale_x(s->x - div_rect->x, mul_rect->width, div_rect->width) + mul_rect->x; ++ d->y = rescale_x(s->y - div_rect->y, mul_rect->height, div_rect->height) + mul_rect->y; ++ d->width = rescale_x(s->width, mul_rect->width, div_rect->width); ++ d->height = rescale_x(s->height, mul_rect->height, div_rect->height); ++#if TRACE_TRANSFORMS ++ fprintf(stderr, "(%d,%d %dx%d) * (%d,%d %dx%d) / (%d,%d %dx%d) -> (%d,%d %dx%d)\n", ++ s->x, s->y, s->width, s->height, ++ mul_rect->x, mul_rect->y, mul_rect->width, mul_rect->height, ++ div_rect->x, div_rect->y, div_rect->width, div_rect->height, ++ d->x, d->y, d->width, d->height); +#endif +} + -+static void isp_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++static MMAL_RECT_T ++rect_untransform(MMAL_RECT_T s, const MMAL_RECT_T c, const MMAL_DISPLAYTRANSFORM_T t) +{ -+ vout_display_t *vd = (vout_display_t *)port->userdata; -+ MMAL_STATUS_T status; -+ -+ if (buffer->cmd == MMAL_EVENT_ERROR) { -+ status = *(uint32_t *)buffer->data; -+ msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); - } - -- sys->input = sys->component->input[0]; -- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd; -+ mmal_buffer_header_release(buffer); ++#if TRACE_TRANSFORMS ++ fprintf(stderr, "t=%d, s=%d,%d:%dx%d, c=%d,%d:%dx%d -> ", (int)t, ++ s.x,s.y,s.width,s.height, ++ c.x,c.y,c.width,c.height); ++#endif ++ if (is_transform_hflip(t)) ++ s = rect_hflip(s, c); ++ if (is_transform_vflip(t) != 0) ++ s = rect_vflip(s, c); ++ if (is_transform_transpose(t) != 0) ++ s = rect_transpose(s); ++#if TRACE_TRANSFORMS ++ fprintf(stderr, "s=%d,%d:%dx%d\n", ++ s.x,s.y,s.width,s.height); ++#endif ++ return s; +} - -- if (sys->opaque) { -- sys->input->format->encoding = MMAL_ENCODING_OPAQUE; -- sys->i_planes = 1; -- sys->buffer_size = sys->input->buffer_size_recommended; -- } else { -- sys->input->format->encoding = MMAL_ENCODING_I420; -- vd->fmt.i_chroma = VLC_CODEC_I420; -- buffer_pitch = align(vd->fmt.i_width, 32); -- buffer_height = align(vd->fmt.i_height, 16); -- sys->i_planes = 3; -- sys->buffer_size = 3 * buffer_pitch * buffer_height / 2; -- } -- -- sys->input->format->es->video.width = vd->fmt.i_width; -- sys->input->format->es->video.height = vd->fmt.i_height; -- sys->input->format->es->video.crop.x = 0; -- sys->input->format->es->video.crop.y = 0; -- sys->input->format->es->video.crop.width = vd->fmt.i_width; -- sys->input->format->es->video.crop.height = vd->fmt.i_height; -- sys->input->format->es->video.par.num = vd->source.i_sar_num; -- sys->input->format->es->video.par.den = vd->source.i_sar_den; -+static void isp_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) ++ ++void hw_mmal_vzc_buf_scale_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const MMAL_RECT_T * const scale_rect, const MMAL_DISPLAYTRANSFORM_T scale_transform) +{ -+ if (buf->cmd == 0 && buf->length != 0) -+ { -+ // The filter structure etc. should always exist if we have contents -+ // but might not on later flushes as we shut down -+ vout_display_t * const vd = (vout_display_t *)port->userdata; -+ struct vout_isp_conf_s *const isp = &vd->sys->isp; - -- status = mmal_port_format_commit(sys->input); -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s: cmd=%d; flags=%#x, pts=%lld", __func__, buf->cmd, buf->flags, (long long) buf->pts); -+#endif -+ mmal_queue_put(isp->out_q, buf); -+#if TRACE_ALL -+ msg_Dbg(vd, ">>> %s: out Q len=%d", __func__, mmal_queue_length(isp->out_q)); -+#endif - } -- sys->input->buffer_size = sys->input->buffer_size_recommended; ++ vzc_subbuf_ent_t * sb = buf->user_data; ++ if (scale_rect == NULL) { ++ sb->dreg.dest_rect = sb->orig_dest_rect; ++ sb->dreg.transform = MMAL_DISPLAY_ROT0; ++ } + else + { -+ mmal_buffer_header_reset(buf); -+ mmal_buffer_header_release(buf); ++ // The scale rect has been transposed if we have a transposing ++ // transform - untranspose so we are the same way up as the source ++ const MMAL_RECT_T c = (scale_transform & 4) == 0 ? *scale_rect : rect_transpose(*scale_rect); ++ rescale_rect(&sb->dreg.dest_rect, &sb->orig_dest_rect, ++ &c, &sb->pic_rect); ++ sb->dreg.dest_rect = rect_untransform(sb->dreg.dest_rect, c, scale_transform); ++ sb->dreg.transform = scale_transform; + } +} - -- vout_display_PlacePicture(&place, &vd->source, vd->cfg, false); -- display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION; -- display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T); -- display_region.fullscreen = MMAL_FALSE; -- display_region.src_rect.x = vd->fmt.i_x_offset; -- display_region.src_rect.y = vd->fmt.i_y_offset; -- display_region.src_rect.width = vd->fmt.i_visible_width; -- display_region.src_rect.height = vd->fmt.i_visible_height; -- display_region.dest_rect.x = place.x; -- display_region.dest_rect.y = place.y; -- display_region.dest_rect.width = place.width; -- display_region.dest_rect.height = place.height; -- display_region.layer = sys->layer; -- display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT | -- MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER; -- status = mmal_port_parameter_set(sys->input, &display_region.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)", -- status, mmal_status_to_string(status)); -- ret = VLC_EGENERIC; -- goto out; -+static void isp_empty_out_q(struct vout_isp_conf_s * const isp) -+{ -+ MMAL_BUFFER_HEADER_T * buf; -+ // We can be called as part of error recovery so allow for missing Q -+ if (isp->out_q == NULL) -+ return; + -+ while ((buf = mmal_queue_get(isp->out_q)) != NULL) -+ mmal_buffer_header_release(buf); ++unsigned int hw_mmal_vzc_buf_seq(MMAL_BUFFER_HEADER_T * const buf) ++{ ++ vzc_subbuf_ent_t * sb = buf->user_data; ++ return sb->ent->seq; +} + -+static void isp_flush(struct vout_isp_conf_s * const isp) -+{ -+ if (!isp->input->is_enabled) -+ mmal_port_disable(isp->input); + -+ if (isp->output->is_enabled) -+ mmal_port_disable(isp->output); ++// The intent with the ents_cur & ents_last stuff is to remember the buffers ++// we used on the last frame and reuse them on the current one if they are the ++// same. Unfortunately detection of "is_first" is only a heuristic (there are ++// no rules governing the order in which things are blended) so we must deal ++// (fairly) gracefully with it never (or always) being set. + -+ isp_empty_out_q(isp); -+ isp->pending = false; -+} ++// dst_fmt gives the number space in which the destination pixels are specified + -+static MMAL_STATUS_T isp_prepare(vout_display_t * const vd, struct vout_isp_conf_s * const isp) ++MMAL_BUFFER_HEADER_T * hw_mmal_vzc_buf_from_pic(vzc_pool_ctl_t * const pc, ++ picture_t * const pic, ++ const video_format_t * src_fmt, ++ const MMAL_RECT_T dst_pic_rect, ++ const int x_offset, const int y_offset, ++ const unsigned int alpha, ++ const bool is_first) +{ -+ MMAL_STATUS_T err; -+ MMAL_BUFFER_HEADER_T * buf; ++ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_get(pc->buf_pool->queue); ++ vzc_subbuf_ent_t * sb; + -+ if (!isp->output->is_enabled) { -+ if ((err = mmal_port_enable(isp->output, isp_output_cb)) != MMAL_SUCCESS) ++ if (buf == NULL) ++ return NULL; ++ ++ if ((sb = calloc(1, sizeof(*sb))) == NULL) ++ goto fail1; ++ ++ // If first or we've had a lot of stuff move everything to the last list ++ // (we could deal more gracefully with the "too many" case but it shouldn't ++ // really happen) ++ if (is_first || pc->ents_cur.n >= CTX_BUFS_MAX) { ++ pool_recycle_list(pc, &pc->ents_prev); ++ ent_list_move(&pc->ents_prev, &pc->ents_cur); ++ } ++ ++ sb->dreg.hdr.id = MMAL_PARAMETER_DISPLAYREGION; ++ sb->dreg.hdr.size = sizeof(sb->dreg); ++ buf->user_data = sb; ++ ++ { ++ // ?? Round start offset as well as length ++ const video_format_t *const fmt = &pic->format; ++ ++ const unsigned int bpp = (fmt->i_bits_per_pixel + 7) >> 3; ++ const unsigned int xl = (fmt->i_x_offset & ~15); ++ const unsigned int xr = (fmt->i_x_offset + fmt->i_visible_width + 15) & ~15; ++ const size_t dst_stride = (xr - xl) * bpp; ++ const size_t dst_lines = ((fmt->i_visible_height + 15) & ~15); ++ const size_t dst_size = dst_stride * dst_lines; ++ ++ pool_ent_t * ent = ent_list_extract_pic_ent(&pc->ents_prev, pic); ++ bool needs_copy = false; ++ ++ // If we didn't find ent in last then look in cur in case is_first ++ // isn't working ++ if (ent == NULL) ++ ent = ent_list_extract_pic_ent(&pc->ents_cur, pic); ++ ++// printf("ent_found: %p\n", ent); ++ ++ if (ent == NULL) + { -+ msg_Err(vd, "ISP output port enable failed"); -+ return err; ++ // Need a new ent ++ needs_copy = true; ++ ++ if ((ent = pool_best_fit(pc, dst_size)) == NULL) ++ goto fail2; ++ if ((ent->enc_type = vlc_to_mmal_video_fourcc(&pic->format)) == 0) ++ goto fail2; ++ ++ ent->pic = picture_Hold(pic); + } - } - -- for (i = 0; i < sys->i_planes; ++i) { -- sys->planes[i].i_lines = buffer_height; -- sys->planes[i].i_pitch = buffer_pitch; -- sys->planes[i].i_visible_lines = vd->fmt.i_visible_height; -- sys->planes[i].i_visible_pitch = vd->fmt.i_visible_width; -+ while ((buf = mmal_queue_get(isp->out_pool->queue)) != NULL) { -+ if ((err = mmal_port_send_buffer(isp->output, buf)) != MMAL_SUCCESS) ++ ++ ent_add_head(&pc->ents_cur, ent); ++ ++ sb->ent = pool_ent_ref(ent); ++ hw_mmal_vzc_pool_ref(pc); ++ ++ // Copy data ++ buf->next = NULL; ++ buf->cmd = 0; ++ buf->data = (uint8_t *)(ent->vc_hdl); ++ buf->alloc_size = buf->length = dst_size; ++ buf->offset = 0; ++ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END; ++ buf->pts = buf->dts = pic->date != VLC_TICK_INVALID ? pic->date : MMAL_TIME_UNKNOWN; ++ buf->type->video = (MMAL_BUFFER_HEADER_VIDEO_SPECIFIC_T){ ++ .planes = 1, ++ .pitch = { dst_stride } ++ }; ++ ++ // Remember offsets ++ sb->dreg.set = MMAL_DISPLAY_SET_SRC_RECT | ++ MMAL_DISPLAY_SET_DEST_RECT | ++ MMAL_DISPLAY_SET_FULLSCREEN | ++ MMAL_DISPLAY_SET_TRANSFORM | ++ MMAL_DISPLAY_SET_ALPHA; ++ ++ sb->dreg.fullscreen = 0; ++ ++ // Will be set later - zero now to avoid any confusion ++ sb->dreg.transform = MMAL_DISPLAY_ROT0; ++ sb->dreg.dest_rect = (MMAL_RECT_T){0, 0, 0, 0}; ++ ++ sb->dreg.alpha = (uint32_t)(alpha & 0xff) | MMAL_DISPLAY_ALPHA_FLAGS_MIX; ++ ++// printf("+++ bpp:%d, vis:%dx%d wxh:%dx%d, d:%dx%d\n", bpp, fmt->i_visible_width, fmt->i_visible_height, fmt->i_width, fmt->i_height, dst_stride, dst_lines); ++ ++ sb->dreg.src_rect = (MMAL_RECT_T){ ++ .x = (fmt->i_x_offset - xl) + src_fmt->i_x_offset, ++ .y = src_fmt->i_y_offset, ++ .width = src_fmt->i_visible_width, ++ .height = src_fmt->i_visible_height ++ }; ++ ++ sb->pic_rect = dst_pic_rect; ++ ++ sb->orig_dest_rect = (MMAL_RECT_T){ ++ .x = x_offset, ++ .y = y_offset, ++ .width = src_fmt->i_visible_width, ++ .height = src_fmt->i_visible_height ++ }; ++ ++ if (needs_copy) + { -+ msg_Err(vd, "ISP output port stuff failed"); -+ return err; ++ ent->width = dst_stride / bpp; ++ ent->height = dst_lines; ++ ++ // 2D copy ++ { ++ uint8_t *d = ent->buf; ++ const uint8_t *s = pic->p[0].p_pixels + xl * bpp + fmt->i_y_offset * pic->p[0].i_pitch; ++ ++ mem_copy_2d(d, dst_stride, s, pic->p[0].i_pitch, fmt->i_visible_height, dst_stride); ++ ++ // And make sure it is actually in memory ++ if (pc->vcsm_init_type != VCSM_INIT_CMA) { // ** CMA is currently always uncached ++ flush_range(ent->buf, dst_stride * fmt->i_visible_height); ++ } ++ } + } + } - -- if (i > 0) { -- sys->planes[i].i_lines /= 2; -- sys->planes[i].i_pitch /= 2; -- sys->planes[i].i_visible_lines /= 2; -- sys->planes[i].i_visible_pitch /= 2; -+ if (!isp->input->is_enabled) { -+ if ((err = mmal_port_enable(isp->input, isp_input_cb)) != MMAL_SUCCESS) -+ { -+ msg_Err(vd, "ISP input port enable failed"); -+ return err; - } - } -+ return MMAL_SUCCESS; -+} - -- vlc_mutex_init(&sys->buffer_mutex); -- vlc_cond_init(&sys->buffer_cond); -- vlc_mutex_init(&sys->manage_mutex); -+static void isp_close(vout_display_t * const vd, vout_display_sys_t * const vd_sys) -+{ -+ struct vout_isp_conf_s * const isp = &vd_sys->isp; -+ VLC_UNUSED(vd); - -- vd->pool = vd_pool; -- vd->prepare = vd_prepare; -- vd->display = vd_display; -- vd->control = vd_control; -- vd->manage = vd_manage; -+ if (isp->component == NULL) -+ return; - -- vc_tv_register_callback(tvservice_cb, vd); -+ isp_flush(isp); - -- if (query_resolution(vd, &sys->display_width, &sys->display_height) >= 0) { -- vout_display_SendEventDisplaySize(vd, sys->display_width, sys->display_height); -- } else { -- sys->display_width = vd->cfg->display.width; -- sys->display_height = vd->cfg->display.height; -+ if (isp->component->control->is_enabled) -+ mmal_port_disable(isp->component->control); + -+ if (isp->out_q != NULL) { -+ // 1st junk anything lying around -+ isp_empty_out_q(isp); ++ return buf; + -+ mmal_queue_destroy(isp->out_q); -+ isp->out_q = NULL; - } - -- sys->dmx_handle = vc_dispmanx_display_open(0); -- vd->info.subpicture_chromas = subpicture_chromas; -+ if (isp->out_pool != NULL) { -+ mmal_port_pool_destroy(isp->output, isp->out_pool); -+ isp->out_pool = NULL; -+ } - -- vout_display_DeleteWindow(vd, NULL); -+ isp->input = NULL; -+ isp->output = NULL; - --out: -- if (ret != VLC_SUCCESS) -- Close(object); -+ mmal_component_release(isp->component); -+ isp->component = NULL; - -- return ret; -+ return; - } - --static void Close(vlc_object_t *object) -+// Restuff into output rather than return to pool is we can -+static MMAL_BOOL_T isp_out_pool_cb(MMAL_POOL_T *pool, MMAL_BUFFER_HEADER_T *buffer, void *userdata) - { -- vout_display_t *vd = (vout_display_t *)object; -- vout_display_sys_t *sys = vd->sys; -- char response[20]; /* answer is hvs_update_fields=%1d */ -- unsigned i; -+ struct vout_isp_conf_s * const isp = userdata; -+ VLC_UNUSED(pool); -+ if (isp->output->is_enabled) { -+ mmal_buffer_header_reset(buffer); -+ if (mmal_port_send_buffer(isp->output, buffer) == MMAL_SUCCESS) -+ return MMAL_FALSE; -+ } -+ return MMAL_TRUE; ++fail2: ++ free(sb); ++fail1: ++ mmal_buffer_header_release(buf); ++ return NULL; +} - -- vc_tv_unregister_callback_full(tvservice_cb, vd); -+static MMAL_STATUS_T isp_setup(vout_display_t * const vd, vout_display_sys_t * const vd_sys) ++ ++void hw_mmal_vzc_pool_flush(vzc_pool_ctl_t * const pc) +{ -+ struct vout_isp_conf_s * const isp = &vd_sys->isp; -+ MMAL_STATUS_T err; - -- if (sys->dmx_handle) -- close_dmx(vd); -+ if ((err = mmal_component_create(MMAL_COMPONENT_ISP_RESIZER, &isp->component)) != MMAL_SUCCESS) { -+ msg_Err(vd, "Cannot create ISP component"); -+ return err; -+ } -+ isp->input = isp->component->input[0]; -+ isp->output = isp->component->output[0]; - -- if (sys->component && sys->component->control->is_enabled) -- mmal_port_disable(sys->component->control); -+ isp->component->control->userdata = (void *)vd; -+ if ((err = mmal_port_enable(isp->component->control, isp_control_port_cb)) != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to enable ISP control port"); -+ goto fail; -+ } - -- if (sys->input && sys->input->is_enabled) -- mmal_port_disable(sys->input); -+ isp->input->userdata = (void *)vd; -+ display_set_format(vd, isp->input->format, false); - -- if (sys->component && sys->component->is_enabled) -- mmal_component_disable(sys->component); -+ if ((err = port_parameter_set_bool(isp->input, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) -+ goto fail; - -- if (sys->pool) -- mmal_port_pool_destroy(sys->input, sys->pool); -+ if ((err = mmal_port_format_commit(isp->input)) != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to set ISP input format"); -+ goto fail; -+ } - -- if (sys->component) -- mmal_component_release(sys->component); -+ isp->input->buffer_size = isp->input->buffer_size_recommended; -+ isp->input->buffer_num = 30; - -- if (sys->picture_pool) -- picture_pool_Release(sys->picture_pool); -- else -- for (i = 0; i < sys->num_buffers; ++i) -- if (sys->pictures[i]) { -- mmal_buffer_header_release(sys->pictures[i]->p_sys->buffer); -- picture_Release(sys->pictures[i]); -- } -+ if ((isp->in_pool = mmal_pool_create(isp->input->buffer_num, 0)) == NULL) -+ { -+ msg_Err(vd, "Failed to create input pool"); -+ goto fail; -+ } - -- vlc_mutex_destroy(&sys->buffer_mutex); -- vlc_cond_destroy(&sys->buffer_cond); -- vlc_mutex_destroy(&sys->manage_mutex); -+ if ((isp->out_q = mmal_queue_create()) == NULL) -+ { -+ err = MMAL_ENOMEM; -+ goto fail; -+ } - -- if (sys->native_interlaced) { -- if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 || -- response[18] != '0') -- msg_Warn(vd, "Could not reset hvs field mode"); -+ display_set_format(vd, isp->output->format, true); ++ pool_recycle_list(pc, &pc->ents_prev); ++ pool_recycle_list(pc, &pc->ents_cur); ++} + -+ if ((err = port_parameter_set_bool(isp->output, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) -+ goto fail; ++static void hw_mmal_vzc_pool_delete(vzc_pool_ctl_t * const pc) ++{ + -+ if ((err = mmal_port_format_commit(isp->output)) != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to set ISP input format"); -+ goto fail; - } - -- free(sys->pictures); -- free(sys); -+ isp->output->buffer_size = isp->output->buffer_size_recommended; -+ isp->output->buffer_num = 2; -+ isp->output->userdata = (void *)vd; ++// printf("<<< %s\n", __func__); + -+ if ((isp->out_pool = mmal_port_pool_create(isp->output, isp->output->buffer_num, isp->output->buffer_size)) == NULL) -+ { -+ msg_Err(vd, "Failed to make ISP port pool"); -+ goto fail; -+ } ++ hw_mmal_vzc_pool_flush(pc); + -+ mmal_pool_callback_set(isp->out_pool, isp_out_pool_cb, isp); ++ ent_free_list(&pc->ent_pool); + -+ if ((err = isp_prepare(vd, isp)) != MMAL_SUCCESS) -+ goto fail; ++ if (pc->buf_pool != NULL) ++ mmal_pool_destroy(pc->buf_pool); + -+ return MMAL_SUCCESS; - -- bcm_host_deinit(); -+fail: -+ isp_close(vd, vd_sys); -+ return err; - } - --static inline uint32_t align(uint32_t x, uint32_t y) { -- uint32_t mod = x % y; -- if (mod == 0) -- return x; -+static MMAL_STATUS_T isp_check(vout_display_t * const vd, vout_display_sys_t * const vd_sys) -+{ -+ struct vout_isp_conf_s *const isp = &vd_sys->isp; -+ const bool has_isp = (isp->component != NULL); -+ const bool wants_isp = want_isp(vd); ++ vlc_mutex_destroy(&pc->lock); + -+ if (has_isp == wants_isp) -+ { -+ // All OK - do nothing -+ } -+ else if (has_isp) -+ { -+ // ISP active but we don't want it -+ isp_flush(isp); ++ cma_vcsm_exit(pc->vcsm_init_type); + -+ // Check we have everything back and then kill it -+ if (mmal_queue_length(isp->out_pool->queue) == isp->output->buffer_num) -+ isp_close(vd, vd_sys); -+ } - else -- return x + y - mod; -+ { -+ // ISP closed but we want it -+ return isp_setup(vd, vd_sys); -+ } ++// memset(pc, 0xba, sizeof(*pc)); // Zap for (hopefully) faster crash ++ free (pc); + -+ return MMAL_SUCCESS; ++ // printf(">>> %s\n", __func__); +} + -+/* TV service */ -+static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, -+ uint32_t param2); -+static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt); -+static int set_latency_target(vout_display_t *vd, bool enable); ++void hw_mmal_vzc_pool_release(vzc_pool_ctl_t * const pc) ++{ ++ int n; + -+// Mmal -+static void maintain_phase_sync(vout_display_t *vd); ++ if (pc == NULL) ++ return; + ++ n = atomic_fetch_sub(&pc->ref_count, 1) - 1; + ++ if (n != 0) ++ return; + -+static void vd_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) -+{ -+#if TRACE_ALL -+ vout_display_t * const vd = (vout_display_t *)port->userdata; -+ pic_ctx_mmal_t * ctx = buf->user_data; -+ msg_Dbg(vd, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buf->cmd, ctx, buf, -+ buf->flags, (long long)buf->pts); -+#else -+ VLC_UNUSED(port); -+#endif ++ hw_mmal_vzc_pool_delete(pc); ++} + -+ mmal_buffer_header_release(buf); -+ -+#if TRACE_ALL -+ msg_Dbg(vd, ">>> %s", __func__); -+#endif ++void hw_mmal_vzc_pool_ref(vzc_pool_ctl_t * const pc) ++{ ++ atomic_fetch_add(&pc->ref_count, 1); +} + -+static int query_resolution(vout_display_t *vd, const int display_id, unsigned *width, unsigned *height) ++static MMAL_BOOL_T vcz_pool_release_cb(MMAL_POOL_T * buf_pool, MMAL_BUFFER_HEADER_T *buf, void *userdata) +{ -+ TV_DISPLAY_STATE_T display_state = {0}; -+ int ret = 0; ++ vzc_pool_ctl_t * const pc = userdata; ++ vzc_subbuf_ent_t * const sb = buf->user_data; + -+ if (vc_tv_get_display_state_id(display_id, &display_state) == 0) { -+ msg_Dbg(vd, "State=%#x", display_state.state); -+ if (display_state.state & 0xFF) { -+ msg_Dbg(vd, "HDMI: %dx%d", display_state.display.hdmi.width, display_state.display.hdmi.height); -+ *width = display_state.display.hdmi.width; -+ *height = display_state.display.hdmi.height; -+ } else if (display_state.state & 0xFF00) { -+ msg_Dbg(vd, "SDTV: %dx%d", display_state.display.sdtv.width, display_state.display.sdtv.height); -+ *width = display_state.display.sdtv.width; -+ *height = display_state.display.sdtv.height; -+ } else { -+ msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state); -+ ret = -1; -+ } -+ } else { -+ msg_Warn(vd, "Failed to query display resolution"); -+ ret = -1; ++ VLC_UNUSED(buf_pool); ++ ++// printf("<<< %s\n", __func__); ++ ++ if (sb != NULL) { ++ buf->user_data = NULL; ++ pool_recycle(pc, sb->ent); ++ hw_mmal_vzc_pool_release(pc); ++ free(sb); + } + -+ return ret; -+} ++// printf(">>> %s\n", __func__); + -+static inline MMAL_RECT_T -+place_to_mmal_rect(const vout_display_place_t place) -+{ -+ return (MMAL_RECT_T){ -+ .x = place.x, -+ .y = place.y, -+ .width = place.width, -+ .height = place.height -+ }; ++ return MMAL_TRUE; +} + -+static MMAL_RECT_T -+place_out(const vout_display_cfg_t * cfg, -+ const video_format_t * fmt, -+ const MMAL_RECT_T r) ++vzc_pool_ctl_t * hw_mmal_vzc_pool_new() +{ -+ video_format_t tfmt; -+ vout_display_cfg_t tcfg; -+ vout_display_place_t place; ++ vzc_pool_ctl_t * const pc = calloc(1, sizeof(*pc)); + -+ // Fix SAR if unknown -+ if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) { -+ tfmt = *fmt; -+ tfmt.i_sar_den = 1; -+ tfmt.i_sar_num = 1; -+ fmt = &tfmt; ++ if (pc == NULL) ++ return NULL; ++ ++ if ((pc->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) ++ { ++ free(pc); ++ return NULL; + } + -+ // Override what VLC thinks might be going on with display size -+ // if we know better -+ if (r.width != 0 && r.height != 0) ++ pc->max_n = 8; ++ vlc_mutex_init(&pc->lock); // Must init before potential destruction ++ ++ if ((pc->buf_pool = mmal_pool_create(64, 0)) == NULL) + { -+ tcfg = *cfg; -+ tcfg.display.width = r.width; -+ tcfg.display.height = r.height; -+ cfg = &tcfg; ++ hw_mmal_vzc_pool_delete(pc); ++ return NULL; + } + -+ vout_display_PlacePicture(&place, fmt, cfg, false); ++ atomic_store(&pc->ref_count, 1); + -+ place.x += r.x; -+ place.y += r.y; ++ mmal_pool_callback_set(pc->buf_pool, vcz_pool_release_cb, pc); + -+ return place_to_mmal_rect(place); ++ return pc; +} + -+static MMAL_RECT_T -+rect_transform(MMAL_RECT_T s, const MMAL_RECT_T c, const MMAL_DISPLAYTRANSFORM_T t) ++//---------------------------------------------------------------------------- ++ ++ ++static const uint8_t shift_00[] = {0,0,0,0}; ++static const uint8_t shift_01[] = {0,1,1,1}; ++ ++int cma_pic_set_data(picture_t * const pic, ++ const MMAL_ES_FORMAT_T * const mm_esfmt, ++ const MMAL_BUFFER_HEADER_T * const buf) +{ -+ if (is_transform_transpose(t)) -+ s = rect_transpose(s); -+ if (is_transform_hflip(t)) -+ s = rect_hflip(s, c); -+ if (is_transform_vflip(t) != 0) -+ s = rect_vflip(s, c); -+ return s; ++ const MMAL_VIDEO_FORMAT_T * const mm_fmt = &mm_esfmt->es->video; ++ const MMAL_BUFFER_HEADER_VIDEO_SPECIFIC_T *const buf_vid = (buf == NULL) ? NULL : &buf->type->video; ++ cma_buf_t *const cb = cma_buf_pic_get(pic); ++ unsigned int planes = 1; ++ ++ uint8_t * const data = cma_buf_addr(cb); ++ if (data == NULL) { ++ return VLC_ENOMEM; ++ } ++ ++ const uint8_t * ws = shift_00; ++ const uint8_t * hs = shift_00; ++ int pb = 1; ++ ++ switch (mm_esfmt->encoding) ++ { ++ case MMAL_ENCODING_ARGB: ++ case MMAL_ENCODING_ABGR: ++ case MMAL_ENCODING_RGBA: ++ case MMAL_ENCODING_BGRA: ++ case MMAL_ENCODING_RGB32: ++ case MMAL_ENCODING_BGR32: ++ pb = 4; ++ break; ++ case MMAL_ENCODING_RGB16: ++ pb = 2; ++ break; ++ ++ case MMAL_ENCODING_I420: ++ ws = shift_01; ++ hs = shift_01; ++ planes = 3; ++ break; ++ ++ case MMAL_ENCODING_YUVUV128: ++ hs = shift_01; ++ planes = 2; ++ break; ++ ++ default: ++// msg_Err(p_filter, "%s: Unexpected format", __func__); ++ return VLC_EGENERIC; ++ } ++ ++ // Fix up SAR if unset ++ if (pic->format.i_sar_den == 0 || pic->format.i_sar_num == 0) { ++ pic->format.i_sar_den = mm_fmt->par.den; ++ pic->format.i_sar_num = mm_fmt->par.num; ++ } ++ ++ pic->i_planes = planes; ++ unsigned int offset = 0; ++ for (unsigned int i = 0; i != planes; ++i) { ++ pic->p[i] = (plane_t){ ++ .p_pixels = data + (buf_vid != NULL ? buf_vid->offset[i] : offset), ++ .i_lines = mm_fmt->height >> hs[i], ++ .i_pitch = buf_vid != NULL ? buf_vid->pitch[i] : mm_fmt->width * pb, ++ .i_pixel_pitch = pb, ++ .i_visible_lines = mm_fmt->crop.height >> hs[i], ++ .i_visible_pitch = mm_fmt->crop.width >> ws[i] ++ }; ++ offset += pic->p[i].i_pitch * pic->p[i].i_lines; ++ } ++ return VLC_SUCCESS; +} + -+static void -+place_dest_rect(vout_display_t * const vd, -+ const vout_display_cfg_t * const cfg, -+ const video_format_t * fmt) ++int cma_buf_pic_attach(cma_buf_t * const cb, picture_t * const pic) +{ -+ vout_display_sys_t * const sys = vd->sys; -+ sys->dest_rect = rect_transform(place_out(cfg, fmt, sys->win_rect), -+ sys->display_rect, sys->dest_transform); ++ if (!is_cma_buf_pic_chroma(pic->format.i_chroma)) ++ return VLC_EGENERIC; ++ if (pic->context != NULL) ++ return VLC_EBADVAR; ++ ++ pic_ctx_mmal_t * const ctx = calloc(1, sizeof(pic_ctx_mmal_t)); ++ ++ if (ctx == NULL) ++ return VLC_ENOMEM; ++ ++ ctx->cmn.copy = hw_mmal_pic_ctx_copy; ++ ctx->cmn.destroy = hw_mmal_pic_ctx_destroy; ++ ctx->buf_count = 1; // cb takes the place of the 1st buf ++ ctx->cb = cb; ++ ++ cma_buf_in_flight(cb); ++ ++ pic->context = &ctx->cmn; ++ return VLC_SUCCESS; +} + -+static void -+place_spu_rect(vout_display_t * const vd, -+ const vout_display_cfg_t * const cfg, -+ const video_format_t * fmt) -+{ -+ vout_display_sys_t * const sys = vd->sys; -+ static const MMAL_RECT_T r0 = {0}; + -+ sys->spu_rect = place_out(cfg, fmt, r0); -+ sys->spu_rect.x = 0; -+ sys->spu_rect.y = 0; ++//---------------------------------------------------------------------------- + -+ // Copy place override logic for spu pos from video_output.c -+ // This info doesn't appear to reside anywhere natively ++/* Returns the type of the Pi being used ++*/ ++bool rpi_is_model_pi4(void) ++{ ++ return bcm_host_is_model_pi4(); ++} + -+ if (fmt->i_width * fmt->i_height >= (unsigned int)(sys->spu_rect.width * sys->spu_rect.height)) { -+ sys->spu_rect.width = fmt->i_visible_width; -+ sys->spu_rect.height = fmt->i_visible_height; -+ } ++// Board types that support PI3 hybrid HEVC accel ++bool rpi_use_pi3_hevc(void) ++{ ++ const int t = bcm_host_get_model_type(); ++ return ++ t == BCM_HOST_BOARD_TYPE_PI3MODELB || ++ t == BCM_HOST_BOARD_TYPE_CM3 || ++ t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS || ++ t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS || ++ t == BCM_HOST_BOARD_TYPE_PI3MODELAPLUS || ++ t == BCM_HOST_BOARD_TYPE_CM3PLUS; ++} + -+ if (ORIENT_IS_SWAP(fmt->orientation)) -+ sys->spu_rect = rect_transpose(sys->spu_rect); ++// Board types that support qpu adv deinterlace ++bool rpi_use_qpu_deinterlace(void) ++{ ++ const int t = bcm_host_get_model_type(); ++ return ++ t == BCM_HOST_BOARD_TYPE_MODELA || ++ t == BCM_HOST_BOARD_TYPE_MODELB || ++ t == BCM_HOST_BOARD_TYPE_MODELAPLUS || ++ t == BCM_HOST_BOARD_TYPE_MODELBPLUS || ++ t == BCM_HOST_BOARD_TYPE_PI2MODELB || ++ t == BCM_HOST_BOARD_TYPE_CM || ++ t == BCM_HOST_BOARD_TYPE_CM2 || ++ t == BCM_HOST_BOARD_TYPE_PI3MODELB || ++ t == BCM_HOST_BOARD_TYPE_PI0 || ++ t == BCM_HOST_BOARD_TYPE_CM3 || ++ t == BCM_HOST_BOARD_TYPE_PI0W || ++ t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS || ++ t == BCM_HOST_BOARD_TYPE_PI3MODELAPLUS || ++ t == BCM_HOST_BOARD_TYPE_CM3PLUS; +} + -+static void -+place_rects(vout_display_t * const vd, -+ const vout_display_cfg_t * const cfg, -+ const video_format_t * fmt) ++ ++// Preferred mode - none->cma on Pi4 otherwise legacy ++static volatile vcsm_init_type_t last_vcsm_type = VCSM_INIT_NONE; ++ ++vcsm_init_type_t cma_vcsm_type(void) +{ -+ place_dest_rect(vd, cfg, fmt); -+ place_spu_rect(vd, cfg, fmt); ++ return last_vcsm_type; +} + -+static int -+set_input_region(vout_display_t * const vd, const video_format_t * const fmt) ++vcsm_init_type_t cma_vcsm_init(void) +{ -+ const vout_display_sys_t * const sys = vd->sys; -+ MMAL_DISPLAYREGION_T display_region = { -+ .hdr = { -+ .id = MMAL_PARAMETER_DISPLAYREGION, -+ .size = sizeof(MMAL_DISPLAYREGION_T) -+ }, -+ .display_num = sys->display_id, -+ .fullscreen = MMAL_FALSE, -+ .transform = sys->video_transform, -+ .dest_rect = sys->dest_rect, -+ .src_rect = display_src_rect(vd, fmt), -+ .noaspect = MMAL_TRUE, -+ .mode = MMAL_DISPLAY_MODE_FILL, -+ .layer = sys->layer, -+ .alpha = 0xff | (sys->transparent ? 0 : (1 << 29)), -+ .set = -+ MMAL_DISPLAY_SET_NUM | -+ MMAL_DISPLAY_SET_FULLSCREEN | -+ MMAL_DISPLAY_SET_TRANSFORM | -+ MMAL_DISPLAY_SET_DEST_RECT | -+ MMAL_DISPLAY_SET_SRC_RECT | -+ MMAL_DISPLAY_SET_NOASPECT | -+ MMAL_DISPLAY_SET_MODE | -+ MMAL_DISPLAY_SET_LAYER | -+ MMAL_DISPLAY_SET_ALPHA -+ }; -+ MMAL_STATUS_T status = mmal_port_parameter_set(sys->input, &display_region.hdr); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)", -+ status, mmal_status_to_string(status)); -+ return -EINVAL; ++ vcsm_init_type_t rv = VCSM_INIT_NONE; ++ // We don't bother locking - taking a copy here should be good enough ++ vcsm_init_type_t try_type = last_vcsm_type; ++ ++ if (try_type == VCSM_INIT_NONE) { ++ if (bcm_host_is_fkms_active() || ++ bcm_host_is_kms_active()) ++ try_type = VCSM_INIT_CMA; ++ else ++ try_type = VCSM_INIT_LEGACY; + } -+ return 0; - } - - static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg, - const video_format_t *fmt) - { -- vout_display_sys_t *sys = vd->sys; -- vout_display_place_t place; -- MMAL_DISPLAYREGION_T display_region; -+ vout_display_sys_t * const sys = vd->sys; - MMAL_STATUS_T status; - - if (!cfg && !fmt) ++ ++ if (try_type == VCSM_INIT_CMA) { ++ if (vcsm_init_ex(1, -1) == 0) ++ rv = VCSM_INIT_CMA; ++ else if (vcsm_init_ex(0, -1) == 0) ++ rv = VCSM_INIT_LEGACY; ++ } ++ else + { -+ msg_Err(vd, "%s: Missing cfg & fmt", __func__); - return -EINVAL; ++ if (vcsm_init_ex(0, -1) == 0) ++ rv = VCSM_INIT_LEGACY; ++ else if (vcsm_init_ex(1, -1) == 0) ++ rv = VCSM_INIT_CMA; + } + -+ isp_check(vd, sys); - - if (fmt) { - sys->input->format->es->video.par.num = fmt->i_sar_num; -@@ -412,30 +733,14 @@ static int configure_display(vout_displa - if (!cfg) - cfg = vd->cfg; - -- vout_display_PlacePicture(&place, fmt, cfg, false); -+ sys->video_transform = combine_transform( -+ vlc_to_mmal_transform(fmt->orientation), sys->display_transform); - -- display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION; -- display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T); -- display_region.fullscreen = MMAL_FALSE; -- display_region.src_rect.x = fmt->i_x_offset; -- display_region.src_rect.y = fmt->i_y_offset; -- display_region.src_rect.width = fmt->i_visible_width; -- display_region.src_rect.height = fmt->i_visible_height; -- display_region.dest_rect.x = place.x; -- display_region.dest_rect.y = place.y; -- display_region.dest_rect.width = place.width; -- display_region.dest_rect.height = place.height; -- display_region.layer = sys->layer; -- display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT | -- MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER; -- status = mmal_port_parameter_set(sys->input, &display_region.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)", -- status, mmal_status_to_string(status)); -+ place_rects(vd, cfg, fmt); ++ // Just in case this affects vcsm init do after that ++ if (rv != VCSM_INIT_NONE) ++ bcm_host_init(); + -+ if (set_input_region(vd, fmt) != 0) - return -EINVAL; -- } - -- show_background(vd, var_InheritBool(vd, MMAL_BLANK_BACKGROUND_NAME)); - sys->adjust_refresh_rate = var_InheritBool(vd, MMAL_ADJUST_REFRESHRATE_NAME); - sys->native_interlaced = var_InheritBool(vd, MMAL_NATIVE_INTERLACED); - if (sys->adjust_refresh_rate) { -@@ -446,204 +751,217 @@ static int configure_display(vout_displa - return 0; - } - -+static void kill_pool(vout_display_sys_t * const sys) -+{ -+ if (sys->pic_pool != NULL) { -+ picture_pool_Release(sys->pic_pool); -+ sys->pic_pool = NULL; -+ } ++ last_vcsm_type = rv; ++ return rv; +} + -+// Actual picture pool for MMAL opaques is just a set of trivial containers - static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count) - { -- vout_display_sys_t *sys = vd->sys; -- picture_resource_t picture_res; -- picture_pool_configuration_t picture_pool_cfg; -- video_format_t fmt = vd->fmt; -- MMAL_STATUS_T status; -- unsigned i; -+ vout_display_sys_t * const sys = vd->sys; - -- if (sys->picture_pool) { -- if (sys->num_buffers < count) -- msg_Warn(vd, "Picture pool with %u pictures requested, but we already have one with %u pictures", -- count, sys->num_buffers); -+ msg_Dbg(vd, "%s: fmt:%dx%d,sar:%d/%d; source:%dx%d", __func__, -+ vd->fmt.i_width, vd->fmt.i_height, vd->fmt.i_sar_num, vd->fmt.i_sar_den, vd->source.i_width, vd->source.i_height); - -- goto out; -+ if (sys->pic_pool == NULL) { -+ sys->pic_pool = picture_pool_NewFromFormat(&vd->fmt, count); - } -+ return sys->pic_pool; -+} - -- if (sys->opaque) { -- if (count <= NUM_ACTUAL_OPAQUE_BUFFERS) -- count = NUM_ACTUAL_OPAQUE_BUFFERS; -+static inline bool -+check_shape(vout_display_t * const vd, const picture_t * const p_pic) ++void cma_vcsm_exit(const vcsm_init_type_t init_mode) +{ -+ if (vd->fmt.i_width == p_pic->format.i_width && -+ vd->fmt.i_height == p_pic->format.i_height) -+ return true; -+ return false; ++ if (init_mode != VCSM_INIT_NONE) ++ { ++ vcsm_exit(); ++ bcm_host_deinit(); // Does nothing but add in case it ever does ++ } +} - -- MMAL_PARAMETER_BOOLEAN_T zero_copy = { -- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, -- 1 -- }; -+static void vd_display(vout_display_t *vd, picture_t *p_pic, -+ subpicture_t *subpicture) ++ ++const char * cma_vcsm_init_str(const vcsm_init_type_t init_mode) +{ -+ vout_display_sys_t * const sys = vd->sys; -+ MMAL_STATUS_T err; - -- status = mmal_port_parameter_set(sys->input, &zero_copy.hdr); -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- goto out; -- } -+#if TRACE_ALL ++ switch (init_mode) + { -+ char dbuf0[5]; -+ msg_Dbg(vd, "<<< %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %dx%d@%d,%d", __func__, -+ str_fourcc(dbuf0, p_pic->format.i_chroma), p_pic->format.i_width, p_pic->format.i_height, -+ p_pic->format.i_x_offset, p_pic->format.i_y_offset, -+ p_pic->format.i_visible_width, p_pic->format.i_visible_height, -+ p_pic->format.i_sar_num, p_pic->format.i_sar_den, -+ sys->dest_rect.width, sys->dest_rect.height, sys->dest_rect.x, sys->dest_rect.y); - } -- -- if (count < sys->input->buffer_num_recommended) -- count = sys->input->buffer_num_recommended; -- --#ifndef NDEBUG -- msg_Dbg(vd, "Creating picture pool with %u pictures", count); - #endif ++ case VCSM_INIT_CMA: ++ return "CMA"; ++ case VCSM_INIT_LEGACY: ++ return "Legacy"; ++ case VCSM_INIT_NONE: ++ return "none"; ++ default: ++ break; ++ } ++ return "???"; ++} ++ ++ +--- a/modules/hw/mmal/mmal_picture.h ++++ b/modules/hw/mmal/mmal_picture.h +@@ -24,19 +24,278 @@ + #ifndef VLC_MMAL_MMAL_PICTURE_H_ + #define VLC_MMAL_MMAL_PICTURE_H_ -- sys->input->buffer_num = count; -- status = mmal_port_enable(sys->input, input_port_cb); -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)", -- sys->input->name, status, mmal_status_to_string(status)); -- goto out; -+ // If we had subpics then we have attached them to the main pic in prepare -+ // so all we have to do here is delete the refs -+ if (subpicture != NULL) { -+ subpicture_Delete(subpicture); - } ++#include ++ + #include + #include -- status = mmal_component_enable(sys->component); -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)", -- sys->component->name, status, mmal_status_to_string(status)); -- goto out; -+ if (!check_shape(vd, p_pic)) -+ { -+ msg_Err(vd, "Pic/fmt shape mismatch"); -+ goto fail; -+ } ++#include "mmal_cma_pic.h" ++#include "mmal_cma.h" + -+ if (!sys->input->is_enabled && -+ (err = mmal_port_enable(sys->input, vd_input_port_cb)) != MMAL_SUCCESS) -+ { -+ msg_Err(vd, "Input port enable failed"); -+ goto fail; -+ } -+ // Stuff into input -+ // We assume the BH is already set up with values reflecting pic date etc. -+ if (sys->copy_buf != NULL) { -+ MMAL_BUFFER_HEADER_T *const buf = sys->copy_buf; -+ sys->copy_buf = NULL; -+#if TRACE_ALL -+ msg_Dbg(vd, "--- %s: Copy stuff", __func__); + /* Think twice before changing this. Incorrect values cause havoc. */ + #define NUM_ACTUAL_OPAQUE_BUFFERS 30 + +-struct picture_sys_t { +- vlc_object_t *owner; ++#ifndef VLC_TICK_INVALID ++#define VLC_TICK_INVALID VLC_TS_INVALID ++#define VLC_VER_3 1 ++#else ++#define VLC_VER_3 0 +#endif -+ if (mmal_port_send_buffer(sys->input, buf) != MMAL_SUCCESS) -+ { -+ mmal_buffer_header_release(buf); -+ msg_Err(vd, "Send copy buffer to render input failed"); -+ goto fail; -+ } - } -- -- sys->num_buffers = count; -- sys->pool = mmal_port_pool_create(sys->input, sys->num_buffers, -- sys->input->buffer_size); -- if (!sys->pool) { -- msg_Err(vd, "Failed to create MMAL pool for %u buffers of size %"PRIu32, -- count, sys->input->buffer_size); -- goto out; -+ else if (sys->isp.pending) { -+ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(sys->isp.out_q); -+ sys->isp.pending = false; -+#if TRACE_ALL -+ msg_Dbg(vd, "--- %s: ISP stuff", __func__); -+#endif -+ if (mmal_port_send_buffer(sys->input, buf) != MMAL_SUCCESS) -+ { -+ mmal_buffer_header_release(buf); -+ msg_Err(vd, "Send ISP buffer to render input failed"); -+ goto fail; -+ } - } -- -- memset(&picture_res, 0, sizeof(picture_resource_t)); -- sys->pictures = calloc(sys->num_buffers, sizeof(picture_t *)); -- for (i = 0; i < sys->num_buffers; ++i) { -- picture_res.p_sys = calloc(1, sizeof(picture_sys_t)); -- picture_res.p_sys->owner = (vlc_object_t *)vd; -- picture_res.p_sys->buffer = mmal_queue_get(sys->pool->queue); -- -- sys->pictures[i] = picture_NewFromResource(&fmt, &picture_res); -- if (!sys->pictures[i]) { -- msg_Err(vd, "Failed to create picture"); -- free(picture_res.p_sys); -- goto out; -+ else -+ { -+ MMAL_BUFFER_HEADER_T *const pic_buf = hw_mmal_pic_buf_replicated(p_pic, sys->pool); -+ if (pic_buf == NULL) -+ { -+ msg_Err(vd, "Replicated buffer get fail"); -+ goto fail; - } - -- sys->pictures[i]->i_planes = sys->i_planes; -- memcpy(sys->pictures[i]->p, sys->planes, sys->i_planes * sizeof(plane_t)); -- } - -- memset(&picture_pool_cfg, 0, sizeof(picture_pool_configuration_t)); -- picture_pool_cfg.picture_count = sys->num_buffers; -- picture_pool_cfg.picture = sys->pictures; -- picture_pool_cfg.lock = mmal_picture_lock; -+ // If dimensions have chnaged then fix that -+ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic)) -+ { -+ msg_Dbg(vd, "Reset port format"); -+ -+ // HVS can deal with on-line dimension changes -+ if (mmal_port_format_commit(sys->input) != MMAL_SUCCESS) -+ msg_Warn(vd, "Input format commit failed"); -+ } - -- sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg); -- if (!sys->picture_pool) { -- msg_Err(vd, "Failed to create picture pool"); -- goto out; -+ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS) -+ { -+ mmal_buffer_header_release(pic_buf); -+ msg_Err(vd, "Send buffer to input failed"); -+ goto fail; -+ } - } - --out: -- return sys->picture_pool; --} -- --static void vd_prepare(vout_display_t *vd, picture_t *picture, -- subpicture_t *subpicture) --{ -- vout_display_sys_t *sys = vd->sys; -- picture_sys_t *pic_sys = picture->p_sys; -- -- if (!sys->adjust_refresh_rate || pic_sys->displayed) -- return; -- -- /* Apply the required phase_offset to the picture, so that vd_display() -- * will be called at the corrected time from the core */ -- picture->date += sys->phase_offset; --} -- --static void vd_display(vout_display_t *vd, picture_t *picture, -- subpicture_t *subpicture) --{ -- vout_display_sys_t *sys = vd->sys; -- picture_sys_t *pic_sys = picture->p_sys; -- MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer; -- MMAL_STATUS_T status; -- -- if (picture->format.i_frame_rate != sys->i_frame_rate || -- picture->format.i_frame_rate_base != sys->i_frame_rate_base || -- picture->b_progressive != sys->b_progressive || -- picture->b_top_field_first != sys->b_top_field_first) { -- sys->b_top_field_first = picture->b_top_field_first; -- sys->b_progressive = picture->b_progressive; -- sys->i_frame_rate = picture->format.i_frame_rate; -- sys->i_frame_rate_base = picture->format.i_frame_rate_base; -- configure_display(vd, NULL, &picture->format); -- } -- -- if (!pic_sys->displayed || !sys->opaque) { -- buffer->cmd = 0; -- buffer->length = sys->input->buffer_size; -- buffer->user_data = picture; -- -- status = mmal_port_send_buffer(sys->input, buffer); -- if (status == MMAL_SUCCESS) -- atomic_fetch_add(&sys->buffers_in_transit, 1); -- -- if (status != MMAL_SUCCESS) { -- msg_Err(vd, "Failed to send buffer to input port. Frame dropped"); -- picture_Release(picture); -+ { -+ unsigned int sub_no = 0; -+ MMAL_BUFFER_HEADER_T **psub_bufs2 = sys->subpic_bufs; -+ const bool is_mmal_pic = hw_mmal_pic_is_mmal(p_pic); -+ -+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { -+ int rv; -+ MMAL_BUFFER_HEADER_T * const sub_buf = !is_mmal_pic ? NULL : -+ hw_mmal_pic_sub_buf_get(p_pic, sub_no); + -+ if ((rv = hw_mmal_subpic_update(VLC_OBJECT(vd), -+ sub_buf != NULL ? sub_buf : *psub_bufs2++, -+ &sys->subs[sub_no].sub, -+ &p_pic->format, -+ &sys->dest_rect, -+ sys->display_transform, -+ p_pic->date)) == 0) -+ break; -+ else if (rv < 0) -+ goto fail; - } -- -- pic_sys->displayed = true; -- } else { -- picture_Release(picture); - } - -- display_subpicture(vd, subpicture); -+fail: -+ for (unsigned int i = 0; i != SUBS_MAX && sys->subpic_bufs[i] != NULL; ++i) { -+ mmal_buffer_header_release(sys->subpic_bufs[i]); -+ sys->subpic_bufs[i] = NULL; -+ } - -- if (subpicture) -- subpicture_Delete(subpicture); -+ picture_Release(p_pic); - - if (sys->next_phase_check == 0 && sys->adjust_refresh_rate) - maintain_phase_sync(vd); - sys->next_phase_check = (sys->next_phase_check + 1) % PHASE_CHECK_INTERVAL; -- -- if (sys->opaque) { -- vlc_mutex_lock(&sys->buffer_mutex); -- while (atomic_load(&sys->buffers_in_transit) >= MAX_BUFFERS_IN_TRANSIT) -- vlc_cond_wait(&sys->buffer_cond, &sys->buffer_mutex); -- vlc_mutex_unlock(&sys->buffer_mutex); -- } - } - - static int vd_control(vout_display_t *vd, int query, va_list args) - { -- vout_display_sys_t *sys = vd->sys; -- vout_display_cfg_t cfg; -- const vout_display_cfg_t *tmp_cfg; -+ vout_display_sys_t * const sys = vd->sys; - int ret = VLC_EGENERIC; -+ VLC_UNUSED(args); - - switch (query) { -- case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: -- tmp_cfg = va_arg(args, const vout_display_cfg_t *); -- if (tmp_cfg->display.width == sys->display_width && -- tmp_cfg->display.height == sys->display_height) { -- cfg = *vd->cfg; -- cfg.display.width = sys->display_width; -- cfg.display.height = sys->display_height; -- if (configure_display(vd, &cfg, NULL) >= 0) -- ret = VLC_SUCCESS; -- } -- break; -- - case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: - case VOUT_DISPLAY_CHANGE_SOURCE_CROP: -- if (configure_display(vd, NULL, &vd->source) >= 0) -+ if (configure_display(vd, vd->cfg, &vd->source) >= 0) - ret = VLC_SUCCESS; - break; - -- case VOUT_DISPLAY_RESET_PICTURES: -- vlc_assert_unreachable(); - case VOUT_DISPLAY_CHANGE_ZOOM: -- msg_Warn(vd, "Unsupported control query %d", query); -+ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: -+ case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED: -+ { -+ const vout_display_cfg_t * const cfg = va_arg(args, const vout_display_cfg_t *); ++typedef struct mmal_port_pool_ref_s ++{ ++ atomic_uint refs; ++ MMAL_POOL_T * pool; ++ MMAL_PORT_T * port; ++} hw_mmal_port_pool_ref_t; + -+ if (configure_display(vd, cfg, &vd->source) >= 0) -+ ret = VLC_SUCCESS; -+ break; -+ } ++typedef struct pic_ctx_subpic_s { ++ picture_t * subpic; ++ int x, y; ++ int alpha; ++} pic_ctx_subpic_t; + -+ case VOUT_DISPLAY_RESET_PICTURES: -+ msg_Warn(vd, "Reset Pictures"); -+ kill_pool(sys); -+ vd->fmt = vd->source; // Take (nearly) whatever source wants to give us -+ vd->fmt.i_chroma = req_chroma(vd); // Adjust chroma to something we can actaully deal with -+ ret = VLC_SUCCESS; -+ break; + -+ case VOUT_DISPLAY_CHANGE_MMAL_HIDE: -+ { -+ MMAL_STATUS_T err; -+ unsigned int i; ++const char * str_fourcc(char * const buf, const unsigned int fcc); + -+ msg_Dbg(vd, "Hide display"); ++MMAL_FOURCC_T vlc_to_mmal_video_fourcc(const video_frame_format_t * const vf_vlc); ++MMAL_FOURCC_T vlc_to_mmal_color_space(const video_color_space_t vlc_cs); ++void hw_mmal_vlc_fmt_to_mmal_fmt(MMAL_ES_FORMAT_T *const es_fmt, const video_frame_format_t * const vf_vlc); ++// Returns true if fmt_changed ++// frame_rate ignored for compare, but is set if something else is updated ++bool hw_mmal_vlc_pic_to_mmal_fmt_update(MMAL_ES_FORMAT_T *const es_fmt, const picture_t * const pic); + -+ for (i = 0; i != SUBS_MAX; ++i) -+ hw_mmal_subpic_flush(VLC_OBJECT(vd), &sys->subs[i].sub); ++// Copy pic contents into an existing buffer ++int hw_mmal_copy_pic_to_buf(void * const buf_data, uint32_t * const pLength, ++ const MMAL_ES_FORMAT_T * const fmt, const picture_t * const pic); + -+ if (sys->input->is_enabled && -+ (err = mmal_port_disable(sys->input)) != MMAL_SUCCESS) -+ { -+ msg_Err(vd, "Unable to disable port: err=%d", err); -+ break; -+ } -+ sys->force_config = true; -+ ret = VLC_SUCCESS; - break; -+ } - - default: - msg_Warn(vd, "Unknown control query %d", query); -@@ -653,79 +971,207 @@ static int vd_control(vout_display_t *vd - return ret; - } - -+static void set_display_windows(vout_display_t *const vd, vout_display_sys_t *const sys) ++hw_mmal_port_pool_ref_t * hw_mmal_port_pool_ref_create(MMAL_PORT_T * const port, ++ const unsigned int headers, const uint32_t payload_size); ++void hw_mmal_port_pool_ref_release(hw_mmal_port_pool_ref_t * const ppr, const bool in_cb); ++bool hw_mmal_port_pool_ref_recycle(hw_mmal_port_pool_ref_t * const ppr, MMAL_BUFFER_HEADER_T * const buf); ++MMAL_STATUS_T hw_mmal_port_pool_ref_fill(hw_mmal_port_pool_ref_t * const ppr); ++static inline void hw_mmal_port_pool_ref_acquire(hw_mmal_port_pool_ref_t * const ppr) +{ -+ unsigned int width, height; -+ if (query_resolution(vd, sys->display_id, &width, &height) < 0) { -+ width = vd->cfg->display.width; -+ height = vd->cfg->display.height; -+ } -+ sys->display_rect = (MMAL_RECT_T){0, 0, width, height}; ++ atomic_fetch_add(&ppr->refs, 1); ++} ++MMAL_STATUS_T hw_mmal_opaque_output(vlc_object_t * const obj, ++ hw_mmal_port_pool_ref_t ** pppr, ++ MMAL_PORT_T * const port, ++ const unsigned int extra_buffers, MMAL_PORT_BH_CB_T callback); + -+ sys->win_rect = (sys->req_win.width != 0) ? -+ sys->req_win : -+ is_transform_transpose(sys->display_transform) ? -+ rect_transpose(sys->display_rect) : sys->display_rect; ++static inline int hw_mmal_pic_has_sub_bufs(picture_t * const pic) ++{ ++ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; ++ return ctx->buf_count > 1; +} + - static void vd_manage(vout_display_t *vd) - { -- vout_display_sys_t *sys = vd->sys; -- unsigned width, height; -+ vout_display_sys_t *const sys = vd->sys; - - vlc_mutex_lock(&sys->manage_mutex); - - if (sys->need_configure_display) { -- close_dmx(vd); -- sys->dmx_handle = vc_dispmanx_display_open(0); -- -- if (query_resolution(vd, &width, &height) >= 0) { -- sys->display_width = width; -- sys->display_height = height; -- vout_display_SendEventDisplaySize(vd, width, height); -- } -- - sys->need_configure_display = false; -+ set_display_windows(vd, sys); - } - - vlc_mutex_unlock(&sys->manage_mutex); - } - --static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++static inline void hw_mmal_pic_sub_buf_add(picture_t * const pic, MMAL_BUFFER_HEADER_T * const sub) ++{ ++ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; + -+static int attach_subpics(vout_display_t * const vd, vout_display_sys_t * const sys, -+ subpicture_t * const subpicture) - { -- vout_display_t *vd = (vout_display_t *)port->userdata; -- MMAL_STATUS_T status; -+ unsigned int n = 0; - -- if (buffer->cmd == MMAL_EVENT_ERROR) { -- status = *(uint32_t *)buffer->data; -- msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); -+ if (sys->vzc == NULL) { -+ if ((sys->vzc = hw_mmal_vzc_pool_new()) == NULL) -+ { -+ msg_Err(vd, "Failed to allocate VZC"); -+ return VLC_ENOMEM; -+ } - } - -- mmal_buffer_header_release(buffer); -+ // Attempt to import the subpics -+ for (subpicture_t * spic = subpicture; spic != NULL; spic = spic->p_next) -+ { -+ for (subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) { -+ picture_t *const src = sreg->p_picture; ++ if (ctx->buf_count >= CTX_BUFS_MAX) { ++ mmal_buffer_header_release(sub); ++ return; ++ } + -+#if TRACE_ALL -+ char dbuf0[5]; -+ msg_Dbg(vd, " [%p:%p] Pos=%d,%d max=%dx%d, src=%dx%d/%dx%d o:%d, spu=%d,%d:%dx%d, vd->fmt=%dx%d/%dx%d, vd->source=%dx%d/%dx%d, cfg=%dx%d, zoom=%d/%d, Alpha=%d, Fmt=%s", src, src->p[0].p_pixels, -+ sreg->i_x, sreg->i_y, -+ sreg->i_max_width, sreg->i_max_height, -+ src->format.i_visible_width, src->format.i_visible_height, -+ src->format.i_width, src->format.i_height, -+ src->format.orientation, -+ sys->spu_rect.x, sys->spu_rect.y, sys->spu_rect.width, sys->spu_rect.height, -+ vd->fmt.i_visible_width, vd->fmt.i_visible_height, -+ vd->fmt.i_width, vd->fmt.i_height, -+ vd->source.i_visible_width, vd->source.i_visible_height, -+ vd->source.i_width, vd->source.i_height, -+ vd->cfg->display.width, vd->cfg->display.height, -+ vd->cfg->zoom.num, vd->cfg->zoom.den, -+ sreg->i_alpha, -+ str_fourcc(dbuf0, src->format.i_chroma)); -+#endif ++ ctx->bufs[ctx->buf_count++] = sub; ++} + -+ // At this point I think the subtitles are being placed in the -+ // coord space of the placed rectangle in the cfg display space -+ if ((sys->subpic_bufs[n] = hw_mmal_vzc_buf_from_pic(sys->vzc, -+ src, -+ (MMAL_RECT_T){.width = sys->spu_rect.width, .height=sys->spu_rect.height}, -+ sreg->i_x, sreg->i_y, -+ sreg->i_alpha, -+ n == 0)) == NULL) -+ { -+ msg_Err(vd, "Failed to allocate vzc buffer for subpic"); -+ return VLC_ENOMEM; -+ } ++static inline MMAL_BUFFER_HEADER_T * hw_mmal_pic_sub_buf_get(picture_t * const pic, const unsigned int n) ++{ ++ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; + -+ if (++n == SUBS_MAX) -+ return VLC_SUCCESS; -+ } -+ } -+ return VLC_SUCCESS; - } - --static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++ return n + 1 > ctx->buf_count ? NULL : ctx->bufs[n + 1]; ++} + -+static void vd_prepare(vout_display_t *vd, picture_t *p_pic, -+#if VLC_VER_3 -+ subpicture_t *subpicture -+#else -+ subpicture_t *subpicture, vlc_tick_t date -+#endif -+ ) - { -- vout_display_t *vd = (vout_display_t *)port->userdata; -+ MMAL_STATUS_T err; -+ vout_display_sys_t * const sys = vd->sys; ++static inline bool hw_mmal_chroma_is_mmal(const vlc_fourcc_t chroma) ++{ ++ return ++ chroma == VLC_CODEC_MMAL_OPAQUE || ++ chroma == VLC_CODEC_MMAL_ZC_SAND8 || ++ chroma == VLC_CODEC_MMAL_ZC_SAND10 || ++ chroma == VLC_CODEC_MMAL_ZC_SAND30 || ++ chroma == VLC_CODEC_MMAL_ZC_I420 || ++ chroma == VLC_CODEC_MMAL_ZC_RGB32; ++} + -+ vd_manage(vd); ++static inline bool hw_mmal_pic_is_mmal(const picture_t * const pic) ++{ ++ return hw_mmal_chroma_is_mmal(pic->format.i_chroma); ++} + -+ if (!check_shape(vd, p_pic)) -+ return; ++picture_context_t * hw_mmal_pic_ctx_copy(picture_context_t * pic_ctx_cmn); ++void hw_mmal_pic_ctx_destroy(picture_context_t * pic_ctx_cmn); ++picture_context_t * hw_mmal_gen_context( ++ MMAL_BUFFER_HEADER_T * buf, hw_mmal_port_pool_ref_t * const ppr); + -+ if (sys->force_config || -+ p_pic->format.i_frame_rate != sys->i_frame_rate || -+ p_pic->format.i_frame_rate_base != sys->i_frame_rate_base || -+ p_pic->b_progressive != sys->b_progressive || -+ p_pic->b_top_field_first != sys->b_top_field_first) -+ { -+ sys->force_config = false; -+ sys->b_top_field_first = p_pic->b_top_field_first; -+ sys->b_progressive = p_pic->b_progressive; -+ sys->i_frame_rate = p_pic->format.i_frame_rate; -+ sys->i_frame_rate_base = p_pic->format.i_frame_rate_base; -+ configure_display(vd, NULL, &vd->source); -+ } ++int hw_mmal_get_gpu_mem(void); + -+ // Subpics can either turn up attached to the main pic or in the -+ // subpic list here - if they turn up here then process into temp -+ // buffers -+ if (subpicture != NULL) { -+ attach_subpics(vd, sys, subpicture); -+ } + -+ // ***** -+ if (want_copy(vd)) { -+ if (sys->copy_buf != NULL) { -+ msg_Err(vd, "Copy buf not NULL"); -+ mmal_buffer_header_release(sys->copy_buf); -+ sys->copy_buf = NULL; -+ } ++static inline MMAL_STATUS_T port_parameter_set_uint32(MMAL_PORT_T * port, uint32_t id, uint32_t val) ++{ ++ const MMAL_PARAMETER_UINT32_T param = { ++ .hdr = {.id = id, .size = sizeof(MMAL_PARAMETER_UINT32_T)}, ++ .value = val ++ }; ++ return mmal_port_parameter_set(port, ¶m.hdr); ++} + -+ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_wait(sys->copy_pool->queue); -+ // Copy 2d -+ hw_mmal_copy_pic_to_buf(buf->data, &buf->length, sys->input->format, p_pic); -+ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END; ++static inline MMAL_STATUS_T port_parameter_set_bool(MMAL_PORT_T * const port, const uint32_t id, const bool val) ++{ ++ const MMAL_PARAMETER_BOOLEAN_T param = { ++ .hdr = {.id = id, .size = sizeof(MMAL_PARAMETER_BOOLEAN_T)}, ++ .enable = val ++ }; ++ return mmal_port_parameter_set(port, ¶m.hdr); ++} + -+ sys->copy_buf = buf; -+ } ++static inline MMAL_STATUS_T port_send_replicated(MMAL_PORT_T * const port, MMAL_POOL_T * const rep_pool, ++ MMAL_BUFFER_HEADER_T * const src_buf, ++ const uint64_t seq) ++{ ++ MMAL_STATUS_T err; ++ MMAL_BUFFER_HEADER_T *const rep_buf = mmal_queue_wait(rep_pool->queue); + -+ if (isp_check(vd, sys) != MMAL_SUCCESS) { -+ return; -+ } ++ if (rep_buf == NULL) ++ return MMAL_ENOSPC; + -+ if (want_isp(vd)) -+ { -+ struct vout_isp_conf_s * const isp = &sys->isp; -+ MMAL_BUFFER_HEADER_T * buf; ++ if ((err = mmal_buffer_header_replicate(rep_buf, src_buf)) != MMAL_SUCCESS) ++ return err; + -+ // This should be empty - make it so if it isn't -+ isp_empty_out_q(isp); -+ isp->pending = false; ++ rep_buf->pts = seq; + -+ // Stuff output -+ if (isp_prepare(vd, isp) != MMAL_SUCCESS) -+ return; ++ if ((err = mmal_port_send_buffer(port, rep_buf)) != MMAL_SUCCESS) ++ { ++ mmal_buffer_header_release(rep_buf); ++ return err; ++ } + -+ if ((buf = hw_mmal_pic_buf_replicated(p_pic, isp->in_pool)) == NULL) -+ { -+ msg_Err(vd, "Pic has no attached buffer"); -+ return; -+ } ++ return MMAL_SUCCESS; ++} + -+ if ((err = mmal_port_send_buffer(isp->input, buf)) != MMAL_SUCCESS) -+ { -+ msg_Err(vd, "Send buffer to input failed"); -+ mmal_buffer_header_release(buf); -+ return; -+ } + -+ isp->pending = true; ++static inline void pic_to_buf_copy_props(MMAL_BUFFER_HEADER_T * const buf, const picture_t * const pic) ++{ ++ if (!pic->b_progressive) ++ { ++ buf->flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; ++ buf->type->video.flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; ++ } ++ else ++ { ++ buf->flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; ++ buf->type->video.flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; ++ } ++ if (pic->b_top_field_first) ++ { ++ buf->flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; ++ buf->type->video.flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; ++ } ++ else ++ { ++ buf->flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; ++ buf->type->video.flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; + } ++ buf->pts = pic->date != VLC_TICK_INVALID ? pic->date : MMAL_TIME_UNKNOWN; ++ buf->dts = buf->pts; ++} + -+#if 0 -+ VLC_UNUSED(date); - vout_display_sys_t *sys = vd->sys; -- picture_t *picture = (picture_t *)buffer->user_data; -+ picture_sys_t *pic_sys = picture->p_sys; - -- if (picture) -- picture_Release(picture); -+ if (!sys->adjust_refresh_rate || pic_sys->displayed) -+ return; - -- vlc_mutex_lock(&sys->buffer_mutex); -- atomic_fetch_sub(&sys->buffers_in_transit, 1); -- vlc_cond_signal(&sys->buffer_cond); -- vlc_mutex_unlock(&sys->buffer_mutex); -+ /* Apply the required phase_offset to the picture, so that vd_display() -+ * will be called at the corrected time from the core */ -+ picture->date += sys->phase_offset; -+#endif - } - --static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height) ++static inline void buf_to_pic_copy_props(picture_t * const pic, const MMAL_BUFFER_HEADER_T * const buf) ++{ ++ // Contrary to docn the interlace & tff flags turn up in the header flags rather than the ++ // video specific flags (which appear to be currently unused). ++ pic->b_progressive = (buf->flags & MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED) == 0; ++ pic->b_top_field_first = (buf->flags & MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST) != 0; ++ ++ pic->date = buf->pts != MMAL_TIME_UNKNOWN ? buf->pts : ++ buf->dts != MMAL_TIME_UNKNOWN ? buf->dts : ++ VLC_TICK_INVALID; ++} ++ ++MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_copied(const picture_t *const pic, ++ MMAL_POOL_T * const rep_pool, ++ MMAL_PORT_T * const port, ++ cma_buf_pool_t * const cbp); ++ ++MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_replicated(const picture_t *const pic, MMAL_POOL_T * const rep_pool); ++ ++struct vzc_pool_ctl_s; ++typedef struct vzc_pool_ctl_s vzc_pool_ctl_t; ++ ++// At the moment we cope with any mono-planar RGBA thing ++// We could cope with many other things but they currently don't occur ++extern const vlc_fourcc_t hw_mmal_vzc_subpicture_chromas[]; ++static inline bool hw_mmal_vzc_subpic_fmt_valid(const video_frame_format_t * const vf_vlc) ++{ ++ const vlc_fourcc_t vfcc_src = vf_vlc->i_chroma; ++ for (const vlc_fourcc_t * p = hw_mmal_vzc_subpicture_chromas; *p != 0; ++p) ++ if (*p == vfcc_src) ++ return true; ++ ++ return false; ++} ++ ++bool hw_mmal_vzc_buf_set_format(MMAL_BUFFER_HEADER_T * const buf, MMAL_ES_FORMAT_T * const es_fmt); ++MMAL_DISPLAYREGION_T * hw_mmal_vzc_buf_region(MMAL_BUFFER_HEADER_T * const buf); ++void hw_mmal_vzc_buf_scale_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const MMAL_RECT_T * const scale_rect, const MMAL_DISPLAYTRANSFORM_T scale_transform); ++void hw_mmal_vzc_buf_get_wh(MMAL_BUFFER_HEADER_T * const buf, int * const pW, int * const pH); ++unsigned int hw_mmal_vzc_buf_seq(MMAL_BUFFER_HEADER_T * const buf); ++MMAL_BUFFER_HEADER_T * hw_mmal_vzc_buf_from_pic(vzc_pool_ctl_t * const pc, picture_t * const pic, ++ const video_format_t * src_fmt, ++ const MMAL_RECT_T dst_pic_rect, ++ const int x_offset, const int y_offset, ++ const unsigned int alpha, const bool is_first); ++void hw_mmal_vzc_buf_frame_size(MMAL_BUFFER_HEADER_T * const buf, ++ uint32_t * const pWidth, uint32_t * const pHeight); ++ ++void hw_mmal_vzc_pool_flush(vzc_pool_ctl_t * const pc); ++void hw_mmal_vzc_pool_release(vzc_pool_ctl_t * const pc); ++void hw_mmal_vzc_pool_ref(vzc_pool_ctl_t * const pc); ++vzc_pool_ctl_t * hw_mmal_vzc_pool_new(void); ++ ++ ++static inline MMAL_RECT_T vis_mmal_rect(const video_format_t * const fmt) ++{ ++ return (MMAL_RECT_T){ ++ .x = fmt->i_x_offset, ++ .y = fmt->i_y_offset, ++ .width = fmt->i_visible_width, ++ .height = fmt->i_visible_height ++ }; ++} ++ ++int cma_pic_set_data(picture_t * const pic, ++ const MMAL_ES_FORMAT_T * const mm_esfmt, ++ const MMAL_BUFFER_HEADER_T * const buf); ++ ++// Attaches cma buf to pic ++// Marks in_flight if not all_in_flight anyway ++int cma_buf_pic_attach(cma_buf_t * const cb, picture_t * const pic); ++ ++int rpi_get_model_type(void); ++bool rpi_is_model_pi4(void); ++bool rpi_use_pi3_hevc(void); ++bool rpi_use_qpu_deinterlace(void); ++bool rpi_is_fkms_active(void); ++ ++typedef enum vcsm_init_type_e { ++ VCSM_INIT_NONE = 0, ++ VCSM_INIT_LEGACY, ++ VCSM_INIT_CMA ++} vcsm_init_type_t; ++ ++vcsm_init_type_t cma_vcsm_init(void); ++void cma_vcsm_exit(const vcsm_init_type_t init_mode); ++vcsm_init_type_t cma_vcsm_type(void); ++const char * cma_vcsm_init_str(const vcsm_init_type_t init_mode); + -+static void vd_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) - { -- TV_DISPLAY_STATE_T display_state; -- int ret = 0; -+ vout_display_t *vd = (vout_display_t *)port->userdata; -+ MMAL_STATUS_T status; - -- if (vc_tv_get_display_state(&display_state) == 0) { -- if (display_state.state & 0xFF) { -- *width = display_state.display.hdmi.width; -- *height = display_state.display.hdmi.height; -- } else if (display_state.state & 0xFF00) { -- *width = display_state.display.sdtv.width; -- *height = display_state.display.sdtv.height; -- } else { -- msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state); -- ret = -1; -- } -- } else { -- msg_Warn(vd, "Failed to query display resolution"); -- ret = -1; -+ if (buffer->cmd == MMAL_EVENT_ERROR) { -+ status = *(uint32_t *)buffer->data; -+ msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); - } - -- return ret; -+ mmal_buffer_header_release(buffer); - } - - static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, uint32_t param2) -@@ -780,9 +1226,9 @@ static void adjust_refresh_rate(vout_dis - double best_score, score; - int i; -- vc_tv_get_display_state(&display_state); -+ vc_tv_get_display_state_id(sys->display_id, &display_state); - if(display_state.display.hdmi.mode != HDMI_MODE_OFF) { -- num_modes = vc_tv_hdmi_get_supported_modes_new(display_state.display.hdmi.group, -+ num_modes = vc_tv_hdmi_get_supported_modes_new_id(sys->display_id, display_state.display.hdmi.group, - supported_modes, VC_TV_MAX_MODE_IDS, NULL, NULL); +- MMAL_BUFFER_HEADER_T *buffer; +- bool displayed; +-}; ++#define VOUT_DISPLAY_CHANGE_MMAL_BASE 1024 ++#define VOUT_DISPLAY_CHANGE_MMAL_HIDE (VOUT_DISPLAY_CHANGE_MMAL_BASE + 0) - for (i = 0; i < num_modes; ++i) { -@@ -810,7 +1256,7 @@ static void adjust_refresh_rate(vout_dis - if((best_id >= 0) && (display_state.display.hdmi.mode != supported_modes[best_id].code)) { - msg_Info(vd, "Setting HDMI refresh rate to %"PRIu32, - supported_modes[best_id].frame_rate); -- vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_HDMI, -+ vc_tv_hdmi_power_on_explicit_new_id(sys->display_id, HDMI_MODE_HDMI, - supported_modes[best_id].group, - supported_modes[best_id].code); - } -@@ -828,148 +1274,12 @@ static void adjust_refresh_rate(vout_dis - } - } +-int mmal_picture_lock(picture_t *picture); ++#define MMAL_COMPONENT_DEFAULT_RESIZER "vc.ril.resize" ++#define MMAL_COMPONENT_ISP_RESIZER "vc.ril.isp" ++#define MMAL_COMPONENT_HVS "vc.ril.hvs" --static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture) --{ -- vout_display_sys_t *sys = vd->sys; -- struct dmx_region_t **dmx_region = &sys->dmx_region; -- struct dmx_region_t *unused_dmx_region; -- DISPMANX_UPDATE_HANDLE_T update = 0; -- picture_t *picture; -- video_format_t *fmt; -- struct dmx_region_t *dmx_region_next; -- -- if(subpicture) { -- subpicture_region_t *region = subpicture->p_region; -- while(region) { -- picture = region->p_picture; -- fmt = ®ion->fmt; -- -- if(!*dmx_region) { -- if(!update) -- update = vc_dispmanx_update_start(10); -- *dmx_region = dmx_region_new(vd, update, region); -- } else if(((*dmx_region)->bmp_rect.width != (int32_t)fmt->i_visible_width) || -- ((*dmx_region)->bmp_rect.height != (int32_t)fmt->i_visible_height) || -- ((*dmx_region)->pos_x != region->i_x) || -- ((*dmx_region)->pos_y != region->i_y) || -- ((*dmx_region)->alpha.opacity != (uint32_t)region->i_alpha)) { -- dmx_region_next = (*dmx_region)->next; -- if(!update) -- update = vc_dispmanx_update_start(10); -- dmx_region_delete(*dmx_region, update); -- *dmx_region = dmx_region_new(vd, update, region); -- (*dmx_region)->next = dmx_region_next; -- } else if((*dmx_region)->picture != picture) { -- if(!update) -- update = vc_dispmanx_update_start(10); -- dmx_region_update(*dmx_region, update, picture); -- } -- -- dmx_region = &(*dmx_region)->next; -- region = region->p_next; -- } -- } -- -- /* Remove remaining regions */ -- unused_dmx_region = *dmx_region; -- while(unused_dmx_region) { -- dmx_region_next = unused_dmx_region->next; -- if(!update) -- update = vc_dispmanx_update_start(10); -- dmx_region_delete(unused_dmx_region, update); -- unused_dmx_region = dmx_region_next; -- } -- *dmx_region = NULL; -- -- if(update) -- vc_dispmanx_update_submit_sync(update); --} -- --static void close_dmx(vout_display_t *vd) --{ -- vout_display_sys_t *sys = vd->sys; + #endif +--- /dev/null ++++ b/modules/hw/mmal/rpi_prof.h +@@ -0,0 +1,110 @@ ++#ifndef RPI_PROFILE_H ++#define RPI_PROFILE_H ++ ++#include ++#include ++ ++#ifndef RPI_PROFILE ++#define RPI_PROFILE 0 ++#endif ++ ++#if RPI_PROFILE ++ ++#include "v7_pmu.h" ++ ++#ifdef RPI_PROC_ALLOC ++#define X volatile ++#define Z =0 ++#else ++#define X extern volatile ++#define Z ++#endif ++ ++X uint64_t av_rpi_prof0_cycles Z; ++X unsigned int av_rpi_prof0_cnt Z; ++#define RPI_prof0_MAX_DURATION 100000 ++ ++X uint64_t av_rpi_prof1_cycles Z; ++X unsigned int av_rpi_prof1_cnt Z; ++#define RPI_prof1_MAX_DURATION 100000 ++ ++X uint64_t av_rpi_prof2_cycles Z; ++X unsigned int av_rpi_prof2_cnt Z; ++#define RPI_prof2_MAX_DURATION 10000 ++ ++X uint64_t av_rpi_prof_n_cycles[128]; ++X unsigned int av_rpi_prof_n_cnt[128]; ++#define RPI_prof_n_MAX_DURATION 10000 ++ ++ ++#undef X ++#undef Z ++ ++#define PROFILE_INIT()\ ++do {\ ++ enable_pmu();\ ++ enable_ccnt();\ ++} while (0) ++ ++#define PROFILE_START()\ ++do {\ ++ volatile uint32_t perf_1 = read_ccnt();\ ++ volatile uint32_t perf_2 ++ ++ ++#define PROFILE_ACC(x)\ ++ perf_2 = read_ccnt();\ ++ {\ ++ const uint32_t duration = perf_2 - perf_1;\ ++ if (duration < RPI_##x##_MAX_DURATION)\ ++ {\ ++ av_rpi_##x##_cycles += duration;\ ++ av_rpi_##x##_cnt += 1;\ ++ }\ ++ }\ ++} while(0) ++ ++ ++#define PROFILE_ACC_N(n)\ ++ if ((n) >= 0) {\ ++ perf_2 = read_ccnt();\ ++ {\ ++ const uint32_t duration = perf_2 - perf_1;\ ++ if (duration < RPI_prof_n_MAX_DURATION)\ ++ {\ ++ av_rpi_prof_n_cycles[n] += duration;\ ++ av_rpi_prof_n_cnt[n] += 1;\ ++ }\ ++ }\ ++ }\ ++} while(0) ++ ++#define PROFILE_PRINTF(x)\ ++ printf("%-20s cycles=%14" PRIu64 "; cnt=%8u; avg=%5" PRIu64 "\n", #x, av_rpi_##x##_cycles, av_rpi_##x##_cnt,\ ++ av_rpi_##x##_cnt == 0 ? (uint64_t)0 : av_rpi_##x##_cycles / (uint64_t)av_rpi_##x##_cnt) ++ ++#define PROFILE_PRINTF_N(n)\ ++ printf("prof[%d] cycles=%14" PRIu64 "; cnt=%8u; avg=%5" PRIu64 "\n", (n), av_rpi_prof_n_cycles[n], av_rpi_prof_n_cnt[n],\ ++ av_rpi_prof_n_cnt[n] == 0 ? (uint64_t)0 : av_rpi_prof_n_cycles[n] / (uint64_t)av_rpi_prof_n_cnt[n]) ++ ++#define PROFILE_CLEAR_N(n) \ ++do {\ ++ av_rpi_prof_n_cycles[n] = 0;\ ++ av_rpi_prof_n_cnt[n] = 0;\ ++} while(0) ++ ++#else ++ ++// No profile ++#define PROFILE_INIT() ++#define PROFILE_START() ++#define PROFILE_ACC(x) ++#define PROFILE_ACC_N(x) ++#define PROFILE_PRINTF(x) ++#define PROFILE_PRINTF_N(x) ++#define PROFILE_CLEAR_N(n) ++ ++#endif ++ ++#endif ++ +--- /dev/null ++++ b/modules/hw/mmal/subpic.c +@@ -0,0 +1,257 @@ ++/***************************************************************************** ++ * mmal.c: MMAL-based decoder plugin for Raspberry Pi ++ ***************************************************************************** ++ * Authors: jc@kynesim.co.uk ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "mmal_picture.h" ++#include "subpic.h" ++ ++ ++#define TRACE_ALL 0 ++ ++static inline bool cmp_rect(const MMAL_RECT_T * const a, const MMAL_RECT_T * const b) ++{ ++ return a->x == b->x && a->y == b->y && a->width == b->width && a->height == b->height; ++} ++ ++void hw_mmal_subpic_flush(vlc_object_t * const p_filter, subpic_reg_stash_t * const sub) ++{ ++ VLC_UNUSED(p_filter); ++ if (sub->port != NULL && sub->port->is_enabled) ++ mmal_port_disable(sub->port); ++ sub->seq = 0; ++} ++ ++void hw_mmal_subpic_close(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe) ++{ ++ hw_mmal_subpic_flush(p_filter, spe); ++ ++ if (spe->pool != NULL) ++ mmal_pool_destroy(spe->pool); ++ ++ // Zap to avoid any accidental reuse ++ *spe = (subpic_reg_stash_t){NULL}; ++} ++ ++MMAL_STATUS_T hw_mmal_subpic_open(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, MMAL_PORT_T * const port, ++ const int display_id, const unsigned int layer) ++{ ++ MMAL_STATUS_T err; ++ ++ // Start by zapping all to zero ++ *spe = (subpic_reg_stash_t){NULL}; ++ ++ if ((err = port_parameter_set_bool(port, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Failed to set sub port zero copy"); ++ return err; ++ } ++ ++ if ((spe->pool = mmal_pool_create(30, 0)) == NULL) ++ { ++ msg_Err(p_filter, "Failed to create sub pool"); ++ return MMAL_ENOMEM; ++ } ++ ++ port->userdata = (void *)p_filter; ++ spe->port = port; ++ spe->display_id = display_id; ++ spe->layer = layer; ++ ++ return MMAL_SUCCESS; ++} ++ ++static void conv_subpic_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) ++{ ++#if TRACE_ALL ++ msg_Dbg((filter_t *)port->userdata, "<<< %s cmd=%d, user=%p, buf=%p, flags=%#x, len=%d/%d, pts=%lld", ++ __func__, buf->cmd, buf->user_data, buf, buf->flags, buf->length, buf->alloc_size, (long long)buf->pts); ++#else ++ VLC_UNUSED(port); ++#endif ++ ++ mmal_buffer_header_release(buf); // Will extract & release pic in pool callback ++} ++ ++static int ++subpic_send_empty(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, const uint64_t pts) ++{ ++ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(spe->pool->queue); ++ MMAL_STATUS_T err; ++ ++ if (buf == NULL) { ++ msg_Err(p_filter, "Buffer get for subpic failed"); ++ return -1; ++ } ++#if TRACE_ALL ++ msg_Dbg(p_filter, "Remove pic for sub %d", spe->seq); ++#endif ++ buf->cmd = 0; ++ buf->data = NULL; ++ buf->alloc_size = 0; ++ buf->offset = 0; ++ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END; ++ buf->pts = pts; ++ buf->dts = MMAL_TIME_UNKNOWN; ++ buf->user_data = NULL; ++ ++ if ((err = mmal_port_send_buffer(spe->port, buf)) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Send buffer to subput failed"); ++ mmal_buffer_header_release(buf); ++ return -1; ++ } ++ return 0; ++} ++ ++// < 0 Error ++// 0 Done & stop ++// 1 Done & continue ++ ++int hw_mmal_subpic_update(vlc_object_t * const p_filter, ++ MMAL_BUFFER_HEADER_T * const sub_buf, ++ subpic_reg_stash_t * const spe, ++ const video_format_t * const fmt, ++ const MMAL_RECT_T * const scale_out, ++ const MMAL_DISPLAYTRANSFORM_T transform_out, ++ const uint64_t pts) ++{ ++ MMAL_STATUS_T err; ++ ++ if (sub_buf == NULL) ++ { ++ if (spe->port->is_enabled && spe->seq != 0) ++ { ++ subpic_send_empty(p_filter, spe, pts); ++ spe->seq = 0; ++ } ++ } ++ else ++ { ++ const unsigned int seq = hw_mmal_vzc_buf_seq(sub_buf); ++ bool needs_update = (spe->seq != seq); ++ ++ hw_mmal_vzc_buf_scale_dest_rect(sub_buf, scale_out, transform_out); ++ ++ if (hw_mmal_vzc_buf_set_format(sub_buf, spe->port->format)) ++ { ++ MMAL_DISPLAYREGION_T * const dreg = hw_mmal_vzc_buf_region(sub_buf); ++ MMAL_VIDEO_FORMAT_T *const v_fmt = &spe->port->format->es->video; ++ ++ v_fmt->frame_rate.den = fmt->i_frame_rate_base; ++ v_fmt->frame_rate.num = fmt->i_frame_rate; ++ v_fmt->par.den = fmt->i_sar_den; ++ v_fmt->par.num = fmt->i_sar_num; ++ v_fmt->color_space = MMAL_COLOR_SPACE_UNKNOWN; ++ ++ if (needs_update || dreg->alpha != spe->alpha || !cmp_rect(&dreg->dest_rect, &spe->dest_rect)) { ++ ++ spe->alpha = dreg->alpha; ++ spe->dest_rect = dreg->dest_rect; ++ needs_update = true; ++ ++ if (spe->display_id >= 0) ++ { ++ dreg->display_num = spe->display_id; ++ dreg->set |= MMAL_DISPLAY_SET_NUM; ++ } ++ dreg->layer = spe->layer; ++ dreg->set |= MMAL_DISPLAY_SET_LAYER; ++ ++#if TRACE_ALL ++ msg_Dbg(p_filter, "%s: Update region: Set=%x, dest=%dx%d @ (%d,%d), src=%dx%d @ (%d,%d), layer=%d, alpha=%#x", ++ __func__, dreg->set, ++ dreg->dest_rect.width, dreg->dest_rect.height, dreg->dest_rect.x, dreg->dest_rect.y, ++ dreg->src_rect.width, dreg->src_rect.height, dreg->src_rect.x, dreg->src_rect.y, ++ dreg->layer, dreg->alpha); ++#endif ++ ++ // If now completely offscreen just flush this & return ++ // We only do -ve as (a) that is easy and (b) it seems to be ++ // something that can confuse mmal ++ if (dreg->dest_rect.y + dreg->dest_rect.height <= 0 || ++ dreg->dest_rect.x + dreg->dest_rect.width <= 0) ++ { ++ if (spe->port->is_enabled) ++ subpic_send_empty(p_filter, spe, pts); ++ spe->seq = seq; ++ return 1; ++ } ++ ++ if ((err = mmal_port_parameter_set(spe->port, &dreg->hdr)) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Set display region on subput failed"); ++ return -1; ++ } ++ ++ if ((err = mmal_port_format_commit(spe->port)) != MMAL_SUCCESS) ++ { ++ msg_Dbg(p_filter, "%s: Subpic commit fail: %d", __func__, err); ++ return -1; ++ } ++ } ++ } ++ ++ if (!spe->port->is_enabled) ++ { ++ spe->port->buffer_num = 30; ++ spe->port->buffer_size = spe->port->buffer_size_recommended; // Not used but shuts up the error checking ++ ++ if ((err = mmal_port_enable(spe->port, conv_subpic_cb)) != MMAL_SUCCESS) ++ { ++ msg_Dbg(p_filter, "%s: Subpic enable fail: %d", __func__, err); ++ return -1; ++ } ++ } ++ ++ if (needs_update) ++ { ++#if TRACE_ALL ++ msg_Dbg(p_filter, "Update pic for sub %d", spe->seq); ++#endif ++ if ((err = port_send_replicated(spe->port, spe->pool, sub_buf, pts)) != MMAL_SUCCESS) ++ { ++ msg_Err(p_filter, "Send buffer to subput failed"); ++ return -1; ++ } ++ ++ spe->seq = seq; ++ } ++ } ++ return 1; ++} ++ ++ ++ +--- /dev/null ++++ b/modules/hw/mmal/subpic.h +@@ -0,0 +1,33 @@ ++#ifndef VLC_HW_MMAL_SUBPIC_H_ ++#define VLC_HW_MMAL_SUBPIC_H_ ++ ++typedef struct subpic_reg_stash_s ++{ ++ MMAL_PORT_T * port; ++ MMAL_POOL_T * pool; ++ int display_id; // -1 => do not set ++ unsigned int layer; ++ // Shadow vars so we can tell if stuff has changed ++ MMAL_RECT_T dest_rect; ++ unsigned int alpha; ++ unsigned int seq; ++} subpic_reg_stash_t; ++ ++int hw_mmal_subpic_update(vlc_object_t * const p_filter, ++ MMAL_BUFFER_HEADER_T * const sub_buf, ++ subpic_reg_stash_t * const spe, ++ const video_format_t * const fmt, ++ const MMAL_RECT_T * const scale_out, ++ const MMAL_DISPLAYTRANSFORM_T transform_out, ++ const uint64_t pts); ++ ++void hw_mmal_subpic_flush(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe); ++ ++void hw_mmal_subpic_close(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe); ++ ++// If display id is -1 it will be unset ++MMAL_STATUS_T hw_mmal_subpic_open(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, MMAL_PORT_T * const port, ++ const int display_id, const unsigned int layer); ++ ++#endif ++ +--- /dev/null ++++ b/modules/hw/mmal/transform_ops.h +@@ -0,0 +1,99 @@ ++#ifndef VLC_MMAL_TRANSFORM_OPS_H ++#define VLC_MMAL_TRANSFORM_OPS_H ++ ++#include ++#include ++#include ++ ++ ++// These are enums with the same order so simply coerce ++static inline MMAL_DISPLAYTRANSFORM_T vlc_to_mmal_transform(const video_orientation_t orientation){ ++ return (MMAL_DISPLAYTRANSFORM_T)orientation; ++} ++ ++// MMAL headers comment these (getting 2 a bit wrong) but do not give ++// defines ++#define XFORM_H_SHIFT 0 // Hflip ++#define XFORM_V_SHIFT 1 // Vflip ++#define XFORM_T_SHIFT 2 // Transpose ++#define XFORM_H_BIT (1 << XFORM_H_SHIFT) ++#define XFORM_V_BIT (1 << XFORM_V_SHIFT) ++#define XFORM_T_BIT (1 << XFORM_T_SHIFT) ++ ++static inline bool ++is_transform_transpose(const MMAL_DISPLAYTRANSFORM_T t) ++{ ++ return ((unsigned int)t & XFORM_T_BIT) != 0; ++} ++ ++static inline bool ++is_transform_hflip(const MMAL_DISPLAYTRANSFORM_T t) ++{ ++ return ((unsigned int)t & XFORM_H_BIT) != 0; ++} ++ ++static inline bool ++is_transform_vflip(const MMAL_DISPLAYTRANSFORM_T t) ++{ ++ return ((unsigned int)t & XFORM_V_BIT) != 0; ++} ++ ++static inline MMAL_DISPLAYTRANSFORM_T ++swap_transform_hv(const MMAL_DISPLAYTRANSFORM_T x) ++{ ++ return (((x >> XFORM_H_SHIFT) & 1) << XFORM_V_SHIFT) | ++ (((x >> XFORM_V_SHIFT) & 1) << XFORM_H_SHIFT) | ++ (x & XFORM_T_BIT); ++} ++ ++static inline MMAL_DISPLAYTRANSFORM_T ++transform_inverse(const MMAL_DISPLAYTRANSFORM_T x) ++{ ++ return is_transform_transpose(x) ? swap_transform_hv(x) : x; ++} ++ ++// Transform generated by A then B ++// All ops are self inverse so can simply be XORed on their own ++// H & V flips after a transpose need to be swapped ++static inline MMAL_DISPLAYTRANSFORM_T ++combine_transform(const MMAL_DISPLAYTRANSFORM_T a, const MMAL_DISPLAYTRANSFORM_T b) ++{ ++ return a ^ (is_transform_transpose(a) ? swap_transform_hv(b) : b); ++} ++ ++static inline MMAL_RECT_T ++rect_transpose(const MMAL_RECT_T s) ++{ ++ return (MMAL_RECT_T){ ++ .x = s.y, ++ .y = s.x, ++ .width = s.height, ++ .height = s.width ++ }; ++} ++ ++// hflip s in c ++static inline MMAL_RECT_T rect_hflip(const MMAL_RECT_T s, const MMAL_RECT_T c) ++{ ++ return (MMAL_RECT_T){ ++ .x = c.x + (c.x + c.width) - (s.x + s.width), ++ .y = s.y, ++ .width = s.width, ++ .height = s.height ++ }; ++} ++ ++// vflip s in c ++static inline MMAL_RECT_T rect_vflip(const MMAL_RECT_T s, const MMAL_RECT_T c) ++{ ++ return (MMAL_RECT_T){ ++ .x = s.x, ++ .y = (c.y + c.height) - (s.y - c.y) - s.height, ++ .width = s.width, ++ .height = s.height ++ }; ++} ++ ++ ++#endif ++ +--- /dev/null ++++ b/modules/hw/mmal/v7_pmu.S +@@ -0,0 +1,263 @@ ++/*------------------------------------------------------------ ++Performance Monitor Block ++------------------------------------------------------------*/ ++ .arm @ Make sure we are in ARM mode. ++ .text ++ .align 2 ++ .global getPMN @ export this function for the linker ++ ++/* Returns the number of progammable counters uint32_t getPMN(void) */ ++ ++getPMN: ++ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC Register */ ++ MOV r0, r0, LSR #11 /* Shift N field down to bit 0 */ ++ AND r0, r0, #0x1F /* Mask to leave just the 5 N bits */ ++ BX lr ++ ++ ++ ++ .global pmn_config @ export this function for the linker ++ /* Sets the event for a programmable counter to record */ ++ /* void pmn_config(unsigned counter, uint32_t event) */ ++ /* counter = r0 = Which counter to program (e.g. 0 for PMN0, 1 for PMN1 */ ++ /* event = r1 = The event code */ ++pmn_config: ++ AND r0, r0, #0x1F /* Mask to leave only bits 4:0 */ ++ MCR p15, 0, r0, c9, c12, 5 /* Write PMNXSEL Register */ ++ MCR p15, 0, r1, c9, c13, 1 /* Write EVTSELx Register */ ++ BX lr ++ ++ ++ ++ .global ccnt_divider @ export this function for the linker ++ /* Enables/disables the divider (1/64) on CCNT */ ++ /* void ccnt_divider(int divider) */ ++ /* divider = r0 = If 0 disable divider, else enable dvider */ ++ccnt_divider: ++ MRC p15, 0, r1, c9, c12, 0 /* Read PMNC */ ++ ++ CMP r0, #0x0 /* IF (r0 == 0) */ ++ BICEQ r1, r1, #0x08 /* THEN: Clear the D bit (disables the */ ++ ORRNE r1, r1, #0x08 /* ELSE: Set the D bit (enables the di */ ++ ++ MCR p15, 0, r1, c9, c12, 0 /* Write PMNC */ ++ BX lr ++ ++ ++ /* --------------------------------------------------------------- */ ++ /* Enable/Disable */ ++ /* --------------------------------------------------------------- */ ++ ++ .global enable_pmu @ export this function for the linker ++ /* Global PMU enable */ ++ /* void enable_pmu(void) */ ++enable_pmu: ++ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ ++ ORR r0, r0, #0x01 /* Set E bit */ ++ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ ++ BX lr ++ ++ ++ ++ .global disable_pmu @ export this function for the linker ++ /* Global PMU disable */ ++ /* void disable_pmu(void) */ ++disable_pmu: ++ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ ++ BIC r0, r0, #0x01 /* Clear E bit */ ++ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ ++ BX lr ++ ++ ++ ++ .global enable_ccnt @ export this function for the linker ++ /* Enable the CCNT */ ++ /* void enable_ccnt(void) */ ++enable_ccnt: ++ MOV r0, #0x80000000 /* Set C bit */ ++ MCR p15, 0, r0, c9, c12, 1 /* Write CNTENS Register */ ++ BX lr ++ ++ ++ ++ .global disable_ccnt @ export this function for the linker ++ /* Disable the CCNT */ ++ /* void disable_ccnt(void) */ ++disable_ccnt: ++ MOV r0, #0x80000000 /* Clear C bit */ ++ MCR p15, 0, r0, c9, c12, 2 /* Write CNTENC Register */ ++ BX lr ++ ++ ++ ++ .global enable_pmn @ export this function for the linker ++ /* Enable PMN{n} */ ++ /* void enable_pmn(uint32_t counter) */ ++ /* counter = r0 = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) ++enable_pmn: */ ++ MOV r1, #0x1 /* Use arg (r0) to set which counter t */ ++ MOV r1, r1, LSL r0 ++ ++ MCR p15, 0, r1, c9, c12, 1 /* Write CNTENS Register */ ++ BX lr ++ ++ ++ ++ .global disable_pmn @ export this function for the linker ++ /* Enable PMN{n} */ ++ /* void disable_pmn(uint32_t counter) */ ++ /* counter = r0 = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) ++disable_pmn: */ ++ MOV r1, #0x1 /* Use arg (r0) to set which counter t */ ++ MOV r1, r1, LSL r0 ++ ++ MCR p15, 0, r1, c9, c12, 1 /* Write CNTENS Register */ ++ BX lr ++ ++ ++ ++ .global enable_pmu_user_access @ export this function for the linker ++ /* Enables User mode access to the PMU (must be called in a priviledge */ ++ /* void enable_pmu_user_access(void) */ ++enable_pmu_user_access: ++ MRC p15, 0, r0, c9, c14, 0 /* Read PMUSERENR Register */ ++ ORR r0, r0, #0x01 /* Set EN bit (bit 0) */ ++ MCR p15, 0, r0, c9, c14, 0 /* Write PMUSERENR Register */ ++ BX lr ++ ++ ++ ++ .global disable_pmu_user_access @ export this function for the linke ++ /* Disables User mode access to the PMU (must be called in a priviledg */ ++ /* void disable_pmu_user_access(void) */ ++disable_pmu_user_access: ++ MRC p15, 0, r0, c9, c14, 0 /* Read PMUSERENR Register */ ++ BIC r0, r0, #0x01 /* Clear EN bit (bit 0) */ ++ MCR p15, 0, r0, c9, c14, 0 /* Write PMUSERENR Register */ ++ BX lr ++ ++ ++ /* --------------------------------------------------------------- */ ++ /* Counter read registers */ ++ /* --------------------------------------------------------------- */ ++ ++ .global read_ccnt @ export this function for the linker ++ /* Returns the value of CCNT */ ++ /* uint32_t read_ccnt(void) */ ++read_ccnt: ++ MRC p15, 0, r0, c9, c13, 0 /* Read CCNT Register */ ++ BX lr ++ ++ ++ .global read_pmn @ export this function for the linker ++ /* Returns the value of PMN{n} */ ++ /* uint32_t read_pmn(uint32_t counter) */ ++ /* counter = r0 = The counter to read (e.g. 0 for PMN0, 1 for PMN1) * ++read_pmn: */ ++ AND r0, r0, #0x1F /* Mask to leave only bits 4:0 */ ++ MCR p15, 0, r0, c9, c12, 5 /* Write PMNXSEL Register */ ++ MRC p15, 0, r0, c9, c13, 2 /* Read current PMNx Register */ ++ BX lr ++ ++ ++ /* --------------------------------------------------------------- */ ++ /* Software Increment */ ++ /* --------------------------------------------------------------- */ ++ ++ .global pmu_software_increment @ export this function for the linker ++ /* Writes to software increment register */ ++ /* void pmu_software_increment(uint32_t counter) */ ++ /* counter = r0 = The counter to increment (e.g. 0 for PMN0, 1 for PMN ++pmu_software_increment: */ ++ MOV r1, #0x01 ++ MOV r1, r1, LSL r0 ++ MCR p15, 0, r1, c9, c12, 4 /* Write SWINCR Register */ ++ BX lr ++ ++ /* --------------------------------------------------------------- */ ++ /* Overflow & Interrupt Generation */ ++ /* --------------------------------------------------------------- */ ++ ++ .global read_flags @ export this function for the linker ++ /* Returns the value of the overflow flags */ ++ /* uint32_t read_flags(void) */ ++read_flags: ++ MRC p15, 0, r0, c9, c12, 3 /* Read FLAG Register */ ++ BX lr ++ ++ ++ .global write_flags @ export this function for the linker ++ /* Writes the overflow flags */ ++ /* void write_flags(uint32_t flags) */ ++write_flags: ++ MCR p15, 0, r0, c9, c12, 3 /* Write FLAG Register */ ++ BX lr ++ ++ ++ .global enable_ccnt_irq @ export this function for the linker ++ /* Enables interrupt generation on overflow of the CCNT */ ++ /* void enable_ccnt_irq(void) */ ++enable_ccnt_irq: ++ MOV r0, #0x80000000 ++ MCR p15, 0, r0, c9, c14, 1 /* Write INTENS Register */ ++ BX lr ++ ++ .global disable_ccnt_irq @ export this function for the linker ++ /* Disables interrupt generation on overflow of the CCNT */ ++ /* void disable_ccnt_irq(void) */ ++disable_ccnt_irq: ++ MOV r0, #0x80000000 ++ MCR p15, 0, r0, c9, c14, 2 /* Write INTENC Register */ ++ BX lr ++ ++ ++ .global enable_pmn_irq @ export this function for the linker ++ /* Enables interrupt generation on overflow of PMN{x} */ ++ /* void enable_pmn_irq(uint32_t counter) */ ++ /* counter = r0 = The counter to enable the interrupt for (e.g. 0 for ++enable_pmn_irq: */ ++ MOV r1, #0x1 /* Use arg (r0) to set which counter */ ++ MOV r0, r1, LSL r0 ++ MCR p15, 0, r0, c9, c14, 1 /* Write INTENS Register */ ++ BX lr ++ ++ .global disable_pmn_irq @ export this function for the linker ++ /* Disables interrupt generation on overflow of PMN{x} */ ++ /* void disable_pmn_irq(uint32_t counter) */ ++ /* counter = r0 = The counter to disable the interrupt for (e.g. 0 fo ++disable_pmn_irq: */ ++ MOV r1, #0x1 /* Use arg (r0) to set which counter t */ ++ MOV r0, r1, LSL r0 ++ MCR p15, 0, r0, c9, c14, 2 /* Write INTENC Register */ ++ BX lr ++ ++ /* --------------------------------------------------------------- */ ++ /* Reset Functions */ ++ /* --------------------------------------------------------------- */ ++ ++ .global reset_pmn @ export this function for the linker ++ /* Resets the programmable counters */ ++ /* void reset_pmn(void) */ ++reset_pmn: ++ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ ++ ORR r0, r0, #0x02 /* Set P bit (Event Counter Reset) */ ++ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ ++ BX lr ++ ++ ++ .global reset_ccnt @ export this function for the linker ++ /* Resets the CCNT */ ++ /* void reset_ccnt(void) */ ++reset_ccnt: ++ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ ++ ORR r0, r0, #0x04 /* Set C bit (Event Counter Reset) */ ++ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ ++ BX lr ++ ++ ++ .end @end of code, this line is optional. ++/* ------------------------------------------------------------ */ ++/* End of v7_pmu.s */ ++/* ------------------------------------------------------------ */ ++ ++ +--- /dev/null ++++ b/modules/hw/mmal/v7_pmu.h +@@ -0,0 +1,113 @@ ++// ------------------------------------------------------------ ++// PMU for Cortex-A/R (v7-A/R) ++// ------------------------------------------------------------ ++ ++#ifndef _V7_PMU_H ++#define _V7_PMU_H ++ ++// Returns the number of progammable counters ++unsigned int getPMN(void); ++ ++// Sets the event for a programmable counter to record ++// counter = r0 = Which counter to program (e.g. 0 for PMN0, 1 for PMN1) ++// event = r1 = The event code (from appropiate TRM or ARM Architecture Reference Manual) ++void pmn_config(unsigned int counter, unsigned int event); ++ ++// Enables/disables the divider (1/64) on CCNT ++// divider = r0 = If 0 disable divider, else enable dvider ++void ccnt_divider(int divider); ++ ++// ++// Enables and disables ++// ++ ++// Global PMU enable ++// On ARM11 this enables the PMU, and the counters start immediately ++// On Cortex this enables the PMU, there are individual enables for the counters ++void enable_pmu(void); ++ ++// Global PMU disable ++// On Cortex, this overrides the enable state of the individual counters ++void disable_pmu(void); ++ ++// Enable the CCNT ++void enable_ccnt(void); ++ ++// Disable the CCNT ++void disable_ccnt(void); ++ ++// Enable PMN{n} ++// counter = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) ++void enable_pmn(unsigned int counter); ++ ++// Enable PMN{n} ++// counter = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) ++void disable_pmn(unsigned int counter); ++ ++// ++// Read counter values ++// ++ ++// Returns the value of CCNT ++unsigned int read_ccnt(void); ++ ++// Returns the value of PMN{n} ++// counter = The counter to read (e.g. 0 for PMN0, 1 for PMN1) ++unsigned int read_pmn(unsigned int counter); ++ ++// ++// Overflow and interrupts ++// ++ ++// Returns the value of the overflow flags ++unsigned int read_flags(void); ++ ++// Writes the overflow flags ++void write_flags(unsigned int flags); ++ ++// Enables interrupt generation on overflow of the CCNT ++void enable_ccnt_irq(void); ++ ++// Disables interrupt generation on overflow of the CCNT ++void disable_ccnt_irq(void); ++ ++// Enables interrupt generation on overflow of PMN{x} ++// counter = The counter to enable the interrupt for (e.g. 0 for PMN0, 1 for PMN1) ++void enable_pmn_irq(unsigned int counter); ++ ++// Disables interrupt generation on overflow of PMN{x} ++// counter = r0 = The counter to disable the interrupt for (e.g. 0 for PMN0, 1 for PMN1) ++void disable_pmn_irq(unsigned int counter); ++ ++// ++// Counter reset functions ++// ++ ++// Resets the programmable counters ++void reset_pmn(void); ++ ++// Resets the CCNT ++void reset_ccnt(void); ++ ++// ++// Software Increment ++ ++// Writes to software increment register ++// counter = The counter to increment (e.g. 0 for PMN0, 1 for PMN1) ++void pmu_software_increment(unsigned int counter); ++ ++// ++// User mode access ++// ++ ++// Enables User mode access to the PMU (must be called in a priviledged mode) ++void enable_pmu_user_access(void); ++ ++// Disables User mode access to the PMU (must be called in a priviledged mode) ++void disable_pmu_user_access(void); ++ ++#endif ++// ------------------------------------------------------------ ++// End of v7_pmu.h ++// ------------------------------------------------------------ ++ +--- a/modules/hw/mmal/vout.c ++++ b/modules/hw/mmal/vout.c +@@ -27,21 +27,28 @@ + #endif + + #include ++#include + + #include +-#include + #include + #include + #include ++#include + +-#include "mmal_picture.h" +- ++#pragma GCC diagnostic push ++#pragma GCC diagnostic ignored "-Wbad-function-cast" + #include ++#pragma GCC diagnostic pop + #include + #include + #include + #include +-#include ++ ++#include "mmal_picture.h" ++#include "subpic.h" ++#include "transform_ops.h" ++ ++#define TRACE_ALL 0 + + #define MAX_BUFFERS_IN_TRANSIT 1 + #define VC_TV_MAX_MODE_IDS 127 +@@ -50,10 +57,28 @@ + #define MMAL_LAYER_TEXT N_("VideoCore layer where the video is displayed.") + #define MMAL_LAYER_LONGTEXT N_("VideoCore layer where the video is displayed. Subpictures are displayed directly above and a black background directly below.") + +-#define MMAL_BLANK_BACKGROUND_NAME "mmal-blank-background" +-#define MMAL_BLANK_BACKGROUND_TEXT N_("Blank screen below video.") +-#define MMAL_BLANK_BACKGROUND_LONGTEXT N_("Render blank screen below video. " \ +- "Increases VideoCore load.") ++#define MMAL_DISPLAY_NAME "mmal-display" ++#define MMAL_DISPLAY_TEXT N_("Output device for Rpi fullscreen.") ++#define MMAL_DISPLAY_LONGTEXT N_("Output device for Rpi fullscreen. " \ ++"Valid values are HDMI-1,HDMI-2. By default if qt-fullscreen-screennumber " \ ++"is specified (or set by Fullscreen Output Device in Preferences) " \ ++"HDMI- will be used, otherwise HDMI-1.") ++ ++#define MMAL_VOUT_TRANSFORM_NAME "mmal-vout-transform" ++#define MMAL_VOUT_TRANSFORM_TEXT N_("Video transform for Rpi fullscreen.") ++#define MMAL_VOUT_TRANSFORM_LONGTEXT N_("Video transform for Rpi fullscreen."\ ++"Transforms availible: auto, 0, 90, 180, 270, hflip, vflip, transpose, antitranspose") ++ ++#define MMAL_VOUT_WINDOW_NAME "mmal-vout-window" ++#define MMAL_VOUT_WINDOW_TEXT N_("Display window for Rpi fullscreen") ++#define MMAL_VOUT_WINDOW_LONGTEXT N_("Display window for Rpi fullscreen."\ ++"fullscreen|x++") ++ ++#define MMAL_VOUT_TRANSPARENT_NAME "mmal-vout-transparent" ++#define MMAL_VOUT_TRANSPARENT_TEXT N_("Enable layers beneeth the vodeo layer.") ++#define MMAL_VOUT_TRANSPARENT_LONGTEXT N_("Enable layers beneath the video layer."\ ++" By default these are disabled."\ ++" Having the lower layers enabled can impact video performance") + + #define MMAL_ADJUST_REFRESHRATE_NAME "mmal-adjust-refreshrate" + #define MMAL_ADJUST_REFRESHRATE_TEXT N_("Adjust HDMI refresh rate to the video.") +@@ -68,332 +93,628 @@ + #define PHASE_OFFSET_TARGET ((double)0.25) + #define PHASE_CHECK_INTERVAL 100 + +-static int Open(vlc_object_t *); +-static void Close(vlc_object_t *); +- +-vlc_module_begin() +- set_shortname(N_("MMAL vout")) +- set_description(N_("MMAL-based vout plugin for Raspberry Pi")) +- set_capability("vout display", 90) +- add_shortcut("mmal_vout") +- add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false) +- add_bool(MMAL_BLANK_BACKGROUND_NAME, true, MMAL_BLANK_BACKGROUND_TEXT, +- MMAL_BLANK_BACKGROUND_LONGTEXT, true); +- add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT, +- MMAL_ADJUST_REFRESHRATE_LONGTEXT, false) +- add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT, +- MMAL_NATIVE_INTERLACE_LONGTEXT, false) +- set_callbacks(Open, Close) +-vlc_module_end() ++#define SUBS_MAX 4 + +-struct dmx_region_t { +- struct dmx_region_t *next; +- picture_t *picture; +- VC_RECT_T bmp_rect; +- VC_RECT_T src_rect; +- VC_RECT_T dst_rect; +- VC_DISPMANX_ALPHA_T alpha; +- DISPMANX_ELEMENT_HANDLE_T element; +- DISPMANX_RESOURCE_HANDLE_T resource; +- int32_t pos_x; +- int32_t pos_y; +-}; ++typedef struct vout_subpic_s { ++ MMAL_COMPONENT_T *component; ++ subpic_reg_stash_t sub; ++} vout_subpic_t; + + struct vout_display_sys_t { +- vlc_cond_t buffer_cond; +- vlc_mutex_t buffer_mutex; + vlc_mutex_t manage_mutex; + +- plane_t planes[3]; /* Depending on video format up to 3 planes are used */ +- picture_t **pictures; /* Actual list of alloced pictures passed into picture_pool */ +- picture_pool_t *picture_pool; +- ++ vcsm_init_type_t init_type; + MMAL_COMPONENT_T *component; + MMAL_PORT_T *input; + MMAL_POOL_T *pool; /* mmal buffer headers, used for pushing pictures to component*/ +- struct dmx_region_t *dmx_region; + int i_planes; /* Number of actually used planes, 1 for opaque, 3 for i420 */ + +- uint32_t buffer_size; /* size of actual mmal buffers */ + int buffers_in_transit; /* number of buffers currently pushed to mmal component */ + unsigned num_buffers; /* number of buffers allocated at mmal port */ + +- DISPMANX_DISPLAY_HANDLE_T dmx_handle; +- DISPMANX_ELEMENT_HANDLE_T bkg_element; +- DISPMANX_RESOURCE_HANDLE_T bkg_resource; +- unsigned display_width; +- unsigned display_height; ++ int display_id; ++ MMAL_RECT_T win_rect; // Window rect after transform(s) ++ MMAL_RECT_T display_rect; // Actual shape of display (x, y always 0) ++ MMAL_RECT_T req_win; // User requested window (w=0 => fullscreen) ++ ++ MMAL_RECT_T spu_rect; // Output rectangle in cfg coords (for subpic placement) ++ MMAL_RECT_T dest_rect; // Output rectangle in display coords ++ MMAL_DISPLAYTRANSFORM_T dest_transform; // Dest window coord transform ++ MMAL_DISPLAYTRANSFORM_T display_transform; // "Native" display transform ++ MMAL_DISPLAYTRANSFORM_T video_transform; // Combined config+native transform + +- int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */ +- int i_frame_rate; ++ unsigned int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */ ++ unsigned int i_frame_rate; + + int next_phase_check; /* lowpass for phase check frequency */ + int phase_offset; /* currently applied offset to presentation time in ns */ + int layer; /* the dispman layer (z-index) used for video rendering */ ++ bool transparent; // Do not disable layers beneath ours + + bool need_configure_display; /* indicates a required display reconfigure to main thread */ + bool adjust_refresh_rate; + bool native_interlaced; + bool b_top_field_first; /* cached interlaced settings to detect changes for native mode */ + bool b_progressive; +- bool opaque; /* indicated use of opaque picture format (zerocopy) */ +-}; ++ bool force_config; + +-static const vlc_fourcc_t subpicture_chromas[] = { +- VLC_CODEC_RGBA, +- 0 +-}; ++ vout_subpic_t subs[SUBS_MAX]; ++ // Stash for subpics derived from the passed subpicture rather than ++ // included with the main pic ++ MMAL_BUFFER_HEADER_T * subpic_bufs[SUBS_MAX]; ++ ++ picture_pool_t * pic_pool; ++ ++ struct vout_isp_conf_s { ++ MMAL_COMPONENT_T *component; ++ MMAL_PORT_T * input; ++ MMAL_PORT_T * output; ++ MMAL_QUEUE_T * out_q; ++ MMAL_POOL_T * in_pool; ++ MMAL_POOL_T * out_pool; ++ bool pending; ++ } isp; + +-/* Utility functions */ +-static inline uint32_t align(uint32_t x, uint32_t y); +-static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg, +- const video_format_t *fmt); ++ MMAL_POOL_T * copy_pool; ++ MMAL_BUFFER_HEADER_T * copy_buf; + +-/* VLC vout display callbacks */ +-static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count); +-static void vd_prepare(vout_display_t *vd, picture_t *picture, +- subpicture_t *subpicture); +-static void vd_display(vout_display_t *vd, picture_t *picture, +- subpicture_t *subpicture); +-static int vd_control(vout_display_t *vd, int query, va_list args); +-static void vd_manage(vout_display_t *vd); +- +-/* MMAL callbacks */ +-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); +-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); ++ // Subpic blend if we have to do it here ++ vzc_pool_ctl_t * vzc; ++}; + +-/* TV service */ +-static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height); +-static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, +- uint32_t param2); +-static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt); +-static int set_latency_target(vout_display_t *vd, bool enable); + +-/* DispManX */ +-static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture); +-static void close_dmx(vout_display_t *vd); +-static struct dmx_region_t *dmx_region_new(vout_display_t *vd, +- DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region); +-static void dmx_region_update(struct dmx_region_t *dmx_region, +- DISPMANX_UPDATE_HANDLE_T update, picture_t *picture); +-static void dmx_region_delete(struct dmx_region_t *dmx_region, +- DISPMANX_UPDATE_HANDLE_T update); +-static void show_background(vout_display_t *vd, bool enable); +-static void maintain_phase_sync(vout_display_t *vd); ++// ISP setup + +-static int Open(vlc_object_t *object) ++static inline bool want_isp(const vout_display_t * const vd) + { +- vout_display_t *vd = (vout_display_t *)object; +- vout_display_sys_t *sys; +- uint32_t buffer_pitch, buffer_height; +- vout_display_place_t place; +- MMAL_DISPLAYREGION_T display_region; +- MMAL_STATUS_T status; +- int ret = VLC_SUCCESS; +- unsigned i; ++ return (vd->fmt.i_chroma == VLC_CODEC_MMAL_ZC_SAND10); ++} + +- if (vout_display_IsWindowed(vd)) +- return VLC_EGENERIC; ++static inline bool want_copy(const vout_display_t * const vd) ++{ ++ return (vd->fmt.i_chroma == VLC_CODEC_I420 || vd->fmt.i_chroma == VLC_CODEC_I420_10L); ++} + +- sys = calloc(1, sizeof(struct vout_display_sys_t)); +- if (!sys) +- return VLC_ENOMEM; +- vd->sys = sys; ++static inline vlc_fourcc_t req_chroma(const vout_display_t * const vd) ++{ ++ return !hw_mmal_chroma_is_mmal(vd->fmt.i_chroma) && !want_copy(vd) ? ++ VLC_CODEC_I420 : ++ vd->fmt.i_chroma; ++} + +- sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME); +- bcm_host_init(); ++static MMAL_FOURCC_T vout_vlc_to_mmal_pic_fourcc(const unsigned int fcc) ++{ ++ switch (fcc){ ++ case VLC_CODEC_MMAL_OPAQUE: ++ return MMAL_ENCODING_OPAQUE; ++ case VLC_CODEC_MMAL_ZC_SAND8: ++ return MMAL_ENCODING_YUVUV128; ++ case VLC_CODEC_MMAL_ZC_SAND10: ++ return MMAL_ENCODING_YUVUV64_10; ++ case VLC_CODEC_MMAL_ZC_SAND30: ++ return MMAL_ENCODING_YUV10_COL; ++ case VLC_CODEC_MMAL_ZC_I420: ++ case VLC_CODEC_I420: ++ return MMAL_ENCODING_I420; ++ default: ++ break; ++ } ++ return MMAL_ENCODING_I420; ++} + +- sys->opaque = vd->fmt.i_chroma == VLC_CODEC_MMAL_OPAQUE; ++static void display_set_format(const vout_display_t * const vd, MMAL_ES_FORMAT_T *const es_fmt, const bool is_intermediate) ++{ ++ const unsigned int w = is_intermediate ? vd->fmt.i_visible_width : vd->fmt.i_width ; ++ const unsigned int h = is_intermediate ? vd->fmt.i_visible_height : vd->fmt.i_height; ++ MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video; + +- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component); +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)", +- MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++ es_fmt->type = MMAL_ES_TYPE_VIDEO; ++ es_fmt->encoding = is_intermediate ? MMAL_ENCODING_I420 : vout_vlc_to_mmal_pic_fourcc(vd->fmt.i_chroma); ++ es_fmt->encoding_variant = 0; ++ ++ v_fmt->width = (w + 31) & ~31; ++ v_fmt->height = (h + 15) & ~15; ++ v_fmt->crop.x = 0; ++ v_fmt->crop.y = 0; ++ v_fmt->crop.width = w; ++ v_fmt->crop.height = h; ++ if (vd->fmt.i_sar_num == 0 || vd->fmt.i_sar_den == 0) { ++ v_fmt->par.num = 1; ++ v_fmt->par.den = 1; ++ } else { ++ v_fmt->par.num = vd->fmt.i_sar_num; ++ v_fmt->par.den = vd->fmt.i_sar_den; + } ++ v_fmt->frame_rate.num = vd->fmt.i_frame_rate; ++ v_fmt->frame_rate.den = vd->fmt.i_frame_rate_base; ++ v_fmt->color_space = vlc_to_mmal_color_space(vd->fmt.space); + +- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd; +- status = mmal_port_enable(sys->component->control, control_port_cb); +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)", +- sys->component->control->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++ msg_Dbg(vd, "WxH: %dx%d, Crop: %dx%d", v_fmt->width, v_fmt->height, v_fmt->crop.width, v_fmt->crop.height); ++} ++ ++static MMAL_RECT_T ++display_src_rect(const vout_display_t * const vd, const video_format_t * const src) ++{ ++ const bool wants_isp = want_isp(vd); ++ ++ // Scale source derived cropping to actual picture shape ++ return (MMAL_RECT_T){ ++ .x = wants_isp ? 0 : src->i_x_offset * vd->fmt.i_width / src->i_width, ++ .y = wants_isp ? 0 : src->i_y_offset * vd->fmt.i_height / src->i_height, ++ .width = src->i_visible_width * vd->fmt.i_width / src->i_width, ++ .height = src->i_visible_height * vd->fmt.i_height / src->i_height ++ }; ++} ++ ++static void isp_input_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) ++{ ++#if TRACE_ALL ++ vout_display_t * const vd = (vout_display_t *)port->userdata; ++ pic_ctx_mmal_t * ctx = buf->user_data; ++ msg_Dbg(vd, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buf->cmd, ctx, buf, ++ buf->flags, (long long)buf->pts); ++#else ++ VLC_UNUSED(port); ++#endif ++ ++ mmal_buffer_header_release(buf); ++ ++#if TRACE_ALL ++ msg_Dbg(vd, ">>> %s", __func__); ++#endif ++} ++ ++static void isp_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++{ ++ vout_display_t *vd = (vout_display_t *)port->userdata; ++ MMAL_STATUS_T status; ++ ++ if (buffer->cmd == MMAL_EVENT_ERROR) { ++ status = *(uint32_t *)buffer->data; ++ msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); + } + +- sys->input = sys->component->input[0]; +- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd; ++ mmal_buffer_header_release(buffer); ++} + +- if (sys->opaque) { +- sys->input->format->encoding = MMAL_ENCODING_OPAQUE; +- sys->i_planes = 1; +- sys->buffer_size = sys->input->buffer_size_recommended; +- } else { +- sys->input->format->encoding = MMAL_ENCODING_I420; +- vd->fmt.i_chroma = VLC_CODEC_I420; +- buffer_pitch = align(vd->fmt.i_width, 32); +- buffer_height = align(vd->fmt.i_height, 16); +- sys->i_planes = 3; +- sys->buffer_size = 3 * buffer_pitch * buffer_height / 2; +- } +- +- sys->input->format->es->video.width = vd->fmt.i_width; +- sys->input->format->es->video.height = vd->fmt.i_height; +- sys->input->format->es->video.crop.x = 0; +- sys->input->format->es->video.crop.y = 0; +- sys->input->format->es->video.crop.width = vd->fmt.i_width; +- sys->input->format->es->video.crop.height = vd->fmt.i_height; +- sys->input->format->es->video.par.num = vd->source.i_sar_num; +- sys->input->format->es->video.par.den = vd->source.i_sar_den; ++static void isp_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) ++{ ++ if (buf->cmd == 0 && buf->length != 0) ++ { ++ // The filter structure etc. should always exist if we have contents ++ // but might not on later flushes as we shut down ++ vout_display_t * const vd = (vout_display_t *)port->userdata; ++ struct vout_isp_conf_s *const isp = &vd->sys->isp; + +- status = mmal_port_format_commit(sys->input); +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s: cmd=%d; flags=%#x, pts=%lld", __func__, buf->cmd, buf->flags, (long long) buf->pts); ++#endif ++ mmal_queue_put(isp->out_q, buf); ++#if TRACE_ALL ++ msg_Dbg(vd, ">>> %s: out Q len=%d", __func__, mmal_queue_length(isp->out_q)); ++#endif + } +- sys->input->buffer_size = sys->input->buffer_size_recommended; ++ else ++ { ++ mmal_buffer_header_reset(buf); ++ mmal_buffer_header_release(buf); ++ } ++} + +- vout_display_PlacePicture(&place, &vd->source, vd->cfg, false); +- display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION; +- display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T); +- display_region.fullscreen = MMAL_FALSE; +- display_region.src_rect.x = vd->fmt.i_x_offset; +- display_region.src_rect.y = vd->fmt.i_y_offset; +- display_region.src_rect.width = vd->fmt.i_visible_width; +- display_region.src_rect.height = vd->fmt.i_visible_height; +- display_region.dest_rect.x = place.x; +- display_region.dest_rect.y = place.y; +- display_region.dest_rect.width = place.width; +- display_region.dest_rect.height = place.height; +- display_region.layer = sys->layer; +- display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT | +- MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER; +- status = mmal_port_parameter_set(sys->input, &display_region.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)", +- status, mmal_status_to_string(status)); +- ret = VLC_EGENERIC; +- goto out; ++static void isp_empty_out_q(struct vout_isp_conf_s * const isp) ++{ ++ MMAL_BUFFER_HEADER_T * buf; ++ // We can be called as part of error recovery so allow for missing Q ++ if (isp->out_q == NULL) ++ return; ++ ++ while ((buf = mmal_queue_get(isp->out_q)) != NULL) ++ mmal_buffer_header_release(buf); ++} ++ ++static void isp_flush(struct vout_isp_conf_s * const isp) ++{ ++ if (!isp->input->is_enabled) ++ mmal_port_disable(isp->input); ++ ++ if (isp->output->is_enabled) ++ mmal_port_disable(isp->output); ++ ++ isp_empty_out_q(isp); ++ isp->pending = false; ++} ++ ++static MMAL_STATUS_T isp_prepare(vout_display_t * const vd, struct vout_isp_conf_s * const isp) ++{ ++ MMAL_STATUS_T err; ++ MMAL_BUFFER_HEADER_T * buf; ++ ++ if (!isp->output->is_enabled) { ++ if ((err = mmal_port_enable(isp->output, isp_output_cb)) != MMAL_SUCCESS) ++ { ++ msg_Err(vd, "ISP output port enable failed"); ++ return err; ++ } + } + +- for (i = 0; i < sys->i_planes; ++i) { +- sys->planes[i].i_lines = buffer_height; +- sys->planes[i].i_pitch = buffer_pitch; +- sys->planes[i].i_visible_lines = vd->fmt.i_visible_height; +- sys->planes[i].i_visible_pitch = vd->fmt.i_visible_width; ++ while ((buf = mmal_queue_get(isp->out_pool->queue)) != NULL) { ++ if ((err = mmal_port_send_buffer(isp->output, buf)) != MMAL_SUCCESS) ++ { ++ msg_Err(vd, "ISP output port stuff failed"); ++ return err; ++ } ++ } + +- if (i > 0) { +- sys->planes[i].i_lines /= 2; +- sys->planes[i].i_pitch /= 2; +- sys->planes[i].i_visible_lines /= 2; +- sys->planes[i].i_visible_pitch /= 2; ++ if (!isp->input->is_enabled) { ++ if ((err = mmal_port_enable(isp->input, isp_input_cb)) != MMAL_SUCCESS) ++ { ++ msg_Err(vd, "ISP input port enable failed"); ++ return err; + } + } ++ return MMAL_SUCCESS; ++} + +- vlc_mutex_init(&sys->buffer_mutex); +- vlc_cond_init(&sys->buffer_cond); +- vlc_mutex_init(&sys->manage_mutex); ++static void isp_close(vout_display_t * const vd, vout_display_sys_t * const vd_sys) ++{ ++ struct vout_isp_conf_s * const isp = &vd_sys->isp; ++ VLC_UNUSED(vd); + +- vd->pool = vd_pool; +- vd->prepare = vd_prepare; +- vd->display = vd_display; +- vd->control = vd_control; +- vd->manage = vd_manage; ++ if (isp->component == NULL) ++ return; + +- vc_tv_register_callback(tvservice_cb, vd); ++ isp_flush(isp); + +- if (query_resolution(vd, &sys->display_width, &sys->display_height) >= 0) { +- vout_display_SendEventDisplaySize(vd, sys->display_width, sys->display_height); +- } else { +- sys->display_width = vd->cfg->display.width; +- sys->display_height = vd->cfg->display.height; ++ if (isp->component->control->is_enabled) ++ mmal_port_disable(isp->component->control); ++ ++ if (isp->out_q != NULL) { ++ // 1st junk anything lying around ++ isp_empty_out_q(isp); ++ ++ mmal_queue_destroy(isp->out_q); ++ isp->out_q = NULL; + } + +- sys->dmx_handle = vc_dispmanx_display_open(0); +- vd->info.subpicture_chromas = subpicture_chromas; ++ if (isp->out_pool != NULL) { ++ mmal_port_pool_destroy(isp->output, isp->out_pool); ++ isp->out_pool = NULL; ++ } + +- vout_display_DeleteWindow(vd, NULL); ++ isp->input = NULL; ++ isp->output = NULL; + +-out: +- if (ret != VLC_SUCCESS) +- Close(object); ++ mmal_component_release(isp->component); ++ isp->component = NULL; + +- return ret; ++ return; + } + +-static void Close(vlc_object_t *object) ++// Restuff into output rather than return to pool is we can ++static MMAL_BOOL_T isp_out_pool_cb(MMAL_POOL_T *pool, MMAL_BUFFER_HEADER_T *buffer, void *userdata) + { +- vout_display_t *vd = (vout_display_t *)object; +- vout_display_sys_t *sys = vd->sys; +- char response[20]; /* answer is hvs_update_fields=%1d */ +- unsigned i; ++ struct vout_isp_conf_s * const isp = userdata; ++ VLC_UNUSED(pool); ++ if (isp->output->is_enabled) { ++ mmal_buffer_header_reset(buffer); ++ if (mmal_port_send_buffer(isp->output, buffer) == MMAL_SUCCESS) ++ return MMAL_FALSE; ++ } ++ return MMAL_TRUE; ++} + +- vc_tv_unregister_callback_full(tvservice_cb, vd); ++static MMAL_STATUS_T isp_setup(vout_display_t * const vd, vout_display_sys_t * const vd_sys) ++{ ++ struct vout_isp_conf_s * const isp = &vd_sys->isp; ++ MMAL_STATUS_T err; + +- if (sys->dmx_handle) +- close_dmx(vd); ++ if ((err = mmal_component_create(MMAL_COMPONENT_ISP_RESIZER, &isp->component)) != MMAL_SUCCESS) { ++ msg_Err(vd, "Cannot create ISP component"); ++ return err; ++ } ++ isp->input = isp->component->input[0]; ++ isp->output = isp->component->output[0]; + +- if (sys->component && sys->component->control->is_enabled) +- mmal_port_disable(sys->component->control); ++ isp->component->control->userdata = (void *)vd; ++ if ((err = mmal_port_enable(isp->component->control, isp_control_port_cb)) != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to enable ISP control port"); ++ goto fail; ++ } + +- if (sys->input && sys->input->is_enabled) +- mmal_port_disable(sys->input); ++ isp->input->userdata = (void *)vd; ++ display_set_format(vd, isp->input->format, false); + +- if (sys->component && sys->component->is_enabled) +- mmal_component_disable(sys->component); ++ if ((err = port_parameter_set_bool(isp->input, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) ++ goto fail; + +- if (sys->pool) +- mmal_port_pool_destroy(sys->input, sys->pool); ++ if ((err = mmal_port_format_commit(isp->input)) != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to set ISP input format"); ++ goto fail; ++ } + +- if (sys->component) +- mmal_component_release(sys->component); ++ isp->input->buffer_size = isp->input->buffer_size_recommended; ++ isp->input->buffer_num = 30; + +- if (sys->picture_pool) +- picture_pool_Release(sys->picture_pool); +- else +- for (i = 0; i < sys->num_buffers; ++i) +- if (sys->pictures[i]) { +- mmal_buffer_header_release(sys->pictures[i]->p_sys->buffer); +- picture_Release(sys->pictures[i]); +- } ++ if ((isp->in_pool = mmal_pool_create(isp->input->buffer_num, 0)) == NULL) ++ { ++ msg_Err(vd, "Failed to create input pool"); ++ goto fail; ++ } + +- vlc_mutex_destroy(&sys->buffer_mutex); +- vlc_cond_destroy(&sys->buffer_cond); +- vlc_mutex_destroy(&sys->manage_mutex); ++ if ((isp->out_q = mmal_queue_create()) == NULL) ++ { ++ err = MMAL_ENOMEM; ++ goto fail; ++ } + +- if (sys->native_interlaced) { +- if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 || +- response[18] != '0') +- msg_Warn(vd, "Could not reset hvs field mode"); ++ display_set_format(vd, isp->output->format, true); ++ ++ if ((err = port_parameter_set_bool(isp->output, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) ++ goto fail; ++ ++ if ((err = mmal_port_format_commit(isp->output)) != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to set ISP input format"); ++ goto fail; + } + +- free(sys->pictures); +- free(sys); ++ isp->output->buffer_size = isp->output->buffer_size_recommended; ++ isp->output->buffer_num = 2; ++ isp->output->userdata = (void *)vd; ++ ++ if ((isp->out_pool = mmal_port_pool_create(isp->output, isp->output->buffer_num, isp->output->buffer_size)) == NULL) ++ { ++ msg_Err(vd, "Failed to make ISP port pool"); ++ goto fail; ++ } ++ ++ mmal_pool_callback_set(isp->out_pool, isp_out_pool_cb, isp); ++ ++ if ((err = isp_prepare(vd, isp)) != MMAL_SUCCESS) ++ goto fail; + +- bcm_host_deinit(); ++ return MMAL_SUCCESS; ++ ++fail: ++ isp_close(vd, vd_sys); ++ return err; + } + +-static inline uint32_t align(uint32_t x, uint32_t y) { +- uint32_t mod = x % y; +- if (mod == 0) +- return x; ++static MMAL_STATUS_T isp_check(vout_display_t * const vd, vout_display_sys_t * const vd_sys) ++{ ++ struct vout_isp_conf_s *const isp = &vd_sys->isp; ++ const bool has_isp = (isp->component != NULL); ++ const bool wants_isp = want_isp(vd); ++ ++ if (has_isp == wants_isp) ++ { ++ // All OK - do nothing ++ } ++ else if (has_isp) ++ { ++ // ISP active but we don't want it ++ isp_flush(isp); ++ ++ // Check we have everything back and then kill it ++ if (mmal_queue_length(isp->out_pool->queue) == isp->output->buffer_num) ++ isp_close(vd, vd_sys); ++ } + else +- return x + y - mod; ++ { ++ // ISP closed but we want it ++ return isp_setup(vd, vd_sys); ++ } ++ ++ return MMAL_SUCCESS; ++} ++ ++/* TV service */ ++static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, ++ uint32_t param2); ++static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt); ++static int set_latency_target(vout_display_t *vd, bool enable); ++ ++// Mmal ++static void maintain_phase_sync(vout_display_t *vd); ++ ++ ++ ++static void vd_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) ++{ ++#if TRACE_ALL ++ vout_display_t * const vd = (vout_display_t *)port->userdata; ++ pic_ctx_mmal_t * ctx = buf->user_data; ++ msg_Dbg(vd, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buf->cmd, ctx, buf, ++ buf->flags, (long long)buf->pts); ++#else ++ VLC_UNUSED(port); ++#endif ++ ++ mmal_buffer_header_release(buf); ++ ++#if TRACE_ALL ++ msg_Dbg(vd, ">>> %s", __func__); ++#endif ++} ++ ++static int query_resolution(vout_display_t *vd, const int display_id, unsigned *width, unsigned *height) ++{ ++ TV_DISPLAY_STATE_T display_state = {0}; ++ int ret = 0; ++ ++ if (vc_tv_get_display_state_id(display_id, &display_state) == 0) { ++ msg_Dbg(vd, "State=%#x", display_state.state); ++ if (display_state.state & 0xFF) { ++ msg_Dbg(vd, "HDMI: %dx%d", display_state.display.hdmi.width, display_state.display.hdmi.height); ++ *width = display_state.display.hdmi.width; ++ *height = display_state.display.hdmi.height; ++ } else if (display_state.state & 0xFF00) { ++ msg_Dbg(vd, "SDTV: %dx%d", display_state.display.sdtv.width, display_state.display.sdtv.height); ++ *width = display_state.display.sdtv.width; ++ *height = display_state.display.sdtv.height; ++ } else { ++ msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state); ++ ret = -1; ++ } ++ } else { ++ msg_Warn(vd, "Failed to query display resolution"); ++ ret = -1; ++ } ++ ++ return ret; ++} ++ ++static inline MMAL_RECT_T ++place_to_mmal_rect(const vout_display_place_t place) ++{ ++ return (MMAL_RECT_T){ ++ .x = place.x, ++ .y = place.y, ++ .width = place.width, ++ .height = place.height ++ }; ++} ++ ++static MMAL_RECT_T ++place_out(const vout_display_cfg_t * cfg, ++ const video_format_t * fmt, ++ const MMAL_RECT_T r) ++{ ++ video_format_t tfmt; ++ vout_display_cfg_t tcfg; ++ vout_display_place_t place; ++ ++ // Fix SAR if unknown ++ if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) { ++ tfmt = *fmt; ++ tfmt.i_sar_den = 1; ++ tfmt.i_sar_num = 1; ++ fmt = &tfmt; ++ } ++ ++ // Override what VLC thinks might be going on with display size ++ // if we know better ++ if (r.width != 0 && r.height != 0) ++ { ++ tcfg = *cfg; ++ tcfg.display.width = r.width; ++ tcfg.display.height = r.height; ++ cfg = &tcfg; ++ } ++ ++ vout_display_PlacePicture(&place, fmt, cfg, false); ++ ++ place.x += r.x; ++ place.y += r.y; ++ ++ return place_to_mmal_rect(place); ++} ++ ++static MMAL_RECT_T ++rect_transform(MMAL_RECT_T s, const MMAL_RECT_T c, const MMAL_DISPLAYTRANSFORM_T t) ++{ ++ if (is_transform_transpose(t)) ++ s = rect_transpose(s); ++ if (is_transform_hflip(t)) ++ s = rect_hflip(s, c); ++ if (is_transform_vflip(t) != 0) ++ s = rect_vflip(s, c); ++ return s; ++} ++ ++static void ++place_dest_rect(vout_display_t * const vd, ++ const vout_display_cfg_t * const cfg, ++ const video_format_t * fmt) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ sys->dest_rect = rect_transform(place_out(cfg, fmt, sys->win_rect), ++ sys->display_rect, sys->dest_transform); ++} ++ ++static void ++place_spu_rect(vout_display_t * const vd, ++ const vout_display_cfg_t * const cfg, ++ const video_format_t * fmt) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ static const MMAL_RECT_T r0 = {0}; ++ ++ sys->spu_rect = place_out(cfg, fmt, r0); ++ sys->spu_rect.x = 0; ++ sys->spu_rect.y = 0; ++ ++ // Copy place override logic for spu pos from video_output.c ++ // This info doesn't appear to reside anywhere natively ++ ++ if (fmt->i_width * fmt->i_height >= (unsigned int)(sys->spu_rect.width * sys->spu_rect.height)) { ++ sys->spu_rect.width = fmt->i_visible_width; ++ sys->spu_rect.height = fmt->i_visible_height; ++ } ++ ++ if (ORIENT_IS_SWAP(fmt->orientation)) ++ sys->spu_rect = rect_transpose(sys->spu_rect); ++} ++ ++static void ++place_rects(vout_display_t * const vd, ++ const vout_display_cfg_t * const cfg, ++ const video_format_t * fmt) ++{ ++ place_dest_rect(vd, cfg, fmt); ++ place_spu_rect(vd, cfg, fmt); ++} ++ ++static int ++set_input_region(vout_display_t * const vd, const video_format_t * const fmt) ++{ ++ const vout_display_sys_t * const sys = vd->sys; ++ MMAL_DISPLAYREGION_T display_region = { ++ .hdr = { ++ .id = MMAL_PARAMETER_DISPLAYREGION, ++ .size = sizeof(MMAL_DISPLAYREGION_T) ++ }, ++ .display_num = sys->display_id, ++ .fullscreen = MMAL_FALSE, ++ .transform = sys->video_transform, ++ .dest_rect = sys->dest_rect, ++ .src_rect = display_src_rect(vd, fmt), ++ .noaspect = MMAL_TRUE, ++ .mode = MMAL_DISPLAY_MODE_FILL, ++ .layer = sys->layer, ++ .alpha = 0xff | (sys->transparent ? 0 : (1 << 29)), ++ .set = ++ MMAL_DISPLAY_SET_NUM | ++ MMAL_DISPLAY_SET_FULLSCREEN | ++ MMAL_DISPLAY_SET_TRANSFORM | ++ MMAL_DISPLAY_SET_DEST_RECT | ++ MMAL_DISPLAY_SET_SRC_RECT | ++ MMAL_DISPLAY_SET_NOASPECT | ++ MMAL_DISPLAY_SET_MODE | ++ MMAL_DISPLAY_SET_LAYER | ++ MMAL_DISPLAY_SET_ALPHA ++ }; ++ MMAL_STATUS_T status = mmal_port_parameter_set(sys->input, &display_region.hdr); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)", ++ status, mmal_status_to_string(status)); ++ return -EINVAL; ++ } ++ return 0; + } + + static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg, + const video_format_t *fmt) + { +- vout_display_sys_t *sys = vd->sys; +- vout_display_place_t place; +- MMAL_DISPLAYREGION_T display_region; ++ vout_display_sys_t * const sys = vd->sys; + MMAL_STATUS_T status; + + if (!cfg && !fmt) ++ { ++ msg_Err(vd, "%s: Missing cfg & fmt", __func__); + return -EINVAL; ++ } ++ ++ isp_check(vd, sys); + + if (fmt) { + sys->input->format->es->video.par.num = fmt->i_sar_num; +@@ -412,30 +733,14 @@ static int configure_display(vout_displa + if (!cfg) + cfg = vd->cfg; + +- vout_display_PlacePicture(&place, fmt, cfg, false); ++ sys->video_transform = combine_transform( ++ vlc_to_mmal_transform(fmt->orientation), sys->display_transform); + +- display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION; +- display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T); +- display_region.fullscreen = MMAL_FALSE; +- display_region.src_rect.x = fmt->i_x_offset; +- display_region.src_rect.y = fmt->i_y_offset; +- display_region.src_rect.width = fmt->i_visible_width; +- display_region.src_rect.height = fmt->i_visible_height; +- display_region.dest_rect.x = place.x; +- display_region.dest_rect.y = place.y; +- display_region.dest_rect.width = place.width; +- display_region.dest_rect.height = place.height; +- display_region.layer = sys->layer; +- display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT | +- MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER; +- status = mmal_port_parameter_set(sys->input, &display_region.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)", +- status, mmal_status_to_string(status)); ++ place_rects(vd, cfg, fmt); ++ ++ if (set_input_region(vd, fmt) != 0) + return -EINVAL; +- } + +- show_background(vd, var_InheritBool(vd, MMAL_BLANK_BACKGROUND_NAME)); + sys->adjust_refresh_rate = var_InheritBool(vd, MMAL_ADJUST_REFRESHRATE_NAME); + sys->native_interlaced = var_InheritBool(vd, MMAL_NATIVE_INTERLACED); + if (sys->adjust_refresh_rate) { +@@ -446,205 +751,218 @@ static int configure_display(vout_displa + return 0; + } + ++static void kill_pool(vout_display_sys_t * const sys) ++{ ++ if (sys->pic_pool != NULL) { ++ picture_pool_Release(sys->pic_pool); ++ sys->pic_pool = NULL; ++ } ++} ++ ++// Actual picture pool for MMAL opaques is just a set of trivial containers + static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count) + { +- vout_display_sys_t *sys = vd->sys; +- picture_resource_t picture_res; +- picture_pool_configuration_t picture_pool_cfg; +- video_format_t fmt = vd->fmt; +- MMAL_STATUS_T status; +- unsigned i; ++ vout_display_sys_t * const sys = vd->sys; + +- if (sys->picture_pool) { +- if (sys->num_buffers < count) +- msg_Warn(vd, "Picture pool with %u pictures requested, but we already have one with %u pictures", +- count, sys->num_buffers); ++ msg_Dbg(vd, "%s: fmt:%dx%d,sar:%d/%d; source:%dx%d", __func__, ++ vd->fmt.i_width, vd->fmt.i_height, vd->fmt.i_sar_num, vd->fmt.i_sar_den, vd->source.i_width, vd->source.i_height); + +- goto out; ++ if (sys->pic_pool == NULL) { ++ sys->pic_pool = picture_pool_NewFromFormat(&vd->fmt, count); + } ++ return sys->pic_pool; ++} + +- if (sys->opaque) { +- if (count <= NUM_ACTUAL_OPAQUE_BUFFERS) +- count = NUM_ACTUAL_OPAQUE_BUFFERS; ++static inline bool ++check_shape(vout_display_t * const vd, const picture_t * const p_pic) ++{ ++ if (vd->fmt.i_width == p_pic->format.i_width && ++ vd->fmt.i_height == p_pic->format.i_height) ++ return true; ++ return false; ++} + +- MMAL_PARAMETER_BOOLEAN_T zero_copy = { +- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, +- 1 +- }; ++static void vd_display(vout_display_t *vd, picture_t *p_pic, ++ subpicture_t *subpicture) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ MMAL_STATUS_T err; + +- status = mmal_port_parameter_set(sys->input, &zero_copy.hdr); +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- goto out; +- } ++#if TRACE_ALL ++ { ++ char dbuf0[5]; ++ msg_Dbg(vd, "<<< %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %dx%d@%d,%d", __func__, ++ str_fourcc(dbuf0, p_pic->format.i_chroma), p_pic->format.i_width, p_pic->format.i_height, ++ p_pic->format.i_x_offset, p_pic->format.i_y_offset, ++ p_pic->format.i_visible_width, p_pic->format.i_visible_height, ++ p_pic->format.i_sar_num, p_pic->format.i_sar_den, ++ sys->dest_rect.width, sys->dest_rect.height, sys->dest_rect.x, sys->dest_rect.y); + } +- +- if (count < sys->input->buffer_num_recommended) +- count = sys->input->buffer_num_recommended; +- +-#ifndef NDEBUG +- msg_Dbg(vd, "Creating picture pool with %u pictures", count); + #endif + +- sys->input->buffer_num = count; +- status = mmal_port_enable(sys->input, input_port_cb); +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)", +- sys->input->name, status, mmal_status_to_string(status)); +- goto out; ++ // If we had subpics then we have attached them to the main pic in prepare ++ // so all we have to do here is delete the refs ++ if (subpicture != NULL) { ++ subpicture_Delete(subpicture); + } + +- status = mmal_component_enable(sys->component); +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)", +- sys->component->name, status, mmal_status_to_string(status)); +- goto out; ++ if (!check_shape(vd, p_pic)) ++ { ++ msg_Err(vd, "Pic/fmt shape mismatch"); ++ goto fail; ++ } ++ ++ if (!sys->input->is_enabled && ++ (err = mmal_port_enable(sys->input, vd_input_port_cb)) != MMAL_SUCCESS) ++ { ++ msg_Err(vd, "Input port enable failed"); ++ goto fail; ++ } ++ // Stuff into input ++ // We assume the BH is already set up with values reflecting pic date etc. ++ if (sys->copy_buf != NULL) { ++ MMAL_BUFFER_HEADER_T *const buf = sys->copy_buf; ++ sys->copy_buf = NULL; ++#if TRACE_ALL ++ msg_Dbg(vd, "--- %s: Copy stuff", __func__); ++#endif ++ if (mmal_port_send_buffer(sys->input, buf) != MMAL_SUCCESS) ++ { ++ mmal_buffer_header_release(buf); ++ msg_Err(vd, "Send copy buffer to render input failed"); ++ goto fail; ++ } + } +- +- sys->num_buffers = count; +- sys->pool = mmal_port_pool_create(sys->input, sys->num_buffers, +- sys->input->buffer_size); +- if (!sys->pool) { +- msg_Err(vd, "Failed to create MMAL pool for %u buffers of size %"PRIu32, +- count, sys->input->buffer_size); +- goto out; ++ else if (sys->isp.pending) { ++ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(sys->isp.out_q); ++ sys->isp.pending = false; ++#if TRACE_ALL ++ msg_Dbg(vd, "--- %s: ISP stuff", __func__); ++#endif ++ if (mmal_port_send_buffer(sys->input, buf) != MMAL_SUCCESS) ++ { ++ mmal_buffer_header_release(buf); ++ msg_Err(vd, "Send ISP buffer to render input failed"); ++ goto fail; ++ } + } +- +- memset(&picture_res, 0, sizeof(picture_resource_t)); +- sys->pictures = calloc(sys->num_buffers, sizeof(picture_t *)); +- for (i = 0; i < sys->num_buffers; ++i) { +- picture_res.p_sys = calloc(1, sizeof(picture_sys_t)); +- picture_res.p_sys->owner = (vlc_object_t *)vd; +- picture_res.p_sys->buffer = mmal_queue_get(sys->pool->queue); +- +- sys->pictures[i] = picture_NewFromResource(&fmt, &picture_res); +- if (!sys->pictures[i]) { +- msg_Err(vd, "Failed to create picture"); +- free(picture_res.p_sys); +- goto out; ++ else ++ { ++ MMAL_BUFFER_HEADER_T *const pic_buf = hw_mmal_pic_buf_replicated(p_pic, sys->pool); ++ if (pic_buf == NULL) ++ { ++ msg_Err(vd, "Replicated buffer get fail"); ++ goto fail; + } + +- sys->pictures[i]->i_planes = sys->i_planes; +- memcpy(sys->pictures[i]->p, sys->planes, sys->i_planes * sizeof(plane_t)); +- } + +- memset(&picture_pool_cfg, 0, sizeof(picture_pool_configuration_t)); +- picture_pool_cfg.picture_count = sys->num_buffers; +- picture_pool_cfg.picture = sys->pictures; +- picture_pool_cfg.lock = mmal_picture_lock; ++ // If dimensions have chnaged then fix that ++ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic)) ++ { ++ msg_Dbg(vd, "Reset port format"); ++ ++ // HVS can deal with on-line dimension changes ++ if (mmal_port_format_commit(sys->input) != MMAL_SUCCESS) ++ msg_Warn(vd, "Input format commit failed"); ++ } + +- sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg); +- if (!sys->picture_pool) { +- msg_Err(vd, "Failed to create picture pool"); +- goto out; ++ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS) ++ { ++ mmal_buffer_header_release(pic_buf); ++ msg_Err(vd, "Send buffer to input failed"); ++ goto fail; ++ } + } + +-out: +- return sys->picture_pool; +-} +- +-static void vd_prepare(vout_display_t *vd, picture_t *picture, +- subpicture_t *subpicture) +-{ +- vout_display_sys_t *sys = vd->sys; +- picture_sys_t *pic_sys = picture->p_sys; +- +- if (!sys->adjust_refresh_rate || pic_sys->displayed) +- return; +- +- /* Apply the required phase_offset to the picture, so that vd_display() +- * will be called at the corrected time from the core */ +- picture->date += sys->phase_offset; +-} +- +-static void vd_display(vout_display_t *vd, picture_t *picture, +- subpicture_t *subpicture) +-{ +- vout_display_sys_t *sys = vd->sys; +- picture_sys_t *pic_sys = picture->p_sys; +- MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer; +- MMAL_STATUS_T status; +- +- if (picture->format.i_frame_rate != sys->i_frame_rate || +- picture->format.i_frame_rate_base != sys->i_frame_rate_base || +- picture->b_progressive != sys->b_progressive || +- picture->b_top_field_first != sys->b_top_field_first) { +- sys->b_top_field_first = picture->b_top_field_first; +- sys->b_progressive = picture->b_progressive; +- sys->i_frame_rate = picture->format.i_frame_rate; +- sys->i_frame_rate_base = picture->format.i_frame_rate_base; +- configure_display(vd, NULL, &picture->format); +- } +- +- if (!pic_sys->displayed || !sys->opaque) { +- buffer->cmd = 0; +- buffer->length = sys->input->buffer_size; +- buffer->user_data = picture; +- +- status = mmal_port_send_buffer(sys->input, buffer); +- if (status == MMAL_SUCCESS) +- atomic_fetch_add(&sys->buffers_in_transit, 1); +- +- if (status != MMAL_SUCCESS) { +- msg_Err(vd, "Failed to send buffer to input port. Frame dropped"); +- picture_Release(picture); ++ { ++ unsigned int sub_no = 0; ++ MMAL_BUFFER_HEADER_T **psub_bufs2 = sys->subpic_bufs; ++ const bool is_mmal_pic = hw_mmal_pic_is_mmal(p_pic); ++ ++ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { ++ int rv; ++ MMAL_BUFFER_HEADER_T * const sub_buf = !is_mmal_pic ? NULL : ++ hw_mmal_pic_sub_buf_get(p_pic, sub_no); ++ ++ if ((rv = hw_mmal_subpic_update(VLC_OBJECT(vd), ++ sub_buf != NULL ? sub_buf : *psub_bufs2++, ++ &sys->subs[sub_no].sub, ++ &p_pic->format, ++ &sys->dest_rect, ++ sys->display_transform, ++ p_pic->date)) == 0) ++ break; ++ else if (rv < 0) ++ goto fail; + } +- +- pic_sys->displayed = true; +- } else { +- picture_Release(picture); + } + +- display_subpicture(vd, subpicture); ++fail: ++ for (unsigned int i = 0; i != SUBS_MAX && sys->subpic_bufs[i] != NULL; ++i) { ++ mmal_buffer_header_release(sys->subpic_bufs[i]); ++ sys->subpic_bufs[i] = NULL; ++ } + +- if (subpicture) +- subpicture_Delete(subpicture); ++ picture_Release(p_pic); + + if (sys->next_phase_check == 0 && sys->adjust_refresh_rate) + maintain_phase_sync(vd); + sys->next_phase_check = (sys->next_phase_check + 1) % PHASE_CHECK_INTERVAL; +- +- if (sys->opaque) { +- vlc_mutex_lock(&sys->buffer_mutex); +- while (atomic_load(&sys->buffers_in_transit) >= MAX_BUFFERS_IN_TRANSIT) +- vlc_cond_wait(&sys->buffer_cond, &sys->buffer_mutex); +- vlc_mutex_unlock(&sys->buffer_mutex); +- } + } + + static int vd_control(vout_display_t *vd, int query, va_list args) + { +- vout_display_sys_t *sys = vd->sys; +- vout_display_cfg_t cfg; +- const vout_display_cfg_t *tmp_cfg; ++ vout_display_sys_t * const sys = vd->sys; + int ret = VLC_EGENERIC; ++ VLC_UNUSED(args); + + switch (query) { +- case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: +- tmp_cfg = va_arg(args, const vout_display_cfg_t *); +- if (tmp_cfg->display.width == sys->display_width && +- tmp_cfg->display.height == sys->display_height) { +- cfg = *vd->cfg; +- cfg.display.width = sys->display_width; +- cfg.display.height = sys->display_height; +- if (configure_display(vd, &cfg, NULL) >= 0) +- ret = VLC_SUCCESS; +- } +- break; +- + case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: + case VOUT_DISPLAY_CHANGE_SOURCE_CROP: +- if (configure_display(vd, NULL, &vd->source) >= 0) ++ if (configure_display(vd, vd->cfg, &vd->source) >= 0) + ret = VLC_SUCCESS; + break; + +- case VOUT_DISPLAY_RESET_PICTURES: +- vlc_assert_unreachable(); + case VOUT_DISPLAY_CHANGE_ZOOM: +- msg_Warn(vd, "Unsupported control query %d", query); ++ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: ++ case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED: ++ { ++ const vout_display_cfg_t * const cfg = va_arg(args, const vout_display_cfg_t *); ++ ++ if (configure_display(vd, cfg, &vd->source) >= 0) ++ ret = VLC_SUCCESS; ++ break; ++ } ++ ++ case VOUT_DISPLAY_RESET_PICTURES: ++ msg_Warn(vd, "Reset Pictures"); ++ kill_pool(sys); ++ vd->fmt = vd->source; // Take (nearly) whatever source wants to give us ++ vd->fmt.i_chroma = req_chroma(vd); // Adjust chroma to something we can actaully deal with ++ ret = VLC_SUCCESS; + break; + ++ case VOUT_DISPLAY_CHANGE_MMAL_HIDE: ++ { ++ MMAL_STATUS_T err; ++ unsigned int i; ++ ++ msg_Dbg(vd, "Hide display"); ++ ++ for (i = 0; i != SUBS_MAX; ++i) ++ hw_mmal_subpic_flush(VLC_OBJECT(vd), &sys->subs[i].sub); ++ ++ if (sys->input->is_enabled && ++ (err = mmal_port_disable(sys->input)) != MMAL_SUCCESS) ++ { ++ msg_Err(vd, "Unable to disable port: err=%d", err); ++ break; ++ } ++ sys->force_config = true; ++ ret = VLC_SUCCESS; ++ break; ++ } ++ + default: + msg_Warn(vd, "Unknown control query %d", query); + break; +@@ -653,79 +971,208 @@ static int vd_control(vout_display_t *vd + return ret; + } + ++static void set_display_windows(vout_display_t *const vd, vout_display_sys_t *const sys) ++{ ++ unsigned int width, height; ++ if (query_resolution(vd, sys->display_id, &width, &height) < 0) { ++ width = vd->cfg->display.width; ++ height = vd->cfg->display.height; ++ } ++ sys->display_rect = (MMAL_RECT_T){0, 0, width, height}; ++ ++ sys->win_rect = (sys->req_win.width != 0) ? ++ sys->req_win : ++ is_transform_transpose(sys->display_transform) ? ++ rect_transpose(sys->display_rect) : sys->display_rect; ++} ++ + static void vd_manage(vout_display_t *vd) + { +- vout_display_sys_t *sys = vd->sys; +- unsigned width, height; ++ vout_display_sys_t *const sys = vd->sys; + + vlc_mutex_lock(&sys->manage_mutex); + + if (sys->need_configure_display) { +- close_dmx(vd); +- sys->dmx_handle = vc_dispmanx_display_open(0); +- +- if (query_resolution(vd, &width, &height) >= 0) { +- sys->display_width = width; +- sys->display_height = height; +- vout_display_SendEventDisplaySize(vd, width, height); +- } +- + sys->need_configure_display = false; ++ set_display_windows(vd, sys); + } + + vlc_mutex_unlock(&sys->manage_mutex); + } + +-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++ ++static int attach_subpics(vout_display_t * const vd, vout_display_sys_t * const sys, ++ subpicture_t * const subpicture) + { +- vout_display_t *vd = (vout_display_t *)port->userdata; +- MMAL_STATUS_T status; ++ unsigned int n = 0; + +- if (buffer->cmd == MMAL_EVENT_ERROR) { +- status = *(uint32_t *)buffer->data; +- msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); ++ if (sys->vzc == NULL) { ++ if ((sys->vzc = hw_mmal_vzc_pool_new()) == NULL) ++ { ++ msg_Err(vd, "Failed to allocate VZC"); ++ return VLC_ENOMEM; ++ } + } + +- mmal_buffer_header_release(buffer); ++ // Attempt to import the subpics ++ for (subpicture_t * spic = subpicture; spic != NULL; spic = spic->p_next) ++ { ++ for (subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) { ++ picture_t *const src = sreg->p_picture; ++ ++#if TRACE_ALL ++ char dbuf0[5]; ++ msg_Dbg(vd, " [%p:%p] Pos=%d,%d max=%dx%d, src=%dx%d/%dx%d o:%d, spu=%d,%d:%dx%d, vd->fmt=%dx%d/%dx%d, vd->source=%dx%d/%dx%d, cfg=%dx%d, zoom=%d/%d, Alpha=%d, Fmt=%s", src, src->p[0].p_pixels, ++ sreg->i_x, sreg->i_y, ++ sreg->i_max_width, sreg->i_max_height, ++ src->format.i_visible_width, src->format.i_visible_height, ++ src->format.i_width, src->format.i_height, ++ src->format.orientation, ++ sys->spu_rect.x, sys->spu_rect.y, sys->spu_rect.width, sys->spu_rect.height, ++ vd->fmt.i_visible_width, vd->fmt.i_visible_height, ++ vd->fmt.i_width, vd->fmt.i_height, ++ vd->source.i_visible_width, vd->source.i_visible_height, ++ vd->source.i_width, vd->source.i_height, ++ vd->cfg->display.width, vd->cfg->display.height, ++ vd->cfg->zoom.num, vd->cfg->zoom.den, ++ sreg->i_alpha, ++ str_fourcc(dbuf0, src->format.i_chroma)); ++#endif ++ ++ // At this point I think the subtitles are being placed in the ++ // coord space of the placed rectangle in the cfg display space ++ if ((sys->subpic_bufs[n] = hw_mmal_vzc_buf_from_pic(sys->vzc, ++ src, ++ &sreg->fmt, ++ (MMAL_RECT_T){.width = sys->spu_rect.width, .height=sys->spu_rect.height}, ++ sreg->i_x, sreg->i_y, ++ sreg->i_alpha, ++ n == 0)) == NULL) ++ { ++ msg_Err(vd, "Failed to allocate vzc buffer for subpic"); ++ return VLC_ENOMEM; ++ } ++ ++ if (++n == SUBS_MAX) ++ return VLC_SUCCESS; ++ } ++ } ++ return VLC_SUCCESS; + } + +-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++ ++static void vd_prepare(vout_display_t *vd, picture_t *p_pic, ++#if VLC_VER_3 ++ subpicture_t *subpicture ++#else ++ subpicture_t *subpicture, vlc_tick_t date ++#endif ++ ) + { +- vout_display_t *vd = (vout_display_t *)port->userdata; ++ MMAL_STATUS_T err; ++ vout_display_sys_t * const sys = vd->sys; ++ ++ vd_manage(vd); ++ ++ if (!check_shape(vd, p_pic)) ++ return; ++ ++ if (sys->force_config || ++ p_pic->format.i_frame_rate != sys->i_frame_rate || ++ p_pic->format.i_frame_rate_base != sys->i_frame_rate_base || ++ p_pic->b_progressive != sys->b_progressive || ++ p_pic->b_top_field_first != sys->b_top_field_first) ++ { ++ sys->force_config = false; ++ sys->b_top_field_first = p_pic->b_top_field_first; ++ sys->b_progressive = p_pic->b_progressive; ++ sys->i_frame_rate = p_pic->format.i_frame_rate; ++ sys->i_frame_rate_base = p_pic->format.i_frame_rate_base; ++ configure_display(vd, NULL, &vd->source); ++ } ++ ++ // Subpics can either turn up attached to the main pic or in the ++ // subpic list here - if they turn up here then process into temp ++ // buffers ++ if (subpicture != NULL) { ++ attach_subpics(vd, sys, subpicture); ++ } ++ ++ // ***** ++ if (want_copy(vd)) { ++ if (sys->copy_buf != NULL) { ++ msg_Err(vd, "Copy buf not NULL"); ++ mmal_buffer_header_release(sys->copy_buf); ++ sys->copy_buf = NULL; ++ } ++ ++ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_wait(sys->copy_pool->queue); ++ // Copy 2d ++ hw_mmal_copy_pic_to_buf(buf->data, &buf->length, sys->input->format, p_pic); ++ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END; ++ ++ sys->copy_buf = buf; ++ } ++ ++ if (isp_check(vd, sys) != MMAL_SUCCESS) { ++ return; ++ } ++ ++ if (want_isp(vd)) ++ { ++ struct vout_isp_conf_s * const isp = &sys->isp; ++ MMAL_BUFFER_HEADER_T * buf; ++ ++ // This should be empty - make it so if it isn't ++ isp_empty_out_q(isp); ++ isp->pending = false; ++ ++ // Stuff output ++ if (isp_prepare(vd, isp) != MMAL_SUCCESS) ++ return; ++ ++ if ((buf = hw_mmal_pic_buf_replicated(p_pic, isp->in_pool)) == NULL) ++ { ++ msg_Err(vd, "Pic has no attached buffer"); ++ return; ++ } ++ ++ if ((err = mmal_port_send_buffer(isp->input, buf)) != MMAL_SUCCESS) ++ { ++ msg_Err(vd, "Send buffer to input failed"); ++ mmal_buffer_header_release(buf); ++ return; ++ } ++ ++ isp->pending = true; ++ } ++ ++#if 0 ++ VLC_UNUSED(date); + vout_display_sys_t *sys = vd->sys; +- picture_t *picture = (picture_t *)buffer->user_data; ++ picture_sys_t *pic_sys = picture->p_sys; + +- if (picture) +- picture_Release(picture); ++ if (!sys->adjust_refresh_rate || pic_sys->displayed) ++ return; + +- vlc_mutex_lock(&sys->buffer_mutex); +- atomic_fetch_sub(&sys->buffers_in_transit, 1); +- vlc_cond_signal(&sys->buffer_cond); +- vlc_mutex_unlock(&sys->buffer_mutex); ++ /* Apply the required phase_offset to the picture, so that vd_display() ++ * will be called at the corrected time from the core */ ++ picture->date += sys->phase_offset; ++#endif + } + +-static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height) ++ ++static void vd_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) + { +- TV_DISPLAY_STATE_T display_state; +- int ret = 0; ++ vout_display_t *vd = (vout_display_t *)port->userdata; ++ MMAL_STATUS_T status; + +- if (vc_tv_get_display_state(&display_state) == 0) { +- if (display_state.state & 0xFF) { +- *width = display_state.display.hdmi.width; +- *height = display_state.display.hdmi.height; +- } else if (display_state.state & 0xFF00) { +- *width = display_state.display.sdtv.width; +- *height = display_state.display.sdtv.height; +- } else { +- msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state); +- ret = -1; +- } +- } else { +- msg_Warn(vd, "Failed to query display resolution"); +- ret = -1; ++ if (buffer->cmd == MMAL_EVENT_ERROR) { ++ status = *(uint32_t *)buffer->data; ++ msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); + } + +- return ret; ++ mmal_buffer_header_release(buffer); + } + + static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, uint32_t param2) +@@ -777,12 +1224,12 @@ static void adjust_refresh_rate(vout_dis + int num_modes; + double frame_rate = (double)fmt->i_frame_rate / fmt->i_frame_rate_base; + int best_id = -1; +- double best_score, score; ++ double best_score = 0.0, score; + int i; + +- vc_tv_get_display_state(&display_state); ++ vc_tv_get_display_state_id(sys->display_id, &display_state); + if(display_state.display.hdmi.mode != HDMI_MODE_OFF) { +- num_modes = vc_tv_hdmi_get_supported_modes_new(display_state.display.hdmi.group, ++ num_modes = vc_tv_hdmi_get_supported_modes_new_id(sys->display_id, display_state.display.hdmi.group, + supported_modes, VC_TV_MAX_MODE_IDS, NULL, NULL); + + for (i = 0; i < num_modes; ++i) { +@@ -810,7 +1257,7 @@ static void adjust_refresh_rate(vout_dis + if((best_id >= 0) && (display_state.display.hdmi.mode != supported_modes[best_id].code)) { + msg_Info(vd, "Setting HDMI refresh rate to %"PRIu32, + supported_modes[best_id].frame_rate); +- vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_HDMI, ++ vc_tv_hdmi_power_on_explicit_new_id(sys->display_id, HDMI_MODE_HDMI, + supported_modes[best_id].group, + supported_modes[best_id].code); + } +@@ -828,148 +1275,12 @@ static void adjust_refresh_rate(vout_dis + } + } + +-static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture) +-{ +- vout_display_sys_t *sys = vd->sys; +- struct dmx_region_t **dmx_region = &sys->dmx_region; +- struct dmx_region_t *unused_dmx_region; +- DISPMANX_UPDATE_HANDLE_T update = 0; +- picture_t *picture; +- video_format_t *fmt; +- struct dmx_region_t *dmx_region_next; +- +- if(subpicture) { +- subpicture_region_t *region = subpicture->p_region; +- while(region) { +- picture = region->p_picture; +- fmt = ®ion->fmt; +- +- if(!*dmx_region) { +- if(!update) +- update = vc_dispmanx_update_start(10); +- *dmx_region = dmx_region_new(vd, update, region); +- } else if(((*dmx_region)->bmp_rect.width != (int32_t)fmt->i_visible_width) || +- ((*dmx_region)->bmp_rect.height != (int32_t)fmt->i_visible_height) || +- ((*dmx_region)->pos_x != region->i_x) || +- ((*dmx_region)->pos_y != region->i_y) || +- ((*dmx_region)->alpha.opacity != (uint32_t)region->i_alpha)) { +- dmx_region_next = (*dmx_region)->next; +- if(!update) +- update = vc_dispmanx_update_start(10); +- dmx_region_delete(*dmx_region, update); +- *dmx_region = dmx_region_new(vd, update, region); +- (*dmx_region)->next = dmx_region_next; +- } else if((*dmx_region)->picture != picture) { +- if(!update) +- update = vc_dispmanx_update_start(10); +- dmx_region_update(*dmx_region, update, picture); +- } +- +- dmx_region = &(*dmx_region)->next; +- region = region->p_next; +- } +- } +- +- /* Remove remaining regions */ +- unused_dmx_region = *dmx_region; +- while(unused_dmx_region) { +- dmx_region_next = unused_dmx_region->next; +- if(!update) +- update = vc_dispmanx_update_start(10); +- dmx_region_delete(unused_dmx_region, update); +- unused_dmx_region = dmx_region_next; +- } +- *dmx_region = NULL; +- +- if(update) +- vc_dispmanx_update_submit_sync(update); +-} +- +-static void close_dmx(vout_display_t *vd) +-{ +- vout_display_sys_t *sys = vd->sys; - DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(10); - struct dmx_region_t *dmx_region = sys->dmx_region; - struct dmx_region_t *dmx_region_next; @@ -12648,1056 +15099,10040 @@ to enable raspiberry pi support. ((double)vd->sys->i_frame_rate / vd->sys->i_frame_rate_base); vout_display_sys_t *sys = vd->sys; -@@ -1012,32 +1322,436 @@ static void maintain_phase_sync(vout_dis +@@ -1012,32 +1323,441 @@ static void maintain_phase_sync(vout_dis + } + } + +-static void show_background(vout_display_t *vd, bool enable) ++static void CloseMmalVout(vlc_object_t *object) + { +- vout_display_sys_t *sys = vd->sys; +- uint32_t image_ptr, color = 0xFF000000; +- VC_RECT_T dst_rect, src_rect; +- DISPMANX_UPDATE_HANDLE_T update; +- +- if (enable && !sys->bkg_element) { +- sys->bkg_resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32, 1, 1, +- &image_ptr); +- vc_dispmanx_rect_set(&dst_rect, 0, 0, 1, 1); +- vc_dispmanx_resource_write_data(sys->bkg_resource, VC_IMAGE_RGBA32, +- sizeof(color), &color, &dst_rect); +- vc_dispmanx_rect_set(&src_rect, 0, 0, 1 << 16, 1 << 16); +- vc_dispmanx_rect_set(&dst_rect, 0, 0, 0, 0); +- update = vc_dispmanx_update_start(0); +- sys->bkg_element = vc_dispmanx_element_add(update, sys->dmx_handle, +- sys->layer - 1, &dst_rect, sys->bkg_resource, &src_rect, +- DISPMANX_PROTECTION_NONE, NULL, NULL, VC_IMAGE_ROT0); +- vc_dispmanx_update_submit_sync(update); +- } else if (!enable && sys->bkg_element) { +- update = vc_dispmanx_update_start(0); +- vc_dispmanx_element_remove(update, sys->bkg_element); +- vc_dispmanx_resource_delete(sys->bkg_resource); +- vc_dispmanx_update_submit_sync(update); +- sys->bkg_element = DISPMANX_NO_HANDLE; +- sys->bkg_resource = DISPMANX_NO_HANDLE; ++ vout_display_t * const vd = (vout_display_t *)object; ++ vout_display_sys_t * const sys = vd->sys; ++ char response[20]; /* answer is hvs_update_fields=%1d */ ++ ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s", __func__); ++#endif ++ ++ kill_pool(sys); ++ ++ vc_tv_unregister_callback_full(tvservice_cb, vd); ++ ++ // Shouldn't be anything here - but just in case ++ for (unsigned int i = 0; i != SUBS_MAX; ++i) ++ if (sys->subpic_bufs[i] != NULL) ++ mmal_buffer_header_release(sys->subpic_bufs[i]); ++ ++ for (unsigned int i = 0; i != SUBS_MAX; ++i) { ++ vout_subpic_t * const sub = sys->subs + i; ++ if (sub->component != NULL) { ++ hw_mmal_subpic_close(VLC_OBJECT(vd), &sub->sub); ++ if (sub->component->control->is_enabled) ++ mmal_port_disable(sub->component->control); ++ if (sub->component->is_enabled) ++ mmal_component_disable(sub->component); ++ mmal_component_release(sub->component); ++ sub->component = NULL; ++ } ++ } ++ ++ if (sys->input && sys->input->is_enabled) ++ mmal_port_disable(sys->input); ++ ++ if (sys->component && sys->component->control->is_enabled) ++ mmal_port_disable(sys->component->control); ++ ++ if (sys->copy_buf != NULL) ++ mmal_buffer_header_release(sys->copy_buf); ++ ++ if (sys->input != NULL && sys->copy_pool != NULL) ++ mmal_port_pool_destroy(sys->input, sys->copy_pool); ++ ++ if (sys->component && sys->component->is_enabled) ++ mmal_component_disable(sys->component); ++ ++ if (sys->pool) ++ mmal_pool_destroy(sys->pool); ++ ++ if (sys->component) ++ mmal_component_release(sys->component); ++ ++ isp_close(vd, sys); ++ ++ hw_mmal_vzc_pool_release(sys->vzc); ++ ++ vlc_mutex_destroy(&sys->manage_mutex); ++ ++ if (sys->native_interlaced) { ++ if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 || ++ response[18] != '0') ++ msg_Warn(vd, "Could not reset hvs field mode"); ++ } ++ ++ cma_vcsm_exit(sys->init_type);; ++ ++ free(sys); ++ ++#if TRACE_ALL ++ msg_Dbg(vd, ">>> %s", __func__); ++#endif ++} ++ ++ ++static const struct { ++ const char * name; ++ int num; ++} display_name_to_num[] = { ++ {"auto", -1}, ++ {"hdmi-1", DISPMANX_ID_HDMI0}, ++ {"hdmi-2", DISPMANX_ID_HDMI1}, ++ {NULL, -2} ++}; ++ ++static const struct { ++ const char * name; ++ int transform_num; ++} transform_name_to_num[] = { ++ {"auto", -1}, ++ {"0", MMAL_DISPLAY_ROT0}, ++ {"hflip", MMAL_DISPLAY_MIRROR_ROT0}, ++ {"vflip", MMAL_DISPLAY_MIRROR_ROT180}, ++ {"180", MMAL_DISPLAY_ROT180}, ++ {"transpose", MMAL_DISPLAY_MIRROR_ROT90}, ++ {"270", MMAL_DISPLAY_ROT270}, ++ {"90", MMAL_DISPLAY_ROT90}, ++ {"antitranspose", MMAL_DISPLAY_MIRROR_ROT270}, ++ {NULL, -2} ++}; ++ ++static int find_display_num(const char * const name) ++{ ++ unsigned int i; ++ for (i = 0; display_name_to_num[i].name != NULL && strcasecmp(display_name_to_num[i].name, name) != 0; ++i) ++ /* Loop */; ++ return display_name_to_num[i].num; ++} ++ ++static int find_transform_num(const char * const name) ++{ ++ unsigned int i; ++ for (i = 0; transform_name_to_num[i].name != NULL && strcasecmp(transform_name_to_num[i].name, name) != 0; ++i) ++ /* Loop */; ++ return transform_name_to_num[i].transform_num; ++} ++ ++#if HAVE_X11_XLIB_H ++#include ++#include ++static MMAL_DISPLAYTRANSFORM_T get_xrandr_rotation(vout_display_t * const vd) ++{ ++ Display * const x = XOpenDisplay(NULL); ++ Rotation cur_rot = 0; ++ MMAL_DISPLAYTRANSFORM_T trans; ++ ++ if (x == NULL) ++ return MMAL_DISPLAY_ROT0; ++ ++ XRRRotations(x, 0, &cur_rot); ++ XCloseDisplay(x); ++ ++ // Convert to MMAL ++ // xrandr seems to rotate the other way to mmal ++ ++ switch (cur_rot) ++ { ++ case 0: ++ case RR_Rotate_0: ++ trans = MMAL_DISPLAY_ROT0; ++ break; ++ case RR_Rotate_90: ++ trans = MMAL_DISPLAY_ROT270; ++ break; ++ case RR_Rotate_180: ++ trans = MMAL_DISPLAY_ROT180; ++ break; ++ case RR_Rotate_270: ++ trans = MMAL_DISPLAY_ROT90; ++ break; ++ case RR_Reflect_X: ++ trans = MMAL_DISPLAY_MIRROR_ROT0; ++ break; ++ case RR_Reflect_Y: ++ trans = MMAL_DISPLAY_MIRROR_ROT180; ++ break; ++ default: ++ msg_Info(vd, "Unexpected X rotation value: %#x", cur_rot); ++ trans = MMAL_DISPLAY_ROT0; ++ break; } ++ ++ return trans; ++} ++#else ++static MMAL_DISPLAYTRANSFORM_T get_xrandr_rotation(vout_display_t * const vd) ++{ ++ VLC_UNUSED(vd); ++ return MMAL_DISPLAY_ROT0; ++} ++#endif ++ ++static MMAL_RECT_T str_to_rect(const char * s) ++{ ++ MMAL_RECT_T rect = {0}; ++ rect.width = strtoul(s, (char**)&s, 0); ++ if (*s == '\0') ++ return rect; ++ if (*s++ != 'x') ++ goto fail; ++ rect.height = strtoul(s, (char**)&s, 0); ++ if (*s == '\0') ++ return rect; ++ if (*s++ != '+') ++ goto fail; ++ rect.x = strtoul(s, (char**)&s, 0); ++ if (*s == '\0') ++ return rect; ++ if (*s++ != '+') ++ goto fail; ++ rect.y = strtoul(s, (char**)&s, 0); ++ if (*s != '\0') ++ goto fail; ++ return rect; ++ ++fail: ++ return (MMAL_RECT_T){0,0,0,0}; } ++ ++static int OpenMmalVout(vlc_object_t *object) ++{ ++ vout_display_t *vd = (vout_display_t *)object; ++ vout_display_sys_t *sys; ++ MMAL_STATUS_T status; ++ int ret = VLC_EGENERIC; ++ // At the moment all copy is via I420 ++ const bool needs_copy = !hw_mmal_chroma_is_mmal(vd->fmt.i_chroma); ++ const MMAL_FOURCC_T enc_in = needs_copy ? MMAL_ENCODING_I420 : ++ vout_vlc_to_mmal_pic_fourcc(vd->fmt.i_chroma); ++ ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s: o:%d", __func__, (int)vd->fmt.orientation); ++#endif ++ ++ if (bcm_host_is_kms_active()) { ++ msg_Dbg(vd, "KMS active - mmal vout disabled"); ++ return VLC_EGENERIC; ++ } ++ ++ get_xrandr_rotation(vd); ++ ++ sys = calloc(1, sizeof(struct vout_display_sys_t)); ++ if (!sys) ++ return VLC_ENOMEM; ++ vd->sys = sys; ++ ++ vlc_mutex_init(&sys->manage_mutex); ++ ++ if ((sys->init_type = cma_vcsm_init()) == VCSM_INIT_NONE) ++ { ++ msg_Err(vd, "VCSM init fail"); ++ goto fail; ++ } ++ ++ vc_tv_register_callback(tvservice_cb, vd); ++ ++ sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME); ++ sys->transparent = var_InheritBool(vd, MMAL_VOUT_TRANSPARENT_NAME); ++ ++ { ++ const char *display_name = var_InheritString(vd, MMAL_DISPLAY_NAME); ++ int qt_num = var_InheritInteger(vd, "qt-fullscreen-screennumber" ); ++ int display_id = find_display_num(display_name); ++// sys->display_id = display_id < 0 ? vc_tv_get_default_display_id() : display_id; ++ sys->display_id = display_id >= 0 ? display_id : ++ qt_num == 1 ? DISPMANX_ID_HDMI1 : DISPMANX_ID_HDMI; ++ if (display_id < -1) ++ msg_Warn(vd, "Unknown display device: '%s'", display_name); ++ else ++ msg_Dbg(vd, "Display device: %s, qt=%d id=%d display=%d", display_name, ++ qt_num, display_id, sys->display_id); ++ } ++ ++ { ++ const char *window_str = var_InheritString(vd, MMAL_VOUT_WINDOW_NAME); ++ sys->req_win = str_to_rect(window_str); ++ if (sys->req_win.width != 0) ++ msg_Dbg(vd, "Window: %dx%d @ %d,%d", ++ sys->req_win.width, sys->req_win.height, ++ sys->req_win.x, sys->req_win.y); ++ } ++ ++ { ++ const char *transform_name = var_InheritString(vd, MMAL_VOUT_TRANSFORM_NAME); ++ int transform_num = find_transform_num(transform_name); ++ sys->display_transform = transform_num < 0 ? ++ get_xrandr_rotation(vd) : ++ (MMAL_DISPLAYTRANSFORM_T)transform_num; ++ ++ if (transform_num < -1) ++ msg_Warn(vd, "Unknown vout transform: '%s'", transform_name); ++ else ++ msg_Dbg(vd, "Display transform: %s, mmal_display_transform=%d", ++ transform_name, (int)sys->display_transform); ++ ++ sys->video_transform = combine_transform( ++ vlc_to_mmal_transform(vd->fmt.orientation), sys->display_transform); ++ sys->dest_transform = transform_inverse(sys->display_transform); ++ } ++ ++ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)", ++ MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ ++ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd; ++ status = mmal_port_enable(sys->component->control, vd_control_port_cb); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)", ++ sys->component->control->name, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ ++ sys->input = sys->component->input[0]; ++ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd; ++ ++ sys->input->format->encoding = enc_in; ++ sys->input->format->encoding_variant = 0; ++ sys->i_planes = 1; ++ ++ display_set_format(vd, sys->input->format, want_isp(vd)); ++ ++ status = port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, true); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", ++ sys->input->name, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ ++ status = mmal_port_format_commit(sys->input); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)", ++ sys->input->name, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ ++ sys->input->buffer_size = sys->input->buffer_size_recommended; ++ ++ if (!needs_copy) { ++ sys->input->buffer_num = 30; ++ } ++ else { ++ sys->input->buffer_num = 2; ++ if ((sys->copy_pool = mmal_port_pool_create(sys->input, 2, sys->input->buffer_size)) == NULL) ++ { ++ msg_Err(vd, "Cannot create copy pool"); ++ goto fail; ++ } ++ } ++ ++ set_display_windows(vd, sys); ++ ++ configure_display(vd, vd->cfg, &vd->source); ++ ++ status = mmal_port_enable(sys->input, vd_input_port_cb); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)", ++ sys->input->name, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ ++ status = mmal_component_enable(sys->component); ++ if (status != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)", ++ sys->component->name, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ ++ if ((sys->pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) ++ { ++ msg_Err(vd, "Failed to create input pool"); ++ goto fail; ++ } ++ ++ for (unsigned int i = 0; i != SUBS_MAX; ++i) { ++ vout_subpic_t * const sub = sys->subs + i; ++ if ((status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sub->component)) != MMAL_SUCCESS) ++ { ++ msg_Dbg(vd, "Failed to create subpic component %d", i); ++ goto fail; ++ } ++ sub->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd; ++ if ((status = mmal_port_enable(sub->component->control, vd_control_port_cb)) != MMAL_SUCCESS) { ++ msg_Err(vd, "Failed to enable control port %s on sub %d (status=%"PRIx32" %s)", ++ sys->component->control->name, i, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ if ((status = hw_mmal_subpic_open(VLC_OBJECT(vd), &sub->sub, sub->component->input[0], ++ sys->display_id, sys->layer + i + 1)) != MMAL_SUCCESS) { ++ msg_Dbg(vd, "Failed to open subpic %d", i); ++ goto fail; ++ } ++ if ((status = mmal_component_enable(sub->component)) != MMAL_SUCCESS) ++ { ++ msg_Dbg(vd, "Failed to enable subpic component %d", i); ++ goto fail; ++ } ++ } ++ ++ // If we can't deal with it directly ask for I420 ++ vd->fmt.i_chroma = req_chroma(vd); ++ ++ vd->info = (vout_display_info_t){ ++ .is_slow = false, ++ .has_double_click = false, ++ .needs_hide_mouse = false, ++ .has_pictures_invalid = true, ++ .subpicture_chromas = hw_mmal_vzc_subpicture_chromas ++ }; ++ ++ vd->pool = vd_pool; ++ vd->prepare = vd_prepare; ++ vd->display = vd_display; ++ vd->control = vd_control; ++ ++ ++ msg_Dbg(vd, ">>> %s: ok", __func__); ++ return VLC_SUCCESS; ++ ++fail: ++ CloseMmalVout(object); ++ ++ msg_Dbg(vd, ">>> %s: rv=%d", __func__, ret); ++ ++ return ret == VLC_SUCCESS ? VLC_EGENERIC : ret; ++} ++ ++vlc_module_begin() ++ ++ add_submodule() ++ ++ set_shortname(N_("MMAL vout")) ++ set_description(N_("MMAL-based vout plugin for Raspberry Pi")) ++ set_capability("vout display", 16) // 1 point better than ASCII art ++ add_shortcut("mmal_vout") ++ set_category( CAT_VIDEO ) ++ set_subcategory( SUBCAT_VIDEO_VOUT ) ++ ++ add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false) ++ add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT, ++ MMAL_ADJUST_REFRESHRATE_LONGTEXT, false) ++ add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT, ++ MMAL_NATIVE_INTERLACE_LONGTEXT, false) ++ add_string(MMAL_DISPLAY_NAME, "auto", MMAL_DISPLAY_TEXT, ++ MMAL_DISPLAY_LONGTEXT, false) ++ add_string(MMAL_VOUT_TRANSFORM_NAME, "auto", MMAL_VOUT_TRANSFORM_TEXT, ++ MMAL_VOUT_TRANSFORM_LONGTEXT, false) ++ add_string(MMAL_VOUT_WINDOW_NAME, "fullscreen", MMAL_VOUT_WINDOW_TEXT, ++ MMAL_VOUT_WINDOW_LONGTEXT, false) ++ add_bool(MMAL_VOUT_TRANSPARENT_NAME, false, MMAL_VOUT_TRANSPARENT_TEXT, ++ MMAL_VOUT_TRANSPARENT_LONGTEXT, false) ++ set_callbacks(OpenMmalVout, CloseMmalVout) ++ ++vlc_module_end() ++ ++ +--- /dev/null ++++ b/modules/hw/mmal/xsplitter.c +@@ -0,0 +1,662 @@ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define TRACE_ALL 0 ++ ++// If set will reduce requested size on X ++// A good idea if we have h/w scaler before X ++// as it limits the work needed by GL but a disaster if we don't ++#define LIMIT_X_PELS 0 ++ ++#define VOUT_DISPLAY_CHANGE_MMAL_BASE 1024 ++#define VOUT_DISPLAY_CHANGE_MMAL_HIDE (VOUT_DISPLAY_CHANGE_MMAL_BASE + 0) ++ ++struct mmal_x11_sys_s; ++ ++typedef struct display_desc_s ++{ ++ vout_display_t * vout; ++ unsigned int max_pels; ++ void (* on_swap_away)(vout_display_t *const vd, struct mmal_x11_sys_s *const sys, struct display_desc_s * const x_desc); ++} display_desc_t; ++ ++typedef struct mmal_x11_sys_s ++{ ++ bool drm_fail; // We tried DRM but it didn't work ++ display_desc_t * cur_desc; ++ display_desc_t mmal_desc; ++ display_desc_t x_desc; ++ display_desc_t drm_desc; ++ uint32_t changed; ++ vlc_fourcc_t subpicture_chromas[16]; ++} mmal_x11_sys_t; ++ ++#define MAX_GL_PELS (1920*1080) ++#define MAX_MMAL_PELS (4096*4096) // Should never be hit ++#define MAX_DRM_PELS (4096*4096) // Should never be hit ++ ++static inline char drmu_log_safechar(int c) ++{ ++ return (c < ' ' || c >=0x7f) ? '?' : c; ++} ++ ++static inline const char * str_fourcc(char buf[5], uint32_t fcc) ++{ ++ if (fcc == 0) ++ return "----"; ++ buf[0] = drmu_log_safechar((fcc >> 0) & 0xff); ++ buf[1] = drmu_log_safechar((fcc >> 8) & 0xff); ++ buf[2] = drmu_log_safechar((fcc >> 16) & 0xff); ++ buf[3] = drmu_log_safechar((fcc >> 24) & 0xff); ++ buf[4] = 0; ++ return buf; ++} ++ ++#if 0 ++// Gen prog for the following table ++// Not done inline in case we end up pulling in FP libs we don't want ++#include ++#include ++ ++int main(int argc, char *argv[]) ++{ ++ unsigned int i; ++ for (i = 0; i != 64; ++i) ++ { ++ printf(" [%2u]=%5u,", i, (unsigned int)(0.5 + (1/sqrt((i + 5)/4.0) * 65536.0))); ++ if (i % 4 == 3) ++ printf("\n"); ++ } ++} ++#endif ++ ++#if LIMIT_X_PELS ++static const uint16_t sqrt_tab[64] = { ++ [ 0]=58617, [ 1]=53510, [ 2]=49541, [ 3]=46341, ++ [ 4]=43691, [ 5]=41449, [ 6]=39520, [ 7]=37837, ++ [ 8]=36353, [ 9]=35030, [10]=33843, [11]=32768, ++ [12]=31790, [13]=30894, [14]=30070, [15]=29309, ++ [16]=28602, [17]=27945, [18]=27330, [19]=26755, ++ [20]=26214, [21]=25705, [22]=25225, [23]=24770, ++ [24]=24339, [25]=23930, [26]=23541, [27]=23170, ++ [28]=22817, [29]=22479, [30]=22155, [31]=21845, ++ [32]=21548, [33]=21263, [34]=20988, [35]=20724, ++ [36]=20470, [37]=20225, [38]=19988, [39]=19760, ++ [40]=19539, [41]=19326, [42]=19119, [43]=18919, ++ [44]=18725, [45]=18536, [46]=18354, [47]=18176, ++ [48]=18004, [49]=17837, [50]=17674, [51]=17515, ++ [52]=17361, [53]=17211, [54]=17064, [55]=16921, ++ [56]=16782, [57]=16646, [58]=16514, [59]=16384, ++ [60]=16257, [61]=16134, [62]=16013, [63]=15895 ++}; ++#define SQRT_MAX (sizeof(sqrt_tab)/sizeof(sqrt_tab[0]) - 1) ++#endif ++ ++static bool cpy_fmt_limit_size(const display_desc_t * const dd, ++ video_format_t * const dst, ++ const video_format_t * const src) ++{ ++#if !LIMIT_X_PELS ++ VLC_UNUSED(dd); ++ *dst = *src; ++ return false; ++#else ++ const unsigned int src_pel = src->i_visible_width * src->i_visible_height; ++ ++ *dst = *src; ++ ++ if (src_pel <= dd->max_pels) ++ return false; ++ ++ // scaling factor sqrt(max_pel/cur_pel) ++ // sqrt done by lookup & 16 bit fixed-point maths - not exactly accurate but ++ // easily good enough & avoids floating point (which may be slow) ++ // src_pel > max_pel so n >= 0 ++ // Rounding should be such that exact sqrts work and everything else rounds ++ // down ++ unsigned int n = ((src_pel * 4 - 1) / dd->max_pels) - 4; ++ unsigned int scale = sqrt_tab[n >= SQRT_MAX ? SQRT_MAX : n]; ++ ++ // Rescale width - rounding up to 16 ++ unsigned int width = ((src->i_visible_width * scale + (16 << 16) - 1) >> 16) & ~15; ++ // Rescale height based on new width ++ unsigned int height = (src->i_visible_height * width + src->i_visible_width/2) / src->i_visible_width; ++ ++// fprintf(stderr, "%dx%d -> %dx%d\n", src->i_visible_width, src->i_visible_height, width, height); ++ ++ dst->i_width = width; ++ dst->i_visible_width = width; ++ dst->i_height = height; ++ dst->i_visible_height = height; ++ return true; ++#endif ++} ++ ++static void unload_display_module(vout_display_t * const x_vout) ++{ ++ if (x_vout != NULL) { ++ if (x_vout->module != NULL) { ++ module_unneed(x_vout, x_vout->module); ++ } ++ vlc_object_release(x_vout); ++ } ++} ++ ++static void stop_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys) ++{ ++ VLC_UNUSED(vd); ++ ++ unload_display_module(sys->drm_desc.vout); ++ sys->drm_desc.vout = NULL; ++} ++ ++static void CloseMmalX11(vlc_object_t *object) ++{ ++ vout_display_t * const vd = (vout_display_t *)object; ++ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; ++ ++ msg_Dbg(vd, "<<< %s", __func__); ++ ++ if (sys == NULL) ++ return; ++ ++ unload_display_module(sys->x_desc.vout); ++ ++ unload_display_module(sys->mmal_desc.vout); ++ ++ stop_drm(vd, sys); ++ ++ free(sys); ++ ++ msg_Dbg(vd, ">>> %s", __func__); ++} ++ ++static void mmal_x11_event(vout_display_t * x_vd, int cmd, va_list args) ++{ ++ vout_display_t * const vd = x_vd->owner.sys; ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s (cmd=%d)", __func__, cmd); ++#endif ++ ++ // Do not fall into the display assert if Invalid not supported ++ if (cmd == VOUT_DISPLAY_EVENT_PICTURES_INVALID && ++ !vd->info.has_pictures_invalid) ++ return; ++ ++ vd->owner.event(vd, cmd, args); ++} ++ ++static vout_window_t * mmal_x11_window_new(vout_display_t * x_vd, unsigned type) ++{ ++ vout_display_t * const vd = x_vd->owner.sys; ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s (type=%d)", __func__, type); ++#endif ++ return vd->owner.window_new(vd, type); ++} ++ ++static void mmal_x11_window_del(vout_display_t * x_vd, vout_window_t * win) ++{ ++ vout_display_t * const vd = x_vd->owner.sys; ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s", __func__); ++#endif ++ vd->owner.window_del(vd, win); ++} ++ ++ ++static int load_display_module(vout_display_t * const vd, ++ display_desc_t * const dd, ++ const char * const cap, ++ const char * const module_name) ++{ ++ vout_display_t * const x_vout = vlc_object_create(vd, sizeof(*x_vout)); ++ ++ dd->vout = NULL; ++ if (!x_vout) ++ return -1; ++ ++ x_vout->owner.sys = vd; ++ x_vout->owner.event = mmal_x11_event; ++ x_vout->owner.window_new = mmal_x11_window_new; ++ x_vout->owner.window_del = mmal_x11_window_del; ++ ++ x_vout->cfg = vd->cfg; ++ x_vout->info = vd->info; ++ cpy_fmt_limit_size(dd, &x_vout->source, &vd->source); ++ cpy_fmt_limit_size(dd, &x_vout->fmt, &vd->fmt); ++ ++ if ((x_vout->module = module_need(x_vout, cap, module_name, true)) == NULL) ++ { ++ msg_Dbg(vd, "Failed to open Xsplitter:%s module", module_name); ++ goto fail; ++ } ++ ++ msg_Dbg(vd, "R/G/B: %08x/%08x/%08x", x_vout->fmt.i_rmask, x_vout->fmt.i_gmask, x_vout->fmt.i_bmask); ++ ++ dd->vout = x_vout; ++ return 0; ++ ++fail: ++ vlc_object_release(x_vout); ++ return -1; ++} ++ ++static int start_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys) ++{ ++ if (sys->drm_desc.vout != NULL) ++ return 0; ++ msg_Info(vd, "Try drm"); ++ return load_display_module(vd, &sys->drm_desc, "vout display", "drm_vout"); ++} ++ ++ ++/* Return a pointer over the current picture_pool_t* (mandatory). ++ * ++ * For performance reasons, it is best to provide at least count ++ * pictures but it is not mandatory. ++ * You can return NULL when you cannot/do not want to allocate ++ * pictures. ++ * The vout display module keeps the ownership of the pool and can ++ * destroy it only when closing or on invalid pictures control. ++ */ ++static picture_pool_t * mmal_x11_pool(vout_display_t * vd, unsigned count) ++{ ++ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; ++ vout_display_t * const x_vd = sys->cur_desc->vout; ++#if TRACE_ALL ++ char buf0[5]; ++ char buf1[5]; ++ msg_Dbg(vd, "<<< %s (count=%d) %s:%dx%d->%s:%dx%d", __func__, count, ++ str_fourcc(buf0, vd->fmt.i_chroma), ++ vd->fmt.i_width, vd->fmt.i_height, ++ str_fourcc(buf1, x_vd->fmt.i_chroma), ++ x_vd->fmt.i_width, x_vd->fmt.i_height); ++#endif ++ picture_pool_t * pool = x_vd->pool(x_vd, count); ++#if TRACE_ALL ++ msg_Dbg(vd, ">>> %s: %p", __func__, pool); ++#endif ++ return pool; ++} ++ ++/* Prepare a picture and an optional subpicture for display (optional). ++ * ++ * It is called before the next pf_display call to provide as much ++ * time as possible to prepare the given picture and the subpicture ++ * for display. ++ * You are guaranted that pf_display will always be called and using ++ * the exact same picture_t and subpicture_t. ++ * You cannot change the pixel content of the picture_t or of the ++ * subpicture_t. ++ */ ++static void mmal_x11_prepare(vout_display_t * vd, picture_t * pic, subpicture_t * sub) ++{ ++ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; ++ vout_display_t * const x_vd = sys->cur_desc->vout; ++#if TRACE_ALL ++ char buf0[5]; ++ msg_Dbg(vd, "<<< %s: fmt=%s, %dx%d", __func__, str_fourcc(buf0, pic->format.i_chroma), pic->format.i_width, pic->format.i_height); ++#endif ++ if (x_vd->prepare) ++ x_vd->prepare(x_vd, pic, sub); ++} ++ ++/* Display a picture and an optional subpicture (mandatory). ++ * ++ * The picture and the optional subpicture must be displayed as soon as ++ * possible. ++ * You cannot change the pixel content of the picture_t or of the ++ * subpicture_t. ++ * ++ * This function gives away the ownership of the picture and of the ++ * subpicture, so you must release them as soon as possible. ++ */ ++static void mmal_x11_display(vout_display_t * vd, picture_t * pic, subpicture_t * sub) ++{ ++ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; ++ vout_display_t * const x_vd = sys->cur_desc->vout; ++ ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s: fmt: %dx%d/%dx%d, pic:%dx%d, pts=%lld", __func__, vd->fmt.i_width, vd->fmt.i_height, x_vd->fmt.i_width, x_vd->fmt.i_height, pic->format.i_width, pic->format.i_height, (long long)pic->date); ++#endif ++ ++ if (x_vd->fmt.i_chroma != pic->format.i_chroma || ++ x_vd->fmt.i_width != pic->format.i_width || ++ x_vd->fmt.i_height != pic->format.i_height) ++ { ++ msg_Dbg(vd, "%s: Picture dropped", __func__); ++ picture_Release(pic); ++ if (sub != NULL) ++ subpicture_Delete(sub); ++ return; ++ } ++ ++ x_vd->display(x_vd, pic, sub); ++} ++ ++ ++static int vout_display_Control(const display_desc_t * const dd, int query, ...) ++{ ++ va_list args; ++ int result; ++ ++ va_start(args, query); ++ result = dd->vout->control(dd->vout, query, args); ++ va_end(args); ++ ++ return result; ++} ++ ++static display_desc_t* wanted_display(vout_display_t *const vd, mmal_x11_sys_t *const sys) ++{ ++ if (sys->x_desc.vout != NULL && !var_InheritBool(vd, "fullscreen")) ++ return &sys->x_desc; ++ ++ // Full screen or no X ++ ++ if (sys->mmal_desc.vout != NULL) ++ return &sys->mmal_desc; ++ ++ if (!sys->drm_fail) { ++ if (start_drm(vd, sys) == 0) ++ return &sys->drm_desc; ++ msg_Info(vd, "Drm no go"); ++ sys->drm_fail = true; // Never try again ++ } ++ ++ return sys->x_desc.vout != NULL ? &sys->x_desc : NULL; ++} ++ ++static inline int ++up_rv(const int a, const int b) ++{ ++ return a != 0 ? a : b; ++} ++ ++static int ++reset_pictures(vout_display_t * const vd, const display_desc_t * const desc) ++{ ++ int rv = 0; ++ VLC_UNUSED(vd); ++ if (desc->vout) ++ { ++ // If the display doesn't have has_pictures_invalid then it doesn't ++ // expect RESET_PICTURES ++ if (desc->vout->info.has_pictures_invalid) ++ vout_display_Control(desc, VOUT_DISPLAY_RESET_PICTURES); ++ } ++ return rv; ++} ++ ++static int ++replay_controls(vout_display_t * const vd, const display_desc_t * const desc, const int32_t changed) ++{ ++ if ((changed & (1 << VOUT_DISPLAY_CHANGE_DISPLAY_FILLED)) != 0) ++ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, vd->cfg); ++ if ((changed & (1 << VOUT_DISPLAY_CHANGE_ZOOM)) != 0) ++ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_ZOOM, vd->cfg); ++ if ((changed & ((1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP) | ++ (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT))) != 0) ++ cpy_fmt_limit_size(desc, &desc->vout->source, &vd->source); ++ if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT)) != 0) ++ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT); ++ if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP)) != 0) ++ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_SOURCE_CROP); ++ if ((changed & (1 << VOUT_DISPLAY_CHANGE_VIEWPOINT)) != 0) ++ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_VIEWPOINT, vd->cfg); ++ return 0; ++} ++ ++static void swap_away_null(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc) ++{ ++ VLC_UNUSED(vd); ++ VLC_UNUSED(sys); ++ VLC_UNUSED(x_desc); ++} ++ ++static void swap_away_mmal(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc) ++{ ++ VLC_UNUSED(vd); ++ VLC_UNUSED(sys); ++ vout_display_Control(x_desc, VOUT_DISPLAY_CHANGE_MMAL_HIDE); ++} ++ ++static void swap_away_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc) ++{ ++ VLC_UNUSED(x_desc); ++ stop_drm(vd, sys); ++} ++ ++ ++/* Control on the module (mandatory) */ ++static int mmal_x11_control(vout_display_t * vd, int ctl, va_list va) ++{ ++ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; ++ display_desc_t *x_desc = sys->cur_desc; ++ int rv; ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s[%s] (ctl=%d)", __func__, x_desc->vout->obj.object_type, ctl); ++#endif ++ // Remember what we've told this vd - unwanted ctls ignored on replay ++ if (ctl >= 0 && ctl <= 31) ++ sys->changed |= (1 << ctl); ++ ++ switch (ctl) { ++ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: ++ { ++ const vout_display_cfg_t * const cfg = va_arg(va, const vout_display_cfg_t *); ++ display_desc_t * const new_desc = wanted_display(vd, sys); ++ const bool swap_vout = (new_desc != sys->cur_desc); ++ ++ msg_Dbg(vd, "Change size: %d, %d: fs=%d", ++ cfg->display.width, cfg->display.height, var_InheritBool(vd, "fullscreen")); ++ ++ // Repeat any control calls that we sent to the previous vd ++ if (swap_vout && sys->changed != 0) { ++ const uint32_t changed = sys->changed; ++ sys->changed = 0; ++ replay_controls(vd, new_desc, changed); ++ } ++ ++ if (swap_vout) { ++ vout_display_SendEventPicturesInvalid(vd); ++ x_desc->on_swap_away(vd, sys, x_desc); ++ } ++ ++ rv = vout_display_Control(new_desc, ctl, cfg); ++ if (rv == VLC_SUCCESS) { ++ vd->fmt = new_desc->vout->fmt; ++ sys->cur_desc = new_desc; ++ // Strictly this is illegal but subpic chromas are consulted ++ // on every render so it is in fact safe. ++ vd->info.subpicture_chromas = sys->cur_desc->vout->info.subpicture_chromas; ++ } ++ ++ break; ++ } ++ ++ case VOUT_DISPLAY_RESET_PICTURES: ++ { ++ char dbuf0[5], dbuf1[5], dbuf2[5]; ++ msg_Dbg(vd, "<<< %s: Pic reset: fmt: %s,%dx%d<-%s,%dx%d, source: %s,%dx%d/%dx%d", __func__, ++ str_fourcc(dbuf0, vd->fmt.i_chroma), vd->fmt.i_width, vd->fmt.i_height, ++ str_fourcc(dbuf1, x_desc->vout->fmt.i_chroma), x_desc->vout->fmt.i_width, x_desc->vout->fmt.i_height, ++ str_fourcc(dbuf2, vd->source.i_chroma), vd->source.i_width, vd->source.i_height, x_desc->vout->source.i_width, ++ x_desc->vout->source.i_height); ++ } ++ rv = reset_pictures(vd, &sys->x_desc); ++ rv = up_rv(rv, reset_pictures(vd, &sys->mmal_desc)); ++ rv = up_rv(rv, reset_pictures(vd, &sys->drm_desc)); ++ ++ vd->fmt = x_desc->vout->fmt; ++ break; ++ ++ case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: ++ case VOUT_DISPLAY_CHANGE_SOURCE_CROP: ++ cpy_fmt_limit_size(x_desc, &x_desc->vout->source, &vd->source); ++ ++ /* FALLTHRU */ ++ default: ++ rv = x_desc->vout->control(x_desc->vout, ctl, va); ++// vd->fmt = x_vd->fmt; ++ break; ++ } ++#if TRACE_ALL ++ msg_Dbg(vd, ">>> %s (rv=%d)", __func__, rv); ++#endif ++ return rv; ++} ++ ++#define DO_MANAGE 0 ++ ++#if DO_MANAGE ++/* Manage pending event (optional) */ ++static void mmal_x11_manage(vout_display_t * vd) ++{ ++ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; ++ vout_display_t * const x_vd = sys->cur_desc->vout; ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s", __func__); ++#endif ++ x_vd->manage(x_vd); ++} ++#endif ++ ++static int OpenMmalX11(vlc_object_t *object) ++{ ++ vout_display_t * const vd = (vout_display_t *)object; ++ mmal_x11_sys_t * const sys = calloc(1, sizeof(*sys)); ++ int ret = VLC_SUCCESS; ++ ++ if (sys == NULL) { ++ return VLC_EGENERIC; ++ } ++ vd->sys = (vout_display_sys_t *)sys; ++ ++ vd->info = (vout_display_info_t){ ++ .is_slow = false, ++ .has_double_click = false, ++ .needs_hide_mouse = false, ++ .has_pictures_invalid = true, ++ .subpicture_chromas = NULL ++ }; ++ ++ { ++ char dbuf0[5]; ++ msg_Dbg(vd, ">>> %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__, ++ str_fourcc(dbuf0, vd->fmt.i_chroma), ++ vd->fmt.i_width, vd->fmt.i_height, ++ vd->fmt.i_x_offset, vd->fmt.i_y_offset, ++ vd->fmt.i_visible_width, vd->fmt.i_visible_height, ++ vd->fmt.i_sar_num, vd->fmt.i_sar_den); ++ } ++ ++ sys->x_desc.max_pels = MAX_GL_PELS; ++ sys->x_desc.on_swap_away = swap_away_null; ++ sys->mmal_desc.max_pels = MAX_MMAL_PELS; ++ sys->mmal_desc.on_swap_away = swap_away_mmal; ++ sys->drm_desc.max_pels = MAX_DRM_PELS; ++ sys->drm_desc.on_swap_away = swap_away_drm; ++ ++ if (load_display_module(vd, &sys->x_desc, "vout display", "opengles2") == 0) ++ { ++ msg_Dbg(vd, "Opengles2 output found"); ++ } ++ else ++ { ++ sys->x_desc.max_pels = MAX_MMAL_PELS; ++ if (load_display_module(vd, &sys->x_desc, "vout display", "xcb_x11") == 0) ++ msg_Dbg(vd, "X11 XCB output found"); ++ } ++ ++ if ((load_display_module(vd, &sys->mmal_desc, "vout display", "mmal_vout")) == 0) ++ msg_Dbg(vd, "MMAL output found"); ++ ++ vd->pool = mmal_x11_pool; ++ vd->prepare = mmal_x11_prepare; ++ vd->display = mmal_x11_display; ++ vd->control = mmal_x11_control; ++#if DO_MANAGE ++ vd->manage = mmal_x11_manage; ++#endif ++ ++ sys->cur_desc = wanted_display(vd, sys); ++ if (sys->cur_desc == NULL) { ++ char dbuf0[5], dbuf1[5]; ++ msg_Warn(vd, "No valid output found for vout (%s/%s)", str_fourcc(dbuf0, vd->fmt.i_chroma), str_fourcc(dbuf1, vd->source.i_chroma)); ++ goto fail; ++ } ++ ++ if (sys->mmal_desc.vout == NULL || sys->x_desc.vout == NULL) { ++ vd->info = sys->cur_desc->vout->info; ++ vd->info.has_pictures_invalid = true; // Should make this unwanted ++ } ++ else { ++ // We have both - construct a combination ++ vd->info = (vout_display_info_t){ ++ .is_slow = false, ++ .has_double_click = sys->mmal_desc.vout->info.has_double_click || sys->x_desc.vout->info.has_double_click, ++ .needs_hide_mouse = sys->mmal_desc.vout->info.needs_hide_mouse || sys->x_desc.vout->info.needs_hide_mouse, ++ .has_pictures_invalid = true, ++ }; ++ // Construct intersection of subpicture chromas ++ // sys calloced so no need to add the terminating zero ++ if (sys->mmal_desc.vout->info.subpicture_chromas != NULL && sys->x_desc.vout->info.subpicture_chromas != NULL) { ++ unsigned int n = 0; ++ // N^2 - fix if we ever care ++ for (const vlc_fourcc_t * p1 = sys->mmal_desc.vout->info.subpicture_chromas; *p1 != 0 && n != 15; ++p1) { ++ for (const vlc_fourcc_t * p2 = sys->x_desc.vout->info.subpicture_chromas; *p2 != 0; ++p2) { ++ if (*p1 == *p2) { ++ sys->subpicture_chromas[n++] = *p1; ++ break; ++ } ++ } ++ } ++ if (n != 0) ++ vd->info.subpicture_chromas = sys->subpicture_chromas; ++ } ++ } ++ vd->fmt = sys->cur_desc->vout->fmt; ++ ++#if TRACE_ALL ++ { ++ char dbuf0[5]; ++ msg_Dbg(vd, ">>> %s: (%s) %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__, ++ module_get_name(sys->cur_desc->vout->module, false), ++ str_fourcc(dbuf0, vd->fmt.i_chroma), ++ vd->fmt.i_width, vd->fmt.i_height, ++ vd->fmt.i_x_offset, vd->fmt.i_y_offset, ++ vd->fmt.i_visible_width, vd->fmt.i_visible_height, ++ vd->fmt.i_sar_num, vd->fmt.i_sar_den); ++ } ++#endif ++ return VLC_SUCCESS; ++ ++fail: ++ CloseMmalX11(VLC_OBJECT(vd)); ++ return ret == VLC_SUCCESS ? VLC_EGENERIC : ret; ++} ++ ++ ++ ++ ++vlc_module_begin() ++ set_shortname(N_("MMAL x11 splitter")) ++ set_description(N_("MMAL x11 splitter for Raspberry Pi")) ++ set_capability("vout display", 300) // Between GLES & GL ++ add_shortcut("mmal_x11") ++ set_category( CAT_VIDEO ) ++ set_subcategory( SUBCAT_VIDEO_VOUT ) ++ set_callbacks(OpenMmalX11, CloseMmalX11) ++vlc_module_end() ++ +--- a/modules/video_output/Makefile.am ++++ b/modules/video_output/Makefile.am +@@ -182,6 +182,28 @@ vout_LTLIBRARIES += libglx_plugin.la + endif + endif + ++### DRM ### ++ ++libdrm_vout_plugin_la_SOURCES = video_output/drmu/drm_vout.c \ ++ video_output/drmu/drmu_vlc.c video_output/drmu/drmu_vlc.h \ ++ video_output/drmu/drmu_fmts.c video_output/drmu/drmu_fmts.h \ ++ video_output/drmu/drmu_chroma.h \ ++ video_output/drmu/drmu_log.h video_output/drmu/drmu.c \ ++ video_output/drmu/drmu_xlease.c video_output/drmu/drmu_atomic.c \ ++ video_output/drmu/drmu_util.c video_output/drmu/drmu_util.h \ ++ video_output/drmu/drmu_output.c video_output/drmu/drmu_output.h \ ++ video_output/drmu/drmu.h\ ++ video_output/drmu/pollqueue.c video_output/drmu/pollqueue.h ++libdrm_vout_plugin_la_CFLAGS = $(AM_CFLAGS) -pthread -I/usr/include/libdrm ++libdrm_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -pthread ++libdrm_vout_plugin_la_LIBADD = -ldrm -lxcb-randr -lxcb ++if HAVE_MMAL ++libdrm_vout_plugin_la_CFLAGS += -DHAS_ZC_CMA=1 ++libdrm_vout_plugin_la_SOURCES += hw/mmal/mmal_cma_pic.h ++endif ++if HAVE_DRM ++vout_LTLIBRARIES += libdrm_vout_plugin.la ++endif --static void show_background(vout_display_t *vd, bool enable) -+static void CloseMmalVout(vlc_object_t *object) - { -- vout_display_sys_t *sys = vd->sys; -- uint32_t image_ptr, color = 0xFF000000; -- VC_RECT_T dst_rect, src_rect; -- DISPMANX_UPDATE_HANDLE_T update; -- -- if (enable && !sys->bkg_element) { -- sys->bkg_resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32, 1, 1, -- &image_ptr); -- vc_dispmanx_rect_set(&dst_rect, 0, 0, 1, 1); -- vc_dispmanx_resource_write_data(sys->bkg_resource, VC_IMAGE_RGBA32, -- sizeof(color), &color, &dst_rect); -- vc_dispmanx_rect_set(&src_rect, 0, 0, 1 << 16, 1 << 16); -- vc_dispmanx_rect_set(&dst_rect, 0, 0, 0, 0); -- update = vc_dispmanx_update_start(0); -- sys->bkg_element = vc_dispmanx_element_add(update, sys->dmx_handle, -- sys->layer - 1, &dst_rect, sys->bkg_resource, &src_rect, -- DISPMANX_PROTECTION_NONE, NULL, NULL, VC_IMAGE_ROT0); -- vc_dispmanx_update_submit_sync(update); -- } else if (!enable && sys->bkg_element) { -- update = vc_dispmanx_update_start(0); -- vc_dispmanx_element_remove(update, sys->bkg_element); -- vc_dispmanx_resource_delete(sys->bkg_resource); -- vc_dispmanx_update_submit_sync(update); -- sys->bkg_element = DISPMANX_NO_HANDLE; -- sys->bkg_resource = DISPMANX_NO_HANDLE; + ### Wayland ### + libwl_shm_plugin_la_SOURCES = video_output/wayland/shm.c +--- /dev/null ++++ b/modules/video_output/drmu/drm_vout.c +@@ -0,0 +1,1040 @@ ++/***************************************************************************** ++ * drm_vout.c: DRM based output device ++ ***************************************************************************** ++ * Copyright © 2014 jusst technologies GmbH ++ * ++ * Authors: Dennis Hamester ++ * Julian Scheel ++ * John Cox ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++#include ++ ++#include "drmu.h" ++#include "drmu_log.h" ++#include "drmu_output.h" ++#include "drmu_util.h" ++#include "drmu_vlc.h" ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#define TRACE_ALL 0 ++ ++#define SUBPICS_MAX 4 ++ ++#define DRM_MODULE "vc4" ++ ++ ++#define DRM_VOUT_SOURCE_MODESET_NAME "drm-vout-source-modeset" ++#define DRM_VOUT_SOURCE_MODESET_TEXT N_("Attempt to match display to source") ++#define DRM_VOUT_SOURCE_MODESET_LONGTEXT N_("Attempt to match display resolution and refresh rate to source.\ ++ Defaults to the 'preferred' mode if no good enough match found. \ ++ If unset then resolution & refresh will not be set.") ++ ++#define DRM_VOUT_MODE_NAME "drm-vout-mode" ++#define DRM_VOUT_MODE_TEXT N_("Set this mode for display") ++ ++#define DRM_VOUT_MODE_LONGTEXT N_("arg: x@ Force mode to arg") ++ ++#define DRM_VOUT_NO_MODESET_NAME "drm-vout-no-modeset" ++#define DRM_VOUT_NO_MODESET_TEXT N_("Do not modeset") ++#define DRM_VOUT_NO_MODESET_LONGTEXT N_("Do no operation that would cause a modeset.\ ++ This overrides the operation of all other flags.") ++ ++#define DRM_VOUT_NO_MAX_BPC "drm-vout-no-max-bpc" ++#define DRM_VOUT_NO_MAX_BPC_TEXT N_("Do not set bpc on output") ++#define DRM_VOUT_NO_MAX_BPC_LONGTEXT N_("Do not try to switch from 8-bit RGB to 12-bit YCC on UHD frames.\ ++ 12 bit is dependant on kernel and display support so may not be availible") ++ ++#define DRM_VOUT_WINDOW_NAME "drm-vout-window" ++#define DRM_VOUT_WINDOW_TEXT N_("Display window for Rpi fullscreen") ++#define DRM_VOUT_WINDOW_LONGTEXT N_("Display window for Rpi fullscreen."\ ++"fullscreen|x++") ++ ++#define DRM_VOUT_DISPLAY_NAME "drm-vout-display" ++#define DRM_VOUT_DISPLAY_TEXT N_("Output device for Rpi fullscreen.") ++#define DRM_VOUT_DISPLAY_LONGTEXT N_("Output device for Rpi fullscreen. " \ ++"Valid values are HDMI-1,HDMI-2. By default if qt-fullscreen-screennumber " \ ++"is specified (or set by Fullscreen Output Device in Preferences) " \ ++"HDMI- will be used, otherwise HDMI-1.") ++ ++#define DRM_VOUT_MODULE_NAME "drm-vout-module" ++#define DRM_VOUT_MODULE_TEXT N_("DRM module to use") ++#define DRM_VOUT_MODULE_LONGTEXT N_("DRM module for Rpi fullscreen") ++ ++ ++typedef struct subpic_ent_s { ++ drmu_fb_t * fb; ++ drmu_rect_t pos; ++ drmu_rect_t space; // display space of pos ++ picture_t * pic; ++ unsigned int alpha; // out of 0xff * 0xff ++} subpic_ent_t; ++ ++typedef struct vout_display_sys_t { ++ drmu_env_t * du; ++ drmu_output_t * dout; ++ drmu_plane_t * dp; ++ drmu_pool_t * pic_pool; ++ drmu_pool_t * sub_fb_pool; ++ drmu_plane_t * subplanes[SUBPICS_MAX]; ++ subpic_ent_t subpics[SUBPICS_MAX]; ++ vlc_fourcc_t * subpic_chromas; ++ ++ drmu_atomic_t * display_set; ++ ++ vout_display_place_t req_win; ++ vout_display_place_t spu_rect; ++ vout_display_place_t dest_rect; ++ vout_display_place_t win_rect; ++ vout_display_place_t display_rect; ++ ++ video_transform_t display_transform; ++ video_transform_t video_transform; ++ video_transform_t dest_transform; ++ ++ uint32_t con_id; ++ int mode_id; ++ ++ picture_pool_t * vlc_pic_pool; ++} vout_display_sys_t; ++ ++static drmu_fb_t * ++copy_pic_to_fb(vout_display_t *vd, drmu_pool_t * const pool, picture_t * const src) ++{ ++ const uint32_t drm_fmt = drmu_format_vlc_to_drm(&src->format); ++ drmu_fb_t * fb; ++ int i; ++ ++ if (drm_fmt == 0) { ++ msg_Warn(vd, "Failed vlc->drm format for copy_pic: %s", drmu_log_fourcc(src->format.i_chroma)); ++ return NULL; ++ } ++ ++ fb = drmu_pool_fb_new_dumb(pool, src->format.i_width, src->format.i_height, drm_fmt); ++ if (fb == NULL) { ++ msg_Warn(vd, "Failed alloc for copy_pic: %dx%d", src->format.i_width, src->format.i_height); ++ return NULL; ++ } ++ ++ for (i = 0; i != src->i_planes; ++i) { ++ plane_t dst_plane; ++ dst_plane = drmu_fb_vlc_plane(fb, i); ++ plane_CopyPixels(&dst_plane, src->p + i); ++ } ++ ++ drmu_fb_vlc_pic_set_metadata(fb, src); ++ ++ return fb; ++} ++ ++ ++static vout_display_place_t str_to_rect(const char * s) ++{ ++ vout_display_place_t rect = {0}; ++ rect.width = strtoul(s, (char**)&s, 0); ++ if (*s == '\0') ++ return rect; ++ if (*s++ != 'x') ++ goto fail; ++ rect.height = strtoul(s, (char**)&s, 0); ++ if (*s == '\0') ++ return rect; ++ if (*s++ != '+') ++ goto fail; ++ rect.x = strtoul(s, (char**)&s, 0); ++ if (*s == '\0') ++ return rect; ++ if (*s++ != '+') ++ goto fail; ++ rect.y = strtoul(s, (char**)&s, 0); ++ if (*s != '\0') ++ goto fail; ++ return rect; ++ ++fail: ++ return (vout_display_place_t){0,0,0,0}; ++} ++ ++// MMAL headers comment these (getting 2 a bit wrong) but do not give ++// defines ++#define VXF_H_SHIFT 0 // Hflip ++#define VXF_V_SHIFT 1 // Vflip ++#define VXF_T_SHIFT 2 // Transpose ++#define VXF_H_BIT (1 << VXF_H_SHIFT) ++#define VXF_V_BIT (1 << VXF_V_SHIFT) ++#define VXF_T_BIT (1 << VXF_T_SHIFT) ++ ++static inline bool ++is_vxf_transpose(const video_transform_t t) ++{ ++ return ((unsigned int)t & VXF_T_BIT) != 0; ++} ++ ++static inline bool ++is_vxf_hflip(const video_transform_t t) ++{ ++ return ((unsigned int)t & VXF_H_BIT) != 0; ++} ++ ++static inline bool ++is_vxf_vflip(const video_transform_t t) ++{ ++ return ((unsigned int)t & VXF_V_BIT) != 0; ++} ++ ++static inline video_transform_t ++swap_vxf_hv(const video_transform_t x) ++{ ++ return (((x >> VXF_H_SHIFT) & 1) << VXF_V_SHIFT) | ++ (((x >> VXF_V_SHIFT) & 1) << VXF_H_SHIFT) | ++ (x & VXF_T_BIT); ++} ++ ++static inline video_transform_t ++vxf_inverse(const video_transform_t x) ++{ ++ return is_vxf_transpose(x) ? swap_vxf_hv(x) : x; ++} ++ ++// Transform generated by A then B ++// All ops are self inverse so can simply be XORed on their own ++// H & V flips after a transpose need to be swapped ++static inline video_transform_t ++combine_vxf(const video_transform_t a, const video_transform_t b) ++{ ++ return a ^ (is_vxf_transpose(a) ? swap_vxf_hv(b) : b); ++} ++ ++static inline vout_display_place_t ++vplace_transpose(const vout_display_place_t s) ++{ ++ return (vout_display_place_t){ ++ .x = s.y, ++ .y = s.x, ++ .width = s.height, ++ .height = s.width ++ }; ++} ++ ++// hflip s in c ++static inline vout_display_place_t vplace_hflip(const vout_display_place_t s, const vout_display_place_t c) ++{ ++ return (vout_display_place_t){ ++ .x = c.x + (c.x + c.width) - (s.x + s.width), ++ .y = s.y, ++ .width = s.width, ++ .height = s.height ++ }; ++} ++ ++// vflip s in c ++static inline vout_display_place_t vplace_vflip(const vout_display_place_t s, const vout_display_place_t c) ++{ ++ return (vout_display_place_t){ ++ .x = s.x, ++ .y = (c.y + c.height) - (s.y - c.y) - s.height, ++ .width = s.width, ++ .height = s.height ++ }; ++} ++ ++static vout_display_place_t ++place_out(const vout_display_cfg_t * cfg, ++ const video_format_t * fmt, ++ const vout_display_place_t r) ++{ ++ video_format_t tfmt; ++ vout_display_cfg_t tcfg; ++ vout_display_place_t place; ++ ++ // Fix SAR if unknown ++ if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) { ++ tfmt = *fmt; ++ tfmt.i_sar_den = 1; ++ tfmt.i_sar_num = 1; ++ fmt = &tfmt; ++ } ++ ++ // Override what VLC thinks might be going on with display size ++ // if we know better ++ if (r.width != 0 && r.height != 0) ++ { ++ tcfg = *cfg; ++ tcfg.display.width = r.width; ++ tcfg.display.height = r.height; ++ cfg = &tcfg; ++ } ++ ++ vout_display_PlacePicture(&place, fmt, cfg, false); ++ ++ place.x += r.x; ++ place.y += r.y; ++ ++ return place; ++} ++ ++static vout_display_place_t ++rect_transform(vout_display_place_t s, const vout_display_place_t c, const video_transform_t t) ++{ ++ if (is_vxf_transpose(t)) ++ s = vplace_transpose(s); ++ if (is_vxf_hflip(t)) ++ s = vplace_hflip(s, c); ++ if (is_vxf_vflip(t) != 0) ++ s = vplace_vflip(s, c); ++ return s; ++} ++ ++static void ++place_dest_rect(vout_display_t * const vd, ++ const vout_display_cfg_t * const cfg, ++ const video_format_t * fmt) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ sys->dest_rect = rect_transform(place_out(cfg, fmt, sys->win_rect), ++ sys->display_rect, sys->dest_transform); ++} ++ ++static void ++place_spu_rect(vout_display_t * const vd, ++ const vout_display_cfg_t * const cfg, ++ const video_format_t * fmt) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ static const vout_display_place_t r0 = {0}; ++ ++ sys->spu_rect = place_out(cfg, fmt, r0); ++ sys->spu_rect.x = 0; ++ sys->spu_rect.y = 0; ++ ++ // Copy place override logic for spu pos from video_output.c ++ // This info doesn't appear to reside anywhere natively ++ ++ if (fmt->i_width * fmt->i_height >= (unsigned int)(sys->spu_rect.width * sys->spu_rect.height)) { ++ sys->spu_rect.width = fmt->i_visible_width; ++ sys->spu_rect.height = fmt->i_visible_height; ++ } ++ ++ if (ORIENT_IS_SWAP(fmt->orientation)) ++ sys->spu_rect = vplace_transpose(sys->spu_rect); ++} ++ ++static void ++place_rects(vout_display_t * const vd, ++ const vout_display_cfg_t * const cfg, ++ const video_format_t * fmt) ++{ ++ place_dest_rect(vd, cfg, fmt); ++ place_spu_rect(vd, cfg, fmt); ++} ++ ++static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg, ++ const video_format_t *fmt) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ ++ if (!cfg && !fmt) ++ { ++ msg_Err(vd, "%s: Missing cfg & fmt", __func__); ++ return -EINVAL; ++ } ++ ++ if (!fmt) ++ fmt = &vd->source; ++ ++ if (!cfg) ++ cfg = vd->cfg; ++ ++ sys->video_transform = combine_vxf((video_transform_t)fmt->orientation, sys->display_transform); ++ ++ place_rects(vd, cfg, fmt); ++ return 0; ++} ++ ++static void set_display_windows(vout_display_t *const vd, vout_display_sys_t *const sys) ++{ ++ const drmu_mode_simple_params_t * const mode = drmu_output_mode_simple_params(sys->dout); ++ VLC_UNUSED(vd); ++ ++ sys->display_rect = (vout_display_place_t) {0, 0, mode->width, mode->height}; ++ ++ sys->win_rect = (sys->req_win.width != 0) ? ++ sys->req_win : ++ is_vxf_transpose(sys->display_transform) ? ++ vplace_transpose(sys->display_rect) : sys->display_rect; ++} ++ ++static void vd_drm_prepare(vout_display_t *vd, picture_t *pic, ++ subpicture_t *subpicture) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ unsigned int n = 0; ++ drmu_atomic_t * da = drmu_atomic_new(sys->du); ++ drmu_fb_t * dfb = NULL; ++ drmu_rect_t r; ++ unsigned int i; ++ int ret; ++ ++ if (da == NULL) ++ goto fail; ++ ++ if (sys->display_set != NULL) { ++ msg_Warn(vd, "sys->display_set != NULL"); ++ drmu_atomic_unref(&sys->display_set); ++ } ++ ++ // * Mode (currently) doesn't change whilst running so no need to set here ++ ++ // Attempt to import the subpics ++ for (subpicture_t * spic = subpicture; spic != NULL; spic = spic->p_next) ++ { ++ for (subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) { ++ picture_t * const src = sreg->p_picture; ++ subpic_ent_t * const dst = sys->subpics + n; ++ ++ // If we've run out of subplanes we could allocate - give up now ++ if (!sys->subplanes[n]) ++ goto subpics_done; ++ ++ // If the same picture then assume the same contents ++ // We keep a ref to the previous pic to ensure that the same picture ++ // structure doesn't get reused and confuse us. ++ if (src != dst->pic) { ++ drmu_fb_unref(&dst->fb); ++ if (dst->pic != NULL) { ++ picture_Release(dst->pic); ++ dst->pic = NULL; ++ } ++ ++ dst->fb = copy_pic_to_fb(vd, sys->sub_fb_pool, src); ++ if (dst->fb == NULL) ++ continue; ++ drmu_fb_pixel_blend_mode_set(dst->fb, DRMU_FB_PIXEL_BLEND_COVERAGE); ++ ++ dst->pic = picture_Hold(src); ++ } ++ drmu_fb_crop_frac_set(dst->fb, drmu_rect_shl16(drmu_rect_vlc_format_crop(&sreg->fmt))); ++ ++ // *** More transform required ++ dst->pos = (drmu_rect_t){ ++ .x = sreg->i_x, ++ .y = sreg->i_y, ++ .w = sreg->fmt.i_visible_width, ++ .h = sreg->fmt.i_visible_height, ++ }; ++ dst->alpha = spic->i_alpha * sreg->i_alpha; ++ ++// msg_Info(vd, "Orig: %dx%d, (%d,%d) %dx%d; offset %d,%d", spic->i_original_picture_width, spic->i_original_picture_height, ++// sreg->i_x, sreg->i_y, src->format.i_visible_width, src->format.i_visible_height, ++// sreg->fmt.i_x_offset, sreg->fmt.i_y_offset); ++ dst->space = drmu_rect_vlc_place(&sys->spu_rect); ++ ++ if (++n == SUBPICS_MAX) ++ goto subpics_done; ++ } ++ } ++subpics_done: ++ ++ // Clear any other entries ++ for (; n != SUBPICS_MAX; ++n) { ++ subpic_ent_t * const dst = sys->subpics + n; ++ if (dst->pic != NULL) { ++ picture_Release(dst->pic); ++ dst->pic = NULL; ++ } ++ drmu_fb_unref(&dst->fb); ++ } ++ ++ r = drmu_rect_vlc_place(&sys->dest_rect); ++ ++#if 0 ++ { ++ static int z = 0; ++ if (--z < 0) { ++ z = 200; ++ msg_Info(vd, "Pic: %d,%d %dx%d/%dx%d %d/%d Fmt: %d,%d %dx%d/%dx%d %d/%d Src: %d,%d %dx%d/%dx%d %d/%d Display: %dx%d %d/%d Place: %d,%d %dx%d", ++ pic->format.i_x_offset, pic->format.i_y_offset, ++ pic->format.i_width, pic->format.i_height, ++ pic->format.i_visible_width, pic->format.i_visible_height, ++ pic->format.i_sar_num, pic->format.i_sar_den, ++ vd->fmt.i_x_offset, vd->fmt.i_y_offset, ++ vd->fmt.i_width, vd->fmt.i_height, ++ vd->fmt.i_visible_width, vd->fmt.i_visible_height, ++ vd->fmt.i_sar_num, vd->fmt.i_sar_den, ++ vd->source.i_x_offset, vd->source.i_y_offset, ++ vd->source.i_width, vd->source.i_height, ++ vd->source.i_visible_width, vd->source.i_visible_height, ++ vd->source.i_sar_num, vd->source.i_sar_den, ++ vd->cfg->display.width, vd->cfg->display.height, ++ vd->cfg->display.sar.num, vd->cfg->display.sar.den, ++ r.x, r.y, r.w, r.h); ++ } ++ } ++#endif ++ ++#if HAS_ZC_CMA ++ if (drmu_format_vlc_to_drm_cma(pic->format.i_chroma) != 0) { ++ dfb = drmu_fb_vlc_new_pic_cma_attach(sys->du, pic); ++ } ++ else ++#endif ++#if HAS_DRMPRIME ++ if (drmu_format_vlc_to_drm_prime(pic->format.i_chroma, NULL) != 0) { ++ dfb = drmu_fb_vlc_new_pic_attach(sys->du, pic); ++ } ++ else ++#endif ++ { ++ dfb = copy_pic_to_fb(vd, sys->pic_pool, pic); ++ } ++ ++ if (dfb == NULL) { ++ msg_Err(vd, "Failed to create frme buffer from pic"); ++ return; ++ } ++ // * Maybe scale cropping by vd->fmt.i_width/height / vd->source.i_width/height ++ // to get pic coord cropping ++ // Wait until we have a bad test case before doing this as I'm worried ++ // that we may get unexpected w/h mismatches that do unwanted scaling ++#if 0 ++ drmu_fb_crop_frac_set(dfb, ++ drmu_rect_rescale( ++ drmu_rect_vlc_format_crop(&vd->source), ++ drmu_rect_shl16(drmu_rect_wh(vd->fmt.i_width, vd->fmt.i_height)), ++ drmu_rect_wh(vd->source.i_width, vd->source.i_height))); ++#else ++ drmu_fb_crop_frac_set(dfb, drmu_rect_shl16(drmu_rect_vlc_format_crop(&vd->source))); ++#endif ++ drmu_output_fb_info_set(sys->dout, dfb); ++ ++ ret = drmu_atomic_plane_add_fb(da, sys->dp, dfb, r); ++ drmu_atomic_output_add_props(da, sys->dout); ++ drmu_fb_unref(&dfb); ++ ++ if (ret != 0) { ++ msg_Err(vd, "Failed to set video plane: %s", strerror(-ret)); ++ goto fail; ++ } ++ ++ for (i = 0; i != SUBPICS_MAX; ++i) { ++ subpic_ent_t * const spe = sys->subpics + i; ++ ++// msg_Info(vd, "pic=%dx%d @ %d,%d, r=%dx%d @ %d,%d, space=%dx%d @ %d,%d", ++// spe->pos.w, spe->pos.h, spe->pos.x, spe->pos.y, ++// r.w, r.h, r.x, r.y, ++// spe->space.w, spe->space.h, spe->space.x, spe->space.y); ++ ++ // Rescale from sub-space ++ if (sys->subplanes[i]) ++ { ++ if ((ret = drmu_atomic_plane_add_fb(da, sys->subplanes[i], spe->fb, ++ drmu_rect_rescale(spe->pos, r, spe->space))) != 0) { ++ msg_Err(vd, "drmModeSetPlane for subplane %d failed: %s", i, strerror(-ret)); ++ } ++ drmu_atomic_plane_add_alpha(da, sys->subplanes[i], (spe->alpha * DRMU_PLANE_ALPHA_OPAQUE) / (0xff * 0xff)); ++ } ++ } ++ ++ sys->display_set = da; ++ ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s", __func__); ++#endif ++ return; ++ ++fail: ++ drmu_fb_unref(&dfb); ++ drmu_atomic_unref(&da); ++} ++ ++static void vd_drm_display(vout_display_t *vd, picture_t *p_pic, ++ subpicture_t *subpicture) ++{ ++ vout_display_sys_t *const sys = vd->sys; ++ VLC_UNUSED(subpicture); ++ ++#if TRACE_ALL ++ msg_Dbg(vd, "<<< %s", __func__); ++#endif ++ ++ drmu_atomic_queue(&sys->display_set); ++ ++ picture_Release(p_pic); ++ return; ++} ++ ++static void subpic_cache_flush(vout_display_sys_t * const sys) ++{ ++ for (unsigned int i = 0; i != SUBPICS_MAX; ++i) { ++ if (sys->subpics[i].pic != NULL) { ++ picture_Release(sys->subpics[i].pic); ++ sys->subpics[i].pic = NULL; ++ } ++ drmu_fb_unref(&sys->subpics[i].fb); ++ } ++} ++ ++static void kill_pool(vout_display_sys_t * const sys) ++{ ++ // Drop all cached subpics ++ subpic_cache_flush(sys); ++ ++ if (sys->vlc_pic_pool != NULL) { ++ picture_pool_Release(sys->vlc_pic_pool); ++ sys->vlc_pic_pool = NULL; ++ } ++} ++ ++// Actual picture pool for MMAL opaques is just a set of trivial containers ++static picture_pool_t *vd_drm_pool(vout_display_t *vd, unsigned count) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ ++#if TRACE_ALL ++ msg_Dbg(vd, "%s: fmt:%dx%d,sar:%d/%d; source:%dx%d", __func__, ++ vd->fmt.i_width, vd->fmt.i_height, vd->fmt.i_sar_num, vd->fmt.i_sar_den, vd->source.i_width, vd->source.i_height); ++#endif ++ ++ if (sys->vlc_pic_pool == NULL) { ++ sys->vlc_pic_pool = picture_pool_NewFromFormat(&vd->fmt, count); ++ } ++ return sys->vlc_pic_pool; ++} ++ ++// Copy format from *fmtp into vd->fmt and make any necessary adjustments to ++// ensure display (tweak chroma) ++static void ++set_format(vout_display_t * const vd, vout_display_sys_t * const sys, const video_format_t *const fmtp) ++{ ++#if HAS_DRMPRIME ++ uint32_t drm_fmt; ++ uint64_t drm_mod; ++ ++ // We think we can deal with the source format so set requested ++ // input format to source ++ vd->fmt = *fmtp; ++ ++ if ((drm_fmt = drmu_format_vlc_to_drm_prime(fmtp->i_chroma, &drm_mod)) != 0 && ++ drmu_plane_format_check(sys->dp, drm_fmt, drm_mod)) { ++ // Hurrah! ++ } ++ else ++#endif ++#if HAS_ZC_CMA ++ if (fmtp->i_chroma == VLC_CODEC_MMAL_OPAQUE) { ++ // Can't deal directly with opaque - but we can always convert it to ZC I420 ++ vd->fmt.i_chroma = VLC_CODEC_MMAL_ZC_I420; ++ } ++ else ++#endif ++ if ((drm_fmt = drmu_format_vlc_to_drm(fmtp)) != 0 && ++ drmu_plane_format_check(sys->dp, drm_fmt, 0)) { ++ // It is a format where simple byte copying works ++ } ++ else { ++ const vlc_fourcc_t *fallback = vlc_fourcc_IsYUV(fmtp->i_chroma) ? ++ vlc_fourcc_GetYUVFallback(fmtp->i_chroma) : ++ vlc_fourcc_GetRGBFallback(fmtp->i_chroma); ++ ++ for (; *fallback; ++fallback) { ++ if (drmu_plane_format_check(sys->dp, drmu_format_vlc_chroma_to_drm(*fallback), 0)) ++ break; ++ } ++ ++ // no conversion - ask for something we know we can deal with ++ vd->fmt.i_chroma = *fallback ? *fallback : VLC_CODEC_I420; ++ } ++} ++ ++ ++static int vd_drm_control(vout_display_t *vd, int query, va_list args) ++{ ++ vout_display_sys_t * const sys = vd->sys; ++ int ret = VLC_EGENERIC; ++ ++ switch (query) { ++ case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: ++ case VOUT_DISPLAY_CHANGE_SOURCE_CROP: ++ if (configure_display(vd, vd->cfg, &vd->source) >= 0) ++ ret = VLC_SUCCESS; ++ break; ++ ++ case VOUT_DISPLAY_CHANGE_ZOOM: ++ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: ++ case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED: ++ { ++ const vout_display_cfg_t * const cfg = va_arg(args, const vout_display_cfg_t *); ++ ++ if (configure_display(vd, cfg, &vd->source) >= 0) ++ ret = VLC_SUCCESS; ++ break; ++ } ++ ++ case VOUT_DISPLAY_RESET_PICTURES: ++ msg_Warn(vd, "Reset Pictures"); ++ kill_pool(sys); ++ set_format(vd, sys, &vd->source); ++ ret = VLC_SUCCESS; ++ break; ++ ++ default: ++ msg_Warn(vd, "Unknown control query %d", query); ++ break; ++ } ++ ++ return ret; ++} ++ ++static void CloseDrmVout(vout_display_t *vd) ++{ ++ vout_display_sys_t *const sys = vd->sys; ++ unsigned int i; ++ ++ msg_Dbg(vd, "<<< %s", __func__); ++ ++ drmu_pool_delete(&sys->sub_fb_pool); ++ drmu_pool_delete(&sys->pic_pool); ++ ++ for (i = 0; i != SUBPICS_MAX; ++i) ++ drmu_plane_unref(sys->subplanes + i); ++ subpic_cache_flush(sys); ++ ++ drmu_plane_unref(&sys->dp); ++ drmu_output_unref(&sys->dout); ++ drmu_env_unref(&sys->du); ++ ++ free(sys->subpic_chromas); ++ vd->info.subpicture_chromas = NULL; ++ ++ vd->sys = NULL; ++ free(sys); ++#if TRACE_ALL ++ msg_Dbg(vd, ">>> %s", __func__); ++#endif ++} ++ ++// VLC will take a list of subpic formats but it then ignores the fact it is a ++// list and picks the 1st one whether it is 'best' or indeed whether or not it ++// can use it. So we have to sort ourselves & have checked usablity. ++// Higher number, higher priority. 0 == Do not use. ++static int subpic_fourcc_usability(const vlc_fourcc_t fcc) ++{ ++ switch (fcc) { ++ case VLC_CODEC_ARGB: ++ return 20; ++ case VLC_CODEC_RGBA: ++ return 22; ++ case VLC_CODEC_BGRA: ++ return 21; ++ case VLC_CODEC_YUVA: ++ return 40; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++// Sort in descending priority number ++static int subpic_fourcc_sort_cb(const void * a, const void * b) ++{ ++ return subpic_fourcc_usability(*(vlc_fourcc_t *)b) - subpic_fourcc_usability(*(vlc_fourcc_t *)a); ++} ++ ++static vlc_fourcc_t * ++subpic_make_chromas_from_drm(const uint32_t * const drm_chromas, const unsigned int n) ++{ ++ vlc_fourcc_t * const c = (n == 0) ? NULL : calloc(n + 1, sizeof(*c)); ++ vlc_fourcc_t * p = c; ++ ++ if (c == NULL) ++ return NULL; ++ ++ for (unsigned int j = 0; j != n; ++j) { ++ if ((*p = drmu_format_vlc_to_vlc(drm_chromas[j])) != 0) ++ ++p; ++ } ++ ++ // Sort for our preferred order & remove any that would confuse VLC ++ qsort(c, p - c, sizeof(*c), subpic_fourcc_sort_cb); ++ while (p != c) { ++ if (subpic_fourcc_usability(p[-1]) != 0) ++ break; ++ *--p = 0; ++ } ++ ++ if (p == c) { ++ free(c); ++ return NULL; ++ } ++ ++ return c; ++} ++ ++static int ++test_simple_plane_set(vout_display_t * const vd, vout_display_sys_t * const sys) ++{ ++ drmu_atomic_t *da = drmu_atomic_new(sys->du); ++ drmu_fb_t *fb; ++ int rv = -ENOMEM; ++ ++ if (da == NULL) { ++ msg_Warn(vd, "Failed to alloc test atomic"); ++ goto fail; ++ } ++ ++ if ((fb = drmu_pool_fb_new_dumb(sys->sub_fb_pool, 128, 128, drmu_format_vlc_chroma_to_drm(sys->subpic_chromas[0]))) == NULL) { ++ msg_Warn(vd, "Failed to alloc test FB"); ++ goto fail; ++ } ++ ++ if ((rv = drmu_atomic_plane_add_fb(da, sys->subplanes[0], fb, drmu_rect_wh(128, 128))) != 0) { ++ msg_Warn(vd, "Failed to add test FB to atomic"); ++ goto fail; ++ } ++ ++ if ((rv = drmu_atomic_commit(da, DRM_MODE_ATOMIC_TEST_ONLY)) != 0) { ++ msg_Warn(vd, "Failed to commit test FB"); ++ goto fail; ++ } ++ ++fail: ++ drmu_atomic_unref(&da); ++ drmu_fb_unref(&fb); ++ return rv; ++} ++ ++static int OpenDrmVout(vlc_object_t *object) ++{ + vout_display_t * const vd = (vout_display_t *)object; -+ vout_display_sys_t * const sys = vd->sys; -+ char response[20]; /* answer is hvs_update_fields=%1d */ ++// video_format_t * const fmtp = &vd->fmt; ++ const video_format_t *const fmtp = &vd->source; ++ const uint32_t src_chroma = fmtp->i_chroma; ++ vout_display_sys_t *sys; ++ int ret = VLC_EGENERIC; ++ int rv; ++ msg_Info(vd, "<<< %s: Fmt=%4.4s", __func__, (const char *)&fmtp->i_chroma); ++ ++// if (!var_InheritBool(vd, "fullscreen")) { ++// msg_Dbg(vd, ">>> %s: Not fullscreen", __func__); ++// return ret; ++// } ++ ++ sys = calloc(1, sizeof(*sys)); ++ if (!sys) ++ return VLC_ENOMEM; ++ vd->sys = sys; ++ ++ sys->mode_id = -1; ++ ++ { ++ const drmu_log_env_t log = { ++ .fn = drmu_log_vlc_cb, ++ .v = vd, ++ .max_level = DRMU_LOG_LEVEL_ALL ++ }; ++ if ((sys->du = drmu_env_new_xlease(&log)) == NULL && ++ (sys->du = drmu_env_new_open(var_InheritString(vd, DRM_VOUT_MODULE_NAME), &log)) == NULL) ++ goto fail; ++ } ++ ++ drmu_env_restore_enable(sys->du); ++ ++ if ((sys->dout = drmu_output_new(sys->du)) == NULL) { ++ msg_Err(vd, "Failed to allocate new drmu output"); ++ goto fail; ++ } ++ ++ drmu_output_modeset_allow(sys->dout, !var_InheritBool(vd, DRM_VOUT_NO_MODESET_NAME)); ++ drmu_output_max_bpc_allow(sys->dout, !var_InheritBool(vd, DRM_VOUT_NO_MAX_BPC)); ++ ++ ++ { ++ const char *display_name = var_InheritString(vd, DRM_VOUT_DISPLAY_NAME); ++ int qt_num = var_InheritInteger(vd, "qt-fullscreen-screennumber"); ++ const char * conn_name = qt_num == 0 ? "HDMI-A-1" : qt_num == 1 ? "HDMI-A-2" : NULL; ++ const char * dname; ++ ++ if (display_name && strcasecmp(display_name, "auto") != 0) { ++ if (strcasecmp(display_name, "hdmi-1") == 0) ++ conn_name = "HDMI-A-1"; ++ else if (strcasecmp(display_name, "hdmi-2") == 0) ++ conn_name = "HDMI-A-2"; ++ } ++ ++ dname = conn_name != NULL ? conn_name : ""; ++ ++ if ((rv = drmu_output_add_output(sys->dout, conn_name)) != 0) { ++ msg_Err(vd, "Failed to find output %s: %s", dname, strerror(-rv)); ++ goto fail; ++ } ++ msg_Dbg(vd, "Using conn %s", dname); ++ } ++ ++ if ((sys->sub_fb_pool = drmu_pool_new(sys->du, 10)) == NULL) ++ goto fail; ++ if ((sys->pic_pool = drmu_pool_new(sys->du, 5)) == NULL) ++ goto fail; ++ ++ // This wants to be the primary ++ if ((sys->dp = drmu_output_plane_ref_primary(sys->dout)) == NULL) ++ goto fail; ++ ++ for (unsigned int i = 0; i != SUBPICS_MAX; ++i) { ++ if ((sys->subplanes[i] = drmu_output_plane_ref_other(sys->dout)) == NULL) { ++ msg_Warn(vd, "Cannot allocate subplane %d", i); ++ break; ++ } ++ if (sys->subpic_chromas == NULL) { ++ unsigned int n; ++ const uint32_t * const drm_chromas = drmu_plane_formats(sys->subplanes[i], &n); ++ sys->subpic_chromas = subpic_make_chromas_from_drm(drm_chromas, n); ++ } ++ } ++ ++ if (test_simple_plane_set(vd, sys) != 0) { ++ msg_Warn(vd, "Failed simple pic test"); ++ goto fail; ++ } ++ ++ vd->info = (vout_display_info_t){ ++ .is_slow = false, ++ .has_double_click = false, ++ .needs_hide_mouse = false, ++ .has_pictures_invalid = true, ++ .subpicture_chromas = sys->subpic_chromas ++ }; ++ ++ vd->pool = vd_drm_pool; ++ vd->prepare = vd_drm_prepare; ++ vd->display = vd_drm_display; ++ vd->control = vd_drm_control; ++ ++ const char *modestr = var_InheritString(vd, DRM_VOUT_MODE_NAME); ++ sys->mode_id = -1; ++ ++ if (var_InheritBool(vd, DRM_VOUT_SOURCE_MODESET_NAME)) ++ modestr = "source"; ++ ++ if (modestr != NULL && strcmp(modestr, "none") != 0) { ++ drmu_mode_simple_params_t pick = { ++ .width = fmtp->i_visible_width, ++ .height = fmtp->i_visible_height, ++ .hz_x_1000 = fmtp->i_frame_rate_base == 0 ? 0 : ++ (unsigned int)(((uint64_t)fmtp->i_frame_rate * 1000) / fmtp->i_frame_rate_base), ++ }; ++ ++ if (strcmp(modestr, "source") != 0) { ++ unsigned int w, h, hz; ++ if (*drmu_util_parse_mode(modestr, &w, &h, &hz) != 0) { ++ msg_Err(vd, "Bad mode string: '%s'", modestr); ++ ret = VLC_EGENERIC; ++ goto fail; ++ } ++ if (w && h) { ++ pick.width = w; ++ pick.height = h; ++ } ++ if (hz) ++ pick.hz_x_1000 = hz; ++ } ++ ++ sys->mode_id = drmu_output_mode_pick_simple(sys->dout, drmu_mode_pick_simple_cb, &pick); ++ ++ msg_Dbg(vd, "Mode id=%d", sys->mode_id); ++ ++ // This will set the mode on the crtc var but won't actually change the output ++ if (sys->mode_id >= 0) { ++ const drmu_mode_simple_params_t * mode; ++ ++ drmu_output_mode_id_set(sys->dout, sys->mode_id); ++ mode = drmu_output_mode_simple_params(sys->dout); ++ msg_Info(vd, "Mode %d: %dx%d@%d.%03d %d/%d - req %dx%d@%d.%d", sys->mode_id, ++ mode->width, mode->height, mode->hz_x_1000 / 1000, mode->hz_x_1000 % 1000, ++ mode->sar.num, mode->sar.den, pick.width, pick.height, pick.hz_x_1000 / 1000, pick.hz_x_1000 % 1000); ++ } ++ } ++ ++ set_format(vd, sys, fmtp); ++ ++// vout_display_SetSizeAndSar(vd, drmu_crtc_width(sys->dc), drmu_crtc_height(sys->dc), ++// drmu_ufrac_vlc_to_rational(drmu_crtc_sar(sys->dc))); ++ ++ { ++ const char *window_str = var_InheritString(vd, DRM_VOUT_WINDOW_NAME); ++ if (strcmp(window_str, "fullscreen") == 0) { ++ /* Leave req_win null */ ++ msg_Dbg(vd, "Window: fullscreen"); ++ } ++ else { ++ sys->req_win = str_to_rect(window_str); ++ if (sys->req_win.width != 0) ++ msg_Dbg(vd, "Window: %dx%d @ %d,%d", ++ sys->req_win.width, sys->req_win.height, ++ sys->req_win.x, sys->req_win.y); ++ else ++ msg_Warn(vd, "Window: '%s': cannot parse (usage: x++) - using fullscreen", window_str); ++ } ++ } ++ ++ if (src_chroma != vd->fmt.i_chroma) ++ msg_Warn(vd, "Cannot display %s directly trying %s", drmu_log_fourcc(src_chroma), drmu_log_fourcc(vd->fmt.i_chroma)); ++ ++ set_display_windows(vd, sys); ++ ++ configure_display(vd, vd->cfg, &vd->source); ++ ++ return VLC_SUCCESS; ++ ++fail: ++ CloseDrmVout(vd); ++ return ret; ++} ++ ++vlc_module_begin() ++ set_shortname(N_("DRM vout")) ++ set_description(N_("DRM vout plugin")) ++ set_capability("vout display", 16) // 1 point better than ASCII art ++ add_shortcut("drm_vout") ++ set_category(CAT_VIDEO) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ ++ add_bool(DRM_VOUT_SOURCE_MODESET_NAME, false, DRM_VOUT_SOURCE_MODESET_TEXT, DRM_VOUT_SOURCE_MODESET_LONGTEXT, false) ++ add_bool(DRM_VOUT_NO_MODESET_NAME, false, DRM_VOUT_NO_MODESET_TEXT, DRM_VOUT_NO_MODESET_LONGTEXT, false) ++ add_bool(DRM_VOUT_NO_MAX_BPC, false, DRM_VOUT_NO_MAX_BPC_TEXT, DRM_VOUT_NO_MAX_BPC_LONGTEXT, false) ++ add_string(DRM_VOUT_MODE_NAME, "none", DRM_VOUT_MODE_TEXT, DRM_VOUT_MODE_LONGTEXT, false) ++ add_string(DRM_VOUT_WINDOW_NAME, "fullscreen", DRM_VOUT_WINDOW_TEXT, DRM_VOUT_WINDOW_LONGTEXT, false) ++ add_string(DRM_VOUT_DISPLAY_NAME, "auto", DRM_VOUT_DISPLAY_TEXT, DRM_VOUT_DISPLAY_LONGTEXT, false) ++ add_string(DRM_VOUT_MODULE_NAME, DRM_MODULE, DRM_VOUT_MODULE_TEXT, DRM_VOUT_MODULE_LONGTEXT, false) ++ ++ set_callbacks(OpenDrmVout, CloseDrmVout) ++vlc_module_end() ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu.c +@@ -0,0 +1,3878 @@ ++// Needed to ensure we get a 64-bit offset to mmap when mapping BOs ++#undef _FILE_OFFSET_BITS ++#define _FILE_OFFSET_BITS 64 ++ ++#include "drmu.h" ++#include "drmu_fmts.h" ++#include "drmu_log.h" ++ ++#include ++ ++#include "pollqueue.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#define TRACE_PROP_NEW 0 ++ ++#ifndef DRM_FORMAT_P030 ++#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') ++#endif ++ ++struct drmu_bo_env_s; ++struct drmu_atomic_q_s; ++static struct drmu_bo_env_s * env_boe(drmu_env_t * const du); ++static struct pollqueue * env_pollqueue(const drmu_env_t * const du); ++static struct drmu_atomic_q_s * env_atomic_q(drmu_env_t * const du); ++static int env_object_state_save(drmu_env_t * const du, const uint32_t obj_id, const uint32_t obj_type); ++ ++// Update return value with a new one for cases where we don't stop on error ++static inline int rvup(int rv1, int rv2) ++{ ++ return rv2 ? rv2 : rv1; ++} ++ ++// Alloc retry helper ++static inline int ++retry_alloc_u32(uint32_t ** const pp, uint32_t * const palloc_count, uint32_t const new_count) ++{ ++ if (new_count <= *palloc_count) ++ return 0; ++ free(*pp); ++ *palloc_count = 0; ++ if ((*pp = malloc(sizeof(**pp) * new_count)) == NULL) ++ return -ENOMEM; ++ *palloc_count = new_count; ++ return 1; ++} ++ ++drmu_ufrac_t ++drmu_ufrac_reduce(drmu_ufrac_t x) ++{ ++ static const unsigned int primes[] = {2,3,5,7,11,13,17,19,23,29,31,UINT_MAX}; ++ const unsigned int * p; ++ ++ // Deal with specials ++ if (x.den == 0) { ++ x.num = 0; ++ return x; ++ } ++ if (x.num == 0) { ++ x.den = 1; ++ return x; ++ } ++ ++ // Shortcut the 1:1 common case - also ensures the default loop terminates ++ if (x.num == x.den) { ++ x.num = 1; ++ x.den = 1; ++ return x; ++ } ++ ++ // As num != den, (num/UINT_MAX == 0 || den/UINT_MAX == 0) must be true ++ // so loop will terminate ++ for (p = primes;; ++p) { ++ const unsigned int n = *p; ++ for (;;) { ++ const unsigned int xd = x.den / n; ++ const unsigned int xn = x.num / n; ++ if (xn == 0 || xd == 0) ++ return x; ++ if (xn * n != x.num || xd * n != x.den) ++ break; ++ x.num = xn; ++ x.den = xd; ++ } ++ } ++} ++ ++//---------------------------------------------------------------------------- ++// ++// propinfo ++ ++typedef struct drmu_propinfo_s { ++ uint64_t val; ++ struct drm_mode_get_property prop; ++} drmu_propinfo_t; ++ ++static uint64_t ++propinfo_val(const drmu_propinfo_t * const pi) ++{ ++ return pi == NULL ? 0 : pi->val; ++} ++ ++static uint32_t ++propinfo_prop_id(const drmu_propinfo_t * const pi) ++{ ++ return pi == NULL ? 0 : pi->prop.prop_id; ++} ++ ++ ++//---------------------------------------------------------------------------- ++// ++// Blob fns ++ ++typedef struct drmu_blob_s { ++ atomic_int ref_count; // 0 == 1 ref for ease of init ++ struct drmu_env_s * du; ++ uint32_t blob_id; ++ // Copy of blob data as we nearly always want to keep a copy to compare ++ size_t len; ++ void * data; ++} drmu_blob_t; ++ ++static void ++blob_free(drmu_blob_t * const blob) ++{ ++ drmu_env_t * const du = blob->du; ++ ++ if (blob->blob_id != 0) { ++ struct drm_mode_destroy_blob dblob = { ++ .blob_id = blob->blob_id ++ }; ++ if (drmu_ioctl(du, DRM_IOCTL_MODE_DESTROYPROPBLOB, &dblob) != 0) ++ drmu_err(du, "%s: Failed to destroy blob: %s", __func__, strerror(errno)); ++ } ++ free(blob->data); ++ free(blob); ++} ++ ++void ++drmu_blob_unref(drmu_blob_t ** const ppBlob) ++{ ++ drmu_blob_t * const blob = *ppBlob; ++ ++ if (blob == NULL) ++ return; ++ *ppBlob = NULL; ++ ++ if (atomic_fetch_sub(&blob->ref_count, 1) != 0) ++ return; ++ ++ blob_free(blob); ++} ++ ++uint32_t ++drmu_blob_id(const drmu_blob_t * const blob) ++{ ++ return blob == NULL ? 0 : blob->blob_id; ++} ++ ++drmu_blob_t * ++drmu_blob_ref(drmu_blob_t * const blob) ++{ ++ if (blob != NULL) ++ atomic_fetch_add(&blob->ref_count, 1); ++ return blob; ++} ++ ++const void * ++drmu_blob_data(const drmu_blob_t * const blob) ++{ ++ return blob->data; ++} ++ ++size_t ++drmu_blob_len(const drmu_blob_t * const blob) ++{ ++ return blob->len; ++} ++ ++drmu_blob_t * ++drmu_blob_new(drmu_env_t * const du, const void * const data, const size_t len) ++{ ++ int rv; ++ drmu_blob_t * blob = calloc(1, sizeof(*blob)); ++ struct drm_mode_create_blob cblob = { ++ .data = (uintptr_t)data, ++ .length = (uint32_t)len, ++ .blob_id = 0 ++ }; ++ ++ if (blob == NULL) { ++ drmu_err(du, "%s: Unable to alloc blob", __func__); ++ return NULL; ++ } ++ blob->du = du; ++ ++ if ((blob->data = malloc(len)) == NULL) { ++ drmu_err(du, "%s: Unable to alloc blob data", __func__); ++ goto fail; ++ } ++ blob->len = len; ++ memcpy(blob->data, data, len); ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_CREATEPROPBLOB, &cblob)) != 0) { ++ drmu_err(du, "%s: Unable to create blob: data=%p, len=%zu: %s", __func__, ++ data, len, strerror(-rv)); ++ goto fail; ++ } ++ ++ atomic_init(&blob->ref_count, 0); ++ blob->blob_id = cblob.blob_id; ++ return blob; ++ ++fail: ++ blob_free(blob); ++ return NULL; ++} ++ ++int ++drmu_blob_update(drmu_env_t * const du, drmu_blob_t ** const ppblob, const void * const data, const size_t len) ++{ ++ drmu_blob_t * blob = *ppblob; ++ ++ if (data == NULL || len == 0) { ++ drmu_blob_unref(ppblob); ++ return 0; ++ } ++ ++ if (blob && len == blob->len && memcmp(data, blob->data, len) == 0) ++ return 0; ++ ++ if ((blob = drmu_blob_new(du, data, len)) == NULL) ++ return -ENOMEM; ++ drmu_blob_unref(ppblob); ++ *ppblob = blob; ++ return 0; ++} ++ ++// Data alloced here needs freeing later ++static int ++blob_data_read(drmu_env_t * const du, uint32_t blob_id, void ** const ppdata, size_t * plen) ++{ ++ void * data; ++ struct drm_mode_get_blob gblob = {.blob_id = blob_id}; ++ int rv; ++ ++ *ppdata = NULL; ++ *plen = 0; ++ ++ if (blob_id == 0) ++ return 0; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPBLOB, &gblob)) != 0) ++ return rv; ++ ++ if (gblob.length == 0) ++ return 0; ++ ++ if ((data = malloc(gblob.length)) == NULL) ++ return -ENOMEM; ++ ++ gblob.data = (uintptr_t)data; ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPBLOB, &gblob)) != 0) { ++ free(data); ++ return rv; ++ } ++ ++ *ppdata = data; ++ *plen = gblob.length; ++ return 0; ++} ++ ++// Copy existing blob into a new one ++// Useful when saving preexisiting values ++drmu_blob_t * ++drmu_blob_copy_id(drmu_env_t * const du, uint32_t blob_id) ++{ ++ void * data; ++ size_t len; ++ drmu_blob_t * blob = NULL; ++ ++ if (blob_data_read(du, blob_id, &data, &len) == 0) ++ blob = drmu_blob_new(du, data, len); // * This copies data - could just get it to take the malloc ++ ++ free(data); ++ return blob; ++} ++ ++static void ++atomic_prop_blob_unref(void * v) ++{ ++ drmu_blob_t * blob = v; ++ drmu_blob_unref(&blob); ++} ++ ++static void ++atomic_prop_blob_ref(void * v) ++{ ++ drmu_blob_ref(v); ++} ++ ++int ++drmu_atomic_add_prop_blob(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_blob_t * const blob) ++{ ++ int rv; ++ static const drmu_atomic_prop_fns_t fns = { ++ .ref = atomic_prop_blob_ref, ++ .unref = atomic_prop_blob_unref, ++ .commit = drmu_prop_fn_null_commit ++ }; ++ ++ if (blob == NULL) ++ return drmu_atomic_add_prop_value(da, obj_id, prop_id, 0); ++ ++ rv = drmu_atomic_add_prop_generic(da, obj_id, prop_id, drmu_blob_id(blob), &fns, blob); ++ if (rv != 0) ++ drmu_warn(drmu_atomic_env(da), "%s: Failed to add blob obj_id=%#x, prop_id=%#x: %s", __func__, obj_id, prop_id, strerror(-rv)); ++ ++ return rv; ++} ++ ++//---------------------------------------------------------------------------- ++// ++// Enum fns ++ ++typedef struct drmu_prop_enum_s { ++ uint32_t id; ++ uint32_t flags; ++ unsigned int n; ++ const struct drm_mode_property_enum * enums; ++ char name[DRM_PROP_NAME_LEN]; ++} drmu_prop_enum_t; ++ ++static void ++prop_enum_free(drmu_prop_enum_t * const pen) ++{ ++ free((void*)pen->enums); // Cast away const ++ free(pen); ++} ++ ++static int ++prop_enum_qsort_cb(const void * va, const void * vb) ++{ ++ const struct drm_mode_property_enum * a = va; ++ const struct drm_mode_property_enum * b = vb; ++ return strcmp(a->name, b->name); ++} ++ ++// NULL if not found ++const uint64_t * ++drmu_prop_enum_value(const drmu_prop_enum_t * const pen, const char * const name) ++{ ++ if (pen != NULL && name != NULL) { ++ unsigned int i = pen->n / 2; ++ unsigned int a = 0; ++ unsigned int b = pen->n; ++ ++ if (name == NULL) ++ return NULL; ++ ++ while (a < b) { ++ const int r = strcmp(name, pen->enums[i].name); ++ ++ if (r == 0) ++ return (const uint64_t *)&pen->enums[i].value; // __u64 defn != uint64_t defn always :-( ++ ++ if (r < 0) { ++ b = i; ++ i = (i + a) / 2; ++ } else { ++ a = i + 1; ++ i = (i + b) / 2; ++ } ++ } ++ } ++ return NULL; ++} ++ ++uint64_t ++drmu_prop_bitmask_value(const drmu_prop_enum_t * const pen, const char * const name) ++{ ++ const uint64_t *const p = drmu_prop_enum_value(pen, name); ++ return p == NULL || *p >= 64 || (pen->flags & DRM_MODE_PROP_BITMASK) == 0 ? ++ (uint64_t)0 : (uint64_t)1 << *p; ++} ++ ++uint32_t ++drmu_prop_enum_id(const drmu_prop_enum_t * const pen) ++{ ++ return pen == NULL ? 0 : pen->id; ++} ++ ++void ++drmu_prop_enum_delete(drmu_prop_enum_t ** const pppen) ++{ ++ drmu_prop_enum_t * const pen = *pppen; ++ if (pen == NULL) ++ return; ++ *pppen = NULL; ++ ++ prop_enum_free(pen); ++} ++ ++drmu_prop_enum_t * ++drmu_prop_enum_new(drmu_env_t * const du, const uint32_t id) ++{ ++ drmu_prop_enum_t * pen; ++ struct drm_mode_property_enum * enums = NULL; ++ unsigned int retries; ++ int rv; ++ ++ // If id 0 return without warning for ease of getting props on init ++ if (id == 0 || (pen = calloc(1, sizeof(*pen))) == NULL) ++ return NULL; ++ pen->id = id; ++ ++ // Docn says we must loop till stable as there may be hotplug races ++ for (retries = 0; retries < 8; ++retries) { ++ struct drm_mode_get_property prop = { ++ .prop_id = id, ++ .count_enum_blobs = pen->n, ++ .enum_blob_ptr = (uintptr_t)enums ++ }; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &prop)) != 0) { ++ drmu_err(du, "%s: get property failed: %s", __func__, strerror(-rv)); ++ goto fail; ++ } ++ ++ if (prop.count_enum_blobs == 0 || ++ (prop.flags & (DRM_MODE_PROP_ENUM | DRM_MODE_PROP_BITMASK)) == 0) { ++ drmu_err(du, "%s: not an enum: flags=%#x, enums=%d", __func__, prop.flags, prop.count_enum_blobs); ++ goto fail; ++ } ++ ++ if (pen->n >= prop.count_enum_blobs) { ++ pen->flags = prop.flags; ++ pen->n = prop.count_enum_blobs; ++ memcpy(pen->name, prop.name, sizeof(pen->name)); ++ break; ++ } ++ ++ free(enums); ++ ++ pen->n = prop.count_enum_blobs; ++ if ((enums = malloc(pen->n * sizeof(*enums))) == NULL) ++ goto fail; ++ } ++ if (retries >= 8) { ++ drmu_err(du, "%s: Too many retries", __func__); ++ goto fail; ++ } ++ ++ qsort(enums, pen->n, sizeof(*enums), prop_enum_qsort_cb); ++ pen->enums = enums; ++ ++#if TRACE_PROP_NEW ++ if (!pen->n) { ++ drmu_info(du, "%32s %2d: no properties"); ++ } ++ else { ++ unsigned int i; ++ for (i = 0; i != pen->n; ++i) { ++ drmu_info(du, "%32s %2d:%02d: %32s %#"PRIx64, pen->name, pen->id, i, pen->enums[i].name, pen->enums[i].value); ++ } ++ } ++#endif ++ ++ return pen; ++ ++fail: ++ free(enums); ++ prop_enum_free(pen); ++ return NULL; ++} ++ ++int ++drmu_atomic_add_prop_enum(drmu_atomic_t * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const char * const name) ++{ ++ const uint64_t * const pval = drmu_prop_enum_value(pen, name); ++ int rv; ++ ++ rv = (pval == NULL) ? -EINVAL : ++ drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_enum_id(pen), *pval, NULL, NULL); ++ ++ if (rv != 0 && name != NULL) ++ drmu_warn(drmu_atomic_env(da), "%s: Failed to add enum obj_id=%#x, prop_id=%#x, name='%s': %s", __func__, ++ obj_id, drmu_prop_enum_id(pen), name, strerror(-rv)); ++ ++ return rv; ++} ++ ++int ++drmu_atomic_add_prop_bitmask(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const uint64_t val) ++{ ++ int rv; ++ ++ rv = !pen ? -ENOENT : ++ ((pen->flags & DRM_MODE_PROP_BITMASK) == 0) ? -EINVAL : ++ drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_enum_id(pen), val, NULL, NULL); ++ ++ if (rv != 0) ++ drmu_warn(drmu_atomic_env(da), "%s: Failed to add bitmask obj_id=%#x, prop_id=%#x, val=%#"PRIx64": %s", __func__, ++ obj_id, drmu_prop_enum_id(pen), val, strerror(-rv)); ++ ++ return rv; ++} ++ ++//---------------------------------------------------------------------------- ++// ++// Range ++ ++typedef struct drmu_prop_range_s { ++ uint32_t id; ++ uint32_t flags; ++ uint64_t range[2]; ++ char name[DRM_PROP_NAME_LEN]; ++} drmu_prop_range_t; ++ ++static void ++prop_range_free(drmu_prop_range_t * const pra) ++{ ++ free(pra); ++} ++ ++void ++drmu_prop_range_delete(drmu_prop_range_t ** pppra) ++{ ++ drmu_prop_range_t * const pra = *pppra; ++ ++ if (pra == NULL) ++ return; ++ *pppra = NULL; ++ ++ prop_range_free(pra); ++} ++ ++bool ++drmu_prop_range_validate(const drmu_prop_range_t * const pra, const uint64_t x) ++{ ++ if (pra == NULL) ++ return false; ++ if ((pra->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_SIGNED_RANGE) { ++ return (int64_t)pra->range[0] <= (int64_t)x && (int64_t)pra->range[1] >= (int64_t)x; ++ } ++ return pra->range[0] <= x && pra->range[1] >= x; ++} ++ ++bool ++drmu_prop_range_immutable(const drmu_prop_range_t * const pra) ++{ ++ return !pra || (pra->flags & DRM_MODE_PROP_IMMUTABLE) != 0; ++} ++ ++uint64_t ++drmu_prop_range_max(const drmu_prop_range_t * const pra) ++{ ++ return pra == NULL ? 0 : pra->range[1]; ++} ++ ++uint64_t ++drmu_prop_range_min(const drmu_prop_range_t * const pra) ++{ ++ return pra == NULL ? 0 : pra->range[0]; ++} ++ ++uint32_t ++drmu_prop_range_id(const drmu_prop_range_t * const pra) ++{ ++ return pra == NULL ? 0 : pra->id; ++} ++ ++const char * ++drmu_prop_range_name(const drmu_prop_range_t * const pra) ++{ ++ return pra == NULL ? "{norange}" : pra->name; ++} ++ ++drmu_prop_range_t * ++drmu_prop_range_new(drmu_env_t * const du, const uint32_t id) ++{ ++ drmu_prop_range_t * pra; ++ int rv; ++ ++ // If id 0 return without warning for ease of getting props on init ++ if (id == 0 || (pra = calloc(1, sizeof(*pra))) == NULL) ++ return NULL; ++ pra->id = id; ++ ++ // We are expecting exactly 2 values so no need to loop ++ { ++ struct drm_mode_get_property prop = { ++ .prop_id = id, ++ .count_values = 2, ++ .values_ptr = (uintptr_t)pra->range ++ }; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &prop)) != 0) { ++ drmu_err(du, "%s: get property failed: %s", __func__, strerror(-rv)); ++ goto fail; ++ } ++ ++ if ((prop.flags & DRM_MODE_PROP_RANGE) == 0 && ++ (prop.flags & DRM_MODE_PROP_EXTENDED_TYPE) != DRM_MODE_PROP_SIGNED_RANGE) { ++ drmu_err(du, "%s: not an signed range: flags=%#x", __func__, prop.flags); ++ goto fail; ++ } ++ if ((prop.count_values != 2)) { ++ drmu_err(du, "%s: unexpected count values: %d", __func__, prop.count_values); ++ goto fail; ++ } ++ ++ pra->flags = prop.flags; ++ memcpy(pra->name, prop.name, sizeof(pra->name)); ++ } ++ ++#if TRACE_PROP_NEW ++ drmu_info(du, "%32s %2d: %"PRId64"->%"PRId64, pra->name, pra->id, pra->range[0], pra->range[1]); ++#endif ++ ++ return pra; ++ ++fail: ++ prop_range_free(pra); ++ return NULL; ++} ++ ++int ++drmu_atomic_add_prop_range(drmu_atomic_t * const da, const uint32_t obj_id, const drmu_prop_range_t * const pra, const uint64_t x) ++{ ++ int rv; ++ ++ rv = !pra ? -ENOENT : ++ !drmu_prop_range_validate(pra, x) ? -EINVAL : ++ drmu_prop_range_immutable(pra) ? -EPERM : ++ drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_range_id(pra), x, NULL, NULL); ++ ++ if (rv != 0) ++ drmu_warn(drmu_atomic_env(da), ++ "%s: Failed to add range %s obj_id=%#x, prop_id=%#x, val=%"PRId64", range=%"PRId64"->%"PRId64": %s", ++ __func__, drmu_prop_range_name(pra), ++ obj_id, drmu_prop_range_id(pra), x, drmu_prop_range_min(pra), drmu_prop_range_max(pra), strerror(-rv)); ++ ++ return rv; ++} ++ ++//---------------------------------------------------------------------------- ++// ++// Object ID (tracked) ++ ++typedef struct drmu_prop_object_s { ++ atomic_int ref_count; ++ uint32_t obj_id; ++ uint32_t prop_id; ++ uint32_t value; ++} drmu_prop_object_t; ++ ++uint32_t ++drmu_prop_object_value(const drmu_prop_object_t * const obj) ++{ ++ return !obj ? 0 : obj->value; ++} ++ ++void ++drmu_prop_object_unref(drmu_prop_object_t ** ppobj) ++{ ++ drmu_prop_object_t * const obj = *ppobj; ++ ++ if (!obj) ++ return; ++ *ppobj = NULL; ++ ++ if (atomic_fetch_sub(&obj->ref_count, 1) != 0) ++ return; ++ ++ free(obj); ++} ++ ++drmu_prop_object_t * ++drmu_prop_object_new_propinfo(drmu_env_t * const du, const uint32_t obj_id, const drmu_propinfo_t * const pi) ++{ ++ const uint64_t val = propinfo_val(pi); ++ const uint32_t prop_id = propinfo_prop_id(pi); ++ ++ if (obj_id == 0 || prop_id == 0) ++ return NULL; ++ ++ if ((val >> 32) != 0) { // We expect 32-bit values ++ drmu_err(du, "Bad object id value: %#"PRIx64, val); ++ return NULL; ++ } ++ else { ++ drmu_prop_object_t *const obj = calloc(1, sizeof(*obj)); ++ ++ if (obj == NULL) ++ return obj; ++ ++ obj->obj_id = obj_id; ++ obj->prop_id = prop_id; ++ obj->value = (uint32_t)val; ++ return obj; ++ } ++} ++ ++static void ++atomic_prop_object_unref(void * v) ++{ ++ drmu_prop_object_t * obj = v; ++ drmu_prop_object_unref(&obj); ++} ++static void ++atomic_prop_object_ref(void * v) ++{ ++ drmu_prop_object_t * obj = v; ++ atomic_fetch_add(&obj->ref_count, 1); ++} ++static void ++atomic_prop_object_commit(void * v, uint64_t val) ++{ ++ drmu_prop_object_t * obj = v; ++ obj->value = (uint32_t)val; ++} ++ ++int ++drmu_atomic_add_prop_object(drmu_atomic_t * const da, drmu_prop_object_t * obj, uint32_t val) ++{ ++ static const drmu_atomic_prop_fns_t fns = { ++ .ref = atomic_prop_object_ref, ++ .unref = atomic_prop_object_unref, ++ .commit = atomic_prop_object_commit, ++ }; ++ ++ return drmu_atomic_add_prop_generic(da, obj->obj_id, obj->prop_id, val, &fns, obj); ++} ++ ++//---------------------------------------------------------------------------- ++// ++// BO fns ++ ++enum drmu_bo_type_e { ++ BO_TYPE_NONE = 0, ++ BO_TYPE_FD, ++ BO_TYPE_DUMB ++}; ++ ++// BO handles come in 2 very distinct types: DUMB and FD ++// They need very different alloc & free but BO usage is the same for both ++// so it is better to have a single type. ++typedef struct drmu_bo_s { ++ // Arguably could be non-atomic for FD as then it is always protected by mutex ++ atomic_int ref_count; ++ struct drmu_env_s * du; ++ enum drmu_bo_type_e bo_type; ++ uint32_t handle; ++ ++ // FD only els - FD BOs need to be tracked globally ++ struct drmu_bo_s * next; ++ struct drmu_bo_s * prev; ++} drmu_bo_t; ++ ++typedef struct drmu_bo_env_s { ++ pthread_mutex_t lock; ++ drmu_bo_t * fd_head; ++} drmu_bo_env_t; ++ ++static int ++bo_close(drmu_env_t * const du, uint32_t * const ph) ++{ ++ struct drm_gem_close gem_close = {.handle = *ph}; ++ ++ if (gem_close.handle == 0) ++ return 0; ++ *ph = 0; ++ ++ return drmu_ioctl(du, DRM_IOCTL_GEM_CLOSE, &gem_close); ++} ++ ++// BOE lock expected ++static void ++bo_free_dumb(drmu_bo_t * const bo) ++{ ++ if (bo->handle != 0) { ++ drmu_env_t * const du = bo->du; ++ struct drm_mode_destroy_dumb destroy_env = {.handle = bo->handle}; ++ ++ if (drmu_ioctl(du, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_env) != 0) ++ drmu_warn(du, "%s: Failed to destroy dumb handle %d", __func__, bo->handle); ++ } ++ free(bo); ++} ++ ++static void ++bo_free_fd(drmu_bo_t * const bo) ++{ ++ if (bo->handle != 0) { ++ drmu_env_t * const du = bo->du; ++ drmu_bo_env_t *const boe = env_boe(du); ++ const uint32_t h = bo->handle; ++ ++ if (bo_close(du, &bo->handle) != 0) ++ drmu_warn(du, "%s: Failed to close BO handle %d", __func__, h); ++ if (bo->next != NULL) ++ bo->next->prev = bo->prev; ++ if (bo->prev != NULL) ++ bo->prev->next = bo->next; ++ else ++ boe->fd_head = bo->next; ++ } ++ free(bo); ++} ++ ++ ++void ++drmu_bo_unref(drmu_bo_t ** const ppbo) ++{ ++ drmu_bo_t * const bo = *ppbo; ++ ++ if (bo == NULL) ++ return; ++ *ppbo = NULL; ++ ++ switch (bo->bo_type) { ++ case BO_TYPE_FD: ++ { ++ drmu_bo_env_t * const boe = env_boe(bo->du); ++ ++ pthread_mutex_lock(&boe->lock); ++ if (atomic_fetch_sub(&bo->ref_count, 1) == 0) ++ bo_free_fd(bo); ++ pthread_mutex_unlock(&boe->lock); ++ break; ++ } ++ case BO_TYPE_DUMB: ++ if (atomic_fetch_sub(&bo->ref_count, 1) == 0) ++ bo_free_dumb(bo); ++ break; ++ case BO_TYPE_NONE: ++ default: ++ free(bo); ++ break; ++ } ++} ++ ++ ++drmu_bo_t * ++drmu_bo_ref(drmu_bo_t * const bo) ++{ ++ if (bo != NULL) ++ atomic_fetch_add(&bo->ref_count, 1); ++ return bo; ++} ++ ++static drmu_bo_t * ++bo_alloc(drmu_env_t *const du, enum drmu_bo_type_e bo_type) ++{ ++ drmu_bo_t * const bo = calloc(1, sizeof(*bo)); ++ if (bo == NULL) { ++ drmu_err(du, "Failed to alloc BO"); ++ return NULL; ++ } ++ ++ bo->du = du; ++ bo->bo_type = bo_type; ++ atomic_init(&bo->ref_count, 0); ++ return bo; ++} ++ ++drmu_bo_t * ++drmu_bo_new_fd(drmu_env_t *const du, const int fd) ++{ ++ drmu_bo_env_t * const boe = env_boe(du); ++ drmu_bo_t * bo = NULL; ++ struct drm_prime_handle ph = { .fd = fd }; ++ int rv; ++ ++ pthread_mutex_lock(&boe->lock); ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_PRIME_FD_TO_HANDLE, &ph)) != 0) { ++ drmu_err(du, "Failed to convert fd %d to BO: %s", __func__, fd, strerror(-rv)); ++ goto unlock; ++ } ++ ++ bo = boe->fd_head; ++ while (bo != NULL && bo->handle != ph.handle) ++ bo = bo->next; ++ ++ if (bo != NULL) { ++ drmu_bo_ref(bo); ++ } ++ else { ++ if ((bo = bo_alloc(du, BO_TYPE_FD)) == NULL) { ++ bo_close(du, &ph.handle); ++ } ++ else { ++ bo->handle = ph.handle; ++ ++ if ((bo->next = boe->fd_head) != NULL) ++ bo->next->prev = bo; ++ boe->fd_head = bo; ++ } ++ } ++ ++unlock: ++ pthread_mutex_unlock(&boe->lock); ++ return bo; ++} ++ ++// Updates the passed dumb structure with the results of creation ++drmu_bo_t * ++drmu_bo_new_dumb(drmu_env_t *const du, struct drm_mode_create_dumb * const d) ++{ ++ drmu_bo_t *bo = bo_alloc(du, BO_TYPE_DUMB); ++ int rv; ++ ++ if (bo == NULL) ++ return NULL; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_CREATE_DUMB, d)) != 0) ++ { ++ drmu_err(du, "%s: Create dumb %dx%dx%d failed: %s", __func__, ++ d->width, d->height, d->bpp, strerror(-rv)); ++ drmu_bo_unref(&bo); // After this point aux is bound to dfb and gets freed with it ++ return NULL; ++ } ++ ++ bo->handle = d->handle; ++ return bo; ++} ++ ++void ++drmu_bo_env_uninit(drmu_bo_env_t * const boe) ++{ ++ if (boe->fd_head != NULL) ++ drmu_warn(boe->fd_head->du, "%s: fd chain not null", __func__); ++ boe->fd_head = NULL; ++ pthread_mutex_destroy(&boe->lock); ++} ++ ++void ++drmu_bo_env_init(drmu_bo_env_t * boe) ++{ ++ boe->fd_head = NULL; ++ pthread_mutex_init(&boe->lock, NULL); ++} ++ ++//---------------------------------------------------------------------------- ++// ++// FB fns ++ ++typedef struct drmu_fb_s { ++ atomic_int ref_count; // 0 == 1 ref for ease of init ++ struct drmu_fb_s * prev; ++ struct drmu_fb_s * next; ++ ++ struct drmu_env_s * du; ++ ++ const struct drmu_fmt_info_s * fmt_info; ++ ++ struct drm_mode_fb_cmd2 fb; ++ ++ drmu_rect_t active; // Area that was asked for inside the buffer; pixels ++ drmu_rect_t crop; // Cropping inside that; fractional pels (16.16, 16.16) ++ ++ void * map_ptr; ++ size_t map_size; ++ size_t map_pitch; ++ ++ drmu_bo_t * bo_list[4]; ++ ++ drmu_color_encoding_t color_encoding; // Assumed to be constant strings that don't need freeing ++ drmu_color_range_t color_range; ++ drmu_colorspace_t colorspace; ++ const char * pixel_blend_mode; ++ drmu_chroma_siting_t chroma_siting; ++ drmu_isset_t hdr_metadata_isset; ++ struct hdr_output_metadata hdr_metadata; ++ ++ void * pre_delete_v; ++ drmu_fb_pre_delete_fn pre_delete_fn; ++ ++ void * on_delete_v; ++ drmu_fb_on_delete_fn on_delete_fn; ++ ++ // We pass a pointer to this to DRM which defines it as s32 so do not use ++ // int that might be s64. ++ int32_t fence_fd; ++} drmu_fb_t; ++ ++int ++drmu_fb_out_fence_wait(drmu_fb_t * const fb, const int timeout_ms) ++{ ++ struct pollfd pf; ++ int rv; ++ ++ if (fb->fence_fd == -1) ++ return -EINVAL; ++ ++ do { ++ pf.fd = fb->fence_fd; ++ pf.events = POLLIN; ++ pf.revents = 0; ++ ++ rv = poll(&pf, 1, timeout_ms); ++ if (rv >= 0) ++ break; ++ ++ rv = -errno; ++ } while (rv == -EINTR); ++ ++ if (rv == 0) ++ return 0; ++ ++ // Both on error & success close the fd ++ close(fb->fence_fd); ++ fb->fence_fd = -1; ++ return rv; ++} ++ ++void ++drmu_fb_int_free(drmu_fb_t * const dfb) ++{ ++ drmu_env_t * const du = dfb->du; ++ unsigned int i; ++ ++ if (dfb->pre_delete_fn && dfb->pre_delete_fn(dfb, dfb->pre_delete_v) != 0) ++ return; ++ ++ // * If we implement callbacks this logic will want revision ++ if (dfb->fence_fd != -1) { ++ drmu_warn(du, "Out fence still set on FB on delete"); ++ if (drmu_fb_out_fence_wait(dfb, 500) == 0) { ++ drmu_err(du, "Out fence stuck in FB free"); ++ close(dfb->fence_fd); ++ } ++ } ++ ++ if (dfb->fb.fb_id != 0) ++ drmu_ioctl(du, DRM_IOCTL_MODE_RMFB, &dfb->fb.fb_id); ++ ++ if (dfb->map_ptr != NULL && dfb->map_ptr != MAP_FAILED) ++ munmap(dfb->map_ptr, dfb->map_size); ++ ++ for (i = 0; i != 4; ++i) ++ drmu_bo_unref(dfb->bo_list + i); ++ ++ // Call on_delete last so we have stopped using anything that might be ++ // freed by it ++ { ++ void * const v = dfb->on_delete_v; ++ const drmu_fb_on_delete_fn fn = dfb->on_delete_fn; ++ ++ free(dfb); ++ ++ if (fn) ++ fn(v); ++ } ++} ++ ++void ++drmu_fb_unref(drmu_fb_t ** const ppdfb) ++{ ++ drmu_fb_t * const dfb = *ppdfb; ++ ++ if (dfb == NULL) ++ return; ++ *ppdfb = NULL; ++ ++ if (atomic_fetch_sub(&dfb->ref_count, 1) > 0) ++ return; ++ ++ drmu_fb_int_free(dfb); ++} ++ ++static void ++atomic_prop_fb_unref_cb(void * v) ++{ ++ drmu_fb_t * dfb = v; ++ drmu_fb_unref(&dfb); ++} ++ ++drmu_fb_t * ++drmu_fb_ref(drmu_fb_t * const dfb) ++{ ++ if (dfb != NULL) ++ atomic_fetch_add(&dfb->ref_count, 1); ++ return dfb; ++} ++ ++static void ++atomic_prop_fb_ref_cb(void * v) ++{ ++ drmu_fb_ref(v); ++} ++ ++// Beware: used by pool fns ++void ++drmu_fb_pre_delete_set(drmu_fb_t *const dfb, drmu_fb_pre_delete_fn fn, void * v) ++{ ++ dfb->pre_delete_fn = fn; ++ dfb->pre_delete_v = v; ++} ++ ++void ++drmu_fb_pre_delete_unset(drmu_fb_t *const dfb) ++{ ++ dfb->pre_delete_fn = (drmu_fb_pre_delete_fn)0; ++ dfb->pre_delete_v = NULL; ++} ++ ++int ++drmu_fb_pixel_blend_mode_set(drmu_fb_t *const dfb, const char * const mode) ++{ ++ dfb->pixel_blend_mode = mode; ++ return 0; ++} ++ ++uint32_t ++drmu_fb_pitch(const drmu_fb_t *const dfb, const unsigned int layer) ++{ ++ return layer >= 4 ? 0 : dfb->fb.pitches[layer]; ++} ++ ++uint32_t ++drmu_fb_pitch2(const drmu_fb_t *const dfb, const unsigned int layer) ++{ ++ if (layer < 4){ ++ const uint64_t m = dfb->fb.modifier[layer]; ++ const uint64_t s2 = fourcc_mod_broadcom_param(m); ++ ++ // No good masks to check modifier so check if we convert back it matches ++ if (m != 0 && m != DRM_FORMAT_MOD_INVALID && ++ DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(s2) == m) ++ return (uint32_t)s2; ++ } ++ return 0; ++} ++ ++void * ++drmu_fb_data(const drmu_fb_t *const dfb, const unsigned int layer) ++{ ++ return (layer >= 4 || dfb->map_ptr == NULL) ? NULL : (uint8_t * )dfb->map_ptr + dfb->fb.offsets[layer]; ++} ++ ++uint32_t ++drmu_fb_width(const drmu_fb_t *const dfb) ++{ ++ return dfb->fb.width; ++} ++ ++uint32_t ++drmu_fb_height(const drmu_fb_t *const dfb) ++{ ++ return dfb->fb.height; ++} ++ ++// Set cropping (fractional) - x, y, relative to active x, y (and must be +ve) ++int ++drmu_fb_crop_frac_set(drmu_fb_t *const dfb, drmu_rect_t crop_frac) ++{ ++ // Sanity check ++ if (crop_frac.x + crop_frac.w > (dfb->active.w << 16) || ++ crop_frac.y + crop_frac.h > (dfb->active.h << 16)) ++ return -EINVAL; ++ ++ dfb->crop = (drmu_rect_t){ ++ .x = crop_frac.x, ++ .y = crop_frac.y, ++ .w = crop_frac.w, ++ .h = crop_frac.h ++ }; ++ return 0; ++} ++ ++drmu_rect_t ++drmu_fb_crop_frac(const drmu_fb_t *const dfb) ++{ ++ return dfb->crop; ++} ++ ++drmu_rect_t ++drmu_fb_active(const drmu_fb_t *const dfb) ++{ ++ return dfb->active; ++} ++ ++ ++// active is in pixels ++void ++drmu_fb_int_fmt_size_set(drmu_fb_t *const dfb, uint32_t fmt, uint32_t w, uint32_t h, const drmu_rect_t active) ++{ ++ dfb->fmt_info = drmu_fmt_info_find_fmt(fmt); ++ dfb->fb.pixel_format = fmt; ++ dfb->fb.width = w; ++ dfb->fb.height = h; ++ dfb->active = active; ++ dfb->crop = drmu_rect_shl16(active); ++ dfb->chroma_siting = drmu_fmt_info_chroma_siting(dfb->fmt_info); ++} ++ ++void ++drmu_fb_int_color_set(drmu_fb_t *const dfb, const drmu_color_encoding_t enc, const drmu_color_range_t range, const drmu_colorspace_t space) ++{ ++ dfb->color_encoding = enc; ++ dfb->color_range = range; ++ dfb->colorspace = space; ++} ++ ++void ++drmu_fb_int_chroma_siting_set(drmu_fb_t *const dfb, const drmu_chroma_siting_t siting) ++{ ++ dfb->chroma_siting = siting; ++} ++ ++void ++drmu_fb_int_on_delete_set(drmu_fb_t *const dfb, drmu_fb_on_delete_fn fn, void * v) ++{ ++ dfb->on_delete_fn = fn; ++ dfb->on_delete_v = v; ++} ++ ++void ++drmu_fb_int_bo_set(drmu_fb_t *const dfb, unsigned int i, drmu_bo_t * const bo) ++{ ++ dfb->bo_list[i] = bo; ++} ++ ++void ++drmu_fb_int_layer_mod_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset, uint64_t modifier) ++{ ++ dfb->fb.handles[i] = dfb->bo_list[obj_idx]->handle; ++ dfb->fb.pitches[i] = pitch; ++ dfb->fb.offsets[i] = offset; ++ // We should be able to have "invalid" modifiers and not set the flag ++ // but that produces EINVAL - so don't do that ++ dfb->fb.modifier[i] = (modifier == DRM_FORMAT_MOD_INVALID) ? 0 : modifier; ++} ++ ++void ++drmu_fb_int_layer_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset) ++{ ++ drmu_fb_int_layer_mod_set(dfb, i, obj_idx, pitch, offset, DRM_FORMAT_MOD_INVALID); ++} ++ ++int ++drmu_fb_int_make(drmu_fb_t *const dfb) ++{ ++ drmu_env_t * du = dfb->du; ++ int rv; ++ ++ dfb->fb.flags = (dfb->fb.modifier[0] == DRM_FORMAT_MOD_INVALID || dfb->fb.modifier[0] == 0) ? 0 : DRM_MODE_FB_MODIFIERS; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_ADDFB2, &dfb->fb)) != 0) ++ drmu_err(du, "AddFB2 failed: %s", strerror(-rv)); ++ return rv; ++} ++ ++void ++drmu_fb_hdr_metadata_set(drmu_fb_t *const dfb, const struct hdr_output_metadata * meta) ++{ ++ if (meta == NULL) { ++ dfb->hdr_metadata_isset = DRMU_ISSET_NULL; ++ } ++ else { ++ dfb->hdr_metadata_isset = DRMU_ISSET_SET; ++ dfb->hdr_metadata = *meta; ++ } ++} ++ ++drmu_isset_t ++drmu_fb_hdr_metadata_isset(const drmu_fb_t *const dfb) ++{ ++ return dfb->hdr_metadata_isset; ++} ++ ++const struct hdr_output_metadata * ++drmu_fb_hdr_metadata_get(const drmu_fb_t *const dfb) ++{ ++ return dfb->hdr_metadata_isset == DRMU_ISSET_SET ? &dfb->hdr_metadata : NULL; ++} ++ ++drmu_colorspace_t ++drmu_fb_colorspace_get(const drmu_fb_t * const dfb) ++{ ++ return dfb->colorspace; ++} ++ ++const char * ++drmu_color_range_to_broadcast_rgb(const drmu_color_range_t range) ++{ ++ if (!drmu_color_range_is_set(range)) ++ return DRMU_BROADCAST_RGB_UNSET; ++ else if (strcmp(range, DRMU_COLOR_RANGE_YCBCR_FULL_RANGE) == 0) ++ return DRMU_BROADCAST_RGB_FULL; ++ else if (strcmp(range, DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE) == 0) ++ return DRMU_BROADCAST_RGB_LIMITED_16_235; ++ return NULL; ++} ++ ++drmu_color_range_t ++drmu_fb_color_range_get(const drmu_fb_t * const dfb) ++{ ++ return dfb->color_range; ++} ++ ++const struct drmu_fmt_info_s * ++drmu_fb_format_info_get(const drmu_fb_t * const dfb) ++{ ++ return dfb->fmt_info; ++} ++ ++drmu_fb_t * ++drmu_fb_int_alloc(drmu_env_t * const du) ++{ ++ drmu_fb_t * const dfb = calloc(1, sizeof(*dfb)); ++ if (dfb == NULL) ++ return NULL; ++ ++ dfb->du = du; ++ dfb->chroma_siting = DRMU_CHROMA_SITING_UNSPECIFIED; ++ dfb->fence_fd = -1; ++ return dfb; ++} ++ ++// Bits per pixel on plane 0 ++unsigned int ++drmu_fb_pixel_bits(const drmu_fb_t * const dfb) ++{ ++ return drmu_fmt_info_pixel_bits(dfb->fmt_info); ++} ++ ++uint32_t ++drmu_fb_pixel_format(const drmu_fb_t * const dfb) ++{ ++ return dfb->fb.pixel_format; ++} ++ ++uint64_t ++drmu_fb_modifier(const drmu_fb_t * const dfb, const unsigned int plane) ++{ ++ return plane >= 4 ? DRM_FORMAT_MOD_INVALID : dfb->fb.modifier[plane]; ++} ++ ++ ++// Writeback fence ++// Must be unset before set again ++// (This is as a handy hint that you must wait for the previous fence ++// to go ready before you set a new one) ++static int ++atomic_fb_add_out_fence(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb) ++{ ++ static const drmu_atomic_prop_fns_t fns = { ++ .ref = atomic_prop_fb_ref_cb, ++ .unref = atomic_prop_fb_unref_cb, ++ .commit = drmu_prop_fn_null_commit, ++ }; ++ ++ if (!dfb) ++ return -EINVAL; ++ if (dfb->fence_fd != -1) ++ return -EBUSY; ++ ++ return drmu_atomic_add_prop_generic(da, obj_id, prop_id, (uintptr_t)&dfb->fence_fd, &fns, dfb); ++} ++ ++// For allocation purposes given fb_pixel bits how tall ++// does the frame have to be to fit all planes if constant width ++static unsigned int ++fb_total_height(const drmu_fb_t * const dfb, const unsigned int h) ++{ ++ unsigned int i; ++ const drmu_fmt_info_t *const f = dfb->fmt_info; ++ unsigned int t = 0; ++ unsigned int h0 = h * drmu_fmt_info_wdiv(f, 0); ++ const unsigned int c = drmu_fmt_info_plane_count(f); ++ ++ for (i = 0; i != c; ++i) ++ t += h0 / (drmu_fmt_info_hdiv(f, i) * drmu_fmt_info_wdiv(f, i)); ++ ++ return t; ++} ++ ++static void ++fb_pitches_set_mod(drmu_fb_t * const dfb, uint64_t mod) ++{ ++ const drmu_fmt_info_t *const f = dfb->fmt_info; ++ const uint32_t pitch0 = dfb->map_pitch * drmu_fmt_info_wdiv(f, 0); ++ const uint32_t h = drmu_fb_height(dfb); ++ const unsigned int c = drmu_fmt_info_plane_count(f); ++ uint32_t t = 0; ++ unsigned int i; ++ ++ // This should be true for anything we've allocated ++ if (mod == DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0)) { ++ // Cope with the joy that is sand ++ mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(h * 3/2); ++ drmu_fb_int_layer_mod_set(dfb, 0, 0, dfb->map_pitch, 0, mod); ++ drmu_fb_int_layer_mod_set(dfb, 1, 0, dfb->map_pitch, h * 128, mod); ++ return; ++ } ++ ++ for (i = 0; i != c; ++i) { ++ const unsigned int wdiv = drmu_fmt_info_wdiv(f, i); ++ drmu_fb_int_layer_mod_set(dfb, i, 0, pitch0 / wdiv, t, mod); ++ t += (pitch0 * h) / (drmu_fmt_info_hdiv(f, i) * wdiv); ++ } ++} ++ ++drmu_fb_t * ++drmu_fb_new_dumb_mod(drmu_env_t * const du, uint32_t w, uint32_t h, ++ const uint32_t format, const uint64_t mod) ++{ ++ drmu_fb_t * const dfb = drmu_fb_int_alloc(du); ++ uint32_t bpp; ++ int rv; ++ uint32_t w2; ++ const uint32_t s30_cw = 128 / 4 * 3; ++ ++ if (dfb == NULL) { ++ drmu_err(du, "%s: Alloc failure", __func__); ++ return NULL; ++ } ++ ++ if (mod != DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0)) ++ w2 = (w + 15) & ~15; ++ else if (format == DRM_FORMAT_NV12) ++ w2 = (w + 127) & ~127; ++ else if (format == DRM_FORMAT_P030) ++ w2 = ((w + s30_cw - 1) / s30_cw) * s30_cw; ++ else { ++ // Unknown form of sand128 ++ drmu_err(du, "Sand modifier on unexpected format"); ++ goto fail; ++ } ++ ++ drmu_fb_int_fmt_size_set(dfb, format, w2, (h + 15) & ~15, drmu_rect_wh(w, h)); ++ ++ if ((bpp = drmu_fb_pixel_bits(dfb)) == 0) { ++ drmu_err(du, "%s: Unexpected format %#x", __func__, format); ++ goto fail; ++ } ++ ++ { ++ struct drm_mode_create_dumb dumb = { ++ .height = fb_total_height(dfb, dfb->fb.height), ++ .width = dfb->fb.width / drmu_fmt_info_wdiv(dfb->fmt_info, 0), ++ .bpp = bpp ++ }; ++ ++ drmu_bo_t * bo = drmu_bo_new_dumb(du, &dumb); ++ if (bo == NULL) ++ goto fail; ++ drmu_fb_int_bo_set(dfb, 0, bo); ++ ++ dfb->map_pitch = dumb.pitch; ++ dfb->map_size = (size_t)dumb.size; ++ } ++ ++ { ++ struct drm_mode_map_dumb map_dumb = { ++ .handle = dfb->bo_list[0]->handle ++ }; ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb)) != 0) ++ { ++ drmu_err(du, "%s: map dumb failed: %s", __func__, strerror(-rv)); ++ goto fail; ++ } ++ ++ if ((dfb->map_ptr = mmap(NULL, dfb->map_size, ++ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, ++ drmu_fd(du), map_dumb.offset)) == MAP_FAILED) { ++ drmu_err(du, "%s: mmap failed (size=%zd, fd=%d, off=%#"PRIx64"): %s", __func__, ++ dfb->map_size, drmu_fd(du), map_dumb.offset, strerror(errno)); ++ goto fail; ++ } ++ } ++ ++ fb_pitches_set_mod(dfb, mod); ++ ++ if (drmu_fb_int_make(dfb)) ++ goto fail; ++ ++ drmu_debug(du, "Create dumb %p %s %dx%d / %dx%d size: %zd", dfb, ++ drmu_log_fourcc(format), dfb->fb.width, dfb->fb.height, dfb->active.w, dfb->active.h, dfb->map_size); ++ return dfb; ++ ++fail: ++ drmu_fb_int_free(dfb); ++ return NULL; ++} ++ ++drmu_fb_t * ++drmu_fb_new_dumb(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format) ++{ ++ return drmu_fb_new_dumb_mod(du, w, h, format, DRM_FORMAT_MOD_INVALID); ++} ++ ++static int ++fb_try_reuse(drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format) ++{ ++ if (w > dfb->fb.width || h > dfb->fb.height || format != dfb->fb.pixel_format) ++ return 0; ++ ++ dfb->active = drmu_rect_wh(w, h); ++ dfb->crop = drmu_rect_shl16(dfb->active); ++ return 1; ++} ++ ++drmu_fb_t * ++drmu_fb_realloc_dumb(drmu_env_t * const du, drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format) ++{ ++ if (dfb == NULL) ++ return drmu_fb_new_dumb(du, w, h, format); ++ ++ if (fb_try_reuse(dfb, w, h, format)) ++ return dfb; ++ ++ drmu_fb_unref(&dfb); ++ return drmu_fb_new_dumb(du, w, h, format); ++} ++ ++static void ++atomic_prop_fb_unref(void * v) ++{ ++ drmu_fb_t * fb = v; ++ drmu_fb_unref(&fb); ++} ++ ++static void ++atomic_prop_fb_ref(void * v) ++{ ++ drmu_fb_ref(v); ++} ++ ++int ++drmu_atomic_add_prop_fb(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb) ++{ ++ int rv; ++ static const drmu_atomic_prop_fns_t fns = { ++ .ref = atomic_prop_fb_ref, ++ .unref = atomic_prop_fb_unref, ++ .commit = drmu_prop_fn_null_commit, ++ }; ++ ++ if (dfb == NULL) ++ return drmu_atomic_add_prop_value(da, obj_id, prop_id, 0); ++ ++ rv = drmu_atomic_add_prop_generic(da, obj_id, prop_id, dfb->fb.fb_id, &fns, dfb); ++ if (rv != 0) ++ drmu_warn(drmu_atomic_env(da), "%s: Failed to add fb obj_id=%#x, prop_id=%#x: %s", __func__, obj_id, prop_id, strerror(-rv)); ++ ++ return rv; ++} ++ ++//---------------------------------------------------------------------------- ++// ++// props fns (internal) ++ ++typedef struct drmu_props_s { ++ struct drmu_env_s * du; ++ unsigned int n; ++ drmu_propinfo_t * info; ++ const drmu_propinfo_t ** by_name; ++} drmu_props_t; ++ ++static void ++props_free(drmu_props_t * const props) ++{ ++ free(props->info); // As yet nothing else is allocated off this ++ free(props->by_name); ++ free(props); ++} ++ ++static const drmu_propinfo_t * ++props_name_to_propinfo(const drmu_props_t * const props, const char * const name) ++{ ++ unsigned int i = props->n / 2; ++ unsigned int a = 0; ++ unsigned int b = props->n; ++ ++ while (a < b) { ++ const int r = strcmp(name, props->by_name[i]->prop.name); ++ ++ if (r == 0) ++ return props->by_name[i]; ++ ++ if (r < 0) { ++ b = i; ++ i = (i + a) / 2; ++ } else { ++ a = i + 1; ++ i = (i + b) / 2; ++ } ++ } ++ return NULL; ++} ++ ++static uint32_t ++props_name_to_id(const drmu_props_t * const props, const char * const name) ++{ ++ return propinfo_prop_id(props_name_to_propinfo(props, name)); ++} ++ ++// Data must be freed later ++static int ++props_name_get_blob(const drmu_props_t * const props, const char * const name, void ** const ppdata, size_t * const plen) ++{ ++ const drmu_propinfo_t * const pinfo = props_name_to_propinfo(props, name); ++ ++ *ppdata = 0; ++ *plen = 0; ++ ++ if (!pinfo) ++ return -ENOENT; ++ if ((pinfo->prop.flags & DRM_MODE_PROP_BLOB) == 0) ++ return -EINVAL; ++ ++ return blob_data_read(props->du, (uint32_t)pinfo->val, ppdata, plen); ++} ++ ++#if TRACE_PROP_NEW ++static void ++props_dump(const drmu_props_t * const props) ++{ ++ if (props != NULL) { ++ unsigned int i; ++ drmu_env_t * const du = props->du; ++ ++ for (i = 0; i != props->n; ++i) { ++ const drmu_propinfo_t * const inf = props->info + i; ++ const struct drm_mode_get_property * const p = &inf->prop; ++ char flagbuf[256]; ++ ++ flagbuf[0] = 0; ++ if (p->flags & DRM_MODE_PROP_PENDING) ++ strcat(flagbuf, ":pending"); ++ if (p->flags & DRM_MODE_PROP_RANGE) ++ strcat(flagbuf, ":urange"); ++ if (p->flags & DRM_MODE_PROP_IMMUTABLE) ++ strcat(flagbuf, ":immutable"); ++ if (p->flags & DRM_MODE_PROP_ENUM) ++ strcat(flagbuf, ":enum"); ++ if (p->flags & DRM_MODE_PROP_BLOB) ++ strcat(flagbuf, ":blob"); ++ if (p->flags & DRM_MODE_PROP_BITMASK) ++ strcat(flagbuf, ":bitmask"); ++ if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_OBJECT) ++ strcat(flagbuf, ":object"); ++ else if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_SIGNED_RANGE) ++ strcat(flagbuf, ":srange"); ++ else if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) != 0) ++ strcat(flagbuf, ":?xtype?"); ++ if (p->flags & ~(DRM_MODE_PROP_LEGACY_TYPE | ++ DRM_MODE_PROP_EXTENDED_TYPE | ++ DRM_MODE_PROP_PENDING | ++ DRM_MODE_PROP_IMMUTABLE | ++ DRM_MODE_PROP_ATOMIC)) ++ strcat(flagbuf, ":?other?"); ++ if (p->flags & DRM_MODE_PROP_ATOMIC) ++ strcat(flagbuf, ":atomic"); ++ ++ ++ drmu_info(du, "Prop%02d/%02d: %#-4x %-16s val=%#-4"PRIx64" flags=%#x%s, values=%d, blobs=%d", ++ i, props->n, p->prop_id, ++ p->name, inf->val, ++ p->flags, flagbuf, ++ p->count_values, ++ p->count_enum_blobs); ++ } ++ } ++} ++#endif ++ ++static int ++props_qsort_by_name_cb(const void * va, const void * vb) ++{ ++ const drmu_propinfo_t * const a = *(drmu_propinfo_t **)va; ++ const drmu_propinfo_t * const b = *(drmu_propinfo_t **)vb; ++ return strcmp(a->prop.name, b->prop.name); ++} ++ ++// At the moment we don't need / want to fill in the values / blob arrays ++// we just want the name - will get the extra info if we need it ++static int ++propinfo_fill(drmu_env_t * const du, drmu_propinfo_t * const inf, uint32_t propid, uint64_t val) ++{ ++ int rv; ++ ++ inf->val = val; ++ inf->prop.prop_id = propid; ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &inf->prop)) != 0) ++ drmu_err(du, "Failed to get property %d: %s", propid, strerror(-rv)); ++ return rv; ++} ++ ++static int ++props_get_properties(drmu_env_t * const du, const uint32_t objid, const uint32_t objtype, ++ uint32_t ** const ppPropids, uint64_t ** const ppValues) ++{ ++ struct drm_mode_obj_get_properties obj_props = { ++ .obj_id = objid, ++ .obj_type = objtype, ++ }; ++ uint64_t * values = NULL; ++ uint32_t * propids = NULL; ++ unsigned int n = 0; ++ int rv; ++ ++ for (;;) { ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &obj_props)) != 0) { ++ drmu_err(du, "drmModeObjectGetProperties failed: %s", strerror(-rv)); ++ goto fail; ++ } ++ ++ if (obj_props.count_props <= n) ++ break; ++ ++ free(values); ++ values = NULL; ++ free(propids); ++ propids = NULL; ++ n = obj_props.count_props; ++ if ((values = malloc(n * sizeof(*values))) == NULL || ++ (propids = malloc(n * sizeof(*propids))) == NULL) { ++ drmu_err(du, "obj/value array alloc failed"); ++ rv = -ENOMEM; ++ goto fail; ++ } ++ obj_props.prop_values_ptr = (uintptr_t)values; ++ obj_props.props_ptr = (uintptr_t)propids; ++ } ++ ++ *ppValues = values; ++ *ppPropids = propids; ++ return (int)n; ++ ++fail: ++ free(values); ++ free(propids); ++ *ppPropids = NULL; ++ *ppValues = NULL; ++ return rv; ++} ++ ++static drmu_props_t * ++props_new(drmu_env_t * const du, const uint32_t objid, const uint32_t objtype) ++{ ++ drmu_props_t * const props = calloc(1, sizeof(*props)); ++ int rv; ++ uint64_t * values; ++ uint32_t * propids; ++ ++ if (props == NULL) { ++ drmu_err(du, "%s: Failed struct alloc", __func__); ++ return NULL; ++ } ++ props->du = du; ++ ++ if ((rv = props_get_properties(du, objid, objtype, &propids, &values)) < 0) ++ goto fail; ++ ++ props->n = rv; ++ if ((props->info = calloc(props->n, sizeof(*props->info))) == NULL || ++ (props->by_name = malloc(props->n * sizeof(*props->by_name))) == NULL) { ++ drmu_err(du, "info/name array alloc failed"); ++ goto fail; ++ } ++ ++ for (unsigned int i = 0; i < props->n; ++i) { ++ drmu_propinfo_t * const inf = props->info + i; ++ ++ props->by_name[i] = inf; ++ if ((rv = propinfo_fill(du, inf, propids[i], values[i])) != 0) ++ goto fail; ++ } ++ ++ // Sort into name order for faster lookup ++ qsort(props->by_name, props->n, sizeof(*props->by_name), props_qsort_by_name_cb); ++ ++ free(values); ++ free(propids); ++ return props; ++ ++fail: ++ props_free(props); ++ free(values); ++ free(propids); ++ return NULL; ++} ++ ++int ++drmu_atomic_obj_add_snapshot(drmu_atomic_t * const da, const uint32_t objid, const uint32_t objtype) ++{ ++#if 0 ++ drmu_env_t * const du = drmu_atomic_env(da); ++ drmu_props_t * props = NULL; ++ unsigned int i; ++ int rv; ++ ++ if (!du) ++ return -EINVAL; ++ ++ if ((props = props_new(du, objid, objtype)) == NULL) ++ return -ENOENT; ++ ++ for (i = 0; i != props->n; ++i) { ++ if ((props->info[i].prop.flags & (DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ATOMIC)) != 0 || props->info[i].prop.prop_id == 2) ++ continue; ++ if ((rv = drmu_atomic_add_prop_generic(da, objid, props->info[i].prop.prop_id, props->info[i].val, 0, 0, NULL)) != 0) ++ goto fail; ++ } ++ rv = 0; ++ ++fail: ++ props_free(props); ++ return rv; ++#else ++ uint64_t * values; ++ uint32_t * propids; ++ int rv; ++ unsigned int i, n; ++ drmu_env_t * const du = drmu_atomic_env(da); ++ ++ if (!du) ++ return -EINVAL; ++ ++ if ((rv = props_get_properties(du, objid, objtype, &propids, &values)) < 0) ++ goto fail; ++ n = rv; ++ ++ for (i = 0; i != n; ++i) { ++ if ((rv = drmu_atomic_add_prop_value(da, objid, propids[i], values[i])) != 0) ++ goto fail; ++ } ++ ++fail: ++ free(values); ++ free(propids); ++ return rv; ++#endif ++} ++ ++static int ++drmu_atomic_props_add_save(drmu_atomic_t * const da, const uint32_t objid, const drmu_props_t * props) ++{ ++ unsigned int i; ++ int rv; ++ drmu_env_t * const du = drmu_atomic_env(da); ++ ++ if (props == NULL) ++ return 0; ++ if (du == NULL) ++ return -EINVAL; ++ ++ for (i = 0; i != props->n; ++i) { ++ if ((props->info[i].prop.flags & DRM_MODE_PROP_IMMUTABLE) != 0) ++ continue; ++ ++ // Blobs, if set, are prone to running out of refs and vanishing, so we ++ // must copy. If we fail to copy the blob for any reason drop through ++ // to the generic add and hope that that will do ++ if ((props->info[i].prop.flags & DRM_MODE_PROP_BLOB) != 0 && props->info[i].val != 0) { ++ drmu_blob_t * b = drmu_blob_copy_id(du, (uint32_t)props->info[i].val); ++ if (b != NULL) { ++ rv = drmu_atomic_add_prop_blob(da, objid, props->info[i].prop.prop_id, b); ++ drmu_blob_unref(&b); ++ if (rv == 0) ++ continue; ++ } ++ } ++ ++ // Generic value ++ if ((rv = drmu_atomic_add_prop_value(da, objid, props->info[i].prop.prop_id, props->info[i].val)) != 0) ++ return rv; ++ } ++ return 0; ++} ++ ++//---------------------------------------------------------------------------- ++// ++// CRTC fns ++ ++typedef struct drmu_crtc_s { ++ struct drmu_env_s * du; ++ int crtc_idx; ++ ++ atomic_int ref_count; ++ bool saved; ++ ++ struct drm_mode_crtc crtc; ++ ++ struct { ++ drmu_prop_range_t * active; ++ uint32_t mode_id; ++ } pid; ++ ++ drmu_blob_t * mode_id_blob; ++ ++} drmu_crtc_t; ++ ++static void ++free_crtc(drmu_crtc_t * const dc) ++{ ++ drmu_blob_unref(&dc->mode_id_blob); ++ free(dc); ++} ++ ++static void ++crtc_uninit(drmu_crtc_t * const dc) ++{ ++ (void)dc; ++} ++ ++ ++#if 0 ++// Set misc derived vars from mode ++static void ++crtc_mode_set_vars(drmu_crtc_t * const dc) ++{ ++ switch (dc->crtc.mode.flags & DRM_MODE_FLAG_PIC_AR_MASK) { ++ case DRM_MODE_FLAG_PIC_AR_4_3: ++ dc->par = (drmu_ufrac_t){4,3}; ++ break; ++ case DRM_MODE_FLAG_PIC_AR_16_9: ++ dc->par = (drmu_ufrac_t){16,9}; ++ break; ++ case DRM_MODE_FLAG_PIC_AR_64_27: ++ dc->par = (drmu_ufrac_t){64,27}; ++ break; ++ case DRM_MODE_FLAG_PIC_AR_256_135: ++ dc->par = (drmu_ufrac_t){256,135}; ++ break; ++ default: ++ case DRM_MODE_FLAG_PIC_AR_NONE: ++ dc->par = (drmu_ufrac_t){0,0}; ++ break; ++ } ++ ++ if (dc->par.den == 0) { ++ // Assume 1:1 ++ dc->sar = (drmu_ufrac_t){1,1}; ++ } ++ else { ++ dc->sar = drmu_ufrac_reduce((drmu_ufrac_t) {dc->par.num * dc->crtc.mode.vdisplay, dc->par.den * dc->crtc.mode.hdisplay}); ++ } ++} ++ ++static int ++atomic_crtc_bpc_set(drmu_atomic_t * const da, drmu_crtc_t * const dc, ++ const char * const colorspace, ++ const unsigned int max_bpc) ++{ ++ const uint32_t con_id = dc->con->connector_id; ++ int rv = 0; ++ ++ if (!dc->du->modeset_allow) ++ return 0; ++ ++ if ((dc->pid.colorspace && ++ (rv = drmu_atomic_add_prop_enum(da, con_id, dc->pid.colorspace, colorspace)) != 0) || ++ (dc->pid.max_bpc && ++ (rv = drmu_atomic_add_prop_range(da, con_id, dc->pid.max_bpc, max_bpc)) != 0)) ++ return rv; ++ return 0; ++} ++ ++ ++static int ++atomic_crtc_hi_bpc_set(drmu_atomic_t * const da, drmu_crtc_t * const dc) ++{ ++ return atomic_crtc_bpc_set(da, dc, "BT2020_YCC", 12); ++} ++#endif ++ ++void ++drmu_crtc_delete(drmu_crtc_t ** ppdc) ++{ ++ drmu_crtc_t * const dc = * ppdc; ++ ++ if (dc == NULL) ++ return; ++ *ppdc = NULL; ++ ++ free_crtc(dc); ++} ++ ++drmu_env_t * ++drmu_crtc_env(const drmu_crtc_t * const dc) ++{ ++ return dc == NULL ? NULL : dc->du; ++} ++ ++uint32_t ++drmu_crtc_id(const drmu_crtc_t * const dc) ++{ ++ return dc->crtc.crtc_id; ++} ++ ++int ++drmu_crtc_idx(const drmu_crtc_t * const dc) ++{ ++ return dc->crtc_idx; ++} ++ ++static int ++crtc_init(drmu_env_t * const du, drmu_crtc_t * const dc, const unsigned int idx, const uint32_t crtc_id) ++{ ++ int rv; ++ drmu_props_t * props; ++ ++ memset(dc, 0, sizeof(*dc)); ++ dc->du = du; ++ dc->crtc_idx = idx; ++ dc->crtc.crtc_id = crtc_id; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETCRTC, &dc->crtc)) != 0) { ++ drmu_err(du, "Failed to get crtc id %d: %s", crtc_id, strerror(-rv)); ++ return rv; ++ } ++ ++ props = props_new(du, dc->crtc.crtc_id, DRM_MODE_OBJECT_CRTC); ++ ++ if (props != NULL) { ++#if TRACE_PROP_NEW ++ drmu_info(du, "CRTC id=%#x, idx=%d:", dc->crtc.crtc_id, dc->crtc_idx); ++ props_dump(props); ++#endif ++ dc->pid.mode_id = props_name_to_id(props, "MODE_ID"); ++ dc->pid.active = drmu_prop_range_new(du, props_name_to_id(props, "ACTIVE")); ++ ++ props_free(props); ++ } ++ ++ return 0; ++} ++ ++static drmu_ufrac_t ++modeinfo_par(const struct drm_mode_modeinfo * const mode) ++{ ++ switch (mode->flags & DRM_MODE_FLAG_PIC_AR_MASK) { ++ case DRM_MODE_FLAG_PIC_AR_4_3: ++ return (drmu_ufrac_t){4,3}; ++ case DRM_MODE_FLAG_PIC_AR_16_9: ++ return (drmu_ufrac_t){16,9}; ++ case DRM_MODE_FLAG_PIC_AR_64_27: ++ return (drmu_ufrac_t){64,27}; ++ case DRM_MODE_FLAG_PIC_AR_256_135: ++ return (drmu_ufrac_t){256,135}; ++ default: ++ case DRM_MODE_FLAG_PIC_AR_NONE: ++ break; ++ } ++ return (drmu_ufrac_t){0,0}; ++} ++ ++static drmu_mode_simple_params_t ++modeinfo_simple_params(const struct drm_mode_modeinfo * const mode) ++{ ++ if (!mode) ++ return (drmu_mode_simple_params_t){ 0 }; ++ else { ++ drmu_mode_simple_params_t rv = { ++ .width = mode->hdisplay, ++ .height = mode->vdisplay, ++ .hz_x_1000 = (uint32_t)(((uint64_t)mode->clock * 1000000) / (mode->htotal * mode->vtotal)), ++ .par = modeinfo_par(mode), ++ .sar = {1, 1}, ++ .type = mode->type, ++ .flags = mode->flags, ++ }; ++ ++ if (rv.par.den != 0) ++ rv.sar = drmu_ufrac_reduce((drmu_ufrac_t) {rv.par.num * rv.height, rv.par.den * rv.width}); ++ ++ return rv; ++ } ++} ++ ++drmu_crtc_t * ++drmu_env_crtc_find_id(drmu_env_t * const du, const uint32_t crtc_id) ++{ ++ unsigned int i; ++ drmu_crtc_t * dc; ++ ++ for (i = 0; (dc = drmu_env_crtc_find_n(du, i)) != NULL; ++i) { ++ if (dc->crtc.crtc_id == crtc_id) ++ return dc; ++ } ++ return NULL; ++} ++ ++const struct drm_mode_modeinfo * ++drmu_crtc_modeinfo(const drmu_crtc_t * const dc) ++{ ++ if (!dc || !dc->crtc.mode_valid) ++ return NULL; ++ return &dc->crtc.mode; ++} ++ ++drmu_mode_simple_params_t ++drmu_crtc_mode_simple_params(const drmu_crtc_t * const dc) ++{ ++ return modeinfo_simple_params(drmu_crtc_modeinfo(dc)); ++} ++ ++int ++drmu_atomic_crtc_add_modeinfo(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, const struct drm_mode_modeinfo * const modeinfo) ++{ ++ drmu_env_t * const du = drmu_atomic_env(da); ++ int rv; ++ ++ if (!modeinfo || dc->pid.mode_id == 0) ++ return 0; ++ ++ if ((rv = drmu_blob_update(du, &dc->mode_id_blob, modeinfo, sizeof(*modeinfo))) != 0) ++ return rv; ++ ++ return drmu_atomic_add_prop_blob(da, dc->crtc.crtc_id, dc->pid.mode_id, dc->mode_id_blob); ++} ++ ++int ++drmu_atomic_crtc_add_active(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, unsigned int val) ++{ ++ return drmu_atomic_add_prop_range(da, dc->crtc.crtc_id, dc->pid.active, val); ++} ++ ++// Use the same claim logic as we do for planes ++// As it stands we don't do anything much on final unref so the logic ++// isn't really needed but it doesn't cost us much so do this way against ++// future need ++ ++bool ++drmu_crtc_is_claimed(const drmu_crtc_t * const dc) ++{ ++ return atomic_load(&dc->ref_count) != 0; ++} ++ ++void ++drmu_crtc_unref(drmu_crtc_t ** const ppdc) ++{ ++ drmu_crtc_t * const dc = *ppdc; ++ ++ if (dc == NULL) ++ return; ++ *ppdc = NULL; ++ ++ if (atomic_fetch_sub(&dc->ref_count, 1) != 2) ++ return; ++ atomic_store(&dc->ref_count, 0); ++} ++ ++drmu_crtc_t * ++drmu_crtc_ref(drmu_crtc_t * const dc) ++{ ++ if (!dc) ++ return NULL; ++ atomic_fetch_add(&dc->ref_count, 1); ++ return dc; ++} ++ ++// A Conn should be claimed before any op that might change its state ++int ++drmu_crtc_claim_ref(drmu_crtc_t * const dc) ++{ ++ drmu_env_t * const du = dc->du; ++ static const int ref0 = 0; ++ if (!atomic_compare_exchange_strong(&dc->ref_count, &ref0, 2)) ++ return -EBUSY; ++ ++ // 1st time through save state ++ if (!dc->saved && env_object_state_save(du, dc->crtc.crtc_id, DRM_MODE_OBJECT_CRTC) == 0) ++ dc->saved = true; ++ ++ return 0; ++} ++ ++//---------------------------------------------------------------------------- ++// ++// CONN functions ++ ++static const char * conn_type_names[32] = { ++ [DRM_MODE_CONNECTOR_Unknown] = "Unknown", ++ [DRM_MODE_CONNECTOR_VGA] = "VGA", ++ [DRM_MODE_CONNECTOR_DVII] = "DVI-I", ++ [DRM_MODE_CONNECTOR_DVID] = "DVI-D", ++ [DRM_MODE_CONNECTOR_DVIA] = "DVI-A", ++ [DRM_MODE_CONNECTOR_Composite] = "Composite", ++ [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO", ++ [DRM_MODE_CONNECTOR_LVDS] = "LVDS", ++ [DRM_MODE_CONNECTOR_Component] = "Component", ++ [DRM_MODE_CONNECTOR_9PinDIN] = "9PinDIN", ++ [DRM_MODE_CONNECTOR_DisplayPort] = "DisplayPort", ++ [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A", ++ [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B", ++ [DRM_MODE_CONNECTOR_TV] = "TV", ++ [DRM_MODE_CONNECTOR_eDP] = "eDP", ++ [DRM_MODE_CONNECTOR_VIRTUAL] = "VIRTUAL", ++ [DRM_MODE_CONNECTOR_DSI] = "DSI", ++ [DRM_MODE_CONNECTOR_DPI] = "DPI", ++ [DRM_MODE_CONNECTOR_WRITEBACK] = "WRITEBACK", ++ [DRM_MODE_CONNECTOR_SPI] = "SPI", ++#ifdef DRM_MODE_CONNECTOR_USB ++ [DRM_MODE_CONNECTOR_USB] = "USB", ++#endif ++}; ++ ++struct drmu_conn_s { ++ drmu_env_t * du; ++ unsigned int conn_idx; ++ ++ atomic_int ref_count; ++ bool saved; ++ ++ struct drm_mode_get_connector conn; ++ bool probed; ++ unsigned int modes_size; ++ unsigned int enc_ids_size; ++ struct drm_mode_modeinfo * modes; ++ uint32_t * enc_ids; ++ ++ uint32_t avail_crtc_mask; ++ ++ struct { ++ drmu_prop_object_t * crtc_id; ++ drmu_prop_range_t * max_bpc; ++ drmu_prop_enum_t * colorspace; ++ drmu_prop_enum_t * broadcast_rgb; ++ uint32_t hdr_output_metadata; ++ uint32_t writeback_out_fence_ptr; ++ uint32_t writeback_fb_id; ++ uint32_t writeback_pixel_formats; ++ } pid; ++ ++ drmu_blob_t * hdr_metadata_blob; ++ ++ char name[32]; ++}; ++ ++ ++int ++drmu_atomic_conn_add_hdr_metadata(drmu_atomic_t * const da, drmu_conn_t * const dn, const struct hdr_output_metadata * const m) ++{ ++ drmu_env_t * const du = drmu_atomic_env(da); ++ int rv; ++ ++ if (!du || !dn) // du will be null if da is null ++ return -ENOENT; ++ ++ if (dn->pid.hdr_output_metadata == 0) ++ return 0; ++ ++ if ((rv = drmu_blob_update(du, &dn->hdr_metadata_blob, m, sizeof(*m))) != 0) ++ return rv; ++ ++ rv = drmu_atomic_add_prop_blob(da, dn->conn.connector_id, dn->pid.hdr_output_metadata, dn->hdr_metadata_blob); ++ if (rv != 0) ++ drmu_err(du, "Set property fail: %s", strerror(errno)); ++ ++ return rv; ++} ++ ++int ++drmu_atomic_conn_add_hi_bpc(drmu_atomic_t * const da, drmu_conn_t * const dn, bool hi_bpc) ++{ ++ return drmu_atomic_add_prop_range(da, dn->conn.connector_id, dn->pid.max_bpc, !hi_bpc ? 8 : ++ drmu_prop_range_max(dn->pid.max_bpc)); ++} ++ ++int ++drmu_atomic_conn_add_colorspace(drmu_atomic_t * const da, drmu_conn_t * const dn, const drmu_colorspace_t colorspace) ++{ ++ if (!dn->pid.colorspace) ++ return 0; ++ ++ return drmu_atomic_add_prop_enum(da, dn->conn.connector_id, dn->pid.colorspace, colorspace); ++} ++ ++int ++drmu_atomic_conn_add_broadcast_rgb(drmu_atomic_t * const da, drmu_conn_t * const dn, const drmu_broadcast_rgb_t bcrgb) ++{ ++ if (!dn->pid.broadcast_rgb) ++ return 0; ++ ++ return drmu_atomic_add_prop_enum(da, dn->conn.connector_id, dn->pid.broadcast_rgb, bcrgb); ++} ++ ++int ++drmu_atomic_conn_add_crtc(drmu_atomic_t * const da, drmu_conn_t * const dn, drmu_crtc_t * const dc) ++{ ++ return drmu_atomic_add_prop_object(da, dn->pid.crtc_id, drmu_crtc_id(dc)); ++} ++ ++int ++drmu_atomic_conn_add_writeback_fb(drmu_atomic_t * const da_out, drmu_conn_t * const dn, ++ drmu_fb_t * const dfb) ++{ ++ // Add both or neither, so build a temp atomic to store the intermediate result ++ drmu_atomic_t * da = drmu_atomic_new(drmu_atomic_env(da_out)); ++ int rv; ++ ++ if (!da) ++ return -ENOMEM; ++ ++ if ((rv = atomic_fb_add_out_fence(da, dn->conn.connector_id, dn->pid.writeback_out_fence_ptr, dfb)) != 0) ++ goto fail; ++ ++ if ((rv = drmu_atomic_add_prop_fb(da, dn->conn.connector_id, dn->pid.writeback_fb_id, dfb)) != 0) ++ goto fail; ++ ++ return drmu_atomic_merge(da_out, &da); ++ ++fail: ++ drmu_atomic_unref(&da); ++ return rv; ++} ++ ++const struct drm_mode_modeinfo * ++drmu_conn_modeinfo(const drmu_conn_t * const dn, const int mode_id) ++{ ++ return !dn || mode_id < 0 || (unsigned int)mode_id >= dn->conn.count_modes ? NULL : ++ dn->modes + mode_id; ++} ++ ++drmu_mode_simple_params_t ++drmu_conn_mode_simple_params(const drmu_conn_t * const dn, const int mode_id) ++{ ++ return modeinfo_simple_params(drmu_conn_modeinfo(dn, mode_id)); ++} ++ ++bool ++drmu_conn_is_output(const drmu_conn_t * const dn) ++{ ++ return dn->conn.connector_type != DRM_MODE_CONNECTOR_WRITEBACK; ++} ++ ++bool ++drmu_conn_is_writeback(const drmu_conn_t * const dn) ++{ ++ return dn->conn.connector_type == DRM_MODE_CONNECTOR_WRITEBACK; ++} ++ ++const char * ++drmu_conn_name(const drmu_conn_t * const dn) ++{ ++ return dn->name; ++} ++ ++uint32_t ++drmu_conn_crtc_id_get(const drmu_conn_t * const dn) ++{ ++ return drmu_prop_object_value(dn->pid.crtc_id); ++} ++ ++uint32_t ++drmu_conn_possible_crtcs(const drmu_conn_t * const dn) ++{ ++ return dn->avail_crtc_mask; ++} ++ ++unsigned int ++drmu_conn_idx_get(const drmu_conn_t * const dn) ++{ ++ return dn->conn_idx; ++} ++ ++static void ++conn_uninit(drmu_conn_t * const dn) ++{ ++ drmu_prop_object_unref(&dn->pid.crtc_id); ++ drmu_prop_range_delete(&dn->pid.max_bpc); ++ drmu_prop_enum_delete(&dn->pid.colorspace); ++ drmu_prop_enum_delete(&dn->pid.broadcast_rgb); ++ ++ drmu_blob_unref(&dn->hdr_metadata_blob); ++ ++ free(dn->modes); ++ free(dn->enc_ids); ++ dn->modes = NULL; ++ dn->enc_ids = NULL; ++ dn->modes_size = 0; ++ dn->enc_ids_size = 0; ++} ++ ++// Assumes zeroed before entry ++static int ++conn_init(drmu_env_t * const du, drmu_conn_t * const dn, unsigned int conn_idx, const uint32_t conn_id) ++{ ++ int rv; ++ drmu_props_t * props; ++ uint32_t modes_req = 0; ++ uint32_t encs_req = 0; ++ ++ dn->du = du; ++ dn->conn_idx = conn_idx; ++ // * As count_modes == 0 this probes - do we really want this? ++ ++ do { ++ memset(&dn->conn, 0, sizeof(dn->conn)); ++ dn->conn.connector_id = conn_id; ++ ++ if (modes_req > dn->modes_size) { ++ free(dn->modes); ++ if ((dn->modes = malloc(modes_req * sizeof(*dn->modes))) == NULL) { ++ drmu_err(du, "Failed to alloc modes array"); ++ goto fail; ++ } ++ dn->modes_size = modes_req; ++ } ++ dn->conn.modes_ptr = (uintptr_t)dn->modes; ++ dn->conn.count_modes = modes_req; ++ ++ if (encs_req > dn->enc_ids_size) { ++ free(dn->enc_ids); ++ if ((dn->enc_ids = malloc(encs_req * sizeof(*dn->enc_ids))) == NULL) { ++ drmu_err(du, "Failed to alloc encs array"); ++ goto fail; ++ } ++ dn->enc_ids_size = encs_req; ++ } ++ dn->conn.encoders_ptr = (uintptr_t)dn->enc_ids; ++ dn->conn.count_encoders = encs_req; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETCONNECTOR, &dn->conn)) != 0) { ++ drmu_err(du, "Get connector id %d failed: %s", dn->conn.connector_id, strerror(-rv)); ++ goto fail; ++ } ++ modes_req = dn->conn.count_modes; ++ encs_req = dn->conn.count_encoders; ++ ++ } while (dn->modes_size < modes_req || dn->enc_ids_size < encs_req); ++ ++ dn->probed = true; ++ ++ if (dn->conn.connector_type >= sizeof(conn_type_names) / sizeof(conn_type_names[0])) ++ snprintf(dn->name, sizeof(dn->name)-1, "CT%"PRIu32"-%"PRIu32, ++ dn->conn.connector_type, dn->conn.connector_type_id); ++ else ++ snprintf(dn->name, sizeof(dn->name)-1, "%s-%"PRIu32, ++ conn_type_names[dn->conn.connector_type], dn->conn.connector_type_id); ++ ++ props = props_new(du, dn->conn.connector_id, DRM_MODE_OBJECT_CONNECTOR); ++ ++ // Spin over encoders to create a crtc mask ++ dn->avail_crtc_mask = 0; ++ for (unsigned int i = 0; i != dn->conn.count_encoders; ++i) { ++ struct drm_mode_get_encoder enc = {.encoder_id = dn->enc_ids[i]}; ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETENCODER, &enc)) != 0) { ++ drmu_warn(du, "Failed to get encoder: id: %#x", enc.encoder_id); ++ continue; ++ } ++ dn->avail_crtc_mask |= enc.possible_crtcs; ++ } ++ ++ if (props != NULL) { ++#if TRACE_PROP_NEW ++ drmu_info(du, "Connector id=%d, type=%d.%d (%s), crtc_mask=%#x:", ++ dn->conn.connector_id, dn->conn.connector_type, dn->conn.connector_type_id, drmu_conn_name(dn), ++ dn->avail_crtc_mask); ++ props_dump(props); ++#endif ++ dn->pid.crtc_id = drmu_prop_object_new_propinfo(du, dn->conn.connector_id, props_name_to_propinfo(props, "CRTC_ID")); ++ dn->pid.max_bpc = drmu_prop_range_new(du, props_name_to_id(props, "max bpc")); ++ dn->pid.colorspace = drmu_prop_enum_new(du, props_name_to_id(props, "Colorspace")); ++ dn->pid.broadcast_rgb = drmu_prop_enum_new(du, props_name_to_id(props, "Broadcast RGB")); ++ dn->pid.hdr_output_metadata = props_name_to_id(props, "HDR_OUTPUT_METADATA"); ++ dn->pid.writeback_fb_id = props_name_to_id(props, "WRITEBACK_FB_ID"); ++ dn->pid.writeback_out_fence_ptr = props_name_to_id(props, "WRITEBACK_OUT_FENCE_PTR"); ++ dn->pid.writeback_pixel_formats = props_name_to_id(props, "WRITEBACK_PIXEL_FORMATS"); // Blob of fourccs (no modifier info) ++ ++ props_free(props); ++ } ++ ++ return 0; ++ ++fail: ++ conn_uninit(dn); ++ return rv; ++} ++ ++// Use the same claim logic as we do for planes ++// As it stands we don't do anything much on final unref so the logic ++// isn't really needed but it doesn't cost us much so do this way against ++// future need ++ ++bool ++drmu_conn_is_claimed(const drmu_conn_t * const dn) ++{ ++ return atomic_load(&dn->ref_count) != 0; ++} ++ ++void ++drmu_conn_unref(drmu_conn_t ** const ppdn) ++{ ++ drmu_conn_t * const dn = *ppdn; ++ ++ if (dn == NULL) ++ return; ++ *ppdn = NULL; ++ ++ if (atomic_fetch_sub(&dn->ref_count, 1) != 2) ++ return; ++ atomic_store(&dn->ref_count, 0); ++} ++ ++drmu_conn_t * ++drmu_conn_ref(drmu_conn_t * const dn) ++{ ++ if (!dn) ++ return NULL; ++ atomic_fetch_add(&dn->ref_count, 1); ++ return dn; ++} ++ ++// A Conn should be claimed before any op that might change its state ++int ++drmu_conn_claim_ref(drmu_conn_t * const dn) ++{ ++ drmu_env_t * const du = dn->du; ++ static const int ref0 = 0; ++ if (!atomic_compare_exchange_strong(&dn->ref_count, &ref0, 2)) ++ return -EBUSY; ++ ++ // 1st time through save state ++ if (!dn->saved && env_object_state_save(du, dn->conn.connector_id, DRM_MODE_OBJECT_CONNECTOR) == 0) ++ dn->saved = true; ++ ++ return 0; ++} ++ ++//---------------------------------------------------------------------------- ++// ++// Atomic Q fns (internal) ++ ++typedef struct drmu_atomic_q_s { ++ pthread_mutex_t lock; ++ pthread_cond_t cond; ++ drmu_atomic_t * next_flip; ++ drmu_atomic_t * cur_flip; ++ drmu_atomic_t * last_flip; ++ unsigned int retry_count; ++ struct polltask * retry_task; ++} drmu_atomic_q_t; ++ ++static void atomic_q_retry(drmu_atomic_q_t * const aq, drmu_env_t * const du); ++ ++// Needs locked ++static int ++atomic_q_attempt_commit_next(drmu_atomic_q_t * const aq) ++{ ++ drmu_env_t * const du = drmu_atomic_env(aq->next_flip); ++ uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET; ++ int rv; ++ ++ if ((rv = drmu_atomic_commit(aq->next_flip, flags)) == 0) { ++ if (aq->retry_count != 0) ++ drmu_warn(du, "%s: Atomic commit OK", __func__); ++ aq->cur_flip = aq->next_flip; ++ aq->next_flip = NULL; ++ aq->retry_count = 0; ++ } ++ else if (rv == -EBUSY && ++aq->retry_count < 16) { ++ // This really shouldn't happen but we observe that the 1st commit after ++ // a modeset often fails with BUSY. It seems to be fine on a 10ms retry ++ // but allow some more in case ww need a bit longer in some cases ++ drmu_warn(du, "%s: Atomic commit BUSY", __func__); ++ atomic_q_retry(aq, du); ++ rv = 0; ++ } ++ else { ++ drmu_err(du, "%s: Atomic commit failed: %s", __func__, strerror(-rv)); ++ drmu_atomic_dump(aq->next_flip); ++ drmu_atomic_unref(&aq->next_flip); ++ aq->retry_count = 0; ++ } ++ ++ return rv; ++} ++ ++static void ++atomic_q_retry_cb(void * v, short revents) ++{ ++ drmu_atomic_q_t * const aq = v; ++ (void)revents; ++ ++ pthread_mutex_lock(&aq->lock); ++ ++ // If we need a retry then next != NULL && cur == NULL ++ // if not that then we've fixed ourselves elsewhere ++ ++ if (aq->next_flip != NULL && aq->cur_flip == NULL) ++ atomic_q_attempt_commit_next(aq); ++ ++ pthread_mutex_unlock(&aq->lock); ++} ++ ++static void ++atomic_q_retry(drmu_atomic_q_t * const aq, drmu_env_t * const du) ++{ ++ if (aq->retry_task == NULL) ++ aq->retry_task = polltask_new_timer(env_pollqueue(du), atomic_q_retry_cb, aq); ++ pollqueue_add_task(aq->retry_task, 20); ++} ++ ++// Called after an atomic commit has completed ++// not called on every vsync, so if we haven't committed anything this won't be called ++static void ++drmu_atomic_page_flip_cb(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, unsigned int crtc_id, void *user_data) ++{ ++ drmu_atomic_t * const da = user_data; ++ drmu_env_t * const du = drmu_atomic_env(da); ++ drmu_atomic_q_t * const aq = env_atomic_q(du); ++ ++ (void)fd; ++ (void)sequence; ++ (void)tv_sec; ++ (void)tv_usec; ++ (void)crtc_id; ++ ++ // At this point: ++ // next The atomic we are about to commit ++ // cur The last atomic we committed, now in use (must be != NULL) ++ // last The atomic that has just become obsolete ++ ++ pthread_mutex_lock(&aq->lock); ++ ++ if (da != aq->cur_flip) { ++ drmu_err(du, "%s: User data el (%p) != cur (%p)", __func__, da, aq->cur_flip); ++ } ++ ++ drmu_atomic_unref(&aq->last_flip); ++ aq->last_flip = aq->cur_flip; ++ aq->cur_flip = NULL; ++ ++ if (aq->next_flip != NULL) ++ atomic_q_attempt_commit_next(aq); ++ ++ pthread_cond_broadcast(&aq->cond); ++ pthread_mutex_unlock(&aq->lock); ++} ++ ++static int ++atomic_q_flush(drmu_atomic_q_t * const aq) ++{ ++ struct timespec ts; ++ int rv = 0; ++ ++ pthread_mutex_lock(&aq->lock); ++ clock_gettime(CLOCK_MONOTONIC, &ts); ++ ts.tv_sec += 1; // We should never timeout if all is well - 1 sec is plenty ++ ++ // Can flush next safely ++ drmu_atomic_unref(&aq->next_flip); ++ ++ // Wait for cur to finish - seems to confuse the world otherwise ++ while (aq->cur_flip != NULL) { ++ if ((rv = pthread_cond_timedwait(&aq->cond, &aq->lock, &ts)) != 0) ++ break; ++ } ++ ++ pthread_mutex_unlock(&aq->lock); ++ return rv; ++} ++ ++// 'consumes' da ++static int ++atomic_q_queue(drmu_atomic_q_t * const aq, drmu_atomic_t * da) ++{ ++ int rv = 0; ++ ++ pthread_mutex_lock(&aq->lock); ++ ++ if (aq->next_flip != NULL) { ++ // We already have something pending or retrying - merge the new with it ++ rv = drmu_atomic_merge(aq->next_flip, &da); ++ } ++ else { ++ aq->next_flip = da; ++ ++ // No pending commit? ++ if (aq->cur_flip == NULL) ++ rv = atomic_q_attempt_commit_next(aq); ++ } ++ ++ pthread_mutex_unlock(&aq->lock); ++ return rv; ++} ++ ++// Consumes the passed atomic structure as it isn't copied ++// * arguably should copy & unref if ref count != 0 ++int ++drmu_atomic_queue(drmu_atomic_t ** ppda) ++{ ++ drmu_atomic_t * da = *ppda; ++ ++ if (da == NULL) ++ return 0; ++ *ppda = NULL; ++ ++ return atomic_q_queue(env_atomic_q(drmu_atomic_env(da)), da); ++} ++ ++int ++drmu_env_queue_wait(drmu_env_t * const du) ++{ ++ ++ drmu_atomic_q_t *const aq = env_atomic_q(du); ++ struct timespec ts; ++ int rv = 0; ++ ++ pthread_mutex_lock(&aq->lock); ++ clock_gettime(CLOCK_MONOTONIC, &ts); ++ ts.tv_sec += 1; // We should never timeout if all is well - 1 sec is plenty ++ ++ // Next should clear quickly ++ while (aq->next_flip != NULL) { ++ if ((rv = pthread_cond_timedwait(&aq->cond, &aq->lock, &ts)) != 0) ++ break; ++ } ++ ++ pthread_mutex_unlock(&aq->lock); ++ return rv; ++} ++ ++static void ++atomic_q_uninit(drmu_atomic_q_t * const aq) ++{ ++ polltask_delete(&aq->retry_task); ++ drmu_atomic_unref(&aq->next_flip); ++ drmu_atomic_unref(&aq->cur_flip); ++ drmu_atomic_unref(&aq->last_flip); ++ pthread_cond_destroy(&aq->cond); ++ pthread_mutex_destroy(&aq->lock); ++} ++ ++static void ++atomic_q_init(drmu_atomic_q_t * const aq) ++{ ++ pthread_condattr_t condattr; ++ ++ aq->next_flip = NULL; ++ pthread_mutex_init(&aq->lock, NULL); ++ ++ pthread_condattr_init(&condattr); ++ pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC); ++ pthread_cond_init(&aq->cond, &condattr); ++ pthread_condattr_destroy(&condattr); ++} ++ ++//---------------------------------------------------------------------------- ++// ++// Pool fns ++ ++typedef struct drmu_fb_list_s { ++ drmu_fb_t * head; ++ drmu_fb_t * tail; ++} drmu_fb_list_t; ++ ++typedef struct drmu_pool_s { ++ atomic_int ref_count; // 0 == 1 ref for ease of init ++ ++ struct drmu_env_s * du; ++ ++ pthread_mutex_t lock; ++ int dead; ++ ++ unsigned int seq; // debug ++ ++ unsigned int fb_count; ++ unsigned int fb_max; ++ ++ drmu_fb_list_t free_fbs; ++} drmu_pool_t; ++ ++static void ++fb_list_add_tail(drmu_fb_list_t * const fbl, drmu_fb_t * const dfb) ++{ ++ assert(dfb->prev == NULL && dfb->next == NULL); ++ ++ if (fbl->tail == NULL) ++ fbl->head = dfb; ++ else ++ fbl->tail->next = dfb; ++ dfb->prev = fbl->tail; ++ fbl->tail = dfb; ++} ++ ++static drmu_fb_t * ++fb_list_extract(drmu_fb_list_t * const fbl, drmu_fb_t * const dfb) ++{ ++ if (dfb == NULL) ++ return NULL; ++ ++ if (dfb->prev == NULL) ++ fbl->head = dfb->next; ++ else ++ dfb->prev->next = dfb->next; ++ ++ if (dfb->next == NULL) ++ fbl->tail = dfb->prev; ++ else ++ dfb->next->prev = dfb->prev; ++ ++ dfb->next = NULL; ++ dfb->prev = NULL; ++ return dfb; ++} ++ ++static drmu_fb_t * ++fb_list_extract_head(drmu_fb_list_t * const fbl) ++{ ++ return fb_list_extract(fbl, fbl->head); ++} ++ ++static drmu_fb_t * ++fb_list_peek_head(drmu_fb_list_t * const fbl) ++{ ++ return fbl->head; ++} ++ ++static bool ++fb_list_is_empty(drmu_fb_list_t * const fbl) ++{ ++ return fbl->head == NULL; ++} ++ ++static void ++pool_free_pool(drmu_pool_t * const pool) ++{ ++ drmu_fb_t * dfb; ++ while ((dfb = fb_list_extract_head(&pool->free_fbs)) != NULL) ++ drmu_fb_unref(&dfb); ++} ++ ++static void ++pool_free(drmu_pool_t * const pool) ++{ ++ pool_free_pool(pool); ++ pthread_mutex_destroy(&pool->lock); ++ free(pool); ++} ++ ++void ++drmu_pool_unref(drmu_pool_t ** const pppool) ++{ ++ drmu_pool_t * const pool = *pppool; ++ ++ if (pool == NULL) ++ return; ++ *pppool = NULL; ++ ++ if (atomic_fetch_sub(&pool->ref_count, 1) != 0) ++ return; ++ ++ pool_free(pool); ++} ++ ++drmu_pool_t * ++drmu_pool_ref(drmu_pool_t * const pool) ++{ ++ atomic_fetch_add(&pool->ref_count, 1); ++ return pool; ++} ++ ++drmu_pool_t * ++drmu_pool_new(drmu_env_t * const du, unsigned int total_fbs_max) ++{ ++ drmu_pool_t * const pool = calloc(1, sizeof(*pool)); ++ ++ if (pool == NULL) { ++ drmu_err(du, "Failed pool env alloc"); ++ return NULL; ++ } ++ ++ pool->du = du; ++ pool->fb_max = total_fbs_max; ++ pthread_mutex_init(&pool->lock, NULL); ++ ++ return pool; ++} ++ ++static int ++pool_fb_pre_delete_cb(drmu_fb_t * dfb, void * v) ++{ ++ drmu_pool_t * pool = v; ++ ++ // Ensure we cannot end up in a delete loop ++ drmu_fb_pre_delete_unset(dfb); ++ ++ // If dead set then might as well delete now ++ // It should all work without this shortcut but this reclaims ++ // storage quicker ++ if (pool->dead) { ++ drmu_pool_unref(&pool); ++ return 0; ++ } ++ ++ drmu_fb_ref(dfb); // Restore ref ++ ++ pthread_mutex_lock(&pool->lock); ++ fb_list_add_tail(&pool->free_fbs, dfb); ++ pthread_mutex_unlock(&pool->lock); ++ ++ // May cause suicide & recursion on fb delete, but that should be OK as ++ // the 1 we return here should cause simple exit of fb delete ++ drmu_pool_unref(&pool); ++ return 1; // Stop delete ++} ++ ++drmu_fb_t * ++drmu_pool_fb_new_dumb(drmu_pool_t * const pool, uint32_t w, uint32_t h, const uint32_t format) ++{ ++ drmu_env_t * const du = pool->du; ++ drmu_fb_t * dfb; ++ ++ pthread_mutex_lock(&pool->lock); ++ ++ dfb = fb_list_peek_head(&pool->free_fbs); ++ while (dfb != NULL) { ++ if (fb_try_reuse(dfb, w, h, format)) { ++ fb_list_extract(&pool->free_fbs, dfb); ++ break; ++ } ++ dfb = dfb->next; ++ } ++ ++ if (dfb == NULL) { ++ if (pool->fb_count >= pool->fb_max && !fb_list_is_empty(&pool->free_fbs)) { ++ --pool->fb_count; ++ dfb = fb_list_extract_head(&pool->free_fbs); ++ } ++ ++pool->fb_count; ++ pthread_mutex_unlock(&pool->lock); ++ ++ drmu_fb_unref(&dfb); // Will free the dfb as pre-delete CB will be unset ++ if ((dfb = drmu_fb_realloc_dumb(du, NULL, w, h, format)) == NULL) { ++ --pool->fb_count; // ??? lock ++ return NULL; ++ } ++ } ++ else { ++ pthread_mutex_unlock(&pool->lock); ++ } ++ ++ drmu_fb_pre_delete_set(dfb, pool_fb_pre_delete_cb, pool); ++ drmu_pool_ref(pool); ++ return dfb; ++} ++ ++// Mark pool as dead (i.e. no new allocs) and unref it ++// Simple unref will also work but this reclaims storage faster ++// Actual pool structure will persist until all referencing fbs are deleted too ++void ++drmu_pool_delete(drmu_pool_t ** const pppool) ++{ ++ drmu_pool_t * pool = *pppool; ++ ++ if (pool == NULL) ++ return; ++ *pppool = NULL; ++ ++ pool->dead = 1; ++ pool_free_pool(pool); ++ ++ drmu_pool_unref(&pool); ++} ++ ++//---------------------------------------------------------------------------- ++// ++// Plane fns ++ ++typedef struct drmu_plane_s { ++ struct drmu_env_s * du; ++ ++ // Unlike most ref counts in drmu this is 0 for unrefed, 2 for single ref ++ // and 1 for whilst unref cleanup is in progress. Guards dc ++ atomic_int ref_count; ++ struct drmu_crtc_s * dc; // NULL if not in use ++ bool saved; ++ ++ int plane_type; ++ struct drm_mode_get_plane plane; ++ ++ void * formats_in; ++ size_t formats_in_len; ++ const struct drm_format_modifier_blob * fmts_hdr; ++ ++ struct { ++ uint32_t crtc_id; ++ uint32_t fb_id; ++ uint32_t crtc_h; ++ uint32_t crtc_w; ++ uint32_t crtc_x; ++ uint32_t crtc_y; ++ uint32_t src_h; ++ uint32_t src_w; ++ uint32_t src_x; ++ uint32_t src_y; ++ drmu_prop_range_t * alpha; ++ drmu_prop_enum_t * color_encoding; ++ drmu_prop_enum_t * color_range; ++ drmu_prop_enum_t * pixel_blend_mode; ++ drmu_prop_bitmask_t * rotation; ++ drmu_prop_range_t * chroma_siting_h; ++ drmu_prop_range_t * chroma_siting_v; ++ drmu_prop_range_t * zpos; ++ } pid; ++ uint64_t rot_vals[8]; ++ ++} drmu_plane_t; ++ ++static int ++plane_set_atomic(drmu_atomic_t * const da, ++ drmu_plane_t * const dp, ++ drmu_fb_t * const dfb, ++ int32_t crtc_x, int32_t crtc_y, ++ uint32_t crtc_w, uint32_t crtc_h, ++ uint32_t src_x, uint32_t src_y, ++ uint32_t src_w, uint32_t src_h) ++{ ++ const uint32_t plid = dp->plane.plane_id; ++ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_id, dfb == NULL ? 0 : drmu_crtc_id(dp->dc)); ++ drmu_atomic_add_prop_fb(da, plid, dp->pid.fb_id, dfb); ++ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_x, crtc_x); ++ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_y, crtc_y); ++ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_w, crtc_w); ++ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_h, crtc_h); ++ drmu_atomic_add_prop_value(da, plid, dp->pid.src_x, src_x); ++ drmu_atomic_add_prop_value(da, plid, dp->pid.src_y, src_y); ++ drmu_atomic_add_prop_value(da, plid, dp->pid.src_w, src_w); ++ drmu_atomic_add_prop_value(da, plid, dp->pid.src_h, src_h); ++ return 0; ++} ++ ++int ++drmu_atomic_plane_add_alpha(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int alpha) ++{ ++ if (alpha == DRMU_PLANE_ALPHA_UNSET) ++ return 0; ++ return drmu_atomic_add_prop_range(da, dp->plane.plane_id, dp->pid.alpha, alpha); ++} ++ ++int ++drmu_atomic_plane_add_zpos(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int zpos) ++{ ++ return drmu_atomic_add_prop_range(da, dp->plane.plane_id, dp->pid.zpos, zpos); ++} ++ ++int ++drmu_atomic_plane_add_rotation(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int rot) ++{ ++ if (!dp->pid.rotation) ++ return rot == DRMU_PLANE_ROTATION_0 ? 0 : -EINVAL; ++ if (rot < 0 || rot >= 8 || !dp->rot_vals[rot]) ++ return -EINVAL; ++ return drmu_atomic_add_prop_bitmask(da, dp->plane.plane_id, dp->pid.rotation, dp->rot_vals[rot]); ++} ++ ++int ++drmu_atomic_plane_add_chroma_siting(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const drmu_chroma_siting_t siting) ++{ ++ int rv = 0; ++ ++ if (!dp->pid.chroma_siting_h || !dp->pid.chroma_siting_v) ++ return -ENOENT; ++ ++ if (!drmu_chroma_siting_eq(siting, DRMU_CHROMA_SITING_UNSPECIFIED)) { ++ const uint32_t plid = dp->plane.plane_id; ++ rv = drmu_atomic_add_prop_range(da, plid, dp->pid.chroma_siting_h, siting.x); ++ rv = rvup(rv, drmu_atomic_add_prop_range(da, plid, dp->pid.chroma_siting_v, siting.y)); ++ } ++ return rv; ++} ++ ++int ++drmu_atomic_plane_add_fb(drmu_atomic_t * const da, drmu_plane_t * const dp, ++ drmu_fb_t * const dfb, const drmu_rect_t pos) ++{ ++ int rv; ++ const uint32_t plid = dp->plane.plane_id; ++ ++ if (dfb == NULL) { ++ rv = plane_set_atomic(da, dp, NULL, ++ 0, 0, 0, 0, ++ 0, 0, 0, 0); ++ } ++ else { ++ rv = plane_set_atomic(da, dp, dfb, ++ pos.x, pos.y, ++ pos.w, pos.h, ++ dfb->crop.x + (dfb->active.x << 16), dfb->crop.y + (dfb->active.y << 16), ++ dfb->crop.w, dfb->crop.h); ++ } ++ if (rv != 0 || dfb == NULL) ++ return rv; ++ ++ drmu_atomic_add_prop_enum(da, plid, dp->pid.pixel_blend_mode, dfb->pixel_blend_mode); ++ drmu_atomic_add_prop_enum(da, plid, dp->pid.color_encoding, dfb->color_encoding); ++ drmu_atomic_add_prop_enum(da, plid, dp->pid.color_range, dfb->color_range); ++ drmu_atomic_plane_add_chroma_siting(da, dp, dfb->chroma_siting); ++ return rv != 0 ? -errno : 0; ++} ++ ++uint32_t ++drmu_plane_id(const drmu_plane_t * const dp) ++{ ++ return dp->plane.plane_id; ++} ++ ++unsigned int ++drmu_plane_type(const drmu_plane_t * const dp) ++{ ++ return dp->plane_type; ++} ++ ++const uint32_t * ++drmu_plane_formats(const drmu_plane_t * const dp, unsigned int * const pCount) ++{ ++ *pCount = dp->fmts_hdr->count_formats; ++ return (const uint32_t *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->formats_offset); ++} ++ ++bool ++drmu_plane_format_check(const drmu_plane_t * const dp, const uint32_t format, const uint64_t modifier) ++{ ++ const struct drm_format_modifier * const mods = (const struct drm_format_modifier *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->modifiers_offset); ++ const uint32_t * const fmts = (const uint32_t *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->formats_offset); ++ uint64_t modbase = modifier; ++ unsigned int i; ++ ++ if (!format) ++ return false; ++ ++ // If broadcom then remove parameters before checking ++ if ((modbase >> 56) == DRM_FORMAT_MOD_VENDOR_BROADCOM) ++ modbase = fourcc_mod_broadcom_mod(modbase); ++ ++ // * Simplistic lookup; Could be made much faster ++ ++ for (i = 0; i != dp->fmts_hdr->count_modifiers; ++i) { ++ const struct drm_format_modifier * const mod = mods + i; ++ uint64_t fbits; ++ unsigned int j; ++ ++ if (mod->modifier != modbase) ++ continue; ++ ++ for (fbits = mod->formats, j = mod->offset; fbits; fbits >>= 1, ++j) { ++ if ((fbits & 1) != 0 && fmts[j] == format) ++ return true; ++ } ++ } ++ return false; ++} ++ ++void ++drmu_plane_unref(drmu_plane_t ** const ppdp) ++{ ++ drmu_plane_t * const dp = *ppdp; ++ ++ if (dp == NULL) ++ return; ++ *ppdp = NULL; ++ ++ if (atomic_fetch_sub(&dp->ref_count, 1) != 2) ++ return; ++ dp->dc = NULL; ++ atomic_store(&dp->ref_count, 0); ++} ++ ++drmu_plane_t * ++drmu_plane_ref(drmu_plane_t * const dp) ++{ ++ if (dp) ++ atomic_fetch_add(&dp->ref_count, 1); ++ return dp; ++} ++ ++// Associate a plane with a crtc and ref it ++// Returns -EBUSY if plane already associated ++int ++drmu_plane_ref_crtc(drmu_plane_t * const dp, drmu_crtc_t * const dc) ++{ ++ drmu_env_t * const du = dp->du; ++ ++ static const int ref0 = 0; ++ if (!atomic_compare_exchange_strong(&dp->ref_count, &ref0, 2)) ++ return -EBUSY; ++ dp->dc = dc; ++ ++ // 1st time through save state ++ if (!dp->saved && env_object_state_save(du, drmu_plane_id(dp), DRM_MODE_OBJECT_PLANE) == 0) ++ dp->saved = true; ++ ++ return 0; ++} ++ ++drmu_plane_t * ++drmu_plane_new_find(drmu_crtc_t * const dc, const drmu_plane_new_find_ok_fn cb, void * const v) ++{ ++ uint32_t i; ++ drmu_env_t * const du = drmu_crtc_env(dc); ++ drmu_plane_t * dp = NULL; ++ drmu_plane_t * dp_t; ++ const uint32_t crtc_mask = (uint32_t)1 << drmu_crtc_idx(dc); ++ ++ for (i = 0; (dp_t = drmu_env_plane_find_n(du, i)) != NULL; ++i) { ++ // Is unused? ++ // Availible for this crtc? ++ if (dp_t->dc != NULL || ++ (dp_t->plane.possible_crtcs & crtc_mask) == 0) ++ continue; ++ ++ if (cb(dp_t, v)) { ++ dp = dp_t; ++ break; ++ } ++ } ++ return dp; ++} ++ ++static bool plane_find_type_cb(const drmu_plane_t * dp, void * v) ++{ ++ const unsigned int * const pReq = v; ++ return (*pReq & drmu_plane_type(dp)) != 0; ++} ++ ++drmu_plane_t * ++drmu_plane_new_find_type(drmu_crtc_t * const dc, const unsigned int req_type) ++{ ++ drmu_env_t * const du = drmu_crtc_env(dc); ++ drmu_plane_t * const dp = drmu_plane_new_find(dc, plane_find_type_cb, (void*)&req_type); ++ if (dp == NULL) { ++ drmu_err(du, "%s: No plane found for types %#x", __func__, req_type); ++ return NULL; ++ } ++ return dp; ++} ++ ++static void ++plane_uninit(drmu_plane_t * const dp) ++{ ++ drmu_prop_range_delete(&dp->pid.alpha); ++ drmu_prop_range_delete(&dp->pid.chroma_siting_h); ++ drmu_prop_range_delete(&dp->pid.chroma_siting_v); ++ drmu_prop_enum_delete(&dp->pid.color_encoding); ++ drmu_prop_enum_delete(&dp->pid.color_range); ++ drmu_prop_enum_delete(&dp->pid.pixel_blend_mode); ++ drmu_prop_enum_delete(&dp->pid.rotation); ++ free(dp->formats_in); ++ dp->formats_in = NULL; ++} ++ ++ ++static int ++plane_init(drmu_env_t * const du, drmu_plane_t * const dp, const uint32_t plane_id) ++{ ++ drmu_props_t *props; ++ int rv; ++ ++ memset(dp, 0, sizeof(*dp)); ++ dp->du = du; ++ ++ dp->plane.plane_id = plane_id; ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPLANE, &dp->plane)) != 0) { ++ drmu_err(du, "%s: drmModeGetPlane failed: %s", __func__, strerror(-rv)); ++ return rv; ++ } ++ ++ if ((props = props_new(du, dp->plane.plane_id, DRM_MODE_OBJECT_PLANE)) == NULL) ++ return -EINVAL; ++ ++#if TRACE_PROP_NEW ++ drmu_info(du, "Plane id %d:", plane_id); ++ props_dump(props); ++#endif ++ ++ if ((dp->pid.crtc_id = props_name_to_id(props, "CRTC_ID")) == 0 || ++ (dp->pid.fb_id = props_name_to_id(props, "FB_ID")) == 0 || ++ (dp->pid.crtc_h = props_name_to_id(props, "CRTC_H")) == 0 || ++ (dp->pid.crtc_w = props_name_to_id(props, "CRTC_W")) == 0 || ++ (dp->pid.crtc_x = props_name_to_id(props, "CRTC_X")) == 0 || ++ (dp->pid.crtc_y = props_name_to_id(props, "CRTC_Y")) == 0 || ++ (dp->pid.src_h = props_name_to_id(props, "SRC_H")) == 0 || ++ (dp->pid.src_w = props_name_to_id(props, "SRC_W")) == 0 || ++ (dp->pid.src_x = props_name_to_id(props, "SRC_X")) == 0 || ++ (dp->pid.src_y = props_name_to_id(props, "SRC_Y")) == 0 || ++ props_name_get_blob(props, "IN_FORMATS", &dp->formats_in, &dp->formats_in_len) != 0) ++ { ++ drmu_err(du, "%s: failed to find required id", __func__); ++ props_free(props); ++ return -EINVAL; ++ } ++ dp->fmts_hdr = dp->formats_in; ++ ++ dp->pid.alpha = drmu_prop_range_new(du, props_name_to_id(props, "alpha")); ++ dp->pid.color_encoding = drmu_prop_enum_new(du, props_name_to_id(props, "COLOR_ENCODING")); ++ dp->pid.color_range = drmu_prop_enum_new(du, props_name_to_id(props, "COLOR_RANGE")); ++ dp->pid.pixel_blend_mode = drmu_prop_enum_new(du, props_name_to_id(props, "pixel blend mode")); ++ dp->pid.rotation = drmu_prop_enum_new(du, props_name_to_id(props, "rotation")); ++ dp->pid.chroma_siting_h = drmu_prop_range_new(du, props_name_to_id(props, "CHROMA_SITING_H")); ++ dp->pid.chroma_siting_v = drmu_prop_range_new(du, props_name_to_id(props, "CHROMA_SITING_V")); ++ dp->pid.zpos = drmu_prop_range_new(du, props_name_to_id(props, "zpos")); ++ ++ dp->rot_vals[DRMU_PLANE_ROTATION_0] = drmu_prop_bitmask_value(dp->pid.rotation, "rotate-0"); ++ if (dp->rot_vals[DRMU_PLANE_ROTATION_0]) { ++ // Flips MUST be combined with a rotate ++ if ((dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] = drmu_prop_bitmask_value(dp->pid.rotation, "reflect-x")) != 0) ++ dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] |= dp->rot_vals[DRMU_PLANE_ROTATION_0]; ++ if ((dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP] = drmu_prop_bitmask_value(dp->pid.rotation, "reflect-y")) != 0) ++ dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP] |= dp->rot_vals[DRMU_PLANE_ROTATION_0]; ++ } ++ dp->rot_vals[DRMU_PLANE_ROTATION_180] = drmu_prop_bitmask_value(dp->pid.rotation, "rotate-180"); ++ if (!dp->rot_vals[DRMU_PLANE_ROTATION_180] && dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] && dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP]) ++ dp->rot_vals[DRMU_PLANE_ROTATION_180] = dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] | dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP]; ++ ++ { ++ const drmu_propinfo_t * const pinfo = props_name_to_propinfo(props, "type"); ++ drmu_prop_enum_t * etype = drmu_prop_enum_new(du, props_name_to_id(props, "type")); ++ const uint64_t * p; ++ ++ if ((p = drmu_prop_enum_value(etype, "Primary")) && *p == pinfo->val) ++ dp->plane_type = DRMU_PLANE_TYPE_PRIMARY; ++ else if ((p = drmu_prop_enum_value(etype, "Cursor")) && *p == pinfo->val) ++ dp->plane_type = DRMU_PLANE_TYPE_CURSOR; ++ else if ((p = drmu_prop_enum_value(etype, "Overlay")) && *p == pinfo->val) ++ dp->plane_type = DRMU_PLANE_TYPE_OVERLAY; ++ else { ++ drmu_debug(du, "Unexpected plane type: %"PRId64, pinfo->val); ++ dp->plane_type = DRMU_PLANE_TYPE_UNKNOWN; ++ } ++ drmu_prop_enum_delete(&etype); ++ } ++ ++ props_free(props); ++ return 0; ++} ++ ++//---------------------------------------------------------------------------- ++// ++// Env fns ++ ++typedef struct drmu_env_s { ++ atomic_int ref_count; // 0 == 1 ref for ease of init ++ int fd; ++ uint32_t plane_count; ++ uint32_t conn_count; ++ uint32_t crtc_count; ++ drmu_plane_t * planes; ++ drmu_conn_t * conns; ++ drmu_crtc_t * crtcs; ++ ++ drmu_log_env_t log; ++ ++ // global env for atomic flip ++ drmu_atomic_q_t aq; ++ // global env for bo tracking ++ drmu_bo_env_t boe; ++ // global atomic for restore op ++ drmu_atomic_t * da_restore; ++ ++ struct pollqueue * pq; ++ struct polltask * pt; ++} drmu_env_t; ++ ++// Retrieve the the n-th conn ++// Use for iteration ++// Returns NULL when none left ++drmu_crtc_t * ++drmu_env_crtc_find_n(drmu_env_t * const du, const unsigned int n) ++{ ++ return n >= du->crtc_count ? NULL : du->crtcs + n; ++} ++ ++// Retrieve the the n-th conn ++// Use for iteration ++// Returns NULL when none left ++drmu_conn_t * ++drmu_env_conn_find_n(drmu_env_t * const du, const unsigned int n) ++{ ++ return n >= du->conn_count ? NULL : du->conns + n; ++} ++ ++drmu_plane_t * ++drmu_env_plane_find_n(drmu_env_t * const du, const unsigned int n) ++{ ++ return n >= du->plane_count ? NULL : du->planes + n; ++} ++ ++int ++drmu_ioctl(const drmu_env_t * const du, unsigned long req, void * arg) ++{ ++ while (ioctl(du->fd, req, arg)) { ++ const int err = errno; ++ // DRM docn suggests we should try again on EAGAIN as well as EINTR ++ // and drm userspace does this. ++ if (err != EINTR && err != EAGAIN) ++ return -err; ++ } ++ return 0; ++} ++ ++static void ++env_free_planes(drmu_env_t * const du) ++{ ++ uint32_t i; ++ for (i = 0; i != du->plane_count; ++i) ++ plane_uninit(du->planes + i); ++ free(du->planes); ++ du->plane_count = 0; ++ du->planes = NULL; ++} ++ ++static void ++env_free_conns(drmu_env_t * const du) ++{ ++ uint32_t i; ++ for (i = 0; i != du->conn_count; ++i) ++ conn_uninit(du->conns + i); ++ free(du->conns); ++ du->conn_count = 0; ++ du->conns = NULL; ++} ++ ++static void ++env_free_crtcs(drmu_env_t * const du) ++{ ++ uint32_t i; ++ for (i = 0; i != du->crtc_count; ++i) ++ crtc_uninit(du->crtcs + i); ++ free(du->crtcs); ++ du->crtc_count = 0; ++ du->crtcs = NULL; ++} ++ ++ ++static int ++env_planes_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids) ++{ ++ uint32_t i; ++ int rv; ++ ++ if ((du->planes = calloc(n, sizeof(*du->planes))) == NULL) { ++ drmu_err(du, "Plane array alloc failed"); ++ return -ENOMEM; ++ } ++ ++ for (i = 0; i != n; ++i) { ++ if ((rv = plane_init(du, du->planes + i, ids[i])) != 0) ++ goto fail2; ++ du->plane_count = i + 1; ++ } ++ return 0; ++ ++fail2: ++ env_free_planes(du); ++ return rv; ++} ++ ++// Doesn't clean up on error - assumes that env construction will abort and ++// that will tidy up for us ++static int ++env_conn_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids) ++{ ++ unsigned int i; ++ int rv; ++ ++ if (n == 0) { ++ drmu_err(du, "No connectors"); ++ return -EINVAL; ++ } ++ ++ if ((du->conns = calloc(n, sizeof(*du->conns))) == NULL) { ++ drmu_err(du, "Failed to malloc conns"); ++ return -ENOMEM; ++ } ++ ++ for (i = 0; i != n; ++i) { ++ if ((rv = conn_init(du, du->conns + i, i, ids[i])) != 0) ++ return rv; ++ du->conn_count = i + 1; ++ } ++ ++ return 0; ++} ++ ++static int ++env_crtc_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids) ++{ ++ unsigned int i; ++ int rv; ++ ++ if (n == 0) { ++ drmu_err(du, "No crtcs"); ++ return -EINVAL; ++ } ++ ++ if ((du->crtcs = malloc(n * sizeof(*du->crtcs))) == NULL) { ++ drmu_err(du, "Failed to malloc conns"); ++ return -ENOMEM; ++ } ++ ++ for (i = 0; i != n; ++i) { ++ if ((rv = crtc_init(du, du->crtcs + i, i, ids[i])) != 0) ++ return rv; ++ du->crtc_count = i + 1; ++ } ++ ++ return 0; ++} ++ ++ ++ ++int ++drmu_fd(const drmu_env_t * const du) ++{ ++ return du->fd; ++} ++ ++const struct drmu_log_env_s * ++drmu_env_log(const drmu_env_t * const du) ++{ ++ return &du->log; ++} ++ ++static struct drmu_bo_env_s * ++env_boe(drmu_env_t * const du) ++{ ++ return &du->boe; ++} ++ ++static struct pollqueue * ++env_pollqueue(const drmu_env_t * const du) ++{ ++ return du->pq; ++} ++ ++static struct drmu_atomic_q_s * ++env_atomic_q(drmu_env_t * const du) ++{ ++ return &du->aq; ++} ++ ++ ++static void ++env_free(drmu_env_t * const du) ++{ ++ if (!du) ++ return; ++ ++ atomic_q_flush(&du->aq); ++ ++ pollqueue_unref(&du->pq); ++ polltask_delete(&du->pt); ++ ++ atomic_q_uninit(&du->aq); ++ ++ if (du->da_restore) { ++ int rv; ++ if ((rv = drmu_atomic_commit(du->da_restore, DRM_MODE_ATOMIC_ALLOW_MODESET)) != 0) { ++ drmu_atomic_t * bad = drmu_atomic_new(du); ++ drmu_atomic_commit_test(du->da_restore, DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET, bad); ++ drmu_atomic_sub(du->da_restore, bad); ++ if ((rv = drmu_atomic_commit(du->da_restore, DRM_MODE_ATOMIC_ALLOW_MODESET)) != 0) ++ drmu_err(du, "Failed to restore old mode on exit: %s", strerror(-rv)); ++ else ++ drmu_err(du, "Failed to completely restore old mode on exit"); ++ ++ if (drmu_env_log(du)->max_level >= DRMU_LOG_LEVEL_DEBUG) ++ drmu_atomic_dump(bad); ++ drmu_atomic_unref(&bad); ++ } ++ drmu_atomic_unref(&du->da_restore); ++ } ++ ++ env_free_planes(du); ++ env_free_conns(du); ++ env_free_crtcs(du); ++ drmu_bo_env_uninit(&du->boe); ++ ++ close(du->fd); ++ free(du); ++} ++ ++void ++drmu_env_unref(drmu_env_t ** const ppdu) ++{ ++ drmu_env_t * const du = *ppdu; ++ if (!du) ++ return; ++ *ppdu = NULL; ++ ++ if (atomic_fetch_sub(&du->ref_count, 1) != 0) ++ return; ++ ++ env_free(du); ++} ++ ++drmu_env_t * ++drmu_env_ref(drmu_env_t * const du) ++{ ++ if (du) ++ atomic_fetch_add(&du->ref_count, 1); ++ return du; ++} ++ ++static int ++env_object_state_save(drmu_env_t * const du, const uint32_t obj_id, const uint32_t obj_type) ++{ ++ drmu_props_t * props; ++ drmu_atomic_t * da; ++ int rv; ++ ++ if (!du->da_restore) ++ return -EINVAL; ++ ++ if ((props = props_new(du, obj_id, obj_type)) == NULL) ++ return -ENOENT; ++ ++ if ((da = drmu_atomic_new(du)) == NULL) { ++ rv = -ENOMEM; ++ goto fail; ++ } ++ ++ if ((rv = drmu_atomic_props_add_save(da, obj_id, props)) != 0) ++ goto fail; ++ ++ props_free(props); ++ return drmu_atomic_env_restore_add_snapshot(&da); ++ ++fail: ++ if (props) ++ props_free(props); ++ return rv; ++} ++ ++int ++drmu_env_restore_enable(drmu_env_t * const du) ++{ ++ if (du->da_restore) ++ return 0; ++ if ((du->da_restore = drmu_atomic_new(du)) == NULL) ++ return -ENOMEM; ++ return 0; ++} ++ ++bool ++drmu_env_restore_is_enabled(const drmu_env_t * const du) ++{ ++ return du->da_restore != NULL; ++} ++ ++int ++drmu_atomic_env_restore_add_snapshot(drmu_atomic_t ** const ppda) ++{ ++ drmu_atomic_t * da = *ppda; ++ drmu_atomic_t * fails = NULL; ++ drmu_env_t * const du = drmu_atomic_env(da); ++ ++ *ppda = NULL; ++ ++ if (!du || !du->da_restore) { ++ drmu_atomic_unref(&da); ++ return 0; ++ } ++ ++ // Lose anything we can't restore ++ fails = drmu_atomic_new(du); ++ if (drmu_atomic_commit_test(da, DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_ATOMIC_TEST_ONLY, fails) != 0) ++ drmu_atomic_sub(da, fails); ++ drmu_atomic_unref(&fails); ++ ++ return drmu_atomic_merge(du->da_restore, &da); ++} ++ ++static void ++drmu_env_polltask_cb(void * v, short revents) ++{ ++ drmu_env_t * const du = v; ++ drmEventContext ctx = { ++ .version = DRM_EVENT_CONTEXT_VERSION, ++ .page_flip_handler2 = drmu_atomic_page_flip_cb, ++ }; ++ ++ if (revents == 0) { ++ drmu_debug(du, "%s: Timeout", __func__); ++ } ++ else { ++ drmHandleEvent(du->fd, &ctx); ++ } ++ ++ pollqueue_add_task(du->pt, 1000); ++} ++ ++static int ++env_set_client_cap(drmu_env_t * const du, uint64_t cap_id, uint64_t cap_val) ++{ ++ struct drm_set_client_cap cap = { ++ .capability = cap_id, ++ .value = cap_val ++ }; ++ return drmu_ioctl(du, DRM_IOCTL_SET_CLIENT_CAP, &cap); ++} ++ ++// Closes fd on failure ++drmu_env_t * ++drmu_env_new_fd(const int fd, const struct drmu_log_env_s * const log) ++{ ++ drmu_env_t * const du = calloc(1, sizeof(*du)); ++ int rv; ++ uint32_t * conn_ids = NULL; ++ uint32_t * crtc_ids = NULL; ++ uint32_t * plane_ids = NULL; ++ ++ if (!du) { ++ drmu_err_log(log, "Failed to create du: No memory"); ++ close(fd); ++ return NULL; ++ } ++ ++ du->log = (log == NULL) ? drmu_log_env_none : *log; ++ du->fd = fd; ++ ++ drmu_bo_env_init(&du->boe); ++ atomic_q_init(&du->aq); ++ ++ if ((du->pq = pollqueue_new()) == NULL) { ++ drmu_err(du, "Failed to create pollqueue"); ++ goto fail1; ++ } ++ if ((du->pt = polltask_new(du->pq, du->fd, POLLIN | POLLPRI, drmu_env_polltask_cb, du)) == NULL) { ++ drmu_err(du, "Failed to create polltask"); ++ goto fail1; ++ } ++ ++ // We want the primary plane for video ++ if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) != 0) ++ drmu_debug(du, "Failed to set universal planes cap"); ++ // We need atomic for almost everything we do ++ if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_ATOMIC, 1)) != 0) { ++ drmu_err(du, "Failed to set atomic cap"); ++ goto fail1; ++ } ++ // We can understand AR info ++ if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_ASPECT_RATIO, 1)) != 0) ++ drmu_debug(du, "Failed to set AR cap"); ++ // We would like to see writeback connectors ++ if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1)) != 0) ++ drmu_debug(du, "Failed to set writeback cap"); ++ ++ { ++ struct drm_mode_get_plane_res res; ++ uint32_t req_planes = 0; ++ ++ do { ++ memset(&res, 0, sizeof(res)); ++ res.plane_id_ptr = (uintptr_t)plane_ids; ++ res.count_planes = req_planes; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPLANERESOURCES, &res)) != 0) { ++ drmu_err(du, "Failed to get resources: %s", strerror(-rv)); ++ goto fail1; ++ } ++ } while ((rv = retry_alloc_u32(&plane_ids, &req_planes, res.count_planes)) == 1); ++ if (rv < 0) ++ goto fail1; ++ ++ if (env_planes_populate(du, res.count_planes, plane_ids) != 0) ++ goto fail1; ++ ++ free(plane_ids); ++ plane_ids = NULL; ++ } ++ ++ { ++ struct drm_mode_card_res res; ++ uint32_t req_conns = 0; ++ uint32_t req_crtcs = 0; ++ ++ for (;;) { ++ memset(&res, 0, sizeof(res)); ++ res.crtc_id_ptr = (uintptr_t)crtc_ids; ++ res.connector_id_ptr = (uintptr_t)conn_ids; ++ res.count_crtcs = req_crtcs; ++ res.count_connectors = req_conns; ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETRESOURCES, &res)) != 0) { ++ drmu_err(du, "Failed to get resources: %s", strerror(-rv)); ++ goto fail1; ++ } ++ ++ if (res.count_crtcs <= req_crtcs && res.count_connectors <= req_conns) ++ break; ++ ++ if (retry_alloc_u32(&conn_ids, &req_conns, res.count_connectors) < 0 || ++ retry_alloc_u32(&crtc_ids, &req_crtcs, res.count_crtcs) < 0) ++ goto fail1; ++ } ++ ++ if (env_conn_populate(du, res.count_connectors, conn_ids) != 0) ++ goto fail1; ++ if (env_crtc_populate(du, res.count_crtcs, crtc_ids) != 0) ++ goto fail1; ++ ++ free(conn_ids); ++ free(crtc_ids); ++ conn_ids = NULL; ++ crtc_ids = NULL; ++ } ++ ++ pollqueue_add_task(du->pt, 1000); ++ ++ free(plane_ids); ++ return du; ++ ++fail1: ++ env_free(du); ++ free(conn_ids); ++ free(crtc_ids); ++ free(plane_ids); ++ return NULL; ++} ++ ++drmu_env_t * ++drmu_env_new_open(const char * name, const struct drmu_log_env_s * const log2) ++{ ++ const struct drmu_log_env_s * const log = (log2 == NULL) ? &drmu_log_env_none : log2; ++ int fd = drmOpen(name, NULL); ++ if (fd == -1) { ++ drmu_err_log(log, "Failed to open %s", name); ++ return NULL; ++ } ++ return drmu_env_new_fd(fd, log); ++} ++ ++//---------------------------------------------------------------------------- ++// ++// Logging ++ ++static void ++log_none_cb(void * v, enum drmu_log_level_e level, const char * fmt, va_list vl) ++{ ++ (void)v; ++ (void)level; ++ (void)fmt; ++ (void)vl; ++} ++ ++const struct drmu_log_env_s drmu_log_env_none = { ++ .fn = log_none_cb, ++ .v = NULL, ++ .max_level = DRMU_LOG_LEVEL_NONE ++}; ++ ++void ++drmu_log_generic(const struct drmu_log_env_s * const log, const enum drmu_log_level_e level, ++ const char * const fmt, ...) ++{ ++ va_list vl; ++ va_start(vl, fmt); ++ log->fn(log->v, level, fmt, vl); ++ va_end(vl); ++} ++ ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu.h +@@ -0,0 +1,591 @@ ++#ifndef _DRMU_DRMU_H ++#define _DRMU_DRMU_H ++ ++#include ++#include ++#include ++#include ++ ++#include "drmu_chroma.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++struct drmu_blob_s; ++typedef struct drmu_blob_s drmu_blob_t; ++ ++struct drmu_prop_enum_s; ++typedef struct drmu_prop_enum_s drmu_prop_enum_t; ++ ++struct drmu_prop_range_s; ++typedef struct drmu_prop_range_s drmu_prop_range_t; ++ ++struct drmu_bo_s; ++typedef struct drmu_bo_s drmu_bo_t; ++struct drmu_bo_env_s; ++typedef struct drmu_bo_env_s drmu_bo_env_t; ++ ++struct drmu_fb_s; ++typedef struct drmu_fb_s drmu_fb_t; ++ ++struct drmu_prop_object_s; ++typedef struct drmu_prop_object_s drmu_prop_object_t; ++ ++struct drmu_pool_s; ++typedef struct drmu_pool_s drmu_pool_t; ++ ++struct drmu_crtc_s; ++typedef struct drmu_crtc_s drmu_crtc_t; ++ ++struct drmu_conn_s; ++typedef struct drmu_conn_s drmu_conn_t; ++ ++struct drmu_plane_s; ++typedef struct drmu_plane_s drmu_plane_t; ++ ++struct drmu_atomic_s; ++ ++struct drmu_env_s; ++typedef struct drmu_env_s drmu_env_t; ++ ++typedef struct drmu_rect_s { ++ int32_t x, y; ++ uint32_t w, h; ++} drmu_rect_t; ++ ++typedef struct drmu_ufrac_s { ++ unsigned int num; ++ unsigned int den; ++} drmu_ufrac_t; ++ ++// HDR enums is copied from linux include/linux/hdmi.h (strangely not part of uapi) ++enum hdmi_metadata_type ++{ ++ HDMI_STATIC_METADATA_TYPE1 = 0, ++}; ++enum hdmi_eotf ++{ ++ HDMI_EOTF_TRADITIONAL_GAMMA_SDR, ++ HDMI_EOTF_TRADITIONAL_GAMMA_HDR, ++ HDMI_EOTF_SMPTE_ST2084, ++ HDMI_EOTF_BT_2100_HLG, ++}; ++ ++typedef enum drmu_isset_e { ++ DRMU_ISSET_UNSET = 0, // Thing unset ++ DRMU_ISSET_NULL, // Thing is empty ++ DRMU_ISSET_SET, // Thing has valid data ++} drmu_isset_t; ++ ++drmu_ufrac_t drmu_ufrac_reduce(drmu_ufrac_t x); ++ ++static inline int_fast32_t ++drmu_rect_rescale_1s(int_fast32_t x, uint_fast32_t mul, uint_fast32_t div) ++{ ++ const int_fast64_t m = x * (int_fast64_t)mul; ++ const uint_fast32_t d2 = div/2; ++ return div == 0 ? (int_fast32_t)m : ++ m >= 0 ? (int_fast32_t)(((uint_fast64_t)m + d2) / div) : ++ -(int_fast32_t)(((uint_fast64_t)(-m) + d2) / div); ++} ++ ++static inline uint_fast32_t ++drmu_rect_rescale_1u(uint_fast32_t x, uint_fast32_t mul, uint_fast32_t div) ++{ ++ const uint_fast64_t m = x * (uint_fast64_t)mul; ++ return (uint_fast32_t)(div == 0 ? m : (m + div/2) / div); ++} ++ ++static inline drmu_rect_t ++drmu_rect_rescale(const drmu_rect_t s, const drmu_rect_t mul, const drmu_rect_t div) ++{ ++ return (drmu_rect_t){ ++ .x = drmu_rect_rescale_1s(s.x - div.x, mul.w, div.w) + mul.x, ++ .y = drmu_rect_rescale_1s(s.y - div.y, mul.h, div.h) + mul.y, ++ .w = drmu_rect_rescale_1u(s.w, mul.w, div.w), ++ .h = drmu_rect_rescale_1u(s.h, mul.h, div.h) ++ }; ++} ++ ++static inline drmu_rect_t ++drmu_rect_add_xy(const drmu_rect_t a, const drmu_rect_t b) ++{ ++ return (drmu_rect_t){ ++ .x = a.x + b.x, ++ .y = a.y + b.y, ++ .w = a.w, ++ .h = a.h ++ }; ++} ++ ++static inline drmu_rect_t ++drmu_rect_wh(const unsigned int w, const unsigned int h) ++{ ++ return (drmu_rect_t){ ++ .w = w, ++ .h = h ++ }; ++} ++ ++static inline drmu_rect_t ++drmu_rect_shl16(const drmu_rect_t a) ++{ ++ return (drmu_rect_t){ ++ .x = a.x << 16, ++ .y = a.y << 16, ++ .w = a.w << 16, ++ .h = a.h << 16 ++ }; ++} ++ ++// Blob ++ ++void drmu_blob_unref(drmu_blob_t ** const ppBlob); ++uint32_t drmu_blob_id(const drmu_blob_t * const blob); ++// blob data & length ++const void * drmu_blob_data(const drmu_blob_t * const blob); ++size_t drmu_blob_len(const drmu_blob_t * const blob); ++ ++drmu_blob_t * drmu_blob_ref(drmu_blob_t * const blob); ++// Make a new blob - keeps a copy of the data ++drmu_blob_t * drmu_blob_new(drmu_env_t * const du, const void * const data, const size_t len); ++// Update a blob with new data ++// Creates if it didn't exist before, unrefs if data NULL ++int drmu_blob_update(drmu_env_t * const du, drmu_blob_t ** const ppblob, const void * const data, const size_t len); ++// Create a new blob from an existing blob_id ++drmu_blob_t * drmu_blob_copy_id(drmu_env_t * const du, uint32_t blob_id); ++int drmu_atomic_add_prop_blob(struct drmu_atomic_s * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_blob_t * const blob); ++ ++// Enum & bitmask ++// These are very close to the same thing so we use the same struct ++typedef drmu_prop_enum_t drmu_prop_bitmask_t; ++ ++// Ptr to value of the named enum/bit, NULL if not found or pen == NULL. If bitmask then bit number ++const uint64_t * drmu_prop_enum_value(const drmu_prop_enum_t * const pen, const char * const name); ++// Bitmask only - value as a (single-bit) bitmask - 0 if not found or not bitmask or pen == NULL ++uint64_t drmu_prop_bitmask_value(const drmu_prop_enum_t * const pen, const char * const name); ++ ++uint32_t drmu_prop_enum_id(const drmu_prop_enum_t * const pen); ++#define drmu_prop_bitmask_id drmu_prop_enum_id ++void drmu_prop_enum_delete(drmu_prop_enum_t ** const pppen); ++#define drmu_prop_bitmask_delete drmu_prop_enum_delete ++drmu_prop_enum_t * drmu_prop_enum_new(drmu_env_t * const du, const uint32_t id); ++#define drmu_prop_bitmask_new drmu_prop_enum_new ++int drmu_atomic_add_prop_enum(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const char * const name); ++int drmu_atomic_add_prop_bitmask(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const uint64_t value); ++ ++// Range ++ ++void drmu_prop_range_delete(drmu_prop_range_t ** pppra); ++bool drmu_prop_range_validate(const drmu_prop_range_t * const pra, const uint64_t x); ++bool drmu_prop_range_immutable(const drmu_prop_range_t * const pra); ++uint64_t drmu_prop_range_max(const drmu_prop_range_t * const pra); ++uint64_t drmu_prop_range_min(const drmu_prop_range_t * const pra); ++uint32_t drmu_prop_range_id(const drmu_prop_range_t * const pra); ++const char * drmu_prop_range_name(const drmu_prop_range_t * const pra); ++drmu_prop_range_t * drmu_prop_range_new(drmu_env_t * const du, const uint32_t id); ++int drmu_atomic_add_prop_range(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_range_t * const pra, const uint64_t x); ++ ++// BO ++ ++struct drm_mode_create_dumb; ++ ++void drmu_bo_unref(drmu_bo_t ** const ppbo); ++drmu_bo_t * drmu_bo_ref(drmu_bo_t * const bo); ++drmu_bo_t * drmu_bo_new_fd(drmu_env_t *const du, const int fd); ++drmu_bo_t * drmu_bo_new_dumb(drmu_env_t *const du, struct drm_mode_create_dumb * const d); ++void drmu_bo_env_uninit(drmu_bo_env_t * const boe); ++void drmu_bo_env_init(drmu_bo_env_t * boe); ++ ++// fb ++struct hdr_output_metadata; ++struct drmu_fmt_info_s; ++ ++// Called pre delete. ++// Zero returned means continue delete. ++// Non-zero means stop delete - fb will have zero refs so will probably want a new ref ++// before next use ++typedef int (* drmu_fb_pre_delete_fn)(struct drmu_fb_s * dfb, void * v); ++// Called after an fb has been deleted and therefore has ceased using any ++// user resources ++typedef void (* drmu_fb_on_delete_fn)(void * v); ++ ++void drmu_fb_pre_delete_set(drmu_fb_t *const dfb, drmu_fb_pre_delete_fn fn, void * v); ++void drmu_fb_pre_delete_unset(drmu_fb_t *const dfb); ++unsigned int drmu_fb_pixel_bits(const drmu_fb_t * const dfb); ++uint32_t drmu_fb_pixel_format(const drmu_fb_t * const dfb); ++uint64_t drmu_fb_modifier(const drmu_fb_t * const dfb, const unsigned int plane); ++drmu_fb_t * drmu_fb_new_dumb(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format); ++drmu_fb_t * drmu_fb_new_dumb_mod(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod); ++drmu_fb_t * drmu_fb_realloc_dumb(drmu_env_t * const du, drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format); ++void drmu_fb_unref(drmu_fb_t ** const ppdfb); ++drmu_fb_t * drmu_fb_ref(drmu_fb_t * const dfb); ++ ++#define DRMU_FB_PIXEL_BLEND_UNSET NULL ++#define DRMU_FB_PIXEL_BLEND_PRE_MULTIPLIED "Pre-multiplied" // Default ++#define DRMU_FB_PIXEL_BLEND_COVERAGE "Coverage" // Not premultipled ++#define DRMU_FB_PIXEL_BLEND_NONE "None" // Ignore pixel alpha (opaque) ++int drmu_fb_pixel_blend_mode_set(drmu_fb_t *const dfb, const char * const mode); ++ ++uint32_t drmu_fb_pitch(const drmu_fb_t *const dfb, const unsigned int layer); ++// Pitch2 is only a sand thing ++uint32_t drmu_fb_pitch2(const drmu_fb_t *const dfb, const unsigned int layer); ++void * drmu_fb_data(const drmu_fb_t *const dfb, const unsigned int layer); ++uint32_t drmu_fb_width(const drmu_fb_t *const dfb); ++uint32_t drmu_fb_height(const drmu_fb_t *const dfb); ++// Set cropping (fractional) - x, y, relative to active x, y (and must be +ve) ++int drmu_fb_crop_frac_set(drmu_fb_t *const dfb, drmu_rect_t crop_frac); ++// get cropping (fractional 16.16) x, y relative to active area ++drmu_rect_t drmu_fb_crop_frac(const drmu_fb_t *const dfb); ++// get active area (all valid pixels - buffer can/will contain padding outside this) ++// rect in pixels (not fractional) ++drmu_rect_t drmu_fb_active(const drmu_fb_t *const dfb); ++ ++int drmu_atomic_add_prop_fb(struct drmu_atomic_s * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb); ++ ++// FB creation helpers - only use for creatino of new FBs ++drmu_fb_t * drmu_fb_int_alloc(drmu_env_t * const du); ++void drmu_fb_int_free(drmu_fb_t * const dfb); ++// Set size ++// w, h are buffer sizes, active is the valid pixel area inside that ++// crop will be set to the whole active area ++void drmu_fb_int_fmt_size_set(drmu_fb_t *const dfb, uint32_t fmt, uint32_t w, uint32_t h, const drmu_rect_t active); ++// All assumed to be const strings that do not need freed ++typedef const char * drmu_color_encoding_t; ++#define DRMU_COLOR_ENCODING_UNSET NULL ++#define DRMU_COLOR_ENCODING_BT2020 "ITU-R BT.2020 YCbCr" ++#define DRMU_COLOR_ENCODING_BT709 "ITU-R BT.709 YCbCr" ++#define DRMU_COLOR_ENCODING_BT601 "ITU-R BT.601 YCbCr" ++static inline bool drmu_color_encoding_is_set(const drmu_color_encoding_t x) {return x != NULL;} ++// Note: Color range only applies to YCbCr planes - ignored for RGB ++typedef const char * drmu_color_range_t; ++#define DRMU_COLOR_RANGE_UNSET NULL ++#define DRMU_COLOR_RANGE_YCBCR_FULL_RANGE "YCbCr full range" ++#define DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE "YCbCr limited range" ++static inline bool drmu_color_range_is_set(const drmu_color_range_t x) {return x != NULL;} ++typedef const char * drmu_colorspace_t; ++#define DRMU_COLORSPACE_UNSET NULL ++#define DRMU_COLORSPACE_DEFAULT "Default" ++#define DRMU_COLORSPACE_BT2020_CYCC "BT2020_CYCC" ++#define DRMU_COLORSPACE_BT2020_RGB "BT2020_RGB" ++#define DRMU_COLORSPACE_BT2020_YCC "BT2020_YCC" ++#define DRMU_COLORSPACE_BT709_YCC "BT709_YCC" ++#define DRMU_COLORSPACE_DCI_P3_RGB_D65 "DCI-P3_RGB_D65" ++#define DRMU_COLORSPACE_DCI_P3_RGB_THEATER "DCI-P3_RGB_Theater" ++#define DRMU_COLORSPACE_SMPTE_170M_YCC "SMPTE_170M_YCC" ++#define DRMU_COLORSPACE_SYCC_601 "SYCC_601" ++#define DRMU_COLORSPACE_XVYCC_601 "XVYCC_601" ++#define DRMU_COLORSPACE_XVYCC_709 "XVYCC_709" ++static inline bool drmu_colorspace_is_set(const drmu_colorspace_t x) {return x != NULL;} ++typedef const char * drmu_broadcast_rgb_t; ++#define DRMU_BROADCAST_RGB_UNSET NULL ++#define DRMU_BROADCAST_RGB_AUTOMATIC "Automatic" ++#define DRMU_BROADCAST_RGB_FULL "Full" ++#define DRMU_BROADCAST_RGB_LIMITED_16_235 "Limited 16:235" ++static inline bool drmu_broadcast_rgb_is_set(const drmu_broadcast_rgb_t x) {return x != NULL;} ++void drmu_fb_int_color_set(drmu_fb_t *const dfb, const drmu_color_encoding_t enc, const drmu_color_range_t range, const drmu_colorspace_t space); ++void drmu_fb_int_chroma_siting_set(drmu_fb_t *const dfb, const drmu_chroma_siting_t siting); ++void drmu_fb_int_on_delete_set(drmu_fb_t *const dfb, drmu_fb_on_delete_fn fn, void * v); ++void drmu_fb_int_bo_set(drmu_fb_t *const dfb, unsigned int i, drmu_bo_t * const bo); ++void drmu_fb_int_layer_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset); ++void drmu_fb_int_layer_mod_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset, uint64_t modifier); ++drmu_isset_t drmu_fb_hdr_metadata_isset(const drmu_fb_t *const dfb); ++const struct hdr_output_metadata * drmu_fb_hdr_metadata_get(const drmu_fb_t *const dfb); ++drmu_broadcast_rgb_t drmu_color_range_to_broadcast_rgb(const drmu_color_range_t range); ++drmu_colorspace_t drmu_fb_colorspace_get(const drmu_fb_t * const dfb); ++drmu_color_range_t drmu_fb_color_range_get(const drmu_fb_t * const dfb); ++const struct drmu_fmt_info_s * drmu_fb_format_info_get(const drmu_fb_t * const dfb); ++void drmu_fb_hdr_metadata_set(drmu_fb_t *const dfb, const struct hdr_output_metadata * meta); ++int drmu_fb_int_make(drmu_fb_t *const dfb); ++ ++// Wait for data to become ready when fb used as destination of writeback ++// Returns: ++// -ve error ++// 0 timeout ++// 1 ready ++int drmu_fb_out_fence_wait(drmu_fb_t * const fb, const int timeout_ms); ++ ++// fb pool ++ ++void drmu_pool_unref(drmu_pool_t ** const pppool); ++drmu_pool_t * drmu_pool_ref(drmu_pool_t * const pool); ++drmu_pool_t * drmu_pool_new(drmu_env_t * const du, unsigned int total_fbs_max); ++drmu_fb_t * drmu_pool_fb_new_dumb(drmu_pool_t * const pool, uint32_t w, uint32_t h, const uint32_t format); ++void drmu_pool_delete(drmu_pool_t ** const pppool); ++ ++// Object Id ++ ++struct drmu_propinfo_s; ++ ++uint32_t drmu_prop_object_value(const drmu_prop_object_t * const obj); ++void drmu_prop_object_unref(drmu_prop_object_t ** ppobj); ++drmu_prop_object_t * drmu_prop_object_new_propinfo(drmu_env_t * const du, const uint32_t obj_id, const struct drmu_propinfo_s * const pi); ++int drmu_atomic_add_prop_object(struct drmu_atomic_s * const da, drmu_prop_object_t * obj, uint32_t val); ++ ++// Props ++ ++// Grab all the props of an object and add to an atomic ++// * Does not add references to any properties (BO or FB) currently, it maybe ++// should but if so we need to avoid accidentally closing BOs that we inherit ++// from outside when we delete the atomic. ++int drmu_atomic_obj_add_snapshot(struct drmu_atomic_s * const da, const uint32_t objid, const uint32_t objtype); ++ ++// CRTC ++ ++struct _drmModeModeInfo; ++struct hdr_output_metadata; ++ ++void drmu_crtc_delete(drmu_crtc_t ** ppdc); ++drmu_env_t * drmu_crtc_env(const drmu_crtc_t * const dc); ++uint32_t drmu_crtc_id(const drmu_crtc_t * const dc); ++int drmu_crtc_idx(const drmu_crtc_t * const dc); ++ ++drmu_crtc_t * drmu_env_crtc_find_id(drmu_env_t * const du, const uint32_t crtc_id); ++drmu_crtc_t * drmu_env_crtc_find_n(drmu_env_t * const du, const unsigned int n); ++ ++typedef struct drmu_mode_simple_params_s { ++ unsigned int width; ++ unsigned int height; ++ unsigned int hz_x_1000; // Frame rate * 1000 i.e. 50Hz = 50000 (N.B. frame not field rate if interlaced) ++ drmu_ufrac_t par; // Picture Aspect Ratio (0:0 if unknown) ++ drmu_ufrac_t sar; // Sample Aspect Ratio ++ uint32_t type; ++ uint32_t flags; ++} drmu_mode_simple_params_t; ++ ++const struct drm_mode_modeinfo * drmu_crtc_modeinfo(const drmu_crtc_t * const dc); ++// Get simple properties of initial crtc mode ++drmu_mode_simple_params_t drmu_crtc_mode_simple_params(const drmu_crtc_t * const dc); ++ ++int drmu_atomic_crtc_add_modeinfo(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, const struct drm_mode_modeinfo * const modeinfo); ++int drmu_atomic_crtc_add_active(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, unsigned int val); ++ ++bool drmu_crtc_is_claimed(const drmu_crtc_t * const dc); ++void drmu_crtc_unref(drmu_crtc_t ** const ppdc); ++drmu_crtc_t * drmu_crtc_ref(drmu_crtc_t * const dc); ++// A Conn should be claimed before any op that might change its state ++int drmu_crtc_claim_ref(drmu_crtc_t * const dc); ++ ++// Connector ++ ++// Set none if m=NULL ++int drmu_atomic_conn_add_hdr_metadata(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const struct hdr_output_metadata * const m); ++ ++// False set max_bpc to 8, true max value ++int drmu_atomic_conn_add_hi_bpc(struct drmu_atomic_s * const da, drmu_conn_t * const dn, bool hi_bpc); ++ ++int drmu_atomic_conn_add_colorspace(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const drmu_colorspace_t colorspace); ++int drmu_atomic_conn_add_broadcast_rgb(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const drmu_broadcast_rgb_t bcrgb); ++ ++// Add crtc id ++int drmu_atomic_conn_add_crtc(struct drmu_atomic_s * const da, drmu_conn_t * const dn, drmu_crtc_t * const dc); ++ ++// Add writeback fb & fence ++// Neither makes sense without the other so do together ++int drmu_atomic_conn_add_writeback_fb(struct drmu_atomic_s * const da, drmu_conn_t * const dn, drmu_fb_t * const dfb); ++ ++ ++const struct drm_mode_modeinfo * drmu_conn_modeinfo(const drmu_conn_t * const dn, const int mode_id); ++drmu_mode_simple_params_t drmu_conn_mode_simple_params(const drmu_conn_t * const dn, const int mode_id); ++ ++// Beware: this refects initial value or the last thing set, but currently ++// has no way of guessing if the atomic from the set was ever committed ++// successfully ++uint32_t drmu_conn_crtc_id_get(const drmu_conn_t * const dn); ++ ++// Bitmask of CRTCs that might be able to use this Conn ++uint32_t drmu_conn_possible_crtcs(const drmu_conn_t * const dn); ++ ++bool drmu_conn_is_output(const drmu_conn_t * const dn); ++bool drmu_conn_is_writeback(const drmu_conn_t * const dn); ++const char * drmu_conn_name(const drmu_conn_t * const dn); ++unsigned int drmu_conn_idx_get(const drmu_conn_t * const dn); ++ ++// Retrieve the the n-th conn. Use for iteration. Returns NULL when none left ++drmu_conn_t * drmu_env_conn_find_n(drmu_env_t * const du, const unsigned int n); ++ ++bool drmu_conn_is_claimed(const drmu_conn_t * const dn); ++void drmu_conn_unref(drmu_conn_t ** const ppdn); ++drmu_conn_t * drmu_conn_ref(drmu_conn_t * const dn); ++// A Conn should be claimed before any op that might change its state ++int drmu_conn_claim_ref(drmu_conn_t * const dn); ++ ++ ++// Plane ++ ++uint32_t drmu_plane_id(const drmu_plane_t * const dp); ++ ++#define DRMU_PLANE_TYPE_CURSOR 4 ++#define DRMU_PLANE_TYPE_PRIMARY 2 ++#define DRMU_PLANE_TYPE_OVERLAY 1 ++#define DRMU_PLANE_TYPE_UNKNOWN 0 ++unsigned int drmu_plane_type(const drmu_plane_t * const dp); ++ ++const uint32_t * drmu_plane_formats(const drmu_plane_t * const dp, unsigned int * const pCount); ++bool drmu_plane_format_check(const drmu_plane_t * const dp, const uint32_t format, const uint64_t modifier); ++ ++// Alpha: -1 = no not set, 0 = transparent, 0xffff = opaque ++#define DRMU_PLANE_ALPHA_UNSET (-1) ++#define DRMU_PLANE_ALPHA_TRANSPARENT 0 ++#define DRMU_PLANE_ALPHA_OPAQUE 0xffff ++int drmu_atomic_plane_add_alpha(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int alpha); ++ ++int drmu_atomic_plane_add_zpos(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int zpos); ++ ++// X, Y & TRANSPOSE can be ORed to get all others ++#define DRMU_PLANE_ROTATION_0 0 ++#define DRMU_PLANE_ROTATION_X_FLIP 1 ++#define DRMU_PLANE_ROTATION_Y_FLIP 2 ++#define DRMU_PLANE_ROTATION_180 3 ++// *** These don't exist on Pi - no inherent transpose ++#define DRMU_PLANE_ROTATION_TRANSPOSE 4 ++#define DRMU_PLANE_ROTATION_90 5 // Rotate 90 clockwise ++#define DRMU_PLANE_ROTATION_270 6 // Rotate 90 anti-cockwise ++#define DRMU_PLANE_ROTATION_180_TRANSPOSE 7 // Rotate 180 & transpose ++int drmu_atomic_plane_add_rotation(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int rot); ++ ++int drmu_atomic_plane_add_chroma_siting(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const drmu_chroma_siting_t siting); ++ ++// Adds the fb to the plane along with all fb properties that apply to a plane ++// pos is dest rect on the plane in full pixels (not frac) ++int drmu_atomic_plane_add_fb(struct drmu_atomic_s * const da, drmu_plane_t * const dp, drmu_fb_t * const dfb, const drmu_rect_t pos); ++ ++// Unref a plane ++void drmu_plane_unref(drmu_plane_t ** const ppdp); ++ ++// Ref a plane - expects it is already associated ++drmu_plane_t * drmu_plane_ref(drmu_plane_t * const dp); ++ ++// Associate a plane with a crtc and ref it ++// Returns -EBUSY if plane already associated ++int drmu_plane_ref_crtc(drmu_plane_t * const dp, drmu_crtc_t * const dc); ++ ++typedef bool (*drmu_plane_new_find_ok_fn)(const drmu_plane_t * dp, void * v); ++ ++// Find a "free" plane that satisfies (returns true) the ok callback ++// Does not ref ++drmu_plane_t * drmu_plane_new_find(drmu_crtc_t * const dc, const drmu_plane_new_find_ok_fn cb, void * const v); ++// Find a "free" plane of the given type. Types can be ORed ++// Does not ref ++drmu_plane_t * drmu_plane_new_find_type(drmu_crtc_t * const dc, const unsigned int req_type); ++ ++drmu_plane_t * drmu_env_plane_find_n(drmu_env_t * const du, const unsigned int n); ++ ++ ++// Env ++struct drmu_log_env_s; ++ ++// Q the atomic on its associated env ++// ++// in-progress = The commit has been done but no ack yet ++// pending = Commit Qed to be done when the in-progress commit has ++// completed ++// ++// If there is a pending commit this atomic wiill be merged with it ++int drmu_atomic_queue(struct drmu_atomic_s ** ppda); ++// Wait for there to be no pending commit (there may be a commit in ++// progress) ++int drmu_env_queue_wait(drmu_env_t * const du); ++ ++// Do ioctl - returns -errno on error, 0 on success ++// deals with recalling the ioctl when required ++int drmu_ioctl(const drmu_env_t * const du, unsigned long req, void * arg); ++int drmu_fd(const drmu_env_t * const du); ++const struct drmu_log_env_s * drmu_env_log(const drmu_env_t * const du); ++void drmu_env_unref(drmu_env_t ** const ppdu); ++drmu_env_t * drmu_env_ref(drmu_env_t * const du); ++// Restore state on env close ++int drmu_env_restore_enable(drmu_env_t * const du); ++bool drmu_env_restore_is_enabled(const drmu_env_t * const du); ++// Add an object snapshot to the restore state ++// Tests for commitability and removes any props that won't commit ++int drmu_atomic_env_restore_add_snapshot(struct drmu_atomic_s ** const ppda); ++ ++// Open a drmu environment with the drm fd ++// Takes a logging structure so early errors can be reported. ++// If log = NULL logging is disabled (set to drmu_log_env_none). ++drmu_env_t * drmu_env_new_fd(const int fd, const struct drmu_log_env_s * const log); ++drmu_env_t * drmu_env_new_open(const char * name, const struct drmu_log_env_s * const log); ++ ++// Logging ++ ++enum drmu_log_level_e { ++ DRMU_LOG_LEVEL_NONE = -1, // Max level specifier for nothing (not a real level) ++ DRMU_LOG_LEVEL_MESSAGE = 0, // (Nearly) always printed info ++ DRMU_LOG_LEVEL_ERROR, // Error ++ DRMU_LOG_LEVEL_WARNING, ++ DRMU_LOG_LEVEL_INFO, // Interesting but not critical info ++ DRMU_LOG_LEVEL_DEBUG, // Info only useful for debug ++ DRMU_LOG_LEVEL_ALL, // Max level specifier for everything (not a real level) ++}; ++ ++typedef void drmu_log_fn(void * v, enum drmu_log_level_e level, const char * fmt, va_list vl); ++ ++typedef struct drmu_log_env_s { ++ drmu_log_fn * fn; ++ void * v; ++ enum drmu_log_level_e max_level; ++} drmu_log_env_t; ++ ++extern const struct drmu_log_env_s drmu_log_env_none; // pre-built do-nothing log structure ++ ++// drmu_atomic ++ ++struct drmu_atomic_s; ++typedef struct drmu_atomic_s drmu_atomic_t; ++ ++void drmu_atomic_dump(const drmu_atomic_t * const da); ++drmu_env_t * drmu_atomic_env(const drmu_atomic_t * const da); ++void drmu_atomic_unref(drmu_atomic_t ** const ppda); ++drmu_atomic_t * drmu_atomic_ref(drmu_atomic_t * const da); ++drmu_atomic_t * drmu_atomic_new(drmu_env_t * const du); ++int drmu_atomic_merge(drmu_atomic_t * const a, drmu_atomic_t ** const ppb); ++ ++// Remove all els in a that are also in b ++// b may be sorted (if not already) but is otherwise unchanged ++void drmu_atomic_sub(drmu_atomic_t * const a, drmu_atomic_t * const b); ++ ++// flags are DRM_MODE_ATOMIC_xxx (e.g. DRM_MODE_ATOMIC_TEST_ONLY) and DRM_MODE_PAGE_FLIP_xxx ++int drmu_atomic_commit(const drmu_atomic_t * const da, uint32_t flags); ++// Attempt commit - if it fails add failing members to da_fail ++// This does NOT remove failing props from da. If da_fail == NULL then same as _commit ++int drmu_atomic_commit_test(const drmu_atomic_t * const da, uint32_t flags, drmu_atomic_t * const da_fail); ++ ++typedef void drmu_prop_unref_fn(void * v); ++typedef void drmu_prop_ref_fn(void * v); ++typedef void drmu_prop_commit_fn(void * v, uint64_t value); ++ ++typedef struct drmu_atomic_prop_fns_s { ++ drmu_prop_ref_fn * ref; ++ drmu_prop_unref_fn * unref; ++ drmu_prop_commit_fn * commit; ++} drmu_atomic_prop_fns_t; ++ ++drmu_prop_ref_fn drmu_prop_fn_null_unref; ++drmu_prop_unref_fn drmu_prop_fn_null_ref; ++drmu_prop_commit_fn drmu_prop_fn_null_commit; ++ ++int drmu_atomic_add_prop_generic(drmu_atomic_t * const da, ++ const uint32_t obj_id, const uint32_t prop_id, const uint64_t value, ++ const drmu_atomic_prop_fns_t * const fns, void * const v); ++int drmu_atomic_add_prop_value(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, const uint64_t value); ++ ++// drmu_xlease ++ ++drmu_env_t * drmu_env_new_xlease(const struct drmu_log_env_s * const log); ++ ++// drmu_xdri3 ++ ++drmu_env_t * drmu_env_new_xdri3(const drmu_log_env_t * const log); ++ ++// drmu_waylease ++ ++drmu_env_t * drmu_env_new_waylease(const struct drmu_log_env_s * const log); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu_atomic.c +@@ -0,0 +1,831 @@ ++#include "drmu.h" ++#include "drmu_log.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++// Atomic property chain structures - no external visibility ++typedef struct aprop_prop_s { ++ uint32_t id; ++ uint64_t value; ++ void * v; ++ const drmu_atomic_prop_fns_t * fns; ++} aprop_prop_t; ++ ++typedef struct aprop_obj_s { ++ uint32_t id; ++ unsigned int n; ++ unsigned int size; ++ bool unsorted; ++ aprop_prop_t * props; ++} aprop_obj_t; ++ ++typedef struct aprop_hdr_s { ++ unsigned int n; ++ unsigned int size; ++ bool unsorted; ++ aprop_obj_t * objs; ++} aprop_hdr_t; ++ ++typedef struct drmu_atomic_s { ++ atomic_int ref_count; // 0 == 1 ref for ease of init ++ ++ struct drmu_env_s * du; ++ ++ aprop_hdr_t props; ++} drmu_atomic_t; ++ ++static inline unsigned int ++max_uint(const unsigned int a, const unsigned int b) ++{ ++ return a < b ? b : a; ++} ++ ++static void ++aprop_prop_unref(aprop_prop_t * const pp) ++{ ++ pp->fns->unref(pp->v); ++} ++ ++static void ++aprop_prop_ref(aprop_prop_t * const pp) ++{ ++ pp->fns->ref(pp->v); ++} ++ ++static void ++aprop_obj_uninit(aprop_obj_t * const po) ++{ ++ unsigned int i; ++ for (i = 0; i != po->n; ++i) ++ aprop_prop_unref(po->props + i); ++ free(po->props); ++ memset(po, 0, sizeof(*po)); ++} ++ ++static int ++aprop_obj_copy(aprop_obj_t * const po_c, const aprop_obj_t * const po_a) ++{ ++ unsigned int i; ++ aprop_prop_t * props; ++ ++ aprop_obj_uninit(po_c); ++ if (po_a->n == 0) ++ return 0; ++ ++ if ((props = calloc(po_a->size, sizeof(*props))) == NULL) ++ return -ENOMEM; ++ memcpy(props, po_a->props, po_a->n * sizeof(*po_a->props)); ++ ++ *po_c = *po_a; ++ po_c->props = props; ++ ++ for (i = 0; i != po_a->n; ++i) ++ aprop_prop_ref(props + i); ++ return 0; ++} ++ ++static void ++aprop_obj_move(aprop_obj_t * const po_c, aprop_obj_t * const po_a) ++{ ++ *po_c = *po_a; ++ memset(po_a, 0, sizeof(*po_a)); ++} ++ ++static int ++aprop_prop_qsort_cb(const void * va, const void * vb) ++{ ++ const aprop_prop_t * const a = va; ++ const aprop_prop_t * const b = vb; ++ return a->id == b->id ? 0 : a->id < b->id ? -1 : 1; ++} ++ ++static void ++aprop_obj_props_sort(aprop_obj_t * const po) ++{ ++ if (!po->unsorted) ++ return; ++ qsort(po->props, po->n, sizeof(po->props[0]), aprop_prop_qsort_cb); ++ po->unsorted = false; ++} ++ ++// Merge b into a and put the result in c. a & b are uninit on exit ++// Could (easily) merge into a but its more convienient for the caller to create new ++static int ++aprop_obj_merge(aprop_obj_t * const po_c, aprop_obj_t * const po_a, aprop_obj_t * const po_b) ++{ ++ unsigned int i, j, k; ++ unsigned int c_size; ++ aprop_prop_t * c; ++ aprop_prop_t * const a = po_a->props; ++ aprop_prop_t * const b = po_b->props; ++ ++ // As we should have no identical els we don't care that qsort is unstable ++ aprop_obj_props_sort(po_a); ++ aprop_obj_props_sort(po_b); ++ ++ // Pick a size ++ c_size = max_uint(po_a->size, po_b->size); ++ if (c_size < po_a->n + po_b->n) ++ c_size *= 2; ++ if ((c = calloc(c_size, sizeof(*c))) == NULL) ++ return -ENOMEM; ++ ++ for (i = 0, j = 0, k = 0; i < po_a->n && j < po_b->n; ++k) { ++ if (a[i].id < b[j].id) ++ c[k] = a[i++]; ++ else if (a[i].id > b[j].id) ++ c[k] = b[j++]; ++ else { ++ c[k] = b[j++]; ++ aprop_prop_unref(a + i++); ++ } ++ } ++ for (; i < po_a->n; ++i, ++k) ++ c[k] = a[i]; ++ for (; j < po_b->n; ++j, ++k) ++ c[k] = b[j]; ++ ++ *po_c = (aprop_obj_t){ ++ .id = po_a->id, ++ .n = k, ++ .size = c_size, ++ .unsorted = false, ++ .props = c ++ }; ++ ++ // We have avoided excess ref / unref by simple copy so just free the props array ++ free(a); ++ free(b); ++ ++ memset(po_a, 0, sizeof(*po_a)); ++ memset(po_b, 0, sizeof(*po_b)); ++ ++ return 0; ++} ++ ++// Remove any props in a that are also in b ++// b must be sorted ++// Returns count of props remaining in a ++static unsigned int ++aprop_obj_sub(aprop_obj_t * const po_a, const aprop_obj_t * const po_b) ++{ ++ unsigned int i = 0, j = 0, k; ++ aprop_prop_t * const a = po_a->props; ++ const aprop_prop_t * const b = po_b->props; ++ ++ if (po_a->n == 0 || po_b->n == 0) ++ return po_a->n; ++ ++ // As we should have no identical els we don't care that qsort is unstable ++ aprop_obj_props_sort(po_a); ++ assert(!po_b->unsorted); ++ ++ // Skip initial non-matches, returning if no match found ++ while (a[i].id != b[j].id) { ++ if (a[i].id < b[j].id) { ++ if (++i == po_a->n) ++ return po_a->n; ++ } ++ else { ++ if (++j == po_b->n) ++ return po_a->n; ++ } ++ } ++ // We have a match - next loop will do the unref ++ k = i; ++ ++ do { ++ if (a[i].id < b[j].id) ++ a[k++] = a[i++]; ++ else { ++ if (a[i].id == b[j].id) ++ aprop_prop_unref(a + i++); ++ j++; ++ } ++ } while (i != po_a->n && j != po_b->n); ++ ++ for (; i < po_a->n; ++i, ++k) ++ a[k] = a[i]; ++ po_a->n = k; ++ ++ return po_a->n; ++} ++ ++ ++static aprop_prop_t * ++aprop_obj_prop_get(aprop_obj_t * const po, const uint32_t id) ++{ ++ unsigned int i; ++ aprop_prop_t * pp = po->props; ++ ++ static const drmu_atomic_prop_fns_t null_fns = { ++ .ref = drmu_prop_fn_null_ref, ++ .unref = drmu_prop_fn_null_unref, ++ .commit = drmu_prop_fn_null_commit ++ }; ++ ++ for (i = 0; i != po->n; ++i, ++pp) { ++ if (pp->id == id) ++ return pp; ++ } ++ ++ if (po->n >= po->size) { ++ size_t newsize = po->size < 16 ? 16 : po->size * 2; ++ if ((pp = realloc(po->props, newsize * sizeof(*pp))) == NULL) ++ return NULL; ++ memset(pp + po->size, 0, (newsize - po->size) * sizeof(*pp)); ++ ++ po->props = pp; ++ po->size = newsize; ++ pp += po->n; ++ } ++ if (!po->unsorted && po->n != 0 && pp[-1].id > id) ++ po->unsorted = true; ++ ++po->n; ++ ++ pp->id = id; ++ pp->fns = &null_fns; ++ return pp; ++} ++ ++static void ++aprop_obj_atomic_fill(const aprop_obj_t * const po, uint32_t * prop_ids, uint64_t * prop_values) ++{ ++ unsigned int i; ++ for (i = 0; i != po->n; ++i) { ++ *prop_ids++ = po->props[i].id; ++ *prop_values++ = po->props[i].value; ++ } ++} ++ ++static void ++aprop_obj_dump(drmu_env_t * const du, const aprop_obj_t * const po) ++{ ++ unsigned int i; ++ drmu_info(du, "Obj: %02x: size %d n %d", po->id, po->size, po->n); ++ for (i = 0; i != po->n; ++i) { ++ struct drm_mode_get_property pattr = {.prop_id = po->props[i].id}; ++ drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &pattr); ++ ++ drmu_info(du, "Obj %02x: Prop %02x (%s) Value %"PRIx64" v %p", po->id, po->props[i].id, pattr.name, po->props[i].value, po->props[i].v); ++ } ++} ++ ++static void ++aprop_hdr_dump(drmu_env_t * const du, const aprop_hdr_t * const ph) ++{ ++ unsigned int i; ++ drmu_info(du, "Header: size %d n %d", ph->size, ph->n); ++ for (i = 0; i != ph->n; ++i) ++ aprop_obj_dump(du, ph->objs + i); ++} ++ ++static aprop_obj_t * ++aprop_hdr_obj_get(aprop_hdr_t * const ph, const uint32_t id) ++{ ++ unsigned int i; ++ aprop_obj_t * po = ph->objs; ++ ++ for (i = 0; i != ph->n; ++i, ++po) { ++ if (po->id == id) ++ return po; ++ } ++ ++ if (ph->n >= ph->size) { ++ size_t newsize = ph->size < 16 ? 16 : ph->size * 2; ++ if ((po = realloc(ph->objs, newsize * sizeof(*po))) == NULL) ++ return NULL; ++ memset(po + ph->size, 0, (newsize - ph->size) * sizeof(*po)); ++ ++ ph->objs = po; ++ ph->size = newsize; ++ po += ph->n; ++ } ++ if (!ph->unsorted && ph->n != 0 && po[-1].id > id) ++ ph->unsorted = true; ++ ++ph->n; ++ ++ po->id = id; ++ return po; ++} ++ ++static void ++aprop_hdr_uninit(aprop_hdr_t * const ph) ++{ ++ unsigned int i; ++ for (i = 0; i != ph->n; ++i) ++ aprop_obj_uninit(ph->objs + i); ++ free(ph->objs); ++ memset(ph, 0, sizeof(*ph)); ++} ++ ++static int ++aprop_hdr_copy(aprop_hdr_t * const ph_c, const aprop_hdr_t * const ph_a) ++{ ++ unsigned int i; ++ ++ aprop_hdr_uninit(ph_c); ++ ++ if (ph_a->n == 0) ++ return 0; ++ ++ if ((ph_c->objs = calloc(ph_a->size, sizeof(*ph_c->objs))) == NULL) ++ return -ENOMEM; ++ ++ ph_c->n = ph_a->n; ++ ph_c->size = ph_a->size; ++ ph_c->unsorted = ph_a->unsorted; ++ ++ for (i = 0; i != ph_a->n; ++i) ++ aprop_obj_copy(ph_c->objs + i, ph_a->objs + i); ++ return 0; ++} ++ ++// Move b to a. a must be empty ++static int ++aprop_hdr_move(aprop_hdr_t * const ph_a, aprop_hdr_t * const ph_b) ++{ ++ *ph_a = *ph_b; ++ *ph_b = (aprop_hdr_t){0}; ++ return 0; ++} ++ ++static int ++aprop_obj_qsort_cb(const void * va, const void * vb) ++{ ++ const aprop_obj_t * const a = va; ++ const aprop_obj_t * const b = vb; ++ return a->id == b->id ? 0 : a->id < b->id ? -1 : 1; ++} ++ ++// As we should have no identical els we don't care that qsort is unstable ++// Doesn't sort props ++static void ++aprop_hdr_sort(aprop_hdr_t * const ph) ++{ ++ if (!ph->unsorted) ++ return; ++ qsort(ph->objs, ph->n, sizeof(ph->objs[0]), aprop_obj_qsort_cb); ++ ph->unsorted = false; ++} ++ ++// Merge b into a. b will be uninited ++static int ++aprop_hdr_merge(aprop_hdr_t * const ph_a, aprop_hdr_t * const ph_b) ++{ ++ unsigned int i, j, k; ++ unsigned int c_size; ++ aprop_obj_t * c; ++ aprop_obj_t * const a = ph_a->objs; ++ aprop_obj_t * const b = ph_b->objs; ++ ++ if (ph_b->n == 0) ++ return 0; ++ if (ph_a->n == 0) ++ return aprop_hdr_move(ph_a, ph_b); ++ ++ aprop_hdr_sort(ph_a); ++ aprop_hdr_sort(ph_b); ++ ++ // Pick a size ++ c_size = max_uint(ph_a->size, ph_b->size); ++ if (c_size < ph_a->n + ph_b->n) ++ c_size *= 2; ++ if ((c = calloc(c_size, sizeof(*c))) == NULL) ++ return -ENOMEM; ++ ++ for (i = 0, j = 0, k = 0; i < ph_a->n && j < ph_b->n; ++k) { ++ if (a[i].id < b[j].id) ++ aprop_obj_move(c + k, a + i++); ++ else if (a[i].id > b[j].id) ++ aprop_obj_move(c + k, b + j++); ++ else ++ aprop_obj_merge(c + k, a + i++, b + j++); ++ } ++ for (; i < ph_a->n; ++i, ++k) ++ aprop_obj_move(c + k, a + i); ++ for (; j < ph_b->n; ++j, ++k) ++ aprop_obj_move(c + k, b + j); ++ ++ aprop_hdr_uninit(ph_a); ++ aprop_hdr_uninit(ph_b); ++ ++ ph_a->n = k; ++ ph_a->size = c_size; ++ ph_a->objs = c; ++ // Merge will maintain sort so leave unsorted false ++ ++ return 0; ++} ++ ++// Remove any props in a that are also in b ++// b must be sorted ++static void ++aprop_hdr_sub(aprop_hdr_t * const ph_a, const aprop_hdr_t * const ph_b) ++{ ++ unsigned int i = 0, j = 0, k; ++ aprop_obj_t * const a = ph_a->objs; ++ const aprop_obj_t * const b = ph_b->objs; ++ ++ aprop_hdr_sort(ph_a); ++ assert(!ph_b->unsorted); ++ ++ // Scan whilst we haven't deleted anything ++ for (;;) { ++ // If we run out of either array then nothing more needed ++ if (i == ph_a->n || j == ph_b->n) ++ return; ++ ++ if (a[i].id < b[j].id) ++ ++i; ++ else if (a[i].id > b[j].id) ++ ++j; ++ else { ++ k = i; ++ if (aprop_obj_sub(a + i++, b + j++) == 0) { ++ aprop_obj_uninit(a + k); ++ break; ++ } ++ } ++ } ++ ++ // Move & scan ++ while (i < ph_a->n && j < ph_b->n) { ++ if (a[i].id < b[j].id) ++ aprop_obj_move(a + k++, a + i++); ++ else if (a[i].id > b[j].id) ++ j++; ++ else { ++ if (aprop_obj_sub(a + i, b + j) == 0) ++ aprop_obj_uninit(a + i); ++ else ++ aprop_obj_move(a + k++, a + i); ++ i++; ++ j++; ++ } ++ } ++ ++ // Move any remaining entries ++ for (; i < ph_a->n; ++i, ++k) ++ aprop_obj_move(a + k, a + i); ++ ph_a->n = k; ++ ++ return; ++} ++ ++// Sort header objs & obj props ++static void ++aprop_hdr_props_sort(aprop_hdr_t * const ph) ++{ ++ aprop_hdr_sort(ph); ++ for (unsigned int i = 0; i != ph->n; ++i) ++ aprop_obj_props_sort(ph->objs + i); ++} ++ ++static aprop_prop_t * ++aprop_hdr_prop_get(aprop_hdr_t * const ph, const uint32_t obj_id, const uint32_t prop_id) ++{ ++ aprop_obj_t * const po = aprop_hdr_obj_get(ph, obj_id); ++ return po == NULL ? NULL : aprop_obj_prop_get(po, prop_id); ++} ++ ++// Total props ++static unsigned int ++aprop_hdr_props_count(const aprop_hdr_t * const ph) ++{ ++ unsigned int i; ++ unsigned int n = 0; ++ ++ for (i = 0; i != ph->n; ++i) ++ n += ph->objs[i].n; ++ return n; ++} ++ ++static unsigned int ++aprop_hdr_objs_count(const aprop_hdr_t * const ph) ++{ ++ return ph->n; ++} ++ ++static void ++aprop_hdr_atomic_fill(const aprop_hdr_t * const ph, ++ uint32_t * obj_ids, ++ uint32_t * prop_counts, ++ uint32_t * prop_ids, ++ uint64_t * prop_values) ++{ ++ unsigned int i; ++ for (i = 0; i != ph->n; ++i) { ++ const unsigned int n = ph->objs[i].n; ++ *obj_ids++ = ph->objs[i].id; ++ *prop_counts++ = n; ++ aprop_obj_atomic_fill(ph->objs +i, prop_ids, prop_values); ++ prop_ids += n; ++ prop_values += n; ++ } ++} ++ ++void ++drmu_prop_fn_null_unref(void * v) ++{ ++ (void)v; ++} ++ ++void ++drmu_prop_fn_null_ref(void * v) ++{ ++ (void)v; ++} ++ ++void ++drmu_prop_fn_null_commit(void * v, uint64_t value) ++{ ++ (void)v; ++ (void)value; ++} ++ ++int ++drmu_atomic_add_prop_generic(drmu_atomic_t * const da, ++ const uint32_t obj_id, const uint32_t prop_id, const uint64_t value, ++ const drmu_atomic_prop_fns_t * const fns, void * const v) ++{ ++ aprop_hdr_t * const ph = &da->props; ++ ++ if (obj_id == 0 || prop_id == 0) ++ { ++ return -EINVAL; ++ } ++ else ++ { ++ aprop_prop_t *const pp = aprop_hdr_prop_get(ph, obj_id, prop_id); ++ if (pp == NULL) ++ return -ENOMEM; ++ ++ aprop_prop_unref(pp); ++ pp->value = value; ++ if (fns) { ++ pp->fns = fns; ++ pp->v = v; ++ } ++ aprop_prop_ref(pp); ++ return 0; ++ } ++} ++ ++int ++drmu_atomic_add_prop_value(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, const uint64_t value) ++{ ++ if (drmu_atomic_add_prop_generic(da, obj_id, prop_id, value, NULL, NULL) < 0) ++ drmu_warn(drmu_atomic_env(da), "%s: Failed to set obj_id=%#x, prop_id=%#x, val=%" PRId64, __func__, ++ obj_id, prop_id, value); ++ return 0; ++} ++ ++void ++drmu_atomic_dump(const drmu_atomic_t * const da) ++{ ++ drmu_info(da->du, "Atomic %p: refs %d", da, atomic_load(&da->ref_count)+1); ++ aprop_hdr_dump(da->du, &da->props); ++} ++ ++drmu_env_t * ++drmu_atomic_env(const drmu_atomic_t * const da) ++{ ++ return da == NULL ? NULL : da->du; ++} ++ ++static void ++drmu_atomic_free(drmu_atomic_t * const da) ++{ ++ aprop_hdr_uninit(&da->props); ++ free(da); ++} ++ ++void ++drmu_atomic_unref(drmu_atomic_t ** const ppda) ++{ ++ drmu_atomic_t * const da = *ppda; ++ ++ if (da == NULL) ++ return; ++ *ppda = NULL; ++ ++ if (atomic_fetch_sub(&da->ref_count, 1) == 0) ++ drmu_atomic_free(da); ++} ++ ++drmu_atomic_t * ++drmu_atomic_ref(drmu_atomic_t * const da) ++{ ++ atomic_fetch_add(&da->ref_count, 1); ++ return da; ++} ++ ++drmu_atomic_t * ++drmu_atomic_new(drmu_env_t * const du) ++{ ++ drmu_atomic_t * const da = calloc(1, sizeof(*da)); ++ ++ if (da == NULL) { ++ drmu_err(du, "%s: Failed to alloc struct", __func__); ++ return NULL; ++ } ++ da->du = du; ++ ++ return da; ++} ++ ++// Merge b into a. b is unrefed (inc on error) ++int ++drmu_atomic_merge(drmu_atomic_t * const a, drmu_atomic_t ** const ppb) ++{ ++ drmu_atomic_t * b = *ppb; ++ aprop_hdr_t bh = {0}; ++ int rv = -EINVAL; ++ ++ if (b == NULL) ++ return 0; ++ *ppb = NULL; ++ ++ if (a == NULL) { ++ drmu_atomic_unref(&b); ++ return -EINVAL; ++ } ++ // We expect this to be the sole ref to a ++ assert(atomic_load(&a->ref_count) == 0); ++ ++ // If this is the only copy of b then use it directly otherwise ++ // copy before (probably) making it unusable ++ if (atomic_load(&b->ref_count) == 0) ++ rv = aprop_hdr_move(&bh, &b->props); ++ else ++ rv = aprop_hdr_copy(&bh, &b->props); ++ drmu_atomic_unref(&b); ++ ++ if (rv != 0) { ++ drmu_err(a->du, "%s: Copy Failed", __func__); ++ return rv; ++ } ++ ++ rv = aprop_hdr_merge(&a->props, &bh); ++ aprop_hdr_uninit(&bh); ++ ++ if (rv != 0) { ++ drmu_err(a->du, "%s: Merge Failed", __func__); ++ return rv; ++ } ++ ++ return 0; ++} ++ ++void ++drmu_atomic_sub(drmu_atomic_t * const a, drmu_atomic_t * const b) ++{ ++ aprop_hdr_props_sort(&b->props); ++ aprop_hdr_sub(&a->props, &b->props); ++} ++ ++static void ++atomic_props_crop(struct drm_mode_atomic * const f, const unsigned int n, uint32_t ** const undo_p, uint32_t * const undo_v) ++{ ++ unsigned int i; ++ unsigned int t = 0; ++ uint32_t * const c = (uint32_t *)(uintptr_t)f->count_props_ptr; ++ ++ for (i = 0; i != f->count_objs; ++i) { ++ t += c[i]; ++ if (t >= n) { ++ f->count_objs = i + 1; ++ *undo_p = c + i; ++ *undo_v = c[i]; ++ c[i] -= t - n; ++ break; ++ } ++ } ++} ++ ++static void ++atomic_props_del(struct drm_mode_atomic * const f, const unsigned int n, const unsigned int cp, ++ uint32_t * const objid, uint32_t * const propid, uint64_t * const val) ++{ ++ unsigned int i; ++ unsigned int t = 0; ++ uint32_t * const c = (uint32_t *)(uintptr_t)f->count_props_ptr; ++ uint32_t * const o = (uint32_t *)(uintptr_t)f->objs_ptr; ++ uint32_t * const p = (uint32_t *)(uintptr_t)f->props_ptr; ++ uint64_t * const v = (uint64_t *)(uintptr_t)f->prop_values_ptr; ++ ++ for (i = 0; i != f->count_objs; ++i) { ++ t += c[i]; ++ if (t > n) { ++ // Copy out what we are going to delete ++ *objid = o[i]; ++ *propid = p[n]; ++ *val = v[n]; ++ ++ memmove(p + n, p + n + 1, (cp - n - 1) * sizeof(*p)); ++ memmove(v + n, v + n + 1, (cp - n - 1) * sizeof(*v)); ++ ++ if (--c[i] == 0) { ++ memmove(c + i, c + i + 1, (f->count_objs - i - 1) * sizeof(*c)); ++ memmove(o + i, o + i + 1, (f->count_objs - i - 1) * sizeof(*o)); ++ --f->count_objs; ++ } ++ break; ++ } ++ } ++} ++ ++// Returns count of initial good els (i.e. n of 1st bad) ++static unsigned int ++commit_find_good(drmu_env_t * const du, const struct drm_mode_atomic * const atomic, const unsigned int n_props) ++{ ++ unsigned int a = 0; // N known good ++ unsigned int b = n_props + 1; // N maybe good + 1 ++ ++ while (a + 1 < b) { ++ struct drm_mode_atomic at = *atomic; ++ unsigned int n = (a + b) / 2; ++ int rv; ++ uint32_t * undo_p = NULL; ++ uint32_t undo_v = 0; ++ ++ at.flags = DRM_MODE_ATOMIC_TEST_ONLY | (DRM_MODE_ATOMIC_ALLOW_MODESET & atomic->flags); ++ atomic_props_crop(&at, n, &undo_p, &undo_v); ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_ATOMIC, &at)) == 0) { ++ a = n; ++ } ++ else { ++ b = n; ++ } ++ ++ *undo_p = undo_v; // Should always be set ++ } ++ ++ return a; ++} ++ ++// da_fail does not keep refs to its values - for info only ++int ++drmu_atomic_commit_test(const drmu_atomic_t * const da, uint32_t flags, drmu_atomic_t * const da_fail) ++{ ++ drmu_env_t * const du = da->du; ++ const unsigned int n_objs = aprop_hdr_objs_count(&da->props); ++ unsigned int n_props = aprop_hdr_props_count(&da->props); ++ int rv = 0; ++ ++ if (n_props != 0) { ++ uint32_t obj_ids[n_objs]; ++ uint32_t prop_counts[n_objs]; ++ uint32_t prop_ids[n_props]; ++ uint64_t prop_values[n_props]; ++ struct drm_mode_atomic atomic = { ++ .flags = flags, ++ .count_objs = n_objs, ++ .objs_ptr = (uintptr_t)obj_ids, ++ .count_props_ptr = (uintptr_t)prop_counts, ++ .props_ptr = (uintptr_t)prop_ids, ++ .prop_values_ptr = (uintptr_t)prop_values, ++ .user_data = (uintptr_t)da ++ }; ++ ++ aprop_hdr_atomic_fill(&da->props, obj_ids, prop_counts, prop_ids, prop_values); ++ ++ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_ATOMIC, &atomic)) == 0 || !da_fail) ++ return rv; ++ ++ for (;;) { ++ unsigned int a = commit_find_good(du, &atomic, n_props); ++ uint32_t objid = 0; ++ uint32_t propid = 0; ++ uint64_t val = 0; ++ ++ if (a >= n_props) ++ break; ++ ++ atomic_props_del(&atomic, a, n_props, &objid, &propid, &val); ++ --n_props; ++ ++ drmu_atomic_add_prop_value(da_fail, objid, propid, val); ++ } ++ } ++ ++ return rv; ++} ++ ++ ++int ++drmu_atomic_commit(const drmu_atomic_t * const da, uint32_t flags) ++{ ++ return drmu_atomic_commit_test(da, flags, NULL); ++} ++ ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu_chroma.h +@@ -0,0 +1,47 @@ ++#ifndef _DRMU_DRMU_CHROMA_H ++#define _DRMU_DRMU_CHROMA_H ++ ++#include ++#include ++ ++#include "drmu_chroma.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++typedef struct drmu_chroma_siting_s { ++ int32_t x, y; ++} drmu_chroma_siting_t; ++ ++// Init constants - C winges if the struct is specified in a const init (which seems like a silly error) ++#define drmu_chroma_siting_float_i(_x, _y) {.x = (int32_t)((double)(_x) * 65536 + .5), .y = (int32_t)((double)(_y) * 65536 + .5)} ++#define DRMU_CHROMA_SITING_BOTTOM_I drmu_chroma_siting_float_i(0.5, 1.0) ++#define DRMU_CHROMA_SITING_BOTTOM_LEFT_I drmu_chroma_siting_float_i(0.0, 1.0) ++#define DRMU_CHROMA_SITING_CENTER_I drmu_chroma_siting_float_i(0.5, 0.5) ++#define DRMU_CHROMA_SITING_LEFT_I drmu_chroma_siting_float_i(0.0, 0.5) ++#define DRMU_CHROMA_SITING_TOP_I drmu_chroma_siting_float_i(0.5, 0.0) ++#define DRMU_CHROMA_SITING_TOP_LEFT_I drmu_chroma_siting_float_i(0.0, 0.0) ++#define DRMU_CHROMA_SITING_UNSPECIFIED_I {INT32_MIN, INT32_MIN} ++// Inline constants ++#define drmu_chroma_siting_float(_x, _y) (drmu_chroma_siting_t)drmu_chroma_siting_float_i(_x, _y) ++#define DRMU_CHROMA_SITING_BOTTOM drmu_chroma_siting_float(0.5, 1.0) ++#define DRMU_CHROMA_SITING_BOTTOM_LEFT drmu_chroma_siting_float(0.0, 1.0) ++#define DRMU_CHROMA_SITING_CENTER drmu_chroma_siting_float(0.5, 0.5) ++#define DRMU_CHROMA_SITING_LEFT drmu_chroma_siting_float(0.0, 0.5) ++#define DRMU_CHROMA_SITING_TOP drmu_chroma_siting_float(0.5, 0.0) ++#define DRMU_CHROMA_SITING_TOP_LEFT drmu_chroma_siting_float(0.0, 0.0) ++#define DRMU_CHROMA_SITING_UNSPECIFIED (drmu_chroma_siting_t){INT32_MIN, INT32_MIN} ++ ++static inline bool ++drmu_chroma_siting_eq(const drmu_chroma_siting_t a, const drmu_chroma_siting_t b) ++{ ++ return a.x == b.x && a.y == b.y; ++} ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu_fmts.c +@@ -0,0 +1,249 @@ ++#include "drmu_fmts.h" ++ ++#include ++ ++#include ++ ++#ifndef HAS_SORTED_FMTS ++#define HAS_SORTED_FMTS 0 ++#endif ++#ifndef BUILD_MK_SORTED_FMTS_H ++#define BUILD_MK_SORTED_FMTS_H 0 ++#endif ++ ++#ifndef DRM_FORMAT_P030 ++#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') ++#endif ++ ++// Format properties ++ ++typedef struct drmu_fmt_info_s { ++ uint32_t fourcc; ++ uint8_t bpp; // For dumb BO alloc ++ uint8_t bit_depth; // For display ++ uint8_t plane_count; ++ struct { ++ uint8_t wdiv; ++ uint8_t hdiv; ++ } planes[4]; ++ drmu_chroma_siting_t chroma_siting; // Default for this format (YUV420 = (0.0, 0.5), otherwise (0, 0) ++} drmu_fmt_info_t; ++ ++#if BUILD_MK_SORTED_FMTS_H || !HAS_SORTED_FMTS ++ ++#define P_ONE {{.wdiv = 1, .hdiv = 1}} ++#define P_YC420 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 2}} ++#define P_YC422 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}} ++#define P_YC444 {{.wdiv = 2, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}} // Assumes doubled .bpp ++#define P_YUV420 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 2, .hdiv = 2}, {.wdiv = 2, .hdiv = 2}} ++#define P_YUV422 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 2, .hdiv = 1}, {.wdiv = 2, .hdiv = 1}} ++#define P_YUV444 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}} ++ ++// Not const 'cos we sort in place when creating the sorted version ++static drmu_fmt_info_t format_info[] = { ++ { .fourcc = DRM_FORMAT_XRGB1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_XBGR1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_RGBX5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_BGRX5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_ARGB1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_ABGR1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_RGBA5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_BGRA5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_BGR565, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_RGB565, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, ++ ++ { .fourcc = DRM_FORMAT_RGB888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_BGR888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ ++ { .fourcc = DRM_FORMAT_XRGB8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_XBGR8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_RGBX8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_BGRX8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_ARGB8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_ABGR8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_RGBA8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_BGRA8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ ++ { .fourcc = DRM_FORMAT_XRGB2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_XBGR2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_RGBX1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_BGRX1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_ARGB2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_ABGR2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_RGBA1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_BGRA1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ ++ { .fourcc = DRM_FORMAT_AYUV, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_XYUV8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_VUY888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_XVYU2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, ++ ++ { .fourcc = DRM_FORMAT_XVYU12_16161616, .bpp = 64, .bit_depth = 12, .plane_count = 1, .planes = P_ONE}, ++ { .fourcc = DRM_FORMAT_XVYU16161616, .bpp = 64, .bit_depth = 16, .plane_count = 1, .planes = P_ONE}, ++ ++ { .fourcc = DRM_FORMAT_YUYV, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_YVYU, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_VYUY, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_UYVY, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ ++ { .fourcc = DRM_FORMAT_NV12, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC420, ++ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, ++ { .fourcc = DRM_FORMAT_NV21, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC420, ++ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, ++ { .fourcc = DRM_FORMAT_P010, .bpp = 16, .bit_depth = 10, .plane_count = 2, .planes = P_YC420, ++ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, ++ { .fourcc = DRM_FORMAT_NV16, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC422, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_NV61, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC422, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_NV24, .bpp = 16, .bit_depth = 8, .plane_count = 2, .planes = P_YC444, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_NV42, .bpp = 16, .bit_depth = 8, .plane_count = 2, .planes = P_YC444, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ ++ { .fourcc = DRM_FORMAT_YUV420, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV420, ++ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, ++ { .fourcc = DRM_FORMAT_YVU420, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV420, ++ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, ++ { .fourcc = DRM_FORMAT_YUV422, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV422, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_YUV422, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV422, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_YUV444, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV444, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ { .fourcc = DRM_FORMAT_YUV444, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV444, ++ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, ++ ++ // 3 pel in 32 bits. So code as 32bpp with wdiv 3. ++ { .fourcc = DRM_FORMAT_P030, .bpp = 32, .bit_depth = 10, .plane_count = 2, ++ .planes = {{.wdiv = 3, .hdiv = 1}, {.wdiv = 3, .hdiv = 2}}, ++ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, ++ ++ { .fourcc = 0 } ++}; ++#endif ++ ++#if BUILD_MK_SORTED_FMTS_H ++// --------------------------------------------------------------------------- ++// ++// Sort & emit format table (not part of the lib) ++ ++#include ++#include ++#include ++ ++static const unsigned int format_count = sizeof(format_info)/sizeof(format_info[0]) - 1; // Ignore null term in count ++ ++static int sort_fn(const void * va, const void * vb) ++{ ++ const drmu_fmt_info_t * a = va; ++ const drmu_fmt_info_t * b = vb; ++ return a->fourcc < b->fourcc ? -1 : a->fourcc == b->fourcc ? 0 : 1; ++} ++ ++int ++main(int argc, char * argv[]) ++{ ++ FILE * f; ++ unsigned int i; ++ ++ if (argc != 2) { ++ fprintf(stderr, "Needs output file only\n"); ++ return 1; ++ } ++ if ((f = fopen(argv[1], "wt")) == NULL) { ++ fprintf(stderr, "Failed to open'%s'\n", argv[1]); ++ return 1; ++ } ++ qsort(format_info, format_count, sizeof(format_info[0]), sort_fn); ++ ++ fprintf(f, "static const drmu_fmt_info_t format_info[] = {\n"); ++ for (i = 0; i != format_count; ++i) { ++ const drmu_fmt_info_t * x = format_info + i; ++ unsigned int j; ++ fprintf(f, "{%#"PRIx32",%d,%d,%d,{", x->fourcc, x->bpp, x->bit_depth, x->plane_count); ++ for (j = 0; j != sizeof(x->planes)/sizeof(x->planes[0]); ++j) { ++ fprintf(f, "{%d,%d},", x->planes[j].wdiv, x->planes[j].hdiv); ++ } ++ fprintf(f, "},"); ++ fprintf(f, "{%d,%d},", x->chroma_siting.x, x->chroma_siting.y); ++ fprintf(f, "},\n"); ++ } ++ fprintf(f, "{0}\n};\n"); ++ fprintf(f, "static const unsigned int format_count = %d;\n", format_count); ++ ++ fclose(f); ++ return 0; ++} ++ ++#else ++// --------------------------------------------------------------------------- ++// ++// Include sorted format table ++#if HAS_SORTED_FMTS ++#include "sorted_fmts.h" ++#endif ++ ++const drmu_fmt_info_t * ++drmu_fmt_info_find_fmt(const uint32_t fourcc) ++{ ++ if (!fourcc) ++ return NULL; ++#if HAS_SORTED_FMTS ++ unsigned int lo = 0; ++ unsigned int hi = format_count; ++ ++ while (lo < hi) { ++ unsigned int x = (hi + lo) / 2; ++ if (format_info[x].fourcc == fourcc) ++ return &format_info[x]; ++ if (format_info[x].fourcc < fourcc) ++ lo = x + 1; ++ else ++ hi = x; ++ } ++#else ++ for (const drmu_fmt_info_t * p = format_info; p->fourcc; ++p) { ++ if (p->fourcc == fourcc) ++ return p; ++ } ++#endif ++ return NULL; ++} ++ ++unsigned int ++drmu_fmt_info_bit_depth(const drmu_fmt_info_t * const fmt_info) ++{ ++ return !fmt_info ? 0 : fmt_info->bit_depth; ++} ++uint32_t drmu_fmt_info_fourcc(const drmu_fmt_info_t * const fmt_info) ++{ ++ return fmt_info->fourcc; ++} ++unsigned int drmu_fmt_info_pixel_bits(const drmu_fmt_info_t * const fmt_info) ++{ ++ return !fmt_info ? 0 : fmt_info->bpp; ++} ++unsigned int drmu_fmt_info_plane_count(const drmu_fmt_info_t * const fmt_info) ++{ ++ return !fmt_info ? 0 : fmt_info->plane_count; ++} ++unsigned int drmu_fmt_info_wdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n) ++{ ++ return fmt_info->planes[plane_n].wdiv; ++} ++unsigned int drmu_fmt_info_hdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n) ++{ ++ return fmt_info->planes[plane_n].hdiv; ++} ++drmu_chroma_siting_t drmu_fmt_info_chroma_siting(const drmu_fmt_info_t * const fmt_info) ++{ ++ return !fmt_info ? DRMU_CHROMA_SITING_TOP_LEFT : fmt_info->chroma_siting; ++} ++ ++#endif ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu_fmts.h +@@ -0,0 +1,31 @@ ++#ifndef _DRMU_DRMU_FMTS_H ++#define _DRMU_DRMU_FMTS_H ++ ++#include ++#include ++ ++#include "drmu_chroma.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++struct drmu_fmt_info_s; ++typedef struct drmu_fmt_info_s drmu_fmt_info_t; ++ ++const drmu_fmt_info_t * drmu_fmt_info_find_fmt(const uint32_t fourcc); ++ ++unsigned int drmu_fmt_info_bit_depth(const drmu_fmt_info_t * const fmt_info); ++uint32_t drmu_fmt_info_fourcc(const drmu_fmt_info_t * const fmt_info); ++unsigned int drmu_fmt_info_pixel_bits(const drmu_fmt_info_t * const fmt_info); ++unsigned int drmu_fmt_info_plane_count(const drmu_fmt_info_t * const fmt_info); ++unsigned int drmu_fmt_info_wdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n); ++unsigned int drmu_fmt_info_hdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n); ++drmu_chroma_siting_t drmu_fmt_info_chroma_siting(const drmu_fmt_info_t * const fmt_info); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu_log.h +@@ -0,0 +1,53 @@ ++#ifndef _DRMU_DRMU_LOG_H ++#define _DRMU_DRMU_LOG_H ++ ++#include ++ ++struct drmu_env_s; ++ ++void drmu_log_generic(const struct drmu_log_env_s * const log, const enum drmu_log_level_e level, ++ const char * const fmt, ...); ++ ++#define drmu_log_macro(_log, _level, _fmt, ...) do {\ ++ const drmu_log_env_t * const _log2 = (_log);\ ++ if ((_level) <= _log2->max_level)\ ++ drmu_log_generic(_log2, (_level), "%s:%u:%s: " _fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);\ ++} while (0) ++ ++// Char offset if file, line extracted - func still in format ++#define DRMU_LOG_FMT_OFFSET_FUNC 6 ++// Char offset if file, line & fn extracted ++#define DRMU_LOG_FMT_OFFSET_FMT 10 ++ ++#define drmu_err_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_ERROR, __VA_ARGS__) ++#define drmu_warn_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_WARNING, __VA_ARGS__) ++#define drmu_info_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_INFO, __VA_ARGS__) ++#define drmu_debug_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_DEBUG, __VA_ARGS__) ++ ++#define drmu_err(_du, ...) drmu_err_log(drmu_env_log(_du), __VA_ARGS__) ++#define drmu_warn(_du, ...) drmu_warn_log(drmu_env_log(_du), __VA_ARGS__) ++#define drmu_info(_du, ...) drmu_info_log(drmu_env_log(_du), __VA_ARGS__) ++#define drmu_debug(_du, ...) drmu_debug_log(drmu_env_log(_du), __VA_ARGS__) ++ ++static inline char drmu_log_safechar(int c) ++{ ++ return (c < ' ' || c >=0x7f) ? '?' : c; ++} ++ ++static inline const char * drmu_log_fourcc_to_str(char buf[5], uint32_t fcc) ++{ ++ if (fcc == 0) ++ return "----"; ++ buf[0] = drmu_log_safechar((fcc >> 0) & 0xff); ++ buf[1] = drmu_log_safechar((fcc >> 8) & 0xff); ++ buf[2] = drmu_log_safechar((fcc >> 16) & 0xff); ++ buf[3] = drmu_log_safechar((fcc >> 24) & 0xff); ++ buf[4] = 0; ++ return buf; ++} ++ ++#define drmu_log_fourcc(fcc) drmu_log_fourcc_to_str((char[5]){0}, fcc) ++ ++ ++#endif ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu_output.c +@@ -0,0 +1,615 @@ ++#include "drmu_output.h" ++ ++#include "drmu_fmts.h" ++#include "drmu_log.h" ++ ++#include ++#include ++ ++#include ++#include ++ ++// Update return value with a new one for cases where we don't stop on error ++static inline int rvup(int rv1, int rv2) ++{ ++ return rv2 ? rv2 : rv1; ++} ++ ++struct drmu_output_s { ++ drmu_env_t * du; ++ drmu_crtc_t * dc; ++ unsigned int conn_n; ++ unsigned int conn_size; ++ drmu_conn_t ** dns; ++ bool has_max_bpc; ++ bool max_bpc_allow; ++ bool modeset_allow; ++ int mode_id; ++ drmu_mode_simple_params_t mode_params; ++ ++ // These are expected to be static consts so no copy / no free ++ const drmu_fmt_info_t * fmt_info; ++ drmu_colorspace_t colorspace; ++ drmu_broadcast_rgb_t broadcast_rgb; ++ ++ // HDR metadata ++ drmu_isset_t hdr_metadata_isset; ++ struct hdr_output_metadata hdr_metadata; ++}; ++ ++drmu_plane_t * ++drmu_output_plane_ref_primary(drmu_output_t * const dout) ++{ ++ drmu_plane_t * const dp = drmu_plane_new_find_type(dout->dc, DRMU_PLANE_TYPE_PRIMARY); ++ ++ if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0) ++ return NULL; ++ ++ return dp; ++} ++ ++drmu_plane_t * ++drmu_output_plane_ref_other(drmu_output_t * const dout) ++{ ++ drmu_plane_t *const dp = drmu_plane_new_find_type(dout->dc, DRMU_PLANE_TYPE_CURSOR | DRMU_PLANE_TYPE_OVERLAY); ++ ++ if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0) ++ return NULL; ++ ++ return dp; ++} ++ ++struct plane_format_s { ++ unsigned int types; ++ uint32_t fmt; ++ uint64_t mod; ++}; ++ ++static bool plane_find_format_cb(const drmu_plane_t * dp, void * v) ++{ ++ const struct plane_format_s * const f = v; ++ return (f->types & drmu_plane_type(dp)) != 0 && ++ drmu_plane_format_check(dp, f->fmt, f->mod); ++} ++ ++drmu_plane_t * ++drmu_output_plane_ref_format(drmu_output_t * const dout, const unsigned int types, const uint32_t format, const uint64_t mod) ++{ ++ struct plane_format_s fm = { ++ .types = (types != 0) ? types : (DRMU_PLANE_TYPE_PRIMARY | DRMU_PLANE_TYPE_CURSOR | DRMU_PLANE_TYPE_OVERLAY), ++ .fmt = format, ++ .mod = mod ++ }; ++ ++ drmu_plane_t *const dp = drmu_plane_new_find(dout->dc, plane_find_format_cb, &fm); ++ ++ if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0) ++ return NULL; ++ ++ return dp; ++} ++ ++ ++int ++drmu_atomic_output_add_props(drmu_atomic_t * const da, drmu_output_t * const dout) ++{ ++ int rv = 0; ++ unsigned int i; ++ ++ if (!dout->modeset_allow) ++ return 0; ++ ++ rv = drmu_atomic_crtc_add_modeinfo(da, dout->dc, drmu_conn_modeinfo(dout->dns[0], dout->mode_id)); ++ ++ for (i = 0; i != dout->conn_n; ++i) { ++ drmu_conn_t * const dn = dout->dns[i]; ++ ++ if (dout->fmt_info && dout->max_bpc_allow) ++ rv = rvup(rv, drmu_atomic_conn_add_hi_bpc(da, dn, (drmu_fmt_info_bit_depth(dout->fmt_info) > 8))); ++ if (drmu_colorspace_is_set(dout->colorspace)) ++ rv = rvup(rv, drmu_atomic_conn_add_colorspace(da, dn, dout->colorspace)); ++ if (drmu_broadcast_rgb_is_set(dout->broadcast_rgb)) ++ rv = rvup(rv, drmu_atomic_conn_add_broadcast_rgb(da, dn, dout->broadcast_rgb)); ++ if (dout->hdr_metadata_isset != DRMU_ISSET_UNSET) ++ rv = rvup(rv, drmu_atomic_conn_add_hdr_metadata(da, dn, ++ dout->hdr_metadata_isset == DRMU_ISSET_NULL ? NULL : &dout->hdr_metadata)); ++ } ++ ++ return rv; ++} ++ ++// Set all the fb info props that might apply to a crtc on the crtc ++// (e.g. hdr_metadata, colorspace) but do not set the mode (resolution ++// and refresh) ++// ++// N.B. Only changes those props that are set in the fb. If unset in the fb ++// then their value is unchanged. ++int ++drmu_output_fb_info_set(drmu_output_t * const dout, const drmu_fb_t * const fb) ++{ ++ const drmu_isset_t hdr_isset = drmu_fb_hdr_metadata_isset(fb); ++ const drmu_fmt_info_t * fmt_info = drmu_fb_format_info_get(fb); ++ const drmu_colorspace_t colorspace = drmu_fb_colorspace_get(fb); ++ const drmu_broadcast_rgb_t broadcast_rgb = drmu_color_range_to_broadcast_rgb(drmu_fb_color_range_get(fb)); ++ ++ if (fmt_info) ++ dout->fmt_info = fmt_info; ++ if (drmu_colorspace_is_set(colorspace)) ++ dout->colorspace = colorspace; ++ if (drmu_broadcast_rgb_is_set(broadcast_rgb)) ++ dout->broadcast_rgb = broadcast_rgb; ++ ++ if (hdr_isset != DRMU_ISSET_UNSET) { ++ dout->hdr_metadata_isset = hdr_isset; ++ if (hdr_isset == DRMU_ISSET_SET) ++ dout->hdr_metadata = *drmu_fb_hdr_metadata_get(fb); ++ } ++ ++ return 0; ++} ++ ++void ++drmu_output_fb_info_unset(drmu_output_t * const dout) ++{ ++ dout->fmt_info = NULL; ++ dout->colorspace = DRMU_COLORSPACE_UNSET; ++ dout->broadcast_rgb = DRMU_BROADCAST_RGB_UNSET; ++ dout->hdr_metadata_isset = DRMU_ISSET_UNSET; ++} ++ ++ ++int ++drmu_output_mode_id_set(drmu_output_t * const dout, const int mode_id) ++{ ++ drmu_info(dout->du, "%s: mode_id=%d", __func__, mode_id); ++ ++ if (mode_id != dout->mode_id) { ++ drmu_mode_simple_params_t sp = drmu_conn_mode_simple_params(dout->dns[0], mode_id); ++ if (sp.width == 0) ++ return -EINVAL; ++ ++ dout->mode_id = mode_id; ++ dout->mode_params = sp; ++ } ++ return 0; ++} ++ ++const drmu_mode_simple_params_t * ++drmu_output_mode_simple_params(const drmu_output_t * const dout) ++{ ++ return &dout->mode_params; ++} ++ ++static int ++score_freq(const drmu_mode_simple_params_t * const mode, const drmu_mode_simple_params_t * const p) ++{ ++ const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0; ++ const unsigned int r_m = (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0 ? ++ mode->hz_x_1000 * 2: mode->hz_x_1000; ++ const unsigned int r_f = (p->flags & DRM_MODE_FLAG_INTERLACE) != 0 ? ++ p->hz_x_1000 * 2 : p->hz_x_1000; ++ ++ // If we haven't been given any hz then pick pref or fastest ++ // Max out at 300Hz (=300,0000) ++ if (r_f == 0) ++ return pref ? 83000000 : 80000000 + (r_m >= 2999999 ? 2999999 : r_m); ++ // Prefer a good match to 29.97 / 30 but allow the other ++ else if ((r_m + 10 >= r_f && r_m <= r_f + 10)) ++ return 100000000; ++ else if ((r_m + 100 >= r_f && r_m <= r_f + 100)) ++ return 95000000; ++ // Double isn't bad ++ else if ((r_m + 10 >= r_f * 2 && r_m <= r_f * 2 + 10)) ++ return 90000000; ++ else if ((r_m + 100 >= r_f * 2 && r_m <= r_f * 2 + 100)) ++ return 85000000; ++ return -1; ++} ++ ++// Avoid interlace no matter what our source ++int ++drmu_mode_pick_simple_cb(void * v, const drmu_mode_simple_params_t * mode) ++{ ++ const drmu_mode_simple_params_t * const p = v; ++ const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0; ++ int score = -1; ++ ++ if (p->width == mode->width && p->height == mode->height && ++ (mode->flags & DRM_MODE_FLAG_INTERLACE) == 0) ++ score = score_freq(mode, p); ++ ++ if (score > 0 && (p->width != mode->width || p->height != mode->height)) ++ score -= 30000000; ++ ++ if (score <= 0 && pref) ++ score = 10000000; ++ ++ return score; ++} ++ ++// Try to match interlace as well as everything else ++int ++drmu_mode_pick_simple_interlace_cb(void * v, const drmu_mode_simple_params_t * mode) ++{ ++ const drmu_mode_simple_params_t * const p = v; ++ ++ const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0; ++ int score = -1; ++ ++ if (p->width == mode->width && p->height == mode->height) ++ score = score_freq(mode, p); ++ ++ if (score > 0 && (p->width != mode->width || p->height != mode->height)) ++ score -= 30000000; ++ if (((mode->flags ^ p->flags) & DRM_MODE_FLAG_INTERLACE) != 0) ++ score -= 20000000; ++ ++ if (score <= 0 && pref) ++ score = 10000000; ++ ++ return score; ++} ++ ++ ++int ++drmu_output_mode_pick_simple(drmu_output_t * const dout, drmu_mode_score_fn * const score_fn, void * const score_v) ++{ ++ int best_score = -1; ++ int best_mode = -1; ++ int i; ++ ++ for (i = 0;; ++i) { ++ const drmu_mode_simple_params_t sp = drmu_conn_mode_simple_params(dout->dns[0], i); ++ int score; ++ ++ if (sp.width == 0) ++ break; ++ ++ score = score_fn(score_v, &sp); ++ if (score > best_score) { ++ best_score = score; ++ best_mode = i; ++ } ++ } ++ ++ return best_mode; ++} ++ ++int ++drmu_output_max_bpc_allow(drmu_output_t * const dout, const bool allow) ++{ ++ dout->max_bpc_allow = allow && dout->has_max_bpc; ++ return allow && !dout->has_max_bpc ? -ENOENT : 0; ++} ++ ++int ++drmu_output_modeset_allow(drmu_output_t * const dout, const bool allow) ++{ ++ dout->modeset_allow = allow; ++ return 0; ++} ++ ++static int ++check_conns_size(drmu_output_t * const dout) ++{ ++ if (dout->conn_n >= dout->conn_size) { ++ unsigned int n = !dout->conn_n ? 4 : dout->conn_n * 2; ++ drmu_conn_t ** dns = realloc(dout->dns, sizeof(*dout->dns) * n); ++ if (dns == NULL) { ++ drmu_err(dout->du, "Failed conn array realloc"); ++ return -ENOMEM; ++ } ++ dout->dns = dns; ++ dout->conn_size = n; ++ } ++ return 0; ++} ++ ++int ++drmu_output_add_output(drmu_output_t * const dout, const char * const conn_name) ++{ ++ const size_t nlen = !conn_name ? 0 : strlen(conn_name); ++ unsigned int i; ++ unsigned int retries = 0; ++ drmu_env_t * const du = dout->du; ++ drmu_conn_t * dn; ++ drmu_conn_t * dn_t; ++ drmu_crtc_t * dc_t; ++ uint32_t crtc_id; ++ int rv; ++ ++ // ***** ++ // This logic fatally flawed for anything other than adding a single ++ // conn already attached to a single crtc ++ ++retry: ++ if (++retries > 16) { ++ drmu_err(du, "Retry count exceeded"); ++ return -EBUSY; ++ } ++ dn = NULL; ++ dc_t = NULL; ++ ++ for (i = 0; (dn_t = drmu_env_conn_find_n(du, i)) != NULL; ++i) { ++ if (!drmu_conn_is_output(dn_t) || drmu_conn_is_claimed(dn_t)) ++ continue; ++ if (nlen && strncmp(conn_name, drmu_conn_name(dn_t), nlen) != 0) ++ continue; ++ // This prefers conns that are already attached to crtcs ++ if ((crtc_id = drmu_conn_crtc_id_get(dn_t)) == 0 || ++ (dc_t = drmu_env_crtc_find_id(du, crtc_id)) == NULL) { ++ dn = dn_t; ++ continue; ++ } ++ if (drmu_crtc_is_claimed(dc_t)) { ++ dc_t = NULL; ++ continue; ++ } ++ dn = dn_t; ++ break; ++ } + -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s", __func__); ++ if (!dn) ++ return -ENOENT; ++ ++ if (!dc_t) { ++ drmu_warn(du, "Adding unattached conns NIF"); ++ return -EINVAL; ++ } ++ ++ if ((rv = check_conns_size(dout)) != 0) ++ return rv; ++ ++ if (drmu_crtc_claim_ref(dc_t)) { ++ drmu_debug(du, "Crtc already claimed"); ++ goto retry; ++ } ++ if (drmu_conn_claim_ref(dn)) { ++ drmu_debug(du, "Conn already claimed"); ++ drmu_crtc_unref(&dc_t); ++ goto retry; ++ } ++ ++ // Test features ++ { ++ drmu_atomic_t * da = drmu_atomic_new(du); ++ if (!da) ++ return -ENOMEM; ++ dout->has_max_bpc = (drmu_atomic_conn_add_hi_bpc(da, dn, true) == 0); ++ drmu_atomic_unref(&da); ++ } ++ ++ dout->dns[dout->conn_n++] = dn; ++ dout->dc = dc_t; ++ ++ dout->mode_params = drmu_crtc_mode_simple_params(dout->dc); ++ ++ return 0; ++} ++ ++static struct drm_mode_modeinfo ++modeinfo_fake(unsigned int w, unsigned int h) ++{ ++ return (struct drm_mode_modeinfo){ ++ .clock = (h + 30)*(w + 20)*60, ++ .hdisplay = w, ++ .hsync_start = w + 10, ++ .hsync_end = w + 20, ++ .htotal = w + 30, ++ .hskew = 0, ++ .vdisplay = h, ++ .vsync_start = h + 10, ++ .vsync_end = h + 12, ++ .vtotal = h + 20, ++ .vscan = 0, ++ .vrefresh = 60, ++ .type = DRM_MODE_TYPE_USERDEF, ++ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, ++ .name = {"fake"}, ++ }; ++} ++ ++static int ++try_conn_crtc(drmu_env_t * du, drmu_conn_t * dn, drmu_crtc_t * dc) ++{ ++ int rv; ++ ++#if 1 ++ const struct drm_mode_modeinfo test_mode = modeinfo_fake(128,128); ++#else ++ // A real mode for testing ++ static const struct drm_mode_modeinfo test_mode = { ++ .clock = 25175, ++ .hdisplay = 640, ++ .hsync_start = 656, ++ .hsync_end = 752, ++ .htotal = 800, ++ .hskew = 0, ++ .vdisplay = 480, ++ .vsync_start = 490, ++ .vsync_end = 492, ++ .vtotal = 525, ++ .vscan = 0, ++ .vrefresh = 60, ++ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, ++ .name = {"640x480-60"}, ++ }; +#endif + -+ kill_pool(sys); ++ drmu_atomic_t * da = drmu_atomic_new(du); + -+ vc_tv_unregister_callback_full(tvservice_cb, vd); ++ if (!da) ++ return -ENOMEM; + -+ // Shouldn't be anything here - but just in case -+ for (unsigned int i = 0; i != SUBS_MAX; ++i) -+ if (sys->subpic_bufs[i] != NULL) -+ mmal_buffer_header_release(sys->subpic_bufs[i]); ++ if ((rv = drmu_atomic_conn_add_crtc(da, dn, dc)) != 0) { ++ drmu_warn(du, "Failed to add writeback connector to crtc: %s", strerror(-rv)); ++ goto fail; ++ } ++ if ((rv = drmu_atomic_crtc_add_modeinfo(da, dc, &test_mode)) != 0) { ++ drmu_warn(du, "Failed to add modeinfo: %s", strerror(-rv)); ++ goto fail; ++ } + -+ for (unsigned int i = 0; i != SUBS_MAX; ++i) { -+ vout_subpic_t * const sub = sys->subs + i; -+ if (sub->component != NULL) { -+ hw_mmal_subpic_close(VLC_OBJECT(vd), &sub->sub); -+ if (sub->component->control->is_enabled) -+ mmal_port_disable(sub->component->control); -+ if (sub->component->is_enabled) -+ mmal_component_disable(sub->component); -+ mmal_component_release(sub->component); -+ sub->component = NULL; -+ } - } ++ if ((rv = drmu_atomic_crtc_add_active(da, dc, 1)) != 0) { ++ drmu_warn(du, "Failed to add active to crtc: %s", strerror(-rv)); ++ goto fail; ++ } + -+ if (sys->input && sys->input->is_enabled) -+ mmal_port_disable(sys->input); ++ if ((rv = drmu_atomic_commit(da, DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET)) != 0) { ++ drmu_warn(du, "Failed test commit of writeback connector to crtc: %s", strerror(-rv)); ++ goto fail; ++ } + -+ if (sys->component && sys->component->control->is_enabled) -+ mmal_port_disable(sys->component->control); ++ drmu_atomic_unref(&da); ++ return 0; + -+ if (sys->copy_buf != NULL) -+ mmal_buffer_header_release(sys->copy_buf); ++fail: ++ drmu_atomic_unref(&da); ++ return rv; ++} + -+ if (sys->input != NULL && sys->copy_pool != NULL) -+ mmal_port_pool_destroy(sys->input, sys->copy_pool); ++int ++drmu_atomic_output_add_writeback_fb(drmu_atomic_t * const da_out, drmu_output_t * const dout, ++ drmu_fb_t * const dfb) ++{ ++ drmu_env_t * const du = dout->du; ++ drmu_atomic_t * da = drmu_atomic_new(drmu_atomic_env(da_out)); ++ int rv = -ENOMEM; ++ struct drm_mode_modeinfo mode = modeinfo_fake(drmu_fb_width(dfb), drmu_fb_height(dfb)); ++ drmu_conn_t * const dn = dout->dns[0]; + -+ if (sys->component && sys->component->is_enabled) -+ mmal_component_disable(sys->component); ++ if (da == NULL) ++ return -ENOMEM; + -+ if (sys->pool) -+ mmal_pool_destroy(sys->pool); ++ if ((rv = drmu_atomic_conn_add_writeback_fb(da, dn, dfb)) != 0) { ++ drmu_err(du, "Failed to add FB to conn"); ++ goto fail; ++ } ++ if ((rv = drmu_atomic_crtc_add_modeinfo(da, dout->dc, &mode)) != 0) { ++ drmu_err(du, "Failed to add modeinfo to CRTC"); ++ goto fail; ++ } ++ if ((rv = drmu_atomic_conn_add_crtc(da, dn, dout->dc)) != 0) { ++ drmu_err(du, "Failed to add CRTC to Conn"); ++ goto fail; ++ } ++ if ((rv = drmu_atomic_crtc_add_active(da, dout->dc, 1)) != 0) { ++ drmu_err(du, "Failed to add Active to Conn"); ++ goto fail; ++ } + -+ if (sys->component) -+ mmal_component_release(sys->component); ++ return drmu_atomic_merge(da_out, &da); + -+ isp_close(vd, sys); ++fail: ++ drmu_atomic_unref(&da); ++ return rv; ++} + -+ hw_mmal_vzc_pool_release(sys->vzc); ++int ++drmu_output_add_writeback(drmu_output_t * const dout) ++{ ++ drmu_env_t * const du = dout->du; ++ drmu_conn_t * dn = NULL; ++ drmu_crtc_t * dc = NULL; ++ drmu_conn_t * dn_t; ++ int rv; ++ uint32_t possible_crtcs; + -+ vlc_mutex_destroy(&sys->manage_mutex); ++ if (!dout->modeset_allow) { ++ drmu_debug(du, "modeset_allow required for writeback"); ++ return -EINVAL; ++ } + -+ if (sys->native_interlaced) { -+ if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 || -+ response[18] != '0') -+ msg_Warn(vd, "Could not reset hvs field mode"); ++ for (unsigned int i = 0; (dn_t = drmu_env_conn_find_n(du, i)) != NULL; ++i) { ++ drmu_info(du, "%d: try %s", i, drmu_conn_name(dn_t)); ++ if (!drmu_conn_is_writeback(dn_t)) ++ continue; ++ dn = dn_t; ++ break; + } + -+ cma_vcsm_exit(sys->init_type);; ++ if (!dn) { ++ drmu_err(du, "no writeback conn found"); ++ return -ENOENT; ++ } + -+ free(sys); ++ possible_crtcs = drmu_conn_possible_crtcs(dn); + -+#if TRACE_ALL -+ msg_Dbg(vd, ">>> %s", __func__); ++ for (unsigned int i = 0; possible_crtcs != 0; ++i, possible_crtcs >>= 1) { ++ drmu_crtc_t *dc_t; ++ ++ if ((possible_crtcs & 1) == 0) ++ continue; ++ ++ drmu_info(du, "try Crtc %d", i); ++ ++ if ((dc_t = drmu_env_crtc_find_n(du, i)) == NULL) ++ break; ++ ++ if (try_conn_crtc(du, dn, dc_t) == 0) { ++ dc = dc_t; ++ break; ++ } ++ } ++ ++ if (!dc) { ++ drmu_err(du, "No crtc for writeback found"); ++ return -ENOENT; ++ } ++ ++ if ((rv = check_conns_size(dout)) != 0) ++ return rv; ++ ++ dout->dns[dout->conn_n++] = dn; ++ dout->dc = dc; ++ return 0; ++} ++ ++drmu_crtc_t * ++drmu_output_crtc(const drmu_output_t * const dout) ++{ ++ return !dout ? NULL : dout->dc; ++} ++ ++drmu_conn_t * ++drmu_output_conn(const drmu_output_t * const dout, const unsigned int n) ++{ ++ return !dout || n >= dout->conn_n ? NULL : dout->dns[n]; ++} ++ ++static void ++output_free(drmu_output_t * const dout) ++{ ++ unsigned int i; ++ for (i = 0; i != dout->conn_n; ++i) ++ drmu_conn_unref(dout->dns + i); ++ drmu_crtc_unref(&dout->dc); ++ free(dout); ++} ++ ++void ++drmu_output_unref(drmu_output_t ** const ppdout) ++{ ++ drmu_output_t * const dout = *ppdout; ++ if (dout == NULL) ++ return; ++ *ppdout = NULL; ++ ++ output_free(dout); ++} ++ ++drmu_output_t * ++drmu_output_new(drmu_env_t * const du) ++{ ++ drmu_output_t * const dout = calloc(1, sizeof(*dout)); ++ ++ if (dout == NULL) { ++ drmu_err(du, "Failed to alloc memory for drmu_output"); ++ return NULL; ++ } ++ ++ dout->du = du; ++ dout->mode_id = -1; ++ return dout; ++} ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu_output.h +@@ -0,0 +1,81 @@ ++#ifndef _DRMU_DRMU_OUTPUT_H ++#define _DRMU_DRMU_OUTPUT_H ++ ++#include "drmu.h" ++ ++#ifdef __cplusplus ++extern "C" { +#endif ++ ++struct drmu_output_s; ++typedef struct drmu_output_s drmu_output_t; ++ ++drmu_plane_t * drmu_output_plane_ref_primary(drmu_output_t * const dout); ++drmu_plane_t * drmu_output_plane_ref_other(drmu_output_t * const dout); ++// Find and ref a plane that supports the given format & mod on the current crtc ++// Types is a bit field of acceptable plane types (DRMU_PLANE_TYPE_xxx), 0 => any ++// ++// add_output must be called before this (so we have a crtc to check against) ++drmu_plane_t * drmu_output_plane_ref_format(drmu_output_t * const dout, const unsigned int types, const uint32_t format, const uint64_t mod); ++ ++// Add all props accumulated on the output to the atomic ++int drmu_atomic_output_add_props(drmu_atomic_t * const da, drmu_output_t * const dout); ++ ++// Set FB info (bit-depth, HDR metadata etc.) ++// Only sets properties that are set in the fb - retains previous value otherwise ++int drmu_output_fb_info_set(drmu_output_t * const dout, const drmu_fb_t * const fb); ++// Unset all FB info ++// (set only sets stuff that is set in the fb, so will never clear anything) ++void drmu_output_fb_info_unset(drmu_output_t * const dout); ++ ++// Set output mode ++int drmu_output_mode_id_set(drmu_output_t * const dout, const int mode_id); ++ ++// Width/height of the currebnt mode ++const drmu_mode_simple_params_t * drmu_output_mode_simple_params(const drmu_output_t * const dout); ++ ++typedef int drmu_mode_score_fn(void * v, const drmu_mode_simple_params_t * mode); ++ ++int drmu_output_mode_pick_simple(drmu_output_t * const dout, drmu_mode_score_fn * const score_fn, void * const score_v); ++ ++// Simple mode picker cb - looks for width / height and then refresh ++// If nothing "plausible" defaults to EDID preferred mode ++drmu_mode_score_fn drmu_mode_pick_simple_cb; ++// As above but may choose an interlaced mode ++drmu_mode_score_fn drmu_mode_pick_simple_interlace_cb; ++ ++// Allow fb max_bpc info to set the output mode (default false) ++int drmu_output_max_bpc_allow(drmu_output_t * const dout, const bool allow); ++ ++// Allow fb to set modes generally ++int drmu_output_modeset_allow(drmu_output_t * const dout, const bool allow); ++ ++// Add a CONN/CRTC pair to an output ++// If conn_name == NULL then 1st connected connector is used ++// If != NULL then 1st conn with prefix-matching name is used ++int drmu_output_add_output(drmu_output_t * const dout, const char * const conn_name); ++ ++// Set writeback fb on output ++int drmu_atomic_output_add_writeback_fb(drmu_atomic_t * const da_req, drmu_output_t * const dout, ++ drmu_fb_t * const dfb); ++ ++// Add a writeback connector & find a crtc for it ++int drmu_output_add_writeback(drmu_output_t * const dout); ++ ++// Conn & CRTC for when output isn't fine grained enough ++drmu_crtc_t * drmu_output_crtc(const drmu_output_t * const dout); ++drmu_conn_t * drmu_output_conn(const drmu_output_t * const dout, const unsigned int n); ++ ++// Create a new empty output - has no crtc or conn ++drmu_output_t * drmu_output_new(drmu_env_t * const du); ++ ++// Unref an output ++void drmu_output_unref(drmu_output_t ** const ppdout); ++ ++#ifdef __cplusplus +} ++#endif + ++#endif + -+static const struct { -+ const char * name; -+ int num; -+} display_name_to_num[] = { -+ {"auto", -1}, -+ {"hdmi-1", DISPMANX_ID_HDMI0}, -+ {"hdmi-2", DISPMANX_ID_HDMI1}, -+ {NULL, -2} -+}; + -+static const struct { -+ const char * name; -+ int transform_num; -+} transform_name_to_num[] = { -+ {"auto", -1}, -+ {"0", MMAL_DISPLAY_ROT0}, -+ {"hflip", MMAL_DISPLAY_MIRROR_ROT0}, -+ {"vflip", MMAL_DISPLAY_MIRROR_ROT180}, -+ {"180", MMAL_DISPLAY_ROT180}, -+ {"transpose", MMAL_DISPLAY_MIRROR_ROT90}, -+ {"270", MMAL_DISPLAY_ROT270}, -+ {"90", MMAL_DISPLAY_ROT90}, -+ {"antitranspose", MMAL_DISPLAY_MIRROR_ROT270}, -+ {NULL, -2} -+}; +--- /dev/null ++++ b/modules/video_output/drmu/drmu_util.c +@@ -0,0 +1,125 @@ ++#include "drmu.h" ++#include "drmu_util.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++static unsigned long ++h_to_w(const unsigned long h) ++{ ++ switch (h) { ++ case 480: ++ case 576: ++ return 720; ++ case 720: ++ return 1280; ++ case 1080: ++ return 1920; ++ case 2160: ++ return 3840; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++char * ++drmu_util_parse_mode_simple_params(const char * s, drmu_mode_simple_params_t * const p) ++{ ++ unsigned long w = 0, h = 0, hz = 0; ++ bool il = false; ++ bool drmhz = false; ++ ++ memset(p , 0, sizeof(*p)); ++ ++ if (isdigit(*s)) { ++ h = strtoul(s, (char **)&s, 10); ++ ++ if (*s == 'p' || *s == 'i') { ++ w = h_to_w(h); ++ } ++ else if (*s == 'x') { ++ w = h; ++ h = strtoul(s + 1, (char **)&s, 10); ++ } ++ else { ++ return (char *)s; ++ } ++ } ++ ++ // Consume 'i' or 'p' ++ // Can still have (now) optional hz separator after ++ if (*s == 'p') { ++ ++s; ++ } ++ else if (*s == 'i') { ++ il = true; ++ ++s; ++ } ++ ++ // I've used '@' in the past to separate size from hz ++ // DRM uses '-' in modetest so accept that ++ if (*s == '@') { ++ ++s; ++ } ++ else if (*s == '-') { ++ drmhz = true; ++ ++s; ++ } ++ ++ if (isdigit(*s)) { ++ hz = strtoul(s, (char **)&s, 10) * 1000; ++ ++ if (*s == '.') { ++ unsigned int m = 100; ++ while (isdigit(*++s)) { ++ hz += (*s - '0') * m; ++ m /= 10; ++ } ++ } ++ } ++ ++ // DRM thinks in frame rate, rest of the world specifies as field rate ++ if (il && !drmhz) ++ hz /= 2; ++ ++ p->width = (unsigned int)w; ++ p->height = (unsigned int)h; ++ p->hz_x_1000 = (unsigned int)hz; ++ p->flags = !il ? 0 : DRM_MODE_FLAG_INTERLACE; ++ ++ return (char *)s; ++} ++ ++char * ++drmu_util_simple_param_to_mode_str(char * buf, size_t buflen, const drmu_mode_simple_params_t * const p) ++{ ++ int hz = p->hz_x_1000; ++ ++ if ((p->flags & DRM_MODE_FLAG_INTERLACE)) ++ hz *= 2; ++ ++ snprintf(buf, buflen, "%dx%d%c%d.%03d", ++ p->width, p->height, ++ (p->flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', ++ hz / 1000, hz % 1000); ++ return buf; ++} ++ ++char * ++drmu_util_parse_mode(const char * s, unsigned int * pw, unsigned int * ph, unsigned int * phz) ++{ ++ drmu_mode_simple_params_t p; ++ char * r = drmu_util_parse_mode_simple_params(s, &p); ++ *pw = p.width; ++ *ph = p.height; ++ *phz = p.hz_x_1000; ++ return r; ++} ++ ++ +--- /dev/null ++++ b/modules/video_output/drmu/drmu_util.h +@@ -0,0 +1,30 @@ ++#ifndef _DRMU_DRMU_UTIL_H ++#define _DRMU_DRMU_UTIL_H ++ ++#ifdef __cplusplus ++extern "C" { ++#endif + -+static int find_display_num(const char * const name) -+{ -+ unsigned int i; -+ for (i = 0; display_name_to_num[i].name != NULL && strcasecmp(display_name_to_num[i].name, name) != 0; ++i) -+ /* Loop */; -+ return display_name_to_num[i].num; -+} ++struct drmu_mode_simple_params_s; + -+static int find_transform_num(const char * const name) -+{ -+ unsigned int i; -+ for (i = 0; transform_name_to_num[i].name != NULL && strcasecmp(transform_name_to_num[i].name, name) != 0; ++i) -+ /* Loop */; -+ return transform_name_to_num[i].transform_num; ++// Parse a string of the form [x][i][@[.]] ++// Returns pointer to terminating char ++// Missing fields are zero ++char * drmu_util_parse_mode(const char * s, unsigned int * pw, unsigned int * ph, unsigned int * pHzx1000); ++ ++// As above but place results into a simple params structure ++// (Unused fields zeroed) ++// Also copes with interlace ++char * drmu_util_parse_mode_simple_params(const char * s, struct drmu_mode_simple_params_s * const p); ++ ++// Simple params to mode string ++char * drmu_util_simple_param_to_mode_str(char * buf, size_t buflen, const drmu_mode_simple_params_t * const p); ++ ++#define drmu_util_simple_mode(p) drmu_util_simple_param_to_mode_str((char[64]){0}, 64, (p)) ++ ++#ifdef __cplusplus +} ++#endif + -+#if HAVE_X11_XLIB_H -+#include -+#include -+static MMAL_DISPLAYTRANSFORM_T get_xrandr_rotation(vout_display_t * const vd) -+{ -+ Display * const x = XOpenDisplay(NULL); -+ Rotation cur_rot = 0; -+ MMAL_DISPLAYTRANSFORM_T trans; ++#endif + -+ if (x == NULL) -+ return MMAL_DISPLAY_ROT0; +--- /dev/null ++++ b/modules/video_output/drmu/drmu_vlc.c +@@ -0,0 +1,578 @@ ++#include "drmu_vlc.h" ++#include "drmu_fmts.h" ++#include "drmu_log.h" ++ ++#if HAS_ZC_CMA ++#include "../../hw/mmal/mmal_cma_pic.h" ++#endif ++#if HAS_DRMPRIME ++#include "../../codec/avcodec/drm_pic.h" ++#endif + -+ XRRRotations(x, 0, &cur_rot); -+ XCloseDisplay(x); ++#include + -+ // Convert to MMAL -+ // xrandr seems to rotate the other way to mmal ++#include ++#include + -+ switch (cur_rot) -+ { -+ case 0: -+ case RR_Rotate_0: -+ trans = MMAL_DISPLAY_ROT0; -+ break; -+ case RR_Rotate_90: -+ trans = MMAL_DISPLAY_ROT270; -+ break; -+ case RR_Rotate_180: -+ trans = MMAL_DISPLAY_ROT180; ++#include ++#include ++#include ++ ++#ifndef DRM_FORMAT_P030 ++#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') ++#endif ++ ++// N.B. DRM seems to order its format descriptor names the opposite way round to VLC ++// DRM is hi->lo within a little-endian word, VLC is byte order ++ ++#if HAS_ZC_CMA ++uint32_t ++drmu_format_vlc_to_drm_cma(const vlc_fourcc_t chroma_in) ++{ ++ switch (chroma_in) { ++ case VLC_CODEC_MMAL_ZC_I420: ++ return DRM_FORMAT_YUV420; ++ case VLC_CODEC_MMAL_ZC_SAND8: ++ return DRM_FORMAT_NV12; ++ case VLC_CODEC_MMAL_ZC_SAND30: ++ return DRM_FORMAT_P030; ++ case VLC_CODEC_MMAL_ZC_RGB32: ++ return DRM_FORMAT_RGBX8888; ++ } ++ return 0; ++} ++#endif ++ ++#if HAS_DRMPRIME ++uint32_t ++drmu_format_vlc_to_drm_prime(const vlc_fourcc_t chroma_in, uint64_t * const pmod) ++{ ++ uint32_t fmt = 0; ++ uint64_t mod = DRM_FORMAT_MOD_LINEAR; ++ ++ switch (chroma_in) { ++ case VLC_CODEC_DRM_PRIME_I420: ++ fmt = DRM_FORMAT_YUV420; + break; -+ case RR_Rotate_270: -+ trans = MMAL_DISPLAY_ROT90; ++ case VLC_CODEC_DRM_PRIME_NV12: ++ fmt = DRM_FORMAT_NV12; + break; -+ case RR_Reflect_X: -+ trans = MMAL_DISPLAY_MIRROR_ROT0; ++ case VLC_CODEC_DRM_PRIME_SAND8: ++ fmt = DRM_FORMAT_NV12; ++ mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0); + break; -+ case RR_Reflect_Y: -+ trans = MMAL_DISPLAY_MIRROR_ROT180; ++ case VLC_CODEC_DRM_PRIME_SAND30: ++ fmt = DRM_FORMAT_P030; ++ mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0); + break; ++ } ++ if (pmod) ++ *pmod = !fmt ? DRM_FORMAT_MOD_INVALID : mod; ++ return fmt; ++} ++#endif ++ ++// Convert chroma to drm - can't cope with RGB32 or RGB16 as they require ++// more info ++uint32_t ++drmu_format_vlc_chroma_to_drm(const vlc_fourcc_t chroma) ++{ ++ switch (chroma) { ++ case VLC_CODEC_RGBA: ++ return DRM_FORMAT_ABGR8888; ++ case VLC_CODEC_BGRA: ++ return DRM_FORMAT_ARGB8888; ++ case VLC_CODEC_ARGB: ++ return DRM_FORMAT_BGRA8888; ++ // VLC_CODEC_ABGR does not exist in VLC ++ case VLC_CODEC_VUYA: ++ return DRM_FORMAT_AYUV; ++ // AYUV appears to be the only DRM YUVA-like format ++ case VLC_CODEC_VYUY: ++ return DRM_FORMAT_YUYV; ++ case VLC_CODEC_UYVY: ++ return DRM_FORMAT_YVYU; ++ case VLC_CODEC_YUYV: ++ return DRM_FORMAT_VYUY; ++ case VLC_CODEC_YVYU: ++ return DRM_FORMAT_UYVY; ++ case VLC_CODEC_NV12: ++ return DRM_FORMAT_NV12; ++ case VLC_CODEC_NV21: ++ return DRM_FORMAT_NV21; ++ case VLC_CODEC_NV16: ++ return DRM_FORMAT_NV16; ++ case VLC_CODEC_NV61: ++ return DRM_FORMAT_NV61; ++ case VLC_CODEC_NV24: ++ return DRM_FORMAT_NV24; ++ case VLC_CODEC_NV42: ++ return DRM_FORMAT_NV42; ++ case VLC_CODEC_P010: ++ return DRM_FORMAT_P010; ++ case VLC_CODEC_J420: ++ case VLC_CODEC_I420: ++ return DRM_FORMAT_YUV420; ++ case VLC_CODEC_YV12: ++ return DRM_FORMAT_YVU420; ++ case VLC_CODEC_J422: ++ case VLC_CODEC_I422: ++ return DRM_FORMAT_YUV422; ++ case VLC_CODEC_J444: ++ case VLC_CODEC_I444: ++ return DRM_FORMAT_YUV444; + default: -+ msg_Info(vd, "Unexpected X rotation value: %#x", cur_rot); -+ trans = MMAL_DISPLAY_ROT0; + break; + } + -+ return trans; -+} ++#if HAS_ZC_CMA ++ return drmu_format_vlc_to_drm_cma(chroma); +#else -+static MMAL_DISPLAYTRANSFORM_T get_xrandr_rotation(vout_display_t * const vd) -+{ -+ VLC_UNUSED(vd); -+ return MMAL_DISPLAY_ROT0; -+} ++ return 0; +#endif ++} + -+static MMAL_RECT_T str_to_rect(const char * s) ++uint32_t ++drmu_format_vlc_to_drm(const video_frame_format_t * const vf_vlc) +{ -+ MMAL_RECT_T rect = {0}; -+ rect.width = strtoul(s, (char**)&s, 0); -+ if (*s == '\0') -+ return rect; -+ if (*s++ != 'x') -+ goto fail; -+ rect.height = strtoul(s, (char**)&s, 0); -+ if (*s == '\0') -+ return rect; -+ if (*s++ != '+') -+ goto fail; -+ rect.x = strtoul(s, (char**)&s, 0); -+ if (*s == '\0') -+ return rect; -+ if (*s++ != '+') -+ goto fail; -+ rect.y = strtoul(s, (char**)&s, 0); -+ if (*s != '\0') -+ goto fail; -+ return rect; ++ switch (vf_vlc->i_chroma) { ++ case VLC_CODEC_RGB32: ++ { ++ // VLC RGB32 aka RV32 means we have to look at the mask values ++ const uint32_t r = vf_vlc->i_rmask; ++ const uint32_t g = vf_vlc->i_gmask; ++ const uint32_t b = vf_vlc->i_bmask; ++ if (r == 0xff0000 && g == 0xff00 && b == 0xff) ++ return DRM_FORMAT_XRGB8888; ++ if (r == 0xff && g == 0xff00 && b == 0xff0000) ++ return DRM_FORMAT_XBGR8888; ++ if (r == 0xff000000 && g == 0xff0000 && b == 0xff00) ++ return DRM_FORMAT_RGBX8888; ++ if (r == 0xff00 && g == 0xff0000 && b == 0xff000000) ++ return DRM_FORMAT_BGRX8888; ++ break; ++ } ++ case VLC_CODEC_RGB24: ++ { ++ // VLC RGB24 aka RV24 means we have to look at the mask values ++ const uint32_t r = vf_vlc->i_rmask; ++ const uint32_t g = vf_vlc->i_gmask; ++ const uint32_t b = vf_vlc->i_bmask; ++ if (r == 0xff0000 && g == 0xff00 && b == 0xff) ++ return DRM_FORMAT_RGB888; ++ if (r == 0xff && g == 0xff00 && b == 0xff0000) ++ return DRM_FORMAT_BGR888; ++ break; ++ } ++ case VLC_CODEC_RGB16: ++ { ++ // VLC RGB16 aka RV16 means we have to look at the mask values ++ const uint32_t r = vf_vlc->i_rmask; ++ const uint32_t g = vf_vlc->i_gmask; ++ const uint32_t b = vf_vlc->i_bmask; ++ if (r == 0xf800 && g == 0x7e0 && b == 0x1f) ++ return DRM_FORMAT_RGB565; ++ if (r == 0x1f && g == 0x7e0 && b == 0xf800) ++ return DRM_FORMAT_BGR565; ++ break; ++ } ++ default: ++ break; ++ } + -+fail: -+ return (MMAL_RECT_T){0,0,0,0}; ++ return drmu_format_vlc_chroma_to_drm(vf_vlc->i_chroma); ++} ++ ++vlc_fourcc_t ++drmu_format_vlc_to_vlc(const uint32_t vf_drm) ++{ ++ switch (vf_drm) { ++ case DRM_FORMAT_XRGB8888: ++ case DRM_FORMAT_XBGR8888: ++ case DRM_FORMAT_RGBX8888: ++ case DRM_FORMAT_BGRX8888: ++ return VLC_CODEC_RGB32; ++ case DRM_FORMAT_RGB888: ++ case DRM_FORMAT_BGR888: ++ return VLC_CODEC_RGB24; ++ case DRM_FORMAT_BGR565: ++ case DRM_FORMAT_RGB565: ++ return VLC_CODEC_RGB16; ++ case DRM_FORMAT_ABGR8888: ++ return VLC_CODEC_RGBA; ++ case DRM_FORMAT_ARGB8888: ++ return VLC_CODEC_BGRA; ++ case DRM_FORMAT_BGRA8888: ++ return VLC_CODEC_ARGB; ++ // VLC_CODEC_ABGR does not exist in VLC ++ case DRM_FORMAT_AYUV: ++ return VLC_CODEC_VUYA; ++ case DRM_FORMAT_YUYV: ++ return VLC_CODEC_VYUY; ++ case DRM_FORMAT_YVYU: ++ return VLC_CODEC_UYVY; ++ case DRM_FORMAT_VYUY: ++ return VLC_CODEC_YUYV; ++ case DRM_FORMAT_UYVY: ++ return VLC_CODEC_YVYU; ++ case DRM_FORMAT_NV12: ++ return VLC_CODEC_NV12; ++ case DRM_FORMAT_NV21: ++ return VLC_CODEC_NV21; ++ case DRM_FORMAT_NV16: ++ return VLC_CODEC_NV16; ++ case DRM_FORMAT_NV61: ++ return VLC_CODEC_NV61; ++ case DRM_FORMAT_NV24: ++ return VLC_CODEC_NV24; ++ case DRM_FORMAT_NV42: ++ return VLC_CODEC_NV42; ++ case DRM_FORMAT_P010: ++ return VLC_CODEC_P010; ++ case DRM_FORMAT_YUV420: ++ return VLC_CODEC_I420; ++ case DRM_FORMAT_YVU420: ++ return VLC_CODEC_YV12; ++ case DRM_FORMAT_YUV422: ++ return VLC_CODEC_I422; ++ case DRM_FORMAT_YUV444: ++ return VLC_CODEC_I444; ++ default: ++ break; ++ } ++ return 0; +} + -+static int OpenMmalVout(vlc_object_t *object) ++typedef struct fb_aux_pic_s { ++ picture_context_t * pic_ctx; ++} fb_aux_pic_t; ++ ++static void ++pic_fb_delete_cb(void * v) +{ -+ vout_display_t *vd = (vout_display_t *)object; -+ vout_display_sys_t *sys; -+ MMAL_STATUS_T status; -+ int ret = VLC_EGENERIC; -+ // At the moment all copy is via I420 -+ const bool needs_copy = !hw_mmal_chroma_is_mmal(vd->fmt.i_chroma); -+ const MMAL_FOURCC_T enc_in = needs_copy ? MMAL_ENCODING_I420 : -+ vout_vlc_to_mmal_pic_fourcc(vd->fmt.i_chroma); ++ fb_aux_pic_t * const aux = v; + -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s: o:%d", __func__, (int)vd->fmt.orientation); -+#endif ++ aux->pic_ctx->destroy(aux->pic_ctx); ++ free(aux); ++} + -+ get_xrandr_rotation(vd); ++static int ++pic_hdr_metadata(struct hdr_output_metadata * const m, const struct video_format_t * const fmt) ++{ ++ struct hdr_metadata_infoframe * const inf = &m->hdmi_metadata_type1; ++ unsigned int i; + -+ sys = calloc(1, sizeof(struct vout_display_sys_t)); -+ if (!sys) -+ return VLC_ENOMEM; -+ vd->sys = sys; ++ memset(m, 0, sizeof(*m)); ++ m->metadata_type = HDMI_STATIC_METADATA_TYPE1; ++ inf->metadata_type = HDMI_STATIC_METADATA_TYPE1; + -+ vlc_mutex_init(&sys->manage_mutex); ++ switch (fmt->transfer) { ++ case TRANSFER_FUNC_SMPTE_ST2084: ++ inf->eotf = HDMI_EOTF_SMPTE_ST2084; ++ break; ++ case TRANSFER_FUNC_ARIB_B67: ++ inf->eotf = HDMI_EOTF_BT_2100_HLG; ++ break; ++ default: ++ // HDMI_EOTF_TRADITIONAL_GAMMA_HDR for 10bit? ++ inf->eotf = HDMI_EOTF_TRADITIONAL_GAMMA_SDR; ++ return -ENOENT; ++ } + -+ if ((sys->init_type = cma_vcsm_init()) == VCSM_INIT_NONE) -+ { -+ msg_Err(vd, "VCSM init fail"); -+ goto fail; ++ // VLC & HDMI use the same scales for everything but max_luma ++ for (i = 0; i != 3; ++i) { ++ inf->display_primaries[i].x = fmt->mastering.primaries[i * 2 + 0]; ++ inf->display_primaries[i].y = fmt->mastering.primaries[i * 2 + 1]; + } ++ inf->white_point.x = fmt->mastering.white_point[0]; ++ inf->white_point.y = fmt->mastering.white_point[1]; ++ inf->max_display_mastering_luminance = (uint16_t)(fmt->mastering.max_luminance / 10000); ++ inf->min_display_mastering_luminance = (uint16_t)fmt->mastering.min_luminance; + -+ vc_tv_register_callback(tvservice_cb, vd); ++ inf->max_cll = fmt->lighting.MaxCLL; ++ inf->max_fall = fmt->lighting.MaxFALL; + -+ sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME); -+ sys->transparent = var_InheritBool(vd, MMAL_VOUT_TRANSPARENT_NAME); ++ return 0; ++} + ++static drmu_color_encoding_t ++fb_vlc_color_encoding(const video_format_t * const fmt) ++{ ++ switch (fmt->space) + { -+ const char *display_name = var_InheritString(vd, MMAL_DISPLAY_NAME); -+ int qt_num = var_InheritInteger(vd, "qt-fullscreen-screennumber" ); -+ int display_id = find_display_num(display_name); -+// sys->display_id = display_id < 0 ? vc_tv_get_default_display_id() : display_id; -+ sys->display_id = display_id >= 0 ? display_id : -+ qt_num == 1 ? DISPMANX_ID_HDMI1 : DISPMANX_ID_HDMI; -+ if (display_id < -1) -+ msg_Warn(vd, "Unknown display device: '%s'", display_name); -+ else -+ msg_Dbg(vd, "Display device: %s, qt=%d id=%d display=%d", display_name, -+ qt_num, display_id, sys->display_id); ++ case COLOR_SPACE_BT2020: ++ return DRMU_COLOR_ENCODING_BT2020; ++ case COLOR_SPACE_BT601: ++ return DRMU_COLOR_ENCODING_BT601; ++ case COLOR_SPACE_BT709: ++ return DRMU_COLOR_ENCODING_BT709; ++ case COLOR_SPACE_UNDEF: ++ default: ++ break; + } + -+ { -+ const char *window_str = var_InheritString(vd, MMAL_VOUT_WINDOW_NAME); -+ sys->req_win = str_to_rect(window_str); -+ if (sys->req_win.width != 0) -+ msg_Dbg(vd, "Window: %dx%d @ %d,%d", -+ sys->req_win.width, sys->req_win.height, -+ sys->req_win.x, sys->req_win.y); -+ } ++ return (fmt->i_visible_width > 1024 || fmt->i_visible_height > 600) ? ++ DRMU_COLOR_ENCODING_BT709 : ++ DRMU_COLOR_ENCODING_BT601; ++} + ++static drmu_color_range_t ++fb_vlc_color_range(const video_format_t * const fmt) ++{ ++#if HAS_VLC4 ++ switch (fmt->color_range) + { -+ const char *transform_name = var_InheritString(vd, MMAL_VOUT_TRANSFORM_NAME); -+ int transform_num = find_transform_num(transform_name); -+ sys->display_transform = transform_num < 0 ? -+ get_xrandr_rotation(vd) : -+ (MMAL_DISPLAYTRANSFORM_T)transform_num; -+ -+ if (transform_num < -1) -+ msg_Warn(vd, "Unknown vout transform: '%s'", transform_name); -+ else -+ msg_Dbg(vd, "Display transform: %s, mmal_display_transform=%d", -+ transform_name, (int)sys->display_transform); -+ -+ sys->video_transform = combine_transform( -+ vlc_to_mmal_transform(vd->fmt.orientation), sys->display_transform); -+ sys->dest_transform = transform_inverse(sys->display_transform); ++ case COLOR_RANGE_FULL: ++ return DRMU_COLOR_RANGE_YCBCR_FULL_RANGE; ++ case COLOR_RANGE_UNDEF: ++ case COLOR_RANGE_LIMITED: ++ default: ++ break; + } ++#else ++ if (fmt->b_color_range_full) ++ return DRMU_COLOR_RANGE_YCBCR_FULL_RANGE; ++#endif ++ return DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE; ++} + -+ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)", -+ MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status)); -+ goto fail; -+ } + -+ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd; -+ status = mmal_port_enable(sys->component->control, vd_control_port_cb); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)", -+ sys->component->control->name, status, mmal_status_to_string(status)); -+ goto fail; ++static const char * ++fb_vlc_colorspace(const video_format_t * const fmt) ++{ ++ switch (fmt->space) { ++ case COLOR_SPACE_BT2020: ++ return DRMU_COLORSPACE_BT2020_RGB; ++ default: ++ break; ++ } ++ return DRMU_COLORSPACE_DEFAULT; ++} ++ ++static drmu_chroma_siting_t ++fb_vlc_chroma_siting(const video_format_t * const fmt) ++{ ++ switch (fmt->chroma_location) { ++ case CHROMA_LOCATION_LEFT: ++ return DRMU_CHROMA_SITING_LEFT; ++ case CHROMA_LOCATION_CENTER: ++ return DRMU_CHROMA_SITING_CENTER; ++ case CHROMA_LOCATION_TOP_LEFT: ++ return DRMU_CHROMA_SITING_TOP_LEFT; ++ case CHROMA_LOCATION_TOP_CENTER: ++ return DRMU_CHROMA_SITING_TOP; ++ case CHROMA_LOCATION_BOTTOM_LEFT: ++ return DRMU_CHROMA_SITING_BOTTOM_LEFT; ++ case CHROMA_LOCATION_BOTTOM_CENTER: ++ return DRMU_CHROMA_SITING_BOTTOM; ++ default: ++ case CHROMA_LOCATION_UNDEF: ++ break; + } ++ return DRMU_CHROMA_SITING_UNSPECIFIED; ++} + -+ sys->input = sys->component->input[0]; -+ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd; ++void ++drmu_fb_vlc_pic_set_metadata(drmu_fb_t * const dfb, const picture_t * const pic) ++{ ++ struct hdr_output_metadata meta; + -+ sys->input->format->encoding = enc_in; -+ sys->input->format->encoding_variant = 0; -+ sys->i_planes = 1; ++ drmu_fb_int_color_set(dfb, ++ fb_vlc_color_encoding(&pic->format), ++ fb_vlc_color_range(&pic->format), ++ fb_vlc_colorspace(&pic->format)); + -+ display_set_format(vd, sys->input->format, want_isp(vd)); ++ drmu_fb_int_chroma_siting_set(dfb, fb_vlc_chroma_siting(&pic->format)); + -+ status = port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, true); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", -+ sys->input->name, status, mmal_status_to_string(status)); -+ goto fail; ++ drmu_fb_hdr_metadata_set(dfb, pic_hdr_metadata(&meta, &pic->format) == 0 ? &meta : NULL); ++} ++ ++#if HAS_DRMPRIME ++// Create a new fb from a VLC DRM_PRIME picture. ++// Picture is held reffed by the fb until the fb is deleted ++drmu_fb_t * ++drmu_fb_vlc_new_pic_attach(drmu_env_t * const du, picture_t * const pic) ++{ ++ int i, j, n; ++ drmu_fb_t * const dfb = drmu_fb_int_alloc(du); ++ const AVDRMFrameDescriptor * const desc = drm_prime_get_desc(pic); ++ fb_aux_pic_t * aux = NULL; ++ ++ if (dfb == NULL) { ++ drmu_err(du, "%s: Alloc failure", __func__); ++ return NULL; + } + -+ status = mmal_port_format_commit(sys->input); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)", -+ sys->input->name, status, mmal_status_to_string(status)); ++ if (desc == NULL) { ++ drmu_err(du, "%s: Missing descriptor", __func__); ++ goto fail; ++ } ++ if (desc->nb_objects > 4) { ++ drmu_err(du, "%s: Bad descriptor", __func__); + goto fail; + } + -+ sys->input->buffer_size = sys->input->buffer_size_recommended; ++ drmu_fb_int_fmt_size_set(dfb, ++ desc->layers[0].format, ++ pic->format.i_width, ++ pic->format.i_height, ++ drmu_rect_vlc_pic_crop(pic)); + -+ if (!needs_copy) { -+ sys->input->buffer_num = 30; ++ ++ // Set delete callback & hold this pic ++ // Aux attached to dfb immediately so no fail cleanup required ++ if ((aux = calloc(1, sizeof(*aux))) == NULL) { ++ drmu_err(du, "%s: Aux alloc failure", __func__); ++ goto fail; + } -+ else { -+ sys->input->buffer_num = 2; -+ if ((sys->copy_pool = mmal_port_pool_create(sys->input, 2, sys->input->buffer_size)) == NULL) -+ { -+ msg_Err(vd, "Cannot create copy pool"); ++ aux->pic_ctx = pic->context->copy(pic->context); ++ drmu_fb_int_on_delete_set(dfb, pic_fb_delete_cb, aux); ++ ++ for (i = 0; i < desc->nb_objects; ++i) ++ { ++ drmu_bo_t * bo = drmu_bo_new_fd(du, desc->objects[i].fd); ++ if (bo == NULL) + goto fail; ++ drmu_fb_int_bo_set(dfb, i, bo); ++ } ++ ++ n = 0; ++ for (i = 0; i < desc->nb_layers; ++i) ++ { ++ for (j = 0; j < desc->layers[i].nb_planes; ++j) ++ { ++ const AVDRMPlaneDescriptor *const p = desc->layers[i].planes + j; ++ const AVDRMObjectDescriptor *const obj = desc->objects + p->object_index; ++ ++ drmu_fb_int_layer_mod_set(dfb, n++, p->object_index, p->pitch, p->offset, obj->format_modifier); + } + } + -+ set_display_windows(vd, sys); ++ drmu_fb_vlc_pic_set_metadata(dfb, pic); + -+ configure_display(vd, vd->cfg, &vd->source); ++ if (drmu_fb_int_make(dfb) != 0) ++ goto fail; ++ return dfb; + -+ status = mmal_port_enable(sys->input, vd_input_port_cb); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)", -+ sys->input->name, status, mmal_status_to_string(status)); ++fail: ++ drmu_fb_int_free(dfb); ++ return NULL; ++} ++#endif ++ ++#if HAS_ZC_CMA ++drmu_fb_t * ++drmu_fb_vlc_new_pic_cma_attach(drmu_env_t * const du, picture_t * const pic) ++{ ++ int i; ++ drmu_fb_t * const dfb = drmu_fb_int_alloc(du); ++ fb_aux_pic_t * aux = NULL; ++ uint32_t fmt = drmu_format_vlc_to_drm_cma(pic->format.i_chroma); ++ const bool is_sand = (pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND8 || ++ pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND30); ++ cma_buf_t * const cb = cma_buf_pic_get(pic); ++ ++ if (dfb == NULL) { ++ drmu_err(du, "%s: Alloc failure", __func__); ++ return NULL; ++ } ++ ++ if (fmt == 0) { ++ drmu_err(du, "Pic bad format for cma"); + goto fail; + } + -+ status = mmal_component_enable(sys->component); -+ if (status != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)", -+ sys->component->name, status, mmal_status_to_string(status)); ++ if (cb == NULL) { ++ drmu_err(du, "Pic missing cma block"); + goto fail; + } + -+ if ((sys->pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) -+ { -+ msg_Err(vd, "Failed to create input pool"); ++ drmu_fb_int_fmt_size_set(dfb, ++ fmt, ++ pic->format.i_width, ++ pic->format.i_height, ++ drmu_rect_vlc_pic_crop(pic)); ++ ++ // Set delete callback & hold this pic ++ // Aux attached to dfb immediately so no fail cleanup required ++ if ((aux = calloc(1, sizeof(*aux))) == NULL) { ++ drmu_err(du, "%s: Aux alloc failure", __func__); + goto fail; + } ++ aux->pic_ctx = pic->context->copy(pic->context); ++ drmu_fb_int_on_delete_set(dfb, pic_fb_delete_cb, aux); + -+ for (unsigned int i = 0; i != SUBS_MAX; ++i) { -+ vout_subpic_t * const sub = sys->subs + i; -+ if ((status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sub->component)) != MMAL_SUCCESS) -+ { -+ msg_Dbg(vd, "Failed to create subpic component %d", i); -+ goto fail; -+ } -+ sub->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd; -+ if ((status = mmal_port_enable(sub->component->control, vd_control_port_cb)) != MMAL_SUCCESS) { -+ msg_Err(vd, "Failed to enable control port %s on sub %d (status=%"PRIx32" %s)", -+ sys->component->control->name, i, status, mmal_status_to_string(status)); -+ goto fail; -+ } -+ if ((status = hw_mmal_subpic_open(VLC_OBJECT(vd), &sub->sub, sub->component->input[0], -+ sys->display_id, sys->layer + i + 1)) != MMAL_SUCCESS) { -+ msg_Dbg(vd, "Failed to open subpic %d", i); -+ goto fail; -+ } -+ if ((status = mmal_component_enable(sub->component)) != MMAL_SUCCESS) -+ { -+ msg_Dbg(vd, "Failed to enable subpic component %d", i); ++ { ++ drmu_bo_t * bo = drmu_bo_new_fd(du, cma_buf_fd(cb)); ++ if (bo == NULL) + goto fail; -+ } ++ drmu_fb_int_bo_set(dfb, 0, bo); + } + -+ // If we can't deal with it directly ask for I420 -+ vd->fmt.i_chroma = req_chroma(vd); -+ -+ vd->info = (vout_display_info_t){ -+ .is_slow = false, -+ .has_double_click = false, -+ .needs_hide_mouse = false, -+ .has_pictures_invalid = true, -+ .subpicture_chromas = hw_mmal_vzc_subpicture_chromas -+ }; -+ -+ vd->pool = vd_pool; -+ vd->prepare = vd_prepare; -+ vd->display = vd_display; -+ vd->control = vd_control; ++ { ++ uint8_t * const base_addr = cma_buf_addr(cb); ++ for (i = 0; i < pic->i_planes; ++i) { ++ if (is_sand) ++ drmu_fb_int_layer_mod_set(dfb, i, 0, pic->format.i_width, pic->p[i].p_pixels - base_addr, ++ DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(pic->p[i].i_pitch)); ++ else ++ drmu_fb_int_layer_mod_set(dfb, i, 0, pic->p[i].i_pitch, pic->p[i].p_pixels - base_addr, 0); ++ } ++ } + ++ drmu_fb_vlc_pic_set_metadata(dfb, pic); + -+ msg_Dbg(vd, ">>> %s: ok", __func__); -+ return VLC_SUCCESS; ++ if (drmu_fb_int_make(dfb) != 0) ++ goto fail; ++ return dfb; + +fail: -+ CloseMmalVout(object); -+ -+ msg_Dbg(vd, ">>> %s: rv=%d", __func__, ret); -+ -+ return ret == VLC_SUCCESS ? VLC_EGENERIC : ret; - } ++ drmu_fb_int_free(dfb); ++ return NULL; ++} ++#endif + -+vlc_module_begin() ++plane_t ++drmu_fb_vlc_plane(drmu_fb_t * const dfb, const unsigned int plane_n) ++{ ++ const drmu_fmt_info_t *const f = drmu_fb_format_info_get(dfb); ++ unsigned int hdiv = drmu_fmt_info_hdiv(f, plane_n); ++ unsigned int wdiv = drmu_fmt_info_wdiv(f, plane_n); ++ const unsigned int bpp = drmu_fmt_info_pixel_bits(f); ++ const uint32_t pitch_n = drmu_fb_pitch(dfb, plane_n); ++ const drmu_rect_t crop = drmu_fb_crop_frac(dfb); + -+ add_submodule() ++ if (pitch_n == 0) { ++ return (plane_t) {.p_pixels = NULL }; ++ } + -+ set_shortname(N_("MMAL vout")) -+ set_description(N_("MMAL-based vout plugin for Raspberry Pi")) -+ set_capability("vout display", 16) // 1 point better than ASCII art -+ add_shortcut("mmal_vout") -+ set_category( CAT_VIDEO ) -+ set_subcategory( SUBCAT_VIDEO_VOUT ) ++ return (plane_t){ ++ .p_pixels = drmu_fb_data(dfb, plane_n), ++ .i_lines = drmu_fb_height(dfb) / hdiv, ++ .i_pitch = pitch_n, ++ .i_pixel_pitch = bpp / 8, ++ .i_visible_lines = (crop.h >> 16) / hdiv, ++ .i_visible_pitch = ((crop.w >> 16) * bpp / 8) / wdiv ++ }; ++} + -+ add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false) -+ add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT, -+ MMAL_ADJUST_REFRESHRATE_LONGTEXT, false) -+ add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT, -+ MMAL_NATIVE_INTERLACE_LONGTEXT, false) -+ add_string(MMAL_DISPLAY_NAME, "auto", MMAL_DISPLAY_TEXT, -+ MMAL_DISPLAY_LONGTEXT, false) -+ add_string(MMAL_VOUT_TRANSFORM_NAME, "auto", MMAL_VOUT_TRANSFORM_TEXT, -+ MMAL_VOUT_TRANSFORM_LONGTEXT, false) -+ add_string(MMAL_VOUT_WINDOW_NAME, "fullscreen", MMAL_VOUT_WINDOW_TEXT, -+ MMAL_VOUT_WINDOW_LONGTEXT, false) -+ add_bool(MMAL_VOUT_TRANSPARENT_NAME, false, MMAL_VOUT_TRANSPARENT_TEXT, -+ MMAL_VOUT_TRANSPARENT_LONGTEXT, false) -+ set_callbacks(OpenMmalVout, CloseMmalVout) ++#if !HAS_VLC4 ++#define vlc_object_vaLog vlc_vaLog ++#endif + -+vlc_module_end() ++void ++drmu_log_vlc_cb(void * v, enum drmu_log_level_e level_drmu, const char * fmt, va_list vl) ++{ ++ const char * const file_name = va_arg(vl, const char *); ++ const unsigned int line_no = va_arg(vl, unsigned int); ++ const char * const function_name = va_arg(vl, const char *); ++ const int level_vlc = ++ level_drmu <= DRMU_LOG_LEVEL_MESSAGE ? VLC_MSG_INFO : ++ level_drmu <= DRMU_LOG_LEVEL_ERROR ? VLC_MSG_ERR : ++ level_drmu <= DRMU_LOG_LEVEL_WARNING ? VLC_MSG_WARN : ++ VLC_MSG_DBG; + ++ vlc_object_vaLog((vlc_object_t *)v, level_vlc, vlc_module_name, file_name, line_no, ++ function_name, fmt + DRMU_LOG_FMT_OFFSET_FMT, vl); ++} + --- /dev/null -+++ b/modules/hw/mmal/xsplitter.c -@@ -0,0 +1,584 @@ -+#ifdef HAVE_CONFIG_H ++++ b/modules/video_output/drmu/drmu_vlc.h +@@ -0,0 +1,93 @@ ++#ifndef _DRMU_DRMU_VLC_H ++#define _DRMU_DRMU_VLC_H ++ +#include "config.h" ++ ++#ifndef HAS_VLC4 ++#define HAS_VLC4 0 +#endif ++#ifndef HAS_ZC_CMA ++#define HAS_ZC_CMA 0 ++#endif ++#define HAS_DRMPRIME 1 + -+#include ++#include + +#include -+#include -+#include ++#include +#include -+#include -+ -+#include -+#include -+#include -+#include + -+#include "mmal_picture.h" -+ -+#define TRACE_ALL 0 ++#include "drmu.h" + -+typedef struct display_desc_s -+{ -+ vout_display_t * vout; -+ unsigned int max_pels; -+} display_desc_t; ++#ifdef __cplusplus ++extern "C" { ++#endif + -+typedef struct mmal_x11_sys_s ++// Get cropping rectangle from a vlc format ++static inline drmu_rect_t ++drmu_rect_vlc_format_crop(const video_frame_format_t * const format) +{ -+ bool use_mmal; -+ display_desc_t * cur_desc; -+ display_desc_t mmal_desc; -+ display_desc_t x_desc; -+ uint32_t changed; -+ vlc_fourcc_t subpicture_chromas[16]; -+} mmal_x11_sys_t; -+ -+#define MAX_GL_PELS (1920*1080) -+#define MAX_MMAL_PELS (4096*4096) // Should never be hit -+ -+#if 0 -+// Gen prog for the following table -+// Not done inline in case we end up pulling in FP libs we don't want -+#include -+#include ++ return (drmu_rect_t){ ++ .x = format->i_x_offset, ++ .y = format->i_y_offset, ++ .w = format->i_visible_width, ++ .h = format->i_visible_height}; ++} + -+int main(int argc, char *argv[]) ++// Get cropping rectangle from a vlc pic ++static inline drmu_rect_t ++drmu_rect_vlc_pic_crop(const picture_t * const pic) +{ -+ unsigned int i; -+ for (i = 0; i != 64; ++i) -+ { -+ printf(" [%2u]=%5u,", i, (unsigned int)(0.5 + (1/sqrt((i + 5)/4.0) * 65536.0))); -+ if (i % 4 == 3) -+ printf("\n"); -+ } ++ return drmu_rect_vlc_format_crop(&pic->format); +} -+#endif -+ -+static const uint16_t sqrt_tab[64] = { -+ [ 0]=58617, [ 1]=53510, [ 2]=49541, [ 3]=46341, -+ [ 4]=43691, [ 5]=41449, [ 6]=39520, [ 7]=37837, -+ [ 8]=36353, [ 9]=35030, [10]=33843, [11]=32768, -+ [12]=31790, [13]=30894, [14]=30070, [15]=29309, -+ [16]=28602, [17]=27945, [18]=27330, [19]=26755, -+ [20]=26214, [21]=25705, [22]=25225, [23]=24770, -+ [24]=24339, [25]=23930, [26]=23541, [27]=23170, -+ [28]=22817, [29]=22479, [30]=22155, [31]=21845, -+ [32]=21548, [33]=21263, [34]=20988, [35]=20724, -+ [36]=20470, [37]=20225, [38]=19988, [39]=19760, -+ [40]=19539, [41]=19326, [42]=19119, [43]=18919, -+ [44]=18725, [45]=18536, [46]=18354, [47]=18176, -+ [48]=18004, [49]=17837, [50]=17674, [51]=17515, -+ [52]=17361, [53]=17211, [54]=17064, [55]=16921, -+ [56]=16782, [57]=16646, [58]=16514, [59]=16384, -+ [60]=16257, [61]=16134, [62]=16013, [63]=15895 -+}; -+#define SQRT_MAX (sizeof(sqrt_tab)/sizeof(sqrt_tab[0]) - 1) + -+static bool cpy_fmt_limit_size(const display_desc_t * const dd, -+ video_format_t * const dst, -+ const video_format_t * const src) ++// Get rect from vlc place ++static inline drmu_rect_t ++drmu_rect_vlc_place(const vout_display_place_t * const place) +{ -+ const unsigned int src_pel = src->i_visible_width * src->i_visible_height; -+ -+ *dst = *src; -+ -+ if (src_pel <= dd->max_pels) -+ return false; -+ -+ // scaling factor sqrt(max_pel/cur_pel) -+ // sqrt done by lookup & 16 bit fixed-point maths - not exactly accurate but -+ // easily good enough & avoids floating point (which may be slow) -+ // src_pel > max_pel so n >= 0 -+ // Rounding should be such that exact sqrts work and everything else rounds -+ // down -+ unsigned int n = ((src_pel * 4 - 1) / dd->max_pels) - 4; -+ unsigned int scale = sqrt_tab[n >= SQRT_MAX ? SQRT_MAX : n]; -+ -+ // Rescale width - rounding up to 16 -+ unsigned int width = ((src->i_visible_width * scale + (16 << 16) - 1) >> 16) & ~15; -+ // Rescale height based on new width -+ unsigned int height = (src->i_visible_height * width + src->i_visible_width/2) / src->i_visible_width; -+ -+// fprintf(stderr, "%dx%d -> %dx%d\n", src->i_visible_width, src->i_visible_height, width, height); -+ -+ dst->i_width = width; -+ dst->i_visible_width = width; -+ dst->i_height = height; -+ dst->i_visible_height = height; -+ return true; ++ return (drmu_rect_t){ ++ .x = place->x, ++ .y = place->y, ++ .w = place->width, ++ .h = place->height ++ }; +} + -+static void unload_display_module(vout_display_t * const x_vout) ++static inline vlc_rational_t ++drmu_ufrac_vlc_to_rational(const drmu_ufrac_t x) +{ -+ if (x_vout != NULL) { -+ if (x_vout->module != NULL) { -+ module_unneed(x_vout, x_vout->module); -+ } -+ vlc_object_release(x_vout); -+ } ++ return (vlc_rational_t) {.num = x.num, .den = x.den}; +} + -+static void CloseMmalX11(vlc_object_t *object) -+{ -+ vout_display_t * const vd = (vout_display_t *)object; -+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + -+ msg_Dbg(vd, "<<< %s", __func__); ++// Convert chroma to drm - can't cope with RGB32 or RGB16 as they require ++// more info. returns 0 if unknown. ++uint32_t drmu_format_vlc_chroma_to_drm(const vlc_fourcc_t chroma); ++// Convert format to drm fourcc - does cope with RGB32 & RGB16 ++uint32_t drmu_format_vlc_to_drm(const video_frame_format_t * const vf_vlc); ++vlc_fourcc_t drmu_format_vlc_to_vlc(const uint32_t vf_drm); + -+ if (sys == NULL) -+ return; ++drmu_fb_t * drmu_fb_vlc_new_pic_attach(drmu_env_t * const du, picture_t * const pic); ++plane_t drmu_fb_vlc_plane(drmu_fb_t * const dfb, const unsigned int plane_n); + -+ unload_display_module(sys->x_desc.vout); ++#if HAS_DRMPRIME ++// pmod may be NULL ++uint32_t drmu_format_vlc_to_drm_prime(const vlc_fourcc_t chroma_in, uint64_t * const pmod); ++#endif ++#if HAS_ZC_CMA ++uint32_t drmu_format_vlc_to_drm_cma(const vlc_fourcc_t chroma_in); ++drmu_fb_t * drmu_fb_vlc_new_pic_cma_attach(drmu_env_t * const du, picture_t * const pic); ++#endif + -+ unload_display_module(sys->mmal_desc.vout); ++// Copy properties like colour_space, hdr_metadata into the fb ++void drmu_fb_vlc_pic_set_metadata(drmu_fb_t * const dfb, const picture_t * const pic); + -+ free(sys); ++// Logging function callback for VLC ++enum drmu_log_level_e; ++void drmu_log_vlc_cb(void * v, enum drmu_log_level_e level_drmu, const char * fmt, va_list vl); + -+ msg_Dbg(vd, ">>> %s", __func__); ++#ifdef __cplusplus +} -+ -+static void mmal_x11_event(vout_display_t * x_vd, int cmd, va_list args) -+{ -+ vout_display_t * const vd = x_vd->owner.sys; -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s (cmd=%d)", __func__, cmd); ++#endif +#endif + -+ // Do not fall into the display assert if Invalid not supported -+ if (cmd == VOUT_DISPLAY_EVENT_PICTURES_INVALID && -+ !vd->info.has_pictures_invalid) -+ return; +--- /dev/null ++++ b/modules/video_output/drmu/drmu_xlease.c +@@ -0,0 +1,143 @@ ++#include "drmu.h" ++#include "drmu_log.h" + -+ vd->owner.event(vd, cmd, args); -+} ++#include ++#include + -+static vout_window_t * mmal_x11_window_new(vout_display_t * x_vd, unsigned type) ++static int ++get_lease_fd(const drmu_log_env_t * const log) +{ -+ vout_display_t * const vd = x_vd->owner.sys; -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s (type=%d)", __func__, type); -+#endif -+ return vd->owner.window_new(vd, type); -+} ++ xcb_generic_error_t *xerr; + -+static void mmal_x11_window_del(vout_display_t * x_vd, vout_window_t * win) -+{ -+ vout_display_t * const vd = x_vd->owner.sys; -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s", __func__); -+#endif -+ vd->owner.window_del(vd, win); -+} ++ int screen = 0; ++ xcb_connection_t * const connection = xcb_connect(NULL, &screen); ++ if (!connection) { ++ drmu_warn_log(log, "Connection to X server failed"); ++ return -1; ++ } + ++ { ++ xcb_randr_query_version_cookie_t rqv_c = xcb_randr_query_version(connection, ++ XCB_RANDR_MAJOR_VERSION, ++ XCB_RANDR_MINOR_VERSION); ++ xcb_randr_query_version_reply_t *rqv_r = xcb_randr_query_version_reply(connection, rqv_c, NULL); + -+static int load_display_module(vout_display_t * const vd, -+ display_desc_t * const dd, -+ const char * const cap, -+ const char * const module_name) -+{ -+ vout_display_t * const x_vout = vlc_object_create(vd, sizeof(*x_vout)); ++ if (!rqv_r) { ++ drmu_warn_log(log, "Failed to get XCB RandR version"); ++ return -1; ++ } + -+ dd->vout = NULL; -+ if (!x_vout) -+ return -1; ++ uint32_t major = rqv_r->major_version; ++ uint32_t minor = rqv_r->minor_version; ++ free(rqv_r); + -+ x_vout->owner.sys = vd; -+ x_vout->owner.event = mmal_x11_event; -+ x_vout->owner.window_new = mmal_x11_window_new; -+ x_vout->owner.window_del = mmal_x11_window_del; ++ if (minor < 6) { ++ drmu_warn_log(log, "XCB RandR version %d.%d too low for lease support", major, minor); ++ return -1; ++ } ++ } + -+ x_vout->cfg = vd->cfg; -+ x_vout->info = vd->info; -+ cpy_fmt_limit_size(dd, &x_vout->source, &vd->source); -+ cpy_fmt_limit_size(dd, &x_vout->fmt, &vd->fmt); ++ xcb_window_t root; + -+ if ((x_vout->module = module_need(x_vout, cap, module_name, true)) == NULL) + { -+ msg_Err(vd, "Failed to open Xsplitter:%s module", module_name); -+ goto fail; ++ xcb_screen_iterator_t s_i = xcb_setup_roots_iterator(xcb_get_setup(connection)); ++ int i; ++ ++ for (i = 0; i != screen && s_i.rem != 0; ++i) { ++ xcb_screen_next(&s_i); ++ } ++ ++ if (s_i.rem == 0) { ++ drmu_err_log(log, "Failed to get root for screen %d", screen); ++ return -1; ++ } ++ ++ drmu_debug_log(log, "index %d screen %d rem %d", s_i.index, screen, s_i.rem); ++ root = s_i.data->root; + } + -+ msg_Dbg(vd, "R/G/B: %08x/%08x/%08x", x_vout->fmt.i_rmask, x_vout->fmt.i_gmask, x_vout->fmt.i_bmask); ++ xcb_randr_output_t output = 0; ++ xcb_randr_crtc_t crtc = 0; + -+ dd->vout = x_vout; -+ return 0; ++ /* Find a connected in-use output */ ++ { ++ xcb_randr_get_screen_resources_cookie_t gsr_c = xcb_randr_get_screen_resources(connection, root); + -+fail: -+ vlc_object_release(x_vout); -+ return -1; -+} ++ xcb_randr_get_screen_resources_reply_t *gsr_r = xcb_randr_get_screen_resources_reply(connection, gsr_c, NULL); ++ int o; + ++ if (!gsr_r) { ++ drmu_err_log(log, "get_screen_resources failed"); ++ return -1; ++ } + -+/* Return a pointer over the current picture_pool_t* (mandatory). -+ * -+ * For performance reasons, it is best to provide at least count -+ * pictures but it is not mandatory. -+ * You can return NULL when you cannot/do not want to allocate -+ * pictures. -+ * The vout display module keeps the ownership of the pool and can -+ * destroy it only when closing or on invalid pictures control. -+ */ -+static picture_pool_t * mmal_x11_pool(vout_display_t * vd, unsigned count) -+{ -+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; -+ vout_display_t * const x_vd = sys->cur_desc->vout; -+#if TRACE_ALL -+ char buf0[5]; -+ msg_Dbg(vd, "<<< %s (count=%d) %s:%dx%d->%s:%dx%d", __func__, count, -+ str_fourcc(buf0, vd->fmt.i_chroma), -+ vd->fmt.i_width, vd->fmt.i_height, -+ str_fourcc(buf0, x_vd->fmt.i_chroma), -+ x_vd->fmt.i_width, x_vd->fmt.i_height); -+#endif -+ picture_pool_t * pool = x_vd->pool(x_vd, count); -+#if TRACE_ALL -+ msg_Dbg(vd, ">>> %s: %p", __func__, pool); -+#endif -+ return pool; -+} ++ xcb_randr_output_t * const ro = xcb_randr_get_screen_resources_outputs(gsr_r); + -+/* Prepare a picture and an optional subpicture for display (optional). -+ * -+ * It is called before the next pf_display call to provide as much -+ * time as possible to prepare the given picture and the subpicture -+ * for display. -+ * You are guaranted that pf_display will always be called and using -+ * the exact same picture_t and subpicture_t. -+ * You cannot change the pixel content of the picture_t or of the -+ * subpicture_t. -+ */ -+static void mmal_x11_prepare(vout_display_t * vd, picture_t * pic, subpicture_t * sub) -+{ -+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; -+ vout_display_t * const x_vd = sys->cur_desc->vout; -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s", __func__); -+#endif -+ if (x_vd->prepare) -+ x_vd->prepare(x_vd, pic, sub); -+} ++ for (o = 0; output == 0 && o < gsr_r->num_outputs; o++) { ++ xcb_randr_get_output_info_cookie_t goi_c = xcb_randr_get_output_info(connection, ro[o], gsr_r->config_timestamp); + -+/* Display a picture and an optional subpicture (mandatory). -+ * -+ * The picture and the optional subpicture must be displayed as soon as -+ * possible. -+ * You cannot change the pixel content of the picture_t or of the -+ * subpicture_t. -+ * -+ * This function gives away the ownership of the picture and of the -+ * subpicture, so you must release them as soon as possible. -+ */ -+static void mmal_x11_display(vout_display_t * vd, picture_t * pic, subpicture_t * sub) -+{ -+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; -+ vout_display_t * const x_vd = sys->cur_desc->vout; ++ xcb_randr_get_output_info_reply_t *goi_r = xcb_randr_get_output_info_reply(connection, goi_c, NULL); + -+#if TRACE_ALL -+ const bool is_mmal_pic = hw_mmal_pic_is_mmal(pic); -+ msg_Dbg(vd, "<<< %s: fmt: %dx%d/%dx%d, pic:%dx%d, pts=%lld, mmal=%d/%d", __func__, vd->fmt.i_width, vd->fmt.i_height, x_vd->fmt.i_width, x_vd->fmt.i_height, pic->format.i_width, pic->format.i_height, (long long)pic->date, -+ is_mmal_pic, sys->use_mmal); -+#endif ++ drmu_debug_log(log, "output[%d/%d] %d: conn %d/%d crtc %d", o, gsr_r->num_outputs, ro[o], goi_r->connection, XCB_RANDR_CONNECTION_CONNECTED, goi_r->crtc); ++ ++ /* Find the first connected and used output */ ++ if (goi_r->connection == XCB_RANDR_CONNECTION_CONNECTED && ++ goi_r->crtc != 0) { ++ output = ro[o]; ++ crtc = goi_r->crtc; ++ } ++ ++ free(goi_r); ++ } ++ ++ free(gsr_r); ++ ++ if (output == 0) { ++ drmu_warn_log(log, "Failed to find active output (outputs=%d)", o); ++ return -1; ++ } ++ } ++ ++ int fd = -1; + -+ if (x_vd->fmt.i_chroma != pic->format.i_chroma || -+ x_vd->fmt.i_width != pic->format.i_width || -+ x_vd->fmt.i_height != pic->format.i_height) + { -+ msg_Dbg(vd, "%s: Picture dropped", __func__); -+ picture_Release(pic); -+ if (sub != NULL) -+ subpicture_Delete(sub); -+ return; ++ xcb_randr_lease_t lease = xcb_generate_id(connection); ++ ++ xcb_randr_create_lease_cookie_t rcl_c = xcb_randr_create_lease(connection, ++ root, ++ lease, ++ 1, ++ 1, ++ &crtc, ++ &output); ++ xcb_randr_create_lease_reply_t *rcl_r = xcb_randr_create_lease_reply(connection, rcl_c, &xerr); ++ ++ if (!rcl_r) { ++ drmu_err_log(log, "create_lease failed: Xerror %d", xerr->error_code); ++ return -1; ++ } ++ ++ int *rcl_f = xcb_randr_create_lease_reply_fds(connection, rcl_r); ++ ++ fd = rcl_f[0]; ++ ++ free(rcl_r); + } + -+ x_vd->display(x_vd, pic, sub); ++ drmu_debug_log(log, "%s OK: fd=%d", __func__, fd); ++ return fd; +} + ++drmu_env_t * ++drmu_env_new_xlease(const drmu_log_env_t * const log2) ++{ ++ const struct drmu_log_env_s * const log = (log2 == NULL) ? &drmu_log_env_none : log2; ++ const int fd = get_lease_fd(log); + -+static int vout_display_Control(const display_desc_t * const dd, int query, ...) ++ if (fd == -1) { ++ drmu_err_log(log, "Failed to get xlease"); ++ return NULL; ++ } ++ return drmu_env_new_fd(fd, log); ++} ++ +--- /dev/null ++++ b/modules/video_output/drmu/pollqueue.c +@@ -0,0 +1,372 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pollqueue.h" ++ ++#define request_log(...) fprintf(stderr, __VA_ARGS__) ++ ++struct pollqueue; ++ ++enum polltask_state { ++ POLLTASK_UNQUEUED = 0, ++ POLLTASK_QUEUED, ++ POLLTASK_RUNNING, ++ POLLTASK_Q_KILL, ++ POLLTASK_RUN_KILL, ++}; ++ ++struct polltask { ++ struct polltask *next; ++ struct polltask *prev; ++ struct pollqueue *q; ++ enum polltask_state state; ++ ++ int fd; ++ short events; ++ ++ void (*fn)(void *v, short revents); ++ void * v; ++ ++ uint64_t timeout; /* CLOCK_MONOTONIC time, 0 => never */ ++ sem_t kill_sem; ++}; ++ ++struct pollqueue { ++ atomic_int ref_count; ++ pthread_mutex_t lock; ++ ++ struct polltask *head; ++ struct polltask *tail; ++ ++ bool kill; ++ bool no_prod; ++ int prod_fd; ++ struct polltask *prod_pt; ++ pthread_t worker; ++}; ++ ++struct polltask *polltask_new(struct pollqueue *const pq, ++ const int fd, const short events, ++ void (*const fn)(void *v, short revents), ++ void *const v) +{ -+ va_list args; -+ int result; ++ struct polltask *pt; + -+ va_start(args, query); -+ result = dd->vout->control(dd->vout, query, args); -+ va_end(args); ++ if (!events && fd != -1) ++ return NULL; + -+ return result; ++ pt = malloc(sizeof(*pt)); ++ if (!pt) ++ return NULL; ++ ++ *pt = (struct polltask){ ++ .next = NULL, ++ .prev = NULL, ++ .q = pollqueue_ref(pq), ++ .fd = fd, ++ .events = events, ++ .fn = fn, ++ .v = v ++ }; ++ ++ sem_init(&pt->kill_sem, 0, 0); ++ ++ return pt; +} + -+static bool want_mmal_vout(vout_display_t * const vd, const mmal_x11_sys_t * const sys) ++struct polltask *polltask_new_timer(struct pollqueue *const pq, ++ void (*const fn)(void *v, short revents), ++ void *const v) +{ -+ return sys->mmal_desc.vout != NULL && -+ (sys->x_desc.vout == NULL || var_InheritBool(vd, "fullscreen")); ++ return polltask_new(pq, -1, 0, fn, v); +} + -+static inline int -+up_rv(const int a, const int b) ++static void pollqueue_rem_task(struct pollqueue *const pq, struct polltask *const pt) +{ -+ return a != 0 ? a : b; ++ if (pt->prev) ++ pt->prev->next = pt->next; ++ else ++ pq->head = pt->next; ++ if (pt->next) ++ pt->next->prev = pt->prev; ++ else ++ pq->tail = pt->prev; ++ pt->next = NULL; ++ pt->prev = NULL; +} + -+static int -+reset_pictures(vout_display_t * const vd, const display_desc_t * const desc) ++static void polltask_free(struct polltask * const pt) +{ -+ int rv = 0; -+ VLC_UNUSED(vd); -+ if (desc->vout) -+ { -+ // If the display doesn't have has_pictures_invalid then it doesn't -+ // expect RESET_PICTURES -+ if (desc->vout->info.has_pictures_invalid) -+ vout_display_Control(desc, VOUT_DISPLAY_RESET_PICTURES); -+ } -+ return rv; ++ sem_destroy(&pt->kill_sem); ++ free(pt); +} + -+static int -+replay_controls(vout_display_t * const vd, const display_desc_t * const desc, const int32_t changed) ++static int pollqueue_prod(const struct pollqueue *const pq) +{ -+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_DISPLAY_FILLED)) != 0) -+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, vd->cfg); -+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_ZOOM)) != 0) -+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_ZOOM, vd->cfg); -+ if ((changed & ((1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP) | -+ (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT))) != 0) -+ cpy_fmt_limit_size(desc, &desc->vout->source, &vd->source); -+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT)) != 0) -+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT); -+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP)) != 0) -+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_SOURCE_CROP); -+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_VIEWPOINT)) != 0) -+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_VIEWPOINT, vd->cfg); -+ return 0; ++ static const uint64_t one = 1; ++ return write(pq->prod_fd, &one, sizeof(one)); +} + -+/* Control on the module (mandatory) */ -+static int mmal_x11_control(vout_display_t * vd, int ctl, va_list va) ++void polltask_delete(struct polltask **const ppt) +{ -+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; -+ display_desc_t *x_desc = sys->cur_desc; -+ int rv; -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s[%d] (ctl=%d)", __func__, sys->use_mmal, ctl); -+#endif -+ // Remember what we've told this vd - unwanted ctls ignored on replay -+ if (ctl >= 0 && ctl <= 31) -+ sys->changed |= (1 << ctl); ++ struct polltask *const pt = *ppt; ++ struct pollqueue * pq; ++ enum polltask_state state; ++ bool prodme; + -+ switch (ctl) { -+ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: -+ { -+ const vout_display_cfg_t * const cfg = va_arg(va, const vout_display_cfg_t *); -+ const bool want_mmal = want_mmal_vout(vd, sys); -+ const bool swap_vout = (sys->use_mmal != want_mmal); -+ display_desc_t * const new_desc = want_mmal ? &sys->mmal_desc : &sys->x_desc; ++ if (!pt) ++ return; + -+ msg_Dbg(vd, "Change size: %d, %d: mmal_vout=%p, want_mmal=%d, fs=%d", -+ cfg->display.width, cfg->display.height, sys->mmal_desc.vout, want_mmal, -+ var_InheritBool(vd, "fullscreen")); ++ pq = pt->q; ++ pthread_mutex_lock(&pq->lock); ++ state = pt->state; ++ pt->state = (state == POLLTASK_RUNNING) ? POLLTASK_RUN_KILL : POLLTASK_Q_KILL; ++ prodme = !pq->no_prod; ++ pthread_mutex_unlock(&pq->lock); + -+ // Repeat any control calls that we sent to the previous vd -+ if (swap_vout && sys->changed != 0) { -+ const uint32_t changed = sys->changed; -+ sys->changed = 0; -+ replay_controls(vd, new_desc, changed); ++ if (state != POLLTASK_UNQUEUED) { ++ if (prodme) ++ pollqueue_prod(pq); ++ while (sem_wait(&pt->kill_sem) && errno == EINTR) ++ /* loop */; ++ } ++ ++ // Leave zapping the ref until we have DQed the PT as might well be ++ // legitimately used in it ++ *ppt = NULL; ++ polltask_free(pt); ++ pollqueue_unref(&pq); ++} ++ ++static uint64_t pollqueue_now(int timeout) ++{ ++ struct timespec now; ++ uint64_t now_ms; ++ ++ if (clock_gettime(CLOCK_MONOTONIC, &now)) ++ return 0; ++ now_ms = (now.tv_nsec / 1000000) + (uint64_t)now.tv_sec * 1000 + timeout; ++ return now_ms ? now_ms : (uint64_t)1; ++} ++ ++void pollqueue_add_task(struct polltask *const pt, const int timeout) ++{ ++ bool prodme = false; ++ struct pollqueue * const pq = pt->q; ++ ++ pthread_mutex_lock(&pq->lock); ++ if (pt->state != POLLTASK_Q_KILL && pt->state != POLLTASK_RUN_KILL) { ++ if (pq->tail) ++ pq->tail->next = pt; ++ else ++ pq->head = pt; ++ pt->prev = pq->tail; ++ pt->next = NULL; ++ pt->state = POLLTASK_QUEUED; ++ pt->timeout = timeout < 0 ? 0 : pollqueue_now(timeout); ++ pq->tail = pt; ++ prodme = !pq->no_prod; ++ } ++ pthread_mutex_unlock(&pq->lock); ++ if (prodme) ++ pollqueue_prod(pq); ++} ++ ++static void *poll_thread(void *v) ++{ ++ struct pollqueue *const pq = v; ++ struct pollfd *a = NULL; ++ size_t asize = 0; ++ ++ pthread_mutex_lock(&pq->lock); ++ do { ++ unsigned int i, j; ++ unsigned int nall = 0; ++ unsigned int npoll = 0; ++ struct polltask *pt; ++ struct polltask *pt_next; ++ uint64_t now = pollqueue_now(0); ++ int timeout = -1; ++ int rv; ++ ++ for (pt = pq->head; pt; pt = pt_next) { ++ int64_t t; ++ ++ pt_next = pt->next; ++ ++ if (pt->state == POLLTASK_Q_KILL) { ++ pollqueue_rem_task(pq, pt); ++ sem_post(&pt->kill_sem); ++ continue; + } + -+ if (swap_vout) { -+ if (sys->use_mmal) { -+ vout_display_Control(x_desc, VOUT_DISPLAY_CHANGE_MMAL_HIDE); ++ if (pt->fd != -1) { ++ if (npoll >= asize) { ++ asize = asize ? asize * 2 : 4; ++ a = realloc(a, asize * sizeof(*a)); ++ if (!a) { ++ request_log("Failed to realloc poll array to %zd\n", asize); ++ goto fail_locked; ++ } + } -+ vout_display_SendEventPicturesInvalid(vd); -+ } + -+ rv = vout_display_Control(new_desc, ctl, cfg); -+ if (rv == VLC_SUCCESS) { -+ vd->fmt = new_desc->vout->fmt; -+ sys->cur_desc = new_desc; -+ sys->use_mmal = want_mmal; ++ a[npoll++] = (struct pollfd){ ++ .fd = pt->fd, ++ .events = pt->events ++ }; + } + -+ -+ break; ++ t = (int64_t)(pt->timeout - now); ++ if (pt->timeout && t < INT_MAX && ++ (timeout < 0 || (int)t < timeout)) ++ timeout = (t < 0) ? 0 : (int)t; ++ ++nall; + } ++ pthread_mutex_unlock(&pq->lock); + -+ case VOUT_DISPLAY_RESET_PICTURES: -+ { -+ char dbuf0[5], dbuf1[5], dbuf2[5]; -+ msg_Dbg(vd, "<<< %s: Pic reset: fmt: %s,%dx%d<-%s,%dx%d, source: %s,%dx%d/%dx%d", __func__, -+ str_fourcc(dbuf0, vd->fmt.i_chroma), vd->fmt.i_width, vd->fmt.i_height, -+ str_fourcc(dbuf1, x_desc->vout->fmt.i_chroma), x_desc->vout->fmt.i_width, x_desc->vout->fmt.i_height, -+ str_fourcc(dbuf2, vd->source.i_chroma), vd->source.i_width, vd->source.i_height, x_desc->vout->source.i_width, -+ x_desc->vout->source.i_height); ++ if ((rv = poll(a, npoll, timeout)) == -1) { ++ if (errno != EINTR) { ++ request_log("Poll error: %s\n", strerror(errno)); ++ goto fail_unlocked; + } -+ rv = reset_pictures(vd, &sys->x_desc); -+ rv = up_rv(rv, reset_pictures(vd, &sys->mmal_desc)); ++ } + -+ vd->fmt = x_desc->vout->fmt; -+ break; ++ pthread_mutex_lock(&pq->lock); ++ now = pollqueue_now(0); ++ ++ /* Prodding in this loop is pointless and might lead to ++ * infinite looping ++ */ ++ pq->no_prod = true; ++ for (i = 0, j = 0, pt = pq->head; i < nall; ++i, pt = pt_next) { ++ const short r = pt->fd == -1 ? 0 : a[j++].revents; ++ pt_next = pt->next; ++ ++ /* Pending? */ ++ if (r || (pt->timeout && (int64_t)(now - pt->timeout) >= 0)) { ++ pollqueue_rem_task(pq, pt); ++ if (pt->state == POLLTASK_QUEUED) ++ pt->state = POLLTASK_RUNNING; ++ if (pt->state == POLLTASK_Q_KILL) ++ pt->state = POLLTASK_RUN_KILL; ++ pthread_mutex_unlock(&pq->lock); ++ ++ /* This can add new entries to the Q but as ++ * those are added to the tail our existing ++ * chain remains intact ++ */ ++ pt->fn(pt->v, r); ++ ++ pthread_mutex_lock(&pq->lock); ++ if (pt->state == POLLTASK_RUNNING) ++ pt->state = POLLTASK_UNQUEUED; ++ if (pt->state == POLLTASK_RUN_KILL) ++ sem_post(&pt->kill_sem); ++ } ++ } ++ pq->no_prod = false; + -+ case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: -+ case VOUT_DISPLAY_CHANGE_SOURCE_CROP: -+ cpy_fmt_limit_size(x_desc, &x_desc->vout->source, &vd->source); ++ } while (!pq->kill); + -+ /* FALLTHRU */ -+ default: -+ rv = x_desc->vout->control(x_desc->vout, ctl, va); -+// vd->fmt = x_vd->fmt; -+ break; -+ } -+#if TRACE_ALL -+ msg_Dbg(vd, ">>> %s (rv=%d)", __func__, rv); -+#endif -+ return rv; ++fail_locked: ++ pthread_mutex_unlock(&pq->lock); ++fail_unlocked: ++ free(a); ++ return NULL; +} + -+#define DO_MANAGE 0 -+ -+#if DO_MANAGE -+/* Manage pending event (optional) */ -+static void mmal_x11_manage(vout_display_t * vd) ++static void prod_fn(void *v, short revents) +{ -+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; -+ vout_display_t * const x_vd = sys->cur_desc->vout; -+#if TRACE_ALL -+ msg_Dbg(vd, "<<< %s", __func__); -+#endif -+ x_vd->manage(x_vd); ++ struct pollqueue *const pq = v; ++ char buf[8]; ++ if (revents) ++ read(pq->prod_fd, buf, 8); ++ if (!pq->kill) ++ pollqueue_add_task(pq->prod_pt, -1); +} -+#endif + -+static int OpenMmalX11(vlc_object_t *object) ++struct pollqueue * pollqueue_new(void) +{ -+ vout_display_t * const vd = (vout_display_t *)object; -+ mmal_x11_sys_t * const sys = calloc(1, sizeof(*sys)); -+ int ret = VLC_SUCCESS; -+ -+ if (sys == NULL) { -+ return VLC_EGENERIC; -+ } -+ vd->sys = (vout_display_sys_t *)sys; -+ -+ vd->info = (vout_display_info_t){ -+ .is_slow = false, -+ .has_double_click = false, -+ .needs_hide_mouse = false, -+ .has_pictures_invalid = true, -+ .subpicture_chromas = NULL ++ struct pollqueue *pq = malloc(sizeof(*pq)); ++ if (!pq) ++ return NULL; ++ *pq = (struct pollqueue){ ++ .ref_count = ATOMIC_VAR_INIT(0), ++ .lock = PTHREAD_MUTEX_INITIALIZER, ++ .head = NULL, ++ .tail = NULL, ++ .kill = false, ++ .prod_fd = -1 + }; + -+ { -+ char dbuf0[5]; -+ msg_Dbg(vd, ">>> %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__, -+ str_fourcc(dbuf0, vd->fmt.i_chroma), -+ vd->fmt.i_width, vd->fmt.i_height, -+ vd->fmt.i_x_offset, vd->fmt.i_y_offset, -+ vd->fmt.i_visible_width, vd->fmt.i_visible_height, -+ vd->fmt.i_sar_num, vd->fmt.i_sar_den); -+ } -+ -+ sys->x_desc.max_pels = MAX_GL_PELS; -+ sys->mmal_desc.max_pels = MAX_MMAL_PELS; ++ pq->prod_fd = eventfd(0, EFD_NONBLOCK); ++ if (pq->prod_fd == 1) ++ goto fail1; ++ pq->prod_pt = polltask_new(pq, pq->prod_fd, POLLIN, prod_fn, pq); ++ if (!pq->prod_pt) ++ goto fail2; ++ pollqueue_add_task(pq->prod_pt, -1); ++ if (pthread_create(&pq->worker, NULL, poll_thread, pq)) ++ goto fail3; ++ // Reset ref count which will have been inced by the add_task ++ atomic_store(&pq->ref_count, 0); ++ return pq; ++ ++fail3: ++ polltask_free(pq->prod_pt); ++fail2: ++ close(pq->prod_fd); ++fail1: ++ free(pq); ++ return NULL; ++} + -+ if (load_display_module(vd, &sys->x_desc, "vout display", "opengles2") == 0) -+ { -+ msg_Dbg(vd, "Opengles2 output found"); -+ } -+ else -+ { -+ sys->x_desc.max_pels = MAX_MMAL_PELS; -+ if (load_display_module(vd, &sys->x_desc, "vout display", "xcb_x11") == 0) -+ msg_Dbg(vd, "X11 XCB output found"); -+ } ++static void pollqueue_free(struct pollqueue *const pq) ++{ ++ void *rv; + -+ if ((load_display_module(vd, &sys->mmal_desc, "vout display", "mmal_vout")) == 0) -+ msg_Dbg(vd, "MMAL output found"); ++ pthread_mutex_lock(&pq->lock); ++ pq->kill = true; ++ pollqueue_prod(pq); ++ pthread_mutex_unlock(&pq->lock); + -+ if (sys->mmal_desc.vout == NULL && sys->x_desc.vout == NULL) { -+ char dbuf0[5], dbuf1[5]; -+ msg_Info(vd, "No valid output found for vout (%s/%s)", str_fourcc(dbuf0, vd->fmt.i_chroma), str_fourcc(dbuf1, vd->source.i_chroma)); -+ goto fail; -+ } ++ pthread_join(pq->worker, &rv); ++ polltask_free(pq->prod_pt); ++ pthread_mutex_destroy(&pq->lock); ++ close(pq->prod_fd); ++ free(pq); ++} + -+ vd->pool = mmal_x11_pool; -+ vd->prepare = mmal_x11_prepare; -+ vd->display = mmal_x11_display; -+ vd->control = mmal_x11_control; -+#if DO_MANAGE -+ vd->manage = mmal_x11_manage; -+#endif ++struct pollqueue * pollqueue_ref(struct pollqueue *const pq) ++{ ++ atomic_fetch_add(&pq->ref_count, 1); ++ return pq; ++} + -+ if (want_mmal_vout(vd, sys)) { -+ sys->cur_desc = &sys->mmal_desc; -+ sys->use_mmal = true; -+ } -+ else { -+ sys->cur_desc = &sys->x_desc; -+ sys->use_mmal = false; -+ } ++void pollqueue_unref(struct pollqueue **const ppq) ++{ ++ struct pollqueue * const pq = *ppq; + -+ if (sys->mmal_desc.vout == NULL || sys->x_desc.vout == NULL) { -+ vd->info = sys->cur_desc->vout->info; -+ vd->info.has_pictures_invalid = true; // Should make this unwanted -+ } -+ else { -+ // We have both - construct a combination -+ vd->info = (vout_display_info_t){ -+ .is_slow = false, -+ .has_double_click = sys->mmal_desc.vout->info.has_double_click || sys->x_desc.vout->info.has_double_click, -+ .needs_hide_mouse = sys->mmal_desc.vout->info.needs_hide_mouse || sys->x_desc.vout->info.needs_hide_mouse, -+ .has_pictures_invalid = true, -+ }; -+ // Construct intersection of subpicture chromas -+ // sys calloced so no need to add the terminating zero -+ if (sys->mmal_desc.vout->info.subpicture_chromas != NULL && sys->x_desc.vout->info.subpicture_chromas != NULL) { -+ unsigned int n = 0; -+ // N^2 - fix if we ever care -+ for (const vlc_fourcc_t * p1 = sys->mmal_desc.vout->info.subpicture_chromas; *p1 != 0 && n != 15; ++p1) { -+ for (const vlc_fourcc_t * p2 = sys->x_desc.vout->info.subpicture_chromas; *p2 != 0; ++p2) { -+ if (*p1 == *p2) { -+ sys->subpicture_chromas[n++] = *p1; -+ break; -+ } -+ } -+ } -+ if (n != 0) -+ vd->info.subpicture_chromas = sys->subpicture_chromas; -+ } -+ } -+ vd->fmt = sys->cur_desc->vout->fmt; ++ if (!pq) ++ return; ++ *ppq = NULL; + -+#if TRACE_ALL -+ { -+ char dbuf0[5]; -+ msg_Dbg(vd, ">>> %s: (%s) %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__, -+ module_get_name(sys->cur_desc->vout->module, false), -+ str_fourcc(dbuf0, vd->fmt.i_chroma), -+ vd->fmt.i_width, vd->fmt.i_height, -+ vd->fmt.i_x_offset, vd->fmt.i_y_offset, -+ vd->fmt.i_visible_width, vd->fmt.i_visible_height, -+ vd->fmt.i_sar_num, vd->fmt.i_sar_den); -+ } -+#endif -+ return VLC_SUCCESS; ++ if (atomic_fetch_sub(&pq->ref_count, 1) != 0) ++ return; + -+fail: -+ CloseMmalX11(VLC_OBJECT(vd)); -+ return ret == VLC_SUCCESS ? VLC_EGENERIC : ret; ++ pollqueue_free(pq); +} + + + +--- /dev/null ++++ b/modules/video_output/drmu/pollqueue.h +@@ -0,0 +1,51 @@ ++#ifndef POLLQUEUE_H_ ++#define POLLQUEUE_H_ + -+vlc_module_begin() -+ set_shortname(N_("MMAL x11 splitter")) -+ set_description(N_("MMAL x11 splitter for Raspberry Pi")) -+ set_capability("vout display", 300) // Between GLES & GL -+ add_shortcut("mmal_x11") -+ set_category( CAT_VIDEO ) -+ set_subcategory( SUBCAT_VIDEO_VOUT ) -+ set_callbacks(OpenMmalX11, CloseMmalX11) -+vlc_module_end() ++#include + ++struct polltask; ++struct pollqueue; ++ ++// Create a new polltask ++// Holds a reference on the pollqueue until the polltask is deleted ++// ++// pq pollqueue this task belongs to ++// fd fd to poll ++// events Events to wait for (POLLxxx) ++// revents Event that triggered the callback ++// 0 => timeout ++// v User pointer to callback ++struct polltask *polltask_new(struct pollqueue *const pq, ++ const int fd, const short events, ++ void (*const fn)(void *v, short revents), ++ void *const v); ++// polltask suitable for timing (i.e. has no trigger event) ++struct polltask *polltask_new_timer(struct pollqueue *const pq, ++ void (*const fn)(void *v, short revents), ++ void *const v); ++ ++// deletes the task ++// Safe to call if *ppt == NULL ++// It is safe to call whilst a polltask is queued (and may be triggered) ++// Callback may occur whilst this is in progress but will not occur ++// once it is done. (*ppt is nulled only once the callback can not occur) ++// DO NOT CALL in a polltask callback ++void polltask_delete(struct polltask **const ppt); ++ ++// timeout_ms == -1 => never ++// May be called from the polltask callback ++// May only be added once (currently) ++void pollqueue_add_task(struct polltask *const pt, const int timeout); ++ ++// Create a pollqueue ++// Generates a new thread to do the polltask callbacks ++struct pollqueue * pollqueue_new(void); ++ ++// Unref a pollqueue ++// Will be deleted once all polltasks (Qed or otherwise) are deleted too ++void pollqueue_unref(struct pollqueue **const ppq); ++ ++// Add a reference to a pollqueue ++struct pollqueue * pollqueue_ref(struct pollqueue *const pq); ++ ++#endif /* POLLQUEUE_H_ */ +--- a/modules/video_output/opengl/display.c ++++ b/modules/video_output/opengl/display.c +@@ -48,7 +48,7 @@ vlc_module_begin () + # define MODULE_VARNAME "gles2" + set_shortname (N_("OpenGL ES2")) + set_description (N_("OpenGL for Embedded Systems 2 video output")) +- set_capability ("vout display", 265) ++ set_capability ("vout display", 271) + set_callbacks (Open, Close) + add_shortcut ("opengles2", "gles2") + add_module ("gles2", "opengl es2", NULL, --- a/modules/video_output/opengl/egl.c +++ b/modules/video_output/opengl/egl.c @@ -43,6 +43,8 @@ @@ -13709,7 +25144,31 @@ to enable raspiberry pi support. typedef struct vlc_gl_sys_t { EGLDisplay display; -@@ -355,6 +357,14 @@ static int Open (vlc_object_t *obj, cons +@@ -58,6 +60,7 @@ typedef struct vlc_gl_sys_t + #endif + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; ++ PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; + } vlc_gl_sys_t; + + static int MakeCurrent (vlc_gl_t *gl) +@@ -129,6 +132,15 @@ static bool DestroyImageKHR(vlc_gl_t *gl + return sys->eglDestroyImageKHR(sys->display, image); + } + ++static bool QueryDmaBufModifiersEXT(vlc_gl_t *gl, uint32_t format, ++ unsigned int max_modifiers, uint64_t *modifiers, ++ unsigned int *external_only, int32_t *num_modifiers) ++{ ++ vlc_gl_sys_t *sys = gl->sys; ++ ++ return sys->eglQueryDmaBufModifiersEXT(sys->display, format, max_modifiers, modifiers, external_only, num_modifiers); ++} ++ + static bool CheckToken(const char *haystack, const char *needle) + { + size_t len = strlen(needle); +@@ -371,6 +383,14 @@ static int Open (vlc_object_t *obj, cons goto error; } @@ -13724,9 +25183,70 @@ to enable raspiberry pi support. const EGLint conf_attr[] = { EGL_RED_SIZE, 5, EGL_GREEN_SIZE, 5, +@@ -427,6 +447,9 @@ static int Open (vlc_object_t *obj, cons + gl->egl.createImageKHR = CreateImageKHR; + gl->egl.destroyImageKHR = DestroyImageKHR; + } ++ sys->eglQueryDmaBufModifiersEXT = (void *)eglGetProcAddress("eglQueryDmaBufModifiersEXT"); ++ if (sys->eglQueryDmaBufModifiersEXT) ++ gl->egl.queryDmaBufModifiersEXT = QueryDmaBufModifiersEXT; + + return VLC_SUCCESS; + +@@ -463,7 +486,7 @@ vlc_module_begin () + add_shortcut ("egl") + + add_submodule () +- set_capability ("opengl es2", 50) ++ set_capability ("opengl es2", 51) + set_callbacks (OpenGLES2, Close) + add_shortcut ("egl") + +--- a/src/audio_output/dec.c ++++ b/src/audio_output/dec.c +@@ -217,20 +217,27 @@ static void aout_DecSilence (audio_outpu + { + aout_owner_t *owner = aout_owner (aout); + const audio_sample_format_t *fmt = &owner->mixer_format; +- size_t frames = (fmt->i_rate * length) / CLOCK_FREQ; ++ size_t frame_count = (fmt->i_rate * length) / CLOCK_FREQ; + +- block_t *block = block_Alloc (frames * fmt->i_bytes_per_frame +- / fmt->i_frame_length); +- if (unlikely(block == NULL)) +- return; /* uho! */ ++ msg_Dbg (aout, "inserting %zu zero frames", frame_count); ++ while (frame_count != 0) ++ { ++ // Block into something that has a multiple of 3 in case this is spdif ++ // and we are going to substitute with pause blocks with a rep count of 3 ++ const size_t frames = frame_count > 3072 ? 3072 : frame_count; ++ block_t *block = block_Alloc((size_t)((uint_fast64_t)frames * fmt->i_bytes_per_frame ++ / fmt->i_frame_length)); ++ if (unlikely(block == NULL)) ++ return; /* uho! */ + +- msg_Dbg (aout, "inserting %zu zeroes", frames); +- memset (block->p_buffer, 0, block->i_buffer); +- block->i_nb_samples = frames; +- block->i_pts = pts; +- block->i_dts = pts; +- block->i_length = length; +- aout_OutputPlay (aout, block); ++ memset (block->p_buffer, 0, block->i_buffer); ++ block->i_nb_samples = frames; ++ block->i_pts = pts; ++ block->i_dts = pts; ++ block->i_length = length; ++ aout_OutputPlay (aout, block); ++ frame_count -= frames; ++ } + } + + static void aout_DecSynchronize (audio_output_t *aout, mtime_t dec_pts, --- a/src/input/decoder.c +++ b/src/input/decoder.c -@@ -1995,6 +1995,7 @@ void input_DecoderDelete( decoder_t *p_d +@@ -2000,6 +2000,7 @@ void input_DecoderDelete( decoder_t *p_d vlc_mutex_lock( &p_owner->lock ); p_owner->b_waiting = false; vlc_cond_signal( &p_owner->wait_request ); @@ -13734,7 +25254,7 @@ to enable raspiberry pi support. /* If the video output is paused or slow, or if the picture pool size was * under-estimated (e.g. greedy video filter, buggy decoder...), the -@@ -2005,7 +2006,6 @@ void input_DecoderDelete( decoder_t *p_d +@@ -2010,7 +2011,6 @@ void input_DecoderDelete( decoder_t *p_d * worker threads (if any) and the decoder thread to terminate. */ if( p_owner->p_vout != NULL ) vout_Cancel( p_owner->p_vout, true ); @@ -13744,7 +25264,42 @@ to enable raspiberry pi support. --- a/src/misc/fourcc.c +++ b/src/misc/fourcc.c -@@ -755,8 +755,13 @@ static const struct +@@ -416,6 +416,10 @@ static const vlc_fourcc_t p_D3D11_OPAQUE + VLC_CODEC_D3D11_OPAQUE_10B, VLC_CODEC_P010, VLC_CODEC_I420_10L, 0, + }; + ++static const vlc_fourcc_t p_DRM_PRIME_SAND30_fallback[] = { ++ VLC_CODEC_DRM_PRIME_SAND30, VLC_CODEC_I420_10L, VLC_CODEC_NV12, 0, ++}; ++ + static const vlc_fourcc_t p_I440_fallback[] = { + VLC_CODEC_I440, + VLC_CODEC_YUV_PLANAR_420, +@@ -506,6 +510,7 @@ static const vlc_fourcc_t *pp_YUV_fallba + p_D3D9_OPAQUE_10B_fallback, + p_D3D11_OPAQUE_fallback, + p_D3D11_OPAQUE_10B_fallback, ++ p_DRM_PRIME_SAND30_fallback, + NULL, + }; + +@@ -537,6 +542,15 @@ static const vlc_fourcc_t p_list_YUV[] = + VLC_CODEC_D3D9_OPAQUE_10B, + VLC_CODEC_D3D11_OPAQUE, + VLC_CODEC_D3D11_OPAQUE_10B, ++ VLC_CODEC_MMAL_OPAQUE, ++ VLC_CODEC_MMAL_ZC_SAND8, ++ VLC_CODEC_MMAL_ZC_SAND10, ++ VLC_CODEC_MMAL_ZC_SAND30, ++ VLC_CODEC_MMAL_ZC_I420, ++ VLC_CODEC_DRM_PRIME_I420, ++ VLC_CODEC_DRM_PRIME_NV12, ++ VLC_CODEC_DRM_PRIME_SAND8, ++ VLC_CODEC_DRM_PRIME_SAND30, + 0, + }; + +@@ -762,11 +776,19 @@ static const struct { { VLC_CODEC_VDPAU_VIDEO_420, VLC_CODEC_VDPAU_VIDEO_422, VLC_CODEC_VDPAU_VIDEO_444, VLC_CODEC_VDPAU_OUTPUT }, FAKE_FMT() }, @@ -13760,6 +25315,12 @@ to enable raspiberry pi support. FAKE_FMT() }, { { VLC_CODEC_D3D11_OPAQUE_10B, VLC_CODEC_D3D9_OPAQUE_10B }, FAKE_FMT() }, ++ { { VLC_CODEC_DRM_PRIME_I420, VLC_CODEC_DRM_PRIME_NV12, ++ VLC_CODEC_DRM_PRIME_SAND8, VLC_CODEC_DRM_PRIME_SAND30 }, ++ FAKE_FMT() }, + + { { VLC_CODEC_CVPX_NV12, VLC_CODEC_CVPX_UYVY, + VLC_CODEC_CVPX_I420, VLC_CODEC_CVPX_BGRA }, --- a/src/misc/picture.c +++ b/src/misc/picture.c @@ -365,10 +365,30 @@ void picture_CopyProperties( picture_t * @@ -13795,32 +25356,3 @@ to enable raspiberry pi support. assert( p_dst->context == NULL ); ---- a/src/video_output/video_output.c -+++ b/src/video_output/video_output.c -@@ -964,6 +964,17 @@ static picture_t *ConvertRGB32AndBlend(v - return NULL; - } - -+ -+static inline bool is_zc_chroma(const vlc_fourcc_t i_chroma) -+{ -+ return i_chroma == VLC_CODEC_MMAL_OPAQUE || -+ i_chroma == VLC_CODEC_MMAL_ZC_I420 || -+ i_chroma == VLC_CODEC_MMAL_ZC_RGB32 || -+ i_chroma == VLC_CODEC_MMAL_ZC_SAND10 || -+ i_chroma == VLC_CODEC_MMAL_ZC_SAND30 || -+ i_chroma == VLC_CODEC_MMAL_ZC_SAND8; -+} -+ - static int ThreadDisplayRenderPicture(vout_thread_t *vout, bool is_forced) - { - vout_thread_sys_t *sys = vout->p; -@@ -1098,7 +1109,7 @@ static int ThreadDisplayRenderPicture(vo - } - - assert(vout_IsDisplayFiltered(vd) == !sys->display.use_dr); -- if (sys->display.use_dr && !is_direct) { -+ if (sys->display.use_dr && !is_direct && !is_zc_chroma(todisplay->format.i_chroma)) { - picture_t *direct = NULL; - if (likely(vout->p->display_pool != NULL)) - direct = picture_pool_Get(vout->p->display_pool); diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0007-armv6.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0007-armv6.patch deleted file mode 100644 index 64a24268c..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0007-armv6.patch +++ /dev/null @@ -1,53 +0,0 @@ -Upstream-Status: Inappropriate - -RPI-Distro repo forks original vlc and applies patches -to enable raspiberry pi support. - ---- a/modules/hw/mmal/blend_rgba_neon.S -+++ b/modules/hw/mmal/blend_rgba_neon.S -@@ -1,10 +1,10 @@ -- .syntax unified -- .arm --// .thumb -- .text -+#include "../../arm_neon/asm.S" - .align 16 - .arch armv7-a -- .fpu neon-vfpv4 -+ .syntax unified -+#if HAVE_AS_FPU_DIRECTIVE -+ .fpu neon-vfpv4 -+#endif - - @ blend_rgbx_rgba_neon - ---- a/modules/hw/mmal/codec.c -+++ b/modules/hw/mmal/codec.c -@@ -29,6 +29,7 @@ - #include - - #include -+#include - #include - #include - #include -@@ -2311,6 +2312,9 @@ static int OpenBlendMmal(vlc_object_t *o - filter_t * const p_filter = (filter_t *)object; - const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma; - -+ if (!vlc_CPU_ARM_NEON()) -+ return VLC_EGENERIC; -+ - if (!hw_mmal_chroma_is_mmal(vfcc_dst) || - !hw_mmal_vzc_subpic_fmt_valid(&p_filter->fmt_in.video)) - { -@@ -2421,6 +2425,9 @@ static int OpenBlendNeon(vlc_object_t *o - MMAL_FOURCC_T mfcc_dst = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video); - blend_neon_fn * blend_fn = (blend_neon_fn *)0; - -+ if (!vlc_CPU_ARM_NEON()) -+ return VLC_EGENERIC; -+ - // Non-alpha RGB only for dest - if (vfcc_dst != VLC_CODEC_RGB32) - return VLC_EGENERIC; diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0007-mmal_caca.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0007-mmal_caca.patch new file mode 100644 index 000000000..6d94e51be --- /dev/null +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0007-mmal_caca.patch @@ -0,0 +1,15 @@ +--- a/modules/video_output/caca.c ++++ b/modules/video_output/caca.c +@@ -160,7 +160,11 @@ static int Open(vlc_object_t *object) + } + + const char *driver = NULL; +-#ifdef __APPLE__ ++// RPI: If driver is NULL then if we have X but DISPLAY is unset then somehow ++// the GL module becomes unloaded without anything noticing and that then ++// causes a segfault. ++//#ifdef __APPLE__ ++#if 1 + // Make sure we don't try to open a window. + driver = "ncurses"; + #endif diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0008-mmal_wayland.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0008-mmal_wayland.patch new file mode 100644 index 000000000..325c70065 --- /dev/null +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0008-mmal_wayland.patch @@ -0,0 +1,72 @@ +--- a/modules/gui/qt/components/interface_widgets.cpp ++++ b/modules/gui/qt/components/interface_widgets.cpp +@@ -108,6 +108,36 @@ void VideoWidget::sync( void ) + if( QX11Info::isPlatformX11() ) + XSync( QX11Info::display(), False ); + #endif ++ refreshHandles(); ++} ++ ++/** ++ * The wayland surface may change if the window is hidden which ++ * seems to happen sometimes on resize ++ * In Qt it looks like this happens if the window is hidden ++ **/ ++void VideoWidget::refreshHandles() ++{ ++#ifdef QT5_HAS_WAYLAND ++ if (!p_window || p_window->type != VOUT_WINDOW_TYPE_WAYLAND) ++ return; ++ ++ /* Ensure only the video widget is native (needed for Wayland) */ ++ stable->setAttribute( Qt::WA_DontCreateNativeAncestors, true); ++ ++ QWindow *window = stable->windowHandle(); ++ assert(window != NULL); ++ window->create(); ++ ++ QPlatformNativeInterface *qni = qApp->platformNativeInterface(); ++ assert(qni != NULL); ++ ++ p_window->handle.wl = static_cast( ++ qni->nativeResourceForWindow(QByteArrayLiteral("surface"), ++ window)); ++ p_window->display.wl = static_cast( ++ qni->nativeResourceForIntegration(QByteArrayLiteral("wl_display"))); ++#endif + } + + /** +@@ -165,21 +195,7 @@ bool VideoWidget::request( struct vout_w + #ifdef QT5_HAS_WAYLAND + case VOUT_WINDOW_TYPE_WAYLAND: + { +- /* Ensure only the video widget is native (needed for Wayland) */ +- stable->setAttribute( Qt::WA_DontCreateNativeAncestors, true); +- +- QWindow *window = stable->windowHandle(); +- assert(window != NULL); +- window->create(); +- +- QPlatformNativeInterface *qni = qApp->platformNativeInterface(); +- assert(qni != NULL); +- +- p_wnd->handle.wl = static_cast( +- qni->nativeResourceForWindow(QByteArrayLiteral("surface"), +- window)); +- p_wnd->display.wl = static_cast( +- qni->nativeResourceForIntegration(QByteArrayLiteral("wl_display"))); ++ refreshHandles(); + break; + } + #endif +--- a/modules/gui/qt/components/interface_widgets.hpp ++++ b/modules/gui/qt/components/interface_widgets.hpp +@@ -88,6 +88,7 @@ private: + bool enable_mouse_events; + + void reportSize(); ++ void refreshHandles(); + + signals: + void sizeChanged( int, int ); diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0008-configure-Disable-incompatible-function-pointer-type.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0009-configure-Disable-incompatible-function-pointer-type.patch similarity index 100% rename from dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0008-configure-Disable-incompatible-function-pointer-type.patch rename to dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0009-configure-Disable-incompatible-function-pointer-type.patch diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0009-demux-dash-include-cstdint-needed-for-uint64_t.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0009-demux-dash-include-cstdint-needed-for-uint64_t.patch deleted file mode 100644 index c526535f7..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/0009-demux-dash-include-cstdint-needed-for-uint64_t.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 6fca76ebd76bf8fce9b111e31bda64015cdc770f Mon Sep 17 00:00:00 2001 -From: Johannes Kauffmann -Date: Mon, 11 Jul 2022 19:35:57 +0000 -Subject: [PATCH] demux: dash: include cstdint, needed for uint64_t - -Fixes #27077. - -Upstream-Status: Backport - -https://github.com/videolan/vlc/commit/6fca76ebd76bf8fce9b111e31bda64015cdc770f - ---- - modules/demux/dash/mpd/TemplatedUri.hpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/modules/demux/dash/mpd/TemplatedUri.hpp b/modules/demux/dash/mpd/TemplatedUri.hpp -index 1eeb70cbb6..7f7264a9c8 100644 ---- a/modules/demux/dash/mpd/TemplatedUri.hpp -+++ b/modules/demux/dash/mpd/TemplatedUri.hpp -@@ -21,6 +21,7 @@ - #ifndef TEMPLATEDURI_HPP - #define TEMPLATEDURI_HPP - -+#include - #include - - namespace dash --- -2.34.1 - diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3001-configure.ac-setup-for-OE-usage.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3001-configure.ac-setup-for-OE-usage.patch index d676be39b..195a34f54 100644 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3001-configure.ac-setup-for-OE-usage.patch +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3001-configure.ac-setup-for-OE-usage.patch @@ -1,6 +1,6 @@ -From ddc2ea76058466b45a1acf37bed0d794cd3112a3 Mon Sep 17 00:00:00 2001 +From 190c13ccf11297549a4322c40ae7a551f9f9210a Mon Sep 17 00:00:00 2001 From: Vincent Davis Jr -Date: Fri, 9 Dec 2022 19:04:42 -0600 +Date: Mon, 13 Nov 2023 22:56:25 -0500 Subject: [PATCH] configure.ac: setup for OE usage Upstream-Status: Inappropriate @@ -33,7 +33,7 @@ Signed-off-by: Vincent Davis Jr 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac -index a72dca0b6..5b8585a26 100644 +index 0f69e3b42..db8d08c75 100644 --- a/configure.ac +++ b/configure.ac @@ -95,6 +95,13 @@ AS_IF([test -n "${with_binary_version}"],[ @@ -50,16 +50,16 @@ index a72dca0b6..5b8585a26 100644 dnl Prevent clang from accepting unknown flags with a mere warning AX_APPEND_COMPILE_FLAGS([-Werror=unknown-warning-option -Werror=invalid-command-line-argument], [CFLAGS]) AX_APPEND_COMPILE_FLAGS([-Werror=unknown-warning-option -Werror=invalid-command-line-argument], [CXXFLAGS]) -@@ -1900,7 +1907,7 @@ PKG_ENABLE_MODULES_VLC([BLURAY], [libbluray], [libbluray >= 0.6.2], (libbluray f +@@ -1888,7 +1895,7 @@ PKG_ENABLE_MODULES_VLC([BLURAY], [libbluray], [libbluray >= 0.6.2], (libbluray f dnl dnl OpenCV wrapper and example filters dnl -PKG_ENABLE_MODULES_VLC([OPENCV], [opencv_example opencv_wrapper], [opencv > 2.0], (OpenCV (computer vision) filter), [auto]) -+PKG_ENABLE_MODULES_VLC([OPENCV], [opencv_example opencv_wrapper], [opencv4 >= 2.0], (OpenCV (computer vision) filter), [auto]) ++PKG_ENABLE_MODULES_VLC([OPENCV], [opencv_example opencv_wrapper], [opencv4 > 2.0], (OpenCV (computer vision) filter), [auto]) dnl -@@ -2077,7 +2084,7 @@ PKG_ENABLE_MODULES_VLC([VNC], [vnc], [libvncclient >= 0.9.9], (VNC/rfb client su +@@ -2065,7 +2072,7 @@ PKG_ENABLE_MODULES_VLC([VNC], [vnc], [libvncclient >= 0.9.9], (VNC/rfb client su dnl RDP/Remote Desktop access module dnl @@ -68,7 +68,7 @@ index a72dca0b6..5b8585a26 100644 dnl dnl Real RTSP plugin -@@ -3089,14 +3096,14 @@ PKG_CHECK_MODULES([GL], [gl], [ +@@ -3058,14 +3065,14 @@ PKG_CHECK_MODULES([GL], [gl], [ #ifdef _WIN32 # include #endif @@ -85,12 +85,12 @@ index a72dca0b6..5b8585a26 100644 ], [ GL_LIBS="-lopengl32" ]) -@@ -3483,15 +3490,14 @@ AC_ARG_ENABLE(mmal_avcodec, +@@ -3468,15 +3475,14 @@ AC_ARG_ENABLE(mmal_avcodec, [Use MMAL enabled avcodec libs (default disable)])) if test "${enable_mmal}" != "no"; then VLC_SAVE_FLAGS - LDFLAGS="${LDFLAGS} -L/opt/vc/lib -lvchostif" -- CPPFLAGS="${CPPFLAGS} -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux" +- CPPFLAGS="${CPPFLAGS} -idirafter /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux" - AC_CHECK_HEADERS(interface/mmal/mmal.h, - [ AC_CHECK_LIB(bcm_host, vc_tv_unregister_callback_full, [ + PKG_CHECK_MODULES(BCMHOST_MMAL, [bcm_host mmal vcsm openmaxil egl], [ @@ -100,7 +100,7 @@ index a72dca0b6..5b8585a26 100644 have_mmal="yes" - VLC_ADD_PLUGIN([mmal]) - VLC_ADD_LDFLAGS([mmal],[ -L/opt/vc/lib ]) -- VLC_ADD_CFLAGS([mmal],[ -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ]) +- VLC_ADD_CFLAGS([mmal],[ -idirafter /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ]) - VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif -lvchiq_arm -lvcsm ]) ], [ + VLC_ADD_PLUGIN([bcm_host mmal vcsm openmaxil egl]) + VLC_ADD_CFLAGS([bcm_host mmal vcsm openmaxil egl],[$BCMHOST_MMAL_CFLAGS]) @@ -108,7 +108,7 @@ index a72dca0b6..5b8585a26 100644 AS_IF([test "${enable_mmal}" = "yes"], [ AC_MSG_ERROR([Cannot find bcm library...]) ], [ AC_MSG_WARN([Cannot find bcm library...]) ]) -@@ -3500,6 +3506,10 @@ if test "${enable_mmal}" != "no"; then +@@ -3485,6 +3491,10 @@ if test "${enable_mmal}" != "no"; then ] , [ AS_IF([test "${enable_mmal}" = "yes"], [ AC_MSG_ERROR([Cannot find development headers for mmal...]) ], [ AC_MSG_WARN([Cannot find development headers for mmal...]) ]) ]) @@ -120,5 +120,5 @@ index a72dca0b6..5b8585a26 100644 fi AM_CONDITIONAL([HAVE_MMAL], [test "${have_mmal}" = "yes"]) -- -2.38.1 +2.34.1 diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3003-codec-omxil_core-replace-opt-vc-path-with-usr-lib.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3002-codec-omxil_core-replace-opt-vc-path-with-usr-lib.patch similarity index 100% rename from dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3003-codec-omxil_core-replace-opt-vc-path-with-usr-lib.patch rename to dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3002-codec-omxil_core-replace-opt-vc-path-with-usr-lib.patch diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3002-fix-EGL-macro-undeclared-and-EGLImageKHR.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3002-fix-EGL-macro-undeclared-and-EGLImageKHR.patch deleted file mode 100644 index ab72b4f3c..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3002-fix-EGL-macro-undeclared-and-EGLImageKHR.patch +++ /dev/null @@ -1,61 +0,0 @@ -From: Vincent Davis Jr -Date: Fri, 07 Jan 2022 07:01:47 PM CST -Subject: [PATCH] Fix EGL macro undeclared and EGLImageKHR - -Upstream-Status: Inappropriate - -RPI-Distro repo forks original vlc and applies patches to enable -raspiberry pi support. - -* Fixes compiler issues related to EGL macro constant/enum value type not being defined -* Updates EGLImage to EGLImageKHR - -Signed-off-by: Vincent Davis Jr -diff --git a/modules/hw/mmal/converter_mmal.c b/modules/hw/mmal/converter_mmal.c -index f31cb81d8..426af668b 100644 ---- a/modules/hw/mmal/converter_mmal.c -+++ b/modules/hw/mmal/converter_mmal.c -@@ -28,6 +28,34 @@ - - #define TRACE_ALL 0 - -+// Pass Yocto related build errors -+#define EGL_LINUX_DMA_BUF_EXT 0x3270 -+#define EGL_LINUX_DRM_FOURCC_EXT 0x3271 -+#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 -+#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 -+#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 -+#define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 -+#define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 -+#define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 -+#define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 -+#define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 -+#define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A -+#define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B -+#define EGL_SAMPLE_RANGE_HINT_EXT 0x327C -+#define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D -+#define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E -+#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 -+#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 -+#define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 -+#define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 -+#define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 -+#define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 -+#define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 -+#define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 -+#define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 -+#define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 -+#define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A -+ - typedef struct mmal_gl_converter_s - { - EGLint drm_fourcc; -@@ -199,7 +227,7 @@ static tex_context_t * get_tex_context(const opengl_tex_converter_t * const tc, - - *a = EGL_NONE; - -- const EGLImage image = tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); -+ const EGLImageKHR image = tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); - if (!image) { - msg_Err(tc, "Failed to import fd %d: Err=%#x", fd, tc->vt->GetError()); - goto fail; diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3007-remove-xorg-related-link-libs.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3003-remove-xorg-related-link-libs.patch similarity index 71% rename from dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3007-remove-xorg-related-link-libs.patch rename to dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3003-remove-xorg-related-link-libs.patch index a0487fa2e..3ecce1335 100644 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3007-remove-xorg-related-link-libs.patch +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3003-remove-xorg-related-link-libs.patch @@ -1,6 +1,6 @@ -From 34e4f4dad923095989ccb0ab8efb883c592bdbfd Mon Sep 17 00:00:00 2001 +From 57013f3dbb8b4cc153e8ad4dee66d171e6bc2e29 Mon Sep 17 00:00:00 2001 From: Vincent Davis Jr -Date: Fri, 9 Dec 2022 20:04:27 -0600 +Date: Tue, 14 Nov 2023 20:44:15 -0500 Subject: [PATCH] remove xorg related link libs Upstream-Status: Inappropriate @@ -19,18 +19,18 @@ Signed-off-by: Vincent Davis Jr 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hw/mmal/Makefile.am b/modules/hw/mmal/Makefile.am -index 4abe68e2e..86dad2c2d 100644 +index f85effd0b..4586cce8a 100644 --- a/modules/hw/mmal/Makefile.am +++ b/modules/hw/mmal/Makefile.am -@@ -8,7 +8,7 @@ libmmal_vout_plugin_la_SOURCES = vout.c mmal_cma.c mmal_picture.c subpic.c\ - mmal_cma.h mmal_picture.h subpic.h transform_ops.h\ +@@ -11,7 +11,7 @@ libmmal_vout_plugin_la_SOURCES = vout.c mmal_cma.c mmal_picture.c subpic.c\ + mmal_cma.h mmal_cma_int.h mmal_picture.h subpic.h transform_ops.h\ mmal_piccpy_neon.S libmmal_vout_plugin_la_CFLAGS = $(AM_CFLAGS) -libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm -lX11 -lXrandr +libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm libmmal_vout_plugin_la_LIBADD = $(LIBS_mmal) - mmal_LTLIBRARIES = libmmal_vout_plugin.la + mmal_LTLIBRARIES += libmmal_vout_plugin.la -- -2.38.1 +2.34.1 diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3004-po-Fix-typos-in-oc.po-for-gettext-compatibility.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3004-po-Fix-typos-in-oc.po-for-gettext-compatibility.patch new file mode 100644 index 000000000..bc5c3f8d8 --- /dev/null +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3004-po-Fix-typos-in-oc.po-for-gettext-compatibility.patch @@ -0,0 +1,57 @@ +From 4caba7560aec54f6d944accd1a8d216e8d9b1d92 Mon Sep 17 00:00:00 2001 +From: Vincent Davis Jr +Date: Tue, 14 Nov 2023 20:17:11 -0500 +Subject: [PATCH] po: Fix typos in oc.po for gettext compatibility + +Moved upstream + +https://code.videolan.org/videolan/vlc/-/commit/9d67e20c2edd25251b46d1780a7973b44ac5e5ba + +gettext-0.22 became stricter and started to validate format strings. Fix +the typos. + +Bug: https://bugs.gentoo.org/909015 + +Signed-off-by: Vincent Davis Jr +--- + po/oc.po | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/po/oc.po b/po/oc.po +index 86f2ed8a1..ce68c581f 100644 +--- a/po/oc.po ++++ b/po/oc.po +@@ -5298,18 +5298,18 @@ msgstr "Comanda+" + #: src/misc/update.c:482 + #, c-format + msgid "%.1f GiB" +-msgstr "%.lf Gio" ++msgstr "%.1f Gio" + + #: src/misc/update.c:484 + #, c-format + msgid "%.1f MiB" +-msgstr "%.lf Mio" ++msgstr "%.1f Mio" + + #: src/misc/update.c:486 modules/gui/macosx/VLCPlaylistInfo.m:138 + #: modules/gui/macosx/VLCPlaylistInfo.m:140 + #, c-format + msgid "%.1f KiB" +-msgstr "%.lf Kio" ++msgstr "%.1f Kio" + + #: src/misc/update.c:488 + #, c-format +@@ -33071,7 +33071,7 @@ msgstr "Lista del gestionari de mèdias" + + #, fuzzy + #~ msgid "%.1f kB" +-#~ msgstr "%.lf Gio" ++#~ msgstr "%.1f Gio" + + #, fuzzy + #~ msgid "Speed" +-- +2.34.1 + diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3004-use-GLESv2-headers-over-GL-headers.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3004-use-GLESv2-headers-over-GL-headers.patch deleted file mode 100644 index 8016ab379..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3004-use-GLESv2-headers-over-GL-headers.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 377a67af6c3f7c38f6f7ba24f042ba1a6cfd3f24 Mon Sep 17 00:00:00 2001 -From: Vincent Davis Jr -Date: Fri, 9 Dec 2022 00:21:43 -0600 -Subject: [PATCH] use GLESv2 headers over GL headers - -Upstream-Status: Inappropriate - -RPI-Distro repo forks original vlc and applies patches to enable -raspiberry pi support. - -We utilize GLESv2 during compilation. Patches ensures -we utilize headers for it. - -Signed-off-by: Vincent Davis Jr ---- - modules/video_output/opengl/converter.h | 12 +++--------- - modules/visualization/glspectrum.c | 4 +++- - 2 files changed, 6 insertions(+), 10 deletions(-) - -diff --git a/modules/video_output/opengl/converter.h b/modules/video_output/opengl/converter.h -index 7000e1f38..a3fe32671 100644 ---- a/modules/video_output/opengl/converter.h -+++ b/modules/video_output/opengl/converter.h -@@ -41,15 +41,9 @@ - # include - # endif - #else /* !defined (__APPLE__) */ --# if defined (USE_OPENGL_ES2) --# include --# include --# else --# ifdef _WIN32 --# include --# endif --# include --# endif -+#define USE_OPENGL_ES2 -+#include -+#include - #endif - - #define VLCGL_PICTURE_MAX 128 -diff --git a/modules/visualization/glspectrum.c b/modules/visualization/glspectrum.c -index 06f8d1bdf..470080b1a 100644 ---- a/modules/visualization/glspectrum.c -+++ b/modules/visualization/glspectrum.c -@@ -37,7 +37,9 @@ - #ifdef __APPLE__ - # include - #else --# include -+#define USE_OPENGL_ES2 -+#include -+#include - #endif - - #include --- -2.38.1 - diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3005-exclude-opengl-vout-plugin-remove-egl.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3005-exclude-opengl-vout-plugin-remove-egl.patch new file mode 100644 index 000000000..fec14062e --- /dev/null +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3005-exclude-opengl-vout-plugin-remove-egl.patch @@ -0,0 +1,264 @@ +From ab4b4791ab961fce3a1eba44246acd572f5dbefb Mon Sep 17 00:00:00 2001 +From: Vincent Davis Jr +Date: Sat, 18 Nov 2023 14:05:58 -0500 +Subject: [PATCH] exclude opengl vout plugin & remove egl + +Upstream-Status: Inappropriate + +RPI-Distro repo forks original vlc and applies patches +to enable raspiberry pi support. + +In the situation where opengl isn't included in +DISTRO_FEATURES. We need to exclude the opengl +vout plugin from being built. Also it appears the +newer vlc requires macros that aren't implemented +in the version of egl that's installed by Yocto. +Remove all references to building anything requiring +GL/EGL. + +Signed-off-by: Vincent Davis Jr +--- + modules/Makefile.in | 18 ------- + modules/hw/drm/Makefile.am | 6 +-- + modules/hw/mmal/Makefile.am | 4 +- + modules/video_output/Makefile.am | 89 -------------------------------- + 4 files changed, 3 insertions(+), 114 deletions(-) + +diff --git a/modules/Makefile.in b/modules/Makefile.in +index b3498e52c..b893856b8 100644 +--- a/modules/Makefile.in ++++ b/modules/Makefile.in +@@ -482,7 +482,6 @@ TESTS = hpack_test$(EXEEXT) hpackenc_test$(EXEEXT) \ + @HAVE_WIN32_FALSE@am__append_248 = $(X_LIBS) $(X_PRE_LIBS) -lX11 + @HAVE_DARWIN_FALSE@@HAVE_WIN32_FALSE@am__append_249 = $(X_LIBS) $(X_PRE_LIBS) -lX11 + @HAVE_EVAS_TRUE@am__append_250 = libevas_plugin.la +-@HAVE_GL_TRUE@am__append_251 = libglspectrum_plugin.la + @ENABLE_SOUT_TRUE@@HAVE_GCRYPT_TRUE@am__append_252 = libaccess_output_livehttp_plugin.la + @ENABLE_SOUT_TRUE@am__append_253 = libaccess_output_shout_plugin.la \ + @ENABLE_SOUT_TRUE@ libaccess_output_srt_plugin.la \ +@@ -2029,13 +2028,6 @@ libgles2_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(libgles2_plugin_la_CFLAGS) $(CFLAGS) \ + $(libgles2_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +-libglspectrum_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ +- $(am__DEPENDENCIES_1) +-am_libglspectrum_plugin_la_OBJECTS = visualization/glspectrum.lo \ +- visualization/visual/fft.lo visualization/visual/window.lo +-libglspectrum_plugin_la_OBJECTS = \ +- $(am_libglspectrum_plugin_la_OBJECTS) +-@HAVE_GL_TRUE@am_libglspectrum_plugin_la_rpath = -rpath $(visudir) + libglwin32_plugin_la_DEPENDENCIES = libchroma_copy.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_5) + am__objects_23 = \ +@@ -6733,7 +6725,6 @@ SOURCES = $(liba52_plugin_la_SOURCES) $(libaa_plugin_la_SOURCES) \ + $(libglconv_vaapi_x11_plugin_la_SOURCES) \ + $(libglconv_vdpau_plugin_la_SOURCES) \ + $(libgles2_plugin_la_SOURCES) \ +- $(libglspectrum_plugin_la_SOURCES) \ + $(libglwin32_plugin_la_SOURCES) $(libglx_plugin_la_SOURCES) \ + $(libgme_plugin_la_SOURCES) $(libgnutls_plugin_la_SOURCES) \ + $(libgoom_plugin_la_SOURCES) $(libgradfun_plugin_la_SOURCES) \ +@@ -7132,7 +7123,6 @@ DIST_SOURCES = $(liba52_plugin_la_SOURCES) $(libaa_plugin_la_SOURCES) \ + $(libglconv_vaapi_x11_plugin_la_SOURCES) \ + $(libglconv_vdpau_plugin_la_SOURCES) \ + $(libgles2_plugin_la_SOURCES) \ +- $(libglspectrum_plugin_la_SOURCES) \ + $(libglwin32_plugin_la_SOURCES) $(libglx_plugin_la_SOURCES) \ + $(libgme_plugin_la_SOURCES) $(libgnutls_plugin_la_SOURCES) \ + $(libgoom_plugin_la_SOURCES) $(libgradfun_plugin_la_SOURCES) \ +@@ -12703,13 +12693,7 @@ libevent_thread_la_LDFLAGS = -static + visudir = $(pluginsdir)/visualization + visu_LTLIBRARIES = $(am__append_251) $(LTLIBgoom) $(LTLIBprojectm) \ + libvisual_plugin.la $(LTLIBvsxu) +-libglspectrum_plugin_la_SOURCES = \ +- visualization/glspectrum.c \ +- visualization/visual/fft.c visualization/visual/fft.h \ +- visualization/visual/window.c visualization/visual/window.h \ +- visualization/visual/window_presets.h + +-libglspectrum_plugin_la_LIBADD = $(GL_LIBS) $(LIBM) + libgoom_plugin_la_SOURCES = visualization/goom.c + libgoom_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(GOOM_CFLAGS) + libgoom_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(visudir)' +@@ -15735,8 +15719,6 @@ visualization/visual/fft.lo: visualization/visual/$(am__dirstamp) \ + visualization/visual/window.lo: visualization/visual/$(am__dirstamp) \ + visualization/visual/$(DEPDIR)/$(am__dirstamp) + +-libglspectrum_plugin.la: $(libglspectrum_plugin_la_OBJECTS) $(libglspectrum_plugin_la_DEPENDENCIES) $(EXTRA_libglspectrum_plugin_la_DEPENDENCIES) +- $(AM_V_CCLD)$(LINK) $(am_libglspectrum_plugin_la_rpath) $(libglspectrum_plugin_la_OBJECTS) $(libglspectrum_plugin_la_LIBADD) $(LIBS) + video_output/opengl/libglwin32_plugin_la-vout_helper.lo: \ + video_output/opengl/$(am__dirstamp) \ + video_output/opengl/$(DEPDIR)/$(am__dirstamp) +diff --git a/modules/hw/drm/Makefile.am b/modules/hw/drm/Makefile.am +index 8c3232364..bfaea8c25 100644 +--- a/modules/hw/drm/Makefile.am ++++ b/modules/hw/drm/Makefile.am +@@ -4,9 +4,6 @@ libdrm_avcodec_plugin_la_SOURCES = hw/drm/drm_avcodec.c + libdrm_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) + libdrm_avcodec_plugin_la_LIBADD = $(AVCODEC_LIBS) + +-libdrm_gl_conv_plugin_la_SOURCES = hw/drm/drm_gl_conv.c +-libdrm_gl_conv_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) +- + libdrm_conv_sand30_plugin_la_SOURCES = hw/drm/conv_sand30.c + libdrm_conv_sand30_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) + libdrm_conv_sand30_plugin_la_LIBADD = $(AVCODEC_LIBS) +@@ -21,6 +18,5 @@ if HAVE_DRM + drm_LTLIBRARIES = \ + libdrm_av_deinterlace_plugin.la \ + libdrm_avcodec_plugin.la \ +- libdrm_conv_sand30_plugin.la \ +- libdrm_gl_conv_plugin.la ++ libdrm_conv_sand30_plugin.la + endif +diff --git a/modules/hw/mmal/Makefile.am b/modules/hw/mmal/Makefile.am +index 4586cce8a..9fa9c816e 100644 +--- a/modules/hw/mmal/Makefile.am ++++ b/modules/hw/mmal/Makefile.am +@@ -30,14 +30,14 @@ libmmal_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) + libmmal_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) + libmmal_deinterlace_plugin_la_LIBADD = $(LIBS_mmal) + mmal_LTLIBRARIES += libmmal_deinterlace_plugin.la +- ++ + libmmal_converter_plugin_la_SOURCES = converter_mmal.c mmal_cma.c mmal_picture.c\ + mmal_cma.h mmal_cma_int.h mmal_picture.h transform_ops.h\ + mmal_piccpy_neon.S + libmmal_converter_plugin_la_CFLAGS = $(AM_CFLAGS) + libmmal_converter_plugin_la_LDFLAGS = $(AM_LDFLAGS) + libmmal_converter_plugin_la_LIBADD = $(LIBS_mmal) +-mmal_LTLIBRARIES += libmmal_converter_plugin.la ++#mmal_LTLIBRARIES += libmmal_converter_plugin.la + endif + + libmmal_xsplitter_plugin_la_SOURCES = xsplitter.c +diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am +index 70d47ee8c..cb80a2eb4 100644 +--- a/modules/video_output/Makefile.am ++++ b/modules/video_output/Makefile.am +@@ -57,70 +57,6 @@ if HAVE_TVOS + vout_LTLIBRARIES += libvout_ios_plugin.la libglconv_cvpx_plugin.la + endif + +-### OpenGL ### +-libgles2_plugin_la_SOURCES = $(OPENGL_COMMONSOURCES) video_output/opengl/display.c +-libgles2_plugin_la_CFLAGS = $(AM_CFLAGS) $(GLES2_CFLAGS) -DUSE_OPENGL_ES2 $(OPENGL_COMMONCLFAGS) +-libgles2_plugin_la_LIBADD = $(GLES2_LIBS) $(LIBM) $(OPENGL_COMMONLIBS) +-libgles2_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' +- +-EXTRA_LTLIBRARIES += libgles2_plugin.la +-vout_LTLIBRARIES += $(LTLIBgles2) +- +-libgl_plugin_la_SOURCES = $(OPENGL_COMMONSOURCES) video_output/opengl/display.c +-libgl_plugin_la_CFLAGS = $(AM_CFLAGS) $(GL_CFLAGS) $(OPENGL_COMMONCLFAGS) +-libgl_plugin_la_LIBADD = $(LIBM) $(OPENGL_COMMONLIBS) +-if HAVE_WIN32 +-libgl_plugin_la_CFLAGS += -DHAVE_GL_CORE_SYMBOLS +-libgl_plugin_la_LIBADD += $(GL_LIBS) +-endif +- +-libglconv_vaapi_wl_plugin_la_SOURCES = video_output/opengl/converter_vaapi.c \ +- video_output/opengl/converter.h \ +- hw/vaapi/vlc_vaapi.c hw/vaapi/vlc_vaapi.h +-libglconv_vaapi_wl_plugin_la_CFLAGS = $(AM_CFLAGS) $(GL_CFLAGS) -DHAVE_VA_WL $(LIBVA_WL_CFLAGS) +-libglconv_vaapi_wl_plugin_la_LIBADD = $(LIBVA_LIBS) $(LIBVA_EGL_LIBS) \ +- $(LIBVA_WL_LIBS) +- +-libglconv_vaapi_x11_plugin_la_SOURCES = $(libglconv_vaapi_wl_plugin_la_SOURCES) +-libglconv_vaapi_x11_plugin_la_CFLAGS = $(AM_CFLAGS) -DHAVE_VA_X11 +-libglconv_vaapi_x11_plugin_la_LIBADD = $(LIBVA_LIBS) $(LIBVA_EGL_LIBS) \ +- $(LIBVA_X11_LIBS) $(X_LIBS) $(X_PRE_LIBS) -lX11 +- +-libglconv_vaapi_drm_plugin_la_SOURCES = $(libglconv_vaapi_wl_plugin_la_SOURCES) +-libglconv_vaapi_drm_plugin_la_CFLAGS = $(AM_CFLAGS) -DHAVE_VA_DRM +-libglconv_vaapi_drm_plugin_la_LIBADD = $(LIBVA_LIBS) $(LIBVA_EGL_LIBS) \ +- $(LIBVA_DRM_LIBS) +- +-libglconv_vdpau_plugin_la_SOURCES = video_output/opengl/converter_vdpau.c \ +- video_output/opengl/converter.h hw/vdpau/vlc_vdpau.h +-libglconv_vdpau_plugin_la_CFLAGS = $(AM_CFLAGS) $(VDPAU_CFLAGS) +-libglconv_vdpau_plugin_la_LIBADD = $(LIBDL) libvlc_vdpau.la $(X_LIBS) $(X_PRE_LIBS) -lX11 +- +-if HAVE_GL +-vout_LTLIBRARIES += libgl_plugin.la +-if HAVE_EGL +-if HAVE_VAAPI +-if HAVE_WAYLAND_EGL +-if HAVE_VAAPI_WL +-vout_LTLIBRARIES += libglconv_vaapi_wl_plugin.la +-endif +-endif +-if HAVE_XCB +-if HAVE_VAAPI_X11 +-vout_LTLIBRARIES += libglconv_vaapi_x11_plugin.la +-endif +-endif +-if HAVE_VAAPI_DRM +-vout_LTLIBRARIES += libglconv_vaapi_drm_plugin.la +-endif +-endif +-endif # HAVE_EGL +- +-if HAVE_VDPAU +-vout_LTLIBRARIES += libglconv_vdpau_plugin.la +-endif +-endif # HAVE_GL +- + ### XCB ### + libvlc_xcb_events_la_SOURCES = \ + video_output/xcb/events.c video_output/xcb/events.h +@@ -156,15 +92,6 @@ libxcb_window_plugin_la_CFLAGS = $(AM_CFLAGS) \ + $(XPROTO_CFLAGS) $(XCB_CFLAGS) $(XCB_KEYSYMS_CFLAGS) + libxcb_window_plugin_la_LIBADD = $(XPROTO_LIBS) $(XCB_LIBS) $(XCB_KEYSYMS_LIBS) + +-libegl_x11_plugin_la_SOURCES = video_output/opengl/egl.c +-libegl_x11_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) -DUSE_PLATFORM_X11=1 +-libegl_x11_plugin_la_CFLAGS = $(AM_CFLAGS) $(EGL_CFLAGS) +-libegl_x11_plugin_la_LIBADD = $(EGL_LIBS) $(X_LIBS) $(X_PRE_LIBS) -lX11 +- +-libglx_plugin_la_SOURCES = video_output/glx.c +-libglx_plugin_la_CFLAGS = $(AM_CFLAGS) $(GL_CFLAGS) +-libglx_plugin_la_LIBADD = $(GL_LIBS) $(X_LIBS) $(X_PRE_LIBS) -lX11 +- + if HAVE_XCB + pkglib_LTLIBRARIES += libvlc_xcb_events.la + vout_LTLIBRARIES += libxcb_x11_plugin.la libxcb_window_plugin.la +@@ -174,12 +101,6 @@ endif + if HAVE_XCB_XVIDEO + vout_LTLIBRARIES += libxcb_xv_plugin.la + endif +-if HAVE_EGL +-vout_LTLIBRARIES += libegl_x11_plugin.la +-endif +-if HAVE_GL +-vout_LTLIBRARIES += libglx_plugin.la +-endif + endif + + ### DRM ### +@@ -253,22 +174,12 @@ video_output/wayland/server-decoration-protocol.c: \ + video_output/wayland/server-decoration.xml + EXTRA_DIST += video_output/wayland/server-decoration.xml + +-libegl_wl_plugin_la_SOURCES = video_output/opengl/egl.c +-libegl_wl_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) -DUSE_PLATFORM_WAYLAND=1 +-libegl_wl_plugin_la_CFLAGS = $(AM_CFLAGS) $(EGL_CFLAGS) $(WAYLAND_EGL_CFLAGS) +-libegl_wl_plugin_la_LIBADD = $(EGL_LIBS) $(WAYLAND_EGL_LIBS) +- + if HAVE_WAYLAND + BUILT_SOURCES += $(nodist_libwl_shm_plugin_la_SOURCES) + vout_LTLIBRARIES += libwl_shm_plugin.la + vout_LTLIBRARIES += libwl_shell_plugin.la + BUILT_SOURCES += $(nodist_libxdg_shell_plugin_la_SOURCES) + vout_LTLIBRARIES += libxdg_shell_plugin.la +-if HAVE_WAYLAND_EGL +-if HAVE_EGL +-vout_LTLIBRARIES += libegl_wl_plugin.la +-endif +-endif + endif + + +-- +2.34.1 + diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3005-modules-remove-glspectrum-usage.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3005-modules-remove-glspectrum-usage.patch deleted file mode 100644 index 7cf210be2..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3005-modules-remove-glspectrum-usage.patch +++ /dev/null @@ -1,149 +0,0 @@ -From 5f1bb5889d838719e381350b25c00ef3a75d0e02 Mon Sep 17 00:00:00 2001 -From: Vincent Davis Jr -Date: Fri, 9 Dec 2022 01:07:55 -0600 -Subject: [PATCH] modules: remove glspectrum usage - -Upstream-Status: Inappropriate - -RPI-Distro repo forks original vlc and applies patches to enable -raspiberry pi support. - -The glspectrum modules requries OpenGL -while we only want to utilize GLESv2. - -Signed-off-by: Vincent Davis Jr ---- - modules/Makefile.in | 24 ------------------------ - modules/visualization/Makefile.am | 10 ---------- - 2 files changed, 34 deletions(-) - -diff --git a/modules/Makefile.in b/modules/Makefile.in -index bde45db53..c9c4342ad 100644 ---- a/modules/Makefile.in -+++ b/modules/Makefile.in -@@ -481,7 +481,6 @@ TESTS = hpack_test$(EXEEXT) hpackenc_test$(EXEEXT) \ - @HAVE_WIN32_FALSE@am__append_247 = $(X_LIBS) $(X_PRE_LIBS) -lX11 - @HAVE_DARWIN_FALSE@@HAVE_WIN32_FALSE@am__append_248 = $(X_LIBS) $(X_PRE_LIBS) -lX11 - @HAVE_EVAS_TRUE@am__append_249 = libevas_plugin.la --@HAVE_GL_TRUE@am__append_250 = libglspectrum_plugin.la - @ENABLE_SOUT_TRUE@@HAVE_GCRYPT_TRUE@am__append_251 = libaccess_output_livehttp_plugin.la - @ENABLE_SOUT_TRUE@am__append_252 = libaccess_output_shout_plugin.la \ - @ENABLE_SOUT_TRUE@ libaccess_output_srt_plugin.la \ -@@ -2028,13 +2027,7 @@ libgles2_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(libgles2_plugin_la_CFLAGS) $(CFLAGS) \ - $(libgles2_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ --libglspectrum_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) --am_libglspectrum_plugin_la_OBJECTS = visualization/glspectrum.lo \ -- visualization/visual/fft.lo visualization/visual/window.lo --libglspectrum_plugin_la_OBJECTS = \ -- $(am_libglspectrum_plugin_la_OBJECTS) --@HAVE_GL_TRUE@am_libglspectrum_plugin_la_rpath = -rpath $(visudir) - libglwin32_plugin_la_DEPENDENCIES = libchroma_copy.la \ - $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_5) - am__objects_23 = \ -@@ -6507,7 +6500,6 @@ am__depfiles_remade = \ - video_splitter/$(DEPDIR)/clone.Plo \ - video_splitter/$(DEPDIR)/libpanoramix_plugin_la-panoramix.Plo \ - video_splitter/$(DEPDIR)/wall.Plo \ -- visualization/$(DEPDIR)/glspectrum.Plo \ - visualization/$(DEPDIR)/libgoom_plugin_la-goom.Plo \ - visualization/$(DEPDIR)/libprojectm_plugin_la-projectm.Plo \ - visualization/$(DEPDIR)/libvsxu_plugin_la-vsxu.Plo \ -@@ -6731,7 +6723,6 @@ SOURCES = $(liba52_plugin_la_SOURCES) $(libaa_plugin_la_SOURCES) \ - $(libglconv_vaapi_x11_plugin_la_SOURCES) \ - $(libglconv_vdpau_plugin_la_SOURCES) \ - $(libgles2_plugin_la_SOURCES) \ -- $(libglspectrum_plugin_la_SOURCES) \ - $(libglwin32_plugin_la_SOURCES) $(libglx_plugin_la_SOURCES) \ - $(libgme_plugin_la_SOURCES) $(libgnutls_plugin_la_SOURCES) \ - $(libgoom_plugin_la_SOURCES) $(libgradfun_plugin_la_SOURCES) \ -@@ -7130,7 +7121,6 @@ DIST_SOURCES = $(liba52_plugin_la_SOURCES) $(libaa_plugin_la_SOURCES) \ - $(libglconv_vaapi_x11_plugin_la_SOURCES) \ - $(libglconv_vdpau_plugin_la_SOURCES) \ - $(libgles2_plugin_la_SOURCES) \ -- $(libglspectrum_plugin_la_SOURCES) \ - $(libglwin32_plugin_la_SOURCES) $(libglx_plugin_la_SOURCES) \ - $(libgme_plugin_la_SOURCES) $(libgnutls_plugin_la_SOURCES) \ - $(libgoom_plugin_la_SOURCES) $(libgradfun_plugin_la_SOURCES) \ -@@ -12696,13 +12686,6 @@ libevent_thread_la_LDFLAGS = -static - visudir = $(pluginsdir)/visualization - visu_LTLIBRARIES = $(am__append_250) $(LTLIBgoom) $(LTLIBprojectm) \ - libvisual_plugin.la $(LTLIBvsxu) --libglspectrum_plugin_la_SOURCES = \ -- visualization/glspectrum.c \ -- visualization/visual/fft.c visualization/visual/fft.h \ -- visualization/visual/window.c visualization/visual/window.h \ -- visualization/visual/window_presets.h -- --libglspectrum_plugin_la_LIBADD = $(GL_LIBS) $(LIBM) - libgoom_plugin_la_SOURCES = visualization/goom.c - libgoom_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(GOOM_CFLAGS) - libgoom_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(visudir)' -@@ -15715,8 +15698,6 @@ visualization/$(am__dirstamp): - visualization/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) visualization/$(DEPDIR) - @: > visualization/$(DEPDIR)/$(am__dirstamp) --visualization/glspectrum.lo: visualization/$(am__dirstamp) \ -- visualization/$(DEPDIR)/$(am__dirstamp) - visualization/visual/$(am__dirstamp): - @$(MKDIR_P) visualization/visual - @: > visualization/visual/$(am__dirstamp) -@@ -15728,8 +15709,6 @@ visualization/visual/fft.lo: visualization/visual/$(am__dirstamp) \ - visualization/visual/window.lo: visualization/visual/$(am__dirstamp) \ - visualization/visual/$(DEPDIR)/$(am__dirstamp) - --libglspectrum_plugin.la: $(libglspectrum_plugin_la_OBJECTS) $(libglspectrum_plugin_la_DEPENDENCIES) $(EXTRA_libglspectrum_plugin_la_DEPENDENCIES) -- $(AM_V_CCLD)$(LINK) $(am_libglspectrum_plugin_la_rpath) $(libglspectrum_plugin_la_OBJECTS) $(libglspectrum_plugin_la_LIBADD) $(LIBS) - video_output/opengl/libglwin32_plugin_la-vout_helper.lo: \ - video_output/opengl/$(am__dirstamp) \ - video_output/opengl/$(DEPDIR)/$(am__dirstamp) -@@ -21420,7 +21399,6 @@ distclean-compile: - @AMDEP_TRUE@@am__include@ @am__quote@video_splitter/$(DEPDIR)/clone.Plo@am__quote@ # am--include-marker - @AMDEP_TRUE@@am__include@ @am__quote@video_splitter/$(DEPDIR)/libpanoramix_plugin_la-panoramix.Plo@am__quote@ # am--include-marker - @AMDEP_TRUE@@am__include@ @am__quote@video_splitter/$(DEPDIR)/wall.Plo@am__quote@ # am--include-marker --@AMDEP_TRUE@@am__include@ @am__quote@visualization/$(DEPDIR)/glspectrum.Plo@am__quote@ # am--include-marker - @AMDEP_TRUE@@am__include@ @am__quote@visualization/$(DEPDIR)/libgoom_plugin_la-goom.Plo@am__quote@ # am--include-marker - @AMDEP_TRUE@@am__include@ @am__quote@visualization/$(DEPDIR)/libprojectm_plugin_la-projectm.Plo@am__quote@ # am--include-marker - @AMDEP_TRUE@@am__include@ @am__quote@visualization/$(DEPDIR)/libvsxu_plugin_la-vsxu.Plo@am__quote@ # am--include-marker -@@ -30324,7 +30302,6 @@ distclean: distclean-recursive - -rm -f video_splitter/$(DEPDIR)/clone.Plo - -rm -f video_splitter/$(DEPDIR)/libpanoramix_plugin_la-panoramix.Plo - -rm -f video_splitter/$(DEPDIR)/wall.Plo -- -rm -f visualization/$(DEPDIR)/glspectrum.Plo - -rm -f visualization/$(DEPDIR)/libgoom_plugin_la-goom.Plo - -rm -f visualization/$(DEPDIR)/libprojectm_plugin_la-projectm.Plo - -rm -f visualization/$(DEPDIR)/libvsxu_plugin_la-vsxu.Plo -@@ -31722,7 +31699,6 @@ maintainer-clean: maintainer-clean-recursive - -rm -f video_splitter/$(DEPDIR)/clone.Plo - -rm -f video_splitter/$(DEPDIR)/libpanoramix_plugin_la-panoramix.Plo - -rm -f video_splitter/$(DEPDIR)/wall.Plo -- -rm -f visualization/$(DEPDIR)/glspectrum.Plo - -rm -f visualization/$(DEPDIR)/libgoom_plugin_la-goom.Plo - -rm -f visualization/$(DEPDIR)/libprojectm_plugin_la-projectm.Plo - -rm -f visualization/$(DEPDIR)/libvsxu_plugin_la-vsxu.Plo -diff --git a/modules/visualization/Makefile.am b/modules/visualization/Makefile.am -index 10619e030..aafc97f87 100644 ---- a/modules/visualization/Makefile.am -+++ b/modules/visualization/Makefile.am -@@ -1,16 +1,6 @@ - visudir = $(pluginsdir)/visualization - visu_LTLIBRARIES = - --libglspectrum_plugin_la_SOURCES = \ -- visualization/glspectrum.c \ -- visualization/visual/fft.c visualization/visual/fft.h \ -- visualization/visual/window.c visualization/visual/window.h \ -- visualization/visual/window_presets.h --libglspectrum_plugin_la_LIBADD = $(GL_LIBS) $(LIBM) --if HAVE_GL --visu_LTLIBRARIES += libglspectrum_plugin.la --endif -- - libgoom_plugin_la_SOURCES = visualization/goom.c - libgoom_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(GOOM_CFLAGS) - libgoom_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(visudir)' --- -2.38.1 - diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3006-codec-omxil_core.h-fix-multiple-definition-of.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3006-codec-omxil_core.h-fix-multiple-definition-of.patch deleted file mode 100644 index e680c88bc..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3006-codec-omxil_core.h-fix-multiple-definition-of.patch +++ /dev/null @@ -1,43 +0,0 @@ -From fd4d233757cc46cd89f68b45ec4b059940dd84ae Mon Sep 17 00:00:00 2001 -From: Vincent Davis Jr -Date: Fri, 9 Dec 2022 19:58:11 -0600 -Subject: [PATCH] codec: omxil_core.h fix multiple definition of - -Upstream-Status: Inappropriate - -RPI-Distro repo forks original vlc and applies patches -to enable raspiberry pi support. - -Issue occurs during compilation as -* pf_enable_graphic_buffers -* pf_get_graphic_buffer_usage -* pf_get_hal_format - -Apears to be defined multiple times as the omxil_core.h -is included in multiple files. - -Signed-off-by: Vincent Davis Jr ---- - modules/codec/omxil/omxil_core.h | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/modules/codec/omxil/omxil_core.h b/modules/codec/omxil/omxil_core.h -index ac3db510b..f6e42f5ed 100644 ---- a/modules/codec/omxil/omxil_core.h -+++ b/modules/codec/omxil/omxil_core.h -@@ -34,9 +34,9 @@ extern OMX_ERRORTYPE (*pf_component_enum)(OMX_STRING, OMX_U32, OMX_U32); - extern OMX_ERRORTYPE (*pf_get_roles_of_component)(OMX_STRING, OMX_U32 *, OMX_U8 **); - - /* Extra IOMX android functions. Can be NULL if we don't link with libiomx */ --OMX_ERRORTYPE (*pf_enable_graphic_buffers)(OMX_HANDLETYPE, OMX_U32, OMX_BOOL); --OMX_ERRORTYPE (*pf_get_graphic_buffer_usage)(OMX_HANDLETYPE, OMX_U32, OMX_U32*); --OMX_ERRORTYPE (*pf_get_hal_format) (const char *, int *); -+extern OMX_ERRORTYPE (*pf_enable_graphic_buffers)(OMX_HANDLETYPE, OMX_U32, OMX_BOOL); -+extern OMX_ERRORTYPE (*pf_get_graphic_buffer_usage)(OMX_HANDLETYPE, OMX_U32, OMX_U32*); -+extern OMX_ERRORTYPE (*pf_get_hal_format) (const char *, int *); - - int InitOmxCore(vlc_object_t *p_this); - void DeinitOmxCore(void); --- -2.38.1 - diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3008-vo-Makefile.am-exclude-libgl_plugin.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3008-vo-Makefile.am-exclude-libgl_plugin.patch deleted file mode 100644 index 8806c80a6..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3008-vo-Makefile.am-exclude-libgl_plugin.patch +++ /dev/null @@ -1,97 +0,0 @@ -From 28917a258a4173af0abda0eef7faef5cbf95f123 Mon Sep 17 00:00:00 2001 -From: Vincent Davis Jr -Date: Fri, 9 Dec 2022 21:28:48 -0600 -Subject: [PATCH] vo: Makefile.am exclude libgl_plugin - -Upstream-Status: Inappropriate - -RPI-Distro repo forks original vlc and applies patches -to enable raspiberry pi support. - -In the situation where opengl isn't included in -DISTRO_FEATURES. We need to exclude the opengl -vout plugin from being built. - -Signed-off-by: Vincent Davis Jr ---- - modules/video_output/Makefile.am | 64 -------------------------------- - 1 file changed, 64 deletions(-) - -diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am -index 78c06cfc4..14a330e68 100644 ---- a/modules/video_output/Makefile.am -+++ b/modules/video_output/Makefile.am -@@ -57,70 +57,6 @@ if HAVE_TVOS - vout_LTLIBRARIES += libvout_ios_plugin.la libglconv_cvpx_plugin.la - endif - --### OpenGL ### --libgles2_plugin_la_SOURCES = $(OPENGL_COMMONSOURCES) video_output/opengl/display.c --libgles2_plugin_la_CFLAGS = $(AM_CFLAGS) $(GLES2_CFLAGS) -DUSE_OPENGL_ES2 $(OPENGL_COMMONCLFAGS) --libgles2_plugin_la_LIBADD = $(GLES2_LIBS) $(LIBM) $(OPENGL_COMMONLIBS) --libgles2_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' -- --EXTRA_LTLIBRARIES += libgles2_plugin.la --vout_LTLIBRARIES += $(LTLIBgles2) -- --libgl_plugin_la_SOURCES = $(OPENGL_COMMONSOURCES) video_output/opengl/display.c --libgl_plugin_la_CFLAGS = $(AM_CFLAGS) $(GL_CFLAGS) $(OPENGL_COMMONCLFAGS) --libgl_plugin_la_LIBADD = $(LIBM) $(OPENGL_COMMONLIBS) --if HAVE_WIN32 --libgl_plugin_la_CFLAGS += -DHAVE_GL_CORE_SYMBOLS --libgl_plugin_la_LIBADD += $(GL_LIBS) --endif -- --libglconv_vaapi_wl_plugin_la_SOURCES = video_output/opengl/converter_vaapi.c \ -- video_output/opengl/converter.h \ -- hw/vaapi/vlc_vaapi.c hw/vaapi/vlc_vaapi.h --libglconv_vaapi_wl_plugin_la_CFLAGS = $(AM_CFLAGS) $(GL_CFLAGS) -DHAVE_VA_WL $(LIBVA_WL_CFLAGS) --libglconv_vaapi_wl_plugin_la_LIBADD = $(LIBVA_LIBS) $(LIBVA_EGL_LIBS) \ -- $(LIBVA_WL_LIBS) -- --libglconv_vaapi_x11_plugin_la_SOURCES = $(libglconv_vaapi_wl_plugin_la_SOURCES) --libglconv_vaapi_x11_plugin_la_CFLAGS = $(AM_CFLAGS) -DHAVE_VA_X11 --libglconv_vaapi_x11_plugin_la_LIBADD = $(LIBVA_LIBS) $(LIBVA_EGL_LIBS) \ -- $(LIBVA_X11_LIBS) $(X_LIBS) $(X_PRE_LIBS) -lX11 -- --libglconv_vaapi_drm_plugin_la_SOURCES = $(libglconv_vaapi_wl_plugin_la_SOURCES) --libglconv_vaapi_drm_plugin_la_CFLAGS = $(AM_CFLAGS) -DHAVE_VA_DRM --libglconv_vaapi_drm_plugin_la_LIBADD = $(LIBVA_LIBS) $(LIBVA_EGL_LIBS) \ -- $(LIBVA_DRM_LIBS) -- --libglconv_vdpau_plugin_la_SOURCES = video_output/opengl/converter_vdpau.c \ -- video_output/opengl/converter.h hw/vdpau/vlc_vdpau.h --libglconv_vdpau_plugin_la_CFLAGS = $(AM_CFLAGS) $(VDPAU_CFLAGS) --libglconv_vdpau_plugin_la_LIBADD = $(LIBDL) libvlc_vdpau.la $(X_LIBS) $(X_PRE_LIBS) -lX11 -- --if HAVE_GL --vout_LTLIBRARIES += libgl_plugin.la --if HAVE_EGL --if HAVE_VAAPI --if HAVE_WAYLAND_EGL --if HAVE_VAAPI_WL --vout_LTLIBRARIES += libglconv_vaapi_wl_plugin.la --endif --endif --if HAVE_XCB --if HAVE_VAAPI_X11 --vout_LTLIBRARIES += libglconv_vaapi_x11_plugin.la --endif --endif --if HAVE_VAAPI_DRM --vout_LTLIBRARIES += libglconv_vaapi_drm_plugin.la --endif --endif --endif # HAVE_EGL -- --if HAVE_VDPAU --vout_LTLIBRARIES += libglconv_vdpau_plugin.la --endif --endif # HAVE_GL -- - ### XCB ### - libvlc_xcb_events_la_SOURCES = \ - video_output/xcb/events.c video_output/xcb/events.h --- -2.38.1 - diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3009-vo-converter_vaapi-Fix-EGL-macro-undeclared.patch b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3009-vo-converter_vaapi-Fix-EGL-macro-undeclared.patch deleted file mode 100644 index 0f2819951..000000000 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/files/3009-vo-converter_vaapi-Fix-EGL-macro-undeclared.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 35276c4b02b9114436108e74727d192f1e21f239 Mon Sep 17 00:00:00 2001 -From: Vincent Davis Jr -Date: Fri, 9 Dec 2022 23:31:33 -0600 -Subject: [PATCH] vo: converter_vaapi Fix EGL macro undeclared - -Upstream-Status: Inappropriate - -RPI-Distro repo forks original vlc and applies patches to enable -raspiberry pi support. - -Fixes compiler issues related to EGL macro constant/enum value type -not being defined - -Signed-off-by: Vincent Davis Jr ---- - modules/video_output/opengl/converter_vaapi.c | 27 +++++++++++++++++++ - 1 file changed, 27 insertions(+) - -diff --git a/modules/video_output/opengl/converter_vaapi.c b/modules/video_output/opengl/converter_vaapi.c -index cd842f711..59245fe4c 100644 ---- a/modules/video_output/opengl/converter_vaapi.c -+++ b/modules/video_output/opengl/converter_vaapi.c -@@ -55,6 +55,33 @@ - - #define DRM_FORMAT_MOD_INVALID fourcc_mod_code(NONE, DRM_FORMAT_RESERVED) - -+#define EGL_LINUX_DMA_BUF_EXT 0x3270 -+#define EGL_LINUX_DRM_FOURCC_EXT 0x3271 -+#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 -+#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 -+#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 -+#define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 -+#define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 -+#define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 -+#define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 -+#define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 -+#define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A -+#define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B -+#define EGL_SAMPLE_RANGE_HINT_EXT 0x327C -+#define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D -+#define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E -+#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 -+#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 -+#define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 -+#define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 -+#define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 -+#define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 -+#define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 -+#define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 -+#define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 -+#define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 -+#define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A -+ - struct priv - { - struct vlc_vaapi_instance *vainst; --- -2.38.1 - diff --git a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/rpidistro-vlc_3.0.17.bb b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/rpidistro-vlc_3.0.18.bb similarity index 84% rename from dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/rpidistro-vlc_3.0.17.bb rename to dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/rpidistro-vlc_3.0.18.bb index 20072019b..a531bc1e5 100644 --- a/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/rpidistro-vlc_3.0.17.bb +++ b/dynamic-layers/multimedia-layer/recipes-multimedia/rpidistro-vlc/rpidistro-vlc_3.0.18.bb @@ -6,31 +6,26 @@ LICENSE = "GPL-2.0-only" LIC_FILES_CHKSUM = "file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263" SRC_URI = "\ - git://git@github.com/RPi-Distro/vlc;protocol=https;branch=buster-rpt \ + git://git@github.com/RPi-Distro/vlc;protocol=https;branch=bookworm-rpt \ file://0001-configure-fix-linking-on-RISC-V-ISA.patch \ file://0002-Revert-configure-Require-libmodplug-0.8.9.patch \ - file://0003-CVE-2022-41325.patch \ - file://0004-mmal_20.patch \ + file://0003-Do-not-generate-cache-during-build.patch \ + file://0004-mmal_35.patch \ file://0005-mmal_exit_fix.patch \ file://0006-mmal_chain.patch \ - file://0007-armv6.patch \ - file://0008-configure-Disable-incompatible-function-pointer-type.patch \ - file://0009-demux-dash-include-cstdint-needed-for-uint64_t.patch \ + file://0007-mmal_caca.patch \ + file://0008-mmal_wayland.patch \ + file://0009-configure-Disable-incompatible-function-pointer-type.patch \ file://2001-fix-luaL-checkint.patch \ file://2002-use-vorbisidec.patch \ file://3001-configure.ac-setup-for-OE-usage.patch \ - file://3002-fix-EGL-macro-undeclared-and-EGLImageKHR.patch \ - file://3003-codec-omxil_core-replace-opt-vc-path-with-usr-lib.patch \ - file://3004-use-GLESv2-headers-over-GL-headers.patch \ - file://3005-modules-remove-glspectrum-usage.patch \ - file://3006-codec-omxil_core.h-fix-multiple-definition-of.patch \ - ${@bb.utils.contains('DISTRO_FEATURES', 'x11', '', 'file://3007-remove-xorg-related-link-libs.patch', d)} \ - ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', '', 'file://3008-vo-Makefile.am-exclude-libgl_plugin.patch', d)} \ - file://3009-vo-converter_vaapi-Fix-EGL-macro-undeclared.patch \ - file://3010-po-Fix-typos-in-oc.po-for-gettext-compatibility.patch \ + file://3002-codec-omxil_core-replace-opt-vc-path-with-usr-lib.patch \ + ${@bb.utils.contains('DISTRO_FEATURES', 'x11', '', 'file://3003-remove-xorg-related-link-libs.patch', d)} \ + file://3004-po-Fix-typos-in-oc.po-for-gettext-compatibility.patch \ + file://3005-exclude-opengl-vout-plugin-remove-egl.patch \ " -SRCREV = "b276eb0d7bc3213363e97dbb681ef7c927be6c73" +SRCREV = "636141a3506e8de95683e3b0eb571bf9a9c19b96" S = "${WORKDIR}/git" @@ -39,7 +34,7 @@ RPROVIDES:${PN} = "${PROVIDES}" DEPENDS = "coreutils-native fribidi libtool libgcrypt libgcrypt-native \ dbus libxml2 gnutls tremor faad2 ffmpeg flac alsa-lib libidn \ jpeg xz libmodplug mpeg2dec libmtp libopus orc libsamplerate0 \ - avahi libusb1 schroedinger taglib tiff" + avahi libusb1 schroedinger taglib tiff bison flex" inherit autotools gettext pkgconfig mime-xdg @@ -66,7 +61,6 @@ EXTRA_OECONF = "\ PACKAGECONFIG ?= "\ ${@bb.utils.contains('DISTRO_FEATURES', 'x11', 'x11', '', d)} \ ${@bb.utils.contains('MACHINE_FEATURES', 'vc4graphics', '', 'mmal', d)} \ - ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', 'gles2', '', d)} \ ${@bb.utils.contains_any('DISTRO_FEATURES', 'x11', 'notify', '', d)} \ live555 dv1394 fontconfig fluidsynth freetype png udev \ x264 alsa harfbuzz jack neon fribidi dvbpsi a52 v4l2 \ @@ -122,6 +116,7 @@ PACKAGECONFIG[sdl-image] = "--enable-sdl-image,,libsdl-image" PACKAGECONFIG[v4l2] = "--enable-v4l2,,v4l-utils" TARGET_CFLAGS:append = " -I${STAGING_INCDIR}/drm" +TARGET_CFLAGS:append = " -I${RECIPE_SYSROOT}/usr/include" TARGET_LDFLAGS:append = " ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', '-lGLESv2', '', d)}" # Ensures the --enable-mmal-avcodec flag is available for usage @@ -129,7 +124,7 @@ do_configure:prepend() { olddir=`pwd` cd ${S} ./bootstrap - cd $olddir + cd $olddir } # This recipe packages vlc as a library as well, so qt4 dependencies