/* Индекс медиапотока поверх общего RS-кольца: хранит только позиции/PTS, без копии кодированного потока. * Один uasync-поток; store принадлежит stream-session, cursor — конкретному локальному reader. * feed получает уже проверенные последовательные пачки; gap обозначает окончательно потерянный диапазон. * Независимые audio/video позиции читают оригинал из кольца. Рабочая память cursor — один H264 кадр, * один Opus-пакет и два scratch медиаблока. Курсоры ничего не закрепляют против вытеснения. * read возвращает заимствованный packet до следующего read/destroy; frontend копирует только ближайшие записи. * WAIT — материал ещё не готов/дальше lookahead. GAP/EXPIRED требуют новой точки KEY; END — весь материал выдан. */ #ifndef STREAM_MEDIA_BUFFER_H #define STREAM_MEDIA_BUFFER_H #include "stream_buffer.h" #include "stream_media.h" #ifdef __cplusplus extern "C" { #endif #define STREAM_MEDIA_INDEX_BYTES (4u * 1024u * 1024u) #define STREAM_MEDIA_READ_AHEAD_US 80000 #define STREAM_MEDIA_PREROLL_US 500000 #define STREAM_MEDIA_TRACK_VIDEO 1 #define STREAM_MEDIA_TRACK_AUDIO 2 struct stream_media_store; struct stream_media_cursor; enum stream_media_read_kind { STREAM_MEDIA_READ_VIDEO, STREAM_MEDIA_READ_AUDIO, STREAM_MEDIA_READ_END }; struct stream_media_packet { enum stream_media_read_kind kind; struct stream_media_format format; struct stream_media_video video; struct stream_media_audio audio; }; struct stream_media_status { int64_t ready_us, video_us, audio_us, start_us; uint64_t generation; int initialized, audio_enabled, finished, video_ended, audio_ended; }; int stream_media_store_create(struct stream_media_store **out, struct stream_buffer *buffer, const struct stream_descriptor *descriptor); void stream_media_store_destroy(struct stream_media_store *store); int stream_media_store_feed(struct stream_media_store *store, uint64_t seq, const uint8_t *data, size_t length); void stream_media_store_gap(struct stream_media_store *store, uint64_t first, uint64_t last); void stream_media_store_finish(struct stream_media_store *store); void stream_media_store_trim(struct stream_media_store *store); int stream_media_cursor_create(struct stream_media_cursor **out, struct stream_media_store *store, unsigned tracks); void stream_media_cursor_destroy(struct stream_media_cursor *cursor); void stream_media_cursor_reset(struct stream_media_cursor *cursor); int stream_media_cursor_status(struct stream_media_cursor *cursor, struct stream_media_status *status); /* position_us<0 — старт: прогреть аудио перед выбранным KEY. Затем отдавать только до position_us+lookahead_us. * При GAP/EXPIRED cursor сбрасывают отдельно: это позволяет frontend сначала обработать разрыв. * Бюджет одного вызова ограничен; WAIT при сборке длинного кадра допускает повтор сразу/по следующему событию. */ int stream_media_cursor_read(struct stream_media_cursor *cursor, int64_t position_us, int64_t video_ahead_us, int64_t audio_ahead_us, struct stream_media_packet *packet); #ifdef __cplusplus } #endif #endif