Preserve stream capture settings and refine audio playback

This commit is contained in:
evgeny committed 2026-10-16 14:05:54 +02:00
1 parent 198ebc0acb
commit d60f8e4b1b
16 files changed
+244 -43

No files matched your search

+38 -5
View File
@@ -40,6 +40,13 @@ QString text(const char* value) { return QCoreApplication::translate("ChatGui",
struct StreamPlayerWindow::Playback {
struct Video { QImage image; int64_t pts; };
struct AudioGap {
uint64_t count = 0, silentFrames = 0;
unsigned requested = 0, received = 0;
int64_t reserve = 0, feedAge = -1, workerAge = -1, audioPts = -1;
size_t videoQueue = 0;
bool held = false;
} audioGap;
uint64_t id = ++nextId, group = 0, source = 0, epoch = 0;
std::atomic<bool> running{true}, audioAvailable{false};
std::atomic<float> volume{1.0f};
@@ -56,6 +63,7 @@ struct StreamPlayerWindow::Playback {
uint64_t audioFrameStart = 0, shown = 0, underruns = 0, endFrames = UINT64_MAX;
bool started = false, presenting = false, videoReady = false, ended = false, videoEnded = false;
bool activeAudio = false, holdAudio = false, resync = false;
int64_t audioFedAt = 0, audioFedPts = -1, workerAt = 0;
~Playback() { stream_playback_audio_destroy(sound); }
uint64_t played(int64_t now) const {
@@ -128,7 +136,13 @@ void StreamPlayerWindow::openAudio() {
if (count < 0) { p.resync = true; count = 0; }
if ((unsigned)count < frames) {
stream_playback_audio_silence(p.sound, frames - (unsigned)count);
if (p.presenting && !p.ended) p.underruns++;
if (p.presenting && !p.ended) {
p.underruns++; p.audioGap.count++; p.audioGap.silentFrames += frames - (unsigned)count;
p.audioGap.requested = frames; p.audioGap.received = (unsigned)count; p.audioGap.held = p.holdAudio;
p.audioGap.reserve = p.ready - p.position; p.audioGap.videoQueue = p.videos.size();
p.audioGap.feedAge = p.audioFedAt ? now - p.audioFedAt : -1; p.audioGap.audioPts = p.audioFedPts;
p.audioGap.workerAge = p.workerAt ? now - p.workerAt : -1;
}
}
float volume = p.volume.load();
for (int i = 0; i < count * 2; i++) pcm[i] = static_cast<int16_t>(pcm[i] * volume);
@@ -200,6 +214,8 @@ void StreamPlayerWindow::decode() {
uint32_t revision = 0;
uint64_t decoded = 0, discarded = 0, reportedShown = 0;
int64_t deadline = nowUs() + 10000000, report = nowUs(), decoderProgress = -1, lastInputAt = -1;
uint64_t reportedGaps = 0;
int64_t gapReportAt = 0;
auto reset = [&](bool request) {
avcodec_free_context(&decoder); av_packet_unref(packet); waitKey = true; finishing = eosSent = false;
decoderProgress = lastInputAt = -1;
@@ -210,6 +226,7 @@ void StreamPlayerWindow::decode() {
p.decoderLead = 0; p.framePeriod = 33333; p.lastInputPts = p.lastDecodedPts = -1;
p.ended = p.videoEnded = false;
p.audioDacAt = 0; p.audioFrameStart = 0; p.holdAudio = p.resync = false; p.endFrames = UINT64_MAX;
p.audioFedAt = 0; p.audioFedPts = -1;
}
if (request && !gui_stream_resync(p.id)) DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop playback resync refused");
deadline = nowUs() + 10000000; emit statusChanged(text("Waiting for a key frame…"));
@@ -226,7 +243,7 @@ void StreamPlayerWindow::decode() {
if (!status.initialized) return;
if (!p.started) {
p.activeAudio = status.audio_enabled && p.audioAvailable;
if (p.activeAudio) p.sound = stream_playback_audio_create(status.start_us);
if (p.activeAudio) p.sound = stream_playback_audio_create(status.start_us, 40);
if (p.activeAudio && !p.sound) { p.running = false; return; }
p.position = status.start_us; p.clockAt = nowUs(); p.started = true;
stream_playback_clock_reset(&p.adaptation, p.id);
@@ -283,7 +300,18 @@ void StreamPlayerWindow::decode() {
else if (!gui_stream_open(p.id, p.group, p.source, p.epoch)) fail(text("Could not connect to the stream"));
else while (p.running) {
bool resync;
{ std::lock_guard<std::mutex> guard(p.lock); resync = p.resync; }
Playback::AudioGap gap;
{ std::lock_guard<std::mutex> guard(p.lock); resync = p.resync; p.workerAt = nowUs(); gap = p.audioGap; }
/* Callback сохраняет снимок; печать выполняется worker без блокировки аудиоустройства. */
if (gap.count != reportedGaps && nowUs() - gapReportAt >= 1000000) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG,
"desktop audio gap id=%llu count=%llu delta=%llu silence_frames=%llu cause=%s got=%u/%u "
"reserve_us=%lld feed_age_us=%lld worker_age_us=%lld audio_pts=%lld videoQ=%zu",
(unsigned long long)p.id, (unsigned long long)gap.count, (unsigned long long)(gap.count - reportedGaps),
(unsigned long long)gap.silentFrames, gap.held ? "video_hold" : "pcm_short", gap.received, gap.requested,
(long long)gap.reserve, (long long)gap.feedAge, (long long)gap.workerAge, (long long)gap.audioPts, gap.videoQueue);
reportedGaps = gap.count; gapReportAt = nowUs();
}
if (resync) reset(true);
observe();
drainVideo();
@@ -359,10 +387,15 @@ void StreamPlayerWindow::decode() {
observe();
if (record.pts > INT64_MAX) { fail(text("Invalid stream timestamp")); break; }
if (record.kind == StreamRecordKind::Audio) {
int result = 0;
int result = 0, wanted = 0, stillWanted = 0;
{ std::lock_guard<std::mutex> guard(p.lock);
if (p.sound) wanted = stream_playback_audio_needs_input(p.sound);
if (p.sound) result = stream_playback_audio_feed(p.sound, (int64_t)record.pts,
reinterpret_cast<const uint8_t*>(record.data.constData()), (size_t)record.data.size()); }
reinterpret_cast<const uint8_t*>(record.data.constData()), (size_t)record.data.size());
if (p.sound && result == 0) { p.audioFedAt = nowUs(); p.audioFedPts = (int64_t)record.pts; }
if (p.sound) stillWanted = stream_playback_audio_needs_input(p.sound); }
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "desktop audio feed id=%llu pts=%llu result=%d needs_input=%d->%d",
(unsigned long long)p.id, (unsigned long long)record.pts, result, wanted, stillWanted);
if (result == -2) reset(true);
else if (result < 0) { fail(text("Stream audio decoder failed")); break; }
continue;