Unity下如何实现低延迟的全景RTMP|RTSP流渲染
技术背景
Unity3D可以用于创建各种类型的的应用程序,包括虚拟现实、培训模拟器等。以下是一些可以使用Unity3D全景播放的场景:
- 虚拟现实体验:全景视频可以用来创建逼真的虚拟环境,使用户能够感受到身临其境的感觉;
- 培训模拟器:全景视频可以用来创建真实的训练环境,例如飞行模拟器、驾驶模拟器等,以提供更加真实的训练体验;
- 建筑设计:全景视频可以用来展示建筑设计的或室内装潢,使客户能够感受到真实的的效果;
- 文旅导览:全景视频可以用来展示旅游景点或城市,使游客能够感受到身临其境的感觉。
在Unity3D平台上实现全景实时RTMP或RTSP流渲染,可以通过以下方式:
- 获取全景视频数据源:首先,需要拉取RTMP或RTSP流数据,解码后,把RGB或YUV数据,回调到unity,从而获取到全景视频流数据;
- Unity创建个Sphere,创建个材质球(Material),并把材质球挂在到Sphere;
- 实现实时渲染:使用Unity3D的渲染管道,您可以将纹理映射到球体或立方体的表面上,并使用着色器来处理纹理的坐标,以实现全景视频的实时渲染。
技术实现

