fix(encoder): 修复特定设备下编码器写入越界崩溃
- 添加边界检查,防止部分设备 buffer limit 偏移导致的 Crash - 兼容 I420 平面格式与 NV12/NV21 半平面格式 - 避免多线程下 encoder 字段被意外置空导致的空指针异常 - 修复 I420Buffer 为 null 时未释放输入缓冲的问题
This commit is contained in:
@@ -147,7 +147,8 @@ public class SelfCodecEncoder {
|
|||||||
* 当编码器输入缓冲不足时会丢弃该帧(编码器跟不上),保证不堆积、不阻塞采集线程。
|
* 当编码器输入缓冲不足时会丢弃该帧(编码器跟不上),保证不堆积、不阻塞采集线程。
|
||||||
*/
|
*/
|
||||||
public void feedFrame(VideoFrame frame) {
|
public void feedFrame(VideoFrame frame) {
|
||||||
if (!running || encoder == null) return;
|
MediaCodec mc = encoder;
|
||||||
|
if (!running || mc == null) return;
|
||||||
Integer idx;
|
Integer idx;
|
||||||
synchronized (inputLock) {
|
synchronized (inputLock) {
|
||||||
idx = freeInputIndices.poll();
|
idx = freeInputIndices.poll();
|
||||||
@@ -156,32 +157,39 @@ public class SelfCodecEncoder {
|
|||||||
return; // 丢掉这一帧,等待下一个空闲输入缓冲
|
return; // 丢掉这一帧,等待下一个空闲输入缓冲
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
encodeFrame(idx, frame);
|
encodeFrame(mc, idx, frame);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "encodeFrame failed", e);
|
Log.e(TAG, "encodeFrame failed", e);
|
||||||
try {
|
try {
|
||||||
encoder.queueInputBuffer(idx, 0, 0, System.nanoTime() / 1000, 0);
|
mc.queueInputBuffer(idx, 0, 0, System.nanoTime() / 1000, 0);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encodeFrame(int index, VideoFrame frame) {
|
private void encodeFrame(MediaCodec mc, int index, VideoFrame frame) {
|
||||||
long ptsUs = System.nanoTime() / 1000;
|
long ptsUs = System.nanoTime() / 1000;
|
||||||
VideoFrame.I420Buffer i420 = frame.getBuffer().toI420();
|
VideoFrame.I420Buffer i420 = frame.getBuffer().toI420();
|
||||||
|
if (i420 == null) {
|
||||||
|
mc.queueInputBuffer(index, 0, 0, ptsUs, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
int w = i420.getWidth();
|
int w = i420.getWidth();
|
||||||
int h = i420.getHeight();
|
int h = i420.getHeight();
|
||||||
ensureNv12Buffer(w, h);
|
byte[] buffer = ensureNv12Buffer(w, h);
|
||||||
i420ToNv12(i420, nv12Buffer, w, h);
|
i420ToNv12(i420, buffer, w, h);
|
||||||
i420.release();
|
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;
|
int need = w * h * 3 / 2;
|
||||||
if (nv12Buffer == null || nv12Buffer.length < need) {
|
byte[] buf = nv12Buffer;
|
||||||
nv12Buffer = new byte[need];
|
if (buf == null || buf.length < need) {
|
||||||
|
buf = new byte[need];
|
||||||
|
nv12Buffer = buf;
|
||||||
}
|
}
|
||||||
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** I420(三平面)→ NV12(Y + UV 交错),写入 out。 */
|
/** I420(三平面)→ NV12(Y + UV 交错),写入 out。 */
|
||||||
@@ -209,48 +217,87 @@ public class SelfCodecEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 把 NV12 按编码器输入 Image 的 stride 写入输入缓冲并入队。 */
|
/** 把 NV12 按编码器输入 Image 的 stride 写入输入缓冲并入队。 */
|
||||||
private void writeNv12(int index, byte[] nv12, int w, int h, long ptsUs) {
|
private void writeNv12(MediaCodec mc, int index, byte[] nv12, int w, int h, long ptsUs) {
|
||||||
Image image = encoder.getInputImage(index);
|
Image image = mc.getInputImage(index);
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
encoder.queueInputBuffer(index, 0, 0, ptsUs, 0);
|
mc.queueInputBuffer(index, 0, 0, ptsUs, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Image.Plane yPlane = image.getPlanes()[0];
|
Image.Plane[] planes = image.getPlanes();
|
||||||
Image.Plane uvPlane = image.getPlanes()[1];
|
// Y 平面写入,添加边界检查防止部分设备 buffer limit 偏移导致的 Crash
|
||||||
|
Image.Plane yPlane = planes[0];
|
||||||
ByteBuffer yBuf = yPlane.getBuffer();
|
ByteBuffer yBuf = yPlane.getBuffer();
|
||||||
ByteBuffer uvBuf = uvPlane.getBuffer();
|
|
||||||
int yRow = yPlane.getRowStride();
|
int yRow = yPlane.getRowStride();
|
||||||
int uvRow = uvPlane.getRowStride();
|
|
||||||
int yPix = yPlane.getPixelStride();
|
int yPix = yPlane.getPixelStride();
|
||||||
int uvPix = uvPlane.getPixelStride();
|
int yLimit = yBuf.limit();
|
||||||
|
|
||||||
int src = 0;
|
int src = 0;
|
||||||
for (int r = 0; r < h; r++) {
|
for (int r = 0; r < h; r++) {
|
||||||
int dst = r * yRow;
|
int dstRow = r * yRow;
|
||||||
for (int c = 0; c < w; c++) {
|
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;
|
src += w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UV 平面写入
|
||||||
int ch = h / 2;
|
int ch = h / 2;
|
||||||
int cw = w / 2;
|
int cw = w / 2;
|
||||||
int uvSrc = w * h;
|
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++) {
|
for (int r = 0; r < ch; r++) {
|
||||||
int sRow = uvSrc + r * cw * 2;
|
|
||||||
int dRow = r * uvRow;
|
int dRow = r * uvRow;
|
||||||
|
int sRow = uvSrcBase + r * cw * 2;
|
||||||
for (int c = 0; c < cw; c++) {
|
for (int c = 0; c < cw; c++) {
|
||||||
int s = sRow + c * 2;
|
int s = sRow + c * 2;
|
||||||
int d = dRow + c * uvPix;
|
int d = dRow + c * uvPix;
|
||||||
|
// 针对 Android 14+ 或部分特殊设备,UV buffer 的 limit 可能刚好少一个字节
|
||||||
|
if (d < uvLimit) {
|
||||||
uvBuf.put(d, nv12[s]);
|
uvBuf.put(d, nv12[s]);
|
||||||
|
}
|
||||||
|
if (d + 1 < uvLimit) {
|
||||||
uvBuf.put(d + 1, nv12[s + 1]);
|
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) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "writeNv12 failed", e);
|
Log.e(TAG, "writeNv12 failed", e);
|
||||||
try {
|
try {
|
||||||
encoder.queueInputBuffer(index, 0, 0, ptsUs, 0);
|
mc.queueInputBuffer(index, 0, 0, ptsUs, 0);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,11 +446,12 @@ public class SelfCodecEncoder {
|
|||||||
|
|
||||||
/** 即时请求一个关键帧(强求 IDR)。 */
|
/** 即时请求一个关键帧(强求 IDR)。 */
|
||||||
public void requestKeyFrame() {
|
public void requestKeyFrame() {
|
||||||
if (encoder == null) return;
|
MediaCodec mc = encoder;
|
||||||
|
if (mc == null) return;
|
||||||
try {
|
try {
|
||||||
Bundle params = new Bundle();
|
Bundle params = new Bundle();
|
||||||
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
|
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
|
||||||
encoder.setParameters(params);
|
mc.setParameters(params);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, "requestKeyFrame failed", e);
|
Log.w(TAG, "requestKeyFrame failed", e);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user