Hello社区,
我尝试使用Swift 4和即将推出的 ARKit-Framework 构建应用程序,但我陷入困境。我需要使用或至少一个UIImage序列来一个视频,但我不知道如何。
我尝试过:
在ARKit中,您有一个跟踪您的世界的会话。此会话有一个 capturedImage 实例,您可以在其中获取当前图像。所以我创建了一个Timer,它将 captureImage每0.1秒附加到List。这对我有用但如果我通过点击"开始" - 按钮启动计时器,摄像头开始滞后。它不是关于我猜的定时器,因为如果我通过点击"停止"按钮使定时器失效,相机再次流畅。
有没有办法解决滞后甚至更好的方法?
由于
答案 0 :(得分:2)
我能够使用ReplayKit来做到这一点。
在您的iOS设备上,转到设置 - >控制中心 - >自定义控件。将“屏幕录制”移动到“包含”部分,然后向上滑动以调出控制中心。您现在应该看到圆形屏幕录制图标,您会注意到当您按下它时,iOS会开始录制您的屏幕。点击蓝色条将结束录制并将视频保存到照片。
使用ReplayKit,您可以让您的应用调用屏幕录像机并捕获您的ARKit内容。
开始录制:
RPScreenRecorder.shared().startRecording { error in
// Handle error, if any
}
停止录制:
RPScreenRecorder.shared().stopRecording(handler: { (previewVc, error) in
// Do things
})
完成录制后,.stopRecording
会为您提供一个可选的RPPreviewViewController,这是
显示用户界面的对象,用户可在其中预览和编辑使用ReplayKit创建的屏幕录像。
因此,在我们的示例中,如果它不是nil
,则可以显示previewVcRPScreenRecorder.shared().stopRecording(handler: { (previewVc, error) in
if let previewVc = previewVc {
previewVc.delegate = self
self.present(previewVc, animated: true, completion: nil)
}
})
您可以直接从previewVc编辑和保存视频,但您可能想让自己(或某人)成为RPPreviewViewControllerDelegate,这样您就可以在完成后轻松关闭预览视图。
extension MyViewController: RPPreviewViewControllerDelegate {
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
// Called when the preview vc is ready to be dismissed
}
}
您会注意到startRecording
会记录“应用程序显示”,因此如果您拥有任何视图(按钮,标签等)也会被记录下来。
我发现在录制时隐藏控件很有用,让我的用户知道点击屏幕会停止录制,但我也读到其他人成功将他们的基本控件放在单独的UIWindow上。
单独的UIWindow技巧有效。我能够制作一个覆盖窗口,我有一个记录按钮和一个计时器,这些都没有记录下来。
let overlayWindow = UIWindow(frame: view.frame)
let recordButton = UIButton( ... )
overlayWindow.backgroundColor = UIColor.clear
默认情况下会隐藏UIWindow
。因此,当您想要显示控件时,必须将isHidden
设置为false
。
祝你好运!
答案 1 :(得分:2)
我已经发布了一个开源框架来处理这个问题。 https://github.com/svtek/SceneKitVideoRecorder
它的工作原理是从场景视图中获取可绘制的金属层。
您可以附加显示链接,以便在屏幕刷新时调用渲染器:
displayLink = CADisplayLink(target: self, selector: #selector(updateDisplayLink))
displayLink?.add(to: .main, forMode: .commonModes)
然后通过以下方式从金属层抓取drawable:
let metalLayer = sceneView.layer as! CAMetalLayer
let nextDrawable = metalLayer.nextDrawable()
警惕nextDrawable()
电话会消费抽奖。你应该尽可能少地调用它,并在autoreleasepool{}
中这样做,这样drawable就会被正确释放并换成新的。
然后你应该从drawable读取MTLTexture到像素缓冲区,你可以将其附加到AVAssetWriter来创建视频。
let destinationTexture = currentDrawable.texture
destinationTexture.getBytes(...)
考虑到这些,剩下的就是iOS / Cocoa上非常简单的视频录制。
您可以在我上面分享的回购中找到所有这些。
答案 2 :(得分:2)
使用自定义渲染器。
使用自定义渲染器渲染场景,然后从自定义渲染器获取纹理,最后将其转换为CVPixelBufferRef
- (void)viewDidLoad {
[super viewDidLoad];
self.rgbColorSpace = CGColorSpaceCreateDeviceRGB();
self.bytesPerPixel = 4;
self.bitsPerComponent = 8;
self.bitsPerPixel = 32;
self.textureSizeX = 640;
self.textureSizeY = 960;
// Set the view's delegate
self.sceneView.delegate = self;
// Show statistics such as fps and timing information
self.sceneView.showsStatistics = YES;
// Create a new scene
SCNScene *scene = [SCNScene scene];//[SCNScene sceneNamed:@"art.scnassets/ship.scn"];
// Set the scene to the view
self.sceneView.scene = scene;
self.sceneView.preferredFramesPerSecond = 30;
[self setupMetal];
[self setupTexture];
self.renderer.scene = self.sceneView.scene;
}
- (void)setupMetal
{
if (self.sceneView.renderingAPI == SCNRenderingAPIMetal) {
self.device = self.sceneView.device;
self.commandQueue = [self.device newCommandQueue];
self.renderer = [SCNRenderer rendererWithDevice:self.device options:nil];
}
else {
NSAssert(nil, @"Only Support Metal");
}
}
- (void)setupTexture
{
MTLTextureDescriptor *descriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm_sRGB width:self.textureSizeX height:self.textureSizeY mipmapped:NO];
descriptor.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
id<MTLTexture> textureA = [self.device newTextureWithDescriptor:descriptor];
self.offscreenTexture = textureA;
}
- (void)renderer:(id <SCNSceneRenderer>)renderer willRenderScene:(SCNScene *)scene atTime:(NSTimeInterval)time
{
[self doRender];
}
- (void)doRender
{
if (self.rendering) {
return;
}
self.rendering = YES;
CGRect viewport = CGRectMake(0, 0, self.textureSizeX, self.textureSizeY);
id<MTLTexture> texture = self.offscreenTexture;
MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor new];
renderPassDescriptor.colorAttachments[0].texture = texture;
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 1, 0, 1.0);
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
self.renderer.pointOfView = self.sceneView.pointOfView;
[self.renderer renderAtTime:0 viewport:viewport commandBuffer:commandBuffer passDescriptor:renderPassDescriptor];
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull bf) {
[self.recorder writeFrameForTexture:texture];
self.rendering = NO;
}];
[commandBuffer commit];
}
然后在录音机中,使用AVAssetWriterInputPixelBufferAdaptor
设置AVAssetWriter
。并将纹理转换为CVPixelBufferRef
:
- (void)writeFrameForTexture:(id<MTLTexture>)texture {
CVPixelBufferPoolRef pixelBufferPool = self.assetWriterPixelBufferInput.pixelBufferPool;
CVPixelBufferRef pixelBuffer;
CVReturn status = CVPixelBufferPoolCreatePixelBuffer(nil, pixelBufferPool, &pixelBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
void *pixelBufferBytes = CVPixelBufferGetBaseAddress(pixelBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
MTLRegion region = MTLRegionMake2D(0, 0, texture.width, texture.height);
[texture getBytes:pixelBufferBytes bytesPerRow:bytesPerRow fromRegion:region mipmapLevel:0];
[self.assetWriterPixelBufferInput appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime];
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
CVPixelBufferRelease(pixelBuffer);
}
确保自定义渲染器和适配器共享相同的像素编码。
我测试了这个默认值ship.scn
并且它只消耗30%的CPU,相比之下,每帧使用snapshot
方法几乎占90%。这不会弹出权限对话框。
答案 3 :(得分:0)
我有类似的需求,并希望在内部在应用程序中记录ARSceneView,并且没有ReplayKit,以便我可以操作从录制生成的视频。我最终使用了这个项目:https://github.com/lacyrhoades/SceneKit2Video。该项目用于将SceneView呈现给视频,但您可以将其配置为接受ARSceneViews。它工作得很好,如果你愿意,你可以选择使用委托功能获取图像源而不是视频。