jjzjj

c# - 统一 : Live Video Streaming

coder 2024-05-29 原文

我正在尝试将实时视频从一个应用程序流式传输到另一个应用程序,目前我有 2 个应用程序。 app 1 是服务器/发送者,app 2 是客户端/接收者。在应用程序 1 中,我成功地将视频字节发送到客户端。在客户端,我也收到了所有的字节。我使用套接字和 TCP。我面临的问题是,当我收到视频字节并将它们分配给原始图像纹理时,纹理上的图像看起来放大太多并且像素化。

更新图片



这是我的流


这就是我在客户端上得到的。


这是第一个问题,但是我目前正在从台式机到另一个进行测试,我的目标是将 IPAD 流式传输到台式机,当我这样做时,速度很慢,并且会同时杀死 ipad 和台式机上的应用程序。

到目前为止我尝试过的一些故障排除。

1:我认为这是因为我有 2 种不同的分辨率,因为我从 ipad 流式传输到桌面

2:纹理图像太大,我输出它并返回630。我尝试使用Unity Texture2D.resize调整它的大小但我得到灰色纹理,因为该函数将像素设置为未识别

3:我使用其他库来调整纹理大小,我确实得到了我想要的,但是在 12 帧之后,原始图像开始在视频和“?”之间闪烁。纹理太多,然后它在两个应用程序(ipad 和桌面)上都卡住了

4:我相信我阅读纹理的方式是导致问题的原因,因为我同时使用了 Setpixels 和 Getpixels 函数,而且它们很重。

我的代码:
服务器/发送方:

using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.UI;
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;

public class Connecting : MonoBehaviour
{
WebCamTexture webCam;
public RawImage myImage;
Texture2D currentTexture;

private TcpListener listner;
private const int port = 8010;
private bool stop = false;

private List<TcpClient> clients = new List<TcpClient>();

private void Start()
{
    // Open the Camera on the desired device, in my case IPAD pro
    webCam = new WebCamTexture();
    // Get all devices , front and back camera
    webCam.deviceName = WebCamTexture.devices[WebCamTexture.devices.Length - 1].name;

    // request the lowest width and heigh possible
    webCam.requestedHeight = 10;
    webCam.requestedWidth = 10;


    webCam.Play();

    /
    currentTexture = new Texture2D(webCam.width, webCam.height);

    // Connect to the server
    listner = new TcpListener(port);

    listner.Start();

    // Create Seperate thread for requesting from client 
    Loom.RunAsync(() => {

        while (!stop)
        {
            // Wait for client approval
            var client = listner.AcceptTcpClient();
            // We are connected
            clients.Add(client);


            Loom.RunAsync(() =>
            {
                while (!stop)
                {

                    var stremReader = client.GetStream();

                    if (stremReader.CanRead)
                    {
                        // we need storage for data
                        using (var messageData = new MemoryStream())
                        {
                            Byte[] buffer = new Byte[client.ReceiveBufferSize];


                            while (stremReader.DataAvailable)
                            {
                                int bytesRead = stremReader.Read(buffer, 0, buffer.Length);

                                if (bytesRead == 0)
                                    break;

                                // Writes to the data storage
                                messageData.Write(buffer, 0, bytesRead);

                            }

                            if (messageData.Length > 0)
                            {
                                // send pngImage
                                SendPng(client);

                            }

                        }
                    }
                }
            });
        }

    });



}

private void Update()
{
    myImage.texture = webCam;
}


// Read video pixels and send them to the client
private void SendPng (TcpClient client)
{
    Loom.QueueOnMainThread(() =>
    {
        // Get the webcame texture pixels   
        currentTexture.SetPixels(webCam.GetPixels());
        var pngBytes = currentTexture.EncodeToPNG();


        // Want to Write 
        var stream = client.GetStream();

        // Write the image bytes
        stream.Write(pngBytes, 0, pngBytes.Length);

        // send it 
        stream.Flush();

    });
}

// stop everything
private void OnApplicationQuit()
{
    webCam.Stop();
    stop = true;
    listner.Stop();

    foreach (TcpClient c in clients)
        c.Close();
}



}

客户端/接收方
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Net.Sockets; 
using System.Net;
using System.IO;

