TASK 21 Increment B: FFmpeg rawvideo video decoder -> VideoFrame
Adds the codec-agnostic decoder half of the media source. Spawns ffmpeg with -f rawvideo -pix_fmt bgra (reusing the already-shipped ffmpeg via IFfmpegLocator) and drains the raw BGRA stdout pipe into VideoFrames. - Services/RawVideoFrameReader.cs: pure rawvideo BGRA stream -> frames (partial reads kept across Feed; no ffmpeg needed to test) - Services/IDecodeProcess.cs + FfmpegDecodeProcess.cs: binary-stdout subprocess seam, mirror of the encoder's IEncoderProcess - Services/MediaVideoSource.cs: owns the decode, raises FrameReady/Completed - MediaVideoSourceTests: 3 pure reader + 1 integration (fake decode process through the real source loop, frames in order) Reference (external scan): ffmpeg rawvideo pipe decode is the canonical codec-agnostic frame feeds pattern (ffmpeg docs -f rawvideo; how OBS/media pipelines push frames to a compositor). Verified: 4/4 tests, build 0 warnings. Native-FPS pacing + resolver/compositor wiring are the next slice.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
namespace ytLive.Services;
|
||||
|
||||
/// <summary>
|
||||
/// TASK 21: parses a stream of tightly-packed raw BGRA frames into
|
||||
/// <see cref="VideoFrame"/>s. The consumer of an ffmpeg <c>-f rawvideo -pix_fmt
|
||||
/// bgra</c> stdout pipe: every frame is exactly <c>width*height*4</c> bytes, so a
|
||||
/// frame is emitted whenever that many bytes have accumulated. Pure and fully
|
||||
/// deterministic, so it is tested with a synthetic byte stream — no ffmpeg, no
|
||||
/// process, no audio device needed.
|
||||
/// </summary>
|
||||
public sealed class RawVideoFrameReader
|
||||
{
|
||||
private readonly int _frameBytes;
|
||||
private readonly int _width;
|
||||
private readonly int _height;
|
||||
private byte[]? _pending;
|
||||
|
||||
public RawVideoFrameReader(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
_frameBytes = width * height * 4;
|
||||
}
|
||||
|
||||
/// <summary>Feeds <paramref name="bytes"/> read from the pipe and returns any
|
||||
/// whole frames reconstructed so far. Partial/pending bytes are kept for the
|
||||
/// next call — frames never split or merge across reads.</summary>
|
||||
public List<VideoFrame> Feed(byte[] bytes)
|
||||
{
|
||||
var pending = _pending;
|
||||
byte[] combined;
|
||||
if (pending != null && pending.Length > 0)
|
||||
{
|
||||
combined = new byte[pending.Length + bytes.Length];
|
||||
Buffer.BlockCopy(pending, 0, combined, 0, pending.Length);
|
||||
Buffer.BlockCopy(bytes, 0, combined, pending.Length, bytes.Length);
|
||||
_pending = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
combined = bytes;
|
||||
}
|
||||
|
||||
var frames = new List<VideoFrame>();
|
||||
var offset = 0;
|
||||
while (combined.Length - offset >= _frameBytes)
|
||||
{
|
||||
var frame = new byte[_frameBytes];
|
||||
Buffer.BlockCopy(combined, offset, frame, 0, _frameBytes);
|
||||
frames.Add(new VideoFrame(_width, _height, frame));
|
||||
offset += _frameBytes;
|
||||
}
|
||||
|
||||
if (offset < combined.Length)
|
||||
_pending = combined[offset..];
|
||||
|
||||
return frames;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user