浏览代码

V0.3.2
1、完成了录制报警文件的功能,完成了噪音报警的功能

Apple 1 周之前
父节点
当前提交
4a033c38bb

+ 2 - 1
Server/GlobalInfo/GlobalInfo.h

@@ -165,7 +165,6 @@ private:
     // int32_t m_oneSecondCount = 0;              /* 这里使用一秒音频的字节个数 */
     int32_t m_queueElementCount = 180;          /* 默认缓冲区的大小,也就是音频处理线程的环形队列的大小,单位是秒 */
     int32_t m_wavFileDuration = 60;             /* 一个wav小文件有多少秒的数据 */
-    int32_t m_noiseElementDuration = 1;         /* 噪音检测元素大小,单位:秒 */
 
     /* 相关的文件夹 */
     const QString m_dirWav = "WAV";             /* wav文件存储目录 */
@@ -202,6 +201,8 @@ private:
     int m_silentThreshold = 0;                  /* 静音阈值 */
     int m_ignorePhaseThreshold = 0;             /* 要忽视的反相阀值,反相值接近0时忽视掉,维持当前反相状态 */
 
+    /******************** 噪音检测参数 *********************/
+    int32_t m_noiseElementDuration = 1;         /* 噪音检测元素大小,单位:秒 */
 
     /******************** 定制化的配置选项 *********************/
     /* 杭州台:在其它报警时,不一致不报警且后台不进行不一致比对,在其它报警恢复后,不一致重新计时比对

+ 2 - 0
Server/ThreadCalculate/CompareDoubleThread.cpp

@@ -719,6 +719,8 @@ void CompareDoubleThread::saveAlarmInfo()
             m_alarmConsistencySub.StartTime = m_localData2.ringQueue[pos2]->startTime;
 
             SPDLOG_LOGGER_WARN(m_logger, "{}: 不一致报警开始", m_logBase);
+            m_threadCreateAlarmFile1->startRecordAlarmFile(m_alarmConsistencyMain);
+            m_threadCreateAlarmFile2->startRecordAlarmFile(m_alarmConsistencySub);
         }
     }
 }

+ 156 - 30
Server/ThreadCalculate/NoiseDetectThread.cpp

@@ -3,6 +3,8 @@
 #include <random>
 
 #include "CreateWAVThread.h"
+#include "CreateLongFileThread.h"
+#include "ThreadAlarmManager.h"
 #include "ThreadManager.h"
 #include "commonDefine.h"
 #include "GlobalInfo.h"
@@ -27,6 +29,41 @@ NoiseDetectThread::~NoiseDetectThread()
 
 }
 
+/* 开启对比项通道的噪音报警功能 */
+void NoiseDetectThread::startCompareItemNoiseAlarm(const int itemID, const QString strName, const CompareItemRoadInfo_t& compareItemRoadInfo)
+{
+    std::lock_guard<std::mutex> lock(m_mutexAlarm);
+    /* 如果已经存在这个对比项的报警信息,就不需要重复添加了 */
+    if(m_mapAlarmInfo.find(itemID) != m_mapAlarmInfo.end())
+    {
+        SPDLOG_LOGGER_INFO(m_logger, "{} 对比项 {} 的噪音报警功能已经开启", m_logBase, strName.toStdString());
+        return;
+    }
+    /* 添加对比项的噪音报警信息 */
+    AlarmInfo_t alarmInfo;
+    alarmInfo.CompareItemID = itemID;
+    alarmInfo.strCompareItemName = strName.toStdString();
+    alarmInfo.isAlarm = false; // 初始状态没有报警
+    alarmInfo.RoadInfo = compareItemRoadInfo; 
+    alarmInfo.AlarmType = EAlarmType::EAT_Noise;
+
+    m_mapAlarmInfo[itemID] = alarmInfo;
+}
+
+/* 关闭对比项通道的噪音报警功能 */
+void NoiseDetectThread::stopCompareItemNoiseAlarm(const int itemID, const QString strName, const CompareItemRoadInfo_t& compareItemRoadInfo)
+{
+    std::lock_guard<std::mutex> lock(m_mutexAlarm);
+    auto it = m_mapAlarmInfo.find(itemID);
+    if(it != m_mapAlarmInfo.end())
+    {
+        SPDLOG_LOGGER_INFO(m_logger, "{} 对比项 {} 的噪音报警功能已关闭", m_logBase, strName.toStdString());
+        m_mapAlarmInfo.erase(it); // 移除对比项的噪音报警信息
+    }else {
+        SPDLOG_LOGGER_WARN(m_logger, "{} 对比项 {} 的噪音报警功能未开启,无法关闭", m_logBase, strName.toStdString());
+    }
+}
+
 
 /* 线程功能函数 */
 void NoiseDetectThread::task()
@@ -46,7 +83,7 @@ void NoiseDetectThread::task()
         /*------------------------------------------------------------------------
          * 获取最新的左右声道数据
          *------------------------------------------------------------------------*/
