Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Unity3D: script to save an AudioClip as a .wav file.
// Copyright (c) 2012 Calvin Rien
// http://the.darktable.com
//
// This software is provided 'as-is', without any express or implied warranty. In
// no event will the authors be held liable for any damages arising from the use
// of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not claim
// that you wrote the original software. If you use this software in a product,
// an acknowledgment in the product documentation would be appreciated but is not
// required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
// =============================================================================
//
// derived from Gregorio Zanon's script
// http://forum.unity3d.com/threads/119295-Writing-AudioListener.GetOutputData-to-wav-problem?p=806734&viewfull=1#post806734
using System;
using System.IO;
using UnityEngine;
using System.Collections.Generic;
public static class SavWav {
const int HEADER_SIZE = 44;
public static bool Save(string filename, AudioClip clip) {
if (!filename.ToLower().EndsWith(".wav")) {
filename += ".wav";
}
var filepath = Path.Combine(Application.persistentDataPath, filename);
Debug.Log(filepath);
// Make sure directory exists if user is saving to sub dir.
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
using (var fileStream = CreateEmpty(filepath)) {
ConvertAndWrite(fileStream, clip);
WriteHeader(fileStream, clip);
}
return true; // TODO: return false if there's a failure saving the file
}
public static AudioClip TrimSilence(AudioClip clip, float min) {
var samples = new float[clip.samples];
clip.GetData(samples, 0);
return TrimSilence(new List<float>(samples), min, clip.channels, clip.frequency);
}
public static AudioClip TrimSilence(List<float> samples, float min, int channels, int hz) {
return TrimSilence(samples, min, channels, hz, false, false);
}
public static AudioClip TrimSilence(List<float> samples, float min, int channels, int hz, bool _3D, bool stream) {
int i;
for (i=0; i<samples.Count; i++) {
if (Mathf.Abs(samples[i]) > min) {
break;
}
}
samples.RemoveRange(0, i);
for (i=samples.Count - 1; i>0; i--) {
if (Mathf.Abs(samples[i]) > min) {
break;
}
}
samples.RemoveRange(i, samples.Count - i);
var clip = AudioClip.Create("TempClip", samples.Count, channels, hz, _3D, stream);
clip.SetData(samples.ToArray(), 0);
return clip;
}
static FileStream CreateEmpty(string filepath) {
var fileStream = new FileStream(filepath, FileMode.Create);
byte emptyByte = new byte();
for(int i = 0; i < HEADER_SIZE; i++) //preparing the header
{
fileStream.WriteByte(emptyByte);
}
return fileStream;
}
static void ConvertAndWrite(FileStream fileStream, AudioClip clip) {
var samples = new float[clip.samples];
clip.GetData(samples, 0);
Int16[] intData = new Int16[samples.Length];
//converting in 2 float[] steps to Int16[], //then Int16[] to Byte[]
Byte[] bytesData = new Byte[samples.Length * 2];
//bytesData array is twice the size of
//dataSource array because a float converted in Int16 is 2 bytes.
int rescaleFactor = 32767; //to convert float to Int16
for (int i = 0; i<samples.Length; i++) {
intData[i] = (short) (samples[i] * rescaleFactor);
Byte[] byteArr = new Byte[2];
byteArr = BitConverter.GetBytes(intData[i]);
byteArr.CopyTo(bytesData, i * 2);
}
fileStream.Write(bytesData, 0, bytesData.Length);
}
static void WriteHeader(FileStream fileStream, AudioClip clip) {
var hz = clip.frequency;
var channels = clip.channels;
var samples = clip.samples;
fileStream.Seek(0, SeekOrigin.Begin);
Byte[] riff = System.Text.Encoding.UTF8.GetBytes("RIFF");
fileStream.Write(riff, 0, 4);
Byte[] chunkSize = BitConverter.GetBytes(fileStream.Length - 8);
fileStream.Write(chunkSize, 0, 4);
Byte[] wave = System.Text.Encoding.UTF8.GetBytes("WAVE");
fileStream.Write(wave, 0, 4);
Byte[] fmt = System.Text.Encoding.UTF8.GetBytes("fmt ");
fileStream.Write(fmt, 0, 4);
Byte[] subChunk1 = BitConverter.GetBytes(16);
fileStream.Write(subChunk1, 0, 4);
UInt16 two = 2;
UInt16 one = 1;
Byte[] audioFormat = BitConverter.GetBytes(one);
fileStream.Write(audioFormat, 0, 2);
Byte[] numChannels = BitConverter.GetBytes(channels);
fileStream.Write(numChannels, 0, 2);
Byte[] sampleRate = BitConverter.GetBytes(hz);
fileStream.Write(sampleRate, 0, 4);
Byte[] byteRate = BitConverter.GetBytes(hz * channels * 2); // sampleRate * bytesPerSample*number of channels, here 44100*2*2
fileStream.Write(byteRate, 0, 4);
UInt16 blockAlign = (ushort) (channels * 2);
fileStream.Write(BitConverter.GetBytes(blockAlign), 0, 2);
UInt16 bps = 16;
Byte[] bitsPerSample = BitConverter.GetBytes(bps);
fileStream.Write(bitsPerSample, 0, 2);
Byte[] datastring = System.Text.Encoding.UTF8.GetBytes("data");
fileStream.Write(datastring, 0, 4);
Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2);
fileStream.Write(subChunk2, 0, 4);
// fileStream.Close();
}
}
@rejahanwer

