web-dev-qa-db-ja.com

ストリームをシーク可能にするストリームラッパー?

読み取り専用ですSystem.IO.Streamシークできない実装(およびそのPositionは常に0を返します)。ストリームでいくつかのSeek操作(別名、位置の設定)を行うコンシューマーに送信する必要があります。それは大きなシークではありません-現在の位置から+/- 100と言います。単純なSeek操作のためにストリームにバッファリング機能を追加する既存のStreamラッパーはありますか?

更新:私のコンシューマーはNAudioMp3FileReaderであることを追加する必要があります。私は本当に(ゆっくりと無期限に)ストリーミングMP3を再生する方法が必要です。 NAudioがデータソースを自由に探すことができると期待しているのはバグだと思います。

19
Brannon

前方を探すのは簡単ですが(読むだけです)、バッファリングせずに後方を探すことはできません。多分ただ:

using(var ms = new MemoryStream()) {
    otherStream.CopyTo(ms);
    ms.Position = 0;
    // now work with ms
}

ただし、これは、終了することがわかっている(GBではなく)小から中程度のストリームにのみ適しています(ストリームを実行する必要はありません)。より大きなストリームが必要な場合は、一時ファイルへのFileStreamが機能しますが、IOを大幅に消費します。

20
Marc Gravell

これは、read操作でStreamをシーク可能にするラッパーです。

これは、コンストラクターで指定されたバイト数まで、基になるストリームからの読み取りをキャッシュすることによって機能します。これは、メモリの制約によりMarcGravellのソリューションが禁止されている場合に役立ちます。

サポートされているシーク操作:

  • SeekOrigin.CurrentおよびSeekOrigin.Beginを使用して前方にシークすることは、任意のオフセットに対して機能します
  • SeekOrigin.CurrentおよびSeekOrigin.Beginを使用した逆方向シークは、基になるストリームの現在の位置から-seekBackBufferSizeバイトまで機能します(前の逆方向シーク後のreadSeekableStream.Positionとは異なる場合があります)
  • SeekOrigin.Endの使用を求めることはoffset >= -seekBackBufferSize && offset <= 0で機能します

総論

  • SeekメソッドとPositionプロパティは完全に内部で処理され、基になるストリームは含まれません(とにかくスローするだけです)
  • シークはストリームのread部分にのみ影響するため、クラスの名前は
  • すべての書き込み操作は、基になるストリームに委任されるだけです。
  • すでにシーク可能なストリームをこれでラップすると、リソースの無駄になります
  • 以下の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(); }
}
11

別の解決策は、他のストリームをラップする独自のストリームクラスを作成することです。 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; }
}

プレイヤーが正当な理由もなく探している場合、これはうまくいくかもしれません。

0
Tergiver

Amazon SDKの MakeStreamSeekable メソッドを使用します。


MakeStreamSeekableメソッド(入力)

シークできないストリームをSystem.IO.MemoryStreamに変換します。 MemoryStreamの位置は任意に移動できます。

宣言構文

C#

_public static Stream MakeStreamSeekable(
    Stream input
)
_

パラメーター

*input* ([Stream][2])変換されるストリーム

戻り値

シーク可能なMemoryStream

備考

MemoryStreamsは、バッキングストアとしてバイト配列を使用します。ストリームが非常に大きいとシステムリソースが使い果たされる可能性があるため、これを慎重に使用してください。

0
fatsheldon

_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を使用する目的を損なう可能性があります。

0
James Esh