1
0
mirror of https://github.com/SineVector241/VoiceCraft-MCBE_Proximity_Chat.git synced 2024-11-24 14:36:13 +00:00
VoiceCraft-MCBE_Proximity_Chat/ATL/AudioData/IO/WAVPack.cs
2024-07-13 11:16:08 +10:00

462 lines
17 KiB
C#

using Commons;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using static ATL.AudioData.AudioDataManager;
using static ATL.ChannelsArrangements;
namespace ATL.AudioData.IO
{
/// <summary>
/// Class for WavPack files manipulation (extension : .WV)
/// </summary>
class WAVPack : IAudioDataIO
{
private static readonly byte[] WAVPACK_HEADER = Utils.Latin1Encoding.GetBytes("wvpk");
private SizeInfo sizeInfo;
#pragma warning disable S4487 // Unread "private" fields should be removed
private sealed class WavpackHeader3
{
public byte[] ckID = new byte[4];
public uint ckSize;
public ushort version;
public ushort bits;
public ushort flags;
public ushort shift;
public uint total_samples;
public uint crc;
public uint crc2;
public char[] extension = new char[4];
public byte extra_bc;
public char[] extras = new char[3];
public void Reset()
{
Array.Clear(ckID, 0, 4);
ckSize = 0;
version = 0;
bits = 0;
flags = 0;
shift = 0;
total_samples = 0;
crc = 0;
crc2 = 0;
Array.Clear(extension, 0, 4);
extra_bc = 0;
Array.Clear(extras, 0, 3);
}
}
private sealed class WavPackHeader4
{
public byte[] ckID = new byte[4];
public uint ckSize;
public ushort version;
public byte track_no;
public byte index_no;
public uint total_samples;
public uint block_index;
public uint block_samples;
public uint flags;
public uint crc;
public void Reset()
{
Array.Clear(ckID, 0, 4);
ckSize = 0;
version = 0;
track_no = 0;
index_no = 0;
total_samples = 0;
block_index = 0;
block_samples = 0;
flags = 0;
crc = 0;
}
}
private struct FormatChunk
{
public ushort wformattag;
public ushort wchannels;
public uint dwsamplespersec;
public uint dwavgbytespersec;
public ushort wblockalign;
public ushort wbitspersample;
}
private sealed class RiffChunk
{
public char[] id = new char[4];
public uint size;
public void Reset()
{
Array.Clear(id, 0, 3);
size = 0;
}
}
#pragma warning restore S4487 // Unread "private" fields should be removed
//version 3 flags
private const int MONO_FLAG_v3 = 1; // not stereo
private const int FAST_FLAG_v3 = 2; // non-adaptive predictor and stereo mode
//private const int RAW_FLAG_v3 = 4; // raw mode (no .wav header)
//private const int CALC_NOISE_v3 = 8; // calc noise in lossy mode (no longer stored)
private const int HIGH_FLAG_v3 = 0x10; // high quality mode (all modes)
//private const int BYTES_3_v3 = 0x20; // files have 3-byte samples
//private const int OVER_20_v3 = 0x40; // samples are over 20 bits
private const int WVC_FLAG_v3 = 0x80; // create/use .wvc (no longer stored)
//private const int LOSSY_SHAPE_v3 = 0x100; // noise shape (lossy mode only)
//private const int VERY_FAST_FLAG_v3 = 0x200; // double fast (no longer stored)
private const int NEW_HIGH_FLAG_v3 = 0x400; // new high quality mode (lossless only)
//private const int CANCEL_EXTREME_v3 = 0x800; // cancel EXTREME_DECORR
//private const int CROSS_DECORR_v3 = 0x1000; // decorrelate chans (with EXTREME_DECORR flag)
//private const int NEW_DECORR_FLAG_v3 = 0x2000; // new high-mode decorrelator
//private const int JOINT_STEREO_v3 = 0x4000; // joint stereo (lossy and high lossless)
private const int EXTREME_DECORR_v3 = 0x8000; // extra decorrelation (+ enables other flags)
private static readonly int[] sample_rates = { 6000, 8000, 9600, 11025, 12000, 16000, 22050,
24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 };
// ---------- INFORMATIVE INTERFACE IMPLEMENTATIONS & MANDATORY OVERRIDES
public Format AudioFormat
{
get;
}
public int SampleRate { get; private set; }
public bool IsVBR => false;
public int CodecFamily { get; private set; }
public string FileName { get; }
public double BitRate { get; private set; }
public int BitDepth { get; private set; }
public double Duration { get; private set; }
public ChannelsArrangement ChannelsArrangement { get; private set; }
/// <inheritdoc/>
public List<MetaDataIOFactory.TagType> GetSupportedMetas()
{
return new List<MetaDataIOFactory.TagType> { MetaDataIOFactory.TagType.APE };
}
public long AudioDataOffset { get; set; }
public long AudioDataSize { get; set; }
// ---------- CONSTRUCTORS & INITIALIZERS
private void resetData()
{
Duration = 0;
BitRate = 0;
CodecFamily = AudioDataIOFactory.CF_LOSSLESS;
BitDepth = -1;
SampleRate = 0;
AudioDataOffset = -1;
AudioDataSize = 0;
}
public WAVPack(string filePath, Format format)
{
this.FileName = filePath;
AudioFormat = format;
resetData();
}
// ---------- SUPPORT METHODS
public static bool IsValidHeader(byte[] data)
{
return StreamUtils.ArrBeginsWith(data, WAVPACK_HEADER); // No auto-detection for V3
}
public bool Read(Stream source, SizeInfo sizeInfo, MetaDataIO.ReadTagParams readTagParams)
{
bool result = false;
this.sizeInfo = sizeInfo;
resetData();
BufferedBinaryReader reader = new BufferedBinaryReader(source);
reader.Seek(0, SeekOrigin.Begin);
byte[] marker = reader.ReadBytes(4);
reader.Seek(0, SeekOrigin.Begin);
if (WAV.IsValidHeader(marker))
{
result = _ReadV3(reader);
}
else
{
if (IsValidHeader(marker))
{
result = _ReadV4(reader);
}
}
return result;
}
private bool _ReadV4(BufferedBinaryReader source)
{
WavPackHeader4 wvh4 = new WavPackHeader4();
byte[] EncBuf = new byte[4096];
string encoder;
bool result = false;
wvh4.Reset();
wvh4.ckID = source.ReadBytes(4);
wvh4.ckSize = source.ReadUInt32();
wvh4.version = source.ReadUInt16();
wvh4.track_no = source.ReadByte();
wvh4.index_no = source.ReadByte();
wvh4.total_samples = source.ReadUInt32();
wvh4.block_index = source.ReadUInt32();
wvh4.block_samples = source.ReadUInt32();
wvh4.flags = source.ReadUInt32();
wvh4.crc = source.ReadUInt32();
StringBuilder encoderBuilder = new StringBuilder();
if (WAVPACK_HEADER.SequenceEqual(wvh4.ckID)) // wavpack header found -- TODO handle exceptions better
{
result = true;
ChannelsArrangement = GuessFromChannelNumber((int)(2 - (wvh4.flags & 4)));
uint samples = wvh4.total_samples;
SampleRate = (int)((wvh4.flags & (0x1F << 23)) >> 23);
BitDepth = (int)((wvh4.flags & 3) * 16);
if (SampleRate > 14 || SampleRate < 0)
{
SampleRate = 44100;
}
else
{
SampleRate = sample_rates[SampleRate];
}
if (8 == (wvh4.flags & 8)) // hybrid flag
{
encoderBuilder.Append("hybrid lossy");
CodecFamily = AudioDataIOFactory.CF_LOSSY;
}
else
{
encoderBuilder.Append("lossless");
CodecFamily = AudioDataIOFactory.CF_LOSSLESS;
}
Duration = wvh4.total_samples * 1000.0 / SampleRate;
long initPos = source.Position;
Array.Clear(EncBuf, 0, 4096);
EncBuf = source.ReadBytes(4096);
for (int i = 0; i < 4096; i++)
{
if (0x65 == EncBuf[i] && 0x02 == EncBuf[i + 1])
{
var encoderbyte = EncBuf[i + 2];
switch (encoderbyte)
{
case 8: encoderBuilder.Append(" (high)"); break;
case 0: encoderBuilder.Append(" (normal)"); break;
case 2: encoderBuilder.Append(" (fast)"); break;
case 6: encoderBuilder.Append(" (very fast)"); break;
}
AudioDataOffset = initPos + i;
break;
}
}
AudioDataSize = sizeInfo.FileSize - sizeInfo.APESize - sizeInfo.ID3v1Size - AudioDataOffset;
if (Duration > 0) BitRate = AudioDataSize * 8.0 / (samples * 1000.0 / SampleRate);
}
encoder = encoderBuilder.ToString();
return result;
}
private bool _ReadV3(BufferedBinaryReader r)
{
RiffChunk chunk = new RiffChunk();
FormatChunk fmt;
bool hasfmt;
WavpackHeader3 wvh3 = new WavpackHeader3();
StringBuilder encoderBuilder = new StringBuilder();
string encoder;
bool result = false;
hasfmt = false;
// read and evaluate header
chunk.Reset();
chunk.id = r.ReadChars(4);
chunk.size = r.ReadUInt32();
char[] wavchunk = r.ReadChars(4);
if (!wavchunk.SequenceEqual("WAVE")) return false;
// start looking for chunks
chunk.Reset();
while (r.Position < r.Length)
{
chunk.id = r.ReadChars(4);
chunk.size = r.ReadUInt32();
if (chunk.size <= 0) break;
if (chunk.id.SequenceEqual("fmt ")) // Format chunk found
{
if (chunk.size >= 16)
{
fmt.wformattag = r.ReadUInt16();
fmt.wchannels = r.ReadUInt16();
fmt.dwsamplespersec = r.ReadUInt32();
fmt.dwavgbytespersec = r.ReadUInt32();
fmt.wblockalign = r.ReadUInt16();
fmt.wbitspersample = r.ReadUInt16();
hasfmt = true;
result = true;
ChannelsArrangement = GuessFromChannelNumber(fmt.wchannels);
SampleRate = (int)fmt.dwsamplespersec;
BitRate = (double)fmt.dwavgbytespersec * 8;
}
else
{
break;
}
}
else
{
if (hasfmt && chunk.id.SequenceEqual("data"))
{
wvh3.Reset();
long initialPos = r.Position;
wvh3.ckID = r.ReadBytes(4);
wvh3.ckSize = r.ReadUInt32();
wvh3.version = r.ReadUInt16();
wvh3.bits = r.ReadUInt16();
wvh3.flags = r.ReadUInt16();
wvh3.shift = r.ReadUInt16();
wvh3.total_samples = r.ReadUInt32();
wvh3.crc = r.ReadUInt32();
wvh3.crc2 = r.ReadUInt32();
wvh3.extension = r.ReadChars(4);
wvh3.extra_bc = r.ReadByte();
wvh3.extras = r.ReadChars(3);
if (WAVPACK_HEADER.SequenceEqual(wvh3.ckID)) // wavpack header found
{
result = true;
AudioDataOffset = initialPos;
AudioDataSize = sizeInfo.FileSize - sizeInfo.APESize - sizeInfo.ID3v1Size - AudioDataOffset;
ChannelsArrangement = GuessFromChannelNumber(2 - (wvh3.flags & 1));
CodecFamily = AudioDataIOFactory.CF_LOSSLESS;
// Encoder guess
if (wvh3.bits > 0)
{
BitDepth = wvh3.bits + 3;
if ((wvh3.flags & NEW_HIGH_FLAG_v3) > 0)
{
encoder = "hybrid";
if ((wvh3.flags & WVC_FLAG_v3) > 0)
{
encoderBuilder.Append(" lossless");
}
else
{
encoderBuilder.Append(" lossy");
CodecFamily = AudioDataIOFactory.CF_LOSSY;
}
if ((wvh3.flags & EXTREME_DECORR_v3) > 0)
encoderBuilder.Append(" (high)");
}
else
{
if ((wvh3.flags & (HIGH_FLAG_v3 | FAST_FLAG_v3)) == 0)
{
encoder = (wvh3.bits + 3).ToString() + "-bit lossy";
CodecFamily = AudioDataIOFactory.CF_LOSSY;
}
else
{
encoder = (wvh3.bits + 3).ToString() + "-bit lossy";
CodecFamily = AudioDataIOFactory.CF_LOSSY;
if ((wvh3.flags & HIGH_FLAG_v3) > 0)
{
encoderBuilder.Append(" high");
}
else
{
encoderBuilder.Append(" fast");
}
}
}
}
else
{
if ((wvh3.flags & HIGH_FLAG_v3) == 0)
{
encoderBuilder.Append("lossless (fast mode)");
}
else
{
if ((wvh3.flags & EXTREME_DECORR_v3) > 0)
{
encoderBuilder.Append("lossless (high mode)");
}
else
{
encoderBuilder.Append("lossless");
}
}
}
if (SampleRate <= 0) SampleRate = 44100;
Duration = wvh3.total_samples * 1000.0 / SampleRate;
if (Duration > 0) BitRate = 8.0 * AudioDataSize / Duration;
}
break;
}
else // not a wv file
{
break;
}
}
} // while
encoder = encoderBuilder.ToString();
return result;
}
}
}