基于Unity Sockect实现摄像头画面实时传输

最近刚好有需要做个视频通话,走局域网的那种,林新发大佬之前发布的那个工程没了,不然可以直接用他的(去年还是前年我试过那个工程,可以手机跟电脑实现视频通话完全不卡,感兴趣可以去看看文章)【游戏开发实战】Unity从零开发多人视频聊天功能,无聊了就和自己视频聊天(附源码 | Mirror | 多人视频 | 详细教程)_unity mirror实现语音-优快云博客

下面是基于yq_sprite大佬的源码改的,原文章的源码只能在本机进行传输,两台机器传输就传不了,我也是折腾了一天才搞通,在此踩了坑特此记录,顺带给新来的人排下雷,废话没了,上代码:

原文中的客户端代码(实际上是服务端)

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

public class UnityClient : MonoBehaviour {

	public int port = 10002;

	Socket socket = null;

	Thread thread = null;

	bool success = true;

	Dictionary<string, Client> clients = new Dictionary<string, Client>();

	private WebCamTexture webCamTexture;
	private Texture2D texture2D;
	public RawImage image;

	private void Start()
	{
		Go();
	}

	public void Go () {
        // 开启Socket
		socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		socket.Bind(new IPEndPoint(IPAddress.Any, port));
		socket.Listen(100);

        // 开启一个线程发送渲染数据
		thread = new Thread(new ThreadStart(OnStart));
		thread.Start();
		
		WebCamDevice[] devices = WebCamTexture.devices;
		webCamTexture = new WebCamTexture(devices[0].name, 1920, 1080, 60);
		webCamTexture.Play();
		texture2D = new Texture2D(webCamTexture.width, webCamTexture.height, TextureFormat.RGB24, false);
	}

	int isNewAdd = 0;

	void OnStart()
    {
        Debug.Log("Socket创建成功");
        while (thread.ThreadState == ThreadState.Running)
        {
			Socket _socket = socket.Accept();
            if (clients.ContainsKey(_socket.RemoteEndPoint.ToString()))
            {
                try
                {
					clients[_socket.RemoteEndPoint.ToString()].socket.Shutdown(SocketShutdown.Both);
                }
                catch
                {
                }
				clients.Remove(_socket.RemoteEndPoint.ToString());
            }

			Client client = new Client
			{
				socket = _socket
			};

			clients.Add(_socket.RemoteEndPoint.ToString(), client);

			isNewAdd = 1;
        }
    }

	void Update()
    {
	    if (success && clients.Count > 0)
	    {
		    success = false;
		    texture2D.SetPixels(webCamTexture.GetPixels());
		    texture2D.Apply();
		    image.texture = texture2D;
		    SendTexture(texture2D);
	    }
    }

	void OnApplicationQuit()
    {
        try
        {
			socket.Shutdown(SocketShutdown.Both);
        }
        catch { }

        try
        {
			thread.Abort();
        }
        catch { }
    }

	int gc_count = 0;

	void SendTexture(Texture2D texture2D)
    {
        if (texture2D != null)
        {
			byte[] bytes = texture2D.EncodeToJPG(100);

            foreach (var val in clients.Values)
            {
                try
                {
					val.socket.Send(bytes);
                }
                catch
                {
                    if (!val.socket.Connected)
                    {
                        clients.Remove(val.socket.RemoteEndPoint.ToString());
                    }
                }
            }
            gc_count++;
            if (gc_count > 5000)
            {
                gc_count = 0;
                GC.Collect(2);
            }
            Debug.Log("发送数据:" + (float)bytes.Length / 1024f + "KB");
        }
        success = true;
    }

    void OnDestroy()
    {
        try
        {
            socket.Shutdown(SocketShutdown.Both);
        }
        catch { }

        try
        {
            thread.Abort();
        }
        catch { }
    }
}

class Client {
	public Socket socket = null;
}

原文中的服务端代码(实际是客户端代码):

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

public class UnityServer : MonoBehaviour {

	Socket socket = null;
	Thread thread = null;
	byte[] buffer = null;
	bool receState = true;

	int readTimes = 0;

    public RawImage rawImage;
    public InputField inputField;

    private Queue<byte[]> datas;

    public void Go()
    {
		buffer = new byte[1024 * 1024 * 10];

        // 创建服务器, 以Tcp的方式
		socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.Connect(IPAddress.Parse(inputField.text), 10002);

        // 开启一个线程, 用于接受数据
		thread = new Thread(new ThreadStart(Receive));
		thread.Start();

        datas = new Queue<byte[]>();
    }

    private void Receive()
    {
        while (thread.ThreadState == ThreadState.Running && socket.Connected)
        {
            // 接受数据Buffer count是数据的长度
			int count = socket.Receive(buffer);
            if (receState && count > 0)
            {
				receState = false;
                BytesToImage(count, buffer);
            }
        }
    }

	MemoryStream ms = null;
	public void BytesToImage(int count, byte[] bytes)
    {
        try
        {
            ms = new MemoryStream(bytes, 0, count);
            datas.Enqueue(ms.ToArray());    // 将数据存储在一个队列中,在主线程中解析数据。这是一个多线程的处理。

            readTimes++;

            if (readTimes > 5000)
            {
                readTimes = 0;
                GC.Collect(2);  // 达到一定次数的时候,开启GC,释放内存
            }
        }
        catch
        {

        }
        receState = true;
    }

    void Update()
    {
        try
        {
        if (datas.Count > 0)
        {
            // 处理纹理数据,并显示
            Texture2D texture2D = new Texture2D(Screen.width, Screen.height);
            texture2D.LoadImage(datas.Dequeue());
            rawImage.texture = texture2D;
        }
        }
        catch (Exception e)
        {
        }
    }

    void OnDestroy()
    {
        try
        {
            if (socket != null)
            {
                socket.Shutdown(SocketShutdown.Both);
            }
        }
        catch { }

        try
        {
            if (thread != null)
            {
                thread.Abort();
            }
        }
        catch { }

        datas.Clear();
    }
}

这里面实际上还有些坑,比如连接完成关闭程序的时候会报错,粘包问题,以及频繁使用SetPixels方法高度消耗性能等问题,还需要后人注意了,再次再次鸣谢大佬们的文章

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值