diff --git a/doc/stream.txt b/doc/stream.txt index 6e4d0dab..b542f6ea 100644 --- a/doc/stream.txt +++ b/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. diff --git a/doc/tasks.md b/doc/tasks.md index a8947637..9c1ab6c5 100644 --- a/doc/tasks.md +++ b/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; длинные кадры собираются diff --git a/tools/chatgui/src/streamplayer_window.cpp b/tools/chatgui/src/streamplayer_window.cpp index e28be510..93aaf560 100644 --- a/tools/chatgui/src/streamplayer_window.cpp +++ b/tools/chatgui/src/streamplayer_window.cpp @@ -17,6 +17,7 @@ #include #include #include +#include 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;