|
@@ -76,18 +76,17 @@ void DecodeVedio::stopDecodeVedio()
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
m_isRunning = false;
|
|
|
- m_fileName = QString();
|
|
|
+ m_pauseDecode = false;
|
|
|
+ /* 唤醒阻塞住的解码线程 */
|
|
|
+ m_condQueueNoFull.wakeAll();
|
|
|
// /* 等待线程执行结束 */
|
|
|
- // while(true)
|
|
|
- // {
|
|
|
- // if(m_threadStopped)
|
|
|
- // {
|
|
|
- // break;
|
|
|
- // }
|
|
|
- // /* 睡眠10ms */
|
|
|
- // std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
- // }
|
|
|
+ while(!m_threadStopped)
|
|
|
+ {
|
|
|
+ /* 睡眠10ms */
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
@@ -102,12 +101,22 @@ void DecodeVedio::setCurrentPos(quint64 pos)
|
|
|
|
|
|
/* 暂停解码 */
|
|
|
m_pauseDecode = true;
|
|
|
- /* 去掉队列中已有的图片数目对应的时长 */
|
|
|
- int queueNumFPS = m_queueImage.count() * 1000.0 / m_fps;
|
|
|
+ // SPDLOG_DEBUG("setCurrentPos threadID:{}",QThread::currentThreadId());
|
|
|
+ while (m_decodeStatus) {
|
|
|
+ /* 需要唤醒阻塞住的线程 */
|
|
|
+ m_condQueueNoFull.wakeAll();
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
+ }
|
|
|
+ m_isSeek = true;
|
|
|
+ /* 去掉队列中已有的图片数目对应的时长,单位是us */
|
|
|
+ int queueNumFPS = m_queueImage.count() * ( 1000.0f / m_fps ) * 1000 ;
|
|
|
qint64 target_pos = m_startPos + pos - queueNumFPS;
|
|
|
+ m_currentPos = target_pos;
|
|
|
SPDLOG_DEBUG("设置播放位置:{}",target_pos);
|
|
|
/* 第二个参数设置为-1,表示所有流都跳转 */
|
|
|
int ret = av_seek_frame(m_pFormatContext, -1, target_pos, AVSEEK_FLAG_BACKWARD);
|
|
|
+ /* 清空缓存 */
|
|
|
+ avcodec_flush_buffers(m_pCodecCtx);
|
|
|
if(ret < 0)
|
|
|
{
|
|
|
SPDLOG_WARN("跳转失败...");
|
|
@@ -253,14 +262,24 @@ int DecodeVedio::getFrameCount()
|
|
|
|
|
|
return m_fps;
|
|
|
}
|
|
|
-
|
|
|
+/**
|
|
|
+ * @brief 设置图像宽度和高度
|
|
|
+ * 注意:目前不好用,建议使用Qt的图片缩放
|
|
|
+ *
|
|
|
+ * @param width
|
|
|
+ * @param height
|
|
|
+ */
|
|
|
void DecodeVedio::setVideoSize(int width, int height)
|
|
|
{
|
|
|
m_width = width;
|
|
|
m_height = height;
|
|
|
|
|
|
- /* 重新初始化缩放参数 */
|
|
|
- // sws_freeContext(m_sws_ctx);
|
|
|
+ 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, /* 目标图像的大小和格式 */
|
|
@@ -273,29 +292,11 @@ void DecodeVedio::setVideoSize(int width, int height)
|
|
|
/* 初始化函数 */
|
|
|
void DecodeVedio::initFFmpeg(const QString& fileName)
|
|
|
{
|
|
|
- if(fileName != m_fileName)
|
|
|
- {
|
|
|
- m_fileName = fileName;
|
|
|
- if(m_initFFmpeg)
|
|
|
- {
|
|
|
- freeFFmpeg();
|
|
|
- }
|
|
|
- }else
|
|
|
+ if(m_initFFmpeg)
|
|
|
{
|
|
|
- /* 清空队列 */
|
|
|
- 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;
|
|
|
}
|
|
|
+ m_fileName = fileName;
|
|
|
/* 清空队列 */
|
|
|
int size = m_queueImage.count();
|
|
|
for(int i = 0; i < size; i++)
|
|
@@ -305,7 +306,6 @@ void DecodeVedio::initFFmpeg(const QString& fileName)
|
|
|
{
|
|
|
delete image;
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
m_queueImage.clear();
|
|
|
|
|
@@ -389,7 +389,8 @@ void DecodeVedio::initFFmpeg(const QString& fileName)
|
|
|
|
|
|
/******** 读取数据(解码数据) ********/
|
|
|
m_packet = av_packet_alloc();
|
|
|
- av_init_packet(m_packet);
|
|
|
+ // av_init_packet(m_packet);
|
|
|
+ av_new_packet(m_packet, m_pCodecCtx->width * m_pCodecCtx->height);
|
|
|
|
|
|
/********* 创建两个pFrame,一个存放原始数据,一个存放转换后的RGB数据 **********/
|
|
|
m_pFrame = av_frame_alloc();
|
|
@@ -410,8 +411,8 @@ void DecodeVedio::initFFmpeg(const QString& fileName)
|
|
|
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_width = m_srcWidth = m_pCodecCtx->width;
|
|
|
+ m_height = m_srcHeight = m_pCodecCtx->height;
|
|
|
m_frameCount = m_pFormatContext->streams[m_videoStream]->nb_frames;
|
|
|
|
|
|
/* 这个函数的实际作用是将buffer设置给pFrameRGB作为原始数据的内存区域 */
|
|
@@ -421,7 +422,7 @@ void DecodeVedio::initFFmpeg(const QString& fileName)
|
|
|
|
|
|
/* 初始化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, /* 目标图像的大小和格式 */
|
|
|
+ m_srcWidth, m_srcHeight, AV_PIX_FMT_RGB24, /* 目标图像的大小和格式 */
|
|
|
SWS_BILINEAR, /* 双线性 */
|
|
|
nullptr,
|
|
|
nullptr,
|
|
@@ -433,6 +434,15 @@ void DecodeVedio::initFFmpeg(const QString& fileName)
|
|
|
// avcodec_parameters_free(&pCodecCtxOrig);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+/* 取消ffmpeg初始化函数 */
|
|
|
+void DecodeVedio::unInitFFmpeg()
|
|
|
+{
|
|
|
+ stopDecodeVedio();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
/* 取消ffmpeg初始化函数 */
|
|
|
void DecodeVedio::freeFFmpeg()
|
|
|
{
|
|
@@ -453,7 +463,7 @@ void DecodeVedio::freeFFmpeg()
|
|
|
|
|
|
QString m_fileName = QString();
|
|
|
int m_videoStream = -1; /* 记录视频流是第几个流 */
|
|
|
- bool m_initFFmpeg = false;
|
|
|
+ m_initFFmpeg = false;
|
|
|
|
|
|
}
|
|
|
|
|
@@ -466,35 +476,62 @@ 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));
|
|
|
}
|
|
|
- std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
|
|
|
+ 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();
|
|
|
+ // 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;
|
|
|
+ emit signal_playCompleted();
|
|
|
+ /* 暂停解码 */
|
|
|
+ m_pauseDecode = true;
|
|
|
+ while(m_pauseDecode)
|
|
|
+ {
|
|
|
+ m_decodeStatus = false;
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
|
+ }
|
|
|
}
|
|
|
- if(retPacket < 0)
|
|
|
+ else 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();
|
|
|
+ /* 现在的位置,转换成时间,乘上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();
|
|
|
+ // std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
|
|
|
if(ret < 0)
|
|
|
{
|
|
|
SPDLOG_ERROR("发送数据包错误...");
|
|
@@ -507,30 +544,19 @@ void DecodeVedio::decodeVedio()
|
|
|
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();
|
|
|
+ // 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();
|
|
|
+ // 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);
|
|
|
+ // SPDLOG_DEBUG("队列中图片个数:{} ",m_queueImage.count());
|
|
|
m_mutexQueue.lock();
|
|
|
if(m_queueImage.count() >= m_queueMaxNum)
|
|
|
{
|
|
@@ -543,20 +569,30 @@ void DecodeVedio::decodeVedio()
|
|
|
m_condQueueNoEmpty.wakeAll();
|
|
|
/* 同时发送信号 */
|
|
|
emit signal_oneImage();
|
|
|
- }
|
|
|
- else if (retFrame == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
|
|
+
|
|
|
+ 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 if (retFrame < 0)
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- SPDLOG_TRACE("解码错误...");
|
|
|
- av_packet_unref(m_packet);
|
|
|
- break;
|
|
|
+ SPDLOG_TRACE("其他错误...{}",retFrame);
|
|
|
}
|
|
|
av_frame_unref(m_pFrame);
|
|
|
- retFrame = -1;
|
|
|
}
|
|
|
|
|
|
}else
|
|
@@ -596,6 +632,17 @@ void DecodeVedio::exitThread()
|
|
|
m_condQueueNoFull.wakeAll();
|
|
|
}
|
|
|
|
|
|
+/* 暂停解码,会阻塞到线程暂停为止 */
|
|
|
+void DecodeVedio::pauseDecode()
|
|
|
+{
|
|
|
+ m_pauseDecode = true;
|
|
|
+ while (m_decodeStatus) {
|
|
|
+ /* 需要唤醒阻塞住的线程 */
|
|
|
+ m_condQueueNoFull.wakeAll();
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
/* 开启解码 */
|
|
|
void DecodeVedio::do_startDecodeVedio()
|