From 3b0609fe76c3af7d5f8e682f21574009ce39a786 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 1 Oct 2026 18:51:44 +0300 Subject: [PATCH] Reject corrupt voice containers and remove unused synchronous playback --- tools/chatgui/src/voiceplayback.cpp | 160 ++++++++-------------------- tools/chatgui/src/voiceplayback.h | 4 - 2 files changed, 44 insertions(+), 120 deletions(-) diff --git a/tools/chatgui/src/voiceplayback.cpp b/tools/chatgui/src/voiceplayback.cpp index 4306d138..b699cdf7 100644 --- a/tools/chatgui/src/voiceplayback.cpp +++ b/tools/chatgui/src/voiceplayback.cpp @@ -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& 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())); - 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)); + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: open failed path=%s reason=%s", qPrintable(filePath), qPrintable(file.errorString())); 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"); + 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; } - - 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 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 + 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 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 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& 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 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; -} diff --git a/tools/chatgui/src/voiceplayback.h b/tools/chatgui/src/voiceplayback.h index 08820b1d..fb84132a 100644 --- a/tools/chatgui/src/voiceplayback.h +++ b/tools/chatgui/src/voiceplayback.h @@ -6,7 +6,6 @@ #include class QObject; -class MediaBlockReader; class VoicePlayer { public: @@ -15,7 +14,4 @@ public: static bool isPreparing(); static int decodeOpusFile(const QString& filePath, std::vector& outPcm, int& outSampleRate, int& outChannels, float& outDurationSec); - static float playOpusFromReader(MediaBlockReader& reader); - static int decodeOpusFromReader(MediaBlockReader& reader, std::vector& outPcm, - int& outSampleRate, int& outChannels, float& outDurationSec); };