iOS腾讯互动直播使用

主要功能

互动直播定义

  1. 互动直播(Interactive Live Video Broadcasting),顾名思义,是 多路音视频实时互动 解决方案。
  2. 接入SDK, 可构建 1对1、1对多,多对多 的音视频通信。
  3. 互动直播房间最多 10 路连麦视频、不限路数纯音频多人直播。
  4. 同一房间最高支持 100万人并发。

和传统的直播有什么区别

打个比方,如果 直播 服务是个信号发射塔,那 互动直播 就是个带舞台的剧院。

  1. 所有人不管在什么地方,只要知道发射塔的信号频率(即频道号或链接)就能收看它发送的节目。
  2. 而想看剧院舞台的演出,就要首先进入到剧场里坐到观众席,当然观众席的位置可以是 1 个,也可以是几万个,还能被邀请上台一起表演。
  3. 因为需要实时的看到舞台上面人的表演,所以互动直播的延时会比直播更低(互动直播是毫秒级,直播是秒级)。
  4. 特别的,剧院舞台上的表演,也可以被发射塔播出去,给剧场外面的人看,这就是“旁路直播”功能。

主要功能列表

  1. 多路音视频: 多人同房间互相音视频通话。

    • 直播时候,主播可以邀请观众上麦互动。
    • 多主播一起互动直播,如远程辩论赛、嘉宾评论等。
    • 一对一、多对多可是通信。
  2. 云通信: 即IMSDK,用于直播房间用户群管理、消息处理。
    直播群聊消息、送礼、红包等功能。

  3. 屏幕分享: 主播可以把屏幕动态展示给同房间用户。
    远程教学课件展示,游戏直播操作展示。

  4. 美颜: 降噪、磨皮、美白。
    秀场类直播、可视通信。

  5. 人脸识别: 识别采集到的人脸画面以及五官位置。
    人像定位、定向美颜、换脸滤镜。

  6. 快速切换房间: 让观看者无缝切换直播房间画面。
    滑动换房。

  7. 录制:从云端录制房间的音视频内容(暂不支持多路混流),需开通点播服务
    直播回放、内容管控存储

  8. 旁路直播: 把互动直播房间的音视频通过 CDN 加速推到房间之外给更多人观看(flv、hls 格式,默认提供 5 个频道)。
    向未安装 App 的用户推广直播内容;房间人数超 100 万人时,让更多人在房间外看视频。

  9. 混流及其录制: 两路连麦的画面混合成一路推出,并且录制在同一个视频里。
    连麦视频内容的推广、保存。

  10. 鉴黄: 从云端对直播画面进行截帧、存储和自动画图鉴别。
    直播内容审核。

  11. 添加水印: 添加 logo 图片到互动直播画面。
    应对政策、内容和品牌推广。

  12. 播放 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

系统架构

架构说明

  1. 延迟低。基于 UDP 或 UDT 的定制协议,可以在最大限度上保障低延迟。
  2. 私密性好。定制协议也可以非常好的保护高价值的视频内容不被盗链侦听。
  3. 扩展性强。如果需要和音视频流精密同步的自定义数据,例如实时面部识别,也可以在定制协议上进行扩展。
  4. 兼容性好。对于已经有观看端的用户,或者 web、H5 用户,也可以采用旁路推流的方式,以通用流媒体协议为其提供服务。
  5. 采用定制协议和采用通用协议的用户,信令和消息也可以实时互通。
流程图.png

数据交互时序说明

  1. 下图步骤中的步骤 1 和 2 解释了独立账号模式下,APP 用户完成互动直播身份认证的过程。如果采用托管账号模式,则不需要开发者 Server 参与,直接调用互动直播的 SDK login 接口即可。
  2. 开播、观看、上麦等音视频接口的调用必须在进房间之后。
  3. 只要 APP �业务逻辑允许,在调用相应的接口后,任何用户都有上麦能力。
  4. 开发者后台 Server 可以通过腾讯互动直播给 APP 里的用户或者群组 push 消息。
流程图.png

互动直播代码结构说明

  1. iLive SDK 封装了诸多 SDK 的接口,统一提供基础的账户、消息和音视频能力。
  2. iLive SDK 基础上,包装了互动直播常用的业务逻辑,提供了 Live SDK。推荐开发者直接在 Live SDK 基础上进行开发。
  3. 在 Live SDK 基础上有一个“随心播“App Demo。通过这个 App Demo, 可以直接体验互动直播的场景功能,也可以作为 App UI 开发的参考。