-        if(!m_pWAVThread->getLatestLeftRightData(m_leftRightData))
+        if(!m_pThreadWav->getLatestLeftRightData(m_leftRightData))
         {
             continue;
         }
@@ -63,7 +100,7 @@ void NoiseDetectThread::task()
          * 处理结果,写噪音报警信息到数据库(这里不写数据库,只计算结果返回给对比项)
          * 上面那个函数已经把结果保存了
          *------------------------------------------------------------------------*/
-        // saveResult();
+        saveResult();
 
     }
     clearData(); // 清理数据
@@ -78,18 +115,37 @@ bool NoiseDetectThread::initData()
     m_roadName = fmt::format("{}:{}", m_roadInfo.strSoundCardID.toStdString(), m_roadInfo.roadInfo.nRoadNum);
     m_logBase = fmt::format("噪音检测通道 {}:", m_roadName);
 
-    m_pWAVThread = ThreadMan.getCreateWAVThread(m_roadInfo.nSoundCardNum, m_roadInfo.roadInfo.nRoadNum);
-    if(m_pWAVThread == nullptr)
+    /* 获取线程 */
+    auto now = std::chrono::steady_clock::now();
+    while(true)
     {
-        SPDLOG_LOGGER_ERROR(m_logger, "{} 获取数据生成线程失败", m_logBase);
-        return false;
+        if(m_pThreadWav == nullptr)
+        {
+            m_pThreadWav = ThreadMan.getCreateWAVThread(m_roadInfo.nSoundCardNum, m_roadInfo.roadInfo.nRoadNum);
+        }
+        if(m_pThreadCreateAlarm == nullptr)
+        {
+            m_pThreadCreateAlarm = ThreadMan.getCreateLongFileThread(m_roadInfo.nSoundCardNum, m_roadInfo.roadInfo.nRoadNum);
+        }
+        if(m_pThreadWav != nullptr && m_pThreadCreateAlarm != nullptr)
+        {
+            break; // 获取到线程了
+        }
+        if(std::chrono::steady_clock::now() - now > std::chrono::seconds(10))
+        {
+            SPDLOG_LOGGER_ERROR(m_logger, "{} 获取数据线程超时", m_logBase);
+            return false; // 超时了,获取线程失败
+        }
     }
 
     auto sampleRate = GInfo.sampleRate();
     m_sample_rate = static_cast<double>(sampleRate);
 
-    /* 初始化噪音检测功能 */
-    // signalstats_wrapper::initialize();
+    /* 获取噪音检测参数 */
+    m_noiseDetectParam = SysConfig.getNoiseDetectParam();
+
+    /* 设置噪音检测参数 */
+    m_ringQueueIsNoise.setQueueCapacity(m_noiseDetectParam.nNoiseDetectContinueCount);
 
     return true;
 }
@@ -108,15 +164,6 @@ bool NoiseDetectThread::detectNoise()
     SPDLOG_LOGGER_INFO(m_logger, "{} 左声道数据大小: {}, 右声道数据大小: {}", 
         m_logBase, m_leftRightData.vecLeftData.size(), m_leftRightData.vecRightData.size());
 
-        // std::vector<double> audio_signal(48000);
-        // std::random_device rd;
-        // std::mt19937 gen(rd());
-        // std::normal_distribution<> dist(0.0, 1.0);
-        
-        // for (auto& sample : audio_signal) {
-        //     sample = dist(gen);
-        // }
-
     auto startTime = std::chrono::steady_clock::now();
     bool isNoiseLeft = false; /* 是否检测到左声道噪音 */
     bool isNoiseRight = false; /* 是否检测到右声道噪音 */
@@ -127,7 +174,6 @@ bool NoiseDetectThread::detectNoise()
         auto ret = signalstats::detect_signal(
             jsonOutput,             /* 返回结果,和jsonResult是一样的 */
             m_leftRightData.vecLeftData,  /* 左声道数据 */
-            // audio_signal,           /* 测试数据 */
             m_sample_rate,          /* 采样率(HZ) */
             m_silence_threshold,    /* 静音阈值 */
             m_db_threshold,         /* 分贝阈值 */
@@ -150,7 +196,6 @@ bool NoiseDetectThread::detectNoise()
         signalstats::detect_signal(
             jsonOutput,             /* 返回结果,和jsonResult是一样的 */
             m_leftRightData.vecRightData,  /* 右声道数据 */
-            // audio_signal,           /* 测试数据 */
             m_sample_rate,          /* 采样率(HZ) */
             m_silence_threshold,    /* 静音阈值 */
             m_db_threshold,         /* 分贝阈值 */
@@ -163,11 +208,7 @@ bool NoiseDetectThread::detectNoise()
         );
         isNoiseRight = jsonOutput["noise"].is_null() ? false : jsonOutput["noise"].get<bool>();
 
-        // for(const auto& it : m_leftRightData.vecRightData)
-        // {
-        //     fmt::print("{:.5f} ", it);
-        // }
-        SPDLOG_LOGGER_DEBUG(m_logger, "{} 右声道噪音检测结果: {}", m_logBase, jsonOutput.dump(4));
+        // SPDLOG_LOGGER_DEBUG(m_logger, "{} 右声道噪音检测结果: {}", m_logBase, jsonOutput.dump(4));
 
     }
     catch (const std::exception& e)
@@ -183,8 +224,7 @@ bool NoiseDetectThread::detectNoise()
     SPDLOG_LOGGER_DEBUG(m_logger, "{} 左声道噪音检测结果: {}, 右声道噪音检测结果: {}", m_logBase, isNoiseLeft, isNoiseRight);
 
     /* -------------------------- 和以往的结果对比 --------------------------*/
-    m_isNoiseLast = m_isNoise.load(); /* 上一次的噪音检测结果 */
-    m_isNoise.store(isNoiseLeft || isNoiseRight); /* 是否检测到噪音 */
+    m_currentIsNoise = (isNoiseLeft || isNoiseRight); /* 是否检测到噪音 */
 
     return true;
 }
@@ -193,14 +233,100 @@ bool NoiseDetectThread::detectNoise()
 /* 保存结果 */
 void NoiseDetectThread::saveResult()
 {
-    if(m_isNoiseLast)
+    /* 将当前噪音检测结果保存到环形队列中 */
+    m_ringQueueIsNoise.push(m_currentIsNoise);
+    /* 计算出噪音个数和噪音所占百分比 */
+    const int size = m_ringQueueIsNoise.QueueSize();
+    int numNoise = 0;           // 噪音的个数
+    int numCountinueNoise = 0;  // 连续噪音的个数
+    double percentNoise = 0.0;  // 噪音所占百分比
+    for(int i = 0; i < size; ++i)
+    {
+        if(m_ringQueueIsNoise.at(i))
+        {
+            numNoise++;
+            numCountinueNoise++;
+        }else {
+            numCountinueNoise = 0; // 如果不是噪音,连续噪音计数清零
+        }
+    }
+    /* 判断是否是噪音预警 */
+    if(numCountinueNoise >= m_noiseDetectParam.nNoiseContinueCountIsWarn)
+    {
+        m_isNoiseWarning.store(true);
+    }
+    percentNoise = numNoise * 100.0 / m_noiseDetectParam.nNoiseContinueCountPercent;
+    /* 根据噪音所占的百分比判断是否是噪音 */
+    if(percentNoise >= m_noiseDetectParam.nNoiseContinueCountPercent)
     {
-        /* 判断这次是否是噪音,没有噪音就报警结束 */
+        m_isNoise.store(true);
+    }else {
+        m_isNoise.store(false);
+    }
+
 
+    if(m_isNoise.load())
+    {
+        /* 判断上次是否是噪音 */
+        if(!m_isNoiseLast)
+        {
+            /* 开始噪音报警 */
+            m_isNoiseWarning = true;
+            /* 通知对比项线程,开始噪音报警 */
+            std::lock_guard<std::mutex> lock(m_mutexAlarm);
+            for(auto& pair : m_mapAlarmInfo)
+            {
+                AlarmInfo_t& alarmInfo = pair.second;
+                if(!alarmInfo.isAlarm) // 如果没有报警
+                {
+                    alarmInfo.isAlarm = true; // 设置为报警状态
+                    /* 向前推算噪音开始时间,开始时间需要向前推 n次噪音检测的时间 x 单次噪音检测需要的时间,单位秒 */
+                    int nNoiseDetectTime = m_noiseDetectParam.nNoiseDetectContinueCount * m_noiseDetectParam.nNoiseDetectDuration;
+                    QDateTime startTime = m_leftRightData.startTime;
+                    startTime = startTime.addSecs(-nNoiseDetectTime);
+                    alarmInfo.StartTime = startTime;
+                    SPDLOG_LOGGER_WARN(m_logger, "{} 对比项-{} {} 开始噪音报警", m_logBase, alarmInfo.strCompareItemName, 
+                                                    alarmInfo.RoadInfo.strCompareRoadName.toStdString());
+                    /* 这里可以添加通知录制报警文件的线程开始录音 */
+                    if(m_pThreadCreateAlarm != nullptr)
+                    {
+                        m_pThreadCreateAlarm->startRecordAlarmFile(alarmInfo);
+                    }
+                }
+            }
+        }
     }else 
     {
-        /* 上一次没有噪音,这次有噪音,噪音报警开始 */
-
+        /* 没有噪音,判断是否结束报警 */
+        if(m_isNoiseLast)
+        {
+            /* 停止噪音报警 */
+            m_isNoiseWarning = false;
+            /* 通知对比项线程,结束噪音报警 */
+            std::lock_guard<std::mutex> lock(m_mutexAlarm);
+            for(auto& pair : m_mapAlarmInfo)
+            {
+                AlarmInfo_t& alarmInfo = pair.second;
+                if(alarmInfo.isAlarm) // 如果正在报警
+                {
+                     // 设置为非报警状态
+                    alarmInfo.isAlarm = false;
+                    /* 设置结束时间 */
+                    alarmInfo.EndTime = m_leftRightData.endTime;
+                    SPDLOG_LOGGER_WARN(m_logger, "{} 对比项-{} {} 结束噪音报警", m_logBase, alarmInfo.strCompareItemName, 
+                                                    alarmInfo.RoadInfo.strCompareRoadName.toStdString());
+                    /* 这里可以添加通知录制报警文件的线程停止录音 */
+                    if(m_pThreadCreateAlarm != nullptr)
+                    {
+                        m_pThreadCreateAlarm->stopRecordAlarmFile(alarmInfo);
+                    }
+                    /* 写入数据库,并清空时间 */
+                    AlarmManager.addAlarmInfo(alarmInfo);
+                    alarmInfo.StartTime = QDateTime();
+                    alarmInfo.EndTime = QDateTime();               
+                }
+            } 
+        }
         
     }
 }

