

# IVS iOS Broadcast SDK の高度なユースケース \$1 Low-Latency Streaming
<a name="broadcast-ios-use-cases"></a>

ここでは、いくつかの高度なユースケースを紹介します。上記の基本的な設定から始まり、ここまで進んできました。

## ブロードキャスト設定の作成
<a name="broadcast-ios-create-configuration"></a>

2 つのミキサースロットを持つカスタム設定を作成し、2 つの動画ソースをミキサーにバインドできます。1 つの画面 (`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>

SDK が検出した入力デバイスを繰り返し処理します。SDK が iOS の組み込みデバイスだけを返します。Bluetooth オーディオデバイスが接続されていても、内蔵デバイスとして表示されます。詳細については、「[IVS iOS Broadcast SDK の既知の問題と回避策 \$1 Low-Latency Streaming](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 データが含まれている必要があります。

Broadcast SDK が提供するカメラデバイスを使用しているときに、カメラ入力で `AVCaptureSession` を使用してカスタム画像ソースをフィードすることはできません。複数のカメラを同時に使用する場合は、`AVCaptureMultiCamSession` を使用して 2 つのカスタムイメージソースを提供します。

カスタム画像ソースは、主に画像などの静的コンテンツや動画コンテンツで使用する必要があります。

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

## ネットワーク接続をモニタリングする
<a name="broadcast-ios-network-connection"></a>

モバイルデバイスは、外出中に一時的にネットワーク接続を失ったり、回復したりするのが普通です。このため、アプリのネットワーク接続をモニタリングし、状況が変化したときに適切に対応することが重要です。

ブロードキャスターの接続が切断されると、Broadcast SDK の状態は `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 Broadcast SDK を使用すれば、`IVSReplayKitBroadcastSession` で ReplayKit を簡単に統合できます。`RPBroadcastSampleHandler` サブクラスで `IVSReplayKitBroadcastSession` のインスタンスを作成してから、次を行います。
+ `broadcastStarted` でセッションを開始する
+ `broadcastFinished` でセッションを停止する

セッションオブジェクトには、画面の画像、アプリオーディオ、マイクオーディオ用の 3 つのカスタムソースがあります。`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))
    }
}
```

`IVSReplayKitBroadcastSession` の代わりに `IVSBroadcastSession` を使用して ReplayKit を統合することが可能です。しかし、ReplayKit 固有のバリアントには、ブロードキャスト拡張のために Apple のメモリ上限内に留まるように、内部メモリフットプリントを減らすためのいくつかの変更が加えられています。

## 推奨されるブロードキャスト設定の取得
<a name="broadcast-ios-recommended-settings"></a>

ブロードキャストを開始する前にユーザーの接続を評価するには、`IVSBroadcastSession.recommendedVideoSettings` を使用して簡単なテストを実行します。テストが実行されると、最も推奨されるものから順に、さまざまな推奨事項が表示されます。このバージョンの SDK では、現在の `IVSBroadcastSession` を再設定することができないため、割り当てを解除してから、推奨設定で新しい設定を作成する必要があります。`result.status` が `Success` または `Error` になるまで、`IVSBroadcastSessionTestResults` を受け取り続けます。進捗状況を確認するには `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];
        }
    }
}
```

## Auto-Reconnect の使用
<a name="broadcast-ios-auto-reconnect"></a>

IVS は、ネットワーク接続が一時的に失われた場合など、ブロードキャストが `stop` API を呼び出すことなく予期せず停止した場合に、ブロードキャストへの自動再接続をサポートします。自動再接続を有効にするには、`IVSBroadcastConfiguration.autoReconnect` の `enabled` プロパティを `true` に設定します。

何らかの原因でストリームが予期せず停止した場合、SDK は線形バックオフ戦略に従って最大 5 回再試行します。`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 に一度に 1 つのアプリケーションしかアクセスできません。Amazon IVS Broadcast SDK は、複数の入力ソースの合成、イメージのスケーリング、イメージのエンコードなど、ビデオパイプラインの複数のステージで GPU を使用します。ブロードキャストアプリケーションがバックグラウンドにある間は、SDK がこれらのアクションを実行できるという保証はありません。

これに対処するには、`createAppBackgroundImageSource` 方法を使用します。これにより、SDK はバックグラウンドでビデオとオーディオの両方をブロードキャストし続けることができます。`IVSBackgroundImageSource` を返します。これは、追加の `IVSCustomImageSource` 関数を持つ通常の `finish` です。バックグラウンドイメージソースに提供されるすべての `CMSampleBuffer` は、元の `IVSVideoConfiguration` によって提供されるフレームレートでエンコードされます。`CMSampleBuffer` のタイムスタンプは無視されます。

SDK は、これらのイメージをスケーリングしてエンコードし、キャッシュし、アプリケーションがバックグラウンドになったときにそのフィードを自動的にループします。アプリケーションがフォアグラウンドに戻ると、アタッチされたイメージデバイスが再びアクティブになり、事前エンコードされたストリームはループを停止します。

このプロセスを元に戻すには、`removeImageSourceOnAppBackgrounded` を使用します。SDK のバックグラウンド動作を明示的に元に戻す場合を除き、これを呼び出す必要はありません。そうしないと、`IVSBroadcastSession` の割り当て解除時に自動的にクリーンアップされます。

**メモ:** *セッションがライブになる前に、ブロードキャストセッションの設定の一環として、このメソッドを呼び出すことを強くお勧めします。*この方法は高価である (ビデオをエンコードする) ため、このメソッドの実行中のライブブロードキャストのパフォーマンスが低下する可能性があります。

### 例:バックグラウンドビデオのスタティックイメージの生成
<a name="background-video-example-static-image"></a>

バックグラウンドソースに 1 つのイメージを指定すると、その静的イメージの完全な 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` を使用して `CMSampleBuffers` から `AVAsset` を生成できます (ただし、HLS ストリーム `AVAsset` ではありません)。

```
// 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()
    }
}
```

`CVPixelBuffers` および `AVPlayer` を使用して `AVPlayerItemVideoOutput` を生成することは可能です。ただし、これには `CADisplayLink` を使用する必要があり、リアルタイムに近い方法で実行されますが、`AVAssetImageGenerator` はフレームをはるかに高速に処理できます。

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

バックグラウンドに入った後に中断されないようにするために、アプリケーションには、[バックグラウンドオーディオエンタイトルメント](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)が必要です。

アプリケーションが完了するには GPU にアクセスする必要があるため、`createAppBackgroundImageSource` は、アプリケーションがフォアグラウンドにあるときにのみ呼び出すことができます。

`createAppBackgroundImageSource` は常に完全な GOP にエンコードします。たとえば、キーフレーム間隔が 2 秒 (デフォルト) で 30 fps で実行されている場合、60 フレームの倍数をエンコードします。
+ 60 フレーム未満を指定すると、トリムオプションの値に関係なく、60 フレームに達するまで最後のフレームが繰り返されます。
+ 60 以上のフレームが提供され、トリムオプションが `true` の場合、最後の N フレームがドロップされます。ここで、N は送信されたフレームの総数の残りを 60 で割った値です。
+ 60 以上のフレームが提供され、トリムオプションが `false` の場合、次の 60 フレームの倍数に達するまで最後のフレームが繰り返されます。