流程图.png

Api

直播接口

ILiveSDK 直播流程图

流程图.png

初始化 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);
}

上麦

流程图

主播邀请上麦流程图:

流程图.png

观众请求上麦流程图:

流程图.png

发送自定义消息接口

接口名 接口描述
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;
    }
}

以上步骤完成,可实现主播邀请观众连麦,观众成功上麦,直播间中出现多路画面(主播和互动观众的)。


录制

录制方法

推荐采用自动录制方案。开发者可以通过控制推流的时机来实现对录制的控制

  1. 全句自动录制:在开通直播码的时候选择【直播录制】,则所有的旁路直播都会被录制下来。
  2. 旁路直播的时候指定录制格式: 参考 直播码模式下旁路直播开发指南 相关参数。
  3. 手动录制:如果有特殊的业务要求,仍然可以手工控制录制开始和结束的时间。但是此种方式容易出错,需要开发者投入较大的精力。

录制处理录制事件通知

回调事件的参数格式和应答方案参考 直播码模式下旁路直播开发指南。录制通知的 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"
}

手工录制的注意事项

  1. 手工录制只有 MP4 格式,无录制回调事件,90分钟生成一个文件。
  2. 在进入音视频房间开启摄像头之后或者启动屏幕分享之后启动录制,在客户端退出房间之前停止录制。
  3. 考虑到外网接入,网络可能存在丢包,延迟等情况,启动录制需要有重试的机制,但是重试间隔建议在 3s 以上
  4. 注意携带的房间 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(@"停止录制失败");
}];

频道模式录制

  1. 录制文件将存储在腾讯云提供的点播服务上,用户可通过点播的管路控制太、API进项管理、转码、分发等操作。
  2. 使用录制功前,需要现在控制台开通腾讯云点播服务,否则将无法使用。

设置录制参数(频道模式)

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 列表)。

注意事项

  1. 双人音视频房间不支持录制功能。
  2. 录制文件格式默认为 MP4。
  3. 录制每隔 90 分钟或录制结束时不足 90 分钟会生成一个 MP4 录制文件,超过 90 分钟则会生成多个文件。
  4. App 运行过程中 Crash 或异常退出,1 分钟没收到数据会自动关闭录制,后端会录制用户异常退出前音视频。
  5. 现版本不支持多路上行视频合并和混音处理。

费用相关提醒:价格和计费说明:按照录制月并发最高路数收费,价格: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(@"角色改变失败");
}];

自己采集(自定义)音频数据

音频本地处理流程

用户可以在下图中任一环节对数据进行拦截并做相应的处理。

观众侧:

流程图.jpg

主播侧:

流程图.jpg

自定义音频采集流程

  1. 主播或上麦观众腾讯云后台 spear 角色配置里音视频场景设置为【开播】(开播场景会占用本地录音权限),也可以设置为【观看】这样就不会占用本地音频设备了;
  2. 进房间时 mic 和 speaker 都要打开;
  3. 进房间成功后调用接口 AVAudioCtrl.changeAudioCategory 切换至观看场景(第一步如果设置为【观看】场景此步骤省略);
  4. 调用 enableExternalAudioDataInput 开启自定义采集音频;
  5. 调用 fillExternalAidioFrame 将外部采集的音频塞给 AVSDK;
  6. 以上接口都需要在主线程调用。

混音

  • 音频透传: 主要用于在直播中对 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;

自己采集(自定义)视频采集

自定义视频:

流程图.png

自定义采集画面:主要用于预处理原始数据,比如用户需要人脸识别,画面美化,动效处理等,如下是通过自定义采集画面后,增加动效效果图。

注:动效效果是用户自己增加的,用户可以对原始数据做任何预处理(不仅是动效)。

效果图.png

流程说明:首先请记住,若要使用自定义采集画面,则采集画面的过程与 ILiveSDK 没有任何关系,完全不依赖 ILiveSDK,自定义采集画面的流程中,ILiveSDK 的作用是透传数据以及渲染远程数据。

而本文介绍的流程是:【自定义采集画面】->【画面传入 ILiveSDK】->【远程端收到画面帧渲染】 的整个过程。

流程图如下:

流程图.png

采集前准备

进入房间之后,采集画面之前。

接口 描述
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 画面帧类型

