Windows平台RTMP推送|轻量级RTSP服务摄像头如何添加动态文字水印

本文涉及的产品
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,视频资源包5000点
简介: 本文介绍了在Windows平台上实现摄像头或屏幕流中动态文字水印的技术方法。通过大牛直播SDK示例,展示了如何从文本获取RGB数据,并将其叠加到视频流上。文中提供了代码片段来说明如何开启文字水印、生成包含实时信息的位图、以及如何更新和控制图层。最终实现了动态显示时间和位置信息的需求。对这一领域的开发者而言,本文提供了实用的参考与指导。

技术背景

我们在做Windows平台RTMP推送、轻量级RTSP服务的时候,遇到过这样的技术需求,除了常规的png图片水印外,开发者希望能在桌面或摄像头上,叠加上实时时间和位置信息,并保存到图像里。

技术实现

本文以大牛直播SDK的摄像头采集+动态文字水印为例,谈谈如何实现的,简单来说,这块分两步,第一步,如何从文字里面获取到rgb数据,第二步,如何吧rgb数据叠加到摄像头上?

废话不多说,先上图,选中采集摄像头和摄像头添加文字水印,如果需要默认打开动态文字水印,直接打开即可,如需关闭,随时可以关闭或二次打开:

image.gif

本文以启动个轻量级RTSP服务为例,效果如下,可以清楚的看到右侧播放端,显示实时更新的文字信息(更新间隔,可以自行设置):

image.gif

打开摄像头添加文字水印:

private void btn_text_osd_Click(object sender, EventArgs e)
        {
            if (btn_text_osd.Text.Equals("打开动态文字水印"))
            {
                if (!timer_clock_.Enabled)
                {
                    timer_clock_.Enabled = true;
                }
                enable_layer(get_text_layer_index(), true);
                btn_text_osd.Text = "关闭动态文字水印";
                is_enable_text_watermarker_ = true;
            }
            else
            {
                if (!is_enable_publish_random_userdata_)
                {
                    timer_clock_.Enabled = false;
                }
                enable_layer(get_text_layer_index(), false);
                btn_text_osd.Text = "打开动态文字水印";
                is_enable_text_watermarker_ = false;
            }
        }

image.gif

添加文字水印图层:

private async void AddWaterMarkerLayer()
        {
            Bitmap bitmap = null;
            try
            {
                string format = "yyyy-MM-dd HH:mm:ss.fff";
                StringBuilder sb = new StringBuilder();
                sb.Append("施工单位:上海视沃信息科技有限公司");
                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 = GenerateBitmap(str);
            }
            catch (Exception)
            {
                return;
            }
            if (null == bitmap)
                return;
            int x = 0;
            int y = 200;
            UpdateLayerRegion(get_text_layer_index(), x, y, bitmap);
            enable_layer(get_text_layer_index(), true);
            await Task.Delay(30);
            post_argb8888_layer_image(get_text_layer_index(), bitmap);
        }

image.gif

投递图层数据:

