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

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

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

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

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

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

  1. я прав?
  2. Есть ли способ снимать со скоростью 120 или 240 кадров в секунду и играть со скоростью 120 и 240 кадров в секунду соответственно? Я имею в виду играть на fps видео были сняты без замедления?
  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 кадров в секунду.

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

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 кадров в секунду, видео будет отображаться нормально, у вас просто будет много кадров, "пропущенных" и заполняемых алгоритмом. Вы даже можете продавать только 1 кадр в секунду и воспроизводить его со скоростью 30 кадров в секунду, оба они отделены друг от друга (насколько быстро я получаю изображение и сколько кадров в секунду)

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

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

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

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

Так

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

3. у вас не работает?

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

Другие вопросы по тегам