0

Hello guys need a quick help here; I was trying to convert images to video using a tutorial online. The process completes and plays correctly on the iphone/i-devices and on quicktime, however when played on any other player such as VLC realplayer, web players etc, it doesnt play. After taking a look at the final file produced I can see the fps for the video ranges between 0 to 0.16 (ideally should be between 24-30fps). Could any one please suggest a way to fix the fps. the code I used is below:

+ (void)createVideoWithImages:(NSArray <NSString *> *)images withDuration:(long int)duration completion:(void (^) (BOOL success, NSString *output, NSError *error))completion
{
    __block BOOL success                = NO;
    __block NSString *videoOutputPath   = [ViewController newfNameWithPath];
    NSError *error                      = nil;

    double width                    = 0;
    double height                   = 0;
    CGSize imageSize                = CGSizeZero;
    NSUInteger framesPerSecond      = 30;
    NSMutableArray *imageArray      = [[NSMutableArray alloc] initWithCapacity:images.count];

    for (NSUInteger i = 0; i < [images count]; i++)
    {
        NSString *imagePath = [images objectAtIndex:i];
        [imageArray addObject:[UIImage imageWithContentsOfFile:imagePath]];

        UIImage *newImage = [ViewController createImage:((UIImage *)[imageArray objectAtIndex:i])];

        UIImage *img = [ViewController imageOrientation:newImage];
        [imageArray replaceObjectAtIndex:i withObject:img];

        width   = (width > ((UIImage *)[imageArray lastObject]).size.width) ? width : ((UIImage *)[imageArray lastObject]).size.width;
        height  = (height > ((UIImage *)[imageArray lastObject]).size.height) ? height : ((UIImage *)[imageArray lastObject]).size.height;
    }

    imageSize  = CGSizeMake(1280, 720);

    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:[ViewController getURL:videoOutputPath] fileType:AVFileTypeQuickTimeMovie error:&error];
    NSParameterAssert(videoWriter);

    NSDictionary *videoCleanApertureSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                                [NSNumber numberWithInt:1280], AVVideoCleanApertureWidthKey,
                                                [NSNumber numberWithInt:720], AVVideoCleanApertureHeightKey,
                                                [NSNumber numberWithInt:10], AVVideoCleanApertureHorizontalOffsetKey,
                                                [NSNumber numberWithInt:10], AVVideoCleanApertureVerticalOffsetKey,
                                                nil];

    NSDictionary *aspectRatioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                         [NSNumber numberWithInt:3], AVVideoPixelAspectRatioHorizontalSpacingKey,
                                         [NSNumber numberWithInt:3],AVVideoPixelAspectRatioVerticalSpacingKey,
                                         nil];

    NSDictionary *codecSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                   [NSNumber numberWithInt:960000], AVVideoAverageBitRateKey,
                                   [NSNumber numberWithInt:1],AVVideoMaxKeyFrameIntervalKey,
                                   videoCleanApertureSettings, AVVideoCleanApertureKey,
                                   aspectRatioSettings, AVVideoPixelAspectRatioKey,
                                   //AVVideoProfileLevelH264Main30, AVVideoProfileLevelKey,
                                   nil];

    NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                   AVVideoCodecH264, AVVideoCodecKey,
                                   codecSettings,AVVideoCompressionPropertiesKey,
                                   [NSNumber numberWithInt: 1280], AVVideoWidthKey,
                                   [NSNumber numberWithInt: 720], AVVideoHeightKey,
                                   nil];


    AVAssetWriterInput* videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput sourcePixelBufferAttributes:nil];

    NSParameterAssert(videoWriterInput);
    NSParameterAssert([videoWriter canAddInput:videoWriterInput]);
    videoWriterInput.expectsMediaDataInRealTime = YES;
    [videoWriter addInput:videoWriterInput];


    videoWriterInput.transform = CGAffineTransformMakeRotation(M_PI_2 * 2);

    if (![videoWriter startWriting])
    {
        completion(success, nil, videoWriter.error);
    }

    [videoWriter startSessionAtSourceTime:kCMTimeZero];

    CVPixelBufferRef buffer         = NULL;
    int frameCount                  = 0;
    double numberOfSecondsPerFrame  = duration / [imageArray count];
    double frameDuration            = framesPerSecond * numberOfSecondsPerFrame;

    for(UIImage *image in imageArray)
    {
        buffer                      = [ViewController pixelBufferFromCGImage:[image CGImage] withSize:imageSize];
        BOOL completeWitnNoError    = NO;
        int counter                 = 0;

        while (!completeWitnNoError && counter < 30)
        {

            NSLog(@"writing image: %ld at frame: %d",(long)frameCount, counter);
            if (adaptor.assetWriterInput.readyForMoreMediaData)
            {

                CMTime frameTime        = CMTimeMake(frameCount*frameDuration, (int32_t)framesPerSecond);
                CMTimeShow(frameTime);
                completeWitnNoError     = [adaptor appendPixelBuffer:buffer withPresentationTime:frameTime];

                if(!completeWitnNoError)
                {
                    if(buffer)
                        CVPixelBufferRelease(buffer);

                    completion(!completion, nil, videoWriter.error);
                }
            }
            else
            {
                if(buffer)
                    CVPixelBufferRelease(buffer);

                NSLog(@"adaptor not ready %d, %d\n", frameCount, counter);
                [NSThread sleepForTimeInterval:0.1];
            }

            counter++;
        }

        if(buffer)
            CVPixelBufferRelease(buffer);

        if (!completeWitnNoError)
        {
            NSDictionary *userInfo = @{
                                       NSLocalizedDescriptionKey:               NSLocalizedString(@"Error\n", nil),
                                       NSLocalizedFailureReasonErrorKey:        NSLocalizedString(@"Images to video writing failed ", nil),
                                       NSLocalizedRecoverySuggestionErrorKey:   NSLocalizedString(@"Images to video writing failed ", nil)
                                       };

            NSError *error = [NSError errorWithDomain:@"Images to video error" code: -1005 userInfo:userInfo];
            completion(!completion, nil, error);
        }

        frameCount++;
    }

    [videoWriterInput markAsFinished];
    [videoWriter finishWritingWithCompletionHandler:^{

        switch ([videoWriter status])
        {
            case AVAssetReaderStatusReading:
                break;

            case AVAssetReaderStatusCompleted:
            {
                NSLog(@"IMAGES TO VIDEO WRITE SUCCESS");
                success = !success;
                completion(success, videoOutputPath, videoWriter.error);
                break;
            }
            case AVAssetReaderStatusCancelled:
            {
                NSLog(@"IMAGES TO VIDEO WRITE FAILURE");
                completion(success, nil, videoWriter.error);
                [videoWriter cancelWriting];
                break;
            }

            case AVAssetReaderStatusFailed:
            {
                NSLog(@"IMAGES TO VIDEO WRITE FAILURE");
                completion(success, nil, videoWriter.error);
                [videoWriter cancelWriting];
                break;
            }

            case AVAssetReaderStatusUnknown:
            {
                NSLog(@"IMAGES TO VIDEO WRITE FAILURE");
                completion(success, nil, videoWriter.error);
                [videoWriter cancelWriting];
                break;
            }
        }
    }];

    return;
}

