diff --git a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java index fea2d90..54da9e3 100644 --- a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java +++ b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java @@ -476,6 +476,13 @@ public class ScreenCaptureService extends Service { } }); 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; } 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 1d30813..0ad12d7 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 @@ -45,6 +45,7 @@ public class SelfCodecEncoder { private MediaCodec encoder; private Surface inputSurface; private VirtualDisplay virtualDisplay; + private MediaProjection.Callback projectionCallback; private boolean running = false; private int frameSeq = 0; private final Object sendLock = new Object(); @@ -88,6 +89,20 @@ public class SelfCodecEncoder { inputSurface = encoder.createInputSurface(); 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( "SelfCodec", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, inputSurface, null, null); @@ -246,6 +261,11 @@ public class SelfCodecEncoder { } } + /** 返回编码器是否正在运行(供上层判断启动结果)。 */ + public boolean isRunning() { + return running; + } + /** 即时请求一个关键帧(强求 IDR)。 */ public void requestKeyFrame() { if (encoder == null) return; @@ -260,6 +280,13 @@ public class SelfCodecEncoder { public void release() { running = false; + if (mediaProjection != null && projectionCallback != null) { + try { + mediaProjection.unregisterCallback(projectionCallback); + } catch (Exception ignored) { + } + projectionCallback = null; + } if (virtualDisplay != null) { try { virtualDisplay.release(); diff --git a/WebRTCController/app/src/main/java/com/ttstd/controller/webrtc/SelfCodecDecoder.java b/WebRTCController/app/src/main/java/com/ttstd/controller/webrtc/SelfCodecDecoder.java index 6a8cea1..d1606e3 100644 --- a/WebRTCController/app/src/main/java/com/ttstd/controller/webrtc/SelfCodecDecoder.java +++ b/WebRTCController/app/src/main/java/com/ttstd/controller/webrtc/SelfCodecDecoder.java @@ -98,9 +98,11 @@ public class SelfCodecDecoder { byte magic = b.get(); if (magic != MAGIC) return; int seq = b.getInt(); - // 关键修复:增加包序号过期检查(处理包乱序或重复)。 - // 实时视频流中,如果收到了序号更旧的包,直接丢弃以保证延迟和状态一致性。 - if (lastSeq != -1 && seq <= lastSeq && (lastSeq - seq) < 1000) { + // 过期检查:被控端每个“单元”共用同一个 seq(同一单元的所有分片 seq 相同、以 idx 区分), + // 因此不能用 seq<=lastSeq 来丢弃——否则同一单元除首片外的分片都会被误删, + // 导致大帧永远无法重组、自编码串流黑屏。这里只在 seq 明显小于 lastSeq(旧单元)时丢弃, + // 相同 seq(同单元的其它分片)放行。 + if (lastSeq != -1 && seq < lastSeq && (lastSeq - seq) < 1000) { return; } short total = b.getShort();