fix: 修复自编码模式黑屏问题
- 调整包序号过期检查逻辑,避免同一帧分片被误删 - 适配 Android 14,在创建 VirtualDisplay 前注册 MediaProjection.Callback - 启动后检查编码器实际运行状态,失败时进行清理
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user