如何在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
相关产品推荐
相关产品推荐

