#include "video.h" #include #include bool has_video_extension(const char* path) { /* Check if the file extension of the given path matches common video formats. */ // TODO: Instead of checking file extension, use MIME type detection // Check if the given path has an extension by looking for the last dot // character. 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) { goto error; } if (avformat_find_stream_info(player->format_ctx, NULL) < 0) { goto error; } player->stream_index = -1; player->audio_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 < 0) { player->stream_index = (int)i; } else if (player->format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && player->audio_stream_index < 0) { player->audio_stream_index = (int)i; } } if (player->stream_index < 0) { goto error; } AVCodecParameters* codecpar = player->format_ctx->streams[player->stream_index]->codecpar; const AVCodec* decoder = avcodec_find_decoder(codecpar->codec_id); if (!decoder) { goto error; } player->codec_ctx = avcodec_alloc_context3(decoder); if (!player->codec_ctx) { goto error; } if (avcodec_parameters_to_context(player->codec_ctx, codecpar) < 0) { goto error; } if (avcodec_open2(player->codec_ctx, decoder, NULL) < 0) { goto error; } 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) { goto error; } 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) { goto error; } 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) { goto error; } player->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, player->width, player->height); if (!player->texture) { goto error; } 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; error: cleanup_video_player(player); return false; } 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_packet_alloc(); if (!packet) { return false; } 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) { av_packet_free(&packet); 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]); av_packet_free(&packet); return true; } } av_seek_frame(player->format_ctx, player->stream_index, 0, AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers(player->codec_ctx); av_packet_free(&packet); return false; }