173 lines
5.7 KiB
C++
173 lines
5.7 KiB
C++
#include "VideoPlayer.h"
|
|
|
|
#include <iostream>
|
|
#include <chrono>
|
|
|
|
extern "C" {
|
|
#include <libavformat/avformat.h>
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libswscale/swscale.h>
|
|
#include <libavutil/imgutils.h>
|
|
}
|
|
|
|
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);
|
|
}
|