首页 > 解决方案 > 使用 AVAssetWriter/AVAssetReader 设置视频帧率的问题

问题描述

情况:

我正在尝试使用一些参数导出视频,例如视频比特率、音频比特率、帧速率、更改视频分辨率等。请注意,我让用户以分数设置视频帧速率;喜欢用户可以设置视频帧速率,比如 23.98。

我使用AVAssetWriterAVAssetReader进行此操作。我使用AVAssetWriterInputPixelBufferAdaptor来编写样本缓冲区。

其他一切都很好,除了视频帧率

我试过的:

  1. 按照此处的建议设置AVAssetWriter.movi​​eTimeScale。这确实会改变视频帧速率,但也会使视频变慢。(要点在这里

  1. 设置AVVideoExpectedSourceFrameRateKey。这没有帮助。(要点在这里

  1. 设置AVAssetWriterInput.mediaTimeScale。同样,它会更改视频帧速率,但会像AVAssetWriter.movi​​eTimeScale一样使视频变慢。该视频在某些时候显示不同的帧,有时它会粘住并再次恢复。(要点在这里

  1. 使用AVAssetReaderVideoCompositionOutput并设置AVMutableVideoComposition.frameDuration;就像SDAVAssetExportSession一样。具有讽刺意味的是,使用 SDAVAssetExportSession 代码,视频正在以我想要的正确帧速率导出,但它在我的代码中不起作用。要点在这里

我不确定为什么它不适用于我的代码。这种方法的问题是它总是从AVAssetReaderVideoCompositionOutput.copyNextSampleBuffer()返回 nil 。


  1. 使用CMSampleTimingInfo手动更改帧的时间戳,如此处所建议类似:
var sampleTimingInfo = CMSampleTimingInfo()
var sampleBufferToWrite: CMSampleBuffer?

CMSampleBufferGetSampleTimingInfo(vBuffer, at: 0, timingInfoOut: &sampleTimingInfo)

sampleTimingInfo.duration = CMTimeMake(value: 100, timescale: Int32(videoConfig.videoFrameRate * 100))

sampleTimingInfo.presentationTimeStamp = CMTimeAdd(previousPresentationTimeStamp, sampleTimingInfo.duration)

previousPresentationTimeStamp = sampleTimingInfo.presentationTimeStamp

let status = CMSampleBufferCreateCopyWithNewTiming(allocator: kCFAllocatorDefault, sampleBuffer: vBuffer,sampleTimingEntryCount: 1, sampleTimingArray: &sampleTimingInfo, sampleBufferOut: &sampleBufferToWrite)

使用这种方法,我确实可以正确设置帧速率,但它会增加视频持续时间(如该问题答案的评论中所述)。我认为在某些时候我可能不得不丢弃一些帧(如果目标帧速率较低;在大多数情况下我需要降低帧速率)。

如果我知道如果我想要 30fps,并且我当前的帧速率是 60fps,那么很容易丢弃每一秒帧并相应地设置 SampleBuffer 时间。

如果我采用这种方法(即设置 23.98 fps),我如何决定丢弃哪一帧,如果目标帧速率更高,复制哪一帧?提醒:帧速率可以是分数。


标签: iosswiftmacosvideovideo-encoding

解决方案


这是一个选择框架的想法。假设源视频的fps是F,目标fps是TF。率 = TF/F

启动一个等于-rate的变量n,每次都加上rate,当n的整数部分改变时,选择帧。

e.g. rate = 0.3
          n: -0.3 0 0.3 0.6 0.9 1.2 1.5 1.8 2.1
                  ^              ^           ^
frame index:      0  1   2   3   4   5   6   7
select 0 4 7
float rate = 0.39999f; // TF/F 
float n =  -rate; // to make sure first frame will be selected
for (int i = 0; i < 100; ++i, n += rate) { // i stands for frame index, take a video with 100 frames as an example
    int m = floor(n);
    int tmp = n+rate;
    // if rate > 1.0 repeat i
    // if rate < 1.0 some of the frames will be dropped
    for (int j = 0; m+j < tmp; ++j) {
        // Use this frame
        printf("%d ", i);
    }
}

推荐阅读