2012-10-23 74 views
17

我有一個只讀的System.IO.Stream實現不可查找(並且其Position總是返回0)。我需要將它發送給在流上執行一些Seek操作(即,設置位置)的消費者。這不是一個巨大的尋求 - 從目前的位置說+/- 100。是否有一個現有的Stream包裝會爲流簡單的Seek操作添加緩衝功能?流包裝使流可搜索?

更新:我應該補充說我的消費者是NAudio Mp3FileReader。我真的只需要一種方式來播放(緩慢而無限期地)流媒體MP3。我認爲這是NAudio期望能夠隨意查找數據源的錯誤。

+1

'MemoryStream'? 'BufferedStream'? –

+3

如果你看看BufferedStream的實現,你會發現它實際上並不允許在基本流不允許的情況下進行seek操作。 MemoryStream沒有構造函數接受Stream參數。 – Brannon

+0

您可以在NAudio中播放MP3網絡流,NAudioDemo應用程序會告訴您如何執行此操作。 Mp3FileReader用於讀取文件,因此期望它們是可搜索的。提前創建TOC允許我們支持快速準確地重新定位到VBR MP3。但是,我同意未來的NAudio支持傳遞到Mp3FileReader的非可搜索流是很好的。 –

回答

14

尋求轉發很容易(只是閱讀),但你不能無緩衝地向後尋求。也許只是:

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

然而,這是隻適合小到中等流(未GB),已知會結束(其流是不是需要做的)。如果你需要一個更大的流,一個FileStream到臨時文件可以工作,但是顯着更多的IO密集。

+1

這是一個有趣的方法。我的輸入流是來自服務器的音頻流。它必須保持開放狀態,直到應用程序關閉,如果數據用完,我的用戶將自行關閉。我不太清楚如何應用這種方法。 – Brannon

+2

@Brannon在這種情況下,你可能會用一個循環緩衝區來手動實現它,以便向後查找(數量有限)。對不起 - 沒有內置。 –

+1

我能夠通過備用計劃解決問題。 NAudio有一個針對流式場景的每幀示例。 – Brannon

0

另一種解決方案可能是創建自己的包裝其他流的流類。實現尋求作爲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

有趣的想法。不幸的是,NAudio Mp3FileStream執行一系列需要有效數據的查找和讀取操作。 – Brannon

9

這裏是一個包裝,使任何Stream可尋求操作。

它通過從底層流中緩存讀取,直到在構造函數中指定的字節數來工作。當內存限制禁止Marc Gravell的解決方案時,這將派上用場。

支持查找操作:

  • 尋求向前使用SeekOrigin.CurrentSeekOrigin.Begin作品任意偏移
  • 逆向查找使用SeekOrigin.CurrentSeekOrigin.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(); } 
} 
+0

這一個缺少'Close'和'Dispose'的實現嗎? – NiKiZe

+0

@NiKiZe你說得對。我通過'Close()'和'Dispose()函數添加了'Dispose(bool)',它被[調用](http://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,dc4ffe046b847b84) '。 –