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_