player: remove all rpi-specific code

vo_rpi and its related code has pretty much historically been a
disaster in mpv. The build regularly gets broken and since nobody uses
it, it takes months for anyone to notice. There was also that time where
fullscreen was broken for about a year and a half. Also building in waf
was entirely broken for about a couple of years or so due to mysterious
reasons no one ever figured out (meson magically fixed it).

Anyways, once again the build is broken due to rpi being forgotten about
again, but instead of pretending to support this crap. Just drop it all.
Nowadays, mmal hwdec is a relic since these devices are better off using
the v4l2m2m ffmpeg fork instead which actually uses KMS properly. RPI 1
and 2 probably can't do this and will remain broken but oh well blame
Broadcom for being special snowflakes and not using standard APIs (my
rockpro worked out of the box; just saying). RPI 2 is nearly 10 years
old anyways, so I think you can afford a new SBC by now. If we were
nicer, there would be a deprecation period, but this is broken in the
last major release anyway so too late.

Closes #13402.
This commit is contained in:
Dudemanguy 2024-02-03 14:27:46 -06:00
parent 8ecb462a9c
commit 343a5fd345
11 changed files with 2 additions and 1714 deletions

View File

@ -44,6 +44,7 @@ Interface changes
- change fallback deinterlace to bwdif
- add the command `load-config-file`
- add the command `load-input-conf`
- remove `--vo=rpi`, `--gpu-context=rpi`, and `--hwdec=mmal`
--- mpv 0.37.0 ---
- `--save-position-on-quit` and its associated commands now store state files
in %LOCALAPPDATA% instead of %APPDATA% directory by default on Windows.

View File

@ -1315,8 +1315,6 @@ Video
:mediacodec: requires ``--vo=gpu --gpu-context=android``
or ``--vo=mediacodec_embed`` (Android only)
:mediacodec-copy: copies video back to system RAM (Android only)
:mmal: requires ``--vo=gpu`` (Raspberry Pi only - default if available)
:mmal-copy: copies video back to system RAM (Raspberry Pi only)
:cuda: requires ``--vo=gpu`` (Any platform CUDA is available)
:cuda-copy: copies video back to system RAM (Any platform CUDA is available)
:crystalhd: copies video back to system RAM (Any platform supported by hardware)
@ -1411,9 +1409,6 @@ Video
affect this additionally. This can give incorrect results even with
completely ordinary video sources.
``rpi`` always uses the hardware overlay renderer, even with
``--vo=gpu``.
``mediacodec`` is not safe. It forces RGB conversion (not with ``-copy``)
and how well it handles non-standard colorspaces is not known.
In the rare cases where 10-bit is supported the bit depth of the output

View File

@ -567,30 +567,6 @@ Available video output drivers are:
This also supports many of the options the ``gpu`` VO has, depending on the
backend.
``rpi`` (Raspberry Pi)
Native video output on the Raspberry Pi using the MMAL API.
The following global options are supported by this video output:
``--rpi-display=<number>``
Select the display number on which the video overlay should be shown
(default: 0).
``--rpi-layer=<number>``
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.
``--rpi-background=<yes|no>``
Whether to render a black background behind the video (default: no).
Normally it's better to kill the console framebuffer instead, which
gives better performance.
``--rpi-osd=<yes|no>``
Enabled by default. If disabled with ``no``, no OSD layer is created.
This also means there will be no subtitles rendered.
``drm`` (Direct Rendering Manager)
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
Should be used when one doesn't want to install full-blown graphical

View File

