#include "DecodeVedio.h" #include "spdlog/spdlog.h" #include #include extern "C" { #include #include #include #include } 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_pauseDecode = false; /* 唤醒阻塞住的解码线程 */ m_condQueueNoFull.wakeAll(); // /* 等待线程执行结束 */ while(!m_threadStopped) { /* 睡眠10ms */ std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } /** * @brief 设置当前播放位置,单位是微秒 * 这里需要去掉队列中已有的图片数目对应的时长 * * @param pos */ void DecodeVedio::setCurrentPos(quint64 pos) { /* 暂停解码 */ m_pauseDecode = true; // 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("跳转失败..."); } /* 清空队列 */ 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(); m_condQueueNoFull.wakeAll(); SPDLOG_INFO("跳转完成 , queue count {}", m_queueImage.count()); /* 继续解码 */ m_pauseDecode = false; // SPDLOG_DEBUG("继续解码... {}", m_pauseDecode.load()); } /* 获取当前播放位置 */ 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; } } /* 获取视频格式是hevc还是h.264 */ enum AVCodecID codecID = pFormatCtx->streams[videoStreamIndex]->codecpar->codec_id; if (codecID == AV_CODEC_ID_HEVC) { SPDLOG_DEBUG("视频格式是HEVC"); m_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; m_fps = frameRate.num*1.0 / frameRate.den; // SPDLOG_DEBUG("分子:{} 分母:{} 帧率:{}",frameRate.num, frameRate.den, fps); }else { m_fps = 30; } avformat_close_input(&pFormatCtx); return m_fps; } /** * @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_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); /************ 找到视频流 ************/ 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); 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_RGB24, 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_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_srcWidth, m_srcHeight, AV_PIX_FMT_RGB24, /* 目标图像的大小和格式 */ 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(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(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(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(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) { // 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; } /* 退出线程,将所有可能暂停线程运行的条件全部唤醒 */ void DecodeVedio::exitThread() { if(m_isRunning) { m_isRunning = false; } m_pauseDecode = false; 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() { SPDLOG_DEBUG("线程ID:{}",QThread::currentThreadId()); // if(!m_initFFmpeg) // { // initFFmpeg(); // } m_isRunning = true; m_threadStopped = false; m_pauseDecode = false; /* 进入解码,直到播放完成或者手动退出 */ decodeVedio(); SPDLOG_TRACE("播放完成..."); }