Document independent video scheduling and verified recovery behavior
This commit is contained in:
1 parent
55e87d20bf
commit
c3c88457a8
3 files changed
+88
-37
No files matched your search
@@ -1144,3 +1144,52 @@ decode_deadline_stage=input/output, work_us и margin_us описывают им
|
||||
тем самым влияют на расписание. Для устранения зависимости нужны независимое исполнение
|
||||
decoder/планировщика и синхронизация presentation с обновлением дисплея; обычный таймер
|
||||
и ранний ACK из QPainter не дают такой гарантии.
|
||||
|
||||
Независимый desktop-планировщик, 19.10.2026 — реализовано
|
||||
TX по-прежнему владеет encoder. RX worker теперь единолично владеет decoder, всеми
|
||||
submit/receive/EOS/open/close, GPU transfer, RGB и rotation. Отдельный display worker
|
||||
владеет call_video_playout и не вызывает decoder, port_status/pull, Qt post/invokeMethod,
|
||||
не ждёт GUI и не форматирует/записывает логи.
|
||||
|
||||
Передача RX -> display: SPSC кольцо из трёх неизменяемых Picture. Producer заполняет слот
|
||||
до release-публикации; consumer освобождает его только после ACK выбранного кадра.
|
||||
Голова во время GUI ACK остаётся в кольце и входит в его ёмкость. При полном кольце
|
||||
останавливается подача decoder; до восьми входов разрешают проверенный hardware pipeline.
|
||||
Старый QImage в слоте заменяет decoder, освобождение его буфера не попадает в display.
|
||||
Смена поколения сначала выводит принятые decoder-входы; старые готовые изображения
|
||||
сохраняют собственные PTS/формат и показываются до новых.
|
||||
|
||||
GUI читает один опубликованный слот по QWinEventNotifier (Windows) или QSocketNotifier
|
||||
(POSIX pipe). Таймер записывает metadata и serial, затем сигналит событие; аллокации
|
||||
queued Qt lambda в нём больше нет. Только GUI копирует shared QImage и вызывает imageChanged.
|
||||
ACK имеет глобально возрастающий serial; дубликаты и ACK предыдущего start не освобождают
|
||||
слот новой сессии. stop запрещает публикации, будит и присоединяет display/RX/TX независимо
|
||||
от GUI ACK, затем закрывает порт. Notifier удаляется до закрытия native handles.
|
||||
|
||||
Общий запас: cumulative port_received - completed - decoder_discarded; переносы между
|
||||
encoded/decoder/ready/GUI не меняют счётчик. port_received читается атомарно, без mutex
|
||||
порта, который может быть занят копированием нового encoded кадра. Decoder-discard
|
||||
меняется только при настоящем отказе цепочки/codec; короткая пауза ничего не отбрасывает.
|
||||
call_audio_try_clock использует try_lock и на call_audio, и на jitter mutex. BUSY вызывает
|
||||
повтор через 500мкс без сброса назначенной головы; экстраполяции устаревшего audio PTS нет.
|
||||
|
||||
У display/RX отдельные wake: Windows event + high-resolution timer, POSIX nonblocking pipe
|
||||
и pselect. notify не берёт пользовательский mutex. Runtime ошибки wake передаются RX
|
||||
для вывода и остановки workers. Логи playout копируются в bounded SPSC очередь; форматирует
|
||||
их RX. Переполнение диагностической очереди считается и явно логируется, кадры не вытесняются.
|
||||
|
||||
Фаза опирается на запланированную презентацию и корректируется по ACK на error/8,
|
||||
не более 1мс за кадр. Настоящая пауза более половины периода привязывает фазу к новому ACK;
|
||||
минимальный плановый интервал ограничивает мгновенный burst темпом 1.6x. Большие разрывы
|
||||
capture PTS не проигрываются повторно. Это не VSync: ACK текущего окна всё ещё paintEvent,
|
||||
а фактическая презентация Windows-композитором требует отдельного render backend.
|
||||
|
||||
Проверки: C playout — 480/480 при сетевых/CPU паузах 50/100/200/500мс, возврат темпа,
|
||||
смена render latency 3->9мс без накопленного сдвига (phase=11625мкс).
|
||||
Qt clock — три сценария 90/90, смена поколения на 45-м кадре, RX80/GUI60мс, audio BUSY,
|
||||
decoder output+12мс, stop без ACK, запоздалый ACK после start, 100000 конкурентных публикаций.
|
||||
В последнем Linux-прогоне p95 показа: обычный 34.7мс, с медленным decoder 35.2мс;
|
||||
максимумы работы display в секундных окнах 26-143мкс. Это локальный замер, не Windows scanout.
|
||||
Qt encoder/GPU/clock/jitter: 4/4 PASS, core и vibechat собраны.
|
||||
Полный check.sh вне sandbox: 163/164; известный test_encrypted_attachment снова phase=5,
|
||||
heartbeats=12001 (описан в doc/tasks.md). Лог: /tmp/video-independent-check-full.log.
|
||||
@@ -9,6 +9,9 @@
|
||||
heartbeats=12001. Отдельный повтор также остановился в phase=5: после старта автора не получены два pending incoming-запроса.
|
||||
Причина и связь с изменениями не установлены. Логи: /tmp/phone-busy-full-check.log,
|
||||
/tmp/phone-busy-encrypted-recheck.log; артефакты: /tmp/utun_encrypted_attachment_RVU2yS.
|
||||
Повторилось при проверке независимого видеопланировщика: check.sh вне sandbox 163/164,
|
||||
тот же phase=5/heartbeats=12001; /tmp/video-independent-check-full.log,
|
||||
артефакты /tmp/utun_encrypted_attachment_IW0W28. Код вложений в этой работе не менялся.
|
||||
|
||||
[ ] **Разобрать нестабильную загрузку storage-узлов в test_media_delivery_chat** — 19.10.2026:
|
||||
В том же общем прогоне storage st1/st2 не собрали файл, n2 исчерпал попытки блока 0 (15/19 внутренних проверок).
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <chrono>
|
||||
#ifdef _WIN32
|
||||
#include <QWinEventNotifier>
|
||||
@@ -151,43 +150,43 @@ void CallVideoEngine::publishImage(const QImage& image, int64_t pts) {
|
||||
m->pendingSerial.store(++m->publicationSerial, std::memory_order_release); m->guiWake.notify();
|
||||
}
|
||||
void CallVideoEngine::deliverImage() {
|
||||
const uint64_t serial = m->pendingSerial.load(std::memory_order_acquire);
|
||||
if (!m->running || !serial || serial == m->deliveredSerial) return;
|
||||
m->deliveredSerial = serial;
|
||||
const int64_t queued = m->publicationQueuedAt, pts = m->publicationPts;
|
||||
QImage image;
|
||||
if (m->publicationVideo) {
|
||||
const auto* slot = m->pictures.readSlot();
|
||||
if (!slot) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video GUI missing published slot serial=%llu; stopping video", (unsigned long long)serial);
|
||||
stop(); return;
|
||||
}
|
||||
image = slot->image;
|
||||
const uint64_t serial = m->pendingSerial.load(std::memory_order_acquire);
|
||||
if (!m->running || !serial || serial == m->deliveredSerial) return;
|
||||
m->deliveredSerial = serial;
|
||||
const int64_t queued = m->publicationQueuedAt, pts = m->publicationPts;
|
||||
QImage image;
|
||||
if (m->publicationVideo) {
|
||||
const auto* slot = m->pictures.readSlot();
|
||||
if (!slot) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video GUI missing published slot serial=%llu; stopping video", (unsigned long long)serial);
|
||||
stop(); return;
|
||||
}
|
||||
const int64_t now = nowUs(), wait = now - queued;
|
||||
++m->uiPublished; m->uiWaitMax = std::max(m->uiWaitMax, wait);
|
||||
if (m->uiLastAt) m->uiGapMax = std::max(m->uiGapMax, now - m->uiLastAt);
|
||||
m->uiLastAt = now;
|
||||
emit imageChanged(image, serial);
|
||||
const int64_t slotUs = nowUs() - now;
|
||||
if (slotUs > 20000 && m->uiImageSlotMax <= 20000)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video GUI image slot slow call=%llu serial=%llu work_us=%lld mono_us=%lld",
|
||||
(unsigned long long)m->id, (unsigned long long)serial, (long long)slotUs, (long long)now);
|
||||
m->uiImageSlotMax = std::max(m->uiImageSlotMax, slotUs);
|
||||
if (!m->presentationDriven || image.isNull()) imagePresented(serial);
|
||||
if (!m->uiReportAt) m->uiReportAt = now;
|
||||
if (now - m->uiReportAt >= 1000000) {
|
||||
const int64_t paintWait = m->paintWaitMax; m->paintWaitMax = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video GUI delivery call=%llu interval_us=%lld published=%u "
|
||||
"notification_wait_max_us=%lld delivery_gap_max_us=%lld paint_wait_max_us=%lld image_slot_max_us=%lld mono_us=%lld",
|
||||
(unsigned long long)m->id, (long long)(now - m->uiReportAt), m->uiPublished,
|
||||
(long long)m->uiWaitMax, (long long)m->uiGapMax, (long long)paintWait, (long long)m->uiImageSlotMax, (long long)now);
|
||||
m->uiReportAt = now; m->uiPublished = 0; m->uiWaitMax = m->uiGapMax = 0;
|
||||
m->uiImageSlotMax = 0;
|
||||
}
|
||||
if (!image.isNull() && pts >= 0 && pts % 1000000 < 34000)
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video timing stage=publish call=%llu pts=%lld mono_us=%lld ui_wait_us=%lld",
|
||||
(unsigned long long)m->id, (long long)pts, (long long)now, (long long)wait);
|
||||
image = slot->image;
|
||||
}
|
||||
const int64_t now = nowUs(), wait = now - queued;
|
||||
++m->uiPublished; m->uiWaitMax = std::max(m->uiWaitMax, wait);
|
||||
if (m->uiLastAt) m->uiGapMax = std::max(m->uiGapMax, now - m->uiLastAt);
|
||||
m->uiLastAt = now;
|
||||
emit imageChanged(image, serial);
|
||||
const int64_t slotUs = nowUs() - now;
|
||||
if (slotUs > 20000 && m->uiImageSlotMax <= 20000)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video GUI image slot slow call=%llu serial=%llu work_us=%lld mono_us=%lld",
|
||||
(unsigned long long)m->id, (unsigned long long)serial, (long long)slotUs, (long long)now);
|
||||
m->uiImageSlotMax = std::max(m->uiImageSlotMax, slotUs);
|
||||
if (!m->presentationDriven || image.isNull()) imagePresented(serial);
|
||||
if (!m->uiReportAt) m->uiReportAt = now;
|
||||
if (now - m->uiReportAt >= 1000000) {
|
||||
const int64_t paintWait = m->paintWaitMax; m->paintWaitMax = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video GUI delivery call=%llu interval_us=%lld published=%u "
|
||||
"notification_wait_max_us=%lld delivery_gap_max_us=%lld paint_wait_max_us=%lld image_slot_max_us=%lld mono_us=%lld",
|
||||
(unsigned long long)m->id, (long long)(now - m->uiReportAt), m->uiPublished,
|
||||
(long long)m->uiWaitMax, (long long)m->uiGapMax, (long long)paintWait, (long long)m->uiImageSlotMax, (long long)now);
|
||||
m->uiReportAt = now; m->uiPublished = 0; m->uiWaitMax = m->uiGapMax = 0;
|
||||
m->uiImageSlotMax = 0;
|
||||
}
|
||||
if (!image.isNull() && pts >= 0 && pts % 1000000 < 34000)
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video timing stage=publish call=%llu pts=%lld mono_us=%lld ui_wait_us=%lld",
|
||||
(unsigned long long)m->id, (long long)pts, (long long)now, (long long)wait);
|
||||
}
|
||||
void CallVideoEngine::start(quint64 id) {
|
||||
if (m->running && m->id == id) return;
|
||||
|
||||
Reference in new issue
Block a user