diff --git a/tools/chatgui/src/voiceplayback.cpp b/tools/chatgui/src/voiceplayback.cpp index 5ca7490f..6c337ab2 100644 --- a/tools/chatgui/src/voiceplayback.cpp +++ b/tools/chatgui/src/voiceplayback.cpp @@ -10,6 +10,10 @@ extern "C" { #include #include +#include +#include +#include +#include static const uint32_t OPUS_MAGIC = 0x5355504F; @@ -78,21 +82,41 @@ int VoicePlayer::decodeOpusFile(const QString& filePath, std::vector& o return frameCount; } -float VoicePlayer::playOpusFile(const QString& filePath) { - DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: %s", qPrintable(filePath)); - std::vector pcm; - int sampleRate, channels; - float duration; - - if (decodeOpusFile(filePath, pcm, sampleRate, channels, duration) <= 0) { - DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: decode failed for %s", qPrintable(filePath)); - return 0.0f; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: decoded %zu samples %dHz %dch %.1fs", - pcm.size(), sampleRate, channels, duration); - SoundManager::instance()->playRawPcm(pcm.data(), pcm.size() / channels, sampleRate, channels); - return duration; +static unsigned s_decodeGeneration = 0; /* Только GUI-поток. */ +static bool s_decoding = false; + +void VoicePlayer::cancelPending() { s_decodeGeneration++; } + +/* Декодирование и выделение PCM выполняются в worker; GUI принимает готовый буфер. */ +void VoicePlayer::playOpusFile(const QString& filePath, QObject* context, std::function ready) { + if (s_decoding) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "voice: decoder busy"); ready(0); return; } + struct Result { + std::shared_ptr> pcm = std::make_shared>(); + int rate = 0, channels = 0; + float duration = 0; + }; + auto result = std::make_shared(); + unsigned generation = ++s_decodeGeneration; + s_decoding = true; + QPointer receiver(context); + auto* thread = QThread::create([filePath, result]() { + try { + if (decodeOpusFile(filePath, *result->pcm, result->rate, result->channels, result->duration) <= 0) + result->duration = 0; + } catch (const std::exception& error) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "voice: decode failed path=%s reason=%s", qPrintable(filePath), error.what()); + result->duration = 0; + } + }); + QObject::connect(thread, &QThread::finished, QCoreApplication::instance(), [result, receiver, ready, generation]() { + s_decoding = false; + if (!receiver || generation != s_decodeGeneration) return; + if (result->duration > 0) SoundManager::instance()->playRawPcm(result->pcm->data(), + (int)(result->pcm->size() / result->channels), result->rate, result->channels, result->pcm); + ready(result->duration); + }); + QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater); + thread->start(); } float VoicePlayer::playOpusFromReader(MediaBlockReader& reader) { diff --git a/tools/chatgui/src/voiceplayback.h b/tools/chatgui/src/voiceplayback.h index 34aa41f2..58129a14 100644 --- a/tools/chatgui/src/voiceplayback.h +++ b/tools/chatgui/src/voiceplayback.h @@ -3,12 +3,15 @@ #include #include #include +#include +class QObject; class MediaBlockReader; class VoicePlayer { public: - static float playOpusFile(const QString& filePath); + static void playOpusFile(const QString& filePath, QObject* context, std::function ready); + static void cancelPending(); static int decodeOpusFile(const QString& filePath, std::vector& outPcm, int& outSampleRate, int& outChannels, float& outDurationSec); static float playOpusFromReader(MediaBlockReader& reader);