C# Unity - Stream from IP camera over 3D Plane object

Hello i made a program with Unity3D that “project” the image of an ip camera over a 3D object, a plane…

The script is working well with streams i got from the internet but i have problems with my Panasonic WV-SW458.

Do not know why because i get the right stream on the internet and in the VLC with both the internet camera and my Panasonic.

I’ll attach my code , maybe i’m missing something that, at the moment, i cannot see…

 using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System;
using System.Net;
using System.IO;

public class WebStream : MonoBehaviour {

     [HideInInspector]
     public Byte [] JpegData;
     [HideInInspector]
     public string resolution = "480x360";

     private Texture2D texture;
     private Stream stream;
     private WebResponse resp;
     public MeshRenderer frame;

     public void Start() {
       GetVideo();
     }

     public void StopStream(){
         stream.Close ();
         resp.Close ();
     }

     public void GetVideo(){
         texture = new Texture2D(2, 2);

         //Working
         //string url = "http://24.172.4.142/mjpg/video.mjpg?COUNTER";
         //Working
         string url = "http://200.36.58.250/mjpg/video.mjpg?resolution=640x480";
         //NotWorking
         //rtsp://192.168.140.120/MediaInput/h264
         //NotWorking
         //string url = "http://192.168.140.120/cgi-bin/mjpeg";

         HttpWebRequest req = (HttpWebRequest) WebRequest.Create(url);
         //For testing
        // req.ProtocolVersion = HttpVersion.Version10;

         // get response
         resp = req.GetResponse();
         // get response stream
         stream = resp.GetResponseStream();
         frame.material.color = Color.white;
         StartCoroutine (GetFrame ());
     }

     public IEnumerator GetFrame (){
        // Byte [] JpegData = new Byte[105536];
       //   Byte [] JpegData = new Byte[205536];
         Byte [] JpegData = new Byte[505536];
         while(true) {
             int bytesToRead = FindLength(stream);
             if (bytesToRead == -1) {
                 yield break;
             }

             int leftToRead=bytesToRead;

             while (leftToRead > 0) {
                Debug.Log("Left To Read" + leftToRead);
                 leftToRead -= stream.Read (JpegData, bytesToRead - leftToRead, leftToRead);
                 yield return null;
             }

             MemoryStream ms = new MemoryStream(JpegData, 0, bytesToRead, false, true);

             texture.LoadImage (ms.GetBuffer ());
             frame.material.mainTexture = texture;
             frame.material.color = Color.white;
             stream.ReadByte(); // CR after bytes
             stream.ReadByte(); // LF after bytes
         }
     }

     int FindLength(Stream stream)  {
         int b;
         string line="";
         int result=-1;
         bool atEOL=false;
         while ((b=stream.ReadByte())!=-1) {
             if (b==10) continue; // ignore LF char
             if (b==13) { // CR
                 if (atEOL) {  // two blank lines means end of header
                     stream.ReadByte(); // eat last LF
                     return result;
                 }
                 if (line.StartsWith("Content-Length:")) {
                     result=Convert.ToInt32(line.Substring("Content-Length:".Length).Trim());
                 } else {
                     line="";
                 }
                 atEOL=true;
             } else {
                 atEOL=false;
                 line+=(char)b;
             }
         }
         return -1;
     }
}
2 Likes

your code works well, it worked for me
Congratulations

Thanks @aegis80 , this script is also working for me!

I have made a few improvement for my project:

  • using UnityEvent to assign the targets texture/renderer/UI from Unity editor
  • reading the JPEG data by smaller chunks than the expected file size to make things more asynchronous (I went from 2FPS to 150FPS with my project (95kB images))
  • added some robust logic to restart the HTTP request if the connection is failing or closed after some time
// based on WebStream by aegis80 https://discussions.unity.com/t/737783

using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System;
using System.Net;
using System.IO;

public class MJPEGClient : MonoBehaviour {

    public string url = "http://localhost:8989/";

    [Min(1)] public int readBufferSize = 1024;

    public UnityEvent<Texture2D> onTextureUpdate;

    Texture2D _texture;
    Stream _stream;
    WebResponse _resp;


    void Awake() {
        _texture = new Texture2D(0, 0);
    }

    void OnEnable() {
        RestartListening(null);
    }

    async void StartListening() {
        Debug.Log($"[MJPEGClient] Connecting to '{url}'...");
        HttpWebRequest req = (HttpWebRequest) WebRequest.Create(url);

        try {
            _resp = await req.GetResponseAsync();
            if(this && isActiveAndEnabled) {
                _stream = _resp.GetResponseStream();

                Debug.Log($"[MJPEGClient] Connected to '{url}'");
                StartCoroutine(GetFrames());
            }
            else {
                _resp.Close();
            }
        }
        catch (WebException e) {
            RestartListening(e);
        }
    }

    void StopListening() {
        StopAllCoroutines();

        _stream?.Close ();
        _resp?.Close ();
    }

    void RestartListening(Exception exception) {
        if(exception != null) {
            Debug.LogError($"[MJPEGClient] Restarting due to exception: {exception.Message}");
        }

        StopListening();

        if(this && isActiveAndEnabled) {
            StartListening();
        }
    }

    void OnDisable() {
        StopListening();
    }

    void OnDestroy() {
        Destroy(_texture);
    }

    IEnumerator GetFrames(){
        while(true) {
            int bytesToRead;
            try {
                bytesToRead = FindLength(_stream);
            }
            catch (Exception e) {
                RestartListening(e);
                yield break;
            }

            if (bytesToRead == -1) {
                RestartListening(new Exception("Unable to find JPEG data length!"));
                yield break;
            }

            var jpegData = new byte[bytesToRead];
            int leftToRead = bytesToRead;

            while (leftToRead > 0) {
                leftToRead -= _stream.Read(jpegData, bytesToRead - leftToRead, Mathf.Min(leftToRead, readBufferSize));
                yield return null;
            }

            var ms = new MemoryStream(jpegData, 0, bytesToRead, false, true);
            _texture.LoadImage (ms.GetBuffer());
            onTextureUpdate.Invoke(_texture);

            try {
                _stream.ReadByte(); // CR after bytes
                _stream.ReadByte(); // LF after bytes
            }
            catch (Exception e) {
                RestartListening(e);
                yield break;
            }
        }
    }

    static readonly string s_contentLengthHeader = "Content-Length:";
    static readonly int s_contentLengthHeaderLength = s_contentLengthHeader.Length;

    int FindLength(Stream stream)  {

        int b;
        string line = "";
        int result = -1;
        bool atEOL = false;
        while ((b = stream.ReadByte()) != -1) {
            if (b == 10) {
                continue; // ignore LF char
            }
            if (b == 13) { // CR
                if (atEOL) {  // two blank lines means end of header
                    stream.ReadByte(); // eat last LF
                    return result;
                }
                if (line.StartsWith(s_contentLengthHeader)) {
                    result = Convert.ToInt32(line.Substring(s_contentLengthHeaderLength).Trim());
                } else {
                    line="";
                }
                atEOL = true;
            }
            else {
                atEOL = false;
                line += (char)b;
            }
        }
        return -1;
    }
}