#include "streamplayer_window.h" #include "videoplayer_window.h" #include "video_frame_image.h" #include "sound_manager.h" #include "../transport/stream_bridge.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include "../../../lib/debug_config.h" #include "../../../src/stream/stream.h" #include "../../../src/stream/stream_playback_clock.h" #include "../../../src/stream/stream_playback_audio.h" #ifdef HAVE_FFMPEG #include #include #endif } namespace { int64_t nowUs() { return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); } std::atomic nextId{0}; QString text(const char* value) { return QCoreApplication::translate("ChatGui", value); } /* Ровно восемь Unicode code points и один компактный знак многоточия; HTML имён экранируется. */ QString shortNodeName(const char* name) { QString value = QString::fromUtf8(name); int end = 0, count = 0; while (end < value.size() && count < 8) { if (value.at(end).isHighSurrogate() && end + 1 < value.size() && value.at(end + 1).isLowSurrogate()) end++; end++; count++; } return (value.left(end) + (end < value.size() ? QStringLiteral("…") : QString())).toHtmlEscaped(); } QString bufferTime(int64_t us) { if (us < 0) return QStringLiteral("?"); QString value = QString::number(us / 1000000.0, 'f', 1); if (value.endsWith(QStringLiteral(".0"))) value.chop(2); return value + QStringLiteral("S"); } } struct StreamPlayerWindow::Playback { struct Video { QImage image; int64_t pts; }; struct AudioGap { uint64_t count = 0, silentFrames = 0, episodes = 0; int64_t at = 0; unsigned requested = 0, received = 0; unsigned pcmBefore = 0, pcmAfter = 0; int tempo = 1000; int64_t reserve = 0, feedAge = -1, workerAge = -1, audioPts = -1; int64_t deviceLead = 0, guard = 0, minimum = 0, callbackGap = 0, pendingGain = 0; size_t videoQueue = 0; } audioGap; uint64_t id = ++nextId, group = 0, source = 0, epoch = 0; int64_t createdAt = nowUs(), firstInputAt = 0; std::atomic running{true}, audioAvailable{false}; std::atomic complete{false}, failed{false}; std::atomic volume{1.0f}; float outputVolume = 0.0f; uint64_t audioGeneration = 0; std::thread worker, videoWorker; std::deque videoInput; // Audio worker -> video worker, под lock; максимум два входа. uint64_t videoGeneration = 1; bool videoBusy = false, videoInputEnded = false; std::mutex lock; std::mutex wakeLock; std::condition_variable wake; uint64_t wakeVersion = 0, dataSignals = 0; std::deque