#include "cffmpeg_decode.h" // Cffmpeg_decode::Cffmpeg_decode() { Cffmpeg_decode::Cffmpeg_decode(QObject *parent) : QObject(parent) { initFFmpegContainer(); avformat_network_init(); m_rtsp_transport = "tcp"; } Cffmpeg_decode::~Cffmpeg_decode() { if (!inputPacket) av_packet_free(&inputPacket); if (!yuvFrame) av_frame_free(&yuvFrame); if (!rgbFrame) av_frame_free(&rgbFrame); if (!encoderCtx) avcodec_free_context(&encoderCtx); if (!encoderCtx) avcodec_close(encoderCtx); if (!decoderCtx) avcodec_free_context(&decoderCtx); if (!decoderCtx) avcodec_close(decoderCtx); if (!inputFormatCtx) avformat_close_input(&inputFormatCtx); } void Cffmpeg_decode::setStreamUrl(QString url) { _url = url; } void Cffmpeg_decode::setPlayVideo(bool bPlay) { mutex.lock(); bPlayVideoFlag = bPlay; if (bPlayVideoFlag) { // 初始化 previous_pts_time 为无效值(拉流使用) previous_pts_time = -1.0; // 表示没有上一帧的时间戳 first_frame_pts_time = 0.0; // 第一帧的 PTS 时间 first_frame_system_time = 0.0; // 第一帧解码时的系统时间 } mutex.unlock(); } /** * @brief 设置推流 * @param bPushStream: 开启/关闭推流 */ void Cffmpeg_decode::setPushStream(bool bPushStream) { mutex.lock(); bPushStreamFlag = bPushStream; if (bPushStreamFlag) { // 推流初始化 // emit sendInitPushStream_Signal(inputFormatCtx); } else { // 停止推流 emit sendStopPushStream_Signal(); bOpenPushStreamFlag = false; firstDts = AV_NOPTS_VALUE; // 初始化第一帧的DTS } mutex.unlock(); } void Cffmpeg_decode::initFFmpegContainer() { inputFormatCtx = avformat_alloc_context(); inputPacket = av_packet_alloc(); yuvFrame = av_frame_alloc(); rgbFrame = av_frame_alloc(); } bool Cffmpeg_decode::open_input_file() { if (_url.isEmpty()) return 0; if (!m_bSetRtspTransport) { m_rtsp_transport = _url.left(4) == "rtmp" ? "tcp" : "udp"; } //========================解码============================ AVDictionary *avdic = NULL; // 如果设置失败,则设置UDP传输 // if (m_rtsp_transport == "udp") // av_dict_set(&avdic, "rtsp_transport", "udp", 0); // else // av_dict_set(&avdic, "rtsp_transport", "tcp", // 0);//注:设置tcp会导致吊舱拉流中断 // av_dict_set(&avdic, "rtsp_transport", "udp", 0); // 设置缓存大小,1080p可将值调大 av_dict_set(&avdic, "buffer_size", "4096000", 0); // 设置超时断开连接时间,单位微秒//listen_timeout // av_dict_set(&avdic, "listen_timeout", "200000", 0); // 在尝试连接到一个流或网络资源时,等待的最长时间。如果在这个时间内无法建立连接,就会返回超时错误 av_dict_set(&avdic, "stimeout", "5000000", 0); // 设置超时5秒 av_dict_set(&avdic, "max_delay", "300000", 0); // 设置最大时延300ms av_dict_set(&avdic, "tune", "zerolatency", 0); // 实时编码 av_dict_set(&avdic, "preset", "ultrafast", 0); // faster // av_dict_set(&avdic, "threads", "auto", 0); // 自动开启线程数 // 设置最大重试时间为1s,解决avformat_open_input打开空流时间过长问题 av_dict_set(&avdic, "max_interleave_delta", "1000000", 0); // av_dict_set(&avdic, "reconnect", "1", 0); // 开启自动重连 // av_dict_set(&avdic, "reconnect_streamed", "1", 0); // 对于流媒体自动重连 av_dict_set(&avdic, "reconnect_delay_max", "5", 0); // 最大重连延时 5 秒 // av_dict_set(&avdic, "rtsp_flags", "prefer_tcp", 0); // 保持TCP连接 // av_dict_set(&avdic, "timeout", "8000000", // 0); // 超时时间(包括连接和数据传输的时间)设置为5s // av_dict_set(&avdic, "probesize", "50000000", 0); // 增加探针大小,1MB // av_dict_set(&avdic, "analyzeduration", "5000000", 0); // // av_dict_set(&avdic, "keepalive", "1", 0); // 增加分析持续时间,5秒 // 打开输入流之前,设置非阻塞模式 inputFormatCtx->flags |= AVFMT_FLAG_NONBLOCK; // inputFormatCtx->probesize = 50000000; // 打开输入流 if (avformat_open_input(&inputFormatCtx, _url.toUtf8().data(), NULL, &avdic) < 0) { printf("Cannot open input file.\n"); return 0; } // 查找流信息 if (avformat_find_stream_info(inputFormatCtx, NULL) < 0) { printf("Cannot find any stream in file.\n"); return 0; } // 从输入流中找到第一个视频流 for (uint i = 0; i < inputFormatCtx->nb_streams; i++) { if (inputFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) { printf("Cannot find video stream in file.\n"); return 0; } // 获取视频流的解码器参数 videoCodecPara = inputFormatCtx->streams[videoStreamIndex]->codecpar; decoder = avcodec_find_decoder(videoCodecPara->codec_id); if (!decoder) { printf("Cannot find valid decode codec.\n"); return 0; } // 为解码器上下文分配空间 decoderCtx = avcodec_alloc_context3(decoder); if (!decoderCtx) { printf("Cannot find valid decode codec context.\n"); return 0; } // 初始化解码器上下文 if (avcodec_parameters_to_context(decoderCtx, videoCodecPara) < 0) { printf("Cannot initialize parameters.\n"); return 0; } // 设置加速解码 decoderCtx->lowres = decoder->max_lowres; decoderCtx->flags2 |= AV_CODEC_FLAG2_FAST; decoderCtx->thread_count = 8; // 打开解码器 if (avcodec_open2(decoderCtx, decoder, NULL) < 0) { printf("Cannot open codec.\n"); return 0; } // 初始化图像转换器 swsCtx = sws_getContext(decoderCtx->width, // decoderCtx->height, // decoderCtx->pix_fmt, // decoderCtx->width, // decoderCtx->height, // AV_PIX_FMT_RGB32, // SWS_BICUBIC, NULL, NULL, NULL); numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, // decoderCtx->width, // decoderCtx->height, // 1); out_buffer = (unsigned char *)av_malloc(numBytes * sizeof(unsigned char)); int res = av_image_fill_arrays(rgbFrame->data, // rgbFrame->linesize, // out_buffer, // AV_PIX_FMT_RGB32, // decoderCtx->width, // decoderCtx->height, 1); if (res < 0) { qDebug() << "Fill arrays failed.\n"; return 0; } // 裸流保存初始化 m_saveVideoFlag = openSave(); return true; } // 线程里持续执行 void Cffmpeg_decode::run() { m_startPullFlag = open_input_file(); if (!m_startPullFlag) { qDebug() << "Please open video file first."; emit sendConnectFail(1); IsstopPlay = true; return; } // 初始化 previous_pts_time 为无效值(拉流使用) previous_pts_time = -1.0; // 表示没有上一帧的时间戳 first_frame_pts_time = 0.0; // 第一帧的 PTS 时间 first_frame_system_time = 0.0; // 第一帧解码时的系统时间 // 推流使用 firstDts = AV_NOPTS_VALUE; // 初始化第一帧的DTS startTime = av_gettime(); int ret1; // 读取数据包 while (m_startPullFlag) { ret1 = av_read_frame(inputFormatCtx, inputPacket); if (ret1 == AVERROR(EAGAIN)) { // 暂时没有数据流 qDebug() << "No Stream Data"; av_usleep(100000); // 等待 100 毫秒 continue; } else if (ret1 == AVERROR_EOF) { // 流文件结束 qDebug() << "Stream End"; break; } else if (ret1 < 0) { // 发生错误 qDebug() << "Stream Error"; bool ss = reconnect(); if (ss) { qDebug() << "reconnect sucess"; // 初始化 previous_pts_time 为无效值(拉流使用) previous_pts_time = -1.0; // 表示没有上一帧的时间戳 first_frame_pts_time = 0.0; // 第一帧的 PTS 时间 first_frame_system_time = 0.0; // 第一帧解码时的系统时间 // 推流使用 firstDts = AV_NOPTS_VALUE; // 初始化第一帧的DTS startTime = av_gettime(); frm_cnt = 0; continue; } else { qDebug() << "reconnect failed"; break; } // break; // continue; } if (IsstopPlay) { qDebug() << "video play stop"; break; } // 开始推流时,第一次获取时间设置起始DTS if (firstDts == AV_NOPTS_VALUE && bPushStreamFlag) { firstDts = inputPacket->dts; startTime = av_gettime(); // 记录第一个包到来的系统时间 } if (inputPacket->stream_index == videoStreamIndex) { // 推流 if (bPushStreamFlag) { if (!bOpenPushStreamFlag) { // 推流初始化 emit sendInitPushStream_Signal(inputFormatCtx); bOpenPushStreamFlag = true; // QThread::msleep(10); } AVPacket *outputPacket = av_packet_clone(inputPacket); emit sendStreamData_Signal(outputPacket, frm_cnt, startTime, firstDts); } // 保存裸流 if (m_saveVideoFlag) { // 由于保存的m_formatContextSave只创建了一个视频流,而读取到的图像的流索引不一定为0,可能会出现错误【Invalid // packet stream index: 1】 // 所以这里需要将stream_index指定为和m_formatContextSave中视频流索引相同,因为就一个流,所以直接设置为0 inputPacket->stream_index = 0; av_write_frame(m_formatContextSave, inputPacket); // 将数据包写入输出媒体文件 } if (bPlayVideoFlag) { // 解码数据包 if (avcodec_send_packet(decoderCtx, inputPacket) >= 0) { int ret; while ((ret = avcodec_receive_frame(decoderCtx, yuvFrame)) >= 0) { if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return; } else if (ret < 0) { fprintf(stderr, "Error during decoding\n"); exit(1); } if (inputPacket->dts <= 0) continue; // 获取当前帧的 PTS(显示时间戳),并转换为相同时间基 double pts_time = av_rescale_q( inputPacket->pts, inputFormatCtx->streams[videoStreamIndex]->time_base, AVRational{1, AV_TIME_BASE}); // 如果是第一帧,记录系统时间和 PTS 时间 if (previous_pts_time == -1.0) { first_frame_pts_time = pts_time; first_frame_system_time = av_gettime(); // 系统时间(秒) previous_pts_time = pts_time; } // 转化为RGB图像并显示 sws_scale(swsCtx, // yuvFrame->data, // yuvFrame->linesize, // 0, // decoderCtx->height, // rgbFrame->data, // rgbFrame->linesize); QImage img(out_buffer, // decoderCtx->width, // decoderCtx->height, // QImage::Format_RGB32); emit sendQImage(img); // QThread::msleep(1); // 计算从第一帧开始的相对 PTS 时间 double elapsed_pts_time = pts_time - first_frame_pts_time; // 计算从第一帧开始的相对系统时间 double elapsed_system_time = av_gettime() - first_frame_system_time; // 计算需要等待的时间(us) double wait_time = elapsed_pts_time - elapsed_system_time; // qDebug() << "pull stream sleep time:" // << QString::number(wait_time / 1000.0); if (wait_time > 0 && bPushStreamFlag) { // av_usleep(wait_time); // 延时以同步 PTS // sleepMsec(wait_time); } // 更新 previous_pts_time 为当前帧的 PTS previous_pts_time = pts_time; } } } av_packet_unref(inputPacket); } frm_cnt++; if (IsstopPlay) { if (m_saveVideoFlag) { saveDone(); } break; } // 花屏 // if (av_read_frame(inputFormatCtx, inputPacket) < 0) { // break; // 达到文件末尾 // } } if (m_saveVideoFlag) { saveDone(); } // QCoreApplication::processEvents(); qDebug() << "All video play done"; } // 退出 void Cffmpeg_decode::stop() { IsstopPlay = true; if (!inputPacket) av_packet_free(&inputPacket); if (!yuvFrame) av_frame_free(&yuvFrame); if (!rgbFrame) av_frame_free(&rgbFrame); if (!encoderCtx) avcodec_close(encoderCtx); if (!encoderCtx) avcodec_free_context(&encoderCtx); if (!decoderCtx) avcodec_close(decoderCtx); if (!decoderCtx) avcodec_free_context(&decoderCtx); if (!inputFormatCtx) avformat_close_input(&inputFormatCtx); } void Cffmpeg_decode::stopPull() { m_startPullFlag = false; m_reconnectFlag = false; } /** * @brief 设置拉流视频保存路径 * @param fileDirPath: 拉流视频保存路径 */ void Cffmpeg_decode::setSaveFileDirPath(QString fileDirPath) { saveFileDirPath = fileDirPath; } /** * @brief 打开本地视频保存输出流 * @return */ bool Cffmpeg_decode::openSave() { QDir dir; if (!dir.exists(saveFileDirPath)) { dir.mkdir(saveFileDirPath); } QString strName = QString("/%1.h264") .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH-mm-ss")); strName = saveFileDirPath + strName; // const AVOutputFormat *ofmt = av_guess_format("mp4", NULL, NULL); int ret = avformat_alloc_output_context2( &m_formatContextSave, nullptr, nullptr, strName.toStdString().data()); if (ret < 0) { // free(); qWarning() << "DecodeVideo Error"; return false; } // m_videoStreamOut->codecpar->codec_tag = 0; // if (m_formatContextSave->oformat->flags & AVFMT_GLOBALHEADER) { // m_formatContextSave->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // // m_videoStreamOut->codecpar->extradata = (uint8_t *)av_malloc(1024); // // m_videoStreamOut->codecpar->extradata_size = 0; // } // 创建并初始化AVIOContext以访问url所指示的资源。 ret = avio_open(&m_formatContextSave->pb, strName.toStdString().data(), AVIO_FLAG_WRITE); if (ret < 0) { // free(); qWarning() << "Open file Error"; return false; } // 向媒体文件添加新流 m_videoStreamOut = avformat_new_stream(m_formatContextSave, nullptr); if (!m_videoStreamOut) { qWarning() << "Create New Stream Error"; return false; } // 拷贝一些参数,给codecpar赋值(这里使用编码器上下文进行赋值) ret = avcodec_parameters_from_context(m_videoStreamOut->codecpar, decoderCtx); if (ret < 0) { // free(); qWarning() << "avcodec_parameters_from_context Failed"; return false; } // 写入文件头 ret = avformat_write_header(m_formatContextSave, nullptr); if (ret < 0) { // free(); qWarning() << "avformat_write_header Error"; return false; } m_writeHeader = true; qDebug() << "保存视频文件初始化成功!"; return true; } /** * @brief 视频保存结束,释放资源 */ void Cffmpeg_decode::saveDone() { if (m_formatContextSave && m_writeHeader) { av_write_trailer(m_formatContextSave); // 写入文件尾 m_writeHeader = false; } // 关闭文件 if (m_formatContextSave && !(m_formatContextSave->flags & AVFMT_NOFILE)) { avio_close(m_formatContextSave->pb); // av_freep(m_videoStreamOut); if (m_formatContextSave) { avformat_free_context(m_formatContextSave); m_formatContextSave = nullptr; } } m_saveVideoFlag = false; } bool Cffmpeg_decode::reconnect() { m_reconnectFlag = true; stop(); for (int i = 0; i < MAXRECONNECT; ++i) { initFFmpegContainer(); m_startPullFlag = open_input_file(); if (!m_startPullFlag) { stop(); } else { IsstopPlay = false; bOpenPushStreamFlag = false; return true; } if (!m_reconnectFlag) break; qDebug() << "reconnect" << QString::number(i + 1); } return false; } void Cffmpeg_decode::setFlowType(QString param) { m_rtsp_transport = param; m_bSetRtspTransport = true; }