public class reciver : MonoBehaviour
{

public RawImage image;

const int port = 8010;

public string IP = "";

TcpClient client;


Texture2D tex;

// Use this for initialization
void Start()
{

    client = new TcpClient();

    // connect to server

    Loom.RunAsync(() => {
        Debug.LogWarning("Connecting to server...");
        // if on desktop
        client.Connect(IPAddress.Loopback, port);

        // if using the IPAD
        //client.Connect(IPAddress.Parse(IP), port);
        Debug.LogWarning("Connected!");




    });

}

float lastTimeRequestedTex = 0;
// Update is called once per frame
void Update()
{

    //if (Time.time - lastTimeRequestedTex < 0.1f)
    //  return;

    lastTimeRequestedTex = Time.time;


    if (!client.Connected)
        return;


    // Send 1 byte to server 
    var serverStream = client.GetStream();

    // request the texture from the server 
    if (serverStream.CanWrite)
    {   
        // Texture request
        // send request
        serverStream.WriteByte(byte.MaxValue);
        serverStream.Flush();
        Debug.Log("Succesfully send 1 byte");
    }


    if (serverStream.CanRead)
    {

        // Read the bytes 
        using (var writer = new MemoryStream())
        {
            var readBuffer = new byte[client.ReceiveBufferSize];


            while (serverStream.DataAvailable)
            {

                int numberOfBytesRead = serverStream.Read(readBuffer, 0, readBuffer.Length);
                if (numberOfBytesRead <= 0)
                {
                    break;
                }

                writer.Write(readBuffer, 0, numberOfBytesRead);


            }

            if (writer.Length > 0)
            {
                // got whole data in writer
                // Get the bytes and apply them to the texture
                var tex = new Texture2D(0, 0);
                tex.LoadImage(writer.ToArray());
                Debug.Log(tex.width + tex.height);
                image.texture = tex;


            }   
        }
    }
}

void OnApplicationQuit()
{
    Debug.LogWarning("OnApplicationQuit");
    client.Close();
}
}

最佳答案

我运行了你的代码,它有时工作,有时失败(大约 90% 的时间)。它在我的电脑上运行 5 帧/秒 .这在移动设备上效果不佳,我相信您的目标是 iPad。

您的代码中几乎没有问题,但它们是非常严重的问题。

1.您的图片在加载之前没有完全收到。

这就是为什么您的图像看起来如此奇怪的原因。

人们在使用 socket 时犯的最大错误是假设您发送的所有内容都会被立即收到。这不是真的。这就是您的客户端的编码方式。请阅读 this .

这是我在回答中使用的方法:

A .Get Texture2D字节数组。

.发送字节数组长度。不是字节数组,而是长度。

C .客户端会先读取长度。

D .客户端将使用该长度读取整个纹理数据/像素直到完成。

E .将接收到的字节转换为数组。

你可以看看private int readImageByteSize(int size)private void readFrameByteArray(int size)如何读取所有字节的函数。

当然,您还必须知道首先发送的数据长度的长度。长度以 int 数据类型保存。

最大 int值为 2,147,483,647那就是 10数字长。所以,我把先发送的数组的数组长度设为15作为协议(protocol)。这也是客户端必须遵守的规则。

这是它现在的工作方式:

Texture2D 读取字节数组,读取该数组的长度,将其发送给客户端。客户端遵循的规则是第一个 15字节只是长度。然后客户端会读到 15字节,将其转换回长度,然后在循环中使用该长度读取完整 Texture2D从服务器。

长度转换是通过 void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes) 完成的和 int frameByteArrayToByteLength(byte[] frameBytesLength)职能。看看那些来理解它们。

2.在主线程中执行socket操作。

这就是为什么每秒帧数 5在我的电脑上。

不要这样做,因为这会使您的帧速率变低,就像它已经一样。我已经回答了很多这样的问题,但不会深入,因为看起来您知道自己在做什么并尝试使用 Thread但做错了。

A .您正在阅读主要 Thread当您这样做时:serverStream.Read(readBuffer, 0, readBuffer.Length);Update功能。

你应该在里面这样做

