キャプチャした写真の保存

オリジナル情報源

https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/capturing_still_and_live_photos/saving_captured_photos

概要

AVCapturePhotoOutputで写真のキャプチャを完了すると、静止画像データだけでなく、カメラのメタデータとキャプチャでリクエストした補助画像(サムネイルや深度マップなど)を含むAVCapturePhotoオブジェクトを受け取ります。これらのタイプのデータをAVCapturePhotoから個別に取得するか、そのfileDataRepresentation()メソッドを呼び出して、AVCapturePhotoSettingsでその写真に要求したコーデックとファイル形式を使用して、Dataオブジェクトをディスクに書き込む準備をすることができます。

通常、写真をキャプチャした後、そのデータをユーザーのフォトライブラリに追加します。Photosフレームワークを使用してそうすることができます。

フォトライブラリの使用許可をリクエストする

iOSがカメラとマイクへのアクセスをユーザーに許可することでユーザーのプライバシーを保護するのと同じように、システムはユーザーにアプリにフォトライブラリへのアクセスを許可することも要求します。アプリの許可を取得するには:

  1. アプリケーションのInfo.plistファイル内のNSPhotoLibraryUsageDescription キーを含むように構成します。このキーの値は、フォトライブラリへのアクセスを要求するシステムアラートでユーザーに表示されるメッセージです。このメッセージを使用して、アプリにそのようなアクセスが必要な理由をユーザーに説明します。
  2. 承認を確認またはリクエストします。このPHPhotoLibrary requestAuthorization(_:)メソッドを使用して、ユーザーが最初にアプリのカメラ機能を開いたときなど、アプリに適切なタイミングでiOSにフォトライブラリへのアクセスを求めるアラートが表示されるようにします。(ユーザーが最初の写真を撮るまで待たないでください。許可の警告により、複数の写真を撮ることができなくなります。)

以下のコードは、アクセスを確認するための簡単なワークフローを示しています。

PHPhotoLibrary.requestAuthorization { status in
    guard status == .authorized else { return }
    
    // Use PHPhotoLibrary.shared().performChanges(...) to add assets.
}

作成リクエストを使用して写真アセットを追加する

このAVCapturePhoto fileDataRepresentation()メソッドは、写真キャプチャからのすべての画像データ、補助画像データ、およびメタデータを、ディスクに書き込む準備ができている単一のデータオブジェクトにパッケージ化します。そのデータを写真ライブラリに追加するには、PHPhotoLibraryおよびPHAssetCreationRequestクラスを使用します。

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    guard error == nil else { print("Error capturing photo: \(error!)"); return }

    PHPhotoLibrary.requestAuthorization { status in
        guard status == .authorized else { return }
        
        PHPhotoLibrary.shared().performChanges({
            // Add the captured photo's file data as the main resource for the Photos asset.
            let creationRequest = PHAssetCreationRequest.forAsset()
            creationRequest.addResource(with: .photo, data: photo.fileDataRepresentation()!, options: nil)
        }, completionHandler: self.handlePhotoLibraryError)
    }
}

注意

performChanges(_:completionHandler:)ブロックは、1つのアトミックアップデートではフォトライブラリに複数の変更を行うことができます。たとえば、新しく作成したアセットをアルバムに追加できます。または、ブラケットキャプチャを実行すると、複数のdidFinishProcessingPhoto結果を蓄積して、それらを一緒にフォトライブラリに保存できます。詳細については、Photosドキュメントを参照してください。

写真キャプチャの進行状況の追跡

オリジナル情報源

https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/capturing_still_and_live_photos/tracking_photo_capture_progress

概要

iOSデバイスのカメラで写真をキャプチャすることは、物理的なカメラメカニズム、画像信号処理、オペレーティングシステム、アプリを含む複雑なプロセスです。アプリがこのプロセスの多くの段階を無視して単純に最終結果を待つことは可能ですが、各ステップを監視することで、より応答性の高いカメラインターフェイスを作成できます。

capturePhoto(with:delegate:)を呼び出した後、デリゲートオブジェクトはプロセスの5つの主要なステップ(写真の設定によってはそれ以上)に沿って進むことができます。作成するキャプチャワークフローとキャプチャUIに応じて、デリゲートは次の手順の一部またはすべてを処理できます。

capturePhoto()を呼び出した後の番号付き写真キャプチャプロセスのステップの図。
  1. 設定完了
  2. 露出開始
  3. 露出完了
  4. 結果データ配信
  5. キャプチャ完了

キャプチャシステムは、このプロセスの各ステップでAVCaptureResolvedPhotoSettingsオブジェクトを提供します。同時に複数のキャプチャが進行している可能性があるため、解決された各写真設定オブジェクトには、写真の撮影に使用したAVCapturePhotoSettingsuniqueIDと一致するuniqueID値を持つ

キャプチャ設定を取得する

写真の設定を指定する場合、選択する設定の一部は自動であり、キャプチャシステムがキャプチャの瞬間に正確に決定するために残されます。たとえば、AVCaptureDevice.FlashMode.autoフラッシュモードを選択すると、カメラ自体がシーンの照明に基づいて、写真を露光するときにフラッシュを発光するかどうかを決定できます。

露光を開始する直前に、写真出力はデリゲートのメソッドを呼び出します。そのメソッドのパラメーターは、キャプチャの実際の設定を示します。たとえば、フラッシュモードを選択した場合、解決された設定オブジェクトは、現在のキャプチャでフラッシュが使用されているかどうかを通知します。この情報を使用して、UIでフラッシュが使用されたことを示すことができます。resolvedSettingsAVCaptureDevice.FlashMode.auto

露出開始の処理

露出時間が始まると、写真出力はデリゲートのphotoOutput(_:willBeginCaptureFor:)メソッドを呼び出します。従来の写真では、この瞬間はカメラのシャッターが開くことに相当します。このとき、シャッター音も自動的に鳴ります。

UIでこのメソッドに応答して、要求された写真が撮影されていることを示すシャッターアニメーションまたはその他のインジケーターを表示できます。

露出終了の処理

写真の出力は、露出時間が完了するとすぐにデリゲートのphotoOutput(_:didCapturePhotoFor:)メソッドを呼び出します。アプリに画像を提供する前に、システムはまだカメラデータを処理する時間を必要としますが、この瞬間を使用して、UIに露出が完了したことを表示できます。たとえば、メソッドでカメラのプレビューを非表示にし、willCapturePhotodidCapturePhotoメソッドでもう一度表示することで、シャッター効果をシミュレートできます。

写真結果の処理

写真出力にアプリで利用できる画像データがある場合、デリゲートのphotoOutput(_:didFinishProcessingPhoto:error:)メソッドを呼び出します。写真の設定によっては、写真の出力でこのメソッドが複数回呼び出される場合があります。

  • ブラケットキャプチャを要求した場合、このメソッドはブラケット内の露出ごとに(少なくとも)1回起動し、その露出の画像を提供します。
  • RAW形式と処理済み形式(HEIF / HEVCやJPEGなど)の両方でのキャプチャを要求した場合、このメソッドは(少なくとも)各形式に対して1回発生します。

