Browse Source

V0.3.4
1、完成了RTP发送音频数据的功能

Apple 6 days ago
parent
commit
d8390a1b91

+ 144 - 62
RTPServer/RtpOneRoadThread.cpp

@@ -4,6 +4,7 @@
 
 
 
+
 RTPOneRoadThread::RTPOneRoadThread(RecordThreadInfo_t& threadInfo)
     : BaseRecordThread(threadInfo),
       m_eventLoop()
@@ -17,7 +18,7 @@ RTPOneRoadThread::RTPOneRoadThread(RecordThreadInfo_t& threadInfo)
     m_logBase = fmt::format("录音通道: {}:{}", m_threadInfo.cardRoadInfo.strSoundCardName.toStdString(), 
                             m_threadInfo.cardRoadInfo.roadInfo.nRoadNum);
     
-    m_isRunning.store(true); // 设置为运行状态
+    
 }
 
 RTPOneRoadThread::~RTPOneRoadThread()
@@ -35,11 +36,15 @@ void RTPOneRoadThread::stopThread()
     {
         m_sendTimer.stop();
     }
-    while(m_eventLoop.isRunning())
+    if(m_eventLoop.isRunning())
     {
         m_eventLoop.quit();
         m_eventLoop.processEvents(QEventLoop::AllEvents, 10);
     }
+    while(m_isRunning.load())
+    {
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    }
 }
 
 /* 设置数据 */
@@ -51,7 +56,6 @@ bool RTPOneRoadThread::setData(const AudioSrcData& srcData)
         return true;
     }
 
-    /* 如果数据列表1被占用,就写入数据列表2 */
     AudioSrcData* data = new AudioSrcData(srcData);
     if(data == nullptr)
     {
@@ -66,63 +70,54 @@ bool RTPOneRoadThread::setData(const AudioSrcData& srcData)
 bool RTPOneRoadThread::addUdpSession(const RtpSendClientInfo_t& udpSession)
 {
     /* 检查是否已有相同的会话,这里只需要检查IP和端口即可,会话ID和名称在这里没什么用 */
-    for(const auto& session : m_listUdpSockets)
+    for(const auto& session : m_listClients)
     {
         if(session.clientIP == udpSession.clientIP && session.clientPort == udpSession.clientPort)
         {
             SPDLOG_LOGGER_DEBUG(m_logger, "{} 已存在相同的UDP会话: {}:{}", m_logBase, udpSession.clientIP.toStdString(), udpSession.clientPort);
-            return true;
+            return false;
         }
     }
+    SPDLOG_LOGGER_INFO(m_logger, "{} 添加UDP会话: {}:{}", m_logBase, udpSession.clientIP.toStdString(), udpSession.clientPort);
 
-    /* 创建新的UDP套接字 */
-    QUdpSocket* udpSocket = new QUdpSocket();
-    /* 绑定本地IP和端口 */
-    if(!udpSocket->bind(QHostAddress::Any, udpSession.localPort))
+    /* 绑定UDP本地端口 */
+    if(m_localPort < 0)
     {
-        SPDLOG_LOGGER_ERROR(m_logger, "{} 创建UDP套接字失败: {}:{}", m_logBase, udpSession.clientIP.toStdString(), udpSession.clientPort);
-        delete udpSocket;
-        return false;
+        m_udpState = eUDPState::eUDP_Init;
+        m_localIP = udpSession.localIP;
+        m_localPort = udpSession.localPort;
     }
-    SPDLOG_LOGGER_INFO(m_logger, "{} 添加UDP会话: {}:{}", m_logBase, udpSession.clientIP.toStdString(), udpSession.clientPort);
-    // 将新的会话添加到列表中
-    RtpSendClientInfo_t newSession = udpSession;
-    newSession.udpSocket = udpSocket;
-
+    
     m_lockUdpSockets.lock();
-    m_listUdpSockets.append(newSession);
+    m_listClients.append(udpSession);
     m_lockUdpSockets.unlock();
-    
-    m_isRecvData.store(true); // 设置为接收数据状态
 
     return true;
 }
 
 /* 删除一个会话 */
-bool RTPOneRoadThread::removeUdpSession(const RtpSendClientInfo_t& udpSession)
+bool RTPOneRoadThread::removeUdpSession(QString clientIP, quint16 clientPort)
 {
-    std::lock_guard<QMutex> lock(m_lockUdpSockets);
-    for(auto it = m_listUdpSockets.begin(); it != m_listUdpSockets.end(); ++it)
+    std::lock_guard<std::mutex> lock(m_lockUdpSockets);
+    for(auto it = m_listClients.begin(); it != m_listClients.end(); ++it)
     {
-        if(it->clientIP == udpSession.clientIP && it->clientPort == udpSession.clientPort)
+        if(it->clientIP == clientIP && it->clientPort == clientPort)
         {
-            if(it->udpSocket != nullptr)
-            {
-                it->udpSocket->close();
-                delete it->udpSocket;
-                it->udpSocket = nullptr;
-            }
+            // if(it->udpSocket != nullptr)
+            // {
+            //     it->udpSocket->close();
+            //     delete it->udpSocket;
+            //     it->udpSocket = nullptr;
+            // }
             SPDLOG_LOGGER_INFO(m_logger, "{} 删除UDP会话: {}:{}", m_logBase, it->clientIP.toStdString(), it->clientPort);
             // 从列表中删除该会话
-            m_listUdpSockets.erase(it);
+            m_listClients.erase(it);
             break;
         }
     }
     /* 如果列表为空,就不再接收数据 */
-    if(m_listUdpSockets.isEmpty())
+    if(m_listClients.isEmpty())
     {
-        m_isRecvData.store(false);
-        SPDLOG_LOGGER_DEBUG(m_logger, "{} UDP会话列表为空,不再接收音频原始数据", m_logBase);
         /* 清空环形队列 */
         while(m_ringQueue.QueueSize() > 0)
         {
@@ -133,6 +128,9 @@ bool RTPOneRoadThread::removeUdpSession(const RtpSendClientInfo_t& udpSession)
                 data = nullptr;
             }
         }
+        m_udpState = eUDPState::eUDP_Closed;
+        
+        SPDLOG_LOGGER_DEBUG(m_logger, "{} UDP会话列表为空,不再接收音频原始数据", m_logBase);
     }
 
     return true;
@@ -153,10 +151,13 @@ void RTPOneRoadThread::task()
     m_sendTimer.setInterval(10); // 每10毫秒发送一次数据
     QEventLoop::connect(&m_sendTimer, &QTimer::timeout, this, &RTPOneRoadThread::do_timerSendData);
     m_sendTimer.start();
+
+    m_isRunning.store(true); // 设置为运行状态
     /* 将线程控制权交给Qt的事件循环 */
     m_eventLoop.exec();
     /* 清空数据 */
     clearData();
+    m_isRunning.store(false);
     SPDLOG_LOGGER_WARN(m_logger, "➢ {} RTP发送数据线程结束 ", m_logBase);
 }
 
