/* Android-очереди live media: attach/detach только в uasync, вне callbacks. * UI/кодировщики используют только session_id, никогда не сохраняют inst/ua. * Один publisher и независимые viewers, TX <=2MiB/128 records/250мс, RX — ограниченный батч на viewer. * Data принимает полные access units пяти кодеков uAV2 (KEY: config_length:4/config/frame) и Opus 48k mono/stereo/20ms. * pull передаёт u_malloc-буфер последовательных записей: * kind:1,flags:1,width:2,height:2,rotation:2,PTS_us:8,length:4,revision:4,codec:1,reserved:3,data. * kinds: video=1,audio=2,gap=3,opened=4,ended=5,error=6,video_end=7,audio_end=8; error.flags = stream_close_reason. * Конец трека следует за его media; GAP/error отменяют старые записи. Числа big-endian. * Медиаданные передаются через pull, не через общий чатовый event bus; полученный буфер освобождает caller через u_free. * Команды/снимки защищены bridge mutex, signal имеет свой mutex; callers используют API из frontend/media workers. * После detach команды получают отказ/пустой результат, read_request с допустимыми параметрами возвращает 0. */ #ifndef ANDROID_STREAM_BRIDGE_H #define ANDROID_STREAM_BRIDGE_H #include #include #include "../../../src/stream/stream_buffer.h" struct UTUN_INSTANCE; struct stream_media_format; struct stream_media_status; struct stream_viewer_telemetry; struct android_stream_signal; #define ANDROID_STREAM_HEADER_SIZE 28 struct android_stream_tx_stats { /* ID сеанса, время снимка в мкс; video/audio/stream — очереди publisher, transport/inflight/acked_1s — всего instance. * total_bytes — сумма video/audio/stream/transport/inflight; байты ACK за 1с в неё не входят. */ uint64_t id, sample_us, video_bytes, audio_bytes, stream_bytes, transport_bytes, inflight_bytes, acked_1s, total_bytes; /* Число соединений в общем ETCP-снимке. */ unsigned connections; /* Позиции получателей в кольце, обновляются раз в 500мс. */ struct stream_buffer_positions buffer; }; /* Снимок publisher обновляется в uasync каждые 100мс; getter копирует под mutex. 0/-1, ошибка не меняет stats. */ int android_stream_tx_snapshot(uint64_t id, struct android_stream_tx_stats *stats); /* 0 attached, -1 invalid/busy: нельзя передать singleton другому ядру до detach старого. */ int android_stream_attach(struct UTUN_INSTANCE *inst); /* Тот же inst: отменить watch, закрыть managers/сеансы и снять привязку; повтор допустим. */ void android_stream_detach(struct UTUN_INSTANCE *inst); /* id/group ненулевые. source=0 — publisher: width/height=1..4096, rotation=0/90/180/270, codec=H264..H266. * source!=0 — viewer с обязательным expected_epoch; размеры/rotation игнорируются. codec проверяется в обоих режимах. * audio включает Opus stereo у publisher либо получение аудио viewer. 0 — команда принята, -1 — отказ; результат через pull. * Publisher ID занят до получения ended/error через pull; viewer ID — до обработки stop в uasync. */ int android_stream_open(uint64_t id, uint64_t group, uint64_t source, unsigned width, unsigned height, unsigned rotation, int audio, uint64_t expected_epoch, unsigned codec); /* Наблюдение канала через CHAT_EVT_STREAM_SOURCES; group=0 снимает его. 0 — поставлено, -1 — отказ; * несколько команд до исполнения сливаются в последнюю. */ int android_stream_watch(uint64_t group); /* Publisher: 0 — команда mute/unmute принята, -1 — нет сеанса/аудиотрека; codec/эпоха не меняются. * Producer сам заглушает PCM; эта команда только публикует состояние. */ int android_stream_set_audio_enabled(uint64_t id, int enabled); /* Publisher: pts — мкс <=INT64_MAX, строго растёт отдельно по трекам; data копируется на вызове. * Видео требует format с revision/размером/поворотом; новая revision принимается атомарно с KEY. * Для аудио format/key игнорируются. 1 — принято в очередь, 0 — drop (video: запросить KEY), -1 — отказ. */ int android_stream_feed(uint64_t id, int video, uint64_t pts, int key, const uint8_t *data, size_t length, const struct stream_media_format *format); /* Асинхронный stop: publisher дренирует очередь и держит FIN 2с; viewer снимает reader и notify. * Уже выданный pull-буфер остаётся у caller. Старый/нулевой ID допустим. */ void android_stream_stop(uint64_t id); /* Передать owned батч; length обязателен, NULL/length=0 — сейчас нет данных либо ошибка. * Publisher выдаёт opened/ended/error без demand; viewer — также media по read_request. */ uint8_t *android_stream_pull(uint64_t id, size_t *length); /* Один батч на запрос: до video кадров и records записей, звук до position_us+audio_ahead_us. * video=0..records, records=1..16; audio_ahead_us=-1 выключает звук, иначе 0..60000000; position_us<0 — прогрев до KEY. * Повторный demand заменяет прежний. 0 — параметры допустимы (даже для неизвестного ID/после detach), -1 — неверные параметры. * CLOSED/END доступны pull после закрытия cursor; указатели кольца остаются в uasync. */ int android_stream_read_request(uint64_t id, int64_t position_us, unsigned video, int64_t audio_ahead_us, unsigned records); /* Viewer-снимки: 0 — скопировано, -1 — неверные аргументы/ID; ошибка не меняет выход. */ int android_stream_get_status(uint64_t id, struct stream_media_status *status); int android_stream_get_telemetry(uint64_t id, struct stream_viewer_telemetry *telemetry); /* Viewer: очистить старый батч и искать ближайший KEY; 0 — принято, -1 — отказ. */ int android_stream_resync(uint64_t id); /* Worker владеет signal; create привязывает его к открытому viewer. Готовность/stop/detach будят wait. * signal также будит worker после расходования PCM. Уведомления сливаются, но не теряются до wait. * create возвращает NULL при ошибке. На viewer допускается один signal/notify. * destroy — только после stream_stop или detach и остановки всех внешних signal/wait callers. */ struct android_stream_signal *android_stream_signal_create(uint64_t id); void android_stream_signal_notify(struct android_stream_signal *signal); /* 1 — уведомление, 0 — deadline, -1 — ошибка; timeout_ms 0..50, ожидание не держит mutex ядра/bridge. */ int android_stream_signal_wait(struct android_stream_signal *signal, unsigned timeout_ms); void android_stream_signal_destroy(struct android_stream_signal *signal); #endif