在我之前的一篇博文《实现语音视频录制(demo源码)》中,详细介绍了在网络视频聊天系统中的客户端如何实现语音视频的录制,而近段时间了,有几个朋友问起,若是想在服务端实现录制功能,该怎么作了?其中有个朋友的需求是这样的:他的系统是一个在线培训系统,须要在服务端将指定老师的讲课(包括语音和视频)录制下来,并保存为.mp4文件,以便随时能够查阅这些文件。 html
本文咱们就作一个demo实现相似的功能,演示如何在服务端录制某个指定在线用户的语音视频,并提供三种录制模式:录制语音视频、仅录制语音、仅录制视频。服务器
一.实现原理 网络
要实现这个demo,需涉及到如下几个技术:ide
(1)在服务端采集指定用户的语音、视频数据。测试
(2)在服务端将图像使用H264编码,语音数据使用AAC编码。ui
(3)将编码后的数据按MP4格式的要求,保存为MP4文件。this
同实现语音视频录制(demo源码)同样,咱们仍然基于OMCS和MFile来实现上述功能,下面是对应的原理。 编码
(1)在OMCS的结构中,客户端之间能够相互获取到对方的摄像头和麦克风的数据,因此,服务端能够做为一个虚拟的客户端用户(好比ID为“_Server”),链接到同一个进程中的OMCS多媒体服务器。spa
(2)在服务端动态建立DynamicCameraConnector组件,链接到指定用户的摄像头。code
(3)在服务端动态建立两个MicrophoneConnector组件,接到指定用户的麦克风。
(4)调用DynamicCameraConnector的GetCurrentImage方法,便可得到所链接的摄像头采集的视频帧。
(5)预约MicrophoneConnector的AudioDataReceived事件,便可得到所链接的麦克风采集的音频数据。
(6)使用MFile将上述结果进行编码并写入mp4文件。
二.实现代码
public partial class RecordForm : Form { private MultimediaServer multimediaServer; private OMCS.Passive.Audio.MicrophoneConnector microphoneConnector; private OMCS.Passive.Video.DynamicCameraConnector dynamicCameraConnector; private IMultimediaManager multimediaManager; private BaseMaker maker; private System.Threading.Timer videoTimer; private RecordMode recordMode = RecordMode.AudioAndVideo; public RecordForm(MultimediaServer server) { InitializeComponent(); this.comboBox_mode.SelectedIndex = 0; this.multimediaServer = server; this.label_port.Text = this.multimediaServer.Port.ToString(); //将服务端虚拟为一个OMCS客户端,并链接上OMCS服务器。 this.multimediaManager = MultimediaManagerFactory.GetSingleton(); this.multimediaManager.Initialize("_server", "", "127.0.0.1", this.multimediaServer.Port);//服务端以虚拟用户登陆 } //在线用户列表 private void comboBox1_DropDown(object sender, EventArgs e) { List<string> list = this.multimediaServer.GetOnlineUserList(); list.Remove("_server"); //将虚拟用户排除在外 this.comboBox1.DataSource = list; } //开始录制视频 private void button1_Click(object sender, EventArgs e) { if (this.comboBox1.SelectedItem == null) { MessageBox.Show("没有选中目标用户!"); return; } string destUserID = this.comboBox1.SelectedItem.ToString(); this.recordMode = (RecordMode)this.comboBox_mode.SelectedIndex; //摄像头链接器 if (this.recordMode != RecordMode.JustAudio) { this.dynamicCameraConnector = new Passive.Video.DynamicCameraConnector(); this.dynamicCameraConnector.MaxIdleSpan4BlackScreen = 0; this.dynamicCameraConnector.ConnectEnded += new ESBasic.CbGeneric<ConnectResult>(cameraConnector1_ConnectEnded); this.dynamicCameraConnector.BeginConnect(destUserID); } //麦克风链接器 if (this.recordMode != RecordMode.JustVideo) { this.microphoneConnector = new Passive.Audio.MicrophoneConnector(); this.microphoneConnector.Mute = true; //在服务器上不播放出正在录制的声音 this.microphoneConnector.ConnectEnded += new ESBasic.CbGeneric<ConnectResult>(microphoneConnector1_ConnectEnded); this.microphoneConnector.AudioDataReceived += new CbGeneric<List<byte[]>>(microphoneConnector_AudioDataReceived); this.microphoneConnector.BeginConnect(destUserID); } this.label1.Text = string.Format("正在链接{0}的设备......" ,destUserID); this.Cursor = Cursors.WaitCursor; this.button1.Enabled = false; this.comboBox1.Enabled = false; this.comboBox_mode.Enabled = false; } //录制接收到的语音数据 void microphoneConnector_AudioDataReceived(List<byte[]> dataList) { if (this.maker != null) { foreach (byte[] audio in dataList) { if (this.recordMode == RecordMode.AudioAndVideo) { ((VideoFileMaker)this.maker).AddAudioFrame(audio); } else if (this.recordMode == RecordMode.JustAudio) { ((AudioFileMaker)this.maker).AddAudioFrame(audio); } else { } } } } void microphoneConnector1_ConnectEnded(ConnectResult obj) { this.ConnectComplete(); } void cameraConnector1_ConnectEnded(ConnectResult obj) { this.ConnectComplete(); } private int connectCompleteCount = 0; private void ConnectComplete() { ++this.connectCompleteCount; if (this.recordMode == RecordMode.AudioAndVideo) { if (this.connectCompleteCount == 2)//当语音、视频 都链接完成后,才正式启动录制。 { System.Threading.Thread.Sleep(500); this.Ready(); } } else { System.Threading.Thread.Sleep(500); this.Ready(); } } //初始化用于录制的FileMaker private void Ready() { if (this.InvokeRequired) { this.BeginInvoke(new CbGeneric(this.Ready)); } else { try { this.Cursor = Cursors.Default; if (this.recordMode == RecordMode.AudioAndVideo) { this.maker = new VideoFileMaker(); ((VideoFileMaker)this.maker).Initialize(this.dynamicCameraConnector.OwnerID + ".mp4", VideoCodecType.H264, this.dynamicCameraConnector.VideoSize.Width, this.dynamicCameraConnector.VideoSize.Height, 10, AudioCodecType.AAC, 16000, 1, true); this.videoTimer = new System.Threading.Timer(new System.Threading.TimerCallback(this.Callback), null, 0, 100); } else if (this.recordMode == RecordMode.JustAudio) { this.maker = new AudioFileMaker(); ((AudioFileMaker)this.maker).Initialize(this.microphoneConnector.OwnerID + ".mp3", AudioCodecType.MP3, 16000, 1); } else { this.maker = new SilenceVideoFileMaker(); ((SilenceVideoFileMaker)this.maker).Initialize(this.dynamicCameraConnector.OwnerID + ".mp4", VideoCodecType.H264, this.dynamicCameraConnector.VideoSize.Width, this.dynamicCameraConnector.VideoSize.Height, 10); this.videoTimer = new System.Threading.Timer(new System.Threading.TimerCallback(this.Callback), null, 0, 100); } this.label1.Text = "正在录制......"; this.label1.Visible = true; this.button1.Enabled = false; this.button2.Enabled = true; } catch (Exception ee) { MessageBox.Show(ee.Message); } } } private int callBackCount = -1; //定时获取视频帧,并录制 private void Callback(object state) { if (this.maker != null) { Bitmap bm = this.dynamicCameraConnector.GetCurrentImage(); if (bm != null) { ++this.callBackCount; if (this.recordMode == RecordMode.AudioAndVideo) { ((VideoFileMaker)this.maker).AddVideoFrame(bm); } else if (this.recordMode == RecordMode.JustVideo) { ((SilenceVideoFileMaker)this.maker).AddVideoFrame(bm); } else { } } else { } } } //中止录制 private void button2_Click(object sender, EventArgs e) { try { this.callBackCount = -1; if (this.videoTimer != null) { this.videoTimer.Dispose(); this.videoTimer = null; } this.connectCompleteCount = 0; if (this.recordMode != RecordMode.JustAudio) { this.dynamicCameraConnector.Disconnect(); this.dynamicCameraConnector = null; } if (this.recordMode != RecordMode.JustVideo) { this.microphoneConnector.Disconnect(); this.microphoneConnector = null; } this.button1.Enabled = true; this.button2.Enabled = false; this.label1.Visible = false; this.comboBox1.Enabled = true; this.comboBox_mode.Enabled = true; this.maker.Close(true); this.maker = null; MessageBox.Show("生成视频文件成功!"); } catch (Exception ee) { MessageBox.Show("生成视频文件失败!"+ ee.Message); } } }
若是熟悉OMCS和MFile的使用,理解上面的代码是很是容易的,并且本文这个Demo就是在语音视频入门Demo的基础上改写而成的,只是有几点是须要注意:
(1)因为在服务端录制时,不须要显示被录制用户的视频,因此不用设置DynamicCameraConnector的Viewer(即不用调用其SetViewer方法来设置绘制视频的面板)。
(2)一样,在服务端录制时,不须要播放被录制用户的语音,因此,将MicrophoneConnector的Mute属性设置为true便可。
(3)若是须要录制视频,则经过一个定时器(videoTimer)每隔100毫秒(即10fps)从DynamicCameraConnector采集一帧图片,并写入录制文件。
(4)若是录制的仅仅是图像视频(不包括音频),采用的视频编码仍然为H264,但生成的录制文件也是.mp4文件,而非.h264文件,不然,生成的视频文件将没法正常播放。
三.Demo下载
服务端运行起来的截图以下所示:
测试时,可按以下步骤:
(1)启动demo的服务端。
(2)修改客户端配置文件中的服务器IP,而后,用不一样的账号在不一样的机器上登陆多个demo的客户端。
(3)在服务端界面上,选择一个在线的用户,点击“开始录制”按钮,便可进行录制。录制结束后,将在服务端的运行目录下,生成以用户ID为名称的mp3/mp4文件。
固然,在运行该demo时,仍然能够像语音视频入门Demo同样,两个客户端之间相互视频对话,并且同时,在服务端录制其中一个客户端的视频。
如你所想,咱们能够将这个demo稍微作些改进,就能够支持在服务端同时录制多个用户的语音视频。
然而,就像本文开头所说的,本Demo所展现的功能很是适合在相似网络培训的系统中,用于录制老师的语音/视频。但若是是在视频聊天系统中,须要将聊天双方的语音视频录制到一个文件中,那么,就要复杂一些了,那须要涉及到图像拼接技术和混音技术了。我会在下篇文章中介绍另外一个Demo,它就实现了这样的目的。