同一路RTSP|RTMP流如何同时回调YUV和RGB数据实现渲染和算法分析

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
简介: 我们播放RTSP|RTMP流,如果需要同时做渲染和算法分析的话,特别是渲染在上层实现(比如Unity),算法是python这种情况,拉两路流,更耗费带宽和性能,拉一路流,同时回调YUV和RGB数据也可以,但是更灵活的是本文提到的按需转算法期望的RGB数据,然后做算法处理

技术背景

我们在做RTSP|RTMP播放器的时候,有这样的技术诉求,开发者希望同时回调YUV、RGB数据,特别是Unity场景下,YUV数据用于渲染,RGB数据用于做视觉算法分析,拿到的RGB数据,想办法和python通信,发给python做视觉算法处理。

一般来说,如果设备带宽和性能比较好的话,可以直接拉两路流,同时解码回调需要的数据,当然,一般是不建议这么做,特别是4K+分辨率的流,同时解两路,耗费性能,没有必要。

另外一种,可以修改播放器底层逻辑,实现同时回调YUV和RGB数据,但是,我们知道,大多场景,RGB数据做算法分析的话,不一定需要全帧和高分辨率,考虑到算法处理能力,比如,有可能一秒钟只需要处理5-10帧,而且,有的视觉算法对高分辨率数据支持不好(比如分析4K的)。

那么,比较好的方式是,回调YUV数据,然后,RGB数据,提供上层接口,按需转,转过后的RGB数据,发给python或者其他算法就好。

技术实现

基于上述场景,我们做了以下的方案:

image.gif

1. 设置回调YUV数据:

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

image.gif

2. 如果是做YUV到RGB数据转换:

/*
 * SmartPlayer.cs
 * Author: https://daniusdk.com
 * WeChat: xinsheng120
 */
rgb_frame.format_ = (int)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_ARGB;
rgb_frame.width_ = video_frame.width_;
rgb_frame.height_ = video_frame.height_;
rgb_frame.timestamp_ = video_frame.timestamp_;
rgb_frame.stride0_ = video_frame.width_ * 4;
rgb_frame.stride1_ = 0;
rgb_frame.stride2_ = 0;
rgb_frame.stride3_ = 0;
Int32 argb_size = rgb_frame.stride0_ * rgb_frame.height_;
rgb_frame.plane0_ = Marshal.AllocHGlobal(argb_size);
NTSmartPlayerSDK.NT_SP_I420ToARGB(video_frame.plane0_, video_frame.stride0_, video_frame.plane1_, video_frame.stride1_, video_frame.plane2_, video_frame.stride2_,
   rgb_frame.plane0_, rgb_frame.stride0_, video_frame.width_, video_frame.height_);

image.gif

3. 这里可以看到,我们设计了I420到ARGB的接口:

/*
 * smart_player_sdk.cs
 * Author: https://daniusdk.com
 * WeChat: xinsheng120
 */
[DllImport(@"SmartPlayerSDK.dll")]
public static extern UInt32 NT_SP_I420ToARGB(IntPtr src_y_plane, Int32 src_y_stride,
    IntPtr src_u_plane, Int32 src_u_stride,
    IntPtr src_v_plane, Int32 src_v_stride,
    IntPtr dst_argb_plane, Int32 dst_argb_stride,
    Int32 width, Int32 height);

image.gif

好多算法,对高分辨率数据支持并不友好,于是我们做了分辨率缩放接口,拿到yuv数据后,先做缩放,然后再做yuv到rgb的转换。

/*
 * SmartPlayer.cs
 * Author: https://daniusdk.com
 * WeChat: xinsheng120
 */
 
int scale_width = 1280;
int scale_height = 720;
int scale_y_stride = scale_width;
int scale_u_stride = (scale_width + 1) / 2;
int scale_v_stride = scale_u_stride;
int scale_y_size = scale_y_stride * scale_height;
int scale_u_size = scale_u_stride * ((scale_height + 1) / 2);
int scale_v_size = scale_u_size;
NT_SP_VideoFrame scale_frame = new NT_SP_VideoFrame();
scale_frame.width_ = scale_width;
scale_frame.height_ = scale_height;
scale_frame.plane0_ = Marshal.AllocHGlobal(scale_y_size);
scale_frame.plane1_ = Marshal.AllocHGlobal(scale_u_size);
scale_frame.plane2_ = Marshal.AllocHGlobal(scale_v_size);
NTSmartPlayerSDK.NT_SP_I420Scale(video_frame.plane0_, video_frame.stride0_,
    video_frame.plane1_, video_frame.stride1_,
    video_frame.plane2_, video_frame.stride2_,
    video_frame.width_, video_frame.height_,
    scale_frame.plane0_, scale_y_stride,
    scale_frame.plane1_, scale_u_stride,
    scale_frame.plane2_, scale_v_stride,
    scale_frame.width_, scale_frame.height_, 3);
rgb_frame.format_ = (int)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_ARGB;
rgb_frame.width_ = scale_frame.width_;
rgb_frame.height_ = scale_frame.height_;
rgb_frame.stride0_ = scale_frame.width_ * 4;
rgb_frame.stride1_ = 0;
rgb_frame.stride2_ = 0;
rgb_frame.stride3_ = 0;
Int32 argb_size = rgb_frame.stride0_ * rgb_frame.height_;
rgb_frame.plane0_ = Marshal.AllocHGlobal(argb_size);
NTSmartPlayerSDK.NT_SP_I420ToARGB(scale_frame.plane0_, scale_y_stride, scale_frame.plane1_, scale_u_stride, scale_frame.plane2_, scale_v_stride,
    rgb_frame.plane0_, rgb_frame.stride0_, scale_frame.width_, scale_frame.height_);

