Раздели аудиопотоки и привяжи AEC к фактическому общему выходу
This commit is contained in:
1 parent
d7fb0c83f9
commit
bd082a26bf
30 files changed
+1839
-412
No files matched your search
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user