@@ -164,10 +165,10 @@ void RTPOneRoadThread::task()
 /* 初始化数据 */
 bool RTPOneRoadThread::initData()
 {
-    m_listUdpSockets.clear();
+    m_listClients.clear();
     m_isRecvData.store(false);
 
-    m_ringQueue.setQueueCapacity(512);
+    m_ringQueue.setQueueCapacity(60);
     m_ringQueue.setDefaultValue(nullptr);
 
     return true;
@@ -178,17 +179,17 @@ void RTPOneRoadThread::clearData()
 {
     m_isRecvData.store(false); // 设置为不接收数据状态
     /* 清空UDP会话列表 */
-    std::lock_guard<QMutex> lock(m_lockUdpSockets);
-    for(auto& session : m_listUdpSockets)
-    {
-        if(session.udpSocket != nullptr)
-        {
-            session.udpSocket->close();
-            delete session.udpSocket;
-            session.udpSocket = nullptr;
-        }
-    }
-    m_listUdpSockets.clear();
+    std::lock_guard<std::mutex> lock(m_lockUdpSockets);
+    // for(auto& session : m_listClients)
+    // {
+        // if(session.udpSocket != nullptr)
+        // {
+        //     session.udpSocket->close();
+        //     delete session.udpSocket;
+        //     session.udpSocket = nullptr;
+        // }
+    // }
+    m_listClients.clear();
     
     /* 清空环形队列 */
     while(m_ringQueue.QueueSize() > 0)
@@ -202,6 +203,58 @@ void RTPOneRoadThread::clearData()
     }
 }
 
