Как записать видео с ARKit?
Сейчас я тестирую реализацию ARKit/SceneKit. Основной рендеринг на экране вроде работает, поэтому я хочу попробовать записать то, что я вижу на экране, в видео.
Просто для записи Scene Kit я нашел этот Gist:
//
// ViewController.swift
// SceneKitToVideo
//
// Created by Lacy Rhoades on 11/29/16.
// Copyright © 2016 Lacy Rhoades. All rights reserved.
//
import SceneKit
import GPUImage
import Photos
class ViewController: UIViewController {
// Renders a scene (and shows it on the screen)
var scnView: SCNView!
// Another renderer
var secondaryRenderer: SCNRenderer?
// Abducts image data via an OpenGL texture
var textureInput: GPUImageTextureInput?
// Recieves image data from textureInput, shows it on screen
var gpuImageView: GPUImageView!
// Recieves image data from the textureInput, writes to a file
var movieWriter: GPUImageMovieWriter?
// Where to write the output file
let path = NSTemporaryDirectory().appending("tmp.mp4")
// Output file dimensions
let videoSize = CGSize(width: 800.0, height: 600.0)
// EAGLContext in the sharegroup with GPUImage
var eaglContext: EAGLContext!
override func viewDidLoad() {
super.viewDidLoad()
let group = GPUImageContext.sharedImageProcessing().context.sharegroup
self.eaglContext = EAGLContext(api: .openGLES2, sharegroup: group )
let options = ["preferredRenderingAPI": SCNRenderingAPI.openGLES2]
// Main view with 3d in it
self.scnView = SCNView(frame: CGRect.zero, options: options)
self.scnView.preferredFramesPerSecond = 60
self.scnView.eaglContext = eaglContext
self.scnView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.scnView)
// Secondary renderer for rendering to an OpenGL framebuffer
self.secondaryRenderer = SCNRenderer(context: eaglContext, options: options)
// Output of the GPUImage pipeline
self.gpuImageView = GPUImageView()
self.gpuImageView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.gpuImageView)
self.setupConstraints()
self.setupScene()
self.setupMovieWriter()
DispatchQueue.main.async {
self.setupOpenGL()
}
}
func setupConstraints() {
let relativeWidth: CGFloat = 0.8
self.view.addConstraint(NSLayoutConstraint(item: self.scnView, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: relativeWidth, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.scnView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.gpuImageView, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: relativeWidth, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.gpuImageView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-(==30.0)-[scnView]-(==30.0)-[gpuImageView]", options: [], metrics: [:], views: ["gpuImageView": gpuImageView, "scnView": scnView]))
let videoRatio = self.videoSize.width / self.videoSize.height
self.view.addConstraint(NSLayoutConstraint(item: self.scnView, attribute: .width, relatedBy: .equal, toItem: self.scnView, attribute: .height, multiplier: videoRatio, constant: 1))
self.view.addConstraint(NSLayoutConstraint(item: self.gpuImageView, attribute: .width, relatedBy: .equal, toItem: self.gpuImageView, attribute: .height, multiplier: videoRatio, constant: 1))
}
override func viewDidAppear(_ animated: Bool) {
self.cameraBoxNode.runAction(
SCNAction.repeatForever(
SCNAction.rotateBy(x: 0.0, y: -2 * CGFloat.pi, z: 0.0, duration: 8.0)
)
)
self.scnView.isPlaying = true
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: {
timer in
self.startRecording()
})
}
var scene: SCNScene!
var geometryNode: SCNNode!
var cameraNode: SCNNode!
var cameraBoxNode: SCNNode!
var imageMaterial: SCNMaterial!
func setupScene() {
self.imageMaterial = SCNMaterial()
self.imageMaterial.isDoubleSided = true
self.imageMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(-1, 1, 1)
self.imageMaterial.diffuse.wrapS = .repeat
self.imageMaterial.diffuse.contents = UIImage(named: "pano_01")
self.scene = SCNScene()
let sphere = SCNSphere(radius: 100.0)
sphere.materials = [imageMaterial!]
self.geometryNode = SCNNode(geometry: sphere)
self.geometryNode.position = SCNVector3Make(0.0, 0.0, 0.0)
scene.rootNode.addChildNode(self.geometryNode)
self.cameraNode = SCNNode()
self.cameraNode.camera = SCNCamera()
self.cameraNode.camera?.yFov = 72.0
self.cameraNode.position = SCNVector3Make(0, 0, 0)
self.cameraNode.eulerAngles = SCNVector3Make(0.0, 0.0, 0.0)
self.cameraBoxNode = SCNNode()
self.cameraBoxNode.addChildNode(self.cameraNode)
scene.rootNode.addChildNode(self.cameraBoxNode)
self.scnView.scene = scene
self.scnView.backgroundColor = UIColor.darkGray
self.scnView.autoenablesDefaultLighting = true
}
func setupMovieWriter() {
let _ = FileUtil.mkdirUsingFile(path)
let _ = FileUtil.unlink(path)
let url = URL(fileURLWithPath: path)
self.movieWriter = GPUImageMovieWriter(movieURL: url, size: self.videoSize)
}
let glRenderQueue = GPUImageContext.sharedContextQueue()!
var outputTexture: GLuint = 0
var outputFramebuffer: GLuint = 0
func setupOpenGL() {
self.glRenderQueue.sync {
let context = EAGLContext.current()
if context != self.eaglContext {
EAGLContext.setCurrent(self.eaglContext)
}
glGenFramebuffers(1, &self.outputFramebuffer)
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), self.outputFramebuffer)
glGenTextures(1, &self.outputTexture)
glBindTexture(GLenum(GL_TEXTURE_2D), self.outputTexture)
}
// Pipe the texture into GPUImage-land
self.textureInput = GPUImageTextureInput(texture: self.outputTexture, size: self.videoSize)
let rotate = GPUImageFilter()
rotate.setInputRotation(kGPUImageFlipVertical, at: 0)
self.textureInput?.addTarget(rotate)
rotate.addTarget(self.gpuImageView)
if let writer = self.movieWriter {
rotate.addTarget(writer)
}
// Call me back on every render
self.scnView.delegate = self
}
func renderToFramebuffer(atTime time: TimeInterval) {
self.glRenderQueue.sync {
let context = EAGLContext.current()
if context != self.eaglContext {
EAGLContext.setCurrent(self.eaglContext)
}
objc_sync_enter(self.eaglContext)
let width = GLsizei(self.videoSize.width)
let height = GLsizei(self.videoSize.height)
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), self.outputFramebuffer)
glBindTexture(GLenum(GL_TEXTURE_2D), self.outputTexture)
glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, width, height, 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), nil)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE)
glFramebufferTexture2D(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_TEXTURE_2D), self.outputTexture, 0)
glViewport(0, 0, width, height)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
self.secondaryRenderer?.render(atTime: time)
self.videoBuildingQueue.sync {
self.textureInput?.processTexture(withFrameTime: CMTime(seconds: time, preferredTimescale: 100000))
}
objc_sync_exit(self.eaglContext)
}
}
func startRecording() {
self.startRecord()
Timer.scheduledTimer(withTimeInterval: 24.0, repeats: false, block: {
timer in
self.stopRecord()
})
}
let videoBuildingQueue = DispatchQueue.global(qos: .default)
func startRecord() {
self.videoBuildingQueue.sync {
//inOrientation: CGAffineTransform(scaleX: 1.0, y: -1.0)
self.movieWriter?.startRecording()
}
}
var renderStartTime: TimeInterval = 0
func stopRecord() {
self.videoBuildingQueue.sync {
self.movieWriter?.finishRecording(completionHandler: {
self.saveFileToCameraRoll()
})
}
}
func saveFileToCameraRoll() {
assert(FileUtil.fileExists(self.path), "Check for file output")
DispatchQueue.global(qos: .utility).async {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: self.path))
}) { (done, err) in
if err != nil {
print("Error creating video file in library")
print(err.debugDescription)
} else {
print("Done writing asset to the user's photo library")
}
}
}
}
}
extension ViewController: SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
self.secondaryRenderer?.scene = scene
self.secondaryRenderer?.pointOfView = (renderer as! SCNView).pointOfView
self.renderToFramebuffer(atTime: time)
}
}
но это не делает изображение с камеры устройства.
Так что я тоже начал искать способ сделать это. До сих пор я нашел способ захватить захваченное изображение как CVImageBufferRef путем доступа к ARFrame. И пример Apple GLCameraRipple, похоже, помогает мне извлечь из него текстуру OpenGL.
Но мой вопрос, как нарисовать его в цикле рендеринга. Это может быть очевидно для опытных OpenGL, но у меня очень мало знаний об OpenGL, поэтому я не могу понять, как добавить это в приведенный выше код.
7 ответов
Вы можете записывать все, что видно на экране (или транслировать его в прямом эфире на такие сервисы, как Twitch) с использованием содержимого ReplayKit, ARKit и SceneKit.
(Как указала Apple на WWDC, ReplayKit фактически является основой для функции записи экрана Центра управления в iOS 11).
Swift 5
Вы можете использовать эту структуру ARCapture для записи видео из представления ARKit.
private var capture: ARCapture?
...
override func viewDidLoad() {
super.viewDidLoad()
// Create a new scene
let scene = SCNScene()
...
// TODO Setup ARSCNView with the scene
// sceneView.scene = scene
// Setup ARCapture
capture = ARCapture(view: sceneView)
}
/// "Record" button action handler
@IBAction func recordAction(_ sender: UIButton) {
capture?.start()
}
/// "Stop" button action handler
@IBAction func stopAction(_ sender: UIButton) {
capture?.stop({ (status) in
print("Video exported: \(status)")
})
}
После того, как вы позвоните
ARCapture.stop
видео будет представлено в приложении «Фото».
ReplayKit не является хорошим решением, потому что вашим пользователям предоставляется уродливое диалоговое окно с разрешениями, и вам также нужно обойти его, записывая элементы пользовательского интерфейса. У вас также меньше возможностей контролировать разрешение видео.
Вместо этого вы должны использовать захваченный кадр CVPixelBuffer
возвращенный ARKit, и обрабатывайте его, как если бы вы записывали кадры, снятые с камеры. Предполагая, что вам нужно обработать кадры видео, вам также может потребоваться использовать фреймворк, например Metal, для обработки чертежа. Это не просто. См. Ответ здесь: Как записать видео в RealityKit?
Не знаю, удалось ли вам ответить на этот вопрос сейчас или нет, но lacyrhoades, человек, который написал класс, на который вы ссылались, выпустил еще один проект на github, который, кажется, выполняет то, что вы просите. Я использовал его, и ему удается записать SceneView с объектами AR, а также с камеры. Вы можете найти его по этой ссылке:
https://github.com/lacyrhoades/SCNKit2Video
Если вы хотите использовать его с AR, вам нужно настроить ARSceneView для проекта, который он делает, так как его один просто запускает SceneView, а не один с AR.
Надеюсь, поможет.
Я только что нашел этот фреймворк, называемый ARVideoKit, и, кажется, его легко реализовать, плюс у него есть больше функций, таких как захват GIF-файлов и Live Photos.
Официальный репозиторий фреймворка: https://github.com/AFathi/ARVideoKit/
Чтобы установить его, вам нужно будет клонировать репозиторий и перетащить файл.framework во встроенный бинарный файл вашего проекта.
Тогда реализация довольно проста:
import ARVideoKit
в вашемUIViewController
учебный классСоздать
RecordAR?
переменнаяvar videoRec:RecordAR?
Инициализируйте вашу переменную в
viewDidLoad
videoRec = RecordAR(ARSpriteKit:sceneView)
Подготовить
RecordAR
вviewWillAppear
videoRec.prepare(configuration)
Начните запись видео
videoRec.record()
Остановись и экспортируй в фотопленку!
videoRec.stopAndExport()
Взгляните на документацию фреймворка, он поддерживает больше возможностей для использования!
Вы можете найти их документацию здесь: https://github.com/AFathi/ARVideoKit/wiki
Надеюсь, что это помогло!
Если вы можете сохранить ваше устройство подключенным к вашему Mac, очень просто использовать QuickTime Player для записи экрана (и звука) с вашего устройства iOS.
В QuickTime выберите новую запись фильма в меню "Файл", затем в диалоговом окне записи рядом с большой красной кнопкой записи есть маленькая выпадающая стрелка, где вы можете выбрать аудиовход и видеовход. Выберите ваше i-устройство там, и вы готовы к работе.
Если вы используете ARKit, то вы используете iOS 11.
iOS 11 имеет встроенную запись экрана (с поддержкой микрофона).
Тем не менее, в iOS 11 Beta 2 он немного глючит - но работает.
Чтобы подвести итог:
- В меню "Настройки", "Центр управления", "Настройка" добавьте виджет " Запись экрана".
- В Центре управления (дважды нажмите кнопку "Домой" или сдвиньте ее вверх от нижней части экрана), нажмите на кнопку " Запись экрана" принудительное нажатие (или длительное нажатие), чтобы настроить, хотите ли вы запись с микрофона
- Нажмите кнопку записи экрана, чтобы начать запись
- Запустите ваше приложение
- Вернитесь в Центр управления и нажмите Запись экрана, чтобы остановить запись.
- Видео есть в вашей фотопленке.
Глючная часть в том, что он не работает с некоторыми ландшафтными приложениями и не всегда записывает.
Вот учебник, который я написал об использовании его более подробно: