應用 AVFoundation 建立一個全螢幕相機 App


今天,我們將學習如何使用 AV Foundation ,它是一個 Apple 系統框架,適用於 macOS、iOS、watchOS 和 tvOS 操作系統上。本教學的目標就是幫助你開發一個有完整功能的 iOS App,能夠使用裝置的相機來拍照與錄影。我們亦會以物件導向程式基礎,設計一個實用的類別,讓它可重覆使用與延伸至你所有的專案中。

注意:本篇教學需要用到實體 iOS 裝置,單靠模擬器無法完成本篇的示範 App。且本篇教學需要讀者對 UIkit 基礎有深入了解,包括如 Action (動作)、Interface Builder (介面建構器)、與 Storyboard (故事板),並對 Swift 語法有一定程度的了解。

提示:你需要使用 Xcode 9(或以上版本)執行此範例程式,所有程式碼已更新至 Swift 4。

甚麼是 AV Foundation?

根據 Apple,AV Foundation 是一個在 iOS、macOS、watchOS 與 tvOS 系統上以時間為主的影音系統框架,你可以輕易地使用 AV Foudation 來撥放、建立與編輯 QuickTime 影片和 MEPG-4 檔案格式、撥放 HLS 串流檔,或是在你的 App 內建立強大影音功能。

明白了吧? AV Foundation 是一個可以在 Apple 裝置上擷取、處理、編輯聲音與影像的系統框架。在本篇教學中,我們將會特別介紹如何使用它,配合前後相機、閃光功能與聲音,來擷取照片與影片。

我需要 AV Foundation 嗎?

在開始這段教學旅程前,你要知道 AV Foundation 是一個複雜又詳細的工具。在很多的案例中,其實應用 Apple 預設的 APIs 如UIImagePickerController就已足夠。所以在開始前,請確認你是否真的需要應用 AV Foundation。

Sessions, Devices, Inputs 和 Outputs

利用 AV Foundation 來擷取相片或影片的核心就是 Capture Sessions。根據 Apple,Capture Session 是用以「管理擷取活動、並協調來自 Input Devices 到擷取 Outputs 的數據流」。在 AV Foundation 內,Capture Sessions 是由AVCaptureSession來管理的。

此外,所謂的 Capture Device 是指在 iOS 裝置上,可實際得到真實聲音與影像的擷取設備。為了使用 AV Foundation,你要利用 Capture Devices 建立 Capture Inputs,並提供予 Capture Session,然後將結果存入 Capture Outputs。以下的架構圖可清楚描述它們的關係:

Flowchart

專案範例

一如往昔,你需要親自探索如何使用這個框架。我們將會提供一個專案範例讓你學習,但我們要先專注在 AV Foundation 的框架上,所以本篇教學會以一個簡易的專案開始。在開始前,請你先在此下載專案,並簡單看一下專案的內容。

這個專案範例內容比較簡單,包括:

  • 一個Assets.xcassets檔,裡面有所有這個專輯範例所需的 icon 圖檔。圖檔是 Google 的 Material Design 團隊設計的,你也可以在這裡material.io/icons 免費下載其他的相關圖檔。
  • 一個 Storyboard 檔和一個 View Controller,我們的 App 將會用這個 View Controller 來處理所有相片與影像擷取,包括:
    • 一個擷取按鈕,用來啟動相片或影像的擷取
    • 一個擷取預覽畫面,可實時看到相機鏡頭要拍攝的畫面
    • 可切換前後相機與閃光燈的功能
  • 一個Viewcontroller.swift檔來管理上面提到的 View Controller,包括:
    • 所有將上述 UI control 與 Code 連結的 Outlet
    • 一個計算屬性 (Computed Property) 來隱藏狀態欄
    • 一個設定功能來適當地設計擷取按鈕的樣式

執行這個專案,你應該會看見這個畫面:

AV Foundation Sample Project

好,我們現在就開始吧!

應用 AVFoundation 實作

在這個教學內,我們將設計一個類別CameraController,這個類別在相片與影像的擷取方面十分重要,我們的 View Controller 將會使用CameraController,並把它結合到使用者介面。

