From 8b6c72b7040585864682a4a4fdc7723683093c1d Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 01:08:20 +0200 Subject: [PATCH] Publish bounded 100 ms send telemetry from the Android media actor --- .../libutun_lite/stream_bridge.c | 54 +++++++++++++++++-- .../libutun_lite/stream_bridge.h | 8 ++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/tools/chatgui-android/libutun_lite/stream_bridge.c b/tools/chatgui-android/libutun_lite/stream_bridge.c index cca1adc8..00ae5d07 100644 --- a/tools/chatgui-android/libutun_lite/stream_bridge.c +++ b/tools/chatgui-android/libutun_lite/stream_bridge.c @@ -9,6 +9,7 @@ #include "../../../src/stream/stream.h" #include "../../../src/stream/stream_member.h" #include "../../../src/stream/stream_media.h" +#include "../../../src/stream/stream_network.h" #include "stream_bridge.h" #define TX_LIMIT (2u * 1024u * 1024u) @@ -38,6 +39,9 @@ static pthread_mutex_t media_lock = PTHREAD_MUTEX_INITIALIZER; static struct UTUN_INSTANCE *media_inst; static struct posted_task *media_post; static void *media_timer; +static void *stats_timer; +static struct android_stream_tx_stats tx_stats; +static uint64_t stats_report_us; static struct android_media_session publisher, viewer; static struct media_queue video_queue, audio_queue, output_queue; static struct stream_media_status viewer_status; @@ -47,6 +51,41 @@ static int watch_pending; static void media_process(void *arg); static void media_close(struct android_media_session *s, int kind); +static void media_stats(void *arg) { + (void)arg; pthread_mutex_lock(&media_lock); stats_timer = NULL; + if (media_inst && publisher.handle && stream_handle_active(publisher.handle)) { + uint64_t now = get_time_us(); struct etcp_tx_snapshot transport; struct stream_info info; + if (etcp_tx_get_snapshot(media_inst, now, &transport) == 0 && stream_get_info(publisher.handle, &info) == STREAM_OK) { + tx_stats = (struct android_stream_tx_stats){.id = publisher.id, .sample_us = now, + .video_bytes = video_queue.bytes - (video_queue.head ? video_queue.head->offset : 0), + .audio_bytes = audio_queue.bytes, .stream_bytes = stream_network_pending_bytes(media_inst, &info.descriptor), + .transport_bytes = transport.queued_bytes, .inflight_bytes = transport.inflight_bytes, + .acked_1s = transport.acked_1s, .connections = transport.connections}; + tx_stats.total_bytes = tx_stats.video_bytes + tx_stats.audio_bytes + tx_stats.stream_bytes + + tx_stats.transport_bytes + tx_stats.inflight_bytes; + if (now - stats_report_us >= 1000000) { + stats_report_us = now; + DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "TX id=%llu video=%llu audio=%llu RS=%llu ETCP=%llu inflight=%llu total=%llu ACK_1s=%llu conns=%u", + (unsigned long long)tx_stats.id, (unsigned long long)tx_stats.video_bytes, (unsigned long long)tx_stats.audio_bytes, + (unsigned long long)tx_stats.stream_bytes, (unsigned long long)tx_stats.transport_bytes, + (unsigned long long)tx_stats.inflight_bytes, (unsigned long long)tx_stats.total_bytes, + (unsigned long long)tx_stats.acked_1s, tx_stats.connections); + } + } + stats_timer = uasync_set_timeout(media_inst->ua, 1000, NULL, media_stats, "stream_tx_stats"); + if (!stats_timer) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "TX stats timer allocation failed"); + } + pthread_mutex_unlock(&media_lock); +} + +int android_stream_tx_snapshot(uint64_t id, struct android_stream_tx_stats *out) { + if (!out) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "TX stats output missing"); return -1; } + pthread_mutex_lock(&media_lock); + int result = media_inst && publisher.id == id && tx_stats.id == id ? 0 : -1; + if (!result) *out = tx_stats; + pthread_mutex_unlock(&media_lock); return result; +} + /* Очереди обмена pthread: LL_QUEUE принадлежит uasync и сюда не подходит. */ static void queue_append(struct media_queue *q, struct media_record *record) { record->next = NULL; @@ -110,6 +149,7 @@ static void media_output(uint64_t id, int kind, const struct stream_media_format if (format) { stream_write_be(p + 2, format->width, 2); stream_write_be(p + 4, format->height, 2); stream_write_be(p + 6, format->rotation, 2); stream_write_be(p + 20, format->revision, 4); + p[24] = format->video_codec; } stream_write_be(p + 8, pts, 8); stream_write_be(p + 16, size, 4); if (size) memcpy(p + ANDROID_STREAM_HEADER_SIZE, data, size); @@ -147,7 +187,10 @@ static void media_event(struct UTUN_INSTANCE *inst, const struct stream_event *e static void media_close(struct android_media_session *s, int kind) { if (kind) media_output(s->id, kind, NULL, 0, kind == 6 ? s->reason : 0, NULL, 0); if (s->handle) stream_close(s->handle); - if (s == &publisher) { queue_clear(&video_queue); queue_clear(&audio_queue); } + if (s == &publisher) { + if (stats_timer) { uasync_cancel_timeout(media_inst->ua, stats_timer); stats_timer = NULL; } + memset(&tx_stats, 0, sizeof(tx_stats)); queue_clear(&video_queue); queue_clear(&audio_queue); + } memset(s, 0, sizeof(*s)); } @@ -237,6 +280,10 @@ static void media_process(void *arg) { media_close(s, 6); } else { if (s->source) { viewer_status_id = s->id; memset(&viewer_status, 0, sizeof(viewer_status)); s->want_read = 1; } + else { + stats_timer = uasync_set_timeout(media_inst->ua, 1000, NULL, media_stats, "stream_tx_stats"); + if (!stats_timer) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "TX stats start timer allocation failed"); + } media_output(s->id, 4, NULL, 0, 0, NULL, 0); DEBUG_INFO(DEBUG_CATEGORY_STREAM, "Android stream opened id=%llu publisher=%d group=%llx source=%llx", (unsigned long long)s->id, !s->source, (unsigned long long)s->group, (unsigned long long)s->source); @@ -386,7 +433,8 @@ int android_stream_feed(uint64_t id, int video, uint64_t pts, int key, const uin int reconfigure = 0; if (video) { if (!format || !format->revision || !format->width || format->width > 4096 || !format->height || format->height > 4096 - || format->rotation > 270 || format->rotation % 90 || format->video_codec != STREAM_MEDIA_H264 + || format->rotation > 270 || format->rotation % 90 + || format->video_codec < STREAM_MEDIA_H264 || format->video_codec > STREAM_MEDIA_H266 || format->audio_codec != publisher.format.audio_codec) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android video feed invalid configuration id=%llu", (unsigned long long)id); goto done; } @@ -396,7 +444,7 @@ int android_stream_feed(uint64_t id, int video, uint64_t pts, int key, const uin } reconfigure = format->revision > publisher.format.revision; if (reconfigure && !key) { DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "Android new revision=%u waits KEY", format->revision); result = 0; goto done; } - if (!reconfigure && (format->width != publisher.format.width || format->height != publisher.format.height + if (!reconfigure && (format->video_codec != publisher.format.video_codec || format->width != publisher.format.width || format->height != publisher.format.height || format->rotation != publisher.format.rotation)) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android video configuration changed without revision=%u", format->revision); goto done; } diff --git a/tools/chatgui-android/libutun_lite/stream_bridge.h b/tools/chatgui-android/libutun_lite/stream_bridge.h index f816a5ca..72472eaf 100644 --- a/tools/chatgui-android/libutun_lite/stream_bridge.h +++ b/tools/chatgui-android/libutun_lite/stream_bridge.h @@ -14,7 +14,13 @@ struct UTUN_INSTANCE; struct stream_media_format; struct stream_media_status; -#define ANDROID_STREAM_HEADER_SIZE 24 +#define ANDROID_STREAM_HEADER_SIZE 28 +struct android_stream_tx_stats { + uint64_t id, sample_us, video_bytes, audio_bytes, stream_bytes, transport_bytes, inflight_bytes, acked_1s, total_bytes; + unsigned connections; +}; +/* Снимок обновляется в uasync каждые 100ms; getter копирует его под bridge mutex. */ +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); void android_stream_detach(struct UTUN_INSTANCE *inst);