Loom.RunAsync(() =>
{ //your red code });

.您在 SendPng 中犯了同样的错误函数,当您使用 stream.Write(pngBytes, 0, pngBytes.Length); 发送数据时在里面
Loom.QueueOnMainThread(() =>
{});

你在里面做的任何事情Loom.QueueOnMainThread将在主 Thread 中完成.

您应该在另一个 Thread.Loom.RunAsync(() =>{}); 中进行发送

最后,listner = new TcpListener(port);是过时的。这没有造成任何问题,但使用 listner = new TcpListener(IPAddress.Any, port);在您的服务器代码中,它应该监听 nay IP。

最后每秒帧数 结束 50完成所有这些修复后在我的电脑上。下面的代码可以改进很多。我会把它留给你去做。

您可以使用 online code compare看到每个类(class)发生的变化。

服务器 :
using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.UI;
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;

public class Connecting : MonoBehaviour
{
    WebCamTexture webCam;
    public RawImage myImage;
    public bool enableLog = false;

    Texture2D currentTexture;

    private TcpListener listner;
    private const int port = 8010;
    private bool stop = false;

    private List<TcpClient> clients = new List<TcpClient>();

    //This must be the-same with SEND_COUNT on the client
    const int SEND_RECEIVE_COUNT = 15;

    private void Start()
    {
        Application.runInBackground = true;

        //Start WebCam coroutine
        StartCoroutine(initAndWaitForWebCamTexture());
    }


    //Converts the data size to byte array and put result to the fullBytes array
    void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes)
    {
        //Clear old data
        Array.Clear(fullBytes, 0, fullBytes.Length);
        //Convert int to bytes
        byte[] bytesToSendCount = BitConverter.GetBytes(byteLength);
        //Copy result to fullBytes
        bytesToSendCount.CopyTo(fullBytes, 0);
    }

    //Converts the byte array to the data size and returns the result
    int frameByteArrayToByteLength(byte[] frameBytesLength)
    {
        int byteLength = BitConverter.ToInt32(frameBytesLength, 0);
        return byteLength;
    }

    IEnumerator initAndWaitForWebCamTexture()
    {
        // Open the Camera on the desired device, in my case IPAD pro
        webCam = new WebCamTexture();
        // Get all devices , front and back camera
        webCam.deviceName = WebCamTexture.devices[WebCamTexture.devices.Length - 1].name;

        // request the lowest width and heigh possible
        webCam.requestedHeight = 10;
        webCam.requestedWidth = 10;

        myImage.texture = webCam;

        webCam.Play();

        currentTexture = new Texture2D(webCam.width, webCam.height);

        // Connect to the server
        listner = new TcpListener(IPAddress.Any, port);

        listner.Start();

        while (webCam.width < 100)
        {
            yield return null;
        }

        //Start sending coroutine
        StartCoroutine(senderCOR());
    }

    WaitForEndOfFrame endOfFrame = new WaitForEndOfFrame();
    IEnumerator senderCOR()
    {

        bool isConnected = false;
        TcpClient client = null;
        NetworkStream stream = null;

        // Wait for client to connect in another Thread 
        Loom.RunAsync(() =>
        {
            while (!stop)
            {
                // Wait for client connection
                client = listner.AcceptTcpClient();
                // We are connected
                clients.Add(client);

                isConnected = true;
                stream = client.GetStream();
            }
        });

        //Wait until client has connected
        while (!isConnected)
        {
            yield return null;
        }

        LOG("Connected!");

        bool readyToGetFrame = true;

        byte[] frameBytesLength = new byte[SEND_RECEIVE_COUNT];

        while (!stop)
        {
            //Wait for End of frame
            yield return endOfFrame;

            currentTexture.SetPixels(webCam.GetPixels());
            byte[] pngBytes = currentTexture.EncodeToPNG();
            //Fill total byte length to send. Result is stored in frameBytesLength
            byteLengthToFrameByteArray(pngBytes.Length, frameBytesLength);

            //Set readyToGetFrame false
            readyToGetFrame = false;

            Loom.RunAsync(() =>
            {
                //Send total byte count first
                stream.Write(frameBytesLength, 0, frameBytesLength.Length);
                LOG("Sent Image byte Length: " + frameBytesLength.Length);

                //Send the image bytes
                stream.Write(pngBytes, 0, pngBytes.Length);
                LOG("Sending Image byte array data : " + pngBytes.Length);

                //Sent. Set readyToGetFrame true
                readyToGetFrame = true;
            });

            //Wait until we are ready to get new frame(Until we are done sending data)
            while (!readyToGetFrame)
            {
                LOG("Waiting To get new frame");
                yield return null;
            }
        }
    }


    void LOG(string messsage)
    {
        if (enableLog)
            Debug.Log(messsage);
    }

    private void Update()
    {
        myImage.texture = webCam;
    }

    // stop everything
    private void OnApplicationQuit()
    {
        if (webCam != null && webCam.isPlaying)
        {
            webCam.Stop();
            stop = true;
        }

        if (listner != null)
        {
            listner.Stop();
        }

        foreach (TcpClient c in clients)
            c.Close();
    }
}

客户 :
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System;

public class reciver : MonoBehaviour
{
    public RawImage image;
    public bool enableLog = false;

    const int port = 8010;
    public string IP = "192.168.1.165";
    TcpClient client;

    Texture2D tex;

    private bool stop = false;

    //This must be the-same with SEND_COUNT on the server
    const int SEND_RECEIVE_COUNT = 15;

    // Use this for initialization
    void Start()
    {
        Application.runInBackground = true;

        tex = new Texture2D(0, 0);
        client = new TcpClient();

        //Connect to server from another Thread
        Loom.RunAsync(() =>
        {
            LOGWARNING("Connecting to server...");
            // if on desktop
            client.Connect(IPAddress.Loopback, port);

            // if using the IPAD
            //client.Connect(IPAddress.Parse(IP), port);
            LOGWARNING("Connected!");

            imageReceiver();
        });
    }


    void imageReceiver()
    {
        //While loop in another Thread is fine so we don't block main Unity Thread
        Loom.RunAsync(() =>
        {
            while (!stop)
            {
                //Read Image Count
                int imageSize = readImageByteSize(SEND_RECEIVE_COUNT);
                LOGWARNING("Received Image byte Length: " + imageSize);

                //Read Image Bytes and Display it
                readFrameByteArray(imageSize);
            }
        });
    }


    //Converts the data size to byte array and put result to the fullBytes array
    void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes)
    {
        //Clear old data
        Array.Clear(fullBytes, 0, fullBytes.Length);
        //Convert int to bytes
        byte[] bytesToSendCount = BitConverter.GetBytes(byteLength);
        //Copy result to fullBytes
        bytesToSendCount.CopyTo(fullBytes, 0);
    }

    //Converts the byte array to the data size and returns the result
    int frameByteArrayToByteLength(byte[] frameBytesLength)
    {
        int byteLength = BitConverter.ToInt32(frameBytesLength, 0);
        return byteLength;
    }


    /////////////////////////////////////////////////////Read Image SIZE from Server///////////////////////////////////////////////////
    private int readImageByteSize(int size)
    {
        bool disconnected = false;

        NetworkStream serverStream = client.GetStream();
        byte[] imageBytesCount = new byte[size];
        var total = 0;
        do
        {
            var read = serverStream.Read(imageBytesCount, total, size - total);
            //Debug.LogFormat("Client recieved {0} bytes", total);
            if (read == 0)
            {
                disconnected = true;
                break;
            }
            total += read;
        } while (total != size);

        int byteLength;

        if (disconnected)
        {
            byteLength = -1;
        }
        else
        {
            byteLength = frameByteArrayToByteLength(imageBytesCount);
        }
        return byteLength;
    }

    /////////////////////////////////////////////////////Read Image Data Byte Array from Server///////////////////////////////////////////////////
    private void readFrameByteArray(int size)
    {
        bool disconnected = false;

        NetworkStream serverStream = client.GetStream();
        byte[] imageBytes = new byte[size];
        var total = 0;
        do
        {
            var read = serverStream.Read(imageBytes, total, size - total);
            //Debug.LogFormat("Client recieved {0} bytes", total);
            if (read == 0)
            {
                disconnected = true;
                break;
            }
            total += read;
        } while (total != size);

        bool readyToReadAgain = false;

        //Display Image
        if (!disconnected)
        {
            //Display Image on the main Thread
            Loom.QueueOnMainThread(() =>
            {
                displayReceivedImage(imageBytes);
                readyToReadAgain = true;
            });
        }

        //Wait until old Image is displayed
        while (!readyToReadAgain)
        {
            System.Threading.Thread.Sleep(1);
        }
    }


    void displayReceivedImage(byte[] receivedImageBytes)
    {
        tex.LoadImage(receivedImageBytes);
        image.texture = tex;
    }


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


    }


    void LOG(string messsage)
    {
        if (enableLog)
            Debug.Log(messsage);
    }

    void LOGWARNING(string messsage)
    {
        if (enableLog)
            Debug.LogWarning(messsage);
    }

    void OnApplicationQuit()
    {
        LOGWARNING("OnApplicationQuit");
        stop = true;

        if (client != null)
        {
            client.Close();
        }
    }
}

