|
@@ -36,10 +36,7 @@ DecodeVedio::~DecodeVedio()
|
|
|
m_thread->wait();
|
|
|
}
|
|
|
}
|
|
|
- if(m_initFFmpeg)
|
|
|
- {
|
|
|
- freeFFmpeg();
|
|
|
- }
|
|
|
+
|
|
|
if(!m_queueImage.isEmpty())
|
|
|
{
|
|
|
int size = m_queueImage.count();
|
|
@@ -77,6 +74,12 @@ void DecodeVedio::getHWDecoder()
|
|
|
FMTLOG_INFO_NON(" {}",type.toStdString());
|
|
|
}
|
|
|
FMTLOG_INFO_NON("\n");
|
|
|
+ if(m_listHWDeviceType.isEmpty())
|
|
|
+ {
|
|
|
+ FMTLOG_WARN("没有找到硬件解码器...");
|
|
|
+ }else {
|
|
|
+ m_HWDecoder = true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/* 开始解码视频 */
|
|
@@ -92,9 +95,9 @@ void DecodeVedio::startDecodeVedio()
|
|
|
return;
|
|
|
}
|
|
|
m_isRunning = true;
|
|
|
- decodeUsingCPU();
|
|
|
+ // decodeUsingCPU();
|
|
|
/* 开启定时器,进入槽函数,就进入了新的线程 */
|
|
|
- // m_startThread.start(0);
|
|
|
+ m_startThread.start(0);
|
|
|
}
|
|
|
|
|
|
/* 停止解码视频,退出工作函数,线程未停止 */
|
|
@@ -162,7 +165,7 @@ void DecodeVedio::setCurrentPos(quint64 pos)
|
|
|
/* 第二个参数设置为-1,表示所有流都跳转 */
|
|
|
int ret = av_seek_frame(m_pFormatContext, -1, target_pos, AVSEEK_FLAG_BACKWARD);
|
|
|
/* 清空缓存 */
|
|
|
- avcodec_flush_buffers(m_pCodecCtx);
|
|
|
+ avcodec_flush_buffers(m_pCodecContext);
|
|
|
if(ret < 0)
|
|
|
{
|
|
|
SPDLOG_WARN("跳转失败...");
|
|
@@ -316,354 +319,31 @@ int DecodeVedio::getFrameCount()
|
|
|
* @param width
|
|
|
* @param height
|
|
|
*/
|
|
|
-void DecodeVedio::setVideoSize(int width, int height)
|
|
|
+// void DecodeVedio::setVideoSize(int width, int height)
|
|
|
+// {
|
|
|
+// m_width = width;
|
|
|
+// m_height = height;
|
|
|
+
|
|
|
+// if(m_sws_ctx != nullptr)
|
|
|
+// {
|
|
|
+// /* 先暂停解码器 */
|
|
|
+// pauseDecode();
|
|
|
+// sws_freeContext(m_sws_ctx);
|
|
|
+// }
|
|
|
+// /* 初始化Sws Context,这是转换规则,转换成RGB */
|
|
|
+// m_sws_ctx = sws_getContext( m_pCodecContext->width, m_pCodecContext->height, m_pCodecContext->pix_fmt, /* 原图像大小和格式 */
|
|
|
+// m_width, m_height, AV_PIX_FMT_RGB24, /* 目标图像的大小和格式 */
|
|
|
+// SWS_BICUBIC,
|
|
|
+// nullptr,
|
|
|
+// nullptr,
|
|
|
+// nullptr);
|
|
|
+// }
|
|
|
+
|
|
|
+
|
|
|
+/* 初始化硬件解码器 */
|
|
|
+void DecodeVedio::initHWDecoder(const AVCodec* codec)
|
|
|
{
|
|
|
- m_width = width;
|
|
|
- m_height = height;
|
|
|
|
|
|
- if(m_sws_ctx != nullptr)
|
|
|
- {
|
|
|
- /* 先暂停解码器 */
|
|
|
- pauseDecode();
|
|
|
- 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(m_initFFmpeg)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- m_fileName = fileName;
|
|
|
- /* 清空队列 */
|
|
|
- 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);
|
|
|
-
|
|
|
- /************ 找到视频流 ************/
|
|
|
- m_videoStream = av_find_best_stream(m_pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
|
|
|
- if(m_videoStream < 0)
|
|
|
- {
|
|
|
- SPDLOG_WARN("没有找到视频流");
|
|
|
- return;
|
|
|
- }
|
|
|
- SPDLOG_INFO("找到视频流");
|
|
|
- /* 获取视频流的编解码信息 */
|
|
|
- AVCodecParameters *pCodecCtxOrig = nullptr;
|
|
|
- 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_INFO("找到解码器 :{}",pCodec->name);
|
|
|
- /* 获取视频信息的上下文,先分配空间,后面记得释放空间 */
|
|
|
- m_pCodecCtx = avcodec_alloc_context3(pCodec);
|
|
|
- SPDLOG_TRACE("分配空间成功!");
|
|
|
- /* 将视频流中的编码器参数拷贝下来,这个函数不是线程安全的 */
|
|
|
- if(avcodec_parameters_to_context(m_pCodecCtx, pCodecCtxOrig) != 0)
|
|
|
- {
|
|
|
- SPDLOG_WARN("复制上下文错误");
|
|
|
- return;
|
|
|
- }
|
|
|
- SPDLOG_INFO("复制上下文成功!");
|
|
|
-
|
|
|
- /*************** 初始化硬件解码器 ******************/
|
|
|
-
|
|
|
- /* 打开解码器,(初始化解码器上下文,如果调用了avcodec_alloc_context3,第二个参数可以设置为nullptr) */
|
|
|
- if(avcodec_open2(m_pCodecCtx, nullptr, nullptr) < 0)
|
|
|
- {
|
|
|
- SPDLOG_ERROR("打开解码器错误");
|
|
|
- return;
|
|
|
- }
|
|
|
- SPDLOG_TRACE("打开编码器成功!");
|
|
|
-
|
|
|
- /******** 创建数据包(解码数据) ********/
|
|
|
- m_packet = av_packet_alloc();
|
|
|
- // av_init_packet(m_packet);
|
|
|
- av_new_packet(m_packet, m_pCodecCtx->width * m_pCodecCtx->height);
|
|
|
-
|
|
|
- /********* 创建两个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_RGBA, m_pCodecCtx->width, m_pCodecCtx->height, 1);
|
|
|
- m_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
|
|
|
- /* 获取视频相关信息 */
|
|
|
- m_width = m_srcWidth = m_pCodecCtx->width;
|
|
|
- m_height = m_srcHeight = m_pCodecCtx->height;
|
|
|
- m_totalFrame = m_pFormatContext->streams[m_videoStream]->nb_frames;
|
|
|
-
|
|
|
- /* 这个函数的实际作用是将buffer设置给pFrameRGB作为原始数据的内存区域 */
|
|
|
- av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, m_buffer, AV_PIX_FMT_RGBA, 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_srcWidth, m_srcHeight, AV_PIX_FMT_RGBA, /* 目标图像的大小和格式 */
|
|
|
- // SWS_BILINEAR, /* 双线性 */
|
|
|
- // nullptr,
|
|
|
- // nullptr,
|
|
|
- // nullptr);
|
|
|
-
|
|
|
- m_initFFmpeg = true;
|
|
|
- SPDLOG_INFO("FFMPEG初始化完成!");
|
|
|
- /* 这里不能释放,否则会出问题 */
|
|
|
- // avcodec_parameters_free(&pCodecCtxOrig);
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-/* 取消ffmpeg初始化函数 */
|
|
|
-void DecodeVedio::unInitFFmpeg()
|
|
|
-{
|
|
|
- stopDecodeVedio();
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-/* 取消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; /* 记录视频流是第几个流 */
|
|
|
- m_initFFmpeg = false;
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * @brief 进入之后就会一直解码,完成一个帧就会发送新一个信号
|
|
|
- *
|
|
|
- */
|
|
|
-void DecodeVedio::decodeVedio()
|
|
|
-{
|
|
|
- int ret = 0;
|
|
|
- int retFrame = 0;
|
|
|
- int retPacket = 0;
|
|
|
- m_pauseDecode = false;
|
|
|
- m_decodeStatus = true;
|
|
|
- while(m_isRunning)
|
|
|
- {
|
|
|
- /* 暂停解码 */
|
|
|
- while(m_pauseDecode)
|
|
|
- {
|
|
|
- m_decodeStatus = false;
|
|
|
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
- }
|
|
|
- m_decodeStatus = true;
|
|
|
- // 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("读取到文件末尾...");
|
|
|
- emit signal_playCompleted();
|
|
|
- /* 暂停解码 */
|
|
|
- m_pauseDecode = true;
|
|
|
- while(m_pauseDecode)
|
|
|
- {
|
|
|
- m_decodeStatus = false;
|
|
|
- std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
|
- }
|
|
|
- }
|
|
|
- else if(retPacket < 0)
|
|
|
- {
|
|
|
- SPDLOG_WARN("读取帧错误...");
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- /* 判断是否是视频帧 */
|
|
|
- if(m_packet->stream_index == m_videoStream)
|
|
|
- {
|
|
|
- /* 现在的位置,转换成时间,乘上AV_TIME_BASE后,单位是微秒 */
|
|
|
- auto tmpPos = m_packet->pts * av_q2d(m_pFormatContext->streams[m_videoStream]->time_base) * AV_TIME_BASE;
|
|
|
- /* 判断是否在跳转状态,和设置的位置相距较远就跳过 */
|
|
|
- if(m_isSeek)
|
|
|
- {
|
|
|
- if( std::abs(m_currentPos - tmpPos) > AV_TIME_BASE)
|
|
|
- {
|
|
|
- SPDLOG_DEBUG("currentPos:{}, tmpPos:{}",m_currentPos,tmpPos);
|
|
|
- av_packet_unref(m_packet);
|
|
|
- continue;
|
|
|
- }
|
|
|
- }
|
|
|
- m_isSeek = false;
|
|
|
- m_currentPos = tmpPos;
|
|
|
- // SPDLOG_DEBUG("======== 当前位置:{} ========",m_currentPos);
|
|
|
- // 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 == 0)
|
|
|
- {
|
|
|
- /* 成功接收到一帧,处理解码后的帧 */
|
|
|
- // 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);
|
|
|
- auto image = new QImage(m_pFrameRGB->data[0], m_width, m_height, QImage::Format_RGBA8888);
|
|
|
- // SPDLOG_DEBUG("队列中图片个数:{} ",m_queueImage.count());
|
|
|
- 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();
|
|
|
-
|
|
|
- retFrame = -1;
|
|
|
- }
|
|
|
- else if(retFrame < 0)
|
|
|
- {
|
|
|
- if( retFrame == AVERROR(EAGAIN) )
|
|
|
- {
|
|
|
- // SPDLOG_TRACE("在此状态下输出不可用-用户必须尝试发送新的输入...");
|
|
|
- }
|
|
|
- else if(retFrame == AVERROR_EOF)
|
|
|
- {
|
|
|
- // SPDLOG_TRACE("读取到文件末尾...");
|
|
|
- }else
|
|
|
- {
|
|
|
- // SPDLOG_TRACE("其他解码错误...{}",retFrame);
|
|
|
- }
|
|
|
- av_frame_unref(m_pFrame);
|
|
|
- break;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- SPDLOG_TRACE("其他错误...{}",retFrame);
|
|
|
- }
|
|
|
- av_frame_unref(m_pFrame);
|
|
|
- }
|
|
|
-
|
|
|
- }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;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -678,7 +358,7 @@ void DecodeVedio::openVedio(const QString& fileName)
|
|
|
m_fileName = fileName;
|
|
|
if(m_initFFmpeg)
|
|
|
{
|
|
|
- freeFFmpeg();
|
|
|
+ freeAll();
|
|
|
}
|
|
|
|
|
|
/* 清空队列 */
|
|
@@ -705,9 +385,9 @@ void DecodeVedio::openVedio(const QString& fileName)
|
|
|
/************ 存储文件格式信息 ************/
|
|
|
/* 打开文件,读取视频文件的头信息,放在第一个参数的结构体中 */
|
|
|
int ret = avformat_open_input( &m_pFormatContext, /* 解封装上下文 */
|
|
|
- m_fileName.toStdString().c_str(), /* 打开视频地址 */
|
|
|
+ m_fileName.toStdString().data(), /* 打开视频地址 */
|
|
|
nullptr, /* 这个参数强制使用特定的输入格式,可以设置为null */
|
|
|
- nullptr); /* 参数设置 */
|
|
|
+ &dict); /* 参数设置 */
|
|
|
if(ret != 0)
|
|
|
{
|
|
|
SPDLOG_WARN("打开视频文件错误,错误代码:{}",ret);
|
|
@@ -768,69 +448,79 @@ void DecodeVedio::openVedio(const QString& fileName)
|
|
|
freeAll();
|
|
|
return;
|
|
|
}
|
|
|
- SPDLOG_INFO("找到解码器 :{}",pCodec->name);
|
|
|
+ SPDLOG_INFO("找到解码器:{}",pCodec->name);
|
|
|
/* 获取视频信息的上下文,先分配空间,后面记得释放空间 */
|
|
|
- m_pCodecCtx = avcodec_alloc_context3(pCodec);
|
|
|
+ m_pCodecContext = avcodec_alloc_context3(pCodec);
|
|
|
/* 将视频流中的编码器参数拷贝下来,这个函数不是线程安全的 */
|
|
|
- if(avcodec_parameters_to_context(m_pCodecCtx, pCodecParams) != 0)
|
|
|
+ if(avcodec_parameters_to_context(m_pCodecContext, pCodecParams) != 0)
|
|
|
{
|
|
|
SPDLOG_WARN("复制上下文错误");
|
|
|
freeAll();
|
|
|
return;
|
|
|
}
|
|
|
- SPDLOG_INFO("复制上下文成功!");
|
|
|
+ SPDLOG_INFO("设置解码器参数成功!");
|
|
|
+ // m_pCodecContext->flags2 |= AV_CODEC_FLAG2_FAST; /* 使用快速解码(允许使用不符合规范的解码) */
|
|
|
+ m_pCodecContext->thread_count = 8; /* 使用8线程解码 */
|
|
|
|
|
|
- /*************** 初始化硬件解码器 ******************/
|
|
|
+ /* 初始化硬件解码器 */
|
|
|
+ if(m_HWDecoder)
|
|
|
+ {
|
|
|
+ initHWDecoder(pCodec);
|
|
|
+ }
|
|
|
|
|
|
/* 打开解码器,(初始化解码器上下文,如果调用了avcodec_alloc_context3,第二个参数可以设置为nullptr) */
|
|
|
- if(avcodec_open2(m_pCodecCtx, nullptr, nullptr) < 0)
|
|
|
+ if(avcodec_open2(m_pCodecContext, nullptr, nullptr) < 0)
|
|
|
{
|
|
|
SPDLOG_ERROR("打开解码器错误");
|
|
|
+ freeAll();
|
|
|
return;
|
|
|
}
|
|
|
- SPDLOG_TRACE("打开编码器成功!");
|
|
|
+ SPDLOG_DEBUG("打开解码器成功!");
|
|
|
|
|
|
- /******** 创建数据包(解码数据) ********/
|
|
|
+ /************ 初始化数据包 ************/
|
|
|
m_packet = av_packet_alloc();
|
|
|
- // av_init_packet(m_packet);
|
|
|
- av_new_packet(m_packet, m_pCodecCtx->width * m_pCodecCtx->height);
|
|
|
+ // av_new_packet(m_packet, m_pCodecContext->width * m_pCodecContext->height);
|
|
|
|
|
|
- /********* 创建两个pFrame,一个存放原始数据,一个存放转换后的RGB数据 **********/
|
|
|
- m_pFrame = av_frame_alloc();
|
|
|
- if(m_pFrame == nullptr)
|
|
|
+ /* 创建两个pFrame,一个存放原始数据,一个存放转换后的RGB数据 */
|
|
|
+ m_pFrameSRC = av_frame_alloc();
|
|
|
+ if(m_pFrameSRC == nullptr)
|
|
|
{
|
|
|
SPDLOG_ERROR("创建pFrame错误");
|
|
|
+ freeAll();
|
|
|
return;
|
|
|
}
|
|
|
m_pFrameRGB = av_frame_alloc();
|
|
|
if(m_pFrameRGB == nullptr)
|
|
|
{
|
|
|
SPDLOG_ERROR("创建pFrameRGB错误");
|
|
|
+ freeAll();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ m_pFrameHW = av_frame_alloc();
|
|
|
+ if(m_pFrameHW == nullptr)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("创建pFrameHW错误");
|
|
|
+ freeAll();
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ if(m_buffer != nullptr)
|
|
|
+ {
|
|
|
+ av_free(m_buffer);
|
|
|
+ m_buffer = nullptr;
|
|
|
+ }
|
|
|
|
|
|
- int numBytes = 0;
|
|
|
- /* 初始化pFrameRGB */
|
|
|
- numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, m_pCodecCtx->width, m_pCodecCtx->height, 1);
|
|
|
- m_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
|
|
|
+ /* 分配图像空间 */
|
|
|
+ int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, m_pCodecContext->width, m_pCodecContext->height, 1);
|
|
|
+ /* 这里多分配了一些空间,防止某些视频图像在使用sws_scale()拷贝后超出数组长度 */
|
|
|
+ m_buffer = (uint8_t *)av_malloc(numBytes + 1000);
|
|
|
|
|
|
/* 这个函数的实际作用是将buffer设置给pFrameRGB作为原始数据的内存区域 */
|
|
|
- av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, m_buffer, AV_PIX_FMT_RGBA, 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_srcWidth, m_srcHeight, AV_PIX_FMT_RGBA, /* 目标图像的大小和格式 */
|
|
|
- // SWS_BILINEAR, /* 双线性 */
|
|
|
- // nullptr,
|
|
|
- // nullptr,
|
|
|
- // nullptr);
|
|
|
+ av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, m_buffer, AV_PIX_FMT_RGBA, m_pCodecContext->width, m_pCodecContext->height, 1);
|
|
|
|
|
|
m_initFFmpeg = true;
|
|
|
SPDLOG_INFO("FFMPEG初始化完成!");
|
|
|
- /* 这里不能释放,否则会出问题 */
|
|
|
- // avcodec_parameters_free(&pCodecCtxOrig);
|
|
|
+ SPDLOG_INFO("视频宽度:{} 高度:{} 帧率:{} 总时长:{} 总帧数:{}",m_width, m_height, m_fps, m_duration, m_totalFrame);
|
|
|
}
|
|
|
|
|
|
|
|
@@ -841,45 +531,26 @@ void DecodeVedio::openVedio(const QString& fileName)
|
|
|
void DecodeVedio::decodeUsingCPU()
|
|
|
{
|
|
|
/******** 初始化局部变量 ********/
|
|
|
+ bool isEnd = false;
|
|
|
int ret = 0;
|
|
|
int retFrame = 0;
|
|
|
int retPacket = 0;
|
|
|
m_pauseDecode = false;
|
|
|
m_decodeStatus = true;
|
|
|
- /* 创建数据包 */
|
|
|
- AVPacket* packet = av_packet_alloc();
|
|
|
- // av_init_packet(m_packet);
|
|
|
- ret = av_new_packet(m_packet, m_pCodecCtx->width * m_pCodecCtx->height);
|
|
|
- if(ret < 0)
|
|
|
- {
|
|
|
- SPDLOG_ERROR("创建数据包packet错误");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- /* 创建两个pFrame,一个存放原始数据,一个存放转换后的RGB数据 */
|
|
|
- AVFrame* pFrameSRC = av_frame_alloc();
|
|
|
- if(pFrameSRC == nullptr)
|
|
|
- {
|
|
|
- SPDLOG_ERROR("创建pFrameSRC错误");
|
|
|
- return;
|
|
|
- }
|
|
|
- AVFrame* pFrameRGB = av_frame_alloc();
|
|
|
- if(pFrameRGB == nullptr)
|
|
|
- {
|
|
|
- SPDLOG_ERROR("创建pFrameRGB错误");
|
|
|
- return;
|
|
|
- }
|
|
|
+
|
|
|
/* 设置环形队列的大小 */
|
|
|
m_ringQueue.setQueueSize(30);
|
|
|
|
|
|
+ /* 开始解码 */
|
|
|
+ SPDLOG_DEBUG("开始解码...");
|
|
|
while(m_isRunning)
|
|
|
{
|
|
|
/******** 读取数据包 av_read_frame ********/
|
|
|
- int retRead = av_read_frame(m_pFormatContext, packet);
|
|
|
+ int retRead = av_read_frame(m_pFormatContext, m_packet);
|
|
|
if(retRead == AVERROR_EOF)
|
|
|
{
|
|
|
/* 读取到末尾后,需要传入一个空的packet,才能读取到最后几帧 */
|
|
|
- avcodec_send_packet(m_pCodecCtx, packet);
|
|
|
+ avcodec_send_packet(m_pCodecContext, m_packet);
|
|
|
}
|
|
|
else if(retRead < 0)
|
|
|
{
|
|
@@ -888,91 +559,122 @@ void DecodeVedio::decodeUsingCPU()
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- if(packet->stream_index == m_videoStream)
|
|
|
+ if(m_packet->stream_index == m_videoStream)
|
|
|
{
|
|
|
- #if 0
|
|
|
+ #if 1
|
|
|
/* 计算当前帧时间,两种方法,第一种适用于所有场景,但是存在一定的误差 */
|
|
|
- packet->pts = qRound64(packet->pts * (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
|
|
|
- packet->dts = qRound64(packet->dts * (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
|
|
|
+ m_packet->pts = qRound64(m_packet->pts * (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
|
|
|
+ m_packet->dts = qRound64(m_packet->dts * (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
|
|
|
#else
|
|
|
/* 适用于本地视频,直接计算本地视频文件的每一帧时间 */
|
|
|
m_currentFrame++;
|
|
|
- packet->pts = qRound64(m_currentFrame * (qreal(m_duration) / m_totalFrame));
|
|
|
+ // m_packet->pts = qRound64(m_currentFrame * (qreal(m_duration) / m_totalFrame));
|
|
|
#endif
|
|
|
|
|
|
/* 将数据传给解码器 */
|
|
|
- int ret = avcodec_send_packet(m_pCodecCtx, packet);
|
|
|
+ int ret = avcodec_send_packet(m_pCodecContext, m_packet);
|
|
|
if(ret < 0)
|
|
|
{
|
|
|
SPDLOG_ERROR("发送数据包错误...");
|
|
|
+ av_packet_unref(m_packet);
|
|
|
continue;
|
|
|
}
|
|
|
+ }else {
|
|
|
+ SPDLOG_INFO("不是视频流。");
|
|
|
+ av_packet_unref(m_packet);
|
|
|
+ continue;
|
|
|
}
|
|
|
}
|
|
|
- av_packet_unref(packet); /* 释放数据包,引用计数-1,为0时释放空间 */
|
|
|
|
|
|
- /* 读取出解码器返回的帧 avcodec_receive_frame */
|
|
|
- int ret = avcodec_receive_frame(m_pCodecCtx, pFrameSRC);
|
|
|
- if(ret < 0)
|
|
|
+ SPDLOG_DEBUG("读取到数据包packet,pts:{}",m_packet->pts);
|
|
|
+ /* 解码packet包的内容 */
|
|
|
+ while(m_isRunning)
|
|
|
{
|
|
|
- av_frame_unref(pFrameSRC);
|
|
|
- if(retRead < 0)
|
|
|
+ /* 读取出解码器返回的帧 avcodec_receive_frame */
|
|
|
+ int ret = avcodec_receive_frame(m_pCodecContext, m_pFrameSRC);
|
|
|
+ if(ret == AVERROR_EOF)
|
|
|
{
|
|
|
- SPDLOG_INFO("读取到文件末尾...");
|
|
|
+ SPDLOG_INFO("解码完成...");
|
|
|
+ isEnd = true;
|
|
|
break;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- AVFrame* pFrameTemp = pFrameSRC;
|
|
|
- if(pFrameTemp->data[0] == nullptr)
|
|
|
- {
|
|
|
- /* 使用的是硬件解码器 */
|
|
|
- pFrameTemp = m_pFrameHW;
|
|
|
- /* 将数据从GPU拷贝到内存中 */
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
- m_pts = pFrameTemp->pts;
|
|
|
- /* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
|
|
|
- if(m_sws_ctx == nullptr)
|
|
|
- {
|
|
|
- m_sws_ctx = sws_getCachedContext(m_sws_ctx,
|
|
|
- pFrameTemp->width, pFrameTemp->height, /* 原图像大小和格式 */
|
|
|
- m_pCodecCtx->pix_fmt, /* 输入图像的像素格式 */
|
|
|
- m_width, m_height, /* 目标图像的大小 */
|
|
|
- AV_PIX_FMT_RGBA, /* 目标图像的格式 */
|
|
|
- SWS_BILINEAR, /* 图像缩放算法,双线性 */
|
|
|
- nullptr, /* 输入图像的滤波器信息,不需要传NULL */
|
|
|
- nullptr, /* 输出图像的滤波器信息,不需要传NULL */
|
|
|
- nullptr); /* 特定缩放算法需要的参数,不需要传NULL */
|
|
|
- if(m_sws_ctx == nullptr)
|
|
|
+ else if (ret == AVERROR(EAGAIN))
|
|
|
{
|
|
|
- SPDLOG_ERROR("创建SwsContext错误...");
|
|
|
- av_packet_free(&packet);
|
|
|
- av_frame_free(&pFrameSRC);
|
|
|
- av_frame_free(&pFrameRGB);
|
|
|
+ /* packet无法解码成一帧,需要更多的输入包 */
|
|
|
+ SPDLOG_WARN("packet无法解码成一帧,需要更多的输入包");
|
|
|
break;
|
|
|
}
|
|
|
+ else if(ret < 0)
|
|
|
+ {
|
|
|
+ av_frame_unref(m_pFrameSRC);
|
|
|
+ if(retRead < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("读取错误,错误值:{}",ret);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* 解码成功,获取到帧数 */
|
|
|
+ m_pts = m_pFrameSRC->pts;
|
|
|
+ /* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
|
|
|
+ if(m_sws_ctx == nullptr)
|
|
|
+ {
|
|
|
+ /* 选择在这里创建,为了兼容硬件解码,硬件解码出来的格式可能和解码器传出的不一样 */
|
|
|
+ m_sws_ctx = sws_getCachedContext(m_sws_ctx,
|
|
|
+ m_pFrameSRC->width, m_pFrameSRC->height, /* 原图像大小和格式 */
|
|
|
+ m_pCodecContext->pix_fmt, /* 输入图像的像素格式 */
|
|
|
+ m_width, m_height, /* 目标图像的大小 */
|
|
|
+ AV_PIX_FMT_RGBA, /* 目标图像的格式 */
|
|
|
+ SWS_BILINEAR, /* 图像缩放算法,双线性 */
|
|
|
+ nullptr, /* 输入图像的滤波器信息,不需要传NULL */
|
|
|
+ nullptr, /* 输出图像的滤波器信息,不需要传NULL */
|
|
|
+ nullptr); /* 特定缩放算法需要的参数,不需要传NULL */
|
|
|
+ if(m_sws_ctx == nullptr)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("创建SwsContext错误...");
|
|
|
+ av_packet_free(&m_packet);
|
|
|
+ av_frame_free(&m_pFrameSRC);
|
|
|
+ av_frame_free(&m_pFrameRGB);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SPDLOG_INFO("创建SwsContext成功...");
|
|
|
+ }
|
|
|
+ /* 转换成RGBA格式 */
|
|
|
+ sws_scale( m_sws_ctx, /* 缩放的上下文 */
|
|
|
+ m_pFrameSRC->data, /* 源图像数组 */
|
|
|
+ m_pFrameSRC->linesize, /* 包含源图像每个平面步幅的数组 */
|
|
|
+ 0, /* 开始位置 */
|
|
|
+ m_pFrameSRC->height, /* 行数 */
|
|
|
+ m_pFrameRGB->data, /* 目标图像数组 */
|
|
|
+ m_pFrameRGB->linesize); /* 目标图像行数 */
|
|
|
+ if(m_pFrameRGB->data[0] != nullptr)
|
|
|
+ {
|
|
|
+ /* 将数据拷贝到QImage中 */
|
|
|
+ auto image = new QImage(m_pFrameRGB->data[0], m_width, m_height, QImage::Format_RGBA8888);
|
|
|
+ m_ringQueue.enQueueBlock(image);
|
|
|
+ av_frame_unref(m_pFrameRGB);
|
|
|
+ }
|
|
|
+ av_frame_unref(m_pFrameSRC);
|
|
|
+ }
|
|
|
+ av_packet_unref(m_packet); /* 释放数据包,引用计数-1,为0时释放空间 */
|
|
|
+ if(isEnd)
|
|
|
+ {
|
|
|
+ break;
|
|
|
}
|
|
|
- /* 转换成RGBA格式 */
|
|
|
- sws_scale( m_sws_ctx, /* 缩放的上下文 */
|
|
|
- pFrameTemp->data, /* 源图像数组 */
|
|
|
- pFrameTemp->linesize, /* 包含源图像每个平面步幅的数组 */
|
|
|
- 0, /* 开始位置 */
|
|
|
- pFrameTemp->height, /* 函数 */
|
|
|
- pFrameRGB->data, /* 目标图像数组 */
|
|
|
- pFrameRGB->linesize); /* 目标图像行数 */
|
|
|
- auto image = new QImage(m_pFrameRGB->data[0], m_width, m_height, QImage::Format_RGBA8888);
|
|
|
- m_ringQueue.enQueueBlock(image);
|
|
|
-
|
|
|
|
|
|
- av_frame_unref(pFrameRGB);
|
|
|
- av_frame_unref(pFrameSRC);
|
|
|
+ // AVFrame* pFrameTemp = m_pFrameSRC;
|
|
|
+ // if(m_pFrameSRC->data[0] == nullptr)
|
|
|
+ // {
|
|
|
+ // /* 使用的是硬件解码器 */
|
|
|
+ // pFrameTemp = m_pFrameHW;
|
|
|
+ // /* 将数据从GPU拷贝到内存中 */
|
|
|
+
|
|
|
+ // break;
|
|
|
+ // }
|
|
|
}
|
|
|
/* 释放空间 */
|
|
|
- av_packet_free(&packet);
|
|
|
- av_free(pFrameSRC);
|
|
|
- av_free(pFrameRGB);
|
|
|
+ av_packet_free(&m_packet);
|
|
|
+ av_free(m_pFrameSRC);
|
|
|
+ av_free(m_pFrameRGB);
|
|
|
}
|
|
|
|
|
|
/* 退出线程,将所有可能暂停线程运行的条件全部唤醒 */
|
|
@@ -1021,7 +723,6 @@ void DecodeVedio::do_startDecodeVedio()
|
|
|
m_threadStopped = false;
|
|
|
m_pauseDecode = false;
|
|
|
/* 进入解码,直到播放完成或者手动退出 */
|
|
|
- // decodeVedio();
|
|
|
decodeUsingCPU();
|
|
|
SPDLOG_TRACE("Decode播放完成...");
|
|
|
|
|
@@ -1039,17 +740,21 @@ void DecodeVedio::freeAll()
|
|
|
{
|
|
|
av_frame_free(&m_pFrameRGB);
|
|
|
}
|
|
|
- if(m_pFrame)
|
|
|
+ if(m_pFrameSRC)
|
|
|
+ {
|
|
|
+ av_frame_free(&m_pFrameSRC);
|
|
|
+ }
|
|
|
+ if(m_pFrameHW)
|
|
|
{
|
|
|
- av_frame_free(&m_pFrame);
|
|
|
+ av_frame_free(&m_pFrameHW);
|
|
|
}
|
|
|
if(m_packet)
|
|
|
{
|
|
|
av_packet_free(&m_packet);
|
|
|
}
|
|
|
- if(m_pCodecCtx)
|
|
|
+ if(m_pCodecContext)
|
|
|
{
|
|
|
- avcodec_free_context(&m_pCodecCtx);
|
|
|
+ avcodec_free_context(&m_pCodecContext);
|
|
|
}
|
|
|
if(m_pFormatContext)
|
|
|
{
|