虚构直播场景为元宇宙社交娱乐模式下的全新直播形式,由虚构形象代替真人出镜,能够给用户打造不一样的直播体验,还能够退出表情随动、手势辨认触发特效等多种玩法,在场景里反对多位虚构形象视频连麦互动,更容易吸引用户参加连麦互动,晋升用户的生产志愿及粘性。
即构虚构形象引擎(Zego Avatar)反对自定义治理人物的虚构形象,通过默认的虚构形象或者自定义生成的专有虚构形象,以表情随动、声音驱动等形式与真人实时互动,可广泛应用于语聊直播、社交互动、在线培训等多种场景中。(_对于虚构形象Avatar的实现咱们下篇文章将进行详细描述)_
本篇文章咱们将具体介绍下如何应用即构 SDK 实现虚构直播间的搭建流程。
1、架构设计
虚构直播场景的次要架构如下图所示(以多人连麦直播互动为例):
虚构直播场景架构设计图
2、体验APP源码
ZEGO 针对虚构直播提供了体验 App 源码_(https://doc-zh.zego.im/article/15666)_,以供开发者进一步理解 ZEGO 虚构直播计划。
开发前的筹备工作
在开始正式的开发工作之前,须要先做好以下的筹备工作:
- 已在ZEGO 控制台 创立我的项目,并申请无效的 AppID 和 AppSign,详情请参考控制台 - 项目管理中的“我的项目信息”;
- 已在我的项目中集成 ZEGO Express SDK,详情请参考 实时音视频 - 疾速开始 - 集成 SDK;
- 已在我的项目中集成 ZEGO Avatar SDK,详情请参考 Avatar 虚构形象 - 疾速开始 - 集成 SDK;
- 请分割 ZEGO 商务人员,提供申请到的 AppID,以及本人我的项目的 Bundle Identifier,并开明 Avatar 服务。
具体实现流程
所有准备就绪,首先介绍下虚构直播场景的整体流程,能够做个初步的理解:
- 主播进入房间后,给 Avatar 设置虚构形象,开始采集 Avatar 纹理内容,并进行预览并推流。
- 观众进入房间后,给 Avatar 设置虚构形象,并进行拉流。
- 主播、观众均通过信令模块进行连贯,信令模块能够管制以后业务房间内的直播流程,同步并告诉各端以后的直播状态。
- 无论是否有连麦观众, 主播和观众均通过 ZEGO 音视频云服务进行推拉流。
- 观众申请与主播连麦后,信令模块会告诉主播,并同步连麦者的个人信息。
- 主播承受连麦申请后,连麦观众开始采集 Avatar 纹理内容并推流,房间内所有成员将会接管到流更新告诉,并拉取连麦观众的音视频流。
- 若连麦观众不再须要连麦,则向业务后盾发动下麦申请。收到信令模块的下麦告诉后,连麦观众进行推流、进行采集 Avatar 纹理内容、进行表情随动,主播和房间内的其余观众进行拉取该观众的流。
具体流程图如下:
接下来咱们依照开发程序,一步步实现咱们想要搭建的虚构直播间:
1、开明Avatar服务
请分割 ZEGO 商务人员为 AppID 开明Avatar服务。
2、初始化 Express Video SDK
在应用 Express Video SDK 进行视频通话之前,须要初始化 SDK。因为初始化操作 SDK 时,外部解决的操作较多,倡议开发者在 App 启动的时候进行。
ZegoEngineProfile *profile = [ZegoEngineProfile new];// 请通过官网注册获取,格局为:1234567890profile.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 创立引擎”。
3、创立虚构形象
在应用虚构直播前,创立本人的集体形象。详情请参考 创立虚构形象。
4、登录直播房间
主播开始直播或观众观看直播前,须要先登录到直播房间。在收到登录房间胜利的回调后,能够间接调用 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 登录房间”。
5、设置集体虚构形象
初始化 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)];
6、单主播直播
6.1 获取 Avatar 的纹理内容
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);}
6.2 主播开启预览并推流
主播向 ZEGO 音视频云服务推流,须要本人生成惟一的 StreamID,而后开始预览并推流。
// 依据 view 对象创立待渲染画布ZegoCanvas *canvas = [ZegoCanvas canvasWithView:view];// 设置渲染模式canvas.viewMode = ZegoViewModeAspectFill;// 开始在画布进行本地预览[[ZegoExpressEngine sharedEngine] startPreview:canvas];// 主播开始推流[[ZegoExpressEngine sharedEngine] startPublishingStream:@"hostStreamID"];
更多应用 Express Video SDK 实现预览和推流的细节请参考:实时音视频 - 疾速开始 - 实现视频通话 的 “3.3 推流”。
6.3 观众拉流
观众进入房间后,会收到 Express Video SDK 的流更新告诉,从中筛选出主播流的 StreamID 进行拉流。
// 观众拉主播流// 依据 view 对象创立待渲染画布ZegoCanvas *canvas = [ZegoCanvas canvasWithView:view];// 设置渲染模式canvas.viewMode = ZegoViewModeAspectFill;// 开始拉流并在画布进行渲染[[ZegoExpressEngine sharedEngine] startPlayingStream:@"audienceStreamID" canvas:canvas];
更多应用 SDK 实现拉流的细节请参考:疾速开始 - 实现流程 的 “3.4 拉流”。
7、观众连麦
7.1 连麦观众推流
观众调用业务后盾申请连麦接口,调用胜利后,业务后盾向主播发送申请连麦自定义信令。主播收到信令后,调用业务后盾批准连麦接口,调用胜利后,业务后盾向房间内所有成员发送连麦胜利的播送信令,连麦观众收到信令后,开始推流,观众下台后也是依照 6.1 获取 Avatar 的纹理内容 的流程,把 Avatar 的内容通过自定义采集推流出去。
// 连麦观众推流[[ZegoExpressEngine sharedEngine] startPublishingStream:@"audienceStreamID"];
7.2 主播拉流
连麦观众推流后,房间内所有成员会收到 Express Video SDK 的流更新告诉,主播获取连麦观众流的 StreamID 进行拉流。
房间内其余观众也在收到流更新回调时,获取连麦观众流的 StreamID 进行拉流。
// 主播拉连麦观众流 // 依据 view 对象创立待渲染画布ZegoCanvas *canvas = [ZegoCanvas canvasWithView:view];// 设置渲染模式canvas.viewMode = ZegoViewModeAspectFill;// 开始拉流并在画布进行渲染[[ZegoExpressEngine sharedEngine] startPlayingStream:@"audienceStreamID" canvas:canvas];
7.3 连麦观众下麦
连麦观众调用业务后盾的下麦接口,调用胜利后,业务后盾向房间内所有成员发送该观众下麦的播送信令。连麦观众收到信令后进行推流、进行采集获取 Avatar 纹理内容、进行表情随动检测,房间内其余观众收到信令后进行拉流。
// 观众进行预览[[ZegoExpressEngine sharedEngine] stopPreview];// 观众完结推流[[ZegoExpressEngine sharedEngine] stopPublishingStream];// 房间内其余成员完结拉流 [[ZegoExpressEngine sharedEngine] stopPlayingStream:@"audienceStreamID"];// 进行采集获取 Avatar[[GoAvatarManager shareInstance] stopCaptureAvatar]
更多应用 Express Video SDK 实现进行推拉流的细节请参考:实时音视频 - 疾速开始 - 实现流程的 “4.2 进行推拉流”。
进阶性能
1、真人和虚构形象切换
真人和虚构形象的切换性能,次要是会体验不同视角成果。“真人”就是惯例意义上的视频画,“虚构形象”则是不同人模形象的展现。
- 设置 RTC 视频采集源当开发者须要从 Avatar 虚构形象切换到真人形象或者从真人形象切换到 Avatar 虚构形象的时候,均须要调用 Express SDK 的callExperimentalAPI设置 RTC 视频采集源。
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 纹理内容从 Avatar 虚构形象切换到真人形象时,须要调用 stopCaptureAvatar 进行采集 Avatar 纹理内容。
// 进行采集 Avatar 纹理内容[[GoAvatarManager shareInstance] stopCaptureAvatar];// 开始采集 Avatar 纹理内容//[[GoAvatarManager shareInstance] startCaptureAvatar];
从真人形象切换到 Avatar 虚构形象时,须要调用 startCaptureAvatar 开始采集 Avatar 纹理内容,详情请参考 本篇文章 6.1 获取 Avatar 的纹理内容。
2、实时音讯互动
ZEGO 反对在虚构直播中退出实时音讯互动性能,实时展现房间内的音讯,例如发消息、进退房提醒、互动告诉等。为便当开发者疾速实现此性能,体验 App 源码 提供了相干组件,无关在您的我的项目中接入该组件的流程,请参考 实时音讯互动 - 组件接入。
Demo展现
用虚构形象代替真人出镜,打造不一样的互动体验,以上就是对于即构虚构直播间的实现流程,感兴趣的小伙伴能够入手尝试搭建,下方是能够出现的 demo 截图:
如在开发过程中遇到疑难,可进行沟通交流