1. 程式人生 > >如何實現:錄製視訊聊天的全過程? 【低調贈送:QQ高仿版GG 4.3 最新原始碼】

如何實現:錄製視訊聊天的全過程? 【低調贈送:QQ高仿版GG 4.3 最新原始碼】

  前段時間做個專案,客戶需要將視訊對話的整個過程錄製下來,這樣,以後就可以隨時觀看。想來錄製整個視訊聊天的過程這樣的功能應該是個比較常見的需求,比如,基於網路語音視訊的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的最新版本中使用了上述的VideoChatRecorder類進行視訊聊天錄製以生成的MP4檔案(預設是在執行目錄下名稱為 VideoChat.mp4 的檔案),用QQ影音播放器進行播放這個檔案,其效果如下所示:

       

________________________________________________________________________ 

歡迎和我探討關於 GG 和 GGMeeting 的一切,我的QQ:2027224508,多多交流!  

大家有什麼問題和建議,可以留言,也可以傳送email到我郵箱:[email protected]。  

如果你覺得還不錯,請粉我,順便再頂一下啊

相關推薦

如何實現錄製視訊聊天全過程低調贈送QQ仿GG 4.3 最新原始碼

  前段時間做個專案,客戶需要將視訊對話的整個過程錄製下來,這樣,以後就可以隨時觀看。想來錄製整個視訊聊天的過程這樣的功能應該是個比較常見的需求,比如,基於網路語音視訊的1:1的英語口語輔導,如果能將輔導的整個過程錄製下來生成一個標準的MP4檔案,就是一份難得的資料,便於以後複習和分享。我將1:1的視訊對話錄

QQ揭祕如何實現托盤閃動訊息提醒?低調贈送QQ仿GG 4.1 最新原始碼

  當QQ收到好友的訊息時,托盤的圖示會變成好友的頭像,並閃動起來,點選托盤,就會彈出與好友的聊天框,隨即,托盤恢復成QQ的圖示,不再閃動。當然,如果還有其它的好友的訊息沒有提取,托盤的圖示會變成另一個好友的圖示,並繼續閃動。那麼,QQ的這一效果是如何實現的了?我在QQ高仿GG2014中實現了同樣的效果,這裡

QQ揭祕如何實現窗體靠邊隱藏?低調贈送QQ仿GG 4.2 最新原始碼

      QQ有個靠邊隱藏的功能,使用起來很方便:在螢幕上拖動QQ的主窗體,當窗體的上邊沿與螢幕的上邊沿對齊時,主窗體就會duang~~地隱藏起來,當將滑鼠移到螢幕上邊沿的對應區域時,主窗體又會duang~~顯示出來。   我在GG的最新版4.2中也增加了靠邊隱藏的功能,支援靠左邊沿隱藏、靠上邊沿隱藏、靠

如何做到在虛擬資料庫和真實資料庫之間自由切換?低調贈送QQ仿GG 4.4 最新原始碼

      記得以前在公司上班時,有時候白天的活沒幹完,我就會把工作帶回家晚上加班繼續做。但是,我們開發用的資料庫是部署在公司局網內部的一臺伺服器上的,在家裡是肯定連不上這臺機器的。在家裡沒有資料庫,服務端就跑不起來,功能也就沒辦法除錯。後來我們的解決方法就是使用虛擬資料庫。在公司上班時,就使用公司局網的真實

可在廣域網部署執行的QQ仿 -- GG嘰嘰V3.7,優化視訊聊天、控制更多相關細節

  在廣域網中,由於網路的結構紛繁複雜、而且其實時狀況又是千變萬化的,所以,要使廣域網中的視訊聊天達到一個令人滿意的效果,存在諸多挑戰。這次釋出的GG 3.7版本嘗試在這一方向上做一些努力,據我自己測試,相比之前版本,新版本GG的視訊聊天的效果確實有較大提升。本文不僅會分享GG的最新原始碼,而且也會把我在過去

即時通訊系統中實現聊天訊息加密,讓通訊更安全低調贈送C#開源即時通訊系統(支援廣域網)——GGTalk4.5 最新原始碼

  在即時通訊系統(IM)中,加密重要的通訊訊息,是一個常見的需求。尤其在一些政府部門的即時通訊軟體中(如稅務系統),對即時聊天訊息進行加密是非常重要的一個功能,因為談話中可能會涉及到機密的資料。我在最新的GG 4.5中,增加了對即時聊天訊息進行加密的功能,但這一功能並不是強制的,可以通過開關來進行控制。本文

可在廣域網部署執行的QQ仿 -- GG2014 完美!新增支援聊天記錄、好友分組、托盤閃動訊息提醒、登入狀態、GIF動態表情

  距上次GG V3.7版本()的釋出,已經有50天了,這50天對於GG來說,是一個重大的飛躍。因為這段時間通過一些基於GG的實戰專案,發現了GG的很多bug和不足之處,我都一一做了修正,並增加了一些基礎且必須的功能。這次釋出的是GG V4.0版本,又稱為GG 2014。   記得在GG早期版本時,園子裡的

原始碼來襲一對一視訊聊天系統在視訊錄製方面的問題解決