開始前,請先在專案內建立一個新的 Swift 檔,命名為CameraController.swift。然後,在程式碼介面上 import AVFoundation,並宣告一個空類別,像下列所示:

相片擷取

首先,我們將實作利用後相機來擷取相片。這是本教學的基本功能,我們會以此為基礎,加入切換前後相機、開關閃光燈、和錄製影片的功能。由於設定與開始一個 Capture Session 程序較為緊湊,我們將會分成幾個步驟。我們init時,先建立一個叫prepare的函式,準備好 Capture Session 予使用,完成後呼叫 Completion Handler。請把這段prepare函式加到CameraController類別內:

這個函式將負責建立與設定一個新的 Capture Session。記住,設定 Capture Session 分為四個步驟:

  1. 建立一個 Capture Session
  2. 取得與設定必要的 Capture Devices
  3. 在 Capture Device 上建立 Inputs
  4. 設定一個 Photo Output 物件來處理擷取到的影像

我們將使用 Swift 的巢狀 (nest) 函式來封裝我們的程式碼。先在prepare內宣告四個空函式,然後呼叫它們:

在上述的程式碼中,我們已經建立了一套樣板函式,執行準備AVCaptureSession來擷取相片的四個步驟。我們也設計了一個非同步計算處理模式來呼叫這四個函式,必要時會捕獲任何錯誤,完成時就會呼叫 Completion Handler。現在就只差實作這四個函式了!我們從createCaptureSession開始吧。

建立 Capture Session

在設定一個特定的AVCaptureSession之前,我們要先建立它。將下列屬性變數加到CameraController.swift程式碼:

接下來,在prepare內的巢狀函式createCaptureSession加入下列程式碼:

這個程式碼很簡單,就這樣建立一個新的AVCaptureSession,並將它儲存在captureSession的屬性裡。

設定 Capture Devices

建立了一個AVCaptureSession後,我們要建立AVCaptureDevice物件來代表實際的 iOS 裝置相機,跟著我來加入這個屬性變數到CameraController類別內吧!現在我們將加入frontCamerarearCamera屬性變數,讓之後可以設定多鏡頭擷取功能,並執行切換相機的功能。

然後,在CameraController.swift宣告一個嵌入類型,我們將使用這個嵌入類型來管理建立 Capture Session 時可能遇到的各種錯誤:

你會發現在這 enum 內有不同類型的錯誤碼。先將它們加到你的程式碼內,我們待會就會用到它們。

現在,來了最有趣的部份了!來找出裝置上可用的相機吧!我們可以利用AVCaptureDeviceDiscoverySession來找,將下列程式碼加入到configureCaptureDevices

讓我解釋一下上面的程式碼吧:

  1. 這三行程式碼使用了AVCaptureDeviceDiscoverySession找出裝置上所有可用的內置相機 (.builtInDualCamera)。若找不到任何實體相機,就會出現錯誤訊息。
  2. 這個循環會查看從第一段程式碼找到的可用相機,從而分辦前後相機。然後,將後相機設定為自動對焦,過程中如遇上任何錯誤,也會出現錯誤訊息。

好了,我們使用了AVCaptureDeviceDiscoverySession來找出裝置上可用的相機,並設定了所需要的功能。下一步,我們要將它們連結到 Capture Session。

設定 Device Inputs

現在我們可以建立 Capture Device Inputs,這步驟將會用到 Capture Device,並把它連結到 Capture Session。開始之前,先將下列屬性變數加至CameraController,以確保我們可以儲存 Inputs:

你會發現程式碼在這個狀態下不會編譯,那是因為CameraPosition沒有被定義。要來定義它,請在CameraController加入另一個嵌入類型:

很好,有了儲存和管理 Capture Device Inputs 所有需要的屬性,就可以實作configureDeviceInputs

