iOS object-c图片帧转视频

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;
    }

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容