diff --git a/doc/service_lifecycle.md b/doc/service_lifecycle.md index 077b0638..a7e3cd6c 100644 --- a/doc/service_lifecycle.md +++ b/doc/service_lifecycle.md @@ -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. diff --git a/doc/stream.txt b/doc/stream.txt index e1c560c1..7d467d4c 100644 --- a/doc/stream.txt +++ b/doc/stream.txt @@ -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. Суперузлы: помощь в раздаче по запросу diff --git a/src/stream/stream_playback_audio.h b/src/stream/stream_playback_audio.h index 1fceec2b..40f38d4a 100644 --- a/src/stream/stream_playback_audio.h +++ b/src/stream/stream_playback_audio.h @@ -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);