Windows平臺如何實現RTSP流二次編碼并添加動態水印后推送RTMP或輕量級RTSP服務

技術背景

我們在對接RTSP播放器相關的技術訴求的時候,遇到這樣的需求,客戶做特種設備巡檢的,需要把攝像頭拍到的RTSP流拉下來,然后添加動態水印后,再生成新的RTSP URL,供平臺調用。真個流程需要延遲盡可能的低,分辨率要支持到1080p,并需要把添加過動態水印的數據,保存到本地。

技術實現

在此之前,大牛直播SDK有非常成熟的RTSP播放、輕量級RTSP服務和錄像模塊,要做的就是,拉取到RTSP流后,把解碼后的YUV或RGB回調給上層,上層通過圖層的形式,添加動態文字水印(圖片水印亦可),然后,投遞給輕量級RTSP服務,RTSP服務對外提供個拉流的RTSP URL,無圖無真相:

左側就是我們基于Windows平臺C#的播放器的demo,二次開發的,添加了軟、硬編碼設置(考慮到分辨率比較高,添加支持了硬編碼選項設置)、動態水印設置、輕量級RTSP服務、實時錄像和RTMP推送。

先說數據回調,本文以回調yuv數據為例:

video_frame_call_back_ = new SP_SDKVideoFrameCallBack(SetVideoFrameCallBack);
NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(player_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, IntPtr.Zero, video_frame_call_back_);

回調后的數據,投遞到輕量級RTSP服務模塊。

