Skip to content

Add DV (MiniDV camcorder) capture: IR tap to .dv file, plus FCP quadlet-write response fix#28

Merged
mrmidi merged 3 commits into
mrmidi:DICEfrom
hoffmabc:feature/dv-capture
Jun 12, 2026
Merged

Add DV (MiniDV camcorder) capture: IR tap to .dv file, plus FCP quadlet-write response fix#28
mrmidi merged 3 commits into
mrmidi:DICEfrom
hoffmabc:feature/dv-capture

Conversation

@hoffmabc

@hoffmabc hoffmabc commented Jun 12, 2026

Copy link
Copy Markdown

Summary

Adds a minimal DV (IEC 61883-2) capture path so MiniDV camcorders can be dumped to playable raw .dv files, validated end-to-end on real hardware (Panasonic MiniDV camcorder over OHCI FireWire 400 on macOS Tahoe). Also includes a standalone FCP bug fix found during testing.

DV capture path

Camcorder ──ch 63──▶ OHCI IR ──▶ IsochReceiveContext::Poll()
                                      │ (existing, previously unused per-packet callback)
                                      ▼
                              DVCaptureSink (driver)
                  CIP FMT=0x00 filter, prefix/CIP/SPH strip
                                      │ 480-byte DIF chunks
                                      ▼
                       shared-memory SPSC ring (~3.9 MB)
                                      │ CopyClientMemoryForType(type=1)
                                      ▼
                      DV Capture view (app) ──▶ capture.dv
  • DVCaptureSink (new, header-only): hooks the existing per-packet callback, filters CIP FMT=0x00, and streams raw 480-byte DIF chunks into the shared ring (drop-on-full, never blocks the poll loop). Handles both SPH=0 streams (consumer camcorders, dv1394-style: timestamp in CIP.SYT, plain 480-byte blocks) and SPH=1 (per-block source packet header).
  • IsochService::StartDVCapture/StopDVCapture: start the IR context on a chosen channel (default 63, camcorder broadcast) without requiring a published audio nub. The packet callback is installed before Start() so it never races Poll().
  • User client: selectors 50/51 (start/stop) + CopyClientMemoryForType type 1 for the ring.
  • App: new "DV Capture" view with AV/C tape transport controls (PLAY/STOP/REWIND via the existing raw FCP path), live stats (frames, drops, CIP diagnostics), and a frame-aware .dv writer that only emits exact-size frames — a glitched packet costs one dropped frame instead of misaligning the entire fixed-record DV file.

The audio RX pipeline is deliberately untouched. DV capture and audio receive are mutually exclusive (guarded with kIOReturnBusy) since both use IR context 0.

Bug fix: FCP responses sent as quadlet writes were dropped

Short AV/C responses (e.g. the 4-byte ACCEPTED a camcorder returns for a transport command) arrive as quadlet writes (tCode 0x0) to the FCP response register, not block writes. The quadlet-write request handler only consulted SBP-2 and returned AddressError for everything else, silently dropping these responses — every short AV/C command timed out on the response path.

The handler now falls through to FCPResponseRouter, reconstructing wire byte order from the little-endian AR header quadlet. Happy to split this into its own PR if preferred.

Hardware test results

  • Panasonic MiniDV camcorder, OHCI FireWire 400, macOS Tahoe (26)
  • Tape transport PLAY/STOP/WIND now complete with ACCEPTED (timed out before the FCP fix)
  • 32 s NTSC capture: 971/973 exact 120000-byte frames, DIF section structure verified, one duplicated-packet anomaly absorbed by the frame-aware writer
  • Output plays in QuickTime and remuxes losslessly with ffmpeg -c copy

Known limitations (intentionally minimal first pass)

  • Shares the single IR context with audio receive (mutually exclusive)
  • No CMP connection management — listens on the broadcast channel
  • No driver-side DBC continuity tracking

If the in-progress IR refactor is introducing a pluggable packet sink, DVCaptureSink can become an implementation of it — happy to adjust the integration shape.

hoffmabc and others added 2 commits June 12, 2026 12:15
Short AV/C responses (e.g. the 4-byte ACCEPTED a camcorder sends for a
tape transport command) arrive as quadlet writes (tCode 0x0) to the FCP
response register, not block writes. FcpLocalHandler only claimed block
writes, so these responses fell through and every short AV/C command
timed out on the response path.

Handle kTcodeWriteQuad by rebuilding wire byte order from the host-order
quadlet value and routing through the same path as block writes. The
handler still self-filters by destination offset, so non-FCP quadlet
writes continue to fall through to DICE/SBP-2.

Verified on hardware: tape subunit PLAY/STOP/WIND against a Panasonic
MiniDV camcorder now complete with ACCEPTED instead of timing out.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a minimal DV capture tap to the IR receive path so MiniDV camcorder
video can be captured to a raw .dv file:

- DVCaptureSink (driver): hooks the existing per-packet callback in
  IsochReceiveContext::Poll(), filters CIP FMT=0x00, and writes raw
  480-byte DIF chunks into a shared memory SPSC ring (~3.9MB,
  drop-on-full). Handles both SPH=0 streams (consumer camcorders,
  dv1394-style: timestamp in CIP.SYT, plain 480-byte blocks) and SPH=1
  (per-block source packet header).
- IsochService: StartDVCapture/StopDVCapture start IR on a given
  channel with no direct-audio binding source; the per-packet callback
  is installed before Start() to avoid racing Poll(). StartReceive
  gains an optional packet callback parameter (default unchanged).
- User client: selectors 50/51 (start/stop DV capture) and
  CopyClientMemoryForType type 1 to map the DIF ring into the app.
- App: new "DV Capture" view with AV/C tape transport controls
  (PLAY/STOP/REWIND via existing raw FCP path), channel selection
  (default 63), live stats (frames, drops, CIP diagnostics), and a
  frame-aware .dv writer that only emits exact-size frames so a
  glitched packet costs one frame instead of misaligning the file.

Verified on hardware (Panasonic MiniDV over OHCI FireWire 400):
32s NTSC capture, 971/973 clean frames, output plays in QuickTime and
remuxes losslessly with ffmpeg -c copy. (Validation was performed with
this change set on main; re-validation on DICE pending.)

Known limitations (intentionally minimal first pass): shares the single
IR context with audio receive (mutually exclusive, guarded with
kIOReturnBusy), no CMP connection management (listens on the broadcast
channel), no DBC continuity tracking in the driver.

Co-authored-by: Cursor <cursoragent@cursor.com>
@hoffmabc hoffmabc changed the base branch from main to DICE June 12, 2026 16:15
@hoffmabc hoffmabc force-pushed the feature/dv-capture branch from d979b35 to 2fa9b10 Compare June 12, 2026 16:15
@hoffmabc

Copy link
Copy Markdown
Author

Rebased onto the DICE branch and retargeted the PR base accordingly (was main). The port adapts to the DICE architecture: the FCP fix now lives in LocalRequestWiring.cpp's FcpLocalHandler (claims kTcodeWriteQuad in addition to block writes, rebuilding wire byte order from ctx.quadletData), DVCaptureSink uses the relocated Audio/Wire/CIP/CIPHeader.hpp, and IsochService::StartDVCapture starts IR with a null direct-audio binding source instead of the old shared-queue path. Builds clean on DICE. Note: hardware validation (the 32s capture) was performed with this change set on main; I'll re-run the capture against this branch on the Panasonic rig and report back.

Cross-checked the capture path against Apple's AVCVideoServices-42
DVReceiver/DVFramer (FireWire SDK 26 reference implementation):

- Frame-start detection now uses Apple's masked comparison
  ((first two DIF bytes & 0xE0FC) == 0x0004, i.e. SCT=header and
  Dseq=0) instead of an exact 0x1F 0x07 0x00 match; the unmasked bits
  are reserved/arbitrary and vary between devices.
- The sink rejects packets whose CIP.DBS is not 120 quadlets (SD-DVCR)
  rather than mis-slicing other DV variants (e.g. DVCPRO50) into
  480-byte records, and validates the payload is a whole number of
  blocks (Apple checks expected packet size the same way).

Apple's receiver also confirms the SPH=0 consumer-DV payload layout
(plain DBS-sized DIF blocks after the CIP, no per-block source packet
header) that the previous commit implemented empirically.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mrmidi

mrmidi commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Thanks a lot for contributing! Everything is nice and clean, I would take it as is.

@mrmidi mrmidi merged commit d299251 into mrmidi:DICE Jun 12, 2026
1 check passed
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.

2 participants