Windows平台如何实现多路RTSP|RTMP流合成后录像或转发RTMP服务

本文涉及的产品
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,视频资源包5000点
简介: 本文介绍了在Windows平台上实现多路RTSP/RTMP视频流的合并技术。主要应用场景包括驾考、全景摄像头以及多路会议录制等。技术实现上,文章详细展示了如何使用特定的SDK来解码并回调YUV或RGB数据,再将这些数据按照图层形式进行合成。示例代码中给出了初始化参数、设置视频帧回调函数、以及如何配置不同图层的具体步骤。最终,合成后的视频可以推送到RTMP服务器、注入到本地RTSP服务,或是直接录制为MP4文件。此外,还提供了添加实时文字水印的方法,并展示了四路视频流合成后的“四宫格”效果。

技术背景

我们在对接Windows平台RTSP|RTMP直播播放模块的时候,有开发者提出来这样的技术需求,他们做驾考、全景摄像头、多路会议录制等场景的时候,希望把多路视频流数据,合并到一路保存或者对外推送到RTMP服务。

技术实现

多路RTSP|RTMP流合流,实际上我们2016年就有这块demo,当时合流的数据是本地采集的摄像头或屏幕数据,和外部RTSP、RTMP流,合成后输出(类似于传统意义的连麦操作)。这里大概说下思路,外部的RTSP|RTMP流数据,解码后,把YUV或RGB数据回调上来,然后,按照图层的形式,分别贴摄像头、屏幕数据或解码后的流数据。

本次以四路RTSP摄像头数据合流为例:

image.gif

开始播放:

/*
 * SmartPlayerDemo.cs
 * Author: daniusdk.com
 * QQ: 89030985
 */
private void btn_play1_Click(object sender, EventArgs e)
{
    if (btn_play1.Text == "播放")
    {
        String url = textBox_url1.Text;
        if (!InitCommonSDKParam(player1_handle_, url))
        {
            MessageBox.Show("设置参数错误!");
            return;
        }
        bool is_support_d3d_render = false;
        Int32 in_support_d3d_render = 0;
        if (NT.NTBaseCodeDefine.NT_ERC_OK == NTSmartPlayerSDK.NT_SP_IsSupportD3DRender(player1_handle_, playWnd1.Handle, ref in_support_d3d_render))
        {
            if (1 == in_support_d3d_render)
            {
                is_support_d3d_render = true;
            }
        }
        if (is_support_d3d_render)
        {
            // 支持d3d绘制的话,就用D3D绘制
            NTSmartPlayerSDK.NT_SP_SetRenderWindow(player1_handle_, playWnd1.Handle);
            if (btn_check_render_scale_mode.Checked)
                NTSmartPlayerSDK.NT_SP_SetRenderScaleMode(player1_handle_, 1);
            else
                NTSmartPlayerSDK.NT_SP_SetRenderScaleMode(player1_handle_, 0);
        }
        video_frame_call_back1_ = new SP_SDKVideoFrameCallBack(SetVideoFrameCallBack);
        NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(player1_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FROMAT_I420, IntPtr.Zero, video_frame_call_back1_);
        UInt32 ret_start = NTSmartPlayerSDK.NT_SP_StartPlay(player1_handle_);
        if (ret_start != 0)
        {
            MessageBox.Show("播放失败..");
            return;
        }
        btn_play1.Text = "停止";
    }
    else
    {
        StopPlayback1();
    }
}

image.gif

其中InitCommonSDKParam()主要完成一些初始化参数设置:

