实现 Alpha 透明特效
场景概述
在各种实时音视频互动场景下,将主播的背景进行人像分割和透明特效处理可以使互动过程更加有趣,提升互动的沉浸感、改善互动体验。以下是一些比较典型的场景:
- 动态礼物特效:提供透明背景的动态礼物效果,避免多视频流合并时遮挡直播内容。
- 主播背景替换:观众端可以将画面中的主播背景替换成虚拟场景,比如游戏场景、发布会、旅游景点等。
- 游戏直播时对主播抠图:观众端可以对主播人像进行抠图,然后放在本地游戏画面的某个位置,使主播看起来像是在游戏中。
前提条件
已经在项目中实现了基本的实时音视频功能。详见实现音视频互动。
实现方案
根据实际业务场景,在以下方案中选择一种实现 Alpha 透明特效的方法。
本文的 API 和示例代码均以 Android 平台为例。如目标平台为其他平台,请参考对应平台的 API 文档。
自定义视频采集场景
该场景实现流程如下图所示:
-
将采集到的视频数据推送到 SDK,同时设置视频帧中的 Alpha 数据。有以下两种方式可供选择。
-
方案一:调用
pushExternalVideoFrame
[2/2] 方法,并通过设置alphaBuffer
参数为视频帧指定 Alpha 通道数据。该数据跟视频帧的尺寸一致,每个像素点的取值范围为 [0,255],其中 0 代表背景;255 代表前景(人像)。警告请务必确保
alphaBuffer
跟视频帧的尺寸 (width × height) 完全一致,否则可能会导致 App 崩溃。JavaJavaI420Buffer javaI420Buffer = JavaI420Buffer.wrap(width, height, dataY, width, dataU, strideUV, dataV, strideUV, null);
VideoFrame frame = new VideoFrame(javaI420Buffer, 0, timestamp);
ByteBuffer alphaBuffer = ByteBuffer.allocateDirect(width * height);
frame.fillAlphaData(alphaBuffer);
rtcEngine.pushExternalVideoFrame(frame); -
方案二:调用
pushExternalVideoFrame
[2/2] 方法,并通过调用VideoFrame
类中的setAlphaStitchMode
方法设置 Alpha 的拼接方式,构造一个拼接了 Alpha 数据的VideoFrame
。JavaJavaI420Buffer javaI420Buffer = JavaI420Buffer.wrap(width, height, dataY, width, dataU, strideUV, dataV, strideUV, null);
VideoFrame frame = new VideoFrame(javaI420Buffer, 0, timestamp);
// 设置 Alpha 拼接方式,以下示例代码中设置 Alpha 在视频图像下方
frame.setAlphaStitchMode(Constants.VIDEO_ALPHA_STITCH_BELOW);
rtcEngine.pushExternalVideoFrame(frame);
-
-
渲染本地视图并实现 Alpha 透明特效。
- 创建一个
TextureView
对象用于渲染本地视图。 - 调用
setupLocalVideo
方法设置本地视图:- 设置
enableAlphaMask
参数为true
,开启 Alpha 遮罩渲染。 - 设置
TextureView
为本地视频的显示窗口。
- 设置
Java// 发送端有 Alpha 数据输入,并且开启 Alpha 传输
VideoEncoderConfiguration config = new VideoEncoderConfiguration(...);
// 设置编码参数的时候需要开启 Alpha 传输
config.advanceOptions.encodeAlpha = true;
rtcEngine.setVideoEncoderConfiguration(videoEncoderConfiguration)
TextureView textureView = new TextureView(context);
textureView.setOpaque(false);
fl_local.addView(textureView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
VideoCanvas local = new VideoCanvas(textureView, RENDER_MODE_FIT, 0);
local.enableAlphaMask = true;
rtcEngine.setupLocalVideo(local); - 创建一个
-
渲染远端视频流在本地的视图并实现 Alpha 透明特效。
- 在收到
onUserJoined
回调后,创建一个TextureView
对象用于渲染远端视图。 - 调用
setupRemoteVideo
方法设置远端视图:- 设置
enableAlphaMask
参数为true
,开启 Alpha 遮罩渲染。 - 设置远端视频流在本地视频的显示窗口。
- 设置
JavaTextureView textureView = new TextureView(context);
textureView.setOpaque(false);
fl_remote.addView(textureView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
VideoCanvas remote = new VideoCanvas(textureView, VideoCanvas.RENDER_MODE_FIT, uid);
remote.enableAlphaMask = true;
rtcEngine.setupRemoteVideo(remote); - 在收到
SDK 采集场景
该场景实现流程如下图所示:
-
在发送端调用
enableVirtualBackground
[2/2] 开启背景分割算法,得到人像区域的 Alpha 数据。建议设置参数如下:enabled
:设置为true
,开启虚拟背景。backgroundSourceType
:设置为BACKGROUND_NONE
(0),即:将人像和背景进行分割,并将背景处理为 Alpha 数据。
JavaVirtualBackgroundSource virtualBackgroundSource = new VirtualBackgroundSource(...);
virtualBackgroundSource.backgroundSourceType = VirtualBackgroundSource.BACKGROUND_NONE;//仅生成alpha,不替换背景
SegmentationProperty segmentationProperty = new SegmentationProperty(...);
rtcEngine.enableVirtualBackground(true, virtualBackgroundSource, segmentationProperty, sourceType); -
在发送端调用
setVideoEncoderConfiguration
设置视频编码属性,将encodeAlpha
设置为true
,则 Alpha 数据会被编码并发送到远端。JavaVideoEncoderConfiguration videoEncoderConfiguration = new VideoEncoderConfiguration(...);
videoEncoderConfiguration.advanceOptions = new VideoEncoderConfiguration.AdvanceOptions(...);
videoEncoderConfiguration.advanceOptions.encodeAlpha = true;
rtcEngine.setVideoEncoderConfiguration(videoEncoderConfiguration); -
渲染本地和远端视图并实现 Alpha 透明特效。详见自定义视频采集场景中的步骤。
原始视频数据场景
该场景实现流程如下图所示:
-
调用
registerVideoFrameObserver
方法注册一个原始视频观测器,并根据需要注册相应回调。Java// 注册 IVideoFrameObserver
public class MyVideoFrameObserver implements IVideoFrameObserver{
@Override
public boolean onRenderVideoFrame(String channelId, int uId, VideoFrame videoFrame) {
...
return false;
}
@Override
public boolean onCaptureVideoFrame(int type, VideoFrame videoFrame) {
...
return false;
}
@Override
public boolean onPreEncodeVideoFrame(int type, VideoFrame videoFrame) {
...
return false;
}
}
MyVideoFrameObserver observer = new MyVideoFrameObserver()
rtcEngine.registerVideoFrameObserver(observer); -
通过
onCaptureVideoFrame
回调获取采集后的视频数据并根据需要进行前处理,可以修改 Alpha 数据或直接添加 Alpha 数据。Javapublic boolean onCaptureVideoFrame(int type, VideoFrame videoFrame) {
// 修改 Alpha 数据或直接添加 Alpha 数据
ByteBuffer alphaBuffer = videoFrame.getAlphaBuffer();
...
videoFrame.fillAlphaData(byteBuffer);
return false;
} -
通过
onPreEncodeVideoFrame
回调获取本地视频编码前的视频数据,可以修改 Alpha 数据或直接添加 Alpha 数据。Javapublic boolean onPreEncodeVideoFrame(int type, VideoFrame videoFrame) {
// 修改 Alpha 数据或直接添加 Alpha 数据
ByteBuffer alphaBuffer = videoFrame.getAlphaBuffer();
...
videoFrame.fillAlphaData(byteBuffer);
return false;
} -
通过
onRenderVideoFrame
回调获取远端发送的视频在本地渲染前的视频数据,可以修改 Alpha 数据、直接添加 Alpha 数据或根据得到的 Alpha 数据自行渲染视频图像。Javapublic boolean onRenderVideoFrame(int type, VideoFrame videoFrame) {
// 修改 Alpha 数据、直接添加 Alpha 数据或根据得到的 Alpha 数据自行渲染视频图像
ByteBuffer alphaBuffer = videoFrame.getAlphaBuffer();
...
videoFrame.fillAlphaData(byteBuffer);
return false;
}
开发注意事项
你需要自行实现 App 窗口的透明度属性,并处理多个 App 窗口叠加时的透明度关系(通过调整窗口的 zOrder
实现)。除此之外,对于不同的目标平台,需遵循以下限制:
- 对于 Android 平台,由于系统限制,设置视图时仅支持使用
TextureView
、不支持使用SurfaceView
。 - 对于 iOS 平台,在设置视图时需要将该视图
layer
属性中的opaque
设置为false
。 - 对于 maOS 平台,不需要额外设置窗口的透明属性,也可以遵循上一条 iOS 的设置方法。
- 对于 Windows 平台,你需要在创建项目时使用
CreateWindowEx
创建单独的窗口,并设置高透明属性。如果目标窗口是子窗口,则需要额外指定WS_POPUP
属性。