|
|
|
|
@ -10,13 +10,11 @@
|
|
|
|
|
#include "../../../src/stream/stream_member.h" |
|
|
|
|
#include "../../../src/stream/stream_media.h" |
|
|
|
|
#include "../../../src/stream/stream_network.h" |
|
|
|
|
#include "../../../src/stream/stream_publisher.h" |
|
|
|
|
#include "stream_bridge.h" |
|
|
|
|
|
|
|
|
|
#define TX_LIMIT (2u * 1024u * 1024u) |
|
|
|
|
#define RX_LIMIT (4u * 1024u * 1024u) |
|
|
|
|
#define TX_RECORDS 128 |
|
|
|
|
#define RX_RECORDS 256 |
|
|
|
|
#define MEDIA_AGE_US 250000u |
|
|
|
|
|
|
|
|
|
struct media_record { |
|
|
|
|
struct media_record *next; |
|
|
|
|
@ -39,11 +37,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 stream_publisher *transmitter; |
|
|
|
|
static struct android_media_session publisher, viewer; |
|
|
|
|
static struct media_queue video_queue, audio_queue, output_queue; |
|
|
|
|
static struct media_queue output_queue; |
|
|
|
|
static struct stream_media_status viewer_status; |
|
|
|
|
static uint64_t viewer_status_id; |
|
|
|
|
static uint64_t watch_group; |
|
|
|
|
@ -51,38 +47,13 @@ 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; |
|
|
|
|
struct stream_publisher_status status; |
|
|
|
|
int result = stream_publisher_snapshot(transmitter, id, &status); |
|
|
|
|
if (!result) *out = (struct android_stream_tx_stats){status.id, status.sample_us, status.video_bytes, status.audio_bytes, |
|
|
|
|
status.stream_bytes, status.transport_bytes, status.inflight_bytes, status.acked_1s, status.total_bytes, status.connections}; |
|
|
|
|
pthread_mutex_unlock(&media_lock); return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -187,49 +158,9 @@ 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) { |
|
|
|
|
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)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Сформировать ограниченную пачку: аудио целиком, видео с продолжениями и несколькими началами. */ |
|
|
|
|
static int media_publish(void) { |
|
|
|
|
struct stream_media_block block = {.format = publisher.format}; |
|
|
|
|
uint8_t wire[STREAM_DEFAULT_INPUT_CAPACITY]; |
|
|
|
|
size_t capacity = stream_publish_capacity(publisher.handle), used = STREAM_MEDIA_HEADER_SIZE; |
|
|
|
|
if (capacity > sizeof(wire) || capacity < 256) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android publisher invalid capacity=%zu", capacity); return STREAM_ERR_STATE; |
|
|
|
|
} |
|
|
|
|
struct media_record *a = audio_queue.head; |
|
|
|
|
while (a && block.audio_count < 8 && used + STREAM_MEDIA_AUDIO_SIZE + a->length + STREAM_MEDIA_VIDEO_SIZE < capacity) { |
|
|
|
|
block.audio[block.audio_count++] = (struct stream_media_audio){a->pts, (uint32_t)a->length, 960, a->data}; |
|
|
|
|
used += STREAM_MEDIA_AUDIO_SIZE + a->length; a = a->next; |
|
|
|
|
} |
|
|
|
|
struct media_record *v = video_queue.head; |
|
|
|
|
while (v && block.video_count < STREAM_MEDIA_MAX_ENTRIES && used + STREAM_MEDIA_VIDEO_SIZE < capacity) { |
|
|
|
|
size_t take = capacity - used - STREAM_MEDIA_VIDEO_SIZE; |
|
|
|
|
if (take > v->length - v->offset) take = v->length - v->offset; |
|
|
|
|
block.video[block.video_count++] = (struct stream_media_video){v->frame_id, v->pts, (uint32_t)v->length, |
|
|
|
|
(uint32_t)v->offset, (uint32_t)take, (uint8_t)v->key, v->data + v->offset}; |
|
|
|
|
used += STREAM_MEDIA_VIDEO_SIZE + take; |
|
|
|
|
if (take < v->length - v->offset) break; |
|
|
|
|
v = v->next; |
|
|
|
|
} |
|
|
|
|
if (!block.video_count && !block.audio_count) return STREAM_WAIT; |
|
|
|
|
size_t length = 0; |
|
|
|
|
int result = stream_media_encode(&block, wire, capacity, &length); |
|
|
|
|
if (result == STREAM_OK) result = stream_publish_block(publisher.handle, wire, length); |
|
|
|
|
if (result != STREAM_OK) return result; |
|
|
|
|
for (unsigned i = 0; i < block.audio_count; i++) u_free(queue_take(&audio_queue)); |
|
|
|
|
for (unsigned i = 0; i < block.video_count; i++) { |
|
|
|
|
video_queue.head->offset += block.video[i].length; |
|
|
|
|
if (video_queue.head->offset == video_queue.head->length) u_free(queue_take(&video_queue)); |
|
|
|
|
} |
|
|
|
|
return STREAM_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Один actor обслуживает команды и не больше двух FEC-пачек за вызов; idle не будит мобильное ядро. */ |
|
|
|
|
static void media_process(void *arg) { |
|
|
|
|
(void)arg; |
|
|
|
|
@ -241,8 +172,8 @@ static void media_process(void *arg) {
|
|
|
|
|
if (stream_watch_sources(media_inst, watch_group) != STREAM_OK) |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android stream source watch failed group=%llx", (unsigned long long)watch_group); |
|
|
|
|
} |
|
|
|
|
struct android_media_session *sessions[] = {&publisher, &viewer}; |
|
|
|
|
for (unsigned i = 0; i < 2; i++) { |
|
|
|
|
struct android_media_session *sessions[] = {&viewer}; |
|
|
|
|
for (unsigned i = 0; i < 1; i++) { |
|
|
|
|
struct android_media_session *s = sessions[i]; |
|
|
|
|
if (s->opening) { |
|
|
|
|
if (s->stopping) { media_close(s, 5); continue; } |
|
|
|
|
@ -280,10 +211,6 @@ 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); |
|
|
|
|
@ -321,36 +248,6 @@ static void media_process(void *arg) {
|
|
|
|
|
memset(&viewer_status, 0, sizeof(viewer_status)); |
|
|
|
|
} else { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android playback read failed result=%d", result); media_close(&viewer, 6); } |
|
|
|
|
} |
|
|
|
|
if (publisher.handle && !stream_handle_active(publisher.handle)) media_close(&publisher, 6); |
|
|
|
|
uint64_t now = get_time_us(); |
|
|
|
|
if (video_queue.head && now - video_queue.head->created > MEDIA_AGE_US) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "Android TX video expired queued=%u bytes=%zu; require KEY", video_queue.count, video_queue.bytes); |
|
|
|
|
queue_clear(&video_queue); publisher.need_key = 1; |
|
|
|
|
} |
|
|
|
|
while (audio_queue.head && now - audio_queue.head->created > MEDIA_AGE_US) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "Android TX audio expired pts=%llu", (unsigned long long)audio_queue.head->pts); |
|
|
|
|
u_free(queue_take(&audio_queue)); |
|
|
|
|
} |
|
|
|
|
if (publisher.handle && !publisher.finished) { |
|
|
|
|
for (unsigned i = 0; i < 2 && (video_queue.head || audio_queue.head); i++) { |
|
|
|
|
size_t pending = video_queue.bytes + audio_queue.bytes - (video_queue.head ? video_queue.head->offset : 0); |
|
|
|
|
uint64_t oldest = video_queue.head ? video_queue.head->created : audio_queue.head->created; |
|
|
|
|
if (audio_queue.head && audio_queue.head->created < oldest) oldest = audio_queue.head->created; |
|
|
|
|
if (!publisher.stopping && pending + STREAM_MEDIA_HEADER_SIZE + STREAM_MEDIA_VIDEO_SIZE < stream_publish_capacity(publisher.handle) |
|
|
|
|
&& now - oldest < 20000) break; |
|
|
|
|
int result = media_publish(); |
|
|
|
|
if (result < 0) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media publish failed result=%d", result); media_close(&publisher, 6); break; } |
|
|
|
|
} |
|
|
|
|
if (publisher.handle && publisher.stopping && !video_queue.head && !audio_queue.head) { |
|
|
|
|
if (stream_publish_finish(publisher.handle) != STREAM_OK) media_close(&publisher, 6); |
|
|
|
|
else { publisher.finished = 1; publisher.drain_until = now + 2000000; } |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (publisher.finished && now >= publisher.drain_until) media_close(&publisher, 5); |
|
|
|
|
if (!media_post && (video_queue.head || audio_queue.head || publisher.finished)) { |
|
|
|
|
media_timer = uasync_set_timeout(media_inst->ua, publisher.finished ? 200 : 50, NULL, media_process, "android_stream"); |
|
|
|
|
if (!media_timer) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media timer allocation failed"); media_close(&publisher, 6); } |
|
|
|
|
} |
|
|
|
|
pthread_mutex_unlock(&media_lock); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -361,6 +258,9 @@ int android_stream_attach(struct UTUN_INSTANCE *inst) {
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android stream attach rejected inst=%p owner=%p", (void *)inst, (void *)media_inst); |
|
|
|
|
pthread_mutex_unlock(&media_lock); return -1; |
|
|
|
|
} |
|
|
|
|
if (!transmitter && stream_publisher_create(&transmitter, inst) < 0) { |
|
|
|
|
pthread_mutex_unlock(&media_lock); return -1; |
|
|
|
|
} |
|
|
|
|
media_inst = inst; stream_set_event_callback(inst, media_event, NULL); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "Android stream bridge attached"); |
|
|
|
|
pthread_mutex_unlock(&media_lock); |
|
|
|
|
@ -375,7 +275,7 @@ void android_stream_detach(struct UTUN_INSTANCE *inst) {
|
|
|
|
|
stream_watch_sources(inst, 0); |
|
|
|
|
stream_set_event_callback(inst, NULL, NULL); |
|
|
|
|
queue_clear(&output_queue); |
|
|
|
|
if (publisher.id) media_close(&publisher, 0); |
|
|
|
|
stream_publisher_destroy(transmitter); transmitter = NULL; memset(&publisher, 0, sizeof(publisher)); |
|
|
|
|
if (viewer.id) media_close(&viewer, 0); |
|
|
|
|
media_inst = NULL; |
|
|
|
|
watch_pending = 0; watch_group = 0; |
|
|
|
|
@ -399,12 +299,13 @@ int android_stream_open(uint64_t id, uint64_t group, uint64_t source, unsigned w
|
|
|
|
|
if (codec < STREAM_MEDIA_H264 || codec > STREAM_MEDIA_H266 || s->id || id == publisher.id || id == viewer.id |
|
|
|
|
|| !media_inst || !uasync_is_running(media_inst->ua)) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android stream open busy/detached id=%llu", (unsigned long long)id); |
|
|
|
|
} else if (media_schedule() == 0) { |
|
|
|
|
} else if ((!source || media_schedule() == 0)) { |
|
|
|
|
*s = (struct android_media_session){.id = id, .group = group, .source = source, .expected_epoch = expected_epoch, |
|
|
|
|
.opening = 1, .need_key = 1, .listen_audio = !!audio, .position = -1, |
|
|
|
|
.video_ahead = STREAM_MEDIA_READ_AHEAD_US, .audio_ahead = STREAM_MEDIA_READ_AHEAD_US, |
|
|
|
|
.format = {(uint16_t)width, (uint16_t)height, (uint16_t)rotation, (uint8_t)codec, audio ? STREAM_MEDIA_OPUS_STEREO : 0, 1}}; |
|
|
|
|
result = 0; |
|
|
|
|
result = source ? 0 : stream_publisher_open(transmitter, id, group, &s->format); |
|
|
|
|
if (result < 0) memset(s, 0, sizeof(*s)); |
|
|
|
|
} |
|
|
|
|
pthread_mutex_unlock(&media_lock); return result; |
|
|
|
|
} |
|
|
|
|
@ -421,64 +322,8 @@ int android_stream_watch(uint64_t group) {
|
|
|
|
|
/* Перегрузка удаляет целые видео-кадры и блокирует P-кадры до нового KEY. */ |
|
|
|
|
int android_stream_feed(uint64_t id, int video, uint64_t pts, int key, const uint8_t *data, size_t length, |
|
|
|
|
const struct stream_media_format *format) { |
|
|
|
|
if (!data || !length || length > (video ? STREAM_MEDIA_MAX_FRAME : STREAM_MEDIA_MAX_OPUS) || pts > INT64_MAX) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android stream feed invalid length=%zu", length); return -1; |
|
|
|
|
} |
|
|
|
|
pthread_mutex_lock(&media_lock); |
|
|
|
|
int result = -1, track = video ? 0 : 1; |
|
|
|
|
if (!media_inst || publisher.id != id || publisher.stopping || publisher.finished) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "Android stream feed rejected id=%llu owner=%llu attached=%d stopping=%d finished=%d", |
|
|
|
|
(unsigned long long)id, (unsigned long long)publisher.id, media_inst != NULL, publisher.stopping, publisher.finished); |
|
|
|
|
goto done; |
|
|
|
|
} |
|
|
|
|
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->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; |
|
|
|
|
} |
|
|
|
|
if (format->revision < publisher.format.revision) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "Android stale video revision=%u current=%u", format->revision, publisher.format.revision); |
|
|
|
|
result = 0; goto done; |
|
|
|
|
} |
|
|
|
|
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->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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if ((!video && !publisher.format.audio_codec) || (publisher.seen[track] && pts <= publisher.last_pts[track])) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android stream feed invalid track/PTS id=%llu pts=%llu", |
|
|
|
|
(unsigned long long)id, (unsigned long long)pts); goto done; |
|
|
|
|
} |
|
|
|
|
if (video && !reconfigure && (video_queue.bytes + audio_queue.bytes + length > TX_LIMIT |
|
|
|
|
|| video_queue.count + audio_queue.count >= TX_RECORDS)) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "Android TX overload bytes=%zu count=%u; require KEY", |
|
|
|
|
video_queue.bytes + audio_queue.bytes, video_queue.count + audio_queue.count); |
|
|
|
|
queue_clear(&video_queue); publisher.need_key = 1; |
|
|
|
|
} |
|
|
|
|
if (video && publisher.need_key && !key) { result = 0; goto done; } |
|
|
|
|
if ((reconfigure ? 0 : video_queue.bytes) + audio_queue.bytes + length > TX_LIMIT |
|
|
|
|
|| (reconfigure ? 0 : video_queue.count) + audio_queue.count >= TX_RECORDS) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "Android TX admission refused length=%zu video=%d", length, video); result = 0; goto done; |
|
|
|
|
} |
|
|
|
|
struct media_record *record = u_calloc(1, sizeof(*record) + length); |
|
|
|
|
if (!record) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android TX allocation failed bytes=%zu", length); goto done; } |
|
|
|
|
if (media_schedule() < 0) { u_free(record); goto done; } |
|
|
|
|
if (reconfigure) { |
|
|
|
|
queue_clear(&video_queue); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "Android video configuration committed id=%llu revision=%u->%u dimensions=%ux%u rotation=%u", |
|
|
|
|
(unsigned long long)id, publisher.format.revision, format->revision, format->width, format->height, format->rotation); |
|
|
|
|
publisher.format = *format; |
|
|
|
|
} |
|
|
|
|
record->length = length; record->pts = pts; record->created = get_time_us(); record->key = key ? STREAM_MEDIA_KEY : 0; |
|
|
|
|
if (video) { record->frame_id = ++publisher.next_frame; publisher.need_key = 0; } |
|
|
|
|
memcpy(record->data, data, length); queue_append(video ? &video_queue : &audio_queue, record); |
|
|
|
|
publisher.seen[track] = 1; publisher.last_pts[track] = pts; result = 1; |
|
|
|
|
done: |
|
|
|
|
int result = stream_publisher_feed(transmitter, id, video, pts, key, data, length, format); |
|
|
|
|
pthread_mutex_unlock(&media_lock); return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -486,7 +331,8 @@ done:
|
|
|
|
|
void android_stream_stop(uint64_t id) { |
|
|
|
|
pthread_mutex_lock(&media_lock); |
|
|
|
|
struct android_media_session *s = publisher.id == id ? &publisher : viewer.id == id ? &viewer : NULL; |
|
|
|
|
if (s) { s->stopping = 1; if (media_schedule() < 0) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android stream stop schedule failed"); } |
|
|
|
|
if (s == &publisher) { s->stopping = 1; stream_publisher_stop(transmitter, id); } |
|
|
|
|
else if (s) { s->stopping = 1; if (media_schedule() < 0) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android stream stop schedule failed"); } |
|
|
|
|
pthread_mutex_unlock(&media_lock); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -494,6 +340,18 @@ void android_stream_stop(uint64_t id) {
|
|
|
|
|
uint8_t *android_stream_pull(uint64_t id, size_t *length) { |
|
|
|
|
if (!length) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android stream pull missing length"); return NULL; } |
|
|
|
|
*length = 0; pthread_mutex_lock(&media_lock); |
|
|
|
|
if (publisher.id) { |
|
|
|
|
struct stream_publisher_status status; |
|
|
|
|
if (stream_publisher_snapshot(transmitter, publisher.id, &status) == 0) { |
|
|
|
|
if (publisher.opening && status.state >= STREAM_PUBLISHER_LIVE && status.state <= STREAM_PUBLISHER_DRAINING) { |
|
|
|
|
publisher.opening = 0; publisher.epoch = status.epoch; media_output(publisher.id, 4, NULL, 0, 0, NULL, 0); |
|
|
|
|
} |
|
|
|
|
if (status.state == STREAM_PUBLISHER_ENDED || status.state == STREAM_PUBLISHER_FAILED) { |
|
|
|
|
media_output(publisher.id, status.state == STREAM_PUBLISHER_ENDED ? 5 : 6, NULL, 0, status.reason, NULL, 0); |
|
|
|
|
memset(&publisher, 0, sizeof(publisher)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
struct media_record **link = &output_queue.head, *previous = NULL; |
|
|
|
|
uint8_t *data = NULL; uint64_t now = get_time_us(); |
|
|
|
|
while (*link) { |
|
|
|
|
|