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,78 @@
|
||||
#include "voiceplayback.h"
|
||||
#include "sound_manager.h"
|
||||
|
||||
extern "C" {
|
||||
#include "../../../lib/opus_codec.h"
|
||||
}
|
||||
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
|
||||
static const uint32_t OPUS_MAGIC = 0x5355504F;
|
||||
|
||||
int VoicePlayer::decodeOpusFile(const QString& filePath, std::vector<int16_t>& outPcm,
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec) {
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qWarning("VoicePlayer: cannot open %s", qPrintable(filePath));
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t magic, sr32;
|
||||
uint16_t ch16, frameSamples;
|
||||
if (file.read((char*)&magic, 4) != 4 || magic != OPUS_MAGIC ||
|
||||
file.read((char*)&sr32, 4) != 4 ||
|
||||
file.read((char*)&ch16, 2) != 2 ||
|
||||
file.read((char*)&frameSamples, 2) != 2) {
|
||||
qWarning("VoicePlayer: invalid header in %s", qPrintable(filePath));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sampleRate = (int)sr32;
|
||||
int channels = (int)ch16;
|
||||
outSampleRate = sampleRate;
|
||||
outChannels = channels;
|
||||
|
||||
opus_codec_decoder_t* dec = opus_codec_decoder_create(sampleRate, channels);
|
||||
if (!dec) {
|
||||
qWarning("VoicePlayer: decoder create failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
outPcm.clear();
|
||||
int frameCount = 0;
|
||||
|
||||
while (!file.atEnd()) {
|
||||
uint16_t plen;
|
||||
if (file.read((char*)&plen, 2) != 2) break;
|
||||
if (plen == 0) break; /* end marker */
|
||||
|
||||
QByteArray pktData = file.read(plen);
|
||||
if (pktData.size() != (int)plen) break;
|
||||
|
||||
std::vector<int16_t> frameBuf(frameSamples * channels);
|
||||
int decoded = opus_codec_decode(dec, (const uint8_t*)pktData.constData(),
|
||||
plen, frameBuf.data(), frameSamples);
|
||||
if (decoded > 0) {
|
||||
size_t cur = outPcm.size();
|
||||
outPcm.resize(cur + decoded * channels);
|
||||
memcpy(outPcm.data() + cur, frameBuf.data(), decoded * channels * sizeof(int16_t));
|
||||
frameCount++;
|
||||
}
|
||||
}
|
||||
|
||||
outDurationSec = (float)frameCount * 20.0f / 1000.0f;
|
||||
qDebug("VoicePlayer: decoded %d frames (%.1fs) from %s", frameCount, outDurationSec, qPrintable(filePath));
|
||||
opus_codec_decoder_destroy(dec);
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
void VoicePlayer::playOpusFile(const QString& filePath) {
|
||||
std::vector<int16_t> pcm;
|
||||
int sampleRate, channels;
|
||||
float duration;
|
||||
|
||||
if (decodeOpusFile(filePath, pcm, sampleRate, channels, duration) <= 0) return;
|
||||
|
||||
SoundManager::instance()->playRawPcm(pcm.data(), pcm.size() / channels, sampleRate, channels);
|
||||
}
|
||||
Reference in new issue
Block a user