You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
391 lines
12 KiB
391 lines
12 KiB
//g++ sdl_opengl_player.cpp $(pkg-config --cflags --libs libvlc sdl2 gl)
|
|
|
|
/* SPDX-License-Identifier: WTFPL */
|
|
/* Written by Pierre Lamot */
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
#include <exception>
|
|
#include <mutex>
|
|
#include <vector>
|
|
|
|
#include <SDL2/SDL.h>
|
|
#define GL_GLEXT_PROTOTYPES 1
|
|
#include <SDL2/SDL_opengl.h>
|
|
#include <vlc/vlc.h>
|
|
|
|
/*
|
|
* This program show how to use libvlc_video_set_output_callbacks API.
|
|
*
|
|
* The main idea is to set up libvlc to render into FBO, and to use a
|
|
* triple buffer mechanism to share textures between VLC and the rendering
|
|
* thread of our application
|
|
*/
|
|
|
|
// Shader sources
|
|
const GLchar* vertexSource =
|
|
"attribute vec4 a_position; \n"
|
|
"attribute vec2 a_uv; \n"
|
|
"varying vec2 v_TexCoordinate; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" v_TexCoordinate = a_uv; \n"
|
|
" gl_Position = vec4(a_position.xyz, 1.0); \n"
|
|
"} \n";
|
|
|
|
const GLchar* fragmentSource =
|
|
"uniform sampler2D u_videotex; \n"
|
|
"varying vec2 v_TexCoordinate; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" gl_FragColor = texture2D(u_videotex, v_TexCoordinate); \n"
|
|
"} \n";
|
|
|
|
class VLCVideo
|
|
{
|
|
public:
|
|
VLCVideo(SDL_Window *window):
|
|
m_win(window)
|
|
{
|
|
const char *args[] = {
|
|
"--verbose=4"
|
|
};
|
|
m_vlc = libvlc_new(sizeof(args) / sizeof(*args), args);
|
|
|
|
//VLC opengl context needs to be shared with SDL context
|
|
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
|
|
m_ctx = SDL_GL_CreateContext(window);
|
|
}
|
|
|
|
~VLCVideo()
|
|
{
|
|
stop();
|
|
if (m_vlc)
|
|
libvlc_release(m_vlc);
|
|
}
|
|
|
|
bool playMedia(const char* url)
|
|
{
|
|
m_media = libvlc_media_new_location(url);
|
|
if (m_media == NULL) {
|
|
fprintf(stderr, "unable to create media %s", url);
|
|
return false;
|
|
}
|
|
m_mp = libvlc_media_player_new_from_media (m_vlc, m_media);
|
|
if (m_mp == NULL) {
|
|
fprintf(stderr, "unable to create media player");
|
|
libvlc_media_release(m_media);
|
|
return false;
|
|
}
|
|
// Define the opengl rendering callbacks
|
|
libvlc_video_set_output_callbacks(m_mp, libvlc_video_engine_opengl,
|
|
setup, cleanup, nullptr, resize, swap,
|
|
make_current, get_proc_address, nullptr, nullptr,
|
|
this);
|
|
|
|
// Play the video
|
|
libvlc_media_player_play (m_mp);
|
|
|
|
return true;
|
|
}
|
|
|
|
void stop()
|
|
{
|
|
if (m_mp) {
|
|
libvlc_media_player_release(m_mp);
|
|
m_mp = nullptr;
|
|
}
|
|
if (m_media) {
|
|
libvlc_media_release(m_media);
|
|
m_media = nullptr;
|
|
}
|
|
}
|
|
|
|
/// return the texture to be displayed
|
|
GLuint getVideoFrame(bool* out_updated)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_text_lock);
|
|
if (out_updated)
|
|
*out_updated = m_updated;
|
|
if (m_updated) {
|
|
std::swap(m_idx_swap, m_idx_display);
|
|
m_updated = false;
|
|
}
|
|
return m_tex[m_idx_display];
|
|
}
|
|
|
|
/// this callback will create the surfaces and FBO used by VLC to perform its rendering
|
|
static bool resize(void* data, const libvlc_video_render_cfg_t *cfg,
|
|
libvlc_video_output_cfg_t *render_cfg)
|
|
{
|
|
VLCVideo* that = static_cast<VLCVideo*>(data);
|
|
if (cfg->width != that->m_width || cfg->height != that->m_height)
|
|
cleanup(data);
|
|
|
|
glGenTextures(3, that->m_tex);
|
|
glGenFramebuffers(3, that->m_fbo);
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
glBindTexture(GL_TEXTURE_2D, that->m_tex[i]);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cfg->width, cfg->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, that->m_fbo[i]);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, that->m_tex[i], 0);
|
|
}
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
|
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
|
return false;
|
|
}
|
|
|
|
that->m_width = cfg->width;
|
|
that->m_height = cfg->height;
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, that->m_fbo[that->m_idx_render]);
|
|
|
|
render_cfg->opengl_format = GL_RGBA;
|
|
render_cfg->full_range = true;
|
|
render_cfg->colorspace = libvlc_video_colorspace_BT709;
|
|
render_cfg->primaries = libvlc_video_primaries_BT709;
|
|
render_cfg->transfer = libvlc_video_transfer_func_SRGB;
|
|
render_cfg->orientation = libvlc_video_orient_top_left;
|
|
|
|
return true;
|
|
}
|
|
|
|
// This callback is called during initialisation.
|
|
static bool setup(void** data, const libvlc_video_setup_device_cfg_t *cfg,
|
|
libvlc_video_setup_device_info_t *out)
|
|
{
|
|
VLCVideo* that = static_cast<VLCVideo*>(*data);
|
|
that->m_width = 0;
|
|
that->m_height = 0;
|
|
return true;
|
|
}
|
|
|
|
|
|
// This callback is called to release the texture and FBO created in resize
|
|
static void cleanup(void* data)
|
|
{
|
|
VLCVideo* that = static_cast<VLCVideo*>(data);
|
|
if (that->m_width == 0 && that->m_height == 0)
|
|
return;
|
|
|
|
glDeleteTextures(3, that->m_tex);
|
|
glDeleteFramebuffers(3, that->m_fbo);
|
|
}
|
|
|
|
//This callback is called after VLC performs drawing calls
|
|
static void swap(void* data)
|
|
{
|
|
VLCVideo* that = static_cast<VLCVideo*>(data);
|
|
std::lock_guard<std::mutex> lock(that->m_text_lock);
|
|
that->m_updated = true;
|
|
std::swap(that->m_idx_swap, that->m_idx_render);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, that->m_fbo[that->m_idx_render]);
|
|
}
|
|
|
|
// This callback is called to set the OpenGL context
|
|
static bool make_current(void* data, bool current)
|
|
{
|
|
VLCVideo* that = static_cast<VLCVideo*>(data);
|
|
if (current)
|
|
return SDL_GL_MakeCurrent(that->m_win, that->m_ctx) == 0;
|
|
else
|
|
return SDL_GL_MakeCurrent(that->m_win, 0) == 0;
|
|
}
|
|
|
|
// This callback is called by VLC to get OpenGL functions.
|
|
static void* get_proc_address(void* /*data*/, const char* current)
|
|
{
|
|
return SDL_GL_GetProcAddress(current);
|
|
}
|
|
|
|
private:
|
|
//VLC objects
|
|
libvlc_instance_t* m_vlc = nullptr;
|
|
libvlc_media_player_t* m_mp = nullptr;
|
|
libvlc_media_t* m_media = nullptr;
|
|
|
|
//FBO data
|
|
unsigned m_width = 0;
|
|
unsigned m_height = 0;
|
|
std::mutex m_text_lock;
|
|
GLuint m_tex[3];
|
|
GLuint m_fbo[3];
|
|
size_t m_idx_render = 0;
|
|
size_t m_idx_swap = 1;
|
|
size_t m_idx_display = 2;
|
|
bool m_updated = false;
|
|
|
|
//SDL context
|
|
SDL_Window* m_win;
|
|
SDL_GLContext m_ctx;
|
|
};
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
if (argc != 2) {
|
|
fprintf(stderr, "usage: %s <uri>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
SDL_Window* wnd = SDL_CreateWindow("test",
|
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
SDL_GL_SetSwapInterval(0);
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
|
|
SDL_GLContext glc = SDL_GL_CreateContext(wnd);
|
|
|
|
VLCVideo video(wnd);
|
|
|
|
SDL_Renderer* rdr = SDL_CreateRenderer(
|
|
wnd, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
|
|
|
// Create Vertex Array Object
|
|
GLuint vao;
|
|
glGenVertexArrays(1, &vao);
|
|
glBindVertexArray(vao);
|
|
|
|
// Create a Vertex Buffer Object and copy the vertex data to it
|
|
GLuint vbo;
|
|
glGenBuffers(1, &vbo);
|
|
|
|
//vertex X, vertex Y, UV X, UV Y
|
|
GLfloat vertices[] = {
|
|
-0.5f, 0.5f, 0.f, 1.f,
|
|
-0.5f, -0.5f, 0.f, 0.f,
|
|
0.5f, 0.5f, 1.f, 1.f,
|
|
0.5f, -0.5f, 1.f, 0.f
|
|
};
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
|
|
|
// Create and compile the vertex shader
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShader, 1, &vertexSource, NULL);
|
|
glCompileShader(vertexShader);
|
|
|
|
// Create and compile the fragment shader
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
|
|
glCompileShader(fragmentShader);
|
|
|
|
{
|
|
GLuint shader[] = {vertexShader, fragmentShader};
|
|
const char *shaderName[] = {"vertex", "fragment"};
|
|
for (int i = 0; i < 2; i++) {
|
|
int len;
|
|
glGetShaderiv(shader[i], GL_INFO_LOG_LENGTH, &len);
|
|
if (len <= 1)
|
|
continue;
|
|
std::vector<char> infoLog(len);
|
|
int charsWritten;
|
|
glGetShaderInfoLog(shader[i], len, &charsWritten, infoLog.data());
|
|
fprintf(stderr, "%s shader info log: %s\n", shaderName[i], infoLog.data());
|
|
|
|
GLint status = GL_TRUE;
|
|
glGetShaderiv(shader[i], GL_COMPILE_STATUS, &status);
|
|
if (status == GL_FALSE) {
|
|
fprintf(stderr, "compile %s shader failed\n", shaderName[i]);
|
|
SDL_DestroyWindow(wnd);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Link the vertex and fragment shader into a shader program
|
|
GLuint shaderProgram = glCreateProgram();
|
|
glAttachShader(shaderProgram, vertexShader);
|
|
glAttachShader(shaderProgram, fragmentShader);
|
|
glLinkProgram(shaderProgram);
|
|
|
|
{
|
|
int len;
|
|
glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &len);
|
|
if (len > 1) {
|
|
std::vector<char> infoLog(len);
|
|
int charsWritten;
|
|
glGetProgramInfoLog(shaderProgram, len, &charsWritten, infoLog.data());
|
|
fprintf(stderr, "shader program: %s\n", infoLog.data());
|
|
}
|
|
|
|
GLint status = GL_TRUE;
|
|
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status);
|
|
if (status == GL_FALSE) {
|
|
fprintf(stderr, "unable to use program\n");
|
|
SDL_DestroyWindow(wnd);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
glUseProgram(shaderProgram);
|
|
|
|
// Specify the layout of the vertex data
|
|
GLint posAttrib = glGetAttribLocation(shaderProgram, "a_position");
|
|
glEnableVertexAttribArray(posAttrib);
|
|
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), 0);
|
|
|
|
// Specify the layout of the vertex data
|
|
GLint uvAttrib = glGetAttribLocation(shaderProgram, "a_uv");
|
|
glEnableVertexAttribArray(uvAttrib);
|
|
glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), (GLvoid*)(2*sizeof(float)));
|
|
|
|
// Specify the texture of the video
|
|
GLint textUniform = glGetUniformLocation(shaderProgram, "u_videotex");
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glUniform1i(textUniform, /*GL_TEXTURE*/0);
|
|
|
|
//start playing the video
|
|
if (!video.playMedia(argv[1])) {
|
|
SDL_DestroyWindow(wnd);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
bool updated = false;
|
|
bool quit = false;
|
|
while(!quit) {
|
|
SDL_Event e;
|
|
while(SDL_PollEvent(&e)) {
|
|
if(e.type == SDL_QUIT)
|
|
quit = true;
|
|
}
|
|
|
|
// Clear the screen to black
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// Get the current video texture and bind it
|
|
GLuint tex = video.getVideoFrame(&updated);
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
|
|
// Draw the video rectangle
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
SDL_GL_SwapWindow(wnd);
|
|
};
|
|
|
|
video.stop();
|
|
|
|
SDL_DestroyWindow(wnd);
|
|
SDL_Quit();
|
|
|
|
return 0;
|
|
}
|
|
|