// // Created by Administrator on 2025/1/17. // #include "AudioInput.h" #include #include #include // 用于处理字节序 AudioInput *AudioInput::instance = nullptr; AudioInput *AudioInput::getInstance() { // 懒汉式 依旧单线程无需加锁 if (instance == nullptr) { instance = new AudioInput(); } return instance; } AudioInput::AudioInput(QObject *parent) : QObject(parent) { // new一些必要的对象 // 初始化定时器 m_timer = new QTimer(this); m_silenceTimer = new QTimer(this); m_thresholdTimer = new QTimer(this); m_thresholdTimer->setSingleShot(true); // 连接定时器信号 connect(m_timer, &QTimer::timeout, this, &AudioInput::onTimeout); // 录音超时槽函数 connect(m_silenceTimer, &QTimer::timeout, this, &AudioInput::stopAudio); // 录音超时槽函数 connect(m_thresholdTimer, &QTimer::timeout, this, &AudioInput::thresholdTimeout); // 阈值检测超时槽函数 // 初始化默认设备和格式 m_currentDevice = QMediaDevices::defaultAudioInput(); setAudioSettings(); // 使用默认参数 } AudioInput::~AudioInput() { stopAudio(); // 停止录音 if (m_audioSource) { delete m_audioSource; } } void AudioInput::setAudioSettings(const int rate, const int channels) { m_format.setSampleRate(rate); m_format.setChannelCount(channels); // 重要:Qt6 默认可能是 float,为了生成标准 WAV 且方便计算 RMS,强制设为 Int16 m_format.setSampleFormat(QAudioFormat::Int16); // 检查设备是否支持该格式,不支持则使用最接近的 if (!m_currentDevice.isFormatSupported(m_format)) { qWarning() << "Requested format not supported, using preferred format."; m_format = m_currentDevice.preferredFormat(); } } void AudioInput::setAudioPath(const QString &path, const QString &fileName) { this->m_outputFilePath = path + fileName; } void AudioInput::startAudio() { // 每次开始前重新创建 QAudioSource,确保状态重置 if (m_audioSource) { delete m_audioSource; m_audioSource = nullptr; } m_audioSource = new QAudioSource(m_currentDevice, m_format, this); // 调大缓冲区以避免溢出 m_audioSource->setBufferSize(128000); // start() 返回一个 QIODevice,我们可以从中读取数据 m_ioDevice = m_audioSource->start(); if (m_ioDevice) { connect(m_ioDevice, &QIODevice::readyRead, this, &AudioInput::onReadyRead); qDebug() << "Started recording with device:" << m_currentDevice.description(); } else { qCritical() << "Failed to start audio recording."; } } void AudioInput::stopAudio() { if (m_audioSource) { m_audioSource->stop(); // 注意:不要立即 delete m_audioSource,某些情况下可能导致 crash,停止即可 } // 停止所有定时器 m_timer->stop(); m_silenceTimer->stop(); m_thresholdTimer->stop(); // 生成 WAV 数据 QByteArray wavData; if (!m_rawPCMData.isEmpty()) { wavData = generateWavHeader(m_rawPCMData.size()); wavData.append(m_rawPCMData); // 如果需要保存文件 if (!m_outputFilePath.isEmpty()) { QFile file(m_outputFilePath); if (file.open(QIODevice::WriteOnly)) { file.write(wavData); file.close(); qDebug() << "Saved WAV to:" << m_outputFilePath; } } m_rawPCMData.clear(); } isAutoRecording = false; isAutoThreshold = false; emit recordingFinished(); emit recordingFinished_Byte(wavData); qDebug() << "Recording stopped."; } // 阈值检测超时槽函数 void AudioInput::onReadyRead() { if (!m_ioDevice) return; // 读取当前所有可用的音频数据 QByteArray data = m_ioDevice->readAll(); if (data.isEmpty()) return; // 1. 保存原始 PCM 数据 m_rawPCMData.append(data); // 2. 计算 RMS (仅用于分析,取最后一小段或者整体计算,这里计算当前块的RMS) m_rmsValue = calculateRMS(data); // 3. 自动停止逻辑 (VAD) if (isAutoRecording) { // 输出 RMS 用于调试 // qDebug() << "RMS:" << m_rmsValue; if (m_rmsValue < m_silenceThreshold) { // 静音状态 if (!m_silenceTimer->isActive()) { m_silenceTimer->start(m_silenceDuration); } } else { // 有声音,重置定时器 m_silenceTimer->stop(); m_silenceTimer->start(m_silenceDuration); } } // 4. 自动阈值计算逻辑 if (isAutoThreshold) { m_rmsValues.push_back(m_rmsValue); emit rmsRealValue(m_rmsValue); } } qreal AudioInput::calculateRMS(const QByteArray& buffer) { if (buffer.isEmpty()) return 0; // 假设是 Int16 格式 (16位深) // 如果是 Stereo,数据排列是 L R L R... // 简单的 RMS 计算可以将所有通道数据视为一个长序列 const qint16 *data = reinterpret_cast(buffer.constData()); const int sampleCount = buffer.size() / sizeof(qint16); // 样本数量 if (sampleCount == 0) return 0; qreal sumSquared = 0; for (int i = 0; i < sampleCount; ++i) { const qreal sample = static_cast(data[i]); sumSquared += sample * sample; } return qSqrt(sumSquared / sampleCount); } // 启动带时长的录音 void AudioInput::startAudioWithDuration(int duration) { startAudio(); m_timer->start(duration * 1000); } void AudioInput::onTimeout() { stopAudio(); qDebug() << "Recording stopped by duration timeout."; } // 获取所有音频输入设备 QList AudioInput::getAvailableAudioInputDevices() { QList list; const auto devices = QMediaDevices::audioInputs(); for (const auto &device : devices) { list.append(device.description()); } return list; } // 设置当前录音设备 void AudioInput::setAudioInputDevice(const QString &deviceName) { const auto devices = QMediaDevices::audioInputs(); for (const auto &device : devices) { if (device.description() == deviceName) { m_currentDevice = device; break; } } } // 启动自动录音 (VAD) void AudioInput::startAutoStopAudio(const qreal silenceThreshold, const int silenceDuration) { isAutoRecording = true; m_silenceThreshold = silenceThreshold; m_silenceDuration = silenceDuration; startAudio(); // 延迟启动静音检测,给一点缓冲时间 QTimer::singleShot(200, this, [this](){ m_silenceTimer->start(m_silenceDuration); }); } // 启动阈值计算 void AudioInput::startAutoThresholdClu(int Duration) { isAutoThreshold = true; m_rmsValues.clear(); startAudio(); m_thresholdTimer->start(Duration); } void AudioInput::thresholdTimeout() { isAutoThreshold = false; stopAudio(); // 内部会处理 stop if (!m_rmsValues.empty()) { const double sum = std::accumulate(m_rmsValues.begin(), m_rmsValues.end(), 0.0); const double avg = sum / m_rmsValues.size(); m_silenceThreshold = avg + 500.0; // 这里的 500 是经验值,可以根据需要调整 emit thresholdCalculated(m_silenceThreshold); } else { emit thresholdCalculated(0); } } QByteArray AudioInput::generateWavHeader(const quint32 dataSize) const { // WAV头结构定义 struct WavHeader { char riff[4] = {'R','I','F','F'}; quint32 chunkSize; char wave[4] = {'W','A','V','E'}; char fmt[4] = {'f','m','t',' '}; quint32 fmtSize = 16; quint16 audioFormat = 1; // PCM quint16 numChannels; quint32 sampleRate; quint32 byteRate; quint16 blockAlign; quint16 bitsPerSample; char data[4] = {'d','a','t','a'}; quint32 dataSize; } header; header.numChannels = static_cast(m_format.channelCount()); header.sampleRate = static_cast(m_format.sampleRate()); header.bitsPerSample = 16; // 我们强制使用了 Int16 header.byteRate = header.sampleRate * header.numChannels * (header.bitsPerSample / 8); header.blockAlign = header.numChannels * (header.bitsPerSample / 8); header.dataSize = dataSize; header.chunkSize = 36 + dataSize; return QByteArray(reinterpret_cast(&header), sizeof(WavHeader)); } void AudioInput::setSilenceThreshold(const qreal silenceThreshold) { this->m_silenceThreshold = silenceThreshold; } qreal AudioInput::getSilenceThreshold() const { return this->m_silenceThreshold; }