private bool InitCommonSDKParam(IntPtr handle, String url) {
    if (IntPtr.Zero == handle)
        return false;
    if (String.IsNullOrEmpty(url))
        return false;
    Int32 buffer_time = int.Parse(textBox_buffer_time.Text);
    NTSmartPlayerSDK.NT_SP_SetBuffer(handle, buffer_time);
    // 设置rtsp tcp模式,rtmp不使用, 可以不设置
    if (checkBox_rtsp_tcp.Checked)
        NTSmartPlayerSDK.NT_SP_SetRTSPTcpMode(handle, 1);
    else
        NTSmartPlayerSDK.NT_SP_SetRTSPTcpMode(handle, 0);
    //RTSP timeout设置
    Int32 rtsp_timeout = 10;
    NTSmartPlayerSDK.NT_SP_SetRtspTimeout(handle, rtsp_timeout);
    //RTSP TCP/UDP自动切换设置
    Int32 is_auto_switch_tcp_udp = 1;
    NTSmartPlayerSDK.NT_SP_SetRtspAutoSwitchTcpUdp(handle, is_auto_switch_tcp_udp);
    if (checkBox_mute.Checked)
        NTSmartPlayerSDK.NT_SP_SetMute(handle, 1);
    else
        NTSmartPlayerSDK.NT_SP_SetMute(handle, 0);
    if (checkBox_fast_startup.Checked)
        NTSmartPlayerSDK.NT_SP_SetFastStartup(handle, 1);
    else
        NTSmartPlayerSDK.NT_SP_SetFastStartup(handle, 0);
    if (checkBox_hardware_decoder.Checked)
    {
        NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(handle, is_support_h264_hardware_decoder_ ? 1 : 0, 0);
        NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(handle, is_support_h265_hardware_decoder_ ? 1 : 0, 0);
    }
    else
    {
        NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(handle, 0, 0);
        NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(handle, 0, 0);
    }
    // 设置是否只解码关键帧
    if (btn_check_only_decode_video_key_frame.Checked)
        NTSmartPlayerSDK.NT_SP_SetOnlyDecodeVideoKeyFrame(handle, 1);
    else
        NTSmartPlayerSDK.NT_SP_SetOnlyDecodeVideoKeyFrame(handle, 0);
    // 设置低延迟模式
    if (checkBox_low_latency.Checked)
        NTSmartPlayerSDK.NT_SP_SetLowLatencyMode(handle, 1);
    else
        NTSmartPlayerSDK.NT_SP_SetLowLatencyMode(handle, 0);
    NTSmartPlayerSDK.NT_SP_SetRotation(handle, rotate_degrees_);
    NTSmartPlayerSDK.NT_SP_SetAudioVolume(handle, slider_audio_volume.Value);
    NTSmartPlayerSDK.NT_SP_SetReportDownloadSpeed(handle, 1, 1);
    NTSmartPlayerSDK.NT_SP_SetURL(handle, url);
    return true;
}

image.gif

开始播放之前,我们设置YUV数据回调:

video_frame_call_back1_ = new SP_SDKVideoFrameCallBack(SetVideoFrameCallBack);
NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(player1_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FROMAT_I420, IntPtr.Zero, video_frame_call_back1_);

image.gif

回调处理如下,如果是多个图层,通过推送端,把yuv或rgb数据,投递给推送端,video frame数据回调,可以根据handle区分不同的图层或实例:

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) {
        int video_layer_index;
        if (handle == player_handle_)
            video_layer_index = publisher_wrapper_.get_external_video_layer0_index();
        else if (handle == player1_handle_)
            video_layer_index = publisher_wrapper_.get_external_video_layer1_index();
        else if (handle == player2_handle_)
            video_layer_index = publisher_wrapper_.get_external_video_layer2_index();
        else if (handle == player3_handle_)
            video_layer_index = publisher_wrapper_.get_external_video_layer3_index();
        else
            video_layer_index = -1;
        if (video_layer_index > -1)
        {
            publisher_wrapper_.post_i420_layer_image(video_layer_index, video_frame.plane0_, video_frame.stride0_, video_frame.plane1_, video_frame.stride1_,
                  video_frame.plane2_, video_frame.stride2_,
                  video_frame.width_, video_frame.height_);
        }
    }
}

image.gif

推送端,目前以四路合成为例,另外加个实时文字水印,图层设计如下:

public bool config_layers(bool is_add_rgbx_zero_layer)
{
    if (video_option_ != (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_LAYER)
        return false;
    if (is_empty_handle())
        return false;
    int w = video_width_;
    int h = video_height_;
    if ((w & 0x1) != 0)
        --w;
    if ((h & 0x1) != 0)
        --h;
    if (w < 2 || h < 2)
        return false;
    NTSmartPublisherSDK.NT_PB_ClearLayersConfig(handle_, 0, 0, IntPtr.Zero);
    int type, index = 0;
    if (is_add_rgbx_zero_layer)
    {
        NT_PB_RGBARectangleLayerConfig rgba_layer = new NT_PB_RGBARectangleLayerConfig();
        type = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_RGBA_RECTANGLE;
        fill_layer_base(rgba_layer, out rgba_layer.base_, type, index, true, 0, 0, w, h);
        rgba_layer.red_ = 0;
        rgba_layer.green_ = 0;
        rgba_layer.blue_ = 0;
        rgba_layer.alpha_ = 255;
        if (add_layer_config(rgba_layer, type))
            index++;
    }
    NT_PB_ExternalVideoFrameLayerConfig external_video_layer0 = new NT_PB_ExternalVideoFrameLayerConfig();
    type = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME;
    fill_layer_base(external_video_layer0, out external_video_layer0.base_, type, index, true, 0, 0, w/2, h/2);
    if (add_layer_config(external_video_layer0, type))
        external_video_layer0_index_ = index++;
    NT_PB_ExternalVideoFrameLayerConfig external_video_layer1 = new NT_PB_ExternalVideoFrameLayerConfig();
    type = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME;
    fill_layer_base(external_video_layer1, out external_video_layer1.base_, type, index, true, w / 2, 0, w / 2, h / 2);
    if (add_layer_config(external_video_layer1, type))
        external_video_layer1_index_ = index++;
    NT_PB_ExternalVideoFrameLayerConfig external_video_layer2 = new NT_PB_ExternalVideoFrameLayerConfig();
    type = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME;
    fill_layer_base(external_video_layer2, out external_video_layer2.base_, type, index, true, 0, h / 2, w / 2, h / 2);
    if (add_layer_config(external_video_layer2, type))
        external_video_layer2_index_ = index++;
    NT_PB_ExternalVideoFrameLayerConfig external_video_layer3 = new NT_PB_ExternalVideoFrameLayerConfig();
    type = (Int32)NTSmartPublisherDefine.NT_PB_E_LAYER_TYPE.NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME;
    fill_layer_base(external_video_layer3, out external_video_layer3.base_, type, index, true, w / 2, h / 2, w / 2, h / 2);
    if (add_layer_config(external_video_layer3, type))
        external_video_layer3_index_ = 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, false, w / 2, h / 2, 64, 64);
    if (add_layer_config(text_layer, type))
        text_layer_index_ = index++;
    return index > 0;
}

