RecordLongFileThread.cpp 11 KB

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