0

I am trying to trim and then compress a video file.

  1. For trimming I am using AVAssetExportSession
  2. For compression I am using AVAssetWriter.

If I use both codes individually every thing works fine but If I trim and then feed the trim output for compression I got compressed but corrupt video.

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self trimVideo];
}

Trimming Code

-(void)trimVideo {

    AVAsset *anAsset = [[AVURLAsset alloc]initWithURL:[self.asset valueForProperty:ALAssetPropertyAssetURL] options:nil];
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:anAsset presetName:AVAssetExportPresetPassthrough];

    NSString *fName = [NSString stringWithFormat:@"%@.%@", @"tempVid", @"mp4"];
    saveURL = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fName]];

    NSString *fName1 = [NSString stringWithFormat:@"%@.%@", @"tempVid1", @"mp4"];
    saveURL1 = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fName1]];

    exportSession.outputURL = saveURL;
    exportSession.outputFileType = AVFileTypeMPEG4;

    CMTime start = CMTimeMakeWithSeconds(1.0, 600);
    CMTime duration = CMTimeMakeWithSeconds(180.0, 600);
    CMTimeRange range = CMTimeRangeMake(start, duration);
    exportSession.timeRange = range;

    [exportSession exportAsynchronouslyWithCompletionHandler:^{

        switch ([exportSession status]) {
            case AVAssetExportSessionStatusFailed:
                NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
                break;
            case AVAssetExportSessionStatusCancelled:
                NSLog(@"Export canceled");
                break;
            case AVAssetExportSessionStatusCompleted:
                [self convertVideoToLowQuailtyWithInputURL:saveURL outputURL:saveURL1];
                break;
            default:
                break;
        }
    }];
}

Compression Code

- (void)convertVideoToLowQuailtyWithInputURL:(NSURL*)inputURL outputURL:(NSURL*)outputURL
{
    AVAsset *videoAsset = [[AVURLAsset alloc] initWithURL:inputURL options:nil];

    AVAssetTrack *videoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

    //CGSize videoSize = videoTrack.naturalSize;

    NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey,
                              [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:960000],AVVideoAverageBitRateKey, AVVideoProfileLevelH264Main32, AVVideoProfileLevelKey,
                               [NSNumber numberWithInt:24], AVVideoMaxKeyFrameIntervalKey, [NSNumber numberWithInt:0.0], AVVideoMaxKeyFrameIntervalDurationKey, nil],
                              AVVideoCompressionPropertiesKey, [NSNumber numberWithInt:640], AVVideoWidthKey, [NSNumber numberWithInt:320], AVVideoHeightKey, nil];

    AVAssetWriterInput* videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];

    videoWriterInput.expectsMediaDataInRealTime = YES;

    videoWriterInput.transform = videoTrack.preferredTransform;

    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:nil];

    [videoWriter addInput:videoWriterInput];


    NSDictionary *videoReaderSettings = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey];

    AVAssetReaderTrackOutput *videoReaderOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:videoReaderSettings];

    AVAssetReader *videoReader = [[AVAssetReader alloc] initWithAsset:videoAsset error:nil];

    [videoReader addOutput:videoReaderOutput];



    AVAssetWriterInput* audioWriterInput = [AVAssetWriterInput
                                            assetWriterInputWithMediaType:AVMediaTypeAudio
                                            outputSettings:nil];

    audioWriterInput.expectsMediaDataInRealTime = NO;

    [videoWriter addInput:audioWriterInput];



    AVAssetTrack* audioTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];

    AVAssetReaderOutput *audioReaderOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:nil];

    AVAssetReader *audioReader = [AVAssetReader assetReaderWithAsset:videoAsset error:nil];

    [audioReader addOutput:audioReaderOutput];

    [videoWriter startWriting];



    [videoReader startReading];

    [videoWriter startSessionAtSourceTime:kCMTimeZero];

    dispatch_queue_t processingQueue = dispatch_queue_create("processingQueue1", NULL);

    [videoWriterInput requestMediaDataWhenReadyOnQueue:processingQueue usingBlock:
     ^{

         while ([videoWriterInput isReadyForMoreMediaData]) {

             CMSampleBufferRef sampleBuffer;

             if ([videoReader status] == AVAssetReaderStatusReading &&
                 (sampleBuffer = [videoReaderOutput copyNextSampleBuffer])) {

                 [videoWriterInput appendSampleBuffer:sampleBuffer];
                 CFRelease(sampleBuffer);
             }

             else {

                 [videoWriterInput markAsFinished];

                 if ([videoReader status] == AVAssetReaderStatusCompleted) {

                     //start writing from audio reader
                     [audioReader startReading];

                     [videoWriter startSessionAtSourceTime:kCMTimeZero];

                     dispatch_queue_t processingQueue = dispatch_queue_create("processingQueue2", NULL);

                     [audioWriterInput requestMediaDataWhenReadyOnQueue:processingQueue usingBlock:^{

                         while (audioWriterInput.readyForMoreMediaData) {

                             CMSampleBufferRef sampleBuffer;

                             if ([audioReader status] == AVAssetReaderStatusReading &&
                                 (sampleBuffer = [audioReaderOutput copyNextSampleBuffer])) {

                                 [audioWriterInput appendSampleBuffer:sampleBuffer];
                                 CFRelease(sampleBuffer);
                             }

                             else {

                                 [audioWriterInput markAsFinished];

                                 if ([audioReader status] == AVAssetReaderStatusCompleted) {

                                     [videoWriter finishWritingWithCompletionHandler:^(){

                                         NSLog(@"Success");
                                     }];

                                 }
                             }
                         }

                     }
                      ];
                 }
             }
         }
     }
     ];
}
S.J
  • 3,063
  • 3
  • 33
  • 66
  • if your AVAssetWriter corrupting then it sure it given you something error code yeah error description. My question is what is that code or error?? Generally export session corrupted due to unsupported file format in which you trying to export or your frame and sound time isn't synchronized. – Tirth Oct 17 '14 at 10:52
  • @Tirth No error no crash, just corrupt video file. – S.J Oct 17 '14 at 10:53
  • Are you sure you go through AVMutableCompositionTrack class ??? – Tirth Oct 17 '14 at 10:58
  • I'm not seen any line of use of AVMutableCompositionTrack. – Tirth Oct 17 '14 at 10:59
  • @Tirth No I have no AVMutableCompositionTrack in my code. – S.J Oct 17 '14 at 11:01
  • 1
    @S.J Your compression code is very popular and variations of it have been shared in other SO questions. For some reason, I always get this error: "[AVAssetReader startReading] cannot be called again after reading has already started" – user3344977 Dec 17 '14 at 05:59