/*
         * SmartPublisherDemoDlg.cs
         * Author: daniusdk.com
         */
        public void post_argb8888_layer_image(int index, Bitmap bitmap)
        {
            if (index < 0 || null == bitmap)
                return;
            if (!is_running())
                return;
            Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
            System.Drawing.Imaging.BitmapData bmp_data = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
            IntPtr ptr = bmp_data.Scan0;
            int stride = bmp_data.Stride;
            if (stride < 0)
                stride = -stride;
            NT_PB_Image pb_image = new NT_PB_Image();
            pb_image.format_ = (int)NT.NTSmartPublisherDefine.NT_PB_E_IMAGE_FORMAT.NT_PB_E_IMAGE_FORMAT_ARGB;
            pb_image.width_ = bmp_data.Width;
            pb_image.height_ = bmp_data.Height;
            pb_image.timestamp_ = 0;
            pb_image.cb_size_ = (UInt32)Marshal.SizeOf(pb_image);
            pb_image.stride_ = new Int32[NTSmartPublisherDefine.NT_PB_IMAGE_MAX_PLANE_NUM];
            pb_image.stride_[0] = stride;
            pb_image.plane_size_ = new Int32[NTSmartPublisherDefine.NT_PB_IMAGE_MAX_PLANE_NUM];
            pb_image.plane_size_[0] = pb_image.stride_[0] * bmp_data.Height;
            pb_image.plane_ = new IntPtr[NTSmartPublisherDefine.NT_PB_IMAGE_MAX_PLANE_NUM];
            pb_image.plane_[0] = ptr;
            for (int i = 1; i < NTSmartPublisherDefine.NT_PB_IMAGE_MAX_PLANE_NUM; ++i)
            {
                pb_image.stride_[i] = 0;
                pb_image.plane_size_[i] = 0;
                pb_image.plane_[i] = IntPtr.Zero;
            }
            IntPtr image_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(pb_image));
            Marshal.StructureToPtr(pb_image, image_ptr, false);
            if (is_running())
            {
                if (shared_lock_.TryEnterReadLock(enter_read_lock_timeout_ms_))
                {
                    try
                    {
                        if (is_running())
                            NTSmartPublisherSDK.NT_PB_PostLayerImage(publisher_handle_, 0, index, image_ptr, 0, IntPtr.Zero);
                    }
                    finally
                    {
                        shared_lock_.ExitReadLock();
                    }
                }
            }
            Marshal.FreeHGlobal(image_ptr);
            bitmap.UnlockBits(bmp_data);
        }

image.gif

再说如何实现两个图层:

if (btn_check_camera_input_.Checked && btn_add_osd_to_camera_.Checked)
{
                    if (-1 != cur_sel_camera_index_
                           && -1 != cur_sel_camera_resolutions_index_
                           && -1 != cur_sel_camera_frame_rate_index_)
                    {
                        NT_PB_CameraLayerConfigV2 camera_layer_c0 = new NT_PB_CameraLayerConfigV2();
                        CameraInfo camera = cameras_[cur_sel_camera_index_];
                        NT_PB_VideoCaptureCapability cap = camera.capabilities_[cur_sel_camera_resolutions_index_];
                        camera_layer_c0.device_unique_id_utf8_ = camera.id_;
                        type = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_CAMERA;
                        fill_layer_base(camera_layer_c0, out camera_layer_c0.base_, type, index, true, 0, 0, cap.width_, cap.height_);
                        camera_layer_index_ = camera_layer_c0.base_.index_;
                        if (btn_check_flip_horizontal_camera_.Checked)
                        {
                            camera_layer_c0.is_flip_horizontal_ = 1;
                        }
                        else
                        {
                            camera_layer_c0.is_flip_horizontal_ = 0;
                        }
                        if (btn_check_flip_vertical_camera_.Checked)
                        {
                            camera_layer_c0.is_flip_vertical_ = 1;
                        }
                        else
                        {
                            camera_layer_c0.is_flip_vertical_ = 0;
                        }
                        // 这种叠加模式下不要旋转,否则变形厉害, 要么就定好一个角度,调整宽高,但不要动态旋转
                        camera_layer_c0.rotate_degress_ = 0;
                        if (add_layer_config(camera_layer_c0, type))
                            index++;
                        //第二层:文字水印
                        //叠加的文本层
                        NT_PB_ExternalVideoFrameLayerConfig text_layer = new NT_PB_ExternalVideoFrameLayerConfig();
                        type = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME;
                        fill_layer_base(text_layer, out text_layer.base_, type, index, true, 0, 0, 64, 64);
                        if (add_layer_config(text_layer, type))
                            text_layer_index_ = index++;
                    }
                    NTSmartPublisherSDK.NT_PB_SetFrameRate(publisher_handle_, (uint)(cur_sel_camera_frame_rate_index_ + 1));
}

image.gif

如果需要更新图层位置:

public bool update_layer_region(int index, int x, int y, int w, int h)
        {
            if (index < 1)
                return false;
            if (is_empty_handle())
                return false;
            NT_PB_RectRegion region = new NT_PB_RectRegion();
            region.x_ = x;
            region.y_ = y;
            region.width_ = w;
            region.height_ = h;
            return NTBaseCodeDefine.NT_ERC_OK == NTSmartPublisherSDK.NT_PB_UpdateLayerRegion(publisher_handle_, 0, index, ref region);
        }

