主要功能
互动直播定义
- 互动直播(Interactive Live Video Broadcasting),顾名思义,是 多路音视频实时互动 解决方案。
- 接入SDK, 可构建 1对1、1对多,多对多 的音视频通信。
- 互动直播房间最多 10 路连麦视频、不限路数纯音频多人直播。
- 同一房间最高支持 100万人并发。
和传统的直播有什么区别
打个比方,如果 直播 服务是个信号发射塔,那 互动直播 就是个带舞台的剧院。
- 所有人不管在什么地方,只要知道发射塔的信号频率(即频道号或链接)就能收看它发送的节目。
- 而想看剧院舞台的演出,就要首先进入到剧场里坐到观众席,当然观众席的位置可以是 1 个,也可以是几万个,还能被邀请上台一起表演。
- 因为需要实时的看到舞台上面人的表演,所以互动直播的延时会比直播更低(互动直播是毫秒级,直播是秒级)。
- 特别的,剧院舞台上的表演,也可以被发射塔播出去,给剧场外面的人看,这就是“旁路直播”功能。
主要功能列表
-
多路音视频: 多人同房间互相音视频通话。
- 直播时候,主播可以邀请观众上麦互动。
- 多主播一起互动直播,如远程辩论赛、嘉宾评论等。
- 一对一、多对多可是通信。
云通信: 即IMSDK,用于直播房间用户群管理、消息处理。
直播群聊消息、送礼、红包等功能。屏幕分享: 主播可以把屏幕动态展示给同房间用户。
远程教学课件展示,游戏直播操作展示。美颜: 降噪、磨皮、美白。
秀场类直播、可视通信。人脸识别: 识别采集到的人脸画面以及五官位置。
人像定位、定向美颜、换脸滤镜。快速切换房间: 让观看者无缝切换直播房间画面。
滑动换房。录制:从云端录制房间的音视频内容(暂不支持多路混流),需开通点播服务
直播回放、内容管控存储旁路直播: 把互动直播房间的音视频通过 CDN 加速推到房间之外给更多人观看(flv、hls 格式,默认提供 5 个频道)。
向未安装 App 的用户推广直播内容;房间人数超 100 万人时,让更多人在房间外看视频。混流及其录制: 两路连麦的画面混合成一路推出,并且录制在同一个视频里。
连麦视频内容的推广、保存。鉴黄: 从云端对直播画面进行截帧、存储和自动画图鉴别。
直播内容审核。添加水印: 添加 logo 图片到互动直播画面。
应对政策、内容和品牌推广。播放 RTMP 视频流: 在互动直播房间播放专业转播设备采集的或是来自第三方的视频流。
活动多机位画面转播;第三方赛事转播信号内嵌主播解说画面。
主要音视频参数
参数类型 | 直播指标 |
---|---|
最多同时音视频上限 | 视频 10 路,纯音频不限 |
同房间最多观众数 | 100 万 |
视频分辨率 | 320×240, 480×360,640×368,640×480,960×540,1280×720 |
图像编码码率范围 | 30~1500kbps |
延迟范围 | 150~400ms |
抗丢包率 | 35% |
音频采样率 | 8000,16000,48000 |
房间进入速度 | WIFI:950ms, 4G:1504ms |
系统架构
架构说明
- 延迟低。基于 UDP 或 UDT 的定制协议,可以在最大限度上保障低延迟。
- 私密性好。定制协议也可以非常好的保护高价值的视频内容不被盗链侦听。
- 扩展性强。如果需要和音视频流精密同步的自定义数据,例如实时面部识别,也可以在定制协议上进行扩展。
- 兼容性好。对于已经有观看端的用户,或者 web、H5 用户,也可以采用旁路推流的方式,以通用流媒体协议为其提供服务。
- 采用定制协议和采用通用协议的用户,信令和消息也可以实时互通。
数据交互时序说明
- 下图步骤中的步骤 1 和 2 解释了独立账号模式下,APP 用户完成互动直播身份认证的过程。如果采用托管账号模式,则不需要开发者 Server 参与,直接调用互动直播的 SDK login 接口即可。
- 开播、观看、上麦等音视频接口的调用必须在进房间之后。
- 只要 APP �业务逻辑允许,在调用相应的接口后,任何用户都有上麦能力。
- 开发者后台 Server 可以通过腾讯互动直播给 APP 里的用户或者群组 push 消息。
互动直播代码结构说明
- iLive SDK 封装了诸多 SDK 的接口,统一提供基础的账户、消息和音视频能力。
- iLive SDK 基础上,包装了互动直播常用的业务逻辑,提供了 Live SDK。推荐开发者直接在 Live SDK 基础上进行开发。
- 在 Live SDK 基础上有一个“随心播“App Demo。通过这个 App Demo, 可以直接体验互动直播的场景功能,也可以作为 App UI 开发的参考。
Api
直播接口
ILiveSDK 直播流程图
初始化 ILveSDK
在应用启动时初始化 ILiveSDK。 (即 Appdelegate 里面初始化)
接口名 | 接口描述 |
---|---|
initSdk: accountType: |
ILiveSDK 内部初始化,告知 sdkAppld。内部包含了 IMSDK 的初始化 |
参数类型 | 参数名 | 说明 |
---|---|---|
int | sdkAppld | 传入业务方 sdkAppld |
int | accountType | 传入业务方 accountType |
示例:
//需替换为用户自己的sdkappid
[[ILiveSDK getInstance] initSdk:1400027849 accountType:11656];
账号登录
接口名 | 接口描述 |
---|---|
iLiveLogin: sig: succ: failed: |
登录到腾腾讯云后台 |
参数类型 | 参数名 | 说明 |
---|---|---|
NSString | uid | 登录账号 |
NSString | sig | 登录鉴权签名 |
TCIVoidBlock | succ | 登录成功回调 |
TCIVoidBlock | failed | 登录失败回调 |
示例:
[[ILiveLoginManager getInstance] iLiveLogin:@"这里是帐号 id" sig:@"这里是签名字符串" succ:^{
NSLog(@"登录成功");
} failed:^(NSString *moudle, int errId, NSString *errMsg) {
NSLog(@"登录失败");
}];
主播创建房间
接口名 | 接口描述 |
---|---|
createRoom: option: succ: failed: |
主播创建直播间,自动渲染本地画面,开始直播 |
参数类型 | 参数名 | 说明 |
---|---|---|
int | roomId | 房间号。业务方后台省的房间号,需要保证唯一性 |
ILiveRoomOption | option | 主播创建房间的配置项,使用 defaultHostLiveOption 接口获取主播默认配置即可 |
TCIVoidBlock | succ | 创建房间成功回调 |
TCIVoidBlock | failed | 创建房间失败回调 |
示例:
//默认主播配置
ILiveRoomOption *option = [ILiveRoomOption defaultHostLiveOption];
TILLiveManager *manager = [TILLiveManager getInstance];
//设置渲染承载的视图
[manager setAVRootView:self.view];
//添加渲染视图,userid:画面所属者id type:相机/屏幕共享
[manager addAVRenderView:[UIScreen mainScreen].bounds forIdentifier:userId srcType:type];
[manager createRoom:self.roomId option:option succ:^{
NSLog(@"创建房间成功");
} failed:^(NSString *moudle, int errId, NSString *errMsg) {
NSLog(@"创建房间失败");
}];
观众进入房间
接口名 | 接口描述 |
---|---|
joinRoom: option: succ: failed: |
观众进入直播间,自动拉取远程会面并渲染,开始观看直播 |
参数类型 | 参数名 | 说明 |
---|---|---|
int | roomId | 房间号。业务方后台生成的房间号,需要保证唯一性 |
ILiveRoomOption | option | 观众进入房间时的配置项,使用 defaultGuestLiveOption 接口获取观众默认配置即可 |
TCIVoidBlock | succ | 进入房间成功回调 |
TCIVoidBlock | failed | 进入房间失败回调 |
注意:普通观众加入房间时,应该配置
authBits
无上行权限,仅观看权限。否则会和主播走一样的核心机房DC,产生高额费用。
示例:
//默认观众配置
ILiveRoomOption *option = [ILiveRoomOption defaultGuestLiveOption];
TILLiveManager *manager = [TILLiveManager getInstance];
//设置渲染承载的视图
[manager setAVRootView:self.view];
//添加渲染视图,userid:画面所属者id type:相机/屏幕共享
[manager addAVRenderView:[UIScreen mainScreen].bounds forIdentifier:userId srcType:type];
[manager joinRoom:self.roomId option:option succ:^{
NSLog(@"进入房间成功");
} failed:^(NSString *moudle, int errId, NSString *errMsg) {
NSLog(@"进入房间失败");
}];
以上步骤完成,主播可以开始直播,观众可以观看,整个流程就可以跑起来了。
互动消息 / 上麦
准备
无论是发消息还是上麦,都需要在创建或进入房间前设置事件和消息监听。
示例:
TILLiveManager *manager = [TILLiveManager getInstance];
//设置音视频事件监听
[manager setAVListener:self];
//设置消息监听
[manager setIMListener:self];
发送文本消息
接口名 | 接口描述 |
---|---|
sendTextMessage: succ: failed: |
发送文本消息 |
参数类型 | 参数名 | 说明 |
---|---|---|
ILiveTextMessage | msg | 文本消息类型 |
TCIVoidBlock | succ | 发送消息成功回调 |
TCIVoidBlock | failed | 发送消息失败回调 |
示例:
// 1. 发送文本消息
TILLiveManager *manager = [TILLiveManager getInstance];
ILVLiveTextMessage *msg = [[ILVLiveTextMessage alloc] init];
//群消息(也可发 C2C 消息)
msg.type = ILVLIVE_IMTYPE_GROUP;
//消息内容
msg.text = @"这里是消息的内容";
msg.sendId = @"这里是消息的发送方 id";
msg.recvId = @"这里是消息的接收方 id";
[manager sendTextMessage:msg succ:^{
NSLog(@"发送成功");
} failed:^(NSString *moudle, int errId, NSString *errMsg) {
NSLog(@"发送失败");
}];
// 2. 文本消息接收(在文本消息回调中接受文本消息)
- (void)onTextMessage:(ILVLiveTextMessage *)msg
{
NSLog(@"收到消息:%@", msg.text);
}
上麦
流程图
主播邀请上麦流程图:
观众请求上麦流程图:
发送自定义消息接口
接口名 | 接口描述 |
---|---|
sendCustomMessage: succ: failed: |
发送自定义消息 |
参数类型 | 参数名 | 说明 |
---|---|---|
ILVLiveCustomMessage | msg | 自定义消息体 |
TCIVoidBlock | succ | 发送自定义消息成功回调 |
TCIVoidBlock | failed | 发送自定义消息失败回调 |
示例:
TILLiveManager *manager = [TILLiveManager getInstance];
ILVLiveCustomMessage *msg = [[ILVLiveCustomMessage alloc] init];
//邀请信令
msg.cmd = ILVLIVE_IMCMD_INVITE;
//C2C 消息类型
msg.type = ILVLIVE_IMTYPE_C2C;
msg.recvId = @"这里是消息的接收方 id";
[manager sendCustomMessage:msg succ:^{
NSLog(@"邀请成功");
} failed:^(NSString *moudle, int errId, NSString *errMsg) {
NSLog(@"邀请失败");
}];
上麦接口
接口名 | 接口描述 |
---|---|
upToVideoMember: role: succ: failed: |
观众上麦,包括打开摄像头、麦克风、切换角色、切换权限等操作 |
参数类型 | 参数名 | 说明 |
---|---|---|
uint64_t | auth | 通话能力权限位,在 QAVCommon.h 文件中可以查看所有的权限位定义 |
NSString | role | 角色字符串 (由业务方 App 的控制台生成) |
TCIVoidBlock | succ | 上麦成功回调 |
TCIVoidBlock | failed | 上麦失败回调 |
示例:
// 观众在消息回调中可以收到主播发送自定义消息
- (void)onCustomMessage:(ILVLiveCustomMessage *)msg
{
TILLiveManager *manager = [TILLiveManager getInstance];
switch (msg.cmd)
{
case ILVLIVE_IMCMD_INVITE:
{
// 收到邀请调用上麦接口
[manager upToVideoMember:ILVLIVEAUTH_INTERACT role:@"腾讯云后台配置的角色" succ:^{
NSLog(@"上麦成功");
} failed:^(NSString *moudle, int errId, NSString *errMsg) {
NSLog(@"上麦失败");
}];
} break;
}
}
设置上麦者渲染画面的区域接口
接口名 | 接口描述 |
---|---|
addAVRenderView: forKey: |
添加渲染界面区域,以及设置渲染视图对应的 key(一般使用渲染视图对应的用户ID)) |
参数类型 | 参数名 | 说明 |
---|---|---|
CGRect | frame | 视图渲染区域 |
NSString | key | 视图对应的 key, 用于业务逻辑,一般使用画面对饮的用户ID |
示例:
- (void)onUserUpdateInfo:(ILVLiveAVEvent)event users:(NSArray *)users
{
TILLiveManager *manager = [TILLiveManager getInstance];
switch (event)
{
case ILVLIVE_AVEVENT_CAMERA_ON:
{
for (NSString *user in users)
{
// 因为主播的渲染位置创建或进入房间的时候已经指定,这里不需要再指定。
// 当然也可根据自己的逻辑再此处指定主播的渲染位置。
if(![user isEqualToString:self.host])
{
[manager addAVRenderView:CGRectMake(20, 20, 120, 160) forKey:user];
}
}
}
break;
}
}
以上步骤完成,可实现主播邀请观众连麦,观众成功上麦,直播间中出现多路画面(主播和互动观众的)。
录制
录制方法
推荐采用自动录制方案。开发者可以通过控制推流的时机来实现对录制的控制
- 全句自动录制:在开通直播码的时候选择【直播录制】,则所有的旁路直播都会被录制下来。
- 旁路直播的时候指定录制格式: 参考 直播码模式下旁路直播开发指南 相关参数。
- 手动录制:如果有特殊的业务要求,仍然可以手工控制录制开始和结束的时间。但是此种方式容易出错,需要开发者投入较大的精力。
录制处理录制事件通知
回调事件的参数格式和应答方案参考 直播码模式下旁路直播开发指南。录制通知的 event_type为 100,代表新的录制文件生成。同事消息体会额外包含如下信息:
字段名称 | 类型 | 含义 | |
---|---|---|---|
file_id | string | 点播用 ID,在点播平台可以唯一定位一个电报视频文件 | |
file_format | string | 录制文件格式 | |
video_url | string | 点播视频的下载地址 | |
file_size | string | 文件大小 | |
start_time | int | 开始时间 (unix 时间戳,由于 | 帧干扰,展示不能精确到秒级) |
end_time | int | 结束时间 (unix 时间戳,由于 | 帧干扰,展示不能精确到秒级) |
stream_param | string | 录制的参数,包括房间、sdkappid等,其中 cliRecold 是客户端推流时传入的 recordId 字段,可以过滤特定的录制文件 |
示例:一个 ID 为 9192487266581821586 的新的 flv 录制分片已经生成,播放地址为:
http://200025724.vod.myqcloud.com/200025724_ac92b781a22c4a3e937c9e61c2624af7.f0.flv。
{
"appid": XXXXX,
"channel_id": "2519_2500647",
"duration": 272,
"end_time": 1496220894,
"event_type": 100,
"file_format": "flv",
"file_id": "9031868222958931071",
"file_size": 30045521,
"record_file_id": "9031868222958931071",
"sign": "XXXXX",
"start_time": 1496220622,
"stream_id": "2519_2500647",
"stream_param": "txSecret=e48f758df8f3c736ebecf6183640330c2&txTime=5a20f5ca&from=interactive&client_business_type=0&sdkappid=1400027849&sdkapptype=1&groupid=801235&userid=eWpw&ts=59f968c5&cliRecoId=213",
"t": 1496221502,
"video_id": "200011683_481565e0befe4e44903839aebe370ef6",
"video_url": "http://1252033264.vod2.myqcloud.com/d7a4cabbvodgzp1252033264/0257ade99031868222958931071/f0.flv"
}
手工录制的注意事项
- 手工录制只有 MP4 格式,无录制回调事件,90分钟生成一个文件。
- 在进入音视频房间开启摄像头之后或者启动屏幕分享之后启动录制,在客户端退出房间之前停止录制。
- 考虑到外网接入,网络可能存在丢包,延迟等情况,启动录制需要有重试的机制,但是重试间隔建议在 3s 以上
- 注意携带的房间 ID,主播 ID 等参数保证对应关系。
iOS SDK 录制接口
设置录制参数
ILiveRecordOption *option = [[ILiveRecordOption alloc] init];
option.fileName = @"录制文件";
option.classId = [tag intValue];
option.avSdkType = sdkType;
option.recordType = recordType;
录制参数说明 ILiveRecordOption:
字段名 | 字段类型 | 默认值 | 说明 |
---|---|---|---|
fileName | NSString | 必填 | 录制生成文件名 |
tags | NSArray | 必填 | 视频标签列表 |
classId | UInt32 | 必填(当前版本填写 0 ) | 视频分类 ID |
isTransCode | BOOL | (暂不支持, 默认 NO) | 是否转码 |
isScreenShot | BOOL | (暂不支持, 默认 NO) | 是否截图 |
isWaterMark | BOOL | (暂不支持, 默认 NO) | 是否打水印 |
sdkType | AVSDKType | 必填(当前版本填写 AVSDK_TYPE_NORMAL) | SDK 对应的业务类型 |
recordType | AVRecordType | AV_RECORD_TYPE_VIDEO | 录制类型 |
开始录制
[[ILiveRoomManager getInstance] startRecordVideo:option succ:^{
NSLog(@"已开始录制");
} failed:^(NSString *module, int errId, NSString *errMsg) {
NSLog(@"开始录制失败");
}];
结束录制
[[ILiveRoomManager getInstance] stopRecordVideo:^(id selfPtr) {
NSArray *fileIds = (NSArray *)selfPtr;
NSLog(@"已停止录制");
} failed:^(NSString *module, int errId, NSString *errMsg) {
NSLog(@"停止录制失败");
}];
频道模式录制
- 录制文件将存储在腾讯云提供的点播服务上,用户可通过点播的管路控制太、API进项管理、转码、分发等操作。
- 使用录制功前,需要现在控制台开通腾讯云点播服务,否则将无法使用。
设置录制参数(频道模式)
ILiveRecordOption *option = [[ILiveRecordOption alloc] init];
option.fileName = @"录制文件";
option.tags = tags;
option.classId = [tag intValue];
option.avSdkType = sdkType;
option.recordType = recordType;
录制参数(频道模式)
字段名 | 字段类型 | 默认值 | 说明 |
---|---|---|---|
fileName | NSString | 必填 | 录制生成文件名 |
tags | NSArray | 必填 | 视频标签列表 |
classId | UInt32 | 必填(当前版本填写 0 ) | 视频分类 ID |
isTransCode | BOOL | (暂不支持, 默认 NO) | 是否转码 |
isScreenShot | BOOL | (暂不支持, 默认 NO) | 是否截图 |
isWaterMark | BOOL | (暂不支持, 默认 NO) | 是否打水印 |
sdkType | AVSDKType | 必填(当前版本填写 AVSDK_TYPE_NORMAL) | SDK 对应的业务类型 |
recordType | AVRecordType | AV_RECORD_TYPE_VIDEO | 录制类型 |
方法名 | 参数 | 说明 |
---|---|---|
addTag | String | 添加视频标签 |
开始录制(频道模式)
[[ILiveRoomManager getInstance] startRecordVideo:option succ:^{
NSLog(@"已开始录制");
} failed:^(NSString *module, int errId, NSString *errMsg) {
NSLog(@"开始录制失败");
}];
结束录制(频道模式)
[[ILiveRoomManager getInstance] stopRecordVideo:^(id selfPtr) {
NSArray *fileIds = (NSArray *)selfPtr;
NSLog(@"已停止录制");
} failed:^(NSString *module, int errId, NSString *errMsg) {
NSLog(@"停止录制失败");
}];
回调结果: NSArray (返回 NSString 类型的文件 ID 列表)。
注意事项
- 双人音视频房间不支持录制功能。
- 录制文件格式默认为 MP4。
- 录制每隔 90 分钟或录制结束时不足 90 分钟会生成一个 MP4 录制文件,超过 90 分钟则会生成多个文件。
- App 运行过程中 Crash 或异常退出,1 分钟没收到数据会自动关闭录制,后端会录制用户异常退出前音视频。
- 现版本不支持多路上行视频合并和混音处理。
费用相关提醒:价格和计费说明:按照录制月并发最高路数收费,价格:30 元/路/月。此外,录制功能会使用点播服务的能力,在云点播中会产生存储、流量的费用。详情参见 计费规则。需要说明的是,如果您已开通点播服务,并选定套餐或后付费中的一种计费模式,将沿用您已选定的计费模式,如果您未开通点播服务,将默认选择后付费按流量的计费模式。
其他
角色
角色配置:在腾讯后台创建、配置、修改角色。角色文档
使用角色:用户可以在进房间的 option 中配置要使用的角色。
//TILLiveSDK(直播 SDK)
TILLiveRoomOption *hostOption = [TILLiveRoomOption defaultHostLiveOption];
// 使用 LiveMaster 角色
hostOption.controlRole = @"LiveMaster";
切换角色:用户在进进入房间后,仍然可以根据需求调整角色。
// 切换角色为 LiveGuest
ILiveRoomManager *manager = [ILiveRoomManager getInstance];
[manager changeRole:@"LiveGuest" succ:^ {
NSLog(@"角色改变成功");
} failed:^(NSString *module, int errId, NSString *errMsg) {
NSLog(@"角色改变失败");
}];
自己采集(自定义)音频数据
音频本地处理流程
用户可以在下图中任一环节对数据进行拦截并做相应的处理。
观众侧:
主播侧:
自定义音频采集流程
- 主播或上麦观众腾讯云后台 spear 角色配置里音视频场景设置为【开播】(开播场景会占用本地录音权限),也可以设置为【观看】这样就不会占用本地音频设备了;
- 进房间时 mic 和 speaker 都要打开;
- 进房间成功后调用接口 AVAudioCtrl.changeAudioCategory 切换至观看场景(第一步如果设置为【观看】场景此步骤省略);
- 调用 enableExternalAudioDataInput 开启自定义采集音频;
- 调用 fillExternalAidioFrame 将外部采集的音频塞给 AVSDK;
- 以上接口都需要在主线程调用。
混音
音频透传: 主要用于在直播中对 Mic 采集到的数据作再加工处理,一般用于在直播间内添加背景音等,其对透传的音频数据有格式要求,默认使用的音频格式为
QAVAudioFrameDesc = {48000, 2, 16}
。通常的有下面两种使用方法:麦克风透传、扬声器透传数据。麦克风透传: 开麦克风端(有上行音频能力端)能听到,其他人可听到:以下代码为设置麦克风透传。
// 设置音频处理回调
[[[ILiveSDK getInstance] getAVContext].audioCtrl setAudioDataEventDelegate:self];
// 注意为 QAVAudioDataSource_MixToSend
[[[ILiveSDK getInstance] getAVContext].audioCtrl registerAudioDataCallback:QAVAudioDataSource_MixToSend];
[[[ILiveSDK getInstance] getAVContext].audioCtrl setAudioDataFormat:QAVAudioDataSource_MixToSend desc:pcmdesc];
- 扬声器透传数据:开扬声器端配置,只有自己听到,其他人听不到:以下代码为设置扬声器透传。
// 设置音频处理回调
[[[ILiveSDK getInstance] getAVContext].audioCtrl setAudioDataEventDelegate:self];
// 注意为 QAVAudioDataSource_MixToPlay
[[[ILiveSDK getInstance] getAVContext].audioCtrl registerAudioDataCallback:QAVAudioDataSource_MixToPlay];
[[[ILiveSDK getInstance] getAVContext].audioCtrl setAudioDataFormat:QAVAudioDataSource_MixToPlay desc:pcmdesc];
- 回调处理:注意三个回调中的注释。
- (QAVResult)audioDataComes:(QAVAudioFrame *)audioFrame type:(QAVAudioDataSourceType)type
{
// 主要用于保存直播中的音频数据
return QAV_OK;
}
- (void)handle:(QAVAudioFrame **)frameRef withPCM:(NSData *)data offset:(NSInteger *)offset
{
// 演示如何将透传的数据添加到 QAVAudioFrame
const QAVAudioFrame *aFrame = *frameRef;
NSInteger off = *offset;
NSInteger length = [aFrame.buffer length];
if (length)
{
NSMutableData *pdata = [NSMutableData data];
const Byte *btyes = [data bytes];
while (pdata.length < length)
{
if (off + length > data.length)
{
const Byte *byteOff = btyes + off;
[pdata appendBytes:byteOff length:data.length - off];
off = 0;
}
else
{
const Byte *byteOff = btyes + off;
[pdata appendBytes:byteOff length:length];
off += length;
}
}
if (pdata.length == length)
{
*offset = off;
const void *abbytes = [aFrame.buffer bytes];
memcpy((void *)abbytes, [pdata bytes], length);
}
}
}
- (QAVResult)audioDataShouInput:(QAVAudioFrame *)audioFrame type:(QAVAudioDataSourceType)type
{
// 混音输入(Mic 和 Speaker)的主要回调
// 麦克风透传处理
if (type == QAVAudioDataSource_MixToSend)
{
// self.micAudioTransmissionData 为要透传的音频数据,默认使用 QAVAudioFrameDesc = {48000, 2, 16},外部传入数据时,注意对应,外部传入的时候,注意相关的参数
if (self.micAudioTransmissionData)
{
NSInteger off = self.micAudioOffset;
[self handle:&audioFrame withPCM:self.micAudioTransmissionData offset:&off];
self.micAudioOffset = off;
}
}
// 扬声明器透传处理
else if (type == QAVAudioDataSource_MixToPlay)
{
// self.speakerAudioTransmissionData 为要透传的音频数据,默认同样使用 QAVAudioFrameDesc = {48000, 2, 16},外部传入数据时,注意对应,外部传入的时候,注意相关的参数
if (self.speakerAudioTransmissionData)
{
NSInteger off = self.speakerAudioOffset;
[self handle:&audioFrame withPCM:self.speakerAudioTransmissionData offset:&off];
self.speakerAudioOffset = off;
}
}
// NSLog(@"%@", audioFrame.buffer);
return QAV_OK;
}
- (QAVResult)audioDataDispose:(QAVAudioFrame *)audioFrame type:(QAVAudioDataSourceType)type
{
// 主要用作作变声处理
return QAV_OK;
}
- 退出直播间,取消透传回调:
// 取消所有音频透传处理
[[[ILiveSDK getInstance] getAVContext].audioCtrl unregisterAudioDataCallbackAll];
[[[ILiveSDK getInstance] getAVContext].audioCtrl setAudioDataEventDelegate:nil];
// 或调用 AVSDK 接口取消不同类型的透传
// 方法详见 QAVSDK.framework 中的 QAVAudioCtrl
/*!
@abstract 反注册音频数据类型的回调
@discussion 要注册监听的音频数据源类型,具体参考 QAVAudioDataSourceType。
@param type 要反注册监听的音频数据源类型,具体参考 QAVAudioDataSourceType
@return 成功返回 QAV_OK, 其他情况请参照 QAVResult。
@see QAVAudioDataSourceType QAVResult
*/
- (QAVResult)unregisterAudioDataCallback:(QAVAudioDataSourceType)type;
自己采集(自定义)视频采集
自定义视频:
自定义采集画面:主要用于预处理原始数据,比如用户需要人脸识别,画面美化,动效处理等,如下是通过自定义采集画面后,增加动效效果图。
注:动效效果是用户自己增加的,用户可以对原始数据做任何预处理(不仅是动效)。
流程说明:首先请记住,若要使用自定义采集画面,则采集画面的过程与 ILiveSDK 没有任何关系,完全不依赖 ILiveSDK,自定义采集画面的流程中,ILiveSDK 的作用是透传数据以及渲染远程数据。
而本文介绍的流程是:【自定义采集画面】->【画面传入 ILiveSDK】->【远程端收到画面帧渲染】 的整个过程。
流程图如下:
采集前准备
进入房间之后,采集画面之前。
接口 | 描述 |
---|---|
enableExternalCapture: | 打开(关闭)外部视频捕获设备,自定义采集时,需要打开.返回 QAV_OK 表示执行成功。用了此方法之后,不能再调用 ILiveSDK 的打开摄像头接口,且 ILiveSDK 的美白,美颜将失效 |
参数类型 | 参数名 | 说明 |
---|---|---|
BOOL | isEnableExternalCapture | 是否打开外部视频捕获设备,自定义采集时传 YES |
BOOL | shouldRender | SDK 是否渲染输入流视频数据,YES 表示会,NO 表示不会 |
示例:
QAVContext *context = [[ILiveSDK getInstance] getAVContext];
[context.videoCtrl enableExternalCapture:YES shouldRender:NO];
自定义采集
自定义采集使用的是系统层级接口,和 ILiveSDK 没有直接联系,不做过多赘述,简单列下需要用到的系统类和方法。
系统类或方法 | 描述 |
---|---|
AVCaptureSession | 采集画面 |
AVCaptureDeviceInput | 画面输入流 |
AVCaptureVideoDataOutput | 画面输出流 |
captureOutput: didOutputSampleBuffer: fromConnection: | 采集画面回调函数,采集到的画面将从这里回吐,用户可在这个接口作预处理 |
采集后处理
接收到系统回吐出的原始数据(CMSampleBufferRef类型数据),用户就可以对其做预处理,比如美白,美颜,人脸识别等,预处理之后的画面需要用户自己完成渲染,与 ILiveSDK 无直接联系。
ILiveSDK
透传
接口 | 描述 |
---|---|
fillExternalCaptureFrame: | 向音视频 SDK 传入捕获的视频帧,返回 QAV_OK 表示执行成功 |
参数类型 | 参数名 | 说明 |
---|---|---|
QAVVideoFrame | frame | AVSDK 画面帧类型,用户需将自定义采集的画面转换成 QAVVideoFrame 类型 |
示例:
QAVVideoCtrl *videoCtrl = [[ILiveSDK getInstance] getAvVideoCtrl].videoCtrl;
QAVResult result = [videoCtrl fillExternalCaptureFrame:frame];
远端渲染
接口 | 描述 |
---|---|
OnVideoPreview: | 远程画面回调,接收远程帧数据,再使用 ILiveSDK 的渲染接口渲染,用户只需要添加一个渲染区域即可。 |
参数类型 | 参数名 | 说明 |
---|---|---|
QAVVideoFrame | frameData | AVSDK 画面帧类型 |
注:
- 如果渲染自定义采集的画面使用了 OpenGL,则不能使用 ILiveSDK 中的渲染,否则会 Crash。也就是说,此时界面上应该有两个 view,一个渲染自定义采集的画面,另一个渲染 QAVVideoFrame 对象。
- 转换成 QAVVideoFrame 时,属性 color_format 必需填写 AVCOLOR_FORMAT_NV12,srcType 属性必须填写 QAVVIDEO_SRC_TYPE_CAMERA。
如何旋转和裁剪画面
由于观众和主播的屏幕方向和大小都可能不一致,所以需要在观众端,按照观众的屏幕大小和方向对主播的画面进行选择,缩放或裁剪。
接口:
参数名/函数名 | 说明 | 默认值 |
---|---|---|
isRotate | 主播画面是否旋转 | YES(旋转) |
sameDirectionRenderMode | 方向一致渲染模式 | ILIVERENDERMODE_SCALEASPECTFILL(全屏适应) |
diffDirectionRenderMode | 方向不一致渲染模式 | ILIVERENDERMODE_SCALEASPECTFIT(黑边) |
方案一 旋转主播画面
效果如下:
实现:
//设定需要旋转画面
iLiveRenderView.isRotate = YES;
//设定是铺满屏幕还是留黑边
iLiveRenderView.sameDirectionRenderMode = ILiveRenderMode;
方案二 不旋转主播画面
效果如下:
实现:
//设定不需要旋转画面
iLiveRenderView.isRotate = NO;
//设定在方向不一致情况下,是铺满屏幕还是留黑边
iLiveRenderView.diffDirectionRenderMode = ILiveRenderMode;
//设定在方向一致情况下,是铺满屏幕还是留黑边
iLiveRenderView.sameDirectionRenderMode = ILiveRenderMode;
直播中断事件的处理
在直播过程中,经常会遇到突发事件导致直播被中断。这里列举一些常见事件的处理方式供开发者参考:
- 来电话
- 音频中断
- 切后台
- 锁屏
- 断网
- crash
- 被踢
来电话
无需做任何处理。来电话时,来电界面会覆盖当前直播界面,参考切后台处理。
音频中断
无需做任何处理。发产生音频中断(如闹钟事件)时,分两种情况:
- 无新界面,不影响当前播放(录制)视频及语音
- 有新界面,参考切后台处理
切后台
在直播房间时,如果 90 秒无上行数据,音视频房间会被后台收回。iOS 分两种情况:
- 界面被覆盖,此时会暂停播放(录制)视频和语音,回到前面时恢复
- 切到后台,短时间只会暂停(同上),长时间会被回收(系统机制)
锁屏
ILiveSDK 内部设置保活模式,不会锁屏(进入房间保活,退出房间不保活)。
断网
在网络中断时,SDK 内部会尝试重连,用户可自行监控系统网络状态。如需了解 SDK 内部网络状态,可参考 ILiveQualityData
。如房间超过 90 秒没有上行数据,音视频房间会被回收,会上抛 onRoomDisconnect
事件。
crash
ILiveSDK
内置了 bugly 上报,可以方便用户迅速定位到问题。同时用户只需记录当前直播房间(roomid)及直播状态,在应用重启后,可以回到原直播房间,恢复直播。如时间较短(未超过 90 秒),观众端只会感到画面卡顿(此时可以根据相应事件做出友好提示)。用户可以在监听 onEndpointsUpdateInfo
事件中的关摄像头事件做出友好提示。
被踢
多终端登录时,会收到被踢事件,此时建议用户重新登录(如果恢复需自己记录状态)。
设置用户状态监听:
// 设置用户状态监听
[[ILiveSDK getInstance] setUserStatusListener:[[LiveUserStatusListener alloc] init]];
// 用户状态监听类
@interface LiveUserStatusListener : NSObject <TIMUserStatusListener>
@end
@implementation LiveUserStatusListener
// 被踢
- (void)onForceOffline {
}
//票据过期
- (void)onUserSigExpired{
}
回调中处理业务逻辑:
- (void)onForceOffline{
//被踢下线(以下处理逻辑可做参考,具体可由自身业务决定)
//1、如果未处于直播间,可跳转到用户登录界面
//2、如果处于直播间
//(1)保存直播状态信息,如房间 ID,房间标题等并退出直播间
//(2)重新登录后,根据直播状态信息恢复(进入)直播间
}
播放背景音乐
需求背景:主播使用第三方音乐 App 播放背景音乐,自己收听的同时也希望观众一起畅听。
只需要主播端开启高音质即可,观众和上麦者的配置无需改变。
//进入房间时开启高音质配置
ILiveRoomOption *option = [ILiveRoomOption defaultHostLiveOption];
option.avOption.autoHdAudio = YES;
画面对焦
自动对焦
iLiveSDK
在 iOS 中已提供自动对焦功能,用户无需任何配置。
手动对焦
注意:当前只支持后置摄像头手动对焦
如果需要对自带对焦效果不满意,或有高级应用场景需求,用户可以自己完成对焦功能,这里以实现手动对焦为例。
手动对焦流程
- 单击事件: 因为交互界面在最顶层,渲染界面在最底层,所以单击事件添加到交互界面上。
- 获取单击点坐标: 获取单击手势在视图上的坐标,此坐标是相对于交互视图的坐标。
- 将单击手势坐标转换为 layer 坐标:【获取单击点坐标】获取的是相对于交互视图的坐标,要转换为画面渲染视图的坐标,将交互视图和渲染视图想对的屏幕的坐标同时计算出来,即可将交互视图坐标映射到渲染视图。
转换函数:
// 功能:将交互视图上的点映射成渲染视图的点
// 本 Demo 只实现了全屏下的聚焦和缩放功能,所以在转换时使用的 liveViewController.livePreview.imageView,如果用户要将交互视图上的点映射为小画面的,这里需要替换成小画面上方的透明视图,在随心播中,小画面上的透明视图对应为 TCShowMultiSubView 对象
- (CGPoint)layerPointOfInterestForPoint:(CGPoint)point
{
FocusDemoViewController *liveViewController = (FocusDemoViewController *)_liveController;
CGRect rect = [liveViewController.livePreview.imageView relativePositionTo:[UIApplication sharedApplication].keyWindow];
BOOL isContain = CGRectContainsPoint(rect, point);
if (isContain)
{
CGFloat x = (point.x - rect.origin.x)/rect.size.width;
CGFloat y = (point.y - rect.origin.y)/rect.size.height;
CGPoint layerPoint = CGPointMake(x, y);
return layerPoint;
}
return CGPointMake(0, 0);
}
获取
AVCaptureSession
并设置焦点:通过 AVSDK 接口获取相机session
,通过此session
设置相机焦点,见 Demo 中onSingleTap
函数
iOS 手动缩放
// 响应双击事件
- (void)onDoubleTap:(UITapGestureRecognizer *)tapGesture
{
CGPoint point = [tapGesture locationInView:self.view];
[_focusView.layer removeAllAnimations];
__weak FocusDemoUIViewController *ws = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[ws layoutFoucsView:point];
});
static BOOL isscale = YES;
CGFloat rate = isscale ? 1.0 : -2.0;
[ self zoomPreview:rate];
isscale = !isscale;
}
//缩放
-(void)zoomPreview:(float)rate
{
// 以下是获取 AVCaptureSession 演示摄像头缩放的。iphone4s 暂时不支持。
if ([FocusDemoUIViewController isIphone4S:self])
{
return;
}
//to do
QAVVideoCtrl *videoCtrl = [_roomEngine getVideoCtrl];
AVCaptureSession *session = [videoCtrl getCaptureSession];
if (session)
{
for( AVCaptureDeviceInput *input in session.inputs)
{
NSError* error = nil;
AVCaptureDevice*device = input.device;
if ( ![device hasMediaType:AVMediaTypeVideo] )
continue;
BOOL ret = [device lockForConfiguration:&error];
if (error)
{
DebugLog(@"ret = %d",ret);
}
if (device.videoZoomFactor == 1.0)
{
CGFloat current = 2.0;
if (current < device.activeFormat.videoMaxZoomFactor)
{
[device rampToVideoZoomFactor:current withRate:10];
}
}
else
{
[device rampToVideoZoomFactor:1.0 withRate:10];
}
[device unlockForConfiguration];
break;
}
}
}
日志
日志位置
SDK | 目录 |
---|---|
iLiveSDK | Library/Caches/ilivesdk_YYYMMDD.log |
IMSDK | Library/Caches/imsdk_YYYMMDD.log |
AVSDK | Library/Caches/QAVSDK_YYYMMDD.log |
日志收集接口
适用场景
任何需要从手机上收集互动直播日志的场景。
使用方法
集成 iLive SDK:1.4.0 以上。在需要用户上报日志的时候,调用日志上报接口。iLive SDK 会直接把日志传到腾讯云后台。
/**
日志上报
@param logDesc 日志描述
@param dayOffset 日期,0-当天,1-昨天,2-前天,以此类推
@param uploadResult 上报回调
*/
- (void)uploadLog:(NSString *)logDesc logDayOffset:(int)dayOffset uploadResult:(ILiveLogUploadResultBlock)uploadResult;
iLive SDK 关键路径的 LOG (查看日志方法)
开发者在遇到问题时,可以根据这些日志,判断哪个流程执行出错,有助于定位问题。在 iLiveSDK 1.0.3 以后版本过滤关键字 Key_Procedure 会搜索到创建房间或加入房间的关键路径。
创建房间流程正确 LOG 如下:
示例:
1. 初始化 SDK
ILiveSDK:Key_Procedure|initSdk|succ
2. 登录 SDK(托管模式,如果是独立模式,无 tlsLogin 的 log)
ILiveLogin:Key_Procedure|tlsLogin|start:id:ken1
ILiveLogin:Key_Procedure|tlsLogin|succ
ILiveLogin:Key_Procedure|iLiveLogin|start:id:ken1
ILiveLogin:Key_Procedure|iLiveLogin|succ
3. 创建渲染根视图和主播渲染视图
ILiveOpenGL:Key_Procedure|createOpenGLView|succ
ILiveOpenGL:Key_Procedure|addRenderView|succ:frame:{{0, 0}, {375, 667}},key:ken1
4. 创建聊天群组
ILiveRoom:Key_Procedure|createIMGroup|start:groupId:9878
ILiveRoom:Key_Procedure|createIMGroup|succ
5. 创建直播房间
ILiveRoom:Key_Procedure|enterAVRoom|start:roomId:9878
ILiveRoom:Key_Procedure|enterAVRoom|succ
6. 打开摄像头并收到 server 事件(此处是摄像头开启事件)回调
ILiveRoom:Key_Procedure|enableCamera|start:enable:YES
ILiveRoom:Key_Procedure|OnEndpointsUpdateInfo|evenId:3,id:ken1
ILiveRoom:Key_Procedure|enableCamera|succ:enable:YES
7. 收到视频帧(间隔打印)
ILiveGLBase:Key_Procedure|videoFrame|id:ken1,index:1
ILiveGLBase:Key_Procedure|videoFrame|id:ken1,index:11
ILiveGLBase:Key_Procedure|videoFrame|id:ken1,index:21
8. 退出直播房间
ILiveRoom:Key_Procedure|exitAVRoom|start
ILiveRoom:Key_Procedure|exitAVRoom|succ
9 退出聊天群组(群主解散群组)
ILiveRoom:Key_Procedure|quitIMGroup|start:groupId:9878
ILiveRoom:Key_Procedure|quitIMGroup|succ:code:10009
ILiveRoom:Key_Procedure|deleteIMGroup|start:groupId:9878
ILiveRoom:Key_Procedure|deleteIMGroup|succ
加入房间流程正确 LOG 如下:
示例:
1. 初始化 SDK
ILiveSDK:Key_Procedure|initSdk|succ
2. 登录 SDK(托管模式,如果是独立模式,无 tlsLogin 的 log)
ILiveLogin:Key_Procedure|tlsLogin|start:id:ken2
ILiveLogin:Key_Procedure|tlsLogin|succ
ILiveLogin:Key_Procedure|iLiveLogin|start:id:ken2
ILiveLogin:Key_Procedure|iLiveLogin|succ
3. 创建渲染根视图和主播渲染视图
ILiveOpenGL:Key_Procedure|createOpenGLView|succ
ILiveOpenGL:Key_Procedure|addRenderView|succ:frame:{{0, 0}, {414, 736}},key:ken1
4. 加入聊天群组
ILiveRoom:Key_Procedure|joinIMGroup|start:groupId:9878
ILiveRoom:Key_Procedure|joinIMGroup|succ
5. 进入直播房间
ILiveRoom:Key_Procedure|enterAVRoom|start:roomId:9878
ILiveRoom:Key_Procedure|enterAVRoom|succ
6. server 事件(此处是摄像头开启事件)回调
ILiveRoom:Key_Procedure|OnEndpointsUpdateInfo|evenId:3,id:ken1
7. 收到主播视频帧(间隔打印)
ILiveGLBase:Key_Procedure|videoFrame|id:ken1,index:1
ILiveGLBase:Key_Procedure|videoFrame|id:ken1,index:11
ILiveGLBase:Key_Procedure|videoFrame|id:ken1,index:21
8. 退出直播房间
ILiveRoom:Key_Procedure|exitAVRoom|start
ILiveRoom:Key_Procedure|exitAVRoom|succ
9 退出聊天群组
ILiveRoom:Key_Procedure|quitIMGroup|start:groupId:9878
ILiveRoom:Key_Procedure|quitIMGroup|succ
摘抄自腾讯云互动直播文档,自己记录使用。