mirror of
https://github.com/claunia/cuetools.net.git
synced 2025-12-16 18:14:25 +00:00
272 lines
8.6 KiB
C#
272 lines
8.6 KiB
C#
using System;
|
|
using System.IO;
|
|
|
|
namespace CUETools.Codecs.WAV
|
|
{
|
|
public class AudioDecoder : IAudioSource
|
|
{
|
|
Stream _IO;
|
|
BinaryReader _br;
|
|
long _dataOffset, _samplePos, _sampleLen;
|
|
private AudioPCMConfig pcm;
|
|
long _dataLen;
|
|
bool _largeFile;
|
|
string _path;
|
|
|
|
private DecoderSettings m_settings;
|
|
public IAudioDecoderSettings Settings => m_settings;
|
|
|
|
public long Position
|
|
{
|
|
get
|
|
{
|
|
return _samplePos;
|
|
}
|
|
set
|
|
{
|
|
long seekPos;
|
|
|
|
if (_samplePos == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var oldSamplePos = _samplePos;
|
|
if (_sampleLen >= 0 && value > _sampleLen)
|
|
_samplePos = _sampleLen;
|
|
else
|
|
_samplePos = value;
|
|
|
|
if (_IO.CanSeek || _samplePos < oldSamplePos)
|
|
{
|
|
seekPos = _dataOffset + _samplePos * PCM.BlockAlign;
|
|
_IO.Seek(seekPos, SeekOrigin.Begin);
|
|
}
|
|
else
|
|
{
|
|
int offs = (int)(_samplePos - oldSamplePos) * PCM.BlockAlign;
|
|
while (offs > 0)
|
|
{
|
|
int chunk = Math.Min(offs, 16536);
|
|
_br.ReadBytes(chunk);
|
|
offs -= chunk;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate);
|
|
|
|
public long Length
|
|
{
|
|
get
|
|
{
|
|
return _sampleLen;
|
|
}
|
|
}
|
|
|
|
public long Remaining
|
|
{
|
|
get
|
|
{
|
|
return _sampleLen - _samplePos;
|
|
}
|
|
}
|
|
|
|
public AudioPCMConfig PCM { get { return pcm; } }
|
|
|
|
public string Path { get { return _path; } }
|
|
|
|
public AudioDecoder(DecoderSettings settings, string path, Stream IO = null)
|
|
{
|
|
m_settings = settings;
|
|
_path = path;
|
|
_IO = IO ?? new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan);
|
|
_br = new BinaryReader(_IO);
|
|
|
|
ParseHeaders();
|
|
|
|
if (_dataLen < 0 || m_settings.IgnoreChunkSizes)
|
|
_sampleLen = -1;
|
|
else
|
|
_sampleLen = _dataLen / pcm.BlockAlign;
|
|
}
|
|
|
|
public AudioDecoder(DecoderSettings settings, string path, Stream IO, AudioPCMConfig _pcm)
|
|
{
|
|
m_settings = settings;
|
|
_path = path;
|
|
_IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan);
|
|
_br = new BinaryReader(_IO);
|
|
|
|
_largeFile = false;
|
|
_dataOffset = 0;
|
|
_samplePos = 0;
|
|
pcm = _pcm;
|
|
_dataLen = _IO.CanSeek ? _IO.Length : -1;
|
|
if (_dataLen < 0)
|
|
_sampleLen = -1;
|
|
else
|
|
{
|
|
_sampleLen = _dataLen / pcm.BlockAlign;
|
|
if ((_dataLen % pcm.BlockAlign) != 0)
|
|
throw new Exception("odd file size");
|
|
}
|
|
}
|
|
|
|
public static AudioBuffer ReadAllSamples(DecoderSettings settings, string path, Stream IO = null)
|
|
{
|
|
AudioDecoder reader = new AudioDecoder(settings, path, IO);
|
|
AudioBuffer buff = new AudioBuffer(reader, (int)reader.Length);
|
|
reader.Read(buff, -1);
|
|
if (reader.Remaining != 0)
|
|
throw new Exception("couldn't read the whole file");
|
|
reader.Close();
|
|
return buff;
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
if (_br != null)
|
|
{
|
|
_br.Close();
|
|
_br = null;
|
|
}
|
|
_IO = null;
|
|
}
|
|
|
|
private void ParseHeaders()
|
|
{
|
|
const long maxFileSize = 0x7FFFFFFEL;
|
|
const uint fccRIFF = 0x46464952;
|
|
const uint fccWAVE = 0x45564157;
|
|
const uint fccFormat = 0x20746D66;
|
|
const uint fccData = 0x61746164;
|
|
|
|
uint lenRIFF;
|
|
bool foundFormat, foundData;
|
|
|
|
if (_br.ReadUInt32() != fccRIFF)
|
|
{
|
|
throw new Exception("Not a valid RIFF file.");
|
|
}
|
|
|
|
lenRIFF = _br.ReadUInt32();
|
|
|
|
if (_br.ReadUInt32() != fccWAVE)
|
|
{
|
|
throw new Exception("Not a valid WAVE file.");
|
|
}
|
|
|
|
_largeFile = false;
|
|
foundFormat = false;
|
|
foundData = false;
|
|
long pos = 12;
|
|
do
|
|
{
|
|
uint ckID, ckSize, ckSizePadded;
|
|
long ckEnd;
|
|
|
|
ckID = _br.ReadUInt32();
|
|
ckSize = _br.ReadUInt32();
|
|
ckSizePadded = (ckSize + 1U) & ~1U;
|
|
pos += 8;
|
|
ckEnd = pos + (long)ckSizePadded;
|
|
|
|
if (ckID == fccFormat)
|
|
{
|
|
foundFormat = true;
|
|
|
|
uint fmtTag = _br.ReadUInt16();
|
|
int _channelCount = _br.ReadInt16();
|
|
int _sampleRate = _br.ReadInt32();
|
|
_br.ReadInt32(); // bytes per second
|
|
int _blockAlign = _br.ReadInt16();
|
|
int _bitsPerSample = _br.ReadInt16();
|
|
int _channelMask = 0;
|
|
pos += 16;
|
|
|
|
if (fmtTag == 0xFFFEU && ckSize >= 34) // WAVE_FORMAT_EXTENSIBLE
|
|
{
|
|
_br.ReadInt16(); // CbSize
|
|
_br.ReadInt16(); // ValidBitsPerSample
|
|
_channelMask = _br.ReadInt32();
|
|
fmtTag = _br.ReadUInt16();
|
|
pos += 10;
|
|
}
|
|
|
|
if (fmtTag != 1) // WAVE_FORMAT_PCM
|
|
throw new Exception("WAVE format tag not WAVE_FORMAT_PCM.");
|
|
|
|
pcm = new AudioPCMConfig(_bitsPerSample, _channelCount, _sampleRate, (AudioPCMConfig.SpeakerConfig)_channelMask);
|
|
if (pcm.BlockAlign != _blockAlign)
|
|
throw new Exception("WAVE has strange BlockAlign");
|
|
}
|
|
else if (ckID == fccData)
|
|
{
|
|
foundData = true;
|
|
|
|
_dataOffset = pos;
|
|
if (!_IO.CanSeek || _IO.Length <= maxFileSize)
|
|
{
|
|
if (ckSize == 0 || ckSize >= 0x7fffffff)
|
|
_dataLen = -1;
|
|
else
|
|
_dataLen = (long)ckSize;
|
|
}
|
|
else
|
|
{
|
|
_largeFile = true;
|
|
_dataLen = _IO.Length - pos;
|
|
}
|
|
}
|
|
|
|
if ((foundFormat & foundData) || _largeFile)
|
|
break;
|
|
if (_IO.CanSeek)
|
|
_IO.Seek(ckEnd, SeekOrigin.Begin);
|
|
else
|
|
_br.ReadBytes((int)(ckEnd - pos));
|
|
pos = ckEnd;
|
|
} while (true);
|
|
|
|
if ((foundFormat & foundData) == false || pcm == null)
|
|
throw new Exception("Format or data chunk not found.");
|
|
if (pcm.ChannelCount <= 0)
|
|
throw new Exception("Channel count is invalid.");
|
|
if (pcm.SampleRate <= 0)
|
|
throw new Exception("Sample rate is invalid.");
|
|
if ((pcm.BitsPerSample <= 0) || (pcm.BitsPerSample > 32))
|
|
throw new Exception("Bits per sample is invalid.");
|
|
if (pos != _dataOffset)
|
|
Position = 0;
|
|
}
|
|
|
|
public int Read(AudioBuffer buff, int maxLength)
|
|
{
|
|
buff.Prepare(this, maxLength);
|
|
|
|
byte[] bytes = buff.Bytes;
|
|
int byteCount = (int)buff.ByteLength;
|
|
int pos = 0;
|
|
|
|
while (pos < byteCount)
|
|
{
|
|
int len = _IO.Read(bytes, pos, byteCount - pos);
|
|
if (len <= 0)
|
|
{
|
|
if ((pos % PCM.BlockAlign) != 0 || _sampleLen >= 0)
|
|
throw new Exception("Incomplete file read.");
|
|
buff.Length = pos / PCM.BlockAlign;
|
|
_samplePos += buff.Length;
|
|
_sampleLen = _samplePos;
|
|
return buff.Length;
|
|
}
|
|
pos += len;
|
|
}
|
|
_samplePos += buff.Length;
|
|
return buff.Length;
|
|
}
|
|
}
|
|
}
|