Specify stream playback clock and audio thread contracts

This commit is contained in:
evgeny committed 2026-10-26 12:43:25 +01:00
1 parent c5292c9994
commit 0f54f5ef66
2 files changed
+45 -12

No files matched your search

+23 -3
View File
@@ -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
+22 -9
View File
@@ -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,