diff --git a/src/stream/stream_playback_audio.h b/src/stream/stream_playback_audio.h index 3065b2b3..37611aa4 100644 --- a/src/stream/stream_playback_audio.h +++ b/src/stream/stream_playback_audio.h @@ -19,11 +19,18 @@ extern "C" { struct stream_playback_audio; /* buffer_ms (1..1000) — порог пополнения, старта и возобновления готового PCM. - * Считается только готовый PCM: аппаратная очередь и закодированные пакеты в него не входят. */ + * Считается только готовый PCM: аппаратная очередь и закодированные пакеты в него не входят. + * start_pts_us >= 0; возвращает объект либо NULL при ошибке. Создание/уничтожение — без параллельных обращений. */ struct stream_playback_audio *stream_playback_audio_create(int64_t start_pts_us, unsigned buffer_ms); +/* После остановки producer/consumer освободить DSP, Opus и PCM; NULL допустим. */ void stream_playback_audio_destroy(struct stream_playback_audio *audio); +/* Producer: один 20-мс Opus mono/stereo, 1..1275 байтов, PTS >= 0; выход всегда stereo 48 кГц. + * PTS нормализуется к шагу 20 мс с допуском 10 мс; пропуски до 100 мс заполняются PLC. + * Возврат 0 — принят, 1 — старый пакет пропущен, -2 — разрыв >=110 мс, нужен новый playback, + * -1 — аргументы/декодирование/DSP. После finish ввод запрещён; перед новым вводом проверяют needs_input. */ int stream_playback_audio_feed(struct stream_playback_audio *audio, int64_t pts_us, const uint8_t *packet, size_t length); -/* Переносит готовый выход DSP в SPSC; вызывать producer после освобождения PCM, в том числе после finish. */ +/* Producer: перенести готовый выход DSP в SPSC; вызывать после освобождения PCM, в том числе после finish. + * 0 — успех (также при полном SPSC), -1 — ошибка состояния/DSP. */ int stream_playback_audio_prepare(struct stream_playback_audio *audio); /* Потокобезопасный снимок готового PCM, в stereo frames (48000 frames/s). */ unsigned stream_playback_audio_buffered(const struct stream_playback_audio *audio); @@ -32,18 +39,31 @@ unsigned stream_playback_audio_buffered(const struct stream_playback_audio *audi int stream_playback_audio_buffering(const struct stream_playback_audio *audio); /* Producer: внутренний lookahead DSP при текущем темпе; добавляется к guard устройства, без PCM target. */ int64_t stream_playback_audio_latency_us(struct stream_playback_audio *audio); +/* Producer: установить темп 1000..1600, сохранив PTS уже подготовленного PCM; неверный аргумент только логируется. */ void stream_playback_audio_set_tempo(struct stream_playback_audio *audio, int tempo_milli); +/* 1 — ввод ещё открыт и готового PCM меньше buffer_ms; 0 — порог достигнут, finish/failed либо audio=NULL. + * Producer ограничивает этим ввод, иначе скрытый выход DSP может накапливаться сверх SPSC. */ int stream_playback_audio_needs_input(const struct stream_playback_audio *audio); /* frames <=8192; limit_pts_us ограничивает исходный PTS, INT64_MAX разрешает весь подготовленный звук. - * Возвращает число stereo frames; при накоплении порога — 0. После finish порог не применяется. + * Возвращает число stereo frames, -1 при ошибке; при накоплении порога — 0. После finish порог не применяется. * Оставшиеся места backend заполняет тишиной и вызывает silence; ограничение PTS не запускает новое накопление. */ int stream_playback_audio_pull(struct stream_playback_audio *audio, int16_t *pcm, unsigned frames, int64_t limit_pts_us); +/* Consumer: учесть frames тишины, реально поставленные после PCM; PTS остаётся последним, written растёт. */ void stream_playback_audio_silence(struct stream_playback_audio *audio, unsigned frames); +/* Consumer либо под его внешней блокировкой: PTS по аппаратному счётчику played_frames с той же нулевой точкой, + * что written. Значение выше written ограничивается; до PCM возвращает start_pts, для NULL -1. */ int64_t stream_playback_audio_position(struct stream_playback_audio *audio, uint64_t played_frames); +/* Producer под блокировкой consumer: ещё не проигранное сокращение длительности в DSP/SPSC/устройстве, мкс. + * Учитывает прежние темпы поставленного PCM; NULL/полностью проигранный EOF дают 0. */ int64_t stream_playback_audio_pending_gain(struct stream_playback_audio *audio, uint64_t played_frames); +/* Consumer либо под его блокировкой: 1 — аппаратная позиция попала в тишину/за written, 0 — PCM/finish/NULL. */ int stream_playback_audio_starved(const struct stream_playback_audio *audio, uint64_t played_frames); +/* Consumer либо под его блокировкой: всего поставлено устройству stereo frames, включая silence; NULL даёт 0. */ uint64_t stream_playback_audio_written(const struct stream_playback_audio *audio); +/* Producer: однократно flush DSP и запретить feed; остаток далее переносится prepare. NULL/повтор допустимы. */ void stream_playback_audio_finish(struct stream_playback_audio *audio); +/* Потокобезопасно: DSP полностью подготовлен и SPSC разобран consumer; аппаратная очередь ещё может играть. + * Возвращает 1 также для NULL, 0 — пока есть остаток или ввод не завершён. */ int stream_playback_audio_empty(const struct stream_playback_audio *audio); #ifdef __cplusplus diff --git a/src/stream/stream_playback_clock.h b/src/stream/stream_playback_clock.h index 49172f7a..adc7b846 100644 --- a/src/stream/stream_playback_clock.h +++ b/src/stream/stream_playback_clock.h @@ -23,13 +23,19 @@ extern "C" { #define STREAM_PLAYBACK_MAX_TEMPO 1600 #define STREAM_PLAYBACK_RAMP_US 500000 -struct stream_playback_bucket { int64_t period, minimum_us; }; +struct stream_playback_bucket { + int64_t period, minimum_us; // Номер 50-мс интервала (-1 пустой) и минимум reserve-correction +}; struct stream_playback_clock { - struct stream_playback_bucket history[STREAM_PLAYBACK_HISTORY]; - uint64_t id, pauses; - int64_t first_us, last_us, last_position_us, correction_us, last_bad_us, pause_us, report_us; - int64_t ramp_remainder; - int tempo_milli, initialized, waiting; + struct stream_playback_bucket history[STREAM_PLAYBACK_HISTORY]; // Четыре последних временных интервала + uint64_t id, pauses; // ID для диагностики и число переходов в ожидание + int64_t first_us, last_us, // Первое/последнее монотонное время update + last_position_us, // Последняя фактически проигранная позиция + correction_us, // Накопленное wall elapsed минус media advanced + last_bad_us, // Начало текущего периода без недостачи/пропуска измерений + pause_us, report_us; // Начало ожидания и время последнего диагностического снимка + int64_t ramp_remainder; // Остаток дробного шага изменения темпа + int tempo_milli, initialized, waiting; // Текущий темп (1000..1600) и флаги первого update/ожидания }; /* Причина решения регулятора; значения передаются также через JNI. */ @@ -38,10 +44,15 @@ enum stream_playback_reason { STREAM_PLAYBACK_RESERVE, STREAM_PLAYBACK_CATCHUP }; struct stream_playback_state { - int64_t position_us, reserve_us, minimum_us, correction_us, stable_us, removable_us; + int64_t position_us, // Переданная фактически проигранная позиция + reserve_us, // ready-position, может быть отрицательным + minimum_us, // Минимум короткой истории с текущей коррекцией времени + correction_us, // Накопленная коррекция wall/media + stable_us, // Время без недостачи и длинных пропусков update + removable_us; // max(0,minimum-guard-pending_gain) int64_t guard_us, pending_gain_us; // Защищённый запас и ещё не проигранное сокращение PCM enum stream_playback_reason reason; // Причина последнего решения регулятора - int tempo_milli, waiting, history_ready; + int tempo_milli, waiting, history_ready; // Темп 1000=1x; ожидание; заполнены все четыре интервала }; /* Короткое постоянное имя причины для лога; неизвестное значение логируется и возвращает "invalid". */ @@ -56,8 +67,10 @@ void stream_playback_clock_reset(struct stream_playback_clock *clock, uint64_t i * ready_us — непрерывная готовая граница, включая кольцо, кодек и устройство. Все времена — микросекунды. * guard_us — неприкосновенный запас; pending_gain_us — ещё не проигранное сокращение длительности PCM в устройстве. * starved отмечает фактическую остановку подготовки/вывода; state получает темп (tempo_milli: 1000=1x) и запас. - * Результат не меняется при ошибке. + * now/position/ready неотрицательны; guard/pending_gain в [0,60000000]. starved трактуется как bool. + * Перед первым update требуется reset. Результат и clock не меняются при ошибке. * Временное уменьшение фактического position_us запрещено; после перемотки вызывают reset. + * Скачок media более 2*wall_elapsed+1 с отвергается; промежуток update >1 с начинает новое наблюдение. * Возврат 0 — успех, отрицательный — неверные аргументы/часы. */ int stream_playback_clock_update(struct stream_playback_clock *clock, int64_t now_us, int64_t position_us, int64_t ready_us, int64_t guard_us, int64_t pending_gain_us,