784 lines
22 KiB
C
784 lines
22 KiB
C
/*
|
|
* Copyright (c) 2020 John Cox for Raspberry Pi Trading
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg 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.
|
|
*
|
|
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
|
|
// *** This module is a work in progress and its utility is strictly
|
|
// limited to testing.
|
|
// Amongst other issues it doesn't wait for the pic to be displayed before
|
|
// returning the buffer so flikering does occur.
|
|
|
|
#include <epoxy/gl.h>
|
|
#include <epoxy/egl.h>
|
|
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/avassert.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/imgutils.h"
|
|
#include "libavutil/hwcontext.h"
|
|
#include "libavutil/hwcontext_drm.h"
|
|
#include "libavformat/mux.h"
|
|
#include "avdevice.h"
|
|
|
|
#include "pthread.h"
|
|
#include <semaphore.h>
|
|
#include <stdatomic.h>
|
|
#include <unistd.h>
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
|
|
#include "libavutil/rpi_sand_fns.h"
|
|
|
|
#define TRACE_ALL 0
|
|
|
|
struct egl_setup {
|
|
int conId;
|
|
|
|
Display *dpy;
|
|
EGLDisplay egl_dpy;
|
|
EGLContext ctx;
|
|
EGLSurface surf;
|
|
Window win;
|
|
|
|
uint32_t crtcId;
|
|
int crtcIdx;
|
|
uint32_t planeId;
|
|
struct {
|
|
int x, y, width, height;
|
|
} compose;
|
|
};
|
|
|
|
typedef struct egl_aux_s {
|
|
int fd;
|
|
GLuint texture;
|
|
|
|
} egl_aux_t;
|
|
|
|
typedef struct egl_display_env_s {
|
|
AVClass *class;
|
|
|
|
struct egl_setup setup;
|
|
enum AVPixelFormat avfmt;
|
|
|
|
int show_all;
|
|
int window_width, window_height;
|
|
int window_x, window_y;
|
|
int fullscreen;
|
|
|
|
egl_aux_t aux[32];
|
|
|
|
pthread_t q_thread;
|
|
pthread_mutex_t q_lock;
|
|
sem_t display_start_sem;
|
|
sem_t q_sem;
|
|
int q_terminate;
|
|
AVFrame *q_this;
|
|
AVFrame *q_next;
|
|
|
|
} egl_display_env_t;
|
|
|
|
|
|
/**
|
|
* Remove window border/decorations.
|
|
*/
|
|
static void
|
|
no_border(Display *dpy, Window w)
|
|
{
|
|
static const unsigned MWM_HINTS_DECORATIONS = (1 << 1);
|
|
static const int PROP_MOTIF_WM_HINTS_ELEMENTS = 5;
|
|
|
|
typedef struct {
|
|
unsigned long flags;
|
|
unsigned long functions;
|
|
unsigned long decorations;
|
|
long inputMode;
|
|
unsigned long status;
|
|
} PropMotifWmHints;
|
|
|
|
PropMotifWmHints motif_hints;
|
|
Atom prop, proptype;
|
|
unsigned long flags = 0;
|
|
|
|
/* setup the property */
|
|
motif_hints.flags = MWM_HINTS_DECORATIONS;
|
|
motif_hints.decorations = flags;
|
|
|
|
/* get the atom for the property */
|
|
prop = XInternAtom(dpy, "_MOTIF_WM_HINTS", True);
|
|
if (!prop) {
|
|
/* something went wrong! */
|
|
return;
|
|
}
|
|
|
|
/* not sure this is correct, seems to work, XA_WM_HINTS didn't work */
|
|
proptype = prop;
|
|
|
|
XChangeProperty(dpy, w, /* display, window */
|
|
prop, proptype, /* property, type */
|
|
32, /* format: 32-bit datums */
|
|
PropModeReplace, /* mode */
|
|
(unsigned char *)&motif_hints, /* data */
|
|
PROP_MOTIF_WM_HINTS_ELEMENTS /* nelements */
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* Create an RGB, double-buffered window.
|
|
* Return the window and context handles.
|
|
*/
|
|
static int
|
|
make_window(struct AVFormatContext *const s,
|
|
egl_display_env_t *const de,
|
|
Display *dpy, EGLDisplay egl_dpy, const char *name,
|
|
Window *winRet, EGLContext *ctxRet, EGLSurface *surfRet)
|
|
{
|
|
int scrnum = DefaultScreen(dpy);
|
|
XSetWindowAttributes attr;
|
|
unsigned long mask;
|
|
Window root = RootWindow(dpy, scrnum);
|
|
Window win;
|
|
EGLContext ctx;
|
|
const int fullscreen = de->fullscreen;
|
|
EGLConfig config;
|
|
int x = de->window_x;
|
|
int y = de->window_y;
|
|
int width = de->window_width ? de->window_width : 1280;
|
|
int height = de->window_height ? de->window_height : 720;
|
|
|
|
|
|
if (fullscreen) {
|
|
int scrnum = DefaultScreen(dpy);
|
|
|
|
x = 0; y = 0;
|
|
width = DisplayWidth(dpy, scrnum);
|
|
height = DisplayHeight(dpy, scrnum);
|
|
}
|
|
|
|
{
|
|
EGLint num_configs;
|
|
static const EGLint attribs[] = {
|
|
EGL_RED_SIZE, 1,
|
|
EGL_GREEN_SIZE, 1,
|
|
EGL_BLUE_SIZE, 1,
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
|
EGL_NONE
|
|
};
|
|
|
|
if (!eglChooseConfig(egl_dpy, attribs, &config, 1, &num_configs)) {
|
|
av_log(s, AV_LOG_ERROR, "Error: couldn't get an EGL visual config\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
{
|
|
EGLint vid;
|
|
if (!eglGetConfigAttrib(egl_dpy, config, EGL_NATIVE_VISUAL_ID, &vid)) {
|
|
av_log(s, AV_LOG_ERROR, "Error: eglGetConfigAttrib() failed\n");
|
|
return -1;
|
|
}
|
|
|
|
{
|
|
XVisualInfo visTemplate = {
|
|
.visualid = vid,
|
|
};
|
|
int num_visuals;
|
|
XVisualInfo *visinfo = XGetVisualInfo(dpy, VisualIDMask,
|
|
&visTemplate, &num_visuals);
|
|
|
|
/* window attributes */
|
|
attr.background_pixel = 0;
|
|
attr.border_pixel = 0;
|
|
attr.colormap = XCreateColormap(dpy, root, visinfo->visual, AllocNone);
|
|
attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
|
|
/* XXX this is a bad way to get a borderless window! */
|
|
mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
|
|
|
|
win = XCreateWindow(dpy, root, x, y, width, height,
|
|
0, visinfo->depth, InputOutput,
|
|
visinfo->visual, mask, &attr);
|
|
XFree(visinfo);
|
|
}
|
|
}
|
|
|
|
if (fullscreen)
|
|
no_border(dpy, win);
|
|
|
|
/* set hints and properties */
|
|
{
|
|
XSizeHints sizehints;
|
|
sizehints.x = x;
|
|
sizehints.y = y;
|
|
sizehints.width = width;
|
|
sizehints.height = height;
|
|
sizehints.flags = USSize | USPosition;
|
|
XSetNormalHints(dpy, win, &sizehints);
|
|
XSetStandardProperties(dpy, win, name, name,
|
|
None, (char **)NULL, 0, &sizehints);
|
|
}
|
|
|
|
eglBindAPI(EGL_OPENGL_ES_API);
|
|
|
|
{
|
|
static const EGLint ctx_attribs[] = {
|
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
EGL_NONE
|
|
};
|
|
ctx = eglCreateContext(egl_dpy, config, EGL_NO_CONTEXT, ctx_attribs);
|
|
if (!ctx) {
|
|
av_log(s, AV_LOG_ERROR, "Error: eglCreateContext failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
XMapWindow(dpy, win);
|
|
|
|
{
|
|
EGLSurface surf = eglCreateWindowSurface(egl_dpy, config, (EGLNativeWindowType)win, NULL);
|
|
if (!surf) {
|
|
av_log(s, AV_LOG_ERROR, "Error: eglCreateWindowSurface failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!eglMakeCurrent(egl_dpy, surf, surf, ctx)) {
|
|
av_log(s, AV_LOG_ERROR, "Error: eglCreateContext failed\n");
|
|
return -1;
|
|
}
|
|
|
|
*winRet = win;
|
|
*ctxRet = ctx;
|
|
*surfRet = surf;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static GLint
|
|
compile_shader(struct AVFormatContext *const avctx, GLenum target, const char *source)
|
|
{
|
|
GLuint s = glCreateShader(target);
|
|
|
|
if (s == 0) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to create shader\n");
|
|
return 0;
|
|
}
|
|
|
|
glShaderSource(s, 1, (const GLchar **)&source, NULL);
|
|
glCompileShader(s);
|
|
|
|
{
|
|
GLint ok;
|
|
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
|
|
|
|
if (!ok) {
|
|
GLchar *info;
|
|
GLint size;
|
|
|
|
glGetShaderiv(s, GL_INFO_LOG_LENGTH, &size);
|
|
info = malloc(size);
|
|
|
|
glGetShaderInfoLog(s, size, NULL, info);
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to compile shader: %ssource:\n%s\n", info, source);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static GLuint link_program(struct AVFormatContext *const s, GLint vs, GLint fs)
|
|
{
|
|
GLuint prog = glCreateProgram();
|
|
|
|
if (prog == 0) {
|
|
av_log(s, AV_LOG_ERROR, "Failed to create program\n");
|
|
return 0;
|
|
}
|
|
|
|
glAttachShader(prog, vs);
|
|
glAttachShader(prog, fs);
|
|
glLinkProgram(prog);
|
|
|
|
{
|
|
GLint ok;
|
|
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
|
|
if (!ok) {
|
|
/* Some drivers return a size of 1 for an empty log. This is the size
|
|
* of a log that contains only a terminating NUL character.
|
|
*/
|
|
GLint size;
|
|
GLchar *info = NULL;
|
|
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &size);
|
|
if (size > 1) {
|
|
info = malloc(size);
|
|
glGetProgramInfoLog(prog, size, NULL, info);
|
|
}
|
|
|
|
av_log(s, AV_LOG_ERROR, "Failed to link: %s\n",
|
|
(info != NULL) ? info : "<empty log>");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return prog;
|
|
}
|
|
|
|
static int
|
|
gl_setup(struct AVFormatContext *const s)
|
|
{
|
|
const char *vs =
|
|
"attribute vec4 pos;\n"
|
|
"varying vec2 texcoord;\n"
|
|
"\n"
|
|
"void main() {\n"
|
|
" gl_Position = pos;\n"
|
|
" texcoord.x = (pos.x + 1.0) / 2.0;\n"
|
|
" texcoord.y = (-pos.y + 1.0) / 2.0;\n"
|
|
"}\n";
|
|
const char *fs =
|
|
"#extension GL_OES_EGL_image_external : enable\n"
|
|
"precision mediump float;\n"
|
|
"uniform samplerExternalOES s;\n"
|
|
"varying vec2 texcoord;\n"
|
|
"void main() {\n"
|
|
" gl_FragColor = texture2D(s, texcoord);\n"
|
|
"}\n";
|
|
|
|
GLuint vs_s;
|
|
GLuint fs_s;
|
|
GLuint prog;
|
|
|
|
if (!(vs_s = compile_shader(s, GL_VERTEX_SHADER, vs)) ||
|
|
!(fs_s = compile_shader(s, GL_FRAGMENT_SHADER, fs)) ||
|
|
!(prog = link_program(s, vs_s, fs_s)))
|
|
return -1;
|
|
|
|
glUseProgram(prog);
|
|
|
|
{
|
|
static const float verts[] = {
|
|
-1, -1,
|
|
1, -1,
|
|
1, 1,
|
|
-1, 1,
|
|
};
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts);
|
|
}
|
|
|
|
glEnableVertexAttribArray(0);
|
|
return 0;
|
|
}
|
|
|
|
static int egl_vout_write_trailer(AVFormatContext *s)
|
|
{
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, "%s\n", __func__);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int egl_vout_write_header(AVFormatContext *s)
|
|
{
|
|
const AVCodecParameters *const par = s->streams[0]->codecpar;
|
|
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, "%s\n", __func__);
|
|
#endif
|
|
if (s->nb_streams > 1
|
|
|| par->codec_type != AVMEDIA_TYPE_VIDEO
|
|
|| par->codec_id != AV_CODEC_ID_WRAPPED_AVFRAME) {
|
|
av_log(s, AV_LOG_ERROR, "Only supports one wrapped avframe stream\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int do_display(AVFormatContext *const s, egl_display_env_t *const de, AVFrame *const frame)
|
|
{
|
|
const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)frame->data[0];
|
|
egl_aux_t *da = NULL;
|
|
unsigned int i;
|
|
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, "<<< %s\n", __func__);
|
|
#endif
|
|
|
|
for (i = 0; i != 32; ++i) {
|
|
if (de->aux[i].fd == -1 || de->aux[i].fd == desc->objects[0].fd) {
|
|
da = de->aux + i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (da == NULL) {
|
|
av_log(s, AV_LOG_INFO, "%s: Out of handles\n", __func__);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
if (da->texture == 0) {
|
|
EGLint attribs[50];
|
|
EGLint *a = attribs;
|
|
int i, j;
|
|
static const EGLint anames[] = {
|
|
EGL_DMA_BUF_PLANE0_FD_EXT,
|
|
EGL_DMA_BUF_PLANE0_OFFSET_EXT,
|
|
EGL_DMA_BUF_PLANE0_PITCH_EXT,
|
|
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
|
|
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
|
|
EGL_DMA_BUF_PLANE1_FD_EXT,
|
|
EGL_DMA_BUF_PLANE1_OFFSET_EXT,
|
|
EGL_DMA_BUF_PLANE1_PITCH_EXT,
|
|
EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
|
|
EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
|
|
EGL_DMA_BUF_PLANE2_FD_EXT,
|
|
EGL_DMA_BUF_PLANE2_OFFSET_EXT,
|
|
EGL_DMA_BUF_PLANE2_PITCH_EXT,
|
|
EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
|
|
EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT,
|
|
};
|
|
const EGLint *b = anames;
|
|
|
|
*a++ = EGL_WIDTH;
|
|
*a++ = av_frame_cropped_width(frame);
|
|
*a++ = EGL_HEIGHT;
|
|
*a++ = av_frame_cropped_height(frame);
|
|
*a++ = EGL_LINUX_DRM_FOURCC_EXT;
|
|
*a++ = desc->layers[0].format;
|
|
|
|
for (i = 0; i < desc->nb_layers; ++i) {
|
|
for (j = 0; j < desc->layers[i].nb_planes; ++j) {
|
|
const AVDRMPlaneDescriptor *const p = desc->layers[i].planes + j;
|
|
const AVDRMObjectDescriptor *const obj = desc->objects + p->object_index;
|
|
*a++ = *b++;
|
|
*a++ = obj->fd;
|
|
*a++ = *b++;
|
|
*a++ = p->offset;
|
|
*a++ = *b++;
|
|
*a++ = p->pitch;
|
|
if (obj->format_modifier == 0) {
|
|
b += 2;
|
|
}
|
|
else {
|
|
*a++ = *b++;
|
|
*a++ = (EGLint)(obj->format_modifier & 0xFFFFFFFF);
|
|
*a++ = *b++;
|
|
*a++ = (EGLint)(obj->format_modifier >> 32);
|
|
}
|
|
}
|
|
}
|
|
|
|
*a = EGL_NONE;
|
|
|
|
#if TRACE_ALL
|
|
for (a = attribs, i = 0; *a != EGL_NONE; a += 2, ++i) {
|
|
av_log(s, AV_LOG_INFO, "[%2d] %4x: %d\n", i, a[0], a[1]);
|
|
}
|
|
#endif
|
|
{
|
|
const EGLImage image = eglCreateImageKHR(de->setup.egl_dpy,
|
|
EGL_NO_CONTEXT,
|
|
EGL_LINUX_DMA_BUF_EXT,
|
|
NULL, attribs);
|
|
if (!image) {
|
|
av_log(s, AV_LOG_ERROR, "Failed to import fd %d\n", desc->objects[0].fd);
|
|
return -1;
|
|
}
|
|
|
|
glGenTextures(1, &da->texture);
|
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, da->texture);
|
|
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
|
|
|
|
eglDestroyImageKHR(de->setup.egl_dpy, image);
|
|
}
|
|
|
|
da->fd = desc->objects[0].fd;
|
|
}
|
|
|
|
glClearColor(0.5, 0.5, 0.5, 0.5);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, da->texture);
|
|
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
|
eglSwapBuffers(de->setup.egl_dpy, de->setup.surf);
|
|
|
|
glDeleteTextures(1, &da->texture);
|
|
da->texture = 0;
|
|
da->fd = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void* display_thread(void *v)
|
|
{
|
|
AVFormatContext *const s = v;
|
|
egl_display_env_t *const de = s->priv_data;
|
|
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, "<<< %s\n", __func__);
|
|
#endif
|
|
{
|
|
EGLint egl_major, egl_minor;
|
|
|
|
de->setup.dpy = XOpenDisplay(NULL);
|
|
if (!de->setup.dpy) {
|
|
av_log(s, AV_LOG_ERROR, "Couldn't open X display\n");
|
|
goto fail;
|
|
}
|
|
|
|
de->setup.egl_dpy = eglGetDisplay(de->setup.dpy);
|
|
if (!de->setup.egl_dpy) {
|
|
av_log(s, AV_LOG_ERROR, "eglGetDisplay() failed\n");
|
|
goto fail;
|
|
}
|
|
|
|
if (!eglInitialize(de->setup.egl_dpy, &egl_major, &egl_minor)) {
|
|
av_log(s, AV_LOG_ERROR, "Error: eglInitialize() failed\n");
|
|
goto fail;
|
|
}
|
|
|
|
av_log(s, AV_LOG_INFO, "EGL version %d.%d\n", egl_major, egl_minor);
|
|
|
|
if (!epoxy_has_egl_extension(de->setup.egl_dpy, "EGL_KHR_image_base")) {
|
|
av_log(s, AV_LOG_ERROR, "Missing EGL KHR image extension\n");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (!de->window_width || !de->window_height) {
|
|
de->window_width = 1280;
|
|
de->window_height = 720;
|
|
}
|
|
if (make_window(s, de, de->setup.dpy, de->setup.egl_dpy, "ffmpeg-vout",
|
|
&de->setup.win, &de->setup.ctx, &de->setup.surf)) {
|
|
av_log(s, AV_LOG_ERROR, "%s: make_window failed\n", __func__);
|
|
goto fail;
|
|
}
|
|
|
|
if (gl_setup(s)) {
|
|
av_log(s, AV_LOG_ERROR, "%s: gl_setup failed\n", __func__);
|
|
goto fail;
|
|
}
|
|
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, "--- %s: Start done\n", __func__);
|
|
#endif
|
|
sem_post(&de->display_start_sem);
|
|
|
|
for (;;) {
|
|
AVFrame *frame;
|
|
|
|
while (sem_wait(&de->q_sem) != 0) {
|
|
av_assert0(errno == EINTR);
|
|
}
|
|
|
|
if (de->q_terminate)
|
|
break;
|
|
|
|
pthread_mutex_lock(&de->q_lock);
|
|
frame = de->q_next;
|
|
de->q_next = NULL;
|
|
pthread_mutex_unlock(&de->q_lock);
|
|
|
|
do_display(s, de, frame);
|
|
|
|
av_frame_free(&de->q_this);
|
|
de->q_this = frame;
|
|
}
|
|
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, ">>> %s\n", __func__);
|
|
#endif
|
|
|
|
return NULL;
|
|
|
|
fail:
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, ">>> %s: FAIL\n", __func__);
|
|
#endif
|
|
de->q_terminate = 1;
|
|
sem_post(&de->display_start_sem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int egl_vout_write_packet(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
const AVFrame *const src_frame = (AVFrame *)pkt->data;
|
|
AVFrame *frame;
|
|
egl_display_env_t *const de = s->priv_data;
|
|
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, "%s\n", __func__);
|
|
#endif
|
|
|
|
if (src_frame->format == AV_PIX_FMT_DRM_PRIME) {
|
|
frame = av_frame_alloc();
|
|
av_frame_ref(frame, src_frame);
|
|
}
|
|
else if (src_frame->format == AV_PIX_FMT_VAAPI) {
|
|
frame = av_frame_alloc();
|
|
frame->format = AV_PIX_FMT_DRM_PRIME;
|
|
if (av_hwframe_map(frame, src_frame, 0) != 0) {
|
|
av_log(s, AV_LOG_WARNING, "Failed to map frame (format=%d) to DRM_PRiME\n", src_frame->format);
|
|
av_frame_free(&frame);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
}
|
|
else {
|
|
av_log(s, AV_LOG_WARNING, "Frame (format=%d) not DRM_PRiME\n", src_frame->format);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
// Really hacky sync
|
|
while (de->show_all && de->q_next) {
|
|
usleep(3000);
|
|
}
|
|
|
|
pthread_mutex_lock(&de->q_lock);
|
|
{
|
|
AVFrame *const t = de->q_next;
|
|
de->q_next = frame;
|
|
frame = t;
|
|
}
|
|
pthread_mutex_unlock(&de->q_lock);
|
|
|
|
if (frame == NULL)
|
|
sem_post(&de->q_sem);
|
|
else
|
|
av_frame_free(&frame);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int egl_vout_write_frame(AVFormatContext *s, int stream_index, AVFrame **ppframe,
|
|
unsigned flags)
|
|
{
|
|
av_log(s, AV_LOG_ERROR, "%s: NIF: idx=%d, flags=%#x\n", __func__, stream_index, flags);
|
|
return AVERROR_PATCHWELCOME;
|
|
}
|
|
|
|
static int egl_vout_control_message(AVFormatContext *s, int type, void *data, size_t data_size)
|
|
{
|
|
#if TRACE_ALL
|
|
av_log(s, AV_LOG_INFO, "%s: %d\n", __func__, type);
|
|
#endif
|
|
switch (type) {
|
|
case AV_APP_TO_DEV_WINDOW_REPAINT:
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
return AVERROR(ENOSYS);
|
|
}
|
|
|
|
// deinit is called if init fails so no need to clean up explicity here
|
|
static int egl_vout_init(struct AVFormatContext *s)
|
|
{
|
|
egl_display_env_t *const de = s->priv_data;
|
|
unsigned int i;
|
|
|
|
av_log(s, AV_LOG_DEBUG, "<<< %s\n", __func__);
|
|
|
|
de->setup = (struct egl_setup) { 0 };
|
|
|
|
for (i = 0; i != 32; ++i) {
|
|
de->aux[i].fd = -1;
|
|
}
|
|
|
|
de->q_terminate = 0;
|
|
pthread_mutex_init(&de->q_lock, NULL);
|
|
sem_init(&de->q_sem, 0, 0);
|
|
sem_init(&de->display_start_sem, 0, 0);
|
|
av_assert0(pthread_create(&de->q_thread, NULL, display_thread, s) == 0);
|
|
|
|
sem_wait(&de->display_start_sem);
|
|
if (de->q_terminate) {
|
|
av_log(s, AV_LOG_ERROR, "%s: Display startup failure\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
av_log(s, AV_LOG_DEBUG, ">>> %s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void egl_vout_deinit(struct AVFormatContext *s)
|
|
{
|
|
egl_display_env_t *const de = s->priv_data;
|
|
|
|
av_log(s, AV_LOG_DEBUG, "<<< %s\n", __func__);
|
|
|
|
de->q_terminate = 1;
|
|
sem_post(&de->q_sem);
|
|
pthread_join(de->q_thread, NULL);
|
|
sem_destroy(&de->q_sem);
|
|
pthread_mutex_destroy(&de->q_lock);
|
|
|
|
av_frame_free(&de->q_next);
|
|
av_frame_free(&de->q_this);
|
|
|
|
av_log(s, AV_LOG_DEBUG, ">>> %s\n", __func__);
|
|
}
|
|
|
|
#define OFFSET(x) offsetof(egl_display_env_t, x)
|
|
static const AVOption options[] = {
|
|
{ "show_all", "show all frames", OFFSET(show_all), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
|
|
{ "window_size", "set window forced size", OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
|
|
{ "window_x", "set window x offset", OFFSET(window_x), AV_OPT_TYPE_INT, { .i64 = 0 }, -INT_MAX, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
|
|
{ "window_y", "set window y offset", OFFSET(window_y), AV_OPT_TYPE_INT, { .i64 = 0 }, -INT_MAX, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
|
|
{ "fullscreen", "set fullscreen display", OFFSET(fullscreen), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
|
|
{ NULL }
|
|
|
|
};
|
|
|
|
static const AVClass egl_vout_class = {
|
|
.class_name = "egl vid outdev",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
.category = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
|
|
};
|
|
|
|
FFOutputFormat ff_vout_egl_muxer = {
|
|
.p = {
|
|
.name = "vout_egl",
|
|
.long_name = NULL_IF_CONFIG_SMALL("Egl video output device"),
|
|
.audio_codec = AV_CODEC_ID_NONE,
|
|
.video_codec = AV_CODEC_ID_WRAPPED_AVFRAME,
|
|
.flags = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
|
|
.priv_class = &egl_vout_class,
|
|
},
|
|
.priv_data_size = sizeof(egl_display_env_t),
|
|
.write_header = egl_vout_write_header,
|
|
.write_packet = egl_vout_write_packet,
|
|
.write_uncoded_frame = egl_vout_write_frame,
|
|
.write_trailer = egl_vout_write_trailer,
|
|
.control_message = egl_vout_control_message,
|
|
.init = egl_vout_init,
|
|
.deinit = egl_vout_deinit,
|
|
};
|
|
|