4

我们正在尝试了解如何控制/指定我们使用AVAssetReader和编码的视频的帧速率AVAssetWriter。具体来说,我们正在使用AVAssetReaderAVAssetWriter转码/编码/压缩我们从照片/视频库访问的视频。我们能够控制比特率、纵横比变化等,但无法弄清楚如何控制帧率。具体来说,我们希望能够将 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除上面代码示例中的方法之外的其他方法,但细节尚不清楚。

4

3 回答 3

1

根据您合成帧的方式,您可能只需要设置movieTimeScale.

或者,您需要在CMTime将每一帧添加到编写器时设置它的时间。

CMTime time = CMTimeMake(0, 30); // (time, time_scale)

这将以每秒 30 帧的帧速率为第一帧创建时间。将第二个参数设置为您想要的帧速率,不要更改它。为您添加到编写器的每一帧增加第一个。

编辑:

有许多不同的方法可以处理传入和传出的数据。因此,对于如何/需要指定时间有很多选择。通常,以上内容适用于使用 a 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决定你会得到什么结果。

于 2013-06-04T07:10:18.613 回答
0

之前,我使用 dispatch_block_wait 在 delta 时间执行块以再次调用整个函数。但是一旦我意识到有一天它会成为一个错误的东西,我使用 dispatch_source_t 作为计时器来执行块作为 FPS 的控制。

创建一个你想要做的块:

var block = dispatch_block_create(...)
var queue = dispatch_queue_create(...)
var source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue) 
dispatch_set_timer(source,STARTTIME,INTERVAL,0)
dispatch_source_set_event_handler(source,block)
dispatch_resume(source)

如果您正在寻找获取缓冲区的真实案例参考,我已经在https://github.com/matthewlui/FSVideoView上找到了。*添加了传入的时间间隔以纳秒计= 1/1,000,000,000秒。将它与您的愿望增量计时到下一帧。

于 2015-10-26T09:53:51.937 回答
-1

更好的方法是相应地设置 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);

应该很明显为什么这是优于所有其他方法的首选方法。

于 2016-08-22T08:00:58.207 回答