+ 43 - 15
Server/ThreadCalculate/NoiseDetectThread.h

@@ -4,16 +4,25 @@
 
 #include "BaseCalculateThread.h"
 #include "AudioData.h"
+#include "SystemConfig.h"
+#include "RingQueueManualMutex.hpp"
 
 class CreateWAVThread;
-
+class CreateLongFileThread ;
 
 
 /**
- * @brief 噪音检测线程类
- *          1、噪音检测线程是调用动态库检测噪音
- *          2、噪音检测线程参数和对比项无关,是公共的参数,这里没有对比项信息
- * 
+    噪音检测线程类
+        1、噪音检测线程是调用动态库检测噪音
+        2、噪音检测线程参数和对比项无关,是公共的参数,这里没有对比项信息
+        3、因为报警信息需要有对比项信息,所以这里有个map,保存对比项传入的信息,根据对比项信息
+           开启报警录制,如果这个通道被多个对比项使用,可能会同时录制多个报警文件。
+    
+    噪音判断逻辑
+        1、每次检测噪音时,都会检测左右声道的噪音,如果有一个声道有噪音,就认为是噪音
+        2、假设设置的噪音检测持续次数是10次,设置的百分比是0.8,10次里有8个是噪音,就判断为噪音
+        3、噪音预警,如果连续n次都是噪音,就认为是噪音预警
+        4、噪音恢复,连续检测次数中低于设置的百分比就认为噪音恢复了
  */
 class NoiseDetectThread : public BaseCalculateThread
 {
@@ -24,8 +33,15 @@ public:
 
     /* 获取通道信息 */
     const SoundCardRoadInfo_t& getRoadInfo() const { return m_roadInfo; }
-    /* 获取噪音结果 */
+    /* 获取当前噪音结果 */
     bool isNoise() const { return m_isNoise.load(); }
+    /* 获取是否噪音预警 */
+    bool isNoiseWarning() const { return m_isNoiseWarning.load(); }
+
+    /* 开启对比项通道的噪音报警功能 */
+    void startCompareItemNoiseAlarm(const int itemID, const QString strName, const CompareItemRoadInfo_t& compareItemRoadInfo);
+    /* 关闭对比项通道的噪音报警功能 */
+    void stopCompareItemNoiseAlarm(const int itemID, const QString strName, const CompareItemRoadInfo_t& compareItemRoadInfo);
 
 protected:
     /* 线程功能函数 */
@@ -45,23 +61,35 @@ private:
 private:
     SoundCardRoadInfo_t m_roadInfo;                 /* 录音通道编号 */
     std::string m_roadName;                         /* 录音通道名称 */
-    CreateWAVThread* m_pWAVThread = nullptr;        /* WAV小文件生成线程指针 */
+    CreateWAVThread* m_pThreadWav = nullptr;        /* WAV小文件生成线程指针 */
+    CreateLongFileThread * m_pThreadCreateAlarm = nullptr; /* 生成报警文件的线程 */
 
     AudioLeftRightData m_leftRightData;             /* 左右声道数据 */
 
+    /* 计算的结果变量 */
+    bool m_currentIsNoise = false;                  /* 当前是否检测到噪音 */
+    /* 结果 */
+    std::atomic_bool m_isNoiseWarning = false;      /* 噪音预警 */
     std::atomic_bool m_isNoise = false;             /* 是否检测到噪音 */
-    bool m_isNoiseLast = false;                     /* 上一次是否检测到噪音 */
+    bool m_isNoiseLast = false;                     /* 上一次的噪音检测结果 */
 
+    /* ------------------------------------ 噪音检测的一些参数 ------------------------------------ */
+    NoiseDetectParam_t m_noiseDetectParam;      /* 噪音检测参数,这个是从数据库中读取过来的 */
 
-    /* 噪音检测的一些参数 */
+    /* 噪音检测动态库需要的参数 */
     const std::vector<std::string> m_window_params = {"tukey", "0.25"};
     double m_sample_rate = 48000;                   /* 采样率 */
-    double m_silence_threshold = 3e-3;        /* 静音检测阈值 */
-    double m_db_threshold = -70.0;            /* 分贝阈值 */
-    double m_cv_threshold = -70.0;            /* 变异系数阈值 */
-    int m_nperseg = 256;                      /* 每段样本数 */
-    int m_noverlap = 32;                      /* 重叠样本数 */
-    int m_nfft = 256;                         /* FFT点数 */
+    double m_silence_threshold = 3e-3;          /* 静音检测阈值 */
+    double m_db_threshold = -70.0;              /* 分贝阈值 */
+    double m_cv_threshold = -70.0;              /* 变异系数阈值 */
+    int m_nperseg = 256;                        /* 每段样本数 */
+    int m_noverlap = 32;                        /* 重叠样本数 */
+    int m_nfft = 256;                           /* FFT点数 */
+
+    /* ------------------------------------ 报警相关变量 ------------------------------------ */
+    std::mutex m_mutexAlarm;                    /* 报警信息互斥锁 */
+    std::map<int, AlarmInfo_t> m_mapAlarmInfo;  /* 报警信息,key是对比项ID */
+    RingQueueManualMutex<bool> m_ringQueueIsNoise; /* 噪音检测结果环形队列,保存最近的噪音检测结果 */
 };
 
 

