using System.Text; using Bmp.Core.Lite.Playback.Outputs.NAudioASIO.Originals; using NAudio.Wave; namespace Bmp.Core.Lite.Playback.Inputs; public class DsfAudioStream : DsdAudioStream, IHaveDecoderInfo, IHaveAsioSampleFormat { public const string SIGNATURE_DSD = "DSD "; private const string SIGNATURE_FMT = "fmt "; private const string SIGNATURE_DATA = "data"; string IHaveDecoderInfo.DecoderName => "DSF"; string? IHaveDecoderInfo.FileFormat => $"V{_formatVersion}"; public AsioSampleType? AsioSampleFormat => AsioSampleType.DSDInt8LSB1; private readonly Stream _underlyingStream; private readonly bool _disposeStream; private readonly long _dataChunkBeginOffset; private readonly long _dataChunkEndOffset; private readonly long _dataSize; private readonly long _pointerToMetadataChunk; public int ChannelNum { get; } public int SampleRate { get; } public int ClusterSizePerChannel { get; } public int BitPerSample { get; } public override WaveFormat WaveFormat { get; } private readonly byte[] _clusterBuf; private int _clusterBufSize; private int _clusterPtrByChannel; private readonly int _formatVersion; public static bool IsDsfFile(Stream stream) { if (stream.Length > 4) { using var binaryReader = new BinaryReader(stream, Encoding.UTF8, true); var signatureDSD = Encoding.ASCII.GetString(binaryReader.ReadBytes(4)); if (signatureDSD == SIGNATURE_DSD) return true; } return false; } public DsfAudioStream(string urlOrPath) : this(InputSourceProviderLite.ReadContentAsSeekableStream(urlOrPath), true) { } public DsfAudioStream(Stream underlyingStream, bool disposeStream) { if (underlyingStream.CanSeek == false) throw new ArgumentException("stream not seekable"); _underlyingStream = underlyingStream; _disposeStream = disposeStream; if (_underlyingStream.Length < 4) throw new InvalidDataException("too small as dsf file"); _underlyingStream.Position = 0; using var reader = new BinaryReader(_underlyingStream, Encoding.UTF8, true); var dsdSignature = Encoding.ASCII.GetString(reader.ReadBytes(4)); if (SIGNATURE_DSD != dsdSignature) throw new InvalidDataException("invalid DSD signature"); var dsdSizeOfThisChunk = reader.ReadInt64(); if (dsdSizeOfThisChunk != 28) throw new InvalidDataException("invalid dsd chunk size"); var totalFileSize = reader.ReadInt64(); if (totalFileSize != _underlyingStream.Length) throw new InvalidDataException("invalid total file size"); _pointerToMetadataChunk = reader.ReadInt64(); var fmtSignature = Encoding.ASCII.GetString(reader.ReadBytes(4)); if (SIGNATURE_FMT != fmtSignature) throw new InvalidDataException("invalid FMT signature"); var fmtSizeOfThisChunk = reader.ReadInt64(); if (fmtSizeOfThisChunk != 52) throw new NotSupportedException("not supported fmt chunk size"); _formatVersion = reader.ReadInt32(); var formatId = reader.ReadInt32(); if (formatId != 0) throw new NotSupportedException("only DSD raw supported"); var channelType = reader.ReadInt32(); ChannelNum = reader.ReadInt32(); SampleRate = reader.ReadInt32(); BitPerSample = reader.ReadInt32(); var sampleCount = reader.ReadInt64(); ClusterSizePerChannel = reader.ReadInt32(); var rsv0000 = reader.ReadInt32(); var dataSignature = Encoding.ASCII.GetString(reader.ReadBytes(4)); if (SIGNATURE_DATA != dataSignature) throw new InvalidDataException("invalid data signature"); var dataSizeOfThisChunk = reader.ReadInt64(); _dataSize = dataSizeOfThisChunk - 12; _dataChunkBeginOffset = _underlyingStream.Position; _dataChunkEndOffset = _dataChunkBeginOffset + _dataSize; var align = ClusterSizePerChannel * ChannelNum; WaveFormat = WaveFormat.CreateCustomFormat(WaveFormatEncoding.Unknown, SampleRate, ChannelNum, SampleRate * channelType / 8, align, 1); _clusterBuf = new byte[align]; } private bool ReadCluster() { // block[ChannelNum] // blockPtr // blockSize = Chunk/ChannelNum var readCount = 0; do { var safeCount = Math.Min(_clusterBuf.Length - readCount, _dataChunkEndOffset - _underlyingStream.Position); if (safeCount <= 0) break; var read = _underlyingStream.Read(_clusterBuf, readCount, (int)safeCount); if (read == 0) break; readCount += read; } while (readCount < _clusterBuf.Length); _clusterBufSize = readCount; _clusterPtrByChannel = 0; return readCount != 0; } public override int Read(byte[] buffer, int offset, int countRequired) { if (countRequired == 0) return 0; if (countRequired < ChannelNum) throw new ArgumentOutOfRangeException(nameof(countRequired)); var mod = countRequired % ChannelNum; var count = countRequired; if (mod != 0) count -= mod; //对齐 BlockSizePerChannel 声道交替字节 (FAKE BIT PER SAMPLE 8bit) or ( 16 SHORT/ 32 INT/ 64 LONG) //Source L[BlockSizePerChannel] R[BlockSizePerChannel] L[BlockSizePerChannel] R[BlockSizePerChannel] -> Transfer LR LR LR LR if (_clusterBufSize == 0) { if (ReadCluster() == false) return 0; } var bytes = count / ChannelNum; var dstPtr = 0; //TODO: DsfAudioStream optimization Unsafe moving ptr for (var s = 0; s < bytes; s++) { for (var ch = 0; ch < ChannelNum; ch++) { var srcPtr = ch * ClusterSizePerChannel + _clusterPtrByChannel; buffer[dstPtr] = _clusterBuf[srcPtr]; ++dstPtr; } _clusterPtrByChannel++; if (_clusterPtrByChannel * ChannelNum == _clusterBufSize) { if (!ReadCluster()) break; } } return dstPtr; } public override long Length => _dataSize; public override long Position { get => _underlyingStream.Position - _dataChunkBeginOffset; set => _underlyingStream.Position = _dataChunkBeginOffset + value - value % (ClusterSizePerChannel * ChannelNum); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (_disposeStream) _underlyingStream.Dispose(); } }