Как управлять AVAssetWriter для записи в правильном FPS

давайте посмотрим, правильно ли я понял.

в настоящее время самое современное оборудование, iOS позволяет мне записывать на следующих fps: 30, 60, 120 и 240.

но эти fps ведут себя по-разному. Если я снимаю на 30 или 60 fps, я ожидаю, что видеофайлы, созданные из съемки на этих fps, будут воспроизводиться на 30 и 60 fps соответственно.

но если я стреляю на 120 или 240 fps, я ожидаю, что видеофайлы, создаваемые из съемки на этих fps, будут играть на 30 fps, или я буду не вижу замедленной съемки.

несколько вопросов:

  1. Я прав?
  2. есть ли способ стрелять на 120 или 240 кадров в секунду и играть на 120 и 240 кадров в секунду соответственно? Я имею в виду игру в fps, видео были сняты без slo-mo?
  3. как я могу контролировать эту частоту кадров, когда я пишу файл?

Я создаю вход 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 кадров в секунду. Это время представления добавляемых кадров.

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

если вы сделать некоторые расчеты между ними, вы увидите, что частота кадров составляет около 240 кадров в секунду. Таким образом, кадры сохраняются с правильным временем.

но когда я смотрю видео, движение не замедляется, и быстрое время говорит, что видео 30 кадров в секунду.

Примечание: это приложение захватывает кадры из камеры, кадры переходит в Сифилтеры и результат этих фильтров преобразуется обратно в буфер образца, который хранится в файл и отображается на экране.

2 ответов


я добираюсь сюда, но я думаю, что здесь вы ошибаетесь. Считайте видеозахват конвейером.

(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

Проверьте настройку frameDuration в AVMutableComposition, вам понадобится что-то вроде 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)}
                                         };

чтобы расширить немного больше - вы не можете строго контролировать или синхронизировать захват камеры точно со скоростью вывода / воспроизведения, время просто не работает таким образом и не так точно, и, конечно, конвейер обработки добавляет накладные расходы. Когда вы захватываете кадры, они помечены временем, которое вы видели, но на этапе записи / сжатия он использует только кадры, которые ему нужно создать выход, указанный для композиции.

это идет в обе стороны, вы можете захватить только 30 кадров в секунду и записать на 240 кадров в секунду, видео будет отображаться нормально, у вас просто будет много кадров "отсутствует" и заполняется алгоритмом. Вы даже можете vend только 1 кадр в секунду и воспроизводить на 30FPS, два отдельно друг от друга (как быстро я захватываю против сколько кадров и что я представляю в секунду)

о том, как воспроизвести его на разной скорости, вам просто нужно настроить скорость воспроизведения - замедлять его по мере необходимости.

Если вы правильно установили время база (frameDuration), он будет всегда проигрывать "нормально" - ты говоришь это "воспроизвести х кадров в секунду", конечно, ваш глаз может заметить разницу (почти наверняка низкий фпс и высокий FPS), а экран не может обновить, что высокий (выше 60), но независимо от того, видео будет в "нормальной" 1Х скорость его развертки. Замедляя видео, Если моя временная база 120, и я замедляю его к.5x я знаю, что эффективно вижу 60FPS, и одна секунда воспроизведения занимает две секунды.

вы контролируете скорость воспроизведения, устанавливая свойство rate на AVPlayer https://developer.apple.com/reference/avfoundation/avplayer


обновление экрана iOS заблокировано на 60fps, поэтому единственный способ "увидеть" дополнительные кадры-это, как вы говорите, замедлить скорость воспроизведения, a.к. медленное движение.

Так

  1. да, вы правы
  2. частота обновления экрана (и, возможно, ограничения зрительной системы человека, предполагая, что вы человек?) означает, что вы не можете воспринимать частоту кадров 120 & 240fps. Вы can играть их на нормальной скорости путем downsampling к частоте обновления экрана. Конечно, это AVPlayer уже делает, хотя я не уверен, что это ответ, который вы ищете.
  3. вы контролируете частоту кадров файла при его записи с помощью CMSampleBuffer метки времени презентации. Если ваши кадры поступают с камеры, вы, вероятно, передаете метки времени прямо через, и в этом случае проверьте, что вы действительно получаете частоту кадров, которую вы просили (оператор журнала в обратном вызове захвата должен быть достаточным для проверки этого). Если вы процедурно создание рам, тогда вы выберете презентации метки так, что они разнесены 1.0/desiredFrameRate секунды!

Is 3. не работаешь на тебя?

p.s. вы можете отбросить & игнорировать AVVideoMaxKeyFrameIntervalKey - это настройка качества и не имеет ничего общего с частотой кадров воспроизведения.