【问题标题】:How to control video frame rate with AVAssetReader and AVAssetWriter?如何使用 AVAssetReader 和 AVAssetWriter 控制视频帧率?
【发布时间】:2013-06-04 06:21:27
【问题描述】:

我们正在尝试了解如何控制/指定使用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,可能还有一些其他方法,而不是上面代码示例中的方法,但细节并不清楚。

【问题讨论】:

    标签: ios avfoundation avassetwriter avassetreader


    【解决方案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 决定了您将获得什么结果。

    【讨论】:

    • Wain - 感谢您的建议。我们可以尝试扩展您的答案以使其更详细地帮助我和下一个人吗? 1.当您使用movieTimeScale说“取决于您如何合成帧”时,您的意思是什么? 2.如果你在添加到writer时使用CMTime设置每一帧的时间,你使用什么方法(意思是AV类/实例方法[s])来设置时间?克里斯
    • 和... 3. 我最大的问题与您关于“为您添加到作者的每一帧增加第一个 [CMTime 中的值]”的最后评论有关...需要跳过/丢帧,只写每 1/30 秒发生的帧吗?如果我从阅读器中获取每一帧并将其时间增加 1/30 秒,并且不跳过帧或其他内容,我是否会加快减慢原始源视频的速度?或者读者是否以某种方式以我想要的速度将帧传递给我(但不知道这是怎么发生的)?
    • Wain - 我们的输入是来自照片库的视频资产,我们看到 copyNextSampleBuffer 结果的呈现时间相隔 1/30 秒(即 30 FPS)。我们希望将编码视频的帧速率更改为 15 FPS。我认为我们不能只获取通过copyNextSampleBuffer 获得的每一帧并更改它的时间信息,可以吗?难道我们不会“删除”一些缓冲区并且只删除 appendSampleBuffer 其中的一个子集吗?
    • 当然可以。如果样本缓冲区为您提供单帧,您可以跳过每个备用帧。如果样本缓冲区包含许多帧,则需要进行一些选择性复制。
    • 知道如何在 Swift 中执行此操作吗?我正在努力获得可以编译的东西。
    【解决方案2】:

    之前,我使用 dispatch_block_wait 在 delta time 执行 block 以再次调用整个函数。但是一旦我意识到有一天它会变成一个错误的东西,我会使用 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 秒。将它与您的期望增量计时到下一帧。

    【讨论】:

      【解决方案3】:

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

      应该很明显为什么这是比所有其他方法更受欢迎的方法。

      【讨论】:

      • 为什么要明显?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多