From f1e8c8090e87e3f070d031d4d5eb9adcc83a415d Mon Sep 17 00:00:00 2001 From: cbwu <504-wuchengbo@htsdfp.com> Date: Mon, 16 Sep 2024 08:58:42 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=8E=A8=E6=8B=89=E6=B5=81=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/GDDC/gddcdlg.cpp | 42 +++--- Src/ModelCamera/modelcameradlg.cpp | 32 +++- Src/ModelCamera/modelcameradlg.h | 2 + Src/ModelCamera/modelcameradlg.ui | 233 ++++++++++++++++++----------- Src/Video/cffmpeg_decode.cpp | 220 ++++++++++++++++++--------- Src/Video/cffmpeg_decode.h | 36 ++++- Src/Video/ffmpegpushstream.cpp | 17 ++- Src/Video/ffmpegvideodlg.cpp | 93 ++++++++---- Src/Video/ffmpegvideodlg.h | 30 +++- 9 files changed, 480 insertions(+), 225 deletions(-) diff --git a/Src/GDDC/gddcdlg.cpp b/Src/GDDC/gddcdlg.cpp index e0b9010..581ac04 100644 --- a/Src/GDDC/gddcdlg.cpp +++ b/Src/GDDC/gddcdlg.cpp @@ -76,7 +76,7 @@ void GDDCdlg::initWindow() { m_DlgGDDCSet = new GDDCSet(this); m_DlgGDDCSet->setWindowFlags(Qt::Dialog); - m_DlgGDDCSet->setWindowModality(Qt::WindowModal);//设置为模态对话框 + m_DlgGDDCSet->setWindowModality(Qt::WindowModal); // 设置为模态对话框 m_GDDCCmdDlg = new GDDCCmdDlg(this); m_GDDCStateDlg = new GDDCStateInfo(this); ui->pushButShowStatePage->setIcon(QIcon(":/res/right.png")); @@ -341,7 +341,7 @@ void GDDCdlg::resizeUI() { // 定时器处理 void GDDCdlg::GDDCControlTimeOut() { - if (m_GDDCCmdDlg->sendTimes > 0) { // 优先发送次数指令 + if (m_GDDCCmdDlg->sendTimes > 0) { // 优先发送次数指令 m_GDDCCmdDlg->sendTimes -= 1; // UDP控制 @@ -357,7 +357,7 @@ void GDDCdlg::GDDCControlTimeOut() { if (m_GDDCCmdDlg->sendTimes == 0) { m_GDDCCmdDlg->clearTJDCCmdBuffer(); } - } else { // 发送常发帧或长按指令 + } else { // 发送常发帧或长按指令 m_GDDCCmdDlg->UpdateDataTJDC(); // UDP控制 if (connectFlag[3]) { @@ -376,10 +376,12 @@ void GDDCdlg::GDDCControlTimeOut() { void GDDCdlg::startConnectURL1() { if (!connectFlag[0]) { connectFlag[0] = true; - + ffmpegvideoDlg *videoDlg = ui->WgtffmpegVideo; // 开始拉流 - ui->WgtffmpegVideo->m_flowType = m_DlgGDDCSet->m_FlowType; - ui->WgtffmpegVideo->play(m_DlgGDDCSet->m_playURL1); + videoDlg->m_flowType = m_DlgGDDCSet->m_FlowType; + videoDlg->setStreamIP(m_DlgGDDCSet->m_playURL1); + videoDlg->setPlayVideo(true); + // ui->WgtffmpegVideo->play(m_DlgGDDCSet->m_playURL1); } } @@ -389,7 +391,8 @@ void GDDCdlg::stopConnectURL1() { connectFlag[0] = false; // Is_openVideo = false; // ui->WgtffmpegVideo->m_PlayStatus = Is_openVideo; - ui->WgtffmpegVideo->stop(); + ui->WgtffmpegVideo->setPlayVideo(false); + // ui->WgtffmpegVideo->stop(); } } @@ -472,7 +475,8 @@ void GDDCdlg::startPushURL() { // //方式1:命令行推流 // process = new QProcess(); - // connect(process, &QProcess::readyReadStandardOutput, this, [=]() mutable { + // connect(process, &QProcess::readyReadStandardOutput, this, [=]() mutable + // { // QString Output = process->readAllStandardOutput(); // qDebug() << "Output:" << Output; // }); @@ -482,14 +486,16 @@ void GDDCdlg::startPushURL() { // }); // // process->start("cmd",QStringList()<<"/c"<<"ffmpeg -i - // // rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid -c copy -f flv + // // rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid -c copy -f + // flv // // rtmp://182.92.130.23/app/test"); // QStringList m_cmd; // QString str; // // str = "ffmpeg -i " + m_DlgGDDCSet->m_playURL1 + " -c copy -f flv " + // // m_DlgGDDCSet->m_pushURL; - // str = "ffmpeg -rtsp_transport tcp -i " + m_DlgGDDCSet->m_playURL1 + " -c:v libx264 -c:a copy -f flv " + + // str = "ffmpeg -rtsp_transport tcp -i " + m_DlgGDDCSet->m_playURL1 + " + // -c:v libx264 -c:a copy -f flv " + // m_DlgGDDCSet->m_pushURL; // m_cmd << "/c" << str; // process->start("cmd", m_cmd); @@ -497,11 +503,13 @@ void GDDCdlg::startPushURL() { // //方式2:代码推流 // if(!connectFlag[0]) // { - // QMessageBox::information(NULL, tr("提示"), "请先开始连接", QMessageBox::Ok); - // m_DlgGDDCSet->setPushStreamText("推送"); - // return; + // QMessageBox::information(NULL, tr("提示"), "请先开始连接", + // QMessageBox::Ok); m_DlgGDDCSet->setPushStreamText("推送"); return; // } - ui->WgtffmpegVideo->setPushStreamIP(m_DlgGDDCSet->m_pushURL); + ffmpegvideoDlg *videoDlg = ui->WgtffmpegVideo; + videoDlg->setStreamIP(m_DlgGDDCSet->m_playURL1); + videoDlg->setPushStreamIP(m_DlgGDDCSet->m_pushURL); + videoDlg->setPushStream(true); m_DlgGDDCSet->setPushStreamText("停止推送"); connectFlag[5] = true; @@ -535,9 +543,9 @@ void GDDCdlg::stopPushURL() { // } // } - - //方式2:代码推流 - ui->WgtffmpegVideo->setPushStreamIP(""); + // 方式2:代码推流 + // ui->WgtffmpegVideo->setPushStreamIP(""); + ui->WgtffmpegVideo->setPushStream(false); m_DlgGDDCSet->setPushStreamText("推送"); connectFlag[5] = false; diff --git a/Src/ModelCamera/modelcameradlg.cpp b/Src/ModelCamera/modelcameradlg.cpp index c4ae139..e9212d7 100644 --- a/Src/ModelCamera/modelcameradlg.cpp +++ b/Src/ModelCamera/modelcameradlg.cpp @@ -4,6 +4,11 @@ ModelCameraDlg::ModelCameraDlg(QWidget *parent) : QDialog(parent), ui(new Ui::ModelCameraDlg) { ui->setupUi(this); + ui->videoIPLineEdit->setText( + QStringLiteral("rtmp://liteavapp.qcloud.com/live/" + "liteavdemoplayerstreamid")); // rtsp://192.168.5.70:8554/LIVE + ui->pushStreamIPEdit->setText( + QStringLiteral("rtmp://182.92.130.23/app/stream999")); ui->cameraVideoWidget->setVedioSaveFileDirPath("./3DCameraVideo"); InitialComboBox(); cameraCMDThread = new QThread(); @@ -164,18 +169,35 @@ void ModelCameraDlg::on_OFFBtn_clicked() { // 播放视频 void ModelCameraDlg::on_pushButton_5_clicked() { QString str = ui->pushButton_5->text(); + ffmpegvideoDlg *videoDlg = ui->cameraVideoWidget; if (str == "播放视频") { - QString ip = ui->videoIPLineEdit->text(); - ui->cameraVideoWidget->setPushStreamIP( - QStringLiteral("rtmp://182.92.130.23/app/stream999")); - ui->cameraVideoWidget->play(ip); + QString streamIP = ui->videoIPLineEdit->text(); + videoDlg->setStreamIP(streamIP); + videoDlg->setPlayVideo(true); ui->pushButton_5->setText("暂停播放"); } else { - ui->cameraVideoWidget->stop(); + videoDlg->setPlayVideo(false); ui->pushButton_5->setText("播放视频"); } } +// 推流 +void ModelCameraDlg::on_pushStreamBtn_clicked() { + QString str = ui->pushStreamBtn->text(); + ffmpegvideoDlg *videoDlg = ui->cameraVideoWidget; + if (str == "开始推流") { + QString streamIP = ui->videoIPLineEdit->text(); + QString pushIP = ui->pushStreamIPEdit->text(); + videoDlg->setStreamIP(streamIP); + videoDlg->setPushStreamIP(pushIP); + videoDlg->setPushStream(true); + ui->pushStreamBtn->setText("停止推流"); + } else { + videoDlg->setPushStream(false); + ui->pushStreamBtn->setText("开始推流"); + } +} + // 快门速度 void ModelCameraDlg::on_SSComboBox_activated(int index) { QString tmp = ui->SSComboBox->itemText(index); diff --git a/Src/ModelCamera/modelcameradlg.h b/Src/ModelCamera/modelcameradlg.h index 7aac316..5e42614 100644 --- a/Src/ModelCamera/modelcameradlg.h +++ b/Src/ModelCamera/modelcameradlg.h @@ -45,6 +45,8 @@ private slots: void on_receiveUDP(QByteArray cmdDataArray); + void on_pushStreamBtn_clicked(); + private: void InitialComboBox(); // 状态查询指令 diff --git a/Src/ModelCamera/modelcameradlg.ui b/Src/ModelCamera/modelcameradlg.ui index 23e4a33..b7dba3d 100644 --- a/Src/ModelCamera/modelcameradlg.ui +++ b/Src/ModelCamera/modelcameradlg.ui @@ -25,7 +25,7 @@ QFrame::Shape::NoFrame - + 12 @@ -55,14 +55,11 @@ - + - 拍照指令 + 推拉流设置 - - false - - + 4 @@ -79,26 +76,17 @@ 4 - - - 5 - - - 5 - - - 5 + + + 4 - 5 - - - 10 + 8 - - + + - + 0 0 @@ -121,12 +109,12 @@ - 拍照间隔: + 拉流地址: - - + + 0 @@ -145,13 +133,30 @@ 16777215 + + + + - 开机 + 推流地址: - - + + + + + + + + + 8 + + + 4 + + + 0 @@ -171,12 +176,81 @@ - 停止拍照 + 播放视频 - - + + + + 开始推流 + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + 拍照指令 + + + false + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + 5 + + + 5 + + + 5 + + + 5 + + + 10 + + + 18 + + + 0 @@ -195,13 +269,18 @@ 16777215 + + + 10 + + - 关机 + 拍照间隔: - - + + 0 @@ -221,12 +300,12 @@ - 开始拍照 + 开机 - - + + 0 @@ -245,47 +324,13 @@ 16777215 - - QAbstractSpinBox::ButtonSymbols::NoButtons - - - 1 - - - 0.800000000000000 - - - 25.000000000000000 - - - QAbstractSpinBox::StepType::DefaultStepType + + 停止拍照 - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 10 - - - 5 - - - + + 0 @@ -304,10 +349,13 @@ 16777215 + + 关机 + - - + + 0 @@ -327,14 +375,14 @@ - 播放视频 + 开始拍照 - - + + - + 0 0 @@ -351,13 +399,20 @@ 16777215 - - - 10 - + + QAbstractSpinBox::ButtonSymbols::NoButtons - - 拉流地址: + + 1 + + + 0.800000000000000 + + + 25.000000000000000 + + + QAbstractSpinBox::StepType::DefaultStepType @@ -393,7 +448,7 @@ 8 - 5 + 14 5 diff --git a/Src/Video/cffmpeg_decode.cpp b/Src/Video/cffmpeg_decode.cpp index 1b80d58..6db2a99 100644 --- a/Src/Video/cffmpeg_decode.cpp +++ b/Src/Video/cffmpeg_decode.cpp @@ -6,6 +6,7 @@ Cffmpeg_decode::Cffmpeg_decode(QObject *parent) : QObject(parent) { yuvFrame = av_frame_alloc(); rgbFrame = av_frame_alloc(); avformat_network_init(); + m_rtsp_transport = "tcp"; } Cffmpeg_decode::~Cffmpeg_decode() { @@ -27,24 +28,62 @@ Cffmpeg_decode::~Cffmpeg_decode() { avformat_close_input(&inputFormatCtx); } -void Cffmpeg_decode::setUrl(QString url) { _url = url; } +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(); +} bool Cffmpeg_decode::open_input_file() { if (_url.isEmpty()) return 0; - + m_rtsp_transport = _url.left(4) == "rtmp" ? "tcp" : "udp"; //========================解码============================ AVDictionary *avdic = NULL; - // 设置缓存大小,1080p可将值调大 - av_dict_set(&avdic, "buffer_size", "2048000", 0); // 以udp方式打开,如果以tcp方式打开将udp替换为tcp - av_dict_set(&avdic, "rtsp_transport", m_rtsp_transport.toUtf8().data(), 0); + int ret = av_dict_set(&avdic, "rtsp_transport", + m_rtsp_transport.toUtf8().data(), 0); + // 如果设置失败,则设置UDP传输 + if (avdic == NULL || ret < 0) { + if (m_rtsp_transport == "tcp") + av_dict_set(&avdic, "rtsp_transport", "udp", 0); + else + av_dict_set(&avdic, "rtsp_transport", "tcp", 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", "200000", 0); - av_dict_set(&avdic, "max_delay", "3", 0); // 设置最大时延 - av_dict_set(&avdic, "tune", "zerolatency", 0); + av_dict_set(&avdic, "stimeout", "2000000", 0); // 设置超时2秒 + av_dict_set(&avdic, "max_delay", "3", 0); // 设置最大时延 + av_dict_set(&avdic, "tune", "zerolatency", 0); // 实时编码 av_dict_set(&avdic, "preset", "ultrafast", 0); + av_dict_set(&avdic, "threads", "auto", 0); // 自动开启线程数 inputFormatCtx->flags |= AVFMT_FLAG_NONBLOCK; @@ -64,7 +103,7 @@ bool Cffmpeg_decode::open_input_file() { if (inputFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; - continue; + break; } } if (videoStreamIndex == -1) { @@ -91,6 +130,9 @@ bool Cffmpeg_decode::open_input_file() { printf("Cannot initialize parameters.\n"); return 0; } + // 设置加速解码 + decoderCtx->lowres = decoder->max_lowres; + decoderCtx->flags2 |= AV_CODEC_FLAG2_FAST; // 打开解码器 if (avcodec_open2(decoderCtx, decoder, NULL) < 0) { printf("Cannot open codec.\n"); @@ -123,15 +165,9 @@ bool Cffmpeg_decode::open_input_file() { return 0; } - // 推流初始化 - if (bPushStreamFlag) { - emit sendInitPushStream_Signal(inputFormatCtx); - } + // 裸流保存初始化 + // m_saveVideoFlag = openSave(); - // 裸流保存 - if (!openSave()) { - return false; - }; return true; } @@ -143,11 +179,13 @@ void Cffmpeg_decode::run() { IsstopPlay = true; return; } - // if (bRecordTime) { - // startTime = av_gettime(); - // qDebug() << "*******StartTime:" << QString::number(startTime); - // bRecordTime = false; - // } + + // 初始化 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(); // 读取数据包 @@ -157,8 +195,8 @@ void Cffmpeg_decode::run() { break; } - // 第一次获取时间设置起始DTS - if (firstDts == AV_NOPTS_VALUE) { + // 开始推流时,第一次获取时间设置起始DTS + if (firstDts == AV_NOPTS_VALUE && bPushStreamFlag) { firstDts = inputPacket->dts; startTime = av_gettime(); // 记录第一个包到来的系统时间 } @@ -166,13 +204,17 @@ void Cffmpeg_decode::run() { if (inputPacket->stream_index == videoStreamIndex) { // 推流 if (bPushStreamFlag) { - // av_packet_clone(inputPacket); + 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); } - // qDebug() << "******拉流" << QString::number(frm_cnt++); + // 保存裸流 - if (m_formatContextSave) { + if (m_saveVideoFlag) { // 由于保存的m_formatContextSave只创建了一个视频流,而读取到的图像的流索引不一定为0,可能会出现错误【Invalid // packet stream index: 1】 // 所以这里需要将stream_index指定为和m_formatContextSave中视频流索引相同,因为就一个流,所以直接设置为0 @@ -181,37 +223,68 @@ void Cffmpeg_decode::run() { inputPacket); // 将数据包写入输出媒体文件 } - // 解码数据包 - 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 (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) { + av_usleep(wait_time); // 延时以同步 PTS + } + // 更新 previous_pts_time 为当前帧的 PTS + previous_pts_time = pts_time; } - 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(28); } } av_packet_unref(inputPacket); } frm_cnt++; if (IsstopPlay) { - if (m_formatContextSave) { + if (m_saveVideoFlag) { saveDone(); } break; @@ -221,17 +294,16 @@ void Cffmpeg_decode::run() { // break; // 达到文件末尾 // } } - if (m_formatContextSave) { + + if (m_saveVideoFlag) { saveDone(); } + // QCoreApplication::processEvents(); qDebug() << "All video play done"; } // 退出 void Cffmpeg_decode::stop() { - - // 写入输出文件的尾信息 - // av_write_trailer(outputFormatCtx); IsstopPlay = true; if (!inputPacket) av_packet_free(&inputPacket); @@ -239,26 +311,30 @@ void Cffmpeg_decode::stop() { 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 (!encoderCtx) + avcodec_free_context(&encoderCtx); if (!decoderCtx) avcodec_close(decoderCtx); + if (!decoderCtx) + avcodec_free_context(&decoderCtx); if (!inputFormatCtx) avformat_close_input(&inputFormatCtx); - - // 停止推流 - bPushStreamFlag = false; - emit sendStopPushStream_Signal(); } +/** + * @brief 设置拉流视频保存路径 + * @param fileDirPath: 拉流视频保存路径 + */ void Cffmpeg_decode::setSaveFileDirPath(QString fileDirPath) { saveFileDirPath = fileDirPath; } +/** + * @brief 打开本地视频保存输出流 + * @return + */ bool Cffmpeg_decode::openSave() { QDir dir; if (!dir.exists(saveFileDirPath)) { @@ -287,13 +363,13 @@ bool Cffmpeg_decode::openSave() { AVIO_FLAG_WRITE); if (ret < 0) { // free(); - qWarning() << "DecodeVideo Error"; + qWarning() << "Open file Error"; return false; } // 向媒体文件添加新流 m_videoStreamOut = avformat_new_stream(m_formatContextSave, nullptr); if (!m_videoStreamOut) { - qWarning() << "DecodeVideo Error"; + qWarning() << "Create New Stream Error"; return false; } // 拷贝一些参数,给codecpar赋值(这里使用编码器上下文进行赋值) @@ -308,15 +384,18 @@ bool Cffmpeg_decode::openSave() { ret = avformat_write_header(m_formatContextSave, nullptr); if (ret < 0) { // free(); - qWarning() << "DecodeVideo Error"; + qWarning() << "avformat_write_header Error"; return false; } m_writeHeader = true; - qDebug() << "开始录制视频!"; + qDebug() << "保存视频文件初始化成功!"; return true; } +/** + * @brief 视频保存结束,释放资源 + */ void Cffmpeg_decode::saveDone() { if (m_formatContextSave && m_writeHeader) { av_write_trailer(m_formatContextSave); // 写入文件尾 @@ -330,11 +409,8 @@ void Cffmpeg_decode::saveDone() { avformat_free_context(m_formatContextSave); m_formatContextSave = nullptr; } - // m_videoStreamOut = nullptr; } + m_saveVideoFlag = false; } -void Cffmpeg_decode::setFlowType(QString param) -{ - m_rtsp_transport = param; -} +void Cffmpeg_decode::setFlowType(QString param) { m_rtsp_transport = param; } diff --git a/Src/Video/cffmpeg_decode.h b/Src/Video/cffmpeg_decode.h index 9512b61..079327a 100644 --- a/Src/Video/cffmpeg_decode.h +++ b/Src/Video/cffmpeg_decode.h @@ -2,10 +2,12 @@ #define CFFMPEG_DECODE_H #include "ffmpeginclude.h" +#include #include #include #include #include +#include #include #include #include @@ -19,7 +21,6 @@ public: Cffmpeg_decode(QObject *parent = nullptr); ~Cffmpeg_decode(); -public: bool open_input_file(); void stop(); /** @@ -27,11 +28,13 @@ public: * @param fileDirPath 文件夹路径 */ void setSaveFileDirPath(QString fileDirPath); - void setFlowType(QString);//设置拉流打开方式 + void setFlowType(QString); // 设置拉流打开方式 + + void setStreamUrl(QString url); public slots: void run(); - void setUrl(QString url); - + void setPlayVideo(bool bPlay); + void setPushStream(bool bPushStream); signals: void sendQImage(QImage); void sendConnectFail(int); @@ -41,10 +44,15 @@ signals: void sendStopPushStream_Signal(); public: + QMutex mutex; + bool IsstopPlay = false; - bool bPushStreamFlag = false; + bool bPushStreamFlag = false; // 推流标志 + bool bPlayVideoFlag = false; // 播放视频标志 private: + bool bOpenPushStreamFlag = false; // 推流初始化 + AVFormatContext *inputFormatCtx = NULL; // AVCodecContext *decoderCtx = NULL; // @@ -70,18 +78,32 @@ private: QString _url; QString saveFileDirPath; bool bRecordTime = true; + + // 推流使用 int64_t startTime; int64_t firstDts; + + // 初始化 previous_pts_time 为无效值(拉流使用) + double previous_pts_time; // 表示没有上一帧的时间戳 + double first_frame_pts_time; // 第一帧的 PTS 时间 + double first_frame_system_time; // 第一帧解码时的系统时间 /******** 保存裸流使用 ******************/ AVFormatContext *m_formatContextSave = nullptr; // 封装上下文 QString m_strCodecName; // 编解码器名称 AVStream *m_videoStreamOut = nullptr; // 输出视频流 bool m_writeHeader = false; // 是否写入文件头 - - QString m_rtsp_transport = ""; //拉流打开方式,UDP或TCP + QString m_rtsp_transport; // 拉流打开方式,UDP或TCP private: + bool m_saveVideoFlag = false; + /** + * @brief 打开视频保存文件 + * @return + */ bool openSave(); + /** + * @brief 视频保存成功,释放资源 + */ void saveDone(); }; diff --git a/Src/Video/ffmpegpushstream.cpp b/Src/Video/ffmpegpushstream.cpp index 34b8f78..588fd53 100644 --- a/Src/Video/ffmpegpushstream.cpp +++ b/Src/Video/ffmpegpushstream.cpp @@ -12,6 +12,9 @@ int FFmpegPushStream::openNetworkStream(AVFormatContext *inputFormatCtx) { if (pushStreamIP.isEmpty()) return -1; + if (!inputFormatCtx) + return -1; + int ret; // 初始化网络输出流 // const char *output_url = "rtsp://182.92.130.23/app/stream999"; @@ -56,8 +59,9 @@ int FFmpegPushStream::openNetworkStream(AVFormatContext *inputFormatCtx) { } // 写入头文件 - if (avformat_write_header(outputFormatCtx, NULL) < 0) { - qDebug() << "Error occurred when opening output file.\n"; + ret = avformat_write_header(outputFormatCtx, NULL); + if (ret < 0) { + qDebug() << "Error occurred when write_header into output file.\n"; return -1; } mInitStatus = true; @@ -110,7 +114,7 @@ int FFmpegPushStream::pushStream(AVPacket *pkt, int frm_cnt, int64_t startTime, auto now_time = av_gettime() - startTime; // 获取差值 int64_t delay = streamTime - now_time; if (delay > 0) { - // qDebug() << "****************sleep time:" << QString::number(delay); + qDebug() << "****************sleep time:" << QString::number(delay / 1000); av_usleep(delay); } @@ -142,6 +146,8 @@ int FFmpegPushStream::pushStream(AVPacket *pkt, int frm_cnt, int64_t startTime, } // 数据包写入成功,现在可以释放pkt av_packet_unref(pkt); + av_packet_free(&pkt); + return 1; } /** @@ -149,14 +155,13 @@ int FFmpegPushStream::pushStream(AVPacket *pkt, int frm_cnt, int64_t startTime, */ void FFmpegPushStream::stopPush() { av_write_trailer(outputFormatCtx); - if (inputFormatCtx != nullptr) { - avformat_close_input(&inputFormatCtx); - } // 关闭输出 if (outputFormatCtx && !(outputFormatCtx->flags & AVFMT_NOFILE)) { avio_close(outputFormatCtx->pb); } if (outputFormatCtx) { avformat_free_context(outputFormatCtx); + outputFormatCtx = nullptr; } + mInitStatus = false; } diff --git a/Src/Video/ffmpegvideodlg.cpp b/Src/Video/ffmpegvideodlg.cpp index 6e72b05..ef70037 100644 --- a/Src/Video/ffmpegvideodlg.cpp +++ b/Src/Video/ffmpegvideodlg.cpp @@ -13,6 +13,8 @@ ffmpegvideoDlg::ffmpegvideoDlg(QWidget *parent) ffmpegvideoDlg::~ffmpegvideoDlg() { stop(); + if (!ffmpeg) + ffmpeg->deleteLater(); delete ui; if (ffmpegPushStream != nullptr) ffmpegPushStream->deleteLater(); @@ -27,16 +29,18 @@ void ffmpegvideoDlg::setVedioSaveFileDirPath(QString saveDirPath) { videoSaveDirPath = saveDirPath; } +/** + * @brief ffmpegvideoDlg::play + * @param url 拉流地址 + */ void ffmpegvideoDlg::play(QString url) { if (!m_PlayStatus) { m_PlayStatus = true; ffmpeg = new Cffmpeg_decode; ffmpeg->setSaveFileDirPath(videoSaveDirPath); + ffmpeg->setStreamUrl(url); // 设置拉流URL ffmpeg->IsstopPlay = false; - - if (!pushStreamIP.isEmpty()){ - startPushStream(); - } + ffmpeg->setPlayVideo(this->m_bVideoPlayFlag); // 设置是否播放视频 ffmpeg->setFlowType(m_flowType); ffmpeg->moveToThread(&workerThread); @@ -45,18 +49,20 @@ void ffmpegvideoDlg::play(QString url) { &QObject::deleteLater); // 线程发送结束标志 connect(this, &ffmpegvideoDlg::operate, ffmpeg, &Cffmpeg_decode::run); // 线程开始处理数据 - connect(this, &ffmpegvideoDlg::setUrlSign, ffmpeg, - &Cffmpeg_decode::setUrl); // 设置URL + // connect(this, &ffmpegvideoDlg::setUrlSign, ffmpeg, + // &Cffmpeg_decode::setUrl); // 设置URL connect(ffmpeg, SIGNAL(sendQImage(QImage)), this, SLOT(receiveQImage(QImage))); // 发送解析的图片 connect(ffmpeg, SIGNAL(sendConnectFail(int)), this, SLOT(showMessagBox(int))); // 发送错误信息提示 workerThread.start(); - emit this->setUrlSign(url); // 设置URL - emit this->operate(); // 启用线程信号 + emit this->operate(); // 启用线程信号,开始拉流 } } +/** + * @brief 停止拉流 + */ void ffmpegvideoDlg::stop() { if (m_PlayStatus) { ffmpeg->stop(); @@ -116,29 +122,66 @@ bool ffmpegvideoDlg::Isplay(bool IsstopPlay) { return ffmpeg->IsstopPlay = IsstopPlay; } +/** + * @brief 设置拉流地址 + * @param streamURL + */ +void ffmpegvideoDlg::setStreamIP(QString streamURL) { m_streamIP = streamURL; } + // 设置推流地址 void ffmpegvideoDlg::setPushStreamIP(QString pushStreamURL) { - pushStreamIP = pushStreamURL; + m_pushStreamIP = pushStreamURL; } // 开始推流 -void ffmpegvideoDlg::startPushStream() { - if (ffmpegPushStream == nullptr) { - ffmpegPushStream = new FFmpegPushStream; - ffmpegPushStream->setRemoteIP(pushStreamIP); // 设置推流地址 +void ffmpegvideoDlg::setPushStream(bool bPush) { + this->m_bPushStreamFlag = bPush; + if (this->m_bPushStreamFlag) { + this->play(m_streamIP); + // QThread::msleep(500); + // 创建推流任务对象 + if (ffmpegPushStream == nullptr) { + ffmpegPushStream = new FFmpegPushStream; + ffmpegPushStream->setRemoteIP(m_pushStreamIP); // 设置推流地址 + } + // 推流线程开启 + if (pushStreamThread == nullptr) { + pushStreamThread = new QThread; + pushStreamThread->start(); + ffmpegPushStream->moveToThread(pushStreamThread); + } + if (ffmpeg) { + connect(ffmpeg, &Cffmpeg_decode::sendInitPushStream_Signal, + ffmpegPushStream, &FFmpegPushStream::openNetworkStream, + Qt::UniqueConnection); + connect(ffmpeg, &Cffmpeg_decode::sendStreamData_Signal, ffmpegPushStream, + &FFmpegPushStream::pushStream, Qt::UniqueConnection); + connect(ffmpeg, &Cffmpeg_decode::sendStopPushStream_Signal, + ffmpegPushStream, &FFmpegPushStream::stopPush, + Qt::UniqueConnection); + // ffmpeg->bPushStreamFlag = true; + } + qDebug() << "video threadID:" << QThread::currentThreadId(); + ffmpeg->setPushStream(true); + } else { // 停止推流 + ffmpeg->setPushStream(false); + if (!this->m_bVideoPlayFlag && !this->m_bPushStreamFlag) + this->stop(); } +} - if (pushStreamThread == nullptr) { - pushStreamThread = new QThread; - pushStreamThread->start(); - ffmpegPushStream->moveToThread(pushStreamThread); +/** + * @brief 视频播放 + * @param bPlay: 是否播放 + */ +void ffmpegvideoDlg::setPlayVideo(bool bPlay) { + if (m_PlayStatus && ffmpeg) { + // emit startPlayVideoSignal(bPlay); + ffmpeg->setPlayVideo(bPlay); } - connect(ffmpeg, &Cffmpeg_decode::sendInitPushStream_Signal, ffmpegPushStream, - &FFmpegPushStream::openNetworkStream); - connect(ffmpeg, &Cffmpeg_decode::sendStreamData_Signal, ffmpegPushStream, - &FFmpegPushStream::pushStream); - connect(ffmpeg, &Cffmpeg_decode::sendStopPushStream_Signal, ffmpegPushStream, - &FFmpegPushStream::stopPush); - - ffmpeg->bPushStreamFlag = true; + m_bVideoPlayFlag = bPlay; + if (!m_streamIP.isEmpty()) + this->play(m_streamIP); + if (!this->m_bVideoPlayFlag && !this->m_bPushStreamFlag) + this->stop(); } diff --git a/Src/Video/ffmpegvideodlg.h b/Src/Video/ffmpegvideodlg.h index 2f2ece5..60c22a2 100644 --- a/Src/Video/ffmpegvideodlg.h +++ b/Src/Video/ffmpegvideodlg.h @@ -4,6 +4,7 @@ #include "cffmpeg_decode.h" #include "ffmpeginclude.h" #include "ffmpegpushstream.h" +#include #include #include #include @@ -23,16 +24,31 @@ public: explicit ffmpegvideoDlg(QWidget *parent = nullptr); ~ffmpegvideoDlg(); + /** + * @brief 设置视频保存路径 + * @param saveDirPath: 保存路径 + */ void setVedioSaveFileDirPath(QString saveDirPath); - //拉流 + // 拉流 void play(QString); void stop(); bool Isplay(bool IsstopPlay); + /** + * @brief 设置拉流地址 + * @param streamURL: + */ + void setStreamIP(QString streamURL); - //推流 + // 推流 void setPushStreamIP(QString pushStreamURL); - void startPushStream(); + void setPushStream(bool bPush); + + /** + * @brief 视频播放 + * @param bPlay: 是否播放 + */ + void setPlayVideo(bool bPlay); private: Ui::ffmpegvideoDlg *ui; @@ -45,6 +61,10 @@ public: double m_ay; QString m_flowType; + + bool m_bVideoPlayFlag = false; // 视频播放标志 + bool m_bPushStreamFlag = false; // 推流标志 + protected: void paintEvent(QPaintEvent *); void resizeEvent(QResizeEvent *event); @@ -63,7 +83,9 @@ private: QString videoSaveDirPath; QThread *pushStreamThread; FFmpegPushStream *ffmpegPushStream; - QString pushStreamIP; + QString m_pushStreamIP; + QString m_streamIP; + // bool bPushStream }; #endif // FFMPEGVIDEODLG_H