Android WebRTC 黑屏问题实践

基于Android WebRTC 画面黑屏排查 和VideoFrame两文,我们应该已经具备了一定的黑屏问题的排查能力,根据黑屏排查1文当中的第三种情况,我们来实践一下看看能不能解决实际问题。

问题

我们正在进行 P2P 的投屏,假设 A 设备现在已经投屏到 B 设备,也就是 A 是采集端,B 是渲染端。 我们现在看下B渲染端的展示情况:

据上图我们看到顶部是手机本机的状态栏,状态栏之下就是投屏出来的 A 设备的桌面情况。画面的中心有个 Toggle 按钮,点击 Toggle 按钮会进行投屏的全屏状态切换到小悬浮窗状态,如下图:

如果再从小窗状态去点击 toggle 按钮则再回到全屏状态,好了上面都是展示的正常情况。

那么问题来了:此时如果 A 端的画面是静止桌面,我们去从小窗模式点击 toggle 则会产生如下效果:

从小窗切回全屏模式,渲染端画面没有填充全屏,产生了很大块的黑边,用户体验很差,从完整展示的大屏切到小窗也是同理,这个过程的刷新是直到 A 端渲染端产生了新的 VideoFrame 发送到了 B 这个问题才会消失,最长可能长达数秒!

方案1 SurfaceView 刷新

渲染端的核心类是 SurfaceViewRenderer 它也是继承了 SurfaceView ,既然这样我们能不能去调用 Android View 体系的方法进行刷新?于是我在点击 Toggle 的时候新增了一个 Listener 尝试了如下方法:

java 复制代码
requestLayout()

postInvalidate()

然而两个方法并没有像我预期的一样起作用给黑屏区域刷新过来。

根据我查阅 WebRTC 渲染线程的一些代码中会出现 GLES20 的一些字样,我推测为什么 Android View 体系的刷新方法不起作用的原因可能就是底下的刷新渲染是基于 OpenGL 的实现,里面的着色器和纹理对于 Android View 的刷新体系不生效,该方案宣告失败

方案2 采集端帧补偿

Android WebRTC 画面黑屏排查 依据文中在采集端 SurfaceTextureHelper 核心代码中提供了一个

java 复制代码
/**
 * Forces a frame to be produced. If no new frame is available, the last frame is sent to the
 * listener again.
 */
public void forceFrame() {
  handler.post(() -> {
    hasPendingTexture = true;
    tryDeliverTextureFrame();
  });
}

根据方法注释这种情况,我们调下强刷方法刷一下不就行了吗?但是这个强刷的时机怎么通知怎么弄?

见上图简单示意,首先我们需要在信令服务器层定义一个新的信令,用于 B 通知 A。

java 复制代码
public enum DataModelType {
 NotifyCaptureFrameRefresh
}

然后我们在 B 端点击 toggle 时发送这个信令到 A

java 复制代码
val data = XxxDataModel(XxxDataModel.DataModelType.NotifyCaptureFrameRefresh,xxx,it,null)
mSignalingChannel.sendTcpMessage(data)

A 监听这个信令,然后调强刷

java 复制代码
XxxDataModel.DataModelType.NotifyCaptureFrameRefresh -> {//收到渲染端大小窗切换事件做采集帧补偿刷新
    forceRefresh()
}

信令没有成功发送到 B 设备,排查原因是因为被 WebRTC 拦截了

java 复制代码
/**
 * Applies the frame adaptation parameters to a frame. Returns null if the frame is meant to be
 * dropped. Returns a new frame. The caller is responsible for releasing the returned frame.
 */
public static @Nullable VideoFrame applyFrameAdaptationParameters(
java 复制代码
/**
 * This function should be called before delivering any frame to determine if the frame should be
 * dropped or what the cropping and scaling parameters should be. If the return value is null, the
 * frame should be dropped, otherwise the frame should be adapted in accordance to the frame
 * adaptation parameters before calling onFrameCaptured().
 */
@Nullable
public VideoProcessor.FrameAdaptationParameters adaptFrame(VideoFrame frame) {
  return nativeAdaptFrame(nativeAndroidVideoTrackSource, frame.getBuffer().getWidth(),
      frame.getBuffer().getHeight(), frame.getRotation(), frame.getTimestampNs());
}

这个函数应该在传递任何帧之前调用,以确定是否应该丢弃帧,或者裁剪和缩放参数应该是什么。如果返回值为 null,则应该丢弃帧,否则应根据帧适应参数调整帧,然后调用 onFrameCaptured()。

通过注释文中代码,屏蔽了RTC的抛弃机制,然后进行A-B的帧补偿,解决了窗口切换黑屏问题。

方案3 渲染端帧补偿

第三种思路是在渲染端每次去保留一帧 lastVideoFrame ,然后在 toggle 的时候直接去 onFrame 应用这帧,这个思路比方案 2 更节约一次通信。适合截留的位置建议在 SurfaceViewRenderer 的 onFrame() 方法中。注意在操作视频帧的时候特别需要注意的是 retain & release 的处理,稍有不慎就会产生 crash 和 native crash ,详可见我的上一篇文章: VideoFrame 。

相关推荐
中屹指纹浏览器svip3 天前
WebRTC泄露是什么,怎么关闭?
webrtc
VimorphTech3 天前
文件名是 MP4 就能处理?用 ffprobe 检查视频流、音轨和时长
java·音视频开发
海边捡石子4 天前
从零设计一个通用智能电话平台:FreeSWITCH + ESL + AI 实战架构
人工智能·架构·webrtc
音视频牛哥4 天前
不换摄像机,也能接入 WebRTC?SmartMediaKit RTSP/RTMP 转 WHIP 技术实践
音视频·webrtc·smartmediakit·rtsp转whip·rtmp转whip·whip推流·rtsp转webrtc播放
冬奇Lab5 天前
开源项目第242期:HyperFrames —— 写 HTML 就能渲染视频,专为 AI Agent 设计的确定性视频框架
人工智能·开源·音视频开发
直播技术开发交流6 天前
Android 端 WHIP 协议推流技术实践:相机/录屏/多路转发WHIP实现测试
webrtc·android whip·rtsp转whip·毫秒级实时媒体流·whip低延时推送·whip推送实现·rtsp转webrtc
程序员阿明8 天前
docker搭建视频流服务go2rtc,webRTC
docker·容器·webrtc
屏什么录11 天前
电脑如何录屏幕视频和声音?从工具到代码,7 种方案帮你彻底搞定
前端·音视频开发
O。O蛋黄酥啊11 天前
实时语音 Agent 开发完全指南:从 ASR/TTS 到 WebRTC/VAD 的链路拆解
大模型·webrtc·语音识别·tts·asr
VimorphTech11 天前
转写结果怎么生成双语字幕?TypeScript 实现 SRT、WebVTT 与索引匹配
javascript·音视频开发