+ 189 - 17
Server/ThreadRecord/CreateLongFileThread.cpp

@@ -38,11 +38,16 @@ bool CreateLongFileThread::setData(const AudioSrcData& srcData)
 
     /* ------------------------------------------------------------------------------- */
     /* 先写入记录报警文件的环形缓冲区 */
-    auto oldData = m_ringQueue.push_pop(new AudioSrcData(srcData));
-    if(oldData != nullptr)
     {
-        delete oldData;
-        oldData = nullptr;
+        std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
+        auto oldData = m_ringQueue.push(new AudioSrcData(srcData));
+        /* 新的元素数加1 */
+        m_numNewAlarmSeconds++;
+        if(oldData != nullptr)
+        {
+            delete oldData;
+            oldData = nullptr;
+        }
     }
     
     /* ------------------------------------------------------------------------------- */
@@ -81,7 +86,7 @@ bool CreateLongFileThread::setData(const AudioSrcData& srcData)
 /* 开启录制 */
 bool CreateLongFileThread::startRecordAlarmFile(const AlarmInfo_t& alarmInfo)
 {
-    std::lock_guard<std::mutex> lock(m_mutexBuffer);
+    std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
     /* 先检查是否已经在队列中了 */
     AlarmKey_t key = {alarmInfo.CompareItemID, alarmInfo.RoadInfo.nCompareRoadNum, 
                         alarmInfo.AlarmType, alarmInfo.StartTime};
@@ -90,8 +95,17 @@ bool CreateLongFileThread::startRecordAlarmFile(const AlarmInfo_t& alarmInfo)
         SPDLOG_LOGGER_WARN(m_logger, "{} 已经在报警录制队列中,无法重复添加", m_logBase);
         return true;
     }
-    /* 添加一个新的报警文件路径 */
-    QString fileName = generateFileName(alarmInfo.StartTime, alarmInfo.EndTime);
+
+    /* 将数据值插入map中 */
+    AlarmValue_t value;
+    value.alarmType = alarmInfo.AlarmType;
+    value.startTime = alarmInfo.StartTime;
+    value.endTime = alarmInfo.EndTime;
+    value.numConpareItemID = alarmInfo.CompareItemID;
+    value.strCompareItemName = QString::fromStdString(alarmInfo.strCompareItemName);
+    value.itemRoadInfo = alarmInfo.RoadInfo;
+
+    m_mapAlarmFile.insert({key, value});
 
     return true;
 }
@@ -99,6 +113,26 @@ bool CreateLongFileThread::startRecordAlarmFile(const AlarmInfo_t& alarmInfo)
 /* 停止录制,alarmInfo既是传入参数,也是传出参数,传出文件路径和开始位置 */
 bool CreateLongFileThread::stopRecordAlarmFile(AlarmInfo_t& alarmInfo)
 {
+    std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
+    for(auto& it : m_mapAlarmFile)
+    {
+        if(it.first.compareItemID == alarmInfo.CompareItemID &&
+           it.first.roadNum == alarmInfo.RoadInfo.nCompareRoadNum &&
+           it.first.alarmType == alarmInfo.AlarmType &&
+           it.second.startTime == alarmInfo.StartTime )
+        {
+            /* 找到对应的报警信息 */
+            AlarmValue_t& value = it.second;
+            value.endTime = alarmInfo.EndTime; // 设置结束时间
+            value.state = eRecordState::eAlarm_Stopped; // 设置为停止状态
+            value.fileNameEnd = generateAlarmFileName(value, false); // 生成结束文件名
+            alarmInfo.strAlarmFilePath = value.fileNameEnd.toStdString(); // 返回文件路径
+            alarmInfo.AlarmStartPos = value.alarmStartPos; // 返回开始位置
+            SPDLOG_LOGGER_INFO(m_logger, "{} 停止录制报警文件: {}, 开始位置: {} 秒", 
+                m_logBase, value.fileNameEnd.toStdString(), value.alarmStartPos);
+            return true;
+        }
+    }
 
     return true;
 }
