refactor: video player code to video.c

This commit is contained in:
2026-05-26 12:19:25 +05:30
parent c96c361973
commit 433ccfb0fe
4 changed files with 241 additions and 224 deletions

View File

@@ -18,7 +18,7 @@ ALL_LIBS := $(LIBS) $(SDL_LIBS) $(FFMPEG_LIBS) -lm
BUILD_DIR ?= build
TARGET = $(BUILD_DIR)/gsplash
DUMMY_TARGET = $(BUILD_DIR)/dummy_game
SRC = src/gsplash.c
SRC = src/gsplash.c src/video.c
DUMMY_SRC = src/dummy_game.c
.PHONY: all clean install uninstall check

View File

@@ -11,10 +11,7 @@
#include <stdbool.h>
#include <ctype.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include "video.h"
static void log_info(const char *fmt, ...)
{
@@ -58,45 +55,6 @@ static RenderMode parse_render_mode(const char *value)
return RENDER_STRETCH;
}
typedef struct VideoPlayer
{
AVFormatContext *format_ctx;
AVCodecContext *codec_ctx;
struct SwsContext *sws_ctx;
AVFrame *frame;
AVFrame *rgba_frame;
uint8_t *rgba_buffer;
int rgba_buffer_size;
int stream_index;
int width;
int height;
int frame_delay_ms;
Uint32 next_frame_tick;
SDL_Texture *texture;
} VideoPlayer;
static bool has_video_extension(const char *path)
{
const char *dot = strrchr(path, '.');
if (!dot || dot == path)
{
return false;
}
char ext[8];
size_t i = 0;
dot++;
while (*dot && i < sizeof(ext) - 1)
{
ext[i++] = (char)tolower((unsigned char)*dot++);
}
ext[i] = '\0';
return strcmp(ext, "mp4") == 0 || strcmp(ext, "mkv") == 0 || strcmp(ext, "webm") == 0 ||
strcmp(ext, "avi") == 0 || strcmp(ext, "mov") == 0 || strcmp(ext, "mpg") == 0 ||
strcmp(ext, "mpeg") == 0;
}
static void compute_dest_rect(int src_w, int src_h, int out_w, int out_h, RenderMode mode, SDL_Rect *dst)
{
dst->x = 0;
@@ -127,186 +85,6 @@ static void compute_dest_rect(int src_w, int src_h, int out_w, int out_h, Render
}
}
static bool init_video_player(VideoPlayer *player, SDL_Renderer *renderer, const char *path)
{
memset(player, 0, sizeof(*player));
if (avformat_open_input(&player->format_ctx, path, NULL, NULL) != 0)
{
return false;
}
if (avformat_find_stream_info(player->format_ctx, NULL) < 0)
{
return false;
}
player->stream_index = -1;
for (unsigned int i = 0; i < player->format_ctx->nb_streams; ++i)
{
if (player->format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
player->stream_index = (int)i;
break;
}
}
if (player->stream_index < 0)
{
return false;
}
AVCodecParameters *codecpar = player->format_ctx->streams[player->stream_index]->codecpar;
const AVCodec *decoder = avcodec_find_decoder(codecpar->codec_id);
if (!decoder)
{
return false;
}
player->codec_ctx = avcodec_alloc_context3(decoder);
if (!player->codec_ctx)
{
return false;
}
if (avcodec_parameters_to_context(player->codec_ctx, codecpar) < 0)
{
return false;
}
if (avcodec_open2(player->codec_ctx, decoder, NULL) < 0)
{
return false;
}
player->width = player->codec_ctx->width;
player->height = player->codec_ctx->height;
player->frame = av_frame_alloc();
player->rgba_frame = av_frame_alloc();
if (!player->frame || !player->rgba_frame)
{
return false;
}
player->rgba_buffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, player->width, player->height, 1);
player->rgba_buffer = (uint8_t *)av_malloc(player->rgba_buffer_size);
if (!player->rgba_buffer)
{
return false;
}
av_image_fill_arrays(player->rgba_frame->data, player->rgba_frame->linesize, player->rgba_buffer,
AV_PIX_FMT_RGBA, player->width, player->height, 1);
player->sws_ctx = sws_getContext(player->width, player->height, player->codec_ctx->pix_fmt,
player->width, player->height, AV_PIX_FMT_RGBA, SWS_BILINEAR,
NULL, NULL, NULL);
if (!player->sws_ctx)
{
return false;
}
player->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING, player->width, player->height);
if (!player->texture)
{
return false;
}
AVRational fr = player->format_ctx->streams[player->stream_index]->avg_frame_rate;
if (fr.num > 0 && fr.den > 0)
{
player->frame_delay_ms = (int)(1000.0 * fr.den / fr.num);
}
else
{
player->frame_delay_ms = 33;
}
player->next_frame_tick = SDL_GetTicks();
return true;
}
static void cleanup_video_player(VideoPlayer *player)
{
if (player->texture)
{
SDL_DestroyTexture(player->texture);
}
if (player->sws_ctx)
{
sws_freeContext(player->sws_ctx);
}
if (player->rgba_buffer)
{
av_free(player->rgba_buffer);
}
if (player->rgba_frame)
{
av_frame_free(&player->rgba_frame);
}
if (player->frame)
{
av_frame_free(&player->frame);
}
if (player->codec_ctx)
{
avcodec_free_context(&player->codec_ctx);
}
if (player->format_ctx)
{
avformat_close_input(&player->format_ctx);
}
memset(player, 0, sizeof(*player));
}
static bool decode_next_frame(VideoPlayer *player)
{
AVPacket packet;
av_init_packet(&packet);
while (av_read_frame(player->format_ctx, &packet) >= 0)
{
if (packet.stream_index != player->stream_index)
{
av_packet_unref(&packet);
continue;
}
if (avcodec_send_packet(player->codec_ctx, &packet) < 0)
{
av_packet_unref(&packet);
continue;
}
av_packet_unref(&packet);
while (true)
{
int ret = avcodec_receive_frame(player->codec_ctx, player->frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
if (ret < 0)
{
return false;
}
sws_scale(player->sws_ctx, (const uint8_t *const *)player->frame->data,
player->frame->linesize, 0, player->height,
player->rgba_frame->data, player->rgba_frame->linesize);
SDL_UpdateTexture(player->texture, NULL, player->rgba_frame->data[0],
player->rgba_frame->linesize[0]);
return true;
}
}
av_seek_frame(player->format_ctx, player->stream_index, 0, AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers(player->codec_ctx);
return false;
}
int main(int argc, char *argv[])
{
RenderMode render_mode = RENDER_CENTER;

205
src/video.c Normal file
View File

@@ -0,0 +1,205 @@
#include "video.h"
#include <string.h>
#include <ctype.h>
bool has_video_extension(const char *path)
{
const char *dot = strrchr(path, '.');
if (!dot || dot == path)
{
return false;
}
char ext[8];
size_t i = 0;
dot++;
while (*dot && i < sizeof(ext) - 1)
{
ext[i++] = (char)tolower((unsigned char)*dot++);
}
ext[i] = '\0';
return strcmp(ext, "mp4") == 0 || strcmp(ext, "mkv") == 0 || strcmp(ext, "webm") == 0 ||
strcmp(ext, "avi") == 0 || strcmp(ext, "mov") == 0 || strcmp(ext, "mpg") == 0 ||
strcmp(ext, "mpeg") == 0;
}
bool init_video_player(VideoPlayer *player, SDL_Renderer *renderer, const char *path)
{
memset(player, 0, sizeof(*player));
if (avformat_open_input(&player->format_ctx, path, NULL, NULL) != 0)
{
return false;
}
if (avformat_find_stream_info(player->format_ctx, NULL) < 0)
{
return false;
}
player->stream_index = -1;
for (unsigned int i = 0; i < player->format_ctx->nb_streams; ++i)
{
if (player->format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
player->stream_index = (int)i;
break;
}
}
if (player->stream_index < 0)
{
return false;
}
AVCodecParameters *codecpar = player->format_ctx->streams[player->stream_index]->codecpar;
const AVCodec *decoder = avcodec_find_decoder(codecpar->codec_id);
if (!decoder)
{
return false;
}
player->codec_ctx = avcodec_alloc_context3(decoder);
if (!player->codec_ctx)
{
return false;
}
if (avcodec_parameters_to_context(player->codec_ctx, codecpar) < 0)
{
return false;
}
if (avcodec_open2(player->codec_ctx, decoder, NULL) < 0)
{
return false;
}
player->width = player->codec_ctx->width;
player->height = player->codec_ctx->height;
player->frame = av_frame_alloc();
player->rgba_frame = av_frame_alloc();
if (!player->frame || !player->rgba_frame)
{
return false;
}
player->rgba_buffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, player->width, player->height, 1);
player->rgba_buffer = (uint8_t *)av_malloc(player->rgba_buffer_size);
if (!player->rgba_buffer)
{
return false;
}
av_image_fill_arrays(player->rgba_frame->data, player->rgba_frame->linesize, player->rgba_buffer,
AV_PIX_FMT_RGBA, player->width, player->height, 1);
player->sws_ctx = sws_getContext(player->width, player->height, player->codec_ctx->pix_fmt,
player->width, player->height, AV_PIX_FMT_RGBA, SWS_BILINEAR,
NULL, NULL, NULL);
if (!player->sws_ctx)
{
return false;
}
player->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING, player->width, player->height);
if (!player->texture)
{
return false;
}
AVRational fr = player->format_ctx->streams[player->stream_index]->avg_frame_rate;
if (fr.num > 0 && fr.den > 0)
{
player->frame_delay_ms = (int)(1000.0 * fr.den / fr.num);
}
else
{
player->frame_delay_ms = 33;
}
player->next_frame_tick = SDL_GetTicks();
return true;
}
void cleanup_video_player(VideoPlayer *player)
{
if (player->texture)
{
SDL_DestroyTexture(player->texture);
}
if (player->sws_ctx)
{
sws_freeContext(player->sws_ctx);
}
if (player->rgba_buffer)
{
av_free(player->rgba_buffer);
}
if (player->rgba_frame)
{
av_frame_free(&player->rgba_frame);
}
if (player->frame)
{
av_frame_free(&player->frame);
}
if (player->codec_ctx)
{
avcodec_free_context(&player->codec_ctx);
}
if (player->format_ctx)
{
avformat_close_input(&player->format_ctx);
}
memset(player, 0, sizeof(*player));
}
bool decode_next_frame(VideoPlayer *player)
{
AVPacket packet;
av_init_packet(&packet);
while (av_read_frame(player->format_ctx, &packet) >= 0)
{
if (packet.stream_index != player->stream_index)
{
av_packet_unref(&packet);
continue;
}
if (avcodec_send_packet(player->codec_ctx, &packet) < 0)
{
av_packet_unref(&packet);
continue;
}
av_packet_unref(&packet);
while (true)
{
int ret = avcodec_receive_frame(player->codec_ctx, player->frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
if (ret < 0)
{
return false;
}
sws_scale(player->sws_ctx, (const uint8_t *const *)player->frame->data,
player->frame->linesize, 0, player->height,
player->rgba_frame->data, player->rgba_frame->linesize);
SDL_UpdateTexture(player->texture, NULL, player->rgba_frame->data[0],
player->rgba_frame->linesize[0]);
return true;
}
}
av_seek_frame(player->format_ctx, player->stream_index, 0, AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers(player->codec_ctx);
return false;
}

34
src/video.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef VIDEO_H
#define VIDEO_H
#include <SDL2/SDL.h>
#include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
typedef struct VideoPlayer
{
AVFormatContext *format_ctx;
AVCodecContext *codec_ctx;
struct SwsContext *sws_ctx;
AVFrame *frame;
AVFrame *rgba_frame;
uint8_t *rgba_buffer;
int rgba_buffer_size;
int stream_index;
int width;
int height;
int frame_delay_ms;
Uint32 next_frame_tick;
SDL_Texture *texture;
} VideoPlayer;
bool has_video_extension(const char *path);
bool init_video_player(VideoPlayer *player, SDL_Renderer *renderer, const char *path);
void cleanup_video_player(VideoPlayer *player);
bool decode_next_frame(VideoPlayer *player);
#endif // VIDEO_H