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

Objective-C转PNG序列为H.264 MP4时出现AVFoundationErrorDomain报错

解决多张PNG转H.264 MP4时的AVFoundation错误(Code=-16364)

问题描述

Objective-C代码可正常将单张PNG截图转为H.264编码的MP4,但处理多张PNG图片序列时触发错误:

Video writing failed: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-16364), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x600000c78150 {Error Domain=NSOSStatusErrorDomain Code=-16364 "(null)"}}

单张图片转换后的MP4可正常播放,但仅处理2张图片时代码就失效,原代码如下:

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <AppKit/AppKit.h>
#import <CoreImage/CoreImage.h> // Import CoreImage framework explicitly

@interface VideoCreator : NSObject

- (void)createH264VideoFromPNGImages;

@end

@implementation VideoCreator

- (void)createH264VideoFromPNGImages {
    // Specify the folder path containing the PNG images
    NSString *imageFolderPath = @"/var/folders/example/path/";

    // Specify the output file path and name for the H.264 video
    NSString *outputPath = @"/var/folders/example/result.mp4";

    // Get a list of files in the image folder
    NSArray *imageFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:imageFolderPath error:nil];

    // Create an AVAssetWriter to write the video file
    NSError *error = nil;
    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:outputPath]
                                                          fileType:AVFileTypeMPEG4
                                                             error:&error];
    if (error) {
        NSLog(@"Failed to create AVAssetWriter: %@", error);
        return;
    }

    // Define the video settings
    NSDictionary *videoSettings = @{
        AVVideoCodecKey: AVVideoCodecTypeH264,
        AVVideoWidthKey: @2880,    // Specify the desired video width
        AVVideoHeightKey: @1800,   // Specify the desired video height
    };

    // Create an AVAssetWriterInput with the video settings
    AVAssetWriterInput *videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
                                                                              outputSettings:videoSettings];
    videoWriterInput.expectsMediaDataInRealTime = YES;

    // Create a pixel buffer attributes dictionary
    NSDictionary *pixelBufferAttributes = @{
        (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32ARGB),
        (id)kCVPixelBufferWidthKey: @2880,     // Specify the same width as video settings
        (id)kCVPixelBufferHeightKey: @1800,    // Specify the same height as video settings
    };

    // Create an AVAssetWriterInputPixelBufferAdaptor with the pixel buffer attributes
    AVAssetWriterInputPixelBufferAdaptor *pixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput sourcePixelBufferAttributes:pixelBufferAttributes];

    // Add the video input to the asset writer
    [videoWriter addInput:videoWriterInput];

    // Start writing the video
    [videoWriter startWriting];
    [videoWriter startSessionAtSourceTime:kCMTimeZero];

    // Iterate through the image files and append them to the video
    for (NSString *imageFile in imageFiles) {
            NSString *imageFilePath = [imageFolderPath stringByAppendingPathComponent:imageFile];
        NSImage *image = [[NSImage alloc] initWithContentsOfFile:imageFilePath];
        CIImage *ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
        CVPixelBufferRef pixelBuffer = [self pixelBufferFromCIImage:ciImage];
        
        // Append the pixel buffer to the asset writer input
        [pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:CMTimeMake(1, 30)];
        
        // Release the pixel buffer
        CVPixelBufferRelease(pixelBuffer);
        
    }

    // Finish writing the video
    [videoWriterInput markAsFinished];
    [videoWriter endSessionAtSourceTime:CMTimeMake(imageFiles.count, 30)];
    [videoWriter finishWritingWithCompletionHandler:^{
        // Handle the completion of video writing
        if (videoWriter.status == AVAssetWriterStatusCompleted) {
                NSLog(@"Video writing completed successfully!");
            } else if (videoWriter.status == AVAssetWriterStatusFailed) {
                NSLog(@"Video writing failed: %@", videoWriter.error);
            } else if (videoWriter.status == AVAssetWriterStatusCancelled) {
                NSLog(@"Video writing cancelled.");
            } else {
                NSLog(@"Video writing encountered an unknown error.");
            }
    }];
}

