WavWriter.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. using System.IO;
  2. //-----------------------------------------------------------------------------
  3. // Copyright 2014-2022 RenderHeads Ltd. All rights reserved.
  4. //-----------------------------------------------------------------------------
  5. namespace RenderHeads.Media.AVProMovieCapture
  6. {
  7. public class WavWriter : System.IDisposable
  8. {
  9. private static byte[] RIFF_HEADER = new byte[] { 0x52, 0x49, 0x46, 0x46 };
  10. private static byte[] FORMAT_WAVE = new byte[] { 0x57, 0x41, 0x56, 0x45 };
  11. private static byte[] FORMAT_TAG = new byte[] { 0x66, 0x6d, 0x74, 0x20 };
  12. private static byte[] AUDIO_FORMAT_PCM = new byte[] { 0x01, 0x00 };
  13. private static byte[] AUDIO_FORMAT_FLOAT = new byte[] { 0x03, 0x00 };
  14. private static byte[] SUBCHUNK_ID = new byte[] { 0x64, 0x61, 0x74, 0x61 };
  15. private static byte[] FACTCHUNK_ID = new byte[] { 0x66, 0x61, 0x63, 0x74 };
  16. private const int BufferDuration = 4;
  17. public enum SampleFormat
  18. {
  19. PCM16 = 2,
  20. Float32 = 4,
  21. }
  22. private FileStream _stream;
  23. private byte[] _outBytes;
  24. private int _byteCount;
  25. private int _byteCountTotal;
  26. private int _channelCount;
  27. private int _sampleRate;
  28. private SampleFormat _sampleFormat;
  29. private int _headerSize;
  30. public WavWriter(string path, int channelCount, int sampleRate, SampleFormat sampleFormat = SampleFormat.Float32)
  31. {
  32. int bytesPerSample = (int)sampleFormat;
  33. _outBytes = new byte[sampleRate * bytesPerSample * channelCount * BufferDuration];
  34. _channelCount = channelCount;
  35. _sampleRate = sampleRate;
  36. _sampleFormat = sampleFormat;
  37. _stream = new FileStream(path, System.IO.FileMode.Create);
  38. WriteHeader(0);
  39. }
  40. public void Dispose()
  41. {
  42. _stream.Seek(0, SeekOrigin.Begin);
  43. WriteHeader(_byteCountTotal);
  44. _stream.Close();
  45. _stream.Dispose();
  46. _stream = null;
  47. }
  48. public void WriteInterleaved(float[] data, int dataLength = -1)
  49. {
  50. if (dataLength < 0)
  51. {
  52. dataLength = data.Length;
  53. }
  54. if (_sampleFormat == SampleFormat.PCM16)
  55. {
  56. // Convert to s16le
  57. _byteCount = 0;
  58. for (int i = 0; i < dataLength; i++)
  59. {
  60. short shortVal = System.Convert.ToInt16(data[i] * 32767f);
  61. _outBytes[_byteCount + 0] = (byte)(shortVal & 0xff);
  62. _outBytes[_byteCount + 1] = (byte)((shortVal >> 8) & 0xff);
  63. _byteCount += 2;
  64. }
  65. }
  66. else
  67. {
  68. _byteCount = dataLength * sizeof(float);
  69. System.Buffer.BlockCopy(data, 0, _outBytes, 0, _byteCount);
  70. }
  71. // Write to stream
  72. if (_byteCount > 0)
  73. {
  74. _stream.Write(_outBytes, 0, _byteCount);
  75. _byteCountTotal += _byteCount;
  76. _byteCount = 0;
  77. }
  78. }
  79. public void WriteHeader(int byteStreamSize)
  80. {
  81. int bytesPerSample = (int)_sampleFormat;
  82. int byteRate = _sampleRate * _channelCount * bytesPerSample;
  83. int blockAlign = _channelCount * bytesPerSample;
  84. _stream.Write(RIFF_HEADER, 0, RIFF_HEADER.Length);
  85. _stream.Write(PackageInt(byteStreamSize + _headerSize, 4), 0, 4);
  86. _stream.Write(FORMAT_WAVE, 0, FORMAT_WAVE.Length);
  87. // 'fmt' chunk
  88. {
  89. _stream.Write(FORMAT_TAG, 0, FORMAT_TAG.Length);
  90. if (_sampleFormat == SampleFormat.PCM16)
  91. {
  92. _stream.Write(PackageInt(16, 4), 0, 4);
  93. _stream.Write(AUDIO_FORMAT_PCM, 0, AUDIO_FORMAT_PCM.Length);
  94. }
  95. else
  96. {
  97. _stream.Write(PackageInt(18, 4), 0, 4);
  98. _stream.Write(AUDIO_FORMAT_FLOAT, 0, AUDIO_FORMAT_FLOAT.Length);
  99. }
  100. _stream.Write(PackageInt(_channelCount, 2), 0, 2);
  101. _stream.Write(PackageInt(_sampleRate, 4), 0, 4);
  102. _stream.Write(PackageInt(byteRate, 4), 0, 4);
  103. _stream.Write(PackageInt(blockAlign, 2), 0, 2);
  104. _stream.Write(PackageInt(bytesPerSample * 8), 0, 2);
  105. if (_sampleFormat == SampleFormat.Float32)
  106. {
  107. _stream.Write(PackageInt(0, 2), 0, 2); // Extension size
  108. }
  109. }
  110. if (_sampleFormat == SampleFormat.Float32)
  111. {
  112. // 'fact' chunk
  113. {
  114. _stream.Write(FACTCHUNK_ID, 0, FACTCHUNK_ID.Length);
  115. _stream.Write(PackageInt(4, 4), 0, 4);
  116. int samplesPerChannel = (byteStreamSize / bytesPerSample);
  117. _stream.Write(PackageInt(samplesPerChannel, 4), 0, 4);
  118. }
  119. }
  120. // 'data' chunk
  121. {
  122. _stream.Write(SUBCHUNK_ID, 0, SUBCHUNK_ID.Length);
  123. _stream.Write(PackageInt(byteStreamSize, 4), 0, 4);
  124. }
  125. _headerSize = (int)_stream.Position - 8;
  126. //UnityEngine.Debug.Log("Header size: " + _headerSize);
  127. }
  128. private static byte[] PackageInt(int source, int length = 2)
  129. {
  130. if((length!=2)&&(length!=4))
  131. throw new System.ArgumentException("length must be either 2 or 4", "length");
  132. var retVal = new byte[length];
  133. retVal[0] = (byte)(source & 0xFF);
  134. retVal[1] = (byte)((source >> 8) & 0xFF);
  135. if (length == 4)
  136. {
  137. retVal[2] = (byte) ((source >> 0x10) & 0xFF);
  138. retVal[3] = (byte) ((source >> 0x18) & 0xFF);
  139. }
  140. return retVal;
  141. }
  142. }
  143. }