Browse Source

Record codec and telemetry validation with precise decoder diagnostics

master
evgeny 5 days ago
parent
commit
643420b644
  1. 25
      doc/stream.txt
  2. 21
      doc/tasks.md
  3. 19
      tools/chatgui/src/streamplayer_window.cpp

25
doc/stream.txt

@ -789,11 +789,12 @@ Android:
скоростные 120/240fps требуют фиксированного диапазона getHighSpeedVideoFpsRangesFor, скоростной сессии,
createHighSpeedRequestList и setRepeatingBurst. Битрейт зависит от размера/FPS, ограничен 16Mbit/s и codec caps.
I-frame interval=1s. Выход нормализуется в Annex B;
partial output собирается в access unit. Codec config передаётся с каждым IDR.
partial output собирается в access unit. Codec config передаётся с каждым KEY.
- Битрейт выбирается отдельно: Авто по разрешению/FPS либо пресет в диапазоне encoder.
На LIVE применяется PARAMETER_KEY_VIDEO_BITRATE; при отказе используется штатная смена конфигурации/rollback.
Смена кодека/камеры/режима создаёт revision и принимается вместе с KEY, без смены source_epoch и перезапуска звука.
Encoder configuration из отдельных NAL callbacks объединяется без дублирования parameter sets.
Разделённые CODEC_CONFIG buffers собираются отдельно с пределом 64KiB до разбора конфигурации.
- Camera MediaCodec Surface timestamps и AudioTimestamp.TIMEBASE_MONOTONIC приводятся к общему
origin System.nanoTime; время callback/получения блока не заменяет capture PTS.
- Микрофон трансляции использует AudioRecord.CAMCORDER, 48kHz stereo. Перед Opus AUDIO/128kbps
@ -908,6 +909,28 @@ late/pressure drops, число и байты кадров, PTS границ о
test_stream_service также проверяет общий каталог двух независимых источников одного канала,
шестисекундную паузу readers при живом сетевом приёме без restart и полное локальное/удалённое дочитывание FIN.
Статистика отправки на экране публикации:
- Отдельный uasync timer publisher каждые 100ms делает согласованный TX snapshot и передаёт его через JNI.
Kotlin обновляет отдельный StateFlow, без пересоздания preview. После close/detach timer отменяется.
- video_queue_bytes — оставшиеся байты с вычитанием offset частично упакованного головного кадра.
audio_queue_bytes — целые ожидающие Opus-пакеты. stream_pending_bytes — RS-части всех выдач текущей эпохи,
ещё не принятые ETCP. Это логическая очередь, исходные части продолжают храниться только в кольце.
- transport_queued_bytes — send_input_q, input_queue и остаток packer normalizer.
inflight_bytes — сумма unacked_bytes всех ETCP_CONN общего instance->connections, включая TCP-only/pending.
TCP registry/физические links не обходятся повторно. input_send_q уже включена в unacked_bytes.
- total_bytes = video_queue_bytes + audio_queue_bytes + stream_pending_bytes + transport_queued_bytes + inflight_bytes.
История RS-кольца не является исходящей очередью. Общие ETCP-показатели включают другой трафик узла;
сумма описывает незавершённую отправку в текущем представлении данных, не резерв памяти или полный объём IP-пакетов.
- ACK — байты найденных подтверждённых ETCP-пакетов, не размер пакета ACK. Повтор ACK ничего не добавляет.
Общая per-instance история живёт до destroy ядра, сохраняется при REINIT/close пира и не зависит от наличия BBR link.
Хранится 11 buckets по 100ms: текущий и десять завершённых. ACK_1s — сумма десяти завершённых интервалов;
окно движется каждые 100ms, отсутствующие интервалы равны нулю. Скорость Mbit/s = ACK_1s * 8 / 1000000.
- На экране: остаток видео, общий inflight, total send buffer, ACK_1s в KiB и Mbit/s.
DEBUG stream раз в секунду показывает каждый компонент суммы, ACK и число соединений.
test_stream_tx_stats проверяет границы окна, повторные ACK, закрытие соединений и разделение queued/inflight.
test_android_stream проверяет остаток большого частично упакованного кадра, кодек в JNI и смену revision/epoch.
test_stream_view воспроизводит настоящие H.264/H.265/VP9/AV1, отдельную decoder configuration и FIN.
Общий запас и адаптация воспроизведения:
- Кодированный поток хранится один раз, в stream_buffer (RS-кольцо 32MiB по умолчанию).
stream_media_store хранит только диапазоны seq, PTS готового видео/аудио и позиции KEY.

21
doc/tasks.md

