iOSのAVPlayerでHDRが白飛びするのを直せなかったのだが解ったことをメモしておく

タイトルの通り、iOSのAVPlayerでHDR(High Dynamic Range)を再生すると白飛びする。

WWDC2022の動画の説明を見たがHDR動画の扱い方はあるが白飛びの解消方法は解らなかった。
https://developer.apple.com/videos/play/wwdc2022/110565/

AVPlayer自体の白飛びは解消できなかったがビデオフレームにリアルタイムにアクセスしてPixelBufferを取り出してデコードすることはできた。
それでもHDRの10bitから8bitへの変換方法は不明だったがPixelFormatに`kCVPixelFormatType_420YpCbCr8BiPlanarFullRange`を指定することで
`ITU_R_2020`から`ITU_R_709_2`に変換できることは確認できた。


以下にコードを示す。

```swift
import SwiftUI
import AVKit

struct ContentView: View {
  
  @StateObject var playerView = PlayerView()
  
  var body: some View {
    VStack {
      if let player = playerView.videoPlayer {
        VideoPlayer(player: player).scaledToFit()
      }
      if let uiImage = playerView.uiImage {
        Image(uiImage: uiImage).resizable().aspectRatio(contentMode: .fit)
      }
    }
  }
}

class PlayerView: ObservableObject {
  
  @Published var videoPlayer: AVPlayer?
  @Published var uiImage: UIImage?
  var videoPlayerItem: AVPlayerItem?
  var statusObserver: NSKeyValueObservation?
  let videoOutput: AVPlayerItemVideoOutput
  
  lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector:#selector(displayLinkCopyPixelBuffers(link:)))
  
  init(){
    let url = Bundle.main.url(forResource: "IMG_0287", withExtension: "MOV")!
    let asset = AVURLAsset(url: url, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
    videoPlayerItem = AVPlayerItem(asset: asset)
    videoPlayer = AVPlayer(playerItem: videoPlayerItem)
    
//    let videoColorProperties = [
//      AVVideoColorPrimariesKey: AVVideoColorPrimaries_P3_D65,
//      AVVideoTransferFunctionKey: AVVideoTransferFunction_Linear,
//      AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_2020]
    let videoColorProperties = [
      AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
      AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709_2,
      AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_709_2]
    
    let outputVideoSettings = [
//      AVVideoAllowWideColorKey: true,
//      kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_64RGBAHalf,
      kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
      AVVideoColorPropertiesKey: videoColorProperties,
      kCVPixelBufferMetalCompatibilityKey as String: true
    ] as [String: Any]
    
    videoOutput = AVPlayerItemVideoOutput(outputSettings: outputVideoSettings)
    statusObserver = videoPlayerItem?.observe(\.status,
                                               changeHandler: { playerItem, change in
      if playerItem.status == .readyToPlay {
        playerItem.add(self.videoOutput)
        self.displayLink.add(to: .main, forMode: .common)
        self.videoPlayer?.play()
      }
    })
  }
  
  @objc func displayLinkCopyPixelBuffers(link: CADisplayLink){
    let currentTime = videoOutput.itemTime(forHostTime: CACurrentMediaTime())
    
    if videoOutput.hasNewPixelBuffer(forItemTime: currentTime){
      if let buffer = videoOutput.copyPixelBuffer(forItemTime: currentTime, itemTimeForDisplay: nil){
 
        let colorAttachments = CVBufferCopyAttachment(buffer, kCVImageBufferYCbCrMatrixKey, nil)
        let colorPrimaries = CVBufferCopyAttachment(buffer, kCVImageBufferColorPrimariesKey, nil)
        let colorTransFunc = CVBufferCopyAttachment(buffer, kCVImageBufferTransferFunctionKey, nil)
        
        print("colorAttachments: ", colorAttachments)
        print("colorPrimaries: ", colorPrimaries)
        print("colorTransFunc: ", colorTransFunc)
        
        let ciImage = CIImage(cvPixelBuffer: buffer)
        let context = CIContext(options: nil)
        let orient = ciImage.oriented(.right)
        let cgImage = context.createCGImage(orient, from: orient.extent)
        if let cgImage = cgImage {
          let uiImage = UIImage(cgImage: cgImage)
          self.uiImage = uiImage
        }
      }
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
```

## 結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35973/a0703a1a-6f71-27b4-5f52-c305608dd7bd.gif" width="200">

上がHDR白飛び、下がデコードした画像。
若干速度は遅いのでAVPlayerとしては駄目だが他の処理に画像を渡すのには使えそうだ。

ここにメモを残すことで、きっと誰かがやり方が判明した時に教えてくれるに違いない。