#include "ffmpegvideopuller.h" #include #include #include FFmpegVideoPuller::FFmpegVideoPuller(QObject* parent) : QObject(parent) { // av_register_all(); avformat_network_init(); } FFmpegVideoPuller::~FFmpegVideoPuller() { stop(); } bool FFmpegVideoPuller::open(const QString& url, int videoBufferSec, int audioBufferSec) { m_url = url; // 打开流 if (avformat_open_input(&m_fmtCtx, url.toStdString().c_str(), nullptr, nullptr) < 0) return false; if (avformat_find_stream_info(m_fmtCtx, nullptr) < 0) return false; // 查找视频流 for (unsigned i = 0; i < m_fmtCtx->nb_streams; ++i) { if (m_fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && m_videoStreamIdx == -1) { m_videoStreamIdx = i; } if (m_fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && m_audioStreamIdx == -1) { m_audioStreamIdx = i; } } if (m_videoStreamIdx == -1) return false; // 打开视频解码器 const AVCodec* vcodec = avcodec_find_decoder( m_fmtCtx->streams[m_videoStreamIdx]->codecpar->codec_id); m_videoCodecCtx = avcodec_alloc_context3(vcodec); avcodec_parameters_to_context(m_videoCodecCtx, m_fmtCtx->streams[m_videoStreamIdx]->codecpar); avcodec_open2(m_videoCodecCtx, vcodec, nullptr); // 打开音频解码器 if (m_audioStreamIdx != -1) { const AVCodec* acodec = avcodec_find_decoder( m_fmtCtx->streams[m_audioStreamIdx]->codecpar->codec_id); m_audioCodecCtx = avcodec_alloc_context3(acodec); avcodec_parameters_to_context(m_audioCodecCtx, m_fmtCtx->streams[m_audioStreamIdx]->codecpar); avcodec_open2(m_audioCodecCtx, acodec, nullptr); } // 计算缓冲区大小 int videoFps = 25; AVRational fr = m_fmtCtx->streams[m_videoStreamIdx]->avg_frame_rate; if (fr.num && fr.den) videoFps = int(av_q2d(fr)); int videoBufferSize = videoBufferSec * videoFps; int audioBufferSize = audioBufferSec * 50; // 假设每秒50帧 // 创建缓冲区 m_videoBuffer = new RingBuffer(videoBufferSize); m_audioBuffer = new RingBuffer(audioBufferSize); m_videoBuffer->setDeleter([](AVFrame*& f) { av_frame_free(&f); }); m_audioBuffer->setDeleter([](AVFrame*& f) { av_frame_free(&f); }); m_videoPlayIndex = 0; m_audioPlayIndex = 0; m_currentPts = 0.0; // 获取总时长 if (m_fmtCtx->duration > 0) { m_totalDuration = m_fmtCtx->duration / (double)AV_TIME_BASE; } else { m_totalDuration = -1.0; } return true; } void FFmpegVideoPuller::setSpeed(float speed) { std::lock_guard lock(m_speedMutex); m_speed = speed; } float FFmpegVideoPuller::getSpeed() const { std::lock_guard lock(m_speedMutex); return m_speed; } void FFmpegVideoPuller::start() { if (m_running) return; m_running = true; // 解码线程 m_decodeThread = QThread::create([this]() { this->decodeLoop(); }); m_decodeThread->start(); // 播放线程 m_playThread = QThread::create([this]() { this->playLoop(); }); m_playThread->start(); } void FFmpegVideoPuller::stop() { m_running = false; if (m_decodeThread) { m_decodeThread->quit(); m_decodeThread->wait(); delete m_decodeThread; m_decodeThread = nullptr; } if (m_playThread) { m_playThread->quit(); m_playThread->wait(); delete m_playThread; m_playThread = nullptr; } // 释放缓冲区 if (m_videoBuffer) { m_videoBuffer->clear(); delete m_videoBuffer; m_videoBuffer = nullptr; } if (m_audioBuffer) { m_audioBuffer->clear(); delete m_audioBuffer; m_audioBuffer = nullptr; } // 释放FFmpeg资源 if (m_videoCodecCtx) avcodec_free_context(&m_videoCodecCtx); if (m_audioCodecCtx) avcodec_free_context(&m_audioCodecCtx); if (m_fmtCtx) avformat_close_input(&m_fmtCtx); } void FFmpegVideoPuller::decodeLoop() { AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); while (m_running) { if (av_read_frame(m_fmtCtx, pkt) < 0) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); continue; } if (pkt->stream_index == m_videoStreamIdx) { avcodec_send_packet(m_videoCodecCtx, pkt); while (avcodec_receive_frame(m_videoCodecCtx, frame) == 0) { double pts = frame->best_effort_timestamp * av_q2d(m_fmtCtx->streams[m_videoStreamIdx]->time_base); m_videoBuffer->push(av_frame_clone(frame), pts); } } else if (pkt->stream_index == m_audioStreamIdx) { avcodec_send_packet(m_audioCodecCtx, pkt); while (avcodec_receive_frame(m_audioCodecCtx, frame) == 0) { double pts = frame->best_effort_timestamp * av_q2d(m_fmtCtx->streams[m_audioStreamIdx]->time_base); //qDebug() << "解码到音频帧, pts=" << pts << " nb_samples=" << frame->nb_samples; m_audioBuffer->push(av_frame_clone(frame), pts); } } av_packet_unref(pkt); } av_frame_free(&frame); av_packet_free(&pkt); } void FFmpegVideoPuller::playLoop() { // while (m_running) { // AVFrame* vFrame = getCurrentVideoFrame(); // if (!vFrame) { // std::this_thread::sleep_for(std::chrono::milliseconds(5)); // continue; // } // double vPts = vFrame->best_effort_timestamp // * av_q2d(m_fmtCtx->streams[m_videoStreamIdx]->time_base); // // 这里你可以直接把vFrame送到OpenGL渲染 // // 例如: renderFrame(vFrame); // // 控制倍速 // AVRational fr = m_fmtCtx->streams[m_videoStreamIdx]->avg_frame_rate; // double frame_delay = 1.0 / (fr.num ? av_q2d(fr) : 25.0); // float speed = getSpeed(); // int delay = int(frame_delay * 1000 / speed); // qDebug() << "playLoop, m_speed=" << speed << delay; // if (delay > 0) // std::this_thread::sleep_for(std::chrono::milliseconds(delay)); // // 前进 // nextVideoFrame(); // // 更新当前pts // { // std::lock_guard lock(m_ptsMutex); // m_currentPts = vPts; // } // } while (m_running) { AVFrame* vFrame = getCurrentVideoFrame(); double vPts = vFrame ? (vFrame->best_effort_timestamp * av_q2d(m_fmtCtx->streams[m_videoStreamIdx]->time_base)) : m_currentPts.load(); if (vFrame && m_videoRenderCallback) { m_videoRenderCallback(vFrame); } // 音频同步:只要音频帧PTS小于等于视频帧PTS,就顺序播放 while (true) { AVFrame* aFrame = getCurrentAudioFrame(); if (!aFrame) break; double aPts = aFrame->best_effort_timestamp * av_q2d(m_fmtCtx->streams[m_audioStreamIdx]->time_base); if (aPts <= vPts + 0.02) { // 允许微小误差 if (m_audioPlayCallback) m_audioPlayCallback(aFrame); nextAudioFrame(); } else { break; } } // 控制倍速 AVRational fr = m_fmtCtx->streams[m_videoStreamIdx]->avg_frame_rate; double frame_delay = 1.0 / (fr.num ? av_q2d(fr) : 25.0); float speed = getSpeed(); int delay = int(frame_delay * 1000 / speed); if (delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(delay)); // 前进 nextVideoFrame(); // 更新当前pts { std::lock_guard lock(m_ptsMutex); m_currentPts = vPts; } } } // 进度相关 double FFmpegVideoPuller::getFirstPts() const { return m_videoBuffer ? m_videoBuffer->firstPts() : 0.0; } double FFmpegVideoPuller::getLastPts() const { return m_videoBuffer ? m_videoBuffer->lastPts() : 0.0; } double FFmpegVideoPuller::getCurrentPts() const { std::lock_guard lock(m_ptsMutex); return m_currentPts; } void FFmpegVideoPuller::seekToPts(double pts) { // 停止线程 m_running = false; if (m_decodeThread) { m_decodeThread->quit(); m_decodeThread->wait(); delete m_decodeThread; m_decodeThread = nullptr; } if (m_playThread) { m_playThread->quit(); m_playThread->wait(); delete m_playThread; m_playThread = nullptr; } // 清空缓冲区 if (m_videoBuffer) m_videoBuffer->clear(); if (m_audioBuffer) m_audioBuffer->clear(); // FFmpeg seek int64_t seek_target = pts / av_q2d(m_fmtCtx->streams[m_videoStreamIdx]->time_base); av_seek_frame(m_fmtCtx, m_videoStreamIdx, seek_target, AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers(m_videoCodecCtx); if (m_audioCodecCtx) avcodec_flush_buffers(m_audioCodecCtx); // 重置索引 m_videoPlayIndex = 0; m_audioPlayIndex = 0; // 重新启动线程 m_running = true; m_decodeThread = QThread::create([this]() { this->decodeLoop(); }); m_decodeThread->start(); m_playThread = QThread::create([this]() { this->playLoop(); }); m_playThread->start(); } // 取帧接口 AVFrame* FFmpegVideoPuller::getCurrentVideoFrame() { if (!m_videoBuffer) return nullptr; return m_videoBuffer->get(m_videoPlayIndex); } AVFrame* FFmpegVideoPuller::getCurrentAudioFrame() { if (!m_audioBuffer) return nullptr; return m_audioBuffer->get(m_audioPlayIndex); } void FFmpegVideoPuller::nextVideoFrame() { ++m_videoPlayIndex; if (m_videoBuffer && m_videoPlayIndex >= m_videoBuffer->size()) m_videoPlayIndex = m_videoBuffer->size() - 1; } void FFmpegVideoPuller::nextAudioFrame() { ++m_audioPlayIndex; if (m_audioBuffer && m_audioPlayIndex >= m_audioBuffer->size()) m_audioPlayIndex = m_audioBuffer->size() - 1; } size_t FFmpegVideoPuller::videoBufferSize() const { return m_videoBuffer ? m_videoBuffer->size() : 0; } size_t FFmpegVideoPuller::audioBufferSize() const { return m_audioBuffer ? m_audioBuffer->size() : 0; }