关于c# - 统一 : Live Video Streaming,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42717713/

有关c# - 统一 : Live Video Streaming的更多相关文章

  1. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  2. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  3. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  4. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  5. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  6. c# - C# 中的 Flatten Ruby 方法 - 2

    我如何做Ruby方法"Flatten"RubyMethod在C#中。此方法将锯齿状数组展平为一维数组。例如:s=[1,2,3]#=>[1,2,3]t=[4,5,6,[7,8]]#=>[4,5,6,[7,8]]a=[s,t,9,10]#=>[[1,2,3],[4,5,6,[7,8]],9,10]a.flatten#=>[1,2,3,4,5,6,7,8,9,10 最佳答案 递归解决方案:IEnumerableFlatten(IEnumerablearray){foreach(variteminarray){if(itemisIEnume

  7. ruby - 可以像在 C# 中使用#region 一样在 Ruby 中使用 begin/end 吗? - 2

    我最近从C#转向了Ruby,我发现自己无法制作可折叠的标记代码区域。我只是想到做这种事情应该没问题:classExamplebegin#agroupofmethodsdefmethod1..enddefmethod2..endenddefmethod3..endend...但是这样做真的可以吗?method1和method2最终与method3是同一种东西吗?还是有一些我还没有见过的用于执行此操作的Ruby惯用语? 最佳答案 正如其他人所说,这不会改变方法定义。但是,如果要标记方法组,为什么不使用Ruby语义来标记它们呢?您可以使用

  8. c# - Ruby 等效于 C# Linq 聚合方法 - 2

    什么是Linq聚合方法的ruby​​等价物。它的工作原理是这样的varfactorial=new[]{1,2,3,4,5}.Aggregate((acc,i)=>acc*i);每次将数组序列中的值传递给lambda时,变量acc都会累积。 最佳答案 这在数学以及几乎所有编程语言中通常称为折叠。它是更普遍的变形概念的一个实例。Ruby从Smalltalk中继承了这个特性的名称,它被称为inject:into:(像aCollectioninject:aStartValueinto:aBlock一样使用。)所以,在Ruby中,它称为inj

  9. c# - 先学什么? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭8年前。Improvethisquestion几年前我去学校学习编程,毕业后我找到了一份系统管理方面的工作,这就是我职业生涯的方向。我想重新开始某种开发,并且一直在“玩”C#和ASP.NET,但我已经听到很多关于其他"new"语言的讨论(新的意思是它们是新的)我)喜欢Ruby和F#。我想我想知道我是否在浪费时间学习主要的MS语言,而不是成为一名通才。很长一段时间没有离开开发社区(如果我曾经离开过的话)让我在潮流中挣扎,我不想落在时代的

  10. c# - 在 C# 中重现 Ruby OpenSSL private_encrypt 输出 - 2

    我有一个简单的Ruby脚本,我用它在某些HTTPheader上执行private_encrypt以签署要发送到ruby​​RESTAPI的Web请求,该API会根据Base64编码字符串测试Base64编码字符串生成而不是解码Base64和解密数据然后测试原始字符串。我使用的脚本是require"openssl"require"base64"path_to_cert=ARGV[0].dupplain_text=Base64.decode64(ARGV[1].dup)private_key=OpenSSL::PKey::RSA.new(File.read(path_to_cert))pu

随机推荐