- (CVPixelBufferRef)pixelBufferFromCIImage:(CIImage *)image {
    // Create a pixel buffer pool
    CVPixelBufferPoolRef pixelBufferPool;
    NSDictionary *pixelBufferAttributes = @{
        (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32ARGB),
        (id)kCVPixelBufferWidthKey: @2880,     // Specify the desired width
        (id)kCVPixelBufferHeightKey: @1800,    // Specify the desired height
        (id)kCVPixelBufferCGImageCompatibilityKey: @YES,
        (id)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES
    };
    CVPixelBufferPoolCreate(nil, nil, (__bridge CFDictionaryRef)pixelBufferAttributes, &pixelBufferPool);

    // Create a pixel buffer
    CVPixelBufferRef pixelBuffer;
    CVPixelBufferPoolCreatePixelBuffer(nil, pixelBufferPool, &pixelBuffer);

    // Lock the pixel buffer
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);

    // Get the pixel buffer data
    void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);

    // Create a Core Graphics context
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
    size_t width = CVPixelBufferGetWidth(pixelBuffer);
    size_t height = CVPixelBufferGetHeight(pixelBuffer);
    CGContextRef context = CGBitmapContextCreate(pixelData, width, height, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst);

    // Render the CIImage into the pixel buffer
    NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
    [NSGraphicsContext setCurrentContext:nsGraphicsContext];
    CIContext *ciContext = [CIContext contextWithCGContext:context options:nil];
    [ciContext drawImage:image inRect:CGRectMake(0, 0, width, height) fromRect:[image extent]];

    // Unlock the pixel buffer and release the Core Graphics context
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    CGContextRelease(context);

    return pixelBuffer;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        VideoCreator *videoCreator = [[VideoCreator alloc] init];
        [videoCreator createH264VideoFromPNGImages];
    }
    return 0;
}

解决方案

1. 修正重复时间戳

所有帧使用相同时间戳会导致AVFoundation拒绝写入,需为每帧生成递增的时间戳,同时对图片文件按文件名排序保证帧顺序正确:

// 替换原循环前的文件列表处理
NSArray *sortedImageFiles = [imageFiles sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    return [obj1 compare:obj2 options:NSNumericSearch]; // 按文件名数字排序
}];

NSUInteger index = 0;
for (NSString *imageFile in sortedImageFiles) {
    // ... 其他代码不变
    CMTime presentationTime = CMTimeMake(index, 30); // 每帧时间戳递增
    [pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime];
    index++;
    // ... 其他代码不变
}

2. 等待输入缓冲区可用

连续写入帧可能导致缓冲区溢出,写入前需检查并等待:

// 在appendPixelBuffer前添加
while (!videoWriterInput.isReadyForMoreMediaData) {
    [NSThread sleepForTimeInterval:0.01];
}

3. 修复像素缓冲区内存泄漏与格式不匹配

  • 释放创建的pixelBufferPool,避免内存泄漏
  • 统一像素格式与CGContext的Alpha通道设置,匹配kCVPixelFormatType_32ARGB:
- (CVPixelBufferRef)pixelBufferFromCIImage:(CIImage *)image {
    // ... 原代码不变
    CVPixelBufferPoolCreate(nil, nil, (__bridge CFDictionaryRef)pixelBufferAttributes, &pixelBufferPool);

    CVPixelBufferRef pixelBuffer;
    CVPixelBufferPoolCreatePixelBuffer(nil, pixelBufferPool, &pixelBuffer);
    
    // 释放不再需要的像素缓冲区池
    CVPixelBufferPoolRelease(pixelBufferPool);

    // ... 原代码不变
    // 修改CGContext的Alpha参数为kCGImageAlphaPremultipliedFirst
    CGContextRef context = CGBitmapContextCreate(pixelData, width, height, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedFirst);
    // ... 原代码不变
}

修正后的完整关键代码片段

