読み取り専用ですSystem.IO.Stream
シークできない実装(およびそのPosition
は常に0を返します)。ストリームでいくつかのSeek
操作(別名、位置の設定)を行うコンシューマーに送信する必要があります。それは大きなシークではありません-現在の位置から+/- 100と言います。単純なSeek操作のためにストリームにバッファリング機能を追加する既存のStream
ラッパーはありますか?
更新:私のコンシューマーはNAudioMp3FileReaderであることを追加する必要があります。私は本当に(ゆっくりと無期限に)ストリーミングMP3を再生する方法が必要です。 NAudioがデータソースを自由に探すことができると期待しているのはバグだと思います。
前方を探すのは簡単ですが(読むだけです)、バッファリングせずに後方を探すことはできません。多分ただ:
using(var ms = new MemoryStream()) {
otherStream.CopyTo(ms);
ms.Position = 0;
// now work with ms
}
ただし、これは、終了することがわかっている(GBではなく)小から中程度のストリームにのみ適しています(ストリームを実行する必要はありません)。より大きなストリームが必要な場合は、一時ファイルへのFileStream
が機能しますが、IOを大幅に消費します。
これは、read操作でStream
をシーク可能にするラッパーです。
これは、コンストラクターで指定されたバイト数まで、基になるストリームからの読み取りをキャッシュすることによって機能します。これは、メモリの制約によりMarcGravellのソリューションが禁止されている場合に役立ちます。
サポートされているシーク操作:
SeekOrigin.Current
およびSeekOrigin.Begin
を使用して前方にシークすることは、任意のオフセットに対して機能しますSeekOrigin.Current
およびSeekOrigin.Begin
を使用した逆方向シークは、基になるストリームの現在の位置から-seekBackBufferSize
バイトまで機能します(前の逆方向シーク後のreadSeekableStream.Position
とは異なる場合があります)SeekOrigin.End
の使用を求めることはoffset >= -seekBackBufferSize && offset <= 0
で機能します総論
Seek
メソッドとPosition
プロパティは完全に内部で処理され、基になるストリームは含まれません(とにかくスローするだけです)ReadSeekableStream
で対処されるいくつかの問題は、私の PeekableStream
クラスでも解決できます。この実装は新鮮で、まだ戦いが強化されていません。ただし、かなりの数のシーク/読み取りケースとコーナーケースについてユニットテストを行い、(自由にシーク可能な)MemoryStream
と相互比較しました。
public class ReadSeekableStream : Stream
{
private long _underlyingPosition;
private readonly byte[] _seekBackBuffer;
private int _seekBackBufferCount;
private int _seekBackBufferIndex;
private readonly Stream _underlyingStream;
public ReadSeekableStream(Stream underlyingStream, int seekBackBufferSize)
{
if (!underlyingStream.CanRead)
throw new Exception("Provided stream " + underlyingStream + " is not readable");
_underlyingStream = underlyingStream;
_seekBackBuffer = new byte[seekBackBufferSize];
}
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return true; } }
public override int Read(byte[] buffer, int offset, int count)
{
int copiedFromBackBufferCount = 0;
if (_seekBackBufferIndex < _seekBackBufferCount)
{
copiedFromBackBufferCount = Math.Min(count, _seekBackBufferCount - _seekBackBufferIndex);
Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferIndex, buffer, offset, copiedFromBackBufferCount);
offset += copiedFromBackBufferCount;
count -= copiedFromBackBufferCount;
_seekBackBufferIndex += copiedFromBackBufferCount;
}
int bytesReadFromUnderlying = 0;
if (count > 0)
{
bytesReadFromUnderlying = _underlyingStream.Read(buffer, offset, count);
if (bytesReadFromUnderlying > 0)
{
_underlyingPosition += bytesReadFromUnderlying;
var copyToBufferCount = Math.Min(bytesReadFromUnderlying, _seekBackBuffer.Length);
var copyToBufferOffset = Math.Min(_seekBackBufferCount, _seekBackBuffer.Length - copyToBufferCount);
var bufferBytesToMove = Math.Min(_seekBackBufferCount - 1, copyToBufferOffset);
if (bufferBytesToMove > 0)
Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferCount - bufferBytesToMove, _seekBackBuffer, 0, bufferBytesToMove);
Buffer.BlockCopy(buffer, offset, _seekBackBuffer, copyToBufferOffset, copyToBufferCount);
_seekBackBufferCount = Math.Min(_seekBackBuffer.Length, _seekBackBufferCount + copyToBufferCount);
_seekBackBufferIndex = _seekBackBufferCount;
}
}
return copiedFromBackBufferCount + bytesReadFromUnderlying;
}
public override long Seek(long offset, SeekOrigin Origin)
{
if (Origin == SeekOrigin.End)
return SeekFromEnd((int) Math.Max(0, -offset));
var relativeOffset = Origin == SeekOrigin.Current
? offset
: offset - Position;
if (relativeOffset == 0)
return Position;
else if (relativeOffset > 0)
return SeekForward(relativeOffset);
else
return SeekBackwards(-relativeOffset);
}
private long SeekForward(long origOffset)
{
long offset = origOffset;
var seekBackBufferLength = _seekBackBuffer.Length;
int backwardSoughtBytes = _seekBackBufferCount - _seekBackBufferIndex;
int seekForwardInBackBuffer = (int) Math.Min(offset, backwardSoughtBytes);
offset -= seekForwardInBackBuffer;
_seekBackBufferIndex += seekForwardInBackBuffer;
if (offset > 0)
{
// first completely fill seekBackBuffer to remove special cases from while loop below
if (_seekBackBufferCount < seekBackBufferLength)
{
var maxRead = seekBackBufferLength - _seekBackBufferCount;
if (offset < maxRead)
maxRead = (int) offset;
var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead);
_underlyingPosition += bytesRead;
_seekBackBufferCount += bytesRead;
_seekBackBufferIndex = _seekBackBufferCount;
if (bytesRead < maxRead)
{
if (_seekBackBufferCount < offset)
throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes");
return Position;
}
offset -= bytesRead;
}
// now alternate between filling tempBuffer and seekBackBuffer
bool fillTempBuffer = true;
var tempBuffer = new byte[seekBackBufferLength];
while (offset > 0)
{
var maxRead = offset < seekBackBufferLength ? (int) offset : seekBackBufferLength;
var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, maxRead);
_underlyingPosition += bytesRead;
var bytesReadDiff = maxRead - bytesRead;
offset -= bytesRead;
if (bytesReadDiff > 0 /* reached end-of-stream */ || offset == 0)
{
if (fillTempBuffer)
{
if (bytesRead > 0)
{
Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff);
Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead);
}
}
else
{
if (bytesRead > 0)
Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead);
Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff);
}
if (offset > 0)
throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes");
}
fillTempBuffer = !fillTempBuffer;
}
}
return Position;
}
private long SeekBackwards(long offset)
{
var intOffset = (int)offset;
if (offset > int.MaxValue || intOffset > _seekBackBufferIndex)
throw new NotSupportedException("Cannot currently seek backwards more than " + _seekBackBufferIndex + " bytes");
_seekBackBufferIndex -= intOffset;
return Position;
}
private long SeekFromEnd(long offset)
{
var intOffset = (int) offset;
var seekBackBufferLength = _seekBackBuffer.Length;
if (offset > int.MaxValue || intOffset > seekBackBufferLength)
throw new NotSupportedException("Cannot seek backwards from end more than " + seekBackBufferLength + " bytes");
// first completely fill seekBackBuffer to remove special cases from while loop below
if (_seekBackBufferCount < seekBackBufferLength)
{
var maxRead = seekBackBufferLength - _seekBackBufferCount;
var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead);
_underlyingPosition += bytesRead;
_seekBackBufferCount += bytesRead;
_seekBackBufferIndex = Math.Max(0, _seekBackBufferCount - intOffset);
if (bytesRead < maxRead)
{
if (_seekBackBufferCount < intOffset)
throw new NotSupportedException("Could not seek backwards from end " + intOffset + " bytes");
return Position;
}
}
else
{
_seekBackBufferIndex = _seekBackBufferCount;
}
// now alternate between filling tempBuffer and seekBackBuffer
bool fillTempBuffer = true;
var tempBuffer = new byte[seekBackBufferLength];
while (true)
{
var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, seekBackBufferLength);
_underlyingPosition += bytesRead;
var bytesReadDiff = seekBackBufferLength - bytesRead;
if (bytesReadDiff > 0) // reached end-of-stream
{
if (fillTempBuffer)
{
if (bytesRead > 0)
{
Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff);
Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead);
}
}
else
{
if (bytesRead > 0)
Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead);
Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff);
}
_seekBackBufferIndex -= intOffset;
return Position;
}
fillTempBuffer = !fillTempBuffer;
}
}
public override long Position
{
get { return _underlyingPosition - (_seekBackBufferCount - _seekBackBufferIndex); }
set { Seek(value, SeekOrigin.Begin); }
}
protected override void Dispose(bool disposing)
{
if (disposing)
_underlyingStream.Close();
base.Dispose(disposing);
}
public override bool CanTimeout { get { return _underlyingStream.CanTimeout; } }
public override bool CanWrite { get { return _underlyingStream.CanWrite; } }
public override long Length { get { return _underlyingStream.Length; } }
public override void SetLength(long value) { _underlyingStream.SetLength(value); }
public override void Write(byte[] buffer, int offset, int count) { _underlyingStream.Write(buffer, offset, count); }
public override void Flush() { _underlyingStream.Flush(); }
}
別の解決策は、他のストリームをラップする独自のストリームクラスを作成することです。 SeekをNOPとして実装します。
class MyStream : Stream
{
public MyStream(Stream baseStream) { this.baseStream = baseStream; }
private Stream baseStream;
// Delegate all operations except Seek/CanSeek to baseStream
public override bool CanSeek { get { return true; } }
public override long Seek(long offset, SeekOrigin Origin) { return baseStream.Position; }
}
プレイヤーが正当な理由もなく探している場合、これはうまくいくかもしれません。
Amazon SDKの MakeStreamSeekable メソッドを使用します。
シークできないストリームをSystem.IO.MemoryStreamに変換します。 MemoryStreamの位置は任意に移動できます。
C#
_public static Stream MakeStreamSeekable(
Stream input
)
_
*input* ([Stream][2])
変換されるストリーム
シーク可能なMemoryStream
MemoryStreamsは、バッキングストアとしてバイト配列を使用します。ストリームが非常に大きいとシステムリソースが使い果たされる可能性があるため、これを慎重に使用してください。
_System.Net.WebClient
_を使用する場合は、Stream
を返すOpenRead()
を使用する代わりに、webClient.DownloadData("https://your.url")
を使用してバイト配列を取得し、それをMemoryStream
。次に例を示します。
_byte[] buffer = client.DownloadData(testBlobFile);
using (var stream = new MemoryStream(buffer))
{
... your code using the stream ...
}
_
明らかに、これはStream
が作成される前にすべてをダウンロードするため、Stream
を使用する目的を損なう可能性があります。