Skip to content

Instantly share code, notes, and snippets.

@unitycoder
Created February 15, 2020 15:25
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save unitycoder/6fece768157c038fb4c28d4b59931625 to your computer and use it in GitHub Desktop.
Save unitycoder/6fece768157c038fb4c28d4b59931625 to your computer and use it in GitHub Desktop.
Encode/Decode Audio to/from PNG Alpha Channel
// https://unitycoder.com/blog/2020/02/15/encode-decode-audio-to-from-png-alpha-channel
// Decode Raw audiodata from PNG alpha channel
// Usage: Assign source texture and audioclip
// Note: You need to use same seed and samplerate that was used in encoding
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace UnityLibrary
{
public class DecodeAudioFromTexture : MonoBehaviour
{
[Tooltip("Texture filename inside StreamingAssets/ folder")]
public string filename = "mytex.png";
[Tooltip("Target MeshRenderer to draw decoded texture into")]
public MeshRenderer targetRenderer;
[Tooltip("Do you want to de-shuffle pixels?")]
public bool deShufflePixels = false;
[Tooltip("Use same seed as the encoding process")]
public int seed = 1;
[Tooltip("Enter SampleRate(hz) used in encoded data")]
public int sampleRate = 1000;
void Start()
{
StartCoroutine(Decode());
}
IEnumerator Decode()
{
string path = Path.Combine(Application.streamingAssetsPath, filename);
Debug.Log("Reading image: " + path);
WWW www = new WWW(path);
yield return www;
if (!string.IsNullOrEmpty(www.error))
{
Debug.LogError("Cannot load texture " + path + " Error:" + www.error);
yield break;
}
Texture2D texTmp = new Texture2D(512, 512, TextureFormat.RGBA32, false, false);
www.LoadImageIntoTexture(texTmp);
// read colors from texture
var pixels = texTmp.GetPixels(0);
// unshuffle
if (deShufflePixels == true) DeShuffle(seed, ref pixels);
texTmp.SetPixels(pixels);
texTmp.Apply(false);
// show texture
if (targetRenderer == null)
{
Debug.LogError("No targetRenderer assigned.. (ErrorCode: Zombie)", gameObject);
yield break;
}
targetRenderer.material.mainTexture = texTmp;
// get audio from alpha
var data = new List<float>();
// NOTE there will be empty values in the end if texture is bigger than audio
for (int i = 0; i < pixels.Length; i++)
{
float f = pixels[i].a * 2f - 1f;
data.Add(f);
}
Debug.Log("Decoded len: " + data.Count);
// create audioclip
var newclip = AudioClip.Create("DecodedAudio", data.Count, 1, sampleRate, false);
newclip.SetData(data.ToArray(), 0);
// play using audiosource
var au = targetRenderer.gameObject.GetComponent<AudioSource>();
if (au == null) au = targetRenderer.gameObject.AddComponent<AudioSource>();
au.clip = newclip;
au.Play();
Debug.Log("Done decoding");
}
// https://stackoverflow.com/a/3542000/5452781
void DeShuffle<T>(int seed, ref T[] array)
{
Random.InitState(seed);
int n = array.Length;
int nn = array.Length;
int l = n;
int rr = 0;
int[] keys = new int[nn - 1];
int i = 0;
while (n > 1)
{
int k = Random.Range(0, n--);
keys[i++] = k;
}
n = 1;
System.Array.Reverse(keys);
while (nn > 1)
{
int k = keys[rr++];
T temp = array[n];
array[n] = array[k];
array[k] = temp;
nn--;
n++;
}
}
}
}
// https://unitycoder.com/blog/2020/02/15/encode-decode-audio-to-from-png-alpha-channel/
// Encode Raw audiodata into PNG alpha channel
// Usage: Assign texture to "tex" field (requires [x] read/write enabled and Texture format: RGBA32, in texture inspector settings)
using System.IO;
using UnityEngine;
public class EncodeAudioToTexture : MonoBehaviour
{
[Tooltip("Source texture")]
public Texture2D tex;
[Tooltip("Source audioclip")]
public AudioClip clip;
[Tooltip("Shuffle pixels, so that it looks more mysterious")]
public bool shufflePixels = false;
[Tooltip("Random seed, you need to use same seed when deconding")]
public int seed = 1;
void Start()
{
Encode();
}
void Encode()
{
// read audio data
float[] data = new float[clip.samples * clip.channels];
var b = clip.GetData(data, 0);
if (b == false)
{
Debug.LogError("Failed to get audio data.. (ErrorCode: Banana)");
return;
}
// read texture data
var pixels = tex.GetPixels(0);
// check if can fit
Debug.Log("AudioLen = " + data.Length);
Debug.Log("TexLen = " + pixels.Length);
if (data.Length > pixels.Length)
{
Debug.LogError("Audio cannot fit into this texture, try lowering the sample rate.. (ErrorCode: Mustard)");
return;
}
//assign audio data into texture alpha
for (int i = 0; i < data.Length; i++)
{
// remap -1 to 1 into 0 to 1 range
pixels[i].a = (data[i] + 1f) * 0.5f;
}
// "randomize" pixels, so cannot see image
if (shufflePixels == true) Shuffle(seed, ref pixels);
// save new texture
var outputTex = new Texture2D(tex.width, tex.height, tex.format, false);
outputTex.SetPixels(pixels, 0);
outputTex.Apply(false);
var bytes = outputTex.EncodeToPNG();
string path = Path.Combine(Application.streamingAssetsPath, tex.name + ".png");
File.WriteAllBytes(path, bytes);
Debug.Log("Done Encoding, saved png: " + path);
}
// https://stackoverflow.com/a/1262619/5452781
void Shuffle<T>(int seed, ref T[] array)
{
Random.InitState(seed);
int n = array.Length;
while (n > 1)
{
int k = Random.Range(0, n--);
T temp = array[n];
array[n] = array[k];
array[k] = temp;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment