@@ -99,17 +99,6 @@ sc_stream_sink_build_connect_url(const char *url) {
9999 }
100100 result = tmp ;
101101 }
102- // Keep SRT protocol latency low (default 120 ms is too high for live
103- // screen mirroring; 50 ms is comfortable for LAN).
104- // Users on high-latency WAN links can override with e.g. ?latency=200.
105- if (!sc_url_has_param (result , "latency" )) {
106- char * tmp = sc_url_append_param (result , "latency" , "50" );
107- free (result );
108- if (!tmp ) {
109- return NULL ;
110- }
111- result = tmp ;
112- }
113102 } else if (is_tcp ) {
114103 // scrcpy acts as the TCP server, waiting for a player to connect
115104 if (!sc_url_has_param (result , "listen" )) {
@@ -121,11 +110,22 @@ sc_stream_sink_build_connect_url(const char *url) {
121110 result = tmp ;
122111 }
123112 }
124- // udp:// and rtp:// are connectionless; no listener mode needed
113+ // udp:// is connectionless; no listener mode needed
125114
126115 return result ;
127116}
128117
118+ /**
119+ * Check if a URL uses a connectionless protocol (UDP).
120+ *
121+ * For this protocol, only a single output stream is needed,
122+ * not multiple client connections.
123+ */
124+ static inline bool
125+ sc_stream_sink_is_connectionless (const char * url ) {
126+ return !strncmp (url , "udp://" , 6 );
127+ }
128+
129129static AVPacket *
130130sc_stream_sink_packet_ref (const AVPacket * packet ) {
131131 AVPacket * p = av_packet_alloc ();
@@ -562,10 +562,24 @@ run_stream_sink_client(void *data) {
562562
563563 sc_stream_sink_client_run_stream (client );
564564
565- // Close this client's network connection.
565+ // WORKAROUND: SRT epoll deadlock on disconnect
566+ // When closing SRT sockets, FFmpeg's interrupt callback and SRT's internal
567+ // epoll management conflict, causing "no sockets to check, this would deadlock".
568+ // Root cause: FFmpeg may call interrupt_callback during avio_close(), but SRT
569+ // has already removed the socket from epoll, causing state inconsistency.
570+ // TODO: Remove this workaround once SRT/FFmpeg fix the socket lifecycle interaction.
571+ // For now, only skip avio_close() for SRT; other protocols are safe.
572+ bool is_srt = sink -> url && !strncmp (sink -> url , "srt://" , 6 );
573+
566574 if (client -> ctx -> pb ) {
567- avio_close (client -> ctx -> pb );
568- client -> ctx -> pb = NULL ;
575+ if (is_srt ) {
576+ // SRT workaround: don't call avio_close(), let avformat_free_context() handle it
577+ client -> ctx -> pb = NULL ;
578+ } else {
579+ // Safe for TCP, UDP and other protocols
580+ avio_close (client -> ctx -> pb );
581+ client -> ctx -> pb = NULL ;
582+ }
569583 }
570584
571585 // Mark as finished so the accept loop can join and free us.
@@ -615,9 +629,12 @@ sc_stream_sink_reap_dead_clients(struct sc_stream_sink *sink) {
615629}
616630
617631/**
618- * Accept loop: initialises the template context once, then repeatedly accepts
619- * incoming connections, spawning a per-client thread for each. Runs until
620- * sink->stopped is set (by sc_stream_sink_stop() or device EOS).
632+ * Main streaming loop: initialises the template context once, then either:
633+ * - For connection-oriented protocols (TCP, SRT): repeatedly accepts incoming
634+ * connections, spawning a per-client thread for each.
635+ * - For connectionless protocols (UDP, RTP): creates a single output stream
636+ * and writes all packets to it directly.
637+ * Runs until sink->stopped is set (by sc_stream_sink_stop() or device EOS).
621638 */
622639
623640// Forward declaration: defined below alongside the other packet-sink callbacks.
@@ -633,17 +650,35 @@ run_stream_sink(void *data) {
633650 goto stop ;
634651 }
635652
653+ bool is_connectionless = sc_stream_sink_is_connectionless (sink -> url );
654+
636655 char * connect_url = sc_stream_sink_build_connect_url (sink -> url );
637656 if (!connect_url ) {
638657 goto stop ;
639658 }
640659
641- LOGI ("Stream sink: listening for clients on %s" , sink -> url );
660+ if (is_connectionless ) {
661+ LOGI ("Stream sink: streaming to %s" , connect_url );
662+ } else {
663+ LOGI ("Stream sink: listening for clients on %s" , connect_url );
664+ }
665+
666+ bool connectionless_done = false;
642667
643668 while (!sink -> stopped ) {
669+ // For connectionless protocols, only attempt one connection
670+ if (is_connectionless && connectionless_done ) {
671+ // Keep the single client thread running; just wait until stopped
672+ sc_mutex_lock (& sink -> mutex );
673+ while (!sink -> stopped ) {
674+ sc_cond_wait (& sink -> cond , & sink -> mutex );
675+ }
676+ sc_mutex_unlock (& sink -> mutex );
677+ break ;
678+ }
679+
644680 // Reap any client threads that finished since the last iteration.
645681 sc_stream_sink_reap_dead_clients (sink );
646-
647682 AVIOInterruptCB int_cb = {
648683 .callback = sc_stream_sink_interrupt_cb ,
649684 .opaque = sink ,
@@ -673,7 +708,10 @@ run_stream_sink(void *data) {
673708 calloc (1 , sizeof (struct sc_stream_sink_client ));
674709 if (!client ) {
675710 LOG_OOM ();
676- avio_close (client_ctx -> pb );
711+ bool is_srt = sink -> url && !strncmp (sink -> url , "srt://" , 6 );
712+ if (!is_srt ) {
713+ avio_close (client_ctx -> pb );
714+ }
677715 client_ctx -> pb = NULL ;
678716 avformat_free_context (client_ctx );
679717 continue ;
@@ -698,8 +736,7 @@ run_stream_sink(void *data) {
698736 // Write the MPEG-TS stream header for this client.
699737 if (avformat_write_header (client_ctx , NULL ) < 0 ) {
700738 LOGE ("Stream sink: failed to write stream header to client" );
701- avio_close (client_ctx -> pb );
702- client_ctx -> pb = NULL ;
739+ client_ctx -> pb = NULL ; // Don't avio_close() - causes SRT epoll issues
703740 avformat_free_context (client_ctx );
704741 free (client );
705742 continue ;
@@ -723,9 +760,11 @@ run_stream_sink(void *data) {
723760 sc_stream_sink_queue_clear (& client -> video_queue );
724761 sc_stream_sink_queue_clear (& client -> audio_queue );
725762 sc_mutex_unlock (& sink -> mutex );
726- // avformat_write_header already moved pb ownership; close it.
727763 if (client_ctx -> pb ) {
728- avio_close (client_ctx -> pb );
764+ bool is_srt = sink -> url && !strncmp (sink -> url , "srt://" , 6 );
765+ if (!is_srt ) {
766+ avio_close (client_ctx -> pb );
767+ }
729768 client_ctx -> pb = NULL ;
730769 }
731770 avformat_free_context (client_ctx );
@@ -736,6 +775,13 @@ run_stream_sink(void *data) {
736775 }
737776
738777 LOGI ("Stream sink: client connected on %s" , sink -> url );
778+
779+ if (is_connectionless ) {
780+ // For connectionless protocols (UDP, RTP), we only need a single
781+ // stream. Mark it as done and the loop will now wait instead of
782+ // trying to accept new connections.
783+ connectionless_done = true;
784+ }
739785 }
740786
741787 free (connect_url );
@@ -762,7 +808,10 @@ run_stream_sink(void *data) {
762808 struct sc_stream_sink_client * next = head -> next ;
763809 sc_thread_join (& head -> thread , NULL );
764810 if (head -> ctx -> pb ) {
765- avio_close (head -> ctx -> pb );
811+ bool is_srt = sink -> url && !strncmp (sink -> url , "srt://" , 6 );
812+ if (!is_srt ) {
813+ avio_close (head -> ctx -> pb );
814+ }
766815 head -> ctx -> pb = NULL ;
767816 }
768817 avformat_free_context (head -> ctx );
0 commit comments