iOS

# 音视频录制

在开展在线理财、开户、面签等业务时,应国家监管要求,必须提供录音录像服务,形成交易记录的视频,存档备查。

Juphoon RTC SDK 在音视频通话过程中,支持全程进行实时的服务器录制或本地录制,录制的场景画面覆盖座席画面和所有终端类型的访客画面,按需可支持多摄像头、多屏幕合成录制,满足用户记录业务办理全过程录制的需求。

# 本地录制

本地录制支持实时的通话过程音频录制,录制文件保存在用户本地设备中,适用于通话过程录音录像场景等其他音视频相关场景。优点不受网络影响,录制画面质量高,减少带宽压力。

录制通话在本地生成视频文件。

/**
* 开启/关闭本地录制
*
* @param enable      开启或关闭本地录制
*                    - true: 开启本地录制
*                    - false: 关闭本地录制
* @param recordParam 本地录制参数配置,当 enable == true 时,{@link JRTCRecordLocalParam#filePath} 必须设置,其余参数不设置则使用默认配置;当 enable == false 时,recordParam 可传 undefined
* @return 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
* @note 确保调用接口前本地录制文件所在目录已经存在,否则会录制失败
* @see JRTCRecordLocalParam
*/
public abstract enableLocalRecord(enable: boolean, recordParam: JRTCRecordLocalParam | undefined): boolean;

/**
* 获取是否正在本地录制
*
* @return 是否正在本地录制
* - true: 本地录制中
* - false: 未进行本地录制
*/
public abstract isLocalRecording(): boolean;

本地录制参数详见 JRTCRecordLocalParam (opens new window)

示例代码:

let param: JRTCRecordLocalParam = new JRTCRecordLocalParam();
param.recVideo = true;//设置是否录制视频
param.recAudio = true;//设置是否录制音频
param.includeSelf = true;//设置录制是否包含自己
param.mergeMode = JRTCVideoMergeMode.INTELLIGENT_LAYOUT;//设置媒体录制视频合并模式,默认智能分屏模式,当使用配置文件时,该参数无效
param.intelligentMergeMode = JRTCIntelligentMergeMode.FREE_LAYOUT;//智能分屏模式下的布局样式(无屏幕共享)
param.scsMergeMode = JRTCScsMergeMode.SCREEN_SHARE;//智能分屏模式下的布局样式(有屏幕共享)
param.frameRate = 18;//设置录制帧率,默认15
param.iBitrate = 0;//设置录制码率
param.videoWidth = 640;//设置录制视频的宽度
param.videoHeight = 360;//设置录制视频的高度
param.filePath = "";//设置保存的文件路径

更新本地录制自定义布局 updateLocalRecordLayout (opens new window),当本地录制已经在进行时,可以通过该接口实时更新本地录制布局

/**
* 更新本地录制自定义布局
*
* @param layoutList 需要更新的布局列表
* @return 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
public abstract updateLocalRecordLayout(layoutList: ArrayList<JRTCRecordLocalLayout>): boolean;

示例代码:

let layoutList:ArrayList<JRTCRecordLocalLayout> = new ArrayList();
let layout1:JRTCRecordLocalLayout = new JRTCRecordLocalLayout();
layout1.setId("streamId0",false);
layout1.position = 0;
layoutList.add(layout1);

let layout2:JRTCRecordLocalLayout = new JRTCRecordLocalLayout();
layout1.setId("streamId1", false);
layout1.position = 1;
layoutList.add(layout2);

call.updateLocalRecordLayout(layoutList);

通过配置文件实现本地录制

流程图如下

img

本地录制配置文件示例如下:

{
  "default" : {   //录制配置类型, 固定值.
    "videoRecord" : {   //视频录制配置, 固定值.
      "layout" : [        //自定义布局,mergeMode 字段为自定义布局模式 {@link JRTCVideoMergeMode#CUSTOM_LAYOUTCUSTOM_LAYOUT(4)} 时生效, JSON数组格式, 可同时设置多个, 实际应用时通过窗口号区分.
        {
          "posX" : 0.25,                  //画面左上角的横坐标(双精度浮点数类型), 代表占画面总宽度的比例.
          "posY" : 0.26805556000000003,   //画面左上角的纵坐标(双精度浮点数类型), 代表占画面总高度的比例.
          "width" : 0.1140625,            //画面的宽度(双精度浮点数类型), 代表占画面总宽度的比例.
          "height" : 0.47916666000000002, //画面的高度(双精度浮点数类型), 代表占画面总高度的比例.
          "window" : 0                    //窗口号(大于或等于的整数), 在使用 {@link RecordLayout#setLayoutList(List) setLayoutList} 自定义视频窗口布局时需要, 对应于用户自定义的位置信息 {@link RecordLayout#setPosition(int)} setPosition} 设置布局位置.
        },
        {
          "posX" : 0.49609375,            //画面左顶点的横坐标(双精度浮点数类型), 范围为[0, 1], 代表占画面总宽度的比例.
          "posY" : 0.033333334999999999,  //画面左顶点的纵坐标(双精度浮点数类型), 范围为[0, 1], 代表占画面总高度的比例.
          "width" : 0.36406250000000001,  //画面的宽度(双精度浮点数类型), 范围为[0, 1], 代表占画面总宽度的比例.
          "height" : 0.94027775999999996, //画面的高度(双精度浮点数类型) 范围为[0, 1], 代表占画面总高度的比例.
          "window" : 1                    //窗口号(大于或等于的整数), 在使用 {@link RecordLayout#setLayoutList(List) setLayoutList} 自定义视频窗口布局时需要, 对应于用户自定义的位置信息 {@link RecordLayout#setPosition(int)} setPosition} 设置布局位置.
        }
      ],
        "mergeBitrate" : 0, //设置录制视频的码率, 单位为千比特每秒(kbps), 默认值为0, 此时码率由内部媒体算法进行自适应调节
        "mergeFPS" : 30,    //设置录制视频的帧率, 单位为每秒传输帧数(fps), 默认值为20.
        "mergeWidth" : 1280,//设置录制视频的宽度, 默认值为 640
        "mergeHeight" : 720,//设置录制视频的高度, 默认值为 360
        "mergeMode" : 4,    //设置媒体推流的视频合并模式, 默认值为 {@link JRTCVideoMergeMode#INTELLIGENT_LAYOUT INTELLIGENT_LAYOUT(5)}, 目前可支持自定义布局 {@link JRTCVideoMergeMode#CUSTOM_LAYOUTCUSTOM_LAYOUT(4)} 和智能布局 {@link JRTCVideoMergeMode#INTELLIGENT_LAYOUT INTELLIGENT_LAYOUT(5)}.
        "mergeModeI" : 1,   //设置智能分屏模式下的布局样式(无屏幕共享), 默认值为自由布局 {@link JRTCIntelligentMergeMode#FREE_LAYOUT FREE_LAYOUT(1)}, 有效值参考 {@link JRTCIntelligentMergeMode. 智能分屏模式下的布局样式(无屏幕共享)}, 本字段仅在 mergeMode 为智能布局 {@link JRTCVideoMergeMode#INTELLIGENT_LAYOUT INTELLIGENT_LAYOUT(5)} 时生效.
        "screenShareType" : 1 //设置智能分屏模式下的布局样式(有屏幕共享), 默认值为屏幕共享独占 {@link JRTCScsMergeMode#SCREEN_SHARE SCREEN_SHARE(1)}, 有效值参考 {@link JRTCScsMergeMode 智能分屏模式下的布局样式(有屏幕共享)}, 本字段仅在 mergeMode 为智能布局 {@link JRTCVideoMergeMode#INTELLIGENT_LAYOUT INTELLIGENT_LAYOUT(5)} 时生效.
    },
    "watermark" : { //水印配置, 固定值.
      "picture" : [   //图片水印配置, 固定值, 图片水印目前只支持 png 格式.
        {
          "enable" : true,    //是否启用(布尔类型).
          "pcUrl" : "http://192.168.17.60:10042/protected_files/6afa960e-fe4a-49ae-a939-48c788122192?attname=juphoon.png",    //图片水印链接地址.
          "index" : 1,        //水印的序号.
          "state" : 1,        //水印的状态. 设置值为1表示使用水印,设置值为2表示关闭水印.
          "posX" : 0,         //相对于基准位置的水平偏移值(整数类型), 负值向左偏移, 正值向右偏移, 实际大小非比例值.
          "posY" : 600        //相对于基准位置的垂直偏移值(整数类型), 负值向上偏移, 正值向下偏移, 实际大小非比例值.
        }
      ],
        "text" : {  //文本水印配置, 固定值.
        "enable" : true,    //是否启用(布尔类型).
          "memo" : [          //文本水印样式配置(JSON数组类型), 如果不设置就使用默认全局样式.
          "Dialogue: 0,0:00:00.0,60:00:00.0,Default,,0,0,0,,{\\pos(34, 56)\\an7}菊风文本水印1$@name@$",
          "Dialogue: 0,0:00:00.0,60:00:00.0,Default,,0,0,0,,{\\pos(50, 100)\\an7}菊风文本水印2"
          //数组的每个元素为一条 ASS 字幕的 event 字符串, 支持通过标签来设置各种文字特效, 添加的event字符串必须是utf-8编码, 否则中文会出现乱码. ASS 格式规范下载地址: http://www.perlfu.co.uk/projects/asa/ass-specs.doc
          //其中以 "$@" 开始并且以 "@$" 结束的部分内容"$@name@$"将提取出关键字"name", 通过解析用户通过 {@link #setWatermarkTextMap(Map) setWatermarkTextMap} 设置的自定义文本水印信息获取其对应值, "$@name@$"格式的内容被其对应值替换后就是实际应用的 event 字符串, "$@xxx@$" 格式的内容支持同时设置多个.
        ],
          "style" : {         //文本水印格式, 固定值.
          "enable" : true,        //格式是否启用(布尔类型).
            "alignment" : 0,        //对齐方式, 有效值参考 0:左对齐;1:居中对齐;2:右对齐;
            "backColor" : 16777215, //背景颜色(整数类型), 10进制颜色代码.
            "blod" : false,         //是否使用粗体(布尔类型).
            "fontColor" : 16777215, //字体颜色(整数类型), 10进制颜色代码.
            "fontFile" : "SourceHanSansCN-Normal.otf",//字体文件路径,Windows系统上只能用相对路径(相对配置文件所在路径),不可以带盘符,建议和配置文件放在同个目录下.
            "fontSize" : 36,        //字体尺寸(整数类型).
            "italic" : true,        //是否使用斜体(布尔类型).
            "underline" : false     //是否带有下划线(布尔类型).
        }
      },
      "timestamp" : { //时间戳水印配置, 固定值.
        "enable" : true,    //是否启用(布尔类型).
          "basePosType" : 0,  //水印基准位置类型(整数类型), 有效值参考 0:左上;1:左下;2:右上;3:右下;4:居中;
          "borderWidth" : 2,  //字体边界宽度(整数类型), 取值范围为[0, 5].
          "fontFile" : "SourceHanSansCN-Normal.otf",//字体文件路径,Windows系统上只能用相对路径(相对配置文件所在路径),不可以带盘符,建议和配置文件放在同个目录下.
          "fontColor" : 0, //字体颜色(整数类型), 有效值参考 0:红色;1:黄色;2:绿色;3:青色;4:蓝色;5:洋红色;6:白色;7:中和色;8:黑色;
          "fontSize" : 36,    //字体尺寸(整数类型).
          "isMs" : true,      //是否显示毫秒值(布尔类型).
          "posX" : 0,         //相对于基准位置的水平偏移值(整数类型), 负值向左偏移, 正值向右偏移, 实际大小非比例值.
          "posY" : 0          //相对于基准位置的垂直偏移值(整数类型), 负值向上偏移, 正值向下偏移, 实际大小非比例值.
      }
    }
  }
}

配置样例文件(不带注解):📎record.cfg.zip (opens new window)

# 本地录制(不需要建立通信)

视频录制参数详见: JRTCRecordVideoCaptureParam (opens new window)

/**
* 开启视频录制(本地录制,不需要建立通信,不能和音频录制 {@link startAudioRecord startAudioRecord} 同时开启)
*
* @param { string } streamId 视频流ID, (包括摄像头ID、文件视频源ID、屏幕采集流ID等)
* @param { JRTCRecordVideoCaptureParam } recordParam 录制参数
* @return { boolean } 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
public abstract startVideoCaptureRecord(streamId: string, recordParam: JRTCRecordVideoCaptureParam): boolean;

/**
* 关闭视频录制(本地录制,不需要建立通信)
*
* @param { string } streamId 视频流ID (包括摄像头ID、文件视频源ID、屏幕采集流ID等)
* @return { boolean } 关闭视频录制是否成功
*/
public abstract stopVideoCaptureRecord(streamId: string): boolean;
/**
* 开启音频录制(本地录制,不需要建立通信,不能和视频录制 {@link startVideoCaptureRecord startVideoCaptureRecord} 同时开启)
*
* @param { string } filePath 保存的文件路径,必须包含文件名(xxx.wav或者xxx.pcm)
* @param { JRTCMediaDeviceRecordAudioSource } audioSource 录制文件音频源
* @param { JRTCMediaDeviceAudioRecordFileType } fileType 录制文件编码封装类型
* @return { boolean } 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
public abstract startAudioRecord(filePath: string, audioSource: JRTCMediaDeviceRecordAudioSource, fileType: JRTCMediaDeviceAudioRecordFileType): boolean

/**
* 关闭音频录制(本地录制,不需要建立通信)
*
* @return { boolean } 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
public abstract stopAudioRecord(): boolean;

录制水印(目前不支持):本地录制支持图片、文字、时间戳水印,通过录制参数设置,详见:Image Text TimeStamp

示例代码:

// 打开摄像头
mediaDevice.startCamera();
// 打开麦克风
mediaDevice.startAudioInput();
let param: JRTCRecordVideoCaptureParam = new JRTCRecordVideoCaptureParam();
param.filePath = "filePath";
param.audioSource = JRTCMediaDeviceRecordAudioSource.FROM_MICROPHONE;
param.fileType = JRTCMediaDeviceVideoRecordFileType.MP4_H264;
param.width = 640
param.height = 340
mMediaDevice.startVideoCaptureRecord(mMediaDevice.getCurrentCamera()!.cameraId!, param)
// 开启本地音频录制
mediaDevice.startAudioRecord("/sdcard/1.wav",param.audioSource, param.fileType);
// 关闭本地音频录制
mediaDevice.stopAudioRecord();

# 远程录制

在线上金融的应用场景中,考虑取证、质检、审核、存档和回放等需求,常需要将整个视频通话过程录制,并存储。

Juphoon RTC SDK 的远程录制,通过会场服务将收到的所有终端数据发送给录制服务器,进行实时录制。在实际的集成中,当 SDK 初始化完成后,集成方通过加入会场的方式将需要进行录制的音视频流上传至服务器并进行实时录制。 

# 开启/关闭远程录制

访客在发起呼叫的时候可以携带 JRTCCallCenterCallParam (opens new window).autoRecord (opens new window) 参数来设置是否在通话开始后就进行远程录制。

如果该次通话没有设置自动远程录制,则可以通过终端来触发录制,在服务端形成通话过程的视频文件。

录制的配置(如水印、时间戳等)与自动录制的相同,都来自系统管理平台后台的配置。

/**
* 开启/关闭远程视频录制
*
* 当呼叫参数 {@link JRTCCallCenterCallParam#autoRecord autoRecord} == false 时,可通过此接口开启服务端录制。<br>
* 可用过 {@link #getRemoteRecordState} 接口获取当前服务器录制状态。
* @param enable 开启或关闭视频录制
* - true: 开启视频录制
* - false: 关闭视频录制
* @param recordParam 录制参数,当 enable == false 时,可传 undefined;当 enable == true 且按照默认配置进行录制可传 undefined
* @return 接口调用结果
* - true: 接口调用成功,录制状态通过 {@link JRTCGuestCallback#onCallPropertyChanged onCallPropertyChanged} 回调获得,具体可关注 {@link JRTCRoom.PropChangeParam#remoteRecordState remoteRecordState}
* - false: 接口调用异常
*/
public abstract enableRemoteRecord(enable: boolean, recordParam: JRTCRecordRemoteParam): boolean;

注意:该远程录制接口不经过排队机服务。

远端录制参数详见 JRTCRecordRemoteParam (opens new window)

录制状态改变通过 onCallPropertyChanged (opens new window) 回调通知到访客。

通话属性改变详见 JRTCRoomPropChangeParam (opens new window),录制状态具体可关注 getRemoteRecordState (opens new window)

/**
* 通话属性改变回调
* @note
* 重点关注屏幕共享,即当{@link PropChangeParam#screenShare screenShare} 属性为 true 时,去处理屏幕共享相关事件。<br>
* 可根据 {@link JRTCGuest#getShareStreamId shareRenderId} 和 {@link JRTCGuest#getShareUserId shareUserId} 属性进行屏幕共享画面的渲染和停止渲染。
* @param propChangeParam 通话改变的属性
*/
onCallPropertyChanged?: (propChangeParam: JRTCRoomPropChangeParam) => void;

示例代码:

let param:JRTCRecordRemoteParam = new JRTCRecordRemoteParam();
param.recordVideo = true
...
let map:HashMap<string,string> = new HashMap()
map.set("guest","juphoon001")
param.watermarkTextMap = map
if (guest.getRemoteRecordState() == JRTCRemoteRecordState.READY) {
  //开启远程录制
  call.enableRemoteRecord(true, param);
}

