AVAssetWriter Outputting Large File (even when

2019-05-09 21:56发布

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:

            //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.

1条回答
闹够了就滚
2楼-- · 2019-05-09 22:46

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);
    }
  }];
}
查看更多
登录 后发表回答