+
+/* 处理UDP状态 */
+bool RTPOneRoadThread::processUdpState()
+{
+    if(m_udpSocket == nullptr)
+    {
+        m_udpSocket = new QUdpSocket();
+        if(m_udpSocket == nullptr)
+        {
+            SPDLOG_LOGGER_ERROR(m_logger, "{} 创建UDP套接字失败", m_logBase);
+            return false;
+        }
+        m_udpSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1); // 设置低延迟选项
+        connect(m_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(do_udpError(QAbstractSocket::SocketError)));
+    }
+
+    if(m_udpState == eUDPState::eUDP_Init)
+    {
+        if(!m_udpSocket->bind(QHostAddress(m_localIP), m_localPort))
+        {
+            SPDLOG_LOGGER_ERROR(m_logger, "{} 绑定UDP套接字失败: {}:{}", m_logBase, m_localIP.toStdString(), m_localPort);
+            return false;
+        }
+        SPDLOG_LOGGER_INFO(m_logger, "{} 绑定UDP套接字到本地IP: {}:{}", m_logBase, 
+                            m_udpSocket->localAddress().toString().toStdString(), m_udpSocket->localPort());
+        /* 设置为接收数据状态 */
+        m_isRecvData.store(true);
+        m_udpState = eUDPState::eUDP_Opened;
+    }
+    else if(m_udpState == eUDPState::eUDP_Closed)
+    {
+        /* 关闭接收 */
+        m_isRecvData.store(false);
+        /* 关闭UDP占用的本地端口 */
+        if(m_udpSocket != nullptr)
+        {
+            m_udpSocket->close();
+        }
+        m_udpState = eUDPState::eUDP_None;
+        /* 清空连接信息 */
+        m_listClients.clear();
+        m_localIP.clear();
+        m_localPort = -1;
+        emit signal_udpClosed(m_threadInfo.cardRoadInfo.nSoundCardNum, 
+                              m_threadInfo.cardRoadInfo.roadInfo.nRoadNum, m_localPort);
+    
+    }
+
+    return true;
+}
+
+
 /**
  * @brief 发送数据,先获取一个缓冲区的数据,获取到后就遍历所有的UDP会话,将这个缓冲区所有的数据都发送出去
  *        然后紧接着获取第二个缓冲区的数据,也一起发送出去
@@ -211,8 +264,9 @@ void RTPOneRoadThread::clearData()
  */
 bool RTPOneRoadThread::sendData()
 {
-    while(m_ringQueue.QueueSize() > 0 && m_isRunning.load())
+    while(m_ringQueue.QueueSize() > 0)
     {
+        // SPDLOG_LOGGER_TRACE(m_logger, "{} 发送音频数据", m_logBase);
         auto data = m_ringQueue.front_pop_noBlock();
         if(data == nullptr)
         {
@@ -223,25 +277,28 @@ bool RTPOneRoadThread::sendData()
             delete data;
             continue;
         }
-        std::lock_guard<QMutex> lock(m_lockUdpSockets);
+        std::lock_guard<std::mutex> lock(m_lockUdpSockets);
         /* 遍历所有的UDP会话,发送数据 */
-        for(const auto& session : m_listUdpSockets)
+        for(const auto& session : m_listClients)
         {
-            if(session.udpSocket == nullptr || !session.udpSocket->isValid())
-            {
-                SPDLOG_LOGGER_WARN(m_logger, "{} 无效的UDP套接字: {}:{}", m_logBase, session.clientIP.toStdString(), session.clientPort);
-                continue;
-            }
+            // if(session.udpSocket == nullptr || !session.udpSocket->isValid())
+            // {
+            //     SPDLOG_LOGGER_WARN(m_logger, "{} 无效的UDP套接字: {}:{}", m_logBase, session.clientIP.toStdString(), session.clientPort);
+            //     continue;
+            // }
 
-            qint64 bytesSent = session.udpSocket->writeDatagram(data->pData, data->dataSize,
+            // qint64 bytesSent = session.udpSocket->writeDatagram(data->pData, data->dataSize,
+                                                                // QHostAddress(session.clientIP), session.clientPort);
+            qint64 bytesSent = m_udpSocket->writeDatagram(QByteArray(reinterpret_cast<const char*>(data->pData), data->dataSize),
                                                                 QHostAddress(session.clientIP), session.clientPort);
             if(bytesSent == -1)
             {
                 SPDLOG_LOGGER_ERROR(m_logger, "{} 发送数据失败: {}:{}", m_logBase, session.clientIP.toStdString(), session.clientPort);
-            }
-            else
+            } else
             {
-                SPDLOG_LOGGER_DEBUG(m_logger, "{} 发送数据成功: {}:{} 大小: {}", m_logBase, session.clientIP.toStdString(), session.clientPort, bytesSent);
+                SPDLOG_LOGGER_TRACE(m_logger, "{} 发送数据成功: {}:{}, 本地IP: {}:{}, 大小: {}", m_logBase, 
+                        session.clientIP.toStdString(), session.clientPort, 
+                        m_udpSocket->localAddress().toString().toStdString(), m_udpSocket->localPort(), bytesSent);
             }
         }
         /* 发送完数据后,删除这个数据 */
@@ -255,13 +312,38 @@ bool RTPOneRoadThread::sendData()
 /* 定时发送数据 */
 void RTPOneRoadThread::do_timerSendData()
 {
-    if(!m_isRecvData)
+    if(m_udpState == eUDPState::eUDP_None)
     {
         return;
     }
-    /* 先初始化UDP */
-
+    
+    processUdpState();
+    
+    
     /* 发送数据 */
     sendData();
 }
 
+/* UDP错误槽函数 */
+void RTPOneRoadThread::do_udpError(QAbstractSocket::SocketError socketError)
+{
+    auto senderSocket = qobject_cast<QUdpSocket*>(sender());
+    // SPDLOG_LOGGER_ERROR(m_logger, "{} UDP套接字错误: {}", m_logBase, static_cast<int>(socketError));
+    /* 这里可以根据需要处理不同的错误 */
+    if(socketError == QAbstractSocket::SocketError::SocketTimeoutError)
+    {
+        SPDLOG_LOGGER_WARN(m_logger, "{} UDP连接超时", m_logBase);
+    } else
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "{} UDP套接字发生错误: {}", m_logBase, static_cast<int>(socketError));
+    }
+    /* 获取UDP远程连接的IP和端口 */
+    if(senderSocket != nullptr)
+    {
+        QString remoteIP = senderSocket->peerAddress().toString();
+        quint16 remotePort = senderSocket->peerPort();
+        /* 从队列中移除这个会话 */
+        removeUdpSession(remoteIP, remotePort);
+    }
+}
+

+ 27 - 12
RTPServer/RtpOneRoadThread.h

@@ -8,24 +8,25 @@
 
 #include <QEventLoop>
 #include <QUdpSocket>
-#include <QMutex>
 #include <QTimer>
 #include <qobject.h>
 #include <qobjectdefs.h>
+#include <mutex>
 
 
 #include "Rtpcommon.h"
 
 
 /**
- * 一路RTP数据发送服务,一路表示的是一个录音通道,也是一个子线程
- * 工作方式:
- *     1、这个其实也是一个录音处理线程,分派录音的线程给这里传递数据
- *     2、这个线程有一个UDP会话列表,会挨个往监听的客户端发送数据,如果客户端列表为空
- *        则不再接收原始音频数据
- *     3、这个线程在打开一个录音通道后就会创建,在后台运行,直到RTP服务设置进来一个UDP会话
- *     4、这个线程类会被 RTPServer 线程和 AssignSrcData 线程调用
- * 
+    一路RTP数据发送服务,一路表示的是一个录音通道,也是一个子线程
+    工作方式:
+        1、这个其实也是一个录音处理线程,分派录音的线程给这里传递数据
+        2、这个线程有一个UDP会话列表,会挨个往监听的客户端发送数据,如果客户端列表为空
+            则不再接收原始音频数据
+        3、这个线程在打开一个录音通道后就会创建,在后台运行,直到RTP服务设置进来一个UDP会话
+        4、这个线程类会被 RTPServer 线程和 AssignSrcData 线程调用
+        5、这个线程添加会话后会占用一个本地的UDP端口,所有会话移除掉后会释放这个端口
+ 
  */
 class RTPOneRoadThread : public QObject, public BaseRecordThread
 {
@@ -43,7 +44,11 @@ public:
     /* 添加一个UDP会话 */
     bool addUdpSession(const RtpSendClientInfo_t& udpSession);
     /* 删除一个会话 */
-    bool removeUdpSession(const RtpSendClientInfo_t& udpSession);
+    bool removeUdpSession(QString clientIP, quint16 clientPort);
+
+signals:
+    /* 一个UDP关闭了,通知RTP服务,释放掉了一个本地端口 */
+    void signal_udpClosed(int soundCardNum, int roadNum, int localPort);
 
 protected:
     /* 任务函数 */
@@ -53,21 +58,31 @@ protected:
     /* 清除数据*/
     void clearData() override;
 
+    /* 处理UDP状态 */
+    bool processUdpState();
     /* 发送数据 */
     bool sendData();
 
 private slots:
     /* 定时发送数据 */
     void do_timerSendData();
+    /* UDP错误槽函数 */
+    void do_udpError(QAbstractSocket::SocketError socketError);
+    
 
 private:
     /* 事件循环 */
     QEventLoop m_eventLoop;
     QTimer m_sendTimer;                             /* 定时器,用于定时发送数据 */
+
+    QUdpSocket* m_udpSocket = nullptr;              /* 用于发送数据的UDP套接字 */
+    QString m_localIP;                              /* 本地IP */
+    int m_localPort = -1;                           /* 本地发送数据的UDP端口 */
+    eUDPState m_udpState = eUDPState::eUDP_None;    /* UDP状态 */
     /* UDP会话列表 */
     std::atomic_bool m_isRecvData = false;          /* 是否接收数据的标志 */
-    QMutex m_lockUdpSockets;                        /* 用于保护UDP套接字列表的读写锁 */
-    QList<RtpSendClientInfo_t> m_listUdpSockets;    /* 用于发送数据的UDP套接字列表 */
+    std::mutex m_lockUdpSockets;                    /* 用于保护UDP套接字列表的读写锁 */
+    QList<RtpSendClientInfo_t> m_listClients;       /* 用于发送数据的客户端信息列表 */
 
     RingQueue<AudioSrcData*> m_ringQueue;           /* 用于存储音频数据的环形队列 */
 

+ 115 - 19
RTPServer/RtpServer.cpp

@@ -1,7 +1,11 @@
 #include "RtpServer.h"
 
 #include <QTcpSocket>
+#include <QNetworkInterface>
+#include <QHostAddress>
+
 #include "ThreadManager.h"
+#include "ThreadCompareItemManager.h"
 
 
 RTPServer::RTPServer(QObject *parent)
@@ -49,6 +53,9 @@ bool RTPServer::thread_task(int port)
         return false;
     }
 
+    /* 查找到出口IP */
+    m_localIP = "192.1.2.118";
+
     /* 开启事件循环 */
     connect(m_tcpServer, &QTcpServer::newConnection, this, &RTPServer::do_newConnection);
     connect(m_tcpServer, &QTcpServer::acceptError, this, &RTPServer::do_error);
@@ -119,34 +126,33 @@ void RTPServer::do_receiveMessage()
         SPDLOG_LOGGER_ERROR(m_logger, "Failed to cast sender to QTcpSocket.");
         return;
     }
+    QString recvIP = clientSocket->peerAddress().toString();
+    quint16 recvPort = clientSocket->peerPort();
 
     QByteArray data = clientSocket->readAll();
     if (data.isEmpty()) 
     {
-        SPDLOG_LOGGER_WARN(m_logger, "Received empty data from client: {}", clientSocket->peerAddress().toString().toStdString());
+        SPDLOG_LOGGER_WARN(m_logger, "Received empty data from client: {}", recvIP.toStdString());
         return;
     }
 
     SPDLOG_LOGGER_TRACE(m_logger, "RTPServer从: {}:{} 接收到原始数据: {}, 大小: {}", 
-                        clientSocket->peerAddress().toString().toStdString(), clientSocket->peerPort(), 
-                        data.toHex().toStdString(), data.size());
+                        recvIP.toStdString(), recvPort, data.toHex().toStdString(), data.size());
     // SPDLOG_LOGGER_TRACE(m_logger, "RTPServer从: {}:{} 接收到数据: {}", 
     //                     clientSocket->peerAddress().toString().toStdString(), clientSocket->peerPort(), 
     //                     QString(data).toStdString());
     // return;
 
-    if(data.size() < sizeof(RtpRecvClientInfo_t)) 
+    if(static_cast<unsigned long>(data.size()) < sizeof(RtpRecvClientInfo_t)) 
     {
         SPDLOG_LOGGER_WARN(m_logger, "从客户端接收到的数据大小: {} 低于应有的大小 {}", 
-                            clientSocket->peerAddress().toString().toStdString(), sizeof(RtpRecvClientInfo_t));
+                            data.size(), sizeof(RtpRecvClientInfo_t));
         return;
     }
 
 
     /* 处理接收到的数据 */
     RtpSendClientInfo_t sendInfo;
-    QString clientIP = clientSocket->peerAddress().toString();
-    quint16 clientPort = clientSocket->peerPort();
     RtpRecvClientInfo_t* recvInfo = reinterpret_cast<RtpRecvClientInfo_t*>(data.data());
 
     SPDLOG_LOGGER_DEBUG(m_logger, "客户端信息: ");
@@ -158,10 +164,20 @@ void RTPServer::do_receiveMessage()
     SPDLOG_LOGGER_DEBUG(m_logger, "    包类型: {}", recvInfo->type);
 
     /* 解析客户端发送的信息 */
