Document independent audio preparation and live stream catchup

This commit is contained in:
evgeny committed 2026-10-21 01:28:20 +02:00
1 parent 4e5530aca0
commit e8ee608ba9
3 files changed
+50 -29

No files matched your search

+12 -3
View File
@@ -144,10 +144,16 @@ VIDEO_END/AUDIO_END следуют за последними пакетами с
Получение батча погашает запрос, включая повторные обращения до pull; скрытого чтения следующего батча нет.
Callback готовности только будит desktop worker и не вызывает API ядра/bridge. Stop снимает callback
под mutex viewer до освобождения его контекста; detach будит ожидающие workers до уничтожения manager.
На Android signal принадлежит worker и живёт до stream_stop и close mixer input; ядро только уведомляет его.
На Android signal принадлежит audio worker и живёт до stream_stop, join video worker и close mixer input;
ядро только уведомляет его. Surface освобождается после join video worker.
В wait не удерживаются mutex bridge/ядра. Расход PCM будит тот же signal, уведомления сливаются без потери пробуждения.
GUI и audio callback будят worker при движении часов/освобождении места; сна между готовыми пакетами нет.
Android worker держит один рабочий input и один готовый output MediaCodec; свободный
Каждый reader имеет отдельные audio/control и video workers. Audio worker читает батчи и готовит
до 250ms PCM через Opus/SoundTouch, не удерживая блокировку consumer. Запас пополняется при наличии
данных без обязательного prefill. SPSC передаёт PCM вместе с отображением исходных PTS; mixer/audio
callback только копирует и смешивает. Короткая блокировка защищает output map/clock и lifetime,
в ней нет декодирования, time-stretch или MediaCodec/FFmpeg. EOF продолжает prepare до опустошения DSP.
Android video worker получает ограниченный mailbox (два кадра) и держит один рабочий input и один готовый output MediaCodec; свободный
input buffer получает лишь для уже готовых данных и сразу возвращает codec.
Первый decoded frame разрешает движение clock/audio. Будущий output ждёт PTS и
приостанавливает новые video requests, сохраняя независимую обработку audio/control.
@@ -155,8 +161,11 @@ input buffer получает лишь для уже готовых данных
первого аудиопакета, сохраняя это основание неизменным. Opus/SoundTouch получает PCM до
запуска audio clock; неподвижные часы не разрешают читать дальше этого ограниченного окна.
PTS поданных кадров служат диагностике/проверке запаздывания, а не лимиту заполнения codec.
Смена revision заменяет только video decoder и его измеренный защитный guard;
Смена revision заменяет только video decoder и его диагностические измерения;
GAP/EXPIRED начинают новую точку KEY. Нехватка сети сохраняет decoder references.
Reset увеличивает video generation; результаты прежнего поколения не публикуются. Video worker
после EOF остаётся доступен для reset до явного stop. Audio clock после старта не ждёт видео:
просроченные изображения декодируются для сохранения references, но не показываются.
Desktop FFmpeg держит один рабочий AVPacket и два готовых изображения; EAGAIN оставляет
packet до следующего прохода. FIN подаёт EOS и дренирует выход до decoder EOF;
конец GUI требует также показа оставшихся изображений и завершения audio tail.
+36 -24
View File
@@ -816,8 +816,9 @@ Android:
lookback=200ms, lookahead=0, release=rise*5, target=0.25. История и gain принадлежат только audio worker
трансляции; оба канала используют общий gain, сохраняющий стереобаланс. Изменения настроек рации применяются на следующем PCM-кадре, без перезапуска микрофона,
Opus или source_epoch; compressor не добавляет задержку и не меняет capture PTS.
- Camera/codec callbacks — HandlerThread, Opus encoder — audio capture thread, decoder — отдельный viewer worker,
общий AudioTrack — worker StreamAudioMixer. Перекладка плиток сохраняет TextureView/decoder.
- Camera/codec callbacks — HandlerThread, Opus encoder — audio capture thread. Каждый viewer имеет
audio/control worker и независимый video worker; общий AudioTrack — worker StreamAudioMixer.
Перекладка плиток сохраняет TextureView/decoder.
- Native bridge использует общий stream_publisher для TX и stream_viewers для RX.
stream_publisher держит pthread mutex, ограниченные очереди и один pending post либо media timer;
статистика обслуживается отдельным таймером 100ms. Тот же модуль используется desktop.
@@ -834,6 +835,7 @@ Android:
Получение ответа и сетевые AVAILABLE не запускают автоматическое чтение следующего кадра.
Запас остаётся в RS-кольце. want_video запрашивает следующий полный видеокадр независимо от часов показа;
audio_ahead ограничивает только звук относительно PTS потребителя. Управление/звук обслуживаются независимо от video pressure.
StreamVideoPlayback владеет MediaCodec и принимает mailbox до двух кадров от audio/control worker.
StreamDecodePump держит один рабочий кодированный кадр и один ожидающий output MediaCodec.
Decoder заполняет свой внутренний конвейер до первого выхода без ограничений по числу поданных кадров или video lookahead.
Перед каждым входом проверяется выход; свободный input buffer берётся только для уже готового рабочего кадра.
@@ -841,9 +843,10 @@ Android:
Низкая задержка запрашивается через KEY_LOW_LATENCY при заявленной поддержке, но корректность не зависит от этого режима.
За проход обрабатываются до 32 готовых записей, до восьми output buffers за один вызов pump.
Готовый батч и расход PCM будят worker через отдельную condition variable, без JVM callbacks из ядра.
Ожидание имеет deadline 5ms при незавершённой работе синхронного MediaCodec, иначе 50ms;
уведомление прерывает ожидание сразу. Между готовыми записями sleep нет.
Worker владеет signal до stream_stop и отсоединения своего mixer input; detach ядра будит signal, не освобождая его.
Video worker проверяет MediaCodec каждые 5ms при незавершённой работе, иначе ждёт до 50ms;
audio worker будят media, расход PCM и изменение video state. Без звука его wall clock обновляется каждые 5ms.
Между готовыми записями sleep нет. Audio worker владеет signal до stream_stop, join video worker
и отсоединения своего mixer input; detach ядра будит signal, не освобождая его.
При обычной нехватке данных decoder references сохраняются; GAP/вытеснение требуют нового KEY.
Просроченный кадр при нормальной последовательности декодируется без показа.
- Фиксированного prebuffer нет. Начало clock/audio output разрешено после первого декодированного кадра.
@@ -856,13 +859,13 @@ Android:
Заполнение поддерживается до фактического bufferSizeInFrames устройства; API>=31 задаёт start threshold=1.
При EOF недописанный PCM передаётся полностью; mixer выдаёт тишину после последнего кадра источника
до его физического воспроизведения. END одного input не закрывает AudioTrack остальных.
Hold/starvation одного источника добавляет silence span только в его native timeline, не двигая его PTS.
Starvation одного источника добавляет silence span только в его native timeline, не двигая его PTS.
Видео не удерживает звук после старта; накопившийся из-за тишины избыток погашается ускорением.
GAP создаёт новый input/origin, сохраняя общее устройство и часы остальных трансляций.
Watchdog 2s проверяет отсутствие движения hardware head только при достаточном заполнении либо заблокированной записи;
намеренная пауза и ожидание данных до порога запуска не считаются зависанием. Ошибка содержит состояние и счётчики устройства.
Защитный guard минутного контроллера учитывает audio device lead и наблюдённое опережение decoder
(максимум last_input_PTS-output_PTS текущей revision плюс период кадра). Drain не обнуляет этот guard;
новый decoder начинает измерение заново. Отдельного контроллера задержки декодера нет.
Защитный guard аудиоконтроллера — максимум 40ms и audio device lead + 20ms.
Наблюдённое опережение decoder остаётся диагностикой и не увеличивает задержку звука.
Отсутствие output progress 10s при наличии работы даёт ERROR; сетевые паузы и ожидание PTS допустимы.
Состояния ожидания KEY и decoder разделены; логи показывают inputs/decoded/shown, рабочие slots, inflight и decoderLeadUs.
Короткие audio gaps заполняются Opus PLC, большой разрыв сбрасывает timeline.
@@ -1041,8 +1044,9 @@ Desktop:
Stop снимает callback до уничтожения worker; detach будит ожидание перед освобождением manager.
- stream_media_cursor собирает ближайший access unit выбранного кодека и выдаёт Opus 48kHz mono/stereo/20ms из RS-кольца.
Android AudioTrack и desktop output работают в stereo; mono-пакеты upmix выполняет libopus.
FFmpeg/Opus декодируются отдельным worker; Qt-очередь не накапливает кадры.
Worker держит один рабочий AVPacket и не более двух готовых изображений. Следующий video request не ограничен часами показа.
Opus/SoundTouch и FFmpeg декодируются разными workers каждого player; Qt-очередь не накапливает кадры.
Video worker держит один рабочий AVPacket и не более двух готовых изображений; audio worker передаёт ему
ближайшие кодированные кадры через ограниченную очередь. Следующий video request не ограничен часами показа.
Бюджет нового видео учитывает AVPacket, готовые изображения и оставшиеся кодированные кадры батча.
Записи аудио/видео расходуются независимо по готовности потребителя; заполненное видео не блокирует запрос звука.
VIDEO_END/AUDIO_END следуют за последними пакетами своего трека и не ждут расходования другого трека.
@@ -1051,14 +1055,15 @@ Desktop:
Между готовыми пакетами нет sleep или нового round trip к ядру.
FFmpeg output обслуживается перед input; EAGAIN сохраняет рабочий packet и означает backpressure, не GAP.
Дальние кадры и звук не копируются из кольца. SoundTouch и устройство держат только рабочую порцию PCM.
Кадры сохраняются до достижения их PTS аудиочасами; задержка показа удерживает clock только этого player.
Видео декодируется без задержки frame-threading. При GAP сбрасываются decoder refs и timeline до KEY.
- Первый decoded frame разрешает движение clock/audio, защитный guard учитывает наблюдённый decoder lead.
Кадры сохраняются до достижения их PTS аудиочасами; опоздавшие более чем на 100ms отбрасываются до RGB conversion.
FFmpeg использует четыре slice threads без frame-threading. При GAP сбрасываются decoder refs и timeline до KEY;
поколение video worker исключает публикацию результатов прежнего decoder.
- Первый decoded frame разрешает движение clock/audio; затем позднее видео не удерживает звук.
VIDEO_END отправляет decoder EOS, AUDIO_END дренирует SoundTouch независимо от видеодекодера.
Общий END не управляет сбросом отдельных декодеров: иначе заполненный PCM, ожидание первой картинки и decoder EOS образуют цикл.
Выход дренируется теми же ограниченными проходами до EOF, GUI продолжает показ хвоста.
После физического воспроизведения последнего PCM оставшееся видео использует монотонные часы;
после EOF видеодекодера оставшийся звук больше не удерживается ожиданием картинки.
EOF видеодекодера не влияет на независимое воспроизведение оставшегося звука.
Ожидающий кадр не теряется при заполненной очереди изображений. Отсутствие прогресса при наличии работы логируется как ошибка.
- При начальном подключении desktop/Android прогревают Opus из кольца до первого KEY (до 500ms истории).
Старый звук обрезается по PTS видео, включая начало stereo PCM-пакета; декодер не сбрасывается из-за preroll.
@@ -1175,23 +1180,30 @@ test_stream_publish проверяет реальные encoder/decoder H.264/H.
- AVAILABLE сообщает готовность индекса, DATA/GAP/END callbacks описывают live ingest.
media_read выдаёт END после обоих треков конкретного reader. MORE продолжает ограниченную сборку
отдельным callback без ожидания новых пакетов; WAIT ожидает новые данные или изменение позиции.
- Общий C-контроллер хранит 60 секундных минимумов запаса: ready_PTS - playback_PTS.
ready_PTS — меньшая из границ полного видео и звука, для preview без звука — граница видео.
Каждое обновление учитывается в минимуме текущей секунды, включая короткий простой.
- Декодированный PCM пополняется до 250ms при наличии данных; фиксированного ожидания перед стартом нет.
Это запас отдельного audio worker, размер аппаратного буфера не увеличивается. Audio lookahead — device lead + 500ms,
достаточный для 250ms PCM даже при x1.6. SPSC имеет 128 слотов по 960 stereo frames с собственными PTS spans.
Mixer/callback не вызывает Opus/SoundTouch; Android переиспользует PCM-массив каждого источника.
- Общий C-контроллер хранит четыре минимума за интервалы по 50ms: ready_PTS - playback_PTS.
ready_PTS — граница готового звука, пока аудиотрек активен; без звука/после AUDIO_END — максимум границ треков.
Каждое обновление учитывается в минимуме текущего интервала, включая короткий простой.
История нормализована через correction += wall_elapsed - actually_played_media_elapsed.
Старые минимумы автоматически увеличиваются на длительность паузы и уменьшаются на реальный расход при догоне.
Измерения опираются на PTS фактически услышанного PCM, включая аппаратную очередь и периоды тишины.
- После полной здоровой минуты темп плавно повышается до x1.1; SoundTouch сохраняет тон и стерео.
Ускорение расходует только минимальный запас за минуту сверх безопасной границы (не менее 33ms,
с учётом аппаратной задержки), уже поставленного ускоренного звука и плавного торможения.
- После 200ms без фактической нехватки темп плавно повышается до x1.6; SoundTouch сохраняет тон и стерео.
Темп пропорционален избытку (масштаб 2s), изменение ограничено x0.6 за 500ms.
Ускорение расходует только минимальный запас сверх защитной границы, уже подготовленного/поставленного
ускоренного звука и плавного торможения. Каждый источник имеет собственный controller и отображение PTS.
При нехватке данных темп возвращается к x1.0, часы ждут, сетевой приём продолжает накапливать запас.
После возобновления отсчёт здоровой минуты начинается заново. Разрыв/GAP/новая эпоха сбрасывают историю.
После возобновления начинается новое окно 200ms. Разрыв/GAP/новая эпоха сбрасывают историю.
- Диагностика stream: PTS, ready, текущий/минимальный запас, correction, healthy interval, tempo,
pending gain, wait/resume, generation, причины resync и размеры рабочих очередей. DEBUG — раз в секунду.
test_stream_playback проверяет историю, плавный догон с отложенной аппаратной очередью,
независимые readers, позднюю повреждённую RS-часть, вытеснение, GAP, FIN и большой многопачечный кадр.
test_stream_audio проверяет настоящий стерео Opus/SoundTouch x1.1, сохранение тона/баланса,
отображение аппаратного frame counter в исходный PTS и остановку PTS на тишине.
test_stream_audio проверяет настоящий стерео Opus/SoundTouch x1.1/x1.6, сохранение тона/баланса,
конкурентные producer/consumer, запас 250ms, аппаратный frame counter и остановку PTS на тишине.
test_stream_playback моделирует паузы 200ms/4s с отложенным выходом 350ms и проверяет возврат к guard без перерасхода.
test_stream_view задерживает видеодекодер на 500ms и проверяет непрерывность аудио.
21. Суперузлы: помощь в раздаче по запросу
+2 -2
View File
@@ -2,7 +2,7 @@
* feed/prepare/set_tempo/finish — один producer; pull/silence — один consumer.
* position/pending_gain сериализуют с consumer короткой внешней блокировкой; pending_gain вызывает producer.
* Producer не держит эту блокировку при feed/prepare/finish: callback не ждёт Opus/SoundTouch.
* feed прогревает Opus перед start_pts, затем подаёт PCM с темпом 1.0..1.1 без изменения высоты звука.
* feed прогревает Opus перед start_pts, затем подаёт PCM с темпом 1.0..1.6 без изменения высоты звука.
* pull записывает соответствие кадров устройства исходному PTS; silence продлевает hardware time, но не media time.
* position/pending_gain используют реально проигранный frame counter, поэтому уже поставленный PCM сохраняет свой темп.
* Последний Opus-пакет/EOF не теряется: finish выталкивает остаток SoundTouch. */
@@ -28,7 +28,7 @@ int stream_playback_audio_prepare(struct stream_playback_audio *audio);
unsigned stream_playback_audio_buffered(const struct stream_playback_audio *audio);
void stream_playback_audio_set_tempo(struct stream_playback_audio *audio, int tempo_milli);
int stream_playback_audio_needs_input(const struct stream_playback_audio *audio);
/* frames <=8192; limit_pts_us ограничивает продвижение исходного материала до готовой границы видео/аудио.
/* frames <=8192; limit_pts_us ограничивает исходный PTS, INT64_MAX разрешает весь подготовленный звук.
* Возвращает число stereo frames; оставшиеся места backend заполняет тишиной и вызывает silence. */
int stream_playback_audio_pull(struct stream_playback_audio *audio, int16_t *pcm, unsigned frames, int64_t limit_pts_us);
void stream_playback_audio_silence(struct stream_playback_audio *audio, unsigned frames);