たとえば、3つの露出ブラケットでRAW + HEIFキャプチャを要求すると、写真出力はデリゲートのdidFinishProcessingPhotoメソッドを6回呼び出し(2つのフォーマット×3つの露出)、6つのAVCapturePhotoオブジェクトを提供します。複数の結果を追跡するには、各写真のを解決済みの設定のphotoCountexpectedPhotoCount比較します。

注意

ライブ写真のキャプチャを要求すると、追加のデリゲートメソッドがそのプロセスに関するフィードバックを提供します。ライブ写真のキャプチャと保存を参照してください。

キャプチャが完了したらクリーンアップする

キャプチャに関するシステムのすべての作業が完了すると、写真出力はデリゲートのphotoOutput(_:didFinishCaptureFor:error:)メソッドを呼び出します。この瞬間を使用して、キャプチャプロセスのアプリの一部を終了できます。

  • キャプチャが複数の結果を予期している場合は、それらをphotoOutput(_:didFinishProcessingPhoto:error:)(およびphotoOutput(_:didFinishProcessingLivePhotoToMovieFileAt:duration:photoDisplayTime:resolvedSettings:error:))メソッドにキャッシュしてから、結果をローカルストレージに保存するか、didFinishCaptureメソッドのフォトライブラリに追加します。
  • キャプチャプロセスが他のリソースを管理している場合は、didFinishCaptureメソッドでそれらのリソースをクリーンアップします。キャプチャごとに個別の写真キャプチャデリゲートオブジェクトを使用する場合は、そのようなオブジェクトへの強い参照を削除するのによいタイミングです。

以下のコードは、複数の写真キャプチャデリゲートオブジェクトを管理する1つの方法を示しています。

class PhotoCaptureProcessor: NSObject, AVCapturePhotoCaptureDelegate {
    var completionHandler: () -> () = {}
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
        completionHandler()
    }
    // ... other delegate methods to handle capture results...
}

// Keep a set of in-progress capture delegates.
var capturesInProgress = Set<PhotoCaptureProcessor>()

func shootPhoto() {    
    // Make a new capture delegate for each capture and add it to the set.
    let captureProcessor = PhotoCaptureProcessor()
    capturesInProgress.insert(captureProcessor)
    
    // Schedule for the capture delegate to be removed from the set after capture.
    captureProcessor.completionHandler = { [weak self] in
        self?.capturesInProgress.remove(captureProcessor); return
    }
    
    self.photoOutput.capturePhoto(with: self.settingsForNextPhoto(), delegate: captureProcessor)
}

メディアキャプチャの承認の要求

オリジナル情報源

このページは、次の情報を日本語に翻訳したものです。お役に立てば幸いです。

Requesting Authorization for Media Capture on iOS

iOS 13.0以降

Xcode 11.1以降

AVFoundation

概要

iOSでは、ユーザーは各アプリがカメラとマイクにアクセスすることを明示的に許可する必要があります。アプリが初めてキャプチャシステムを使用できるようになる前に、iOSは、以下に示すように、アプリにカメラへのアクセスを許可するようユーザーに要求するアラートを表示します。iOSはこのアラートに対するユーザーの応答を記憶しているため、その後キャプチャシステムを使用しても、アラートが再び表示されることはありません。ユーザーは、[設定]> [プライバシー]でアプリの権限設定を変更できます。

メディアをキャプチャする前にアプリに権限があることを確認するには、以下の手順に従います。

アプリのinfo.plistファイルを構成する

iOSでは、システムがカメラまたはマイクの許可を要求したときにユーザーに表示する静的メッセージをアプリが提供する必要があります。

  • アプリがデバイスカメラを使用している場合は、アプリのInfo.plistファイルにNSCameraUsageDescriptionキーを含めます。
  • アプリがデバイスマイクを使用する場合は、アプリのInfo.plistファイルにNSMicrophoneUsageDescriptionキーを含めます。

キーごとに、アプリがメディアをキャプチャする必要がある理由をユーザーに説明するメッセージを提供します。これにより、ユーザーはアプリに許可を与えることに自信を持つことができます。

重要

アプリが承認を要求したとき、またはキャプチャデバイスを使用しようとしたときに、適切なキーがアプリのInfo.plistファイルに存在しない場合、システムはアプリを終了します。

キャプチャの承認を確認してリクエストする

キャプチャセッションを設定する前に、必ずAVCaptureDevice authorizationStatus(for:)メソッドをテストしてください。ユーザーがキャプチャ権限をまだ付与または拒否していない場合、承認ステータスはAVAuthorizationStatus.notDeterminedです。この場合、requestAccess(for:completionHandler:)メソッドを使用して、ユーザーにプロンプ​​トを表示するようにiOSに指示します。

switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized: // The user has previously granted access to the camera.
        self.setupCaptureSession()
    
    case .notDetermined: // The user has not yet been asked for camera access.
        AVCaptureDevice.requestAccess(for: .video) { granted in
            if granted {
                self.setupCaptureSession()
            }
        }
    
    case .denied: // The user has previously denied access.
        return

    case .restricted: // The user can't grant access due to restrictions.
        return
}

requestAccess(for:completionHandler:)メソッドは非同期です:あなたのアプリは、iOSが許可警告を示しながら実行し続けます。ユーザーが応答すると、システムは完了ハンドラーを呼び出します。完了ハンドラの成功パラメータがtrueの場合、キャプチャセッションのセットアップと開始に進むことができます。

注意

キャプチャを開始する前に requestAccess(for:completionHandler:)を呼び出しますが、アプリに適した時間にのみ行ってください。たとえば、写真やビデオの録画がアプリの主な焦点ではない場合は、ユーザーがアプリのカメラ関連機能を呼び出したときにのみカメラの権限を確認します。

キャプチャしたメディアを保存する前に承認を要求する

写真またはビデオをキャプチャした後、それらをユーザーのフォトライブラリに保存することができます。フォトライブラリにアクセスするには、ユーザーの権限も必要です(カメラとマイクの権限とは別に)。いつどのように許可を要求するかは、メディアの保存に使用する機能によって異なります。

  • ほとんどの写真およびビデオキャプチャワークフロー(Live PhotosやRAW形式のキャプチャを含む)では、PHPhotoLibraryおよびPHAssetCreationRequestクラスを使用します。これらのクラスにはPhotosライブラリへの読み取り/書き込みアクセス権が必要なため、Info.plistのNSPhotoLibraryUsageDescriptionキーを使用して、アクセスを要求するときにユーザーにメッセージを提供する必要があります。詳細については、キャプチャした写真の保存を参照してください。
  • アプリがムービーファイルをPhotosライブラリに保存するだけでよい場合、このUISaveVideoAtPathToSavedPhotosAlbum(_:_:_:_:)関数はPHPhotoLibraryより簡単な方法を提供します。この関数はライブラリへの書き込みアクセスのみを必要とするため、Info.plistのNSPhotoLibraryAddUsageDescriptionキーを使用して、フォトライブラリへの保存の許可を求めるときにユーザーにメッセージを提供します。