讓我解釋一下這些程式碼的意思吧:

  1. 這行先簡單地確認captureSession是否存在,若不存在就會出現錯誤訊息。
  2. 這些if流程主要是要建立所需的 Capture Device Input 來支援相片擷取。AVFoundation每一次 Capture Session 僅能允許一台相機的輸入。由於裝置的初始設定通常是後相機,所以我們會先嘗試用後相機建立 Input,再加到 Capture Session;如出現錯誤,就會轉成前相機;若還是有問題,就會出現錯誤訊息。

設定 Photo Outputs

到目前為止,我們已經加了所有必要的 Inputs 到captureSession,現在我們只需一種方式從 Capture Session 中獲取必要的資料。我們可以使用AVCapturePhotoOutput,請多加一個屬性變數至CameraController

現在我們來實作configurePhotoOutput

這是一個簡單的實作方式,我們設定了photoOutput,讓它使用 JPEG 檔案格式作為影片編碼格式。然後,它會將photoOutput加入到captureSession。最後,就開始進行captureSession

快完成了!你的CameraController.swift檔應該是這樣的:

我用了 extension 適當地區分程式碼,這不是必須的;但我認為這是一個好習慣,讓程式碼更容易理解和編寫。

顯示預覽畫面

現在我們已經準備好相機裝置了,是時候來看看它在螢幕上擷取的內容了。在CameraControllerprepare外加入另一個函式,命名為displayPreview。函式應有這個特徵:

另外,要import UIKit到你的CameraController.swift檔,我們也將需要應用UIView

顧名思義,這個函式主要負責建立一個擷取的預覽畫面,並顯示於提供的視圖上。現在,在CameraController加入一個屬性變數到來支援這個函式:

這個屬性變數會讓預覽層顯示captureSession的 Output。現在我們來實作這個方法:

這個函式應用captureSession來建立一個AVCaptureVideoPreview,並設定它為一個直向預覽畫面,再加入到所提供的視圖上。

整理

好了,現在我們試著把這些連接到 View Controller。請轉到ViewController.swift。首先,在ViewController.swift加入一個屬性變數:

接著,在viewDidLoad()內加入一個巢狀函式:

這個函式就只是將 Camera Controller 設定成我們設計的那樣。

最後一步,就是 Apple 規定的安全性要求,你必須提供一個理由向使用者解釋 App 需要使用相機權限的原因。請開啟Info.plist,並如圖加入一行指令:

privacy-info-list-camera

當 App 需要取得的使用者的的允許時,這個 Key 就向使用者解釋原因。

現在你的ViewController.swift檔應該是這樣的:

請建立並執行你的專案,裝置要求允許使用權限時請按接受。然後——耶!你應該已經有一個可運作的擷取預覽畫面,若沒有的話,請重新確認你的程式碼。如果需要協助,你也可以留言。

avfoundation-app-demo

開關閃光燈/切換相機功能

現在有了一個可運作的預覽畫面,我們來繼續加入其他功能吧。許多相機 App 都可以讓使用者切換相機、啟用或關閉閃光燈,我們也來添加這個功能吧。完成後,我們將會增加擷取相片並儲存到相機膠卷的功能。

要啟用開關閃光燈的功能,首先將這個屬性變數加到CameraController

然後轉到ViewController,加入一個@IBAction函式來開關閃光燈:

完成了!現在,當我想要擷取一張相片時,我們的CameraController類別就會控制閃光燈的開關。接著,讓我們啟用切換前後相機功能。

在 AV Foundation 切換前後相機是非常簡單的,我們僅需要移除在原本(前/後)相機的 Capture Input,並增加新的 Capture Input 到想切換到的相機就可以。要啟用這個功能,先添加另一個函式到CameraController

當我們切換相機時,我們會選擇切換到前或後相機,所以要在switchCameras內宣告兩個巢狀函式:

然後,將下列程式碼添加到switchCameras()內:

讓我解釋一下上列程式碼的功能:

  1. 這個guard語法可以確保在切換相機時,有一個有效可運作的 Capture Session,同時確認有正在使用的相機裝置。
  2. 這行告知 Capture Session 開始設定。
  3. 視乎正在使用前或後相機,使用 switch 語法,呼叫switchToRearCameraswitchToFrontCamera
  4. 最後,提交或儲存設定好的 Capture Session。

