|
@@ -1,235 +1,227 @@
|
|
|
-using System;
|
|
|
-using System.Collections.Generic;
|
|
|
-using System.Linq;
|
|
|
-using System.Text;
|
|
|
-using System.Threading.Tasks;
|
|
|
-using Bmp.Core.NAudioExt;
|
|
|
+using System.Text;
|
|
|
using NAudio.Wave;
|
|
|
|
|
|
-namespace Bmp.Core.Playback.Inputs
|
|
|
-{
|
|
|
- public class DsfSourceStream : WaveStream
|
|
|
- {
|
|
|
- public const string SIGNATURE_DSD = "DSD ";
|
|
|
- private const string SIGNATURE_FMT = "fmt ";
|
|
|
- private const string SIGNATURE_DATA = "data";
|
|
|
-
|
|
|
+namespace Bmp.Core.Playback.Inputs;
|
|
|
|
|
|
+public class DsfSourceStream : DsdAbstractSourceStream
|
|
|
+{
|
|
|
+ public const string SIGNATURE_DSD = "DSD ";
|
|
|
+ private const string SIGNATURE_FMT = "fmt ";
|
|
|
+ private const string SIGNATURE_DATA = "data";
|
|
|
|
|
|
- private readonly Stream _underlyingSeekableStream;
|
|
|
- private readonly bool _disposeStream;
|
|
|
+ private readonly Stream _underlyingStream;
|
|
|
+ private readonly bool _disposeStream;
|
|
|
|
|
|
- private readonly long _dataChunkBeginOffset;
|
|
|
- private readonly long _dataChunkEndOffset;
|
|
|
- private readonly long _dataSize;
|
|
|
- private readonly long _pointerToMetadataChunk;
|
|
|
+ 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 BlockSizePerChannel { get; }
|
|
|
- public int BitPerSample { get; }
|
|
|
+ public int ChannelNum { get; }
|
|
|
+ public int SampleRate { get; }
|
|
|
+ public int BlockSizePerChannel { get; }
|
|
|
+ public int BitPerSample { get; }
|
|
|
|
|
|
- public override WaveFormat WaveFormat { get; }
|
|
|
+ public override WaveFormat WaveFormat { get; }
|
|
|
|
|
|
- private readonly byte[] _blockBuf;
|
|
|
- private int _blockBufSize;
|
|
|
- private int _blockPtrByChannel;
|
|
|
+ private readonly byte[] _blockBuf;
|
|
|
+ private int _blockBufSize;
|
|
|
+ private int _blockPtrByChannel;
|
|
|
|
|
|
- public static bool IsDsfFile(Stream stream)
|
|
|
+ public static bool IsDsfFile(Stream stream)
|
|
|
+ {
|
|
|
+ if (stream.Length > 4)
|
|
|
{
|
|
|
- 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;
|
|
|
+ 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 DsfSourceStream(string urlOrPath) : this(InputSourceProvider.ReadContentAsSeekableStream(urlOrPath), true)
|
|
|
- {
|
|
|
- }
|
|
|
+ public DsfSourceStream(string urlOrPath) : this(InputSourceProvider.ReadContentAsSeekableStream(urlOrPath), true)
|
|
|
+ {
|
|
|
+ }
|
|
|
|
|
|
- public DsfSourceStream(Stream underlyingSeekableStream, bool disposeStream)
|
|
|
- {
|
|
|
- if (underlyingSeekableStream.CanSeek == false) throw new ArgumentException("stream not seekable");
|
|
|
- _underlyingSeekableStream = underlyingSeekableStream;
|
|
|
- _disposeStream = disposeStream;
|
|
|
-
|
|
|
- if (_underlyingSeekableStream.Length < 4) throw new InvalidDataException("too small as dsf file");
|
|
|
-
|
|
|
- _underlyingSeekableStream.Position = 0;
|
|
|
-
|
|
|
- using var reader = new BinaryReader(_underlyingSeekableStream, 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 != _underlyingSeekableStream.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");
|
|
|
- var 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();
|
|
|
- BlockSizePerChannel = 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 = _underlyingSeekableStream.Position;
|
|
|
- _dataChunkEndOffset = _dataChunkBeginOffset + _dataSize;
|
|
|
-
|
|
|
- var align = BlockSizePerChannel * ChannelNum;
|
|
|
- WaveFormat = WaveFormat.CreateCustomFormat(WaveFormatEncoding.Unknown, SampleRate, ChannelNum, SampleRate * channelType / 8, align, 1);
|
|
|
-
|
|
|
- _blockBuf = new byte[align];
|
|
|
- }
|
|
|
+ public DsfSourceStream(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");
|
|
|
+ var 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();
|
|
|
+ BlockSizePerChannel = 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 = BlockSizePerChannel * ChannelNum;
|
|
|
+ WaveFormat = WaveFormat.CreateCustomFormat(WaveFormatEncoding.Unknown, SampleRate, ChannelNum, SampleRate * channelType / 8, align, 1);
|
|
|
+
|
|
|
+ _blockBuf = new byte[align];
|
|
|
+ }
|
|
|
|
|
|
- private bool ReadBlock()
|
|
|
- {
|
|
|
- //TODO: Rename to ReadChunk
|
|
|
- // block[ChannelNum]
|
|
|
- // blockPtr
|
|
|
- // blockSize = Chunk/ChannelNum
|
|
|
+ private bool ReadBlock()
|
|
|
+ {
|
|
|
+ //TODO: Rename to ReadChunk
|
|
|
+ // block[ChannelNum]
|
|
|
+ // blockPtr
|
|
|
+ // blockSize = Chunk/ChannelNum
|
|
|
|
|
|
- var readCount = 0;
|
|
|
- do
|
|
|
- {
|
|
|
- var safeCount = Math.Min(_blockBuf.Length - readCount, _dataChunkEndOffset - _underlyingSeekableStream.Position);
|
|
|
- if (safeCount <= 0) break;
|
|
|
+ var readCount = 0;
|
|
|
+ do
|
|
|
+ {
|
|
|
+ var safeCount = Math.Min(_blockBuf.Length - readCount, _dataChunkEndOffset - _underlyingStream.Position);
|
|
|
+ if (safeCount <= 0) break;
|
|
|
|
|
|
- var read = _underlyingSeekableStream.Read(_blockBuf, readCount, (int)safeCount);
|
|
|
- if (read == 0) break;
|
|
|
+ var read = _underlyingStream.Read(_blockBuf, readCount, (int)safeCount);
|
|
|
+ if (read == 0) break;
|
|
|
|
|
|
- readCount += read;
|
|
|
- } while (readCount < _blockBuf.Length);
|
|
|
+ readCount += read;
|
|
|
+ } while (readCount < _blockBuf.Length);
|
|
|
|
|
|
- _blockBufSize = readCount;
|
|
|
- _blockPtrByChannel = 0;
|
|
|
+ _blockBufSize = readCount;
|
|
|
+ _blockPtrByChannel = 0;
|
|
|
|
|
|
- return readCount != 0;
|
|
|
- }
|
|
|
+ return readCount != 0;
|
|
|
+ }
|
|
|
|
|
|
- public override int Read(byte[] buffer, int offset, int countRequired)
|
|
|
- {
|
|
|
- if (countRequired == 0) return 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;
|
|
|
+ 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)
|
|
|
+ //对齐 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
|
|
|
+ //Source L[BlockSizePerChannel] R[BlockSizePerChannel] L[BlockSizePerChannel] R[BlockSizePerChannel] -> Transfer LR LR LR LR
|
|
|
|
|
|
- if (_blockBufSize == 0)
|
|
|
- {
|
|
|
- if (ReadBlock() == false) return 0;
|
|
|
- }
|
|
|
+ if (_blockBufSize == 0)
|
|
|
+ {
|
|
|
+ if (ReadBlock() == false) return 0;
|
|
|
+ }
|
|
|
|
|
|
- var bytes = count / ChannelNum;
|
|
|
- var dstPtr = 0;
|
|
|
+ var bytes = count / ChannelNum;
|
|
|
+ var dstPtr = 0;
|
|
|
|
|
|
- //TODO: optimization Unsafe moving prt
|
|
|
+ //TODO: optimization Unsafe moving prt
|
|
|
|
|
|
- for (var s = 0; s < bytes; s++)
|
|
|
+ for (var s = 0; s < bytes; s++)
|
|
|
+ {
|
|
|
+ for (var ch = 0; ch < ChannelNum; ch++)
|
|
|
{
|
|
|
- for (var ch = 0; ch < ChannelNum; ch++)
|
|
|
- {
|
|
|
- var srcPtr = ch * BlockSizePerChannel + _blockPtrByChannel;
|
|
|
+ var srcPtr = ch * BlockSizePerChannel + _blockPtrByChannel;
|
|
|
|
|
|
- buffer[dstPtr] = _blockBuf[srcPtr];
|
|
|
+ buffer[dstPtr] = _blockBuf[srcPtr];
|
|
|
|
|
|
- ++dstPtr;
|
|
|
- }
|
|
|
+ ++dstPtr;
|
|
|
+ }
|
|
|
|
|
|
- _blockPtrByChannel++;
|
|
|
+ _blockPtrByChannel++;
|
|
|
|
|
|
- if (_blockPtrByChannel * ChannelNum == _blockBufSize)
|
|
|
- {
|
|
|
- if (!ReadBlock()) break;
|
|
|
- }
|
|
|
+ if (_blockPtrByChannel * ChannelNum == _blockBufSize)
|
|
|
+ {
|
|
|
+ if (!ReadBlock()) break;
|
|
|
}
|
|
|
-
|
|
|
- return dstPtr;
|
|
|
}
|
|
|
|
|
|
- public override long Seek(long offset, SeekOrigin origin)
|
|
|
- {
|
|
|
- var align = offset % (ChannelNum * BlockSizePerChannel);
|
|
|
- if (align != 0) offset -= align;
|
|
|
+ return dstPtr;
|
|
|
+ }
|
|
|
|
|
|
- var seekPosition = _underlyingSeekableStream.Position;
|
|
|
+ public override long Seek(long offset, SeekOrigin origin)
|
|
|
+ {
|
|
|
+ var align = offset % (ChannelNum * BlockSizePerChannel);
|
|
|
+ if (align != 0) offset -= align;
|
|
|
|
|
|
- switch (origin)
|
|
|
- {
|
|
|
- case SeekOrigin.Begin:
|
|
|
- seekPosition = _dataChunkBeginOffset + offset;
|
|
|
- break;
|
|
|
+ var seekPosition = _underlyingStream.Position;
|
|
|
|
|
|
- case SeekOrigin.Current:
|
|
|
- seekPosition += offset;
|
|
|
- break;
|
|
|
+ switch (origin)
|
|
|
+ {
|
|
|
+ case SeekOrigin.Begin:
|
|
|
+ seekPosition = _dataChunkBeginOffset + offset;
|
|
|
+ break;
|
|
|
|
|
|
- case SeekOrigin.End:
|
|
|
- seekPosition += _dataChunkEndOffset + offset;
|
|
|
- break;
|
|
|
+ case SeekOrigin.Current:
|
|
|
+ seekPosition += offset;
|
|
|
+ break;
|
|
|
|
|
|
- default:
|
|
|
- throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
|
|
|
- }
|
|
|
+ case SeekOrigin.End:
|
|
|
+ seekPosition += _dataChunkEndOffset + offset;
|
|
|
+ break;
|
|
|
|
|
|
- if (seekPosition < _dataChunkBeginOffset) seekPosition = _dataChunkBeginOffset;
|
|
|
- if (seekPosition > _dataChunkEndOffset) seekPosition = _dataChunkEndOffset;
|
|
|
+ default:
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
|
|
|
+ }
|
|
|
|
|
|
- _underlyingSeekableStream.Position = seekPosition;
|
|
|
+ if (seekPosition < _dataChunkBeginOffset) seekPosition = _dataChunkBeginOffset;
|
|
|
+ if (seekPosition > _dataChunkEndOffset) seekPosition = _dataChunkEndOffset;
|
|
|
|
|
|
- //ReadBlock();
|
|
|
+ _underlyingStream.Position = seekPosition;
|
|
|
|
|
|
- return seekPosition;
|
|
|
- }
|
|
|
+ //ReadBlock();
|
|
|
|
|
|
- public override long Length => _dataSize;
|
|
|
+ return seekPosition;
|
|
|
+ }
|
|
|
|
|
|
- public override long Position
|
|
|
- {
|
|
|
- get => _underlyingSeekableStream.Position - _dataChunkBeginOffset;
|
|
|
- set => Seek(value, SeekOrigin.Begin);
|
|
|
- }
|
|
|
+ public override long Length => _dataSize;
|
|
|
|
|
|
- protected override void Dispose(bool disposing)
|
|
|
- {
|
|
|
- base.Dispose(disposing);
|
|
|
- if (_disposeStream) _underlyingSeekableStream.Dispose();
|
|
|
- }
|
|
|
+ public override long Position
|
|
|
+ {
|
|
|
+ get => _underlyingStream.Position - _dataChunkBeginOffset;
|
|
|
+ set => Seek(value, SeekOrigin.Begin);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void Dispose(bool disposing)
|
|
|
+ {
|
|
|
+ base.Dispose(disposing);
|
|
|
+ if (_disposeStream) _underlyingStream.Dispose();
|
|
|
+ }
|
|
|
|
|
|
- public override bool CanRead => true;
|
|
|
- public override bool CanSeek => true;
|
|
|
+ public override bool CanRead => true;
|
|
|
+ public override bool CanSeek => true;
|
|
|
|
|
|
- // -------------- unused stubs --------------
|
|
|
+ // -------------- unused stubs --------------
|
|
|
|
|
|
- public override void SetLength(long value) => throw new NotSupportedException();
|
|
|
+ public override void SetLength(long value) => throw new NotSupportedException();
|
|
|
|
|
|
- public override void Flush() => throw new NotSupportedException();
|
|
|
+ public override void Flush() => throw new NotSupportedException();
|
|
|
|
|
|
- public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
|
|
+ public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
|
|
|
|
|
- public override bool CanWrite => false;
|
|
|
- }
|
|
|
-}
|
|
|
+ public override bool CanWrite => false;
|
|
|
+}
|