

# IVS 廣播 SDK：iOS 指南 \$1 低延遲串流
<a name="broadcast-ios"></a>

IVS 低延遲串流 iOS 廣播 SDK 提供了在 iOS 上廣播到 Amazon IVS 所需的介面。

`AmazonIVSBroadcast` 模組會實作本文件中所述的界面。支援以下操作：
+ 設定 (初始化) 廣播工作階段。
+ 管理廣播。
+ 連接和分離輸入裝置。
+ 管理合成工作階段。
+ 接收事件。
+ 接收錯誤。

**最新版 iOS 廣播 SDK：**1.40.0 ([版本備註](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/release-notes.html#mar12-26-broadcast-mobile-ll)) 

**參考文件：**如需有關 Amazon IVS iOS 廣播 SDK 中最重要方法的資訊，請參閱參考文件，網址為 [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/)。

**範本程式碼：**請參閱 GitHub 上的 iOS 範本儲存庫：[https://github.com/aws-samples/amazon-ivs-broadcast-ios-sample](https://github.com/aws-samples/amazon-ivs-broadcast-ios-sample)。

**平台要求：**iOS 14\$1

## iOS 如何選擇攝影機解析度和影格速率
<a name="ios-publish-subscribe-resolution-framerate"></a>

廣播 SDK 管理的攝影機會優化其解析度和影格速率 (每秒影格數或 FPS)，以將溫升和能耗降至最低。本節說明如何選取解析度和影格速率，以協助主持人應用程式針對其使用案例進行優化。

當將 `IVSCamera` 連接至時 `IVSBroadcastSession`，會針對 `IVSVideoConfiguration.targetFramerate` 的影格速率和 `IVSVideoConfiguration.size` 的解析度優化攝影機。這些值會在初始化時提供給 `IVSBroadcastSession`。

# 開始使用 IVS iOS 廣播 SDK \$1 低延遲串流
<a name="broadcast-ios-getting-started"></a>

本文件將帶您了解開始使用 Amazon IVS 低延遲串流功能 iOS 廣播 SDK 的相關步驟。

## 安裝程式庫
<a name="broadcast-ios-install"></a>

建議您透過 Swift Package Manager 來整合廣播 SDK。(或者，您可以手動將架構新增到您的專案中)。

### 建議：整合廣播 SDK (Swift Package Manager)
<a name="broadcast-ios-install-swift"></a>

1. 從 [https://broadcast.live-video.net/1.40.0/Package.swift](https://broadcast.live-video.net/1.40.0/Package.swift) 下載 Package.swift 檔案。

1. 在您的專案中，建立一個名為 AmazonIVSBroadcast 的新目錄，然後將其新增至版本控制。

1. 將下載的 Package.swift 檔案放在新目錄中。

1. 在 Xcode 中，前往**檔案 > 新增套件相依性**，然後選取**新增本機...**

1. 導覽至建立的 AmazonIVSBroadcast 目錄，將其選取，然後選取**新增套件**。

1. 系統提示您**選擇用於 AmazonIVSBroadcast 的套件產品**時，請在**新增至目標**區段中設定應用程式目標，從而選取 **AmazonIVSBroadcast** 作為**套件產品**。

1. 選取**新增套件**。

### 替代方法：手動安裝架構
<a name="broadcast-ios-install-manual"></a>

1. 從 [https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast.xcframework.zip](https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast.xcframework.zip) 中下載最新版本。

1. 解壓縮封存檔的內容。`AmazonIVSBroadcast.xcframework` 包含用於裝置和模擬器的開發套件。

1. 內嵌 `AmazonIVSBroadcast.xcframework`，方法是將其拖曳至您的應用程式目標的**一般**索引標籤的**架構、程式庫和內嵌內容**部分中。  
![\[您的應用程式目標的一般索引標籤的架構、程式庫和內嵌內容區段。\]](http://docs.aws.amazon.com/zh_tw/ivs/latest/LowLatencyUserGuide/images/iOS_Broadcast_SDK_Guide_xcframework.png)

## 實作 IVSBroadcastSession.Delegate
<a name="broadcast-ios-implement-ivsbroadcastsessiondelegate"></a>

實作可讓您接收狀態更新和裝置變更通知的 `IVSBroadcastSession.Delegate`：

```
extension ViewController : IVSBroadcastSession.Delegate {
   func broadcastSession(_ session: IVSBroadcastSession,
                         didChange state: IVSBroadcastSession.State) {
      print("IVSBroadcastSession did change state \(state)")
   }

   func broadcastSession(_ session: IVSBroadcastSession,
                         didEmitError error: Error) {
      print("IVSBroadcastSession did emit error \(error)")
   }
}
```

## 請求權限
<a name="broadcast-ios-permissions"></a>

您的應用程式必須請求許可才能存取使用者的攝影機和麥克風。(這不限於 Amazon IVS；任何需要存取攝影機和麥克風的應用程式都必須如此)。

在這裡，我們檢查使用者是否已經授予許可，如果沒有則提出請求：

```
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized: // permission already granted.
case .notDetermined:
   AVCaptureDevice.requestAccess(for: .video) { granted in
       // permission granted based on granted bool.
   }
case .denied, .restricted: // permission denied.
@unknown default: // permissions unknown.
}
```

如果您想要存取攝影機和麥克風，必須分別對 `.video` 和 `.audio` 媒體類型執行此動作。

此外，您必須將 `NSCameraUsageDescription` 和 `NSMicrophoneUsageDescription` 的項目新增至您的 `Info.plist`。否則，您的應用程式將在嘗試請求許可時當機。

## 停用應用程式閒置計時器
<a name="broadcast-ios-disable-idle-timer"></a>

此為選用操作，但建議您採用。這可以防止您的裝置在使用廣播開發套件時進入休眠狀態，導致廣播中斷。

```
override func viewDidAppear(_ animated: Bool) {
   super.viewDidAppear(animated)
   UIApplication.shared.isIdleTimerDisabled = true
}
override func viewDidDisappear(_ animated: Bool) {
   super.viewDidDisappear(animated)
   UIApplication.shared.isIdleTimerDisabled = false
}
```

## (選擇性) 設定 AVAudioSession
<a name="broadcast-ios-setup-avaudiosession"></a>

根據預設，廣播開發套件會設定您應用程式的 `AVAudioSession`。如果您要自行管理，請將 `IVSBroadcastSession.applicationAudioSessionStrategy` 設為 `noAction`。如果沒有控制 `AVAudioSession`，則廣播開發套件無法在內部管理麥克風。若要搭配 `noAction` 選項使用麥克風，您可以建立一個 `IVSCustomAudioSource`，並透過 `AVCaptureSession`、`AVAudioEngine` 或提供 PCM 音訊樣本的其他工具，提供您自己的樣本。

如果您要手動設定 `AVAudioSession`，至少必須將類別設為 `.record` 或 `.playbackAndRecord`，並將其設為 `active`。如果您想要從藍牙裝置錄製音訊，則必須一併指定 `.allowBluetooth` 選項：

```
do {
   try AVAudioSession.sharedInstance().setCategory(.record, options: .allowBluetooth)
   try AVAudioSession.sharedInstance().setActive(true)
} catch {
   print("Error configuring AVAudioSession")
}
```

我們建議您讓開發套件為您處理設定。否則，如果您想要在不同的音訊裝置之間進行選擇，則必須手動管理連接埠。

## 建立廣播工作階段
<a name="broadcast-ios-create-session"></a>

廣播介面是 `IVSBroadcastSession`。如下所示進行初始化：

```
let broadcastSession = try IVSBroadcastSession(
   configuration: IVSPresets.configurations().standardLandscape(),
   descriptors: IVSPresets.devices().frontCamera(),
   delegate: self)
```

另請參閱[建立廣播工作階段 (進階版)](broadcast-ios-use-cases.md#broadcast-ios-create-session-advanced)

## 設定 IVSImagePreviewView 以供預覽
<a name="broadcast-ios-set-imagepreviewview"></a>

如果您想要顯示作用中攝影機裝置的預覽，請將裝置的預覽 `IVSImagePreviewView` 新增至您的視圖層次結構中：

```
// If the session was just created, execute the following 
// code in the callback of IVSBroadcastSession.awaitDeviceChanges 
// to ensure all devices have been attached.
if let devicePreview = try broadcastSession.listAttachedDevices()
   .compactMap({ $0 as? IVSImageDevice })
   .first?
   .previewView()
{
   previewView.addSubview(devicePreview)
}
```

## 開始廣播
<a name="broadcast-ios-start"></a>

您在 `GetChannel` 操作的 `ingestEndpoint` 回應欄位中收到的主機名稱需具有 `rtmps://` 前綴和 `/app` 後綴。完整的 URL 格式如下：`rtmps://{{ ingestEndpoint }}/app`

```
try broadcastSession.start(with: IVS_RTMPS_URL, streamKey: IVS_STREAMKEY)
```

 iOS 廣播 SDK 僅支援 RTMPS 擷取 (非不安全的 RTMP 擷取)。

## 停止廣播
<a name="broadcast-ios-stop"></a>

```
broadcastSession.stop()
```

## 管理生命週期事件
<a name="broadcast-ios-lifecycle-events"></a>

### 音訊中斷
<a name="broadcast-ios-audio-interruptions"></a>

在幾種情境下，廣播開發套件將不具有音訊輸入硬體的專屬存取權。您需要處理的一些範例情境如下：
+ 使用者接聽電話或 FaceTime 通話
+ 使用者啟用 Siri

Apple 可讓您訂閱 `AVAudioSession.interruptionNotification` 來輕鬆回應這些事件：

```
NotificationCenter.default.addObserver(
   self,
   selector: #selector(audioSessionInterrupted(_:)),
   name: AVAudioSession.interruptionNotification,
   object: nil)
```

然後，您可以仿照以下作法處理事件：

```
// This assumes you have a variable `isRunning` which tracks if the broadcast is currently live, and another variable `wasRunningBeforeInterruption` which tracks whether the broadcast was active before this interruption to determine if it should resume after the interruption has ended.

@objc
private func audioSessionInterrupted(_ notification: Notification) {
   guard let userInfo = notification.userInfo,
         let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
         let type = AVAudioSession.InterruptionType(rawValue: typeValue)
   else {
      return
   }
   switch type {
   case .began:
      wasRunningBeforeInterruption = isRunning
      if isRunning {
         broadcastSession.stop()
      }
   case .ended:
      defer {
         wasRunningBeforeInterruption = false
      }
      guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
      let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
      if options.contains(.shouldResume) && wasRunningBeforeInterruption {
         try broadcastSession.start(
            with: IVS_RTMPS_URL,
            streamKey: IVS_STREAMKEY)
      }
   @unknown default: break
   }
}
```

### 應用程式進入背景
<a name="broadcast-ios-app-to-background"></a>

iOS 上的標準應用程式不允許在背景中使用攝影機。背景中的影片編碼也有限制：由於硬體編碼器有限，前景應用程式才有存取權。因此，廣播開發套件會自動終止其工作階段，並將其 `isReady` 屬性設為 `false`。當您的應用程式即將再次進入前景時，廣播開發套件會將所有裝置重新連接到其原始 `IVSMixerSlotConfiguration` 項目。

廣播開發套件的作法是回應 `UIApplication.didEnterBackgroundNotification` 和 `UIApplication.willEnterForegroundNotification`。

如果您提供自訂影像來源，必須準備好處理這些通知。您可能需要採取額外的步驟，才能在串流終止前妥善處理。

請參閱[使用背景影片](broadcast-ios-use-cases.md#broadcast-ios-background-video)以取得在應用程式位於背景時啟用串流的解決方法。

### 媒體服務遺失
<a name="broadcast-ios-media-services-lost"></a>

在極少數情況下，iOS 裝置上的整個媒體子系統將會當機。此時無法再進行廣播。您的應用程式必須適當地回應這些通知。請至少訂閱這些通知：
+ [mediaServicesWereLostNotification](https://developer.apple.com/documentation/avfaudio/avaudiosession/1616457-mediaserviceswerelostnotificatio) — 以停止廣播並完全取消配置 `IVSBroadcastSession` 作為回應。廣播工作階段使用的所有內部元件都會失效。
+ [mediaServicesWereResetNotification](https://developer.apple.com/documentation/avfaudio/avaudiosession/1616540-mediaserviceswereresetnotificati) — 以通知使用者可再次廣播作為回應。依據您的使用案例而定，您可能會在此時再次自動開始廣播。

# IVS iOS 廣播 SDK 的進階使用案例 \$1 低延遲串流
<a name="broadcast-ios-use-cases"></a>

以下介紹一些進階使用案例，銜接上述基本設定繼續延伸。

## 建立廣播組態
<a name="broadcast-ios-create-configuration"></a>

在這裡，我們建立有兩個混音器插槽的自訂組態，可讓我們將兩個影片來源綁定到混音器。一個 (`custom`) 是全螢幕，並配置於另一個 (`camera`) 後方，前方的插槽較小，位於右下角。請注意，對於 `custom` 插槽，我們並不設定位置、大小或長寬比模式。因為我們未設定這些參數，插槽將使用影片設定的大小和位置。

```
let config = IVSBroadcastConfiguration()
try config.audio.setBitrate(128_000)
try config.video.setMaxBitrate(3_500_000)
try config.video.setMinBitrate(500_000)
try config.video.setInitialBitrate(1_500_000)
try config.video.setSize(CGSize(width: 1280, height: 720))
config.video.defaultAspectMode = .fit
config.mixer.slots = [
    try {
        let slot = IVSMixerSlotConfiguration()
        // Do not automatically bind to a source
        slot.preferredAudioInput = .unknown
        // Bind to user image if unbound
        slot.preferredVideoInput = .userImage
        try slot.setName("custom")
        return slot
    }(),
    try {
        let slot = IVSMixerSlotConfiguration()
        slot.zIndex = 1
        slot.aspect = .fill
        slot.size = CGSize(width: 300, height: 300)
        slot.position = CGPoint(x: config.video.size.width - 400, y: config.video.size.height - 400)
        try slot.setName("camera")
        return slot
    }()
]
```

## 建立廣播工作階段 (進階版)
<a name="broadcast-ios-create-session-advanced"></a>

按照您在[基本範例](broadcast-ios-getting-started.md#broadcast-ios-create-session)中的作法建立 `IVSBroadcastSession`，但在此提供自訂組態。同時為裝置陣列提供 `nil`，因為我們將手動新增。

```
let broadcastSession = try IVSBroadcastSession(
   configuration: config, // The configuration we created above
   descriptors: nil, // We’ll manually attach devices after
   delegate: self)
```

## 逐一查看和連接攝影機裝置
<a name="broadcast-ios-attach-camera"></a>

在這裡，我們逐一查看開發套件偵測到的輸入裝置。開發套件只會傳回 iOS 上的內建裝置。即使藍牙音訊裝置已連線，它們也會顯示為內建裝置。如需詳細資訊，請參閱[IVS iOS 廣播 SDK 中的已知問題和解決方法 \$1 低延遲串流](broadcast-ios-issues.md)。

一旦我們找到要使用的裝置，就呼叫 `attachDevice` 予以連接：

```
let frontCamera = IVSBroadcastSession.listAvailableDevices()
    .filter { $0.type == .camera && $0.position == .front }
    .first
if let camera = frontCamera {
    broadcastSession.attach(camera, toSlotWithName: "camera") { device, error in
        // check error
    }
}
```

## 交換攝影機
<a name="broadcast-ios-swap-cameras"></a>

```
// This assumes you’ve kept a reference called `currentCamera` that points to the current camera.
let wants: IVSDevicePosition = (currentCamera.descriptor().position == .front) ? .back : .front
// Remove the current preview view since the device will be changing.
previewView.subviews.forEach { $0.removeFromSuperview() }
let foundCamera = IVSBroadcastSession
        .listAvailableDevices()
        .first { $0.type == .camera && $0.position == wants }
guard let newCamera = foundCamera else { return }
broadcastSession.exchangeOldDevice(currentCamera, withNewDevice: newCamera) { newDevice, _ in
    currentCamera = newDevice
    if let camera = newDevice as? IVSImageDevice {
        do {
            previewView.addSubview(try finalCamera.previewView())
        } catch {
            print("Error creating preview view \(error)")
        }
    }
}
```

## 建立自訂輸入來源
<a name="broadcast-ios-create-input-source"></a>

若要輸入應用程式產生的聲音或影像資料，請使用 `createImageSource` 或 `createAudioSource`。這兩種方法都會建立像其他任何裝置一樣可以繫結至混音器的虛擬裝置 (`IVSCustomImageSource` 和 `IVSCustomAudioSource`)。

這兩種方法所傳回的裝置都會透過 `onSampleBuffer` 函數接受 `CMSampleBuffer`：
+ 對於影片來源，像素格式必須是 `kCVPixelFormatType_32BGRA`、`420YpCbCr8BiPlanarFullRange` 或 `420YpCbCr8BiPlanarVideoRange`。
+ 對於音訊來源，緩衝區必須包含線性 PCM 資料。

您無法使用 `AVCaptureSession` 搭配攝影機輸入以提供自訂影像來源，同時也使用廣播開發套件提供的攝影機裝置。如果您想同時使用多部攝影機，請使用 `AVCaptureMultiCamSession` 並提供兩個自訂影像來源。

自訂影像來源主要應與靜態內容 (例如影像) 或影片內容搭配使用：

```
let customImageSource = broadcastSession.createImageSource(withName: "video")
try broadcastSession.attach(customImageSource, toSlotWithName: "custom")
```

## 監控網路連線
<a name="broadcast-ios-network-connection"></a>

行動裝置通常會在外出時暫時中斷網路連線，隨後重新連上網路。因此，重要的是監控您應用程式的網路連線，並於情況變更時適當地回應。

當廣播者的連線中斷時，廣播開發套件的狀態會依序變更為 `error` 和 `disconnected`。您將透過 `IVSBroadcastSessionDelegate` 收到這些變更的通知。當您收到這些狀態變更時：

1. 監控廣播應用程式的連線狀態，並於連線恢復時以您的端點和串流金鑰呼叫 `start`。

1. **重要：**監控狀態委派回呼，並確保再次呼叫 `start` 後狀態變更為 `connected`。

## 分離裝置
<a name="broadcast-ios-detach-device"></a>

如果您要分離 (而不是取代) 裝置，請使用 `IVSDevice` 或 `IVSDeviceDescriptor`：

```
broadcastSession.detachDevice(currentCamera)
```

## ReplayKit 整合
<a name="broadcast-ios-replaykit"></a>

若要在 iOS 上串流裝置的螢幕和系統音訊，您必須與 [ReplayKit](https://developer.apple.com/documentation/replaykit?language=objc) 整合。Amazon IVS 廣播開發套件可讓您輕鬆使用 `IVSReplayKitBroadcastSession` 與 ReplayKit 整合。在您的 `RPBroadcastSampleHandler` 子類別中，建立 `IVSReplayKitBroadcastSession` 的執行個體，然後：
+ 在 `broadcastStarted` 中啟動工作階段
+ 在 `broadcastFinished` 中停止工作階段

工作階段物件會有螢幕影像、應用程式音訊和麥克風音訊的三個自訂來源。將 `processSampleBuffer` 中提供的 `CMSampleBuffers` 傳遞給這些自訂來源。

若要處理裝置方向，您必須從範例緩衝區擷取 ReplayKit 專屬中繼資料。使用下列程式碼：

```
let imageSource = session.systemImageSource;
if let orientationAttachment = CMGetAttachment(sampleBuffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil) as? NSNumber,
    let orientation = CGImagePropertyOrientation(rawValue: orientationAttachment.uint32Value) {
    switch orientation {
    case .up, .upMirrored:
        imageSource.setHandsetRotation(0)
    case .down, .downMirrored:
        imageSource.setHandsetRotation(Float.pi)
    case .right, .rightMirrored:
        imageSource.setHandsetRotation(-(Float.pi / 2))
    case .left, .leftMirrored:
        imageSource.setHandsetRotation((Float.pi / 2))
    }
}
```

您可以使用 `IVSBroadcastSession` 取代 `IVSReplayKitBroadcastSession` 來整合 ReplayKit。不過，ReplayKit 專屬變數有多項為了減少內部記憶體使用量所做的修改，以保持不超過 Apple 的廣播延伸記憶體限制。

## 取得建議的廣播設定
<a name="broadcast-ios-recommended-settings"></a>

若要在開始廣播之前評估使用者的連線，請使用 `IVSBroadcastSession.recommendedVideoSettings` 來執行簡短的測試。在測試執行時，您會收到數個建議，依建議強烈程度 (從高到低) 排序。在此版本的開發套件中，您無法重新設定目前的 `IVSBroadcastSession`，所以需要將其重新配置，然後使用建議的設定重新建立。您將持續收到 `IVSBroadcastSessionTestResults`，直到 `result.status` 為 `Success` 或 `Error` 為止。您可以使用 `result.progress` 檢查進度。

Amazon IVS 支援的最大位元速率為 8.5 Mbps (適用於 `type` 為 `STANDARD` 或 `ADVANCED` 的頻道)，所以此方法傳回的 `maximumBitrate` 絕不會超過 8.5 Mbps。由於網路效能可能有微小波動，此方法傳回之建議的 `initialBitrate` 會稍微小於測試中測得的真實位元速率。(通常不建議使用 100% 的可用頻寬)。

```
func runBroadcastTest() {
    self.test = session.recommendedVideoSettings(with: IVS_RTMPS_URL, streamKey: IVS_STREAMKEY) { [weak self] result in
        if result.status == .success {
            self?.recommendation = result.recommendations[0];
        }
    }
}
```

## 使用自動重新連線
<a name="broadcast-ios-auto-reconnect"></a>

如果廣播在未呼叫 `stop` API 的情況下意外停止，IVS 會支援自動重新連線至廣播 (例如，網路連線暫時中斷)。若要啟用自動重新連線，請將 `IVSBroadcastConfiguration.autoReconnect` 上的 `enabled` 屬性設定為 `true`。

當某些情況導致串流意外停止時，SDK 會按照線性退避策略重試最多 5 次。SDK 會透過 `IVSBroadcastSessionDelegate.didChangeRetryState` 函數通知您的應用程式重試狀態的相關資訊。

自動重新連線會在背景透過將優先順序號碼 (從 1 開始) 附加至所提供之串流金鑰的結尾，以使用 IVS [串流接收](streaming-config.md#streaming-config-stream-takeover)功能。在 `IVSBroadcastSession` 執行個體的持續時間內，每次嘗試重新連線時，該數字會遞增 1。這表示如果裝置在廣播期間中斷連線 4 次，且每次中斷皆需要重試 1 到 4 次，則上次串流上傳的優先順序可能介於 5 到 17 之間。因此，*我們建議您不要在 SDK 中針對相同頻道啟用自動重新連線時，使用其他裝置的 IVS 串流接管*。我們無法保證 SDK 目前使用的優先順序，如果其他裝置接管，SDK 將會嘗試以更高的優先順序重新連線。

## 使用背景影片
<a name="broadcast-ios-background-video"></a>

您可以繼續進行非 RelayKit 廣播，即使您的應用程式在背景中也是如此。

為了節省電力並保持前景應用程式有回應，iOS 一次只允許一個應用程式存取 GPU。Amazon IVS 廣播 SDK 在影片管道的多個階段使用 GPU，包括合成多個輸入來源、擴展影像以及編碼影像。雖然廣播應用程式位於背景，但無法保證開發套件可以執行上述任何動作。

若要解決此問題，請使用 `createAppBackgroundImageSource` 方法。它可讓開發套件在背景中繼續廣播影片和音訊。它會傳回 `IVSBackgroundImageSource`，這是一個正常的 `IVSCustomImageSource`，其中包含其他 `finish` 函數。提供給背景影像來源的每個 `CMSampleBuffer` 都會以原始 `IVSVideoConfiguration` 提供的影格速率編碼。`CMSampleBuffer` 上的時間戳記會遭到忽略。

接著，開發套件會擴展和編碼這些影像並快取它們，以便在您的應用程式進入背景時自動循環該摘要。當您的應用程式回到前景時，附加的影像裝置會再次變成作用中，而且預先編碼的串流會停止循環。

若要復原此程序，請使用 `removeImageSourceOnAppBackgrounded`。只有在您想要明確地還原開發套件的背景行為時，才需要呼叫此方法，否則，它會在取消配置 `IVSBroadcastSession` 時自動清除。

**備註：***強烈建議您在設定廣播工作階段時呼叫此方法，工作階段才能上線。*此方法的成本高昂 (它會將影片編碼)，因此在此方法執行時直播的效能可能會降低。

### 範例：為背景影片產生靜態影像
<a name="background-video-example-static-image"></a>

將單一影像提供給背景來源會產生該靜態影像的完整 GOP。

以下為使用 CIImage 的範例：

```
// Create the background image source
guard let source = session.createAppBackgroundImageSource(withAttemptTrim: true, onComplete: { error in
    print("Background Video Generation Done - Error: \(error.debugDescription)")
}) else {
    return
}

// Create a CIImage of the color red.
let ciImage = CIImage(color: .red)

// Convert the CIImage to a CVPixelBuffer
let attrs = [
    kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue,
] as CFDictionary

var pixelBuffer: CVPixelBuffer!
CVPixelBufferCreate(kCFAllocatorDefault,
                    videoConfig.width,
                    videoConfig.height,
                    kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
                    attrs,
                    &pixelBuffer)

let context = CIContext()
context.render(ciImage, to: pixelBuffer)

// Submit to CVPixelBuffer and finish the source
source.add(pixelBuffer)
source.finish()
```

或者，您可以使用綁定的影像，而不是建立純色的 CIImage。這裡顯示的唯一程式碼是如何將 UIImage 轉換為 CIImage 以便搭配上一個範例使用：

```
// Load the pre-bundled image and get it’s CGImage
guard let cgImage = UIImage(named: "image")?.cgImage else {
    return
}

// Create a CIImage from the CGImage
let ciImage = CIImage(cgImage: cgImage)
```

### 範例：具有 AVAssetImageGenerator 的影片
<a name="background-video-example-avassetimagegenerator"></a>

您可以使用 `AVAssetImageGenerator` 從 `AVAsset` (儘管不是 HLS 串流 `AVAsset`) 產生 `CMSampleBuffers`：

```
// Create the background image source
guard let source = session.createAppBackgroundImageSource(withAttemptTrim: true, onComplete: { error in
    print("Background Video Generation Done - Error: \(error.debugDescription)")
}) else {
    return
}

// Find the URL for the pre-bundled MP4 file
guard let url = Bundle.main.url(forResource: "sample-clip", withExtension: "mp4") else {
    return
}
// Create an image generator from an asset created from the URL.
let generator = AVAssetImageGenerator(asset: AVAsset(url: url))
// It is important to specify a very small time tolerance.
generator.requestedTimeToleranceAfter = .zero
generator.requestedTimeToleranceBefore = .zero

// At 30 fps, this will generate 4 seconds worth of samples.
let times: [NSValue] = (0...120).map { NSValue(time: CMTime(value: $0, timescale: CMTimeScale(config.video.targetFramerate))) }
var completed = 0

let context = CIContext(options: [.workingColorSpace: NSNull()])

// Create a pixel buffer pool to efficiently feed the source
let attrs = [
    kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
    kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferWidthKey: videoConfig.width,
    kCVPixelBufferHeightKey: videoConfig.height,
] as CFDictionary
var pool: CVPixelBufferPool!
CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, attrs, &pool)

generator.generateCGImagesAsynchronously(forTimes: times) { requestTime, image, actualTime, result, error in
    if let image = image {
        // convert to CIImage then CVpixelBuffer
        let ciImage = CIImage(cgImage: image)
        var pixelBuffer: CVPixelBuffer!
        CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer)
        context.render(ciImage, to: pixelBuffer)
        source.add(pixelBuffer)
    }
    completed += 1
    if completed == times.count {
        // Mark the source finished when all images have been processed
        source.finish()
    }
}
```

使用 `AVPlayer` 和 `AVPlayerItemVideoOutput` 可以產生 `CVPixelBuffers`。但是，這需要使用 `CADisplayLink` 並且更接近即時執行，同時 `AVAssetImageGenerator` 可以更快地處理影格。

### 限制
<a name="background-video-limitations"></a>

您的應用程式需要[背景音訊授權](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)，以避免在進入背景後暫停。

`createAppBackgroundImageSource`只有在應用程式在前景時才能呼叫 ，因為它需要存取 GPU 才能完成。

`createAppBackgroundImageSource` 一律編碼為完整的 GOP。例如，如果您的關鍵影格間隔為 2 秒 (預設)，且以 30 fps 執行，則會編碼 60 個影格的倍數。
+ 如果提供的影格少於 60 個，則無論裁剪選項的值為何，都會重複最後一個影格，直到達到 60 個影格為止。
+ 如果提供的影格超過 60 個，且裁剪選項為 `true`，則會捨棄最後 N 個影格，其中 N 是提交的影格總數除以 60 的餘數。
+ 如果提供的影格超過 60 個，且裁剪選項為 `false`，則會重複最後一個影格，直到達到 60 個影格的下一個倍數為止。

# IVS iOS 廣播 SDK 中的已知問題和解決方法 \$1 低延遲串流
<a name="broadcast-ios-issues"></a>

本文件列出您在使用 Amazon IVS 低延遲串流功能 iOS 廣播 SDK 時可能遇到的已知問題，並建議潛在的解決方法。
+ 在串流期間插入有線耳機時，ReplayKit 中的錯誤會導致記憶體快速耗用。

  **解決方法：**在已插入有線耳機情況下開始串流、使用藍牙耳機，或不要使用外接麥克風。
+ 如果您在 ReplayKit 串流期間啟用麥克風，然後中斷音訊工作階段 (例如進行通話或啟用 Siri)，系統音訊將會停止運作。我們正在與 Apple 合作解決這項 ReplayKit 錯誤。

  **解決方法：**在音訊中斷時，停止廣播並提醒使用者。
+ 如果 `AVAudioSession` 類別設為 `record`，AirPods 不會錄製任何音訊。根據預設，開發套件會使用 `playAndRecord`，所以只有在類別變更為 `record` 時才會發生此問題。

  **解決方法：**如果有機會使用 AirPods 錄製音訊，即使您的應用程式未播放媒體，亦請使用 `playAndRecord`。
+ 在 AirPods 已連接至 iOS 12 裝置時，無法使用其他麥克風來錄製音訊。嘗試切換至內部麥克風會立即還原回到 AirPods。

  **解決方法**：無。如果 AirPods 已連接至 iOS 12，則其為唯一可用來錄製音訊的裝置。
+ 音訊資料提交速度比即時更快 (使用自訂音訊來源) 會導致音訊漂移。

  **解決方法：**請勿讓音訊資料提交速度比即時更快。
+ 使用高取樣率 (44100 Hz 或更高) 和兩個聲道時，音訊成品會以低於 68 kbps 的位元速率顯示。

  **解決方法：**將位元速率提高至 68 kbps 或更高、將取樣率降低至 24000 Hz 或更低，或將聲道設定為 1。
+ 在 `IVSMicrophone` 裝置上啟用回音消除時，`listAvailableInputSources` 方法僅會傳回單一麥克風來源。

  **解決方法**：無。此行為由 iOS 控制。
+ 變更藍牙音訊路由可能無法預測。如果您在工作階段中連接新裝置，iOS 可能會自動變更輸入路由。此外，您無法在同一時間連接的多個藍牙耳機之間進行選擇。這會出現在一般廣播和階段工作階段中。

  **解決方法：** 如果您打算使用藍牙耳機，請在開始廣播或舞台之前先連接耳機，並在整個工作階段保持連線狀態。
+ 在開啟配對的 AirPods 充電盒後 AirPods 快顯出現時，iOS 會移除對攝影機的存取，而將 AirPods 本身留在充電盒中。這會導致影片廣播或階段凍結。

  **解決方法：**無。iOS 在呈現快顯時完全撤銷攝影機存取，而第三方應用程式不可能阻止快顯。
+ 啟用 B 影格可以改善壓縮品質；不過，某些編碼器在啟用 B 影格時提供的位元速率控制較不精確，這可能會在網路波動期間導致問題。

  **解決方法：**若在您的使用案例中，遵循一致的位元速率比壓縮效率更重要，請考慮停用 B 影格。