# 视频管理
Juphoon 视频能力平台支持跨平台的一对一、多对多的通话模式的高清音视频通话,Juphoon RTC SDK 供业务灵活集成应用,实现视频管理能力。
# 1. 发送本地视频流
房间内的成员可通过调用 enableUploadVideoStream (opens new window) 方法来开启关闭发送本地视频流。
/**
* 开启/关闭发送本地视频流
*
* - 调用该方法可开启或关闭发送本地视频流。开启后,房间成员将可以看见本端视频画面;关闭后,房间成员将看不见本端视频画面
* - 房间中调用此方法不影响接收远端视频
* - 初始化 JRTCRoom 时,默认发送本地视频流。若要加入房间时,让房间内其他成员看见本端视频画面,建议在调用 {@link join:joinParam: join} 加入房间前设置
* - 该方法在房间内和房间外均可调用,且在离开房间后该设置仍然有效。也就是说这一次设置了关闭发送本地视频流,那么在下一次加入房间时默认会关闭发送本地视频流
* - 会议中也可调用此方法开启或关闭发送本地视频流,服务器会更新状态并同步给其他房间成员,即房间中所有成员都会收到 {@link JRTCRoomCallback.onParticipantUpdate:participantChangeParam:room onParticipantUpdate} 回调
*
* @param enable 是否发送本地视频流
* - true: 开启,即发送本地视频流
* - false: 关闭,即不发送本地视频流
* @return 接口调用结果
* - true: 接口调用成功
* - 在调用此方法时,用户不在房间中,不会收到回调
* - 在调用此方法时,用户在房间中,会收到 {@link JRTCRoomCallback.onRoomPropertyChanged:room: onRoomPropertyChanged} 回调
* - false: 接口调用异常
*/
- (bool)enableUploadVideoStream:(bool)enable;
- 在多方通话中,enableUploadVideoStream (opens new window) 的作用是设置“是否上传视频流数据”。调用该方法可开启或关闭发送本地视频流。开启后,房间成员将可以看见本端视频画面;关闭后,房间成员将看不见本端视频画面。房间中调用此方法不影响接收远端音频。
- 初始化 JRTCRoom 时,默认发送本地视频流。若要加入房间时,让房间内其他成员看见本端视频画面,建议在调用 join (opens new window) 加入房间前设置。房间中调用此方法不影响接收远端视频。
- 房间中也可调用此方法开启或关闭发送本地视频流,服务器会更新状态并同步给其他房间成员,房间中的其他成员会收到该成员“是否上传音频“的状态变化回调 onParticipantUpdate (opens new window) 。
- 该方法在房间内和房间外均可调用,且在离开房间该设置仍然有效。也就是说这一次设置了关闭发送本地视频流,那么在下一次加入房间时默认会关闭发送本地视频流。
此外,调用该方法发送本地视频流数据还要依赖摄像头是否已经打开。
/**
* 成员更新回调
*
* 当房间中有成员的属性发生变化时,房间中的其他成员会收到此回调,例如音频上传状态、视频上传状态、网络状态等发生变化。
* @param participant JRTCRoomParticipant 成员对象
* @param participantChangeParam JRTCRoomParticipantChangeParam 更新标识类对象
* @param room 当前 JRTCRoom 对象
*/
- (void)onParticipantUpdate:(JRTCRoomParticipant*)participant participantChangeParam:(JRTCRoomParticipantChangeParam *)participantChangeParam room:(JRTCRoom *)room;
示例代码:
// 发送视频流
[_room enableUploadVideoStream:true];
// 停止发送视频流
[_room enableUploadVideoStream:false];
// 成员状态改变通知
- (void)onParticipantUpdate:(JRTCRoomParticipant*)participant participantChangeParam:(JRTCRoomParticipantChangeParam *)participantChangeParam room:(JRTCRoom *)room {
if (changeParam.video) {
// 成员视频上传状态发生改变
if (participant.video) {
// 该成员当前正在视频上传
} else {
// 该成员当前没有视频上传
}
}
}
# 2. 订阅/取消订阅视频流
在房间中可以订阅其他成员不同分辨率的视频流,这个参数结合 SVC 来使用可以实现通话中切换设置的分辨率;订阅视频流 requestVideo (opens new window) ,取消订阅视频流 unRequestVideo (opens new window) 。
/**
* 订阅房间中其他用户的视频流
*
* @param participant JRTCRoomParticipant 成员对象
* @param videoSize 视频请求的尺寸,详见 @ref JRTCVideoSize
* @return 接口调用结果
* - true: 接口调用成功,会收到 {@link JRTCRoomCallback.onParticipantUpdate:changeParam:room: onParticipantUpdate} 回调
* - false: 接口调用异常
*/
- (bool)requestVideo:(JRTCRoomParticipant* __nonnull)participant videoSize:(JRTCVideoSize *__nonnull)videoSize;
/**
* 取消订阅房间中其他用户的视频流
*
* @param participant JRTCRoomParticipant 房间中其他成员对象
* @return 调用是否正常
* - true: 正常执行调用流程,会收到 {@link JRTCRoomCallback.onParticipantUpdate:participantChangeParam:room: onParticipantUpdate} 回调
* - false: 调用失败,不会收到回调通知
*/
- (bool)unRequestVideo:(JRTCRoomParticipant* __nonnull)participant;
示例代码:
-(void)onParticipantJoin:(JRTCRoomParticipant*)participant room:(JRTCRoom *)room {
// 使用 JRTCVideoSize 订阅视频流
JRTCVideoSize *videoSize = [[JRTCVideoSize alloc] initWithSize:720 height:1080];
[_room requestVideo:participant videoSize:videoSize];
//...渲染视频
}
-(void)onParticipantLeft:(JRTCRoomParticipant*)participant reason:(ReasonCode)reason room:(JRTCRoom *)room {
// 取消订阅该成员视频流
[_room unRequestVideo:participant];
//...停止渲染视频
}
# 3. SVC 设置说明
根据实际订阅需求和网络状况动态调整视频发送分辨率是 JSM 房间的特性之一,SVC 可用于设置房间视频的每一层编码分辨率。该参数在房间创建时设置,且全局统一。
具体使用详见 SVC 说明 (opens new window)。
可在加入房间时,通过加入房间参数 JRTCRoomJoinParam (opens new window) 的 svcResolution (opens new window) 属性进行设置,房间全局属性,只有第一个加入房间用户设置有效。
/// SVC层
@property (nonatomic, copy, nullable) NSString *svcResolution;
示例代码:
JRTCRoomJoinParam *param = [[JRTCRoomJoinParam alloc] init];
// 设置svc参数
param.svcResolution = @"1 180 250 360 600 720 1400 1080 1600";
...
// 加入房间
[_room join:@"roomId" joinParam:param];
# 4. 设置本地视频宽高比
在房间内设置本地房间宽高比,会影响视频画面宽高比,用于适配不同屏幕的显示需求,需在进入房间后调用。
/**
* 设置本端视频宽高比
*
* 将自己的视频采集根据宽高比裁剪后进行发送,通话中其他成员收到的画面将是裁剪后的比例。<br>
* 该方法不影响其他成员的画面在本端的显示比例,也不影响其他成员相互之间的画面显示比例。<br>
* 必须 ***加入房间后*** 设置才能生效,即收到 {@link JRTCRoomCallback.onJoin:reason:roomId:room: onJoin} 回调后设置才生效。
* @param ratio 视频宽高比
* @return 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
- (bool)setRatio:(float)ratio;
示例代码:
//加入房间后
//设置本地宽高比9/16
[_room setRatio: 0.5625];
# 5. 设置房间视频清晰度
如果觉得设置 SVC 不好理解,可以直接设置属性 videoDefinition (opens new window) 来调整通话视频清晰度(一组已经定义的 SVC 和 帧率),VideoDefinitionType (opens new window) 详见 API 文档。
/**
* 设置通话视频清晰度,主要通过修改 svcResolution 参数和 maxFrameRate 参数调整清晰度,
* 默认为 DefinitionCustom
*/
@property (nonatomic, assign) VideoDefinitionType videoDefinition;
示例代码:
// 创建配置参数
JRTCRoomJoinParam *param = [[JRTCRoomJoinParam alloc] init];
// 设置通话视频清晰为流畅模式,低帧率
param.videoDefinition = DefinitionFluencyFrameLow;
// 加入房间
[_room join:@"12345" joinParam:param];
# 6. 视频渲染管理
# 查创建本地视频画面
初始化成功后,可以创建本地的视频画面,创建本地视频画面的时机没有具体要求,在加入房间前后调用皆可。
- 通过 startCameraVideo (opens new window) 方法,获取 JRTCMediaDeviceVideoCanvas (opens new window)本地的视频对象。
- 在界面画布上渲染本地视频对象画面。
/**
* 开始本端视频渲染
*
* 获取本端视频预览对象 JRTCMediaDeviceVideoCanvas ,通过此对象能获得视图用于UI显示
* @note
* 调用此方法时需要保证默认摄像头不为空,即 @ref defaultCamera 不为空,否则将直接返回 nil
* @param renderType 渲染模式:
* - @ref JRTCMediaDeviceRenderFullScreen : 铺满窗口,会有裁剪
* - @ref JRTCMediaDeviceRenderFullContent : 全图像显示,会有黑边
* - @ref JRTCMediaDeviceRenderFullAuto : 自适应
* @return
* - JRTCMediaDeviceVideoCanvas 对象: 开始自身视频渲染成功
* - nil: 开始自身视频渲染失败
*/
- (JRTCMediaDeviceVideoCanvas* __nullable)startCameraVideo:(JRTCMediaDeviceRender)renderType;
/**
* 开始自身视频渲染
*
* 获取本端视频预览对象 JRTCMediaDeviceVideoCanvas ,通过此对象能获得视图用于UI显示
* @note
* 调用此方法时需要保证默认摄像头不为空,即 @ref defaultCamera 不为空,否则将直接返回 nil
* @param type 渲染模式:
* - @ref JRTCMediaDeviceRenderFullScreen : 铺满窗口,会有裁剪
* - @ref JRTCMediaDeviceRenderFullContent : 全图像显示,会有黑边
* - @ref JRTCMediaDeviceRenderFullAuto : 自适应
* @param view 渲染视图控件
* @return
* - JRTCMediaDeviceVideoCanvas 对象: 开始自身视频渲染成功
* - nil: 开始自身视频渲染失败
*/
- (JRTCMediaDeviceVideoCanvas* __nullable)startCameraVideo:(JRTCMediaDeviceRender)type view:(JCView* __nonnull)view;
JRTCMediaDeviceRender (opens new window) 决定了视频的渲染模式:
- FullScreen 为填充模式:即将画面内容居中等比缩放以充满整个显示区域,超出显示区域的部分将会被裁剪掉,此模式下画面可能不完整;
- FullContent 为适应模式:即按画面长边进行缩放以适应显示区域,短边部分会被填充为黑色,此模式下图像完整但可能留有黑边。
示例代码:
// 创建本端视图渲染对象
JRTCMediaDeviceVideoCanvas *canvas = [_mediaDevice startCameraVideo:JRTCMediaDeviceRenderFullContent];
// 设置视图位置与尺寸
canvas.videoView.frame = CGRectMake(0, 0, 100, 100);
// 将渲染视图添加到界面上
[self.view addSubview:canvas.videoView];
# 创建远端视频画面
当加入房间后,除了本地的视频画面,还有房间内其他成员的视频画面,如果房间内其他成员有视频流上传,本端可以获取到其他成员的的视频流并进行渲染,详细见 4.3.6 章节;
当成员视频状态变化,比如该成员开始上传视频,此时可以去渲染该成员视频,该成员结束上传视频,则可以去停止渲染该成员视频。
# 订阅/取消订阅视频
在渲染成员视频前,需要先通过调用 requestVideo (opens new window) 方法订阅该视频,当成员离开需要调用 unRequestVideo (opens new window) 及时取消订阅该视频流。
/**
* 订阅房间中其他用户的视频流
*
* @param participant JRTCRoomParticipant 成员对象
* @param videoSize 视频请求的尺寸,详见 @ref JRTCVideoSize
* @return 接口调用结果
* - true: 接口调用成功,会收到 {@link JRTCRoomCallback.onParticipantUpdate:changeParam:room: onParticipantUpdate} 回调
* - false: 接口调用异常
*/
- (bool)requestVideo:(JRTCRoomParticipant* __nonnull)participant videoSize:(JRTCVideoSize *__nonnull)videoSize;
/**
* 取消订阅房间中其他用户的视频流
*
* @param participant JRTCRoomParticipant 房间中其他成员对象
* @return 调用是否正常
* - true: 正常执行调用流程,会收到 {@link JRTCRoomCallback.onParticipantUpdate:participantChangeParam:room: onParticipantUpdate} 回调
* - false: 调用失败,不会收到回调通知
*/
- (bool)unRequestVideo:(JRTCRoomParticipant* __nonnull)participant;
# 渲染/停止渲染视频
远端视频渲染可以通过调用 startVideo (opens new window) 来实现,停止渲染通过调用 stopVideo (opens new window) 来实现。
/**
* 开始其他端的视频渲染
*
* 获取其他端的视频预览对象 JRTCMediaDeviceVideoCanvas ,通过此对象能获得视图用于UI显示
*
* @param streamId 视频流ID
* @param type 渲染模式:
* - @ref JRTCMediaDeviceRenderFullScreen : 铺满窗口,会有裁剪
* - @ref JRTCMediaDeviceRenderFullContent : 全图像显示,会有黑边
* - @ref JRTCMediaDeviceRenderFullAuto : 自适应
* @return
* - JRTCMediaDeviceVideoCanvas 对象: 开始其他端视频渲染成功
* - nil: 开始其他端视频渲染失败
*/
- (JRTCMediaDeviceVideoCanvas* __nullable)startVideo:(NSString* __nonnull)streamId renderType:(JRTCMediaDeviceRender)type;
/**
* 开始其他端的视频渲染
*
* 获取其他端的视频预览对象 JRTCMediaDeviceVideoCanvas ,通过此对象能获得视图用于UI显示
*
* @param streamId 视频流ID
* @param type 渲染模式:
* - @ref JRTCMediaDeviceRenderFullScreen : 铺满窗口,会有裁剪
* - @ref JRTCMediaDeviceRenderFullContent : 全图像显示,会有黑边
* - @ref JRTCMediaDeviceRenderFullAuto : 自适应
* @param view 渲染视图控件
* @return
* - JRTCMediaDeviceVideoCanvas 对象: 开始其他端视频渲染成功
* - nil: 开始其他端视频渲染失败
*/
- (JRTCMediaDeviceVideoCanvas* __nullable)startVideo:(NSString* __nonnull)streamId renderType:(JRTCMediaDeviceRender)type view:(JCView* __nonnull)view;
/**
* 停止视频渲染
* @param canvas JRTCMediaDeviceVideoCanvas 对象,由 {@link startVideo:renderType: startVideo} 或 {@link startCameraVideo: startCameraVideo} 接口返回
*/
- (void)stopVideo:(JRTCMediaDeviceVideoCanvas* __nonnull)canvas;
示例代码:
- (void)onParticipantUpdate:(JRTCRoomParticipant*)participant changeParam:(JRTCRoomParticipantChangeParam *)changeParam room:(JRTCRoom *)room {
if (changeParam.video) {
if (participant.video) {
//根据需要的视频尺寸订阅该成员视频
[_room requestVideo:participant videoSize:[[JRTCVideoSize alloc] initWithSize:1280 height:720]];
JRTCMediaDeviceVideoCanvas *canvas = [_mediaDevice startVideo:participant.streamId renderType:JRTCMediaDeviceRenderFullContent];
canvas.videoView.frame = CGRectMake(0, 0, 100, 100);
[self.view addSubview:canvas.videoView];
} else {
//取消订阅该成员视频
[_room unRequestVideo:participant];
//停止渲染该成员视频
[_mediaDevice stopVideo:canvas];
}
}
}
#
# 停止视频渲染
# 销毁本地和远端视频画面
当不再需要查看视频画面,包括房间成员离开,或者离开房间,需要调用 stopVideo (opens new window) 接口来停止渲染的资源,该方法需传入要释放的 JRTCMediaDeviceVideoCanvas (opens new window) 对象,或者调用 stopAllVideos (opens new window) 接口来时停止所有正在渲染的视频,必须进行这步操作,不然会造成渲染内存不释放。
/**
* 停止视频渲染
* @param canvas JRTCMediaDeviceVideoCanvas 对象,由 {@link startVideo:renderType: startVideo} 或 {@link startCameraVideo: startCameraVideo} 接口返回
*/
- (void)stopVideo:(JRTCMediaDeviceVideoCanvas* __nonnull)canvas;
/**
* 停止所有视频渲染
*/
- (void)stopAllVideos;
示例代码:
// 离开房间回调
- (void)onLeave:(ReasonCode)reason roomId:(NSString *)roomId room:(JRTCRoom *)room {
// 停止本端视频
[_mediaDevice stopVideo:localCanvas];
// 停止远端成员视频
[_mediaDevice stopVideo:remoteCanvas];
// 停止所有正在渲染的视频
[_mediaDevice stopAllVideos];
}
- (void)onParticipantLeft:(JRTCRoomParticipant*)participant reason:(ReasonCode)reason room:(JRTCRoom *)room {
//其中remoteCanvas对象是通过 startVideo 返回
[_mediaDevice stopVideo:remoteCanvas];
}
# 视频渲染回调
调用视频渲染接口后主要关注两个回调接口 onRenderReceived (opens new window) 和 onRenderStart (opens new window) ,
- onRenderReceived (opens new window) 第一帧视频数据;
- onRenderStart (opens new window) 渲染并展示画面。
使用场景举例:为了提升交互体验,可以在调用渲染接口后,UI 界面先显示 "视频加载中" 字样,等收到渲染开始接口时,再显示视频画面。
/**
* 收到第一帧数据回调
*
* @param canvas 视图渲染对象
* @param ratio 宽高比
*/
- (void)onRenderReceived:(JRTCMediaDeviceVideoCanvas*)canvas ratio:(CGFloat)ratio;
/**
* 渲染开始回调
*
* @param canvas 视图渲染对象
* @param ratio 宽高比
*/
- (void)onRenderStart:(JRTCMediaDeviceVideoCanvas*)canvas ratio:(CGFloat)ratio;
# 7. 视频截图
视频业务存在对当前通话截图的业务操作,以便于记录用户的操作行为,详见 snapshotWithStreamId (opens new window) 。
/**
* 截图
*
* @param streamId 要截图的视频流ID
* @param path 要存放截图的文件路径
* @return 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
- (bool)snapshotWithStreamId:(NSString* __nonnull)streamId path:(NSString* __nonnull)path;
结果通过实现 JRTCMediaDeviceCallback (opens new window) 的 onSnapshotComplete (opens new window) 接口上报。
/**
* 截图完成回调
*
* @param file 截图路径
* @param width 图片像素宽
* @param height 图片像素高
*/
- (void)onSnapshotComplete:(NSString *)file width:(int)width height:(int)height;
示例代码:
// 截取指定 视频流ID的帧图片并且保存到指定路径
// 视频流,可以是本地视频流、对端视频流或者屏幕共享视频流
[_mediaDevice snapshotWithStreamId:@"streamId" path:@"filePath"];
// 截图完成
- (void)onSnapshotComplete:(NSString *)file width:(int)width height:(int)height {
// 可以从文件路径获取截图文件进行后续操作
}
# 8. 视频采集回调
当打开本端摄像头视频预览或者打开本地屏幕采集时,通过实现 JRTCMediaDeviceCallback (opens new window) 的 onVideoCaptureDidStart (opens new window) 接口能收到采集开始通知。
/**
* 视频采集开始回调
*
* @param streamId 视频流ID
* @param ratio 宽高比
*/
- (void)onVideoCaptureDidStart:(NSString *)streamId ratio:(CGFloat)ratio;
# 9. 视频异常回调
通过实现 JRTCMediaDeviceCallback (opens new window) 的 onVideoError (opens new window) 接口来监听视频异常、采集异常、渲染错误等事件,具体原因查看参数 error 描述。
/**
* 视频异常,渲染错误,包括摄像头采集错误、屏幕采集错误等回调
*
* @param errorType 异常类型
* @param errorDetail 异常详细描述
*/
- (void)onVideoError:(JRTCMediaDeviceVideoErrorType)errorType errorDetail:(NSString *)errorDetail;
# 10. 自定义视频数据
如果在房间内,用户不想用默认的摄像头视频数据,可以通过以下接口自定义注入视频数据;
- 首先需要调用 startVideoFile (opens new window) 开启使用视频文件作为视频输入源,加入房间前或者加入房间后均可调用;
- 然后通过 setVideoFileFrame (opens new window) 循环注入视频数据,目前只支持 I420 数据;
- 如果结束视频数据中注入,则需要调用 stopVideoFile (opens new window) 接口;
/**
* 开启视频文件作为视频输入源
*
* @note 文件和摄像头作为视频输入源只能存在一种,如果当前摄像头已开启的话会关闭摄像头
* @return 接口调用结果
* - true: 接口调用成功
* - 若调用此方法时文件视频源已开启,则不会收到回调
* - 若调用此方法时文件视频源还未开启,则会收到 {@link JRTCMediaDeviceCallback.onCameraUpdate onCameraUpdate} 回调
* - false: 接口调用异常,不会收到回调
*/
- (bool)startVideoFile;
/**
* 关闭视频文件作为视频输入源
* @return 接口调用结果
* - true: 接口调用成功
* - 若调用此方法时文件视频源已关闭,不会收到回调
* - 若调用此方法时文件视频源未关闭,则会收到 {@link JRTCMediaDeviceCallback.onCameraUpdate onCameraUpdate} 回调
* - false: 接口调用异常
*/
- (bool)stopVideoFile;
/**
* 逐帧采集视频画面
*
* 调用此方法时要保证文件视频源已开启
*
* @note 目前 format 只支持类型 I420
*
* @param srcFrame 画面二进制数据
* @param format @ref JRTCMediaDeviceVideoPixelFormat "视频像素格式"
* @param width 视频画面像素宽
* @param height 视频画面像素高
* @param angle 视频角度,为 90 的倍数
* @return 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
- (bool)setVideoFileFrame:(NSData* __nonnull)srcFrame format:(JRTCMediaDeviceVideoPixelFormat)format width:(int)width height:(int)height angle:(int)angle;
# 11. 视频帧回调
目前只支持渲染视频帧回调,即需要渲染某个成员视频或者渲染屏幕共享、本地视频后可以收到视频帧回调,
接口如下 setVideoFrameCallBack (opens new window) 。
/**
* 设置视频帧回调
* @param frameCallBack 获得视频流数据回调代理对象
*/
- (void)setVideoFrameCallBack:(id<JRTCMediaDeviceVideoFrameCallback>)frameCallBack;
/// 视频帧数据回调
@protocol JRTCMediaDeviceVideoFrameCallback <NSObject>
/**
* 获得视频流数据回调
* @param angle 视频角度, 为 90 的倍数
* @param mirror 是否镜像,0 不镜像,1 镜像
* @param width 画面像素宽
* @param height 画面像素高
* @param data I420格式帧数据
*/
- (void)onFrame:(int)angle mirror:(int)mirror width:(int *)width height:(int *)height data:(NSData *)data;
@end
示例代码:
//开始本地视频渲染
JRTCMediaDeviceVideoCanvas *canvas = [_mediaDevice startCameraVideo: JRTCMediaDeviceRenderFullContent];
@interface xxx ()<JRTCMediaDeviceVideoFrameCallback> {
}
- (void)onFrame:(int)angle mirror:(int)mirror width:(int *)width height:(int *)height data:(NSData *)data {
//视频数据
}
//设置视频数据回调
[canvas setVideoFrameCallBack: self];