注意

UIImageクラスは写真出力に含まれる機能とメタデータをサポートしていないため、このUIImageWriteToSavedPhotosAlbum(_:_:_:_:)関数をAVCapturePhotoOutputでキャプチャした写真で使用することはお勧めしません。

キャプチャセッションのセットアップ

オリジナル情報源

https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/setting_up_a_capture_session

概要

AVCaptureSessionがiOSとMacOSの中のすべてのメディアキャプチャの基礎です。OSキャプチャインフラストラクチャとキャプチャデバイスへのアプリの排他的アクセス、および入力デバイスからメディア出力へのデータのフローを管理します。入力と出力の間の接続を構成するこよにより、キャプチャセッションの機能が定義されます。たとえば、下の図は、iPhoneのバックカメラとマイクを使用して、写真と動画の両方をキャプチャし、カメラプレビューを提供するキャプチャセッションを示しています。

詳細なキャプチャセッションアーキテクチャの例のブロック図:カメラとマイクの個別のAVCaptureDeviceInputオブジェクトは、AVCaptureSessionによって管理されるAVCaptureConnectionオブジェクトを介して、AVCapturePhotoOutput、AVCaptureMovieFileOutput、およびAVCaptureVideoPreviewLayerに接続します。
図1  サンプルのキャプチャセッションのアーキテクチャ

入力と出力をセッションに接続

すべてのキャプチャセッションには、少なくとも1つのキャプチャ入力とキャプチャ出力が必要です。キャプチャ入力(AVCaptureInputサブクラス)はメディアソースです。通常、iOSデバイスやMacに組み込まれているカメラやマイクなどの録音デバイスです。キャプチャ出力(AVCaptureOutputサブクラス)は、キャプチャ入力によって提供されるデータを使用して、画像や動画ファイルなどのメディアを生成します。

ビデオ入力にカメラを使用するには(写真またはムービーをキャプチャするため)、適切なAVCaptureDeviceを選択し、対応するAVCaptureDeviceInputを作成して、セッションに追加します。

captureSession.beginConfiguration()
let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
                                          for: .video, position: .unspecified)
guard
    let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!),
    captureSession.canAddInput(videoDeviceInput)
    else { return }
captureSession.addInput(videoDeviceInput)

注意

iOSには、カメラデバイスを選択する方法が他にもいくつかあります。詳細については、「キャプチャデバイスの選択」を参照してください。

次に、選択したカメラからキャプチャする予定のメディアの種類の出力を追加します。たとえば、写真のキャプチャを有効にするには、セッションにAVCapturePhotoOutputを追加します。

let photoOutput = AVCapturePhotoOutput()
guard captureSession.canAddOutput(photoOutput) else { return }
captureSession.sessionPreset = .photo
captureSession.addOutput(photoOutput)
captureSession.commitConfiguration()

セッションは複数の入力と出力を持つことができます。例えば:

  • 映画のビデオとオーディオの両方を記録するには、カメラとマイクの両方のデバイスに入力を追加します。
  • 同じカメラから写真とムービーの両方をキャプチャするには、AVCapturePhotoOutputAVCaptureMovieFileOutputの両方をセッションに追加します。

重要

セッションの入力または出力の変更前にbeginConfiguration()を呼び出し、変更後にcommitConfiguration()を呼ぶこと。

カメラのプレビューを表示

従来のカメラのビューファインダーのように、写真を撮ったり、ビデオ録画を開始したりする前に、ユーザーがカメラからの入力を確認できるようにすることが重要です。そのようなプレビューを提供するには、AVCaptureVideoPreviewLayerをキャプチャセッションに接続します。これにより、セッションが実行されているときはいつでも、カメラからのライブビデオフィードが表示されます。

AVCaptureVideoPreviewLayerコアアニメーションレイヤーであるため、他のCALayerサブクラスと同じように、インターフェイスで表示およびスタイル設定できます。以下に示すように、プレビューレイヤーをUIKitアプリに追加する最も簡単な方法は、UIViewサブクラスを定義することです。そのlayerClassAVCaptureVideoPreviewLayerです。

class PreviewView: UIView {
    override class var layerClass: AnyClass {
        return AVCaptureVideoPreviewLayer.self
    }
    
    /// Convenience wrapper to get layer as its statically known type.
    var videoPreviewLayer: AVCaptureVideoPreviewLayer {
        return layer as! AVCaptureVideoPreviewLayer
    }
}

次に、キャプチャセッションでプレビューレイヤーを使用するには、レイヤーのsessionプロパティを設定します。

self.previewView.videoPreviewLayer.session = self.captureSession

注意

アプリが複数のインターフェイスの向きをサポートしている場合は、プレビューレイヤーのconnectionキャプチャセッションを使用して、UIと一致するようにvideoOrientation を設定します。

キャプチャセッションを実行する

入力、出力、プレビューを構成したら、startRunning()を呼び出して、データを入力から出力に流します。

一部のキャプチャ出力では、メディアキャプチャを開始するために必要なのはセッションの実行だけです。たとえば、セッションにAVCaptureVideoDataOutputが含まれている場合、セッションが実行されるとすぐにビデオフレームの配信の受信を開始します。

他のキャプチャ出力では、最初にセッションの実行を開始し、次にキャプチャ出力クラス自体を使用してキャプチャを開始します。たとえば、写真アプリでは、セッションを実行するとビューファインダースタイルのプレビューが有効になりますが、このAVCapturePhotoOutput capturePhoto(with:delegate:)メソッドを使用して写真をスナップします。

AVCam: カメラアプリの作成

オリジナル情報源

このページは、次の情報を日本語に翻訳したものです。お役に立てば幸いです。

https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/avcam_building_a_camera_app

iOS 13.0以降

Xcode 11.1以降

AVFoundation

概要

iOSカメラアプリを使用すると、前面カメラと背面カメラの両方から写真と動画をキャプチャできます。デバイスによっては、カメラアプリは深度データの静止画キャプチャ、ポートレートエフェクトマット、ライブフォトもサポートしています。

このサンプルコードプロジェクトAVCamは、これらのキャプチャ機能を独自のカメラアプリに実装する方法を示しています。内蔵のフロントおよびリアiPhoneおよびiPadカメラの基本機能を活用します。

注意

AVCamを使用するには、iOS 13以降を実行しているiOSデバイスが必要です。Xcodeはデバイスのカメラにアクセスできないため、このサンプルはシミュレーターでは機能しません。AVCamは、iPhone 7 Plusのポートレートエフェクトマット配信など、現在のデバイスがサポートしていないモードのボタンを非表示にします。

キャプチャセッションの構成

AVCaptureSession は、カメラやマイクなどのキャプチャデバイスからの入力データを受け入れます。入力を受け取った後、そのデータを適切な出力にマーシャリングして処理し、最終的に動画ファイルまたは静止画を作成します。キャプチャセッションの入力と出力を構成した後、キャプチャを開始・停止を師事できるよになります。

private let session = AVCaptureSession()

AVCamはデフォルトで背面カメラを選択し、コンテンツをビデオプレビュービューにストリーミングするようにカメラキャプチャセッションを構成します。PreviewView は、AVCaptureVideoPreviewLayer によってサポートされる UIView のカスタムサブクラスです。AVFoundationクラスには PreviewView はありませんが、サンプルコードはセッション管理を容易にするためにクラスを作成します。

次の図は、セッションが入力デバイスを管理し、出力をキャプチャする方法を示しています。

メインキャプチャセッションに関連する入力デバイスとキャプチャ出力を含む、AVCamアプリのアーキテクチャの図。

入力と出力を含むすべてのAVCaptureSessionとのインタラクションを専用のシリアルディスパッチキュー(sessionQueue)に委任して、インタラクションがメインキューをブロックしないようにします。セッションの構成は、キューが変更を処理するまで常に他のタスクの実行をブロックするため、セッションのトポロジの変更や、実行中のビデオストリームの中断を含む構成を別のディスパッチキューで実行します。同様に、サンプルコードは、中断されたセッションの再開、キャプチャモードの切り替え、カメラの切り替え、ファイルへのメディアの書き込みなどの他のタスクをセッションキューにディスパッチして、ユーザーの処理がアプリとのユーザーインタラクションをブロックまたは遅延しないようにします。

対照的に、コードは、UIに影響するタスク(プレビュービューの更新など)をメインキューにディスパッチします。これは、CALayerのサブクラスである AVCaptureVideoPreviewLayer がサンプルのプレビュービューのバッキングレイヤーであるためです。メインスレッドで UIViewサブクラスを操作して、それらがタイムリーでインタラクティブな方法で表示されるようにする必要があります。

viewDidLoad で、AVCamはセッションを作成し、それをプレビュービューに割り当てます。

previewView.session = session

イメージキャプチャセッションの構成の詳細については、「キャプチャセッションのセットアップ」を参照してください。

入力デバイスへのアクセスの承認を要求する

セッションを構成すると、入力を受け入れる準備が整います。各AVCaptureDevice (カメラやマイク)は、ユーザーからのアクセス許可が必要です。AVFoundationはAVAuthorizationStatusを使用して認証状態を列挙します。これにより、ユーザーがキャプチャデバイスへのアクセスを制限したか拒否したかをアプリに通知します。

アプリの Info.plist カスタム認証リクエストの準備の詳細については、「iOSでのメディアキャプチャの認証のリクエスト」を参照してください。

背面カメラと前面カメラを切り替える

このchangeCameraメソッドは、ユーザーがUIのボタンをタップしたときにカメラ間の切り替えを処理します。使用可能なデバイスタイプを優先順にリストする検出セッションを使用し、そのアレイの最初のデバイスを受け入れます。たとえば、AVCamで、アプリが実行されているデバイスに videoDeviceDiscoverySession で使用可能な入力デバイスを照会します。ユーザーのデバイスのカメラが壊れている場合、そのデバイスは、配列の中で有効(available)にはなりません。

switch currentPosition {
case .unspecified, .front:
    preferredPosition = .back
    preferredDeviceType = .builtInDualCamera
    
case .back:
    preferredPosition = .front
    preferredDeviceType = .builtInTrueDepthCamera
    
@unknown default:
    print("Unknown capture position. Defaulting to back, dual-camera.")
    preferredPosition = .back
    preferredDeviceType = .builtInDualCamera
}

検出セッションで適切な位置にカメラが見つかると、キャプチャセッションから以前の入力が削除され、新しいカメラが入力として追加されます。

// Remove the existing device input first, because AVCaptureSession doesn't support
// simultaneous use of the rear and front cameras.
self.session.removeInput(self.videoDeviceInput)

if self.session.canAddInput(videoDeviceInput) {
    NotificationCenter.default.removeObserver(self, name: .AVCaptureDeviceSubjectAreaDidChange, object: currentVideoDevice)
    NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange), name: .AVCaptureDeviceSubjectAreaDidChange, object: videoDeviceInput.device)
    
    self.session.addInput(videoDeviceInput)
    self.videoDeviceInput = videoDeviceInput
} else {
    self.session.addInput(self.videoDeviceInput)
}

割り込みとエラーの処理

キャプチャセッション中に、通話、他のアプリからの通知、音楽の再生などの中断が発生する場合があります。監視するAVCaptureSessionWasInterruptedオブザーバーを追加して、これらの中断を処理します

NotificationCenter.default.addObserver(self,
                                       selector: #selector(sessionWasInterrupted),
                                       name: .AVCaptureSessionWasInterrupted,
                                       object: session)
NotificationCenter.default.addObserver(self,
                                       selector: #selector(sessionInterruptionEnded),
                                       name: .AVCaptureSessionInterruptionEnded,
                                       object: session)

AVCamが中断通知を受信すると、中断が終了したときにアクティビティを再開するオプションを使用して、セッションを一時停止または一時停止できます。AVCamは通知を受信するためのsessionWasInterruptedハンドラーを登録し、キャプチャセッションが中断されたときにユーザーに通知します。

if reason == .audioDeviceInUseByAnotherClient || reason == .videoDeviceInUseByAnotherClient {
    showResumeButton = true
} else if reason == .videoDeviceNotAvailableWithMultipleForegroundApps {
    // Fade-in a label to inform the user that the camera is unavailable.
    cameraUnavailableLabel.alpha = 0
    cameraUnavailableLabel.isHidden = false
    UIView.animate(withDuration: 0.25) {
        self.cameraUnavailableLabel.alpha = 1
    }
} else if reason == .videoDeviceNotAvailableDueToSystemPressure {
    print("Session stopped running due to shutdown system pressure level.")
}

カメラビューコントローラーは、エラーが発生したときに通知を受け取るようにAVCaptureSessionRuntimeError を監視します。

NotificationCenter.default.addObserver(self,
                                       selector: #selector(sessionRuntimeError),
                                       name: .AVCaptureSessionRuntimeError,
                                       object: session)

ランタイムエラーが発生したら、キャプチャセッションを再開します。

// If media services were reset, and the last start succeeded, restart the session.
if error.code == .mediaServicesWereReset {
    sessionQueue.async {
        if self.isSessionRunning {
            self.session.startRunning()
            self.isSessionRunning = self.session.isRunning
        } else {
            DispatchQueue.main.async {
                self.resumeButton.isHidden = false
            }
        }
    }
} else {
    resumeButton.isHidden = false
}

