Skip to content

Instantly share code, notes, and snippets.

@Beyley
Created June 21, 2022 01:43
Show Gist options
  • Save Beyley/eb83d9d5f138dfca36c284b7831333b5 to your computer and use it in GitHub Desktop.
Save Beyley/eb83d9d5f138dfca36c284b7831333b5 to your computer and use it in GitHub Desktop.
OpenGL FFmpeg integration, for modern FFmpeg
/*
based on github gist from rcolinray: https://gist.github.com/rcolinray/7552384
you need to build with glfw2, not glfw3.
adjusted for newer ffmpeg ( version>55 - avcodec_alloc_frame now is
av_frame_alloc, and some other defines have AV_ prefix added to it )
build it using something like:
g++ ./gl_ffmpeg.cpp \
-lavcodec -lavutil \
-lavformat \
-lavfilter \
-lswscale \
-lGL \
-lGLU \
-lGLEW \
-lglfw2 \
-ggdb -DDEBUG \
-fpermissive -g -o gl_ffmpeg
*/
// Use OpenGL 3.0+, but don't use GLU
//#define GLFW_INCLUDE_GL3
#define GLFW_NO_GLU
#include <GL/glew.h>
#include <GL/glfw.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <sys/time.h>
}
#include <iostream>
#include <fstream>
#include <string>
std::string const vert_shader_source =
"#version 150\n"
"in vec3 vertex;\n"
"in vec2 texCoord0;\n"
"uniform mat4 mvpMatrix;\n"
"out vec2 texCoord;\n"
"void main() {\n"
" gl_Position = mvpMatrix * vec4(vertex, 1.0);\n"
" texCoord = texCoord0;\n"
"}\n";
std::string const frag_shader_source =
"#version 150\n"
"uniform sampler2D frameTex;\n"
"in vec2 texCoord;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = texture(frameTex, texCoord);\n"
"}\n";
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
// attribute indices
enum {
VERTICES = 0,
TEX_COORDS
};
// uniform indices
enum {
MVP_MATRIX = 0,
FRAME_TEX
};
// app data structure
typedef struct {
AVFormatContext *fmt_ctx;
int stream_idx;
AVStream *video_stream;
AVCodecContext *codec_ctx;
const AVCodec *decoder;
AVPacket *packet;
AVFrame *av_frame;
AVFrame *gl_frame;
struct SwsContext *conv_ctx;
GLuint vao;
GLuint vert_buf;
GLuint elem_buf;
GLuint frame_tex;
GLuint program;
GLuint attribs[2];
GLuint uniforms[2];
} AppData;
// initialize the app data structure
void initializeAppData(AppData *data) {
data->fmt_ctx = NULL;
data->stream_idx = -1;
data->video_stream = NULL;
data->codec_ctx = NULL;
data->decoder = NULL;
data->av_frame = NULL;
data->gl_frame = NULL;
data->conv_ctx = NULL;
}
// clean up the app data structure
void clearAppData(AppData *data) {
if (data->av_frame) av_free(data->av_frame);
if (data->gl_frame) av_free(data->gl_frame);
if (data->packet) av_free(data->packet);
if (data->codec_ctx) avcodec_close(data->codec_ctx);
if (data->fmt_ctx) avformat_free_context(data->fmt_ctx);
glDeleteVertexArrays(1, &data->vao);
glDeleteBuffers(1, &data->vert_buf);
glDeleteBuffers(1, &data->elem_buf);
glDeleteTextures(1, &data->frame_tex);
initializeAppData(data);
}
// read a video frame
bool readFrame(AppData *data) {
do {
if (av_read_frame(data->fmt_ctx, data->packet) < 0) {
av_packet_unref(data->packet);
return false;
}
if (data->packet->stream_index == data->stream_idx) {
int frame_finished = 0;
// if(avcodec_send_packet(data->codec_ctx, data->packet) < 0) {
// if (avcodec_decode_video2(data->codec_ctx, data->av_frame, &frame_finished, data->packet) < 0) {
// av_packet_unref(data->packet);
// return false;
// }
int response;
if (data->codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO ||
data->codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
response = avcodec_send_packet(data->codec_ctx, data->packet);
if (response < 0 && response != AVERROR(EAGAIN) && response != AVERROR_EOF) {
} else {
if (response >= 0)
data->packet->size = 0;
response = avcodec_receive_frame(data->codec_ctx, data->av_frame);
if (response >= 0)
frame_finished = 1;
//if (response == AVERROR(EAGAIN) || response == AVERROR_EOF)
//response = 0;
}
}
if (frame_finished) {
if (!data->conv_ctx) {
data->conv_ctx = sws_getContext(data->codec_ctx->width,
data->codec_ctx->height, data->codec_ctx->pix_fmt,
data->codec_ctx->width, data->codec_ctx->height, AV_PIX_FMT_RGB24,
SWS_BICUBIC, NULL, NULL, NULL);
}
sws_scale(data->conv_ctx, data->av_frame->data, data->av_frame->linesize, 0,
data->codec_ctx->height, data->gl_frame->data, data->gl_frame->linesize);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, data->codec_ctx->width,
data->codec_ctx->height, GL_RGB, GL_UNSIGNED_BYTE,
data->gl_frame->data[0]);
}
}
av_packet_unref(data->packet);
} while (data->packet->stream_index != data->stream_idx);
return true;
}
bool buildShader(std::string const &shader_source, GLuint &shader, GLenum type) {
int size = shader_source.length();
shader = glCreateShader(type);
char const *c_shader_source = shader_source.c_str();
glShaderSource(shader, 1, (GLchar const **)&c_shader_source, &size);
glCompileShader(shader);
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status != GL_TRUE) {
std::cout << "failed to compile shader" << std::endl;
int length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
char *log = new char[length];
glGetShaderInfoLog(shader, length, &length, log);
std::cout << log << std::endl;
delete[] log;
return false;
}
return true;
}
// initialize shaders
bool buildProgram(AppData *data) {
GLuint v_shader, f_shader;
if (!buildShader(vert_shader_source, v_shader, GL_VERTEX_SHADER)) {
std::cout << "failed to build vertex shader" << std::endl;
return false;
}
if (!buildShader(frag_shader_source, f_shader, GL_FRAGMENT_SHADER)) {
std::cout << "failed to build fragment shader" << std::endl;
return false;
}
data->program = glCreateProgram();
glAttachShader(data->program, v_shader);
glAttachShader(data->program, f_shader);
glLinkProgram(data->program);
GLint status;
glGetProgramiv(data->program, GL_LINK_STATUS, &status);
if (status != GL_TRUE) {
std::cout << "failed to link program" << std::endl;
int length;
glGetProgramiv(data->program, GL_INFO_LOG_LENGTH, &length);
char *log = new char[length];
glGetShaderInfoLog(data->program, length, &length, log);
std::cout << log << std::endl;
delete[] log;
return false;
}
data->uniforms[MVP_MATRIX] = glGetUniformLocation(data->program, "mvpMatrix");
data->uniforms[FRAME_TEX] = glGetUniformLocation(data->program, "frameTex");
data->attribs[VERTICES] = glGetAttribLocation(data->program, "vertex");
data->attribs[TEX_COORDS] = glGetAttribLocation(data->program, "texCoord0");
return true;
}
// draw frame in opengl context
void drawFrame(AppData *data) {
glClear(GL_COLOR_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, data->frame_tex);
glBindVertexArray(data->vao);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
glBindVertexArray(0);
glfwSwapBuffers();
}
void glew_init(){
GLenum err = glewInit();
if (GLEW_OK != err)
{
/* Problem: glewInit failed, something is seriously wrong. */
std::cout << "Error: " << glewGetErrorString(err) << std::endl;
}
std::cout << "Status: Using GLEW " << glewGetString(GLEW_VERSION) << std::endl;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
std::cout << "provide a filename" << std::endl;
return -1;
}
// initialize libav
// av_register_all(); //no longer needed since 4.0
avformat_network_init();
// initialize custom data structure
AppData data;
initializeAppData(&data);
// open video
if (avformat_open_input(&data.fmt_ctx, argv[1], NULL, NULL) < 0) {
std::cout << "failed to open input" << std::endl;
clearAppData(&data);
return -1;
}
// find stream info
if (avformat_find_stream_info(data.fmt_ctx, NULL) < 0) {
std::cout << "failed to get stream info" << std::endl;
clearAppData(&data);
return -1;
}
// dump debug info
av_dump_format(data.fmt_ctx, 0, argv[1], 0);
std::cout << "Amount of streams: " << data.fmt_ctx->nb_streams << std::endl;
// find the video stream
for (unsigned int i = 0; i < data.fmt_ctx->nb_streams; ++i)
{
std::cout << "stream: " << data.fmt_ctx->streams[i]->codecpar->codec_type << " i:" << i << std::endl;
if (data.fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
std::cout << "found stream: " << data.fmt_ctx->streams[i]->codecpar->codec_type << " i:" << i << std::endl;
data.stream_idx = i;
break;
}
}
if (data.stream_idx == -1)
{
std::cout << "failed to find video stream" << std::endl;
clearAppData(&data);
return -1;
}
data.video_stream = data.fmt_ctx->streams[data.stream_idx];
// data.codec_ctx = data.video_stream->codecpar->;
data.codec_ctx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(data.codec_ctx, data.video_stream->codecpar);
// find the decoder
data.decoder = avcodec_find_decoder(data.codec_ctx->codec_id);
if (data.decoder == NULL)
{
std::cout << "failed to find decoder" << std::endl;
clearAppData(&data);
return -1;
}
// open the decoder
if (avcodec_open2(data.codec_ctx, data.decoder, NULL) < 0)
{
std::cout << "failed to open codec" << std::endl;
clearAppData(&data);
return -1;
}
// allocate the video frames
data.av_frame = av_frame_alloc();
data.gl_frame = av_frame_alloc();
int size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, data.codec_ctx->width,
data.codec_ctx->height, 1);
uint8_t *internal_buffer = (uint8_t *)av_malloc(size * sizeof(uint8_t));
av_image_fill_arrays(data.gl_frame->data, data.gl_frame->linesize, internal_buffer, AV_PIX_FMT_RGB24, data.codec_ctx->width, data.codec_ctx->height, 1);
data.packet = av_packet_alloc();
if(data.packet == NULL) {
std::cout << "Alloccing packet failed" << std::endl;
return -1;
}
// initialize glfw
if (!glfwInit()) {
std::cout << "glfw failed to init" << std::endl;
glfwTerminate();
clearAppData(&data);
return -1;
}
// open a window
float aspect = (float)data.codec_ctx->width / (float)data.codec_ctx->height;
int adj_width = aspect * 300;
int adj_height = 300;
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 2);
glfwOpenWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
if (!glfwOpenWindow(adj_width, adj_height, 0, 0, 0, 0, 0, 0, GLFW_WINDOW)) {
std::cout << "failed to open window" << std::endl;
glfwTerminate();
clearAppData(&data);
return -1;
}
glew_init();
// initialize opengl
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glEnable(GL_TEXTURE_2D);
// initialize shaders
if (!buildProgram(&data)) {
std::cout << "failed to initialize shaders" << std::endl;
glfwTerminate();
clearAppData(&data);
return -1;
}
glUseProgram(data.program);
// initialize renderable
glGenVertexArrays(1, &data.vao);
glBindVertexArray(data.vao);
glGenBuffers(1, &data.vert_buf);
glBindBuffer(GL_ARRAY_BUFFER, data.vert_buf);
float quad[20] = {
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f, 0.0f
};
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
glVertexAttribPointer(data.attribs[VERTICES], 3, GL_FLOAT, GL_FALSE, 20,
BUFFER_OFFSET(0));
glEnableVertexAttribArray(data.attribs[VERTICES]);
glVertexAttribPointer(data.attribs[TEX_COORDS], 2, GL_FLOAT, GL_FALSE, 20,
BUFFER_OFFSET(12));
glEnableVertexAttribArray(data.attribs[TEX_COORDS]);
glGenBuffers(1, &data.elem_buf);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.elem_buf);
unsigned char elem[6] = {
0, 1, 2,
0, 2, 3
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elem), elem, GL_STATIC_DRAW);
glBindVertexArray(0);
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &data.frame_tex);
glBindTexture(GL_TEXTURE_2D, data.frame_tex);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, data.codec_ctx->width, data.codec_ctx->height,
0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glUniform1i(data.uniforms[FRAME_TEX], 0);
glm::mat4 mvp = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
glUniformMatrix4fv(data.uniforms[MVP_MATRIX], 1, GL_FALSE, glm::value_ptr(mvp));
bool running = true;
// run the application mainloop
while (readFrame(&data) && running) {
running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
drawFrame(&data);
}
std::cout << "Cleaning up!" << std::endl;
avformat_close_input(&data.fmt_ctx);
// clean up
glfwTerminate();
clearAppData(&data);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment