|
|
|
|
@ -10,6 +10,10 @@ extern "C" {
|
|
|
|
|
|
|
|
|
|
#include <QFile> |
|
|
|
|
#include <QDebug> |
|
|
|
|
#include <QThread> |
|
|
|
|
#include <QPointer> |
|
|
|
|
#include <QCoreApplication> |
|
|
|
|
#include <memory> |
|
|
|
|
|
|
|
|
|
static const uint32_t OPUS_MAGIC = 0x5355504F; |
|
|
|
|
|
|
|
|
|
@ -78,21 +82,41 @@ int VoicePlayer::decodeOpusFile(const QString& filePath, std::vector<int16_t>& o
|
|
|
|
|
return frameCount; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
float VoicePlayer::playOpusFile(const QString& filePath) { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: %s", qPrintable(filePath)); |
|
|
|
|
std::vector<int16_t> 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<void(float)> ready) { |
|
|
|
|
if (s_decoding) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "voice: decoder busy"); ready(0); return; } |
|
|
|
|
struct Result { |
|
|
|
|
std::shared_ptr<std::vector<int16_t>> pcm = std::make_shared<std::vector<int16_t>>(); |
|
|
|
|
int rate = 0, channels = 0; |
|
|
|
|
float duration = 0; |
|
|
|
|
}; |
|
|
|
|
auto result = std::make_shared<Result>(); |
|
|
|
|
unsigned generation = ++s_decodeGeneration; |
|
|
|
|
s_decoding = true; |
|
|
|
|
QPointer<QObject> 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) { |
|
|
|
|
|