在我之前的一篇博文《實現語音視頻錄製(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,它就實現了這樣的目的。