diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 763e0c832a..45e312eea7 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -555,6 +555,7 @@ Video :vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only) :vda: requires ``--vo=opengl`` (OS X only) :dxva2-copy: copies video back to system RAM (Windows only) + :rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available) ``auto`` tries to automatically enable hardware decoding using the first available method. This still depends what VO you are using. For example, diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst index 7ac4cfe367..d0fe3c070a 100644 --- a/DOCS/man/vo.rst +++ b/DOCS/man/vo.rst @@ -866,3 +866,16 @@ Available video output drivers are: ``mpv --vo=opengl-cb:help`` for a list. This also supports the ``vo_cmdline`` command. + +``rpi`` (Raspberry Pi) + Native video output on the Raspberry Pi using the MMAL API. + + ``display=`` + Select the display number on which the video overlay should be shown + (default: 0). + + ``layer=`` + Select the dispmanx layer on which the video overlay should be shown + (default: -10). Note that mpv will also use the 2 layers above the + selected layer, to handle the window background and OSD. Actual video + rendering will happen on the layer above the selected layer. diff --git a/old-configure b/old-configure index 11c2e3ff90..d4b4f31436 100755 --- a/old-configure +++ b/old-configure @@ -932,6 +932,9 @@ cat > $TMPC << EOF #define HAVE_NETBSD_THREAD_NAME 0 #define HAVE_DXVA2_HWACCEL 0 #define HAVE_FCHMOD 0 +#define HAVE_RPI 0 +#define HAVE_RPI_GLES 0 +#define HAVE_AV_PIX_FMT_MMAL 0 #ifdef __OpenBSD__ #define DEFAULT_CDROM_DEVICE "/dev/rcd0c" diff --git a/options/options.c b/options/options.c index 20876c0e4a..80b2c2bc5d 100644 --- a/options/options.c +++ b/options/options.c @@ -292,7 +292,8 @@ const m_option_t mp_opts[] = { {"vda", 2}, {"vaapi", 4}, {"vaapi-copy", 5}, - {"dxva2-copy", 6})), + {"dxva2-copy", 6}, + {"rpi", 7})), OPT_STRING("hwdec-codecs", hwdec_codecs, 0), OPT_SUBSTRUCT("sws", vo.sws_opts, sws_conf, 0), @@ -797,6 +798,10 @@ const struct MPOpts mp_default_opts = { .mf_fps = 1.0, +#if HAVE_RPI + .hwdec_api = -1, +#endif + .display_tags = (char **)(const char*[]){ "Artist", "Album", "Album_Artist", "Comment", "Composer", "Genre", "Performer", "Title", "Track", "icy-title", diff --git a/video/decode/lavc.h b/video/decode/lavc.h index 85cf49abab..eaddd4abc2 100644 --- a/video/decode/lavc.h +++ b/video/decode/lavc.h @@ -52,6 +52,8 @@ struct vd_lavc_hwdec { // For horrible Intel shit-drivers only void (*lock)(struct lavc_ctx *ctx); void (*unlock)(struct lavc_ctx *ctx); + // Optional; if a special hardware decoder is needed (instead of "hwaccel"). + const char *(*get_codec)(struct lavc_ctx *ctx); }; enum { diff --git a/video/decode/rpi.c b/video/decode/rpi.c new file mode 100644 index 0000000000..44a550fe2e --- /dev/null +++ b/video/decode/rpi.c @@ -0,0 +1,56 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include "lavc.h" +#include "common/common.h" + +static int init_decoder(struct lavc_ctx *ctx, int fmt, int w, int h) +{ + return 0; +} + +static void uninit(struct lavc_ctx *ctx) +{ +} + +static int init(struct lavc_ctx *ctx) +{ + return 0; +} + +static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, + const char *decoder) +{ + if (strcmp(decoder, "h264") != 0) + return HWDEC_ERR_NO_CODEC; + return 0; +} + +static const char *get_codec(struct lavc_ctx *ctx) +{ + return "h264_mmal"; +} + +const struct vd_lavc_hwdec mp_vd_lavc_rpi = { + .type = HWDEC_RPI, + .image_format = IMGFMT_MMAL, + .probe = probe, + .init = init, + .uninit = uninit, + .init_decoder = init_decoder, + .get_codec = get_codec, +}; diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index eecc130208..b328d24102 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -121,8 +121,12 @@ const struct vd_lavc_hwdec mp_vd_lavc_vda; const struct vd_lavc_hwdec mp_vd_lavc_vaapi; const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy; const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy; +const struct vd_lavc_hwdec mp_vd_lavc_rpi; static const struct vd_lavc_hwdec *const hwdec_list[] = { +#if HAVE_RPI + &mp_vd_lavc_rpi, +#endif #if HAVE_VDPAU_HWACCEL &mp_vd_lavc_vdpau, #endif @@ -303,6 +307,8 @@ static int init(struct dec_video *vd, const char *decoder) if (hwdec) { ctx->software_fallback_decoder = talloc_strdup(ctx, decoder); + if (hwdec->get_codec) + decoder = hwdec->get_codec(ctx); MP_INFO(vd, "Using hardware decoding.\n"); } else if (vd->opts->hwdec_api != HWDEC_NONE) { MP_INFO(vd, "Using software decoding.\n"); @@ -370,7 +376,8 @@ static void init_avctx(struct dec_video *vd, const char *decoder, if (ctx->hwdec) { avctx->thread_count = 1; avctx->get_format = get_format_hwdec; - avctx->get_buffer2 = get_buffer2_hwdec; + if (ctx->hwdec->allocate_image) + avctx->get_buffer2 = get_buffer2_hwdec; if (ctx->hwdec->init(ctx) < 0) goto error; } else { diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c index 9ed9777736..2288512011 100644 --- a/video/fmt-conversion.c +++ b/video/fmt-conversion.c @@ -124,6 +124,9 @@ static const struct { #endif {IMGFMT_VAAPI, AV_PIX_FMT_VAAPI_VLD}, {IMGFMT_DXVA2, AV_PIX_FMT_DXVA2_VLD}, +#if HAVE_AV_PIX_FMT_MMAL + {IMGFMT_MMAL, AV_PIX_FMT_MMAL}, +#endif {0, AV_PIX_FMT_NONE} }; diff --git a/video/hwdec.h b/video/hwdec.h index 0574e5c562..b04b7c519b 100644 --- a/video/hwdec.h +++ b/video/hwdec.h @@ -12,6 +12,7 @@ enum hwdec_type { HWDEC_VAAPI = 4, HWDEC_VAAPI_COPY = 5, HWDEC_DXVA2_COPY = 6, + HWDEC_RPI = 7, }; struct mp_hwdec_ctx { diff --git a/video/img_format.h b/video/img_format.h index e734185045..c6c2886909 100644 --- a/video/img_format.h +++ b/video/img_format.h @@ -206,6 +206,7 @@ enum mp_imgfmt { IMGFMT_VDA, IMGFMT_VAAPI, IMGFMT_DXVA2, // IDirect3DSurface9 (NV12) + IMGFMT_MMAL, // MMAL_BUFFER_HEADER_T // Generic pass-through of AV_PIX_FMT_*. Used for formats which don't have // a corresponding IMGFMT_ value. diff --git a/video/out/gl_common.c b/video/out/gl_common.c index 1470227b5c..7e8ed7ce39 100644 --- a/video/out/gl_common.c +++ b/video/out/gl_common.c @@ -529,6 +529,9 @@ struct backend { }; static const struct backend backends[] = { +#if HAVE_RPI_GLES + {"rpi", mpgl_set_backend_rpi}, +#endif #if HAVE_GL_COCOA {"cocoa", mpgl_set_backend_cocoa}, #endif diff --git a/video/out/gl_common.h b/video/out/gl_common.h index 1d33682f3e..2e25d331c7 100644 --- a/video/out/gl_common.h +++ b/video/out/gl_common.h @@ -137,6 +137,7 @@ void mpgl_set_backend_x11es(MPGLContext *ctx); void mpgl_set_backend_x11egl(MPGLContext *ctx); void mpgl_set_backend_x11egles(MPGLContext *ctx); void mpgl_set_backend_wayland(MPGLContext *ctx); +void mpgl_set_backend_rpi(MPGLContext *ctx); void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *), const char *ext2, struct mp_log *log); diff --git a/video/out/gl_rpi.c b/video/out/gl_rpi.c new file mode 100644 index 0000000000..f82046e899 --- /dev/null +++ b/video/out/gl_rpi.c @@ -0,0 +1,219 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + * + * You can alternatively redistribute this file 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. + */ + +#include +#include + +#include +#include +#include + +#include "common/common.h" +#include "x11_common.h" +#include "gl_common.h" + +struct priv { + EGLDisplay egl_display; + EGLContext egl_context; + EGLSurface egl_surface; + DISPMANX_DISPLAY_HANDLE_T display; + DISPMANX_ELEMENT_HANDLE_T window; + DISPMANX_UPDATE_HANDLE_T update; + // yep, the API keeps a pointer to it + EGL_DISPMANX_WINDOW_T egl_window; + int w, h; +}; + +static void *get_proc_address(const GLubyte *name) +{ + void *p = eglGetProcAddress(name); + // It looks like eglGetProcAddress() should work even for builtin + // functions, but it doesn't work at least with RPI/Broadcom crap. + // (EGL 1.4, which current RPI firmware pretends to support, definitely + // is required to return non-extension functions.) + if (!p) { + void *h = dlopen("/opt/vc/lib/libGLESv2.so", RTLD_LAZY); + if (h) { + p = dlsym(h, name); + dlclose(h); + } + } + return p; +} + +static EGLConfig select_fb_config_egl(struct MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + + EGLint attributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLint config_count; + EGLConfig config; + + eglChooseConfig(p->egl_display, attributes, &config, 1, &config_count); + + if (!config_count) { + MP_FATAL(ctx->vo, "Could find EGL configuration!\n"); + return NULL; + } + + return config; +} + +static bool sc_config_window(struct MPGLContext *ctx, int flags) +{ + struct priv *p = ctx->priv; + struct vo *vo = ctx->vo; + + if (p->egl_context) { + vo->dwidth = p->w; + vo->dheight = p->h; + return true; + } + + bcm_host_init(); + + p->display = vc_dispmanx_display_open(0); + p->update = vc_dispmanx_update_start(0); + if (!p->display || !p->update) { + MP_FATAL(ctx->vo, "Could not get DISPMANX objects.\n"); + return false; + } + + uint32_t w, h; + if (graphics_get_display_size(0, &w, &h) < 0) { + MP_FATAL(ctx->vo, "Could not get display size.\n"); + return false; + } + + // dispmanx is like a neanderthal version of Wayland - you can add an + // overlay any place on the screen. Just use the whole screen. + VC_RECT_T dst = {.width = w, .height = h}; + VC_RECT_T src = {.width = w << 16, .height = h << 16}; + VC_DISPMANX_ALPHA_T alpha = { + .flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, + .opacity = 0xFF, + }; + p->window = vc_dispmanx_element_add(p->update, p->display, 1, &dst, 0, + &src, DISPMANX_PROTECTION_NONE, &alpha, 0, 0); + if (!p->window) { + MP_FATAL(ctx->vo, "Could not add DISPMANX element.\n"); + return false; + } + + vc_dispmanx_update_submit_sync(p->update); + + p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (!eglInitialize(p->egl_display, NULL, NULL)) { + MP_FATAL(ctx->vo, "EGL failed to initialize.\n"); + return false; + } + + eglBindAPI(EGL_OPENGL_ES_API); + + EGLConfig config = select_fb_config_egl(ctx); + if (!config) + return false; + + p->egl_window = (EGL_DISPMANX_WINDOW_T){.element = p->window, .width = w, .height = h}; + p->egl_surface = eglCreateWindowSurface(p->egl_display, config, &p->egl_window, NULL); + + if (p->egl_surface == EGL_NO_SURFACE) { + MP_FATAL(ctx->vo, "Could not create EGL surface!\n"); + return false; + } + + EGLint context_attributes[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + p->egl_context = eglCreateContext(p->egl_display, config, + EGL_NO_CONTEXT, context_attributes); + + if (p->egl_context == EGL_NO_CONTEXT) { + MP_FATAL(ctx->vo, "Could not create EGL context!\n"); + return false; + } + + eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface, + p->egl_context); + + const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS); + mpgl_load_functions(ctx->gl, get_proc_address, exts, vo->log); + + vo->dwidth = p->w = w; + vo->dheight = p->h = h; + + return true; +} + +static void sc_releaseGlContext(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + if (p->egl_context) { + eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + eglDestroyContext(p->egl_display, p->egl_context); + } + p->egl_context = EGL_NO_CONTEXT; + eglTerminate(p->egl_display); + vc_dispmanx_display_close(p->display); +} + +static void sc_swapGlBuffers(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + eglSwapBuffers(p->egl_display, p->egl_surface); +} + +static int sc_vo_init(struct vo *vo) +{ + return 1; +} + +static void sc_vo_uninit(struct vo *vo) +{ +} + +static int sc_vo_control(struct vo *vo, int *events, int request, void *arg) +{ + return VO_NOTIMPL; +} + +void mpgl_set_backend_rpi(MPGLContext *ctx) +{ + ctx->priv = talloc_zero(ctx, struct priv); + ctx->config_window = sc_config_window; + ctx->releaseGlContext = sc_releaseGlContext; + ctx->swapGlBuffers = sc_swapGlBuffers; + ctx->vo_init = sc_vo_init; + ctx->vo_uninit = sc_vo_uninit; + ctx->vo_control = sc_vo_control; +} diff --git a/video/out/vo.c b/video/out/vo.c index a99e9f69f4..e9b1cbfc1d 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -63,9 +63,13 @@ extern const struct vo_driver video_out_direct3d_shaders; extern const struct vo_driver video_out_sdl; extern const struct vo_driver video_out_vaapi; extern const struct vo_driver video_out_wayland; +extern const struct vo_driver video_out_rpi; const struct vo_driver *const video_out_drivers[] = { +#if HAVE_RPI + &video_out_rpi, +#endif #if HAVE_GL &video_out_opengl, #endif diff --git a/video/out/vo_rpi.c b/video/out/vo_rpi.c new file mode 100644 index 0000000000..e197477987 --- /dev/null +++ b/video/out/vo_rpi.c @@ -0,0 +1,567 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "common/common.h" +#include "common/msg.h" +#include "options/m_config.h" +#include "vo.h" +#include "video/mp_image.h" +#include "sub/osd.h" +#include "sub/img_convert.h" + +// In theory, the number of RGBA subbitmaps the OSD code could give us is +// unlimited; but in practice there will be rarely many elements. +#define MAX_OSD_ELEMS MP_SUB_BB_LIST_MAX + +struct osd_elem { + DISPMANX_RESOURCE_HANDLE_T resource; + DISPMANX_ELEMENT_HANDLE_T element; +}; + +struct osd_part { + struct osd_elem elems[MAX_OSD_ELEMS]; + int num_elems; + int change_id; + bool needed; +}; + +struct priv { + DISPMANX_DISPLAY_HANDLE_T display; + DISPMANX_ELEMENT_HANDLE_T window; + DISPMANX_UPDATE_HANDLE_T update; + uint32_t w, h; + + struct osd_part osd_parts[MAX_OSD_PARTS]; + double osd_pts; + struct mp_osd_res osd_res; + + MMAL_COMPONENT_T *renderer; + bool renderer_enabled; + + struct mp_image *next_image; + + // for RAM input + MMAL_POOL_T *swpool; + + int background_layer; + int video_layer; + int osd_layer; + + int display_nr; + int layer; +}; + +// Magic alignments (in pixels) expected by the MMAL internals. +#define ALIGN_W 32 +#define ALIGN_H 16 + +// Make mpi point to buffer, assuming MMAL_ENCODING_I420. +// buffer can be NULL. +// Return the required buffer space. +static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer, + struct mp_image_params *params) +{ + assert(params->imgfmt == IMGFMT_420P); + mp_image_set_params(mpi, params); + int w = MP_ALIGN_UP(params->w, ALIGN_W); + int h = MP_ALIGN_UP(params->h, ALIGN_H); + uint8_t *cur = buffer ? buffer->data : NULL; + size_t size = 0; + for (int i = 0; i < 3; i++) { + int div = i ? 2 : 1; + mpi->planes[i] = cur; + mpi->stride[i] = w / div; + size_t plane_size = h / div * mpi->stride[i]; + if (cur) + cur += plane_size; + size += plane_size; + } + return size; +} + +static void wipe_osd_part(struct vo *vo, struct osd_part *part) +{ + struct priv *p = vo->priv; + + for (int n = 0; n < part->num_elems; n++) { + vc_dispmanx_element_remove(p->update, part->elems[n].element); + vc_dispmanx_resource_delete(part->elems[n].resource); + } + part->num_elems = 0; + part->change_id = -1; +} + +static void wipe_osd(struct vo *vo) +{ + struct priv *p = vo->priv; + + for (int x = 0; x < MAX_OSD_PARTS; x++) + wipe_osd_part(vo, &p->osd_parts[x]); +} + +static int add_element(struct vo *vo, struct osd_part *part, int index, + struct sub_bitmap *sub) +{ + struct priv *p = vo->priv; + VC_IMAGE_TYPE_T format = VC_IMAGE_ARGB8888; // assuming RPI is always LE + + struct osd_elem *elem = &part->elems[index]; + *elem = (struct osd_elem){0}; + + // I have no idea why stride must be passed in such a hacky way. It's not + // documented. Other software does it too. Other software claims aligning + // the width and "probably" the height is required too, but for me it works + // just fine without on rpi2. (See Weston's rpi renderer.) + elem->resource = vc_dispmanx_resource_create(format, + sub->w | (sub->stride << 16), + sub->h, + &(int32_t){0}); + if (!elem->resource) { + MP_ERR(vo, "Could not create %dx%d sub-bitmap\n", sub->w, sub->h); + return -1; + } + + VC_RECT_T rc = {.width = sub->w, .height = sub->h}; + vc_dispmanx_resource_write_data(elem->resource, format, + sub->stride, sub->bitmap, &rc); + VC_RECT_T src = {.width = sub->w << 16, .height = sub->h << 16}; + VC_RECT_T dst = {.x = sub->x, .y = sub->y, .width = sub->dw, .height = sub->dh}; + VC_DISPMANX_ALPHA_T alpha = { + .flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_PREMULT, + .opacity = 0xFF, + }; + elem->element = vc_dispmanx_element_add(p->update, p->display, p->osd_layer, + &dst, elem->resource, &src, + DISPMANX_PROTECTION_NONE, + &alpha, 0, 0); + if (!elem->element) { + MP_ERR(vo, "Could not create sub-bitmap element\n"); + return -1; + } + + return 0; +} + +static void osd_draw_cb(void *ctx, struct sub_bitmaps *imgs) +{ + struct vo *vo = ctx; + struct priv *p = vo->priv; + struct osd_part *part = &p->osd_parts[imgs->render_index]; + + part->needed = true; + + if (imgs->change_id == part->change_id) + return; + + wipe_osd_part(vo, part); + part->change_id = imgs->change_id; + + for (int n = 0; n < imgs->num_parts; n++) { + if (part->num_elems == MAX_OSD_ELEMS) { + MP_ERR(vo, "Too many OSD elements.\n"); + break; + } + int index = part->num_elems++; + if (add_element(vo, part, index, &imgs->parts[n]) < 0) + break; + } +} + +static void update_osd(struct vo *vo) +{ + struct priv *p = vo->priv; + + for (int x = 0; x < MAX_OSD_PARTS; x++) + p->osd_parts[x].needed = false; + + static const bool formats[SUBBITMAP_COUNT] = {[SUBBITMAP_RGBA] = true}; + osd_draw(vo->osd, p->osd_res, p->osd_pts, 0, formats, osd_draw_cb, vo); + + for (int x = 0; x < MAX_OSD_PARTS; x++) { + struct osd_part *part = &p->osd_parts[x]; + if (!part->needed) + wipe_osd_part(vo, part); + } +} + +static void resize(struct vo *vo) +{ + struct priv *p = vo->priv; + MMAL_PORT_T *input = p->renderer->input[0]; + + struct mp_rect src, dst; + + vo_get_src_dst_rects(vo, &src, &dst, &p->osd_res); + + MMAL_DISPLAYREGION_T dr = { + .hdr = { .id = MMAL_PARAMETER_DISPLAYREGION, + .size = sizeof(MMAL_DISPLAYREGION_T), }, + .src_rect = { .x = src.x0, .y = src.y0, + .width = src.x1 - src.x0, .height = src.y1 - src.y0, }, + .dest_rect = { .x = dst.x0, .y = dst.y0, + .width = dst.x1 - dst.x0, .height = dst.y1 - dst.y0, }, + .layer = p->video_layer, + .display_num = p->display_nr, + .set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT | + MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM, + }; + + if (mmal_port_parameter_set(input, &dr.hdr)) + MP_WARN(vo, "could not set video rectangle\n"); +} + +static void flip_page(struct vo *vo) +{ + struct priv *p = vo->priv; + struct mp_image *mpi = p->next_image; + p->next_image = NULL; + + // For OSD + vc_dispmanx_update_submit_sync(p->update); + p->update = vc_dispmanx_update_start(10); + + if (mpi) { + MMAL_PORT_T *input = p->renderer->input[0]; + MMAL_BUFFER_HEADER_T *ref = (void *)mpi->planes[3]; + + // Assume this field is free for use by us. + ref->user_data = mpi; + + if (mmal_port_send_buffer(input, ref)) { + MP_ERR(vo, "could not queue picture!\n"); + talloc_free(mpi); + } + } +} + +static void free_mmal_buffer(void *arg) +{ + MMAL_BUFFER_HEADER_T *buffer = arg; + mmal_buffer_header_release(buffer); +} + +static void draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct priv *p = vo->priv; + + talloc_free(p->next_image); + p->next_image = NULL; + + p->osd_pts = mpi->pts; + update_osd(vo); + + if (vo->params->imgfmt != IMGFMT_MMAL) { + MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue); + if (!buffer) { + talloc_free(mpi); + MP_ERR(vo, "Can't allocate buffer.\n"); + return; + } + mmal_buffer_header_reset(buffer); + + struct mp_image *new_ref = mp_image_new_custom_ref(&(struct mp_image){0}, + buffer, + free_mmal_buffer); + if (!new_ref) { + mmal_buffer_header_release(buffer); + talloc_free(mpi); + MP_ERR(vo, "Out of memory.\n"); + return; + } + + mp_image_setfmt(new_ref, IMGFMT_MMAL); + new_ref->planes[3] = (void *)buffer; + + struct mp_image dmpi = {0}; + buffer->length = layout_buffer(&dmpi, buffer, vo->params); + mp_image_copy(&dmpi, mpi); + + talloc_free(mpi); + mpi = new_ref; + } + + p->next_image = mpi; +} + +static int query_format(struct vo *vo, int format) +{ + return format == IMGFMT_MMAL || format == IMGFMT_420P; +} + +static MMAL_FOURCC_T map_csp(enum mp_csp csp) +{ + switch (csp) { + case MP_CSP_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601; + case MP_CSP_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709; + case MP_CSP_SMPTE_240M: return MMAL_COLOR_SPACE_SMPTE240M; + default: return MMAL_COLOR_SPACE_UNKNOWN; + } +} + +static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + mmal_buffer_header_release(buffer); +} + +static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + struct mp_image *mpi = buffer->user_data; + talloc_free(mpi); +} + +static void disable_renderer(struct vo *vo) +{ + struct priv *p = vo->priv; + + if (p->renderer_enabled) { + mmal_port_disable(p->renderer->control); + mmal_port_disable(p->renderer->input[0]); + + mmal_port_flush(p->renderer->control); + mmal_port_flush(p->renderer->input[0]); + + mmal_component_disable(p->renderer); + } + mmal_pool_destroy(p->swpool); + p->swpool = NULL; + p->renderer_enabled = false; +} + +static int reconfig(struct vo *vo, struct mp_image_params *params, int flags) +{ + struct priv *p = vo->priv; + MMAL_PORT_T *input = p->renderer->input[0]; + bool opaque = params->imgfmt == IMGFMT_MMAL; + + vo->dwidth = p->w; + vo->dheight = p->h; + + disable_renderer(vo); + + AVRational dr = {params->d_w, params->d_h}; + AVRational ir = {params->w, params->h}; + AVRational par = av_div_q(dr, ir); + + input->format->encoding = opaque ? MMAL_ENCODING_OPAQUE : MMAL_ENCODING_I420; + input->format->es->video.width = MP_ALIGN_UP(params->w, ALIGN_W); + input->format->es->video.height = MP_ALIGN_UP(params->h, ALIGN_H); + input->format->es->video.crop = (MMAL_RECT_T){0, 0, params->w, params->h}; + input->format->es->video.par = (MMAL_RATIONAL_T){par.num, par.den}; + input->format->es->video.color_space = map_csp(params->colorspace); + + if (mmal_port_format_commit(input)) + return -1; + + input->buffer_num = MPMAX(input->buffer_num_min, + input->buffer_num_recommended) + 3; + input->buffer_size = MPMAX(input->buffer_size_min, + input->buffer_size_recommended); + + if (!opaque) { + size_t size = layout_buffer(&(struct mp_image){0}, NULL, params); + if (input->buffer_size != size) { + MP_FATAL(vo, "We disagree with MMAL about buffer sizes.\n"); + return -1; + } + + p->swpool = mmal_pool_create(input->buffer_num, input->buffer_size); + if (!p->swpool) { + MP_FATAL(vo, "Could not allocate buffer pool.\n"); + return -1; + } + } + + resize(vo); + + p->renderer_enabled = true; + + if (mmal_port_enable(p->renderer->control, control_port_cb)) + return -1; + + if (mmal_port_enable(input, input_port_cb)) + return -1; + + if (mmal_component_enable(p->renderer)) { + MP_FATAL(vo, "Failed to enable video renderer.\n"); + return -1; + } + + return 0; +} + +static struct mp_image *take_screenshot(struct vo *vo) +{ + struct priv *p = vo->priv; + + struct mp_image *img = mp_image_alloc(IMGFMT_BGRA, p->w, p->h); + if (!img) + return NULL; + + DISPMANX_RESOURCE_HANDLE_T resource = + vc_dispmanx_resource_create(VC_IMAGE_ARGB8888, + img->w | ((img->w * 4) << 16), img->h, + &(int32_t){0}); + if (!resource) + goto fail; + + if (vc_dispmanx_snapshot(p->display, resource, 0)) + goto fail; + + VC_RECT_T rc = {.width = img->w, .height = img->h}; + if (vc_dispmanx_resource_read_data(resource, &rc, img->planes[0], img->stride[0])) + goto fail; + + vc_dispmanx_resource_delete(resource); + return img; + +fail: + vc_dispmanx_resource_delete(resource); + talloc_free(img); + return NULL; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct priv *p = vo->priv; + + switch (request) { + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_SET_PANSCAN: + if (p->renderer_enabled) + resize(vo); + return VO_TRUE; + case VOCTRL_REDRAW_FRAME: + update_osd(vo); + return VO_TRUE; + case VOCTRL_SCREENSHOT_WIN: + *(struct mp_image **)data = take_screenshot(vo); + return VO_TRUE; + } + + return VO_NOTIMPL; +} + +static void uninit(struct vo *vo) +{ + struct priv *p = vo->priv; + + talloc_free(p->next_image); + + wipe_osd(vo); + + if (p->update) + vc_dispmanx_update_submit_sync(p->update); + + if (p->renderer) { + disable_renderer(vo); + mmal_component_release(p->renderer); + } + + if (p->display) + vc_dispmanx_display_close(p->display); +} + +static int preinit(struct vo *vo) +{ + struct priv *p = vo->priv; + + p->background_layer = p->layer; + p->video_layer = p->layer + 1; + p->osd_layer = p->layer + 2; + + bcm_host_init(); + + p->display = vc_dispmanx_display_open(p->display_nr); + p->update = vc_dispmanx_update_start(0); + if (!p->display || !p->update) { + MP_FATAL(vo, "Could not get DISPMANX objects.\n"); + goto fail; + } + + if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer)) + { + MP_FATAL(vo, "Could not create MMAL renderer.\n"); + goto fail; + } + + if (graphics_get_display_size(0, &p->w, &p->h) < 0) { + MP_FATAL(vo, "Could not get display size.\n"); + goto fail; + } + + MP_VERBOSE(vo, "Display size: %dx%d\n", p->w, p->h); + + // Just use the whole screen. + VC_RECT_T dst = {.width = p->w, .height = p->h}; + VC_RECT_T src = {.width = p->w << 16, .height = p->h << 16}; + VC_DISPMANX_ALPHA_T alpha = { + .flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, + .opacity = 0xFF, + }; + p->window = vc_dispmanx_element_add(p->update, p->display, p->background_layer, + &dst, 0, &src, DISPMANX_PROTECTION_NONE, + &alpha, 0, 0); + if (!p->window) { + MP_FATAL(vo, "Could not add DISPMANX element.\n"); + goto fail; + } + + vc_dispmanx_update_submit_sync(p->update); + p->update = vc_dispmanx_update_start(10); + + return 0; + +fail: + uninit(vo); + return -1; +} + +#define OPT_BASE_STRUCT struct priv +static const struct m_option options[] = { + OPT_INT("display", display_nr, 0), + OPT_INT("layer", layer, 0, OPTDEF_INT(-10)), + {0}, +}; + +const struct vo_driver video_out_rpi = { + .description = "Raspberry Pi (MMAL)", + .name = "rpi", + .preinit = preinit, + .query_format = query_format, + .reconfig = reconfig, + .control = control, + .draw_image = draw_image, + .flip_page = flip_page, + .uninit = uninit, + .priv_size = sizeof(struct priv), + .options = options, +}; diff --git a/wscript b/wscript index df0c02a676..5d42446b93 100644 --- a/wscript +++ b/wscript @@ -420,6 +420,12 @@ FFmpeg/Libav libraries. You need at least {0}. Aborting.".format(libav_versions_ 'func': check_statement('libavutil/frame.h', 'enum AVFrameSideDataType type = AV_FRAME_DATA_SKIP_SAMPLES', use='libav') + }, { + 'name': 'av-pix-fmt-mmal', + 'desc': 'libavutil AV_PIX_FMT_MMAL', + 'func': check_statement('libavutil/pixfmt.h', + 'int x = AV_PIX_FMT_MMAL', + use='libav'), } ] @@ -589,11 +595,6 @@ video_output_features = [ 'groups': [ 'gl' ], 'func': check_statement('windows.h', 'wglCreateContext(0)', lib='opengl32') - } , { - 'name': '--gl', - 'desc': 'OpenGL video outputs', - 'deps_any': [ 'gl-cocoa', 'gl-x11', 'gl-win32', 'gl-wayland' ], - 'func': check_true } , { 'name': '--vdpau', 'desc': 'VDPAU acceleration', @@ -634,6 +635,39 @@ video_output_features = [ 'desc': 'Direct3D support', 'deps': [ 'win32' ], 'func': check_cc(header_name='d3d9.h'), + }, { + # We need MMAL/bcm_host/dispmanx APIs. Also, most RPI distros require + # every project to hardcode the paths to the include directories. Also, + # these headers are so broken that they spam tons of warnings by merely + # including them (compensate with -isystem and -fgnu89-inline). + 'name': '--rpi', + 'desc': 'Raspberry Pi support', + 'func': + check_cc(cflags="-isystem/opt/vc/include/ "+ + "-isystem/opt/vc/include/interface/vcos/pthreads " + + "-isystem/opt/vc/include/interface/vmcs_host/linux " + + "-fgnu89-inline", + linkflags="-L/opt/vc/lib", + header_name="bcm_host.h", + lib=['mmal_core', 'mmal_util', 'mmal_vc_client', 'bcm_host']), + }, { + 'name': '--rpi-gles', + 'desc': 'GLES on Raspberry Pi', + 'groups': [ 'gl' ], + 'deps': ['rpi'], + # We still need all OpenGL symbols, because the vo_opengl code is + # generic and supports anything from GLES2/OpenGL 2.1 to OpenGL 4 core. + 'func': compose_checks( + check_cc(lib="EGL"), + check_cc(lib="GLESv2"), + check_statement('GL/gl.h', '(void)GL_RGB32F'), # arbitrary OpenGL 3.0 symbol + check_statement('GL/gl.h', '(void)GL_LUMINANCE16') # arbitrary OpenGL legacy-only symbol + ), + } , { + 'name': '--gl', + 'desc': 'OpenGL video outputs', + 'deps_any': [ 'gl-cocoa', 'gl-x11', 'gl-win32', 'gl-wayland', 'rpi-gles' ], + 'func': check_true } ] diff --git a/wscript_build.py b/wscript_build.py index 0ba763f22f..20a2633d99 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -283,6 +283,7 @@ def build(ctx): ( "video/vdpau_mixer.c", "vdpau" ), ( "video/decode/dec_video.c"), ( "video/decode/dxva2.c", "dxva2-hwaccel" ), + ( "video/decode/rpi.c", "rpi" ), ( "video/decode/vaapi.c", "vaapi-hwaccel" ), ( "video/decode/vd_lavc.c" ), ( "video/decode/vda.c", "vda-hwaccel" ), @@ -324,6 +325,7 @@ def build(ctx): ( "video/out/filter_kernels.c" ), ( "video/out/gl_cocoa.c", "gl-cocoa" ), ( "video/out/gl_common.c", "gl" ), + ( "video/out/gl_rpi.c", "rpi-gles" ), ( "video/out/gl_hwdec.c", "gl" ), ( "video/out/gl_hwdec_vaglx.c", "vaapi-glx" ), ( "video/out/gl_hwdec_vda.c", "vda-gl" ), @@ -341,6 +343,7 @@ def build(ctx): ( "video/out/vo_direct3d.c", "direct3d" ), ( "video/out/vo_image.c" ), ( "video/out/vo_lavc.c", "encoding" ), + ( "video/out/vo_rpi.c", "rpi" ), ( "video/out/vo_null.c" ), ( "video/out/vo_opengl.c", "gl" ), ( "video/out/vo_opengl_cb.c", "gl" ),