@@ -843,6 +843,10 @@ private void startBrowser() {
843843 Log .w (TAG , "Cannot open WebUI: invalid host in URL" );
844844 return ;
845845 }
846+ int port = uri .getPort ();
847+ if (port >= 0 ) {
848+ link = link + ":" + port ;
849+ }
846850
847851 WebView view = new WebView (this );
848852 view .getSettings ().setJavaScriptEnabled (true );
@@ -1121,6 +1125,11 @@ private void createAacDecoder() {
11211125 byte [] aacData = aacQueue .poll (100 , TimeUnit .MILLISECONDS );
11221126 if (aacData == null ) continue ;
11231127
1128+ // Check if the codec was released by closeAudio() while we were waiting
1129+ synchronized (aacDecoderLock ) {
1130+ if (aacDecoder != codec ) break ;
1131+ }
1132+
11241133 // Check if the frame already has ADTS header (starts with 0xFFF)
11251134 boolean hasAdts = (aacData .length >= 2
11261135 && (aacData [0 ] & 0xFF ) == 0xFF
@@ -1570,10 +1579,12 @@ private void rtspConnect() throws Exception {
15701579 if (mType ) {
15711580 udpVideo = new DatagramSocket (null );
15721581 udpVideo .setReuseAddress (true );
1582+ mUdpSocket = udpVideo ; // assign immediately to close race
15731583 try { udpVideo .setReceiveBufferSize (512 * 1024 ); } catch (Exception ignored ) {}
15741584 udpVideo .bind (new InetSocketAddress (5000 ));
15751585 udpAudio = new DatagramSocket (null );
15761586 udpAudio .setReuseAddress (true );
1587+ mUdpAudioSocket = udpAudio ; // assign immediately to close race
15771588 try { udpAudio .setReceiveBufferSize (512 * 1024 ); } catch (Exception ignored ) {}
15781589 udpAudio .bind (new InetSocketAddress (5002 ));
15791590 }
@@ -2100,7 +2111,7 @@ private class QuadCell {
21002111
21012112 private volatile Surface surface ;
21022113 private volatile MediaCodec decoder ;
2103- private boolean decoderFailed ;
2114+ private volatile boolean decoderFailed ;
21042115 private final Object decoderLock = new Object ();
21052116
21062117 private final BlockingQueue <Frame > nalQueue = new ArrayBlockingQueue <>(30 );
@@ -2146,9 +2157,16 @@ public void onSurfaceTextureAvailable(android.graphics.SurfaceTexture st, int w,
21462157 }
21472158 @ Override
21482159 public void onSurfaceTextureSizeChanged (android .graphics .SurfaceTexture st , int w , int h ) {
2160+ // Swap surface reference immediately so the decoder thread sees
2161+ // the new Surface; release the old Surface synchronised via decoderLock
2162+ // to avoid a use-after-free while the decoder is mid-render.
21492163 Surface old = surface ;
21502164 surface = new Surface (st );
21512165 if (old != null ) {
2166+ synchronized (decoderLock ) {
2167+ // decoderLock guarantees the decoder thread has finished
2168+ // with any prior releaseOutputBuffer(..., true) call
2169+ }
21522170 try {
21532171 old .release ();
21542172 } catch (Exception e ) {
@@ -2270,6 +2288,7 @@ private void startThreads() {
22702288 void stop () {
22712289 running = false ;
22722290 activeStream = false ;
2291+ nalAssembler .reset ();
22732292 view .setSurfaceTextureListener (null );
22742293 Socket tcp = tcpSocket ;
22752294 if (tcp != null ) {
@@ -2481,27 +2500,33 @@ private void readTcp(InputStream rawInput) throws IOException {
24812500
24822501 // channel 0 = video RTP; skip audio (channel 2)
24832502 if (channel == 0 ) {
2484- byte [] copy = new byte [ len ] ;
2485- System .arraycopy (pktBuf , 0 , copy , 0 , len );
2486- Frame f = new Frame ( copy , len );
2503+ Frame f = obtainFrame ( len ) ;
2504+ System .arraycopy (pktBuf , 0 , f . data () , 0 , len );
2505+ f . setLength ( len );
24872506 handlePacket (f );
24882507 }
24892508 }
24902509 }
24912510
24922511 private void handlePacket (Frame frame ) {
2493- if (frame .length () < 12 ) return ;
2512+ if (frame .length () < 12 ) { recycleFrame ( frame ); return ; }
24942513 byte [] data = frame .data ();
2495- if ((data [0 ] & 0x0F ) != 0 || (data [0 ] & 0x10 ) != 0 ) return ;
2514+ if ((data [0 ] & 0x0F ) != 0 || (data [0 ] & 0x10 ) != 0 ) { recycleFrame ( frame ); return ; }
24962515
24972516 int pt = data [1 ] & 0x7F ;
24982517 if (pt == RTP_PT_H265 || pt == RTP_PT_H264 ) {
24992518 codecH265 = (pt == RTP_PT_H265 );
25002519 Frame output = nalAssembler .assemble (frame , codecH265 );
2501- if (output != null ) nalQueue .offer (output );
2520+ recycleFrame (frame ); // assemble() copies what it needs
2521+ if (output != null && !nalQueue .offer (output )) {
2522+ Log .w (TAG , tag + " queue full, dropping frame" );
2523+ }
25022524 } else if (pt != lastUnknownPayload ) {
25032525 lastUnknownPayload = pt ;
25042526 Log .w (TAG , tag + " unknown PT: " + pt );
2527+ recycleFrame (frame );
2528+ } else {
2529+ recycleFrame (frame );
25052530 }
25062531 }
25072532
@@ -2707,6 +2732,7 @@ private static class FramePool {
27072732 Frame obtain (int size ) {
27082733 Frame frame = pool .poll ();
27092734 if (frame != null && frame .data ().length >= size ) {
2735+ frame .setLength (0 ); // clear stale length from prior use
27102736 return frame ;
27112737 }
27122738 return new Frame (new byte [size ], 0 );
0 commit comments