本文以大牛直播SDK的RTMP推送端作为数据采集,获取全景窗体数据后,编码打包推送到RTMP服务,或启动个轻量级RTSP服务,对外提供个RTSP的拉流URL。
然后,播放端,拉取RTSP或RTMP的URL,把YUV或RGB数据回调上来,然后,再在Unity窗体绘制出来。
获取数据源:
    public void Play(int sel){if (videoctrl[sel].is_running){Debug.Log("已经在播放..");return;}lock (videoctrl[sel].frame_lock_){videoctrl[sel].cur_video_frame_ = null;}OpenPlayer(sel);if (videoctrl[sel].player_handle_ == IntPtr.Zero)return;//设置播放URLNTSmartPlayerSDK.NT_SP_SetURL(videoctrl[sel].player_handle_, videoctrl[sel].videoUrl);/* ++ 播放前参数配置可加在此处 ++ */int play_buffer_time_ = 0;NTSmartPlayerSDK.NT_SP_SetBuffer(videoctrl[sel].player_handle_, play_buffer_time_);                 //设置buffer timeint is_using_tcp = 0;        //TCP模式NTSmartPlayerSDK.NT_SP_SetRTSPTcpMode(videoctrl[sel].player_handle_, is_using_tcp);int timeout = 10;NTSmartPlayerSDK.NT_SP_SetRtspTimeout(videoctrl[sel].player_handle_, timeout);int is_auto_switch_tcp_udp = 1;NTSmartPlayerSDK.NT_SP_SetRtspAutoSwitchTcpUdp(videoctrl[sel].player_handle_, is_auto_switch_tcp_udp);Boolean is_mute_ = false;NTSmartPlayerSDK.NT_SP_SetMute(videoctrl[sel].player_handle_, is_mute_ ? 1 : 0);                    //是否启动播放的时候静音int is_fast_startup = 1;NTSmartPlayerSDK.NT_SP_SetFastStartup(videoctrl[sel].player_handle_, is_fast_startup);              //设置快速启动模式Boolean is_low_latency_ = false;NTSmartPlayerSDK.NT_SP_SetLowLatencyMode(videoctrl[sel].player_handle_, is_low_latency_ ? 1 : 0);    //设置是否启用低延迟模式//设置旋转角度(设置0, 90, 180, 270度有效,其他值无效)int rotate_degrees = 0;NTSmartPlayerSDK.NT_SP_SetRotation(videoctrl[sel].player_handle_, rotate_degrees);int volume = 100;NTSmartPlayerSDK.NT_SP_SetAudioVolume(videoctrl[sel].player_handle_, volume);	//设置播放音量, 范围是[0, 100], 0是静音,100是最大音量, 默认是100// 设置上传下载报速度int is_report = 0;int report_interval = 2;NTSmartPlayerSDK.NT_SP_SetReportDownloadSpeed(videoctrl[sel].player_handle_, is_report, report_interval);/* -- 播放前参数配置可加在此处 -- *///video frame callback (YUV/RGB)videoctrl[sel].video_frame_call_back_ = new SP_SDKVideoFrameCallBack(NT_SP_SetVideoFrameCallBack);NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(videoctrl[sel].player_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FROMAT_I420, window_handle_, videoctrl[sel].video_frame_call_back_);UInt32 flag = NTSmartPlayerSDK.NT_SP_StartPlay(videoctrl[sel].player_handle_);if (flag == DANIULIVE_RETURN_OK){videoctrl[sel].is_need_get_frame_ = true;Debug.Log("播放成功");}else{videoctrl[sel].is_need_get_frame_ = false;Debug.LogError("播放失败");}videoctrl[sel].is_running = true;}针对数据处理:
    private void SDKVideoFrameCallBack(UInt32 status, IntPtr frame, int sel){//这里拿到回调frame,进行相关操作NT_SP_VideoFrame video_frame = (NT_SP_VideoFrame)Marshal.PtrToStructure(frame, typeof(NT_SP_VideoFrame));VideoFrame  u3d_frame = new VideoFrame();u3d_frame.width_  = video_frame.width_;u3d_frame.height_ = video_frame.height_;u3d_frame.timestamp_ = (UInt64)video_frame.timestamp_;int d_y_stride = video_frame.width_;int d_u_stride = (video_frame.width_ + 1) / 2;int d_v_stride = d_u_stride;int d_y_size = d_y_stride * video_frame.height_;int d_u_size = d_u_stride * ((video_frame.height_ + 1) / 2);int d_v_size = d_u_size;int u_v_height = ((u3d_frame.height_ + 1) / 2);u3d_frame.y_stride_ = d_y_stride;u3d_frame.u_stride_ = d_u_stride;u3d_frame.v_stride_ = d_v_stride;u3d_frame.y_data_ = new byte[d_y_size];u3d_frame.u_data_ = new byte[d_u_size];u3d_frame.v_data_ = new byte[d_v_size];CopyFramePlane(u3d_frame.y_data_, d_y_stride,video_frame.plane0_, video_frame.stride0_, u3d_frame.height_);CopyFramePlane(u3d_frame.u_data_, d_u_stride,video_frame.plane1_, video_frame.stride1_, u_v_height);CopyFramePlane(u3d_frame.v_data_, d_v_stride,video_frame.plane2_, video_frame.stride2_, u_v_height);lock (videoctrl[sel].frame_lock_ ){videoctrl[sel].cur_video_frame_ = u3d_frame;}}刷新Texture:
private void UpdateYUVTexture(VideoFrame video_frame, int sel)
{if (video_frame.y_data_ == null || video_frame.u_data_ == null || video_frame.v_data_ == null){Debug.Log("video frame with null..");return;}if (videoctrl[sel].yTexture_ != null){videoctrl[sel].yTexture_.LoadRawTextureData(video_frame.y_data_);videoctrl[sel].yTexture_.Apply();}if (videoctrl[sel].uTexture_ != null){videoctrl[sel].uTexture_.LoadRawTextureData(video_frame.u_data_);videoctrl[sel].uTexture_.Apply();}if (videoctrl[sel].vTexture_ != null){videoctrl[sel].vTexture_.LoadRawTextureData(video_frame.v_data_);videoctrl[sel].vTexture_.Apply();}
}全景播放的时候,如果需要移动显示区域,可以用以下代码:
public class MouseMove : MonoBehaviour {Vector2 p1, p2;private Vector3 PreMouseMPos;private Vector3 PreMouseLPos;public float minimumY = -60F;public float maximumY = 60F;float rotationY = 0F;private float wheelSpeed = 5.0f;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {if (Input.GetMouseButton(0)){if (PreMouseLPos.x <= 0){PreMouseLPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.0f);}else{Vector3 CurMouseLPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.0f);Vector3 offset = CurMouseLPos - PreMouseLPos;Quaternion tt = Quaternion.Euler(offset);float rotationX = transform.localEulerAngles.y - tt.x * 20;rotationY += tt.y * 20;rotationY = Mathf.Clamp(rotationY, minimumY, maximumY);transform.localEulerAngles = new Vector3(rotationY, rotationX, 0);PreMouseLPos = CurMouseLPos;}}else{PreMouseLPos = new Vector3(0.0f, 0.0f, 0.0f);}}
}
总结
Unity全景播放RTMP或RTSP实时流,可以广泛用于各种需要提供真实场景或沉浸式体验的场景,为用户带来更加逼真的体验。与此同时,Unity全景实时播放,需要有非常高的延迟要求和性能要求,特别是全景数据源,分辨率和码率都非常高,对解码效率和解码后的数据拷贝投递,提了更高的要求。
相关文章:
 
Unity下如何实现低延迟的全景RTMP|RTSP流渲染
技术背景 Unity3D可以用于创建各种类型的的应用程序,包括虚拟现实、培训模拟器等。以下是一些可以使用Unity3D全景播放的场景: 虚拟现实体验:全景视频可以用来创建逼真的虚拟环境,使用户能够感受到身临其境的感觉;培…...
 
STM32 USB使用记录:HID类设备(后篇)
文章目录 目的基础说明项目构建与代码调整接收发送代码与测试示例链接报告描述符总结 目的 接上篇: 《STM32 USB使用记录:HID类设备(前篇)》 USB HID 类的设备有个比较大的好处是大部分时候接入主机中都是可以免驱使用的。这篇文…...
 
