Browse Source

Clarify stream bootstrap and log bounded publisher drain

master
evgeny 4 days ago
parent
commit
84472cc276
  1. 8
      doc/stream.txt
  2. 18
      src/stream/stream_publisher.c

8
doc/stream.txt

@ -884,6 +884,7 @@ Android:
Publisher сохраняет конечный буфер ещё 2s для сетевой выдачи, затем снимает своё владение.
При наличии локального reader завершённый источник и кольцо живут до его close, позволяя дочитать FIN.
Это ограниченный drain, не подтверждение воспроизведения всеми viewers. Abort/core restart очищает очередь сразу.
Завершение логирует срок drain, оставшиеся RS-байты выдач и локальных readers; RS=0 не означает подтверждение доставки ETCP.
Поздние команды старого session ID не меняют новый запуск.
- Звонок, рация и voice recording не начинают новый capture во время трансляции;
трансляция также отвергается при активном звонке/рации/voice recording.
@ -1057,9 +1058,10 @@ test_stream_publish проверяет реальные encoder/decoder H.264/H.
Сам старт публикации не запускает приём на суперузлах. Новая подписка сразу запускает INFO-пробы и отдельный
дедлайн 100 мс. Если пригодного ответа ещё нет, запрашивается один суперузел. Первый валидный INFO с разрешённой
выдачей выигрывает: порядок приёма фиксируется локальным счётчиком, независимо от RTT и порядка списка кандидатов.
Проигравшие пробы и неподключённая помощь отменяются. DATA сначала открывается только у победителя; проверенный
первый блок фиксирует общий старт всех остальных субпотоков. До первого восстановления FEC выдачи открываются у победителя,
затем действует обычный оптимизатор. Пустой доступный источник не требует помощи, пока он не публикует данные.
DATA сначала открывается только у победителя; проверенный первый блок фиксирует общий старт всех остальных субпотоков.
Другие INFO-пробы продолжаются: после фиксации старта недостающие части можно брать у любых подходящих поставщиков,
даже до первого восстановления FEC. Победитель гонки не обязан иметь все k частей. Ненужная помощь отменяется.
Пустой доступный источник не требует помощи, пока он не публикует данные.
Для восстановления работающего потока сохраняются 5 секунд недостатка действующих субпотоков для k=n-r.
Для остановившихся выдач требуется также свидетельство более свежих данных;
пауза источника, отсутствие полной FEC-защиты и недобор всех n сами по себе не запускают помощь.

18
src/stream/stream_publisher.c

@ -12,6 +12,7 @@
#define TX_LIMIT (2u * 1024u * 1024u)
#define TX_RECORDS 128
#define MEDIA_AGE_US 250000u
#define PUBLISHER_DRAIN_US 2000000u
struct publisher_record {
struct publisher_record *next;
@ -66,6 +67,15 @@ static int publisher_schedule(struct stream_publisher *p) {
/* Только uasync под lock; терминальный снимок остаётся доступен до следующего open. */
static void publisher_close(struct stream_publisher *p, int reason) {
struct stream_info info; unsigned readers = 0; uint64_t pending = 0, drained = 0;
if (p->handle && stream_handle_active(p->handle) && stream_get_info(p->handle, &info) == STREAM_OK) {
readers = info.owners - 1; pending = stream_network_pending_bytes(p->inst, &info.descriptor);
}
if (p->status.state == STREAM_PUBLISHER_DRAINING) drained = get_time_us() - (p->drain_until - PUBLISHER_DRAIN_US);
DEBUG_INFO(DEBUG_CATEGORY_STREAM,
"publisher closing id=%llu epoch=%llx reason=%d state=%d drain_ms=%llu pending_RS=%llu readers=%u video=%zu audio=%zu",
(unsigned long long)p->status.id, (unsigned long long)p->status.epoch, reason, p->status.state,
(unsigned long long)(drained / 1000), (unsigned long long)pending, readers, p->video.bytes, p->audio.bytes);
if (p->timer) uasync_cancel_timeout(p->inst->ua, p->timer);
if (p->stats_timer) uasync_cancel_timeout(p->inst->ua, p->stats_timer);
p->timer = p->stats_timer = NULL;
@ -74,8 +84,6 @@ static void publisher_close(struct stream_publisher *p, int reason) {
p->status.state = reason ? STREAM_PUBLISHER_FAILED : STREAM_PUBLISHER_ENDED; p->status.reason = reason;
p->status.video_bytes = p->status.audio_bytes = p->status.total_bytes = 0;
p->status.stream_bytes = p->status.transport_bytes = p->status.inflight_bytes = p->status.acked_1s = 0;
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "publisher closed id=%llu epoch=%llx reason=%d",
(unsigned long long)p->status.id, (unsigned long long)p->status.epoch, reason);
}
/* Один снимок всех соединений, без повторного учёта input_send_q внутри inflight. */
@ -193,7 +201,11 @@ static void publisher_process(void *arg) {
}
if (p->handle && p->stopping && !p->video.head && !p->audio.head) {
if (stream_publish_finish(p->handle) != STREAM_OK) publisher_close(p, STREAM_CLOSE_FAILED);
else { p->status.state = STREAM_PUBLISHER_DRAINING; p->drain_until = now + 2000000; }
else {
p->status.state = STREAM_PUBLISHER_DRAINING; p->drain_until = now + PUBLISHER_DRAIN_US;
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "publisher FIN id=%llu epoch=%llx drain_ms=%u",
(unsigned long long)p->status.id, (unsigned long long)p->status.epoch, PUBLISHER_DRAIN_US / 1000);
}
}
}
if (p->handle && p->status.state == STREAM_PUBLISHER_DRAINING && now >= p->drain_until) publisher_close(p, 0);

Loading…
Cancel
Save