繁体   English   中英

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

[英]How to control video frame rate with AVAssetReader and AVAssetWriter?

我们试图了解如何控制/指定我们使用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以及除上面代码示例中的其他方法之外的其他方法,但细节尚不清楚。

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

或者,您需要使用CMTime在将每个帧添加到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决定了你会得到什么结果。

在此之前,我使用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秒。 将您的愿望加速到下一帧。

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

应该明白为什么这是所有其他方式的首选方式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM