基于即构 SDK 实现虚拟直播间的搭建流程
2022/09/22

虚拟直播场景为元宇宙社交娱乐模式下的全新直播方式,由虚拟形象替代真人出镜,可以给用户打造不一样的直播体验,还可以加入表情随动、手势识别触发特效等多种玩法,在场景里支持多位虚拟形象视频连麦互动,更容易吸引用户参与连麦互动,提升用户的消费意愿及粘性。 

即构虚拟形象引擎(Zego Avatar)支持自定义管理人物的虚拟形象,通过默认的虚拟形象或者自定义生成的专有虚拟形象,以表情随动、声音驱动等方式与真人实时互动,可广泛应用于语聊直播、社交互动、在线培训等多种场景中。(关于虚拟形象Avatar的实现我们下篇文章将进行详细描述)

本篇文章我们将详细介绍下如何使用即构 SDK 实现虚拟直播间的搭建流程

1 架构设计   

虚拟直播场景的主要架构如下图所示(以多人连麦直播互动为例):

虚拟直播场景架构设计图

2 体验APP源码   

ZEGO 针对虚拟直播提供了 体验 App 源码,以供开发者进一步了解 ZEGO 虚拟直播方案。

开发前的准备工作

在开始正式的开发工作之前,需要先做好以下的准备工作:

具体实现流程

一切准备就绪,首先介绍下虚拟直播场景的整体流程,可以做个初步的了解:

  1. 主播进入房间后,给 Avatar 设置虚拟形象,开始采集 Avatar 纹理内容,并进行预览并推流。
  2. 观众进入房间后,给 Avatar 设置虚拟形象,并进行拉流。
  3. 主播、观众均通过信令模块进行连接,信令模块可以控制当前业务房间内的直播流程,同步并通知各端当前的直播状态。
  4. 无论是否有连麦观众, 主播和观众均通过 ZEGO 音视频云服务进行推拉流。
  5. 观众请求与主播连麦后,信令模块会通知主播,并同步连麦者的个人信息。
  6. 主播接受连麦申请后,连麦观众开始采集 Avatar 纹理内容并推流,房间内所有成员将会接收到流更新通知,并拉取连麦观众的音视频流。
  7. 若连麦观众不再需要连麦,则向业务后台发起下麦请求。收到信令模块的下麦通知后,连麦观众停止推流、停止采集 Avatar 纹理内容、停止表情随动,主播和房间内的其他观众停止拉取该观众的流。

详细流程图如下:

接下来我们按照开发顺序,一步步实现我们想要搭建的虚拟直播间:

1开通Avatar服务

请联系 ZEGO 商务人员为 AppID 开通Avatar服务。

2初始化 Express Video SDK

在使用 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 创建引擎”。 

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)(void*)texture color:GoAvatarHexColor(colorStr) ];
    }
}];
// 设置推流背景颜色
- (void)setupBgColorWithTexture:(id)texture color:(UIColor*)color
{
    @weakify(self);
    [[MetalTools sharedInstance] setupBgColorWithTexture:texture 
                                                 bgColor:color 
                                                callback:^(id  _Nonnull newTexture) {
        @strongify(self);
        [self sendCustomerBuffer:newTexture];
    }];
}
// 生成完整的 Avatar 纹理数据
- (void)sendCustomerBuffer:(id)newTexture{
    //推流
    @weakify(self);
    [self getPixelBufferFromBGRAMTLTexture:(__bridge id)(__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)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 截图:

扫一扫,获取更多服务与支持
热门推荐
H.264 与 H.265 视频编解码器的区别,哪个更好?
2024/07/26
直播产品中的“六边形战士”来了!ZEGO 超低延迟直播,高质量带来新增长!
2024/07/23
什么是抖动?如何使用抖动缓冲区来减少抖动
2024/07/22
热门标签
AI 降噪
AI课堂
ExpressSDK
MSDN
RTI
SEI
webrtc
ZIM
互动白板
即构融资
在线KTV
在线K歌
屏幕共享
录屏采集
数智人
直播技术
范围语音
行业报告
语聊房
语音社交
超分
音视频
音视频开发
音视频技术
音频编码
关注我们
获得更多服务与支持了解价格与优惠 扫码关注我们
关注我们
获得更多服务与支持了解价格与优惠 扫码关注我们