unity 接收拼接数据进行纹理替换且保存相机纹理到rtsp server(一)

news2025/5/14 21:30:12

1 rtsp 协议后编码解码

rtsp协议的问题就是,拼接完成后,还需要编码,而unity里面再需要解码,需要的过程多了一步编码再解码,大大加重了

2 rtsp 协议后轻量编码

rtsp协议使用mjpeg进行图片传输。why?这样做的好处是解码端进行像素处理以后不用再进行h264和h265编码,而unity端也不用再解码一次,这样增加了程序运行效率

3 server

1 rtsp server
2 websocket server

c# c++ 都可以做websocket server,
使用c++ 做一个server
server 既能接收推流,又能接收流

4 unity client

在这里插入图片描述
unity 上建立一个两个plane,调整位置

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
//using NetM
public class test : MonoBehaviour
{
    // Start is called before the first frame update
    private string msg = null;
    string[] file1Lines;
    void Start()
    {
        string path = Application.dataPath + "/rtsp.txt";
        
        path = path.Replace("/", "\\");
        //File静态类方式
        if (File.Exists(path))
        {
            Debug.Log("FileExists");
            file1Lines = File.ReadAllLines(path);
            foreach (string line in file1Lines)
            {
                Debug.Log(line);
            }
        }
        else
        {
            Debug.Log("FileNotExists");
            File.CreateText(path);
        }
   
        

        //Texture2D m_resultTure = new Texture2D((int)widthSize, (int)heighSize, TextureFormat.RGB24, false);
        //Texture2D m_resultTure = GetComponent(texture1);
        //texture rigidbody2D = GetComponent(texture1);
        //rawImage = GetComponent<RawImage>();
        //rawImage.texture = texture;
        //NetManager.M_Instance.Connect("ws://127.0.0.1:8888");   //本机地址
    }
    private void OnGUI()
    {
        //绘制输入框,以及获取输入框中的内容
        //PS:第二参数必须是msg,否则在我们输入后,虽然msg可以获得到输入内容,但马上就被第二参数在下一帧重新覆盖。
        msg = GUI.TextField(new Rect(10, 10, 100, 20), msg);

        //绘制按钮,以及按下发送信息按钮,发送信息
        if (GUI.Button(new Rect(120, 10, 80, 20), "发送信息") && msg != null)
        {
            NetManager.M_Instance.Send(msg);
        }

        //绘制按钮,以及按下断开连接按钮,发送断开连接请求
        if (GUI.Button(new Rect(210, 10, 80, 20), "断开连接"))
        {
            Debug.Log("向服务器请求断开连接......");
            NetManager.M_Instance.CloseClientWebSocket();
        }

    }

    void Update()
    {
        
    }
}

4.1 websocket client

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.WebSockets;
using System.Threading;
using System.Text;

public class NetManager
{
    #region 实现单例的代码
    //变量
    private volatile static NetManager m_instance;          
    private static object m_locker = new object();          

    //属性
    public static NetManager M_Instance
    {
        get
        {
            //线程锁。防止同时判断为null时同时创建对象
            lock (m_locker)
            {
                //如果不存在对象则创建对象
                if (m_instance == null)
                {
                    m_instance = new NetManager();
                }
            }
            return m_instance;
        }
    }
    #endregion

    //私有化构造
    private NetManager() { }

    //客户端webSocket
    private ClientWebSocket m_clientWebSocket;
    //处理接收数据的线程
    private Thread m_dataReceiveThread;
    //线程持续执行的标识符
    private bool m_isDoThread;
    private bool m_isConnected = false;

