简体   繁体   中英

AVAssetWriter - Set Custom frame rate

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
    }

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")
                        }
                    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM