The Card That Recorded at 30
The first time both capture cards recorded together, each was asked for 60 frames a second and each delivered exactly 30.000. Nothing was dropped, nothing errored. A probe read the device's frame duration back at each step and found the cause: 16.667 ms (60 fps) inside the capture session's configuration, still 16.667 ms after the configuration was committed, and 33.367 ms (29.97 fps) the moment startRunning() returned. AVFoundation puts a UVC device back on its default rate when the session starts.
The fix is three lines in the recorder: set the frame duration again on the running device. The recorder then reads the duration back and reports fps_applied in its status, so the engine and the page see the rate the device actually took, not the rate that was asked for. There is a quiet lesson inside the fix too. Only a duration the device itself reports may be set; an unsupported one raises an Objective-C exception that Swift cannot catch. One card's "60" turned out to be 1000000/60000240 of a second, not 1/60, so the recorder takes the exact value from the device's own ranges.
One Clock for Two Pictures
Oriel records two feeds at once and promises that a moment means the same instant in both. Each input maps its segment's first capture timestamp to wall time on the host's clock, and every frame after it is placed from its own capture timestamp. A five-minute recording of both feeds checked this: the segment joints were off by the same small amounts on both feeds (around a millisecond, and once about 23 ms, on both at once), so the two timelines move together and stay aligned to within a frame.
Note what that check is and is not. It is a timestamp check. A visual check needs one event both cards can see, like a clap in front of the camera shown on the game screen at the same moment. None was available, so the architecture document says plainly that the visual frame-level check was not run. Saying which check you did is part of the measurement.
Two Clocks That Disagree
The external recorder whose decks feed the cards keeps its own files, with its own clock, and the family's editing workflow sometimes needs to line Oriel's frames up with those files. Two measured facts shape how that is done:
- 60.000 is not 59.94. The capture card delivers exactly 60.000 frames a second while the external recorder runs at 59.94. Oriel's frames are therefore exactly 16.667 ms apart, and the card repeats a source frame about every 16.7 seconds. Mapping a frame to the other recording by its index drifts a frame every 16.7 s; mapping it by time does not.
- The external recorder's clock drifts. Against the Mac's, it gains or loses about 2.2 ms a minute (34 to 38 parts per million, on both decks), and its timecode restarts in each clip. One offset carried across hours is wrong by seconds. So the bridge is made from the card's audio, which both recordings carry, by cross-correlation, and refitted along the way.
Sound on the Picture's Clock
Since a deck card's HDMI audio is now recorded inside the same segment files as its picture, the sound has to live on the picture's clock too. The probe stamps each audio buffer with the host time of its first sample. The recorder converts that host time to the capture session's synchronization clock with CMSyncConvertTime, the clock the video frames are stamped on, and writes AAC beside the HEVC.
Two small rules keep it clean. A buffer that starts within 2 ms of where the last one ended continues it exactly, because the card's clock and the host's differ by parts per million and each buffer is stamped on its own; a larger gap stays a gap, and an overlap is cut. And the picture never waits on the sound: a segment gets an audio track only if sound is flowing as it opens, sound that stops for a second finishes that segment's audio early, and sound that comes back opens the next segment at once.