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 AVVideoCodecKey: AVVideoCodecH264,
105 AVVideoWidthKey: [NSNumber numberWithInt:_originalWidth],
106 AVVideoHeightKey: [NSNumber numberWithInt:_originalHeight]
110 self.assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
111 _assetWriterInput.expectsMediaDataInRealTime = YES;
115 NSDictionary *pa = @{
116 (NSString *)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA],
117 (NSString *)kCVPixelBufferWidthKey: [NSNumber numberWithInt:_originalWidth],
118 (NSString *)kCVPixelBufferHeightKey: [NSNumber numberWithInt:_originalHeight],
121 self.assetWriterInputPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor
122 assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_assetWriterInput
123 sourcePixelBufferAttributes:pa];
125 if (![_assetWriter canAddInput:_assetWriterInput])
127 VUserLog(
"Error adding AVAssetWriterInput: %s", [[_assetWriter.error description] UTF8String]);
130 [_assetWriter addInput:_assetWriterInput];
132 if (![_assetWriter startWriting])
134 VUserLog(
"Error starting writing: %s", [[_assetWriter.error description] UTF8String]);
137 [_assetWriter startSessionAtSourceTime:CMTimeMake(0, TIMEBASE)];
141 [
self saveImage:l.ioSurface];
151 - (void)appendBuffer:(const
unsigned char *)sourceBytes width:(
unsigned long)width height:(
unsigned long)height
168 if (!_assetWriterInput.readyForMoreMediaData)
170 VUserLog(
"Warning: AVFoundation video encoder isn't keeping up. Dropping this frame.");
174 CVPixelBufferRef pb = NULL;
175 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(NULL, [_assetWriterInputPixelBufferAdaptor pixelBufferPool], &pb);
176 if (ret != kCVReturnSuccess)
178 VUserLog(
"Error: Couldn't get buffer from pool: %d", ret);
181 VuoDefer(^{ CVPixelBufferRelease(pb); });
183 ret = CVPixelBufferLockBaseAddress(pb, 0);
184 if (ret != kCVReturnSuccess)
186 VUserLog(
"Error locking buffer: %d", ret);
190 unsigned char *bytes = (
unsigned char *)CVPixelBufferGetBaseAddress(pb);
193 VUserLog(
"Error getting buffer base address.");
194 ret = CVPixelBufferUnlockBaseAddress(pb, 0);
195 if (ret != kCVReturnSuccess)
196 VUserLog(
"Error unlocking buffer: %d", ret);
200 unsigned int bytesPerRow = CVPixelBufferGetBytesPerRow(pb);
204 for (
unsigned long y = 0; y < height; ++y)
205 memcpy(bytes + bytesPerRow * (height - y - 1), sourceBytes + width * y * 4, width * 4);
207 ret = CVPixelBufferUnlockBaseAddress(pb, 0);
208 if (ret != kCVReturnSuccess)
209 VUserLog(
"Error unlocking buffer: %d", ret);
212 if (CMTimeCompare(time, _priorFrameTime) <= 0)
215 time = _priorFrameTime;
220 if (![_assetWriterInputPixelBufferAdaptor appendPixelBuffer:pb withPresentationTime:time])
221 VUserLog(
"Error appending buffer: %s", [[_assetWriter.error description] UTF8String]);
223 _priorFrameTime = time;
226 static void VuoWindowRecorder_doNothingCallback(
VuoImage imageToFree)
239 VUserLog(
"Error: NULL IOSurface. Skipping frame.");
246 if (width == 0 || height == 0)
248 VUserLog(
"Error: Invalid viewport size %dx%d. Skipping frame.", width, height);
254 dispatch_sync(_queue, ^{
260 if (width == _originalWidth && height == _originalHeight)
271 if (width != _originalWidth || height != _originalHeight)
276 VUserLog(
"Error: Failed to resize image.");
283 image = resizedImage;
288 dispatch_async(_queue, ^{
293 [
self appendBuffer:sourceBytes width:_originalWidth height:_originalHeight];
296 _totalAsyncTime += t1 - t0;
300 VUserLog(
"Error: Couldn't download image.");
304 _priorHeight = height;
307 _totalSyncTime += t1 - t0;
321 dispatch_sync(_queue, ^{
322 dispatch_semaphore_t finishedWriting = dispatch_semaphore_create(0);
323 [_assetWriter finishWritingWithCompletionHandler:^{
324 dispatch_semaphore_signal(finishedWriting);
326 dispatch_semaphore_wait(finishedWriting, DISPATCH_TIME_FOREVER);
327 dispatch_release(finishedWriting);
329 if (_assetWriter.status != AVAssetWriterStatusCompleted)
330 VUserLog(
"Error: %s", [[_assetWriter.error localizedDescription] UTF8String]);
332 dispatch_release(_queue);
338 VUserLog(
"Average render-blocking record time per frame: %g", _totalSyncTime / _frameCount);
339 VUserLog(
"Average background record time per frame: %g", _totalAsyncTime / _frameCount);