-    sendInfo.clientIP = clientIP;
-    sendInfo.clientPort = clientPort;
-    /* 查找本地可用的UDP端口,这里先使用10010测试 */
-    sendInfo.localPort = 10010;
+    sendInfo.clientIP = recvInfo->clientIP;
+    sendInfo.clientPort = recvInfo->clientPort;
+    sendInfo.compareItemID = recvInfo->compareItemID;
+    sendInfo.compareItemRoadNum = recvInfo->compareItemRoadNum;
+    /* 根据对比项ID和通道编号查找到使用到的声卡通道编号 */
+    SoundCardRoadInfo_t roadInfo = CompareItemManager.getSoundCardRoadInfo(sendInfo.compareItemID, sendInfo.compareItemRoadNum);
+    if(roadInfo.nSoundCardNum < 0 || roadInfo.roadInfo.nRoadNum < 0)
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "无法获取对比项 {} 通道 {} 的声卡信息", 
+                            sendInfo.compareItemID, sendInfo.compareItemRoadNum);
+        return;
+    }
+    sendInfo.SoundCardNum = roadInfo.nSoundCardNum;
+    sendInfo.SoundCardRoadNum = roadInfo.roadInfo.nRoadNum;
 
     switch(recvInfo->type)
     {
@@ -175,36 +191,116 @@ void RTPServer::do_receiveMessage()
             handleLogout(sendInfo);
             break;
         default:
-            SPDLOG_LOGGER_WARN(m_logger, "来自客户端为止的请求 {}:{}", sendInfo.clientIP.toStdString(), sendInfo.clientPort);
+            SPDLOG_LOGGER_WARN(m_logger, "来自客户端未知的请求:{}, {}:{}", recvInfo->type, sendInfo.clientIP.toStdString(), sendInfo.clientPort);
             break;
     }
 }
 
+/* 接收到释放端口的信号 */
+void RTPServer::do_udpClosed(int soundCardNum, int roadNum, int localPort)
+{
+    SPDLOG_LOGGER_DEBUG(m_logger, "接收到释放本地端口的信号: 声卡编号: {}, 通道编号: {}, 本地端口: {}", 
+                        soundCardNum, roadNum, localPort);
+    
+    for(auto it = m_mapSoundCardRoadPorts.begin(); it != m_mapSoundCardRoadPorts.end(); ++it)
+    {
+        if(it.key().nSoundCardNum == soundCardNum && it.key().nRoadNum == roadNum)
+        {
+            // SPDLOG_LOGGER_DEBUG(m_logger, "从声卡通道列表中删除: 声卡编号: {}, 通道编号: {}, 本地端口: {}", 
+            //                     soundCardNum, roadNum, localPort);
+            m_mapSoundCardRoadPorts.remove(it.key());
+            break;
+        }
+    }
+    
+}
+
 /* 处理登录请求 */
 void RTPServer::handleLogin(RtpSendClientInfo_t& clientInfo)
 {
-    /* 获取RTP发送线程 */
-    
+    SPDLOG_LOGGER_TRACE(m_logger, "处理登录请求: {}:{}", clientInfo.clientIP.toStdString(), clientInfo.clientPort);
+    /* 查找本地可用的UDP端口,这里先使用10010测试 */
+    clientInfo.localPort = 10010;
+    clientInfo.localIP = m_localIP;
+    /* 查找RTP发送线程 */
+    auto pThread = ThreadMan.getRtpSendThread(clientInfo.SoundCardNum, clientInfo.SoundCardRoadNum);
+    if(pThread == nullptr)
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "没有找到对应的RTP发送线程,声卡编号: {}, 通道编号: {}", 
+                            clientInfo.SoundCardNum, clientInfo.SoundCardRoadNum);
+        return;
+    }
+    /* 设置客户端信息 */
+    if(!pThread->addUdpSession(clientInfo))
+    {
+        return;
+    }
+    connect(pThread, &RTPOneRoadThread::signal_udpClosed, this, &RTPServer::do_udpClosed);
+    /* 将本地端口添加到声卡通道列表中 */
+    for(auto it = m_mapSoundCardRoadPorts.begin(); it != m_mapSoundCardRoadPorts.end(); ++it)
+    {
+        if(it.key().nSoundCardNum == clientInfo.SoundCardNum && it.key().nRoadNum == clientInfo.SoundCardRoadNum)
+        {
+            return;
+        }
+    }
+    SoundCardRoadKey_t roadInfo;
+    roadInfo.nSoundCardNum = clientInfo.SoundCardNum;
+    roadInfo.nRoadNum = clientInfo.SoundCardRoadNum;
+    m_mapSoundCardRoadPorts.insert(roadInfo, clientInfo.localPort);
 }
 
 /* 处理心跳请求 */
 void RTPServer::handleHeartbeat(RtpSendClientInfo_t& clientInfo)
 {
-
+    SPDLOG_LOGGER_TRACE(m_logger, "处理心跳请求: {}:{}", clientInfo.clientIP.toStdString(), clientInfo.clientPort);
+    
 }
 
 /* 处理注销请求 */
 void RTPServer::handleLogout(RtpSendClientInfo_t& clientInfo)
 {
+    SPDLOG_LOGGER_TRACE(m_logger, "处理注销请求: {}:{}", clientInfo.clientIP.toStdString(), clientInfo.clientPort);
 
+    /* 查找RTP发送线程 */
+    auto pThread = ThreadMan.getRtpSendThread(clientInfo.SoundCardNum, clientInfo.SoundCardRoadNum);
+    if(pThread == nullptr)
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "没有找到对应的RTP发送线程,声卡编号: {}, 通道编号: {}", 
+                            clientInfo.SoundCardNum, clientInfo.SoundCardRoadNum);
+        return;
+    }
+    /* 删除UDP会话 */
+    if(!pThread->removeUdpSession(clientInfo.clientIP, clientInfo.clientPort))
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "删除UDP会话失败: {}:{}", clientInfo.clientIP.toStdString(), clientInfo.clientPort);
+        return;
+    }
 }
 
 
