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.
117 lines
3.9 KiB
117 lines
3.9 KiB
#include "voicemessageencoder.h" |
|
|
|
extern "C" { |
|
#include "../../../lib/opus_codec.h" |
|
} |
|
|
|
#include "../../lib/debug_config.h" |
|
|
|
#include <QDebug> |
|
#include <QDir> |
|
#include <QDateTime> |
|
#include <QFile> |
|
#include <cstring> |
|
|
|
static const int FRAME_MS = 20; |
|
static const uint32_t OPUS_MAGIC = 0x5355504F; |
|
static const int MAX_PACKET = 4000; |
|
|
|
static int s_preset = 1; /* 0=Low, 1=Standard, 2=High */ |
|
static const int s_bitrates[] = {16000, 32000, 64000}; |
|
static const int s_complexities[] = {2, 5, 10}; |
|
|
|
void VoiceEncoder::setPreset(int preset) { |
|
if (preset < 0) preset = 0; |
|
if (preset > 2) preset = 2; |
|
s_preset = preset; |
|
} |
|
|
|
int VoiceEncoder::preset() { return s_preset; } |
|
|
|
int VoiceEncoder::encodeToFile(const int16_t* pcm, size_t pcmCount, int sampleRate, int channels, |
|
const QString& filePath, float& outDurationSec) { |
|
if (!pcm || pcmCount == 0 || sampleRate <= 0 || channels <= 0) { |
|
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoiceEncoder: invalid input (pcm=%zu rate=%d ch=%d)", pcmCount, sampleRate, channels); |
|
return -1; |
|
} |
|
|
|
int frameSamples = sampleRate * FRAME_MS / 1000; |
|
size_t totalSamples = pcmCount / (size_t)channels; |
|
if (totalSamples < (size_t)frameSamples) return -1; |
|
|
|
opus_codec_encoder_t* enc = opus_codec_encoder_create(sampleRate, channels); |
|
if (!enc) { |
|
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoiceEncoder: encoder create failed"); |
|
return -1; |
|
} |
|
opus_codec_encoder_bitrate_set(enc, s_bitrates[s_preset]); |
|
opus_codec_encoder_complexity_set(enc, s_complexities[s_preset]); |
|
|
|
QFile file(filePath); |
|
if (!file.open(QIODevice::WriteOnly)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoiceEncoder: cannot open file %s (%s)", qPrintable(filePath), qPrintable(file.errorString())); |
|
opus_codec_encoder_destroy(enc); |
|
return -1; |
|
} |
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoiceEncoder::encodeToFile: writing to %s pcm=%zu samples preset=%d", |
|
qPrintable(filePath), pcmCount, s_preset); |
|
|
|
/* write header */ |
|
uint32_t magic = OPUS_MAGIC; |
|
uint32_t sr = (uint32_t)sampleRate; |
|
uint16_t ch = (uint16_t)channels; |
|
uint16_t fs = (uint16_t)frameSamples; |
|
file.write((const char*)&magic, 4); |
|
file.write((const char*)&sr, 4); |
|
file.write((const char*)&ch, 2); |
|
file.write((const char*)&fs, 2); |
|
|
|
int frameCount = 0; |
|
uint8_t packet[MAX_PACKET]; |
|
size_t pcmOff = 0; |
|
|
|
while (pcmOff + (size_t)frameSamples * channels <= totalSamples) { |
|
int len = opus_codec_encode(enc, pcm + pcmOff, frameSamples, packet, MAX_PACKET); |
|
pcmOff += (size_t)frameSamples * channels; |
|
|
|
if (len > 0) { |
|
uint16_t plen = (uint16_t)len; |
|
file.write((const char*)&plen, 2); |
|
file.write((const char*)packet, len); |
|
frameCount++; |
|
} else if (len < 0) { |
|
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoiceEncoder: encode error at frame %d: %d", frameCount, len); |
|
} |
|
} |
|
|
|
/* end marker */ |
|
uint16_t zero = 0; |
|
file.write((const char*)&zero, 2); |
|
file.close(); |
|
|
|
outDurationSec = (float)frameCount * FRAME_MS / 1000.0f; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoiceEncoder: wrote %d frames (%.1fs) to %s", frameCount, outDurationSec, qPrintable(filePath)); |
|
opus_codec_encoder_destroy(enc); |
|
return frameCount; |
|
} |
|
|
|
QString VoiceEncoder::generateFileName() { |
|
QDateTime now = QDateTime::currentDateTime(); |
|
QString ts = now.toString("yyyyMMdd-HHmmss"); |
|
int rnd = rand() & 0xFFFF; |
|
return QString("voice_%1_%2.opus").arg(ts).arg(rnd, 4, 16, QChar('0')); |
|
} |
|
|
|
QString VoiceEncoder::mediaDir(const QString& dbPath, const QString& channelId) { |
|
QDir d(dbPath); |
|
QString mediaPath = d.absoluteFilePath("media/" + channelId); |
|
return mediaPath; |
|
} |
|
|
|
void VoiceEncoder::ensureMediaDir(const QString& dir) { |
|
QDir d; |
|
if (!d.mkpath(dir)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoiceEncoder: cannot create media dir %s", qPrintable(dir)); |
|
} |
|
}
|
|
|