diff --git a/doc/call_video.txt b/doc/call_video.txt index eb5e98ed..bc5794d0 100644 --- a/doc/call_video.txt +++ b/doc/call_video.txt @@ -34,11 +34,35 @@ call_id, video_generation и request_id; одно исполнение и пос Принятая настройка не заменяет измерение фактического выхода. Shutdown отменяет уведомления/таймеры, останавливает producers, затем освобождает очереди и codecs. +Применение bitrate на ключевом кадре (уточнение пользователя) +Worker хранит pending-команду и применяет её перед кодированием ближайшего +планового IDR. GOP ограничен одной секундой исходной шкалы capture PTS, а не +числом принятых сетью кадров: backpressure не должен блокировать переключение. +Для x265 используется новый encoder с нужным bitrate; обычный reconfigure даже +на IDR не обходит найденный дефект 4.1. Исправленная внешняя библиотека не нужна. +Первый выход нового encoder обязан содержать IDR и актуальные VPS/SPS/PPS. +Камера, audio epoch, capture PTS и последовательность frame_id не перезапускаются. +При неизменном формате bitrate не меняет video_generation; новые parameter sets +идут с KEY. Смена codec/разрешения/камеры сохраняет отдельный протокол generation. +Приём применяет конфигурацию перед декодированием соответствующего KEY, не удаляя +уже декодированные старые кадры, ожидающие своего времени по audio clock. +Подтверждение rate_applied отправляется после проверки первого нового IDR, +независимо от сетевого admission; иначе закрытый admission создаст взаимное ожидание. +Успех означает применение настройки, не гарантию бит-в-бит постоянного выхода. +До IDR регулятор видит старый applied bitrate и одну pending-команду; его текущий +таймаут 2с включает ожидание IDR и открытие encoder. При отсутствии кадров/ошибке +команда завершается отказом; нельзя бесконечно продлевать её таймаут. +Ограничитель очереди работает и во время ожидания. Если новый IDR не принят, +producer запрашивает следующий IDR по существующему контракту send_frame. +Открытие кандидата и проверка первого IDR предшествуют освобождению старого encoder; +ошибка не должна уничтожать рабочий контекст. Реальные затраты переключения и +пиковый объём IDR нужно проверить для каждого доступного режима/backend. + Порядок реализации [+] История первых ACK 500мс/50мс и снимок payload send_q/inflight отдельного сервиса. [+] Unit счётчиков: повторные ACK, reset, отдельные сервисы; UDP/STCP регрессии пройдены. [+] Дрейф feed/tempo устранён картой фактических участков SoundTouch; см. ниже. -[ ] H.265 runtime bitrate: поддерживаемый backend без циклического пересоздания. +[ ] H.265 bitrate: замена encoder перед ближайшим плановым IDR, с непрерывными PTS. [+] Общий регулятор с детерминированными сценариями и подключением к router/encoder-командам. [+] Видеофрагменты, ограниченная сборка, обмен возможностями с подтверждением. [ ] Дополнить возможности ограничениями profile/level/tier и диапазоном битрейта. @@ -267,9 +291,30 @@ frameBitsTotal на i=0 при пустом lookahead, что в этой про (HEVC level 3.1), has_b_frames=0. Системная библиотека и приложение не заменены. ChatGUI успешно собирается; /tmp/call-x265-desktop-build.log. -Рекомендуемое продолжение: закрепить проверенную версию x265 и патч в локальной +Первоначальное предложение (заменено решением переключать encoder на IDR ниже): +закрепить проверенную версию x265 и патч в локальной сборке зависимости, повторить codec-пробу для рабочих разрешений и decoder, затем подключить direct API к backend звонка. Непрошедший проверку backend не объявлять в capabilities; H.264 остаётся альтернативой, когда H.265 недоступен. Патч пока не подключён к build_ffmpeg.sh; изменение поставки зависимости и интеграция backend остановлены по правилу AGENTS.md о выявленной нестыковке. + +Проверка переключения на плановом IDR, 14.10.2026 +По уточнению пользователя вместо поставки изменённой x265 проверено пересоздание +encoder на очередном IDR. Добавлены режимы probe_call_x265: + --keyframe-reconfig + --keyframe-reopen --output /tmp/call-x265-key-reopen.hevc +Запрос приходит на кадре 7 очередного участка; применение перед кадром 30, +через 23 кадра (767мс при 30fps). Плановый IDR задаётся каждые 30 кадров, +проверяются его NAL-тип и наличие VPS/SPS/PPS. PTS всех 360 кадров проверяются +на точное соответствие исходным, включая обе границы замены encoder. +Измеряются последние 60 кадров каждого 120-кадрового участка, после перехода. +На системной x265 4.1 без патча: + reconfigure перед IDR: 4008828 / 4001940 / 3977828 бит/с — отказ, код 2; + новый encoder на IDR: 4008828 / 309564 / 4019812 бит/с — PASS, код 0. +Логи: /tmp/call-x265-key-reconfig.log, /tmp/call-x265-key-reopen.log. +ffmpeg -v error -xerror декодирует объединённый поток без ошибок; +ffprobe подтверждает Main/level 3.1, B=0 и 360 кадров. +Это проверка 640x360@30 на синтетической сложной сцене, не готовый тракт звонка: +worker/camera/decoder UI и Android MediaCodec ещё предстоит подключить и проверить. +Патч x265 остаётся диагностическим материалом и не требуется выбранному способу. diff --git a/doc/tasks.md b/doc/tasks.md index 065eff34..198471a6 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -26,7 +26,10 @@ Минимальный патч владения параметрами проверен на отдельной библиотеке в /tmp: реальный выход 4.02 -> 0.298 -> 4.21 Мбит/с, декодируются 360/360 кадров. Патч сохранён в tools/chatgui/ffmpeg/patches, к сборке/приложению пока не подключён. - Согласно AGENTS.md остановлено перед изменением зависимости; подробности и варианты в плане. + Пользователь выбрал переключение на ближайшем плановом IDR: проверено пересоздание x265 без патча, + 4.01 -> 0.310 -> 4.02 Мбит/с, 360/360 кадров декодируются с сохранёнными PTS кодера. + Обычный reconfigure на IDR не помогает. В плане уточнены ожидание <= одного GOP, pending ACK, + backpressure и сохранение аудиошкалы; подключение к платформенным workers ещё предстоит. Контракты, этапы и проверки: `doc/call_video.txt`. [+] **Android live: увеличение и перемещение видео как в просмотрщике файлов** — 14.10.2026: diff --git a/tools/chatgui/tests/probe_call_x265.cpp b/tools/chatgui/tests/probe_call_x265.cpp index 1badf6c8..856539ff 100644 --- a/tools/chatgui/tests/probe_call_x265.cpp +++ b/tools/chatgui/tests/probe_call_x265.cpp @@ -1,4 +1,4 @@ -/* Диагностика пригодности x265 для звонка: один encoder, 4M -> 300k -> 4M, +/* Диагностика пригодности x265 для звонка: reconfigure или замена на IDR, 4M -> 300k -> 4M, * фиксированный Main 8-bit / Main tier / level 3.1, без lookahead/B-кадров. * Проверяем фактический выход, а не только return/getter reconfigure. * Запускается вручную; код 2 означает, что backend не прошёл пробу адаптации. */ @@ -12,11 +12,12 @@ struct Probe { const x265_api* api = nullptr; x265_param *parameters = nullptr, *actual = nullptr; - x265_encoder* encoder = nullptr; + x265_encoder *encoder = nullptr, *previous = nullptr; FILE* dump = nullptr; ~Probe() { if (dump && fclose(dump)) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 probe output close failed"); if (encoder) api->encoder_close(encoder); + if (previous) api->encoder_close(previous); if (parameters) api->param_free(parameters); if (actual) api->param_free(actual); } @@ -91,7 +92,7 @@ int main(int argc, char** argv) { if (keyReopen) { x265_encoder* candidate = p.api->encoder_open(config); if (!candidate) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 reopen failed phase=%d", phase); return 1; } - p.api->encoder_close(p.encoder); p.encoder = candidate; + p.previous = p.encoder; p.encoder = candidate; } else { result = p.api->encoder_reconfig(p.encoder, config); if (result) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 key reconfig failed phase=%d result=%d", phase, result); return 1; } @@ -130,6 +131,11 @@ int main(int argc, char** argv) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 incomplete IDR phase=%d frame=%d idr=%d vps=%d sps=%d pps=%d", phase, frame, idr, vps, sps, pps); return 1; } + if (p.previous) { + p.api->encoder_close(p.previous); p.previous = nullptr; + DEBUG_INFO(DEBUG_CATEGORY_CALL, "x265 replacement committed phase=%d frame=%d pts=%lld idr=%d", + phase, frame, (long long)output.pts, idr); + } if (frame < 3 || (keyMode && frame == 30)) { p.api->encoder_parameters(p.encoder, p.actual); DEBUG_INFO(DEBUG_CATEGORY_CALL, "x265 probe phase=%d frame=%d requested_kbps=%d getter_kbps=%d vbv_kbps=%d level=%d",