image.gif

动态打开关闭图层:

public bool enable_layer(int index, bool enable)
        {
            if (index < 1)
                return false;
            if (is_empty_handle())
                return false;
            return NTBaseCodeDefine.NT_ERC_OK == NTSmartPublisherSDK.NT_PB_EnableLayer(publisher_handle_, 0, index, enable ? 1 : 0);
        }

image.gif

总结

Windows平台添加动态文字水印,首先确保从文字拿到rgb数据,然后,设置两个图层,摄像头或者屏幕数据,作为底层,上层添加文字图层,如果需要实时更新,有个定制器,刷新即可,感兴趣的开发者,可以单独和我交流。

相关文章
WK
|
25天前
|
存储 JavaScript 前端开发
如何在Windows平台上手micro:bit
micro:bit是一款口袋大小的可编程计算机,使用ARM处理器,跨平台兼容性强,适用于青少年学习编程。通过USB接口轻松连接Windows电脑,找到“MICROBIT”文件夹开始互动。提供MakeCode(支持拖拽编程及JavaScript)和MicroPython平台,满足不同编程需求。创建项目后,下载.hex文件至micro:bit,即可运行程序,展现创意成果。
WK
23 1
|
28天前
|
Windows
Windows平台如何修改监听的服务名称?
【8月更文挑战第15天】在Windows平台上可透过注册表编辑器、命令提示符或第三方工具修改服务的显示名称。首先,通过注册表编辑器找到`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services`下的目标服务,修改其“DisplayName”键值。或者,在命令提示符中使用`sc config`命令来变更服务名称。此外,利用第三方工具如Windows Service Manager也能简化此过程。修改前请确保了解可能的影响并做好备份。
|
29天前
|
图形学 Android开发 iOS开发
穿越数字洪流,揭秘Unity3d中的视频魔法!Windows、Android和iOS如何征服RTSP与RTMP的终极指南!
【8月更文挑战第15天】在数字媒体的海洋中,实时视频流是连接世界的桥梁。对于那些渴望在Unity3d中搭建这座桥梁的开发者来说,本文将揭示如何在Windows、Android和iOS平台上征服RTSP与RTMP的秘密。我们将深入探讨这两种协议的特性,以及在不同平台上实现流畅播放的技巧。无论你是追求稳定性的RTSP拥趸,还是低延迟的RTMP忠实粉丝,这里都有你需要的答案。让我们一起穿越数字洪流,探索Unity3d中视频魔法的世界吧!
35 2
|
14天前
|
编解码 开发工具 数据安全/隐私保护
如何快速实现Windows平台屏幕摄像头采集并推送RTMP|轻量级RTSP服务能力?
一个好的推送模块,除了实现高效率的编码传输外,还要有好的音视频采集机制和灵活的架构支持,便于后期功能扩展,比如实时快照、预览、实时录像等。除此之外,还要有好的交互机制(比如envent callback)、低延迟和长期运行稳定的性能。
|
9天前
|
网络安全 虚拟化 Windows
windows 11安装openSSH server 遇到的"kex_exchange_identification: read: Connection reset"问题
windows 11安装openSSH server 遇到的"kex_exchange_identification: read: Connection reset"问题
|
19天前
|
PHP Windows
【Azure App Service for Windows】 PHP应用出现500 : The page cannot be displayed because an internal server error has occurred. 错误
【Azure App Service for Windows】 PHP应用出现500 : The page cannot be displayed because an internal server error has occurred. 错误
|
29天前
|
开发框架 .NET API
Windows Server 2022 安装IIS 报错 访问临时文件夹 C:\WINDOWS\TEMP\3C 读取/写入权限 错误: 0x80070005
Windows Server 2022 安装IIS 报错 访问临时文件夹 C:\WINDOWS\TEMP\3C 读取/写入权限 错误: 0x80070005
64 0