Skip to content

Instantly share code, notes, and snippets.

@ousttrue
Last active November 7, 2019 09:04
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ousttrue/d8a5fd7f5b58b9ef8ce1 to your computer and use it in GitHub Desktop.
Save ousttrue/d8a5fd7f5b58b9ef8ce1 to your computer and use it in GitHub Desktop.
TextureUpdater for Unity by ffmpeg
using UnityEngine;
using System.IO;
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Collections;
public class FFMpegYUV4Texture : MonoBehaviour
{
public static class RecursiveReader
{
public delegate void OnReadFunc(Byte[] bytes, int count);
public static void BeginRead(Stream s, Byte[] buffer, OnReadFunc onread)
{
AsyncCallback callback = ar => {
var ss=ar.AsyncState as Stream;
var readCount=ss.EndRead(ar);
if (readCount == 0)
{
Debug.Log("zero");
return;
}
onread(buffer, readCount);
BeginRead(ss, buffer, onread);
};
s.BeginRead(buffer, 0, buffer.Length, callback, s);
}
}
[SerializeField]
string Source;
const string KEY = "FFMPEG_DIR";
const string EXE = "ffmpeg.exe";
System.Diagnostics.Process m_process;
List<Byte> m_queue = new List<byte>();
List<Byte> m_error = new List<byte>();
[SerializeField]
Texture2D Texture;
// ffmpeg -v 0 -i "{0}" -pix_fmt yuv420p -an -vcodec rawvideo -f yuv4mpegpipe -
void Awake()
{
m_header = null;
var file = new FileInfo(Path.Combine(Environment.GetEnvironmentVariable(KEY), EXE));
if (!file.Exists)
{
Debug.LogFormat("{0} not exits", file);
return;
}
var args = String.Format("-i \"{0}\" -f yuv4mpegpipe -", Source);
Debug.Log(args);
var startInfo = new System.Diagnostics.ProcessStartInfo(file.FullName, args)
{
CreateNoWindow=true,
RedirectStandardError=true,
StandardErrorEncoding=Encoding.UTF8,
RedirectStandardOutput=true,
UseShellExecute=false,
};
m_process = System.Diagnostics.Process.Start(startInfo);
RecursiveReader.BeginRead(m_process.StandardOutput.BaseStream, new Byte[8192], (b, c) => OnRead(m_queue, b, c));
RecursiveReader.BeginRead(m_process.StandardError.BaseStream, new Byte[1024], (b, c) => OnRead(m_error, b, c));
}
static void OnRead(List<Byte> queue, Byte[] buffer, int count)
{
lock (((ICollection)queue).SyncRoot)
{
queue.AddRange(buffer.Take(count));
}
}
static Byte[] Dequeue(List<Byte> queue)
{
Byte[] tmp;
lock (((ICollection)queue).SyncRoot)
{
tmp = queue.ToArray();
queue.Clear();
}
return tmp;
}
public enum YUVFormat
{
YUV420,
}
[Serializable]
public class Frame
{
[SerializeField]
List<Byte> m_header = new List<byte>();
int m_fill;
Byte[] m_body;
public Byte[] Body
{
get { return m_body; }
}
public bool IsFill
{
get
{
return m_fill >= m_body.Length;
}
}
bool m_isHeader;
public Frame(int bytes)
{
m_body = new Byte[bytes];
}
public void Clear()
{
m_isHeader = true;
m_header.Clear();
m_fill = 0;
}
public int Push(Byte[] bytes, int i)
{
if (m_isHeader) {
for(; i<bytes.Length; ++i)
{
if (bytes[i] == 0x0a) {
m_isHeader = false;
++i;
break;
}
m_header.Add(bytes[i]);
}
}
for(; i<bytes.Length && m_fill<m_body.Length; ++i, ++m_fill)
{
m_body[m_fill] = bytes[i];
}
return i;
}
}
[Serializable]
public class YUVInfo
{
public YUVFormat Format;
public int Width;
public int Height;
public YUVInfo(int w, int h, YUVFormat format)
{
Width = w;
Height = h;
Format = format;
m_current = new Frame(FrameBodyLength);
m_next = new Frame(FrameBodyLength);
}
List<Byte> m_buffer = new List<byte>();
public int FrameBodyLength
{
get
{
return Width * Height * 3 / 2;
}
}
public override string ToString()
{
return String.Format("[{0}]{1}x{2}", Format, Width, Height);
}
public Texture2D CreateTexture()
{
return new Texture2D(Width, Height*3/2, TextureFormat.Alpha8, false);
}
readonly Byte[] frame_header = new[] { (byte)0x46, (byte)0x52, (byte)0x41, (byte)0x4D, (byte)0x45 };
[SerializeField]
Frame m_current;
[SerializeField]
Frame m_next;
bool IsHead(List<Byte> src)
{
for(int i=0; i< frame_header.Length; ++i)
{
if (src[i] != frame_header[i]) return false;
}
return true;
}
public bool Push(Byte[] bytes)
{
bool hasNewFrame = false;
var i = 0;
while (i<bytes.Length)
{
i=m_next.Push(bytes, i);
if (m_next.IsFill)
{
var tmp = m_current;
m_current = m_next;
m_next = tmp;
m_next.Clear();
hasNewFrame = true;
}
}
return hasNewFrame;
}
public void Apply(Texture2D texture)
{
if (texture != null)
{
texture.LoadRawTextureData(m_current.Body);
texture.Apply();
}
}
public static YUVInfo Parse(String header)
{
Debug.Log("[Parse]" + header);
int width = 0;
int height = 0;
var format = default(YUVFormat);
foreach (var value in header.Split())
{
switch (value.FirstOrDefault())
{
case 'W':
try {
width = int.Parse(value.Substring(1));
}
catch(Exception ex)
{
Debug.LogError(ex + ": "+value);
throw;
}
break;
case 'H':
try {
height = int.Parse(value.Substring(1));
}
catch(Exception ex)
{
Debug.LogError(ex + ": " + value);
throw;
}
break;
case 'C':
if (value == "C420mpeg2")
{
format = YUVFormat.YUV420;
}
break;
}
}
return new YUVInfo(width, height, format);
}
}
[SerializeField]
YUVInfo m_header;
// Update is called once per frame
void Update () {
{
var data = Dequeue(m_queue);
if (data != null && data.Any())
{
if (m_header==null)
{
var tmp = data.TakeWhile(x => x != 0x0A).ToArray();
m_header = YUVInfo.Parse(Encoding.ASCII.GetString(tmp));
Debug.Log(m_header);
data = data.Skip(tmp.Length).ToArray();
// create texture
Texture = m_header.CreateTexture();
}
// parse data
if (m_header.Push(data))
{
m_header.Apply(Texture);
}
}
}
{
var error = Dequeue(m_error);
if (error.Any())
{
var text = Encoding.UTF8.GetString(error, 0, error.Length);
Debug.LogWarning(text);
}
}
}
void OnApplicationQuit()
{
Debug.Log("kill");
if (m_process == null) return;
if (m_process.HasExited) return;
m_process.Kill();
}
}
Shader "Unlit/YUV4"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, float2(i.uv.x, (1-i.uv.y)*2/3));
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return float4(col.a, col.a, col.a, 1.0);
}
ENDCG
}
}
}
@shabbyrobe
Copy link

Does this code have a license?

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