iOSでアルファチャンネル付きで動画を作成する

# iOS でアルファチャンネル付きで動画を作成する
* AVAssetWriter を使うと、iOS で透過情報付きの動画を作成する事が可能です
* iOS の写真アプリは透過付きの動画を再生可能です
* ツールバーの表示/非表示で背景色が変わったり、プルダウンすることで後ろの画像が見えるので、動画の背景が透過されている事がわかります

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">iOSでアルファチャンネル付きの動画を作成して保存できた✌️<br>MTLTexture にレンダリングして hevc に保存してます <a href="https://t.co/hWrjQBGxJ6" >pic.twitter.com/hWrjQBGxJ6</a></p>&mdash; ふじき (@fzkqi) <a href="https://twitter.com/fzkqi/status/1401725230360395777?ref_src=twsrc%5Etfw" >June 7, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# サンプルアプリ
* リポジトリはこちらです

https://github.com/fuziki/VideoCreator

* `Examples/NativeExamples/NativeExamples.xcodeproj` の `CrossMetal iOS` ターゲットです
* Xcode の Metal の Game アプリをベースにしています

# 透過情報付きで保存するポイント

* `AVAssetWriterInput` の生成時の settings で、`hevcWithAlpha` を指定します

```swift
let videoConfigs: [String: Any] = [AVVideoCodecKey : AVVideoCodecType.hevcWithAlpha,
                                   AVVideoWidthKey : 1920,
                                   AVVideoHeightKey :1080]
let videoAssetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoConfigs)
```

* 書き込む CMSampleBuffer の CVPixelBuffer のフォーマットとして `kCVPixelFormatType_32BGRA` を選択します

```swift
let options = [kCVPixelBufferIOSurfacePropertiesKey: [:]] as [String : Any]            
let status = CVPixelBufferCreate(nil, width, height,
                                 kCVPixelFormatType_32BGRA,
                                 options as CFDictionary,
                                 &pixelBuffer)
```

* 透過情報付きの画像データを生成し、AVAssetWriterInput に書き込むことで、アルファチャンネル付きの動画を取得可能です

# 実装
## 録画開始

* ファイルタイプは mov を指定しました
* hevcWithAlpha が保存できるコンテナであれば良いと思います

```swift
let assetWriter = try! AVAssetWriter(outputURL: url, fileType: AVFileType.mov)

let videoConfigs: [String: Any] = [AVVideoCodecKey : AVVideoCodecType.hevcWithAlpha,
                                   AVVideoWidthKey : 1920,
                                   AVVideoHeightKey : 1080]
let videoAssetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoConfigs)
videoAssetWriterInput.expectsMediaDataInRealTime = true
assetWriter.add(videoAssetWriterInput)
```

* 元の画像を Metal でレンダリングしているので、MTLTexture と描画時刻を受け取ります
* MTLTexture -> CIImage -> CVPixelBuffer -> CMSampleBuffer の順番に変換します

```swift
func make(mtlTexture: MTLTexture, time: CMTime) -> CMSampleBuffer? {
    let ci = CIImage(mtlTexture: mtlTexture, options: nil)!
    CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
    context.render(ci, to: pixelBuffer)
    var res: CMSampleBuffer? = nil
    var sampleTiming = CMSampleTimingInfo()
    sampleTiming.presentationTimeStamp = time
    let _ = CMSampleBufferCreateForImageBuffer(allocator: kCFAllocatorDefault,
                                               imageBuffer: pixelBuffer,
                                               dataReady: true,
                                               makeDataReadyCallback: nil,
                                               refcon: nil,
                                               formatDescription: formatDescription,
                                               sampleTiming: &sampleTiming,
                                               sampleBufferOut: &res)
    CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
    return res
}
```

* 作成した CMSampleBuffer を videoAssetWriterInput に書き込みます

```swift
videoAssetWriterInput.append(sample)
```

## 録画終了

* finishWriting することで mov ファイルが作成されています

```swift
assetWriter.finishWriting(completionHandler: { })
```

## 保存

* 指定した url に動画が保存されているので、必要に応じて保存します
* ALAssetsLibrary は deprecated なので Photos framework を使った方が良さそうです

```swift
ALAssetsLibrary()
    .writeVideoAtPath(toSavedPhotosAlbum: url) { (url: URL?, error: Error?) in
        print("url: \(String(describing: url)), error: \(String(describing: error))")
    }
```

# おわりに
* アルファチャンネル付きの動画を保存可能になると、汎用性が高そうです
* 例えば、LiDAR のデプスをアルファチャンネルに入れて保存するなど活用できそうです
* Unity でも R8G8B8A8_UNorm など、alpha チャンネルがあるテクスチャを渡すことで、透過情報付きの動作を作成する事が可能です