This comment has been minimized.

Show comment
Hide comment
@rejahanwer

rejahanwer Mar 12, 2013

nice work. a note of thanks to u and gregzo for this.

nice work. a note of thanks to u and gregzo for this.

@PhilGal

This comment has been minimized.

Show comment
Hide comment
@PhilGal

PhilGal Jun 25, 2013

Good job! However I have an optimization tip to suggest.

I tried to apply this class in an Android app where ConvertAndWrite() worked incredibly slow (approx 5sec for a ~1mb file). I came up with the following implementation that does its job in less then a second:

static void ConvertAndWrite(MemoryStream memStream, ClipData clipData)
    {

        var samples = clipData.samples;

        Int16[] intData = new Int16[samples.Length];

        Byte[] bytesData = new Byte[samples.Length * 2];

        const int rescaleFactor = 32767; //to convert float to Int16

        for (int i = 0; i < samples.Length; i++)
        {
            intData[i] = (short)(samples[i] * rescaleFactor);
        }
        Buffer.BlockCopy(intData, 0, bytesData, 0, bytesData.Length);
        memStream.Write(bytesData, 0, bytesData.Length);
    }

ClipData is a struct replacement for the AudioClip class, so we can run the method in a separate thread.

PhilGal commented Jun 25, 2013

Good job! However I have an optimization tip to suggest.

I tried to apply this class in an Android app where ConvertAndWrite() worked incredibly slow (approx 5sec for a ~1mb file). I came up with the following implementation that does its job in less then a second:

static void ConvertAndWrite(MemoryStream memStream, ClipData clipData)
    {

        var samples = clipData.samples;

        Int16[] intData = new Int16[samples.Length];

        Byte[] bytesData = new Byte[samples.Length * 2];

        const int rescaleFactor = 32767; //to convert float to Int16

        for (int i = 0; i < samples.Length; i++)
        {
            intData[i] = (short)(samples[i] * rescaleFactor);
        }
        Buffer.BlockCopy(intData, 0, bytesData, 0, bytesData.Length);
        memStream.Write(bytesData, 0, bytesData.Length);
    }

ClipData is a struct replacement for the AudioClip class, so we can run the method in a separate thread.

@harmonius

This comment has been minimized.

Show comment
Hide comment
@harmonius

harmonius Aug 13, 2013

@ PhilGal,

Can you explain a bit more about ClipData. Is this part of unity?

@ PhilGal,

Can you explain a bit more about ClipData. Is this part of unity?

@chulini

This comment has been minimized.

Show comment
Hide comment
@chulini

chulini Sep 26, 2013

First of all, thanks for this. Saved me lot of work.

Second, I've figure that the conversion only works for mono (1 channel) AudioClips.
To make this work for multichannel AudioClips (like stereo or sorround) I've replaced (line 110):

var samples = new float[clip.samples];

for this:

var samples = new float[clip.samples*clip.channels];

chulini commented Sep 26, 2013

First of all, thanks for this. Saved me lot of work.

Second, I've figure that the conversion only works for mono (1 channel) AudioClips.
To make this work for multichannel AudioClips (like stereo or sorround) I've replaced (line 110):

var samples = new float[clip.samples];

for this:

var samples = new float[clip.samples*clip.channels];
@5argon

This comment has been minimized.

Show comment
Hide comment
@5argon

5argon Oct 10, 2013

Hi, I have tried this and it's working OK but experienced some audio quality loss (The saved .wav file sounds like compressed 128 kbps mp3 file.) which can be fixed by replacing :

int rescaleFactor = 32767; //to convert float to Int16

with this

float rescaleFactor = 32767;

5argon commented Oct 10, 2013

Hi, I have tried this and it's working OK but experienced some audio quality loss (The saved .wav file sounds like compressed 128 kbps mp3 file.) which can be fixed by replacing :

int rescaleFactor = 32767; //to convert float to Int16

with this

float rescaleFactor = 32767;
@tqnst

This comment has been minimized.

Show comment
Hide comment
@tqnst

tqnst Aug 19, 2014

Hi everybody,

Thank you for excellent code to record audio, but I have a problem : possible to save an AudioClip as a .mp4 file? If possible, can anyone suggest me a solution?

tqnst commented Aug 19, 2014

Hi everybody,

Thank you for excellent code to record audio, but I have a problem : possible to save an AudioClip as a .mp4 file? If possible, can anyone suggest me a solution?

@Harteroider

This comment has been minimized.

Show comment
Hide comment
@Harteroider

Harteroider Aug 31, 2014

Thank you for a great work!
Would be cool to add Windows Store Apps support though (FileStream doesn't work on that platform).

Thank you for a great work!
Would be cool to add Windows Store Apps support though (FileStream doesn't work on that platform).

@eyalfx

This comment has been minimized.

Show comment
Hide comment
@eyalfx

eyalfx Oct 3, 2014

Yeah, a compress format would be awesome. but either way, this is a kick ass class.
Thank you.

eyalfx commented Oct 3, 2014

Yeah, a compress format would be awesome. but either way, this is a kick ass class.
Thank you.

@GMAlfonso

This comment has been minimized.

Show comment
Hide comment
@GMAlfonso

GMAlfonso Jan 28, 2015

thanks!
but I have a problem. is it possible to trip/cut the audiosoucre.audio.clip when i record it with the microphone?
cause when I start to record with the mic. Microphone.Start(null, true, 10, maxFreq);
then stopped after 5 sec. it saves the audio but its a 10 second audioclip.
is it possible to save at runtime?

thanks!
but I have a problem. is it possible to trip/cut the audiosoucre.audio.clip when i record it with the microphone?
cause when I start to record with the mic. Microphone.Start(null, true, 10, maxFreq);
then stopped after 5 sec. it saves the audio but its a 10 second audioclip.
is it possible to save at runtime?

@mitay-walle

This comment has been minimized.

Show comment
Hide comment
@mitay-walle

mitay-walle Feb 8, 2015

Hi all, I've recreated couple of function with PhilGal Threading.

struct ClipData
{
    public int samples;
}
    private Thread WritingThread;

    void ConvertAndWrite(MemoryStream memStream, ClipData clipData)
    {
        float[] samples = new float[clipData.samples];

        Int16[] intData = new Int16[samples.Length];

        Byte[] bytesData = new Byte[samples.Length * 2];

        const float rescaleFactor = 32767; //to convert float to Int16

        for (int i = 0; i < samples.Length; i++)
        {
            intData[i] = (short)(samples[i] * rescaleFactor);
        }
        Buffer.BlockCopy(intData, 0, bytesData, 0, bytesData.Length);
        memStream.Write(bytesData, 0, bytesData.Length);
    }

    public AudioClip Save(string filename, AudioClip clip)
    {
        if (!filename.ToLower().EndsWith(".wav"))
            filename += ".wav";
        string filepath = Path.Combine(Application.persistentDataPath, filename);
        Debug.Log(filepath);
        Directory.CreateDirectory(Path.GetDirectoryName(filepath));
        ClipData clipData = new ClipData();
        clipData.samples = clip.samples;
        using (FileStream fileStream = CreateEmpty(filepath))
        {
            MemoryStream memStream = new MemoryStream();
            WritingThread = new Thread(() => ConvertAndWrite(memStream, clipData));
            memStream.WriteTo(fileStream);
            WriteHeader(fileStream, clip);
        }
        return clip;
    }

Hi all, I've recreated couple of function with PhilGal Threading.

struct ClipData
{
    public int samples;
}
    private Thread WritingThread;

    void ConvertAndWrite(MemoryStream memStream, ClipData clipData)
    {
        float[] samples = new float[clipData.samples];

        Int16[] intData = new Int16[samples.Length];

        Byte[] bytesData = new Byte[samples.Length * 2];

        const float rescaleFactor = 32767; //to convert float to Int16

        for (int i = 0; i < samples.Length; i++)
        {
            intData[i] = (short)(samples[i] * rescaleFactor);
        }
        Buffer.BlockCopy(intData, 0, bytesData, 0, bytesData.Length);
        memStream.Write(bytesData, 0, bytesData.Length);
    }

    public AudioClip Save(string filename, AudioClip clip)
    {
        if (!filename.ToLower().EndsWith(".wav"))
            filename += ".wav";
        string filepath = Path.Combine(Application.persistentDataPath, filename);
        Debug.Log(filepath);
        Directory.CreateDirectory(Path.GetDirectoryName(filepath));
        ClipData clipData = new ClipData();
        clipData.samples = clip.samples;
        using (FileStream fileStream = CreateEmpty(filepath))
        {
            MemoryStream memStream = new MemoryStream();
            WritingThread = new Thread(() => ConvertAndWrite(memStream, clipData));
            memStream.WriteTo(fileStream);
            WriteHeader(fileStream, clip);
        }
        return clip;
    }
@worla

This comment has been minimized.

Show comment
Hide comment
@worla

worla Mar 15, 2015

hi, i already have a script that records sound from a microphone and plays back. I am wondering how i can incorporate this in my code so i just don't play back but also save as a wav file.

Here is my code:

using UnityEngine;  
using System.Collections;  

   [RequireComponent (typeof (AudioSource))]  

   public class VoiceChat : Photon.MonoBehaviour   
   {  
//A boolean that flags whether there's a connected microphone  
private bool micConnected = false;  

//The maximum and minimum available recording frequencies  
private int minFreq;  
private int maxFreq;  

//A handle to the attached AudioSource  
private AudioSource goAudioSource;  

//Use this for initialization  
void Start()   
{  
    //Check if there is at least one microphone connected  
    if(Microphone.devices.Length <= 0)  
    {  
        //Throw a warning message at the console if there isn't  
        Debug.LogWarning("Microphone not connected!");  
    }  
    else //At least one microphone is present  
    {  
        //Set 'micConnected' to true  
        micConnected = true;  

        //Get the default microphone recording capabilities  
        Microphone.GetDeviceCaps(null, out minFreq, out maxFreq);  

        //According to the documentation, if minFreq and maxFreq are zero, the microphone supports any frequency...  
        if(minFreq == 0 && maxFreq == 0)  
        {  
            //...meaning 44100 Hz can be used as the recording sampling rate  
            maxFreq = 44100;  
        }  

        //Get the attached AudioSource component  
        goAudioSource = this.GetComponent<AudioSource>();  
    }  
}  

void OnGUI()   
{  
    //If there is a microphone  
    if(micConnected)  
    {  
        //If the audio from any microphone isn't being captured  
        if(!Microphone.IsRecording(null))  
        {  
            //Case the 'Record' button gets pressed  
            if(GUI.Button(new Rect(Screen.width/2-100, Screen.height/2-25, 200, 50), "Record"))  
            {  
                //Start recording and store the audio captured from the microphone at the AudioClip in the AudioSource  
                goAudioSource.clip = Microphone.Start(null, true, 20, maxFreq);  
            }  
        }  
        else //Recording is in progress  
        {  
            //Case the 'Stop and Play' button gets pressed  
            if(GUI.Button(new Rect(Screen.width/2-100, Screen.height/2-25, 200, 50), "Stop and Play!"))  
            {  
                Microphone.End(null); //Stop the audio recording  
                goAudioSource.Play(); //Playback the recorded audio  
            }  

            GUI.Label(new Rect(Screen.width/2-100, Screen.height/2+25, 200, 50), "Recording in progress...");  
        }  
    }  
    else // No microphone  
    {  
        //Print a red "Microphone not connected!" message at the center of the screen  
        GUI.contentColor = Color.red;  
        GUI.Label(new Rect(Screen.width/2-100, Screen.height/2-25, 200, 50), "Microphone not connected!");  
    }  

}  

}

worla commented Mar 15, 2015

hi, i already have a script that records sound from a microphone and plays back. I am wondering how i can incorporate this in my code so i just don't play back but also save as a wav file.

Here is my code:

using UnityEngine;  
using System.Collections;  

   [RequireComponent (typeof (AudioSource))]  

   public class VoiceChat : Photon.MonoBehaviour   
   {  
//A boolean that flags whether there's a connected microphone  
private bool micConnected = false;  

//The maximum and minimum available recording frequencies  
private int minFreq;  
private int maxFreq;  

//A handle to the attached AudioSource  
private AudioSource goAudioSource;  

//Use this for initialization  
void Start()   
{  
    //Check if there is at least one microphone connected  
    if(Microphone.devices.Length <= 0)  
    {  
        //Throw a warning message at the console if there isn't  
        Debug.LogWarning("Microphone not connected!");  
    }  
    else //At least one microphone is present  
    {  
        //Set 'micConnected' to true  
        micConnected = true;  

        //Get the default microphone recording capabilities  
        Microphone.GetDeviceCaps(null, out minFreq, out maxFreq);  

        //According to the documentation, if minFreq and maxFreq are zero, the microphone supports any frequency...  
        if(minFreq == 0 && maxFreq == 0)  
        {  
            //...meaning 44100 Hz can be used as the recording sampling rate  
            maxFreq = 44100;  
        }  

        //Get the attached AudioSource component  
        goAudioSource = this.GetComponent<AudioSource>();  
    }  
}  

void OnGUI()   
{  
    //If there is a microphone  
    if(micConnected)  
    {  
        //If the audio from any microphone isn't being captured  
        if(!Microphone.IsRecording(null))  
        {  
            //Case the 'Record' button gets pressed  
            if(GUI.Button(new Rect(Screen.width/2-100, Screen.height/2-25, 200, 50), "Record"))  
            {  
                //Start recording and store the audio captured from the microphone at the AudioClip in the AudioSource  
                goAudioSource.clip = Microphone.Start(null, true, 20, maxFreq);  
            }  
        }  
        else //Recording is in progress  
        {  
            //Case the 'Stop and Play' button gets pressed  
            if(GUI.Button(new Rect(Screen.width/2-100, Screen.height/2-25, 200, 50), "Stop and Play!"))  
            {  
                Microphone.End(null); //Stop the audio recording  
                goAudioSource.Play(); //Playback the recorded audio  
            }  

            GUI.Label(new Rect(Screen.width/2-100, Screen.height/2+25, 200, 50), "Recording in progress...");  
        }  
    }  
    else // No microphone  
    {  
        //Print a red "Microphone not connected!" message at the center of the screen  
        GUI.contentColor = Color.red;  
        GUI.Label(new Rect(Screen.width/2-100, Screen.height/2-25, 200, 50), "Microphone not connected!");  
    }  

}  

}

@ChiefLD

This comment has been minimized.

Show comment
Hide comment
@ChiefLD

ChiefLD May 1, 2015

i know im late to this party, but, is it possible to then load the .wav file back as an audioclip during runtime?

ChiefLD commented May 1, 2015

i know im late to this party, but, is it possible to then load the .wav file back as an audioclip during runtime?

@Jakhongir91

This comment has been minimized.

Show comment
Hide comment
@Jakhongir91

Jakhongir91 May 11, 2015

.wav files can be loaded as an audioClip in several ways, this is how I do it

        AudioClip audioClip = Resources.Load ("voiceRecord") as AudioClip;
        audioSource.audio.clip = audioClip;
audioSource.audio.Play ();

.wav files can be loaded as an audioClip in several ways, this is how I do it

        AudioClip audioClip = Resources.Load ("voiceRecord") as AudioClip;
        audioSource.audio.clip = audioClip;
audioSource.audio.Play ();
@Jakhongir91

This comment has been minimized.

Show comment
Hide comment
@Jakhongir91

Jakhongir91 May 11, 2015

can anyone help me to rewright the same script to save audioClips as .mp3

can anyone help me to rewright the same script to save audioClips as .mp3

@Lomyo

This comment has been minimized.

Show comment
Hide comment
@Lomyo

Lomyo Jun 11, 2015

NIce,Its very helpful,Thx

Lomyo commented Jun 11, 2015

NIce,Its very helpful,Thx

@nsathish16

This comment has been minimized.

Show comment
Hide comment
@nsathish16

nsathish16 Sep 29, 2015

I have used this script to save audio wav file. The above code records a wav file and saves but when we try to play it back the audio quality and modulation of recorded voice is changed but it plays the recorded one.

I need to convert this wav file to base 64 and pass it it to ispeech api but it says as bad audio data. so we contacted the support team of ispeech they said us to set sample rate = 16khz bit rate = 16 bit channel = mono

How can I set these values while writing a wav.

I have used this script to save audio wav file. The above code records a wav file and saves but when we try to play it back the audio quality and modulation of recorded voice is changed but it plays the recorded one.

I need to convert this wav file to base 64 and pass it it to ispeech api but it says as bad audio data. so we contacted the support team of ispeech they said us to set sample rate = 16khz bit rate = 16 bit channel = mono

How can I set these values while writing a wav.

@MrAnonymous110

This comment has been minimized.

Show comment
Hide comment
@MrAnonymous110

MrAnonymous110 Mar 28, 2016

Why in function WriteHeader have comment out:
// fileStream.Close();
can it make memory leak, can you explain for me, thanks you

Why in function WriteHeader have comment out:
// fileStream.Close();
can it make memory leak, can you explain for me, thanks you

@DaganBog2fast

This comment has been minimized.

Show comment
Hide comment
@DaganBog2fast

DaganBog2fast Jun 7, 2016

Is there a nice way to reduce file size?

Is there a nice way to reduce file size?

@BeatUpir

This comment has been minimized.

Show comment
Hide comment
@BeatUpir

BeatUpir Nov 20, 2016

this code does the job in seconds!

using System;
using System.IO;
using UnityEngine;
using System.Collections.Generic;
using System.Threading;

public class SavWav {

	const int HEADER_SIZE = 44;
	struct ClipData{

		public  int samples;
		public  int channels;
		public float[] samplesData;

	}

	public bool Save(string filename, AudioClip clip) {
		if (!filename.ToLower().EndsWith(".wav")) {
			filename += ".wav";
		}

		var filepath = filename;

		Debug.Log(filepath);

		// Make sure directory exists if user is saving to sub dir.
		Directory.CreateDirectory(Path.GetDirectoryName(filepath));
		ClipData clipdata = new ClipData ();
		clipdata.samples = clip.samples;
		clipdata.channels = clip.channels;
		float[] dataFloat = new float[clip.samples*clip.channels];
		clip.GetData (dataFloat, 0);
		clipdata.samplesData = dataFloat;
		using (var fileStream = CreateEmpty(filepath)) {
			MemoryStream memstrm = new MemoryStream ();
			ConvertAndWrite(memstrm, clipdata);
			memstrm.WriteTo (fileStream);
			WriteHeader(fileStream, clip);
		}

		return true; // TODO: return false if there's a failure saving the file
	}

	public AudioClip TrimSilence(AudioClip clip, float min) {
		var samples = new float[clip.samples];

		clip.GetData(samples, 0);

		return TrimSilence(new List<float>(samples), min, clip.channels, clip.frequency);
	}

	public AudioClip TrimSilence(List<float> samples, float min, int channels, int hz) {
		return TrimSilence(samples, min, channels, hz, false, false);
	}

	public AudioClip TrimSilence(List<float> samples, float min, int channels, int hz, bool _3D, bool stream) {
		int i;

		for (i=0; i<samples.Count; i++) {
			if (Mathf.Abs(samples[i]) > min) {
				break;
			}
		}

		samples.RemoveRange(0, i);

		for (i=samples.Count - 1; i>0; i--) {
			if (Mathf.Abs(samples[i]) > min) {
				break;
			}
		}

		samples.RemoveRange(i, samples.Count - i);

		var clip = AudioClip.Create("TempClip", samples.Count, channels, hz, _3D, stream);

		clip.SetData(samples.ToArray(), 0);

		return clip;
	}

	 FileStream CreateEmpty(string filepath) {
		var fileStream = new FileStream(filepath, FileMode.Create);
	    byte emptyByte = new byte();

	    for(int i = 0; i < HEADER_SIZE; i++) //preparing the header
	    {
	        fileStream.WriteByte(emptyByte);
	    }

		return fileStream;
	}

	void ConvertAndWrite(MemoryStream memStream, ClipData clipData)
	{
		float[] samples = new float[clipData.samples*clipData.channels];

		samples = clipData.samplesData;

		Int16[] intData = new Int16[samples.Length];

		Byte[] bytesData = new Byte[samples.Length * 2];

		const float rescaleFactor = 32767; //to convert float to Int16

		for (int i = 0; i < samples.Length; i++)
		{
			intData[i] = (short)(samples[i] * rescaleFactor);
			//Debug.Log (samples [i]);
		}
		Buffer.BlockCopy(intData, 0, bytesData, 0, bytesData.Length);
		memStream.Write(bytesData, 0, bytesData.Length);
	}

	 void WriteHeader(FileStream fileStream, AudioClip clip) {

		var hz = clip.frequency;
		var channels = clip.channels;
		var samples = clip.samples;

		fileStream.Seek(0, SeekOrigin.Begin);

		Byte[] riff = System.Text.Encoding.UTF8.GetBytes("RIFF");
		fileStream.Write(riff, 0, 4);

		Byte[] chunkSize = BitConverter.GetBytes(fileStream.Length - 8);
		fileStream.Write(chunkSize, 0, 4);

		Byte[] wave = System.Text.Encoding.UTF8.GetBytes("WAVE");
		fileStream.Write(wave, 0, 4);

		Byte[] fmt = System.Text.Encoding.UTF8.GetBytes("fmt ");
		fileStream.Write(fmt, 0, 4);

		Byte[] subChunk1 = BitConverter.GetBytes(16);
		fileStream.Write(subChunk1, 0, 4);

		UInt16 two = 2;
		UInt16 one = 1;

		Byte[] audioFormat = BitConverter.GetBytes(one);
		fileStream.Write(audioFormat, 0, 2);

		Byte[] numChannels = BitConverter.GetBytes(channels);
		fileStream.Write(numChannels, 0, 2);

		Byte[] sampleRate = BitConverter.GetBytes(hz);
		fileStream.Write(sampleRate, 0, 4);

		Byte[] byteRate = BitConverter.GetBytes(hz * channels * 2); // sampleRate * bytesPerSample*number of channels, here 44100*2*2
		fileStream.Write(byteRate, 0, 4);

		UInt16 blockAlign = (ushort) (channels * 2);
		fileStream.Write(BitConverter.GetBytes(blockAlign), 0, 2);

		UInt16 bps = 16;
		Byte[] bitsPerSample = BitConverter.GetBytes(bps);
		fileStream.Write(bitsPerSample, 0, 2);

		Byte[] datastring = System.Text.Encoding.UTF8.GetBytes("data");
		fileStream.Write(datastring, 0, 4);

		Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2);
		fileStream.Write(subChunk2, 0, 4);

//		fileStream.Close();
	}
}

this code does the job in seconds!

using System;
using System.IO;
using UnityEngine;
using System.Collections.Generic;
using System.Threading;

public class SavWav {

	const int HEADER_SIZE = 44;
	struct ClipData{

		public  int samples;
		public  int channels;
		public float[] samplesData;

	}

	public bool Save(string filename, AudioClip clip) {
		if (!filename.ToLower().EndsWith(".wav")) {
			filename += ".wav";
		}

		var filepath = filename;

		Debug.Log(filepath);

		// Make sure directory exists if user is saving to sub dir.
		Directory.CreateDirectory(Path.GetDirectoryName(filepath));
		ClipData clipdata = new ClipData ();
		clipdata.samples = clip.samples;
		clipdata.channels = clip.channels;
		float[] dataFloat = new float[clip.samples*clip.channels];
		clip.GetData (dataFloat, 0);
		clipdata.samplesData = dataFloat;
		using (var fileStream = CreateEmpty(filepath)) {
			MemoryStream memstrm = new MemoryStream ();
			ConvertAndWrite(memstrm, clipdata);
			memstrm.WriteTo (fileStream);
			WriteHeader(fileStream, clip);
		}

		return true; // TODO: return false if there's a failure saving the file
	}

	public AudioClip TrimSilence(AudioClip clip, float min) {
		var samples = new float[clip.samples];

		clip.GetData(samples, 0);

		return TrimSilence(new List<float>(samples), min, clip.channels, clip.frequency);
	}

	public AudioClip TrimSilence(List<float> samples, float min, int channels, int hz) {
		return TrimSilence(samples, min, channels, hz, false, false);
	}

	public AudioClip TrimSilence(List<float> samples, float min, int channels, int hz, bool _3D, bool stream) {
		int i;

		for (i=0; i<samples.Count; i++) {
			if (Mathf.Abs(samples[i]) > min) {
				break;
			}
		}

		samples.RemoveRange(0, i);

		for (i=samples.Count - 1; i>0; i--) {
			if (Mathf.Abs(samples[i]) > min) {
				break;
			}
		}

		samples.RemoveRange(i, samples.Count - i);

		var clip = AudioClip.Create("TempClip", samples.Count, channels, hz, _3D, stream);

		clip.SetData(samples.ToArray(), 0);

		return clip;
	}

	 FileStream CreateEmpty(string filepath) {
		var fileStream = new FileStream(filepath, FileMode.Create);
	    byte emptyByte = new byte();

	    for(int i = 0; i < HEADER_SIZE; i++) //preparing the header
	    {
	        fileStream.WriteByte(emptyByte);
	    }

		return fileStream;
	}

	void ConvertAndWrite(MemoryStream memStream, ClipData clipData)
	{
		float[] samples = new float[clipData.samples*clipData.channels];

		samples = clipData.samplesData;

		Int16[] intData = new Int16[samples.Length];

		Byte[] bytesData = new Byte[samples.Length * 2];

		const float rescaleFactor = 32767; //to convert float to Int16

		for (int i = 0; i < samples.Length; i++)
		{
			intData[i] = (short)(samples[i] * rescaleFactor);
			//Debug.Log (samples [i]);
		}
		Buffer.BlockCopy(intData, 0, bytesData, 0, bytesData.Length);
		memStream.Write(bytesData, 0, bytesData.Length);
	}

	 void WriteHeader(FileStream fileStream, AudioClip clip) {

		var hz = clip.frequency;
		var channels = clip.channels;
		var samples = clip.samples;

		fileStream.Seek(0, SeekOrigin.Begin);

		Byte[] riff = System.Text.Encoding.UTF8.GetBytes("RIFF");
		fileStream.Write(riff, 0, 4);

		Byte[] chunkSize = BitConverter.GetBytes(fileStream.Length - 8);
		fileStream.Write(chunkSize, 0, 4);

		Byte[] wave = System.Text.Encoding.UTF8.GetBytes("WAVE");
		fileStream.Write(wave, 0, 4);

		Byte[] fmt = System.Text.Encoding.UTF8.GetBytes("fmt ");
		fileStream.Write(fmt, 0, 4);

		Byte[] subChunk1 = BitConverter.GetBytes(16);
		fileStream.Write(subChunk1, 0, 4);

		UInt16 two = 2;
		UInt16 one = 1;

		Byte[] audioFormat = BitConverter.GetBytes(one);
		fileStream.Write(audioFormat, 0, 2);

		Byte[] numChannels = BitConverter.GetBytes(channels);
		fileStream.Write(numChannels, 0, 2);

		Byte[] sampleRate = BitConverter.GetBytes(hz);
		fileStream.Write(sampleRate, 0, 4);

		Byte[] byteRate = BitConverter.GetBytes(hz * channels * 2); // sampleRate * bytesPerSample*number of channels, here 44100*2*2
		fileStream.Write(byteRate, 0, 4);

		UInt16 blockAlign = (ushort) (channels * 2);
		fileStream.Write(BitConverter.GetBytes(blockAlign), 0, 2);

		UInt16 bps = 16;
		Byte[] bitsPerSample = BitConverter.GetBytes(bps);
		fileStream.Write(bitsPerSample, 0, 2);

		Byte[] datastring = System.Text.Encoding.UTF8.GetBytes("data");
		fileStream.Write(datastring, 0, 4);

		Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2);
		fileStream.Write(subChunk2, 0, 4);

//		fileStream.Close();
	}
}
@BeatUpir

This comment has been minimized.

Show comment
Hide comment
@BeatUpir

BeatUpir Nov 20, 2016

also I have gathered some libraries and made a package to save audioclip to mp3!

you can find it here: https://github.com/BeatUpir/Unity3D-save-audioClip-to-MP3

also I have gathered some libraries and made a package to save audioclip to mp3!

you can find it here: https://github.com/BeatUpir/Unity3D-save-audioClip-to-MP3

@CanisLupus

This comment has been minimized.

Show comment
Hide comment
@CanisLupus

CanisLupus Jan 20, 2017

Just noting that Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2); is incorrect. The variable samples comes from clip.samples, which is the total number of samples with all channels already included. This means that multiplying by channels will effectively double the expected number of samples if your clip has 2 channels, which is wrong. It should simply be samples * 2.

The result of this bug is that some audio players will report the wav file as being double its real duration, as they may read only this value and not the size of the data section itself (i.e. the samples). Most of them will stop the clip "midway", when the samples themselves end, because there are no more samples to play.

CanisLupus commented Jan 20, 2017

Just noting that Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2); is incorrect. The variable samples comes from clip.samples, which is the total number of samples with all channels already included. This means that multiplying by channels will effectively double the expected number of samples if your clip has 2 channels, which is wrong. It should simply be samples * 2.