1 Answers1

1

You must use AVMutableCompositionTrack class and for media file trimming you must need use - (void)removeTimeRange:(CMTimeRange)timeRange method.

AVAsset *videoAsset = <#AVAsset with at least one video track#>;

AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:videoAssetTrack];

if (compatibleCompositionTrack) {

// Implementation continues.
 [compatibleCompositionTrack removeTimeRange: yourTimeRange];

 //Export Now your media file using exportSession

   AVAssetExportSession* _assetExport = [[AVAssetExportSession alloc] initWithAsset:compatibleCompositionTrack presetName:AVAssetExportPresetHighestQuality];
     exportSession.outputFileType = AVFileTypeMPEG4;
    _assetExport.outputURL = outputFileUrl;

    [_assetExport exportAsynchronouslyWithCompletionHandler:
     ^(void ) {

         switch ([exportSession status]) {
        case AVAssetExportSessionStatusFailed:
            NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
            break;
        case AVAssetExportSessionStatusCancelled:
            NSLog(@"Export canceled");
            break;
        case AVAssetExportSessionStatusCompleted:
            [self convertVideoToLowQuailtyWithInputURL:saveURL outputURL:saveURL1];
            break;
        default:
            break;
       }
     }
     ];
}
Tirth
  • 7,801
  • 9
  • 55
  • 88
  • this line have some issues AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:videoAssetTrack]; – S.J Oct 17 '14 at 11:45
  • Which issue, please describe and mention what ever error you facing its better for us. – Tirth Oct 17 '14 at 11:57
  • xcode is highlighting mutableComposition and suggesting to use AVMutableComposition, but AVMutableComposition has no mutableTrackCompatibleWithTrack. – S.J Oct 17 '14 at 12:04
  • @S.J sorry i was forgot to write up mutableComposition initialization line. Check updated code and edit it. – Tirth Oct 17 '14 at 12:13
  • this line has wrong init parameter and if I use videoAsset as parameter then compatibleCompositionTrack will be unused AVAssetExportSession* _assetExport = [[AVAssetExportSession alloc] initWithAsset:compatibleCompositionTrack presetName:AVAssetExportPresetHighestQuality]; – S.J Oct 17 '14 at 12:19
  • BTW, this not real code. I just giving you basic structure to deal with or use of media file using AVFoundation framework export session. – Tirth Oct 17 '14 at 12:22
  • Check dependencies of compatibleCompositionTrack. Without method of removeTimeRange: you can't trim the video. – Tirth Oct 17 '14 at 12:23
  • I am unable to resolve the issue, I am referring apple avfoundation doc but it is also passing mutableComposition in initWithAsset. And output is nothing. – S.J Oct 17 '14 at 13:11
  • [mutableComposition mutableTrackCompatibleWithTrack:videoAssetTrack] is returning nil. – S.J Oct 17 '14 at 13:14
  • That mean your videoAssetTrack media file having problem. Check videoAssetTrack variable having videoAsset or not. – Tirth Oct 17 '14 at 13:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/63229/discussion-between-tirth-and-s-j). – Tirth Oct 17 '14 at 13:15