You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在iOS平台使用Swift无需自定义库或FFmpeg转换HDR视频至SDR

iOS下用Swift实现HDR到SDR的原生转换

核心依赖

全程使用iOS原生框架AVFoundation和Core Image,无需任何第三方库或FFmpeg。HDR到SDR的转换核心是色域映射(从BT.2020转BT.709/sRGB)和色调映射(压缩HDR的高亮度范围到SDR的标准范围)。

实现步骤与代码示例

1. 定义转换函数

创建异步函数处理视频转换,接收输入/输出URL,完成后回调结果:

import AVFoundation
import CoreImage

func convertHDRtoSDR(inputURL: URL, outputURL: URL, completion: @escaping (Error?) -> Void) {
    let asset = AVAsset(url: inputURL)
    guard let videoTrack = asset.tracks(withMediaType: .video).first else {
        completion(NSError(domain: "HDRConverter", code: -1, userInfo: [NSLocalizedDescriptionKey: "无视频轨道"]))
        return
    }
    
    // 配置AssetReader读取HDR视频帧
    let readerSettings: [String: Any] = [
        kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
        kCVPixelBufferIOSurfacePropertiesKey as String: [:]
    ]
    guard let reader = try? AVAssetReader(asset: asset),
          let videoReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: readerSettings) else {
        completion(NSError(domain: "HDRConverter", code: -2, userInfo: [NSLocalizedDescriptionKey: "创建Reader失败"]))
        return
    }
    reader.add(videoReaderOutput)
    
    // 配置AssetWriter写入SDR视频
    let outputSize = videoTrack.naturalSize.applying(videoTrack.preferredTransform)
    let videoWriterSettings: [String: Any] = [
        AVVideoCodecKey: AVVideoCodecType.h264,
        AVVideoWidthKey: outputSize.width,
        AVVideoHeightKey: outputSize.height,
        AVVideoCompressionPropertiesKey: [
            AVVideoAverageBitRateKey: 10_000_000, // 根据需求调整比特率
            AVVideoMaxKeyFrameIntervalKey: videoTrack.nominalFrameRate
        ],
        AVVideoColorPropertiesKey: [
            AVVideoColorSpaceKey: AVVideoColorSpace.sRGB,
            AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709,
            AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_709
        ]
    ]
    
    guard let writer = try? AVAssetWriter(outputURL: outputURL, fileType: .mov),
          let videoWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoWriterSettings) else {
        completion(NSError(domain: "HDRConverter", code: -3, userInfo: [NSLocalizedDescriptionKey: "创建Writer失败"]))
        return
    }
    videoWriterInput.expectsMediaDataInRealTime = false
    writer.add(videoWriterInput)
    
    // 处理音频(可选,直接拷贝原音频轨道)
    if let audioTrack = asset.tracks(withMediaType: .audio).first {
        let audioWriterInput = AVAssetWriterInput(mediaType: .audio, outputSettings: nil)
        audioWriterInput.expectsMediaDataInRealTime = false
        writer.add(audioWriterInput)
        
        let audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
        reader.add(audioReaderOutput)
        
        DispatchQueue.global().async {
            while reader.status == .reading {
                if let sampleBuffer = audioReaderOutput.copyNextSampleBuffer() {
                    while !audioWriterInput.isReadyForMoreMediaData {
                        Thread.sleep(forTimeInterval: 0.01)
                    }
                    audioWriterInput.append(sampleBuffer)
                }
            }
        }
    }
    
    // 初始化Core Image上下文(用Metal加速)
    let ciContext = CIContext(options: [
        .useSoftwareRenderer: false,
        .priorityRequestLowPower: false
    ])
    
    // 开始读写
    reader.startReading()
    writer.startWriting()
    writer.startSession(atSourceTime: .zero)
    
    let processingQueue = DispatchQueue(label: "com.hdrconverter.processing")
    videoWriterInput.requestMediaDataWhenReady(on: processingQueue) {
        while videoWriterInput.isReadyForMoreMediaData {
            guard reader.status == .reading,
                  let sampleBuffer = videoReaderOutput.copyNextSampleBuffer(),
                  let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
                if reader.status == .completed {
                    videoWriterInput.markAsFinished()
                    writer.finishWriting {
                        completion(writer.error)
                    }
                } else if reader.status == .failed {
                    completion(reader.error)
                    writer.cancelWriting()
                }
                return
            }
            
            // 1. 将HDR帧转为CIImage
            let hdrImage = CIImage(cvImageBuffer: imageBuffer)
            
            // 2. 色域转换:BT.2020 -> sRGB
            guard let bt2020Space = CGColorSpace(name: CGColorSpaceITU_R_2020),
                  let srgbSpace = CGColorSpace(name: CGColorSpaceSRGB) else {
                completion(NSError(domain: "HDRConverter", code: -4, userInfo: [NSLocalizedDescriptionKey: "颜色空间不支持"]))
                writer.cancelWriting()
                return
            }
            
            var convertedImage = hdrImage.applyingFilter("CIColorSpaceConversion", parameters: [
                kCIInputColorSpaceKey: bt2020Space,
                kCIOutputColorSpaceKey: srgbSpace
            ])
            
            // 3. 色调映射:压缩HDR高亮度到SDR范围(可根据需求调整参数)
            convertedImage = convertedImage.applyingFilter("CIToneCurve", parameters: [
                kCIInputPoint0Key: CGPoint(x: 0, y: 0),
                kCIInputPoint1Key: CGPoint(x: 0.2, y: 0.25),
                kCIInputPoint2Key: CGPoint(x: 0.5, y: 0.5),
                kCIInputPoint3Key: CGPoint(x: 0.8, y: 0.75),
                kCIInputPoint4Key: CGPoint(x: 1, y: 1)
            ])
            
            // 4. 创建输出PixelBuffer
            let pixelBufferAttributes: [String: Any] = [
                kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
                kCVPixelBufferWidthKey as String: outputSize.width,
                kCVPixelBufferHeightKey as String: outputSize.height,
                kCVPixelBufferIOSurfacePropertiesKey as String: [:]
            ]
            
            var outputPixelBuffer: CVPixelBuffer?
            CVPixelBufferCreate(kCFAllocatorDefault,
                               Int(outputSize.width),
                               Int(outputSize.height),
                               kCVPixelFormatType_32BGRA,
                               pixelBufferAttributes as CFDictionary,
                               &outputPixelBuffer)
            
            guard let finalBuffer = outputPixelBuffer else {
                completion(NSError(domain: "HDRConverter", code: -5, userInfo: [NSLocalizedDescriptionKey: "创建输出缓冲失败"]))
                writer.cancelWriting()
                return
            }
            
            // 5. 渲染转换后的图像到PixelBuffer
            ciContext.render(convertedImage, to: finalBuffer)
            
            // 6. 封装为SampleBuffer并写入
            var timingInfo = CMSampleTimingInfo()
            timingInfo.presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
            timingInfo.duration = CMSampleBufferGetDuration(sampleBuffer)
            timingInfo.decodeTimeStamp = CMSampleBufferGetDecodeTimeStamp(sampleBuffer)
            
            var sampleBufferOut: CMSampleBuffer?
            CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault,
                                               finalBuffer,
                                               true,
                                               nil,
                                               nil,
                                               videoTrack.formatDescription,
                                               &timingInfo,
                                               &sampleBufferOut)
            
            if let outputSampleBuffer = sampleBufferOut {
                videoWriterInput.append(outputSampleBuffer)
            }
            
            // 释放资源
            CVPixelBufferRelease(finalBuffer)
            CMSampleBufferInvalidate(sampleBufferOut)
        }
    }
}

2. 调用示例

let inputURL = URL(fileURLWithPath: "/path/to/hdr-video.mov")
let outputURL = URL(fileURLWithPath: "/path/to/sdr-output.mov")

convertHDRtoSDR(inputURL: inputURL, outputURL: outputURL) { error in
    if let error = error {
        print("转换失败:\(error.localizedDescription)")
    } else {
        print("转换完成!")
    }
}

关键细节说明

  • 颜色空间转换:CIColorSpaceConversion滤镜负责将HDR的BT.2020色域映射到SDR的sRGB色域,这是基础转换步骤。
  • 色调映射:CIToneCurve用于调整亮度曲线,把HDR中超过SDR亮度范围的部分压缩到0-200nits左右,避免过曝或丢失细节。你可以根据需求调整曲线的5个控制点,优化转换后的视觉效果。
  • 性能优化:使用Metal加速的CIContext,避免软件渲染导致的卡顿;处理逻辑放在后台队列,不阻塞主线程。
  • 音频处理:示例中直接拷贝原音频轨道,因为音频不涉及HDR/SDR属性,无需额外处理。

内容的提问来源于stack exchange,提问作者mufumade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 06:20:47