public void SetVideoFrameCallBack(IntPtr handle, IntPtr userData, UInt32 status, IntPtr frame){if (frame == IntPtr.Zero){return;}NT_SP_VideoFrame video_frame = (NT_SP_VideoFrame)Marshal.PtrToStructure(frame, typeof(NT_SP_VideoFrame));if (publisher_wrapper_ != null){if (publisher_wrapper_.IsPublisherHandleAvailable()){if (publisher_wrapper_.IsPublishing() || publisher_wrapper_.IsRecording() || publisher_wrapper_.IsRTSPPublisherRunning()){//publisher_wrapper_.OnPostRGB32Data(0, video_frame.plane0_, video_frame.width_ * 4 * video_frame.height_, video_frame.stride0_,//            video_frame.width_, video_frame.height_);publisher_wrapper_.OnPostYUVData(0, video_frame.plane0_, video_frame.stride0_, video_frame.plane1_, video_frame.stride1_,video_frame.plane2_, video_frame.stride2_,video_frame.width_, video_frame.height_);}}}}

音頻由于暫時不要二次處理,直接投遞過去,如果需要處理的話,處理后再投遞給publisher wrapper:

public void SetAudioPCMFrameCallBack(IntPtr handle, IntPtr user_data,UInt32 status, IntPtr data, UInt32 size,Int32 sample_rate, Int32 channel, Int32 per_channel_sample_number){if (data == IntPtr.Zero || size == 0){return;}if (publisher_wrapper_ != null){if (publisher_wrapper_.IsPublisherHandleAvailable()){if (publisher_wrapper_.IsPublishing() || publisher_wrapper_.IsRecording() || publisher_wrapper_.IsRTSPPublisherRunning()){publisher_wrapper_.OnPostAudioPCMData(data, size, 0, sample_rate, channel, per_channel_sample_number);}}}}

設置文字水印字體:

        private void btn_set_font_Click(object sender, EventArgs e){FontDialog font_dlg = new FontDialog();DialogResult result = font_dlg.ShowDialog();if (result == DialogResult.OK){// 獲取用戶所選字體Font selectedFont = font_dlg.Font;btn_set_font.Text = "" + selectedFont.Name + ", " + selectedFont.Size + "pt";selected_osd_font_ = new Font(selectedFont.Name, selectedFont.Size, FontStyle.Regular, GraphicsUnit.Point);}}

動態設置文字水印:

        private async void btn_text_osd_Click(object sender, EventArgs e){string format = "yyyy-MM-dd HH:mm:ss.fff";StringBuilder sb = new StringBuilder();sb.Append("施工單位:上海視沃信息科技有限公司(daniusdk.com)");sb.Append("\r\n");sb.Append("施工時間:");sb.Append(DateTime.Now.DayOfWeek.ToString());sb.Append(" ");sb.Append(DateTime.Now.ToString(format));sb.Append("\r\n");sb.Append("當前位置:上海市");string str = sb.ToString();Bitmap bmp = GenerateBitmap(str);int index = 1;int x = 0;int y = 200;UpdateLayerRegion(index, x, y, bmp);await Task.Delay(30);UpdateARGBBitmap(index, bmp);}

如果需要硬編碼:

            if (btn_check_video_hardware_encoder_.Checked){is_hw_encoder = true;}Int32 cur_sel_encoder_id = 0;Int32 cur_sel_gpu = 0;if (is_hw_encoder){int cur_sel_hw = combobox_video_encoders_.SelectedIndex;if (cur_sel_hw >= 0){cur_sel_encoder_id = Convert.ToInt32(combobox_video_encoders_.SelectedValue);cur_sel_gpu = -1;int cur_sel_hw_dev = combobox_video_hardware_encoder_devices_.SelectedIndex;if (cur_sel_hw_dev >= 0){cur_sel_gpu = Convert.ToInt32(combobox_video_hardware_encoder_devices_.SelectedValue);}}else{is_hw_encoder = false;}}if (!is_hw_encoder){if ((int)NTCommonMediaDefine.NT_MEDIA_CODEC_ID.NT_MEDIA_CODEC_ID_H264 == cur_video_codec_id){cur_sel_encoder_id = btn_check_openh264_encoder_.Checked ? 1 : 0;}}publisher_wrapper_.SetVideoEncoder((int)(is_hw_encoder ? 1 : 0), (int)cur_sel_encoder_id, (uint)cur_video_codec_id, (int)cur_sel_gpu);publisher_wrapper_.SetVideoQualityV2(publisher_wrapper_.CalVideoQuality(width_, height_, is_h264_encoder));publisher_wrapper_.SetVideoBitRate(publisher_wrapper_.CalBitRate(video_fps_, width_, height_));publisher_wrapper_.SetVideoMaxBitRate(publisher_wrapper_.CalMaxKBitRate(video_fps_, width_, height_, false));publisher_wrapper_.SetVideoKeyFrameInterval(key_frame_interval_);if (is_h264_encoder){publisher_wrapper_.SetVideoEncoderProfile(1);}publisher_wrapper_.SetVideoEncoderSpeed(publisher_wrapper_.CalVideoEncoderSpeed(width_, height_, is_h264_encoder));

啟動停止RTSP服務:

        private void btn_rtsp_service_Click(object sender, EventArgs e){if(publisher_wrapper_.IsRTSPSerivceRunning()){publisher_wrapper_.StopRtspService();btn_rtsp_service.Text = "啟動RTSP服務";btn_rtsp_stream.Enabled = false;}else{if(publisher_wrapper_.StartRtspService()){btn_rtsp_service.Text = "停止RTSP服務";btn_rtsp_stream.Enabled = true;}}}

發布RTSP流:

        private void btn_rtsp_stream_Click(object sender, EventArgs e){if (publisher_wrapper_.IsRTSPPublisherRunning()){publisher_wrapper_.StopRtspStream();btn_rtsp_stream.Text = "發布RTSP流";btn_get_rtsp_session_numbers.Enabled = false;btn_rtsp_service.Enabled = true;}else{if (!publisher_wrapper_.IsPublisherHandleAvailable()){if (!OpenPublisherHandle()){return;}}if (publisher_wrapper_.GetPublisherHandleCount() < 1){SetCommonOptionToPublisherSDK();}if (!publisher_wrapper_.StartRtspStream()){MessageBox.Show("調用StartRtspStream失敗..");return;}btn_rtsp_stream.Text = "停止RTSP流";btn_get_rtsp_session_numbers.Enabled = true;btn_rtsp_service.Enabled = false;}}

獲取RTSP會話數:

        private void btn_get_rtsp_session_numbers_Click(object sender, EventArgs e){if (publisher_wrapper_.IsRTSPPublisherRunning()){int session_numbers = publisher_wrapper_.GetRtspSessionNumbers();MessageBox.Show(session_numbers.ToString(), "當前RTSP連接會話數");}}

本地錄像:

        private void btn_start_recorder_Click(object sender, EventArgs e){if (!publisher_wrapper_.IsPublisherHandleAvailable()){if (!OpenPublisherHandle()){return;}}if (publisher_wrapper_.GetPublisherHandleCount() < 1){SetCommonOptionToPublisherSDK();}if (!publisher_wrapper_.StartRecorder()){MessageBox.Show("調用StartRecorder失敗..");return;}btn_start_recorder.Enabled = false;btn_stop_recorder.Enabled = true;}private void btn_stop_recorder_Click(object sender, EventArgs e){if (!publisher_wrapper_.IsPublisherHandleAvailable())return;if (publisher_wrapper_.IsRecording()){publisher_wrapper_.StopRecorder();btn_start_recorder.Enabled = true;btn_stop_recorder.Enabled = false;}}

暫停錄像、恢復錄像:

        private void btn_pause_recorder_Click(object sender, EventArgs e){if (!publisher_wrapper_.IsPublisherHandleAvailable()){return;}String btn_pause_rec_text = btn_pause_recorder.Text;if ("暫停錄像" == btn_pause_rec_text){UInt32 ret = publisher_wrapper_.PauseRecorder(true);if ((UInt32)NT.NTSmartPublisherDefine.NT_PB_E_ERROR_CODE.NT_ERC_PB_NEED_RETRY == ret){MessageBox.Show("暫停錄像失敗, 請重新嘗試!");return;}else if (NTBaseCodeDefine.NT_ERC_OK == ret){btn_pause_recorder.Text = "恢復錄像";}}else{UInt32 ret = publisher_wrapper_.PauseRecorder(false);if ((UInt32)NT.NTSmartPublisherDefine.NT_PB_E_ERROR_CODE.NT_ERC_PB_NEED_RETRY == ret){MessageBox.Show("恢復錄像失敗, 請重新嘗試!");return;}else if (NTBaseCodeDefine.NT_ERC_OK == ret){btn_pause_recorder.Text = "暫停錄像";}}}

推送RTMP:

        private void btn_publish_rtmp_Click(object sender, EventArgs e){if (!publisher_wrapper_.IsPublisherHandleAvailable()){if (!OpenPublisherHandle()){return;}}if (publisher_wrapper_.GetPublisherHandleCount() < 1){SetCommonOptionToPublisherSDK();}String url = "rtmp://192.168.0.108:1935/hls/stream1";if (url.Length < 8){publisher_wrapper_.Close();MessageBox.Show("請輸入推送地址");return;}if (!publisher_wrapper_.StartPublisher(url)){MessageBox.Show("調用StartPublisher失敗..");return;}btn_publish_rtmp.Enabled = false;btn_stop_publish_rtmp.Enabled = true;}private void btn_stop_publish_rtmp_Click(object sender, EventArgs e){if (!publisher_wrapper_.IsPublisherHandleAvailable())return;if (publisher_wrapper_.IsPublishing()){publisher_wrapper_.StopPublisher();btn_publish_rtmp.Enabled = true;btn_stop_publish_rtmp.Enabled = false;}}

圖層設計,目前設計兩個圖層,一個是原始YUV底層,另外一個是文字水印圖層,如果需要動態去除文字水印,只要index為1的圖層,enable設置為0即可。

            NTSmartPublisherSDK.NT_PB_ClearLayersConfig(publisher_handle_, 0,0, IntPtr.Zero);if (video_option_ == (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_LAYER){NT_PB_ExternalVideoFrameLayerConfig external_layer_c0 = new NT_PB_ExternalVideoFrameLayerConfig();external_layer_c0.base_.type_ = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME;external_layer_c0.base_.index_ = 0;external_layer_c0.base_.enable_ = 1;external_layer_c0.base_.region_.x_ = 0;external_layer_c0.base_.region_.y_ = 0;external_layer_c0.base_.region_.width_ = video_width_;external_layer_c0.base_.region_.height_ = video_height_;external_layer_c0.base_.offset_ = Marshal.OffsetOf(external_layer_c0.GetType(), "base_").ToInt32();external_layer_c0.base_.cb_size_ = (uint)Marshal.SizeOf(external_layer_c0);IntPtr external_layer_conf0 = Marshal.AllocHGlobal(Marshal.SizeOf(external_layer_c0));Marshal.StructureToPtr(external_layer_c0, external_layer_conf0, true);UInt32 external_r0 = NTSmartPublisherSDK.NT_PB_AddLayerConfig(publisher_handle_, 0,external_layer_conf0, (int)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME,0, IntPtr.Zero);Marshal.FreeHGlobal(external_layer_conf0);//OSD水印層NT_PB_ExternalVideoFrameLayerConfig external_layer_c1 = new NT_PB_ExternalVideoFrameLayerConfig();external_layer_c1.base_.type_ = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME;external_layer_c1.base_.index_ = 1;external_layer_c1.base_.enable_ = 1;external_layer_c1.base_.region_.x_ = 0;external_layer_c1.base_.region_.y_ = 200;external_layer_c1.base_.region_.width_ = 200;external_layer_c1.base_.region_.height_ = 200;external_layer_c1.base_.offset_ = Marshal.OffsetOf(external_layer_c1.GetType(), "base_").ToInt32();external_layer_c1.base_.cb_size_ = (uint)Marshal.SizeOf(external_layer_c1);IntPtr external_layer_conf = Marshal.AllocHGlobal(Marshal.SizeOf(external_layer_c1));Marshal.StructureToPtr(external_layer_c1, external_layer_conf, true);UInt32 external_r1 = NTSmartPublisherSDK.NT_PB_AddLayerConfig(publisher_handle_, 0,external_layer_conf, (int)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME,0, IntPtr.Zero);Marshal.FreeHGlobal(external_layer_conf);//end}

總結

RTSP拉流二次編碼,整體邏輯不復雜,就是把數據回調后,二次處理,我們推送端設計的是圖層的形式,所以,回調后的數據,直接作為第0層,文字水印作為第一層,如果需要圖片水印,圖片水印作為第三層即可。RTSP拉流二次編碼,如果做到客戶端盡量無感知,需要盡可能的壓縮整體處理的延遲,確保從數據采集,到二次處理,到再次播放出來毫秒級,滿足絕大多數場景下的技術需求。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/160582.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/160582.shtml
英文地址,請注明出處:http://en.pswp.cn/news/160582.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Anthropic LLM論文閱讀筆記

研究時間&#xff1a;與Instrcut GPT同期的工作&#xff0c;雖然其比ChatGPT發布更晚&#xff0c;但是其實完成的時間比ChatGPT更早。與ChatGPT的應用區別&#xff1a;該模型比ChatGPT回答我不知道的概率更高。將強化學習用于大語言模型&#xff08;RLHF&#xff09;&#xff1…

6.基于蜻蜓優化算法 (DA)優化的VMD參數(DA-VMD)

代碼原理 基于蜻蜓優化算法 (Dragonfly Algorithm, DA) 優化的 VMD 參數&#xff08;DA-VMD&#xff09;是指使用蜻蜓優化算法對 VMD 方法中的參數進行自動調優和優化。 VMD&#xff08;Variational Mode Decomposition&#xff09;是一種信號分解方法&#xff0c;用于將復雜…

【數據結構】鏈表中二級指針的應用

&#x1f984;個人主頁:修修修也 &#x1f38f;所屬專欄:數據結構 ??操作環境:Visual Studio 2022 (注:為方便演示本篇使用的x86系統,因此指針的大小為4個字節) 目錄 &#x1f4cc;形參的改變不影響實參! 1.調用函數更改整型時傳值調用與傳址調用的區別 &#x1f38f;傳值…

微服務學習|初識Docker、使用Docker、自定義鏡像、DockerCompose、Docker鏡像倉庫

初識Docker 項目部署的問題 大型項目組件較多&#xff0c;運行環境也較為復雜&#xff0c;部署時會碰到一些問題 依賴關系復雜&#xff0c;容易出現兼容性問題 開發、測試、生產環境有差異 Docker如何解決依賴的兼容問題的? 將應用的Libs (函數庫)、Deps (依賴)配置與應用…

線性回歸的正則方法:嶺回歸和Lasso

線性回歸的正則方法包括嶺回歸&#xff08;Ridge Regression&#xff09;和Lasso回歸&#xff08;Least Absolute Shrinkage and Selection Operator Regression&#xff09;。這兩種方法都是為了解決線性回歸中可能存在的過擬合問題而提出的。 選擇使用嶺回歸還是Lasso回歸通常…

使用 goland 開發 golang 項目環境配置

方式1&#xff1a;使用 GOPATH 和 GOROOT 在 goland 中打開&#xff1a;Settings - Go&#xff0c;會看到 GOROOT、GOPATH&#xff0c;其相關解釋與配置如下&#xff1a; GOROOT&#xff1a;對應 go 的安裝路徑&#xff0c;例如&#xff1a;D:\go\binGOPATH&#xff1a;是我們…

JavaScript中的事件循環 為什么是微任務先運行

無意中看到這個問題&#xff0c;以下是個人的看法 1、性能和響應性&#xff1a; 微任務通常比宏任務執行得更快&#xff0c;因為微任務通常涉及更少的工作量。將微任務放在宏任務之前可以盡早執行那些需要快速響應的任務&#xff0c;提高系統的響應性能。 2、Promise 的異步特…

3d標簽云實現過程(tagcloud.js)同步原生和 vue

寫在前面 本來是沒有準備寫這個知識點&#xff0c;但是下載這個 js 的時候發現很多都是要錢或者是積分的&#xff0c;我就不明白了一個開源了這么久的 js 怎么還有人拿來掙錢的&#xff0c;同時還有一些只有原生 html 的例子&#xff0c;但是現在都是 框架主導的一些項目&#…

【Exception】Error: Dynamic require of “path“ is not supported

Talk is cheap, show me the code. 環境 | Environment kversionOSwindows 11Node.jsv18.14.2npm9.5.0vite5.0.0vue3.3.8 報錯日志 | Error log >npm run dev> app10.0.0 dev > viteERROR failed to load config from C:\code\frontend\app1\vite.config.js …

【LeetCode二叉樹進階題目】606,102,107

二叉樹進階題目 606. 根據二叉樹創建字符串解題思路及實現 102. 二叉樹的層序遍歷解題思路及實現 107. 二叉樹的層序遍歷 II解題思路及實現 606. 根據二叉樹創建字符串 描述 給你二叉樹的根節點 root &#xff0c;請你采用前序遍歷的方式&#xff0c;將二叉樹轉化為一個由括號…

從零開始學習typescript——運算符(算術運算符、賦值運算符、比較運算符)

算術運算符 算術運算符主要是針對數值類型和長整型&#xff1b;包括有加法、減法、乘法、除法、自增、自減等運算 加法&#xff08;&#xff09; let x:number1let y:number 2console.log(xy)減法&#xff08;-&#xff09; let x:number1let y:number 2console.log(y-x)乘法…

晶振有哪幾種?晶振旁邊的兩個電容起什么作用?

晶振可以分為普通晶振、溫補晶振、壓控晶振、恒溫晶振、差分晶振。 普通晶振通常用作微處理器的時鐘器件&#xff0c;主要應用于那些穩定度要求不要的設備中&#xff0c;例如電視機、微波爐。 溫補晶振&#xff0c;在晶振內部采取了對晶體頻率、溫度特性進行補償&#xff0c;已…

軟件工程理論與實踐 (呂云翔) 第十三章 軟件測試方法與過程課后習題及其答案解析

第十三章 軟件測試方法與過程 1.判斷題 &#xff08;1&#xff09;白盒測試無須考慮模塊內部的執行過程和程序結構&#xff0c;只需了解模塊的功能即可。() 解析&#xff1a;白盒測試需要考慮模塊內部的執行過程和程序結構&#xff0c;以便設計測試用例和覆蓋代碼路徑。 &a…

軟文推廣有什么作用?媒介盒子分享

數字時代&#xff0c;品牌方以往的營銷打法可能需要應時而變&#xff0c;傳統的廣告模式很難將品牌推廣出去&#xff0c;原因就在于傳統廣告的成本高昂并且針對性較弱&#xff0c;而軟文推廣能夠通過較低的成本將產品或品牌信息送到消費者面前&#xff0c;今天媒介盒子就來分享…

58同城算法工程師一面&二面 面試題

來源&#xff1a;投稿 作者&#xff1a;LSC 編輯&#xff1a;學姐 一面 40min 1.Gbdt和xgboost的區別 XGBoost是對GBDT的改進和擴展&#xff0c;它提供了更高的效率、更好的性能、正則化技術、內置特征選擇等功能。 (1)正則化: GBDT使用基本的樹模型&#xff0c;并在每一輪…

vue3.0 + qiankun遇到的問題

進入子應用再回到主應用切換動態路由時 TypeError: Cannot read properties of undefined (reading ‘appWrapperGetter’) application ‘plat’ died in status UNMOUNTING: instance.$destroy is not a function 第一個報錯是因為子應用切走時沒有銷毀 vue的實例&#xff0…

常用RFC規范匯總

官網&#xff1a;https://www.rfc-editor.org/ The RFC Series (ISSN 2070-1721) contains technical and organizational documents about the Internet, including the specifications and policy documents produced by five streams: the Internet Engineering Task Force …

TCP/IP

分層模型 TCP 傳輸控制協議 UDP 用戶數據包協議 四層 應用層 負責發送/接收消息 傳輸層 負責拆分和組裝 .期間會有編號 網絡層 TCP/UDP 屬于網絡層, 不會判斷和處理編號 數據鏈路層 以太網 ,網絡設備 TCP 連接 TCP連接需要端口,進行通信 Java 通過Socket 接收消息 發送 …

基于SpringBoot+Vue的體檢預約管理系統

基于SpringBootVue的體檢預約管理系統的設計與實現~ 開發語言&#xff1a;Java數據庫&#xff1a;MySQL技術&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系統展示 主頁 管理員界面 用戶界面 摘要 體檢預約管理系統是一種基于Spring Boot…

Vue3常用操作

一、Vue3項目構建 1、安裝最新版本vue npm create vuelatest 2、選擇需要的配置 3、進入項目 cd 項目名稱 4、下載依賴 npm install 5、啟動項目 npm run dev