Skip to content

Instantly share code, notes, and snippets.

@trikko
Last active November 5, 2023 00:34
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save trikko/738d0a5e76458cab39218a5055f02416 to your computer and use it in GitHub Desktop.
Save trikko/738d0a5e76458cab39218a5055f02416 to your computer and use it in GitHub Desktop.
How to render a video with raylib and gstreamer
#include "raylib.h"
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
GstElement* createPipeline(const char* filename)
{
GError *error;
gchar *pipelineString = g_strdup_printf ("filesrc location=%s ! tee name=t ! queue ! decodebin ! videoconvert ! appsink name=output caps=video/x-raw,format=RGBA,pixel-aspect-ratio=1/1 t. ! queue ! decodebin ! audioconvert ! audioresample ! autoaudiosink", filename);
GstElement *pipeline = gst_parse_launch (pipelineString, &error);
if (error != NULL) {
g_print ("Something goes wrong with pipeline initialization: %s\n", error->message);
g_clear_error (&error);
exit (-1);
}
g_free(pipelineString);
return pipeline;
}
void restartPipeline(GstElement *pipeline)
{
gst_element_seek_simple (pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, 0);
gst_element_set_state (pipeline, GST_STATE_PLAYING);
}
int main(int argc, char** argv)
{
// Gstreamer Initialization
gst_init (&argc, &argv);
GstElement *pipeline = createPipeline("BigBuckBunny.mp4");
GstElement *sink = gst_bin_get_by_name(GST_BIN (pipeline), "output");
GstMapInfo mapInfo;
gint64 duration, position;
int frameWidth = 0, frameHeight = 0;
int renderWidth = 0, renderHeight = 0;
float renderScale = -1.0f;
int status = 0;
// Raylib Initialization
//--------------------------------------------------------------------------------------
RenderTexture2D renderTexture;
int screenWidth = 800;
int screenHeight = 450;
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
InitWindow(screenWidth, screenHeight, "raylib + gstreamer");
// Start video
gst_element_set_state(pipeline, GST_STATE_PLAYING);
restartPipeline(pipeline);
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update duration & position
gst_element_query_duration(pipeline, GST_FORMAT_TIME, &duration);
gst_element_query_position(pipeline, GST_FORMAT_TIME, &position);
// Keyboard events
//----------------------------------------------------------------------------------
if (IsKeyPressed(KEY_SPACE))
{
GstState state;
gst_element_get_state (pipeline, &state, NULL, 1);
if (state == GST_STATE_PLAYING)
{
status = 1;
gst_element_set_state(pipeline, GST_STATE_PAUSED);
}
else
{
status = 0;
gst_element_set_state (pipeline, GST_STATE_PLAYING);
}
}
else if (IsKeyPressed(KEY_R))
{
status = 2;
restartPipeline(pipeline);
}
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
{
Vector2 mouse = GetMousePosition();
if (mouse.x >= 5 && mouse.x <= screenWidth-5 && mouse.y>=screenHeight-15 && mouse.y<=screenHeight-5)
gst_element_seek_simple (pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, (gint64)((1.0f*(mouse.x - 5)/(screenWidth-10))*duration));
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
// Read a sample, if available
GstSample* sample = gst_app_sink_try_pull_sample(GST_APP_SINK(sink), 1);
if (IsWindowResized()) renderScale = -1.0f;
// First time: get frame width & height from video sample
if (sample != NULL && frameWidth == 0)
{
GstCaps *caps = gst_sample_get_caps (sample);
GstStructure *s = gst_caps_get_structure (caps, 0);
gst_structure_get_int (s, "width", &frameWidth);
gst_structure_get_int (s, "height", &frameHeight);
renderTexture = LoadRenderTexture(frameWidth, frameHeight);
}
// Scale video to fit window if needed
if (renderScale < 0 && frameWidth > 0)
{
screenWidth = GetScreenWidth();
screenHeight = GetScreenHeight();
renderWidth = screenWidth;
renderHeight = (int)(screenWidth * 1.0f*frameHeight/frameWidth);
if (renderHeight > screenHeight)
{
renderHeight = screenHeight;
renderWidth = (int)(screenHeight * 1.0f*frameWidth/frameHeight);
}
renderScale = 1.0f*renderWidth/frameWidth;
}
// Render a frame from video, if available.
if (sample != NULL)
{
GstBuffer *buffer = gst_sample_get_buffer(sample);
GstMapInfo mapInfo;
if (gst_buffer_map (buffer, &mapInfo, GST_MAP_READ))
{
UpdateTexture(renderTexture.texture, mapInfo.data);
gst_buffer_unmap (buffer, &mapInfo);
}
gst_sample_unref(sample);
}
DrawTextureEx(renderTexture.texture, (Vector2){(screenWidth-renderWidth)/2, (screenHeight-renderHeight)/2 }, 0, 1.0f*renderWidth/frameWidth, WHITE);
// Draw progress
DrawRectangle(5, screenHeight-15, screenWidth-10, 10, RAYWHITE);
DrawRectangle(5, screenHeight-15, (int)((screenWidth-10)*(1.0f*position/duration)), 10, BLUE);
// Draw the texts
DrawRectangle(4,4,222,82,GRAY);
DrawRectangle(5,5,220,80,RAYWHITE);
DrawText("Press SPACE to play/pause video", 15, 15, 10, GRAY);
DrawText("Press R to restart video", 15, 30, 10, GRAY);
// Draw status
switch (status)
{
case 0: DrawText("PLAYING", 15, 60, 20, GREEN); break;
case 1: DrawText("PAUSED", 15, 60, 20, BLUE); break;
case 2: DrawText("RESTARTED", 15, 60, 20, GREEN); break;
}
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
gst_deinit();
UnloadRenderTexture(renderTexture);
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment