gs_ffmpeg

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

gs_avdecode.h (16736B)


      1 #ifndef GS_AVDECODE_H_
      2 #define GS_AVDECODE_H_
      3 
      4 /*
      5  * Header only style....
      6  * #define GS_AVDECODE_IMPL
      7  * before including header in one and only one source file
      8  * to implement declared functions.
      9  *
     10  * requirers linking with ffmpeg stuff.
     11  * on gcc/clang that would for example be
     12  *       -lavcodec -lavformat -lavcodec -lswresample -lswscale -lavutil
     13  *
     14  *
     15  *  requires at least Lavc55.38.100 for mutex locking.
     16  *       https://stackoverflow.com/a/39786484
     17  */
     18 
     19 
     20 #include <libavutil/imgutils.h>
     21 #include <libavutil/samplefmt.h>
     22 #include <libavcodec/avcodec.h>
     23 #include <libavcodec/version.h>
     24 #include <libavformat/avformat.h>
     25 
     26 #include <libswscale/swscale.h>
     27 
     28 #include <gs/gs.h>
     29 
     30 #include <pthread.h>
     31 #include <time.h>
     32 #include <stdatomic.h>
     33 
     34 _Static_assert(LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 38, 100), "FFmpeg major version too low: " LIBAVCODEC_IDENT " needs Lavc55.38.100");
     35 
     36 typedef struct gs_avdecode_ctx_s {
     37         const char* src_filename;
     38         int width, height;
     39         int img_sz;
     40         int video_stream_idx, audio_stream_idx;
     41         int read_next_packet;
     42         int alpha;
     43         enum AVPixelFormat pix_fmt;
     44         float frametime;
     45 
     46         AVFormatContext *fmt_ctx;
     47         AVCodecContext *video_dec_ctx, *audio_dec_ctx;
     48         AVStream *video_stream, *audio_stream;
     49         AVFrame *frame;
     50         AVPacket *pkt;
     51         struct SwsContext * sws;
     52 
     53         void* img[1];
     54 } gs_avdecode_ctx_t;
     55 
     56 // return zero on success
     57 // TODO: specify weather <0 is error or if its just non-zero
     58 // TODO: gs_avdecode_rewind
     59 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);
     60 extern int gs_avdecode_next_frame(gs_avdecode_ctx_t* ctx); // -1 if all frames read
     61 extern void gs_avdecode_destroy(gs_avdecode_ctx_t* ctx, gs_asset_texture_t* tex);
     62 
     63 
     64 // Multi-threading usage:
     65 // Atomic integers are used instead of locks (for performance reasons)
     66 //
     67 // if new_frame is 0, then the "main" thread(s) shall not access anything
     68 // if new_frame is -1 then the "decoder" thread shall not access anything
     69 // if new_frame is 1, then a frame is complete and either thread can do a cmpxchg to aquire it.
     70 //
     71 // when the decoder thread exits it sets done to 1
     72 typedef struct gs_avdecode_pthread_s {
     73         pthread_t thread;
     74         pthread_attr_t attr;
     75 
     76         gs_avdecode_ctx_t video;
     77 
     78         _Atomic int new_frame;
     79         _Atomic int loop; // TODO...
     80         _Atomic int done;
     81 } gs_avdecode_pthread_t;
     82 extern int gs_avdecode_pthread_play_video(gs_avdecode_pthread_t* ctxp, const char* path,
     83                                           const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out);
     84 extern void gs_avdecode_pthread_destroy(gs_avdecode_pthread_t* ctxp, gs_asset_texture_t* tex);
     85 
     86 #define gs_avdecode_aquire_m(_ctxp, ...)                                \
     87         do {                                                            \
     88                 int __check = 1;                                        \
     89                 if (atomic_compare_exchange_strong(&(_ctxp)->new_frame, &__check, -1)) { \
     90                         __VA_ARGS__                                     \
     91                         (_ctxp)->new_frame = 0;                         \
     92                 }                                                       \
     93         } while(0)
     94 
     95 
     96 
     97 
     98 #ifdef GS_AVDECODE_IMPL
     99 
    100 static int
    101 _gs_avdecode_decode_packet(gs_avdecode_ctx_t* ctx, AVCodecContext *dec, const AVPacket *pkt, int new_pkt)
    102 {
    103         int ret = 0;
    104 
    105         if (new_pkt) {
    106                 // submit the packet to the decoder
    107                 ret = avcodec_send_packet(dec, pkt);
    108                 if (ret < 0) {
    109                         fprintf(stderr, "gs_avdecode.h: Error submitting a packet for decoding (%s)\n", av_err2str(ret));
    110                         return ret;
    111                 }
    112         }
    113 
    114         // get all the available frames from the decoder
    115         int valid_frame = 1;
    116         do {
    117                 ret = avcodec_receive_frame(dec, ctx->frame);
    118                 if (ret < 0) {
    119                         // those two return values are special and mean there is no output
    120                         // frame available, but there were no errors during decoding
    121                         if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
    122                                 return 0;
    123 
    124                         fprintf(stderr, "gs_avdecode.h: Error during decoding (%s)\n", av_err2str(ret));
    125                         return ret;
    126                 }
    127                 if (dec->codec->type == AVMEDIA_TYPE_VIDEO) {
    128                         if (ctx->frame->width != ctx->width   ||
    129                             ctx->frame->height != ctx->height ||
    130                             ctx->frame->format != ctx->pix_fmt) {
    131                                 // No support for variable size videos...
    132                                 // To do so you would have to reallocate img
    133                                 valid_frame = 0;
    134                         } else {
    135                                 sws_scale(ctx->sws,
    136                                           (const uint8_t **)(ctx->frame->data), ctx->frame->linesize,
    137                                           0, ctx->height, (uint8_t**)ctx->img, (int[1]){ctx->width * (3 + ctx->alpha)}
    138                                         );
    139                         }
    140                 }
    141 
    142                 av_frame_unref(ctx->frame);
    143         } while (!valid_frame);
    144 
    145         return ret;
    146 }
    147 
    148 static int
    149 _gs_avdecode_open_codec_context(
    150         gs_avdecode_ctx_t* ctx,
    151         int *stream_idx,
    152         AVCodecContext **dec_ctx,
    153         AVFormatContext *fmt_ctx,
    154         int* alpha,
    155         enum AVMediaType type
    156 ) {
    157         int ret, stream_index;
    158         AVStream *st;
    159         const AVCodec *dec = NULL;
    160 
    161         ret = av_find_best_stream(ctx->fmt_ctx, type, -1, -1, NULL, 0);
    162         if (ret < 0) {
    163                 fprintf(stderr, "gs_avdecode.h: Could not find %s stream in input file '%s'\n",
    164                         av_get_media_type_string(type), ctx->src_filename);
    165                 return ret;
    166         } else {
    167                 stream_index = ret;
    168                 st = ctx->fmt_ctx->streams[stream_index];
    169 
    170                 int a = 0;
    171                 if (alpha) {
    172                         AVDictionaryEntry* tag = NULL;
    173                         tag = av_dict_get(st->metadata, "ALPHA_MODE", tag, 0);
    174                         a = tag && atoi(tag->value);
    175                         *alpha = a;
    176                 }
    177 
    178                 // find decoder for the stream
    179                 // use libvpx for transparent video
    180                 dec = a ? (
    181                         AV_CODEC_ID_VP9 == st->codecpar->codec_id
    182                         ? avcodec_find_decoder_by_name("libvpx-vp9")
    183                         : avcodec_find_decoder_by_name("libvpx-vp8")
    184                         ) : avcodec_find_decoder(st->codecpar->codec_id);
    185                 if (!dec) {
    186                         fprintf(stderr, "gs_avdecode.h: Failed to find %s codec\n",
    187                                 av_get_media_type_string(type));
    188                         return AVERROR(EINVAL);
    189                 }
    190 
    191                 /* Allocate a codec context for the decoder */
    192                 *dec_ctx = avcodec_alloc_context3(dec);
    193                 if (!*dec_ctx) {
    194                         fprintf(stderr, "gs_avdecode.h: Failed to allocate the %s codec context\n",
    195                                 av_get_media_type_string(type));
    196                         return AVERROR(ENOMEM);
    197                 }
    198 
    199                 /* Copy codec parameters from input stream to output codec context */
    200                 if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) {
    201                         fprintf(stderr, "gs_avdecode.h: Failed to copy %s codec parameters to decoder context\n",
    202                                 av_get_media_type_string(type));
    203                         return ret;
    204                 }
    205 
    206                 // Init the decoders
    207                 if ((ret = avcodec_open2(*dec_ctx, dec, NULL)) < 0) {
    208                         fprintf(stderr, "gs_avdecode.h: Failed to open %s codec\n",
    209                                 av_get_media_type_string(type));
    210                         return ret;
    211                 }
    212                 *stream_idx = stream_index;
    213         }
    214 
    215         return 0;
    216 }
    217 
    218 int
    219 gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out)
    220 {
    221         if (!ctx) return 2;
    222         *ctx =  (gs_avdecode_ctx_t){0};
    223         ctx->video_stream_idx = -1;
    224         ctx->audio_stream_idx = -1;
    225         ctx->src_filename = path;
    226         ctx->read_next_packet = 1;
    227 
    228         /* open input file, and allocate format context */
    229         if (avformat_open_input(&ctx->fmt_ctx, ctx->src_filename, NULL, NULL) < 0) {
    230                 fprintf(stderr, "gs_avdecode.h: Could not open source file %s", ctx->src_filename);
    231                 return 1;
    232         }
    233 
    234         /* retrieve stream information */
    235         if (avformat_find_stream_info(ctx->fmt_ctx, NULL) < 0) {
    236                 fprintf(stderr, "gs_avdecode.h: Could not find stream information");
    237                 avformat_close_input(&ctx->fmt_ctx);
    238                 return 1;
    239         }
    240 
    241         int ret = 0;
    242         if (_gs_avdecode_open_codec_context(ctx, &ctx->video_stream_idx, &ctx->video_dec_ctx, ctx->fmt_ctx, &ctx->alpha, AVMEDIA_TYPE_VIDEO) >= 0) {
    243                 ctx->video_stream = ctx->fmt_ctx->streams[ctx->video_stream_idx];
    244                 ctx->frametime = (float)ctx->video_stream->r_frame_rate.den / (float)ctx->video_stream->r_frame_rate.num;
    245 
    246                 ctx->width = ctx->video_dec_ctx->width;
    247                 ctx->height = ctx->video_dec_ctx->height;
    248                 ctx->pix_fmt = ctx->video_dec_ctx->pix_fmt;
    249                 if (ctx->alpha && ctx->pix_fmt == AV_PIX_FMT_YUV420P)
    250                         ctx->pix_fmt = AV_PIX_FMT_YUVA420P;
    251                 ctx->sws = sws_getContext(ctx->width, ctx->height, ctx->pix_fmt,
    252                                           ctx->width, ctx->height, ctx->alpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24,
    253                                           SWS_BICUBIC, 0, 0, 0);
    254         }
    255 
    256         if (_gs_avdecode_open_codec_context(ctx, &ctx->audio_stream_idx, &ctx->audio_dec_ctx, ctx->fmt_ctx, NULL, AVMEDIA_TYPE_AUDIO) >= 0) {
    257                 ctx->audio_stream = ctx->fmt_ctx->streams[ctx->audio_stream_idx];
    258         }
    259 
    260         // dump input information to stderr
    261         av_dump_format(ctx->fmt_ctx, 0, ctx->src_filename, 0);
    262 
    263         if (!ctx->audio_stream && !ctx->video_stream) {
    264                 fprintf(stderr, "gs_avdecode.h: Could not find audio or video stream in the input, aborting\n");
    265                 ret = 1;
    266                 goto end;
    267         }
    268 
    269         ctx->frame = av_frame_alloc();
    270         if (!ctx->frame) {
    271                 fprintf(stderr, "gs_avdecode.h: Could not allocate frame\n");
    272                 ret = AVERROR(ENOMEM);
    273                 goto end;
    274         }
    275 
    276         ctx->pkt = av_packet_alloc();
    277         if (!ctx->pkt) {
    278                 fprintf(stderr, "gs_avdecode.h: Could not allocate packet\n");
    279                 ret = AVERROR(ENOMEM);
    280                 goto end;
    281         }
    282 
    283 
    284         ctx->img_sz = ctx->width * ctx->height * (3 + ctx->alpha);
    285         *ctx->img = malloc(ctx->img_sz);
    286         memset(*ctx->img, 150, ctx->img_sz);
    287 
    288         ////////////////////////////////////
    289         // asset_texture
    290 
    291         gs_asset_texture_t* t = out;
    292         if (t) {
    293                 if (desc) {
    294                         t->desc = *desc;
    295                 } else {
    296                         t->desc.format = ctx->alpha ? GS_GRAPHICS_TEXTURE_FORMAT_RGBA8 : GS_GRAPHICS_TEXTURE_FORMAT_RGB8;
    297                         t->desc.min_filter = GS_GRAPHICS_TEXTURE_FILTER_LINEAR;
    298                         t->desc.mag_filter = GS_GRAPHICS_TEXTURE_FILTER_LINEAR;
    299                         t->desc.wrap_s = GS_GRAPHICS_TEXTURE_WRAP_REPEAT;
    300                         t->desc.wrap_t = GS_GRAPHICS_TEXTURE_WRAP_REPEAT;
    301                 }
    302                 t->desc.width = ctx->width;
    303                 t->desc.height = ctx->height;
    304 
    305                 *t->desc.data = malloc(ctx->img_sz);
    306                 memset(*t->desc.data, 150, ctx->img_sz);
    307                 t->hndl = gs_graphics_texture_create(&t->desc);
    308         }
    309 
    310         return 0;
    311 end:
    312         gs_avdecode_destroy(ctx, NULL);
    313         return ret;
    314 }
    315 
    316 int
    317 gs_avdecode_next_frame(gs_avdecode_ctx_t* ctx)
    318 {
    319         int skip_packet;
    320         int ret;
    321         do {
    322                 skip_packet = 0;
    323                 ret = 0;
    324                 int new = 0;
    325                 if (ctx->read_next_packet) {
    326                         if (av_read_frame(ctx->fmt_ctx, ctx->pkt) < 0) return -1;
    327                         ctx->read_next_packet = 0;
    328                         new = 1;
    329                 }
    330 
    331                 // check if the packet belongs to a stream we are interested in, otherwise skip it
    332                 if (ctx->pkt->stream_index == ctx->video_stream_idx) {
    333                         ret = _gs_avdecode_decode_packet(ctx, ctx->video_dec_ctx, ctx->pkt, new);
    334                 } else if (ctx->pkt->stream_index == ctx->audio_stream_idx) {
    335                         ret = _gs_avdecode_decode_packet(ctx, ctx->audio_dec_ctx, ctx->pkt, new);
    336                         skip_packet = 1;
    337                 } else {
    338                         skip_packet = 1;
    339                 }
    340                 if (ret >= 0) {
    341                         av_packet_unref(ctx->pkt);
    342                         ctx->read_next_packet = 1;
    343                 }
    344         } while (skip_packet);
    345 
    346         return ret;
    347 }
    348 
    349 void
    350 gs_avdecode_destroy(gs_avdecode_ctx_t* ctx, gs_asset_texture_t* tex)
    351 {
    352         if (!ctx) return;
    353         // flush the decoders
    354         if (ctx->video_dec_ctx)
    355                 _gs_avdecode_decode_packet(ctx, ctx->video_dec_ctx, NULL, 1);
    356         if (ctx->audio_dec_ctx)
    357                 _gs_avdecode_decode_packet(ctx, ctx->audio_dec_ctx, NULL, 1);
    358 
    359         avcodec_free_context(&ctx->video_dec_ctx);
    360         avcodec_free_context(&ctx->audio_dec_ctx);
    361         avformat_close_input(&ctx->fmt_ctx);
    362         av_packet_free(&ctx->pkt);
    363         sws_freeContext(ctx->sws);
    364         av_frame_free(&ctx->frame);
    365 
    366         if (tex) {
    367                 gs_graphics_texture_destroy(tex->hndl);
    368                 free(*tex->desc.data);
    369         }
    370 }
    371 
    372 static void*
    373 _gs_avdecode_pthread_player(void* data)
    374 {
    375         gs_avdecode_pthread_t* ctxp = data;
    376         gs_avdecode_ctx_t* ctx = &ctxp->video;
    377         ctxp->new_frame = 0;
    378 
    379         const float frametime = ctx->frametime * 1e3;
    380         int frames = 0;
    381 
    382         const float t_start = gs_platform_elapsed_time();
    383 
    384         int res = 0;
    385         int prerendered = 0;
    386         const float dt = 0.05;
    387         for (;;) {
    388                 if (!prerendered && ctxp->new_frame == 0) {
    389                         res = gs_avdecode_next_frame(ctx);
    390                         prerendered = 1;
    391                 }
    392 
    393                 const float diff = gs_platform_elapsed_time() - t_start;
    394 
    395                 if (diff + dt < frametime * frames
    396                     // also wait for the first frame, this prevents possible jitter
    397                     || (frames == 1 && ctxp->new_frame)) {
    398                         gs_platform_sleep(dt);
    399                         continue;
    400                 }
    401 
    402                 int locked = 0;
    403                 if (ctxp->new_frame == 0) {
    404                         locked = 1;
    405                 } else {
    406                         int check = 1;
    407                         locked = atomic_compare_exchange_strong(&ctxp->new_frame, &check, 0);
    408                 }
    409                 if (!locked)
    410                         continue;
    411 
    412                 if (!prerendered) res = gs_avdecode_next_frame(ctx);
    413 
    414                 ctxp->new_frame = 1;
    415                 frames++;
    416 
    417                 prerendered = 0;
    418 
    419                 if (res < 0) break;
    420         }
    421 
    422         ctxp->done = 1;
    423 
    424         return NULL;
    425 }
    426 
    427 int
    428 gs_avdecode_pthread_play_video(gs_avdecode_pthread_t* ctxp, const char* path,
    429                                const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out)
    430 {
    431         if (!ctxp) return 2;
    432         *ctxp = (gs_avdecode_pthread_t){0};
    433 
    434         pthread_attr_init(&ctxp->attr);
    435         pthread_attr_setdetachstate(&ctxp->attr, PTHREAD_CREATE_DETACHED);
    436         int res = gs_avdecode_init(path, &ctxp->video, desc, out);
    437         if (res) return res;
    438 
    439         pthread_create(&ctxp->thread, &ctxp->attr, &_gs_avdecode_pthread_player, ctxp);
    440         // TODO: error code from pthread functions as well
    441 
    442         return res;
    443 }
    444 
    445 void
    446 gs_avdecode_pthread_destroy(gs_avdecode_pthread_t* ctxp, gs_asset_texture_t* tex)
    447 {
    448 
    449         pthread_attr_destroy(&ctxp->attr);
    450         gs_avdecode_destroy(&ctxp->video, tex);
    451 }
    452 
    453 #endif // GS_AVDECODE_IMPL
    454 
    455 #endif // GS_AVDECODE_H_