    /// <summary>
    /// ClientWebSocket,与服务器建立连接。
    /// </summary>
    /// <param name="uriStr"></param>
    public void Connect(string uriStr)
    {
        try
        {
            //创建ClientWebSocket
            m_clientWebSocket = new ClientWebSocket();

            //初始化标识符
            m_isDoThread = true;

            //创建线程
            m_dataReceiveThread = new Thread(ReceiveData);  //创建数据接收线程  
            m_dataReceiveThread.IsBackground = true;        //设置为后台可以运行,主线程关闭时,此线程也会关闭(实际在Unity中并没什么用,还是要手动关闭)

            //设置请求头部
            //m_clientWebSocket.Options.SetRequestHeader("headerName", "hearValue");

            //开始连接
            var task = m_clientWebSocket.ConnectAsync(new Uri(uriStr), CancellationToken.None);
            task.Wait();    //等待

            //启动数据接收线程
            m_dataReceiveThread.Start(m_clientWebSocket);

            //输出提示
            if (m_clientWebSocket.State == WebSocketState.Open)
            {
                Debug.Log("连接服务器完毕。");
                m_isConnected = true;
            }
        }
        catch (WebSocketException ex)
        {
            Debug.LogError("连接出错:" + ex.Message);
            Debug.LogError("WebSokcet状态:" + m_clientWebSocket.State);
            CloseClientWebSocket();
        }

    }


 
    /// <summary>
    /// 持续接收服务器的信息。
    /// </summary>
    /// <param name="socket"></param>
    private void ReceiveData(object socket)
    {
        //类型转换
        ClientWebSocket socketClient = (ClientWebSocket)socket;
        //持续接收信息
        while (m_isDoThread)
        {
            //接收数据
            string data = Receive(socketClient);
            //数据处理(可以和服务器一样使用事件(委托)来处理)
            if (data != null)
            {
                Debug.Log("接收的服务器消息:" + data);
            }
            //组装纹理
            //Texture2D tex2D = new Texture2D(100, 100);
            //tex2D.LoadImage(bytes);
         

        }
        Debug.Log("接收信息线程结束。");
    }

    /// <summary>
    /// 接收服务器信息。
    /// </summary>
    /// <param name="socket"></param>
    /// <returns></returns>
    private byte[] Receive(ClientWebSocket socket)
    {
        try
        {
            //接收消息时,对WebSocketState是有要求的,所以加上if判断(如果不是这两种状态,会报出异常)
            if (socket != null && (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseSent))
            {
                byte[] arrry = new byte[1024];  //注意长度,如果服务器发送消息过长,这也需要跟着调整
                ArraySegment<byte> buffer = new ArraySegment<byte>(arrry);  //实例化一个ArraySegment结构体
                //接收数据
                var task = socket.ReceiveAsync(buffer, CancellationToken.None);
                task.Wait();//等待

                //仅作状态展示。在客户端发送关闭消息后,服务器会回复确认信息,在收到确认信息后状态便是CloseReceived,这里打印输出。
                Debug.Log("socekt当前状态:" + socket.State);

                //如果是结束消息确认,则返回null,不再解析信息
                if (socket.State == WebSocketState.CloseReceived || task.Result.MessageType == WebSocketMessageType.Close)
                {
                    return null;
                }
                //如果是字符串
                //return Encoding.UTF8.GetString(buffer.Array, 0, task.Result.Count);
                return buffer.Array;
            }
            else
            {
                return null;
            }
        }
        catch (WebSocketException ex)
        {
            Debug.LogError("接收服务器信息错误:" + ex.Message);
            CloseClientWebSocket();
            return null;
        }
    }

