CreateLongFileThread.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. #include "CreateLongFileThread.h"
  2. #include "GlobalInfo.h"
  3. #include "spdlog.h"
  4. #include <cstring>
  5. #include <qt5/QtCore/qchar.h>
  6. CreateLongFileThread::CreateLongFileThread(RecordThreadInfo_t& threadInfo)
  7. : BaseRecordThread(threadInfo)
  8. {
  9. m_logger = spdlog::get("RecordAudio");
  10. if(m_logger == nullptr)
  11. {
  12. fmt::print("RecordThread: RecordAudio Logger not found.\n");
  13. return;
  14. }
  15. /* 初始化数据 */
  16. initData();
  17. }
  18. CreateLongFileThread::~CreateLongFileThread()
  19. {
  20. }
  21. /* 设置数据 */
  22. bool CreateLongFileThread::setData(const AudioSrcData& srcData)
  23. {
  24. if(srcData.pData == nullptr || srcData.dataSize == 0)
  25. {
  26. SPDLOG_LOGGER_ERROR(m_logger, "{} 设置数据失败,srcData为空或dataSize为0", m_logBase);
  27. return false;
  28. }
  29. /* 锁定缓冲区 */
  30. std::lock_guard<std::mutex> lock(m_mutexBuffer);
  31. /* 如果缓冲区没有分配内存,先分配 */
  32. if(m_bufferData.pData == nullptr)
  33. {
  34. if(!m_bufferData.allocateMemory(m_writeCriticalSize * 3))
  35. {
  36. SPDLOG_LOGGER_ERROR(m_logger, "{} 分配缓冲区内存失败", m_logBase);
  37. return false;
  38. }
  39. }
  40. /* 添加数据到缓冲区 */
  41. int32_t writtenSize = m_bufferData.appendData(srcData.pData, srcData.dataSize);
  42. if(writtenSize == 0)
  43. {
  44. SPDLOG_LOGGER_ERROR(m_logger, "{} 添加数据到缓冲区失败", m_logBase);
  45. return false;
  46. }
  47. /* 记录日期 */
  48. if(m_bufferData.startTime.isNull() || m_bufferData.startTime.isValid())
  49. {
  50. m_bufferData.startTime = srcData.startTime;
  51. }
  52. m_bufferData.endTime = srcData.endTime;
  53. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 设置数据,dataSize: {}, startTime: {}, endTime: {}",
  54. // 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());
  55. return true;
  56. }
  57. /* 开启录制 */
  58. bool CreateLongFileThread::startRecordAlarmFile(const AlarmInfo_t& alarmInfo)
  59. {
  60. return true;
  61. }
  62. /* 停止录制 */
  63. bool CreateLongFileThread::stopRecordAlarmFile(AlarmInfo_t& alarmInfo)
  64. {
  65. return true;
  66. }
  67. /**
  68. * @brief 生成长文件的线程函数,文件生成逻辑如下
  69. * 1、这里有一个缓冲区,存储音频数据,缓冲区大小是2分钟的数据
  70. * 2、每分钟写入一次文件,文件名格式为:ChannelX_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
  71. * 3、文件总长度是1小时的数据,超过1小时则重新开始记录一个新的文件
  72. *
  73. */
  74. void CreateLongFileThread::task()
  75. {
  76. SPDLOG_LOGGER_INFO(m_logger, "➢ {} 开启记录文件线程 ", m_logBase);
  77. /* 计算一小时的文件大小 */
  78. while(m_isRunning)
  79. {
  80. /* 线程休眠1秒 */
  81. std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  82. /* 判断缓存是否达到1分钟数据临界值 */
  83. {
  84. std::lock_guard<std::mutex> lock(m_mutexBuffer);
  85. if(m_bufferData.dataSize < m_writeCriticalSize)
  86. {
  87. continue; // 缓存数据不足,继续等待
  88. }
  89. /* 数据足够了将缓冲区数据拷贝出来 */
  90. memcpy(m_srcData.pData, m_bufferData.pData, m_bufferData.dataSize);
  91. m_srcData.dataSize = m_bufferData.dataSize;
  92. m_srcData.startTime = m_bufferData.startTime;
  93. m_srcData.endTime = m_bufferData.endTime;
  94. /* 清空缓冲区数据 */
  95. m_bufferData.clear();
  96. }
  97. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 设置数据,dataSize: {}, startTime: {}, endTime: {}",
  98. // 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());
  99. /*--------------------------------------------------------------
  100. * 打开文件。写入的时候判断是否到达了整点,如果到达了整点,则关闭文件
  101. * 重新创建一个新的文件
  102. *--------------------------------------------------------------*/
  103. bool isNewFile = false;
  104. if(m_writtenSize == 0)
  105. {
  106. /* 如果没有写入过数据,则是新文件 */
  107. isNewFile = true;
  108. m_writtenStartTime = m_srcData.startTime; // 记录开始时间
  109. m_writtenNowTime = m_writtenStartTime; // 记录当前时间
  110. }
  111. /* 设置今日目录 */
  112. if(!setTodayPath(isNewFile))
  113. {
  114. continue;
  115. }
  116. /* 打开文件 */
  117. QFile wavFile;
  118. if(!openFile(wavFile, isNewFile))
  119. {
  120. if(m_openFileErrorSize >= 3)
  121. {
  122. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开文件失败次数过多,重新开始记录", m_logBase);
  123. m_writtenSize = 0;
  124. m_writtenStartTime = QDateTime::currentDateTime(); // 重新开始时间
  125. m_writtenNowTime = m_writtenStartTime; // 重新开始时间
  126. m_wavFileName.clear(); // 清空文件名
  127. m_srcData.clear(); // 清空缓冲区数据
  128. m_openFileErrorSize = 0; // 重置错误次数
  129. continue; // 重新开始记录
  130. }
  131. }
  132. /*--------------------------------------------------------------
  133. * 将数据写入文件,并记录其携带的时间和写入的数据大小
  134. *--------------------------------------------------------------*/
  135. int64_t wSize = 0;
  136. {
  137. std::lock_guard<std::mutex> lock(m_mutexBuffer);
  138. wSize = wavFile.write(m_srcData.pData, m_srcData.dataSize);
  139. /* 更新结束时间 */
  140. m_writtenNowTime = m_srcData.endTime;
  141. /* 清空缓冲区 */
  142. m_srcData.clear();
  143. }
  144. if(wSize < 0)
  145. {
  146. SPDLOG_LOGGER_ERROR(m_logger, "{} 写入WAV文件失败: {}", m_logBase, wavFile.errorString().toStdString());
  147. SPDLOG_LOGGER_WARN(m_logger, "文件路径:{}", m_wavFileName.toStdString());
  148. wavFile.close();
  149. continue;
  150. } else {
  151. SPDLOG_LOGGER_TRACE(m_logger, "{} 写入WAV文件成功: {}, 大小: {} 字节", m_logBase, m_wavFileName.toStdString(), wSize);
  152. }
  153. wavFile.close();
  154. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 写入WAV文件完成: {}, 大小: {} 字节",
  155. // m_logBase, m_wavFileName.toStdString(), wSize);
  156. /*--------------------------------------------------------------
  157. * 对该文件进行其他操作,判断是否已经过了一个整点,修改其文件名称
  158. * 现在这里的时间是这一分钟的开始时间,现在需要根据开始时间求出已写入
  159. * 数据大小对应的结束时间
  160. *--------------------------------------------------------------*/
  161. m_writtenSize += wSize;
  162. /* 修改文件名称 */
  163. QString newFileName = generateFileName(m_writtenStartTime, m_writtenNowTime);
  164. if(modifyFileName(m_wavFileName, newFileName))
  165. {
  166. m_wavFileName = newFileName;
  167. }
  168. /* 判断是否过了整点 */
  169. if(isOneHourPassed())
  170. {
  171. /* 修改文件头中记录的数据大小 */
  172. m_wavHeader.setDataSize(m_writtenSize);
  173. m_wavHeader.calculateDerivedFields();
  174. modifyWavFileHeader(m_wavFileName, m_wavHeader);
  175. SPDLOG_LOGGER_INFO(m_logger, "{} 结束记录一个文件: {}, 已写入大小: {} 字节",
  176. m_logBase, m_wavFileName.toStdString(), m_writtenSize);
  177. /* 重置已写入大小 */
  178. m_writtenSize = 0;
  179. m_writtenStartTime = QDateTime(); // 重新开始时间
  180. m_writtenNowTime = m_writtenStartTime; // 重新开始时间
  181. m_wavFileName.clear(); // 清空文件名
  182. m_openFileErrorSize = 0; // 重置错误次数
  183. }
  184. }
  185. SPDLOG_LOGGER_WARN(m_logger, "➢ {} 记录长文件线程结束运行", m_logBase);
  186. }
  187. /* 初始化一些数据 */
  188. bool CreateLongFileThread::initData()
  189. {
  190. m_logBase = fmt::format("录音通道 {}:{} - 记录长文件线程",
  191. m_threadInfo.cardRoadInfo.strSoundCardName.toStdString(),
  192. m_threadInfo.cardRoadInfo.roadInfo.nRoadNum);
  193. /* 获取全局数据 */
  194. m_sampleRate = GInfo.sampleRate(); /* 采样率 */
  195. m_numChannels = GInfo.numChannels(); /* 声道数 */
  196. m_bitsPerSample = GInfo.bitsPerSample(); /* 每个采样点的位数 */
  197. /* 一秒的数据大小 */
  198. m_oneSecondSize = m_sampleRate * m_numChannels * (m_bitsPerSample / 8);
  199. /* 一分钟数据大小 */
  200. m_writeCriticalSize = m_oneSecondSize * 60;
  201. /* 一小时数据大小 */
  202. m_oneHourSize = m_writeCriticalSize * 60;
  203. /* 给缓存分配空间 */
  204. m_bufferData.allocateMemory(m_writeCriticalSize * 3);
  205. m_srcData.allocateMemory(m_writeCriticalSize * 3);
  206. /* 获取记录文件的位置 */
  207. m_recordPath = GInfo.longWavPath();
  208. return true;
  209. }
  210. /* 清理数据 */
  211. void CreateLongFileThread::clearData()
  212. {
  213. /* 清理缓存数据 */
  214. m_bufferData.clear();
  215. }
  216. /* 设置今日目录 */
  217. bool CreateLongFileThread::setTodayPath(bool isNewFile)
  218. {
  219. if(isNewFile)
  220. {
  221. QString todayDirName = QString("%1/%2/Record_%3-%4")
  222. .arg(m_recordPath)
  223. .arg(QDate::currentDate().toString("yyyy-MM-dd"))
  224. .arg(m_threadInfo.cardRoadInfo.strSoundCardID)
  225. .arg(QString::number(m_threadInfo.cardRoadInfo.roadInfo.nRoadNum));
  226. m_todayDir.setPath(todayDirName);
  227. if(!m_todayDir.exists())
  228. {
  229. if(!m_todayDir.mkpath(todayDirName))
  230. {
  231. SPDLOG_LOGGER_ERROR(m_logger, "{} 创建目录失败: {}", m_logBase, todayDirName.toStdString());
  232. } else {
  233. SPDLOG_LOGGER_INFO(m_logger, "{} 创建目录成功: {}", m_logBase, todayDirName.toStdString());
  234. }
  235. }
  236. }
  237. return m_todayDir.exists();
  238. }
  239. /* 打开文件 */
  240. bool CreateLongFileThread::openFile(QFile& wavFile, bool isNewFile)
  241. {
  242. if(isNewFile)
  243. {
  244. /* 如果没有写入过数据,则生成一个新的文件名 */
  245. m_wavFileName = generateFileName(m_writtenStartTime, m_writtenNowTime);
  246. m_wavHeader.setSampleRate(m_sampleRate);
  247. m_wavHeader.setNumChannels(m_numChannels);
  248. m_wavHeader.setBitsPerSample(m_bitsPerSample);
  249. m_wavHeader.setDataSize(m_writtenSize);
  250. m_wavHeader.calculateDerivedFields();
  251. wavFile.setFileName(m_wavFileName);
  252. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
  253. {
  254. m_openFileErrorSize ++;
  255. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开WAV文件失败: {}", m_logBase, m_wavFileName.toStdString());
  256. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  257. return false;
  258. }
  259. wavFile.write(reinterpret_cast<const char*>(&m_wavHeader), sizeof(WavHeader));
  260. } else
  261. {
  262. /* 不是新文件 */
  263. wavFile.setFileName(m_wavFileName);
  264. if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
  265. {
  266. m_openFileErrorSize ++;
  267. SPDLOG_LOGGER_ERROR(m_logger, "{} 打开WAV文件失败: {}", m_logBase, m_wavFileName.toStdString());
  268. SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
  269. return false;
  270. }
  271. }
  272. m_openFileErrorSize = 0;
  273. return true;
  274. }
  275. /* 写入音频数据到文件 */
  276. bool CreateLongFileThread::writeAudioDataToFile(const AudioSrcData& audioData, const QString& fileName)
  277. {
  278. return true;
  279. }
  280. /* 生成文件名 */
  281. QString CreateLongFileThread::generateFileName(const QDateTime& startTime, const QDateTime& endTime) const
  282. {
  283. // SPDLOG_LOGGER_DEBUG(m_logger, "{} 生成文件名: 开始时间: {}, 结束时间: {}",
  284. // m_logBase, startTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(),
  285. // endTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
  286. QString chnannelStr = QString("%1-%2").arg(m_threadInfo.cardRoadInfo.strSoundCardID)
  287. .arg(QString::number(m_threadInfo.cardRoadInfo.roadInfo.nRoadNum));
  288. QString fileName = QString("%1_%2-%3.wav")
  289. .arg(chnannelStr)
  290. .arg(startTime.toString("yyyyMMdd_hhmmss"))
  291. .arg(endTime.toString("yyyyMMdd_hhmmss"));
  292. /* 通过目录获取文件的全路径,dir不会检查文件是否存在 */
  293. return m_todayDir.filePath(fileName);
  294. }
  295. /* 判断是否过了整点 */
  296. bool CreateLongFileThread::isOneHourPassed()
  297. {
  298. if(m_writtenSize >= m_oneHourSize)
  299. {
  300. return true; // 已经写入的数据大小超过了一小时的大小
  301. }
  302. /* 下面是判断刚启动的时候,到下一个整点不足1小时,也会保存文件 */
  303. int minute = m_writtenNowTime.time().minute();
  304. bool isPassed = false;
  305. /* 如果当前时间的分钟数小于等于2,并且已经写入的大小超过2分钟的大小 */
  306. if(minute <= 2 && m_writtenSize >= m_writeCriticalSize * 2)
  307. {
  308. isPassed = true;
  309. }
  310. /* 或者已经写入的数据大小超过了一小时的大小,则认为过了整点 */
  311. if(m_writtenSize >= m_oneHourSize)
  312. {
  313. isPassed = true;
  314. }
  315. return isPassed;
  316. }