Как мне управлять AVAssetWriter для записи на правильном FPS
Дай посмотреть, правильно ли я понял.
В настоящее время самое современное аппаратное обеспечение iOS позволяет мне записывать со следующими fps: 30, 60, 120 и 240.
Но эти фпс ведут себя по-разному. Если я снимаю со скоростью 30 или 60 кадров в секунду, я ожидаю, что видеофайлы, созданные при съемке на этих кадрах в секунду, будут воспроизводиться со скоростью 30 и 60 кадров в секунду соответственно.
Но если я снимаю со скоростью 120 или 240 кадров в секунду, я ожидаю, что видеофайлы, создаваемые при съемке на этих кадрах в секунду, будут воспроизводиться со скоростью 30 кадров в секунду, иначе я не увижу замедленное движение.
Несколько вопросов:
- я прав?
- Есть ли способ снимать со скоростью 120 или 240 кадров в секунду и играть со скоростью 120 и 240 кадров в секунду соответственно? Я имею в виду играть на fps видео были сняты без замедления?
- Как мне контролировать частоту кадров при записи файла?
Я создаю вход 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 кадров в секунду, поэтому единственный способ "увидеть" дополнительные кадры - это, как вы говорите, замедлить скорость воспроизведения, то есть замедленное движение.
Так
- да ты прав
- частота обновления экрана (и, возможно, ограничения зрительной системы человека, если вы человек?) означает, что вы не можете воспринимать частоту кадров 120 и 240 кадров в секунду. Вы можете воспроизводить их с нормальной скоростью, понижая частоту до частоты обновления экрана. Конечно, это то, что
AVPlayer
уже делает, хотя я не уверен, что это ответ, который вы ищете. - вы контролируете частоту кадров файла, когда вы пишете его с
CMSampleBuffer
метки времени представления. Если ваши кадры поступают с камеры, вы, вероятно, пропускаете временные метки напрямую, и в этом случае проверьте, действительно ли вы получаете запрашиваемую частоту кадров (для проверки этого должно быть достаточно записи журнала в обратном вызове захвата). Если вы процедурно создаете кадры, то вы выбираете временные метки презентации так, чтобы они были разнесены на 1,0/ требуемый интервал в секундах!
3. у вас не работает?
PS вы можете отказаться и игнорировать AVVideoMaxKeyFrameIntervalKey
- это настройка качества и не имеет ничего общего с частотой кадров воспроизведения.