AudioSettings.outputSampleRate on the Android device

(using JS)
My English is bad but I’ll try,

I’m doing a small development on Android, I want to record the sounds of my scene and save it to “WAV”
and for this I took the code from this person: gregzo.

#pragma strict

import System.IO; // for FileStream
import System; // for BitConverter and Byte Type



private var bufferSize : int;
private var numBuffers : int;
private var outputRate : int = 44100;
private var fileName : String = "recTest.wav";
private var headerSize : int = 44; //default for uncompressed wav

private var recOutput : boolean;

private var fileStream : FileStream;

function Awake()
{
    AudioSettings.outputSampleRate = outputRate;
}

function Start()
{
    AudioSettings.GetDSPBufferSize(bufferSize,numBuffers);
}

function Update()
{
    if(Input.GetKeyDown("r"))
    {
    print("rec");
        if(recOutput == false)
        {
            StartWriting(fileName); 
            recOutput = true;
        }
        else
        {
            recOutput = false;
            WriteHeader();      
            print("rec stop");
        }
    }   
}

function StartWriting(name : String)
{
    fileStream = new FileStream(name, FileMode.Create); 
    var emptyByte : byte = new byte();

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

function OnAudioFilterRead(data : float[], channels : int)
{
    if(recOutput)
    {
        ConvertAndWrite(data); //audio data is interlaced
    }
}

function ConvertAndWrite(dataSource : float[])
{

    var intData : Int16[] = new Int16[dataSource.length]; 
//converting in 2 steps : float[] to Int16[], //then Int16[] to Byte[]


    var bytesData : Byte[] = new Byte[dataSource.length*2]; 
//bytesData array is twice the size of 
//dataSource array because a float converted in Int16 is 2 bytes.

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

    for (var i : int = 0; i<dataSource.length;i++)
    {
        intData[i] = dataSource[i]*rescaleFactor;
        var byteArr : Byte[] = new Byte[2];
        byteArr = BitConverter.GetBytes(intData[i]);
        byteArr.CopyTo(bytesData,i*2);
    }
    fileStream.Write(bytesData,0,bytesData.length); 
}

function WriteHeader()
{

    fileStream.Seek(0,SeekOrigin.Begin);

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

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

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

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

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

    var two : UInt16 = 2;
    var one : UInt16 = 1;

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

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

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

    var byteRate : Byte[] = BitConverter.GetBytes(outputRate*4); 
 // sampleRate * bytesPerSample*number of channels, here 44100*2*2

    fileStream.Write(byteRate,0,4);

    var four : UInt16 = 4;
    var blockAlign : Byte[] = BitConverter.GetBytes(four);
    fileStream.Write(blockAlign,0,2);

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

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

    var subChunk2 : Byte[] = BitConverter.GetBytes(fileStream.Length-headerSize);
    fileStream.Write(subChunk2,0,4);

    fileStream.Close();
}

And I put it in “Android” but the recording sounds very fast and sharp.
The problem is that the sample rate is 44.1Khz in the PC and in my Andorid device is 24kHz.

Then I have to change the value of outputRate:

private var outputRate: int = 44100;

as follows:

private var outputRate: int = 24000;

Besides that I had to make other changes to make to save the WAV file and want to share with you:
1. Set the permission on the “AndroidManifest.xml” to create files in the device memory,
this is done with the following configuration:
Player Settings>Android>Configuration>Write Access>External (SDCard)

2. Change line:
[I]fileStream = new FileStream(name, FileMode.Create);[/I]
And put it this way:
[I]fileStream = FileStream(Application.persistentDataPath + "/" + name,FileMode.Create);[/I]
Here I find my WAV file:
/storage/sdcard0/Android/data/com.company.MyApp/files/recTest.wav

3. Delete this update function:

function Update()
{
    if(Input.GetKeyDown("r"))
    {
    print("rec");
        if(recOutput == false)
        {
            StartWriting(fileName); 
            recOutput = true;
        }
        else
        {
            recOutput = false;
            WriteHeader();      
            print("rec stop");
        }
    }   
}

And replace it with this function OnGUI:

function OnGUI()
{
     if(GUI.Button(new Rect(10, 10, 150, 100), NameButton))
    {
         if(recOutput == false)
        {
            StartWriting(fileName); 
            recOutput = true;
            print("rec");
            NameButton= "Stop";
        }
        else
        {
            recOutput = false;
            WriteHeader();      
            print("rec stop");
            NameButton= "finished";
        } 
    }
}

Thank you very much to gregzo for sharing your code and solve my problem of sample rate :smile::smile::smile:

Hi,

A lot of time has passed since I posted that script.

Here’s a new version, in C#, adapted from my audio framework G-Audio - it can both parse wav headers and write wav files.
Both SimpleWavFile and WavWriter classes are in the same file - splitting in seperate files would be nicer.
Also, number of channels is defined as 2. G-Audio provides more options, but for most use cases stereo output is the norm.

SimpleWaveFile does the dirty work, WavWriter encapsulates it in an easy to use Monobehaviour component.

I’ve adapted this quickly without testing, let me know if there are issues!

Cheers,

Gregzo

using UnityEngine;
using System.Collections;
using System.IO;
using System;

public class PublicWavWriter: MonoBehaviour
{
	public string path;
	public bool IsRecording{ get{ return _doWrite; } }
	
	volatile bool _doWrite;
	SimpleWavFile _file;
	
	void Awake()
	{
		
	}
	
	public void StartRecording()
	{
		Debug.Log("start rec");
		_file = new SimpleWavFile( path, WavFileMode.Create );
		_file.PrepareToRecord();
		_doWrite = true;
	}
	
	public void StopRecording()
	{
		_doWrite = false;
		_file.FinalizeRecording();
	}
	
	void OnAudioFilterRead( float[] data, int numChannels )
	{
		if( _doWrite )
		{
			_file.WriteData( data );
		}
	}
	
#if UNITY_EDITOR
	//For testing purposes, press r to toggle recording
	void Update()
	{
		if( Input.GetKeyDown("r"))
		{
			if( IsRecording )
			{
				StopRecording();
			}
			else
			{
				StartRecording();
			}
		}
	}
#endif
}

public enum WavFileMode{ Create, Read }

public class SimpleWavFile
{	
	const int NUM_CHANNELS 		= 2;
	const int HEADER_SIZE  		= 44;
	const int RESCALE_FACTOR 	= 32768;
	
	public string     Path{ get; protected set; }
	public float  Duration{ get; protected set; }
	
	public int  SampleRate{ get; protected set; }
	public int	  BitDepth{ get; protected set; }
	public int  NumSamples{ get; protected set; }
	public int NumChannels{ get; protected set; }
	
	int _blockAlign;
	int _bytesPerSample;
	
	FileStream   _fs;
	BinaryReader _br;
	
	static Int16[] __intBuffer;
	static  byte[] __bytesBuffer;
	
	static readonly byte[] __riffBytes;
	static readonly byte[] __waveBytes;
	static readonly byte[] __fmtBytes;
	static readonly byte[] __dataBytes;

	static SimpleWavFile()
	{
		int bufferSize;
		int numBuffers;
		
		AudioSettings.GetDSPBufferSize( out bufferSize, out numBuffers );
		__intBuffer = new Int16[ bufferSize * NUM_CHANNELS ]; 
		__bytesBuffer = new byte[ __intBuffer.Length * 2 ]; //2 bytes per sample at bit depth 16
		
		__riffBytes = System.Text.Encoding.UTF8.GetBytes("RIFF");
		__waveBytes = System.Text.Encoding.UTF8.GetBytes("WAVE");
		__fmtBytes  = System.Text.Encoding.UTF8.GetBytes("fmt ");
		__dataBytes = System.Text.Encoding.UTF8.GetBytes("data");
	}
	
	public SimpleWavFile( string path, WavFileMode fileMode )
	{
		Path = path;
		
		if( fileMode == WavFileMode.Read )
		{
			ParseHeader();
		}
		else if( fileMode == WavFileMode.Create )
		{
			InitDefaultValues();
		}
	}
	
	public void PrepareToRecord()
	{
		_fs = new FileStream( Path, FileMode.Create );
		_fs.Seek( HEADER_SIZE,SeekOrigin.Begin );
	}
	
	public void FinalizeRecording()
	{
		WriteHeader();
		Close();
	}
	
	public void LogDescription()
	{
		Debug.Log( "rate: "+SampleRate+"_bitdepth: "+BitDepth+" Channels: "+NumChannels+" numSamples: "+NumSamples+" duration: "+Duration );
	}
	
	public void WriteData( float[] data )
	{
		int i;
		
		for( i = 0; i < data.Length; i++ )
		{
			__intBuffer[ i ] = ( short )( data[i] * RESCALE_FACTOR );
		}
		
		Buffer.BlockCopy( __intBuffer, 0, __bytesBuffer, 0, data.Length * _bytesPerSample );
		
		_fs.Write( __bytesBuffer, 0, data.Length * _bytesPerSample );
	}
	
	protected void InitDefaultValues()
	{
		SampleRate  	= AudioSettings.outputSampleRate;
		BitDepth    	= 16;
		_bytesPerSample = BitDepth / 8;
		NumChannels 	= NUM_CHANNELS;
		_blockAlign 	= NumChannels * ( _bytesPerSample );
	}
	
	protected void ParseHeader()
	{
		byte[] bytes;	
		_fs = new FileStream( Path, FileMode.Open );
		_br = new BinaryReader( _fs );
		
		bytes = _br.ReadBytes( 4 ); //ChunkID
		
		if( BytesEqual( bytes, __riffBytes ) == false )
		{
			throw new UnityException("File is not RIFF" );
		}
		
		_br.ReadInt32(); //fileSize
		
		bytes = _br.ReadBytes( 4 ); //format
		
		if( BytesEqual( bytes, __waveBytes ) == false )
		{
			throw new UnityException("File is not WAVE" );
		}
		
		bytes = _br.ReadBytes( 4 );
		
		if( BytesEqual( bytes, __fmtBytes ) == false )
		{
			throw new UnityException( "Header error (subchunk1_ID is not fmt )" );
		}
		
		int fmtSize = _br.ReadInt32();
		
		if ( fmtSize != 16 )//Not PCM!!!
		{
		    throw new UnityException( "Header error: fmt size is not 16." );
		}
		
		Int16 format = _br.ReadInt16();
		
		if( format != 1 )
		{
			throw new UnityException( "Compressed wav files not supported." );
		}
		
		NumChannels = ( int )_br.ReadInt16();
		
		if( NumChannels != 1 )
		{
			throw new UnityException( "Only mono files are supported at the moment" );
		}
		
		SampleRate  = _br.ReadInt32();
		
		_br.ReadInt32(); //byte rate
		
		_blockAlign = _br.ReadInt16(); //block align
		
		BitDepth = _br.ReadInt16();
		_bytesPerSample = BitDepth / 8;
		
		bytes = _br.ReadBytes( 4 );
		
		if( BytesEqual( bytes, __dataBytes ) == false )
		{
			throw new UnityException( "Header error (subchunk2_ID is not data )" );
		}
		
		NumSamples = _br.ReadInt32() / ( _bytesPerSample * NumChannels );
		
		Duration = ( float )NumSamples / SampleRate;
		
		Close();
	}
	
	protected void WriteHeader()
	{
		_fs.Seek( 0, SeekOrigin.Begin );
		NumSamples = ( int )_fs.Length - HEADER_SIZE;
		_fs.Write( __riffBytes, 0, 4 );
		_fs.Write( BitConverter.GetBytes( NumSamples + HEADER_SIZE - 8 ), 0, 4 );
		_fs.Write( __waveBytes, 0, 4 );
		_fs.Write( __fmtBytes, 0, 4 );
		_fs.Write( BitConverter.GetBytes( 16 ), 0, 4 ); //fmtSize, int32 = 16
		_fs.Write( BitConverter.GetBytes( ( ushort )1 ), 0, 2 ); //Audio Format: 1 is PCM( uncompressed )
		_fs.Write( BitConverter.GetBytes( ( ushort )NumChannels ), 0, 2 );
		_fs.Write( BitConverter.GetBytes( SampleRate ), 0, 4 );
		_fs.Write( BitConverter.GetBytes( SampleRate * _blockAlign ), 0, 4 ); //byteRate
		_fs.Write( BitConverter.GetBytes( _blockAlign ), 0, 2 );
		_fs.Write( BitConverter.GetBytes( ( ushort )BitDepth ), 0, 2 );
		_fs.Write( __dataBytes, 0, 4 );
		_fs.Write( BitConverter.GetBytes( NumSamples ), 0, 4 );
		
	}
	
	protected void Close()
	{
		_fs.Close();
		if( _br != null )
			_br.Close();
	}
	
	static bool BytesEqual( byte[] bytes, byte[] comparand )
	{
		int  i;
		
		if( bytes.Length != comparand.Length )
		{
			throw new UnityException( "Lengths don't match!");
		}
		
		for( i = 0; i < bytes.Length; i++ )
		{
			if( bytes[ i ] != comparand[ i ] )
			{
				return false;
			}
		}
		
		return true;
	}
}

hi - thanks for this. bit of a problem - it throws an error here:
Buffer.BlockCopy(__intBuffer, 0, __bytesBuffer, 0, data.Length * _bytesPerSample);

error: ArgumentException: Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.
System.Buffer.BlockCopy (System.Array src, Int32 srcOffset, System.Array dst, Int32 dstOffset, Int32 count) (at /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System/Buffer.cs:112)
SimpleWavFile.WriteData (System.Single[ ] data) (at Assets/Scripts/Test/PublicWavWriter.cs:150)
PublicWavWriter.OnAudioFilterRead (System.Single[ ] data, Int32 numChannels) (at Assets/Scripts/Test/PublicWavWriter.cs:38)

thanks!
warren

Hi Warren,

Sorry about that, silly mistake in __bytesBuffer allocation. Corrected…

I’ll post another correction later tonight which makes it more thread safe-as ot is, there is maybe a risk that the header might be written before the last data chunk. Didn’t in tests, but with threading, better safe than sorry.

Cheers,

Gregzo