Сохрани накопленные исправления аудио, async и диагностики
This commit is contained in:
1 parent
b1e3193899
commit
bdcf8b5634
59 files changed
+4611
-1441
No files matched your search
@@ -2,6 +2,7 @@
|
||||
#include "media_async/attachment_send.h"
|
||||
#include "miniaudio.h"
|
||||
#include "sound_manager.h"
|
||||
#include "../transport/audio_diagnostics.h"
|
||||
|
||||
extern "C" {
|
||||
#include "../../lib/audio_compressor.h"
|
||||
@@ -11,57 +12,60 @@ extern "C" {
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
static AudioRecorder* g_recorder = nullptr;
|
||||
|
||||
void audioCaptureCallback(ma_device* pDevice, void* pOutput, const void* pInput, unsigned int frameCount) {
|
||||
(void)pOutput;
|
||||
AudioRecorder* self = g_recorder;
|
||||
if (!self || !self->m_recording) return;
|
||||
if (!pInput) return;
|
||||
|
||||
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)self->m_channels;
|
||||
size_t add = (size_t)frameCount * (size_t)m_channels;
|
||||
|
||||
if (self->m_compressor && self->m_compressorEnabled) {
|
||||
audio_compressor_push(self->m_compressor, src, add);
|
||||
if (m_compressor && m_captureCompressorEnabled) {
|
||||
audio_compressor_push(m_compressor, src, add);
|
||||
} else {
|
||||
size_t cur = self->m_pcmBuffer.size();
|
||||
self->m_pcmBuffer.resize(cur + add);
|
||||
std::memcpy(self->m_pcmBuffer.data() + cur, src, add * sizeof(int16_t));
|
||||
size_t cur = m_pcmBuffer.size();
|
||||
m_pcmBuffer.resize(cur + add);
|
||||
std::memcpy(m_pcmBuffer.data() + cur, src, add * sizeof(int16_t));
|
||||
}
|
||||
|
||||
float sumSq = 0.0f;
|
||||
for (unsigned int i = 0; i < frameCount * (unsigned int)self->m_channels; i++) {
|
||||
for (unsigned int i = 0; i < frameCount * (unsigned int)m_channels; i++) {
|
||||
float v = (float)src[i] / 32768.0f;
|
||||
sumSq += v * v;
|
||||
}
|
||||
float rms = sqrtf(sumSq / (float)(frameCount * self->m_channels));
|
||||
self->m_peakLevel = rms;
|
||||
float rms = sqrtf(sumSq / (float)(frameCount * m_channels));
|
||||
m_peakLevel = rms;
|
||||
|
||||
self->m_recentRms[self->m_rmsIdx % 5] = rms;
|
||||
self->m_rmsIdx++;
|
||||
if (self->m_rmsIdx >= 5) {
|
||||
float avg = 0.0f;
|
||||
for (int i = 0; i < 5; i++) avg += self->m_recentRms[i];
|
||||
avg /= 5.0f;
|
||||
self->m_waveformLevels.push_back(avg);
|
||||
}
|
||||
m_recordedFrames.fetch_add(frameCount, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
AudioRecorder::AudioRecorder(QObject* parent)
|
||||
: QObject(parent) {
|
||||
g_recorder = this;
|
||||
m_compressor = audio_compressor_create();
|
||||
if (m_compressor) {
|
||||
audio_compressor_config_t cfg = {0};
|
||||
cfg.sample_rate = m_sampleRate;
|
||||
cfg.channels = m_channels;
|
||||
m_compressorConfig = cfg;
|
||||
audio_compressor_configure(m_compressor, &cfg);
|
||||
}
|
||||
m_captureId = SoundManager::instance()->captureDevice();
|
||||
connect(SoundManager::instance(), &SoundManager::devicesChanged, this, [this]() {
|
||||
QString id = SoundManager::instance()->captureDevice();
|
||||
if (!m_customDevice && id != m_captureId) {
|
||||
m_captureId = id; m_capture.close(); m_capture.retryNow(); recoverCapture();
|
||||
}
|
||||
});
|
||||
m_recoveryTimer.setInterval(200);
|
||||
connect(&m_recoveryTimer, &QTimer::timeout, this, &AudioRecorder::recoverCapture);
|
||||
connect(SoundManager::instance(), &SoundManager::contextAboutToReset, this, [this]() {
|
||||
m_capture.close(); m_durationTimer.stop();
|
||||
});
|
||||
connect(SoundManager::instance(), &SoundManager::contextRestored, this, [this]() {
|
||||
m_capture.retryNow(); recoverCapture();
|
||||
});
|
||||
m_durationTimer.setInterval(100);
|
||||
connect(&m_durationTimer, &QTimer::timeout, this, [this]() {
|
||||
if (m_recording) {
|
||||
m_elapsedMs += 100;
|
||||
m_elapsedMs = (int)(m_recordedFrames.load(std::memory_order_relaxed) * 1000 / m_sampleRate);
|
||||
emit durationChanged(m_elapsedMs);
|
||||
}
|
||||
});
|
||||
@@ -71,103 +75,71 @@ AudioRecorder::~AudioRecorder() {
|
||||
shutdown();
|
||||
audio_compressor_destroy(m_compressor);
|
||||
m_compressor = nullptr;
|
||||
if (g_recorder == this) g_recorder = nullptr;
|
||||
}
|
||||
|
||||
bool AudioRecorder::init() {
|
||||
if (m_initialized) return true;
|
||||
m_device = new ma_device;
|
||||
std::memset(m_device, 0, sizeof(*m_device));
|
||||
m_initialized = true;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "initialized (sampleRate=%d, channels=%d)", m_sampleRate, m_channels);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioRecorder::shutdown() {
|
||||
if (!m_initialized) return;
|
||||
if (m_recording) stopRecording();
|
||||
if (m_deviceStarted) {
|
||||
m_deviceStarted = false;
|
||||
if (m_device) { ma_device_stop(m_device); ma_device_uninit(m_device); }
|
||||
}
|
||||
if (m_device) { delete m_device; m_device = nullptr; }
|
||||
stopRecording();
|
||||
m_recoveryTimer.stop();
|
||||
m_capture.close();
|
||||
m_initialized = false;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "shutdown");
|
||||
}
|
||||
|
||||
void AudioRecorder::startRecording() {
|
||||
if (m_recording) return;
|
||||
if (!m_initialized) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "not initialized"); return; }
|
||||
|
||||
if (!m_initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "recorder: not initialized"); return; }
|
||||
m_pcmBuffer.clear();
|
||||
m_waveformLevels.clear();
|
||||
m_rmsIdx = 0;
|
||||
m_elapsedMs = 0;
|
||||
|
||||
if (m_compressor) audio_compressor_reset(m_compressor);
|
||||
|
||||
ma_context* ctx = SoundManager::instance()->context();
|
||||
if (!ctx) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "no audio context"); return; }
|
||||
|
||||
ma_device_config config = ma_device_config_init(ma_device_type_capture);
|
||||
config.capture.format = ma_format_s16;
|
||||
config.capture.channels = (ma_uint32)m_channels;
|
||||
config.sampleRate = (ma_uint32)m_sampleRate;
|
||||
config.periodSizeInFrames = 480;
|
||||
config.dataCallback = audioCaptureCallback;
|
||||
config.pUserData = this;
|
||||
|
||||
if (m_captureDeviceIndex >= 0) {
|
||||
ma_device_info* pCaptureDevices = nullptr;
|
||||
ma_uint32 captureCount = 0;
|
||||
ma_result r = ma_context_get_devices(ctx, nullptr, nullptr, &pCaptureDevices, &captureCount);
|
||||
if (r == MA_SUCCESS && (ma_uint32)m_captureDeviceIndex < captureCount) {
|
||||
config.capture.pDeviceID = &pCaptureDevices[m_captureDeviceIndex].id;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "using capture device[%d]: %s", m_captureDeviceIndex,
|
||||
pCaptureDevices[m_captureDeviceIndex].name);
|
||||
}
|
||||
m_recordedFrames = 0; m_elapsedMs = 0; m_peakLevel = 0;
|
||||
m_captureCompressorEnabled = m_compressorEnabled;
|
||||
if (m_compressor) {
|
||||
audio_compressor_configure(m_compressor, &m_compressorConfig);
|
||||
audio_compressor_set_enabled(m_compressor, m_captureCompressorEnabled);
|
||||
audio_compressor_reset(m_compressor);
|
||||
}
|
||||
|
||||
ma_result r = ma_device_init(ctx, &config, m_device);
|
||||
if (r != MA_SUCCESS) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "device init failed (%d), trying default", r);
|
||||
config.capture.pDeviceID = nullptr;
|
||||
r = ma_device_init(ctx, &config, m_device);
|
||||
if (r != MA_SUCCESS) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "default device init also failed (%d)", r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
r = ma_device_start(m_device);
|
||||
if (r != MA_SUCCESS) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "device start failed (%d)", r);
|
||||
ma_device_uninit(m_device);
|
||||
return;
|
||||
}
|
||||
|
||||
m_recording = true;
|
||||
m_deviceStarted = true;
|
||||
m_durationTimer.start();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "recording started");
|
||||
m_capture.retryNow();
|
||||
recoverCapture();
|
||||
m_recoveryTimer.start();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "recorder: recording requested compressor=%d", m_captureCompressorEnabled);
|
||||
emit recordingStarted();
|
||||
}
|
||||
|
||||
void AudioRecorder::stopRecording() {
|
||||
void AudioRecorder::recoverCapture() {
|
||||
if (!m_recording) return;
|
||||
|
||||
m_durationTimer.stop();
|
||||
if (m_deviceStarted) {
|
||||
ma_device_stop(m_device);
|
||||
ma_device_uninit(m_device);
|
||||
m_deviceStarted = false;
|
||||
if (m_capture.needsRecovery()) { m_capture.close(); m_durationTimer.stop(); m_capture.retryLater(); }
|
||||
if (!m_capture.retryReady()) return;
|
||||
ma_context* context = SoundManager::instance()->context();
|
||||
if (!context) return;
|
||||
ma_device_config config = ma_device_config_init(ma_device_type_capture);
|
||||
config.capture.format = ma_format_s16;
|
||||
config.capture.channels = m_channels;
|
||||
config.sampleRate = m_sampleRate;
|
||||
ma_device_id id;
|
||||
bool selected = SoundManager::deviceId(m_captureId, &id);
|
||||
if (selected) config.capture.pDeviceID = &id;
|
||||
auto callback = [this](const void* input, void*, unsigned int frames) { captureCallback(input, frames); };
|
||||
bool ready = m_capture.init(context, config, "voice-recorder", 0, callback) && m_capture.start();
|
||||
if (!ready) {
|
||||
if (!selected) return;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "recorder: selected input unavailable, trying default");
|
||||
config.capture.pDeviceID = nullptr;
|
||||
ready = m_capture.init(context, config, "voice-recorder", 0, callback) && m_capture.start();
|
||||
}
|
||||
m_recording = false;
|
||||
if (ready) m_durationTimer.start();
|
||||
}
|
||||
|
||||
void AudioRecorder::stopRecording() {
|
||||
if (!m_recording.exchange(false)) return;
|
||||
m_recoveryTimer.stop(); m_durationTimer.stop();
|
||||
m_capture.close();
|
||||
m_elapsedMs = (int)(m_recordedFrames.load() * 1000 / m_sampleRate);
|
||||
int duration = m_elapsedMs;
|
||||
size_t bufSize = (m_compressor && m_compressorEnabled) ? audio_compressor_output_size(m_compressor) : m_pcmBuffer.size();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "recording stopped, duration=%dms, pcmSamples=%zu, compressor=%d",
|
||||
duration, bufSize, m_compressorEnabled ? 1 : 0);
|
||||
size_t samples = m_compressor && m_captureCompressorEnabled ? audio_compressor_output_size(m_compressor) : m_pcmBuffer.size();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "recorder: stopped duration=%dms samples=%zu", duration, samples);
|
||||
emit recordingStopped(duration);
|
||||
emit durationChanged(0);
|
||||
}
|
||||
@@ -179,7 +151,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 (m_compressor && m_compressorEnabled) {
|
||||
if (m_compressor && m_captureCompressorEnabled) {
|
||||
auto* replacement = audio_compressor_clone_config(m_compressor);
|
||||
if (!replacement) return false;
|
||||
request->compressor = m_compressor;
|
||||
@@ -192,24 +164,14 @@ bool AudioRecorder::takeRecording(struct attachment_send_req* request) {
|
||||
return true;
|
||||
}
|
||||
|
||||
float AudioRecorder::peakLevel() const {
|
||||
float v = m_peakLevel;
|
||||
m_peakLevel = 0.0f;
|
||||
return v;
|
||||
}
|
||||
float AudioRecorder::peakLevel() const { return m_peakLevel.exchange(0.0f); }
|
||||
|
||||
const int16_t* AudioRecorder::bufferData() const {
|
||||
if (m_compressor && m_compressorEnabled) return audio_compressor_output(m_compressor);
|
||||
return m_pcmBuffer.data();
|
||||
}
|
||||
|
||||
size_t AudioRecorder::bufferSize() const {
|
||||
if (m_compressor && m_compressorEnabled) return audio_compressor_output_size(m_compressor);
|
||||
return m_pcmBuffer.size();
|
||||
void AudioRecorder::configureCompressor(const audio_compressor_config_t& config) {
|
||||
m_compressorConfig = config; /* Применяется перед следующей записью, когда callback ещё не запущен. */
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "recorder: compressor configuration saved for next recording");
|
||||
}
|
||||
|
||||
void AudioRecorder::setCompressorEnabled(bool enabled) {
|
||||
m_compressorEnabled = enabled;
|
||||
if (m_compressor) audio_compressor_set_enabled(m_compressor, enabled);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "compressor %s", enabled ? "enabled" : "disabled");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "recorder: compressor=%d for next recording", enabled);
|
||||
}
|
||||
Reference in new issue
Block a user