and the buffer:

+ (CVPixelBufferRef) pixelBufferFromCGImage:(CGImageRef)image withSize:(CGSize)size
{

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
                             nil];

    CVPixelBufferRef pxbuffer = NULL;
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,
                                          size.width,
                                          size.height,
                                          kCVPixelFormatType_32ARGB,
                                          (__bridge CFDictionaryRef) options,
                                          &pxbuffer);

    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);

    if (status != kCVReturnSuccess)
    {
        NSString *description  = [NSString stringWithFormat:@"Failed to create pixel buffer."];
        NSDictionary *userInfo = @{
                                   NSLocalizedDescriptionKey:               NSLocalizedString(description, nil),
                                   NSLocalizedFailureReasonErrorKey:        NSLocalizedString(@"Could create buffer", nil),
                                   NSLocalizedRecoverySuggestionErrorKey:   NSLocalizedString(@"Try again", nil)
                                   };

        NSError *error = [NSError errorWithDomain:@"CVPixelBufferRef" code: -1005 userInfo:userInfo];
        NSLog(@"ALERT: %@", [error localizedDescription]);
    }

    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
    NSParameterAssert(pxdata != NULL);

    CGColorSpaceRef rgbColorSpace   = CGColorSpaceCreateDeviceRGB();
    CGContextRef context            = CGBitmapContextCreate(pxdata, size.width,
                                                            size.height, 8, 4*size.width, rgbColorSpace,
                                                            kCGImageAlphaPremultipliedFirst);


    CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
    CGContextDrawImage(context, CGRectMake((size.width/(CGImageGetWidth(image) / 2)), (size.height/(CGImageGetWidth(image) / 2)), 1280, 720), image);

    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);
    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

    return pxbuffer;
}

