1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039 |
- #include "CreateRecordFileThread.h"
- #include "AudioData.h"
- #include "GlobalInfo.h"
- #include "spdlog/spdlog.h"
- #include "ThreadWriteDBManager.h"
- #include "SystemConfig.h"
- #include <cstring>
- #include <mutex>
- #include <QString>
- #include <qdatetime.h>
- CreateRecordFileThread::CreateRecordFileThread(RecordThreadInfo_t& threadInfo)
- : BaseRecordThread(threadInfo)
- {
-
-
- }
- CreateRecordFileThread::~CreateRecordFileThread()
- {
- }
- /* 设置数据 */
- bool CreateRecordFileThread::setData(const AudioSrcData& srcData)
- {
- if(srcData.pData == nullptr || srcData.dataSize == 0)
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 设置数据失败,srcData为空或dataSize为0", m_logBase);
- return false;
- }
- /* ------------------------------------------------------------------------------- */
- /* 写入数据到环形缓冲区 */
- {
- std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
- auto oldData = m_ringQueue.push(new AudioSrcData(srcData));
- /* 新的元素数加1 */
- m_numNewAlarmSeconds++;
- m_numNewRecordSeconds++;
- if(oldData != nullptr)
- {
- delete oldData;
- oldData = nullptr;
- }
- }
-
- // SPDLOG_LOGGER_DEBUG(m_logger, "{} 设置数据,dataSize: {}, startTime: {}, endTime: {}",
- // 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());
- return true;
- }
- /* 开始录制长文件 */
- bool CreateRecordFileThread::startRecordLongFile(const OneCompareItemRoadInfo_t& compareItemRoadInfo)
- {
- if(compareItemRoadInfo.scRoadInfo.strSoundCardName != m_threadInfo.cardRoadInfo.strSoundCardName ||
- compareItemRoadInfo.scRoadInfo.pcmInfo.strPCMName != m_threadInfo.cardRoadInfo.pcmInfo.strPCMName)
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 开始录制音频文件失败,通道信息不匹配", m_logBase);
- return false;
- }
- std::lock_guard<std::mutex> lock(m_mutexCompareItemRoadInfo);
- if(m_listCompareItemRoadInfo.contains(compareItemRoadInfo))
- {
- return true;
- }
- m_listCompareItemRoadInfo.append(compareItemRoadInfo);
- m_isRequireRecord.store(true); // 设置为需要录音状态
- return true;
- }
- /* 停止录制长文件 */
- bool CreateRecordFileThread::stopRecordLongFile(const OneCompareItemRoadInfo_t& compareItemRoadInfo)
- {
- if(compareItemRoadInfo.scRoadInfo.strSoundCardName != m_threadInfo.cardRoadInfo.strSoundCardName ||
- compareItemRoadInfo.scRoadInfo.pcmInfo.strPCMName != m_threadInfo.cardRoadInfo.pcmInfo.strPCMName)
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 结束录制音频文件失败,通道信息不匹配", m_logBase);
- return false;
- }
- std::lock_guard<std::mutex> lock(m_mutexCompareItemRoadInfo);
- if(!m_listCompareItemRoadInfo.contains(compareItemRoadInfo))
- {
- // SPDLOG_LOGGER_WARN(m_logger, "{} 停止录制音频文件失败,通道信息不在列表中", m_logBase);
- return true;
- }
- /* 从列表中移除 */
- m_listCompareItemRoadInfo.removeAll(compareItemRoadInfo);
- if(m_listCompareItemRoadInfo.isEmpty())
- {
- /* 如果没有对比项信息了,则设置为不需要录音状态 */
- m_isRequireRecord.store(false);
- clearData();
- }
- return true;
- }
- /* 开启录制 */
- bool CreateRecordFileThread::startRecordAlarmFile(const AlarmInfo_t& alarmInfo)
- {
- {
- std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
- /* 先检查是否已经在队列中了 */
- AlarmKey_t key = {alarmInfo.CompareItemID, alarmInfo.RoadInfo.nCompareRoadNum,
- alarmInfo.AlarmType, alarmInfo.StartTime};
- if(m_mapAlarmFile.find(key) != m_mapAlarmFile.end())
- {
- SPDLOG_LOGGER_WARN(m_logger, "{} 对比项ID:{},{}, 通道:{}, 报警类型: {}, 已经在报警录制队列中,无法重复添加", m_logBase,
- alarmInfo.CompareItemID, alarmInfo.strCompareItemName.toStdString(), alarmInfo.RoadInfo.nCompareRoadNum,
- static_cast<int>(alarmInfo.AlarmType));
- return true;
- }
- /* 将数据值插入map中 */
- AlarmValue_t value;
- value.alarmType = alarmInfo.AlarmType;
- value.startTime = alarmInfo.StartTime;
- value.endTime = alarmInfo.EndTime;
- value.numConpareItemID = alarmInfo.CompareItemID;
- value.strCompareItemName = alarmInfo.strCompareItemName;
- value.itemRoadInfo = alarmInfo.RoadInfo;
- m_mapAlarmFile.insert({key, value});
- }
- m_cond_var.notify_one();
- return true;
- }
- /* 停止录制,alarmInfo既是传入参数,也是传出参数,传出文件路径和开始位置 */
- bool CreateRecordFileThread::stopRecordAlarmFile(AlarmInfo_t& alarmInfo)
- {
- std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
- for(auto& it : m_mapAlarmFile)
- {
- if(it.first.compareItemID == alarmInfo.CompareItemID &&
- it.first.roadNum == alarmInfo.RoadInfo.nCompareRoadNum &&
- it.first.alarmType == alarmInfo.AlarmType &&
- it.second.startTime == alarmInfo.StartTime )
- {
- /* 找到对应的报警信息 */
- AlarmValue_t& value = it.second;
- value.endTime = alarmInfo.EndTime; // 设置结束时间
- value.state = eRecordState::eRS_RecordCompleted; // 设置为录音完成
- value.fileNameEnd = generateAlarmFileName(value, false); // 生成结束文件名
- alarmInfo.strAlarmFilePath = value.fileNameEnd; // 返回文件路径
- alarmInfo.AlarmStartPos = value.alarmStartPos; // 返回开始位置
- SPDLOG_LOGGER_INFO(m_logger, "{} 停止录制报警文件: {}, 开始位置: {} 秒",
- m_logBase, value.fileNameEnd.toStdString(), value.alarmStartPos);
- return true;
- }
- }
- return true;
- }
- /**
- * @brief 生成长文件的线程函数,文件生成逻辑如下
- * 1、这里有一个缓冲区,存储音频数据,缓冲区大小是2分钟的数据
- * 2、每分钟写入一次文件,文件名格式为:ChannelX_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
- * 3、文件总长度是1小时的数据,超过1小时则重新开始记录一个新的文件
- *
- */
- void CreateRecordFileThread::task()
- {
- SPDLOG_LOGGER_INFO(m_logger, "➢ {} 开启记录文件线程 ", m_logBase);
- /* 计算一小时的文件大小 */
- initData();
-
- while(m_isRunning)
- {
- /* 使用条件变量阻塞线程,加快开启报警时的响应速度 */
- std::unique_lock<std::mutex> lock(m_mutexCondVar);
- m_cond_var.wait_for(lock, std::chrono::milliseconds(500));
- /*--------------------------------------------------------------
- * 写入报警文件
- *--------------------------------------------------------------*/
- writeAlarmFile();
-
- /*--------------------------------------------------------------
- * 写入长记录文件
- *--------------------------------------------------------------*/
- writeLongRecordFile();
-
- }
-
- clearData();
- SPDLOG_LOGGER_WARN(m_logger, "➢ {} 记录长文件线程结束运行", m_logBase);
- }
- /* 初始化一些数据 */
- bool CreateRecordFileThread::initData()
- {
- m_logBase = fmt::format("录音通道 {}:{} - 录音文件线程",
- m_threadInfo.cardRoadInfo.strSoundCardName, m_threadInfo.cardRoadInfo.pcmInfo.strPCMName);
- /* 获取全局数据 */
- m_sampleRate = GInfo.sampleRate(); /* 采样率 */
- m_numChannels = GInfo.numChannels(); /* 声道数 */
- m_bitsPerSample = GInfo.bitsPerSample(); /* 每个采样点的位数 */
- /* 一秒的数据大小 */
- m_oneSecondSize = m_sampleRate * m_numChannels * (m_bitsPerSample / 8);
- /* 一次写入的大小,10秒钟 */
- // m_writeCriticalSize = m_oneSecondSize * 10;
- /* 一小时数据大小 */
- // m_oneHourSize = m_oneSecondSize * 60 * 60;
- /* 给缓存分配空间 */
- // m_bufferData.allocateMemory(m_writeCriticalSize * 3);
- // m_srcData.allocateMemory(m_writeCriticalSize * 3);
- /* 设置环形队列大小 */
- // m_ringQueue.setQueueCapacity(GInfo.queueElementCount());
- m_ringQueue.setQueueCapacity(180); /* 3分钟数据 */
- return true;
- }
- /* 清理数据 */
- void CreateRecordFileThread::clearData()
- {
- /* 清理缓存数据 */
- // std::lock_guard<std::mutex> lock(m_mutexBuffer);
- // m_bufferData.clear();
- // m_srcData.clear();
- std::lock_guard<std::mutex> lockSrcData(m_ringQueue.mutex);
- while(!m_ringQueue.isEmpty())
- {
- auto data = m_ringQueue.front_pop();
- if(data != nullptr)
- {
- delete data;
- data = nullptr;
- }
- }
- }
- /* 写入长记录文件 */
- bool CreateRecordFileThread::writeLongRecordFile()
- {
- /* 判断是否需要录音 */
- std::lock_guard<std::mutex> lock(m_mutexCompareItemRoadInfo);
- if(m_isRequireRecord.load() == false)
- {
- /* 没有对比项信息,不进行录音 */
- /* 判断当前有没有录音,如果有,则结束录音 */
- m_numNewRecordSeconds = 0;
- return true;
- }
- /* 取出最新的 m_numNewRecordSeconds 个音频数据 */
- std::list<AudioSrcData*> newList;
- {
- std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
- /* 判断缓存是否达到写入数据的临界值 */
- if(m_numNewRecordSeconds < 10)
- {
- return true; // 缓存数据不足,继续等待
- }
- int startPos = m_ringQueue.QueueSize() - m_numNewRecordSeconds;
- if(startPos < 0)
- {
- startPos = 0;
- }
- for(int i = startPos; i < m_ringQueue.QueueSize(); i++)
- {
- newList.push_back(m_ringQueue[i]);
- }
- }
- /*--------------------------------------------------------------
- * 打开文件。写入的时候判断是否到达了整点,如果到达了整点,则关闭文件
- * 重新创建一个新的文件
- *--------------------------------------------------------------*/
- bool isPassed = isOneHourPassed(newList);
- /* 判断 newList 中有没有跨过整点,如果有则分开,分别写入 */
- std::list<AudioSrcData*> writeList;
- std::list<AudioSrcData*> writeNewFileList;
- if(isPassed)
- {
- /* 跨过了整点 */
- for(const auto& it : newList)
- {
- if(it == nullptr || it->dataSize == 0)
- {
- continue;
- }
- if(it->startTime.time().minute() == 59)
- {
- writeList.push_back(it);
- }else {
- writeNewFileList.push_back(it);
- }
- }
- } else {
- writeList = newList;
- }
-
- /* 判断是不是刚启动的新文件 */
- if(m_writtenSize == 0)
- {
- if(isPassed)
- {
- /* 刚启动,并且这几秒内也跨过了整点,直接写入并结束这个文件 */
- writeRecordFileData(writeList, true, true);
- /* 写新的文件 */
- writeRecordFileData(writeNewFileList, true, false);
- }else {
- /* 刚启动,并且没有跨过整点,直接写入 */
- writeRecordFileData(writeList, true, false);
- }
- }else
- {
- if(isPassed)
- {
- if(writeList.size() == 0)
- {
- /* 跨过了整点,但是没有数据需要写入 */
- endRecordFile();
- }else {
- /* 不是新文件,并且跨过了整点,先写完这个文件,然后结束 */
- writeRecordFileData(writeList, false, true);
- }
- /* 再写新的文件 */
- writeRecordFileData(writeNewFileList, true, false);
- }else {
- /* 不是新文件,也没有跨过整点,直接写入 */
- writeRecordFileData(writeList, false, false);
- }
- }
-
- m_numNewRecordSeconds = 0;
- return true;
- }
- /* 写入数据 */
- bool CreateRecordFileThread::writeRecordFileData(const std::list<AudioSrcData*>& dataList, bool isNewFile, bool isRecordCompleted)
- {
- if(dataList.empty())
- {
- return true;
- }
- if(isNewFile)
- {
- /* 如果没有写入过数据,则是新文件 */
- m_writtenStartTime = dataList.front()->startTime;
- m_writtenNowTime = m_writtenStartTime;
- // SPDLOG_LOGGER_WARN(m_logger, "新录音文件: {} - {}", m_writtenStartTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(),
- // m_writtenNowTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
- }
- /* 设置今日目录 */
- if(isNewFile)
- {
- if(!setTodayPath(isNewFile))
- {
- return false;
- }
- }
-
- /* 打开文件 */
- QFile wavFile;
- if(!openFile(wavFile, isNewFile))
- {
- if(m_openFileErrorSize >= 3)
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 打开文件失败次数过多,重新开始记录", m_logBase);
- m_writtenSize = 0;
- m_writtenStartTime = QDateTime::currentDateTime(); // 重新开始时间
- m_writtenNowTime = m_writtenStartTime; // 重新开始时间
- m_wavFileName.clear(); // 清空文件名
- // m_srcData.clear(); // 清空缓冲区数据
- m_openFileErrorSize = 0; // 重置错误次数
- return false; // 重新开始记录
- }
- }
- /*--------------------------------------------------------------
- * 将数据写入文件,并记录其携带的时间和写入的数据大小
- *--------------------------------------------------------------*/
- int64_t wSize = 0;
- bool isWriteSucess = true;
- {
- for(auto it : dataList)
- {
- if(it == nullptr || it->dataSize == 0)
- {
- continue;
- }
- int64_t thisWSize = wavFile.write(it->pData, it->dataSize);
- if(thisWSize > 0)
- {
- wSize += thisWSize;
- m_writtenNowTime = it->endTime; // 记录当前结束时间
- }else {
- isWriteSucess = false;
- break;
- }
- }
- }
- if(isWriteSucess == false)
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 写入WAV文件失败: {}", m_logBase, wavFile.errorString().toStdString());
- SPDLOG_LOGGER_WARN(m_logger, "文件路径:{}", m_wavFileName.toStdString());
- wavFile.close();
- return false;
- }
- SPDLOG_LOGGER_TRACE(m_logger, "{} 写入WAV文件成功: {}, 大小: {} 字节", m_logBase, m_wavFileName.toStdString(), wSize);
-
- wavFile.close();
- // SPDLOG_LOGGER_DEBUG(m_logger, "写入录音文件成功, 写入的时间段: {} - {}",
- // m_writtenStartTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(), m_writtenNowTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
- /*--------------------------------------------------------------
- * 对该文件进行其他操作,判断是否已经过了一个整点,修改其文件名称
- * 现在这里的时间是这一分钟的开始时间,现在需要根据开始时间求出已写入
- * 数据大小对应的结束时间
- *--------------------------------------------------------------*/
- m_writtenSize += wSize;
-
- /* 修改文件名称 */
- QString newFileName = generateFileName(m_writtenStartTime, m_writtenNowTime);
- if(modifyFileName(m_wavFileName, newFileName))
- {
- m_wavFileName = newFileName;
- }else {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 修改文件名失败: {} -> {}", m_logBase, m_wavFileName.toStdString(), newFileName.toStdString());
- }
-
- /* 判断是否需要结束文件 */
- if(isRecordCompleted)
- {
- /* 修改文件头中记录的数据大小 */
- m_wavHeader.setDataSize(m_writtenSize);
- m_wavHeader.calculateDerivedFields();
- modifyWavFileHeader(m_wavFileName, m_wavHeader);
- SPDLOG_LOGGER_INFO(m_logger, "{} 结束记录一个文件: {}, 已写入大小: {} 字节",
- m_logBase, m_wavFileName.toStdString(), m_writtenSize);
- }
- /* 更新文件信息到数据库 */
- updateRecordFileInfoToDB(isNewFile, isRecordCompleted);
- /* 清空已经结束的文件信息 */
- if(isRecordCompleted)
- {
- /* 重置已写入大小 */
- m_writtenSize = 0;
- m_writtenStartTime = QDateTime(); // 重新开始时间
- m_writtenNowTime = QDateTime(); // 重新开始时间
- m_wavFileName.clear(); // 清空文件名
- m_openFileErrorSize = 0; // 重置错误次数
- }
- return true;
- }
- /* 结束文件录制 */
- bool CreateRecordFileThread::endRecordFile()
- {
- /* 修改文件头中记录的数据大小 */
- m_wavHeader.setDataSize(m_writtenSize);
- m_wavHeader.calculateDerivedFields();
- modifyWavFileHeader(m_wavFileName, m_wavHeader);
- SPDLOG_LOGGER_INFO(m_logger, "{} 结束记录一个文件: {}, 已写入大小: {} 字节",
- m_logBase, m_wavFileName.toStdString(), m_writtenSize);
- /* 更新文件信息到数据库 */
- updateRecordFileInfoToDB(false, true);
- /* 清空已经结束的文件信息 */
- m_writtenSize = 0;
- m_writtenStartTime = QDateTime(); // 重新开始时间
- m_writtenNowTime = QDateTime(); // 重新开始时间
- m_wavFileName.clear(); // 清空文件名
- m_openFileErrorSize = 0; // 重置错误次数
-
- return true;
- }
- /* 设置今日目录 */
- bool CreateRecordFileThread::setTodayPath(bool isNewFile)
- {
- if(!isNewFile)
- {
- return true;
- }
- /* 判断现在日期是否还是当天日期 */
- QDate today = QDate::currentDate();
- if(m_todayDateRecord == today)
- {
- return true;
- }
- m_todayDateRecord = today;
- /* 先检查现在的日期文件夹是否存在,因为其他线程可能已经创建了 */
- QString todayDirName = QString("%1/%2").arg(GInfo.longWavPath()).arg(m_todayDateRecord.toString("yyyy-MM-dd"));
- m_todayDir.setPath(todayDirName);
- if(!m_todayDir.exists())
- {
- if(!m_todayDir.mkpath(todayDirName))
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 创建今日目录失败: {}", m_logBase, todayDirName.toStdString());
- return false; // 创建目录失败
- } else {
- SPDLOG_LOGGER_INFO(m_logger, "{} 创建今日目录成功: {}", m_logBase, todayDirName.toStdString());
- }
- }
- /* 创建这个通道的文件夹,文件夹格式: AudioPCI-0 */
- QString roadDirName = QString("%1/%2-%3")
- .arg(todayDirName)
- .arg(QString::fromStdString(m_threadInfo.cardRoadInfo.strSoundCardName))
- .arg(QString::fromStdString(m_threadInfo.cardRoadInfo.pcmInfo.strPCMName));
- m_todayDir.setPath(roadDirName);
- if(!m_todayDir.exists())
- {
- SPDLOG_LOGGER_WARN(m_logger, "{} 记录文件目录不存在: {}", m_logBase, roadDirName.toStdString());
- if(!m_todayDir.mkpath(roadDirName))
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 创建目录失败: {}", m_logBase, roadDirName.toStdString());
- return false;
- } else {
- SPDLOG_LOGGER_INFO(m_logger, "{} 创建目录成功: {}", m_logBase, roadDirName.toStdString());
- }
- }
-
- return true;
- }
- /* 打开文件 */
- bool CreateRecordFileThread::openFile(QFile& wavFile, bool isNewFile)
- {
- if(isNewFile)
- {
- /* 如果没有写入过数据,则生成一个新的文件名 */
- m_wavFileName = generateFileName(m_writtenStartTime, m_writtenNowTime);
- m_wavHeader.setSampleRate(m_sampleRate);
- m_wavHeader.setNumChannels(m_numChannels);
- m_wavHeader.setBitsPerSample(m_bitsPerSample);
- m_wavHeader.setDataSize(m_writtenSize);
- m_wavHeader.calculateDerivedFields();
- wavFile.setFileName(m_wavFileName);
- if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
- {
- m_openFileErrorSize ++;
- SPDLOG_LOGGER_ERROR(m_logger, "{} 打开WAV文件失败: {}", m_logBase, m_wavFileName.toStdString());
- SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
- return false;
- }
- wavFile.write(reinterpret_cast<const char*>(&m_wavHeader), sizeof(WavHeader));
- } else
- {
- /* 不是新文件 */
- wavFile.setFileName(m_wavFileName);
- if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
- {
- m_openFileErrorSize ++;
- SPDLOG_LOGGER_ERROR(m_logger, "{} 打开WAV文件失败: {}", m_logBase, m_wavFileName.toStdString());
- SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
- return false;
- }
- }
-
- m_openFileErrorSize = 0;
- return true;
- }
- /* 写入音频数据到文件 */
- bool CreateRecordFileThread::writeAudioDataToFile(const AudioSrcData& audioData, const QString& fileName)
- {
- return true;
- }
- /* 生成文件名 */
- QString CreateRecordFileThread::generateFileName(const QDateTime& startTime, const QDateTime& endTime) const
- {
- // SPDLOG_LOGGER_DEBUG(m_logger, "{} 生成文件名: 开始时间: {}, 结束时间: {}",
- // m_logBase, startTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(),
- // endTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
- QString chnannelStr = QString("%1-%2").arg(QString::fromStdString(m_threadInfo.cardRoadInfo.strSoundCardName))
- .arg(QString::fromStdString(m_threadInfo.cardRoadInfo.pcmInfo.strPCMName));
- QString fileName = QString("%1_%2-%3.wav")
- .arg(chnannelStr)
- .arg(startTime.toString("yyyyMMdd_hhmmss"))
- .arg(endTime.toString("yyyyMMdd_hhmmss"));
- /* 通过目录获取文件的全路径,dir不会检查文件是否存在 */
- return m_todayDir.filePath(fileName);
- }
- /* 判断是否过了整点 */
- // bool CreateRecordFileThread::isOneHourPassed()
- // {
- // if(m_writtenSize >= m_oneHourSize)
- // {
- // // 已经写入的数据大小超过了一小时的大小
- // return true;
- // }
- // /* 下面是判断刚启动的时候,到下一个整点不足1小时,也会保存文件 */
- // int minute = m_writtenNowTime.time().minute();
- // bool isPassed = false;
- // /* 如果当前时间的分钟数小于等于2,并且已经写入的大小超过三分钟大小 */
- // if( (minute <= 2) && (m_writtenSize > m_oneSecondSize * 60 * 3) )
- // {
- // isPassed = true;
- // }
- // return isPassed;
- // }
- bool CreateRecordFileThread::isOneHourPassed(const std::list<AudioSrcData*>& dataList)
- {
- if(dataList.empty())
- {
- return false;
- }
- bool has59Minute = false;
- bool has00Minute = false;
- /* 计算这些数据中是否跨过了整点 */
- for(const auto& it : dataList)
- {
- if(it == nullptr || it->dataSize == 0)
- {
- continue;
- }
- if(it->startTime.time().minute() == 59)
- {
- has59Minute = true;
- }
- if(it->startTime.time().minute() == 0)
- {
- has00Minute = true;
- }
- }
- if(has59Minute && has00Minute)
- {
- return true;
- }
- return false;
- }
- /* 更新文件信息到数据库 */
- void CreateRecordFileThread::updateRecordFileInfoToDB(bool isNewFile, bool isRecordCompleted)
- {
- std::list<RecordFileInfo_t> listRecordFileInfo;
- for(const auto& it : m_listCompareItemRoadInfo)
- {
- RecordFileInfo_t recordFileInfo;
- recordFileInfo.ItemID = it.nCompareItemID;
- recordFileInfo.ItemName = it.strCompareItemName;
- recordFileInfo.ItemRoadNum = it.nCompareRoadNum;
- recordFileInfo.ItemRoadName = it.strCompareRoadName;
- recordFileInfo.scRoadInfo = it.scRoadInfo;
- recordFileInfo.FileStartTime = m_writtenStartTime;
- recordFileInfo.FileEndTime = m_writtenNowTime;
- recordFileInfo.FilePath = m_wavFileName;
- recordFileInfo.FileDuration = m_writtenStartTime.secsTo(m_writtenNowTime);
- recordFileInfo.fileState = isRecordCompleted ? eRecordState::eRS_RecordCompleted : eRecordState::eRS_Recording;
-
- listRecordFileInfo.push_back(recordFileInfo);
- }
- WriteDB.addRecordFileInfo(listRecordFileInfo, isNewFile);
- }
- /* 生成报警文件名 */
- QString CreateRecordFileThread::generateAlarmFileName(const AlarmValue_t& value, bool isNewFile)
- {
- QString retFileName;
- if(isNewFile)
- {
- /* 先检查是否已经过了一天了,设置日期文件夹 */
- setTodayAlarmPath();
- /* 检查这个对比项的报警文件夹是否存在 */
- // QString itemDirName = m_todayDirAlarm.filePath(QString("CompareItemID_%1").arg(QString::number(value.numConpareItemID)));
- QString itemDirName = QString("CompareItemID_%1").arg(QString::number(value.numConpareItemID));
- QDir itemDir(m_todayDirAlarm.filePath(itemDirName));
- if(!itemDir.exists())
- {
- SPDLOG_LOGGER_WARN(m_logger, "{} 对比项报警文件夹不存在: {}", m_logBase, itemDir.path().toStdString());
- if(!itemDir.mkpath("."))
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 创建对比项报警文件夹失败: {}", m_logBase, itemDir.path().toStdString());
- return QString(); // 创建目录失败
- } else {
- SPDLOG_LOGGER_INFO(m_logger, "{} 创建对比项报警文件夹成功: {}", m_logBase, itemDir.path().toStdString());
- }
- }
- /* 生成文件名, 格式: Alarm_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav
- 这里创建的录音文件只有开始日期,没有结束日期,等报警结束之后才会有结束日期 */
- QString fileName = QString("Road%1_%2_%3-.wav")
- .arg(QString::number(value.itemRoadInfo.nCompareRoadNum))
- .arg(getAlarmTypeString(value.alarmType))
- .arg(value.startTime.toString("yyyyMMdd_hhmmss"));
-
- /* 拼接文件夹路径 */
- retFileName = itemDir.filePath(fileName);
- // SPDLOG_LOGGER_DEBUG(m_logger, "{} 生成新的报警文件名: {}", m_logBase, retFileName.toStdString());
- }else
- {
- /* 已有的文件,是报警结束的文件名 */
- retFileName = QString::fromStdString(value.fileName.toStdString());
- /* 这里的文件名格式是:Alarm_CompareItemID_RoadNum_AlarmType_yyyyMMdd_hhmmss-yyyyMMdd_hhmmss.wav,
- 这里带上结束时间 */
- QString endTimeStr = value.endTime.toString("yyyyMMdd_hhmmss");
- /* 替换掉原来的结束时间 */
- retFileName.replace("-.wav", QString("-%1.wav").arg(endTimeStr));
- // SPDLOG_LOGGER_DEBUG(m_logger, "{} 原文件名:{} 生成报警结束文件名: {}", m_logBase, value.fileName.toStdString(), retFileName.toStdString());
- }
- return retFileName;
- }
- /* 写入报警文件 */
- void CreateRecordFileThread::writeAlarmFile()
- {
- /* 取出环形队列中后100秒的数据,前面20秒给环形队列满的时候缓冲使用 */
- std::list<AudioSrcData*> newDataList;
- int writeSize = m_numNewAlarmSeconds > 120 ? m_numNewAlarmSeconds : 120;
- {
- std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
- /* 取出环形队列中的数据 */
- int size = m_ringQueue.QueueSize();
- int start = size - writeSize;
- if(start < 0)
- {
- start = 0; // 防止越界
- }
- /* 取出新数据,往前推10秒 */
- for(int i = start; i < size; ++i)
- {
- AudioSrcData* data = m_ringQueue[i];
- if(data != nullptr)
- {
- newDataList.push_back(data);
- }
- }
- }
- /* 写入已经创建的报警文件的数据,需要先写入已经创建的文件,再创建新的文件,防止
- 创建新的文件后再次写入重复的数据 */
- writeExistingAlarmFileData(newDataList);
- /* 写入新的报警文件 */
- writeNewAlarmFileData(newDataList);
-
- /* 处理已经结束的报警,修改文件名,并将其移除队列中 */
- for(auto it = m_mapAlarmFile.begin(); it != m_mapAlarmFile.end(); )
- {
- AlarmValue_t& value = it->second;
- if(value.state == eRecordState::eRS_RecordCompleted)
- {
- /* 修改wav头文件,修改写入的数据大小 */
- value.wavHeader.setDataSize(value.writtenSize);
- value.wavHeader.calculateDerivedFields();
- modifyWavFileHeader(value.fileName, value.wavHeader);
- /* 已经结束的报警,修改文件名 */
- if(!modifyFileName(value.fileName, value.fileNameEnd))
- {
- SPDLOG_LOGGER_ERROR(m_logger, "修改文件名失败: {} -> {}", value.fileName.toStdString(), value.fileNameEnd.toStdString());
- }
- /* 移除这个报警 */
- it = m_mapAlarmFile.erase(it);
- } else {
- ++it; // 继续下一个
- }
- }
- /* 清空标志位 */
- m_numNewAlarmSeconds = 0;
-
- }
- /* 创建新的文件 */
- void CreateRecordFileThread::createNewAlarmFile(AlarmValue_t& value, const std::list<AudioSrcData*>& dataList)
- {
- /* 新的报警录音,生成文件名 */
- value.fileName = generateAlarmFileName(value, true);
- SPDLOG_LOGGER_INFO(m_logger, "{} 开始写入报警文件: {}", m_logBase, value.fileName.toStdString());
-
- /* 计算出报警开始时间在报警文件中的位置 */
- int startPos = 0;
- for(const auto& audioData : dataList)
- {
- if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
- {
- continue; // 跳过空数据
- }
- if(audioData->startTime >= value.startTime)
- {
- /* 计算报警开始位置 */
- break;
- }
- startPos++;
- }
-
- /* 打开文件 */
- QFile wavFile(value.fileName);
- if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
- SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
- return;
- }
- /* 先写入wav头文件 */
- value.wavHeader.setSampleRate(m_sampleRate);
- value.wavHeader.setNumChannels(m_numChannels);
- value.wavHeader.setBitsPerSample(m_bitsPerSample);
- value.wavHeader.setDataSize(m_writtenSize);
- value.wavHeader.calculateDerivedFields();
- wavFile.write(reinterpret_cast<const char*>(&value.wavHeader), sizeof(WavHeader));
- /* 写入音频数据到文件 */
- int nowPos = 0;
- for(auto& audioData : dataList)
- {
- if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
- {
- continue; // 跳过空数据
- }
- if(nowPos < startPos)
- {
- nowPos++;
- continue; // 跳过前面的数据
- }
- auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
- if(writeSize < 0)
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
- SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
- }else
- {
- value.writtenSize += writeSize; // 累加写入大小
- }
- }
- wavFile.close();
- }
- /* 写入新文件数据 */
- void CreateRecordFileThread::writeNewAlarmFileData(std::list<AudioSrcData*> dataList)
- {
- /* 判断是否有新的报警信息 */
- bool isNewAlarm = false;
- std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
- for(const auto& pair : m_mapAlarmFile)
- {
- const AlarmValue_t& value = pair.second;
- if(value.state == eRecordState::eRS_Init)
- {
- isNewAlarm = true;
- break;
- }
- }
- if(!isNewAlarm)
- {
- return; // 没有新的报警信息
- }
-
-
- /* 创建新文件并写入数据 */
- for(auto& alarmInfo : m_mapAlarmFile)
- {
- AlarmValue_t& value = alarmInfo.second;
- /* 判断是新否是新录音的文件 */
- if(value.state == eRecordState::eRS_Init)
- {
- /* 创建新的文件,并写入wav头文件 */
- createNewAlarmFile(value, dataList);
- value.state = eRecordState::eRS_Recording; // 设置为录音状态
- }
- }
- }
- /* 写入已经创建的报警文件数据 */
- void CreateRecordFileThread::writeExistingAlarmFileData(std::list<AudioSrcData*> newList)
- {
- const int listSize = static_cast<int>(newList.size());
- if(m_numNewAlarmSeconds > listSize)
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 音频队列中数据小于需要写入的新的数据大小", m_logBase);
- return;
- }
- /* 已经打开的文件写入数据 */
- std::list<AudioSrcData*> dataList;
- if(m_numNewAlarmSeconds > 0)
- {
- /* 取出最后 m_numNewAlarmSeconds 个数据 */
- int i = newList.size() - m_numNewAlarmSeconds;
- for(auto it = newList.end(); i < listSize; ++i)
- {
- --it;
- dataList.push_back(*it);
- }
- }
- /* 写入数据 */
- std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
- /* 将数据循环写入到文件中 */
- for(auto& alarmInfo : m_mapAlarmFile)
- {
- /* 获取报警信息 */
- // const AlarmKey_t& key = alarmInfo.first;
- AlarmValue_t& value = alarmInfo.second;
- /* 判断是新否是新录音的文件 */
- if(eRecordState::eRS_Recording == value.state)
- {
- /* 已经存在的文件,直接写入新录制的文件 */
- /* 打开文件 */
- QFile wavFile(value.fileName);
- if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
- SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
- continue;
- }
- /* 写入音频数据到文件 */
- for(auto& audioData : dataList)
- {
- if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
- {
- continue; // 跳过空数据
- }
- auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
- if(writeSize < 0)
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
- SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
- break; // 写入失败,跳出循环
- }else
- {
- value.writtenSize += writeSize; // 累加写入大小
- }
- }
- wavFile.close();
- }
- }
- }
- /* 设置今日报警文件夹 */
- bool CreateRecordFileThread::setTodayAlarmPath()
- {
- /* 先检查当天日期文件夹是否存在,因为其他线程可能已经创建了 */
- QDate today = QDate::currentDate();
- if(today == m_todayDateAlarm)
- {
- return true; // 今天的目录已经存在
- }
- m_todayDateAlarm = today;
- /* 创建今日报警目录 */
- /* 格式: Application/2025-07-21 */
- QString todayDirName = QString("%1/%2").arg(GInfo.alarmWavPath()).arg(today.toString("yyyy-MM-dd"));
- m_todayDirAlarm.setPath(todayDirName);
- if(!m_todayDirAlarm.exists())
- {
- if(!m_todayDirAlarm.mkpath(todayDirName))
- {
- SPDLOG_LOGGER_ERROR(m_logger, "{} 创建今日报警目录失败: {}", m_logBase, todayDirName.toStdString());
- return false; // 创建目录失败
- } else {
- SPDLOG_LOGGER_INFO(m_logger, "{} 创建今日报警目录成功: {}", m_logBase, todayDirName.toStdString());
- }
- }
- /* 设置目录 */
- m_todayDirAlarm.setPath(todayDirName);
- QString yesterdayDirName = QString("%1/%2").arg(GInfo.alarmWavPath()).arg(QDate::currentDate().addDays(-1).toString("yyyy-MM-dd"));
- m_yesterdayDir.setPath(yesterdayDirName);
- return true;
- }
- /* 根据报警类型的枚举获取字符 */
- QString CreateRecordFileThread::getAlarmTypeString(EAlarmType type) const
- {
- switch(type)
- {
- case EAlarmType::EAT_Silent:
- return "Silent";
- case EAlarmType::EAT_Overload:
- return "Overload";
- case EAlarmType::EAT_Reversed:
- return "Reversed";
- case EAlarmType::EAR_Consistency:
- return "Consistency";
- case EAlarmType::EAT_Noise:
- return "Noise";
- default:
- return "Unknown";
- }
- }
|