From efa533a2b14259115ecb7d31770ad03a0ff56f3c Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Fri, 24 Jul 2026 22:40:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(encoder):=20=E4=BF=AE=E5=A4=8D=E7=89=B9?= =?UTF-8?q?=E5=AE=9A=E8=AE=BE=E5=A4=87=E4=B8=8B=E7=BC=96=E7=A0=81=E5=99=A8?= =?UTF-8?q?=E5=86=99=E5=85=A5=E8=B6=8A=E7=95=8C=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加边界检查,防止部分设备 buffer limit 偏移导致的 Crash - 兼容 I420 平面格式与 NV12/NV21 半平面格式 - 避免多线程下 encoder 字段被意外置空导致的空指针异常 - 修复 I420Buffer 为 null 时未释放输入缓冲的问题 --- .../controlled/webrtc/SelfCodecEncoder.java | 114 +++++++++++++----- 1 file changed, 81 insertions(+), 33 deletions(-) diff --git a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/webrtc/SelfCodecEncoder.java b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/webrtc/SelfCodecEncoder.java index 6ce5c16..2634dc3 100644 --- a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/webrtc/SelfCodecEncoder.java +++ b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/webrtc/SelfCodecEncoder.java @@ -147,7 +147,8 @@ public class SelfCodecEncoder { * 当编码器输入缓冲不足时会丢弃该帧(编码器跟不上),保证不堆积、不阻塞采集线程。 */ public void feedFrame(VideoFrame frame) { - if (!running || encoder == null) return; + MediaCodec mc = encoder; + if (!running || mc == null) return; Integer idx; synchronized (inputLock) { idx = freeInputIndices.poll(); @@ -156,32 +157,39 @@ public class SelfCodecEncoder { return; // 丢掉这一帧,等待下一个空闲输入缓冲 } try { - encodeFrame(idx, frame); + encodeFrame(mc, idx, frame); } catch (Exception e) { Log.e(TAG, "encodeFrame failed", e); try { - encoder.queueInputBuffer(idx, 0, 0, System.nanoTime() / 1000, 0); + mc.queueInputBuffer(idx, 0, 0, System.nanoTime() / 1000, 0); } catch (Exception ignored) { } } } - private void encodeFrame(int index, VideoFrame frame) { + private void encodeFrame(MediaCodec mc, int index, VideoFrame frame) { long ptsUs = System.nanoTime() / 1000; VideoFrame.I420Buffer i420 = frame.getBuffer().toI420(); + if (i420 == null) { + mc.queueInputBuffer(index, 0, 0, ptsUs, 0); + return; + } int w = i420.getWidth(); int h = i420.getHeight(); - ensureNv12Buffer(w, h); - i420ToNv12(i420, nv12Buffer, w, h); + byte[] buffer = ensureNv12Buffer(w, h); + i420ToNv12(i420, buffer, w, h); i420.release(); - writeNv12(index, nv12Buffer, w, h, ptsUs); + writeNv12(mc, index, buffer, w, h, ptsUs); } - private void ensureNv12Buffer(int w, int h) { + private byte[] ensureNv12Buffer(int w, int h) { int need = w * h * 3 / 2; - if (nv12Buffer == null || nv12Buffer.length < need) { - nv12Buffer = new byte[need]; + byte[] buf = nv12Buffer; + if (buf == null || buf.length < need) { + buf = new byte[need]; + nv12Buffer = buf; } + return buf; } /** I420(三平面)→ NV12(Y + UV 交错),写入 out。 */ @@ -209,48 +217,87 @@ public class SelfCodecEncoder { } /** 把 NV12 按编码器输入 Image 的 stride 写入输入缓冲并入队。 */ - private void writeNv12(int index, byte[] nv12, int w, int h, long ptsUs) { - Image image = encoder.getInputImage(index); + private void writeNv12(MediaCodec mc, int index, byte[] nv12, int w, int h, long ptsUs) { + Image image = mc.getInputImage(index); if (image == null) { - encoder.queueInputBuffer(index, 0, 0, ptsUs, 0); + mc.queueInputBuffer(index, 0, 0, ptsUs, 0); return; } try { - Image.Plane yPlane = image.getPlanes()[0]; - Image.Plane uvPlane = image.getPlanes()[1]; + Image.Plane[] planes = image.getPlanes(); + // Y 平面写入,添加边界检查防止部分设备 buffer limit 偏移导致的 Crash + Image.Plane yPlane = planes[0]; ByteBuffer yBuf = yPlane.getBuffer(); - ByteBuffer uvBuf = uvPlane.getBuffer(); int yRow = yPlane.getRowStride(); - int uvRow = uvPlane.getRowStride(); int yPix = yPlane.getPixelStride(); - int uvPix = uvPlane.getPixelStride(); + int yLimit = yBuf.limit(); int src = 0; for (int r = 0; r < h; r++) { - int dst = r * yRow; + int dstRow = r * yRow; for (int c = 0; c < w; c++) { - yBuf.put(dst + c * yPix, nv12[src + c]); + int dst = dstRow + c * yPix; + if (dst < yLimit) { + yBuf.put(dst, nv12[src + c]); + } } src += w; } + + // UV 平面写入 int ch = h / 2; int cw = w / 2; - int uvSrc = w * h; - for (int r = 0; r < ch; r++) { - int sRow = uvSrc + r * cw * 2; - int dRow = r * uvRow; - for (int c = 0; c < cw; c++) { - int s = sRow + c * 2; - int d = dRow + c * uvPix; - uvBuf.put(d, nv12[s]); - uvBuf.put(d + 1, nv12[s + 1]); + int uvSrcBase = w * h; + + if (planes.length >= 3 && planes[1].getPixelStride() == 1 && planes[2].getPixelStride() == 1) { + // I420 平面格式 (Planar) + for (int p = 1; p <= 2; p++) { + Image.Plane plane = planes[p]; + ByteBuffer buf = plane.getBuffer(); + int row = plane.getRowStride(); + int limit = buf.limit(); + for (int r = 0; r < ch; r++) { + int dstRow = r * row; + for (int c = 0; c < cw; c++) { + // nv12 数组中 UV 是交错存储的 + int s = uvSrcBase + (r * cw + c) * 2 + (p - 1); + int dst = dstRow + c; + if (dst < limit) { + buf.put(dst, nv12[s]); + } + } + } + } + } else if (planes.length >= 2) { + // NV12/NV21 半平面格式 (Semi-planar) + Image.Plane uvPlane = planes[1]; + ByteBuffer uvBuf = uvPlane.getBuffer(); + int uvRow = uvPlane.getRowStride(); + int uvPix = uvPlane.getPixelStride(); + int uvLimit = uvBuf.limit(); + + for (int r = 0; r < ch; r++) { + int dRow = r * uvRow; + int sRow = uvSrcBase + r * cw * 2; + for (int c = 0; c < cw; c++) { + int s = sRow + c * 2; + int d = dRow + c * uvPix; + // 针对 Android 14+ 或部分特殊设备,UV buffer 的 limit 可能刚好少一个字节 + if (d < uvLimit) { + uvBuf.put(d, nv12[s]); + } + if (d + 1 < uvLimit) { + uvBuf.put(d + 1, nv12[s + 1]); + } + } } } - encoder.queueInputBuffer(index, 0, w * h * 3 / 2, ptsUs, 0); + + mc.queueInputBuffer(index, 0, w * h * 3 / 2, ptsUs, 0); } catch (Exception e) { Log.e(TAG, "writeNv12 failed", e); try { - encoder.queueInputBuffer(index, 0, 0, ptsUs, 0); + mc.queueInputBuffer(index, 0, 0, ptsUs, 0); } catch (Exception ignored) { } } @@ -399,11 +446,12 @@ public class SelfCodecEncoder { /** 即时请求一个关键帧(强求 IDR)。 */ public void requestKeyFrame() { - if (encoder == null) return; + MediaCodec mc = encoder; + if (mc == null) return; try { Bundle params = new Bundle(); params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); - encoder.setParameters(params); + mc.setParameters(params); } catch (Exception e) { Log.w(TAG, "requestKeyFrame failed", e); }