C# 快速写入日志 不卡线程 生产者 消费者模式
有这样一种场景需求,就是某个方法,对耗时要求很高,但是又要记录日志到数据库便于分析,由于访问数据库基本都要几十毫秒,可在方法里写入BlockingCollection,由另外的线程写入数据库。 可以看到,在…...
Pandas将对角线元素设为1
Pandas将对角线元素设为1 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 入门之pandas的使用 提示:np.fill_diagonal(df.values, 1)的用法 Pandas将对角线元素设为1 Pandas将对角线元素设为…...
WPF实战学习笔记28-登录界面
添加登录界面UI 添加文件loginview.xaml。注意本界面使用的是md内的图标。没有登录界面的图片 <UserControlx:Class"Mytodo.Views.LoginView"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsof…...
01背包
动态规划解题步骤: 动态规划问题,一般从三个步骤进行考虑。 步骤一:集合及集合的状态。 所谓的集合,就是一些方案的集合。 用 g[i][j] 表示从前 i 种物品中进行选择,且总体积不大于 j 的各个选法获得的价值的集合。注意&#…...
 
064、故障处理之OMM_TiDB
oom 内存溢出,内存泄漏,相当于TiDB不能用了 TiDB Server OOM对业务的影响 TiDB Server上的业务SQL会失败业务响应时间升高前端体验变差 诊断方法 客户端应用 ERROR 2013(HY000): Lost connection to MySQL Server during query日志 dmesg -T | gr…...
 
网络设备中的配置文件管理
建立强大网络的第一步是为灾难和网络中断做好准备,许多企业在中断期间遭受损失,因为他们缺乏备份计划并且配置管理不达标,使用配置文件管理工具进行适当的配置文件管理不仅有助于处理网络中断,还有助于优化网络性能。 使用配置文…...
 
HCIP BGP综合实验
题目 1、AS1存在两个环回,一个地址为192.168.1.0/24该地址不能在任何协议中宣告; 2、AS3中存在两个环回,一个地址为192.168.2.0/24该地址不能在任何协议中宣告,最终要求这两个环回可以互相通讯; 3、AS间的骨干链路I…...
 
【mysql学习篇】Order by与Group by优化以及排序算法详解
一、Order by与Group by优化 Case1: 分析: 利用最左前缀法则:中间字段不能断,因此查询用到了name索引,从key_len74也能看出,age索引列用在排序过程中,因为Extra字段里没有using filesort 注意…...
 
【业务功能篇60】Springboot + Spring Security 权限管理 【终篇】
4.4.7 权限校验扩展 4.4.7.1 PreAuthorize注解中的其他方法 hasAuthority:检查调用者是否具有指定的权限; RequestMapping("/hello")PreAuthorize("hasAuthority(system:user:list)")public String hello(){return "hello Sp…...
 
文章详情页 - 评论功能的实现
目录 1. 准备工作 1.1 创建评论表 1.2 创建评论实体类 1.3 创建 mapper 层评论接口和对应的 xml 实现 1.4 准备评论的 service 层 1.5 准备评论的 controller 层 2. 总的初始化详情页 2.1 加载评论列表 2.1.1 实现前端代码 2.1.2 实现后端代码 2.2 查询当前登录用户的…...
 
使用贝叶斯滤波器通过运动模型和嘈杂的墙壁传感器定位机器人研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
 
Day 69-70:矩阵分解
代码: package dl;import java.io.*; import java.util.Random;/** Matrix factorization for recommender systems.*/public class MatrixFactorization {/*** Used to generate random numbers.*/Random rand new Random();/*** Number of users.*/int numUsers…...
 
数据结构:树的存储结构
学习树之前,我们已经了解了二叉树的顺序存储和链式存储,哪么我们如何来存储普通型的树结构的数据?如下图1: 如图1所示,这是一颗普通的树,我们要如何来存储呢?通常,存储这种树结构的数…...
 
Vue前端渲染blob二进制对象图片的方法
近期做开发,联调接口。接口返回的是一张图片,是对二进制图片处理并渲染,特此记录一下。 本文章是转载文章,原文章:Vue前端处理blob二进制对象图片的方法 接口response是下图 显然,获取到的是一堆乱码&…...
 
Java的标记接口(Marker Interface)
Java中的标记接口(Marker Interface)是一个空接口,接口内什么也没有定义。它标识了一种能力,标识继承自该接口的接口、实现了此接口的类具有某种能力。 例如,jdk的com.sun.org.apache.xalan.internal.xsltc.trax.Temp…...
 
Kafka基础架构与核心概念
Kafka简介 Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。架构特点是分区、多副本、多生产者、多订阅者,性能特点主要是…...
 
观察者模式与观察者模式实例EventBus
什么是观察者模式 顾名思义,观察者模式就是在多个对象之间,定义一个一对多的依赖,当一个对象状态改变时,所有依赖这个对象的对象都会自动收到通知。 观察者模式也称为发布订阅模式(Publish-Subscribe Design Pattern)࿰…...
 
科普 | OSI模型
本文简要地介绍 OSI 模型 1’ 2’ 3。 更新:2023 / 7 / 23 科普 | OSI模型 术语节点链路协议网络拓扑 概念作用结构应用层表示层会话层传输层网络层数据链路层物理层 数据如何流动OSI 和TCP/IP 的对应关系和协议参考链接 术语 节点 节点( Node &#…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
 
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
 
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
 
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
 
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
 
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
 
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
 
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
