CreateRecordFileThread.cpp 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. #include "CreateRecordFileThread.h"
  2. #include "AudioData.h"
  3. #include "GlobalInfo.h"
  4. #include "spdlog/spdlog.h"
  5. #include "ThreadWriteDBManager.h"
  6. #include "SystemConfig.h"
  7. #include <cstring>
  8. #include <mutex>
  9. #include <QString>
  10. #include <qdatetime.h>
  11. CreateRecordFileThread::CreateRecordFileThread(RecordThreadInfo_t& threadInfo)
  12. : BaseRecordThread(threadInfo)
  13. {
  14. }
  15. CreateRecordFileThread::~CreateRecordFileThread()
  16. {
  17. }
  18. /* 设置数据 */
  19. bool CreateRecordFileThread::setData(const AudioSrcData& srcData)
  20. {
  21. if(srcData.pData == nullptr || srcData.dataSize == 0)
  22. {
  23. SPDLOG_LOGGER_ERROR(m_logger, "{} 设置数据失败,srcData为空或dataSize为0", m_logBase);
  24. return false;
  25. }
  26. /* ------------------------------------------------------------------------------- */
  27. /* 写入数据到环形缓冲区 */
  28. {
  29. std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
  30. auto oldData = m_ringQueue.push(new AudioSrcData(srcData));
  31. /* 新的元素数加1 */
  32. m_numNewAlarmSeconds++;
  33. m_numNewRecordSeconds++;
  34. if(oldData != nullptr)
  35. {
  36. delete oldData;
  37. oldData = nullptr;
  38. }
  39. }
  40. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 设置数据,dataSize: {}, startTime: {}, endTime: {}",
  41. // 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());
  42. return true;
  43. }
  44. /* 开始录制长文件 */
  45. bool CreateRecordFileThread::startRecordLongFile(const OneCompareItemRoadInfo_t& compareItemRoadInfo)
  46. {
  47. if(compareItemRoadInfo.scRoadInfo.strSoundCardName != m_threadInfo.cardRoadInfo.strSoundCardName ||
  48. compareItemRoadInfo.scRoadInfo.pcmInfo.strPCMName != m_threadInfo.cardRoadInfo.pcmInfo.strPCMName)
  49. {
  50. SPDLOG_LOGGER_ERROR(m_logger, "{} 开始录制音频文件失败,通道信息不匹配", m_logBase);
  51. return false;
  52. }
  53. std::lock_guard<std::mutex> lock(m_mutexCompareItemRoadInfo);
  54. if(m_listCompareItemRoadInfo.contains(compareItemRoadInfo))
  55. {
  56. return true;
  57. }
  58. m_listCompareItemRoadInfo.append(compareItemRoadInfo);
  59. m_isRequireRecord.store(true); // 设置为需要录音状态
  60. return true;
  61. }
  62. /* 停止录制长文件 */
  63. bool CreateRecordFileThread::stopRecordLongFile(const OneCompareItemRoadInfo_t& compareItemRoadInfo)
  64. {
  65. if(compareItemRoadInfo.scRoadInfo.strSoundCardName != m_threadInfo.cardRoadInfo.strSoundCardName ||
  66. compareItemRoadInfo.scRoadInfo.pcmInfo.strPCMName != m_threadInfo.cardRoadInfo.pcmInfo.strPCMName)
  67. {
  68. SPDLOG_LOGGER_ERROR(m_logger, "{} 结束录制音频文件失败,通道信息不匹配", m_logBase);
  69. return false;
  70. }
  71. std::lock_guard<std::mutex> lock(m_mutexCompareItemRoadInfo);
  72. if(!m_listCompareItemRoadInfo.contains(compareItemRoadInfo))
  73. {
  74. // SPDLOG_LOGGER_WARN(m_logger, "{} 停止录制音频文件失败,通道信息不在列表中", m_logBase);
  75. return true;
  76. }
  77. /* 从列表中移除 */
  78. m_listCompareItemRoadInfo.removeAll(compareItemRoadInfo);
  79. if(m_listCompareItemRoadInfo.isEmpty())
  80. {
  81. /* 如果没有对比项信息了,则设置为不需要录音状态 */
  82. m_isRequireRecord.store(false);
  83. clearData();
  84. }
  85. return true;
  86. }
  87. /* 开启录制 */
  88. bool CreateRecordFileThread::startRecordAlarmFile(const AlarmInfo_t& alarmInfo)
  89. {
  90. {
  91. std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
  92. /* 先检查是否已经在队列中了 */
  93. AlarmKey_t key = {alarmInfo.CompareItemID, alarmInfo.RoadInfo.nCompareRoadNum,
  94. alarmInfo.AlarmType, alarmInfo.StartTime};
  95. if(m_mapAlarmFile.find(key) != m_mapAlarmFile.end())
  96. {
  97. SPDLOG_LOGGER_WARN(m_logger, "{} 对比项ID:{},{}, 通道:{}, 报警类型: {}, 已经在报警录制队列中,无法重复添加", m_logBase,
  98. alarmInfo.CompareItemID, alarmInfo.strCompareItemName.toStdString(), alarmInfo.RoadInfo.nCompareRoadNum,
  99. static_cast<int>(alarmInfo.AlarmType));
  100. return true;
  101. }
  102. /* 将数据值插入map中 */
  103. AlarmValue_t value;
  104. value.alarmType = alarmInfo.AlarmType;
  105. value.startTime = alarmInfo.StartTime;
  106. value.endTime = alarmInfo.EndTime;
  107. value.numConpareItemID = alarmInfo.CompareItemID;
  108. value.strCompareItemName = alarmInfo.strCompareItemName;
  109. value.itemRoadInfo = alarmInfo.RoadInfo;
  110. m_mapAlarmFile.insert({key, value});
  111. }
  112. m_cond_var.notify_one();
  113. return true;
  114. }
  115. /* 停止录制,alarmInfo既是传入参数,也是传出参数,传出文件路径和开始位置 */
  116. bool CreateRecordFileThread::stopRecordAlarmFile(AlarmInfo_t& alarmInfo)
  117. {
  118. std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
  119. for(auto& it : m_mapAlarmFile)
  120. {
  121. if(it.first.compareItemID == alarmInfo.CompareItemID &&
  122. it.first.roadNum == alarmInfo.RoadInfo.nCompareRoadNum &&
  123. it.first.alarmType == alarmInfo.AlarmType &&
  124. it.second.startTime == alarmInfo.StartTime )
  125. {
  126. /* 找到对应的报警信息 */
  127. AlarmValue_t& value = it.second;
  128. value.endTime = alarmInfo.EndTime; // 设置结束时间
  129. value.state = eRecordState::eRS_RecordCompleted; // 设置为录音完成
  130. value.fileNameEnd = generateAlarmFileName(value, false); // 生成结束文件名
  131. alarmInfo.strAlarmFilePath = value.fileNameEnd; // 返回文件路径
  132. alarmInfo.AlarmStartPos = value.alarmStartPos; // 返回开始位置
  133. SPDLOG_LOGGER_INFO(m_logger, "{} 停止录制报警文件: {}, 开始位置: {} 秒",
  134. m_logBase, value.fileNameEnd.toStdString(), value.alarmStartPos);
  135. return true;
  136. }
  137. }
  138. return true;
  139. }
  140. /**
  141. * @brief 生成长文件的线程函数,文件生成逻辑如下
  142. * 1、这里有一个缓冲区,存储音频数据,缓冲区大小是2分钟的数据
  143. * 2、每分钟写入一次文件,文件名格式为:ChannelX_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
  144. * 3、文件总长度是1小时的数据,超过1小时则重新开始记录一个新的文件
  145. *
  146. */
  147. void CreateRecordFileThread::task()
  148. {
  149. SPDLOG_LOGGER_INFO(m_logger, "➢ {} 开启记录文件线程 ", m_logBase);
  150. /* 计算一小时的文件大小 */
  151. initData();
  152. while(m_isRunning)
  153. {
  154. /* 使用条件变量阻塞线程,加快开启报警时的响应速度 */
  155. std::unique_lock<std::mutex> lock(m_mutexCondVar);
  156. m_cond_var.wait_for(lock, std::chrono::milliseconds(500));
  157. /*--------------------------------------------------------------
  158. * 写入报警文件
  159. *--------------------------------------------------------------*/
  160. writeAlarmFile();
  161. /*--------------------------------------------------------------
  162. * 写入长记录文件
  163. *--------------------------------------------------------------*/
  164. writeLongRecordFile();
  165. }
  166. clearData();
  167. SPDLOG_LOGGER_WARN(m_logger, "➢ {} 记录长文件线程结束运行", m_logBase);
  168. }
  169. /* 初始化一些数据 */
  170. bool CreateRecordFileThread::initData()
  171. {
  172. m_logBase = fmt::format("录音通道 {}:{} - 录音文件线程",
  173. m_threadInfo.cardRoadInfo.strSoundCardName, m_threadInfo.cardRoadInfo.pcmInfo.strPCMName);
  174. /* 获取全局数据 */
  175. m_sampleRate = GInfo.sampleRate(); /* 采样率 */
  176. m_numChannels = GInfo.numChannels(); /* 声道数 */
  177. m_bitsPerSample = GInfo.bitsPerSample(); /* 每个采样点的位数 */
  178. /* 一秒的数据大小 */
  179. m_oneSecondSize = m_sampleRate * m_numChannels * (m_bitsPerSample / 8);
  180. /* 一次写入的大小,10秒钟 */
  181. // m_writeCriticalSize = m_oneSecondSize * 10;
  182. /* 一小时数据大小 */
  183. // m_oneHourSize = m_oneSecondSize * 60 * 60;
  184. /* 给缓存分配空间 */
  185. // m_bufferData.allocateMemory(m_writeCriticalSize * 3);
  186. // m_srcData.allocateMemory(m_writeCriticalSize * 3);
  187. /* 设置环形队列大小 */
  188. // m_ringQueue.setQueueCapacity(GInfo.queueElementCount());
  189. m_ringQueue.setQueueCapacity(180); /* 3分钟数据 */
  190. return true;
  191. }
  192. /* 清理数据 */
  193. void CreateRecordFileThread::clearData()
  194. {
  195. /* 清理缓存数据 */
  196. // std::lock_guard<std::mutex> lock(m_mutexBuffer);
  197. // m_bufferData.clear();
  198. // m_srcData.clear();
  199. std::lock_guard<std::mutex> lockSrcData(m_ringQueue.mutex);
  200. while(!m_ringQueue.isEmpty())
  201. {
  202. auto data = m_ringQueue.front_pop();
  203. if(data != nullptr)
  204. {
  205. delete data;
  206. data = nullptr;
  207. }
  208. }
  209. }
  210. /* 写入长记录文件 */
  211. bool CreateRecordFileThread::writeLongRecordFile()
  212. {
  213. /* 判断是否需要录音 */
  214. std::lock_guard<std::mutex> lock(m_mutexCompareItemRoadInfo);
  215. if(m_isRequireRecord.load() == false)
  216. {
  217. /* 没有对比项信息,不进行录音 */
  218. /* 判断当前有没有录音,如果有,则结束录音 */
  219. m_numNewRecordSeconds = 0;
  220. return true;
  221. }
  222. /* 取出最新的 m_numNewRecordSeconds 个音频数据 */
  223. std::list<AudioSrcData*> newList;
  224. {
  225. std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
  226. /* 判断缓存是否达到写入数据的临界值 */
  227. if(m_numNewRecordSeconds < 10)
  228. {
  229. return true; // 缓存数据不足,继续等待
  230. }
  231. int startPos = m_ringQueue.QueueSize() - m_numNewRecordSeconds;
  232. if(startPos < 0)
  233. {
  234. startPos = 0;
  235. }
  236. for(int i = startPos; i < m_ringQueue.QueueSize(); i++)
  237. {
  238. newList.push_back(m_ringQueue[i]);
  239. }
  240. }
  241. /*--------------------------------------------------------------
  242. * 打开文件。写入的时候判断是否到达了整点,如果到达了整点,则关闭文件
  243. * 重新创建一个新的文件
  244. *--------------------------------------------------------------*/
  245. bool isPassed = isOneHourPassed(newList);
  246. /* 判断 newList 中有没有跨过整点,如果有则分开,分别写入 */
  247. std::list<AudioSrcData*> writeList;
  248. std::list<AudioSrcData*> writeNewFileList;
  249. if(isPassed)
  250. {
  251. /* 跨过了整点 */
  252. for(const auto& it : newList)
  253. {
  254. if(it == nullptr || it->dataSize == 0)
  255. {
  256. continue;
  257. }
  258. if(it->startTime.time().minute() == 59)
  259. {
  260. writeList.push_back(it);
  261. }else {
  262. writeNewFileList.push_back(it);
  263. }
  264. }
  265. } else {
  266. writeList = newList;
  267. }
  268. /* 判断是不是刚启动的新文件 */
  269. if(m_writtenSize == 0)
  270. {
  271. if(isPassed)
  272. {
  273. /* 刚启动,并且这几秒内也跨过了整点,直接写入并结束这个文件 */
  274. writeRecordFileData(writeList, true, true);
  275. /* 写新的文件 */
  276. writeRecordFileData(writeNewFileList, true, false);
  277. }else {
  278. /* 刚启动,并且没有跨过整点,直接写入 */
  279. writeRecordFileData(writeList, true, false);
  280. }
  281. }else
  282. {
  283. if(isPassed)
  284. {
  285. if(writeList.size() == 0)
  286. {
  287. /* 跨过了整点,但是没有数据需要写入 */
  288. endRecordFile();
  289. }else {
  290. /* 不是新文件,并且跨过了整点,先写完这个文件,然后结束 */
  291. writeRecordFileData(writeList, false, true);
  292. }
  293. /* 再写新的文件 */
  294. writeRecordFileData(writeNewFileList, true, false);
  295. }else {
  296. /* 不是新文件,也没有跨过整点,直接写入 */
  297. writeRecordFileData(writeList, false, false);
  298. }
  299. }
  300. m_numNewRecordSeconds = 0;
  301. return true;
  302. }
  303. /* 写入数据 */
  304. bool CreateRecordFileThread::writeRecordFileData(const std::list<AudioSrcData*>& dataList, bool isNewFile, bool isRecordCompleted)
  305. {
  306. if(dataList.empty())
  307. {
  308. return true;
  309. }
  310. if(isNewFile)
  311. {
  312. /* 如果没有写入过数据,则是新文件 */
  313. m_writtenStartTime = dataList.front()->startTime;
  314. m_writtenNowTime = m_writtenStartTime;
  315. // SPDLOG_LOGGER_WARN(m_logger, "新录音文件: {} - {}", m_writtenStartTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(),
  316. // m_writtenNowTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
  317. }
  318. /* 设置今日目录 */
  319. if(isNewFile)
  320. {
  321. if(!setTodayPath(isNewFile))
  322. {
  323. return false;
  324. }
  325. }
  326. /* 打开文件 */
  327. QFile wavFile;
  328. if(!openFile(wavFile, isNewFile))
  329. {
  330. if(m_openFileErrorSize >= 3)
  331. {
  332. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开文件失败次数过多,重新开始记录", m_logBase);
  333. m_writtenSize = 0;
  334. m_writtenStartTime = QDateTime::currentDateTime(); // 重新开始时间
  335. m_writtenNowTime = m_writtenStartTime; // 重新开始时间
  336. m_wavFileName.clear(); // 清空文件名
  337. // m_srcData.clear(); // 清空缓冲区数据
  338. m_openFileErrorSize = 0; // 重置错误次数
  339. return false; // 重新开始记录
  340. }
  341. }
  342. /*--------------------------------------------------------------
  343. * 将数据写入文件,并记录其携带的时间和写入的数据大小
  344. *--------------------------------------------------------------*/
  345. int64_t wSize = 0;
  346. bool isWriteSucess = true;
  347. {
  348. for(auto it : dataList)
  349. {
  350. if(it == nullptr || it->dataSize == 0)
  351. {
  352. continue;
  353. }
  354. int64_t thisWSize = wavFile.write(it->pData, it->dataSize);
  355. if(thisWSize > 0)
  356. {
  357. wSize += thisWSize;
  358. m_writtenNowTime = it->endTime; // 记录当前结束时间
  359. }else {
  360. isWriteSucess = false;
  361. break;
  362. }
  363. }
  364. }
  365. if(isWriteSucess == false)
  366. {
  367. SPDLOG_LOGGER_ERROR(m_logger, "{} 写入WAV文件失败: {}", m_logBase, wavFile.errorString().toStdString());
  368. SPDLOG_LOGGER_WARN(m_logger, "文件路径:{}", m_wavFileName.toStdString());
  369. wavFile.close();
  370. return false;
  371. }
  372. SPDLOG_LOGGER_TRACE(m_logger, "{} 写入WAV文件成功: {}, 大小: {} 字节", m_logBase, m_wavFileName.toStdString(), wSize);
  373. wavFile.close();
  374. // SPDLOG_LOGGER_DEBUG(m_logger, "写入录音文件成功, 写入的时间段: {} - {}",
  375. // m_writtenStartTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(), m_writtenNowTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
  376. /*--------------------------------------------------------------
  377. * 对该文件进行其他操作,判断是否已经过了一个整点,修改其文件名称
  378. * 现在这里的时间是这一分钟的开始时间,现在需要根据开始时间求出已写入
  379. * 数据大小对应的结束时间
  380. *--------------------------------------------------------------*/
  381. m_writtenSize += wSize;
  382. /* 修改文件名称 */
  383. QString newFileName = generateFileName(m_writtenStartTime, m_writtenNowTime);
  384. if(modifyFileName(m_wavFileName, newFileName))
  385. {
  386. m_wavFileName = newFileName;
  387. }else {
  388. SPDLOG_LOGGER_ERROR(m_logger, "{} 修改文件名失败: {} -> {}", m_logBase, m_wavFileName.toStdString(), newFileName.toStdString());
  389. }
  390. /* 判断是否需要结束文件 */
  391. if(isRecordCompleted)
  392. {
  393. /* 修改文件头中记录的数据大小 */
  394. m_wavHeader.setDataSize(m_writtenSize);
  395. m_wavHeader.calculateDerivedFields();
  396. modifyWavFileHeader(m_wavFileName, m_wavHeader);
  397. SPDLOG_LOGGER_INFO(m_logger, "{} 结束记录一个文件: {}, 已写入大小: {} 字节",
  398. m_logBase, m_wavFileName.toStdString(), m_writtenSize);
  399. }
  400. /* 更新文件信息到数据库 */
  401. updateRecordFileInfoToDB(isNewFile, isRecordCompleted);
  402. /* 清空已经结束的文件信息 */
  403. if(isRecordCompleted)
  404. {
  405. /* 重置已写入大小 */
  406. m_writtenSize = 0;
  407. m_writtenStartTime = QDateTime(); // 重新开始时间
  408. m_writtenNowTime = QDateTime(); // 重新开始时间
  409. m_wavFileName.clear(); // 清空文件名
  410. m_openFileErrorSize = 0; // 重置错误次数
  411. }
  412. return true;
  413. }
  414. /* 结束文件录制 */
  415. bool CreateRecordFileThread::endRecordFile()
  416. {
  417. /* 修改文件头中记录的数据大小 */
  418. m_wavHeader.setDataSize(m_writtenSize);
  419. m_wavHeader.calculateDerivedFields();
  420. modifyWavFileHeader(m_wavFileName, m_wavHeader);
  421. SPDLOG_LOGGER_INFO(m_logger, "{} 结束记录一个文件: {}, 已写入大小: {} 字节",
  422. m_logBase, m_wavFileName.toStdString(), m_writtenSize);
  423. /* 更新文件信息到数据库 */
  424. updateRecordFileInfoToDB(false, true);
  425. /* 清空已经结束的文件信息 */
  426. m_writtenSize = 0;
  427. m_writtenStartTime = QDateTime(); // 重新开始时间
  428. m_writtenNowTime = QDateTime(); // 重新开始时间
  429. m_wavFileName.clear(); // 清空文件名
  430. m_openFileErrorSize = 0; // 重置错误次数
  431. return true;
  432. }
  433. /* 设置今日目录 */
  434. bool CreateRecordFileThread::setTodayPath(bool isNewFile)
  435. {
  436. if(!isNewFile)
  437. {
  438. return true;
  439. }
  440. /* 判断现在日期是否还是当天日期 */
  441. QDate today = QDate::currentDate();
  442. if(m_todayDateRecord == today)
  443. {
  444. return true;
  445. }
  446. m_todayDateRecord = today;
  447. /* 先检查现在的日期文件夹是否存在,因为其他线程可能已经创建了 */
  448. QString todayDirName = QString("%1/%2").arg(GInfo.longWavPath()).arg(m_todayDateRecord.toString("yyyy-MM-dd"));
  449. m_todayDir.setPath(todayDirName);
  450. if(!m_todayDir.exists())
  451. {
  452. if(!m_todayDir.mkpath(todayDirName))
  453. {
  454. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建今日目录失败: {}", m_logBase, todayDirName.toStdString());
  455. return false; // 创建目录失败
  456. } else {
  457. SPDLOG_LOGGER_INFO(m_logger, "{} 创建今日目录成功: {}", m_logBase, todayDirName.toStdString());
  458. }
  459. }
  460. /* 创建这个通道的文件夹,文件夹格式: AudioPCI-0 */
  461. QString roadDirName = QString("%1/%2-%3")
  462. .arg(todayDirName)
  463. .arg(QString::fromStdString(m_threadInfo.cardRoadInfo.strSoundCardName))
  464. .arg(QString::fromStdString(m_threadInfo.cardRoadInfo.pcmInfo.strPCMName));
  465. m_todayDir.setPath(roadDirName);
  466. if(!m_todayDir.exists())
  467. {
  468. SPDLOG_LOGGER_WARN(m_logger, "{} 记录文件目录不存在: {}", m_logBase, roadDirName.toStdString());
  469. if(!m_todayDir.mkpath(roadDirName))
  470. {
  471. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建目录失败: {}", m_logBase, roadDirName.toStdString());
  472. return false;
  473. } else {
  474. SPDLOG_LOGGER_INFO(m_logger, "{} 创建目录成功: {}", m_logBase, roadDirName.toStdString());
  475. }
  476. }
  477. return true;
  478. }
  479. /* 打开文件 */
  480. bool CreateRecordFileThread::openFile(QFile& wavFile, bool isNewFile)
  481. {
  482. if(isNewFile)
  483. {
  484. /* 如果没有写入过数据,则生成一个新的文件名 */
  485. m_wavFileName = generateFileName(m_writtenStartTime, m_writtenNowTime);
  486. m_wavHeader.setSampleRate(m_sampleRate);
  487. m_wavHeader.setNumChannels(m_numChannels);
  488. m_wavHeader.setBitsPerSample(m_bitsPerSample);
  489. m_wavHeader.setDataSize(m_writtenSize);
  490. m_wavHeader.calculateDerivedFields();
  491. wavFile.setFileName(m_wavFileName);
  492. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
  493. {
  494. m_openFileErrorSize ++;
  495. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开WAV文件失败: {}", m_logBase, m_wavFileName.toStdString());
  496. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  497. return false;
  498. }
  499. wavFile.write(reinterpret_cast<const char*>(&m_wavHeader), sizeof(WavHeader));
  500. } else
  501. {
  502. /* 不是新文件 */
  503. wavFile.setFileName(m_wavFileName);
  504. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
  505. {
  506. m_openFileErrorSize ++;
  507. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开WAV文件失败: {}", m_logBase, m_wavFileName.toStdString());
  508. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  509. return false;
  510. }
  511. }
  512. m_openFileErrorSize = 0;
  513. return true;
  514. }
  515. /* 写入音频数据到文件 */
  516. bool CreateRecordFileThread::writeAudioDataToFile(const AudioSrcData& audioData, const QString& fileName)
  517. {
  518. return true;
  519. }
  520. /* 生成文件名 */
  521. QString CreateRecordFileThread::generateFileName(const QDateTime& startTime, const QDateTime& endTime) const
  522. {
  523. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 生成文件名: 开始时间: {}, 结束时间: {}",
  524. // m_logBase, startTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(),
  525. // endTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
  526. QString chnannelStr = QString("%1-%2").arg(QString::fromStdString(m_threadInfo.cardRoadInfo.strSoundCardName))
  527. .arg(QString::fromStdString(m_threadInfo.cardRoadInfo.pcmInfo.strPCMName));
  528. QString fileName = QString("%1_%2-%3.wav")
  529. .arg(chnannelStr)
  530. .arg(startTime.toString("yyyyMMdd_hhmmss"))
  531. .arg(endTime.toString("yyyyMMdd_hhmmss"));
  532. /* 通过目录获取文件的全路径,dir不会检查文件是否存在 */
  533. return m_todayDir.filePath(fileName);
  534. }
  535. /* 判断是否过了整点 */
  536. // bool CreateRecordFileThread::isOneHourPassed()
  537. // {
  538. // if(m_writtenSize >= m_oneHourSize)
  539. // {
  540. // // 已经写入的数据大小超过了一小时的大小
  541. // return true;
  542. // }
  543. // /* 下面是判断刚启动的时候,到下一个整点不足1小时,也会保存文件 */
  544. // int minute = m_writtenNowTime.time().minute();
  545. // bool isPassed = false;
  546. // /* 如果当前时间的分钟数小于等于2,并且已经写入的大小超过三分钟大小 */
  547. // if( (minute <= 2) && (m_writtenSize > m_oneSecondSize * 60 * 3) )
  548. // {
  549. // isPassed = true;
  550. // }
  551. // return isPassed;
  552. // }
  553. bool CreateRecordFileThread::isOneHourPassed(const std::list<AudioSrcData*>& dataList)
  554. {
  555. if(dataList.empty())
  556. {
  557. return false;
  558. }
  559. bool has59Minute = false;
  560. bool has00Minute = false;
  561. /* 计算这些数据中是否跨过了整点 */
  562. for(const auto& it : dataList)
  563. {
  564. if(it == nullptr || it->dataSize == 0)
  565. {
  566. continue;
  567. }
  568. if(it->startTime.time().minute() == 59)
  569. {
  570. has59Minute = true;
  571. }
  572. if(it->startTime.time().minute() == 0)
  573. {
  574. has00Minute = true;
  575. }
  576. }
  577. if(has59Minute && has00Minute)
  578. {
  579. return true;
  580. }
  581. return false;
  582. }
  583. /* 更新文件信息到数据库 */
  584. void CreateRecordFileThread::updateRecordFileInfoToDB(bool isNewFile, bool isRecordCompleted)
  585. {
  586. std::list<RecordFileInfo_t> listRecordFileInfo;
  587. for(const auto& it : m_listCompareItemRoadInfo)
  588. {
  589. RecordFileInfo_t recordFileInfo;
  590. recordFileInfo.ItemID = it.nCompareItemID;
  591. recordFileInfo.ItemName = it.strCompareItemName;
  592. recordFileInfo.ItemRoadNum = it.nCompareRoadNum;
  593. recordFileInfo.ItemRoadName = it.strCompareRoadName;
  594. recordFileInfo.scRoadInfo = it.scRoadInfo;
  595. recordFileInfo.FileStartTime = m_writtenStartTime;
  596. recordFileInfo.FileEndTime = m_writtenNowTime;
  597. recordFileInfo.FilePath = m_wavFileName;
  598. recordFileInfo.FileDuration = m_writtenStartTime.secsTo(m_writtenNowTime);
  599. recordFileInfo.fileState = isRecordCompleted ? eRecordState::eRS_RecordCompleted : eRecordState::eRS_Recording;
  600. listRecordFileInfo.push_back(recordFileInfo);
  601. }
  602. WriteDB.addRecordFileInfo(listRecordFileInfo, isNewFile);
  603. }
  604. /* 生成报警文件名 */
  605. QString CreateRecordFileThread::generateAlarmFileName(const AlarmValue_t& value, bool isNewFile)
  606. {
  607. QString retFileName;
  608. if(isNewFile)
  609. {
  610. /* 先检查是否已经过了一天了,设置日期文件夹 */
  611. setTodayAlarmPath();
  612. /* 检查这个对比项的报警文件夹是否存在 */
  613. // QString itemDirName = m_todayDirAlarm.filePath(QString("CompareItemID_%1").arg(QString::number(value.numConpareItemID)));
  614. QString itemDirName = QString("CompareItemID_%1").arg(QString::number(value.numConpareItemID));
  615. QDir itemDir(m_todayDirAlarm.filePath(itemDirName));
  616. if(!itemDir.exists())
  617. {
  618. SPDLOG_LOGGER_WARN(m_logger, "{} 对比项报警文件夹不存在: {}", m_logBase, itemDir.path().toStdString());
  619. if(!itemDir.mkpath("."))
  620. {
  621. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建对比项报警文件夹失败: {}", m_logBase, itemDir.path().toStdString());
  622. return QString(); // 创建目录失败
  623. } else {
  624. SPDLOG_LOGGER_INFO(m_logger, "{} 创建对比项报警文件夹成功: {}", m_logBase, itemDir.path().toStdString());
  625. }
  626. }
  627. /* 生成文件名, 格式: Alarm_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
  628. 这里创建的录音文件只有开始日期,没有结束日期,等报警结束之后才会有结束日期 */
  629. QString fileName = QString("Road%1_%2_%3-.wav")
  630. .arg(QString::number(value.itemRoadInfo.nCompareRoadNum))
  631. .arg(getAlarmTypeString(value.alarmType))
  632. .arg(value.startTime.toString("yyyyMMdd_hhmmss"));
  633. /* 拼接文件夹路径 */
  634. retFileName = itemDir.filePath(fileName);
  635. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 生成新的报警文件名: {}", m_logBase, retFileName.toStdString());
  636. }else
  637. {
  638. /* 已有的文件,是报警结束的文件名 */
  639. retFileName = QString::fromStdString(value.fileName.toStdString());
  640. /* 这里的文件名格式是:Alarm_CompareItemID_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav,
  641. 这里带上结束时间 */
  642. QString endTimeStr = value.endTime.toString("yyyyMMdd_hhmmss");
  643. /* 替换掉原来的结束时间 */
  644. retFileName.replace("-.wav", QString("-%1.wav").arg(endTimeStr));
  645. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 原文件名:{} 生成报警结束文件名: {}", m_logBase, value.fileName.toStdString(), retFileName.toStdString());
  646. }
  647. return retFileName;
  648. }
  649. /* 写入报警文件 */
  650. void CreateRecordFileThread::writeAlarmFile()
  651. {
  652. /* 取出环形队列中后100秒的数据,前面20秒给环形队列满的时候缓冲使用 */
  653. std::list<AudioSrcData*> newDataList;
  654. int writeSize = m_numNewAlarmSeconds > 120 ? m_numNewAlarmSeconds : 120;
  655. {
  656. std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
  657. /* 取出环形队列中的数据 */
  658. int size = m_ringQueue.QueueSize();
  659. int start = size - writeSize;
  660. if(start < 0)
  661. {
  662. start = 0; // 防止越界
  663. }
  664. /* 取出新数据,往前推10秒 */
  665. for(int i = start; i < size; ++i)
  666. {
  667. AudioSrcData* data = m_ringQueue[i];
  668. if(data != nullptr)
  669. {
  670. newDataList.push_back(data);
  671. }
  672. }
  673. }
  674. /* 写入已经创建的报警文件的数据,需要先写入已经创建的文件,再创建新的文件,防止
  675. 创建新的文件后再次写入重复的数据 */
  676. writeExistingAlarmFileData(newDataList);
  677. /* 写入新的报警文件 */
  678. writeNewAlarmFileData(newDataList);
  679. /* 处理已经结束的报警,修改文件名,并将其移除队列中 */
  680. for(auto it = m_mapAlarmFile.begin(); it != m_mapAlarmFile.end(); )
  681. {
  682. AlarmValue_t& value = it->second;
  683. if(value.state == eRecordState::eRS_RecordCompleted)
  684. {
  685. /* 修改wav头文件,修改写入的数据大小 */
  686. value.wavHeader.setDataSize(value.writtenSize);
  687. value.wavHeader.calculateDerivedFields();
  688. modifyWavFileHeader(value.fileName, value.wavHeader);
  689. /* 已经结束的报警,修改文件名 */
  690. if(!modifyFileName(value.fileName, value.fileNameEnd))
  691. {
  692. SPDLOG_LOGGER_ERROR(m_logger, "修改文件名失败: {} -> {}", value.fileName.toStdString(), value.fileNameEnd.toStdString());
  693. }
  694. /* 移除这个报警 */
  695. it = m_mapAlarmFile.erase(it);
  696. } else {
  697. ++it; // 继续下一个
  698. }
  699. }
  700. /* 清空标志位 */
  701. m_numNewAlarmSeconds = 0;
  702. }
  703. /* 创建新的文件 */
  704. void CreateRecordFileThread::createNewAlarmFile(AlarmValue_t& value, const std::list<AudioSrcData*>& dataList)
  705. {
  706. /* 新的报警录音,生成文件名 */
  707. value.fileName = generateAlarmFileName(value, true);
  708. SPDLOG_LOGGER_INFO(m_logger, "{} 开始写入报警文件: {}", m_logBase, value.fileName.toStdString());
  709. /* 计算出报警开始时间在报警文件中的位置 */
  710. int startPos = 0;
  711. for(const auto& audioData : dataList)
  712. {
  713. if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
  714. {
  715. continue; // 跳过空数据
  716. }
  717. if(audioData->startTime >= value.startTime)
  718. {
  719. /* 计算报警开始位置 */
  720. break;
  721. }
  722. startPos++;
  723. }
  724. /* 打开文件 */
  725. QFile wavFile(value.fileName);
  726. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
  727. {
  728. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
  729. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  730. return;
  731. }
  732. /* 先写入wav头文件 */
  733. value.wavHeader.setSampleRate(m_sampleRate);
  734. value.wavHeader.setNumChannels(m_numChannels);
  735. value.wavHeader.setBitsPerSample(m_bitsPerSample);
  736. value.wavHeader.setDataSize(m_writtenSize);
  737. value.wavHeader.calculateDerivedFields();
  738. wavFile.write(reinterpret_cast<const char*>(&value.wavHeader), sizeof(WavHeader));
  739. /* 写入音频数据到文件 */
  740. int nowPos = 0;
  741. for(auto& audioData : dataList)
  742. {
  743. if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
  744. {
  745. continue; // 跳过空数据
  746. }
  747. if(nowPos < startPos)
  748. {
  749. nowPos++;
  750. continue; // 跳过前面的数据
  751. }
  752. auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
  753. if(writeSize < 0)
  754. {
  755. SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
  756. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  757. }else
  758. {
  759. value.writtenSize += writeSize; // 累加写入大小
  760. }
  761. }
  762. wavFile.close();
  763. }
  764. /* 写入新文件数据 */
  765. void CreateRecordFileThread::writeNewAlarmFileData(std::list<AudioSrcData*> dataList)
  766. {
  767. /* 判断是否有新的报警信息 */
  768. bool isNewAlarm = false;
  769. std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
  770. for(const auto& pair : m_mapAlarmFile)
  771. {
  772. const AlarmValue_t& value = pair.second;
  773. if(value.state == eRecordState::eRS_Init)
  774. {
  775. isNewAlarm = true;
  776. break;
  777. }
  778. }
  779. if(!isNewAlarm)
  780. {
  781. return; // 没有新的报警信息
  782. }
  783. /* 创建新文件并写入数据 */
  784. for(auto& alarmInfo : m_mapAlarmFile)
  785. {
  786. AlarmValue_t& value = alarmInfo.second;
  787. /* 判断是新否是新录音的文件 */
  788. if(value.state == eRecordState::eRS_Init)
  789. {
  790. /* 创建新的文件,并写入wav头文件 */
  791. createNewAlarmFile(value, dataList);
  792. value.state = eRecordState::eRS_Recording; // 设置为录音状态
  793. }
  794. }
  795. }
  796. /* 写入已经创建的报警文件数据 */
  797. void CreateRecordFileThread::writeExistingAlarmFileData(std::list<AudioSrcData*> newList)
  798. {
  799. const int listSize = static_cast<int>(newList.size());
  800. if(m_numNewAlarmSeconds > listSize)
  801. {
  802. SPDLOG_LOGGER_ERROR(m_logger, "{} 音频队列中数据小于需要写入的新的数据大小", m_logBase);
  803. return;
  804. }
  805. /* 已经打开的文件写入数据 */
  806. std::list<AudioSrcData*> dataList;
  807. if(m_numNewAlarmSeconds > 0)
  808. {
  809. /* 取出最后 m_numNewAlarmSeconds 个数据 */
  810. int i = newList.size() - m_numNewAlarmSeconds;
  811. for(auto it = newList.end(); i < listSize; ++i)
  812. {
  813. --it;
  814. dataList.push_back(*it);
  815. }
  816. }
  817. /* 写入数据 */
  818. std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
  819. /* 将数据循环写入到文件中 */
  820. for(auto& alarmInfo : m_mapAlarmFile)
  821. {
  822. /* 获取报警信息 */
  823. // const AlarmKey_t& key = alarmInfo.first;
  824. AlarmValue_t& value = alarmInfo.second;
  825. /* 判断是新否是新录音的文件 */
  826. if(eRecordState::eRS_Recording == value.state)
  827. {
  828. /* 已经存在的文件,直接写入新录制的文件 */
  829. /* 打开文件 */
  830. QFile wavFile(value.fileName);
  831. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
  832. {
  833. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
  834. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  835. continue;
  836. }
  837. /* 写入音频数据到文件 */
  838. for(auto& audioData : dataList)
  839. {
  840. if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
  841. {
  842. continue; // 跳过空数据
  843. }
  844. auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
  845. if(writeSize < 0)
  846. {
  847. SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
  848. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  849. break; // 写入失败,跳出循环
  850. }else
  851. {
  852. value.writtenSize += writeSize; // 累加写入大小
  853. }
  854. }
  855. wavFile.close();
  856. }
  857. }
  858. }
  859. /* 设置今日报警文件夹 */
  860. bool CreateRecordFileThread::setTodayAlarmPath()
  861. {
  862. /* 先检查当天日期文件夹是否存在,因为其他线程可能已经创建了 */
  863. QDate today = QDate::currentDate();
  864. if(today == m_todayDateAlarm)
  865. {
  866. return true; // 今天的目录已经存在
  867. }
  868. m_todayDateAlarm = today;
  869. /* 创建今日报警目录 */
  870. /* 格式: Application/2025-07-21 */
  871. QString todayDirName = QString("%1/%2").arg(GInfo.alarmWavPath()).arg(today.toString("yyyy-MM-dd"));
  872. m_todayDirAlarm.setPath(todayDirName);
  873. if(!m_todayDirAlarm.exists())
  874. {
  875. if(!m_todayDirAlarm.mkpath(todayDirName))
  876. {
  877. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建今日报警目录失败: {}", m_logBase, todayDirName.toStdString());
  878. return false; // 创建目录失败
  879. } else {
  880. SPDLOG_LOGGER_INFO(m_logger, "{} 创建今日报警目录成功: {}", m_logBase, todayDirName.toStdString());
  881. }
  882. }
  883. /* 设置目录 */
  884. m_todayDirAlarm.setPath(todayDirName);
  885. QString yesterdayDirName = QString("%1/%2").arg(GInfo.alarmWavPath()).arg(QDate::currentDate().addDays(-1).toString("yyyy-MM-dd"));
  886. m_yesterdayDir.setPath(yesterdayDirName);
  887. return true;
  888. }
  889. /* 根据报警类型的枚举获取字符 */
  890. QString CreateRecordFileThread::getAlarmTypeString(EAlarmType type) const
  891. {
  892. switch(type)
  893. {
  894. case EAlarmType::EAT_Silent:
  895. return "Silent";
  896. case EAlarmType::EAT_Overload:
  897. return "Overload";
  898. case EAlarmType::EAT_Reversed:
  899. return "Reversed";
  900. case EAlarmType::EAR_Consistency:
  901. return "Consistency";
  902. case EAlarmType::EAT_Noise:
  903. return "Noise";
  904. default:
  905. return "Unknown";
  906. }
  907. }