キャプチャセッションは、デバイスが過熱などのシステムプレッシャーに耐える場合にも停止することがあります。カメラ自体がキャプチャ品質を低下させたり、フレームをドロップすることはありません。臨界点に達すると、カメラが動作を停止するか、デバイスがシャットダウンします。ユーザーを驚かせないようにするには、フレームレートを手動で下げたり、深度をオフにしたり、AVCaptureDevice.SystemPressureState からのフィードバックに基づいてパフォーマンスを調整したりすることができます。

let pressureLevel = systemPressureState.level
if pressureLevel == .serious || pressureLevel == .critical {
    if self.movieFileOutput == nil || self.movieFileOutput?.isRecording == false {
        do {
            try self.videoDeviceInput.device.lockForConfiguration()
            print("WARNING: Reached elevated system pressure level: \(pressureLevel). Throttling frame rate.")
            self.videoDeviceInput.device.activeVideoMinFrameDuration = CMTime(value: 1, timescale: 20)
            self.videoDeviceInput.device.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: 15)
            self.videoDeviceInput.device.unlockForConfiguration()
        } catch {
            print("Could not lock device for configuration: \(error)")
        }
    }
} else if pressureLevel == .shutdown {
    print("Session stopped running due to shutdown system pressure level.")
}

写真撮影

写真の撮影はセッションキューで行われます。プロセスは、ビデオプレビューレイヤーのビデオの向きに合わせて AVCapturePhotoOutput 接続を更新することから始まります。これにより、カメラはユーザーが画面に表示するものを正確にキャプチャできます。

if let photoOutputConnection = self.photoOutput.connection(with: .video) {
    photoOutputConnection.videoOrientation = videoPreviewLayerOrientation!
}

出力を調整した後、AVCamは AVCapturePhotoSettings の作成に進み、フォーカス、フラッシュ、解像度などのキャプチャパラメーターを構成します。

var photoSettings = AVCapturePhotoSettings()

// Capture HEIF photos when supported. Enable auto-flash and high-resolution photos.
if  self.photoOutput.availablePhotoCodecTypes.contains(.hevc) {
    photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc])
}

if self.videoDeviceInput.device.isFlashAvailable {
    photoSettings.flashMode = .auto
}

photoSettings.isHighResolutionPhotoEnabled = true
if !photoSettings.__availablePreviewPhotoPixelFormatTypes.isEmpty {
    photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoSettings.__availablePreviewPhotoPixelFormatTypes.first!]
}
// Live Photo capture is not supported in movie mode.
if self.livePhotoMode == .on && self.photoOutput.isLivePhotoCaptureSupported {
    let livePhotoMovieFileName = NSUUID().uuidString
    let livePhotoMovieFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((livePhotoMovieFileName as NSString).appendingPathExtension("mov")!)
    photoSettings.livePhotoMovieFileURL = URL(fileURLWithPath: livePhotoMovieFilePath)
}

photoSettings.isDepthDataDeliveryEnabled = (self.depthDataDeliveryMode == .on
    && self.photoOutput.isDepthDataDeliveryEnabled)

photoSettings.isPortraitEffectsMatteDeliveryEnabled = (self.portraitEffectsMatteDeliveryMode == .on
    && self.photoOutput.isPortraitEffectsMatteDeliveryEnabled)

if photoSettings.isDepthDataDeliveryEnabled {
    if !self.photoOutput.availableSemanticSegmentationMatteTypes.isEmpty {
        photoSettings.enabledSemanticSegmentationMatteTypes = self.selectedSemanticSegmentationMatteTypes
    }
}

photoSettings.photoQualityPrioritization = self.photoQualityPrioritizationMode

このサンプルでは、写真キャプチャデリゲートに個別の PhotoCaptureProcessor オブジェクトを使用して、各キャプチャライフサイクルを分離します。キャプチャサイクルのこの明確な分離は、単一のキャプチャサイクルが複数のフレームのキャプチャに関係するLive Photosに必要です。

ユーザーが中央のシャッターボタンを押すたびに、AVCamはcapturePhoto(with:delegate:) を呼び出して、以前に構成された設定で写真をキャプチャします。

self.photoOutput.capturePhoto(with: photoSettings, delegate: photoCaptureProcessor)

このcapturePhotoメソッドは2つのパラメーターを受け入れます。

  • 露出、フラッシュ、フォーカス、トーチなど、ユーザーがアプリを通じて構成した設定をカプセル化するAVCapturePhotoSettingsオブジェクト。
  • AVCapturePhotoCaptureDelegate
    プロトコルに準拠するデリゲート。写真のキャプチャ中にシステムが配信する後続のコールバックに応答します。

アプリがcapturePhoto(with:delegate:)を呼び出すと、写真撮影を開始するプロセスが終了します。それ以降、その個々の写真キャプチャの操作はデリゲートコールバックで行われます。

Photo Capture Delegateを通じて結果を追跡

この方法は、写真を撮るcapturePhotoプロセスを開始するだけです。残りのプロセスは、アプリが実装するデリゲートメソッドで行われます。

静止画キャプチャのデリゲートコールバックのタイムライン。
  • photoOutput(_:willBeginCaptureFor:)
    電話をかけるとすぐに最初に到着します。解決された設定は、カメラが次の写真に適用する実際の設定を表します。AVCamは、Live Photosに固有の動作にのみこのメソッドを使用します。AVCamは、livePhotoMovieDimensions サイズをチェックして、写真がライブ写真かどうかを判断しようとします。写真がライブ写真の場合、AVCamはカウントをインクリメントして進行中のライブ写真を追跡します。
self.sessionQueue.async {
    if capturing {
        self.inProgressLivePhotoCapturesCount += 1
    } else {
        self.inProgressLivePhotoCapturesCount -= 1
    }
    
    let inProgressLivePhotoCapturesCount = self.inProgressLivePhotoCapturesCount
    DispatchQueue.main.async {
        if inProgressLivePhotoCapturesCount > 0 {
            self.capturingLivePhotoLabel.isHidden = false
        } else if inProgressLivePhotoCapturesCount == 0 {
            self.capturingLivePhotoLabel.isHidden = true
        } else {
            print("Error: In progress Live Photo capture count is less than 0.")
        }
    }
}
  • photoOutput(_:willCapturePhotoFor:)
    システムがシャッター音を鳴らした直後に到着します。AVCamはこの機会を利用して画面を点滅させ、カメラが写真を撮影したことをユーザーに警告します。サンプルコードでは、プレビュービューレイヤーのopacityfromから0toをアニメーション化することにより、このフラッシュを実装してい1ます。
// Flash the screen to signal that AVCam took a photo.
DispatchQueue.main.async {
    self.previewView.videoPreviewLayer.opacity = 0
    UIView.animate(withDuration: 0.25) {
        self.previewView.videoPreviewLayer.opacity = 1
    }
}
  • photoOutput(_:didFinishProcessingPhoto:error:)
    システムが深度データとポートレートエフェクトマットの処理を完了すると到着します。AVCamは、この段階でポートレート効果のマットと深度メタデータをチェックします。
// A portrait effects matte gets generated only if AVFoundation detects a face.
if var portraitEffectsMatte = photo.portraitEffectsMatte {
    if let orientation = photo.metadata[ String(kCGImagePropertyOrientation) ] as? UInt32 {
        portraitEffectsMatte = portraitEffectsMatte.applyingExifOrientation(CGImagePropertyOrientation(rawValue: orientation)!)
    }
    let portraitEffectsMattePixelBuffer = portraitEffectsMatte.mattingImage
    let portraitEffectsMatteImage = CIImage( cvImageBuffer: portraitEffectsMattePixelBuffer, options: [ .auxiliaryPortraitEffectsMatte: true ] )
  • photoOutput(_:didFinishCaptureFor:error:)
    1つの写真のキャプチャの終了を示す最後のコールバックです。AVCamはデリゲートと設定をクリーンアップして、後続の写真キャプチャのために残さないようにします。
self.sessionQueue.async {
    self.inProgressPhotoCaptureDelegates[photoCaptureProcessor.requestedPhotoSettings.uniqueID] = nil
}

このデリゲートメソッドでは、キャプチャした写真のプレビューサムネイルをアニメーション化するなど、他の視覚効果を適用できます。

デリゲートコールバックによる写真の進行状況の追跡の詳細については、「写真キャプチャの進行状況の追跡」を参照してください。

ライブ写真のキャプチャ

ライブフォトのキャプチャを有効にすると、カメラはキャプチャの瞬間を中心に1つの静止画像と短いムービーを撮影します。アプリは、静止写真のキャプチャと同じ方法でライブ写真のキャプチャをトリガーします。つまり、capturePhotoWithSettingsへの1回の呼び出しで、livePhotoMovieFileURLプロパティを通じてライブ写真の短いビデオのURLを渡します。AVCapturePhotoOutputレベルでLive Photosを有効にするか、キャプチャごとにAVCapturePhotoSettingsレベルでLive Photosを構成できます。

Live Photoキャプチャは短いムービーファイルを作成するため、AVCamはムービーファイルを保存する場所をURLとして表現する必要があります。また、Live Photoキャプチャはオーバーラップする可能性があるため、コードは進行中のLive Photoキャプチャの数を追跡して、これらのキャプチャ中にLive Photoラベルが常に表示されるようにする必要があります。前のセクションの photoOutput(_:willBeginCaptureFor:)デリゲートメソッドは、この追跡カウンターを実装します。

Live Photoキャプチャのデリゲートコールバックのタイムライン。
livePhotoCaptureHandler(false)
if error != nil {
    print("Error processing Live Photo companion movie: \(String(describing: error))")
    return
}
livePhotoCompanionMovieURL = outputFileURL

アプリにライブ写真キャプチャを組み込む方法の詳細については、「静止画とライブ写真キャプチャする」を参照してください。

深度データとPortrait Effects Matteのキャプチャ

AVCapturePhotoOutput を使用して、AVCamはキャプチャデバイスにクエリを実行し、その構成が深度データとポートレートエフェクトマットを静止画像に提供できるかどうかを確認します。入力デバイスがこれらのモードのいずれかをサポートし、キャプチャ設定でそれらを有効にした場合、カメラは深度ごとのEffects Matteを写真ごとのリクエストに基づいて補助メタデータとして添付します。デバイスが深度データ、portrait effects matte、またはライブ写真の配信をサポートしている場合、アプリにはボタンが表示され、機能を有効または無効にする設定を切り替えるために使用されます。

           if self.photoOutput.isDepthDataDeliverySupported {
               self.photoOutput.isDepthDataDeliveryEnabled = true
               
               DispatchQueue.main.async {
                   self.depthDataDeliveryButton.isEnabled = true
               }
           }
           
           if self.photoOutput.isPortraitEffectsMatteDeliverySupported {
               self.photoOutput.isPortraitEffectsMatteDeliveryEnabled = true
               
               DispatchQueue.main.async {
                   self.portraitEffectsMatteDeliveryButton.isEnabled = true
               }
           }
           
           if !self.photoOutput.availableSemanticSegmentationMatteTypes.isEmpty {
self.photoOutput.enabledSemanticSegmentationMatteTypes = self.photoOutput.availableSemanticSegmentationMatteTypes
               self.selectedSemanticSegmentationMatteTypes = self.photoOutput.availableSemanticSegmentationMatteTypes
               
               DispatchQueue.main.async {
                   self.semanticSegmentationMatteDeliveryButton.isEnabled = (self.depthDataDeliveryMode == .on) ? true : false
               }
           }
           
           DispatchQueue.main.async {
               self.livePhotoModeButton.isHidden = false
               self.depthDataDeliveryButton.isHidden = false
               self.portraitEffectsMatteDeliveryButton.isHidden = false
               self.semanticSegmentationMatteDeliveryButton.isHidden = false
               self.photoQualityPrioritizationSegControl.isHidden = false
               self.photoQualityPrioritizationSegControl.isEnabled = true
           }

カメラは、奥行きとポートレート効果のマットメタデータを補助画像として保存し、Image I / O API を通じて検出およびアドレス指定できます。AVCamは、次のkCGImageAuxiliaryDataTypePortraitEffectsMatte型の補助画像を検索して、このメタデータにアクセスします。

if var portraitEffectsMatte = photo.portraitEffectsMatte {
    if let orientation = photo.metadata[String(kCGImagePropertyOrientation)] as? UInt32 {
        portraitEffectsMatte = portraitEffectsMatte.applyingExifOrientation(CGImagePropertyOrientation(rawValue: orientation)!)
    }
    let portraitEffectsMattePixelBuffer = portraitEffectsMatte.mattingImage

深度データキャプチャの詳細については、深度を使用した写真のキャプチャを参照してください。

セマンティックセグメンテーションMattesのキャプチャ

また、AVCapturePhotoOutputを使用すると、AVCamはセマンティックセグメンテーションマットをキャプチャして、人の髪、皮膚、歯を個別のマットイメージにセグメント化できます。メインの写真とともにこれらの補助画像をキャプチャできるため、人物の髪の色を変更したり、笑顔を明るくしたりするなど、写真の効果を簡単に適用できます。

enabledSemanticSegmentationMatteTypes プロパティにお好みの値(hairskinteeth)を写真出力に設定することにより、これらの補助画像の撮影が可能になります。サポートされているすべてのタイプをキャプチャするには、このプロパティを写真出力のプロパティと一致するように設定します。

// Capture all available semantic segmentation matte types.
photoOutput.enabledSemanticSegmentationMatteTypes = 
    photoOutput.availableSemanticSegmentationMatteTypes

写真出力で写真のキャプチャが終了したら、写真のsemanticSegmentationMatte(for:)メソッドをクエリして、関連するセグメンテーションマットイメージを取得します。このメソッドは、マット画像と、画像の処理時に使用できる追加のメタデータを含むAVSemanticSegmentationMatteを返します。サンプルアプリは、セマンティックセグメンテーションマットイメージのデータを配列に追加して、ユーザーのフォトライブラリに書き込むことができるようにします。

// Find the semantic segmentation matte image for the specified type.
guard var segmentationMatte = photo.semanticSegmentationMatte(for: ssmType) else { return }

// Retrieve the photo orientation and apply it to the matte image.
if let orientation = photo.metadata[String(kCGImagePropertyOrientation)] as? UInt32,
    let exifOrientation = CGImagePropertyOrientation(rawValue: orientation) {
    // Apply the Exif orientation to the matte image.
    segmentationMatte = segmentationMatte.applyingExifOrientation(exifOrientation)
}

var imageOption: CIImageOption!

// Switch on the AVSemanticSegmentationMatteType value.
switch ssmType {
case .hair:
    imageOption = .auxiliarySemanticSegmentationHairMatte
case .skin:
    imageOption = .auxiliarySemanticSegmentationSkinMatte
case .teeth:
    imageOption = .auxiliarySemanticSegmentationTeethMatte
default:
    print("This semantic segmentation type is not supported!")
    return
}

guard let perceptualColorSpace = CGColorSpace(name: CGColorSpace.sRGB) else { return }

// Create a new CIImage from the matte's underlying CVPixelBuffer.
let ciImage = CIImage( cvImageBuffer: segmentationMatte.mattingImage,
                       options: [imageOption: true,
                                 .colorSpace: perceptualColorSpace])

// Get the HEIF representation of this image.
guard let imageData = context.heifRepresentation(of: ciImage,
                                                 format: .RGBA8,
                                                 colorSpace: perceptualColorSpace,
                                                 options: [.depthImage: ciImage]) else { return }

// Add the image data to the SSM data array for writing to the photo library.
semanticSegmentationMatteDataArray.append(imageData)

ユーザーの写真ライブラリに写真を保存する

画像またはムービーをユーザーのフォトライブラリに保存する前に、まずそのライブラリへのアクセスをリクエストする必要があります。書き込み承認ミラーを要求するプロセスは、デバイス承認をキャプチャします。Info.plist で指定したテキストを含むアラートを表示します。

AVCamは、fileOutput(_:didFinishRecordingTo:from:error:)コールバックメソッドで認証をチェックします。これは、AVCaptureOutput が出力として保存するメディアデータを提供する場所です。

PHPhotoLibrary.requestAuthorization { status in

ユーザーの写真ライブラリへのアクセスのリクエストの詳細については、写真へのアクセスを承認するリクエスト を参照してください。

動画ファイルを記録する

AVCamは、.video修飾子を使用して入力デバイスをクエリおよび追加することにより、ビデオキャプチャをサポートします。アプリのデフォルトは背面デュアルカメラですが、デバイスにデュアルカメラがない場合、アプリのデフォルトは広角カメラになります。

if let dualCameraDevice = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back) {
    defaultVideoDevice = dualCameraDevice
} else if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
    // If a rear dual camera is not available, default to the rear wide angle camera.
    defaultVideoDevice = backCameraDevice
} else if let frontCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) {
    // If the rear wide angle camera isn't available, default to the front wide angle camera.
    defaultVideoDevice = frontCameraDevice
}

