Friday 17 April 2015

MP4 Conversion and Video Merging in iOS


MP4 Conversion


Sometimes we need to develop applications both for android and iOS that uses the videos captured from the devices.By default iOS saves the video in .mov format which can not be played on the android devices.To make the iOS videos compatible for all devices(android and iOS) we use a common video format and that is .mp4. Now we can achieve this by using AVFoundation framework. This blog will provide the steps to convert video in mp4 format.


// Create the asset url with the video file
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];

// Check if video is supported for conversion or not
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality])
{
//Create Export session
     AVAssetExportSession *exportSession = [[AVAssetExportSession       alloc]initWithAsset:avAsset presetName:AVAssetExportPresetLowQuality];

//Creating temp path to save the converted video
     NSString* documentsDirectory=     [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
     NSString* myDocumentPath= [documentsDirectory stringByAppendingPathComponent:@"temp.mp4"];
     NSURL *url = [[NSURL alloc] initFileURLWithPath:myDocumentPath];

//Check if the file already exists then remove the previous file
     if ([[NSFileManager defaultManager]fileExistsAtPath:myDocumentPath])
     {
          [[NSFileManager defaultManager]removeItemAtPath:myDocumentPath error:nil];
     }
     exportSession.outputURL = url;
     //set the output file format if you want to make it in other file format (ex .3gp)
     exportSession.outputFileType = AVFileTypeMPEG4;
     exportSession.shouldOptimizeForNetworkUse = YES;

     [exportSession exportAsynchronouslyWithCompletionHandler:^{
     switch ([exportSession status])
     {
          case AVAssetExportSessionStatusFailed:
               NSLog(@"Export session failed");
               break;
          case AVAssetExportSessionStatusCancelled:
               NSLog(@"Export canceled");
               break;
          case AVAssetExportSessionStatusCompleted:
          {
               //Video conversion finished
               NSLog(@"Successful!");
          }
               break;
          default:
               break;
      }
     }];
}
else
{
       NSLog(@"Video file not supported!");
}

Videos Merging(Adding two videos)



While developing iOS applications some times we required a functionality where we have to merge small video clips in one large video file. Now this we can achieve through iOS AVFoundation framework.

//Create the AVMutable composition to add tracks
AVMutableComposition* composition = [[AVMutableComposition alloc]init];

//Create assets url for first video
AVURLAsset* video1 = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:path1]options:nil];

//Create assets url for second video
AVURLAsset* video2 = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:path2]options:nil];

//Create the mutable composition track with video media type. You can also create the tracks depending on your need if you want to merge audio files and other stuffs.
AVMutableCompositionTrack* composedTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];

