From c5a88688504a2c59989cce33ab6912cfef9e44fc Mon Sep 17 00:00:00 2001 From: John Boiles Date: Wed, 20 May 2020 08:29:08 -0700 Subject: [PATCH] Fixing QuickTime recording (#133) --- src/dal-plugin/CMSampleBufferUtils.h | 2 ++ src/dal-plugin/CMSampleBufferUtils.mm | 15 ++++++++++++ src/dal-plugin/Stream.mm | 35 +++++++-------------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/dal-plugin/CMSampleBufferUtils.h b/src/dal-plugin/CMSampleBufferUtils.h index 43f295f..a64901c 100644 --- a/src/dal-plugin/CMSampleBufferUtils.h +++ b/src/dal-plugin/CMSampleBufferUtils.h @@ -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); diff --git a/src/dal-plugin/CMSampleBufferUtils.mm b/src/dal-plugin/CMSampleBufferUtils.mm index feab93d..4eb33b2 100644 --- a/src/dal-plugin/CMSampleBufferUtils.mm +++ b/src/dal-plugin/CMSampleBufferUtils.mm @@ -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; +} diff --git a/src/dal-plugin/Stream.mm b/src/dal-plugin/Stream.mm index cd4be08..4bf276d 100644 --- a/src/dal-plugin/Stream.mm +++ b/src/dal-plugin/Stream.mm @@ -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); } @@ -198,7 +186,7 @@ - (void)fillFrame { kCFAllocatorDefault, pixelBuffer, format, - &timing, + &timingInfo, self.sequenceNumber, kCMIOSampleBufferNoDiscontinuities, &buffer @@ -224,14 +212,9 @@ - (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); } @@ -239,7 +222,7 @@ - (void)queueFrameWithSize:(NSSize)size timestamp:(uint64_t)timestamp frameData: 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