163 lines
4.7 KiB
C#
163 lines
4.7 KiB
C#
using System.IO;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright 2014-2021 RenderHeads Ltd. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace RenderHeads.Media.AVProMovieCapture
|
|
{
|
|
public class WavWriter : System.IDisposable
|
|
{
|
|
private static byte[] RIFF_HEADER = new byte[] { 0x52, 0x49, 0x46, 0x46 };
|
|
private static byte[] FORMAT_WAVE = new byte[] { 0x57, 0x41, 0x56, 0x45 };
|
|
private static byte[] FORMAT_TAG = new byte[] { 0x66, 0x6d, 0x74, 0x20 };
|
|
private static byte[] AUDIO_FORMAT_PCM = new byte[] { 0x01, 0x00 };
|
|
private static byte[] AUDIO_FORMAT_FLOAT = new byte[] { 0x03, 0x00 };
|
|
private static byte[] SUBCHUNK_ID = new byte[] { 0x64, 0x61, 0x74, 0x61 };
|
|
private static byte[] FACTCHUNK_ID = new byte[] { 0x66, 0x61, 0x63, 0x74 };
|
|
|
|
private const int BufferDuration = 4;
|
|
|
|
public enum SampleFormat
|
|
{
|
|
PCM16 = 2,
|
|
Float32 = 4,
|
|
}
|
|
|
|
private FileStream _stream;
|
|
private byte[] _outBytes;
|
|
private int _byteCount;
|
|
private int _byteCountTotal;
|
|
private int _channelCount;
|
|
private int _sampleRate;
|
|
private SampleFormat _sampleFormat;
|
|
private int _headerSize;
|
|
|
|
public WavWriter(string path, int channelCount, int sampleRate, SampleFormat sampleFormat = SampleFormat.Float32)
|
|
{
|
|
int bytesPerSample = (int)sampleFormat;
|
|
_outBytes = new byte[sampleRate * bytesPerSample * channelCount * BufferDuration];
|
|
|
|
_channelCount = channelCount;
|
|
_sampleRate = sampleRate;
|
|
_sampleFormat = sampleFormat;
|
|
|
|
_stream = new FileStream(path, System.IO.FileMode.Create);
|
|
WriteHeader(0);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_stream.Seek(0, SeekOrigin.Begin);
|
|
WriteHeader(_byteCountTotal);
|
|
_stream.Close();
|
|
_stream.Dispose();
|
|
_stream = null;
|
|
}
|
|
|
|
public void WriteInterleaved(float[] data, int dataLength = -1)
|
|
{
|
|
if (dataLength < 0)
|
|
{
|
|
dataLength = data.Length;
|
|
}
|
|
if (_sampleFormat == SampleFormat.PCM16)
|
|
{
|
|
// Convert to s16le
|
|
_byteCount = 0;
|
|
for (int i = 0; i < dataLength; i++)
|
|
{
|
|
short shortVal = System.Convert.ToInt16(data[i] * 32767f);
|
|
_outBytes[_byteCount + 0] = (byte)(shortVal & 0xff);
|
|
_outBytes[_byteCount + 1] = (byte)((shortVal >> 8) & 0xff);
|
|
_byteCount += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_byteCount = dataLength * sizeof(float);
|
|
System.Buffer.BlockCopy(data, 0, _outBytes, 0, _byteCount);
|
|
}
|
|
|
|
// Write to stream
|
|
if (_byteCount > 0)
|
|
{
|
|
_stream.Write(_outBytes, 0, _byteCount);
|
|
_byteCountTotal += _byteCount;
|
|
_byteCount = 0;
|
|
}
|
|
}
|
|
|
|
public void WriteHeader(int byteStreamSize)
|
|
{
|
|
int bytesPerSample = (int)_sampleFormat;
|
|
int byteRate = _sampleRate * _channelCount * bytesPerSample;
|
|
int blockAlign = _channelCount * bytesPerSample;
|
|
|
|
_stream.Write(RIFF_HEADER, 0, RIFF_HEADER.Length);
|
|
_stream.Write(PackageInt(byteStreamSize + _headerSize, 4), 0, 4);
|
|
|
|
_stream.Write(FORMAT_WAVE, 0, FORMAT_WAVE.Length);
|
|
|
|
// 'fmt' chunk
|
|
{
|
|
_stream.Write(FORMAT_TAG, 0, FORMAT_TAG.Length);
|
|
|
|
if (_sampleFormat == SampleFormat.PCM16)
|
|
{
|
|
_stream.Write(PackageInt(16, 4), 0, 4);
|
|
_stream.Write(AUDIO_FORMAT_PCM, 0, AUDIO_FORMAT_PCM.Length);
|
|
}
|
|
else
|
|
{
|
|
_stream.Write(PackageInt(18, 4), 0, 4);
|
|
_stream.Write(AUDIO_FORMAT_FLOAT, 0, AUDIO_FORMAT_FLOAT.Length);
|
|
}
|
|
_stream.Write(PackageInt(_channelCount, 2), 0, 2);
|
|
_stream.Write(PackageInt(_sampleRate, 4), 0, 4);
|
|
_stream.Write(PackageInt(byteRate, 4), 0, 4);
|
|
_stream.Write(PackageInt(blockAlign, 2), 0, 2);
|
|
_stream.Write(PackageInt(bytesPerSample * 8), 0, 2);
|
|
if (_sampleFormat == SampleFormat.Float32)
|
|
{
|
|
_stream.Write(PackageInt(0, 2), 0, 2); // Extension size
|
|
}
|
|
}
|
|
|
|
if (_sampleFormat == SampleFormat.Float32)
|
|
{
|
|
// 'fact' chunk
|
|
{
|
|
_stream.Write(FACTCHUNK_ID, 0, FACTCHUNK_ID.Length);
|
|
_stream.Write(PackageInt(4, 4), 0, 4);
|
|
int samplesPerChannel = (byteStreamSize / bytesPerSample);
|
|
_stream.Write(PackageInt(samplesPerChannel, 4), 0, 4);
|
|
}
|
|
}
|
|
|
|
// 'data' chunk
|
|
{
|
|
_stream.Write(SUBCHUNK_ID, 0, SUBCHUNK_ID.Length);
|
|
_stream.Write(PackageInt(byteStreamSize, 4), 0, 4);
|
|
}
|
|
|
|
_headerSize = (int)_stream.Position - 8;
|
|
//UnityEngine.Debug.Log("Header size: " + _headerSize);
|
|
}
|
|
|
|
private static byte[] PackageInt(int source, int length = 2)
|
|
{
|
|
if((length!=2)&&(length!=4))
|
|
throw new System.ArgumentException("length must be either 2 or 4", "length");
|
|
var retVal = new byte[length];
|
|
retVal[0] = (byte)(source & 0xFF);
|
|
retVal[1] = (byte)((source >> 8) & 0xFF);
|
|
if (length == 4)
|
|
{
|
|
retVal[2] = (byte) ((source >> 0x10) & 0xFF);
|
|
retVal[3] = (byte) ((source >> 0x18) & 0xFF);
|
|
}
|
|
return retVal;
|
|
}
|
|
}
|
|
} |