image.gif

合成后数据,可以对外推送到RTMP服务,也可以注入到本地RTSP服务,或者本地直接录制MP4文件,录制出来四宫格效果如下:

image.gif

总结

多路RTSP|RTMP数据合流,在多媒体处理、实时监控、驾考、教育等各个行业,应用非常广泛,除了视频外,音频如果需要合成,可以以采集系统扬声器的形式合流出来。多路合流,可以事先做好排版编辑,如果期间不希望显示某一路数据,可以点隐藏图层,实时对图层进行隐藏。感兴趣的开发者,可以单独跟我沟通交流。

目录
打赏
0
0
0
0
69
分享
相关文章
Docker Desktop 4.38 安装与配置全流程指南(Windows平台)
Docker Desktop 是容器化应用开发与部署的一体化工具,支持本地创建、管理和运行 Docker 容器。4.38 版本新增 GPU 加速、WSL 2 性能优化和 Kubernetes 1.28 集群管理功能,适用于微服务开发和 CI/CD 流水线搭建。安装要求为 Windows 10 2004 及以上(64 位),需启用 Hyper-V 或 WSL 2。硬件最低配置为 4GB 内存、20GB 存储和虚拟化技术支持的 CPU。安装步骤包括启用系统功能、下载并运行安装程序,完成后配置镜像加速并验证功能。常见问题涵盖 WSL 2 安装不完整、磁盘空间清理及容器外网访问等。
1055 13
Windows平台GIMP 2.10下载教程:零基础入门高级图像编辑
GIMP(GNU Image Manipulation Program)是一款开源跨平台图像编辑工具,支持图层管理、高级修图、色彩校正等功能,广泛应用于平面设计和照片修复。其优势包括全功能免费、插件生态丰富(600+扩展插件)、硬件要求低(1GB内存即可流畅运行)。本文详细介绍GIMP的软件定位、安装流程、首次配置及常见问题解答,帮助用户快速上手并充分利用其强大功能。
|
21天前
|
Gitea Enterprise 23.4.0 (Linux, macOS, Windows) - 本地部署的企业级 Gti 服务
Gitea Enterprise 23.4.0 (Linux, macOS, Windows) - 本地部署的企业级 Gti 服务
37 0
Gitea Enterprise 23.4.0 (Linux, macOS, Windows) - 本地部署的企业级 Gti 服务
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
204 2
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
Windows server 2012R2系统安装远程桌面服务后无法多用户同时登录是什么原因?
【11月更文挑战第15天】本文介绍了在Windows Server 2012 R2中遇到的多用户无法同时登录远程桌面的问题及其解决方法,包括许可模式限制、组策略配置问题、远程桌面服务配置错误以及网络和防火墙问题四个方面的原因分析及对应的解决方案。
289 4
|
3天前
|
Windows Server 2025 中文版、英文版下载 (2025 年 3 月更新)
Windows Server 2025 中文版、英文版下载 (2025 年 3 月更新)
39 4
Windows Server 2025 中文版、英文版下载 (2025 年 3 月更新)
Windows Server 2022 中文版、英文版下载 (2025 年 3 月更新)
Windows Server 2022 中文版、英文版下载 (2025 年 3 月更新)
28 4
Windows Server 2022 中文版、英文版下载 (2025 年 3 月更新)
|
1月前
|
Windows 7 & Windows Server 2008 R2 简体中文版下载 (2025 年 2 月更新)
Windows 7 & Windows Server 2008 R2 简体中文版下载 (2025 年 2 月更新)
60 11
Windows 7 & Windows Server 2008 R2 简体中文版下载 (2025 年 2 月更新)
|
1月前
|
Windows Server 2025 中文版、英文版下载 (2025 年 2 月更新)
Windows Server 2025 中文版、英文版下载 (2025 年 2 月更新)
90 7
Windows Server 2025 中文版、英文版下载 (2025 年 2 月更新)
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等