fix: 修复自编码模式黑屏问题

- 调整包序号过期检查逻辑,避免同一帧分片被误删
- 适配 Android 14,在创建 VirtualDisplay 前注册 MediaProjection.Callback
- 启动后检查编码器实际运行状态,失败时进行清理
This commit is contained in:
TongTongStudio
2026-07-24 20:08:17 +08:00
parent 23698d0be7
commit 3792b75244
3 changed files with 39 additions and 3 deletions

View File

@@ -476,6 +476,13 @@ public class ScreenCaptureService extends Service {
} }
}); });
selfEncoder.start(); selfEncoder.start();
// start() 内部捕获异常后 running 仍为 false需据此判断真实启动结果
// 否则会误报 SELF_CODEC 模式已生效而实际无任何画面(黑屏)。
if (!selfEncoder.isRunning()) {
Log.e(TAG, "self codec encoder failed to start, cleaning up");
stopSelfCodecEncoder();
return false;
}
return true; return true;
} }

View File

@@ -45,6 +45,7 @@ public class SelfCodecEncoder {
private MediaCodec encoder; private MediaCodec encoder;
private Surface inputSurface; private Surface inputSurface;
private VirtualDisplay virtualDisplay; private VirtualDisplay virtualDisplay;
private MediaProjection.Callback projectionCallback;
private boolean running = false; private boolean running = false;
private int frameSeq = 0; private int frameSeq = 0;
private final Object sendLock = new Object(); private final Object sendLock = new Object();
@@ -88,6 +89,20 @@ public class SelfCodecEncoder {
inputSurface = encoder.createInputSurface(); inputSurface = encoder.createInputSurface();
encoder.start(); encoder.start();
// Android 14+ (API 34) 强制要求:在 createVirtualDisplay 之前必须先注册
// MediaProjection.Callback否则会抛
// IllegalStateException("Must register a callback before starting capture...")
// 导致自编码模式无法启动、控制端黑屏。onStop 时释放资源。
projectionCallback = new MediaProjection.Callback() {
@Override
public void onStop() {
Log.e(TAG, "MediaProjection stopped, releasing encoder");
release();
if (callback != null) callback.onEncoderError("MediaProjection stopped");
}
};
mediaProjection.registerCallback(projectionCallback, null);
virtualDisplay = mediaProjection.createVirtualDisplay( virtualDisplay = mediaProjection.createVirtualDisplay(
"SelfCodec", width, height, dpi, "SelfCodec", width, height, dpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, inputSurface, null, null); DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, inputSurface, null, null);
@@ -246,6 +261,11 @@ public class SelfCodecEncoder {
} }
} }
/** 返回编码器是否正在运行(供上层判断启动结果)。 */
public boolean isRunning() {
return running;
}
/** 即时请求一个关键帧(强求 IDR。 */ /** 即时请求一个关键帧(强求 IDR。 */
public void requestKeyFrame() { public void requestKeyFrame() {
if (encoder == null) return; if (encoder == null) return;
@@ -260,6 +280,13 @@ public class SelfCodecEncoder {
public void release() { public void release() {
running = false; running = false;
if (mediaProjection != null && projectionCallback != null) {
try {
mediaProjection.unregisterCallback(projectionCallback);
} catch (Exception ignored) {
}
projectionCallback = null;
}
if (virtualDisplay != null) { if (virtualDisplay != null) {
try { try {
virtualDisplay.release(); virtualDisplay.release();

View File

@@ -98,9 +98,11 @@ public class SelfCodecDecoder {
byte magic = b.get(); byte magic = b.get();
if (magic != MAGIC) return; if (magic != MAGIC) return;
int seq = b.getInt(); int seq = b.getInt();
// 关键修复:增加包序号过期检查(处理包乱序或重复)。 // 过期检查:被控端每个“单元”共用同一个 seq同一单元的所有分片 seq 相同、以 idx 区分),
// 实时视频流中,如果收到了序号更旧的包,直接丢弃以保证延迟和状态一致性。 // 因此不能用 seq<=lastSeq 来丢弃——否则同一单元除首片外的分片都会被误删,
if (lastSeq != -1 && seq <= lastSeq && (lastSeq - seq) < 1000) { // 导致大帧永远无法重组、自编码串流黑屏。这里只在 seq 明显小于 lastSeq旧单元时丢弃
// 相同 seq同单元的其它分片放行。
if (lastSeq != -1 && seq < lastSeq && (lastSeq - seq) < 1000) {
return; return;
} }
short total = b.getShort(); short total = b.getShort();