用ARKIT拍摄视频

时间:2017-07-26 11:56:38

标签: swift list video-capture arkit

Hello社区

我尝试使用Swift 4和即将推出的 ARKit-Framework 构建应用程序,但我陷入困境。我需要使用或至少一个UIImage序列一个视频,但我不知道如何。

我尝试过

在ARKit中,您有一个跟踪您的世界的会话。此会话有一个 capturedImage 实例,您可以在其中获取当前图像。所以我创建了一个Timer,它将 captureImage每0.1秒附加到List。这对我有用但如果我通过点击"开始" - 按钮启动计时器,摄像头开始滞后。它不是关于我猜的定时器,因为如果我通过点击"停止"按钮使定时器失效,相机再次流畅。

有没有办法解决滞后甚至更好的方法?

由于

4 个答案:

答案 0 :(得分:2)

我能够使用ReplayKit来做到这一点。

看看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

,则可以显示previewVc
RPScreenRecorder.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。它工作得很好,如果你愿意,你可以选择使用委托功能获取图像源而不是视频。