#include "VideoPlayer.h" #include #include extern "C" { #include #include #include #include } VideoPlayer::VideoPlayer() {} VideoPlayer::~VideoPlayer() { if (m_texture) SDL_DestroyTexture(m_texture); if (m_rgbBuffer) av_free(m_rgbBuffer); if (m_frame) av_frame_free(&m_frame); if (m_sws) sws_freeContext(m_sws); if (m_dec) avcodec_free_context(&m_dec); if (m_fmt) avformat_close_input(&m_fmt); } bool VideoPlayer::open(const std::string& path, SDL_Renderer* renderer) { m_path = path; avformat_network_init(); if (avformat_open_input(&m_fmt, path.c_str(), nullptr, nullptr) != 0) { std::cerr << "VideoPlayer: failed to open " << path << "\n"; return false; } if (avformat_find_stream_info(m_fmt, nullptr) < 0) { std::cerr << "VideoPlayer: failed to find stream info\n"; return false; } // Find video stream m_videoStream = -1; for (unsigned i = 0; i < m_fmt->nb_streams; ++i) { if (m_fmt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { m_videoStream = (int)i; break; } } if (m_videoStream < 0) { std::cerr << "VideoPlayer: no video stream\n"; return false; } AVCodecParameters* codecpar = m_fmt->streams[m_videoStream]->codecpar; const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id); if (!codec) { std::cerr << "VideoPlayer: decoder not found\n"; return false; } m_dec = avcodec_alloc_context3(codec); if (!m_dec) { std::cerr << "VideoPlayer: failed to alloc codec ctx\n"; return false; } if (avcodec_parameters_to_context(m_dec, codecpar) < 0) { std::cerr << "VideoPlayer: param to ctx failed\n"; return false; } if (avcodec_open2(m_dec, codec, nullptr) < 0) { std::cerr << "VideoPlayer: open codec failed\n"; return false; } m_width = m_dec->width; m_height = m_dec->height; m_frame = av_frame_alloc(); m_sws = sws_getContext(m_width, m_height, m_dec->pix_fmt, m_width, m_height, AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr); m_rgbBufferSize = av_image_get_buffer_size(AV_PIX_FMT_RGBA, m_width, m_height, 1); m_rgbBuffer = (uint8_t*)av_malloc(m_rgbBufferSize); if (renderer) { m_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, m_width, m_height); if (!m_texture) { std::cerr << "VideoPlayer: failed create texture\n"; } } m_finished = false; m_textureReady = false; m_started = false; m_frameAccumulatorMs = 0.0; // Estimate frame interval. m_frameIntervalMs = 33.333; if (m_fmt && m_videoStream >= 0) { AVRational fr = m_fmt->streams[m_videoStream]->avg_frame_rate; if (fr.num > 0 && fr.den > 0) { const double fps = av_q2d(fr); if (fps > 1.0) { m_frameIntervalMs = 1000.0 / fps; } } } // Seek to start av_seek_frame(m_fmt, m_videoStream, 0, AVSEEK_FLAG_BACKWARD); if (m_dec) avcodec_flush_buffers(m_dec); return true; } bool VideoPlayer::decodeOneFrame() { if (m_finished || !m_fmt) return false; AVPacket* pkt = av_packet_alloc(); if (!pkt) { m_finished = true; return false; } int ret = 0; while (av_read_frame(m_fmt, pkt) >= 0) { if (pkt->stream_index == m_videoStream) { ret = avcodec_send_packet(m_dec, pkt); if (ret < 0) { av_packet_unref(pkt); continue; } while (ret >= 0) { ret = avcodec_receive_frame(m_dec, m_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) break; uint8_t* dstData[4] = { m_rgbBuffer, nullptr, nullptr, nullptr }; int dstLinesize[4] = { m_width * 4, 0, 0, 0 }; sws_scale(m_sws, m_frame->data, m_frame->linesize, 0, m_height, dstData, dstLinesize); m_textureReady = true; if (m_texture) { SDL_UpdateTexture(m_texture, nullptr, m_rgbBuffer, dstLinesize[0]); } av_frame_unref(m_frame); av_packet_unref(pkt); av_packet_free(&pkt); return true; } } av_packet_unref(pkt); } av_packet_free(&pkt); m_finished = true; return false; } bool VideoPlayer::decodeFirstFrame() { if (!m_fmt || m_finished) return false; if (m_textureReady) return true; // Ensure we are at the beginning. av_seek_frame(m_fmt, m_videoStream, 0, AVSEEK_FLAG_BACKWARD); if (m_dec) avcodec_flush_buffers(m_dec); return decodeOneFrame(); } void VideoPlayer::start() { m_started = true; } bool VideoPlayer::update(double deltaMs) { if (m_finished || !m_fmt) return false; if (!m_started) return true; m_frameAccumulatorMs += deltaMs; // Decode at most a small burst per frame to avoid spiral-of-death. int framesDecoded = 0; const int maxFramesPerTick = 4; while (m_frameAccumulatorMs >= m_frameIntervalMs && framesDecoded < maxFramesPerTick) { m_frameAccumulatorMs -= m_frameIntervalMs; if (!decodeOneFrame()) { return false; } ++framesDecoded; } return !m_finished; } bool VideoPlayer::update() { // Legacy behavior: decode exactly one frame. return decodeOneFrame(); } void VideoPlayer::render(SDL_Renderer* renderer, int winW, int winH) { if (!m_textureReady || !m_texture || !renderer) return; if (winW <= 0 || winH <= 0) return; SDL_FRect dst = { 0.0f, 0.0f, (float)winW, (float)winH }; SDL_RenderTexture(renderer, m_texture, nullptr, &dst); }