feat: integrate Opus codec into lib/libopus + voice messages in chatgui
- Add lib/libopus/ (opus v1.6.1 embedded source: CELT 18 .c, SILK 77 .c, SILK float 28 .c, OPUS core 14 .c) - Add lib/opus_codec.h/.c (wrapper: encoder, decoder, raw PCM int16) - Add tests/test_opus_codec.c (7 tests: silence, sine, compression, PLC, null, defaults, multi-frame) - chatgui: AudioRecorder (miniaudio capture, 48kHz mono) - chatgui: VoiceEncoder (PCM→Opus file with custom container) - chatgui: VoicePlayer (Opus→PCM playback via SoundManager) - chatgui: PTT button in InputBar, voice message send/receive in MessageList - chatgui: AudioDeviceSettingsPage (device selection, persisted in ui_state) - chatgui: MessageDelegate voice message rendering (play button + duration) - Update autotools: configure.ac, Makefile.am, src/Makefile.am, tests/Makefile.am - Update CMake: chatgui CMakeLists.txt, libutun CMakeLists.txt (link libopus_internal.a)
This commit is contained in:
1 parent
4a23593020
commit
4fde0b0325
260 files changed
+72665
-16
No files matched your search
@@ -0,0 +1,129 @@
|
||||
#include "audiorecorder.h"
|
||||
#include "sound_manager.h"
|
||||
#include "miniaudio.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
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 cur = self->m_pcmBuffer.size();
|
||||
size_t add = (size_t)frameCount * (size_t)self->m_channels;
|
||||
self->m_pcmBuffer.resize(cur + add);
|
||||
std::memcpy(self->m_pcmBuffer.data() + cur, src, add * sizeof(int16_t));
|
||||
}
|
||||
|
||||
AudioRecorder::AudioRecorder(QObject* parent)
|
||||
: QObject(parent) {
|
||||
g_recorder = this;
|
||||
m_durationTimer.setInterval(100);
|
||||
connect(&m_durationTimer, &QTimer::timeout, this, [this]() {
|
||||
if (m_recording) {
|
||||
m_elapsedMs += 100;
|
||||
emit durationChanged(m_elapsedMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AudioRecorder::~AudioRecorder() {
|
||||
shutdown();
|
||||
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;
|
||||
qDebug("AudioRecorder: initialized (sampleRate=%d, channels=%d)", m_sampleRate, m_channels);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioRecorder::shutdown() {
|
||||
if (m_recording) stopRecording();
|
||||
if (m_device) {
|
||||
delete m_device;
|
||||
m_device = nullptr;
|
||||
}
|
||||
m_initialized = false;
|
||||
qDebug("AudioRecorder: shutdown");
|
||||
}
|
||||
|
||||
void AudioRecorder::startRecording() {
|
||||
if (m_recording) return;
|
||||
if (!m_initialized) { qWarning("AudioRecorder: not initialized"); return; }
|
||||
|
||||
m_pcmBuffer.clear();
|
||||
m_elapsedMs = 0;
|
||||
|
||||
ma_context* ctx = SoundManager::instance()->context();
|
||||
if (!ctx) { qWarning("AudioRecorder: 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;
|
||||
qDebug("AudioRecorder: 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) {
|
||||
qWarning("AudioRecorder: device init failed (%d), trying default", r);
|
||||
config.capture.pDeviceID = nullptr;
|
||||
r = ma_device_init(ctx, &config, m_device);
|
||||
if (r != MA_SUCCESS) {
|
||||
qWarning("AudioRecorder: default device init also failed (%d)", r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
r = ma_device_start(m_device);
|
||||
if (r != MA_SUCCESS) {
|
||||
qWarning("AudioRecorder: device start failed (%d)", r);
|
||||
ma_device_uninit(m_device);
|
||||
return;
|
||||
}
|
||||
|
||||
m_recording = true;
|
||||
m_durationTimer.start();
|
||||
qDebug("AudioRecorder: recording started");
|
||||
emit recordingStarted();
|
||||
}
|
||||
|
||||
void AudioRecorder::stopRecording() {
|
||||
if (!m_recording) return;
|
||||
|
||||
m_durationTimer.stop();
|
||||
ma_device_stop(m_device);
|
||||
ma_device_uninit(m_device);
|
||||
|
||||
m_recording = false;
|
||||
int duration = m_elapsedMs;
|
||||
qDebug("AudioRecorder: recording stopped, duration=%dms, pcmSamples=%zu", duration, m_pcmBuffer.size());
|
||||
emit recordingStopped(duration);
|
||||
emit durationChanged(0);
|
||||
}
|
||||
|
||||
int AudioRecorder::durationMs() const {
|
||||
return m_elapsedMs;
|
||||
}
|
||||
Reference in new issue
Block a user