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
相关产品推荐
相关产品推荐

