commit 89160c3b72d929aadb031f96b2d006cd8076764f
parent 060f06d1e9e53ed116a3f6a936e351369bd9e505
Author: Samdal <samdal@protonmail.com>
Date: Thu, 8 Dec 2022 23:54:53 +0100
multi threading
Diffstat:
M | source/gs_avdecode.h | | | 252 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------- |
M | source/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,
};
}