Снимите видео с ARKIT

Здравствуйте, сообщество!

Я пытаюсь создать приложение с помощью Swift 4 и замечательной предстоящей ARKit-Framework, но я застрял. Мне нужно взять видео с помощью Framework или хотя бы UIImage-последовательность, но я не знаю, как это сделать.

Вот что я пробовал:

В ARKit у вас есть сеанс, который отслеживает ваш мир. В этом сеансе есть экземпляр capturedImage, из которого можно получить текущее изображение. Поэтому я создал таймер, который добавляет capturedImage каждые 0,1 с в список. Это сработало бы для меня, но если я запущу таймер, нажав кнопку «Пуск», камера начнет отставать. Я думаю, дело не в таймере, потому что, если я отключу таймер, нажав кнопку «стоп», камера снова будет работать свободно.

Есть ли способ решить лаги или даже лучший способ?

Спасибо


person Olfa Nomla    schedule 26.07.2017    source источник
comment
Можете ли вы добавить код, чтобы показать, как вы извлекаете и добавляете все изображения в список?   -  person donnywals    schedule 26.07.2017
comment
Пробовали ли вы использовать ReplayKit?   -  person jlsiewert    schedule 26.07.2017
comment
Возможный дубликат рендеринга сцены SceneKit для вывода видео   -  person Ömer Karışman    schedule 10.09.2017


Ответы (4)


Именно для этого я смог использовать 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.

Итак, в нашем примере вы можете представить 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.

Удачи тебе!

person Tulio Troncoso    schedule 27.07.2017

Используйте пользовательский рендерер.

Визуализируйте сцену с помощью пользовательского рендерера, затем получите текстуру из пользовательского рендерера, наконец, скройте это в 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% ЦП по сравнению с почти 90% по сравнению с использованием метода snapshot для каждого кадра. И это не приведет к появлению диалогового окна разрешения.

person sbhhbs    schedule 27.10.2017

Я выпустил фреймворк с открытым исходным кодом, который позаботится об этом. https://github.com/svtek/SceneKitVideoRecorder

Он работает, получая чертежи из металлического слоя представлений сцены.

Вы можете прикрепить ссылку на отображение, чтобы ваш рендерер вызывался при обновлении экрана:

displayLink = CADisplayLink(target: self, selector: #selector(updateDisplayLink))
displayLink?.add(to: .main, forMode: .commonModes)

А затем возьмите слой, который можно нарисовать из металла, с помощью:

let metalLayer = sceneView.layer as! CAMetalLayer
let nextDrawable = metalLayer.nextDrawable()

Будьте осторожны, что вызов nextDrawable() расходует чертежи. Вы должны называть это как можно реже и делать это в autoreleasepool{}, чтобы вытягиваемый объект был правильно выпущен и заменен новым.

Затем вы должны прочитать MTLTexture из рисуемого в пиксельный буфер, который вы можете добавить в AVAssetWriter для создания видео.

let destinationTexture = currentDrawable.texture
destinationTexture.getBytes(...)

Имея это в виду, остальное довольно просто записать видео на iOS/Cocoa.

Вы можете найти все это в репозитории, которым я поделился выше.

person Ömer Karışman    schedule 10.09.2017
comment
Ömer, SceneKitVideoRenderer выглядит круто, но физика и анимация работают с удвоенной скоростью, предположительно от рендеринга каждого кадра дважды — один раз для отображения и один раз для записи. Это правильно? И знаете ли вы (или кто-либо) какой-либо обходной путь? - person drewster; 01.01.2018
comment
По этому поводу есть открытый вопрос. Нам еще предстоит найти хорошее решение. Когда вы говорите об анимации, вы имеете в виду SCNAnimations? Потому что анимация моделей выглядит нормально. - person Ömer Karışman; 01.01.2018

У меня была похожая потребность, и я хотел записать ARSceneView в приложении внутри и без ReplayKit, чтобы я мог управлять видео, созданным из записи. В итоге я использовал этот проект: https://github.com/lacyrhoades/SceneKit2Video. Проект создан для рендеринга SceneView в видео, но вы можете настроить его для приема ARSceneViews. Это работает довольно хорошо, и вы можете выбрать получение изображения вместо видео, используя функцию делегата, если хотите.

person Alan    schedule 16.08.2017