@@ -127,7 +161,7 @@ void CreateLongFileThread::task()
         /*--------------------------------------------------------------
         * 写入报警文件
         *--------------------------------------------------------------*/
-
+        writeAlarmFile();
         
         /*--------------------------------------------------------------
         * 写入长记录文件
@@ -425,7 +459,7 @@ bool CreateLongFileThread::isOneHourPassed()
 
 
 /* 生成报警文件名 */
-QString CreateLongFileThread::generateAlarmFileName(const AlarmInfo_t& alarmInfo, bool isNewFile)
+QString CreateLongFileThread::generateAlarmFileName(const AlarmValue_t& value, bool isNewFile)
 {
     QString retFileName;
     if(isNewFile)
@@ -434,7 +468,7 @@ QString CreateLongFileThread::generateAlarmFileName(const AlarmInfo_t& alarmInfo
         setTodayAlarmPath();
 
         /* 检查这个对比项的报警文件夹是否存在 */
-        QString itemDirName = QString("CompareItemID_%1").arg(QString::number(alarmInfo.CompareItemID));
+        QString itemDirName = QString("CompareItemID_%1").arg(QString::number(value.numConpareItemID));
         QDir itemDir = m_todayDirAlarm;
         itemDir.cd(itemDirName);
         if(!itemDir.exists())
@@ -450,21 +484,21 @@ QString CreateLongFileThread::generateAlarmFileName(const AlarmInfo_t& alarmInfo
         /* 生成文件名, 格式: Alarm_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
            这里创建的录音文件只有开始日期,没有结束日期,等报警结束之后才会有结束日期 */
         QString fileName = QString("Alarm_%1_%2_%3-.wav")
-            .arg(QString::number(alarmInfo.RoadInfo.nCompareRoadNum))
-            .arg(getAlarmTypeString(alarmInfo.AlarmType))
-            .arg(alarmInfo.StartTime.toString("yyyyMMdd_hhmmss"));
+            .arg(QString::number(value.itemRoadInfo.nCompareRoadNum))
+            .arg(getAlarmTypeString(value.alarmType))
+            .arg(value.startTime.toString("yyyyMMdd_hhmmss"));
         
         /* 拼接文件夹路径 */
         retFileName = itemDir.filePath(fileName);
     }else 
     {
         /* 已有的文件,是报警结束的文件名 */
-        retFileName = QString::fromStdString(alarmInfo.strAlarmFilePath);
+        retFileName = QString::fromStdString(value.fileName.toStdString());
         /* 这里的文件名格式是:Alarm_CompareItemID_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav,
            这里带上结束时间 */
-        QString endTimeStr = alarmInfo.EndTime.toString("yyyyMMdd_hhmmss");
+        QString endTimeStr = value.endTime.toString("yyyyMMdd_hhmmss");
         /* 替换掉原来的结束时间 */
-        retFileName.replace("-.wav", QString("-%1.wav").arg(endTimeStr));    
+        retFileName.replace("-.wav", QString("-%1.wav").arg(endTimeStr));
     }
 
     return retFileName;
@@ -474,7 +508,145 @@ QString CreateLongFileThread::generateAlarmFileName(const AlarmInfo_t& alarmInfo
 /* 写入报警文件 */
 void CreateLongFileThread::writeAlarmFile()
 {
-    /* 先判断环形队列中数据是否足够的秒数,不一定是每一秒都会写的 */
+    const int writeSeconds = 2;
+    const int newAlarmSeconds = 10;
+    /* 新文件,从报警时间往前推1秒写入 */
+    std::list<AudioSrcData*> newDataList;
+    /* 给已经写入的问价使用 */
+    std::list<AudioSrcData*> dataList;
+    {
+        /* 先判断环形队列中数据是否足够的秒数,不一定是每一秒都会写的 */
+        std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
+        /* 目前暂定2秒写一次 */
+        if(m_numNewAlarmSeconds < writeSeconds)
+        {
+            /* 不够两秒时间,退出 */
+            return;
+        }
+        /* 取出环形队列中的数据 */
+        int size = m_ringQueue.QueueSize();
+        for(int i = size - writeSeconds; i < size; ++i)
+        {
+            AudioSrcData* data = m_ringQueue[i];
+            if(data != nullptr)
+            {
+                dataList.push_back(data);
+            }
+        }
+        int start = size - newAlarmSeconds;
+        if(start < 0)
+        {
+            start = 0; // 防止越界
+        }
+        /* 取出新数据,往前推10秒 */
+        for(int i = start; i < size; ++i)
+        {
+            AudioSrcData* data = m_ringQueue[i];
+            if(data != nullptr)
+            {
+                newDataList.push_back(data);
+            }
+        }
+    }
+
+    std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
+    /* 将数据循环写入到文件中 */
+    for(auto& alarmInfo : m_mapAlarmFile)
+    {
+        /* 获取报警信息 */
+        // const AlarmKey_t& key = alarmInfo.first;
+        AlarmValue_t& value = alarmInfo.second;
+        /* 判断是新否是新录音的文件 */
+        if(value.state == eRecordState::eAlarm_Init)
+        {
+            /* 新的报警录音,生成文件名 */
+            value.fileName = generateAlarmFileName(value, true);
+            value.state = eRecordState::eAlarm_Recording; // 设置为录音状态
+            SPDLOG_LOGGER_DEBUG(m_logger, "{} 开始写入报警文件: {}", m_logBase, value.fileName.toStdString());
+            
+            /* 计算出报警开始位置在报警文件中的偏移,这个偏移值在设置结束的时候取出 */
+            for(const auto& audioData : newDataList)
+            {
+                if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
+                {
+                    continue; // 跳过空数据
+                }
+                if(audioData->startTime <= value.startTime)
+                {
+                    /* 计算报警开始位置 */
+                    value.alarmStartPos = audioData->startTime.secsTo(value.startTime);
+                }
+            }
+            
+            /* 打开文件 */
+            QFile wavFile(value.fileName);
+            if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
+            {
+                SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
+                SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
+                continue;
+            }
+            /* 写入音频数据到文件 */
+            for(auto& audioData : newDataList)
+            {
+                if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
+                {
+                    continue; // 跳过空数据
+                }
+                auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
+                if(writeSize < 0)
+                {
+                    SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
+                    SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
+                }
+            }
+            wavFile.close();
+        } else
+        {
+            /* 已经存在的文件,直接写入新录制的文件 */
+            /* 打开文件 */
+            QFile wavFile(value.fileName);
+            if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
+            {
+                SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
+                SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
+                continue;
+            }
+            /* 写入音频数据到文件 */
+            for(auto& audioData : dataList)
+            {
+                if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
+                {
+                    continue; // 跳过空数据
+                }
+                auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
+                if(writeSize < 0)
+                {
+                    SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
+                    SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
+                    break; // 写入失败,跳出循环
+                }
+            }
+            wavFile.close();
+        }
+        
+    }
+
+    /* 处理已经结束的报警,修改文件名,并将其移除队列中 */
+    for(auto it = m_mapAlarmFile.begin(); it != m_mapAlarmFile.end(); )
+    {
+        AlarmValue_t& value = it->second;
+        if(value.state == eRecordState::eAlarm_Stopped)
+        {
+            /* 已经结束的报警,修改文件名 */
+            modifyFileName(value.fileName, value.fileNameEnd);
+            /* 移除这个报警 */
+            it = m_mapAlarmFile.erase(it);
+        } else {
+            ++it; // 继续下一个
+        }
+    }
+    
 }
 
 

+ 23 - 11
Server/ThreadRecord/CreateLongFileThread.h

@@ -5,6 +5,7 @@
 #include "AudioData.h"
 #include "GlobalVariable.h"
 #include "RingQueue.hpp"
+#include "RingQueueManualMutex.hpp"
 
 #include <mutex>
 #include <QFile>
@@ -16,7 +17,7 @@
  * @brief 录音状态
  * 
  */
-enum class eAlarmState
+enum class eRecordState
 {
     eAlarm_Init = 0,        /* 初始化状态 */
     eAlarm_Recording,       /* 录音状态 */
@@ -63,12 +64,21 @@ struct AlarmKey_t
  */
 struct AlarmValue_t
 {
-    QString fileName;          /* 报警文件名 */
-    eAlarmState state;         /* 录音状态 */
-    QDateTime startTime;       /* 录音开始时间 */
-    QDateTime endTime;         /* 录音结束时间 */
-
-    AlarmValue_t(const QString& name, eAlarmState s, const QDateTime& start, const QDateTime& end)
+    QString fileName;           /* 报警文件名 */
+    QString fileNameEnd;        /* 报警文件结束名,只有在报警结束时才有值 */
+    eRecordState state;         /* 录音状态 */
+    QDateTime startTime;        /* 录音开始时间 */
+    QDateTime endTime;          /* 录音结束时间 */
+    int alarmStartPos = 0;      /* 录音开始位置,单位:秒,报警问价的开始位置不一定是报警的开始位置 */
+    
+    /*  对比项和通道信息*/
+    EAlarmType alarmType;       /* 报警类型 */ 
+    int numConpareItemID;       /* 对比项ID */
+    QString strCompareItemName; /* 对比项名称 */
+    CompareItemRoadInfo_t itemRoadInfo; /* 对比项通道信息 */
+
+    AlarmValue_t() : state(eRecordState::eAlarm_Init) {}
+    AlarmValue_t(const QString& name, eRecordState s, const QDateTime& start, const QDateTime& end)
         : fileName(name), state(s), startTime(start), endTime(end) {}
 };
 
@@ -91,7 +101,7 @@ struct AlarmValue_t
     文件夹路径格式:
         1、长录音文件夹: Application/ACAServerData/Record/yyyy-MM-dd/SoundCardID-RoadNum/Record_yyyy-MM-dd-hh-mm-ss-yyyy-MM-dd-hh-mm-ss.wav
               例如:Application/2025-01-01/SoundCard1-1/Record_2025-01-01-12-00-00-2025-01-01-12-01-00.wav
-        2、报警录音文件夹:Application/ACAServerData/AlarmWav/yyyy-MM-dd/CompareItemName/Alarm_CompareItemID_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
+        2、报警录音文件夹:Application/ACAServerData/AlarmWav/yyyy-MM-dd/CompareItemID-id/Alarm_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
               例如:Application/2025-01-01/1-1/Alarm_1_1_Silent_20250101_120000-20250101_120100.wav
 
  */
@@ -136,7 +146,7 @@ private:
     /* 写入报警文件 */
     void writeAlarmFile();
     /* 生成报警文件名 */
-    inline QString generateAlarmFileName(const AlarmInfo_t& alarmInfo, bool isNewFile);
+    inline QString generateAlarmFileName(const AlarmValue_t& value, bool isNewFile);
     /* 设置今日报警文件夹 */
     bool setTodayAlarmPath();
     /* 根据报警类型的枚举获取字符 */
@@ -166,14 +176,16 @@ private:
 
     /*===============================================================================*/
     /* 报警文件名,key是报警信息,value是文件信息 */
+    std::mutex m_mutexAlarmFile;
     std::map<AlarmKey_t, AlarmValue_t> m_mapAlarmFile;
 
     QDir m_yesterdayDir;                    /* 昨日目录,用来给还未录制完成的报警文件使用的 */
     QDir m_todayDirAlarm;                   /* 今日报警目录,这个目录只到日期,里面的子文件夹是对比项相关的 */
     QDate m_todayDateAlarm;                 /* 今日日期,记录报警文件用 */
 
-    RingQueue<AudioSrcData*> m_ringQueue;   /* 环形队列,存储几分钟的数据,用来写入报警文件 */
-    QDateTime m_alarmWrittenTime;       /* 报警文件中环形队列已写入文件的时间 */
+    RingQueueManualMutex<AudioSrcData*> m_ringQueue; /* 环形队列,存储报警文件数据 */
+    QDateTime m_alarmWrittenTime;           /* 报警文件中环形队列已写入文件的时间 */
+    int m_numNewAlarmSeconds = 0;           /* 新的报警文件的秒数,新加入的报警文件的秒数,每次写完后清零 */
 };
 
 

+ 5 - 1
Server/ThreadRecord/CreateWAVThread.cpp

@@ -419,8 +419,12 @@ bool CreateWAVThread::splitLeftRightChannel()
             {
                 pLeftRightData->vecRightData.push_back(sampleDouble);
             }
+            // if(j < 20) // 只打印前10个数据
+            // {
+            //     fmt::print(" |{} {}, short: {}, double: {:.2f}\n", static_cast<uint8_t>(*(data->pData + j)), static_cast<uint8_t>(*(data->pData + j * 2)), sample, sampleDouble);
+
+            // }
             // fmt::print(" |{} {}, short: {}, double: {:.2f}", static_cast<uint8_t>(*(data->pData + j)), static_cast<uint8_t>(*(data->pData + 1001)), sample, sampleDouble);
-            
         }
         pLeftRightData->numMSecond += 1000; // 每次处理1秒的数据,增加1000毫秒
 

+ 6 - 0
Server/ThreadRecord/RecordThread.cpp

@@ -91,6 +91,12 @@ void RecordThread::task()
         {
             m_assignThread->setSrcData(m_pRecordBuffer, m_recordBufferSize, currentTime);
         }
+        for(int i = 0; i < 30; ++i)
+        {
+            /* 这里可以添加对音频数据的处理,比如噪音检测等 */
+            fmt::print(" |{} {}, short: {}\n", static_cast<uint8_t>(*(m_pRecordBuffer + i*2)), static_cast<uint8_t>(*(m_pRecordBuffer + i*2+1)), 
+            static_cast<int16_t>(*(m_pRecordBuffer + i * 2)));
+        }
         memset(m_pRecordBuffer, 0, m_recordBufferSize);  /* 清空缓存,准备下一次录音 */
         // SPDLOG_LOGGER_DEBUG(m_logger, "{} 录音数据已分派给AssignSrcDataThread线程", m_logBase);
     }