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,161 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "opus_codec.h"
|
||||
#include "opus.h"
|
||||
#include "debug_config.h"
|
||||
|
||||
#define OPUS_DEFAULT_SAMPLE_RATE 48000
|
||||
#define OPUS_DEFAULT_BITRATE 32000
|
||||
#define OPUS_DEFAULT_COMPLEXITY 5
|
||||
#define OPUS_DEFAULT_FRAME_MS 20
|
||||
|
||||
struct opus_codec_encoder {
|
||||
OpusEncoder* opus_enc;
|
||||
int sample_rate;
|
||||
int channels;
|
||||
int frame_samples;
|
||||
};
|
||||
|
||||
struct opus_codec_decoder {
|
||||
OpusDecoder* opus_dec;
|
||||
int sample_rate;
|
||||
int channels;
|
||||
};
|
||||
|
||||
static int frame_samples_for_rate(int sample_rate) {
|
||||
return sample_rate * OPUS_DEFAULT_FRAME_MS / 1000;
|
||||
}
|
||||
|
||||
opus_codec_encoder_t* opus_codec_encoder_create(int sample_rate, int channels) {
|
||||
int err;
|
||||
opus_codec_encoder_t* enc;
|
||||
|
||||
if (sample_rate <= 0) sample_rate = OPUS_DEFAULT_SAMPLE_RATE;
|
||||
if (channels <= 0 || channels > 2) channels = 1;
|
||||
|
||||
enc = (opus_codec_encoder_t*)calloc(1, sizeof(*enc));
|
||||
if (!enc) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "opus_codec_encoder_create: OOM");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enc->sample_rate = sample_rate;
|
||||
enc->channels = channels;
|
||||
enc->frame_samples = frame_samples_for_rate(sample_rate);
|
||||
|
||||
enc->opus_enc = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_VOIP, &err);
|
||||
if (err != OPUS_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "opus_codec_encoder_create: opus_encoder_create failed: %s (%d)",
|
||||
opus_codec_strerror(err), err);
|
||||
free(enc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
opus_encoder_ctl(enc->opus_enc, OPUS_SET_BITRATE(OPUS_DEFAULT_BITRATE));
|
||||
opus_encoder_ctl(enc->opus_enc, OPUS_SET_COMPLEXITY(OPUS_DEFAULT_COMPLEXITY));
|
||||
opus_encoder_ctl(enc->opus_enc, OPUS_SET_SIGNAL(OPUS_AUTO));
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "opus_codec_encoder_create: ok rate=%d ch=%d frame_samples=%d bitrate=%d",
|
||||
sample_rate, channels, enc->frame_samples, OPUS_DEFAULT_BITRATE);
|
||||
return enc;
|
||||
}
|
||||
|
||||
void opus_codec_encoder_destroy(opus_codec_encoder_t* enc) {
|
||||
if (!enc) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "opus_codec_encoder_destroy");
|
||||
if (enc->opus_enc) opus_encoder_destroy(enc->opus_enc);
|
||||
free(enc);
|
||||
}
|
||||
|
||||
int opus_codec_encode(opus_codec_encoder_t* enc, const int16_t* pcm, int frame_samples,
|
||||
uint8_t* out, int out_cap) {
|
||||
if (!enc || !pcm || !out || out_cap <= 0 || frame_samples <= 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "opus_codec_encode: invalid args");
|
||||
return -1;
|
||||
}
|
||||
int result = opus_encode(enc->opus_enc, pcm, frame_samples, out, out_cap);
|
||||
if (result < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "opus_codec_encode: encode failed: %s (%d), pcm_samples=%d cap=%d",
|
||||
opus_codec_strerror(result), result, frame_samples, out_cap);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
opus_codec_decoder_t* opus_codec_decoder_create(int sample_rate, int channels) {
|
||||
int err;
|
||||
opus_codec_decoder_t* dec;
|
||||
|
||||
if (sample_rate <= 0) sample_rate = OPUS_DEFAULT_SAMPLE_RATE;
|
||||
if (channels <= 0 || channels > 2) channels = 1;
|
||||
|
||||
dec = (opus_codec_decoder_t*)calloc(1, sizeof(*dec));
|
||||
if (!dec) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "opus_codec_decoder_create: OOM");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dec->sample_rate = sample_rate;
|
||||
dec->channels = channels;
|
||||
|
||||
dec->opus_dec = opus_decoder_create(sample_rate, channels, &err);
|
||||
if (err != OPUS_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "opus_codec_decoder_create: opus_decoder_create failed: %s (%d)",
|
||||
opus_codec_strerror(err), err);
|
||||
free(dec);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "opus_codec_decoder_create: ok rate=%d ch=%d",
|
||||
sample_rate, channels);
|
||||
return dec;
|
||||
}
|
||||
|
||||
void opus_codec_decoder_destroy(opus_codec_decoder_t* dec) {
|
||||
if (!dec) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "opus_codec_decoder_destroy");
|
||||
if (dec->opus_dec) opus_decoder_destroy(dec->opus_dec);
|
||||
free(dec);
|
||||
}
|
||||
|
||||
int opus_codec_decode(opus_codec_decoder_t* dec, const uint8_t* data, int len,
|
||||
int16_t* pcm, int frame_samples) {
|
||||
if (!dec || !pcm || frame_samples <= 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "opus_codec_decode: invalid args");
|
||||
return -1;
|
||||
}
|
||||
int result = opus_decode(dec->opus_dec, data, len, pcm, frame_samples, 0);
|
||||
if (result < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "opus_codec_decode: decode failed: %s (%d), len=%d",
|
||||
opus_codec_strerror(result), result, len);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int opus_codec_encoder_bitrate_set(opus_codec_encoder_t* enc, int bitrate_bps) {
|
||||
if (!enc || !enc->opus_enc) return -1;
|
||||
return opus_encoder_ctl(enc->opus_enc, OPUS_SET_BITRATE(bitrate_bps));
|
||||
}
|
||||
|
||||
int opus_codec_encoder_complexity_set(opus_codec_encoder_t* enc, int complexity) {
|
||||
if (!enc || !enc->opus_enc) return -1;
|
||||
return opus_encoder_ctl(enc->opus_enc, OPUS_SET_COMPLEXITY(complexity));
|
||||
}
|
||||
|
||||
int opus_codec_encoder_signal_set(opus_codec_encoder_t* enc, int signal_type) {
|
||||
if (!enc || !enc->opus_enc) return -1;
|
||||
return opus_encoder_ctl(enc->opus_enc, OPUS_SET_SIGNAL(signal_type));
|
||||
}
|
||||
|
||||
const char* opus_codec_strerror(int err) {
|
||||
switch (err) {
|
||||
case OPUS_OK: return "OK";
|
||||
case OPUS_BAD_ARG: return "BAD_ARG";
|
||||
case OPUS_BUFFER_TOO_SMALL: return "BUFFER_TOO_SMALL";
|
||||
case OPUS_INTERNAL_ERROR: return "INTERNAL_ERROR";
|
||||
case OPUS_INVALID_PACKET: return "INVALID_PACKET";
|
||||
case OPUS_UNIMPLEMENTED: return "UNIMPLEMENTED";
|
||||
case OPUS_INVALID_STATE: return "INVALID_STATE";
|
||||
case OPUS_ALLOC_FAIL: return "ALLOC_FAIL";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user