#ios #objective-c #avfoundation
#iOS #objective-c #avfoundation
Вопрос:
Я хотел бы экспортировать сингл UIImage
в виде фильма и сохранить на диск. Я нашел пару примеров создания фильма из массива UIImage
s. Самое близкое, что у меня есть, — это использование этого кода: https://github.com/HarrisonJackson/HJImagesToVideo . Просто передать массив с одним изображением, однако я не уверен, как изменить этот код, чтобы иметь возможность устанавливать желаемую продолжительность фильма. Я попытался изменить CMTime
с CMTimeMakeWithSeconds(5, 300)
помощью, однако экспортированное видео иногда появляется пустым.
(void)writeImageAsMovie:(NSArray *)array
toPath:(NSString*)path
size:(CGSize)size
fps:(int)fps
animateTransitions:(BOOL)shouldAnimateTransitions
withCallbackBlock:(SuccessBlock)callbackBlock
{
NSLog(@"%@", path);
NSError *error = nil;
AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:path]
fileType:AVFileTypeMPEG4
error:amp;error];
if (error) {
if (callbackBlock) {
callbackBlock(NO);
}
return;
}
NSParameterAssert(videoWriter);
NSDictionary *videoSettings = @{AVVideoCodecKey: AVVideoCodecH264,
AVVideoWidthKey: [NSNumber numberWithInt:size.width],
AVVideoHeightKey: [NSNumber numberWithInt:size.height]};
AVAssetWriterInput* writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoSettings];
AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
sourcePixelBufferAttributes:nil];
NSParameterAssert(writerInput);
NSParameterAssert([videoWriter canAddInput:writerInput]);
[videoWriter addInput:writerInput];
//Start a session:
[videoWriter startWriting];
[videoWriter startSessionAtSourceTime:kCMTimeZero];
CVPixelBufferRef buffer;
CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, amp;buffer);
CMTime presentTime = CMTimeMake(0, fps);
int i = 0;
while (1)
{
if(writerInput.readyForMoreMediaData){
presentTime = CMTimeMake(i, fps);
if (i >= [array count]) {
buffer = NULL;
} else {
buffer = [HJImagesToVideo pixelBufferFromCGImage:[array[i] CGImage] size:CGSizeMake(480, 320)];
}
if (buffer) {
//append buffer
BOOL appendSuccess = [HJImagesToVideo appendToAdapter:adaptor
pixelBuffer:buffer
atTime:presentTime
withInput:writerInput];
NSAssert(appendSuccess, @"Failed to append");
if (shouldAnimateTransitions amp;amp; i 1 < array.count) {
//Create time each fade frame is displayed
CMTime fadeTime = CMTimeMake(1, fps*TransitionFrameCount);
//Add a delay, causing the base image to have more show time before fade begins.
for (int b = 0; b < FramesToWaitBeforeTransition; b ) {
presentTime = CMTimeAdd(presentTime, fadeTime);
}
//Adjust fadeFrameCount so that the number and curve of the fade frames and their alpha stay consistant
NSInteger framesToFadeCount = TransitionFrameCount - FramesToWaitBeforeTransition;
//Apply fade frames
for (double j = 1; j < framesToFadeCount; j ) {
buffer = [HJImagesToVideo crossFadeImage:[array[i] CGImage]
toImage:[array[i 1] CGImage]
atSize:CGSizeMake(480, 320)
withAlpha:j/framesToFadeCount];
BOOL appendSuccess = [HJImagesToVideo appendToAdapter:adaptor
pixelBuffer:buffer
atTime:presentTime
withInput:writerInput];
presentTime = CMTimeAdd(presentTime, fadeTime);
NSAssert(appendSuccess, @"Failed to append");
}
}
i ;
} else {
//Finish the session:
[writerInput markAsFinished];
[videoWriter finishWritingWithCompletionHandler:^{
NSLog(@"Successfully closed video writer");
if (videoWriter.status == AVAssetWriterStatusCompleted) {
if (callbackBlock) {
callbackBlock(YES);
}
} else {
if (callbackBlock) {
callbackBlock(NO);
}
}
}];
CVPixelBufferPoolRelease(adaptor.pixelBufferPool);
NSLog (@"Done");
break;
}
}
}
}
(CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image
size:(CGSize)imageSize
{
NSDictionary *options = @{(id)kCVPixelBufferCGImageCompatibilityKey: @YES,
(id)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES};
CVPixelBufferRef pxbuffer = NULL;
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, imageSize.width,
imageSize.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options,
amp;pxbuffer);
NSParameterAssert(status == kCVReturnSuccess amp;amp; pxbuffer != NULL);
CVPixelBufferLockBaseAddress(pxbuffer, 0);
void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
NSParameterAssert(pxdata != NULL);
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pxdata, imageSize.width,
imageSize.height, 8, 4*imageSize.width, rgbColorSpace,
kCGImageAlphaNoneSkipFirst);
NSParameterAssert(context);
CGContextDrawImage(context, CGRectMake(0 (imageSize.width-CGImageGetWidth(image))/2,
(imageSize.height-CGImageGetHeight(image))/2,
CGImageGetWidth(image),
CGImageGetHeight(image)), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
return pxbuffer;
}
(CVPixelBufferRef)crossFadeImage:(CGImageRef)baseImage
toImage:(CGImageRef)fadeInImage
atSize:(CGSize)imageSize
withAlpha:(CGFloat)alpha
{
NSDictionary *options = @{(id)kCVPixelBufferCGImageCompatibilityKey: @YES,
(id)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES};
CVPixelBufferRef pxbuffer = NULL;
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, imageSize.width,
imageSize.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options,
amp;pxbuffer);
NSParameterAssert(status == kCVReturnSuccess amp;amp; pxbuffer != NULL);
CVPixelBufferLockBaseAddress(pxbuffer, 0);
void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
NSParameterAssert(pxdata != NULL);
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pxdata, imageSize.width,
imageSize.height, 8, 4*imageSize.width, rgbColorSpace,
kCGImageAlphaNoneSkipFirst);
NSParameterAssert(context);
CGRect drawRect = CGRectMake(0 (imageSize.width-CGImageGetWidth(baseImage))/2,
(imageSize.height-CGImageGetHeight(baseImage))/2,
CGImageGetWidth(baseImage),
CGImageGetHeight(baseImage));
CGContextDrawImage(context, drawRect, baseImage);
CGContextBeginTransparencyLayer(context, nil);
CGContextSetAlpha( context, alpha );
CGContextDrawImage(context, drawRect, fadeInImage);
CGContextEndTransparencyLayer(context);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
return pxbuffer;
}
(BOOL)appendToAdapter:(AVAssetWriterInputPixelBufferAdaptor*)adaptor
pixelBuffer:(CVPixelBufferRef)buffer
atTime:(CMTime)presentTime
withInput:(AVAssetWriterInput*)writerInput
{
while (!writerInput.readyForMoreMediaData) {
usleep(1);
}
return [adaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
}
Ответ №1:
CMTimeMake (0, fps); —> Вы не должны использовать 0. CMTimeMake(x, y); определяет длину каждого кадра. x / y секунд. Так, например, если вы установите CMTimeMake (1, 10), это означает, что каждое изображение будет отображаться в течение 1/10 секунды. Я думаю, что ваше видео стало пустым, потому что вы установили 0 для x, это означает 0 / y секунд, которые будут равны 0, поэтому все кадры будут отображаться в течение 0 секунд… Если вы хотите отправить точную длину видео, вы можете сделать это следующим образом:
желаемая длина видео, деленная на количество ваших изображений)… Преобразуйте это в формат x / y, и вы получите то, что вы должны ввести в CMTime.
Если вы хотите сделать 10-секундное видео со 100 кадрами, оно должно выглядеть следующим образом: 10/100 = 0,1 = 1/10 сек / кадр -> CMTimeMake(1,10)
Если вы хотите сделать 30-секундное видео с 1000 кадрами, оно должно выглядеть следующим образом: 30/1000 = 0,03 = 3/100 сек / кадр -> CMTimeMake(3,100)
Надеюсь, это помогло :).