使用AVAssetWriter/AVAssetReader设置视频帧率的问题设定

112

情况:

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

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

除了视频帧速率之外,其他所有内容都正常工作。

我尝试过的:

  1. 按照这里建议设置AVAssetWriter.movieTimeScale。这确实会更改视频帧速率,但也使视频变得缓慢。(代码片段在这里

  1. 设置AVVideoExpectedSourceFrameRateKey。这并没有帮助。(代码片段在这里

  1. 设置AVAssetWriterInput.mediaTimeScale。同样,它更改了视频帧速率,但使视频变得缓慢,就像AVAssetWriter.movieTimeScale一样。视频在某些点上显示不同的帧,有时会卡住并重新开始。(代码片段在这里

  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),如何确定要丢弃哪一帧?如果目标帧速率更高,又该如何复制帧?请记住:帧速率可能是分数。



3
如果你想得到任意帧率,首先将视频上采样(滤波)到更高的帧率(比如120或更高),然后再将其下采样到正确的帧率,这样可以保持运动的平滑性,而直接跳过每n帧将完全破坏视频的节奏。 - StarShine
2个回答

1
这里有一个选择帧的想法。假设源视频的fps为F,目标fps为TF。rate = TF / F。
初始化一个变量n等于-rate,并每次加上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);
    }
}

0
    NSMutableDictionary *writerInputParams = [[NSMutableDictionary alloc] init];
[writerInputParams setObject:AVVideoCodecTypeH264 forKey:AVVideoCodecKey];
[writerInputParams setObject:[NSNumber numberWithInt:width] forKey:AVVideoWidthKey];
[writerInputParams setObject:[NSNumber numberWithInt:height] forKey:AVVideoHeightKey];
[writerInputParams setObject:AVVideoScalingModeResizeAspectFill forKey:AVVideoScalingModeKey];
NSMutableDictionary * compressionProperties = [[NSMutableDictionary alloc] init];
[compressionProperties setObject:[NSNumber numberWithInt: 20] forKey:AVVideoExpectedSourceFrameRateKey];
[compressionProperties setObject:[NSNumber numberWithInt: 20] forKey:AVVideoAverageNonDroppableFrameRateKey];
[compressionProperties setObject:[NSNumber numberWithInt: 0.0] forKey:AVVideoMaxKeyFrameIntervalDurationKey];
[compressionProperties setObject:[NSNumber numberWithInt: 1] forKey:AVVideoMaxKeyFrameIntervalKey];
[compressionProperties setObject:[NSNumber numberWithBool:YES] forKey:AVVideoAllowFrameReorderingKey];
[compressionProperties setObject:AVVideoProfileLevelH264BaselineAutoLevel forKey:AVVideoProfileLevelKey];
[writerInputParams setObject:compressionProperties forKey:AVVideoCompressionPropertiesKey];

self.assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:writerInputParams];
self.assetWriterInput.expectsMediaDataInRealTime = YES;

已经确认 SCNView 每秒刷新 60 帧,但是使用 AVAssetWriter 只想保存每秒 20 帧,应该怎么做?

无论是 AVVideoExpectedSourceFrameRateKey 还是 AVVideoAverageNonDroppableFrameRateKey 都不会影响 fps,配置 fps 也不起作用!!! //设置这个可以确保在录制被中断的情况下产生一个功能性电影。 在那种情况下,只有最后一秒钟会丢失。 self.videoWriter.movieFragmentInterval = CMTimeMakeWithSeconds(1.0,1000); self.videoWriter.shouldOptimizeForNetworkUse = YES; self.videoWriter.movieTimeScale = 20; 以上配置也不会影响 fps。

self.assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:writerInputParams];
self.assetWriterInput.expectsMediaDataInRealTime = YES;
/// this config will change video frame presenttime to fit fps, but it will be change video duration.

// self.assetWriterInput.mediaTimeScale = 20; self.assetWriterInput.mediaTimeScale会影响fps,但会导致视频时长被拉伸3倍,因为 BOOL isSUc = [self.writerAdaptor appendPixelBuffer:cvBuffer withPresentationTime:presentationTime]; 填充帧的时间将被重新修改,所以需要配置self.assetWriterInput.mediaTimeScale值,这与预期严重不一致,视频时长不应该被拉伸。

因此,如果您想控制AVAssetWriter最终保存的视频的fps,则必须进行控制,并确保每秒调用20次。

CMTime presentationTime = CMTimeMake(_writeCount * (1.0/20.0) * 1000, 1000);
BOOL isSUc = [self.writerAdaptor appendPixelBuffer:cvBuffer withPresentationTime:presentationTime];
_writeCount += 1;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接