この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
かんたんですが、複数枚の画像から動画を作成するロジックを紹介します。以外と紹介されていないので、探されていた方はどうぞ!
この処理に必要なのは、以下の3つのクラスです。
- AVAssetWriter
- AVAssetWriterInput
- AVAssetWriterInputPixelBufferAdaptor
これらのクラスはAVFoundation.frameworkで定義されています。なので、AVFoundation.frameworkをプロジェクトにリンクしておきましょう。
サンプル
それでは早速サンプルを紹介します。画像は以下の5つを使用します。
画像をプロジェクトに追加したら、ViewController.mを以下のように変更してください。
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property (nonatomic) AVAssetWriter *videoWriter;
@end
@implementation ViewController
// サンプルなのでとりあえずviewDidAppearで実行する
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// 動画にする画像
NSArray *images =
@[
[UIImage imageNamed:@"red"],
[UIImage imageNamed:@"yellow"],
[UIImage imageNamed:@"green"],
[UIImage imageNamed:@"blue"],
[UIImage imageNamed:@"pink"],
];
// 動画の出力先
NSString *documentPath = (NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]);
NSString *moviePath = [documentPath stringByAppendingPathComponent:@"movie.mov"];
[self writeImagesAsMovie:images
toPath:moviePath];
}
#pragma mark - Movie generator methods
/*!
画像の配列から動画を生成する。
@param images 画像の配列
@param path 動画ファイルの出力先
*/
- (void)writeImagesAsMovie:(NSArray *)images
toPath:(NSString *)path
{
NSParameterAssert(images);
NSParameterAssert(path);
NSAssert((images.count > 0), @"Set least one image.");
NSFileManager *fileManager = [NSFileManager defaultManager];
// 既にファイルがある場合は削除する
if ([fileManager fileExistsAtPath:path]) {
[fileManager removeItemAtPath:path error:nil];
}
// 最初の画像から動画のサイズ指定する
CGSize size = ((UIImage *)images[0]).size;
NSError *error = nil;
self.videoWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:path]
fileType:AVFileTypeQuickTimeMovie
error:&error];
if (error) {
NSLog(@"%@", [error localizedDescription]);
return;
}
NSDictionary *outputSettings =
@{
AVVideoCodecKey : AVVideoCodecH264,
AVVideoWidthKey : @(size.width),
AVVideoHeightKey : @(size.height),
};
AVAssetWriterInput *writerInput = [AVAssetWriterInput
assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:outputSettings];
[self.videoWriter addInput:writerInput];
NSDictionary *sourcePixelBufferAttributes =
@{
(NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32ARGB),
(NSString *)kCVPixelBufferWidthKey : @(size.width),
(NSString *)kCVPixelBufferHeightKey : @(size.height),
};
AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
sourcePixelBufferAttributes:sourcePixelBufferAttributes];
writerInput.expectsMediaDataInRealTime = YES;
// 動画生成開始
if (![self.videoWriter startWriting]) {
NSLog(@"Failed to start writing.");
return;
}
[self.videoWriter startSessionAtSourceTime:kCMTimeZero];
CVPixelBufferRef buffer = NULL;
int frameCount = 0;
int durationForEachImage = 1;
int32_t fps = 24;
for (UIImage *image in images) {
if (adaptor.assetWriterInput.readyForMoreMediaData) {
CMTime frameTime = CMTimeMake((int64_t)frameCount * fps * durationForEachImage, fps);
buffer = [self pixelBufferFromCGImage:image.CGImage];
if (![adaptor appendPixelBuffer:buffer withPresentationTime:frameTime]) {
NSLog(@"Failed to append buffer. [image : %@]", image);
}
if(buffer) {
CVBufferRelease(buffer);
}
frameCount++;
}
}
// 動画生成終了
[writerInput markAsFinished];
[self.videoWriter finishWritingWithCompletionHandler:^{
NSLog(@"Finish writing!");
}];
CVPixelBufferPoolRelease(adaptor.pixelBufferPool);
}
- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image
{
NSDictionary *options = @{
(NSString *)kCVPixelBufferCGImageCompatibilityKey : @(YES),
(NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey: @(YES),
};
CVPixelBufferRef pxbuffer = NULL;
CVPixelBufferCreate(kCFAllocatorDefault,
CGImageGetWidth(image),
CGImageGetHeight(image),
kCVPixelFormatType_32ARGB,
(__bridge CFDictionaryRef)options,
&pxbuffer);
CVPixelBufferLockBaseAddress(pxbuffer, 0);
void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pxdata,
CGImageGetWidth(image),
CGImageGetHeight(image),
8,
4 * CGImageGetWidth(image),
rgbColorSpace,
(CGBitmapInfo)kCGImageAlphaNoneSkipFirst);
CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, CGImageGetHeight(image));
CGContextConcatCTM(context, flipVertical);
CGAffineTransform flipHorizontal = CGAffineTransformMake(-1.0, 0.0, 0.0, 1.0, CGImageGetWidth(image), 0.0);
CGContextConcatCTM(context, flipHorizontal);
CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
return pxbuffer;
}
@end
これを実行すると、iOS端末のドキュメントディレクトリ(シミュレータの場合は/Users/ユーザ名/Library/Application Support/iPhone Simulator/7.x.x/アプリID/Documents/)にmovie.movが生成されます。これをQuickTimeなどで再生すると以下のように赤>黃>緑>青>ピンクの順にそれぞれ1秒ずつ表示されます。
みなさんお好きなようにいじってみてください!ではまた。