mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-12-23 12:43:46 +02:00
36aee69f0d
When piping ffmpeg into ffplay both programs write a status line in the terminal. That causes flickering and invisibility of one or the other status line. As compromise set ffplay log level to warning, so it doesn't show the status line. The user is usually testing ffmpeg command lines and want's a preview of the result. This way the user can see the ffmpeg output and still see errors and warnings from ffplay, should they occur. Additionally set PTS to zero in ffplay to lessen the delay until the frames are displayed. Without it delay is quite observable when e.g. live capturing with low frame rates.
1326 lines
56 KiB
C
1326 lines
56 KiB
C
/*
|
|
* Copyright (c) 2014 Lukasz Marek
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
//TODO: support for more formats
|
|
//TODO: support for more systems.
|
|
//TODO: implement X11, Windows, Mac OS native default window. SDL 1.2 doesn't allow to render to custom thread.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
|
|
#include "config.h"
|
|
|
|
#if HAVE_WINDOWS_H
|
|
#include <windows.h>
|
|
#endif
|
|
#if HAVE_OPENGL_GL3_H
|
|
#include <OpenGL/gl3.h>
|
|
#elif HAVE_ES2_GL_H
|
|
#include <ES2/gl.h>
|
|
#else
|
|
#include <GL/gl.h>
|
|
#include <GL/glext.h>
|
|
#endif
|
|
#if HAVE_GLXGETPROCADDRESS
|
|
#include <GL/glx.h>
|
|
#endif
|
|
|
|
#if CONFIG_SDL2
|
|
#include <SDL.h>
|
|
#endif
|
|
|
|
#include "libavutil/common.h"
|
|
#include "libavutil/frame.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/log.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/avassert.h"
|
|
#include "libavformat/avformat.h"
|
|
#include "libavformat/internal.h"
|
|
#include "libavformat/mux.h"
|
|
#include "libavdevice/avdevice.h"
|
|
#include "opengl_enc_shaders.h"
|
|
|
|
#ifndef APIENTRY
|
|
#define APIENTRY
|
|
#endif
|
|
|
|
/* FF_GL_RED_COMPONENT is used for planar pixel types.
|
|
* Only red component is sampled in shaders.
|
|
* On some platforms GL_RED is not available and GL_LUMINANCE have to be used,
|
|
* but since OpenGL 3.0 GL_LUMINANCE is deprecated.
|
|
* GL_RED produces RGBA = value, 0, 0, 1.
|
|
* GL_LUMINANCE produces RGBA = value, value, value, 1.
|
|
* Note: GL_INTENSITY may also be used which produce RGBA = value, value, value, value. */
|
|
#if defined(GL_RED)
|
|
#define FF_GL_RED_COMPONENT GL_RED
|
|
#elif defined(GL_LUMINANCE)
|
|
#define FF_GL_RED_COMPONENT GL_LUMINANCE
|
|
#else
|
|
#define FF_GL_RED_COMPONENT 0x1903; //GL_RED
|
|
#endif
|
|
|
|
/* Constants not defined for iOS */
|
|
#define FF_GL_UNSIGNED_BYTE_3_3_2 0x8032
|
|
#define FF_GL_UNSIGNED_BYTE_2_3_3_REV 0x8362
|
|
#define FF_GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366
|
|
#define FF_GL_UNPACK_ROW_LENGTH 0x0CF2
|
|
|
|
/* MinGW exposes only OpenGL 1.1 API */
|
|
#define FF_GL_ARRAY_BUFFER 0x8892
|
|
#define FF_GL_ELEMENT_ARRAY_BUFFER 0x8893
|
|
#define FF_GL_STATIC_DRAW 0x88E4
|
|
#define FF_GL_FRAGMENT_SHADER 0x8B30
|
|
#define FF_GL_VERTEX_SHADER 0x8B31
|
|
#define FF_GL_COMPILE_STATUS 0x8B81
|
|
#define FF_GL_LINK_STATUS 0x8B82
|
|
#define FF_GL_INFO_LOG_LENGTH 0x8B84
|
|
typedef void (APIENTRY *FF_PFNGLACTIVETEXTUREPROC) (GLenum texture);
|
|
typedef void (APIENTRY *FF_PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
|
|
typedef void (APIENTRY *FF_PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
|
|
typedef void (APIENTRY *FF_PFNGLBUFFERDATAPROC) (GLenum target, ptrdiff_t size, const GLvoid *data, GLenum usage);
|
|
typedef void (APIENTRY *FF_PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
|
|
typedef GLint (APIENTRY *FF_PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const char *name);
|
|
typedef void (APIENTRY *FF_PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index);
|
|
typedef void (APIENTRY *FF_PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, uintptr_t pointer);
|
|
typedef GLint (APIENTRY *FF_PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const char *name);
|
|
typedef void (APIENTRY *FF_PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0);
|
|
typedef void (APIENTRY *FF_PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
|
|
typedef void (APIENTRY *FF_PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
|
|
typedef GLuint (APIENTRY *FF_PFNGLCREATEPROGRAMPROC) (void);
|
|
typedef void (APIENTRY *FF_PFNGLDELETEPROGRAMPROC) (GLuint program);
|
|
typedef void (APIENTRY *FF_PFNGLUSEPROGRAMPROC) (GLuint program);
|
|
typedef void (APIENTRY *FF_PFNGLLINKPROGRAMPROC) (GLuint program);
|
|
typedef void (APIENTRY *FF_PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
|
|
typedef void (APIENTRY *FF_PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, char *infoLog);
|
|
typedef void (APIENTRY *FF_PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
|
|
typedef GLuint (APIENTRY *FF_PFNGLCREATESHADERPROC) (GLenum type);
|
|
typedef void (APIENTRY *FF_PFNGLDELETESHADERPROC) (GLuint shader);
|
|
typedef void (APIENTRY *FF_PFNGLCOMPILESHADERPROC) (GLuint shader);
|
|
typedef void (APIENTRY *FF_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* *string, const GLint *length);
|
|
typedef void (APIENTRY *FF_PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
|
|
typedef void (APIENTRY *FF_PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog);
|
|
|
|
typedef struct FFOpenGLFunctions {
|
|
FF_PFNGLACTIVETEXTUREPROC glActiveTexture; //Require GL ARB multitexture
|
|
FF_PFNGLGENBUFFERSPROC glGenBuffers; //Require GL_ARB_vertex_buffer_object
|
|
FF_PFNGLDELETEBUFFERSPROC glDeleteBuffers; //Require GL_ARB_vertex_buffer_object
|
|
FF_PFNGLBUFFERDATAPROC glBufferData; //Require GL_ARB_vertex_buffer_object
|
|
FF_PFNGLBINDBUFFERPROC glBindBuffer; //Require GL_ARB_vertex_buffer_object
|
|
FF_PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation; //Require GL_ARB_vertex_shader
|
|
FF_PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; //Require GL_ARB_vertex_shader
|
|
FF_PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; //Require GL_ARB_vertex_shader
|
|
FF_PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; //Require GL_ARB_shader_objects
|
|
FF_PFNGLUNIFORM1FPROC glUniform1f; //Require GL_ARB_shader_objects
|
|
FF_PFNGLUNIFORM1IPROC glUniform1i; //Require GL_ARB_shader_objects
|
|
FF_PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv; //Require GL_ARB_shader_objects
|
|
FF_PFNGLCREATEPROGRAMPROC glCreateProgram; //Require GL_ARB_shader_objects
|
|
FF_PFNGLDELETEPROGRAMPROC glDeleteProgram; //Require GL_ARB_shader_objects
|
|
FF_PFNGLUSEPROGRAMPROC glUseProgram; //Require GL_ARB_shader_objects
|
|
FF_PFNGLLINKPROGRAMPROC glLinkProgram; //Require GL_ARB_shader_objects
|
|
FF_PFNGLGETPROGRAMIVPROC glGetProgramiv; //Require GL_ARB_shader_objects
|
|
FF_PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; //Require GL_ARB_shader_objects
|
|
FF_PFNGLATTACHSHADERPROC glAttachShader; //Require GL_ARB_shader_objects
|
|
FF_PFNGLCREATESHADERPROC glCreateShader; //Require GL_ARB_shader_objects
|
|
FF_PFNGLDELETESHADERPROC glDeleteShader; //Require GL_ARB_shader_objects
|
|
FF_PFNGLCOMPILESHADERPROC glCompileShader; //Require GL_ARB_shader_objects
|
|
FF_PFNGLSHADERSOURCEPROC glShaderSource; //Require GL_ARB_shader_objects
|
|
FF_PFNGLGETSHADERIVPROC glGetShaderiv; //Require GL_ARB_shader_objects
|
|
FF_PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; //Require GL_ARB_shader_objects
|
|
} FFOpenGLFunctions;
|
|
|
|
#define OPENGL_ERROR_CHECK(ctx) \
|
|
{\
|
|
GLenum err_code; \
|
|
if ((err_code = glGetError()) != GL_NO_ERROR) { \
|
|
av_log(ctx, AV_LOG_ERROR, "OpenGL error occurred in '%s', line %d: %d\n", __func__, __LINE__, err_code); \
|
|
goto fail; \
|
|
} \
|
|
}\
|
|
|
|
typedef struct OpenGLVertexInfo
|
|
{
|
|
float x, y, z; ///<Position
|
|
float s0, t0; ///<Texture coords
|
|
} OpenGLVertexInfo;
|
|
|
|
/* defines 2 triangles to display */
|
|
static const GLushort g_index[6] =
|
|
{
|
|
0, 1, 2,
|
|
0, 3, 2,
|
|
};
|
|
|
|
typedef struct OpenGLContext {
|
|
AVClass *class; ///< class for private options
|
|
|
|
#if CONFIG_SDL2
|
|
SDL_Window *window;
|
|
SDL_GLContext glcontext;
|
|
#endif
|
|
FFOpenGLFunctions glprocs;
|
|
|
|
int inited; ///< Set to 1 when write_header was successfully called.
|
|
uint8_t background[4]; ///< Background color
|
|
int no_window; ///< 0 for create default window
|
|
char *window_title; ///< Title of the window
|
|
|
|
/* OpenGL implementation limits */
|
|
GLint max_texture_size; ///< Maximum texture size
|
|
GLint max_viewport_width; ///< Maximum viewport size
|
|
GLint max_viewport_height; ///< Maximum viewport size
|
|
int non_pow_2_textures; ///< 1 when non power of 2 textures are supported
|
|
int unpack_subimage; ///< 1 when GL_EXT_unpack_subimage is available
|
|
|
|
/* Current OpenGL configuration */
|
|
GLuint program; ///< Shader program
|
|
GLuint vertex_shader; ///< Vertex shader
|
|
GLuint fragment_shader; ///< Fragment shader for current pix_pmt
|
|
GLuint texture_name[4]; ///< Textures' IDs
|
|
GLuint index_buffer; ///< Index buffer
|
|
GLuint vertex_buffer; ///< Vertex buffer
|
|
OpenGLVertexInfo vertex[4]; ///< VBO
|
|
GLint projection_matrix_location; ///< Uniforms' locations
|
|
GLint model_view_matrix_location;
|
|
GLint color_map_location;
|
|
GLint chroma_div_w_location;
|
|
GLint chroma_div_h_location;
|
|
GLint texture_location[4];
|
|
GLint position_attrib; ///< Attibutes' locations
|
|
GLint texture_coords_attrib;
|
|
|
|
GLfloat projection_matrix[16]; ///< Projection matrix
|
|
GLfloat model_view_matrix[16]; ///< Modev view matrix
|
|
GLfloat color_map[16]; ///< RGBA color map matrix
|
|
GLfloat chroma_div_w; ///< Chroma subsampling w ratio
|
|
GLfloat chroma_div_h; ///< Chroma subsampling h ratio
|
|
|
|
/* Stream information */
|
|
GLenum format;
|
|
GLenum type;
|
|
int width; ///< Stream width
|
|
int height; ///< Stream height
|
|
enum AVPixelFormat pix_fmt; ///< Stream pixel format
|
|
int picture_width; ///< Rendered width
|
|
int picture_height; ///< Rendered height
|
|
int window_width;
|
|
int window_height;
|
|
|
|
int warned;
|
|
} OpenGLContext;
|
|
|
|
static const struct OpenGLFormatDesc {
|
|
enum AVPixelFormat fixel_format;
|
|
const char * const * fragment_shader;
|
|
GLenum format;
|
|
GLenum type;
|
|
} opengl_format_desc[] = {
|
|
{ AV_PIX_FMT_YUV420P, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUV444P, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUV422P, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUV410P, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUV411P, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUV440P, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUV420P16, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_YUV422P16, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_YUV444P16, &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_YUVA420P, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUVA444P, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUVA422P, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_YUVA420P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_YUVA422P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_YUVA444P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_RGB24, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGB, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_BGR24, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGB, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_0RGB, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_RGB0, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_0BGR, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_BGR0, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_RGB565, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGB, GL_UNSIGNED_SHORT_5_6_5 },
|
|
{ AV_PIX_FMT_BGR565, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGB, GL_UNSIGNED_SHORT_5_6_5 },
|
|
{ AV_PIX_FMT_RGB555, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGBA, FF_GL_UNSIGNED_SHORT_1_5_5_5_REV },
|
|
{ AV_PIX_FMT_BGR555, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGBA, FF_GL_UNSIGNED_SHORT_1_5_5_5_REV },
|
|
{ AV_PIX_FMT_RGB8, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGB, FF_GL_UNSIGNED_BYTE_3_3_2 },
|
|
{ AV_PIX_FMT_BGR8, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGB, FF_GL_UNSIGNED_BYTE_2_3_3_REV },
|
|
{ AV_PIX_FMT_RGB48, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGB, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_BGR48, &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET, GL_RGB, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_ARGB, &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_RGBA, &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_ABGR, &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_BGRA, &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_RGBA64, &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_BGRA64, &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_GBRP, &FF_OPENGL_FRAGMENT_SHADER_RGB_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_GBRP16, &FF_OPENGL_FRAGMENT_SHADER_RGB_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_GBRAP, &FF_OPENGL_FRAGMENT_SHADER_RGBA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_GBRAP16, &FF_OPENGL_FRAGMENT_SHADER_RGBA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_GRAY8, &FF_OPENGL_FRAGMENT_SHADER_GRAY, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
|
|
{ AV_PIX_FMT_GRAY16, &FF_OPENGL_FRAGMENT_SHADER_GRAY, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
|
|
{ AV_PIX_FMT_NONE, NULL }
|
|
};
|
|
|
|
static av_cold int opengl_prepare_vertex(AVFormatContext *s);
|
|
static int opengl_draw(AVFormatContext *h, void *intput, int repaint, int is_pkt);
|
|
static av_cold int opengl_init_context(OpenGLContext *opengl);
|
|
|
|
static av_cold void opengl_deinit_context(OpenGLContext *opengl)
|
|
{
|
|
glDeleteTextures(4, opengl->texture_name);
|
|
opengl->texture_name[0] = opengl->texture_name[1] =
|
|
opengl->texture_name[2] = opengl->texture_name[3] = 0;
|
|
if (opengl->glprocs.glUseProgram)
|
|
opengl->glprocs.glUseProgram(0);
|
|
if (opengl->glprocs.glDeleteProgram) {
|
|
opengl->glprocs.glDeleteProgram(opengl->program);
|
|
opengl->program = 0;
|
|
}
|
|
if (opengl->glprocs.glDeleteShader) {
|
|
opengl->glprocs.glDeleteShader(opengl->vertex_shader);
|
|
opengl->glprocs.glDeleteShader(opengl->fragment_shader);
|
|
opengl->vertex_shader = opengl->fragment_shader = 0;
|
|
}
|
|
if (opengl->glprocs.glBindBuffer) {
|
|
opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, 0);
|
|
opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
}
|
|
if (opengl->glprocs.glDeleteBuffers) {
|
|
opengl->glprocs.glDeleteBuffers(2, &opengl->index_buffer);
|
|
opengl->vertex_buffer = opengl->index_buffer = 0;
|
|
}
|
|
}
|
|
|
|
static int opengl_resize(AVFormatContext *h, int width, int height)
|
|
{
|
|
int ret = 0;
|
|
OpenGLContext *opengl = h->priv_data;
|
|
opengl->window_width = width;
|
|
opengl->window_height = height;
|
|
if (opengl->inited) {
|
|
if (opengl->no_window &&
|
|
(ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0)) < 0) {
|
|
av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n");
|
|
goto end;
|
|
}
|
|
if ((ret = opengl_prepare_vertex(h)) < 0)
|
|
goto end;
|
|
ret = opengl_draw(h, NULL, 1, 0);
|
|
}
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
static int opengl_control_message(AVFormatContext *h, int type, void *data, size_t data_size)
|
|
{
|
|
OpenGLContext *opengl = h->priv_data;
|
|
switch(type) {
|
|
case AV_APP_TO_DEV_WINDOW_SIZE:
|
|
if (data) {
|
|
AVDeviceRect *message = data;
|
|
return opengl_resize(h, message->width, message->height);
|
|
}
|
|
return AVERROR(EINVAL);
|
|
case AV_APP_TO_DEV_WINDOW_REPAINT:
|
|
return opengl_resize(h, opengl->window_width, opengl->window_height);
|
|
}
|
|
return AVERROR(ENOSYS);
|
|
}
|
|
|
|
#if CONFIG_SDL2
|
|
static int opengl_sdl_process_events(AVFormatContext *h)
|
|
{
|
|
OpenGLContext *opengl = h->priv_data;
|
|
AVDeviceRect message;
|
|
SDL_Event event;
|
|
SDL_PumpEvents();
|
|
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) > 0) {
|
|
switch (event.type) {
|
|
case SDL_QUIT:
|
|
return AVERROR(EIO);
|
|
case SDL_KEYDOWN:
|
|
switch (event.key.keysym.sym) {
|
|
case SDLK_ESCAPE:
|
|
case SDLK_q:
|
|
return AVERROR(EIO);
|
|
}
|
|
return 0;
|
|
case SDL_WINDOWEVENT:
|
|
switch(event.window.event) {
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
SDL_GL_GetDrawableSize(opengl->window, &message.width, &message.height);
|
|
return opengl_control_message(h, AV_APP_TO_DEV_WINDOW_SIZE, &message, sizeof(AVDeviceRect));
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int av_cold opengl_sdl_create_window(AVFormatContext *h)
|
|
{
|
|
OpenGLContext *opengl = h->priv_data;
|
|
AVDeviceRect message;
|
|
if (SDL_Init(SDL_INIT_VIDEO)) {
|
|
av_log(opengl, AV_LOG_ERROR, "Unable to initialize SDL: %s\n", SDL_GetError());
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
opengl->window = SDL_CreateWindow(opengl->window_title,
|
|
SDL_WINDOWPOS_UNDEFINED,
|
|
SDL_WINDOWPOS_UNDEFINED,
|
|
opengl->window_width, opengl->window_height,
|
|
SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
|
|
if (!opengl->window) {
|
|
av_log(opengl, AV_LOG_ERROR, "Unable to create default window: %s\n", SDL_GetError());
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
opengl->glcontext = SDL_GL_CreateContext(opengl->window);
|
|
if (!opengl->glcontext) {
|
|
av_log(opengl, AV_LOG_ERROR, "Unable to create OpenGL context on default window: %s\n", SDL_GetError());
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
av_log(opengl, AV_LOG_INFO, "SDL driver: '%s'.\n", SDL_GetCurrentVideoDriver());
|
|
SDL_GL_GetDrawableSize(opengl->window, &message.width, &message.height);
|
|
return opengl_control_message(h, AV_APP_TO_DEV_WINDOW_SIZE, &message, sizeof(AVDeviceRect));
|
|
}
|
|
|
|
static int av_cold opengl_sdl_load_procedures(OpenGLContext *opengl)
|
|
{
|
|
FFOpenGLFunctions *procs = &opengl->glprocs;
|
|
|
|
#define LOAD_OPENGL_FUN(name, type) \
|
|
procs->name = (type)SDL_GL_GetProcAddress(#name); \
|
|
if (!procs->name) { \
|
|
av_log(opengl, AV_LOG_ERROR, "Cannot load OpenGL function: '%s'\n", #name); \
|
|
return AVERROR(ENOSYS); \
|
|
}
|
|
|
|
LOAD_OPENGL_FUN(glActiveTexture, FF_PFNGLACTIVETEXTUREPROC)
|
|
LOAD_OPENGL_FUN(glGenBuffers, FF_PFNGLGENBUFFERSPROC)
|
|
LOAD_OPENGL_FUN(glDeleteBuffers, FF_PFNGLDELETEBUFFERSPROC)
|
|
LOAD_OPENGL_FUN(glBufferData, FF_PFNGLBUFFERDATAPROC)
|
|
LOAD_OPENGL_FUN(glBindBuffer, FF_PFNGLBINDBUFFERPROC)
|
|
LOAD_OPENGL_FUN(glGetAttribLocation, FF_PFNGLGETATTRIBLOCATIONPROC)
|
|
LOAD_OPENGL_FUN(glGetUniformLocation, FF_PFNGLGETUNIFORMLOCATIONPROC)
|
|
LOAD_OPENGL_FUN(glUniform1f, FF_PFNGLUNIFORM1FPROC)
|
|
LOAD_OPENGL_FUN(glUniform1i, FF_PFNGLUNIFORM1IPROC)
|
|
LOAD_OPENGL_FUN(glUniformMatrix4fv, FF_PFNGLUNIFORMMATRIX4FVPROC)
|
|
LOAD_OPENGL_FUN(glCreateProgram, FF_PFNGLCREATEPROGRAMPROC)
|
|
LOAD_OPENGL_FUN(glDeleteProgram, FF_PFNGLDELETEPROGRAMPROC)
|
|
LOAD_OPENGL_FUN(glUseProgram, FF_PFNGLUSEPROGRAMPROC)
|
|
LOAD_OPENGL_FUN(glLinkProgram, FF_PFNGLLINKPROGRAMPROC)
|
|
LOAD_OPENGL_FUN(glGetProgramiv, FF_PFNGLGETPROGRAMIVPROC)
|
|
LOAD_OPENGL_FUN(glGetProgramInfoLog, FF_PFNGLGETPROGRAMINFOLOGPROC)
|
|
LOAD_OPENGL_FUN(glAttachShader, FF_PFNGLATTACHSHADERPROC)
|
|
LOAD_OPENGL_FUN(glCreateShader, FF_PFNGLCREATESHADERPROC)
|
|
LOAD_OPENGL_FUN(glDeleteShader, FF_PFNGLDELETESHADERPROC)
|
|
LOAD_OPENGL_FUN(glCompileShader, FF_PFNGLCOMPILESHADERPROC)
|
|
LOAD_OPENGL_FUN(glShaderSource, FF_PFNGLSHADERSOURCEPROC)
|
|
LOAD_OPENGL_FUN(glGetShaderiv, FF_PFNGLGETSHADERIVPROC)
|
|
LOAD_OPENGL_FUN(glGetShaderInfoLog, FF_PFNGLGETSHADERINFOLOGPROC)
|
|
LOAD_OPENGL_FUN(glEnableVertexAttribArray, FF_PFNGLENABLEVERTEXATTRIBARRAYPROC)
|
|
LOAD_OPENGL_FUN(glVertexAttribPointer, FF_PFNGLVERTEXATTRIBPOINTERPROC)
|
|
|
|
return 0;
|
|
|
|
#undef LOAD_OPENGL_FUN
|
|
}
|
|
#endif /* CONFIG_SDL2 */
|
|
|
|
#if defined(__APPLE__)
|
|
static int av_cold opengl_load_procedures(OpenGLContext *opengl)
|
|
{
|
|
FFOpenGLFunctions *procs = &opengl->glprocs;
|
|
|
|
#if CONFIG_SDL2
|
|
if (!opengl->no_window)
|
|
return opengl_sdl_load_procedures(opengl);
|
|
#endif
|
|
|
|
procs->glActiveTexture = glActiveTexture;
|
|
procs->glGenBuffers = glGenBuffers;
|
|
procs->glDeleteBuffers = glDeleteBuffers;
|
|
procs->glBufferData = glBufferData;
|
|
procs->glBindBuffer = glBindBuffer;
|
|
procs->glGetAttribLocation = glGetAttribLocation;
|
|
procs->glGetUniformLocation = glGetUniformLocation;
|
|
procs->glUniform1f = glUniform1f;
|
|
procs->glUniform1i = glUniform1i;
|
|
procs->glUniformMatrix4fv = glUniformMatrix4fv;
|
|
procs->glCreateProgram = glCreateProgram;
|
|
procs->glDeleteProgram = glDeleteProgram;
|
|
procs->glUseProgram = glUseProgram;
|
|
procs->glLinkProgram = glLinkProgram;
|
|
procs->glGetProgramiv = glGetProgramiv;
|
|
procs->glGetProgramInfoLog = glGetProgramInfoLog;
|
|
procs->glAttachShader = glAttachShader;
|
|
procs->glCreateShader = glCreateShader;
|
|
procs->glDeleteShader = glDeleteShader;
|
|
procs->glCompileShader = glCompileShader;
|
|
procs->glShaderSource = glShaderSource;
|
|
procs->glGetShaderiv = glGetShaderiv;
|
|
procs->glGetShaderInfoLog = glGetShaderInfoLog;
|
|
procs->glEnableVertexAttribArray = glEnableVertexAttribArray;
|
|
procs->glVertexAttribPointer = (FF_PFNGLVERTEXATTRIBPOINTERPROC) glVertexAttribPointer;
|
|
return 0;
|
|
}
|
|
#else
|
|
static int av_cold opengl_load_procedures(OpenGLContext *opengl)
|
|
{
|
|
FFOpenGLFunctions *procs = &opengl->glprocs;
|
|
|
|
#if HAVE_GLXGETPROCADDRESS
|
|
#define SelectedGetProcAddress glXGetProcAddress
|
|
#elif HAVE_WGLGETPROCADDRESS
|
|
#define SelectedGetProcAddress wglGetProcAddress
|
|
#endif
|
|
|
|
#define LOAD_OPENGL_FUN(name, type) \
|
|
procs->name = (type)SelectedGetProcAddress(#name); \
|
|
if (!procs->name) { \
|
|
av_log(opengl, AV_LOG_ERROR, "Cannot load OpenGL function: '%s'\n", #name); \
|
|
return AVERROR(ENOSYS); \
|
|
}
|
|
|
|
#if CONFIG_SDL2
|
|
if (!opengl->no_window)
|
|
return opengl_sdl_load_procedures(opengl);
|
|
#endif
|
|
|
|
LOAD_OPENGL_FUN(glActiveTexture, FF_PFNGLACTIVETEXTUREPROC)
|
|
LOAD_OPENGL_FUN(glGenBuffers, FF_PFNGLGENBUFFERSPROC)
|
|
LOAD_OPENGL_FUN(glDeleteBuffers, FF_PFNGLDELETEBUFFERSPROC)
|
|
LOAD_OPENGL_FUN(glBufferData, FF_PFNGLBUFFERDATAPROC)
|
|
LOAD_OPENGL_FUN(glBindBuffer, FF_PFNGLBINDBUFFERPROC)
|
|
LOAD_OPENGL_FUN(glGetAttribLocation, FF_PFNGLGETATTRIBLOCATIONPROC)
|
|
LOAD_OPENGL_FUN(glGetUniformLocation, FF_PFNGLGETUNIFORMLOCATIONPROC)
|
|
LOAD_OPENGL_FUN(glUniform1f, FF_PFNGLUNIFORM1FPROC)
|
|
LOAD_OPENGL_FUN(glUniform1i, FF_PFNGLUNIFORM1IPROC)
|
|
LOAD_OPENGL_FUN(glUniformMatrix4fv, FF_PFNGLUNIFORMMATRIX4FVPROC)
|
|
LOAD_OPENGL_FUN(glCreateProgram, FF_PFNGLCREATEPROGRAMPROC)
|
|
LOAD_OPENGL_FUN(glDeleteProgram, FF_PFNGLDELETEPROGRAMPROC)
|
|
LOAD_OPENGL_FUN(glUseProgram, FF_PFNGLUSEPROGRAMPROC)
|
|
LOAD_OPENGL_FUN(glLinkProgram, FF_PFNGLLINKPROGRAMPROC)
|
|
LOAD_OPENGL_FUN(glGetProgramiv, FF_PFNGLGETPROGRAMIVPROC)
|
|
LOAD_OPENGL_FUN(glGetProgramInfoLog, FF_PFNGLGETPROGRAMINFOLOGPROC)
|
|
LOAD_OPENGL_FUN(glAttachShader, FF_PFNGLATTACHSHADERPROC)
|
|
LOAD_OPENGL_FUN(glCreateShader, FF_PFNGLCREATESHADERPROC)
|
|
LOAD_OPENGL_FUN(glDeleteShader, FF_PFNGLDELETESHADERPROC)
|
|
LOAD_OPENGL_FUN(glCompileShader, FF_PFNGLCOMPILESHADERPROC)
|
|
LOAD_OPENGL_FUN(glShaderSource, FF_PFNGLSHADERSOURCEPROC)
|
|
LOAD_OPENGL_FUN(glGetShaderiv, FF_PFNGLGETSHADERIVPROC)
|
|
LOAD_OPENGL_FUN(glGetShaderInfoLog, FF_PFNGLGETSHADERINFOLOGPROC)
|
|
LOAD_OPENGL_FUN(glEnableVertexAttribArray, FF_PFNGLENABLEVERTEXATTRIBARRAYPROC)
|
|
LOAD_OPENGL_FUN(glVertexAttribPointer, FF_PFNGLVERTEXATTRIBPOINTERPROC)
|
|
|
|
return 0;
|
|
|
|
#undef SelectedGetProcAddress
|
|
#undef LOAD_OPENGL_FUN
|
|
}
|
|
#endif
|
|
|
|
static void opengl_make_identity(float matrix[16])
|
|
{
|
|
memset(matrix, 0, 16 * sizeof(float));
|
|
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0f;
|
|
}
|
|
|
|
static void opengl_make_ortho(float matrix[16], float left, float right,
|
|
float bottom, float top, float nearZ, float farZ)
|
|
{
|
|
float ral = right + left;
|
|
float rsl = right - left;
|
|
float tab = top + bottom;
|
|
float tsb = top - bottom;
|
|
float fan = farZ + nearZ;
|
|
float fsn = farZ - nearZ;
|
|
|
|
memset(matrix, 0, 16 * sizeof(float));
|
|
matrix[0] = 2.0f / rsl;
|
|
matrix[5] = 2.0f / tsb;
|
|
matrix[10] = -2.0f / fsn;
|
|
matrix[12] = -ral / rsl;
|
|
matrix[13] = -tab / tsb;
|
|
matrix[14] = -fan / fsn;
|
|
matrix[15] = 1.0f;
|
|
}
|
|
|
|
static av_cold int opengl_read_limits(AVFormatContext *h)
|
|
{
|
|
OpenGLContext *opengl = h->priv_data;
|
|
static const struct{
|
|
const char *extension;
|
|
int major;
|
|
int minor;
|
|
} required_extensions[] = {
|
|
{ "GL_ARB_multitexture", 1, 3 },
|
|
{ "GL_ARB_vertex_buffer_object", 1, 5 }, //GLX_ARB_vertex_buffer_object
|
|
{ "GL_ARB_vertex_shader", 2, 0 },
|
|
{ "GL_ARB_fragment_shader", 2, 0 },
|
|
{ "GL_ARB_shader_objects", 2, 0 },
|
|
{ NULL, 0, 0 }
|
|
};
|
|
int i, major, minor;
|
|
const char *extensions, *version;
|
|
|
|
version = glGetString(GL_VERSION);
|
|
extensions = glGetString(GL_EXTENSIONS);
|
|
if (!version || !extensions) {
|
|
av_log(h, AV_LOG_ERROR, "No OpenGL context initialized for the current thread\n");
|
|
return AVERROR(ENOSYS);
|
|
}
|
|
|
|
av_log(h, AV_LOG_DEBUG, "OpenGL version: %s\n", version);
|
|
if (sscanf(version, "%d.%d", &major, &minor) != 2)
|
|
return AVERROR(ENOSYS);
|
|
|
|
for (i = 0; required_extensions[i].extension; i++) {
|
|
if (major < required_extensions[i].major &&
|
|
(major == required_extensions[i].major && minor < required_extensions[i].minor) &&
|
|
!strstr(extensions, required_extensions[i].extension)) {
|
|
av_log(h, AV_LOG_ERROR, "Required extension %s is not supported.\n",
|
|
required_extensions[i].extension);
|
|
av_log(h, AV_LOG_DEBUG, "Supported extensions are: %s\n", extensions);
|
|
return AVERROR(ENOSYS);
|
|
}
|
|
}
|
|
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &opengl->max_texture_size);
|
|
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, &opengl->max_viewport_width);
|
|
opengl->non_pow_2_textures = major >= 2 || strstr(extensions, "GL_ARB_texture_non_power_of_two");
|
|
#if defined(GL_ES_VERSION_2_0)
|
|
opengl->unpack_subimage = !!strstr(extensions, "GL_EXT_unpack_subimage");
|
|
#else
|
|
opengl->unpack_subimage = 1;
|
|
#endif
|
|
|
|
av_log(h, AV_LOG_DEBUG, "Non Power of 2 textures support: %s\n", opengl->non_pow_2_textures ? "Yes" : "No");
|
|
av_log(h, AV_LOG_DEBUG, "Unpack Subimage extension support: %s\n", opengl->unpack_subimage ? "Yes" : "No");
|
|
av_log(h, AV_LOG_DEBUG, "Max texture size: %dx%d\n", opengl->max_texture_size, opengl->max_texture_size);
|
|
av_log(h, AV_LOG_DEBUG, "Max viewport size: %dx%d\n",
|
|
opengl->max_viewport_width, opengl->max_viewport_height);
|
|
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
return 0;
|
|
fail:
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
static const char* opengl_get_fragment_shader_code(enum AVPixelFormat format)
|
|
{
|
|
int i;
|
|
for (i = 0; i < FF_ARRAY_ELEMS(opengl_format_desc); i++) {
|
|
if (opengl_format_desc[i].fixel_format == format)
|
|
return *opengl_format_desc[i].fragment_shader;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int opengl_type_size(GLenum type)
|
|
{
|
|
switch(type) {
|
|
case GL_UNSIGNED_SHORT:
|
|
case FF_GL_UNSIGNED_SHORT_1_5_5_5_REV:
|
|
case GL_UNSIGNED_SHORT_5_6_5:
|
|
return 2;
|
|
case GL_UNSIGNED_BYTE:
|
|
case FF_GL_UNSIGNED_BYTE_3_3_2:
|
|
case FF_GL_UNSIGNED_BYTE_2_3_3_REV:
|
|
default:
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static av_cold void opengl_get_texture_params(OpenGLContext *opengl)
|
|
{
|
|
int i;
|
|
for (i = 0; i < FF_ARRAY_ELEMS(opengl_format_desc); i++) {
|
|
if (opengl_format_desc[i].fixel_format == opengl->pix_fmt) {
|
|
opengl->format = opengl_format_desc[i].format;
|
|
opengl->type = opengl_format_desc[i].type;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void opengl_compute_display_area(AVFormatContext *s)
|
|
{
|
|
AVRational sar, dar; /* sample and display aspect ratios */
|
|
OpenGLContext *opengl = s->priv_data;
|
|
AVStream *st = s->streams[0];
|
|
AVCodecParameters *par = st->codecpar;
|
|
|
|
/* compute overlay width and height from the codec context information */
|
|
sar = st->sample_aspect_ratio.num ? st->sample_aspect_ratio : (AVRational){ 1, 1 };
|
|
dar = av_mul_q(sar, (AVRational){ par->width, par->height });
|
|
|
|
/* we suppose the screen has a 1/1 sample aspect ratio */
|
|
/* fit in the window */
|
|
if (av_cmp_q(dar, (AVRational){ opengl->window_width, opengl->window_height }) > 0) {
|
|
/* fit in width */
|
|
opengl->picture_width = opengl->window_width;
|
|
opengl->picture_height = av_rescale(opengl->picture_width, dar.den, dar.num);
|
|
} else {
|
|
/* fit in height */
|
|
opengl->picture_height = opengl->window_height;
|
|
opengl->picture_width = av_rescale(opengl->picture_height, dar.num, dar.den);
|
|
}
|
|
}
|
|
|
|
static av_cold void opengl_get_texture_size(OpenGLContext *opengl, int in_width, int in_height,
|
|
int *out_width, int *out_height)
|
|
{
|
|
if (opengl->non_pow_2_textures) {
|
|
*out_width = in_width;
|
|
*out_height = in_height;
|
|
} else {
|
|
int max = FFMIN(FFMAX(in_width, in_height), opengl->max_texture_size);
|
|
unsigned power_of_2 = 1;
|
|
while (power_of_2 < max)
|
|
power_of_2 *= 2;
|
|
*out_height = power_of_2;
|
|
*out_width = power_of_2;
|
|
av_log(opengl, AV_LOG_DEBUG, "Texture size calculated from %dx%d into %dx%d\n",
|
|
in_width, in_height, *out_width, *out_height);
|
|
}
|
|
}
|
|
|
|
static av_cold void opengl_fill_color_map(OpenGLContext *opengl)
|
|
{
|
|
const AVPixFmtDescriptor *desc;
|
|
int shift;
|
|
enum AVPixelFormat pix_fmt = opengl->pix_fmt;
|
|
|
|
/* We need order of components, not exact position, some minor HACKs here */
|
|
if (pix_fmt == AV_PIX_FMT_RGB565 || pix_fmt == AV_PIX_FMT_BGR555 ||
|
|
pix_fmt == AV_PIX_FMT_BGR8 || pix_fmt == AV_PIX_FMT_RGB8)
|
|
pix_fmt = AV_PIX_FMT_RGB24;
|
|
else if (pix_fmt == AV_PIX_FMT_BGR565 || pix_fmt == AV_PIX_FMT_RGB555)
|
|
pix_fmt = AV_PIX_FMT_BGR24;
|
|
|
|
desc = av_pix_fmt_desc_get(pix_fmt);
|
|
if (!(desc->flags & AV_PIX_FMT_FLAG_RGB))
|
|
return;
|
|
|
|
#define FILL_COMPONENT(i) { \
|
|
shift = (desc->comp[i].depth - 1) >> 3; \
|
|
opengl->color_map[(i << 2) + (desc->comp[i].offset >> shift)] = 1.0; \
|
|
}
|
|
|
|
memset(opengl->color_map, 0, sizeof(opengl->color_map));
|
|
FILL_COMPONENT(0);
|
|
FILL_COMPONENT(1);
|
|
FILL_COMPONENT(2);
|
|
if (desc->flags & AV_PIX_FMT_FLAG_ALPHA)
|
|
FILL_COMPONENT(3);
|
|
|
|
#undef FILL_COMPONENT
|
|
}
|
|
|
|
static av_cold GLuint opengl_load_shader(OpenGLContext *opengl, GLenum type, const char *source)
|
|
{
|
|
GLuint shader = opengl->glprocs.glCreateShader(type);
|
|
GLint result;
|
|
if (!shader) {
|
|
av_log(opengl, AV_LOG_ERROR, "glCreateShader() failed\n");
|
|
return 0;
|
|
}
|
|
opengl->glprocs.glShaderSource(shader, 1, &source, NULL);
|
|
opengl->glprocs.glCompileShader(shader);
|
|
|
|
opengl->glprocs.glGetShaderiv(shader, FF_GL_COMPILE_STATUS, &result);
|
|
if (!result) {
|
|
char *log;
|
|
opengl->glprocs.glGetShaderiv(shader, FF_GL_INFO_LOG_LENGTH, &result);
|
|
if (result) {
|
|
if ((log = av_malloc(result))) {
|
|
opengl->glprocs.glGetShaderInfoLog(shader, result, NULL, log);
|
|
av_log(opengl, AV_LOG_ERROR, "Compile error: %s\n", log);
|
|
av_free(log);
|
|
}
|
|
}
|
|
goto fail;
|
|
}
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
return shader;
|
|
fail:
|
|
opengl->glprocs.glDeleteShader(shader);
|
|
return 0;
|
|
}
|
|
|
|
static av_cold int opengl_compile_shaders(OpenGLContext *opengl, enum AVPixelFormat pix_fmt)
|
|
{
|
|
GLint result;
|
|
const char *fragment_shader_code = opengl_get_fragment_shader_code(pix_fmt);
|
|
|
|
if (!fragment_shader_code) {
|
|
av_log(opengl, AV_LOG_ERROR, "Provided pixel format '%s' is not supported\n",
|
|
av_get_pix_fmt_name(pix_fmt));
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
opengl->vertex_shader = opengl_load_shader(opengl, FF_GL_VERTEX_SHADER,
|
|
FF_OPENGL_VERTEX_SHADER);
|
|
if (!opengl->vertex_shader) {
|
|
av_log(opengl, AV_LOG_ERROR, "Vertex shader loading failed.\n");
|
|
goto fail;
|
|
}
|
|
opengl->fragment_shader = opengl_load_shader(opengl, FF_GL_FRAGMENT_SHADER,
|
|
fragment_shader_code);
|
|
if (!opengl->fragment_shader) {
|
|
av_log(opengl, AV_LOG_ERROR, "Fragment shader loading failed.\n");
|
|
goto fail;
|
|
}
|
|
|
|
opengl->program = opengl->glprocs.glCreateProgram();
|
|
if (!opengl->program)
|
|
goto fail;
|
|
|
|
opengl->glprocs.glAttachShader(opengl->program, opengl->vertex_shader);
|
|
opengl->glprocs.glAttachShader(opengl->program, opengl->fragment_shader);
|
|
opengl->glprocs.glLinkProgram(opengl->program);
|
|
|
|
opengl->glprocs.glGetProgramiv(opengl->program, FF_GL_LINK_STATUS, &result);
|
|
if (!result) {
|
|
char *log;
|
|
opengl->glprocs.glGetProgramiv(opengl->program, FF_GL_INFO_LOG_LENGTH, &result);
|
|
if (result) {
|
|
log = av_malloc(result);
|
|
if (!log)
|
|
goto fail;
|
|
opengl->glprocs.glGetProgramInfoLog(opengl->program, result, NULL, log);
|
|
av_log(opengl, AV_LOG_ERROR, "Link error: %s\n", log);
|
|
av_free(log);
|
|
}
|
|
goto fail;
|
|
}
|
|
|
|
opengl->position_attrib = opengl->glprocs.glGetAttribLocation(opengl->program, "a_position");
|
|
opengl->texture_coords_attrib = opengl->glprocs.glGetAttribLocation(opengl->program, "a_textureCoords");
|
|
opengl->projection_matrix_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_projectionMatrix");
|
|
opengl->model_view_matrix_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_modelViewMatrix");
|
|
opengl->color_map_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_colorMap");
|
|
opengl->texture_location[0] = opengl->glprocs.glGetUniformLocation(opengl->program, "u_texture0");
|
|
opengl->texture_location[1] = opengl->glprocs.glGetUniformLocation(opengl->program, "u_texture1");
|
|
opengl->texture_location[2] = opengl->glprocs.glGetUniformLocation(opengl->program, "u_texture2");
|
|
opengl->texture_location[3] = opengl->glprocs.glGetUniformLocation(opengl->program, "u_texture3");
|
|
opengl->chroma_div_w_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_chroma_div_w");
|
|
opengl->chroma_div_h_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_chroma_div_h");
|
|
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
return 0;
|
|
fail:
|
|
opengl->glprocs.glDeleteShader(opengl->vertex_shader);
|
|
opengl->glprocs.glDeleteShader(opengl->fragment_shader);
|
|
opengl->glprocs.glDeleteProgram(opengl->program);
|
|
opengl->fragment_shader = opengl->vertex_shader = opengl->program = 0;
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
static av_cold int opengl_configure_texture(OpenGLContext *opengl, GLuint texture,
|
|
GLsizei width, GLsizei height)
|
|
{
|
|
if (texture) {
|
|
int new_width, new_height;
|
|
opengl_get_texture_size(opengl, width, height, &new_width, &new_height);
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, opengl->format, new_width, new_height, 0,
|
|
opengl->format, opengl->type, NULL);
|
|
OPENGL_ERROR_CHECK(NULL);
|
|
}
|
|
return 0;
|
|
fail:
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
static av_cold int opengl_prepare_vertex(AVFormatContext *s)
|
|
{
|
|
OpenGLContext *opengl = s->priv_data;
|
|
int tex_w, tex_h;
|
|
|
|
if (opengl->window_width > opengl->max_viewport_width || opengl->window_height > opengl->max_viewport_height) {
|
|
opengl->window_width = FFMIN(opengl->window_width, opengl->max_viewport_width);
|
|
opengl->window_height = FFMIN(opengl->window_height, opengl->max_viewport_height);
|
|
av_log(opengl, AV_LOG_WARNING, "Too big viewport requested, limited to %dx%d", opengl->window_width, opengl->window_height);
|
|
}
|
|
glViewport(0, 0, opengl->window_width, opengl->window_height);
|
|
opengl_make_ortho(opengl->projection_matrix,
|
|
- (float)opengl->window_width / 2.0f, (float)opengl->window_width / 2.0f,
|
|
- (float)opengl->window_height / 2.0f, (float)opengl->window_height / 2.0f,
|
|
1.0f, -1.0f);
|
|
opengl_make_identity(opengl->model_view_matrix);
|
|
|
|
opengl_compute_display_area(s);
|
|
|
|
opengl->vertex[0].z = opengl->vertex[1].z = opengl->vertex[2].z = opengl->vertex[3].z = 0.0f;
|
|
opengl->vertex[0].x = opengl->vertex[1].x = - (float)opengl->picture_width / 2.0f;
|
|
opengl->vertex[2].x = opengl->vertex[3].x = (float)opengl->picture_width / 2.0f;
|
|
opengl->vertex[1].y = opengl->vertex[2].y = - (float)opengl->picture_height / 2.0f;
|
|
opengl->vertex[0].y = opengl->vertex[3].y = (float)opengl->picture_height / 2.0f;
|
|
|
|
opengl_get_texture_size(opengl, opengl->width, opengl->height, &tex_w, &tex_h);
|
|
|
|
opengl->vertex[0].s0 = 0.0f;
|
|
opengl->vertex[0].t0 = 0.0f;
|
|
opengl->vertex[1].s0 = 0.0f;
|
|
opengl->vertex[1].t0 = (float)opengl->height / (float)tex_h;
|
|
opengl->vertex[2].s0 = (float)opengl->width / (float)tex_w;
|
|
opengl->vertex[2].t0 = (float)opengl->height / (float)tex_h;
|
|
opengl->vertex[3].s0 = (float)opengl->width / (float)tex_w;
|
|
opengl->vertex[3].t0 = 0.0f;
|
|
|
|
opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, opengl->vertex_buffer);
|
|
opengl->glprocs.glBufferData(FF_GL_ARRAY_BUFFER, sizeof(opengl->vertex), opengl->vertex, FF_GL_STATIC_DRAW);
|
|
opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, 0);
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
return 0;
|
|
fail:
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
static int opengl_prepare(OpenGLContext *opengl)
|
|
{
|
|
int i;
|
|
opengl->glprocs.glUseProgram(opengl->program);
|
|
opengl->glprocs.glUniformMatrix4fv(opengl->projection_matrix_location, 1, GL_FALSE, opengl->projection_matrix);
|
|
opengl->glprocs.glUniformMatrix4fv(opengl->model_view_matrix_location, 1, GL_FALSE, opengl->model_view_matrix);
|
|
for (i = 0; i < 4; i++)
|
|
if (opengl->texture_location[i] != -1) {
|
|
opengl->glprocs.glActiveTexture(GL_TEXTURE0 + i);
|
|
glBindTexture(GL_TEXTURE_2D, opengl->texture_name[i]);
|
|
opengl->glprocs.glUniform1i(opengl->texture_location[i], i);
|
|
}
|
|
if (opengl->color_map_location != -1)
|
|
opengl->glprocs.glUniformMatrix4fv(opengl->color_map_location, 1, GL_FALSE, opengl->color_map);
|
|
if (opengl->chroma_div_h_location != -1)
|
|
opengl->glprocs.glUniform1f(opengl->chroma_div_h_location, opengl->chroma_div_h);
|
|
if (opengl->chroma_div_w_location != -1)
|
|
opengl->glprocs.glUniform1f(opengl->chroma_div_w_location, opengl->chroma_div_w);
|
|
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
return 0;
|
|
fail:
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
static int opengl_create_window(AVFormatContext *h)
|
|
{
|
|
OpenGLContext *opengl = h->priv_data;
|
|
int ret;
|
|
|
|
if (!opengl->no_window) {
|
|
#if CONFIG_SDL2
|
|
if ((ret = opengl_sdl_create_window(h)) < 0) {
|
|
av_log(opengl, AV_LOG_ERROR, "Cannot create default SDL window.\n");
|
|
return ret;
|
|
}
|
|
#else
|
|
av_log(opengl, AV_LOG_ERROR, "FFmpeg is compiled without SDL. Cannot create default window.\n");
|
|
return AVERROR(ENOSYS);
|
|
#endif
|
|
} else {
|
|
AVDeviceRect message;
|
|
message.x = message.y = 0;
|
|
message.width = opengl->window_width;
|
|
message.height = opengl->window_height;
|
|
if ((ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_CREATE_WINDOW_BUFFER,
|
|
&message , sizeof(message))) < 0) {
|
|
av_log(opengl, AV_LOG_ERROR, "Application failed to create window buffer.\n");
|
|
return ret;
|
|
}
|
|
if ((ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0)) < 0) {
|
|
av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n");
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int opengl_release_window(AVFormatContext *h)
|
|
{
|
|
int ret;
|
|
OpenGLContext *opengl = h->priv_data;
|
|
if (!opengl->no_window) {
|
|
#if CONFIG_SDL2
|
|
SDL_GL_DeleteContext(opengl->glcontext);
|
|
SDL_DestroyWindow(opengl->window);
|
|
SDL_Quit();
|
|
#endif
|
|
} else if ((ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_DESTROY_WINDOW_BUFFER, NULL , 0)) < 0) {
|
|
av_log(opengl, AV_LOG_ERROR, "Application failed to release window buffer.\n");
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static av_cold int opengl_write_trailer(AVFormatContext *h)
|
|
{
|
|
OpenGLContext *opengl = h->priv_data;
|
|
|
|
if (opengl->no_window &&
|
|
avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0) < 0)
|
|
av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n");
|
|
|
|
opengl_deinit_context(opengl);
|
|
opengl_release_window(h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static av_cold int opengl_init_context(OpenGLContext *opengl)
|
|
{
|
|
int i, ret;
|
|
const AVPixFmtDescriptor *desc;
|
|
|
|
if ((ret = opengl_compile_shaders(opengl, opengl->pix_fmt)) < 0)
|
|
goto fail;
|
|
|
|
desc = av_pix_fmt_desc_get(opengl->pix_fmt);
|
|
av_assert0(desc->nb_components > 0 && desc->nb_components <= 4);
|
|
glGenTextures(desc->nb_components, opengl->texture_name);
|
|
|
|
opengl->glprocs.glGenBuffers(2, &opengl->index_buffer);
|
|
if (!opengl->index_buffer || !opengl->vertex_buffer) {
|
|
av_log(opengl, AV_LOG_ERROR, "Buffer generation failed.\n");
|
|
ret = AVERROR_EXTERNAL;
|
|
goto fail;
|
|
}
|
|
|
|
opengl_configure_texture(opengl, opengl->texture_name[0], opengl->width, opengl->height);
|
|
if (desc->nb_components > 1) {
|
|
int has_alpha = desc->flags & AV_PIX_FMT_FLAG_ALPHA;
|
|
int num_planes = desc->nb_components - (has_alpha ? 1 : 0);
|
|
if (opengl->non_pow_2_textures) {
|
|
opengl->chroma_div_w = 1.0f;
|
|
opengl->chroma_div_h = 1.0f;
|
|
} else {
|
|
opengl->chroma_div_w = 1 << desc->log2_chroma_w;
|
|
opengl->chroma_div_h = 1 << desc->log2_chroma_h;
|
|
}
|
|
for (i = 1; i < num_planes; i++)
|
|
if (opengl->non_pow_2_textures)
|
|
opengl_configure_texture(opengl, opengl->texture_name[i],
|
|
AV_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w),
|
|
AV_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h));
|
|
else
|
|
opengl_configure_texture(opengl, opengl->texture_name[i], opengl->width, opengl->height);
|
|
if (has_alpha)
|
|
opengl_configure_texture(opengl, opengl->texture_name[3], opengl->width, opengl->height);
|
|
}
|
|
|
|
opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, opengl->index_buffer);
|
|
opengl->glprocs.glBufferData(FF_GL_ELEMENT_ARRAY_BUFFER, sizeof(g_index), g_index, FF_GL_STATIC_DRAW);
|
|
opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
glClearColor((float)opengl->background[0] / 255.0f, (float)opengl->background[1] / 255.0f,
|
|
(float)opengl->background[2] / 255.0f, 1.0f);
|
|
|
|
ret = AVERROR_EXTERNAL;
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
|
|
return 0;
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static av_cold int opengl_write_header(AVFormatContext *h)
|
|
{
|
|
OpenGLContext *opengl = h->priv_data;
|
|
AVCodecParameters *par = h->streams[0]->codecpar;
|
|
AVStream *st;
|
|
int ret;
|
|
|
|
if (!opengl->warned) {
|
|
av_log(opengl, AV_LOG_WARNING,
|
|
"The opengl output device is deprecated due to being fundamentally incompatible with libavformat API. "
|
|
"For monitoring purposes in ffmpeg you can output to a file or use pipes and a video player.\n"
|
|
"Example: ffmpeg -i INPUT -f nut -c:v rawvideo - | ffplay -loglevel warning -vf setpts=0 -\n"
|
|
);
|
|
opengl->warned = 1;
|
|
}
|
|
|
|
if (h->nb_streams != 1 ||
|
|
par->codec_type != AVMEDIA_TYPE_VIDEO ||
|
|
(par->codec_id != AV_CODEC_ID_WRAPPED_AVFRAME && par->codec_id != AV_CODEC_ID_RAWVIDEO)) {
|
|
av_log(opengl, AV_LOG_ERROR, "Only a single raw or wrapped avframe video stream is supported.\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
st = h->streams[0];
|
|
opengl->width = st->codecpar->width;
|
|
opengl->height = st->codecpar->height;
|
|
opengl->pix_fmt = st->codecpar->format;
|
|
if (!opengl->window_width)
|
|
opengl->window_width = opengl->width;
|
|
if (!opengl->window_height)
|
|
opengl->window_height = opengl->height;
|
|
|
|
if (!opengl->window_title && !opengl->no_window)
|
|
opengl->window_title = av_strdup(h->url);
|
|
|
|
if ((ret = opengl_create_window(h)))
|
|
goto fail;
|
|
|
|
if ((ret = opengl_read_limits(h)) < 0)
|
|
goto fail;
|
|
|
|
if (opengl->width > opengl->max_texture_size || opengl->height > opengl->max_texture_size) {
|
|
av_log(opengl, AV_LOG_ERROR, "Too big picture %dx%d, max supported size is %dx%d\n",
|
|
opengl->width, opengl->height, opengl->max_texture_size, opengl->max_texture_size);
|
|
ret = AVERROR(EINVAL);
|
|
goto fail;
|
|
}
|
|
|
|
if ((ret = opengl_load_procedures(opengl)) < 0)
|
|
goto fail;
|
|
|
|
opengl_fill_color_map(opengl);
|
|
opengl_get_texture_params(opengl);
|
|
|
|
if ((ret = opengl_init_context(opengl)) < 0)
|
|
goto fail;
|
|
|
|
if ((ret = opengl_prepare_vertex(h)) < 0)
|
|
goto fail;
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
#if CONFIG_SDL2
|
|
if (!opengl->no_window)
|
|
SDL_GL_SwapWindow(opengl->window);
|
|
#endif
|
|
if (opengl->no_window &&
|
|
(ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_DISPLAY_WINDOW_BUFFER, NULL , 0)) < 0) {
|
|
av_log(opengl, AV_LOG_ERROR, "Application failed to display window buffer.\n");
|
|
goto fail;
|
|
}
|
|
|
|
ret = AVERROR_EXTERNAL;
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
|
|
opengl->inited = 1;
|
|
return 0;
|
|
|
|
fail:
|
|
opengl_write_trailer(h);
|
|
return ret;
|
|
}
|
|
|
|
static uint8_t* opengl_get_plane_pointer(OpenGLContext *opengl, AVPacket *pkt, int comp_index,
|
|
const AVPixFmtDescriptor *desc)
|
|
{
|
|
uint8_t *data = pkt->data;
|
|
int wordsize = opengl_type_size(opengl->type);
|
|
int width_chroma = AV_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w);
|
|
int height_chroma = AV_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h);
|
|
int plane = desc->comp[comp_index].plane;
|
|
|
|
switch(plane) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
data += opengl->width * opengl->height * wordsize;
|
|
break;
|
|
case 2:
|
|
data += opengl->width * opengl->height * wordsize;
|
|
data += width_chroma * height_chroma * wordsize;
|
|
break;
|
|
case 3:
|
|
data += opengl->width * opengl->height * wordsize;
|
|
data += 2 * width_chroma * height_chroma * wordsize;
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
#define LOAD_TEXTURE_DATA(comp_index, sub) \
|
|
{ \
|
|
int width = sub ? AV_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w) : opengl->width; \
|
|
int height = sub ? AV_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h): opengl->height; \
|
|
uint8_t *data; \
|
|
int plane = desc->comp[comp_index].plane; \
|
|
\
|
|
glBindTexture(GL_TEXTURE_2D, opengl->texture_name[comp_index]); \
|
|
if (!is_pkt) { \
|
|
GLint length = ((AVFrame *)input)->linesize[plane]; \
|
|
int bytes_per_pixel = opengl_type_size(opengl->type); \
|
|
if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR)) \
|
|
bytes_per_pixel *= desc->nb_components; \
|
|
data = ((AVFrame *)input)->data[plane]; \
|
|
if (!(length % bytes_per_pixel) && \
|
|
(opengl->unpack_subimage || ((length / bytes_per_pixel) == width))) { \
|
|
length /= bytes_per_pixel; \
|
|
if (length != width) \
|
|
glPixelStorei(FF_GL_UNPACK_ROW_LENGTH, length); \
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, \
|
|
opengl->format, opengl->type, data); \
|
|
if (length != width) \
|
|
glPixelStorei(FF_GL_UNPACK_ROW_LENGTH, 0); \
|
|
} else { \
|
|
int h; \
|
|
for (h = 0; h < height; h++) { \
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, h, width, 1, \
|
|
opengl->format, opengl->type, data); \
|
|
data += length; \
|
|
} \
|
|
} \
|
|
} else { \
|
|
data = opengl_get_plane_pointer(opengl, input, comp_index, desc); \
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, \
|
|
opengl->format, opengl->type, data); \
|
|
} \
|
|
}
|
|
|
|
static int opengl_draw(AVFormatContext *h, void *input, int repaint, int is_pkt)
|
|
{
|
|
OpenGLContext *opengl = h->priv_data;
|
|
enum AVPixelFormat pix_fmt = h->streams[0]->codecpar->format;
|
|
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
|
|
int ret;
|
|
|
|
#if CONFIG_SDL2
|
|
/* At this point, opengl->glcontext implies opengl->glcontext */
|
|
if (opengl->glcontext)
|
|
SDL_GL_MakeCurrent(opengl->window, opengl->glcontext);
|
|
|
|
if (!opengl->no_window && (ret = opengl_sdl_process_events(h)) < 0)
|
|
goto fail;
|
|
#endif
|
|
if (opengl->no_window &&
|
|
(ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0)) < 0) {
|
|
av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n");
|
|
goto fail;
|
|
}
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
if (!repaint) {
|
|
if (is_pkt)
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
LOAD_TEXTURE_DATA(0, 0)
|
|
if (desc->flags & AV_PIX_FMT_FLAG_PLANAR) {
|
|
LOAD_TEXTURE_DATA(1, 1)
|
|
LOAD_TEXTURE_DATA(2, 1)
|
|
if (desc->flags & AV_PIX_FMT_FLAG_ALPHA)
|
|
LOAD_TEXTURE_DATA(3, 0)
|
|
}
|
|
}
|
|
ret = AVERROR_EXTERNAL;
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
|
|
if ((ret = opengl_prepare(opengl)) < 0)
|
|
goto fail;
|
|
|
|
opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, opengl->vertex_buffer);
|
|
opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, opengl->index_buffer);
|
|
opengl->glprocs.glVertexAttribPointer(opengl->position_attrib, 3, GL_FLOAT, GL_FALSE, sizeof(OpenGLVertexInfo), 0);
|
|
opengl->glprocs.glEnableVertexAttribArray(opengl->position_attrib);
|
|
opengl->glprocs.glVertexAttribPointer(opengl->texture_coords_attrib, 2, GL_FLOAT, GL_FALSE, sizeof(OpenGLVertexInfo), 12);
|
|
opengl->glprocs.glEnableVertexAttribArray(opengl->texture_coords_attrib);
|
|
|
|
glDrawElements(GL_TRIANGLES, FF_ARRAY_ELEMS(g_index), GL_UNSIGNED_SHORT, 0);
|
|
|
|
ret = AVERROR_EXTERNAL;
|
|
OPENGL_ERROR_CHECK(opengl);
|
|
|
|
#if CONFIG_SDL2
|
|
if (!opengl->no_window)
|
|
SDL_GL_SwapWindow(opengl->window);
|
|
#endif
|
|
if (opengl->no_window &&
|
|
(ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_DISPLAY_WINDOW_BUFFER, NULL , 0)) < 0) {
|
|
av_log(opengl, AV_LOG_ERROR, "Application failed to display window buffer.\n");
|
|
goto fail;
|
|
}
|
|
|
|
return 0;
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static int opengl_write_packet(AVFormatContext *h, AVPacket *pkt)
|
|
{
|
|
AVCodecParameters *par = h->streams[0]->codecpar;
|
|
if (par->codec_id == AV_CODEC_ID_WRAPPED_AVFRAME) {
|
|
AVFrame *frame = (AVFrame *)pkt->data;
|
|
return opengl_draw(h, frame, 0, 0);
|
|
} else {
|
|
return opengl_draw(h, pkt, 0, 1);
|
|
}
|
|
}
|
|
|
|
static int opengl_write_frame(AVFormatContext *h, int stream_index,
|
|
AVFrame **frame, unsigned flags)
|
|
{
|
|
if ((flags & AV_WRITE_UNCODED_FRAME_QUERY))
|
|
return 0;
|
|
return opengl_draw(h, *frame, 0, 0);
|
|
}
|
|
|
|
#define OFFSET(x) offsetof(OpenGLContext, x)
|
|
#define ENC AV_OPT_FLAG_ENCODING_PARAM
|
|
static const AVOption options[] = {
|
|
{ "background", "set background color", OFFSET(background), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, ENC },
|
|
{ "no_window", "disable default window", OFFSET(no_window), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, ENC },
|
|
{ "window_title", "set window title", OFFSET(window_title), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, ENC },
|
|
{ "window_size", "set window size", OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, ENC },
|
|
{ NULL }
|
|
};
|
|
|
|
static const AVClass opengl_class = {
|
|
.class_name = "opengl outdev",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
.category = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
|
|
};
|
|
|
|
const FFOutputFormat ff_opengl_muxer = {
|
|
.p.name = "opengl",
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("OpenGL output"),
|
|
.p.audio_codec = AV_CODEC_ID_NONE,
|
|
.p.video_codec = AV_CODEC_ID_WRAPPED_AVFRAME,
|
|
.p.flags = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
|
|
.p.priv_class = &opengl_class,
|
|
.priv_data_size = sizeof(OpenGLContext),
|
|
.write_header = opengl_write_header,
|
|
.write_packet = opengl_write_packet,
|
|
.write_uncoded_frame = opengl_write_frame,
|
|
.write_trailer = opengl_write_trailer,
|
|
.control_message = opengl_control_message,
|
|
};
|