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.
200 lines
6.5 KiB
200 lines
6.5 KiB
#include "audiorecorder.h" |
|
#include "miniaudio.h" |
|
#include "sound_manager.h" |
|
|
|
extern "C" { |
|
#include "../../lib/audio_compressor.h" |
|
} |
|
|
|
#include "../../lib/debug_config.h" |
|
#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; |
|
|
|
const int16_t* src = (const int16_t*)pInput; |
|
size_t add = (size_t)frameCount * (size_t)self->m_channels; |
|
|
|
if (self->m_compressor && self->m_compressorEnabled) { |
|
audio_compressor_push(self->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)); |
|
} |
|
|
|
float sumSq = 0.0f; |
|
for (unsigned int i = 0; i < frameCount * (unsigned int)self->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; |
|
|
|
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); |
|
} |
|
} |
|
|
|
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; |
|
audio_compressor_configure(m_compressor, &cfg); |
|
} |
|
m_durationTimer.setInterval(100); |
|
connect(&m_durationTimer, &QTimer::timeout, this, [this]() { |
|
if (m_recording) { |
|
m_elapsedMs += 100; |
|
emit durationChanged(m_elapsedMs); |
|
} |
|
}); |
|
} |
|
|
|
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_INFO(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; } |
|
m_initialized = false; |
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "shutdown"); |
|
} |
|
|
|
void AudioRecorder::startRecording() { |
|
if (m_recording) return; |
|
if (!m_initialized) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "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_INFO(DEBUG_CATEGORY_DEBUG, "using capture device[%d]: %s", m_captureDeviceIndex, |
|
pCaptureDevices[m_captureDeviceIndex].name); |
|
} |
|
} |
|
|
|
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"); |
|
emit recordingStarted(); |
|
} |
|
|
|
void AudioRecorder::stopRecording() { |
|
if (!m_recording) return; |
|
|
|
m_durationTimer.stop(); |
|
if (m_deviceStarted) { |
|
ma_device_stop(m_device); |
|
ma_device_uninit(m_device); |
|
m_deviceStarted = false; |
|
} |
|
m_recording = false; |
|
|
|
if (m_compressor && m_compressorEnabled) audio_compressor_flush(m_compressor); |
|
|
|
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); |
|
emit recordingStopped(duration); |
|
emit durationChanged(0); |
|
} |
|
|
|
int AudioRecorder::durationMs() const { |
|
return m_elapsedMs; |
|
} |
|
|
|
float AudioRecorder::peakLevel() const { |
|
float v = m_peakLevel; |
|
m_peakLevel = 0.0f; |
|
return v; |
|
} |
|
|
|
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::setCompressorEnabled(bool enabled) { |
|
m_compressorEnabled = enabled; |
|
if (m_compressor) audio_compressor_set_enabled(m_compressor, enabled); |
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "compressor %s", enabled ? "enabled" : "disabled"); |
|
}
|
|
|