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.mzph.cn/news/160582.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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回归通常…

3d标签云实现过程(tagcloud.js)同步原生和 vue

写在前面 本来是没有准备写这个知识点&#xff0c;但是下载这个 js 的时候发现很多都是要钱或者是积分的&#xff0c;我就不明白了一个开源了这么久的 js 怎么还有人拿来挣钱的&#xff0c;同时还有一些只有原生 html 的例子&#xff0c;但是现在都是 框架主导的一些项目&#…

【LeetCode二叉树进阶题目】606,102,107

二叉树进阶题目 606. 根据二叉树创建字符串解题思路及实现 102. 二叉树的层序遍历解题思路及实现 107. 二叉树的层序遍历 II解题思路及实现 606. 根据二叉树创建字符串 描述 给你二叉树的根节点 root &#xff0c;请你采用前序遍历的方式&#xff0c;将二叉树转化为一个由括号…

晶振有哪几种?晶振旁边的两个电容起什么作用?

晶振可以分为普通晶振、温补晶振、压控晶振、恒温晶振、差分晶振。 普通晶振通常用作微处理器的时钟器件&#xff0c;主要应用于那些稳定度要求不要的设备中&#xff0c;例如电视机、微波炉。 温补晶振&#xff0c;在晶振内部采取了对晶体频率、温度特性进行补偿&#xff0c;已…

软文推广有什么作用?媒介盒子分享

数字时代&#xff0c;品牌方以往的营销打法可能需要应时而变&#xff0c;传统的广告模式很难将品牌推广出去&#xff0c;原因就在于传统广告的成本高昂并且针对性较弱&#xff0c;而软文推广能够通过较低的成本将产品或品牌信息送到消费者面前&#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…

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

chatGLM3微调

文章目录 一、问答数据集生成器使用设置问题启动使用产出效果 二、进行微调第一步&#xff1a;下载模型第二步&#xff1a;项目准备2.1 下载项目2.2 然后使用 pip 安装依赖2.3 开始 第三步进行微调3.1安装相关依赖3.2准备数据集&#xff0c;并且上传3.3对数据集进行预处理3.4 进…

如何使用技术SEO来优化评论

你在网上购买吗&#xff1f;我的意思是&#xff0c;在当今时代&#xff0c;谁不这样做&#xff1f;作为买家&#xff0c;无论您想购买什么&#xff0c;您都了解全面和高质量评论的价值。这是您在决定是否购买产品时考虑的重要因素。 这就是为什么许多人在网上购物之前使用评论…

【开源】基于Vue和SpringBoot的康复中心管理系统

项目编号&#xff1a; S 056 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S056&#xff0c;文末获取源码。} 项目编号&#xff1a;S056&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员…

基于WEB的停车场管理系统的设计和实现【附源码】

基于WEB的停车场管理系统的设计和实现 摘 要 随着现代社会的快速发展&#xff0c;人民生活水平快速提高&#xff0c;汽车的数量飞速增加&#xff0c;与此同时停车问题也越来越受到人们的关注&#xff0c;为了实现对停车场进行有效的管理&#xff0c;结合一些停车场的模式和现状…

【LeetCode刷题】--40.组合总和II

40.组合总和II 本题详解&#xff1a;回溯算法 class Solution {public List<List<Integer>> combinationSum2(int[] candidates, int target) {int len candidates.length;List<List<Integer>> res new ArrayList<>();if (len 0) {return re…

深度学习之基于YoloV5车辆和行人目标检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介YOLOv5 简介YOLOv5 特点 车辆和行人目标检测系统 二、功能三、系统四. 总结 一项目简介 # 深度学习之基于 YOLOv5 车辆和行人目标检测系统介绍 深度学习在…

2023 年 亚太赛 APMCM 国际大学生数学建模挑战赛 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 cs数模团队在亚太赛 APMCM前为大家提供了许多资料的内容呀&…

【西行纪年番】孙悟空对战阴界王,素衣奄奄一息,巨灵拳霸气一击

Hello,小伙伴们&#xff0c;我是拾荒君。 《西行纪年番》第20集已更新。为了救回素衣&#xff0c;孙悟空想尽办法&#xff0c;最后他拜托沙悟净帮忙&#xff0c;终于成功把自己传送到阴界。原来&#xff0c;素衣的魂魄被阴界王藏在了他制造的人偶之中。沙悟净提醒孙悟空必须在…