#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; // 初始化PTS m_videoPts = 0.0; m_audioPts = 0.0; // 解码线程 m_decodeThread = QThread::create([this]() { this->decodeLoop(); }); m_decodeThread->start(); // 视频播放线程 m_videoPlayThread = QThread::create([this]() { this->videoPlayLoop(); }); m_videoPlayThread->start(); // 音频播放线程 m_audioPlayThread = QThread::create([this]() { this->audioPlayLoop(); }); m_audioPlayThread->start(); } void FFmpegVideoPuller::stop() { m_running = false; if (m_decodeThread) { m_decodeThread->quit(); m_decodeThread->wait(); delete m_decodeThread; m_decodeThread = nullptr; } if (m_videoPlayThread) { m_videoPlayThread->quit(); m_videoPlayThread->wait(); delete m_videoPlayThread; m_videoPlayThread = nullptr; } if (m_audioPlayThread) { m_audioPlayThread->quit(); m_audioPlayThread->wait(); delete m_audioPlayThread; m_audioPlayThread = 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::videoPlayLoop() { // 如果没有视频流,直接返回 if (m_videoStreamIdx == -1) { return; } 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); // 更新视频PTS m_videoPts = vPts; // 渲染视频帧 if (m_videoRenderCallback) { m_videoRenderCallback(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); // 如果只有视频没有音频,直接按照视频帧率播放 if (m_audioStreamIdx == -1) { if (delay > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } } else { // 有音频时,需要考虑音视频同步 double aPts = m_audioPts.load(); // 如果视频比音频快太多,等待音频追上 if (vPts > aPts + 0.1) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); continue; // 不前进到下一帧,等待音频追上 } // 正常播放速度控制 if (delay > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } } // 前进到下一帧 nextVideoFrame(); // 更新当前pts(用于进度显示) { std::lock_guard lock(m_ptsMutex); m_currentPts = vPts; } } } void FFmpegVideoPuller::audioPlayLoop() { // 如果没有音频流,直接返回 if (m_audioStreamIdx == -1) { return; } while (m_running) { // 获取当前音频帧 AVFrame* aFrame = getCurrentAudioFrame(); if (!aFrame) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } // 计算音频PTS double aPts = aFrame->best_effort_timestamp * av_q2d(m_fmtCtx->streams[m_audioStreamIdx]->time_base); // 更新音频PTS m_audioPts = aPts; // 如果只有音频没有视频,直接播放音频 if (m_videoStreamIdx == -1) { // 播放音频帧 if (m_audioPlayCallback) { m_audioPlayCallback(aFrame); } // 更新当前pts(用于进度显示) { std::lock_guard lock(m_ptsMutex); m_currentPts = aPts; } // 前进到下一帧 nextAudioFrame(); // 控制音频播放速度 float speed = getSpeed(); int samples = aFrame->nb_samples; int sample_rate = aFrame->sample_rate; double frame_duration = samples / (double)sample_rate; int delay = int(frame_duration * 1000 / speed); if (delay > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } continue; // 继续下一帧处理 } // 有视频时,需要考虑音视频同步 double vPts = m_videoPts.load(); // 如果音频PTS超前于视频PTS太多,等待视频追上 if (aPts > vPts + 0.1) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; // 不前进到下一帧,等待视频追上 } // 如果音频比视频慢太多,可能需要跳过一些音频帧来追赶视频 if (aPts < vPts - 0.2) { // 跳过当前音频帧,直接前进 nextAudioFrame(); continue; } // 播放音频帧 if (m_audioPlayCallback) { m_audioPlayCallback(aFrame); } // 前进到下一帧 nextAudioFrame(); // 控制音频播放速度,与视频保持一致 float speed = getSpeed(); if (speed != 1.0f) { // 根据倍速调整音频播放间隔 int samples = aFrame->nb_samples; int sample_rate = aFrame->sample_rate; double frame_duration = samples / (double)sample_rate; int delay = int(frame_duration * 1000 / speed); if (delay > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } } } } // 进度相关 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_videoPlayThread) { m_videoPlayThread->quit(); m_videoPlayThread->wait(); delete m_videoPlayThread; m_videoPlayThread = nullptr; } if (m_audioPlayThread) { m_audioPlayThread->quit(); m_audioPlayThread->wait(); delete m_audioPlayThread; m_audioPlayThread = 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; // 重置PTS m_videoPts = pts; m_audioPts = pts; m_currentPts = pts; // 重新启动线程 m_running = true; m_decodeThread = QThread::create([this]() { this->decodeLoop(); }); m_decodeThread->start(); m_videoPlayThread = QThread::create([this]() { this->videoPlayLoop(); }); m_videoPlayThread->start(); m_audioPlayThread = QThread::create([this]() { this->audioPlayLoop(); }); m_audioPlayThread->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; }