CreateRecordFileThread.cpp 30 KB

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