🐕
SwiftUIでAVFoundationを使ってフレームバッファを取得する
SwiftUIからAVFoundationのAVCaptureVideoDataOutput()
を使用するコードを書いてみました。
基本的にはUIKitから使用する場合と同じようなイメージで利用できました。
- AVFoundationを呼び出すコードを専用のクラスに記述
- クロージャを使ってSwiftUI側から
AVCaptureVideoDataOutputSampleBufferDelegate
のCMSampleBufferを取得する - UIImageに変換して
Image()
で表示
ソースコードはこちらに置きました
VideoCapture クラス
import Foundation
import AVFoundation
class VideoCapture: NSObject {
let captureSession = AVCaptureSession()
var handler: ((CMSampleBuffer) -> Void)?
override init() {
super.init()
setup()
}
func setup() {
captureSession.beginConfiguration()
let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
guard
let deviceInput = try? AVCaptureDeviceInput(device: device!),
captureSession.canAddInput(deviceInput)
else { return }
captureSession.addInput(deviceInput)
let videoDataOutput = AVCaptureVideoDataOutput()
videoDataOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "mydispatchqueue"))
videoDataOutput.alwaysDiscardsLateVideoFrames = true
guard captureSession.canAddOutput(videoDataOutput) else { return }
captureSession.addOutput(videoDataOutput)
// アウトプットの画像を縦向きに変更(標準は横)
for connection in videoDataOutput.connections {
if connection.isVideoOrientationSupported {
connection.videoOrientation = .portrait
}
}
captureSession.commitConfiguration()
}
func run(_ handler: @escaping (CMSampleBuffer) -> Void) {
if !captureSession.isRunning {
self.handler = handler
captureSession.startRunning()
}
}
func stop() {
if captureSession.isRunning {
captureSession.stopRunning()
}
}
}
extension VideoCapture: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
if let handler = handler {
handler(sampleBuffer)
}
}
}
SwiftUI側のコード
import SwiftUI
import AVFoundation
struct ContentView: View {
let videoCapture = VideoCapture()
@State var image: UIImage? = nil
var body: some View {
VStack {
if let image = image {
Image(uiImage: image)
.resizable()
.scaledToFit()
}
HStack {
Button("run") {
videoCapture.run { sampleBuffer in
if let convertImage = UIImageFromSampleBuffer(sampleBuffer) {
DispatchQueue.main.async {
self.image = convertImage
}
}
}
}
Button("stop") {
videoCapture.stop()
}
}
.font(.largeTitle)
}
}
func UIImageFromSampleBuffer(_ sampleBuffer: CMSampleBuffer) -> UIImage? {
if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
let imageRect = CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))
let context = CIContext()
if let image = context.createCGImage(ciImage, from: imageRect) {
return UIImage(cgImage: image)
}
}
return nil
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
実行画面
Discussion