Browse Source

Measure call video throughput and audio gating losses

master
evgeny 4 days ago
parent
commit
572392511b
  1. 22
      src/call/call_video_transport.c
  2. 24
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt

22
src/call/call_video_transport.c

@ -31,6 +31,9 @@ struct call_video_transport {
int rate_pending, admit; int rate_pending, admit;
uint64_t received_generation, decode_generation; uint64_t received_generation, decode_generation;
int decode_result, decode_pending, tx_rejected; int decode_result, decode_pending, tx_rejected;
uint64_t report_us;
unsigned offered, accepted, held_busy, transport_busy, key_wait;
int64_t max_audio_ahead;
}; };
static struct call_session *active(struct UTUN_INSTANCE *inst, uint64_t id) { static struct call_session *active(struct UTUN_INSTANCE *inst, uint64_t id) {
@ -128,6 +131,15 @@ static void update_rate(struct call_video_transport *v) {
} }
struct route_tx_window produced = {0}; route_tx_meter_window(&v->production, now, &produced); struct route_tx_window produced = {0}; route_tx_meter_window(&v->production, now, &produced);
int held = v->data && v->tx.pts_us > v->audio_end_pts; int held = v->data && v->tx.pts_us > v->audio_end_pts;
if (v->audio_end_pts >= 0 && held && v->tx.pts_us - v->audio_end_pts > v->max_audio_ahead)
v->max_audio_ahead = v->tx.pts_us - v->audio_end_pts;
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",
(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->report_us = now; v->offered = v->accepted = v->held_busy = v->transport_busy = v->key_wait = 0; v->max_audio_ahead = 0;
}
struct call_video_rate_sample sample = {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; sample.now_us = now; sample.window_end_us = now / ROUTE_TX_SAMPLE_US * ROUTE_TX_SAMPLE_US;
sample.delivered_bytes = media_bytes(net.window.bytes, net.window.packets); sample.produced_bytes = produced.bytes; sample.delivered_bytes = media_bytes(net.window.bytes, net.window.packets); sample.produced_bytes = produced.bytes;
@ -263,7 +275,11 @@ int call_video_send_frame(struct UTUN_INSTANCE *inst, const struct call_video_fr
} }
if (!v->admit) { v->need_key = 1; return 0; } if (!v->admit) { v->need_key = 1; return 0; }
} }
if (v->data) { v->need_key = 1; return 0; } v->offered++;
if (v->data) {
if (v->tx.pts_us > v->audio_end_pts) v->held_busy++; else v->transport_busy++;
v->need_key = 1; return 0;
}
struct ETCP_ROUTER_TX_STATS net; struct ETCP_ROUTER_TX_STATS net;
if (etcp_router_tx_stats(inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_VIDEO, get_time_us(), &net) == 0) { if (etcp_router_tx_stats(inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_VIDEO, get_time_us(), &net) == 0) {
uint64_t debt = media_bytes(net.queued_bytes + net.inflight_bytes, (uint64_t)net.queued_packets + net.inflight_packets); uint64_t debt = media_bytes(net.queued_bytes + net.inflight_bytes, (uint64_t)net.queued_packets + net.inflight_packets);
@ -274,12 +290,12 @@ int call_video_send_frame(struct UTUN_INSTANCE *inst, const struct call_video_fr
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video TX format changed without new generation"); return -1; DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video TX format changed without new generation"); return -1;
} }
if (changed || (v->tx_last_id && f->frame_id != v->tx_last_id + 1)) v->need_key = 1; if (changed || (v->tx_last_id && f->frame_id != v->tx_last_id + 1)) v->need_key = 1;
if (v->need_key && !(f->flags & CALL_VIDEO_KEY)) return 0; if (v->need_key && !(f->flags & CALL_VIDEO_KEY)) { v->key_wait++; return 0; }
v->data = u_malloc(f->size); v->data = u_malloc(f->size);
if (!v->data) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video TX allocation failed bytes=%u", f->size); return -1; } if (!v->data) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video TX allocation failed bytes=%u", f->size); return -1; }
memcpy(v->data, f->data, f->size); v->tx = *f; v->tx.data = v->data; memcpy(v->data, f->data, f->size); v->tx = *f; v->tx.data = v->data;
v->tx_started_tb = get_time_tb(); v->tx_last_id = f->frame_id; v->tx_last_pts = f->pts_us; v->need_key = 0; v->tx_started_tb = get_time_tb(); v->tx_last_id = f->frame_id; v->tx_last_pts = f->pts_us; v->need_key = 0;
flush(v); return 1; v->accepted++; flush(v); return 1;
} }
int call_video_configure_rate(struct UTUN_INSTANCE *inst, uint64_t id, const struct call_video_format *format, int call_video_configure_rate(struct UTUN_INSTANCE *inst, uint64_t id, const struct call_video_format *format,
uint32_t applied, uint32_t maximum) { uint32_t applied, uint32_t maximum) {

24
tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt

@ -31,6 +31,8 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
var held = -1; var heldPts = -1L; var inflight = 0; var draining = false var held = -1; var heldPts = -1L; var inflight = 0; var draining = false
var sourceEpoch = 0L; var lastOutput = -1L; var lastProgress = System.nanoTime() var sourceEpoch = 0L; var lastOutput = -1L; var lastProgress = System.nanoTime()
var reportedGeneration = 0L; var rejectedGeneration = 0L var reportedGeneration = 0L; var rejectedGeneration = 0L
var reportAt = System.nanoTime(); var pulled = 0; var queued = 0; var decoded = 0; var rendered = 0; var late = 0
var maxAheadUs = 0L; var maxLateUs = 0L; var invalidClock = 0
val clock = LongArray(8); val info = MediaCodec.BufferInfo() val clock = LongArray(8); val info = MediaCodec.BufferInfo()
fun close() { fun close() {
try { decoder?.stop() } catch (e: Exception) { LogManager.addLog("WARN", "call", "video decoder stop: ${e.message}") } try { decoder?.stop() } catch (e: Exception) { LogManager.addLog("WARN", "call", "video decoder stop: ${e.message}") }
@ -48,6 +50,10 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
LogManager.addLog("INFO", "call", "video output attached=${surface != null} call=$id") LogManager.addLog("INFO", "call", "video output attached=${surface != null} call=$id")
} }
val valid = NativeLib.callAudioClock(id, now / 1000, clock) && clock[5] != 0L val valid = NativeLib.callAudioClock(id, now / 1000, clock) && clock[5] != 0L
if (!valid) invalidClock++
if (held >= 0 && valid) {
maxAheadUs = maxOf(maxAheadUs, heldPts - clock[4]); maxLateUs = maxOf(maxLateUs, clock[4] - heldPts)
}
if (valid && sourceEpoch != clock[1]) { if (valid && sourceEpoch != clock[1]) {
if (sourceEpoch != 0L) LogManager.addLog("DEBUG", "call", if (sourceEpoch != 0L) LogManager.addLog("DEBUG", "call",
"video audio clock epoch $sourceEpoch -> ${clock[1]}; decoder retained generation=${format?.generation}") "video audio clock epoch $sourceEpoch -> ${clock[1]}; decoder retained generation=${format?.generation}")
@ -57,9 +63,10 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
val render = heldPts >= clock[4] - 500_000 val render = heldPts >= clock[4] - 500_000
decoder!!.releaseOutputBuffer(held, render); held = -1 decoder!!.releaseOutputBuffer(held, render); held = -1
if (render) { if (render) {
rendered++
if (displayed.value != format) LogManager.addLog("INFO", "call", "video visible call=$id format=$format") if (displayed.value != format) LogManager.addLog("INFO", "call", "video visible call=$id format=$format")
displayed.value = format; lastRendered = now displayed.value = format; lastRendered = now
} } else late++
lastProgress = now lastProgress = now
} }
if (decoder != null && held < 0) { if (decoder != null && held < 0) {
@ -75,10 +82,13 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
} } } }
lastOutput = info.presentationTimeUs; inflight = (inflight - 1).coerceAtLeast(0) lastOutput = info.presentationTimeUs; inflight = (inflight - 1).coerceAtLeast(0)
held = out; heldPts = info.presentationTimeUs; lastProgress = now held = out; heldPts = info.presentationTimeUs; lastProgress = now
decoded++
} }
} }
} }
if (pending == null && held < 0 && inflight < 8) pending = CallVideoNative.pull(id, meta) if (pending == null && held < 0 && inflight < 8) {
pending = CallVideoNative.pull(id, meta); if (pending != null) pulled++
}
val data = pending val data = pending
if (data != null && surface?.isValid == true && held < 0) { if (data != null && surface?.isValid == true && held < 0) {
val next = CallVideoFormat.from(meta) val next = CallVideoFormat.from(meta)
@ -109,7 +119,7 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
} else data } else data
val buffer = checkNotNull(decoder!!.getInputBuffer(index)); check(bytes.size <= buffer.capacity()) val buffer = checkNotNull(decoder!!.getInputBuffer(index)); check(bytes.size <= buffer.capacity())
buffer.put(bytes); decoder!!.queueInputBuffer(index, 0, bytes.size, meta[10], 0) buffer.put(bytes); decoder!!.queueInputBuffer(index, 0, bytes.size, meta[10], 0)
pending = null; inflight++ pending = null; inflight++; queued++
} }
} }
} else if (data != null && surface == null) { pending = null; CallVideoNative.requestKey(id) } } else if (data != null && surface == null) { pending = null; CallVideoNative.requestKey(id) }
@ -118,6 +128,14 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
displayed.value = null; lastRendered = 0 displayed.value = null; lastRendered = 0
LogManager.addLog("INFO", "call", "video hidden: no rendered frames for 5s call=$id") LogManager.addLog("INFO", "call", "video hidden: no rendered frames for 5s call=$id")
} }
if (now - reportAt >= 1_000_000_000) {
LogManager.addLog("DEBUG", "call", "video playback call=$id intervalMs=${(now - reportAt) / 1_000_000} " +
"pulled=$pulled queued=$queued decoded=$decoded rendered=$rendered late=$late inflight=$inflight " +
"pending=${pending != null} held=$held aheadUs=$maxAheadUs lateUs=$maxLateUs " +
"invalidClock=$invalidClock audioPts=${clock[4]} videoPts=$heldPts")
reportAt = now; pulled = 0; queued = 0; decoded = 0; rendered = 0; late = 0
maxAheadUs = 0; maxLateUs = 0; invalidClock = 0
}
} catch (e: Exception) { } catch (e: Exception) {
LogManager.addLog("ERROR", "call", "video receive call=$id: ${e.message}") LogManager.addLog("ERROR", "call", "video receive call=$id: ${e.message}")
val generation = if (pending != null) meta[0] else format?.generation ?: 0 val generation = if (pending != null) meta[0] else format?.generation ?: 0

Loading…
Cancel
Save