#include "VideoPlayer.h"
#include <iostream>
VideoPlayer::VideoPlayer() {
state = STOPPED;
speed_factor = 1.0;
seek_req = false;
seek_target = 0.0;
format_ctx = nullptr;
codec_ctx = nullptr;
sws_ctx = nullptr;
video_stream_idx = -1;
previous_pts = 0.0;
window = nullptr;
renderer = nullptr;
texture = nullptr;
}
VideoPlayer::~VideoPlayer() {
stop();
}
// ---------------------------------------------------------
// [Feature] Open Video: Initialize FFmpeg decoder and SDL renderer
// ---------------------------------------------------------
bool VideoPlayer::open(const std::string& filepath) {
// 1. Open file and parse header info
format_ctx = avformat_alloc_context();
if (avformat_open_input(&format_ctx, filepath.c_str(), nullptr, nullptr) != 0) {
std::cerr << "Failed to open video file." << std::endl;
return false;
}
if (avformat_find_stream_info(format_ctx, nullptr) < 0) {
std::cerr << "Failed to find stream info." << std::endl;
return false;
}
// 2. Locate the video stream
const AVCodec* codec = nullptr;
video_stream_idx = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
if (video_stream_idx < 0) {
std::cerr << "Failed to find video stream." << std::endl;
return false;
}
// 3. Configure FFmpeg 7.0 decoding context
AVStream* video_stream = format_ctx->streams[video_stream_idx];
codec_ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codec_ctx, video_stream->codecpar);
if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
std::cerr << "Failed to open codec." << std::endl;
return false;
}
// 4. Initialize SDL rendering layer
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
std::cerr << "SDL Initialization failed." << std::endl;
return false;
}
window = SDL_CreateWindow("FFmpeg 7.0 Player",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
codec_ctx->width, codec_ctx->height, SDL_WINDOW_SHOWN);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
codec_ctx->width, codec_ctx->height);
// 5. Initialize image conversion context (e.g., YUV to RGB adaptation)
sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
SWS_BILINEAR, nullptr, nullptr, nullptr);
return true;
}
// ---------------------------------------------------------
// [Feature] Play: Launch independent decoding & rendering thread
// ---------------------------------------------------------
void VideoPlayer::play() {
if (state == STOPPED) {
state = PLAYING;
decode_thread = std::thread(&VideoPlayer::decodeLoop, this);
}
}
// ---------------------------------------------------------
// [Feature] Pause: Change state flag, thread will suspend itself gracefully
// ---------------------------------------------------------
void VideoPlayer::pause() {
if (state == PLAYING) {
state = PAUSED;
}
}
// ---------------------------------------------------------
// [Feature] Resume: Wake up the suspended decoding thread via condition variable
// ---------------------------------------------------------
void VideoPlayer::resume() {
if (state == PAUSED) {
std::lock_guard<std::mutex> lock(state_mutex);
state = PLAYING;
pause_cv.notify_one();
}
}
// ---------------------------------------------------------
// [Feature] Stop: End lifecycle, join threads securely, and free memory
// ---------------------------------------------------------
void VideoPlayer::stop() {
if (state != STOPPED) {
state = STOPPED;
pause_cv.notify_all(); // Prevent thread deadlock if stopped while paused
if (decode_thread.joinable()) {
decode_thread.join();
}
}
cleanup();
}
// ---------------------------------------------------------
// [Feature] Seek: Set flag, thread captures it and relocates file pointer
// ---------------------------------------------------------
void VideoPlayer::seek(double target_seconds) {
seek_target = target_seconds;
seek_req = true; // Trigger atomic flag
}
// ---------------------------------------------------------
// [Feature] Speed: Adjust rate factor modifying physical thread sleep time
// ---------------------------------------------------------
void VideoPlayer::setSpeed(double speed) {
if (speed > 0.0) {
speed_factor = speed;
}
}
// ---------------------------------------------------------
// [Feature] Main Worker Thread: Demuxing, decoding, and A/V sync control
// ---------------------------------------------------------
void VideoPlayer::decodeLoop() {
AVPacket* packet = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
while (state != STOPPED) {
// 1. Handle Pause logic
if (state == PAUSED) {
std::unique_lock<std::mutex> lock(state_mutex);
pause_cv.wait(lock, [this] { return state != PAUSED; });
previous_pts = 0; // Reset clock upon resume to avoid huge delay bursts
continue;
}
// 2. Handle Seek request
if (seek_req) {
// Convert to FFmpeg internal time base (1,000,000)
int64_t seek_pos = seek_target * AV_TIME_BASE;
// Seek file pointer (AVSEEK_FLAG_BACKWARD: prioritize nearest preceding I-Frame)
if (avformat_seek_file(format_ctx, -1, INT64_MIN, seek_pos, seek_pos, AVSEEK_FLAG_BACKWARD) >= 0) {
// [MANDATORY for FFmpeg 7.0] Flush leftover cache in decoder to prevent mosaic/artifacts
avcodec_flush_buffers(codec_ctx);
}
seek_req = false;
previous_pts = 0; // Reset clock reference
}
// 3. Read demuxed packets
if (av_read_frame(format_ctx, packet) >= 0) {
if (packet->stream_index == video_stream_idx) {
// FFmpeg 7.0 new API: Send Packet to decoder
int ret = avcodec_send_packet(codec_ctx, packet);
if (ret < 0) {
av_packet_unref(packet);
continue;
}
// Loop to receive decoded continuous Frames
while (ret >= 0) {
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
// --- Clock Sync & Speed Calculation ---
AVStream* stream = format_ctx->streams[video_stream_idx];
// Calculate current frame's physical timestamp (seconds)
double current_pts = frame->pts * av_q2d(stream->time_base);
if (previous_pts > 0) {
// Standard delay from the previous frame
double delay_seconds = current_pts - previous_pts;
// Apply speed factor (e.g., 2.0x halves the delay)
double actual_delay = delay_seconds / speed_factor;
// Sleep current thread waiting for rendering time
if (actual_delay > 0 && actual_delay < 1.0) { // Filter anomalous data
av_usleep((int64_t)(actual_delay * 1000000.0));
}
}
previous_pts = current_pts;
// -----------------------
// Execute screen rendering
renderFrame(frame);
}
}
av_packet_unref(packet);
} else {
// End of File (EOF) reached
break;
}
}
av_frame_free(&frame);
av_packet_free(&packet);
state = STOPPED;
}
// ---------------------------------------------------------
// [Feature] Render Frame: Update texture and refresh SDL window
// ---------------------------------------------------------
void VideoPlayer::renderFrame(AVFrame* frame) {
if (!renderer || !texture) return;
// Extract YUV pixel data
SDL_UpdateYUVTexture(texture, nullptr,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
SDL_RenderPresent(renderer);
}
// ---------------------------------------------------------
// [Feature] Cleanup: Destroy all C-style heap memory and handles
// ---------------------------------------------------------
void VideoPlayer::cleanup() {
if (sws_ctx) { sws_freeContext(sws_ctx); sws_ctx = nullptr; }
if (codec_ctx) { avcodec_free_context(&codec_ctx); }
if (format_ctx) { avformat_close_input(&format_ctx); }
if (texture) { SDL_DestroyTexture(texture); texture = nullptr; }
if (renderer) { SDL_DestroyRenderer(renderer); renderer = nullptr; }
if (window) { SDL_DestroyWindow(window); window = nullptr; }
SDL_Quit();
}