|
@@ -68,7 +68,7 @@ void DecodeVedio::getHWDecoder()
|
|
|
/* 开始解码视频 */
|
|
|
void DecodeVedio::startDecodeVedio()
|
|
|
{
|
|
|
- if(m_isRunning)
|
|
|
+ if(m_threadRuning)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
@@ -77,7 +77,7 @@ void DecodeVedio::startDecodeVedio()
|
|
|
SPDLOG_WARN("未初始化FFMPEG...");
|
|
|
return;
|
|
|
}
|
|
|
- m_isRunning = true;
|
|
|
+ m_threadRuning = true;
|
|
|
// decodeUsingCPU();
|
|
|
/* 发送信号,开启新线程 */
|
|
|
emit signal_startDecode();
|
|
@@ -86,7 +86,7 @@ void DecodeVedio::startDecodeVedio()
|
|
|
/* 停止解码视频,退出工作函数,线程未停止 */
|
|
|
void DecodeVedio::stopDecodeVedio()
|
|
|
{
|
|
|
- if(!m_isRunning)
|
|
|
+ if(!m_threadRuning)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
@@ -100,32 +100,65 @@ void DecodeVedio::stopDecodeVedio()
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
|
}
|
|
|
freeAll();
|
|
|
- m_isRunning = false;
|
|
|
+ m_threadRuning = false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @brief 设置当前播放位置,单位是微秒
|
|
|
+ * @brief 设置当前播放位置,单位是毫秒
|
|
|
* 这里需要去掉队列中已有的图片数目对应的时长
|
|
|
*
|
|
|
- * @param pos
|
|
|
+ * @param pos 要跳转的位置,范围从0~duration
|
|
|
*/
|
|
|
-void DecodeVedio::setCurrentPos(quint64 pos)
|
|
|
+void DecodeVedio::setCurrentPos(qint64 pos)
|
|
|
{
|
|
|
-
|
|
|
+ m_isSeek = true;
|
|
|
+ /* 先暂停解码 */
|
|
|
+ pauseDecode();
|
|
|
+ SPDLOG_DEBUG("跳转到:{}ms",pos);
|
|
|
+ /*在环形队列中有已解码的视频帧数,需要去掉已有的帧数所占的时间 */
|
|
|
+ pos = pos - m_queueImage.QueueSize() * (1000 / m_fps);
|
|
|
+ if(pos < 0) {
|
|
|
+ pos = 0;
|
|
|
+ }
|
|
|
+ if(pos > m_duration) {
|
|
|
+ pos = m_duration;
|
|
|
+ }
|
|
|
+ pos = pos + m_startPos;
|
|
|
+ qint64 targetPos = qRound64((double)pos / (1000 * rationalToDouble(&m_pFormatContext->streams[m_videoStream]->time_base)));
|
|
|
+ /* 开始跳转,这里设置标志为AVSEEK_FLAG_BACKWARD,跳转到目标位置的前一个关键帧中,然后开始解码,直到到达目标位置为止 */
|
|
|
+ int ret = av_seek_frame(m_pFormatContext, m_videoStream, targetPos, AVSEEK_FLAG_BACKWARD);
|
|
|
+ if(ret < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("跳转失败!");
|
|
|
+ }
|
|
|
+ m_targetPos = pos;
|
|
|
+ /* 刷新解码器缓冲区 */
|
|
|
+ m_flushDecoder.store(true);
|
|
|
+ /* 清空环形队列中的视频 */
|
|
|
+ QImage* image = 0;
|
|
|
+ for(int i = 0; i < m_queueImage.QueueSize(); i++)
|
|
|
+ {
|
|
|
+ image = nullptr;
|
|
|
+ m_queueImage.front_pop_NoBlock(image);
|
|
|
+ if(image != nullptr)
|
|
|
+ {
|
|
|
+ delete image;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* 继续解码 */
|
|
|
+ continueDecode();
|
|
|
}
|
|
|
|
|
|
-/* 获取当前播放位置 */
|
|
|
+/* 获取当前播放位置,单位ms */
|
|
|
qint64 DecodeVedio::getCurrentPos()
|
|
|
{
|
|
|
-
|
|
|
- return m_currentPos - m_startPos;
|
|
|
+ return m_pts.load() - m_startPos;
|
|
|
}
|
|
|
|
|
|
/* 获取视频时长 */
|
|
|
qint64 DecodeVedio::getDuration()
|
|
|
{
|
|
|
return m_duration;
|
|
|
-
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -136,7 +169,7 @@ qint64 DecodeVedio::getDuration()
|
|
|
QImage* DecodeVedio::getOneImage()
|
|
|
{
|
|
|
QImage* image = nullptr;
|
|
|
- if(!m_ringQueue.front_pop_NoBlock(image))
|
|
|
+ if(!m_queueImage.front_pop_NoBlock(image))
|
|
|
{
|
|
|
return nullptr;
|
|
|
}
|
|
@@ -146,39 +179,11 @@ QImage* DecodeVedio::getOneImage()
|
|
|
/* 获取一帧图像,直到有图像为止 */
|
|
|
QImage* DecodeVedio::getOneImageUntilHave()
|
|
|
{
|
|
|
- QImage* image = m_ringQueue.front_pop();
|
|
|
+ QImage* image = m_queueImage.front_pop();
|
|
|
|
|
|
return image;
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * @brief 设置图像宽度和高度
|
|
|
- * 注意:目前不好用,建议使用Qt的图片缩放
|
|
|
- *
|
|
|
- * @param width
|
|
|
- * @param 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)
|
|
|
{
|
|
@@ -201,19 +206,19 @@ void DecodeVedio::openVedio(const QString& fileName)
|
|
|
}
|
|
|
|
|
|
/* 清空队列 */
|
|
|
- if(m_ringQueue.QueueSize() > 0)
|
|
|
+ if(m_queueImage.QueueSize() > 0)
|
|
|
{
|
|
|
- for(int i = 0; i < m_ringQueue.QueueSize(); i++)
|
|
|
+ for(int i = 0; i < m_queueImage.QueueSize(); i++)
|
|
|
{
|
|
|
QImage* image = nullptr;
|
|
|
- if (m_ringQueue.front_pop_NoBlock(image))
|
|
|
+ if (m_queueImage.front_pop_NoBlock(image))
|
|
|
{
|
|
|
delete image;
|
|
|
}
|
|
|
}
|
|
|
- m_ringQueue.clearQueue();
|
|
|
+ m_queueImage.clearQueue();
|
|
|
}
|
|
|
- m_ringQueue.setQueueSize(30);
|
|
|
+ m_queueImage.setQueueCapacity(30);
|
|
|
|
|
|
SPDLOG_DEBUG("开始初始化FFMPEG");
|
|
|
AVDictionary* dict = nullptr;
|
|
@@ -251,8 +256,7 @@ void DecodeVedio::openVedio(const QString& fileName)
|
|
|
return;
|
|
|
}
|
|
|
m_duration = m_pFormatContext->duration / (AV_TIME_BASE / 1000); /* 获取视频时长,单位是毫秒 */
|
|
|
- m_currentPos = 0;
|
|
|
- m_startPos = m_pFormatContext->start_time;
|
|
|
+ m_startPos = m_pFormatContext->start_time / (AV_TIME_BASE / 1000); /* 获取视频开始时间,单位是毫秒 */
|
|
|
// SPDLOG_DEBUG("开始时间:{} 时长:{}",m_startPos, m_duration);
|
|
|
|
|
|
/* 一个调试函数,将流信息输出到控制台 */
|
|
@@ -276,7 +280,7 @@ void DecodeVedio::openVedio(const QString& fileName)
|
|
|
m_srcSize.setHeight(pCodecParams->height);
|
|
|
m_fps = rationalToDouble(&pStream->avg_frame_rate);
|
|
|
m_totalFrame = m_pFormatContext->streams[m_videoStream]->nb_frames;
|
|
|
-
|
|
|
+ m_pts.store(0);
|
|
|
|
|
|
/************ 查找并设置解码器 ************/
|
|
|
/* 找到解码器 */
|
|
@@ -375,14 +379,23 @@ void DecodeVedio::threadDecodeUsingCPU()
|
|
|
m_pauseDecode = false;
|
|
|
m_decodeStatus = true;
|
|
|
m_threadState = true;
|
|
|
-
|
|
|
- /* 设置环形队列的大小 */
|
|
|
- m_ringQueue.setQueueSize(30);
|
|
|
|
|
|
/* 开始解码 */
|
|
|
SPDLOG_DEBUG("开始解码...");
|
|
|
- while(m_isRunning)
|
|
|
+ while(m_threadRuning)
|
|
|
{
|
|
|
+ /******** 判断是否在暂停状态 ********/
|
|
|
+ if(m_pauseDecode)
|
|
|
+ {
|
|
|
+ m_decodeState.store(DecodeState::DecodePause);
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
|
+ }
|
|
|
+ /* 刷新解码器缓冲区,清除掉里面残留的解码文件 */
|
|
|
+ if(m_flushDecoder.load())
|
|
|
+ {
|
|
|
+ avcodec_flush_buffers(m_pCodecContext);
|
|
|
+ m_flushDecoder.store(false);
|
|
|
+ }
|
|
|
/******** 读取数据包 av_read_frame ********/
|
|
|
int retRead = av_read_frame(m_pFormatContext, m_packet);
|
|
|
if(retRead == AVERROR_EOF)
|
|
@@ -399,6 +412,7 @@ void DecodeVedio::threadDecodeUsingCPU()
|
|
|
{
|
|
|
if(m_packet->stream_index == m_videoStream)
|
|
|
{
|
|
|
+ // SPDLOG_DEBUG("源pts:{}", m_packet->pts);
|
|
|
/* pts 表示显示时间戳(Presentation Timestamp),它指示解码后的帧应该在什么时候显示。pts 是用于同步音视频的关键时间戳。
|
|
|
dts 表示解码时间戳(Decoding Timestamp),它指示解码器应该在什么时候解码这个数据包。dts 用于确保解码器按照正确的顺序解码数据包。 */
|
|
|
#if 1
|
|
@@ -410,7 +424,7 @@ void DecodeVedio::threadDecodeUsingCPU()
|
|
|
m_currentFrame++;
|
|
|
// m_packet->pts = qRound64(m_currentFrame * (qreal(m_duration) / m_totalFrame));
|
|
|
#endif
|
|
|
-
|
|
|
+
|
|
|
/* 将数据传给解码器 */
|
|
|
int ret = avcodec_send_packet(m_pCodecContext, m_packet);
|
|
|
if(ret < 0)
|
|
@@ -426,9 +440,9 @@ void DecodeVedio::threadDecodeUsingCPU()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- SPDLOG_DEBUG("读取到数据包packet,pts:{}",m_packet->pts);
|
|
|
+ // SPDLOG_DEBUG("读取到数据包packet,pts:{}",m_packet->pts);
|
|
|
/* 解码packet包的内容,一个packet包内可能包含好多帧视频 */
|
|
|
- while(m_isRunning)
|
|
|
+ while(m_threadRuning)
|
|
|
{
|
|
|
/* 读取出解码器返回的帧 avcodec_receive_frame */
|
|
|
int ret = avcodec_receive_frame(m_pCodecContext, m_pFrameSRC);
|
|
@@ -455,8 +469,24 @@ void DecodeVedio::threadDecodeUsingCPU()
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
- /* 解码成功,获取到帧数 */
|
|
|
+ /* 解码成功,获取当前时间,现在已经是准的时间了
|
|
|
+ * 如果在跳转状态,在这里判断是否到了目标位置 */
|
|
|
m_pts = m_pFrameSRC->pts;
|
|
|
+ if(m_isSeek)
|
|
|
+ {
|
|
|
+ if(m_pts < m_targetPos)
|
|
|
+ {
|
|
|
+ SPDLOG_DEBUG("目标位置:{} 当前位置:{}",m_targetPos, m_pts.load());
|
|
|
+ av_frame_unref(m_pFrameSRC);
|
|
|
+ continue;
|
|
|
+ }else {
|
|
|
+ m_isSeek = false;
|
|
|
+ m_targetPos = -1;
|
|
|
+ SPDLOG_INFO("跳转结束。");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // SPDLOG_DEBUG("当前帧的pts:{}", m_pts.load());
|
|
|
+
|
|
|
/* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
|
|
|
if(m_sws_ctx == nullptr)
|
|
|
{
|
|
@@ -494,11 +524,17 @@ void DecodeVedio::threadDecodeUsingCPU()
|
|
|
{
|
|
|
/* 将数据拷贝到QImage中 */
|
|
|
auto image = new QImage(m_buffer, m_srcSize.width(), m_srcSize.height(), QImage::Format_RGBA8888);
|
|
|
- m_ringQueue.push(image);
|
|
|
+ /* 如果队列满,线程会阻塞在这里 */
|
|
|
+ m_queueImage.push(image);
|
|
|
av_frame_unref(m_pFrameRGB);
|
|
|
// SPDLOG_DEBUG("一帧视频入队");
|
|
|
}
|
|
|
av_frame_unref(m_pFrameSRC);
|
|
|
+ /* 如果在跳转过程中,直接退出,防止一个packet中有多个视频帧,再次阻塞在上面 */
|
|
|
+ if(m_isSeek)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
av_packet_unref(m_packet); /* 释放数据包,引用计数-1,为0时释放空间 */
|
|
|
if(isEnd)
|
|
@@ -516,31 +552,56 @@ label_ThreadDecodeExit:
|
|
|
/* 退出线程,将所有可能暂停线程运行的条件全部唤醒 */
|
|
|
void DecodeVedio::exitThread()
|
|
|
{
|
|
|
- if(m_isRunning)
|
|
|
+ if(m_threadRuning)
|
|
|
{
|
|
|
- m_isRunning = false;
|
|
|
+ m_threadRuning = false;
|
|
|
}
|
|
|
m_pauseDecode = false;
|
|
|
/* 先退出可能阻塞住的解码线程 */
|
|
|
- m_ringQueue.exit();
|
|
|
+ m_queueImage.exit();
|
|
|
}
|
|
|
|
|
|
/* 暂停解码,会阻塞到线程暂停为止 */
|
|
|
void DecodeVedio::pauseDecode()
|
|
|
{
|
|
|
m_pauseDecode = true;
|
|
|
+ /* 队列出队两张图片,防止解码线程阻塞到环形队列满上面 */
|
|
|
+ QImage* image = nullptr;
|
|
|
+ m_queueImage.front_pop_NoBlock(image);
|
|
|
+ if(image != nullptr)
|
|
|
+ {
|
|
|
+ delete image;
|
|
|
+ image = nullptr;
|
|
|
+ }
|
|
|
+ m_queueImage.front_pop_NoBlock(image);
|
|
|
+ if(image != nullptr)
|
|
|
+ {
|
|
|
+ delete image;
|
|
|
+ image = nullptr;
|
|
|
+ }
|
|
|
+ /* 等待线程状态变为暂停为止 */
|
|
|
+ while (m_decodeState.load() != DecodeState::DecodePause)
|
|
|
+ {
|
|
|
+ std::this_thread::sleep_for(std::chrono::microseconds(100));
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
+/* 继续解码 */
|
|
|
+void DecodeVedio::continueDecode()
|
|
|
+{
|
|
|
+ m_pauseDecode = false;
|
|
|
+ m_decodeState.store(DecodeState::DecodeRun);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @brief AVRational存储的是分子和分母,这里相除转换为double,用于计算帧率
|
|
|
+ * @brief AVRational存储的是分子和分母,这里相除转换为double,用于计算帧率
|
|
|
+ * 这个函数就等同于av_q2d()
|
|
|
* @param rational
|
|
|
* @return
|
|
|
*/
|
|
|
qreal DecodeVedio::rationalToDouble(AVRational* rational)
|
|
|
{
|
|
|
- qreal frameRate = (rational->den == 0) ? 0 : (qreal(rational->num) / rational->den);
|
|
|
- return frameRate;
|
|
|
+ return ( (rational->den == 0) ? 0 : (qreal(rational->num) / rational->den) );
|
|
|
}
|
|
|
|
|
|
|
|
@@ -552,7 +613,7 @@ void DecodeVedio::do_startDecodeVedio()
|
|
|
// {
|
|
|
// initFFmpeg();
|
|
|
// }
|
|
|
- m_isRunning = true;
|
|
|
+ m_threadRuning = true;
|
|
|
m_pauseDecode = false;
|
|
|
/* 进入解码,直到播放完成或者手动退出 */
|
|
|
threadDecodeUsingCPU();
|
|
@@ -602,14 +663,14 @@ void DecodeVedio::freeAll()
|
|
|
av_buffer_unref(&m_hw_device_ctx);
|
|
|
}
|
|
|
|
|
|
- for(int i = 0; i < m_ringQueue.QueueSize(); i++)
|
|
|
+ for(int i = 0; i < m_queueImage.QueueSize(); i++)
|
|
|
{
|
|
|
QImage* image = nullptr;
|
|
|
- if (m_ringQueue.front_pop_NoBlock(image))
|
|
|
+ if (m_queueImage.front_pop_NoBlock(image))
|
|
|
{
|
|
|
delete image;
|
|
|
}
|
|
|
}
|
|
|
- m_ringQueue.clearQueue();
|
|
|
+ m_queueImage.clearQueue();
|
|
|
}
|
|
|
|