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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 01:31:16