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

时间:2017-04-12 09:14:41

标签: ios avfoundation 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. 我在编写文件时如何控制该帧速率?
  4. 我正在创建AVAssetWriter输入......

      NSDictionary *videoCompressionSettings = @{AVVideoCodecKey                  : AVVideoCodecH264,
                                                 AVVideoWidthKey                  : @(videoWidth),
                                                 AVVideoHeightKey                 : @(videoHeight),
                                                 AVVideoCompressionPropertiesKey  : @{ AVVideoAverageBitRateKey      : @(bitsPerSecond),
                                                                                       AVVideoMaxKeyFrameIntervalKey : @(1)}
                                                 };
    
        _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoCompressionSettings];
    

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

    注意:我尝试了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];
    

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

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

    time ======= 113594.311510508
    time ======= 113594.324011508
    time ======= 113594.328178716
    time ======= 113594.340679424
    time ======= 113594.344846383
    

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

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

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

2 个答案:

答案 0 :(得分:3)

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

(1) Capture buffer -> (2) Do Something With buffer -> (3) Write buffer as frames in video.

听起来你已经成功地完成了(1)和(2),你得到的缓冲区足够快,你正在处理它们,所以你可以把它们作为帧出售。

问题几乎可以肯定是(3)编写视频帧。

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

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

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

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

要进一步扩展 - 你不能严格控制或同步你的摄像头捕获精确到输出/播放速率,时间只是不那样工作,并不是那么精确,当然还有处理管道增加了开销。当您捕获帧时,它们会被标记为时间戳,但是在写入/压缩阶段,它只使用它所需的帧来生成为合成指定的输出。

它有两种方式,你只能捕获30 FPS并以240 FPS写出,视频显示正常,你只需要很多帧“丢失”并被算法填充。你甚至可以每秒只卖1帧,然后以30FPS回放,两者是相互分开的(我用多快的速度捕获多少帧和我每秒出现的帧数)

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

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

您可以通过在AVPlayer https://developer.apple.com/reference/avfoundation/avplayer

上设置rate属性来控制播放速度

答案 1 :(得分:1)

iOS屏幕刷新锁定为60fps,因此只有这样才能看到&#34;&#34;正如你所说,额外的帧是减慢播放速度,慢动作。

所以

  1. 是的,你是对的
  2. 屏幕刷新率(也许是人类视觉系统的限制,假设你是人类?)意味着你无法感知120&amp;帧速率为240fps。您可以通过下采样到屏幕刷新率,以正常速度 播放它们。当然这是AVPlayer已经做过的事情,虽然我不确定这是否是您正在寻找的答案。
  3. 使用CMSampleBuffer演示文稿时间戳编写文件时,可以控制文件的帧速率。如果您的帧来自摄像机,您可能会直接传递时间戳,在这种情况下,检查您是否确实获得了所要求的帧率(捕获回调中的日志语句应该足以验证这一点) 。如果您在程序上创建框架,那么您选择演示文稿时间戳,以便它们间隔1.0 / desiredFrameRate秒!
  4. 3.不适合你?

    P.S。你可以丢弃&amp;忽略AVVideoMaxKeyFrameIntervalKey - 它是一种质量设置,与播放帧率无关。