image.gif

有了rgb数据,下一步,就是如何跟python通信的问题,在此之前,我们有专门写过一篇blog,跟python交互,有多种方式,比如共享内存、通过UDP发送或者写bitmap文件,然后python实时读取就好。以写bitmap为例(Linux demo),开始播放后,video frame数据回调处理如下:

extern "C" void NT_SDK_SDKVideoFrameCallBack(NT_HANDLE handle, NT_PVOID user_data, NT_UINT32 status,
    const NT_SP_VideoFrame* frame)
{
    if (!frame)
        return;
    fprintf(stdout, "OnSDKVideoFrameCallBack handle:%p frame:%p, timestamp:%llu\n", handle, frame, frame->timestamp_);
#if NEED_SAVE_BITMAP
    if (NT_SP_E_VIDEO_FRAME_FORMAT_RGB32 == frame->format_
        || NT_SP_E_VIDEO_FRAME_FORMAT_ARGB == frame->format_) {
        struct timeval tv;
        if (gettimeofday(&tv, nullptr) != 0) {
            fprintf(stderr, "save bitmap file call gettimeofday failed");
            return;
        }
        uint64_t local_time_us = tv.tv_sec*UINT64_C(1000000) + tv.tv_usec;
        char file_name[128] = { 0 };
        sprintf(file_name, "./outbitmaps/%llu.bmp", (unsigned long long)local_time_us);
        if (!save_bitmap_file(frame->width_, frame->height_, frame->plane0_, frame->stride0_, frame->stride0_*frame->height_, file_name))
            fprintf(stderr, "save bitmap file failed, name:%s", file_name);
        else
            g_bitmap_file_names_.emplace_back(file_name);
        while (g_bitmap_file_names_.size() > 32) {
            remove(g_bitmap_file_names_.front().c_str());
            g_bitmap_file_names_.pop_front();
        }
    }
#endif // NEED_SAVE_BITMAP
}

image.gif

video frame回调后的数据,直接调用save_bitmap_file()实现bitmap文件写入,python程序,只需要到指定的文件夹下,读取生成的bitmap,然后做算法分析即可。拿到rgb数据,当然也可以通过共享内存或UDP直接发出去。

总结

我们播放RTSP|RTMP流,如果需要同时做渲染和算法分析的话,特别是渲染在上层实现(比如Unity),算法是python这种情况,拉两路流,更耗费带宽和性能,拉一路流,同时回调YUV和RGB数据也可以,但是更灵活的是本文提到的按需转算法期望的RGB数据,然后做算法处理。以上是大概的逻辑实现,感兴趣的开发者,可以单独跟我交流。

相关文章
|
1月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
57 4
|
13天前
|
存储 编解码 负载均衡
数据分片算法
【10月更文挑战第25天】不同的数据分片算法适用于不同的应用场景和数据特点,在实际应用中,需要根据具体的业务需求、数据分布情况、系统性能要求等因素综合考虑,选择合适的数据分片算法,以实现数据的高效存储、查询和处理。
|
13天前
|
存储 缓存 算法
分布式缓存有哪些常用的数据分片算法?
【10月更文挑战第25天】在实际应用中,需要根据具体的业务需求、数据特征以及系统的可扩展性要求等因素综合考虑,选择合适的数据分片算法,以实现分布式缓存的高效运行和数据的合理分布。
|
19天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
26天前
|
机器学习/深度学习 人工智能 算法
"拥抱AI规模化浪潮:从数据到算法,解锁未来无限可能,你准备好迎接这场技术革命了吗?"
【10月更文挑战第14天】本文探讨了AI规模化的重要性和挑战,涵盖数据、算法、算力和应用场景等方面。通过使用Python和TensorFlow的示例代码,展示了如何训练并应用一个基本的AI模型进行图像分类,强调了AI规模化在各行业的广泛应用前景。
29 5
|
18天前
|
存储 JSON 算法
TDengine 检测数据最佳压缩算法工具,助你一键找出最优压缩方案
在使用 TDengine 存储时序数据时,压缩数据以节省磁盘空间是至关重要的。TDengine 支持用户根据自身数据特性灵活指定压缩算法,从而实现更高效的存储。然而,如何选择最合适的压缩算法,才能最大限度地降低存储开销?为了解决这一问题,我们特别推出了一个实用工具,帮助用户快速判断并选择最适合其数据特征的压缩算法。
27 0
|
25天前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
28天前
|
人工智能 算法 前端开发
无界批发零售定义及无界AI算法,打破传统壁垒,累积数据流量
“无界批发与零售”是一种结合了批发与零售的商业模式,通过后端逻辑、数据库设计和前端用户界面实现。该模式支持用户注册、登录、商品管理、订单处理、批发与零售功能,并根据用户行为计算信用等级,确保交易安全与高效。
|
28天前
|
前端开发 算法 JavaScript
无界SaaS模式深度解析:算力算法、链接力、数据确权制度
私域电商的无界SaaS模式涉及后端开发、前端开发、数据库设计、API接口、区块链技术、支付和身份验证系统等多个技术领域。本文通过简化框架和示例代码,指导如何将核心功能转化为技术实现,涵盖用户管理、企业店铺管理、数据流量管理等关键环节。
|
1月前
|
算法
PID算法原理分析及优化
【10月更文挑战第6天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。

热门文章

最新文章