@ -4,6 +4,27 @@
## Текущие задачи
[+] **Live: выбор видеокодека/битрейта и статистика отправки 10Hz** — 14.10.2026:
Android показывает доступные Surface encoder в порядке H.264/H.265/VP9/AV1/H.266,
проверяя профиль, разрешение/FPS и диапазон bitrate; аппаратные encoder имеют приоритет.
Битрейт выбирается отдельно (Авто либо пресет), LIVE обновляется без перезапуска encoder,
при отказе используется смена видеоконфигурации с rollback. Звук и source_epoch сохраняются.
uAV2: прежний 32-byte header, пять video_codec IDs, KEY = config_length:4BE/config/access unit.
Конфигурация повторяется при каждом KEY и защищена общей подписью/RS; оба плеера выбирают decoder по codec ID.
NAL parameter sets объединяются без дублирования, partial codec config собирается с пределом 64KiB.
Publisher каждые 100ms показывает остаток video queue, общий inflight, полный send buffer и ACK_1s.
Буфер учитывает отдельные очереди video/audio, ожидающие RS-выдачи и ETCP/normalizer без двойного учёта input_send_q;
историю кольца не включает. ACK всех соединений учитывается один раз при подтверждении найденного пакета;
окно — десять завершённых buckets по 100ms, переживает REINIT/close пира и не требует BBR link.
Статистика имеет отдельный StateFlow; строки не меняют высоту превью при обычном изменении чисел.
Реальные H.264/H.265/VP9/AV1, отдельная decoder configuration, FIN и смена revision проверены.
H.266 проверен на уровне формата/KEY; реального VVC encoder в тестовом окружении нет.
Устранена блокировка нового desktop viewer старыми control records в лимите подготовки медиа.
151 тест ядра прошёл без ошибок/пропусков; check.sh с proxy/burst/load — 119s.
Qt stream/audio timing и Kotlin-проверки прошли; vibechat и APK arm64-v8a/x86_64 собраны.
Устройства/colo2 не обновлялись. Протокол и формулы: `doc/stream.txt`, lifecycle: `doc/service_lifecycle.md`.
Логи: `/tmp/stream-codecs-{full-check,final-gui-tests,android-build,media-test,tx-test,native-test}.log`.
[+] **Live: воспроизведение из RS-кольца и минутная адаптация задержки** — 14.10.2026:
Android и desktop используют общее кольцо кодированных данных, метаданные PTS/KEY и отдельные
cursor каждого reader. Пауза player не останавливает live ingest/selector; длинные кадры собираются

19
tools/chatgui/src/streamplayer_window.cpp

@ -17,6 +17,7 @@
#include <chrono>
#include <algorithm>
#include <cstring>
#include <cerrno>
extern "C" {
#include "../../../lib/debug_config.h"
@ -319,7 +320,12 @@ void StreamPlayerWindow::decode() {
std::memcpy(decoder->extradata, configuration, configurationSize); decoder->extradata_size = (int)configurationSize;
}
}
if (!decoder || avcodec_open2(decoder, codec, nullptr) < 0) { fail(text("Stream video decoder failed")); break; }
int openResult = !codec ? AVERROR_DECODER_NOT_FOUND : !decoder ? AVERROR(ENOMEM) : avcodec_open2(decoder, codec, nullptr);
if (openResult < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop decoder open failed codec=%u name=%s config=%zu result=%d",
record.format.video_codec, codec ? codec->name : "unavailable", configurationSize, openResult);
fail(text("Stream video decoder failed")); break;
}
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop decoder codec=%u name=%s config=%zu", record.format.video_codec, codec->name, configurationSize);
decoder->pkt_timebase = AVRational{1, 1000000}; waitKey = false;
}
@ -328,13 +334,16 @@ void StreamPlayerWindow::decode() {
std::memcpy(packet->data, data, size);
packet->pts = packet->dts = (int64_t)record.pts;
int sent = avcodec_send_packet(decoder, packet);
if (sent < 0) { DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop H264 packet rejected result=%d", sent); reset(true); continue; }
if (sent < 0) {
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop video packet rejected codec=%u bytes=%zu result=%d", record.format.video_codec, size, sent);
reset(true); continue;
}
int received;
while ((received = avcodec_receive_frame(decoder, frame)) == 0) {
decoded++;
if (frame->width <= 0 || frame->width > 4096 || frame->height <= 0 || frame->height > 4096) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop H264 dimensions invalid id=%llu size=%dx%d",
(unsigned long long)p.id, frame->width, frame->height);
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop video dimensions invalid id=%llu codec=%u size=%dx%d",
(unsigned long long)p.id, record.format.video_codec, frame->width, frame->height);
fail(text("Invalid stream video dimensions")); p.running = false; break;
}
int64_t pts = frame->best_effort_timestamp == AV_NOPTS_VALUE ? (int64_t)record.pts : frame->best_effort_timestamp;
@ -358,7 +367,7 @@ void StreamPlayerWindow::decode() {
p.videoBytes += (size_t)image.sizeInBytes(); p.videos.push_back({std::move(image), pts});
}
if (received != AVERROR(EAGAIN) && received != AVERROR_EOF && p.running) {
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop H264 decode rejected result=%d", received); reset(true);
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop video decode rejected codec=%u result=%d", record.format.video_codec, received); reset(true);
}
if (nowUs() - report >= 1000000) {
int64_t now = nowUs(); double seconds = (now - report) / 1000000.0; report = now;

Loading…
Cancel
Save