@ -1219,16 +1219,7 @@ if plain_gl.allowed()
features += {'gl': true}
endif
rpi = dependency('/opt/vc/lib/pkgconfig/brcmegl.pc', 'brcmegl', required: get_option('rpi'))
features += {'rpi': gl_allowed and rpi.found()}
if features['rpi']
dependencies += rpi
features += {'gl': true}
sources += files('video/out/opengl/context_rpi.c')
endif
features += {'egl-helpers': features['egl'] or egl_android.found() or
egl_angle_win32.allowed() or features['rpi']}
features += {'egl-helpers': features['egl'] or egl_android.found() or egl_angle_win32.allowed()}
if features['egl-helpers']
sources += files('video/out/opengl/egl_helpers.c')
endif
@ -1377,18 +1368,6 @@ if features['ios-gl']
sources += files('video/out/hwdec/hwdec_ios_gl.m')
endif
rpi_mmal_opt = get_option('rpi-mmal').require(
features['rpi'],
error_message: 'rpi was not found!',
)
rpi_mmal = dependency('/opt/vc/lib/pkgconfig/mmal.pc', 'mmal', required: rpi_mmal_opt)
features += {'rpi-mmal': rpi_mmal.found()}
if features['rpi-mmal']
dependencies += rpi_mmal
sources += files('video/out/opengl/hwdec_rpi.c',
'video/out/vo_rpi.c')
endif
libva = dependency('libva', version: '>= 1.1.0', required: get_option('vaapi'))
vaapi_drm = dependency('libva-drm', version: '>= 1.1.0',

View File

@ -74,7 +74,6 @@ option('gl-dxinterop', type: 'feature', value: 'auto', description: 'OpenGL/Dire
option('gl-win32', type: 'feature', value: 'auto', description: 'OpenGL Win32 Backend')
option('gl-x11', type: 'feature', value: 'disabled', description: 'OpenGL X11/GLX (deprecated/legacy)')
option('jpeg', type: 'feature', value: 'auto', description: 'JPEG support')
option('rpi', type: 'feature', value: 'disabled', description: 'Raspberry Pi support')
option('sdl2-video', type: 'feature', value: 'auto', description: 'SDL2 video output')
option('shaderc', type: 'feature', value: 'auto', description: 'libshaderc SPIR-V compiler')
option('sixel', type: 'feature', value:'auto', description: 'Sixel')
@ -100,7 +99,6 @@ option('d3d-hwaccel', type: 'feature', value: 'auto', description: 'D3D11VA hwac
option('d3d9-hwaccel', type: 'feature', value: 'auto', description: 'DXVA2 hwaccel')
option('gl-dxinterop-d3d9', type: 'feature', value: 'auto', description: 'OpenGL/DirectX Interop Backend DXVA2 interop')
option('ios-gl', type: 'feature', value: 'auto', description: 'iOS OpenGL ES hardware decoding interop support')
option('rpi-mmal', type: 'feature', value: 'auto', description: 'Raspberry Pi MMAL hwaccel')
option('videotoolbox-gl', type: 'feature', value: 'auto', description: 'Videotoolbox with OpenGL')
option('videotoolbox-pl', type: 'feature', value: 'auto', description: 'Videotoolbox with libplacebo')
option('vulkan-interop', type: 'feature', value: 'auto', description: 'Vulkan graphics interop')

View File

@ -41,7 +41,6 @@ extern const struct ra_ctx_fns ra_ctx_wayland_egl;
extern const struct ra_ctx_fns ra_ctx_wgl;
extern const struct ra_ctx_fns ra_ctx_angle;
extern const struct ra_ctx_fns ra_ctx_dxgl;
extern const struct ra_ctx_fns ra_ctx_rpi;
extern const struct ra_ctx_fns ra_ctx_android;
/* Vulkan */
@ -67,9 +66,6 @@ static const struct ra_ctx_fns *contexts[] = {
#if HAVE_EGL_ANDROID
&ra_ctx_android,
#endif
#if HAVE_RPI
&ra_ctx_rpi,
#endif
#if HAVE_EGL_ANGLE_WIN32
&ra_ctx_angle,
#endif

View File

@ -34,7 +34,6 @@ extern const struct ra_hwdec_driver ra_hwdec_dxva2gldx;
extern const struct ra_hwdec_driver ra_hwdec_d3d11va;
extern const struct ra_hwdec_driver ra_hwdec_dxva2dxgi;
extern const struct ra_hwdec_driver ra_hwdec_cuda;
extern const struct ra_hwdec_driver ra_hwdec_rpi_overlay;
extern const struct ra_hwdec_driver ra_hwdec_drmprime;
extern const struct ra_hwdec_driver ra_hwdec_drmprime_overlay;
extern const struct ra_hwdec_driver ra_hwdec_aimagereader;
@ -70,9 +69,6 @@ const struct ra_hwdec_driver *const ra_hwdec_drivers[] = {
#if HAVE_VDPAU_GL_X11
&ra_hwdec_vdpau,
#endif
#if HAVE_RPI_MMAL
&ra_hwdec_rpi_overlay,
#endif
#if HAVE_DRM
&ra_hwdec_drmprime,
&ra_hwdec_drmprime_overlay,

View File

@ -1,327 +0,0 @@
/*
* This file is part of mpv.
*
* mpv 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.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <bcm_host.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "common/common.h"
#include "video/out/win_state.h"
#include "context.h"
#include "egl_helpers.h"
struct priv {
struct GL gl;
DISPMANX_DISPLAY_HANDLE_T display;
DISPMANX_ELEMENT_HANDLE_T window;
DISPMANX_UPDATE_HANDLE_T update;
EGLDisplay egl_display;
EGLConfig egl_config;
EGLContext egl_context;
EGLSurface egl_surface;
// yep, the API keeps a pointer to it
EGL_DISPMANX_WINDOW_T egl_window;
int x, y, w, h;
double display_fps;
atomic_int reload_display;
int win_params[4];
};
static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1,
uint32_t param2)
{
struct ra_ctx *ctx = callback_data;
struct priv *p = ctx->priv;
atomic_store(&p->reload_display, true);
vo_wakeup(ctx->vo);
}
static void destroy_dispmanx(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (p->egl_surface) {
eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
eglDestroySurface(p->egl_display, p->egl_surface);
p->egl_surface = EGL_NO_SURFACE;
}
if (p->window)
vc_dispmanx_element_remove(p->update, p->window);
p->window = 0;
if (p->display)
vc_dispmanx_display_close(p->display);
p->display = 0;
if (p->update)
vc_dispmanx_update_submit_sync(p->update);
p->update = 0;
}
static void rpi_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
ra_gl_ctx_uninit(ctx);
vc_tv_unregister_callback_full(tv_callback, ctx);
destroy_dispmanx(ctx);
if (p->egl_context)
eglDestroyContext(p->egl_display, p->egl_context);
p->egl_context = EGL_NO_CONTEXT;
eglReleaseThread();
p->egl_display = EGL_NO_DISPLAY;
}
static bool recreate_dispmanx(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
int display_nr = 0;
int layer = 0;
MP_VERBOSE(ctx, "Recreating DISPMANX state...\n");
destroy_dispmanx(ctx);
p->display = vc_dispmanx_display_open(display_nr);
p->update = vc_dispmanx_update_start(0);
if (!p->display || !p->update) {
MP_FATAL(ctx, "Could not get DISPMANX objects.\n");
goto fail;
}
uint32_t dispw, disph;
if (graphics_get_display_size(0, &dispw, &disph) < 0) {
MP_FATAL(ctx, "Could not get display size.\n");
goto fail;
}
p->w = dispw;
p->h = disph;
if (ctx->vo->opts->fullscreen) {
p->x = p->y = 0;
} else {
struct vo_win_geometry geo;
struct mp_rect screenrc = {0, 0, p->w, p->h};
vo_calc_window_geometry(ctx->vo, &screenrc, &geo);
mp_rect_intersection(&geo.win, &screenrc);
p->x = geo.win.x0;
p->y = geo.win.y0;
p->w = geo.win.x1 - geo.win.x0;
p->h = geo.win.y1 - geo.win.y0;
}
// dispmanx is like a neanderthal version of Wayland - you can add an
// overlay any place on the screen.
VC_RECT_T dst = {.x = p->x, .y = p->y, .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, layer, &dst, 0,
&src, DISPMANX_PROTECTION_NONE, &alpha,
0, 0);
if (!p->window) {
MP_FATAL(ctx, "Could not add DISPMANX element.\n");
goto fail;
}
vc_dispmanx_update_submit_sync(p->update);
p->update = vc_dispmanx_update_start(0);
p->egl_window = (EGL_DISPMANX_WINDOW_T){
.element = p->window,
.width = p->w,
.height = p->h,
};
p->egl_surface = eglCreateWindowSurface(p->egl_display, p->egl_config,
&p->egl_window, NULL);
if (p->egl_surface == EGL_NO_SURFACE) {
MP_FATAL(ctx, "Could not create EGL surface!\n");
goto fail;
}
if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
p->egl_context))
{
MP_FATAL(ctx, "Failed to set context!\n");
goto fail;
}
p->display_fps = 0;
TV_GET_STATE_RESP_T tvstate;
TV_DISPLAY_STATE_T tvstate_disp;
if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) {
if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) {
p->display_fps = tvstate_disp.display.hdmi.frame_rate;
HDMI_PROPERTY_PARAM_T param = {
.property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE,
};
if (!vc_tv_hdmi_get_property(&param) &&
param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC)
p->display_fps = p->display_fps / 1.001;
} else {
p->display_fps = tvstate_disp.display.sdtv.frame_rate;
}
}
p->win_params[0] = display_nr;
p->win_params[1] = layer;
p->win_params[2] = p->x;
p->win_params[3] = p->y;
ctx->vo->dwidth = p->w;
ctx->vo->dheight = p->h;
if (ctx->swapchain)
ra_gl_ctx_resize(ctx->swapchain, p->w, p->h, 0);
ctx->vo->want_redraw = true;
vo_event(ctx->vo, VO_EVENT_WIN_STATE);
return true;
fail:
destroy_dispmanx(ctx);
return false;
}
static void rpi_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
eglSwapBuffers(p->egl_display, p->egl_surface);
}
static bool rpi_init(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
bcm_host_init();
vc_tv_register_callback(tv_callback, ctx);
p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!eglInitialize(p->egl_display, NULL, NULL)) {
MP_FATAL(ctx, "EGL failed to initialize.\n");
goto fail;
}
if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &p->egl_config))
goto fail;
if (!recreate_dispmanx(ctx))
goto fail;
mpegl_load_functions(&p->gl, ctx->log);
struct ra_gl_ctx_params params = {
.swap_buffers = rpi_swap_buffers,
};
if (!ra_gl_ctx_init(ctx, &p->gl, params))
goto fail;
ra_add_native_resource(ctx->ra, "MPV_RPI_WINDOW", p->win_params);
ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
return true;
fail:
rpi_uninit(ctx);
return false;
}
static bool rpi_reconfig(struct ra_ctx *ctx)
{
return recreate_dispmanx(ctx);
}
static struct mp_image *take_screenshot(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (!p->display)
return NULL;
struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, 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 rpi_control(struct ra_ctx *ctx, int *events, int request, void *arg)
{
struct priv *p = ctx->priv;
switch (request) {
case VOCTRL_SCREENSHOT_WIN:
*(struct mp_image **)arg = take_screenshot(ctx);
return VO_TRUE;
case VOCTRL_CHECK_EVENTS:
if (atomic_fetch_and(&p->reload_display, 0)) {
MP_WARN(ctx, "Recovering from display mode switch...\n");
recreate_dispmanx(ctx);
}
return VO_TRUE;
case VOCTRL_GET_DISPLAY_FPS:
*(double *)arg = p->display_fps;
return VO_TRUE;
}
return VO_NOTIMPL;
}
const struct ra_ctx_fns ra_ctx_rpi = {
.type = "opengl",
.name = "rpi",
.reconfig = rpi_reconfig,
.control = rpi_control,
.init = rpi_init,
.uninit = rpi_uninit,
};

View File

@ -1,384 +0,0 @@
/*
* This file is part of mpv.
*
* mpv 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.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <assert.h>
#include <bcm_host.h>
#include <interface/mmal/mmal.h>
#include <interface/mmal/util/mmal_util.h>
#include <interface/mmal/util/mmal_default_components.h>
#include <interface/mmal/vc/mmal_vc_api.h>
#include <libavutil/rational.h>
#include "common/common.h"
#include "common/msg.h"
#include "video/mp_image.h"
#include "video/out/gpu/hwdec.h"
#include "common.h"
struct priv {
struct mp_log *log;
struct mp_image_params params;
MMAL_COMPONENT_T *renderer;
bool renderer_enabled;
// for RAM input
MMAL_POOL_T *swpool;
struct mp_image *current_frame;
struct mp_rect src, dst;
int cur_window[4]; // raw user params
};
// 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 MMAL_FOURCC_T map_csp(enum pl_color_system csp)
{
switch (csp) {
case PL_COLOR_SYSTEM_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601;
case PL_COLOR_SYSTEM_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709;
case PL_COLOR_SYSTEM_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 ra_hwdec *hw)
{
struct priv *p = hw->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;
}
// check_window_only: assume params and dst/src rc are unchanged
static void update_overlay(struct ra_hwdec *hw, bool check_window_only)
{
struct priv *p = hw->priv;
MMAL_PORT_T *input = p->renderer->input[0];
struct mp_rect src = p->src;
struct mp_rect dst = p->dst;
int defs[4] = {0, 0, 0, 0};
int *z = ra_get_native_resource(hw->ra_ctx->ra, "MPV_RPI_WINDOW");
if (!z)
z = defs;
// As documented in the libmpv openglcb headers.
int display = z[0];
int layer = z[1];
int x = z[2];
int y = z[3];
if (check_window_only && memcmp(z, p->cur_window, sizeof(p->cur_window)) == 0)
return;
memcpy(p->cur_window, z, sizeof(p->cur_window));
int rotate[] = {MMAL_DISPLAY_ROT0,
MMAL_DISPLAY_ROT90,
MMAL_DISPLAY_ROT180,
MMAL_DISPLAY_ROT270};
int src_w = src.x1 - src.x0, src_h = src.y1 - src.y0,
dst_w = dst.x1 - dst.x0, dst_h = dst.y1 - dst.y0;
int p_x, p_y;
av_reduce(&p_x, &p_y, dst_w * src_h, src_w * dst_h, 16000);
MMAL_DISPLAYREGION_T dr = {
.hdr = { .id = MMAL_PARAMETER_DISPLAYREGION,
.size = sizeof(MMAL_DISPLAYREGION_T), },
.src_rect = { .x = src.x0, .y = src.y0,
.width = src_w, .height = src_h },
.dest_rect = { .x = dst.x0 + x, .y = dst.y0 + y,
.width = dst_w, .height = dst_h },
.layer = layer - 1, // under the GL layer
.display_num = display,
.pixel_x = p_x,
.pixel_y = p_y,
.transform = rotate[p->params.rotate / 90],
.fullscreen = 0,
.set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT |
MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM |
MMAL_DISPLAY_SET_PIXEL | MMAL_DISPLAY_SET_TRANSFORM |
MMAL_DISPLAY_SET_FULLSCREEN,
};
if (p->params.rotate % 180 == 90) {
MPSWAP(int, dr.src_rect.x, dr.src_rect.y);
MPSWAP(int, dr.src_rect.width, dr.src_rect.height);
}
if (mmal_port_parameter_set(input, &dr.hdr))
MP_WARN(p, "could not set video rectangle\n");
}
static int enable_renderer(struct ra_hwdec *hw)
{
struct priv *p = hw->priv;
MMAL_PORT_T *input = p->renderer->input[0];
struct mp_image_params *params = &p->params;
if (p->renderer_enabled)
return 0;
if (!params->imgfmt)
return -1;
bool opaque = params->imgfmt == IMGFMT_MMAL;
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){params->p_w, params->p_h};
input->format->es->video.color_space = map_csp(params->repr.sys);
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(hw, "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(hw, "Could not allocate buffer pool.\n");
return -1;
}
}
update_overlay(hw, false);
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(hw, "Failed to enable video renderer.\n");
return -1;
}
return 0;
}
static void free_mmal_buffer(void *arg)
{
MMAL_BUFFER_HEADER_T *buffer = arg;
mmal_buffer_header_release(buffer);
}
static struct mp_image *upload(struct ra_hwdec *hw, struct mp_image *hw_image)
{
struct priv *p = hw->priv;
MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue);
if (!buffer) {
MP_ERR(hw, "Can't allocate buffer.\n");
return NULL;
}
mmal_buffer_header_reset(buffer);
struct mp_image *new_ref = mp_image_new_custom_ref(NULL, buffer,
free_mmal_buffer);
if (!new_ref) {
mmal_buffer_header_release(buffer);
MP_ERR(hw, "Out of memory.\n");
return NULL;
}
mp_image_setfmt(new_ref, IMGFMT_MMAL);
new_ref->planes[3] = (void *)buffer;
struct mp_image dmpi = {0};
buffer->length = layout_buffer(&dmpi, buffer, &p->params);
mp_image_copy(&dmpi, hw_image);
return new_ref;
}
static int overlay_frame(struct ra_hwdec *hw, struct mp_image *hw_image,
struct mp_rect *src, struct mp_rect *dst, bool newframe)
{
struct priv *p = hw->priv;
if (hw_image && !mp_image_params_equal(&p->params, &hw_image->params)) {
p->params = hw_image->params;
disable_renderer(hw);
mp_image_unrefp(&p->current_frame);
if (enable_renderer(hw) < 0)
return -1;
}
if (hw_image && p->current_frame && !newframe) {
if (!mp_rect_equals(&p->src, src) ||mp_rect_equals(&p->dst, dst)) {
p->src = *src;
p->dst = *dst;
update_overlay(hw, false);
}
return 0; // don't reupload
}
mp_image_unrefp(&p->current_frame);
if (!hw_image) {
disable_renderer(hw);
return 0;
}
if (enable_renderer(hw) < 0)
return -1;
update_overlay(hw, true);
struct mp_image *mpi = NULL;
if (hw_image->imgfmt == IMGFMT_MMAL) {
mpi = mp_image_new_ref(hw_image);
} else {
mpi = upload(hw, hw_image);
}
if (!mpi) {
disable_renderer(hw);
return -1;
}
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(p->renderer->input[0], ref)) {
MP_ERR(hw, "could not queue picture!\n");
talloc_free(mpi);
return -1;
}
return 0;
}
static void destroy(struct ra_hwdec *hw)
{
struct priv *p = hw->priv;
disable_renderer(hw);
if (p->renderer)
mmal_component_release(p->renderer);
mmal_vc_deinit();
}
static int create(struct ra_hwdec *hw)
{
struct priv *p = hw->priv;
p->log = hw->log;
bcm_host_init();
if (mmal_vc_init()) {
MP_FATAL(hw, "Could not initialize MMAL.\n");
return -1;
}
if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer))
{
MP_FATAL(hw, "Could not create MMAL renderer.\n");
mmal_vc_deinit();
return -1;
}
return 0;
}
const struct ra_hwdec_driver ra_hwdec_rpi_overlay = {
.name = "rpi-overlay",
.priv_size = sizeof(struct priv),
.imgfmts = {IMGFMT_MMAL, IMGFMT_420P, 0},
.init = create,
.overlay_frame = overlay_frame,
.uninit = destroy,
};

View File

@ -63,7 +63,6 @@ extern const struct vo_driver video_out_sdl;
extern const struct vo_driver video_out_vaapi;
extern const struct vo_driver video_out_dmabuf_wayland;
extern const struct vo_driver video_out_wlshm;
extern const struct vo_driver video_out_rpi;
extern const struct vo_driver video_out_tct;
extern const struct vo_driver video_out_sixel;
extern const struct vo_driver video_out_kitty;
@ -110,9 +109,6 @@ static const struct vo_driver *const video_out_drivers[] =
#if HAVE_DRM
&video_out_drm,
#endif
#if HAVE_RPI_MMAL
&video_out_rpi,
#endif
#if HAVE_SIXEL
&video_out_sixel,
#endif

View File

@ -1,938 +0,0 @@
/*
* This file is part of mpv.
*
* mpv 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.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <assert.h>
#include <bcm_host.h>
#include <interface/mmal/mmal.h>
#include <interface/mmal/util/mmal_util.h>
#include <interface/mmal/util/mmal_default_components.h>
#include <interface/mmal/vc/mmal_vc_api.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <libavutil/rational.h>
#include "common/common.h"
#include "common/msg.h"
#include "opengl/common.h"
#include "options/m_config.h"
#include "osdep/timer.h"
#include "vo.h"
#include "win_state.h"
#include "video/mp_image.h"
#include "sub/osd.h"
#include "opengl/ra_gl.h"
#include "gpu/video.h"
struct mp_egl_rpi {
struct mp_log *log;
struct GL *gl;
struct ra *ra;
EGLDisplay egl_display;
EGLConfig egl_config;
EGLContext egl_context;
EGLSurface egl_surface;
// yep, the API keeps a pointer to it
EGL_DISPMANX_WINDOW_T egl_window;
};
struct priv {
DISPMANX_DISPLAY_HANDLE_T display;
DISPMANX_ELEMENT_HANDLE_T window;
DISPMANX_ELEMENT_HANDLE_T osd_overlay;
DISPMANX_UPDATE_HANDLE_T update;
uint32_t w, h;
uint32_t x, y;
double display_fps;
double osd_pts;
struct mp_osd_res osd_res;
struct m_config_cache *opts_cache;
struct mp_egl_rpi egl;
struct gl_video *gl_video;
struct mpgl_osd *osd;
MMAL_COMPONENT_T *renderer;
bool renderer_enabled;
bool display_synced, skip_osd;
struct mp_image *next_image;
// for RAM input
MMAL_POOL_T *swpool;
mp_mutex display_mutex;
mp_cond display_cond;
int64_t vsync_counter;
bool reload_display;
int background_layer;
int video_layer;
int osd_layer;
int display_nr;
int layer;
bool background;
bool enable_osd;
};
// Magic alignments (in pixels) expected by the MMAL internals.
#define ALIGN_W 32
#define ALIGN_H 16
static void recreate_renderer(struct vo *vo);
static void *get_proc_address(const GLubyte *name)
{
void *p = eglGetProcAddress(name);
// EGL 1.4 (supported by the RPI firmware) does not necessarily return
// function pointers for core functions.
if (!p) {
void *h = dlopen("/opt/vc/lib/libbrcmGLESv2.so", RTLD_LAZY);
if (h) {
p = dlsym(h, name);
dlclose(h);
}
}
return p;
}
static EGLConfig select_fb_config_egl(struct mp_egl_rpi *p)
{
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(p, "Could find EGL configuration!\n");
return NULL;
}
return config;
}
static void mp_egl_rpi_destroy(struct mp_egl_rpi *p)
{
if (p->egl_display) {
eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
}
if (p->egl_surface)
eglDestroySurface(p->egl_display, p->egl_surface);
if (p->egl_context)
eglDestroyContext(p->egl_display, p->egl_context);
p->egl_context = EGL_NO_CONTEXT;
eglReleaseThread();
p->egl_display = EGL_NO_DISPLAY;
talloc_free(p->gl);
p->gl = NULL;
}
static int mp_egl_rpi_init(struct mp_egl_rpi *p, DISPMANX_ELEMENT_HANDLE_T window,
int w, int h)
{
p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!eglInitialize(p->egl_display, NULL, NULL)) {
MP_FATAL(p, "EGL failed to initialize.\n");
goto fail;
}
eglBindAPI(EGL_OPENGL_ES_API);
EGLConfig config = select_fb_config_egl(p);
if (!config)
goto fail;
p->egl_window = (EGL_DISPMANX_WINDOW_T){
.element = 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(p, "Could not create EGL surface!\n");
goto fail;
}
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(p, "Could not create EGL context!\n");
goto fail;
}
eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
p->egl_context);
p->gl = talloc_zero(NULL, struct GL);
const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS);
mpgl_load_functions(p->gl, get_proc_address, exts, p->log);
if (!p->gl->version && !p->gl->es)
goto fail;
p->ra = ra_create_gl(p->gl, p->log);
if (!p->ra)
goto fail;
return 0;
fail:
mp_egl_rpi_destroy(p);
return -1;
}
// 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 update_osd(struct vo *vo)
{
struct priv *p = vo->priv;
if (!p->enable_osd)
return;
if (!gl_video_check_osd_change(p->gl_video, &p->osd_res, p->osd_pts)) {
p->skip_osd = true;
return;
}
MP_STATS(vo, "start rpi_osd");
struct vo_frame frame = {0};
struct ra_fbo target = {
.tex = ra_create_wrapped_fb(p->egl.ra, 0, p->osd_res.w, p->osd_res.h),
.flip = true,
};
gl_video_set_osd_pts(p->gl_video, p->osd_pts);
gl_video_render_frame(p->gl_video, &frame, &target, RENDER_FRAME_DEF);
ra_tex_free(p->egl.ra, &target.tex);
MP_STATS(vo, "stop rpi_osd");
}
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);
int rotate[] = {MMAL_DISPLAY_ROT0,
MMAL_DISPLAY_ROT90,
MMAL_DISPLAY_ROT180,
MMAL_DISPLAY_ROT270};
int src_w = src.x1 - src.x0, src_h = src.y1 - src.y0,
dst_w = dst.x1 - dst.x0, dst_h = dst.y1 - dst.y0;
int p_x, p_y;
av_reduce(&p_x, &p_y, dst_w * src_h, src_w * dst_h, 16000);
MMAL_DISPLAYREGION_T dr = {
.hdr = { .id = MMAL_PARAMETER_DISPLAYREGION,
.size = sizeof(MMAL_DISPLAYREGION_T), },
.src_rect = { .x = src.x0, .y = src.y0, .width = src_w, .height = src_h },
.dest_rect = { .x = dst.x0 + p->x, .y = dst.y0 + p->y,
.width = dst_w, .height = dst_h },
.layer = p->video_layer,
.display_num = p->display_nr,
.pixel_x = p_x,
.pixel_y = p_y,
.transform = rotate[vo->params ? vo->params->rotate / 90 : 0],
.fullscreen = vo->opts->fullscreen,
.set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT |
MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM |
MMAL_DISPLAY_SET_PIXEL | MMAL_DISPLAY_SET_TRANSFORM |
MMAL_DISPLAY_SET_FULLSCREEN,
};
if (vo->params && (vo->params->rotate % 180) == 90) {
MPSWAP(int, dr.src_rect.x, dr.src_rect.y);
MPSWAP(int, dr.src_rect.width, dr.src_rect.height);
}
if (mmal_port_parameter_set(input, &dr.hdr))
MP_WARN(vo, "could not set video rectangle\n");
if (p->gl_video)
gl_video_resize(p->gl_video, &src, &dst, &p->osd_res);
}
static void destroy_overlays(struct vo *vo)
{
struct priv *p = vo->priv;
if (p->window)
vc_dispmanx_element_remove(p->update, p->window);
p->window = 0;
gl_video_uninit(p->gl_video);
p->gl_video = NULL;
ra_free(&p->egl.ra);
mp_egl_rpi_destroy(&p->egl);
if (p->osd_overlay)
vc_dispmanx_element_remove(p->update, p->osd_overlay);
p->osd_overlay = 0;
}
static int update_display_size(struct vo *vo)
{
struct priv *p = vo->priv;
uint32_t n_w = 0, n_h = 0;
if (graphics_get_display_size(0, &n_w, &n_h) < 0) {
MP_FATAL(vo, "Could not get display size.\n");
return -1;
}
if (p->w == n_w && p->h == n_h)
return 0;
p->w = n_w;
p->h = n_h;
MP_VERBOSE(vo, "Display size: %dx%d\n", p->w, p->h);
return 0;
}
static int create_overlays(struct vo *vo)
{
struct priv *p = vo->priv;
destroy_overlays(vo);
if (!p->display)
return -1;
if (vo->opts->fullscreen && p->background) {
// Use the whole screen.
VC_RECT_T dst = {.width = p->w, .height = p->h};
VC_RECT_T src = {.width = 1 << 16, .height = 1 << 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");
return -1;
}
}
if (p->enable_osd) {
VC_RECT_T dst = {.x = p->x, .y = p->y,
.width = p->osd_res.w, .height = p->osd_res.h};
VC_RECT_T src = {.width = p->osd_res.w << 16, .height = p->osd_res.h << 16};
VC_DISPMANX_ALPHA_T alpha = {
.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE,
.opacity = 0xFF,
};
p->osd_overlay = vc_dispmanx_element_add(p->update, p->display,
p->osd_layer,
&dst, 0, &src,
DISPMANX_PROTECTION_NONE,
&alpha, 0, 0);
if (!p->osd_overlay) {
MP_FATAL(vo, "Could not add DISPMANX element.\n");
return -1;
}
if (mp_egl_rpi_init(&p->egl, p->osd_overlay,
p->osd_res.w, p->osd_res.h) < 0)
{
MP_FATAL(vo, "EGL/GLES initialization for OSD renderer failed.\n");
return -1;
}
p->gl_video = gl_video_init(p->egl.ra, vo->log, vo->global);
gl_video_set_clear_color(p->gl_video, (struct m_color){.a = 0});
gl_video_set_osd_source(p->gl_video, vo->osd);
}
p->display_fps = 0;
TV_GET_STATE_RESP_T tvstate;
TV_DISPLAY_STATE_T tvstate_disp;
if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) {
if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) {
p->display_fps = tvstate_disp.display.hdmi.frame_rate;
HDMI_PROPERTY_PARAM_T param = {
.property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE,
};
if (!vc_tv_hdmi_get_property(&param) &&
param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC)
p->display_fps = p->display_fps / 1.001;
} else {
p->display_fps = tvstate_disp.display.sdtv.frame_rate;
}
}
resize(vo);
vo_event(vo, VO_EVENT_WIN_STATE);
vc_dispmanx_update_submit_sync(p->update);
p->update = vc_dispmanx_update_start(10);
return 0;
}
static int set_geometry(struct vo *vo)
{
struct priv *p = vo->priv;
if (vo->opts->fullscreen) {
vo->dwidth = p->w;
vo->dheight = p->h;
p->x = p->y = 0;
} else {
struct vo_win_geometry geo;
struct mp_rect screenrc = {0, 0, p->w, p->h};
vo_calc_window_geometry(vo, &screenrc, &geo);
vo_apply_window_geometry(vo, &geo);
p->x = geo.win.x0;
p->y = geo.win.y0;
}
resize(vo);
if (create_overlays(vo) < 0)
return -1;
return 0;
}
static void wait_next_vsync(struct vo *vo)
{
struct priv *p = vo->priv;
mp_mutex_lock(&p->display_mutex);
int64_t end = mp_time_ns() + MP_TIME_MS_TO_NS(50);
int64_t old = p->vsync_counter;
while (old == p->vsync_counter && !p->reload_display) {
if (mp_cond_timedwait_until(&p->display_cond, &p->display_mutex, end))
break;
}
mp_mutex_unlock(&p->display_mutex);
}
static void flip_page(struct vo *vo)
{
struct priv *p = vo->priv;
if (!p->renderer_enabled)
return;
struct mp_image *mpi = p->next_image;
p->next_image = NULL;
// For OSD
if (!p->skip_osd && p->egl.gl)
eglSwapBuffers(p->egl.egl_display, p->egl.egl_surface);
p->skip_osd = false;
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);
}
}
if (p->display_synced)
wait_next_vsync(vo);
}
static void free_mmal_buffer(void *arg)
{
MMAL_BUFFER_HEADER_T *buffer = arg;
mmal_buffer_header_release(buffer);
}
static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct priv *p = vo->priv;
if (!p->renderer_enabled)
return;
mp_image_t *mpi = NULL;
if (!frame->redraw && !frame->repeat)
mpi = mp_image_new_ref(frame->current);
talloc_free(p->next_image);
p->next_image = NULL;
if (mpi)
p->osd_pts = mpi->pts;
// Redraw only if the OSD has meaningfully changed, which we assume it
// hasn't when a frame is merely repeated for display sync.
p->skip_osd = !frame->redraw && frame->repeat;
if (!p->skip_osd && p->egl.gl)
update_osd(vo);
p->display_synced = frame->display_synced;
if (mpi && mpi->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(NULL, 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 pl_color_system csp)
{
switch (csp) {
case PL_COLOR_SYSTEM_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601;
case PL_COLOR_SYSTEM_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709;
case PL_COLOR_SYSTEM_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)
{
struct priv *p = vo->priv;
MMAL_PORT_T *input = p->renderer->input[0];
bool opaque = params->imgfmt == IMGFMT_MMAL;
if (!p->display)
return -1;
disable_renderer(vo);
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){params->p_w, params->p_h};
input->format->es->video.color_space = map_csp(params->repr.sys);
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;
}
}
if (set_geometry(vo) < 0)
return -1;
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;
}
resize(vo);
return 0;
}
static struct mp_image *take_screenshot(struct vo *vo)
{
struct priv *p = vo->priv;
if (!p->display)
return NULL;
struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, 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 void set_fullscreen(struct vo *vo) {
struct priv *p = vo->priv;
if (p->renderer_enabled)
set_geometry(vo);
vo->want_redraw = true;
}
static int control(struct vo *vo, uint32_t request, void *data)
{
struct priv *p = vo->priv;
switch (request) {
case VOCTRL_VO_OPTS_CHANGED: {
void *opt;
while (m_config_cache_get_next_changed(p->opts_cache, &opt)) {
struct mp_vo_opts *opts = p->opts_cache->opts;
if (&opts->fullscreen == opt)
set_fullscreen(vo);
}
return VO_TRUE;
}
case VOCTRL_SET_PANSCAN:
if (p->renderer_enabled)
resize(vo);
vo->want_redraw = true;
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;
case VOCTRL_CHECK_EVENTS: {
mp_mutex_lock(&p->display_mutex);
bool reload_required = p->reload_display;
p->reload_display = false;
mp_mutex_unlock(&p->display_mutex);
if (reload_required)
recreate_renderer(vo);
return VO_TRUE;
}
case VOCTRL_GET_DISPLAY_FPS:
*(double *)data = p->display_fps;
return VO_TRUE;
case VOCTRL_GET_DISPLAY_RES:
((int *)data)[0] = p->w;
((int *)data)[1] = p->h;
return VO_TRUE;
}
return VO_NOTIMPL;
}
static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1,
uint32_t param2)
{
struct vo *vo = callback_data;
struct priv *p = vo->priv;
mp_mutex_lock(&p->display_mutex);
p->reload_display = true;
mp_cond_signal(&p->display_cond);
mp_mutex_unlock(&p->display_mutex);
vo_wakeup(vo);
}
static void vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void *arg)
{
struct vo *vo = arg;
struct priv *p = vo->priv;
mp_mutex_lock(&p->display_mutex);
p->vsync_counter += 1;
mp_cond_signal(&p->display_cond);
mp_mutex_unlock(&p->display_mutex);
}
static void destroy_dispmanx(struct vo *vo)
{
struct priv *p = vo->priv;
disable_renderer(vo);
destroy_overlays(vo);
if (p->update)
vc_dispmanx_update_submit_sync(p->update);
p->update = 0;
if (p->display) {
vc_dispmanx_vsync_callback(p->display, NULL, NULL);
vc_dispmanx_display_close(p->display);
}
p->display = 0;
}
static int recreate_dispmanx(struct vo *vo)
{
struct priv *p = vo->priv;
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");
if (p->display)
vc_dispmanx_display_close(p->display);
p->display = 0;
p->update = 0;
return -1;
}
update_display_size(vo);
vc_dispmanx_vsync_callback(p->display, vsync_callback, vo);
return 0;
}
static void recreate_renderer(struct vo *vo)
{
MP_WARN(vo, "Recreating renderer after display change.\n");
destroy_dispmanx(vo);
recreate_dispmanx(vo);
if (vo->params) {
if (reconfig(vo, vo->params) < 0)
MP_FATAL(vo, "Recreation failed.\n");
}
}
static void uninit(struct vo *vo)
{
struct priv *p = vo->priv;
vc_tv_unregister_callback_full(tv_callback, vo);
talloc_free(p->next_image);
destroy_dispmanx(vo);
if (p->renderer)
mmal_component_release(p->renderer);
mmal_vc_deinit();
mp_cond_destroy(&p->display_cond);
mp_mutex_destroy(&p->display_mutex);
}
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;
p->egl.log = vo->log;
bcm_host_init();
if (mmal_vc_init()) {
MP_FATAL(vo, "Could not initialize MMAL.\n");
return -1;
}
mp_mutex_init(&p->display_mutex);
mp_cond_init(&p->display_cond);
p->opts_cache = m_config_cache_alloc(p, vo->global, &vo_sub_opts);
if (recreate_dispmanx(vo) < 0)
goto fail;
if (update_display_size(vo) < 0)
goto fail;
if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer))
{
MP_FATAL(vo, "Could not create MMAL renderer.\n");
goto fail;
}
vc_tv_register_callback(tv_callback, vo);
return 0;
fail:
uninit(vo);
return -1;
}
#define OPT_BASE_STRUCT struct priv
static const struct m_option options[] = {
{"display", OPT_INT(display_nr)},
{"layer", OPT_INT(layer), OPTDEF_INT(-10)},
{"background", OPT_BOOL(background)},
{"osd", OPT_BOOL(enable_osd), OPTDEF_INT(1)},
{0},
};
const struct vo_driver video_out_rpi = {
.description = "Raspberry Pi (MMAL)",
.name = "rpi",
.caps = VO_CAP_ROTATE90,
.preinit = preinit,
.query_format = query_format,
.reconfig = reconfig,
.control = control,
.draw_frame = draw_frame,
.flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct priv),
.options = options,
.options_prefix = "rpi",
};