diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5f2a291..ce8851a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -468,7 +468,10 @@ void MainWindow::pushStreamSlot(bool bPush) { } else { pushStreamIP = g_pushStreamInfoStruct.pushStreamIP; } - ui->videoWidget->pushStream(pushStreamIP); + QStringList pushStreamIPs; + pushStreamIPs.append(pushStreamIP); + // pushStreamIPs.append("rtmp://182.92.130.23/videoclient/fp98C_1_gcs"); + ui->videoWidget->pushStream(pushStreamIPs); } else { ui->videoWidget->stopPushStream(); // ui->videoWidget1->stopPushStream(); diff --git a/src/video/pushstream.cpp b/src/video/pushstream.cpp index 96677b9..a1338e1 100644 --- a/src/video/pushstream.cpp +++ b/src/video/pushstream.cpp @@ -4,8 +4,8 @@ PushStream::PushStream(QObject *parent) : QObject{parent} { connect(this,&PushStream::initStreamPusherSignal,this,&PushStream::init,Qt::BlockingQueuedConnection); } -void PushStream::setRemoteIP(QString url) { - m_pushStreamIP = url; +void PushStream::setRemoteIP(QStringList& urls) { + m_pushStreamIPs = urls; } bool PushStream::init(AVFormatContext *inputFormatCtx, @@ -14,7 +14,18 @@ bool PushStream::init(AVFormatContext *inputFormatCtx, // m_pusherQueue = queue; m_queueManager = queueManager; m_inputFormatCtx = inputFormatCtx; - m_start = openNetworkStream(inputFormatCtx); + // m_start = openNetworkStream(inputFormatCtx); + bool ss = false; + for (int i = 0; i < m_pushStreamIPs.size(); ++i) { + AVFormatContext* outputformatCtx = nullptr; + ss = openNetworkStream(inputFormatCtx,outputformatCtx,m_pushStreamIPs.at(i)); + m_outputFormatCtxArray.append(outputformatCtx); + m_initStatus.append(ss); + if(ss){ + m_start = ss; + } + } + initStatus = m_start; return m_start; } @@ -26,36 +37,37 @@ void PushStream::close() { // qDebug() << "*******m_end0:" << m_end; } -bool PushStream::openNetworkStream(AVFormatContext *inputFormatCtx) { - if (m_pushStreamIP.isEmpty()) return false; +bool PushStream::openNetworkStream(AVFormatContext *&inputFormatCtx,AVFormatContext *&outputFormatCtx,QString pushStreamIP) { + if (pushStreamIP.isEmpty()) return false; if (!inputFormatCtx) return false; // 初始化网络输出流 // QString m_pushStreamIP = "rtsp://182.92.130.23/app/stream999"; QString format_name = "flv"; - if (m_pushStreamIP.left(3) == "udp" || m_pushStreamIP.left(3) == "UDP") + if (pushStreamIP.left(3) == "udp" || pushStreamIP.left(3) == "UDP") format_name = "mpegts"; int ret = avformat_alloc_output_context2( - &m_outputFormatCtx, NULL, format_name.toStdString().data(), - m_pushStreamIP.toUtf8().constData()); + &outputFormatCtx, NULL, format_name.toStdString().data(), + pushStreamIP.toUtf8().constData()); if (ret < 0) { showError(ret); - free(); + free(outputFormatCtx); qDebug() << "Could not create output context."; return false; } + AVStream *m_ostream = nullptr; // 复制流信息 for (unsigned int i = 0; i < inputFormatCtx->nb_streams; ++i) { // AVStream *stream = inputFormatCtx->streams[i]; if (inputFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { m_istream = inputFormatCtx->streams[i]; - m_ostream = avformat_new_stream(m_outputFormatCtx, nullptr); + m_ostream = avformat_new_stream(outputFormatCtx, nullptr); if (!m_ostream) { qDebug() << "Failed allocating output stream.\n"; - free(); + free(outputFormatCtx); return false; } // 复制编解码器参数 @@ -63,7 +75,7 @@ bool PushStream::openNetworkStream(AVFormatContext *inputFormatCtx) { m_istream->codecpar); if (ret < 0) { showError(ret); - free(); + free(outputFormatCtx); qWarning() << "avcodec_parameters_from_context Failed"; return false; } @@ -78,22 +90,22 @@ bool PushStream::openNetworkStream(AVFormatContext *inputFormatCtx) { // 打开输出文件 - if (!(m_outputFormatCtx->flags & AVFMT_NOFILE)) { - if (ret = avio_open(&m_outputFormatCtx->pb, - m_pushStreamIP.toUtf8().constData(), + if (!(outputFormatCtx->flags & AVFMT_NOFILE)) { + if (ret = avio_open(&outputFormatCtx->pb, + pushStreamIP.toUtf8().constData(), AVIO_FLAG_WRITE) < 0) { showError(ret); - free(); + free(outputFormatCtx); qDebug() << "Could not open output file.\n"; return false; } } // 写入头文件 - ret = avformat_write_header(m_outputFormatCtx, NULL); + ret = avformat_write_header(outputFormatCtx, NULL); if (ret < 0) { showError(ret); - free(); + free(outputFormatCtx); qDebug() << "Error occurred when write_header into output file.\n"; return false; } @@ -108,57 +120,63 @@ bool PushStream::openNetworkStream(AVFormatContext *inputFormatCtx) { return true; } -int PushStream::reconnect(int ret) { +int PushStream::reconnect(int ret,int id) { if(!m_queueManager->m_isPullReconnect) return 0; if (ret == -10053 || ret == -10054) { m_queueManager->m_isPushReconnect = true; m_end = false; // qDebug() << "m_end:" << m_end; + AVFormatContext* outputFormatCtx = m_outputFormatCtxArray.at(id); + QString message; for (int nRetryCount = 0; nRetryCount < MAXCONNECT; ++nRetryCount) { if(m_queueManager->m_isPullReconnect) { break; } // 关闭输出 - if (m_outputFormatCtx && - !(m_outputFormatCtx->flags & AVFMT_NOFILE)) { - avio_close(m_outputFormatCtx->pb); + if (outputFormatCtx && + !(outputFormatCtx->flags & AVFMT_NOFILE)) { + avio_close(outputFormatCtx->pb); } ret = - avio_open(&m_outputFormatCtx->pb, - m_pushStreamIP.toUtf8().constData(), AVIO_FLAG_WRITE); + avio_open(&outputFormatCtx->pb, + m_pushStreamIPs[id].toUtf8().constData(), AVIO_FLAG_WRITE); if (ret < 0) { showError(ret); qDebug() << "Failed to reconnect" << QString::number(nRetryCount + 1); - QString str = - QString("网络中断,尝试重连第%1次!").arg(nRetryCount + 1); - emit sendErrorMessageSignal(str, 3); + message = + QString("推流地址%1网络中断,尝试重连第%2次!").arg(id+1,nRetryCount + 1); + emit sendErrorMessageSignal(message, 3); if (m_end) break; // av_usleep(5 * 1000000); continue; } // Try to reconnect - ret = avformat_write_header(m_outputFormatCtx, nullptr); + ret = avformat_write_header(outputFormatCtx, nullptr); if (ret < 0) { + m_initStatus[id] = false; showError(ret); // free(); qDebug() << "Failed to reconnect" << QString::number(nRetryCount + 1); - QString str = - QString("网络中断,尝试重连第%1次!").arg(nRetryCount + 1); - emit sendErrorMessageSignal(str, 3); + message = + QString("推流地址%1网络中断,尝试重连第%2次!").arg(id+1,nRetryCount + 1); + emit sendErrorMessageSignal(message, 3); if (m_end) break; // nRetryCount++; // av_usleep(5 * 1000000); } else { + m_initStatus[id] = true; m_start = true; m_firstPts = AV_NOPTS_VALUE; m_frm_cnt = 0; m_bwriteHeader = true; - emit sendErrorMessageSignal("重连成功!", 1); + message = + QString("推流地址%1重连成功!").arg(id+1); + emit sendErrorMessageSignal(message, 1); m_queueManager->clearPushQueue(); - qDebug() << "重连成功!"; + qDebug() << message; m_queueManager->m_isPushReconnect = false; return ret; @@ -167,8 +185,16 @@ int PushStream::reconnect(int ret) { if (m_end) break; } m_start = false; + for (int i = 0; i < m_initStatus.size(); ++i) { + if(m_initStatus.at(i)){ + m_start = true; + break; + } + } m_bwriteHeader = false; - emit sendErrorMessageSignal("重连失败,推流停止!", 2); + message = + QString("推流地址%1重连失败,推流停止!").arg(id+1); + emit sendErrorMessageSignal(message, 2); m_queueManager->m_isPushReconnect = false; return -1; } @@ -254,29 +280,49 @@ void PushStream::pushStream(int64_t startTime) { // 向推流服务器推送流数据 m_frm_cnt++; - int ret = - av_interleaved_write_frame(m_outputFormatCtx, inputPacket); - if (ret < 0) { - av_packet_unref(inputPacket); - showError(ret); - // if (ret == -10053) { - // qDebug() << "网络不稳定"; - // } - if (reconnect(ret) < 0) { - break; - }; - continue; + int ret; + + for (int i = 0; i < m_outputFormatCtxArray.size(); ++i) { + if(!m_initStatus.at(i)) continue; + AVFormatContext* outputFormatCtx = m_outputFormatCtxArray.at(i); + if(outputFormatCtx!=nullptr){ + AVPacket* clonePacket = nullptr; + if(i!= m_outputFormatCtxArray.size()-1){ + clonePacket = av_packet_clone(inputPacket); + }else{ + clonePacket = inputPacket; + } + ret = + av_interleaved_write_frame(outputFormatCtx, clonePacket); + if (ret < 0) { + av_packet_unref(clonePacket); + showError(ret); + // if (ret == -10053) { + // qDebug() << "网络不稳定"; + // } + if (reconnect(ret,i) < 0) { + // break; + }; + continue; + } + // 数据包写入成功,现在可以释放pkt + av_packet_unref(clonePacket); + av_packet_free(&clonePacket); + } } - // 数据包写入成功,现在可以释放pkt - av_packet_unref(inputPacket); - av_packet_free(&inputPacket); } else { // QThread::usleep(1000); av_usleep(1000); } } - if (m_bwriteHeader) av_write_trailer(m_outputFormatCtx); + if (m_bwriteHeader) { + for (auto& outputFormatCtx : m_outputFormatCtxArray) { + if(outputFormatCtx!=nullptr){ + av_write_trailer(outputFormatCtx); + } + } + } free(); m_queueManager->m_isStreamPusherEnd = true; m_queueManager->wakeStreamPusher(); @@ -286,13 +332,27 @@ void PushStream::pushStream(int64_t startTime) { } void PushStream::free() { - m_start = false; + for (auto &outputFormatCtx : m_outputFormatCtxArray) { + // 关闭输出 + if (outputFormatCtx && !(outputFormatCtx->flags & AVFMT_NOFILE)) { + avio_close(outputFormatCtx->pb); + } + if (outputFormatCtx) { + avformat_free_context(outputFormatCtx); + outputFormatCtx = nullptr; + } + } + m_outputFormatCtxArray.clear(); +} + +void PushStream::free(AVFormatContext *&outputFormatCtx) +{ // 关闭输出 - if (m_outputFormatCtx && !(m_outputFormatCtx->flags & AVFMT_NOFILE)) { - avio_close(m_outputFormatCtx->pb); + if (outputFormatCtx && !(outputFormatCtx->flags & AVFMT_NOFILE)) { + avio_close(outputFormatCtx->pb); } - if (m_outputFormatCtx) { - avformat_free_context(m_outputFormatCtx); - m_outputFormatCtx = nullptr; + if (outputFormatCtx) { + avformat_free_context(outputFormatCtx); + outputFormatCtx = nullptr; } } diff --git a/src/video/pushstream.h b/src/video/pushstream.h index b076f59..25c28f4 100644 --- a/src/video/pushstream.h +++ b/src/video/pushstream.h @@ -17,7 +17,7 @@ public: * @brief 设置推流地址 * @param url 远端推流地址 */ - void setRemoteIP(QString url); + void setRemoteIP(QStringList& urls); void close(); public slots: @@ -29,22 +29,24 @@ signals: void sendErrorMessageSignal(QString message, int type); void initStreamPusherSignal(AVFormatContext *inputFormatCtx,AVPacketQueueManager *queueManager); private: - bool openNetworkStream(AVFormatContext *inputFormatCtx); - int reconnect(int ret); + bool openNetworkStream(AVFormatContext *&inputFormatCtx,AVFormatContext *&outputFormatCtx,QString pushStreamIP); + int reconnect(int ret,int id=0); void free(); - + void free(AVFormatContext *&outputFormatCtx); public: bool initStatus = false; private: // QQueue *m_pusherQueue = nullptr; AVFormatContext *m_inputFormatCtx = nullptr; // - AVFormatContext *m_outputFormatCtx = NULL; // + AVFormatContext *m_outputFormatCtx = nullptr; // + AVFormatContext *m_outputFormatCtx2 = nullptr; //推流2 + QVectorm_outputFormatCtxArray; AVStream *m_istream = nullptr; - AVStream *m_ostream = nullptr; + // AVStream *m_ostream = nullptr; bool m_bwriteHeader = false; int m_videoIndex = -1; - QString m_pushStreamIP; // 推流地址 + QStringList m_pushStreamIPs; // 推流地址 std::atomic m_start = false; std::atomic m_end = false; int64_t m_startTime; @@ -58,6 +60,8 @@ private: AVRational m_inputTimeBase; AVRational m_inputFrameRate; AVRational m_outputTimeBase; + + QVector m_initStatus; }; #endif // PUSHSTREAM_H diff --git a/src/video/videowidget.cpp b/src/video/videowidget.cpp index 8c825c3..5170d71 100644 --- a/src/video/videowidget.cpp +++ b/src/video/videowidget.cpp @@ -171,8 +171,8 @@ void VideoWidget::stopPlay() { } // 推流 -bool VideoWidget::pushStream(const QString &url) { - if (url.isEmpty()) { +bool VideoWidget::pushStream(QStringList &urls) { + if (urls.isEmpty()) { return false; } else { // 先拉流 @@ -192,7 +192,7 @@ bool VideoWidget::pushStream(const QString &url) { connect(streamPusher, &PushStream::sendErrorMessageSignal, this, &VideoWidget::receiveErrorMessage, Qt::UniqueConnection); } - streamPusher->setRemoteIP(url); + streamPusher->setRemoteIP(urls); streamPusher->moveToThread(&pushStreamThread); pushStreamThread.start(); @@ -203,7 +203,7 @@ bool VideoWidget::pushStream(const QString &url) { return false; } - m_pushURL = url; + m_pushURLs = urls; m_pushFlag = true; return true; } @@ -230,8 +230,8 @@ void VideoWidget::setPullURL(const QString &url) { m_pullURL = url; } -void VideoWidget::setPushURL(const QString &url) { - m_pushURL = url; +void VideoWidget::setPushURL(QStringList &urls) { + m_pushURLs = urls; } void VideoWidget::setVedioSaveFileDirPath(const QString &dirPath) { diff --git a/src/video/videowidget.h b/src/video/videowidget.h index a915172..a49fc7a 100644 --- a/src/video/videowidget.h +++ b/src/video/videowidget.h @@ -46,10 +46,10 @@ public: bool play(const QString &url, bool bSave = true); bool udpPlay(QString ip, int port); void stopPlay(); - bool pushStream(const QString &url); + bool pushStream(QStringList &urls); void stopPushStream(); void setPullURL(const QString &url); - void setPushURL(const QString &url); + void setPushURL(QStringList &urls); void setVedioSaveFileDirPath(const QString &dirPath); bool getPlayStatus(); double getVideoDuration(); @@ -98,7 +98,7 @@ private: private: QString m_pullURL; - QString m_pushURL; + QStringList m_pushURLs; bool m_playFlag = false; bool m_pullFlag = false; bool m_pushFlag = false;