Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Noisy audio clip after decoding from base64

I encoded the wav file in base64 (audioClipName.txt in Resources/Sounds).

HERE IS THE SOURCE WAVE FILE

Then I tried to decode it, make an AudioClip from it and play it like this:

public static void CreateAudioClip()
{
    string s = Resources.Load<TextAsset> ("Sounds/audioClipName").text;

    byte[] bytes = System.Convert.FromBase64String (s);
    float[] f = ConvertByteToFloat(bytes);

    AudioClip audioClip = AudioClip.Create("testSound", f.Length, 2, 44100, false, false);
    audioClip.SetData(f, 0);

    AudioSource as = GameObject.FindObjectOfType<AudioSource> ();
    as.PlayOneShot (audioClip);
}

private static float[] ConvertByteToFloat(byte[] array) 
{
    float[] floatArr = new float[array.Length / 4];

    for (int i = 0; i < floatArr.Length; i++) 
    {
        if (BitConverter.IsLittleEndian) 
            Array.Reverse(array, i * 4, 4);

        floatArr[i] = BitConverter.ToSingle(array, i * 4);
    }

    return floatArr;
}

Every thing works fine, except the sound is just one noise.

I found this here on stack overflow, but the answer dosnt solve the problem.

Here are details about the wav file from Unity3D:

enter image description here

Does anyone know what the problem is here?

EDIT

I wrote down binary files, one just after decoding from base64, second after final converting, and compared it to the original binary wav file:

enter image description here

As you can see, file was encoded correctly cause just decoding it and writing the file down like this:

string scat = Resources.Load<TextAsset> ("Sounds/test").text;

byte[] bcat = System.Convert.FromBase64String (scat);
System.IO.File.WriteAllBytes ("Assets/just_decoded.wav", bcat);

gave same files. All files have some length.

But the final one is wrong, so the problem is somewhere in converting to float array. But I dont understand what could be wrong.

EDIT:

Here is the code for writing down the final.wav:

string scat = Resources.Load<TextAsset> ("Sounds/test").text;

byte[] bcat = System.Convert.FromBase64String (scat);
float[] f = ConvertByteToFloat(bcat);

byte[] byteArray = new byte[f.Length * 4];
Buffer.BlockCopy(f, 0, byteArray, 0, byteArray.Length);

System.IO.File.WriteAllBytes ("Assets/final.wav", byteArray);
like image 936
Jerry Switalski Avatar asked Feb 05 '16 16:02

Jerry Switalski


2 Answers

The wave file you try to play (meow.wav) has the following properties:

  • PCM
  • 2 channels
  • 44100 Hz
  • signed 16-bit little-endian

Your main mistake is, that you are interpreting the binary data as if it was already representing a float. This is, what BitConverter.ToSingle() does.

But what you need to do is, to create a signed 16-bit little-endian value (as specified in the Wavefile-header) from each two bytes, cast it to a float and then normalize it. And each two bytes make a sample in the case of your file (16-Bit!), not four bytes. The data is little endian (s16le), so you would only have to swap it if the host machine wasn't.

This would be the corrected conversion function:

private static float[] ConvertByteToFloat(byte[] array) {
    float[] floatArr = new float[array.Length / 2];

    for (int i = 0; i < floatArr.Length; i++) {
        floatArr[i] = ((float) BitConverter.ToInt16(array, i * 2))/32768.0;
    }

    return floatArr;
}

And you should skip over the header of your wave-file (The real audio data starts at offset 44).

For a clean solution, you would have to interpret the Wave-header correctly and adapt your operations according to what is specified there (or bail out if it contains unsupported parameters). For example the sample format (bits per sample and endianess), sample rate and number of channels must be taken care of.

like image 51
Ctx Avatar answered Oct 18 '22 00:10

Ctx


According to the documentation here,

The samples should be floats ranging from -1.0f to 1.0f (exceeding these limits will lead to artifacts and undefined behaviour). The sample count is determined by the length of the float array. Use offsetSamples to write into a random position in the clip. If the length from the offset is longer than the clip length, the write will wrap around and write the remaining samples from the start of the clip.

it seems that you have exactly that effect. So I guess you will have to normalize the array before it can be processed.

As you are operating in unity, I am not sure what functionality you can use so I provided a little basic extension method for float arrays:

/// <summary>
/// Normalizes the values within this array.
/// </summary>
/// <param name="data">The array which holds the values to be normalized.</param>
static void Normalize(this float[] data)
{
    float max = float.MinValue;

    // Find maximum
    for (int i = 0; i < data.Length; i++)
    {
        if (Math.Abs(data[i]) > max)
        {
            max = Math.Abs(data[i]);
        }
    }

    // Divide all by max
    for (int i = 0; i < data.Length; i++)
    {
        data[i] = data[i] / max;
    }
}

Use this extension method before further processing the data like so:

byte[] bytes = System.Convert.FromBase64String (s);
float[] f = ConvertByteToFloat(bytes);

// Normalize the values before using them
f.Normalize();

AudioClip audioClip = AudioClip.Create("testSound", f.Length, 2, 44100, false, false);
audioClip.SetData(f, 0);
like image 41
Markus Safar Avatar answered Oct 18 '22 00:10

Markus Safar