随着远程办公与异地协作越来越频繁,视频会议系统的使用也是越来越普遍。同时,用户对视频会议系统的功能也提出了更高的要求,比如,其中之一就是希望可以将整个视频会议的过程录制下来,以备之后可以查阅观看。
我们可以选择在视频会议系统的服务端或客户端来录制整个视频会议过程,在服务端录制与在客户端录制各有优劣。比如,在服务端录制对服务器配置要求更高,因为同时可能有很多个会议同时录制;而在客户端录制,录制的文件存放在客户端本地电脑上,只能在本地播放,如果其他人需要观看,则需要在录制完成后将该文件再上传到服务器。
无论是在服务端录制,还是在客户端录制,其技术原理是一样的。这里我就 傲瑞视频会议(OrayMeeting)在服务端(Windows、Linux、信创国产OS、银河麒麟、统信UOS)录制会议过程的技术原理和实现介绍给大家。
如下图就是某会议录制文件使用QQ影音播放的效果:
接下来,我们将具体介绍傲瑞视频会议是怎么实现会议录制的。
一. 录制流程说明
(1)会议信息中心包含了一个字段,用于指示该会议是否开启录制。
(2)当第一个人进入会议时,启动录制。
(3)当到会议结束时间:若会议室没有人,这立即结束录制;若会议室还有人,等最后一个人退出时,结束录制。
(3)任何时候,主持人结束会议,则将同时结束录制。
(4)中途若会议室没有人时,则暂停录制;当再有人进入会议时,则继续录制。
二. 傲瑞会议录制的画面布局
(1)录制画面最上面有一行高为30px标题栏,将实时显示如下信息:会议名称、系统时间、参会人数。
(2)标题栏下面的剩下区域为内容区,用来渲染用户视频/头像,或者是渲染用户分享屏幕的桌面图像。
(3)当参会人数不超过4个人时,采用2x2四宫格;当参会人数多于4个人时,采用3x3九宫格。
(4)录制时最多渲染9个人的视频或头像(3x3),开启了视频的用户排在最前面,其次是开启了麦克风的用户。
(5)如果用户开启了视频,录制时就渲染其视频图像,否则,就渲染该用户的默认头像。
(6)如果参会人员中有人开启了屏幕分享,这录制画面的内容区将不再渲染用户视频/头像,而是改为渲染被分享屏幕的桌面图像。
三. 程序实现技术要点
(1)获取参会人员的PCM声音数据和RGB图像数据。
使用 OMCS 提供的 MicrophoneConnector 和 DynamicCameraConnector 就可以获取每个参会人员的声音数据以及图像数据。
(2)拼接并渲染要录制的视频图像帧
在Windows可使用GDI+技术、在Linux上则可使用Skia技术来完成录制图像帧的拼接渲染。
if (desktopShare) //如果有人分享桌面,这主体内容区就是桌面图像
{
//获取屏幕分享的最新桌面图像帧
Image image = this.connectorManager.GetCurrentImage(null);
canvas.SetDesktopImage(image);
}
else
{
for (int i = 0; i < recordMembers.Count; i++)
{
string userID = recordMembers[i];
Image image = null;
if (!meeting.CamClosedMemberList.Contains(userID))
{
//获取参会成员的最新视频图像帧
image = this.connectorManager.GetCurrentImage(userID);
}
DrawInfo renderModel = this.drawInfoManagers.Get(userID);
renderModel.SetCameraImage(image);
renderModel.SetMicState(!meeting.MuteMemberList.Contains(userID));
}
//绘制主体内容区
canvas.SetCameraImage(this.drawInfoManagers.GetAll());
}
byte[] bytes = canvas.RenderImage(); //准备好要录制的图像
if (bytes != null)
{
this.videoFileMaker?.AddVideoFrame(bytes); //提交给录制器
}
(3)混音
使用 OMCS 提供的 MicrophoneConnectorMixer 可以将参会人员的声音混成一路。
IConnectorManager connectorManager = new OMCSConnectorManager(globalCache);
connectorManager.AudioMixed += ConnectorManager_AudioMixed;
private void ConnectorManager_AudioMixed(string userID, byte[] data)
{
if (recording && data != null)
{
this.videoFileMaker?.AddAudioFrame(data); //将混音数据提交给录制器
}
}
(4)定时器
使用定时器来调度声音数据和图像数据。比如:每隔10毫秒就从各个MicrophoneConnector获取一帧语音数据,并将它们混音。每隔40毫秒就从各个 DynamicCameraConnector 获取相应的图像数据,并将它们拼接,并按照前面描述的画面布局进行渲染。
//开启录制线程,定时调用
internal void StartRecord()
{
recording = true;
Task.Factory.StartNew(() =>
{
RecordThread();
});
}
private void RecordThread()
{
int sleepSeconds = 1000 / frameRate;
while (true)
{
System.Threading.Thread.Sleep(sleepSeconds);
//record ...
}
}
(5)将图像帧和声音帧编码并生成MP4文件。
将混音好的声音数据、拼接好的渲染图像提交给 MFile,MFile会将它们编码并写入到MP4文件中。
会议结束时,将结束录制,并释放相关的资源。
internal void FinishRecord()
{
recording = false;
//释放麦克风、摄像头、桌面设备连接器
connectorManager.DisconnectAllConnect();
//释放录制器
videoFileMaker?.Close(true);
}
四. 结语
在将录制会议的流程、画面布局、技术要点做了简单介绍后,相信大家对视频会议服务端在程序上是如何实现会议录制功能的,已经初步了解了。
本文只是粗略地介绍了视频会议录制的原理与技术实现,如果你有更具体的实现细节需要了解的,欢迎与我讨论。