I'm trying to stream a live video from one app to the another, Currently i have 2 apps. were app 1 is the server / sender and app 2 is the client / receiver. In app 1 i successfully send the video bytes to the client. and on the client side I'm also receiving all of the bytes. Im using sockets and TCP. The issue that I'm facing is, When i receive the video bytes and assigning them to a Raw Image texture, the image on the texture looks zoomed in too much and it's so pixilated.
Updated Image
and this is what i get on the client.
This is the 1st issue, however I'm currently testing from desktop to the another, my goal is to stream form an IPAD to a desktop, and when i do that it's so slow and it kills the app on both the ipad and desktop.
Some troubleshooting i tried so far.
1: I think this is is happening because i have 2 different resolutions because i stream from ipad to Desktop
2: The texture image is too large, i output it and it returns 630. I tried to resize it using Unity Texture2D.resize but i get a gray texture because the function sets the pixels as unidentified
3: I used other libraries for resizing textures and i do get what i want, but after 12 frames the rawimage starts flickering between the video and "?" texture so much then it freezes on both app (ipad and desktop)
4: i believe the way I'm reading the texture is causing the issue because i use both Setpixels and Getpixels functions and they are heavy.
My Code: Server / Sender Side:
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();
}
}
Client / receiver side
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();
}
}
I ran your code and it worked sometimes and failed sometimes(about 90% of the time). It ran with on my computer with 5 FPS. This will not play well on mobile device which I am sure you are targeting iPad.
There are few problems in your code but they are very serious problems.
1.Your image is not completely received before you load them.
This is why your image look so weird.
The biggest mistake people make when working with socket is to assume that everything you send will be received at once. This is not true. That's the way your client is coded. Please read this.
This is the method I used in my answer:
A.Get
Texture2D
byte array.B.Send the byte array length. Not the byte array but the length.
C.The client will read the length first.
D.The client will use that length to read the whole texture data/pixel until completion.
E.Convert the received bytes to array.
You can look at the
private int readImageByteSize(int size)
and theprivate void readFrameByteArray(int size)
functions for how to read all the bytes.Of-course, you must also know the length of the data's length that is sent first. The length is saved in int data-type.
The max
int
value is2,147,483,647
and that is10
digit long. So, I made the array length of the array that is sent first to be15
as a protocol. This is a rule that must be obeyed on the client side too.This how it works now:
Read the byte array from
Texture2D
, read the length of that array, send it to the client. Client follows a rule that the first15
bytes is simply the length. Client would then read that15
bytes, convert it back into length then use that length in a loop to read completeTexture2D
from the server.The length conversion is done with the
void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes)
andint frameByteArrayToByteLength(byte[] frameBytesLength)
functions. Take a look at those to understand them.2.Performing socket operation in the Main Thread.
This is why the FPS is
5
on my computer.Don't do this as this will make your frame rate to be low just like it is already. I have answered many questions like this but won't go deep because it looks like you know what you are doing and tried to use
Thread
but did it wrong.A.You were reading from the main
Thread
when you did:serverStream.Read(readBuffer, 0, readBuffer.Length);
in theUpdate
function.You should have done that inside
B. You made the-same mistake in the
SendPng
function, when you were sending data with thestream.Write(pngBytes, 0, pngBytes.Length);
in theAnything you do inside
Loom.QueueOnMainThread
will be done in the mainThread
.You are supposed to do the sending in another
Thread.Loom.RunAsync(() =>{});
Finally,
listner = new TcpListener(port);
is obsolute. This did not cause any problem but uselistner = new TcpListener(IPAddress.Any, port);
in your server code which should listen to nay IP.The final FPS is over
50
on my computer after making all these fixes. The code below can be improved a-lot. I will leave that for you to do.You can use online code compare to see things that changed in each class.
SERVER:
CLIENT: