call: continue bounded video flush without waiting for media ticks
This commit is contained in:
1 parent
ff51e30a55
commit
9886bc2fde
3 files changed
+167
-9
No files matched your search
@@ -16,12 +16,13 @@ struct video_tx_frame {
|
||||
struct call_video_frame frame;
|
||||
uint8_t *data;
|
||||
uint64_t started_tb;
|
||||
unsigned budget_yields;
|
||||
};
|
||||
struct call_video_transport {
|
||||
struct call_session *session;
|
||||
struct call_video_caps local, peer;
|
||||
struct call_video_ops ops;
|
||||
void *arg, *timer;
|
||||
void *arg, *timer, *flush_pending;
|
||||
struct call_video_receiver *receiver;
|
||||
struct video_tx_frame tx[VIDEO_TX_FRAMES];
|
||||
unsigned tx_count, tx_bytes;
|
||||
@@ -41,6 +42,8 @@ struct call_video_transport {
|
||||
uint64_t report_us;
|
||||
unsigned offered, accepted, held_busy, transport_busy, key_wait;
|
||||
int64_t max_audio_ahead;
|
||||
uint64_t flush_scheduled_us, flush_wait_max_us;
|
||||
unsigned flush_yields, flush_runs, flush_net_wait, flush_audio_wait;
|
||||
};
|
||||
|
||||
static struct call_session *active(struct UTUN_INSTANCE *inst, uint64_t id) {
|
||||
@@ -86,9 +89,16 @@ static void pop_tx(struct call_video_transport *v) {
|
||||
v->tx_bytes -= v->tx[0].frame.size; u_free(v->tx[0].data); --v->tx_count;
|
||||
memmove(v->tx, v->tx + 1, v->tx_count * sizeof(v->tx[0]));
|
||||
}
|
||||
/* call_soon остаётся в очереди с NULL callback до прохода uasync; v можно освободить сразу. */
|
||||
static void cancel_flush(struct call_video_transport *v) {
|
||||
if (v->flush_pending && uasync_call_soon_cancel(v->session->ctx->inst->ua, v->flush_pending) != ERR_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video flush cancel failed call=%llu", (unsigned long long)v->session->call_id);
|
||||
v->flush_pending = NULL; v->flush_scheduled_us = 0;
|
||||
}
|
||||
static void discard_tx(struct call_video_transport *v, const char *reason) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX discard call=%llu frames=%u bytes=%u reason=%s",
|
||||
(unsigned long long)v->session->call_id, v->tx_count, v->tx_bytes, reason);
|
||||
cancel_flush(v);
|
||||
while (v->tx_count) pop_tx(v);
|
||||
v->need_key = 1;
|
||||
if (v->ops.need_key) v->ops.need_key(v->arg);
|
||||
@@ -149,13 +159,16 @@ static void update_rate(struct call_video_transport *v) {
|
||||
if (now - v->report_us >= 1000000) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL,
|
||||
"video TX flow call=%llu offered=%u accepted=%u audio_busy=%u transport_busy=%u key_wait=%u "
|
||||
"audio_ahead_us=%lld held=%d queue=%u bytes=%u audio_pts=%lld queue_age_us=%llu tail_ahead_us=%lld",
|
||||
"audio_ahead_us=%lld held=%d queue=%u bytes=%u audio_pts=%lld queue_age_us=%llu tail_ahead_us=%lld "
|
||||
"flush_yields=%u flush_runs=%u flush_wait_max_us=%llu flush_net_wait=%u flush_audio_wait=%u",
|
||||
(unsigned long long)s->call_id, v->offered, v->accepted, v->held_busy, v->transport_busy, v->key_wait,
|
||||
(long long)v->max_audio_ahead, held, v->tx_count, v->tx_bytes,
|
||||
(long long)v->audio_end_pts,
|
||||
(unsigned long long)(v->tx_count ? (get_time_tb() - v->tx[0].started_tb) * 100 : 0),
|
||||
(long long)(v->tx_count && v->audio_end_pts >= 0 ? v->tx[v->tx_count - 1].frame.pts_us - v->audio_end_pts : 0));
|
||||
(long long)(v->tx_count && v->audio_end_pts >= 0 ? v->tx[v->tx_count - 1].frame.pts_us - v->audio_end_pts : 0),
|
||||
v->flush_yields, v->flush_runs, (unsigned long long)v->flush_wait_max_us, v->flush_net_wait, v->flush_audio_wait);
|
||||
v->report_us = now; v->offered = v->accepted = v->held_busy = v->transport_busy = v->key_wait = 0; v->max_audio_ahead = 0;
|
||||
v->flush_yields = v->flush_runs = v->flush_net_wait = v->flush_audio_wait = 0; v->flush_wait_max_us = 0;
|
||||
}
|
||||
struct call_video_rate_sample sample = {0};
|
||||
sample.now_us = now; sample.window_end_us = now / ROUTE_TX_SAMPLE_US * ROUTE_TX_SAMPLE_US;
|
||||
@@ -182,12 +195,25 @@ static void update_rate(struct call_video_transport *v) {
|
||||
v->ops.bitrate(v->arg, v->rate_format.generation, v->rate_request, result.target_bps);
|
||||
}
|
||||
}
|
||||
static void flush(struct call_video_transport *v);
|
||||
/* Снимаем handle до callback: повторный бюджет будет поставлен в следующий batch uasync. */
|
||||
static void continue_flush(void *arg) {
|
||||
struct call_video_transport *v = arg;
|
||||
v->flush_pending = NULL; v->flush_runs++;
|
||||
flush(v);
|
||||
}
|
||||
static void flush(struct call_video_transport *v) {
|
||||
if (v->flush_scheduled_us) {
|
||||
uint64_t wait = get_time_us() - v->flush_scheduled_us;
|
||||
if (wait > v->flush_wait_max_us) v->flush_wait_max_us = wait;
|
||||
}
|
||||
// Другой источник flush мог успеть раньше отложенного вызова: оставляем только одно продолжение.
|
||||
cancel_flush(v);
|
||||
if (!v->tx_count) return;
|
||||
struct call_session *s = v->session;
|
||||
struct ETCP_ROUTER_CONN *conn = NULL;
|
||||
uint8_t wire[CALL_VIDEO_HEADER_BYTES + CALL_VIDEO_FRAGMENT_BYTES];
|
||||
// Ограниченный burst одного вызова; повторяется по audio send и таймеру 50мс.
|
||||
// Ограниченный burst одного вызова. Только budget ставит немедленное продолжение; backpressure — нет.
|
||||
for (unsigned budget = 0; v->tx_count && budget < 16; budget++) {
|
||||
struct video_tx_frame *head = &v->tx[0];
|
||||
struct call_video_frame *f = &head->frame;
|
||||
@@ -195,15 +221,15 @@ static void flush(struct call_video_transport *v) {
|
||||
if (!v->have_peer || !call_video_format_supported(&v->local, &v->peer, &f->format)) {
|
||||
discard_tx(v, "capabilities_changed"); return;
|
||||
}
|
||||
if (v->acknowledged_revision != v->local_revision || f->pts_us > v->audio_end_pts) return;
|
||||
if (v->acknowledged_revision != v->local_revision || f->pts_us > v->audio_end_pts) { v->flush_audio_wait++; return; }
|
||||
if (!conn) conn = etcp_router_conn_get(s->ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_VIDEO);
|
||||
if (!conn) { discard_tx(v, "router_allocation"); return; }
|
||||
struct ETCP_ROUTER_TX_STATS net;
|
||||
if (!etcp_router_tx_stats(s->ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_VIDEO, get_time_us(), &net)
|
||||
&& media_bytes(net.queued_bytes + net.inflight_bytes, (uint64_t)net.queued_packets + net.inflight_packets)
|
||||
>= debt_limit(v, net.baseline_rtt_us)) return;
|
||||
>= debt_limit(v, net.baseline_rtt_us)) { v->flush_net_wait++; return; }
|
||||
if (!etcp_router_send_q_has_room(s->ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_VIDEO)
|
||||
|| queue_entry_count(conn->send_q) >= ROUTER_MAX_SEND_Q_PACKETS) return;
|
||||
|| queue_entry_count(conn->send_q) >= ROUTER_MAX_SEND_Q_PACKETS) { v->flush_net_wait++; return; }
|
||||
uint32_t remaining = f->size - f->offset;
|
||||
f->length = remaining < CALL_VIDEO_FRAGMENT_BYTES ? remaining : CALL_VIDEO_FRAGMENT_BYTES;
|
||||
f->data = head->data + f->offset;
|
||||
@@ -214,12 +240,19 @@ static void flush(struct call_video_transport *v) {
|
||||
if (f->offset == f->size) {
|
||||
if (f->pts_us % 1000000 < 34000)
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
|
||||
"video timing stage=send call=%llu frame=%llu pts=%lld boot_us=%llu bytes=%u queue_age_us=%llu",
|
||||
"video timing stage=send call=%llu frame=%llu pts=%lld boot_us=%llu bytes=%u queue_age_us=%llu budget_yields=%u",
|
||||
(unsigned long long)f->call_id, (unsigned long long)f->frame_id, (long long)f->pts_us,
|
||||
(unsigned long long)get_time_us(), f->size, (unsigned long long)((get_time_tb() - head->started_tb) * 100));
|
||||
(unsigned long long)get_time_us(), f->size, (unsigned long long)((get_time_tb() - head->started_tb) * 100),
|
||||
head->budget_yields);
|
||||
pop_tx(v);
|
||||
}
|
||||
}
|
||||
if (v->tx_count && v->acknowledged_revision == v->local_revision && v->tx[0].frame.pts_us <= v->audio_end_pts) {
|
||||
v->flush_pending = uasync_call_soon(s->ctx->inst->ua, v, continue_flush);
|
||||
if (v->flush_pending) {
|
||||
v->flush_scheduled_us = get_time_us(); v->flush_yields++; v->tx[0].budget_yields++;
|
||||
} else DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video flush continuation allocation failed call=%llu", (unsigned long long)s->call_id);
|
||||
}
|
||||
}
|
||||
static void tick(void *arg) {
|
||||
struct call_video_transport *v = arg;
|
||||
@@ -242,6 +275,7 @@ void call_video_transport_stop(struct call_session *s) {
|
||||
struct call_video_transport *v = s->video;
|
||||
if (!v) return;
|
||||
s->video = NULL;
|
||||
cancel_flush(v);
|
||||
if (v->timer) uasync_cancel_timeout(s->ctx->inst->ua, v->timer);
|
||||
call_video_receiver_destroy(v->receiver);
|
||||
while (v->tx_count) pop_tx(v);
|
||||
@@ -326,6 +360,7 @@ int call_video_send_frame(struct UTUN_INSTANCE *inst, const struct call_video_fr
|
||||
if (!data) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video TX allocation failed bytes=%u", f->size); v->need_key = 1; return -1; }
|
||||
memcpy(data, f->data, f->size);
|
||||
struct video_tx_frame *tail = &v->tx[v->tx_count++];
|
||||
tail->budget_yields = 0;
|
||||
tail->frame = *f; tail->frame.data = tail->data = data; tail->started_tb = get_time_tb(); v->tx_bytes += f->size;
|
||||
v->tx_last_id = f->frame_id; v->tx_last_pts = f->pts_us; v->tx_last_format = f->format; v->need_key = 0;
|
||||
v->accepted++; flush(v); return 1;
|
||||
|
||||
Reference in new issue
Block a user