Files
utun2/src/stream/stream_buffer.h
T
evgeny 0e17756e19 Show stream playback buffer chains on desktop and Android
Send coalesced per-hop telemetry every 200 ms ahead of video DATA using the shared ETCP transport. Measure pending frames before ETCP handoff, retain stale and unavailable node values, and display the worst path with playback tempo.

Bump stream wire protocol to v7. Validate with 187 core tests, 21 desktop player tests, two Android indicator tests, and an Android APK build.
2026-10-25 15:21:23 +01:00

101 lines
8.2 KiB
C

/* Кольцо структурно проверенных частей одного запуска; подпись проверяется после восстановления.
* Слоты резервируются по максимальному размеру, stored_bytes учитывает фактические длины.
* Вытеснение целой старейшей пачки не закрепляет данные для медленного получателя.
* Все вызовы — один uasync-поток. read возвращает память до следующего put/destroy.
* public_key должен происходить из проверенной записи источника в группе. */
#ifndef STREAM_BUFFER_H
#define STREAM_BUFFER_H
#include "stream_block.h"
#include "stream_wire.h"
#ifdef __cplusplus
extern "C" {
#endif
struct stream_buffer;
struct stream_buffer_delay {
uint64_t frames, video_us, audio_us; // Непереданные кадры и длительности параллельных треков
uint64_t first_pts, last_pts; // Границы видео для объединения с ещё не упакованной очередью источника
uint32_t period_us; // Измеренный период последнего кадра
int known; // 0 — отсутствует кадровая подсказка или ещё неизвестен период первого кадра
};
/* Подсказки DATA хранятся один раз на пачку, независимо от числа RS-частей; на воспроизведение не влияют. */
int stream_buffer_set_timing(struct stream_buffer *buffer, uint64_t seq, const struct stream_frame_timing *timing);
int stream_buffer_get_timing(struct stream_buffer *buffer, uint64_t seq, struct stream_frame_timing *timing);
/* Длительность очереди одного subblock от seq; история до cursor не учитывается. */
int stream_buffer_get_delay(struct stream_buffer *buffer, uint64_t seq, uint8_t subblock, struct stream_buffer_delay *delay);
/* Последняя телеметрия субпотока переживает закрытие receive; память принадлежит кольцу. */
int stream_buffer_set_telemetry(struct stream_buffer *buffer, uint8_t subblock, const struct stream_telemetry *telemetry);
int stream_buffer_get_telemetry(struct stream_buffer *buffer, uint8_t subblock, uint64_t now_ms, struct stream_telemetry *telemetry);
void stream_buffer_telemetry_down(struct stream_buffer *buffer, uint8_t subblock, uint64_t peer);
/* Observer не выполняет I/O и не меняет кольцо; wire действует лишь внутри callback. Destroy observer не вызывает. */
typedef void (*stream_buffer_event_fn)(void *arg, const uint8_t *wire, size_t length, int added);
void stream_buffer_set_observer(struct stream_buffer *buffer, stream_buffer_event_fn observer, void *arg);
/* Точное число/сумма байтов частей >= seq и время старейшего принятия.
* count=bytes=NULL ищет только старейший, без полного обхода. */
int stream_buffer_pending(struct stream_buffer *buffer, uint64_t seq, uint8_t subblock,
uint64_t *count, uint64_t *bytes, uint64_t *oldest_ms);
struct stream_buffer_stats {
size_t capacity_bytes;
size_t stored_bytes;
uint32_t blocks;
uint32_t batches;
uint64_t expired_before;
uint64_t highest_seq;
uint64_t evicted_batches;
uint64_t duplicates;
uint64_t rejected_batches;
};
#define STREAM_BUFFER_POSITION_PEERS 16
/* Одна линия индикатора — получатель; next обозначает позицию передачи в ETCP, а не подтверждённой доставки. */
struct stream_buffer_peer_position {
uint64_t peer; // ID получателя
uint64_t worst, best; // Минимальный/максимальный следующий seq его субпотоков
unsigned subflows; // Число действующих выдач получателю
};
struct stream_buffer_positions {
uint64_t first, head; // [first, head) — сохранённое окно; head — позиция после последней пачки
unsigned count, total; // Показанные/все получатели; показаны до 16 наиболее отставших
struct stream_buffer_peer_position peers[STREAM_BUFFER_POSITION_PEERS];
};
/* out заранее NULL; bytes=0 выбирает 32 МБ, меньший бюджет допускается для стенда.
* Должна помещаться хотя бы одна полная пачка n блоков; верхний предел — STREAM_BUFFER_BYTES. */
int stream_buffer_create(struct stream_buffer **out, const struct stream_descriptor *descriptor,
const uint8_t public_key[SC_PUBKEY_SIZE], size_t bytes);
void stream_buffer_destroy(struct stream_buffer *buffer);
/* Формат новой части проверяется до изменения кольца. Повтор неизменной части: STREAM_DUPLICATE.
* STREAM_EXPIRED не меняет более свежие данные; конфликт возвращает отрицательный код.
* Части ещё не подтверждены подписью источника и могут пересылаться до локального decode. */
int stream_buffer_put(struct stream_buffer *buffer, const uint8_t *wire, size_t length);
/* Сохранённая история содержит текущего владельца кольца; первый путь повтора остаётся неизменным. */
int stream_buffer_put_path(struct stream_buffer *buffer, const uint8_t *wire, size_t length, const struct stream_path *path);
int stream_buffer_path(struct stream_buffer *buffer, uint64_t seq, uint8_t subblock, struct stream_path *path);
/* Найти ближайший имеющийся блок >= seq для скачка через GAP; WAIT при отсутствии. */
int stream_buffer_next_seq(struct stream_buffer *buffer, uint64_t seq, uint8_t subblock, uint64_t *next);
int stream_buffer_next_decodable(struct stream_buffer *buffer, uint64_t seq, uint64_t *next);
/* seq=UINT64_MAX читает последний имеющийся блок именно запрошенного субпотока.
* WAIT — блок впереди головы/пустое кольцо; GAP — дыра; EXPIRED — вытеснен.
* При отсутствии блока выходные указатели/номер не меняются. */
int stream_buffer_read(struct stream_buffer *buffer, uint64_t seq, uint8_t subblock,
const uint8_t **wire, size_t *length, uint64_t *actual_seq);
int stream_buffer_range(struct stream_buffer *buffer, uint8_t subblock, uint64_t *first, uint64_t *last);
/* Восстановить пачку и проверить одну подпись; достаточно capacity >= действительной длины.
* После успеха фиксируется использованный набор k неизменяемых частей: повторное чтение не проверяет подпись заново
* и не доверяет поздним частям, которые не участвовали в подтверждённом восстановлении.
* STREAM_REJECTED означает неверную подпись/подписанные метаданные; такая пачка повторно не декодируется.
* Выход не меняется при WAIT/GAP/EXPIRED/REJECTED/ошибке; отсутствующие части в кольцо не добавляются. */
int stream_buffer_decode(struct stream_buffer *buffer, uint64_t seq, uint8_t *output, size_t capacity, size_t *length);
int stream_buffer_get_stats(const struct stream_buffer *buffer, struct stream_buffer_stats *stats);
#ifdef __cplusplus
}
#endif
#endif