You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

178 lines
7.0 KiB

#include "audiorecorder.h"
#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"
}
#include "../../lib/debug_config.h"
#include <cstring>
#include <cstdlib>
#include <cmath>
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));
}
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;
}
float rms = sqrtf(sumSq / (float)(frameCount * m_channels));
m_peakLevel = rms;
m_recordedFrames.fetch_add(frameCount, std::memory_order_relaxed);
}
AudioRecorder::AudioRecorder(QObject* parent)
: QObject(parent) {
m_compressor = audio_compressor_create();
if (m_compressor) {
audio_compressor_config_t cfg = {0};
cfg.sample_rate = m_sampleRate;
cfg.channels = m_channels;
cfg.lookahead_ms = 100;
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 = (int)(m_recordedFrames.load(std::memory_order_relaxed) * 1000 / m_sampleRate);
emit durationChanged(m_elapsedMs);
}
});
}
AudioRecorder::~AudioRecorder() {
shutdown();
audio_compressor_destroy(m_compressor);
m_compressor = nullptr;
}
bool AudioRecorder::init() {
m_initialized = true;
return true;
}
void AudioRecorder::shutdown() {
stopRecording();
m_recoveryTimer.stop();
m_capture.close();
m_initialized = false;
}
void AudioRecorder::startRecording() {
if (m_recording) return;
if (!m_initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "recorder: not initialized"); return; }
m_pcmBuffer.clear();
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);
}
m_recording = true;
m_capture.retryNow();
recoverCapture();
m_recoveryTimer.start();
DEBUG_INFO(DEBUG_CATEGORY_CALL, "recorder: recording requested compressor=%d", m_captureCompressorEnabled);
emit recordingStarted();
}
void AudioRecorder::recoverCapture() {
if (!m_recording) 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();
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();
}
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 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);
}
int AudioRecorder::durationMs() const {
return m_elapsedMs;
}
/* 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_captureCompressorEnabled) {
auto* replacement = audio_compressor_clone_config(m_compressor);
if (!replacement) return false;
request->compressor = m_compressor;
m_compressor = replacement;
} else {
auto* samples = new std::vector<int16_t>(std::move(m_pcmBuffer));
request->pcm = samples->data(); request->pcm_count = samples->size(); request->pcm_owner = samples;
request->pcm_release = [](void* owner) { delete static_cast<std::vector<int16_t>*>(owner); };
}
return true;
}
float AudioRecorder::peakLevel() const { return m_peakLevel.exchange(0.0f); }
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;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "recorder: compressor=%d for next recording", enabled);
}