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

​技术背景

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

技术实现

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

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

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

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

ini 复制代码
        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;
            }
        }

添加文字水印图层:

ini 复制代码
       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);
        }

投递图层数据:

ini 复制代码
        /*
         * 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);
        }

再说如何实现两个图层:

ini 复制代码
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));
}

如果需要更新图层位置:

ini 复制代码
        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);
        }

动态打开关闭图层:

arduino 复制代码
        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);
        }

总结

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

相关推荐
dvlinker20 小时前
【音视频开发】使用支持硬件加速的D3D11绘图遇到的绘图失败与绘图崩溃问题的记录与总结
音视频开发·c/c++·视频播放·d3d11·d3d11绘图模式
aqi004 天前
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
ffmpeg·音视频·直播·流媒体
音视频牛哥6 天前
Android平台GB28181实时回传流程和技术实现
音视频开发·视频编码·直播
音视频牛哥8 天前
RTMP、RTSP直播播放器的低延迟设计探讨
音视频开发·视频编码·直播
音视频牛哥12 天前
电脑共享同屏的几种方法分享
音视频开发·视频编码·直播
aqi0014 天前
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
android·ffmpeg·音视频·直播·流媒体
aqi0015 天前
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
android·ffmpeg·音视频·直播·流媒体
加油吧x青年16 天前
Web端开启直播技术方案分享
前端·webrtc·直播
aqi001 个月前
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
android·ffmpeg·音视频·直播·流媒体