The result of this bug is that some audio players will report the wav file as being double its real duration, as they may read only this value and not the size of the data section itself (i.e. the samples). Most of them will stop the clip "midway", when the samples themselves end, because there are no more samples to play.

@tammyhuang

This comment has been minimized.

Show comment
Hide comment
@tammyhuang

tammyhuang Jan 31, 2017

I have two questions after tried the original code in this post. It worked with saving a WAV file onto my disk. However, I can't trim the silence at the beginning and the end of the wav file. Each wav file saved is with 30 seconds (if Byte[] subChunk2 = BitConverter.GetBytes(samples * channels)), or 1 minute (if Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2)). How to solve this problem?

Is there a modified code to convert the source AudioClip into FLAC format? I can only use FLAC format for my application.

Thanks and hope to get help from you guys!

I have two questions after tried the original code in this post. It worked with saving a WAV file onto my disk. However, I can't trim the silence at the beginning and the end of the wav file. Each wav file saved is with 30 seconds (if Byte[] subChunk2 = BitConverter.GetBytes(samples * channels)), or 1 minute (if Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2)). How to solve this problem?

Is there a modified code to convert the source AudioClip into FLAC format? I can only use FLAC format for my application.

Thanks and hope to get help from you guys!

@infinitypbr

This comment has been minimized.

Show comment
Hide comment
@infinitypbr

infinitypbr Feb 7, 2017

@CanisLupus

Correct, I recently found this as well. iTunes would play it normal, but Unity would play it twice as long, with the 2nd half silent.

@CanisLupus

Correct, I recently found this as well. iTunes would play it normal, but Unity would play it twice as long, with the 2nd half silent.

@infinitypbr

This comment has been minimized.

Show comment
Hide comment
@infinitypbr

infinitypbr Feb 7, 2017

Question: Has anyone had issues with volume being low? I have a version that combines arrays of audio clips:

https://github.com/infinitypbr/AudioClipArrayCombiner

Basically if each "Layer" has 5 clips, the script will export 125 variations, every possible combination of them. It has options for volume & delay as well. It works great with small clips.

But, when I try to combine some song layers, basically 2 minutes each, the result sounds right, but is much quieter than it should be. Any thoughts?

Question: Has anyone had issues with volume being low? I have a version that combines arrays of audio clips:

https://github.com/infinitypbr/AudioClipArrayCombiner

Basically if each "Layer" has 5 clips, the script will export 125 variations, every possible combination of them. It has options for volume & delay as well. It works great with small clips.

But, when I try to combine some song layers, basically 2 minutes each, the result sounds right, but is much quieter than it should be. Any thoughts?

