AVAssetWriter Outputting Large File (even when applying compression settings)

I’m working on a personal iOS project that requires full screen videos (15 seconds in length) to be uploaded to a backend over a 4G connection. While I can take videos just fine, the output size of the file comes out to 30MB which makes me think I’m doing something drastically wrong when it comes to compression. Below is the code I’m using to se up the AssetWriter:

-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections
{
    NSLog(@"Started Recording! *******************");
    self.movieWriter = [AVAssetWriter assetWriterWithURL:fileURL fileType:AVFileTypeMPEG4 error:nil];
    [self.movieWriter setShouldOptimizeForNetworkUse:YES];

    NSDictionary *videoCleanApertureSettings = @{
                                                 AVVideoCleanApertureWidthKey: [NSNumber numberWithFloat:self.view.frame.size.width],
                                                 AVVideoCleanApertureHeightKey: [NSNumber numberWithFloat:self.view.frame.size.height],
                                                 AVVideoCleanApertureHorizontalOffsetKey: [NSNumber numberWithInt:10],
                                                 AVVideoCleanApertureVerticalOffsetKey: [NSNumber numberWithInt:10],
                                                 };

    NSDictionary *videoCompressionSettings = @{
                                          AVVideoAverageBitRateKey: [NSNumber numberWithFloat:5000000.0],
                                          AVVideoMaxKeyFrameIntervalKey: [NSNumber numberWithInteger:1],
                                          AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline30,
                                          AVVideoCleanApertureKey: videoCleanApertureSettings,
                                          };

    NSDictionary *videoSettings = @{AVVideoCodecKey: AVVideoCodecH264,
                                    AVVideoWidthKey: [NSNumber numberWithFloat:self.view.frame.size.width],
                                    AVVideoHeightKey: [NSNumber numberWithFloat:self.view.frame.size.height],
                                    AVVideoCompressionPropertiesKey: videoCompressionSettings,
                                    };

    self.movieWriterVideoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
    self.movieWriterVideoInput.expectsMediaDataInRealTime = YES;
    [self.movieWriter addInput:self.movieWriterVideoInput];

    NSDictionary *audioSettings = @{AVFormatIDKey: [NSNumber numberWithInteger:kAudioFormatMPEG4AAC],
                                    AVSampleRateKey: [NSNumber numberWithFloat:44100.0],
                                    AVNumberOfChannelsKey: [NSNumber numberWithInteger:1],
                                    };

    self.movieWriterAudioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
    self.movieWriterAudioInput.expectsMediaDataInRealTime = YES;
    [self.movieWriter addInput:self.movieWriterAudioInput];


    [self.movieWriter startWriting];
}

-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
{
    NSLog(@"Done Recording!");
    [self.movieWriterVideoInput markAsFinished];
    [self.movieWriterAudioInput markAsFinished];
    [self.movieWriter finishWritingWithCompletionHandler:^{
        AVURLAsset *compressedVideoAsset = [[AVURLAsset alloc] initWithURL:self.movieWriter.outputURL options:nil];
        //Upload video to server

    }];
}

