| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- #include "ffmpegvideopuller.h"
- #include <QDebug>
- #include <chrono>
- #include <thread>
- 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<AVFrame*>(videoBufferSize);
- m_audioBuffer = new RingBuffer<AVFrame*>(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<std::mutex> lock(m_speedMutex);
- m_speed = speed;
- }
- float FFmpegVideoPuller::getSpeed() const
- {
- std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
- }
|