diff --git a/SOURCES/0001-Get-addr-by-ref.-from-getConnectionEndpointAddress.patch b/SOURCES/0001-Get-addr-by-ref.-from-getConnectionEndpointAddress.patch new file mode 100644 index 0000000..a4dca5c --- /dev/null +++ b/SOURCES/0001-Get-addr-by-ref.-from-getConnectionEndpointAddress.patch @@ -0,0 +1,50 @@ +From 94845266b705dc9de7921408531b9d7704f4458f Mon Sep 17 00:00:00 2001 +From: Dominic Mayers +Date: Sun, 28 Mar 2021 04:37:54 -0400 +Subject: [PATCH] Get addr by ref. from getConnectionEndpointAddress. + +Fixes issue #25473 in code.videolan.org. The maintainers of live555 changed +connectionEndpointAddresss to getConnectionEndpointAddress, which now provides +the address value by reference. Before, connectionEndpointAddresss returned +the value. So, in modules/access/live555.cpp, we must first get the value and +then pass it to IsMulticastAddress. The code will not compile with the recent +live555 unless we also modify modules/access/Makefile.am - a different patch. +--- + modules/access/live555.cpp | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/modules/access/live555.cpp b/modules/access/live555.cpp +index 01c535ca5b..95e15e35d9 100644 +--- a/modules/access/live555.cpp ++++ b/modules/access/live555.cpp +@@ -60,6 +60,7 @@ + #include + #include + #include ++#include + + extern "C" { + #include "../access/mms/asf.h" /* Who said ugly ? */ +@@ -727,7 +728,8 @@ static int SessionsSetup( demux_t *p_demux ) + unsigned const thresh = 200000; /* RTP reorder threshold .2 second (default .1) */ + const char *p_sess_lang = NULL; + const char *p_lang; +- ++ struct sockaddr_storage addr; ++ + b_rtsp_tcp = var_CreateGetBool( p_demux, "rtsp-tcp" ) || + var_GetBool( p_demux, "rtsp-http" ); + i_client_port = var_InheritInteger( p_demux, "rtp-client-port" ); +@@ -850,7 +852,8 @@ static int SessionsSetup( demux_t *p_demux ) + if( !p_sys->b_multicast ) + { + /* We need different rollover behaviour for multicast */ +- p_sys->b_multicast = IsMulticastAddress( sub->connectionEndpointAddress() ); ++ sub->getConnectionEndpointAddress(addr); ++ p_sys->b_multicast = IsMulticastAddress( addr ); + } + + tk = (live_track_t*)malloc( sizeof( live_track_t ) ); +-- +2.25.1 + diff --git a/SOURCES/0001-Revert-access-libdvdread-6.1.2-supports-UTF-8-paths-.patch b/SOURCES/0001-Revert-access-libdvdread-6.1.2-supports-UTF-8-paths-.patch new file mode 100644 index 0000000..84eee48 --- /dev/null +++ b/SOURCES/0001-Revert-access-libdvdread-6.1.2-supports-UTF-8-paths-.patch @@ -0,0 +1,74 @@ +From b107d105f8489edd6deafb340c65f435ab6e1c29 Mon Sep 17 00:00:00 2001 +From: Leigh Scott +Date: Mon, 14 Jun 2021 12:26:09 +0100 +Subject: [PATCH] Revert "access: libdvdread 6.1.2 supports UTF-8 paths in + Windows." + +This reverts commit 02b784c0cefe6d0d36bbc3b668a7f238c7f7b3ed. +--- + modules/access/dvdnav.c | 9 --------- + modules/access/dvdread.c | 9 --------- + 2 files changed, 18 deletions(-) + +diff --git a/modules/access/dvdnav.c b/modules/access/dvdnav.c +index 69abe50c8c..89fb66f11e 100644 +--- a/modules/access/dvdnav.c ++++ b/modules/access/dvdnav.c +@@ -371,14 +371,7 @@ static int AccessDemuxOpen ( vlc_object_t *p_this ) + goto bailout; + + /* Open dvdnav */ +-#if DVDREAD_VERSION < DVDREAD_VERSION_CODE(6, 1, 2) +- /* In libdvdread prior to 6.1.2, UTF8 is not supported for windows and +- * requires a prior conversion. +- * For non win32/os2 platforms, this is just a no-op */ + psz_path = ToLocale( psz_file ); +-#else +- psz_path = psz_file; +-#endif + #if DVDNAV_VERSION >= 60100 + dvdnav_logger_cb cbs; + cbs.pf_log = DvdNavLog; +@@ -397,10 +390,8 @@ static int AccessDemuxOpen ( vlc_object_t *p_this ) + + bailout: + free( psz_file ); +-#if DVDREAD_VERSION < DVDREAD_VERSION_CODE(6, 1, 2) + if( psz_path ) + LocaleFree( psz_path ); +-#endif + return i_ret; + } + +diff --git a/modules/access/dvdread.c b/modules/access/dvdread.c +index 749cbd47e0..e75bef5986 100644 +--- a/modules/access/dvdread.c ++++ b/modules/access/dvdread.c +@@ -212,14 +212,7 @@ static int Open( vlc_object_t *p_this ) + } + + /* Open dvdread */ +-#if DVDREAD_VERSION < DVDREAD_VERSION_CODE(6, 1, 2) +- /* In libdvdread prior to 6.1.2, UTF8 is not supported for windows and +- * requires a prior conversion. +- * For non win32/os2 platforms, this is just a no-op */ + const char *psz_path = ToLocale( psz_file ); +-#else +- const char *psz_path = psz_file; +-#endif + #if DVDREAD_VERSION >= DVDREAD_VERSION_CODE(6, 1, 0) + dvd_logger_cb cbs; + cbs.pf_log = DvdReadLog; +@@ -227,9 +220,7 @@ static int Open( vlc_object_t *p_this ) + #else + dvd_reader_t *p_dvdread = DVDOpen( psz_path ); + #endif +-#if DVDREAD_VERSION < DVDREAD_VERSION_CODE(6, 1, 2) + LocaleFree( psz_path ); +-#endif + if( p_dvdread == NULL ) + { + msg_Err( p_demux, "DVDRead cannot open source: %s", psz_file ); +-- +2.31.1 + diff --git a/SOURCES/0001-po-Fixup-invalid-format-string.patch b/SOURCES/0001-po-Fixup-invalid-format-string.patch new file mode 100644 index 0000000..2fd7609 --- /dev/null +++ b/SOURCES/0001-po-Fixup-invalid-format-string.patch @@ -0,0 +1,45 @@ +From 982f44d09bb61490194baf371d52c12016e0c5c9 Mon Sep 17 00:00:00 2001 +From: Nicolas Chauvet +Date: Fri, 28 Jul 2023 12:25:44 +0200 +Subject: [PATCH] po: Fixup invalid format string + +Will fix the following errors + +oc.po:5301: 'msgstr' is not a valid C format string, unlike 'msgid'. Reason: In the directive number 1, the argument size specifier is invalid. +oc.po:5306: 'msgstr' is not a valid C format string, unlike 'msgid'. Reason: In the directive number 1, the argument size specifier is invalid. +oc.po:5312: 'msgstr' is not a valid C format string, unlike 'msgid'. Reason: In the directive number 1, the argument size specifier is invalid. + +Signed-off-by: Nicolas Chauvet +--- + po/oc.po | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/po/oc.po b/po/oc.po +index 667696cfe785..ddb1677d8a53 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 +-- +2.41.0 + diff --git a/SOURCES/Remove_legacy_caca.patch b/SOURCES/Remove_legacy_caca.patch new file mode 100644 index 0000000..8374e71 --- /dev/null +++ b/SOURCES/Remove_legacy_caca.patch @@ -0,0 +1,98 @@ +diff -uNrp a/modules/video_output/caca.c b/modules/video_output/caca.c +--- a/modules/video_output/caca.c 2022-01-14 06:09:28.000000000 +0000 ++++ b/modules/video_output/caca.c 2022-01-18 23:09:44.979137301 +0000 +@@ -74,9 +74,9 @@ static void Place(vout_display_t *, vout + + /* */ + struct vout_display_sys_t { +- cucul_canvas_t *cv; ++ caca_canvas_t *cv; + caca_display_t *dp; +- cucul_dither_t *dither; ++ caca_dither_t *dither; + + picture_pool_t *pool; + vout_display_event_thread_t *et; +@@ -153,9 +153,9 @@ static int Open(vlc_object_t *object) + if (!sys) + goto error; + +- sys->cv = cucul_create_canvas(0, 0); ++ sys->cv = caca_create_canvas(0, 0); + if (!sys->cv) { +- msg_Err(vd, "cannot initialize libcucul"); ++ msg_Err(vd, "cannot initialize libcaca"); + goto error; + } + +@@ -209,11 +209,11 @@ error: + if (sys->pool) + picture_pool_Release(sys->pool); + if (sys->dither) +- cucul_free_dither(sys->dither); ++ caca_free_dither(sys->dither); + if (sys->dp) + caca_free_display(sys->dp); + if (sys->cv) +- cucul_free_canvas(sys->cv); ++ caca_free_canvas(sys->cv); + + free(sys); + } +@@ -235,9 +235,9 @@ static void Close(vlc_object_t *object) + if (sys->pool) + picture_pool_Release(sys->pool); + if (sys->dither) +- cucul_free_dither(sys->dither); ++ caca_free_dither(sys->dither); + caca_free_display(sys->dp); +- cucul_free_canvas(sys->cv); ++ caca_free_canvas(sys->cv); + + #if defined(_WIN32) + FreeConsole(); +@@ -266,7 +266,7 @@ static void Prepare(vout_display_t *vd, + + if (!sys->dither) { + /* Create the libcaca dither object */ +- sys->dither = cucul_create_dither(32, ++ sys->dither = caca_create_dither(32, + vd->source.i_visible_width, + vd->source.i_visible_height, + picture->p[0].i_pitch, +@@ -284,12 +284,12 @@ static void Prepare(vout_display_t *vd, + vout_display_place_t place; + Place(vd, &place); + +- cucul_set_color_ansi(sys->cv, CUCUL_COLOR_DEFAULT, CUCUL_COLOR_BLACK); +- cucul_clear_canvas(sys->cv); ++ caca_set_color_ansi(sys->cv, CACA_DEFAULT, CACA_BLACK); ++ caca_clear_canvas(sys->cv); + + const int crop_offset = vd->source.i_y_offset * picture->p->i_pitch + + vd->source.i_x_offset * picture->p->i_pixel_pitch; +- cucul_dither_bitmap(sys->cv, place.x, place.y, ++ caca_dither_bitmap(sys->cv, place.x, place.y, + place.width, place.height, + sys->dither, + &picture->p->p_pixels[crop_offset]); +@@ -328,7 +328,7 @@ static int Control(vout_display_t *vd, i + + case VOUT_DISPLAY_CHANGE_SOURCE_CROP: + if (sys->dither) +- cucul_free_dither(sys->dither); ++ caca_free_dither(sys->dither); + sys->dither = NULL; + return VLC_SUCCESS; + +@@ -366,8 +366,8 @@ static void Place(vout_display_t *vd, vo + + vout_display_PlacePicture(place, &vd->source, vd->cfg, false); + +- const int canvas_width = cucul_get_canvas_width(sys->cv); +- const int canvas_height = cucul_get_canvas_height(sys->cv); ++ const int canvas_width = caca_get_canvas_width(sys->cv); ++ const int canvas_height = caca_get_canvas_height(sys->cv); + const int display_width = caca_get_display_width(sys->dp); + const int display_height = caca_get_display_height(sys->dp); + diff --git a/SOURCES/fix-dav1d-1.0.patch b/SOURCES/fix-dav1d-1.0.patch new file mode 100644 index 0000000..47799bc --- /dev/null +++ b/SOURCES/fix-dav1d-1.0.patch @@ -0,0 +1,149 @@ +From 55b24abf7219a0c6a6560187496e41fd60638552 Mon Sep 17 00:00:00 2001 +From: Steve Lhomme +Date: Fri, 18 Mar 2022 11:42:49 +0100 +Subject: [PATCH 1/2] dav1d: fix compilation with (upcoming) dav1d 1.0 + +(cherry picked from commit dbf45cea2a8abdfbef897b8a71f3eb782bb1b712) (edited) +edited: +- 3.0 has the 128 pixels padding elsewhere +- 3.0 has an extra parameter for add_integer_with_range() +- 3.0 was setting i_extra_picture_buffers further down in the code +- 3.0 uses 16 threads max + +Signed-off-by: Steve Lhomme +--- + modules/codec/dav1d.c | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +diff --git a/modules/codec/dav1d.c b/modules/codec/dav1d.c +index 039165f52e..cfabbc27cb 100644 +--- a/modules/codec/dav1d.c ++++ b/modules/codec/dav1d.c +@@ -63,10 +63,16 @@ vlc_module_begin () + set_category(CAT_INPUT) + set_subcategory(SUBCAT_INPUT_VCODEC) + ++#if DAV1D_API_VERSION_MAJOR >= 6 ++ add_integer_with_range("dav1d-thread-frames", 0, 0, DAV1D_MAX_THREADS, ++ THREAD_FRAMES_TEXT, THREAD_FRAMES_LONGTEXT, false) ++ add_obsolete_string("dav1d-thread-tiles") // unused with dav1d 1.0 ++#else + add_integer_with_range("dav1d-thread-frames", 0, 0, DAV1D_MAX_FRAME_THREADS, + THREAD_FRAMES_TEXT, THREAD_FRAMES_LONGTEXT, false) + add_integer_with_range("dav1d-thread-tiles", 0, 0, DAV1D_MAX_TILE_THREADS, + THREAD_TILES_TEXT, THREAD_TILES_LONGTEXT, false) ++#endif + vlc_module_end () + + /***************************************************************************** +@@ -294,6 +300,11 @@ static int OpenDecoder(vlc_object_t *p_this) + return VLC_ENOMEM; + + dav1d_default_settings(&p_sys->s); ++#if DAV1D_API_VERSION_MAJOR >= 6 ++ p_sys->s.n_threads = var_InheritInteger(p_this, "dav1d-thread-frames"); ++ if (p_sys->s.n_threads == 0) ++ p_sys->s.n_threads = (i_core_count < 16) ? i_core_count : 16; ++#else + p_sys->s.n_tile_threads = var_InheritInteger(p_this, "dav1d-thread-tiles"); + if (p_sys->s.n_tile_threads == 0) + p_sys->s.n_tile_threads = +@@ -303,6 +314,7 @@ static int OpenDecoder(vlc_object_t *p_this) + p_sys->s.n_frame_threads = var_InheritInteger(p_this, "dav1d-thread-frames"); + if (p_sys->s.n_frame_threads == 0) + p_sys->s.n_frame_threads = (i_core_count < 16) ? i_core_count : 16; ++#endif + p_sys->s.allocator.cookie = dec; + p_sys->s.allocator.alloc_picture_callback = NewPicture; + p_sys->s.allocator.release_picture_callback = FreePicture; +@@ -313,12 +325,20 @@ static int OpenDecoder(vlc_object_t *p_this) + return VLC_EGENERIC; + } + ++#if DAV1D_API_VERSION_MAJOR >= 6 ++ msg_Dbg(p_this, "Using dav1d version %s with %d threads", ++ dav1d_version(), p_sys->s.n_threads); ++ ++ dec->i_extra_picture_buffers = (p_sys->s.n_threads - 1); ++#else + msg_Dbg(p_this, "Using dav1d version %s with %d/%d frame/tile threads", + dav1d_version(), p_sys->s.n_frame_threads, p_sys->s.n_tile_threads); + ++ dec->i_extra_picture_buffers = (p_sys->s.n_frame_threads - 1); ++#endif ++ + dec->pf_decode = Decode; + dec->pf_flush = FlushDecoder; +- dec->i_extra_picture_buffers = (p_sys->s.n_frame_threads - 1); + + dec->fmt_out.video.i_width = dec->fmt_in.video.i_width; + dec->fmt_out.video.i_height = dec->fmt_in.video.i_height; +-- +2.36.1 + + +From c95e5288ab2d222346b19552a462afe5159d1122 Mon Sep 17 00:00:00 2001 +From: Steve Lhomme +Date: Mon, 21 Mar 2022 15:53:52 +0100 +Subject: [PATCH 2/2] dav1d: limit the number of extra frames needed by the + decoder + +The i_extra_picture_buffers is used to add pictures to the pool that the core +will allocate. dav1d is actually using n_threads frames. And the core is +allocating 10 frames per default for AV1. So we need to add the missing ones. + +(cherry picked from commit a32031dc0f5f32083fc54a21397bce732742ccbe) (rebased) +rebased: +- the code dav1d 1.0.0 in 3.0 uses different max versions + +Signed-off-by: Steve Lhomme +--- + modules/codec/dav1d.c | 25 +++++++++++++++++++++++-- + 1 file changed, 23 insertions(+), 2 deletions(-) + +diff --git a/modules/codec/dav1d.c b/modules/codec/dav1d.c +index cfabbc27cb..8a439ce4ff 100644 +--- a/modules/codec/dav1d.c ++++ b/modules/codec/dav1d.c +@@ -304,7 +304,28 @@ static int OpenDecoder(vlc_object_t *p_this) + p_sys->s.n_threads = var_InheritInteger(p_this, "dav1d-thread-frames"); + if (p_sys->s.n_threads == 0) + p_sys->s.n_threads = (i_core_count < 16) ? i_core_count : 16; +-#else ++ ++#if DAV1D_API_VERSION_MAJOR > 6 || DAV1D_API_VERSION_MINOR >= 7 ++ // after dav1d 1.0.0 ++ p_sys->s.max_frame_delay = dav1d_get_frame_delay( &p_sys->s ); ++#else // 1.0.0 ++ // corresponds to c->n_fc when max_frame_delay is 0 in dav1d 1.0.0 ++ static const uint8_t fc_lut[49] = { ++ 1, /* 1 */ ++ 2, 2, 2, /* 2- 4 */ ++ 3, 3, 3, 3, 3, /* 5- 9 */ ++ 4, 4, 4, 4, 4, 4, 4, /* 10-16 */ ++ 5, 5, 5, 5, 5, 5, 5, 5, 5, /* 17-25 */ ++ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, /* 26-36 */ ++ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* 37-49 */ ++ }; ++ if (p_sys->s.n_threads >= 50) ++ p_sys->s.max_frame_delay = 8; ++ else ++ p_sys->s.max_frame_delay = fc_lut[p_sys->s.n_threads - 1]; ++#endif ++ ++#else // before dav1d 1.0.0 + p_sys->s.n_tile_threads = var_InheritInteger(p_this, "dav1d-thread-tiles"); + if (p_sys->s.n_tile_threads == 0) + p_sys->s.n_tile_threads = +@@ -329,7 +350,7 @@ static int OpenDecoder(vlc_object_t *p_this) + msg_Dbg(p_this, "Using dav1d version %s with %d threads", + dav1d_version(), p_sys->s.n_threads); + +- dec->i_extra_picture_buffers = (p_sys->s.n_threads - 1); ++ dec->i_extra_picture_buffers = p_sys->s.max_frame_delay; + #else + msg_Dbg(p_this, "Using dav1d version %s with %d/%d frame/tile threads", + dav1d_version(), p_sys->s.n_frame_threads, p_sys->s.n_tile_threads); +-- +2.36.1 + diff --git a/SOURCES/mmal_20.patch b/SOURCES/mmal_20.patch new file mode 100644 index 0000000..923f3ba --- /dev/null +++ b/SOURCES/mmal_20.patch @@ -0,0 +1,13821 @@ +--- a/configure.ac ++++ b/configure.ac +@@ -3444,6 +3444,9 @@ dnl + AC_ARG_ENABLE(mmal, + AS_HELP_STRING([--enable-mmal], + [Multi-Media Abstraction Layer (MMAL) hardware plugin (default enable)])) ++AC_ARG_ENABLE(mmal_avcodec, ++ AS_HELP_STRING([--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" +@@ -3454,7 +3457,7 @@ if test "${enable_mmal}" != "no"; then + 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_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif ]) ], [ ++ 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...]) ]) +@@ -3466,6 +3469,7 @@ if test "${enable_mmal}" != "no"; then + VLC_RESTORE_FLAGS + fi + AM_CONDITIONAL([HAVE_MMAL], [test "${have_mmal}" = "yes"]) ++AM_CONDITIONAL([HAVE_MMAL_AVCODEC], [test "${enable_mmal_avcodec}" = "yes"]) + + dnl + dnl evas plugin +--- a/include/vlc_fourcc.h ++++ b/include/vlc_fourcc.h +@@ -365,6 +365,11 @@ + + /* Broadcom MMAL opaque buffer type */ + #define VLC_CODEC_MMAL_OPAQUE VLC_FOURCC('M','M','A','L') ++#define VLC_CODEC_MMAL_ZC_SAND8 VLC_FOURCC('Z','S','D','8') ++#define VLC_CODEC_MMAL_ZC_SAND10 VLC_FOURCC('Z','S','D','0') ++#define VLC_CODEC_MMAL_ZC_SAND30 VLC_FOURCC('Z','S','D','3') ++#define VLC_CODEC_MMAL_ZC_I420 VLC_FOURCC('Z','4','2','0') ++#define VLC_CODEC_MMAL_ZC_RGB32 VLC_FOURCC('Z','R','G','B') + + /* 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 + +-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 + +-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 ++ ++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 ++ ++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 ++ ++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 ++ ++ +--- /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 ++ ++@ blend_rgbx_rgba_neon ++ ++@ Implements /255 as ((x * 257) + 0x8000) >> 16 ++@ This generates something in the range [(x+126)/255, (x+127)/255] which is good enough ++ ++@ There is advantage to aligning src and/or dest - dest gives a bit more due to being used twice ++ ++ ++ ++@ [r0] RGBx dest loaded into d20-d23 ++@ [r1] RGBA src merge loaded into d16-d19 ++@ r2 plane alpha ++@ r3 count (pixels) ++ ++.macro blend_main sR, sG, sB, sA, dR, dG, dB, dA ++ ++ push { r4, lr } ++ ++ vdup.u8 d7, r2 ++ ++ subs r3, #8 ++ vmov.u8 d6, #0xff ++ ++ blt 2f ++ ++ @ 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 ++ ++ @ Align r1 on a 32 byte boundary ++ neg r3, r0 ++ ubfx r3, r3, #2, #3 ++ ++ cmp r3, #0 ++ blne 10f ++ ++ sub r3, r4, r3 ++ ++1: ++ vld4.8 {d16, d17, d18, d19}, [r1] ++ ++1: ++ vmull.u8 q15, \sA, d7 ++ ++ vld4.8 {d20, d21, d22, d23}, [r0] ++ ++ vsra.u16 q15, q15, #8 ++ subs r3, #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 ++ addge r1, #32 ++ ++ vmlal.u8 q12, \dR, d30 ++ vmlal.u8 q13, \dG, d30 ++ vmlal.u8 q14, \dB, d30 ++ vld4.8 {d16, d17, d18, d19}, [r1] ++ ++ vsra.u16 q12, q12, #8 @ * 257/256 ++ vsra.u16 q13, q13, #8 ++ vsra.u16 q14, q14, #8 ++ ++ vrshrn.u16 \dR, q12, #8 ++ vrshrn.u16 \dG, q13, #8 ++ vrshrn.u16 \dB, q14, #8 ++ vmov.u8 \dA, #0xff ++ ++ vst4.8 {d20, d21, d22, d23}, [r0]! ++ bge 1b ++ add r1, #32 ++ ++2: ++ cmp r3, #-8 ++ blgt 10f ++ ++ pop { r4, pc } ++ ++ ++// 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 ++ ++ 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]! ++ ++ bx lr ++ ++.endm ++ ++ ++@ [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) ++ ++@ Whilst specified as RGBx+RGBA the only important part is the position of ++@ alpha, the other components are all treated the same ++ ++@ [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 ++ ++ ++@ [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 ++ ++ ++ +--- /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 ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++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; ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif ++ +--- /dev/null ++++ b/modules/hw/mmal/blend_test.c +@@ -0,0 +1,180 @@ ++#include ++#include ++#include ++ ++#include "blend_rgba_neon.h" ++ ++#define RPI_PROFILE 1 ++#define RPI_PROC_ALLOC 1 ++#include "rpi_prof.h" ++ ++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; ++} ++ ++static inline unsigned int a_merge(unsigned int dst, unsigned src, unsigned f) ++{ ++ return div255((255 - f) * (dst) + src * f); ++} ++ ++ ++static void merge_line(void * dest, const void * src, int alpha, unsigned int n) ++{ ++ unsigned int i; ++ const uint8_t * s_data = src; ++ uint8_t * d_data = dest; ++ ++ 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 ); ++ } ++} ++ ++ ++// 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; ++ ++ 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); ++ } ++} ++ ++#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) ++{ ++ uint32_t d0_buf[BUF_ALLOC]; ++ uint32_t d1_buf[BUF_ALLOC]; ++ const uint32_t * const s0 = sx + s_off; ++ ++ 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; ++ ++ 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); ++ } ++ } ++} ++ ++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) ++{ ++ uint32_t d0_buf[BUF_ALLOC]; ++ uint32_t d1_buf[BUF_ALLOC]; ++ const uint32_t * const s0 = sx + s_off; ++ ++ 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; ++ ++ memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4); ++ memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4); ++ ++ merge_line2(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); ++ ++ PROFILE_START(); ++ blend_bgrx_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); ++ } ++ } ++} ++ ++ ++ ++int main(int argc, char *argv[]) ++{ ++ unsigned int i, j; ++ uint32_t d0_buf[BUF_ALLOC]; ++ uint32_t s0_buf[BUF_ALLOC]; ++ ++ 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; ++ ++ PROFILE_INIT(); ++ ++ 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); ++ } ++ ++ for (i = 0; i != BUF_ALLOC; ++i) { ++ d0_buf[i] = 0xff00 | i; ++ s0_buf[i] = (i << 24) | 0x40ffc0; ++ } ++ ++ 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); ++ } ++ ++ 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"); ++ ++ 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); ++ } ++ ++ 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); ++ } ++ printf("Done 2\n"); ++ ++ return 0; ++} ++ +--- a/modules/hw/mmal/codec.c ++++ b/modules/hw/mmal/codec.c +@@ -26,267 +26,443 @@ + #include "config.h" + #endif + ++#include ++ + #include +-#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" ++ ++#define TRACE_ALL 0 ++ ++#define OPT_TO_FROM_ZC 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 ++ ++#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.") ++ ++#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 ++{ + 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; ++ ++ vcsm_init_type_t vcsm_init_type; ++ ++ // 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; ++ + /* 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; ++ ++#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; ++ ++ 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); ++} + +- 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 + +- 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); ++ ++ if (pic == NULL) ++ goto fail1; ++ ++ 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 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 ++ ++ return pic; ++ ++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; ++} ++ ++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(dec, "<<< %s: cmd=%d, data=%p", __func__, buffer->cmd, buffer->data); ++#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) ++{ ++ 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); ++ ++ 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) ++ { ++#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 ++ ++ picture_t *pic = alloc_opaque_pic(dec, buffer); ++#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; ++ } + +- 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; ++ ++ 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(dec, "%s: Updated", __func__); ++#endif ++ ++ 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 ++ + 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__); ++#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); ++ + 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; ++ ++ 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; ++ } + } +- 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 (!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->err_stream != MMAL_SUCCESS) { ++ msg_Err(dec, "MMAL error reported by ctrl"); ++ flush_decoder(dec); ++ return VLCDEC_ECRITICAL; /// I think they are all fatal ++ } ++ + /* + * 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); ++ } ++ ++ 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; ++ } ++ ++ if (!sys->input->is_enabled) ++ { ++ if ((status = set_extradata_and_commit(dec, sys)) != MMAL_SUCCESS) ++ goto fail; ++ ++ if ((status = mmal_port_enable(sys->input, input_port_cb)) != MMAL_SUCCESS) ++ { ++ msg_Err(dec, "Input port enable failed"); ++ goto fail; ++ } ++ ++ 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 +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) ++ { ++ 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; + } ++ + 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 +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; + ++#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); ++ ++ // 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) ++ ++static void CloseDecoder(decoder_t *dec) + { +- decoder_t *dec = (decoder_t *)port->userdata; ++ decoder_sys_t *sys = dec->p_sys; ++ ++#if TRACE_ALL ++ msg_Dbg(dec, "%s: <<<", __func__); ++#endif ++ ++ if (!sys) ++ return; ++ ++ if (sys->component != NULL) { ++ if (sys->input->is_enabled) ++ mmal_port_disable(sys->input); ++ ++ if (sys->output->is_enabled) ++ mmal_port_disable(sys->output); ++ ++ if (sys->component->control->is_enabled) ++ mmal_port_disable(sys->component->control); ++ ++ if (sys->component->is_enabled) ++ mmal_component_disable(sys->component); ++ ++ mmal_component_release(sys->component); ++ } ++ ++ 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); ++ ++#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); ++ ++ 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; ++ ++ 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 ((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)); ++ ++ sys->err_stream = MMAL_SUCCESS; ++ ++ 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; ++ } ++ ++ 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; ++ } ++ ++ 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; ++ } ++ ++ if ((status = set_extradata_and_commit(dec, sys)) != MMAL_SUCCESS) ++ goto fail; ++ ++ 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)); ++ 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; ++ ++ 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(dec, "Failed to enable component %s (status=%"PRIx32" %s)", ++ sys->component->name, status, mmal_status_to_string(status)); ++ goto fail; ++ } ++ ++ if ((sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) ++ { ++ msg_Err(dec, "Failed to create input pool"); ++ goto fail; ++ } ++ ++ sys->b_flushed = true; ++ ++ if ((status = decoder_send_extradata(dec, sys)) != MMAL_SUCCESS) ++ goto fail; ++ ++ // 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; ++ ++ ++ dec->pf_decode = decode; ++ dec->pf_flush = flush_decoder; ++ ++#if TRACE_ALL ++ msg_Dbg(dec, ">>> %s: ok", __func__); ++#endif ++ return 0; ++ ++fail: ++ CloseDecoder(dec); ++#if TRACE_ALL ++msg_Dbg(dec, ">>> %s: FAIL: ret=%d", __func__, ret); ++#endif ++ return ret; ++} ++ ++// ---------------------------- ++ ++#define CONV_MAX_LATENCY 1 // In frames ++ ++typedef struct pic_fifo_s { ++ picture_t * head; ++ picture_t * tail; ++} pic_fifo_t; ++ ++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; ++} ++ ++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); ++ } ++} ++ ++static inline void pic_fifo_init(pic_fifo_t * const pf) ++{ ++ pf->head = NULL; ++ pf->tail = NULL; // Not strictly needed ++} ++ ++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 ++ ++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 ++ ++ cma_buf_pool_t * cma_in_pool; ++ cma_buf_pool_t * cma_out_pool; ++ ++ subpic_reg_stash_t subs[SUBS_MAX]; ++ ++ pic_fifo_t ret_pics; ++ ++ unsigned int pic_n; ++ vlc_sem_t sem; ++ vlc_mutex_t lock; ++ ++ MMAL_STATUS_T err_stream; ++ ++ 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; ++ ++ uint64_t frame_seq; ++ conv_frame_stash_t stash[16]; ++ ++ // Slice specific tracking stuff ++ struct { ++ pic_fifo_t pics; ++ unsigned int line; // Lines filled ++ } slice; ++ ++ vcsm_init_type_t vcsm_init_type; ++} filter_sys_t; ++ ++ ++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 MMAL_STATUS_T conv_enable_in(filter_t * const p_filter, filter_sys_t * const sys) ++{ ++ MMAL_STATUS_T err = MMAL_SUCCESS; ++ ++ 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)); ++ } ++ return err; ++} ++ ++static MMAL_STATUS_T conv_enable_out(filter_t * const p_filter, filter_sys_t * const sys) ++{ ++ MMAL_STATUS_T err = MMAL_SUCCESS; ++ ++ 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); ++ } ++ ++ 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)); ++ } ++ return err; ++} ++ ++static void conv_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) ++{ ++ 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; ++ ++ p_filter->p_sys->err_stream = status; ++ ++ 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; ++ ++ 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 ++ ++ mmal_buffer_header_release(buf); ++ ++#if TRACE_ALL ++ msg_Dbg((filter_t *)port->userdata, ">>> %s", __func__); ++#endif ++} ++ ++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) ++{ ++ 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: 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 (buf->cmd != 0) ++ { ++ mmal_buffer_header_release(buf); ++ 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 ++ ++ 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; ++ ++ vlc_mutex_lock(&sys->lock); ++ pic_fifo_get(&sys->slice.pics); // Remove head from Q ++ vlc_mutex_unlock(&sys->lock); ++ ++ buf_to_pic_copy_props(pic, buf); ++ conv_out_q_pic(sys, pic); ++ } ++ } ++ } ++ ++ // Put back ++ buf->user_data = NULL; // Zap here to make sure we can't reuse later ++ mmal_buffer_header_reset(buf); ++ ++ if (mmal_port_send_buffer(sys->output, buf) != MMAL_SUCCESS) { ++ mmal_buffer_header_release(buf); ++ } ++ return; ++ ++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 ++} ++ ++ ++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 ++ ++ 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); ++ } ++ } ++ ++ if (sys->input != NULL && sys->input->is_enabled) ++ mmal_port_disable(sys->input); ++ ++ if (sys->output != NULL && sys->output->is_enabled) ++ mmal_port_disable(sys->output); ++ ++// 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; ++ ++ // Reset error status ++ sys->err_stream = MMAL_SUCCESS; ++ ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s", __func__); ++#endif ++} ++ ++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); ++ ++ 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; ++ } ++ } ++} ++ ++// 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); ++ ++ picture_t * const pic = header->user_data; ++ header->user_data = NULL; ++ ++ if (pic != NULL) ++ picture_Release(pic); ++ ++ return MMAL_FALSE; ++} ++ ++static MMAL_STATUS_T conv_set_output(filter_t * const p_filter, filter_sys_t * const sys, picture_t * const pic) ++{ ++ 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; ++ ++ return MMAL_SUCCESS; ++} ++ ++ ++static picture_t *conv_get_out_pics(filter_sys_t * const sys) ++{ ++ 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; ++} ++ ++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); ++ } ++#endif ++ ++ if (sys->err_stream != MMAL_SUCCESS) { ++ goto stream_fail; ++ } ++ ++ // 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)); ++ } ++ } ++ ++// 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 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; ++ ++ 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 ???? ++ } ++ ++#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 ((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; ++ } ++ } ++ ++ ++ // 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); ++ ++ // 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); ++#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; ++ } ++ ++ 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 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) ++{ ++ filter_t * const p_filter = (filter_t *)obj; ++ filter_sys_t * const sys = p_filter->p_sys; ++ unsigned int i; ++ ++#if TRACE_ALL ++ msg_Dbg(obj, "<<< %s", __func__); ++#endif ++ ++ 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); ++ ++ 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); ++ } ++ } ++ ++ 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->in_pool != NULL) ++ mmal_pool_destroy(sys->in_pool); ++ ++ if (sys->component) ++ mmal_component_release(sys->component); ++ ++ cma_vcsm_exit(sys->vcsm_init_type); ++ ++ vlc_sem_destroy(&sys->sem); ++ vlc_mutex_destroy(&sys->lock); ++ ++ p_filter->p_sys = NULL; ++ free(sys); ++} ++ ++ ++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); ++ ++ if (fmt->i_chroma == VLC_CODEC_I420 || ++ fmt->i_chroma == VLC_CODEC_I420_10L) ++ return MMAL_ENCODING_I420; ++ ++ return 0; ++} ++ ++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; ++} ++ ++ ++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; ++ ++ // At least in principle we should deal with any mmal format as input ++ if (enc_in == 0 || enc_out == 0) ++ return VLC_EGENERIC; ++ ++ // 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; ++ } + +- sys->output_format = format; + +- mmal_buffer_header_release(buffer); ++ if (use_resizer) { ++ // use resizer overrides use_isp ++ use_isp = false; ++ } ++ ++ // 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; ++ } ++ ++ gpu_mem = hw_mmal_get_gpu_mem(); ++ ++ { ++ 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); ++ } ++ ++ sys = calloc(1, sizeof(filter_sys_t)); ++ if (!sys) { ++ ret = VLC_ENOMEM; ++ goto fail; ++ } ++ p_filter->p_sys = sys; ++ ++ // 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; ++ ++ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { ++ msg_Err(p_filter, "VCSM init failed"); ++ 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); ++ 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; ++ } ++ ++ 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"); ++ 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); ++ ++ 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; ++ ++ if ((status = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS) ++ goto fail; ++ ++ port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, sys->is_sliced || sys->is_cma); ++ ++ 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->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) ++ { ++ msg_Err(p_filter, "Failed to open subpic %d", i); ++ goto fail; ++ } ++ } ++ } ++ ++ p_filter->pf_video_filter = conv_filter; ++ p_filter->pf_flush = conv_flush; ++ // video_drain NIF in filter structure ++ ++#if TRACE_ALL ++ msg_Dbg(p_filter, ">>> %s: ok", __func__); ++#endif ++ ++ return VLC_SUCCESS; ++ ++fail: ++ CloseConverter(obj); ++ ++ 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: FAIL: %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; ++ ++ ++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; ++ ++ 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 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 ++ ++ assert(p_filter->fmt_out.video.i_chroma == VLC_CODEC_MMAL_ZC_I420); ++ ++ picture_t * const out_pic = filter_NewPicture(p_filter); ++ if (out_pic == NULL) ++ goto fail0; ++ ++ 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}; ++ ++ hw_mmal_vlc_fmt_to_mmal_fmt(&mm_esfmt, &p_filter->fmt_out.video); ++ ++ 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) ++{ ++ VLC_UNUSED(p_filter); + } ++ ++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; ++ ++ if (sys == NULL) ++ return; ++ ++ 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; ++ } ++ ++ return true; ++} ++ ++static int OpenConverterToZc(vlc_object_t * obj) ++{ ++ 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; ++ ++ { ++ 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; ++ } ++ 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->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; ++ } ++ ++ 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); ++} ++ ++static int OpenConverterFromZc(vlc_object_t * obj) ++{ ++ 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; ++ ++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) ++ { ++ 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); ++ ++ sys->last_dst = dst; ++ } ++} ++ ++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); ++ } ++} ++ ++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)) ++ { ++ 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); ++ } ++ ++ { ++ 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; ++ } ++ ++ 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; ++ 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; ++ ++#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); ++#endif ++ ++ if (alpha == 0 || ++ src_pic->format.i_visible_height == 0 || ++ src_pic->format.i_visible_width == 0) ++ { ++ 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; ++ } ++ ++ // *** L/U overrun ++ ++ 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; ++ ++ ++ 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; ++ ++ // 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; ++ } ++ ++ if (blend_fn == (blend_neon_fn *)0) ++ { ++ return VLC_EGENERIC; ++ } ++ ++ 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); ++ } ++ ++ return VLC_SUCCESS; ++} ++ ++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) ++ ++ 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() ++ ++ +--- /dev/null ++++ b/modules/hw/mmal/converter_mmal.c +@@ -0,0 +1,479 @@ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "mmal_cma.h" ++ ++#include "../../video_output/opengl/converter.h" ++ ++#include "mmal_picture.h" ++ ++#include ++ ++#define TRACE_ALL 0 ++ ++typedef struct mmal_gl_converter_s ++{ ++ EGLint drm_fourcc; ++ vcsm_init_type_t vcsm_init_type; ++ cma_buf_t * last_cb; ++ ++ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; ++} mmal_gl_converter_t; ++ ++ ++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)) ++ { ++ 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 0; ++} ++ ++typedef struct tex_context_s { ++ picture_context_t cmn; ++ GLuint texture; ++ ++ PFNGLDELETETEXTURESPROC DeleteTextures; // Copy fn pointer so we don't need tc on delete ++} tex_context_t; ++ ++static void tex_context_delete(tex_context_t * const tex) ++{ ++ tex->DeleteTextures(1, &tex->texture); ++ free(tex); ++} ++ ++static void tex_context_destroy(picture_context_t * pic_ctx) ++{ ++ tex_context_delete((tex_context_t *)pic_ctx); ++} ++ ++static picture_context_t * tex_context_copy(picture_context_t * pic_ctx) ++{ ++ return pic_ctx; ++} ++ ++static tex_context_t * get_tex_context(const opengl_tex_converter_t * const tc, picture_t * const pic, cma_buf_t * const cb) ++{ ++ mmal_gl_converter_t * const sys = tc->priv; ++ tex_context_t * tex = (tex_context_t *)cma_buf_context2(cb); ++ if (tex != NULL) ++ return tex; ++ ++ if ((tex = malloc(sizeof(*tex))) == NULL) ++ return NULL; ++ ++ *tex = (tex_context_t){ ++ .cmn = { ++ .destroy = tex_context_destroy, ++ .copy = tex_context_copy ++ }, ++ .texture = 0, ++ .DeleteTextures = tc->vt->DeleteTextures ++ }; ++ ++ { ++ EGLint attribs[30]; ++ EGLint * a = attribs; ++ const int fd = cma_buf_fd(cb); ++ uint8_t * base_addr = cma_buf_addr(cb); ++ ++ 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; ++ ++ 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 ++ }; ++ ++ const EGLint * n = attnames; ++ ++ 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); ++ ++ *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; ++ ++ 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; ++ } ++ } ++ ++ *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; ++ } ++ ++ // ** ?? 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) ++ { ++ msg_Err(tc, "%s: add_context2 failed", __func__); ++ goto fail; ++ } ++ return tex; ++ ++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); ++ ++ 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; ++ } ++ ++ 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; ++} ++ ++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); ++} ++ ++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_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) ++{ ++ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; ++ mmal_gl_converter_t * const sys = tc->priv; ++ ++ if (sys == NULL) ++ return; ++ ++ 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) ++{ ++ 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; ++ } ++ return 0; ++} ++ ++ ++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); ++ ++ // Do we know what to do with this? ++ if (chroma_out == 0) ++ return rv; ++ ++ { ++ 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 (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; ++ } ++ ++ 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; ++ ++ sys->drm_fourcc = eglfmt; ++ ++ if ((sys->vcsm_init_type = cma_vcsm_init()) != VCSM_INIT_CMA) { ++ msg_Dbg(tc, "VCSM init failed"); ++ goto fail; ++ } ++ ++ if ((sys->glEGLImageTargetTexture2DOES = vlc_gl_GetProcAddress(tc->gl, "glEGLImageTargetTexture2DOES")) == NULL) ++ { ++ msg_Err(tc, "Failed to bind GL fns"); ++ goto fail; ++ } ++ ++ 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) ++ { ++ 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; ++ } ++ 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)); ++ } ++#endif ++ ++ return VLC_SUCCESS; ++ ++fail: ++ CloseGLConverter(obj); ++ return rv; ++} ++ ++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 () ++ +--- a/modules/hw/mmal/deinterlace.c ++++ b/modules/hw/mmal/deinterlace.c +@@ -26,11 +26,12 @@ + #include "config.h" + #endif + +-#include ++#include ++ + #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") ++ ++#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") ++ ++#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 { ++ ++typedef struct filter_sys_t ++{ + MMAL_COMPONENT_T *component; + MMAL_PORT_T *input; + MMAL_PORT_T *output; ++ MMAL_POOL_T *in_pool; ++ ++ 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; ++ ++ hw_mmal_port_pool_ref_t *out_ppr; ++ ++ 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 ++ ++ ++ ++// 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) ++{ ++#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 ++} ++ ++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; ++ ++#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 ++ ++ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic)) ++ { ++ // ****** Breaks on opaque (at least) ++ ++ if (sys->input->is_enabled) ++ mmal_port_disable(sys->input); ++#if 0 ++ if (sys->output->is_enabled) ++ mmal_port_disable(sys->output); ++ ++ 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); ++ } ++ ++ // 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"); ++ 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; ++ } + } ++ 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); ++ ++ 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; ++ ++#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; ++ } + +- 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); ++ ++ 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); ++ ++ // 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); ++ ++ 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; ++ ++ // 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 ++ ++ 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; ++ ++ picture_t * out_pic; ++ ++ 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; ++ } ++ ++ // 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; ++ ++ buf_to_pic_copy_props(out_pic, out_buf); ++ ++ // 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); ++ } ++ ++ 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 (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 ++ ++ *pp_pic = out_pic; ++ pp_pic = &out_pic->p_next; ++ ++ // Ignore 0 seqs ++ // Don't think these should actually happen ++ if (seq_out != 0) ++ sys->seq_out = seq_out; ++ } ++ ++ // 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; ++ ++fail: ++ if (out_buf != NULL) ++ mmal_buffer_header_release(out_buf); ++ picture_Release(p_pic); ++ return NULL; ++} ++ ++static void di_flush(filter_t *p_filter) ++{ ++ filter_sys_t * const sys = 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); ++ ++ 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); ++ ++ mmal_port_disable(sys->output); ++ ++ // 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); ++ ++ // 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); ++ ++ p_pic->b_progressive = true; ++ return p_pic; + } + +-static void Close(filter_t *filter) ++ ++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)); ++ } ++ ++ mmal_buffer_header_reset(buffer); ++ mmal_buffer_header_release(buffer); ++} ++ ++static void CloseMmalDeinterlace(filter_t *filter) ++{ ++ filter_sys_t * const sys = filter->p_sys; ++ ++#if TRACE_ALL ++ msg_Dbg(filter, "<<< %s", __func__); ++#endif ++ ++ 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; ++ } + +- 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); ++ ++ cma_buf_pool_deletez(&sys->cma_out_pool); ++ ++ 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); ++} ++ + +- 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; ++ ++ int ret = VLC_EGENERIC; + MMAL_STATUS_T status; +- picture_t *picture; +- int ret = 0; ++ filter_sys_t *sys; ++ ++ 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; + +- 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); ++ ++ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { ++ msg_Err(filter, "VCSM init failed"); ++ goto fail; ++ } ++ ++ if (rpi_is_model_pi4()) ++ { ++ 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; ++ ++ 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)) ++ { ++ 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; ++ } ++ if (var_InheritBool(filter, MMAL_DEINTERLACE_FAST)) ++ { ++ 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 (sys->use_passthrough) ++ { ++ 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; ++ } ++ ++ { ++ 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; ++ } + +- 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)); ++ 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; ++ } ++ 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) ++ { ++ 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; ++ } + +- 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) ++ { ++ if ((status = hw_mmal_opaque_output(VLC_OBJECT(filter), &sys->out_ppr, sys->output, 5, di_output_port_cb)) != MMAL_SUCCESS) ++ goto fail; ++ } ++ 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; ++ ++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); ++ ++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. ++ *****************************************************************************/ ++ ++/***************************************************************************** ++ * Preamble ++ *****************************************************************************/ ++#include "config.h" ++ ++#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 "avcodec.h" ++//#include "va.h" ++ ++#include ++#include ++#include ++#include "../../codec/cc.h" ++#include "../../codec/avcodec/avcommon.h" // ??? Beware over inclusion ++#include "mmal_cma.h" ++#include "mmal_picture.h" ++ ++#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 ++{ ++ 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; ++ 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); ++ ++ return fmt->i_chroma == 0 ? -1 : 0; ++} ++ ++ ++// Format chooser is way simpler than vlc ++static enum PixelFormat ++ZcGetFormat(AVCodecContext *p_context, const enum PixelFormat *pi_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++) ++ { ++ if (ZcFindVlcChroma(pi_fmt[i]) != 0) ++ return pi_fmt[i]; ++ } ++ return swfmt; ++} ++ ++ ++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); ++} ++ ++static unsigned int zc_buf_vc_handle(void * v) ++{ ++ return cma_buf_vc_handle(v); ++} ++ ++static void * zc_buf_map_arm(void * v) ++{ ++ return cma_buf_addr(v); ++} ++ ++static unsigned int zc_buf_map_vc(void * v) ++{ ++ return cma_buf_vc_addr(v); ++} ++ ++ ++ ++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) ++{ ++ decoder_t * const dec = v; ++ decoder_sys_t * const sys = dec->p_sys; ++ ++ VLC_UNUSED(geo); ++ ++ assert(sys != NULL); ++ ++ 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); ++ ++ if (!sys->pool_alloc_1) ++ { ++ 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); ++ } ++ } ++ ++ 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; ++ } ++ ++ AVBufferRef *const avbuf = av_rpi_zc_buf(cma_buf_size(cmabuf), 0, cmabuf, &zc_buf_fn_tab); ++ ++ if (avbuf == NULL) ++ { ++ msg_Err(dec, "av_rpi_zc_buf failed"); ++ cma_buf_unref(cmabuf); ++ return NULL; ++ } ++ ++ return avbuf; ++} ++ ++static void ++zc_free_pool(void * v) ++{ ++ decoder_t * const dec = v; ++ cma_buf_pool_delete(dec->p_sys->cma_pool); ++} ++ ++ ++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 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; ++ ++ 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; ++ } ++ ++ 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; ++ } ++ ++ 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] ++ }; ++ } ++ return 0; ++} ++ ++ ++//============================================================================ ++// ++// Nicked from avcodec/fourcc.c ++// ++// * Really we should probably use that directly ++ ++/* ++ * Video Codecs ++ */ ++ ++struct vlc_avcodec_fourcc ++{ ++ vlc_fourcc_t i_fourcc; ++ unsigned i_codec; ++}; ++ ++ ++static const struct vlc_avcodec_fourcc video_codecs[] = ++{ ++ { 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 */ ++ ++ { 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 }, ++ ++ { VLC_CODEC_HQ_HQA, AV_CODEC_ID_HQ_HQA }, ++ ++ { VLC_CODEC_HAP, AV_CODEC_ID_HAP }, ++ /* AV_CODEC_ID_DDS */ ++ ++ { VLC_CODEC_DXV, AV_CODEC_ID_DXV }, ++ ++ /* 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 LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 24, 102 ) ++ { VLC_CODEC_CINEFORM, AV_CODEC_ID_CFHD }, ++#endif ++ ++#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 70, 100 ) ++ { VLC_CODEC_PIXLET, AV_CODEC_ID_PIXLET }, ++#endif ++ ++#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( 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; ++} ++ ++ ++ ++//============================================================================ ++// 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)); ++ ++ /* *** 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 ) ++ { ++ 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_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); ++ } ++ } ++ ++ if( !p_codec ) ++ { ++ msg_Dbg( p_dec, "codec not found (%s)", psz_namecodec ); ++ return NULL; ++ } ++ ++ *codecp = p_codec; ++ ++ /* *** get a p_context *** */ ++ AVCodecContext *avctx = avcodec_alloc_context3(p_codec); ++ if( unlikely(avctx == NULL) ) ++ return NULL; ++ ++ avctx->debug = var_InheritInteger( p_dec, "avcodec-debug" ); ++ avctx->opaque = p_dec; ++ return avctx; ++} ++ ++/***************************************************************************** ++ * 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; ++ ++ if (psz_opts) { ++ vlc_av_get_options(psz_opts, &options); ++ free(psz_opts); ++ } ++ ++ 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; ++ } ++ ++ vlc_avcodec_lock(); ++ ret = avcodec_open2( ctx, codec, options ? &options : NULL ); ++ vlc_avcodec_unlock(); ++ ++ 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 ++ ++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 ++} ++ ++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 * ); ++ ++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) ++{ ++ int width = ctx->coded_width; ++ int height = ctx->coded_height; ++ ++ video_format_Init(fmt, 0); ++ ++#if 1 ++ VLC_UNUSED(sw_pix_fmt); ++ if ((fmt->i_chroma = ZcFindVlcChroma(pix_fmt)) == 0) ++ return -1; ++#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); ++#endif ++ ++ if( width == 0 || height == 0 || width > 8192 || height > 8192 || ++ width < ctx->width || height < ctx->height ) ++ { ++ 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; ++ ++ /* 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 ++ { ++ 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; ++ } ++ ++ 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); ++ } ++ ++ /* 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; ++ } ++ ++ 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; ++ } ++ ++ 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; ++ } ++ ++ 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 ) ++ { ++ 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; ++ } ++ ++ return 0; ++} ++ ++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__); ++#endif ++ val = lavc_GetVideoFormat(dec, &fmt_out, ctx, fmt, swfmt); ++ if (val) ++ { ++ 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 ++ date_Init(&dec->p_sys->pts, fmt_out.i_frame_rate * ++ __MAX(ctx->ticks_per_frame, 1), ++ fmt_out.i_frame_rate_base); ++ ++ fmt_out.p_palette = dec-> fmt_out.video.p_palette; ++ dec->fmt_out.video.p_palette = NULL; ++ ++ 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; ++} ++ ++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 ) ++ { ++ msg_Warn( p_dec, "waiting for extra data for codec %s", ++ codec->name ); ++ return 1; ++ } ++ } ++ ++ 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; ++ ++ 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; ++ ++ 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; ++} ++ ++/***************************************************************************** ++ * 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; ++ ++ int extra_buffers = var_InheritInteger(p_dec, MMAL_AVCODEC_BUFFERS); ++ ++ 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; ++ } ++ ++ if (extra_buffers <= 0) ++ { ++ msg_Dbg(p_dec, "%s: extra_buffers=%d - cannot use module", __func__, extra_buffers); ++ return VLC_EGENERIC; ++ } ++ ++ 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; ++ ++#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 ++ ++ 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; ++ } ++#endif ++ ++ AVCodecContext *p_context = ZcFfmpeg_AllocContext( p_dec, &p_codec ); ++ if( p_context == NULL ) ++ { ++ cma_vcsm_exit(vcsm_type); ++ return VLC_EGENERIC; ++ } ++ ++ int i_val; ++ ++ /* 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; ++ } ++ ++ 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 ); ++ ++ /* ***** 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 ); ++ ++ /* ***** 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" ); ++ ++ if( var_CreateGetBool( p_dec, "grayscale" ) ) ++ p_context->flags |= AV_CODEC_FLAG_GRAY; ++ ++ /* ***** Output always the frames ***** */ ++ p_context->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT; ++ ++ 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( var_CreateGetBool( p_dec, "avcodec-fast" ) ) ++ p_context->flags2 |= AV_CODEC_FLAG2_FAST; ++ ++ /* ***** 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" ); ++ ++ 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; ++ ++ 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; ++ } ++ ++ 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 ++ ++ 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++; ++ ++ //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; ++ ++ 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; ++ } ++ ++ if( p_context->thread_type & FF_THREAD_FRAME ) ++ p_dec->i_extra_picture_buffers = 2 * p_context->thread_count; ++ ++ /* ***** 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) ++ { ++ 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; ++ ++fail: ++ MmalAvcodecCloseDecoder(VLC_OBJECT(p_dec)); ++ ++#if TRACE_ALL ++ msg_Dbg(p_dec, "<<< %s: FAIL", __func__); ++#endif ++ ++ return VLC_EGENERIC; ++} ++ ++/***************************************************************************** ++ * Flush: ++ *****************************************************************************/ ++static void Flush( decoder_t *p_dec ) ++{ ++ 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 ++ ++} ++ ++static bool check_block_validity( decoder_sys_t *p_sys, block_t *block ) ++{ ++ 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 ) ++ { ++ block_Release( block ); ++ return 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 ) ++ { ++ /* 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; ++ } ++ ++ if( p_sys->i_late_frames <= 0 ) ++ return false; ++ ++ 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; ++} ++ ++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 ++ } ++#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 ++ ++ 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 ); ++ } ++ } ++ 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 ) ++ { ++ ffmpeg_InitCodec( p_dec ); ++ if( !avcodec_is_open( p_context ) ) ++ OpenVideoCodec( p_dec ); ++ } ++ ++ 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; ++ } ++ ++ 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) ) ++ { ++ msg_Err( p_dec, "more than 5 seconds of late video -> " ++ "dropping frame (computer too slow ?)" ); ++ return NULL; ++ } ++ ++ ++ /* 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; ++ ++ /* 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 ); ++ } ++ ++ /* ++ * 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 ) ++ { ++ 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( !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; ++ } ++ } ++ ++ /* Make sure we don't reuse the same timestamps twice */ ++ if( p_block ) ++ { ++ p_block->i_pts = ++ p_block->i_dts = VLC_TS_INVALID; ++ } ++ ++ 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 ); ++ ++ frame = av_frame_alloc(); ++ if (unlikely(frame == NULL)) ++ { ++ *error = true; ++ break; ++ } ++ ++ 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; ++ ++ wait_mt( p_sys ); ++ ++ if( eos_spotted ) ++ p_sys->b_first_frame = true; ++ ++ if( p_block ) ++ { ++ if( p_block->i_buffer <= 0 ) ++ eos_spotted = false; ++ ++ /* Consumed bytes */ ++ p_block->p_buffer += i_used; ++ p_block->i_buffer -= i_used; ++ } ++ ++ /* 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; ++ ++ if( i_pts == AV_NOPTS_VALUE ) ++ i_pts = date_Get( &p_sys->pts ); ++ ++ /* Interpolate the next PTS */ ++ if( i_pts > VLC_TS_INVALID ) ++ date_Set( &p_sys->pts, i_pts ); ++ ++ const mtime_t i_next_pts = interpolate_next_pts(p_dec, frame); ++ ++ 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->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 ); ++ ++ 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; ++ } ++ ++ 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; ++ } ++ ++ if ((p_pic = decoder_NewPicture(p_dec)) == NULL) ++ { ++ msg_Err(p_dec, "Failed to allocate pic"); ++ goto fail; ++ } ++ ++ 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; ++ } ++ ++ // ****** 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 ++ ++ 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; ++ } ++ } ++ ++ 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; ++ ++ if (DecodeSidedata(p_dec, frame, p_pic)) ++ i_pts = VLC_TS_INVALID; ++ ++ av_frame_free(&frame); ++ ++ /* 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 ); ++ ++#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 DecodeVideo( decoder_t *p_dec, block_t *p_block ) ++{ ++ 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; ++} ++ ++/***************************************************************************** ++ * EndVideo: decoder destruction ++ ***************************************************************************** ++ * This function is called when the thread ends after a successful ++ * initialization. ++ *****************************************************************************/ ++static void MmalAvcodecCloseDecoder( vlc_object_t *obj ) ++{ ++ 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; ++ ++ msg_Dbg(obj, "<<< %s", __func__); ++ ++ post_mt( p_sys ); ++ ++ 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 ); ++ ++ av_rpi_zc_uninit2(ctx); ++ ++ wait_mt( p_sys ); ++ ++ cc_Flush( &p_sys->cc ); ++ ++// hwaccel_context = ctx->hwaccel_context; ++ avcodec_free_context( &ctx ); ++ ++// 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; ++ ++ if( p_sys->p_codec->id == AV_CODEC_ID_SVQ3 ) ++ { ++ uint8_t *p; ++ ++ 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 ); ++ ++ /* 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]; ++ ++ 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; ++ } ++ ++ 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 ); ++ } ++ } ++} ++ ++ ++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 ++ ++#define TRACE_ALL 0 ++ ++//----------------------------------------------------------------------------- ++// ++// Generic pool functions ++// Knows nothing about pool entries ++ ++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 TRACE_ALL ++static atomic_int pool_seq; ++#endif ++ ++// 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; ++ ++ 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; ++ ++ 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 inline unsigned int inc_mod(const unsigned int n, const unsigned int m) ++{ ++ return n + 1 >= m ? 0 : n + 1; ++} ++ ++static void free_pool(const cma_pool_fixed_t * const p, void ** const pool, ++ const unsigned int pool_size, const size_t el_size) ++{ ++ if (pool == NULL) ++ return; ++ ++ 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); ++} ++ ++// 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; ++ ++ free_pool(p, p->pool, p->pool_size, p->el_size); ++ ++ if (p->name != NULL) ++ free((void *)p->name); // Discard const ++ ++ vlc_cond_destroy(&p->flight_cond); ++ vlc_mutex_destroy(&p->lock); ++ free(p); ++ ++ // 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) ++{ ++ if (atomic_fetch_sub(&p->ref_count, 1) <= 1) ++ cma_pool_fixed_delete(p); ++} ++ ++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); ++} ++ ++static void cma_pool_fixed_dec_in_flight(cma_pool_fixed_t * const p) ++{ ++ vlc_mutex_lock(&p->lock); ++ if (--p->in_flight == 0) ++ vlc_cond_signal(&p->flight_cond); ++ vlc_mutex_unlock(&p->lock); ++} ++ ++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) ++{ ++ void * v = NULL; ++ ++ vlc_mutex_lock(&p->lock); ++ ++ 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; ++ ++ p->pool = NULL; ++ p->n_in = 0; ++ p->n_out = 0; ++ p->el_size = req_el_size; ++ ++ 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; ++ } ++ } ++ ++ // 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_cond_wait(&p->flight_cond, &p->lock); ++ } ++ ++ if (inc_flight) ++ ++p->in_flight; ++ ++ 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; ++} ++ ++static void cma_pool_fixed_put(cma_pool_fixed_t * const p, void * v, const size_t el_size, const bool was_in_flight) ++{ ++ vlc_mutex_lock(&p->lock); ++ ++ 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*)); ++ ++ p->pool[p->n_out] = v; ++ p->n_out = inc_mod(p->n_out, p->pool_size); ++ v = NULL; ++ } ++ ++ 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); ++} ++ ++static int cma_pool_fixed_resize(cma_pool_fixed_t * const p, ++ const unsigned int new_pool_size, const int new_flight_size) ++{ ++ void ** dead_pool = NULL; ++ size_t dead_size = 0; ++ unsigned int dead_n = 0; ++ ++ // 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; ++ ++ vlc_mutex_lock(&p->lock); ++ ++ 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; ++ ++ if (new_pool == NULL) ++ { ++ vlc_mutex_unlock(&p->lock); ++ return -1; ++ } ++ ++ 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; ++ } ++ ++ 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(&p->lock); ++ ++ free_pool(p, dead_pool, dead_n, dead_size); ++ return 0; ++} ++ ++static int cma_pool_fixed_fill(cma_pool_fixed_t * const p, const size_t el_size) ++{ ++ for (;;) ++ { ++ 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); ++ } ++ 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); ++} ++ ++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); ++} ++ ++ ++// Purge pool & unref ++static void cma_pool_fixed_kill(cma_pool_fixed_t * const p) ++{ ++ 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); ++} ++ ++// 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) ++{ ++ 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); ++#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; ++ ++ bool all_in_flight; ++#if TRACE_ALL ++ size_t alloc_n; ++ size_t alloc_size; ++#endif ++}; ++ ++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; ++ ++static void cma_pool_delete(cma_buf_t * const cb) ++{ ++ 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 ++ ++ if (cb->ctx2 != NULL) ++ cb->ctx2->destroy(cb->ctx2); ++ ++ 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 (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); ++} ++ ++static void * cma_pool_alloc_cb(void * v, size_t size) ++{ ++ cma_buf_pool_t * const cbp = v; ++ ++ cma_buf_t * const cb = malloc(sizeof(cma_buf_t)); ++ if (cb == NULL) ++ return NULL; ++ ++ *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 ++ ++ // 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; ++ } ++ ++ 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; ++ } ++ ++ if (cbp->init_type == VCSM_INIT_CMA) ++ { ++ 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; ++ } ++ 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; ++ } ++ ++ cb->vc_addr = vcsm_vc_addr_from_hdl(cb->vcsm_h); ++ ++ return cb; ++ ++fail: ++ cma_pool_delete(cb); ++ return NULL; ++} ++ ++// 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); ++} ++ ++void cma_buf_pool_cancel(cma_buf_pool_t * const cbp) ++{ ++ if (cbp == NULL || cbp->pool == NULL) ++ return; ++ ++ cma_pool_fixed_cancel(cbp->pool); ++} ++ ++void cma_buf_pool_uncancel(cma_buf_pool_t * const cbp) ++{ ++ if (cbp == NULL || cbp->pool == NULL) ++ return; ++ ++ cma_pool_fixed_uncancel(cbp->pool); ++} ++ ++// User finished with pool ++void cma_buf_pool_delete(cma_buf_pool_t * const cbp) ++{ ++ if (cbp == NULL) ++ return; ++ ++ 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); ++ } ++} ++ ++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); ++} ++ ++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); ++} ++ ++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; ++ ++ cma_buf_pool_t * const cbp = calloc(1, sizeof(cma_buf_pool_t)); ++ if (cbp == NULL) ++ return NULL; ++ ++ cbp->init_type = init_type; ++ cbp->all_in_flight = all_in_flight; ++ ++ 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; ++ ++fail: ++ cma_buf_pool_delete(cbp); ++ return NULL; ++} ++ ++ ++void cma_buf_in_flight(cma_buf_t * const cb) ++{ ++ if (!cb->cbp->all_in_flight) ++ { ++ assert(!cb->in_flight); ++ cb->in_flight = true; ++ cma_pool_fixed_inc_in_flight(cb->cbp->pool); ++ } ++} ++ ++void cma_buf_end_flight(cma_buf_t * const cb) ++{ ++ if (cb != NULL && !cb->cbp->all_in_flight && cb->in_flight) ++ { ++ cb->in_flight = false; ++ cma_pool_fixed_dec_in_flight(cb->cbp->pool); ++ } ++} ++ ++ ++// Return vcsm handle ++unsigned int cma_buf_vcsm_handle(const cma_buf_t * const cb) ++{ ++ 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; ++} ++ ++ ++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); ++ } ++} ++ ++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 (cb == NULL) ++ return NULL; ++ ++ 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); ++} ++ +--- /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_ ++ ++ ++struct cma_pool_fixed_s; ++typedef struct cma_pool_fixed_s cma_pool_fixed_t; ++ ++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); ++#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; ++ ++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); ++ ++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); ++ } ++} ++ ++#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 ++ ++struct mmal_gl_converter_s; ++ ++typedef struct cma_buf_s { ++ struct mmal_gl_converter_s * sys; ++ ++ size_t size; ++ __u32 h_dumb; ++ int fd; ++ unsigned int h_vcsm; ++ void * mapped_addr; ++ GLuint texture; ++} cma_buf_t; ++ ++typedef struct cma_pic_sys_s { ++ cma_buf_t * cmabuf; ++} cma_pic_sys_t; ++ ++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; ++ ++ if (pic->format.i_chroma != VLC_CODEC_MMAL_GL_RGB32 || ++ pic_sys == NULL || pic_sys->cmabuf == NULL) { ++ return 0; ++ } ++ ++ return pic_sys->cmabuf->h_vcsm; ++} ++ +--- /dev/null ++++ b/modules/hw/mmal/mmal_piccpy_neon.S +@@ -0,0 +1,105 @@ ++// Copy pix ++ ++ .syntax unified ++ .arm ++// .thumb ++ .text ++ .align 16 ++ .arch armv7-a ++ .fpu neon-vfpv4 ++ ++ ++.macro function name ++ .global \name ++#ifdef __ELF__ ++ .type \name, %function ++#endif ++\name: ++.endm ++ ++ ++.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 ++ ++ 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 ++ ++ vldm r1!, {q0} ++ vqrshrn.u16 d0, q0, #\bit_depth - 8 ++ vstr d0, [r0] ++ add r0, #8 ++1: ++ adds r2, #4 ++ blt 1f ++ ++ vldr d0, [r1] ++ vqrshrn.u16 d0, q0, #\bit_depth - 8 ++ vstr s0, [r0] ++1: ++ vpop {q4-q7} ++ bx lr ++.endm ++ ++ ++@ [r0] Dest ++@ [r1] Src ++@ r2 Pels ++function mmal_piccpy_10_to_8_neon ++ piccpy_to_8 10 ++ +--- 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 ++ ++#include ++#include ++#include ++ + #include ++#include + #include ++ ++#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" ++ ++#define TRACE_TRANSFORMS 0 ++ ++#define UINT64_SIZE(s) (((s) + sizeof(uint64_t) - 1)/sizeof(uint64_t)) ++ ++static inline char safe_char(const unsigned int c0) ++{ ++ const unsigned int c = c0 & 0xff; ++ return c > ' ' && c < 0x7f ? c : '.'; ++} ++ ++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; ++} ++ ++// 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; ++ ++ *b = (struct vcsm_user_clean_invalid2_s){ ++ .op_count = 1 ++ }; ++ ++ 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); ++} ++ ++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; ++} ++ ++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; ++} ++ ++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; ++ ++ 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); ++} ++ ++ ++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); ++} ++ ++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; ++ ++ vlc_fmt_to_video_format(vf_new, &pic->format); ++ ++ // 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; ++ } ++ ++ 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; ++} ++ ++ ++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; ++ ++ if ((ppr->pool = mmal_port_pool_create(port, headers, payload_size)) == NULL) ++ goto fail; ++ ++ ppr->port = port; ++ atomic_store(&ppr->refs, 1); ++ return ppr; ++ ++fail: ++ free(ppr); ++ return NULL; ++} ++ ++static void do_detached(void *(*fn)(void *), void * v) ++{ ++ pthread_t dothread; ++ pthread_create(&dothread, NULL, fn, v); ++ pthread_detach(dothread); ++} ++ ++// 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; ++} ++ ++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); ++} ++ ++// 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; ++ ++ if (mmal_port_send_buffer(ppr->port, buf) == MMAL_SUCCESS) ++ return true; ++ mmal_buffer_header_release(buf); ++ return false; ++} ++ ++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_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; ++ } ++ ++ 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(obj, "Failed to commit format on port %s (status=%"PRIx32" %s)", ++ port->name, status, mmal_status_to_string(status)); ++ return status; ++ } ++ ++ port->buffer_num = 30; ++ port->buffer_size = port->buffer_size_recommended; ++ ++ 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; ++ } ++ ++ 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; ++ } ++ ++ return MMAL_SUCCESS; ++} ++ ++ ++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; ++ ++ for (i = 0; i != ctx->buf_count; ++i) { ++ if (ctx->bufs[i] != NULL) ++ mmal_buffer_header_release(ctx->bufs[i]); ++ } ++ ++ cma_buf_end_flight(ctx->cb); ++ cma_buf_unref(ctx->cb); ++ ++ free(ctx); ++} ++ ++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 (dst_ctx == NULL) ++ return NULL; ++ ++ // Copy ++ dst_ctx->cmn = src_ctx->cmn; ++ ++ dst_ctx->cb = cma_buf_ref(src_ctx->cb); ++ ++ 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]); ++ } ++ ++ return &dst_ctx->cmn; ++} ++ ++static MMAL_BOOL_T ++buf_pre_release_cb(MMAL_BUFFER_HEADER_T * buf, void *userdata) ++{ ++ hw_mmal_port_pool_ref_t * const ppr = userdata; ++ ++ // 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 ++ ++ // 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 ++ ++ return MMAL_TRUE; ++} ++ ++// 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)); ++ ++ if (ctx == NULL) ++ return NULL; ++ ++ // 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; ++ } ++ ++ ctx->cmn.copy = hw_mmal_pic_ctx_copy; ++ ctx->cmn.destroy = hw_mmal_pic_ctx_destroy; ++ ++ ctx->buf_count = 1; ++ ctx->bufs[0] = buf; ++ ++ return &ctx->cmn; ++} ++ ++// 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; ++ } ++ } ++} ++ ++// 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; ++ } ++ } ++} ++ ++ ++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; ++ ++ //**** Worry about x/y_offsets ++ ++ assert(fmt->encoding == MMAL_ENCODING_I420); ++ ++ 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); ++ ++ 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); ++ ++ 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); ++ ++ // And make sure it is actually in memory ++ length = y_size + y_size / 2; ++ break; ++ } ++ ++ 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); ++ ++ 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); ++ ++ // And make sure it is actually in memory ++ length = y_size + y_size / 2; ++ break; ++ } ++ ++ default: ++ if (pLength != NULL) ++ *pLength = 0; ++ return VLC_EBADVAR; ++ } ++ ++ if (cma_vcsm_type() == VCSM_INIT_LEGACY) { // ** CMA is currently always uncached ++ flush_range(dest, length); ++ } ++ ++ if (pLength != NULL) ++ *pLength = (uint32_t)length; ++ ++ return VLC_SUCCESS; ++} ++ ++ ++static MMAL_BOOL_T rep_buf_free_cb(MMAL_BUFFER_HEADER_T *header, void *userdata) ++{ ++ cma_buf_t * const cb = userdata; ++ VLC_UNUSED(header); ++ ++ cma_buf_unref(cb); ++ return MMAL_FALSE; ++} ++ ++static int cma_buf_buf_attach(MMAL_BUFFER_HEADER_T * const buf, cma_buf_t * const cb) ++{ ++ // 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; ++} ++ ++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 *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; ++ ++ if (cma_buf_buf_attach(buf, cb) != VLC_SUCCESS) ++ goto fail2; ++ ++ pic_to_buf_copy_props(buf, pic); ++ ++ 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; ++ ++ 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); ++ ++ 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; ++ ++fail: ++ mmal_buffer_header_release(rep_buf); ++ return NULL; ++} ++ ++ ++ ++ ++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 }; ++ ++ 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; ++ ++ 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; ++ ++ 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; ++ ++ vc_gencmd_stop(); ++ ++ //close the vchi connection ++ vchi_disconnect(vchi_instance); ++ ++ return stashed_val; ++ ++fail: ++ vc_gencmd_stop(); ++ vchi_disconnect(vchi_instance); ++fail0: ++ stashed_val = -1; ++ return -1; ++}; ++ ++// =========================================================================== ++ ++typedef struct pool_ent_s ++{ ++ struct pool_ent_s * next; ++ struct pool_ent_s * prev; ++ ++ atomic_int ref_count; ++ unsigned int seq; ++ ++ size_t size; ++ ++ 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; ++ ++ ++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 \ ++} ++ ++struct vzc_pool_ctl_s ++{ ++ atomic_int ref_count; ++ ++ ent_list_hdr_t ent_pool; ++ ent_list_hdr_t ents_cur; ++ ent_list_hdr_t ents_prev; ++ ++ unsigned int max_n; ++ unsigned int seq; ++ ++ vlc_mutex_t lock; ++ ++ MMAL_POOL_T * buf_pool; ++ ++ vcsm_init_type_t vcsm_init_type; ++}; ++ ++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; ++ ++ ++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); ++ ++ 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 ++} ++ ++static inline pool_ent_t * ent_extract_tail(ent_list_hdr_t * const elh) ++{ ++ return ent_extract(elh, elh->tail); ++} ++ ++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); ++ ++ if ((ent->next = elh->ents) == NULL) ++ elh->tail = ent; ++ else ++ ent->next->prev = ent; ++ ++ ent->prev = NULL; ++ elh->ents = ent; ++ ++elh->n; ++} ++ ++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); ++ ++ vcsm_free(ent->vcsm_hdl); ++ ++ free(ent); ++ } ++} ++ ++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); ++ ++ *elh = ENT_LIST_HDR_INIT; ++ ++ while (ent != NULL) { ++ pool_ent_t * const t = ent; ++ ent = t->next; ++ ent_free(t); ++ } ++} ++ ++static void ent_list_move(ent_list_hdr_t * const dst, ent_list_hdr_t * const src) ++{ ++// printf("Move %p->%p\n", src, dst); ++ ++ *dst = *src; ++ *src = ENT_LIST_HDR_INIT; ++} ++ ++// 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; ++ ++// printf("Find list: %p [%d]; pic:%p\n", elh, elh->n, pic); ++ ++ while (ent != NULL) { ++// printf("Check ent: %p, pic:%p\n", ent, ent->pic); ++ ++ if (ent->pic == pic) ++ return ent_extract(elh, ent); ++ ent = ent->prev; ++ } ++ 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; ++ ++ ent->next = ent->prev = NULL; ++ ++ // 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; ++ ++ ent->size = alloc_size; ++ return ent; ++ ++fail2: ++ vcsm_free(ent->vcsm_hdl); ++fail1: ++ free(ent); ++ return NULL; ++} ++ ++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; ++} ++ ++static void pool_recycle(vzc_pool_ctl_t * const pc, pool_ent_t * const ent) ++{ ++ pool_ent_t * xs = NULL; ++ int n; ++ ++ if (ent == NULL) ++ return; ++ ++ n = atomic_fetch_sub(&ent->ref_count, 1) - 1; ++ ++// printf("%s: Pool: %p: Ent: %p: %d\n", __func__, &pc->ent_pool, ent, n); ++ ++ if (n != 0) ++ return; ++ ++ if (ent->pic != NULL) { ++ picture_Release(ent->pic); ++ ent->pic = NULL; ++ } ++ ++ 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); ++ ++ vlc_mutex_unlock(&pc->lock); ++ ++ ent_free(xs); ++} ++ ++// * 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) ++{ ++ pool_ent_t * best = NULL; ++ ++ vlc_mutex_lock(&pc->lock); ++ ++ { ++ 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_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; ++} ++ ++ ++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) ++{ ++ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent; ++ *pW = ent->width; ++ *pH = ent->height; ++} ++ ++bool hw_mmal_vzc_buf_set_format(MMAL_BUFFER_HEADER_T * const buf, MMAL_ES_FORMAT_T * const es_fmt) ++{ ++ 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; ++ ++ 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; ++ ++ return true; ++} ++ ++void hw_mmal_vzc_buf_frame_size(MMAL_BUFFER_HEADER_T * const buf, ++ uint32_t * const pWidth, uint32_t * const pHeight) ++{ ++ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent; ++ *pWidth = ent->width; ++ *pHeight = ent->height; ++} ++ ++ ++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; ++} ++ ++static inline int rescale_x(int x, int mul, int div) ++{ ++ return div == 0 ? x * mul : (x * mul + div/2) / div; ++} ++ ++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 MMAL_RECT_T ++rect_untransform(MMAL_RECT_T s, const MMAL_RECT_T c, const MMAL_DISPLAYTRANSFORM_T t) ++{ ++#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; ++} ++ ++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 ++ { ++ // 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; ++ } ++} ++ ++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; ++} ++ ++ ++// 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. ++ ++// dst_fmt gives the number space in which the destination pixels are specified ++ ++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) ++{ ++ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_get(pc->buf_pool->queue); ++ vzc_subbuf_ent_t * sb; ++ ++ 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); + +-int mmal_picture_lock(picture_t *picture) ++ if (ent == NULL) ++ { ++ // 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); ++ } ++ ++ 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) ++ { ++ 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); ++ } ++ } ++ } ++ } ++ ++ return buf; ++ ++fail2: ++ free(sb); ++fail1: ++ mmal_buffer_header_release(buf); ++ return NULL; ++} ++ ++void hw_mmal_vzc_pool_flush(vzc_pool_ctl_t * const pc) ++{ ++ pool_recycle_list(pc, &pc->ents_prev); ++ pool_recycle_list(pc, &pc->ents_cur); ++} ++ ++static void hw_mmal_vzc_pool_delete(vzc_pool_ctl_t * const pc) ++{ ++ ++// 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__); ++} ++ ++void hw_mmal_vzc_pool_release(vzc_pool_ctl_t * const pc) ++{ ++ int n; ++ ++ if (pc == NULL) ++ return; ++ ++ n = atomic_fetch_sub(&pc->ref_count, 1) - 1; ++ ++ if (n != 0) ++ return; ++ ++ hw_mmal_vzc_pool_delete(pc); ++} ++ ++void hw_mmal_vzc_pool_ref(vzc_pool_ctl_t * const pc) ++{ ++ atomic_fetch_add(&pc->ref_count, 1); ++} ++ ++static MMAL_BOOL_T vcz_pool_release_cb(MMAL_POOL_T * buf_pool, MMAL_BUFFER_HEADER_T *buf, void *userdata) ++{ ++ vzc_pool_ctl_t * const pc = userdata; ++ vzc_subbuf_ent_t * const sb = buf->user_data; ++ ++ 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); ++ } ++ ++// printf(">>> %s\n", __func__); ++ ++ return MMAL_TRUE; ++} ++ ++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) ++ { ++ free(pc); ++ return NULL; ++ } ++ ++ pc->max_n = 8; ++ vlc_mutex_init(&pc->lock); // Must init before potential destruction ++ ++ if ((pc->buf_pool = mmal_pool_create(64, 0)) == NULL) ++ { ++ hw_mmal_vzc_pool_delete(pc); ++ return NULL; ++ } ++ ++ atomic_store(&pc->ref_count, 1); ++ ++ mmal_pool_callback_set(pc->buf_pool, vcz_pool_release_cb, pc); ++ ++ return pc; ++} ++ ++//---------------------------------------------------------------------------- ++ ++ ++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) + { +- 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; ++ ++ 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; + +- 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; ++ ++ 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; ++ } + return VLC_SUCCESS; + } ++ ++int cma_buf_pic_attach(cma_buf_t * const cb, picture_t * const pic) ++{ ++ 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; ++} ++ ++cma_buf_t * cma_buf_pic_get(picture_t * const pic) ++{ ++ 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; ++} ++ ++ ++//---------------------------------------------------------------------------- ++ ++/* Returns the type of the Pi being used ++*/ ++bool rpi_is_model_pi4(void) { ++ return bcm_host_is_model_pi4(); ++} ++ ++// 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) ++{ ++ return last_vcsm_type; ++} ++ ++vcsm_init_type_t cma_vcsm_init(void) ++{ ++ 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()) ++ try_type = VCSM_INIT_CMA; ++ else ++ try_type = VCSM_INIT_LEGACY; ++ } ++ ++ 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; ++ } ++ ++ // Just in case this affects vcsm init do after that ++ if (rv != VCSM_INIT_NONE) ++ bcm_host_init(); ++ ++ last_vcsm_type = rv; ++ return rv; ++} ++ ++void cma_vcsm_exit(const vcsm_init_type_t init_mode) ++{ ++ if (init_mode != VCSM_INIT_NONE) ++ { ++ vcsm_exit(); ++ bcm_host_deinit(); // Does nothing but add in case it ever does ++ } ++} ++ ++const char * cma_vcsm_init_str(const vcsm_init_type_t init_mode) ++{ ++ switch (init_mode) ++ { ++ 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,298 @@ + #ifndef VLC_MMAL_MMAL_PICTURE_H_ + #define VLC_MMAL_MMAL_PICTURE_H_ + ++#include ++ + #include + #include + ++#include "mmal_cma.h" ++ + /* 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 ++ ++typedef struct mmal_port_pool_ref_s ++{ ++ atomic_uint refs; ++ MMAL_POOL_T * pool; ++ MMAL_PORT_T * port; ++} hw_mmal_port_pool_ref_t; ++ ++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 ++ ++ cma_buf_t * cb; ++ ++ unsigned int buf_count; ++ MMAL_BUFFER_HEADER_T * bufs[CTX_BUFS_MAX]; ++ ++} pic_ctx_mmal_t; ++ ++const char * str_fourcc(char * const buf, const unsigned int fcc); ++ ++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); ++ ++// 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); ++ ++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); ++ ++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 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; ++ ++ if (ctx->buf_count >= CTX_BUFS_MAX) { ++ mmal_buffer_header_release(sub); ++ return; ++ } ++ ++ ctx->bufs[ctx->buf_count++] = sub; ++} ++ ++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; ++ ++ return n + 1 > ctx->buf_count ? NULL : ctx->bufs[n + 1]; ++} ++ ++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; ++} ++ ++static inline bool hw_mmal_pic_is_mmal(const picture_t * const pic) ++{ ++ return hw_mmal_chroma_is_mmal(pic->format.i_chroma); ++} ++ ++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); ++ ++int hw_mmal_get_gpu_mem(void); ++ ++ ++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); ++} ++ ++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); ++} ++ ++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; ++ ++ rep_buf->pts = seq; ++ ++ if ((err = mmal_port_send_buffer(port, rep_buf)) != MMAL_SUCCESS) ++ { ++ mmal_buffer_header_release(rep_buf); ++ return err; ++ } ++ ++ return MMAL_SUCCESS; ++} ++ ++ ++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; ++} ++ ++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 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); ++// 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); ++ ++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; ++} ++ ++ ++int rpi_get_model_type(void); ++bool rpi_is_model_pi4(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); ++ + +- 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 ++ ++#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; ++ ++ 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); ++ ++ 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,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; ++ } ++} ++ ++// 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); +@@ -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) ++{ ++ 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, ++ (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) +@@ -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); + + 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 + } + } + +-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; +- +- while(dmx_region) { +- dmx_region_next = dmx_region->next; +- dmx_region_delete(dmx_region, update); +- dmx_region = dmx_region_next; +- } +- +- vc_dispmanx_update_submit_sync(update); +- sys->dmx_region = NULL; +- +- show_background(vd, false); +- +- vc_dispmanx_display_close(sys->dmx_handle); +- sys->dmx_handle = DISPMANX_NO_HANDLE; +-} +- +-static struct dmx_region_t *dmx_region_new(vout_display_t *vd, +- DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region) +-{ +- vout_display_sys_t *sys = vd->sys; +- video_format_t *fmt = ®ion->fmt; +- struct dmx_region_t *dmx_region = malloc(sizeof(struct dmx_region_t)); +- uint32_t image_handle; +- +- dmx_region->pos_x = region->i_x; +- dmx_region->pos_y = region->i_y; +- +- vc_dispmanx_rect_set(&dmx_region->bmp_rect, 0, 0, fmt->i_visible_width, +- fmt->i_visible_height); +- vc_dispmanx_rect_set(&dmx_region->src_rect, 0, 0, fmt->i_visible_width << 16, +- fmt->i_visible_height << 16); +- vc_dispmanx_rect_set(&dmx_region->dst_rect, region->i_x, region->i_y, +- fmt->i_visible_width, fmt->i_visible_height); +- +- dmx_region->resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32, +- dmx_region->bmp_rect.width | (region->p_picture->p[0].i_pitch << 16), +- dmx_region->bmp_rect.height | (dmx_region->bmp_rect.height << 16), +- &image_handle); +- vc_dispmanx_resource_write_data(dmx_region->resource, VC_IMAGE_RGBA32, +- region->p_picture->p[0].i_pitch, +- region->p_picture->p[0].p_pixels, &dmx_region->bmp_rect); +- +- dmx_region->alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_MIX; +- dmx_region->alpha.opacity = region->i_alpha; +- dmx_region->alpha.mask = DISPMANX_NO_HANDLE; +- dmx_region->element = vc_dispmanx_element_add(update, sys->dmx_handle, +- sys->layer + 1, &dmx_region->dst_rect, dmx_region->resource, +- &dmx_region->src_rect, DISPMANX_PROTECTION_NONE, +- &dmx_region->alpha, NULL, VC_IMAGE_ROT0); +- +- dmx_region->next = NULL; +- dmx_region->picture = region->p_picture; +- +- return dmx_region; +-} +- +-static void dmx_region_update(struct dmx_region_t *dmx_region, +- DISPMANX_UPDATE_HANDLE_T update, picture_t *picture) +-{ +- vc_dispmanx_resource_write_data(dmx_region->resource, VC_IMAGE_RGBA32, +- picture->p[0].i_pitch, picture->p[0].p_pixels, &dmx_region->bmp_rect); +- vc_dispmanx_element_change_source(update, dmx_region->element, dmx_region->resource); +- dmx_region->picture = picture; +-} +- +-static void dmx_region_delete(struct dmx_region_t *dmx_region, +- DISPMANX_UPDATE_HANDLE_T update) +-{ +- vc_dispmanx_element_remove(update, dmx_region->element); +- vc_dispmanx_resource_delete(dmx_region->resource); +- free(dmx_region); +-} +- + static void maintain_phase_sync(vout_display_t *vd) + { + MMAL_PARAMETER_VIDEO_RENDER_STATS_T render_stats = { + .hdr = { MMAL_PARAMETER_VIDEO_RENDER_STATS, sizeof(render_stats) }, + }; +- int32_t frame_duration = 1000000 / ++ int32_t frame_duration = CLOCK_FREQ / + ((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 + } + } + +-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 ++ ++ 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,584 @@ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "mmal_picture.h" ++ ++#define TRACE_ALL 0 ++ ++typedef struct display_desc_s ++{ ++ vout_display_t * vout; ++ unsigned int max_pels; ++} display_desc_t; ++ ++typedef struct mmal_x11_sys_s ++{ ++ 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 ++ ++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 ++ ++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) ++{ ++ 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; ++} ++ ++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 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); ++ ++ 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_Err(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; ++} ++ ++ ++/* 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; ++} ++ ++/* 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); ++} ++ ++/* 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 ++ 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 ++ ++ 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 bool want_mmal_vout(vout_display_t * const vd, const mmal_x11_sys_t * const sys) ++{ ++ return sys->mmal_desc.vout != NULL && ++ (sys->x_desc.vout == NULL || var_InheritBool(vd, "fullscreen")); ++} ++ ++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; ++} ++ ++/* 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[%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); ++ ++ 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; ++ ++ 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")); ++ ++ // 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) { ++ if (sys->use_mmal) { ++ vout_display_Control(x_desc, VOUT_DISPLAY_CHANGE_MMAL_HIDE); ++ } ++ 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; ++ } ++ ++ ++ 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)); ++ ++ 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->mmal_desc.max_pels = MAX_MMAL_PELS; ++ ++ 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"); ++ ++ 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; ++ } ++ ++ 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 ++ ++ 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; ++ } ++ ++ 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/opengl/egl.c ++++ b/modules/video_output/opengl/egl.c +@@ -43,6 +43,8 @@ + # include "../android/utils.h" + #endif + ++#define REQUIRE_DMA_BUF_IMPORT 1 ++ + typedef struct vlc_gl_sys_t + { + EGLDisplay display; +@@ -355,6 +357,14 @@ static int Open (vlc_object_t *obj, cons + goto error; + } + ++#if REQUIRE_DMA_BUF_IMPORT ++ if (!CheckToken(ext, "EGL_EXT_image_dma_buf_import")) ++ { ++ msg_Dbg(obj, "No dma_buf_import - fall back to X"); ++ goto error; ++ } ++#endif ++ + const EGLint conf_attr[] = { + EGL_RED_SIZE, 5, + EGL_GREEN_SIZE, 5, +--- a/src/input/decoder.c ++++ b/src/input/decoder.c +@@ -1995,6 +1995,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 ); ++ vlc_mutex_unlock( &p_owner->lock ); + + /* 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 + * worker threads (if any) and the decoder thread to terminate. */ + if( p_owner->p_vout != NULL ) + vout_Cancel( p_owner->p_vout, true ); +- vlc_mutex_unlock( &p_owner->lock ); + + vlc_join( p_owner->thread, NULL ); + +--- a/src/misc/fourcc.c ++++ b/src/misc/fourcc.c +@@ -755,8 +755,13 @@ static const struct + { { VLC_CODEC_VDPAU_VIDEO_420, VLC_CODEC_VDPAU_VIDEO_422, + VLC_CODEC_VDPAU_VIDEO_444, VLC_CODEC_VDPAU_OUTPUT }, + FAKE_FMT() }, +- { { VLC_CODEC_ANDROID_OPAQUE, VLC_CODEC_MMAL_OPAQUE, +- VLC_CODEC_D3D9_OPAQUE, VLC_CODEC_D3D11_OPAQUE }, ++ { { VLC_CODEC_ANDROID_OPAQUE }, FAKE_FMT() }, ++ { { VLC_CODEC_MMAL_OPAQUE, VLC_CODEC_MMAL_ZC_SAND30 }, ++ FAKE_FMT() }, ++ { { VLC_CODEC_MMAL_ZC_I420, VLC_CODEC_MMAL_ZC_SAND8, ++ VLC_CODEC_MMAL_ZC_SAND10, VLC_CODEC_MMAL_ZC_RGB32 }, ++ FAKE_FMT() }, ++ { { VLC_CODEC_D3D9_OPAQUE, VLC_CODEC_D3D11_OPAQUE }, + FAKE_FMT() }, + { { VLC_CODEC_D3D11_OPAQUE_10B, VLC_CODEC_D3D9_OPAQUE_10B }, + FAKE_FMT() }, +--- a/src/misc/picture.c ++++ b/src/misc/picture.c +@@ -365,10 +365,30 @@ void picture_CopyProperties( picture_t * + p_dst->b_top_field_first = p_src->b_top_field_first; + } + ++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; ++} ++ + void picture_CopyPixels( picture_t *p_dst, const picture_t *p_src ) + { +- for( int i = 0; i < p_src->i_planes ; i++ ) +- plane_CopyPixels( p_dst->p+i, p_src->p+i ); ++ if( is_zc_chroma(p_src->format.i_chroma) ) ++ { ++ assert(p_dst->i_planes == 0); ++ p_dst->i_planes = p_src->i_planes; ++ for( int i = 0; i < p_src->i_planes; i++ ) ++ p_dst->p[i] = p_src->p[i]; ++ } ++ else ++ { ++ for( int i = 0; i < p_src->i_planes; i++ ) ++ plane_CopyPixels( p_dst->p+i, p_src->p+i ); ++ } + + 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/SOURCES/mmal_chain.patch b/SOURCES/mmal_chain.patch new file mode 100644 index 0000000..03e4acb --- /dev/null +++ b/SOURCES/mmal_chain.patch @@ -0,0 +1,14 @@ +--- a/modules/video_chroma/chain.c ++++ b/modules/video_chroma/chain.c +@@ -280,8 +280,9 @@ static int BuildTransformChain( filter_t + return VLC_SUCCESS; + + /* Lets try resize+chroma first, then transform */ +- msg_Dbg( p_filter, "Trying to build chroma+resize" ); +- EsFormatMergeSize( &fmt_mid, &p_filter->fmt_out, &p_filter->fmt_in ); ++ msg_Dbg( p_filter, "Trying to build chroma+resize, then transform" ); ++ es_format_Copy( &fmt_mid, &p_filter->fmt_out ); ++ video_format_TransformTo(&fmt_mid.video, p_filter->fmt_in.video.orientation); + i_ret = CreateChain( p_filter, &fmt_mid ); + es_format_Clean( &fmt_mid ); + if( i_ret == VLC_SUCCESS ) diff --git a/SOURCES/vlc-libplacebo-5.patch b/SOURCES/vlc-libplacebo-5.patch new file mode 100644 index 0000000..8da84e5 --- /dev/null +++ b/SOURCES/vlc-libplacebo-5.patch @@ -0,0 +1,131 @@ +From efbb1fdbc4420365b3ffd22e55dd27ad520037c7 Mon Sep 17 00:00:00 2001 +From: Niklas Haas +Date: Sat, 16 Jul 2022 14:41:13 +0200 +Subject: [PATCH] opengl: port to libplacebo v4 API + +These v3.x APIs will be removed in v5.x. Fortunately, the new APIs are a +near drop-in replacement, so the change was minimal. Only the error +handling was cleaned up slightly. +--- + modules/video_output/opengl/converter.h | 18 ++++++++++-------- + modules/video_output/opengl/fragment_shaders.c | 4 ++-- + modules/video_output/opengl/vout_helper.c | 14 +++++++------- + 3 files changed, 19 insertions(+), 17 deletions(-) + +diff --git a/modules/video_output/opengl/converter.h b/modules/video_output/opengl/converter.h +index 7000e1f38e..cb8e593a9a 100644 +--- a/modules/video_output/opengl/converter.h ++++ b/modules/video_output/opengl/converter.h +@@ -52,6 +52,11 @@ + # endif + #endif + ++#ifdef HAVE_LIBPLACEBO ++# include ++# include ++#endif ++ + #define VLCGL_PICTURE_MAX 128 + + #ifndef GL_TEXTURE_RECTANGLE +@@ -253,10 +258,6 @@ static inline bool HasExtension(const char *apis, const char *api) + return false; + } + +-struct pl_context; +-struct pl_shader; +-struct pl_shader_res; +- + /* + * Structure that is filled by "glhw converter" module probe function + * The implementation should initialize every members of the struct that are +@@ -272,8 +273,12 @@ struct opengl_tex_converter_t + /* Pointer to object gl, set by the caller */ + vlc_gl_t *gl; + ++#ifdef HAVE_LIBPLACEBO + /* libplacebo context, created by the caller (optional) */ +- struct pl_context *pl_ctx; ++ pl_log pl_log; ++ pl_shader pl_sh; ++ const struct pl_shader_res *pl_sh_res; ++#endif + + /* Function pointers to OpenGL functions, set by the caller */ + const opengl_vtable_t *vt; +@@ -337,9 +342,6 @@ struct opengl_tex_converter_t + bool yuv_color; + GLfloat yuv_coefficients[16]; + +- struct pl_shader *pl_sh; +- const struct pl_shader_res *pl_sh_res; +- + /* Private context */ + void *priv; + +diff --git a/modules/video_output/opengl/fragment_shaders.c b/modules/video_output/opengl/fragment_shaders.c +index 2246e33afd..16380335cc 100644 +--- a/modules/video_output/opengl/fragment_shaders.c ++++ b/modules/video_output/opengl/fragment_shaders.c +@@ -611,7 +611,7 @@ opengl_fragment_shader_init_impl(opengl_tex_converter_t *tc, GLenum tex_target, + + #ifdef HAVE_LIBPLACEBO + if (tc->pl_sh) { +- struct pl_shader *sh = tc->pl_sh; ++ pl_shader sh = tc->pl_sh; + struct pl_color_map_params color_params = pl_color_map_default_params; + color_params.intent = var_InheritInteger(tc->gl, "rendering-intent"); + color_params.tone_mapping_algo = var_InheritInteger(tc->gl, "tone-mapping"); +@@ -634,7 +634,7 @@ opengl_fragment_shader_init_impl(opengl_tex_converter_t *tc, GLenum tex_target, + pl_color_space_from_video_format(&tc->fmt), + dst_space, NULL, false); + +- struct pl_shader_obj *dither_state = NULL; ++ pl_shader_obj dither_state = NULL; + int method = var_InheritInteger(tc->gl, "dither-algo"); + if (method >= 0) { + +diff --git a/modules/video_output/opengl/vout_helper.c b/modules/video_output/opengl/vout_helper.c +index 13d65e04c8..e971f5170b 100644 +--- a/modules/video_output/opengl/vout_helper.c ++++ b/modules/video_output/opengl/vout_helper.c +@@ -570,8 +570,8 @@ opengl_deinit_program(vout_display_opengl_t *vgl, struct prgm *prgm) + + #ifdef HAVE_LIBPLACEBO + FREENULL(tc->uloc.pl_vars); +- if (tc->pl_ctx) +- pl_context_destroy(&tc->pl_ctx); ++ pl_shader_free(&tc->pl_sh); ++ pl_log_destroy(&tc->pl_log); + #endif + + vlc_object_release(tc); +@@ -622,21 +622,21 @@ opengl_init_program(vout_display_opengl_t *vgl, struct prgm *prgm, + // create the main libplacebo context + if (!subpics) + { +- tc->pl_ctx = pl_context_create(PL_API_VER, &(struct pl_context_params) { ++ tc->pl_log = pl_log_create(PL_API_VER, &(struct pl_log_params) { + .log_cb = log_cb, + .log_priv = tc, + .log_level = PL_LOG_INFO, + }); +- if (tc->pl_ctx) { ++ if (tc->pl_log) { + # if PL_API_VER >= 20 +- tc->pl_sh = pl_shader_alloc(tc->pl_ctx, &(struct pl_shader_params) { ++ tc->pl_sh = pl_shader_alloc(tc->pl_log, &(struct pl_shader_params) { + .glsl.version = tc->glsl_version, + .glsl.gles = tc->is_gles, + }); + # elif PL_API_VER >= 6 +- tc->pl_sh = pl_shader_alloc(tc->pl_ctx, NULL, 0); ++ tc->pl_sh = pl_shader_alloc(tc->pl_log, NULL, 0); + # else +- tc->pl_sh = pl_shader_alloc(tc->pl_ctx, NULL, 0, 0); ++ tc->pl_sh = pl_shader_alloc(tc->pl_log, NULL, 0, 0); + # endif + } + } +-- +2.38.1 diff --git a/SOURCES/vlc-onevpl.patch b/SOURCES/vlc-onevpl.patch new file mode 100644 index 0000000..3475fef --- /dev/null +++ b/SOURCES/vlc-onevpl.patch @@ -0,0 +1,24 @@ +diff -up vlc-26fb05e95724bd19f9a144c51bc11f79c609f3bc/configure.ac.vpl vlc-26fb05e95724bd19f9a144c51bc11f79c609f3bc/configure.ac +--- vlc-26fb05e95724bd19f9a144c51bc11f79c609f3bc/configure.ac.vpl 2023-06-17 17:19:28.954540887 +0200 ++++ vlc-26fb05e95724bd19f9a144c51bc11f79c609f3bc/configure.ac 2023-06-17 17:19:28.956540899 +0200 +@@ -2925,7 +2925,7 @@ fi + dnl + dnl Intel QuickSync (aka MediaSDK) H264/H262 encoder + dnl +-PKG_ENABLE_MODULES_VLC([MFX], [qsv], [libmfx], [Intel QuickSync MPEG4-Part10/MPEG2 (aka H.264/H.262) encoder], [auto]) ++PKG_ENABLE_MODULES_VLC([MFX], [qsv], [vpl], [Intel QuickSync MPEG4-Part10/MPEG2 (aka H.264/H.262) encoder], [auto]) + + dnl + dnl libfluidsynth (MIDI synthetizer) plugin +diff -up vlc-26fb05e95724bd19f9a144c51bc11f79c609f3bc/modules/codec/qsv.c.vpl vlc-26fb05e95724bd19f9a144c51bc11f79c609f3bc/modules/codec/qsv.c +--- vlc-26fb05e95724bd19f9a144c51bc11f79c609f3bc/modules/codec/qsv.c.vpl 2023-06-09 17:29:29.000000000 +0200 ++++ vlc-26fb05e95724bd19f9a144c51bc11f79c609f3bc/modules/codec/qsv.c 2023-06-17 17:22:05.468382540 +0200 +@@ -33,7 +33,7 @@ + #include + #include + +-#include ++#include + + #define SOUT_CFG_PREFIX "sout-qsv-" +