images 图片数组
outputURL 视频保存的地址
duration 视频长度
-
(void)createVideoFromImages:(NSArray<UIImage *> *)images
outputURL:(NSURL *)outputURL
duration:(NSTimeInterval)duration
completion:(void (^)(BOOL success, NSError *error))completion {// 检查参数
if (images.count == 0 || !outputURL) {
if (completion) {
completion(NO, [NSError errorWithDomain:@"VideoCreator"
code:1001
userInfo:@{NSLocalizedDescriptionKey: @"无效的参数"}]);
}
return;
}// 清理旧文件
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
// 获取第一张图片的尺寸
CGSize imageSize = images.firstObject.size;
// 配置视频写入器
NSError *error = nil;
AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL
fileType:AVFileTypeMPEG4
error:&error];
if (error) {
if (completion) {
completion(NO, error);
}
return;
}
// 视频设置
NSDictionary *videoSettings = @{
AVVideoCodecKey: AVVideoCodecTypeH264,
AVVideoWidthKey: @(imageSize.width),
AVVideoHeightKey: @(imageSize.height),
AVVideoCompressionPropertiesKey: @{
AVVideoAverageBitRateKey: @(imageSize.width * imageSize.height * 10), // 比特率
AVVideoProfileLevelKey: AVVideoProfileLevelH264High40,
AVVideoMaxKeyFrameIntervalKey: @(30)
}
};
AVAssetWriterInput *writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoSettings];
// 像素缓冲适配器
NSDictionary *sourcePixelBufferAttributes = @{
(NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32ARGB),
(NSString *)kCVPixelBufferWidthKey: @(imageSize.width),
(NSString *)kCVPixelBufferHeightKey: @(imageSize.height)
};
AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
sourcePixelBufferAttributes:sourcePixelBufferAttributes];
if ([assetWriter canAddInput:writerInput]) {
[assetWriter addInput:writerInput];
} else {
if (completion) {
completion(NO, [NSError errorWithDomain:@"VideoCreator"
code:1002
userInfo:@{NSLocalizedDescriptionKey: @"无法添加视频输入"}]);
}
return;
}
// 开始写入
[assetWriter startWriting];
[assetWriter startSessionAtSourceTime:kCMTimeZero];
// 计算每帧的持续时间
CMTime frameTime = CMTimeMakeWithSeconds(duration / images.count, 600);
dispatch_queue_t queue = dispatch_queue_create("video.creation.queue", DISPATCH_QUEUE_SERIAL);
[writerInput requestMediaDataWhenReadyOnQueue:queue usingBlock:^{
CMTime currentTime = kCMTimeZero;
for (int i = 0; i < images.count; i++) {
while (!writerInput.readyForMoreMediaData) {
[NSThread sleepForTimeInterval:0.1];
}
@autoreleasepool {
UIImage *image = images[i];
CVPixelBufferRef pixelBuffer = [self pixelBufferFromCGImage:image.CGImage size:imageSize];
if (pixelBuffer) {
if (![adaptor appendPixelBuffer:pixelBuffer withPresentationTime:currentTime]) {
NSLog(@"无法添加第 %d 帧", i);
}
CFRelease(pixelBuffer);
}
currentTime = CMTimeAdd(currentTime, frameTime);
}
}
[writerInput markAsFinished];
[assetWriter finishWritingWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(assetWriter.status == AVAssetWriterStatusCompleted, assetWriter.error);
}
});
}];
}];
}
// 将 CGImage 转换为 CVPixelBuffer
-
(CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image size:(CGSize)size {
NSDictionary *options = @{
(NSString *)kCVPixelBufferCGImageCompatibilityKey: @YES,
(NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES
};CVPixelBufferRef pixelBuffer = NULL;
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,
size.width,
size.height,
kCVPixelFormatType_32ARGB,
(__bridge CFDictionaryRef)options,
&pixelBuffer);if (status != kCVReturnSuccess) {
return NULL;
}CVPixelBufferLockBaseAddress(pixelBuffer, 0);
void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pixelData,
size.width,
size.height,
8,
CVPixelBufferGetBytesPerRow(pixelBuffer),
rgbColorSpace,
kCGImageAlphaPremultipliedFirst);CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
return pixelBuffer;
}