Раздели аудиопотоки и привяжи AEC к фактическому общему выходу

This commit is contained in:
evgeny committed 2026-10-02 10:22:34 +03:00
1 parent d7fb0c83f9
commit bd082a26bf
30 files changed
+1839 -412

No files matched your search

+60 -24
View File
@@ -12,29 +12,50 @@ extern "C" {
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <chrono>
#include <exception>
#include <system_error>
void AudioRecorder::captureCallback(const void* pInput, unsigned int frameCount) {
if (!pInput || !frameCount || !m_recording.load()) return;
const int16_t* src = (const int16_t*)pInput;
size_t add = (size_t)frameCount * (size_t)m_channels;
if (m_compressor && m_captureCompressorEnabled) {
audio_compressor_push(m_compressor, src, add);
} else {
size_t cur = m_pcmBuffer.size();
m_pcmBuffer.resize(cur + add);
std::memcpy(m_pcmBuffer.data() + cur, src, add * sizeof(int16_t));
/* Аппаратный callback не выделяет память и не обрабатывает компрессор. */
void AudioRecorder::captureCallback(const void* input, unsigned frames) {
if (!input || !m_recording.load() || m_captureFailed.load()) return;
auto* pcm = static_cast<const int16_t*>(input);
for (unsigned offset = 0; offset < frames;) {
CaptureBlock block{}; block.frames = std::min(frames - offset, 960u);
memcpy(block.pcm, pcm + offset * m_channels, block.frames * m_channels * sizeof(int16_t));
if (!m_captureQueue.push(block)) { m_captureFailed = true; break; }
offset += block.frames;
}
m_wake.notify_one();
}
float sumSq = 0.0f;
for (unsigned int i = 0; i < frameCount * (unsigned int)m_channels; i++) {
float v = (float)src[i] / 32768.0f;
sumSq += v * v;
/* Только worker изменяет PCM и compressor; GUI получает атомарные метрики. */
void AudioRecorder::recordLoop() {
try {
while (m_workerRunning || m_captureQueue.size()) {
CaptureBlock block{};
if (!m_captureQueue.pop(block)) {
std::unique_lock lock(m_waitMutex);
m_wake.wait_for(lock, std::chrono::milliseconds(5));
continue;
}
if (m_captureFailed) continue;
size_t count = (size_t)block.frames * m_channels;
if (!m_meterOnly) {
if (m_compressor && m_captureCompressorEnabled) {
if (audio_compressor_push(m_compressor, block.pcm, count) != 0) { m_captureFailed = true; continue; }
} else m_pcmBuffer.insert(m_pcmBuffer.end(), block.pcm, block.pcm + count);
}
double sum = 0;
for (size_t i = 0; i < count; ++i) { double value = block.pcm[i] / 32768.0; sum += value * value; }
m_peakLevel = (float)std::sqrt(sum / count);
m_recordedFrames.fetch_add(block.frames, std::memory_order_relaxed);
}
} catch (const std::exception& error) {
m_captureFailed = true;
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "recorder: worker failed reason=%s", error.what());
}
float rms = sqrtf(sumSq / (float)(frameCount * m_channels));
m_peakLevel = rms;
m_recordedFrames.fetch_add(frameCount, std::memory_order_relaxed);
}
AudioRecorder::AudioRecorder(QObject* parent)
@@ -90,17 +111,26 @@ void AudioRecorder::shutdown() {
m_initialized = false;
}
void AudioRecorder::startRecording() {
void AudioRecorder::startRecording(bool meterOnly) {
if (m_recording) return;
if (!m_initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "recorder: not initialized"); return; }
m_pcmBuffer.clear();
m_pcmBuffer.clear(); m_meterOnly = meterOnly;
m_captureQueue.reset(); m_captureFailed = false;
m_recordedFrames = 0; m_elapsedMs = 0; m_peakLevel = 0;
m_captureCompressorEnabled = m_compressorEnabled;
m_captureCompressorEnabled = !meterOnly && m_compressorEnabled;
if (m_compressor) {
audio_compressor_configure(m_compressor, &m_compressorConfig);
if (audio_compressor_configure(m_compressor, &m_compressorConfig) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "recorder: cannot configure compressor"); return;
}
audio_compressor_set_enabled(m_compressor, m_captureCompressorEnabled);
audio_compressor_reset(m_compressor);
}
m_workerRunning = true;
try { m_recordWorker = std::thread(&AudioRecorder::recordLoop, this); }
catch (const std::system_error& error) {
m_workerRunning = false;
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "recorder: worker start failed reason=%s", error.what()); return;
}
m_recording = true;
m_capture.retryNow();
recoverCapture();
@@ -111,6 +141,10 @@ void AudioRecorder::startRecording() {
void AudioRecorder::recoverCapture() {
if (!m_recording) return;
if (m_captureFailed) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "recorder: capture loss or processing failure; recording rejected");
stopRecording(); return;
}
if (m_capture.needsRecovery()) { m_capture.close(); m_durationTimer.stop(); m_capture.retryLater(); }
if (!m_capture.retryReady()) return;
ma_context* context = SoundManager::instance()->context();
@@ -137,6 +171,8 @@ void AudioRecorder::stopRecording() {
if (!m_recording.exchange(false)) return;
m_recoveryTimer.stop(); m_durationTimer.stop();
m_capture.close();
m_workerRunning = false; m_wake.notify_one();
if (m_recordWorker.joinable()) m_recordWorker.join();
m_elapsedMs = (int)(m_recordedFrames.load() * 1000 / m_sampleRate);
int duration = m_elapsedMs;
size_t samples = m_compressor && m_captureCompressorEnabled ? audio_compressor_output_size(m_compressor) : m_pcmBuffer.size();
@@ -151,7 +187,7 @@ int AudioRecorder::durationMs() const {
/* Worker получает владельца буфера; новая запись имеет отдельное состояние. */
bool AudioRecorder::takeRecording(struct attachment_send_req* request) {
if (!request || m_recording) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "recorder: cannot detach active recording"); return false; }
if (!request || m_recording || m_meterOnly || m_captureFailed) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "recorder: cannot detach active recording"); return false; }
if (m_compressor && m_captureCompressorEnabled) {
auto* replacement = audio_compressor_clone_config(m_compressor);
if (!replacement) return false;