实现 Alpha 透明特效
场景概述
Alpha 透明特效是指通过调整图像中每个像素的 Alpha 通道值来控制其透明度,从而实现不同的视觉效果,如渐变和阴影。在各种实时音视频互动场景下,将主播的背景进行人像分割和透明特效处理可以使互动过程更加有趣,提升互动的沉浸感、改善互动体验。以下是一些比较典型的场景:
- 动态礼物特效:提供透明背景的动态礼物效果,避免多视频流合并时遮挡直播内容。
- 主播背景替换:观众端可以将画面中的主播背景替换成虚拟场景,比如游戏场景、发布会、旅游景点等。
- 游戏直播时对主播抠图:观众端可以对主播人像进行抠图,然后放在本地游戏画面的某个位置,使主播看起来像是在游戏中。
前提条件
已经在项目中实现了基本的实时音视频功能。详见实现音视频互动。
实现方案
根据实际业务场景选择一种实现 Alpha 透明特效的方法。
SDK 采集场景
该场景实现流程如下图所示:
-
在使用
VideoSurface
组件渲染本地视频时,调用SetLocalVideoDataSourcePosition
获取视频帧的位置。将position
设为POSITION_POST_CAPTURER
,表示获取本地视频采集完成后的视频帧。C#//设置本地视频帧的位置为 POSITION_POST_CAPTURER
RtcEngine.SetLocalVideoDataSourcePosition(VIDEO_MODULE_POSITION.POSITION_POST_CAPTURER); -
在发送端调用
EnableVirtualBackground
开启背景分割算法,得到人像区域的 Alpha 数据。建议设置参数如下:enabled
:设置为true
,开启虚拟背景。backgroundSourceType
:设置为BACKGROUND_NONE
(0),即:将人像和背景进行分割,并将背景处理为 Alpha 通道。
C#var source = new VirtualBackgroundSource();
source.background_source_type = BACKGROUND_SOURCE_TYPE.BACKGROUND_NONE;
var segproperty = new SegmentationProperty();
var nRet = RtcEngine.EnableVirtualBackground(true, source, segproperty, MEDIA_SOURCE_TYPE.PRIMARY_CAMERA_SOURCE);
this.Log.UpdateLog("EnableVirtualBackground true :" + nRet); -
调用
SetVideoEncoderConfiguration
将encodeAlpha
设为true
,将 Alpha 信息进行编码并发送至远端,然后调用JoinChannel
[2/2] 加入频道。C#VideoEncoderConfiguration videoEncoderConfiguration = new VideoEncoderConfiguration();
videoEncoderConfiguration.advanceOptions.encodeAlpha = true;
RtcEngine.SetVideoEncoderConfiguration(videoEncoderConfiguration);
RtcEngine.JoinChannel(_token, _channelName, "", 0); -
通过
VideoSurfaceYUVA
组件来渲染带有 Alpha 透明特效的视图。C#private VideoSurface MakeImageSurface(string goName)
{
......
// 使用 VideoSurfaceYUVA 进行渲染
VideoSurface videoSurface = go.AddComponent<VideoSurfaceYUVA>();
return videoSurface;
}
自定义视频采集场景
该场景实现流程如下图所示:
声网提供以下两种方案把自采集且带有 Alpha 信息的视频帧推送给 SDK 发布至频道内。你可以根据需求选择一种来实现。
由于 Unity SDK 的限制,目前无法在本地通过 VideoSurfaceYUVA
组件来预览需要推送的自定义采集的视频流,建议在接收端查看推流效果。
- 为视频帧设置 Alpha 信息
- 将视频数据和 Alpha 数据拼接
你可以参考下列步骤来对自采集的视频数据进行处理、在频道内推送自采集的视频流、并为每一帧单独设置 Alpha 信息,以实现动态视觉效果。下列示例代码以推送下图为例。
-
处理纹理数据。将 Texture2D 对象的颜色数据提取并转换为两个字节数组,一个包含 RGBA 数据,另一个包含 Alpha 通道数据,以便后续处理和传输。
C#// Read/Write Enable 属性设置为 true,并且绑定到 TextureWithAlphaBuffer 变量上
public Texture2D TextureWithAlphaBuffer;
private byte[] _rgbaData = null;
private byte[] _alphaData = null;
private int _textureWidth;
private int _textureHeight;
// 自定义视频轨道 ID
private uint _videoTrackId = 0;
// 将颜色数据从 Color32 数组转换为 rgbaData 和 alphaData 数组
private void ConvertColor32(Color32[] colors, byte[] rgbaData, byte[] alphaData)
{
int i = 0;
int l = 0;
foreach (var color in colors)
{
rgbaData[i++] = color.r;
rgbaData[i++] = color.g;
rgbaData[i++] = color.b;
rgbaData[i++] = color.a;
alphaData[l++] = color.a;
}
}
// 从纹理中读取颜色数据,并初始化 _rgbaData 和 _alphaData 数组,同时记录纹理的宽度和高度
public void InitTextureDataWithAlphaBuffer()
{
Color32[] color32s = TextureWithAlphaBuffer.GetPixels32(0);
_rgbaData = new byte[color32s.Length * 4];
_alphaData = new byte[color32s.Length];
ConvertColor32(color32s, _rgbaData, _alphaData);
_textureWidth = TextureWithAlphaBuffer.width;
_textureHeight = TextureWithAlphaBuffer.height;
this.Log.UpdateLog(string.Format("AlphaBuffer width: {0}, height: {1}", _textureWidth, _textureHeight));
} -
配置视频编码参数来把 Alpha 数据发送至远端。调用
SetVideoEncoderConfiguration
设置视频编码属性,将encodeAlpha
设为true
来编码并发送 Alpha 数据至远端。 -
创建一个视频轨道用于发布自定义采集的视频到频道内。调用
CreateCustomVideoTrack
创建一个自定义的视频轨道。 -
调用
JoinChannel
[2/2] 加入频道,并设置频道媒体选项:- 将
customVideoTrackId
设置为第 3 步中获得的视频轨道 ID。 - 将
publishCameraTrack
设为false
,不发布摄像头采集的视频流。 - 将
publishCustomVideoTrack
设置为true
,发布自定义采集的视频流。
示例代码如下:
C#public void OnStartPushVideoFrameWithAlphaBuffer()
{
// 读取图片中的 RGBA 数据和 Alpha 数据
InitTextureDataWithAlphaBuffer();
// 设置视频编码属性
VideoEncoderConfiguration videoEncoderConfiguration = new VideoEncoderConfiguration();
videoEncoderConfiguration.dimensions.width = _textureWidth;
videoEncoderConfiguration.dimensions.height = _textureHeight;
// 设置发送编码 Alpha 数据
videoEncoderConfiguration.advanceOptions.encodeAlpha = true;
RtcEngine.SetVideoEncoderConfiguration(videoEncoderConfiguration);
// 创建自定义视频轨道用于在频道内发布自定义采集的视频流
_videoTrackId = RtcEngine.CreateCustomVideoTrack();
// 设置频道媒体选项
var options = new ChannelMediaOptions();
// 不发布摄像头采集的视频流
options.publishCameraTrack.SetValue(false);
// 发布麦克风采集的音频流
options.publishMicrophoneTrack.SetValue(true);
// 发布自定义采集的视频流
options.publishCustomVideoTrack.SetValue(true);
// 把 customVideoTrackId 设为你在调用 CreateCustomVideoTrack 方法时返回的视频轨道 ID
options.customVideoTrackId.SetValue(_videoTrackId);
// 加入频道
RtcEngine.JoinChannel(_token, _channelName, 0, options)
InvokeRepeating("UpdateForPushVideoFrameWithAlphaBuffer", 0.1f, 0.1f);
} - 将
-
推送自定义采集的视频流至 SDK。调用
PushVideoFrame
方法来推送视频帧,同时为视频帧设置 Alpha 数据。你可以通过设置alphaBuffer
或fillAlphaBuffer
参数来为视频帧设置 Alpha 通道数据。alphaBuffer
:Alpha 通道数据。该数据跟视频帧的尺寸一致,每个像素点的取值范围为 [0,255],其中 0 代表背景;255 代表前景(人像)。fillAlphaBuffer
:将该参数设为true
后 SDK 可自动提取并填充 Alpha 通道数据,无需额外设置alphaBuffer
。fillAlphaBuffer
参数目前仅支持 BGRA 或 RGBA 格式的视频数据。
注意请务必确保
alphaBuffer
跟视频帧的尺寸 (width × height) 完全一致,否则可能会导致 App 崩溃。C#public void UpdateForPushVideoFrameWithAlphaBuffer()
{
var timetick = System.DateTime.Now.Ticks / 10000;
ExternalVideoFrame externalVideoFrame = new ExternalVideoFrame();
// 视频 buffer 类型为原始数据
externalVideoFrame.type = VIDEO_BUFFER_TYPE.VIDEO_BUFFER_RAW_DATA;
// 视频像素为 RGBA 格式
externalVideoFrame.format = VIDEO_PIXEL_FORMAT.VIDEO_PIXEL_RGBA;
externalVideoFrame.buffer = _rgbaData;
externalVideoFrame.alphaStitchMode = ALPHA_STITCH_MODE.NO_ALPHA_STITCH
externalVideoFrame.alphaBuffer = null;
// 提取并填充 Alpha 通道数据,当前仅对 RGBA 或 BGRA 格式的视频数据生效
externalVideoFrame.fillAlphaBuffer = true;
// 如果你要推送的视频数据格式不是 RGBA 或 BGRA,你需要设置 alphaBuffer 参数,并将 fillAlphaBuffer 设为 false
//externalVideoFrame.alphaBuffer = _alphaData;
//externalVideoFrame.fillAlphaBuffer = false
externalVideoFrame.stride = _textureWidth;
externalVideoFrame.height = _textureHeight;
externalVideoFrame.rotation = 180;
externalVideoFrame.timestamp = timetick;
// 推送自定义采集的视频至 SDK
var ret = RtcEngine.PushVideoFrame(externalVideoFrame, _videoTrackId);
Debug.Log("PushVideoFrame ret = " + ret + " time: " + timetick);
}
参考下列步骤将采集的视频数据与 Alpha 通道数据进行拼接,并将拼接后的视频数据推送至 SDK 以发布到频道内。下列步骤展示如何将 Alpha 通道数据拼接至视频数据上方。
原始视频数据:
拼接后的数据:
-
把采集的视频数据和 Alpha 数据拼接。
C#public void InitTextureDataWithAlphaStitchMode()
{
// 读取原始纹理数据
Color32[] color32s = TextureWithAlphaBuffer.GetPixels32(0);
// 获取原始纹理的宽度和高度
var width = TextureWithAlphaBuffer.width;
var height = TextureWithAlphaBuffer.height;
// 创建一个新的纹理,其高度是原始纹理高度的两倍
TextureWithAlphaStitchMode = new Texture2D(width, height * 2, TextureFormat.RGBA32, false);
// 初始化一个新的颜色数组,用于存储拼接后的像素数据
Color32[] newColor32s = new Color32[color32s.Length * 2];
// 将原始颜色数据复制到新颜色数组的上半部分
Array.Copy(color32s, newColor32s, color32s.Length);
// 将 Alpha 通道数据拼接到新颜色数组的下半部分
for (int i = color32s.Length; i < newColor32s.Length; i++)
{
newColor32s[i].r = color32s[i - color32s.Length].a;
newColor32s[i].g = newColor32s[i].r;
newColor32s[i].b = newColor32s[i].r;
newColor32s[i].a = 255;
}
// 将新颜色数组应用到新的纹理中
TextureWithAlphaStitchMode.SetPixels32(newColor32s);
TextureWithAlphaStitchMode.Apply();
// 转换颜色数据并存储在 _rgbaData 和 _alphaData 数组中
_rgbaData = new byte[newColor32s.Length * 4];
_alphaData = new byte[newColor32s.Length];
ConvertColor32(newColor32s, _rgbaData, _alphaData);
// 更新纹理宽度和高度
_textureWidth = TextureWithAlphaStitchMode.width;
_textureHeight = TextureWithAlphaStitchMode.height;
this.Log.UpdateLog(string.Format("AlphaStitchMode width: {0}, height: {1}", _textureWidth, _textureHeight));
// 将新纹理绑定到 UI 中的 RawImage 组件
GameObject.Find("RawImage").GetComponent<RawImage>().texture = TextureWithAlphaStitchMode;
GameObject.Find("RawImage").GetComponent<RectTransform>().sizeDelta = new Vector2(_textureWidth, _textureHeight);
} -
配置视频编码参数来把 Alpha 数据发送至远端。调用
SetVideoEncoderConfiguration
设置视频编码属性,将encodeAlpha
设为true
来编码并发送 Alpha 数据至远端。 -
创建一个视频轨道用于发布自定义采集的视频到频道内。调用
CreateCustomVideoTrack
创建一个自定义的视频轨道。 -
调用
JoinChannel
[2/2] 加入频道,并设置频道媒体选项:- 将
customVideoTrackId
设置为第 3 步中获得的视频轨道 ID。 - 将
publishCameraTrack
设为false
,不发布摄像头采集的视频流。 - 将
publishCustomVideoTrack
设置为true
,发布自定义采集的视频流。
示例代码如下:
C#public void OnStartPushVideoFrameWithAlphaStitchMode()
{
InitTextureDataWithAlphaStitchMode();
VideoEncoderConfiguration videoEncoderConfiguration = new VideoEncoderConfiguration();
videoEncoderConfiguration.dimensions.width = _textureWidth;
//实际发送的高度为拼接后纹理高度的一半
videoEncoderConfiguration.dimensions.height = _textureHeight / 2;
// 编码并发送 Alpha 数据至远端
videoEncoderConfiguration.advanceOptions.encodeAlpha = true;
// 设置视频编码属性
RtcEngine.SetVideoEncoderConfiguration(videoEncoderConfiguration);
// 创建自定义视频轨道用于在频道内发布自定义采集的视频流
_videoTrackId = RtcEngine.CreateCustomVideoTrack();
var options = new ChannelMediaOptions();
// 设置不发布摄像头采集的视频流
options.publishCameraTrack.SetValue(false);
// 设置发布麦克风采集的音频流
options.publishMicrophoneTrack.SetValue(true);
// 设置发布自定义采集的视频流
options.publishCustomVideoTrack.SetValue(true);
// 把 customVideoTrackId 设为你在调用 CreateCustomVideoTrack 方法时返回的视频轨道 ID
options.customVideoTrackId.SetValue(_videoTrackId);
// 加入频道
RtcEngine.JoinChannel(_token, _channelName, 0, options);
InvokeRepeating("UpdateForPushVideoFrameWithAlphaStitchMode", 0.1f, 0.1f);
} - 将
-
推送拼接后的视频帧至 SDK。调用
PushVideoFrame
方法并通过alphaStitchMode
参数来设置 Alpha 数据和视频帧的相对位置。示例代码如下:
C#public void UpdateForPushVideoFrameWithAlphaStitchMode()
{
var timetick = System.DateTime.Now.Ticks / 10000;
ExternalVideoFrame externalVideoFrame = new ExternalVideoFrame();
// 视频 buffer 类型为原始数据
externalVideoFrame.type = VIDEO_BUFFER_TYPE.VIDEO_BUFFER_RAW_DATA;
// 视频像素为 RGBA 格式
externalVideoFrame.format = VIDEO_PIXEL_FORMAT.VIDEO_PIXEL_RGBA;
externalVideoFrame.buffer = _rgbaData;
//由于 Unity 纹理坐标和 SDK 坐标原点不同,如需将 Alpha 数据拼接至原始数据的上方,则这里 SDK 读取时需要设置拼接位置为下方,
externalVideoFrame.alphaStitchMode = ALPHA_STITCH_MODE.ALPHA_STITCH_BELOW;
externalVideoFrame.stride = _textureWidth;
externalVideoFrame.height = _textureHeight;
externalVideoFrame.rotation = 180;
externalVideoFrame.timestamp = timetick;
// 推送视频帧至 SDK
var ret = RtcEngine.PushVideoFrame(externalVideoFrame, _videoTrackId);
Debug.Log("PushVideoFrame ret = " + ret + " time: " + timetick);
}