gs_ffmpeg

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README

commit 89160c3b72d929aadb031f96b2d006cd8076764f
parent 060f06d1e9e53ed116a3f6a936e351369bd9e505
Author: Samdal <samdal@protonmail.com>
Date:   Thu,  8 Dec 2022 23:54:53 +0100

multi threading

Diffstat:
Msource/gs_avdecode.h | 252++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msource/main.c | 88++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
2 files changed, 255 insertions(+), 85 deletions(-)

diff --git a/source/gs_avdecode.h b/source/gs_avdecode.h @@ -7,58 +7,84 @@ * before including header in one and only one source file * to implement declared functions. * + * #define GS_AVDECODE_NO_DECLARE_CLOCK_GETTIME + * if it's being redefined on windows (used for timing for multi-threaded decoding) + * * requirers linking with ffmpeg stuff. - * On linux that would for example be + * on gcc/clang that would for example be * -lavcodec -lavformat -lavcodec -lswresample -lswscale -lavutil * + * + * requires at least Lavc55.38.100 for mutex locking. + * https://stackoverflow.com/a/39786484 */ + #include <libavutil/imgutils.h> #include <libavutil/samplefmt.h> #include <libavutil/timestamp.h> #include <libavcodec/avcodec.h> +#include <libavcodec/version.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <gs/gs.h> +#include <pthread.h> +#include <time.h> + +_Static_assert(LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 38, 100), "FFmpeg major version too low: " LIBAVCODEC_IDENT " needs Lavc55.38.100"); + typedef struct gs_avdecode_ctx_s { const char* src_filename; int width, height; + int img_sz; int video_stream_idx, audio_stream_idx; int read_next_packet; int alpha; enum AVPixelFormat pix_fmt; + float frametime; AVFormatContext *fmt_ctx; AVCodecContext *video_dec_ctx, *audio_dec_ctx; AVStream *video_stream, *audio_stream; AVFrame *frame; AVPacket *pkt; - struct SwsContext * sws; // TODO: FREE THE CONTEXT!!!!!!!! + struct SwsContext * sws; - gs_asset_texture_t tex; + void* img[1]; } gs_avdecode_ctx_t; +typedef struct gs_avdecode_pthread_s { + pthread_mutex_t lock; + + gs_avdecode_ctx_t video; + + int new_frame; + _Atomic int done; +} gs_avdecode_pthread_t; + // return zero on success // TODO: specify weather <0 is error or if its just non-zero -extern int gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, gs_graphics_texture_desc_t* desc); +extern int gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out); extern int gs_avdecode_next_frame(gs_avdecode_ctx_t* ctx); // -1 if all frames read -extern void gs_avdecode_destroy(gs_avdecode_ctx_t* ctx); +extern void gs_avdecode_destroy(gs_avdecode_ctx_t* ctx, gs_asset_texture_t* tex); #ifdef GS_AVDECODE_IMPL static int -gs_avdecode_decode_packet(gs_avdecode_ctx_t* ctx, AVCodecContext *dec, const AVPacket *pkt) +_gs_avdecode_decode_packet(gs_avdecode_ctx_t* ctx, AVCodecContext *dec, const AVPacket *pkt, int new_pkt) { int ret = 0; - // submit the packet to the decoder - ret = avcodec_send_packet(dec, pkt); - if (ret < 0) { - fprintf(stderr, "gs_avdecode.h: Error submitting a packet for decoding (%s)\n", av_err2str(ret)); - return ret; + if (new_pkt) { + // submit the packet to the decoder + ret = avcodec_send_packet(dec, pkt); + if (ret < 0) { + fprintf(stderr, "gs_avdecode.h: Error submitting a packet for decoding (%s)\n", av_err2str(ret)); + return ret; + } } // get all the available frames from the decoder @@ -84,7 +110,7 @@ gs_avdecode_decode_packet(gs_avdecode_ctx_t* ctx, AVCodecContext *dec, const AVP } else { sws_scale(ctx->sws, (const uint8_t **)(ctx->frame->data), ctx->frame->linesize, - 0, ctx->height, (uint8_t**)ctx->tex.desc.data, (int[1]){ctx->width * (3 + ctx->alpha)} + 0, ctx->height, (uint8_t**)ctx->img, (int[1]){ctx->width * (3 + ctx->alpha)} ); } } @@ -96,7 +122,7 @@ gs_avdecode_decode_packet(gs_avdecode_ctx_t* ctx, AVCodecContext *dec, const AVP } static int -open_codec_context( +_gs_avdecode_open_codec_context( gs_avdecode_ctx_t* ctx, int *stream_idx, AVCodecContext **dec_ctx, @@ -127,8 +153,11 @@ open_codec_context( // find decoder for the stream // use libvpx for transparent video - dec = a ? avcodec_find_decoder_by_name("libvpx-vp9") - : avcodec_find_decoder(st->codecpar->codec_id); + dec = a ? ( + AV_CODEC_ID_VP9 == st->codecpar->codec_id + ? avcodec_find_decoder_by_name("libvpx-vp9") + : avcodec_find_decoder_by_name("libvpx-vp8") + ) : avcodec_find_decoder(st->codecpar->codec_id); if (!dec) { fprintf(stderr, "gs_avdecode.h: Failed to find %s codec\n", av_get_media_type_string(type)); @@ -163,7 +192,7 @@ open_codec_context( } int -gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, gs_graphics_texture_desc_t* desc) +gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out) { if (!ctx) return 2; *ctx = (gs_avdecode_ctx_t){0}; @@ -186,8 +215,10 @@ gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, gs_graphics_texture_d } int ret = 0; - if (open_codec_context(ctx, &ctx->video_stream_idx, &ctx->video_dec_ctx, ctx->fmt_ctx, &ctx->alpha, AVMEDIA_TYPE_VIDEO) >= 0) { + if (_gs_avdecode_open_codec_context(ctx, &ctx->video_stream_idx, &ctx->video_dec_ctx, ctx->fmt_ctx, &ctx->alpha, AVMEDIA_TYPE_VIDEO) >= 0) { ctx->video_stream = ctx->fmt_ctx->streams[ctx->video_stream_idx]; + ctx->frametime = (float)ctx->video_stream->r_frame_rate.den / (float)ctx->video_stream->r_frame_rate.num; + ctx->width = ctx->video_dec_ctx->width; ctx->height = ctx->video_dec_ctx->height; ctx->pix_fmt = ctx->video_dec_ctx->pix_fmt; @@ -198,7 +229,7 @@ gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, gs_graphics_texture_d SWS_BICUBIC, 0, 0, 0); } - if (open_codec_context(ctx, &ctx->audio_stream_idx, &ctx->audio_dec_ctx, ctx->fmt_ctx, NULL, AVMEDIA_TYPE_AUDIO) >= 0) { + if (_gs_avdecode_open_codec_context(ctx, &ctx->audio_stream_idx, &ctx->audio_dec_ctx, ctx->fmt_ctx, NULL, AVMEDIA_TYPE_AUDIO) >= 0) { ctx->audio_stream = ctx->fmt_ctx->streams[ctx->audio_stream_idx]; } @@ -225,71 +256,186 @@ gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, gs_graphics_texture_d goto end; } + + ctx->img_sz = ctx->width * ctx->height * (3 + ctx->alpha); + *ctx->img = malloc(ctx->img_sz); + memset(*ctx->img, 150, ctx->img_sz); + //////////////////////////////////// // asset_texture - gs_asset_texture_t* t = &ctx->tex; + gs_asset_texture_t* t = out; + if (t) { + if (desc) { + t->desc = *desc; + } else { + t->desc.format = ctx->alpha ? GS_GRAPHICS_TEXTURE_FORMAT_RGBA8 : GS_GRAPHICS_TEXTURE_FORMAT_RGB8; + t->desc.min_filter = GS_GRAPHICS_TEXTURE_FILTER_LINEAR; + t->desc.mag_filter = GS_GRAPHICS_TEXTURE_FILTER_LINEAR; + t->desc.wrap_s = GS_GRAPHICS_TEXTURE_WRAP_REPEAT; + t->desc.wrap_t = GS_GRAPHICS_TEXTURE_WRAP_REPEAT; + } + t->desc.width = ctx->width; + t->desc.height = ctx->height; - if (desc) { - t->desc = *desc; - } else { - t->desc.format = ctx->alpha ? GS_GRAPHICS_TEXTURE_FORMAT_RGBA8 : GS_GRAPHICS_TEXTURE_FORMAT_RGB8; - t->desc.min_filter = GS_GRAPHICS_TEXTURE_FILTER_LINEAR; - t->desc.mag_filter = GS_GRAPHICS_TEXTURE_FILTER_LINEAR; - t->desc.wrap_s = GS_GRAPHICS_TEXTURE_WRAP_REPEAT; - t->desc.wrap_t = GS_GRAPHICS_TEXTURE_WRAP_REPEAT; + *t->desc.data = malloc(ctx->img_sz); + memset(*t->desc.data, 150, ctx->img_sz); + t->hndl = gs_graphics_texture_create(&t->desc); } - t->desc.width = ctx->width; - t->desc.height = ctx->height; - *t->desc.data = malloc(ctx->width * ctx->height * (3 + ctx->alpha) + 1); - ((char**)t->desc.data)[0][ctx->width * ctx->height * (3 + ctx->alpha)] = 0; - memset(*t->desc.data, 150, ctx->width * ctx->height * (3 + ctx->alpha)); - t->hndl = gs_graphics_texture_create(&t->desc); return 0; end: - gs_avdecode_destroy(ctx); + gs_avdecode_destroy(ctx, NULL); return ret; } - -// TODO fix conflicting types in img.... int gs_avdecode_next_frame(gs_avdecode_ctx_t* ctx) { - int ret = 0; - if (ctx->read_next_packet) { - if (av_read_frame(ctx->fmt_ctx, ctx->pkt) < 0) return -1; - ctx->read_next_packet = 0; - } + int skip_packet; + int ret; + do { + skip_packet = 0; + ret = 0; + int new = 0; + if (ctx->read_next_packet) { + if (av_read_frame(ctx->fmt_ctx, ctx->pkt) < 0) return -1; + ctx->read_next_packet = 0; + new = 1; + } - // check if the packet belongs to a stream we are interested in, otherwise skip it - if (ctx->pkt->stream_index == ctx->video_stream_idx) - ret = gs_avdecode_decode_packet(ctx, ctx->video_dec_ctx, ctx->pkt); - if (ctx->pkt->stream_index == ctx->audio_stream_idx) - ret = gs_avdecode_decode_packet(ctx, ctx->audio_dec_ctx, ctx->pkt); - if (ret >= 0) { - av_packet_unref(ctx->pkt); - ctx->read_next_packet = 1; - } + // check if the packet belongs to a stream we are interested in, otherwise skip it + if (ctx->pkt->stream_index == ctx->video_stream_idx) { + ret = _gs_avdecode_decode_packet(ctx, ctx->video_dec_ctx, ctx->pkt, new); + } else if (ctx->pkt->stream_index == ctx->audio_stream_idx) { + ret = _gs_avdecode_decode_packet(ctx, ctx->audio_dec_ctx, ctx->pkt, new); + skip_packet = 1; + } else { + skip_packet = 1; + } + if (ret >= 0) { + av_packet_unref(ctx->pkt); + ctx->read_next_packet = 1; + } + } while (skip_packet); return ret; } void -gs_avdecode_destroy(gs_avdecode_ctx_t* ctx) +gs_avdecode_destroy(gs_avdecode_ctx_t* ctx, gs_asset_texture_t* tex) { + if (!ctx) return; // flush the decoders if (ctx->video_dec_ctx) - gs_avdecode_decode_packet(ctx, ctx->video_dec_ctx, NULL); + _gs_avdecode_decode_packet(ctx, ctx->video_dec_ctx, NULL, 1); if (ctx->audio_dec_ctx) - gs_avdecode_decode_packet(ctx, ctx->audio_dec_ctx, NULL); + _gs_avdecode_decode_packet(ctx, ctx->audio_dec_ctx, NULL, 1); avcodec_free_context(&ctx->video_dec_ctx); avcodec_free_context(&ctx->audio_dec_ctx); avformat_close_input(&ctx->fmt_ctx); av_packet_free(&ctx->pkt); + sws_freeContext(ctx->sws); av_frame_free(&ctx->frame); + + if (tex) { + gs_graphics_texture_destroy(tex->hndl); + free(*tex->desc.data); + } +} + +#ifdef _WIN32 +#ifndef GS_AVDECODE_NO_DECLARE_CLOCK_GETTIME +// untested +struct timespec { long tv_sec; long tv_nsec; }; +static int +clock_gettime(int, struct timespec *spec) +{ + __int64 wintime; + GetSystemTimeAsFileTime((FILETIME*)&wintime); + wintime -= 116444736000000000i64; // 1jan1601 to 1jan1970 + spec->tv_sec = wintime / 10000000i64; // seconds + spec->tv_nsec = wintime % 10000000i64 * 100; // nano-seconds + return 0; +} +#endif // GS_AVDECODE_NO_DECLARE_CLOCK_GETTIME +#endif // _WIN32 + +static void* +_gs_avdecode_pthread_player(void* data) +{ + gs_avdecode_pthread_t* ctxp = data; + gs_avdecode_ctx_t* ctx = &ctxp->video; + + pthread_mutex_lock(&ctxp->lock); + const float frametime = ctx->frametime * 1e3; + pthread_mutex_unlock(&ctxp->lock); + int frames = 0; + + struct timespec ts_start; + clock_gettime(CLOCK_MONOTONIC, &ts_start); + + int res = 0; + int prerendered = 0; + const float dt = 0.001; + for (;;) { + if (!prerendered) { + pthread_mutex_lock(&ctxp->lock); + if (ctxp->new_frame == 0) { + res = gs_avdecode_next_frame(ctx); + prerendered = 1; + } + pthread_mutex_unlock(&ctxp->lock); + } + + float diff = 0.0; + struct timespec ts_check; + clock_gettime(CLOCK_MONOTONIC, &ts_check); + diff = (ts_check.tv_sec - ts_start.tv_sec) * 1e3; + diff += (ts_check.tv_nsec - ts_start.tv_nsec) * 1e-6; + + if (diff + dt < frametime * frames) { + gs_platform_sleep(dt); + continue; + } + + pthread_mutex_lock(&ctxp->lock); + if (!prerendered) res = gs_avdecode_next_frame(ctx); + ctxp->new_frame = 1; + pthread_mutex_unlock(&ctxp->lock); + frames++; + prerendered = 0; + + if (res < 0) break; + } + + pthread_mutex_lock(&ctxp->lock); + ctxp->done = 1; + pthread_mutex_unlock(&ctxp->lock); + gs_platform_sleep(0.001f); + pthread_mutex_lock(&ctxp->lock); + + gs_avdecode_destroy(ctx, NULL); + pthread_mutex_destroy(&ctxp->lock); + return NULL; +} + +int +gs_avdecode_pthread_play_video(gs_avdecode_pthread_t* ctxp, pthread_t* thread, const char* path, + const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out) +{ + if (!ctxp) return 2; + *ctxp = (gs_avdecode_pthread_t){0}; + + pthread_mutex_init(&ctxp->lock, NULL); + int res = gs_avdecode_init(path, &ctxp->video, desc, out); + if (res) return res; + + pthread_create(thread, NULL, &_gs_avdecode_pthread_player, ctxp); + // TODO: error code from pthread functions as well + + return res; } #endif // GS_AVDECODE_IMPL diff --git a/source/main.c b/source/main.c @@ -3,52 +3,75 @@ #define GS_AVDECODE_IMPL #include "gs_avdecode.h" -static gs_avdecode_ctx_t video; -static gs_asset_texture_t tex = {0}; -static gs_immediate_draw_t gsi = {0}; -static gs_command_buffer_t cb = {0}; +static gs_immediate_draw_t gsi; +static gs_command_buffer_t cb; static const char* filename; +static gs_asset_texture_t tex; +static gs_asset_texture_t ptex; +static gs_avdecode_ctx_t video; +static gs_avdecode_pthread_t pvideo; +static pthread_t t; + void app_update() { - const gs_vec2 fb = gs_platform_framebuffer_sizev(gs_platform_main_window()); - - gsi_defaults(&gsi); - gsi_camera2D(&gsi, fb.x, fb.y); - const float t = gs_platform_elapsed_time() * 0.0001f; - - gsi_camera3D(&gsi, fb.x, fb.y); - gsi_rotatev(&gsi, gs_deg2rad(90.f), GS_ZAXIS); gsi_rotatev(&gsi, t, GS_YAXIS); - gsi_sphere(&gsi, 0.f, 0.f, 0.f, 1.f, 50, 150, 200, 50, GS_GRAPHICS_PRIMITIVE_LINES); - - gsi_defaults(&gsi); - gsi_camera2D(&gsi, fb.x, fb.y); - //gsi_rectvd(&gsi, gs_v2s(0.0f), fb, gs_v2s(0.f), gs_v2s(1.f), GS_COLOR_WHITE, GS_GRAPHICS_PRIMITIVE_TRIANGLES); - - int res = gs_avdecode_next_frame(&video); - if (res) gs_quit(); - - gs_graphics_texture_request_update(&cb, video.tex.hndl, &video.tex.desc); - gsi_texture(&gsi, video.tex.hndl); - gsi_rectvd(&gsi, gs_v2s(0.0f), fb, gs_v2s(0.f), gs_v2s(1.f), GS_COLOR_WHITE, GS_GRAPHICS_PRIMITIVE_TRIANGLES); - + const gs_vec2 fb = gs_platform_framebuffer_sizev(gs_platform_main_window()); + + gsi_defaults(&gsi); + gsi_camera2D(&gsi, fb.x, fb.y); + const float t = gs_platform_elapsed_time() * 0.0001f; + + gsi_camera3D(&gsi, fb.x, fb.y); + gsi_rotatev(&gsi, gs_deg2rad(90.f), GS_ZAXIS); gsi_rotatev(&gsi, t, GS_YAXIS); + gsi_sphere(&gsi, 0.f, 0.f, 0.f, 1.f, 50, 150, 200, 50, GS_GRAPHICS_PRIMITIVE_LINES); + + gsi_defaults(&gsi); + gsi_camera2D(&gsi, fb.x, fb.y); + + int res = gs_avdecode_next_frame(&video); + memcpy(*tex.desc.data, *video.img, video.img_sz); + gs_graphics_texture_request_update(&cb, tex.hndl, &tex.desc); + + gsi_texture(&gsi, tex.hndl); + gsi_rectvd(&gsi, gs_v2s(0.0f), fb, gs_v2s(0.f), gs_v2s(1.f), GS_COLOR_WHITE, GS_GRAPHICS_PRIMITIVE_TRIANGLES); + + if (!pvideo.done) { + pthread_mutex_lock(&pvideo.lock); + if (pvideo.new_frame) { + pvideo.new_frame = 0; + memcpy(*ptex.desc.data, *pvideo.video.img, pvideo.video.img_sz); + gs_graphics_texture_request_update(&cb, ptex.hndl, &ptex.desc); + } + pthread_mutex_unlock(&pvideo.lock); + } + gsi_texture(&gsi, ptex.hndl); + gsi_rectvd(&gsi, gs_v2(fb.x/2, fb.y/2), gs_v2(fb.x/2, fb.y/2), gs_v2s(0.f), gs_v2s(1.f), GS_COLOR_WHITE, GS_GRAPHICS_PRIMITIVE_TRIANGLES); - gsi_renderpass_submit(&gsi, &cb, gs_v4(0, 0, fb.x, fb.y), gs_color(10, 10, 10, 255)); - gs_graphics_command_buffer_submit(&cb); + gsi_renderpass_submit(&gsi, &cb, gs_v4(0, 0, fb.x, fb.y), gs_color(10, 10, 10, 255)); + gs_graphics_command_buffer_submit(&cb); } void app_init() { - cb = gs_command_buffer_new(); - gsi = gs_immediate_draw_new(); + cb = gs_command_buffer_new(); + gsi = gs_immediate_draw_new(); - int res = gs_avdecode_init(filename, &video, NULL); + int res = gs_avdecode_init(filename, &video, NULL, &tex); - if (res) { + int res2 = gs_avdecode_pthread_play_video(&pvideo, &t, filename, NULL, &ptex); + + if (res) { gs_println("Unable to initialize video '%s' (error code %d)", filename, res); exit(1); - } + } +} + +void app_shutdown() +{ + gs_avdecode_destroy(&video, &tex); + gs_immediate_draw_free(&gsi); + gs_command_buffer_free(&cb); } gs_app_desc_t @@ -69,5 +92,6 @@ gs_main(int32_t argc, char** argv) }, .init = app_init, .update = app_update, + .shutdown = app_shutdown, }; }