Skip to content

Commit

Permalink
Remove StreamIOContext in favor of IOContext.CreateFromStream()
Browse files Browse the repository at this point in the history
  • Loading branch information
dubiousconst282 committed Jun 26, 2023
1 parent 8ce5ad6 commit 90504e1
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 18 deletions.
4 changes: 4 additions & 0 deletions FFmpegWrapper/Codecs/VideoEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public long GetFramePts(long frameNumber)
return ffmpeg.av_rescale_q(frameNumber, ffmpeg.av_inv_q(FrameRate), TimeBase);
}

/// <summary> Searches for a hardware device suitable for encoding the given codec and frame format. </summary>
public static HardwareDevice? CreateCompatibleHardwareDevice(AVCodecID codecId, in PictureFormat format, out CodecHardwareConfig codecConfig)
{
foreach (var config in VideoEncoder.GetHardwareConfigs(codecId)) {
Expand All @@ -89,6 +90,9 @@ public long GetFramePts(long frameNumber)
return null;
}

/// <summary> Returns a new list containing all hardware encoder configurations that may or not be available. </summary>
/// <param name="codecId"> If specified, the list will only include configs for this codec. </param>
/// <param name="deviceType"> If specified, the list will only include configs for this device type. </param>
public static List<CodecHardwareConfig> GetHardwareConfigs(AVCodecID? codecId = null, AVHWDeviceType? deviceType = null)
{
var configs = new List<CodecHardwareConfig>();
Expand Down
38 changes: 36 additions & 2 deletions FFmpegWrapper/Containers/IOContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ public AVIOContext* Handle {
public bool CanRead => _readFn != null;
public bool CanWrite => _writeFn != null;
public bool CanSeek => _seekFn != null;

//Keep lambda refs to prevent them from being GC collected
private avio_alloc_context_read_packet? _readFn;
private avio_alloc_context_write_packet? _writeFn;
private avio_alloc_context_seek? _seekFn;

public IOContext(int bufferSize, bool canRead, bool canWrite, bool canSeek)
{
if (!(canRead ^ canWrite)) {
throw new InvalidOperationException("IOContext must be either readable or writeable");
}
var buffer = (byte*)ffmpeg.av_mallocz((ulong)bufferSize);

_readFn = canRead ? ReadBridge : null;
Expand Down Expand Up @@ -51,8 +54,39 @@ long SeekBridge(void* opaque, long offset, int whence)
}
}

/// <summary> Forces the internal buffer to be written to the output stream. </summary>
public void Flush()
{
if (!CanWrite) {
throw new InvalidOperationException();
}
ffmpeg.avio_flush(Handle);
}

/// <summary> Creates an IOContext that reads from the given stream. </summary>
/// <param name="leaveOpen"> If true, don't dispose the stream along with the IOContext. </param>
/// <param name="bufferSize"> IOContext internal buffer size. </param>
public static IOContext CreateInputFromStream(Stream stream, bool leaveOpen = false, int bufferSize = 4096)
{
if (!stream.CanRead) {
throw new InvalidOperationException("Stream must be readable.");
}
return new StreamIOContext(stream, read: true, leaveOpen, bufferSize);
}

/// <summary> Creates an IOContext that writes to the given stream. </summary>
/// <param name="leaveOpen"> If true, don't dispose the stream along with the IOContext. </param>
/// <param name="bufferSize"> IOContext internal buffer size. </param>
public static IOContext CreateOutputFromStream(Stream stream, bool leaveOpen = false, int bufferSize = 4096)
{
if (!stream.CanWrite) {
throw new InvalidOperationException("Stream must be writeable.");
}
return new StreamIOContext(stream, read: false, leaveOpen, bufferSize);
}

/// <summary> Reads data from the underlying stream to <paramref name="buffer"/>. </summary>
/// <returns>The number of bytes read. </returns>
/// <returns> The number of bytes read. </returns>
protected abstract int Read(Span<byte> buffer);

/// <summary> Writes data to the underlying stream. </summary>
Expand Down
28 changes: 13 additions & 15 deletions FFmpegWrapper/Containers/StreamIOContext.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
namespace FFmpeg.Wrapper;

/// <summary> Wraps a <see cref="Stream"/> into a <see cref="AVIOContext"/>. </summary>
public class StreamIOContext : IOContext
internal class StreamIOContext : IOContext
{
public Stream BaseStream { get; }
readonly Stream _stream;
readonly bool _leaveOpen;

private bool _leaveOpen;

public StreamIOContext(Stream stream, bool leaveOpen = false, int bufferSize = 4096)
: base(bufferSize, stream.CanRead, stream.CanWrite, stream.CanSeek)
public StreamIOContext(Stream stream, bool read, bool leaveOpen, int bufferSize)
: base(bufferSize, canRead: read, canWrite: !read, stream.CanSeek)
{
BaseStream = stream;
_stream = stream;
_leaveOpen = leaveOpen;
}

#if NETSTANDARD2_1_OR_GREATER
protected override int Read(Span<byte> buffer) => BaseStream.Read(buffer);
protected override void Write(ReadOnlySpan<byte> buffer) => BaseStream.Write(buffer);
protected override int Read(Span<byte> buffer) => _stream.Read(buffer);
protected override void Write(ReadOnlySpan<byte> buffer) => _stream.Write(buffer);
#else
private readonly byte[] _scratchBuffer = new byte[4096 * 4];

protected override int Read(Span<byte> buffer)
{
int bytesRead = BaseStream.Read(_scratchBuffer, 0, Math.Min(buffer.Length, _scratchBuffer.Length));
int bytesRead = _stream.Read(_scratchBuffer, 0, Math.Min(buffer.Length, _scratchBuffer.Length));
_scratchBuffer.AsSpan(0, bytesRead).CopyTo(buffer);
return bytesRead;
}
Expand All @@ -32,18 +30,18 @@ protected override void Write(ReadOnlySpan<byte> buffer)
while (pos < buffer.Length) {
int count = Math.Min(_scratchBuffer.Length, buffer.Length - pos);
buffer.Slice(pos, count).CopyTo(_scratchBuffer);
BaseStream.Write(_scratchBuffer, 0, count);
_stream.Write(_scratchBuffer, 0, count);
pos += count;
}
}
#endif

protected override long Seek(long offset, SeekOrigin origin) => BaseStream.Seek(offset, origin);
protected override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin);

protected override long? GetLength()
{
try {
return BaseStream.Length;
return _stream.Length;
} catch (NotSupportedException) {
return null;
}
Expand All @@ -54,7 +52,7 @@ protected override void Free()
base.Free();

if (!_leaveOpen) {
BaseStream.Dispose();
_stream.Dispose();
}
}
}
31 changes: 30 additions & 1 deletion FFmpegWrapper/MediaPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ public int StreamIndex {
set => _pkt->stream_index = value;
}

