CreateLongFileThread.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. #include "CreateLongFileThread.h"
  2. #include "AudioData.h"
  3. #include "GlobalInfo.h"
  4. #include "spdlog.h"
  5. #include <cstring>
  6. #include <qt5/QtCore/qchar.h>
  7. CreateLongFileThread::CreateLongFileThread(RecordThreadInfo_t& threadInfo)
  8. : BaseRecordThread(threadInfo)
  9. {
  10. m_logger = spdlog::get("RecordAudio");
  11. if(m_logger == nullptr)
  12. {
  13. fmt::print("RecordThread: RecordAudio Logger not found.\n");
  14. return;
  15. }
  16. /* 初始化数据 */
  17. initData();
  18. }
  19. CreateLongFileThread::~CreateLongFileThread()
  20. {
  21. }
  22. /* 设置数据 */
  23. bool CreateLongFileThread::setData(const AudioSrcData& srcData)
  24. {
  25. if(srcData.pData == nullptr || srcData.dataSize == 0)
  26. {
  27. SPDLOG_LOGGER_ERROR(m_logger, "{} 设置数据失败,srcData为空或dataSize为0", m_logBase);
  28. return false;
  29. }
  30. /* ------------------------------------------------------------------------------- */
  31. /* 先写入记录报警文件的环形缓冲区 */
  32. {
  33. std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
  34. auto oldData = m_ringQueue.push(new AudioSrcData(srcData));
  35. /* 新的元素数加1 */
  36. m_numNewAlarmSeconds++;
  37. if(oldData != nullptr)
  38. {
  39. delete oldData;
  40. oldData = nullptr;
  41. }
  42. }
  43. /* ------------------------------------------------------------------------------- */
  44. /* 锁定缓冲区 */
  45. std::lock_guard<std::mutex> lock(m_mutexBuffer);
  46. /* 如果缓冲区没有分配内存,先分配 */
  47. if(m_bufferData.pData == nullptr)
  48. {
  49. if(!m_bufferData.allocateMemory(m_writeCriticalSize * 3))
  50. {
  51. SPDLOG_LOGGER_ERROR(m_logger, "{} 分配缓冲区内存失败", m_logBase);
  52. return false;
  53. }
  54. }
  55. /* 添加数据到缓冲区 */
  56. int32_t writtenSize = m_bufferData.appendData(srcData.pData, srcData.dataSize);
  57. if(writtenSize == 0)
  58. {
  59. SPDLOG_LOGGER_ERROR(m_logger, "{} 添加数据到缓冲区失败", m_logBase);
  60. return false;
  61. }
  62. /* 记录日期 */
  63. if(m_bufferData.startTime.isNull() || m_bufferData.startTime.isValid())
  64. {
  65. m_bufferData.startTime = srcData.startTime;
  66. }
  67. m_bufferData.endTime = srcData.endTime;
  68. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 设置数据,dataSize: {}, startTime: {}, endTime: {}",
  69. // m_logBase, m_bufferData.dataSize, m_bufferData.startTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(), m_bufferData.endTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
  70. return true;
  71. }
  72. /* 开启录制 */
  73. bool CreateLongFileThread::startRecordAlarmFile(const AlarmInfo_t& alarmInfo)
  74. {
  75. std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
  76. /* 先检查是否已经在队列中了 */
  77. AlarmKey_t key = {alarmInfo.CompareItemID, alarmInfo.RoadInfo.nCompareRoadNum,
  78. alarmInfo.AlarmType, alarmInfo.StartTime};
  79. if(m_mapAlarmFile.find(key) != m_mapAlarmFile.end())
  80. {
  81. SPDLOG_LOGGER_WARN(m_logger, "{} 已经在报警录制队列中,无法重复添加", m_logBase);
  82. return true;
  83. }
  84. /* 将数据值插入map中 */
  85. AlarmValue_t value;
  86. value.alarmType = alarmInfo.AlarmType;
  87. value.startTime = alarmInfo.StartTime;
  88. value.endTime = alarmInfo.EndTime;
  89. value.numConpareItemID = alarmInfo.CompareItemID;
  90. value.strCompareItemName = QString::fromStdString(alarmInfo.strCompareItemName);
  91. value.itemRoadInfo = alarmInfo.RoadInfo;
  92. m_mapAlarmFile.insert({key, value});
  93. return true;
  94. }
  95. /* 停止录制,alarmInfo既是传入参数,也是传出参数,传出文件路径和开始位置 */
  96. bool CreateLongFileThread::stopRecordAlarmFile(AlarmInfo_t& alarmInfo)
  97. {
  98. std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
  99. for(auto& it : m_mapAlarmFile)
  100. {
  101. if(it.first.compareItemID == alarmInfo.CompareItemID &&
  102. it.first.roadNum == alarmInfo.RoadInfo.nCompareRoadNum &&
  103. it.first.alarmType == alarmInfo.AlarmType &&
  104. it.second.startTime == alarmInfo.StartTime )
  105. {
  106. /* 找到对应的报警信息 */
  107. AlarmValue_t& value = it.second;
  108. value.endTime = alarmInfo.EndTime; // 设置结束时间
  109. value.state = eRecordState::eAlarm_Stopped; // 设置为停止状态
  110. value.fileNameEnd = generateAlarmFileName(value, false); // 生成结束文件名
  111. alarmInfo.strAlarmFilePath = value.fileNameEnd.toStdString(); // 返回文件路径
  112. alarmInfo.AlarmStartPos = value.alarmStartPos; // 返回开始位置
  113. SPDLOG_LOGGER_INFO(m_logger, "{} 停止录制报警文件: {}, 开始位置: {} 秒",
  114. m_logBase, value.fileNameEnd.toStdString(), value.alarmStartPos);
  115. return true;
  116. }
  117. }
  118. return true;
  119. }
  120. /**
  121. * @brief 生成长文件的线程函数,文件生成逻辑如下
  122. * 1、这里有一个缓冲区,存储音频数据,缓冲区大小是2分钟的数据
  123. * 2、每分钟写入一次文件,文件名格式为:ChannelX_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
  124. * 3、文件总长度是1小时的数据,超过1小时则重新开始记录一个新的文件
  125. *
  126. */
  127. void CreateLongFileThread::task()
  128. {
  129. SPDLOG_LOGGER_INFO(m_logger, "➢ {} 开启记录文件线程 ", m_logBase);
  130. /* 计算一小时的文件大小 */
  131. while(m_isRunning)
  132. {
  133. /* 线程休眠100ms */
  134. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  135. /*--------------------------------------------------------------
  136. * 写入报警文件
  137. *--------------------------------------------------------------*/
  138. writeAlarmFile();
  139. /*--------------------------------------------------------------
  140. * 写入长记录文件
  141. *--------------------------------------------------------------*/
  142. writeLongRecordFile();
  143. }
  144. SPDLOG_LOGGER_WARN(m_logger, "➢ {} 记录长文件线程结束运行", m_logBase);
  145. }
  146. /* 初始化一些数据 */
  147. bool CreateLongFileThread::initData()
  148. {
  149. m_logBase = fmt::format("录音通道 {}:{} - 记录长文件线程",
  150. m_threadInfo.cardRoadInfo.strSoundCardName.toStdString(),
  151. m_threadInfo.cardRoadInfo.roadInfo.nRoadNum);
  152. /* 获取全局数据 */
  153. m_sampleRate = GInfo.sampleRate(); /* 采样率 */
  154. m_numChannels = GInfo.numChannels(); /* 声道数 */
  155. m_bitsPerSample = GInfo.bitsPerSample(); /* 每个采样点的位数 */
  156. /* 一秒的数据大小 */
  157. m_oneSecondSize = m_sampleRate * m_numChannels * (m_bitsPerSample / 8);
  158. /* 一分钟数据大小 */
  159. m_writeCriticalSize = m_oneSecondSize * 60;
  160. /* 一小时数据大小 */
  161. m_oneHourSize = m_writeCriticalSize * 60;
  162. /* 给缓存分配空间 */
  163. m_bufferData.allocateMemory(m_writeCriticalSize * 3);
  164. m_srcData.allocateMemory(m_writeCriticalSize * 3);
  165. /* 设置环形队列大小 */
  166. m_ringQueue.setQueueCapacity(GInfo.queueElementCount());
  167. return true;
  168. }
  169. /* 清理数据 */
  170. void CreateLongFileThread::clearData()
  171. {
  172. /* 清理缓存数据 */
  173. m_bufferData.clear();
  174. }
  175. /* 写入长记录文件 */
  176. bool CreateLongFileThread::writeLongRecordFile()
  177. {
  178. /* 判断缓存是否达到1分钟数据临界值 */
  179. {
  180. std::lock_guard<std::mutex> lock(m_mutexBuffer);
  181. if(m_bufferData.dataSize < m_writeCriticalSize)
  182. {
  183. return true; // 缓存数据不足,继续等待
  184. }
  185. /* 数据足够了将缓冲区数据拷贝出来 */
  186. memcpy(m_srcData.pData, m_bufferData.pData, m_bufferData.dataSize);
  187. m_srcData.dataSize = m_bufferData.dataSize;
  188. m_srcData.startTime = m_bufferData.startTime;
  189. m_srcData.endTime = m_bufferData.endTime;
  190. /* 清空缓冲区数据 */
  191. m_bufferData.clear();
  192. }
  193. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 设置数据,dataSize: {}, startTime: {}, endTime: {}",
  194. // m_logBase, m_srcData.dataSize, m_srcData.startTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(), m_srcData.endTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
  195. /*--------------------------------------------------------------
  196. * 打开文件。写入的时候判断是否到达了整点,如果到达了整点,则关闭文件
  197. * 重新创建一个新的文件
  198. *--------------------------------------------------------------*/
  199. bool isNewFile = false;
  200. if(m_writtenSize == 0)
  201. {
  202. /* 如果没有写入过数据,则是新文件 */
  203. isNewFile = true;
  204. m_writtenStartTime = m_srcData.startTime; // 记录开始时间
  205. m_writtenNowTime = m_writtenStartTime; // 记录当前时间
  206. }
  207. /* 设置今日目录 */
  208. if(!setTodayPath(isNewFile))
  209. {
  210. return false;
  211. }
  212. /* 打开文件 */
  213. QFile wavFile;
  214. if(!openFile(wavFile, isNewFile))
  215. {
  216. if(m_openFileErrorSize >= 3)
  217. {
  218. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开文件失败次数过多,重新开始记录", m_logBase);
  219. m_writtenSize = 0;
  220. m_writtenStartTime = QDateTime::currentDateTime(); // 重新开始时间
  221. m_writtenNowTime = m_writtenStartTime; // 重新开始时间
  222. m_wavFileName.clear(); // 清空文件名
  223. m_srcData.clear(); // 清空缓冲区数据
  224. m_openFileErrorSize = 0; // 重置错误次数
  225. return false; // 重新开始记录
  226. }
  227. }
  228. /*--------------------------------------------------------------
  229. * 将数据写入文件,并记录其携带的时间和写入的数据大小
  230. *--------------------------------------------------------------*/
  231. int64_t wSize = 0;
  232. {
  233. std::lock_guard<std::mutex> lock(m_mutexBuffer);
  234. wSize = wavFile.write(m_srcData.pData, m_srcData.dataSize);
  235. /* 更新结束时间 */
  236. m_writtenNowTime = m_srcData.endTime;
  237. /* 清空缓冲区 */
  238. m_srcData.clear();
  239. }
  240. if(wSize < 0)
  241. {
  242. SPDLOG_LOGGER_ERROR(m_logger, "{} 写入WAV文件失败: {}", m_logBase, wavFile.errorString().toStdString());
  243. SPDLOG_LOGGER_WARN(m_logger, "文件路径:{}", m_wavFileName.toStdString());
  244. wavFile.close();
  245. return false;
  246. } else {
  247. SPDLOG_LOGGER_TRACE(m_logger, "{} 写入WAV文件成功: {}, 大小: {} 字节", m_logBase, m_wavFileName.toStdString(), wSize);
  248. }
  249. wavFile.close();
  250. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 写入WAV文件完成: {}, 大小: {} 字节",
  251. // m_logBase, m_wavFileName.toStdString(), wSize);
  252. /*--------------------------------------------------------------
  253. * 对该文件进行其他操作,判断是否已经过了一个整点,修改其文件名称
  254. * 现在这里的时间是这一分钟的开始时间,现在需要根据开始时间求出已写入
  255. * 数据大小对应的结束时间
  256. *--------------------------------------------------------------*/
  257. m_writtenSize += wSize;
  258. /* 修改文件名称 */
  259. QString newFileName = generateFileName(m_writtenStartTime, m_writtenNowTime);
  260. if(modifyFileName(m_wavFileName, newFileName))
  261. {
  262. m_wavFileName = newFileName;
  263. }
  264. /* 判断是否过了整点 */
  265. if(isOneHourPassed())
  266. {
  267. /* 修改文件头中记录的数据大小 */
  268. m_wavHeader.setDataSize(m_writtenSize);
  269. m_wavHeader.calculateDerivedFields();
  270. modifyWavFileHeader(m_wavFileName, m_wavHeader);
  271. SPDLOG_LOGGER_INFO(m_logger, "{} 结束记录一个文件: {}, 已写入大小: {} 字节",
  272. m_logBase, m_wavFileName.toStdString(), m_writtenSize);
  273. /* 重置已写入大小 */
  274. m_writtenSize = 0;
  275. m_writtenStartTime = QDateTime(); // 重新开始时间
  276. m_writtenNowTime = m_writtenStartTime; // 重新开始时间
  277. m_wavFileName.clear(); // 清空文件名
  278. m_openFileErrorSize = 0; // 重置错误次数
  279. }
  280. return true;
  281. }
  282. /* 设置今日目录 */
  283. bool CreateLongFileThread::setTodayPath(bool isNewFile)
  284. {
  285. if(!isNewFile)
  286. {
  287. return true;
  288. }
  289. /* 判断现在日期是否还是当天日期 */
  290. QDate today = QDate::currentDate();
  291. if(m_todayDateRecord == today)
  292. {
  293. return true;
  294. }
  295. m_todayDateRecord = today;
  296. /* 先检查现在的日期文件夹是否存在,因为其他线程可能已经创建了 */
  297. QString todayDirName = QString("%1/%2").arg(GInfo.longWavPath()).arg(today.toString("yyyy-MM-dd"));
  298. m_todayDir.setPath(todayDirName);
  299. if(!m_todayDir.exists())
  300. {
  301. if(!m_todayDir.mkpath(todayDirName))
  302. {
  303. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建今日目录失败: {}", m_logBase, todayDirName.toStdString());
  304. return false; // 创建目录失败
  305. } else {
  306. SPDLOG_LOGGER_INFO(m_logger, "{} 创建今日目录成功: {}", m_logBase, todayDirName.toStdString());
  307. }
  308. }
  309. /* 创建这个通道的文件夹,文件夹格式: AudioPCI-0 */
  310. QString roadDirName = QString("%1/%2-%3")
  311. .arg(todayDirName)
  312. .arg(m_threadInfo.cardRoadInfo.strSoundCardID)
  313. .arg(QString::number(m_threadInfo.cardRoadInfo.roadInfo.nRoadNum));
  314. m_todayDir.setPath(roadDirName);
  315. if(!m_todayDir.exists())
  316. {
  317. if(!m_todayDir.mkpath(todayDirName))
  318. {
  319. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建目录失败: {}", m_logBase, roadDirName.toStdString());
  320. return false;
  321. } else {
  322. SPDLOG_LOGGER_INFO(m_logger, "{} 创建目录成功: {}", m_logBase, roadDirName.toStdString());
  323. }
  324. }
  325. return true;
  326. }
  327. /* 打开文件 */
  328. bool CreateLongFileThread::openFile(QFile& wavFile, bool isNewFile)
  329. {
  330. if(isNewFile)
  331. {
  332. /* 如果没有写入过数据,则生成一个新的文件名 */
  333. m_wavFileName = generateFileName(m_writtenStartTime, m_writtenNowTime);
  334. m_wavHeader.setSampleRate(m_sampleRate);
  335. m_wavHeader.setNumChannels(m_numChannels);
  336. m_wavHeader.setBitsPerSample(m_bitsPerSample);
  337. m_wavHeader.setDataSize(m_writtenSize);
  338. m_wavHeader.calculateDerivedFields();
  339. wavFile.setFileName(m_wavFileName);
  340. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
  341. {
  342. m_openFileErrorSize ++;
  343. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开WAV文件失败: {}", m_logBase, m_wavFileName.toStdString());
  344. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  345. return false;
  346. }
  347. wavFile.write(reinterpret_cast<const char*>(&m_wavHeader), sizeof(WavHeader));
  348. } else
  349. {
  350. /* 不是新文件 */
  351. wavFile.setFileName(m_wavFileName);
  352. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
  353. {
  354. m_openFileErrorSize ++;
  355. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开WAV文件失败: {}", m_logBase, m_wavFileName.toStdString());
  356. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  357. return false;
  358. }
  359. }
  360. m_openFileErrorSize = 0;
  361. return true;
  362. }
  363. /* 写入音频数据到文件 */
  364. bool CreateLongFileThread::writeAudioDataToFile(const AudioSrcData& audioData, const QString& fileName)
  365. {
  366. return true;
  367. }
  368. /* 生成文件名 */
  369. QString CreateLongFileThread::generateFileName(const QDateTime& startTime, const QDateTime& endTime) const
  370. {
  371. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 生成文件名: 开始时间: {}, 结束时间: {}",
  372. // m_logBase, startTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(),
  373. // endTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
  374. QString chnannelStr = QString("%1-%2").arg(m_threadInfo.cardRoadInfo.strSoundCardID)
  375. .arg(QString::number(m_threadInfo.cardRoadInfo.roadInfo.nRoadNum));
  376. QString fileName = QString("%1_%2-%3.wav")
  377. .arg(chnannelStr)
  378. .arg(startTime.toString("yyyyMMdd_hhmmss"))
  379. .arg(endTime.toString("yyyyMMdd_hhmmss"));
  380. /* 通过目录获取文件的全路径,dir不会检查文件是否存在 */
  381. return m_todayDir.filePath(fileName);
  382. }
  383. /* 判断是否过了整点 */
  384. bool CreateLongFileThread::isOneHourPassed()
  385. {
  386. if(m_writtenSize >= m_oneHourSize)
  387. {
  388. return true; // 已经写入的数据大小超过了一小时的大小
  389. }
  390. /* 下面是判断刚启动的时候,到下一个整点不足1小时,也会保存文件 */
  391. int minute = m_writtenNowTime.time().minute();
  392. bool isPassed = false;
  393. /* 如果当前时间的分钟数小于等于2,并且已经写入的大小超过2分钟的大小 */
  394. if(minute <= 2 && m_writtenSize >= m_writeCriticalSize * 2)
  395. {
  396. isPassed = true;
  397. }
  398. /* 或者已经写入的数据大小超过了一小时的大小,则认为过了整点 */
  399. if(m_writtenSize >= m_oneHourSize)
  400. {
  401. isPassed = true;
  402. }
  403. return isPassed;
  404. }
  405. /* 生成报警文件名 */
  406. QString CreateLongFileThread::generateAlarmFileName(const AlarmValue_t& value, bool isNewFile)
  407. {
  408. QString retFileName;
  409. if(isNewFile)
  410. {
  411. /* 先检查是否已经过了一天了,设置日期文件夹 */
  412. setTodayAlarmPath();
  413. /* 检查这个对比项的报警文件夹是否存在 */
  414. QString itemDirName = QString("CompareItemID_%1").arg(QString::number(value.numConpareItemID));
  415. QDir itemDir = m_todayDirAlarm;
  416. itemDir.cd(itemDirName);
  417. if(!itemDir.exists())
  418. {
  419. if(!itemDir.mkpath(itemDirName))
  420. {
  421. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建报警文件夹失败: {}", m_logBase, itemDirName.toStdString());
  422. return QString(); // 创建目录失败
  423. } else {
  424. SPDLOG_LOGGER_INFO(m_logger, "{} 创建报警文件夹成功: {}", m_logBase, itemDirName.toStdString());
  425. }
  426. }
  427. /* 生成文件名, 格式: Alarm_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
  428. 这里创建的录音文件只有开始日期,没有结束日期,等报警结束之后才会有结束日期 */
  429. QString fileName = QString("Alarm_%1_%2_%3-.wav")
  430. .arg(QString::number(value.itemRoadInfo.nCompareRoadNum))
  431. .arg(getAlarmTypeString(value.alarmType))
  432. .arg(value.startTime.toString("yyyyMMdd_hhmmss"));
  433. /* 拼接文件夹路径 */
  434. retFileName = itemDir.filePath(fileName);
  435. }else
  436. {
  437. /* 已有的文件,是报警结束的文件名 */
  438. retFileName = QString::fromStdString(value.fileName.toStdString());
  439. /* 这里的文件名格式是:Alarm_CompareItemID_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav,
  440. 这里带上结束时间 */
  441. QString endTimeStr = value.endTime.toString("yyyyMMdd_hhmmss");
  442. /* 替换掉原来的结束时间 */
  443. retFileName.replace("-.wav", QString("-%1.wav").arg(endTimeStr));
  444. }
  445. return retFileName;
  446. }
  447. /* 写入报警文件 */
  448. void CreateLongFileThread::writeAlarmFile()
  449. {
  450. const int writeSeconds = 2;
  451. const int newAlarmSeconds = 10;
  452. /* 新文件,从报警时间往前推1秒写入 */
  453. std::list<AudioSrcData*> newDataList;
  454. /* 给已经写入的问价使用 */
  455. std::list<AudioSrcData*> dataList;
  456. {
  457. /* 先判断环形队列中数据是否足够的秒数,不一定是每一秒都会写的 */
  458. std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
  459. /* 目前暂定2秒写一次 */
  460. if(m_numNewAlarmSeconds < writeSeconds)
  461. {
  462. /* 不够两秒时间,退出 */
  463. return;
  464. }
  465. /* 取出环形队列中的数据 */
  466. int size = m_ringQueue.QueueSize();
  467. for(int i = size - writeSeconds; i < size; ++i)
  468. {
  469. AudioSrcData* data = m_ringQueue[i];
  470. if(data != nullptr)
  471. {
  472. dataList.push_back(data);
  473. }
  474. }
  475. int start = size - newAlarmSeconds;
  476. if(start < 0)
  477. {
  478. start = 0; // 防止越界
  479. }
  480. /* 取出新数据,往前推10秒 */
  481. for(int i = start; i < size; ++i)
  482. {
  483. AudioSrcData* data = m_ringQueue[i];
  484. if(data != nullptr)
  485. {
  486. newDataList.push_back(data);
  487. }
  488. }
  489. }
  490. std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
  491. /* 将数据循环写入到文件中 */
  492. for(auto& alarmInfo : m_mapAlarmFile)
  493. {
  494. /* 获取报警信息 */
  495. // const AlarmKey_t& key = alarmInfo.first;
  496. AlarmValue_t& value = alarmInfo.second;
  497. /* 判断是新否是新录音的文件 */
  498. if(value.state == eRecordState::eAlarm_Init)
  499. {
  500. /* 新的报警录音,生成文件名 */
  501. value.fileName = generateAlarmFileName(value, true);
  502. value.state = eRecordState::eAlarm_Recording; // 设置为录音状态
  503. SPDLOG_LOGGER_DEBUG(m_logger, "{} 开始写入报警文件: {}", m_logBase, value.fileName.toStdString());
  504. /* 计算出报警开始位置在报警文件中的偏移,这个偏移值在设置结束的时候取出 */
  505. for(const auto& audioData : newDataList)
  506. {
  507. if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
  508. {
  509. continue; // 跳过空数据
  510. }
  511. if(audioData->startTime <= value.startTime)
  512. {
  513. /* 计算报警开始位置 */
  514. value.alarmStartPos = audioData->startTime.secsTo(value.startTime);
  515. }
  516. }
  517. /* 打开文件 */
  518. QFile wavFile(value.fileName);
  519. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
  520. {
  521. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
  522. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  523. continue;
  524. }
  525. /* 写入音频数据到文件 */
  526. for(auto& audioData : newDataList)
  527. {
  528. if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
  529. {
  530. continue; // 跳过空数据
  531. }
  532. auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
  533. if(writeSize < 0)
  534. {
  535. SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
  536. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  537. }
  538. }
  539. wavFile.close();
  540. } else
  541. {
  542. /* 已经存在的文件,直接写入新录制的文件 */
  543. /* 打开文件 */
  544. QFile wavFile(value.fileName);
  545. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
  546. {
  547. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
  548. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  549. continue;
  550. }
  551. /* 写入音频数据到文件 */
  552. for(auto& audioData : dataList)
  553. {
  554. if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
  555. {
  556. continue; // 跳过空数据
  557. }
  558. auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
  559. if(writeSize < 0)
  560. {
  561. SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
  562. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  563. break; // 写入失败,跳出循环
  564. }
  565. }
  566. wavFile.close();
  567. }
  568. }
  569. /* 处理已经结束的报警,修改文件名,并将其移除队列中 */
  570. for(auto it = m_mapAlarmFile.begin(); it != m_mapAlarmFile.end(); )
  571. {
  572. AlarmValue_t& value = it->second;
  573. if(value.state == eRecordState::eAlarm_Stopped)
  574. {
  575. /* 已经结束的报警,修改文件名 */
  576. modifyFileName(value.fileName, value.fileNameEnd);
  577. /* 移除这个报警 */
  578. it = m_mapAlarmFile.erase(it);
  579. } else {
  580. ++it; // 继续下一个
  581. }
  582. }
  583. }
  584. /* 设置今日报警文件夹 */
  585. bool CreateLongFileThread::setTodayAlarmPath()
  586. {
  587. /* 先检查当天日期文件夹是否存在,因为其他线程可能已经创建了 */
  588. QDate today = QDate::currentDate();
  589. if(today == m_todayDateAlarm)
  590. {
  591. return true; // 今天的目录已经存在
  592. }
  593. m_todayDateAlarm = today;
  594. /* 创建今日报警目录 */
  595. /* 格式: Application/2025-07-21 */
  596. QString todayDirName = QString("%1/%2").arg(GInfo.alarmWavPath()).arg(today.toString("yyyy-MM-dd"));
  597. m_todayDirAlarm.setPath(todayDirName);
  598. if(!m_todayDirAlarm.exists())
  599. {
  600. if(!m_todayDirAlarm.mkpath(todayDirName))
  601. {
  602. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建今日报警目录失败: {}", m_logBase, todayDirName.toStdString());
  603. return false; // 创建目录失败
  604. } else {
  605. SPDLOG_LOGGER_INFO(m_logger, "{} 创建今日报警目录成功: {}", m_logBase, todayDirName.toStdString());
  606. }
  607. }
  608. /* 设置目录 */
  609. m_todayDirAlarm.setPath(todayDirName);
  610. QString yesterdayDirName = QString("%1/%2").arg(GInfo.alarmWavPath()).arg(QDate::currentDate().addDays(-1).toString("yyyy-MM-dd"));
  611. m_yesterdayDir.setPath(yesterdayDirName);
  612. return true;
  613. }
  614. /* 根据报警类型的枚举获取字符 */
  615. QString CreateLongFileThread::getAlarmTypeString(EAlarmType type) const
  616. {
  617. switch(type)
  618. {
  619. case EAlarmType::EAT_Silent:
  620. return "Silent";
  621. case EAlarmType::EAT_Overload:
  622. return "Overload";
  623. case EAlarmType::EAT_Reversed:
  624. return "Reversed";
  625. case EAlarmType::EAR_Consistency:
  626. return "Consistency";
  627. case EAlarmType::EAT_Noise:
  628. return "Noise";
  629. default:
  630. return "Unknown";
  631. }
  632. }