如何使用AVAssetReader和AVAssetWriter控制视频帧速率?

时间:2013-06-04 06:21:27

标签: ios avfoundation avassetwriter avassetreader

我们正在尝试了解如何控制/指定我们使用AVAssetReaderAVAssetWriter编码的视频的帧速率。具体来说,我们正在使用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以及除上面代码示例中的其他方法之外的其他方法,但细节不明确。

3 个答案:

答案 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);

为什么这是所有其他方式的首选方法应该是显而易见的。