public Span<byte> Data => new(_pkt->data, _pkt->size);
/// <summary> Whether this packet contains a key-frame. (Checks if AV_PKT_FLAG_KEY is set) </summary>
public bool IsKeyFrame {
get => (_pkt->flags & ffmpeg.AV_PKT_FLAG_KEY) != 0;
set => _pkt->flags = value ? (_pkt->flags | ffmpeg.AV_PKT_FLAG_KEY) : (_pkt->flags & ~ffmpeg.AV_PKT_FLAG_KEY);
}

public Span<byte> Data {
get => new(_pkt->data, _pkt->size);
}

public MediaPacket()
{
Expand All @@ -51,6 +59,27 @@ public MediaPacket()
throw new OutOfMemoryException();
}
}
public MediaPacket(int size)
: this()
{
ffmpeg.av_new_packet(_pkt, size).CheckError("Failed to allocate packet buffer");
}

/// <summary> Copies the specified data span to the packet, ensuring buffer space. </summary>
public void SetData(ReadOnlySpan<byte> data)
{
ThrowIfDisposed();

if (_pkt->buf == null || _pkt->buf->size < (ulong)data.Length + ffmpeg.AV_INPUT_BUFFER_PADDING_SIZE) {
byte* buffer = (byte*)ffmpeg.av_malloc((ulong)data.Length + ffmpeg.AV_INPUT_BUFFER_PADDING_SIZE);
if (buffer == null) {
throw new OutOfMemoryException();
}
ffmpeg.av_packet_from_data(_pkt, buffer, data.Length).CheckError("Failed to allocate packet buffer");
}
_pkt->size = data.Length;
data.CopyTo(Data);
}

/// <inheritdoc cref="ffmpeg.av_packet_rescale_ts(AVPacket*, AVRational, AVRational)"/>
public void RescaleTS(Rational sourceBase, Rational destBase)
Expand Down

0 comments on commit 90504e1

Please sign in to comment.