Skip to content

MV-HEVC tools - encoder and tester#168

Merged
elv-serban merged 28 commits into
masterfrom
ss/mvhevc-tools
May 28, 2026
Merged

MV-HEVC tools - encoder and tester#168
elv-serban merged 28 commits into
masterfrom
ss/mvhevc-tools

Conversation

@elv-serban
Copy link
Copy Markdown
Contributor

@elv-serban elv-serban commented May 6, 2026

Add MV-HEVC tools

Process is documented in cmd/mvhevc/README.md

mvhevc_encoder (C, using libx264 directly - no ffmpeg libav*)

  • create one "HEVC" file (raw HEVC format) from two source files left-eye, right eye
  • only 8 bit (SDR) - there is no 10 bit MV-HEVC support in libx265

mvhevc with commands add info and fix

  • converts a raw "HEVC" file to "MP4" adding all the MP4 signaling for MV-HEVC
  • the fix command adds missing MP4 signaling for the

mvhevc_apple

  • create one "mov" file from two source files left-eye, right-eye
  • must run mvhevc fix after - the apple toolkit doesn't add oinf/linf and colr boxes

@elv-serban elv-serban requested a review from elv-john May 6, 2026 22:25
@elv-serban elv-serban changed the title WIP MV-HEVC tools - encoder and tester MV-HEVC tools - encoder and tester May 19, 2026
@elv-serban elv-serban marked this pull request as ready for review May 19, 2026 20:47
Comment thread cmd/mvhevc/Makefile Outdated
Comment thread cmd/mvhevc/README.md Outdated
@elv-serban elv-serban requested a review from elv-john May 21, 2026 00:55
Copy link
Copy Markdown
Contributor

@elv-peter elv-peter left a comment

Choose a reason for hiding this comment

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

I only looked at the modified files (not new ones)

Comment thread avpipe.go Outdated
Comment thread avpipe_test.go Outdated
@elv-serban elv-serban merged commit 9bf73b3 into master May 28, 2026
@elv-serban elv-serban deleted the ss/mvhevc-tools branch May 28, 2026 21:44
@elv-peter
Copy link
Copy Markdown
Contributor

Claude:

XcInit/XcRun MV-HEVC output wrapper never installed

Xc (one-shot) correctly registers the URL before calling into C:

  // avpipe.go, Xc()
  registerMvhevcRestoreURL(params.Url)   // line 911 — before C call
  ...
  rc := C.xc(...)                         // line 921 — AVPipeOpenInput fires here

XcInit gets this backwards:

  // avpipe.go, XcInit()
  rc := C.xc_init(...)                              // line 1212 — AVPipeOpenInput fires HERE
  ...
  registerMvhevcRestoreHandle(int32(handle), ...)   // line 1220 — too late

AVPipeOpenInput calls shouldRestoreMvhevcForURL to decide whether to set h.restoreMvhevc = true. But at the time it runs, the URL hasn't been registered yet, so it returns false. The output wrapper is never installed, and the MV-HEVC boxes (oinf, linf, trgr) are stripped from every output segment. The fix is to register before C.xc_init, mirroring the Xc pattern.

@elv-peter
Copy link
Copy Markdown
Contributor

Another one:

enhanceStreamInfo/ExtractCodecInfo track index misalignment

File: avpipe_probe.go, line 44

ExtractCodecInfo iterates every moov.Traks entry and emits a CodecInfo for each — including non-AV tracks like timecode (tmcd) or subtitles, which get a stub entry (CodecInfo{CodecTagString: "tmcd"}).

enhanceStreamInfo walks the probe's StreamInfo slice and skips non-AV streams with continue — but it only increments codecInfoIdx when it consumes an entry, not when it skips one. So with tracks [video, tmcd, audio]:

codecInfos streams What happens
[0] videoCI video stream matched correctly
[1] tmcdCI data stream stream skipped, codecInfoIdx stays at 1
[2] audioCI audio stream gets codecInfos[1] = tmcdCI ← wrong

The audio stream ends up with CodecTagString = "tmcd" and wrong Mp4Info.

