mirror of https://github.com/mpv-player/mpv
399 lines
11 KiB
C
399 lines
11 KiB
C
|
/*
|
||
|
* 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 "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;
|
||
|
|
||
|
int w, h;
|
||
|
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 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 gl_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 gl_hwdec *hw, bool check_window_only)
|
||
|
{
|
||
|
struct priv *p = hw->priv;
|
||
|
GL *gl = hw->gl;
|
||
|
MMAL_PORT_T *input = p->renderer->input[0];
|
||
|
struct mp_rect src = p->src;
|
||
|
struct mp_rect dst = p->dst;
|
||
|
|
||
|
if (!p->w || !p->h)
|
||
|
return;
|
||
|
|
||
|
int defs[4] = {0, 0, 0, 0};
|
||
|
int *z =
|
||
|
gl->MPGetNativeDisplay ? gl->MPGetNativeDisplay("MPV_RPI_WINDOW") : 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 gl_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->color.space);
|
||
|
|
||
|
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 overlay_adjust(struct gl_hwdec *hw, int w, int h,
|
||
|
struct mp_rect *src, struct mp_rect *dst)
|
||
|
{
|
||
|
struct priv *p = hw->priv;
|
||
|
|
||
|
p->w = w;
|
||
|
p->h = h;
|
||
|
p->src = *src;
|
||
|
p->dst = *dst;
|
||
|
|
||
|
update_overlay(hw, false);
|
||
|
}
|
||
|
|
||
|
static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
|
||
|
{
|
||
|
struct priv *p = hw->priv;
|
||
|
|
||
|
p->params = *params;
|
||
|
|
||
|
*params = (struct mp_image_params){0};
|
||
|
|
||
|
disable_renderer(hw);
|
||
|
|
||
|
if (enable_renderer(hw) < 0)
|
||
|
return -1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void free_mmal_buffer(void *arg)
|
||
|
{
|
||
|
MMAL_BUFFER_HEADER_T *buffer = arg;
|
||
|
mmal_buffer_header_release(buffer);
|
||
|
}
|
||
|
|
||
|
// currently dead code; for a force-overlay mode
|
||
|
static struct mp_image *upload(struct gl_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 gl_hwdec *hw, struct mp_image *hw_image)
|
||
|
{
|
||
|
struct priv *p = hw->priv;
|
||
|
|
||
|
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 = mp_image_new_ref(hw_image);
|
||
|
if (hw_image->imgfmt != IMGFMT_MMAL)
|
||
|
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 gl_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 gl_hwdec *hw)
|
||
|
{
|
||
|
struct priv *p = talloc_zero(hw, struct priv);
|
||
|
hw->priv = p;
|
||
|
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 gl_hwdec_driver gl_hwdec_rpi_overlay = {
|
||
|
.name = "rpi-overlay",
|
||
|
.api = HWDEC_RPI,
|
||
|
.imgfmt = IMGFMT_MMAL,
|
||
|
.create = create,
|
||
|
.reinit = reinit,
|
||
|
.overlay_frame = overlay_frame,
|
||
|
.overlay_adjust = overlay_adjust,
|
||
|
.destroy = destroy,
|
||
|
};
|