@sadajpe

This comment has been minimized.

Show comment
Hide comment
@sadajpe

sadajpe Aug 17, 2017

hi
Can I load saved audio into my audio clip?

sadajpe commented Aug 17, 2017

hi
Can I load saved audio into my audio clip?

@Eldoir

This comment has been minimized.

Show comment
Hide comment
@Eldoir

Eldoir Jan 11, 2018

Really good script.
However, I recommend you set the return type of the Save method to void, as that method currently always returns true, which is absolutely non-informative.
There is also an extra empty line in the method, which is useless to me.
And as a C# developer, I virtually die when I read the var keyword. C# is a strongly-typed language, so go ahead and use it the way it was designed for! (I'm always open to discuss further on this, though)
I would put the comment of CreateDirectory() on the same line as the method call, to prevent leaving the useless comment alone if ever its associated code was moved around, or copied in another place.
Finally, you probably want the recording file to be not longer than the actual recorded audio, e.g. if you declared the clip to be 120s long with Microphone.Start(deviceName, false, 120, sampleRate);, the recording file will be 120s long no matter what, even if you only recorded actual audio, let's say, the first 6 seconds. You need to use the TrimSilence() method in order to cut the recording file right after no audio is detected.
Thus, to fulfill my needs, the Save() method becomes (I also removed the Debug.Log):

