核心思路
通过SCNView的回调,取到id<SCNSceneRenderer>
中 metal renderPass的渲染目标纹理,将渲染目标纹理拷贝到基于CVPixelBuffer创建的Metal纹理,再将CVPixelBuffer写到视频文件中即可。
准备视频写入工具
使用系统自带的AVAssetWriter
可以轻松的进行视频创建,通过下面的代码创建并配置AVAssetWriter
self.videoWriter = [AVAssetWriter assetWriterWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:&error];
NSDictionary *writerInputParams = [NSDictionary dictionaryWithObjectsAndKeys:
AVVideoCodecTypeH264, AVVideoCodecKey,
[NSNumber numberWithInt:width], AVVideoWidthKey,
[NSNumber numberWithInt:height], AVVideoHeightKey,
AVVideoScalingModeResizeAspectFill, AVVideoScalingModeKey,
nil];
self.assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:writerInputParams];
if ([self.videoWriter canAddInput:self.assetWriterInput]) {
[self.videoWriter addInput:self.assetWriterInput];
} else {
// show error message
}
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], (NSString*)kCVPixelBufferPixelFormatTypeKey,
[NSNumber numberWithBool:YES], (NSString *)kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey,
nil];
self.writerAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.assetWriterInput sourcePixelBufferAttributes:attributes];
通过下面的API可以将CVPixelBuffer写入视频文件
[self.writerAdaptor appendPixelBuffer:cvBuffer withPresentationTime:presentationTime]
准备接受图像数据的CVPixelBuffer
创建CVPixelBuffer并通过CVPixelBuffer创建MTLTexture,用于复制SCNView的渲染目标纹理
int imageWidth = size.width;
int imageHeight = size.height;
NSDictionary *pixelAttributes = @{( id )kCVPixelBufferIOSurfacePropertiesKey : @{}};
CVPixelBufferCreate(
kCFAllocatorDefault,
imageWidth,
imageHeight,
kCVPixelFormatType_32BGRA,
(__bridge CFDictionaryRef)pixelAttributes,
&_renderTargetPixelBuffer);
CVReturn ret = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, _mtlDevice, nil, &_textureCache);
CVMetalTextureRef renderTargetMetalTextureRef;
ret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCache, _renderTargetPixelBuffer, nil, MTLPixelFormatBGRA8Unorm, imageWidth, imageHeight, 0, &renderTargetMetalTextureRef);
id<MTLTexture> mtlTexture = CVMetalTextureGetTexture(renderTargetMetalTextureRef);
CFRelease(renderTargetMetalTextureRef);
将SCNView的渲染结果拷贝到CVPixelBuffer
我们需要在SCNView的- (void)renderer:(id<SCNSceneRenderer>)renderer didRenderScene:(SCNScene *)scene atTime:(NSTimeInterval)time
回调中拷贝渲染结果,为了可以拷贝SCNView的渲染目标纹理,需要做以下设置
CAMetalLayer *metalLayer = (CAMetalLayer *)self.sceneView.layer;
[metalLayer setAllowsNextDrawableTimeout:NO];
metalLayer.framebufferOnly = NO;
在回调中我们利用MTLBlitCommandEncoder
拷贝渲染目标纹理到我们自己的纹理
MTLRenderPassDescriptor *renderPassDesc = renderer.currentRenderPassDescriptor;
id<MTLTexture> readTexture = renderPassDesc.colorAttachments[0].texture;
if (readTexture.width != _renderTargetTexture.width || readTexture.height != _renderTargetTexture.height) {
_renderTargetTexture = [self createRenderTargetTexture:CGSizeMake(readTexture.width, readTexture.height)];
}
id<MTLCommandBuffer> blitCommandBuffer = [renderer.commandQueue commandBuffer];
id <MTLBlitCommandEncoder> blitEncoder = [blitCommandBuffer blitCommandEncoder];
[blitEncoder copyFromTexture:readTexture toTexture:_renderTargetTexture];
[blitEncoder endEncoding];
[blitCommandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull buffer) {
[self updateWithFrame:self->_renderTargetPixelBuffer];
}];
[blitCommandBuffer commit];
blitCommandBuffer
来自SCNView的commandQueue
,所以拷贝纹理的command会在SCNView渲染完之后才执行,保证了拷贝的纹理是一次完整的渲染。注意blitCommandBuffer
不可以使用waitUntilCompleted
堵塞线程,会导致死锁。最后在updateWithFrame
将CVPixelBuffer写入AVAssetWriter
。