2012-10-23 13 views
17

Ho un'implementazione System.IO.Stream di sola lettura che non è ricercabile (e il suo Position restituisce sempre 0). Devo inviarlo a un utente che fa alcune operazioni su Seek (ovvero imposta la posizione) sullo stream. Non è una ricerca enorme - diciamo +/- 100 dalla posizione corrente. Esiste un wrapper Stream esistente che aggiungerà una capacità di buffering allo stream per operazioni di ricerca semplici?Stream wrapper per rendere Stream ricercabile?

Aggiornamento: Devo aggiungere che il mio utente è NAudio Mp3FileReader. Ho davvero bisogno di un modo per riprodurre uno (lento e indefinito) streaming di MP3. Penso che sia un errore che NAudio si aspetta di poter cercare a piacimento la propria fonte di dati.

+1

'MemoryStream'? 'BufferedStream'? –

+3

Se si considera l'implementazione di BufferedStream, si noterà che non consente effettivamente le operazioni di ricerca quando lo streaming di base non le consente. MemoryStream non ha costruttori che adottano un parametro Stream. – Brannon

+0

È possibile riprodurre da un flusso di rete MP3 in NAudio e l'app NAudioDemo mostra come eseguire questa operazione. Mp3FileReader è per la lettura dei file, quindi si aspetta che siano cercabili. Creare un TOC in anticipo ci permette di supportare repository molto veloci e accurati in un VBR MP3. Tuttavia, sono d'accordo che sarebbe bello per un futuro NAudio supportare flussi non ricercabili passati in Mp3FileReader. –

risposta

14

Cercare avanti è abbastanza semplice (basta leggere), ma non è possibile cercare all'indietro senza buffering. Forse solo:

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

Questo, tuttavia, è adatto solo per piccole e moderata flussi (non GB), che sono noti per terminare (che flussi non richiede di fare). Se hai bisogno di un flusso più grande, un FileStream in un file temporaneo funzionerebbe, ma è molto più intensivo di IO.

+1

Questo è un approccio interessante Il mio flusso di input è un flusso audio proveniente da un server. Deve rimanere aperto fino alla chiusura dell'applicazione e il mio cliente si chiuderà se esaurisce i dati. Non sto proprio vedendo come applicare questo approccio. – Brannon

+2

@Brannon in questo caso probabilmente lo implementerete manualmente con un buffer circolare per la ricerca all'indietro (di una quantità limitata). Scusa - niente di originale. –

+1

Sono stato in grado di affrontare il problema con un piano alternativo. NAudio ha un esempio per frame per le situazioni di streaming. – Brannon

0

Un'altra soluzione potrebbe essere quella di creare la propria classe di stream che avvolge l'altro stream. Implementare cercare come un 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; } 
} 

Se il giocatore non cerca una buona ragione, questo potrebbe funzionare.

+0

Idea interessante.Sfortunatamente, NAudio Mp3FileStream fa una serie di operazioni di ricerca e lettura che richiedono dati validi. – Brannon

9

Ecco un wrapper per rendere qualsiasi Stream disponibile per leggere le operazioni.

Funziona memorizzando nella cache le letture dal flusso sottostante, fino al numero di byte specificato nel costruttore. Questo sarà utile quando i vincoli della memoria vietano la soluzione di Marc Gravell.

supportati operazioni di ricerca:

  • cerca avanti utilizzando SeekOrigin.Current e SeekOrigin.Begin lavori per spostamenti arbitrari
  • cerca all'indietro utilizzando SeekOrigin.Current e SeekOrigin.Begin opere per giù a -seekBackBufferSize byte dalla posizione corrente nel flusso sottostante (che può differire da readSeekableStream.Position dopo una ricerca precedente a ritroso)
  • che cerca di utilizzare SeekOrigin.End opere per offset >= -seekBackBufferSize && offset <= 0

Osservazioni generali

  • il metodo Seek e la proprietà Position sono completamente gestite internamente e non comportano il flusso sottostante (che sarebbe solo un tiro in ogni caso)
  • seeking colpisce il leggere parte del flusso solo, da cui il nome della classe
  • tutte le operazioni di scrittura sono semplicemente delegate attraverso il flusso sottostante
  • avvolgimento già flussi ricercabile con questo sarebbe uno spreco di risorse
  • alcuni problemi affrontati da ReadSeekableStream di seguito possono anche essere risolti con la mia classe PeekableStream

Questa implementazione è fresco e non ancora agguerriti. Ho comunque testato l'unità per un bel po 'di casi di ricerca/lettura e casi angolari, confrontandola con un (liberamente ricercabile) 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

Questo manca implementazione per 'Chiudi' e' Dispose'? – NiKiZe

+0

@NiKiZe Hai ragione. Ho appena aggiunto 'Dispose (bool)', che [viene chiamato] (http://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,dc4ffe046b847b84) di 'Close()' e 'Dispose() '. –