iOS

# 视频管理

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;
  1. 在多方通话中,enableUploadVideoStream (opens new window) 的作用是设置“是否上传视频流数据”。调用该方法可开启或关闭发送本地视频流。开启后,房间成员将可以看见本端视频画面;关闭后,房间成员将看不见本端视频画面。房间中调用此方法不影响接收远端音频。
  2. 初始化 JRTCRoom 时,默认发送本地视频流。若要加入房间时,让房间内其他成员看见本端视频画面,建议在调用 join (opens new window) 加入房间前设置。房间中调用此方法不影响接收远端视频。
  3. 房间中也可调用此方法开启或关闭发送本地视频流,服务器会更新状态并同步给其他房间成员,房间中的其他成员会收到该成员“是否上传音频“的状态变化回调 onParticipantUpdate (opens new window)
  4. 该方法在房间内和房间外均可调用,且在离开房间该设置仍然有效。也就是说这一次设置了关闭发送本地视频流,那么在下一次加入房间时默认会关闭发送本地视频流。

此外,调用该方法发送本地视频流数据还要依赖摄像头是否已经打开。

/**
 * 成员更新回调
 *
 * 当房间中有成员的属性发生变化时,房间中的其他成员会收到此回调,例如音频上传状态、视频上传状态、网络状态等发生变化。
 * @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. 视频渲染管理

# 查创建本地视频画面

初始化成功后,可以创建本地的视频画面,创建本地视频画面的时机没有具体要求,在加入房间前后调用皆可。

  1. 通过 startCameraVideo (opens new window) 方法,获取 JRTCMediaDeviceVideoCanvas (opens new window)本地的视频对象。
  2. 在界面画布上渲染本地视频对象画面。
/**
 * 开始本端视频渲染
 *
 * 获取本端视频预览对象 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 为适应模式:即按画面长边进行缩放以适应显示区域,短边部分会被填充为黑色,此模式下图像完整但可能留有黑边。

img

示例代码:

// 创建本端视图渲染对象
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)

使用场景举例:为了提升交互体验,可以在调用渲染接口后,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. 自定义视频数据

如果在房间内,用户不想用默认的摄像头视频数据,可以通过以下接口自定义注入视频数据;

/**
 * 开启视频文件作为视频输入源
 *
 * @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];