//关闭远程录制
call.enableRemoteRecord(false, new JRTCRecordRemoteParam());

//远程录制状态发生改变
onCallPropertyChanged: (propChangeParam: JRTCRoomPropChangeParam) => {
  if (propChangeParam.recordState) {
    if (call.getRemoteRecordState == JRTCRemoteRecordState.RUNNING) {
      //当前正在远程录制
    } 
  }
}

# 远程录制异常回调

详见 onDeliveryAbort (opens new window)

/**
* 录制异常回调
* <p>
* 远程录制异常退出时会上报此回调。
*
* @param isShutDown  录制异常时服务器是否自动结束通话
*                    - true: 自动结束通话
*                    - false: 不自动结束通话
* @param deliveryUserId 录制异常的用户ID
* @param reason      录制异常的原因
* @param room        当前 JRTCRoom 对象
*/
onDeliveryAbort?: (isShutDown: boolean, deliveryUserId: string, reason: string) => void;

# 水印

开启服务器音视频录制接口,支持配置文字水印,录制完成后视频保存于服务端。

Juphoon RTC SDK 支持以下三种画布水印设置:

  • 文字水印:使用一段文字信息作为水印,支持设置字体和字号。
  • 动态时间戳水印:使用当前时间戳作为水印,显示格式为“2021-03-18 14:30:35"。
  • 静态图片水印:使用图片作为水印。

# 注意事项

/**
* 设置录制视频水印串
*/
public set watermarkTextMap(watermarkTextMap: HashMap<string, string> | undefined);

# 添加、修改或删除水印

  • 文字水印,如要求水印内容需具备客户经理工号、姓名、地理位置、经纬度;则需要终端开启录制时通过 watermarkTextMap (opens new window) 设置: {"userInfo": "工号+姓名"} 、{"location":"地理位置"}、{"JWD": "经纬度"}这样的键值对;具体体现和系统管理平台端配置如下 ,红框内就是文字水印,支持在业务管理平台调整位置和字体颜色等,而$@xxx@$最终会替换成键xxx对应的值,如$@userInfo@$,替换成“工号+姓名”,$@location@$,替换成“地理位置”,$@JWD@$,替换成“经纬度”。
  • 图片水印,在业务管理平台支持配置图片水印,目前知支持 png 格式图片,可以自由调整位置。
  • 时间戳水印,在业务管理平台支持配置时间戳水印,可以自由调整位置,字体颜色、大小等。

img

# 自定义布局

在系统管理平台端中的录制配置目录下,可以配置录制的一些参数,为了录制布局更加灵活,还提供了自定义布局,自定义布局可以配置每个录制流的位置和大小。

系统管理平台端默认使用的是智能分屏,如需切换自定义布局,在系统管理平台端修改如下:

