Skip to content

Instantly share code, notes, and snippets.

@lightfromshadows
Created October 15, 2019 19:16
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lightfromshadows/79029ca480393270009173abc7cad858 to your computer and use it in GitHub Desktop.
Save lightfromshadows/79029ca480393270009173abc7cad858 to your computer and use it in GitHub Desktop.
Simple MJPEG stream decoder for Unity written in C#
/*
* I needed a simple MJPEG Stream Decoder and I couldn't find one that worked for me.
*
* It reads a response stream and when there's a new frame it updates the render texture.
* That's it. No authenication or options.
* It's something stupid simple for readimg a video stream from an equally stupid simple Arduino.
*
* I fixed most of the large memory leaks, but there's at least one small one left.
*/
using System.IO;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using UnityEngine;
public class MJPEGStreamDecoder : MonoBehaviour
{
[SerializeField] bool tryOnStart = false;
[SerializeField] string defaultStreamURL = "http://127.0.0.1/stream";
[SerializeField] RenderTexture renderTexture;
float RETRY_DELAY = 5f;
int MAX_RETRIES = 3;
int retryCount = 0;
byte[] nextFrame = null;
Thread worker;
int threadID = 0;
static System.Random randu; // I use my own System.Random instead of the shared UnityEngine.Random to avoid collisions
List<BufferedStream> trackedBuffers = new List<BufferedStream>();
// Start is called before the first frame update
void Start()
{
randu = new System.Random(Random.Range(0, 65536));
if (tryOnStart)
StartStream(defaultStreamURL);
}
private void Update()
{
if (nextFrame != null)
{
SendFrame(nextFrame);
nextFrame = null;
}
}
private void OnDestroy()
{
foreach (var b in trackedBuffers)
{
if (b != null)
b.Close();
}
}
public void StartStream(string url)
{
retryCount = 0;
StopAllCoroutines();
foreach (var b in trackedBuffers)
b.Close();
worker = new Thread(() => ReadMJPEGStreamWorker(threadID = randu.Next(65536), url));
worker.Start();
}
void ReadMJPEGStreamWorker(int id, string url)
{
var webRequest = WebRequest.Create(url);
webRequest.Method = "GET";
List<byte> frameBuffer = new List<byte>();
int lastByte = 0x00;
bool addToBuffer = false;
BufferedStream buffer = null;
try
{
Stream stream = webRequest.GetResponse().GetResponseStream();
buffer = new BufferedStream(stream);
trackedBuffers.Add(buffer);
}
catch (System.Exception ex)
{
Debug.LogError(ex);
}
int newByte;
while (buffer != null)
{
if (threadID != id) return; // We are no longer the active thread! stop doing things damnit!
if (!buffer.CanRead)
{
Debug.LogError("Can't read buffer!");
break;
}
newByte = -1;
try
{
newByte = buffer.ReadByte();
}
catch
{
break; // Something happened to the stream, start a new one
}
if (newByte < 0) // end of stream or failure
{
continue; // End of data
}
if (addToBuffer)
frameBuffer.Add((byte)newByte);
if (lastByte == 0xFF) // It's a command!
{
if (!addToBuffer) // We're not reading a frame, should we be?
{
if (IsStartOfImage(newByte))
{
addToBuffer = true;
frameBuffer.Add((byte)lastByte);
frameBuffer.Add((byte)newByte);
}
}
else // We're reading a frame, should we stop?
{
if (newByte == 0xD9)
{
frameBuffer.Add((byte)newByte);
addToBuffer = false;
nextFrame = frameBuffer.ToArray();
frameBuffer.Clear();
}
}
}
lastByte = newByte;
}
if (retryCount < MAX_RETRIES)
{
retryCount++;
Debug.LogFormat("[{0}] Retrying Connection {1}...", id, retryCount);
foreach (var b in trackedBuffers)
b.Dispose();
trackedBuffers.Clear();
worker = new Thread(() => ReadMJPEGStreamWorker(threadID = randu.Next(65536), url));
worker.Start();
}
}
bool IsStartOfImage(int command)
{
switch (command)
{
case 0x8D:
Debug.Log("Command SOI");
return true;
case 0xC0:
Debug.Log("Command SOF0");
return true;
case 0xC2:
Debug.Log("Command SOF2");
return true;
case 0xC4:
Debug.Log("Command DHT");
break;
case 0xD8:
//Debug.Log("Command DQT");
return true;
case 0xDD:
Debug.Log("Command DRI");
break;
case 0xDA:
Debug.Log("Command SOS");
break;
case 0xFE:
Debug.Log("Command COM");
break;
case 0xD9:
Debug.Log("Command EOI");
break;
}
return false;
}
void SendFrame(byte[] bytes)
{
Texture2D texture2D = new Texture2D(2, 2);
texture2D.LoadImage(bytes);
//Debug.LogFormat("Loaded {0}b image [{1},{2}]", bytes.Length, texture2D.width, texture2D.height);
if (texture2D.width == 2)
return; // Failure!
Graphics.Blit(texture2D, renderTexture);
Destroy(texture2D); // LoadImage discards the previous buffer, so there's no point in trying to reuse it
}
}
@Blaspberry
Copy link

This. This is awsome. This helped where literally youtube and the unity regestry itself couldn't. thank you

@Danial-Kord
Copy link

Thank you for sharing your implementation of MJPEG stream :D

@julian274
Copy link

Hey how i can use it to stream a live stream in unity??
What steps should i take?
Its my first time using unity so I'm lost!
If you can help me I'd appreciate that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment