如何控制AVAssetWriter以正确的FPS写入

Spa*_*Dog 10 avfoundation ios avassetwriter

让我看看我是否理解正确.

在目前最先进的硬件上,iOS允许我以下列fps进行录制:30,60,120和240.

但这些fps表现不同.如果我以30或60 fps拍摄,我希望通过这些fps拍摄创建的视频文件分别以30和60 fps播放.

但是如果我以120或240 fps的速度拍摄,我希望以这些fps拍摄的视频文件以30 fps的速度播放,否则我将看不到慢动作.

几个问题:

  1. 我对吗?
  2. 有没有办法以120或240 fps的速度分别以120和240 fps的速度进行拍摄?我的意思是在没有慢动作的情况下拍摄视频的fps?
  3. 在编写文件时如何控制该帧速率?

我正在创建像这样的AVAssetWriter输入......

  NSDictionary *videoCompressionSettings = @{AVVideoCodecKey                  : AVVideoCodecH264,
                                             AVVideoWidthKey                  : @(videoWidth),
                                             AVVideoHeightKey                 : @(videoHeight),
                                             AVVideoCompressionPropertiesKey  : @{ AVVideoAverageBitRateKey      : @(bitsPerSecond),
                                                                                   AVVideoMaxKeyFrameIntervalKey : @(1)}
                                             };

    _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoCompressionSettings];
Run Code Online (Sandbox Code Playgroud)

并没有明显的方法来控制它.

注意:我尝试了不同的数字1.我试过1.0/fps,我试过fps,我已经删除了密钥.没有不同.

这就是我设置`AVAssetWriter的方法:

  AVAssetWriter *newAssetWriter = [[AVAssetWriter alloc] initWithURL:_movieURL fileType:AVFileTypeQuickTimeMovie
                                          error:&error];

  _assetWriter = newAssetWriter;
  _assetWriter.shouldOptimizeForNetworkUse = NO;

  CGFloat videoWidth = size.width;
  CGFloat videoHeight  = size.height;

  NSUInteger numPixels = videoWidth * videoHeight;
  NSUInteger bitsPerSecond;

  // Assume that lower-than-SD resolutions are intended for streaming, and use a lower bitrate
  //  if ( numPixels < (640 * 480) )
  //    bitsPerPixel = 4.05; // This bitrate matches the quality produced by AVCaptureSessionPresetMedium or Low.
  //  else
  NSUInteger bitsPerPixel = 11.4; // This bitrate matches the quality produced by AVCaptureSessionPresetHigh.

  bitsPerSecond = numPixels * bitsPerPixel;

  NSDictionary *videoCompressionSettings = @{AVVideoCodecKey                  : AVVideoCodecH264,
                                             AVVideoWidthKey                  : @(videoWidth),
                                             AVVideoHeightKey                 : @(videoHeight),
                                             AVVideoCompressionPropertiesKey  : @{ AVVideoAverageBitRateKey      : @(bitsPerSecond)}
                                             };

  if (![_assetWriter canApplyOutputSettings:videoCompressionSettings forMediaType:AVMediaTypeVideo]) {
    NSLog(@"Couldn't add asset writer video input.");
    return;
  }

 _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
                                                              outputSettings:videoCompressionSettings
                                                            sourceFormatHint:formatDescription];
  _assetWriterVideoInput.expectsMediaDataInRealTime = YES;      

  NSDictionary *adaptorDict = @{
                                (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
                                (id)kCVPixelBufferWidthKey : @(videoWidth),
                                (id)kCVPixelBufferHeightKey : @(videoHeight)
                                };

  _pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
                         initWithAssetWriterInput:_assetWriterVideoInput
                         sourcePixelBufferAttributes:adaptorDict];


  // Add asset writer input to asset writer
  if (![_assetWriter canAddInput:_assetWriterVideoInput]) {
    return;
  }

  [_assetWriter addInput:_assetWriterVideoInput];
Run Code Online (Sandbox Code Playgroud)

captureOutput方法很简单.我从过滤器获取图像并使用以下方法将其写入文件:

if (videoJustStartWriting)
    [_assetWriter startSessionAtSourceTime:presentationTime];

  CVPixelBufferRef renderedOutputPixelBuffer = NULL;
  OSStatus err = CVPixelBufferPoolCreatePixelBuffer(nil,
                                                    _pixelBufferAdaptor.pixelBufferPool,
                                                    &renderedOutputPixelBuffer);

  if (err) return; //          NSLog(@"Cannot obtain a pixel buffer from the buffer pool");

  //_ciContext is a metal context
  [_ciContext render:finalImage
     toCVPixelBuffer:renderedOutputPixelBuffer
              bounds:[finalImage extent]
          colorSpace:_sDeviceRgbColorSpace];

   [self writeVideoPixelBuffer:renderedOutputPixelBuffer
                  withInitialTime:presentationTime];


- (void)writeVideoPixelBuffer:(CVPixelBufferRef)pixelBuffer withInitialTime:(CMTime)presentationTime
{

  if ( _assetWriter.status == AVAssetWriterStatusUnknown ) {
    // If the asset writer status is unknown, implies writing hasn't started yet, hence start writing with start time as the buffer's presentation timestamp
    if ([_assetWriter startWriting]) {
      [_assetWriter startSessionAtSourceTime:presentationTime];
    }
  }

  if ( _assetWriter.status == AVAssetWriterStatusWriting ) {
    // If the asset writer status is writing, append sample buffer to its corresponding asset writer input

      if (_assetWriterVideoInput.readyForMoreMediaData) {
        if (![_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime]) {
          NSLog(@"error", [_assetWriter.error localizedFailureReason]);
        }
      }
  }

  if ( _assetWriter.status == AVAssetWriterStatusFailed ) {
    NSLog(@"failed");
  }

}
Run Code Online (Sandbox Code Playgroud)

我把整个东西以240 fps的速度拍摄.这些是附加帧的呈现时间.

time ======= 113594.311510508
time ======= 113594.324011508
time ======= 113594.328178716
time ======= 113594.340679424
time ======= 113594.344846383
Run Code Online (Sandbox Code Playgroud)

如果你在它们之间进行一些计算,你会发现帧速率约为240 fps.因此,帧以正确的时间存储.

但是,当我观看视频时,动作不是慢动作,快速时间表示视频是30 fps.

注意:此应用程序从相机抓取帧,帧进入CIF过滤器,这些过滤器的结果将转换回样本缓冲区,该缓冲区存储到文件并显示在屏幕上.

Tim*_*ull 5

我正在到达这里,但我认为这是你出错的地方。将您的视频捕获视为一个管道。

(1) Capture buffer -> (2) Do Something With buffer -> (3) Write buffer as frames in video.
Run Code Online (Sandbox Code Playgroud)

听起来您已经成功完成了 (1) 和 (2),您获得的缓冲区足够快并且正在处理它们,以便您可以将它们作为帧出售。

问题几乎肯定出在(3)编写视频帧中。

https://developer.apple.com/reference/avfoundation/avmutablevideocomposition

查看 AVMutableComposition 中的 frameDuration 设置,您需要像 CMTime(1, 60) //60FPS 或 CMTime(1, 240) // 240FPS 之类的东西来获得您想要的(告诉视频写入这么多帧)并以此速率编码)。

使用 AVAssetWriter,原理完全相同,但您将帧速率设置为 AVAssetWriterInput outputSettings 中的一个属性,并添加到 AVVideoExpectedSourceFrameRateKey 中。

NSDictionary *videoCompressionSettings = @{AVVideoCodecKey                  : AVVideoCodecH264,
                                         AVVideoWidthKey                  : @(videoWidth),
                                         AVVideoHeightKey                 : @(videoHeight),
                                       AVVideoExpectedSourceFrameRateKey : @(60),
                                         AVVideoCompressionPropertiesKey  : @{ AVVideoAverageBitRateKey      : @(bitsPerSecond),
                                                                               AVVideoMaxKeyFrameIntervalKey : @(1)}
                                         };
Run Code Online (Sandbox Code Playgroud)

再扩展一点——你不能严格控制或同步你的相机捕捉到输出/播放速率,时间只是不能那样工作,也不是那么精确,当然处理管道会增加开销。当您捕获帧时,它们带有时间戳,您已经看到了,但在写入/压缩阶段,它仅使用生成为合成指定的输出所需的帧。

它是双向的,您只能捕获 30 FPS 并以 240 FPS 的速度输出,视频会显示正常,您只会有很多帧“丢失”并被算法填充。你甚至可以每秒只输出 1 帧并以 30FPS 播放,两者是相互独立的(我捕捉的速度与每秒呈现多少帧和呈现的内容)

至于如何以不同的速度播放,您只需要调整播放速度 - 根据需要放慢速度。

如果您正确设置了时基 (frameDuration),它将始终以“正常”播放 - 您告诉它“以每秒 X 帧的速度播放”,当然,您的眼睛可能会注意到差异(几乎可以肯定低 FPS 和高 FPS),并且屏幕可能不会刷新那么高(超过 60FPS),但无论如何视频都会以“正常”的 1 倍速度为其时基。通过减慢视频速度,如果我的时基是 120,我将它减慢到 0.5 倍,我知道可以有效地看到 60FPS,并且一秒钟的播放需要两秒钟。

您可以通过在 AVPlayer https://developer.apple.com/reference/avfoundation/avplayer上设置 rate 属性来控制播放速度

  • @TimBull 你好!我一直在努力解决@SpaceDog 遇到的同样问题(编码 120+ fps 的视频并保存正确的 fps),您的建议似乎可以解决我的问题。但是,当我将 `AVVideoExpectedSourceFrameRateKey` 添加到我的 `AVAssetWriterInput` 设置时,我收到此错误:`输出设置字典包含一个或多个无效键:ExpectedFrameRate'`。我一直在互联网上搜索有关该问题的信息,但不幸的是 - 没有运气。您是否遇到过同样的问题,如果是,您是如何解决的?谢谢你。 (3认同)
  • AVVideoCompressionPropertiesKey : @{ AVVideoAverageBitRateKey : @(bitsPerSecond), AVVideoMaxKeyFrameIntervalKey : @(1), AVVideoExpectedSourceFrameRateKey : @(60)} (2认同)