img

img

配置每个成员的布局位置和大小,配置完成后点保存:

图中的0,1表示每个窗口对应的窗口位置,终端开启远程录制绑定视频流和窗口位置需要用到。

img

以上就是系统管理平台端的配置,实现自定义布局还需要在代码中绑定对应的窗口位置,参考代码如下:

let param:JRTCRecordRemoteParam = new JRTCRecordRemoteParam();
param.frameRate = 24;
param.iBitrate = 500;
// 设置录制合并模式为自定义布局模式
param.mergeMode = JRTCVideoMergeMode.CUSTOM_LAYOUT;
param.videoWidth = 640;
param.videoHeight = 360;
param.recordVideo = true;
param.layoutType = "default";//设置录制样式,对应业务管理平台上录制配置中的编号ID, 不传则用默认
// 开启远程录制
call.enableRemoteRecord(true, param);

# 更新远程录制自定义布局

如果需要在远程录制已经开启的情况下更改录制布局,使用场景:比如录制中有新成员加入,需要调整布局位置,可以使用如下接口,需要在远程录制进行中调用,详见: updateRemoteRecordLayout (opens new window)

/**
* 更新远程录制自定义布局
*
* @param layoutList 需要更新的布局列表
* @return 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
public abstract updateRemoteRecordLayout(layoutList: ArrayList<JRTCRecordRemoteLayout>): boolean;

示例代码:

let param:JRTCRecordRemoteParam = new JRTCRecordRemoteParam();
// ......远程录制参数设置
// 开启远程录制
call.enableRemoteRecord(true, param);
let layoutList:ArrayList<JRTCRecordRemoteLayout> = new ArrayList();
let layout:JRTCRecordRemoteLayout = new JRTCRecordRemoteLayout();
layout.setId("成员用户ID", true);
// 设置系统管理平台中对应的窗口位置
layout.position = 0;
layoutList.add(layout);
//......设置其他成员视频流或者屏幕共享流对应窗口位置
//更新录制自定义布局
let result:boolean = call.updateRemoteRecordLayout(layoutList);
// 关闭远程录制
call.enableRemoteRecord(false, new JRTCRecordRemoteParam());

# 更新远程录制水印信息

如果需要在远程录制已经开启的情况下更改水印信息,使用场景:比如录制中有成员离开,需要修改该成员原先位置水印标签(可能是用户名字),可以使用如下接口,需要在远程录制进行中调用,详见: updateRemoteRecordWatermark (opens new window)

/**
* 更新远程录制水印信息
*
* @param watermarkTextMap 水印信息
* @return 接口调用结果
* - true: 接口调用成功
* - false: 接口调用异常
*/
public abstract updateRemoteRecordWatermark(watermarkTextMap: HashMap<string, string>): boolean;

示例代码:

let param:JRTCRecordRemoteParam = new JRTCRecordRemoteParam();
let watermarkTextMap1:HashMap<string, string> = new HashMap();
watermarkTextMap1.set("w1", "张三");
watermarkTextMap1.set("w2", "李四");
param.watermarkTextMap = watermarkTextMap1;//初始水印
// ......远程录制参数设置
// 开启远程录制
call.enableRemoteRecord(true, param);
let watermarkTextMap2:HashMap<string, string> = new HashMap();
watermarkTextMap2.set("w1", "李四");//修改水印信息
watermarkTextMap2.set("w2", "王五");//修改水印信息
let result:boolean = call.updateRemoteRecordWatermark(watermarkTextMap2);//更改录制水印信息
// 关闭远程录制
call.enableRemoteRecord(false, new JRTCRecordRemoteParam());