    /// <summary>
    /// 发送消息
    /// </summary>
    /// <param name="content"></param>
    public void Send(string content)
    {
        try
        {
            //发送消息时,对WebSocketState是有要求的,加上if判断(如果不是这两种状态,会报出异常)
            if (m_clientWebSocket != null && (m_clientWebSocket.State == WebSocketState.Open || m_clientWebSocket.State == WebSocketState.CloseReceived))
            {
                ArraySegment<byte> array = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content)); //创建内容的字节编码数组并实例化一个ArraySegment结构体
                var task = m_clientWebSocket.SendAsync(array, WebSocketMessageType.Binary, true, CancellationToken.None);  //发送
                task.Wait();  //等待

                Debug.Log("发送了一个消息到服务器。");
            }
        }
        catch (WebSocketException ex)
        {
            Debug.LogError("向服务器发送信息错误:" + ex.Message);
            CloseClientWebSocket();
        }
    }

    /// <summary>
    /// 关闭ClientWebSocket。
    /// </summary>
    public void CloseClientWebSocket()
    {
        //关闭Socket
        if (m_clientWebSocket != null && m_clientWebSocket.State == WebSocketState.Open)
        {
            var task = m_clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
            Debug.Log("socekt状态:" + m_clientWebSocket.State);
            Debug.Log("close");
        }
        //关闭线程
        if (m_dataReceiveThread != null && m_dataReceiveThread.IsAlive)
        {
            m_isDoThread = false;   
            m_dataReceiveThread = null;
        }
    }
}

以上为websocket client, 接收到数据以后就可以替换纹理,接下去我们把里面的纹理图像保存出来,这样可以使用多个不同的camera 来保存不同角度和三维场景中不同的图像流。然后使用websocket 传输出去,形成一个rtsp server。
在这里插入图片描述

4.2 脚本start时候读取本地纹理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
public class canvasrtsp : MonoBehaviour
{
    // Start is called before the first frame update
    //public RawImage rawImage;//相机渲染的UI
    //public GameObject plane1;//相机渲染的GameObject

    //StartCoroutine(LoadTextureFromInternet("http://avatar.csdnimg.cn/1/E/6/2_u013012420.jpg"));

    //IEnumerator LoadTextureFromInternet(string path)
    //{
    //    UnityWebRequest request = new UnityWebRequest(path);
    //    //给request的downloadhandle赋值,new出来的UnityWebRequest不附加downloadhandle
    //    //数据,不赋值的话访问不到下载出来的数据
    //    DownloadHandlerTexture texture = new DownloadHandlerTexture(true);
    //    request.downloadHandler = texture;
    //    yield return request.Send();
    //    if (string.IsNullOrEmpty(request.error))
    //    {
    //        pic = texture.texture;
    //    }

    //    Image tempImage = GameObject.Find("Image").GetComponent<Image>();
    //    Sprite sp = Sprite.Create((Texture2D)pic, new Rect(0, 0, pic.width, pic.height), Vector2.zero);
    //    tempImage.sprite = sp;

    //    GameObject go = GameObject.Find("plane1");
    //    go.GetComponent<MeshRenderer>().material.mainTexture = pic;
    //}