スチール写真のようにシステムに設定を渡す代わりに、Live Photosのように出力URLを渡します。デリゲートコールバックは同じURLを提供するので、アプリはそれを中間変数に格納する必要はありません。

ユーザーが[記録]をタップしてキャプチャを開始すると、AVCamがstartRecording(to:recordingDelegate:)を呼び出します。

movieFileOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: self)

capturePhoto が静止キャプチャのためにデリゲートコールバックをトリガーしたのと同様に、startRecording はムービーの記録のための一連のデリゲートコールバックをトリガーします。

映画記録のためのデリゲートコールバックのタイムライン。

デリゲートコールバックチェーンを通じて、映画の録画の進行状況を追跡します。AVCapturePhotoCaptureDelegate を実装する代わりに、AVCaptureFileOutputRecordingDelegate を実装します。ムービーを記録するデリゲートコールバックはキャプチャセッションとの相互作用を必要とするため、AVCamは個別のデリゲートオブジェクトを作成する代わりにデリゲートを作成します。

  • fileOutput(_:didStartRecordingTo:from:)
    ファイル出力がファイルへのデータの書き込みを開始すると発生します。AVCamはこの機会を利用して、[記録]ボタンを[停止]ボタンに変更します。
DispatchQueue.main.async {
    self.recordButton.isEnabled = true
    self.recordButton.setImage(#imageLiteral(resourceName: "CaptureStop"), for: [])
}
  • fileOutput(_:didFinishRecordingTo:from:error:)
    最後に起動され、映画が完全にディスクに書き込まれ、使用できる状態になったことを示します。AVCamはこの機会に、一時的に保存されたムービーを、指定されたURLからユーザーの写真ライブラリまたはアプリのドキュメントフォルダーに移動します。
PHPhotoLibrary.shared().performChanges({
    let options = PHAssetResourceCreationOptions()
    options.shouldMoveFile = true
    let creationRequest = PHAssetCreationRequest.forAsset()
    creationRequest.addResource(with: .video, fileURL: outputFileURL, options: options)
}, completionHandler: { success, error in
    if !success {
        print("AVCam couldn't save the movie to your photo library: \(String(describing: error))")
    }
    cleanup()
}
)

AVCamがバックグラウンドに入った場合(ユーザーが電話の着信を受け入れる場合など)は、アプリはユーザーに録音を続行する許可を求める必要があります。AVCamは、バックグラウンドタスクを通じてこの保存を実行するためにシステムに時間を要求します。このバックグラウンドタスクにより、AVCamがバックグラウンドに戻った場合でも、ファイルをフォトライブラリに書き込むのに十分な時間が確保されます。バックグラウンド実行を完了するために、AVCamは記録されたファイルを保存した 後にfileOutput(_:didFinishRecordingTo:from:error:) の endBackgroundTask(_:)を呼び出します。

self.backgroundRecordingID = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)

動画の撮影中に写真を撮る

iOSカメラアプリと同様に、AVCamは写真を撮りながらムービーをキャプチャできます。AVCamはそのような写真をビデオと同じ解像度でキャプチャします。

let movieFileOutput = AVCaptureMovieFileOutput()

if self.session.canAddOutput(movieFileOutput) {
    self.session.beginConfiguration()
    self.session.addOutput(movieFileOutput)
    self.session.sessionPreset = .high
    if let connection = movieFileOutput.connection(with: .video) {
        if connection.isVideoStabilizationSupported {
            connection.preferredVideoStabilizationMode = .auto
        }
    }
    self.session.commitConfiguration()
    
    DispatchQueue.main.async {
        captureModeControl.isEnabled = true
    }
    
    self.movieFileOutput = movieFileOutput
    
    DispatchQueue.main.async {
        self.recordButton.isEnabled = true
        
        /*
         For photo captures during movie recording, Speed quality photo processing is prioritized
         to avoid frame drops during recording.
         */
        self.photoQualityPrioritizationSegControl.selectedSegmentIndex = 0
        self.photoQualityPrioritizationSegControl.sendActions(for: UIControl.Event.valueChanged)
    }
}