Any suggestions??

  • We know your `framesPerSecond` is `30` but... What is the value of `duration`also how many images are in `[imageArray count]`? Also you likely want a video bitrate of `2500000` because 960 k/b makes a low picture quality for 720p. – VC.One May 30 '16 at 23:42
  • **_"...Could any one please suggest a way to fix the fps?"_**. Never mind, I had a silly idea that maybe if you shared those numbers then some volunteer could help you work out what's wrong. I suspect an issue with your input numbers VS your divide & multiply code. Also a video always has **one FPS rate**, it does not do a _"...the fps for the video ranges between 0 to 0.16"_ so what's going on? Give all facts needed to recreate your problem (not just code dump with unknown mystery inputs)... – VC.One Jun 02 '16 at 06:12

1 Answers1

1

Try to use CEMovieMaker i used it before and works like charm as i think that your problem is in the videoCodec

https://github.com/cameronehrlich/CEMovieMaker

    @interface ViewController ()

@property (nonatomic, strong) CEMovieMaker *movieMaker;

@end

- (IBAction)process:(id)sender
{
    NSMutableArray *frames = [[NSMutableArray alloc] init];

    UIImage *icon1 = [UIImage imageNamed:@"icon1"];
    UIImage *icon2 = [UIImage imageNamed:@"icon2"];
    UIImage *icon3 = [UIImage imageNamed:@"icon3"];

    NSDictionary *settings = [CEMovieMaker videoSettingsWithCodec:AVVideoCodecH264 withHeight:icon1.size.width andWidth:icon1.size.height];
    self.movieMaker = [[CEMovieMaker alloc] initWithSettings:settings];
    for (NSInteger i = 0; i < 10; i++) {
        [frames addObject:icon1];
        [frames addObject:icon2];
        [frames addObject:icon3];
    }

    [self.movieMaker createMovieFromImages:[frames copy] withCompletion:^(BOOL success, NSURL *fileURL){
        if (success) {
            [self viewMovieAtUrl:fileURL];
        }
    }];
}

- (void)viewMovieAtUrl:(NSURL *)fileURL
{
    MPMoviePlayerViewController *playerController = [[MPMoviePlayerViewController alloc] initWithContentURL:fileURL];
    [playerController.view setFrame:self.view.bounds];
    [self presentMoviePlayerViewControllerAnimated:playerController];
    [playerController.moviePlayer prepareToPlay];
    [playerController.moviePlayer play];
    [self.view addSubview:playerController.view];
}
Mohamed Mostafa
  • 1,057
  • 7
  • 12
  • Thanks Mohamed it was quite help, but the CEMovieMaker creates a video with 10fps video and the images switch between one another really quickly. any reason why?? – user3355301 May 30 '16 at 20:41
  • He use to repeat adding 3 images 10 times to be 30 images Here to use your images & avoid switching for (UIImage *image in imageArray) { UIImageView *temptemp=[[UIImageView alloc] initWithImage:image]; [finalimgs addObject:temptemp]; } [self.movieMaker createMovieFromImages:finalimgs ....... to change frame rate 10fps you should change CEMovieMaker's - (instancetype)initWithSettings:(NSDictionary *)videoSettings; _frameTime = CMTimeMake(1, 30); – Mohamed Mostafa May 31 '16 at 00:37