OMCS Demo -- 远程录制或在服务器端录制语音、视频、屏幕

   远程录制的意思是,客户端A希望录制客户端B的语音视频屏幕等数据;而在服务器端录制,实际上原理是一样的,我们只要在服务端集成一个虚拟的客户端用户就OK了。

        本文我们以在服务器端录制为例,至于远程录制,大家只需要将本文的demo稍作修改就行。比如这样的需求:有个系统是一个在线培训系统,需要在服务端将指定老师的讲课(包括语音和视频)录制下来,并保存为.mp4文件,以便随时可以查阅这些文件。

  本文我们就做一个demo实现类似的功能,演示如何在服务端录制某个指定在线用户的语音视频,并提供三种录制模式:录制语音视频、仅录制语音、仅录制视频。

一.实现原理   

    我们基于OMCSMFile来实现上述功能,下面是对应的原理。

(1)在OMCS的结构中,客户端之间可以相互获取到对方的摄像头和麦克风的数据,所以,服务端可以作为一个虚拟的客户端用户(比如ID为“_Server”),连接到同一个进程中的OMCS多媒体服务器。

(2)在服务端动态创建DynamicCameraConnector组件,连接到指定用户的摄像头。

(3)在服务端动态创建两个MicrophoneConnector组件,接到指定用户的麦克风。

(4)调用DynamicCameraConnector的GetCurrentImage方法,即可获得所连接的摄像头采集的视频帧。

(5)预定MicrophoneConnector的AudioDataReceived事件,即可获得所连接的麦克风采集的音频数据。

(6)使用MFile将上述结果进行编码并写入mp4文件。

注:如果需要录制用户的屏幕,则需要使用DynamicDesktopConnector组件,其也有GetCurrentImage方法,与DynamicCameraConnector使用方法几乎一样。 

二.实现代码 

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);
            }
        }
    }
View Code

  如果熟悉OMCS和MFile的使用,理解上面的代码是非常容易的,而且本文这个Demo就是在语音视频入门Demo的基础上改写而成的,只是有几点是需要注意:

(1)由于在服务端录制时,不需要显示被录制用户的视频,所以不用设置DynamicCameraConnector的Viewer(即不用调用其SetViewer方法来设置绘制视频的面板)。

(2)同样,在服务端录制时,不需要播放被录制用户的语音,所以,将MicrophoneConnector的Mute属性设置为true即可。

(3)如果需要录制视频,则通过一个定时器(videoTimer)每隔100毫秒(即10fps)从DynamicCameraConnector采集一帧图片,并写入录制文件。

(4)如果是录制桌面,想同时录制鼠标位置,只要将DynamicDesktopConnector的WatchingOnly及ShowMouseCursor属性均设置为true即可。

(5)如果录制的仅仅是图像视频(不包括音频),采用的视频编码仍然为H264,但生成的录制文件也是.mp4文件,而非.h264文件,否则,生成的视频文件将无法正常播放。 

三.Demo下载

      RecordOnServerDemo.rar   

  服务端运行起来的截图如下所示:

  

   测试时,可按如下步骤:

(1)启动demo的服务端。 

(2)修改客户端配置文件中的服务器IP,然后,用不同的帐号在不同的机器上登录多个demo的客户端。 

(3)在服务端界面上,选择一个在线的用户,点击“开始录制”按钮,即可进行录制。录制结束后,将在服务端的运行目录下,生成以用户ID为名称的mp3/mp4文件。 

  当然,在运行该demo时,仍然可以像语音视频入门Demo一样,两个客户端之间相互视频对话,而且同时,在服务端录制其中一个客户端的视频。

  如你所想,我们可以将这个demo稍微做些改进,就可以支持在服务端同时录制多个用户的语音视频。  

四.扩展1:录制聊天双方的对话过程

      我们切换到在二人对话的视频聊天系统中,聊天的一方希望将自己和对方对话的整个过程录制下来,包括语音和视频。 这就涉及到将自己的视频图像与对方的视频图像合成一个整体图像,自己的声音和对方的声音合成一路声音。

(1)图像合成

      通过两个DynamicCameraConnector可以分别获得自己和对方的图像,然后通过GDI+技术即可将两张图片拼接成一张。

(2)声音合成

      声音合成使用的是混音技术。OMCS已经在内部完成了这一功能,我们可以直接拿来使用。

       OMCS提供的OMCS.Passive.Audio.AudioInOutMixerAudioInOutMixer混音器用于将本地话筒设备的输入数据以及本地声音播放的输出数据进行混音。所以,只需要调用AudioInOutMixer的Initialize方法,并预定其AudioMixed事件即可。AudioMixed事件提供的就是我们需要的最终的混音结果。请记住,当不再使用AudioInOutMixer时,请调用其Dispose方法释放混音器。 

      当然,如果希望自己实现混音,也是可以的。我们可以预定IMultimediaManager的AudioCaptured事件和AudioPlayed事件AudioCaptured事件提供的数据就是自己麦克风采集到的数据,而AudioPlayed事件提供的数据就是播放的声音,即对方说话的声音。 

      这里有一个Demo供大家参考:如何实现:录制视频聊天的全过程? 

五.扩展2:在服务端录制群组的语音聊天(群聊)

       如果需要在服务端将群聊的过程录制下来,其原理与上述是一样的。 在服务端使用一个虚拟的客户端用户登录OMCS,然后使用多个MicrophoneConnector连接到目标群组的每个成员,这样IMultimediaManager的AudioCaptured事件将暴露所有这些成员的声音进行混音后的数据。

       一般而言,如果同时讲话的人数大于3个时,混音后的结果基本就很嘈杂了,所以,建议在设计群聊时,通过逻辑控制一下发言的权限,最多允许有三个人在同时说话, 关于类似的权限设计,可以参考 OMCS FAQ -- OMCS 常见问题解答 一文中的“麦克风相关”章节对“4.在同一个房间中,多个人语音/视频聊天时,OMCS如何对发言控制权进行支持?”的解答。

 

 

下载免费版本的OMCS以及 demo源码

阅读 更多OMCS开发手册系列文章

Q Q:168757008

官网:www.oraycn.com

导航

首页

官方网站

立即咨询 

站内搜索

ESFramework 通信框架

详细说明

SDK下载

ESFramework FAQ

版本变更记录

OMCS 语音视频框架

详细说明

SDK下载

OMCS FAQ

版本变更记录

OrayTalk 企业即时通讯系统

详细说明

客户端下载

傲瑞实用组件

SDK下载

NPush 消息推送组件

StriveEngine 轻量级的通信引擎

MFile 语音视频录制组件

MCapture 语音视频采集组件

MPlayer 语音视频播放组件

OAUS 自动升级系统

傲瑞组件 FAQ

授权

授权流程

产品授权说明

产品选购指南

授权SDK使用说明

其它

SDK使用技巧

联系我们

电话:027-87638960

Q Q:168757008

邮件:master@oraycn.com