fix: 修复自编码模式分辨率切换及画面拉伸问题
- 重启编码器以支持动态分辨率切换 - 同步调整解码端 SurfaceView 尺寸以保持宽高比 - 修复 MediaCodec Image 未及时关闭导致的潜在崩溃
This commit is contained in:
@@ -355,6 +355,9 @@ public class ScreenCaptureService extends Service {
|
||||
// 关键:分辨率切换后,如果是自编码模式,同样需要触发扰动确保产生首帧,避免静态画面黑屏。
|
||||
if (currentStreamMode == SelfCodecEncoder.STREAM_MODE_SELF_CODEC) {
|
||||
updateNotification("正在切换分辨率...");
|
||||
// 自编码器(MediaCodec)不支持动态切换分辨率,必须重启编码器实例。
|
||||
stopSelfCodecEncoder();
|
||||
startSelfCodecEncoder();
|
||||
}
|
||||
|
||||
Log.i(TAG, "Capture resolution changed -> " + targetW + "x" + targetH + " @ " + targetFps + "fps (remote=" + fromRemote + ")");
|
||||
|
||||
@@ -147,22 +147,31 @@ public class SelfCodecEncoder {
|
||||
* 当编码器输入缓冲不足时会丢弃该帧(编码器跟不上),保证不堆积、不阻塞采集线程。
|
||||
*/
|
||||
public void feedFrame(VideoFrame frame) {
|
||||
MediaCodec mc = encoder;
|
||||
if (!running || mc == null) return;
|
||||
Integer idx;
|
||||
synchronized (inputLock) {
|
||||
idx = freeInputIndices.poll();
|
||||
}
|
||||
if (idx == null) {
|
||||
return; // 丢掉这一帧,等待下一个空闲输入缓冲
|
||||
}
|
||||
try {
|
||||
encodeFrame(mc, idx, frame);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "encodeFrame failed", e);
|
||||
synchronized (this) {
|
||||
MediaCodec mc = encoder;
|
||||
if (!running || mc == null) return;
|
||||
|
||||
// 检查分辨率:自编码器 MediaCodec 尺寸是固定的,若采集分辨率已切换则丢弃本帧,
|
||||
// 等待外部(ScreenCaptureService)重启本编码器实例。
|
||||
if (frame.getRotatedWidth() != width || frame.getRotatedHeight() != height) {
|
||||
return;
|
||||
}
|
||||
|
||||
Integer idx;
|
||||
synchronized (inputLock) {
|
||||
idx = freeInputIndices.poll();
|
||||
}
|
||||
if (idx == null) {
|
||||
return; // 丢掉这一帧,等待下一个空闲输入缓冲
|
||||
}
|
||||
try {
|
||||
mc.queueInputBuffer(idx, 0, 0, System.nanoTime() / 1000, 0);
|
||||
} catch (Exception ignored) {
|
||||
encodeFrame(mc, idx, frame);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "encodeFrame failed", e);
|
||||
try {
|
||||
mc.queueInputBuffer(idx, 0, 0, System.nanoTime() / 1000, 0);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,14 +227,15 @@ public class SelfCodecEncoder {
|
||||
|
||||
/** 把 NV12 按编码器输入 Image 的 stride 写入输入缓冲并入队。 */
|
||||
private void writeNv12(MediaCodec mc, int index, byte[] nv12, int w, int h, long ptsUs) {
|
||||
Image image = mc.getInputImage(index);
|
||||
if (image == null) {
|
||||
mc.queueInputBuffer(index, 0, 0, ptsUs, 0);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 使用 try-with-resources 确保 Image 及时关闭。
|
||||
// 根据 MediaCodec 文档,Image 必须在 queueInputBuffer 之前关闭,否则可能导致 native 崩溃或阻塞。
|
||||
try (Image image = mc.getInputImage(index)) {
|
||||
if (image == null) {
|
||||
mc.queueInputBuffer(index, 0, 0, ptsUs, 0);
|
||||
return;
|
||||
}
|
||||
Image.Plane[] planes = image.getPlanes();
|
||||
// Y 平面写入,添加边界检查防止部分设备 buffer limit 偏移导致的 Crash
|
||||
// Y 平面写入
|
||||
Image.Plane yPlane = planes[0];
|
||||
ByteBuffer yBuf = yPlane.getBuffer();
|
||||
int yRow = yPlane.getRowStride();
|
||||
@@ -259,7 +269,6 @@ public class SelfCodecEncoder {
|
||||
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) {
|
||||
@@ -282,7 +291,6 @@ public class SelfCodecEncoder {
|
||||
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]);
|
||||
}
|
||||
@@ -292,7 +300,8 @@ public class SelfCodecEncoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 显式关闭 image 确保在 queueInputBuffer 前释放 native 句柄
|
||||
image.close();
|
||||
mc.queueInputBuffer(index, 0, w * h * 3 / 2, ptsUs, 0);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "writeNv12 failed", e);
|
||||
@@ -463,19 +472,21 @@ public class SelfCodecEncoder {
|
||||
}
|
||||
|
||||
public void release() {
|
||||
running = false;
|
||||
freeInputIndices.clear();
|
||||
if (encoder != null) {
|
||||
try {
|
||||
encoder.stop();
|
||||
} catch (Exception ignored) {
|
||||
synchronized (this) {
|
||||
running = false;
|
||||
freeInputIndices.clear();
|
||||
if (encoder != null) {
|
||||
try {
|
||||
encoder.stop();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
encoder.release();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
encoder = null;
|
||||
}
|
||||
try {
|
||||
encoder.release();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
encoder = null;
|
||||
nv12Buffer = null;
|
||||
}
|
||||
nv12Buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user