//Set the video time ranges of both the videos in composition
[composedTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, video1.duration)
ofTrack:[[video1 tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
atTime:kCMTimeZero error:nil];

[composedTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, video2.duration)
ofTrack:[[video2 tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
atTime:video1.duration error:nil];

// Create a temp path to save the video in the documents dir.
NSString* documentsDirectory= [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

NSString* myDocumentPath= [documentsDirectory stringByAppendingPathComponent:@"merge_video.mp4"];

NSURL *url = [[NSURL alloc] initFileURLWithPath: myDocumentPath];

//Check if the file exists then delete the old file to save the merged video file.
if([[NSFileManager defaultManager]fileExistsAtPath:myDocumentPath])
{
          [[NSFileManager defaultManager]removeItemAtPath:myDocumentPath error:nil];
}

// Create the export session to merge and save the video
AVAssetExportSession*exporter = [[AVAssetExportSession alloc]initWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
exporter.outputURL=url;
exporter.outputFileType=@"com.apple.quicktime-movie";
exporter.shouldOptimizeForNetworkUse=YES;
[exporter exportAsynchronouslyWithCompletionHandler:^{
          switch([exporter status])
          {
                    case AVAssetExportSessionStatusFailed:
                         NSLog(@"Failed to export video");
                    break;
                    case AVAssetExportSessionStatusCancelled:
                         NSLog(@"export cancelled");
                    break;
                    case AVAssetExportSessionStatusCompleted:
                         //Here you go you have got the merged video :)
                         NSLog(@"Merging completed");

                    break;
                    default:
                    break;
          }

}];


For Mixing audio and video


//From above code to mix a audio with video simply create a mutable composition of audio track and insert it in the video time range.

NSArray *pathComponents = [NSArray arrayWithObjects:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],@"MyAudio.m4a",nil];

NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
AVAsset *audioAsset = [AVAsset assetWithURL:outputFileURL];

//Create mutable composition of audio type
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,firstAsset.duration)
ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];



Here is sample project with all of the code from the above tutorial.

42 comments:

  1. it works but remaining video getting mute.

    ReplyDelete
    Replies
    1. It will not make the video mute.Check your integration again and if you face any problem, share you code. I will be happy to help you.

      Delete
  2. because of no audio in my videos i am getting error for --> index 0 beyond bounds for empty NSArray' Please provide solution

    ReplyDelete
    Replies
    1. If you are mixing the audio and video then you should apply the check that file should exist in the path then only the code should execute.

      If there is no file then it will crash, so make sure file exists.

      Please provide more details as what you are doing so that I can better help you.

      Delete
  3. Hello

    It is not working giving me an error index 0 beyond bounds for empty NSArray'.. Because my videos dont have audio.. Please provide solution

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Please check for the array count before you access any index of array... Please make habit of checking count of array if it is greater then 0 then only proceed... put condition like that before execution.

      Delete
  4. I am aiyub
    above is my code to merge two videos i've checked both videos are ready and complete at directory. Buyt the problem is both dont contain audio

    ReplyDelete
    Replies
    1. Hi Aiyub,

      What I have understand from your issue is that you are merging two videos that does not contain the audio and you are getting the crash.

      It should not crash until video have some issue. Could you replace your videos in my sample project and check that you are getting the same crash or not.

      I have checked with some video without audio and it's working fine.

      Please check and let me know if you still face the issue.

      Also, try with some different videos and if possible share the videos so that I can check and better help you.

      Thanks,
      Pradeep

      Delete
  5. OK I don't know why but I tried it with different paths and yepp.. It's working Now!!!!!

    ReplyDelete
  6. Pradeep .. Can you please help me into this???
    http://stackoverflow.com/questions/37762393/save-video-with-dezired-playback-speed-in-ios

    ReplyDelete
    Replies
    1. I don't think it is possible but I will check more and will let you know if I will found any solution.

      Delete
  7. Hi ,

    This is Sharukh, an iOS developer from India. This code is not working with iOS 10. Can anybody let me know how we can merge videos in iOS 10.

    Any help will be highly appreciated.

    ReplyDelete
  8. Hi ,

    This is Sharukh, an iOS developer from India. This code is not working with iOS 10. Can anybody let me know how we can merge videos in iOS 10.

    Any help will be highly appreciated.

    ReplyDelete
    Replies
    1. Could you please let me know what exact issue you are getting while merging ?

      Delete
    2. Hi Sharooque,

      This code is also working with iOS 11... I have checked this code...

      Delete
  9. Hi Pradeep,

    When I am checking the instance of AVASSetExportSession, it is null. Lets' see following code :

    AVAssetExportSession*exporter = [[AVAssetExportSession alloc]initWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
    exporter.outputURL=url;
    exporter.outputFileType=@"com.apple.quicktime-movie";
    exporter.shouldOptimizeForNetworkUse=YES;
    NSLog(@"exporter == %@",exporter);

    The ouptput is : exporter == null

    ReplyDelete
  10. Kindly suggest what might be problem here.

    ReplyDelete
  11. // 2.3 - Create an AVMutableVideoCompositionLayerInstruction for the second track
    AVMutableVideoCompositionLayerInstruction *secondlayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:secondTrack];
    AVAssetTrack *secondAssetTrack = [[secondAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    UIImageOrientation secondAssetOrientation_ = UIImageOrientationUp;
    BOOL isSecondAssetPortrait_ = NO;
    CGAffineTransform secondTransform = secondAssetTrack.preferredTransform;
    if (secondTransform.a == 0 && secondTransform.b == 1.0 && secondTransform.c == -1.0 && secondTransform.d == 0) {
    secondAssetOrientation_= UIImageOrientationRight;
    isSecondAssetPortrait_ = YES;
    }
    if (secondTransform.a == 0 && secondTransform.b == -1.0 && secondTransform.c == 1.0 && secondTransform.d == 0) {
    secondAssetOrientation_ = UIImageOrientationLeft;
    isSecondAssetPortrait_ = YES;
    }
    if (secondTransform.a == 1.0 && secondTransform.b == 0 && secondTransform.c == 0 && secondTransform.d == 1.0) {
    secondAssetOrientation_ = UIImageOrientationUp;
    }
    if (secondTransform.a == -1.0 && secondTransform.b == 0 && secondTransform.c == 0 && secondTransform.d == -1.0) {
    secondAssetOrientation_ = UIImageOrientationDown;
    }
    [secondlayerInstruction setTransform:secondAsset.preferredTransform atTime:firstAsset.duration];

    ReplyDelete
  12. // 2.4 - Add instructions
    mainInstruction.layerInstructions = [NSArray arrayWithObjects:firstlayerInstruction, secondlayerInstruction,nil];
    AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];
    mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction];
    mainCompositionInst.frameDuration = CMTimeMake(1, 30);

    CGSize naturalSizeFirst, naturalSizeSecond;
    if(isFirstAssetPortrait_){
    naturalSizeFirst = CGSizeMake(firstAssetTrack.naturalSize.height, firstAssetTrack.naturalSize.width);
    } else {
    naturalSizeFirst = firstAssetTrack.naturalSize;
    }
    if(isSecondAssetPortrait_){
    naturalSizeSecond = CGSizeMake(secondAssetTrack.naturalSize.height, secondAssetTrack.naturalSize.width);
    } else {
    naturalSizeSecond = secondAssetTrack.naturalSize;
    }

    float renderWidth, renderHeight;
    if(naturalSizeFirst.width > naturalSizeSecond.width) {
    renderWidth = naturalSizeFirst.width;
    } else {
    renderWidth = naturalSizeSecond.width;
    }
    if(naturalSizeFirst.height > naturalSizeSecond.height) {
    renderHeight = naturalSizeFirst.height;
    } else {
    renderHeight = naturalSizeSecond.height;
    }
    mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight);

    ReplyDelete
  13. // 4 - Get path
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *video_title = [NSString stringWithFormat:@"%@%@",titleLbl.text,@".mov"];

    NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent:
    video_title];
    // NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent:
    //[NSString stringWithFormat:@"mergeVideo-%d.mov",arc4random() % 1000]];

    if([[NSFileManager defaultManager] fileExistsAtPath:myPathDocs]){
    NSError *error;
    [[NSFileManager defaultManager] removeItemAtPath:myPathDocs error:&error];
    if (error){
    NSLog(@"%@", error);
    }
    else{
    NSLog(@"file deleted olddddd");
    }
    }
    else{
    NSLog(@"File doesn't exist ......");
    }

    ReplyDelete
  14. NSURL *url = [NSURL fileURLWithPath:myPathDocs];
    // 5 - Create exporter

    NSLog(@"mixComposition == %@",mixComposition);
    AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition
    presetName:AVAssetExportPresetHighestQuality];
    exporter.outputURL=url;
    NSLog(@"url == %@",url);
    exporter.outputFileType = AVFileTypeQuickTimeMovie;
    exporter.shouldOptimizeForNetworkUse = YES;
    exporter.videoComposition = mainCompositionInst;
    NSLog(@"mainCompositionInst == %@",mainCompositionInst);
    NSLog(@"exporter === %@",exporter);
    [exporter exportAsynchronouslyWithCompletionHandler:^{
    NSLog(@"Did it execute ?");
    dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"Export Now ....");
    [self exportDidFinish:exporter];
    });
    }];

    ReplyDelete
  15. I've posted my complete code for your convenience.

    ReplyDelete
    Replies
    1. Have you check the attached sample project with your videos? It seems somewhere your mixComposition (asset) is not created correct.

      Delete
  16. Hi Pradeep,

    I've tried sample code with my videos. It's not working. When I am saving videos in resource folder within the application, it's working fine. But when I am fetching videos from library, it's not working at all.

    ReplyDelete
  17. Pradeep, I am using ASAsset for creating the URLs of the videos. Is there any problem with that ? Sample code is here :

    for(ALAsset *mediaALasset in self.selectedVideosArray){
    NSURL *URL = [[mediaALasset defaultRepresentation] url];
    [allURLArray addObject:URL];
    }

    ReplyDelete
    Replies
    1. You have to copy the video to document or temp directory and then you have to use the file path.

      Delete
  18. Any one can suggest me that how can i get the audio file from recorded video file ?

    ReplyDelete
  19. If you have download song then mp3 option is right if you want to Look Ace Baker download video then you will have to download it in mp4 format.

    ReplyDelete