public static void Save(string filename, AudioClip clip, bool makeClipShort = true)
{
    if (!filename.ToLower().EndsWith(".wav"))
    {
        filename += ".wav";
    }

    string filepath = Path.Combine(Application.persistentDataPath, filename);

    Directory.CreateDirectory(Path.GetDirectoryName(filepath)); // Make sure directory exists if user is saving to sub dir.

    if (makeClipShort)
    {
        clip = TrimSilence(clip, 0);
    }

    using (var fileStream = CreateEmpty(filepath))
    {
        ConvertAndWrite(fileStream, clip);

        WriteHeader(fileStream, clip);
    }
}

I'd be happy if this code sample saved you some time.
Cheers!

Eldoir commented Jan 11, 2018

Really good script.
However, I recommend you set the return type of the Save method to void, as that method currently always returns true, which is absolutely non-informative.
There is also an extra empty line in the method, which is useless to me.
And as a C# developer, I virtually die when I read the var keyword. C# is a strongly-typed language, so go ahead and use it the way it was designed for! (I'm always open to discuss further on this, though)
I would put the comment of CreateDirectory() on the same line as the method call, to prevent leaving the useless comment alone if ever its associated code was moved around, or copied in another place.
Finally, you probably want the recording file to be not longer than the actual recorded audio, e.g. if you declared the clip to be 120s long with Microphone.Start(deviceName, false, 120, sampleRate);, the recording file will be 120s long no matter what, even if you only recorded actual audio, let's say, the first 6 seconds. You need to use the TrimSilence() method in order to cut the recording file right after no audio is detected.
Thus, to fulfill my needs, the Save() method becomes (I also removed the Debug.Log):

public static void Save(string filename, AudioClip clip, bool makeClipShort = true)
{
    if (!filename.ToLower().EndsWith(".wav"))
    {
        filename += ".wav";
    }

    string filepath = Path.Combine(Application.persistentDataPath, filename);

    Directory.CreateDirectory(Path.GetDirectoryName(filepath)); // Make sure directory exists if user is saving to sub dir.

    if (makeClipShort)
    {
        clip = TrimSilence(clip, 0);
    }

    using (var fileStream = CreateEmpty(filepath))
    {
        ConvertAndWrite(fileStream, clip);

        WriteHeader(fileStream, clip);
    }
}

I'd be happy if this code sample saved you some time.
Cheers!

@XY01

This comment has been minimized.

Show comment
Hide comment
@XY01

XY01 Apr 29, 2018

Hey, thanks for the script. I put together a little repo using this script along with the ability to record and load files if anyone is interested.

https://github.com/EXP-Productions/Unity-Audio_Recording_and_serialization_example/

XY01 commented Apr 29, 2018

Hey, thanks for the script. I put together a little repo using this script along with the ability to record and load files if anyone is interested.

https://github.com/EXP-Productions/Unity-Audio_Recording_and_serialization_example/

@kwalkerk

This comment has been minimized.

Show comment
Hide comment
@kwalkerk

kwalkerk May 30, 2018

I'm using the script and it works quite well with one glitch for me. It truncates the files. I can't see anything in the code which limits the file size of the clip. Am I missing something?

I'm using the script and it works quite well with one glitch for me. It truncates the files. I can't see anything in the code which limits the file size of the clip. Am I missing something?

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