15 #include <OpenGL/OpenGL.h>
16 #include <OpenGL/CGLMacro.h>
17 #include <AVFoundation/AVFoundation.h>
26 "title" :
"VuoWindowRecorder",
28 "AVFoundation.framework",
29 "CoreMedia.framework",
52 @property dispatch_queue_t
queue;
64 @property (retain) AVAssetWriter *assetWriter;
65 @property (retain) AVAssetWriterInput *assetWriterInput;
66 @property (retain) AVAssetWriterInputPixelBufferAdaptor *assetWriterInputPixelBufferAdaptor;
80 if (
self = [super init])
82 _queue = dispatch_queue_create(
"org.vuo.VuoWindowRecorder", NULL);
85 _priorFrameTime = CMTimeMake(-1,
TIMEBASE);
87 NSView *v = window.contentView;
89 NSRect frameInPoints = v.frame;
90 NSRect frameInPixels = [window convertRectToBacking:frameInPoints];
91 _originalWidth = _priorWidth = frameInPixels.size.width;
92 _originalHeight = _priorHeight = frameInPixels.size.height;
100 self.assetWriter = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeQuickTimeMovie error:&e];
103 NSDictionary *videoSettings = @{
104 #pragma clang diagnostic push
105 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
107 AVVideoCodecKey: AVVideoCodecH264,
108 #pragma clang diagnostic pop
109 AVVideoWidthKey: [NSNumber numberWithInt:_originalWidth],
110 AVVideoHeightKey: [NSNumber numberWithInt:_originalHeight]
114 self.assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
115 _assetWriterInput.expectsMediaDataInRealTime = YES;
119 NSDictionary *pa = @{
120 (NSString *)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA],
121 (NSString *)kCVPixelBufferWidthKey: [NSNumber numberWithInt:_originalWidth],
122 (NSString *)kCVPixelBufferHeightKey: [NSNumber numberWithInt:_originalHeight],
125 self.assetWriterInputPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor
126 assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_assetWriterInput
127 sourcePixelBufferAttributes:pa];
129 if (![_assetWriter canAddInput:_assetWriterInput])
131 VUserLog(
"Error adding AVAssetWriterInput: %s", [[_assetWriter.error description] UTF8String]);
134 [_assetWriter addInput:_assetWriterInput];
136 if (![_assetWriter startWriting])
138 VUserLog(
"Error starting writing: %s", [[_assetWriter.error description] UTF8String]);
141 [_assetWriter startSessionAtSourceTime:CMTimeMake(0, TIMEBASE)];
155 - (void)appendBuffer:(const
unsigned char *)sourceBytes width:(
unsigned long)width height:(
unsigned long)height
172 if (!_assetWriterInput.readyForMoreMediaData)
174 VUserLog(
"Warning: AVFoundation video encoder isn't keeping up. Dropping this frame.");
178 CVPixelBufferRef pb = NULL;
179 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(NULL, [_assetWriterInputPixelBufferAdaptor pixelBufferPool], &pb);
180 if (ret != kCVReturnSuccess)
182 VUserLog(
"Error: Couldn't get buffer from pool: %d", ret);
185 VuoDefer(^{ CVPixelBufferRelease(pb); });
187 ret = CVPixelBufferLockBaseAddress(pb, 0);
188 if (ret != kCVReturnSuccess)
190 VUserLog(
"Error locking buffer: %d", ret);
194 unsigned char *bytes = (
unsigned char *)CVPixelBufferGetBaseAddress(pb);
197 VUserLog(
"Error getting buffer base address.");
198 ret = CVPixelBufferUnlockBaseAddress(pb, 0);
199 if (ret != kCVReturnSuccess)
200 VUserLog(
"Error unlocking buffer: %d", ret);
204 unsigned int bytesPerRow = CVPixelBufferGetBytesPerRow(pb);
208 for (
unsigned long y = 0; y < height; ++y)
209 memcpy(bytes + bytesPerRow * (height - y - 1), sourceBytes + width * y * 4, width * 4);
211 ret = CVPixelBufferUnlockBaseAddress(pb, 0);
212 if (ret != kCVReturnSuccess)
213 VUserLog(
"Error unlocking buffer: %d", ret);
216 if (CMTimeCompare(time, _priorFrameTime) <= 0)
219 time = _priorFrameTime;
224 if (![_assetWriterInputPixelBufferAdaptor appendPixelBuffer:pb withPresentationTime:time])
225 VUserLog(
"Error appending buffer: %s", [[_assetWriter.error description] UTF8String]);
227 _priorFrameTime = time;
230 static void VuoWindowRecorder_doNothingCallback(
VuoImage imageToFree)
243 VUserLog(
"Error: NULL IOSurface. Skipping frame.");
250 if (width == 0 || height == 0)
252 VUserLog(
"Error: Invalid viewport size %dx%d. Skipping frame.", width, height);
258 dispatch_sync(_queue, ^{
264 if (width == _originalWidth && height == _originalHeight)
275 if (width != _originalWidth || height != _originalHeight)
280 VUserLog(
"Error: Failed to resize image.");
287 image = resizedImage;
292 dispatch_async(_queue, ^{
297 [
self appendBuffer:sourceBytes width:_originalWidth height:_originalHeight];
300 _totalAsyncTime += t1 - t0;
304 VUserLog(
"Error: Couldn't download image.");
308 _priorHeight = height;
311 _totalSyncTime += t1 - t0;
325 dispatch_sync(_queue, ^{
326 dispatch_semaphore_t finishedWriting = dispatch_semaphore_create(0);
327 [_assetWriter finishWritingWithCompletionHandler:^{
328 dispatch_semaphore_signal(finishedWriting);
330 dispatch_semaphore_wait(finishedWriting, DISPATCH_TIME_FOREVER);
331 dispatch_release(finishedWriting);
333 if (_assetWriter.status != AVAssetWriterStatusCompleted)
334 VUserLog(
"Error: %s", [[_assetWriter.error localizedDescription] UTF8String]);
336 dispatch_release(_queue);
342 VUserLog(
"Average render-blocking record time per frame: %g", _totalSyncTime / _frameCount);
343 VUserLog(
"Average background record time per frame: %g", _totalAsyncTime / _frameCount);