setSampleBufferDelegate(_:queue:)

Apple Developer より

https://developer.apple.com/documentation/avfoundation/avcapturevideodataoutput/1389008-setsamplebufferdelegate

サンプルバッファデリゲートと、コールバックが呼び出されるキューを設定します。

iOS 4.0以降

macOS 10.7以降

Mac Catalyst 13.0以降

AVFoundation

宣言

func setSampleBufferDelegate(_ sampleBufferDelegate: AVCaptureVideoDataOutputSampleBufferDelegate?, 
                       queue sampleBufferCallbackQueue: DispatchQueue?)

パラメーター

sampleBufferDelegate

キャプチャ後にサンプルバッファを受信するプロトコル AVCaptureVideoDataOutputSampleBufferDelegate に準拠したオブジェクト。

sampleBufferCallbackQueue

コールバックが呼び出されるキュー。ビデオフレームが順番に配信されるようにするには、シリアルディスパッチキューを使用する必要があります。

sampleBufferCallbackQueue パラメータは、sampleBufferDelegatが NULL に設定される場合を除き、NULLに設定してはいけない。

ディスカッション

新しいビデオサンプルバッファーがキャプチャされると、captureOutput(_:didOutput:from:) により、サンプルバッファーデリゲートに送信されます。すべてのデリゲートメソッドは、指定されたディスパッチキューで呼び出されます。

新しいフレームがキャプチャされたときにキューがブロックされている場合、それらのフレームは、alwaysDiscardsLateVideoFrames プロパティの値によって決定されたときに自動的にドロップされます。これにより、同じフレームで既存のフレームを処理できるようになります。処理が着信フレームのレートに追いつかない場合、メモリ使用量が増加しますが、それを管理する必要がなくなります。要は、データが捨てられるので、メモリの解放をする必要がなくなるということ。

フレーム処理が常に着信フレームのレートに対応できない場合は、minFrameDuration プロパティを使用することを検討する必要があります。これにより、通常、フレームドロップのみの場合よりもパフォーマンス特性が向上し、フレームレートの一貫性が向上します。

フレームがドロップされる可能性を最小限に抑える必要がある場合は、受信するサンプルバッファーの外で十分に少ない量の処理が実行されるキューを指定する必要があります。ただし、追加の処理を別のキューに移行する場合は、処理されていないフレームの影響を受けずにメモリ使用量が増加しないようにする必要があります。

VideoRecorder

Please refer to the original information.
https://qiita.com/daigou26/items/74bbdfce46db8898fb47

Swift 5.2、1iOS 13.6

import UIKit
import AVFoundation
import Photos

class VideoViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {
    let fileOutput = AVCaptureMovieFileOutput()
    var isRecording = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setUpPreview()
        
        // インターバルボタンが押された
        NotificationCenter.default.addObserver(self, selector:
            #selector(recButtonNotification(notification:)), name: .recButtonPressed, object: nil)
    }
    
    @objc func recButtonNotification(notification:Notification)
    {
        if !isRecording {
            let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let fileURL = documentPath.appendingPathComponent( "temp.mp4" )
            
            fileOutput.startRecording(to: fileURL, recordingDelegate: self)
            print("録画開始")
            isRecording = true
        } else {
            // 録画終了
            fileOutput.stopRecording()
            print("録画終了")
            isRecording = false
        }
    }
    
    func setUpPreview() {
        let videoDevice = AVCaptureDevice.default(for: AVMediaType.video)
        let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
        
        do {
            if videoDevice == nil || audioDevice == nil {
                throw NSError(domain: "device error", code: -1, userInfo: nil)
            }
            let captureSession = AVCaptureSession()
            
            // video inputを capture sessionに追加
            let videoInput = try AVCaptureDeviceInput(device: videoDevice!)
            captureSession.addInput(videoInput)
            
            // audio inputを capture sessionに追加
            let audioInput = try AVCaptureDeviceInput(device: audioDevice!)
            captureSession.addInput(audioInput)
            
            // ファイル出力を追加
            // 最大5秒で録画終了
            self.fileOutput.maxRecordedDuration = CMTimeMake(value: 5, timescale: 1)
            captureSession.addOutput(fileOutput)
            
            // プレビュー
            let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            videoLayer.frame = self.view.bounds
            videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
            self.view.layer.addSublayer(videoLayer)
            
            captureSession.startRunning()
        } catch {
            // エラー処理
        }
    }
    
    func changeButtonColor(target: UIButton, color: UIColor) {
        target.backgroundColor = color
    }
    
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        // ライブラリへ保存
        PHPhotoLibrary.shared().performChanges ({
            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL)
        }) { completed, error in
            if completed {
                print("Video is saved!")
            }
        }
    }
    
}

UIImagePickerController

import UIKit

class ViewController: UIViewController,
    UIImagePickerControllerDelegate,
    UINavigationControllerDelegate
{

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBOutlet weak var imageView: UIImageView!
    
    @IBAction func butonPressed(_ sender: Any) {
//    let sourceType:UIImagePickerController.SourceType = .camera
//    let sourceType:UIImagePickerController.SourceType = .savedPhotosAlbum
        let sourceType:UIImagePickerController.SourceType = .photoLibrary
        
        if UIImagePickerController.isSourceTypeAvailable(
            UIImagePickerController.SourceType.photoLibrary){
            // UIImagePickerControllerを表示
            let imagePicker = UIImagePickerController()
            imagePicker.sourceType = sourceType
            imagePicker.delegate = self
            self.present(imagePicker, animated: true, completion: nil)
        }
        else{
            debugPrint("error")
        }
    }
    
    // 画像選択時に呼ばれる
    func imagePickerController(_ imagePicker: UIImagePickerController,
            didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]){
        
        if let pickedImage = info[.originalImage]
            as? UIImage {
            
            imageView.contentMode = .scaleAspectFit
            imageView.image = pickedImage
        }

        //閉じる処理
        imagePicker.dismiss(animated: true, completion: nil)
    }
    
    // キャンセルされた時に呼ばれる
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
    
}
UIImagePickerControllerの Delegateとして、
UIImagePickerControllerDelegate,
UINavigationControllerDelegate
が必要。

カメラを使用する場合

   let sourceType:UIImagePickerController.SourceType = .camera

写真(Photo Library) から選択する場合

   let sourceType:UIImagePickerController.SourceType = .photoLibrary

モーメントから選択する場合

   let sourceType:UIImagePickerController.SourceType = .savedPhotosAlbum