c# - tag - ¿Envolver la secuencia para hacer que Stream se pueda buscar?
propiedad tag c# (5)
Aquí hay una envoltura para hacer que cualquier Stream
buscada para operaciones de lectura .
Funciona almacenando en caché las lecturas de la secuencia subyacente, hasta el número de bytes especificados en el constructor. Esto será útil cuando las restricciones de memoria prohíban la solución de Marc Gravell.
Operaciones de búsqueda soportadas:
- búsqueda hacia adelante utilizando
SeekOrigin.Current
ySeekOrigin.Begin
funciona para compensaciones arbitrarias - buscar hacia atrás utilizando
SeekOrigin.Current
ySeekOrigin.Begin
funciona para los bytes-seekBackBufferSize
desde la posición actual en el flujo subyacente (que puede diferir dereadSeekableStream.Position
después de una búsqueda anterior hacia atrás) - buscar utilizando
SeekOrigin.End
funciona paraoffset >= -seekBackBufferSize && offset <= 0
Observaciones generales
- el método de
Seek
y la propiedadPosition
se manejan completamente de manera interna y no involucran la secuencia subyacente (que solo se lanzaría de todos modos) - la búsqueda afecta solo a la parte leída de la transmisión, de ahí el nombre de la clase
- Todas las operaciones de escritura son simplemente delegadas a través de la secuencia subyacente
- Envolver los flujos ya buscados con esto sería un desperdicio de recursos
- algunos problemas resueltos por
ReadSeekableStream
continuación también pueden ser resueltos por mi clasePeekableStream
Esta implementación es reciente y aún no está endurecida por la batalla. Sin embargo, lo he probado en unidad para un buen número de casos de búsqueda / lectura y de esquina, y lo comparé con un MemoryStream
(de búsqueda libre).
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(); }
}
Tengo una implementación de solo lectura System.IO.Stream
que no se puede buscar (y su Position
siempre devuelve 0). Necesito enviarlo a un consumidor que realice algunas operaciones de Seek
(también conocida como, establece la Posición) en la transmisión. No es una gran búsqueda, digamos +/- 100 desde la posición actual. ¿Existe un contenedor de Stream
existente que agregará una capacidad de almacenamiento en búfer a la secuencia para operaciones de búsqueda simples?
Actualización: debo agregar que mi consumidor es el NAudio Mp3FileReader. Realmente solo necesito una forma de reproducir un MP3 de forma lenta e indefinida. Creo que es un error que NAudio espera poder buscar a su fuente de datos a voluntad.
La búsqueda hacia delante es bastante fácil (solo lea), pero no puede buscar hacia atrás sin búfer. Tal vez sólo:
using(var ms = new MemoryStream()) {
otherStream.CopyTo(ms);
ms.Position = 0;
// now work with ms
}
Sin embargo, esto solo es adecuado para flujos pequeños a moderados (no GB), que se sabe que finalizan (que no se requiere que hagan los flujos). Si necesita un flujo más grande, un FileStream
a un archivo temporal funcionaría, pero es mucho más intensivo en IO.
Otra solución podría ser crear su propia clase de secuencia que envuelva la otra secuencia. Implementar la búsqueda como 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; }
}
Si el jugador está buscando sin una buena razón, esto podría funcionar.
Si usa System.Net.WebClient
, en lugar de usar OpenRead()
que devuelve un Stream
use webClient.DownloadData("https://your.url")
para obtener una matriz de bytes que luego puede convertir en un MemoryStream
. Aquí hay un ejemplo:
byte[] buffer = client.DownloadData(testBlobFile);
using (var stream = new MemoryStream(buffer))
{
... your code using the stream ...
}
Obviamente, esto descarga todo antes de crear el Stream
, por lo que puede anular el propósito de usar un Stream
.
Solo uso el método MakeStreamSeekable del SDK de Amazon:
Método MakeStreamSeekable (entrada)
Convierte una secuencia no buscable en un System.IO.MemoryStream. La posición de un MemoryStream se puede mover arbitrariamente.
Sintaxis de la declaracion
DO#
public static Stream MakeStreamSeekable(
Stream input
)
Parámetros
*input* ([Stream][2])
El stream a convertir
Valor de retorno
Un MemoryStream buscable
Observaciones
MemoryStreams utiliza matrices de bytes como su almacén de respaldo. Utilícelo judicialmente, ya que es probable que un flujo muy grande cause que los recursos del sistema se agoten.