Skip to content

Commit 39673df

Browse files
committed
fix: critical thread safety and resource leak fixes
- Remove System.exit(1) so crash activity renders - Include port in WebView URL for cameras on non-default ports - Fix AAC decoder race: check codec under lock before dequeueInputBuffer - Fix UDP socket race: assign to volatile fields immediately after creation - Make decoderFailed volatile in QuadCell (cross-thread visibility) - Fix Surface use-after-free in QuadCell via decoderLock synchronization - Add nalAssembler.reset() in QuadCell.stop() for clean restart state - Use obtainFrame() pool in QuadCell.readTcp() instead of raw allocation - Recycle frames in QuadCell.handlePacket() on all code paths - Clear stale Frame.length on pool reuse
1 parent 5ade079 commit 39673df

2 files changed

Lines changed: 34 additions & 8 deletions

File tree

‎android/app/src/main/java/com/openipc/decoder/App.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public void onCreate() {
2424
intent.putExtra("error", Log.getStackTraceString(throwable));
2525
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
2626
startActivity(intent);
27-
System.exit(1);
27+
// Process will terminate naturally with the crash activity visible
2828
});
2929
}
3030
}

‎android/app/src/main/java/com/openipc/decoder/Decoder.java‎

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)