タイトルの通り、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
に変換できることは確認できた。
以下にコードを示す。
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()
}
}
結果

上がHDR白飛び、下がデコードした画像。
若干速度は遅いのでAVPlayerとしては駄目だが他の処理に画像を渡すのには使えそうだ。
ここにメモを残すことで、きっと誰かがやり方が判明した時に教えてくれるに違いない。