2

I am using AVAssetWriter to write video frames from ARSession using delegate.

func session(_ session: ARSession, didUpdate frame: ARFrame)

See below the code used to write images.

How can I set custom frame rate like 24, 30 or 60 etc as per our needs.

In output settings the value given for AVVideoExpectedSourceFrameRateKey is 30. But what ever value we given for it, Always getting 'Frame Rate' as 60 when check with VLC player -> media information -> Codec Details

What changes should I make to set desired frame rate? Thanks in Advance.

func writeImage(_ image: CVPixelBuffer, thisTimestamp: TimeInterval) {

        guard let videoDirector = videoWriter else { return }

        serialQueue.async(execute: {

            let scale = CMTimeScale(NSEC_PER_SEC)

            if (!self.seenTimestamps.contains(thisTimestamp)) {

                self.seenTimestamps.append(thisTimestamp)
                let pts = CMTime(value: CMTimeValue((thisTimestamp) * Double(scale)),
                                 timescale: scale)
                var timingInfo = CMSampleTimingInfo(duration: kCMTimeInvalid,
                                                    presentationTimeStamp: pts,
                                                    decodeTimeStamp: kCMTimeInvalid)

                var vidInfo:CMVideoFormatDescription! = nil
                CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, image, &vidInfo)

                var sampleBuffer:CMSampleBuffer! = nil
                CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, image, true, nil, nil, vidInfo, &timingInfo, &sampleBuffer)

                let imageBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!

                if self.videoWriterInput == nil {

                    let width = CVPixelBufferGetWidth(imageBuffer)
                    let height = CVPixelBufferGetHeight(imageBuffer)


                    let numPixels: Double = Double(width * height);
                    let bitsPerPixel = 11.4;
                    let bitsPerSecond = Int(numPixels * bitsPerPixel)

                    // add video input
                    let outputSettings: [String: Any] = [
                        AVVideoCodecKey : AVVideoCodecType.h264,
                        AVVideoWidthKey : width,
                        AVVideoHeightKey : height,
                        AVVideoCompressionPropertiesKey : [
                            AVVideoExpectedSourceFrameRateKey: 30,
                            AVVideoAverageBitRateKey : bitsPerSecond,
                            AVVideoMaxKeyFrameIntervalKey : 1
                        ]
                    ]
                    self.videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: outputSettings)
                    self.videoWriterInput?.expectsMediaDataInRealTime = true
                    guard let input = self.videoWriterInput else { return }

                    if videoDirector.canAdd(input) {
                        videoDirector.add(input)
                    }
                    videoDirector.startWriting()
                }

                let writable = self.canWrite()
                if writable, self.sessionAtSourceTime == nil {
                    let timeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
                    self.sessionAtSourceTime = timeStamp
                    videoDirector.startSession(atSourceTime: timeStamp)
                }

                if self.videoWriterInput?.isReadyForMoreMediaData == true {
                    let appendResult = self.videoWriterInput?.append(sampleBuffer)
                    if appendResult == false {
                        printDebug("writer status: \(videoDirector.status.rawValue)")
                        printDebug("writer error: \(videoDirector.error.debugDescription)")
                    }
                }
            }
        })
    }
    func canWrite() -> Bool {
        return isRecording && videoWriter?.status == .writing
    }
jpulikkottil
  • 666
  • 9
  • 24
  • The documentation for [AVVideoExpectedSourceFrameRateKey](https://developer.apple.com/documentation/avfoundation/avvideoexpectedsourceframeratekey) says 'This is not used to control the frame rate; it is provided as a hint to the video encoder'. Perhaps the CMSampleTimingInfo.duration needs to be set to the desired frame rate? – Jonathan Feb 23 '19 at 00:28
  • @jpulikkottil , Did you find any solution for above issue ? – Hardik Thakkar Jun 14 '19 at 12:59
  • What we can do is start a timer and get the image from ARCamera. So if you need 60 frames per second, then start a timer with interval 1/60. But not recommended to do this unless it is necessary. – jpulikkottil Jun 18 '19 at 13:47

1 Answers1

1

Reply from Apple support:

if you want to actually change the frame rate of the movie with AVAssetWriter, then you would have to correctly set the timestamps for each individual video frame as you write them out.

One way to do this is with an AVAssetWriterInputPixelBufferAdaptor object. For example, you use an AVAssetWriterInputPixelBufferAdaptor to append video samples packaged as CVPixelBuffer objects to a single AVAssetWriterInput object.

The initialization code looks something like this:

[dictionary setObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(NSString*) kCVPixelBufferPixelFormatTypeKey];

...

AVAssetWriterInputPixelBufferAdaptor *avAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:assetWriterInput sourcePixelBufferAttributes:dictionary];

Then, to specify a certain playback frame rate (15 fps, for example) for the new movie, the timestamps (represented as CMTime structures) should be specified as 1/15 of a second apart. Here's a code snippet:

CMTime frameTime = CMTimeMake(1, 15);
result = [avAdaptor appendPixelBuffer:buffer withPresentationTime:frameTime];

Another alternative is to use CMSampleBufferCreateCopyWithNewTiming to retime the buffers, then pass them to AVAssetWriter. Here's a rough outline:

CMSampleTimingInfo sampleTimingInfo = {0};
CMSampleBufferRef newBuffer = NULL;

CMSampleBufferGetSampleTimingInfo(existingSampleBuffer, 0, &sampleTimingInfo);

sampleTimingInfo.duration = CMTimeMake(1, 30) // Specify new frame rate.
sampleTimingInfo.presentationTimeStamp =          CMTimeAdd(previousPresentationTimeStamp, sampleTimingInfo.duration);
previousPresentationTimeStamp = sampleTimingInfo.presentationTimeStamp;

OSStatus status = CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, existingSampleBuffer, 1, &sampleTimingInfo, &newBuffer);

if (status == noErr) {
/* Write your newBuffer here */
}

//////////

I tried with 'CMSampleBufferCreateCopyWithNewTiming'. FPS is properly set. But got a slowmotion ouput. Following is my code.

autoreleasepool {
                        //set FPS
                        var sampleTimingInfo: CMSampleTimingInfo = CMSampleTimingInfo(duration: kCMTimeInvalid, presentationTimeStamp: CMTime(), decodeTimeStamp: kCMTimeInvalid)
                        var newBuffer: CMSampleBuffer!  = nil

                        CMSampleBufferGetSampleTimingInfo(sampleBufferMain, 0, &sampleTimingInfo);

                        sampleTimingInfo.duration = CMTimeMake(1, 15) // Specify new frame rate.
                        sampleTimingInfo.presentationTimeStamp = CMTimeAdd(self.previousPresentationTimeStamp, sampleTimingInfo.duration)
                        self.previousPresentationTimeStamp = sampleTimingInfo.presentationTimeStamp

                        let status: OSStatus = CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, sampleBufferMain, 1, &sampleTimingInfo, &newBuffer);

                        if status == noErr {
                            let appendResult = self.videoWriterInput?.append(newBuffer)
                            if appendResult == false {
                                printError("writer status: \(String(describing: self.videoWriter?.status.rawValue))")
                                printError("writer error: \(self.videoWriter?.error.debugDescription ?? "")")
                            }
                        } else {
                            print("write error")
                        }
                    }
jpulikkottil
  • 666
  • 9
  • 24