    Texture2D Base64ToRGBA32(string imageData, int offset = 0)
    {
        Texture2D tex2D = new Texture2D(2, 2, TextureFormat.RGB24, false);
        imageData = imageData.Substring(offset);
        byte[] data = Convert.FromBase64String(imageData);
        tex2D.LoadImage(data);
        return tex2D;
    }
    void Start()
    {
        string path1 = @"F:\pic\b.png";
        FileStream fileStream = new FileStream(path1, FileMode.Open, FileAccess.Read);
        fileStream.Seek(0, SeekOrigin.Begin);
        byte[] bye = new byte[fileStream.Length];
        fileStream.Read(bye, 0, bye.Length);
        fileStream.Close();
        //创建texture
        Texture2D texture2D = new Texture2D(240, 144);
        texture2D.LoadImage(bye);
        //plane1.gameObject.SetActive(true);
        GameObject go = GameObject.Find("Plane");
        //GameObject go1 = GameObject.FindWithTag("ppp");
        GameObject go1 = GameObject.Find("Cube");
        //Debug.Log(texture2D.width, texture2D.height);
        go.GetComponent<MeshRenderer>().material.mainTexture = texture2D;
        go1.GetComponent<MeshRenderer>().material.mainTexture = texture2D;
        //T image = GetComponent("image1");
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

5 保存相机纹理

增加一个camera capture函数,这个函数将当前相机所见保存成图像

 Texture2D CaptureCamera(Camera camera, Rect rect)
    {
        RenderTexture rt = new RenderTexture((int)rect.width, (int)rect.height, 0);//创建一个RenderTexture对象
        camera.targetTexture = rt;//临时设置相关相机的targetTexture为rt, 并手动渲染相关相机
        camera.Render();
        //ps: --- 如果这样加上第二个相机,可以实现只截图某几个指定的相机一起看到的图像。
        //ps: camera2.targetTexture = rt;
        //ps: camera2.Render();
        //ps: -------------------------------------------------------------------

        RenderTexture.active = rt;//激活这个rt, 并从中中读取像素。
        Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24, false);
        screenShot.ReadPixels(rect, 0, 0);//注:这个时候,它是从RenderTexture.active中读取像素
        screenShot.Apply();

        //重置相关参数,以使用camera继续在屏幕上显示
        camera.targetTexture = null;
        //ps: camera2.targetTexture = null;
        RenderTexture.active = null; //JC: added to avoid errors
        GameObject.Destroy(rt);

        byte[] bytes = screenShot.EncodeToPNG();//最后将这些纹理数据,成一个png图片文件
        //string filename = Application.dataPath + "/Screenshot.png";
        string filename = "e:/qianbo2.png";
        System.IO.File.WriteAllBytes(filename, bytes);
        Debug.Log(string.Format("截屏了一张照片: {0}", filename));

        return screenShot;
    }

readpixel 方式

readpixel方式一定要注意等到 frameEnd 结束才能读取


 WaitForEndOfFrame frameEnd = new WaitForEndOfFrame();

    IEnumerator CaptureScreenshot2(Rect rect)
    {
        yield return frameEnd;
        Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24, false);//先创建一个的空纹理,大小可根据实现需要来设置
#pragma warning disable UNT0017 // SetPixels invocation is slow
        screenShot.ReadPixels(rect, 0, 0);//读取屏幕像素信息并存储为纹理数据,
#pragma warning restore UNT0017 // SetPixels invocation is slow
        screenShot.Apply();
        byte[] bytes = screenShot.EncodeToJPG();//然后将这些纹理数据,成一个png图片文件

        string filename = "e:/qianbo1.JPG";
        System.IO.File.WriteAllBytes(filename, bytes);
        Debug.Log(string.Format("截屏了一张图片: {0}", filename));
        //最后,我返回这个Texture2d对象,这样我们直接,所这个截图图示在游戏中,当然这个根据自己的需求的。
        //yield return screenShot;
    }

update

我们使用update 来测试我们的函数,按下空格,按下S,按下F,分别调用函数

 void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            int screenWidth = Screen.width;
            int screenHeight = Screen.height;
            RenderTexture rt = new RenderTexture(screenWidth, screenHeight, 24);
            Camera.main.targetTexture = rt;
            Camera.main.Render();
            RenderTexture.active = rt;//激活这个rt, 并从中中读取像素。
            Texture2D screenShot = new Texture2D(screenWidth, screenHeight, TextureFormat.RGB24, false);
            screenShot.ReadPixels(new Rect(0, 0, screenWidth, screenHeight), 0, 0);
            screenShot.Apply();
            byte[] bytes = screenShot.EncodeToJPG();
            //string path = Application.dataPath + "/../" + fileName + ".jpg";
            //string path = Application.dataPath + "/../" + fileName + ".jpg";
            string path = "e:/qianbo3.jpg" ;
            File.WriteAllBytes(path, bytes);
            Debug.Log(string.Format("space: {0}", path));
        }
        else if(Input.GetKeyDown(KeyCode.F))
        {
            
            Rect rect = new Rect(10, 10, 300, 200);
            CaptureScreenshot2(rect);
            //Debug.Log(string.Format("F: {0}", path));
        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
            mainCamera = GameObject.Find("Main Camera");

            Rect rect = new Rect(0, 0, 300, 200);
            //获取主摄像机的Camera组件
            //mainCamera= GameObject.Find("MainCamera").GetComponent<Camera>();
            //Camera ca = Camera.main;
            CaptureCamera(Camera.main, rect);
        }
    }

