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

使用AVAssetWriter将UIImage转为视频时,长视频出现黑帧闪烁问题

解决长视频生成时的黑帧闪烁问题

哥们,这种被同一个bug卡几周的滋味太难受了!我之前也碰到过类似的情况——用图片序列生成视频时,短视频正常,长视频到一定时长就开始出黑帧,不同机型触发时间还不一样。你遇到的问题大概率和CVPixelBuffer的内存管理、尺寸不匹配或者时间戳计算错误有关,结合你贴的代码,咱们一步步来解决:

先看你代码里的潜在坑

你提供的fillPixelBufferFromImage函数有几个容易被忽略的问题:

  • 硬编码了720x720的尺寸,如果输入图片和这个尺寸不匹配,绘制时可能出现空白/拉伸,累积到一定程度就会触发黑帧
  • 没有用defer确保CVPixelBuffer一定会解锁,一旦中间代码抛出异常,会导致后续帧无法正确写入
  • 直接按buffer尺寸绘制图片,没有考虑图片的原始比例,可能导致渲染异常

针对性修复方案

1. 修复像素填充函数,适配动态尺寸

把硬编码的尺寸改成从pixelBuffer获取,同时确保图片按比例绘制,还要用defer保证解锁:

// MARK: - Fill Pixel Buffer -
private func fillPixelBufferFromImage(image: UIImage, pixelBuffer: CVPixelBuffer) {
    CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
    // 用defer确保不管中间出什么问题,buffer都会被解锁
    defer {
        CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
    }
    
    guard let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer) else { return }
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    
    // 从pixelBuffer获取真实的宽高和字节数,不要硬编码
    let bufferWidth = CVPixelBufferGetWidth(pixelBuffer)
    let bufferHeight = CVPixelBufferGetHeight(pixelBuffer)
    let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
    
    guard let context = CGContext(
        data: pixelData,
        width: bufferWidth,
        height: bufferHeight,
        bitsPerComponent: 8,
        bytesPerRow: bytesPerRow,
        space: rgbColorSpace,
        bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
    ) else { return }
    
    // 按比例缩放图片,避免拉伸导致的渲染问题
    let imageSize = image.size
    let scale = min(CGFloat(bufferWidth)/imageSize.width, CGFloat(bufferHeight)/imageSize.height)
    let scaledSize = CGSize(width: imageSize.width*scale, height: imageSize.height*scale)
    let drawRect = CGRect(
        x: (CGFloat(bufferWidth)-scaledSize.width)/2,
        y: (CGFloat(bufferHeight)-scaledSize.height)/2,
        width: scaledSize.width,
        height: scaledSize.height
    )
    
    context.draw(image.cgImage!, in: drawRect)
}

2. 修复时间戳计算,避免溢出

黑帧的另一个常见原因是时间戳不连续或整数溢出,尤其是处理几百上千帧的时候。一定要用CMTime的API来计算时间,不要直接用循环索引:

// 假设你的帧率是12fps,定义每帧的时长
let frameDuration = CMTimeMake(value: 1, timescale: 12)
var currentTime = CMTime.zero

// 在循环添加每帧时:
for image in imageArray {
    // 获取或创建pixelBuffer
    guard let pixelBuffer = adaptor.pixelBufferPool?.createPixelBuffer() else { continue }
    fillPixelBufferFromImage(image: image, pixelBuffer: pixelBuffer)
    // 用连续的时间戳写入
    adaptor.append(pixelBuffer, withPresentationTime: currentTime)
    currentTime = CMTimeAdd(currentTime, frameDuration)
}

3. 优化内存,减少碎片化

  • 重用CVPixelBuffer:用AVAssetWriterInputPixelBufferAdaptor的pixelBufferPool来获取可重用的buffer,不要每次都新建,减少内存分配
  • 用autoreleasepool包裹每帧处理:避免UIImage和临时对象占用过多内存,防止内存不足导致渲染失败
for image in imageArray {
    autoreleasepool {
        guard let pixelBuffer = adaptor.pixelBufferPool?.createPixelBuffer() else { return }
        fillPixelBufferFromImage(image: image, pixelBuffer: pixelBuffer)
        adaptor.append(pixelBuffer, withPresentationTime: currentTime)
        currentTime = CMTimeAdd(currentTime, frameDuration)
    }
}

4. 监控写入状态,排查错误

在写入过程中加入状态检查,一旦写入失败能及时看到错误信息:

if writer.status == .failed {
    print("视频写入出错:\(writer.error?.localizedDescription ?? "未知错误")")
    // 这里可以加入错误处理逻辑
}

额外测试建议

  • 用Xcode的Memory Graph Debugger观察内存变化,看黑帧出现时是否有内存突然飙升或泄漏
  • 尝试降低视频分辨率(比如改成480p),如果黑帧消失,说明是旧机型的硬件性能限制,可以考虑分批次渲染再拼接视频

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:58:51