For the setup of the actual session I’m using the following code:

  • Trying to compile the FFMPEG libraries for iPhoneOS platform with armv6 and arv7 architecture
  • Swift - How can I get a list of all photos and videos on iPhone?
  • Converting raw data to displayable video for iOS
  • How should I choose a video format to be played on Web, Android & iOS?
  • How can I reduce the file size of a video created with UIImagePickerController?
  • MPMoviePlayerController showing black empty screen
  •             //Indicate that some changes will be made to the session
                [self.captureSession beginConfiguration];
                self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
    
                AVCaptureInput* currentCameraInput = [self.captureSession.inputs objectAtIndex:0];
                for (AVCaptureInput *captureInput in self.captureSession.inputs) {
                    [self.captureSession removeInput:captureInput];
                }
    
    
                //Get currently selected camera and use for input
                AVCaptureDevice *videoCamera = nil;
                if(((AVCaptureDeviceInput*)currentCameraInput).device.position == AVCaptureDevicePositionBack)
                {
                    videoCamera = [self cameraWithPosition:AVCaptureDevicePositionBack];
                }
                else
                {
                    videoCamera = [self cameraWithPosition:AVCaptureDevicePositionFront];
                }
    
                //Add input to session
                AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoCamera error:nil];
                [self.captureSession addInput:newVideoInput];
    
                //Add mic input to the session
                AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
                AVCaptureInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:nil];
                [self.captureSession addInput:audioInput];
    
                //Add movie output to session
                for (AVCaptureOutput *output in self.captureSession.outputs) {
                    [self.captureSession removeOutput:output];
                }
    
                self.movieOutput = [AVCaptureMovieFileOutput new];
                int32_t preferredTimeScale = 30; //Frames per second
                self.movieOutput.maxRecordedDuration = CMTimeMakeWithSeconds(15, preferredTimeScale); //Setting the max video length
                [self.captureSession addOutput:self.movieOutput];
    
                //Commit all the configuration changes at once
                [self.captureSession commitConfiguration];
    

    I know that if I change AVCaptureSessionPresetHigh to a different preset I can reduce the file size of the final video, but unfortunately is looks like AVCaptureSessionPresetiFrame1280x720 is the only one that provides the full frame I’m trying to capture (which leaves me with an output size of about 20MB and is still too large for 4G uploads).

    I’ve spent a lot of time googling and searching through other posts on Stack Overflow, but I can’t seem to figure out what I’m doing wrong for the life of me and any help at all would be greatly appreciated.

    Solutions Collect From Internet About “AVAssetWriter Outputting Large File (even when applying compression settings)”

    You need a PhD to work with AVAssetWriter – it’s non-trivial: https://developer.apple.com/library/mac/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/05_Export.html#//apple_ref/doc/uid/TP40010188-CH9-SW1

    There’s an amazing library for doing exactly what you want which is just an AVAssetExportSession drop-in replacement with more crucial features like changing the bit rate: https://github.com/rs/SDAVAssetExportSession

    Here’s how to use it:

    -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
    {
    
      SDAVAssetExportSession *encoder = [SDAVAssetExportSession.alloc initWithAsset:[AVAsset assetWithURL:[info objectForKey:UIImagePickerControllerMediaURL]]];
      NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
      NSString *documentsDirectory = [paths objectAtIndex:0];
      self.myPathDocs =  [documentsDirectory stringByAppendingPathComponent:
                          [NSString stringWithFormat:@"lowerBitRate-%d.mov",arc4random() % 1000]];
      NSURL *url = [NSURL fileURLWithPath:self.myPathDocs];
      encoder.outputURL=url;
      encoder.outputFileType = AVFileTypeMPEG4;
      encoder.shouldOptimizeForNetworkUse = YES;
    
      encoder.videoSettings = @
      {
      AVVideoCodecKey: AVVideoCodecH264,
      AVVideoCompressionPropertiesKey: @
        {
        AVVideoAverageBitRateKey: @2300000, // Lower bit rate here
        AVVideoProfileLevelKey: AVVideoProfileLevelH264High40,
        },
      };
      encoder.audioSettings = @
      {
      AVFormatIDKey: @(kAudioFormatMPEG4AAC),
      AVNumberOfChannelsKey: @2,
      AVSampleRateKey: @44100,
      AVEncoderBitRateKey: @128000,
      };
    
      [encoder exportAsynchronouslyWithCompletionHandler:^
      {
        int status = encoder.status;
    
        if (status == AVAssetExportSessionStatusCompleted)
        {
          AVAssetTrack *videoTrack = nil;
          AVURLAsset *asset = [AVAsset assetWithURL:encoder.outputURL];
          NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
          videoTrack = [videoTracks objectAtIndex:0];
          float frameRate = [videoTrack nominalFrameRate];
          float bps = [videoTrack estimatedDataRate];
          NSLog(@"Frame rate == %f",frameRate);
          NSLog(@"bps rate == %f",bps/(1024.0 * 1024.0));
          NSLog(@"Video export succeeded");
          // encoder.outputURL <- this is what you want!!
        }
        else if (status == AVAssetExportSessionStatusCancelled)
        {
          NSLog(@"Video export cancelled");
        }
        else
        {
          NSLog(@"Video export failed with error: %@ (%d)", encoder.error.localizedDescription, encoder.error.code);
        }
      }];
    }