CreateRecordFileThread.cpp 34 KB

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