diff --git a/tools/chatgui-android/libutun_lite/stream_bridge.c b/tools/chatgui-android/libutun_lite/stream_bridge.c index 86f4385f..9fb76e94 100644 --- a/tools/chatgui-android/libutun_lite/stream_bridge.c +++ b/tools/chatgui-android/libutun_lite/stream_bridge.c @@ -29,8 +29,8 @@ struct android_media_session { struct STREAM_HANDLE *handle; struct stream_media_format format; int opening, stopping, finished, need_key, seen[2], rx_overflow, reason; - int want_read, waiting, resync, listen_audio; - int64_t position, video_ahead, audio_ahead; + int want_read, want_video, waiting, resync, listen_audio; + int64_t position, audio_ahead; }; static pthread_mutex_t media_lock = PTHREAD_MUTEX_INITIALIZER; @@ -147,7 +147,7 @@ static void media_event(struct UTUN_INSTANCE *inst, const struct stream_event *e viewer.reason = event->reason; media_close(&viewer, 6); } else if (event->type == STREAM_EVENT_AVAILABLE) { - viewer.want_read = 1; viewer.waiting = 0; + viewer.waiting = 0; if (media_schedule() < 0) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media ready scheduling failed"); } } @@ -161,7 +161,7 @@ static void media_close(struct android_media_session *s, int kind) { memset(s, 0, sizeof(*s)); } -/* Один actor обслуживает команды и не больше двух FEC-пачек за вызов; idle не будит мобильное ядро. */ +/* Один actor обслуживает команды и один ответ на запрос; idle не копирует поток из кольца. */ static void media_process(void *arg) { (void)arg; pthread_mutex_lock(&media_lock); @@ -210,7 +210,7 @@ static void media_process(void *arg) { (unsigned long long)s->id, (unsigned long long)s->group, (unsigned long long)s->source, result); media_close(s, 6); } else { - if (s->source) { viewer_status_id = s->id; memset(&viewer_status, 0, sizeof(viewer_status)); s->want_read = 1; } + if (s->source) { viewer_status_id = s->id; memset(&viewer_status, 0, sizeof(viewer_status)); } 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); @@ -228,11 +228,11 @@ static void media_process(void *arg) { } unsigned queued_viewer = 0; for (struct media_record *r = output_queue.head; r; r = r->next) if (r->id == viewer.id) queued_viewer++; - if (viewer.handle && viewer.want_read && queued_viewer < 2 + if (viewer.handle && viewer.want_read && !queued_viewer && output_queue.bytes + STREAM_MEDIA_MAX_FRAME + ANDROID_STREAM_HEADER_SIZE <= RX_LIMIT) { viewer.want_read = 0; struct stream_media_packet packet; - int result = stream_media_read(viewer.handle, viewer.position, viewer.video_ahead, viewer.audio_ahead, &packet); + int result = stream_media_read(viewer.handle, viewer.position, viewer.want_video, viewer.audio_ahead, &packet); if (stream_media_get_status(viewer.handle, &viewer_status) < 0) result = STREAM_ERR_FORMAT; if (result == STREAM_OK) { if (packet.kind == STREAM_MEDIA_READ_END) media_close(&viewer, 5); @@ -241,7 +241,7 @@ static void media_process(void *arg) { } else if (result == STREAM_MORE) { viewer.want_read = 1; if (media_schedule() < 0) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media assembly scheduling failed"); - } else if (result == STREAM_WAIT) viewer.waiting = 1; + } else if (result == STREAM_WAIT) viewer.want_read = viewer.waiting = 1; else if (result == STREAM_GAP || result == STREAM_EXPIRED) { DEBUG_INFO(DEBUG_CATEGORY_STREAM, "Android playback resync id=%llu result=%d", (unsigned long long)viewer.id, result); stream_media_resync(viewer.handle); viewer.position = -1; media_output(viewer.id, 3, NULL, 0, 0, NULL, 0); @@ -302,7 +302,7 @@ int android_stream_open(uint64_t id, uint64_t group, uint64_t source, unsigned w } 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, + .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 = source ? 0 : stream_publisher_open(transmitter, id, group, &s->format); if (result < 0) memset(s, 0, sizeof(*s)); @@ -368,24 +368,25 @@ uint8_t *android_stream_pull(uint64_t id, size_t *length) { u_free(r); } else { previous = r; link = &r->next; } } - if (viewer.id == id && viewer.handle && (data || !viewer.waiting)) { - viewer.want_read = 1; - if (media_schedule() < 0) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media read scheduling failed"); - } pthread_mutex_unlock(&media_lock); return data; } -int android_stream_read_request(uint64_t id, int64_t position, int64_t video_ahead, int64_t audio_ahead) { - if (video_ahead < -1 || video_ahead > 60000000 || audio_ahead < -1 || audio_ahead > 60000000) { - DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media demand invalid ahead=%lld/%lld", - (long long)video_ahead, (long long)audio_ahead); return -1; +int android_stream_read_request(uint64_t id, int64_t position, int want_video, int64_t audio_ahead) { + if ((want_video != 0 && want_video != 1) || audio_ahead < -1 || audio_ahead > 60000000) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media demand invalid video=%d audio_ahead=%lld", + want_video, (long long)audio_ahead); return -1; } pthread_mutex_lock(&media_lock); int result = 0; - if (viewer.id == id && (!viewer.waiting || viewer.position != position - || viewer.video_ahead != video_ahead || viewer.audio_ahead != audio_ahead)) { - viewer.position = position; viewer.video_ahead = video_ahead; viewer.audio_ahead = audio_ahead; viewer.want_read = 1; - result = media_schedule(); + if (viewer.id == id) { + int queued = 0; + for (struct media_record *r = output_queue.head; r; r = r->next) if (r->id == id) queued = 1; + if (!queued) { + int changed = viewer.position != position || viewer.want_video != want_video || viewer.audio_ahead != audio_ahead; + viewer.position = position; viewer.want_video = want_video; viewer.audio_ahead = audio_ahead; + viewer.want_read = want_video || audio_ahead >= 0; + if (viewer.want_read && (!viewer.waiting || changed)) { viewer.waiting = 0; result = media_schedule(); } + } } pthread_mutex_unlock(&media_lock); return result; } diff --git a/tools/chatgui-android/libutun_lite/stream_bridge.h b/tools/chatgui-android/libutun_lite/stream_bridge.h index 395480d5..dcf5a0c1 100644 --- a/tools/chatgui-android/libutun_lite/stream_bridge.h +++ b/tools/chatgui-android/libutun_lite/stream_bridge.h @@ -35,8 +35,9 @@ int android_stream_feed(uint64_t id, int video, uint64_t pts, int key, const uin /* feed: 1 accepted, 0 dropped (video: запросить KEY), -1 detached/stopped/invalid. */ void android_stream_stop(uint64_t id); uint8_t *android_stream_pull(uint64_t id, size_t *length); -/* Запрос ближайших данных и снимок готовности; UI передаёт PTS, указатели кольца остаются в uasync. */ -int android_stream_read_request(uint64_t id, int64_t position_us, int64_t video_ahead_us, int64_t audio_ahead_us); +/* Один ответ на запрос: следующий кадр при want_video=1, звук до position_us+audio_ahead_us. + * Повторные запросы до получения ответа не создают дополнительные записи. Указатели кольца остаются в uasync. */ +int android_stream_read_request(uint64_t id, int64_t position_us, int want_video, int64_t audio_ahead_us); int android_stream_get_status(uint64_t id, struct stream_media_status *status); int android_stream_resync(uint64_t id);