Skip to content
This repository has been archived by the owner on Dec 15, 2020. It is now read-only.

Commit

Permalink
Fixing QuickTime recording (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnboiles committed May 20, 2020
1 parent f01212d commit c5a8868
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 26 deletions.
2 changes: 2 additions & 0 deletions src/dal-plugin/CMSampleBufferUtils.h
Expand Up @@ -10,3 +10,5 @@
OSStatus CMSampleBufferCreateFromData(NSSize size, CMSampleTimingInfo timingInfo, UInt64 sequenceNumber, NSData *data, CMSampleBufferRef *sampleBuffer);

OSStatus CMSampleBufferCreateFromDataNoCopy(NSSize size, CMSampleTimingInfo timingInfo, UInt64 sequenceNumber, NSData *data, CMSampleBufferRef *sampleBuffer);

CMSampleTimingInfo CMSampleTimingInfoForTimestamp(uint64_t timestampNanos, double fps);
15 changes: 15 additions & 0 deletions src/dal-plugin/CMSampleBufferUtils.mm
Expand Up @@ -119,3 +119,18 @@ OSStatus CMSampleBufferCreateFromDataNoCopy(NSSize size, CMSampleTimingInfo timi

return noErr;
}

CMSampleTimingInfo CMSampleTimingInfoForTimestamp(uint64_t timestampNanos, double fps) {
// The timing here is quite important. For frames to be delivered correctly and successfully be recorded by apps
// like QuickTime Player, we need to be accurate in both our timestamps _and_ have a sensible scale. Using large
// timestamps and scales like mach_absolute_time() and NSEC_PER_SEC will work for display, but will error out
// when trying to record.
//
// 600 is a commmon default in Apple's docs https://developer.apple.com/documentation/avfoundation/avmutablemovie/1390622-timescale
CMTimeScale scale = 600;
CMSampleTimingInfo timing;
timing.duration = CMTimeMake(scale, fps * scale);
timing.presentationTimeStamp = CMTimeMake((timestampNanos / (double)NSEC_PER_SEC) * scale, scale);
timing.decodeTimeStamp = kCMTimeInvalid;
return timing;
}
35 changes: 9 additions & 26 deletions src/dal-plugin/Stream.mm
Expand Up @@ -168,22 +168,10 @@ - (void)fillFrame {

CVPixelBufferRef pixelBuffer = [self createPixelBufferWithTestAnimation];

// The timing here is quite important. For frames to be delivered correctly and successfully be recorded by apps
// like QuickTime Player, we need to be accurate in both our timestamps _and_ have a sensible scale. Using large
// timestamps and scales like mach_absolute_time() and NSEC_PER_SEC will work for display, but will error out
// when trying to record.
//
// Instead, we start our presentation times from zero (using the sequence number as a base), and use a scale that's
// a multiple of our framerate. This has been observed in parts of AVFoundation and lets us be frame-accurate even
// on non-round framerates (i.e., we can use a scale of 2997 for 29,97 fps content if we want to).
CMTimeScale scale = FPS * 10;
CMTime frameDuration = CMTimeMake(scale / FPS, scale);
CMTime pts = CMTimeMake(frameDuration.value * self.sequenceNumber, scale);
CMSampleTimingInfo timing;
timing.duration = frameDuration;
timing.presentationTimeStamp = pts;
timing.decodeTimeStamp = pts;
OSStatus err = CMIOStreamClockPostTimingEvent(pts, mach_absolute_time(), true, self.clock);
uint64_t hostTime = mach_absolute_time();
CMSampleTimingInfo timingInfo = CMSampleTimingInfoForTimestamp(hostTime, FPS);

OSStatus err = CMIOStreamClockPostTimingEvent(timingInfo.presentationTimeStamp, hostTime, true, self.clock);
if (err != noErr) {
DLog(@"CMIOStreamClockPostTimingEvent err %d", err);
}
Expand All @@ -198,7 +186,7 @@ - (void)fillFrame {
kCFAllocatorDefault,
pixelBuffer,
format,
&timing,
&timingInfo,
self.sequenceNumber,
kCMIOSampleBufferNoDiscontinuities,
&buffer
Expand All @@ -224,22 +212,17 @@ - (void)queueFrameWithSize:(NSSize)size timestamp:(uint64_t)timestamp frameData:
}
OSStatus err = noErr;

CMTimeScale scale = FPS * 100;
CMTime frameDuration = CMTimeMake(scale / FPS, scale);
CMTime pts = CMTimeMake(timestamp, NSEC_PER_SEC);
CMSampleTimingInfo timing;
timing.duration = frameDuration;
timing.presentationTimeStamp = pts;
timing.decodeTimeStamp = kCMTimeInvalid;
err = CMIOStreamClockPostTimingEvent(pts, mach_absolute_time(), true, self.clock);
CMSampleTimingInfo timingInfo = CMSampleTimingInfoForTimestamp(timestamp, FPS);

err = CMIOStreamClockPostTimingEvent(timingInfo.presentationTimeStamp, mach_absolute_time(), true, self.clock);
if (err != noErr) {
DLog(@"CMIOStreamClockPostTimingEvent err %d", err);
}

self.sequenceNumber = CMIOGetNextSequenceNumber(self.sequenceNumber);

CMSampleBufferRef sampleBuffer;
CMSampleBufferCreateFromData(size, timing, self.sequenceNumber, frameData, &sampleBuffer);
CMSampleBufferCreateFromData(size, timingInfo, self.sequenceNumber, frameData, &sampleBuffer);
CMSimpleQueueEnqueue(self.queue, sampleBuffer);

// Inform the clients that the queue has been altered
Expand Down

0 comments on commit c5a8868

Please sign in to comment.