|
@@ -0,0 +1,608 @@
|
|
|
+#include "DecodeVedio.h"
|
|
|
+#include "spdlog/spdlog.h"
|
|
|
+
|
|
|
+#include <QImage>
|
|
|
+#include <QThread>
|
|
|
+
|
|
|
+extern "C"
|
|
|
+{
|
|
|
+#include <libavcodec/avcodec.h>
|
|
|
+#include <libavformat/avformat.h>
|
|
|
+#include <libswscale/swscale.h>
|
|
|
+#include <libavutil/imgutils.h>
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+DecodeVedio::DecodeVedio(QThread* thread, QObject* parent) : QObject(parent) , m_thread(thread)
|
|
|
+{
|
|
|
+ /* 在连接之前调用,移动到新的线程 */
|
|
|
+ this->moveToThread(thread);
|
|
|
+ m_startThread.setSingleShot(true);
|
|
|
+ connect(&m_startThread, &QTimer::timeout, this, &DecodeVedio::do_startDecodeVedio);
|
|
|
+ thread->start();
|
|
|
+}
|
|
|
+
|
|
|
+DecodeVedio::~DecodeVedio()
|
|
|
+{
|
|
|
+ exitThread();
|
|
|
+ if(m_thread != nullptr)
|
|
|
+ {
|
|
|
+ if(m_thread->isRunning())
|
|
|
+ {
|
|
|
+ m_thread->quit();
|
|
|
+ m_thread->wait();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(m_initFFmpeg)
|
|
|
+ {
|
|
|
+ freeFFmpeg();
|
|
|
+ }
|
|
|
+ if(!m_queueImage.isEmpty())
|
|
|
+ {
|
|
|
+ int size = m_queueImage.count();
|
|
|
+ for(int i = 0; i < size; i++)
|
|
|
+ {
|
|
|
+ auto image = m_queueImage.dequeue();
|
|
|
+ if (image)
|
|
|
+ {
|
|
|
+ delete image;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 开始解码视频 */
|
|
|
+void DecodeVedio::startDecodeVedio()
|
|
|
+{
|
|
|
+ if(m_isRunning)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if(!m_initFFmpeg)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("未初始化FFMPEG...");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ /* 开启定时器,进入槽函数,就进入了新的线程 */
|
|
|
+ m_startThread.start(0);
|
|
|
+}
|
|
|
+
|
|
|
+/* 停止解码视频,退出工作函数,线程未停止 */
|
|
|
+void DecodeVedio::stopDecodeVedio()
|
|
|
+{
|
|
|
+ if(!m_isRunning)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ m_isRunning = false;
|
|
|
+ m_fileName = QString();
|
|
|
+ // /* 等待线程执行结束 */
|
|
|
+ // while(true)
|
|
|
+ // {
|
|
|
+ // if(m_threadStopped)
|
|
|
+ // {
|
|
|
+ // break;
|
|
|
+ // }
|
|
|
+ // /* 睡眠10ms */
|
|
|
+ // std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
+ // }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/* 设置当前播放位置,单位是微秒 */
|
|
|
+void DecodeVedio::setCurrentPos(quint64 pos)
|
|
|
+{
|
|
|
+ // AV_TIME_BASE
|
|
|
+ qint64 target_pos = m_startPos + pos;
|
|
|
+ SPDLOG_DEBUG("设置播放位置:{}",target_pos);
|
|
|
+ /* 暂停解码 */
|
|
|
+ m_pauseDecode = true;
|
|
|
+
|
|
|
+ /* 第二个参数设置为-1,表示所有流都跳转 */
|
|
|
+ int ret = av_seek_frame(m_pFormatContext, -1, target_pos, AVSEEK_FLAG_ANY);
|
|
|
+ if(ret < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("跳转失败...");
|
|
|
+ }
|
|
|
+ /* 清空队列 */
|
|
|
+ m_mutexQueue.lock();
|
|
|
+ int size = m_queueImage.count();
|
|
|
+ for(int i = 0; i < size; i++)
|
|
|
+ {
|
|
|
+ auto image = m_queueImage.dequeue();
|
|
|
+ if (image)
|
|
|
+ {
|
|
|
+ delete image;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ m_mutexQueue.unlock();
|
|
|
+ SPDLOG_INFO("跳转完成 , queue {}", m_queueImage.count());
|
|
|
+ /* 继续解码 */
|
|
|
+ m_pauseDecode = false;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/* 获取当前播放位置 */
|
|
|
+qint64 DecodeVedio::getCurrentPos()
|
|
|
+{
|
|
|
+
|
|
|
+ return m_currentPos - m_startPos;
|
|
|
+}
|
|
|
+
|
|
|
+/* 获取视频时长 */
|
|
|
+qint64 DecodeVedio::getDuration()
|
|
|
+{
|
|
|
+ return m_duration;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/* 唤醒队列不满条件变量 */
|
|
|
+void DecodeVedio::wakeUpCondQueueNoEmpty()
|
|
|
+{
|
|
|
+ m_condQueueNoFull.wakeAll();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 获取一帧图像,队列为空就返回nullptr,这个函数应该是运行在UI线程中的
|
|
|
+ * @warning 传出这个指针后,队列就出队了,内存需要外面获取的实例释放
|
|
|
+ * @return QImage* 一帧图像的指针
|
|
|
+ */
|
|
|
+QImage* DecodeVedio::getOneImage()
|
|
|
+{
|
|
|
+ if(m_queueImage.count() == 0)
|
|
|
+ {
|
|
|
+ // SPDLOG_TRACE("队列为空...");
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+ // SPDLOG_TRACE("******************************** 队列中图片个数:{} ",m_queueImage.count());
|
|
|
+ m_mutexQueue.lock();
|
|
|
+ auto image = m_queueImage.dequeue();
|
|
|
+ m_mutexQueue.unlock();
|
|
|
+ /* 唤醒可能阻塞住的解码线程,队列中的图片低于20之后再唤醒 */
|
|
|
+ if(m_queueImage.count() < 20)
|
|
|
+ {
|
|
|
+ m_condQueueNoFull.wakeAll();
|
|
|
+ }
|
|
|
+ return image;
|
|
|
+}
|
|
|
+
|
|
|
+/* 获取一帧图像,直到有图像为止 */
|
|
|
+QImage* DecodeVedio::getOneImageUntilHave()
|
|
|
+{
|
|
|
+ QImage* image = nullptr;
|
|
|
+ if(m_queueImage.count() == 0)
|
|
|
+ {
|
|
|
+ m_mutexQueue.lock();
|
|
|
+ m_condQueueNoEmpty.wait(&m_mutexQueue);
|
|
|
+ image = m_queueImage.dequeue();
|
|
|
+ m_mutexQueue.unlock();
|
|
|
+ }
|
|
|
+
|
|
|
+ return image;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 获取每秒的帧数
|
|
|
+ *
|
|
|
+ * @return int 正值表示帧数,-1表示未知,-2表示HEVC
|
|
|
+
|
|
|
+ */
|
|
|
+int DecodeVedio::getFrameCount()
|
|
|
+{
|
|
|
+ AVFormatContext *pFormatCtx = NULL;
|
|
|
+ avformat_open_input(&pFormatCtx, m_fileName.toStdString().c_str(), NULL, NULL);
|
|
|
+ avformat_find_stream_info(pFormatCtx, NULL);
|
|
|
+ /* 找到视频流 */
|
|
|
+ int videoStreamIndex = -1;
|
|
|
+ for (int i = 0; i < pFormatCtx->nb_streams; i++)
|
|
|
+ {
|
|
|
+ if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
|
|
+ videoStreamIndex = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ int fps = 0;
|
|
|
+ /* 获取视频格式是hevc还是h.264 */
|
|
|
+ enum AVCodecID codecID = pFormatCtx->streams[videoStreamIndex]->codecpar->codec_id;
|
|
|
+ if (codecID == AV_CODEC_ID_HEVC)
|
|
|
+ {
|
|
|
+ SPDLOG_DEBUG("视频格式是HEVC");
|
|
|
+ fps = -2;
|
|
|
+ // AVPacket packet;
|
|
|
+ // int frameCount = 0;
|
|
|
+ // double timeBase = av_q2d(pFormatCtx->streams[videoStreamIndex]->time_base);
|
|
|
+ // double duration = 2.0; // Duration in seconds
|
|
|
+
|
|
|
+ // while (av_read_frame(pFormatCtx, &packet) >= 0)
|
|
|
+ // {
|
|
|
+ // if (packet.stream_index == videoStreamIndex)
|
|
|
+ // {
|
|
|
+ // double packetTime = packet.pts * timeBase;
|
|
|
+ // if (packetTime <= duration) {
|
|
|
+ // frameCount++;
|
|
|
+ // } else {
|
|
|
+ // break;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // av_packet_unref(&packet);
|
|
|
+ // }
|
|
|
+ // fps = frameCount / duration;
|
|
|
+ }
|
|
|
+ else if(codecID == AV_CODEC_ID_H264)
|
|
|
+ {
|
|
|
+ SPDLOG_DEBUG("视频格式是H.264");
|
|
|
+ /* 获取到分子和分母,一除就是帧率 */
|
|
|
+ AVRational frameRate = pFormatCtx->streams[videoStreamIndex]->avg_frame_rate;
|
|
|
+ fps = frameRate.num*1.0 / frameRate.den;
|
|
|
+ // SPDLOG_DEBUG("分子:{} 分母:{} 帧率:{}",frameRate.num, frameRate.den, fps);
|
|
|
+ }else
|
|
|
+ {
|
|
|
+ fps = 30;
|
|
|
+ }
|
|
|
+
|
|
|
+ avformat_close_input(&pFormatCtx);
|
|
|
+
|
|
|
+ return fps;
|
|
|
+}
|
|
|
+
|
|
|
+void DecodeVedio::setVideoSize(int width, int height)
|
|
|
+{
|
|
|
+ m_width = width;
|
|
|
+ m_height = height;
|
|
|
+
|
|
|
+ /* 重新初始化缩放参数 */
|
|
|
+ // sws_freeContext(m_sws_ctx);
|
|
|
+ /* 初始化Sws Context,这是转换规则,转换成RGB */
|
|
|
+ m_sws_ctx = sws_getContext( m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt, /* 原图像大小和格式 */
|
|
|
+ m_width, m_height, AV_PIX_FMT_RGB24, /* 目标图像的大小和格式 */
|
|
|
+ SWS_BICUBIC,
|
|
|
+ nullptr,
|
|
|
+ nullptr,
|
|
|
+ nullptr);
|
|
|
+}
|
|
|
+
|
|
|
+/* 初始化函数 */
|
|
|
+void DecodeVedio::initFFmpeg(const QString& fileName)
|
|
|
+{
|
|
|
+ if(fileName != m_fileName)
|
|
|
+ {
|
|
|
+ m_fileName = fileName;
|
|
|
+ if(m_initFFmpeg)
|
|
|
+ {
|
|
|
+ freeFFmpeg();
|
|
|
+ }
|
|
|
+ }else
|
|
|
+ {
|
|
|
+ /* 清空队列 */
|
|
|
+ int size = m_queueImage.count();
|
|
|
+ for(int i = 0; i < size; i++)
|
|
|
+ {
|
|
|
+ auto image = m_queueImage.dequeue();
|
|
|
+ if (image)
|
|
|
+ {
|
|
|
+ delete image;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ m_queueImage.clear();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ /* 清空队列 */
|
|
|
+ int size = m_queueImage.count();
|
|
|
+ for(int i = 0; i < size; i++)
|
|
|
+ {
|
|
|
+ auto image = m_queueImage.dequeue();
|
|
|
+ if (image)
|
|
|
+ {
|
|
|
+ delete image;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ m_queueImage.clear();
|
|
|
+
|
|
|
+ SPDLOG_DEBUG("开始初始化FFMPEG");
|
|
|
+ /* 注册所有的解码器,从4.0开始就不需要这个初始化了 */
|
|
|
+ // av_register_all();
|
|
|
+
|
|
|
+ /* 存储文件格式信息 */
|
|
|
+ /* 打开文件,读取视频文件的头信息,放在第一个参数的结构体中 */
|
|
|
+ int ret = avformat_open_input(&m_pFormatContext, m_fileName.toStdString().c_str(), nullptr, nullptr);
|
|
|
+ if(ret != 0)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("打开视频文件错误,错误代码:{}",ret);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = 0;
|
|
|
+ /* 检查视频容器内部的流信息,将流存储到了pFormatContext->streams中
|
|
|
+ * 这个会补充avformat_open_input()没有获取到的信息 */
|
|
|
+ ret = avformat_find_stream_info(m_pFormatContext, nullptr);
|
|
|
+ if(ret < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("获取视频流错误,错误代码:{}",ret);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ m_duration = m_pFormatContext->duration;
|
|
|
+ m_currentPos = 0;
|
|
|
+ m_startPos = m_pFormatContext->start_time;
|
|
|
+ // SPDLOG_DEBUG("开始时间:{} 时长:{}",m_startPos, m_duration);
|
|
|
+
|
|
|
+ /* 一个调试函数,将流信息输出到控制台 */
|
|
|
+ av_dump_format(m_pFormatContext, 0, m_fileName.toStdString().c_str(), 0);
|
|
|
+
|
|
|
+ /************ 找到视频流 ************/
|
|
|
+ int i = 0;
|
|
|
+ AVCodecParameters *pCodecCtxOrig = nullptr;
|
|
|
+
|
|
|
+ for(i = 0;i < m_pFormatContext->nb_streams; i++)
|
|
|
+ {
|
|
|
+ if(m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
|
|
+ {
|
|
|
+ m_videoStream = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(m_videoStream == -1)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("没有找到视频流");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SPDLOG_INFO("找到视频流");
|
|
|
+
|
|
|
+ pCodecCtxOrig = m_pFormatContext->streams[m_videoStream]->codecpar;
|
|
|
+ SPDLOG_INFO("获取视频流参数成功!");
|
|
|
+ /* 使用编码器指向流 */
|
|
|
+ AVCodec *pCodec = nullptr;
|
|
|
+ /* 找到解码器 */
|
|
|
+ pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
|
|
|
+ if(pCodec == nullptr)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("没有找到解码器");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SPDLOG_TRACE("找到解码器");
|
|
|
+ /* 复制上下文,先分配空间,后面记得释放空间 */
|
|
|
+ m_pCodecCtx = avcodec_alloc_context3(pCodec);
|
|
|
+ SPDLOG_TRACE("分配空间成功!");
|
|
|
+ /* 将视频流中的编码器参数拷贝下来,这个函数不是线程安全的 */
|
|
|
+ if(avcodec_parameters_to_context(m_pCodecCtx, pCodecCtxOrig) != 0)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("复制上下文错误");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SPDLOG_INFO("复制上下文成功!");
|
|
|
+ if(avcodec_open2(m_pCodecCtx, pCodec, nullptr) < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("打开解码器错误");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SPDLOG_TRACE("打开编码器成功!");
|
|
|
+
|
|
|
+ /******** 读取数据(解码数据) ********/
|
|
|
+ m_packet = av_packet_alloc();
|
|
|
+ av_init_packet(m_packet);
|
|
|
+
|
|
|
+ /********* 创建两个pFrame,一个存放原始数据,一个存放转换后的RGB数据 **********/
|
|
|
+ m_pFrame = av_frame_alloc();
|
|
|
+ if(m_pFrame == nullptr)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("创建pFrame错误");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ m_pFrameRGB = av_frame_alloc();
|
|
|
+ if(m_pFrameRGB == nullptr)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("创建pFrameRGB错误");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ int numBytes = 0;
|
|
|
+ /* 初始化pFrameRGB */
|
|
|
+ numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, m_pCodecCtx->width, m_pCodecCtx->height, 1);
|
|
|
+ m_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
|
|
|
+ /* 获取视频相关信息 */
|
|
|
+ m_width = m_pCodecCtx->width;
|
|
|
+ m_height = m_pCodecCtx->height;
|
|
|
+ m_frameCount = m_pFormatContext->streams[m_videoStream]->nb_frames;
|
|
|
+
|
|
|
+ /* 这个函数的实际作用是将buffer设置给pFrameRGB作为原始数据的内存区域 */
|
|
|
+ av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, m_buffer, AV_PIX_FMT_RGB24, m_pCodecCtx->width, m_pCodecCtx->height, 1);
|
|
|
+
|
|
|
+ /********** 创建一个SwsContext结构体,主要为了格式转化的时候使用 ***********/
|
|
|
+
|
|
|
+ /* 初始化Sws Context,这是转换规则,转换成RGB */
|
|
|
+ m_sws_ctx = sws_getContext( m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt, /* 原图像大小和格式 */
|
|
|
+ m_width, m_height, AV_PIX_FMT_RGB24, /* 目标图像的大小和格式 */
|
|
|
+ SWS_BILINEAR, /* 双线性 */
|
|
|
+ nullptr,
|
|
|
+ nullptr,
|
|
|
+ nullptr);
|
|
|
+
|
|
|
+ m_initFFmpeg = true;
|
|
|
+ SPDLOG_INFO("FFMPEG初始化完成!");
|
|
|
+ /* 这里不能释放,否则会出问题 */
|
|
|
+ // avcodec_parameters_free(&pCodecCtxOrig);
|
|
|
+}
|
|
|
+
|
|
|
+/* 取消ffmpeg初始化函数 */
|
|
|
+void DecodeVedio::freeFFmpeg()
|
|
|
+{
|
|
|
+ /************** 清理缓冲区,释放空间 *********************/
|
|
|
+ av_free(m_buffer);
|
|
|
+ m_buffer = nullptr;
|
|
|
+ av_free(m_pFrameRGB);
|
|
|
+ m_pFrameRGB = nullptr;
|
|
|
+ /* 释放pFrame */
|
|
|
+ av_free(m_pFrame);
|
|
|
+ m_pFrame = nullptr;
|
|
|
+ av_packet_free(&m_packet);
|
|
|
+ // sws_freeContext(m_sws_ctx);
|
|
|
+ /* 关闭解码器 */
|
|
|
+ avcodec_close(m_pCodecCtx);
|
|
|
+ /* 关闭文件 */
|
|
|
+ avformat_close_input(&m_pFormatContext);
|
|
|
+
|
|
|
+ QString m_fileName = QString();
|
|
|
+ int m_videoStream = -1; /* 记录视频流是第几个流 */
|
|
|
+ bool m_initFFmpeg = false;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 进入之后就会一直解码,完成一个帧就会发送新一个信号
|
|
|
+ *
|
|
|
+ */
|
|
|
+void DecodeVedio::decodeVedio()
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ int retFrame = 0;
|
|
|
+ int retPacket = 0;
|
|
|
+ while(m_isRunning)
|
|
|
+ {
|
|
|
+ /* 暂停解码 */
|
|
|
+ while(m_pauseDecode)
|
|
|
+ {
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
+ }
|
|
|
+ std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
|
|
|
+ /* 读取一帧压缩码流,如果使用多线程解码,就从这里分发数据流的包 */
|
|
|
+ retPacket = av_read_frame(m_pFormatContext, m_packet);
|
|
|
+ std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
|
|
|
+ // SPDLOG_TRACE("======================================= 读取数据包耗时(us):{}",std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count());
|
|
|
+ if(retPacket == AVERROR_EOF)
|
|
|
+ {
|
|
|
+ SPDLOG_INFO("读取到文件末尾...");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if(retPacket < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("读取帧错误...");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ /* 判断是否是视频帧 */
|
|
|
+ if(m_packet->stream_index == m_videoStream)
|
|
|
+ {
|
|
|
+ std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
|
|
|
+ /* 解码视频帧,现在使用新的API */
|
|
|
+ ret = avcodec_send_packet(m_pCodecCtx, m_packet);
|
|
|
+ std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
|
|
|
+ if(ret < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("发送数据包错误...");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // SPDLOG_TRACE("======================================= 发送数据包耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t3).count());
|
|
|
+ while (m_isRunning)
|
|
|
+ {
|
|
|
+ /* 从解码器接收解码后的帧的函数。*/
|
|
|
+ retFrame = avcodec_receive_frame(m_pCodecCtx, m_pFrame);
|
|
|
+ std::chrono::steady_clock::time_point t5 = std::chrono::steady_clock::now();
|
|
|
+ // SPDLOG_TRACE("======================================= 接收帧耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t5 - t4).count());
|
|
|
+ if(retFrame == AVERROR(EAGAIN) || retFrame == AVERROR_EOF)
|
|
|
+ {
|
|
|
+ // SPDLOG_TRACE("没有更多的帧可以输出,跳出循环。");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if(retFrame < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("解码错误...");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (retFrame == 0)
|
|
|
+ {
|
|
|
+ /* 现在的位置,转换成时间,乘上AV_TIME_BASE后,单位是微秒 */
|
|
|
+ m_currentPos = m_pFrame->pts * av_q2d(m_pFormatContext->streams[m_videoStream]->time_base) * AV_TIME_BASE;
|
|
|
+ SPDLOG_DEBUG("======== 当前位置:{} ========",m_currentPos);
|
|
|
+ /* 成功接收到一帧,处理解码后的帧 */
|
|
|
+ std::chrono::steady_clock::time_point t6 = std::chrono::steady_clock::now();
|
|
|
+ /* 将帧转换为RGB */
|
|
|
+ sws_scale(m_sws_ctx, (uint8_t const * const *)m_pFrame->data, m_pFrame->linesize, 0, m_pCodecCtx->height,
|
|
|
+ m_pFrameRGB->data, m_pFrameRGB->linesize);
|
|
|
+ std::chrono::steady_clock::time_point t7 = std::chrono::steady_clock::now();
|
|
|
+ // SPDLOG_TRACE("======================================= 转换RGB耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t7 - t6).count());
|
|
|
+ /* 一帧图像入队 */
|
|
|
+ auto image = new QImage(m_pFrameRGB->data[0], m_width, m_height, QImage::Format_RGB888);
|
|
|
+ m_mutexQueue.lock();
|
|
|
+ if(m_queueImage.count() >= m_queueMaxNum)
|
|
|
+ {
|
|
|
+ // SPDLOG_TRACE("队列满了...");
|
|
|
+ m_condQueueNoFull.wait(&m_mutexQueue);
|
|
|
+ }
|
|
|
+ m_queueImage.enqueue(image);
|
|
|
+ m_mutexQueue.unlock();
|
|
|
+ /* 队列有数据,唤醒阻塞函数 */
|
|
|
+ m_condQueueNoEmpty.wakeAll();
|
|
|
+ /* 同时发送信号 */
|
|
|
+ emit signal_oneImage();
|
|
|
+ }
|
|
|
+ else if (retFrame == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
|
|
+ {
|
|
|
+ // 没有更多的帧可以输出,跳出循环
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (retFrame < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_TRACE("解码错误...");
|
|
|
+ av_packet_unref(m_packet);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ av_frame_unref(m_pFrame);
|
|
|
+ retFrame = -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ }else
|
|
|
+ {
|
|
|
+ // SPDLOG_TRACE("不是视频帧...");
|
|
|
+ }
|
|
|
+ // av_free_packet(AVPacket *pkt)
|
|
|
+ av_packet_unref(m_packet);
|
|
|
+ }
|
|
|
+
|
|
|
+ freeFFmpeg();
|
|
|
+ /* 清空队列 */
|
|
|
+ m_mutexQueue.lock();
|
|
|
+ int count = m_queueImage.count();
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
+ {
|
|
|
+ auto image = m_queueImage.dequeue();
|
|
|
+ if (image)
|
|
|
+ {
|
|
|
+ delete image;
|
|
|
+ image = nullptr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m_mutexQueue.unlock();
|
|
|
+ /* 线程已停止 */
|
|
|
+ m_threadStopped = true;
|
|
|
+}
|
|
|
+
|
|
|
+/* 退出线程,将所有可能暂停线程运行的条件全部唤醒 */
|
|
|
+void DecodeVedio::exitThread()
|
|
|
+{
|
|
|
+ if(m_isRunning)
|
|
|
+ {
|
|
|
+ m_isRunning = false;
|
|
|
+ }
|
|
|
+ m_pauseDecode = false;
|
|
|
+ m_condQueueNoFull.wakeAll();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* 开启解码 */
|
|
|
+void DecodeVedio::do_startDecodeVedio()
|
|
|
+{
|
|
|
+ SPDLOG_DEBUG("线程ID:{}",QThread::currentThreadId());
|
|
|
+ // if(!m_initFFmpeg)
|
|
|
+ // {
|
|
|
+ // initFFmpeg();
|
|
|
+ // }
|
|
|
+ m_isRunning = true;
|
|
|
+ m_threadStopped = false;
|
|
|
+ m_pauseDecode = false;
|
|
|
+ /* 进入解码,直到播放完成或者手动退出 */
|
|
|
+ decodeVedio();
|
|
|
+ SPDLOG_TRACE("播放完成...");
|
|
|
+}
|