diff --git a/bin/vlc.c b/bin/vlc.c index 72e0eee428..6d92b95990 100644 --- a/bin/vlc.c +++ b/bin/vlc.c @@ -106,7 +106,10 @@ static void vlc_kill (void *data) static void exit_timeout (int signum) { (void) signum; - signal (SIGINT, SIG_DFL); +// This doesn't seem to be strong enough to reliably kill us if we fail to exit +// in a timely fashion - so upgrade to _exit(). +// signal (SIGINT, SIG_DFL); + _exit(0); } /***************************************************************************** diff --git a/configure.ac b/configure.ac index 8fa2a87595..1f41d1ebcf 100644 --- a/configure.ac +++ b/configure.ac @@ -3108,6 +3108,21 @@ AS_IF([test "${have_gl}" = "yes"], [ dnl OpenGL ES 2: depends on EGL 1.1 PKG_ENABLE_MODULES_VLC([GLES2], [], [glesv2], [OpenGL ES v2 support], [disabled]) +dnl +dnl DRM +dnl +AC_ARG_ENABLE(drm, + [ --enable-drm DRM output support (default enabled)],, [ + enable_drm="yes" +]) + +have_drm="no" +AS_IF([test "${enable_drm}" != "no"], [ + PKG_CHECK_MODULES(DRM, [libdrm]) + have_drm="yes" +]) +AM_CONDITIONAL([HAVE_DRM], [test "${have_drm}" = "yes"]) + dnl dnl Xlib dnl @@ -3207,6 +3222,7 @@ AC_ARG_ENABLE(wayland, [AS_HELP_STRING([--enable-wayland], [Incomplete Wayland support (default disabled)])]) have_wayland="no" have_wayland_egl="no" +have_wayland_single_pixel_buffer="no" AS_IF([test "${enable_wayland}" = "yes"], [ PKG_CHECK_MODULES([WAYLAND_CLIENT], [wayland-client >= 1.5.91], [ @@ -3214,6 +3230,13 @@ AS_IF([test "${enable_wayland}" = "yes"], [ PKG_CHECK_EXISTS([wayland-protocols >= 1.4], [ WAYLAND_PROTOCOLS="$(${PKG_CONFIG} wayland-protocols --variable pkgdatadir)" AC_MSG_RESULT([${WAYLAND_PROTOCOLS}]) + AC_MSG_CHECKING([for Wayland protocols single pixel buffer]) + PKG_CHECK_EXISTS([wayland-protocols >= 1.26], [ + AC_MSG_RESULT([yes]) + have_wayland_single_pixel_buffer="yes" + ], [ + AC_MSG_RESULT([no]) + ]) ], [ AC_MSG_RESULT([not found]) AC_MSG_ERROR([$(${PKG_CONFIG} --print-errors 'wayland-protocols >= 1.4')]) @@ -3247,6 +3270,7 @@ AC_SUBST([WAYLAND_PROTOCOLS]) AC_SUBST([WAYLAND_SCANNER]) AM_CONDITIONAL([HAVE_WAYLAND], [test "${have_wayland}" = "yes"]) AM_CONDITIONAL([HAVE_WAYLAND_EGL], [test "${have_wayland_egl}" = "yes"]) +AM_CONDITIONAL([HAVE_WAYLAND_SINGLE_PIXEL_BUFFER], [test "${have_wayland_single_pixel_buffer}" = "yes"]) dnl @@ -3473,20 +3497,24 @@ AM_CONDITIONAL([HAVE_KVA], [test "${have_kva}" = "yes"]) dnl dnl MMAL plugin dnl +have_mmal="no" AC_ARG_ENABLE(mmal, AS_HELP_STRING([--enable-mmal], [Multi-Media Abstraction Layer (MMAL) hardware plugin (default enable)])) +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" - CPPFLAGS="${CPPFLAGS} -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux" + CPPFLAGS="${CPPFLAGS} -idirafter /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux" AC_CHECK_HEADERS(interface/mmal/mmal.h, [ AC_CHECK_LIB(bcm_host, vc_tv_unregister_callback_full, [ have_mmal="yes" VLC_ADD_PLUGIN([mmal]) VLC_ADD_LDFLAGS([mmal],[ -L/opt/vc/lib ]) - VLC_ADD_CFLAGS([mmal],[ -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ]) - VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif ]) ], [ + VLC_ADD_CFLAGS([mmal],[ -idirafter /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ]) + VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif -lvchiq_arm -lvcsm ]) ], [ AS_IF([test "${enable_mmal}" = "yes"], [ AC_MSG_ERROR([Cannot find bcm library...]) ], [ AC_MSG_WARN([Cannot find bcm library...]) ]) @@ -3498,6 +3526,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 diff --git a/include/vlc_fourcc.h b/include/vlc_fourcc.h index 97827bd4c1..6d4f62ab93 100644 --- a/include/vlc_fourcc.h +++ b/include/vlc_fourcc.h @@ -374,6 +374,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 */ @@ -383,6 +388,13 @@ #define VLC_CODEC_D3D11_OPAQUE VLC_FOURCC('D','X','1','1') /* 4:2:0 8 bpc */ #define VLC_CODEC_D3D11_OPAQUE_10B VLC_FOURCC('D','X','1','0') /* 4:2:0 10 bpc */ +/* DRM Prime */ +#define VLC_CODEC_DRM_PRIME_I420 VLC_FOURCC('D','P','V','0') +#define VLC_CODEC_DRM_PRIME_NV12 VLC_FOURCC('D','P','N','1') +#define VLC_CODEC_DRM_PRIME_SAND8 VLC_FOURCC('D','P','S','8') +#define VLC_CODEC_DRM_PRIME_SAND30 VLC_FOURCC('D','P','S','3') +#define VLC_CODEC_DRM_PRIME_RGB32 VLC_FOURCC('D','P','3','2') + /* CVPixelBuffer opaque buffer type */ #define VLC_CODEC_CVPX_NV12 VLC_FOURCC('C','V','P','N') #define VLC_CODEC_CVPX_UYVY VLC_FOURCC('C','V','P','Y') diff --git a/include/vlc_opengl.h b/include/vlc_opengl.h index fdebfeb2e1..42671f85db 100644 --- a/include/vlc_opengl.h +++ b/include/vlc_opengl.h @@ -68,6 +68,9 @@ struct vlc_gl_t const int32_t *attrib_list); /* call eglDestroyImageKHR() with current display, can be NULL */ bool (*destroyImageKHR)(vlc_gl_t *, void *image); + bool (*queryDmaBufModifiersEXT)(vlc_gl_t *gl, uint32_t format, + unsigned int max_modifiers, uint64_t *modifiers, + unsigned int *external_only, int32_t *num_modifiers); } egl; /* if ext == VLC_GL_EXT_WGL */ struct diff --git a/include/vlc_vout_window.h b/include/vlc_vout_window.h index 3a613d4e47..90f70abb01 100644 --- a/include/vlc_vout_window.h +++ b/include/vlc_vout_window.h @@ -197,6 +197,14 @@ struct vout_window_t { vout_window_sys_t *sys; vout_window_owner_t owner; + + /* Wayland handle sync objects - potentially other windowing systems + * too if the handle might change + * handle seq incremented if there is a possibility that the handle has + * changed - 0 => handle is fixed + */ + vlc_mutex_t handle_lock; + unsigned int handle_seq; }; /** diff --git a/modules/Makefile.am b/modules/Makefile.am index 53b90a2713..6a29b126e9 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -10,9 +10,7 @@ EXTRA_SUBDIRS = \ SUBDIRS = . DIST_SUBDIRS = . $(EXTRA_SUBDIRS) -if HAVE_MMAL SUBDIRS += hw/mmal -endif TESTS = @@ -34,6 +32,7 @@ include demux/Makefile.am include gui/Makefile.am include hw/d3d9/Makefile.am include hw/d3d11/Makefile.am +include hw/drm/Makefile.am include hw/vaapi/Makefile.am include hw/vdpau/Makefile.am include keystore/Makefile.am diff --git a/modules/audio_filter/converter/tospdif.c b/modules/audio_filter/converter/tospdif.c index 0c725d99e7..8d962d6e81 100644 --- a/modules/audio_filter/converter/tospdif.c +++ b/modules/audio_filter/converter/tospdif.c @@ -484,6 +484,7 @@ static int write_buffer_dtshd( filter_t *p_filter, block_t *p_in_buf ) p_in_buf->i_buffer ) != VLC_SUCCESS ) return SPDIF_ERROR; unsigned i_period = p_filter->fmt_out.audio.i_rate + * (p_filter->fmt_out.audio.i_bytes_per_frame / p_filter->fmt_out.audio.i_frame_length) / 4 * core.i_frame_length / core.i_rate; int i_subtype = dtshd_get_subtype( i_period ); diff --git a/modules/audio_output/alsa.c b/modules/audio_output/alsa.c index e10e1eb8c3..b8f190d782 100644 --- a/modules/audio_output/alsa.c +++ b/modules/audio_output/alsa.c @@ -39,6 +39,8 @@ #include #include +#define TRACE_ALL 0 + /** Private data for an ALSA PCM playback stream */ struct aout_sys_t { @@ -51,11 +53,19 @@ struct aout_sys_t bool soft_mute; float soft_gain; char *device; + unsigned int pause_bytes; + + vlc_fourcc_t * passthrough_types; }; #include "audio_output/volume.h" -#define A52_FRAME_NB 1536 +enum { + PASSTHROUGH_UNSET = -1, + PASSTHROUGH_NONE = 0, + PASSTHROUGH_SPDIF, + PASSTHROUGH_HDMI, +}; static int Open (vlc_object_t *); static void Close (vlc_object_t *); @@ -77,6 +87,22 @@ static const char *const channels_text[] = { N_("Surround 5.0"), N_("Surround 5.1"), N_("Surround 7.1"), }; +#define PASSTHROUGH_NAME "alsa-passthrough" +#define PASSTHROUGH_TEXT N_("Audio passthrough mode") +#define PASSTHROUGH_LONGTEXT N_("Audio passthrough mode. Defaults to 0 (none)") +static const int passthrough_modes[] = { + PASSTHROUGH_UNSET, PASSTHROUGH_NONE, PASSTHROUGH_SPDIF, PASSTHROUGH_HDMI, +}; +static const char *const passthrough_modes_text[] = { + N_("unset"), N_("none"), N_("S/PDIF"), N_("HDMI"), +}; + +#define PASSTHROUGH_TYPES_NAME "alsa-passthrough-types" +#define PASSTHROUGH_TYPES_TEXT "List of codecs to accept for passthrough" +#define PASSTHROUGH_TYPES_LONGTEXT "List of codecs to accept for passthrough, comma separated. Default is to try everything."\ + "If this option is given then " PASSTHROUGH_NAME " defaults to HDMI" + + vlc_module_begin () set_shortname( "ALSA" ) set_description( N_("ALSA audio output") ) @@ -88,12 +114,85 @@ vlc_module_begin () add_integer ("alsa-audio-channels", AOUT_CHANS_FRONT, AUDIO_CHAN_TEXT, AUDIO_CHAN_LONGTEXT, false) change_integer_list (channels, channels_text) + add_integer (PASSTHROUGH_NAME, PASSTHROUGH_UNSET, PASSTHROUGH_TEXT, + PASSTHROUGH_LONGTEXT, false) + change_integer_list (passthrough_modes, passthrough_modes_text) + add_string(PASSTHROUGH_TYPES_NAME, NULL, PASSTHROUGH_TYPES_TEXT, + PASSTHROUGH_TYPES_LONGTEXT, false) add_sw_gain () set_capability( "audio output", 150 ) set_callbacks( Open, Close ) vlc_module_end () +static vlc_fourcc_t * parse_passthrough(audio_output_t * const aout, const char * const str) +{ + const char * p = str; + size_t n = 2; + vlc_fourcc_t * rv = NULL; + vlc_fourcc_t * f; + + if (str == NULL) + return NULL; + + while (*p != '\0') + if (*p++ == ',') + ++n; + + rv = malloc(sizeof(vlc_fourcc_t) * n); + if (rv == NULL) + return NULL; + f = rv; + + if (strcasecmp(str, "none") == 0) + goto done; + + for (p = str; *p != 0;) + { + unsigned int i; + const char *c = strchrnul(p, ','); + vlc_fourcc_t fcc = 0; + + static const struct { + const char * str; + vlc_fourcc_t val; + } codecs[] = { + {.str = "truehd", .val = VLC_CODEC_TRUEHD }, + {.str = "mlp", .val = VLC_CODEC_MLP }, + {.str = "dts", .val = VLC_CODEC_DTS }, + {.str = "dtshd", .val = VLC_CODEC_DTS }, + {.str = "ac3", .val = VLC_CODEC_A52 }, + {.str = "ac-3", .val = VLC_CODEC_A52 }, + {.str = "eac3", .val = VLC_CODEC_EAC3 }, + {.str = "eac-3", .val = VLC_CODEC_EAC3 }, + {.str = "all", .val = VLC_CODEC_UNKNOWN }, + }; + + for (i = 0; i != ARRAY_SIZE(codecs); ++i) + { + if (strncasecmp(p, codecs[i].str, c - p) == 0) + { + fcc = codecs[i].val; + break; + } + } + + if (fcc != 0) + *f++ = fcc; + else + msg_Warn(aout, "Unknown codec type '%.*s'", (int)(c - p), p); + + if (*c == 0) + break; + + p = c + 1; + } + +done: + *f = 0; + return rv; +} + /** Helper for ALSA -> VLC debugging output */ static void Dump (vlc_object_t *obj, const char *msg, int (*cb)(void *, snd_output_t *), void *p) @@ -285,11 +384,20 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt) { aout_sys_t *sys = aout->sys; snd_pcm_format_t pcm_format; /* ALSA sample format */ - bool spdif = false; + unsigned channels; + int passthrough = PASSTHROUGH_NONE; + snd_pcm_uframes_t periodSizeMax; + snd_pcm_uframes_t periodSize; + snd_pcm_uframes_t bufferSize; + unsigned int req_rate = fmt->i_rate; + vlc_fourcc_t req_format = fmt->i_format; - if (aout_FormatNbChannels(fmt) == 0) + msg_Dbg(aout, "Start: Format: %.4s, Chans: %d, Rate:%d", (char*)&fmt->i_format, aout_FormatNbChannels(fmt), fmt->i_rate); + + if (aout_FormatNbChannels(fmt) == 0 && AOUT_FMT_LINEAR(fmt)) return VLC_EGENERIC; + sys->pause_bytes = 0; switch (fmt->i_format) { case VLC_CODEC_FL64: @@ -308,36 +416,88 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt) pcm_format = SND_PCM_FORMAT_U8; break; default: - if (AOUT_FMT_SPDIF(fmt)) - spdif = var_InheritBool (aout, "spdif"); - if (spdif) + if (AOUT_FMT_SPDIF(fmt) || AOUT_FMT_HDMI(fmt)) { - fmt->i_format = VLC_CODEC_SPDIFL; + if (sys->passthrough_types != NULL) + { + // VLC_CODEC_UNKNOWN used as explicit "all" + const vlc_fourcc_t *p; + for (p = sys->passthrough_types; *p != 0 || *p == VLC_CODEC_UNKNOWN; ++p) + if (*p == fmt->i_format) + break; + if (*p == 0) + { + msg_Dbg(aout, "Codec %.4s not in passthrough-types", (const char *)&fmt->i_format); + return VLC_EGENERIC; + } + } + + passthrough = var_InheritInteger(aout, PASSTHROUGH_NAME); + // Explicit passthrough will override spdif + if (passthrough == PASSTHROUGH_UNSET) + passthrough = + var_InheritBool(aout, "spdif") ? PASSTHROUGH_SPDIF : + sys->passthrough_types != NULL ? PASSTHROUGH_HDMI : PASSTHROUGH_NONE; + msg_Dbg(aout, "Passthrough %d for format %4.4s", passthrough, (const char *)&fmt->i_format); + } + + if (passthrough != PASSTHROUGH_NONE) + { + req_format = VLC_CODEC_SPDIFL; pcm_format = SND_PCM_FORMAT_S16; + sys->pause_bytes = 3 * 4; + channels = 2; + + switch (fmt->i_format) { + case VLC_CODEC_MLP: + case VLC_CODEC_TRUEHD: + sys->pause_bytes = 4 * 4; + req_rate = fmt->i_rate * 4; + channels = 8; + break; + + case VLC_CODEC_DTS: + { + if (passthrough == PASSTHROUGH_SPDIF) + break; + req_rate = 192000; + channels = 8; + break; + } + + case VLC_CODEC_EAC3: + sys->pause_bytes = 4 * 4; + req_rate = fmt->i_rate * 4; + break; + + default: + break; + } } else if (HAVE_FPU) { - fmt->i_format = VLC_CODEC_FL32; + req_format = VLC_CODEC_FL32; pcm_format = SND_PCM_FORMAT_FLOAT; } else { - fmt->i_format = VLC_CODEC_S16N; + req_format = VLC_CODEC_S16N; pcm_format = SND_PCM_FORMAT_S16; } + break; } const char *device = sys->device; /* Choose the IEC device for S/PDIF output */ char sep = '\0'; - if (spdif) + if (passthrough != PASSTHROUGH_NONE) { const char *opt = NULL; if (!strcmp (device, "default")) - device = "iec958"; /* TODO: hdmi */ + device = (passthrough == PASSTHROUGH_HDMI) ? "hdmi" : "iec958"; if (!strncmp (device, "iec958", 6)) opt = device + 6; @@ -364,8 +524,12 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt) FS( 44100) /* def. */ FS( 48000) FS( 32000) FS( 22050) FS( 24000) FS( 88200) FS(768000) FS( 96000) - FS(176400) FS(192000) + FS(176400) #undef FS + case 192000: + aes3 = (passthrough == PASSTHROUGH_HDMI && channels == 8) ? + IEC958_AES3_CON_FS_768000 : IEC958_AES3_CON_FS_192000; + break; default: aes3 = IEC958_AES3_CON_FS_NOTID; break; @@ -430,21 +594,27 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt) if (snd_pcm_hw_params_test_format (pcm, hw, pcm_format) == 0) ; else - if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_FLOAT) == 0) + if (passthrough != PASSTHROUGH_NONE) { - fmt->i_format = VLC_CODEC_FL32; + msg_Warn(aout, "Failed to set required passthrough format"); + goto error; + } + else + if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT) == 0) + { + req_format = VLC_CODEC_FL32; pcm_format = SND_PCM_FORMAT_FLOAT; } else if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_S32) == 0) { - fmt->i_format = VLC_CODEC_S32N; + req_format = VLC_CODEC_S32N; pcm_format = SND_PCM_FORMAT_S32; } else if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_S16) == 0) { - fmt->i_format = VLC_CODEC_S16N; + req_format = VLC_CODEC_S16N; pcm_format = SND_PCM_FORMAT_S16; } else @@ -459,10 +629,10 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt) msg_Err (aout, "cannot set sample format: %s", snd_strerror (val)); goto error; } + sys->format = req_format; /* Set channels count */ - unsigned channels; - if (!spdif) + if (passthrough == PASSTHROUGH_NONE) { uint16_t map = var_InheritInteger (aout, "alsa-audio-channels"); @@ -474,7 +644,6 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt) else { sys->chans_to_reorder = 0; - channels = 2; } /* By default, ALSA plug will pad missing channels with zeroes, which is @@ -490,14 +659,26 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt) } /* Set sample rate */ - val = snd_pcm_hw_params_set_rate_near (pcm, hw, &fmt->i_rate, NULL); + sys->rate = req_rate; + val = snd_pcm_hw_params_set_rate_near (pcm, hw, &sys->rate, NULL); if (val) { msg_Err (aout, "cannot set sample rate: %s", snd_strerror (val)); goto error; } - sys->rate = fmt->i_rate; + if (passthrough != PASSTHROUGH_NONE && sys->rate != req_rate) + { + msg_Warn(aout, "Passthrough requires rate %d, got %d", req_rate, sys->rate); + goto error; + } + bufferSize = req_rate / 10; // 100ms - bigger than this & truehd goes unhappy? + periodSize = bufferSize / 4; + periodSizeMax = bufferSize / 3; + snd_pcm_hw_params_set_period_size_max(pcm, hw, &periodSizeMax, NULL); + + snd_pcm_hw_params_set_buffer_size_near(pcm, hw, &bufferSize); + snd_pcm_hw_params_set_period_size_near(pcm, hw, &periodSize, NULL); #if 1 /* work-around for period-long latency outputs (e.g. PulseAudio): */ param = AOUT_MIN_PREPARE_TIME; val = snd_pcm_hw_params_set_period_time_near (pcm, hw, ¶m, NULL); @@ -579,13 +760,12 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt) } /* Setup audio_output_t */ - if (spdif) - { - fmt->i_bytes_per_frame = AOUT_SPDIF_SIZE; - fmt->i_frame_length = A52_FRAME_NB; - } + fmt->i_frame_length = 1; + fmt->i_bytes_per_frame = snd_pcm_frames_to_bytes(pcm, fmt->i_frame_length); + fmt->i_channels = channels; + fmt->i_rate = sys->rate; + fmt->i_format = sys->format; fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP; - sys->format = fmt->i_format; aout->time_get = TimeGet; aout->play = Play; @@ -616,7 +796,7 @@ static int TimeGet (audio_output_t *aout, vlc_tick_t *restrict delay) msg_Err (aout, "cannot estimate delay: %s", snd_strerror (val)); return -1; } - *delay = frames * CLOCK_FREQ / sys->rate; + *delay = (uint_fast64_t)frames * CLOCK_FREQ / sys->rate; return 0; } @@ -627,6 +807,29 @@ static void Play (audio_output_t *aout, block_t *block) { aout_sys_t *sys = aout->sys; +#if TRACE_ALL + static mtime_t last_pts = 0; + msg_Dbg(aout, "<<< %s: PTS: %"PRId64" samples: %u, bytes: %zu, delta: %"PRId64, __func__, + block->i_pts, block->i_nb_samples, block->i_buffer, + block->i_pts - last_pts); + last_pts = block->i_pts; +#endif + + // S/pdif packets always start with sync so if no sync then this must + // be a padding buffer + if (sys->pause_bytes != 0 && block->p_buffer[0] == 0) + { + static const uint8_t pause_le[16] = {0x72, 0xf8, 0x1f, 0x4e, 3, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const uint8_t pause_be[16] = {0xf8, 0x72, 0x4e, 0x1f, 0, 3, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0}; + const uint8_t *const pause = sys->format == VLC_CODEC_SPDIFB ? pause_be : pause_le; + size_t n = block->i_buffer / sys->pause_bytes; + size_t i; + + msg_Dbg(aout, "Silence detected"); + for (i = 0; i != n; ++i) + memcpy(block->p_buffer + i * sys->pause_bytes, pause, sys->pause_bytes); + } + if (sys->chans_to_reorder != 0) aout_ChannelReorder(block->p_buffer, block->i_buffer, sys->chans_to_reorder, sys->chans_table, sys->format); @@ -792,7 +995,7 @@ static int DeviceSelect (audio_output_t *aout, const char *id) static int Open(vlc_object_t *obj) { audio_output_t *aout = (audio_output_t *)obj; - aout_sys_t *sys = malloc (sizeof (*sys)); + aout_sys_t *sys = calloc (1, sizeof (*sys)); if (unlikely(sys == NULL)) return VLC_ENOMEM; @@ -822,6 +1025,12 @@ static int Open(vlc_object_t *obj) free (ids); } + { + const char *types = var_InheritString(aout, PASSTHROUGH_TYPES_NAME); + sys->passthrough_types = parse_passthrough(aout, types); + free((void *)types); + } + return VLC_SUCCESS; error: free (sys); @@ -833,6 +1042,7 @@ static void Close(vlc_object_t *obj) audio_output_t *aout = (audio_output_t *)obj; aout_sys_t *sys = aout->sys; + free (sys->passthrough_types); free (sys->device); free (sys); } diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am index 6d9465fdae..5d46e5e95f 100644 --- a/modules/codec/Makefile.am +++ b/modules/codec/Makefile.am @@ -378,6 +378,7 @@ libavcodec_plugin_la_SOURCES = \ codec/avcodec/subtitle.c \ codec/avcodec/audio.c \ codec/avcodec/va.c codec/avcodec/va.h \ + codec/avcodec/drm_pic.c codec/avcodec/drm_pic.h \ codec/avcodec/avcodec.c codec/avcodec/avcodec.h \ packetizer/av1_obu.c packetizer/av1_obu.h packetizer/av1.h if ENABLE_SOUT diff --git a/modules/codec/avcodec/avcodec.c b/modules/codec/avcodec/avcodec.c index d0780e9a17..d8d3ed3657 100644 --- a/modules/codec/avcodec/avcodec.c +++ b/modules/codec/avcodec/avcodec.c @@ -252,17 +252,41 @@ vlc_module_begin () #endif vlc_module_end () -AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec, - const AVCodec **restrict codecp ) +static const char * +hw_v4l2m2m_dec_str(const unsigned i_codec_id) +{ + switch( i_codec_id ) + { + case AV_CODEC_ID_MPEG2VIDEO: + return "mpeg2_v4l2m2m"; + case AV_CODEC_ID_H264: + return "h264_v4l2m2m"; + default: + break; + } + return NULL; +} + +AVCodecContext *ffmpeg_AllocContextHw( decoder_t *p_dec, + const AVCodec **restrict codecp, const int hw ) { unsigned i_codec_id; const char *psz_namecodec; const AVCodec *p_codec = NULL; + const char * hw_dec_name = NULL; + const char * const psz_decoder = var_InheritString( p_dec, "avcodec-codec" ); + + // If named decoder do not attempt hw override - wait for non-hw pass + if( hw != 0 && psz_decoder != NULL ) + goto fail_free_psz_decoder; /* *** determine codec type *** */ if( !GetFfmpegCodec( p_dec->fmt_in.i_cat, p_dec->fmt_in.i_codec, &i_codec_id, &psz_namecodec ) ) - return NULL; + goto fail_free_psz_decoder; + + if( hw != 0 && (hw_dec_name = hw_v4l2m2m_dec_str(i_codec_id)) == NULL ) + goto fail_free_psz_decoder; msg_Dbg( p_dec, "using %s %s", AVPROVIDER(LIBAVCODEC), LIBAVCODEC_IDENT ); @@ -270,7 +294,6 @@ AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec, 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 ); @@ -282,9 +305,11 @@ AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec, psz_decoder, (char*)&p_dec->fmt_in.i_codec ); p_codec = NULL; } - free( psz_decoder ); + free( (char*)psz_decoder ); } - if( !p_codec ) + if( hw_dec_name != NULL ) + p_codec = avcodec_find_decoder_by_name(hw_dec_name); + else if( !p_codec ) p_codec = avcodec_find_decoder( i_codec_id ); if( !p_codec ) { @@ -302,6 +327,16 @@ AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec, avctx->debug = var_InheritInteger( p_dec, "avcodec-debug" ); avctx->opaque = p_dec; return avctx; + +fail_free_psz_decoder: + free((char*)psz_decoder); + return NULL; +} + +AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec, + const AVCodec **restrict codecp ) +{ + return ffmpeg_AllocContextHw(p_dec, codecp, 0); } /***************************************************************************** diff --git a/modules/codec/avcodec/avcodec.h b/modules/codec/avcodec/avcodec.h index c14170ebb8..7ee1463463 100644 --- a/modules/codec/avcodec/avcodec.h +++ b/modules/codec/avcodec/avcodec.h @@ -48,6 +48,7 @@ int InitSubtitleDec( vlc_object_t * ); void EndSubtitleDec( vlc_object_t * ); /* Initialize decoder */ +AVCodecContext *ffmpeg_AllocContextHw( decoder_t *p_dec, const AVCodec **restrict codecp, const int hw ); AVCodecContext *ffmpeg_AllocContext( decoder_t *, const AVCodec ** ); int ffmpeg_OpenCodec( decoder_t *p_dec, AVCodecContext *, const AVCodec * ); diff --git a/modules/codec/avcodec/avcommon.h b/modules/codec/avcodec/avcommon.h index ff5dba06c9..9b3f045c47 100644 --- a/modules/codec/avcodec/avcommon.h +++ b/modules/codec/avcodec/avcommon.h @@ -84,7 +84,10 @@ static inline void vlc_init_avutil(vlc_object_t *obj) break; case VLC_MSG_DBG+1: level = AV_LOG_DEBUG; + break; default: + if (verbose + VLC_MSG_ERR >= VLC_MSG_DBG+2) + level = AV_LOG_TRACE; break; } } diff --git a/modules/codec/avcodec/drm_pic.c b/modules/codec/avcodec/drm_pic.c new file mode 100644 index 0000000000..8319c1c210 --- /dev/null +++ b/modules/codec/avcodec/drm_pic.c @@ -0,0 +1,62 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include +#include + +#include "drm_pic.h" + +static picture_context_t * +drm_prime_picture_context_new(AVBufferRef * const buf, const void * data, AVBufferRef * const hw_frames_ctx); + +static void +drm_prime_pic_ctx_destroy(struct picture_context_t * ctx) +{ + drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)ctx; + av_buffer_unref(&pctx->buf); + av_buffer_unref(&pctx->hw_frames_ctx); + free(pctx); +} + +static picture_context_t * +drm_prime_pic_ctx_copy(struct picture_context_t * src) +{ + drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)src; + + // We could ref count this structure but easier to just create a new one + // (which will ref the buf) + return drm_prime_picture_context_new(pctx->buf, pctx->desc, pctx->hw_frames_ctx); +} + +static picture_context_t * +drm_prime_picture_context_new(AVBufferRef * const buf, const void * data, AVBufferRef * const hw_frames_ctx) +{ + drm_prime_video_sys_t * const pctx = calloc(1, sizeof(*pctx)); + + if (pctx == NULL) + return NULL; + + pctx->cmn.copy = drm_prime_pic_ctx_copy; + pctx->cmn.destroy = drm_prime_pic_ctx_destroy; + + pctx->buf = av_buffer_ref(buf); + pctx->desc = data; + pctx->hw_frames_ctx = !hw_frames_ctx ? NULL : av_buffer_ref(hw_frames_ctx); + return &pctx->cmn; +} + +int drm_prime_attach_buf_to_pic(picture_t *pic, AVFrame *frame) +{ + if (pic->context) + return VLC_EGENERIC; + + pic->context = drm_prime_picture_context_new(frame->buf[0], frame->data[0], frame->hw_frames_ctx); + return VLC_SUCCESS; +} + + + diff --git a/modules/codec/avcodec/drm_pic.h b/modules/codec/avcodec/drm_pic.h new file mode 100644 index 0000000000..a15f512c23 --- /dev/null +++ b/modules/codec/avcodec/drm_pic.h @@ -0,0 +1,46 @@ +#include +#include + +struct picture_t; +struct AVFrame; +struct AVBufferRef; +struct AVDRMFrameDescriptor; + +typedef struct drm_prime_video_sys_s { + picture_context_t cmn; // PARENT: Common els at start + + struct AVBufferRef * buf; + const struct AVDRMFrameDescriptor * desc; + struct AVBufferRef * hw_frames_ctx; +} drm_prime_video_sys_t; + +static inline const struct AVDRMFrameDescriptor * +drm_prime_get_desc(picture_t *pic) +{ + drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)pic->context; + + return !pctx ? NULL : pctx->desc; +} + +static inline char safechar(unsigned int x) +{ + const unsigned int c = x & 0xff; + return c > ' ' && c < 0x7f ? (char)c : '.'; +} + +static inline const char * +str_fourcc(char buf[5], const uint32_t fcc) +{ + buf[0] = safechar(fcc); + buf[1] = safechar(fcc >> 8); + buf[2] = safechar(fcc >> 16); + buf[3] = safechar(fcc >> 24); + buf[4] = 0; + return buf; +} + +#define fourcc2str(fcc) \ + str_fourcc((char[5]){0}, fcc) + +int drm_prime_attach_buf_to_pic(struct picture_t *pic, struct AVFrame *frame); + diff --git a/modules/codec/avcodec/va.c b/modules/codec/avcodec/va.c index 0feb03b974..70956eb885 100644 --- a/modules/codec/avcodec/va.c +++ b/modules/codec/avcodec/va.c @@ -57,6 +57,24 @@ vlc_fourcc_t vlc_va_GetChroma(enum PixelFormat hwfmt, enum PixelFormat swfmt) return VLC_CODEC_D3D9_OPAQUE; } break; + case AV_PIX_FMT_DRM_PRIME: + switch (swfmt) + { + case AV_PIX_FMT_YUV420P: + return VLC_CODEC_DRM_PRIME_I420; + case AV_PIX_FMT_NV12: + return VLC_CODEC_DRM_PRIME_NV12; + case AV_PIX_FMT_RPI4_8: + return VLC_CODEC_DRM_PRIME_SAND8; + case AV_PIX_FMT_RPI4_10: + case AV_PIX_FMT_YUV420P10LE: // When probing this is the swfmt given + return VLC_CODEC_DRM_PRIME_SAND30; + case AV_PIX_FMT_BGR0: + return VLC_CODEC_DRM_PRIME_RGB32; + default: + return 0; + } + break; #if LIBAVUTIL_VERSION_CHECK(54, 13, 1, 24, 100) case AV_PIX_FMT_D3D11VA_VLD: diff --git a/modules/codec/avcodec/video.c b/modules/codec/avcodec/video.c index 8c892dd3f4..88456f9e48 100644 --- a/modules/codec/avcodec/video.c +++ b/modules/codec/avcodec/video.c @@ -29,6 +29,8 @@ # include "config.h" #endif +#define OPT_RPI 1 + #include #include #include @@ -46,6 +48,8 @@ #include "avcodec.h" #include "va.h" +#include "drm_pic.h" + #include "../../packetizer/av1_obu.h" #include "../../packetizer/av1.h" #include "../codec/cc.h" @@ -53,6 +57,13 @@ /***************************************************************************** * decoder_sys_t : decoder descriptor *****************************************************************************/ + +struct hw_setup_params_s { + uint32_t i_codec; + unsigned int width; + unsigned int height; +}; + struct decoder_sys_t { AVCodecContext *p_context; @@ -61,6 +72,9 @@ struct decoder_sys_t /* Video decoder specific part */ date_t pts; + mtime_t dts0; + bool dts0_used; + /* Closed captions for decoders */ cc_data_t cc; @@ -91,6 +105,7 @@ struct decoder_sys_t /* VA API */ vlc_va_t *p_va; enum PixelFormat pix_fmt; + enum PixelFormat sw_pix_fmt; int profile; int level; @@ -359,6 +374,13 @@ static int lavc_CopyPicture(decoder_t *dec, picture_t *pic, AVFrame *frame) { decoder_sys_t *sys = dec->p_sys; + // DRM prime frames are alloced by the decoder + // To copy out just attach the buf to the pic + if (frame->format == AV_PIX_FMT_DRM_PRIME) + { + return drm_prime_attach_buf_to_pic(pic, frame); + } + vlc_fourcc_t fourcc = FindVlcChroma(frame->format); if (!fourcc) { @@ -423,6 +445,9 @@ static int OpenVideoCodec( decoder_t *p_dec ) ctx->bits_per_coded_sample = p_dec->fmt_in.video.i_bits_per_pixel; p_sys->pix_fmt = AV_PIX_FMT_NONE; + p_sys->sw_pix_fmt = AV_PIX_FMT_NONE; + p_sys->profile = -1; + p_sys->level = -1; cc_Init( &p_sys->cc ); set_video_color_settings( &p_dec->fmt_in.video, ctx ); @@ -462,6 +487,32 @@ static int OpenVideoCodec( decoder_t *p_dec ) return 0; } +static es_format_t hw_fail; + +static bool +hw_check_bad(const es_format_t * const fmt) +{ + if (hw_fail.i_codec == fmt->i_codec && + hw_fail.video.i_width == fmt->video.i_width && + hw_fail.video.i_height == fmt->video.i_height) + return true; + + return false; +} + +static void +hw_set_bad(const es_format_t * const fmt) +{ + if (fmt->video.i_width != 0 && fmt->video.i_height != 0) + hw_fail = *fmt; +} + +/***************************************************************************** + * 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 InitVideoDecCommon( decoder_t *p_dec ) { decoder_sys_t *p_sys = p_dec->p_sys; @@ -587,6 +638,8 @@ static int InitVideoDecCommon( decoder_t *p_dec ) /* ***** misc init ***** */ date_Init(&p_sys->pts, 1, 30001); date_Set(&p_sys->pts, VLC_TICK_INVALID); + p_sys->dts0 = VLC_TICK_INVALID; + p_sys->dts0_used = false; p_sys->b_first_frame = true; p_sys->i_late_frames = 0; p_sys->b_from_preroll = false; @@ -610,17 +663,31 @@ static int InitVideoDecCommon( decoder_t *p_dec ) } else p_sys->palette_sent = true; + // If we want DRM_PRIME then we need to create the context before Open + // * This probably applies to anything that wants device_ctx init + { + const AVCodecHWConfig * hw_config; + for (int i = 0; (hw_config = avcodec_get_hw_config(p_codec, i)) != NULL; ++i) + { + if ((hw_config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0 && + hw_config->device_type == AV_HWDEVICE_TYPE_DRM) + { + int err; + if ((err = av_hwdevice_ctx_create(&p_context->hw_device_ctx, hw_config->device_type, NULL, NULL, 0)) < 0) { + msg_Dbg(p_dec, "Failed to create specified HW device: %s", av_err2str(err)); + goto error; + } + break; + } + } + } + /* ***** 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; - } + goto error; p_dec->pf_decode = DecodeVideo; p_dec->pf_flush = Flush; @@ -631,6 +698,12 @@ static int InitVideoDecCommon( decoder_t *p_dec ) if( p_context->level != FF_LEVEL_UNKNOWN ) p_dec->fmt_in.i_level = p_context->level; return VLC_SUCCESS; + +error: + vlc_sem_destroy( &p_sys->sem_mt ); + free( p_sys ); + avcodec_free_context( &p_context ); + return VLC_EGENERIC; } static int ffmpeg_OpenVa(decoder_t *p_dec, AVCodecContext *p_context, @@ -652,7 +725,8 @@ static int ffmpeg_OpenVa(decoder_t *p_dec, AVCodecContext *p_context, return VLC_EGENERIC; } const AVPixFmtDescriptor *dsc = av_pix_fmt_desc_get(hwfmt); - msg_Dbg(p_dec, "trying format %s", dsc ? dsc->name : "unknown"); + const AVPixFmtDescriptor *dsc_sw = av_pix_fmt_desc_get(swfmt); + msg_Dbg(p_dec, "trying format %s:%s", dsc ? dsc->name : "unknown", dsc_sw ? dsc_sw->name : "unknown"); if (lavc_UpdateVideoFormat(p_dec, p_context, hwfmt, swfmt)) return VLC_EGENERIC; /* Unsupported brand of hardware acceleration */ @@ -686,6 +760,10 @@ static int ffmpeg_OpenVa(decoder_t *p_dec, AVCodecContext *p_context, static const enum PixelFormat hwfmts[] = { +#if OPT_RPI + // If Pi then do not bother with stuff we know will fail + AV_PIX_FMT_DRM_PRIME, +#else #ifdef _WIN32 #if LIBAVUTIL_VERSION_CHECK(54, 13, 1, 24, 100) AV_PIX_FMT_D3D11VA_VLD, @@ -695,6 +773,7 @@ static const enum PixelFormat hwfmts[] = AV_PIX_FMT_VAAPI, #if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 4, 0)) AV_PIX_FMT_VDPAU, +#endif #endif AV_PIX_FMT_NONE, }; @@ -813,11 +892,11 @@ failed: * the ffmpeg codec will be opened, some memory allocated. The vout is not yet * opened (done after the first decoded frame). *****************************************************************************/ -int InitVideoDec( vlc_object_t *obj ) +static int InitVideoDec2( vlc_object_t *obj, const int hw ) { decoder_t *p_dec = (decoder_t *)obj; const AVCodec *p_codec; - AVCodecContext *p_context = ffmpeg_AllocContext( p_dec, &p_codec ); + AVCodecContext *p_context = ffmpeg_AllocContextHw( p_dec, &p_codec, hw ); if( p_context == NULL ) return VLC_EGENERIC; @@ -839,6 +918,27 @@ int InitVideoDec( vlc_object_t *obj ) return InitVideoDecCommon( p_dec ); } +int InitVideoDec( vlc_object_t *obj ) +{ + decoder_t * const p_dec = (decoder_t *)obj; + + // Don't retry something we know failed + if (!hw_check_bad(&p_dec->fmt_in)) + { + if (InitVideoDec2(obj, 1) == 0) + return 0; + + hw_set_bad(&p_dec->fmt_in); + msg_Dbg(p_dec, "Set hw fail for %4.4s %dx%d", (char *)&p_dec->fmt_in.i_codec, p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height); + } + else + { + msg_Dbg(p_dec, "Avoid trying hw decoder for %4.4s %dx%d", (char *)&p_dec->fmt_in.i_codec, p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height); + } + + return InitVideoDec2(obj, 0); +} + /***************************************************************************** * Flush: *****************************************************************************/ @@ -848,6 +948,8 @@ static void Flush( decoder_t *p_dec ) AVCodecContext *p_context = p_sys->p_context; date_Set(&p_sys->pts, VLC_TICK_INVALID); /* To make sure we recover properly */ + p_sys->dts0 = VLC_TICK_INVALID; + p_sys->dts0_used = false; p_sys->i_late_frames = 0; p_sys->b_draining = false; cc_Flush( &p_sys->cc ); @@ -875,6 +977,8 @@ static bool check_block_validity( decoder_sys_t *p_sys, block_t *block ) if( block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) ) { date_Set( &p_sys->pts, VLC_TICK_INVALID ); /* To make sure we recover properly */ + p_sys->dts0 = VLC_TICK_INVALID; + p_sys->dts0_used = false; cc_Flush( &p_sys->cc ); p_sys->i_late_frames = 0; @@ -1215,6 +1319,10 @@ static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error } if( b_has_data ) { + /* Remember 1st DTS in case we need to invent a timebase */ + if (p_sys->dts0 <= VLC_TICK_INVALID) + p_sys->dts0 = p_block->i_dts; + pkt->data = p_block->p_buffer; pkt->size = p_block->i_buffer; pkt->pts = p_block->i_pts > VLC_TICK_INVALID ? p_block->i_pts : AV_NOPTS_VALUE; @@ -1321,6 +1429,17 @@ static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error if( i_pts == AV_NOPTS_VALUE ) i_pts = date_Get( &p_sys->pts ); + /* VLC doesn't like having no pts - but a simple timestamp at the + * start of time is all that is needed to get it going - pick the + * first dts we saw as being in the right general area */ + if (i_pts > VLC_TICK_INVALID) + p_sys->dts0_used = true; + else if (p_sys->dts0 > VLC_TICK_INVALID && !p_sys->dts0_used) + { + i_pts = p_sys->dts0; + p_sys->dts0_used = true; + } + /* Interpolate the next PTS */ if( i_pts > VLC_TICK_INVALID ) date_Set( &p_sys->pts, i_pts ); @@ -1373,9 +1492,10 @@ static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error { /* When direct rendering is not used, get_format() and get_buffer() * might not be called. The output video format must be set here * then picture buffer can be allocated. */ - if (p_sys->p_va == NULL + if ((frame->format == AV_PIX_FMT_DRM_PRIME || + p_sys->p_va == NULL) && lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, - p_context->pix_fmt) == 0) + p_context->sw_pix_fmt) == 0) p_pic = decoder_NewPicture(p_dec); if( !p_pic ) @@ -1768,6 +1888,7 @@ static enum PixelFormat ffmpeg_GetFormat( AVCodecContext *p_context, } swfmt = defaultfmt; } + p_sys->sw_pix_fmt = swfmt; if (p_sys->pix_fmt == AV_PIX_FMT_NONE) goto no_reuse; @@ -1821,7 +1942,10 @@ no_reuse: p_sys->level = p_context->level; if (!can_hwaccel) + { + msg_Dbg(p_dec, "No hwaccel - using %s", av_get_pix_fmt_name(swfmt)); return swfmt; + } #if (LIBAVCODEC_VERSION_MICRO >= 100) \ && (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 83, 101)) @@ -1840,11 +1964,23 @@ no_reuse: for( size_t i = 0; hwfmts[i] != AV_PIX_FMT_NONE; i++ ) { enum PixelFormat hwfmt = AV_PIX_FMT_NONE; + enum PixelFormat defsw = swfmt; + for( size_t j = 0; hwfmt == AV_PIX_FMT_NONE && pi_fmt[j] != AV_PIX_FMT_NONE; j++ ) if( hwfmts[i] == pi_fmt[j] ) hwfmt = hwfmts[i]; - if (ffmpeg_OpenVa(p_dec, p_context, hwfmt, swfmt, src_desc, &p_sys->sem_mt) != VLC_SUCCESS) +#if OPT_RPI + if (hwfmt == AV_PIX_FMT_DRM_PRIME && p_context->codec_id == AV_CODEC_ID_HEVC) + { + if (swfmt == AV_PIX_FMT_P010) + defsw = AV_PIX_FMT_RPI4_10; + if (swfmt == AV_PIX_FMT_YUV420P) + defsw = AV_PIX_FMT_RPI4_8; + } +#endif + + if (ffmpeg_OpenVa(p_dec, p_context, hwfmt, defsw, src_desc, &p_sys->sem_mt) != VLC_SUCCESS) continue; post_mt(p_sys); diff --git a/modules/demux/adaptive/http/ConnectionParams.cpp b/modules/demux/adaptive/http/ConnectionParams.cpp index 6e3f221b66..a60239d16d 100644 --- a/modules/demux/adaptive/http/ConnectionParams.cpp +++ b/modules/demux/adaptive/http/ConnectionParams.cpp @@ -70,8 +70,8 @@ void ConnectionParams::setPath(const std::string &path_) if(!hostname.empty()) { os << hostname; - if( (port != 80 && scheme != "http") || - (port != 443 && scheme != "https") ) + if( (port != 80 && scheme == "http") || + (port != 443 && scheme == "https") ) os << ":" << port; } os << path; diff --git a/modules/gui/qt/components/controller.cpp b/modules/gui/qt/components/controller.cpp index 3adfe67c0d..96d18a7289 100644 --- a/modules/gui/qt/components/controller.cpp +++ b/modules/gui/qt/components/controller.cpp @@ -812,6 +812,17 @@ FullscreenControllerWidget::FullscreenControllerWidget( intf_thread_t *_p_i, QWi vout.clear(); +#ifdef QT5_HAS_WAYLAND + if( b_hasWayland ) + { + // Popup is less than perfect in that it seems impossible to make it non-modal + // and you can't get it to fade but at least it goes where it is asked to and + // does less confusing things with other popups + setWindowFlags( Qt::Popup | Qt::FramelessWindowHint); + setWindowModality( Qt::NonModal ); + } + else +#endif setWindowFlags( Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint ); setAttribute( Qt::WA_ShowWithoutActivating ); setMinimumWidth( FSC_WIDTH ); diff --git a/modules/gui/qt/components/interface_widgets.cpp b/modules/gui/qt/components/interface_widgets.cpp index 4a0a0dae12..9ee73a3eaa 100644 --- a/modules/gui/qt/components/interface_widgets.cpp +++ b/modules/gui/qt/components/interface_widgets.cpp @@ -110,6 +110,44 @@ void VideoWidget::sync( void ) #endif } +static inline void incnz(unsigned int * px) +{ + if ( ++*px == 0 ) + *px = 1; +} + +/** + * The wayland surface may change if the window is hidden which + * seems to happen sometimes on resize + * In Qt it looks like this happens if the window is hidden + **/ +void VideoWidget::refreshHandle() +{ +#ifdef QT5_HAS_WAYLAND + if (!p_window || p_window->type != VOUT_WINDOW_TYPE_WAYLAND) + return; + + QWindow *window = stable->windowHandle(); + assert(window != NULL); + window->create(); + + QPlatformNativeInterface *qni = qApp->platformNativeInterface(); + assert(qni != NULL); + + struct wl_surface * const new_surface = static_cast( + qni->nativeResourceForWindow(QByteArrayLiteral("surface"), + window)); + + if ( p_window->handle.wl != new_surface ) + { + vlc_mutex_lock(&p_window->handle_lock); + p_window->handle.wl = new_surface; + incnz(&p_window->handle_seq); + vlc_mutex_unlock(&p_window->handle_lock); + } +#endif +} + /** * Request the video to avoid the conflicts **/ @@ -180,6 +218,7 @@ bool VideoWidget::request( struct vout_window_t *p_wnd ) window)); p_wnd->display.wl = static_cast( qni->nativeResourceForIntegration(QByteArrayLiteral("wl_display"))); + p_wnd->handle_seq = 1; break; } #endif @@ -238,6 +277,8 @@ void VideoWidget::reportSize() if( !p_window ) return; + refreshHandle(); + QSize size = physicalSize(); WindowResized(p_window, size); } @@ -293,6 +334,27 @@ bool VideoWidget::nativeEvent( const QByteArray& eventType, void* message, long* return false; } +void VideoWidget::showEvent(QShowEvent *event) +{ + QFrame::showEvent(event); + + if (p_window && p_window->type == VOUT_WINDOW_TYPE_WAYLAND && p_window->handle.wl == NULL) + refreshHandle(); +} + +void VideoWidget::hideEvent(QHideEvent *event) +{ + if (p_window && p_window->type == VOUT_WINDOW_TYPE_WAYLAND && p_window->handle.wl != NULL) + { + vlc_mutex_lock(&p_window->handle_lock); + p_window->handle.wl = NULL; + incnz(&p_window->handle_seq); + vlc_mutex_unlock(&p_window->handle_lock); + } + + QFrame::hideEvent(event); +} + void VideoWidget::resizeEvent( QResizeEvent *event ) { QWidget::resizeEvent( event ); diff --git a/modules/gui/qt/components/interface_widgets.hpp b/modules/gui/qt/components/interface_widgets.hpp index 583f2d1f50..e949e65b1a 100644 --- a/modules/gui/qt/components/interface_widgets.hpp +++ b/modules/gui/qt/components/interface_widgets.hpp @@ -69,6 +69,8 @@ protected: return NULL; } + void hideEvent(QHideEvent *event) Q_DECL_OVERRIDE; + void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; bool nativeEvent(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE; virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; @@ -88,6 +90,7 @@ private: bool enable_mouse_events; void reportSize(); + void refreshHandle(); signals: void sizeChanged( int, int ); diff --git a/modules/gui/qt/qt.cpp b/modules/gui/qt/qt.cpp index cefc75830f..297feb53bd 100644 --- a/modules/gui/qt/qt.cpp +++ b/modules/gui/qt/qt.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "qt.hpp" @@ -284,7 +285,7 @@ vlc_module_begin () add_string( "qt-slider-colours", "153;210;153;20;210;20;255;199;15;245;39;29", SLIDERCOL_TEXT, SLIDERCOL_LONGTEXT, false ) - add_bool( "qt-privacy-ask", true, PRIVACY_TEXT, PRIVACY_TEXT, + add_bool( "qt-privacy-ask", false, PRIVACY_TEXT, PRIVACY_TEXT, false ) change_private () @@ -529,6 +530,22 @@ static void *ThreadPlatform( void *obj, char *platform_name ) QApplication::setAttribute( Qt::AA_UseHighDpiPixmaps ); #endif + bool do_disable = false; + { + QVLCApp app( argc, argv ); + const double dpr = app.devicePixelRatio(); + if( dpr > 2.1 ) + { + do_disable = true; + msg_Warn(p_intf, "devicePixelRatio (%g) > 2.1: assuming broken", dpr); + } + app.quit(); + } + if( do_disable ) + { + QApplication::setAttribute( Qt::AA_DisableHighDpiScaling ); + } + /* Start the QApplication here */ QVLCApp app( argc, argv ); diff --git a/modules/hw/drm/Makefile.am b/modules/hw/drm/Makefile.am new file mode 100644 index 0000000000..8c3232364e --- /dev/null +++ b/modules/hw/drm/Makefile.am @@ -0,0 +1,26 @@ +drmdir = $(pluginsdir)/drm + +libdrm_avcodec_plugin_la_SOURCES = hw/drm/drm_avcodec.c +libdrm_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) +libdrm_avcodec_plugin_la_LIBADD = $(AVCODEC_LIBS) + +libdrm_gl_conv_plugin_la_SOURCES = hw/drm/drm_gl_conv.c +libdrm_gl_conv_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) + +libdrm_conv_sand30_plugin_la_SOURCES = hw/drm/conv_sand30.c +libdrm_conv_sand30_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) +libdrm_conv_sand30_plugin_la_LIBADD = $(AVCODEC_LIBS) + +libdrm_av_deinterlace_plugin_la_SOURCES = hw/drm/drm_av_deinterlace.c \ + codec/avcodec/drm_pic.c codec/avcodec/drm_pic.h +libdrm_av_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS) +libdrm_av_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) $(AVCODEC_LDFLAGS) -lavfilter +libdrm_av_deinterlace_plugin_la_LIBADD = $(AVCODEC_LIBS) + +if HAVE_DRM +drm_LTLIBRARIES = \ + libdrm_av_deinterlace_plugin.la \ + libdrm_avcodec_plugin.la \ + libdrm_conv_sand30_plugin.la \ + libdrm_gl_conv_plugin.la +endif diff --git a/modules/hw/drm/conv_sand30.c b/modules/hw/drm/conv_sand30.c new file mode 100644 index 0000000000..c60b5462e5 --- /dev/null +++ b/modules/hw/drm/conv_sand30.c @@ -0,0 +1,212 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include "../../codec/avcodec/drm_pic.h" + +#include + +#define TRACE_ALL 0 + +//---------------------------------------------------------------------------- +// +// Simple copy in to ZC + +typedef struct to_nv12_sys_s { + int dummy; +} to_nv12_sys_t; + +static vlc_fourcc_t +dst_fourcc_vlc_to_av(const vlc_fourcc_t av) +{ + switch (av) { + case VLC_CODEC_I420: + return AV_PIX_FMT_YUV420P; + case VLC_CODEC_NV12: + return AV_PIX_FMT_NV12; + case VLC_CODEC_I420_10L: + return AV_PIX_FMT_YUV420P10LE; + } + return 0; +} + +static void +pic_buf_free(void *opaque, uint8_t *data) +{ + VLC_UNUSED(data); + picture_Release(opaque); +} + +static AVBufferRef * +mk_buf_from_pic(picture_t * const pic, uint8_t * const data, const size_t size) +{ + return av_buffer_create(data, size, pic_buf_free, picture_Hold(pic), 0); +} + +static picture_t * +to_nv12_filter(filter_t *p_filter, picture_t *in_pic) +{ + to_nv12_sys_t * const sys = (to_nv12_sys_t *)p_filter->p_sys; +#if TRACE_ALL + msg_Dbg(p_filter, "<<< %s", __func__); +#endif + AVFrame * frame_in = av_frame_alloc(); + AVFrame * frame_out = av_frame_alloc(); + drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)in_pic->context; + int rv; + + VLC_UNUSED(sys); + + if (!frame_in || !frame_out || !pctx) + goto fail0; + + picture_t * const out_pic = filter_NewPicture(p_filter); + if (out_pic == NULL) + goto fail0; + + frame_in->format = AV_PIX_FMT_DRM_PRIME; + frame_in->buf[0] = av_buffer_ref(pctx->buf); + frame_in->data[0] = (uint8_t *)pctx->desc; + frame_in->hw_frames_ctx = av_buffer_ref(pctx->hw_frames_ctx); + frame_in->width = in_pic->format.i_width; + frame_in->height = in_pic->format.i_height; + frame_in->crop_left = in_pic->format.i_x_offset; + frame_in->crop_top = in_pic->format.i_y_offset; + frame_in->crop_right = frame_in->width - in_pic->format.i_visible_width - frame_in->crop_left; + frame_in->crop_bottom = frame_in->height - in_pic->format.i_visible_height - frame_in->crop_top; + + frame_out->format = dst_fourcc_vlc_to_av(p_filter->fmt_out.video.i_chroma); + frame_out->width = out_pic->format.i_width; + frame_out->height = out_pic->format.i_height; + for (int i = 0; i != out_pic->i_planes; ++i) { + frame_out->buf[i] = mk_buf_from_pic(out_pic, out_pic->p[i].p_pixels, out_pic->p[i].i_lines * out_pic->p[i].i_pitch); + if (!frame_out->buf[i]) { + msg_Err(p_filter, "Failed to make buf from pic"); + goto fail1; + } + frame_out->data[i] = out_pic->p[i].p_pixels; + frame_out->linesize[i] = out_pic->p[i].i_pitch; + } + + if ((rv = av_hwframe_transfer_data(frame_out, frame_in, 0)) != 0) { + msg_Err(p_filter, "Failed to transfer data: %s", av_err2str(rv)); + goto fail1; + } + + av_frame_free(&frame_in); + av_frame_free(&frame_out); + picture_Release(in_pic); + return out_pic; + +fail1: + picture_Release(out_pic); +fail0: + av_frame_free(&frame_in); + av_frame_free(&frame_out); + picture_Release(in_pic); + return NULL; +} + +static void to_nv12_flush(filter_t * p_filter) +{ + VLC_UNUSED(p_filter); +} + +static void CloseConverterToNv12(vlc_object_t * obj) +{ + filter_t * const p_filter = (filter_t *)obj; + to_nv12_sys_t * const sys = (to_nv12_sys_t *)p_filter->p_sys; + + if (sys == NULL) + return; + + p_filter->p_sys = NULL; + + free(sys); +} + +static bool to_nv12_validate_fmt(const video_format_t * const f_in, const video_format_t * const f_out) +{ + if (f_in->i_height != f_out->i_height || + f_in->i_width != f_out->i_width) + { + return false; + } + + if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_SAND8 && + (f_out->i_chroma == VLC_CODEC_I420 || f_out->i_chroma == VLC_CODEC_NV12)) + return true; + + if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_I420 && + f_out->i_chroma == VLC_CODEC_I420) + return true; + + if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_NV12 && + f_out->i_chroma == VLC_CODEC_NV12) + return true; + + if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_SAND30 && + (f_out->i_chroma == VLC_CODEC_I420_10L || f_out->i_chroma == VLC_CODEC_NV12)) + return true; + + return false; +} + +static int OpenConverterToNv12(vlc_object_t * obj) +{ + int ret = VLC_EGENERIC; + filter_t * const p_filter = (filter_t *)obj; + + if (!to_nv12_validate_fmt(&p_filter->fmt_in.video, &p_filter->fmt_out.video)) + goto fail; + + { + msg_Dbg(p_filter, "%s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d", __func__, + fourcc2str(p_filter->fmt_in.video.i_chroma), + p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, + p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, + p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, + p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den, + fourcc2str(p_filter->fmt_out.video.i_chroma), + p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, + p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, + p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height, + p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask, + p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den); + } + + to_nv12_sys_t * const sys = calloc(1, sizeof(*sys)); + if (!sys) { + ret = VLC_ENOMEM; + goto fail; + } + p_filter->p_sys = (filter_sys_t *)sys; + + p_filter->pf_video_filter = to_nv12_filter; + p_filter->pf_flush = to_nv12_flush; + return VLC_SUCCESS; + +fail: + CloseConverterToNv12(obj); + return ret; +} + +vlc_module_begin() + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VFILTER ) + set_shortname(N_("DRMPRIME to s/w")) + set_description(N_("DRMPRIME-to software picture filter")) + add_shortcut("drmprime_to_sw") + set_capability( "video converter", 50 ) + set_callbacks(OpenConverterToNv12, CloseConverterToNv12) +vlc_module_end() + diff --git a/modules/hw/drm/drm_av_deinterlace.c b/modules/hw/drm/drm_av_deinterlace.c new file mode 100644 index 0000000000..9e0b569989 --- /dev/null +++ b/modules/hw/drm/drm_av_deinterlace.c @@ -0,0 +1,273 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#include "../../codec/avcodec/drm_pic.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TRACE_ALL 1 + +typedef struct filter_sys_t { + AVFilterGraph *filter_graph; + AVFilterContext *buffersink_ctx; // Allocated within graph - no explicit free + AVFilterContext *buffersrc_ctx; // Allocated within graph - no explicit free + bool has_out; + AVFrame *out_frame; +} filter_sys_t; + +static void drmp_av_flush(filter_t * filter) +{ + // Nothing to do + VLC_UNUSED(filter); + +#if TRACE_ALL + msg_Dbg(filter, "<<< %s", __func__); +#endif +} + +static picture_t * drmp_av_deinterlace(filter_t * filter, picture_t * in_pic) +{ + filter_sys_t *const sys = filter->p_sys; + AVFrame * frame = av_frame_alloc(); + drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)in_pic->context; + picture_t * out_pic = NULL; + picture_t ** pp_pic = &out_pic; + int ret; + +#if TRACE_ALL + msg_Dbg(filter, "<<< %s", __func__); +#endif + + if (!frame) { + msg_Err(filter, "Frame alloc failure"); + goto fail; + } + + frame->format = AV_PIX_FMT_DRM_PRIME; + frame->buf[0] = av_buffer_ref(pctx->buf); + frame->data[0] = (uint8_t *)pctx->desc; + frame->hw_frames_ctx = av_buffer_ref(pctx->hw_frames_ctx); + frame->width = in_pic->format.i_width; + frame->height = in_pic->format.i_height; + frame->crop_left = in_pic->format.i_x_offset; + frame->crop_top = in_pic->format.i_y_offset; + frame->crop_right = frame->width - in_pic->format.i_visible_width - frame->crop_left; + frame->crop_bottom = frame->height - in_pic->format.i_visible_height - frame->crop_top; + frame->interlaced_frame = !in_pic->b_progressive; + frame->top_field_first = in_pic->b_top_field_first; + frame->pts = (in_pic->date == VLC_TS_INVALID) ? AV_NOPTS_VALUE : in_pic->date; + + picture_Release(in_pic); + in_pic = NULL; + + if ((ret = av_buffersrc_add_frame_flags(sys->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { + msg_Err(filter, "Failed to feed filtergraph: %s", av_err2str(ret)); + goto fail; + } + av_frame_unref(frame); + + while (sys->has_out || (ret = av_buffersink_get_frame(sys->buffersink_ctx, sys->out_frame)) == 0) { + picture_t *const pic = filter_NewPicture(filter); + sys->has_out = true; + // Failure to get an output pic happens quite often, just keep the + // frame for next time + if (!pic) + break; + + if (drm_prime_attach_buf_to_pic(pic, sys->out_frame) != VLC_SUCCESS) { + msg_Err(filter, "Failed to attach frame to out pic"); + picture_Release(pic); + goto fail; + } + pic->date = sys->out_frame->pts == AV_NOPTS_VALUE ? VLC_TS_INVALID : sys->out_frame->pts; + av_frame_unref(sys->out_frame); + sys->has_out = false; + + *pp_pic = pic; + pp_pic = &pic->p_next; + } + + if (ret < 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) { + msg_Err(filter, "Failed to get frame: %s", av_err2str(ret)); + goto fail; + } + + // Even if we get an error we may have processed some pics and we need to return them +fail: + if (in_pic) + picture_Release(in_pic); + av_frame_free(&frame); + +#if TRACE_ALL + msg_Dbg(filter, ">>> %s: %p", __func__, out_pic); +#endif + + return out_pic; +} + +static void CloseDrmpAvDeinterlace(filter_t *filter) +{ + filter_sys_t * const sys = filter->p_sys; + +#if TRACE_ALL + msg_Dbg(filter, "<<< %s", __func__); +#endif + + if (sys == NULL) + return; + + av_frame_free(&sys->out_frame); + avfilter_graph_free(&sys->filter_graph); + free(sys); +} + + +// Copied almost directly from ffmpeg filtering_video.c example +static int init_filters(filter_t * const filter, + const char * const filters_descr) +{ + filter_sys_t *const sys = filter->p_sys; + const video_format_t * const fmt = &filter->fmt_in.video; + char args[512]; + int ret = 0; + const AVFilter *buffersrc = avfilter_get_by_name("buffer"); + const AVFilter *buffersink = avfilter_get_by_name("buffersink"); + AVFilterInOut *outputs = avfilter_inout_alloc(); + AVFilterInOut *inputs = avfilter_inout_alloc(); + enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_DRM_PRIME, AV_PIX_FMT_NONE }; + + sys->out_frame = av_frame_alloc(); + sys->filter_graph = avfilter_graph_alloc(); + if (!outputs || !inputs || !sys->filter_graph || !sys->out_frame) { + ret = AVERROR(ENOMEM); + goto end; + } + + /* buffer video source: the decoded frames from the decoder will be inserted here. */ + snprintf(args, sizeof(args), + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + fmt->i_visible_width, fmt->i_visible_height, AV_PIX_FMT_DRM_PRIME, + 1, (int)CLOCK_FREQ, + fmt->i_sar_num, fmt->i_sar_den); + + ret = avfilter_graph_create_filter(&sys->buffersrc_ctx, buffersrc, "in", + args, NULL, sys->filter_graph); + if (ret < 0) { + msg_Err(filter, "Cannot create buffer source"); + goto end; + } + + /* buffer video sink: to terminate the filter chain. */ + ret = avfilter_graph_create_filter(&sys->buffersink_ctx, buffersink, "out", + NULL, NULL, sys->filter_graph); + if (ret < 0) { + msg_Err(filter, "Cannot create buffer sink"); + goto end; + } + + ret = av_opt_set_int_list(sys->buffersink_ctx, "pix_fmts", pix_fmts, + AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + msg_Err(filter, "Cannot set output pixel format"); + goto end; + } + + /* + * Set the endpoints for the filter graph. The filter_graph will + * be linked to the graph described by filters_descr. + */ + + /* + * The buffer source output must be connected to the input pad of + * the first filter described by filters_descr; since the first + * filter input label is not specified, it is set to "in" by + * default. + */ + outputs->name = av_strdup("in"); + outputs->filter_ctx = sys->buffersrc_ctx; + outputs->pad_idx = 0; + outputs->next = NULL; + + /* + * The buffer sink input must be connected to the output pad of + * the last filter described by filters_descr; since the last + * filter output label is not specified, it is set to "out" by + * default. + */ + inputs->name = av_strdup("out"); + inputs->filter_ctx = sys->buffersink_ctx; + inputs->pad_idx = 0; + inputs->next = NULL; + + if ((ret = avfilter_graph_parse_ptr(sys->filter_graph, filters_descr, + &inputs, &outputs, NULL)) < 0) + goto end; + + if ((ret = avfilter_graph_config(sys->filter_graph, NULL)) < 0) + goto end; + +end: + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + + return ret == 0 ? VLC_SUCCESS : ret == AVERROR(ENOMEM) ? VLC_ENOMEM : VLC_EGENERIC; +} + +static bool is_fmt_valid_in(const vlc_fourcc_t fmt) +{ + return fmt == VLC_CODEC_DRM_PRIME_I420 || + fmt == VLC_CODEC_DRM_PRIME_NV12 || + fmt == VLC_CODEC_DRM_PRIME_SAND8; +} + +static int OpenDrmpAvDeinterlace(filter_t *filter) +{ + filter_sys_t *sys; + int ret; + + msg_Dbg(filter, "<<< %s", __func__); + + if (!is_fmt_valid_in(filter->fmt_in.video.i_chroma) || + filter->fmt_out.video.i_chroma != filter->fmt_in.video.i_chroma) + return VLC_EGENERIC; + + sys = calloc(1, sizeof(filter_sys_t)); + if (!sys) + return VLC_ENOMEM; + filter->p_sys = sys; + + if ((ret = init_filters(filter, "deinterlace_v4l2m2m")) != 0) + goto fail; + + filter->pf_video_filter = drmp_av_deinterlace; + filter->pf_flush = drmp_av_flush; + + return VLC_SUCCESS; + +fail: + CloseDrmpAvDeinterlace(filter); + return VLC_EGENERIC; +} + +vlc_module_begin() + set_shortname(N_("DRM PRIME deinterlace")) + set_description(N_("libav-based DRM_PRIME deinterlace filter plugin")) + set_capability("video filter", 902) + set_category(CAT_VIDEO) + set_subcategory(SUBCAT_VIDEO_VFILTER) + set_callbacks(OpenDrmpAvDeinterlace, CloseDrmpAvDeinterlace) + add_shortcut("deinterlace") +vlc_module_end() + diff --git a/modules/hw/drm/drm_avcodec.c b/modules/hw/drm/drm_avcodec.c new file mode 100644 index 0000000000..b20eaf700e --- /dev/null +++ b/modules/hw/drm/drm_avcodec.c @@ -0,0 +1,80 @@ +/***************************************************************************** + * avcodec.c: VDPAU decoder for libav + ***************************************************************************** + * Copyright (C) 2012-2013 Rémi Denis-Courmont + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include "../../codec/avcodec/va.h" + +// Dummy - not expected to be called +static int drm_va_get(vlc_va_t *va, picture_t *pic, uint8_t **data) +{ + VLC_UNUSED(va); + VLC_UNUSED(pic); + VLC_UNUSED(data); + + return VLC_SUCCESS; +} + +static int Open(vlc_va_t *va, AVCodecContext *avctx, const AVPixFmtDescriptor *desc, + enum PixelFormat pix_fmt, + const es_format_t *fmt, picture_sys_t *p_sys) +{ + VLC_UNUSED(fmt); + VLC_UNUSED(p_sys); + VLC_UNUSED(desc); + + msg_Dbg(va, "%s: pix_fmt=%d", __func__, pix_fmt); + + if (pix_fmt != AV_PIX_FMT_DRM_PRIME) + return VLC_EGENERIC; + + // This gives us whatever the decode requires + 6 frames that will be + // alloced by ffmpeg before it blocks (at least for Pi HEVC) + avctx->extra_hw_frames = 6; + + va->description = "DRM Video Accel"; + va->get = drm_va_get; + return VLC_SUCCESS; +} + +static void Close(vlc_va_t *va, void **hwctx) +{ + VLC_UNUSED(hwctx); + + msg_Dbg(va, "%s", __func__); +} + +vlc_module_begin() + set_description(N_("DRM video decoder")) + set_capability("hw decoder", 100) + set_category(CAT_INPUT) + set_subcategory(SUBCAT_INPUT_VCODEC) + set_callbacks(Open, Close) + add_shortcut("drm_prime") +vlc_module_end() diff --git a/modules/hw/drm/drm_gl_conv.c b/modules/hw/drm/drm_gl_conv.c new file mode 100644 index 0000000000..157d4a9c77 --- /dev/null +++ b/modules/hw/drm/drm_gl_conv.c @@ -0,0 +1,425 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../../video_output/opengl/converter.h" +#include "../../codec/avcodec/drm_pic.h" + +#include + +#define TRACE_ALL 0 + +#define ICACHE_SIZE 2 + +typedef struct drm_gl_converter_s +{ + EGLint drm_fourcc; + + unsigned int icache_n; + struct icache_s { + EGLImageKHR last_image; + picture_context_t * last_ctx; + } icache[ICACHE_SIZE]; + + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; +} drm_gl_converter_t; + + +static void +unset_icache_ent(const opengl_tex_converter_t * const tc, struct icache_s * const s) +{ + if (s->last_image) + { + tc->gl->egl.destroyImageKHR(tc->gl, s->last_image); + s->last_image = NULL; + } + + if (s->last_ctx) + { + s->last_ctx->destroy(s->last_ctx); + s->last_ctx = NULL; + } +} + +static void +update_icache(const opengl_tex_converter_t * const tc, EGLImageKHR image, picture_t * pic) +{ + drm_gl_converter_t * const sys = tc->priv; + struct icache_s * const s = sys->icache + sys->icache_n; + + s->last_image = image; + // DRM buffer is held by the context, pictures can be in surprisingly + // small pools for filters so let go of the pic and keep a ref on the + // context + unset_icache_ent(tc, s); + s->last_ctx = pic->context->copy(pic->context); + sys->icache_n = sys->icache_n + 1 >= ICACHE_SIZE ? 0 : sys->icache_n + 1; +} + +static int +tc_drm_update(const opengl_tex_converter_t *tc, GLuint *textures, + const GLsizei *tex_width, const GLsizei *tex_height, + picture_t *pic, const size_t *plane_offset) +{ + drm_gl_converter_t * const sys = tc->priv; +#if TRACE_ALL + { + 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); + + { + const AVDRMFrameDescriptor * const desc = drm_prime_get_desc(pic); + EGLint attribs[64] = {0}; + + static const EGLint plane_exts[] = { + EGL_DMA_BUF_PLANE0_FD_EXT, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE0_PITCH_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE1_FD_EXT, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE2_FD_EXT, + EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, + }; + + // msg_Info(o, "<<< %s", __func__); + + if (!desc) + { + msg_Err(tc, "%s: No DRM Frame desriptor found", __func__); + return VLC_EGENERIC; + } + + EGLint *a = attribs; + *a++ = EGL_WIDTH; + *a++ = tex_width[0]; + *a++ = EGL_HEIGHT; + *a++ = tex_height[0]; + *a++ = EGL_LINUX_DRM_FOURCC_EXT; + *a++ = desc->layers[0].format; + + if (vlc_fourcc_IsYUV(pic->format.i_chroma)) + { + *a++ = EGL_SAMPLE_RANGE_HINT_EXT; + *a++ = pic->format.b_color_range_full ? EGL_YUV_FULL_RANGE_EXT : EGL_YUV_NARROW_RANGE_EXT; + + switch (pic->format.chroma_location) + { + case CHROMA_LOCATION_LEFT: + *a++ = EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT; + *a++ = EGL_YUV_CHROMA_SITING_0_EXT; + *a++ = EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT; + *a++ = EGL_YUV_CHROMA_SITING_0_5_EXT; + break; + case CHROMA_LOCATION_CENTER: + *a++ = EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT; + *a++ = EGL_YUV_CHROMA_SITING_0_5_EXT; + *a++ = EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT; + *a++ = EGL_YUV_CHROMA_SITING_0_5_EXT; + break; + case CHROMA_LOCATION_TOP_LEFT: + *a++ = EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT; + *a++ = EGL_YUV_CHROMA_SITING_0_EXT; + *a++ = EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT; + *a++ = EGL_YUV_CHROMA_SITING_0_EXT; + break; + case CHROMA_LOCATION_TOP_CENTER: + *a++ = EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT; + *a++ = EGL_YUV_CHROMA_SITING_0_5_EXT; + *a++ = EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT; + *a++ = EGL_YUV_CHROMA_SITING_0_EXT; + break; + case CHROMA_LOCATION_BOTTOM_LEFT: + case CHROMA_LOCATION_BOTTOM_CENTER: + case CHROMA_LOCATION_UNDEF: + default: + break; + } + + switch (pic->format.space) + { + case COLOR_SPACE_BT2020: + *a++ = EGL_YUV_COLOR_SPACE_HINT_EXT; + *a++ = EGL_ITU_REC2020_EXT; + break; + case COLOR_SPACE_BT601: + *a++ = EGL_YUV_COLOR_SPACE_HINT_EXT; + *a++ = EGL_ITU_REC601_EXT; + break; + case COLOR_SPACE_BT709: + *a++ = EGL_YUV_COLOR_SPACE_HINT_EXT; + *a++ = EGL_ITU_REC709_EXT; + break; + case COLOR_SPACE_UNDEF: + default: + break; + } + } + + const EGLint * ext = plane_exts; + + for (int i = 0; i < desc->nb_layers; ++i) + { + const AVDRMLayerDescriptor * const layer = desc->layers + i; + for (int j = 0; j != layer->nb_planes; ++j) + { + const AVDRMPlaneDescriptor * const plane = layer->planes + j; + const AVDRMObjectDescriptor * const obj = desc->objects + plane->object_index; + + *a++ = *ext++; // FD + *a++ = obj->fd; + *a++ = *ext++; // OFFSET + *a++ = plane->offset; + *a++ = *ext++; // PITCH + *a++ = plane->pitch; + if (obj->format_modifier == DRM_FORMAT_MOD_INVALID) + { + ext += 2; + } + else + { + *a++ = *ext++; // MODIFIER_LO + *a++ = (EGLint)(obj->format_modifier & 0xffffffff); + *a++ = *ext++; // MODIFIER_HI + *a++ = (EGLint)(obj->format_modifier >> 32); + } + } + } + *a++ = EGL_NONE; + *a++ = 0; + + const EGLImageKHR image = tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); + if (!image) { + msg_Err(tc, "Failed to createImageKHR: Err=%#x", tc->vt->GetError()); + return VLC_EGENERIC; + } + + // *** MMAL ZC does this a little differently + // tc->tex_target == OES??? + + tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, textures[0]); + tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + sys->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image); + + update_icache(tc, image, pic); + } + + return VLC_SUCCESS; +} + +static int +tc_drm_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_drm_prepare_shader(const opengl_tex_converter_t *tc, + const GLsizei *tex_width, const GLsizei *tex_height, + float alpha) +{ + (void) tex_width; (void) tex_height; (void) alpha; + VLC_UNUSED(tc); +// tc->vt->Uniform1i(tc->uloc.Texture[0], 0); +} + +static GLuint +tc_fragment_shader_init(opengl_tex_converter_t * const tc, const GLenum tex_target, + const vlc_fourcc_t chroma, const video_color_space_t yuv_space) +{ + VLC_UNUSED(yuv_space); + + tc->tex_count = 1; + tc->tex_target = tex_target; + tc->texs[0] = (struct opengl_tex_cfg) { + { 1, 1 }, { 1, 1 }, GL_RGB, chroma, GL_UNSIGNED_SHORT //** ?? + }; + + tc->pf_fetch_locations = tc_drm_fetch_locations; + tc->pf_prepare_shader = tc_drm_prepare_shader; + + + const char fs[] = + "#extension GL_OES_EGL_image_external : enable\n" + "precision mediump float;\n" + "uniform samplerExternalOES Texture0;\n" + "varying vec2 TexCoord0;\n" + "void main() {\n" + " gl_FragColor = texture2D(Texture0, TexCoord0);\n" + "}\n"; + + + const char *code = fs; + + GLuint fragment_shader = tc->vt->CreateShader(GL_FRAGMENT_SHADER); + tc->vt->ShaderSource(fragment_shader, 1, &code, NULL); + tc->vt->CompileShader(fragment_shader); + return fragment_shader; +} + + +static void +CloseGLConverter(vlc_object_t *obj) +{ + opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; + drm_gl_converter_t * const sys = tc->priv; + unsigned int i; + + if (sys == NULL) + return; + + for (i = 0; i != ICACHE_SIZE; ++i) + unset_icache_ent(tc, sys->icache + i); + free(sys); +} + +#ifndef DRM_FORMAT_P030 +#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') +#endif + +static struct vlc_to_drm_mod_s { + vlc_fourcc_t chroma; + uint32_t drm_fmt; + uint64_t drm_mod; +} vlc_to_drm_mods[] = { + {VLC_CODEC_DRM_PRIME_I420, DRM_FORMAT_YUV420, DRM_FORMAT_MOD_LINEAR}, + {VLC_CODEC_DRM_PRIME_NV12, DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR}, + {VLC_CODEC_DRM_PRIME_SAND8, DRM_FORMAT_NV12, DRM_FORMAT_MOD_BROADCOM_SAND128}, + {VLC_CODEC_DRM_PRIME_SAND30, DRM_FORMAT_P030, DRM_FORMAT_MOD_BROADCOM_SAND128}, +}; + +static bool check_chroma(opengl_tex_converter_t * const tc) +{ + char fcc[5] = {0}; + vlc_fourcc_to_char(tc->fmt.i_chroma, fcc); + uint32_t fmt = 0; + uint64_t mod = DRM_FORMAT_MOD_INVALID; + uint64_t mods[16]; + int32_t mod_count = 0; + + for (unsigned int i = 0; i != ARRAY_SIZE(vlc_to_drm_mods); ++i) + { + if (tc->fmt.i_chroma == vlc_to_drm_mods[i].chroma) + { + fmt = vlc_to_drm_mods[i].drm_fmt; + mod = vlc_to_drm_mods[i].drm_mod; + break; + } + } + if (!fmt) + return false; + + if (!tc->gl->egl.queryDmaBufModifiersEXT) + { + msg_Dbg(tc, "No queryDmaBufModifiersEXT"); + return false; + } + + if (!tc->gl->egl.queryDmaBufModifiersEXT(tc->gl, fmt, 16, mods, NULL, &mod_count)) + { + msg_Dbg(tc, "queryDmaBufModifiersEXT Failed for %s", fcc); + return false; + } + + for (int32_t i = 0; i < mod_count; ++i) + { + if (mods[i] == mod) + return true; + } + msg_Dbg(tc, "Mod %" PRIx64 " not found for %s/%.4s in %d mods", mod, fcc, (char*)&fmt, mod_count); + return false; +} + +static int +OpenGLConverter(vlc_object_t *obj) +{ + opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj; + int rv = VLC_EGENERIC; + + // Do we know what to do with this? + if (!check_chroma(tc)) + return VLC_EGENERIC; + + { + msg_Dbg(tc, "<<< %s: %.4s %dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__, + (char *)&tc->fmt.i_chroma, + tc->fmt.i_width, tc->fmt.i_height, + tc->fmt.i_x_offset, tc->fmt.i_y_offset, + tc->fmt.i_visible_width, tc->fmt.i_visible_height, + tc->fmt.i_sar_num, tc->fmt.i_sar_den); + } + + if (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(drm_gl_converter_t))) == NULL) + { + msg_Err(tc, "priv alloc failure"); + rv = VLC_ENOMEM; + goto fail; + } + drm_gl_converter_t * const sys = tc->priv; + + if ((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, tc->fmt.i_chroma, tc->fmt.space)) == 0) + { + msg_Err(tc, "Failed to make shader"); + goto fail; + } + + tc->handle_texs_gen = true; // We manage the texs + tc->pf_update = tc_drm_update; + + return VLC_SUCCESS; + +fail: + CloseGLConverter(obj); + return rv; +} + +vlc_module_begin () + set_description("DRM OpenGL surface converter") + set_shortname (N_("DRMGLConverter")) + set_capability("glconv", 900) + set_callbacks(OpenGLConverter, CloseGLConverter) + set_category(CAT_VIDEO) + set_subcategory(SUBCAT_VIDEO_VOUT) + add_shortcut("drm_gl_converter") +vlc_module_end () + diff --git a/modules/hw/mmal/Makefile.am b/modules/hw/mmal/Makefile.am index 1dfb3e35ce..f85effd0b5 100644 --- a/modules/hw/mmal/Makefile.am +++ b/modules/hw/mmal/Makefile.am @@ -1,23 +1,60 @@ include $(top_srcdir)/modules/common.am mmaldir = $(pluginsdir)/mmal -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 +mmal_LTLIBRARIES = + +if HAVE_MMAL +libmmal_vout_plugin_la_SOURCES = vout.c mmal_cma.c mmal_picture.c subpic.c\ + mmal_cma.h mmal_cma_int.h mmal_picture.h subpic.h transform_ops.h\ + mmal_piccpy_neon.S libmmal_vout_plugin_la_CFLAGS = $(AM_CFLAGS) -libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm +libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm -lX11 -lXrandr libmmal_vout_plugin_la_LIBADD = $(LIBS_mmal) -mmal_LTLIBRARIES = libmmal_vout_plugin.la +mmal_LTLIBRARIES += libmmal_vout_plugin.la -libmmal_codec_plugin_la_SOURCES = codec.c +libmmal_codec_plugin_la_SOURCES = codec.c mmal_cma.c mmal_picture.c subpic.c\ + mmal_cma.h mmal_cma_int.h mmal_picture.h subpic.h transform_ops.h\ + blend_rgba_neon.S mmal_piccpy_neon.S libmmal_codec_plugin_la_CFLAGS = $(AM_CFLAGS) libmmal_codec_plugin_la_LDFLAGS = $(AM_LDFLAGS) libmmal_codec_plugin_la_LIBADD = $(LIBS_mmal) mmal_LTLIBRARIES += libmmal_codec_plugin.la -libmmal_deinterlace_plugin_la_SOURCES = deinterlace.c mmal_picture.c +libmmal_deinterlace_plugin_la_SOURCES = deinterlace.c mmal_picture.c mmal_cma.c\ + mmal_cma.h mmal_cma_int.h mmal_picture.h transform_ops.h\ + mmal_piccpy_neon.S libmmal_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) libmmal_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) libmmal_deinterlace_plugin_la_LIBADD = $(LIBS_mmal) mmal_LTLIBRARIES += libmmal_deinterlace_plugin.la + +libmmal_converter_plugin_la_SOURCES = converter_mmal.c mmal_cma.c mmal_picture.c\ + mmal_cma.h mmal_cma_int.h mmal_picture.h transform_ops.h\ + mmal_piccpy_neon.S +libmmal_converter_plugin_la_CFLAGS = $(AM_CFLAGS) +libmmal_converter_plugin_la_LDFLAGS = $(AM_LDFLAGS) +libmmal_converter_plugin_la_LIBADD = $(LIBS_mmal) +mmal_LTLIBRARIES += libmmal_converter_plugin.la +endif + +libmmal_xsplitter_plugin_la_SOURCES = xsplitter.c +libmmal_xsplitter_plugin_la_CFLAGS = $(AM_CFLAGS) +libmmal_xsplitter_plugin_la_LDFLAGS = $(AM_LDFLAGS) +libmmal_xsplitter_plugin_la_LIBADD = $(LIBS_mmal) +mmal_LTLIBRARIES += libmmal_xsplitter_plugin.la + + +if HAVE_MMAL_AVCODEC +libmmal_avcodec_plugin_la_SOURCES = mmal_avcodec.c mmal_cma_drmprime.c mmal_cma.c\ + mmal_picture.c\ + mmal_cma.h mmal_cma_int.h mmal_cma_drmprime.h mmal_picture.h transform_ops.h\ + mmal_piccpy_neon.S +libmmal_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) +libmmal_avcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS) +libmmal_avcodec_plugin_la_LIBADD = $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(LIBS_mmal) +mmal_LTLIBRARIES += libmmal_avcodec_plugin.la +endif + diff --git a/modules/hw/mmal/blend_rgba_neon.S b/modules/hw/mmal/blend_rgba_neon.S new file mode 100644 index 0000000000..d17bf6b9aa --- /dev/null +++ b/modules/hw/mmal/blend_rgba_neon.S @@ -0,0 +1,189 @@ +#include "../../arm_neon/asm.S" + .align 16 + .arch armv7-a + .syntax unified +#if HAVE_AS_FPU_DIRECTIVE + .fpu neon-vfpv4 +#endif + +@ 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 +function 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 +function blend_bgrx_rgba_neon + blend_main d18, d17, d16, d19, d20, d21, d22, d23 + + + diff --git a/modules/hw/mmal/blend_rgba_neon.h b/modules/hw/mmal/blend_rgba_neon.h new file mode 100644 index 0000000000..ac0810855e --- /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 + diff --git a/modules/hw/mmal/blend_test.c b/modules/hw/mmal/blend_test.c new file mode 100644 index 0000000000..1612a0e07c --- /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; +} + diff --git a/modules/hw/mmal/codec.c b/modules/hw/mmal/codec.c index 99ff21ca70..a6ee2919d3 100644 --- a/modules/hw/mmal/codec.c +++ b/modules/hw/mmal/codec.c @@ -26,267 +26,448 @@ #include "config.h" #endif +#include + #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); +#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.") -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_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; +#define MMAL_DECODE_ENABLE_NAME "mmal-decode-enable" +#define MMAL_DECODE_ENABLE_TEXT N_("Enable mmal decode even if normally disabled") +#define MMAL_DECODE_ENABLE_LONGTEXT N_("Enable mmal decode even if normally disabled. MMAL decode is normally disabled on Pi4 or later.") + +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); +typedef struct supported_mmal_enc_s { + struct { + MMAL_PARAMETER_HEADER_T header; + MMAL_FOURCC_T encodings[64]; + } supported; + int n; +} supported_mmal_enc_t; -/* 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); +#define SUPPORTED_MMAL_ENC_INIT \ +{ \ + {{MMAL_PARAMETER_SUPPORTED_ENCODINGS, sizeof(((supported_mmal_enc_t *)0)->supported)}, {0}}, \ + -1 \ +} -static int OpenDecoder(decoder_t *dec) -{ - int ret = VLC_SUCCESS; - decoder_sys_t *sys; - MMAL_PARAMETER_UINT32_T extra_buffers; - MMAL_STATUS_T status; +static supported_mmal_enc_t supported_decode_in_enc = SUPPORTED_MMAL_ENC_INIT; - if (dec->fmt_in.i_codec != VLC_CODEC_MPGV && - dec->fmt_in.i_codec != VLC_CODEC_H264) - return VLC_EGENERIC; +static bool is_enc_supported(supported_mmal_enc_t * const support, const MMAL_FOURCC_T fcc) +{ + int i; - sys = calloc(1, sizeof(decoder_sys_t)); - if (!sys) { - ret = VLC_ENOMEM; - goto out; + 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); +} + +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->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 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; +} - 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; - - 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; + } + } + + format->es->video.par = rationalize_sar(n, d); + } - if (sys->component && sys->component->is_enabled) - mmal_component_disable(sys->component); + if (sys->output_format != NULL) + mmal_format_free(sys->output_format); - if (sys->input_pool) - mmal_pool_destroy(sys->input_pool); + 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_format) - mmal_format_free(sys->output_format); + // 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->output_pool) - mmal_pool_destroy(sys->output_pool); - if (sys->component) - mmal_component_release(sys->component); - vlc_sem_destroy(&sys->sem); - free(sys); +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; + } - bcm_host_deinit(); + 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 +481,9 @@ static int change_output_format(decoder_t *dec) } 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 +493,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 +502,10 @@ port_reset: goto out; } - if (sys->opaque) { - sys->output->buffer_num = NUM_DECODER_BUFFER_HEADERS; - pool_size = NUM_DECODER_BUFFER_HEADERS; - } else { - sys->output->buffer_num = __MAX(sys->output->buffer_num_recommended, - MIN_NUM_BUFFERS_IN_TRANSIT); - pool_size = sys->output->buffer_num; - } - + sys->output->buffer_num = NUM_DECODER_BUFFER_HEADERS; sys->output->buffer_size = sys->output->buffer_size_recommended; - status = mmal_port_enable(sys->output, output_port_cb); + status = mmal_port_enable(sys->output, decoder_output_cb); if (status != MMAL_SUCCESS) { msg_Err(dec, "Failed to enable output port (status=%"PRIx32" %s)", status, mmal_status_to_string(status)); @@ -338,25 +514,14 @@ port_reset: } if (!atomic_load(&sys->started)) { - if (!sys->opaque) { - sys->output_pool = mmal_port_pool_create(sys->output, pool_size, 0); - msg_Dbg(dec, "Created output pool with %d pictures", sys->output_pool->headers_num); - } - atomic_store(&sys->started, true); /* we need one picture from vout for each buffer header on the output * port */ - dec->i_extra_picture_buffers = pool_size; - - /* remove what VLC core reserves as it is part of the pool_size - * already */ - if (dec->fmt_in.i_codec == VLC_CODEC_H264) - dec->i_extra_picture_buffers -= 19; - else - dec->i_extra_picture_buffers -= 3; - + dec->i_extra_picture_buffers = 10; +#if TRACE_ALL msg_Dbg(dec, "Request %d extra pictures", dec->i_extra_picture_buffers); +#endif } apply_fmt: @@ -366,8 +531,8 @@ apply_fmt: dec->fmt_out.video.i_y_offset = sys->output->format->es->video.crop.y; dec->fmt_out.video.i_visible_width = sys->output->format->es->video.crop.width; dec->fmt_out.video.i_visible_height = sys->output->format->es->video.crop.height; - dec->fmt_out.video.i_sar_num = sys->output->format->es->video.par.num; - dec->fmt_out.video.i_sar_den = sys->output->format->es->video.par.den; + dec->fmt_out.video.i_sar_num = sys->output_format->es->video.par.num; // SAR can be killed by commit + dec->fmt_out.video.i_sar_den = sys->output_format->es->video.par.den; dec->fmt_out.video.i_frame_rate = sys->output->format->es->video.frame_rate.num; dec->fmt_out.video.i_frame_rate_base = sys->output->format->es->video.frame_rate.den; @@ -382,12 +547,19 @@ apply_fmt: sys->b_progressive = (interlace_type.eMode == MMAL_InterlaceProgressive); sys->b_top_field_first = sys->b_progressive ? true : (interlace_type.eMode == MMAL_InterlaceFieldsInterleavedUpperFirst); +#if TRACE_ALL msg_Dbg(dec, "Detected %s%s video (%d)", sys->b_progressive ? "progressive" : "interlaced", sys->b_progressive ? "" : (sys->b_top_field_first ? " tff" : " bff"), interlace_type.eMode); +#endif } + // Tell the rest of the world we have changed format + vlc_mutex_lock(&sys->pic_lock); + ret = decoder_UpdateVideoFormat(dec); + vlc_mutex_unlock(&sys->pic_lock); + out: mmal_format_free(sys->output_format); sys->output_format = NULL; @@ -395,144 +567,85 @@ out: return ret; } -static int send_output_buffer(decoder_t *dec) +static MMAL_STATUS_T +set_extradata_and_commit(decoder_t * const dec, decoder_sys_t * const sys) { - decoder_sys_t *sys = dec->p_sys; - MMAL_BUFFER_HEADER_T *buffer; - picture_sys_t *p_sys; - picture_t *picture = NULL; MMAL_STATUS_T status; - unsigned buffer_size = 0; - int ret = 0; - - if (!sys->output->is_enabled) - return VLC_EGENERIC; - - /* If local output pool is allocated, use it - this is only the case for - * non-opaque modes */ - if (sys->output_pool) { - buffer = mmal_queue_get(sys->output_pool->queue); - if (!buffer) { - msg_Warn(dec, "Failed to get new buffer"); - return VLC_EGENERIC; - } - } - - if (!decoder_UpdateVideoFormat(dec)) - picture = decoder_NewPicture(dec); - if (!picture) { - msg_Warn(dec, "Failed to get new picture"); - ret = -1; - goto err; - } - - p_sys = picture->p_sys; - for (int i = 0; i < picture->i_planes; i++) - buffer_size += picture->p[i].i_lines * picture->p[i].i_pitch; - - if (sys->output_pool) { - mmal_buffer_header_reset(buffer); - buffer->alloc_size = sys->output->buffer_size; - if (buffer_size < sys->output->buffer_size) { - msg_Err(dec, "Retrieved picture with too small data block (%d < %d)", - buffer_size, sys->output->buffer_size); - ret = VLC_EGENERIC; - goto err; - } - - if (!sys->opaque) - buffer->data = picture->p[0].p_pixels; - } else { - buffer = p_sys->buffer; - if (!buffer) { - msg_Warn(dec, "Picture has no buffer attached"); - picture_Release(picture); - return VLC_EGENERIC; - } - buffer->data = p_sys->buffer->data; - } - buffer->user_data = picture; - buffer->cmd = 0; - status = mmal_port_send_buffer(sys->output, buffer); + status = mmal_port_format_commit(sys->input); if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to send buffer to output port (status=%"PRIx32" %s)", - status, mmal_status_to_string(status)); - ret = -1; - goto err; - } - atomic_fetch_add(&sys->output_in_transit, 1); - - return ret; - -err: - if (picture) - picture_Release(picture); - if (sys->output_pool && buffer) { - buffer->data = NULL; - mmal_buffer_header_release(buffer); + msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)", + sys->input->name, status, mmal_status_to_string(status)); } - return ret; + return status; } -static void fill_output_port(decoder_t *dec) +static MMAL_STATUS_T decoder_send_extradata(decoder_t * const dec, decoder_sys_t *const sys) { - decoder_sys_t *sys = dec->p_sys; - - unsigned max_buffers_in_transit = 0; - int buffers_available = 0; - int buffers_to_send = 0; - int i; - - if (sys->output_pool) { - max_buffers_in_transit = __MAX(sys->output_pool->headers_num, - MIN_NUM_BUFFERS_IN_TRANSIT); - buffers_available = mmal_queue_length(sys->output_pool->queue); - } else { - max_buffers_in_transit = NUM_DECODER_BUFFER_HEADERS; - buffers_available = NUM_DECODER_BUFFER_HEADERS - atomic_load(&sys->output_in_transit); + 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; + + 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); +#if TRACE_ALL + msg_Dbg(dec, "%s: <<<", __func__); +#endif - while (atomic_load(&sys->output_in_transit) || - atomic_load(&sys->input_in_transit)) - vlc_sem_wait(&sys->sem); + if (!sys->b_flushed) { + mmal_port_disable(sys->input); + mmal_port_disable(sys->output); + // We can leave the input disabled, but we want the output enabled + // in order to sink any buffers returning from other modules + mmal_port_enable(sys->output, decoder_output_cb); + sys->b_flushed = true; + } +#if TRACE_ALL + msg_Dbg(dec, "%s: >>>", __func__); +#endif } static int decode(decoder_t *dec, block_t *block) { decoder_sys_t *sys = dec->p_sys; MMAL_BUFFER_HEADER_T *buffer; - bool need_flush = false; uint32_t len; - uint32_t flags = 0; + uint32_t flags = MMAL_BUFFER_HEADER_FLAG_FRAME_START; MMAL_STATUS_T status; +#if TRACE_ALL + msg_Dbg(dec, "<<< %s: %lld/%lld", __func__, block == NULL ? -1LL : block->i_dts, block == NULL ? -1LL : block->i_pts); +#endif + + if (sys->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 +654,50 @@ static int decode(decoder_t *dec, block_t *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 +708,21 @@ static int decode(decoder_t *dec, block_t *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 +733,1835 @@ static int decode(decoder_t *dec, block_t *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); + static bool is_kms = false; + static bool kms_checked = false; + + if (!kms_checked) { + // This is true for Pi3 and earlier + // On Pi4 and later it is almost certainly better to be using V4L2 + is_kms = !rpi_use_qpu_deinterlace(); + kms_checked = true; + } + if (is_kms && !var_InheritBool(dec, MMAL_DECODE_ENABLE_NAME)) { +#if TRACE_ALL + msg_Dbg(dec, "%s: <<< Disabled: Is KMS", __func__); +#endif + return VLC_EGENERIC; + } + +#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); + 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/%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 (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); } - 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); + } - format = mmal_format_alloc(); - mmal_format_full_copy(format, fmt->format); + mmal_buffer_header_release(buf); +} - if (sys->opaque) - format->encoding = MMAL_ENCODING_OPAQUE; - sys->output_format = format; +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; - mmal_buffer_header_release(buffer); - } else { - mmal_buffer_header_release(buffer); +#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; + } + + if (p_filter->fmt_out.video.i_sar_den == 0 || p_filter->fmt_out.video.i_sar_num == 0) { + out_pic->format.i_sar_den = 1; + out_pic->format.i_sar_num = 1; + } + else { + out_pic->format.i_sar_den = p_filter->fmt_out.video.i_sar_den; + out_pic->format.i_sar_num = p_filter->fmt_out.video.i_sar_num; + } + + 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 = 0; + MMAL_FOURCC_T enc_out = filter_enc_out(&p_filter->fmt_out.video); + const MMAL_FOURCC_T enc_in = filter_enc_in(&p_filter->fmt_in.video); + bool use_resizer; + bool use_isp; + int gpu_mem; + + // 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; + } + + + 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 { + 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, + &p_filter->fmt_in.video, + vis_mmal_rect(&p_filter->fmt_out.video), + x_offset, y_offset, + alpha, + dst != sys->last_dst || !hw_mmal_pic_has_sub_bufs(dst)); + if (buf == NULL) { + msg_Err(p_filter, "Failed to allocate vzc buffer for subpic"); + return; + } + + 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; + const video_format_t *const src_fmt = &p_filter->fmt_in.video; // Format here has different visible params to pic + const video_format_t *const dst_fmt = &p_filter->fmt_out.video; + int width = src_fmt->i_visible_width; + int height = src_fmt->i_visible_height; + blend_neon_fn *const blend_fn = (blend_neon_fn * )p_filter->p_sys; + +#if TRACE_ALL + msg_Dbg(p_filter, "%s (%d,%d:%d) pic=%p (%dx%d/%dx%d), pts=%lld, force=%d, filter in %dx%d/%dx%d", __func__, x_offset, y_offset, alpha, + src_pic, src_pic->format.i_width, src_pic->format.i_height, src_pic->format.i_visible_width, src_pic->format.i_visible_height, + src_pic->date, src_pic->b_force, + src_fmt->i_width, src_fmt->i_height, src_fmt->i_visible_width, src_fmt->i_visible_height); +#endif + + if (alpha == 0) + return; + + x_offset += dst_fmt->i_x_offset; + y_offset += dst_fmt->i_y_offset; + + // Deal with R/B overrun + if (x_offset + width >= (int)(dst_fmt->i_x_offset + dst_fmt->i_visible_width)) + width = dst_fmt->i_x_offset + dst_fmt->i_visible_width - x_offset; + if (y_offset + height >= (int)(dst_fmt->i_y_offset + dst_fmt->i_visible_height)) + height = dst_fmt->i_y_offset + dst_fmt->i_visible_height - y_offset; + + if (width <= 0 || height <= 0) + return; + + // *** L/U overrun + + s_data = src_pic->p[0].p_pixels + + src_pic->p[0].i_pixel_pitch * src_fmt->i_x_offset + + src_pic->p[0].i_pitch * src_fmt->i_y_offset; + d_data = dst_pic->p[0].p_pixels + + dst_pic->p[0].i_pixel_pitch * x_offset + + dst_pic->p[0].i_pitch * y_offset; + + + do { + blend_fn(d_data, s_data, alpha, width); + s_data += src_pic->p[0].i_pitch; + d_data += dst_pic->p[0].i_pitch; + } while (--height > 0); +} + +static void CloseBlendNeon(vlc_object_t *object) +{ + VLC_UNUSED(object); +} + +static int OpenBlendNeon(vlc_object_t *object) +{ + filter_t * const p_filter = (filter_t *)object; + const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma; + MMAL_FOURCC_T mfcc_src = vlc_to_mmal_video_fourcc(&p_filter->fmt_in.video); + MMAL_FOURCC_T mfcc_dst = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video); + blend_neon_fn * blend_fn = (blend_neon_fn *)0; + + // Obviously can't use this if we have no neon + if (!vlc_CPU_ARM_NEON()) + return VLC_EGENERIC; + + // Non-alpha RGB only for dest + if (vfcc_dst != VLC_CODEC_RGB32) + return VLC_EGENERIC; + + // Check we have appropriate blend fn (mmal doesn't have a non-alpha RGB32) + switch (mfcc_src) { + case MMAL_ENCODING_RGBA: + if (mfcc_dst == MMAL_ENCODING_RGBA) + blend_fn = blend_rgbx_rgba_neon; + else if (mfcc_dst == MMAL_ENCODING_BGRA) + blend_fn = blend_bgrx_rgba_neon; + break; + + case MMAL_ENCODING_BGRA: + if (mfcc_dst == MMAL_ENCODING_BGRA) + blend_fn = blend_rgbx_rgba_neon; + else if (mfcc_dst == MMAL_ENCODING_RGBA) + blend_fn = blend_bgrx_rgba_neon; + break; + + default: + break; + } + + 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) + add_bool(MMAL_DECODE_ENABLE_NAME, false, MMAL_DECODE_ENABLE_TEXT, MMAL_DECODE_ENABLE_LONGTEXT, true) + set_callbacks(OpenDecoder, CloseDecoder) + + add_submodule() + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VFILTER ) + set_shortname(N_("MMAL resizer")) + set_description(N_("MMAL resizing conversion filter")) + add_shortcut("mmal_converter") + set_capability( "video converter", 900 ) + add_bool(MMAL_RESIZE_NAME, /* default */ false, MMAL_RESIZE_TEXT, MMAL_RESIZE_LONGTEXT, /* advanced option */ false) + add_bool(MMAL_ISP_NAME, /* default */ false, MMAL_ISP_TEXT, MMAL_ISP_LONGTEXT, /* advanced option */ false) + set_callbacks(OpenConverter, CloseConverter) + +#if OPT_TO_FROM_ZC + add_submodule() + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VFILTER ) + set_shortname(N_("MMAL to ZC")) + set_description(N_("MMAL conversion to ZC filter")) + add_shortcut("mmal_to_zc") + set_capability( "video converter", 901 ) + set_callbacks(OpenConverterToZc, CloseConverterToZc) + + add_submodule() + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VFILTER ) + set_shortname(N_("MMAL from ZC")) + set_description(N_("MMAL conversion from ZC filter")) + add_shortcut("mmal_from_zc") + set_capability( "video converter", 902 ) + set_callbacks(OpenConverterFromZc, CloseConverterFromZc) +#endif + + add_submodule() + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VFILTER ) + set_description(N_("Video pictures blending for MMAL")) + add_shortcut("mmal_blend") + set_capability("video blending", 120) + set_callbacks(OpenBlendMmal, CloseBlendMmal) + + add_submodule() + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VFILTER ) + set_description(N_("Video pictures blending for neon")) + add_shortcut("neon_blend") + set_capability("video blending", 110) + set_callbacks(OpenBlendNeon, CloseBlendNeon) + +vlc_module_end() + + diff --git a/modules/hw/mmal/converter_mmal.c b/modules/hw/mmal/converter_mmal.c new file mode 100644 index 0000000000..44333c43f8 --- /dev/null +++ b/modules/hw/mmal/converter_mmal.c @@ -0,0 +1,480 @@ +#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); + + *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_SAND10: // ISP only + return VLC_CODEC_MMAL_ZC_I420; + case VLC_CODEC_MMAL_ZC_SAND30: // HVS only + case VLC_CODEC_MMAL_ZC_RGB32: + return VLC_CODEC_MMAL_ZC_RGB32; // HVS can't generate YUV of any sort + case VLC_CODEC_MMAL_ZC_SAND8: + return VLC_CODEC_MMAL_ZC_SAND8; + default: + break; + } + return 0; +} + + +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 () + diff --git a/modules/hw/mmal/deinterlace.c b/modules/hw/mmal/deinterlace.c index 4b08eee9b6..e535dbe07f 100644 --- 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 *filtered_pictures; - vlc_sem_t sem; + MMAL_QUEUE_T * out_q; - atomic_bool started; + // Bind this lot somehow into ppr???? + bool is_cma; + cma_buf_pool_t * cma_out_pool; + MMAL_POOL_T * out_pool; - /* statistics */ - int output_in_transit; - int input_in_transit; -}; + 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] + + vcsm_init_type_t vcsm_init_type; + +} filter_sys_t; -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; + +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; + + 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; + } } - sys->input->buffer_size = sys->input->buffer_size_recommended; - sys->input->buffer_num = sys->input->buffer_num_recommended; + return MMAL_SUCCESS; +} - 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 - }; +// 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); - 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; - } + cma_buf_t * const cb = header->user_data; + header->user_data = NULL; + cma_buf_unref(cb); // Copes fine with NULL + + 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); } - 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; + // 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; } - 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); + 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); + + // 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 - 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; + if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS) + { + msg_Err(p_filter, "Send buffer to output failed"); + goto fail; + } + out_buf = NULL; } - sys->output->buffer_num = 3; + // 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 (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 (pic_buf == NULL) + { + msg_Err(p_filter, "Pic has not attached buffer"); + goto fail; } - MMAL_PARAMETER_BOOLEAN_T zero_copy = { - { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, - 1 - }; + picture_Release(p_pic); - 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; + // 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; - if (!sys->output->is_enabled) { - ret = VLC_EGENERIC; - goto out; - } + msg_Dbg(filter, "<<< %s", __func__); - picture = filter_NewPicture(filter); - if (!picture) { - msg_Warn(filter, "Failed to get new picture"); - ret = -1; - goto out; - } - 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; + 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; - buffer = picture->p_sys->buffer; - buffer->user_data = picture; - buffer->cmd = 0; + sys = calloc(1, sizeof(filter_sys_t)); + if (!sys) + return VLC_ENOMEM; + filter->p_sys = sys; - mmal_picture_lock(picture); + sys->seq_in = 1; + sys->seq_out = 15; + sys->is_cma = is_cma_buf_pic_chroma(filter->fmt_out.video.i_chroma); - status = mmal_port_send_buffer(sys->output, buffer); - 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); + if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) { + msg_Err(filter, "VCSM init failed"); + goto fail; } -out: - return ret; -} + if (!rpi_use_qpu_deinterlace()) + { + 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"); + } + } -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; - - if (buffers_to_send > buffers_available) - buffers_to_send = buffers_available; - -#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 (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; } -} -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; + { + 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"); + } - fill_output_port(filter); + 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; + } - buffer = picture->p_sys->buffer; - buffer->user_data = picture; - buffer->pts = picture->date; - buffer->cmd = 0; + { + 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 } + }; - if (!picture->p_sys->displayed) { - status = mmal_port_send_buffer(sys->input, buffer); + status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr); 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; + msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)", + MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); + 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->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)); + goto fail; + } - msg_Dbg(filter, "flush deinterlace filter"); + 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); - 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); + es_format_Copy(&filter->fmt_out, &filter->fmt_in); + if (!sys->half_rate) + filter->fmt_out.video.i_frame_rate *= 2; - 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); + 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; - 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); + if ((sys->in_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) + { + msg_Err(filter, "Failed to create input pool"); + goto fail; } - atomic_store(&sys->started, false); - msg_Dbg(filter, "flush: done"); -} -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; + 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; + } - if (buffer->cmd == MMAL_EVENT_ERROR) { - status = *(uint32_t *)buffer->data; - msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status, - mmal_status_to_string(status)); + 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; } - mmal_buffer_header_release(buffer); -} -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; + if ((sys->out_q = mmal_queue_create()) == NULL) + { + msg_Err(filter, "Failed to create out Q"); + goto fail; + } + + sys->output = sys->component->output[0]; + mmal_format_full_copy(sys->output->format, sys->input->format); - if (picture) { - picture_Release(picture); - } else { - msg_Warn(filter, "Got buffer without picture on input port - OOOPS"); - mmal_buffer_header_release(buffer); + 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; + } - atomic_fetch_sub(&sys->input_in_transit, 1); - vlc_sem_post(&sys->sem); -} + // 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 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; - - 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); + port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, true); + + if ((status = mmal_port_format_commit(sys->output)) != MMAL_SUCCESS) + { + msg_Err(filter, "Output port format commit failed"); + goto fail; } - 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); + sys->output->buffer_num = 30; + sys->output->buffer_size = sys->output->buffer_size_recommended; + + // 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; + } } + + 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; + } + + filter->pf_video_filter = deinterlace; + filter->pf_flush = di_flush; + return 0; + +fail: + CloseMmalDeinterlace(filter); + return ret; } + +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() + + diff --git a/modules/hw/mmal/mmal_avcodec.c b/modules/hw/mmal/mmal_avcodec.c new file mode 100644 index 0000000000..e32c2819f4 --- /dev/null +++ b/modules/hw/mmal/mmal_avcodec.c @@ -0,0 +1,2302 @@ +/***************************************************************************** + * video.c: video decoder using the libavcodec library + ***************************************************************************** + * Copyright (C) 1999-2001 VLC authors and VideoLAN + * $Id$ + * + * Authors: Laurent Aimar + * Gildas Bazin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#include "config.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 16, 101 ) ) +#include +#endif + +//#include "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_cma_drmprime.h" +#include "mmal_picture.h" + +#include + +#define TRACE_ALL 0 + +#define BUFFERS_IN_FLIGHT 5 // Default max value for in flight buffers +#define BUFFERS_IN_FLIGHT_UHD 3 // Fewer if very big + +#define MMAL_AVCODEC_BUFFERS "mmal-avcodec-buffers" +#define MMAL_AVCODEC_BUFFERS_TEXT N_("In flight buffer count before blocking.") +#define MMAL_AVCODEC_BUFFERS_LONGTEXT N_("In flight buffer count before blocking. " \ +"Beware that incautious changing of this can lead to lockup. " \ +"Zero will disable the module.") + + +// Fwd declarations required due to wanting to avoid reworking the original +// code too much +static void MmalAvcodecCloseDecoder( vlc_object_t *obj ); + + +/***************************************************************************** + * decoder_sys_t : decoder descriptor + *****************************************************************************/ +struct decoder_sys_t +{ + 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; + bool use_drm; + // Debug + decoder_t * p_dec; +}; + + +static vlc_fourcc_t +ZcFindVlcChroma(const int i_ffmpeg_chroma, const int i_ffmpeg_sw_chroma) +{ + int c = i_ffmpeg_chroma; + + if (c == AV_PIX_FMT_DRM_PRIME) + c = i_ffmpeg_sw_chroma; + + switch (c) + { + // This is all we claim to deal with + // In theory RGB should be doable within our current framework + case AV_PIX_FMT_YUV420P: + return VLC_CODEC_MMAL_ZC_I420; + case AV_PIX_FMT_SAND128: + case AV_PIX_FMT_RPI4_8: + return VLC_CODEC_MMAL_ZC_SAND8; + case AV_PIX_FMT_SAND64_10: + return VLC_CODEC_MMAL_ZC_SAND10; + case AV_PIX_FMT_RPI4_10: + return VLC_CODEC_MMAL_ZC_SAND30; + default: + break; + } + return 0; +} + +// Pix Fmt conv for MMal +// video_fromat from ffmpeg pic_fmt +static int +ZcGetVlcChroma( video_format_t *fmt, int i_ffmpeg_chroma, const int i_ffmpeg_sw_chroma) +{ + fmt->i_rmask = 0; + fmt->i_gmask = 0; + fmt->i_bmask = 0; + fmt->i_chroma = ZcFindVlcChroma(i_ffmpeg_chroma, i_ffmpeg_sw_chroma); + + return fmt->i_chroma == 0 ? -1 : 0; +} + + +// 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); + decoder_t * const p_dec = av_rpi_zc_in_use(p_context) ? NULL : p_context->opaque; + const bool use_drm = (p_dec != NULL) && p_dec->p_sys->use_drm; + + for (size_t i = 0; pi_fmt[i] != AV_PIX_FMT_NONE; i++) + { + if (use_drm && pi_fmt[i] == AV_PIX_FMT_DRM_PRIME) + return pi_fmt[i]; + + if (!use_drm && pi_fmt[i] != AV_PIX_FMT_DRM_PRIME && + ZcFindVlcChroma(pi_fmt[i], p_context->sw_pix_fmt) != 0) + return pi_fmt[i]; + } + return swfmt; +} + + +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 inline int pitch_from_mod(const uint64_t mod) +{ + return fourcc_mod_broadcom_mod(mod) != DRM_FORMAT_MOD_BROADCOM_SAND128 ? 0 : + fourcc_mod_broadcom_param(mod); +} + +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; + } + + if (frame->format == AV_PIX_FMT_DRM_PRIME) + { + const AVDRMFrameDescriptor * const desc = (AVDRMFrameDescriptor*)frame->data[0]; + const AVDRMLayerDescriptor * layer = desc->layers + 0; + const AVDRMPlaneDescriptor * plane = layer->planes + 0; + const uint64_t mod = desc->objects[0].format_modifier; + const int set_pitch = pitch_from_mod(mod); + int nb_plane = 0; + + if (desc->nb_objects != 1) + return VLC_EGENERIC; + + for (int i = 0; i != pic->i_planes; ++i) + { + if (nb_plane >= layer->nb_planes) + { + ++layer; + plane = layer->planes + 0; + nb_plane = 0; + } + + pic->p[i] = (plane_t){ + .p_pixels = data + plane->offset, + .i_lines = frame->height >> hs[i], + .i_pitch = set_pitch != 0 ? set_pitch : plane->pitch, + .i_pixel_pitch = pb[i], + .i_visible_lines = av_frame_cropped_height(frame) >> hs[i], + .i_visible_pitch = av_frame_cropped_width(frame) >> ws[i] + }; + + ++plane; + ++nb_plane; + } + + // Calculate lines from gap between planes + // This will give us an accurate "height" for later use by MMAL + for (int i = 0; i + 1 < pic->i_planes; ++i) + pic->p[i].i_lines = (pic->p[i + 1].p_pixels - pic->p[i].p_pixels) / pic->p[i].i_pitch; + } + else + { + uint8_t * frame_end = frame->data[0] + cma_buf_size(cb); + for (int i = 0; i != pic->i_planes; ++i) { + // Calculate lines from gap between planes + // This will give us an accurate "height" for later use by MMAL + const int lines = ((i + 1 == pic->i_planes ? frame_end : frame->data[i + 1]) - + frame->data[i]) / frame->linesize[i]; + pic->p[i] = (plane_t){ + .p_pixels = data + (frame->data[i] - frame->data[0]), + .i_lines = lines, + .i_pitch = av_rpi_is_sand_frame(frame) ? av_rpi_sand_frame_stride2(frame) : frame->linesize[i], + .i_pixel_pitch = pb[i], + .i_visible_lines = av_frame_cropped_height(frame) >> hs[i], + .i_visible_pitch = av_frame_cropped_width(frame) >> ws[i] + }; + } + } + return 0; +} + + +//============================================================================ +// +// 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_use_pi3_hevc() ? "hevc_rpi" : "hevc" ; + msg_Info(p_dec, "Looking for HEVC decoder '%s'", psz_namecodec); + p_codec = avcodec_find_decoder_by_name(psz_namecodec); + } + } + + 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 (!p_dec->p_sys->use_drm && + 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 + if ((fmt->i_chroma = ZcFindVlcChroma(pix_fmt, sw_pix_fmt)) == 0) { + msg_Info(dec, "Find chroma fail"); + 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 bool has_legacy_rpivid() +{ + static int cached_state = 0; + + if (cached_state == 0) + { + struct stat buf; + cached_state = stat("/dev/rpivid-intcmem", &buf) != 0 ? -1 : 1; + } + return cached_state > 0; +} + + +static int MmalAvcodecOpenDecoder( vlc_object_t *obj ) +{ + decoder_t *p_dec = (decoder_t *)obj; + const AVCodec *p_codec; + bool use_zc; + bool use_drm; + AVBufferRef * hw_dev_ctx = NULL; + + int extra_buffers = var_InheritInteger(p_dec, MMAL_AVCODEC_BUFFERS); + + 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; + } + + use_zc = has_legacy_rpivid(); + use_drm = !use_zc; // ** At least for the moment + const vcsm_init_type_t vcsm_type = use_zc ? cma_vcsm_init() : VCSM_INIT_NONE; + const int vcsm_size = vcsm_type == VCSM_INIT_NONE ? 0 : + vcsm_type == VCSM_INIT_LEGACY ? hw_mmal_get_gpu_mem() : 512 << 20; + +#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 && !use_drm) + return VLC_EGENERIC; + + if (use_drm) + { + int dev_type = av_hwdevice_find_type_by_name("drm"); + + if (dev_type == AV_HWDEVICE_TYPE_NONE || + av_hwdevice_ctx_create(&hw_dev_ctx, dev_type, NULL, NULL, 0)) + { + msg_Warn(p_dec, "Failed to create drm hw device context"); + use_drm = false; + } + } + + +#if 1 + if (use_zc && + ((p_dec->fmt_in.i_codec != VLC_CODEC_HEVC && + (vcsm_type == VCSM_INIT_CMA || vcsm_size < (96 << 20))) || + (p_dec->fmt_in.i_codec == VLC_CODEC_HEVC && + vcsm_size < (128 << 20)))) + { + cma_vcsm_exit(vcsm_type); + return VLC_EGENERIC; + } +#endif + + AVCodecContext *p_context = ZcFfmpeg_AllocContext( p_dec, &p_codec ); + if( p_context == NULL ) + { + av_buffer_unref(&hw_dev_ctx); + cma_vcsm_exit(vcsm_type); + return VLC_EGENERIC; + } + p_context->hw_device_ctx = hw_dev_ctx; + hw_dev_ctx = NULL; + + 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; + p_sys->use_drm = use_drm; + 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, p_context->sw_pix_fmt) != VLC_SUCCESS) + { + /* we are doomed. but not really, because most codecs set their pix_fmt later on */ +// p_dec->fmt_out.i_codec = VLC_CODEC_I420; + p_dec->fmt_out.i_codec = VLC_CODEC_MMAL_ZC_I420; + } + p_dec->fmt_out.i_codec = p_dec->fmt_out.video.i_chroma; + + 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 (use_drm) + p_sys->cma_pool = cma_drmprime_pool_new(p_sys->cma_in_flight_max, p_sys->cma_in_flight_max, false, "drm_avcodec"); + else if (use_zc) + p_sys->cma_pool = cma_buf_pool_new(p_sys->cma_in_flight_max, p_sys->cma_in_flight_max, false, "mmal_avcodec"); + + if (p_sys->cma_pool == 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; + cma_buf_t * cb = 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->format != AV_PIX_FMT_DRM_PRIME && !frame->linesize[0] ) || + ( p_dec->b_frame_drop_allowed && (frame->flags & AV_FRAME_FLAG_CORRUPT) && + !p_sys->b_show_corrupted ) ) + { + 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 + if (lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, + p_context->sw_pix_fmt) != 0) + { + msg_Err(p_dec, "Failed to update format"); + goto fail; + } + + if ((p_pic = decoder_NewPicture(p_dec)) == NULL) + { + msg_Err(p_dec, "Failed to allocate pic"); + goto fail; + } + + if (p_sys->use_drm) + { + cb = cma_drmprime_pool_alloc_buf(p_sys->cma_pool, frame); + if (cb == NULL) + { + msg_Err(p_dec, "Failed to alloc CMA buf from DRM_PRIME"); + goto fail; + } + } + else + { + cb = cma_buf_ref(av_rpi_zc_buf_v(frame->buf[0])); + if (cb == NULL) + { + msg_Err(p_dec, "Frame has no attached CMA buffer"); + goto fail; + } + } + + if (cma_buf_pic_attach(cb, p_pic) != 0) + { + cma_buf_unref(cb); // Undo the in_flight + char dbuf0[5]; + msg_Err(p_dec, "Failed to attach bufs to pic: fmt=%s", str_fourcc(dbuf0, p_pic->format.i_chroma)); + goto fail; + } + cb = NULL; // Now attached to pic + + // ****** 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 ); + + if (!p_sys->use_drm) + 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() + diff --git a/modules/hw/mmal/mmal_cma.c b/modules/hw/mmal/mmal_cma.c new file mode 100644 index 0000000000..4dfedbc57c --- /dev/null +++ b/modules/hw/mmal/mmal_cma.c @@ -0,0 +1,672 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "mmal_cma.h" +#include "mmal_cma_int.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_put_fn * on_put_fn; + cma_pool_on_delete_fn * on_delete_fn; + + const char * name; +#if TRACE_ALL + int seq; +#endif +}; + +static void cma_pool_fixed_on_put_null_cb(void * v) +{ + VLC_UNUSED(v); +} + +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) +{ + p->on_put_fn(v); + + 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 +cma_pool_fixed_t* +cma_pool_fixed_new(const unsigned int pool_size, + const int flight_size, + void * const alloc_v, + cma_pool_alloc_fn * const alloc_fn, cma_pool_free_fn * const free_fn, + cma_pool_on_put_fn * const on_put_fn, cma_pool_on_delete_fn * const on_delete_fn, + const char * const name) +{ + 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_put_fn = on_put_fn; + p->on_delete_fn = on_delete_fn; + p->name = name == NULL ? NULL : strdup(name); +#if TRACE_ALL + p->seq = atomic_fetch_add(&pool_seq, 1); +#endif + + return p; +} + +// --------------------------------------------------------------------------- +// +// CMA buffer functions - uses cma_pool_fixed for pooling + +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->buf_type != CMA_BUF_TYPE_VCSM) + munmap(cb->mmap, cb->size); + else + vcsm_unlock_hdl(cb->vcsm_h); + } + if (cb->fd != -1) + close(cb->fd); + if (cb->vcsm_h != 0) + vcsm_free(cb->vcsm_h); + free(cb); +} + +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->buf_type == CMA_BUF_TYPE_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; + + switch (cbp->buf_type) + { + case CMA_BUF_TYPE_CMA: + cma_vcsm_exit(VCSM_INIT_CMA); + break; + case CMA_BUF_TYPE_VCSM: + cma_vcsm_exit(VCSM_INIT_LEGACY); + break; + default: + break; + } + free(cbp); +} + +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->buf_type = (init_type == VCSM_INIT_CMA) ? CMA_BUF_TYPE_CMA : CMA_BUF_TYPE_VCSM; + 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_pool_fixed_on_put_null_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(cma_buf_t * const cb) +{ + if (cb->vcsm_h == 0 && cb->fd != -1) + cb->vcsm_h = vcsm_import_dmabuf(cb->fd, "vlc-drmprime"); + return cb->vcsm_h; +} + +size_t cma_buf_size(const cma_buf_t * const cb) +{ + return cb->size; +} + +int cma_buf_add_context2(cma_buf_t *const cb, picture_context_t * const ctx2) +{ + if (cb->ctx2 != NULL) + return VLC_EGENERIC; + + cb->ctx2 = ctx2; + return VLC_SUCCESS; +} + +unsigned int cma_buf_vc_handle(cma_buf_t *const cb) +{ + if (cb->vc_h == 0) + { + const int vcsm_h = cma_buf_vcsm_handle(cb); + if (vcsm_h != 0) + cb->vc_h = vcsm_vc_hdl_from_hdl(vcsm_h); + } + return cb->vc_h; +} + +unsigned int cma_buf_vc_addr(cma_buf_t *const cb) +{ + if (cb->vc_addr == 0) + { + const int vcsm_h = cma_buf_vcsm_handle(cb); + if (vcsm_h != 0) + cb->vc_addr = vcsm_vc_addr_from_hdl(vcsm_h); + } + return cb->vc_addr; +} + + +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->type = CMA_BUF_TYPE_CMA; + cb->in_flight = cbp->all_in_flight; + // When 1st allocated or retrieved from the pool the block will have a + // ref count of 0 so ref here + return cma_buf_ref(cb); +} + diff --git a/modules/hw/mmal/mmal_cma.h b/modules/hw/mmal/mmal_cma.h new file mode 100644 index 0000000000..fd8ea34562 --- /dev/null +++ b/modules/hw/mmal/mmal_cma.h @@ -0,0 +1,43 @@ +#ifndef VLC_MMAL_MMAL_CMA_H_ +#define VLC_MMAL_MMAL_CMA_H_ + +#include + +struct cma_buf_s; +typedef struct cma_buf_s cma_buf_t; + +void cma_buf_in_flight(cma_buf_t * const cb); +void cma_buf_end_flight(cma_buf_t * const cb); +unsigned int cma_buf_vcsm_handle(cma_buf_t * const cb); +size_t cma_buf_size(const cma_buf_t * const cb); +int cma_buf_add_context2(cma_buf_t *const cb, picture_context_t * const ctx2); +unsigned int cma_buf_vc_handle(cma_buf_t *const cb); +unsigned int cma_buf_vc_addr(cma_buf_t *const cb); +picture_context_t * cma_buf_context2(const cma_buf_t *const cb); + +void cma_buf_unref(cma_buf_t * const cb); +cma_buf_t * cma_buf_ref(cma_buf_t * const cb); + +struct cma_buf_pool_s; +typedef struct cma_buf_pool_s cma_buf_pool_t; + +cma_buf_t * cma_buf_pool_alloc_buf(cma_buf_pool_t * const p, const size_t size); +void cma_buf_pool_cancel(cma_buf_pool_t * const cbp); +void cma_buf_pool_uncancel(cma_buf_pool_t * const cbp); +void cma_buf_pool_delete(cma_buf_pool_t * const p); +int cma_buf_pool_fill(cma_buf_pool_t * const cbp, const size_t el_size); +int cma_buf_pool_resize(cma_buf_pool_t * const cbp, + const unsigned int new_pool_size, const int new_flight_size); +cma_buf_pool_t * cma_buf_pool_new(const unsigned int pool_size, const unsigned int flight_size, + const bool all_in_flight, const char * const name); + +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_ diff --git a/modules/hw/mmal/mmal_cma_drmprime.c b/modules/hw/mmal/mmal_cma_drmprime.c new file mode 100644 index 0000000000..d58bfdd944 --- /dev/null +++ b/modules/hw/mmal/mmal_cma_drmprime.c @@ -0,0 +1,120 @@ +#include + +#include "mmal_cma.h" +#include "mmal_cma_int.h" +#include "mmal_cma_drmprime.h" + +#include +#include +#include +#include + +#define TRACE_ALL 0 + +typedef struct cma_drmprime_buf_s +{ + cma_buf_t cb; + const AVDRMFrameDescriptor *desc; + AVBufferRef * avbuf; +} cma_drmprime_buf_t; + +static void drmprime_pool_free_cb(void * v, void * el, size_t size) +{ + VLC_UNUSED(v); + VLC_UNUSED(size); + + cma_pool_delete(el); +} + +static void * drmprime_pool_alloc_cb(void * v, size_t size) +{ + cma_buf_pool_t * const cbp = v; + + cma_drmprime_buf_t * const cdb = malloc(sizeof(cma_drmprime_buf_t)); + if (cdb == NULL) + return NULL; + + *cdb = (cma_drmprime_buf_t){ + .cb = { + .ref_count = ATOMIC_VAR_INIT(0), + .cbp = cbp, + .in_flight = 0, + .size = size, + .vcsm_h = 0, + .vc_h = 0, + .fd = -1, + .mmap = MAP_FAILED, + .ctx2 = NULL + } + }; +#if TRACE_ALL + cdb->cbp->alloc_size += cdb->size; + ++cdb->cbp->alloc_n; + fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cbp->pool->seq, cbp->pool->name, cbp->alloc_n, cbp->alloc_size); +#endif + + return cdb; +} + +// Buf being returned to pool +static void drmprime_buf_pool_on_put_cb(void * v) +{ + cma_drmprime_buf_t * const cdb = v; + cdb->cb.fd = -1; + if (cdb->cb.vcsm_h != 0) + vcsm_free(cdb->cb.vcsm_h); + cdb->cb.vcsm_h = 0; + cdb->cb.vc_h = 0; + cdb->cb.vc_addr = 0; + av_buffer_unref(&cdb->avbuf); + if (cdb->cb.ctx2) { + cdb->cb.ctx2->destroy(cdb->cb.ctx2); + cdb->cb.ctx2 = NULL; + } +} + +// Pool has died - safe now to exit vcsm +static void drmprime_buf_pool_on_delete_cb(void * v) +{ + cma_buf_pool_t * const cbp = v; + free(cbp); +} + +cma_buf_pool_t * cma_drmprime_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name) +{ + cma_buf_pool_t * const cbp = calloc(1, sizeof(cma_buf_pool_t)); + if (cbp == NULL) + return NULL; + + cbp->buf_type = CMA_BUF_TYPE_DRMPRIME; + cbp->all_in_flight = all_in_flight; + + if ((cbp->pool = cma_pool_fixed_new(pool_size, flight_size, cbp, + drmprime_pool_alloc_cb, drmprime_pool_free_cb, + drmprime_buf_pool_on_put_cb, drmprime_buf_pool_on_delete_cb, + name)) == NULL) + goto fail; + return cbp; + +fail: + cma_buf_pool_delete(cbp); + return NULL; +} + +cma_buf_t * cma_drmprime_pool_alloc_buf(cma_buf_pool_t * const cbp, struct AVFrame * frame) +{ + const AVDRMFrameDescriptor* const desc = (AVDRMFrameDescriptor*)frame->data[0]; + cma_drmprime_buf_t *const cdb = (cma_drmprime_buf_t *)cma_buf_pool_alloc_buf(cbp, desc->objects[0].size); + + if (cdb == NULL) + return NULL; + + cdb->cb.type = CMA_BUF_TYPE_DRMPRIME; + cdb->cb.fd = desc->objects[0].fd; + + cdb->desc = desc; + cdb->avbuf = av_buffer_ref(frame->buf[0]); + return &cdb->cb; +} + + diff --git a/modules/hw/mmal/mmal_cma_drmprime.h b/modules/hw/mmal/mmal_cma_drmprime.h new file mode 100644 index 0000000000..2ca92a72a7 --- /dev/null +++ b/modules/hw/mmal/mmal_cma_drmprime.h @@ -0,0 +1,9 @@ +#ifndef VLC_HW_MMAL_MMAL_CMA_DRMPRIME_H_ +#define VLC_HW_MMAL_MMAL_CMA_DRMPRIME_H_ + +struct AVFrame; +cma_buf_pool_t * cma_drmprime_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name); +cma_buf_t * cma_drmprime_pool_alloc_buf(cma_buf_pool_t * const cbp, struct AVFrame * frame); + +#endif // VLC_MMAL_MMAL_CMA_H_ + diff --git a/modules/hw/mmal/mmal_cma_int.h b/modules/hw/mmal/mmal_cma_int.h new file mode 100644 index 0000000000..1ff3fa4fd0 --- /dev/null +++ b/modules/hw/mmal/mmal_cma_int.h @@ -0,0 +1,58 @@ +#ifndef VLC_HW_MMAL_MMAL_CMA_INT_H_ +#define VLC_HW_MMAL_MMAL_CMA_INT_H_ + +#include + +typedef void * cma_pool_alloc_fn(void * usr_v, size_t size); +typedef void cma_pool_free_fn(void * usr_v, void * buffer_v, size_t size); +typedef void cma_pool_on_delete_fn(void * usr_v); +typedef void cma_pool_on_put_fn(void * buffer_v); + +// Pool structure +// Ref count is held by pool owner and pool els that have been got +// Els in the pool do not count towards its ref count +struct cma_pool_fixed_s; +typedef struct cma_pool_fixed_s cma_pool_fixed_t; + +cma_pool_fixed_t* +cma_pool_fixed_new(const unsigned int pool_size, + const int flight_size, + void * const alloc_v, + cma_pool_alloc_fn * const alloc_fn, cma_pool_free_fn * const free_fn, + cma_pool_on_put_fn * const on_put_fn, cma_pool_on_delete_fn * const on_delete_fn, + const char * const name); + +typedef enum cma_buf_type_e { + CMA_BUF_TYPE_NONE = 0, + CMA_BUF_TYPE_CMA, + CMA_BUF_TYPE_VCSM, + CMA_BUF_TYPE_DRMPRIME, +} cma_buf_type_t; + +struct cma_buf_pool_s { + cma_pool_fixed_t * pool; + cma_buf_type_t buf_type; + + bool all_in_flight; + size_t alloc_n; + size_t alloc_size; +}; + +typedef struct cma_buf_s { + atomic_int ref_count; + cma_buf_type_t type; + struct cma_buf_pool_s * cbp; + bool in_flight; + size_t size; + unsigned int vcsm_h; // VCSM handle from initial alloc + unsigned int vc_h; // VC handle for ZC mmal buffers + unsigned int vc_addr; // VC addr - unused by us but wanted by FFmpeg + int fd; // dmabuf handle for GL + void * mmap; // ARM mapped address + picture_context_t *ctx2; +} cma_buf_t; + +void cma_pool_delete(cma_buf_t * const cb); + +#endif + diff --git a/modules/hw/mmal/mmal_cma_pic.h b/modules/hw/mmal/mmal_cma_pic.h new file mode 100644 index 0000000000..7d7d60d3a3 --- /dev/null +++ b/modules/hw/mmal/mmal_cma_pic.h @@ -0,0 +1,49 @@ +#include +#include +#include "mmal_cma_int.h" + +static inline int +cma_buf_fd(const cma_buf_t *const cb) +{ + return cb->fd; +} + +static inline void * +cma_buf_addr(const cma_buf_t *const cb) +{ + return cb->mmap; +} + +#define CTX_BUFS_MAX 4 +struct MMAL_BUFFER_HEADER_T; + +typedef struct pic_ctx_mmal_s { + picture_context_t cmn; // PARENT: Common els at start + + cma_buf_t * cb; + + unsigned int buf_count; + struct MMAL_BUFFER_HEADER_T * bufs[CTX_BUFS_MAX]; + +} pic_ctx_mmal_t; + +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; +} + +// Returns a pointer to the cma_buf attached to the pic +// Just a pointer - doesn't add a ref +static inline cma_buf_t * +cma_buf_pic_get(picture_t * const pic) +{ + 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; +} + + diff --git a/modules/hw/mmal/mmal_piccpy_neon.S b/modules/hw/mmal/mmal_piccpy_neon.S new file mode 100644 index 0000000000..6fec851800 --- /dev/null +++ b/modules/hw/mmal/mmal_piccpy_neon.S @@ -0,0 +1,95 @@ +#include "../../arm_neon/asm.S" + .align 16 + .arch armv7-a + .syntax unified +#if HAVE_AS_FPU_DIRECTIVE + .fpu neon-vfpv4 +#endif + +// Copy pix + +.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 + diff --git a/modules/hw/mmal/mmal_picture.c b/modules/hw/mmal/mmal_picture.c index 7ddd5b566c..c7f8b42af0 100644 --- a/modules/hw/mmal/mmal_picture.c +++ b/modules/hw/mmal/mmal_picture.c @@ -21,25 +21,1574 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ +// We would really like to use vlc_thread.h but the detach thread stuff can't be +// used here :-( +#include + +#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; -int mmal_picture_lock(picture_t *picture) + if (mmal_port_send_buffer(ppr->port, buf) == MMAL_SUCCESS) + return true; + mmal_buffer_header_release(buf); + return false; +} + +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) { - picture_sys_t *pic_sys = picture->p_sys; - MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer; + pic_ctx_mmal_t * const ctx = calloc(1, sizeof(pic_ctx_mmal_t)); - int offset = 0; - picture->p[0].p_pixels = buffer->data; - for (int i = 1; i < picture->i_planes; i++) { - offset = offset + picture->p[i - 1].i_pitch * picture->p[i - 1].i_lines; - picture->p[i].p_pixels = (ptrdiff_t)buffer->data + offset; + if (ctx == NULL) + return NULL; + + // If we have an associated ppr then ref & set appropriate callbacks + if (ppr != NULL) { + hw_mmal_port_pool_ref_acquire(ppr); + mmal_buffer_header_pre_release_cb_set(buf, buf_pre_release_cb, ppr); + buf->user_data = NULL; } - pic_sys->displayed = false; + ctx->cmn.copy = hw_mmal_pic_ctx_copy; + ctx->cmn.destroy = hw_mmal_pic_ctx_destroy; + + 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 video_format_t * src_fmt, + const MMAL_RECT_T dst_pic_rect, + const int x_offset, const int y_offset, + const unsigned int alpha, + const bool is_first) +{ + MMAL_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); + + 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) + src_fmt->i_x_offset, + .y = src_fmt->i_y_offset, + .width = src_fmt->i_visible_width, + .height = src_fmt->i_visible_height + }; + + sb->pic_rect = dst_pic_rect; + + sb->orig_dest_rect = (MMAL_RECT_T){ + .x = x_offset, + .y = y_offset, + .width = src_fmt->i_visible_width, + .height = src_fmt->i_visible_height + }; + + if (needs_copy) + { + 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) +{ + const MMAL_VIDEO_FORMAT_T * const mm_fmt = &mm_esfmt->es->video; + const MMAL_BUFFER_HEADER_VIDEO_SPECIFIC_T *const buf_vid = (buf == NULL) ? NULL : &buf->type->video; + cma_buf_t *const cb = cma_buf_pic_get(pic); + unsigned int planes = 1; + + uint8_t * const data = cma_buf_addr(cb); + if (data == NULL) { + return VLC_ENOMEM; + } + + const uint8_t * ws = shift_00; + const uint8_t * hs = shift_00; + int pb = 1; + + switch (mm_esfmt->encoding) + { + case MMAL_ENCODING_ARGB: + case MMAL_ENCODING_ABGR: + case MMAL_ENCODING_RGBA: + case MMAL_ENCODING_BGRA: + case MMAL_ENCODING_RGB32: + case MMAL_ENCODING_BGR32: + pb = 4; + break; + case MMAL_ENCODING_RGB16: + pb = 2; + break; + + case MMAL_ENCODING_I420: + ws = shift_01; + hs = shift_01; + planes = 3; + break; + + case MMAL_ENCODING_YUVUV128: + hs = shift_01; + planes = 2; + break; + + default: +// msg_Err(p_filter, "%s: Unexpected format", __func__); + return VLC_EGENERIC; + } + + // Fix up SAR if unset + if (pic->format.i_sar_den == 0 || pic->format.i_sar_num == 0) { + pic->format.i_sar_den = mm_fmt->par.den; + pic->format.i_sar_num = mm_fmt->par.num; + } + + pic->i_planes = planes; + unsigned int offset = 0; + for (unsigned int i = 0; i != planes; ++i) { + pic->p[i] = (plane_t){ + .p_pixels = data + (buf_vid != NULL ? buf_vid->offset[i] : offset), + .i_lines = mm_fmt->height >> hs[i], + .i_pitch = buf_vid != NULL ? buf_vid->pitch[i] : mm_fmt->width * pb, + .i_pixel_pitch = pb, + .i_visible_lines = mm_fmt->crop.height >> hs[i], + .i_visible_pitch = mm_fmt->crop.width >> ws[i] + }; + offset += pic->p[i].i_pitch * pic->p[i].i_lines; + } + return VLC_SUCCESS; +} + +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; +} + + +//---------------------------------------------------------------------------- + +/* Returns the type of the Pi being used +*/ +bool rpi_is_model_pi4(void) +{ + return bcm_host_is_model_pi4(); +} + +// Board types that support PI3 hybrid HEVC accel +bool rpi_use_pi3_hevc(void) +{ + const int t = bcm_host_get_model_type(); + return + t == BCM_HOST_BOARD_TYPE_PI3MODELB || + t == BCM_HOST_BOARD_TYPE_CM3 || + t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS || + t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS || + t == BCM_HOST_BOARD_TYPE_PI3MODELAPLUS || + t == BCM_HOST_BOARD_TYPE_CM3PLUS; +} + +// Board types that support qpu adv deinterlace +bool rpi_use_qpu_deinterlace(void) +{ + const int t = bcm_host_get_model_type(); + return + t == BCM_HOST_BOARD_TYPE_MODELA || + t == BCM_HOST_BOARD_TYPE_MODELB || + t == BCM_HOST_BOARD_TYPE_MODELAPLUS || + t == BCM_HOST_BOARD_TYPE_MODELBPLUS || + t == BCM_HOST_BOARD_TYPE_PI2MODELB || + t == BCM_HOST_BOARD_TYPE_CM || + t == BCM_HOST_BOARD_TYPE_CM2 || + t == BCM_HOST_BOARD_TYPE_PI3MODELB || + t == BCM_HOST_BOARD_TYPE_PI0 || + t == BCM_HOST_BOARD_TYPE_CM3 || + t == BCM_HOST_BOARD_TYPE_PI0W || + t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS || + t == BCM_HOST_BOARD_TYPE_PI3MODELAPLUS || + t == BCM_HOST_BOARD_TYPE_CM3PLUS; +} + + +// 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() || + bcm_host_is_kms_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 "???"; +} + + diff --git a/modules/hw/mmal/mmal_picture.h b/modules/hw/mmal/mmal_picture.h index 3539f2cfc8..8d04517e08 100644 --- a/modules/hw/mmal/mmal_picture.h +++ b/modules/hw/mmal/mmal_picture.h @@ -24,19 +24,278 @@ #ifndef VLC_MMAL_MMAL_PICTURE_H_ #define VLC_MMAL_MMAL_PICTURE_H_ +#include + #include #include +#include "mmal_cma_pic.h" +#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; + + +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 video_format_t * src_fmt, + const MMAL_RECT_T dst_pic_rect, + const int x_offset, const int y_offset, + const unsigned int alpha, const bool is_first); +void hw_mmal_vzc_buf_frame_size(MMAL_BUFFER_HEADER_T * const buf, + uint32_t * const pWidth, uint32_t * const pHeight); + +void hw_mmal_vzc_pool_flush(vzc_pool_ctl_t * const pc); +void hw_mmal_vzc_pool_release(vzc_pool_ctl_t * const pc); +void hw_mmal_vzc_pool_ref(vzc_pool_ctl_t * const pc); +vzc_pool_ctl_t * hw_mmal_vzc_pool_new(void); + + +static inline MMAL_RECT_T vis_mmal_rect(const video_format_t * const fmt) +{ + return (MMAL_RECT_T){ + .x = fmt->i_x_offset, + .y = fmt->i_y_offset, + .width = fmt->i_visible_width, + .height = fmt->i_visible_height + }; +} + +int cma_pic_set_data(picture_t * const pic, + const MMAL_ES_FORMAT_T * const mm_esfmt, + const MMAL_BUFFER_HEADER_T * const buf); + +// Attaches cma buf to pic +// Marks in_flight if not all_in_flight anyway +int cma_buf_pic_attach(cma_buf_t * const cb, picture_t * const pic); + +int rpi_get_model_type(void); +bool rpi_is_model_pi4(void); +bool rpi_use_pi3_hevc(void); +bool rpi_use_qpu_deinterlace(void); +bool rpi_is_fkms_active(void); + +typedef enum vcsm_init_type_e { + VCSM_INIT_NONE = 0, + VCSM_INIT_LEGACY, + VCSM_INIT_CMA +} vcsm_init_type_t; + +vcsm_init_type_t cma_vcsm_init(void); +void cma_vcsm_exit(const vcsm_init_type_t init_mode); +vcsm_init_type_t cma_vcsm_type(void); +const char * cma_vcsm_init_str(const vcsm_init_type_t init_mode); + - 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 diff --git a/modules/hw/mmal/rpi_prof.h b/modules/hw/mmal/rpi_prof.h new file mode 100644 index 0000000000..bae488b623 --- /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 + diff --git a/modules/hw/mmal/subpic.c b/modules/hw/mmal/subpic.c new file mode 100644 index 0000000000..42770eaac9 --- /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; +} + + + diff --git a/modules/hw/mmal/subpic.h b/modules/hw/mmal/subpic.h new file mode 100644 index 0000000000..4599f8a5da --- /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 + diff --git a/modules/hw/mmal/transform_ops.h b/modules/hw/mmal/transform_ops.h new file mode 100644 index 0000000000..acfb5c254b --- /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 + diff --git a/modules/hw/mmal/v7_pmu.S b/modules/hw/mmal/v7_pmu.S new file mode 100644 index 0000000000..dc82357bf3 --- /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 */ +/* ------------------------------------------------------------ */ + + diff --git a/modules/hw/mmal/v7_pmu.h b/modules/hw/mmal/v7_pmu.h new file mode 100644 index 0000000000..55c2bdf885 --- /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 +// ------------------------------------------------------------ + diff --git a/modules/hw/mmal/vout.c b/modules/hw/mmal/vout.c index 76188a457c..b9fef71e3c 100644 --- 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 *); +#define SUBS_MAX 4 -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() - -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) - int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */ - int i_frame_rate; + 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 + + 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; + + 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; + + MMAL_POOL_T * copy_pool; + MMAL_BUFFER_HEADER_T * copy_buf; + + // Subpic blend if we have to do it here + vzc_pool_ctl_t * vzc; }; -static const vlc_fourcc_t subpicture_chromas[] = { - VLC_CODEC_RGBA, - 0 -}; -/* 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); +// ISP setup + +static inline bool want_isp(const vout_display_t * const vd) +{ + return (vd->fmt.i_chroma == VLC_CODEC_MMAL_ZC_SAND10); +} -/* 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); +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); +} -/* 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 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; +} -/* 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); +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; +} -/* 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); +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; + + 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); -static int Open(vlc_object_t *object) + 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) { - 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; + 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; - int ret = VLC_SUCCESS; - unsigned i; - if (vout_display_IsWindowed(vd)) - return VLC_EGENERIC; + 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 = calloc(1, sizeof(struct vout_display_sys_t)); - if (!sys) - return VLC_ENOMEM; - vd->sys = sys; + mmal_buffer_header_release(buffer); +} - sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME); - bcm_host_init(); +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; + +#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 + } + else + { + mmal_buffer_header_reset(buf); + mmal_buffer_header_release(buf); + } +} - sys->opaque = vd->fmt.i_chroma == VLC_CODEC_MMAL_OPAQUE; +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; - 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; + 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; + } } - 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; + 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; + } } - sys->input = sys->component->input[0]; - sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd; + 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; +} - 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_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); - 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 (isp->component == NULL) + return; + + isp_flush(isp); + + 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->input->buffer_size = sys->input->buffer_size_recommended; - 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; + if (isp->out_pool != NULL) { + mmal_port_pool_destroy(isp->output, isp->out_pool); + isp->out_pool = NULL; } - 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; + isp->input = NULL; + isp->output = NULL; - 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; - } + mmal_component_release(isp->component); + isp->component = NULL; + + return; +} + +// 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) +{ + 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; +} - vlc_mutex_init(&sys->buffer_mutex); - vlc_cond_init(&sys->buffer_cond); - vlc_mutex_init(&sys->manage_mutex); +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; - vd->pool = vd_pool; - vd->prepare = vd_prepare; - vd->display = vd_display; - vd->control = vd_control; - vd->manage = vd_manage; + 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]; - vc_tv_register_callback(tvservice_cb, vd); + 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 (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; + isp->input->userdata = (void *)vd; + display_set_format(vd, isp->input->format, false); + + if ((err = port_parameter_set_bool(isp->input, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) + goto fail; + + if ((err = mmal_port_format_commit(isp->input)) != MMAL_SUCCESS) { + msg_Err(vd, "Failed to set ISP input format"); + goto fail; + } + + isp->input->buffer_size = isp->input->buffer_size_recommended; + isp->input->buffer_num = 30; + + if ((isp->in_pool = mmal_pool_create(isp->input->buffer_num, 0)) == NULL) + { + msg_Err(vd, "Failed to create input pool"); + goto fail; + } + + if ((isp->out_q = mmal_queue_create()) == NULL) + { + err = MMAL_ENOMEM; + goto fail; + } + + 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; + } + + 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; + +fail: + isp_close(vd, vd_sys); + return err; +} + +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 + { + // ISP closed but we want it + return isp_setup(vd, vd_sys); } - sys->dmx_handle = vc_dispmanx_display_open(0); - vd->info.subpicture_chromas = subpicture_chromas; + return MMAL_SUCCESS; +} - vout_display_DeleteWindow(vd, NULL); +/* 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); -out: - if (ret != VLC_SUCCESS) - Close(object); + + +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 void Close(vlc_object_t *object) +static inline MMAL_RECT_T +place_to_mmal_rect(const vout_display_place_t place) { - 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; + return (MMAL_RECT_T){ + .x = place.x, + .y = place.y, + .width = place.width, + .height = place.height + }; +} - vc_tv_unregister_callback_full(tvservice_cb, vd); +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; - if (sys->dmx_handle) - close_dmx(vd); + // Fix SAR if unknown + if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) { + tfmt = *fmt; + tfmt.i_sar_den = 1; + tfmt.i_sar_num = 1; + fmt = &tfmt; + } - if (sys->component && sys->component->control->is_enabled) - mmal_port_disable(sys->component->control); + // 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; + } - if (sys->input && sys->input->is_enabled) - mmal_port_disable(sys->input); + vout_display_PlacePicture(&place, fmt, cfg, false); - if (sys->component && sys->component->is_enabled) - mmal_component_disable(sys->component); + place.x += r.x; + place.y += r.y; - if (sys->pool) - mmal_port_pool_destroy(sys->input, sys->pool); + return place_to_mmal_rect(place); +} - if (sys->component) - mmal_component_release(sys->component); +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; +} - 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]); - } +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); +} - vlc_mutex_destroy(&sys->buffer_mutex); - vlc_cond_destroy(&sys->buffer_cond); - vlc_mutex_destroy(&sys->manage_mutex); +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}; - 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"); + 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; } - free(sys->pictures); - free(sys); + if (ORIENT_IS_SWAP(fmt->orientation)) + sys->spu_rect = rect_transpose(sys->spu_rect); +} - bcm_host_deinit(); +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 inline uint32_t align(uint32_t x, uint32_t y) { - uint32_t mod = x % y; - if (mod == 0) - return x; - else - return x + y - mod; +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_display_t *vd, const vout_display_cfg_t *cfg, 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_display_t *vd, const vout_display_cfg_t *cfg, return 0; } -static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count) +static void kill_pool(vout_display_sys_t * const sys) { - 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; - - 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); - - goto out; + if (sys->pic_pool != NULL) { + picture_pool_Release(sys->pic_pool); + sys->pic_pool = NULL; } +} - if (sys->opaque) { - if (count <= NUM_ACTUAL_OPAQUE_BUFFERS) - count = NUM_ACTUAL_OPAQUE_BUFFERS; +// 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 * const sys = vd->sys; - MMAL_PARAMETER_BOOLEAN_T zero_copy = { - { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, - 1 - }; + 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); - 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 (sys->pic_pool == NULL) { + sys->pic_pool = picture_pool_NewFromFormat(&vd->fmt, count); } + return sys->pic_pool; +} - if (count < sys->input->buffer_num_recommended) - count = sys->input->buffer_num_recommended; +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; +} -#ifndef NDEBUG - msg_Dbg(vd, "Creating picture pool with %u pictures", count); +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; + +#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); + } #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; - } - - 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; - } - - 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; - } - - sys->pictures[i]->i_planes = sys->i_planes; - memcpy(sys->pictures[i]->p, sys->planes, sys->i_planes * sizeof(plane_t)); + if (!check_shape(vd, p_pic)) + { + msg_Err(vd, "Pic/fmt shape mismatch"); + goto fail; } - 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; - - sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg); - if (!sys->picture_pool) { - msg_Err(vd, "Failed to create picture pool"); - goto out; + 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; + } + } + 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; + } + } + 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; + } -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; + // 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"); - /* 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; + // HVS can deal with on-line dimension changes + if (mmal_port_format_commit(sys->input) != MMAL_SUCCESS) + msg_Warn(vd, "Input format commit failed"); + } - 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 ((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; + } } - 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,30 +971,198 @@ static int vd_control(vout_display_t *vd, int query, va_list args) 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); + sys->need_configure_display = false; + set_display_windows(vd, sys); + } + + vlc_mutex_unlock(&sys->manage_mutex); +} - if (query_resolution(vd, &width, &height) >= 0) { - sys->display_width = width; - sys->display_height = height; - vout_display_SendEventDisplaySize(vd, width, height); + +static int attach_subpics(vout_display_t * const vd, vout_display_sys_t * const sys, + subpicture_t * const subpicture) +{ + unsigned int n = 0; + + if (sys->vzc == NULL) { + if ((sys->vzc = hw_mmal_vzc_pool_new()) == NULL) + { + msg_Err(vd, "Failed to allocate VZC"); + return VLC_ENOMEM; } + } - sys->need_configure_display = false; + // Attempt to import the subpics + for (subpicture_t * spic = subpicture; spic != NULL; spic = spic->p_next) + { + for (subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) { + picture_t *const src = sreg->p_picture; + +#if TRACE_ALL + char dbuf0[5]; + msg_Dbg(vd, " [%p:%p] Pos=%d,%d max=%dx%d, src=%dx%d/%dx%d o:%d, spu=%d,%d:%dx%d, vd->fmt=%dx%d/%dx%d, vd->source=%dx%d/%dx%d, cfg=%dx%d, zoom=%d/%d, Alpha=%d, Fmt=%s", src, src->p[0].p_pixels, + sreg->i_x, sreg->i_y, + sreg->i_max_width, sreg->i_max_height, + src->format.i_visible_width, src->format.i_visible_height, + src->format.i_width, src->format.i_height, + src->format.orientation, + sys->spu_rect.x, sys->spu_rect.y, sys->spu_rect.width, sys->spu_rect.height, + vd->fmt.i_visible_width, vd->fmt.i_visible_height, + vd->fmt.i_width, vd->fmt.i_height, + vd->source.i_visible_width, vd->source.i_visible_height, + vd->source.i_width, vd->source.i_height, + vd->cfg->display.width, vd->cfg->display.height, + vd->cfg->zoom.num, vd->cfg->zoom.den, + sreg->i_alpha, + str_fourcc(dbuf0, src->format.i_chroma)); +#endif + + // At this point I think the subtitles are being placed in the + // coord space of the placed rectangle in the cfg display space + if ((sys->subpic_bufs[n] = hw_mmal_vzc_buf_from_pic(sys->vzc, + src, + &sreg->fmt, + (MMAL_RECT_T){.width = sys->spu_rect.width, .height=sys->spu_rect.height}, + sreg->i_x, sreg->i_y, + sreg->i_alpha, + n == 0)) == NULL) + { + msg_Err(vd, "Failed to allocate vzc buffer for subpic"); + return VLC_ENOMEM; + } + + if (++n == SUBS_MAX) + return VLC_SUCCESS; + } } + return VLC_SUCCESS; +} + + +static void 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 + ) +{ + 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_sys_t *pic_sys = picture->p_sys; + + if (!sys->adjust_refresh_rate || pic_sys->displayed) + return; - vlc_mutex_unlock(&sys->manage_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 void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) + +static void vd_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; @@ -689,45 +1175,6 @@ static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) mmal_buffer_header_release(buffer); } -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - vout_display_t *vd = (vout_display_t *)port->userdata; - vout_display_sys_t *sys = vd->sys; - picture_t *picture = (picture_t *)buffer->user_data; - - if (picture) - picture_Release(picture); - - 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); -} - -static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height) -{ - TV_DISPLAY_STATE_T display_state; - int ret = 0; - - 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; - } - - return ret; -} - static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, uint32_t param2) { VLC_UNUSED(reason); @@ -777,12 +1224,12 @@ static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt) int num_modes; double frame_rate = (double)fmt->i_frame_rate / fmt->i_frame_rate_base; int best_id = -1; - double best_score, score; + double best_score = 0.0, score; int i; - vc_tv_get_display_state(&display_state); + vc_tv_get_display_state_id(sys->display_id, &display_state); if(display_state.display.hdmi.mode != HDMI_MODE_OFF) { - num_modes = vc_tv_hdmi_get_supported_modes_new(display_state.display.hdmi.group, + num_modes = vc_tv_hdmi_get_supported_modes_new_id(sys->display_id, display_state.display.hdmi.group, supported_modes, VC_TV_MAX_MODE_IDS, NULL, NULL); for (i = 0; i < num_modes; ++i) { @@ -810,7 +1257,7 @@ static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt) if((best_id >= 0) && (display_state.display.hdmi.mode != supported_modes[best_id].code)) { msg_Info(vd, "Setting HDMI refresh rate to %"PRIu32, supported_modes[best_id].frame_rate); - vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_HDMI, + vc_tv_hdmi_power_on_explicit_new_id(sys->display_id, HDMI_MODE_HDMI, supported_modes[best_id].group, supported_modes[best_id].code); } @@ -828,148 +1275,12 @@ static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt) } } -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 +1323,441 @@ static void maintain_phase_sync(vout_display_t *vd) } } -static void show_background(vout_display_t *vd, bool enable) +static void CloseMmalVout(vlc_object_t *object) { - vout_display_sys_t *sys = vd->sys; - uint32_t image_ptr, color = 0xFF000000; - VC_RECT_T dst_rect, src_rect; - DISPMANX_UPDATE_HANDLE_T update; - - if (enable && !sys->bkg_element) { - sys->bkg_resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32, 1, 1, - &image_ptr); - vc_dispmanx_rect_set(&dst_rect, 0, 0, 1, 1); - vc_dispmanx_resource_write_data(sys->bkg_resource, VC_IMAGE_RGBA32, - sizeof(color), &color, &dst_rect); - vc_dispmanx_rect_set(&src_rect, 0, 0, 1 << 16, 1 << 16); - vc_dispmanx_rect_set(&dst_rect, 0, 0, 0, 0); - update = vc_dispmanx_update_start(0); - sys->bkg_element = vc_dispmanx_element_add(update, sys->dmx_handle, - sys->layer - 1, &dst_rect, sys->bkg_resource, &src_rect, - DISPMANX_PROTECTION_NONE, NULL, NULL, VC_IMAGE_ROT0); - vc_dispmanx_update_submit_sync(update); - } else if (!enable && sys->bkg_element) { - update = vc_dispmanx_update_start(0); - vc_dispmanx_element_remove(update, sys->bkg_element); - vc_dispmanx_resource_delete(sys->bkg_resource); - vc_dispmanx_update_submit_sync(update); - sys->bkg_element = DISPMANX_NO_HANDLE; - sys->bkg_resource = DISPMANX_NO_HANDLE; + vout_display_t * const vd = (vout_display_t *)object; + vout_display_sys_t * const sys = vd->sys; + char response[20]; /* answer is hvs_update_fields=%1d */ + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); +#endif + + kill_pool(sys); + + vc_tv_unregister_callback_full(tvservice_cb, vd); + + // Shouldn't be anything here - but just in case + for (unsigned int i = 0; i != SUBS_MAX; ++i) + if (sys->subpic_bufs[i] != NULL) + mmal_buffer_header_release(sys->subpic_bufs[i]); + + for (unsigned int i = 0; i != SUBS_MAX; ++i) { + vout_subpic_t * const sub = sys->subs + i; + if (sub->component != NULL) { + hw_mmal_subpic_close(VLC_OBJECT(vd), &sub->sub); + if (sub->component->control->is_enabled) + mmal_port_disable(sub->component->control); + if (sub->component->is_enabled) + mmal_component_disable(sub->component); + mmal_component_release(sub->component); + sub->component = NULL; + } + } + + if (sys->input && sys->input->is_enabled) + mmal_port_disable(sys->input); + + if (sys->component && sys->component->control->is_enabled) + mmal_port_disable(sys->component->control); + + if (sys->copy_buf != NULL) + mmal_buffer_header_release(sys->copy_buf); + + if (sys->input != NULL && sys->copy_pool != NULL) + mmal_port_pool_destroy(sys->input, sys->copy_pool); + + if (sys->component && sys->component->is_enabled) + mmal_component_disable(sys->component); + + if (sys->pool) + mmal_pool_destroy(sys->pool); + + if (sys->component) + mmal_component_release(sys->component); + + isp_close(vd, sys); + + hw_mmal_vzc_pool_release(sys->vzc); + + vlc_mutex_destroy(&sys->manage_mutex); + + if (sys->native_interlaced) { + if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 || + response[18] != '0') + msg_Warn(vd, "Could not reset hvs field mode"); + } + + cma_vcsm_exit(sys->init_type);; + + free(sys); + +#if TRACE_ALL + msg_Dbg(vd, ">>> %s", __func__); +#endif +} + + +static const struct { + const char * name; + int num; +} display_name_to_num[] = { + {"auto", -1}, + {"hdmi-1", DISPMANX_ID_HDMI0}, + {"hdmi-2", DISPMANX_ID_HDMI1}, + {NULL, -2} +}; + +static const struct { + const char * name; + int transform_num; +} transform_name_to_num[] = { + {"auto", -1}, + {"0", MMAL_DISPLAY_ROT0}, + {"hflip", MMAL_DISPLAY_MIRROR_ROT0}, + {"vflip", MMAL_DISPLAY_MIRROR_ROT180}, + {"180", MMAL_DISPLAY_ROT180}, + {"transpose", MMAL_DISPLAY_MIRROR_ROT90}, + {"270", MMAL_DISPLAY_ROT270}, + {"90", MMAL_DISPLAY_ROT90}, + {"antitranspose", MMAL_DISPLAY_MIRROR_ROT270}, + {NULL, -2} +}; + +static int find_display_num(const char * const name) +{ + unsigned int i; + for (i = 0; display_name_to_num[i].name != NULL && strcasecmp(display_name_to_num[i].name, name) != 0; ++i) + /* Loop */; + return display_name_to_num[i].num; +} + +static int find_transform_num(const char * const name) +{ + unsigned int i; + for (i = 0; transform_name_to_num[i].name != NULL && strcasecmp(transform_name_to_num[i].name, name) != 0; ++i) + /* Loop */; + return transform_name_to_num[i].transform_num; +} + +#if HAVE_X11_XLIB_H +#include +#include +static MMAL_DISPLAYTRANSFORM_T get_xrandr_rotation(vout_display_t * const vd) +{ + Display * const x = XOpenDisplay(NULL); + Rotation cur_rot = 0; + MMAL_DISPLAYTRANSFORM_T trans; + + if (x == NULL) + return MMAL_DISPLAY_ROT0; + + XRRRotations(x, 0, &cur_rot); + XCloseDisplay(x); + + // Convert to MMAL + // xrandr seems to rotate the other way to mmal + + switch (cur_rot) + { + case 0: + case RR_Rotate_0: + trans = MMAL_DISPLAY_ROT0; + break; + case RR_Rotate_90: + trans = MMAL_DISPLAY_ROT270; + break; + case RR_Rotate_180: + trans = MMAL_DISPLAY_ROT180; + break; + case RR_Rotate_270: + trans = MMAL_DISPLAY_ROT90; + break; + case RR_Reflect_X: + trans = MMAL_DISPLAY_MIRROR_ROT0; + break; + case RR_Reflect_Y: + trans = MMAL_DISPLAY_MIRROR_ROT180; + break; + default: + msg_Info(vd, "Unexpected X rotation value: %#x", cur_rot); + trans = MMAL_DISPLAY_ROT0; + break; + } + + return trans; +} +#else +static MMAL_DISPLAYTRANSFORM_T get_xrandr_rotation(vout_display_t * const vd) +{ + VLC_UNUSED(vd); + return MMAL_DISPLAY_ROT0; +} +#endif + +static MMAL_RECT_T str_to_rect(const char * s) +{ + MMAL_RECT_T rect = {0}; + rect.width = strtoul(s, (char**)&s, 0); + if (*s == '\0') + return rect; + if (*s++ != 'x') + goto fail; + rect.height = strtoul(s, (char**)&s, 0); + if (*s == '\0') + return rect; + if (*s++ != '+') + goto fail; + rect.x = strtoul(s, (char**)&s, 0); + if (*s == '\0') + return rect; + if (*s++ != '+') + goto fail; + rect.y = strtoul(s, (char**)&s, 0); + if (*s != '\0') + goto fail; + return rect; + +fail: + return (MMAL_RECT_T){0,0,0,0}; +} + +static int OpenMmalVout(vlc_object_t *object) +{ + vout_display_t *vd = (vout_display_t *)object; + vout_display_sys_t *sys; + MMAL_STATUS_T status; + int ret = VLC_EGENERIC; + // At the moment all copy is via I420 + const bool needs_copy = !hw_mmal_chroma_is_mmal(vd->fmt.i_chroma); + const MMAL_FOURCC_T enc_in = needs_copy ? MMAL_ENCODING_I420 : + vout_vlc_to_mmal_pic_fourcc(vd->fmt.i_chroma); + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s: o:%d", __func__, (int)vd->fmt.orientation); +#endif + + if (bcm_host_is_kms_active()) { + msg_Dbg(vd, "KMS active - mmal vout disabled"); + return VLC_EGENERIC; + } + + get_xrandr_rotation(vd); + + sys = calloc(1, sizeof(struct vout_display_sys_t)); + if (!sys) + return VLC_ENOMEM; + vd->sys = sys; + + vlc_mutex_init(&sys->manage_mutex); + + if ((sys->init_type = cma_vcsm_init()) == VCSM_INIT_NONE) + { + msg_Err(vd, "VCSM init fail"); + goto fail; + } + + vc_tv_register_callback(tvservice_cb, vd); + + sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME); + sys->transparent = var_InheritBool(vd, MMAL_VOUT_TRANSPARENT_NAME); + + { + const char *display_name = var_InheritString(vd, MMAL_DISPLAY_NAME); + int qt_num = var_InheritInteger(vd, "qt-fullscreen-screennumber" ); + int display_id = find_display_num(display_name); +// sys->display_id = display_id < 0 ? vc_tv_get_default_display_id() : display_id; + sys->display_id = display_id >= 0 ? display_id : + qt_num == 1 ? DISPMANX_ID_HDMI1 : DISPMANX_ID_HDMI; + if (display_id < -1) + msg_Warn(vd, "Unknown display device: '%s'", display_name); + else + msg_Dbg(vd, "Display device: %s, qt=%d id=%d display=%d", display_name, + qt_num, display_id, sys->display_id); + } + + { + const char *window_str = var_InheritString(vd, MMAL_VOUT_WINDOW_NAME); + sys->req_win = str_to_rect(window_str); + if (sys->req_win.width != 0) + msg_Dbg(vd, "Window: %dx%d @ %d,%d", + sys->req_win.width, sys->req_win.height, + sys->req_win.x, sys->req_win.y); + } + + { + const char *transform_name = var_InheritString(vd, MMAL_VOUT_TRANSFORM_NAME); + int transform_num = find_transform_num(transform_name); + sys->display_transform = transform_num < 0 ? + get_xrandr_rotation(vd) : + (MMAL_DISPLAYTRANSFORM_T)transform_num; + + if (transform_num < -1) + msg_Warn(vd, "Unknown vout transform: '%s'", transform_name); + else + msg_Dbg(vd, "Display transform: %s, mmal_display_transform=%d", + transform_name, (int)sys->display_transform); + + sys->video_transform = combine_transform( + vlc_to_mmal_transform(vd->fmt.orientation), sys->display_transform); + sys->dest_transform = transform_inverse(sys->display_transform); + } + + status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component); + if (status != MMAL_SUCCESS) { + msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)", + MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status)); + goto fail; + } + + sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd; + status = mmal_port_enable(sys->component->control, vd_control_port_cb); + if (status != MMAL_SUCCESS) { + msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)", + sys->component->control->name, status, mmal_status_to_string(status)); + goto fail; + } + + sys->input = sys->component->input[0]; + sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd; + + sys->input->format->encoding = enc_in; + sys->input->format->encoding_variant = 0; + sys->i_planes = 1; + + display_set_format(vd, sys->input->format, want_isp(vd)); + + status = port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, true); + if (status != MMAL_SUCCESS) { + msg_Err(vd, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", + sys->input->name, status, mmal_status_to_string(status)); + goto fail; + } + + status = mmal_port_format_commit(sys->input); + if (status != MMAL_SUCCESS) { + msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)", + sys->input->name, status, mmal_status_to_string(status)); + goto fail; + } + + sys->input->buffer_size = sys->input->buffer_size_recommended; + + if (!needs_copy) { + sys->input->buffer_num = 30; + } + else { + sys->input->buffer_num = 2; + if ((sys->copy_pool = mmal_port_pool_create(sys->input, 2, sys->input->buffer_size)) == NULL) + { + msg_Err(vd, "Cannot create copy pool"); + goto fail; + } + } + + set_display_windows(vd, sys); + + configure_display(vd, vd->cfg, &vd->source); + + status = mmal_port_enable(sys->input, vd_input_port_cb); + if (status != MMAL_SUCCESS) { + msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)", + sys->input->name, status, mmal_status_to_string(status)); + goto fail; + } + + status = mmal_component_enable(sys->component); + if (status != MMAL_SUCCESS) { + msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)", + sys->component->name, status, mmal_status_to_string(status)); + goto fail; + } + + if ((sys->pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL) + { + msg_Err(vd, "Failed to create input pool"); + goto fail; + } + + for (unsigned int i = 0; i != SUBS_MAX; ++i) { + vout_subpic_t * const sub = sys->subs + i; + if ((status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sub->component)) != MMAL_SUCCESS) + { + msg_Dbg(vd, "Failed to create subpic component %d", i); + goto fail; + } + sub->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd; + if ((status = mmal_port_enable(sub->component->control, vd_control_port_cb)) != MMAL_SUCCESS) { + msg_Err(vd, "Failed to enable control port %s on sub %d (status=%"PRIx32" %s)", + sys->component->control->name, i, status, mmal_status_to_string(status)); + goto fail; + } + if ((status = hw_mmal_subpic_open(VLC_OBJECT(vd), &sub->sub, sub->component->input[0], + sys->display_id, sys->layer + i + 1)) != MMAL_SUCCESS) { + msg_Dbg(vd, "Failed to open subpic %d", i); + goto fail; + } + if ((status = mmal_component_enable(sub->component)) != MMAL_SUCCESS) + { + msg_Dbg(vd, "Failed to enable subpic component %d", i); + goto fail; + } } + + // If we can't deal with it directly ask for I420 + vd->fmt.i_chroma = req_chroma(vd); + + vd->info = (vout_display_info_t){ + .is_slow = false, + .has_double_click = false, + .needs_hide_mouse = false, + .has_pictures_invalid = true, + .subpicture_chromas = hw_mmal_vzc_subpicture_chromas + }; + + vd->pool = vd_pool; + vd->prepare = vd_prepare; + vd->display = vd_display; + vd->control = vd_control; + + + msg_Dbg(vd, ">>> %s: ok", __func__); + return VLC_SUCCESS; + +fail: + CloseMmalVout(object); + + msg_Dbg(vd, ">>> %s: rv=%d", __func__, ret); + + return ret == VLC_SUCCESS ? VLC_EGENERIC : ret; } + +vlc_module_begin() + + add_submodule() + + set_shortname(N_("MMAL vout")) + set_description(N_("MMAL-based vout plugin for Raspberry Pi")) + set_capability("vout display", 16) // 1 point better than ASCII art + add_shortcut("mmal_vout") + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VOUT ) + + add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false) + add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT, + MMAL_ADJUST_REFRESHRATE_LONGTEXT, false) + add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT, + MMAL_NATIVE_INTERLACE_LONGTEXT, false) + add_string(MMAL_DISPLAY_NAME, "auto", MMAL_DISPLAY_TEXT, + MMAL_DISPLAY_LONGTEXT, false) + add_string(MMAL_VOUT_TRANSFORM_NAME, "auto", MMAL_VOUT_TRANSFORM_TEXT, + MMAL_VOUT_TRANSFORM_LONGTEXT, false) + add_string(MMAL_VOUT_WINDOW_NAME, "fullscreen", MMAL_VOUT_WINDOW_TEXT, + MMAL_VOUT_WINDOW_LONGTEXT, false) + add_bool(MMAL_VOUT_TRANSPARENT_NAME, false, MMAL_VOUT_TRANSPARENT_TEXT, + MMAL_VOUT_TRANSPARENT_LONGTEXT, false) + set_callbacks(OpenMmalVout, CloseMmalVout) + +vlc_module_end() + + diff --git a/modules/hw/mmal/xsplitter.c b/modules/hw/mmal/xsplitter.c new file mode 100644 index 0000000000..68771421ed --- /dev/null +++ b/modules/hw/mmal/xsplitter.c @@ -0,0 +1,669 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#define TRACE_ALL 0 + +// If set will reduce requested size on X +// A good idea if we have h/w scaler before X +// as it limits the work needed by GL but a disaster if we don't +#define LIMIT_X_PELS 0 + +#define VOUT_DISPLAY_CHANGE_MMAL_BASE 1024 +#define VOUT_DISPLAY_CHANGE_MMAL_HIDE (VOUT_DISPLAY_CHANGE_MMAL_BASE + 0) + +struct mmal_x11_sys_s; + +typedef struct display_desc_s +{ + vout_display_t * vout; + unsigned int max_pels; + void (* on_swap_away)(vout_display_t *const vd, struct mmal_x11_sys_s *const sys, struct display_desc_s * const x_desc); +} display_desc_t; + +typedef struct mmal_x11_sys_s +{ + bool drm_fail; // We tried DRM but it didn't work + display_desc_t * cur_desc; + display_desc_t mmal_desc; + display_desc_t x_desc; + display_desc_t drm_desc; + uint32_t changed; + vlc_fourcc_t subpicture_chromas[16]; +} mmal_x11_sys_t; + +#define MAX_GL_PELS (1920*1080) +#define MAX_MMAL_PELS (4096*4096) // Should never be hit +#define MAX_DRM_PELS (4096*4096) // Should never be hit + +static inline char drmu_log_safechar(int c) +{ + return (c < ' ' || c >=0x7f) ? '?' : c; +} + +static inline const char * str_fourcc(char buf[5], uint32_t fcc) +{ + if (fcc == 0) + return "----"; + buf[0] = drmu_log_safechar((fcc >> 0) & 0xff); + buf[1] = drmu_log_safechar((fcc >> 8) & 0xff); + buf[2] = drmu_log_safechar((fcc >> 16) & 0xff); + buf[3] = drmu_log_safechar((fcc >> 24) & 0xff); + buf[4] = 0; + return buf; +} + +#if 0 +// Gen prog for the following table +// Not done inline in case we end up pulling in FP libs we don't want +#include +#include + +int main(int argc, char *argv[]) +{ + unsigned int i; + for (i = 0; i != 64; ++i) + { + printf(" [%2u]=%5u,", i, (unsigned int)(0.5 + (1/sqrt((i + 5)/4.0) * 65536.0))); + if (i % 4 == 3) + printf("\n"); + } +} +#endif + +#if LIMIT_X_PELS +static const uint16_t sqrt_tab[64] = { + [ 0]=58617, [ 1]=53510, [ 2]=49541, [ 3]=46341, + [ 4]=43691, [ 5]=41449, [ 6]=39520, [ 7]=37837, + [ 8]=36353, [ 9]=35030, [10]=33843, [11]=32768, + [12]=31790, [13]=30894, [14]=30070, [15]=29309, + [16]=28602, [17]=27945, [18]=27330, [19]=26755, + [20]=26214, [21]=25705, [22]=25225, [23]=24770, + [24]=24339, [25]=23930, [26]=23541, [27]=23170, + [28]=22817, [29]=22479, [30]=22155, [31]=21845, + [32]=21548, [33]=21263, [34]=20988, [35]=20724, + [36]=20470, [37]=20225, [38]=19988, [39]=19760, + [40]=19539, [41]=19326, [42]=19119, [43]=18919, + [44]=18725, [45]=18536, [46]=18354, [47]=18176, + [48]=18004, [49]=17837, [50]=17674, [51]=17515, + [52]=17361, [53]=17211, [54]=17064, [55]=16921, + [56]=16782, [57]=16646, [58]=16514, [59]=16384, + [60]=16257, [61]=16134, [62]=16013, [63]=15895 +}; +#define SQRT_MAX (sizeof(sqrt_tab)/sizeof(sqrt_tab[0]) - 1) +#endif + +static bool cpy_fmt_limit_size(const display_desc_t * const dd, + video_format_t * const dst, + const video_format_t * const src) +{ +#if !LIMIT_X_PELS + VLC_UNUSED(dd); + *dst = *src; + return false; +#else + const unsigned int src_pel = src->i_visible_width * src->i_visible_height; + + *dst = *src; + + if (src_pel <= dd->max_pels) + return false; + + // scaling factor sqrt(max_pel/cur_pel) + // sqrt done by lookup & 16 bit fixed-point maths - not exactly accurate but + // easily good enough & avoids floating point (which may be slow) + // src_pel > max_pel so n >= 0 + // Rounding should be such that exact sqrts work and everything else rounds + // down + unsigned int n = ((src_pel * 4 - 1) / dd->max_pels) - 4; + unsigned int scale = sqrt_tab[n >= SQRT_MAX ? SQRT_MAX : n]; + + // Rescale width - rounding up to 16 + unsigned int width = ((src->i_visible_width * scale + (16 << 16) - 1) >> 16) & ~15; + // Rescale height based on new width + unsigned int height = (src->i_visible_height * width + src->i_visible_width/2) / src->i_visible_width; + +// fprintf(stderr, "%dx%d -> %dx%d\n", src->i_visible_width, src->i_visible_height, width, height); + + dst->i_width = width; + dst->i_visible_width = width; + dst->i_height = height; + dst->i_visible_height = height; + return true; +#endif +} + +static void unload_display_module(vout_display_t * const x_vout) +{ + if (x_vout != NULL) { + if (x_vout->module != NULL) { + module_unneed(x_vout, x_vout->module); + } + vlc_object_release(x_vout); + } +} + +static void stop_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys) +{ + VLC_UNUSED(vd); + + unload_display_module(sys->drm_desc.vout); + sys->drm_desc.vout = NULL; +} + +static void CloseMmalX11(vlc_object_t *object) +{ + vout_display_t * const vd = (vout_display_t *)object; + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + + msg_Dbg(vd, "<<< %s", __func__); + + if (sys == NULL) + return; + + unload_display_module(sys->x_desc.vout); + + unload_display_module(sys->mmal_desc.vout); + + stop_drm(vd, sys); + + free(sys); + + msg_Dbg(vd, ">>> %s", __func__); +} + +static void mmal_x11_event(vout_display_t * x_vd, int cmd, va_list args) +{ + vout_display_t * const vd = x_vd->owner.sys; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s (cmd=%d)", __func__, cmd); +#endif + + // Do not fall into the display assert if Invalid not supported + if (cmd == VOUT_DISPLAY_EVENT_PICTURES_INVALID && + !vd->info.has_pictures_invalid) + return; + + vd->owner.event(vd, cmd, args); +} + +static vout_window_t * mmal_x11_window_new(vout_display_t * x_vd, unsigned type) +{ + vout_display_t * const vd = x_vd->owner.sys; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s (type=%d)", __func__, type); +#endif + return vd->owner.window_new(vd, type); +} + +static void mmal_x11_window_del(vout_display_t * x_vd, vout_window_t * win) +{ + vout_display_t * const vd = x_vd->owner.sys; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); +#endif + vd->owner.window_del(vd, win); +} + + +static int load_display_module(vout_display_t * const vd, + display_desc_t * const dd, + const char * const cap, + const char * const module_name) +{ + vout_display_t * const x_vout = vlc_object_create(vd, sizeof(*x_vout)); + + dd->vout = NULL; + if (!x_vout) + return -1; + + x_vout->owner.sys = vd; + x_vout->owner.event = mmal_x11_event; + x_vout->owner.window_new = mmal_x11_window_new; + x_vout->owner.window_del = mmal_x11_window_del; + + x_vout->cfg = vd->cfg; + x_vout->info = vd->info; + cpy_fmt_limit_size(dd, &x_vout->source, &vd->source); + cpy_fmt_limit_size(dd, &x_vout->fmt, &vd->fmt); + + if ((x_vout->module = module_need(x_vout, cap, module_name, true)) == NULL) + { + msg_Dbg(vd, "Failed to open Xsplitter:%s module", module_name); + goto fail; + } + + msg_Dbg(vd, "R/G/B: %08x/%08x/%08x", x_vout->fmt.i_rmask, x_vout->fmt.i_gmask, x_vout->fmt.i_bmask); + + dd->vout = x_vout; + return 0; + +fail: + vlc_object_release(x_vout); + return -1; +} + +static int start_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys) +{ + if (sys->drm_desc.vout != NULL) + return 0; + msg_Info(vd, "Try drm"); + return load_display_module(vd, &sys->drm_desc, "vout display", "drm_vout"); +} + + +/* Return a pointer over the current picture_pool_t* (mandatory). + * + * For performance reasons, it is best to provide at least count + * pictures but it is not mandatory. + * You can return NULL when you cannot/do not want to allocate + * pictures. + * The vout display module keeps the ownership of the pool and can + * destroy it only when closing or on invalid pictures control. + * + * If the X pool doesn't have pictures invalid then it isn't safe + * to swap pools so always use that one (MMAL & DRM can cope with + * most stuff) + */ +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 = vd->info.has_pictures_invalid ? sys->cur_desc->vout : sys->x_desc.vout; +#if TRACE_ALL + char buf0[5]; + char buf1[5]; + msg_Dbg(vd, "<<< %s (count=%d) %s:%dx%d->%s:%dx%d", __func__, count, + str_fourcc(buf0, vd->fmt.i_chroma), + vd->fmt.i_width, vd->fmt.i_height, + str_fourcc(buf1, x_vd->fmt.i_chroma), + x_vd->fmt.i_width, x_vd->fmt.i_height); +#endif + picture_pool_t * pool = x_vd->pool(x_vd, count); +#if TRACE_ALL + msg_Dbg(vd, ">>> %s: %p", __func__, pool); +#endif + return pool; +} + +/* Prepare a picture and an optional subpicture for display (optional). + * + * It is called before the next pf_display call to provide as much + * time as possible to prepare the given picture and the subpicture + * for display. + * You are guaranted that pf_display will always be called and using + * the exact same picture_t and subpicture_t. + * You cannot change the pixel content of the picture_t or of the + * subpicture_t. + */ +static void mmal_x11_prepare(vout_display_t * vd, picture_t * pic, subpicture_t * sub) +{ + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + vout_display_t * const x_vd = sys->cur_desc->vout; +#if TRACE_ALL + char buf0[5]; + msg_Dbg(vd, "<<< %s: fmt=%s, %dx%d", __func__, str_fourcc(buf0, pic->format.i_chroma), pic->format.i_width, pic->format.i_height); +#endif + if (x_vd->prepare) + x_vd->prepare(x_vd, pic, sub); +} + +/* Display a picture and an optional subpicture (mandatory). + * + * The picture and the optional subpicture must be displayed as soon as + * possible. + * You cannot change the pixel content of the picture_t or of the + * subpicture_t. + * + * This function gives away the ownership of the picture and of the + * subpicture, so you must release them as soon as possible. + */ +static void mmal_x11_display(vout_display_t * vd, picture_t * pic, subpicture_t * sub) +{ + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + vout_display_t * const x_vd = sys->cur_desc->vout; + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s: fmt: %dx%d/%dx%d, pic:%dx%d, pts=%lld", __func__, vd->fmt.i_width, vd->fmt.i_height, x_vd->fmt.i_width, x_vd->fmt.i_height, pic->format.i_width, pic->format.i_height, (long long)pic->date); +#endif + + if (x_vd->fmt.i_chroma != pic->format.i_chroma || + x_vd->fmt.i_width != pic->format.i_width || + x_vd->fmt.i_height != pic->format.i_height) + { + msg_Dbg(vd, "%s: Picture dropped", __func__); + picture_Release(pic); + if (sub != NULL) + subpicture_Delete(sub); + return; + } + + x_vd->display(x_vd, pic, sub); +} + + +static int vout_display_Control(const display_desc_t * const dd, int query, ...) +{ + va_list args; + int result; + + va_start(args, query); + result = dd->vout->control(dd->vout, query, args); + va_end(args); + + return result; +} + +static display_desc_t* wanted_display(vout_display_t *const vd, mmal_x11_sys_t *const sys) +{ + if (sys->x_desc.vout != NULL && !var_InheritBool(vd, "fullscreen")) + return &sys->x_desc; + + // Full screen or no X + + if (sys->mmal_desc.vout != NULL) + return &sys->mmal_desc; + + if (!sys->drm_fail) { + if (start_drm(vd, sys) == 0) + return &sys->drm_desc; + msg_Info(vd, "Drm no go"); + sys->drm_fail = true; // Never try again + } + + return sys->x_desc.vout != NULL ? &sys->x_desc : NULL; +} + +static inline int +up_rv(const int a, const int b) +{ + return a != 0 ? a : b; +} + +static int +reset_pictures(vout_display_t * const vd, const display_desc_t * const desc) +{ + int rv = 0; + VLC_UNUSED(vd); + if (desc->vout) + { + // If the display doesn't have has_pictures_invalid then it doesn't + // expect RESET_PICTURES + if (desc->vout->info.has_pictures_invalid) + vout_display_Control(desc, VOUT_DISPLAY_RESET_PICTURES); + } + return rv; +} + +static int +replay_controls(vout_display_t * const vd, const display_desc_t * const desc, const int32_t changed) +{ + if ((changed & (1 << VOUT_DISPLAY_CHANGE_DISPLAY_FILLED)) != 0) + vout_display_Control(desc, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, vd->cfg); + if ((changed & (1 << VOUT_DISPLAY_CHANGE_ZOOM)) != 0) + vout_display_Control(desc, VOUT_DISPLAY_CHANGE_ZOOM, vd->cfg); + if ((changed & ((1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP) | + (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT))) != 0) + cpy_fmt_limit_size(desc, &desc->vout->source, &vd->source); + if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT)) != 0) + vout_display_Control(desc, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT); + if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP)) != 0) + vout_display_Control(desc, VOUT_DISPLAY_CHANGE_SOURCE_CROP); + if ((changed & (1 << VOUT_DISPLAY_CHANGE_VIEWPOINT)) != 0) + vout_display_Control(desc, VOUT_DISPLAY_CHANGE_VIEWPOINT, vd->cfg); + return 0; +} + +static void swap_away_null(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc) +{ + VLC_UNUSED(vd); + VLC_UNUSED(sys); + VLC_UNUSED(x_desc); +} + +static void swap_away_mmal(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc) +{ + VLC_UNUSED(vd); + VLC_UNUSED(sys); + vout_display_Control(x_desc, VOUT_DISPLAY_CHANGE_MMAL_HIDE); +} + +static void swap_away_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc) +{ + VLC_UNUSED(x_desc); + stop_drm(vd, sys); +} + + +/* Control on the module (mandatory) */ +static int mmal_x11_control(vout_display_t * vd, int ctl, va_list va) +{ + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + display_desc_t *x_desc = sys->cur_desc; + int rv; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s[%s] (ctl=%d)", __func__, x_desc->vout->obj.object_type, ctl); +#endif + // Remember what we've told this vd - unwanted ctls ignored on replay + if (ctl >= 0 && ctl <= 31) + sys->changed |= (1 << ctl); + + switch (ctl) { + case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: + { + const vout_display_cfg_t * const cfg = va_arg(va, const vout_display_cfg_t *); + display_desc_t * const new_desc = wanted_display(vd, sys); + const bool swap_vout = (new_desc != sys->cur_desc); + + msg_Dbg(vd, "Change size: %d, %d: fs=%d", + cfg->display.width, cfg->display.height, var_InheritBool(vd, "fullscreen")); + + // Repeat any control calls that we sent to the previous vd + if (swap_vout && sys->changed != 0) { + const uint32_t changed = sys->changed; + sys->changed = 0; + replay_controls(vd, new_desc, changed); + } + + if (swap_vout) { + vout_display_SendEventPicturesInvalid(vd); + x_desc->on_swap_away(vd, sys, x_desc); + } + + rv = vout_display_Control(new_desc, ctl, cfg); + if (rv == VLC_SUCCESS) { + vd->fmt = new_desc->vout->fmt; + sys->cur_desc = new_desc; + // Strictly this is illegal but subpic chromas are consulted + // on every render so it is in fact safe. + vd->info.subpicture_chromas = sys->cur_desc->vout->info.subpicture_chromas; + } + + break; + } + + case VOUT_DISPLAY_RESET_PICTURES: + { + char dbuf0[5], dbuf1[5], dbuf2[5]; + msg_Dbg(vd, "<<< %s: Pic reset: fmt: %s,%dx%d<-%s,%dx%d, source: %s,%dx%d/%dx%d", __func__, + str_fourcc(dbuf0, vd->fmt.i_chroma), vd->fmt.i_width, vd->fmt.i_height, + str_fourcc(dbuf1, x_desc->vout->fmt.i_chroma), x_desc->vout->fmt.i_width, x_desc->vout->fmt.i_height, + str_fourcc(dbuf2, vd->source.i_chroma), vd->source.i_width, vd->source.i_height, x_desc->vout->source.i_width, + x_desc->vout->source.i_height); + } + rv = reset_pictures(vd, &sys->x_desc); + rv = up_rv(rv, reset_pictures(vd, &sys->mmal_desc)); + rv = up_rv(rv, reset_pictures(vd, &sys->drm_desc)); + + vd->fmt = x_desc->vout->fmt; + break; + + case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: + case VOUT_DISPLAY_CHANGE_SOURCE_CROP: + cpy_fmt_limit_size(x_desc, &x_desc->vout->source, &vd->source); + + /* FALLTHRU */ + default: + rv = x_desc->vout->control(x_desc->vout, ctl, va); +// vd->fmt = x_vd->fmt; + break; + } +#if TRACE_ALL + msg_Dbg(vd, ">>> %s (rv=%d)", __func__, rv); +#endif + return rv; +} + +#define DO_MANAGE 0 + +#if DO_MANAGE +/* Manage pending event (optional) */ +static void mmal_x11_manage(vout_display_t * vd) +{ + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + vout_display_t * const x_vd = sys->cur_desc->vout; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); +#endif + x_vd->manage(x_vd); +} +#endif + +static int OpenMmalX11(vlc_object_t *object) +{ + vout_display_t * const vd = (vout_display_t *)object; + mmal_x11_sys_t * const sys = calloc(1, sizeof(*sys)); + int ret = VLC_SUCCESS; + + if (sys == NULL) { + return VLC_EGENERIC; + } + vd->sys = (vout_display_sys_t *)sys; + + vd->info = (vout_display_info_t){ + .is_slow = false, + .has_double_click = false, + .needs_hide_mouse = false, + .has_pictures_invalid = false, + .subpicture_chromas = NULL + }; + + { + char dbuf0[5]; + msg_Dbg(vd, ">>> %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__, + str_fourcc(dbuf0, vd->fmt.i_chroma), + vd->fmt.i_width, vd->fmt.i_height, + vd->fmt.i_x_offset, vd->fmt.i_y_offset, + vd->fmt.i_visible_width, vd->fmt.i_visible_height, + vd->fmt.i_sar_num, vd->fmt.i_sar_den); + } + + sys->x_desc.max_pels = MAX_GL_PELS; + sys->x_desc.on_swap_away = swap_away_null; + sys->mmal_desc.max_pels = MAX_MMAL_PELS; + sys->mmal_desc.on_swap_away = swap_away_mmal; + sys->drm_desc.max_pels = MAX_DRM_PELS; + sys->drm_desc.on_swap_away = swap_away_drm; + + if (load_display_module(vd, &sys->x_desc, "vout display", "opengles2") == 0) + { + msg_Dbg(vd, "Opengles2 output found"); + } + else if (load_display_module(vd, &sys->x_desc, "vout display", "xcb_x11") == 0) + { + sys->x_desc.max_pels = MAX_MMAL_PELS; + msg_Dbg(vd, "X11 XCB output found"); + } + else + { + msg_Dbg(vd, "No X output found"); + goto fail; + } + + if ((load_display_module(vd, &sys->mmal_desc, "vout display", "mmal_vout")) == 0) + msg_Dbg(vd, "MMAL output found"); + + vd->pool = mmal_x11_pool; + vd->prepare = mmal_x11_prepare; + vd->display = mmal_x11_display; + vd->control = mmal_x11_control; +#if DO_MANAGE + vd->manage = mmal_x11_manage; +#endif + + sys->cur_desc = wanted_display(vd, sys); + if (sys->cur_desc == NULL) { + char dbuf0[5], dbuf1[5]; + msg_Warn(vd, "No valid output found for vout (%s/%s)", str_fourcc(dbuf0, vd->fmt.i_chroma), str_fourcc(dbuf1, vd->source.i_chroma)); + goto fail; + } + + if (sys->mmal_desc.vout == NULL) { + vd->info = sys->cur_desc->vout->info; + } + 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() + diff --git a/modules/video_chroma/chain.c b/modules/video_chroma/chain.c index 2709425255..5ac384e633 100644 --- a/modules/video_chroma/chain.c +++ b/modules/video_chroma/chain.c @@ -280,8 +280,9 @@ static int BuildTransformChain( filter_t *p_filter ) 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/modules/video_output/Makefile.am b/modules/video_output/Makefile.am index 8f2cea4c67..210c595ab2 100644 --- a/modules/video_output/Makefile.am +++ b/modules/video_output/Makefile.am @@ -187,6 +187,33 @@ vout_LTLIBRARIES += libglx_plugin.la endif endif +### DRM ### + +libdrm_vout_plugin_la_SOURCES = video_output/drmu/drm_vout.c \ + video_output/drmu/drmu_vlc.c video_output/drmu/drmu_vlc.h \ + video_output/drmu/drmu_vlc_fmts.c video_output/drmu/drmu_vlc_fmts.h \ + video_output/drmu/drmu_fmts.c video_output/drmu/drmu_fmts.h \ + video_output/drmu/drmu_chroma.h \ + video_output/drmu/drmu_log.h video_output/drmu/drmu.c \ + video_output/drmu/drmu_xlease.c video_output/drmu/drmu_atomic.c \ + video_output/drmu/drmu_util.c video_output/drmu/drmu_util.h \ + video_output/drmu/drmu_output.c video_output/drmu/drmu_output.h \ + video_output/drmu/drmu_scan.c video_output/drmu/drmu_scan.h \ + video_output/drmu/drmu.h\ + video_output/drmu/drmu_dmabuf.c video_output/drmu/drmu_dmabuf.h \ + video_output/drmu/drmu_pool.c video_output/drmu/drmu_pool.h \ + video_output/drmu/drmu_math.c video_output/drmu/drmu_math.h \ + video_output/drmu/pollqueue.c video_output/drmu/pollqueue.h +libdrm_vout_plugin_la_CFLAGS = $(AM_CFLAGS) -pthread -I/usr/include/libdrm +libdrm_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -pthread +libdrm_vout_plugin_la_LIBADD = -ldrm -lxcb-randr -lxcb +if HAVE_MMAL +libdrm_vout_plugin_la_CFLAGS += -DHAS_ZC_CMA=1 +libdrm_vout_plugin_la_SOURCES += hw/mmal/mmal_cma_pic.h +endif +if HAVE_DRM +vout_LTLIBRARIES += libdrm_vout_plugin.la +endif ### Wayland ### libwl_shm_plugin_la_SOURCES = video_output/wayland/shm.c @@ -205,7 +232,52 @@ video_output/wayland/viewporter-client-protocol.h: \ video_output/wayland/viewporter-protocol.c: \ $(WAYLAND_PROTOCOLS)/stable/viewporter/viewporter.xml - $(AM_V_GEN)$(WAYLAND_SCANNER) code $< $@ + $(AM_V_GEN)$(WAYLAND_SCANNER) private-code $< $@ + +video_output/wayland/linux-dmabuf-unstable-v1-client-protocol.h: \ + $(WAYLAND_PROTOCOLS)/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml + $(AM_V_GEN)$(WAYLAND_SCANNER) client-header $< $@ + +video_output/wayland/linux-dmabuf-unstable-v1-protocol.c: \ + $(WAYLAND_PROTOCOLS)/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml + $(AM_V_GEN)$(WAYLAND_SCANNER) private-code $< $@ + +video_output/wayland/single-pixel-buffer-v1-client-protocol.h: \ + $(WAYLAND_PROTOCOLS)/staging/single-pixel-buffer/single-pixel-buffer-v1.xml + $(AM_V_GEN)$(WAYLAND_SCANNER) client-header $< $@ + +libwl_dmabuf_plugin_la_SOURCES = video_output/wayland/wl_dmabuf.c\ + video_output/wayland/picpool.c video_output/wayland/picpool.h\ + video_output/wayland/dmabuf_alloc.c video_output/wayland/dmabuf_alloc.h\ + video_output/wayland/rgba_premul.c video_output/wayland/rgba_premul.h\ + video_output/drmu/pollqueue.c video_output/drmu/pollqueue.h\ + video_output/drmu/drmu_vlc_fmts.c video_output/drmu/drmu_vlc_fmts.h +nodist_libwl_dmabuf_plugin_la_SOURCES = \ + video_output/wayland/viewporter-client-protocol.h \ + video_output/wayland/viewporter-protocol.c \ + video_output/wayland/linux-dmabuf-unstable-v1-client-protocol.h \ + video_output/wayland/linux-dmabuf-unstable-v1-protocol.c + +libwl_dmabuf_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) \ + -I$(builddir)/video_output/wayland -pthread +libwl_dmabuf_plugin_la_CFLAGS = $(WAYLAND_CLIENT_CFLAGS) -pthread +libwl_dmabuf_plugin_la_LIBADD = $(WAYLAND_CLIENT_LIBS) +libwl_dmabuf_plugin_la_LDFLAGS = $(AM_LDFLAGS) -pthread +if HAVE_ARM64 +libwl_dmabuf_plugin_la_SOURCES += video_output/wayland/rgba_premul_aarch64.S\ + video_output/wayland/rgba_premul_aarch64.h +libwl_dmabuf_plugin_la_CFLAGS += -DHAVE_AARCH64_ASM=1 +endif +if HAVE_WAYLAND_SINGLE_PIXEL_BUFFER +video_output/wayland/single-pixel-buffer-v1-protocol.c: \ + $(WAYLAND_PROTOCOLS)/staging/single-pixel-buffer/single-pixel-buffer-v1.xml + $(AM_V_GEN)$(WAYLAND_SCANNER) private-code $< $@ +nodist_libwl_dmabuf_plugin_la_SOURCES +=\ + video_output/wayland/single-pixel-buffer-v1-client-protocol.h \ + video_output/wayland/single-pixel-buffer-v1-protocol.c +libwl_dmabuf_plugin_la_CFLAGS += -DHAVE_WAYLAND_SINGLE_PIXEL_BUFFER=1 +endif +CLEANFILES += $(nodist_libwl_dmabuf_plugin_la_SOURCES) libwl_shell_plugin_la_SOURCES = video_output/wayland/shell.c libwl_shell_plugin_la_CFLAGS = $(WAYLAND_CLIENT_CFLAGS) @@ -244,6 +316,8 @@ libegl_wl_plugin_la_LIBADD = $(EGL_LIBS) $(WAYLAND_EGL_LIBS) if HAVE_WAYLAND BUILT_SOURCES += $(nodist_libwl_shm_plugin_la_SOURCES) vout_LTLIBRARIES += libwl_shm_plugin.la +BUILT_SOURCES += $(nodist_libwl_dmabuf_plugin_la_SOURCES) +vout_LTLIBRARIES += libwl_dmabuf_plugin.la vout_LTLIBRARIES += libwl_shell_plugin.la BUILT_SOURCES += $(nodist_libxdg_shell_plugin_la_SOURCES) vout_LTLIBRARIES += libxdg_shell_plugin.la diff --git a/modules/video_output/caca.c b/modules/video_output/caca.c index 3440401082..9297823267 100644 --- a/modules/video_output/caca.c +++ b/modules/video_output/caca.c @@ -160,7 +160,11 @@ static int Open(vlc_object_t *object) } const char *driver = NULL; -#ifdef __APPLE__ +// RPI: If driver is NULL then if we have X but DISPLAY is unset then somehow +// the GL module becomes unloaded without anything noticing and that then +// causes a segfault. +//#ifdef __APPLE__ +#if 1 // Make sure we don't try to open a window. driver = "ncurses"; #endif diff --git a/modules/video_output/drmu/drm_vout.c b/modules/video_output/drmu/drm_vout.c new file mode 100644 index 0000000000..bd81fe4850 --- /dev/null +++ b/modules/video_output/drmu/drm_vout.c @@ -0,0 +1,1425 @@ +/***************************************************************************** + * drm_vout.c: DRM based output device + ***************************************************************************** + * Copyright © 2014 jusst technologies GmbH + * + * Authors: Dennis Hamester + * Julian Scheel + * John Cox + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "drmu.h" +#include "drmu_dmabuf.h" +#include "drmu_fmts.h" +#include "drmu_log.h" +#include "drmu_output.h" +#include "drmu_pool.h" +#include "drmu_scan.h" +#include "drmu_util.h" +#include "drmu_vlc.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#define TRACE_ALL 0 +#define PIC_POOL_FB 1 + +#define SUBPICS_MAX 4 + +#define DRM_MODULE "vc4" + + +#define DRM_VOUT_SOURCE_MODESET_NAME "drm-vout-source-modeset" +#define DRM_VOUT_SOURCE_MODESET_TEXT N_("Attempt to match display to source") +#define DRM_VOUT_SOURCE_MODESET_LONGTEXT N_("Attempt to match display resolution and refresh rate to source.\ + Defaults to the 'preferred' mode if no good enough match found. \ + If unset then resolution & refresh will not be set.") + +#define DRM_VOUT_MODE_NAME "drm-vout-mode" +#define DRM_VOUT_MODE_TEXT N_("Set this mode for display") +#define DRM_VOUT_MODE_LONGTEXT N_("arg: x@ Force mode to arg") + +#define DRM_VOUT_NO_MODESET_NAME "drm-vout-no-modeset" +#define DRM_VOUT_NO_MODESET_TEXT N_("Do not modeset") +#define DRM_VOUT_NO_MODESET_LONGTEXT N_("Do no operation that would cause a modeset.\ + This overrides the operation of all other flags.") + +#define DRM_VOUT_NO_MAX_BPC "drm-vout-no-max-bpc" +#define DRM_VOUT_NO_MAX_BPC_TEXT N_("Do not set bpc on output") +#define DRM_VOUT_NO_MAX_BPC_LONGTEXT N_("Do not try to switch from 8-bit RGB to 12-bit YCC on UHD frames.\ + 12 bit is dependant on kernel and display support so may not be availible") + +#define DRM_VOUT_WINDOW_NAME "drm-vout-window" +#define DRM_VOUT_WINDOW_TEXT N_("Display window for Rpi fullscreen") +#define DRM_VOUT_WINDOW_LONGTEXT N_("Display window for Rpi fullscreen."\ +"fullscreen|x++") + +#define DRM_VOUT_DISPLAY_NAME "drm-vout-display" +#define DRM_VOUT_DISPLAY_TEXT N_("Output device for Rpi fullscreen.") +#define DRM_VOUT_DISPLAY_LONGTEXT N_("Output device for Rpi fullscreen. " \ +"Valid values are HDMI-1,HDMI-2 or a drm connector name. By default if qt-fullscreen-screennumber " \ +"is specified (or set by Fullscreen Output Device in Preferences) " \ +"HDMI- will be used, otherwise HDMI-1.") + +#define DRM_VOUT_MODULE_NAME "drm-vout-module" +#define DRM_VOUT_MODULE_TEXT N_("DRM module to use") +#define DRM_VOUT_MODULE_LONGTEXT N_("DRM module for Rpi fullscreen") + +#define DRM_VOUT_POOL_DMABUF_NAME "drm-vout-pool-dmabuf" +#define DRM_VOUT_POOL_DMABUF_TEXT N_("Use dmabufs for pic pool") +#define DRM_VOUT_POOL_DMABUF_LONGTEXT N_("Use dmabufs for pic pool. Saves a frame copy on output but may use up limited dmabuf resource.") + + +typedef struct subpic_ent_s { + drmu_fb_t * fb; + drmu_rect_t pos; + drmu_rect_t space; // display space of pos + picture_t * pic; + unsigned int alpha; // out of 0xff * 0xff +} subpic_ent_t; + +typedef struct vout_display_sys_t { + drmu_env_t * du; + drmu_output_t * dout; + drmu_plane_t * dp; + drmu_pool_t * pic_pool; + drmu_pool_t * sub_fb_pool; + drmu_plane_t * subplanes[SUBPICS_MAX]; + subpic_ent_t subpics[SUBPICS_MAX]; + vlc_fourcc_t * subpic_chromas; + + drmu_atomic_t * display_set; + + vout_display_place_t req_win; + vout_display_place_t spu_rect; + vout_display_place_t dest_rect; + vout_display_place_t win_rect; + vout_display_place_t display_rect; + + video_transform_t display_transform; + video_transform_t video_transform; + video_transform_t dest_transform; + + bool pool_try_fb; + bool pool_is_fb; + bool output_simple; + uint32_t con_id; + int mode_id; + + picture_pool_t * vlc_pic_pool; +} vout_display_sys_t; + +#define PIC_SYS_SIG VLC_FOURCC('D', 'R', 'M', 'U') + + +// pic->p_sys when we are allocating our own pics +struct picture_sys_t { + uint32_t sig; + drmu_fb_t * fb; +}; + +static drmu_fb_t * +copy_pic_to_fb(vout_display_t *const vd, drmu_pool_t *const pool, picture_t *const src) +{ + uint64_t mod; + const uint32_t drm_fmt = drmu_format_vlc_to_drm(&src->format, &mod); + drmu_fb_t * fb; + int i; + + if (drm_fmt == 0 || mod != DRM_FORMAT_MOD_LINEAR) { + msg_Warn(vd, "Failed vlc->drm format for copy_pic: %s", drmu_log_fourcc(src->format.i_chroma)); + return NULL; + } + + fb = drmu_pool_fb_new(pool, src->format.i_width, src->format.i_height, drm_fmt, mod); + if (fb == NULL) { + msg_Warn(vd, "Failed alloc for copy_pic: %dx%d", src->format.i_width, src->format.i_height); + return NULL; + } + + drmu_fb_write_start(fb); + for (i = 0; i != src->i_planes; ++i) { + plane_t dst_plane; + dst_plane = drmu_fb_vlc_plane(fb, i); + plane_CopyPixels(&dst_plane, src->p + i); + } + drmu_fb_write_end(fb); + + drmu_fb_vlc_pic_set_metadata(fb, src); + + return fb; +} + +static void +create_box(drmu_fb_t * const fb, const unsigned int layer_no) +{ + const drmu_fmt_info_t *const f = drmu_fb_format_info_get(fb); + unsigned int hdiv = drmu_fmt_info_hdiv(f, layer_no); + unsigned int wdiv = drmu_fmt_info_wdiv(f, layer_no); + const unsigned int pby = (drmu_fmt_info_pixel_bits(f) + 7) / 8; + const uint32_t pitch_n = drmu_fb_pitch(fb, layer_no); + const drmu_rect_t crop = drmu_rect_shr16_rnd(drmu_fb_crop_frac(fb)); + const drmu_rect_t active = drmu_fb_active(fb); + + uint8_t * const p0 = drmu_fb_data(fb, layer_no); + uint8_t * p1 = p0; + uint8_t * p2; + + // Assumes RGB as currently no better idea (and it is RGB in the case we expect) + const unsigned int c = 0; + + // Top + p2 = p1 + pitch_n * (crop.y / hdiv) + (crop.x / wdiv) * pby; + if (p1 != p2) + memset(p1, c, p2 - p1); + if (active.w == crop.w) { + p1 = p2 + (crop.h / hdiv) * pitch_n; // We expect crop.x == 0 + } + else { + unsigned int i; + const unsigned int vis_pitch = (crop.w / wdiv) * pby; + for (i = 1; i < (crop.h / hdiv); ++i) { + p1 = p2 + vis_pitch; + p2 = p2 + pitch_n; + memset(p1, c, p2 - p1); + } + p1 = p2 + vis_pitch; + } + p2 = p0 + pitch_n * (active.h / hdiv); + if (p1 != p2) + memset(p1, c, p2 - p1); +} + +static drmu_fb_t * +copy_pic_to_fixed_fb(vout_display_t * const vd, vout_display_sys_t * const sys, + drmu_pool_t *const pool, picture_t *const src) +{ + uint64_t mod; + const uint32_t drm_fmt = drmu_format_vlc_to_drm(&src->format, &mod); + drmu_fb_t * fb; + int i; + + if (drm_fmt == 0 || mod != DRM_FORMAT_MOD_LINEAR) { + msg_Warn(vd, "Failed vlc->drm format for copy_pic: %s", drmu_log_fourcc(src->format.i_chroma)); + return NULL; + } + + fb = drmu_pool_fb_new(pool, sys->display_rect.width, sys->display_rect.height, drm_fmt, mod); + if (fb == NULL) { + msg_Warn(vd, "Failed alloc for copy_pic_fixed: %dx%d", sys->display_rect.width, sys->display_rect.height); + return NULL; + } + + drmu_fb_crop_frac_set(fb, drmu_rect_shl16(drmu_rect_vlc_place(&sys->dest_rect))); + + { + const drmu_fmt_info_t *const f = drmu_fb_format_info_get(fb); + const drmu_rect_t crop = drmu_rect_shr16_rnd(drmu_fb_crop_frac(fb)); + const unsigned int bypp = (drmu_fmt_info_pixel_bits(f) + 7) / 8; + + drmu_fb_write_start(fb); + for (i = 0; i != src->i_planes; ++i) { + // It would seem more logical to use src->format than to use vd->fmt + // for the source rect but src->fmt doesn't have offset_x/y set (bug?) + drmu_memcpy_rect(drmu_fb_data(fb, i), drmu_fb_pitch(fb, i), + drmu_rect_div_xy(crop, drmu_fmt_info_wdiv(f, i), drmu_fmt_info_hdiv(f, i)), + src->p[i].p_pixels, src->p[i].i_pitch, + drmu_rect_vlc_format_crop(&vd->fmt), + bypp); + create_box(fb, i); + } + drmu_fb_write_end(fb); + } + + // Reset crop for display after we've used it for copy + drmu_fb_crop_frac_set(fb, drmu_rect_shl16(drmu_fb_active(fb))); + + drmu_fb_vlc_pic_set_metadata(fb, src); + + return fb; +} + + +static vout_display_place_t str_to_rect(const char * s) +{ + vout_display_place_t rect = {0}; + rect.width = strtoul(s, (char**)&s, 0); + if (*s == '\0') + return rect; + if (*s++ != 'x') + goto fail; + rect.height = strtoul(s, (char**)&s, 0); + if (*s == '\0') + return rect; + if (*s++ != '+') + goto fail; + rect.x = strtoul(s, (char**)&s, 0); + if (*s == '\0') + return rect; + if (*s++ != '+') + goto fail; + rect.y = strtoul(s, (char**)&s, 0); + if (*s != '\0') + goto fail; + return rect; + +fail: + return (vout_display_place_t){0,0,0,0}; +} + +// MMAL headers comment these (getting 2 a bit wrong) but do not give +// defines +#define VXF_H_SHIFT 0 // Hflip +#define VXF_V_SHIFT 1 // Vflip +#define VXF_T_SHIFT 2 // Transpose +#define VXF_H_BIT (1 << VXF_H_SHIFT) +#define VXF_V_BIT (1 << VXF_V_SHIFT) +#define VXF_T_BIT (1 << VXF_T_SHIFT) + +static inline bool +is_vxf_transpose(const video_transform_t t) +{ + return ((unsigned int)t & VXF_T_BIT) != 0; +} + +static inline bool +is_vxf_hflip(const video_transform_t t) +{ + return ((unsigned int)t & VXF_H_BIT) != 0; +} + +static inline bool +is_vxf_vflip(const video_transform_t t) +{ + return ((unsigned int)t & VXF_V_BIT) != 0; +} + +static inline video_transform_t +swap_vxf_hv(const video_transform_t x) +{ + return (((x >> VXF_H_SHIFT) & 1) << VXF_V_SHIFT) | + (((x >> VXF_V_SHIFT) & 1) << VXF_H_SHIFT) | + (x & VXF_T_BIT); +} + +static inline video_transform_t +vxf_inverse(const video_transform_t x) +{ + return is_vxf_transpose(x) ? swap_vxf_hv(x) : x; +} + +// Transform generated by A then B +// All ops are self inverse so can simply be XORed on their own +// H & V flips after a transpose need to be swapped +static inline video_transform_t +combine_vxf(const video_transform_t a, const video_transform_t b) +{ + return a ^ (is_vxf_transpose(a) ? swap_vxf_hv(b) : b); +} + +static inline vout_display_place_t +vplace_transpose(const vout_display_place_t s) +{ + return (vout_display_place_t){ + .x = s.y, + .y = s.x, + .width = s.height, + .height = s.width + }; +} + +// hflip s in c +static inline vout_display_place_t vplace_hflip(const vout_display_place_t s, const vout_display_place_t c) +{ + return (vout_display_place_t){ + .x = c.x + (c.x + c.width) - (s.x + s.width), + .y = s.y, + .width = s.width, + .height = s.height + }; +} + +// vflip s in c +static inline vout_display_place_t vplace_vflip(const vout_display_place_t s, const vout_display_place_t c) +{ + return (vout_display_place_t){ + .x = s.x, + .y = (c.y + c.height) - (s.y - c.y) - s.height, + .width = s.width, + .height = s.height + }; +} + +static vout_display_place_t +place_out(const vout_display_cfg_t * cfg, + const video_format_t * fmt, + const vout_display_place_t r) +{ + video_format_t tfmt; + vout_display_cfg_t tcfg; + vout_display_place_t place; + + // Fix SAR if unknown + if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) { + tfmt = *fmt; + tfmt.i_sar_den = 1; + tfmt.i_sar_num = 1; + fmt = &tfmt; + } + + // Override what VLC thinks might be going on with display size + // if we know better + if (r.width != 0 && r.height != 0) + { + tcfg = *cfg; + tcfg.display.width = r.width; + tcfg.display.height = r.height; + cfg = &tcfg; + } + + vout_display_PlacePicture(&place, fmt, cfg, false); + + place.x += r.x; + place.y += r.y; + + return place; +} + +static vout_display_place_t +rect_transform(vout_display_place_t s, const vout_display_place_t c, const video_transform_t t) +{ + if (is_vxf_transpose(t)) + s = vplace_transpose(s); + if (is_vxf_hflip(t)) + s = vplace_hflip(s, c); + if (is_vxf_vflip(t) != 0) + s = vplace_vflip(s, c); + return s; +} + +static void +place_dest_rect(vout_display_sys_t * const sys, + const vout_display_cfg_t * const cfg, + const video_format_t * fmt) +{ + 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_sys_t * const sys, + const vout_display_cfg_t * const cfg, + const video_format_t * fmt) +{ + static const vout_display_place_t r0 = {0}; + + sys->spu_rect = place_out(cfg, fmt, r0); + sys->spu_rect.x = 0; + sys->spu_rect.y = 0; + + // Copy place override logic for spu pos from video_output.c + // This info doesn't appear to reside anywhere natively + + if (fmt->i_width * fmt->i_height >= (unsigned int)(sys->spu_rect.width * sys->spu_rect.height)) { + sys->spu_rect.width = fmt->i_visible_width; + sys->spu_rect.height = fmt->i_visible_height; + } + + if (ORIENT_IS_SWAP(fmt->orientation)) + sys->spu_rect = vplace_transpose(sys->spu_rect); +} + +static void +place_rects(vout_display_sys_t * const sys, + const vout_display_cfg_t * const cfg, + const video_format_t * fmt) +{ + place_dest_rect(sys, cfg, fmt); + place_spu_rect(sys, cfg, fmt); +} + +static int configure_display(const vout_display_t *vd, vout_display_sys_t *const sys, + const vout_display_cfg_t *cfg, const video_format_t *fmt) +{ + if (!cfg && !fmt) + { + msg_Err(vd, "%s: Missing cfg & fmt", __func__); + return -EINVAL; + } + + if (!fmt) + fmt = &vd->source; + + if (!cfg) + cfg = vd->cfg; + + sys->video_transform = combine_vxf((video_transform_t)fmt->orientation, sys->display_transform); + + place_rects(sys, cfg, fmt); + return 0; +} + +static void set_display_windows(vout_display_t *const vd, vout_display_sys_t *const sys) +{ + const drmu_mode_simple_params_t * const mode = drmu_output_mode_simple_params(sys->dout); + VLC_UNUSED(vd); + + sys->display_rect = (vout_display_place_t) {0, 0, mode->width, mode->height}; + + sys->win_rect = (sys->req_win.width != 0) ? + sys->req_win : + is_vxf_transpose(sys->display_transform) ? + vplace_transpose(sys->display_rect) : sys->display_rect; +} + +static void vd_drm_prepare(vout_display_t *vd, picture_t *pic, + subpicture_t *subpicture) +{ + vout_display_sys_t * const sys = vd->sys; + unsigned int n = 0; + drmu_atomic_t * da = drmu_atomic_new(sys->du); + drmu_fb_t * dfb = NULL; + drmu_rect_t r; + unsigned int i; + int ret; + + if (da == NULL) + goto fail; + + if (sys->display_set != NULL) { + msg_Warn(vd, "sys->display_set != NULL"); + drmu_atomic_unref(&sys->display_set); + } + + // * Mode (currently) doesn't change whilst running so no need to set here + + // Attempt to import the subpics + for (subpicture_t * spic = subpicture; spic != NULL; spic = spic->p_next) + { + for (subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) { + picture_t * const src = sreg->p_picture; + subpic_ent_t * const dst = sys->subpics + n; + + // If we've run out of subplanes we could allocate - give up now + if (!sys->subplanes[n]) + goto subpics_done; + + // If the same picture then assume the same contents + // We keep a ref to the previous pic to ensure that the same picture + // structure doesn't get reused and confuse us. + if (src != dst->pic) { + drmu_fb_unref(&dst->fb); + if (dst->pic != NULL) { + picture_Release(dst->pic); + dst->pic = NULL; + } + + dst->fb = copy_pic_to_fb(vd, sys->sub_fb_pool, src); + if (dst->fb == NULL) + continue; + drmu_fb_pixel_blend_mode_set(dst->fb, DRMU_FB_PIXEL_BLEND_COVERAGE); + + dst->pic = picture_Hold(src); + } + drmu_fb_crop_frac_set(dst->fb, drmu_rect_shl16(drmu_rect_vlc_format_crop(&sreg->fmt))); + + // *** More transform required + dst->pos = (drmu_rect_t){ + .x = sreg->i_x, + .y = sreg->i_y, + .w = sreg->fmt.i_visible_width, + .h = sreg->fmt.i_visible_height, + }; + dst->alpha = spic->i_alpha * sreg->i_alpha; + +// msg_Info(vd, "Orig: %dx%d, (%d,%d) %dx%d; offset %d,%d", spic->i_original_picture_width, spic->i_original_picture_height, +// sreg->i_x, sreg->i_y, src->format.i_visible_width, src->format.i_visible_height, +// sreg->fmt.i_x_offset, sreg->fmt.i_y_offset); + dst->space = drmu_rect_vlc_place(&sys->spu_rect); + + if (++n == SUBPICS_MAX) + goto subpics_done; + } + } +subpics_done: + + // Clear any other entries + for (; n != SUBPICS_MAX; ++n) { + subpic_ent_t * const dst = sys->subpics + n; + if (dst->pic != NULL) { + picture_Release(dst->pic); + dst->pic = NULL; + } + drmu_fb_unref(&dst->fb); + } + + r = sys->output_simple ? drmu_rect_vlc_place(&sys->display_rect): drmu_rect_vlc_place(&sys->dest_rect); + +#if 0 + { + static int z = 0; + if (--z < 0) { + z = 200; + msg_Info(vd, "Pic: %d,%d %dx%d/%dx%d %d/%d Fmt: %d,%d %dx%d/%dx%d %d/%d Src: %d,%d %dx%d/%dx%d %d/%d Display: %dx%d %d/%d Place: %d,%d %dx%d", + pic->format.i_x_offset, pic->format.i_y_offset, + pic->format.i_width, pic->format.i_height, + pic->format.i_visible_width, pic->format.i_visible_height, + pic->format.i_sar_num, pic->format.i_sar_den, + vd->fmt.i_x_offset, vd->fmt.i_y_offset, + vd->fmt.i_width, vd->fmt.i_height, + vd->fmt.i_visible_width, vd->fmt.i_visible_height, + vd->fmt.i_sar_num, vd->fmt.i_sar_den, + vd->source.i_x_offset, vd->source.i_y_offset, + vd->source.i_width, vd->source.i_height, + vd->source.i_visible_width, vd->source.i_visible_height, + vd->source.i_sar_num, vd->source.i_sar_den, + vd->cfg->display.width, vd->cfg->display.height, + vd->cfg->display.sar.num, vd->cfg->display.sar.den, + r.x, r.y, r.w, r.h); + } + } +#endif + +#if HAS_ZC_CMA + if (drmu_format_vlc_to_drm_cma(&pic->format, NULL) != 0) { + dfb = drmu_fb_vlc_new_pic_cma_attach(sys->du, pic); + } + else +#endif +#if HAS_DRMPRIME + if (drmu_format_vlc_to_drm_prime(&pic->format, NULL) != 0) { + dfb = drmu_fb_vlc_new_pic_attach(sys->du, pic); + } + else +#endif + + if (sys->pool_is_fb && pic->p_sys != NULL && pic->p_sys->sig == PIC_SYS_SIG) { + dfb = drmu_fb_ref(pic->p_sys->fb); + } + else if (sys->output_simple) { + dfb = copy_pic_to_fixed_fb(vd, sys, sys->pic_pool, pic); + } + else { + dfb = copy_pic_to_fb(vd, sys->pic_pool, pic); + } + + if (dfb == NULL) { + msg_Err(vd, "Failed to create frme buffer from pic"); + return; + } + // * Maybe scale cropping by vd->fmt.i_width/height / vd->source.i_width/height + // to get pic coord cropping + // Wait until we have a bad test case before doing this as I'm worried + // that we may get unexpected w/h mismatches that do unwanted scaling +#if 0 + drmu_fb_crop_frac_set(dfb, + drmu_rect_rescale( + drmu_rect_vlc_format_crop(&vd->source), + drmu_rect_shl16(drmu_rect_wh(vd->fmt.i_width, vd->fmt.i_height)), + drmu_rect_wh(vd->source.i_width, vd->source.i_height))); +#else + if (!sys->output_simple) + drmu_fb_crop_frac_set(dfb, drmu_rect_shl16(drmu_rect_vlc_format_crop(&vd->source))); +#endif + drmu_output_fb_info_set(sys->dout, dfb); + + ret = drmu_atomic_plane_add_fb(da, sys->dp, dfb, r); + drmu_atomic_output_add_props(da, sys->dout); + drmu_fb_unref(&dfb); + + if (ret != 0) { + msg_Err(vd, "Failed to set video plane: %s", strerror(-ret)); + goto fail; + } + + for (i = 0; i != SUBPICS_MAX; ++i) { + subpic_ent_t * const spe = sys->subpics + i; + +// msg_Info(vd, "pic=%dx%d @ %d,%d, r=%dx%d @ %d,%d, space=%dx%d @ %d,%d", +// spe->pos.w, spe->pos.h, spe->pos.x, spe->pos.y, +// r.w, r.h, r.x, r.y, +// spe->space.w, spe->space.h, spe->space.x, spe->space.y); + + // Rescale from sub-space + if (sys->subplanes[i]) + { + if ((ret = drmu_atomic_plane_add_fb(da, sys->subplanes[i], spe->fb, + drmu_rect_rescale(spe->pos, r, spe->space))) != 0) { + msg_Err(vd, "drmModeSetPlane for subplane %d failed: %s", i, strerror(-ret)); + } + drmu_atomic_plane_add_alpha(da, sys->subplanes[i], (spe->alpha * DRMU_PLANE_ALPHA_OPAQUE) / (0xff * 0xff)); + } + } + + sys->display_set = da; + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); +#endif + return; + +fail: + drmu_fb_unref(&dfb); + drmu_atomic_unref(&da); +} + +static void vd_drm_display(vout_display_t *vd, picture_t *p_pic, + subpicture_t *subpicture) +{ + vout_display_sys_t *const sys = vd->sys; + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); +#endif + + drmu_atomic_queue(&sys->display_set); + + if (subpicture) + subpicture_Delete(subpicture); + picture_Release(p_pic); + return; +} + +static void +destroy_drmu_pic(picture_t * pic) +{ + drmu_fb_unref(&pic->p_sys->fb); + free(pic->p_sys); + free(pic); +} + +static picture_t * +alloc_drmu_pic(vout_display_t * const vd, drmu_pool_t *const pool) +{ + const video_format_t * const fmt = &vd->fmt; + uint64_t mod; + const uint32_t drm_fmt = drmu_format_vlc_to_drm(fmt, &mod); + const drmu_fmt_info_t * fmti; + drmu_fb_t * fb; + unsigned int layers; + unsigned int i; + picture_t * pic; + picture_resource_t res = { + .p_sys = NULL, + .pf_destroy = destroy_drmu_pic, + }; + + if (drm_fmt == 0 || mod != DRM_FORMAT_MOD_LINEAR) { + msg_Warn(vd, "Failed vlc->drm format for copy_pic: %s", drmu_log_fourcc(fmt->i_chroma)); + return NULL; + } + + fb = drmu_pool_fb_new(pool, fmt->i_width, fmt->i_height, drm_fmt, mod); + if (fb == NULL) { + msg_Warn(vd, "Failed alloc for copy_pic: %dx%d", fmt->i_width, fmt->i_height); + return NULL; + } + + if ((res.p_sys = calloc(1, sizeof(*res.p_sys))) == NULL) + goto fail; + + res.p_sys->sig = PIC_SYS_SIG; + res.p_sys->fb = fb; + + fmti = drmu_fb_format_info_get(fb); + layers = drmu_fmt_info_plane_count(fmti); + + for (i = 0; i != layers; ++i) { + res.p[i].p_pixels = drmu_fb_data(fb, i); + res.p[i].i_lines = drmu_fb_height(fb) / drmu_fmt_info_hdiv(fmti, i); + res.p[i].i_pitch = drmu_fb_pitch(fb, i); + } + + if ((pic = picture_NewFromResource(fmt, &res)) == NULL) + goto fail; + + return pic; + +fail: + drmu_fb_unref(&fb); + free(res.p_sys); + return NULL; +} + +static void subpic_cache_flush(vout_display_sys_t * const sys) +{ + for (unsigned int i = 0; i != SUBPICS_MAX; ++i) { + if (sys->subpics[i].pic != NULL) { + picture_Release(sys->subpics[i].pic); + sys->subpics[i].pic = NULL; + } + drmu_fb_unref(&sys->subpics[i].fb); + } +} + +static void kill_pool(vout_display_sys_t * const sys) +{ + // Drop all cached subpics + subpic_cache_flush(sys); + + if (sys->vlc_pic_pool != NULL) { + picture_pool_Release(sys->vlc_pic_pool); + sys->vlc_pic_pool = NULL; + } +} + +static picture_pool_t * +make_fb_pool(vout_display_t * const vd, vout_display_sys_t * const sys, const unsigned int count) +{ + picture_t * pics[40]; + unsigned int pics_alloc; + picture_pool_t * pool; + + if (count > ARRAY_SIZE(pics)) + return NULL; + + for (pics_alloc = 0; pics_alloc != count; ++pics_alloc) { + if ((pics[pics_alloc] = alloc_drmu_pic(vd, sys->pic_pool)) == NULL) { + msg_Err(vd, "Failed to alloc pic pool entry %u", pics_alloc); + goto fail; + } + } + + if ((pool = picture_pool_New(pics_alloc, pics)) == NULL) { + msg_Err(vd, "Failed to alloc picture pool"); + goto fail; + } + + return pool; + +fail: + while (pics_alloc != 0) + picture_Release(pics[--pics_alloc]); + return NULL; +} + +// Actual picture pool for MMAL opaques is just a set of trivial containers +static picture_pool_t *vd_drm_pool(vout_display_t *vd, unsigned count) +{ + vout_display_sys_t * const sys = vd->sys; + + msg_Dbg(vd, "%s: fmt:%dx%d,sar:%d/%d; source:%dx%d, count=%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, count); + + if (sys->vlc_pic_pool != NULL) { + msg_Dbg(vd, "Pool exists"); + return sys->vlc_pic_pool; + } + + if (sys->pool_try_fb && drmu_format_vlc_to_drm_prime(&vd->fmt, NULL) == 0) { + if ((sys->vlc_pic_pool = make_fb_pool(vd, sys, count)) != NULL) { + msg_Dbg(vd, "Pool allocated using dmabufs"); + return sys->vlc_pic_pool; + } + msg_Warn(vd, "Pool failed dmabuf allocation"); + } + + msg_Dbg(vd, "Pool allocation from main memory"); + sys->vlc_pic_pool = picture_pool_NewFromFormat(&vd->fmt, count); + return sys->vlc_pic_pool; +} + +static const drmu_vlc_fmt_info_t * +find_fmt_fallback(const vout_display_t * const vd, const vout_display_sys_t * const sys, const vlc_fourcc_t * fallback) +{ + VLC_UNUSED(vd); + + for (; *fallback; ++fallback) { + const video_frame_format_t vf = {.i_chroma = *fallback}; + const drmu_vlc_fmt_info_t * fi; + + for (fi = drmu_vlc_fmt_info_find_vlc(&vf); + fi != NULL; + fi = drmu_vlc_fmt_info_find_vlc_next(&vf, fi)) + { + if (drmu_plane_format_check(sys->dp, drmu_vlc_fmt_info_drm_pixelformat(fi), drmu_vlc_fmt_info_drm_modifier(fi))) + return fi; + } + } + return NULL; +} + + +// Adjust *fmtp to fix format for display (tweak chroma) +static int +set_format(const vout_display_t * const vd, vout_display_sys_t * const sys, video_format_t *const fmtp) +{ + const drmu_vlc_fmt_info_t * fi = drmu_vlc_fmt_info_find_vlc(fmtp); + const uint64_t drm_mod = drmu_vlc_fmt_info_drm_modifier(fi); + const uint32_t drm_fmt = drmu_vlc_fmt_info_drm_pixelformat(fi); + + msg_Dbg(vd, "%s: %s -> %s (%#"PRIx64"): prime: %d", __func__, + drmu_log_fourcc(fmtp->i_chroma), drmu_log_fourcc(drm_fmt), drm_mod, + drmu_vlc_fmt_info_is_drmprime(fi)); + +#if HAS_ZC_CMA + if (fmtp->i_chroma == VLC_CODEC_MMAL_OPAQUE) { + // Can't deal directly with opaque - but we can always convert it to ZC I420 + fmtp->i_chroma = VLC_CODEC_MMAL_ZC_I420; + } + else +#endif + if (drmu_plane_format_check(sys->dp, drm_fmt, drm_mod)) { + // DRMP or it is a format where simple byte copying works + } + else { + const vlc_fourcc_t *fallback = vlc_fourcc_IsYUV(fmtp->i_chroma) ? + vlc_fourcc_GetYUVFallback(fmtp->i_chroma) : + vlc_fourcc_GetRGBFallback(fmtp->i_chroma); + static const vlc_fourcc_t fallback2[] = { + VLC_CODEC_I420, + VLC_CODEC_RGB32, + 0 + }; + + if ((fi = find_fmt_fallback(vd, sys, fallback)) == NULL && + (fi = find_fmt_fallback(vd, sys, fallback2)) == NULL) + return VLC_EGENERIC; + + fmtp->i_chroma = drmu_vlc_fmt_info_vlc_chroma(fi); + drmu_vlc_fmt_info_vlc_rgb_masks(fi, &fmtp->i_rmask, &fmtp->i_gmask, &fmtp->i_bmask); + + msg_Dbg(vd, "%s: Fallback %s/%x/%x/%x -> %s %"PRIx64, __func__, + drmu_log_fourcc(fmtp->i_chroma), + fmtp->i_rmask, fmtp->i_gmask, fmtp->i_bmask, + drmu_log_fourcc(drmu_vlc_fmt_info_drm_pixelformat(fi)), + drmu_vlc_fmt_info_drm_modifier(fi)); + } + return 0; +} + +static void +set_simple_format_size(video_format_t * const dst_fmt, const video_format_t * const src_fmt, const drmu_rect_t dst_rect) +{ +#if 0 + // Create a full pic with a centre cropping region + const drmu_rect_t fmt_crop = drmu_rect_vlc_format_crop(src_fmt); + const drmu_rect_t src_rect = drmu_rect_resize(drmu_rect_wh(src_fmt->i_width, src_fmt->i_height), dst_rect, fmt_crop); + const drmu_rect_t crop_rect = drmu_rect_resize(fmt_crop, dst_rect, fmt_crop); + + dst_fmt->i_width = src_rect.w; + dst_fmt->i_height = src_rect.h; + dst_fmt->i_visible_width = crop_rect.w; + dst_fmt->i_visible_height = crop_rect.h; + dst_fmt->i_x_offset = crop_rect.x; + dst_fmt->i_y_offset = crop_rect.y; +#else + // Just give us the cropped bit + VLC_UNUSED(src_fmt); + dst_fmt->i_width = dst_rect.w; + dst_fmt->i_height = dst_rect.h; + dst_fmt->i_visible_width = dst_rect.w; + dst_fmt->i_visible_height = dst_rect.h; + dst_fmt->i_x_offset = 0; + dst_fmt->i_y_offset = 0; +#endif +} + +// Updates sys but shouldn't touch vd +// Sets fmt +static int +reconfigure_display(const vout_display_t * const vd, vout_display_sys_t * const sys, + const vout_display_cfg_t * const cfg, video_format_t * const fmt) +{ + int ret; + *fmt = vd->source; + if ((ret = set_format(vd, sys, fmt)) != 0) + return ret; + configure_display(vd, sys, cfg, fmt); + if (sys->output_simple) + set_simple_format_size(fmt, &vd->source, drmu_rect_vlc_place(&sys->dest_rect)); + return 0; +} + +static int vd_drm_control(vout_display_t *vd, int query, va_list args) +{ + vout_display_sys_t * const sys = vd->sys; + video_format_t fmt; + int ret = VLC_EGENERIC; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s: query=%d", __func__, query); +#endif + + switch (query) { + case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: + case VOUT_DISPLAY_CHANGE_SOURCE_CROP: + if ((ret = reconfigure_display(vd, sys, NULL, &fmt)) != 0) + break; + if (!video_format_IsSimilar(&vd->fmt, &fmt)) { + if (vd->info.has_pictures_invalid) + vout_display_SendEventPicturesInvalid(vd); + else + msg_Err(vd, "Wanted Pic Invalid but not allowed"); + } + break; + + case VOUT_DISPLAY_CHANGE_ZOOM: + case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: + case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED: + if ((ret = reconfigure_display(vd, sys, va_arg(args, const vout_display_cfg_t *), &fmt)) != 0) + break; + if (!video_format_IsSimilar(&vd->fmt, &fmt)) { + if (vd->info.has_pictures_invalid) + vout_display_SendEventPicturesInvalid(vd); + else + msg_Err(vd, "Wanted Pic Invalid but not allowed"); + } + break; + + case VOUT_DISPLAY_RESET_PICTURES: + kill_pool(sys); + + if ((ret = reconfigure_display(vd, sys, NULL, &fmt)) != 0) + break; + vd->fmt = fmt; + break; + + default: + msg_Warn(vd, "Unknown control query %d", query); + break; + } + + return ret; +} + +static void CloseDrmVout(vout_display_t *vd) +{ + vout_display_sys_t *const sys = vd->sys; + unsigned int i; + + msg_Dbg(vd, "<<< %s", __func__); + + drmu_pool_kill(&sys->sub_fb_pool); + drmu_pool_kill(&sys->pic_pool); + + for (i = 0; i != SUBPICS_MAX; ++i) + drmu_plane_unref(sys->subplanes + i); + + kill_pool(sys); + + drmu_plane_unref(&sys->dp); + drmu_output_unref(&sys->dout); + drmu_env_kill(&sys->du); + + free(sys->subpic_chromas); + vd->info.subpicture_chromas = NULL; + + vd->sys = NULL; + free(sys); +#if TRACE_ALL + msg_Dbg(vd, ">>> %s", __func__); +#endif +} + +// VLC will take a list of subpic formats but it then ignores the fact it is a +// list and picks the 1st one whether it is 'best' or indeed whether or not it +// can use it. So we have to sort ourselves & have checked usablity. +// Higher number, higher priority. 0 == Do not use. +static int subpic_fourcc_usability(const vlc_fourcc_t fcc) +{ + switch (fcc) { + case VLC_CODEC_ARGB: + return 20; + case VLC_CODEC_RGBA: + return 22; + case VLC_CODEC_BGRA: + return 21; + case VLC_CODEC_YUVA: + return 40; + default: + break; + } + return 0; +} + +// Sort in descending priority number +static int subpic_fourcc_sort_cb(const void * a, const void * b) +{ + return subpic_fourcc_usability(*(vlc_fourcc_t *)b) - subpic_fourcc_usability(*(vlc_fourcc_t *)a); +} + +static vlc_fourcc_t * +subpic_make_chromas_from_drm(const uint32_t * const drm_chromas, const unsigned int n) +{ + vlc_fourcc_t * const c = (n == 0) ? NULL : calloc(n + 1, sizeof(*c)); + vlc_fourcc_t * p = c; + + if (c == NULL) + return NULL; + + for (unsigned int j = 0; j != n; ++j) { + if ((*p = drmu_vlc_fmt_info_vlc_chroma(drmu_vlc_fmt_info_find_drm(drm_chromas[j], 0))) != 0) + ++p; + } + + // Sort for our preferred order & remove any that would confuse VLC + qsort(c, p - c, sizeof(*c), subpic_fourcc_sort_cb); + while (p != c) { + if (subpic_fourcc_usability(p[-1]) != 0) + break; + *--p = 0; + } + + if (p == c) { + free(c); + return NULL; + } + + return c; +} + +static int +test_simple_plane_set(vout_display_t * const vd, vout_display_sys_t * const sys, + const video_format_t * const fmt, + unsigned int w, unsigned int h, + const drmu_rect_t dst_rect) +{ + drmu_atomic_t *da = drmu_atomic_new(sys->du); + drmu_fb_t *fb; + int rv = -ENOMEM; + const drmu_vlc_fmt_info_t * const fi = drmu_vlc_fmt_info_find_vlc(fmt); + + if (fi == NULL) { + msg_Err(vd, "Can't find chroma format"); + goto fail; + } + + if (da == NULL) { + msg_Warn(vd, "Failed to alloc test atomic"); + goto fail; + } + + if ((fb = drmu_pool_fb_new(sys->sub_fb_pool, w, h, + drmu_vlc_fmt_info_drm_pixelformat(fi), + drmu_vlc_fmt_info_drm_modifier(fi))) == NULL) { + msg_Warn(vd, "Failed to alloc test FB"); + goto fail; + } + + if ((rv = drmu_atomic_plane_add_fb(da, sys->dp, fb, dst_rect)) != 0) { + msg_Warn(vd, "Failed to add test FB to atomic"); + goto fail; + } + + if ((rv = drmu_atomic_commit(da, DRM_MODE_ATOMIC_TEST_ONLY)) != 0) { + msg_Warn(vd, "Failed to commit test FB"); + goto fail; + } + +fail: + drmu_atomic_unref(&da); + drmu_fb_unref(&fb); + return rv; +} + +static int OpenDrmVout(vlc_object_t *object) +{ + vout_display_t * const vd = (vout_display_t *)object; +// video_format_t * const fmtp = &vd->fmt; + video_format_t out_fmt = vd->source; + const video_format_t *const src_fmt = &vd->source; + const uint32_t src_chroma = src_fmt->i_chroma; + vout_display_sys_t *sys; + char * display_name = NULL; + int ret = VLC_EGENERIC; + int rv; + msg_Info(vd, "<<< %s: Fmt=%4.4s", __func__, (const char *)&src_fmt->i_chroma); + +// if (!var_InheritBool(vd, "fullscreen")) { +// msg_Dbg(vd, ">>> %s: Not fullscreen", __func__); +// return ret; +// } + + sys = calloc(1, sizeof(*sys)); + if (!sys) + return VLC_ENOMEM; + vd->sys = sys; + + sys->mode_id = -1; + + display_name = var_InheritString(vd, DRM_VOUT_DISPLAY_NAME); + + { + int qt_num = var_InheritInteger(vd, "qt-fullscreen-screennumber"); + const char * conn_name = qt_num == 0 ? "HDMI-A-1" : qt_num == 1 ? "HDMI-A-2" : NULL; + const char * dname; + const drmu_log_env_t log = { + .fn = drmu_log_vlc_cb, + .v = vd, + .max_level = DRMU_LOG_LEVEL_ALL + }; + + if (display_name && strcasecmp(display_name, "auto") != 0) { + if (strcasecmp(display_name, "hdmi-1") == 0) + conn_name = "HDMI-A-1"; + else if (strcasecmp(display_name, "hdmi-2") == 0) + conn_name = "HDMI-A-2"; + else + conn_name = display_name; + } + + dname = conn_name != NULL ? conn_name : ""; + + sys->du = drmu_env_new_xlease(&log); + + if (sys->du == NULL) { + if (drmu_scan_output(conn_name, &log, &sys->du, &sys->dout) == 0) + msg_Dbg(vd, "Using conn %s", dname); + } + + if (sys->du == NULL) { + char * module_name = var_InheritString(vd, DRM_VOUT_MODULE_NAME); + if (module_name != NULL) { + sys->du = drmu_env_new_open(module_name, &log); + free(module_name); + if (sys->du == NULL) + goto fail; + } + } + + if (sys->dout == NULL) { + if ((sys->dout = drmu_output_new(sys->du)) == NULL) { + msg_Err(vd, "Failed to allocate new drmu output"); + goto fail; + } + + if ((rv = drmu_output_add_output(sys->dout, conn_name)) != 0) + msg_Err(vd, "Failed to find output %s: %s", dname, strerror(-rv)); + else + msg_Dbg(vd, "Using conn %s", dname); + + if (rv != 0) + goto fail; + } + } + + drmu_env_restore_enable(sys->du); + + drmu_output_modeset_allow(sys->dout, !var_InheritBool(vd, DRM_VOUT_NO_MODESET_NAME)); + drmu_output_max_bpc_allow(sys->dout, !var_InheritBool(vd, DRM_VOUT_NO_MAX_BPC)); + + // Get frame buffer pools - try generic dmabufs first for cached + // buffers that are faster than uncached BOs + if ((sys->sub_fb_pool = drmu_pool_new_dmabuf_video(sys->du, 10)) == NULL && + (sys->sub_fb_pool = drmu_pool_new_dumb(sys->du, 10)) == NULL) + goto fail; + if ((sys->pic_pool = drmu_pool_new_dmabuf_video(sys->du, 40)) == NULL && + (sys->pic_pool = drmu_pool_new_dumb(sys->du, 40)) == NULL) + goto fail; + + // This wants to be the primary + if ((sys->dp = drmu_output_plane_ref_primary(sys->dout)) == NULL) + goto fail; + + for (unsigned int i = 0; i != SUBPICS_MAX; ++i) { + if ((sys->subplanes[i] = drmu_output_plane_ref_other(sys->dout)) == NULL) { + msg_Warn(vd, "Cannot allocate subplane %d", i); + break; + } + if (sys->subpic_chromas == NULL) { + unsigned int n; + const uint32_t * const drm_chromas = drmu_plane_formats(sys->subplanes[i], &n); + sys->subpic_chromas = subpic_make_chromas_from_drm(drm_chromas, n); + } + } + + if (set_format(vd, sys, &out_fmt)) { + msg_Warn(vd, "Failed to find compatible output format"); + goto fail; + } + + sys->mode_id = -1; + + char * mode_name = NULL; + const char * modestr; + + if (var_InheritBool(vd, DRM_VOUT_SOURCE_MODESET_NAME)) + modestr = "source"; + else { + mode_name = var_InheritString(vd, DRM_VOUT_MODE_NAME); + modestr = mode_name; + } + + if (modestr != NULL && strcmp(modestr, "none") != 0) { + drmu_mode_simple_params_t pick = { + .width = src_fmt->i_visible_width, + .height = src_fmt->i_visible_height, + .hz_x_1000 = src_fmt->i_frame_rate_base == 0 ? 0 : + (unsigned int)(((uint64_t)src_fmt->i_frame_rate * 1000) / src_fmt->i_frame_rate_base), + }; + + if (strcmp(modestr, "source") != 0) { + unsigned int w, h, hz; + if (*drmu_util_parse_mode(modestr, &w, &h, &hz) != 0) { + msg_Err(vd, "Bad mode string: '%s'", modestr); + free(mode_name); + ret = VLC_EGENERIC; + goto fail; + } + if (w && h) { + pick.width = w; + pick.height = h; + } + if (hz) + pick.hz_x_1000 = hz; + } + + sys->mode_id = drmu_output_mode_pick_simple(sys->dout, drmu_mode_pick_simple_cb, &pick); + + msg_Dbg(vd, "Mode id=%d", sys->mode_id); + + // This will set the mode on the crtc var but won't actually change the output + if (sys->mode_id >= 0) { + const drmu_mode_simple_params_t * mode; + + drmu_output_mode_id_set(sys->dout, sys->mode_id); + mode = drmu_output_mode_simple_params(sys->dout); + msg_Info(vd, "Mode %d: %dx%d@%d.%03d %d/%d - req %dx%d@%d.%d", sys->mode_id, + mode->width, mode->height, mode->hz_x_1000 / 1000, mode->hz_x_1000 % 1000, + mode->sar.num, mode->sar.den, pick.width, pick.height, pick.hz_x_1000 / 1000, pick.hz_x_1000 % 1000); + } + } + free(mode_name); + + { + char * const window_str = var_InheritString(vd, DRM_VOUT_WINDOW_NAME); + if (strcmp(window_str, "fullscreen") == 0) { + /* Leave req_win null */ + msg_Dbg(vd, "Window: fullscreen"); + } + else { + sys->req_win = str_to_rect(window_str); + if (sys->req_win.width != 0) + msg_Dbg(vd, "Window: %dx%d @ %d,%d", + sys->req_win.width, sys->req_win.height, + sys->req_win.x, sys->req_win.y); + else + msg_Warn(vd, "Window: '%s': cannot parse (usage: x++) - using fullscreen", window_str); + } + free(window_str); + } + + if (src_chroma != out_fmt.i_chroma) + msg_Warn(vd, "Cannot display %s directly trying %s", drmu_log_fourcc(src_chroma), drmu_log_fourcc(out_fmt.i_chroma)); + + set_display_windows(vd, sys); + + { + const unsigned int w = sys->display_rect.width; + const unsigned int h = sys->display_rect.height; + const drmu_rect_t sr = {.x = w / 5, .y = h / 5, .w = w / 3, .h = h / 3}; + if (test_simple_plane_set(vd, sys, &out_fmt, w, h, drmu_rect_wh(w, h)) != 0) { + msg_Warn(vd, "Failed simple pic test for mode %dx%d", w, h); + goto fail; + } + else { + msg_Dbg(vd, "OK simple pic test for mode %dx%d", w, h); + } + + // Test for full scale & position capability - the incoming stream + // might not need it but if anything changes it is better to be sure we + // can cope with it + if (test_simple_plane_set(vd, sys, &out_fmt, vd->source.i_visible_width, vd->source.i_visible_height, sr) != 0) { + msg_Warn(vd, "Failed scale pic test for %dx%d->%dx%d", vd->source.i_visible_width, vd->source.i_visible_height, sr.w, sr.h); + sys->output_simple = true; + } + else { + msg_Dbg(vd, "OK source pic test for %dx%d->%dx%d", vd->source.i_visible_width, vd->source.i_visible_height, sr.w, sr.h); + } + } + + configure_display(vd, sys, vd->cfg, &vd->source); + + if (sys->output_simple) + set_simple_format_size(&out_fmt, src_fmt, drmu_rect_vlc_place(&sys->dest_rect)); + + // Simple does not work usefully with dmabuf input + sys->pool_try_fb = !sys->output_simple && var_InheritBool(vd, DRM_VOUT_POOL_DMABUF_NAME); + + // All setup done - no possibility of error from here on + // Do final config setup & cleanup + + free(display_name); + + vd->fmt = out_fmt; + + vd->info = (vout_display_info_t){ + .is_slow = false, + .has_double_click = false, + .needs_hide_mouse = false, + .has_pictures_invalid = sys->output_simple, + .subpicture_chromas = sys->subpic_chromas + }; + + vd->pool = vd_drm_pool; + vd->prepare = vd_drm_prepare; + vd->display = vd_drm_display; + vd->control = vd_drm_control; + + { + const drmu_mode_simple_params_t * const mode = drmu_output_mode_simple_params(sys->dout); + if (vd->cfg->display.width != mode->width || vd->cfg->display.height != mode->height) { + msg_Dbg(vd, "Set display size to %ux%u", mode->width, mode->height); + vout_display_SendEventDisplaySize(vd, mode->width, mode->height); + } + } + + msg_Dbg(vd, ">>> %s", __func__); + return VLC_SUCCESS; + +fail: + CloseDrmVout(vd); + free(display_name); + return ret; +} + +vlc_module_begin() + set_shortname(N_("DRM vout")) + set_description(N_("DRM vout plugin")) + set_capability("vout display", 32) // 2 points better than fb(30), ascii(15) + add_shortcut("drm-vout") + set_category(CAT_VIDEO) + set_subcategory(SUBCAT_VIDEO_VOUT) + + add_bool(DRM_VOUT_SOURCE_MODESET_NAME, false, DRM_VOUT_SOURCE_MODESET_TEXT, DRM_VOUT_SOURCE_MODESET_LONGTEXT, false) + add_bool(DRM_VOUT_NO_MODESET_NAME, false, DRM_VOUT_NO_MODESET_TEXT, DRM_VOUT_NO_MODESET_LONGTEXT, false) + add_bool(DRM_VOUT_NO_MAX_BPC, false, DRM_VOUT_NO_MAX_BPC_TEXT, DRM_VOUT_NO_MAX_BPC_LONGTEXT, false) + add_bool(DRM_VOUT_POOL_DMABUF_NAME, false, DRM_VOUT_POOL_DMABUF_TEXT, DRM_VOUT_POOL_DMABUF_LONGTEXT, false) + add_string(DRM_VOUT_MODE_NAME, "none", DRM_VOUT_MODE_TEXT, DRM_VOUT_MODE_LONGTEXT, false) + add_string(DRM_VOUT_WINDOW_NAME, "fullscreen", DRM_VOUT_WINDOW_TEXT, DRM_VOUT_WINDOW_LONGTEXT, false) + add_string(DRM_VOUT_DISPLAY_NAME, "auto", DRM_VOUT_DISPLAY_TEXT, DRM_VOUT_DISPLAY_LONGTEXT, false) + add_string(DRM_VOUT_MODULE_NAME, DRM_MODULE, DRM_VOUT_MODULE_TEXT, DRM_VOUT_MODULE_LONGTEXT, false) + + set_callbacks(OpenDrmVout, CloseDrmVout) +vlc_module_end() + diff --git a/modules/video_output/drmu/drmu.c b/modules/video_output/drmu/drmu.c new file mode 100644 index 0000000000..55ec75f8dd --- /dev/null +++ b/modules/video_output/drmu/drmu.c @@ -0,0 +1,3888 @@ +// Needed to ensure we get a 64-bit offset to mmap when mapping BOs +#undef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 + +#include "drmu.h" +#include "drmu_fmts.h" +#include "drmu_log.h" + +#include + +#include "pollqueue.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define TRACE_PROP_NEW 0 + +#ifndef OPT_IO_CALLOC +#define OPT_IO_CALLOC 0 +#endif + +#ifndef DRM_FORMAT_P030 +#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') +#endif + +struct drmu_bo_env_s; +struct drmu_atomic_q_s; +static struct drmu_bo_env_s * env_boe(drmu_env_t * const du); +static struct pollqueue * env_pollqueue(const drmu_env_t * const du); +static struct drmu_atomic_q_s * env_atomic_q(drmu_env_t * const du); +static int env_object_state_save(drmu_env_t * const du, const uint32_t obj_id, const uint32_t obj_type); + +// Update return value with a new one for cases where we don't stop on error +static inline int rvup(int rv1, int rv2) +{ + return rv2 ? rv2 : rv1; +} + +// Use io_alloc when allocating arrays to pass into ioctls. +// +// When debugging with valgrind use calloc rather than malloc otherwise arrays +// set by ioctls that valgrind doesn't know about (e.g. all drm ioctls) will +// still be full of 'undefined'. +// For normal use malloc should be fine +#if OPT_IO_CALLOC +#define io_alloc(p, n) (uintptr_t)((p) = calloc((n), sizeof(*(p)))) +#else +#define io_alloc(p, n) (uintptr_t)((p) = malloc((n) * sizeof(*(p)))) +#endif + +// Alloc retry helper +static inline int +retry_alloc_u32(uint32_t ** const pp, uint32_t * const palloc_count, uint32_t const new_count) +{ + if (new_count <= *palloc_count) + return 0; + free(*pp); + *palloc_count = 0; + if (io_alloc(*pp, new_count) == 0) + return -ENOMEM; + *palloc_count = new_count; + return 1; +} + +//---------------------------------------------------------------------------- +// +// propinfo + +typedef struct drmu_propinfo_s { + uint64_t val; + struct drm_mode_get_property prop; +} drmu_propinfo_t; + +static uint64_t +propinfo_val(const drmu_propinfo_t * const pi) +{ + return pi == NULL ? 0 : pi->val; +} + +static uint32_t +propinfo_prop_id(const drmu_propinfo_t * const pi) +{ + return pi == NULL ? 0 : pi->prop.prop_id; +} + + +//---------------------------------------------------------------------------- +// +// Blob fns + +typedef struct drmu_blob_s { + atomic_int ref_count; // 0 == 1 ref for ease of init + struct drmu_env_s * du; + uint32_t blob_id; + // Copy of blob data as we nearly always want to keep a copy to compare + size_t len; + void * data; +} drmu_blob_t; + +static void +blob_free(drmu_blob_t * const blob) +{ + drmu_env_t * const du = blob->du; + + if (blob->blob_id != 0) { + struct drm_mode_destroy_blob dblob = { + .blob_id = blob->blob_id + }; + if (drmu_ioctl(du, DRM_IOCTL_MODE_DESTROYPROPBLOB, &dblob) != 0) + drmu_err(du, "%s: Failed to destroy blob: %s", __func__, strerror(errno)); + } + free(blob->data); + free(blob); +} + +void +drmu_blob_unref(drmu_blob_t ** const ppBlob) +{ + drmu_blob_t * const blob = *ppBlob; + + if (blob == NULL) + return; + *ppBlob = NULL; + + if (atomic_fetch_sub(&blob->ref_count, 1) != 0) + return; + + blob_free(blob); +} + +uint32_t +drmu_blob_id(const drmu_blob_t * const blob) +{ + return blob == NULL ? 0 : blob->blob_id; +} + +drmu_blob_t * +drmu_blob_ref(drmu_blob_t * const blob) +{ + if (blob != NULL) + atomic_fetch_add(&blob->ref_count, 1); + return blob; +} + +const void * +drmu_blob_data(const drmu_blob_t * const blob) +{ + return blob->data; +} + +size_t +drmu_blob_len(const drmu_blob_t * const blob) +{ + return blob->len; +} + +drmu_blob_t * +drmu_blob_new(drmu_env_t * const du, const void * const data, const size_t len) +{ + int rv; + drmu_blob_t * blob = calloc(1, sizeof(*blob)); + struct drm_mode_create_blob cblob = { + .data = (uintptr_t)data, + .length = (uint32_t)len, + .blob_id = 0 + }; + + if (blob == NULL) { + drmu_err(du, "%s: Unable to alloc blob", __func__); + return NULL; + } + blob->du = du; + + if ((blob->data = malloc(len)) == NULL) { + drmu_err(du, "%s: Unable to alloc blob data", __func__); + goto fail; + } + blob->len = len; + memcpy(blob->data, data, len); + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_CREATEPROPBLOB, &cblob)) != 0) { + drmu_err(du, "%s: Unable to create blob: data=%p, len=%zu: %s", __func__, + data, len, strerror(-rv)); + goto fail; + } + + atomic_init(&blob->ref_count, 0); + blob->blob_id = cblob.blob_id; + return blob; + +fail: + blob_free(blob); + return NULL; +} + +int +drmu_blob_update(drmu_env_t * const du, drmu_blob_t ** const ppblob, const void * const data, const size_t len) +{ + drmu_blob_t * blob = *ppblob; + + if (data == NULL || len == 0) { + drmu_blob_unref(ppblob); + return 0; + } + + if (blob && len == blob->len && memcmp(data, blob->data, len) == 0) + return 0; + + if ((blob = drmu_blob_new(du, data, len)) == NULL) + return -ENOMEM; + drmu_blob_unref(ppblob); + *ppblob = blob; + return 0; +} + +// Data alloced here needs freeing later +static int +blob_data_read(drmu_env_t * const du, uint32_t blob_id, void ** const ppdata, size_t * plen) +{ + uint8_t * data; + struct drm_mode_get_blob gblob = {.blob_id = blob_id}; + int rv; + + *ppdata = NULL; + *plen = 0; + + if (blob_id == 0) + return 0; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPBLOB, &gblob)) != 0) + return rv; + + if (gblob.length == 0) + return 0; + + if ((gblob.data = io_alloc(data, gblob.length)) == 0) + return -ENOMEM; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPBLOB, &gblob)) != 0) { + free(data); + return rv; + } + + *ppdata = data; + *plen = gblob.length; + return 0; +} + +// Copy existing blob into a new one +// Useful when saving preexisiting values +drmu_blob_t * +drmu_blob_copy_id(drmu_env_t * const du, uint32_t blob_id) +{ + void * data; + size_t len; + drmu_blob_t * blob = NULL; + + if (blob_data_read(du, blob_id, &data, &len) == 0) + blob = drmu_blob_new(du, data, len); // * This copies data - could just get it to take the malloc + + free(data); + return blob; +} + +static void +atomic_prop_blob_unref(void * v) +{ + drmu_blob_t * blob = v; + drmu_blob_unref(&blob); +} + +static void +atomic_prop_blob_ref(void * v) +{ + drmu_blob_ref(v); +} + +int +drmu_atomic_add_prop_blob(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_blob_t * const blob) +{ + int rv; + static const drmu_atomic_prop_fns_t fns = { + .ref = atomic_prop_blob_ref, + .unref = atomic_prop_blob_unref, + .commit = drmu_prop_fn_null_commit + }; + + if (blob == NULL) + return drmu_atomic_add_prop_value(da, obj_id, prop_id, 0); + + rv = drmu_atomic_add_prop_generic(da, obj_id, prop_id, drmu_blob_id(blob), &fns, blob); + if (rv != 0) + drmu_warn(drmu_atomic_env(da), "%s: Failed to add blob obj_id=%#x, prop_id=%#x: %s", __func__, obj_id, prop_id, strerror(-rv)); + + return rv; +} + +//---------------------------------------------------------------------------- +// +// Enum fns + +typedef struct drmu_prop_enum_s { + uint32_t id; + uint32_t flags; + unsigned int n; + const struct drm_mode_property_enum * enums; + char name[DRM_PROP_NAME_LEN]; +} drmu_prop_enum_t; + +static void +prop_enum_free(drmu_prop_enum_t * const pen) +{ + free((void*)pen->enums); // Cast away const + free(pen); +} + +static int +prop_enum_qsort_cb(const void * va, const void * vb) +{ + const struct drm_mode_property_enum * a = va; + const struct drm_mode_property_enum * b = vb; + return strcmp(a->name, b->name); +} + +// NULL if not found +const uint64_t * +drmu_prop_enum_value(const drmu_prop_enum_t * const pen, const char * const name) +{ + if (pen != NULL && name != NULL) { + unsigned int i = pen->n / 2; + unsigned int a = 0; + unsigned int b = pen->n; + + if (name == NULL) + return NULL; + + while (a < b) { + const int r = strcmp(name, pen->enums[i].name); + + if (r == 0) + return (const uint64_t *)&pen->enums[i].value; // __u64 defn != uint64_t defn always :-( + + if (r < 0) { + b = i; + i = (i + a) / 2; + } else { + a = i + 1; + i = (i + b) / 2; + } + } + } + return NULL; +} + +uint64_t +drmu_prop_bitmask_value(const drmu_prop_enum_t * const pen, const char * const name) +{ + const uint64_t *const p = drmu_prop_enum_value(pen, name); + return p == NULL || *p >= 64 || (pen->flags & DRM_MODE_PROP_BITMASK) == 0 ? + (uint64_t)0 : (uint64_t)1 << *p; +} + +uint32_t +drmu_prop_enum_id(const drmu_prop_enum_t * const pen) +{ + return pen == NULL ? 0 : pen->id; +} + +void +drmu_prop_enum_delete(drmu_prop_enum_t ** const pppen) +{ + drmu_prop_enum_t * const pen = *pppen; + if (pen == NULL) + return; + *pppen = NULL; + + prop_enum_free(pen); +} + +drmu_prop_enum_t * +drmu_prop_enum_new(drmu_env_t * const du, const uint32_t id) +{ + drmu_prop_enum_t * pen; + struct drm_mode_property_enum * enums = NULL; + unsigned int retries; + int rv; + + // If id 0 return without warning for ease of getting props on init + if (id == 0 || (pen = calloc(1, sizeof(*pen))) == NULL) + return NULL; + pen->id = id; + + // Docn says we must loop till stable as there may be hotplug races + for (retries = 0; retries < 8; ++retries) { + struct drm_mode_get_property prop = { + .prop_id = id, + .count_enum_blobs = pen->n, + .enum_blob_ptr = (uintptr_t)enums + }; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &prop)) != 0) { + drmu_err(du, "%s: get property failed: %s", __func__, strerror(-rv)); + goto fail; + } + + if (prop.count_enum_blobs == 0 || + (prop.flags & (DRM_MODE_PROP_ENUM | DRM_MODE_PROP_BITMASK)) == 0) { + drmu_err(du, "%s: not an enum: flags=%#x, enums=%d", __func__, prop.flags, prop.count_enum_blobs); + goto fail; + } + + if (pen->n >= prop.count_enum_blobs) { + pen->flags = prop.flags; + pen->n = prop.count_enum_blobs; + memcpy(pen->name, prop.name, sizeof(pen->name)); + break; + } + + free(enums); + + pen->n = prop.count_enum_blobs; + if (io_alloc(enums, pen->n) == 0) + goto fail; + } + if (retries >= 8) { + drmu_err(du, "%s: Too many retries", __func__); + goto fail; + } + + qsort(enums, pen->n, sizeof(*enums), prop_enum_qsort_cb); + pen->enums = enums; + +#if TRACE_PROP_NEW + if (!pen->n) { + drmu_info(du, "%32s %2d: no properties"); + } + else { + unsigned int i; + for (i = 0; i != pen->n; ++i) { + drmu_info(du, "%32s %2d:%02d: %32s %#"PRIx64, pen->name, pen->id, i, pen->enums[i].name, pen->enums[i].value); + } + } +#endif + + return pen; + +fail: + free(enums); + prop_enum_free(pen); + return NULL; +} + +int +drmu_atomic_add_prop_enum(drmu_atomic_t * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const char * const name) +{ + const uint64_t * const pval = drmu_prop_enum_value(pen, name); + int rv; + + rv = (pval == NULL) ? -EINVAL : + drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_enum_id(pen), *pval, NULL, NULL); + + if (rv != 0 && name != NULL) + drmu_warn(drmu_atomic_env(da), "%s: Failed to add enum obj_id=%#x, prop_id=%#x, name='%s': %s", __func__, + obj_id, drmu_prop_enum_id(pen), name, strerror(-rv)); + + return rv; +} + +int +drmu_atomic_add_prop_bitmask(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const uint64_t val) +{ + int rv; + + rv = !pen ? -ENOENT : + ((pen->flags & DRM_MODE_PROP_BITMASK) == 0) ? -EINVAL : + drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_enum_id(pen), val, NULL, NULL); + + if (rv != 0) + drmu_warn(drmu_atomic_env(da), "%s: Failed to add bitmask obj_id=%#x, prop_id=%#x, val=%#"PRIx64": %s", __func__, + obj_id, drmu_prop_enum_id(pen), val, strerror(-rv)); + + return rv; +} + +//---------------------------------------------------------------------------- +// +// Range + +typedef struct drmu_prop_range_s { + uint32_t id; + uint32_t flags; + uint64_t range[2]; + char name[DRM_PROP_NAME_LEN]; +} drmu_prop_range_t; + +static void +prop_range_free(drmu_prop_range_t * const pra) +{ + free(pra); +} + +void +drmu_prop_range_delete(drmu_prop_range_t ** pppra) +{ + drmu_prop_range_t * const pra = *pppra; + + if (pra == NULL) + return; + *pppra = NULL; + + prop_range_free(pra); +} + +bool +drmu_prop_range_validate(const drmu_prop_range_t * const pra, const uint64_t x) +{ + if (pra == NULL) + return false; + if ((pra->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_SIGNED_RANGE) { + return (int64_t)pra->range[0] <= (int64_t)x && (int64_t)pra->range[1] >= (int64_t)x; + } + return pra->range[0] <= x && pra->range[1] >= x; +} + +bool +drmu_prop_range_immutable(const drmu_prop_range_t * const pra) +{ + return !pra || (pra->flags & DRM_MODE_PROP_IMMUTABLE) != 0; +} + +uint64_t +drmu_prop_range_max(const drmu_prop_range_t * const pra) +{ + return pra == NULL ? 0 : pra->range[1]; +} + +uint64_t +drmu_prop_range_min(const drmu_prop_range_t * const pra) +{ + return pra == NULL ? 0 : pra->range[0]; +} + +uint32_t +drmu_prop_range_id(const drmu_prop_range_t * const pra) +{ + return pra == NULL ? 0 : pra->id; +} + +const char * +drmu_prop_range_name(const drmu_prop_range_t * const pra) +{ + return pra == NULL ? "{norange}" : pra->name; +} + +drmu_prop_range_t * +drmu_prop_range_new(drmu_env_t * const du, const uint32_t id) +{ + drmu_prop_range_t * pra; + int rv; + + // If id 0 return without warning for ease of getting props on init + if (id == 0 || (pra = calloc(1, sizeof(*pra))) == NULL) + return NULL; + pra->id = id; + + // We are expecting exactly 2 values so no need to loop + { + struct drm_mode_get_property prop = { + .prop_id = id, + .count_values = 2, + .values_ptr = (uintptr_t)pra->range + }; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &prop)) != 0) { + drmu_err(du, "%s: get property failed: %s", __func__, strerror(-rv)); + goto fail; + } + + if ((prop.flags & DRM_MODE_PROP_RANGE) == 0 && + (prop.flags & DRM_MODE_PROP_EXTENDED_TYPE) != DRM_MODE_PROP_SIGNED_RANGE) { + drmu_err(du, "%s: not an signed range: flags=%#x", __func__, prop.flags); + goto fail; + } + if ((prop.count_values != 2)) { + drmu_err(du, "%s: unexpected count values: %d", __func__, prop.count_values); + goto fail; + } + + pra->flags = prop.flags; + memcpy(pra->name, prop.name, sizeof(pra->name)); + } + +#if TRACE_PROP_NEW + drmu_info(du, "%32s %2d: %"PRId64"->%"PRId64, pra->name, pra->id, pra->range[0], pra->range[1]); +#endif + + return pra; + +fail: + prop_range_free(pra); + return NULL; +} + +int +drmu_atomic_add_prop_range(drmu_atomic_t * const da, const uint32_t obj_id, const drmu_prop_range_t * const pra, const uint64_t x) +{ + int rv; + + rv = !pra ? -ENOENT : + !drmu_prop_range_validate(pra, x) ? -EINVAL : + drmu_prop_range_immutable(pra) ? -EPERM : + drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_range_id(pra), x, NULL, NULL); + + if (rv != 0) + drmu_warn(drmu_atomic_env(da), + "%s: Failed to add range %s obj_id=%#x, prop_id=%#x, val=%"PRId64", range=%"PRId64"->%"PRId64": %s", + __func__, drmu_prop_range_name(pra), + obj_id, drmu_prop_range_id(pra), x, drmu_prop_range_min(pra), drmu_prop_range_max(pra), strerror(-rv)); + + return rv; +} + +//---------------------------------------------------------------------------- +// +// Object ID (tracked) + +typedef struct drmu_prop_object_s { + atomic_int ref_count; + uint32_t obj_id; + uint32_t prop_id; + uint32_t value; +} drmu_prop_object_t; + +uint32_t +drmu_prop_object_value(const drmu_prop_object_t * const obj) +{ + return !obj ? 0 : obj->value; +} + +void +drmu_prop_object_unref(drmu_prop_object_t ** ppobj) +{ + drmu_prop_object_t * const obj = *ppobj; + + if (!obj) + return; + *ppobj = NULL; + + if (atomic_fetch_sub(&obj->ref_count, 1) != 0) + return; + + free(obj); +} + +drmu_prop_object_t * +drmu_prop_object_new_propinfo(drmu_env_t * const du, const uint32_t obj_id, const drmu_propinfo_t * const pi) +{ + const uint64_t val = propinfo_val(pi); + const uint32_t prop_id = propinfo_prop_id(pi); + + if (obj_id == 0 || prop_id == 0) + return NULL; + + if ((val >> 32) != 0) { // We expect 32-bit values + drmu_err(du, "Bad object id value: %#"PRIx64, val); + return NULL; + } + else { + drmu_prop_object_t *const obj = calloc(1, sizeof(*obj)); + + if (obj == NULL) + return obj; + + obj->obj_id = obj_id; + obj->prop_id = prop_id; + obj->value = (uint32_t)val; + return obj; + } +} + +static void +atomic_prop_object_unref(void * v) +{ + drmu_prop_object_t * obj = v; + drmu_prop_object_unref(&obj); +} +static void +atomic_prop_object_ref(void * v) +{ + drmu_prop_object_t * obj = v; + atomic_fetch_add(&obj->ref_count, 1); +} +static void +atomic_prop_object_commit(void * v, uint64_t val) +{ + drmu_prop_object_t * obj = v; + obj->value = (uint32_t)val; +} + +int +drmu_atomic_add_prop_object(drmu_atomic_t * const da, drmu_prop_object_t * obj, uint32_t val) +{ + static const drmu_atomic_prop_fns_t fns = { + .ref = atomic_prop_object_ref, + .unref = atomic_prop_object_unref, + .commit = atomic_prop_object_commit, + }; + + return drmu_atomic_add_prop_generic(da, obj->obj_id, obj->prop_id, val, &fns, obj); +} + +//---------------------------------------------------------------------------- +// +// BO fns +// +// Beware that when importing from FD we need to check that we don't already +// have the BO as multiple FDs can map to the same BO and a single close will +// close it irrespective of how many imports have occured. + +enum drmu_bo_type_e { + BO_TYPE_NONE = 0, + BO_TYPE_FD, // Created from FD import + BO_TYPE_DUMB, // Locally allocated + BO_TYPE_EXTERNAL, // Externally allocated and closed +}; + +// BO handles come in 2 very distinct types: DUMB and FD +// They need very different alloc & free but BO usage is the same for both +// so it is better to have a single type. +typedef struct drmu_bo_s { + // Arguably could be non-atomic for FD as then it is always protected by mutex + atomic_int ref_count; + struct drmu_env_s * du; + enum drmu_bo_type_e bo_type; + uint32_t handle; + + // FD only els - FD BOs need to be tracked globally + struct drmu_bo_s * next; + struct drmu_bo_s * prev; +} drmu_bo_t; + +typedef struct drmu_bo_env_s { + pthread_mutex_t lock; + drmu_bo_t * fd_head; +} drmu_bo_env_t; + +static int +bo_close(drmu_env_t * const du, uint32_t * const ph) +{ + struct drm_gem_close gem_close = {.handle = *ph}; + + if (gem_close.handle == 0) + return 0; + *ph = 0; + + return drmu_ioctl(du, DRM_IOCTL_GEM_CLOSE, &gem_close); +} + +// BOE lock expected +static void +bo_free_dumb(drmu_bo_t * const bo) +{ + if (bo->handle != 0) { + drmu_env_t * const du = bo->du; + struct drm_mode_destroy_dumb destroy_env = {.handle = bo->handle}; + + if (drmu_ioctl(du, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_env) != 0) + drmu_warn(du, "%s: Failed to destroy dumb handle %d", __func__, bo->handle); + } + free(bo); +} + +static void +bo_free_fd(drmu_bo_t * const bo) +{ + if (bo->handle != 0) { + drmu_env_t * const du = bo->du; + drmu_bo_env_t *const boe = env_boe(du); + const uint32_t h = bo->handle; + + if (bo_close(du, &bo->handle) != 0) + drmu_warn(du, "%s: Failed to close BO handle %d", __func__, h); + if (bo->next != NULL) + bo->next->prev = bo->prev; + if (bo->prev != NULL) + bo->prev->next = bo->next; + else + boe->fd_head = bo->next; + } + free(bo); +} + + +void +drmu_bo_unref(drmu_bo_t ** const ppbo) +{ + drmu_bo_t * const bo = *ppbo; + + if (bo == NULL) + return; + *ppbo = NULL; + + switch (bo->bo_type) { + case BO_TYPE_FD: + { + drmu_bo_env_t * const boe = env_boe(bo->du); + + pthread_mutex_lock(&boe->lock); + if (atomic_fetch_sub(&bo->ref_count, 1) == 0) + bo_free_fd(bo); + pthread_mutex_unlock(&boe->lock); + break; + } + case BO_TYPE_DUMB: + if (atomic_fetch_sub(&bo->ref_count, 1) == 0) + bo_free_dumb(bo); + break; + case BO_TYPE_EXTERNAL: + // Simple imported BO - close dealt with elsewhere + if (atomic_fetch_sub(&bo->ref_count, 1) == 0) + free(bo); + break; + case BO_TYPE_NONE: + default: + free(bo); + break; + } +} + + +drmu_bo_t * +drmu_bo_ref(drmu_bo_t * const bo) +{ + if (bo != NULL) + atomic_fetch_add(&bo->ref_count, 1); + return bo; +} + +int +drmu_bo_export_fd(drmu_bo_t * bo, uint32_t flags) +{ + struct drm_prime_handle prime_handle = { + .handle = bo->handle, + .flags = flags == 0 ? DRM_RDWR | DRM_CLOEXEC : flags, + .fd = 0 + }; + + if (drmu_ioctl(bo->du, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime_handle) != 0) + return -1; + + return prime_handle.fd; +} + +static drmu_bo_t * +bo_alloc(drmu_env_t *const du, enum drmu_bo_type_e bo_type) +{ + drmu_bo_t * const bo = calloc(1, sizeof(*bo)); + if (bo == NULL) { + drmu_err(du, "Failed to alloc BO"); + return NULL; + } + + bo->du = du; + bo->bo_type = bo_type; + atomic_init(&bo->ref_count, 0); + return bo; +} + +drmu_bo_t * +drmu_bo_new_external(drmu_env_t *const du, const uint32_t bo_handle) +{ + drmu_bo_t *const bo = bo_alloc(du, BO_TYPE_EXTERNAL); + + if (bo == NULL) { + drmu_err(du, "%s: Failed to alloc BO", __func__); + return NULL; + } + + bo->handle = bo_handle; + return bo; +} + +drmu_bo_t * +drmu_bo_new_fd(drmu_env_t *const du, const int fd) +{ + drmu_bo_env_t * const boe = env_boe(du); + drmu_bo_t * bo = NULL; + struct drm_prime_handle ph = { .fd = fd }; + int rv; + + pthread_mutex_lock(&boe->lock); + + if ((rv = drmu_ioctl(du, DRM_IOCTL_PRIME_FD_TO_HANDLE, &ph)) != 0) { + drmu_err(du, "Failed to convert fd %d to BO: %s", __func__, fd, strerror(-rv)); + goto unlock; + } + + bo = boe->fd_head; + while (bo != NULL && bo->handle != ph.handle) + bo = bo->next; + + if (bo != NULL) { + drmu_bo_ref(bo); + } + else { + if ((bo = bo_alloc(du, BO_TYPE_FD)) == NULL) { + bo_close(du, &ph.handle); + } + else { + bo->handle = ph.handle; + + if ((bo->next = boe->fd_head) != NULL) + bo->next->prev = bo; + boe->fd_head = bo; + } + } + +unlock: + pthread_mutex_unlock(&boe->lock); + return bo; +} + +// Updates the passed dumb structure with the results of creation +drmu_bo_t * +drmu_bo_new_dumb(drmu_env_t *const du, struct drm_mode_create_dumb * const d) +{ + drmu_bo_t *bo = bo_alloc(du, BO_TYPE_DUMB); + int rv; + + if (bo == NULL) + return NULL; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_CREATE_DUMB, d)) != 0) + { + drmu_err(du, "%s: Create dumb %dx%dx%d failed: %s", __func__, + d->width, d->height, d->bpp, strerror(-rv)); + drmu_bo_unref(&bo); // After this point aux is bound to dfb and gets freed with it + return NULL; + } + + bo->handle = d->handle; + return bo; +} + +void +drmu_bo_env_uninit(drmu_bo_env_t * const boe) +{ + if (boe->fd_head != NULL) + drmu_warn(boe->fd_head->du, "%s: fd chain not null", __func__); + boe->fd_head = NULL; + pthread_mutex_destroy(&boe->lock); +} + +void +drmu_bo_env_init(drmu_bo_env_t * boe) +{ + boe->fd_head = NULL; + pthread_mutex_init(&boe->lock, NULL); +} + +//---------------------------------------------------------------------------- +// +// FB fns + +typedef struct drmu_fb_s { + atomic_int ref_count; // 0 == 1 ref for ease of init + + struct drmu_env_s * du; + + const struct drmu_fmt_info_s * fmt_info; + + struct drm_mode_fb_cmd2 fb; + + drmu_rect_t active; // Area that was asked for inside the buffer; pixels + drmu_rect_t crop; // Cropping inside that; fractional pels (16.16, 16.16) + + int buf_fd; + + void * map_ptr; + size_t map_size; + size_t map_pitch; + + drmu_bo_t * bo_list[4]; + + drmu_color_encoding_t color_encoding; // Assumed to be constant strings that don't need freeing + drmu_color_range_t color_range; + drmu_colorspace_t colorspace; + const char * pixel_blend_mode; + drmu_chroma_siting_t chroma_siting; + drmu_isset_t hdr_metadata_isset; + struct hdr_output_metadata hdr_metadata; + + void * pre_delete_v; + drmu_fb_pre_delete_fn pre_delete_fn; + + void * on_delete_v; + drmu_fb_on_delete_fn on_delete_fn; + + // We pass a pointer to this to DRM which defines it as s32 so do not use + // int that might be s64. + int32_t fence_fd; +} drmu_fb_t; + +int +drmu_fb_out_fence_wait(drmu_fb_t * const fb, const int timeout_ms) +{ + struct pollfd pf; + int rv; + + if (fb->fence_fd == -1) + return -EINVAL; + + do { + pf.fd = fb->fence_fd; + pf.events = POLLIN; + pf.revents = 0; + + rv = poll(&pf, 1, timeout_ms); + if (rv >= 0) + break; + + rv = -errno; + } while (rv == -EINTR); + + if (rv == 0) + return 0; + + // Both on error & success close the fd + close(fb->fence_fd); + fb->fence_fd = -1; + return rv; +} + +void +drmu_fb_int_free(drmu_fb_t * const dfb) +{ + drmu_env_t * const du = dfb->du; + unsigned int i; + + if (dfb->pre_delete_fn && dfb->pre_delete_fn(dfb, dfb->pre_delete_v) != 0) + return; + + // * If we implement callbacks this logic will want revision + if (dfb->fence_fd != -1) { + drmu_warn(du, "Out fence still set on FB on delete"); + if (drmu_fb_out_fence_wait(dfb, 500) == 0) { + drmu_err(du, "Out fence stuck in FB free"); + close(dfb->fence_fd); + } + } + + if (dfb->fb.fb_id != 0) + drmu_ioctl(du, DRM_IOCTL_MODE_RMFB, &dfb->fb.fb_id); + + if (dfb->map_ptr != NULL && dfb->map_ptr != MAP_FAILED) + munmap(dfb->map_ptr, dfb->map_size); + + for (i = 0; i != 4; ++i) + drmu_bo_unref(dfb->bo_list + i); + + if (dfb->buf_fd != -1) + close(dfb->buf_fd); + + // Call on_delete last so we have stopped using anything that might be + // freed by it + { + void * const v = dfb->on_delete_v; + const drmu_fb_on_delete_fn fn = dfb->on_delete_fn; + + free(dfb); + + if (fn) + fn(v); + } +} + +void +drmu_fb_unref(drmu_fb_t ** const ppdfb) +{ + drmu_fb_t * const dfb = *ppdfb; + + if (dfb == NULL) + return; + *ppdfb = NULL; + + if (atomic_fetch_sub(&dfb->ref_count, 1) > 0) + return; + + drmu_fb_int_free(dfb); +} + +static void +atomic_prop_fb_unref_cb(void * v) +{ + drmu_fb_t * dfb = v; + drmu_fb_unref(&dfb); +} + +drmu_fb_t * +drmu_fb_ref(drmu_fb_t * const dfb) +{ + if (dfb != NULL) + atomic_fetch_add(&dfb->ref_count, 1); + return dfb; +} + +static void +atomic_prop_fb_ref_cb(void * v) +{ + drmu_fb_ref(v); +} + +// Beware: used by pool fns +void +drmu_fb_pre_delete_set(drmu_fb_t *const dfb, drmu_fb_pre_delete_fn fn, void * v) +{ + dfb->pre_delete_fn = fn; + dfb->pre_delete_v = v; +} + +void +drmu_fb_pre_delete_unset(drmu_fb_t *const dfb) +{ + dfb->pre_delete_fn = (drmu_fb_pre_delete_fn)0; + dfb->pre_delete_v = NULL; +} + +int +drmu_fb_pixel_blend_mode_set(drmu_fb_t *const dfb, const char * const mode) +{ + dfb->pixel_blend_mode = mode; + return 0; +} + +uint32_t +drmu_fb_pitch(const drmu_fb_t *const dfb, const unsigned int layer) +{ + return layer >= 4 ? 0 : dfb->fb.pitches[layer]; +} + +uint32_t +drmu_fb_pitch2(const drmu_fb_t *const dfb, const unsigned int layer) +{ + if (layer < 4){ + const uint64_t m = dfb->fb.modifier[layer]; + const uint64_t s2 = fourcc_mod_broadcom_param(m); + + // No good masks to check modifier so check if we convert back it matches + if (m != 0 && m != DRM_FORMAT_MOD_INVALID && + DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(s2) == m) + return (uint32_t)s2; + } + return 0; +} + +void * +drmu_fb_data(const drmu_fb_t *const dfb, const unsigned int layer) +{ + return (layer >= 4 || dfb->map_ptr == NULL) ? NULL : (uint8_t * )dfb->map_ptr + dfb->fb.offsets[layer]; +} + +drmu_bo_t * +drmu_fb_bo(const drmu_fb_t * const dfb, const unsigned int layer) +{ + return (layer >= 4) ? NULL : dfb->bo_list[layer]; +} + +uint32_t +drmu_fb_width(const drmu_fb_t *const dfb) +{ + return dfb->fb.width; +} + +uint32_t +drmu_fb_height(const drmu_fb_t *const dfb) +{ + return dfb->fb.height; +} + +// Set cropping (fractional) - x, y, relative to active x, y (and must be +ve) +int +drmu_fb_crop_frac_set(drmu_fb_t *const dfb, drmu_rect_t crop_frac) +{ + // Sanity check + if (crop_frac.x + crop_frac.w > (dfb->active.w << 16) || + crop_frac.y + crop_frac.h > (dfb->active.h << 16)) + return -EINVAL; + + dfb->crop = (drmu_rect_t){ + .x = crop_frac.x, + .y = crop_frac.y, + .w = crop_frac.w, + .h = crop_frac.h + }; + return 0; +} + +drmu_rect_t +drmu_fb_crop_frac(const drmu_fb_t *const dfb) +{ + return dfb->crop; +} + +drmu_rect_t +drmu_fb_active(const drmu_fb_t *const dfb) +{ + return dfb->active; +} + + +// active is in pixels +void +drmu_fb_int_fmt_size_set(drmu_fb_t *const dfb, uint32_t fmt, uint32_t w, uint32_t h, const drmu_rect_t active) +{ + dfb->fmt_info = drmu_fmt_info_find_fmt(fmt); + dfb->fb.pixel_format = fmt; + dfb->fb.width = w; + dfb->fb.height = h; + dfb->active = active; + dfb->crop = drmu_rect_shl16(active); + dfb->chroma_siting = drmu_fmt_info_chroma_siting(dfb->fmt_info); +} + +void +drmu_fb_color_set(drmu_fb_t *const dfb, const drmu_color_encoding_t enc, const drmu_color_range_t range, const drmu_colorspace_t space) +{ + dfb->color_encoding = enc; + dfb->color_range = range; + dfb->colorspace = space; +} + +void +drmu_fb_chroma_siting_set(drmu_fb_t *const dfb, const drmu_chroma_siting_t siting) +{ + dfb->chroma_siting = siting; +} + +void +drmu_fb_int_on_delete_set(drmu_fb_t *const dfb, drmu_fb_on_delete_fn fn, void * v) +{ + dfb->on_delete_fn = fn; + dfb->on_delete_v = v; +} + +void +drmu_fb_int_bo_set(drmu_fb_t *const dfb, unsigned int i, drmu_bo_t * const bo) +{ + dfb->bo_list[i] = bo; +} + +void +drmu_fb_int_fd_set(drmu_fb_t *const dfb, const int fd) +{ + dfb->buf_fd = fd; +} + +void +drmu_fb_int_mmap_set(drmu_fb_t *const dfb, void * const buf, const size_t size, const size_t pitch) +{ + dfb->map_ptr = buf; + dfb->map_size = size; + dfb->map_pitch = pitch; +} + +void +drmu_fb_int_layer_mod_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset, uint64_t modifier) +{ + dfb->fb.handles[i] = dfb->bo_list[obj_idx]->handle; + dfb->fb.pitches[i] = pitch; + dfb->fb.offsets[i] = offset; + // We should be able to have "invalid" modifiers and not set the flag + // but that produces EINVAL - so don't do that + dfb->fb.modifier[i] = (modifier == DRM_FORMAT_MOD_INVALID) ? 0 : modifier; +} + +void +drmu_fb_int_layer_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset) +{ + drmu_fb_int_layer_mod_set(dfb, i, obj_idx, pitch, offset, DRM_FORMAT_MOD_INVALID); +} + +int +drmu_fb_int_make(drmu_fb_t *const dfb) +{ + drmu_env_t * du = dfb->du; + int rv; + + dfb->fb.flags = (dfb->fb.modifier[0] == DRM_FORMAT_MOD_INVALID || + dfb->fb.modifier[0] == DRM_FORMAT_MOD_LINEAR) ? 0 : DRM_MODE_FB_MODIFIERS; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_ADDFB2, &dfb->fb)) != 0) + drmu_err(du, "AddFB2 failed: %s", strerror(-rv)); + return rv; +} + +void +drmu_fb_hdr_metadata_set(drmu_fb_t *const dfb, const struct hdr_output_metadata * meta) +{ + if (meta == NULL) { + dfb->hdr_metadata_isset = DRMU_ISSET_NULL; + } + else { + dfb->hdr_metadata_isset = DRMU_ISSET_SET; + dfb->hdr_metadata = *meta; + } +} + +drmu_isset_t +drmu_fb_hdr_metadata_isset(const drmu_fb_t *const dfb) +{ + return dfb->hdr_metadata_isset; +} + +const struct hdr_output_metadata * +drmu_fb_hdr_metadata_get(const drmu_fb_t *const dfb) +{ + return dfb->hdr_metadata_isset == DRMU_ISSET_SET ? &dfb->hdr_metadata : NULL; +} + +drmu_colorspace_t +drmu_fb_colorspace_get(const drmu_fb_t * const dfb) +{ + return dfb->colorspace; +} + +const char * +drmu_color_range_to_broadcast_rgb(const drmu_color_range_t range) +{ + if (!drmu_color_range_is_set(range)) + return DRMU_BROADCAST_RGB_UNSET; + else if (strcmp(range, DRMU_COLOR_RANGE_YCBCR_FULL_RANGE) == 0) + return DRMU_BROADCAST_RGB_FULL; + else if (strcmp(range, DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE) == 0) + return DRMU_BROADCAST_RGB_LIMITED_16_235; + return NULL; +} + +drmu_color_range_t +drmu_fb_color_range_get(const drmu_fb_t * const dfb) +{ + return dfb->color_range; +} + +const struct drmu_fmt_info_s * +drmu_fb_format_info_get(const drmu_fb_t * const dfb) +{ + return dfb->fmt_info; +} + +drmu_fb_t * +drmu_fb_int_alloc(drmu_env_t * const du) +{ + drmu_fb_t * const dfb = calloc(1, sizeof(*dfb)); + if (dfb == NULL) + return NULL; + + dfb->du = du; + dfb->chroma_siting = DRMU_CHROMA_SITING_UNSPECIFIED; + dfb->buf_fd = -1; + dfb->fence_fd = -1; + return dfb; +} + +// Bits per pixel on plane 0 +unsigned int +drmu_fb_pixel_bits(const drmu_fb_t * const dfb) +{ + return drmu_fmt_info_pixel_bits(dfb->fmt_info); +} + +uint32_t +drmu_fb_pixel_format(const drmu_fb_t * const dfb) +{ + return dfb->fb.pixel_format; +} + +uint64_t +drmu_fb_modifier(const drmu_fb_t * const dfb, const unsigned int plane) +{ + return plane >= 4 ? DRM_FORMAT_MOD_INVALID : dfb->fb.modifier[plane]; +} + +static int +fb_sync(drmu_fb_t * const dfb, unsigned int flags) +{ + struct dma_buf_sync sync = { + .flags = flags + }; + if (dfb->buf_fd == -1 || dfb->map_ptr == NULL) + return 0; + while (ioctl(dfb->buf_fd, DMA_BUF_IOCTL_SYNC, &sync) == -1) { + const int err = errno; + if (errno == EINTR) + continue; + drmu_debug(dfb->du, "%s: ioctl failed: flags=%#x\n", __func__, flags); + return -err; + } + return 0; +} + +int drmu_fb_write_start(drmu_fb_t * const dfb) +{ + return fb_sync(dfb, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE); +} + +int drmu_fb_write_end(drmu_fb_t * const dfb) +{ + return fb_sync(dfb, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE); +} + +int drmu_fb_read_start(drmu_fb_t * const dfb) +{ + return fb_sync(dfb, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ); +} + +int drmu_fb_read_end(drmu_fb_t * const dfb) +{ + return fb_sync(dfb, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ); +} + +// Writeback fence +// Must be unset before set again +// (This is as a handy hint that you must wait for the previous fence +// to go ready before you set a new one) +static int +atomic_fb_add_out_fence(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb) +{ + static const drmu_atomic_prop_fns_t fns = { + .ref = atomic_prop_fb_ref_cb, + .unref = atomic_prop_fb_unref_cb, + .commit = drmu_prop_fn_null_commit, + }; + + if (!dfb) + return -EINVAL; + if (dfb->fence_fd != -1) + return -EBUSY; + + return drmu_atomic_add_prop_generic(da, obj_id, prop_id, (uintptr_t)&dfb->fence_fd, &fns, dfb); +} + +// For allocation purposes given fb_pixel bits how tall +// does the frame have to be to fit all planes if constant width +static unsigned int +fb_total_height(const drmu_fb_t * const dfb, const unsigned int h) +{ + unsigned int i; + const drmu_fmt_info_t *const f = dfb->fmt_info; + unsigned int t = 0; + unsigned int h0 = h * drmu_fmt_info_wdiv(f, 0); + const unsigned int c = drmu_fmt_info_plane_count(f); + + for (i = 0; i != c; ++i) + t += h0 / (drmu_fmt_info_hdiv(f, i) * drmu_fmt_info_wdiv(f, i)); + + return t; +} + +static void +fb_pitches_set_mod(drmu_fb_t * const dfb, uint64_t mod) +{ + const drmu_fmt_info_t *const f = dfb->fmt_info; + const uint32_t pitch0 = dfb->map_pitch * drmu_fmt_info_wdiv(f, 0); + const uint32_t h = drmu_fb_height(dfb); + const unsigned int c = drmu_fmt_info_plane_count(f); + uint32_t t = 0; + unsigned int i; + + // This should be true for anything we've allocated + if (mod == DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0)) { + // Cope with the joy that is sand + mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(h * 3/2); + drmu_fb_int_layer_mod_set(dfb, 0, 0, dfb->map_pitch, 0, mod); + drmu_fb_int_layer_mod_set(dfb, 1, 0, dfb->map_pitch, h * 128, mod); + return; + } + + for (i = 0; i != c; ++i) { + const unsigned int wdiv = drmu_fmt_info_wdiv(f, i); + drmu_fb_int_layer_mod_set(dfb, i, 0, pitch0 / wdiv, t, mod); + t += (pitch0 * h) / (drmu_fmt_info_hdiv(f, i) * wdiv); + } +} + +drmu_fb_t * +drmu_fb_new_dumb_mod(drmu_env_t * const du, uint32_t w, uint32_t h, + const uint32_t format, const uint64_t mod) +{ + drmu_fb_t * const dfb = drmu_fb_int_alloc(du); + uint32_t bpp; + int rv; + uint32_t w2; + const uint32_t s30_cw = 128 / 4 * 3; + + if (dfb == NULL) { + drmu_err(du, "%s: Alloc failure", __func__); + return NULL; + } + + if (mod != DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0)) + w2 = (w + 15) & ~15; + else if (format == DRM_FORMAT_NV12) + w2 = (w + 127) & ~127; + else if (format == DRM_FORMAT_P030) + w2 = ((w + s30_cw - 1) / s30_cw) * s30_cw; + else { + // Unknown form of sand128 + drmu_err(du, "Sand modifier on unexpected format"); + goto fail; + } + + drmu_fb_int_fmt_size_set(dfb, format, w2, (h + 15) & ~15, drmu_rect_wh(w, h)); + + if ((bpp = drmu_fb_pixel_bits(dfb)) == 0) { + drmu_err(du, "%s: Unexpected format %#x", __func__, format); + goto fail; + } + + { + struct drm_mode_create_dumb dumb = { + .height = fb_total_height(dfb, dfb->fb.height), + .width = dfb->fb.width / drmu_fmt_info_wdiv(dfb->fmt_info, 0), + .bpp = bpp + }; + + drmu_bo_t * bo = drmu_bo_new_dumb(du, &dumb); + if (bo == NULL) + goto fail; + drmu_fb_int_bo_set(dfb, 0, bo); + + dfb->map_pitch = dumb.pitch; + dfb->map_size = (size_t)dumb.size; + } + + { + struct drm_mode_map_dumb map_dumb = { + .handle = dfb->bo_list[0]->handle + }; + void * map_ptr; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb)) != 0) + { + drmu_err(du, "%s: map dumb failed: %s", __func__, strerror(-rv)); + goto fail; + } + + // Avoid having to test for MAP_FAILED when testing for mapped/unmapped + if ((map_ptr = mmap(NULL, dfb->map_size, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, + drmu_fd(du), map_dumb.offset)) == MAP_FAILED) { + drmu_err(du, "%s: mmap failed (size=%zd, fd=%d, off=%#"PRIx64"): %s", __func__, + dfb->map_size, drmu_fd(du), map_dumb.offset, strerror(errno)); + goto fail; + } + + dfb->map_ptr = map_ptr; + } + + fb_pitches_set_mod(dfb, mod); + + if (drmu_fb_int_make(dfb)) + goto fail; + + drmu_debug(du, "Create dumb %p %s %dx%d / %dx%d size: %zd", dfb, + drmu_log_fourcc(format), dfb->fb.width, dfb->fb.height, dfb->active.w, dfb->active.h, dfb->map_size); + return dfb; + +fail: + drmu_fb_int_free(dfb); + return NULL; +} + +drmu_fb_t * +drmu_fb_new_dumb(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format) +{ + return drmu_fb_new_dumb_mod(du, w, h, format, DRM_FORMAT_MOD_LINEAR); +} + +bool +drmu_fb_try_reuse(drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod) +{ + if (w > dfb->fb.width || h > dfb->fb.height || format != dfb->fb.pixel_format || mod != dfb->fb.modifier[0]) + return false; + + dfb->active = drmu_rect_wh(w, h); + dfb->crop = drmu_rect_shl16(dfb->active); + return true; +} + +drmu_fb_t * +drmu_fb_realloc_dumb_mod(drmu_env_t * const du, drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod) +{ + if (dfb == NULL) + return drmu_fb_new_dumb_mod(du, w, h, format, mod); + + if (drmu_fb_try_reuse(dfb, w, h, format, mod)) + return dfb; + + drmu_fb_unref(&dfb); + return drmu_fb_new_dumb_mod(du, w, h, format, mod); +} + +drmu_fb_t * +drmu_fb_realloc_dumb(drmu_env_t * const du, drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format) +{ + return drmu_fb_realloc_dumb_mod(du, dfb, w, h, format, DRM_FORMAT_MOD_LINEAR); +} + +static void +atomic_prop_fb_unref(void * v) +{ + drmu_fb_t * fb = v; + drmu_fb_unref(&fb); +} + +static void +atomic_prop_fb_ref(void * v) +{ + drmu_fb_ref(v); +} + +int +drmu_atomic_add_prop_fb(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb) +{ + int rv; + static const drmu_atomic_prop_fns_t fns = { + .ref = atomic_prop_fb_ref, + .unref = atomic_prop_fb_unref, + .commit = drmu_prop_fn_null_commit, + }; + + if (dfb == NULL) + return drmu_atomic_add_prop_value(da, obj_id, prop_id, 0); + + rv = drmu_atomic_add_prop_generic(da, obj_id, prop_id, dfb->fb.fb_id, &fns, dfb); + if (rv != 0) + drmu_warn(drmu_atomic_env(da), "%s: Failed to add fb obj_id=%#x, prop_id=%#x: %s", __func__, obj_id, prop_id, strerror(-rv)); + + return rv; +} + +//---------------------------------------------------------------------------- +// +// props fns (internal) + +typedef struct drmu_props_s { + struct drmu_env_s * du; + unsigned int n; + drmu_propinfo_t * info; + const drmu_propinfo_t ** by_name; +} drmu_props_t; + +static void +props_free(drmu_props_t * const props) +{ + free(props->info); // As yet nothing else is allocated off this + free(props->by_name); + free(props); +} + +static const drmu_propinfo_t * +props_name_to_propinfo(const drmu_props_t * const props, const char * const name) +{ + unsigned int i = props->n / 2; + unsigned int a = 0; + unsigned int b = props->n; + + while (a < b) { + const int r = strcmp(name, props->by_name[i]->prop.name); + + if (r == 0) + return props->by_name[i]; + + if (r < 0) { + b = i; + i = (i + a) / 2; + } else { + a = i + 1; + i = (i + b) / 2; + } + } + return NULL; +} + +static uint32_t +props_name_to_id(const drmu_props_t * const props, const char * const name) +{ + return propinfo_prop_id(props_name_to_propinfo(props, name)); +} + +// Data must be freed later +static int +props_name_get_blob(const drmu_props_t * const props, const char * const name, void ** const ppdata, size_t * const plen) +{ + const drmu_propinfo_t * const pinfo = props_name_to_propinfo(props, name); + + *ppdata = 0; + *plen = 0; + + if (!pinfo) + return -ENOENT; + if ((pinfo->prop.flags & DRM_MODE_PROP_BLOB) == 0) + return -EINVAL; + + return blob_data_read(props->du, (uint32_t)pinfo->val, ppdata, plen); +} + +#if TRACE_PROP_NEW +static void +props_dump(const drmu_props_t * const props) +{ + if (props != NULL) { + unsigned int i; + drmu_env_t * const du = props->du; + + for (i = 0; i != props->n; ++i) { + const drmu_propinfo_t * const inf = props->info + i; + const struct drm_mode_get_property * const p = &inf->prop; + char flagbuf[256]; + + flagbuf[0] = 0; + if (p->flags & DRM_MODE_PROP_PENDING) + strcat(flagbuf, ":pending"); + if (p->flags & DRM_MODE_PROP_RANGE) + strcat(flagbuf, ":urange"); + if (p->flags & DRM_MODE_PROP_IMMUTABLE) + strcat(flagbuf, ":immutable"); + if (p->flags & DRM_MODE_PROP_ENUM) + strcat(flagbuf, ":enum"); + if (p->flags & DRM_MODE_PROP_BLOB) + strcat(flagbuf, ":blob"); + if (p->flags & DRM_MODE_PROP_BITMASK) + strcat(flagbuf, ":bitmask"); + if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_OBJECT) + strcat(flagbuf, ":object"); + else if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_SIGNED_RANGE) + strcat(flagbuf, ":srange"); + else if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) != 0) + strcat(flagbuf, ":?xtype?"); + if (p->flags & ~(DRM_MODE_PROP_LEGACY_TYPE | + DRM_MODE_PROP_EXTENDED_TYPE | + DRM_MODE_PROP_PENDING | + DRM_MODE_PROP_IMMUTABLE | + DRM_MODE_PROP_ATOMIC)) + strcat(flagbuf, ":?other?"); + if (p->flags & DRM_MODE_PROP_ATOMIC) + strcat(flagbuf, ":atomic"); + + + drmu_info(du, "Prop%02d/%02d: %#-4x %-16s val=%#-4"PRIx64" flags=%#x%s, values=%d, blobs=%d", + i, props->n, p->prop_id, + p->name, inf->val, + p->flags, flagbuf, + p->count_values, + p->count_enum_blobs); + } + } +} +#endif + +static int +props_qsort_by_name_cb(const void * va, const void * vb) +{ + const drmu_propinfo_t * const a = *(drmu_propinfo_t **)va; + const drmu_propinfo_t * const b = *(drmu_propinfo_t **)vb; + return strcmp(a->prop.name, b->prop.name); +} + +// At the moment we don't need / want to fill in the values / blob arrays +// we just want the name - will get the extra info if we need it +static int +propinfo_fill(drmu_env_t * const du, drmu_propinfo_t * const inf, uint32_t propid, uint64_t val) +{ + int rv; + + inf->val = val; + inf->prop.prop_id = propid; + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &inf->prop)) != 0) + drmu_err(du, "Failed to get property %d: %s", propid, strerror(-rv)); + return rv; +} + +static int +props_get_properties(drmu_env_t * const du, const uint32_t objid, const uint32_t objtype, + uint32_t ** const ppPropids, uint64_t ** const ppValues) +{ + struct drm_mode_obj_get_properties obj_props = { + .obj_id = objid, + .obj_type = objtype, + }; + uint64_t * values = NULL; + uint32_t * propids = NULL; + unsigned int n = 0; + int rv; + + for (;;) { + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &obj_props)) != 0) { + drmu_err(du, "drmModeObjectGetProperties failed: %s", strerror(-rv)); + goto fail; + } + + if (obj_props.count_props <= n) + break; + + free(values); + values = NULL; + free(propids); + propids = NULL; + n = obj_props.count_props; + if ((obj_props.prop_values_ptr = io_alloc(values, n)) == 0 || + (obj_props.props_ptr = io_alloc(propids, n)) == 0) { + drmu_err(du, "obj/value array alloc failed"); + rv = -ENOMEM; + goto fail; + } + } + + *ppValues = values; + *ppPropids = propids; + return (int)n; + +fail: + free(values); + free(propids); + *ppPropids = NULL; + *ppValues = NULL; + return rv; +} + +static drmu_props_t * +props_new(drmu_env_t * const du, const uint32_t objid, const uint32_t objtype) +{ + drmu_props_t * const props = calloc(1, sizeof(*props)); + int rv; + uint64_t * values; + uint32_t * propids; + + if (props == NULL) { + drmu_err(du, "%s: Failed struct alloc", __func__); + return NULL; + } + props->du = du; + + if ((rv = props_get_properties(du, objid, objtype, &propids, &values)) < 0) + goto fail; + + props->n = rv; + if ((props->info = calloc(props->n, sizeof(*props->info))) == NULL || + (props->by_name = malloc(props->n * sizeof(*props->by_name))) == NULL) { + drmu_err(du, "info/name array alloc failed"); + goto fail; + } + + for (unsigned int i = 0; i < props->n; ++i) { + drmu_propinfo_t * const inf = props->info + i; + + props->by_name[i] = inf; + if ((rv = propinfo_fill(du, inf, propids[i], values[i])) != 0) + goto fail; + } + + // Sort into name order for faster lookup + qsort(props->by_name, props->n, sizeof(*props->by_name), props_qsort_by_name_cb); + + free(values); + free(propids); + return props; + +fail: + props_free(props); + free(values); + free(propids); + return NULL; +} + +int +drmu_atomic_obj_add_snapshot(drmu_atomic_t * const da, const uint32_t objid, const uint32_t objtype) +{ +#if 0 + drmu_env_t * const du = drmu_atomic_env(da); + drmu_props_t * props = NULL; + unsigned int i; + int rv; + + if (!du) + return -EINVAL; + + if ((props = props_new(du, objid, objtype)) == NULL) + return -ENOENT; + + for (i = 0; i != props->n; ++i) { + if ((props->info[i].prop.flags & (DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ATOMIC)) != 0 || props->info[i].prop.prop_id == 2) + continue; + if ((rv = drmu_atomic_add_prop_generic(da, objid, props->info[i].prop.prop_id, props->info[i].val, 0, 0, NULL)) != 0) + goto fail; + } + rv = 0; + +fail: + props_free(props); + return rv; +#else + uint64_t * values; + uint32_t * propids; + int rv; + unsigned int i, n; + drmu_env_t * const du = drmu_atomic_env(da); + + if (!du) + return -EINVAL; + + if ((rv = props_get_properties(du, objid, objtype, &propids, &values)) < 0) + goto fail; + n = rv; + + for (i = 0; i != n; ++i) { + if ((rv = drmu_atomic_add_prop_value(da, objid, propids[i], values[i])) != 0) + goto fail; + } + +fail: + free(values); + free(propids); + return rv; +#endif +} + +static int +drmu_atomic_props_add_save(drmu_atomic_t * const da, const uint32_t objid, const drmu_props_t * props) +{ + unsigned int i; + int rv; + drmu_env_t * const du = drmu_atomic_env(da); + + if (props == NULL) + return 0; + if (du == NULL) + return -EINVAL; + + for (i = 0; i != props->n; ++i) { + if ((props->info[i].prop.flags & DRM_MODE_PROP_IMMUTABLE) != 0) + continue; + + // Blobs, if set, are prone to running out of refs and vanishing, so we + // must copy. If we fail to copy the blob for any reason drop through + // to the generic add and hope that that will do + if ((props->info[i].prop.flags & DRM_MODE_PROP_BLOB) != 0 && props->info[i].val != 0) { + drmu_blob_t * b = drmu_blob_copy_id(du, (uint32_t)props->info[i].val); + if (b != NULL) { + rv = drmu_atomic_add_prop_blob(da, objid, props->info[i].prop.prop_id, b); + drmu_blob_unref(&b); + if (rv == 0) + continue; + } + } + + // Generic value + if ((rv = drmu_atomic_add_prop_value(da, objid, props->info[i].prop.prop_id, props->info[i].val)) != 0) + return rv; + } + return 0; +} + +//---------------------------------------------------------------------------- +// +// CRTC fns + +typedef struct drmu_crtc_s { + struct drmu_env_s * du; + int crtc_idx; + + atomic_int ref_count; + bool saved; + + struct drm_mode_crtc crtc; + + struct { + drmu_prop_range_t * active; + uint32_t mode_id; + } pid; + + drmu_blob_t * mode_id_blob; + +} drmu_crtc_t; + +static void +crtc_uninit(drmu_crtc_t * const dc) +{ + drmu_prop_range_delete(&dc->pid.active); + drmu_blob_unref(&dc->mode_id_blob); +} + +static void +crtc_free(drmu_crtc_t * const dc) +{ + crtc_uninit(dc); + free(dc); +} + + +#if 0 +// Set misc derived vars from mode +static void +crtc_mode_set_vars(drmu_crtc_t * const dc) +{ + switch (dc->crtc.mode.flags & DRM_MODE_FLAG_PIC_AR_MASK) { + case DRM_MODE_FLAG_PIC_AR_4_3: + dc->par = (drmu_ufrac_t){4,3}; + break; + case DRM_MODE_FLAG_PIC_AR_16_9: + dc->par = (drmu_ufrac_t){16,9}; + break; + case DRM_MODE_FLAG_PIC_AR_64_27: + dc->par = (drmu_ufrac_t){64,27}; + break; + case DRM_MODE_FLAG_PIC_AR_256_135: + dc->par = (drmu_ufrac_t){256,135}; + break; + default: + case DRM_MODE_FLAG_PIC_AR_NONE: + dc->par = (drmu_ufrac_t){0,0}; + break; + } + + if (dc->par.den == 0) { + // Assume 1:1 + dc->sar = (drmu_ufrac_t){1,1}; + } + else { + dc->sar = drmu_ufrac_reduce((drmu_ufrac_t) {dc->par.num * dc->crtc.mode.vdisplay, dc->par.den * dc->crtc.mode.hdisplay}); + } +} + +static int +atomic_crtc_bpc_set(drmu_atomic_t * const da, drmu_crtc_t * const dc, + const char * const colorspace, + const unsigned int max_bpc) +{ + const uint32_t con_id = dc->con->connector_id; + int rv = 0; + + if (!dc->du->modeset_allow) + return 0; + + if ((dc->pid.colorspace && + (rv = drmu_atomic_add_prop_enum(da, con_id, dc->pid.colorspace, colorspace)) != 0) || + (dc->pid.max_bpc && + (rv = drmu_atomic_add_prop_range(da, con_id, dc->pid.max_bpc, max_bpc)) != 0)) + return rv; + return 0; +} + + +static int +atomic_crtc_hi_bpc_set(drmu_atomic_t * const da, drmu_crtc_t * const dc) +{ + return atomic_crtc_bpc_set(da, dc, "BT2020_YCC", 12); +} +#endif + +void +drmu_crtc_delete(drmu_crtc_t ** ppdc) +{ + drmu_crtc_t * const dc = * ppdc; + + if (dc == NULL) + return; + *ppdc = NULL; + + crtc_free(dc); +} + +drmu_env_t * +drmu_crtc_env(const drmu_crtc_t * const dc) +{ + return dc == NULL ? NULL : dc->du; +} + +uint32_t +drmu_crtc_id(const drmu_crtc_t * const dc) +{ + return dc->crtc.crtc_id; +} + +int +drmu_crtc_idx(const drmu_crtc_t * const dc) +{ + return dc->crtc_idx; +} + +static int +crtc_init(drmu_env_t * const du, drmu_crtc_t * const dc, const unsigned int idx, const uint32_t crtc_id) +{ + int rv; + drmu_props_t * props; + + memset(dc, 0, sizeof(*dc)); + dc->du = du; + dc->crtc_idx = idx; + dc->crtc.crtc_id = crtc_id; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETCRTC, &dc->crtc)) != 0) { + drmu_err(du, "Failed to get crtc id %d: %s", crtc_id, strerror(-rv)); + return rv; + } + + props = props_new(du, dc->crtc.crtc_id, DRM_MODE_OBJECT_CRTC); + + if (props != NULL) { +#if TRACE_PROP_NEW + drmu_info(du, "CRTC id=%#x, idx=%d:", dc->crtc.crtc_id, dc->crtc_idx); + props_dump(props); +#endif + dc->pid.mode_id = props_name_to_id(props, "MODE_ID"); + dc->pid.active = drmu_prop_range_new(du, props_name_to_id(props, "ACTIVE")); + + props_free(props); + } + + return 0; +} + +static drmu_ufrac_t +modeinfo_par(const struct drm_mode_modeinfo * const mode) +{ + switch (mode->flags & DRM_MODE_FLAG_PIC_AR_MASK) { + case DRM_MODE_FLAG_PIC_AR_4_3: + return (drmu_ufrac_t){4,3}; + case DRM_MODE_FLAG_PIC_AR_16_9: + return (drmu_ufrac_t){16,9}; + case DRM_MODE_FLAG_PIC_AR_64_27: + return (drmu_ufrac_t){64,27}; + case DRM_MODE_FLAG_PIC_AR_256_135: + return (drmu_ufrac_t){256,135}; + default: + case DRM_MODE_FLAG_PIC_AR_NONE: + break; + } + return (drmu_ufrac_t){0,0}; +} + +static drmu_mode_simple_params_t +modeinfo_simple_params(const struct drm_mode_modeinfo * const mode) +{ + if (!mode) + return (drmu_mode_simple_params_t){ 0 }; + else { + drmu_mode_simple_params_t rv = { + .width = mode->hdisplay, + .height = mode->vdisplay, + .hz_x_1000 = (uint32_t)(((uint64_t)mode->clock * 1000000) / (mode->htotal * mode->vtotal)), + .par = modeinfo_par(mode), + .sar = {1, 1}, + .type = mode->type, + .flags = mode->flags, + }; + + if (rv.par.den != 0) + rv.sar = drmu_ufrac_reduce((drmu_ufrac_t) {rv.par.num * rv.height, rv.par.den * rv.width}); + + return rv; + } +} + +drmu_crtc_t * +drmu_env_crtc_find_id(drmu_env_t * const du, const uint32_t crtc_id) +{ + unsigned int i; + drmu_crtc_t * dc; + + for (i = 0; (dc = drmu_env_crtc_find_n(du, i)) != NULL; ++i) { + if (dc->crtc.crtc_id == crtc_id) + return dc; + } + return NULL; +} + +const struct drm_mode_modeinfo * +drmu_crtc_modeinfo(const drmu_crtc_t * const dc) +{ + if (!dc || !dc->crtc.mode_valid) + return NULL; + return &dc->crtc.mode; +} + +drmu_mode_simple_params_t +drmu_crtc_mode_simple_params(const drmu_crtc_t * const dc) +{ + return modeinfo_simple_params(drmu_crtc_modeinfo(dc)); +} + +int +drmu_atomic_crtc_add_modeinfo(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, const struct drm_mode_modeinfo * const modeinfo) +{ + drmu_env_t * const du = drmu_atomic_env(da); + int rv; + + if (!modeinfo || dc->pid.mode_id == 0) + return 0; + + if ((rv = drmu_blob_update(du, &dc->mode_id_blob, modeinfo, sizeof(*modeinfo))) != 0) + return rv; + + return drmu_atomic_add_prop_blob(da, dc->crtc.crtc_id, dc->pid.mode_id, dc->mode_id_blob); +} + +int +drmu_atomic_crtc_add_active(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, unsigned int val) +{ + return drmu_atomic_add_prop_range(da, dc->crtc.crtc_id, dc->pid.active, val); +} + +// Use the same claim logic as we do for planes +// As it stands we don't do anything much on final unref so the logic +// isn't really needed but it doesn't cost us much so do this way against +// future need + +bool +drmu_crtc_is_claimed(const drmu_crtc_t * const dc) +{ + return atomic_load(&dc->ref_count) != 0; +} + +void +drmu_crtc_unref(drmu_crtc_t ** const ppdc) +{ + drmu_crtc_t * const dc = *ppdc; + + if (dc == NULL) + return; + *ppdc = NULL; + + if (atomic_fetch_sub(&dc->ref_count, 1) != 2) + return; + atomic_store(&dc->ref_count, 0); +} + +drmu_crtc_t * +drmu_crtc_ref(drmu_crtc_t * const dc) +{ + if (!dc) + return NULL; + atomic_fetch_add(&dc->ref_count, 1); + return dc; +} + +static int +crtc_state_save(drmu_env_t * const du, drmu_crtc_t * const dc) +{ + int rv = 0; + // 1st time through save state + if (!dc->saved && + (rv = env_object_state_save(du, dc->crtc.crtc_id, DRM_MODE_OBJECT_CRTC)) == 0) + dc->saved = true; + return rv; +} + +// A Conn should be claimed before any op that might change its state +int +drmu_crtc_claim_ref(drmu_crtc_t * const dc) +{ + drmu_env_t * const du = dc->du; + static const int ref0 = 0; + if (!atomic_compare_exchange_strong(&dc->ref_count, &ref0, 2)) + return -EBUSY; + + // 1st time through save state + crtc_state_save(du, dc); + + return 0; +} + +//---------------------------------------------------------------------------- +// +// CONN functions + +static const char * conn_type_names[32] = { + [DRM_MODE_CONNECTOR_Unknown] = "Unknown", + [DRM_MODE_CONNECTOR_VGA] = "VGA", + [DRM_MODE_CONNECTOR_DVII] = "DVI-I", + [DRM_MODE_CONNECTOR_DVID] = "DVI-D", + [DRM_MODE_CONNECTOR_DVIA] = "DVI-A", + [DRM_MODE_CONNECTOR_Composite] = "Composite", + [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO", + [DRM_MODE_CONNECTOR_LVDS] = "LVDS", + [DRM_MODE_CONNECTOR_Component] = "Component", + [DRM_MODE_CONNECTOR_9PinDIN] = "9PinDIN", + [DRM_MODE_CONNECTOR_DisplayPort] = "DisplayPort", + [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A", + [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B", + [DRM_MODE_CONNECTOR_TV] = "TV", + [DRM_MODE_CONNECTOR_eDP] = "eDP", + [DRM_MODE_CONNECTOR_VIRTUAL] = "VIRTUAL", + [DRM_MODE_CONNECTOR_DSI] = "DSI", + [DRM_MODE_CONNECTOR_DPI] = "DPI", + [DRM_MODE_CONNECTOR_WRITEBACK] = "WRITEBACK", + [DRM_MODE_CONNECTOR_SPI] = "SPI", +#ifdef DRM_MODE_CONNECTOR_USB + [DRM_MODE_CONNECTOR_USB] = "USB", +#endif +}; + +struct drmu_conn_s { + drmu_env_t * du; + unsigned int conn_idx; + + atomic_int ref_count; + bool saved; + + struct drm_mode_get_connector conn; + bool probed; + unsigned int modes_size; + unsigned int enc_ids_size; + struct drm_mode_modeinfo * modes; + uint32_t * enc_ids; + + uint32_t avail_crtc_mask; + + struct { + drmu_prop_object_t * crtc_id; + drmu_prop_range_t * max_bpc; + drmu_prop_enum_t * colorspace; + drmu_prop_enum_t * broadcast_rgb; + uint32_t hdr_output_metadata; + uint32_t writeback_out_fence_ptr; + uint32_t writeback_fb_id; + uint32_t writeback_pixel_formats; + } pid; + + drmu_blob_t * hdr_metadata_blob; + + char name[32]; +}; + + +int +drmu_atomic_conn_add_hdr_metadata(drmu_atomic_t * const da, drmu_conn_t * const dn, const struct hdr_output_metadata * const m) +{ + drmu_env_t * const du = drmu_atomic_env(da); + int rv; + + if (!du || !dn) // du will be null if da is null + return -ENOENT; + + if (dn->pid.hdr_output_metadata == 0) + return 0; + + if ((rv = drmu_blob_update(du, &dn->hdr_metadata_blob, m, sizeof(*m))) != 0) + return rv; + + rv = drmu_atomic_add_prop_blob(da, dn->conn.connector_id, dn->pid.hdr_output_metadata, dn->hdr_metadata_blob); + if (rv != 0) + drmu_err(du, "Set property fail: %s", strerror(errno)); + + return rv; +} + +int +drmu_atomic_conn_add_hi_bpc(drmu_atomic_t * const da, drmu_conn_t * const dn, bool hi_bpc) +{ + return drmu_atomic_add_prop_range(da, dn->conn.connector_id, dn->pid.max_bpc, !hi_bpc ? 8 : + drmu_prop_range_max(dn->pid.max_bpc)); +} + +int +drmu_atomic_conn_add_colorspace(drmu_atomic_t * const da, drmu_conn_t * const dn, const drmu_colorspace_t colorspace) +{ + if (!dn->pid.colorspace) + return 0; + + return drmu_atomic_add_prop_enum(da, dn->conn.connector_id, dn->pid.colorspace, colorspace); +} + +int +drmu_atomic_conn_add_broadcast_rgb(drmu_atomic_t * const da, drmu_conn_t * const dn, const drmu_broadcast_rgb_t bcrgb) +{ + if (!dn->pid.broadcast_rgb) + return 0; + + return drmu_atomic_add_prop_enum(da, dn->conn.connector_id, dn->pid.broadcast_rgb, bcrgb); +} + +int +drmu_atomic_conn_add_crtc(drmu_atomic_t * const da, drmu_conn_t * const dn, drmu_crtc_t * const dc) +{ + return drmu_atomic_add_prop_object(da, dn->pid.crtc_id, drmu_crtc_id(dc)); +} + +int +drmu_atomic_conn_add_writeback_fb(drmu_atomic_t * const da_out, drmu_conn_t * const dn, + drmu_fb_t * const dfb) +{ + // Add both or neither, so build a temp atomic to store the intermediate result + drmu_atomic_t * da = drmu_atomic_new(drmu_atomic_env(da_out)); + int rv; + + if (!da) + return -ENOMEM; + + if ((rv = atomic_fb_add_out_fence(da, dn->conn.connector_id, dn->pid.writeback_out_fence_ptr, dfb)) != 0) + goto fail; + + if ((rv = drmu_atomic_add_prop_fb(da, dn->conn.connector_id, dn->pid.writeback_fb_id, dfb)) != 0) + goto fail; + + return drmu_atomic_merge(da_out, &da); + +fail: + drmu_atomic_unref(&da); + return rv; +} + +const struct drm_mode_modeinfo * +drmu_conn_modeinfo(const drmu_conn_t * const dn, const int mode_id) +{ + return !dn || mode_id < 0 || (unsigned int)mode_id >= dn->conn.count_modes ? NULL : + dn->modes + mode_id; +} + +drmu_mode_simple_params_t +drmu_conn_mode_simple_params(const drmu_conn_t * const dn, const int mode_id) +{ + return modeinfo_simple_params(drmu_conn_modeinfo(dn, mode_id)); +} + +bool +drmu_conn_is_output(const drmu_conn_t * const dn) +{ + return dn->conn.connector_type != DRM_MODE_CONNECTOR_WRITEBACK; +} + +bool +drmu_conn_is_writeback(const drmu_conn_t * const dn) +{ + return dn->conn.connector_type == DRM_MODE_CONNECTOR_WRITEBACK; +} + +const char * +drmu_conn_name(const drmu_conn_t * const dn) +{ + return dn->name; +} + +uint32_t +drmu_conn_crtc_id_get(const drmu_conn_t * const dn) +{ + return drmu_prop_object_value(dn->pid.crtc_id); +} + +uint32_t +drmu_conn_possible_crtcs(const drmu_conn_t * const dn) +{ + return dn->avail_crtc_mask; +} + +unsigned int +drmu_conn_idx_get(const drmu_conn_t * const dn) +{ + return dn->conn_idx; +} + +static void +conn_uninit(drmu_conn_t * const dn) +{ + drmu_prop_object_unref(&dn->pid.crtc_id); + drmu_prop_range_delete(&dn->pid.max_bpc); + drmu_prop_enum_delete(&dn->pid.colorspace); + drmu_prop_enum_delete(&dn->pid.broadcast_rgb); + + drmu_blob_unref(&dn->hdr_metadata_blob); + + free(dn->modes); + free(dn->enc_ids); + dn->modes = NULL; + dn->enc_ids = NULL; + dn->modes_size = 0; + dn->enc_ids_size = 0; +} + +// Assumes zeroed before entry +static int +conn_init(drmu_env_t * const du, drmu_conn_t * const dn, unsigned int conn_idx, const uint32_t conn_id) +{ + int rv; + drmu_props_t * props; + uint32_t modes_req = 0; + uint32_t encs_req = 0; + + dn->du = du; + dn->conn_idx = conn_idx; + // * As count_modes == 0 this probes - do we really want this? + + do { + memset(&dn->conn, 0, sizeof(dn->conn)); + dn->conn.connector_id = conn_id; + + if (modes_req > dn->modes_size) { + free(dn->modes); + if (io_alloc(dn->modes, modes_req) == 0) { + drmu_err(du, "Failed to alloc modes array"); + goto fail; + } + dn->modes_size = modes_req; + } + dn->conn.modes_ptr = (uintptr_t)dn->modes; + dn->conn.count_modes = modes_req; + + if (encs_req > dn->enc_ids_size) { + free(dn->enc_ids); + if (io_alloc(dn->enc_ids, encs_req) == 0) { + drmu_err(du, "Failed to alloc encs array"); + goto fail; + } + dn->enc_ids_size = encs_req; + } + dn->conn.encoders_ptr = (uintptr_t)dn->enc_ids; + dn->conn.count_encoders = encs_req; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETCONNECTOR, &dn->conn)) != 0) { + drmu_err(du, "Get connector id %d failed: %s", dn->conn.connector_id, strerror(-rv)); + goto fail; + } + modes_req = dn->conn.count_modes; + encs_req = dn->conn.count_encoders; + + } while (dn->modes_size < modes_req || dn->enc_ids_size < encs_req); + + dn->probed = true; + + if (dn->conn.connector_type >= sizeof(conn_type_names) / sizeof(conn_type_names[0])) + snprintf(dn->name, sizeof(dn->name)-1, "CT%"PRIu32"-%"PRIu32, + dn->conn.connector_type, dn->conn.connector_type_id); + else + snprintf(dn->name, sizeof(dn->name)-1, "%s-%"PRIu32, + conn_type_names[dn->conn.connector_type], dn->conn.connector_type_id); + + props = props_new(du, dn->conn.connector_id, DRM_MODE_OBJECT_CONNECTOR); + + // Spin over encoders to create a crtc mask + dn->avail_crtc_mask = 0; + for (unsigned int i = 0; i != dn->conn.count_encoders; ++i) { + struct drm_mode_get_encoder enc = {.encoder_id = dn->enc_ids[i]}; + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETENCODER, &enc)) != 0) { + drmu_warn(du, "Failed to get encoder: id: %#x", enc.encoder_id); + continue; + } + dn->avail_crtc_mask |= enc.possible_crtcs; + } + + if (props != NULL) { +#if TRACE_PROP_NEW + drmu_info(du, "Connector id=%d, type=%d.%d (%s), crtc_mask=%#x:", + dn->conn.connector_id, dn->conn.connector_type, dn->conn.connector_type_id, drmu_conn_name(dn), + dn->avail_crtc_mask); + props_dump(props); +#endif + dn->pid.crtc_id = drmu_prop_object_new_propinfo(du, dn->conn.connector_id, props_name_to_propinfo(props, "CRTC_ID")); + dn->pid.max_bpc = drmu_prop_range_new(du, props_name_to_id(props, "max bpc")); + dn->pid.colorspace = drmu_prop_enum_new(du, props_name_to_id(props, "Colorspace")); + dn->pid.broadcast_rgb = drmu_prop_enum_new(du, props_name_to_id(props, "Broadcast RGB")); + dn->pid.hdr_output_metadata = props_name_to_id(props, "HDR_OUTPUT_METADATA"); + dn->pid.writeback_fb_id = props_name_to_id(props, "WRITEBACK_FB_ID"); + dn->pid.writeback_out_fence_ptr = props_name_to_id(props, "WRITEBACK_OUT_FENCE_PTR"); + dn->pid.writeback_pixel_formats = props_name_to_id(props, "WRITEBACK_PIXEL_FORMATS"); // Blob of fourccs (no modifier info) + + props_free(props); + } + + return 0; + +fail: + conn_uninit(dn); + return rv; +} + +// Use the same claim logic as we do for planes +// As it stands we don't do anything much on final unref so the logic +// isn't really needed but it doesn't cost us much so do this way against +// future need + +bool +drmu_conn_is_claimed(const drmu_conn_t * const dn) +{ + return atomic_load(&dn->ref_count) != 0; +} + +void +drmu_conn_unref(drmu_conn_t ** const ppdn) +{ + drmu_conn_t * const dn = *ppdn; + + if (dn == NULL) + return; + *ppdn = NULL; + + if (atomic_fetch_sub(&dn->ref_count, 1) != 2) + return; + atomic_store(&dn->ref_count, 0); +} + +drmu_conn_t * +drmu_conn_ref(drmu_conn_t * const dn) +{ + if (!dn) + return NULL; + atomic_fetch_add(&dn->ref_count, 1); + return dn; +} + +static int +conn_state_save(drmu_env_t * const du, drmu_conn_t * const dn) +{ + int rv = 0; + // 1st time through save state + if (!dn->saved && + (rv = env_object_state_save(du, dn->conn.connector_id, DRM_MODE_OBJECT_CONNECTOR)) == 0) + dn->saved = true; + return rv; +} + +// A Conn should be claimed before any op that might change its state +int +drmu_conn_claim_ref(drmu_conn_t * const dn) +{ + drmu_env_t * const du = dn->du; + static const int ref0 = 0; + if (!atomic_compare_exchange_strong(&dn->ref_count, &ref0, 2)) + return -EBUSY; + + // 1st time through save state + conn_state_save(du, dn); + + return 0; +} + +//---------------------------------------------------------------------------- +// +// Atomic Q fns (internal) + +typedef struct drmu_atomic_q_s { + bool kill; + pthread_mutex_t lock; + pthread_cond_t cond; + drmu_atomic_t * next_flip; + drmu_atomic_t * cur_flip; + drmu_atomic_t * last_flip; + unsigned int retry_count; + struct polltask * retry_task; +} drmu_atomic_q_t; + +static void atomic_q_retry(drmu_atomic_q_t * const aq, drmu_env_t * const du); + +// Needs locked +static int +atomic_q_attempt_commit_next(drmu_atomic_q_t * const aq) +{ + drmu_env_t * const du = drmu_atomic_env(aq->next_flip); + uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET; + int rv; + + if ((rv = drmu_atomic_commit(aq->next_flip, flags)) == 0) { + if (aq->retry_count != 0) + drmu_warn(du, "%s: Atomic commit OK", __func__); + aq->cur_flip = aq->next_flip; + aq->next_flip = NULL; + aq->retry_count = 0; + } + else if (rv == -EBUSY && ++aq->retry_count < 16) { + // This really shouldn't happen but we observe that the 1st commit after + // a modeset often fails with BUSY. It seems to be fine on a 10ms retry + // but allow some more in case ww need a bit longer in some cases + drmu_warn(du, "%s: Atomic commit BUSY", __func__); + atomic_q_retry(aq, du); + rv = 0; + } + else { + drmu_err(du, "%s: Atomic commit failed: %s", __func__, strerror(-rv)); + drmu_atomic_dump(aq->next_flip); + drmu_atomic_unref(&aq->next_flip); + aq->retry_count = 0; + } + + return rv; +} + +static void +atomic_q_retry_cb(void * v, short revents) +{ + drmu_atomic_q_t * const aq = v; + (void)revents; + + pthread_mutex_lock(&aq->lock); + + // If we need a retry then next != NULL && cur == NULL + // if not that then we've fixed ourselves elsewhere + + if (aq->next_flip != NULL && aq->cur_flip == NULL) + atomic_q_attempt_commit_next(aq); + + pthread_mutex_unlock(&aq->lock); +} + +static void +atomic_q_retry(drmu_atomic_q_t * const aq, drmu_env_t * const du) +{ + if (aq->retry_task == NULL) + aq->retry_task = polltask_new_timer(env_pollqueue(du), atomic_q_retry_cb, aq); + pollqueue_add_task(aq->retry_task, 20); +} + +// Called after an atomic commit has completed +// not called on every vsync, so if we haven't committed anything this won't be called +static void +drmu_atomic_page_flip_cb(drmu_env_t * const du, void *user_data) +{ + drmu_atomic_t * const da = user_data; + drmu_atomic_q_t * const aq = env_atomic_q(du); + + // At this point: + // next The atomic we are about to commit + // cur The last atomic we committed, now in use (must be != NULL) + // last The atomic that has just become obsolete + + pthread_mutex_lock(&aq->lock); + + if (da != aq->cur_flip) { + drmu_err(du, "%s: User data el (%p) != cur (%p)", __func__, da, aq->cur_flip); + } + + // Must merge cur into last rather than just replace last as there may + // still be things on screen not updated by the current commit + drmu_atomic_move_merge(&aq->last_flip, &aq->cur_flip); + + if (aq->next_flip != NULL) + atomic_q_attempt_commit_next(aq); + + pthread_cond_broadcast(&aq->cond); + pthread_mutex_unlock(&aq->lock); +} + +static int +atomic_q_kill(drmu_atomic_q_t * const aq) +{ + struct timespec ts; + int rv = 0; + + pthread_mutex_lock(&aq->lock); + aq->kill = true; + + clock_gettime(CLOCK_MONOTONIC, &ts); + ts.tv_sec += 1; // We should never timeout if all is well - 1 sec is plenty + + // Can flush next safely - but call commit cbs + drmu_atomic_run_commit_callbacks(aq->next_flip); + drmu_atomic_unref(&aq->next_flip); + polltask_delete(&aq->retry_task); // If we've got here then retry would not succeed + + // Wait for cur to finish - seems to confuse the world otherwise + while (aq->cur_flip != NULL) { + if ((rv = pthread_cond_timedwait(&aq->cond, &aq->lock, &ts)) != 0) + break; + } + + pthread_mutex_unlock(&aq->lock); + return rv; +} + +static void +atomic_q_clear_flips(drmu_atomic_q_t * const aq) +{ + drmu_atomic_unref(&aq->next_flip); + drmu_atomic_unref(&aq->cur_flip); + drmu_atomic_unref(&aq->last_flip); +} + +int +drmu_atomic_queue(drmu_atomic_t ** ppda) +{ + int rv; + drmu_atomic_q_t * aq; + + if (*ppda == NULL) + return 0; + + aq = env_atomic_q(drmu_atomic_env(*ppda)); + + pthread_mutex_lock(&aq->lock); + + if (aq->kill) { + drmu_atomic_unref(ppda); + rv = -EBUSY; + } + else { + rv = drmu_atomic_move_merge(&aq->next_flip, ppda); + + // No pending commit? + if (rv == 0 && aq->cur_flip == NULL) + rv = atomic_q_attempt_commit_next(aq); + } + + pthread_mutex_unlock(&aq->lock); + return rv; +} + +int +drmu_env_queue_wait(drmu_env_t * const du) +{ + + drmu_atomic_q_t *const aq = env_atomic_q(du); + struct timespec ts; + int rv = 0; + + pthread_mutex_lock(&aq->lock); + clock_gettime(CLOCK_MONOTONIC, &ts); + ts.tv_sec += 1; // We should never timeout if all is well - 1 sec is plenty + + // Next should clear quickly + while (aq->next_flip != NULL) { + if ((rv = pthread_cond_timedwait(&aq->cond, &aq->lock, &ts)) != 0) + break; + } + + pthread_mutex_unlock(&aq->lock); + return rv; +} + +static void +atomic_q_uninit(drmu_atomic_q_t * const aq) +{ + aq->kill = true; + polltask_delete(&aq->retry_task); + atomic_q_clear_flips(aq); + pthread_cond_destroy(&aq->cond); + pthread_mutex_destroy(&aq->lock); +} + +static void +atomic_q_init(drmu_atomic_q_t * const aq) +{ + pthread_condattr_t condattr; + + aq->kill = false; + aq->next_flip = NULL; + aq->cur_flip = NULL; + aq->last_flip = NULL; + pthread_mutex_init(&aq->lock, NULL); + + pthread_condattr_init(&condattr); + pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC); + pthread_cond_init(&aq->cond, &condattr); + pthread_condattr_destroy(&condattr); +} + +//---------------------------------------------------------------------------- +// +// Plane fns + +typedef struct drmu_plane_s { + struct drmu_env_s * du; + + // Unlike most ref counts in drmu this is 0 for unrefed, 2 for single ref + // and 1 for whilst unref cleanup is in progress. Guards dc + atomic_int ref_count; + struct drmu_crtc_s * dc; // NULL if not in use + bool saved; + + int plane_type; + struct drm_mode_get_plane plane; + + void * formats_in; + size_t formats_in_len; + const struct drm_format_modifier_blob * fmts_hdr; + + struct { + uint32_t crtc_id; + uint32_t fb_id; + drmu_prop_range_t * crtc_h; + drmu_prop_range_t * crtc_w; + uint32_t crtc_x; + uint32_t crtc_y; + drmu_prop_range_t * src_h; + drmu_prop_range_t * src_w; + uint32_t src_x; + uint32_t src_y; + drmu_prop_range_t * alpha; + drmu_prop_enum_t * color_encoding; + drmu_prop_enum_t * color_range; + drmu_prop_enum_t * pixel_blend_mode; + drmu_prop_bitmask_t * rotation; + drmu_prop_range_t * chroma_siting_h; + drmu_prop_range_t * chroma_siting_v; + drmu_prop_range_t * zpos; + } pid; + uint64_t rot_vals[8]; + +} drmu_plane_t; + +static int +plane_set_atomic(drmu_atomic_t * const da, + drmu_plane_t * const dp, + drmu_fb_t * const dfb, + int32_t crtc_x, int32_t crtc_y, + uint32_t crtc_w, uint32_t crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + const uint32_t plid = dp->plane.plane_id; + drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_id, dfb == NULL ? 0 : drmu_crtc_id(dp->dc)); + drmu_atomic_add_prop_fb(da, plid, dp->pid.fb_id, dfb); + drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_x, crtc_x); + drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_y, crtc_y); + drmu_atomic_add_prop_range(da, plid, dp->pid.crtc_w, crtc_w); + drmu_atomic_add_prop_range(da, plid, dp->pid.crtc_h, crtc_h); + drmu_atomic_add_prop_value(da, plid, dp->pid.src_x, src_x); + drmu_atomic_add_prop_value(da, plid, dp->pid.src_y, src_y); + drmu_atomic_add_prop_range(da, plid, dp->pid.src_w, src_w); + drmu_atomic_add_prop_range(da, plid, dp->pid.src_h, src_h); + return 0; +} + +int +drmu_atomic_plane_add_alpha(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int alpha) +{ + if (alpha == DRMU_PLANE_ALPHA_UNSET) + return 0; + return drmu_atomic_add_prop_range(da, dp->plane.plane_id, dp->pid.alpha, alpha); +} + +int +drmu_atomic_plane_add_zpos(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int zpos) +{ + return drmu_atomic_add_prop_range(da, dp->plane.plane_id, dp->pid.zpos, zpos); +} + +int +drmu_atomic_plane_add_rotation(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int rot) +{ + if (!dp->pid.rotation) + return rot == DRMU_PLANE_ROTATION_0 ? 0 : -EINVAL; + if (rot < 0 || rot >= 8 || !dp->rot_vals[rot]) + return -EINVAL; + return drmu_atomic_add_prop_bitmask(da, dp->plane.plane_id, dp->pid.rotation, dp->rot_vals[rot]); +} + +int +drmu_atomic_plane_add_chroma_siting(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const drmu_chroma_siting_t siting) +{ + int rv = 0; + + if (!dp->pid.chroma_siting_h || !dp->pid.chroma_siting_v) + return -ENOENT; + + if (!drmu_chroma_siting_eq(siting, DRMU_CHROMA_SITING_UNSPECIFIED)) { + const uint32_t plid = dp->plane.plane_id; + rv = drmu_atomic_add_prop_range(da, plid, dp->pid.chroma_siting_h, siting.x); + rv = rvup(rv, drmu_atomic_add_prop_range(da, plid, dp->pid.chroma_siting_v, siting.y)); + } + return rv; +} + +int +drmu_atomic_plane_clear_add(drmu_atomic_t * const da, drmu_plane_t * const dp) +{ + return plane_set_atomic(da, dp, NULL, + 0, 0, 0, 0, + 0, 0, 0, 0); +} + +int +drmu_atomic_plane_add_fb(drmu_atomic_t * const da, drmu_plane_t * const dp, + drmu_fb_t * const dfb, const drmu_rect_t pos) +{ + int rv; + const uint32_t plid = dp->plane.plane_id; + + if (dfb == NULL) + return drmu_atomic_plane_clear_add(da, dp); + + if ((rv = plane_set_atomic(da, dp, dfb, + pos.x, pos.y, + pos.w, pos.h, + dfb->crop.x + (dfb->active.x << 16), dfb->crop.y + (dfb->active.y << 16), + dfb->crop.w, dfb->crop.h)) != 0) + return rv; + + drmu_atomic_add_prop_enum(da, plid, dp->pid.pixel_blend_mode, dfb->pixel_blend_mode); + drmu_atomic_add_prop_enum(da, plid, dp->pid.color_encoding, dfb->color_encoding); + drmu_atomic_add_prop_enum(da, plid, dp->pid.color_range, dfb->color_range); + drmu_atomic_plane_add_chroma_siting(da, dp, dfb->chroma_siting); + return 0; +} + +uint32_t +drmu_plane_id(const drmu_plane_t * const dp) +{ + return dp->plane.plane_id; +} + +unsigned int +drmu_plane_type(const drmu_plane_t * const dp) +{ + return dp->plane_type; +} + +const uint32_t * +drmu_plane_formats(const drmu_plane_t * const dp, unsigned int * const pCount) +{ + *pCount = dp->fmts_hdr->count_formats; + return (const uint32_t *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->formats_offset); +} + +bool +drmu_plane_format_check(const drmu_plane_t * const dp, const uint32_t format, const uint64_t modifier) +{ + const struct drm_format_modifier * const mods = (const struct drm_format_modifier *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->modifiers_offset); + const uint32_t * const fmts = (const uint32_t *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->formats_offset); + uint64_t modbase = modifier; + unsigned int i; + + if (!format) + return false; + + // If broadcom then remove parameters before checking + if ((modbase >> 56) == DRM_FORMAT_MOD_VENDOR_BROADCOM) + modbase = fourcc_mod_broadcom_mod(modbase); + + // * Simplistic lookup; Could be made much faster + + for (i = 0; i != dp->fmts_hdr->count_modifiers; ++i) { + const struct drm_format_modifier * const mod = mods + i; + uint64_t fbits; + unsigned int j; + + if (mod->modifier != modbase) + continue; + + for (fbits = mod->formats, j = mod->offset; fbits; fbits >>= 1, ++j) { + if ((fbits & 1) != 0 && fmts[j] == format) + return true; + } + } + return false; +} + +bool +drmu_plane_is_claimed(drmu_plane_t * const dp) +{ + return atomic_load(&dp->ref_count) != 0; +} + +void +drmu_plane_unref(drmu_plane_t ** const ppdp) +{ + drmu_plane_t * const dp = *ppdp; + + if (dp == NULL) + return; + *ppdp = NULL; + + if (atomic_fetch_sub(&dp->ref_count, 1) != 2) + return; + dp->dc = NULL; + atomic_store(&dp->ref_count, 0); +} + +drmu_plane_t * +drmu_plane_ref(drmu_plane_t * const dp) +{ + if (dp) + atomic_fetch_add(&dp->ref_count, 1); + return dp; +} + +static int +plane_state_save(drmu_env_t * const du, drmu_plane_t * const dp) +{ + int rv = 0; + + // 1st time through save state + if (!dp->saved && + (rv = env_object_state_save(du, drmu_plane_id(dp), DRM_MODE_OBJECT_PLANE)) == 0) + dp->saved = true; + return rv; +} + +// Associate a plane with a crtc and ref it +// Returns -EBUSY if plane already associated +int +drmu_plane_ref_crtc(drmu_plane_t * const dp, drmu_crtc_t * const dc) +{ + drmu_env_t * const du = dp->du; + + static const int ref0 = 0; + if (!atomic_compare_exchange_strong(&dp->ref_count, &ref0, 2)) + return -EBUSY; + dp->dc = dc; + + // 1st time through save state if required - ignore fail + plane_state_save(du, dp); + + return 0; +} + +drmu_plane_t * +drmu_plane_new_find(drmu_crtc_t * const dc, const drmu_plane_new_find_ok_fn cb, void * const v) +{ + uint32_t i; + drmu_env_t * const du = drmu_crtc_env(dc); + drmu_plane_t * dp = NULL; + drmu_plane_t * dp_t; + const uint32_t crtc_mask = (uint32_t)1 << drmu_crtc_idx(dc); + + for (i = 0; (dp_t = drmu_env_plane_find_n(du, i)) != NULL; ++i) { + // Is unused? + // Availible for this crtc? + if (dp_t->dc != NULL || + (dp_t->plane.possible_crtcs & crtc_mask) == 0) + continue; + + if (cb(dp_t, v)) { + dp = dp_t; + break; + } + } + return dp; +} + +static bool plane_find_type_cb(const drmu_plane_t * dp, void * v) +{ + const unsigned int * const pReq = v; + return (*pReq & drmu_plane_type(dp)) != 0; +} + +drmu_plane_t * +drmu_plane_new_find_type(drmu_crtc_t * const dc, const unsigned int req_type) +{ + drmu_env_t * const du = drmu_crtc_env(dc); + drmu_plane_t * const dp = drmu_plane_new_find(dc, plane_find_type_cb, (void*)&req_type); + if (dp == NULL) { + drmu_err(du, "%s: No plane found for types %#x", __func__, req_type); + return NULL; + } + return dp; +} + +static void +plane_uninit(drmu_plane_t * const dp) +{ + drmu_prop_range_delete(&dp->pid.crtc_h); + drmu_prop_range_delete(&dp->pid.crtc_w); + drmu_prop_range_delete(&dp->pid.src_h); + drmu_prop_range_delete(&dp->pid.src_w); + drmu_prop_range_delete(&dp->pid.alpha); + drmu_prop_range_delete(&dp->pid.chroma_siting_h); + drmu_prop_range_delete(&dp->pid.chroma_siting_v); + drmu_prop_enum_delete(&dp->pid.color_encoding); + drmu_prop_enum_delete(&dp->pid.color_range); + drmu_prop_enum_delete(&dp->pid.pixel_blend_mode); + drmu_prop_enum_delete(&dp->pid.rotation); + drmu_prop_range_delete(&dp->pid.zpos); + free(dp->formats_in); + dp->formats_in = NULL; +} + + +static int +plane_init(drmu_env_t * const du, drmu_plane_t * const dp, const uint32_t plane_id) +{ + drmu_props_t *props; + int rv; + + memset(dp, 0, sizeof(*dp)); + dp->du = du; + + dp->plane.plane_id = plane_id; + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPLANE, &dp->plane)) != 0) { + drmu_err(du, "%s: drmModeGetPlane failed: %s", __func__, strerror(-rv)); + return rv; + } + + if ((props = props_new(du, dp->plane.plane_id, DRM_MODE_OBJECT_PLANE)) == NULL) + return -EINVAL; + +#if TRACE_PROP_NEW + drmu_info(du, "Plane id %d:", plane_id); + props_dump(props); +#endif + + if ((dp->pid.crtc_id = props_name_to_id(props, "CRTC_ID")) == 0 || + (dp->pid.fb_id = props_name_to_id(props, "FB_ID")) == 0 || + (dp->pid.crtc_h = drmu_prop_range_new(du, props_name_to_id(props, "CRTC_H"))) == NULL || + (dp->pid.crtc_w = drmu_prop_range_new(du, props_name_to_id(props, "CRTC_W"))) == NULL || + (dp->pid.crtc_x = props_name_to_id(props, "CRTC_X")) == 0 || + (dp->pid.crtc_y = props_name_to_id(props, "CRTC_Y")) == 0 || + (dp->pid.src_h = drmu_prop_range_new(du, props_name_to_id(props, "SRC_H"))) == NULL || + (dp->pid.src_w = drmu_prop_range_new(du, props_name_to_id(props, "SRC_W"))) == NULL || + (dp->pid.src_x = props_name_to_id(props, "SRC_X")) == 0 || + (dp->pid.src_y = props_name_to_id(props, "SRC_Y")) == 0 || + props_name_get_blob(props, "IN_FORMATS", &dp->formats_in, &dp->formats_in_len) != 0) + { + drmu_err(du, "%s: failed to find required id", __func__); + props_free(props); + return -EINVAL; + } + dp->fmts_hdr = dp->formats_in; + + dp->pid.alpha = drmu_prop_range_new(du, props_name_to_id(props, "alpha")); + dp->pid.color_encoding = drmu_prop_enum_new(du, props_name_to_id(props, "COLOR_ENCODING")); + dp->pid.color_range = drmu_prop_enum_new(du, props_name_to_id(props, "COLOR_RANGE")); + dp->pid.pixel_blend_mode = drmu_prop_enum_new(du, props_name_to_id(props, "pixel blend mode")); + dp->pid.rotation = drmu_prop_enum_new(du, props_name_to_id(props, "rotation")); + dp->pid.chroma_siting_h = drmu_prop_range_new(du, props_name_to_id(props, "CHROMA_SITING_H")); + dp->pid.chroma_siting_v = drmu_prop_range_new(du, props_name_to_id(props, "CHROMA_SITING_V")); + dp->pid.zpos = drmu_prop_range_new(du, props_name_to_id(props, "zpos")); + + dp->rot_vals[DRMU_PLANE_ROTATION_0] = drmu_prop_bitmask_value(dp->pid.rotation, "rotate-0"); + if (dp->rot_vals[DRMU_PLANE_ROTATION_0]) { + // Flips MUST be combined with a rotate + if ((dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] = drmu_prop_bitmask_value(dp->pid.rotation, "reflect-x")) != 0) + dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] |= dp->rot_vals[DRMU_PLANE_ROTATION_0]; + if ((dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP] = drmu_prop_bitmask_value(dp->pid.rotation, "reflect-y")) != 0) + dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP] |= dp->rot_vals[DRMU_PLANE_ROTATION_0]; + } + dp->rot_vals[DRMU_PLANE_ROTATION_180] = drmu_prop_bitmask_value(dp->pid.rotation, "rotate-180"); + if (!dp->rot_vals[DRMU_PLANE_ROTATION_180] && dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] && dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP]) + dp->rot_vals[DRMU_PLANE_ROTATION_180] = dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] | dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP]; + + { + const drmu_propinfo_t * const pinfo = props_name_to_propinfo(props, "type"); + drmu_prop_enum_t * etype = drmu_prop_enum_new(du, props_name_to_id(props, "type")); + const uint64_t * p; + + if ((p = drmu_prop_enum_value(etype, "Primary")) && *p == pinfo->val) + dp->plane_type = DRMU_PLANE_TYPE_PRIMARY; + else if ((p = drmu_prop_enum_value(etype, "Cursor")) && *p == pinfo->val) + dp->plane_type = DRMU_PLANE_TYPE_CURSOR; + else if ((p = drmu_prop_enum_value(etype, "Overlay")) && *p == pinfo->val) + dp->plane_type = DRMU_PLANE_TYPE_OVERLAY; + else { + drmu_debug(du, "Unexpected plane type: %"PRId64, pinfo->val); + dp->plane_type = DRMU_PLANE_TYPE_UNKNOWN; + } + drmu_prop_enum_delete(&etype); + } + + props_free(props); + return 0; +} + +//---------------------------------------------------------------------------- +// +// Env fns + +typedef struct drmu_env_s { + atomic_int ref_count; // 0 == 1 ref for ease of init + int fd; + uint32_t plane_count; + uint32_t conn_count; + uint32_t crtc_count; + drmu_plane_t * planes; + drmu_conn_t * conns; + drmu_crtc_t * crtcs; + + drmu_log_env_t log; + + // global env for atomic flip + drmu_atomic_q_t aq; + // global env for bo tracking + drmu_bo_env_t boe; + // global atomic for restore op + drmu_atomic_t * da_restore; + + struct pollqueue * pq; + struct polltask * pt; +} drmu_env_t; + +// Retrieve the the n-th conn +// Use for iteration +// Returns NULL when none left +drmu_crtc_t * +drmu_env_crtc_find_n(drmu_env_t * const du, const unsigned int n) +{ + return n >= du->crtc_count ? NULL : du->crtcs + n; +} + +// Retrieve the the n-th conn +// Use for iteration +// Returns NULL when none left +drmu_conn_t * +drmu_env_conn_find_n(drmu_env_t * const du, const unsigned int n) +{ + return n >= du->conn_count ? NULL : du->conns + n; +} + +drmu_plane_t * +drmu_env_plane_find_n(drmu_env_t * const du, const unsigned int n) +{ + return n >= du->plane_count ? NULL : du->planes + n; +} + +int +drmu_ioctl(const drmu_env_t * const du, unsigned long req, void * arg) +{ + while (ioctl(du->fd, req, arg)) { + const int err = errno; + // DRM docn suggests we should try again on EAGAIN as well as EINTR + // and drm userspace does this. + if (err != EINTR && err != EAGAIN) + return -err; + } + return 0; +} + +static void +env_free_planes(drmu_env_t * const du) +{ + uint32_t i; + for (i = 0; i != du->plane_count; ++i) + plane_uninit(du->planes + i); + free(du->planes); + du->plane_count = 0; + du->planes = NULL; +} + +static void +env_free_conns(drmu_env_t * const du) +{ + uint32_t i; + for (i = 0; i != du->conn_count; ++i) + conn_uninit(du->conns + i); + free(du->conns); + du->conn_count = 0; + du->conns = NULL; +} + +static void +env_free_crtcs(drmu_env_t * const du) +{ + uint32_t i; + for (i = 0; i != du->crtc_count; ++i) + crtc_uninit(du->crtcs + i); + free(du->crtcs); + du->crtc_count = 0; + du->crtcs = NULL; +} + + +static int +env_planes_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids) +{ + uint32_t i; + int rv; + + if ((du->planes = calloc(n, sizeof(*du->planes))) == NULL) { + drmu_err(du, "Plane array alloc failed"); + return -ENOMEM; + } + + for (i = 0; i != n; ++i) { + if ((rv = plane_init(du, du->planes + i, ids[i])) != 0) + goto fail2; + du->plane_count = i + 1; + } + return 0; + +fail2: + env_free_planes(du); + return rv; +} + +// Doesn't clean up on error - assumes that env construction will abort and +// that will tidy up for us +static int +env_conn_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids) +{ + unsigned int i; + int rv; + + if (n == 0) { + drmu_err(du, "No connectors"); + return -EINVAL; + } + + if ((du->conns = calloc(n, sizeof(*du->conns))) == NULL) { + drmu_err(du, "Failed to malloc conns"); + return -ENOMEM; + } + + for (i = 0; i != n; ++i) { + if ((rv = conn_init(du, du->conns + i, i, ids[i])) != 0) + return rv; + du->conn_count = i + 1; + } + + return 0; +} + +static int +env_crtc_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids) +{ + unsigned int i; + int rv; + + if (n == 0) { + drmu_err(du, "No crtcs"); + return -EINVAL; + } + + if ((du->crtcs = malloc(n * sizeof(*du->crtcs))) == NULL) { + drmu_err(du, "Failed to malloc conns"); + return -ENOMEM; + } + + for (i = 0; i != n; ++i) { + if ((rv = crtc_init(du, du->crtcs + i, i, ids[i])) != 0) + return rv; + du->crtc_count = i + 1; + } + + return 0; +} + + + +int +drmu_fd(const drmu_env_t * const du) +{ + return du->fd; +} + +const struct drmu_log_env_s * +drmu_env_log(const drmu_env_t * const du) +{ + return &du->log; +} + +static struct drmu_bo_env_s * +env_boe(drmu_env_t * const du) +{ + return &du->boe; +} + +static struct pollqueue * +env_pollqueue(const drmu_env_t * const du) +{ + return du->pq; +} + +static struct drmu_atomic_q_s * +env_atomic_q(drmu_env_t * const du) +{ + return &du->aq; +} + +static void +env_restore(drmu_env_t * const du) +{ + int rv; + drmu_atomic_t * bad = drmu_atomic_new(du); + if ((rv = drmu_atomic_commit_test(du->da_restore, DRM_MODE_ATOMIC_ALLOW_MODESET, bad)) != 0) { + drmu_atomic_sub(du->da_restore, bad); + if ((rv = drmu_atomic_commit(du->da_restore, DRM_MODE_ATOMIC_ALLOW_MODESET)) != 0) + drmu_err(du, "Failed to restore old mode on exit: %s", strerror(-rv)); + else + drmu_err(du, "Failed to completely restore old mode on exit"); + + if (drmu_env_log(du)->max_level >= DRMU_LOG_LEVEL_DEBUG) + drmu_atomic_dump(bad); + } + drmu_atomic_unref(&bad); + drmu_atomic_unref(&du->da_restore); +} + +static void +env_free(drmu_env_t * const du) +{ + if (!du) + return; + + atomic_q_kill(env_atomic_q(du)); + + polltask_delete(&du->pt); + pollqueue_finish(&du->pq); + + // Restore previous values after shutting down the polltask but + // before uniniting the Q commits + // + // The Q uninit stands a plausible chance of causing BOs or dmabufs that + // are in use to be reclaimed so restoring the old FBs before that is a + // good idea (prevents visible artifacts in VLC). + if (du->da_restore) + env_restore(du); + + atomic_q_uninit(&du->aq); + + env_free_planes(du); + env_free_conns(du); + env_free_crtcs(du); + drmu_bo_env_uninit(&du->boe); + + close(du->fd); + free(du); +} + +void +drmu_env_unref(drmu_env_t ** const ppdu) +{ + drmu_env_t * const du = *ppdu; + int n; + + if (!du) + return; + *ppdu = NULL; + + n = atomic_fetch_sub(&du->ref_count, 1); + assert(n >= 0); + if (n == 0) + env_free(du); +} + +// Kill the Q +// Ordering goes: +// Shutdown Q events +// Restore old state +// Clear previous atomics +// Must be done in that order or we end up destroying buffers that are +// still in use +void +drmu_env_kill(drmu_env_t ** const ppdu) +{ + drmu_env_t * du = *ppdu; + + if (!du) + return; + *ppdu = NULL; + + atomic_q_kill(&du->aq); + + polltask_delete(&du->pt); + pollqueue_finish(&du->pq); + + if (du->da_restore) + env_restore(du); + + atomic_q_clear_flips(&du->aq); + + // Q is now mostly shutdown. The mutex and cond are still valid so + // it is still possible to abtain teh lock and then note that it is + // dead. Remaining environment will be freed when drmu_env is freed. + + drmu_env_unref(&du); +} + +drmu_env_t * +drmu_env_ref(drmu_env_t * const du) +{ + if (du) + atomic_fetch_add(&du->ref_count, 1); + return du; +} + +static int +env_object_state_save(drmu_env_t * const du, const uint32_t obj_id, const uint32_t obj_type) +{ + drmu_props_t * props; + drmu_atomic_t * da; + int rv; + + if (!du->da_restore) + return -EINVAL; + + if ((props = props_new(du, obj_id, obj_type)) == NULL) + return -ENOENT; + + if ((da = drmu_atomic_new(du)) == NULL) { + rv = -ENOMEM; + goto fail; + } + + if ((rv = drmu_atomic_props_add_save(da, obj_id, props)) != 0) + goto fail; + + props_free(props); + return drmu_atomic_env_restore_add_snapshot(&da); + +fail: + if (props) + props_free(props); + return rv; +} + +int +drmu_env_restore_enable(drmu_env_t * const du) +{ + uint32_t i; + + if (du->da_restore) + return 0; + if ((du->da_restore = drmu_atomic_new(du)) == NULL) + return -ENOMEM; + + // Save state of anything already claimed + // Cannot rewind time but this allows us to be a bit lax with the + // precise ordering of calls on setup (which is handy for scan) + for (i = 0; i != du->conn_count; ++i) + if (drmu_conn_is_claimed(du->conns + i)) + conn_state_save(du, du->conns + i); + for (i = 0; i != du->crtc_count; ++i) + if (drmu_crtc_is_claimed(du->crtcs + i)) + crtc_state_save(du, du->crtcs + i); + for (i = 0; i != du->plane_count; ++i) + if (drmu_plane_is_claimed(du->planes + i)) + plane_state_save(du, du->planes + i); + + return 0; +} + +bool +drmu_env_restore_is_enabled(const drmu_env_t * const du) +{ + return du->da_restore != NULL; +} + +int +drmu_atomic_env_restore_add_snapshot(drmu_atomic_t ** const ppda) +{ + drmu_atomic_t * da = *ppda; + drmu_atomic_t * fails = NULL; + drmu_env_t * const du = drmu_atomic_env(da); + + *ppda = NULL; + + if (!du || !du->da_restore) { + drmu_atomic_unref(&da); + return 0; + } + + // Lose anything we can't restore + fails = drmu_atomic_new(du); + if (drmu_atomic_commit_test(da, DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_ATOMIC_TEST_ONLY, fails) != 0) + drmu_atomic_sub(da, fails); + drmu_atomic_unref(&fails); + + return drmu_atomic_merge(du->da_restore, &da); +} + +#define EVT(p) ((const struct drm_event *)(p)) +static int +evt_read(drmu_env_t * const du) +{ + uint8_t buf[256] __attribute__((aligned(__BIGGEST_ALIGNMENT__))); + const ssize_t rlen = read(drmu_fd(du), buf, sizeof(buf)); + size_t i; + + if (rlen < 0) { + const int err = errno; + drmu_err(du, "Event read failure: %s", strerror(err)); + return -err; + } + + for (i = 0; + i + sizeof(struct drm_event) <= (size_t)rlen && EVT(buf + i)->length <= (size_t)rlen - i; + i += EVT(buf + i)->length) { + switch (EVT(buf + i)->type) { + case DRM_EVENT_FLIP_COMPLETE: + { + const struct drm_event_vblank * const vb = (struct drm_event_vblank*)(buf + i); + if (EVT(buf + i)->length < sizeof(*vb)) + break; + + drmu_atomic_page_flip_cb(du, (void *)(uintptr_t)vb->user_data); + break; + } + default: + drmu_warn(du, "Unexpected DRM event #%x", EVT(buf + i)->type); + break; + } + } + + if (i != (size_t)rlen) + drmu_warn(du, "Partial event received: len=%zd, processed=%zd", rlen, i); + + return 0; +} +#undef EVT + +static void +drmu_env_polltask_cb(void * v, short revents) +{ + drmu_env_t * const du = v; + + if (revents == 0) { + drmu_debug(du, "%s: Timeout", __func__); + } + else { + evt_read(du); + } + + pollqueue_add_task(du->pt, 1000); +} + +static int +env_set_client_cap(drmu_env_t * const du, uint64_t cap_id, uint64_t cap_val) +{ + struct drm_set_client_cap cap = { + .capability = cap_id, + .value = cap_val + }; + return drmu_ioctl(du, DRM_IOCTL_SET_CLIENT_CAP, &cap); +} + +// Closes fd on failure +drmu_env_t * +drmu_env_new_fd(const int fd, const struct drmu_log_env_s * const log) +{ + drmu_env_t * const du = calloc(1, sizeof(*du)); + int rv; + uint32_t * conn_ids = NULL; + uint32_t * crtc_ids = NULL; + uint32_t * plane_ids = NULL; + + if (!du) { + drmu_err_log(log, "Failed to create du: No memory"); + close(fd); + return NULL; + } + + du->log = (log == NULL) ? drmu_log_env_none : *log; + du->fd = fd; + + drmu_bo_env_init(&du->boe); + atomic_q_init(&du->aq); + + // We need atomic for almost everything we do + if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_ATOMIC, 1)) != 0) { + drmu_err(du, "Failed to set atomic cap"); + goto fail1; + } + // We want the primary plane for video + if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) != 0) + drmu_debug(du, "Failed to set universal planes cap"); + // We can understand AR info + if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_ASPECT_RATIO, 1)) != 0) + drmu_debug(du, "Failed to set AR cap"); + // We would like to see writeback connectors + if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1)) != 0) + drmu_debug(du, "Failed to set writeback cap"); + + { + struct drm_mode_get_plane_res res; + uint32_t req_planes = 0; + + do { + memset(&res, 0, sizeof(res)); + res.plane_id_ptr = (uintptr_t)plane_ids; + res.count_planes = req_planes; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPLANERESOURCES, &res)) != 0) { + drmu_err(du, "Failed to get resources: %s", strerror(-rv)); + goto fail1; + } + } while ((rv = retry_alloc_u32(&plane_ids, &req_planes, res.count_planes)) == 1); + if (rv < 0) + goto fail1; + + if (env_planes_populate(du, res.count_planes, plane_ids) != 0) + goto fail1; + + free(plane_ids); + plane_ids = NULL; + } + + { + struct drm_mode_card_res res; + uint32_t req_conns = 0; + uint32_t req_crtcs = 0; + + for (;;) { + memset(&res, 0, sizeof(res)); + res.crtc_id_ptr = (uintptr_t)crtc_ids; + res.connector_id_ptr = (uintptr_t)conn_ids; + res.count_crtcs = req_crtcs; + res.count_connectors = req_conns; + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETRESOURCES, &res)) != 0) { + drmu_err(du, "Failed to get resources: %s", strerror(-rv)); + goto fail1; + } + + if (res.count_crtcs <= req_crtcs && res.count_connectors <= req_conns) + break; + + if (retry_alloc_u32(&conn_ids, &req_conns, res.count_connectors) < 0 || + retry_alloc_u32(&crtc_ids, &req_crtcs, res.count_crtcs) < 0) + goto fail1; + } + + if (env_conn_populate(du, res.count_connectors, conn_ids) != 0) + goto fail1; + if (env_crtc_populate(du, res.count_crtcs, crtc_ids) != 0) + goto fail1; + + free(conn_ids); + free(crtc_ids); + conn_ids = NULL; + crtc_ids = NULL; + } + + if ((du->pq = pollqueue_new()) == NULL) { + drmu_err(du, "Failed to create pollqueue"); + goto fail1; + } + if ((du->pt = polltask_new(du->pq, du->fd, POLLIN | POLLPRI, drmu_env_polltask_cb, du)) == NULL) { + drmu_err(du, "Failed to create polltask"); + goto fail1; + } + + pollqueue_add_task(du->pt, 1000); + + free(plane_ids); + return du; + +fail1: + env_free(du); + free(conn_ids); + free(crtc_ids); + free(plane_ids); + return NULL; +} + +drmu_env_t * +drmu_env_new_open(const char * name, const struct drmu_log_env_s * const log2) +{ + const struct drmu_log_env_s * const log = (log2 == NULL) ? &drmu_log_env_none : log2; + int fd = drmOpen(name, NULL); + if (fd == -1) { + drmu_err_log(log, "Failed to open %s", name); + return NULL; + } + return drmu_env_new_fd(fd, log); +} + +//---------------------------------------------------------------------------- +// +// Logging + +static void +log_none_cb(void * v, enum drmu_log_level_e level, const char * fmt, va_list vl) +{ + (void)v; + (void)level; + (void)fmt; + (void)vl; +} + +const struct drmu_log_env_s drmu_log_env_none = { + .fn = log_none_cb, + .v = NULL, + .max_level = DRMU_LOG_LEVEL_NONE +}; + +void +drmu_log_generic(const struct drmu_log_env_s * const log, const enum drmu_log_level_e level, + const char * const fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + log->fn(log->v, level, fmt, vl); + va_end(vl); +} + + diff --git a/modules/video_output/drmu/drmu.h b/modules/video_output/drmu/drmu.h new file mode 100644 index 0000000000..c2f404926e --- /dev/null +++ b/modules/video_output/drmu/drmu.h @@ -0,0 +1,565 @@ +#ifndef _DRMU_DRMU_H +#define _DRMU_DRMU_H + +#include +#include +#include +#include + +#include "drmu_chroma.h" +#include "drmu_math.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct drmu_blob_s; +typedef struct drmu_blob_s drmu_blob_t; + +struct drmu_prop_enum_s; +typedef struct drmu_prop_enum_s drmu_prop_enum_t; + +struct drmu_prop_range_s; +typedef struct drmu_prop_range_s drmu_prop_range_t; + +struct drmu_bo_s; +typedef struct drmu_bo_s drmu_bo_t; +struct drmu_bo_env_s; +typedef struct drmu_bo_env_s drmu_bo_env_t; + +struct drmu_fb_s; +typedef struct drmu_fb_s drmu_fb_t; + +struct drmu_prop_object_s; +typedef struct drmu_prop_object_s drmu_prop_object_t; + +struct drmu_crtc_s; +typedef struct drmu_crtc_s drmu_crtc_t; + +struct drmu_conn_s; +typedef struct drmu_conn_s drmu_conn_t; + +struct drmu_plane_s; +typedef struct drmu_plane_s drmu_plane_t; + +struct drmu_atomic_s; + +struct drmu_env_s; +typedef struct drmu_env_s drmu_env_t; + +struct drm_log_env_s; +typedef struct drmu_log_env_s drmu_log_env_t; + +// HDR enums is copied from linux include/linux/hdmi.h (strangely not part of uapi) +enum hdmi_metadata_type +{ + HDMI_STATIC_METADATA_TYPE1 = 0, +}; +enum hdmi_eotf +{ + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, + HDMI_EOTF_TRADITIONAL_GAMMA_HDR, + HDMI_EOTF_SMPTE_ST2084, + HDMI_EOTF_BT_2100_HLG, +}; + +typedef enum drmu_isset_e { + DRMU_ISSET_UNSET = 0, // Thing unset + DRMU_ISSET_NULL, // Thing is empty + DRMU_ISSET_SET, // Thing has valid data +} drmu_isset_t; + +// Blob + +void drmu_blob_unref(drmu_blob_t ** const ppBlob); +uint32_t drmu_blob_id(const drmu_blob_t * const blob); +// blob data & length +const void * drmu_blob_data(const drmu_blob_t * const blob); +size_t drmu_blob_len(const drmu_blob_t * const blob); + +drmu_blob_t * drmu_blob_ref(drmu_blob_t * const blob); +// Make a new blob - keeps a copy of the data +drmu_blob_t * drmu_blob_new(drmu_env_t * const du, const void * const data, const size_t len); +// Update a blob with new data +// Creates if it didn't exist before, unrefs if data NULL +int drmu_blob_update(drmu_env_t * const du, drmu_blob_t ** const ppblob, const void * const data, const size_t len); +// Create a new blob from an existing blob_id +drmu_blob_t * drmu_blob_copy_id(drmu_env_t * const du, uint32_t blob_id); +int drmu_atomic_add_prop_blob(struct drmu_atomic_s * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_blob_t * const blob); + +// Enum & bitmask +// These are very close to the same thing so we use the same struct +typedef drmu_prop_enum_t drmu_prop_bitmask_t; + +// Ptr to value of the named enum/bit, NULL if not found or pen == NULL. If bitmask then bit number +const uint64_t * drmu_prop_enum_value(const drmu_prop_enum_t * const pen, const char * const name); +// Bitmask only - value as a (single-bit) bitmask - 0 if not found or not bitmask or pen == NULL +uint64_t drmu_prop_bitmask_value(const drmu_prop_enum_t * const pen, const char * const name); + +uint32_t drmu_prop_enum_id(const drmu_prop_enum_t * const pen); +#define drmu_prop_bitmask_id drmu_prop_enum_id +void drmu_prop_enum_delete(drmu_prop_enum_t ** const pppen); +#define drmu_prop_bitmask_delete drmu_prop_enum_delete +drmu_prop_enum_t * drmu_prop_enum_new(drmu_env_t * const du, const uint32_t id); +#define drmu_prop_bitmask_new drmu_prop_enum_new +int drmu_atomic_add_prop_enum(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const char * const name); +int drmu_atomic_add_prop_bitmask(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const uint64_t value); + +// Range + +void drmu_prop_range_delete(drmu_prop_range_t ** pppra); +bool drmu_prop_range_validate(const drmu_prop_range_t * const pra, const uint64_t x); +bool drmu_prop_range_immutable(const drmu_prop_range_t * const pra); +uint64_t drmu_prop_range_max(const drmu_prop_range_t * const pra); +uint64_t drmu_prop_range_min(const drmu_prop_range_t * const pra); +uint32_t drmu_prop_range_id(const drmu_prop_range_t * const pra); +const char * drmu_prop_range_name(const drmu_prop_range_t * const pra); +drmu_prop_range_t * drmu_prop_range_new(drmu_env_t * const du, const uint32_t id); +int drmu_atomic_add_prop_range(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_range_t * const pra, const uint64_t x); + +// BO + +struct drm_mode_create_dumb; + +// Create an fd from a bo +// fd not tracked by the bo so it is the callers reponsibility to free it +// if flags are 0 then RDWR | CLOEXEC will be used +int drmu_bo_export_fd(drmu_bo_t * bo, uint32_t flags); + +void drmu_bo_unref(drmu_bo_t ** const ppbo); +drmu_bo_t * drmu_bo_ref(drmu_bo_t * const bo); +drmu_bo_t * drmu_bo_new_fd(drmu_env_t *const du, const int fd); +drmu_bo_t * drmu_bo_new_dumb(drmu_env_t *const du, struct drm_mode_create_dumb * const d); +drmu_bo_t * drmu_bo_new_external(drmu_env_t *const du, const uint32_t bo_handle); +void drmu_bo_env_uninit(drmu_bo_env_t * const boe); +void drmu_bo_env_init(drmu_bo_env_t * boe); + +// fb +struct hdr_output_metadata; +struct drmu_fmt_info_s; + +// Called pre delete. +// Zero returned means continue delete. +// Non-zero means stop delete - fb will have zero refs so will probably want a new ref +// before next use +typedef int (* drmu_fb_pre_delete_fn)(struct drmu_fb_s * dfb, void * v); +// Called after an fb has been deleted and therefore has ceased using any +// user resources +typedef void (* drmu_fb_on_delete_fn)(void * v); + +void drmu_fb_pre_delete_set(drmu_fb_t *const dfb, drmu_fb_pre_delete_fn fn, void * v); +void drmu_fb_pre_delete_unset(drmu_fb_t *const dfb); +unsigned int drmu_fb_pixel_bits(const drmu_fb_t * const dfb); +uint32_t drmu_fb_pixel_format(const drmu_fb_t * const dfb); +uint64_t drmu_fb_modifier(const drmu_fb_t * const dfb, const unsigned int plane); +drmu_fb_t * drmu_fb_new_dumb(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format); +drmu_fb_t * drmu_fb_new_dumb_mod(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod); +drmu_fb_t * drmu_fb_realloc_dumb(drmu_env_t * const du, drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format); +drmu_fb_t * drmu_fb_realloc_dumb_mod(drmu_env_t * const du, drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod); +// Try to reset geometry to these values +// True if done, false if not +bool drmu_fb_try_reuse(drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod); +void drmu_fb_unref(drmu_fb_t ** const ppdfb); +drmu_fb_t * drmu_fb_ref(drmu_fb_t * const dfb); + +#define DRMU_FB_PIXEL_BLEND_UNSET NULL +#define DRMU_FB_PIXEL_BLEND_PRE_MULTIPLIED "Pre-multiplied" // Default +#define DRMU_FB_PIXEL_BLEND_COVERAGE "Coverage" // Not premultipled +#define DRMU_FB_PIXEL_BLEND_NONE "None" // Ignore pixel alpha (opaque) +int drmu_fb_pixel_blend_mode_set(drmu_fb_t *const dfb, const char * const mode); + +uint32_t drmu_fb_pitch(const drmu_fb_t *const dfb, const unsigned int layer); +// Pitch2 is only a sand thing +uint32_t drmu_fb_pitch2(const drmu_fb_t *const dfb, const unsigned int layer); +void * drmu_fb_data(const drmu_fb_t *const dfb, const unsigned int layer); +drmu_bo_t * drmu_fb_bo(const drmu_fb_t * const dfb, const unsigned int layer); +// Allocated width height - may be rounded up from requested w/h +uint32_t drmu_fb_width(const drmu_fb_t *const dfb); +uint32_t drmu_fb_height(const drmu_fb_t *const dfb); +// Set cropping (fractional) - x, y, relative to active x, y (and must be +ve) +int drmu_fb_crop_frac_set(drmu_fb_t *const dfb, drmu_rect_t crop_frac); +// get cropping (fractional 16.16) x, y relative to active area +drmu_rect_t drmu_fb_crop_frac(const drmu_fb_t *const dfb); +// get active area (all valid pixels - buffer can/will contain padding outside this) +// rect in pixels (not fractional) +drmu_rect_t drmu_fb_active(const drmu_fb_t *const dfb); + +int drmu_atomic_add_prop_fb(struct drmu_atomic_s * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb); + +// FB creation helpers - only use for creatino of new FBs +drmu_fb_t * drmu_fb_int_alloc(drmu_env_t * const du); +void drmu_fb_int_free(drmu_fb_t * const dfb); +// Set size +// w, h are buffer sizes, active is the valid pixel area inside that +// crop will be set to the whole active area +void drmu_fb_int_fmt_size_set(drmu_fb_t *const dfb, uint32_t fmt, uint32_t w, uint32_t h, const drmu_rect_t active); +// All assumed to be const strings that do not need freed +typedef const char * drmu_color_encoding_t; +#define DRMU_COLOR_ENCODING_UNSET NULL +#define DRMU_COLOR_ENCODING_BT2020 "ITU-R BT.2020 YCbCr" +#define DRMU_COLOR_ENCODING_BT709 "ITU-R BT.709 YCbCr" +#define DRMU_COLOR_ENCODING_BT601 "ITU-R BT.601 YCbCr" +static inline bool drmu_color_encoding_is_set(const drmu_color_encoding_t x) {return x != NULL;} +// Note: Color range only applies to YCbCr planes - ignored for RGB +typedef const char * drmu_color_range_t; +#define DRMU_COLOR_RANGE_UNSET NULL +#define DRMU_COLOR_RANGE_YCBCR_FULL_RANGE "YCbCr full range" +#define DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE "YCbCr limited range" +static inline bool drmu_color_range_is_set(const drmu_color_range_t x) {return x != NULL;} +typedef const char * drmu_colorspace_t; +#define DRMU_COLORSPACE_UNSET NULL +#define DRMU_COLORSPACE_DEFAULT "Default" +#define DRMU_COLORSPACE_BT2020_CYCC "BT2020_CYCC" +#define DRMU_COLORSPACE_BT2020_RGB "BT2020_RGB" +#define DRMU_COLORSPACE_BT2020_YCC "BT2020_YCC" +#define DRMU_COLORSPACE_BT709_YCC "BT709_YCC" +#define DRMU_COLORSPACE_DCI_P3_RGB_D65 "DCI-P3_RGB_D65" +#define DRMU_COLORSPACE_DCI_P3_RGB_THEATER "DCI-P3_RGB_Theater" +#define DRMU_COLORSPACE_SMPTE_170M_YCC "SMPTE_170M_YCC" +#define DRMU_COLORSPACE_SYCC_601 "SYCC_601" +#define DRMU_COLORSPACE_XVYCC_601 "XVYCC_601" +#define DRMU_COLORSPACE_XVYCC_709 "XVYCC_709" +static inline bool drmu_colorspace_is_set(const drmu_colorspace_t x) {return x != NULL;} +typedef const char * drmu_broadcast_rgb_t; +#define DRMU_BROADCAST_RGB_UNSET NULL +#define DRMU_BROADCAST_RGB_AUTOMATIC "Automatic" +#define DRMU_BROADCAST_RGB_FULL "Full" +#define DRMU_BROADCAST_RGB_LIMITED_16_235 "Limited 16:235" +static inline bool drmu_broadcast_rgb_is_set(const drmu_broadcast_rgb_t x) {return x != NULL;} +void drmu_fb_color_set(drmu_fb_t *const dfb, const drmu_color_encoding_t enc, const drmu_color_range_t range, const drmu_colorspace_t space); +void drmu_fb_chroma_siting_set(drmu_fb_t *const dfb, const drmu_chroma_siting_t siting); +void drmu_fb_int_on_delete_set(drmu_fb_t *const dfb, drmu_fb_on_delete_fn fn, void * v); +void drmu_fb_int_bo_set(drmu_fb_t *const dfb, unsigned int i, drmu_bo_t * const bo); +void drmu_fb_int_layer_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset); +void drmu_fb_int_layer_mod_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset, uint64_t modifier); +void drmu_fb_int_fd_set(drmu_fb_t *const dfb, const int fd); +void drmu_fb_int_mmap_set(drmu_fb_t *const dfb, void * const buf, const size_t size, const size_t pitch); +drmu_isset_t drmu_fb_hdr_metadata_isset(const drmu_fb_t *const dfb); +const struct hdr_output_metadata * drmu_fb_hdr_metadata_get(const drmu_fb_t *const dfb); +drmu_broadcast_rgb_t drmu_color_range_to_broadcast_rgb(const drmu_color_range_t range); +drmu_colorspace_t drmu_fb_colorspace_get(const drmu_fb_t * const dfb); +drmu_color_range_t drmu_fb_color_range_get(const drmu_fb_t * const dfb); +const struct drmu_fmt_info_s * drmu_fb_format_info_get(const drmu_fb_t * const dfb); +void drmu_fb_hdr_metadata_set(drmu_fb_t *const dfb, const struct hdr_output_metadata * meta); +int drmu_fb_int_make(drmu_fb_t *const dfb); + +// Cached fb sync ops +int drmu_fb_write_start(drmu_fb_t * const dfb); +int drmu_fb_write_end(drmu_fb_t * const dfb); +int drmu_fb_read_start(drmu_fb_t * const dfb); +int drmu_fb_read_end(drmu_fb_t * const dfb); + +// Wait for data to become ready when fb used as destination of writeback +// Returns: +// -ve error +// 0 timeout +// 1 ready +int drmu_fb_out_fence_wait(drmu_fb_t * const fb, const int timeout_ms); + +// Object Id + +struct drmu_propinfo_s; + +uint32_t drmu_prop_object_value(const drmu_prop_object_t * const obj); +void drmu_prop_object_unref(drmu_prop_object_t ** ppobj); +drmu_prop_object_t * drmu_prop_object_new_propinfo(drmu_env_t * const du, const uint32_t obj_id, const struct drmu_propinfo_s * const pi); +int drmu_atomic_add_prop_object(struct drmu_atomic_s * const da, drmu_prop_object_t * obj, uint32_t val); + +// Props + +// Grab all the props of an object and add to an atomic +// * Does not add references to any properties (BO or FB) currently, it maybe +// should but if so we need to avoid accidentally closing BOs that we inherit +// from outside when we delete the atomic. +int drmu_atomic_obj_add_snapshot(struct drmu_atomic_s * const da, const uint32_t objid, const uint32_t objtype); + +// CRTC + +struct _drmModeModeInfo; +struct hdr_output_metadata; + +void drmu_crtc_delete(drmu_crtc_t ** ppdc); +drmu_env_t * drmu_crtc_env(const drmu_crtc_t * const dc); +uint32_t drmu_crtc_id(const drmu_crtc_t * const dc); +int drmu_crtc_idx(const drmu_crtc_t * const dc); + +drmu_crtc_t * drmu_env_crtc_find_id(drmu_env_t * const du, const uint32_t crtc_id); +drmu_crtc_t * drmu_env_crtc_find_n(drmu_env_t * const du, const unsigned int n); + +typedef struct drmu_mode_simple_params_s { + unsigned int width; + unsigned int height; + unsigned int hz_x_1000; // Frame rate * 1000 i.e. 50Hz = 50000 (N.B. frame not field rate if interlaced) + drmu_ufrac_t par; // Picture Aspect Ratio (0:0 if unknown) + drmu_ufrac_t sar; // Sample Aspect Ratio + uint32_t type; + uint32_t flags; +} drmu_mode_simple_params_t; + +const struct drm_mode_modeinfo * drmu_crtc_modeinfo(const drmu_crtc_t * const dc); +// Get simple properties of initial crtc mode +drmu_mode_simple_params_t drmu_crtc_mode_simple_params(const drmu_crtc_t * const dc); + +int drmu_atomic_crtc_add_modeinfo(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, const struct drm_mode_modeinfo * const modeinfo); +int drmu_atomic_crtc_add_active(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, unsigned int val); + +bool drmu_crtc_is_claimed(const drmu_crtc_t * const dc); +void drmu_crtc_unref(drmu_crtc_t ** const ppdc); +drmu_crtc_t * drmu_crtc_ref(drmu_crtc_t * const dc); +// A Conn should be claimed before any op that might change its state +int drmu_crtc_claim_ref(drmu_crtc_t * const dc); + +// Connector + +// Set none if m=NULL +int drmu_atomic_conn_add_hdr_metadata(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const struct hdr_output_metadata * const m); + +// False set max_bpc to 8, true max value +int drmu_atomic_conn_add_hi_bpc(struct drmu_atomic_s * const da, drmu_conn_t * const dn, bool hi_bpc); + +int drmu_atomic_conn_add_colorspace(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const drmu_colorspace_t colorspace); +int drmu_atomic_conn_add_broadcast_rgb(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const drmu_broadcast_rgb_t bcrgb); + +// Add crtc id +int drmu_atomic_conn_add_crtc(struct drmu_atomic_s * const da, drmu_conn_t * const dn, drmu_crtc_t * const dc); + +// Add writeback fb & fence +// Neither makes sense without the other so do together +int drmu_atomic_conn_add_writeback_fb(struct drmu_atomic_s * const da, drmu_conn_t * const dn, drmu_fb_t * const dfb); + + +const struct drm_mode_modeinfo * drmu_conn_modeinfo(const drmu_conn_t * const dn, const int mode_id); +drmu_mode_simple_params_t drmu_conn_mode_simple_params(const drmu_conn_t * const dn, const int mode_id); + +// Beware: this refects initial value or the last thing set, but currently +// has no way of guessing if the atomic from the set was ever committed +// successfully +uint32_t drmu_conn_crtc_id_get(const drmu_conn_t * const dn); + +// Bitmask of CRTCs that might be able to use this Conn +uint32_t drmu_conn_possible_crtcs(const drmu_conn_t * const dn); + +bool drmu_conn_is_output(const drmu_conn_t * const dn); +bool drmu_conn_is_writeback(const drmu_conn_t * const dn); +const char * drmu_conn_name(const drmu_conn_t * const dn); +unsigned int drmu_conn_idx_get(const drmu_conn_t * const dn); + +// Retrieve the the n-th conn. Use for iteration. Returns NULL when none left +drmu_conn_t * drmu_env_conn_find_n(drmu_env_t * const du, const unsigned int n); + +bool drmu_conn_is_claimed(const drmu_conn_t * const dn); +void drmu_conn_unref(drmu_conn_t ** const ppdn); +drmu_conn_t * drmu_conn_ref(drmu_conn_t * const dn); +// A Conn should be claimed before any op that might change its state +int drmu_conn_claim_ref(drmu_conn_t * const dn); + + +// Plane + +uint32_t drmu_plane_id(const drmu_plane_t * const dp); + +#define DRMU_PLANE_TYPE_CURSOR 4 +#define DRMU_PLANE_TYPE_PRIMARY 2 +#define DRMU_PLANE_TYPE_OVERLAY 1 +#define DRMU_PLANE_TYPE_UNKNOWN 0 +unsigned int drmu_plane_type(const drmu_plane_t * const dp); + +const uint32_t * drmu_plane_formats(const drmu_plane_t * const dp, unsigned int * const pCount); +bool drmu_plane_format_check(const drmu_plane_t * const dp, const uint32_t format, const uint64_t modifier); + +// Alpha: -1 = no not set, 0 = transparent, 0xffff = opaque +#define DRMU_PLANE_ALPHA_UNSET (-1) +#define DRMU_PLANE_ALPHA_TRANSPARENT 0 +#define DRMU_PLANE_ALPHA_OPAQUE 0xffff +int drmu_atomic_plane_add_alpha(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int alpha); + +int drmu_atomic_plane_add_zpos(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int zpos); + +// X, Y & TRANSPOSE can be ORed to get all others +#define DRMU_PLANE_ROTATION_0 0 +#define DRMU_PLANE_ROTATION_X_FLIP 1 +#define DRMU_PLANE_ROTATION_Y_FLIP 2 +#define DRMU_PLANE_ROTATION_180 3 +// *** These don't exist on Pi - no inherent transpose +#define DRMU_PLANE_ROTATION_TRANSPOSE 4 +#define DRMU_PLANE_ROTATION_90 5 // Rotate 90 clockwise +#define DRMU_PLANE_ROTATION_270 6 // Rotate 90 anti-cockwise +#define DRMU_PLANE_ROTATION_180_TRANSPOSE 7 // Rotate 180 & transpose +int drmu_atomic_plane_add_rotation(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int rot); + +int drmu_atomic_plane_add_chroma_siting(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const drmu_chroma_siting_t siting); + +// Set FB to 0 (i.e. clear the plane) +int drmu_atomic_plane_clear_add(struct drmu_atomic_s * const da, drmu_plane_t * const dp); + +// Adds the fb to the plane along with all fb properties that apply to a plane +// If fb == NULL is equivalent to _plane_clear_add +// pos is dest rect on the plane in full pixels (not frac) +int drmu_atomic_plane_add_fb(struct drmu_atomic_s * const da, drmu_plane_t * const dp, drmu_fb_t * const dfb, const drmu_rect_t pos); + +// Is this plane reffed? +bool drmu_plane_is_claimed(drmu_plane_t * const dp); + +// Unref a plane +void drmu_plane_unref(drmu_plane_t ** const ppdp); + +// Ref a plane - expects it is already associated +drmu_plane_t * drmu_plane_ref(drmu_plane_t * const dp); + +// Associate a plane with a crtc and ref it +// Returns -EBUSY if plane already associated +int drmu_plane_ref_crtc(drmu_plane_t * const dp, drmu_crtc_t * const dc); + +typedef bool (*drmu_plane_new_find_ok_fn)(const drmu_plane_t * dp, void * v); + +// Find a "free" plane that satisfies (returns true) the ok callback +// Does not ref +drmu_plane_t * drmu_plane_new_find(drmu_crtc_t * const dc, const drmu_plane_new_find_ok_fn cb, void * const v); +// Find a "free" plane of the given type. Types can be ORed +// Does not ref +drmu_plane_t * drmu_plane_new_find_type(drmu_crtc_t * const dc, const unsigned int req_type); + +drmu_plane_t * drmu_env_plane_find_n(drmu_env_t * const du, const unsigned int n); + + +// Env +struct drmu_log_env_s; + +// Q the atomic on its associated env +// +// in-progress = The commit has been done but no ack yet +// pending = Commit Qed to be done when the in-progress commit has +// completed +// +// If no in-progress commit then this will be committed immediately +// otherwise it becomes the pending commit +// If there is a pending commit this atomic will be merged with it +// Commits are done with the PAGE_FLIP flag set so we expect the ack +// on the next page flip. +int drmu_atomic_queue(struct drmu_atomic_s ** ppda); +// Wait for there to be no pending commit (there may be a commit in +// progress) +int drmu_env_queue_wait(drmu_env_t * const du); + +// Do ioctl - returns -errno on error, 0 on success +// deals with recalling the ioctl when required +int drmu_ioctl(const drmu_env_t * const du, unsigned long req, void * arg); +int drmu_fd(const drmu_env_t * const du); +const struct drmu_log_env_s * drmu_env_log(const drmu_env_t * const du); +void drmu_env_unref(drmu_env_t ** const ppdu); +drmu_env_t * drmu_env_ref(drmu_env_t * const du); +// Disable queue, restore saved state and unref +// Doesn't guarantee that the env will be freed by exit as there may still be +// buffers that hold a ref for logging or DRM fd but it should resolve circular +// reference problems where buffers on the screen hold refs to the env. +void drmu_env_kill(drmu_env_t ** const ppdu); +// Restore state on env close +int drmu_env_restore_enable(drmu_env_t * const du); +bool drmu_env_restore_is_enabled(const drmu_env_t * const du); +// Add an object snapshot to the restore state +// Tests for commitability and removes any props that won't commit +int drmu_atomic_env_restore_add_snapshot(struct drmu_atomic_s ** const ppda); + +// Open a drmu environment with the drm fd +// Takes a logging structure so early errors can be reported. The logging +// environment is copied so does not have to be valid for greater than the +// duration of the call. +// If log = NULL logging is disabled (set to drmu_log_env_none). +drmu_env_t * drmu_env_new_fd(const int fd, const struct drmu_log_env_s * const log); +drmu_env_t * drmu_env_new_open(const char * name, const struct drmu_log_env_s * const log); + +// Logging + +extern const struct drmu_log_env_s drmu_log_env_none; // pre-built do-nothing log structure + +// drmu_atomic + +struct drmu_atomic_s; +typedef struct drmu_atomic_s drmu_atomic_t; + +void drmu_atomic_dump(const drmu_atomic_t * const da); +drmu_env_t * drmu_atomic_env(const drmu_atomic_t * const da); +void drmu_atomic_unref(drmu_atomic_t ** const ppda); +drmu_atomic_t * drmu_atomic_ref(drmu_atomic_t * const da); +drmu_atomic_t * drmu_atomic_new(drmu_env_t * const du); + +// Copy (rather than just ref) b +drmu_atomic_t * drmu_atomic_copy(drmu_atomic_t * const b); + +// 'Move' b to the return value +// If b has a single ref then rv is simply b otherwise it is a copy of b +drmu_atomic_t * drmu_atomic_move(drmu_atomic_t ** const ppb); + +// Merge b into a +// This reference to b is unrefed (inc. on error); if this was the only +// reference to b this allows the code to simply move properites from b +// to a rather than having to copy. If there is >1 ref then the merge +// will copy safely without breaking the other refs to b. +int drmu_atomic_merge(drmu_atomic_t * const a, drmu_atomic_t ** const ppb); + +static inline int drmu_atomic_move_merge(drmu_atomic_t ** const ppa, drmu_atomic_t ** const ppb) +{ + if (*ppa) + return drmu_atomic_merge(*ppa, ppb); + *ppa = drmu_atomic_move(ppb); + return 0; +} + +// Remove all els in a that are also in b +// b may be sorted (if not already) but is otherwise unchanged +void drmu_atomic_sub(drmu_atomic_t * const a, drmu_atomic_t * const b); + +// flags are DRM_MODE_ATOMIC_xxx (e.g. DRM_MODE_ATOMIC_TEST_ONLY) and DRM_MODE_PAGE_FLIP_xxx +int drmu_atomic_commit(const drmu_atomic_t * const da, uint32_t flags); +// Attempt commit - if it fails add failing members to da_fail +// This does NOT remove failing props from da. If da_fail == NULL then same as _commit +int drmu_atomic_commit_test(const drmu_atomic_t * const da, uint32_t flags, drmu_atomic_t * const da_fail); + +// Add a callback that occurs when the atomic has been committed +// This will occur on flip if atomic queued via _atomic_queue - if multiple +// atomics are queued before flip then all fill occur on the same flip +// If cb is 0 then NOP +typedef void drmu_atomic_commit_fn(void * v); +int drmu_atomic_add_commit_callback(drmu_atomic_t * const da, drmu_atomic_commit_fn * const cb, void * const v); +// Clear all commit callbacks from this atomic +void drmu_atomic_clear_commit_callbacks(drmu_atomic_t * const da); +// Run all commit callbacks on this atomic. Callbacks are not cleared. +void drmu_atomic_run_commit_callbacks(const drmu_atomic_t * const da); + +typedef void drmu_prop_unref_fn(void * v); +typedef void drmu_prop_ref_fn(void * v); +typedef void drmu_prop_commit_fn(void * v, uint64_t value); + +typedef struct drmu_atomic_prop_fns_s { + drmu_prop_ref_fn * ref; + drmu_prop_unref_fn * unref; + drmu_prop_commit_fn * commit; +} drmu_atomic_prop_fns_t; + +drmu_prop_ref_fn drmu_prop_fn_null_unref; +drmu_prop_unref_fn drmu_prop_fn_null_ref; +drmu_prop_commit_fn drmu_prop_fn_null_commit; + +int drmu_atomic_add_prop_generic(drmu_atomic_t * const da, + const uint32_t obj_id, const uint32_t prop_id, const uint64_t value, + const drmu_atomic_prop_fns_t * const fns, void * const v); +int drmu_atomic_add_prop_value(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, const uint64_t value); + +// drmu_xlease + +drmu_env_t * drmu_env_new_xlease(const struct drmu_log_env_s * const log); + +// drmu_xdri3 + +drmu_env_t * drmu_env_new_xdri3(const drmu_log_env_t * const log); + +// drmu_waylease + +drmu_env_t * drmu_env_new_waylease(const struct drmu_log_env_s * const log); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/modules/video_output/drmu/drmu_atomic.c b/modules/video_output/drmu/drmu_atomic.c new file mode 100644 index 0000000000..79d772dedc --- /dev/null +++ b/modules/video_output/drmu/drmu_atomic.c @@ -0,0 +1,929 @@ +#include "drmu.h" +#include "drmu_log.h" + +#include +#include +#include +#include +#include + +#include +#include + +// Atomic property chain structures - no external visibility +typedef struct aprop_prop_s { + uint32_t id; + uint64_t value; + void * v; + const drmu_atomic_prop_fns_t * fns; +} aprop_prop_t; + +typedef struct aprop_obj_s { + uint32_t id; + unsigned int n; + unsigned int size; + bool unsorted; + aprop_prop_t * props; +} aprop_obj_t; + +typedef struct aprop_hdr_s { + unsigned int n; + unsigned int size; + bool unsorted; + aprop_obj_t * objs; +} aprop_hdr_t; + +typedef struct atomic_cb_s { + struct atomic_cb_s * next; + void * v; + drmu_atomic_commit_fn * cb; +} atomic_cb_t; + +typedef struct drmu_atomic_s { + atomic_int ref_count; // 0 == 1 ref for ease of init + + struct drmu_env_s * du; + + aprop_hdr_t props; + + atomic_cb_t * commit_cb_q; + atomic_cb_t ** commit_cb_last_ptr; +} drmu_atomic_t; + +static inline unsigned int +max_uint(const unsigned int a, const unsigned int b) +{ + return a < b ? b : a; +} + +static atomic_cb_t * +atomic_cb_new(drmu_atomic_commit_fn * cb, void * v) +{ + atomic_cb_t * acb = malloc(sizeof(*acb)); + if (acb == NULL) + return NULL; + + *acb = (atomic_cb_t){ + .next = NULL, + .cb = cb, + .v = v + }; + return acb; +} + +static void +aprop_prop_unref(aprop_prop_t * const pp) +{ + pp->fns->unref(pp->v); +} + +static void +aprop_prop_ref(aprop_prop_t * const pp) +{ + pp->fns->ref(pp->v); +} + +static void +aprop_obj_uninit(aprop_obj_t * const po) +{ + unsigned int i; + for (i = 0; i != po->n; ++i) + aprop_prop_unref(po->props + i); + free(po->props); + memset(po, 0, sizeof(*po)); +} + +static int +aprop_obj_copy(aprop_obj_t * const po_c, const aprop_obj_t * const po_a) +{ + unsigned int i; + aprop_prop_t * props; + + aprop_obj_uninit(po_c); + if (po_a->n == 0) + return 0; + + if ((props = calloc(po_a->size, sizeof(*props))) == NULL) + return -ENOMEM; + memcpy(props, po_a->props, po_a->n * sizeof(*po_a->props)); + + *po_c = *po_a; + po_c->props = props; + + for (i = 0; i != po_a->n; ++i) + aprop_prop_ref(props + i); + return 0; +} + +static void +aprop_obj_move(aprop_obj_t * const po_c, aprop_obj_t * const po_a) +{ + *po_c = *po_a; + memset(po_a, 0, sizeof(*po_a)); +} + +static int +aprop_prop_qsort_cb(const void * va, const void * vb) +{ + const aprop_prop_t * const a = va; + const aprop_prop_t * const b = vb; + return a->id == b->id ? 0 : a->id < b->id ? -1 : 1; +} + +static void +aprop_obj_props_sort(aprop_obj_t * const po) +{ + if (!po->unsorted) + return; + qsort(po->props, po->n, sizeof(po->props[0]), aprop_prop_qsort_cb); + po->unsorted = false; +} + +// Merge b into a and put the result in c. a & b are uninit on exit +// Could (easily) merge into a but its more convienient for the caller to create new +static int +aprop_obj_merge(aprop_obj_t * const po_c, aprop_obj_t * const po_a, aprop_obj_t * const po_b) +{ + unsigned int i, j, k; + unsigned int c_size; + aprop_prop_t * c; + aprop_prop_t * const a = po_a->props; + aprop_prop_t * const b = po_b->props; + + // As we should have no identical els we don't care that qsort is unstable + aprop_obj_props_sort(po_a); + aprop_obj_props_sort(po_b); + + // Pick a size + c_size = max_uint(po_a->size, po_b->size); + if (c_size < po_a->n + po_b->n) + c_size *= 2; + if ((c = calloc(c_size, sizeof(*c))) == NULL) + return -ENOMEM; + + for (i = 0, j = 0, k = 0; i < po_a->n && j < po_b->n; ++k) { + if (a[i].id < b[j].id) + c[k] = a[i++]; + else if (a[i].id > b[j].id) + c[k] = b[j++]; + else { + c[k] = b[j++]; + aprop_prop_unref(a + i++); + } + } + for (; i < po_a->n; ++i, ++k) + c[k] = a[i]; + for (; j < po_b->n; ++j, ++k) + c[k] = b[j]; + + *po_c = (aprop_obj_t){ + .id = po_a->id, + .n = k, + .size = c_size, + .unsorted = false, + .props = c + }; + + // We have avoided excess ref / unref by simple copy so just free the props array + free(a); + free(b); + + memset(po_a, 0, sizeof(*po_a)); + memset(po_b, 0, sizeof(*po_b)); + + return 0; +} + +// Remove any props in a that are also in b +// b must be sorted +// Returns count of props remaining in a +static unsigned int +aprop_obj_sub(aprop_obj_t * const po_a, const aprop_obj_t * const po_b) +{ + unsigned int i = 0, j = 0, k; + aprop_prop_t * const a = po_a->props; + const aprop_prop_t * const b = po_b->props; + + if (po_a->n == 0 || po_b->n == 0) + return po_a->n; + + // As we should have no identical els we don't care that qsort is unstable + aprop_obj_props_sort(po_a); + assert(!po_b->unsorted); + + // Skip initial non-matches, returning if no match found + while (a[i].id != b[j].id) { + if (a[i].id < b[j].id) { + if (++i == po_a->n) + return po_a->n; + } + else { + if (++j == po_b->n) + return po_a->n; + } + } + // We have a match - next loop will do the unref + k = i; + + do { + if (a[i].id < b[j].id) + a[k++] = a[i++]; + else { + if (a[i].id == b[j].id) + aprop_prop_unref(a + i++); + j++; + } + } while (i != po_a->n && j != po_b->n); + + for (; i < po_a->n; ++i, ++k) + a[k] = a[i]; + po_a->n = k; + + return po_a->n; +} + + +static aprop_prop_t * +aprop_obj_prop_get(aprop_obj_t * const po, const uint32_t id) +{ + unsigned int i; + aprop_prop_t * pp = po->props; + + static const drmu_atomic_prop_fns_t null_fns = { + .ref = drmu_prop_fn_null_ref, + .unref = drmu_prop_fn_null_unref, + .commit = drmu_prop_fn_null_commit + }; + + for (i = 0; i != po->n; ++i, ++pp) { + if (pp->id == id) + return pp; + } + + if (po->n >= po->size) { + size_t newsize = po->size < 16 ? 16 : po->size * 2; + if ((pp = realloc(po->props, newsize * sizeof(*pp))) == NULL) + return NULL; + memset(pp + po->size, 0, (newsize - po->size) * sizeof(*pp)); + + po->props = pp; + po->size = newsize; + pp += po->n; + } + if (!po->unsorted && po->n != 0 && pp[-1].id > id) + po->unsorted = true; + ++po->n; + + pp->id = id; + pp->fns = &null_fns; + return pp; +} + +static void +aprop_obj_atomic_fill(const aprop_obj_t * const po, uint32_t * prop_ids, uint64_t * prop_values) +{ + unsigned int i; + for (i = 0; i != po->n; ++i) { + *prop_ids++ = po->props[i].id; + *prop_values++ = po->props[i].value; + } +} + +static void +aprop_obj_dump(drmu_env_t * const du, const aprop_obj_t * const po) +{ + unsigned int i; + drmu_info(du, "Obj: %02x: size %d n %d", po->id, po->size, po->n); + for (i = 0; i != po->n; ++i) { + struct drm_mode_get_property pattr = {.prop_id = po->props[i].id}; + drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &pattr); + + drmu_info(du, "Obj %02x: Prop %02x (%s) Value %"PRIx64" v %p", po->id, po->props[i].id, pattr.name, po->props[i].value, po->props[i].v); + } +} + +static void +aprop_hdr_dump(drmu_env_t * const du, const aprop_hdr_t * const ph) +{ + unsigned int i; + drmu_info(du, "Header: size %d n %d", ph->size, ph->n); + for (i = 0; i != ph->n; ++i) + aprop_obj_dump(du, ph->objs + i); +} + +static aprop_obj_t * +aprop_hdr_obj_get(aprop_hdr_t * const ph, const uint32_t id) +{ + unsigned int i; + aprop_obj_t * po = ph->objs; + + for (i = 0; i != ph->n; ++i, ++po) { + if (po->id == id) + return po; + } + + if (ph->n >= ph->size) { + size_t newsize = ph->size < 16 ? 16 : ph->size * 2; + if ((po = realloc(ph->objs, newsize * sizeof(*po))) == NULL) + return NULL; + memset(po + ph->size, 0, (newsize - ph->size) * sizeof(*po)); + + ph->objs = po; + ph->size = newsize; + po += ph->n; + } + if (!ph->unsorted && ph->n != 0 && po[-1].id > id) + ph->unsorted = true; + ++ph->n; + + po->id = id; + return po; +} + +static void +aprop_hdr_uninit(aprop_hdr_t * const ph) +{ + unsigned int i; + for (i = 0; i != ph->n; ++i) + aprop_obj_uninit(ph->objs + i); + free(ph->objs); + memset(ph, 0, sizeof(*ph)); +} + +static int +aprop_hdr_copy(aprop_hdr_t * const ph_c, const aprop_hdr_t * const ph_a) +{ + unsigned int i; + + aprop_hdr_uninit(ph_c); + + if (ph_a->n == 0) + return 0; + + if ((ph_c->objs = calloc(ph_a->size, sizeof(*ph_c->objs))) == NULL) + return -ENOMEM; + + ph_c->n = ph_a->n; + ph_c->size = ph_a->size; + ph_c->unsorted = ph_a->unsorted; + + for (i = 0; i != ph_a->n; ++i) + aprop_obj_copy(ph_c->objs + i, ph_a->objs + i); + return 0; +} + +// Move b to a. a must be empty +static int +aprop_hdr_move(aprop_hdr_t * const ph_a, aprop_hdr_t * const ph_b) +{ + *ph_a = *ph_b; + *ph_b = (aprop_hdr_t){0}; + return 0; +} + +static int +aprop_obj_qsort_cb(const void * va, const void * vb) +{ + const aprop_obj_t * const a = va; + const aprop_obj_t * const b = vb; + return a->id == b->id ? 0 : a->id < b->id ? -1 : 1; +} + +// As we should have no identical els we don't care that qsort is unstable +// Doesn't sort props +static void +aprop_hdr_sort(aprop_hdr_t * const ph) +{ + if (!ph->unsorted) + return; + qsort(ph->objs, ph->n, sizeof(ph->objs[0]), aprop_obj_qsort_cb); + ph->unsorted = false; +} + +// Merge b into a. b will be uninited +static int +aprop_hdr_merge(aprop_hdr_t * const ph_a, aprop_hdr_t * const ph_b) +{ + unsigned int i, j, k; + unsigned int c_size; + aprop_obj_t * c; + aprop_obj_t * const a = ph_a->objs; + aprop_obj_t * const b = ph_b->objs; + + if (ph_b->n == 0) + return 0; + if (ph_a->n == 0) + return aprop_hdr_move(ph_a, ph_b); + + aprop_hdr_sort(ph_a); + aprop_hdr_sort(ph_b); + + // Pick a size + c_size = max_uint(ph_a->size, ph_b->size); + if (c_size < ph_a->n + ph_b->n) + c_size *= 2; + if ((c = calloc(c_size, sizeof(*c))) == NULL) + return -ENOMEM; + + for (i = 0, j = 0, k = 0; i < ph_a->n && j < ph_b->n; ++k) { + if (a[i].id < b[j].id) + aprop_obj_move(c + k, a + i++); + else if (a[i].id > b[j].id) + aprop_obj_move(c + k, b + j++); + else + aprop_obj_merge(c + k, a + i++, b + j++); + } + for (; i < ph_a->n; ++i, ++k) + aprop_obj_move(c + k, a + i); + for (; j < ph_b->n; ++j, ++k) + aprop_obj_move(c + k, b + j); + + aprop_hdr_uninit(ph_a); + aprop_hdr_uninit(ph_b); + + ph_a->n = k; + ph_a->size = c_size; + ph_a->objs = c; + // Merge will maintain sort so leave unsorted false + + return 0; +} + +// Remove any props in a that are also in b +// b must be sorted +static void +aprop_hdr_sub(aprop_hdr_t * const ph_a, const aprop_hdr_t * const ph_b) +{ + unsigned int i = 0, j = 0, k; + aprop_obj_t * const a = ph_a->objs; + const aprop_obj_t * const b = ph_b->objs; + + aprop_hdr_sort(ph_a); + assert(!ph_b->unsorted); + + // Scan whilst we haven't deleted anything + for (;;) { + // If we run out of either array then nothing more needed + if (i == ph_a->n || j == ph_b->n) + return; + + if (a[i].id < b[j].id) + ++i; + else if (a[i].id > b[j].id) + ++j; + else { + k = i; + if (aprop_obj_sub(a + i++, b + j++) == 0) { + aprop_obj_uninit(a + k); + break; + } + } + } + + // Move & scan + while (i < ph_a->n && j < ph_b->n) { + if (a[i].id < b[j].id) + aprop_obj_move(a + k++, a + i++); + else if (a[i].id > b[j].id) + j++; + else { + if (aprop_obj_sub(a + i, b + j) == 0) + aprop_obj_uninit(a + i); + else + aprop_obj_move(a + k++, a + i); + i++; + j++; + } + } + + // Move any remaining entries + for (; i < ph_a->n; ++i, ++k) + aprop_obj_move(a + k, a + i); + ph_a->n = k; + + return; +} + +// Sort header objs & obj props +static void +aprop_hdr_props_sort(aprop_hdr_t * const ph) +{ + aprop_hdr_sort(ph); + for (unsigned int i = 0; i != ph->n; ++i) + aprop_obj_props_sort(ph->objs + i); +} + +static aprop_prop_t * +aprop_hdr_prop_get(aprop_hdr_t * const ph, const uint32_t obj_id, const uint32_t prop_id) +{ + aprop_obj_t * const po = aprop_hdr_obj_get(ph, obj_id); + return po == NULL ? NULL : aprop_obj_prop_get(po, prop_id); +} + +// Total props +static unsigned int +aprop_hdr_props_count(const aprop_hdr_t * const ph) +{ + unsigned int i; + unsigned int n = 0; + + for (i = 0; i != ph->n; ++i) + n += ph->objs[i].n; + return n; +} + +static unsigned int +aprop_hdr_objs_count(const aprop_hdr_t * const ph) +{ + return ph->n; +} + +static void +aprop_hdr_atomic_fill(const aprop_hdr_t * const ph, + uint32_t * obj_ids, + uint32_t * prop_counts, + uint32_t * prop_ids, + uint64_t * prop_values) +{ + unsigned int i; + for (i = 0; i != ph->n; ++i) { + const unsigned int n = ph->objs[i].n; + *obj_ids++ = ph->objs[i].id; + *prop_counts++ = n; + aprop_obj_atomic_fill(ph->objs +i, prop_ids, prop_values); + prop_ids += n; + prop_values += n; + } +} + +void +drmu_prop_fn_null_unref(void * v) +{ + (void)v; +} + +void +drmu_prop_fn_null_ref(void * v) +{ + (void)v; +} + +void +drmu_prop_fn_null_commit(void * v, uint64_t value) +{ + (void)v; + (void)value; +} + +int +drmu_atomic_add_commit_callback(drmu_atomic_t * const da, drmu_atomic_commit_fn * const cb, void * const v) +{ + if (cb) { + atomic_cb_t *acb = atomic_cb_new(cb, v); + if (acb == NULL) + return -ENOMEM; + + *da->commit_cb_last_ptr = acb; + da->commit_cb_last_ptr = &acb->next; + } + + return 0; +} + +void +drmu_atomic_clear_commit_callbacks(drmu_atomic_t * const da) +{ + atomic_cb_t *p = da->commit_cb_q; + + da->commit_cb_q = NULL; + da->commit_cb_last_ptr = &da->commit_cb_q; + + while (p != NULL) { + atomic_cb_t * const next = p->next; + free(p); + p = next; + } +} + +void +drmu_atomic_run_commit_callbacks(const drmu_atomic_t * const da) +{ + if (da == NULL) + return; + + for (const atomic_cb_t *p = da->commit_cb_q; p != NULL; p = p->next) + p->cb(p->v); +} + +int +drmu_atomic_add_prop_generic(drmu_atomic_t * const da, + const uint32_t obj_id, const uint32_t prop_id, const uint64_t value, + const drmu_atomic_prop_fns_t * const fns, void * const v) +{ + aprop_hdr_t * const ph = &da->props; + + if (obj_id == 0 || prop_id == 0) + { + return -EINVAL; + } + else + { + aprop_prop_t *const pp = aprop_hdr_prop_get(ph, obj_id, prop_id); + if (pp == NULL) + return -ENOMEM; + + aprop_prop_unref(pp); + pp->value = value; + if (fns) { + pp->fns = fns; + pp->v = v; + } + aprop_prop_ref(pp); + return 0; + } +} + +int +drmu_atomic_add_prop_value(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, const uint64_t value) +{ + if (drmu_atomic_add_prop_generic(da, obj_id, prop_id, value, NULL, NULL) < 0) + drmu_warn(drmu_atomic_env(da), "%s: Failed to set obj_id=%#x, prop_id=%#x, val=%" PRId64, __func__, + obj_id, prop_id, value); + return 0; +} + +void +drmu_atomic_dump(const drmu_atomic_t * const da) +{ + drmu_info(da->du, "Atomic %p: refs %d", da, atomic_load(&da->ref_count)+1); + aprop_hdr_dump(da->du, &da->props); +} + +drmu_env_t * +drmu_atomic_env(const drmu_atomic_t * const da) +{ + return da == NULL ? NULL : da->du; +} + +static void +drmu_atomic_free(drmu_atomic_t * const da) +{ + drmu_atomic_clear_commit_callbacks(da); + aprop_hdr_uninit(&da->props); + free(da); +} + +void +drmu_atomic_unref(drmu_atomic_t ** const ppda) +{ + drmu_atomic_t * const da = *ppda; + + if (da == NULL) + return; + *ppda = NULL; + + if (atomic_fetch_sub(&da->ref_count, 1) == 0) + drmu_atomic_free(da); +} + +drmu_atomic_t * +drmu_atomic_ref(drmu_atomic_t * const da) +{ + atomic_fetch_add(&da->ref_count, 1); + return da; +} + +drmu_atomic_t * +drmu_atomic_new(drmu_env_t * const du) +{ + drmu_atomic_t * const da = calloc(1, sizeof(*da)); + + if (da == NULL) { + drmu_err(du, "%s: Failed to alloc struct", __func__); + return NULL; + } + da->du = du; + da->commit_cb_last_ptr = &da->commit_cb_q; + + return da; +} + +drmu_atomic_t * +drmu_atomic_copy(drmu_atomic_t * const b) +{ + drmu_atomic_t * a; + + if (b == NULL || (a = drmu_atomic_new(b->du)) == NULL) + return NULL; + + if (aprop_hdr_copy(&a->props, &b->props) != 0) + goto fail; + for (atomic_cb_t * p = b->commit_cb_q; p != NULL; p = p->next) + if (drmu_atomic_add_commit_callback(a, p->cb, p->v) != 0) + goto fail; + return a; + +fail: + drmu_atomic_free(a); + return NULL; +} + +drmu_atomic_t * +drmu_atomic_move(drmu_atomic_t ** const ppb) +{ + drmu_atomic_t * a; + drmu_atomic_t * b = *ppb; + *ppb = NULL; + + if (b == NULL || atomic_load(&b->ref_count) == 0) + return b; + + a = drmu_atomic_copy(b); + drmu_atomic_unref(&b); + return a; +} + +// Merge b into a. b is unrefed (inc on error) +// Commit cbs are added +int +drmu_atomic_merge(drmu_atomic_t * const a, drmu_atomic_t ** const ppb) +{ + drmu_atomic_t * b; + int rv = -EINVAL; + + if (*ppb == NULL) + return 0; + + if (a == NULL) { + drmu_atomic_unref(ppb); + return -EINVAL; + } + + if ((b = drmu_atomic_move(ppb)) == NULL) + return -ENOMEM; + + if (b->commit_cb_q != NULL) { + *a->commit_cb_last_ptr = b->commit_cb_q; + a->commit_cb_last_ptr = b->commit_cb_last_ptr; + b->commit_cb_q = NULL; + } + + rv = aprop_hdr_merge(&a->props, &b->props); + drmu_atomic_unref(&b); + + if (rv != 0) { + drmu_err(a->du, "%s: Merge Failed", __func__); + return rv; + } + + return 0; +} + +void +drmu_atomic_sub(drmu_atomic_t * const a, drmu_atomic_t * const b) +{ + aprop_hdr_props_sort(&b->props); + aprop_hdr_sub(&a->props, &b->props); +} + +static void +atomic_props_crop(struct drm_mode_atomic * const f, const unsigned int n, uint32_t ** const undo_p, uint32_t * const undo_v) +{ + unsigned int i; + unsigned int t = 0; + uint32_t * const c = (uint32_t *)(uintptr_t)f->count_props_ptr; + + for (i = 0; i != f->count_objs; ++i) { + t += c[i]; + if (t >= n) { + f->count_objs = i + 1; + *undo_p = c + i; + *undo_v = c[i]; + c[i] -= t - n; + break; + } + } +} + +static void +atomic_props_del(struct drm_mode_atomic * const f, const unsigned int n, const unsigned int cp, + uint32_t * const objid, uint32_t * const propid, uint64_t * const val) +{ + unsigned int i; + unsigned int t = 0; + uint32_t * const c = (uint32_t *)(uintptr_t)f->count_props_ptr; + uint32_t * const o = (uint32_t *)(uintptr_t)f->objs_ptr; + uint32_t * const p = (uint32_t *)(uintptr_t)f->props_ptr; + uint64_t * const v = (uint64_t *)(uintptr_t)f->prop_values_ptr; + + for (i = 0; i != f->count_objs; ++i) { + t += c[i]; + if (t > n) { + // Copy out what we are going to delete + *objid = o[i]; + *propid = p[n]; + *val = v[n]; + + memmove(p + n, p + n + 1, (cp - n - 1) * sizeof(*p)); + memmove(v + n, v + n + 1, (cp - n - 1) * sizeof(*v)); + + if (--c[i] == 0) { + memmove(c + i, c + i + 1, (f->count_objs - i - 1) * sizeof(*c)); + memmove(o + i, o + i + 1, (f->count_objs - i - 1) * sizeof(*o)); + --f->count_objs; + } + break; + } + } +} + +// Returns count of initial good els (i.e. n of 1st bad) +static unsigned int +commit_find_good(drmu_env_t * const du, const struct drm_mode_atomic * const atomic, const unsigned int n_props) +{ + unsigned int a = 0; // N known good + unsigned int b = n_props + 1; // N maybe good + 1 + + while (a + 1 < b) { + struct drm_mode_atomic at = *atomic; + unsigned int n = (a + b) / 2; + int rv; + uint32_t * undo_p = NULL; + uint32_t undo_v = 0; + + at.flags = DRM_MODE_ATOMIC_TEST_ONLY | (DRM_MODE_ATOMIC_ALLOW_MODESET & atomic->flags); + atomic_props_crop(&at, n, &undo_p, &undo_v); + + if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_ATOMIC, &at)) == 0) { + a = n; + } + else { + b = n; + } + + *undo_p = undo_v; // Should always be set + } + + return a; +} + +// da_fail does not keep refs to its values - for info only +int +drmu_atomic_commit_test(const drmu_atomic_t * const da, uint32_t flags, drmu_atomic_t * const da_fail) +{ + drmu_env_t * const du = da->du; + const unsigned int n_objs = aprop_hdr_objs_count(&da->props); + unsigned int n_props = aprop_hdr_props_count(&da->props); + int rv = 0; + + if (n_props != 0) { + uint32_t obj_ids[n_objs]; + uint32_t prop_counts[n_objs]; + uint32_t prop_ids[n_props]; + uint64_t prop_values[n_props]; + struct drm_mode_atomic atomic = { + .flags = flags, + .count_objs = n_objs, + .objs_ptr = (uintptr_t)obj_ids, + .count_props_ptr = (uintptr_t)prop_counts, + .props_ptr = (uintptr_t)prop_ids, + .prop_values_ptr = (uintptr_t)prop_values, + .user_data = (uintptr_t)da + }; + + aprop_hdr_atomic_fill(&da->props, obj_ids, prop_counts, prop_ids, prop_values); + + rv = drmu_ioctl(du, DRM_IOCTL_MODE_ATOMIC, &atomic); + + drmu_atomic_run_commit_callbacks(da); + + if (rv == 0 || !da_fail) + return rv; + + for (;;) { + unsigned int a = commit_find_good(du, &atomic, n_props); + uint32_t objid = 0; + uint32_t propid = 0; + uint64_t val = 0; + + if (a >= n_props) + break; + + atomic_props_del(&atomic, a, n_props, &objid, &propid, &val); + --n_props; + + drmu_atomic_add_prop_value(da_fail, objid, propid, val); + } + } + + return rv; +} + + +int +drmu_atomic_commit(const drmu_atomic_t * const da, uint32_t flags) +{ + return drmu_atomic_commit_test(da, flags, NULL); +} + + diff --git a/modules/video_output/drmu/drmu_chroma.h b/modules/video_output/drmu/drmu_chroma.h new file mode 100644 index 0000000000..3e3f7e9735 --- /dev/null +++ b/modules/video_output/drmu/drmu_chroma.h @@ -0,0 +1,47 @@ +#ifndef _DRMU_DRMU_CHROMA_H +#define _DRMU_DRMU_CHROMA_H + +#include +#include + +#include "drmu_chroma.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct drmu_chroma_siting_s { + int32_t x, y; +} drmu_chroma_siting_t; + +// Init constants - C winges if the struct is specified in a const init (which seems like a silly error) +#define drmu_chroma_siting_float_i(_x, _y) {.x = (int32_t)((double)(_x) * 65536 + .5), .y = (int32_t)((double)(_y) * 65536 + .5)} +#define DRMU_CHROMA_SITING_BOTTOM_I drmu_chroma_siting_float_i(0.5, 1.0) +#define DRMU_CHROMA_SITING_BOTTOM_LEFT_I drmu_chroma_siting_float_i(0.0, 1.0) +#define DRMU_CHROMA_SITING_CENTER_I drmu_chroma_siting_float_i(0.5, 0.5) +#define DRMU_CHROMA_SITING_LEFT_I drmu_chroma_siting_float_i(0.0, 0.5) +#define DRMU_CHROMA_SITING_TOP_I drmu_chroma_siting_float_i(0.5, 0.0) +#define DRMU_CHROMA_SITING_TOP_LEFT_I drmu_chroma_siting_float_i(0.0, 0.0) +#define DRMU_CHROMA_SITING_UNSPECIFIED_I {INT32_MIN, INT32_MIN} +// Inline constants +#define drmu_chroma_siting_float(_x, _y) (drmu_chroma_siting_t)drmu_chroma_siting_float_i(_x, _y) +#define DRMU_CHROMA_SITING_BOTTOM drmu_chroma_siting_float(0.5, 1.0) +#define DRMU_CHROMA_SITING_BOTTOM_LEFT drmu_chroma_siting_float(0.0, 1.0) +#define DRMU_CHROMA_SITING_CENTER drmu_chroma_siting_float(0.5, 0.5) +#define DRMU_CHROMA_SITING_LEFT drmu_chroma_siting_float(0.0, 0.5) +#define DRMU_CHROMA_SITING_TOP drmu_chroma_siting_float(0.5, 0.0) +#define DRMU_CHROMA_SITING_TOP_LEFT drmu_chroma_siting_float(0.0, 0.0) +#define DRMU_CHROMA_SITING_UNSPECIFIED (drmu_chroma_siting_t){INT32_MIN, INT32_MIN} + +static inline bool +drmu_chroma_siting_eq(const drmu_chroma_siting_t a, const drmu_chroma_siting_t b) +{ + return a.x == b.x && a.y == b.y; +} + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/modules/video_output/drmu/drmu_dmabuf.c b/modules/video_output/drmu/drmu_dmabuf.c new file mode 100644 index 0000000000..240f01c472 --- /dev/null +++ b/modules/video_output/drmu/drmu_dmabuf.c @@ -0,0 +1,206 @@ +#include "drmu_dmabuf.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "drmu.h" +#include "drmu_fmts.h" +#include "drmu_log.h" +#include "drmu_pool.h" + +struct drmu_dmabuf_env_s { + atomic_int ref_count; + drmu_env_t * du; + int fd; + size_t page_size; +}; + +drmu_fb_t * +drmu_fb_new_dmabuf_mod(drmu_dmabuf_env_t * const dde, const uint32_t w, const uint32_t h, const uint32_t format, const uint64_t mod) +{ + const drmu_fmt_info_t * const fmti = drmu_fmt_info_find_fmt(format); + drmu_env_t * const du = dde->du; + unsigned int i; + unsigned int layers; + unsigned int bypp; + uint32_t w2 = (w + 15) & ~15; + uint32_t h2 = (h + 15) & ~15; + drmu_fb_t * fb; + uint32_t offset = 0; + + if (fmti == NULL) { + drmu_err(du, "%s: Format not found: %s", __func__, drmu_log_fourcc(format)); + return NULL; + } + + if ((fb = drmu_fb_int_alloc(du)) == NULL) + return NULL; + + drmu_fb_int_fmt_size_set(fb, format, w2, h2, drmu_rect_wh(w, h)); + + layers = drmu_fmt_info_plane_count(fmti); + bypp = (drmu_fmt_info_pixel_bits(fmti) + 7) / 8; + + for (offset = 0, i = 0; i != layers; ++i) { + const uint32_t stride = w2 * bypp / drmu_fmt_info_wdiv(fmti, i); + const uint32_t size = stride * h2 / drmu_fmt_info_hdiv(fmti, i); + offset += size; + } + + + { + struct dma_heap_allocation_data data = { + .len = (offset + dde->page_size - 1) & ~(dde->page_size - 1), + .fd = 0, + .fd_flags = O_RDWR | O_CLOEXEC, + .heap_flags = 0 + }; + void * map_ptr; + drmu_bo_t * bo; + + while (ioctl(dde->fd, DMA_HEAP_IOCTL_ALLOC, &data)) { + const int err = errno; + if (err == EINTR) + continue; + drmu_err(dde->du, "Failed to alloc %" PRIu64 " from dma-heap(fd=%d): %d (%s)", + (uint64_t)data.len, dde->fd, err, strerror(err)); + goto fail; + } + + drmu_fb_int_fd_set(fb, data.fd); + + if ((bo = drmu_bo_new_fd(du, data.fd)) == NULL) { + drmu_err(du, "%s: Failed to allocate BO", __func__); + goto fail; + } + + drmu_fb_int_bo_set(fb, 0, bo); + + if ((map_ptr = mmap(NULL, (size_t)data.len, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, + data.fd, 0)) == MAP_FAILED) { + drmu_err(du, "%s: mmap failed (size=%zd, fd=%d): %s", __func__, + (size_t)data.len, data.fd, strerror(errno)); + goto fail; + } + + drmu_fb_int_mmap_set(fb, map_ptr, (size_t)data.len, w2 * bypp); + } + + for (offset = 0, i = 0; i != layers; ++i) { + const uint32_t stride = w2 * bypp / drmu_fmt_info_wdiv(fmti, i); + const uint32_t size = stride * h2 / drmu_fmt_info_hdiv(fmti, i); + drmu_fb_int_layer_mod_set(fb, i, 0, stride, offset, mod); + offset += size; + } + + if (drmu_fb_int_make(fb)) + goto fail; + + return fb; + +fail: + drmu_fb_int_free(fb); + return NULL; +} + +drmu_dmabuf_env_t * +drmu_dmabuf_env_ref(drmu_dmabuf_env_t * const dde) +{ + atomic_fetch_add(&dde->ref_count, 1); + return dde; +} + +void drmu_dmabuf_env_unref(drmu_dmabuf_env_t ** const ppdde) +{ + drmu_dmabuf_env_t * const dde = *ppdde; + if (dde == NULL) + return; + *ppdde = NULL; + if (atomic_fetch_sub(&dde->ref_count, 1) != 0) + return; + + drmu_env_unref(&dde->du); + if (dde->fd != -1) + close(dde->fd); + free(dde); +} + +drmu_dmabuf_env_t * +drmu_dmabuf_env_new_fd(struct drmu_env_s * const du, const int fd) +{ + if (fd == -1) { + return NULL; + } + else { + drmu_dmabuf_env_t *const dde = calloc(1, sizeof(*dde)); + if (dde == NULL) { + close(fd); + return NULL; + } + dde->du = drmu_env_ref(du); + dde->fd = fd; + dde->page_size = (size_t)sysconf(_SC_PAGE_SIZE); + + return dde; + } +} + +drmu_dmabuf_env_t * +drmu_dmabuf_env_new_video(struct drmu_env_s * const du) +{ + static const char * const names[] = { + "/dev/dma_heap/vidbuf_cached", + "/dev/dma_heap/linux,cma", + "/dev/dma_heap/reserved", + NULL + }; + const char * const * pfname; + + for (pfname = names; pfname != NULL; ++pfname) { + const int fd = open(*pfname, O_RDWR | O_CLOEXEC); + drmu_dmabuf_env_t * const dde = drmu_dmabuf_env_new_fd(du, fd); + if (dde != NULL) + return dde; + } + return NULL; +} + +static drmu_fb_t * +pool_dmabuf_alloc_cb(void * const v, const uint32_t w, const uint32_t h, const uint32_t format, const uint64_t mod) +{ + return drmu_fb_new_dmabuf_mod(v, w, h, format, mod); +} + +static void +pool_dmabuf_on_delete_cb(void * const v) +{ + drmu_dmabuf_env_t * dde = v; + drmu_dmabuf_env_unref(&dde); +} + +drmu_pool_t * +drmu_pool_new_dmabuf(drmu_dmabuf_env_t * dde, unsigned int total_fbs_max) +{ + static const drmu_pool_callback_fns_t fns = { + .alloc_fn = pool_dmabuf_alloc_cb, + .on_delete_fn = pool_dmabuf_on_delete_cb, + .try_reuse_fn = drmu_fb_try_reuse, + }; + if (dde == NULL) + return NULL; + return drmu_pool_new_alloc(dde->du, total_fbs_max, + &fns, drmu_dmabuf_env_ref(dde)); +} + diff --git a/modules/video_output/drmu/drmu_dmabuf.h b/modules/video_output/drmu/drmu_dmabuf.h new file mode 100644 index 0000000000..880f4e355d --- /dev/null +++ b/modules/video_output/drmu/drmu_dmabuf.h @@ -0,0 +1,47 @@ +#ifndef _DRMU_DRMU_DMABUF_H +#define _DRMU_DRMU_DMABUF_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct drmu_env_s; +struct drmu_fb_s; +struct drmu_pool_s; + +struct drmu_dmabuf_env_s; +typedef struct drmu_dmabuf_env_s drmu_dmabuf_env_t; + +struct drmu_fb_s * drmu_fb_new_dmabuf_mod(drmu_dmabuf_env_t * const dde, const uint32_t w, const uint32_t h, const uint32_t format, const uint64_t mod); + +drmu_dmabuf_env_t * drmu_dmabuf_env_ref(drmu_dmabuf_env_t * const dde); +void drmu_dmabuf_env_unref(drmu_dmabuf_env_t ** const ppdde); +// Takes control of fd and will close it when the env is deleted +// or on creation error so dup if it is needed to survive the pool +drmu_dmabuf_env_t * drmu_dmabuf_env_new_fd(struct drmu_env_s * const du, int fd); + +drmu_dmabuf_env_t * drmu_dmabuf_env_new_video(struct drmu_env_s * const du); + +// Construct an fb pool from dmabufs +// A reference to dde is held by the pool so it is safe to unref immediately +// after this call +// dde = NULL returns NULL safely +struct drmu_pool_s * drmu_pool_new_dmabuf(drmu_dmabuf_env_t * dde, unsigned int total_fbs_max); + +// Convienience fn. +static inline struct drmu_pool_s * +drmu_pool_new_dmabuf_video(struct drmu_env_s * const du, unsigned int total_fbs_max) +{ + drmu_dmabuf_env_t * dde = drmu_dmabuf_env_new_video(du); + struct drmu_pool_s * const pool = drmu_pool_new_dmabuf(dde, total_fbs_max); + drmu_dmabuf_env_unref(&dde); + return pool; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/video_output/drmu/drmu_fmts.c b/modules/video_output/drmu/drmu_fmts.c new file mode 100644 index 0000000000..41abcf5bbc --- /dev/null +++ b/modules/video_output/drmu/drmu_fmts.c @@ -0,0 +1,253 @@ +#include "drmu_fmts.h" + +#include + +#include + +#ifndef HAS_SORTED_FMTS +#define HAS_SORTED_FMTS 0 +#endif +#ifndef BUILD_MK_SORTED_FMTS_H +#define BUILD_MK_SORTED_FMTS_H 0 +#endif + +#ifndef DRM_FORMAT_P030 +#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') +#endif + +// Format properties + +typedef struct drmu_fmt_info_s { + uint32_t fourcc; + uint8_t bpp; // For dumb BO alloc + uint8_t bit_depth; // For display + uint8_t plane_count; + struct { + uint8_t wdiv; + uint8_t hdiv; + } planes[4]; + drmu_chroma_siting_t chroma_siting; // Default for this format (YUV420 = (0.0, 0.5), otherwise (0, 0) +} drmu_fmt_info_t; + +#if BUILD_MK_SORTED_FMTS_H || !HAS_SORTED_FMTS + +#define P_ONE {{.wdiv = 1, .hdiv = 1}} +#define P_YC420 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 2}} +#define P_YC422 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}} +#define P_YC444 {{.wdiv = 2, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}} // Assumes doubled .bpp +#define P_YUV420 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 2, .hdiv = 2}, {.wdiv = 2, .hdiv = 2}} +#define P_YUV422 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 2, .hdiv = 1}, {.wdiv = 2, .hdiv = 1}} +#define P_YUV444 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}} + +static +// Not const when creating the sorted version 'cos we sort in place +#if !BUILD_MK_SORTED_FMTS_H +const +#endif +drmu_fmt_info_t format_info[] = { + { .fourcc = DRM_FORMAT_XRGB1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_XBGR1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_RGBX5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_BGRX5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_ARGB1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_ABGR1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_RGBA5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_BGRA5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_BGR565, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_RGB565, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE}, + + { .fourcc = DRM_FORMAT_RGB888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_BGR888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + + { .fourcc = DRM_FORMAT_XRGB8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_XBGR8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_RGBX8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_BGRX8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_ARGB8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_ABGR8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_RGBA8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_BGRA8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + + { .fourcc = DRM_FORMAT_XRGB2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_XBGR2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_RGBX1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_BGRX1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_ARGB2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_ABGR2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_RGBA1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_BGRA1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + + { .fourcc = DRM_FORMAT_AYUV, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_XYUV8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_VUY888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_XVYU2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE}, + + { .fourcc = DRM_FORMAT_XVYU12_16161616, .bpp = 64, .bit_depth = 12, .plane_count = 1, .planes = P_ONE}, + { .fourcc = DRM_FORMAT_XVYU16161616, .bpp = 64, .bit_depth = 16, .plane_count = 1, .planes = P_ONE}, + + { .fourcc = DRM_FORMAT_YUYV, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_YVYU, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_VYUY, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_UYVY, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + + { .fourcc = DRM_FORMAT_NV12, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC420, + .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, + { .fourcc = DRM_FORMAT_NV21, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC420, + .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, + { .fourcc = DRM_FORMAT_P010, .bpp = 16, .bit_depth = 10, .plane_count = 2, .planes = P_YC420, + .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, + { .fourcc = DRM_FORMAT_NV16, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC422, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_NV61, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC422, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_NV24, .bpp = 16, .bit_depth = 8, .plane_count = 2, .planes = P_YC444, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_NV42, .bpp = 16, .bit_depth = 8, .plane_count = 2, .planes = P_YC444, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + + { .fourcc = DRM_FORMAT_YUV420, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV420, + .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, + { .fourcc = DRM_FORMAT_YVU420, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV420, + .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, + { .fourcc = DRM_FORMAT_YUV422, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV422, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_YUV422, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV422, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_YUV444, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV444, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + { .fourcc = DRM_FORMAT_YUV444, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV444, + .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I }, + + // 3 pel in 32 bits. So code as 32bpp with wdiv 3. + { .fourcc = DRM_FORMAT_P030, .bpp = 32, .bit_depth = 10, .plane_count = 2, + .planes = {{.wdiv = 3, .hdiv = 1}, {.wdiv = 3, .hdiv = 2}}, + .chroma_siting = DRMU_CHROMA_SITING_LEFT_I }, + + { .fourcc = 0 } +}; +#endif + +#if BUILD_MK_SORTED_FMTS_H +// --------------------------------------------------------------------------- +// +// Sort & emit format table (not part of the lib) + +#include +#include +#include + +static const unsigned int format_count = sizeof(format_info)/sizeof(format_info[0]) - 1; // Ignore null term in count + +static int sort_fn(const void * va, const void * vb) +{ + const drmu_fmt_info_t * a = va; + const drmu_fmt_info_t * b = vb; + return a->fourcc < b->fourcc ? -1 : a->fourcc == b->fourcc ? 0 : 1; +} + +int +main(int argc, char * argv[]) +{ + FILE * f; + unsigned int i; + + if (argc != 2) { + fprintf(stderr, "Needs output file only\n"); + return 1; + } + if ((f = fopen(argv[1], "wt")) == NULL) { + fprintf(stderr, "Failed to open'%s'\n", argv[1]); + return 1; + } + qsort(format_info, format_count, sizeof(format_info[0]), sort_fn); + + fprintf(f, "static const drmu_fmt_info_t format_info[] = {\n"); + for (i = 0; i != format_count; ++i) { + const drmu_fmt_info_t * x = format_info + i; + unsigned int j; + fprintf(f, "{%#"PRIx32",%d,%d,%d,{", x->fourcc, x->bpp, x->bit_depth, x->plane_count); + for (j = 0; j != sizeof(x->planes)/sizeof(x->planes[0]); ++j) { + fprintf(f, "{%d,%d},", x->planes[j].wdiv, x->planes[j].hdiv); + } + fprintf(f, "},"); + fprintf(f, "{%d,%d},", x->chroma_siting.x, x->chroma_siting.y); + fprintf(f, "},\n"); + } + fprintf(f, "{0}\n};\n"); + fprintf(f, "static const unsigned int format_count = %d;\n", format_count); + + fclose(f); + return 0; +} + +#else +// --------------------------------------------------------------------------- +// +// Include sorted format table +#if HAS_SORTED_FMTS +#include "sorted_fmts.h" +#endif + +const drmu_fmt_info_t * +drmu_fmt_info_find_fmt(const uint32_t fourcc) +{ + if (!fourcc) + return NULL; +#if HAS_SORTED_FMTS + unsigned int lo = 0; + unsigned int hi = format_count; + + while (lo < hi) { + unsigned int x = (hi + lo) / 2; + if (format_info[x].fourcc == fourcc) + return &format_info[x]; + if (format_info[x].fourcc < fourcc) + lo = x + 1; + else + hi = x; + } +#else + for (const drmu_fmt_info_t * p = format_info; p->fourcc; ++p) { + if (p->fourcc == fourcc) + return p; + } +#endif + return NULL; +} + +unsigned int +drmu_fmt_info_bit_depth(const drmu_fmt_info_t * const fmt_info) +{ + return !fmt_info ? 0 : fmt_info->bit_depth; +} +uint32_t drmu_fmt_info_fourcc(const drmu_fmt_info_t * const fmt_info) +{ + return fmt_info->fourcc; +} +unsigned int drmu_fmt_info_pixel_bits(const drmu_fmt_info_t * const fmt_info) +{ + return !fmt_info ? 0 : fmt_info->bpp; +} +unsigned int drmu_fmt_info_plane_count(const drmu_fmt_info_t * const fmt_info) +{ + return !fmt_info ? 0 : fmt_info->plane_count; +} +unsigned int drmu_fmt_info_wdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n) +{ + return fmt_info->planes[plane_n].wdiv; +} +unsigned int drmu_fmt_info_hdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n) +{ + return fmt_info->planes[plane_n].hdiv; +} +drmu_chroma_siting_t drmu_fmt_info_chroma_siting(const drmu_fmt_info_t * const fmt_info) +{ + return !fmt_info ? DRMU_CHROMA_SITING_TOP_LEFT : fmt_info->chroma_siting; +} + +#endif + diff --git a/modules/video_output/drmu/drmu_fmts.h b/modules/video_output/drmu/drmu_fmts.h new file mode 100644 index 0000000000..a2400357e4 --- /dev/null +++ b/modules/video_output/drmu/drmu_fmts.h @@ -0,0 +1,31 @@ +#ifndef _DRMU_DRMU_FMTS_H +#define _DRMU_DRMU_FMTS_H + +#include +#include + +#include "drmu_chroma.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct drmu_fmt_info_s; +typedef struct drmu_fmt_info_s drmu_fmt_info_t; + +const drmu_fmt_info_t * drmu_fmt_info_find_fmt(const uint32_t fourcc); + +unsigned int drmu_fmt_info_bit_depth(const drmu_fmt_info_t * const fmt_info); +uint32_t drmu_fmt_info_fourcc(const drmu_fmt_info_t * const fmt_info); +unsigned int drmu_fmt_info_pixel_bits(const drmu_fmt_info_t * const fmt_info); +unsigned int drmu_fmt_info_plane_count(const drmu_fmt_info_t * const fmt_info); +unsigned int drmu_fmt_info_wdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n); +unsigned int drmu_fmt_info_hdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n); +drmu_chroma_siting_t drmu_fmt_info_chroma_siting(const drmu_fmt_info_t * const fmt_info); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/modules/video_output/drmu/drmu_log.h b/modules/video_output/drmu/drmu_log.h new file mode 100644 index 0000000000..cc6ba5cc91 --- /dev/null +++ b/modules/video_output/drmu/drmu_log.h @@ -0,0 +1,71 @@ +#ifndef _DRMU_DRMU_LOG_H +#define _DRMU_DRMU_LOG_H + +#include + +struct drmu_env_s; + +enum drmu_log_level_e { + DRMU_LOG_LEVEL_NONE = -1, // Max level specifier for nothing (not a real level) + DRMU_LOG_LEVEL_MESSAGE = 0, // (Nearly) always printed info + DRMU_LOG_LEVEL_ERROR, // Error + DRMU_LOG_LEVEL_WARNING, + DRMU_LOG_LEVEL_INFO, // Interesting but not critical info + DRMU_LOG_LEVEL_DEBUG, // Info only useful for debug + DRMU_LOG_LEVEL_ALL, // Max level specifier for everything (not a real level) +}; + +typedef void drmu_log_fn(void * v, enum drmu_log_level_e level, const char * fmt, va_list vl); + +typedef struct drmu_log_env_s { + drmu_log_fn * fn; + void * v; + enum drmu_log_level_e max_level; +} drmu_log_env_t; + +void drmu_log_generic(const struct drmu_log_env_s * const log, const enum drmu_log_level_e level, + const char * const fmt, ...); + +#define drmu_log_macro(_log, _level, _fmt, ...) do {\ + const drmu_log_env_t * const _log2 = (_log);\ + if ((_level) <= _log2->max_level)\ + drmu_log_generic(_log2, (_level), "%s:%u:%s: " _fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);\ +} while (0) + +// Char offset if file, line extracted - func still in format +#define DRMU_LOG_FMT_OFFSET_FUNC 6 +// Char offset if file, line & fn extracted +#define DRMU_LOG_FMT_OFFSET_FMT 10 + +#define drmu_err_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_ERROR, __VA_ARGS__) +#define drmu_warn_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_WARNING, __VA_ARGS__) +#define drmu_info_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_INFO, __VA_ARGS__) +#define drmu_debug_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_DEBUG, __VA_ARGS__) + +#define drmu_err(_du, ...) drmu_err_log(drmu_env_log(_du), __VA_ARGS__) +#define drmu_warn(_du, ...) drmu_warn_log(drmu_env_log(_du), __VA_ARGS__) +#define drmu_info(_du, ...) drmu_info_log(drmu_env_log(_du), __VA_ARGS__) +#define drmu_debug(_du, ...) drmu_debug_log(drmu_env_log(_du), __VA_ARGS__) + +static inline char drmu_log_safechar(int c) +{ + return (c < ' ' || c >=0x7f) ? '?' : c; +} + +static inline const char * drmu_log_fourcc_to_str(char buf[5], uint32_t fcc) +{ + if (fcc == 0) + return "----"; + buf[0] = drmu_log_safechar((fcc >> 0) & 0xff); + buf[1] = drmu_log_safechar((fcc >> 8) & 0xff); + buf[2] = drmu_log_safechar((fcc >> 16) & 0xff); + buf[3] = drmu_log_safechar((fcc >> 24) & 0xff); + buf[4] = 0; + return buf; +} + +#define drmu_log_fourcc(fcc) drmu_log_fourcc_to_str((char[5]){0}, fcc) + + +#endif + diff --git a/modules/video_output/drmu/drmu_math.c b/modules/video_output/drmu/drmu_math.c new file mode 100644 index 0000000000..800da1b9ac --- /dev/null +++ b/modules/video_output/drmu/drmu_math.c @@ -0,0 +1,43 @@ +#include "drmu_math.h" + +#include + +drmu_ufrac_t +drmu_ufrac_reduce(drmu_ufrac_t x) +{ + static const unsigned int primes[] = {2,3,5,7,11,13,17,19,23,29,31,UINT_MAX}; + const unsigned int * p; + + // Deal with specials + if (x.den == 0) { + x.num = 0; + return x; + } + if (x.num == 0) { + x.den = 1; + return x; + } + + // Shortcut the 1:1 common case - also ensures the default loop terminates + if (x.num == x.den) { + x.num = 1; + x.den = 1; + return x; + } + + // As num != den, (num/UINT_MAX == 0 || den/UINT_MAX == 0) must be true + // so loop will terminate + for (p = primes;; ++p) { + const unsigned int n = *p; + for (;;) { + const unsigned int xd = x.den / n; + const unsigned int xn = x.num / n; + if (xn == 0 || xd == 0) + return x; + if (xn * n != x.num || xd * n != x.den) + break; + x.num = xn; + x.den = xd; + } + } +} diff --git a/modules/video_output/drmu/drmu_math.h b/modules/video_output/drmu/drmu_math.h new file mode 100644 index 0000000000..4d1e27dc50 --- /dev/null +++ b/modules/video_output/drmu/drmu_math.h @@ -0,0 +1,127 @@ +#ifndef _DRMU_DRMU_MATH_H +#define _DRMU_DRMU_MATH_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct drmu_rect_s { + int32_t x, y; + uint32_t w, h; +} drmu_rect_t; + +typedef struct drmu_ufrac_s { + unsigned int num; + unsigned int den; +} drmu_ufrac_t; + +drmu_ufrac_t drmu_ufrac_reduce(drmu_ufrac_t x); + +static inline int_fast32_t +drmu_rect_rescale_1s(int_fast32_t x, uint_fast32_t mul, uint_fast32_t div) +{ + const int_fast64_t m = x * (int_fast64_t)mul; + const uint_fast32_t d2 = div/2; + return div == 0 ? (int_fast32_t)m : + m >= 0 ? (int_fast32_t)(((uint_fast64_t)m + d2) / div) : + -(int_fast32_t)(((uint_fast64_t)(-m) + d2) / div); +} + +static inline uint_fast32_t +drmu_rect_rescale_1u(uint_fast32_t x, uint_fast32_t mul, uint_fast32_t div) +{ + const uint_fast64_t m = x * (uint_fast64_t)mul; + return (uint_fast32_t)(div == 0 ? m : (m + div/2) / div); +} + +static inline drmu_rect_t +drmu_rect_rescale(const drmu_rect_t s, const drmu_rect_t mul, const drmu_rect_t div) +{ + return (drmu_rect_t){ + .x = drmu_rect_rescale_1s(s.x - div.x, mul.w, div.w) + mul.x, + .y = drmu_rect_rescale_1s(s.y - div.y, mul.h, div.h) + mul.y, + .w = drmu_rect_rescale_1u(s.w, mul.w, div.w), + .h = drmu_rect_rescale_1u(s.h, mul.h, div.h) + }; +} + +static inline drmu_rect_t +drmu_rect_add_xy(const drmu_rect_t a, const drmu_rect_t b) +{ + return (drmu_rect_t){ + .x = a.x + b.x, + .y = a.y + b.y, + .w = a.w, + .h = a.h + }; +} + +static inline drmu_rect_t +drmu_rect_wh(const unsigned int w, const unsigned int h) +{ + return (drmu_rect_t){ + .w = w, + .h = h + }; +} + +static inline drmu_rect_t +drmu_rect_shl16(const drmu_rect_t a) +{ + return (drmu_rect_t){ + .x = a.x << 16, + .y = a.y << 16, + .w = a.w << 16, + .h = a.h << 16 + }; +} + +static inline drmu_rect_t +drmu_rect_shr16(const drmu_rect_t a) +{ + return (drmu_rect_t){ + .x = a.x >> 16, + .y = a.y >> 16, + .w = a.w >> 16, + .h = a.h >> 16 + }; +} + +static inline drmu_rect_t +drmu_rect_shr_rnd(const drmu_rect_t a, unsigned int n) +{ + if (n == 0) + return a; + --n; + return (drmu_rect_t) { + .x = ((a.x >> n) + 1) >> 1, + .y = ((a.y >> n) + 1) >> 1, + .w = ((a.w >> n) + 1) >> 1, + .h = ((a.h >> n) + 1) >> 1 + }; +} + +static inline drmu_rect_t +drmu_rect_shr16_rnd(const drmu_rect_t a) +{ + return drmu_rect_shr_rnd(a, 16); +} + +static inline drmu_rect_t +drmu_rect_div_xy(const drmu_rect_t a, const unsigned int dx, const unsigned int dy) +{ + return (drmu_rect_t) { + .x = a.x / (int)dx, + .y = a.y / (int)dy, + .w = a.w / dx, + .h = a.h / dy + }; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/video_output/drmu/drmu_output.c b/modules/video_output/drmu/drmu_output.c new file mode 100644 index 0000000000..c6a9eafb78 --- /dev/null +++ b/modules/video_output/drmu/drmu_output.c @@ -0,0 +1,635 @@ +#include "drmu_output.h" + +#include "drmu_fmts.h" +#include "drmu_log.h" + +#include +#include +#include + +#include +#include + +// Update return value with a new one for cases where we don't stop on error +static inline int rvup(int rv1, int rv2) +{ + return rv2 ? rv2 : rv1; +} + +struct drmu_output_s { + atomic_int ref_count; + + drmu_env_t * du; + drmu_crtc_t * dc; + unsigned int conn_n; + unsigned int conn_size; + drmu_conn_t ** dns; + bool has_max_bpc; + bool max_bpc_allow; + bool modeset_allow; + int mode_id; + drmu_mode_simple_params_t mode_params; + + // These are expected to be static consts so no copy / no free + const drmu_fmt_info_t * fmt_info; + drmu_colorspace_t colorspace; + drmu_broadcast_rgb_t broadcast_rgb; + + // HDR metadata + drmu_isset_t hdr_metadata_isset; + struct hdr_output_metadata hdr_metadata; +}; + +drmu_plane_t * +drmu_output_plane_ref_primary(drmu_output_t * const dout) +{ + drmu_plane_t * const dp = drmu_plane_new_find_type(dout->dc, DRMU_PLANE_TYPE_PRIMARY); + + if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0) + return NULL; + + return dp; +} + +drmu_plane_t * +drmu_output_plane_ref_other(drmu_output_t * const dout) +{ + drmu_plane_t *const dp = drmu_plane_new_find_type(dout->dc, DRMU_PLANE_TYPE_CURSOR | DRMU_PLANE_TYPE_OVERLAY); + + if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0) + return NULL; + + return dp; +} + +struct plane_format_s { + unsigned int types; + uint32_t fmt; + uint64_t mod; +}; + +static bool plane_find_format_cb(const drmu_plane_t * dp, void * v) +{ + const struct plane_format_s * const f = v; + return (f->types & drmu_plane_type(dp)) != 0 && + drmu_plane_format_check(dp, f->fmt, f->mod); +} + +drmu_plane_t * +drmu_output_plane_ref_format(drmu_output_t * const dout, const unsigned int types, const uint32_t format, const uint64_t mod) +{ + struct plane_format_s fm = { + .types = (types != 0) ? types : (DRMU_PLANE_TYPE_PRIMARY | DRMU_PLANE_TYPE_CURSOR | DRMU_PLANE_TYPE_OVERLAY), + .fmt = format, + .mod = mod + }; + + drmu_plane_t *const dp = drmu_plane_new_find(dout->dc, plane_find_format_cb, &fm); + + if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0) + return NULL; + + return dp; +} + + +int +drmu_atomic_output_add_props(drmu_atomic_t * const da, drmu_output_t * const dout) +{ + int rv = 0; + unsigned int i; + + if (!dout->modeset_allow) + return 0; + + rv = drmu_atomic_crtc_add_modeinfo(da, dout->dc, drmu_conn_modeinfo(dout->dns[0], dout->mode_id)); + + for (i = 0; i != dout->conn_n; ++i) { + drmu_conn_t * const dn = dout->dns[i]; + + if (dout->fmt_info && dout->max_bpc_allow) + rv = rvup(rv, drmu_atomic_conn_add_hi_bpc(da, dn, (drmu_fmt_info_bit_depth(dout->fmt_info) > 8))); + if (drmu_colorspace_is_set(dout->colorspace)) + rv = rvup(rv, drmu_atomic_conn_add_colorspace(da, dn, dout->colorspace)); + if (drmu_broadcast_rgb_is_set(dout->broadcast_rgb)) + rv = rvup(rv, drmu_atomic_conn_add_broadcast_rgb(da, dn, dout->broadcast_rgb)); + if (dout->hdr_metadata_isset != DRMU_ISSET_UNSET) + rv = rvup(rv, drmu_atomic_conn_add_hdr_metadata(da, dn, + dout->hdr_metadata_isset == DRMU_ISSET_NULL ? NULL : &dout->hdr_metadata)); + } + + return rv; +} + +// Set all the fb info props that might apply to a crtc on the crtc +// (e.g. hdr_metadata, colorspace) but do not set the mode (resolution +// and refresh) +// +// N.B. Only changes those props that are set in the fb. If unset in the fb +// then their value is unchanged. +int +drmu_output_fb_info_set(drmu_output_t * const dout, const drmu_fb_t * const fb) +{ + const drmu_isset_t hdr_isset = drmu_fb_hdr_metadata_isset(fb); + const drmu_fmt_info_t * fmt_info = drmu_fb_format_info_get(fb); + const drmu_colorspace_t colorspace = drmu_fb_colorspace_get(fb); + const drmu_broadcast_rgb_t broadcast_rgb = drmu_color_range_to_broadcast_rgb(drmu_fb_color_range_get(fb)); + + if (fmt_info) + dout->fmt_info = fmt_info; + if (drmu_colorspace_is_set(colorspace)) + dout->colorspace = colorspace; + if (drmu_broadcast_rgb_is_set(broadcast_rgb)) + dout->broadcast_rgb = broadcast_rgb; + + if (hdr_isset != DRMU_ISSET_UNSET) { + dout->hdr_metadata_isset = hdr_isset; + if (hdr_isset == DRMU_ISSET_SET) + dout->hdr_metadata = *drmu_fb_hdr_metadata_get(fb); + } + + return 0; +} + +void +drmu_output_fb_info_unset(drmu_output_t * const dout) +{ + dout->fmt_info = NULL; + dout->colorspace = DRMU_COLORSPACE_UNSET; + dout->broadcast_rgb = DRMU_BROADCAST_RGB_UNSET; + dout->hdr_metadata_isset = DRMU_ISSET_UNSET; +} + + +int +drmu_output_mode_id_set(drmu_output_t * const dout, const int mode_id) +{ + drmu_info(dout->du, "%s: mode_id=%d", __func__, mode_id); + + if (mode_id != dout->mode_id) { + drmu_mode_simple_params_t sp = drmu_conn_mode_simple_params(dout->dns[0], mode_id); + if (sp.width == 0) + return -EINVAL; + + dout->mode_id = mode_id; + dout->mode_params = sp; + } + return 0; +} + +const drmu_mode_simple_params_t * +drmu_output_mode_simple_params(const drmu_output_t * const dout) +{ + return &dout->mode_params; +} + +static int +score_freq(const drmu_mode_simple_params_t * const mode, const drmu_mode_simple_params_t * const p) +{ + const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0; + const unsigned int r_m = (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0 ? + mode->hz_x_1000 * 2: mode->hz_x_1000; + const unsigned int r_f = (p->flags & DRM_MODE_FLAG_INTERLACE) != 0 ? + p->hz_x_1000 * 2 : p->hz_x_1000; + + // If we haven't been given any hz then pick pref or fastest + // Max out at 300Hz (=300,0000) + if (r_f == 0) + return pref ? 83000000 : 80000000 + (r_m >= 2999999 ? 2999999 : r_m); + // Prefer a good match to 29.97 / 30 but allow the other + else if ((r_m + 10 >= r_f && r_m <= r_f + 10)) + return 100000000; + else if ((r_m + 100 >= r_f && r_m <= r_f + 100)) + return 95000000; + // Double isn't bad + else if ((r_m + 10 >= r_f * 2 && r_m <= r_f * 2 + 10)) + return 90000000; + else if ((r_m + 100 >= r_f * 2 && r_m <= r_f * 2 + 100)) + return 85000000; + return -1; +} + +// Avoid interlace no matter what our source +int +drmu_mode_pick_simple_cb(void * v, const drmu_mode_simple_params_t * mode) +{ + const drmu_mode_simple_params_t * const p = v; + const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0; + int score = -1; + + if (p->width == mode->width && p->height == mode->height && + (mode->flags & DRM_MODE_FLAG_INTERLACE) == 0) + score = score_freq(mode, p); + + if (score > 0 && (p->width != mode->width || p->height != mode->height)) + score -= 30000000; + + if (score <= 0 && pref) + score = 10000000; + + return score; +} + +// Try to match interlace as well as everything else +int +drmu_mode_pick_simple_interlace_cb(void * v, const drmu_mode_simple_params_t * mode) +{ + const drmu_mode_simple_params_t * const p = v; + + const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0; + int score = -1; + + if (p->width == mode->width && p->height == mode->height) + score = score_freq(mode, p); + + if (score > 0 && (p->width != mode->width || p->height != mode->height)) + score -= 30000000; + if (((mode->flags ^ p->flags) & DRM_MODE_FLAG_INTERLACE) != 0) + score -= 20000000; + + if (score <= 0 && pref) + score = 10000000; + + return score; +} + + +int +drmu_output_mode_pick_simple(drmu_output_t * const dout, drmu_mode_score_fn * const score_fn, void * const score_v) +{ + int best_score = -1; + int best_mode = -1; + int i; + + for (i = 0;; ++i) { + const drmu_mode_simple_params_t sp = drmu_conn_mode_simple_params(dout->dns[0], i); + int score; + + if (sp.width == 0) + break; + + score = score_fn(score_v, &sp); + if (score > best_score) { + best_score = score; + best_mode = i; + } + } + + return best_mode; +} + +int +drmu_output_max_bpc_allow(drmu_output_t * const dout, const bool allow) +{ + dout->max_bpc_allow = allow && dout->has_max_bpc; + return allow && !dout->has_max_bpc ? -ENOENT : 0; +} + +int +drmu_output_modeset_allow(drmu_output_t * const dout, const bool allow) +{ + dout->modeset_allow = allow; + return 0; +} + +static int +check_conns_size(drmu_output_t * const dout) +{ + if (dout->conn_n >= dout->conn_size) { + unsigned int n = !dout->conn_n ? 4 : dout->conn_n * 2; + drmu_conn_t ** dns = realloc(dout->dns, sizeof(*dout->dns) * n); + if (dns == NULL) { + drmu_err(dout->du, "Failed conn array realloc"); + return -ENOMEM; + } + dout->dns = dns; + dout->conn_size = n; + } + return 0; +} + +int +drmu_output_add_output(drmu_output_t * const dout, const char * const conn_name) +{ + const size_t nlen = !conn_name ? 0 : strlen(conn_name); + unsigned int i; + unsigned int retries = 0; + drmu_env_t * const du = dout->du; + drmu_conn_t * dn; + drmu_conn_t * dn_t; + drmu_crtc_t * dc_t; + uint32_t crtc_id; + int rv; + + // ***** + // This logic fatally flawed for anything other than adding a single + // conn already attached to a single crtc + +retry: + if (++retries > 16) { + drmu_err(du, "Retry count exceeded"); + return -EBUSY; + } + dn = NULL; + dc_t = NULL; + + for (i = 0; (dn_t = drmu_env_conn_find_n(du, i)) != NULL; ++i) { + if (!drmu_conn_is_output(dn_t) || drmu_conn_is_claimed(dn_t)) + continue; + if (nlen && strncmp(conn_name, drmu_conn_name(dn_t), nlen) != 0) + continue; + // This prefers conns that are already attached to crtcs + if ((crtc_id = drmu_conn_crtc_id_get(dn_t)) == 0 || + (dc_t = drmu_env_crtc_find_id(du, crtc_id)) == NULL) { + dn = dn_t; + continue; + } + if (drmu_crtc_is_claimed(dc_t)) { + dc_t = NULL; + continue; + } + dn = dn_t; + break; + } + + if (!dn) + return -ENOENT; + + if (!dc_t) { + drmu_warn(du, "Adding unattached conns NIF"); + return -EINVAL; + } + + if ((rv = check_conns_size(dout)) != 0) + return rv; + + if (drmu_crtc_claim_ref(dc_t)) { + drmu_debug(du, "Crtc already claimed"); + goto retry; + } + if (drmu_conn_claim_ref(dn)) { + drmu_debug(du, "Conn already claimed"); + drmu_crtc_unref(&dc_t); + goto retry; + } + + // Test features + { + drmu_atomic_t * da = drmu_atomic_new(du); + if (!da) + return -ENOMEM; + dout->has_max_bpc = (drmu_atomic_conn_add_hi_bpc(da, dn, true) == 0); + drmu_atomic_unref(&da); + } + + dout->dns[dout->conn_n++] = dn; + dout->dc = dc_t; + + dout->mode_params = drmu_crtc_mode_simple_params(dout->dc); + + return 0; +} + +static struct drm_mode_modeinfo +modeinfo_fake(unsigned int w, unsigned int h) +{ + return (struct drm_mode_modeinfo){ + .clock = (h + 30)*(w + 20)*60, + .hdisplay = w, + .hsync_start = w + 10, + .hsync_end = w + 20, + .htotal = w + 30, + .hskew = 0, + .vdisplay = h, + .vsync_start = h + 10, + .vsync_end = h + 12, + .vtotal = h + 20, + .vscan = 0, + .vrefresh = 60, + .type = DRM_MODE_TYPE_USERDEF, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .name = {"fake"}, + }; +} + +static int +try_conn_crtc(drmu_env_t * du, drmu_conn_t * dn, drmu_crtc_t * dc) +{ + int rv; + +#if 1 + const struct drm_mode_modeinfo test_mode = modeinfo_fake(128,128); +#else + // A real mode for testing + static const struct drm_mode_modeinfo test_mode = { + .clock = 25175, + .hdisplay = 640, + .hsync_start = 656, + .hsync_end = 752, + .htotal = 800, + .hskew = 0, + .vdisplay = 480, + .vsync_start = 490, + .vsync_end = 492, + .vtotal = 525, + .vscan = 0, + .vrefresh = 60, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .name = {"640x480-60"}, + }; +#endif + + drmu_atomic_t * da = drmu_atomic_new(du); + + if (!da) + return -ENOMEM; + + if ((rv = drmu_atomic_conn_add_crtc(da, dn, dc)) != 0) { + drmu_warn(du, "Failed to add writeback connector to crtc: %s", strerror(-rv)); + goto fail; + } + if ((rv = drmu_atomic_crtc_add_modeinfo(da, dc, &test_mode)) != 0) { + drmu_warn(du, "Failed to add modeinfo: %s", strerror(-rv)); + goto fail; + } + + if ((rv = drmu_atomic_crtc_add_active(da, dc, 1)) != 0) { + drmu_warn(du, "Failed to add active to crtc: %s", strerror(-rv)); + goto fail; + } + + if ((rv = drmu_atomic_commit(da, DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET)) != 0) { + drmu_warn(du, "Failed test commit of writeback connector to crtc: %s", strerror(-rv)); + goto fail; + } + + drmu_atomic_unref(&da); + return 0; + +fail: + drmu_atomic_unref(&da); + return rv; +} + +int +drmu_atomic_output_add_writeback_fb(drmu_atomic_t * const da_out, drmu_output_t * const dout, + drmu_fb_t * const dfb) +{ + drmu_env_t * const du = dout->du; + drmu_atomic_t * da = drmu_atomic_new(drmu_atomic_env(da_out)); + int rv = -ENOMEM; + struct drm_mode_modeinfo mode = modeinfo_fake(drmu_fb_width(dfb), drmu_fb_height(dfb)); + drmu_conn_t * const dn = dout->dns[0]; + + if (da == NULL) + return -ENOMEM; + + if ((rv = drmu_atomic_conn_add_writeback_fb(da, dn, dfb)) != 0) { + drmu_err(du, "Failed to add FB to conn"); + goto fail; + } + if ((rv = drmu_atomic_crtc_add_modeinfo(da, dout->dc, &mode)) != 0) { + drmu_err(du, "Failed to add modeinfo to CRTC"); + goto fail; + } + if ((rv = drmu_atomic_conn_add_crtc(da, dn, dout->dc)) != 0) { + drmu_err(du, "Failed to add CRTC to Conn"); + goto fail; + } + if ((rv = drmu_atomic_crtc_add_active(da, dout->dc, 1)) != 0) { + drmu_err(du, "Failed to add Active to Conn"); + goto fail; + } + + return drmu_atomic_merge(da_out, &da); + +fail: + drmu_atomic_unref(&da); + return rv; +} + +int +drmu_output_add_writeback(drmu_output_t * const dout) +{ + drmu_env_t * const du = dout->du; + drmu_conn_t * dn = NULL; + drmu_crtc_t * dc = NULL; + drmu_conn_t * dn_t; + int rv; + uint32_t possible_crtcs; + + if (!dout->modeset_allow) { + drmu_debug(du, "modeset_allow required for writeback"); + return -EINVAL; + } + + for (unsigned int i = 0; (dn_t = drmu_env_conn_find_n(du, i)) != NULL; ++i) { + drmu_info(du, "%d: try %s", i, drmu_conn_name(dn_t)); + if (!drmu_conn_is_writeback(dn_t)) + continue; + dn = dn_t; + break; + } + + if (!dn) { + drmu_err(du, "no writeback conn found"); + return -ENOENT; + } + + possible_crtcs = drmu_conn_possible_crtcs(dn); + + for (unsigned int i = 0; possible_crtcs != 0; ++i, possible_crtcs >>= 1) { + drmu_crtc_t *dc_t; + + if ((possible_crtcs & 1) == 0) + continue; + + drmu_info(du, "try Crtc %d", i); + + if ((dc_t = drmu_env_crtc_find_n(du, i)) == NULL) + break; + + if (try_conn_crtc(du, dn, dc_t) == 0) { + dc = dc_t; + break; + } + } + + if (!dc) { + drmu_err(du, "No crtc for writeback found"); + return -ENOENT; + } + + if ((rv = check_conns_size(dout)) != 0) + return rv; + + dout->dns[dout->conn_n++] = dn; + dout->dc = dc; + return 0; +} + +drmu_crtc_t * +drmu_output_crtc(const drmu_output_t * const dout) +{ + return !dout ? NULL : dout->dc; +} + +drmu_conn_t * +drmu_output_conn(const drmu_output_t * const dout, const unsigned int n) +{ + return !dout || n >= dout->conn_n ? NULL : dout->dns[n]; +} + +drmu_env_t * +drmu_output_env(const drmu_output_t * const dout) +{ + return dout->du; +} + +static void +output_free(drmu_output_t * const dout) +{ + unsigned int i; + for (i = 0; i != dout->conn_n; ++i) + drmu_conn_unref(dout->dns + i); + free(dout->dns); + drmu_crtc_unref(&dout->dc); + drmu_env_unref(&dout->du); + free(dout); +} + +void +drmu_output_unref(drmu_output_t ** const ppdout) +{ + drmu_output_t * const dout = *ppdout; + if (dout == NULL) + return; + *ppdout = NULL; + + if (atomic_fetch_sub(&dout->ref_count, 1) == 0) + output_free(dout); +} + +drmu_output_t * +drmu_output_ref(drmu_output_t * const dout) +{ + if (dout != NULL) + atomic_fetch_add(&dout->ref_count, 1); + return dout; +} + +drmu_output_t * +drmu_output_new(drmu_env_t * const du) +{ + drmu_output_t * const dout = calloc(1, sizeof(*dout)); + + if (dout == NULL) { + drmu_err(du, "Failed to alloc memory for drmu_output"); + return NULL; + } + + dout->du = drmu_env_ref(du); + dout->mode_id = -1; + return dout; +} + diff --git a/modules/video_output/drmu/drmu_output.h b/modules/video_output/drmu/drmu_output.h new file mode 100644 index 0000000000..5ec2d71840 --- /dev/null +++ b/modules/video_output/drmu/drmu_output.h @@ -0,0 +1,87 @@ +#ifndef _DRMU_DRMU_OUTPUT_H +#define _DRMU_DRMU_OUTPUT_H + +#include "drmu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct drmu_output_s; +typedef struct drmu_output_s drmu_output_t; + +drmu_plane_t * drmu_output_plane_ref_primary(drmu_output_t * const dout); +drmu_plane_t * drmu_output_plane_ref_other(drmu_output_t * const dout); +// Find and ref a plane that supports the given format & mod on the current crtc +// Types is a bit field of acceptable plane types (DRMU_PLANE_TYPE_xxx), 0 => any +// +// add_output must be called before this (so we have a crtc to check against) +drmu_plane_t * drmu_output_plane_ref_format(drmu_output_t * const dout, const unsigned int types, const uint32_t format, const uint64_t mod); + +// Add all props accumulated on the output to the atomic +int drmu_atomic_output_add_props(drmu_atomic_t * const da, drmu_output_t * const dout); + +// Set FB info (bit-depth, HDR metadata etc.) +// Only sets properties that are set in the fb - retains previous value otherwise +int drmu_output_fb_info_set(drmu_output_t * const dout, const drmu_fb_t * const fb); +// Unset all FB info +// (set only sets stuff that is set in the fb, so will never clear anything) +void drmu_output_fb_info_unset(drmu_output_t * const dout); + +// Set output mode +int drmu_output_mode_id_set(drmu_output_t * const dout, const int mode_id); + +// Width/height of the currebnt mode +const drmu_mode_simple_params_t * drmu_output_mode_simple_params(const drmu_output_t * const dout); + +typedef int drmu_mode_score_fn(void * v, const drmu_mode_simple_params_t * mode); + +int drmu_output_mode_pick_simple(drmu_output_t * const dout, drmu_mode_score_fn * const score_fn, void * const score_v); + +// Simple mode picker cb - looks for width / height and then refresh +// If nothing "plausible" defaults to EDID preferred mode +drmu_mode_score_fn drmu_mode_pick_simple_cb; +// As above but may choose an interlaced mode +drmu_mode_score_fn drmu_mode_pick_simple_interlace_cb; + +// Allow fb max_bpc info to set the output mode (default false) +int drmu_output_max_bpc_allow(drmu_output_t * const dout, const bool allow); + +// Allow fb to set modes generally +int drmu_output_modeset_allow(drmu_output_t * const dout, const bool allow); + +// Add a CONN/CRTC pair to an output +// If conn_name == NULL then 1st connected connector is used +// If != NULL then 1st conn with prefix-matching name is used +int drmu_output_add_output(drmu_output_t * const dout, const char * const conn_name); + +// Set writeback fb on output +int drmu_atomic_output_add_writeback_fb(drmu_atomic_t * const da_req, drmu_output_t * const dout, + drmu_fb_t * const dfb); + +// Add a writeback connector & find a crtc for it +int drmu_output_add_writeback(drmu_output_t * const dout); + +// Conn & CRTC for when output isn't fine grained enough +drmu_crtc_t * drmu_output_crtc(const drmu_output_t * const dout); +drmu_conn_t * drmu_output_conn(const drmu_output_t * const dout, const unsigned int n); + +// Return the in-use drmu environment +drmu_env_t * drmu_output_env(const drmu_output_t * const dout); + +// Create a new empty output - has no crtc or conn +// Takes a ref on the env (released when the output is deleted) +drmu_output_t * drmu_output_new(drmu_env_t * const du); + +// Increment ref count on an output - cannot fail +drmu_output_t * drmu_output_ref(drmu_output_t * const dout); +// Unref an output - delete if ref count now zero +void drmu_output_unref(drmu_output_t ** const ppdout); + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/modules/video_output/drmu/drmu_pool.c b/modules/video_output/drmu/drmu_pool.c new file mode 100644 index 0000000000..b047154366 --- /dev/null +++ b/modules/video_output/drmu/drmu_pool.c @@ -0,0 +1,306 @@ +#include "drmu_pool.h" + +#include +#include +#include +#include +#include +#include + +#include "drmu.h" +#include "drmu_log.h" + +//---------------------------------------------------------------------------- +// +// Pool fns + +typedef struct drmu_fb_slot_s { + struct drmu_fb_s * fb; + struct drmu_fb_slot_s * next; + struct drmu_fb_slot_s * prev; +} drmu_fb_slot_t; + +typedef struct drmu_fb_list_s { + drmu_fb_slot_t * head; // Double linked list of free FBs; LRU @ head + drmu_fb_slot_t * tail; + drmu_fb_slot_t * unused; // Single linked list of unused slots +} drmu_fb_list_t; + +struct drmu_pool_s { + atomic_int ref_count; // 0 == 1 ref for ease of init + bool dead; // Pool killed - never alloc again + + unsigned int fb_count; // FBs allocated (not free count) + unsigned int fb_max; // Max FBs to allocate + + struct drmu_env_s * du; // Logging only - not reffed + + drmu_pool_callback_fns_t callback_fns; + void * callback_v; + + pthread_mutex_t lock; + + drmu_fb_list_t free_fbs; // Free FB list header + drmu_fb_slot_t * slots; // [fb_max] +}; + +static void +fb_list_add_tail(drmu_fb_list_t * const fbl, drmu_fb_t * const dfb) +{ + drmu_fb_slot_t * const slot = fbl->unused; + + assert(slot != NULL); + fbl->unused = slot->next; + slot->fb = dfb; + slot->next = NULL; + + if (fbl->tail == NULL) + fbl->head = slot; + else + fbl->tail->next = slot; + slot->prev = fbl->tail; + fbl->tail = slot; +} + +static drmu_fb_t * +fb_list_extract(drmu_fb_list_t * const fbl, drmu_fb_slot_t * const slot) +{ + drmu_fb_t * dfb; + + if (slot == NULL) + return NULL; + + if (slot->prev == NULL) + fbl->head = slot->next; + else + slot->prev->next = slot->next; + + if (slot->next == NULL) + fbl->tail = slot->prev; + else + slot->next->prev = slot->prev; + + dfb = slot->fb; + slot->fb = NULL; + slot->next = fbl->unused; + slot->prev = NULL; + fbl->unused = slot; + return dfb; +} + +static drmu_fb_t * +fb_list_extract_head(drmu_fb_list_t * const fbl) +{ + return fb_list_extract(fbl, fbl->head); +} + +static void +pool_free_pool(drmu_pool_t * const pool) +{ + drmu_fb_t * dfb; + pthread_mutex_lock(&pool->lock); + while ((dfb = fb_list_extract_head(&pool->free_fbs)) != NULL) { + --pool->fb_count; + pthread_mutex_unlock(&pool->lock); + drmu_fb_unref(&dfb); + pthread_mutex_lock(&pool->lock); + } + pthread_mutex_unlock(&pool->lock); +} + +static void +pool_free(drmu_pool_t * const pool) +{ + void *const v = pool->callback_v; + const drmu_pool_on_delete_fn on_delete_fn = pool->callback_fns.on_delete_fn; + pool_free_pool(pool); + free(pool->slots); + pthread_mutex_destroy(&pool->lock); + free(pool); + + on_delete_fn(v); +} + +void +drmu_pool_unref(drmu_pool_t ** const pppool) +{ + drmu_pool_t * const pool = *pppool; + int n; + + if (pool == NULL) + return; + *pppool = NULL; + + n = atomic_fetch_sub(&pool->ref_count, 1); + assert(n >= 0); + if (n == 0) + pool_free(pool); +} + +drmu_pool_t * +drmu_pool_ref(drmu_pool_t * const pool) +{ + atomic_fetch_add(&pool->ref_count, 1); + return pool; +} + +drmu_pool_t * +drmu_pool_new_alloc(drmu_env_t * const du, const unsigned int total_fbs_max, + const drmu_pool_callback_fns_t * const cb_fns, + void * const v) +{ + drmu_pool_t * const pool = calloc(1, sizeof(*pool)); + unsigned int i; + + if (pool == NULL) + goto fail0; + if ((pool->slots = calloc(total_fbs_max, sizeof(*pool->slots))) == NULL) + goto fail1; + + pool->du = du; + pool->fb_max = total_fbs_max; + pool->callback_fns = *cb_fns; + pool->callback_v = v; + + for (i = 1; i != total_fbs_max; ++i) + pool->slots[i - 1].next = pool->slots + i; + pool->free_fbs.unused = pool->slots + 0; + + pthread_mutex_init(&pool->lock, NULL); + + return pool; + +fail1: + free(pool); +fail0: + cb_fns->on_delete_fn(v); + drmu_err(du, "Failed pool env alloc"); + return NULL; +} + +static int +pool_fb_pre_delete_cb(drmu_fb_t * dfb, void * v) +{ + drmu_pool_t * pool = v; + + // Ensure we cannot end up in a delete loop + drmu_fb_pre_delete_unset(dfb); + + // If dead set then might as well delete now + // It should all work without this shortcut but this reclaims + // storage quicker + if (pool->dead) { + drmu_pool_unref(&pool); + return 0; + } + + drmu_fb_ref(dfb); // Restore ref + + pthread_mutex_lock(&pool->lock); + fb_list_add_tail(&pool->free_fbs, dfb); + pthread_mutex_unlock(&pool->lock); + + // May cause suicide & recursion on fb delete, but that should be OK as + // the 1 we return here should cause simple exit of fb delete + drmu_pool_unref(&pool); + return 1; // Stop delete +} + +drmu_fb_t * +drmu_pool_fb_new(drmu_pool_t * const pool, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod) +{ + drmu_fb_t * dfb; + drmu_fb_slot_t * slot; + + pthread_mutex_lock(&pool->lock); + + // If pool killed then _fb_new must fail + if (pool->dead) + goto fail_unlock; + + slot = pool->free_fbs.head; + while (slot != NULL) { + dfb = slot->fb; + if (pool->callback_fns.try_reuse_fn(dfb, w, h, format, mod)) { + fb_list_extract(&pool->free_fbs, slot); + pthread_mutex_unlock(&pool->lock); + goto found; + } + slot = slot->next; + } + // Nothing reusable + dfb = NULL; + + // Simply allocate new buffers until we hit fb_max then free LRU + // first. If nothing to free then fail. + if (pool->fb_count++ >= pool->fb_max) { + --pool->fb_count; + if ((dfb = fb_list_extract_head(&pool->free_fbs)) == NULL) + goto fail_unlock; + } + pthread_mutex_unlock(&pool->lock); + + drmu_fb_unref(&dfb); // Will free the dfb as pre-delete CB will be unset + + if ((dfb = pool->callback_fns.alloc_fn(pool->callback_v, w, h, format, mod)) == NULL) { + pthread_mutex_lock(&pool->lock); + --pool->fb_count; + goto fail_unlock; + } + +found: + drmu_fb_pre_delete_set(dfb, pool_fb_pre_delete_cb, drmu_pool_ref(pool)); + return dfb; + +fail_unlock: + pthread_mutex_unlock(&pool->lock); + return NULL; +} + +// Mark pool as dead (i.e. no new allocs) and unref it +// Simple unref will also work but this reclaims storage faster +// Actual pool structure will persist until all referencing fbs are deleted too +void +drmu_pool_kill(drmu_pool_t ** const pppool) +{ + drmu_pool_t * pool = *pppool; + + if (pool == NULL) + return; + *pppool = NULL; + + pool->dead = true; + pool_free_pool(pool); + + drmu_pool_unref(&pool); +} + +//---------------------------------------------------------------------------- +// +// Dumb pool setup + +static drmu_fb_t * +pool_dumb_alloc_cb(void * const v, const uint32_t w, const uint32_t h, const uint32_t format, const uint64_t mod) +{ + return drmu_fb_new_dumb_mod(v, w, h, format, mod); +} + +static void +pool_dumb_on_delete_cb(void * const v) +{ + drmu_env_t * du = v; + drmu_env_unref(&du); +} + +drmu_pool_t * +drmu_pool_new_dumb(drmu_env_t * const du, unsigned int total_fbs_max) +{ + static const drmu_pool_callback_fns_t fns = { + .alloc_fn = pool_dumb_alloc_cb, + .on_delete_fn = pool_dumb_on_delete_cb, + .try_reuse_fn = drmu_fb_try_reuse, + }; + return drmu_pool_new_alloc(du, total_fbs_max, &fns, drmu_env_ref(du)); +} + + diff --git a/modules/video_output/drmu/drmu_pool.h b/modules/video_output/drmu/drmu_pool.h new file mode 100644 index 0000000000..181d3389a5 --- /dev/null +++ b/modules/video_output/drmu/drmu_pool.h @@ -0,0 +1,58 @@ +#ifndef _DRMU_DRMU_POOL_H +#define _DRMU_DRMU_POOL_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct drmu_pool_s; +typedef struct drmu_pool_s drmu_pool_t; + +struct drmu_env_s; +struct drmu_fb_s; + +// fb pool + +void drmu_pool_unref(drmu_pool_t ** const pppool); +drmu_pool_t * drmu_pool_ref(drmu_pool_t * const pool); + +// cb to allocate a new pool fb +typedef struct drmu_fb_s * (* drmu_pool_alloc_fn)(void * const v, const uint32_t w, const uint32_t h, const uint32_t format, const uint64_t mod); +// cb called when pool deleted or on new_pool failure - takes the same v as alloc +typedef void (* drmu_pool_on_delete_fn)(void * const v); +typedef bool (* drmu_pool_try_reuse_fn)(struct drmu_fb_s * dfb, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod); + +typedef struct drmu_pool_callback_fns_s { + drmu_pool_alloc_fn alloc_fn; + drmu_pool_on_delete_fn on_delete_fn; + drmu_pool_try_reuse_fn try_reuse_fn; +} drmu_pool_callback_fns_t; + +// Create a new pool with custom alloc & pool delete +// If pool creation fails then on_delete_fn(v) called and NULL returned +// Pool entries are not pre-allocated. +drmu_pool_t * drmu_pool_new_alloc(struct drmu_env_s * const du, const unsigned int total_fbs_max, + const drmu_pool_callback_fns_t * const cb_fns, + void * const v); +// Create a new pool of fb allocated from dumb objects +// N.B. BOs are alloced from uncached memory so may be slow to do anything other +// than copy into. (See drmu_dmabuf_ if you want cached data) +struct drmu_fb_s * drmu_pool_fb_new(drmu_pool_t * const pool, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod); +// Marks the pool as dead & unrefs this reference +// No allocs will succeed after this +// All free fbs are unrefed +void drmu_pool_kill(drmu_pool_t ** const pppool); + +drmu_pool_t * drmu_pool_new_dumb(struct drmu_env_s * const du, unsigned int total_fbs_max); +// Allocate a fb from the pool +// Allocations need not be all of the same size but no guarantees are made about +// efficient memory use if this is the case + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/video_output/drmu/drmu_scan.c b/modules/video_output/drmu/drmu_scan.c new file mode 100644 index 0000000000..9cb0d20c90 --- /dev/null +++ b/modules/video_output/drmu/drmu_scan.c @@ -0,0 +1,60 @@ +#include "drmu_scan.h" + +#include +#include +#include + +#include "drmu.h" +#include "drmu_log.h" +#include "drmu_output.h" + +#define CARD_MAX 16 + + +int +drmu_scan_output(const char * const cname, const drmu_log_env_t * const dlog, + drmu_env_t ** const pDu, drmu_output_t ** const pDoutput) +{ + static const char * card_prefix = "/dev/dri/card"; + unsigned int i; + + *pDu = NULL; + *pDoutput = NULL; + + for (i = 0; i != CARD_MAX; ++i) { + drmu_env_t * du; + drmu_output_t * dout = NULL; + char fname[32]; + int fd; + + drmu_debug_log(dlog, "Try card %d", i); + + sprintf(fname, "%s%d", card_prefix, i); + while ((fd = open(fname, O_RDWR | O_CLOEXEC)) == -1 && errno == EINTR) + /* Loop */; + if (fd == -1) { + if (errno == ENOENT) + break; + continue; + } + + // Have FD + if ((du = drmu_env_new_fd(fd, dlog)) == NULL) + continue; + + if ((dout = drmu_output_new(du)) == NULL) + goto loop1; + + if (drmu_output_add_output(dout, cname) == 0) { + *pDu = du; + *pDoutput = dout; + return 0; + } + + drmu_output_unref(&dout); +loop1: + drmu_env_unref(&du); + } + return -ENOENT; +} + diff --git a/modules/video_output/drmu/drmu_scan.h b/modules/video_output/drmu/drmu_scan.h new file mode 100644 index 0000000000..4351deec08 --- /dev/null +++ b/modules/video_output/drmu/drmu_scan.h @@ -0,0 +1,20 @@ +#ifndef _DRMU_DRMU_SCAN_H +#define _DRMU_DRMU_SCAN_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct drmu_log_env_s; +struct drmu_env_s; +struct drmu_output_s; + +int +drmu_scan_output(const char * const cname, const struct drmu_log_env_s * const dlog, + struct drmu_env_s ** const pDu, struct drmu_output_s ** const pDoutput); + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/modules/video_output/drmu/drmu_util.c b/modules/video_output/drmu/drmu_util.c new file mode 100644 index 0000000000..20d24bfc09 --- /dev/null +++ b/modules/video_output/drmu/drmu_util.c @@ -0,0 +1,163 @@ +#include "drmu_util.h" + +#include "drmu.h" + +#include +#include +#include +#include +#include + +#include + +static unsigned long +h_to_w(const unsigned long h) +{ + switch (h) { + case 480: + case 576: + return 720; + case 720: + return 1280; + case 1080: + return 1920; + case 2160: + return 3840; + default: + break; + } + return 0; +} + +char * +drmu_util_parse_mode_simple_params(const char * s, drmu_mode_simple_params_t * const p) +{ + unsigned long w = 0, h = 0, hz = 0; + bool il = false; + bool drmhz = false; + + memset(p , 0, sizeof(*p)); + + if (isdigit(*s)) { + h = strtoul(s, (char **)&s, 10); + + if (*s == 'p' || *s == 'i') { + w = h_to_w(h); + } + else if (*s == 'x') { + w = h; + h = strtoul(s + 1, (char **)&s, 10); + } + else { + return (char *)s; + } + } + + // Consume 'i' or 'p' + // Can still have (now) optional hz separator after + if (*s == 'p') { + ++s; + } + else if (*s == 'i') { + il = true; + ++s; + } + + // I've used '@' in the past to separate size from hz + // DRM uses '-' in modetest so accept that + if (*s == '@') { + ++s; + } + else if (*s == '-') { + drmhz = true; + ++s; + } + + if (isdigit(*s)) { + hz = strtoul(s, (char **)&s, 10) * 1000; + + if (*s == '.') { + unsigned int m = 100; + while (isdigit(*++s)) { + hz += (*s - '0') * m; + m /= 10; + } + } + } + + // DRM thinks in frame rate, rest of the world specifies as field rate + if (il && !drmhz) + hz /= 2; + + p->width = (unsigned int)w; + p->height = (unsigned int)h; + p->hz_x_1000 = (unsigned int)hz; + p->flags = !il ? 0 : DRM_MODE_FLAG_INTERLACE; + + return (char *)s; +} + +char * +drmu_util_simple_param_to_mode_str(char * buf, size_t buflen, const drmu_mode_simple_params_t * const p) +{ + int hz = p->hz_x_1000; + + if ((p->flags & DRM_MODE_FLAG_INTERLACE)) + hz *= 2; + + snprintf(buf, buflen, "%dx%d%c%d.%03d", + p->width, p->height, + (p->flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', + hz / 1000, hz % 1000); + return buf; +} + +char * +drmu_util_parse_mode(const char * s, unsigned int * pw, unsigned int * ph, unsigned int * phz) +{ + drmu_mode_simple_params_t p; + char * r = drmu_util_parse_mode_simple_params(s, &p); + *pw = p.width; + *ph = p.height; + *phz = p.hz_x_1000; + return r; +} + +drmu_ufrac_t +drmu_util_guess_par(const unsigned int w, const unsigned int h) +{ + if (((w == 720 || w == 704) && (h == 480 || h == 576)) || + ((w == 360 || w == 352) && (h == 240 || h == 288))) + { + return (drmu_ufrac_t){.num = 4, .den = 3}; + } + return drmu_ufrac_reduce((drmu_ufrac_t){.num = w, .den = h}); +} + +drmu_ufrac_t +drmu_util_guess_simple_mode_par(const drmu_mode_simple_params_t * const p) +{ + if (p->par.den != 0 && p->par.num != 0) + return p->par; + return drmu_util_guess_par(p->width, p->height); +} + +void +drmu_memcpy_2d(void * const dst_p, const size_t dst_stride, + const void * const src_p, const size_t src_stride, + const size_t width, const size_t height) +{ + if (dst_stride == src_stride && dst_stride == width) { + memcpy(dst_p, src_p, width * height); + } + else { + size_t i; + char * d = dst_p; + const char * s = src_p; + for (i = 0; i != height; ++i) { + memcpy(d, s, width); + d += dst_stride; + s += src_stride; + } + } +} diff --git a/modules/video_output/drmu/drmu_util.h b/modules/video_output/drmu/drmu_util.h new file mode 100644 index 0000000000..3d6cdd6103 --- /dev/null +++ b/modules/video_output/drmu/drmu_util.h @@ -0,0 +1,58 @@ +#ifndef _DRMU_DRMU_UTIL_H +#define _DRMU_DRMU_UTIL_H + +#include + +#include "drmu_math.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct drmu_mode_simple_params_s; + +// Parse a string of the form [x][i][@[.]] +// Returns pointer to terminating char +// Missing fields are zero +char * drmu_util_parse_mode(const char * s, unsigned int * pw, unsigned int * ph, unsigned int * pHzx1000); + +// As above but place results into a simple params structure +// (Unused fields zeroed) +// Also copes with interlace +char * drmu_util_parse_mode_simple_params(const char * s, struct drmu_mode_simple_params_s * const p); + +// Simple params to mode string +char * drmu_util_simple_param_to_mode_str(char * buf, size_t buflen, const struct drmu_mode_simple_params_s * const p); + +#define drmu_util_simple_mode(p) drmu_util_simple_param_to_mode_str((char[64]){0}, 64, (p)) + +// Given width & height guess par. Spots Likely SD and returns 4:3 otherwise reduced w:h +drmu_ufrac_t drmu_util_guess_par(const unsigned int w, const unsigned int h); +// Get a par from simple_params. par can be zero & if so then guess +drmu_ufrac_t drmu_util_guess_simple_mode_par(const struct drmu_mode_simple_params_s * const p); + +// Misc memcpy util + +// Simple 2d memcpy +void drmu_memcpy_2d(void * const dst_p, const size_t dst_stride, + const void * const src_p, const size_t src_stride, + const size_t width, const size_t height); +// 'FB' copy +static inline void +drmu_memcpy_rect(void * const dst_p, const size_t dst_stride, const drmu_rect_t dst_rect, + const void * const src_p, const size_t src_stride, const drmu_rect_t src_rect, + const unsigned int pixel_stride) +{ + drmu_memcpy_2d((char *)dst_p + dst_rect.x * pixel_stride + dst_rect.y * dst_stride, dst_stride, + (char *)src_p + src_rect.x * pixel_stride + src_rect.y * src_stride, src_stride, + (src_rect.w < dst_rect.w ? src_rect.w : dst_rect.w) * pixel_stride, + src_rect.h < dst_rect.h ? src_rect.h : dst_rect.h); +} + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/modules/video_output/drmu/drmu_vlc.c b/modules/video_output/drmu/drmu_vlc.c new file mode 100644 index 0000000000..506de90fbb --- /dev/null +++ b/modules/video_output/drmu/drmu_vlc.c @@ -0,0 +1,353 @@ +#include "drmu_vlc.h" +#include "drmu_fmts.h" +#include "drmu_log.h" + +#if HAS_ZC_CMA +#include "../../hw/mmal/mmal_cma_pic.h" +#endif +#if HAS_DRMPRIME +#include "../../codec/avcodec/drm_pic.h" +#endif + +#include + +#include +#include + +#include + +typedef struct fb_aux_pic_s { + picture_context_t * pic_ctx; +} fb_aux_pic_t; + +static void +pic_fb_delete_cb(void * v) +{ + fb_aux_pic_t * const aux = v; + + aux->pic_ctx->destroy(aux->pic_ctx); + free(aux); +} + +static int +pic_hdr_metadata(struct hdr_output_metadata * const m, const struct video_format_t * const fmt) +{ + struct hdr_metadata_infoframe * const inf = &m->hdmi_metadata_type1; + unsigned int i; + + memset(m, 0, sizeof(*m)); + m->metadata_type = HDMI_STATIC_METADATA_TYPE1; + inf->metadata_type = HDMI_STATIC_METADATA_TYPE1; + + switch (fmt->transfer) { + case TRANSFER_FUNC_SMPTE_ST2084: + inf->eotf = HDMI_EOTF_SMPTE_ST2084; + break; + case TRANSFER_FUNC_ARIB_B67: + inf->eotf = HDMI_EOTF_BT_2100_HLG; + break; + default: + // HDMI_EOTF_TRADITIONAL_GAMMA_HDR for 10bit? + inf->eotf = HDMI_EOTF_TRADITIONAL_GAMMA_SDR; + return -ENOENT; + } + + // VLC & HDMI use the same scales for everything but max_luma + for (i = 0; i != 3; ++i) { + inf->display_primaries[i].x = fmt->mastering.primaries[i * 2 + 0]; + inf->display_primaries[i].y = fmt->mastering.primaries[i * 2 + 1]; + } + inf->white_point.x = fmt->mastering.white_point[0]; + inf->white_point.y = fmt->mastering.white_point[1]; + inf->max_display_mastering_luminance = (uint16_t)(fmt->mastering.max_luminance / 10000); + inf->min_display_mastering_luminance = (uint16_t)fmt->mastering.min_luminance; + + inf->max_cll = fmt->lighting.MaxCLL; + inf->max_fall = fmt->lighting.MaxFALL; + + return 0; +} + +static drmu_color_encoding_t +fb_vlc_color_encoding(const video_format_t * const fmt) +{ + switch (fmt->space) + { + case COLOR_SPACE_BT2020: + return DRMU_COLOR_ENCODING_BT2020; + case COLOR_SPACE_BT601: + return DRMU_COLOR_ENCODING_BT601; + case COLOR_SPACE_BT709: + return DRMU_COLOR_ENCODING_BT709; + case COLOR_SPACE_UNDEF: + default: + break; + } + + return (fmt->i_visible_width > 1024 || fmt->i_visible_height > 600) ? + DRMU_COLOR_ENCODING_BT709 : + DRMU_COLOR_ENCODING_BT601; +} + +static drmu_color_range_t +fb_vlc_color_range(const video_format_t * const fmt) +{ +#if HAS_VLC4 + switch (fmt->color_range) + { + case COLOR_RANGE_FULL: + return DRMU_COLOR_RANGE_YCBCR_FULL_RANGE; + case COLOR_RANGE_UNDEF: + case COLOR_RANGE_LIMITED: + default: + break; + } +#else + if (fmt->b_color_range_full) + return DRMU_COLOR_RANGE_YCBCR_FULL_RANGE; +#endif + return DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE; +} + + +static const char * +fb_vlc_colorspace(const video_format_t * const fmt) +{ + switch (fmt->space) { + case COLOR_SPACE_BT2020: + return DRMU_COLORSPACE_BT2020_RGB; + default: + break; + } + return DRMU_COLORSPACE_DEFAULT; +} + +static drmu_chroma_siting_t +fb_vlc_chroma_siting(const video_format_t * const fmt) +{ + switch (fmt->chroma_location) { + case CHROMA_LOCATION_LEFT: + return DRMU_CHROMA_SITING_LEFT; + case CHROMA_LOCATION_CENTER: + return DRMU_CHROMA_SITING_CENTER; + case CHROMA_LOCATION_TOP_LEFT: + return DRMU_CHROMA_SITING_TOP_LEFT; + case CHROMA_LOCATION_TOP_CENTER: + return DRMU_CHROMA_SITING_TOP; + case CHROMA_LOCATION_BOTTOM_LEFT: + return DRMU_CHROMA_SITING_BOTTOM_LEFT; + case CHROMA_LOCATION_BOTTOM_CENTER: + return DRMU_CHROMA_SITING_BOTTOM; + default: + case CHROMA_LOCATION_UNDEF: + break; + } + return DRMU_CHROMA_SITING_UNSPECIFIED; +} + +void +drmu_fb_vlc_pic_set_metadata(drmu_fb_t * const dfb, const picture_t * const pic) +{ + struct hdr_output_metadata meta; + + drmu_fb_color_set(dfb, + fb_vlc_color_encoding(&pic->format), + fb_vlc_color_range(&pic->format), + fb_vlc_colorspace(&pic->format)); + + drmu_fb_chroma_siting_set(dfb, fb_vlc_chroma_siting(&pic->format)); + + drmu_fb_hdr_metadata_set(dfb, pic_hdr_metadata(&meta, &pic->format) == 0 ? &meta : NULL); +} + +#if HAS_DRMPRIME +// Create a new fb from a VLC DRM_PRIME picture. +// Picture is held reffed by the fb until the fb is deleted +drmu_fb_t * +drmu_fb_vlc_new_pic_attach(drmu_env_t * const du, picture_t * const pic) +{ + int i, j, n; + drmu_fb_t * const dfb = drmu_fb_int_alloc(du); + const AVDRMFrameDescriptor * const desc = drm_prime_get_desc(pic); + fb_aux_pic_t * aux = NULL; + + if (dfb == NULL) { + drmu_err(du, "%s: Alloc failure", __func__); + return NULL; + } + + if (desc == NULL) { + drmu_err(du, "%s: Missing descriptor", __func__); + goto fail; + } + if (desc->nb_objects > 4) { + drmu_err(du, "%s: Bad descriptor", __func__); + goto fail; + } + + drmu_fb_int_fmt_size_set(dfb, + desc->layers[0].format, + pic->format.i_width, + pic->format.i_height, + drmu_rect_vlc_pic_crop(pic)); + + + // Set delete callback & hold this pic + // Aux attached to dfb immediately so no fail cleanup required + if ((aux = calloc(1, sizeof(*aux))) == NULL) { + drmu_err(du, "%s: Aux alloc failure", __func__); + goto fail; + } + aux->pic_ctx = pic->context->copy(pic->context); + drmu_fb_int_on_delete_set(dfb, pic_fb_delete_cb, aux); + + for (i = 0; i < desc->nb_objects; ++i) + { + drmu_bo_t * bo = drmu_bo_new_fd(du, desc->objects[i].fd); + if (bo == NULL) + goto fail; + drmu_fb_int_bo_set(dfb, i, bo); + } + + n = 0; + for (i = 0; i < desc->nb_layers; ++i) + { + for (j = 0; j < desc->layers[i].nb_planes; ++j) + { + const AVDRMPlaneDescriptor *const p = desc->layers[i].planes + j; + const AVDRMObjectDescriptor *const obj = desc->objects + p->object_index; + + drmu_fb_int_layer_mod_set(dfb, n++, p->object_index, p->pitch, p->offset, obj->format_modifier); + } + } + + drmu_fb_vlc_pic_set_metadata(dfb, pic); + + if (drmu_fb_int_make(dfb) != 0) + goto fail; + return dfb; + +fail: + drmu_fb_int_free(dfb); + return NULL; +} +#endif + +#if HAS_ZC_CMA +drmu_fb_t * +drmu_fb_vlc_new_pic_cma_attach(drmu_env_t * const du, picture_t * const pic) +{ + int i; + drmu_fb_t * const dfb = drmu_fb_int_alloc(du); + fb_aux_pic_t * aux = NULL; + uint64_t mod; + uint32_t fmt = drmu_format_vlc_to_drm_cma(&pic->format, &mod); + const bool is_sand = (pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND8 || + pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND30); + cma_buf_t * const cb = cma_buf_pic_get(pic); + + if (dfb == NULL) { + drmu_err(du, "%s: Alloc failure", __func__); + return NULL; + } + + if (fmt == 0) { + drmu_err(du, "Pic bad format for cma"); + goto fail; + } + + if (cb == NULL) { + drmu_err(du, "Pic missing cma block"); + goto fail; + } + + drmu_fb_int_fmt_size_set(dfb, + fmt, + pic->format.i_width, + pic->format.i_height, + drmu_rect_vlc_pic_crop(pic)); + + // Set delete callback & hold this pic + // Aux attached to dfb immediately so no fail cleanup required + if ((aux = calloc(1, sizeof(*aux))) == NULL) { + drmu_err(du, "%s: Aux alloc failure", __func__); + goto fail; + } + aux->pic_ctx = pic->context->copy(pic->context); + drmu_fb_int_on_delete_set(dfb, pic_fb_delete_cb, aux); + + { + drmu_bo_t * bo = drmu_bo_new_fd(du, cma_buf_fd(cb)); + if (bo == NULL) + goto fail; + drmu_fb_int_bo_set(dfb, 0, bo); + } + + { + uint8_t * const base_addr = cma_buf_addr(cb); + for (i = 0; i < pic->i_planes; ++i) { + if (is_sand) + drmu_fb_int_layer_mod_set(dfb, i, 0, pic->format.i_width, pic->p[i].p_pixels - base_addr, + DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(pic->p[i].i_pitch)); + else + drmu_fb_int_layer_mod_set(dfb, i, 0, pic->p[i].i_pitch, pic->p[i].p_pixels - base_addr, mod); + } + } + + drmu_fb_vlc_pic_set_metadata(dfb, pic); + + if (drmu_fb_int_make(dfb) != 0) + goto fail; + return dfb; + +fail: + drmu_fb_int_free(dfb); + return NULL; +} +#endif + +plane_t +drmu_fb_vlc_plane(drmu_fb_t * const dfb, const unsigned int plane_n) +{ + const drmu_fmt_info_t *const f = drmu_fb_format_info_get(dfb); + unsigned int hdiv = drmu_fmt_info_hdiv(f, plane_n); + unsigned int wdiv = drmu_fmt_info_wdiv(f, plane_n); + const unsigned int bypp = (drmu_fmt_info_pixel_bits(f) + 7) / 8; + const uint32_t pitch_n = drmu_fb_pitch(dfb, plane_n); + const drmu_rect_t crop = drmu_rect_shr16_rnd(drmu_fb_crop_frac(dfb)); + + if (pitch_n == 0) { + return (plane_t) {.p_pixels = NULL }; + } + + return (plane_t){ + .p_pixels = (uint8_t *)drmu_fb_data(dfb, plane_n) + + pitch_n * (crop.y / hdiv) + (crop.x / wdiv) * bypp, + .i_lines = drmu_fb_height(dfb) / hdiv, + .i_pitch = pitch_n, + .i_pixel_pitch = bypp, + .i_visible_lines = crop.h / hdiv, + .i_visible_pitch = (crop.w / wdiv) * bypp + }; +} + +#if !HAS_VLC4 +#define vlc_object_vaLog vlc_vaLog +#endif + +void +drmu_log_vlc_cb(void * v, enum drmu_log_level_e level_drmu, const char * fmt, va_list vl) +{ + const char * const file_name = va_arg(vl, const char *); + const unsigned int line_no = va_arg(vl, unsigned int); + const char * const function_name = va_arg(vl, const char *); + const int level_vlc = + level_drmu <= DRMU_LOG_LEVEL_MESSAGE ? VLC_MSG_INFO : + level_drmu <= DRMU_LOG_LEVEL_ERROR ? VLC_MSG_ERR : + level_drmu <= DRMU_LOG_LEVEL_WARNING ? VLC_MSG_WARN : + VLC_MSG_DBG; + + vlc_object_vaLog((vlc_object_t *)v, level_vlc, vlc_module_name, file_name, line_no, + function_name, fmt + DRMU_LOG_FMT_OFFSET_FMT, vl); +} + diff --git a/modules/video_output/drmu/drmu_vlc.h b/modules/video_output/drmu/drmu_vlc.h new file mode 100644 index 0000000000..5c19141930 --- /dev/null +++ b/modules/video_output/drmu/drmu_vlc.h @@ -0,0 +1,81 @@ +#ifndef _DRMU_DRMU_VLC_H +#define _DRMU_DRMU_VLC_H + +#include "config.h" + +#ifndef HAS_VLC4 +#define HAS_VLC4 0 +#endif +#ifndef HAS_ZC_CMA +#define HAS_ZC_CMA 0 +#endif +#define HAS_DRMPRIME 1 + +#include + +#include +#include +#include + +#include "drmu.h" +#include "drmu_vlc_fmts.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Get cropping rectangle from a vlc format +static inline drmu_rect_t +drmu_rect_vlc_format_crop(const video_frame_format_t * const format) +{ + return (drmu_rect_t){ + .x = format->i_x_offset, + .y = format->i_y_offset, + .w = format->i_visible_width, + .h = format->i_visible_height}; +} + +// Get cropping rectangle from a vlc pic +static inline drmu_rect_t +drmu_rect_vlc_pic_crop(const picture_t * const pic) +{ + return drmu_rect_vlc_format_crop(&pic->format); +} + +// Get rect from vlc place +static inline drmu_rect_t +drmu_rect_vlc_place(const vout_display_place_t * const place) +{ + return (drmu_rect_t){ + .x = place->x, + .y = place->y, + .w = place->width, + .h = place->height + }; +} + +static inline vlc_rational_t +drmu_ufrac_vlc_to_rational(const drmu_ufrac_t x) +{ + return (vlc_rational_t) {.num = x.num, .den = x.den}; +} + +drmu_fb_t * drmu_fb_vlc_new_pic_attach(drmu_env_t * const du, picture_t * const pic); +plane_t drmu_fb_vlc_plane(drmu_fb_t * const dfb, const unsigned int plane_n); + +#if HAS_ZC_CMA +drmu_fb_t * drmu_fb_vlc_new_pic_cma_attach(drmu_env_t * const du, picture_t * const pic); +#endif + +// Copy properties like colour_space, hdr_metadata into the fb +void drmu_fb_vlc_pic_set_metadata(drmu_fb_t * const dfb, const picture_t * const pic); + +// Logging function callback for VLC +enum drmu_log_level_e; +void drmu_log_vlc_cb(void * v, enum drmu_log_level_e level_drmu, const char * fmt, va_list vl); + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/modules/video_output/drmu/drmu_vlc_fmts.c b/modules/video_output/drmu/drmu_vlc_fmts.c new file mode 100644 index 0000000000..3e12095b62 --- /dev/null +++ b/modules/video_output/drmu/drmu_vlc_fmts.c @@ -0,0 +1,246 @@ +#include "drmu_vlc_fmts.h" + +#include +#include + +#ifndef DRM_FORMAT_P030 +#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') +#endif + +#define DRMU_VLC_FMTS_FLAG_DRMP 1 +#define DRMU_VLC_FMTS_FLAG_ZC 2 +#define DRMU_VLC_FMTS_FLAG_FULL_RANGE 4 + +struct drmu_vlc_fmt_info_ss { + vlc_fourcc_t vlc_chroma; + uint32_t drm_pixelformat; + uint32_t rmask; + uint32_t gmask; + uint32_t bmask; + uint64_t drm_modifier; + unsigned int flags; +}; + +// N.B. DRM seems to order its format descriptor names the opposite way round to VLC +// DRM is hi->lo within a little-endian word, VLC is byte order + +#define I2(vlc, drm) {(vlc), (drm), 0, 0, 0, DRM_FORMAT_MOD_LINEAR, 0 } +#define RM(vlc, drm, r, g, b) {(vlc), (drm), (r), (g), (b), DRM_FORMAT_MOD_LINEAR, 0 } +#define R2(vlc, drm) RM((vlc), (drm), 0, 0, 0) + +static const drmu_vlc_fmt_info_t fmt_table[] = { + R2(VLC_CODEC_RGBA, DRM_FORMAT_ABGR8888), + R2(VLC_CODEC_BGRA, DRM_FORMAT_ARGB8888), + R2(VLC_CODEC_ARGB, DRM_FORMAT_BGRA8888), + // VLC_CODEC_ABGR does not exist in VLC + // AYUV appears to be the only DRM YUVA-like format + I2(VLC_CODEC_VUYA, DRM_FORMAT_AYUV), + I2(VLC_CODEC_VYUY, DRM_FORMAT_YUYV), + I2(VLC_CODEC_UYVY, DRM_FORMAT_YVYU), + I2(VLC_CODEC_YUYV, DRM_FORMAT_VYUY), + I2(VLC_CODEC_YVYU, DRM_FORMAT_UYVY), + I2(VLC_CODEC_NV12, DRM_FORMAT_NV12), + I2(VLC_CODEC_NV21, DRM_FORMAT_NV21), + I2(VLC_CODEC_NV16, DRM_FORMAT_NV16), + I2(VLC_CODEC_NV61, DRM_FORMAT_NV61), + I2(VLC_CODEC_NV24, DRM_FORMAT_NV24), + I2(VLC_CODEC_NV42, DRM_FORMAT_NV42), + I2(VLC_CODEC_P010, DRM_FORMAT_P010), + I2(VLC_CODEC_I420, DRM_FORMAT_YUV420), + { VLC_CODEC_J420, DRM_FORMAT_YUV420, 0, 0, 0, DRM_FORMAT_MOD_LINEAR, DRMU_VLC_FMTS_FLAG_FULL_RANGE }, + I2(VLC_CODEC_YV12, DRM_FORMAT_YVU420), + I2(VLC_CODEC_I422, DRM_FORMAT_YUV422), + { VLC_CODEC_J422, DRM_FORMAT_YUV422, 0, 0, 0, DRM_FORMAT_MOD_LINEAR, DRMU_VLC_FMTS_FLAG_FULL_RANGE }, + I2(VLC_CODEC_I444, DRM_FORMAT_YUV444), + { VLC_CODEC_J444, DRM_FORMAT_YUV444, 0, 0, 0, DRM_FORMAT_MOD_LINEAR, DRMU_VLC_FMTS_FLAG_FULL_RANGE }, +#if HAS_DRMPRIME + { VLC_CODEC_DRM_PRIME_I420, DRM_FORMAT_YUV420, 0, 0, 0, DRM_FORMAT_MOD_LINEAR, DRMU_VLC_FMTS_FLAG_DRMP }, + { VLC_CODEC_DRM_PRIME_NV12, DRM_FORMAT_NV12, 0, 0, 0, DRM_FORMAT_MOD_LINEAR, DRMU_VLC_FMTS_FLAG_DRMP }, + { VLC_CODEC_DRM_PRIME_SAND8, DRM_FORMAT_NV12, 0, 0, 0, DRM_FORMAT_MOD_BROADCOM_SAND128, DRMU_VLC_FMTS_FLAG_DRMP }, + { VLC_CODEC_DRM_PRIME_SAND30, DRM_FORMAT_P030, 0, 0, 0, DRM_FORMAT_MOD_BROADCOM_SAND128, DRMU_VLC_FMTS_FLAG_DRMP }, + { VLC_CODEC_DRM_PRIME_RGB32, DRM_FORMAT_XRGB8888, 0, 0, 0, DRM_FORMAT_MOD_LINEAR, DRMU_VLC_FMTS_FLAG_DRMP }, +#endif +#if HAS_ZC_CMA + { VLC_CODEC_MMAL_ZC_I420, DRM_FORMAT_YUV420, 0, 0, 0, DRM_FORMAT_MOD_LINEAR, DRMU_VLC_FMTS_FLAG_ZC }, + { VLC_CODEC_MMAL_ZC_SAND8, DRM_FORMAT_NV12, 0, 0, 0, DRM_FORMAT_MOD_BROADCOM_SAND128, DRMU_VLC_FMTS_FLAG_ZC }, + { VLC_CODEC_MMAL_ZC_SAND30, DRM_FORMAT_P030, 0, 0, 0, DRM_FORMAT_MOD_BROADCOM_SAND128, DRMU_VLC_FMTS_FLAG_ZC }, + { VLC_CODEC_MMAL_ZC_RGB32, DRM_FORMAT_RGBX8888, 0, 0, 0, DRM_FORMAT_MOD_LINEAR, DRMU_VLC_FMTS_FLAG_ZC }, +#endif + + RM(VLC_CODEC_RGB32, DRM_FORMAT_XRGB8888, 0xff0000, 0xff00, 0xff), + RM(VLC_CODEC_RGB32, DRM_FORMAT_XBGR8888, 0xff, 0xff00, 0xff0000), + RM(VLC_CODEC_RGB32, DRM_FORMAT_RGBX8888, 0xff000000, 0xff0000, 0xff00), + RM(VLC_CODEC_RGB32, DRM_FORMAT_BGRX8888, 0xff00, 0xff0000, 0xff000000), + // The 24-bit versions seem to have BE masks?! + RM(VLC_CODEC_RGB24, DRM_FORMAT_BGR888, 0xff0000, 0xff00, 0xff), + RM(VLC_CODEC_RGB24, DRM_FORMAT_RGB888, 0xff, 0xff00, 0xff0000), + RM(VLC_CODEC_RGB16, DRM_FORMAT_RGB565, 0xf800, 0x7e0, 0x1f), + RM(VLC_CODEC_RGB16, DRM_FORMAT_BGR565, 0x1f, 0x7e0, 0xf800), + + I2(0, 0) +}; +#undef I2 +#undef RM +#undef R2 + +// *** Sorted lookups? + +const drmu_vlc_fmt_info_t * +drmu_vlc_fmt_info_find_vlc_next(const video_frame_format_t * const vf_vlc, const drmu_vlc_fmt_info_t * f) +{ + f = (f == NULL) ? fmt_table : f + 1; + + for (; f->vlc_chroma != 0; ++f) + { + if (f->vlc_chroma != vf_vlc->i_chroma) + continue; + if (f->rmask != 0 && vf_vlc->i_rmask != 0 && + (f->rmask != vf_vlc->i_rmask || f->gmask != vf_vlc->i_gmask || f->bmask != vf_vlc->i_bmask)) + continue; + return f; + } + return NULL; +} + +const drmu_vlc_fmt_info_t * +drmu_vlc_fmt_info_find_vlc(const video_frame_format_t * const vf_vlc) +{ + return drmu_vlc_fmt_info_find_vlc_next(vf_vlc, NULL); +} + +// Remove any params from a modifier +static inline uint64_t canon_mod(const uint64_t m) +{ + return fourcc_mod_is_vendor(m, BROADCOM) ? fourcc_mod_broadcom_mod(m) : m; +} + +const drmu_vlc_fmt_info_t * +drmu_vlc_fmt_info_find_drm_next(const uint32_t pixelformat, const uint64_t modifier, const drmu_vlc_fmt_info_t * f) +{ + const uint64_t cmod = canon_mod(modifier); + + f = (f == NULL) ? fmt_table : f + 1; + + for (; f->vlc_chroma != 0; ++f) + { + if (f->drm_pixelformat != pixelformat || f->drm_modifier != cmod) + continue; + // Only return the "base" version + if (f->flags != 0) + continue; + return f; + } + return NULL; +} + +const drmu_vlc_fmt_info_t * +drmu_vlc_fmt_info_find_drm(const uint32_t pixelformat, const uint64_t modifier) +{ + return drmu_vlc_fmt_info_find_drm_next(pixelformat, modifier, NULL); +} + +vlc_fourcc_t +drmu_vlc_fmt_info_vlc_chroma(const drmu_vlc_fmt_info_t * const f) +{ + return f == NULL ? 0 : f->vlc_chroma; +} + +void +drmu_vlc_fmt_info_vlc_rgb_masks(const drmu_vlc_fmt_info_t * const f, uint32_t * r, uint32_t * g, uint32_t * b) +{ + if (f == NULL) + { + *r = 0; + *g = 0; + *b = 0; + return; + } + *r = f->rmask; + *g = f->gmask; + *b = f->bmask; +} + +uint32_t +drmu_vlc_fmt_info_drm_pixelformat(const drmu_vlc_fmt_info_t * const f) +{ + return f == NULL ? 0 : f->drm_pixelformat; +} + +uint64_t +drmu_vlc_fmt_info_drm_modifier(const drmu_vlc_fmt_info_t * const f) +{ + return f == NULL ? DRM_FORMAT_MOD_INVALID : f->drm_modifier; +} + +bool +drmu_vlc_fmt_info_is_drmprime(const drmu_vlc_fmt_info_t * const f) +{ +#if HAS_DRMPRIME + return f != NULL && (f->flags & DRMU_VLC_FMTS_FLAG_DRMP); +#else + (void)f; + return false; +#endif +} + +bool +drmu_vlc_fmt_info_is_zc_cma(const drmu_vlc_fmt_info_t * const f) +{ +#if HAS_ZC_CMA + return f != NULL && (f->flags & DRMU_VLC_FMTS_FLAG_ZC); +#else + (void)f; + return false; +#endif +} + + +uint32_t +drmu_format_vlc_to_drm(const video_frame_format_t * const vf_vlc, uint64_t * const pMod) +{ + const drmu_vlc_fmt_info_t * const f = drmu_vlc_fmt_info_find_vlc(vf_vlc); + + if (pMod) + *pMod = drmu_vlc_fmt_info_drm_modifier(f); + return drmu_vlc_fmt_info_drm_pixelformat(f); +} + +#if HAS_ZC_CMA +uint32_t +drmu_format_vlc_to_drm_cma(const video_frame_format_t * const vf_vlc, uint64_t * const pMod) +{ + const drmu_vlc_fmt_info_t * f = drmu_vlc_fmt_info_find_vlc(vf_vlc); + + if (!drmu_vlc_fmt_info_is_zc_cma(f)) + f = NULL; + + if (pMod) + *pMod = drmu_vlc_fmt_info_drm_modifier(f); + return drmu_vlc_fmt_info_drm_pixelformat(f); +} +#endif + +#if HAS_DRMPRIME +uint32_t +drmu_format_vlc_to_drm_prime(const video_frame_format_t * const vf_vlc, uint64_t * const pMod) +{ + const drmu_vlc_fmt_info_t * f = drmu_vlc_fmt_info_find_vlc(vf_vlc); + + if (!drmu_vlc_fmt_info_is_drmprime(f)) + f = NULL; + + if (pMod) + *pMod = drmu_vlc_fmt_info_drm_modifier(f); + return drmu_vlc_fmt_info_drm_pixelformat(f); +} +#endif + +// Convert chroma to drm - can't cope with RGB32 or RGB16 as they require +// more info +uint32_t +drmu_format_vlc_chroma_to_drm(const vlc_fourcc_t chroma) +{ + const drmu_vlc_fmt_info_t * f = drmu_vlc_fmt_info_find_vlc(&(const video_frame_format_t){.i_chroma = chroma}); + return drmu_vlc_fmt_info_drm_pixelformat(f); +} + diff --git a/modules/video_output/drmu/drmu_vlc_fmts.h b/modules/video_output/drmu/drmu_vlc_fmts.h new file mode 100644 index 0000000000..1e7fa4711c --- /dev/null +++ b/modules/video_output/drmu/drmu_vlc_fmts.h @@ -0,0 +1,57 @@ +#ifndef _DRMU_DRMU_VLC_FMTS_H +#define _DRMU_DRMU_VLC_FMTS_H + +#include "config.h" + +#ifndef HAS_ZC_CMA +#define HAS_ZC_CMA 0 +#endif +#define HAS_DRMPRIME 1 + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct drmu_vlc_fmt_info_ss; +typedef struct drmu_vlc_fmt_info_ss drmu_vlc_fmt_info_t; + +const drmu_vlc_fmt_info_t * drmu_vlc_fmt_info_find_vlc(const video_frame_format_t * const vf_vlc); +// f == NULL => start at the beginning +const drmu_vlc_fmt_info_t * drmu_vlc_fmt_info_find_vlc_next(const video_frame_format_t * const vf_vlc, const drmu_vlc_fmt_info_t * f); +const drmu_vlc_fmt_info_t * drmu_vlc_fmt_info_find_drm(const uint32_t pixelformat, const uint64_t modifier); +// f == NULL => start at the beginning +const drmu_vlc_fmt_info_t * drmu_vlc_fmt_info_find_drm_next(const uint32_t pixelformat, const uint64_t modifier, const drmu_vlc_fmt_info_t * f); + +vlc_fourcc_t drmu_vlc_fmt_info_vlc_chroma(const drmu_vlc_fmt_info_t * const f); +void drmu_vlc_fmt_info_vlc_rgb_masks(const drmu_vlc_fmt_info_t * const f, uint32_t * r, uint32_t * g, uint32_t * b); +uint32_t drmu_vlc_fmt_info_drm_pixelformat(const drmu_vlc_fmt_info_t * const f); +uint64_t drmu_vlc_fmt_info_drm_modifier(const drmu_vlc_fmt_info_t * const f); + +bool drmu_vlc_fmt_info_is_zc_cma(const drmu_vlc_fmt_info_t * const f); +bool drmu_vlc_fmt_info_is_drmprime(const drmu_vlc_fmt_info_t * const f); + + +// Convert chroma to drm - can't cope with RGB32 or RGB16 as they require +// more info. returns 0 if unknown. +uint32_t drmu_format_vlc_chroma_to_drm(const vlc_fourcc_t chroma); +// Convert format to drm fourcc - does cope with RGB32 & RGB16 +// pMod receives modifier - may be null +uint32_t drmu_format_vlc_to_drm(const video_frame_format_t * const vf_vlc, uint64_t * const pMod); + +#if HAS_DRMPRIME +// pmod may be NULL +uint32_t drmu_format_vlc_to_drm_prime(const video_frame_format_t * const vf_vlc, uint64_t * const pmod); +#endif +#if HAS_ZC_CMA +uint32_t drmu_format_vlc_to_drm_cma(const video_frame_format_t * const vf_vlc, uint64_t * const pMod); +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/modules/video_output/drmu/drmu_xlease.c b/modules/video_output/drmu/drmu_xlease.c new file mode 100644 index 0000000000..9bf2d7b25b --- /dev/null +++ b/modules/video_output/drmu/drmu_xlease.c @@ -0,0 +1,143 @@ +#include "drmu.h" +#include "drmu_log.h" + +#include +#include + +static int +get_lease_fd(const drmu_log_env_t * const log) +{ + xcb_generic_error_t *xerr; + + int screen = 0; + xcb_connection_t * const connection = xcb_connect(NULL, &screen); + if (!connection) { + drmu_warn_log(log, "Connection to X server failed"); + return -1; + } + + { + xcb_randr_query_version_cookie_t rqv_c = xcb_randr_query_version(connection, + XCB_RANDR_MAJOR_VERSION, + XCB_RANDR_MINOR_VERSION); + xcb_randr_query_version_reply_t *rqv_r = xcb_randr_query_version_reply(connection, rqv_c, NULL); + + if (!rqv_r) { + drmu_warn_log(log, "Failed to get XCB RandR version"); + return -1; + } + + uint32_t major = rqv_r->major_version; + uint32_t minor = rqv_r->minor_version; + free(rqv_r); + + if (minor < 6) { + drmu_warn_log(log, "XCB RandR version %d.%d too low for lease support", major, minor); + return -1; + } + } + + xcb_window_t root; + + { + xcb_screen_iterator_t s_i = xcb_setup_roots_iterator(xcb_get_setup(connection)); + int i; + + for (i = 0; i != screen && s_i.rem != 0; ++i) { + xcb_screen_next(&s_i); + } + + if (s_i.rem == 0) { + drmu_err_log(log, "Failed to get root for screen %d", screen); + return -1; + } + + drmu_debug_log(log, "index %d screen %d rem %d", s_i.index, screen, s_i.rem); + root = s_i.data->root; + } + + xcb_randr_output_t output = 0; + xcb_randr_crtc_t crtc = 0; + + /* Find a connected in-use output */ + { + xcb_randr_get_screen_resources_cookie_t gsr_c = xcb_randr_get_screen_resources(connection, root); + + xcb_randr_get_screen_resources_reply_t *gsr_r = xcb_randr_get_screen_resources_reply(connection, gsr_c, NULL); + int o; + + if (!gsr_r) { + drmu_err_log(log, "get_screen_resources failed"); + return -1; + } + + xcb_randr_output_t * const ro = xcb_randr_get_screen_resources_outputs(gsr_r); + + for (o = 0; output == 0 && o < gsr_r->num_outputs; o++) { + xcb_randr_get_output_info_cookie_t goi_c = xcb_randr_get_output_info(connection, ro[o], gsr_r->config_timestamp); + + xcb_randr_get_output_info_reply_t *goi_r = xcb_randr_get_output_info_reply(connection, goi_c, NULL); + + drmu_debug_log(log, "output[%d/%d] %d: conn %d/%d crtc %d", o, gsr_r->num_outputs, ro[o], goi_r->connection, XCB_RANDR_CONNECTION_CONNECTED, goi_r->crtc); + + /* Find the first connected and used output */ + if (goi_r->connection == XCB_RANDR_CONNECTION_CONNECTED && + goi_r->crtc != 0) { + output = ro[o]; + crtc = goi_r->crtc; + } + + free(goi_r); + } + + free(gsr_r); + + if (output == 0) { + drmu_warn_log(log, "Failed to find active output (outputs=%d)", o); + return -1; + } + } + + int fd = -1; + + { + xcb_randr_lease_t lease = xcb_generate_id(connection); + + xcb_randr_create_lease_cookie_t rcl_c = xcb_randr_create_lease(connection, + root, + lease, + 1, + 1, + &crtc, + &output); + xcb_randr_create_lease_reply_t *rcl_r = xcb_randr_create_lease_reply(connection, rcl_c, &xerr); + + if (!rcl_r) { + drmu_err_log(log, "create_lease failed: Xerror %d", xerr->error_code); + return -1; + } + + int *rcl_f = xcb_randr_create_lease_reply_fds(connection, rcl_r); + + fd = rcl_f[0]; + + free(rcl_r); + } + + drmu_debug_log(log, "%s OK: fd=%d", __func__, fd); + return fd; +} + +drmu_env_t * +drmu_env_new_xlease(const drmu_log_env_t * const log2) +{ + const struct drmu_log_env_s * const log = (log2 == NULL) ? &drmu_log_env_none : log2; + const int fd = get_lease_fd(log); + + if (fd == -1) { + drmu_err_log(log, "Failed to get xlease"); + return NULL; + } + return drmu_env_new_fd(fd, log); +} + diff --git a/modules/video_output/drmu/pollqueue.c b/modules/video_output/drmu/pollqueue.c new file mode 100644 index 0000000000..6cf81ba0fb --- /dev/null +++ b/modules/video_output/drmu/pollqueue.c @@ -0,0 +1,545 @@ +#include "pollqueue.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define request_log(...) fprintf(stderr, __VA_ARGS__) + +struct pollqueue; + +enum polltask_state { + POLLTASK_UNQUEUED = 0, + POLLTASK_QUEUED, + POLLTASK_RUNNING, + POLLTASK_Q_KILL, + POLLTASK_Q_DEAD, + POLLTASK_RUN_KILL, +}; + +#define POLLTASK_FLAG_ONCE 1 + +struct polltask { + struct polltask *next; + struct polltask *prev; + struct pollqueue *q; + enum polltask_state state; + + int fd; + short events; + unsigned short flags; + + void (*fn)(void *v, short revents); + void * v; + + uint64_t timeout; /* CLOCK_MONOTONIC time, 0 => never */ +}; + +struct pollqueue { + atomic_int ref_count; + pthread_mutex_t lock; + pthread_cond_t cond; + + struct polltask *head; + struct polltask *tail; + + struct prepost_ss { + void (*pre)(void *v, struct pollfd *pfd); + void (*post)(void *v, short revents); + void *v; + } prepost; + + void (* exit_fn)(void * v); + void * exit_v; + + bool kill; + bool join_req; // On thread exit do not detach + bool no_prod; + + bool sig_seq; // Signal cond when seq incremented + uint32_t seq; + + int prod_fd; + struct polltask *prod_pt; + pthread_t worker; +}; + +static struct polltask * +polltask_new2(struct pollqueue *const pq, + const int fd, const short events, + void (*const fn)(void *v, short revents), + void *const v, + const unsigned short flags) +{ + struct polltask *pt; + + if (!events && fd != -1) + return NULL; + + pt = malloc(sizeof(*pt)); + if (!pt) + return NULL; + + *pt = (struct polltask){ + .next = NULL, + .prev = NULL, + .q = pollqueue_ref(pq), + .fd = fd, + .events = events, + .flags = flags, + .fn = fn, + .v = v + }; + + return pt; +} + +struct polltask * +polltask_new(struct pollqueue *const pq, + const int fd, const short events, + void (*const fn)(void *v, short revents), + void *const v) +{ + return polltask_new2(pq, fd, events, fn, v, 0); +} + +struct polltask *polltask_new_timer(struct pollqueue *const pq, + void (*const fn)(void *v, short revents), + void *const v) +{ + return polltask_new(pq, -1, 0, fn, v); +} + +int +pollqueue_callback_once(struct pollqueue *const pq, + void (*const fn)(void *v, short revents), + void *const v) +{ + struct polltask * const pt = polltask_new2(pq, -1, 0, fn, v, POLLTASK_FLAG_ONCE); + if (pt == NULL) + return -EINVAL; + pollqueue_add_task(pt, 0); + return 0; +} + +static void pollqueue_rem_task(struct pollqueue *const pq, struct polltask *const pt) +{ + if (pt->prev) + pt->prev->next = pt->next; + else + pq->head = pt->next; + if (pt->next) + pt->next->prev = pt->prev; + else + pq->tail = pt->prev; + pt->next = NULL; + pt->prev = NULL; +} + +static void polltask_free(struct polltask * const pt) +{ + free(pt); +} + +static void polltask_kill(struct polltask * const pt) +{ + struct pollqueue * pq = pt->q; + polltask_free(pt); + pollqueue_unref(&pq); +} + +static void polltask_dead(struct polltask * const pt) +{ + pt->state = POLLTASK_Q_DEAD; + pthread_cond_broadcast(&pt->q->cond); +} + +static int pollqueue_prod(const struct pollqueue *const pq) +{ + static const uint64_t one = 1; + return write(pq->prod_fd, &one, sizeof(one)); +} + +void polltask_delete(struct polltask **const ppt) +{ + struct polltask *const pt = *ppt; + struct pollqueue * pq; + enum polltask_state state; + bool prodme; + bool inthread; + + if (!pt) + return; + + pq = pt->q; + inthread = pthread_equal(pthread_self(), pq->worker); + + pthread_mutex_lock(&pq->lock); + state = pt->state; + pt->state = inthread ? POLLTASK_RUN_KILL : POLLTASK_Q_KILL; + prodme = !pq->no_prod; + pthread_mutex_unlock(&pq->lock); + + switch (state) { + case POLLTASK_UNQUEUED: + *ppt = NULL; + polltask_kill(pt); + break; + + case POLLTASK_QUEUED: + case POLLTASK_RUNNING: + { + int rv = 0; + + if (inthread) { + // We are in worker thread - kill in main loop to avoid confusion or deadlock + *ppt = NULL; + break; + } + + if (prodme) + pollqueue_prod(pq); + + pthread_mutex_lock(&pq->lock); + while (rv == 0 && pt->state != POLLTASK_Q_DEAD) + rv = pthread_cond_wait(&pq->cond, &pq->lock); + pthread_mutex_unlock(&pq->lock); + + // Leave zapping the ref until we have DQed the PT as might well be + // legitimately used in it + *ppt = NULL; + polltask_kill(pt); + break; + } + default: + request_log("%s: Unexpected task state: %d\n", __func__, state); + *ppt = NULL; + break; + } +} + +static uint64_t pollqueue_now(int timeout) +{ + struct timespec now; + uint64_t now_ms; + + if (clock_gettime(CLOCK_MONOTONIC, &now)) + return 0; + now_ms = (now.tv_nsec / 1000000) + (uint64_t)now.tv_sec * 1000 + timeout; + return now_ms ? now_ms : (uint64_t)1; +} + +void pollqueue_add_task(struct polltask *const pt, const int timeout) +{ + bool prodme = false; + struct pollqueue * const pq = pt->q; + const uint64_t timeout_time = timeout < 0 ? 0 : pollqueue_now(timeout); + + pthread_mutex_lock(&pq->lock); + if (pt->state == POLLTASK_UNQUEUED || pt->state == POLLTASK_RUNNING) { + if (pq->tail) + pq->tail->next = pt; + else + pq->head = pt; + pt->prev = pq->tail; + pt->next = NULL; + pt->state = POLLTASK_QUEUED; + pt->timeout = timeout_time; + pq->tail = pt; + prodme = !pq->no_prod; + } + pthread_mutex_unlock(&pq->lock); + if (prodme) + pollqueue_prod(pq); +} + +static void *poll_thread(void *v) +{ + struct pollqueue *const pq = v; + + pthread_mutex_lock(&pq->lock); + do { + struct pollfd a[POLLQUEUE_MAX_QUEUE]; + unsigned int i, j; + unsigned int nall = 0; + unsigned int npoll = 0; + struct polltask *pt; + struct polltask *pt_next; + struct prepost_ss prepost; + uint64_t now = pollqueue_now(0); + int timeout = -1; + int rv; + + for (pt = pq->head; pt; pt = pt_next) { + int64_t t; + + pt_next = pt->next; + + if (pt->state == POLLTASK_Q_KILL) { + pollqueue_rem_task(pq, pt); + polltask_dead(pt); + continue; + } + if (pt->state == POLLTASK_RUN_KILL) { + pollqueue_rem_task(pq, pt); + polltask_kill(pt); + continue; + } + + if (pt->fd != -1) { + assert(npoll < POLLQUEUE_MAX_QUEUE - 1); // Allow for pre/post + a[npoll++] = (struct pollfd){ + .fd = pt->fd, + .events = pt->events + }; + } + + t = (int64_t)(pt->timeout - now); + if (pt->timeout && t < INT_MAX && + (timeout < 0 || (int)t < timeout)) + timeout = (t < 0) ? 0 : (int)t; + ++nall; + } + prepost = pq->prepost; + pthread_mutex_unlock(&pq->lock); + + a[npoll] = (struct pollfd){.fd=-1, .events=0, .revents=0}; + if (prepost.pre) + prepost.pre(prepost.v, a + npoll); + + while ((rv = poll(a, npoll + (a[npoll].fd != -1), timeout)) == -1) + { + if (errno != EINTR) + break; + } + + if (prepost.post) + prepost.post(prepost.v, a[npoll].revents); + + if (rv == -1) { + request_log("Poll error: %s\n", strerror(errno)); + goto fail_unlocked; + } + + now = pollqueue_now(0); + + pthread_mutex_lock(&pq->lock); + /* Prodding in this loop is pointless and might lead to + * infinite looping + */ + pq->no_prod = true; + + // Sync for prepost changes + ++pq->seq; + if (pq->sig_seq) { + pq->sig_seq = false; + pthread_cond_broadcast(&pq->cond); + } + + for (i = 0, j = 0, pt = pq->head; i < nall; ++i, pt = pt_next) { + const short r = pt->fd == -1 ? 0 : a[j++].revents; + pt_next = pt->next; + + if (pt->state != POLLTASK_QUEUED) + continue; + + /* Pending? */ + if (r || (pt->timeout && (int64_t)(now - pt->timeout) >= 0)) { + pollqueue_rem_task(pq, pt); + pt->state = POLLTASK_RUNNING; + pthread_mutex_unlock(&pq->lock); + + /* This can add new entries to the Q but as + * those are added to the tail our existing + * chain remains intact + */ + pt->fn(pt->v, r); + + pthread_mutex_lock(&pq->lock); + if (pt->state == POLLTASK_Q_KILL) + polltask_dead(pt); + else if (pt->state == POLLTASK_RUN_KILL || + (pt->flags & POLLTASK_FLAG_ONCE) != 0) + polltask_kill(pt); + else if (pt->state == POLLTASK_RUNNING) + pt->state = POLLTASK_UNQUEUED; + } + } + pq->no_prod = false; + + } while (!pq->kill); + + pthread_mutex_unlock(&pq->lock); +fail_unlocked: + + { + void (*const exit_fn)(void *v) = pq->exit_fn; + void * const exit_v = pq->exit_v; + + polltask_free(pq->prod_pt); + pthread_cond_destroy(&pq->cond); + pthread_mutex_destroy(&pq->lock); + close(pq->prod_fd); + if (!pq->join_req) + pthread_detach(pthread_self()); + free(pq); + + if (exit_fn) + exit_fn(exit_v); + } + + return NULL; +} + +static void prod_fn(void *v, short revents) +{ + struct pollqueue *const pq = v; + char buf[8]; + if (revents) + read(pq->prod_fd, buf, 8); + if (!pq->kill) + pollqueue_add_task(pq->prod_pt, -1); +} + +struct pollqueue * pollqueue_new(void) +{ + struct pollqueue *pq = malloc(sizeof(*pq)); + if (!pq) + return NULL; + *pq = (struct pollqueue){ + .ref_count = ATOMIC_VAR_INIT(0), + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .head = NULL, + .tail = NULL, + .kill = false, + .prod_fd = -1 + }; + + pq->prod_fd = eventfd(0, EFD_NONBLOCK); + if (pq->prod_fd == -1) + goto fail1; + pq->prod_pt = polltask_new(pq, pq->prod_fd, POLLIN, prod_fn, pq); + if (!pq->prod_pt) + goto fail2; + pollqueue_add_task(pq->prod_pt, -1); + if (pthread_create(&pq->worker, NULL, poll_thread, pq)) + goto fail3; + // Reset ref count which will have been inced by the add_task + atomic_store(&pq->ref_count, 0); + return pq; + +fail3: + polltask_free(pq->prod_pt); +fail2: + close(pq->prod_fd); +fail1: + free(pq); + return NULL; +} + +static void pollqueue_free(struct pollqueue *const pq) +{ + const pthread_t worker = pq->worker; + + if (pthread_equal(worker, pthread_self())) { + pq->kill = true; + if (!pq->no_prod) + pollqueue_prod(pq); + } + else + { + pthread_mutex_lock(&pq->lock); + pq->kill = true; + // Must prod inside lock here as otherwise there is a potential race + // where the worker terminates and pq is freed before the prod + if (!pq->no_prod) + pollqueue_prod(pq); + pthread_mutex_unlock(&pq->lock); + } +} + +struct pollqueue * pollqueue_ref(struct pollqueue *const pq) +{ + atomic_fetch_add(&pq->ref_count, 1); + return pq; +} + +void pollqueue_unref(struct pollqueue **const ppq) +{ + struct pollqueue * const pq = *ppq; + + if (!pq) + return; + *ppq = NULL; + + if (atomic_fetch_sub(&pq->ref_count, 1) != 0) + return; + + pollqueue_free(pq); +} + +void pollqueue_finish(struct pollqueue **const ppq) +{ + struct pollqueue * pq = *ppq; + pthread_t worker; + + if (!pq) + return; + + pq->join_req = true; + worker = pq->worker; + + pollqueue_unref(&pq); + + pthread_join(worker, NULL); + + // Delay zapping the ref until after the join as it is legit for the + // remaining active polltasks to use it. + *ppq = NULL; +} + +void pollqueue_set_pre_post(struct pollqueue *const pq, + void (*fn_pre)(void *v, struct pollfd *pfd), + void (*fn_post)(void *v, short revents), + void *v) +{ + bool no_prod; + + pthread_mutex_lock(&pq->lock); + pq->prepost.pre = fn_pre; + pq->prepost.post = fn_post; + pq->prepost.v = v; + no_prod = pq->no_prod; + + if (!no_prod) { + const uint32_t seq = pq->seq; + int rv = 0; + + pollqueue_prod(pq); + + pq->sig_seq = true; + while (rv == 0 && pq->seq == seq) + rv = pthread_cond_wait(&pq->cond, &pq->lock); + } + pthread_mutex_unlock(&pq->lock); +} + +void pollqueue_set_exit(struct pollqueue *const pq, + void (* const exit_fn)(void * v), void * v) +{ + pq->exit_fn = exit_fn; + pq->exit_v = v; +} + diff --git a/modules/video_output/drmu/pollqueue.h b/modules/video_output/drmu/pollqueue.h new file mode 100644 index 0000000000..dd9c22fe5e --- /dev/null +++ b/modules/video_output/drmu/pollqueue.h @@ -0,0 +1,87 @@ +#ifndef POLLQUEUE_H_ +#define POLLQUEUE_H_ + +#include + +struct polltask; +struct pollqueue; + +// Max number of tasks that can be Qed +#define POLLQUEUE_MAX_QUEUE 128 + +// Create a new polltask +// Holds a reference on the pollqueue until the polltask is deleted +// +// pq pollqueue this task belongs to +// fd fd to poll +// events Events to wait for (POLLxxx) +// revents Event that triggered the callback +// 0 => timeout +// v User pointer to callback +struct polltask *polltask_new(struct pollqueue *const pq, + const int fd, const short events, + void (*const fn)(void *v, short revents), + void *const v); +// polltask suitable for timing (i.e. has no trigger event) +struct polltask *polltask_new_timer(struct pollqueue *const pq, + void (*const fn)(void *v, short revents), + void *const v); + +// deletes the task +// Safe to call if *ppt == NULL +// It is safe to call whilst a polltask is queued (and may be triggered) +// Callback may occur whilst this is in progress but will not occur +// once it is done. (*ppt is nulled only once the callback can not occur) +// May be called in a polltask callback +// If called from outside the polltask thread and this causes the pollqueue +// to be deleted then it will wait for the polltask thread to terminate +// before returning. +void polltask_delete(struct polltask **const ppt); + +// Queue a polltask +// timeout_ms == -1 => never +// May be called from the polltask callback +// May only be added once (currently) +void pollqueue_add_task(struct polltask *const pt, const int timeout); + +// Run a callback once on the poll thread +int pollqueue_callback_once(struct pollqueue *const pq, + void (*const fn)(void *v, short revents), + void *const v); + +// Create a pollqueue +// Generates a new thread to do the polltask callbacks +struct pollqueue * pollqueue_new(void); + +// Unref a pollqueue +// Will be deleted once all polltasks (Qed or otherwise) are deleted too +// Will not wait for polltask termination whether or not this is the last +// ref. +void pollqueue_unref(struct pollqueue **const ppq); + +// Unrefs a pollqueue and then waits for the polltask thread to terminate +// before returning. *ppq is not set to NULL until after the polltask thread +// has terminated so it is safe for use by any remaining polltasks (e.g. for +// creating other tasks that need to complete before finishing.) +void pollqueue_finish(struct pollqueue **const ppq); + +// Add a reference to a pollqueue +struct pollqueue * pollqueue_ref(struct pollqueue *const pq); + +// Set pre & post poll functions +// Both set s.t. there is always matched pre - poll - post even when +// changing the fns. +// pollfd will be added to polls if *pfd set (leave unset if not wanted) +// One or both of pre/post may be 0 (uncalled) +// Will wait if needed until the poll thread is not in the old pre/poll/post +// sequence +void pollqueue_set_pre_post(struct pollqueue *const pq, + void (*fn_pre)(void *v, struct pollfd *pfd), + void (*fn_post)(void *v, short revents), + void *v); + +// Set callback to execture on the poll thread when it exits +void pollqueue_set_exit(struct pollqueue *const pq, + void (* const exit_fn)(void * v), void * v); + +#endif /* POLLQUEUE_H_ */ diff --git a/modules/video_output/opengl/display.c b/modules/video_output/opengl/display.c index ec671109bc..46f693a13a 100644 --- a/modules/video_output/opengl/display.c +++ b/modules/video_output/opengl/display.c @@ -48,7 +48,7 @@ vlc_module_begin () # define MODULE_VARNAME "gles2" set_shortname (N_("OpenGL ES2")) set_description (N_("OpenGL for Embedded Systems 2 video output")) - set_capability ("vout display", 265) + set_capability ("vout display", 271) set_callbacks (Open, Close) add_shortcut ("opengles2", "gles2") add_module ("gles2", "opengl es2", NULL, diff --git a/modules/video_output/opengl/egl.c b/modules/video_output/opengl/egl.c index ab20fb4c9c..dbff2e5efe 100644 --- 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; @@ -58,6 +60,7 @@ typedef struct vlc_gl_sys_t #endif PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; } vlc_gl_sys_t; static int MakeCurrent (vlc_gl_t *gl) @@ -129,6 +132,15 @@ static bool DestroyImageKHR(vlc_gl_t *gl, void *image) return sys->eglDestroyImageKHR(sys->display, image); } +static bool QueryDmaBufModifiersEXT(vlc_gl_t *gl, uint32_t format, + unsigned int max_modifiers, uint64_t *modifiers, + unsigned int *external_only, int32_t *num_modifiers) +{ + vlc_gl_sys_t *sys = gl->sys; + + return sys->eglQueryDmaBufModifiersEXT(sys->display, format, max_modifiers, modifiers, external_only, num_modifiers); +} + static bool CheckToken(const char *haystack, const char *needle) { size_t len = strlen(needle); @@ -371,6 +383,14 @@ static int Open (vlc_object_t *obj, const struct gl_api *api) 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, @@ -427,6 +447,9 @@ static int Open (vlc_object_t *obj, const struct gl_api *api) gl->egl.createImageKHR = CreateImageKHR; gl->egl.destroyImageKHR = DestroyImageKHR; } + sys->eglQueryDmaBufModifiersEXT = (void *)eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + if (sys->eglQueryDmaBufModifiersEXT) + gl->egl.queryDmaBufModifiersEXT = QueryDmaBufModifiersEXT; return VLC_SUCCESS; @@ -463,7 +486,7 @@ vlc_module_begin () add_shortcut ("egl") add_submodule () - set_capability ("opengl es2", 50) + set_capability ("opengl es2", 51) set_callbacks (OpenGLES2, Close) add_shortcut ("egl") diff --git a/modules/video_output/wayland/dmabuf_alloc.c b/modules/video_output/wayland/dmabuf_alloc.c new file mode 100644 index 0000000000..b8442c08c7 --- /dev/null +++ b/modules/video_output/wayland/dmabuf_alloc.c @@ -0,0 +1,516 @@ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dmabuf_alloc.h" + +#define DMABUF_NAME1 "/dev/dma_heap/linux,cma" +#define DMABUF_NAME2 "/dev/dma_heap/reserved" + +#define TRACE_ALLOC 0 + +#define request_log(...) +#define request_debug(...) + +struct dmabufs_ctl; +struct dmabuf_h; + +struct dmabuf_fns { + int (*buf_alloc)(struct dmabufs_ctl * dbsc, struct dmabuf_h * dh, size_t size); + void (*buf_free)(struct dmabuf_h * dh); + int (*ctl_new)(struct dmabufs_ctl * dbsc); + void (*ctl_free)(struct dmabufs_ctl * dbsc); +}; + +struct dmabufs_ctl { + atomic_int ref_count; + int fd; + size_t page_size; + void * v; + const struct dmabuf_fns * fns; +}; + +#define DH_FLAG_FAKE 1 + +struct dmabuf_h { + atomic_int ref_count; + int fd; + size_t size; + size_t len; + void * mapptr; + void * v; + const struct dmabuf_fns * fns; + unsigned int flags; + + void * predel_v; + int (* predel_fn)(struct dmabuf_h * dh, void * v); +}; + +#if TRACE_ALLOC +static unsigned int total_bufs = 0; +static size_t total_size = 0; +#endif + +struct dmabuf_h * dmabuf_import_mmap(void * mapptr, size_t size) +{ + struct dmabuf_h *dh; + + if (mapptr == MAP_FAILED) + return NULL; + + dh = malloc(sizeof(*dh)); + if (!dh) + return NULL; + + *dh = (struct dmabuf_h) { + .fd = -1, + .size = size, + .mapptr = mapptr, + .flags = DH_FLAG_FAKE, + }; + + return dh; +} + +struct dmabuf_h * dmabuf_import(int fd, size_t size) +{ + struct dmabuf_h *dh; + + fd = dup(fd); + if (fd < 0 || size == 0) + return NULL; + + dh = malloc(sizeof(*dh)); + if (!dh) { + close(fd); + return NULL; + } + + *dh = (struct dmabuf_h) { + .fd = fd, + .size = size, + .mapptr = MAP_FAILED + }; + +#if TRACE_ALLOC + ++total_bufs; + total_size += dh->size; + request_log("%s: Import: %zd, total=%zd, bufs=%d\n", __func__, dh->size, total_size, total_bufs); +#endif + + return dh; +} + +void dmabuf_free(struct dmabuf_h * dh) +{ + if (!dh) + return; + +#if TRACE_ALLOC + --total_bufs; + total_size -= dh->size; + request_log("%s: Free: %zd, total=%zd, bufs=%d\n", __func__, dh->size, total_size, total_bufs); +#endif + + dh->fns->buf_free(dh); + + if (dh->mapptr != MAP_FAILED && dh->mapptr != NULL) + munmap(dh->mapptr, dh->size); + if (dh->fd != -1) + while (close(dh->fd) == -1 && errno == EINTR) + /* loop */; + free(dh); +} + +void dmabuf_unref(struct dmabuf_h ** const ppdh) +{ + struct dmabuf_h * dh = *ppdh; + int n; + + if (dh == NULL) + return; + *ppdh = NULL; + + n = atomic_fetch_sub(&dh->ref_count, 1); +// fprintf(stderr, "%s[%p]: Ref: %d\n", __func__, dh, n); + if (n != 0) + return; + + if (dh->predel_fn && dh->predel_fn(dh, dh->predel_v) != 0) + return; + + dmabuf_free(dh); +} + +struct dmabuf_h * dmabuf_ref(struct dmabuf_h * const dh) +{ + if (dh != NULL) + { + int n = atomic_fetch_add(&dh->ref_count, 1); +// fprintf(stderr, "%s[%p]: Ref: %d\n", __func__, dh, n); + (void)n; + } + return dh; +} + +void dmabuf_predel_cb_set(struct dmabuf_h * const dh, + int (* const predel_fn)(struct dmabuf_h * dh, void * v), void * const predel_v) +{ + dh->predel_fn = predel_fn; + dh->predel_v = predel_v; +} + +struct dmabuf_h * dmabuf_realloc(struct dmabufs_ctl * dbsc, struct dmabuf_h * old, size_t size) +{ + struct dmabuf_h * dh; + if (old != NULL) { + if (old->size >= size) { + return old; + } + dmabuf_free(old); + } + + if (size == 0 || + (dh = malloc(sizeof(*dh))) == NULL) + return NULL; + + *dh = (struct dmabuf_h){ + .fd = -1, + .mapptr = MAP_FAILED, + .fns = dbsc->fns + }; + + if (dh->fns->buf_alloc(dbsc, dh, size) != 0) + goto fail; + + +#if TRACE_ALLOC + ++total_bufs; + total_size += dh->size; + request_log("%s: Alloc: %zd, total=%zd, bufs=%d\n", __func__, dh->size, total_size, total_bufs); +#endif + + return dh; + +fail: + free(dh); + return NULL; +} + +int dmabuf_sync(struct dmabuf_h * const dh, unsigned int flags) +{ + struct dma_buf_sync sync = { + .flags = flags + }; + if ((dh->flags & DH_FLAG_FAKE) != 0) + return 0; + while (ioctl(dh->fd, DMA_BUF_IOCTL_SYNC, &sync) == -1) { + const int err = errno; + if (errno == EINTR) + continue; + request_log("%s: ioctl failed: flags=%#x\n", __func__, flags); + return -err; + } + return 0; +} + +int dmabuf_write_start(struct dmabuf_h * const dh) +{ + return dmabuf_sync(dh, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE); +} + +int dmabuf_write_end(struct dmabuf_h * const dh) +{ + return dmabuf_sync(dh, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE); +} + +int dmabuf_read_start(struct dmabuf_h * const dh) +{ + if (!dmabuf_map(dh)) + return -1; + return dmabuf_sync(dh, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ); +} + +int dmabuf_read_end(struct dmabuf_h * const dh) +{ + return dmabuf_sync(dh, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ); +} + + +void * dmabuf_map(struct dmabuf_h * const dh) +{ + if (!dh) + return NULL; + if (dh->mapptr != MAP_FAILED) + return dh->mapptr; + dh->mapptr = mmap(NULL, dh->size, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, + dh->fd, 0); + if (dh->mapptr == MAP_FAILED) { + request_log("%s: Map failed\n", __func__); + return NULL; + } +// fprintf(stderr, "map to %p\n", dh->mapptr); + return dh->mapptr; +} + +int dmabuf_fd(const struct dmabuf_h * const dh) +{ + if (!dh) + return -1; + return dh->fd; +} + +size_t dmabuf_size(const struct dmabuf_h * const dh) +{ + if (!dh) + return 0; + return dh->size; +} + +size_t dmabuf_len(const struct dmabuf_h * const dh) +{ + if (!dh) + return 0; + return dh->len; +} + +void dmabuf_len_set(struct dmabuf_h * const dh, const size_t len) +{ + dh->len = len; +} + +bool dmabuf_is_fake(const struct dmabuf_h * const dh) +{ + return (dh->flags & DH_FLAG_FAKE) != 0; +} + +static struct dmabufs_ctl * dmabufs_ctl_new2(const struct dmabuf_fns * const fns) +{ + struct dmabufs_ctl * dbsc = calloc(1, sizeof(*dbsc)); + + if (!dbsc) + return NULL; + + dbsc->fd = -1; + dbsc->fns = fns; + + dbsc->page_size = (size_t)sysconf(_SC_PAGE_SIZE); + // Check page size for plausability & power of 2 - set to 4k if not + if (dbsc->page_size < 0x1000 || dbsc->page_size > 0x1000000 || + (dbsc->page_size & (dbsc->page_size - 1)) != 0) + dbsc->page_size = 0x1000; + + if (fns->ctl_new(dbsc) != 0) + goto fail; + + return dbsc; + +fail: + free(dbsc); + return NULL; +} + +static void dmabufs_ctl_free(struct dmabufs_ctl * const dbsc) +{ + request_debug(NULL, "Free dmabuf ctl\n"); + + dbsc->fns->ctl_free(dbsc); + + free(dbsc); +} + +void dmabufs_ctl_unref(struct dmabufs_ctl ** const pDbsc) +{ + struct dmabufs_ctl * const dbsc = *pDbsc; + + if (!dbsc) + return; + *pDbsc = NULL; + + if (atomic_fetch_sub(&dbsc->ref_count, 1) != 0) + return; + + dmabufs_ctl_free(dbsc); +} + +struct dmabufs_ctl * dmabufs_ctl_ref(struct dmabufs_ctl * const dbsc) +{ + atomic_fetch_add(&dbsc->ref_count, 1); + return dbsc; +} + +//----------------------------------------------------------------------------- +// +// Alloc dmabuf via CMA + +static int ctl_cma_new2(struct dmabufs_ctl * dbsc, const char * const * names) +{ + for (; *names != NULL; ++names) + { + while ((dbsc->fd = open(*names, O_RDWR | __O_CLOEXEC)) == -1 && + errno == EINTR) + /* Loop */; + if (dbsc->fd != -1) + return 0; + } + request_log("Unable to open any dma_heap device\n"); + return -1; +} + +static int ctl_cma_new(struct dmabufs_ctl * dbsc) +{ + static const char * const names[] = { + "/dev/dma_heap/vidbuf_cached", + "/dev/dma_heap/linux,cma", + "/dev/dma_heap/reserved", + NULL + }; + + return ctl_cma_new2(dbsc, names); +} + +static void ctl_cma_free(struct dmabufs_ctl * dbsc) +{ + if (dbsc->fd != -1) + while (close(dbsc->fd) == -1 && errno == EINTR) + /* loop */; + +} + +static int buf_cma_alloc(struct dmabufs_ctl * const dbsc, struct dmabuf_h * dh, size_t size) +{ + struct dma_heap_allocation_data data = { + .len = (size + dbsc->page_size - 1) & ~(dbsc->page_size - 1), + .fd = 0, + .fd_flags = O_RDWR, + .heap_flags = 0 + }; + + while (ioctl(dbsc->fd, DMA_HEAP_IOCTL_ALLOC, &data)) { + int err = errno; + request_log("Failed to alloc %" PRIu64 " from dma-heap(fd=%d): %d (%s)\n", + (uint64_t)data.len, + dbsc->fd, + err, + strerror(err)); + if (err == EINTR) + continue; + return -err; + } + + dh->fd = data.fd; + dh->size = (size_t)data.len; + +// fprintf(stderr, "%s: size=%#zx, ftell=%#zx\n", __func__, +// dh->size, (size_t)lseek(dh->fd, 0, SEEK_END)); + + return 0; +} + +static void buf_cma_free(struct dmabuf_h * dh) +{ + (void)dh; + // Nothing needed +} + +static const struct dmabuf_fns dmabuf_cma_fns = { + .buf_alloc = buf_cma_alloc, + .buf_free = buf_cma_free, + .ctl_new = ctl_cma_new, + .ctl_free = ctl_cma_free, +}; + +struct dmabufs_ctl * dmabufs_ctl_new(void) +{ + request_debug(NULL, "Dmabufs using CMA\n");; + return dmabufs_ctl_new2(&dmabuf_cma_fns); +} + +//----------------------------------------------------------------------------- +// +// Alloc "dmabuf" via shm (one file per alloc) + +static int ctl_shm_new(struct dmabufs_ctl * dbsc) +{ + (void)dbsc; + return 0; +} + +static void ctl_shm_free(struct dmabufs_ctl * dbsc) +{ + (void)dbsc; +} + +static int buf_shm_alloc(struct dmabufs_ctl * const dbsc, struct dmabuf_h * dh, size_t size) +{ + int fd; + +#if 0 + const char * const tmpdir = "/tmp"; + fd = open(tmpdir, __O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); + if (fd == -1) { + const int err = errno; + request_log("Failed to open tmp file in %s: %s\n", tmpdir, strerror(err)); + return -err; + } +#else + fd = memfd_create("vlc/shm_buf", 0); + if (fd == -1) { + const int err = errno; + request_log("Failed to create memfd: %s\n", strerror(err)); + return -err; + } +#endif + + // Round up to page size + size = (size + dbsc->page_size - 1) & ~(dbsc->page_size - 1); + + if (ftruncate(fd, (off_t)size) != 0) + { + const int err = errno; + request_log("Failed to extend tmp file to %zd: %s\n", size, strerror(err)); + return -err; + } + + dh->fd = fd; + dh->size = size; + dh->flags = DH_FLAG_FAKE; + +// fprintf(stderr, "%s: size=%#zx, ftell=%#zx\n", __func__, +// dh->size, (size_t)lseek(dh->fd, 0, SEEK_END)); + + return 0; +} + +static void buf_shm_free(struct dmabuf_h * dh) +{ + (void)dh; + // Nothing needed +} + +static const struct dmabuf_fns dmabuf_shm_fns = { + .buf_alloc = buf_shm_alloc, + .buf_free = buf_shm_free, + .ctl_new = ctl_shm_new, + .ctl_free = ctl_shm_free, +}; + +struct dmabufs_ctl * dmabufs_shm_new() +{ + request_debug(NULL, "Dmabufs using SHM\n");; + return dmabufs_ctl_new2(&dmabuf_shm_fns); +} + + diff --git a/modules/video_output/wayland/dmabuf_alloc.h b/modules/video_output/wayland/dmabuf_alloc.h new file mode 100644 index 0000000000..9757886d0e --- /dev/null +++ b/modules/video_output/wayland/dmabuf_alloc.h @@ -0,0 +1,59 @@ +#ifndef _WAYLAND_DMABUF_ALLOC_H +#define _WAYLAND_DMABUF_ALLOC_H + +#include +#include + +struct dmabufs_ctl; +struct dmabuf_h; + +struct dmabufs_ctl * dmabufs_ctl_new(void); +void dmabufs_ctl_unref(struct dmabufs_ctl ** const pdbsc); +struct dmabufs_ctl * dmabufs_ctl_ref(struct dmabufs_ctl * const dbsc); + +// Build a "dmabuf" struct that uses ordinary shared memory +struct dmabufs_ctl * dmabufs_shm_new(void); + + +// Need not preserve old contents +// On NULL return old buffer is freed +struct dmabuf_h * dmabuf_realloc(struct dmabufs_ctl * dbsc, struct dmabuf_h *, size_t size); + +static inline struct dmabuf_h * dmabuf_alloc(struct dmabufs_ctl * dbsc, size_t size) { + return dmabuf_realloc(dbsc, NULL, size); +} +/* Create from existing fd - dups(fd) */ +struct dmabuf_h * dmabuf_import(int fd, size_t size); +/* Import an MMAP - return NULL if mapptr = MAP_FAIL */ +struct dmabuf_h * dmabuf_import_mmap(void * mapptr, size_t size); + +void * dmabuf_map(struct dmabuf_h * const dh); + +/* flags from linux/dmabuf.h DMA_BUF_SYNC_xxx */ +int dmabuf_sync(struct dmabuf_h * const dh, unsigned int flags); + +int dmabuf_write_start(struct dmabuf_h * const dh); +int dmabuf_write_end(struct dmabuf_h * const dh); +int dmabuf_read_start(struct dmabuf_h * const dh); +int dmabuf_read_end(struct dmabuf_h * const dh); + +int dmabuf_fd(const struct dmabuf_h * const dh); +/* Allocated size */ +size_t dmabuf_size(const struct dmabuf_h * const dh); +/* Bytes in use */ +size_t dmabuf_len(const struct dmabuf_h * const dh); +/* Set bytes in use */ +void dmabuf_len_set(struct dmabuf_h * const dh, const size_t len); +/* Are these real dmabufs (false) or is this just something else mmapable (true) */ +bool dmabuf_is_fake(const struct dmabuf_h * const dh); + +void dmabuf_predel_cb_set(struct dmabuf_h * const dh, + int (* const predel_fn)(struct dmabuf_h * dh, void * v), void * const predel_v); +static inline void dmabuf_predel_cb_unset(struct dmabuf_h * const dh) {dmabuf_predel_cb_set(dh, 0, NULL);} + +void dmabuf_unref(struct dmabuf_h ** const ppdh); +struct dmabuf_h * dmabuf_ref(struct dmabuf_h * const dh); + +void dmabuf_free(struct dmabuf_h * dh); + +#endif diff --git a/modules/video_output/wayland/picpool.c b/modules/video_output/wayland/picpool.c new file mode 100644 index 0000000000..78d753759a --- /dev/null +++ b/modules/video_output/wayland/picpool.c @@ -0,0 +1,369 @@ +#include +#include +#include + +#include +#include + +#include "picpool.h" +#include "dmabuf_alloc.h" + +// =========================================================================== + +typedef struct pool_ent_s +{ + struct pool_ent_s * next; + struct pool_ent_s * prev; + + atomic_int ref_count; + unsigned int seq; + + struct picpool_ctl_s * pc; + struct dmabuf_h * db; + + size_t size; + unsigned int width; + unsigned int height; +} 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 picpool_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; + struct dmabufs_ctl * dbsc; +}; + + +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 buffer - kill it now + dmabuf_free(ent->db); + 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); + } +} + +#if 0 +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; +} +#endif + +#if 0 +// 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; +} +#endif + +#define POOL_ENT_ALLOC_BLOCK 0x10000 + +static pool_ent_t * pool_ent_alloc_new(picpool_ctl_t * const pc, 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 + if ((ent->db = dmabuf_realloc(pc->dbsc, NULL, alloc_size)) == NULL) + goto fail1; +// fprintf(stderr, "%s: ent %p db %p req=%zd size=%zd\n", __func__, ent, ent->db, req_size, alloc_size); + ent->size = dmabuf_size(ent->db); + return ent; + +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(picpool_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; + +// fprintf(stderr, "%s: Pool: %p: Ent: %p: %d dh: %p\n", __func__, &pc->ent_pool, ent, n, ent->db); + + if (n != 0) + return; + + 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(picpool_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 int pool_predel_cb(struct dmabuf_h * dh, void * v) +{ + pool_ent_t * const ent = v; + picpool_ctl_t * pc = ent->pc; + + assert(ent->db == dh); + + ent->pc = NULL; + dmabuf_ref(dh); + dmabuf_predel_cb_unset(dh); + pool_recycle(pc, ent); + picpool_unref(&pc); + return 1; // Do not delete +} + +struct dmabuf_h * picpool_get(picpool_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) { + if ((best = pool_ent_alloc_new(pc, req_size)) == NULL) + return NULL; + } + + if ((best->seq = ++pc->seq) == 0) + best->seq = ++pc->seq; // Never allow to be zero + + atomic_store(&best->ref_count, 1); + best->pc = picpool_ref(pc); + dmabuf_predel_cb_set(best->db, pool_predel_cb, best); +// fprintf(stderr, "%s: find ent %p db %p size %zd\n", __func__, best, best->db, best->size); + return best->db; +} + +void picpool_flush(picpool_ctl_t * const pc) +{ + pool_recycle_list(pc, &pc->ents_prev); + pool_recycle_list(pc, &pc->ents_cur); +} + +static void picpool_delete(picpool_ctl_t * const pc) +{ + +// printf("<<< %s\n", __func__); + + picpool_flush(pc); + + ent_free_list(&pc->ent_pool); + + dmabufs_ctl_unref(&pc->dbsc); + + vlc_mutex_destroy(&pc->lock); + +// memset(pc, 0xba, sizeof(*pc)); // Zap for (hopefully) faster crash + free (pc); + + // printf(">>> %s\n", __func__); +} + +void picpool_unref(picpool_ctl_t ** const pppc) +{ + int n; + picpool_ctl_t * const pc = *pppc; + + if (pc == NULL) + return; + *pppc = NULL; + + n = atomic_fetch_sub(&pc->ref_count, 1) - 1; + + if (n != 0) + return; + + picpool_delete(pc); +} + +picpool_ctl_t * picpool_ref(picpool_ctl_t * const pc) +{ + atomic_fetch_add(&pc->ref_count, 1); + return pc; +} + +#if 0 +static MMAL_BOOL_T vcz_pool_release_cb(MMAL_POOL_T * buf_pool, MMAL_BUFFER_HEADER_T *buf, void *userdata) +{ + picpool_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); + picpool_release(pc); + free(sb); + } + +// printf(">>> %s\n", __func__); + + return MMAL_TRUE; +} +#endif + +picpool_ctl_t * picpool_new(struct dmabufs_ctl * dbsc) +{ + picpool_ctl_t * pc; + + if (dbsc == NULL) + return NULL; + + pc = calloc(1, sizeof(*pc)); + if (pc == NULL) + return NULL; + + pc->max_n = 8; + pc->dbsc = dmabufs_ctl_ref(dbsc); + atomic_store(&pc->ref_count, 1); + vlc_mutex_init(&pc->lock); + + return pc; +} diff --git a/modules/video_output/wayland/picpool.h b/modules/video_output/wayland/picpool.h new file mode 100644 index 0000000000..d188c8b416 --- /dev/null +++ b/modules/video_output/wayland/picpool.h @@ -0,0 +1,24 @@ +#ifndef _WAYLAND_PICPOOL_H +#define _WAYLAND_PICPOOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct dmabuf_h; +struct dmabufs_ctl; +struct picpool_ctl_s; +typedef struct picpool_ctl_s picpool_ctl_t; + +struct dmabuf_h * picpool_get(picpool_ctl_t * const pc, size_t req_size); + +void picpool_flush(picpool_ctl_t * const pc); +void picpool_unref(picpool_ctl_t ** const pppc); +picpool_ctl_t * picpool_ref(picpool_ctl_t * const pc); +picpool_ctl_t * picpool_new(struct dmabufs_ctl * dbsc); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/video_output/wayland/rgba_premul.c b/modules/video_output/wayland/rgba_premul.c new file mode 100644 index 0000000000..a60bf78a1c --- /dev/null +++ b/modules/video_output/wayland/rgba_premul.c @@ -0,0 +1,243 @@ +#include + +#ifdef MAKE_TEST +#define vlc_CPU_ARM64_NEON() (1) +#define HAVE_AARCH64_ASM 1 +#else +#include +#include +#endif + +#include "rgba_premul.h" + +#ifdef HAVE_AARCH64_ASM +#include "rgba_premul_aarch64.h" +#endif + +// x, y src offset, not dest +// This won't be bit exact with aarch64 asm which has slightly different +// rounding (this is faster when done in C) +static void +copy_xxxa_with_premul_c(void * restrict dst_data, int dst_stride, + const void * restrict src_data, int src_stride, + const unsigned int w, const unsigned int h, + const unsigned int global_alpha) +{ + uint8_t * dst = (uint8_t*)dst_data; + const uint8_t * src = (uint8_t*)src_data; + const int src_inc = src_stride - (int)w * 4; + const int dst_inc = dst_stride - (int)w * 4; + + for (unsigned int i = 0; i != h; ++i) + { + for (unsigned int j = 0; j != w; ++j, src+=4, dst += 4) + { + unsigned int a = src[3] * global_alpha * 258; + const unsigned int k = 0x800000; + dst[0] = (src[0] * a + k) >> 24; + dst[1] = (src[1] * a + k) >> 24; + dst[2] = (src[2] * a + k) >> 24; + dst[3] = (src[3] * global_alpha * 257 + 0x8000) >> 16; + } + src += src_inc; + dst += dst_inc; + } +} + +void +copy_xxxa_with_premul(void * dst_data, int dst_stride, + const void * src_data, int src_stride, + const unsigned int w, const unsigned int h, + const unsigned int global_alpha) +{ +#ifdef HAVE_AARCH64_ASM + if (vlc_CPU_ARM64_NEON()) + copy_xxxa_with_premul_aarch64(dst_data, dst_stride, src_data, src_stride, w, h, global_alpha); + else +#endif + copy_xxxa_with_premul_c(dst_data, dst_stride, src_data, src_stride, w, h, global_alpha); +} + +// Has the optimization of copying as a single lump if strides are the same +// and the width is fairly close to the stride +// at the expense of possibly overwriting some bytes outside the active area +// (but within the frame) +void +copy_frame_xxxa_with_premul(void * dst_data, int dst_stride, + const void * src_data, int src_stride, + const unsigned int w, const unsigned int h, + const unsigned int global_alpha) +{ + if (dst_stride == src_stride && (dst_stride & 3) == 0 && (int)w * 4 <= dst_stride && (int)w * 4 + 64 >= dst_stride) + copy_xxxa_with_premul(dst_data, dst_stride, src_data, src_stride, h * dst_stride / 4, 1, global_alpha); + else + copy_xxxa_with_premul(dst_data, dst_stride, src_data, src_stride, w, h, global_alpha); +} + +//============================================================================ +#ifdef MAKE_TEST +#include +#include +#include +#include +#include + +static bool verbose = false; +static bool checkfail = false; + +static uint64_t +utime(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_nsec / 1000 + (uint64_t)ts.tv_sec * 1000000; +} + +// What the ASM is meant to do exactly +static void +copy_xxxa_with_premul_c_asm(void * restrict dst_data, int dst_stride, + const void * restrict src_data, int src_stride, + const unsigned int w, const unsigned int h, + const uint8_t global_alpha) +{ + uint8_t * dst = (uint8_t*)dst_data; + const uint8_t * src = (uint8_t*)src_data; + const int src_inc = src_stride - (int)w * 4; + const int dst_inc = dst_stride - (int)w * 4; + + for (unsigned int i = 0; i != h; ++i) + { + for (unsigned int j = 0; j != w; ++j, src+=4, dst += 4) + { + // What the ASM is meant to do exactly + unsigned int a = global_alpha * 257; + const unsigned int k = 0x800000; + dst[0] = (((src[0] * src[3] * 257) >> 8) * a + k) >> 24; + dst[1] = (((src[1] * src[3] * 257) >> 8) * a + k) >> 24; + dst[2] = (((src[2] * src[3] * 257) >> 8) * a + k) >> 24; + dst[3] = (src[3] * global_alpha * 257 + 0x8000) >> 16; + } + src += src_inc; + dst += dst_inc; + } +} + +#define ALIGN_SIZE 128 +#define ALIGN_PTR(p) ((uint8_t*)(((uintptr_t)p + (ALIGN_SIZE -1)) & ~(ALIGN_SIZE - 1))) + +static void +timetest(const unsigned int w, const unsigned int h, const int stride, bool use_c) +{ + uint64_t now; + uint64_t done; + size_t dsize = h * stride + ALIGN_SIZE; + + uint8_t * src = malloc(dsize); + uint8_t * dst = malloc(dsize); + uint8_t * s = ALIGN_PTR(src); + uint8_t * d = ALIGN_PTR(dst); + + memset(src, 0x80, dsize); + memset(dst, 0xff, dsize); + + now = utime(); + for (unsigned int i = 0; i != 10; ++i) + { + if (use_c) + copy_xxxa_with_premul_c(d, stride, s, stride, w, h, 0xba); + else + copy_xxxa_with_premul(d, stride, s, stride, w, h, 0xba); + } + done = utime(); + + printf("Time %3s: %dx%d stride %d: %6dus\n", use_c ? "C" : "Asm", w, h, stride, + (int)((done - now)/10)); + + free(src); + free(dst); +} + +static int +docheck(const uint8_t * const a, const uint8_t * const b, const size_t n) +{ + int t = 0; + + if (!verbose) + return memcmp(a, b, n); + + for (size_t i = 0; i != n && t < 128; ++i) + { + if (a[i] != b[i]) + { + printf("@ %zd: %02x %02x\n", i, a[i], b[i]); + ++t; + } + } + return t; +} + +static void +checktest(const unsigned int w, const unsigned int h, const int stride, const int offset) +{ + size_t dsize = ((h + 3) * stride + ALIGN_SIZE); + + uint8_t * src = malloc(dsize); + uint8_t * dst = malloc(dsize); + uint8_t * dst2 = malloc(dsize); + uint8_t * s = ALIGN_PTR(src + stride); + uint8_t * d = ALIGN_PTR(dst + stride); + uint8_t * d2 = ALIGN_PTR(dst2 + stride); + + for (unsigned int i = 0; i != dsize; ++i) + src[i] = rand(); + + memset(dst2, 0xff, dsize); + memset(dst, 0xff, dsize); + + copy_xxxa_with_premul_c_asm(d + offset, stride, s, stride, w, h, 0xba); + copy_xxxa_with_premul(d2 + offset, stride, s, stride, w, h, 0xba); + + if (docheck(d - stride, d2 - stride, (h + 2) * stride) != 0) + { + printf("Check: %dx%d stride %d offset %d: FAIL\n", w, h, stride, offset); + checkfail = true; + } + else if (verbose) + { + printf("Check: %dx%d stride %d offset %d: ok\n", w, h, stride, offset); + } + + free(src); + free(dst); + free(dst2); +} + +int +main (int argc, char *argv[]) +{ + if (argc >= 2 && strcmp(argv[1], "-v") == 0) + verbose = true; + + timetest(1920, 1080, 1920 * 4, true); + timetest(1920, 1080, 1920 * 4, false); + timetest(1917, 1080, 1920 * 4, false); + timetest(1917, 1080, 1917 * 4, false); + timetest(1920 * 1080, 1, 1920 * 1080 * 4, false); + + checktest(1920, 1080, 1920 * 4, 0); + + // Stride of 65pel will rotate alignment vertically + for (unsigned int i = 1; i != 64; ++i) + { + checktest(i, 32, 65 * 4, 0); + } + + if (!checkfail) + { + printf("All chacks passed\n"); + } + + return checkfail; +} + +#endif diff --git a/modules/video_output/wayland/rgba_premul.h b/modules/video_output/wayland/rgba_premul.h new file mode 100644 index 0000000000..14edfcd173 --- /dev/null +++ b/modules/video_output/wayland/rgba_premul.h @@ -0,0 +1,18 @@ +#ifndef _WAYLAND_RGBA_PREMUL_H +#define _WAYLAND_RGBA_PREMUL_H + +void copy_xxxa_with_premul(void * dst_data, int dst_stride, + const void * src_data, int src_stride, + const unsigned int w, const unsigned int h, + const unsigned int global_alpha); + +// Has the optimization of copying as a single lump if strides are the same +// and the width is fairly close to the stride +// at the expense of possibly overwriting some bytes outside the active area +// (but within the frame) +void copy_frame_xxxa_with_premul(void * dst_data, int dst_stride, + const void * src_data, int src_stride, + const unsigned int w, const unsigned int h, + const unsigned int global_alpha); + +#endif diff --git a/modules/video_output/wayland/rgba_premul_aarch64.S b/modules/video_output/wayland/rgba_premul_aarch64.S new file mode 100644 index 0000000000..7e2ed01503 --- /dev/null +++ b/modules/video_output/wayland/rgba_premul_aarch64.S @@ -0,0 +1,170 @@ +#include "../../arm_neon/asm.S" + +// copy_xxxa_with_premul( +// void * dst_data, // x0 +// int dst_stride, // w1 +// const void * src_data, // x2 +// int src_stride, // w3 +// unsigned int w, // w4 +// unsigned int h, // w5 +// unsigned int global_alpha) // w6 + +// rC R, G or B In/Out +// rA A In +// sG Global alpha *257 as h scalar (v7.h[1]) In +// rT1 tmp +// rT2 tmp +// +// vC = (vC * vA * 257/256 * sG + 0x800000) >> 24 +.macro mul_rgb vC, vA, sG, vT1, vT2 + + umull \vT2\().8h, \vC\().8b, \vA\().8b + umull2 \vT1\().8h, \vC\().16b, \vA\().16b + + usra \vT2\().8h, \vT2\().8h, #8 + usra \vT1\().8h, \vT1\().8h, #8 + + umull \vC\().4s, \vT2\().4h, \sG + umull2 \vT2\().4s, \vT2\().8h, \sG + uzp2 \vC\().8h, \vC\().8h, \vT2\().8h + + umull \vT2\().4s, \vT1\().4h, \sG + umull2 \vT1\().4s, \vT1\().8h, \sG + uzp2 \vT2\().8h, \vT2\().8h, \vT1\().8h + + uqrshrn \vC\().8b, \vC\().8h, #8 + uqrshrn2 \vC\().16b, \vT2\().8h, #8 +.endm + +// rA A In/Out +// vG Global alpha duped bytewise into vector In +// rT1 tmp +// +// vA = (vA * vG *257/256 + 0x80) >> 8 +.macro mul_a vA, vG, vT1, vT2 + umull \vT1\().8h, \vA\().8b, \vG\().8b // Alpha + umull2 \vT2\().8h, \vA\().16b, \vG\().16b + usra \vT1\().8h, \vT1\().8h, #8 // * 257/256 + usra \vT2\().8h, \vT2\().8h, #8 + uqrshrn \vA\().8b, \vT1\().8h, #8 + uqrshrn2 \vA\().16b, \vT2\().8h, #8 +.endm + +function copy_xxxa_with_premul_aarch64 + mov x15, lr // Stash return addr + // Sanity check w & h + cbz w4, 90f + cbz w5, 90f + + // Put alpha values somewhere we can use them + mov w7, #257 // Would like 258 but 258*255 overflows h + mul w7, w7, w6 + dup v6.16b, w6 + mov v7.h[1], w7 + + // Calc EOL stride add + sub w1, w1, w4, LSL #2 + sub w3, w3, w4, LSL #2 + + // Deal with very short stuff separately + // Saves annoying conditionals later + cmp w4, #16 + bge 22f + + // Loop for w < 16 + mov w6, w4 +1: + bl 50f + add x2, x2, w3, SXTW + add x0, x0, w1, SXTW + subs w5, w5, #1 + bne 1b + b 90f + + // Top of height loop +20: + add x2, x2, w3, SXTW + add x0, x0, w1, SXTW + subs w5, w5, #1 + beq 90f + +22: + // Align destination before main loop + tst x0, #63 + mov w6, w4 + beq 1f + + mov w7, #16 + ubfm w6, w0, #2, #5 + sub w6, w7, w6 + bl 50f + sub w6, w4, w6 +1: + // If w % 16 != 0 then -16 so the main loop runs 1 fewer times with + // the remainder done in the tail + tst w6, #15 + bne 15f + + // Top of width loop +10: + ld4 {v0.16b, v1.16b, v2.16b, v3.16b}, [x2], #64 + + mul_rgb v0, v3, v7.h[1], v16, v17 + mul_rgb v1, v3, v7.h[1], v16, v17 + mul_rgb v2, v3, v7.h[1], v16, v17 + mul_a v3, v6, v16, v17 + + st4 {v0.16b, v1.16b, v2.16b, v3.16b}, [x0], #64 + +15: + subs w6, w6, #16 + bgt 10b + beq 20b // No tail + + // Tail + bl 50f + b 20b + + // Return +90: + mov lr, x15 + ret + +// Tail & Head core +// +// w6 Noof pixels to convert +// Only bottom 3 bits considered, left unchanged on exit +50: + tbz w6, #3, 1f + ld4 {v0.8b, v1.8b, v2.8b, v3.8b}, [x2], #32 +1: tbz w6, #2, 1f + ld4 {v0.b, v1.b, v2.b, v3.b}[8], [x2], #4 + ld4 {v0.b, v1.b, v2.b, v3.b}[9], [x2], #4 + ld4 {v0.b, v1.b, v2.b, v3.b}[10], [x2], #4 + ld4 {v0.b, v1.b, v2.b, v3.b}[11], [x2], #4 +1: tbz w6, #1, 1f + ld4 {v0.b, v1.b, v2.b, v3.b}[12], [x2], #4 + ld4 {v0.b, v1.b, v2.b, v3.b}[13], [x2], #4 +1: tbz w6, #0, 1f + ld4 {v0.b, v1.b, v2.b, v3.b}[14], [x2], #4 +1: + + mul_rgb v0, v3, v7.h[1], v16, v17 + mul_rgb v1, v3, v7.h[1], v16, v17 + mul_rgb v2, v3, v7.h[1], v16, v17 + mul_a v3, v6, v16, v17 + + tbz w6, #3, 1f + st4 {v0.8b, v1.8b, v2.8b, v3.8b}, [x0], #32 +1: tbz w6, #2, 1f + st4 {v0.b, v1.b, v2.b, v3.b}[8], [x0], #4 + st4 {v0.b, v1.b, v2.b, v3.b}[9], [x0], #4 + st4 {v0.b, v1.b, v2.b, v3.b}[10], [x0], #4 + st4 {v0.b, v1.b, v2.b, v3.b}[11], [x0], #4 +1: tbz w6, #1, 1f + st4 {v0.b, v1.b, v2.b, v3.b}[12], [x0], #4 + st4 {v0.b, v1.b, v2.b, v3.b}[13], [x0], #4 +1: tbz w6, #0, 1f + st4 {v0.b, v1.b, v2.b, v3.b}[14], [x0], #4 +1: + ret diff --git a/modules/video_output/wayland/rgba_premul_aarch64.h b/modules/video_output/wayland/rgba_premul_aarch64.h new file mode 100644 index 0000000000..829a5d1014 --- /dev/null +++ b/modules/video_output/wayland/rgba_premul_aarch64.h @@ -0,0 +1,9 @@ +#ifndef _WAYLAND_RGBA_PREMUL_AARCH64_H +#define _WAYLAND_RGBA_PREMUL_AARCH64_H + +void copy_xxxa_with_premul_aarch64(void * dst_data, int dst_stride, + const void * src_data, int src_stride, + const unsigned int w, const unsigned int h, + const unsigned int global_alpha); + +#endif diff --git a/modules/video_output/wayland/shm.c b/modules/video_output/wayland/shm.c index 08ad5022f9..b1feb622d8 100644 --- a/modules/video_output/wayland/shm.c +++ b/modules/video_output/wayland/shm.c @@ -509,7 +509,7 @@ vlc_module_begin() set_description(N_("Wayland shared memory video output")) set_category(CAT_VIDEO) set_subcategory(SUBCAT_VIDEO_VOUT) - set_capability("vout display", 170) + set_capability("vout display", 0) set_callbacks(Open, Close) add_shortcut("wl") vlc_module_end() diff --git a/modules/video_output/wayland/wl_dmabuf.c b/modules/video_output/wayland/wl_dmabuf.c new file mode 100644 index 0000000000..c7fb902936 --- /dev/null +++ b/modules/video_output/wayland/wl_dmabuf.c @@ -0,0 +1,2420 @@ +/** + * @file shm.c + * @brief Wayland shared memory video output module for VLC media player + */ +/***************************************************************************** + * Copyright © 2014, 2017 Rémi Denis-Courmont + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif +#ifndef HAVE_WAYLAND_SINGLE_PIXEL_BUFFER +#define HAVE_WAYLAND_SINGLE_PIXEL_BUFFER 0 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#if HAVE_WAYLAND_SINGLE_PIXEL_BUFFER +#include "single-pixel-buffer-v1-client-protocol.h" +#endif +#include "viewporter-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" + +#include +#include +#include +#include +#include + +// *** Avoid this include if possible +#include + +#include "dmabuf_alloc.h" +#include "picpool.h" +#include "rgba_premul.h" +#include "../drmu/drmu_log.h" +#include "../drmu/drmu_vlc_fmts.h" +#include "../drmu/pollqueue.h" +#include "../../codec/avcodec/drm_pic.h" +#include + +#define TRACE_ALL 0 +#define CHECK_VDRE_COUNTS 0 + +#define MAX_PICTURES 4 +#define MAX_SUBPICS 6 + +#define WL_DMABUF_DISABLE_NAME "wl-dmabuf-disable" +#define WL_DMABUF_DISABLE_TEXT N_("Disable wl-dmabuf") +#define WL_DMABUF_DISABLE_LONGTEXT N_("Disable wl-dmabuf - useful if auto selection is wanted but not wl-dmabuf") + +#define WL_DMABUF_USE_SHM_NAME "wl-dmabuf-use-shm" +#define WL_DMABUF_USE_SHM_TEXT N_("Attempt to map via shm") +#define WL_DMABUF_USE_SHM_LONGTEXT N_("Attempt to map via shm rather than linux_dmabuf") + +#define WL_DMABUF_CHEQUERBOARD_NAME "wl-dmabuf-chequerboard" +#define WL_DMABUF_CHEQUERBOARD_TEXT N_("Chequerboard background") +#define WL_DMABUF_CHEQUERBOARD_LONGTEXT N_("Fill unused window area with chequerboard rather than black") + +#define WL_DMABUF_STATS_NAME "wl-dmabuf-stats" +#define WL_DMABUF_STATS_TEXT N_("Display some display stats") +#define WL_DMABUF_STATS_LONGTEXT N_("When display is closed report frames displayed/discarded and avg fps. "\ + "N.B. Unfortunately current implementation cannot track frames discarded by Wayland before display") + +typedef struct fmt_ent_s { + uint32_t fmt; + int32_t pri; + uint64_t mod; +} fmt_ent_t; + +typedef struct fmt_list_s { + fmt_ent_t * fmts; + unsigned int size; + unsigned int len; +} fmt_list_t; + +typedef struct eq_env_ss { + atomic_int eq_count; + sem_t sem; + + struct wl_display *display; + struct pollqueue *pq; + struct wl_event_queue *q; + struct wl_display *wrapped_display; +} eq_env_t; + +typedef struct video_dmabuf_release_env_ss +{ + void (* dma_rel_fn)(void *); + void * dma_rel_v; + eq_env_t * eq; + unsigned int rel_count; + unsigned int pt_count; + struct polltask * pt[AV_DRM_MAX_PLANES]; +#if CHECK_VDRE_COUNTS + atomic_int * vdre_check; +#endif +} video_dmabuf_release_env_t; + +typedef struct subpic_ent_s { + struct wl_buffer * wb; + struct dmabuf_h * dh; + video_dmabuf_release_env_t * vdre; + picture_t * pic; + int alpha; + enum wl_output_transform trans; + vout_display_place_t src_rect; + vout_display_place_t dst_rect; + vout_display_place_t orig_rect; + + atomic_int ready; + + struct polltask * pt; + vout_display_t * vd; + vout_display_sys_t * sys; +} subpic_ent_t; + +typedef struct subplane_s { + struct wl_surface * surface; + struct wl_subsurface * subsurface; + struct wp_viewport * viewport; + + bool buffer_attached; + bool commit_req; + int commit_parent; + + enum wl_output_transform trans; + vout_display_place_t src_rect; + vout_display_place_t dst_rect; + + subpic_ent_t * spe_cur; + subpic_ent_t * spe_next; +} subplane_t; + +typedef struct w_bound_ss +{ + struct wp_viewporter *viewporter; + struct zwp_linux_dmabuf_v1 * linux_dmabuf_v1; + struct wl_compositor *compositor; + struct wl_subcompositor *subcompositor; + struct wl_shm *shm; +#if HAVE_WAYLAND_SINGLE_PIXEL_BUFFER + struct wp_single_pixel_buffer_manager_v1 *single_pixel_buffer_manager_v1; +#endif +} w_bound_t; + +#define PLANE_BKG 0 +#define PLANE_VID 1 +#define PLANE_SUB 2 + +struct vout_display_sys_t +{ + vout_window_t *embed; /* VLC window */ + + w_bound_t bound; + + picture_pool_t *vlc_pic_pool; /* picture pool */ + + struct wl_surface * last_embed_surface; + unsigned int last_embed_seq; + + int x; + int y; + bool use_shm; + bool chequerboard; + bool want_stats; + + struct wp_viewport * bkg_viewport; + // Current size of background viewport if we have one + // If not created yet then the size that the viewport should be created + unsigned int bkg_w; + unsigned int bkg_h; + + enum wl_output_transform video_trans; + vout_display_place_t video_src_rect; + vout_display_place_t video_dst_rect; + +#if CHECK_VDRE_COUNTS + atomic_int vdre_check_bkg; + atomic_int vdre_check_fg; +#endif + + eq_env_t * eq; + + struct pollqueue * pollq; + struct pollqueue * speq; + + picpool_ctl_t * subpic_pool; + subplane_t planes[MAX_SUBPICS + 2]; + subpic_ent_t * video_spe_prep; + struct wl_callback * video_frame_callback; + vlc_fourcc_t * subpic_chromas; + + struct wl_region * region_none; + struct wl_region * region_all; + + fmt_list_t dmabuf_fmts; + fmt_list_t shm_fmts; + + struct display_stats_s { + unsigned int frame_n; + unsigned int frame_frame; + unsigned int frame_display; + unsigned int frame_discard; + vlc_tick_t time_frame0; + vlc_tick_t time_frameN; + } stats; +}; + + +static struct wl_surface * bkg_surface_get_lock(vout_display_t * const vd, vout_display_sys_t * const sys); +static void bkg_surface_unlock(vout_display_t * const vd, vout_display_sys_t * const sys); + +static void +msg_stats(vout_display_t * const vd, const struct display_stats_s * const s) +{ + unsigned int tframes = (s->frame_n + s->frame_discard); + unsigned int frx1000 = tframes < 2 ? 0 : + (unsigned int)((uint64_t)(tframes - 1) * 1000000000ULL / (s->time_frameN - s->time_frame0)); + msg_Info(vd, "Frames: Total: %d, Discarded %d, Display %d FpS(total):%d.%03d", + tframes, s->frame_discard, s->frame_display, + frx1000 / 1000, frx1000 % 1000); +} + + +static inline struct wl_display * +video_display(const vout_display_sys_t * const sys) +{ + return sys->embed->display.wl; +} + +static inline struct wl_surface * +video_surface(const vout_display_sys_t * const sys) +{ + return sys->planes[PLANE_VID].surface; +} + +static inline struct wl_compositor * +video_compositor(const vout_display_sys_t * const sys) +{ + return sys->bound.compositor; +} + +static void +buffer_destroy(struct wl_buffer ** ppbuffer) +{ + struct wl_buffer * const buffer = *ppbuffer; + if (buffer == NULL) + return; + *ppbuffer = NULL; + wl_buffer_destroy(buffer); +} + +static void +region_destroy(struct wl_region ** const ppregion) +{ + if (*ppregion == NULL) + return; + wl_region_destroy(*ppregion); + *ppregion = NULL; +} + +static void +subsurface_destroy(struct wl_subsurface ** const ppsubsurface) +{ + if (*ppsubsurface == NULL) + return; + wl_subsurface_destroy(*ppsubsurface); + *ppsubsurface = NULL; +} + +static void +surface_destroy(struct wl_surface ** const ppsurface) +{ + if (*ppsurface == NULL) + return; + wl_surface_destroy(*ppsurface); + *ppsurface = NULL; +} + +static void +viewport_destroy(struct wp_viewport ** const ppviewport) +{ + if (*ppviewport == NULL) + return; + wp_viewport_destroy(*ppviewport); + *ppviewport = NULL; +} + +static inline int_fast32_t +place_rescale_1s(int_fast32_t x, uint_fast32_t mul, uint_fast32_t div) +{ + const int_fast64_t m = x * (int_fast64_t)mul; + const uint_fast32_t d2 = div/2; + return div == 0 ? (int_fast32_t)m : + m >= 0 ? (int_fast32_t)(((uint_fast64_t)m + d2) / div) : + -(int_fast32_t)(((uint_fast64_t)(-m) + d2) / div); +} + +static inline uint_fast32_t +place_rescale_1u(uint_fast32_t x, uint_fast32_t mul, uint_fast32_t div) +{ + const uint_fast64_t m = x * (uint_fast64_t)mul; + return (uint_fast32_t)(div == 0 ? m : (m + div/2) / div); +} + +static inline vout_display_place_t +place_rescale(const vout_display_place_t s, const vout_display_place_t mul, const vout_display_place_t div) +{ + return (vout_display_place_t){ + .x = place_rescale_1s(s.x - div.x, mul.width, div.width) + mul.x, + .y = place_rescale_1s(s.y - div.y, mul.height, div.height) + mul.y, + .width = place_rescale_1u(s.width, mul.width, div.width), + .height = place_rescale_1u(s.height, mul.height, div.height) + }; +} + +static inline vout_display_place_t +place_zoffset(const vout_display_place_t s) +{ + return (vout_display_place_t){ + .x = 0, + .y = 0, + .width = s.width, + .height = s.height + }; +} + +static inline bool +place_xy_eq(const vout_display_place_t a, const vout_display_place_t b) +{ + return a.x == b.x && a.y == b.y; +} + +static inline bool +place_wh_eq(const vout_display_place_t a, const vout_display_place_t b) +{ + return a.width == b.width && a.height == b.height; +} + +static inline bool +place_eq(const vout_display_place_t a, const vout_display_place_t b) +{ + return place_xy_eq(a, b) && place_wh_eq(a, b); +} + + +// MMAL headers comment these (getting 2 a bit wrong) but do not give +// defines +#define VXF_H_SHIFT 0 // Hflip +#define VXF_V_SHIFT 1 // Vflip +#define VXF_T_SHIFT 2 // Transpose +#define VXF_H_BIT (1 << VXF_H_SHIFT) +#define VXF_V_BIT (1 << VXF_V_SHIFT) +#define VXF_T_BIT (1 << VXF_T_SHIFT) + +static inline bool +is_vxf_transpose(const video_transform_t t) +{ + return ((unsigned int)t & VXF_T_BIT) != 0; +} + +static inline bool +is_vxf_hflip(const video_transform_t t) +{ + return ((unsigned int)t & VXF_H_BIT) != 0; +} + +static inline bool +is_vxf_vflip(const video_transform_t t) +{ + return ((unsigned int)t & VXF_V_BIT) != 0; +} + +static inline video_transform_t +swap_vxf_hv(const video_transform_t x) +{ + return (((x >> VXF_H_SHIFT) & 1) << VXF_V_SHIFT) | + (((x >> VXF_V_SHIFT) & 1) << VXF_H_SHIFT) | + (x & VXF_T_BIT); +} + +static inline video_transform_t +vxf_inverse(const video_transform_t x) +{ + return is_vxf_transpose(x) ? swap_vxf_hv(x) : x; +} + +// Transform generated by A then B +// All ops are self inverse so can simply be XORed on their own +// H & V flips after a transpose need to be swapped +static inline video_transform_t +combine_vxf(const video_transform_t a, const video_transform_t b) +{ + return a ^ (is_vxf_transpose(a) ? swap_vxf_hv(b) : b); +} + +static inline vout_display_place_t +vplace_transpose(const vout_display_place_t s) +{ + return (vout_display_place_t){ + .x = s.y, + .y = s.x, + .width = s.height, + .height = s.width + }; +} + +// hflip s in c +static inline vout_display_place_t vplace_hflip(const vout_display_place_t s, const vout_display_place_t c) +{ + return (vout_display_place_t){ + .x = c.x + (c.x + c.width) - (s.x + s.width), + .y = s.y, + .width = s.width, + .height = s.height + }; +} + +// vflip s in c +static inline vout_display_place_t vplace_vflip(const vout_display_place_t s, const vout_display_place_t c) +{ + return (vout_display_place_t){ + .x = s.x, + .y = (c.y + c.height) - (s.y - c.y) - s.height, + .width = s.width, + .height = s.height + }; +} + +static int +fmt_list_add(fmt_list_t * const fl, uint32_t fmt, uint64_t mod, int32_t pri) +{ + if (fl->len >= fl->size) + { + unsigned int n = fl->len == 0 ? 64 : fl->len * 2; + fmt_ent_t * t = realloc(fl->fmts, n * sizeof(*t)); + if (t == NULL) + return VLC_ENOMEM; + fl->fmts = t; + fl->size = n; + } + fl->fmts[fl->len++] = (fmt_ent_t){ + .fmt = fmt, + .pri = pri, + .mod = mod + }; + return 0; +} + +static int +fmt_sort_cb(const void * va, const void * vb) +{ + const fmt_ent_t * const a = va; + const fmt_ent_t * const b = vb; + return a->fmt < b->fmt ? -1 : a->fmt != b->fmt ? 1 : + a->mod < b->mod ? -1 : a->mod != b->mod ? 1 : 0; +} + +static void +fmt_list_sort(fmt_list_t * const fl) +{ + unsigned int n = 0; + if (fl->len <= 1) + return; + qsort(fl->fmts, fl->len, sizeof(*fl->fmts), fmt_sort_cb); + // Dedup - in case we have multiple working callbacks + for (unsigned int i = 1; i != fl->len; ++i) + { + if (fl->fmts[i].fmt != fl->fmts[n].fmt || fl->fmts[i].mod != fl->fmts[n].mod) + fl->fmts[n++] = fl->fmts[i]; + } + fl->len = n + 1; +} + +static int +fmt_list_find(const fmt_list_t * const fl, const drmu_vlc_fmt_info_t * const fmti) +{ + if (fmti == NULL || fl->len == 0) + { + return -1; + } + else + { + const fmt_ent_t x = { + .fmt = drmu_vlc_fmt_info_drm_pixelformat(fmti), + .mod = drmu_vlc_fmt_info_drm_modifier(fmti), + }; + const fmt_ent_t * const fe = + bsearch(&x, fl->fmts, fl->len, sizeof(x), fmt_sort_cb); + return fe == NULL ? -1 : fe->pri; + } +} + +static void +fmt_list_uninit(fmt_list_t * const fl) +{ + free(fl->fmts); + fl->fmts = NULL; + fl->size = 0; + fl->len = 0; +} + +static int +fmt_list_init(fmt_list_t * const fl, const size_t initial_size) +{ + fl->size = 0; + fl->len = 0; + if ((fl->fmts = malloc(initial_size * sizeof(*fl->fmts))) == NULL) + return VLC_ENOMEM; + fl->size = initial_size; + return VLC_SUCCESS; +} + +// ---------------------------------------------------------------------------- + +static struct wl_display * +eq_wrapper(eq_env_t * const eq) +{ + return eq->wrapped_display; +} + +static void +eq_ref(eq_env_t * const eq) +{ + const int n = atomic_fetch_add(&eq->eq_count, 1); + (void)n; +// fprintf(stderr, "Ref: count=%d\n", n + 1); +} + +static void +eq_unref(eq_env_t ** const ppeq) +{ + eq_env_t * eq = *ppeq; + if (eq != NULL) + { + int n; + *ppeq = NULL; + n = atomic_fetch_sub(&eq->eq_count, 1); +// fprintf(stderr, "Unref: Buffer count=%d\n", n); + if (n == 0) + { + pollqueue_set_pre_post(eq->pq, 0, 0, NULL); + pollqueue_unref(&eq->pq); + + wl_proxy_wrapper_destroy(eq->wrapped_display); + wl_event_queue_destroy(eq->q); + + sem_destroy(&eq->sem); + free(eq); +// fprintf(stderr, "Eq closed\n"); + } + } +} + +static int +eq_finish(eq_env_t ** const ppeq) +{ + eq_env_t * const eq = *ppeq; + + if (eq == NULL) + return 0; + + eq_unref(ppeq); + return 0; +} + +static void +pollq_pre_cb(void * v, struct pollfd * pfd) +{ + eq_env_t * const eq = v; + struct wl_display *const display = eq->display; + int ferr; + int frv; + +// fprintf(stderr, "Start Prepare\n"); + + while (wl_display_prepare_read_queue(display, eq->q) != 0) { + int n = wl_display_dispatch_queue_pending(display, eq->q); + (void)n; +// fprintf(stderr, "Dispatch=%d\n", n); + } + if ((frv = wl_display_flush(display)) >= 0) { + pfd->events = POLLIN; + ferr = 0; + } + else { + ferr = errno; + pfd->events = POLLOUT | POLLIN; + } + pfd->fd = wl_display_get_fd(display); +(void)ferr; +// fprintf(stderr, "Done Prepare: fd=%d, evts=%#x, frv=%d, ferr=%s\n", pfd->fd, pfd->events, frv, ferr == 0 ? "ok" : strerror(ferr)); +} + +static void +pollq_post_cb(void *v, short revents) +{ + eq_env_t * const eq = v; + struct wl_display *const display = eq->display; + int n; + + if ((revents & POLLIN) == 0) { +// fprintf(stderr, "Cancel read: Events=%#x: IN=%#x, OUT=%#x, ERR=%#x\n", (int)revents, POLLIN, POLLOUT, POLLERR); + wl_display_cancel_read(display); + } + else { +// fprintf(stderr, "Read events: Events=%#x: IN=%#x, OUT=%#x, ERR=%#x\n", (int)revents, POLLIN, POLLOUT, POLLERR); + wl_display_read_events(display); + } + +// fprintf(stderr, "Start Dispatch\n"); + n = wl_display_dispatch_queue_pending(display, eq->q); + (void)n; +// fprintf(stderr, "Dispatch=%d\n", n); +} + +static eq_env_t * +eq_new(struct wl_display * const display, struct pollqueue * const pq) +{ + eq_env_t * eq = calloc(1, sizeof(*eq)); + + if (eq == NULL) + return NULL; + + atomic_init(&eq->eq_count, 0); + sem_init(&eq->sem, 0, 0); + + if ((eq->q = wl_display_create_queue(display)) == NULL) + goto err1; + if ((eq->wrapped_display = wl_proxy_create_wrapper(display)) == NULL) + goto err2; + wl_proxy_set_queue((struct wl_proxy *)eq->wrapped_display, eq->q); + + eq->display = display; + eq->pq = pollqueue_ref(pq); + + pollqueue_set_pre_post(eq->pq, pollq_pre_cb, pollq_post_cb, eq); + + return eq; + +err2: + wl_event_queue_destroy(eq->q); +err1: + free(eq); + return NULL; +} + +static void eventq_sync_cb(void * data, struct wl_callback * cb, unsigned int cb_data) +{ + sem_t * const sem = data; + VLC_UNUSED(cb_data); + wl_callback_destroy(cb); + sem_post(sem); +} + +static const struct wl_callback_listener eq_sync_listener = {.done = eventq_sync_cb}; + +struct eq_sync_env_ss { + eq_env_t * eq; + sem_t sem; +}; + +static void +eq_sync_pq_cb(void * v, short revents) +{ + struct eq_sync_env_ss * const eqs = v; + struct wl_callback * const cb = wl_display_sync(eq_wrapper(eqs->eq)); + VLC_UNUSED(revents); + wl_callback_add_listener(cb, &eq_sync_listener, &eqs->sem); + // No flush needed as that will occur as part of the pollqueue loop +} + +static int +eventq_sync(eq_env_t * const eq) +{ + struct eq_sync_env_ss eqs = {.eq = eq}; + int rv; + + if (!eq) + return -1; + + sem_init(&eqs.sem, 0, 0); + // Bounce execution to pollqueue to avoid race setting up listener + pollqueue_callback_once(eq->pq, eq_sync_pq_cb, &eqs); + while ((rv = sem_wait(&eqs.sem)) == -1 && errno == EINTR) + /* Loop */; + sem_destroy(&eqs.sem); + return rv; +} + +// ---------------------------------------------------------------------------- + +static void +chequerboard(uint32_t *const data, unsigned int stride, const unsigned int width, const unsigned int height) +{ + stride /= sizeof(uint32_t); + + /* Draw checkerboxed background */ + for (unsigned int y = 0; y < height; ++y) { + for (unsigned int x = 0; x < width; ++x) { + if ((x + y / 8 * 8) % 16 < 8) + data[y * stride + x] = 0xFF666666; + else + data[y * stride + x] = 0xFFEEEEEE; + } + } +} + +static void +fill_uniform(uint32_t *const data, unsigned int stride, const unsigned int width, const unsigned int height, const uint32_t val) +{ + stride /= sizeof(uint32_t); + + /* Draw solid background */ + for (unsigned int y = 0; y < height; ++y) { + for (unsigned int x = 0; x < width; ++x) + data[y * stride + x] = val; + } +} + +// ---------------------------------------------------------------------------- + +static void +vdre_free(video_dmabuf_release_env_t * const vdre) +{ + unsigned int i; + if (vdre->dma_rel_fn) + vdre->dma_rel_fn(vdre->dma_rel_v); + for (i = 0; i != vdre->pt_count; ++i) + polltask_delete(vdre->pt + i); + eq_unref(&vdre->eq); +#if CHECK_VDRE_COUNTS + if (vdre->vdre_check != NULL) + atomic_fetch_sub(vdre->vdre_check, 1); +#endif + free(vdre); +} + +static video_dmabuf_release_env_t * +vdre_new_null(void) +{ + video_dmabuf_release_env_t * const vdre = calloc(1, sizeof(*vdre)); + return vdre; +} + +static void +vdre_dma_rel_cb(void * v) +{ + struct picture_context_t * ctx = v; + ctx->destroy(ctx); +} + +static video_dmabuf_release_env_t * +vdre_new_ctx(struct picture_context_t * ctx) +{ + video_dmabuf_release_env_t * const vdre = vdre_new_null(); + if (vdre == NULL) + return NULL; + if ((vdre->dma_rel_v = ctx->copy(ctx)) == NULL) + { + free(vdre); + return NULL; + } + vdre->dma_rel_fn = vdre_dma_rel_cb; + return vdre; +} + +static void +vdre_delete(video_dmabuf_release_env_t ** const ppvdre) +{ + video_dmabuf_release_env_t * const vdre = *ppvdre; + if (vdre == NULL) + return; + *ppvdre = NULL; + vdre_free(vdre); +} + +static void +vdre_polltask_cb(void * v, short revents) +{ + video_dmabuf_release_env_t * const vdre = v; + VLC_UNUSED(revents); + // Wait for all callbacks to come back before releasing buffer + if (++vdre->rel_count >= vdre->pt_count) + vdre_free(vdre); +} + +static void +vdre_eq_ref(video_dmabuf_release_env_t * const vdre, eq_env_t * const eq) +{ + if (vdre == NULL) + return; + vdre->eq = eq; + eq_ref(vdre->eq); +} + +#if CHECK_VDRE_COUNTS +static void +vdre_add_check(video_dmabuf_release_env_t * const vdre, atomic_int * const pa) +{ + vdre->vdre_check = pa; + atomic_fetch_add(pa, 1); +} +#endif + +static void +vdre_add_pt(video_dmabuf_release_env_t * const vdre, struct pollqueue * pq, int fd) +{ + assert(vdre->pt_count < AV_DRM_MAX_PLANES); + vdre->pt[vdre->pt_count++] = polltask_new(pq, fd, POLLOUT, vdre_polltask_cb, vdre); +} + +static void +vdre_dh_rel_cb(void * v) +{ + struct dmabuf_h * dh = v; + dmabuf_unref(&dh); +} + +static video_dmabuf_release_env_t * +vdre_new_dh(struct dmabuf_h *const dh, struct pollqueue *const pq) +{ + video_dmabuf_release_env_t * const vdre = vdre_new_null(); + + vdre->dma_rel_fn = vdre_dh_rel_cb; + vdre->dma_rel_v = dh; + + if (!dmabuf_is_fake(dh)) + vdre_add_pt(vdre, pq, dmabuf_fd(dh)); + return vdre; +} + +// Avoid use of vd here as there's a possibility this will be called after +// it has gone +static void +vdre_buffer_release_cb(void *data, struct wl_buffer *wl_buffer) +{ + video_dmabuf_release_env_t * const vdre = data; + unsigned int i = vdre->pt_count; + + /* Sent by the compositor when it's no longer using this buffer */ + buffer_destroy(&wl_buffer); + + eq_unref(&vdre->eq); + + if (i == 0) + vdre_free(vdre); + else + { + // Whilst we can happily destroy the buffer that doesn't mean we can reuse + // the dmabufs yet - we have to wait for them to be free of fences. + // We don't want to wait in this callback so do the waiting in pollqueue + while (i-- != 0) + pollqueue_add_task(vdre->pt[i], 1000); + } +} + +static const struct wl_buffer_listener vdre_buffer_listener = { + .release = vdre_buffer_release_cb, +}; + +// ---------------------------------------------------------------------------- + +static inline size_t cpypic_plane_alloc_size(const plane_t * const p) +{ + return p->i_pitch * p->i_lines; +} + +static inline uint32_t +drm_fmt_to_wl_shm(const uint32_t drm_fmt) +{ + return (drm_fmt == DRM_FORMAT_ARGB8888) ? WL_SHM_FORMAT_ARGB8888 : + (drm_fmt == DRM_FORMAT_XRGB8888) ? WL_SHM_FORMAT_XRGB8888 : drm_fmt; +} + +static struct wl_buffer * +dfd_make_buffer(vout_display_t * const vd, vout_display_sys_t * const sys, + const bool use_shm, + const AVDRMFrameDescriptor * const desc, + const unsigned int width, const unsigned int height, + const uint32_t flags) +{ + struct wl_buffer * w_buffer; + + if (!sys->bound.linux_dmabuf_v1 || use_shm) + { + const AVDRMPlaneDescriptor *const p = desc->layers[0].planes + 0; + struct wl_shm_pool *pool = wl_shm_create_pool(sys->bound.shm, desc->objects[0].fd, desc->objects[0].size); + const uint32_t w_fmt = drm_fmt_to_wl_shm(desc->layers[0].format); + + if (pool == NULL) + { + msg_Err(vd, "Failed to create pool from dmabuf"); + return NULL; + } + w_buffer = wl_shm_pool_create_buffer(pool, p->offset, width, height, p->pitch, w_fmt); + wl_shm_pool_destroy(pool); + if (w_buffer == NULL) + msg_Err(vd, "Failed to create buffer from pool"); + } + else + { + /* Creation and configuration of planes */ + int i; + unsigned int n = 0; + struct zwp_linux_buffer_params_v1 * const params = zwp_linux_dmabuf_v1_create_params(sys->bound.linux_dmabuf_v1); + + if (!params) + { + msg_Err(vd, "zwp_linux_dmabuf_v1_create_params FAILED"); + return NULL; + } + + for (i = 0; i < desc->nb_layers; ++i) + { + int j; + for (j = 0; j < desc->layers[i].nb_planes; ++j) + { + const AVDRMPlaneDescriptor *const p = desc->layers[i].planes + j; + const AVDRMObjectDescriptor *const obj = desc->objects + p->object_index; + + zwp_linux_buffer_params_v1_add(params, obj->fd, n++, p->offset, p->pitch, + (unsigned int)(obj->format_modifier >> 32), + (unsigned int)(obj->format_modifier & 0xFFFFFFFF)); + } + } + + /* Request buffer creation */ + if ((w_buffer = zwp_linux_buffer_params_v1_create_immed(params, width, height, desc->layers[0].format, flags)) == NULL) + msg_Err(vd, "zwp_linux_buffer_params_v1_create_immed FAILED"); + zwp_linux_buffer_params_v1_destroy(params); + } + return w_buffer; +} + +static int +copy_subpic_to_w_buffer(vout_display_t *vd, vout_display_sys_t * const sys, picture_t * const src, + int alpha, + video_dmabuf_release_env_t ** pVdre, struct wl_buffer ** pW_buffer) +{ + unsigned int w = src->format.i_width; + unsigned int h = src->format.i_height; + uint64_t mod; + const uint32_t drm_fmt = drmu_format_vlc_to_drm(&src->format, &mod); + struct dmabuf_h * dh = NULL; + int i; + AVDRMFrameDescriptor dfd = { + .nb_objects = 1, + .objects = {{ + .fd = -1, + .size = 0, + .format_modifier = mod + }}, + .nb_layers = 1, + .layers = {{ + .format = drm_fmt, + .nb_planes = src->i_planes, + }} + }; + + for (i = 0; i != src->i_planes; ++i) + { + dfd.layers[0].planes[i].object_index = 0; + dfd.layers[0].planes[i].offset = dfd.objects[0].size; + dfd.layers[0].planes[i].pitch = src->p[i].i_pitch; + dfd.objects[0].size += cpypic_plane_alloc_size(src->p + i); + } + + *pW_buffer = NULL; + *pVdre = NULL; + + if ((dh = picpool_get(sys->subpic_pool, dfd.objects[0].size)) == NULL) + { + msg_Warn(vd, "Failed to alloc dmabuf for subpic"); + goto error; + } + dfd.objects[0].fd = dmabuf_fd(dh); + + if ((*pVdre = vdre_new_dh(dh, sys->pollq)) == NULL) + { + msg_Warn(vd, "Failed to alloc vdre for subpic"); + dmabuf_unref(&dh); + goto error; + } + + // Copy to dh + dmabuf_write_start(dh); + for (i = 0; i != dfd.layers[0].nb_planes; ++i) + { + const size_t stride = dfd.layers[0].planes[i].pitch; + const size_t offset = dfd.layers[0].planes[i].offset; + void * const dst_data = (char *)dmabuf_map(dh) + offset; + + if (src->format.i_chroma == VLC_CODEC_RGBA || + src->format.i_chroma == VLC_CODEC_BGRA) + copy_frame_xxxa_with_premul(dst_data, stride, src->p[i].p_pixels, stride, w, h, alpha); + else + memcpy(dst_data, src->p[i].p_pixels, cpypic_plane_alloc_size(src->p + i)); + } + dmabuf_write_end(dh); + + *pW_buffer = dfd_make_buffer(vd, sys, dmabuf_is_fake(dh), &dfd, w, h, 0); + if (*pW_buffer == NULL) + goto error; + +#if CHECK_VDRE_COUNTS + vdre_add_check(*pVdre, &sys->vdre_check_fg); +#endif + + return VLC_SUCCESS; + +error: + vdre_delete(pVdre); + return VLC_EGENERIC; +} + +static void kill_pool(vout_display_sys_t * const sys) +{ + if (sys->vlc_pic_pool != NULL) + { + picture_pool_Release(sys->vlc_pic_pool); + sys->vlc_pic_pool = NULL; + } +} + +// Actual picture pool for dmabufs is just a set of trivial containers +static picture_pool_t *vd_dmabuf_pool(vout_display_t * const vd, unsigned count) +{ + vout_display_sys_t * const sys = vd->sys; + + msg_Dbg(vd, "%s: fmt:%dx%d,sar:%d/%d; source:%dx%d, count=%u", __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, count); + + if (sys->vlc_pic_pool == NULL) + sys->vlc_pic_pool = picture_pool_NewFromFormat(&vd->fmt, count); + return sys->vlc_pic_pool; +} + +static int +do_display_dmabuf(vout_display_t * const vd, vout_display_sys_t * const sys, picture_t * const pic, + video_dmabuf_release_env_t ** const pVdre, struct wl_buffer ** const pWbuffer) +{ + const AVDRMFrameDescriptor * const desc = drm_prime_get_desc(pic); + const unsigned int width = pic->format.i_width; + const unsigned int height = pic->format.i_height; + unsigned int flags = 0; + int i; + video_dmabuf_release_env_t * const vdre = vdre_new_ctx(pic->context); + + assert(*pWbuffer == NULL); + assert(*pVdre == NULL); + + if (vdre == NULL) { + msg_Err(vd, "Failed to create vdre"); + return VLC_ENOMEM; + } + + for (i = 0; i != desc->nb_objects; ++i) + vdre_add_pt(vdre, sys->pollq, desc->objects[i].fd); + + if (!pic->b_progressive) + { + flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED; + if (!pic->b_top_field_first) + flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST; + } + + *pWbuffer = dfd_make_buffer(vd, sys, false, desc, width, height, flags); + if (*pWbuffer == NULL) + goto error; + + *pVdre = vdre; + return VLC_SUCCESS; + +error: + vdre_free(vdre); + return VLC_EGENERIC; +} + +static void +subpic_ent_flush(subpic_ent_t * const spe) +{ + if (spe->pic != NULL) { + picture_Release(spe->pic); + spe->pic = NULL; + } + buffer_destroy(&spe->wb); + vdre_delete(&spe->vdre); + dmabuf_unref(&spe->dh); +} + +static void +subpic_ent_attach(subplane_t * const plane, subpic_ent_t * const spe, eq_env_t * const eq) +{ + struct wl_surface *const surface = plane->surface; + + if (spe->wb == NULL) + { + vdre_delete(&spe->vdre); + if (plane->buffer_attached) + { + wl_surface_attach(surface, NULL, 0, 0); + plane->buffer_attached = false; + plane->commit_req = true; + } + } + else + { + vdre_eq_ref(spe->vdre, eq); + wl_buffer_add_listener(spe->wb, &vdre_buffer_listener, spe->vdre); + wl_surface_attach(surface, spe->wb, 0, 0); + spe->vdre = NULL; + spe->wb = NULL; + wl_surface_damage(surface, 0, 0, INT32_MAX, INT32_MAX); + plane->buffer_attached = true; + plane->commit_req = true; + } +} + +static void +spe_convert_cb(void * v, short revents) +{ + subpic_ent_t * const spe = v; + VLC_UNUSED(revents); + + copy_subpic_to_w_buffer(spe->vd, spe->sys, spe->pic, spe->alpha, &spe->vdre, &spe->wb); + atomic_store(&spe->ready, 1); +} + +static inline bool +spe_no_pic(const subpic_ent_t * const spe) +{ + return spe == NULL || spe->pic == NULL; +} + +static bool +spe_changed(const subpic_ent_t * const spe, const subpicture_region_t * const sreg) +{ + const bool no_pic = (sreg == NULL || sreg->i_alpha == 0); + if (no_pic && spe_no_pic(spe)) + return false; + return no_pic || spe_no_pic(spe) || spe->pic != sreg->p_picture || spe->alpha != sreg->i_alpha; +} + +static void +spe_update_rect(subpic_ent_t * const spe, + const subpicture_t * const spic, + const subpicture_region_t * const sreg) +{ + spe->src_rect = (vout_display_place_t) { + .x = sreg->fmt.i_x_offset, + .y = sreg->fmt.i_y_offset, + .width = sreg->fmt.i_visible_width, + .height = sreg->fmt.i_visible_height, + }; + spe->dst_rect = (vout_display_place_t) { + .x = sreg->i_x, + .y = sreg->i_y, + .width = sreg->fmt.i_visible_width, + .height = sreg->fmt.i_visible_height, + }; + spe->orig_rect = (vout_display_place_t) { + .x = 0, + .y = 0, + .width = spic->i_original_picture_width, + .height = spic->i_original_picture_height, + }; +} + +static subpic_ent_t * +spe_new_pic(vout_display_t * const vd, vout_display_sys_t * const sys, + picture_t * const pic) +{ + subpic_ent_t * const spe = calloc(1, sizeof(*spe)); + + if (spe == NULL) + return NULL; + + atomic_init(&spe->ready, 0); + spe->vd = vd; + spe->sys = sys; + + if (pic == NULL) + { + atomic_init(&spe->ready, 1); + return spe; + } + + spe->pic = picture_Hold(pic); + spe->alpha = 0xff; + return spe; +} + + +static subpic_ent_t * +spe_new(vout_display_t * const vd, vout_display_sys_t * const sys, + const subpicture_t * const spic, + const subpicture_region_t * const sreg) +{ + subpic_ent_t * const spe = spe_new_pic(vd, sys, + (sreg == NULL || sreg->i_alpha == 0) ? NULL : sreg->p_picture); + + if (spe_no_pic(spe)) + return spe; + + spe->alpha = sreg->i_alpha; + + spe_update_rect(spe, spic, sreg); + + spe->pt = polltask_new_timer(sys->speq, spe_convert_cb, spe); + + return spe; +} + +static void +spe_delete(subpic_ent_t ** const ppspe) +{ + subpic_ent_t * const spe = *ppspe; + + if (spe == NULL) + return; + *ppspe = NULL; + + polltask_delete(&spe->pt); + subpic_ent_flush(spe); + free(spe); +} + +static int +spe_convert(subpic_ent_t * const spe) +{ + if (spe->pt != NULL) + pollqueue_add_task(spe->pt, 0); + return 0; +} + +static void +commit_req(vout_display_sys_t * const sys, const unsigned int layer) +{ + sys->planes[layer].commit_req = true; +} + +static void +commit_do(vout_display_t * const vd, vout_display_sys_t * const sys) +{ + int i; + bool flush_req = false; + + for (i = MAX_SUBPICS + PLANE_SUB - 1; i >= PLANE_VID; --i) + { + if (sys->planes[i].commit_req) + { + sys->planes[i].commit_req = false; + wl_surface_commit(sys->planes[i].surface); + flush_req = true; + } + } + if (sys->planes[PLANE_BKG].commit_req) + { + struct wl_surface * const bkg_surface = bkg_surface_get_lock(vd, sys); + if (bkg_surface != NULL) + { + if (sys->bkg_viewport) + { + wp_viewport_set_destination(sys->bkg_viewport, sys->bkg_w, sys->bkg_h); + wl_surface_commit(bkg_surface); + } + bkg_surface_unlock(vd, sys); + flush_req = true; + } + sys->planes[PLANE_BKG].commit_req = false; + } + if (flush_req) + wl_display_flush(video_display(sys)); +} + +static void +clear_surface_buffer(struct wl_surface * surface) +{ + if (surface == NULL) + return; + wl_surface_attach(surface, NULL, 0, 0); + wl_surface_commit(surface); +} + +static void +plane_clear(subplane_t * const plane) +{ + spe_delete(&plane->spe_next); + spe_delete(&plane->spe_cur); + plane->buffer_attached = false; + clear_surface_buffer(plane->surface); +} + +static void +clear_all_buffers(vout_display_sys_t * const sys, const bool bkg_valid) +{ + for (unsigned int i = MAX_SUBPICS + PLANE_SUB - 1; i >= PLANE_VID; --i) + plane_clear(sys->planes + i); + spe_delete(&sys->video_spe_prep); + + if (bkg_valid) + clear_surface_buffer(sys->last_embed_surface); +} + +static void +plane_destroy(subplane_t * const spl) +{ + viewport_destroy(&spl->viewport); + subsurface_destroy(&spl->subsurface); + surface_destroy(&spl->surface); + // Zap all tracking vars + spl->buffer_attached = false; + spl->trans = 0; + memset(&spl->src_rect, 0, sizeof(spl->src_rect)); + memset(&spl->dst_rect, 0, sizeof(spl->dst_rect)); +} + +static int +plane_create(vout_display_sys_t * const sys, subplane_t * const plane, + struct wl_surface * const parent, + const int commit_parent, + struct wl_surface * const above, + const bool sync) +{ + plane->commit_parent = commit_parent; + if ((plane->surface = wl_compositor_create_surface(video_compositor(sys))) == NULL || + (plane->subsurface = wl_subcompositor_get_subsurface(sys->bound.subcompositor, plane->surface, parent)) == NULL || + (plane->viewport = wp_viewporter_get_viewport(sys->bound.viewporter, plane->surface)) == NULL) + return VLC_EGENERIC; + wl_subsurface_place_above(plane->subsurface, above); + if (sync) + wl_subsurface_set_sync(plane->subsurface); + else + wl_subsurface_set_desync(plane->subsurface); + wl_surface_set_input_region(plane->surface, sys->region_none); + return 0; +} + +static void +unmap_all(vout_display_sys_t * const sys, const bool bkg_valid) +{ + clear_all_buffers(sys, bkg_valid); + + // Free subpic resources + for (unsigned int i = MAX_SUBPICS + PLANE_SUB - 1; i >= PLANE_VID; --i) + plane_destroy(sys->planes + i); + + viewport_destroy(&sys->bkg_viewport); +} + +static struct wl_surface * +bkg_surface_get_lock(vout_display_t * const vd, vout_display_sys_t * const sys) +{ + if (!sys->embed) { + msg_Err(vd, "%s: Embed NULL", __func__); + return NULL; + } + + vlc_mutex_lock(&sys->embed->handle_lock); + + if (sys->embed->handle.wl != sys->last_embed_surface || sys->embed->handle_seq != sys->last_embed_seq) + { + msg_Warn(vd, "%s: Embed surface changed %p (%u)->%p (%u)", __func__, + sys->last_embed_surface, sys->last_embed_seq, + sys->embed->handle.wl, sys->embed->handle_seq); + + sys->last_embed_surface = sys->embed->handle.wl; + sys->last_embed_seq = sys->embed->handle_seq; + unmap_all(sys, false); + } + + if (sys->last_embed_surface == NULL) + vlc_mutex_unlock(&sys->embed->handle_lock); + + return sys->last_embed_surface; +} + +static void +bkg_surface_unlock(vout_display_t * const vd, vout_display_sys_t * const sys) +{ + VLC_UNUSED(vd); + vlc_mutex_unlock(&sys->embed->handle_lock); +} + +static int +make_subpic_surfaces(vout_display_t * const vd, vout_display_sys_t * const sys) +{ + unsigned int i; + subplane_t * const subplanes = sys->planes + PLANE_SUB; + struct wl_surface * const surface = video_surface(sys); + struct wl_surface * below = surface; + int rv; + + if (subplanes[0].surface) + return VLC_SUCCESS; + + for (i = 0; i != MAX_SUBPICS; ++i) + { + subplane_t * const plane = subplanes + i; + if ((rv = plane_create(sys, plane, surface, PLANE_VID, below, true)) != 0) + { + msg_Err(vd, "%s: Failed to create subpic plane %d", __func__, i); + return rv; + } + below = plane->surface; + } + return VLC_SUCCESS; +} + +static int +make_background_and_video(vout_display_t * const vd, vout_display_sys_t * const sys) +{ + // Build a background + // Use single_pixel_surface extension if we have it & want a simple + // single colour (black) patch + video_dmabuf_release_env_t * vdre = NULL; + struct wl_buffer * w_buffer = NULL; + struct wl_surface * bkg_surface = NULL; + + if (sys->bkg_viewport) + return VLC_SUCCESS; + +#if HAVE_WAYLAND_SINGLE_PIXEL_BUFFER + if (sys->bound.single_pixel_buffer_manager_v1 && !sys->chequerboard) + { + w_buffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( + sys->bound.single_pixel_buffer_manager_v1, + 0, 0, 0, UINT32_MAX); // R, G, B, A + vdre = vdre_new_null(); + } + else +#endif + { + // Buffer width & height - not display + const unsigned int width = sys->chequerboard ? 640 : 32; + const unsigned int height = sys->chequerboard ? 480 : 32; + const unsigned int stride = width * 4; + struct dmabuf_h * const dh = picpool_get(sys->subpic_pool, stride * height); + const AVDRMFrameDescriptor dfd = { + .nb_objects = 1, + .objects = {{ + .fd = dmabuf_fd(dh), + .size = dmabuf_size(dh)}}, + .nb_layers = 1, + .layers = {{ + .format = DRM_FORMAT_XRGB8888, + .nb_planes = 1, + .planes = {{.pitch = stride}} + }} + }; + + if (dh == NULL) + { + msg_Err(vd, "Failed to get DmaBuf for background"); + goto error; + } + + vdre = vdre_new_dh(dh, sys->pollq); + + dmabuf_write_start(dh); + if (sys->chequerboard) + chequerboard(dmabuf_map(dh), stride, width, height); + else + fill_uniform(dmabuf_map(dh), stride, width, height, 0xff000000); + dmabuf_write_end(dh); + + w_buffer = dfd_make_buffer(vd, sys, dmabuf_is_fake(dh), &dfd, width, height, 0); + } + if (!w_buffer || !vdre) + { + msg_Err(vd, "Failed to create background buffer"); + goto error; + } + + if ((bkg_surface = bkg_surface_get_lock(vd, sys)) == NULL) + goto error; + + sys->bkg_viewport = wp_viewporter_get_viewport(sys->bound.viewporter, bkg_surface); + if (sys->bkg_viewport == NULL) + { + msg_Err(vd, "Failed to create background viewport"); + goto err_unlock; + } + +#if CHECK_VDRE_COUNTS + vdre_add_check(vdre, &sys->vdre_check_bkg); +#endif + vdre_eq_ref(vdre, sys->eq); + wl_buffer_add_listener(w_buffer, &vdre_buffer_listener, vdre); + wl_surface_attach(bkg_surface, w_buffer, 0, 0); + vdre = NULL; + w_buffer = NULL; + + wp_viewport_set_destination(sys->bkg_viewport, sys->bkg_w, sys->bkg_h); + wl_surface_set_opaque_region(bkg_surface, sys->region_all); + + wl_surface_damage(bkg_surface, 0, 0, INT32_MAX, INT32_MAX); + + if (plane_create(sys, sys->planes + PLANE_VID, bkg_surface, PLANE_BKG, bkg_surface, false) != 0) + { + msg_Err(vd, "Failed to create video plane"); + goto err_unlock; + } + + wl_surface_set_opaque_region(video_surface(sys), sys->region_all); + + commit_req(sys, PLANE_BKG); + + bkg_surface_unlock(vd, sys); + + return VLC_SUCCESS; + +err_unlock: + bkg_surface_unlock(vd, sys); +error: + buffer_destroy(&w_buffer); + vdre_delete(&vdre); + return VLC_ENOMEM; +} + +// Get tranform & adjusted source coords for orientation +static enum wl_output_transform +transform_from_fmt(const video_format_t * const fmt, vout_display_place_t * const s) +{ + const int rx_offset = fmt->i_width - (fmt->i_visible_width + fmt->i_x_offset); + const int by_offset = fmt->i_height - (fmt->i_visible_height + fmt->i_y_offset); + + switch (fmt->orientation) + { + case ORIENT_ROTATED_90: // ORIENT_RIGHT_TOP, + *s = (vout_display_place_t){ + .x = by_offset, + .y = fmt->i_x_offset, + .width = fmt->i_visible_height, + .height = fmt->i_visible_width}; + return WL_OUTPUT_TRANSFORM_90; + + case ORIENT_ROTATED_180: // ORIENT_BOTTOM_RIGHT, + *s = (vout_display_place_t){ + .x = by_offset, + .y = rx_offset, + .width = fmt->i_visible_width, + .height = fmt->i_visible_height}; + return WL_OUTPUT_TRANSFORM_180; + + case ORIENT_ROTATED_270: // ORIENT_LEFT_BOTTOM, + *s = (vout_display_place_t){ + .x = fmt->i_y_offset, + .y = rx_offset, + .width = fmt->i_visible_height, + .height = fmt->i_visible_width}; + return WL_OUTPUT_TRANSFORM_270; + + case ORIENT_HFLIPPED: // ORIENT_TOP_RIGHT, + *s = (vout_display_place_t){ + .x = rx_offset, + .y = fmt->i_y_offset, + .width = fmt->i_visible_width, + .height = fmt->i_visible_height}; + return WL_OUTPUT_TRANSFORM_FLIPPED; + + case ORIENT_VFLIPPED: // ORIENT_BOTTOM_LEFT, + *s = (vout_display_place_t){ + .x = fmt->i_x_offset, + .y = by_offset, + .width = fmt->i_visible_width, + .height = fmt->i_visible_height}; + return WL_OUTPUT_TRANSFORM_FLIPPED_180; + + case ORIENT_TRANSPOSED: // ORIENT_LEFT_TOP, + *s = (vout_display_place_t){ + .x = fmt->i_y_offset, + .y = fmt->i_x_offset, + .width = fmt->i_visible_height, + .height = fmt->i_visible_width}; + return WL_OUTPUT_TRANSFORM_FLIPPED_90; + + case ORIENT_ANTI_TRANSPOSED: // ORIENT_RIGHT_BOTTOM, + *s = (vout_display_place_t){ + .x = rx_offset, + .y = by_offset, + .width = fmt->i_visible_height, + .height = fmt->i_visible_width}; + return WL_OUTPUT_TRANSFORM_FLIPPED_270; + + case ORIENT_NORMAL: // ORIENT_TOP_LEFT, + default: + *s = (vout_display_place_t){ + .x = fmt->i_x_offset, + .y = fmt->i_y_offset, + .width = fmt->i_visible_width, + .height = fmt->i_visible_height}; + return WL_OUTPUT_TRANSFORM_NORMAL; + } +} + +static void +place_rects(vout_display_t * const vd, + const vout_display_cfg_t * const cfg) +{ + vout_display_sys_t * const sys = vd->sys; + + vout_display_PlacePicture(&sys->video_dst_rect, &vd->source, cfg, true); + sys->video_trans = transform_from_fmt(&vd->source, &sys->video_src_rect); +} + +static const drmu_vlc_fmt_info_t * +find_fmt_fallback(vout_display_t * const vd, const fmt_list_t * const flist, const vlc_fourcc_t * fallback) +{ + const drmu_vlc_fmt_info_t * fmti_best = NULL; + int pri_best = INT_MAX; + + for (; *fallback != 0; ++fallback) + { + const video_frame_format_t vf = {.i_chroma = *fallback}; + const drmu_vlc_fmt_info_t * fmti = NULL; + + msg_Dbg(vd, "Try %s", drmu_log_fourcc(*fallback)); + + for (fmti = drmu_vlc_fmt_info_find_vlc(&vf); + fmti != NULL; + fmti = drmu_vlc_fmt_info_find_vlc_next(&vf, fmti)) + { + const int pri = fmt_list_find(flist, fmti); + msg_Dbg(vd, "Try %s -> %s %"PRIx64": %d", drmu_log_fourcc(*fallback), + drmu_log_fourcc(drmu_vlc_fmt_info_drm_pixelformat(fmti)), + drmu_vlc_fmt_info_drm_modifier(fmti), pri); + if (pri >= 0 && pri < pri_best) + { + fmti_best = fmti; + pri_best = pri; + + // If we've got pri 0 then might as well stop now + if (pri == 0) + return fmti_best; + } + } + } + + return fmti_best; +} + +static const drmu_vlc_fmt_info_t * +get_usable_format(vout_display_t * const vd, + const fmt_list_t * const flist, + const video_format_t * const fmt) +{ + const drmu_vlc_fmt_info_t * pic_fmti; + + // Check PIC DRM format here + if ((pic_fmti = drmu_vlc_fmt_info_find_vlc(fmt)) == NULL || + fmt_list_find(flist, pic_fmti) < 0) + { + static const vlc_fourcc_t fallback2[] = { + VLC_CODEC_I420, + VLC_CODEC_RGB32, + 0 + }; + + msg_Warn(vd, "Could not find %s -> %s mod %#"PRIx64" in supported formats", + drmu_log_fourcc(fmt->i_chroma), + drmu_log_fourcc(drmu_vlc_fmt_info_drm_pixelformat(pic_fmti)), + drmu_vlc_fmt_info_drm_modifier(pic_fmti)); + + if ((pic_fmti = find_fmt_fallback(vd, flist, + vlc_fourcc_IsYUV(fmt->i_chroma) ? + vlc_fourcc_GetYUVFallback(fmt->i_chroma) : + vlc_fourcc_GetRGBFallback(fmt->i_chroma))) == NULL && + (pic_fmti = find_fmt_fallback(vd, flist, fallback2)) == NULL) { + msg_Warn(vd, "Failed to find any usable fallback format"); + } + } + return pic_fmti; +} + +static int +set_req_format(vout_display_t * const vd, const vout_display_sys_t * const sys, video_format_t * const fmt) +{ + const video_format_t * const src_fmt = &vd->source; + const drmu_vlc_fmt_info_t * const fmti = get_usable_format(vd, sys->use_shm ? &sys->shm_fmts : &sys->dmabuf_fmts, src_fmt); + + if (fmti == NULL) + return VLC_EGENERIC; + + *fmt = *src_fmt; + fmt->i_chroma = drmu_vlc_fmt_info_vlc_chroma(fmti); + drmu_vlc_fmt_info_vlc_rgb_masks(fmti, &fmt->i_rmask, &fmt->i_gmask, &fmt->i_bmask); + + return VLC_SUCCESS; +} + +static void +plane_set_rect(vout_display_sys_t * const sys, subplane_t * const plane, const subpic_ent_t * const spe) +{ + // Always called after the attach + if (!plane->buffer_attached || spe == NULL) + return; + + const vout_display_place_t dst_rect = place_rescale(spe->dst_rect, + plane->commit_parent == PLANE_VID ? place_zoffset(sys->video_dst_rect) : sys->video_dst_rect, + spe->orig_rect); + + if (spe->trans != plane->trans) + { + wl_surface_set_buffer_transform(plane->surface, spe->trans); + plane->commit_req = true; + } + if (!place_eq(spe->src_rect, plane->src_rect)) + { + wp_viewport_set_source(plane->viewport, + wl_fixed_from_int(spe->src_rect.x), wl_fixed_from_int(spe->src_rect.y), + wl_fixed_from_int(spe->src_rect.width), wl_fixed_from_int(spe->src_rect.height)); + plane->commit_req = true; + } + if (!place_xy_eq(dst_rect, plane->dst_rect)) + { + wl_subsurface_set_position(plane->subsurface, dst_rect.x, dst_rect.y); + plane->commit_req = true; + } + if (!place_wh_eq(dst_rect, plane->dst_rect)) + { + wp_viewport_set_destination(plane->viewport, dst_rect.width, dst_rect.height); + commit_req(sys, plane->commit_parent); // Subsurface pos needs parent commit (video) + } + + plane->trans = spe->trans; + plane->src_rect = spe->src_rect; + plane->dst_rect = dst_rect; +} + +static void Prepare(vout_display_t *vd, picture_t *pic, subpicture_t *subpic) +{ + vout_display_sys_t * const sys = vd->sys; + unsigned int n = 0; + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s: Surface: %p", __func__, sys->embed->handle.wl); +#endif + + { + subpic_ent_t * const spe = spe_new_pic(vd, sys, pic); + if (spe == NULL) + { + msg_Err(vd, "Failed to create new video spe"); + return; + } + spe->trans = sys->video_trans; + spe->src_rect = sys->video_src_rect; + spe->dst_rect = sys->video_dst_rect; + spe->orig_rect = spe->dst_rect; + + if (sys->video_spe_prep) + { + msg_Err(vd, "Spe prep != NULL"); + spe_delete(&sys->video_spe_prep); + ++sys->stats.frame_discard; + } + sys->video_spe_prep = spe; + + if (drmu_format_vlc_to_drm_prime(&pic->format, NULL) == 0) + copy_subpic_to_w_buffer(vd, sys, pic, 0xff, &spe->vdre, &spe->wb); + else + do_display_dmabuf(vd, sys, pic, &spe->vdre, &spe->wb); + atomic_store(&spe->ready, 1); + wl_display_flush(video_display(sys)); // Kick off any work required by Wayland + } + + // Attempt to import the subpics + for (const subpicture_t * spic = subpic; spic != NULL; spic = spic->p_next) + { + for (const subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) + { + subplane_t * const plane = sys->planes + n + PLANE_SUB; + + if (plane->spe_next != NULL) + { + if (!spe_changed(plane->spe_next, sreg)) + spe_update_rect(plane->spe_next, spic, sreg); + // else if changed ignore as we are already doing stuff + } + else + { + if (!spe_changed(plane->spe_cur, sreg)) + spe_update_rect(plane->spe_cur, spic, sreg); + else + { + plane->spe_next = spe_new(vd, sys, spic, sreg); + spe_convert(plane->spe_next); + } + } + + if (++n == MAX_SUBPICS) + goto subpics_done; + } + } +subpics_done: + + // Clear any other entries + for (; n != MAX_SUBPICS; ++n) { + subplane_t * const plane = sys->planes + n + PLANE_SUB; + if (plane->spe_next == NULL && spe_changed(plane->spe_cur, NULL)) + plane->spe_next = spe_new(vd, sys, NULL, NULL); + } + +#if TRACE_ALL + msg_Dbg(vd, ">>> %s: Surface: %p", __func__, sys->embed->handle.wl); +#endif +} + +static void +do_resize(vout_display_t * const vd, vout_display_sys_t * const sys) +{ + if (!sys->bkg_viewport) + return; + + for (unsigned int i = PLANE_VID; i != PLANE_SUB + MAX_SUBPICS; ++i) + { + subplane_t * const plane = sys->planes + i; + subpic_ent_t * spe = plane->spe_cur; + + plane_set_rect(sys, plane, spe); + } + + if (sys->bkg_viewport != NULL && (vd->cfg->display.width != sys->bkg_w || vd->cfg->display.height != sys->bkg_h)) + { + msg_Dbg(vd, "Resize background: %dx%d", vd->cfg->display.width, vd->cfg->display.height); + commit_req(sys, PLANE_BKG); + } + sys->bkg_w = vd->cfg->display.width; + sys->bkg_h = vd->cfg->display.height; +} + +static void +do_display(vout_display_t * const vd, vout_display_sys_t * const sys) +{ +// msg_Info(vd, "<<< %s: Surface: %p", __func__, sys->embed->handle.wl); + + sys->stats.time_frameN = mdate(); + if (!sys->stats.time_frame0) + sys->stats.time_frame0 = sys->stats.time_frameN; + ++sys->stats.frame_n; + + if (spe_no_pic(sys->planes[PLANE_VID].spe_next)) + { + msg_Warn(vd, "%s: No current pic", __func__); + return; + } + + if (make_background_and_video(vd, sys) != 0) + { + msg_Warn(vd, "%s: Make background fail", __func__); + return; + } + make_subpic_surfaces(vd, sys); + + for (unsigned int i = PLANE_VID; i != PLANE_SUB + MAX_SUBPICS; ++i) + { + subplane_t * const plane = sys->planes + i; + subpic_ent_t * spe = plane->spe_cur; + + if (plane->spe_next && atomic_load(&plane->spe_next->ready)) + { + spe_delete(&plane->spe_cur); + spe = plane->spe_cur = plane->spe_next; + plane->spe_next = NULL; + subpic_ent_attach(plane, spe, sys->eq); + } + } + + do_resize(vd, sys); + + commit_do(vd, sys); + return; +} + +static void Display(vout_display_t *vd, picture_t *pic, subpicture_t *subpic) +{ + vout_display_sys_t * const sys = vd->sys; + +#if TRACE_ALL || 1 + msg_Dbg(vd, "<<< %s: Surface: %p", __func__, sys->embed->handle.wl); +#endif + + // Check we have a surface to put the video on + if (bkg_surface_get_lock(vd, sys) == NULL) + { + msg_Warn(vd, "%s: No background surface", __func__); + goto done; + } + bkg_surface_unlock(vd, sys); + + if (!sys->video_spe_prep) + { + msg_Warn(vd, "No prepared video spe"); + goto done; + } + + if (sys->planes[PLANE_VID].spe_next) + { + msg_Warn(vd, "Current video spe discarded"); + spe_delete(&sys->planes[PLANE_VID].spe_next); + ++sys->stats.frame_discard; + } + sys->planes[PLANE_VID].spe_next = sys->video_spe_prep; + sys->video_spe_prep = NULL; + + ++sys->stats.frame_display; + do_display(vd, sys); + +done: + if (subpic) + subpicture_Delete(subpic); + picture_Release(pic); + +#if TRACE_ALL + msg_Dbg(vd, ">>> %s: Surface: %p", __func__, sys->embed->handle.wl); +#endif +} + +static int Control(vout_display_t *vd, int query, va_list ap) +{ + vout_display_sys_t * const sys = vd->sys; + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s: Query=%d", __func__, query); +#endif + + switch (query) + { + case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: + case VOUT_DISPLAY_CHANGE_SOURCE_CROP: + place_rects(vd, vd->cfg); + do_resize(vd, sys); + commit_do(vd, sys); + break; + + case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: + case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED: + case VOUT_DISPLAY_CHANGE_ZOOM: + { + const vout_display_cfg_t * const cfg = va_arg(ap, const vout_display_cfg_t *); + + place_rects(vd, cfg); + do_resize(vd, sys); + commit_do(vd, sys); + break; + } + + case VOUT_DISPLAY_RESET_PICTURES: + msg_Err(vd, "Unexpected reset pictures"); + return VLC_EGENERIC; + + default: + msg_Err(vd, "unknown request %d", query); + return VLC_EGENERIC; + } + +#if TRACE_ALL + msg_Dbg(vd, ">>> %s: Surface: %p", __func__, sys->embed->handle.wl); +#endif + return VLC_SUCCESS; +} + +static void linux_dmabuf_v1_listener_format(void *data, + struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, + uint32_t format) +{ + // Superceeded by _modifier + vout_display_t * const vd = data; + vout_display_sys_t * const sys = vd->sys; + (void)zwp_linux_dmabuf_v1; +#if TRACE_ALL + msg_Dbg(vd, "%s[%p], %.4s", __func__, (void*)vd, (const char *)&format); +#endif + fmt_list_add(&sys->dmabuf_fmts, format, DRM_FORMAT_MOD_LINEAR, 0); +} + +static void +linux_dmabuf_v1_listener_modifier(void *data, + struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, + uint32_t format, + uint32_t modifier_hi, + uint32_t modifier_lo) +{ + vout_display_t * const vd = data; + vout_display_sys_t * const sys = vd->sys; + (void)zwp_linux_dmabuf_v1; +#if TRACE_ALL + msg_Dbg(vd, "%s[%p], %.4s %08x%08x", __func__, (void*)vd, (const char *)&format, modifier_hi, modifier_lo); +#endif + fmt_list_add(&sys->dmabuf_fmts, format, modifier_lo | ((uint64_t)modifier_hi << 32), 0); +} + +static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_v1_listener = { + .format = linux_dmabuf_v1_listener_format, + .modifier = linux_dmabuf_v1_listener_modifier, +}; + +static void shm_listener_format(void *data, + struct wl_shm *shm, + uint32_t format) +{ + vout_display_t * const vd = data; + vout_display_sys_t * const sys = vd->sys; + (void)shm; + + if (format == 0) + format = DRM_FORMAT_ARGB8888; + else if (format == 1) + format = DRM_FORMAT_XRGB8888; + +#if TRACE_ALL + msg_Dbg(vd, "%s[%p], %.4s", __func__, (void*)vd, (const char *)&format); +#endif + fmt_list_add(&sys->shm_fmts, format, DRM_FORMAT_MOD_LINEAR, 0); +} + +static const struct wl_shm_listener shm_listener = { + .format = shm_listener_format, +}; + + +static void w_bound_add(vout_display_t * const vd, w_bound_t * const b, + struct wl_registry * const registry, + const uint32_t name, const char *const iface, const uint32_t vers) +{ +#if TRACE_ALL + msg_Dbg(vd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers); +#endif + if (strcmp(iface, wl_subcompositor_interface.name) == 0) + b->subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); + else + if (strcmp(iface, wl_shm_interface.name) == 0) + { + b->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(b->shm, &shm_listener, vd); + } + else + if (strcmp(iface, wp_viewporter_interface.name) == 0) + b->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); + else + if (!strcmp(iface, wl_compositor_interface.name)) + { + if (vers >= 4) + b->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); + else + msg_Warn(vd, "Interface %s wanted v 4 got v %d", wl_compositor_interface.name, vers); + } + else + if (!vd->sys->use_shm && strcmp(iface, zwp_linux_dmabuf_v1_interface.name) == 0) + { + if (vers >= 3) + { + b->linux_dmabuf_v1 = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, 3); + zwp_linux_dmabuf_v1_add_listener(b->linux_dmabuf_v1, &linux_dmabuf_v1_listener, vd); + } + else + msg_Warn(vd, "Interface %s wanted v 3 got v %d", zwp_linux_dmabuf_v1_interface.name, vers); + } +#if HAVE_WAYLAND_SINGLE_PIXEL_BUFFER + else + if (strcmp(iface, wp_single_pixel_buffer_manager_v1_interface.name) == 0) + b->single_pixel_buffer_manager_v1 = wl_registry_bind(registry, name, &wp_single_pixel_buffer_manager_v1_interface, 1); +#endif +} + +static void w_bound_destroy(w_bound_t * const b) +{ + if (b->viewporter != NULL) + wp_viewporter_destroy(b->viewporter); + if (b->linux_dmabuf_v1 != NULL) + zwp_linux_dmabuf_v1_destroy(b->linux_dmabuf_v1); + if (b->subcompositor != NULL) + wl_subcompositor_destroy(b->subcompositor); + if (b->compositor != NULL) + wl_compositor_destroy(b->compositor); + if (b->shm != NULL) + wl_shm_destroy(b->shm); +#if HAVE_WAYLAND_SINGLE_PIXEL_BUFFER + if (b->single_pixel_buffer_manager_v1) + wp_single_pixel_buffer_manager_v1_destroy(b->single_pixel_buffer_manager_v1); +#endif + memset(b, 0, sizeof(*b)); +} + +static void registry_global_cb(void *data, struct wl_registry *registry, + uint32_t name, const char *iface, uint32_t vers) +{ + vout_display_t * const vd = data; + vout_display_sys_t * const sys = vd->sys; + + w_bound_add(vd, &sys->bound, registry, name, iface, vers); +} + +static void registry_global_remove_cb(void *data, struct wl_registry *registry, + uint32_t name) +{ + vout_display_t *vd = data; + + msg_Dbg(vd, "global remove %3"PRIu32, name); + (void) registry; +} + +static const struct wl_registry_listener registry_cbs = +{ + registry_global_cb, + registry_global_remove_cb, +}; + +struct registry_scan_bounce_env { + struct wl_registry * registry; + eq_env_t * const eq; + vout_display_t * const vd; +}; + +// Only safe place to add a listener is on pollq thread +static void +registry_scan_bounce_cb(void * v, short revents) +{ + struct registry_scan_bounce_env * rsbe = v; + (void)revents; + rsbe->registry = wl_display_get_registry(eq_wrapper(rsbe->eq)), + wl_registry_add_listener(rsbe->registry, ®istry_cbs, rsbe->vd); +} + +// N.B. Having got the registry with a wrapped display +// by default everything we do with the newly bound interfaces will turn +// up on the wrapped queue + +static int +registry_scan(vout_display_t * const vd, vout_display_sys_t * const sys) +{ + struct registry_scan_bounce_env rsbe = { + .registry = NULL, + .eq = sys->eq, + .vd = vd + }; + + pollqueue_callback_once(rsbe.eq->pq, registry_scan_bounce_cb, &rsbe); + + eventq_sync(rsbe.eq); + // Registry callback provokes shm & fmt callbacks so another sync needed + eventq_sync(rsbe.eq); + + if (rsbe.registry == NULL) + return -1; + + wl_registry_destroy(rsbe.registry); + return 0; +} + +static void Close(vlc_object_t *obj) +{ + vout_display_t * const vd = (vout_display_t *)obj; + vout_display_sys_t * const sys = vd->sys; + + msg_Dbg(vd, "<<< %s", __func__); + + if (sys == NULL) + return; + + if (sys->embed == NULL) + goto no_window; + + if (sys->want_stats) + msg_stats(vd, &sys->stats); + + if (bkg_surface_get_lock(vd, sys) != NULL) + { + unmap_all(sys, true); + bkg_surface_unlock(vd, sys); + } + + region_destroy(&sys->region_all); + region_destroy(&sys->region_none); + + pollqueue_finish(&sys->speq); + + w_bound_destroy(&sys->bound); + + eventq_sync(sys->eq); + + if (eq_finish(&sys->eq) != 0) + msg_Err(vd, "Failed to reclaim all buffers on close"); + + // There is a risk of deadlock here if we wait for the pq to die as some + // wl buffers may only be relased after close returns so just unref and the + // pq will clean up after itself once the last buffer has been released. + pollqueue_unref(&sys->pollq); + + vout_display_DeleteWindow(vd, sys->embed); + sys->embed = NULL; + + kill_pool(sys); + picpool_unref(&sys->subpic_pool); + + free(sys->subpic_chromas); + +no_window: + fmt_list_uninit(&sys->dmabuf_fmts); + fmt_list_uninit(&sys->shm_fmts); + +#if CHECK_VDRE_COUNTS + msg_Info(vd, "%s: vdre_check_bkg: %d", __func__, atomic_load(&sys->vdre_check_bkg)); + msg_Info(vd, "%s: vdre_check_fg: %d", __func__, atomic_load(&sys->vdre_check_fg)); +#endif + + free(sys); + + msg_Dbg(vd, ">>> %s", __func__); +} + +static int Open(vlc_object_t *obj) +{ + vout_display_t * const vd = (vout_display_t *)obj; + vout_display_sys_t *sys; + fmt_list_t * flist = NULL; + video_format_t req_fmt; + + if (var_InheritBool(vd, WL_DMABUF_DISABLE_NAME)) + return VLC_EGENERIC; + + sys = calloc(1, sizeof(*sys)); + if (unlikely(sys == NULL)) + return VLC_ENOMEM; + + vd->sys = sys; + if (fmt_list_init(&sys->dmabuf_fmts, 128)) { + msg_Err(vd, "Failed to allocate dmabuf format list!"); + goto error; + } + if (fmt_list_init(&sys->shm_fmts, 32)) { + msg_Err(vd, "Failed to allocate shm format list!"); + goto error; + } + + sys->use_shm = var_InheritBool(vd, WL_DMABUF_USE_SHM_NAME); + sys->chequerboard = var_InheritBool(vd, WL_DMABUF_CHEQUERBOARD_NAME); + + /* Get window */ + sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_WAYLAND); + if (sys->embed == NULL) { + msg_Dbg(vd, "Cannot create window - probably not using Wayland"); + goto error; + } + sys->last_embed_surface = sys->embed->handle.wl; + sys->last_embed_seq = sys->embed->handle_seq; + + msg_Info(vd, "<<< %s: %s %dx%d(%dx%d @ %d,%d %d/%d), cfg.display: %dx%d, source: %dx%d(%dx%d @ %d,%d %d/%d)", __func__, + drmu_log_fourcc(vd->fmt.i_chroma), vd->fmt.i_width, vd->fmt.i_height, + vd->fmt.i_visible_width, vd->fmt.i_visible_height, vd->fmt.i_x_offset, vd->fmt.i_y_offset, + vd->fmt.i_sar_num, vd->fmt.i_sar_den, + vd->cfg->display.width, vd->cfg->display.height, + vd->source.i_width, vd->source.i_height, + vd->source.i_visible_width, vd->source.i_visible_height, vd->source.i_x_offset, vd->source.i_y_offset, + vd->source.i_sar_num, vd->source.i_sar_den); + + if ((sys->pollq = pollqueue_new()) == NULL || + (sys->speq = pollqueue_new()) == NULL) + { + msg_Err(vd, "Failed to create pollqueues"); + goto error; + } + if ((sys->eq = eq_new(video_display(sys), sys->pollq)) == NULL) + { + msg_Err(vd, "Failed to create event Q"); + goto error; + } + + if (registry_scan(vd, sys) != 0) + { + msg_Err(vd, "Cannot get registry for display"); + goto error; + } + + if (sys->bound.compositor == NULL) { + msg_Warn(vd, "Interface %s missing", wl_compositor_interface.name); + goto error; + } + if (sys->bound.subcompositor == NULL) { + msg_Warn(vd, "Interface %s missing", wl_subcompositor_interface.name); + goto error; + } + if (sys->bound.viewporter == NULL) { + msg_Warn(vd, "Interface %s missing", wp_viewporter_interface.name); + goto error; + } + if (!sys->use_shm && sys->bound.linux_dmabuf_v1 == NULL) { + msg_Warn(vd, "Interface %s missing", zwp_linux_dmabuf_v1_interface.name); + goto error; + } + + fmt_list_sort(&sys->dmabuf_fmts); + fmt_list_sort(&sys->shm_fmts); + flist = sys->use_shm ? &sys->shm_fmts : &sys->dmabuf_fmts; + + // Check PIC DRM format here + if (set_req_format(vd, sys, &req_fmt) != VLC_SUCCESS) + goto error; + + // Get subpic format(s) - it is a list but VLC only looks at list[0] + { + static vlc_fourcc_t const tryfmts[] = { + VLC_CODEC_RGBA, + VLC_CODEC_BGRA, + VLC_CODEC_ARGB, + VLC_CODEC_VUYA, + VLC_CODEC_YUVA, + }; + unsigned int n = 0; + + if ((sys->subpic_chromas = calloc(ARRAY_SIZE(tryfmts) + 1, sizeof(vlc_fourcc_t))) == NULL) + goto error; + for (unsigned int i = 0; i != ARRAY_SIZE(tryfmts); ++i) + { + const video_frame_format_t vf = {.i_chroma = tryfmts[i]}; + if (fmt_list_find(flist, drmu_vlc_fmt_info_find_vlc(&vf)) >= 0) + sys->subpic_chromas[n++] = tryfmts[i]; + } + + if (n == 0) + msg_Warn(vd, "No compatible subpic formats found"); + } + + { + struct dmabufs_ctl *dbsc = sys->use_shm ? dmabufs_shm_new() : dmabufs_ctl_new(); + if (dbsc == NULL) + { + msg_Err(vd, "Failed to create dmabuf ctl"); + goto error; + } + sys->subpic_pool = picpool_new(dbsc); + dmabufs_ctl_unref(&dbsc); + if (sys->subpic_pool == NULL) + { + msg_Err(vd, "Failed to create picpool"); + goto error; + } + } + + sys->bkg_w = vd->cfg->display.width; + sys->bkg_h = vd->cfg->display.height; + + sys->region_all = wl_compositor_create_region(video_compositor(sys)); + wl_region_add(sys->region_all, 0, 0, INT32_MAX, INT32_MAX); + sys->region_none = wl_compositor_create_region(video_compositor(sys)); + wl_region_add(sys->region_all, 0, 0, 0, 0); + + vd->fmt = req_fmt; + + place_rects(vd, vd->cfg); + + sys->want_stats = var_InheritBool(vd, WL_DMABUF_STATS_NAME); + + // If we can invalidate the pic pool then DRI is disabled - we want DRI + vd->info = (vout_display_info_t){ + .is_slow = false, + .has_double_click = false, + .needs_hide_mouse = false, + .has_pictures_invalid = false, + .subpicture_chromas = sys->subpic_chromas, + }; + + vd->pool = vd_dmabuf_pool; + vd->prepare = Prepare; + vd->display = Display; + vd->control = Control; + + msg_Dbg(vd, ">>> %s: OK: %.4s (%#x/%#x/%#x)", __func__, + (char*)&vd->fmt.i_chroma, + vd->fmt.i_rmask, vd->fmt.i_gmask, vd->fmt.i_bmask); + return VLC_SUCCESS; + +error: + Close(obj); + msg_Dbg(vd, ">>> %s: ERROR", __func__); + return VLC_EGENERIC; +} + +vlc_module_begin() + set_shortname(N_("WL DMABUF")) + set_description(N_("Wayland dmabuf video output")) + set_category(CAT_VIDEO) + set_subcategory(SUBCAT_VIDEO_VOUT) + set_capability("vout display", 310) + set_callbacks(Open, Close) + add_shortcut("wl-dmabuf") + add_bool(WL_DMABUF_DISABLE_NAME, false, WL_DMABUF_DISABLE_TEXT, WL_DMABUF_DISABLE_LONGTEXT, false) + add_bool(WL_DMABUF_USE_SHM_NAME, false, WL_DMABUF_USE_SHM_TEXT, WL_DMABUF_USE_SHM_LONGTEXT, false) + add_bool(WL_DMABUF_CHEQUERBOARD_NAME, false, WL_DMABUF_CHEQUERBOARD_TEXT, WL_DMABUF_CHEQUERBOARD_LONGTEXT, false) + add_bool(WL_DMABUF_STATS_NAME, false, WL_DMABUF_STATS_TEXT, WL_DMABUF_STATS_LONGTEXT, false) +vlc_module_end() diff --git a/pi-util/README.txt b/pi-util/README.txt new file mode 100644 index 0000000000..59a8655955 --- /dev/null +++ b/pi-util/README.txt @@ -0,0 +1,100 @@ +Release notes +============= + +This version should run with gpu-mem=64 with the default switches. Having +said that this will only allow for 1 stream. If you are playing >1 stream +(even transiently) then you will need more (say gpu_mem=128) and you will +need to set the --mmal-decoders option to the desired max number. The code +should give up cleanly if it cannot allocate a h/w video decoder and give +the stream to old-style ffmpeg decode, but as it stands in many cases it +thinks it has allocated a decoder cleanly only to find that it fails when +it tries to use it. + +Needs firmware from "Sep 13 2016 17:01:56" or later to work properly +("vcgencmd version" will give the date). + +There are a few command-line switches - in general you shouldn't use +them! + + +Decode and resizer options +-------------------------- + +--mmal-decode-opaque Set the decoder to use opaque frames between +decoder and resizer. This should be faster than i420 but doesn't work +with old firmware. This is the default with newer firmware (>= +2016-11-01). (see --mmal-decode-i420) + +--mmal-decode-i420 Set the decoder to use I420 frames between +decoder and resizer. This generates an unnecessary conversion but works +with all firmware. This is the default with older firmware (< +2016-11-01). (see --mmal-decode-opaque) + +--mmal-low-delay Force "low-delay" mode on the decoder pipe. This +reduces the number of buffered ES frames before the decoder. It isn't +exactly low-delay but is definitely lower than otherwise. May have a +slight performance penalty and increase the risk of stuttering. This mode +will be automatically set by Chrome for some streams. + +--mmal-resize-isp Use ISP resize rather than resizer. Is noticably +faster but requires --mmal-frame-copy or --mmal-zero-copy and newer +firmware. This is the default with newer firmware (>= 2016-11-01) and +enough gpu memory to support --mmal-frame-copy. + +--mmal-resize-resizer Use resizer rather than ISP. Slower than ISP +resize but supports older firmware and --mmal-slice-copy which may be +needed if GPU memory is very limited (as will be the case on a Pi1 with a +default setup). + +Copy-modes +---------- + +These are modes for getting frames out of mmal. Current default is +--mmal-frame-copy if --mmal-resize-isp is the default resizer or it looks +like the firmware doesn't support --mmal-slice-copy otherwise +--mmal-slice-copy is the default. Explicit use of a copy mode option will +override the default regardless of whether or not we think the firmware +supports the selected option. Only use one of of these flags. + +--mmal-zero-copy Pass gpu frames directly to chrome. Chrome +buffers some frames and stalls if it doesn't get them. So this option +needs 6+ gpu frames allocated. This is now a legacy and testing option as +--mmal-frame-copy is faster and you probably want to have gpu_mem=192 if +you are going to use it. Default frame-buffers = 6 (8M each) + +--mmal-frame-copy Copy frame at a time out of mmal to chrome. +Currently the fastest option. Needs 2+ gpu frames for plausible +performance. Default frame-buffers = 2 (8M each). You probably want +gpu-mem=80 for 1 decoder with this option. + +--mmal-slice-copy Copy frames out in 16-line slices. Has the +lowest memory overhead, but the highest CPU load. If this is selected +then --mmal-frame-buffers is the number of slice buffers. Default frame +buffers = 16 (~122k each). + +Misc options +------------ + +--enable-logging=stderr This is a standard option for chrome but worth +noting as the mmal code will print out its interpretation of the command +line options passed to it along with how much GPU memory it has detected +and the firmware date. + +--pi-patch-version Print out the versions of Chromium and Pi +patches. Chrome will then terminate + +--mmal-decoders= Set the number of mmal decoders we wil try to +create simultainiously. Default=1. If this number is exceeded then decoder +init will fail and chrome will fallback to ffmpeg decode. There is no +panalty for setting this to a large number if you wish to have "unlimited" +decoders. However if it is set too big and there isn't the gpu mem to +satisfy the requirements of the decode it may fail cleanly and revert to +software (ffmpeg) decode or init may appear to succeed and decode then +fails in an undefined manner. + +--mmal-frame-buffers= Set the number of gpu "frame" buffers (see +--mmal-xxx-copy). Change with care. + +--mmal-red-pixel Puts a red square in the top left of a frame +decoded by mmal so you can tell that it is active. Doesn't work if +zero-copy is set. diff --git a/pi-util/conf.sh b/pi-util/conf.sh new file mode 100755 index 0000000000..f3b52b81d2 --- /dev/null +++ b/pi-util/conf.sh @@ -0,0 +1,48 @@ +BASE=`pwd`/.. +A=arm-linux-gnueabihf +TOOLS=$BASE/tools/arm-bcm2708/gcc-arm-8.2-2019.01-x86_64-arm-linux-gnueabihf +SYSROOT2=$BASE/tools/arm-bcm2708/sysroot-glibc-8.2-2019.01-x86_64-arm-linux-gnueabihf +#TOOLS=$BASE/tools/arm-bcm2708/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf +#SYSROOT2=$BASE/tools/arm-bcm2708/sysroot-glibc-linaro-2.23-2017.05-arm-linux-gnueabihf +SYSROOT=`pwd`/sysroot/raspian_stretch_pi1-sysroot +HLIB3=/lib/$A +HLIB1=/usr/lib/$A +HLIB2=/usr/lib +HLIB4=/opt/vc/lib +HLIB5=/usr/local/lib/$A + +LIB2=$SYSROOT$HLIB2 +LIB1=$SYSROOT$HLIB1 +LIB3=$SYSROOT$HLIB3 +LIB4=$SYSROOT$HLIB4 +LIB5=$SYSROOT$HLIB5 + +#INCLUDES="-I$SYSROOT/usr/include -I$SYSROOT/usr/include/$A -I$SYSROOT/opt/vc/include -I$SYSROOT/usr/lib/arm-linux-gnueabihf/dbus-1.0/include -I$SYSROOT/usr/include/libdrm" +INCLUDES="-I$SYSROOT/usr/include -I$SYSROOT/usr/include/$A -I$SYSROOT/usr/local/include -I$SYSROOT/opt/vc/include -I$SYSROOT/usr/lib/arm-linux-gnueabihf/dbus-1.0/include" +#DEFINES="-ggdb -D__VCCOREVER__=0x04000000 -DQT_WARNING_DISABLE_DEPRECATED=\"\"" +DEFINES="-ggdb -D__VCCOREVER__=0x04000000" +ARCH="-march=armv7-a -mfpu=neon-vfpv4" +PREFIX=$TOOLS/bin/$A- + + +PATH="$TOOLS/bin:$PATH" \ + PKG_CONFIG="pkg-config --define-prefix" \ + PKG_CONFIG_PATH="$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/local/lib/$A/pkgconfig:$LIB3/pkgconfig" \ + CC=${PREFIX}gcc \ + CFLAGS="$ARCH $INCLUDES" \ + CPP=${PREFIX}cpp \ + CPPFLAGS="$ARCH $INCLUDES $DEFINES" \ + CXX=${PREFIX}g++ \ + CXXFLAGS="$ARCH $INCLUDES" \ + LDFLAGS="-ggdb -L$TOOLS/lib -L$LIB1 -L$LIB2 -L$LIB3 -L$LIB4 -L$LIB5 -Wl,-rpath=$HLIB1,-rpath-link=$LIB1,-rpath=$HLIB2,-rpath-link=$LIB2,-rpath=$HLIB3,-rpath-link=$LIB3,-rpath=$HLIB4,-rpath-link=$LIB4,-rpath=$HLIB5,-rpath-link=$LIB5,-rpath-link=`pwd`/src/.libs" \ + MOC="`which moc` -qt=5" \ + UIC="`which uic` -qt=5" \ + RCC="`which rcc` -qt=5" \ + ./configure --host=$A --enable-mmal-avcodec --disable-vdpau --disable-libva --enable-debug --disable-lua --disable-chromecast --disable-wayland --enable-gles2 --disable-opencv --enable-dav1d --disable-aom + +# ./configure --host=$A --enable-debug --disable-lua --disable-qt --disable-vdpau --disable-chromecast --disable-wayland --disable-bluray --disable-opencv +# ./configure --host=$A --enable-debug --disable-wayland + + + + diff --git a/pi-util/conf_native.sh b/pi-util/conf_native.sh new file mode 100755 index 0000000000..66032c3b92 --- /dev/null +++ b/pi-util/conf_native.sh @@ -0,0 +1,97 @@ +set -e +BASE=`pwd` +OUT_BASE=$BASE/out + +DO_BOOTSTRAP= +DO_MAKE= +DO_CONFIGURE=1 + +while [ "$1" != "" ] ; do + case $1 in + --make) + DO_MAKE=1 + DO_CONFIGURE= + ;; + --bootstrap) + DO_BOOTSTRAP=1 + ;; + *) + echo "Usage $0: [--bootstrap] [--make]" + echo " bootstrap Do bootstrap before configure" + echo " (will always bootstrap if clean)" + echo " make Do make after configure" + exit 1 + ;; + esac + shift +done + +if [ ! -f $BASE/configure ]; then + echo "configure not found - will bootstrap" + DO_BOOTSTRAP=1 +fi + +CONF_MMAL=--disable-mmal + +# uname -m gives kernel type which may not have the same +# 32/64bitness as userspace :-( getconf shoudl provide the answer +# but use uname to check we are on the right processor +MC=`uname -m` +LB=`getconf LONG_BIT` +if [ "$MC" == "armv7l" ] || [ "$MC" == "aarch64" ]; then + if [ "$LB" == "32" ]; then + # CONF_MMAL=--enable-mmal-avcodec + CONF_MMAL= + A=arm-linux-gnueabihf + ARM=armv7 + elif [ "$LB" == "64" ]; then + A=aarch64-linux-gnu + ARM=arm64 + else + echo "Unknown LONG_BIT name: $LB" + exit 1 + fi +else + echo "Unknown machine name: $MC" + exit 1 +fi +OUT=$OUT_BASE/$ARM-`lsb_release -sc`-rel + +if [ $DO_BOOTSTRAP ]; then + echo "==== Bootstrapping & cleaning $OUT" + rm -rf $OUT + ./bootstrap +fi + +if [ ! -f $OUT/Makefile ]; then + DO_CONFIGURE=1 +fi + +USR_PREFIX=$OUT/install +LIB_PREFIX=$USR_PREFIX/lib/$A +INC_PREFIX=$USR_PREFIX/include/$A + +echo "==== Configuring in $OUT" +mkdir -p $OUT +# Nothing under here need worry git - including this .gitignore! +echo "**" > $OUT_BASE/.gitignore + +cd $OUT +if [ $DO_CONFIGURE ]; then + $BASE/configure \ + --build=$A \ + --prefix=$USR_PREFIX\ + --libdir=$LIB_PREFIX\ + --includedir=$INC_PREFIX\ + --disable-vdpau\ + --enable-wayland\ + --enable-gles2\ + $CONF_MMAL + echo "==== Configured in $OUT" +fi + +if [ $DO_MAKE ]; then + echo "==== Making $OUT" + make -j8 + echo "==== Made $OUT" +fi diff --git a/pi-util/genpatch.sh b/pi-util/genpatch.sh new file mode 100755 index 0000000000..d24ce46814 --- /dev/null +++ b/pi-util/genpatch.sh @@ -0,0 +1,71 @@ +set -e + +NOTAG= +if [ "$1" == "--notag" ]; then + shift + NOTAG=1 +fi + +if [ "$1" == "" ]; then + echo Usage: $0 [--notag] \ + echo e.g.: $0 mmal_4 + exit 1 +fi +REF=$1 + +VERSION=`awk '/AC_INIT/{match($0,/[0-9]+(\.[0-9]+)+/);print substr($0,RSTART,RLENGTH)}' configure.ac` +if [ "$VERSION" == "" ]; then + echo Can\'t find version in configure.ac + exit 1 +fi + +if [ $NOTAG ]; then + echo Not tagged +else + # Only continue if we are all comitted + git diff --name-status --exit-code + + PATCHTAG=pi/$VERSION/$REF + echo Tagging: $PATCHTAG + + git tag $PATCHTAG +fi + +DSTDIR=.. +PATCHNAME=vlc-$VERSION-$REF +DIFFBASE=$DSTDIR/$PATCHNAME +ZIPNAME=$PATCHNAME-patch.zip + +# We seem to sometimes gain add +echo Generating patches in: $DSTDIR/$ZIPNAME +REFNAME=refs/tags/$VERSION +git diff $REFNAME -- \ + configure.ac \ + include \ + modules/Makefile.am \ + modules/audio_filter \ + modules/audio_output \ + modules/codec \ + modules/gui/qt/qt.cpp \ + modules/hw/drm \ + modules/hw/mmal \ + modules/video_output/Makefile.am \ + modules/video_output/drmu \ + modules/video_output/opengl \ + modules/video_output/wayland \ + src/audio_output \ + src/input \ + src/misc \ + src/video_output \ + > $DIFFBASE-001-rpi.patch +git diff $REFNAME -- modules/video_chroma/chain.c > $DIFFBASE-002-chain.patch +git diff $REFNAME -- bin/vlc.c > $DIFFBASE-003-vlc.patch +git diff $REFNAME -- modules/video_output/caca.c > $DIFFBASE-004-caca.patch +git diff $REFNAME -- modules/gui/qt/components/interface_widgets.* > $DIFFBASE-005-qt-wayland.patch +git diff $REFNAME -- modules/gui/qt/components/controller.cpp > $DIFFBASE-006-qt-fullscreen.patch +git diff $REFNAME -- modules/demux/adaptive/http/ConnectionParams.cpp > $DIFFBASE-007-http-port-fix.patch +cd $DSTDIR +zip -m $ZIPNAME $PATCHNAME-*.patch + +#echo Copying patch to arm-build +#scp $PATCHFILE john@arm-build:patches/0002-vlc-3.0.6-mmal_test_4.patch diff --git a/pi-util/host-install-dev.sh b/pi-util/host-install-dev.sh new file mode 100755 index 0000000000..e4706312ce --- /dev/null +++ b/pi-util/host-install-dev.sh @@ -0,0 +1,4 @@ +lua5.2 +protobuf-compiler +# Unsure exactly which gt5 targets are needed ??libqt5-dev-bin +sudo apt install qt5-default diff --git a/pi-util/mmvlc.sh b/pi-util/mmvlc.sh new file mode 100755 index 0000000000..a5f503d88a --- /dev/null +++ b/pi-util/mmvlc.sh @@ -0,0 +1,8 @@ +DSTBASE=/usr/lib/arm-linux-gnueabihf +DSTPLUGINS=$DSTBASE/vlc/plugins +DSTDIR=$DSTPLUGINS/mmal +sudo mkdir -p $DSTDIR +sudo cp modules/hw/mmal/.libs/*.so $DSTDIR/ +#sudo cp modules/.libs/libxcb_x11_plugin.so $DSTPLUGINS/video_output/ +#sudo cp src/.libs/libvlccore.so.9.0.0 $DSTBASE/ +vlc --no-plugins-cache $* diff --git a/pi-util/pi-install-dev.sh b/pi-util/pi-install-dev.sh new file mode 100755 index 0000000000..e99f872065 --- /dev/null +++ b/pi-util/pi-install-dev.sh @@ -0,0 +1,75 @@ +# Install set to build appropriate root on a clean pi + +sudo apt-get install \ + libprotobuf-dev \ + libepoxy-dev \ + libavutil-dev \ + libavcodec-dev \ + libavformat-dev \ + libswscale-dev \ + libva-dev \ + libpostproc-dev \ + libtwolame-dev \ + liba52-dev \ + libflac-dev \ + libmpeg2-4-dev \ + libass-dev \ + libaribb24-dev \ + libzvbi-dev \ + libkate-dev \ + libogg-dev \ + libdca-dev \ + libxcb-keysyms1-dev \ + libsdl2-dev \ + librsvg2-dev \ + libsystemd-dev \ + libarchive-dev \ + libnfs-dev \ + libssh2-1-dev \ + libopencv-dev \ + libsmbclient-dev \ + libmodplug-dev \ + libshine-dev \ + libvorbis-dev \ + libxml2-dev \ +comerr-dev \ +libasound2-dev \ +libatk1.0-dev \ +libcap-dev \ +libcups2-dev \ +libexif-dev \ +libffi-dev \ +libgconf2-dev \ +libgl1-mesa-dev \ +libgnome-keyring-dev \ +libgnutls28-dev \ +libidn11-dev \ +libjpeg-dev \ +libkrb5-dev \ +libnspr4-dev \ +libnss3-dev \ +libpam0g-dev \ +libpango1.0-dev \ +libpci-dev \ +libpcre3-dev \ +libssl-dev \ +libudev-dev \ +libx11-dev \ +libx11-xcb-dev \ +libxcb1-dev \ +libxcb-shm0-dev \ +libxcb-composite0-dev \ +libxcb-xv0-dev \ +libxss-dev \ +libxt-dev \ +libxtst-dev \ +mesa-common-dev + +# Pulse (hopefully) disabled +# libpulse-dev \ + +# Obviously replace paths appropriately below +# Now run pi-util/syncroot.sh on the compile m/c to grab the appropriate +# bits of the root and fix up the paths. +# e.g. ON COMPILE M/C in src dir: +# pi-util/syncroot.sh my-pi: raspian_jessie_pi1 diff --git a/pi-util/rebase_liblinks.py b/pi-util/rebase_liblinks.py new file mode 100755 index 0000000000..1f143ed2f6 --- /dev/null +++ b/pi-util/rebase_liblinks.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +import os, sys +from stat import * + +def walktree(top, callback, n, prefix): + '''recursively descend the directory tree rooted at top, + calling the callback function for each regular file''' + + for f in os.listdir(top): + pathname = os.path.join(top, f) + mode = os.lstat(pathname).st_mode + if S_ISDIR(mode): + # It's a directory, recurse into it + walktree(pathname, callback, n+1, prefix) + elif S_ISLNK(mode): + # It's a file, call the callback function + callback(pathname, os.readlink(pathname), n, prefix) + +def visitfile(file, linkname, n, prefix): + if (linkname.startswith(prefix + 'lib/')): + newlink = "../" * n + linkname[len(prefix):] + print('relinking', file, "->", newlink) + os.remove(file) + os.symlink(newlink, file) + +if __name__ == '__main__': + argc = len(sys.argv) + if argc == 2: + walktree(sys.argv[1], visitfile, 0, "/") + elif argc == 3: + walktree(sys.argv[1], visitfile, 0, sys.argv[2]) + else: + print("rebase_liblinks.py []") + + + diff --git a/pi-util/syncroot.sh b/pi-util/syncroot.sh new file mode 100755 index 0000000000..aedaa28323 --- /dev/null +++ b/pi-util/syncroot.sh @@ -0,0 +1,51 @@ +set -e + +if [ "$1" == "" ]; then + echo Usage: $0 \ [\] + echo src_dir is a source for rsync so may contain m/c name. + echo rootname will be set to \"raspian_stretch_pi1\" if missing + echo e.g.: pi-util/syncroot.sh my-pi: raspian_stretch_pi1 + exit 1 +fi + +SYSROOT_NAME=$2 +if [ "$SYSROOT_NAME" == "" ]; then + SYSROOT_NAME=raspian_stretch_pi1 +fi + +DST_ROOT=`pwd` +DST=$DST_ROOT/sysroot/$SYSROOT_NAME-sysroot +SRC=$1 + +RPI_BASE=$DST_ROOT/.. +TOOL_BASE=$RPI_BASE/tools +#FIRMWARE_BASE=$RPI_BASE/firmware4 + +echo Sync src: $SRC +echo Sync dest: $DST + +mkdir -p $DST/lib +mkdir -p $DST/opt +mkdir -p $DST/usr/share +mkdir -p $DST/usr/local/include +mkdir -p $DST/usr/local/lib + +#ln -sf $FIRMWARE_BASE/opt/vc $DST/opt +rsync -rl $SRC/opt/vc $DST/opt +rsync -rl $SRC/lib/arm-linux-gnueabihf $DST/lib +rsync -rl --exclude "*/cups/backend/*" $SRC/usr/lib $DST/usr +rsync -rl $SRC/usr/include $DST/usr +rsync -rl $SRC/usr/share/pkgconfig $DST/usr/share +rsync -rl $SRC/usr/local/include $DST/usr/local +rsync -rl $SRC/usr/local/lib $DST/usr/local + +rm $DST/usr/lib/arm-linux-gnueabihf/libpthread* +rm $DST/usr/lib/arm-linux-gnueabihf/libc.* + +PUSHDIR=`pwd` +cd $DST/usr/lib/pkgconfig +ln -sf ../arm-linux-gnueabihf/pkgconfig/* . +cd $PUSHDIR +pi-util/rebase_liblinks.py $DST + + diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c index 7eca0de2fd..67aad79855 100644 --- a/src/audio_output/dec.c +++ b/src/audio_output/dec.c @@ -217,20 +217,27 @@ static void aout_DecSilence (audio_output_t *aout, vlc_tick_t length, vlc_tick_t { aout_owner_t *owner = aout_owner (aout); const audio_sample_format_t *fmt = &owner->mixer_format; - size_t frames = (fmt->i_rate * length) / CLOCK_FREQ; - - block_t *block = block_Alloc (frames * fmt->i_bytes_per_frame - / fmt->i_frame_length); - if (unlikely(block == NULL)) - return; /* uho! */ - - msg_Dbg (aout, "inserting %zu zeroes", frames); - memset (block->p_buffer, 0, block->i_buffer); - block->i_nb_samples = frames; - block->i_pts = pts; - block->i_dts = pts; - block->i_length = length; - aout_OutputPlay (aout, block); + size_t frame_count = (fmt->i_rate * length) / CLOCK_FREQ; + + msg_Dbg (aout, "inserting %zu zero frames", frame_count); + while (frame_count != 0) + { + // Block into something that has a multiple of 3 in case this is spdif + // and we are going to substitute with pause blocks with a rep count of 3 + const size_t frames = frame_count > 3072 ? 3072 : frame_count; + block_t *block = block_Alloc((size_t)((uint_fast64_t)frames * fmt->i_bytes_per_frame + / fmt->i_frame_length)); + if (unlikely(block == NULL)) + return; /* uho! */ + + memset (block->p_buffer, 0, block->i_buffer); + block->i_nb_samples = frames; + block->i_pts = pts; + block->i_dts = pts; + block->i_length = length; + aout_OutputPlay (aout, block); + frame_count -= frames; + } } static void aout_DecSynchronize (audio_output_t *aout, vlc_tick_t dec_pts, diff --git a/src/input/decoder.c b/src/input/decoder.c index 69488a681c..cb88c163d0 100644 --- a/src/input/decoder.c +++ b/src/input/decoder.c @@ -2001,6 +2001,11 @@ void input_DecoderDelete( decoder_t *p_dec ) p_owner->b_waiting = false; vlc_cond_signal( &p_owner->wait_request ); + /* RPI: V4L2 stateful (H265) can deadlock on buffer starvation if output + * buffers not returned */ + if( p_owner->p_vout != NULL ) + vout_Flush(p_owner->p_vout, 0); + /* 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 * the picture pool may be empty, and the decoder thread or any decoder diff --git a/src/misc/fourcc.c b/src/misc/fourcc.c index ebb7707a34..0bec4e75da 100644 --- a/src/misc/fourcc.c +++ b/src/misc/fourcc.c @@ -416,6 +416,10 @@ static const vlc_fourcc_t p_D3D11_OPAQUE_10B_fallback[] = { VLC_CODEC_D3D11_OPAQUE_10B, VLC_CODEC_P010, VLC_CODEC_I420_10L, 0, }; +static const vlc_fourcc_t p_DRM_PRIME_SAND30_fallback[] = { + VLC_CODEC_DRM_PRIME_SAND30, VLC_CODEC_I420_10L, VLC_CODEC_NV12, 0, +}; + static const vlc_fourcc_t p_I440_fallback[] = { VLC_CODEC_I440, VLC_CODEC_YUV_PLANAR_420, @@ -506,6 +510,7 @@ static const vlc_fourcc_t *pp_YUV_fallback[] = { p_D3D9_OPAQUE_10B_fallback, p_D3D11_OPAQUE_fallback, p_D3D11_OPAQUE_10B_fallback, + p_DRM_PRIME_SAND30_fallback, NULL, }; @@ -537,6 +542,15 @@ static const vlc_fourcc_t p_list_YUV[] = { VLC_CODEC_D3D9_OPAQUE_10B, VLC_CODEC_D3D11_OPAQUE, VLC_CODEC_D3D11_OPAQUE_10B, + VLC_CODEC_MMAL_OPAQUE, + VLC_CODEC_MMAL_ZC_SAND8, + VLC_CODEC_MMAL_ZC_SAND10, + VLC_CODEC_MMAL_ZC_SAND30, + VLC_CODEC_MMAL_ZC_I420, + VLC_CODEC_DRM_PRIME_I420, + VLC_CODEC_DRM_PRIME_NV12, + VLC_CODEC_DRM_PRIME_SAND8, + VLC_CODEC_DRM_PRIME_SAND30, 0, }; @@ -762,11 +776,20 @@ 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() }, + { { VLC_CODEC_DRM_PRIME_I420, VLC_CODEC_DRM_PRIME_NV12, + VLC_CODEC_DRM_PRIME_SAND8, VLC_CODEC_DRM_PRIME_SAND30 }, + FAKE_FMT() }, + { { VLC_CODEC_DRM_PRIME_RGB32, 0 }, FAKE_FMT() }, { { VLC_CODEC_CVPX_NV12, VLC_CODEC_CVPX_UYVY, VLC_CODEC_CVPX_I420, VLC_CODEC_CVPX_BGRA }, diff --git a/src/misc/picture.c b/src/misc/picture.c index 892d5b7dac..3c75b6450f 100644 --- a/src/misc/picture.c +++ b/src/misc/picture.c @@ -365,10 +365,30 @@ void picture_CopyProperties( picture_t *p_dst, const picture_t *p_src ) 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 ); diff --git a/src/video_output/window.c b/src/video_output/window.c index 36aaa9d046..6e0b5dadf2 100644 --- a/src/video_output/window.c +++ b/src/video_output/window.c @@ -51,6 +51,12 @@ static int vout_window_start(void *func, va_list ap) return activate(wnd, cfg); } +static void vout_window_destructor(vlc_object_t *obj) +{ + vout_window_t *window = (vout_window_t *)obj; + vlc_mutex_destroy(&window->handle_lock); +} + vout_window_t *vout_window_New(vlc_object_t *obj, const char *module, const vout_window_cfg_t *cfg, const vout_window_owner_t *owner) @@ -58,10 +64,7 @@ vout_window_t *vout_window_New(vlc_object_t *obj, const char *module, window_t *w = vlc_custom_create(obj, sizeof(*w), "window"); vout_window_t *window = &w->wnd; - memset(&window->handle, 0, sizeof(window->handle)); - window->info.has_double_click = false; - window->control = NULL; - window->sys = NULL; + memset((char*)window + sizeof(window->obj), 0, sizeof(*window) - sizeof(window->obj)); if (owner != NULL) window->owner = *owner; @@ -87,6 +90,13 @@ vout_window_t *vout_window_New(vlc_object_t *obj, const char *module, } else w->inhibit = NULL; + + if (window->type == VOUT_WINDOW_TYPE_WAYLAND) + { + vlc_mutex_init(&window->handle_lock); + vlc_object_set_destructor(window, vout_window_destructor); + } + return window; }