8f490102ee
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.
60 lines
2.0 KiB
C#
60 lines
2.0 KiB
C#
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;
|
|
}
|
|
}
|