はじめに
以前AVFundationを利用したカメラの実装サンプルを書いた際、
バースト(連射)カメラの実装サンプルも書いてほしいと
要望がありましたので、纏めました。
完成版は、Githubを御覧ください。
開発環境
開発環境は、下記のとおりです。
category | Version |
---|---|
XCode | 8.2 |
iOS | 10.0〜 |
Swift | 3.0.2 |
登場人物
バースト(連射)カメラで利用する主なクラスは、下記のとおりです。
クラス名 | 説明 |
---|---|
AVCaptureDeviceInput | 入力デバイスとして、全面カメラ、背面カメラなどを設定する |
AVCaptureSession | カメラ撮影用のセッション?? |
AVCapturePhotoOutput | 出力に、静止画を設定する |
AVCaputreVideoOutput | 出力に、動画フレームデータを設定する |
※iOS10では、AVCaptureStillImageOutputが廃止になり、
AVCapturePhotoOutputを使います。
実装手順
- カメラの利用許可の設定
- 入出力の初期化および、接続
- セッションの開始/終了
- フレームごとに画像を取得
- 使ってみる
1. カメラの利用許可の設定
Info.plist
<key>NSCameraUsageDescription</key>
<string>カメラへアクセスするために必要です</string>
2. 入出力の初期化および、接続
2.1. セッションのインスタンス化
BurstCameraUtil.swift
private let session = AVCaptureSession()
2.2. 入力デバイスの初期化
BurstCameraUtil.swift
func findDevice(position: AVCaptureDevicePosition) -> AVCaptureDevice? {
let device = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera,
mediaType: AVMediaTypeVideo,
position: position)
device?.activeVideoMinFrameDuration = CMTimeMake(1, 30)
return device
}
2.3. 入力デバイスの接続
BurstCameraUtil.swift
func createVideoPreviewLayer(device: AVCaptureDevice?) -> AVCaptureVideoPreviewLayer?{
let videoInput = try! AVCaptureDeviceInput.init(device: device)
if (session.canAddInput(videoInput)) {
session.addInput(videoInput)
}
if (session.canAddOutput(photoOutput)) {
session.addOutput(photoOutput)
}
return AVCaptureVideoPreviewLayer.init(session: session)
}
2.4. 出力デバイスの初期化
BurstCameraUtil.swift
private let photoOutput = AVCapturePhotoOutput()
private let videoDataOutput = AVCaptureVideoDataOutput()
2.5. 出力デバイスの接続
BurstCameraUtil.swift
func createVideoDataOutput() {
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable : Int(kCVPixelFormatType_32BGRA)]
videoDataOutput.alwaysDiscardsLateVideoFrames = true
videoDataOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
session.addOutput(videoDataOutput)
session.sessionPreset = AVCaptureSessionPreset1920x1080
}
3. セッションの開始/終了
3.1. セッションの開始
BurstCameraUtil.swift
func startRunning() {
session.startRunning()
}
3.2. セッションの終了
BurstCameraUtil.swift
func stopRunning() {
session.stopRunning()
}
4. フレームごとに画像を取得
4.1. 写真を保存するための配列等を準備する
BurstCameraUtil.swift
fileprivate var images:[UIImage] = []
fileprivate var isShooting = false
fileprivate var counter = 0
4.2. AVCaptureVideoDataOutputSampleBufferDelegate
BurstCameraUtil.swift
extension BurstCameraUtil: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ captureOutput: AVCaptureOutput!,
didOutputSampleBuffer sampleBuffer: CMSampleBuffer!,
from connection: AVCaptureConnection!) {
if counter % 3 == 0 { // 1/10秒だけ処理する
if isShooting {
let image = imageFromSampleBuffer(sampleBuffer: sampleBuffer)
images.append(image)
}
}
counter += 1
}
private func imageFromSampleBuffer(sampleBuffer :CMSampleBuffer) -> UIImage {
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
CVPixelBufferLockBaseAddress(imageBuffer,
CVPixelBufferLockFlags(rawValue: 0))
let base = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)!
let bytesPerRow = UInt(CVPixelBufferGetBytesPerRow(imageBuffer))
let width = UInt(CVPixelBufferGetWidth(imageBuffer))
let height = UInt(CVPixelBufferGetHeight(imageBuffer))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitsPerCompornent = 8
let bitmapInfo = CGBitmapInfo(rawValue: (CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) as UInt32)
let newContext = CGContext(data: base,
width: Int(width),
height: Int(height),
bitsPerComponent: Int(bitsPerCompornent),
bytesPerRow: Int(bytesPerRow),
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue)! as CGContext
let imageRef = newContext.makeImage()!
let image = UIImage(cgImage: imageRef,
scale: 1.0,
orientation: UIImageOrientation.right)
CVPixelBufferUnlockBaseAddress(imageBuffer,
CVPixelBufferLockFlags(rawValue: 0))
return image
}
}
4.3. 連射開始、終了、連射写真の画像リスト
BurstCameraUtil.swift
func start() {
images = []
isShooting = true
}
func stop() {
isShooting = false
}
func photos() -> [UIImage] {
return images
}
#5. 使ってみる
UI周りは、割愛します。
ViewController.swift
import UIKit
import AVFoundation
final class ViewController: UIViewController {
@IBOutlet weak var baseView: UIView!
private var camera: CameraTakeable?
override func viewDidLoad() {
super.viewDidLoad()
setupCameraView(camera: BurstCameraUtil())
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
camera?.startRunning()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
camera?.stopRunning()
camera?.removeVideoInput()
}
private func setupCameraView(camera: CameraTakeable?) {
self.camera = camera
let device = camera?.findDevice(position: .back)
camera?.createVideoDataOutput()
if let videoLayer = camera?.createVideoPreviewLayer(device: device) {
videoLayer.frame = baseView.bounds
videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
baseView.layer.addSublayer(videoLayer)
} else {
fatalError("VideoLayer is Nil")
}
}
//MARK:- Actions
@IBAction func photoDidTapDown(_ sender: UIButton) {
camera?.start()
}
@IBAction func photoDidTapUp(_ sender: UIButton) {
camera?.stop()
if let images = camera?.photos() {
print("I took \(images.count) photos")
}
}
}
まとめ
完成版は、Githubを御覧ください。
カメラを利用して、様々な出力が出来ますね。
クラス名 | 説明 |
---|---|
AVCaptureMovieFileOutput | 動画ファイル |
AVCaptureAudioFileOutput | 音声ファイル |
AVCaptureVideoDataOutput | 動画フレームデータ |
AVCaptureAudioDataOutput | 音声データ |
AVCapturePhotoOutput | 静止画 |
AVCaptureMetadataOutput | メタデータ |