Swift中CVPixelBuffer生成视频比原快照图像偏暗问题求助
视频生成后画面偏暗的问题排查与解决思路
我的App会将包含ZStack(带0.4透明度的图片、0.25透明度的白色矩形及文字)的视图快照保存为图片,再结合音频生成视频。视频能正常生成且包含音频和图像,但输出视频的画面始终比快照图片偏暗。
核心问题分析与解决方向
- 颜色空间不匹配:快照使用sRGB空间,但视频编码和像素缓冲区创建时用了设备RGB空间,导致颜色/亮度转换偏差。
- Alpha通道处理错误:像素缓冲区格式为
kCVPixelFormatType_32ARGB(预乘Alpha),但绘制时的CGContext使用了非预乘的Alpha配置,导致透明度叠加计算错误,画面偏暗。 - 视频编码缺少颜色标准配置:H.264编码默认会压缩亮度,需强制指定sRGB对应的ITU-R 709标准,避免亮度丢失。
修改后的关键代码
1. 修复CVPixelBuffer创建逻辑(颜色空间+Alpha匹配)
extension CGImage { static func pixelBuffer(fromImage image: CGImage, size: CGSize) throws -> CVPixelBuffer { let options: CFDictionary = [ kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true ] as CFDictionary var pxbuffer: CVPixelBuffer? = nil let status = CVPixelBufferCreate( kCFAllocatorDefault, Int(size.width), Int(size.height), kCVPixelFormatType_32ARGB, options, &pxbuffer ) guard let buffer = pxbuffer, status == kCVReturnSuccess else { throw NSError() } CVPixelBufferLockBaseAddress(buffer, []) guard let pxdata = CVPixelBufferGetBaseAddress(buffer) else { throw NSError() } let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer) // 强制使用sRGB颜色空间,匹配快照的颜色标准 guard let rgbColorSpace = CGColorSpace(name: CGColorSpace.sRGB) else { throw NSError() } // 匹配kCVPixelFormatType_32ARGB的预乘Alpha格式 guard let context = CGContext( data: pxdata, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue ) else { print("CGContext创建失败") throw NSError() } context.draw(image, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) CVPixelBufferUnlockBaseAddress(buffer, []) return buffer } }
2. 修复视频编码配置(添加颜色标准)
func writeSingleImageToMovie(image: UIImage, movieLength: TimeInterval, outputFileURL: URL, completion: @escaping (Error?) -> ()) { do { let imageSize = image.size let videoWriter = try AVAssetWriter(outputURL: outputFileURL, fileType: AVFileType.mp4) // 添加sRGB对应的ITU-R 709颜色属性,避免编码时压缩亮度 let videoSettings: [String: Any] = [ AVVideoCodecKey: AVVideoCodecType.h264, AVVideoWidthKey: imageSize.width, AVVideoHeightKey: imageSize.height, AVVideoColorPropertiesKey: [ AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2, AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709_2, AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_709_2 ] ] let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings) // 指定源像素缓冲区属性,确保和CVPixelBuffer格式匹配 let sourcePixelBufferAttributes: [String: Any] = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32ARGB, kCVPixelBufferWidthKey as String: imageSize.width, kCVPixelBufferHeightKey as String: imageSize.height ] let adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, sourcePixelBufferAttributes: sourcePixelBufferAttributes) if !videoWriter.canAdd(videoWriterInput) { throw NSError() } videoWriterInput.expectsMediaDataInRealTime = true videoWriter.add(videoWriterInput) videoWriter.startWriting() let timeScale: Int32 = 60 let startFrameTime = CMTimeMake(value: 0, timescale: timeScale) let endFrameTime = CMTimeMakeWithSeconds(movieLength, preferredTimescale: timeScale) videoWriter.startSession(atSourceTime: startFrameTime) guard let cgImage = image.cgImage else { throw NSError() } let buffer: CVPixelBuffer = try CGImage.pixelBuffer(fromImage: cgImage, size: imageSize) while !adaptor.assetWriterInput.isReadyForMoreMediaData { usleep(10) } adaptor.append(buffer, withPresentationTime: startFrameTime) while !adaptor.assetWriterInput.isReadyForMoreMediaData { usleep(10) } adaptor.append(buffer, withPresentationTime: endFrameTime) videoWriterInput.markAsFinished() videoWriter.finishWriting { completion(videoWriter.error) } } catch { print("writeSingleImageToMovie出错: \(error)") completion(error) } }
内容的提问来源于stack exchange,提问作者user2814778
相关产品推荐
相关产品推荐