我們知道,一對一視訊聊天系統的流媒體傳輸流程與傳統直播的傳輸流程沒有什麼區別,他們的流程都是“視訊採集”→“加濾鏡”→“音視訊編碼”→“推流”→“流媒體伺服器”→“拉流”→“音視訊解碼播放”。 今天我們就通過視訊錄製的一段程式碼來講講視訊錄製中的一些小細節。 視訊錄製: private

120行程式碼實現 瀏覽器WebRTC視訊聊天

本例子是參考webrtc-tutorial-simple-video-chat做的。 這個教程應該主要是去宣傳ScaleDrone的sdk, 他們的服務是收費的,但是免費的也可以用,就是有些次數限制。 1 準備 使用最新版谷歌瀏覽器(62版)視訊聊天中 一個是win

C#實現多人視訊聊天

      在 《C#實現多人語音聊天》一文釋出後,很多朋友建議我也實現一個視訊聊天室給他們參考一下,其實,視訊聊天室與語音聊天室的原理是差不多的,由於加入了攝像頭、視訊的處理,邏輯會繁雜一些,本文就實現一個簡單的多人視訊聊天系統,讓多個人可以進入同一個房間進行語音視訊溝通。

WebRTC一個視訊聊天的簡單例子

相關API簡介 在前面的章節中,已經對WebRTC相關的重要知識點進行了介紹,包括涉及的網路協議、會話描述協議、如何進行網路穿透等,剩下的就是WebRTC的API了。 WebRTC通訊相關的API非常多,主要完成了如下功能: 信令交換 通訊候選地址交換 音視訊採集 音視訊傳送、接收 相關API太多,為避免

多媒體開發(2)錄製視訊

上一節介紹了用ffplay來播放檔案(或url),這裡有一個概念,如果是播放已經存在的檔案,那叫“回放”,也就是Playback(從流媒體的角度也叫點播),如果播放的是正在錄製的資料(邊錄邊播),那叫直播。 不管是回放還是直播,都需要有媒體資料,那這個媒體資料是怎麼來的呢?從已有的檔案編輯而來是一個辦法,但

仿介面實現功能

第一步: 匯入類連結1連結2 第二步: 在AppDelegate.m裡 標頭檔案 #import “ViewController.h” 程式碼如下: self.window.rootViewController = [ViewController new]; self.window.back

分享《Keras快速上手基於Python的深度學習實戰-謝樑》PDF免費下載

2018-11-29 15:27:13 百度網盤下載:https://pan.baidu.com/s/1FsmtI2JqzsWPPc9Ev8iXww 《Keras快速上手:基於Python的深度學習實戰》高清PDF,531頁,帶書籤目錄,文字可以複製。 這是一本少見的深入淺出介紹深度學習的入門書籍。

《資料結構演算法實現及解析》電子書下載 -(百度網盤 PDF格式)

     作者:高一凡 副書名:配合嚴蔚敏、吳偉民編著的《資料結構》(C語言版) 出版日期:2002-10-1 出版社:其它 頁數:450 ISBN:7-5606-1176-1/TP*0608 檔案格式:PDF 檔案大小:8

Maven 報Could not find artifact org.apache.maven.plugins:maven-resources-plugin:pom:2.4.3

前些時候我學習Maven,我遇到了以下兩個錯誤(該死的這兩個錯誤,讓我浪費了四天時間): 1、Could not resolve archetype org.apache.maven.archetypes:maven-archetype-webapp:RELEASE from any of the conf

並發架構系列Redis為什麽是單線程、及並發快的3大原因詳解

一點 方案 優勢 時間 自己實現 詳細 http color 哪些 Redis的高並發和快速原因1.redis是基於內存的,內存的讀寫速度非常快; 2.redis是單線程的,省去了很多上下文切換線程的時間; 3.redis使用多路復用技術,可以處理並發的連接。非阻塞IO 內

移動開發關於一對一視訊聊天直播技術(七)直播雲 SDK 效能測試模

本篇是《一對一視訊直播技術詳解》系列的最後一篇直播雲 SDK 效能測試模型,SDK 的效能對最終 App 的影響非常大。SDK 版本迭代快速,每次釋出前都要進行系統的測試,測試要有比較一致的行為,要有效能模型作為理論基礎,對 SDK 的效能做量化評估。本文就是來探討影響 SDK 效能的指標並建立相應的效能模型

初學Python關於python實現Cameo對視訊的擷取和錄製

學習《OpenCV3計算機視覺,Python語言實現》時讀書筆記。 最近對計算機視覺產生了濃厚的興趣,又剛好在學校的圖書館看到了這本書,那可剛好滿足了我的興趣,今天先敲一個Cameo。 儲存圖片視訊時,直接通過當前日期進行儲存。 先建立一個manager.py imp

C#呼叫FFMPEG實現桌面錄製視訊+音訊+生成本地檔案)筆記

不得不說FFMPEG真是個神奇的玩意,所接觸的部分不過萬一。網上有個很火的例子是c++方面的,當然這個功能還是用c++來實現比較妥當。 然而我不會c++ 因為我的功能需求比較簡單,只要實現基本的錄製就可以了,其實就是一句命令的事 先來程式碼:RecordHelper類 u