很好!現在我們開始實作switchToFrontCameraswitchToRearCamera的功能:

兩種函式的程式碼有著十分類似的實作方式。首先,取得 Capture Session 內所有 Inputs 的數組,並確保能切換至所要求的(前/後)相機。下一步,要建立所需的 Input Device,我們只要移除掉舊有的,再增加一個新 Device 就可以了。最後,設定currentCameraPosition,這樣CameraController才會注意到這個改變。很容易吧!回到ViewController.swift,我們就可以加入一個函式來切換相機了:

好!現在打開你的 stroryboard,連接所需的 outlets,建立並執行你的 App。此時,你應該能夠自由地切換相機了!我們現在要實作最重要的功能——相片擷取了!

實作 Image Capture

現在我們要實作期待許久的功能:Image Capture 了。開始之前,讓我們先回顧目前已實作的功能:

  • 設計了一個實用的類別,可以輕易隱藏 AV Foundation 的複雜性。
  • 在這個類別內實作了許多功能,讓我們建立 Capture Session、開關閃光燈、切換相機、和顯示預覽畫面。
  • 將類別與UIViewController連接,並建立一個輕量化的相機 App。

現在我們就只差擷取相片的功能了!

打開CameraController.swift並開始作業。我們先要加入一個有這個特徵的captureImage函式:

這個函式顧名思義,就是會擷取一張圖像,供我們設計的 Camera Controller 使用。一起來實作它吧:

這並不是一個複雜的實作,但你會發現程式碼還不能編譯,因為我們還沒定義photoCaptureCompletionBlockCameraController來符合AVCapturePhotoCaptureDelegate。首先,將一個屬性變數photoCaptureCompletionBlock加到CameraController

然後,擴展CameraController以符合AVCapturePhotoCaptureDelegate

很好,但現在,編譯又出現另一個問題:

要解決這個問題,我們只需要建立CameraController來繼承NSObject。將CameraController的類別宣告修正為class CameraController: NSObject就可以了。

現在回過再看看ViewController。首先,導入Photos框架,因為我們將使用內置的 APIs 來儲存相片。

接著,插入下列的函式:

我們呼叫了 Camera Controller 內的captureImage方法來擷取相片,然後使用PHPhotoLibary類別來儲存圖片到內置的相片資料庫。

最後,連接@IBAction func到 Storyboard 的 capture 按鈕,並到Info.plist加入一行:

privacy-info-list-photolib

這是 iOS 10 的私隱要求,你要解釋 App 需要使用相片資料庫的原因。

現在,你可以建立並執行 App 來擷取圖片,然後在相片資料庫看到剛擷取的相片。恭喜你!現在你懂得如何在你的 App 中使用 AV Foundation 了!本篇教學第二部將涵蓋擷取影像的部分,敬請期待!

若想要完整的專案內容,你可以從 Github 下載

譯者簡介:Oliver Chen-工程師,喜歡美麗的事物,所以也愛上Apple,目前在iOS程式設計上仍是新手,正研讀Swift與Sketch中。生活另一個身份是兩個孩子的爸,喜歡和孩子一起玩樂高,幻想著某天自己開發的App,可以讓孩子覺得老爸好棒!。聯絡方式:電郵[email protected]

原文Building a Full Screen Camera App Using AVFoundation


現時為高中生,喜歡在課餘時間創作App,享受把創作App的經驗與人分享。除此之外還喜歡滑雪、高爾夫球、足球,與朋友在一起。

blog comments powered by Disqus
訂閲電子報

訂閲電子報

AppCoda致力於發佈優質iOS程式教學,你不必每天上站,輸入你的電子郵件地址訂閱網站的最新教學文章。每當有新文章發佈,我們會使用電子郵件通知你。

已收你的指示。請你檢查你的電郵,我們已寄出一封認證信,點擊信中鏈結才算完成訂閱。

Shares
Share This