Reject corrupt voice containers and remove unused synchronous playback
This commit is contained in:
1 parent
8e108425ae
commit
3b0609fe76
2 files changed
+44
-120
No files matched your search
@@ -1,6 +1,5 @@
|
||||
#include "voiceplayback.h"
|
||||
#include "sound_manager.h"
|
||||
#include "media_blocks.h"
|
||||
|
||||
extern "C" {
|
||||
#include "../../../lib/opus_codec.h"
|
||||
@@ -18,68 +17,58 @@ extern "C" {
|
||||
static const uint32_t OPUS_MAGIC = 0x5355504F;
|
||||
|
||||
int VoicePlayer::decodeOpusFile(const QString& filePath, std::vector<int16_t>& outPcm,
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec) {
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec) {
|
||||
outPcm.clear(); outDurationSec = 0; outSampleRate = 0; outChannels = 0;
|
||||
QFile file(filePath);
|
||||
if (!file.exists()) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer: file not found: %s", qPrintable(filePath));
|
||||
return -1;
|
||||
}
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer: cannot open %s (%s)", qPrintable(filePath), qPrintable(file.errorString()));
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: open failed path=%s reason=%s", qPrintable(filePath), qPrintable(file.errorString()));
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer: opened file %s size=%lld", qPrintable(filePath), (long long)file.size());
|
||||
|
||||
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) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer: invalid header in %s", qPrintable(filePath));
|
||||
uint32_t magic = 0, rate = 0;
|
||||
uint16_t channels = 0, frameSamples = 0;
|
||||
if (file.read((char*)&magic, 4) != 4 || file.read((char*)&rate, 4) != 4 ||
|
||||
file.read((char*)&channels, 2) != 2 || file.read((char*)&frameSamples, 2) != 2 || magic != OPUS_MAGIC ||
|
||||
(rate != 8000 && rate != 12000 && rate != 16000 && rate != 24000 && rate != 48000) ||
|
||||
(channels != 1 && channels != 2) || frameSamples != rate / 50) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: invalid header path=%s rate=%u channels=%u frame=%u",
|
||||
qPrintable(filePath), rate, channels, frameSamples);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sampleRate = (int)sr32;
|
||||
int channels = (int)ch16;
|
||||
outSampleRate = sampleRate;
|
||||
outChannels = channels;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer: header sr=%d ch=%d frameSamples=%u file=%s",
|
||||
sampleRate, channels, (unsigned)frameSamples, qPrintable(filePath));
|
||||
|
||||
opus_codec_decoder_t* dec = opus_codec_decoder_create(sampleRate, channels);
|
||||
if (!dec) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "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++;
|
||||
std::unique_ptr<opus_codec_decoder_t, decltype(&opus_codec_decoder_destroy)>
|
||||
decoder(opus_codec_decoder_create((int)rate, channels), opus_codec_decoder_destroy);
|
||||
if (!decoder) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: decoder allocation failed"); return -1; }
|
||||
std::vector<int16_t> frame((size_t)frameSamples * channels);
|
||||
constexpr size_t maxPcmSamples = 256 * 1024 * 1024 / sizeof(int16_t);
|
||||
int frames = 0;
|
||||
for (;;) {
|
||||
uint16_t length = 0;
|
||||
if (file.read((char*)&length, 2) != 2) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: missing end marker path=%s frame=%d", qPrintable(filePath), frames);
|
||||
return -1;
|
||||
}
|
||||
if (!length) {
|
||||
if (!frames || !file.atEnd()) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: empty stream or trailing bytes path=%s", qPrintable(filePath));
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (length > 4000 || outPcm.size() > maxPcmSamples - frame.size()) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: packet/PCM limit exceeded path=%s frame=%d size=%u",
|
||||
qPrintable(filePath), frames, length);
|
||||
return -1;
|
||||
}
|
||||
QByteArray packet = file.read(length);
|
||||
if (packet.size() != length || opus_codec_decode(decoder.get(), (const uint8_t*)packet.constData(),
|
||||
length, frame.data(), frameSamples) != frameSamples) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: truncated/invalid packet path=%s frame=%d", qPrintable(filePath), frames);
|
||||
return -1;
|
||||
}
|
||||
outPcm.insert(outPcm.end(), frame.begin(), frame.end()); frames++;
|
||||
}
|
||||
|
||||
outDurationSec = (float)frameCount * 20.0f / 1000.0f;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer: decoded %d frames (%.1fs) from %s", frameCount, outDurationSec, qPrintable(filePath));
|
||||
opus_codec_decoder_destroy(dec);
|
||||
return frameCount;
|
||||
outSampleRate = (int)rate; outChannels = channels; outDurationSec = frames / 50.0f;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "voice: decoded path=%s frames=%d duration=%.2f", qPrintable(filePath), frames, outDurationSec);
|
||||
return frames;
|
||||
}
|
||||
|
||||
static unsigned s_decodeGeneration = 0; /* Только GUI-поток. */
|
||||
@@ -119,64 +108,3 @@ void VoicePlayer::playOpusFile(const QString& filePath, QObject* context, std::f
|
||||
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
float VoicePlayer::playOpusFromReader(MediaBlockReader& reader) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: %s", qPrintable(reader.debugPath()));
|
||||
std::vector<int16_t> pcm;
|
||||
int sampleRate, channels;
|
||||
float duration;
|
||||
if (decodeOpusFromReader(reader, pcm, sampleRate, channels, duration) <= 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: decode failed");
|
||||
return 0.0f;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: decoded %zu samples %dHz %dch %.1fs",
|
||||
pcm.size(), sampleRate, channels, duration);
|
||||
SoundManager::instance()->playRawPcm(pcm.data(), pcm.size() / channels, sampleRate, channels);
|
||||
return duration;
|
||||
}
|
||||
|
||||
int VoicePlayer::decodeOpusFromReader(MediaBlockReader& reader, std::vector<int16_t>& outPcm,
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec) {
|
||||
QByteArray data = reader.readAll();
|
||||
if (data.isEmpty()) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer::decodeOpusFromReader: empty data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const uint8_t* ptr = (const uint8_t*)data.constData();
|
||||
size_t len = (size_t)data.size();
|
||||
size_t off = 0;
|
||||
|
||||
if (off + 12 > len) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer: header truncated"); return -1; }
|
||||
uint32_t magic; memcpy(&magic, ptr + off, 4); off += 4;
|
||||
if (magic != OPUS_MAGIC) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer: bad magic %08x", magic); return -1; }
|
||||
uint32_t sr32; memcpy(&sr32, ptr + off, 4); off += 4;
|
||||
uint16_t ch16; memcpy(&ch16, ptr + off, 2); off += 2;
|
||||
uint16_t frameSamples; memcpy(&frameSamples, ptr + off, 2); off += 2;
|
||||
int sampleRate = (int)sr32, channels = (int)ch16;
|
||||
outSampleRate = sampleRate; outChannels = channels;
|
||||
|
||||
opus_codec_decoder_t* dec = opus_codec_decoder_create(sampleRate, channels);
|
||||
if (!dec) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer: decoder create failed"); return -1; }
|
||||
|
||||
outPcm.clear();
|
||||
int frameCount = 0;
|
||||
while (off + 2 <= len) {
|
||||
uint16_t plen; memcpy(&plen, ptr + off, 2); off += 2;
|
||||
if (plen == 0) break;
|
||||
if (off + plen > len) break;
|
||||
std::vector<int16_t> frameBuf(frameSamples * channels);
|
||||
int decoded = opus_codec_decode(dec, ptr + off, plen, frameBuf.data(), frameSamples);
|
||||
off += plen;
|
||||
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;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer: decoded %d frames (%.1fs)", frameCount, outDurationSec);
|
||||
opus_codec_decoder_destroy(dec);
|
||||
return frameCount;
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <functional>
|
||||
class QObject;
|
||||
|
||||
class MediaBlockReader;
|
||||
|
||||
class VoicePlayer {
|
||||
public:
|
||||
@@ -15,7 +14,4 @@ public:
|
||||
static bool isPreparing();
|
||||
static int decodeOpusFile(const QString& filePath, std::vector<int16_t>& outPcm,
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec);
|
||||
static float playOpusFromReader(MediaBlockReader& reader);
|
||||
static int decodeOpusFromReader(MediaBlockReader& reader, std::vector<int16_t>& outPcm,
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec);
|
||||
};
|
||||
Reference in new issue
Block a user