Fix: Either skip non-AV entries in codecInfos inside enhanceStreamInfo (advance codecInfoIdx past entries whose CodecTagString is not a known AV type), or have ExtractCodecInfo omit non-AV tracks entirely.

@elv-peter
Copy link
Copy Markdown
Contributor

mvhevcEnhancementPTL returns nil for same-profile MV-HEVC

File: mp4e/codec_info.go, line 462

mvhevcEnhancementPTL walks vps.ExtProfileTierLevels looking for an entry that differs from the base layer. If all entries match (or are zero), it returns nil. The caller only sets VideoLayout = Mp4VideoLayoutMVHEVC when it returns non-nil:

if vps.IsMultiLayer() {
    if enhPTL := mvhevcEnhancementPTL(vps); enhPTL != nil {
        info.VideoLayout = Mp4VideoLayoutMVHEVC   // skipped when nil
        ...
    }
}

So a file can satisfy IsMultiLayer() (genuinely multi-layer VPS) but still be classified as mono if the encoder encodes the enhancement PTL with the same GeneralProfileIDC/GeneralProfileSpace as the base layer. The stream is silently misclassified with no error or log.

For well-formed Apple MV-HEVC (base IDC 1, enhancement IDC 6) this won't fire. The risk is non-standard encoders that copy the base PTL into enhancement slots.

Fix: If vps.IsMultiLayer() is true but mvhevcEnhancementPTL returns nil, either log a warning, fall back to Mp4VideoLayoutMVHEVC with the base PTL, or document the assumption that IsMultiLayer + same-IDC is not a supported case.

@elv-serban
Copy link
Copy Markdown
Contributor Author

Claude:

XcInit/XcRun MV-HEVC output wrapper never installed

Xc (one-shot) correctly registers the URL before calling into C:

  // avpipe.go, Xc()
  registerMvhevcRestoreURL(params.Url)   // line 911 — before C call
  ...
  rc := C.xc(...)                         // line 921 — AVPipeOpenInput fires here

XcInit gets this backwards:

  // avpipe.go, XcInit()
  rc := C.xc_init(...)                              // line 1212 — AVPipeOpenInput fires HERE
  ...
  registerMvhevcRestoreHandle(int32(handle), ...)   // line 1220 — too late

AVPipeOpenInput calls shouldRestoreMvhevcForURL to decide whether to set h.restoreMvhevc = true. But at the time it runs, the URL hasn't been registered yet, so it returns false. The output wrapper is never installed, and the MV-HEVC boxes (oinf, linf, trgr) are stripped from every output segment. The fix is to register before C.xc_init, mirroring the Xc pattern.

This is correct because xc_init() doesn't do the output wrapper registration. That is done later, in xc_run(). So both Xc() and XcInit() initialize the wrapper correctly.

@elv-serban
Copy link
Copy Markdown
Contributor Author

Another one:

enhanceStreamInfo/ExtractCodecInfo track index misalignment

File: avpipe_probe.go, line 44

ExtractCodecInfo iterates every moov.Traks entry and emits a CodecInfo for each — including non-AV tracks like timecode (tmcd) or subtitles, which get a stub entry (CodecInfo{CodecTagString: "tmcd"}).

enhanceStreamInfo walks the probe's StreamInfo slice and skips non-AV streams with continue — but it only increments codecInfoIdx when it consumes an entry, not when it skips one. So with tracks [video, tmcd, audio]:

codecInfos streams What happens
[0] videoCI video stream matched correctly
[1] tmcdCI data stream stream skipped, codecInfoIdx stays at 1
[2] audioCI audio stream gets codecInfos[1] = tmcdCI ← wrong
The audio stream ends up with CodecTagString = "tmcd" and wrong Mp4Info.

Fix: Either skip non-AV entries in codecInfos inside enhanceStreamInfo (advance codecInfoIdx past entries whose CodecTagString is not a known AV type), or have ExtractCodecInfo omit non-AV tracks entirely.

This is indeed a bug: fixed in #177

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants