ReplayKit の RPScreenRecorder#startCapture を使ってアプリをキャプチャする

iOS9でReplayKitが登場し、iOS10でReplayKitにライブ機能が追加されました。
そしてiOS11で ReplayKit Live に対応したアプリでiPhoneの画面自体をキャプチャできるようになるなど新たな機能が追加されました。
<img width="160" alt="IMG_5975.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/14751/d5c8280b-dc5a-a833-dc01-976e8c78e050.png">

今回はiOS11で追加された `RPScreenRecorder#startCapture` を触ってみたのでメモしていきます。
`AVPlayer` と `Metal`、 `SpriteKit` をキャプチャし動画に書き出すサンプルを作ってみたので興味があれば見てみてください。
https://github.com/yoshimin/ReplayKitSample
<img width="160" alt="IMG_5979.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/14751/df3711b9-8bbc-e351-fbb4-73362fa0835f.png"> <img width="160" alt="IMG_5984.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/14751/7208d8e7-c4b0-d954-4534-55231a745afa.png">
※左がMetalで右がSpriteKit




### [RPScreenRecorder](https://developer.apple.com/documentation/replaykit/rpscreenrecorder)とは
`RPScreenRecorder` 自体はiOS9で登場したアプリ画面をキャプチャしてくれるクラスです。
ただ、iOS9で登場した機能は

```
func startRecording(handler: ((Error?) -> Void)? = nil)
```
により画面のキャプチャを開始し

```
func stopRecording(handler: ((RPPreviewViewController?, Error?) -> Void)? = nil)
```
によりキャプチャを終了すると、 `RPPreviewViewController` というキャプチャした動画を再生するためのViewControllerが渡されるので、それを表示することでユーザーが動画の確認やカメラロールへの保存、シェアなどができるというものでした。
実装はシンプルでかんたんなのですが、プログラム側で `RPScreenRecorder` 録画された動画を取得することはできず使いみちは限られていました。

それが今回iOS11で `RPScreenRecorder` でキャプチャした映像をリアルタイムに受け取ることができるようになりました。

### [PScreenRecorder#startCapture](https://developer.apple.com/documentation/replaykit/rpscreenrecorder/2867291-startcapture)の使い方
ではどうやってキャプチャするかというと

```
func startCapture(handler captureHandler: ((CMSampleBuffer, RPSampleBufferType, Error?) -> Void)?, completionHandler: ((Error?) -> Void)? = nil)
```
で開始して

```
func stopCapture(handler: ((Error?) -> Void)? = nil)
```
で終了する。
これだけです。

`startCapture` でキャプチャを開始すると第二引数のコールバックが呼ばれます。
もし開始に失敗した場合はここで `Error` が返ってきます。
第一引数の `captureHandler` はキャプチャ中継続的に呼ばれ、ここで `CMSampleBuffer` が返ってくるのであとはこれを好きに扱うことができます。
今回作ったサンプルのように動画に書き出す場合、 返された `CMSampleBuffer` が音声データなのか映像データなのかを判別する必要がありますが、それは `captureHandler` の第二引数である
 [RPSampleBufferType](https://developer.apple.com/documentation/replaykit/rpsamplebuffertype) を使います。
`RPSampleBufferType` は以下のように3つのタイプがあり、 `.video` なら映像データ、 `.audioApp` ならアプリ音声と判別ができます。

```
public enum RPSampleBufferType : Int {
    case video
    case audioApp
    case audioMic
}
```
3つめに `audioMic` というのがありますが、マイク音声も取れます。
マイク音声を録音するには `startCapture` する前に

```
RPScreenRecorder.shared().isMicrophoneEnabled = true
```
を１行書いてあげればOKです。
ただし、音声が録音できるのは `isMicrophoneEnabled` を `true` にした状態で `startCapture` すると、以下のようなダイアログが表示されるので、ユーザーが「画面とマイクを収録」を選んでくれた場合のみです。
<img width="160" alt="IMG_5999.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/14751/53fa4378-c060-0607-8d44-3f651b9d85a4.png">



### 使ってみて
使い方は簡単だし、 `CMSampleBuffer` があれば使い方は開発者の自由なので実装の幅が広がりそうかなと思いました。
`UIView` だけでなく `Metal` や `SpriteKit` の画面も簡単にキャプチャできるのでゲーム配信とかライトに作れそう。（SpriteKit使ってゲーム作る人は少なそうですが）
使ってみて気づいたのは、サイレントモード中の消音や、端末のボリュームの上げ下げはそのままキャプチャに反映されます（ただしボリュームを上げ下げしたときのUIまでは映り込みません）。
アプリの画面上に映るものはキャプチャされてしまうのでサンプルアプリも「録画/停止」ボタン（上のスクショを参照）も一緒にキャプチャされています。
`Metal` による描画だけをキャプチャしたいとかはできなそうです。
あと、`startCapture` したときにユーザーに「画面の収録を許可しますか？」というダイアログをOSが出してきますが、このダイアログのメッセージ部分には「収録内容はカメラロールに保存したり友達と共有したりできます。」と書いてます。
実装者がそのような機能を実装しないと保存も共有もできないのでどうゆう意図なのか気になります。
これらの機能を実装しないとリジェクトされたりするのでしょうか？

ということで `RPScreenRecorder#startCapture` を使ってみたまとめでした。