2022-12-16 18:20:23 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
namespace CUETools.Codecs.WAV;
|
|
|
|
|
|
|
|
|
|
|
|
public class AudioEncoder : IAudioDest
|
2022-12-16 18:20:23 +00:00
|
|
|
|
{
|
2024-05-01 04:39:38 +01:00
|
|
|
|
readonly EncoderSettings m_settings;
|
|
|
|
|
|
BinaryWriter _bw;
|
|
|
|
|
|
List<uint> _chunkFCCs;
|
|
|
|
|
|
List<byte[]> _chunks;
|
|
|
|
|
|
long _finalSampleCount = -1;
|
|
|
|
|
|
bool _headersWritten;
|
|
|
|
|
|
Stream _IO;
|
|
|
|
|
|
long hdrLen;
|
|
|
|
|
|
|
|
|
|
|
|
public AudioEncoder(EncoderSettings settings, string path, Stream IO = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_settings = settings;
|
|
|
|
|
|
Path = path;
|
|
|
|
|
|
_IO = IO ?? new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
|
|
|
|
_bw = new BinaryWriter(_IO);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public long Position { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
#region IAudioDest Members
|
|
|
|
|
|
|
|
|
|
|
|
public long FinalSampleCount
|
|
|
|
|
|
{
|
|
|
|
|
|
set => _finalSampleCount = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IAudioEncoderSettings Settings => m_settings;
|
|
|
|
|
|
|
|
|
|
|
|
public string Path { get; }
|
|
|
|
|
|
|
|
|
|
|
|
public void Close()
|
2022-12-16 18:20:23 +00:00
|
|
|
|
{
|
2024-05-01 04:39:38 +01:00
|
|
|
|
if(_finalSampleCount <= 0 && _IO.CanSeek)
|
2022-12-16 18:20:23 +00:00
|
|
|
|
{
|
2024-05-01 04:39:38 +01:00
|
|
|
|
long dataLen = Position * Settings.PCM.BlockAlign;
|
|
|
|
|
|
long dataLenPadded = dataLen + (dataLen & 1);
|
|
|
|
|
|
|
|
|
|
|
|
if(dataLenPadded + hdrLen - 8 < 0xffffffff)
|
2022-12-16 18:20:23 +00:00
|
|
|
|
{
|
2024-05-01 04:39:38 +01:00
|
|
|
|
if((dataLen & 1) == 1) _bw.Write((byte)0);
|
|
|
|
|
|
|
|
|
|
|
|
_bw.Seek(4, SeekOrigin.Begin);
|
|
|
|
|
|
_bw.Write((uint)(dataLenPadded + hdrLen - 8));
|
|
|
|
|
|
|
|
|
|
|
|
_bw.Seek((int)hdrLen - 4, SeekOrigin.Begin);
|
|
|
|
|
|
_bw.Write((uint)dataLen);
|
2022-12-16 18:20:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
_bw.Close();
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
_bw = null;
|
|
|
|
|
|
_IO = null;
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
if(_finalSampleCount > 0 && Position != _finalSampleCount)
|
|
|
|
|
|
throw new Exception("Samples written differs from the expected sample count.");
|
|
|
|
|
|
}
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
public void Delete()
|
|
|
|
|
|
{
|
|
|
|
|
|
_bw.Close();
|
|
|
|
|
|
_bw = null;
|
|
|
|
|
|
_IO = null;
|
|
|
|
|
|
if(Path != "") File.Delete(Path);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Write(AudioBuffer buff)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(buff.Length == 0) return;
|
|
|
|
|
|
buff.Prepare(this);
|
|
|
|
|
|
if(!_headersWritten) WriteHeaders();
|
|
|
|
|
|
_IO.Write(buff.Bytes, 0, buff.ByteLength);
|
|
|
|
|
|
Position += buff.Length;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
public void WriteChunk(uint fcc, byte[] data)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(Position > 0) throw new Exception("data already written, no chunks allowed");
|
|
|
|
|
|
|
|
|
|
|
|
if(_chunks == null)
|
2022-12-16 18:20:23 +00:00
|
|
|
|
{
|
2024-05-01 04:39:38 +01:00
|
|
|
|
_chunks = [];
|
|
|
|
|
|
_chunkFCCs = [];
|
2022-12-16 18:20:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
_chunkFCCs.Add(fcc);
|
|
|
|
|
|
_chunks.Add(data);
|
|
|
|
|
|
hdrLen += 8 + data.Length + (data.Length & 1);
|
|
|
|
|
|
}
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
void WriteHeaders()
|
|
|
|
|
|
{
|
|
|
|
|
|
const uint fccRIFF = 0x46464952;
|
|
|
|
|
|
const uint fccWAVE = 0x45564157;
|
|
|
|
|
|
const uint fccFormat = 0x20746D66;
|
|
|
|
|
|
const uint fccData = 0x61746164;
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
bool wavex = Settings.PCM.BitsPerSample != 16 && Settings.PCM.BitsPerSample != 24 ||
|
|
|
|
|
|
Settings.PCM.ChannelMask != AudioPCMConfig.GetDefaultChannelMask(Settings.PCM.ChannelCount);
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
hdrLen += 36 + (wavex ? 24 : 0) + 8;
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
var dataLen = (uint)(_finalSampleCount * Settings.PCM.BlockAlign);
|
|
|
|
|
|
uint dataLenPadded = dataLen + (dataLen & 1);
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
_bw.Write(fccRIFF);
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
if(_finalSampleCount <= 0)
|
|
|
|
|
|
_bw.Write(0xffffffff);
|
|
|
|
|
|
else
|
|
|
|
|
|
_bw.Write((uint)(dataLenPadded + hdrLen - 8));
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
_bw.Write(fccWAVE);
|
|
|
|
|
|
_bw.Write(fccFormat);
|
2022-12-16 18:20:23 +00:00
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
if(wavex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_bw.Write((uint)40);
|
|
|
|
|
|
_bw.Write((ushort)0xfffe); // WAVEX follows
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_bw.Write((uint)16);
|
|
|
|
|
|
_bw.Write((ushort)1); // PCM
|
2022-12-16 18:20:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
_bw.Write((ushort)Settings.PCM.ChannelCount);
|
|
|
|
|
|
_bw.Write((uint)Settings.PCM.SampleRate);
|
|
|
|
|
|
_bw.Write((uint)(Settings.PCM.SampleRate * Settings.PCM.BlockAlign));
|
|
|
|
|
|
_bw.Write((ushort)Settings.PCM.BlockAlign);
|
|
|
|
|
|
_bw.Write((ushort)((Settings.PCM.BitsPerSample + 7) / 8 * 8));
|
|
|
|
|
|
|
|
|
|
|
|
if(wavex)
|
2022-12-16 18:20:23 +00:00
|
|
|
|
{
|
2024-05-01 04:39:38 +01:00
|
|
|
|
_bw.Write((ushort)22); // length of WAVEX structure
|
|
|
|
|
|
_bw.Write((ushort)Settings.PCM.BitsPerSample);
|
|
|
|
|
|
_bw.Write((uint)Settings.PCM.ChannelMask);
|
|
|
|
|
|
_bw.Write((ushort)1); // PCM Guid
|
|
|
|
|
|
_bw.Write((ushort)0);
|
|
|
|
|
|
_bw.Write((ushort)0);
|
|
|
|
|
|
_bw.Write((ushort)0x10);
|
|
|
|
|
|
_bw.Write((byte)0x80);
|
|
|
|
|
|
_bw.Write((byte)0x00);
|
|
|
|
|
|
_bw.Write((byte)0x00);
|
|
|
|
|
|
_bw.Write((byte)0xaa);
|
|
|
|
|
|
_bw.Write((byte)0x00);
|
|
|
|
|
|
_bw.Write((byte)0x38);
|
|
|
|
|
|
_bw.Write((byte)0x9b);
|
|
|
|
|
|
_bw.Write((byte)0x71);
|
2022-12-16 18:20:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-01 04:39:38 +01:00
|
|
|
|
if(_chunks != null)
|
2022-12-16 18:20:23 +00:00
|
|
|
|
{
|
2024-05-01 04:39:38 +01:00
|
|
|
|
for(var i = 0; i < _chunks.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
_bw.Write(_chunkFCCs[i]);
|
|
|
|
|
|
_bw.Write((uint)_chunks[i].Length);
|
|
|
|
|
|
_bw.Write(_chunks[i]);
|
|
|
|
|
|
if((_chunks[i].Length & 1) != 0) _bw.Write((byte)0);
|
|
|
|
|
|
}
|
2022-12-16 18:20:23 +00:00
|
|
|
|
}
|
2024-05-01 04:39:38 +01:00
|
|
|
|
|
|
|
|
|
|
_bw.Write(fccData);
|
|
|
|
|
|
|
|
|
|
|
|
if(_finalSampleCount <= 0)
|
|
|
|
|
|
_bw.Write(0xffffffff);
|
|
|
|
|
|
else
|
|
|
|
|
|
_bw.Write(dataLen);
|
|
|
|
|
|
|
|
|
|
|
|
_headersWritten = true;
|
2022-12-16 18:20:23 +00:00
|
|
|
|
}
|
2024-05-01 04:39:38 +01:00
|
|
|
|
}
|