Search code examples
iosavfoundationavassetwriter

Using AVAssetWriter to re-encode H264 mov file - how to set frame-rate?


I'm attempting to re-encode an input MOV file with changeable frame-rate and clipped in duration, in iOS. At the moment I have an AVAssetWriter setting video properties a bit like this:

NSMutableDictionary* compressionPropertiesDict = [NSMutableDictionary new];
compressionPropertiesDict[AVVideoProfileLevelKey] = AVVideoProfileLevelH264High40;

if(self.fps > 0) {
    compressionPropertiesDict[AVVideoAverageNonDroppableFrameRateKey] = [NSNumber numberWithInt:self.fps];


_sessionMgr.videoSettings = @
{
AVVideoCodecKey: AVVideoCodecH264,
AVVideoWidthKey: [NSNumber numberWithFloat:self.outputSize.width],
AVVideoHeightKey: [NSNumber numberWithFloat:self.outputSize.height],
AVVideoCompressionPropertiesKey: compressionPropertiesDict,
};

That comes out at runtime looking like this:

videoSettings = 
{
AVVideoCodecKey = avc1;
AVVideoCompressionPropertiesKey =     {
    AverageNonDroppableFrameRate = 15;
    ProfileLevel = "H264_High_4_0";
};
AVVideoHeightKey = 960;
AVVideoWidthKey = 640;
}

At the end of which, I get a crash with NSInvalidArgumentException: "Compression property AverageNonDroppableFrameRate is not supported for video codec type avc1". (In unit tests using the simulator.)

There's only one codec type that's useable in iOS, AVVideoCodecH264 / "avc1" - and I notice other projects have used the AVVideoAverageNonDroppableFrameRateKey. In fact, I'm using SDAVAssetExportSession and in that codebase I see explicit use of this key. So I would have thought there must be a way to use this key to set frame-rate..?

I've also experimented a bit using AVVideoMaxKeyFrameIntervalKey instead, but that doesn't change my frame-rate at all...

So, to summarise, can anyone help me with setting a different (always lower) output frame-rate for an iOS AVFoundation-based video conversion? Thanks!


Solution

  • As mentioned in the question, I used SDAVAssetExportSession for ease of video export. I made some small changes to it that enabled me to use that easily to change the framerate.

    The main gist is you can change framerate using AVMutableVideoComposition, setting the frameDuration property to your desired framerate, and passing this composition object to the AVAssetReaderVideoCompositionOutput object used in the transcoding.

    In SDAVAssetExportSession's buildDefaultVideoComposition method, I modified it to look a bit like this:

    - (AVMutableVideoComposition *)buildDefaultVideoComposition
    {
      AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
      AVAssetTrack *videoTrack = [[self.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    
      // ...
    
      videoComposition.frameDuration = CMTimeMake(1, myDesiredFramerate);
    
      // ...
    

    That did the trick.