Skip to content

feat(media): read Hikvision SD-card recordings with mu-law sound - #1462

Merged
KnifeLemon merged 1 commit into
KnifeLemon:mainfrom
Ugoyo:claude/cctv-hikvision-hanwha
Nov 13, 2026
Merged

KnifeLemon merged 1 commit into
KnifeLemon:mainfrom
Ugoyo:claude/cctv-hikvision-hanwha

Conversation

@Ugoyo

@Ugoyo Ugoyo commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator

What does this change?

Hikvision cameras and NVRs record MPEG program streams with H.264 / H.265 video and G.711 sound. Exports from iVMS-4200 and the NVR (.mp4, sometimes .264) start with a 40-byte IMKH header, and FFmpeg's mpeg demuxer already reads them by their content: Filee converts them today, whatever their extension (checked, see below). The recordings on a camera's SD card or a NAS (hiv00000.mp4, indexed by index00.bin) are the same stream without the header. With A-law sound they convert too, but with mu-law sound (stream type 0x91 in the stream map) FFmpeg takes it for MP2 and the conversion fails ("Decode error rate 1 exceeds maximum", nothing written). FFmpeg only reads 0x91 as mu-law in files that start with IMKH (libavformat/mpeg.c, imkh_cctv).

This PR adds Media/HikvisionStream.cs. When a file starts with a pack header and a stream map in its first 256 KB lists mu-law sound, Filee gives FFmpeg concat:<header>|<file>: a 40-byte IMKH header written to the job's work folder, followed by the recording. The recording itself is not copied. Everything else is handed to FFmpeg as before. A damaged or cut-short stream map leaves the file to FFmpeg as it is. Paths with | (the concat separator, not allowed in Windows names) are left alone. No new extension, so no settings migration.

Old 4HKH .264 files (Hikvision's own frame format, from early DS-series DVRs) and recordings made with stream encryption on can't be read: they still fail with FFmpeg's error. docs/ENGINES.md says so.

Type of change

  • Bug fix
  • New feature or improvement
  • New format or engine
  • Translation
  • Documentation
  • Build, CI or dependencies

How was it tested?

  • dotnet format --verify-no-changes (and dotnet build -c Release)
  • dotnet test: new tests pass. Run on Linux with the system's FFmpeg 6.1 (the mpeg demuxer's IMKH handling is the same in 9.0.2). Without this change, only the SD-card / mu-law case fails. The 5 MediaTests that fail here fail on main too (FFmpeg build missing codecs), plus the Core tests that need a non-root user.
  • Tried it in the app: not tried. filee-cli convert was used (Linux) on generated recordings.

New tests:

  • HikvisionStreamTests (no FFmpeg): a header-less stream with mu-law needs the header (H.264 and H.265); A-law, AAC, an export that already has IMKH, a program stream without a map, an ISO MP4 and an empty file don't; damaged maps (cut short, length past the end, entry info past the map) are left to FFmpeg; only the first 256 KB is looked at; InputFor writes the header and returns the concat input, and leaves other files and missing files alone.
  • MediaTests.A_hikvision_recording_converts_to_mp4_with_its_sound: FFmpeg has no muxer for these, so HikvisionRecording writes one from FFmpeg-made H.264 frames and G.711 sound (pack header, system header and stream map before each key frame, 40 ms of sound per frame), with or without IMKH, A-law or mu-law, named .mp4 or .264. Each converts to MP4 with 50 frames of 160x120 H.264, AAC sound, ~2 s.

No real sample was used. I found no public sample with a clear licence on the hosts I could reach. The FFmpeg trac samples (#4182, #3566) and streams.videolan.org/samples/IMKH/ couldn't be downloaded from my environment. The generated files follow FFmpeg's demuxer and Hikvision's documented layout. A short real hiv*.mp4 from an SD card would be the best confirmation, if anyone has one.

Screenshots (UI changes)

None, no UI changes.

Checklist

  • New UI strings are in all src/Filee.App/Assets/i18n/*.json files (no new UI strings)
  • New third-party code or binaries are listed in THIRD-PARTY-NOTICES.md (none added)
  • The PR does one thing (unrelated changes go in a separate PR)

🤖 Generated with Claude Code

Hikvision cameras and NVRs record MPEG program streams. iVMS-4200 / NVR
exports start with a 40-byte "IMKH" header and FFmpeg's mpeg demuxer
already reads them, whatever their extension (.mp4, .264). The recordings
on a camera's SD card or a NAS (hiv00000.mp4) are the same stream without
the header, and FFmpeg then reads their G.711 mu-law sound (stream type
0x91) as MP2 and fails. When a program stream's map lists mu-law sound and
the header is missing, Filee gives FFmpeg the header in front of the file
through the concat protocol, without copying the recording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
@Ugoyo
Ugoyo requested a review from KnifeLemon as a code owner November 13, 2026 06:53
@github-actions github-actions Bot added the feature New features label Nov 13, 2026

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @Ugoyo. Small and well contained: the stream-map check is bounded and fails safe (anything unexpected goes to
FFmpeg as it does today), the | guard is there, and the recording is never copied.

Looks good, merging. A few notes, none blocking:

  • Other entry points still get the raw file. The header is added only in FfmpegConverter.ConvertAsync. Caption
    (WhisperConverter runs -i step.InputPath), Live / Motion Photo (returns before the new line), Merge videos
    (JoinAsync) and the Cut a clip editor (VideoFrames, ClipPreview) still pass the plain path, so a mu-law SD-card
    recording keeps failing there. They fail today too, so this isn't a regression. #1463 needs the same input rewrite for
    .nov, so one shared resolver for both would be a good follow-up.
  • #1463 touches the same line (var input = ... in FfmpegConverter.cs) and the same spot in docs/ENGINES.md.
    This one goes in first, so #1463 rebases on it: .nov should go to NovExport.InputFor with the plain path, not a
    concat: string.
  • InputFor reads up to 256 KB of every FFmpeg input, audio included, with blocking I/O. Cheap locally; limiting it to
    video sources or reading with RandomAccess is optional.
  • The FFmpeg-backed A_hikvision_recording_converts_to_mp4_with_its_sound cases are skipped on every CI job, so CI covers
    the sniffer only. A short real hiv*.mp4 would be welcome if anyone has one.

@KnifeLemon
KnifeLemon merged commit 88e0198 into KnifeLemon:main Nov 13, 2026
11 checks passed
Ugoyo added a commit to Ugoyo/Filee that referenced this pull request Nov 13, 2026
Hikvision SD-card recordings (KnifeLemon#1462) and .nov exports now share the line
where FfmpegConverter picks FFmpeg's input: a .nov goes to
NovExport.InputFor with the plain path, every other file to
HikvisionStream.InputFor. Schema 87 went to the Arduino sketches on main,
so .nov takes 88.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
Ugoyo added a commit to Ugoyo/Filee that referenced this pull request Nov 13, 2026
From the review of KnifeLemon#1462: HikvisionStream read the first 256 KB of every
FFmpeg input through a FileStream. It now reads with RandomAccess, and
only the first four bytes unless they are a pack header, so an MP4 or a
Matroska file costs one small read. The shared header of the Cut a clip
editor is written once, under another name first, so a reader never sees
half of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants