虚拟直播既可以实现单人视频直播,也可以邀请观众上麦、进行多人连麦互动。
虚拟直播场景的主要架构如下图所示(以多人连麦直播互动为例):
ZEGO 针对虚拟直播提供了 体验 App 源码,以供开发者进一步了解 ZEGO 虚拟直播方案。
虚拟直播场景的整体流程如下:
详细流程图如下:
请联系 ZEGO 商务人员为 AppID 开通Avatar服务。
在使用 Express Video SDK 进行视频通话之前,需要初始化 SDK。由于初始化操作 SDK 时,内部处理的操作较多,建议开发者在 App 启动的时候进行。
ZegoEngineProfile *profile = [ZegoEngineProfile new];
// 请通过官网注册获取,格式为:1234567890
profile.appID = appID;
//请通过官网注册获取,格式为:@"0123456789012345678901234567890123456789012345678901234567890123"(共64个字符)
profile.appSign = appSign;
//通用场景接入
profile.scenario = ZegoScenarioGeneral;
// 创建引擎,并注册 self 为 eventHandler 回调。不需要注册回调的话,eventHandler 参数可以传 nil,后续可调用 "-setEventHandler:" 方法设置回调
[ZegoExpressEngine createEngineWithProfile:profile eventHandler:self];
在初始化 Express Video SDK 的时候需要开通 RTC 的自定义采集,Avatar 形象是通过自定义采集推送纹理。由于 Avatar 的数据是相反方向的,所以在初始化的时候需要设置镜像。
//设置 RTC 镜像 (Avatar 推送的镜像相反)
[engine setVideoMirrorMode:ZegoVideoMirrorModeBothMirror];
// 设置自定义采集推流
ZegoCustomVideoCaptureConfig *captureConfig = [[ZegoCustomVideoCaptureConfig alloc] init];
captureConfig.bufferType = ZegoVideoBufferTypeCVPixelBuffer;
[[ZegoExpressEngine sharedEngine] enableCustomVideoCapture:YES config:captureConfig channel:ZegoPublishChannelMain];
// 设置自定义采集回调
[[ZegoExpressEngine sharedEngine] setCustomVideoCaptureHandler:self];
float scaleScreen = [UIScreen mainScreen].nativeScale;
int captureWidth = ([ZGMetaLiveCurrentUser shared].user.userType == ZGMetaLiveUserTypeHost) ? 720 : 360;
int captureHeight = ([ZGMetaLiveCurrentUser shared].user.userType == ZGMetaLiveUserTypeHost) ? 1280 : 640;
// 配置Avatar采集画布尺寸
ZegoVideoConfig *videoConfig = [ZegoVideoConfig configWithPreset:([ZGMetaLiveCurrentUser shared].user.userType == ZGMetaLiveUserTypeHost) ? ZegoVideoConfigPreset720P :ZegoVideoConfigPreset360P];
videoConfig.encodeResolution = CGSizeMake(captureWidth, captureHeight );
[[ZegoExpressEngine sharedEngine] setVideoConfig:videoConfig];
更多初始化 Express Video SDK 的细节请参考:实时音视频 - 快速开始 - 实现流程 的 “3.1 创建引擎”。
在使用虚拟直播前,创建自己的个人形象。详情请参考 创建虚拟形象。
主播开始直播或观众观看直播前,需要先登录到直播房间。在收到登录房间成功的回调后,可以直接调用 Express Video SDK 的接口进行推拉流操作。
// 创建用户
ZegoUser *user = [ZegoUser userWithUserID:userID userName:userName];
// 设置为 YES 后才能接受 [onRoomUserUpdate] 回调
ZegoRoomConfig *config = [[ZegoRoomConfig alloc] init];
config.isUserStatusNotify = YES;
// 登录房间
[[ZegoExpressEngine sharedEngine] loginRoom:roomID user:user config:config];
更多使用 Express Video SDK 实现登录直播房间的细节请参考:实时音视频 - 快速开始 - 实现视频通话 的 “3.2 登录房间”。
初始化 ZegoCharacterHelper 类,设置已经创建的个人的虚拟形象,用于直播的个人形象展示。
_helper = [[ZegoCharacterHelper alloc] init:assetBundlesPath];
NSString *packagePath = [bundlePath stringByAppendingString:@"/ios/Packages/"]; //Resource/ios
[_helper setExtendPackagesPath:packagePath];
[_helper setDefaultAvatar:((self.currentGender == ZegoGenderType_Female) ? MODEL_ID_FEMALEBODY : MODEL_ID_MALEBODY)];
Avatar 的虚拟形象数据是通过 startCaptureAvatar 回调到上层通过自定义采集推送出去。由于 Avatar 数据是透明背景,RTC 是没背景的,转换的时候默认黑色,开发者可以自行将背景设置为需要的颜色。
//根据实际需求设置 Avatar 返回内容的宽(captureWidth)和高(captureHeight)
AvatarCaptureConfig* config = [[AvatarCaptureConfig alloc] initWithWidth:captureWidth height:captureHeight];
@weakify(self);//解决self循环引用
[self.helper startCaptureAvatar:config callback:^(unsigned long long texture, int width, int height) {
@strongify(self);
@autoreleasepool {
[self setupBgColorWithTexture:(__bridge id<MTLTexture>)(void*)texture color:GoAvatarHexColor(colorStr) ];
}
}];
// 设置推流背景颜色
- (void)setupBgColorWithTexture:(id<MTLTexture>)texture color:(UIColor*)color
{
@weakify(self);
[[MetalTools sharedInstance] setupBgColorWithTexture:texture
bgColor:color
callback:^(id<MTLTexture> _Nonnull newTexture) {
@strongify(self);
[self sendCustomerBuffer:newTexture];
}];
}
// 生成完整的 Avatar 纹理数据
- (void)sendCustomerBuffer:(id)newTexture{
//推流
@weakify(self);
[self getPixelBufferFromBGRAMTLTexture:(__bridge id<MTLTexture>)(__bridge void*)newTexture result:^(CVPixelBufferRef pixelBuffer) {
@strongify(self);
//这里的格式是 BGRA,需要转换
CMTime time = CMTimeMakeWithSeconds([[NSDate date] timeIntervalSince1970], 1000);
CMSampleTimingInfo timingInfo = { kCMTimeInvalid, time, time };
CMVideoFormatDescriptionRef desc;
CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &desc);
CMSampleBufferRef sampleBuffer;
CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, (CVImageBufferRef)pixelBuffer, desc, &timingInfo, &sampleBuffer);
if ([self.captureHandler respondsToSelector:@selector(onAvatarCaptureDeviceDidCapturedData:)]) {
[self.captureHandler onAvatarCaptureDeviceDidCapturedData:sampleBuffer];
}
CFRelease(sampleBuffer);
CFRelease(desc);
}];
}
// 把当前纹理数据转换成 pixelBuffer
- (void)getPixelBufferFromBGRAMTLTexture:(id<MTLTexture>)texture result:(void(^)(CVPixelBufferRef pixelBuffer))block {
CVPixelBufferRef pxbuffer = NULL;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber
numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil];
unsigned long width = texture.width;
unsigned long height = texture.height;
size_t imageByteCount = width * height * 4;
void *imageBytes = malloc(imageByteCount);
NSUInteger bytesPerRow = texture.width * 4;
MTLRegion region = MTLRegionMake2D(0, 0, texture.width, texture.height);
[texture getBytes:imageBytes bytesPerRow:bytesPerRow fromRegion:region mipmapLevel:0];
CVPixelBufferCreateWithBytes(kCFAllocatorDefault,texture.width,texture.height,kCVPixelFormatType_32BGRA,imageBytes,bytesPerRow,NULL,NULL,(__bridge CFDictionaryRef)options,&pxbuffer);
if (block) {
block(pxbuffer);
}
CVPixelBufferRelease(pxbuffer);
free(imageBytes);
}
主播向 ZEGO 音视频云服务推流,需要自己生成唯一的 StreamID,然后开始预览并推流。
// 根据 view 对象创建待渲染画布
ZegoCanvas *canvas = [ZegoCanvas canvasWithView:view];
// 设置渲染模式
canvas.viewMode = ZegoViewModeAspectFill;
// 开始在画布进行本地预览
[[ZegoExpressEngine sharedEngine] startPreview:canvas];
// 主播开始推流
[[ZegoExpressEngine sharedEngine] startPublishingStream:@"hostStreamID"];
更多使用 Express Video SDK 实现预览和推流的细节请参考:实时音视频 - 快速开始 - 实现视频通话 的 “3.3 推流”。
观众进入房间后,会收到 Express Video SDK 的流更新通知,从中筛选出主播流的 StreamID 进行拉流。
// 观众拉主播流
// 根据 view 对象创建待渲染画布
ZegoCanvas *canvas = [ZegoCanvas canvasWithView:view];
// 设置渲染模式
canvas.viewMode = ZegoViewModeAspectFill;
// 开始拉流并在画布进行渲染
[[ZegoExpressEngine sharedEngine] startPlayingStream:@"audienceStreamID" canvas:canvas];
更多使用 SDK 实现拉流的细节请参考:快速开始 - 实现流程 的 “3.4 拉流”。
观众调用业务后台请求连麦接口,调用成功后,业务后台向主播发送请求连麦自定义信令。主播收到信令后,调用业务后台同意连麦接口,调用成功后,业务后台向房间内所有成员发送连麦成功的广播信令,连麦观众收到信令后,开始推流,观众上台后也是按照 6.1 获取 Avatar 的纹理内容 的流程,把 Avatar 的内容通过自定义采集推流出去。
// 连麦观众推流
[[ZegoExpressEngine sharedEngine] startPublishingStream:@"audienceStreamID"];
连麦观众推流后,房间内所有成员会收到 Express Video SDK 的流更新通知,主播获取连麦观众流的 StreamID 进行拉流。
房间内其他观众也在收到流更新回调时,获取连麦观众流的 StreamID 进行拉流。
// 主播拉连麦观众流
// 根据 view 对象创建待渲染画布
ZegoCanvas *canvas = [ZegoCanvas canvasWithView:view];
// 设置渲染模式
canvas.viewMode = ZegoViewModeAspectFill;
// 开始拉流并在画布进行渲染
[[ZegoExpressEngine sharedEngine] startPlayingStream:@"audienceStreamID" canvas:canvas];
连麦观众调用业务后台的下麦接口,调用成功后,业务后台向房间内所有成员发送该观众下麦的广播信令。连麦观众收到信令后停止推流、停止采集获取 Avatar 纹理内容、停止表情随动检测,房间内其他观众收到信令后停止拉流。
// 观众停止预览
[[ZegoExpressEngine sharedEngine] stopPreview];
// 观众结束推流
[[ZegoExpressEngine sharedEngine] stopPublishingStream];
// 房间内其他成员结束拉流
[[ZegoExpressEngine sharedEngine] stopPlayingStream:@"audienceStreamID"];
// 停止采集获取 Avatar
[[GoAvatarManager shareInstance] stopCaptureAvatar]
更多使用 Express Video SDK 实现停止推拉流的细节请参考:实时音视频 - 快速开始 - 实现流程 的 “4.2 停止推拉流”。
static int const VIDEO_SRC_CAMERA = 2; // 摄像头视频源,在该场景中特指真人画面
static int const VIDEO_SRC_EXTERNAL_CAPTURE = 3; // 外部视频源,在该场景中特指虚拟形象
NSDictionary *param = @{
@"method":@"express.video.set_video_source",
@"params":@{
// source 表示 RTC 视频采集源;
// VIDEO_SRC_CAMERA 为真人形象;
@"source":@(VIDEO_SRC_CAMERA),
// VIDEO_SRC_EXTERNAL_CAPTURE 为 Avatar 虚拟形象
// @"source":@(VIDEO_SRC_EXTERNAL_CAPTURE),
@"channel":@(ZegoPublishChannelMain),
},
};
[[ZegoExpressEngine sharedEngine] callExperimentalAPI:[param modelToJSONString]]
// 停止采集 Avatar 纹理内容
[[GoAvatarManager shareInstance] stopCaptureAvatar];
// 开始采集 Avatar 纹理内容
//[[GoAvatarManager shareInstance] startCaptureAvatar];
从真人形象切换到 Avatar 虚拟形象时,需要调用 startCaptureAvatar 开始采集 Avatar 纹理内容,详情请参考 本文档 6.1 获取 Avatar 的纹理内容。
ZEGO 支持在虚拟直播中加入实时消息互动功能,实时展示房间内的消息,例如发消息、进退房提示、互动通知等。为便利开发者快速实现此功能,体验 App 源码 提供了相关组件,有关在您的项目中接入该组件的流程,请参考 实时消息互动 - 组件接入。
联系我们
文档反馈