- (void)createH264VideoFromPNGImages {
    NSString *imageFolderPath = @"/var/folders/example/path/";
    NSString *outputPath = @"/var/folders/example/result.mp4";

    NSError *dirError = nil;
    NSArray *imageFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:imageFolderPath error:&dirError];
    if (dirError) {
        NSLog(@"Failed to read directory: %@", dirError);
        return;
    }

    // 按文件名排序保证帧顺序
    NSArray *sortedImageFiles = [imageFiles sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return [obj1 compare:obj2 options:NSNumericSearch];
    }];

    NSError *writerError = nil;
    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:outputPath]
                                                          fileType:AVFileTypeMPEG4
                                                             error:&writerError];
    if (writerError) {
        NSLog(@"Failed to create AVAssetWriter: %@", writerError);
        return;
    }

    NSDictionary *videoSettings = @{
        AVVideoCodecKey: AVVideoCodecTypeH264,
        AVVideoWidthKey: @2880,
        AVVideoHeightKey: @1800,
    };

    AVAssetWriterInput *videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
                                                                              outputSettings:videoSettings];
    videoWriterInput.expectsMediaDataInRealTime = YES;

    NSDictionary *pixelBufferAttributes = @{
        (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32ARGB),
        (id)kCVPixelBufferWidthKey: @2880,
        (id)kCVPixelBufferHeightKey: @1800,
    };

    AVAssetWriterInputPixelBufferAdaptor *pixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput sourcePixelBufferAttributes:pixelBufferAttributes];

    [videoWriter addInput:videoWriterInput];

    [videoWriter startWriting];
    [videoWriter startSessionAtSourceTime:kCMTimeZero];

    NSUInteger index = 0;
    for (NSString *imageFile in sortedImageFiles) {
        NSString *imageFilePath = [imageFolderPath stringByAppendingPathComponent:imageFile];
        NSImage *image = [[NSImage alloc] initWithContentsOfFile:imageFilePath];
        if (!image) {
            NSLog(@"Failed to load image: %@", imageFilePath);
            continue;
        }
        CIImage *ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
        CVPixelBufferRef pixelBuffer = [self pixelBufferFromCIImage:ciImage];
        
        // 等待缓冲区可用
        while (!videoWriterInput.isReadyForMoreMediaData) {
            [NSThread sleepForTimeInterval:0.01];
        }
        
        CMTime presentationTime = CMTimeMake(index, 30);
        [pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime];
        
        CVPixelBufferRelease(pixelBuffer);
        index++;
    }

    [videoWriterInput markAsFinished];
    [videoWriter endSessionAtSourceTime:CMTimeMake(sortedImageFiles.count, 30)];
    [videoWriter finishWritingWithCompletionHandler:^{
        if (videoWriter.status == AVAssetWriterStatusCompleted) {
            NSLog(@"Video writing completed successfully!");
        } else if (videoWriter.status == AVAssetWriterStatusFailed) {
            NSLog(@"Video writing failed: %@", videoWriter.error);
        } else if (videoWriter.status == AVAssetWriterStatusCancelled) {
            NSLog(@"Video writing cancelled.");
        } else {
            NSLog(@"Video writing encountered an unknown error.");
        }
    }];
}

- (CVPixelBufferRef)pixelBufferFromCIImage:(CIImage *)image {
    CVPixelBufferPoolRef pixelBufferPool;
    NSDictionary *pixelBufferAttributes = @{
        (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32ARGB),
        (id)kCVPixelBufferWidthKey: @2880,
        (id)kCVPixelBufferHeightKey: @1800,
        (id)kCVPixelBufferCGImageCompatibilityKey: @YES,
        (id)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES
    };
    CVPixelBufferPoolCreate(nil, nil, (__bridge CFDictionaryRef)pixelBufferAttributes, &pixelBufferPool);

    CVPixelBufferRef pixelBuffer;
    CVPixelBufferPoolCreatePixelBuffer(nil, pixelBufferPool, &pixelBuffer);
    CVPixelBufferPoolRelease(pixelBufferPool); // 释放池

    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
    size_t width = CVPixelBufferGetWidth(pixelBuffer);
    size_t height = CVPixelBufferGetHeight(pixelBuffer);
    
    // 匹配ARGB格式的Alpha设置
    CGContextRef context = CGBitmapContextCreate(pixelData, width, height, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedFirst);

    NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
    [NSGraphicsContext setCurrentContext:nsGraphicsContext];
    CIContext *ciContext = [CIContext contextWithCGContext:context options:nil];
    [ciContext drawImage:image inRect:CGRectMake(0, 0, width, height) fromRect:[image extent]];

    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    CGContextRelease(context);

    return pixelBuffer;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:27:03