-/* 获取发送UDP数据的指针 */
-bool RTPServer::getSendUdpSocketPtr(SoundCardRoadInfo_t roadID)
+
+/* 查找本地出口IP,适用于单个出口IP,如果电脑上有多个网口,需要手动指定IP */
+QString RTPServer::findLocalIP()
 {
-    
-    return true;
+    QString localIP;
+    QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
+    for (const QHostAddress& address : addresses) 
+    {
+        if (address.protocol() == QAbstractSocket::IPv4Protocol && !address.isLoopback()) 
+        {
+            if(address == QHostAddress::AnyIPv4)
+            {
+                continue; // 跳过
+            }
+            localIP = address.toString();
+            SPDLOG_LOGGER_INFO(m_logger, "找到本地出口IP: {}", localIP.toStdString());
+            return localIP;
+        }
+    }
+
+    SPDLOG_LOGGER_ERROR(m_logger, "未找到有效的本地出口IP");
+    return localIP;
 }
 
 

+ 8 - 5
RTPServer/RtpServer.h

@@ -52,6 +52,9 @@ private slots:
     /* 接收消息 */
     void do_receiveMessage();
 
+    /* 接收到释放端口的信号 */
+    void do_udpClosed(int soundCardNum, int roadNum, int localPort);
+
 private:
     /* 处理登录请求 */
     void handleLogin(RtpSendClientInfo_t& clientInfo);
@@ -60,9 +63,8 @@ private:
     /* 处理注销请求 */
     void handleLogout(RtpSendClientInfo_t& clientInfo);
 
-    /* 获取发送UDP数据的指针 */
-    bool getSendUdpSocketPtr(SoundCardRoadInfo_t roadInfo);
-
+    /* 查找本地出口IP,适用于单个出口IP,如果电脑上有多个网口,需要手动指定IP */
+    QString findLocalIP();
 
 private:
     std::shared_ptr<spdlog::logger> m_logger = nullptr;     /* 日志记录器 */
@@ -71,10 +73,11 @@ private:
     QTcpServer *m_tcpServer;                /* TCP服务器对象 */
     int m_port;                             /* 监听端口 */
 
+    QString m_localIP;                      /* 本地IP地址,给UDP使用,指定本地出口IP,防止自动连接到 0.0.0.0 上 */
     QList<QTcpSocket*> m_listClients;       /* 客户端连接列表,用于管理多个客户端连接 */
 
-    /* 录音通道线程列表,键为录音通道ID,值为对应的RTP线程对象 */
-    QMap<SoundCardRoadInfo_t, RTPOneRoadThread*> m_mapRoadThreads;
+    /* 录音通道线程列表,键为录音通道ID,值为通道使用到的本地端口 */
+    QMap<SoundCardRoadKey_t, int> m_mapSoundCardRoadPorts;
 };
 
 

+ 3 - 0
RTPServer/Rtpcommon.cpp