以下为存储到磁盘的图像
在这里插入图片描述

rtsp 方式传输

我们将使用rtsp方式传送抓取到的纹理图像,使用mjpeg over rtsp, 将jpeg 传送到server,由server提供rtsp 服务,或者http服务,使用c++来写这个server,这个由下一次再讲了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1004270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++信息学奥赛1170:计算2的N次方

#include <iostream> #include <string> #include <cstring>using namespace std;int main() {int n;cin >> n; // 输入一个整数nint arr[100];memset(arr, -1, sizeof(arr)); // 将数组arr的元素初始化为-1&#xff0c;sizeof(arr)表示arr数组的字节…

没有办法destory 一部分array

C Deleting part of dynamic array - Stack Overflow

C++之std::enable_shared_from_this实例(一百九十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

如何利用在线考试系统优化答题过程

随着技术的不断进步&#xff0c;越来越多的考试机构和教育机构开始采用在线考试系统进行评估和测验。使用在线考试系统可以极大地提升答题的效率和准确性。 准备阶段 在开始答题前&#xff0c;有一些准备工作可以帮助优化答题过程。 要仔细阅读考试要求和题目说明。这些说明…

JS中应该注意的点

本帖子记录在使用前端时遇到的一些小点。 1.html()和text()和val()的使用及区别 1.1 val() val&#xff08;&#xff09;是对于单标签元素的值&#xff0c;其中一个很重要的特性是value"" Value:<input id"input" type"text" value"LO…

postman导入json脚本文件(Collection v1.0、Collection v2.0)

1. 以postman v8.5.1 版本为例 2. 在postman v5.0.2 低版本中导出json脚本文件, 请选择Collection v2.0 Export - Collection v2 3. 在postman v8.5.1 版本 导入 json脚本文件 Import - Collection v2 - Export - Import

ROS rviz常用可视化插件

文章目录 ROS rviz常用可视化插件rviz常用可视化组件GridMapLaser ScanPointCloud2TFImagesPathMarker ROS rviz常用可视化插件 rviz RViz&#xff08;Robot Visualization&#xff09;是ROS&#xff08;Robot Operating System&#xff09;中的一个重要工具&#xff0c;用于…

常见的网络欺诈风险类型有哪些?

身份伪冒&#xff0c;这是非常典型的第三方欺诈&#xff0c;指的是不法分子使用虚假身份证等身份信息、未经他人同意而冒用他人身份获取贷款的骗贷行为。 另外还有帐号垃圾注册&#xff0c;通过大规模的帐号注册&#xff0c;养号养卡&#xff0c;控制帐号骗贷。此外还有中介包装…

原型链解释

一、什么是原型链 原型链是javascript中用来实现类似类继承的一套机制。像链条一样把javascript中的对象连接起来&#xff0c;实现类似子联系父的现象。 二、原型链的实现 总的来说&#xff0c;就是&#xff1a; 对象的__proto__指向其构造器的prototype对象&#xff0c;然后…

上半年实现营收9.24亿元,创新奇智的AI成制造业福星?

如今&#xff0c;AI大模型迈入了商业化落地的新阶段&#xff0c;并且已经有不少产品被不知不觉地应用到了生活各个方面。 其中&#xff0c;作为AI领域的后起之秀&#xff0c;创新奇智也于近日发布了截至2023年6月30日止六个月的中期业绩报告。数据显示&#xff0c;创新奇智202…

新建的SFTP用户,连接报错

useradd -m -d /usr/local/data/ftp/abc abc 新建SFTP用户 usermod -g sftpgroup -G sftpgroup abc 将abc加入到sftpgroup组 Received disconnect from 192.168.10.2 port 22:2: Too many authentication failures Disconnected from 192.168.10.2 port 22 Connection clos…

6、DVWA——SQL injection

文章目录 一、概述二、low2.1 通关思路&#xff08;1&#xff09;判断是否存在sql注入漏洞。&#xff08;2&#xff09;判断字符型还是数字型&#xff08;3&#xff09;判断字段数&#xff08;4&#xff09;查看回显点&#xff08;5&#xff09;查看数据库名&#xff08;6&…

R730xd风扇调速

共使用了三个方法都是有效的&#xff0c;dell_fans_controller_v1.0.0和Dell_EMC_Fans_Controller_1.0.1以及ipmitool&#xff0c;前面两个是GUI界面后面一个是命令行工具 重点 我虽然能通过设置的ip地址能访问idrac管理界面&#xff0c;但是使用上面三个工具都是无法获取风扇…

C++:模板(函数模板、类模板)

本文主要介绍泛型编程、函数模板和类模板。 目录 一、泛型编程 二、函数模板 1.函数模板概念 2.函数模板格式 3.函数模板的原理 4.函数模板的实例化 5.模板参数的匹配原则 三、类模板 1.类模板的定义格式 2.类模板的实例化 一、泛型编程 如何实现一个通用的交换函数…

普通的maven里面没有配置tomcat服务器问题

上面的意思也就是可以直接如下访问 他会直接给我们跳转到 webapp下面的index.jsp页面 为什么跳转到这个页面呢&#xff0c;原因在于我们在tomcat服务器里面的配置文件web.xml做了如下配置 只要webapp下面有如上几个页面&#xff0c;就会被默认运行 如果运行中出现控制台中文…

2023年8月京东彩瞳行业数据分析(京东商品数据)

和传统的框架眼镜、隐形眼镜相比&#xff0c;多种花纹、颜色的美瞳镜片给了爱美的年轻人更多的选择&#xff0c;因此&#xff0c;在颜值经济叠加悦己思潮的影响下&#xff0c;兼具“视力矫正美妆”的彩瞳受追捧。 根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;8月份&…

TorchAudio has CUDA version 11.7.

RuntimeError: Detected that PyTorch and TorchAudio were compiled with different CUDA versions. PyTorch has CUDA version 11.8 whereas TorchAudio has CUDA version 11.7. Please install the TorchAudio version that matches your PyTorch version.升级版本即可 pi…

HTML导航栏二级菜单(垂直、水平方向)

二级菜单是指主菜单的子菜单。菜单栏实际是一种树型结构&#xff0c;子菜单是菜单栏的一个分支。简单分享主要的垂直和水平方向的CSS设计。 垂直方向&#xff1a; HTML: <body><div><ul><li><a href"#">家用电器</a><ul>…

VRTK4⭐三.VRTK4 : 射线传送模块 [包含API传送]

文章目录 &#x1f7e5; 项目配置方法1️⃣ 添加相应模块2️⃣ 配置相关属性3️⃣ 体验一下吧 &#x1f7e7; 传送组件讲解&#x1f7e8; Locomotors.Teleporter.Dash : 缓动传送&#x1f7e9; API 传送示例 &#x1f7e5; 项目配置方法 1️⃣ 添加相应模块 我们要实现的功能…

10:STM32------I2C通信

目录​​​​​​​ 一:I2C通信协议 1:I2C简历 2:硬件电路 3:I2C时序基本单元 A : 开/ 终条件 2:发送一个字节 3:接收一个字节 4:应答机制 4:I2C时序 1:指定地址写 2:当前地址读 3: 指定地址读 二:MPU6050 1:简历 2:参数 3:硬件电路 4:框图 5:寄存器地址 …