在服务端录制语音视频
在我以前的一篇博文《实现语音视频录制(demo源码)》中,详细介绍了在网络视频聊天系统中的客户端如何实现语音视频的录制,而近段时间了,有几个朋友问起,如果想在服务端实现录制功能,该怎么做了?其中有个朋友的需求是这样的:他的系统是一个在线培训系统,需要在服务端将指定老师的讲课(包括语音和视频)录制下来,并保存为.mp4文件,以便随时可以查阅这些文件。
本文我们就做一个demo实现类似的功能,演示如何在服务端录制某个指定在线用户的语音视频,并提供三种录制模式:录制语音视频、仅录制语音、仅录制视频。
一.实现原理
要实现这个demo,需涉及到以下几个技术:
(1)在服务端采集指定用户的语音、视频数据。
(2)在服务端将图像使用H264编码,语音数据使用AAC编码。
(3)将编码后的数据按MP4格式的要求,保存为MP4文件。
同实现语音视频录制(demo源码)一样,我们仍然基于OMCS和MFile来实现上述功能,下面是对应的原理。
(1)在OMCS的结构中,客户端之间可以相互获取到对方的摄像头和麦克风的数据,所以,服务端可以作为一个虚拟的客户端用户(比如ID为“_Server”),连接到同一个进程中的OMCS多媒体服务器。
(2)在服务端动态创建DynamicCameraConnector组件,连接到指定用户的摄像头。
(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();}}//初始化用于录制的FileMakerprivate 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下载
RecordOnServerDemo.rar
服务端运行起来的截图如下所示:
测试时,可按如下步骤:
(1)启动demo的服务端。
(2)修改客户端配置文件中的服务器IP,然后,用不同的帐号在不同的机器上登录多个demo的客户端。
(3)在服务端界面上,选择一个在线的用户,点击“开始录制”按钮,即可进行录制。录制结束后,将在服务端的运行目录下,生成以用户ID为名称的mp3/mp4文件。
当然,在运行该demo时,仍然可以像语音视频入门Demo一样,两个客户端之间相互视频对话,而且同时,在服务端录制其中一个客户端的视频。
如你所想,我们可以将这个demo稍微做些改进,就可以支持在服务端同时录制多个用户的语音视频。
然而,就像本文开头所说的,本Demo所展示的功能非常适合在类似网络培训的系统中,用于录制老师的语音/视频。但如果是在视频聊天系统中,需要将聊天双方的语音视频录制到一个文件中,那么,就要复杂一些了,那需要涉及到图像拼接技术和混音技术了。我会在下篇文章中介绍另一个Demo,它就实现了这样的目的。
在服务端录制语音视频相关推荐
- 即构服务端录制SDK上线,音视频数据实现本地存储录制回放
日常生活中,我们可能会有这些需求: 看到有趣的直播内容,会想能不能保存下来再次回看: 报名了线上的课程,家长想了解孩子的学习状态,希望能看到在线学习的过程: 金融行业,管理人员需要定期调取视频客服的服 ...
- 声网 Agora 本地服务端录制 SDK v3.0 版正式上线
声网 Agora 本地服务端录制 SDK v3.0 版已经上线,并更新至声网官网「SDK和应用下载」. 新增特性 1. 支持录制以 H.265/HEVC 标准编码的视频流 v3.0 起支持录制以 H. ...
- 丝路传说架设服务器维护,《丝路传说》一键整合服务端+GM工具+视频架设教程...
双击[一键整合包.exe]自动解压到D盘 解压完后自动生成相关快捷方式 解压后 弹出以下安装 [如有安装选择,请[默认]或[全部]安装] D:\SiLuChuanShuo\Tools\Microsof ...
- esp32cam 服务端远程视频方案
esp32cam 服务端远程视频方案 说明 本方案为esp32cam 服务端 浏览器 三端联合使用.将服务端部署在公网即可远程使用,没有远程需求,可以直接在局域网使用.代码无需修改. 本文取缔了esp ...
- OMCS ——卓尔不群的网络语音视频聊天框架(跨平台)
OMCS 网络语音视频框架是集成了语音通话.视频通话.远程桌面观看与协助.电子白板编辑与观看等多种媒体于一身的跨平台(.NET.Android.iOS )网络多媒体框架, 实现了多媒体设备[麦克风.摄 ...
- Dart 全栈之服务端
Dart 的服务端开发 本文视频 本文已录制视频上传B站,请配合视频食用 <Dart 全栈之服务端> 本文是博主<Flutter全栈式开发>系列课程的拓展.我们在Flutter ...
- NOD32企业版2.5的安装和配置服务端教学视频
NOD32企业版2.5的安装和配置服务端教学视频(提供下载)-独家原创 作者:细水 日期:2007-02-04 字体大小: 小 中 大 在作动画之前,我们需要了解下NOd32的功能和特性,即使你非常喜 ...
- teamspeak搭建_搭建一个自己的语音通讯服务器——teamspeak服务端搭建及配置心得...
首先搬上teamspeak的官网:https://www.teamspeak.com/en/ 先来介绍一下teamspeak吧,简单的来说,他是一款老牌的开源VoIP工具软件,可以进行语音通话,在线聊 ...
- 基于webrtc的视频聊天室(五)之服务端设计
组件的描述已在前几个博文中给出.下面针对具体的流程给出详细描述. 如何进行sdp协商 建立任何聊天都需要保证服务端有相应的 WebRtc 对象,当然,客户端也需要建立对应的 WebRtc 对象,它们之 ...
- Silverlight+WCF 实战-网络象棋最终篇之对战视频-下篇[客户端发送与服务端中转](六)...
本篇继上一篇:Silverlight+WCF 实战-网络象棋最终篇之对战视频-上篇[客户端开启视频/注册编号/接收视频](五) 一:对战视频 简单原理 略,内容见上篇. 二:对战视频 步骤解析: 略 ...
最新文章
- 多视图立体匹配论文分享:P-MVSNet
- 直播 | 清华大学王晨阳:轻量级Top-K推荐框架及相关论文介绍
- [洛谷P4012] [网络流24题] 深海机器人问题
- linux下面升级 Python版本并修改yum属性信息
- Linux打开txt文件乱码的解决方法
- angularjs增删改查数据_MongoDB数据读写操作(增删改查)总结
- Error:Expected linebreaks to be ‘LF‘ but found ‘CRLF‘ linebreak-style
- 2012年8月20日 我单身了!
- PAT甲级1004 (DFS,树的父子节点)
- SpringBoot 拦截器和过滤器区别及应用
- 车路协同科研教学与实训先导平台 ——一种面向新一代智能交通人才培养的综合实验平台及系统
- 远程访问 Sql Server Express
- 如何在手机或平板上编写代码?
- 轩小陌的Python笔记-day03 Python基础
- PHP笔记03-数组1
- 配置Cisco VWIC T1/E1
- SELinux策略语言--类型强制(编写TE规则)
- python 的循环嵌套
- 怎样查找Xilinx的资料
- (跟我一起来学区块链(1.3))之区块链的起源与发展
热门文章
- 微信小程序-云开发3云函数、云存储
- Torch中的benchmarkdeterministic是什么含义?
- cpu_relax()函数的意义
- 随手写的一个小程序,将gz压缩文件读取到内存中 并用base64编码 及相关解码
- 阿里云EMAS移动测试|快速掌握移动端兼容性测试技巧
- 小生意同样能赚大钱,卖豆芽都能一年存20多万?你怎么看?
- 面试5173的奇葩经历——老板与员工的博弈论
- 微信小程序文件预览(doc、ppt、pdf)
- php汉字转换拼音的类 做了修改用mb_convert_encoding代替iconv实现编码转换
- 怎样拆卸惠普微型计算机,HP Compaq 8200 Elite USDT微机拆机给风扇加油