CreateRecordFileThread.cpp 32 KB

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