|
|
|
|
@ -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; |
|
|
|
|
} |
|
|
|
|
|