--- a/configure.ac +++ b/configure.ac @@ -3419,6 +3419,9 @@ 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" @@ -3429,7 +3432,7 @@ VLC_ADD_PLUGIN([mmal]) VLC_ADD_LDFLAGS([mmal],[ -L/opt/vc/lib ]) VLC_ADD_CFLAGS([mmal],[ -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ]) - VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif ]) ], [ + VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif -lvchiq_arm -lvcsm ]) ], [ AS_IF([test "${enable_mmal}" = "yes"], [ AC_MSG_ERROR([Cannot find bcm library...]) ], [ AC_MSG_WARN([Cannot find bcm library...]) ]) @@ -3441,6 +3444,7 @@ VLC_RESTORE_FLAGS fi AM_CONDITIONAL([HAVE_MMAL], [test "${have_mmal}" = "yes"]) +AM_CONDITIONAL([HAVE_MMAL_AVCODEC], [test "${enable_mmal_avcodec}" = "yes"]) dnl dnl evas plugin --- a/include/vlc_fourcc.h +++ b/include/vlc_fourcc.h @@ -365,6 +365,9 @@ /* 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_I420 VLC_FOURCC('Z','4','2','0') /* DXVA2 opaque video surface for use with D3D9 */ #define VLC_CODEC_D3D9_OPAQUE VLC_FOURCC('D','X','A','9') /* 4:2:0 8 bpc */ --- a/modules/hw/mmal/Makefile.am +++ b/modules/hw/mmal/Makefile.am @@ -1,23 +1,39 @@ include $(top_srcdir)/modules/common.am mmaldir = $(pluginsdir)/mmal -AM_CFLAGS += $(CFLAGS_mmal) -AM_LDFLAGS += -rpath '$(mmaldir)' $(LDFLAGS_mmal) +AM_CFLAGS += -pthread $(CFLAGS_mmal) +AM_LDFLAGS += -pthread -rpath '$(mmaldir)' $(LDFLAGS_mmal) -libmmal_vout_plugin_la_SOURCES = vout.c mmal_picture.c mmal_picture.h +libmmal_vout_plugin_la_SOURCES = vout.c subpic.c mmal_picture.c subpic.h mmal_picture.h libmmal_vout_plugin_la_CFLAGS = $(AM_CFLAGS) libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm libmmal_vout_plugin_la_LIBADD = $(LIBS_mmal) mmal_LTLIBRARIES = libmmal_vout_plugin.la -libmmal_codec_plugin_la_SOURCES = codec.c +libmmal_codec_plugin_la_SOURCES = codec.c subpic.c mmal_picture.c blend_rgba_neon.S subpic.h mmal_picture.h 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_picture.h libmmal_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) libmmal_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) libmmal_deinterlace_plugin_la_LIBADD = $(LIBS_mmal) mmal_LTLIBRARIES += libmmal_deinterlace_plugin.la + +libmmal_xsplitter_plugin_la_SOURCES = xsplitter.c mmal_picture.c mmal_picture.h +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_picture.c mmal_picture.h +libmmal_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) +libmmal_avcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS) +libmmal_avcodec_plugin_la_LIBADD = $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(LIBS_mmal) +mmal_LTLIBRARIES += libmmal_avcodec_plugin.la +endif + + --- /dev/null +++ b/modules/hw/mmal/blend_rgba_neon.S @@ -0,0 +1,200 @@ + .syntax unified + .arm +// .thumb + .text + .align 16 + .arch armv7-a + .fpu neon-vfpv4 + +@ blend_rgbx_rgba_neon + +@ Implements /255 as ((x * 257) + 0x8000) >> 16 +@ This generates something in the range [(x+126)/255, (x+127)/255] which is good enough + +@ There is advantage to aligning src and/or dest - dest gives a bit more due to being used twice + + + +@ [r0] RGBx dest loaded into d20-d23 +@ [r1] RGBA src merge loaded into d16-d19 +@ r2 plane alpha +@ r3 count (pixels) + +.macro blend_main sR, sG, sB, sA, dR, dG, dB, dA + + push { r4, lr } + + vdup.u8 d7, r2 + + subs r3, #8 + vmov.u8 d6, #0xff + + blt 2f + + @ If < 16 bytes to move then don't bother trying to align + @ (a) This means the the align doesn't need to worry about r3 underflow + @ (b) The overhead would be greater than any gain + cmp r3, #8 + mov r4, r3 + ble 1f + + @ Align r1 on a 32 byte boundary + neg r3, r0 + ubfx r3, r3, #2, #3 + + cmp r3, #0 + blne 10f + + sub r3, r4, r3 + +1: + vld4.8 {d16, d17, d18, d19}, [r1] + +1: + @ Alpha ends up in d19 + 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]}, [r0]! + vld4.8 {d16[1], d17[1], d18[1], d19[1]}, [r1]! + vld4.8 {d20[1], d21[1], d22[1], d23[1]}, [r0]! + vld4.8 {d16[2], d17[2], d18[2], d19[2]}, [r1]! + vld4.8 {d20[2], d21[2], d22[2], d23[2]}, [r0]! + vld4.8 {d16[3], d17[3], d18[3], d19[3]}, [r1]! + vld4.8 {d20[3], d21[3], d22[3], d23[3]}, [r0]! +1: + bpl 1f + vld4.8 {d16[4], d17[4], d18[4], d19[4]}, [r1]! + vld4.8 {d20[4], d21[4], d22[4], d23[4]}, [r0]! + vld4.8 {d16[5], d17[5], d18[5], d19[5]}, [r1]! + vld4.8 {d20[5], d21[5], d22[5], d23[5]}, [r0]! +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]}, [r0]! +1: + @ Alpha ends up in d19 + + 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 + + mov r0, r2 + lsls r2, r3, #30 @ b2 -> C, b1 -> N + mov r2, r0 + 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 + beq 1f + vst4.8 {d20[6], d21[6], d22[6], d23[6]}, [r0]! +1: + bx lr + +.endm + + +@ [r0] RGBx dest (Byte order: R, G, B, x) +@ [r1] RGBA src merge (Byte order: R, G, B, A) +@ r2 plane alpha +@ r3 count (pixels) + +@ Whilst specified as RGBx+RGBA the only important part is the position of +@ alpha, the other components are all treated the same + +@ [r0] RGBx dest (Byte order: R, G, B, x) +@ [r1] RGBA src merge (Byte order: R, G, B, A) - same as above +@ r2 plane alpha +@ r3 count (pixels) + .align 16 + .global blend_rgbx_rgba_neon +#ifdef __ELF__ + .type blend_rgbx_rgba_neon, %function +#endif +blend_rgbx_rgba_neon: + blend_main d16, d17, d18, d19, d20, d21, d22, d23 + + +@ [r0] RGBx dest (Byte order: R, G, B, x) +@ [r1] RGBA src merge (Byte order: B, G, R, A) - B / R swapped +@ r2 plane alpha +@ r3 count (pixels) + .align 16 + .global blend_bgrx_rgba_neon +#ifdef __ELF__ + .type blend_bgrx_rgba_neon, %function +#endif +blend_bgrx_rgba_neon: + blend_main d18, d17, d16, d19, d20, d21, d22, d23 + + + --- /dev/null +++ b/modules/hw/mmal/blend_rgba_neon.h @@ -0,0 +1,17 @@ +#ifndef HW_MMAL_BLEND_RGBA_NEON_H +#define HW_MMAL_BLEND_RGBA_NEON_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void blend_neon_fn(void * dest, const void * src, int alpha, unsigned int n); +extern blend_neon_fn blend_rgbx_rgba_neon; +extern blend_neon_fn blend_bgrx_rgba_neon; + +#ifdef __cplusplus +} +#endif + +#endif + --- /dev/null +++ b/modules/hw/mmal/blend_test.c @@ -0,0 +1,180 @@ +#include +#include +#include + +#include "blend_rgba_neon.h" + +#define RPI_PROFILE 1 +#define RPI_PROC_ALLOC 1 +#include "rpi_prof.h" + +static inline unsigned div255(unsigned v) +{ + // This models what we we do in the asm for / 255 + // It generates something in the range [(i+126)/255, (i+127)/255] which is good enough + return ((v * 257) + 0x8000) >> 16; +} + +static inline unsigned int a_merge(unsigned int dst, unsigned src, unsigned f) +{ + return div255((255 - f) * (dst) + src * f); +} + + +static void merge_line(void * dest, const void * src, int alpha, unsigned int n) +{ + unsigned int i; + const uint8_t * s_data = src; + uint8_t * d_data = dest; + + for (i = 0; i != n; ++i) { + const uint32_t s_pel = ((const uint32_t *)s_data)[i]; + const uint32_t d_pel = ((const uint32_t *)d_data)[i]; + const unsigned int a = div255(alpha * (s_pel >> 24)); + ((uint32_t *)d_data)[i] = 0xff000000 | + (a_merge((d_pel >> 16) & 0xff, (s_pel >> 16) & 0xff, a) << 16) | + (a_merge((d_pel >> 8) & 0xff, (s_pel >> 8) & 0xff, a) << 8 ) | + (a_merge((d_pel >> 0) & 0xff, (s_pel >> 0) & 0xff, a) << 0 ); + } +} + + +// Merge RGBA with BGRA +static void merge_line2(void * dest, const void * src, int alpha, unsigned int n) +{ + unsigned int i; + const uint8_t * s_data = src; + uint8_t * d_data = dest; + + for (i = 0; i != n; ++i) { + const uint32_t s_pel = ((const uint32_t *)s_data)[i]; + const uint32_t d_pel = ((const uint32_t *)d_data)[i]; + const unsigned int a = div255(alpha * (s_pel >> 24)); + ((uint32_t *)d_data)[i] = 0xff000000 | + (a_merge((d_pel >> 0) & 0xff, (s_pel >> 16) & 0xff, a) << 0 ) | + (a_merge((d_pel >> 8) & 0xff, (s_pel >> 8) & 0xff, a) << 8 ) | + (a_merge((d_pel >> 16) & 0xff, (s_pel >> 0) & 0xff, a) << 16); + } +} + +#define BUF_SIZE 256 +#define BUF_SLACK 16 +#define BUF_ALIGN 64 +#define BUF_ALLOC (BUF_SIZE + 2*BUF_SLACK + BUF_ALIGN) + +static void test_line(const uint32_t * const dx, const unsigned int d_off, + const uint32_t * const sx, const unsigned int s_off, + const unsigned int alpha, const unsigned int len, const int prof_no) +{ + uint32_t d0_buf[BUF_ALLOC]; + uint32_t d1_buf[BUF_ALLOC]; + const uint32_t * const s0 = sx + s_off; + + uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; + uint32_t * const d1 = (uint32_t *)(((uintptr_t)d1_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; + unsigned int i; + + memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4); + memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4); + + merge_line(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); + + PROFILE_START(); + blend_rgbx_rgba_neon(d1 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); + PROFILE_ACC_N(prof_no); + + for (i = 0; i != BUF_SIZE + BUF_SLACK*2; ++i) { + if (d0[i] != d1[i]) { + printf("%3d: %08x + %08x * %02x: %08x / %08x: len=%d\n", (int)(i - BUF_SLACK), dx[i], s0[i], alpha, d0[i], d1[i], len); + } + } +} + +static void test_line2(const uint32_t * const dx, const unsigned int d_off, + const uint32_t * const sx, const unsigned int s_off, + const unsigned int alpha, const unsigned int len, const int prof_no) +{ + uint32_t d0_buf[BUF_ALLOC]; + uint32_t d1_buf[BUF_ALLOC]; + const uint32_t * const s0 = sx + s_off; + + uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; + uint32_t * const d1 = (uint32_t *)(((uintptr_t)d1_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off; + unsigned int i; + + memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4); + memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4); + + merge_line2(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); + + PROFILE_START(); + blend_bgrx_rgba_neon(d1 + BUF_SLACK, s0 + BUF_SLACK, alpha, len); + PROFILE_ACC_N(prof_no); + + for (i = 0; i != BUF_SIZE + BUF_SLACK*2; ++i) { + if (d0[i] != d1[i]) { + printf("%3d: %08x + %08x * %02x: %08x / %08x: len=%d\n", (int)(i - BUF_SLACK), dx[i], s0[i], alpha, d0[i], d1[i], len); + } + } +} + + + +int main(int argc, char *argv[]) +{ + unsigned int i, j; + uint32_t d0_buf[BUF_ALLOC]; + uint32_t s0_buf[BUF_ALLOC]; + + uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + 63) & ~63) + 0; + uint32_t * const s0 = (uint32_t *)(((uintptr_t)s0_buf + 63) & ~63) + 0; + + PROFILE_INIT(); + + for (i = 0; i != 255*255; ++i) { + unsigned int a = div255(i); + unsigned int b = (i + 127)/255; + unsigned int c = (i + 126)/255; + if (a != b && a != c) + printf("%d/255: %d != %d/%d\n", i, a, b, c); + } + + for (i = 0; i != BUF_ALLOC; ++i) { + d0_buf[i] = 0xff00 | i; + s0_buf[i] = (i << 24) | 0x40ffc0; + } + + for (i = 0; i != 256; ++i) { + test_line(d0, 0, s0, 0, i, 256, -1); + } + for (i = 0; i != 256; ++i) { + test_line(d0, 0, s0, 0, 128, i, -1); + } + + for (j = 0; j != 16; ++j) { + for (i = 0; i != 256; ++i) { + test_line(d0, j & 3, s0, j >> 2, i, 256, j); + } + PROFILE_PRINTF_N(j); + PROFILE_CLEAR_N(j); + } + printf("Done 1\n"); + + for (i = 0; i != 256; ++i) { + test_line2(d0, 0, s0, 0, i, 256, -1); + } + for (i = 0; i != 256; ++i) { + test_line2(d0, 0, s0, 0, 128, i, -1); + } + + for (j = 0; j != 16; ++j) { + for (i = 0; i != 256; ++i) { + test_line2(d0, j & 3, s0, j >> 2, i, 256, j); + } + PROFILE_PRINTF_N(j); + } + printf("Done 2\n"); + + return 0; +} + --- a/modules/hw/mmal/codec.c +++ b/modules/hw/mmal/codec.c @@ -26,10 +26,12 @@ #include "config.h" #endif +#include + #include -#include #include #include +#include #include #include @@ -38,255 +40,393 @@ #include #include "mmal_picture.h" +#include "subpic.h" +#include "blend_rgba_neon.h" + +#define TRACE_ALL 0 /* * This seems to be a bit high, but reducing it causes instabilities */ -#define NUM_EXTRA_BUFFERS 5 +#define NUM_EXTRA_BUFFERS 3 +//#define NUM_EXTRA_BUFFERS 6 +//#define NUM_EXTRA_BUFFERS 10 #define NUM_DECODER_BUFFER_HEADERS 30 #define MIN_NUM_BUFFERS_IN_TRANSIT 2 +#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.") + +#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.") -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() -struct decoder_sys_t { - bool opaque; +typedef struct decoder_sys_t +{ MMAL_COMPONENT_T *component; MMAL_PORT_T *input; MMAL_POOL_T *input_pool; MMAL_PORT_T *output; - MMAL_POOL_T *output_pool; /* only used for non-opaque mode */ + hw_mmal_port_pool_ref_t *ppr; MMAL_ES_FORMAT_T *output_format; - vlc_sem_t sem; + MMAL_STATUS_T err_stream; bool b_top_field_first; bool b_progressive; + bool b_flushed; + + // 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; + + +typedef struct supported_mmal_enc_s { + struct { + MMAL_PARAMETER_HEADER_T header; + MMAL_FOURCC_T encodings[64]; + } supported; + int n; +} supported_mmal_enc_t; + +static supported_mmal_enc_t supported_mmal_enc = +{ + {{MMAL_PARAMETER_SUPPORTED_ENCODINGS, sizeof(((supported_mmal_enc_t *)0)->supported)}, {0}}, + -1 }; -/* Utilities */ -static int change_output_format(decoder_t *dec); -static int send_output_buffer(decoder_t *dec); -static void fill_output_port(decoder_t *dec); - -/* VLC decoder callback */ -static int decode(decoder_t *dec, block_t *block); -static void flush_decoder(decoder_t *dec); - -/* MMAL callbacks */ -static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); -static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); +static inline char safe_char(const unsigned int c0) +{ + const unsigned int c = c0 & 0xff; + return c > ' ' && c < 0x7f ? c : '.'; +} -static int OpenDecoder(decoder_t *dec) +static const char * str_fourcc(char * buf, unsigned int fcc) { - int ret = VLC_SUCCESS; - decoder_sys_t *sys; - MMAL_PARAMETER_UINT32_T extra_buffers; - MMAL_STATUS_T status; + if (fcc == 0) + return "----"; + buf[0] = safe_char(fcc >> 0); + buf[1] = safe_char(fcc >> 8); + buf[2] = safe_char(fcc >> 16); + buf[3] = safe_char(fcc >> 24); + buf[4] = 0; + return buf; +} - if (dec->fmt_in.i_codec != VLC_CODEC_MPGV && - dec->fmt_in.i_codec != VLC_CODEC_H264) - return VLC_EGENERIC; +static bool is_enc_supported(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 (supported_mmal_enc.n == -1) + return true; // Unknown - say OK + for (i = 0; i < supported_mmal_enc.n; ++i) { + if (supported_mmal_enc.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(MMAL_PORT_T * port, const MMAL_FOURCC_T fcc) +{ + if (supported_mmal_enc.n >= 0) + /* already done */; + else if (mmal_port_parameter_get(port, (MMAL_PARAMETER_HEADER_T *)&supported_mmal_enc.supported) != MMAL_SUCCESS) + supported_mmal_enc.n = 0; + else + supported_mmal_enc.n = (supported_mmal_enc.supported.header.size - sizeof(supported_mmal_enc.supported.header)) / + sizeof(supported_mmal_enc.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(fcc); +} - sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)dec; - status = mmal_port_enable(sys->component->control, control_port_cb); - if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to enable control port %s (status=%"PRIx32" %s)", - sys->component->control->name, status, mmal_status_to_string(status)); - ret = VLC_EGENERIC; - goto out; +static MMAL_FOURCC_T vlc_to_mmal_es_fourcc(const unsigned int fcc) +{ + switch (fcc){ + case VLC_CODEC_MJPG: + return MMAL_ENCODING_MJPEG; + case VLC_CODEC_MP1V: + return MMAL_ENCODING_MP1V; + case VLC_CODEC_MPGV: + case VLC_CODEC_MP2V: + return MMAL_ENCODING_MP2V; + case VLC_CODEC_H263: + return MMAL_ENCODING_H263; + case VLC_CODEC_MP4V: + return MMAL_ENCODING_MP4V; + case VLC_CODEC_H264: + return MMAL_ENCODING_H264; + case VLC_CODEC_VP6: + return MMAL_ENCODING_VP6; + case VLC_CODEC_VP8: + return MMAL_ENCODING_VP8; + case VLC_CODEC_WMV1: + return MMAL_ENCODING_WMV1; + case VLC_CODEC_WMV2: + return MMAL_ENCODING_WMV2; + case VLC_CODEC_WMV3: + return MMAL_ENCODING_WMV3; + case VLC_CODEC_VC1: + return MMAL_ENCODING_WVC1; + case VLC_CODEC_THEORA: + return MMAL_ENCODING_THEORA; + default: + break; } + return 0; +} - sys->input = sys->component->input[0]; - sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)dec; - if (dec->fmt_in.i_codec == VLC_CODEC_MPGV) - sys->input->format->encoding = MMAL_ENCODING_MP2V; - else - sys->input->format->encoding = MMAL_ENCODING_H264; +static MMAL_FOURCC_T pic_to_slice_mmal_fourcc(const MMAL_FOURCC_T fcc) +{ + switch (fcc){ + case MMAL_ENCODING_I420: + return MMAL_ENCODING_I420_SLICE; + case MMAL_ENCODING_I422: + return MMAL_ENCODING_I422_SLICE; + case MMAL_ENCODING_ARGB: + return MMAL_ENCODING_ARGB_SLICE; + case MMAL_ENCODING_RGBA: + return MMAL_ENCODING_RGBA_SLICE; + case MMAL_ENCODING_ABGR: + return MMAL_ENCODING_ABGR_SLICE; + case MMAL_ENCODING_BGRA: + return MMAL_ENCODING_BGRA_SLICE; + case MMAL_ENCODING_RGB16: + return MMAL_ENCODING_RGB16_SLICE; + case MMAL_ENCODING_RGB24: + return MMAL_ENCODING_RGB24_SLICE; + case MMAL_ENCODING_RGB32: + return MMAL_ENCODING_RGB32_SLICE; + case MMAL_ENCODING_BGR16: + return MMAL_ENCODING_BGR16_SLICE; + case MMAL_ENCODING_BGR24: + return MMAL_ENCODING_BGR24_SLICE; + case MMAL_ENCODING_BGR32: + return MMAL_ENCODING_BGR32_SLICE; + default: + break; + } + return 0; +} - if (dec->fmt_in.i_codec == VLC_CODEC_H264) { - if (dec->fmt_in.i_extra > 0) { - status = mmal_format_extradata_alloc(sys->input->format, - dec->fmt_in.i_extra); - if (status == MMAL_SUCCESS) { - memcpy(sys->input->format->extradata, dec->fmt_in.p_extra, - dec->fmt_in.i_extra); - sys->input->format->extradata_size = dec->fmt_in.i_extra; - } else { - msg_Err(dec, "Failed to allocate extra format data on input port %s (status=%"PRIx32" %s)", - sys->input->name, status, mmal_status_to_string(status)); - } +#define DEBUG_SQUARES 0 +#if DEBUG_SQUARES +static void draw_square(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t val) +{ + uint32_t * p = (uint32_t *)pic_buf + y * pic_stride + x; + unsigned int i; + for (i = 0; i != h; ++i) { + unsigned int j; + for (j = 0; j != w; ++j) { + p[j] = val; } + p += pic_stride; } +} +#endif - status = mmal_port_format_commit(sys->input); - if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)", - sys->input->name, status, mmal_status_to_string(status)); - ret = VLC_EGENERIC; - goto out; +#if 0 +static inline void draw_line(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int len, int inc) +{ + uint32_t * p = (uint32_t *)pic_buf + y * pic_stride + x; + while (len-- != 0) { + *p = ~0U; + p += inc; } - sys->input->buffer_size = sys->input->buffer_size_recommended; - sys->input->buffer_num = sys->input->buffer_num_recommended; +} - status = mmal_port_enable(sys->input, input_port_cb); - if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to enable input port %s (status=%"PRIx32" %s)", - sys->input->name, status, mmal_status_to_string(status)); - ret = VLC_EGENERIC; - goto out; - } - sys->output = sys->component->output[0]; - sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)dec; +static void draw_corners(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int w, unsigned int h) +{ + const unsigned int len = 20; + draw_line(pic_buf, pic_stride, x, y, len, 1); + draw_line(pic_buf, pic_stride, x, y, len, pic_stride); + draw_line(pic_buf, pic_stride, x + w - 1, y, len, -1); + draw_line(pic_buf, pic_stride, x + w - 1, y, len, pic_stride); + draw_line(pic_buf, pic_stride, x + w - 1, y + h - 1, len, -1); + draw_line(pic_buf, pic_stride, x + w - 1, y + h - 1, len, -(int)pic_stride); + draw_line(pic_buf, pic_stride, x, y + h - 1, len, 1); + draw_line(pic_buf, pic_stride, x, y + h - 1, len, -(int)pic_stride); +} +#endif - if (sys->opaque) { - extra_buffers.hdr.id = MMAL_PARAMETER_EXTRA_BUFFERS; - extra_buffers.hdr.size = sizeof(MMAL_PARAMETER_UINT32_T); - extra_buffers.value = NUM_EXTRA_BUFFERS; - status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr); - if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", - status, mmal_status_to_string(status)); - ret = VLC_EGENERIC; - goto out; - } +// 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; - 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 - }; + vlc_mutex_lock(&dec_sys->pic_lock); + picture_t * const pic = decoder_NewPicture(dec); + vlc_mutex_unlock(&dec_sys->pic_lock); - 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; - } - } + if (pic == NULL) + goto fail1; - 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; + if (buf->length == 0) { + msg_Err(dec, "%s: Empty buffer", __func__); + goto fail2; } - 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; - } + if ((pic->context = hw_mmal_gen_context(MMAL_ENCODING_OPAQUE, buf, dec_sys->ppr)) == NULL) + goto fail2; - sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0); + buf_to_pic_copy_props(pic, buf); - 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; +#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 (sys->output_format != NULL) + mmal_format_free(sys->output_format); - if (sys->component && sys->component->is_enabled) - mmal_component_disable(sys->component); + 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->input_pool) - mmal_pool_destroy(sys->input_pool); + // If we get here then we were flushing (cmd == 0 && len == 0) or + // that was an EVENT - in either case we want to release the buffer + // back to its pool rather than recycle it. + mmal_buffer_header_reset(buffer); + buffer->user_data = NULL; + mmal_buffer_header_release(buffer); +} - if (sys->output_format) - mmal_format_free(sys->output_format); - if (sys->output_pool) - mmal_pool_destroy(sys->output_pool); - if (sys->component) - mmal_component_release(sys->component); +static void fill_output_port(decoder_t *dec) +{ + decoder_sys_t *sys = dec->p_sys; - vlc_sem_destroy(&sys->sem); - free(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 - bcm_host_deinit(); + return; + } + + hw_mmal_port_pool_ref_fill(sys->ppr); + return; } static int change_output_format(decoder_t *dec) { MMAL_PARAMETER_VIDEO_INTERLACE_TYPE_T interlace_type; - decoder_sys_t *sys = dec->p_sys; + decoder_sys_t * const sys = dec->p_sys; MMAL_STATUS_T status; - int pool_size; int ret = 0; +#if TRACE_ALL + msg_Dbg(dec, "%s: <<<", __func__); +#endif + if (atomic_load(&sys->started)) { mmal_format_full_copy(sys->output->format, sys->output_format); status = mmal_port_format_commit(sys->output); @@ -300,7 +440,9 @@ } 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)", @@ -318,18 +460,10 @@ 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 +472,14 @@ } 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: @@ -382,12 +505,19 @@ 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 +525,85 @@ return ret; } -static int send_output_buffer(decoder_t *dec) +static MMAL_STATUS_T +set_extradata_and_commit(decoder_t * const dec, decoder_sys_t * const sys) { - decoder_sys_t *sys = dec->p_sys; - MMAL_BUFFER_HEADER_T *buffer; - picture_sys_t *p_sys; - picture_t *picture = NULL; MMAL_STATUS_T status; - unsigned buffer_size = 0; - int ret = 0; - - if (!sys->output->is_enabled) - return VLC_EGENERIC; - - /* If local output pool is allocated, use it - this is only the case for - * non-opaque modes */ - if (sys->output_pool) { - buffer = mmal_queue_get(sys->output_pool->queue); - if (!buffer) { - msg_Warn(dec, "Failed to get new buffer"); - return VLC_EGENERIC; - } - } - - if (!decoder_UpdateVideoFormat(dec)) - picture = decoder_NewPicture(dec); - if (!picture) { - msg_Warn(dec, "Failed to get new picture"); - ret = -1; - goto err; - } - - p_sys = picture->p_sys; - for (int i = 0; i < picture->i_planes; i++) - buffer_size += picture->p[i].i_lines * picture->p[i].i_pitch; - - if (sys->output_pool) { - mmal_buffer_header_reset(buffer); - buffer->alloc_size = sys->output->buffer_size; - if (buffer_size < sys->output->buffer_size) { - msg_Err(dec, "Retrieved picture with too small data block (%d < %d)", - buffer_size, sys->output->buffer_size); - ret = VLC_EGENERIC; - goto err; - } - - if (!sys->opaque) - buffer->data = picture->p[0].p_pixels; - } else { - buffer = p_sys->buffer; - if (!buffer) { - msg_Warn(dec, "Picture has no buffer attached"); - picture_Release(picture); - return VLC_EGENERIC; - } - buffer->data = p_sys->buffer->data; - } - buffer->user_data = picture; - buffer->cmd = 0; - status = mmal_port_send_buffer(sys->output, buffer); + status = mmal_port_format_commit(sys->input); if (status != MMAL_SUCCESS) { - msg_Err(dec, "Failed to send buffer to output port (status=%"PRIx32" %s)", - status, mmal_status_to_string(status)); - ret = -1; - goto err; - } - atomic_fetch_add(&sys->output_in_transit, 1); - - return ret; - -err: - if (picture) - picture_Release(picture); - if (sys->output_pool && buffer) { - buffer->data = NULL; - mmal_buffer_header_release(buffer); + msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)", + sys->input->name, status, mmal_status_to_string(status)); } - return ret; + return status; } -static void fill_output_port(decoder_t *dec) +static MMAL_STATUS_T decoder_send_extradata(decoder_t * const dec, decoder_sys_t *const sys) { - decoder_sys_t *sys = dec->p_sys; - - unsigned max_buffers_in_transit = 0; - int buffers_available = 0; - int buffers_to_send = 0; - int i; + if (dec->fmt_in.i_codec == VLC_CODEC_H264 && + dec->fmt_in.i_extra > 0) + { + MMAL_BUFFER_HEADER_T * const buf = mmal_queue_wait(sys->input_pool->queue); + MMAL_STATUS_T status; + + mmal_buffer_header_reset(buf); + buf->cmd = 0; + buf->user_data = NULL; + buf->alloc_size = sys->input->buffer_size; + buf->length = dec->fmt_in.i_extra; + buf->data = dec->fmt_in.p_extra; + buf->flags = MMAL_BUFFER_HEADER_FLAG_CONFIG; - if (sys->output_pool) { - max_buffers_in_transit = __MAX(sys->output_pool->headers_num, - MIN_NUM_BUFFERS_IN_TRANSIT); - buffers_available = mmal_queue_length(sys->output_pool->queue); - } else { - max_buffers_in_transit = NUM_DECODER_BUFFER_HEADERS; - buffers_available = NUM_DECODER_BUFFER_HEADERS - atomic_load(&sys->output_in_transit); + status = mmal_port_send_buffer(sys->input, buf); + if (status != MMAL_SUCCESS) { + msg_Err(dec, "Failed to send extradata buffer to input port (status=%"PRIx32" %s)", + status, mmal_status_to_string(status)); + return status; + } } - buffers_to_send = max_buffers_in_transit - atomic_load(&sys->output_in_transit); - - if (buffers_to_send > buffers_available) - buffers_to_send = buffers_available; -#ifndef NDEBUG - msg_Dbg(dec, "Send %d buffers to output port (available: %d, " - "in_transit: %d, buffer_num: %d)", - buffers_to_send, buffers_available, - atomic_load(&sys->output_in_transit), - sys->output->buffer_num); -#endif - for (i = 0; i < buffers_to_send; ++i) - if (send_output_buffer(dec) < 0) - break; + return MMAL_SUCCESS; } static void flush_decoder(decoder_t *dec) { - decoder_sys_t *sys = dec->p_sys; - MMAL_BUFFER_HEADER_T *buffer; - MMAL_STATUS_T status; + decoder_sys_t *const sys = dec->p_sys; - msg_Dbg(dec, "Flushing decoder ports..."); - mmal_port_flush(sys->output); - mmal_port_flush(sys->input); - - while (atomic_load(&sys->output_in_transit) || - atomic_load(&sys->input_in_transit)) - vlc_sem_wait(&sys->sem); +#if TRACE_ALL + msg_Dbg(dec, "%s: <<<", __func__); +#endif + + if (!sys->b_flushed) { + mmal_port_disable(sys->input); + mmal_port_disable(sys->output); + // We can leave the input disabled, but we want the output enabled + // in order to sink any buffers returning from other modules + mmal_port_enable(sys->output, decoder_output_cb); + sys->b_flushed = true; + } +#if TRACE_ALL + msg_Dbg(dec, "%s: >>>", __func__); +#endif } static int decode(decoder_t *dec, block_t *block) { decoder_sys_t *sys = dec->p_sys; MMAL_BUFFER_HEADER_T *buffer; - bool need_flush = false; uint32_t len; uint32_t flags = 0; 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 +612,50 @@ 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 +666,21 @@ 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) @@ -590,89 +696,1443 @@ } buffer->flags = flags; +#if TRACE_ALL + msg_Dbg(dec, "%s: -- Send buffer: len=%d", __func__, len); +#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; } + 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); + + vlc_mutex_destroy(&sys->pic_lock); + free(sys); + + bcm_host_deinit(); +} + +static int OpenDecoder(decoder_t *dec) +{ + int ret = VLC_EGENERIC; + decoder_sys_t *sys; MMAL_STATUS_T status; + const MMAL_FOURCC_T in_fcc = vlc_to_mmal_es_fourcc(dec->fmt_in.i_codec); + +#if TRACE_ALL + { + char buf1[5], buf2[5], buf2a[5]; + char buf3[5], buf4[5]; + msg_Dbg(dec, "%s: <<< (%s/%s)[%s] %dx%d -> (%s/%s) %dx%d", __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, + 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); + } +#endif + + if (!is_enc_supported(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); + + bcm_host_init(); + + 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(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; + } + + 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; + dec->fmt_out.i_codec = VLC_CODEC_MMAL_OPAQUE; + dec->fmt_out.video.i_chroma = VLC_CODEC_MMAL_OPAQUE; + + if ((status = decoder_send_extradata(dec, sys)) != MMAL_SUCCESS) + goto fail; + + 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 + + subpic_reg_stash_t subs[SUBS_MAX]; + + pic_fifo_t ret_pics; + + vlc_sem_t sem; + vlc_mutex_t lock; + + bool b_top_field_first; + bool b_progressive; + + MMAL_STATUS_T err_stream; + int in_count; + + bool is_sliced; + bool out_fmt_set; + bool latency_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; + +} 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; + + if (bpp < 1 || bpp > 4) + return MMAL_EINVAL; + + 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. + vlc_to_mmal_video_fmt(es_fmt, &pic->format); + // Override width / height with strides + 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->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 + picture_Release(pic); + } + else + { + buf_to_pic_copy_props(pic, buf); + +// 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 + conv_out_q_pic(sys, pic); + } + } + + buf->user_data = NULL; // Zap here to make sure we can't reuse later + mmal_buffer_header_release(buf); +} + + +static void slice_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) +{ + filter_t * const p_filter = (filter_t *)port->userdata; + filter_sys_t * const sys = p_filter->p_sys; + +#if TRACE_ALL + msg_Dbg(p_filter, "<<< %s: cmd=%d, flags=%#x, pic=%p, data=%p, len=%d/%d, pts=%lld", __func__, + buf->cmd, buf->flags, buf->user_data, buf->data, buf->length, buf->alloc_size, (long long)buf->pts); +#endif + + if (buf->cmd != 0) + { + mmal_buffer_header_release(buf); + return; + } + + if (buf->data == NULL || buf->length == 0) + { +#if TRACE_ALL + msg_Dbg(p_filter, "%s: Buffer has no data", __func__); +#endif + } + else + { + // Got slice + picture_t *pic = sys->slice.pics.head; + const unsigned int scale_lines = sys->output->format->es->video.height; // Expected lines of callback + + if (pic == NULL) { + msg_Err(p_filter, "No output picture"); + goto fail; + } + + // Copy lines + // * single plane only - fix for I420 + { + const unsigned int scale_n = __MIN(scale_lines - sys->slice.line, MMAL_SLICE_HEIGHT); + const unsigned int pic_lines = pic->p[0].i_lines; + const unsigned int copy_n = sys->slice.line + scale_n <= pic_lines ? scale_n : + sys->slice.line >= pic_lines ? 0 : + pic_lines - sys->slice.line; + + const unsigned int src_stride = buf->type->video.pitch[0]; + const unsigned int dst_stride = pic->p[0].i_pitch; + uint8_t *dst = pic->p[0].p_pixels + sys->slice.line * dst_stride; + const uint8_t *src = buf->data + buf->type->video.offset[0]; + + if (src_stride == dst_stride) { + if (copy_n != 0) + memcpy(dst, src, src_stride * copy_n); } + else { + unsigned int i; + for (i = 0; i != copy_n; ++i) { + memcpy(dst, src, __MIN(dst_stride, src_stride)); + dst += dst_stride; + src += src_stride; + } + } + sys->slice.line += scale_n; } - 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); + if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) != 0 || sys->slice.line >= scale_lines) { - if (sys->opaque) - format->encoding = MMAL_ENCODING_OPAQUE; + 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); + + // 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 + // This will also dig us out of situations where we have got out of sync somehow + vlc_sem_destroy(&sys->sem); + vlc_sem_init(&sys->sem, CONV_MAX_LATENCY); + + // No buffers in either port now + sys->in_count = 0; + + // Reset error status + sys->err_stream = MMAL_SUCCESS; + +#if TRACE_ALL + msg_Dbg(p_filter, ">>> %s", __func__); +#endif +} + +static void conv_flush_passthrough(filter_t * p_filter) +{ + VLC_UNUSED(p_filter); +} + +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; + } + } +} + +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; + vlc_to_mmal_video_fmt(sys->output->format, &p_filter->fmt_out.video); + + // Override default format width/height if we have a pic we need to match + if (pic != NULL) + { + if ((status = pic_to_format(sys->output->format, pic)) != MMAL_SUCCESS) + { + char cbuf[5]; + msg_Err(p_filter, "Bad format desc: %s, bits=%d", str_fourcc(cbuf, pic->format.i_chroma), 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); + } + + 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; - sys->output_format = format; + return MMAL_SUCCESS; +} + +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; + MMAL_STATUS_T err; + const uint64_t frame_seq = ++sys->frame_seq; + conv_frame_stash_t * const stash = sys->stash + (frame_seq & 0xf); + +#if TRACE_ALL + msg_Dbg(p_filter, "<<< %s", __func__); +#endif +#if 0 + { + 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; + } + + if (p_pic->context == NULL) { + 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), p_pic, sub_no, sys->subs + sub_no, + &sys->output->format->es->video.crop, 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; + } + 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; + } + + msg_Dbg(p_filter, "Outpool: zc=%d, num=%d, size=%d", sys->is_sliced, sys->output->buffer_num, sys->output->buffer_size); + sys->out_pool = sys->is_sliced ? + mmal_port_pool_create(sys->output, sys->output->buffer_num, sys->output->buffer_size) : + 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; + + // If ZC then we need to allocate the out pic before we stuff the input + if (sys->is_sliced) { + MMAL_BUFFER_HEADER_T * out_buf; + 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; + } + + 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); + + ++sys->in_count; + } + + // Stuff into input + // We assume the BH is already set up with values reflecting pic date etc. + { + MMAL_BUFFER_HEADER_T * const pic_buf = pic_mmal_buffer(p_pic); +#if TRACE_ALL + msg_Dbg(p_filter, "In buf send: pic=%p, buf=%p, user=%p, pts=%lld/%lld", + p_pic, pic_buf, pic_buf->user_data, (long long)frame_seq, (long long)p_pic->date); +#endif + if (pic_buf == NULL) { + msg_Err(p_filter, "Pic has no attached buffer"); + goto fail; + } + + stash->pts = p_pic->date; + if ((err = port_send_replicated(sys->input, sys->in_pool, pic_buf, frame_seq)) != MMAL_SUCCESS) + { + msg_Err(p_filter, "Send buffer to input failed"); + goto fail; + } + + picture_Release(p_pic); + p_pic = NULL; + --sys->in_count; + } + + if (!sys->is_sliced) { + MMAL_BUFFER_HEADER_T * out_buf; + + while ((out_buf = sys->in_count < 0 ? + mmal_queue_wait(sys->out_pool->queue) : mmal_queue_get(sys->out_pool->queue)) != NULL) + { + picture_t * const out_pic = filter_NewPicture(p_filter); + + if (out_pic == NULL) { + msg_Warn(p_filter, "Failed to alloc new filter output pic"); + mmal_buffer_header_release(out_buf); + break; + } + +#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 + + mmal_buffer_header_reset(out_buf); + out_buf->user_data = out_pic; + 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, buf=%p, flags=%#x, len=%d/%d, pts=%lld", + p_pic, 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"); + mmal_buffer_header_release(out_buf); + break; + } + + ++sys->in_count; + } + } + + if (sys->in_count < 0) + { + msg_Err(p_filter, "Buffer count somehow negative"); + goto fail; + } + + // Avoid being more than 1 pic behind + vlc_sem_wait(&sys->sem); + + // Set sem for delayed scale if not already set + if (!sys->latency_set) { + unsigned int i; + sys->latency_set = true; + for (i = 0; i != CONV_MAX_LATENCY; ++i) { + vlc_sem_post(&sys->sem); + } + } + + // Return all pending buffers + vlc_mutex_lock(&sys->lock); + ret_pics = pic_fifo_get_all(&sys->ret_pics); + vlc_mutex_unlock(&sys->lock); + + if (sys->err_stream != MMAL_SUCCESS) + goto stream_fail; + + // Sink as many sem posts as we have pics + // (shouldn't normally wait, but there is a small race) + if (ret_pics != NULL) + { + picture_t *next_pic = ret_pics->p_next; + + conv_stash_fixup(p_filter, sys, ret_pics); +#if 0 + char dbuf0[5]; + + msg_Dbg(p_filter, "pic_out %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", + str_fourcc(dbuf0, ret_pics->format.i_chroma), + ret_pics->format.i_width, ret_pics->format.i_height, + ret_pics->format.i_x_offset, ret_pics->format.i_y_offset, + ret_pics->format.i_visible_width, ret_pics->format.i_visible_height, + ret_pics->format.i_sar_num, ret_pics->format.i_sar_den); +#endif + while (next_pic != NULL) { + vlc_sem_wait(&sys->sem); + conv_stash_fixup(p_filter, sys, next_pic); + next_pic = next_pic->p_next; + } + } + +#if TRACE_ALL + msg_Dbg(p_filter, ">>> %s: pic=%p", __func__, ret_pics); +#endif - mmal_buffer_header_release(buffer); + return ret_pics; + +stream_fail: + msg_Err(p_filter, "MMAL error reported by callback"); +fail: + if (p_pic != NULL) + picture_Release(p_pic); + conv_flush(p_filter); + return NULL; +} + +static picture_t *conv_filter_passthrough(filter_t *p_filter, picture_t *p_pic) +{ + VLC_UNUSED(p_filter); +#if TRACE_ALL + msg_Dbg(p_filter, "<<< %s", __func__); +#endif + return p_pic; +} + +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); + + 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); + + vlc_sem_destroy(&sys->sem); + vlc_mutex_destroy(&sys->lock); + + free(sys); +} + + +static int open_converter_passthrough(filter_t * const p_filter) +{ + { + char dbuf0[5], dbuf1[5]; + msg_Dbg(p_filter, "%s: (%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__, + "passthrough", + 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); + } + + + p_filter->pf_video_filter = conv_filter_passthrough; + p_filter->pf_flush = conv_flush_passthrough; + return VLC_SUCCESS; +} + +static int OpenConverter(vlc_object_t * obj) +{ + filter_t * const p_filter = (filter_t *)obj; + int ret = VLC_EGENERIC; + filter_sys_t *sys; + MMAL_STATUS_T status; + MMAL_FOURCC_T enc_out; + const MMAL_FOURCC_T enc_in = vlc_to_mmal_video_fourcc(&p_filter->fmt_in.video); + bool use_resizer; + bool use_isp; + int gpu_mem; + + if ((enc_in != MMAL_ENCODING_OPAQUE && + enc_in != MMAL_ENCODING_YUVUV128 && + enc_in != MMAL_ENCODING_YUVUV64_10) || + (enc_out = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video)) == 0) + return VLC_EGENERIC; + + if (enc_in == enc_out) { + return open_converter_passthrough(p_filter); + } + + use_resizer = var_InheritBool(p_filter, MMAL_RESIZE_NAME); + use_isp = var_InheritBool(p_filter, MMAL_ISP_NAME); + +retry: + // 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; + } + + 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->in_port_cb_fn = conv_input_port_cb; + if (use_resizer) { + sys->resizer_type = FILTER_RESIZER_RESIZER; + sys->is_sliced = true; + sys->component_name = MMAL_COMPONENT_DEFAULT_RESIZER; + sys->out_port_cb_fn = slice_output_port_cb; + } + else if (use_isp) { + sys->resizer_type = FILTER_RESIZER_ISP; + sys->is_sliced = false; // Copy directly into filter picture + sys->component_name = MMAL_COMPONENT_ISP_RESIZER; + sys->out_port_cb_fn = conv_output_port_cb; } else { - mmal_buffer_header_release(buffer); + sys->resizer_type = FILTER_RESIZER_HVS; + sys->is_sliced = false; // Copy directly into filter picture + sys->component_name = MMAL_COMPONENT_HVS; + sys->out_port_cb_fn = conv_output_port_cb; + } + + 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; + } + + 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; + vlc_to_mmal_video_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); + + 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], 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; +} + + +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 +} 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, dst, + 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; + } + MMAL_DISPLAYREGION_T * const reg = hw_mmal_vzc_buf_region(buf); + + reg->set |= + MMAL_DISPLAY_SET_ALPHA | MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_DEST_RECT; + + reg->fullscreen = 0; + + reg->alpha = (uint32_t)(alpha & 0xff) | (1U << 31); + + hw_mmal_vzc_buf_set_dest_rect(buf, x_offset, y_offset, src->format.i_visible_width, src->format.i_visible_height); + + reg->dest_rect = (MMAL_RECT_T){0, 0, 0, 0}; + + 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 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 ((vfcc_dst != VLC_CODEC_MMAL_OPAQUE && + vfcc_dst != VLC_CODEC_MMAL_ZC_SAND8 && + vfcc_dst != VLC_CODEC_MMAL_ZC_SAND10) || + !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; + if ((sys->vzc = hw_mmal_vzc_pool_new()) == NULL) + { + free(sys); + return VLC_ENOMEM; + } + p_filter->p_sys = (filter_sys_t *)sys; + } + + p_filter->pf_video_blend = FilterBlendMmal; + p_filter->pf_flush = FlushBlendMmal; + + return VLC_SUCCESS; +} + +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; + + hw_mmal_vzc_pool_release(sys->vzc); + free(sys); +} + +// --------------------------------------------------------------------------- + +static void FilterBlendNeon(filter_t *p_filter, + picture_t *dst_pic, const picture_t * src_pic, + int x_offset, int y_offset, int alpha) +{ + const uint8_t * s_data; + uint8_t * d_data; + int width = src_pic->format.i_visible_width; + int height = src_pic->format.i_visible_height; + blend_neon_fn *const blend_fn = (blend_neon_fn * )p_filter->p_sys; + +#if TRACE_ALL + msg_Dbg(p_filter, "%s (%d,%d:%d) pic=%p, pts=%lld, force=%d", __func__, x_offset, y_offset, alpha, src_pic, src_pic->date, src_pic->b_force); +#endif + + if (alpha == 0 || + src_pic->format.i_visible_height == 0 || + src_pic->format.i_visible_width == 0) + { + return; + } + + x_offset += dst_pic->format.i_x_offset; + y_offset += dst_pic->format.i_y_offset; + + // Deal with R/B overrun + if (x_offset + width >= (int)(dst_pic->format.i_x_offset + dst_pic->format.i_visible_width)) + width = dst_pic->format.i_x_offset + dst_pic->format.i_visible_width - x_offset; + if (y_offset + height >= (int)(dst_pic->format.i_y_offset + dst_pic->format.i_visible_height)) + height = dst_pic->format.i_y_offset + dst_pic->format.i_visible_height - y_offset; + + if (width <= 0 || height <= 0) { + return; + } + + // *** L/U overrun + + s_data = src_pic->p[0].p_pixels + + src_pic->p[0].i_pixel_pitch * src_pic->format.i_x_offset + + src_pic->p[0].i_pitch * src_pic->format.i_y_offset; + d_data = dst_pic->p[0].p_pixels + + dst_pic->p[0].i_pixel_pitch * x_offset + + dst_pic->p[0].i_pitch * y_offset; + + + do { + blend_fn(d_data, s_data, alpha, width); + s_data += src_pic->p[0].i_pitch; + d_data += dst_pic->p[0].i_pitch; + } while (--height > 0); +} + +static void CloseBlendNeon(vlc_object_t *object) +{ + VLC_UNUSED(object); } + +static int OpenBlendNeon(vlc_object_t *object) +{ + filter_t * const p_filter = (filter_t *)object; + const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma; + MMAL_FOURCC_T mfcc_src = vlc_to_mmal_video_fourcc(&p_filter->fmt_in.video); + MMAL_FOURCC_T mfcc_dst = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video); + blend_neon_fn * blend_fn = (blend_neon_fn *)0; + + // Non-alpha RGB only for dest + if (vfcc_dst != VLC_CODEC_RGB32) + return VLC_EGENERIC; + + // Check we have appropriate blend fn (mmal doesn't have a non-alpha RGB32) + switch (mfcc_src) { + case MMAL_ENCODING_RGBA: + if (mfcc_dst == MMAL_ENCODING_RGBA) + blend_fn = blend_rgbx_rgba_neon; + else if (mfcc_dst == MMAL_ENCODING_BGRA) + blend_fn = blend_bgrx_rgba_neon; + break; + + case MMAL_ENCODING_BGRA: + if (mfcc_dst == MMAL_ENCODING_BGRA) + blend_fn = blend_rgbx_rgba_neon; + else if (mfcc_dst == MMAL_ENCODING_RGBA) + blend_fn = blend_bgrx_rgba_neon; + break; + + default: + break; + } + + if (blend_fn == (blend_neon_fn *)0) + return VLC_EGENERIC; + + p_filter->p_sys = (void *)blend_fn; + p_filter->pf_video_blend = FilterBlendNeon; + + { + char dbuf0[5], dbuf1[5]; + char dbuf0a[5], dbuf1a[5]; + msg_Dbg(p_filter, "%s: (%s) %s/%s,%dx%d [(%d,%d) %dx%d]->%s/%s,%dx%d [(%d,%d) %dx%d]", __func__, + "blend", + str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), + str_fourcc(dbuf0a, mfcc_src), + p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height, + p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset, + p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height, + str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), + str_fourcc(dbuf1a, mfcc_dst), + p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height, + p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset, + p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height); + } + + return VLC_SUCCESS; +} + +vlc_module_begin() + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_VCODEC ) + set_shortname(N_("MMAL decoder")) + set_description(N_("MMAL-based decoder plugin for Raspberry Pi")) + set_capability("video decoder", 90) + add_shortcut("mmal_decoder") + add_bool(MMAL_OPAQUE_NAME, true, MMAL_OPAQUE_TEXT, MMAL_OPAQUE_LONGTEXT, false) + set_callbacks(OpenDecoder, CloseDecoder) + + add_submodule() + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VFILTER ) + set_shortname(N_("MMAL converterer")) + set_description(N_("MMAL 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) + + 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() + + --- 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" @@ -41,466 +42,569 @@ #define MIN_NUM_BUFFERS_IN_TRANSIT 2 -#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_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.") -static int Open(filter_t *filter); -static void Close(filter_t *filter); +#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") -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_FAST "mmal-deinterlace-fast" +#define MMAL_DEINTERLACE_FAST_TEXT N_("Force fast deinterlace") +#define MMAL_DEINTERLACE_FAST_LONGTEXT N_("Force fast deinterlace") + +#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; + hw_mmal_port_pool_ref_t *out_ppr; - MMAL_QUEUE_T *filtered_pictures; - vlc_sem_t sem; + MMAL_QUEUE_T * out_q; - atomic_bool started; + bool half_rate; + bool use_qpu; + bool use_fast; + bool use_passthrough; + unsigned int seq_in; + unsigned int seq_out; +} filter_sys_t; - /* statistics */ - int output_in_transit; - int input_in_transit; -}; - -static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); -static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); -static picture_t *deinterlace(filter_t *filter, picture_t *picture); -static void flush(filter_t *filter); #define MMAL_COMPONENT_DEFAULT_DEINTERLACE "vc.ril.image_fx" -static int Open(filter_t *filter) -{ - 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); +#define TRACE_ALL 0 - 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 } - }; - int ret = VLC_SUCCESS; - MMAL_STATUS_T status; - filter_sys_t *sys; - msg_Dbg(filter, "Try to open mmal_deinterlace filter. frame_duration: %d, QPU %s!", - frame_duration, use_qpu ? "used" : "unused"); +// 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) +{ + filter_sys_t *const filter_sys = p_filter->p_sys; + picture_t * const pic = filter_NewPicture(p_filter); - if (filter->fmt_in.video.i_chroma != VLC_CODEC_MMAL_OPAQUE) - return VLC_EGENERIC; + if (pic == NULL) + goto fail1; - if (filter->fmt_out.video.i_chroma != VLC_CODEC_MMAL_OPAQUE) - return VLC_EGENERIC; + if (buf->length == 0) { + msg_Err(p_filter, "%s: Empty buffer", __func__); + goto fail2; + } - sys = calloc(1, sizeof(filter_sys_t)); - if (!sys) - return VLC_ENOMEM; - filter->p_sys = sys; + if ((pic->context = hw_mmal_gen_context(MMAL_ENCODING_OPAQUE, buf, filter_sys->out_ppr)) == NULL) + goto fail2; - bcm_host_init(); + buf_to_pic_copy_props(pic, buf); - 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; - } +#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 - 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; - } + return pic; - 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; - } +fail2: + picture_Release(pic); +fail1: +// mmal_buffer_header_release(buf); + return NULL; +} - 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; +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 - es_format_Copy(&filter->fmt_out, &filter->fmt_in); - filter->fmt_out.video.i_frame_rate *= 2; + mmal_buffer_header_release(buffer); - 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; +#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 } - sys->input->buffer_size = sys->input->buffer_size_recommended; - sys->input->buffer_num = sys->input->buffer_num_recommended; + else + { + mmal_buffer_header_reset(buf); + mmal_buffer_header_release(buf); + } +} - if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) { - MMAL_PARAMETER_BOOLEAN_T zero_copy = { - { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, - 1 - }; +static MMAL_STATUS_T fill_output_from_q(filter_t * const p_filter, filter_sys_t * const sys, MMAL_QUEUE_T * const q) +{ + MMAL_BUFFER_HEADER_T * out_buf; - status = mmal_port_parameter_set(sys->input, &zero_copy.hdr); - if (status != MMAL_SUCCESS) { - msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", - sys->input->name, status, mmal_status_to_string(status)); - goto out; + while ((out_buf = mmal_queue_get(q)) != NULL) + { + MMAL_STATUS_T err; + if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS) + { + msg_Err(p_filter, "Send buffer to output failed"); + mmal_queue_put_back(q, out_buf); + return err; } } + return MMAL_SUCCESS; +} - status = mmal_port_enable(sys->input, input_port_cb); - if (status != MMAL_SUCCESS) { - msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)", - sys->input->name, status, mmal_status_to_string(status)); - ret = VLC_EGENERIC; - goto out; - } +static inline unsigned int seq_inc(unsigned int x) +{ + return x + 1 >= 16 ? 1 : x + 1; +} - 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); +static inline unsigned int seq_delta(unsigned int sseq, unsigned int fseq) +{ + return fseq == 0 ? 0 : fseq <= sseq ? sseq - fseq : 15 - (fseq - sseq); +} - 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; - } +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; - sys->output->buffer_num = 3; +#if TRACE_ALL + msg_Dbg(p_filter, "<<< %s", __func__); +#endif - if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) { - MMAL_PARAMETER_UINT32_T extra_buffers = { - { MMAL_PARAMETER_EXTRA_BUFFERS, sizeof(MMAL_PARAMETER_UINT32_T) }, - 5 - }; - status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr); - if (status != MMAL_SUCCESS) { - msg_Err(filter, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", - status, mmal_status_to_string(status)); - goto out; + // 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 enable failed"); + goto fail; + } + + // 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; + } + + // Stuff into input + // We assume the BH is already set up with values reflecting pic date etc. + { + MMAL_BUFFER_HEADER_T * const pic_buf = pic_mmal_buffer(p_pic); + MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(sys->in_pool->queue); + + if ((err = mmal_buffer_header_replicate(buf, pic_buf)) != MMAL_SUCCESS) + { + msg_Err(p_filter, "Failed to replicate input buffer: %d", err); + goto fail; } - MMAL_PARAMETER_BOOLEAN_T zero_copy = { - { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) }, - 1 - }; +#if TRACE_ALL + msg_Dbg(p_filter, "In buf send: pic=%p, buf=%p/%p, ctx=%p, flags=%#x, len=%d/%d, pts=%lld", + p_pic, pic_buf, buf, pic_buf->user_data, buf->flags, buf->length, buf->alloc_size, (long long)buf->pts); +#endif - status = mmal_port_parameter_set(sys->output, &zero_copy.hdr); - if (status != MMAL_SUCCESS) { - msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", - sys->output->name, status, mmal_status_to_string(status)); - goto out; + picture_Release(p_pic); + + // Add a sequence to the flags so we can track what we have actually + // deinterlaced + buf->flags = (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, buf)) != MMAL_SUCCESS) + { + msg_Err(p_filter, "Send buffer to input failed"); + 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 + { + MMAL_BUFFER_HEADER_T * out_buf; + 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. + while ((out_buf = (seq_delta(sys->seq_in, sys->seq_out) > 3 ? mmal_queue_wait(sys->out_q) : mmal_queue_get(sys->out_q))) != NULL) + { + picture_t * const out_pic = di_alloc_opaque(p_filter, out_buf); + const unsigned int seq_out = (out_buf->flags / MMAL_BUFFER_HEADER_FLAG_USER0) & 0xf; + + if (out_pic == NULL) { + msg_Warn(p_filter, "Failed to alloc new filter output pic"); + mmal_queue_put_back(sys->out_q, out_buf); + break; + } - 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: 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 - sys->filtered_pictures = mmal_queue_create(); + *pp_pic = out_pic; + pp_pic = &out_pic->p_next; - filter->pf_video_filter = deinterlace; - filter->pf_flush = flush; + // Ignore 0 seqs + // Don't think these should actually happen + if (seq_out != 0) + sys->seq_out = seq_out; + } + } - vlc_sem_init(&sys->sem, 0); +#if TRACE_ALL + msg_Dbg(p_filter, ">>> %s: pic=%p", __func__, ret_pics); +#endif -out: - if (ret != VLC_SUCCESS) - Close(filter); + return ret_pics; - return ret; +fail: + picture_Release(p_pic); + return NULL; } -static void Close(filter_t *filter) +static void di_flush(filter_t *p_filter) { - filter_sys_t *sys = filter->p_sys; - MMAL_BUFFER_HEADER_T *buffer; + filter_sys_t * const sys = p_filter->p_sys; - if (!sys) - return; - - if (sys->component && sys->component->control->is_enabled) - mmal_port_disable(sys->component->control); +#if TRACE_ALL + msg_Dbg(p_filter, "<<< %s", __func__); +#endif - if (sys->input && sys->input->is_enabled) + if (sys->input != NULL && sys->input->is_enabled) mmal_port_disable(sys->input); - if (sys->output && sys->output->is_enabled) + if (sys->output != NULL && sys->output->is_enabled) + { + // 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 (sys->component && sys->component->is_enabled) - mmal_component_disable(sys->component); + // 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); - while ((buffer = mmal_queue_get(sys->filtered_pictures))) { - picture_t *pic = (picture_t *)buffer->user_data; - picture_Release(pic); + // 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 } - if (sys->filtered_pictures) - mmal_queue_destroy(sys->filtered_pictures); + sys->seq_in = 1; + sys->seq_out = 1; - if (sys->component) - mmal_component_release(sys->component); +#if TRACE_ALL + msg_Dbg(p_filter, ">>> %s", __func__); +#endif +} - vlc_sem_destroy(&sys->sem); - free(sys); - bcm_host_deinit(); +static void pass_flush(filter_t *p_filter) +{ + // Nothing to do + VLC_UNUSED(p_filter); +} + +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 int send_output_buffer(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; - picture_t *picture; - int ret = 0; - if (!sys->output->is_enabled) { - ret = VLC_EGENERIC; - goto out; + if (buffer->cmd == MMAL_EVENT_ERROR) { + status = *(uint32_t *)buffer->data; + msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status, + mmal_status_to_string(status)); } - 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; + mmal_buffer_header_reset(buffer); + mmal_buffer_header_release(buffer); +} + +static void CloseMmalDeinterlace(filter_t *filter) +{ + filter_sys_t * const sys = filter->p_sys; - buffer = picture->p_sys->buffer; - buffer->user_data = picture; - buffer->cmd = 0; +#if TRACE_ALL + msg_Dbg(filter, "<<< %s", __func__); +#endif - mmal_picture_lock(picture); + if (sys == NULL) + return; - 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->use_passthrough) + { + free(sys); + return; } -out: - return ret; + di_flush(filter); + + 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->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 + sys->output->userdata = NULL; + + if (sys->out_q != NULL) + mmal_queue_destroy(sys->out_q); + + if (sys->component) + mmal_component_release(sys->component); + + free(sys); + + bcm_host_deinit(); } -static void fill_output_port(filter_t *filter) + +static int OpenMmalDeinterlace(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; + 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; + filter_sys_t *sys; + + msg_Dbg(filter, "<<< %s", __func__); - if (buffers_to_send > buffers_available) - buffers_to_send = buffers_available; + if (filter->fmt_in.video.i_chroma != VLC_CODEC_MMAL_OPAQUE || + filter->fmt_out.video.i_chroma != VLC_CODEC_MMAL_OPAQUE) + return VLC_EGENERIC; -#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); +#if TRACE_ALL + msg_Dbg(filter, "Try to open mmal_deinterlace filter. frame_duration: %d, QPU %s!", + frame_duration, use_qpu ? "used" : "unused"); #endif - for (i = 0; i < buffers_to_send; ++i) { - if (send_output_buffer(filter) < 0) - break; + + sys = calloc(1, sizeof(filter_sys_t)); + if (!sys) + return VLC_ENOMEM; + filter->p_sys = sys; + + sys->seq_in = 1; + sys->seq_out = 1; + 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 + if (hw_mmal_get_gpu_mem() < (96 << 20)) + sys->use_passthrough = true; + } -} -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; + 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; + return 0; + } + + bcm_host_init(); - 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); + msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)", + MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status)); + goto fail; } - } 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; - } + 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; } - if (out_picture) - out_picture->p_next = NULL; - return ret; -} + 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; + vlc_to_mmal_video_fmt(sys->input->format, &filter->fmt_in.video); -static void flush(filter_t *filter) -{ - filter_sys_t *sys = filter->p_sys; - MMAL_BUFFER_HEADER_T *buffer; + 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 deinterlace filter"); + 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; - msg_Dbg(filter, "flush: flush ports (input: %d, output: %d in transit)", - sys->input_in_transit, sys->output_in_transit); - mmal_port_flush(sys->output); - mmal_port_flush(sys->input); - - msg_Dbg(filter, "flush: wait for all buffers to be returned"); - while (atomic_load(&sys->input_in_transit) || - atomic_load(&sys->output_in_transit)) - vlc_sem_wait(&sys->sem); - - while ((buffer = mmal_queue_get(sys->filtered_pictures))) { - picture_t *pic = (picture_t *)buffer->user_data; - msg_Dbg(filter, "flush: release already filtered pic %p", - (void *)pic); - picture_Release(pic); + 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; + } - if (picture) { - picture_Release(picture); - } else { - msg_Warn(filter, "Got buffer without picture on input port - OOOPS"); - mmal_buffer_header_release(buffer); + sys->output = sys->component->output[0]; + mmal_format_full_copy(sys->output->format, sys->input->format); + + if ((status = hw_mmal_opaque_output(VLC_OBJECT(filter), &sys->out_ppr, sys->output, 5, di_output_port_cb)) != MMAL_SUCCESS) + 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; } - atomic_fetch_sub(&sys->input_in_transit, 1); - vlc_sem_post(&sys->sem); + filter->pf_video_filter = deinterlace; + filter->pf_flush = di_flush; + return 0; + +fail: + CloseMmalDeinterlace(filter); + return ret; } -static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - filter_t *filter = (filter_t *)port->userdata; - filter_sys_t *sys = filter->p_sys; - picture_t *picture; +vlc_module_begin() + set_shortname(N_("MMAL deinterlace")) + set_description(N_("MMAL-based deinterlace filter plugin")) + set_capability("video filter", 900) + set_category(CAT_VIDEO) + set_subcategory(SUBCAT_VIDEO_VFILTER) + set_callbacks(OpenMmalDeinterlace, CloseMmalDeinterlace) + add_shortcut("deinterlace") + add_bool(MMAL_DEINTERLACE_NO_QPU, false, MMAL_DEINTERLACE_NO_QPU_TEXT, + MMAL_DEINTERLACE_NO_QPU_LONGTEXT, true); + add_bool(MMAL_DEINTERLACE_ADV, false, MMAL_DEINTERLACE_ADV_TEXT, + MMAL_DEINTERLACE_ADV_LONGTEXT, true); + add_bool(MMAL_DEINTERLACE_FAST, false, MMAL_DEINTERLACE_FAST_TEXT, + MMAL_DEINTERLACE_FAST_LONGTEXT, true); + add_bool(MMAL_DEINTERLACE_NONE, false, MMAL_DEINTERLACE_NONE_TEXT, + MMAL_DEINTERLACE_NONE_LONGTEXT, true); + add_bool(MMAL_DEINTERLACE_HALF_RATE, false, MMAL_DEINTERLACE_HALF_RATE_TEXT, + MMAL_DEINTERLACE_HALF_RATE_LONGTEXT, true); + add_bool(MMAL_DEINTERLACE_FULL_RATE, false, MMAL_DEINTERLACE_FULL_RATE_TEXT, + MMAL_DEINTERLACE_FULL_RATE_LONGTEXT, true); + +vlc_module_end() - if (buffer->cmd == 0) { - if (buffer->length > 0) { - atomic_store(&sys->started, true); - mmal_queue_put(sys->filtered_pictures, buffer); - picture = (picture_t *)buffer->user_data; - } else { - picture = (picture_t *)buffer->user_data; - picture_Release(picture); - } - atomic_fetch_sub(&sys->output_in_transit, 1); - vlc_sem_post(&sys->sem); - } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) { - msg_Warn(filter, "MMAL_EVENT_FORMAT_CHANGED seen but not handled"); - mmal_buffer_header_release(buffer); - } else { - mmal_buffer_header_release(buffer); - } -} --- /dev/null +++ b/modules/hw/mmal/mmal_avcodec.c @@ -0,0 +1,2275 @@ +/***************************************************************************** + * 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 + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "../../codec/avcodec/avcommon.h" + +#include + +#include +#include +#include +#include +#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 16, 101 ) ) +#include +#endif + +#include "mmal_picture.h" + +#define TRACE_ALL 0 + +#define AVPROVIDER(lib) ((lib##_VERSION_MICRO < 100) ? "libav" : "ffmpeg") + +#ifdef HAVE_LIBAVCODEC_AVCODEC_H +#include + +/* LIBAVCODEC_VERSION_CHECK checks for the right version of libav and FFmpeg + * a is the major version + * b and c the minor and micro versions of libav + * d and e the minor and micro versions of FFmpeg */ +#define LIBAVCODEC_VERSION_CHECK( a, b, c, d, e ) \ + ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( a, b, c ) ) || \ + (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( a, d, e ) ) ) + +#ifndef AV_CODEC_FLAG_OUTPUT_CORRUPT +# define AV_CODEC_FLAG_OUTPUT_CORRUPT CODEC_FLAG_OUTPUT_CORRUPT +#endif +#ifndef AV_CODEC_FLAG_GRAY +# define AV_CODEC_FLAG_GRAY CODEC_FLAG_GRAY +#endif +#ifndef AV_CODEC_FLAG_DR1 +# define AV_CODEC_FLAG_DR1 CODEC_FLAG_DR1 +#endif +#ifndef AV_CODEC_FLAG_DELAY +# define AV_CODEC_FLAG_DELAY CODEC_FLAG_DELAY +#endif +#ifndef AV_CODEC_FLAG2_FAST +# define AV_CODEC_FLAG2_FAST CODEC_FLAG2_FAST +#endif +#ifndef FF_INPUT_BUFFER_PADDING_SIZE +# define FF_INPUT_BUFFER_PADDING_SIZE AV_INPUT_BUFFER_PADDING_SIZE +#endif +#ifndef AV_CODEC_FLAG_INTERLACED_DCT +# define AV_CODEC_FLAG_INTERLACED_DCT CODEC_FLAG_INTERLACED_DCT +#endif +#ifndef AV_CODEC_FLAG_INTERLACED_ME +# define AV_CODEC_FLAG_INTERLACED_ME CODEC_FLAG_INTERLACED_ME +#endif +#ifndef AV_CODEC_FLAG_GLOBAL_HEADER +# define AV_CODEC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER +#endif +#ifndef AV_CODEC_FLAG_LOW_DELAY +# define AV_CODEC_FLAG_LOW_DELAY CODEC_FLAG_LOW_DELAY +#endif +#ifndef AV_CODEC_CAP_SMALL_LAST_FRAME +# define AV_CODEC_CAP_SMALL_LAST_FRAME CODEC_CAP_SMALL_LAST_FRAME +#endif +#ifndef AV_INPUT_BUFFER_MIN_SIZE +# define AV_INPUT_BUFFER_MIN_SIZE FF_MIN_BUFFER_SIZE +#endif +#ifndef FF_MAX_B_FRAMES +# define FF_MAX_B_FRAMES 16 // FIXME: remove this +#endif + +#endif /* HAVE_LIBAVCODEC_AVCODEC_H */ + +#ifdef HAVE_LIBAVUTIL_AVUTIL_H +# include + +/* LIBAVUTIL_VERSION_CHECK checks for the right version of libav and FFmpeg + * a is the major version + * b and c the minor and micro versions of libav + * d and e the minor and micro versions of FFmpeg */ +#define LIBAVUTIL_VERSION_CHECK( a, b, c, d, e ) \ + ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( a, b, c ) ) || \ + (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( a, d, e ) ) ) + +#if !LIBAVUTIL_VERSION_CHECK( 52, 11, 0, 32, 100 ) +# define AV_PIX_FMT_FLAG_HWACCEL PIX_FMT_HWACCEL +#endif + +#endif /* HAVE_LIBAVUTIL_AVUTIL_H */ + +#if LIBAVUTIL_VERSION_MAJOR >= 55 +# define FF_API_AUDIOCONVERT 1 +#endif + +/* libavutil/pixfmt.h */ +#ifndef PixelFormat +# define PixelFormat AVPixelFormat +#endif + +#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H +# include + +#define LIBAVFORMAT_VERSION_CHECK( a, b, c, d, e ) \ + ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT( a, b, c ) ) || \ + (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT( a, d, e ) ) ) + +#endif + +/***************************************************************************** + * Codec fourcc -> libavcodec Codec_id mapping + * Sorted by AVCodecID enumeration order + *****************************************************************************/ +struct vlc_avcodec_fourcc +{ + vlc_fourcc_t i_fourcc; + unsigned i_codec; +}; + +/* + * Video Codecs + */ +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 }, + /* 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_AYUV */ + /* 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 +}; + +/* + * Audio Codecs + */ +static const struct vlc_avcodec_fourcc audio_codecs[] = +{ + /* PCM */ + { VLC_CODEC_S16L, AV_CODEC_ID_PCM_S16LE }, + { VLC_CODEC_S16B, AV_CODEC_ID_PCM_S16BE }, + { VLC_CODEC_U16L, AV_CODEC_ID_PCM_U16LE }, + { VLC_CODEC_U16B, AV_CODEC_ID_PCM_U16BE }, + { VLC_CODEC_S8, AV_CODEC_ID_PCM_S8 }, + { VLC_CODEC_U8, AV_CODEC_ID_PCM_U8 }, + { VLC_CODEC_MULAW, AV_CODEC_ID_PCM_MULAW }, + { VLC_CODEC_ALAW, AV_CODEC_ID_PCM_ALAW }, + { VLC_CODEC_S32L, AV_CODEC_ID_PCM_S32LE }, + { VLC_CODEC_S32B, AV_CODEC_ID_PCM_S32BE }, + { VLC_CODEC_U32L, AV_CODEC_ID_PCM_U32LE }, + { VLC_CODEC_U32B, AV_CODEC_ID_PCM_U32BE }, + { VLC_CODEC_S24L, AV_CODEC_ID_PCM_S24LE }, + { VLC_CODEC_S24B, AV_CODEC_ID_PCM_S24BE }, + { VLC_CODEC_U24L, AV_CODEC_ID_PCM_U24LE }, + { VLC_CODEC_U24B, AV_CODEC_ID_PCM_U24BE }, + { VLC_CODEC_S24DAUD, AV_CODEC_ID_PCM_S24DAUD }, + /* AV_CODEC_ID_PCM_ZORK */ + { VLC_CODEC_S16L_PLANAR, AV_CODEC_ID_PCM_S16LE_PLANAR }, + /* AV_CODEC_ID_PCM_DVD */ + { VLC_CODEC_F32B, AV_CODEC_ID_PCM_F32BE }, + { VLC_CODEC_F32L, AV_CODEC_ID_PCM_F32LE }, + { VLC_CODEC_F64B, AV_CODEC_ID_PCM_F64BE }, + { VLC_CODEC_F64L, AV_CODEC_ID_PCM_F64LE }, + { VLC_CODEC_BD_LPCM, AV_CODEC_ID_PCM_BLURAY }, + /* AV_CODEC_ID_PCM_LXF */ + /* AV_CODEC_ID_S302M */ + /* AV_CODEC_ID_PCM_S8_PLANAR */ + /* AV_CODEC_ID_PCM_S24LE_PLANAR */ + /* AV_CODEC_ID_PCM_S32LE_PLANAR */ + /* ffmpeg only: AV_CODEC_ID_PCM_S16BE_PLANAR */ + + /* ADPCM */ + { VLC_CODEC_ADPCM_IMA_QT, AV_CODEC_ID_ADPCM_IMA_QT }, + { VLC_CODEC_ADPCM_IMA_WAV, AV_CODEC_ID_ADPCM_IMA_WAV }, + /* AV_CODEC_ID_ADPCM_IMA_DK3 */ + /* AV_CODEC_ID_ADPCM_IMA_DK4 */ + { VLC_CODEC_ADPCM_IMA_WS, AV_CODEC_ID_ADPCM_IMA_WS }, + /* AV_CODEC_ID_ADPCM_IMA_SMJPEG */ + { VLC_CODEC_ADPCM_MS, AV_CODEC_ID_ADPCM_MS }, + { VLC_CODEC_ADPCM_4XM, AV_CODEC_ID_ADPCM_4XM }, + { VLC_CODEC_ADPCM_XA, AV_CODEC_ID_ADPCM_XA }, + { VLC_CODEC_ADPCM_ADX, AV_CODEC_ID_ADPCM_ADX }, + { VLC_CODEC_ADPCM_EA, AV_CODEC_ID_ADPCM_EA }, + { VLC_CODEC_ADPCM_G726, AV_CODEC_ID_ADPCM_G726 }, + { VLC_CODEC_ADPCM_CREATIVE, AV_CODEC_ID_ADPCM_CT }, + { VLC_CODEC_ADPCM_SWF, AV_CODEC_ID_ADPCM_SWF }, + { VLC_CODEC_ADPCM_YAMAHA, AV_CODEC_ID_ADPCM_YAMAHA }, + { VLC_CODEC_ADPCM_SBPRO_4, AV_CODEC_ID_ADPCM_SBPRO_4 }, + { VLC_CODEC_ADPCM_SBPRO_3, AV_CODEC_ID_ADPCM_SBPRO_3 }, + { VLC_CODEC_ADPCM_SBPRO_2, AV_CODEC_ID_ADPCM_SBPRO_2 }, + { VLC_CODEC_ADPCM_THP, AV_CODEC_ID_ADPCM_THP }, + { VLC_CODEC_ADPCM_IMA_AMV, AV_CODEC_ID_ADPCM_IMA_AMV }, + { VLC_CODEC_ADPCM_EA_R1, AV_CODEC_ID_ADPCM_EA_R1 }, + /* AV_CODEC_ID_ADPCM_EA_R3 */ + /* AV_CODEC_ID_ADPCM_EA_R2 */ + { VLC_CODEC_ADPCM_IMA_EA_SEAD, AV_CODEC_ID_ADPCM_IMA_EA_SEAD }, + /* AV_CODEC_ID_ADPCM_IMA_EA_EACS */ + /* AV_CODEC_ID_ADPCM_EA_XAS */ + /* AV_CODEC_ID_ADPCM_EA_MAXIS_XA */ + /* AV_CODEC_ID_ADPCM_IMA_ISS */ + { VLC_CODEC_ADPCM_G722, AV_CODEC_ID_ADPCM_G722 }, + { VLC_CODEC_ADPCM_IMA_APC, AV_CODEC_ID_ADPCM_IMA_APC }, + /* ffmpeg only: AV_CODEC_ID_VIMA */ + /* ffmpeg only: AV_CODEC_ID_ADPCM_AFC */ + /* ffmpeg only: AV_CODEC_ID_ADPCM_IMA_OKI */ + /* ffmpeg only: AV_CODEC_ID_ADPCM_DTK */ + /* ffmpeg only: AV_CODEC_ID_ADPCM_IMA_RAD */ + /* ffmpeg only: AV_CODEC_ID_ADPCM_G726LE */ + + /* AMR */ + { VLC_CODEC_AMR_NB, AV_CODEC_ID_AMR_NB }, + { VLC_CODEC_AMR_WB, AV_CODEC_ID_AMR_WB }, + + /* RealAudio */ + { VLC_CODEC_RA_144, AV_CODEC_ID_RA_144 }, + { VLC_CODEC_RA_288, AV_CODEC_ID_RA_288 }, + + /* DPCM */ + { VLC_CODEC_ROQ_DPCM, AV_CODEC_ID_ROQ_DPCM }, + { VLC_CODEC_INTERPLAY_DPCM, AV_CODEC_ID_INTERPLAY_DPCM }, + /* AV_CODEC_ID_XAN_DPCM */ + /* AV_CODEC_ID_SOL_DPCM */ + + /* audio codecs */ + { VLC_CODEC_MPGA, AV_CODEC_ID_MP2 }, + { VLC_CODEC_MP2, AV_CODEC_ID_MP2 }, + { VLC_CODEC_MP3, AV_CODEC_ID_MP3 }, + { VLC_CODEC_MP4A, AV_CODEC_ID_AAC }, + { VLC_CODEC_A52, AV_CODEC_ID_AC3 }, + { VLC_CODEC_DTS, AV_CODEC_ID_DTS }, + { VLC_CODEC_VORBIS, AV_CODEC_ID_VORBIS }, + { VLC_CODEC_DVAUDIO, AV_CODEC_ID_DVAUDIO }, + { VLC_CODEC_WMA1, AV_CODEC_ID_WMAV1 }, + { VLC_CODEC_WMA2, AV_CODEC_ID_WMAV2 }, + { VLC_CODEC_MACE3, AV_CODEC_ID_MACE3 }, + { VLC_CODEC_MACE6, AV_CODEC_ID_MACE6 }, + { VLC_CODEC_VMDAUDIO, AV_CODEC_ID_VMDAUDIO }, + { VLC_CODEC_FLAC, AV_CODEC_ID_FLAC }, + /* AV_CODEC_ID_MP3ADU */ + /* AV_CODEC_ID_MP3ON4 */ + { VLC_CODEC_SHORTEN, AV_CODEC_ID_SHORTEN }, + { VLC_CODEC_ALAC, AV_CODEC_ID_ALAC }, + /* AV_CODEC_ID_WESTWOOD_SND1 */ + { VLC_CODEC_GSM, AV_CODEC_ID_GSM }, + { VLC_CODEC_QDM2, AV_CODEC_ID_QDM2 }, +#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 71, 100 ) + { VLC_CODEC_QDMC, AV_CODEC_ID_QDMC }, +#endif + { VLC_CODEC_COOK, AV_CODEC_ID_COOK }, + { VLC_CODEC_TRUESPEECH, AV_CODEC_ID_TRUESPEECH }, + { VLC_CODEC_TTA, AV_CODEC_ID_TTA }, + { VLC_CODEC_SMACKAUDIO, AV_CODEC_ID_SMACKAUDIO }, + { VLC_CODEC_QCELP, AV_CODEC_ID_QCELP }, + { VLC_CODEC_WAVPACK, AV_CODEC_ID_WAVPACK }, + { VLC_CODEC_DSICINAUDIO, AV_CODEC_ID_DSICINAUDIO }, + { VLC_CODEC_IMC, AV_CODEC_ID_IMC }, + { VLC_CODEC_MUSEPACK7, AV_CODEC_ID_MUSEPACK7 }, + { VLC_CODEC_MLP, AV_CODEC_ID_MLP }, + { VLC_CODEC_GSM_MS, AV_CODEC_ID_GSM_MS }, + { VLC_CODEC_ATRAC3, AV_CODEC_ID_ATRAC3 }, + { VLC_CODEC_APE, AV_CODEC_ID_APE }, + { VLC_CODEC_NELLYMOSER, AV_CODEC_ID_NELLYMOSER }, + { VLC_CODEC_MUSEPACK8, AV_CODEC_ID_MUSEPACK8 }, + { VLC_CODEC_SPEEX, AV_CODEC_ID_SPEEX }, + { VLC_CODEC_WMAS, AV_CODEC_ID_WMAVOICE }, + { VLC_CODEC_WMAP, AV_CODEC_ID_WMAPRO }, + { VLC_CODEC_WMAL, AV_CODEC_ID_WMALOSSLESS }, + { VLC_CODEC_ATRAC3P, AV_CODEC_ID_ATRAC3P }, + { VLC_CODEC_EAC3, AV_CODEC_ID_EAC3 }, + { VLC_CODEC_SIPR, AV_CODEC_ID_SIPR }, + /* AV_CODEC_ID_MP1 */ + { VLC_CODEC_TWINVQ, AV_CODEC_ID_TWINVQ }, + { VLC_CODEC_TRUEHD, AV_CODEC_ID_TRUEHD }, + { VLC_CODEC_ALS, AV_CODEC_ID_MP4ALS }, + { VLC_CODEC_ATRAC1, AV_CODEC_ID_ATRAC1 }, + { VLC_CODEC_BINKAUDIO_RDFT, AV_CODEC_ID_BINKAUDIO_RDFT }, + { VLC_CODEC_BINKAUDIO_DCT, AV_CODEC_ID_BINKAUDIO_DCT }, + { VLC_CODEC_MP4A, AV_CODEC_ID_AAC_LATM }, + /* AV_CODEC_ID_QDMC */ + /* AV_CODEC_ID_CELT */ + { VLC_CODEC_G723_1, AV_CODEC_ID_G723_1 }, + /* AV_CODEC_ID_G729 */ + /* AV_CODEC_ID_8SVX_EXP */ + /* AV_CODEC_ID_8SVX_FIB */ + { VLC_CODEC_BMVAUDIO, AV_CODEC_ID_BMV_AUDIO }, + { VLC_CODEC_RALF, AV_CODEC_ID_RALF }, + { VLC_CODEC_INDEO_AUDIO, AV_CODEC_ID_IAC }, + /* AV_CODEC_ID_ILBC */ + { VLC_CODEC_OPUS, AV_CODEC_ID_OPUS }, + /* AV_CODEC_ID_COMFORT_NOISE */ + { VLC_CODEC_TAK, AV_CODEC_ID_TAK }, + { VLC_CODEC_METASOUND, AV_CODEC_ID_METASOUND }, + /* AV_CODEC_ID_PAF_AUDIO */ + { VLC_CODEC_ON2AVC, AV_CODEC_ID_ON2AVC }, + + /* ffmpeg only: AV_CODEC_ID_FFWAVESYNTH */ + /* ffmpeg only: AV_CODEC_ID_SONIC */ + /* ffmpeg only: AV_CODEC_ID_SONIC_LS */ + /* ffmpeg only: AV_CODEC_ID_PAF_AUDIO */ + /* ffmpeg only: AV_CODEC_ID_EVRC */ + /* ffmpeg only: AV_CODEC_ID_SMV */ +}; + +/* Subtitle streams */ +static const struct vlc_avcodec_fourcc spu_codecs[] = +{ + { VLC_CODEC_SPU, AV_CODEC_ID_DVD_SUBTITLE }, + { VLC_CODEC_DVBS, AV_CODEC_ID_DVB_SUBTITLE }, + { VLC_CODEC_SUBT, AV_CODEC_ID_TEXT }, + { VLC_CODEC_XSUB, AV_CODEC_ID_XSUB }, + { VLC_CODEC_SSA, AV_CODEC_ID_SSA }, + /* AV_CODEC_ID_MOV_TEXT */ + { VLC_CODEC_BD_PG, AV_CODEC_ID_HDMV_PGS_SUBTITLE }, +#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 71, 100 ) + { VLC_CODEC_BD_TEXT, AV_CODEC_ID_HDMV_TEXT_SUBTITLE }, +#endif + { VLC_CODEC_TELETEXT, AV_CODEC_ID_DVB_TELETEXT }, + /* AV_CODEC_ID_SRT */ + /* ffmpeg only: AV_CODEC_ID_MICRODVD */ + /* ffmpeg only: AV_CODEC_ID_EIA_608 */ + /* ffmpeg only: AV_CODEC_ID_JACOSUB */ + /* ffmpeg only: AV_CODEC_ID_SAMI */ + /* ffmpeg only: AV_CODEC_ID_REALTEXT */ + /* ffmpeg only: AV_CODEC_ID_SUBVIEWER1 */ + /* ffmpeg only: AV_CODEC_ID_SUBVIEWER */ + /* ffmpeg only: AV_CODEC_ID_SUBRIP */ + /* ffmpeg only: AV_CODEC_ID_WEBVTT */ + /* ffmpeg only: AV_CODEC_ID_MPL2 */ + /* ffmpeg only: AV_CODEC_ID_VPLAYER */ + /* ffmpeg only: AV_CODEC_ID_PJS */ + /* ffmpeg only: AV_CODEC_ID_ASS */ +}; + +static bool GetFfmpegCodec( 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; + + switch( cat ) + { + case VIDEO_ES: + base = video_codecs; + count = ARRAY_SIZE(video_codecs); + break; + case AUDIO_ES: + base = audio_codecs; + count = ARRAY_SIZE(audio_codecs); + break; + case SPU_ES: + base = spu_codecs; + count = ARRAY_SIZE(spu_codecs); + break; + default: + base = NULL; + count = 0; + } + + 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; +} + +/***************************************************************************** + * Chroma fourcc -> libavutil pixfmt mapping + *****************************************************************************/ +#if defined(WORDS_BIGENDIAN) +# define VLC_RGB_ES( fcc, leid, beid ) \ + { fcc, beid, 0, 0, 0 }, +#else +# define VLC_RGB_ES( fcc, leid, beid ) \ + { fcc, leid, 0, 0, 0 }, +#endif + +#define VLC_RGB( fcc, leid, beid, rmask, gmask, bmask ) \ + { fcc, leid, rmask, gmask, bmask }, \ + { fcc, beid, bmask, gmask, rmask }, \ + VLC_RGB_ES( fcc, leid, beid ) + + +static const struct +{ + vlc_fourcc_t i_chroma; + int i_chroma_id; + uint32_t i_rmask; + uint32_t i_gmask; + uint32_t i_bmask; + +} chroma_table[] = +{ + // Sand +// {VLC_CODEC_MMAL_OPAQUE, AV_PIX_FMT_SAND128, 0, 0, 0 }, + {VLC_CODEC_MMAL_ZC_SAND8, AV_PIX_FMT_SAND128, 0, 0, 0 }, + {VLC_CODEC_MMAL_ZC_SAND10, AV_PIX_FMT_SAND64_10, 0, 0, 0 }, + + /* Planar YUV formats */ + {VLC_CODEC_I444, AV_PIX_FMT_YUV444P, 0, 0, 0 }, + {VLC_CODEC_J444, AV_PIX_FMT_YUVJ444P, 0, 0, 0 }, + + {VLC_CODEC_I440, AV_PIX_FMT_YUV440P, 0, 0, 0 }, + {VLC_CODEC_J440, AV_PIX_FMT_YUVJ440P, 0, 0, 0 }, + + {VLC_CODEC_I422, AV_PIX_FMT_YUV422P, 0, 0, 0 }, + {VLC_CODEC_J422, AV_PIX_FMT_YUVJ422P, 0, 0, 0 }, + + {VLC_CODEC_I420, AV_PIX_FMT_YUV420P, 0, 0, 0 }, + {VLC_CODEC_YV12, AV_PIX_FMT_YUV420P, 0, 0, 0 }, + {VLC_FOURCC('I','Y','U','V'), AV_PIX_FMT_YUV420P, 0, 0, 0 }, + {VLC_CODEC_J420, AV_PIX_FMT_YUVJ420P, 0, 0, 0 }, + {VLC_CODEC_I411, AV_PIX_FMT_YUV411P, 0, 0, 0 }, + {VLC_CODEC_I410, AV_PIX_FMT_YUV410P, 0, 0, 0 }, + {VLC_FOURCC('Y','V','U','9'), AV_PIX_FMT_YUV410P, 0, 0, 0 }, + + {VLC_CODEC_NV12, AV_PIX_FMT_NV12, 0, 0, 0 }, + {VLC_CODEC_NV21, AV_PIX_FMT_NV21, 0, 0, 0 }, + + {VLC_CODEC_I420_9L, AV_PIX_FMT_YUV420P9LE, 0, 0, 0 }, + {VLC_CODEC_I420_9B, AV_PIX_FMT_YUV420P9BE, 0, 0, 0 }, + {VLC_CODEC_I420_10L, AV_PIX_FMT_YUV420P10LE, 0, 0, 0 }, + {VLC_CODEC_I420_10B, AV_PIX_FMT_YUV420P10BE, 0, 0, 0 }, +#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 54, 17, 100 ) ) + {VLC_CODEC_I420_12L, AV_PIX_FMT_YUV420P12LE, 0, 0, 0 }, + {VLC_CODEC_I420_12B, AV_PIX_FMT_YUV420P12BE, 0, 0, 0 }, +#endif + {VLC_CODEC_I420_16L, AV_PIX_FMT_YUV420P16LE, 0, 0, 0 }, + {VLC_CODEC_I420_16B, AV_PIX_FMT_YUV420P16BE, 0, 0, 0 }, +#ifdef AV_PIX_FMT_P010 + {VLC_CODEC_P010, AV_PIX_FMT_P010, 0, 0, 0 }, +#endif + + {VLC_CODEC_I422_9L, AV_PIX_FMT_YUV422P9LE, 0, 0, 0 }, + {VLC_CODEC_I422_9B, AV_PIX_FMT_YUV422P9BE, 0, 0, 0 }, + {VLC_CODEC_I422_10L, AV_PIX_FMT_YUV422P10LE, 0, 0, 0 }, + {VLC_CODEC_I422_10B, AV_PIX_FMT_YUV422P10BE, 0, 0, 0 }, +#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 54, 17, 100 ) ) + {VLC_CODEC_I422_12L, AV_PIX_FMT_YUV422P12LE, 0, 0, 0 }, + {VLC_CODEC_I422_12B, AV_PIX_FMT_YUV422P12BE, 0, 0, 0 }, +#endif + + {VLC_CODEC_YUV420A, AV_PIX_FMT_YUVA420P, 0, 0, 0 }, + {VLC_CODEC_YUV422A, AV_PIX_FMT_YUVA422P, 0, 0, 0 }, + {VLC_CODEC_YUVA, AV_PIX_FMT_YUVA444P, 0, 0, 0 }, + + {VLC_CODEC_YUVA_444_10L, AV_PIX_FMT_YUVA444P10LE, 0, 0, 0 }, + {VLC_CODEC_YUVA_444_10B, AV_PIX_FMT_YUVA444P10BE, 0, 0, 0 }, + + {VLC_CODEC_I444_9L, AV_PIX_FMT_YUV444P9LE, 0, 0, 0 }, + {VLC_CODEC_I444_9B, AV_PIX_FMT_YUV444P9BE, 0, 0, 0 }, + {VLC_CODEC_I444_10L, AV_PIX_FMT_YUV444P10LE, 0, 0, 0 }, + {VLC_CODEC_I444_10B, AV_PIX_FMT_YUV444P10BE, 0, 0, 0 }, +#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 54, 17, 100 ) ) + {VLC_CODEC_I444_12L, AV_PIX_FMT_YUV444P12LE, 0, 0, 0 }, + {VLC_CODEC_I444_12B, AV_PIX_FMT_YUV444P12BE, 0, 0, 0 }, +#endif + {VLC_CODEC_I444_16L, AV_PIX_FMT_YUV444P16LE, 0, 0, 0 }, + {VLC_CODEC_I444_16B, AV_PIX_FMT_YUV444P16BE, 0, 0, 0 }, + + /* Packed YUV formats */ + {VLC_CODEC_YUYV, AV_PIX_FMT_YUYV422, 0, 0, 0 }, + {VLC_FOURCC('Y','U','Y','V'), AV_PIX_FMT_YUYV422, 0, 0, 0 }, + {VLC_CODEC_UYVY, AV_PIX_FMT_UYVY422, 0, 0, 0 }, + {VLC_CODEC_YVYU, AV_PIX_FMT_YVYU422, 0, 0, 0 }, + {VLC_FOURCC('Y','4','1','1'), AV_PIX_FMT_UYYVYY411, 0, 0, 0 }, + + /* Packed RGB formats */ + VLC_RGB( VLC_FOURCC('R','G','B','4'), AV_PIX_FMT_RGB4, AV_PIX_FMT_BGR4, 0x10, 0x06, 0x01 ) + VLC_RGB( VLC_CODEC_RGB8, AV_PIX_FMT_RGB8, AV_PIX_FMT_BGR8, 0xC0, 0x38, 0x07 ) + + VLC_RGB( VLC_CODEC_RGB15, AV_PIX_FMT_RGB555, AV_PIX_FMT_BGR555, 0x7c00, 0x03e0, 0x001f ) + VLC_RGB( VLC_CODEC_RGB16, AV_PIX_FMT_RGB565, AV_PIX_FMT_BGR565, 0xf800, 0x07e0, 0x001f ) + VLC_RGB( VLC_CODEC_RGB24, AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, 0xff0000, 0x00ff00, 0x0000ff ) + + VLC_RGB( VLC_CODEC_RGB32, AV_PIX_FMT_RGB32, AV_PIX_FMT_BGR32, 0x00ff0000, 0x0000ff00, 0x000000ff ) + VLC_RGB( VLC_CODEC_RGB32, AV_PIX_FMT_RGB32_1, AV_PIX_FMT_BGR32_1, 0xff000000, 0x00ff0000, 0x0000ff00 ) + +#ifdef AV_PIX_FMT_0BGR32 + VLC_RGB( VLC_CODEC_RGB32, AV_PIX_FMT_0BGR32, AV_PIX_FMT_0RGB32, 0x000000ff, 0x0000ff00, 0x00ff0000 ) +#endif + + {VLC_CODEC_RGBA, AV_PIX_FMT_RGBA, 0, 0, 0 }, + {VLC_CODEC_ARGB, AV_PIX_FMT_ARGB, 0, 0, 0 }, + {VLC_CODEC_BGRA, AV_PIX_FMT_BGRA, 0, 0, 0 }, + {VLC_CODEC_GREY, AV_PIX_FMT_GRAY8, 0, 0, 0}, + + /* Paletized RGB */ + {VLC_CODEC_RGBP, AV_PIX_FMT_PAL8, 0, 0, 0}, + + {VLC_CODEC_GBR_PLANAR, AV_PIX_FMT_GBRP, 0, 0, 0 }, + {VLC_CODEC_GBR_PLANAR_9L, AV_PIX_FMT_GBRP9LE, 0, 0, 0 }, + {VLC_CODEC_GBR_PLANAR_9B, AV_PIX_FMT_GBRP9BE, 0, 0, 0 }, + {VLC_CODEC_GBR_PLANAR_10L, AV_PIX_FMT_GBRP10LE, 0, 0, 0 }, + {VLC_CODEC_GBR_PLANAR_10B, AV_PIX_FMT_GBRP10BE, 0, 0, 0 }, + + /* XYZ */ +#if LIBAVUTIL_VERSION_CHECK(52, 10, 0, 25, 100) + {VLC_CODEC_XYZ12, AV_PIX_FMT_XYZ12, 0xfff0, 0xfff0, 0xfff0}, +#endif + { 0, 0, 0, 0, 0 } +}; + +static int GetVlcChroma( video_format_t *fmt, int i_ffmpeg_chroma ) +{ + /* TODO FIXME for rgb format we HAVE to set rgb mask/shift */ + for( int i = 0; chroma_table[i].i_chroma != 0; i++ ) + { + if( chroma_table[i].i_chroma_id == i_ffmpeg_chroma ) + { + fmt->i_rmask = chroma_table[i].i_rmask; + fmt->i_gmask = chroma_table[i].i_gmask; + fmt->i_bmask = chroma_table[i].i_bmask; + fmt->i_chroma = chroma_table[i].i_chroma; + return VLC_SUCCESS; + } + } + return VLC_EGENERIC; +} + +//#include "../codec/cc.h" + +static AVCodecContext *ffmpeg_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( !GetFfmpegCodec( 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_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; +} + +static int ffmpeg_OpenCodec( decoder_t *p_dec, AVCodecContext *ctx, + const AVCodec *codec ) +{ + char *psz_opts = var_InheritString( p_dec, "avcodec-options" ); + AVDictionary *options = NULL; + int ret; + + if (psz_opts) { + vlc_av_get_options(psz_opts, &options); + free(psz_opts); + } + + if (av_rpi_zc_init(ctx) != 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; +} + + +/***************************************************************************** + * 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 AVPixelFormat pix_fmt; + int profile; + int level; + + MMAL_POOL_T * out_pool; +}; + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ +static void ffmpeg_InitCodec ( decoder_t * ); +static enum PixelFormat ffmpeg_GetFormat( AVCodecContext *, + const enum PixelFormat * ); +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 (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); + + 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); + } + + if( ctx->color_range == AVCOL_RANGE_JPEG ) + fmt->b_color_range_full = true; + + 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; + + val = lavc_GetVideoFormat(dec, &fmt_out, ctx, fmt, swfmt); + if (val) + 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; + + return decoder_UpdateVideoFormat(dec); +} + +/***************************************************************************** + * Flush: + *****************************************************************************/ +static void Flush( decoder_t *p_dec ) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + AVCodecContext *p_context = p_sys->p_context; + + 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 */ + decoder_AbortPictures( p_dec, true ); + + /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */ + if( avcodec_is_open( p_context ) ) + avcodec_flush_buffers( p_context ); + + /* Reset cancel state to false */ + decoder_AbortPictures( p_dec, false ); +} + +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 void 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; + + int i_tick = p_context->ticks_per_frame; + if( i_tick <= 0 ) + i_tick = 1; + + /* interpolate the next PTS */ + 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 ) +{ + 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 ); + + if( i_display_date > VLC_TS_INVALID && i_display_date <= 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; +#if 0 + 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 ); + } + } +#endif + return 0; +} + + + +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 TRACE_ALL + msg_Dbg(p_dec, "<<< %s", __func__); +#endif + + 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 ); + + ret = ffmpeg_OpenCodec( p_dec, ctx, codec ); + 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; +} + +static MMAL_BOOL_T +zc_buf_pre_release_cb(MMAL_BUFFER_HEADER_T * buf, void *userdata) +{ + const AVRpiZcRefPtr fr_ref = userdata; + VLC_UNUSED(buf); + + av_rpi_zc_unref(fr_ref); + + return MMAL_FALSE; +} + +static MMAL_FOURCC_T +avfmt_to_mmal(const int avfmt) +{ + switch( avfmt ) + { + case AV_PIX_FMT_SAND128: + return MMAL_ENCODING_YUVUV128; + case AV_PIX_FMT_SAND64_10: + return MMAL_ENCODING_YUVUV64_10; + default: + break; + } + return MMAL_ENCODING_UNKNOWN; +} + +/***************************************************************************** + * DecodeBlock: Called to decode one or more frames + *****************************************************************************/ + + +// Returns +// -ve error +// 0 Need more input (EAGAIN) +// 1 Frame decoded (dropped or Qed) +// 2 Decode err +// 3 EOF + +static int rx_frame(decoder_t * const p_dec, decoder_sys_t * const p_sys, AVCodecContext * const p_context) +{ + AVFrame * frame = av_frame_alloc(); + picture_t * p_pic = NULL; + int ret; + + if (frame == NULL) + return VLC_ENOMEM; + + ret = avcodec_receive_frame(p_context, frame); + + if (ret != 0) + { + av_frame_free(&frame); + switch (ret) + { + case AVERROR(EAGAIN): + return 0; + + case AVERROR(ENOMEM): + case AVERROR(EINVAL): + msg_Err(p_dec, "avcodec_receive_frame critical error"); + return VLC_EGENERIC; + + case AVERROR_EOF: + msg_Dbg(p_dec, "Rx EOF"); + avcodec_flush_buffers(p_context); + return 2; + + default: + msg_Warn(p_dec, "Decode error: %d", ret); + return 1; + } + } + + /* 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 ); + + interpolate_next_pts( p_dec, frame ); + +// update_late_frame_count( p_dec, p_block, current_time, i_pts); //********************* + + if( ( /* !p_sys->p_va && */ !frame->linesize[0] ) || + ( p_dec->b_frame_drop_allowed && (frame->flags & AV_FRAME_FLAG_CORRUPT) && + !p_sys->b_show_corrupted ) ) + { + msg_Dbg(p_dec, "Frame drop"); + av_frame_free(&frame); + return 1; + } + + lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt, + p_context->pix_fmt); + + { + MMAL_BUFFER_HEADER_T * const buf = mmal_queue_wait(p_sys->out_pool->queue); +// MMAL_BUFFER_HEADER_T * const buf = mmal_queue_get(p_sys->out_pool->queue); + if (buf == NULL) { + msg_Err(p_dec, "MMAL buffer alloc failure"); + av_frame_free(&frame); + return 1; + } + + mmal_buffer_header_reset(buf); // length, offset, flags, pts, dts + buf->cmd = 0; + buf->user_data = NULL; + + { + const AVRpiZcRefPtr fr_buf = av_rpi_zc_ref(p_context, frame, frame->format, 0); + + if (fr_buf == NULL) { + mmal_buffer_header_release(buf); + av_frame_free(&frame); + return VLC_ENOMEM; + } + + const intptr_t vc_handle = (intptr_t)av_rpi_zc_vc_handle(fr_buf); + + buf->data = (uint8_t *)vc_handle; // Cast our handle to a pointer for mmal - 2 steps to avoid gcc warnings + buf->offset = av_rpi_zc_offset(fr_buf); + buf->length = av_rpi_zc_length(fr_buf); + buf->alloc_size = av_rpi_zc_numbytes(fr_buf); + buf->flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END; + + mmal_buffer_header_pre_release_cb_set(buf, zc_buf_pre_release_cb, fr_buf); + } + + p_pic = decoder_NewPicture(p_dec); // *** Really want an empy pic + if (p_pic == NULL) + { + msg_Err(p_dec, "Picture alloc failure"); + mmal_buffer_header_release(buf); + av_frame_free(&frame); + return VLC_ENOMEM; + } + + p_pic->context = hw_mmal_gen_context(avfmt_to_mmal(frame->format), buf, NULL); + } + + + 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); + + p_sys->b_first_frame = false; + decoder_QueueVideo(p_dec, p_pic); + + return 1; +} + + + + + +static int DecodeVideo( decoder_t *p_dec, block_t * p_block) +{ + 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; + mtime_t current_time; + int rv = VLCDEC_SUCCESS; + + if( !p_context->extradata_size && p_dec->fmt_in.i_extra ) + { + ffmpeg_InitCodec( p_dec ); + if( !avcodec_is_open( p_context ) ) + OpenVideoCodec( p_dec ); + } + + if(!p_block && !(p_sys->p_codec->capabilities & AV_CODEC_CAP_DELAY) ) + return VLCDEC_SUCCESS; + + if( !avcodec_is_open( p_context ) ) + { + if( p_block ) + block_Release( p_block ); + return VLCDEC_ECRITICAL; + } + + if( !check_block_validity( p_sys, p_block ) ) + return VLCDEC_SUCCESS; + + 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 VLCDEC_SUCCESS; + } + + + /* A good idea could be to decode all I pictures and see for the other */ + b_need_output_picture = true; + + /* 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 VLCDEC_SUCCESS; + } + } + 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 VLCDEC_ECRITICAL; + + p_block->i_buffer -= FF_INPUT_BUFFER_PADDING_SIZE; + memset( p_block->p_buffer + p_block->i_buffer, 0, + FF_INPUT_BUFFER_PADDING_SIZE ); + } + + AVPacket pkt = {.data = NULL, .size = 0}; + + 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; + } + + if( !p_sys->palette_sent ) + { + uint8_t *pal = av_packet_new_side_data(&pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE); + if (pal) { + memcpy(pal, p_dec->fmt_in.video.p_palette->palette, AVPALETTE_SIZE); + p_sys->palette_sent = true; + } + } + +#if LIBAVCODEC_VERSION_CHECK( 57, 0, 0xFFFFFFFFU, 64, 101 ) + if( !b_need_output_picture ) + pkt.flags |= AV_PKT_FLAG_DISCARD; +#endif + + int ret = avcodec_send_packet(p_context, &pkt); + + if (ret == AVERROR(EAGAIN)) + { + // Cannot send more data until output drained - so do drain + while (rx_frame(p_dec, p_sys, p_context) == 1) + /* Loop */; + + // And try again - should not fail the same way + ret = avcodec_send_packet(p_context, &pkt); + } + + // Now done with pkt & block + av_packet_unref(&pkt); + if (p_block != NULL) + { + block_Release( p_block ); + p_block = NULL; + } + + if (ret != 0) + { + if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL)) + { + msg_Err(p_dec, "avcodec_send_packet critical error"); + rv = VLCDEC_ECRITICAL; + } + } + + while (rx_frame(p_dec, p_sys, p_context) == 1) + /* Loop */; + + if (eos_spotted) + p_sys->b_first_frame = true; + + return rv; +} + +/***************************************************************************** + * 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; + + /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */ + if( avcodec_is_open( ctx ) ) + avcodec_flush_buffers( ctx ); + +// cc_Flush( &p_sys->cc ); + + avcodec_close(ctx); + av_rpi_zc_uninit(ctx); + +// hwaccel_context = ctx->hwaccel_context; + avcodec_free_context( &ctx ); + + if( p_sys->out_pool != NULL ) + mmal_pool_destroy(p_sys->out_pool); + +// if( p_sys->p_va ) +// vlc_va_Delete( p_sys->p_va, &hwaccel_context ); + + 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 ); + } + } +} + + +static enum PixelFormat ffmpeg_GetFormat( AVCodecContext *p_context, + const enum PixelFormat *pi_fmt ) +{ + decoder_t *p_dec = p_context->opaque; + decoder_sys_t *p_sys = p_dec->p_sys; + video_format_t fmt; + + /* Enumerate available formats */ + enum PixelFormat swfmt = avcodec_default_get_format(p_context, pi_fmt); +// bool can_hwaccel = false; + + for (size_t i = 0; pi_fmt[i] != AV_PIX_FMT_NONE; i++) + { + const AVPixFmtDescriptor *dsc = av_pix_fmt_desc_get(pi_fmt[i]); + if (dsc == NULL) + continue; + bool hwaccel = (dsc->flags & AV_PIX_FMT_FLAG_HWACCEL) != 0; + + msg_Dbg( p_dec, "available %sware decoder output format %d (%s)", + hwaccel ? "hard" : "soft", pi_fmt[i], dsc->name ); +// if (hwaccel) +// can_hwaccel = true; + } + + /* If the format did not actually change (e.g. seeking), try to reuse the + * existing output format, and if present, hardware acceleration back-end. + * This avoids resetting the pipeline downstream. This also avoids + * needlessly probing for hardware acceleration support. */ + if (p_sys->pix_fmt != AV_PIX_FMT_NONE + && lavc_GetVideoFormat(p_dec, &fmt, p_context, p_sys->pix_fmt, swfmt) == 0 + && fmt.i_width == p_dec->fmt_out.video.i_width + && fmt.i_height == p_dec->fmt_out.video.i_height + && p_context->profile == p_sys->profile + && p_context->level <= p_sys->level) + { + for (size_t i = 0; pi_fmt[i] != AV_PIX_FMT_NONE; i++) + if (pi_fmt[i] == p_sys->pix_fmt) + { + msg_Dbg(p_dec, "reusing decoder output format %d", pi_fmt[i]); + return p_sys->pix_fmt; + } + } + +// if (p_sys->p_va != NULL) +// { +// msg_Err(p_dec, "existing hardware acceleration cannot be reused"); +// vlc_va_Delete(p_sys->p_va, &p_context->hwaccel_context); +// p_sys->p_va = NULL; +// } + + p_sys->profile = p_context->profile; + p_sys->level = p_context->level; + +#if 1 + return swfmt; +#else + if (!can_hwaccel) + return swfmt; + +#if (LIBAVCODEC_VERSION_MICRO >= 100) \ + && (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 83, 101)) + if (p_context->active_thread_type) + { + msg_Warn(p_dec, "thread type %d: disabling hardware acceleration", + p_context->active_thread_type); + return swfmt; + } +#endif + + wait_mt(p_sys); + + static const enum PixelFormat hwfmts[] = + { +#ifdef _WIN32 +#if LIBAVUTIL_VERSION_CHECK(54, 13, 1, 24, 100) + AV_PIX_FMT_D3D11VA_VLD, +#endif + AV_PIX_FMT_DXVA2_VLD, +#endif + AV_PIX_FMT_VAAPI_VLD, +#if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 4, 0)) + AV_PIX_FMT_VDPAU, +#endif + AV_PIX_FMT_NONE, + }; + + for( size_t i = 0; hwfmts[i] != AV_PIX_FMT_NONE; i++ ) + { + enum PixelFormat hwfmt = AV_PIX_FMT_NONE; + 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( hwfmt == AV_PIX_FMT_NONE ) + continue; + + p_dec->fmt_out.video.i_chroma = vlc_va_GetChroma(hwfmt, swfmt); + if (p_dec->fmt_out.video.i_chroma == 0) + continue; /* Unknown brand of hardware acceleration */ + if (p_context->width == 0 || p_context->height == 0) + { /* should never happen */ + msg_Err(p_dec, "unspecified video dimensions"); + continue; + } + const AVPixFmtDescriptor *dsc = av_pix_fmt_desc_get(hwfmt); + msg_Dbg(p_dec, "trying format %s", dsc ? dsc->name : "unknown"); + if (lavc_UpdateVideoFormat(p_dec, p_context, hwfmt, swfmt)) + continue; /* Unsupported brand of hardware acceleration */ + post_mt(p_sys); + + picture_t *test_pic = decoder_NewPicture(p_dec); + assert(!test_pic || test_pic->format.i_chroma == p_dec->fmt_out.video.i_chroma); + vlc_va_t *va = vlc_va_New(VLC_OBJECT(p_dec), p_context, hwfmt, + &p_dec->fmt_in, + test_pic ? test_pic->p_sys : NULL); + if (test_pic) + picture_Release(test_pic); + if (va == NULL) + { + wait_mt(p_sys); + continue; /* Unsupported codec profile or such */ + } + + if (va->description != NULL) + msg_Info(p_dec, "Using %s for hardware decoding", va->description); + + p_sys->p_va = va; + p_sys->pix_fmt = hwfmt; + p_context->draw_horiz_band = NULL; + return hwfmt; + } + + post_mt(p_sys); + /* Fallback to default behaviour */ + p_sys->pix_fmt = swfmt; + return swfmt; +#endif +} + +/***************************************************************************** + * 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). + *****************************************************************************/ + +/***************************************************************************** + * ffmpeg_OpenCodec: + *****************************************************************************/ + +static int MmalAvcodecOpenDecoder( vlc_object_t *obj ) +{ + decoder_t *p_dec = (decoder_t *)obj; + const AVCodec *p_codec; + + if (p_dec->fmt_in.i_codec != VLC_CODEC_HEVC) + return VLC_EGENERIC; + + AVCodecContext *p_context = ffmpeg_AllocContext( p_dec, &p_codec ); + if( p_context == NULL ) + return VLC_EGENERIC; + + int i_val; + + /* Allocate the memory needed to store the decoder's structure */ + decoder_sys_t *p_sys = calloc( 1, sizeof(*p_sys) ); + if( unlikely(p_sys == NULL) ) + { + avcodec_free_context( &p_context ); + return VLC_ENOMEM; + } + + p_dec->p_sys = p_sys; + p_sys->p_context = p_context; + p_sys->p_codec = p_codec; +// p_sys->p_va = NULL; + + /* ***** 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 = 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; + + int i_thread_count = var_InheritInteger( p_dec, "avcodec-threads" ); + if( i_thread_count <= 0 ) + i_thread_count = 6; +#if 0 + if( i_thread_count <= 0 ) + { + 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; + + p_context->thread_type = FF_THREAD_SLICE | FF_THREAD_FRAME; + + 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( GetVlcChroma( &p_dec->fmt_out.video, p_context->pix_fmt ) != VLC_SUCCESS ) + { + /* we are doomed. but not really, because most codecs set their pix_fmt later on */ + p_dec->fmt_out.i_codec = VLC_CODEC_I420; + } + p_dec->fmt_out.i_codec = 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; + + /* ***** init this codec with special data ***** */ + ffmpeg_InitCodec( p_dec ); + + /* ***** Open the codec ***** */ + if( OpenVideoCodec( p_dec ) < 0 ) + { + free( p_sys ); + avcodec_free_context( &p_context ); + return VLC_EGENERIC; + } + + if ((p_sys->out_pool = mmal_pool_create(5, 0)) == NULL) + { + msg_Err(p_dec, "Failed to create mmal buffer pool"); + goto fail; + } + + 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; + return VLC_SUCCESS; + +fail: + MmalAvcodecCloseDecoder(VLC_OBJECT(p_dec)); + return VLC_EGENERIC; +} + + + +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", 800) + add_shortcut("mmal_avcodec") + set_callbacks(MmalAvcodecOpenDecoder, MmalAvcodecCloseDecoder) +vlc_module_end() + --- a/modules/hw/mmal/mmal_picture.c +++ b/modules/hw/mmal/mmal_picture.c @@ -21,25 +21,961 @@ * 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 +#include +#include #include "mmal_picture.h" -int mmal_picture_lock(picture_t *picture) + +static void flush_range(void * start, size_t len) +{ + struct vcsm_user_clean_invalid2_s * b = calloc(1, + sizeof(struct vcsm_user_clean_invalid2_s) + sizeof(struct vcsm_user_clean_invalid2_block_s)); + + b->op_count = 1, + + b->s[0] = (struct vcsm_user_clean_invalid2_block_s){ + .invalidate_mode = 3, + .block_count = 1, + .start_address = start, + .block_size = len, + .inter_block_stride = 0 + }; + + vcsm_clean_invalid2(b); + + free(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_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_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; + default: + break; + } + return 0; +} + + +void vlc_to_mmal_video_fmt(MMAL_ES_FORMAT_T *const es_fmt, const video_frame_format_t * const vf_vlc) +{ + MMAL_VIDEO_FORMAT_T * const vf_mmal = &es_fmt->es->video; + + vf_mmal->width = (vf_vlc->i_width + 31) & ~31; + 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); +} + +hw_mmal_port_pool_ref_t * hw_mmal_port_pool_ref_create(MMAL_PORT_T * const port, + const unsigned int headers, const uint32_t payload_size) +{ + hw_mmal_port_pool_ref_t * ppr = calloc(1, sizeof(hw_mmal_port_pool_ref_t)); + if (ppr == NULL) + return NULL; + + if ((ppr->pool = mmal_port_pool_create(port, headers, payload_size)) == NULL) + goto fail; + + ppr->port = port; + atomic_store(&ppr->refs, 1); + return ppr; + +fail: + free(ppr); + return NULL; +} + +static void do_detached(void *(*fn)(void *), void * v) +{ + pthread_t dothread; + pthread_create(&dothread, NULL, fn, v); + pthread_detach(dothread); +} + +// Destroy a ppr - aranged s.t. it has the correct prototype for a pthread +static void * kill_ppr(void * v) +{ + hw_mmal_port_pool_ref_t * const ppr = v; + if (ppr->port->is_enabled) + mmal_port_disable(ppr->port); // Avoid annoyed messages from MMAL when we kill the pool + mmal_port_pool_destroy(ppr->port, ppr->pool); + free(ppr); + return NULL; +} + +void hw_mmal_port_pool_ref_release(hw_mmal_port_pool_ref_t * const ppr, const bool in_cb) +{ + if (ppr == NULL) + return; + if (atomic_fetch_sub(&ppr->refs, 1) != 1) + return; + if (in_cb) + do_detached(kill_ppr, ppr); + else + kill_ppr(ppr); +} + +// Put buffer in port if possible - if not then release to pool +// Returns true if sent, false if recycled +bool hw_mmal_port_pool_ref_recycle(hw_mmal_port_pool_ref_t * const ppr, MMAL_BUFFER_HEADER_T * const buf) +{ + mmal_buffer_header_reset(buf); + buf->user_data = NULL; + + if (mmal_port_send_buffer(ppr->port, buf) == MMAL_SUCCESS) + return true; + mmal_buffer_header_release(buf); + return false; +} + +MMAL_STATUS_T hw_mmal_port_pool_ref_fill(hw_mmal_port_pool_ref_t * const ppr) +{ + MMAL_BUFFER_HEADER_T * buf; + MMAL_STATUS_T err = MMAL_SUCCESS; + + while ((buf = mmal_queue_get(ppr->pool->queue)) != NULL) { + if ((err = mmal_port_send_buffer(ppr->port, buf)) != MMAL_SUCCESS) + { + mmal_queue_put_back(ppr->pool->queue, buf); + break; + } + } + return err; +} + + +MMAL_STATUS_T hw_mmal_opaque_output(vlc_object_t * const obj, + hw_mmal_port_pool_ref_t ** pppr, + MMAL_PORT_T * const port, + const unsigned int extra_buffers, MMAL_PORT_BH_CB_T callback) +{ + MMAL_STATUS_T status; + + port->userdata = (struct MMAL_PORT_USERDATA_T *)obj; + + status = port_parameter_set_uint32(port, MMAL_PARAMETER_EXTRA_BUFFERS, extra_buffers); + if (status != MMAL_SUCCESS) { + msg_Err(obj, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)", + status, mmal_status_to_string(status)); + return status; + } + + status = port_parameter_set_bool(port, MMAL_PARAMETER_ZERO_COPY, 1); + if (status != MMAL_SUCCESS) { + msg_Err(obj, "Failed to set zero copy on port %s (status=%"PRIx32" %s)", + port->name, status, mmal_status_to_string(status)); + return status; + } + + port->format->encoding = MMAL_ENCODING_OPAQUE; + port->format->encoding_variant = 0; + if ((status = mmal_port_format_commit(port)) != MMAL_SUCCESS) + { + msg_Err(obj, "Failed to commit format on port %s (status=%"PRIx32" %s)", + port->name, status, mmal_status_to_string(status)); + return status; + } + + port->buffer_num = 30; + port->buffer_size = port->buffer_size_recommended; + + if ((*pppr = hw_mmal_port_pool_ref_create(port, port->buffer_num, port->buffer_size)) == NULL) { + msg_Err(obj, "Failed to create output pool"); + return status; + } + + status = mmal_port_enable(port, callback); + if (status != MMAL_SUCCESS) { + 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) { + mmal_buffer_header_release(ctx->bufs[i]); + } + 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->buf_count = src_ctx->buf_count; + for (i = 0; i != src_ctx->buf_count; ++i) { + dst_ctx->bufs[i] = src_ctx->bufs[i]; + 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(const MMAL_FOURCC_T fmt, MMAL_BUFFER_HEADER_T * buf, hw_mmal_port_pool_ref_t * const ppr) +{ + pic_ctx_mmal_t * const ctx = calloc(1, sizeof(pic_ctx_mmal_t)); + + if (ctx == NULL) + return NULL; + + // If we have an associated ppr then ref & set appropriate callbacks + if (ppr != NULL) { + hw_mmal_port_pool_ref_acquire(ppr); + mmal_buffer_header_pre_release_cb_set(buf, buf_pre_release_cb, ppr); + buf->user_data = NULL; + } + + ctx->cmn.copy = hw_mmal_pic_ctx_copy; + ctx->cmn.destroy = hw_mmal_pic_ctx_destroy; + + ctx->fmt = fmt; + ctx->buf_count = 1; + ctx->bufs[0] = buf; + + return &ctx->cmn; +} + +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; +}; + +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; +} + + +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; +} + +void hw_mmal_vzc_buf_set_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const int x, const int y, const int w, const int h) +{ + vzc_subbuf_ent_t * sb = buf->user_data; + sb->orig_dest_rect.x = x; + sb->orig_dest_rect.y = y; + sb->orig_dest_rect.width = w; + sb->orig_dest_rect.height = h; +} + +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, mul_rect->width, div_rect->width); + d->y = rescale_x(s->y, mul_rect->height, div_rect->height); + d->width = rescale_x(s->width, mul_rect->width, div_rect->width); + d->height = rescale_x(s->height, mul_rect->height, div_rect->height); +} + +void hw_mmal_vzc_buf_scale_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const MMAL_RECT_T * const scale_rect) { - picture_sys_t *pic_sys = picture->p_sys; - MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer; + vzc_subbuf_ent_t * sb = buf->user_data; + if (scale_rect == NULL) { + sb->dreg.dest_rect = sb->orig_dest_rect; + } + else + { + rescale_rect(&sb->dreg.dest_rect, &sb->orig_dest_rect, + scale_rect, &sb->pic_rect); + } +} + +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. + +MMAL_BUFFER_HEADER_T * hw_mmal_vzc_buf_from_pic(vzc_pool_ctl_t * const pc, picture_t * const pic, const picture_t * const dst_pic, 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; + +// printf("+++ bpp:%d, vis:%dx%d wxh:%dx%d, d:%dx%d\n", bpp, fmt->i_visible_width, fmt->i_visible_height, fmt->i_width, fmt->i_height, dst_stride, dst_lines); + + sb->dreg.src_rect = (MMAL_RECT_T){ + .x = (fmt->i_x_offset - xl), + .y = 0, + .width = fmt->i_visible_width, + .height = fmt->i_visible_height + }; + + sb->pic_rect = (MMAL_RECT_T){ + .x = dst_pic->format.i_x_offset, + .y = dst_pic->format.i_y_offset, + .width = dst_pic->format.i_visible_width, + .height = dst_pic->format.i_visible_height + }; + + if (needs_copy) + { + ent->width = dst_stride / bpp; + ent->height = dst_lines; + + // 2D copy + { + unsigned int i; + 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; + for (i = 0; i != fmt->i_visible_height; ++i) { + memcpy(d, s, dst_stride); + d += dst_stride; + s += pic->p[0].i_pitch; + } + + // And make sure it is actually in memory + flush_range(ent->buf, d - (uint8_t *)ent->buf); + } + } + } + + 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); +} - 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; +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); + +// memset(pc, 0xba, sizeof(*pc)); // Zap for (hopefully) faster crash + + free (pc); + + vcsm_exit(); + +// 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; + + vcsm_init(); + + 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; } - pic_sys->displayed = false; + atomic_store(&pc->ref_count, 1); + + mmal_pool_callback_set(pc->buf_pool, vcz_pool_release_cb, pc); - return VLC_SUCCESS; + return pc; } + + + --- a/modules/hw/mmal/mmal_picture.h +++ b/modules/hw/mmal/mmal_picture.h @@ -24,19 +24,243 @@ #ifndef VLC_MMAL_MMAL_PICTURE_H_ #define VLC_MMAL_MMAL_PICTURE_H_ +#include + #include #include /* Think twice before changing this. Incorrect values cause havoc. */ #define NUM_ACTUAL_OPAQUE_BUFFERS 30 -struct picture_sys_t { - vlc_object_t *owner; +#ifndef VLC_TICK_INVALID +#define VLC_TICK_INVALID VLC_TS_INVALID +#define VLC_VER_3 1 +#else +#define VLC_VER_3 0 +#endif + +typedef struct mmal_port_pool_ref_s +{ + atomic_uint refs; + MMAL_POOL_T * pool; + MMAL_PORT_T * port; +} hw_mmal_port_pool_ref_t; + +typedef struct pic_ctx_subpic_s { + picture_t * subpic; + int x, y; + int alpha; +} pic_ctx_subpic_t; + + +#define CTX_BUFS_MAX 4 - MMAL_BUFFER_HEADER_T *buffer; - bool displayed; -}; +typedef struct pic_ctx_mmal_s { + picture_context_t cmn; // PARENT: Common els at start + + MMAL_FOURCC_T fmt; + + unsigned int buf_count; + MMAL_BUFFER_HEADER_T * bufs[CTX_BUFS_MAX]; + +#if 0 + MMAL_BUFFER_HEADER_T * buf; + hw_mmal_port_pool_ref_t * ppr; + + MMAL_BUFFER_HEADER_T * sub_bufs; + MMAL_BUFFER_HEADER_T * sub_tail; + + vlc_object_t * obj; +#endif +} pic_ctx_mmal_t; -int mmal_picture_lock(picture_t *picture); +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 vlc_to_mmal_video_fmt(MMAL_ES_FORMAT_T *const es_fmt, const video_frame_format_t * const vf_vlc); + +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_pic_is_mmal(const picture_t * const pic) +{ + return pic->format.i_chroma == VLC_CODEC_MMAL_OPAQUE || + pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND8 || + pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND10 || + pic->format.i_chroma == VLC_CODEC_MMAL_ZC_I420; +} + +static inline MMAL_FOURCC_T hw_mmal_pic_format(const picture_t *const pic) +{ + const pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context; + return ctx->fmt; +} + +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(const MMAL_FOURCC_T fmt, + 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; +} + +// Retrieve buf from pic & update with pic props +// Note that this is a weak pointer - replicate before putting in a Q +static inline MMAL_BUFFER_HEADER_T * pic_mmal_buffer(const picture_t *const pic) +{ + MMAL_BUFFER_HEADER_T * const buf = ((pic_ctx_mmal_t *)pic->context)->bufs[0]; + if (buf != NULL) + pic_to_buf_copy_props(buf, pic); + + return buf; +} + +struct vzc_pool_ctl_s; +typedef struct vzc_pool_ctl_s vzc_pool_ctl_t; + +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; + // At the moment we cope with any mono-planar RGBA thing + // We could cope with many other things but they currently don't occur + return vfcc_src == VLC_CODEC_RGBA || vfcc_src == VLC_CODEC_BGRA || vfcc_src == VLC_CODEC_ARGB; +} + +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_set_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const int x, const int y, const int w, const int h); +void hw_mmal_vzc_buf_scale_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const MMAL_RECT_T * const scale_rect); +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 picture_t * const dst_pic, 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); + +#define VOUT_DISPLAY_CHANGE_MMAL_BASE 1024 +#define VOUT_DISPLAY_CHANGE_MMAL_HIDE (VOUT_DISPLAY_CHANGE_MMAL_BASE + 0) + +#define MMAL_COMPONENT_DEFAULT_RESIZER "vc.ril.resize" +#define MMAL_COMPONENT_ISP_RESIZER "vc.ril.isp" +#define MMAL_COMPONENT_HVS "vc.ril.hvs" #endif --- /dev/null +++ b/modules/hw/mmal/rpi_prof.h @@ -0,0 +1,110 @@ +#ifndef RPI_PROFILE_H +#define RPI_PROFILE_H + +#include +#include + +#ifndef RPI_PROFILE +#define RPI_PROFILE 0 +#endif + +#if RPI_PROFILE + +#include "v7_pmu.h" + +#ifdef RPI_PROC_ALLOC +#define X volatile +#define Z =0 +#else +#define X extern volatile +#define Z +#endif + +X uint64_t av_rpi_prof0_cycles Z; +X unsigned int av_rpi_prof0_cnt Z; +#define RPI_prof0_MAX_DURATION 100000 + +X uint64_t av_rpi_prof1_cycles Z; +X unsigned int av_rpi_prof1_cnt Z; +#define RPI_prof1_MAX_DURATION 100000 + +X uint64_t av_rpi_prof2_cycles Z; +X unsigned int av_rpi_prof2_cnt Z; +#define RPI_prof2_MAX_DURATION 10000 + +X uint64_t av_rpi_prof_n_cycles[128]; +X unsigned int av_rpi_prof_n_cnt[128]; +#define RPI_prof_n_MAX_DURATION 10000 + + +#undef X +#undef Z + +#define PROFILE_INIT()\ +do {\ + enable_pmu();\ + enable_ccnt();\ +} while (0) + +#define PROFILE_START()\ +do {\ + volatile uint32_t perf_1 = read_ccnt();\ + volatile uint32_t perf_2 + + +#define PROFILE_ACC(x)\ + perf_2 = read_ccnt();\ + {\ + const uint32_t duration = perf_2 - perf_1;\ + if (duration < RPI_##x##_MAX_DURATION)\ + {\ + av_rpi_##x##_cycles += duration;\ + av_rpi_##x##_cnt += 1;\ + }\ + }\ +} while(0) + + +#define PROFILE_ACC_N(n)\ + if ((n) >= 0) {\ + perf_2 = read_ccnt();\ + {\ + const uint32_t duration = perf_2 - perf_1;\ + if (duration < RPI_prof_n_MAX_DURATION)\ + {\ + av_rpi_prof_n_cycles[n] += duration;\ + av_rpi_prof_n_cnt[n] += 1;\ + }\ + }\ + }\ +} while(0) + +#define PROFILE_PRINTF(x)\ + printf("%-20s cycles=%14" PRIu64 "; cnt=%8u; avg=%5" PRIu64 "\n", #x, av_rpi_##x##_cycles, av_rpi_##x##_cnt,\ + av_rpi_##x##_cnt == 0 ? (uint64_t)0 : av_rpi_##x##_cycles / (uint64_t)av_rpi_##x##_cnt) + +#define PROFILE_PRINTF_N(n)\ + printf("prof[%d] cycles=%14" PRIu64 "; cnt=%8u; avg=%5" PRIu64 "\n", (n), av_rpi_prof_n_cycles[n], av_rpi_prof_n_cnt[n],\ + av_rpi_prof_n_cnt[n] == 0 ? (uint64_t)0 : av_rpi_prof_n_cycles[n] / (uint64_t)av_rpi_prof_n_cnt[n]) + +#define PROFILE_CLEAR_N(n) \ +do {\ + av_rpi_prof_n_cycles[n] = 0;\ + av_rpi_prof_n_cnt[n] = 0;\ +} while(0) + +#else + +// No profile +#define PROFILE_INIT() +#define PROFILE_START() +#define PROFILE_ACC(x) +#define PROFILE_ACC_N(x) +#define PROFILE_PRINTF(x) +#define PROFILE_PRINTF_N(x) +#define PROFILE_CLEAR_N(n) + +#endif + +#endif + --- /dev/null +++ b/modules/hw/mmal/subpic.c @@ -0,0 +1,222 @@ +/***************************************************************************** + * 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 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->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 +} + + +int hw_mmal_subpic_update(vlc_object_t * const p_filter, + picture_t * const p_pic, const unsigned int sub_no, + subpic_reg_stash_t * const spe, + const MMAL_RECT_T * const scale_out, + const uint64_t pts) +{ + MMAL_STATUS_T err; + MMAL_BUFFER_HEADER_T * const sub_buf = hw_mmal_pic_sub_buf_get(p_pic, sub_no); + + if (sub_buf == NULL) + { + if (spe->port->is_enabled && spe->seq != 0) + { + MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(spe->pool->queue); + + 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", sub_no); +#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; + } + + 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); + + 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 = p_pic->format.i_frame_rate_base; + v_fmt->frame_rate.num = p_pic->format.i_frame_rate; + v_fmt->par.den = p_pic->format.i_sar_den; + v_fmt->par.num = p_pic->format.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 TRACE_ALL + msg_Dbg(p_filter, "Update region for sub %d", sub_no); +#endif + dreg->layer = spe->layer; + dreg->set |= MMAL_DISPLAY_SET_LAYER; + + 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", sub_no); +#endif + if ((err = port_send_replicated(spe->port, spe->pool, sub_buf, pts)) != MMAL_SUCCESS) + { + msg_Err(p_filter, "Send buffer to subput failed"); + return -1; + } + + spe->seq = seq; + } + } + return 1; +} + + + --- /dev/null +++ b/modules/hw/mmal/subpic.h @@ -0,0 +1,28 @@ +#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; + 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, + picture_t * const p_pic, const unsigned int sub_no, + subpic_reg_stash_t * const stash, + const MMAL_RECT_T * const scale_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); + +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 unsigned int layer); + +#endif + --- /dev/null +++ b/modules/hw/mmal/v7_pmu.S @@ -0,0 +1,263 @@ +/*------------------------------------------------------------ +Performance Monitor Block +------------------------------------------------------------*/ + .arm @ Make sure we are in ARM mode. + .text + .align 2 + .global getPMN @ export this function for the linker + +/* Returns the number of progammable counters uint32_t getPMN(void) */ + +getPMN: + MRC p15, 0, r0, c9, c12, 0 /* Read PMNC Register */ + MOV r0, r0, LSR #11 /* Shift N field down to bit 0 */ + AND r0, r0, #0x1F /* Mask to leave just the 5 N bits */ + BX lr + + + + .global pmn_config @ export this function for the linker + /* Sets the event for a programmable counter to record */ + /* void pmn_config(unsigned counter, uint32_t event) */ + /* counter = r0 = Which counter to program (e.g. 0 for PMN0, 1 for PMN1 */ + /* event = r1 = The event code */ +pmn_config: + AND r0, r0, #0x1F /* Mask to leave only bits 4:0 */ + MCR p15, 0, r0, c9, c12, 5 /* Write PMNXSEL Register */ + MCR p15, 0, r1, c9, c13, 1 /* Write EVTSELx Register */ + BX lr + + + + .global ccnt_divider @ export this function for the linker + /* Enables/disables the divider (1/64) on CCNT */ + /* void ccnt_divider(int divider) */ + /* divider = r0 = If 0 disable divider, else enable dvider */ +ccnt_divider: + MRC p15, 0, r1, c9, c12, 0 /* Read PMNC */ + + CMP r0, #0x0 /* IF (r0 == 0) */ + BICEQ r1, r1, #0x08 /* THEN: Clear the D bit (disables the */ + ORRNE r1, r1, #0x08 /* ELSE: Set the D bit (enables the di */ + + MCR p15, 0, r1, c9, c12, 0 /* Write PMNC */ + BX lr + + + /* --------------------------------------------------------------- */ + /* Enable/Disable */ + /* --------------------------------------------------------------- */ + + .global enable_pmu @ export this function for the linker + /* Global PMU enable */ + /* void enable_pmu(void) */ +enable_pmu: + MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ + ORR r0, r0, #0x01 /* Set E bit */ + MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ + BX lr + + + + .global disable_pmu @ export this function for the linker + /* Global PMU disable */ + /* void disable_pmu(void) */ +disable_pmu: + MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ + BIC r0, r0, #0x01 /* Clear E bit */ + MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ + BX lr + + + + .global enable_ccnt @ export this function for the linker + /* Enable the CCNT */ + /* void enable_ccnt(void) */ +enable_ccnt: + MOV r0, #0x80000000 /* Set C bit */ + MCR p15, 0, r0, c9, c12, 1 /* Write CNTENS Register */ + BX lr + + + + .global disable_ccnt @ export this function for the linker + /* Disable the CCNT */ + /* void disable_ccnt(void) */ +disable_ccnt: + MOV r0, #0x80000000 /* Clear C bit */ + MCR p15, 0, r0, c9, c12, 2 /* Write CNTENC Register */ + BX lr + + + + .global enable_pmn @ export this function for the linker + /* Enable PMN{n} */ + /* void enable_pmn(uint32_t counter) */ + /* counter = r0 = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) +enable_pmn: */ + MOV r1, #0x1 /* Use arg (r0) to set which counter t */ + MOV r1, r1, LSL r0 + + MCR p15, 0, r1, c9, c12, 1 /* Write CNTENS Register */ + BX lr + + + + .global disable_pmn @ export this function for the linker + /* Enable PMN{n} */ + /* void disable_pmn(uint32_t counter) */ + /* counter = r0 = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) +disable_pmn: */ + MOV r1, #0x1 /* Use arg (r0) to set which counter t */ + MOV r1, r1, LSL r0 + + MCR p15, 0, r1, c9, c12, 1 /* Write CNTENS Register */ + BX lr + + + + .global enable_pmu_user_access @ export this function for the linker + /* Enables User mode access to the PMU (must be called in a priviledge */ + /* void enable_pmu_user_access(void) */ +enable_pmu_user_access: + MRC p15, 0, r0, c9, c14, 0 /* Read PMUSERENR Register */ + ORR r0, r0, #0x01 /* Set EN bit (bit 0) */ + MCR p15, 0, r0, c9, c14, 0 /* Write PMUSERENR Register */ + BX lr + + + + .global disable_pmu_user_access @ export this function for the linke + /* Disables User mode access to the PMU (must be called in a priviledg */ + /* void disable_pmu_user_access(void) */ +disable_pmu_user_access: + MRC p15, 0, r0, c9, c14, 0 /* Read PMUSERENR Register */ + BIC r0, r0, #0x01 /* Clear EN bit (bit 0) */ + MCR p15, 0, r0, c9, c14, 0 /* Write PMUSERENR Register */ + BX lr + + + /* --------------------------------------------------------------- */ + /* Counter read registers */ + /* --------------------------------------------------------------- */ + + .global read_ccnt @ export this function for the linker + /* Returns the value of CCNT */ + /* uint32_t read_ccnt(void) */ +read_ccnt: + MRC p15, 0, r0, c9, c13, 0 /* Read CCNT Register */ + BX lr + + + .global read_pmn @ export this function for the linker + /* Returns the value of PMN{n} */ + /* uint32_t read_pmn(uint32_t counter) */ + /* counter = r0 = The counter to read (e.g. 0 for PMN0, 1 for PMN1) * +read_pmn: */ + AND r0, r0, #0x1F /* Mask to leave only bits 4:0 */ + MCR p15, 0, r0, c9, c12, 5 /* Write PMNXSEL Register */ + MRC p15, 0, r0, c9, c13, 2 /* Read current PMNx Register */ + BX lr + + + /* --------------------------------------------------------------- */ + /* Software Increment */ + /* --------------------------------------------------------------- */ + + .global pmu_software_increment @ export this function for the linker + /* Writes to software increment register */ + /* void pmu_software_increment(uint32_t counter) */ + /* counter = r0 = The counter to increment (e.g. 0 for PMN0, 1 for PMN +pmu_software_increment: */ + MOV r1, #0x01 + MOV r1, r1, LSL r0 + MCR p15, 0, r1, c9, c12, 4 /* Write SWINCR Register */ + BX lr + + /* --------------------------------------------------------------- */ + /* Overflow & Interrupt Generation */ + /* --------------------------------------------------------------- */ + + .global read_flags @ export this function for the linker + /* Returns the value of the overflow flags */ + /* uint32_t read_flags(void) */ +read_flags: + MRC p15, 0, r0, c9, c12, 3 /* Read FLAG Register */ + BX lr + + + .global write_flags @ export this function for the linker + /* Writes the overflow flags */ + /* void write_flags(uint32_t flags) */ +write_flags: + MCR p15, 0, r0, c9, c12, 3 /* Write FLAG Register */ + BX lr + + + .global enable_ccnt_irq @ export this function for the linker + /* Enables interrupt generation on overflow of the CCNT */ + /* void enable_ccnt_irq(void) */ +enable_ccnt_irq: + MOV r0, #0x80000000 + MCR p15, 0, r0, c9, c14, 1 /* Write INTENS Register */ + BX lr + + .global disable_ccnt_irq @ export this function for the linker + /* Disables interrupt generation on overflow of the CCNT */ + /* void disable_ccnt_irq(void) */ +disable_ccnt_irq: + MOV r0, #0x80000000 + MCR p15, 0, r0, c9, c14, 2 /* Write INTENC Register */ + BX lr + + + .global enable_pmn_irq @ export this function for the linker + /* Enables interrupt generation on overflow of PMN{x} */ + /* void enable_pmn_irq(uint32_t counter) */ + /* counter = r0 = The counter to enable the interrupt for (e.g. 0 for +enable_pmn_irq: */ + MOV r1, #0x1 /* Use arg (r0) to set which counter */ + MOV r0, r1, LSL r0 + MCR p15, 0, r0, c9, c14, 1 /* Write INTENS Register */ + BX lr + + .global disable_pmn_irq @ export this function for the linker + /* Disables interrupt generation on overflow of PMN{x} */ + /* void disable_pmn_irq(uint32_t counter) */ + /* counter = r0 = The counter to disable the interrupt for (e.g. 0 fo +disable_pmn_irq: */ + MOV r1, #0x1 /* Use arg (r0) to set which counter t */ + MOV r0, r1, LSL r0 + MCR p15, 0, r0, c9, c14, 2 /* Write INTENC Register */ + BX lr + + /* --------------------------------------------------------------- */ + /* Reset Functions */ + /* --------------------------------------------------------------- */ + + .global reset_pmn @ export this function for the linker + /* Resets the programmable counters */ + /* void reset_pmn(void) */ +reset_pmn: + MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ + ORR r0, r0, #0x02 /* Set P bit (Event Counter Reset) */ + MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ + BX lr + + + .global reset_ccnt @ export this function for the linker + /* Resets the CCNT */ + /* void reset_ccnt(void) */ +reset_ccnt: + MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */ + ORR r0, r0, #0x04 /* Set C bit (Event Counter Reset) */ + MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */ + BX lr + + + .end @end of code, this line is optional. +/* ------------------------------------------------------------ */ +/* End of v7_pmu.s */ +/* ------------------------------------------------------------ */ + + --- /dev/null +++ b/modules/hw/mmal/v7_pmu.h @@ -0,0 +1,113 @@ +// ------------------------------------------------------------ +// PMU for Cortex-A/R (v7-A/R) +// ------------------------------------------------------------ + +#ifndef _V7_PMU_H +#define _V7_PMU_H + +// Returns the number of progammable counters +unsigned int getPMN(void); + +// Sets the event for a programmable counter to record +// counter = r0 = Which counter to program (e.g. 0 for PMN0, 1 for PMN1) +// event = r1 = The event code (from appropiate TRM or ARM Architecture Reference Manual) +void pmn_config(unsigned int counter, unsigned int event); + +// Enables/disables the divider (1/64) on CCNT +// divider = r0 = If 0 disable divider, else enable dvider +void ccnt_divider(int divider); + +// +// Enables and disables +// + +// Global PMU enable +// On ARM11 this enables the PMU, and the counters start immediately +// On Cortex this enables the PMU, there are individual enables for the counters +void enable_pmu(void); + +// Global PMU disable +// On Cortex, this overrides the enable state of the individual counters +void disable_pmu(void); + +// Enable the CCNT +void enable_ccnt(void); + +// Disable the CCNT +void disable_ccnt(void); + +// Enable PMN{n} +// counter = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) +void enable_pmn(unsigned int counter); + +// Enable PMN{n} +// counter = The counter to enable (e.g. 0 for PMN0, 1 for PMN1) +void disable_pmn(unsigned int counter); + +// +// Read counter values +// + +// Returns the value of CCNT +unsigned int read_ccnt(void); + +// Returns the value of PMN{n} +// counter = The counter to read (e.g. 0 for PMN0, 1 for PMN1) +unsigned int read_pmn(unsigned int counter); + +// +// Overflow and interrupts +// + +// Returns the value of the overflow flags +unsigned int read_flags(void); + +// Writes the overflow flags +void write_flags(unsigned int flags); + +// Enables interrupt generation on overflow of the CCNT +void enable_ccnt_irq(void); + +// Disables interrupt generation on overflow of the CCNT +void disable_ccnt_irq(void); + +// Enables interrupt generation on overflow of PMN{x} +// counter = The counter to enable the interrupt for (e.g. 0 for PMN0, 1 for PMN1) +void enable_pmn_irq(unsigned int counter); + +// Disables interrupt generation on overflow of PMN{x} +// counter = r0 = The counter to disable the interrupt for (e.g. 0 for PMN0, 1 for PMN1) +void disable_pmn_irq(unsigned int counter); + +// +// Counter reset functions +// + +// Resets the programmable counters +void reset_pmn(void); + +// Resets the CCNT +void reset_ccnt(void); + +// +// Software Increment + +// Writes to software increment register +// counter = The counter to increment (e.g. 0 for PMN0, 1 for PMN1) +void pmu_software_increment(unsigned int counter); + +// +// User mode access +// + +// Enables User mode access to the PMU (must be called in a priviledged mode) +void enable_pmu_user_access(void); + +// Disables User mode access to the PMU (must be called in a priviledged mode) +void disable_pmu_user_access(void); + +#endif +// ------------------------------------------------------------ +// End of v7_pmu.h +// ------------------------------------------------------------ + --- a/modules/hw/mmal/vout.c +++ b/modules/hw/mmal/vout.c @@ -27,21 +27,24 @@ #endif #include +#include #include -#include #include #include #include +#include #include "mmal_picture.h" +#include "subpic.h" #include #include #include #include #include -#include + +#define TRACE_ALL 0 #define MAX_BUFFERS_IN_TRANSIT 1 #define VC_TV_MAX_MODE_IDS 127 @@ -50,11 +53,6 @@ #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_ADJUST_REFRESHRATE_NAME "mmal-adjust-refreshrate" #define MMAL_ADJUST_REFRESHRATE_TEXT N_("Adjust HDMI refresh rate to the video.") #define MMAL_ADJUST_REFRESHRATE_LONGTEXT N_("Adjust HDMI refresh rate to the video.") @@ -68,64 +66,30 @@ #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; - 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 i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */ - int i_frame_rate; + unsigned int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */ + unsigned int i_frame_rate; int next_phase_check; /* lowpass for phase check frequency */ int phase_offset; /* currently applied offset to presentation time in ns */ @@ -136,264 +100,415 @@ bool native_interlaced; bool b_top_field_first; /* cached interlaced settings to detect changes for native mode */ bool b_progressive; - bool opaque; /* indicated use of opaque picture format (zerocopy) */ -}; + bool force_config; -static const vlc_fourcc_t subpicture_chromas[] = { - VLC_CODEC_RGBA, - 0 + vout_subpic_t subs[SUBS_MAX]; + + picture_pool_t * pic_pool; + + struct vout_isp_conf_s { + MMAL_COMPONENT_T *component; + MMAL_PORT_T * input; + MMAL_PORT_T * output; + MMAL_QUEUE_T * out_q; + MMAL_POOL_T * in_pool; + MMAL_POOL_T * out_pool; + bool pending; + } isp; }; -/* Utility functions */ -static inline uint32_t align(uint32_t x, uint32_t y); -static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg, - const video_format_t *fmt); -/* VLC vout display callbacks */ -static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count); -static void vd_prepare(vout_display_t *vd, picture_t *picture, - subpicture_t *subpicture); -static void vd_display(vout_display_t *vd, picture_t *picture, - subpicture_t *subpicture); -static int vd_control(vout_display_t *vd, int query, va_list args); -static void vd_manage(vout_display_t *vd); - -/* MMAL callbacks */ -static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer); +// ISP setup -/* 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 inline bool want_isp(const vout_display_t * const vd) +{ + return (vd->fmt.i_chroma == VLC_CODEC_MMAL_ZC_SAND10); +} -/* 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 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; // It will be after we've converted it... + default: + break; + } + return 0; +} -static int Open(vlc_object_t *object) +static void display_set_format(const vout_display_t * const vd, MMAL_ES_FORMAT_T *const es_fmt, const bool is_intermediate) { - vout_display_t *vd = (vout_display_t *)object; - vout_display_sys_t *sys; - uint32_t buffer_pitch, buffer_height; - vout_display_place_t place; - MMAL_DISPLAYREGION_T display_region; - MMAL_STATUS_T status; - int ret = VLC_SUCCESS; - unsigned i; + 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); +} - if (vout_display_IsWindowed(vd)) - return VLC_EGENERIC; +static void display_src_rect(const vout_display_t * const vd, MMAL_RECT_T *const rect) +{ + const bool wants_isp = want_isp(vd); + rect->x = wants_isp ? 0 : vd->fmt.i_x_offset; + rect->y = wants_isp ? 0 : vd->fmt.i_y_offset; + rect->width = vd->fmt.i_visible_width; + rect->height = vd->fmt.i_visible_height; +} - sys = calloc(1, sizeof(struct vout_display_sys_t)); - if (!sys) - return VLC_ENOMEM; - vd->sys = sys; +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 - sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME); - bcm_host_init(); + mmal_buffer_header_release(buf); - sys->opaque = vd->fmt.i_chroma == VLC_CODEC_MMAL_OPAQUE; +#if TRACE_ALL + msg_Dbg(vd, ">>> %s", __func__); +#endif +} - 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; - } +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; - 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; + if (buffer->cmd == MMAL_EVENT_ERROR) { + status = *(uint32_t *)buffer->data; + msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); } - sys->input = sys->component->input[0]; - sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd; + mmal_buffer_header_release(buffer); +} - if (sys->opaque) { - sys->input->format->encoding = MMAL_ENCODING_OPAQUE; - sys->i_planes = 1; - sys->buffer_size = sys->input->buffer_size_recommended; - } else { - sys->input->format->encoding = MMAL_ENCODING_I420; - vd->fmt.i_chroma = VLC_CODEC_I420; - buffer_pitch = align(vd->fmt.i_width, 32); - buffer_height = align(vd->fmt.i_height, 16); - sys->i_planes = 3; - sys->buffer_size = 3 * buffer_pitch * buffer_height / 2; - } - - sys->input->format->es->video.width = vd->fmt.i_width; - sys->input->format->es->video.height = vd->fmt.i_height; - sys->input->format->es->video.crop.x = 0; - sys->input->format->es->video.crop.y = 0; - sys->input->format->es->video.crop.width = vd->fmt.i_width; - sys->input->format->es->video.crop.height = vd->fmt.i_height; - sys->input->format->es->video.par.num = vd->source.i_sar_num; - sys->input->format->es->video.par.den = vd->source.i_sar_den; +static void isp_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) +{ + if (buf->cmd == 0 && buf->length != 0) + { + // The filter structure etc. should always exist if we have contents + // but might not on later flushes as we shut down + vout_display_t * const vd = (vout_display_t *)port->userdata; + struct vout_isp_conf_s *const isp = &vd->sys->isp; - status = mmal_port_format_commit(sys->input); - if (status != MMAL_SUCCESS) { - msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)", - sys->input->name, status, mmal_status_to_string(status)); - ret = VLC_EGENERIC; - goto out; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s: cmd=%d; flags=%#x, pts=%lld", __func__, buf->cmd, buf->flags, (long long) buf->pts); +#endif + mmal_queue_put(isp->out_q, buf); +#if TRACE_ALL + msg_Dbg(vd, ">>> %s: out Q len=%d", __func__, mmal_queue_length(isp->out_q)); +#endif } - sys->input->buffer_size = sys->input->buffer_size_recommended; + else + { + mmal_buffer_header_reset(buf); + mmal_buffer_header_release(buf); + } +} - vout_display_PlacePicture(&place, &vd->source, vd->cfg, false); - display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION; - display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T); - display_region.fullscreen = MMAL_FALSE; - display_region.src_rect.x = vd->fmt.i_x_offset; - display_region.src_rect.y = vd->fmt.i_y_offset; - display_region.src_rect.width = vd->fmt.i_visible_width; - display_region.src_rect.height = vd->fmt.i_visible_height; - display_region.dest_rect.x = place.x; - display_region.dest_rect.y = place.y; - display_region.dest_rect.width = place.width; - display_region.dest_rect.height = place.height; - display_region.layer = sys->layer; - display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT | - MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER; - status = mmal_port_parameter_set(sys->input, &display_region.hdr); - if (status != MMAL_SUCCESS) { - msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)", - status, mmal_status_to_string(status)); - ret = VLC_EGENERIC; - goto out; +static void isp_empty_out_q(struct vout_isp_conf_s * const isp) +{ + MMAL_BUFFER_HEADER_T * buf; + // We can be called as part of error recovery so allow for missing Q + if (isp->out_q == NULL) + return; + + while ((buf = mmal_queue_get(isp->out_q)) != NULL) + mmal_buffer_header_release(buf); +} + +static void isp_flush(struct vout_isp_conf_s * const isp) +{ + if (!isp->input->is_enabled) + mmal_port_disable(isp->input); + + if (isp->output->is_enabled) + mmal_port_disable(isp->output); + + isp_empty_out_q(isp); + isp->pending = false; +} + +static MMAL_STATUS_T isp_prepare(vout_display_t * const vd, struct vout_isp_conf_s * const isp) +{ + MMAL_STATUS_T err; + MMAL_BUFFER_HEADER_T * buf; + + if (!isp->output->is_enabled) { + if ((err = mmal_port_enable(isp->output, isp_output_cb)) != MMAL_SUCCESS) + { + msg_Err(vd, "ISP output port enable failed"); + return err; + } } - for (i = 0; i < sys->i_planes; ++i) { - sys->planes[i].i_lines = buffer_height; - sys->planes[i].i_pitch = buffer_pitch; - sys->planes[i].i_visible_lines = vd->fmt.i_visible_height; - sys->planes[i].i_visible_pitch = vd->fmt.i_visible_width; + while ((buf = mmal_queue_get(isp->out_pool->queue)) != NULL) { + if ((err = mmal_port_send_buffer(isp->output, buf)) != MMAL_SUCCESS) + { + msg_Err(vd, "ISP output port stuff failed"); + return err; + } + } - if (i > 0) { - sys->planes[i].i_lines /= 2; - sys->planes[i].i_pitch /= 2; - sys->planes[i].i_visible_lines /= 2; - sys->planes[i].i_visible_pitch /= 2; + if (!isp->input->is_enabled) { + if ((err = mmal_port_enable(isp->input, isp_input_cb)) != MMAL_SUCCESS) + { + msg_Err(vd, "ISP input port enable failed"); + return err; } } + return MMAL_SUCCESS; +} - vlc_mutex_init(&sys->buffer_mutex); - vlc_cond_init(&sys->buffer_cond); - vlc_mutex_init(&sys->manage_mutex); +static void isp_close(vout_display_t * const vd, vout_display_sys_t * const vd_sys) +{ + struct vout_isp_conf_s * const isp = &vd_sys->isp; + VLC_UNUSED(vd); - vd->pool = vd_pool; - vd->prepare = vd_prepare; - vd->display = vd_display; - vd->control = vd_control; - vd->manage = vd_manage; + if (isp->component == NULL) + return; - vc_tv_register_callback(tvservice_cb, vd); + isp_flush(isp); - if (query_resolution(vd, &sys->display_width, &sys->display_height) >= 0) { - vout_display_SendEventDisplaySize(vd, sys->display_width, sys->display_height); - } else { - sys->display_width = vd->cfg->display.width; - sys->display_height = vd->cfg->display.height; + if (isp->component->control->is_enabled) + mmal_port_disable(isp->component->control); + + if (isp->out_q != NULL) { + // 1st junk anything lying around + isp_empty_out_q(isp); + + mmal_queue_destroy(isp->out_q); + isp->out_q = NULL; } - sys->dmx_handle = vc_dispmanx_display_open(0); - vd->info.subpicture_chromas = subpicture_chromas; + if (isp->out_pool != NULL) { + mmal_port_pool_destroy(isp->output, isp->out_pool); + isp->out_pool = NULL; + } - vout_display_DeleteWindow(vd, NULL); + isp->input = NULL; + isp->output = NULL; -out: - if (ret != VLC_SUCCESS) - Close(object); + mmal_component_release(isp->component); + isp->component = NULL; - return ret; + return; } -static void Close(vlc_object_t *object) +// Restuff into output rather than return to pool is we can +static MMAL_BOOL_T isp_out_pool_cb(MMAL_POOL_T *pool, MMAL_BUFFER_HEADER_T *buffer, void *userdata) { - vout_display_t *vd = (vout_display_t *)object; - vout_display_sys_t *sys = vd->sys; - char response[20]; /* answer is hvs_update_fields=%1d */ - unsigned i; + struct vout_isp_conf_s * const isp = userdata; + VLC_UNUSED(pool); + if (isp->output->is_enabled) { + mmal_buffer_header_reset(buffer); + if (mmal_port_send_buffer(isp->output, buffer) == MMAL_SUCCESS) + return MMAL_FALSE; + } + return MMAL_TRUE; +} - vc_tv_unregister_callback_full(tvservice_cb, vd); +static MMAL_STATUS_T isp_setup(vout_display_t * const vd, vout_display_sys_t * const vd_sys) +{ + struct vout_isp_conf_s * const isp = &vd_sys->isp; + MMAL_STATUS_T err; - if (sys->dmx_handle) - close_dmx(vd); + if ((err = mmal_component_create(MMAL_COMPONENT_ISP_RESIZER, &isp->component)) != MMAL_SUCCESS) { + msg_Err(vd, "Cannot create ISP component"); + return err; + } + isp->input = isp->component->input[0]; + isp->output = isp->component->output[0]; - if (sys->component && sys->component->control->is_enabled) - mmal_port_disable(sys->component->control); + isp->component->control->userdata = (void *)vd; + if ((err = mmal_port_enable(isp->component->control, isp_control_port_cb)) != MMAL_SUCCESS) { + msg_Err(vd, "Failed to enable ISP control port"); + goto fail; + } - if (sys->input && sys->input->is_enabled) - mmal_port_disable(sys->input); + isp->input->userdata = (void *)vd; + display_set_format(vd, isp->input->format, false); - if (sys->component && sys->component->is_enabled) - mmal_component_disable(sys->component); + if ((err = port_parameter_set_bool(isp->input, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) + goto fail; - if (sys->pool) - mmal_port_pool_destroy(sys->input, sys->pool); + if ((err = mmal_port_format_commit(isp->input)) != MMAL_SUCCESS) { + msg_Err(vd, "Failed to set ISP input format"); + goto fail; + } - if (sys->component) - mmal_component_release(sys->component); + isp->input->buffer_size = isp->input->buffer_size_recommended; + isp->input->buffer_num = 30; - if (sys->picture_pool) - picture_pool_Release(sys->picture_pool); - else - for (i = 0; i < sys->num_buffers; ++i) - if (sys->pictures[i]) { - mmal_buffer_header_release(sys->pictures[i]->p_sys->buffer); - picture_Release(sys->pictures[i]); - } + if ((isp->in_pool = mmal_pool_create(isp->input->buffer_num, 0)) == NULL) + { + msg_Err(vd, "Failed to create input pool"); + goto fail; + } - vlc_mutex_destroy(&sys->buffer_mutex); - vlc_cond_destroy(&sys->buffer_cond); - vlc_mutex_destroy(&sys->manage_mutex); + if ((isp->out_q = mmal_queue_create()) == NULL) + { + err = MMAL_ENOMEM; + goto fail; + } - if (sys->native_interlaced) { - if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 || - response[18] != '0') - msg_Warn(vd, "Could not reset hvs field mode"); + display_set_format(vd, isp->output->format, true); + + if ((err = port_parameter_set_bool(isp->output, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS) + goto fail; + + if ((err = mmal_port_format_commit(isp->output)) != MMAL_SUCCESS) { + msg_Err(vd, "Failed to set ISP input format"); + goto fail; } - free(sys->pictures); - free(sys); + isp->output->buffer_size = isp->output->buffer_size_recommended; + isp->output->buffer_num = 2; + isp->output->userdata = (void *)vd; - bcm_host_deinit(); + 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 inline uint32_t align(uint32_t x, uint32_t y) { - uint32_t mod = x % y; - if (mod == 0) - return x; +static MMAL_STATUS_T isp_check(vout_display_t * const vd, vout_display_sys_t * const vd_sys) +{ + struct vout_isp_conf_s *const isp = &vd_sys->isp; + const bool has_isp = (isp->component != NULL); + const bool wants_isp = want_isp(vd); + + if (has_isp == wants_isp) + { + // All OK - do nothing + } + else if (has_isp) + { + // ISP active but we don't want it + isp_flush(isp); + + // Check we have everything back and then kill it + if (mmal_queue_length(isp->out_pool->queue) == isp->output->buffer_num) + isp_close(vd, vd_sys); + } else - return x + y - mod; + { + // ISP closed but we want it + return isp_setup(vd, vd_sys); + } + + return MMAL_SUCCESS; +} + +/* TV service */ +static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, + uint32_t param2); +static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt); +static int set_latency_target(vout_display_t *vd, bool enable); + +// Mmal +static void maintain_phase_sync(vout_display_t *vd); + + + +static void vd_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) +{ +#if TRACE_ALL + vout_display_t * const vd = (vout_display_t *)port->userdata; + pic_ctx_mmal_t * ctx = buf->user_data; + msg_Dbg(vd, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buf->cmd, ctx, buf, + buf->flags, (long long)buf->pts); +#else + VLC_UNUSED(port); +#endif + + mmal_buffer_header_release(buf); + +#if TRACE_ALL + msg_Dbg(vd, ">>> %s", __func__); +#endif +} + +static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height) +{ + TV_DISPLAY_STATE_T display_state; + int ret = 0; + + if (vc_tv_get_display_state(&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 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_sys_t * const sys = vd->sys; vout_display_place_t place; MMAL_DISPLAYREGION_T display_region; 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,22 +527,29 @@ if (!cfg) cfg = vd->cfg; - vout_display_PlacePicture(&place, fmt, cfg, false); + { + // Ignore what VLC thinks might be going on with display size + vout_display_cfg_t tcfg = *cfg; + tcfg.display.width = sys->display_width; + tcfg.display.height = sys->display_height; + tcfg.is_display_filled = true; + vout_display_PlacePicture(&place, fmt, &tcfg, false); + + msg_Dbg(vd, "%dx%d -> %dx%d @ %d,%d", tcfg.display.width, tcfg.display.height, place.width, place.height, place.x, place.y); + } 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_src_rect(vd, &display_region.src_rect); 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.alpha = 0xff | (1 << 29); display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT | - MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER; + MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_ALPHA; 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)", @@ -435,7 +557,6 @@ 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,191 +567,130 @@ 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 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; -#ifndef NDEBUG - msg_Dbg(vd, "Creating picture pool with %u pictures", count); +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); #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; - } - - 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; + // Not expecting subpictures in the current setup + // Subpics should be attached to the main pic + if (subpicture != NULL) { + subpicture_Delete(subpicture); } - 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; + 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, &p_pic->format); + } + + 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->isp.pending) { + MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(sys->isp.out_q); + sys->isp.pending = false; +#if TRACE_ALL + msg_Dbg(vd, "--- %s: ISP stuff", __func__); +#endif + if (mmal_port_send_buffer(sys->input, buf) != MMAL_SUCCESS) + { + mmal_buffer_header_release(buf); + msg_Err(vd, "Send ISP buffer to render input failed"); + goto fail; + } } - - memset(&picture_res, 0, sizeof(picture_resource_t)); - sys->pictures = calloc(sys->num_buffers, sizeof(picture_t *)); - for (i = 0; i < sys->num_buffers; ++i) { - picture_res.p_sys = calloc(1, sizeof(picture_sys_t)); - picture_res.p_sys->owner = (vlc_object_t *)vd; - picture_res.p_sys->buffer = mmal_queue_get(sys->pool->queue); - - sys->pictures[i] = picture_NewFromResource(&fmt, &picture_res); - if (!sys->pictures[i]) { - msg_Err(vd, "Failed to create picture"); - free(picture_res.p_sys); - goto out; + else + { + MMAL_BUFFER_HEADER_T * const pic_buf = pic_mmal_buffer(p_pic); +#if TRACE_ALL + msg_Dbg(vd, "--- %s: Buf stuff", __func__); +#endif + if ((err = port_send_replicated(sys->input, sys->pool, pic_buf, pic_buf->pts)) != MMAL_SUCCESS) + { + msg_Err(vd, "Send buffer to input failed"); + goto fail; } - - sys->pictures[i]->i_planes = sys->i_planes; - memcpy(sys->pictures[i]->p, sys->planes, sys->i_planes * sizeof(plane_t)); } - memset(&picture_pool_cfg, 0, sizeof(picture_pool_configuration_t)); - picture_pool_cfg.picture_count = sys->num_buffers; - picture_pool_cfg.picture = sys->pictures; - picture_pool_cfg.lock = mmal_picture_lock; - - sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg); - if (!sys->picture_pool) { - msg_Err(vd, "Failed to create picture pool"); - goto out; + if (p_pic->context == NULL) { + msg_Dbg(vd, "%s: No context", __func__); } + else + { + unsigned int sub_no = 0; -out: - return sys->picture_pool; -} - -static void vd_prepare(vout_display_t *vd, picture_t *picture, - subpicture_t *subpicture) -{ - vout_display_sys_t *sys = vd->sys; - picture_sys_t *pic_sys = picture->p_sys; - - if (!sys->adjust_refresh_rate || pic_sys->displayed) - return; - - /* Apply the required phase_offset to the picture, so that vd_display() - * will be called at the corrected time from the core */ - picture->date += sys->phase_offset; -} - -static void vd_display(vout_display_t *vd, picture_t *picture, - subpicture_t *subpicture) -{ - vout_display_sys_t *sys = vd->sys; - picture_sys_t *pic_sys = picture->p_sys; - MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer; - MMAL_STATUS_T status; - - if (picture->format.i_frame_rate != sys->i_frame_rate || - picture->format.i_frame_rate_base != sys->i_frame_rate_base || - picture->b_progressive != sys->b_progressive || - picture->b_top_field_first != sys->b_top_field_first) { - sys->b_top_field_first = picture->b_top_field_first; - sys->b_progressive = picture->b_progressive; - sys->i_frame_rate = picture->format.i_frame_rate; - sys->i_frame_rate_base = picture->format.i_frame_rate_base; - configure_display(vd, NULL, &picture->format); - } - - if (!pic_sys->displayed || !sys->opaque) { - buffer->cmd = 0; - buffer->length = sys->input->buffer_size; - buffer->user_data = picture; - - status = mmal_port_send_buffer(sys->input, buffer); - if (status == MMAL_SUCCESS) - atomic_fetch_add(&sys->buffers_in_transit, 1); - - if (status != MMAL_SUCCESS) { - msg_Err(vd, "Failed to send buffer to input port. Frame dropped"); - picture_Release(picture); + for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) { + int rv; + if ((rv = hw_mmal_subpic_update(VLC_OBJECT(vd), p_pic, sub_no, &sys->subs[sub_no].sub, + &(MMAL_RECT_T){.width = sys->display_width, .height = sys->display_height}, + p_pic->date)) == 0) + break; + else if (rv < 0) + goto fail; } - - pic_sys->displayed = true; - } else { - picture_Release(picture); } - display_subpicture(vd, subpicture); - - 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); - } +fail: + /* NOP */; } 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; - } + // Ignore this - we just use full screen anyway + ret = VLC_SUCCESS; break; case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: @@ -640,10 +700,37 @@ break; case VOUT_DISPLAY_RESET_PICTURES: - vlc_assert_unreachable(); + msg_Warn(vd, "Reset Pictures"); + kill_pool(sys); + vd->fmt = vd->source; // Take whatever source wants to give us + ret = VLC_SUCCESS; + break; + case VOUT_DISPLAY_CHANGE_ZOOM: msg_Warn(vd, "Unsupported control query %d", query); + 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); @@ -661,13 +748,11 @@ vlc_mutex_lock(&sys->manage_mutex); if (sys->need_configure_display) { - close_dmx(vd); - sys->dmx_handle = vc_dispmanx_display_open(0); - if (query_resolution(vd, &width, &height) >= 0) { sys->display_width = width; sys->display_height = height; - vout_display_SendEventDisplaySize(vd, width, height); +// msg_Dbg(vd, "%s: %dx%d", __func__, width, height); +// vout_window_ReportSize(vd->cfg->window, width, height); } sys->need_configure_display = false; @@ -676,56 +761,76 @@ vlc_mutex_unlock(&sys->manage_mutex); } -static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +static void vd_prepare(vout_display_t *vd, picture_t *p_pic, +#if VLC_VER_3 + subpicture_t *subpicture +#else + subpicture_t *subpicture, vlc_tick_t date +#endif + ) { - vout_display_t *vd = (vout_display_t *)port->userdata; - MMAL_STATUS_T status; + MMAL_STATUS_T err; + vout_display_sys_t * const sys = vd->sys; - if (buffer->cmd == MMAL_EVENT_ERROR) { - status = *(uint32_t *)buffer->data; - msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); + VLC_UNUSED(subpicture); +// VLC_UNUSED(date); + + vd_manage(vd); + + if (isp_check(vd, sys) != MMAL_SUCCESS) { + return; } - mmal_buffer_header_release(buffer); -} + 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; -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - vout_display_t *vd = (vout_display_t *)port->userdata; + // Stuff output + if (isp_prepare(vd, isp) != MMAL_SUCCESS) + return; + + buf = pic_mmal_buffer(p_pic); + if ((err = port_send_replicated(isp->input, isp->in_pool, + buf, buf->pts)) != MMAL_SUCCESS) + { + msg_Err(vd, "Send buffer to input failed"); + return; + } + + isp->pending = true; + } + +#if 0 + VLC_UNUSED(date); vout_display_sys_t *sys = vd->sys; - picture_t *picture = (picture_t *)buffer->user_data; + picture_sys_t *pic_sys = picture->p_sys; - if (picture) - picture_Release(picture); + if (!sys->adjust_refresh_rate || pic_sys->displayed) + return; - vlc_mutex_lock(&sys->buffer_mutex); - atomic_fetch_sub(&sys->buffers_in_transit, 1); - vlc_cond_signal(&sys->buffer_cond); - vlc_mutex_unlock(&sys->buffer_mutex); + /* Apply the required phase_offset to the picture, so that vd_display() + * will be called at the corrected time from the core */ + picture->date += sys->phase_offset; +#endif } -static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height) + +static void vd_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) { - TV_DISPLAY_STATE_T display_state; - int ret = 0; + vout_display_t *vd = (vout_display_t *)port->userdata; + MMAL_STATUS_T status; - if (vc_tv_get_display_state(&display_state) == 0) { - if (display_state.state & 0xFF) { - *width = display_state.display.hdmi.width; - *height = display_state.display.hdmi.height; - } else if (display_state.state & 0xFF00) { - *width = display_state.display.sdtv.width; - *height = display_state.display.sdtv.height; - } else { - msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state); - ret = -1; - } - } else { - msg_Warn(vd, "Failed to query display resolution"); - ret = -1; + if (buffer->cmd == MMAL_EVENT_ERROR) { + status = *(uint32_t *)buffer->data; + msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status)); } - return ret; + mmal_buffer_header_release(buffer); } static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, uint32_t param2) @@ -828,148 +933,12 @@ } } -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 +981,260 @@ } } -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); + + if (sys->component && sys->component->control->is_enabled) + mmal_port_disable(sys->component->control); + + { + unsigned int i; + for (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->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); + + 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"); + } + + free(sys); + + bcm_host_deinit(); + +#if TRACE_ALL + msg_Dbg(vd, ">>> %s", __func__); +#endif } + +static int OpenMmalVout(vlc_object_t *object) +{ + vout_display_t *vd = (vout_display_t *)object; + vout_display_sys_t *sys; + vout_display_place_t place; + MMAL_DISPLAYREGION_T display_region; + MMAL_STATUS_T status; + int ret = VLC_EGENERIC; + const MMAL_FOURCC_T enc_in = vout_vlc_to_mmal_pic_fourcc(vd->fmt.i_chroma); + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); +#endif + if (enc_in == 0) + { +#if TRACE_ALL + msg_Dbg(vd, ">>> %s: Format not MMAL", __func__); +#endif + return VLC_EGENERIC; + } + + sys = calloc(1, sizeof(struct vout_display_sys_t)); + if (!sys) + return VLC_ENOMEM; + vd->sys = sys; + + bcm_host_init(); + + sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME); + + 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; + sys->buffer_size = sys->input->buffer_size_recommended; + + 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; + sys->input->buffer_num = 30; + + 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_src_rect(vd, &display_region.src_rect); + 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)); + goto fail; + } + + 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; + } + + { + unsigned int i; + for (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->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; + } + } + } + + + vlc_mutex_init(&sys->manage_mutex); + + vd->info = (vout_display_info_t){ + .is_slow = false, + .has_double_click = false, + .needs_hide_mouse = false, + .has_pictures_invalid = true, + .subpicture_chromas = NULL + }; + + vd->pool = vd_pool; + vd->prepare = vd_prepare; + vd->display = vd_display; + vd->control = vd_control; + + vc_tv_register_callback(tvservice_cb, vd); + + if (query_resolution(vd, &sys->display_width, &sys->display_height) < 0) + { + sys->display_width = vd->cfg->display.width; + sys->display_height = vd->cfg->display.height; + } + + 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", 0) + 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) + set_callbacks(OpenMmalVout, CloseMmalVout) + +vlc_module_end() + + --- /dev/null +++ b/modules/hw/mmal/xsplitter.c @@ -0,0 +1,403 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mmal_picture.h" + +#define TRACE_ALL 0 + +typedef struct mmal_x11_sys_s +{ + bool use_mmal; + vout_display_t * cur_vout; + vout_display_t * mmal_vout; + vout_display_t * x_vout; + uint32_t changed; +} mmal_x11_sys_t; + +static const char * str_fourcc(char * buf, unsigned int fcc) +{ + if (fcc == 0) + return "----"; + buf[0] = (fcc >> 0) & 0xff; + buf[1] = (fcc >> 8) & 0xff; + buf[2] = (fcc >> 16) & 0xff; + buf[3] = (fcc >> 24) & 0xff; + buf[4] = 0; + return buf; +} + + +static void unload_display_module(vout_display_t * const x_vout) +{ + if (x_vout != NULL) { + if (x_vout->module != NULL) { + module_unneed(x_vout, x_vout->module); + } + vlc_object_release(x_vout); + } +} + +static void CloseMmalX11(vlc_object_t *object) +{ + vout_display_t * const vd = (vout_display_t *)object; + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + + msg_Dbg(vd, "<<< %s", __func__); + + if (sys == NULL) + return; + + unload_display_module(sys->x_vout); + + unload_display_module(sys->mmal_vout); + + free(sys); + + msg_Dbg(vd, ">>> %s", __func__); +} + +static void mmal_x11_event(vout_display_t * x_vd, int cmd, va_list args) +{ + vout_display_t * const vd = x_vd->owner.sys; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s (cmd=%d)", __func__, cmd); +#endif + 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 vout_display_t * load_display_module(vout_display_t * const vd, + const char * const cap, const char * const module_name) +{ + vout_display_t * const x_vout = vlc_object_create(vd, sizeof(*x_vout)); + + if (!x_vout) + return NULL; + + 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->source = vd->source; + x_vout->info = vd->info; + + x_vout->fmt = vd->fmt; + + if ((x_vout->module = module_need(x_vout, cap, module_name, true)) == NULL) + { + msg_Err(vd, "Failed to open Xsplitter:%s module", module_name); + goto fail; + } + + msg_Dbg(vd, "R/G/B: %08x/%08x/%08x", x_vout->fmt.i_rmask, x_vout->fmt.i_gmask, x_vout->fmt.i_bmask); + + return x_vout; + +fail: + vlc_object_release(x_vout); + return NULL; +} + + +/* Return a pointer over the current picture_pool_t* (mandatory). + * + * For performance reasons, it is best to provide at least count + * pictures but it is not mandatory. + * You can return NULL when you cannot/do not want to allocate + * pictures. + * The vout display module keeps the ownership of the pool and can + * destroy it only when closing or on invalid pictures control. + */ +static picture_pool_t * mmal_x11_pool(vout_display_t * vd, unsigned count) +{ + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + vout_display_t * const x_vd = sys->cur_vout; +#if TRACE_ALL + char buf0[5]; + msg_Dbg(vd, "<<< %s (count=%d) %s:%dx%d->%s:%dx%d", __func__, count, + str_fourcc(buf0, vd->fmt.i_chroma), + vd->fmt.i_width, vd->fmt.i_height, + str_fourcc(buf0, x_vd->fmt.i_chroma), + x_vd->fmt.i_width, x_vd->fmt.i_height); +#endif + picture_pool_t * pool = x_vd->pool(x_vd, count); +#if TRACE_ALL + msg_Dbg(vd, ">>> %s: %p", __func__, pool); +#endif + return pool; +} + +/* Prepare a picture and an optional subpicture for display (optional). + * + * It is called before the next pf_display call to provide as much + * time as possible to prepare the given picture and the subpicture + * for display. + * You are guaranted that pf_display will always be called and using + * the exact same picture_t and subpicture_t. + * You cannot change the pixel content of the picture_t or of the + * subpicture_t. + */ +static void mmal_x11_prepare(vout_display_t * vd, picture_t * pic, subpicture_t * sub) +{ + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + vout_display_t * const x_vd = sys->cur_vout; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); +#endif + if (x_vd->prepare) + x_vd->prepare(x_vd, pic, sub); +} + +/* Display a picture and an optional subpicture (mandatory). + * + * The picture and the optional subpicture must be displayed as soon as + * possible. + * You cannot change the pixel content of the picture_t or of the + * subpicture_t. + * + * This function gives away the ownership of the picture and of the + * subpicture, so you must release them as soon as possible. + */ +static void mmal_x11_display(vout_display_t * vd, picture_t * pic, subpicture_t * sub) +{ + mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys; + vout_display_t * const x_vd = sys->cur_vout; + const bool is_mmal_pic = hw_mmal_pic_is_mmal(pic); + +#if TRACE_ALL + msg_Dbg(vd, "<<< %s: fmt: %dx%d/%dx%d, pic:%dx%d, pts=%lld, mmal=%d/%d", __func__, vd->fmt.i_width, vd->fmt.i_height, x_vd->fmt.i_width, x_vd->fmt.i_height, pic->format.i_width, pic->format.i_height, (long long)pic->date, + is_mmal_pic, sys->use_mmal); +#endif + + if (sys->use_mmal != is_mmal_pic) { + 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(vout_display_t *vd, int query, ...) +{ + va_list args; + int result; + + va_start(args, query); + result = vd->control(vd, query, args); + va_end(args); + + return result; +} + +static bool want_mmal_vout(vout_display_t * vd, const mmal_x11_sys_t * const sys) +{ + return sys->mmal_vout != NULL && var_InheritBool(vd, "fullscreen"); +} + +/* 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; + vout_display_t *x_vd = sys->cur_vout; + int rv; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s[%d] (ctl=%d)", __func__, sys->use_mmal, ctl); +#endif + // Remember what we've told this vd - unwanted ctls ignored on replay + if (ctl >= 0 && ctl <= 31) + sys->changed |= (1 << ctl); + + switch (ctl) { + case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: + { + const vout_display_cfg_t * cfg = va_arg(va, const vout_display_cfg_t *); + const bool want_mmal = want_mmal_vout(vd, sys); + vout_display_t *new_vd = want_mmal ? sys->mmal_vout : sys->x_vout; + + msg_Dbg(vd, "Change size: %d, %d: mmal_vout=%p, want_mmal=%d, fs=%d", + cfg->display.width, cfg->display.height, sys->mmal_vout, want_mmal, + var_InheritBool(vd, "fullscreen")); + + if (sys->use_mmal != want_mmal) { + if (sys->use_mmal) { + vout_display_Control(x_vd, VOUT_DISPLAY_CHANGE_MMAL_HIDE); + } + vout_display_SendEventPicturesInvalid(x_vd); + } + + rv = vout_display_Control(new_vd, ctl, cfg); + if (rv == VLC_SUCCESS) { + vd->fmt = new_vd->fmt; + sys->cur_vout = new_vd; + sys->use_mmal = want_mmal; + } + + // Repeat any control calls that we sent to the previous vd + if (sys->changed != 0) { + const uint32_t changed = sys->changed; + sys->changed = 0; + if ((changed & (1 << VOUT_DISPLAY_CHANGE_DISPLAY_FILLED)) != 0) + vout_display_Control(new_vd, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, vd->cfg); + if ((changed & (1 << VOUT_DISPLAY_CHANGE_ZOOM)) != 0) + vout_display_Control(new_vd, VOUT_DISPLAY_CHANGE_ZOOM, vd->cfg); + if ((changed & ((1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP) | (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT))) != 0) + new_vd->source = vd->source; + if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT)) != 0) + vout_display_Control(new_vd, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT); + if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP)) != 0) + vout_display_Control(new_vd, VOUT_DISPLAY_CHANGE_SOURCE_CROP); + if ((changed & (1 << VOUT_DISPLAY_CHANGE_VIEWPOINT)) != 0) + vout_display_Control(new_vd, VOUT_DISPLAY_CHANGE_ZOOM, vd->cfg); + } + + break; + } + + case VOUT_DISPLAY_RESET_PICTURES: + msg_Dbg(vd, "Reset pictures"); +// rv = x_vd->control(x_vd, ctl, va); + rv = sys->x_vout->control(sys->x_vout, ctl, va); + if (sys->mmal_vout) + rv = sys->mmal_vout->control(sys->mmal_vout, ctl, va); + msg_Dbg(vd, "<<< %s: Pic reset: fmt: %dx%d<-%dx%d, source: %dx%d/%dx%d", __func__, + vd->fmt.i_width, vd->fmt.i_height, x_vd->fmt.i_width, x_vd->fmt.i_height, + vd->source.i_width, vd->source.i_height, x_vd->source.i_width, x_vd->source.i_height); + vd->fmt = x_vd->fmt; + break; + + case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: + case VOUT_DISPLAY_CHANGE_SOURCE_CROP: + x_vd->source = vd->source; + default: + rv = x_vd->control(x_vd, 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_vout; +#if TRACE_ALL + msg_Dbg(vd, "<<< %s", __func__); +#endif + x_vd->manage(x_vd); +} +#endif + +static int OpenMmalX11(vlc_object_t *object) +{ + vout_display_t * const vd = (vout_display_t *)object; + mmal_x11_sys_t * const sys = calloc(1, sizeof(*sys)); + int ret = VLC_SUCCESS; + + if (sys == NULL) { + return VLC_EGENERIC; + } + vd->sys = (vout_display_sys_t *)sys; + + vd->info = (vout_display_info_t){ + .is_slow = false, + .has_double_click = false, + .needs_hide_mouse = false, + .has_pictures_invalid = true, + .subpicture_chromas = NULL + }; + + if ((sys->x_vout = load_display_module(vd, "vout display", "xcb_x11")) == NULL) + goto fail; + + if ((sys->mmal_vout = load_display_module(vd, "vout display", "mmal_vout")) == NULL) + { + // Winge but do no more than that - route everything to X + char dbuf0[5], dbuf1[5]; + msg_Info(vd, "Not a valid format for mmal vout (%s/%s)", str_fourcc(dbuf0, vd->fmt.i_chroma), str_fourcc(dbuf1, vd->source.i_chroma)); + } + + vd->pool = mmal_x11_pool; + vd->prepare = mmal_x11_prepare; + vd->display = mmal_x11_display; + vd->control = mmal_x11_control; +#if DO_MANAGE + vd->manage = mmal_x11_manage; +#endif + + if (want_mmal_vout(vd, sys)) { + sys->cur_vout = sys->mmal_vout; + sys->use_mmal = true; + } + else { + sys->cur_vout = sys->x_vout; + sys->use_mmal = false; + } + + vd->info = sys->cur_vout->info; + vd->fmt = sys->cur_vout->fmt; + + 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", 900) + add_shortcut("mmal_x11") + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_VOUT ) + set_callbacks(OpenMmalX11, CloseMmalX11) +vlc_module_end() + --- a/src/misc/fourcc.c +++ b/src/misc/fourcc.c @@ -756,6 +756,8 @@ VLC_CODEC_VDPAU_VIDEO_444, VLC_CODEC_VDPAU_OUTPUT }, FAKE_FMT() }, { { VLC_CODEC_ANDROID_OPAQUE, VLC_CODEC_MMAL_OPAQUE, + VLC_CODEC_MMAL_ZC_SAND8, VLC_CODEC_MMAL_ZC_SAND10, + VLC_CODEC_MMAL_ZC_I420, VLC_CODEC_D3D9_OPAQUE, VLC_CODEC_D3D11_OPAQUE }, FAKE_FMT() }, { { VLC_CODEC_D3D11_OPAQUE_10B, VLC_CODEC_D3D9_OPAQUE_10B },