@@ -48,11 +48,14 @@ RtpSendClientInfo_t& RtpSendClientInfo_t::operator=(const RtpSendClientInfo_t& o
 {
     if (this != &other)
     {
+        localIP = other.localIP;
         localPort = other.localPort;
         clientIP = other.clientIP;
         clientPort = other.clientPort;
         compareItemID = other.compareItemID;
         compareItemRoadNum = other.compareItemRoadNum;
+        SoundCardNum = other.SoundCardNum;
+        SoundCardRoadNum = other.SoundCardRoadNum;
         // udpSocket = other.udpSocket;     /* 不拷贝udp套接字 */ 
     }
     return *this;

+ 21 - 5
RTPServer/Rtpcommon.h

@@ -2,7 +2,21 @@
 #define _RTPCOMMON_H_
 
 #include <QList>
-#include <QUdpSocket>
+
+
+/**
+ * @brief UDP的状态
+ * 
+ */
+enum class eUDPState
+{
+    eUDP_None = 0,              /* 未知状态 */
+    eUDP_Init,                  /* 初始化 */
+    eUDP_Opened,                /* 已打开 */
+    eUDP_Closed,                /* 已关闭 */
+    eUDP_Error                  /* 错误状态 */
+};
+
 
 /**
     接收到的客户端结构体
@@ -35,19 +49,21 @@ struct RtpReplyClientInfo_t
 
 
 /**
- * @brief RTP通道包含带有UDP信息的客户端信息,这个是传递给RTP发送线程的
+ * @brief 客户端信息结构体
  * 
  */
 struct RtpSendClientInfo_t
 {
+    QString localIP;                    /* 本地IP */
     int localPort;                      /* 本地发送数据的UDP端口 */
     QString clientIP;                   /* 客户端IP */
     int clientPort;                     /* 客户端接收数据的UDP端口 */
-    // int sessionID;                      /* 连接的SessionID */
-    // QString sessionName;                /* 客户端名称 */
+    
     int compareItemID;                  /* 对比项ID */
     int compareItemRoadNum;             /* 对比项通道号 */
-    QUdpSocket* udpSocket = nullptr;    /* 用于发送数据的UDP套接字 */
+    int SoundCardNum;                   /* 声卡编号 */
+    int SoundCardRoadNum;               /* 声卡通道编号 */
+
 
     RtpSendClientInfo_t();
     RtpSendClientInfo_t(const RtpSendClientInfo_t& other);

+ 24 - 0
Server/ThreadManager/ThreadCompareItemManager.cpp

@@ -18,6 +18,7 @@ void ThreadCompareItemManager::thread_compareItem(CalculateThreadInfo_t threadIn
         return;
     }
     CompareItemManager.addCompareItemThread(pThread);
+    
     /* 启动线程,就会一直阻塞在这里了 */
     pThread->threadTask();
 }
@@ -121,6 +122,29 @@ void ThreadCompareItemManager::addCompareItemThread(CompareItemThread* pThread)
     SPDLOG_LOGGER_INFO(m_logger, "添加对比项线程成功,ID: {}", compareItemID);
 }
 
+/* 通过对比项ID和通道ID获取声卡通道信息 */
+SoundCardRoadInfo_t ThreadCompareItemManager::getSoundCardRoadInfo(int compareItemID, int roadNum)
+{
+    std::lock_guard<std::mutex> lock(m_mutexCompareItemThreads);
+    auto it  = m_mapThreads.find(compareItemID);
+    if(it == m_mapThreads.end())
+    {
+        SPDLOG_LOGGER_WARN(m_logger, "对比项线程不存在,ID: {}", compareItemID);
+        return SoundCardRoadInfo_t();
+    }
+    auto compareInfo = it.value()->getThreadInfo().compareItemInfo;
+    SoundCardRoadInfo_t roadInfo;
+    for(const auto& road : compareInfo.mapRoad)
+    {
+        if(road.nCompareRoadNum == roadNum)
+        {
+            roadInfo = road.scRoadInfo;
+            break;
+        }
+    }
+    return roadInfo;
+}
+
 /* 更新基础设置信息,如数据库设置,噪音参数等 */
 bool ThreadCompareItemManager::updateBaseSettings()
 {

+ 5 - 3
Server/ThreadManager/ThreadCompareItemManager.h

@@ -28,15 +28,17 @@ public:
     void thread_CompareItemManager();
     /* 添加对比项实例 */
     void addCompareItemThread(CompareItemThread* pThread);
+    /* 通过对比项ID和通道ID获取声卡通道信息 */
+    SoundCardRoadInfo_t getSoundCardRoadInfo(int compareItemID, int roadNum);
+    
 
     /*  给对比项套一层壳,这个函数就是新的线程,在里面new出新的对比项实例,防止Qt报线程归属权错误
         在函数中将对比项实例插入到线程管理器中 */
     static void thread_compareItem(CalculateThreadInfo_t threadInfo);
 
+private:
     /* 更新基础设置信息,如数据库设置,噪音参数等 */
     bool updateBaseSettings();
-    
-private:
     /* 处理对比项信息,新获取的和已有的对比 */
     void processCompareItemInfo(QList<CompareItemInfo_t>& createList,
                                 QList<CompareItemInfo_t>& updateList,
@@ -58,7 +60,7 @@ private:
 
 
     std::mutex m_mutexCompareItemThreads;       /* 对比项线程的互斥锁 */
-    QList<CompareItemInfo_t> m_listNewItems;    /* 对比项列表 */
+    QList<CompareItemInfo_t> m_listNewItems;    /* 对比项列表,从数据库获取到的新的列表 */
     QMap<int, CompareItemThread*> m_mapThreads; /* 对比项线程列表,key是对比项ID,value是对应的线程指针 */
 
 };

+ 21 - 18
Server/ThreadManager/ThreadManager.cpp

@@ -463,7 +463,7 @@ void ThreadManager::destroyeRecordThread()
 
 
 /* 查找录音线程 */
-BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID, int recordID)
+BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID, int roadID)
 {
     switch(type)
     {
@@ -472,7 +472,7 @@ BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID,
                 std::lock_guard<std::mutex> lock(m_mutexRecordThreads);
                 for (auto& pThread : m_recordThreads)
                 {
-                    if (pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID &&
+                    if (pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID &&
                         pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID)
                     {
                         return pThread;
@@ -486,7 +486,7 @@ BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID,
                 for (auto& pThread : m_createWAVThreads)
                 {
                     if (pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID)
                     {
                         return pThread;
                     }
@@ -499,7 +499,7 @@ BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID,
                 for (auto& pThread : m_createDBThreads)
                 {
                     if (pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID)
                     {
                         return pThread;
                     }
@@ -512,7 +512,7 @@ BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID,
                 for (auto& pThread : m_createLongWAVThreads)
                 {
                     if (pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID)
                     {
                         return pThread;
                     }
@@ -525,7 +525,7 @@ BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID,
                 for (auto& pThread : m_assignSrcDataThreads)
                 {
                     if (pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID)
                     {
                         return pThread;
                     }
@@ -538,7 +538,7 @@ BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID,
                 for (auto& pThread : m_rtpSendThreads)
                 {
                     if (pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+                        pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID)
                     {
                         return pThread;
                     }
@@ -556,13 +556,13 @@ BaseRecordThread* ThreadManager::findRecordThread(EThreadType type, int cardID,
 
 
 /* 获取创建WAV线程指针 */
-CreateWAVThread* ThreadManager::getCreateWAVThread(int cardID, int recordID)
+CreateWAVThread* ThreadManager::getCreateWAVThread(int cardID, int roadID)
 {
     std::lock_guard<std::mutex> lock(m_mutexCreateWAVThreads);
     for(auto& pThread : m_createWAVThreads)
     {
         if(pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-           pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+           pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID)
         {
             return dynamic_cast<CreateWAVThread*>(pThread);
         }
@@ -571,13 +571,13 @@ CreateWAVThread* ThreadManager::getCreateWAVThread(int cardID, int recordID)
 }
 
 /* 获取创建音量值的线程 */
-CreateDBThread* ThreadManager::getCreateDBThread(int cardID, int recordID)
+CreateDBThread* ThreadManager::getCreateDBThread(int cardID, int roadID)
 {
     std::lock_guard<std::mutex> lock(m_mutexCreateDBThreads);
     for(auto& pThread : m_createDBThreads)
     {
         if(pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-           pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+           pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID)
         {
             return dynamic_cast<CreateDBThread*>(pThread);
         }
@@ -586,13 +586,14 @@ CreateDBThread* ThreadManager::getCreateDBThread(int cardID, int recordID)
 }
 
 /* 获取发送Rtp数据的线程 */
-RTPOneRoadThread* ThreadManager::getRtpSendThread(int cardID, int recordID)
+RTPOneRoadThread* ThreadManager::getRtpSendThread(int cardID, int roadID)
 {
     std::lock_guard<std::mutex> lock(m_mutexRtpSendThreads);
     for(auto& pThread : m_rtpSendThreads)
     {
-        if(pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-           pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+        const auto& threadInfo = pThread->getThreadInfo();
+        if(threadInfo.cardRoadInfo.nSoundCardNum == cardID &&
+           threadInfo.cardRoadInfo.roadInfo.nRoadNum == roadID)
         {
             return dynamic_cast<RTPOneRoadThread*>(pThread);
         }
@@ -601,13 +602,13 @@ RTPOneRoadThread* ThreadManager::getRtpSendThread(int cardID, int recordID)
 }
 
 /* 获取录制报警文件的线程 */
-CreateLongFileThread* ThreadManager::getCreateLongFileThread(int cardID, int recordID)
+CreateLongFileThread* ThreadManager::getCreateLongFileThread(int cardID, int roadID)
 {
     std::lock_guard<std::mutex> lock(m_mutexCreateLongWAVThreads);
     for(auto& pThread : m_createLongWAVThreads)
     {
         if(pThread->getThreadInfo().cardRoadInfo.nSoundCardNum == cardID &&
-           pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == recordID)
+           pThread->getThreadInfo().cardRoadInfo.roadInfo.nRoadNum == roadID)
         {
             return dynamic_cast<CreateLongFileThread*>(pThread);
         }
@@ -906,8 +907,10 @@ void ThreadManager::thread_RTPSend(RecordThreadInfo_t& threadInfo)
         return;
     }
     /* 先加入队列,再开启线程 */
-    std::lock_guard<std::mutex> lock(ThreadMan.m_mutexRtpSendThreads);
+    ThreadMan.m_mutexRtpSendThreads.lock();
     ThreadMan.m_rtpSendThreads.push_back(pRtpSendThread);
-    // pRtpSendThread->threadTask();
+    ThreadMan.m_mutexRtpSendThreads.unlock();
+    
+    pRtpSendThread->threadTask();
 }
 

+ 5 - 5
Server/ThreadManager/ThreadManager.h

@@ -67,17 +67,17 @@ public:
      * 获取录音线程,录音线程是统一创建的,有一路录音通道,就会有所有的对应的录音处理线程
      * -------------------------------------------------------------------------------------------- */
     /* 查找录音线程,给原始音频分派数据的线程使用的 */
-    BaseRecordThread* findRecordThread(EThreadType type, int cardID, int recordID);
+    BaseRecordThread* findRecordThread(EThreadType type, int cardID, int roadID);
     
     /* 这三个线程给对比项以及其附属的计算线程使用的 */
     /* 获取创建WAV线程指针 */
-    CreateWAVThread* getCreateWAVThread(int cardID, int recordID);
+    CreateWAVThread* getCreateWAVThread(int cardID, int roadID);
     /* 获取创建音量值的线程 */
-    CreateDBThread* getCreateDBThread(int cardID, int recordID);
+    CreateDBThread* getCreateDBThread(int cardID, int roadID);
     /* 获取发送Rtp数据的线程 */
-    RTPOneRoadThread* getRtpSendThread(int cardID, int recordID);
+    RTPOneRoadThread* getRtpSendThread(int cardID, int roadID);
     /* 获取录制报警文件的线程 */
-    CreateLongFileThread* getCreateLongFileThread(int cardID, int recordID);
+    CreateLongFileThread* getCreateLongFileThread(int cardID, int roadID);
 
     /* -------------------------------------------------------------------------------------------
      * 获取计算线程,如果该线程不存在则创建该线程

+ 4 - 1
common/DataManager/CompareItemData.h

@@ -5,7 +5,10 @@
 #include "spdlog/spdlog.h"
 
 
-/* 对比项数据,管理对比项所有的数据 */
+/* 
+    对比项数据,管理对比项所有的数据 
+    服务里没用到,只在设置动态库中使用到了
+ */
 
 #define CIData CompareItemDataManager::instance()
 class CompareItemDataManager

+ 20 - 0
common/GlobalInfo/GlobalVariable.h

@@ -125,6 +125,26 @@ struct SoundCardRoadInfo_t
     bool operator<(const SoundCardRoadInfo_t &other) const;
 };
 
+/* 使用声卡通道最为Key的结构体 */
+struct SoundCardRoadKey_t
+{
+    int nSoundCardNum = -1;             /* 声卡编号 */
+    int nRoadNum = -1;                  /* 通道编号 */
+
+    bool operator==(const SoundCardRoadKey_t &other) const
+    {
+        return (nSoundCardNum == other.nSoundCardNum) && (nRoadNum == other.nRoadNum);
+    }
+    bool operator<(const SoundCardRoadKey_t &other) const
+    {
+        if(nSoundCardNum < other.nSoundCardNum)
+            return true;
+        if(nSoundCardNum > other.nSoundCardNum)
+            return false;
+        return nRoadNum < other.nRoadNum;
+    }
+};
+
 /* 注册数据类型到系统 */
 Q_DECLARE_METATYPE(SoundCardRoadInfo_t)