Commit graph

2 commits

Author SHA1 Message Date
Deivid Soto
c2e9925162 test(streaming): integration tests with real ffmpeg (skipped without it)
Three end-to-end checks that the transcoder actually produces playable
output, not just plausible argv. Skip cleanly on hosts without ffmpeg
on PATH so unit-test CI keeps working.

- TestTranscoder_DirectPlayProducesH264 — synth h264+aac MP4 via
  `ffmpeg -f lavfi testsrc/sine`, run Analyze (expect direct play),
  Stream to disk, ffprobe the result, assert codecs are still h264+aac.
- TestTranscoder_TranscodeHEVCToH264 — synth hevc+ac3 MKV, expect
  transcode decision, Stream to memory, ffprobe-verify the output is
  h264+aac. Skipped if libx265 isn't compiled in.
- TestTranscoder_AnalyzeReportsRealMediaInfo — sanity check that
  Analyze returns a usable mediainfo (320x240, ~2s duration) the API
  handler can show to the player.

Verified locally:
  PASS: TestTranscoder_DirectPlayProducesH264 (0.09s)
  PASS: TestTranscoder_TranscodeHEVCToH264   (0.22s)
  PASS: TestTranscoder_AnalyzeReportsRealMediaInfo (0.06s)
2026-05-06 11:35:52 +02:00
Deivid Soto
75dcc0f1cb feat(streaming): ffmpeg transcoding pipeline (direct play / fMP4 / HW accel)
The browser-side WebRTC reproductor needs MP4 / H.264 / AAC / yuv420p to
keep MSE happy. This package decides per request whether to:

  • direct-play  — input already MSE-compatible, just remux to fMP4
  • transcode    — re-encode video (libx264 / NVENC / QSV / VAAPI /
                   VideoToolbox) + audio (AAC), fragment to fMP4

Pieces:

- internal/streaming/transcoder.go — AnalyzeCompatibility decides the
  recipe from a parsed mediainfo. CompatibilityReport carries the reasons
  so the player UI can show "transcoding video: HEVC → H.264".

- internal/streaming/ffmpeg_args.go — BuildFFmpegArgs assembles the argv
  for ffmpeg. Direct play uses `-c copy`; transcode uses libx264 or the
  selected HW encoder. Output is always fragmented MP4 piped to stdout
  (-movflags frag_keyframe+empty_moov+default_base_moof) so the HTTP
  handler can stream straight to the browser without disk I/O.

  Quality ladder: 480p (1.5Mb), 720p (3.5Mb), 1080p (6Mb), 2160p (25Mb).
  Default 1080p when unset / unknown. -ss seek for resume / scrubbing.

- internal/streaming/hwaccel.go — DetectHWAccel runs `ffmpeg -encoders`
  once per process and caches the best available. Order: NVENC → QSV →
  VAAPI → VideoToolbox → libx264. VAAPI is the only family that wires up
  HW decode too (`-hwaccel vaapi`); the others software-decode and HW-
  encode (works fine and avoids /dev/dri permission rabbit holes).

- internal/streaming/stream.go — Transcoder facade wires Analyze + Stream
  together for the API handler in Fase 4. Captures the last 8 KiB of
  ffmpeg stderr for diagnosable errors without unbounded memory.

Tests (20 unit, all green):
- AnalyzeCompatibility: h264+aac direct, video-only direct, HEVC →
  transcode, 10-bit HDR → transcode, EAC3 audio → transcode, nil guards
- ResolveQuality: empty + unknown fallback to 1080p, 4-step ladder
- BuildFFmpegArgs: direct play -c copy, transcode libx264 + bitrate +
  scale, NVENC swaps encoder & drops preset, VAAPI injects -hwaccel +
  scale_vaapi, -ss timestamp formatting
- HWAccel: encoder-name table, VAAPI is the only one with HW decode
- formatDuration: zero, sub-second, HH:MM:SS, negative-clamped
- cappedBuffer: tail retention through multi-write and large-write paths
- NewTranscoder: rejects empty paths
2026-05-06 11:34:57 +02:00