注:

  1. 如果渲染自定义采集的画面使用了 OpenGL,则不能使用 ILiveSDK 中的渲染,否则会 Crash。也就是说,此时界面上应该有两个 view,一个渲染自定义采集的画面,另一个渲染 QAVVideoFrame 对象。
  2. 转换成 QAVVideoFrame 时,属性 color_format 必需填写 AVCOLOR_FORMAT_NV12,srcType 属性必须填写 QAVVIDEO_SRC_TYPE_CAMERA。

如何旋转和裁剪画面

由于观众和主播的屏幕方向和大小都可能不一致,所以需要在观众端,按照观众的屏幕大小和方向对主播的画面进行选择,缩放或裁剪。

接口:

参数名/函数名 说明 默认值
isRotate 主播画面是否旋转 YES(旋转)
sameDirectionRenderMode 方向一致渲染模式 ILIVERENDERMODE_SCALEASPECTFILL(全屏适应)
diffDirectionRenderMode 方向不一致渲染模式 ILIVERENDERMODE_SCALEASPECTFIT(黑边)

方案一 旋转主播画面

效果如下:

屏幕快照 2018-09-19 下午6.04.28.png

实现:

//设定需要旋转画面
iLiveRenderView.isRotate = YES;
//设定是铺满屏幕还是留黑边
iLiveRenderView.sameDirectionRenderMode = ILiveRenderMode;

方案二 不旋转主播画面

效果如下:

屏幕快照 2018-09-19 下午6.04.14.png

实现:

//设定不需要旋转画面
iLiveRenderView.isRotate = NO;
//设定在方向不一致情况下,是铺满屏幕还是留黑边
iLiveRenderView.diffDirectionRenderMode = ILiveRenderMode;
//设定在方向一致情况下,是铺满屏幕还是留黑边
iLiveRenderView.sameDirectionRenderMode = ILiveRenderMode;

直播中断事件的处理

在直播过程中,经常会遇到突发事件导致直播被中断。这里列举一些常见事件的处理方式供开发者参考:

  • 来电话
  • 音频中断
  • 切后台
  • 锁屏
  • 断网
  • crash
  • 被踢

来电话

无需做任何处理。来电话时,来电界面会覆盖当前直播界面,参考切后台处理。

音频中断

无需做任何处理。发产生音频中断(如闹钟事件)时,分两种情况:

  1. 无新界面,不影响当前播放(录制)视频及语音
  2. 有新界面,参考切后台处理

切后台

在直播房间时,如果 90 秒无上行数据,音视频房间会被后台收回。iOS 分两种情况:

  1. 界面被覆盖,此时会暂停播放(录制)视频和语音,回到前面时恢复
  2. 切到后台,短时间只会暂停(同上),长时间会被回收(系统机制)

锁屏

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 中已提供自动对焦功能,用户无需任何配置。

手动对焦

注意:当前只支持后置摄像头手动对焦

如果需要对自带对焦效果不满意,或有高级应用场景需求,用户可以自己完成对焦功能,这里以实现手动对焦为例。

手动对焦流程
流程图.png
  1. 单击事件: 因为交互界面在最顶层,渲染界面在最底层,所以单击事件添加到交互界面上。
  2. 获取单击点坐标: 获取单击手势在视图上的坐标,此坐标是相对于交互视图的坐标。
  3. 将单击手势坐标转换为 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

日志收集接口

适用场景

任何需要从手机上收集互动直播日志的场景。

效果图.png

使用方法

集成 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 如下:

log.jpg

示例:

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 如下:

log.png

示例:

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

摘抄自腾讯云互动直播文档,自己记录使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 本文主要探讨人工智能相关技术在大微映画公司直播业务中的应用场景。 AI+直播应用场景一:直播内容审核 内容审核难点...
    shenciyou阅读 3,256评论 0 6
  • 2016 年是直播元年,也是这个行业最辉煌的一年,不少平台拿到了B轮,甚至是C轮融资。而直播行业的火爆,直接引来了...
    方弟阅读 48,329评论 7 126
  • 视频会议 初试水: 需求是13人同屏视频会议,最多进入视频会议房间的人数上线也就是13人;也因最大障碍网络问题,设...
    ZIM东东阅读 821评论 0 1
  • 全局创建context? 创建一个全局的context,然后退出SDK层房间时不销毁只是停止context。 SD...
    Carden阅读 1,406评论 0 2
  • 戳下边蓝字 无提取码 百度云 日在校园全集 更多动漫 戳右边蓝字→Neets.cc-日在校园
    总想取个不同的昵称阅读 7,670评论 0 0