如何实现:录制视频聊天的全过程? 【低调赠送:QQ高仿版GG 4.3 最新源码】

  前段时间作个项目,客户须要将视频对话的整个过程录制下来,这样,之后就能够随时观看。想来录制整个视频聊天的过程这样的功能应该是个比较常见的需求,好比,基于网络语音视频的1:1的英语口语辅导,若是能将辅导的整个过程录制下来生成一个标准的MP4文件,就是一份可贵的资料,便于之后复习和分享。我将1:1的视频对话录制的功能实现为了一个组件VideoChatRecorder,方便你们复用。而且,我在GG的最新版本4.3中使用了它,这样GG也有了视频聊天录制的功能。html

      (想要直接下载体验的朋友请点击:“下载中心” 算法

      若是你们已经作过相似录制单我的的摄像头和麦克风程序的话,那么,录制两人视频聊天就会遇到两个新的难点:缓存

(1)如何将两我的的视频图像整合成一个图像?网络

(2)如何将两我的的声音混成一路?ide

一.实现原理

1.视频合成

      经过.NET提供的GDI+技术,咱们能够将两张图片合成一张。在实现VideoChatRecorder组件时,我合成图片所采用的规则是这样的:布局

(1)将对方的视频做为录制的主体,而本身的视频则覆盖在对方视频的右下角。this

(2)对方视频的大小,就是其摄像头的采集分辨率,依据(1),咱们知道这也是录制生成的MP4文件播放时视频的Size。spa

(3)合成后本身视频图像的宽和高,设定为对方视频宽和高的 1/3。线程

      合成后的视频的示意图以下所示:code

     

2.音频合成

     咱们能够手动将本身的声音与对方的声音混音成一路,网上能够搜到不少混音算法(如直接相加法、平均法、归一化算法、衰减因子法等),可是,混音算法的好坏直接关系到混音最终的质量。

     还有一种更简单的方案,就是直接使用OMCS提供的AudioInOutMixer组件,它能够将麦克风采集的声音(也就是本身的声音)和扬声器播放的声音(也就是对方的声音)混音成一路,并经过 AudioMixed 事件暴露混音后的数据。

二.实现具体步骤

  解决了视频合成和音频合成两个关键难点后,咱们就能够将实现的整个流程串起来了。

(1)使用一个摄像头链接器实例链接到对方的摄像头,而后调用其GetCurrentImage方法,就能够获取对方的视频图像。

(2)使用另外一个摄像头链接器实例链接到本身的摄像头,而后调用其GetCurrentImage方法,就能够获取本身的视频图像。

(3)使用一个MFile提供的VideoFileMaker来将语音、视频录制成标准的MP4文件。

(4)使用一个AudioInOutMixer实例,来进行混音。预约其AudioMixed 事件,以获取混音后的语音数据,并将其提交给VideoFileMaker进行录制声音。

(5)使用一个后台线程,每隔100ms(即对应帧频为10fps)就调用前面两个链接器的GetCurrentImage方法,并将返回的两个图片进行合成变成一张,并将其提交给VideoFileMaker进行录制图像。

        这里的关键,是使用GDI+进行图像合成的过程,其代码比较简单,以下所示:

        Bitmap bmFriend = this.dynamicCameraConnector2Friend.GetCurrentImage();
        if (bmFriend != null)
        {
            Bitmap bmMyself = this.cameraConnector2Myself.GetCurrentImage();
            //合成图像
            if (bmMyself != null)
            {
                Graphics g = Graphics.FromImage(bmFriend);
                g.DrawImage(bmMyself ,this.myVideoRect);   
                g.Dispose();
            }

            //录制图像
            this.videoFileMaker.AddVideoFrame(bmFriend);
        }

   注:若是不想将本身的视频图像叠加在对方的图像之上,那么,上述的代码稍做修改便可。能够new一个新的Bitmap,而后在上面的不一样区域分别绘制对方的图像和本身的图像就能够了。固然,新的Bitmap的Size,以及对方和本身图像在新的Bitmap中的布局位置要设置正确。

(6)当中止录制时,就中止用于合成图像的后台线程,并关闭VideoFileMaker。

       注意:在某些配置比较差的机器上,可能生产的速度大于录制(也就是消费)的速度,这样,在关闭VideoFileMaker时,就会阻塞一段时间,直至全部的缓存中的全部视频帧都写入了录制文件中,才会返回。

       在有了上面的总体思路以后,再来看VideoChatRecorder的完整代码,就很容易理解了。

    /// <summary>
    /// 视频聊天录制器。将视频聊天的完整过程录制成标准的MP4文件。
    /// </summary>
    class VideoChatRecorder : IDisposable
    {
        private DynamicCameraConnector dynamicCameraConnector2Friend ; //链接到好友摄像头的链接器。
        private CameraConnector cameraConnector2Myself; //链接到本身摄像头的链接器。
        private IMultimediaManager multimediaManager;
        private VideoFileMaker videoFileMaker;
        private Size videoSize;
        private Rectangle myVideoRect;
        private volatile bool isRecording = false;
        private AudioInOutMixer audioInOutMixer;

        public VideoChatRecorder(IMultimediaManager mgr ,DynamicCameraConnector friend, CameraConnector myself)
        {
            this.multimediaManager = mgr;
            this.dynamicCameraConnector2Friend = friend;
            this.cameraConnector2Myself = myself;
            this.dynamicCameraConnector2Friend.Disconnected += new ESBasic.CbGeneric<ConnectorDisconnectedType>(dynamicCameraConnector2Friend_Disconnected);

            //混音器。将本身和对方的声音混成一路。
            this.audioInOutMixer = new AudioInOutMixer();
            this.audioInOutMixer.AudioMixed += new CbGeneric<byte[]>(audioInOutMixer_AudioMixed);
        }

        //获得混音数据,将其录制到文件。
        void audioInOutMixer_AudioMixed(byte[] data)
        {
            if (this.isRecording)
            {
                this.videoFileMaker.AddAudioFrame(data);
            }
        }

        //摄像头链接器断开时,就中止录制。
        void dynamicCameraConnector2Friend_Disconnected(ConnectorDisconnectedType obj)
        {
            if (!this.isRecording)
            {
                return;
            }
           
            this.Dispose();
        }

        //初始化录像设备,并开始录制。
        public void Initialize(string filePath)
        {
            if (!this.dynamicCameraConnector2Friend.Connected)
            {
                throw new Exception("链接器还没有链接到对方的摄像头!");
            }
            this.videoSize = this.dynamicCameraConnector2Friend.VideoSize;
            Size myVideoSize = new Size(this.videoSize.Width / 3, this.videoSize.Height / 3);
            this.myVideoRect = new Rectangle(this.videoSize.Width - myVideoSize.Width, this.videoSize.Height - myVideoSize.Height, myVideoSize.Width, myVideoSize.Height);

            this.videoFileMaker = new VideoFileMaker();
            this.videoFileMaker.AutoDisposeVideoFrame = true;
            this.videoFileMaker.Initialize(filePath, VideoCodecType.H264, this.videoSize.Width, this.videoSize.Height, 10, AudioCodecType.AAC, 16000, 1, true);

            this.audioInOutMixer.Initialize(this.multimediaManager);
            this.isRecording = true;

            CbGeneric cb = new CbGeneric(this.RecordThread);
            cb.BeginInvoke(null, null);
        }

        //录制线程。每隔100ms(对应VideoFileMaker的帧频为10fps)就合成一张图片,并录制它。
        private void RecordThread()
        {
            while (this.isRecording)
            {
                Bitmap bmFriend = this.dynamicCameraConnector2Friend.GetCurrentImage();
                if (bmFriend != null)
                {
                    Bitmap bmMyself = this.cameraConnector2Myself.GetCurrentImage();
                    //合成图像
                    if (bmMyself != null)
                    {
                        Graphics g = Graphics.FromImage(bmFriend);
                        g.DrawImage(bmMyself ,this.myVideoRect);   
                        g.Dispose();
                    }

                    //录制图像
                    this.videoFileMaker.AddVideoFrame(bmFriend);
                }

                System.Threading.Thread.Sleep(100);
            }

        }

        /// <summary>
        /// 中止录制,并释放录制设备。
        /// </summary>
        public void Dispose()
        {            
            this.dynamicCameraConnector2Friend.Disconnected -= new ESBasic.CbGeneric<ConnectorDisconnectedType>(dynamicCameraConnector2Friend_Disconnected);
            this.audioInOutMixer.AudioMixed -= new CbGeneric<byte[]>(audioInOutMixer_AudioMixed);
            this.audioInOutMixer.Dispose();

            if (!this.isRecording)
            {
                return;
            }

            this.isRecording = false;
            this.videoFileMaker.Close(true);
        }
    }  

  

三.GG V4.3 源码 

   下载最新版本,请转到这里。 

   在GG的最新版本中使用了上述的VideoChatRecorder类进行视频聊天录制以生成的MP4文件(默认是在运行目录下名称为 VideoChat.mp4 的文件),用QQ影音播放器进行播放这个文件,其效果以下所示:

       

 

________________________________________________________________________ 

欢迎和我探讨关于 GG 和 GGMeeting 的一切,个人QQ:2027224508,多多交流!  

你们有什么问题和建议,能够留言,也能够发送email到我邮箱:2027224508@qq.com。  

若是你以为还不错,请粉我,顺便再顶一下啊

相关文章
相关标签/搜索