C#录制视频聊天

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

如果大家已经做过类似录制单个人的摄像头和麦克风程序的话,那么,录制两人视频聊天就会遇到两个新的难点:

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

(2)如何将两个人的声音混成一路?

一.实现原理

1.视频合成

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

(1)将对方的视频作为录制的主体,而自己的视频则覆盖在对方视频的右下角。

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

(3)合成后自己视频图像的宽和高,设定为对方视频宽和高的 1/3。

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

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是可在广域网部署运行的QQ高仿版,2013.8.7发布V1.0版本,至今最新是4.3版本,关于GG更详细的介绍,可以查看 可在广域网部署运行的QQ高仿版 -- GG2013概要

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

   GG 4.3 下载:GG-V4.3.rar

  

________________________________________________________________________

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

大家有什么问题和建议,可以留言,也可以发送email到我邮箱:[email protected]。

如果你觉得还不错,请粉我,顺便再顶一下啊

时间: 2024-10-19 02:04:55

C#录制视频聊天的相关文章

iOS环信视频聊天

这是我自己利用环信写的一个单纯的视频聊天功能demo 首先需要去环信官网申请一个appkey,并将sdk集成到项目中 然后加入类库 APPDelegate.m // // AppDelegate.m // VideoChat // // Created by 小灰灰的pro on 16/8/31. // Copyright © 2016年 小灰灰. All rights reserved. // #import "AppDelegate.h" #import "EMSDK.h

高清视频会议 视频聊天室源码下载

高清视频会议.视频聊天室源码简介: "SDK即时通讯平台"是一套跨平台的即时通讯解决方案,基于先进的H.264视频编码标准.AAC音频编码标准与P2P技术,支持高清视频,整合了佰锐在音视频编码.多媒体通讯领域领先的开发技术和丰富的产品经验而设计的高质量.宽适应性.分布式.模块化的网络音视频互动平台 成熟产品可提供全套系统示例源代码(包服务端,客户端)下载地址http://download.csdn.net/detail/little_rui/7969285,同时有完善的开发文档指南,且

Fms3和Flex打造在线多人视频会议和视频聊天(附原代码)

Flex,Fms3系列文章导航 Flex,Fms3相关文章索引 本篇是视频聊天,会议开发实例系列文章的第3篇,该系列所有文章链接如下: http://www.cnblogs.com/aierong/archive/2008/12/30/Flex.html#sp 1.工作原理NetStream.publish方法的应用publish () 方法:将音频流.视频流和文本消息流从客户端发送到 Flash Media Server,并可选择在传输期间录制该流. 此方法仅供指定的流的发布者使用.第1个参数

「iOS开发」关于一对一视频聊天直播系统技术(二)处理

针对视频直播的实时流网络 LiveNet 和完整的直播云解决方案,很多开发者对这个网络和解决方案的细节和使用场景非常感兴趣. 结合实时流网络 LiveNet 和直播云解决方案的实践,我们将用一系列文章,更系统化地介绍当下大热的视频直播各环节的关键技术,帮助视频直播创业者们更全面.深入地了解视频直播技术,更好地技术选型. 在上期采集中,我们介绍了视频采集针对音频采集和图像采集以及它们分别对应两种完全不同的输入源和数据格式. 本篇是<解密一对一视频聊天直播技术>系列之二:处理.我们将讲解常见视频处

华为荣耀手机录制视频 华为手机如何录制视频

如今手机已经成为了每个人形影不离的工具,随着科技的发展,不仅实现了扫码支付,还可以购物导航.视频聊天等一系列便捷方式,平时在休息的时候使用手机最多的就是看视频,聊天,但是想要录制视频分享给好友怎么操作呢?因为小编使用的是华为荣耀手机,所以今天就以华为荣耀手机录制视频为例.使用工具:方法/步骤:1.随着手机的更新换代,很多都有自带的屏幕录制功能,但是也有很多是无法录制的,对于手机中有屏幕录制功能的来说,打开设置,即可找到屏幕录制:2.手机中没有自带屏幕录制功能的可以借助第三方工具,录制之后是可以直

在Ubuntu上录制视频和编辑(很全)

Linux多媒体三剑客:GIMP,Inkscape,Blender3D Blender基金会制作的开源微电影Sintel:http://www.sintel.org/about电影采用Creative Commons Attribution 3.0授权.整个电影的制作,完全采用开源软件.使用64位Linux的图形工作站,用Blender进行3D图形,合成和视频编辑任务,用GIMP和Inkscape作图和绘画,在OpenEXR中渲染输出,用Python写脚本,在SVN中储存数据等等. 在Ubunt

delphi视频聊天

用Delphi开发视频聊天软件 一.引言 我们知道视频聊天软件的关键技术在于采集视频,并实时传输给聊天软件在线的人.对于视频的采集,这里采用微软公司的关于数字视频的一个软件包VFW(Video for Windows).相信很多人对它都很熟习,VFW能使应用程序通过数字化设备从传统的模拟视频源得到数字化的视频剪辑,VFW的一个关键思想是播放时不需要专用硬件.为了解决数字视频数据量大的问题,需要对数据进行压缩,而VFW引进了AVI的文件标准.该标准未规定如何对视频进行捕捉.压缩及播放,仅规定视频和

Android开发之打开闪光灯录制视频

Android的SDK在线API上对录制视频的方法.步骤都写得非常清楚,但是如果没有一点思路,写起来也比较式费事.录制视频的全过程要打开闪光灯(可能是因为项目需要,或者特殊原因),则必须按照一定的顺序进行开关,毕竟容易出错.要实现录制的同时开启闪光灯也不难,官方API给出了一个大体的步骤.因为要采集点视频数据,临时写了个简单的Demo学习下,必要时再深度开发. 首先在工程中的AndroidManifest.xml中添加权限声明,因为要使用到摄像头,故需要添加Camera的相关权限,另外还需要写S

bandicam如何录制视频

我们一般都很熟悉这类软件:屏幕录制专家和kk录制等,这些都是国内比较优秀的作品.不过exe的封装格式以及录制的清晰度让人很纠结.所以这里要为大家分享的是一款韩国人写录制软件Bandicam.Bandicam能很轻松的录制电脑的屏幕操作和游戏等视频录制,录制出来的视频高清,容量小,重要的是直接支持h264编码录制.还有一个很重要的一点就是其他国家的人也在使用,特别是俄罗斯,这就意味着一些特殊的支持-- 这里将分享我的一些经验,较为具体地说说如何用bandicam录制一个让人满意的视频.其中的DX录