如何在 iOS 中将多张 WebP 图像转换为视频

在 iOS 中将多张 WebP 图像(通常是动图或序列帧)转换为视频,核心逻辑是利用 AVAssetWriter 将解码后的像素数据(CVPixelBuffer)按时间顺序写入到一个视频文件中。

由于 iOS 原生 UIImage 对 WebP 的支持在不同版本有所差异(iOS 14+ 支持较好),通常建议配合 libwebp 或 SDWebImageWebPCoder 来确保解码的兼容性。

1、转换流程架构

  1. 解码阶段:将 WebP 动图拆解为每一帧的 UIImage 或 CGImage
  2. 缓冲区准备:创建一个 CVPixelBufferPool,将 CGImage 绘制到 CVPixelBuffer 中。
  3. 写入阶段:使用 AVAssetWriterInputPixelBufferAdaptor 按指定帧率将 Buffer 压入视频流。

2、Objective-C 代码实现

以下是一个将 WebP 图像序列转换为 MP4 视频的核心类实现:

#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>

@implementation WebPToVideoConverter

- (void)convertImages:(NSArray<UIImage *> *)images toPath:(NSString *)outputPath {
    // 1. 初始化视频输出配置
    NSError *error = nil;
    NSURL *videoURL = [NSURL fileURLWithPath:outputPath];
    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:videoURL 
                                                           fileType:AVFileTypeMPEG4
                                                              error:&error];
    
    // 设置 H.264 编码参数
    NSDictionary *videoSettings = @{
        AVVideoCodecKey: AVVideoCodecTypeH264,
        AVVideoWidthKey: @(images.firstObject.size.width),
        AVVideoHeightKey: @(images.firstObject.size.height),
    };
    
    AVAssetWriterInput *writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
                                                                         outputSettings:videoSettings];
    
    // 2. 创建适配器(用于输入 CVPixelBuffer)
    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
        assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput 
        sourcePixelBufferAttributes:@{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32ARGB)}];
    
    [videoWriter addInput:writerInput];
    [videoWriter startWriting];
    [videoWriter startSessionAtSourceTime:kCMTimeZero];
    
    // 3. 循环写入帧
    NSInteger __block frameCount = 0;
    CGFloat durationPerFrame = 0.1; // 假设每帧 100ms (10fps)
    
    [writerInput requestMediaDataWhenReadyOnQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) usingBlock:^{
        while ([writerInput isReadyForMoreMediaData] && frameCount < images.count) {
            UIImage *image = images[frameCount];
            CVPixelBufferRef buffer = [self pixelBufferFromCGImage:image.CGImage size:image.size];
            
            if (buffer) {
                CMTime presentationTime = CMTimeMake(frameCount, 10); // 10 为帧率
                if (![adaptor appendPixelBuffer:buffer withPresentationTime:presentationTime]) {
                    NSLog(@"写入失败: %@", videoWriter.error);
                }
                CFRelease(buffer);
            }
            frameCount++;
        }
        
        if (frameCount >= images.count) {
            [writerInput markAsFinished];
            [videoWriter finishWritingWithCompletionHandler:^{
                NSLog(@"视频转换完成: %@", outputPath);
            }];
        }
    }];
}

// 辅助方法:CGImage 转 CVPixelBuffer
- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image size:(CGSize)size {
    NSDictionary *options = @{(id)kCVPixelBufferCGImageCompatibilityKey: @YES,
                             (id)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES};
    CVPixelBufferRef pxbuffer = NULL;
    CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef)options, &pxbuffer);
    
    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
    
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, 4 * size.width, rgbColorSpace, kCGImageAlphaPremultipliedFirst);
    
    CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), image);
    
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);
    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    
    return pxbuffer;
}

@end

3、关键优化建议

3.1、WebP 解码

如果你的 WebP 是动图(Animated WebP),系统原生的 UIImage 可能无法直接获取序列。建议使用:

  • SDWebImageWebPCoder:调用 [[SDImageWebPCoder sharedCoder] decodedImageWithData:webpData options:nil]
  • YYImage:它可以非常方便地遍历动图的每一帧及其持续时间(Duration)。

3.2、内存控制

大量 WebP 帧解码后会占用巨大内存。

  • 不要一次性将所有 UIImage 加载到 NSArray 中。
  • 推荐方案:使用 CGImageSourceRef 边解码边写入。

3.3、视频编码格式选择

  • H.264 (AVVideoCodecTypeH264):兼容性最好。
  • HEVC (AVVideoCodecTypeHEVC):如果 WebP 带有透明通道,且你希望输出带透明度的视频,建议使用 HEVC 并在配置中开启 Alpha 支持。

3.4、帧率对齐

WebP 的每一帧持续时间可能是不固定的。在 CMTimeMake 时,应累加实际的 duration 而非使用固定的 frameCount,以保证视频丝滑不卡顿。

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

如何在 iOS 中将多张 WebP 图像转换为视频

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论