我们正在尝试了解如何控制/指定我们使用AVAssetReader
和AVAssetWriter
编码的视频的帧速率。具体来说,我们正在使用AVAssetReader
和AVAssetWriter
对我们从照片/视频库中访问过的视频进行转码/编码/压缩。我们能够控制比特率,宽高比变化等等,但无法弄清楚如何控制帧速率。具体而言,我们希望能够输入长达5分钟的30 FPS视频作为输入,并以15 FPS发出5分钟视频。
我们处理样本缓冲区的当前循环是:
[writer startWriting];
[writer startSessionAtSourceTime:kCMTimeZero];
[videoReader startReading];
[videoWriterInput requestMediaDataWhenReadyOnQueue:videoEncoderQueue usingBlock:
^{
while ([videoWriterInput isReadyForMoreMediaData]) {
CMSampleBufferRef sampleBuffer;
if ([videoReader status] == AVAssetReaderStatusReading
&& (sampleBuffer = [videoReaderTrackOutput copyNextSampleBuffer])) {
if (sampleBuffer) {
BOOL result = [videoWriterInput appendSampleBuffer:sampleBuffer];
CFRelease(sampleBuffer);
if (!result) {
[videoReader cancelReading];
break;
}
}
} else {
// deal with status other than AVAssetReaderStatusReading
[videoWriterInput markAsFinished];
// [...]
break;
}
}
}];
我们如何扩充或更改此设置,以便我们可以控制创建的视频的帧速率?我们似乎无法在SO或其他任何明确解释如何执行此操作的地方找到样本。我认为我们应该使用CMTime
以及除上面代码示例中的其他方法之外的其他方法,但细节不明确。
答案 0 :(得分:1)
根据您合成帧的方式,您可能只需要设置movieTimeScale
。
或者,您需要使用CMTime
设置每个帧的时间,并将其添加到编写器中。
CMTime time = CMTimeMake(0, 30); // (time, time_scale)
这将以每秒30帧的帧速率创建第一帧的时间。将第二个参数设置为所需的帧速率,不要更改它。为添加到编写器的每个帧增加第一个。
编辑:
有许多不同的方法可以处理传入和传出的数据。因此,有多种选择可以如何/需要指定时间。通常,上述内容适用于AVAssetWriterInputPixelBufferAdaptor
(如果您正在编辑视频帧)。
根据您更新的代码,您正在进行更“简单”的传递,您可能需要使用CMSampleBufferCreateCopyWithNewTiming
生成从阅读器收到的sampleBuffer
的副本。奇怪的是,我认为,这使得时间更加复杂。根据您尝试通过编辑实现的目标,您可能需要创建一个可用于所有帧的新单CMSampleTimingInfo
,或者使用CMSampleBufferGetSampleTimingInfoArray
从样本缓冲区获取现有时序信息,然后创建它的编辑版本。有点像:
CMItemCount count;
CMTime newTimeStamp = CMTimeMake(...);
CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, 0, nil, &count);
CMSampleTimingInfo *timingInfo = malloc(sizeof(CMSampleTimingInfo) * count);
CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, count, timingInfo, &count);
for (CMItemCount i = 0; i < count; i++)
{
timingInfo[i].decodeTimeStamp = kCMTimeInvalid;
timingInfo[i].presentationTimeStamp = newTimeStamp;
}
CMSampleBufferRef completedSampleBuffer;
CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, sampleBuffer, count, timingInfo, &completedSampleBuffer);
free(timingInfo);
您如何选择newTimeStamp
决定了您将获得的结果。
答案 1 :(得分:0)
之前,我使用dispatch_block_wait在delta时间执行块以再次调用整个函数。但是一旦我意识到有一天它会成为一个有缺陷的东西,我会使用dispatch_source_t作为计时器来执行块作为FPS的控制。
创建一个你想做的事情的块:
whenever --update-crontab # to update crontab
service crond restart # to restart crontab
如果您正在寻找获取缓冲区的真实案例参考,我已在https://github.com/matthewlui/FSVideoView上创建了它。 *添加 传入的时间间隔以纳秒为单位= 1 / 1,000,000,000秒。将你的愿望加速到下一帧。
答案 2 :(得分:0)
更好的方法是相应地设置AVSampleBufferDisplayLayer的timebase属性:
CMTimebaseRef timebase;
OSStatus timebaseResult;
timebaseResult = CMTimebaseCreateWithMasterClock(NULL, CMClockGetHostTimeClock(), &timebase);
if (timebaseResult != 0)
{
NSLog(@"ERROR: could not create timebase");
} else {
CMTimebaseSetTime(timebase, CMTimeMake(1, 3000));
CMTimebaseSetRate(timebase, 1.0f);
}
[(AVSampleBufferDisplayLayer *)self.layer setControlTimebase:timebase];
CFRelease(timebase);
为什么这是所有其他方式的首选方法应该是显而易见的。