2017-04-10 183 views
1

我正在研究播放视频并允许用户在视频中向前和向后擦除的应用程序。清理过程必须顺利进行,因此我们总是使用SDAVAssetExportSession重新编写视频压缩属性为AVVideoMaxKeyFrameIntervalKey:@1的视频,以便每个帧都将成为关键帧并允许平滑的反向清理。这很好,并提供流畅的播放。该应用程序使用来自各种来源的视频,可以在Android或iOS设备上录制,甚至可以从网上下载并添加到应用程序中,因此我们最终得到的编码格式完全不同,其中一些编码已经适用于清理(每帧是一个关键帧)。有没有办法检测视频文件的关键帧间隔,以避免不必要的视频处理?我经历了AVFoundation的大部分文档,并没有看到获取这些信息的明显方法。感谢您的帮助。在AVAsset中检测当前关键帧间隔

回答

1

如果您可以通过创建AVAssetReaderTrackOutput而无需对图像进行解码来快速解析文件,并且无outputSettings。你遇到的帧样本缓冲区有一个附件数组,包含一个包含有用信息的字典,包括该帧是依赖于其他帧还是依赖于其他帧。我会将前者解释为指示关键帧,尽管它给了我一些较低的数字(一个文件中的关键帧为4%?)。无论如何,代码:

let asset = AVAsset(url: inputUrl) 
let reader = try! AVAssetReader(asset: asset) 

let videoTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0] 
let trackReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: nil) 

reader.add(trackReaderOutput) 
reader.startReading() 

var numFrames = 0 
var keyFrames = 0 

while true { 
    if let sampleBuffer = trackReaderOutput.copyNextSampleBuffer() { 
     // NB: not every sample buffer corresponds to a frame! 
     if CMSampleBufferGetNumSamples(sampleBuffer) > 0 { 
      numFrames += 1 
      if let attachmentArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false) as? NSArray { 
       let attachment = attachmentArray[0] as! NSDictionary 
       // print("attach on frame \(frame): \(attachment)") 
       if let depends = attachment[kCMSampleAttachmentKey_DependsOnOthers] as? NSNumber { 
        if !depends.boolValue { 
         keyFrames += 1 
        } 
       } 
      } 
     } 
    } else { 
     break 
    } 
} 

print("\(keyFrames) on \(numFrames)") 

N.B.这仅适用于本地文件资产。

p.s.你不会说你如何洗刷或玩耍。一个AVPlayerViewController和一个AVPlayer

+0

感谢您的回答。我今天实施了这个(目标c虽然)。我正在使用AVPlayer和AVPlayerLayer的自定义UIView。这似乎相当准确,并返回我编码为使用所有关键帧的视频中大约96%的关键帧。其他压缩设置的视频少得多。 Scrubber是一个滚轮(scrollView),它将滚动速度转换为逐步扫描一帧的擦洗循环。我可以将我的方法作为Q的答案发布,如果我这样做,我会链接。我也会发布这个Objective C版本。 – johnrechd

+0

酷!我期待着看到它。奇怪的是4%的失踪。我想知道可能的解释。 –

1

下面是相同答案的Objective C版本。在实现并使用它之后,应该包含所有关键帧的视频将从此代码返回大约96%的关键帧。我不确定为什么,所以我使用这个数字作为决定因素,尽管我希望它更准确。我也只是通过前600帧或视频结尾(以先到者为准),因为我不需要通读整个20分钟的视频来做出这个决定。

+ (BOOL)videoNeedsProcessingForSlomo:(NSURL*)fileUrl { 
    BOOL needsProcessing = YES; 
    AVAsset* anAsset = [AVAsset assetWithURL:fileUrl]; 
    NSError *error; 
    AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:anAsset error:&error]; 
    if (error) { 
     DLog(@"Error:%@", error.localizedDescription); 
     return YES; 
    } 

    AVAssetTrack *videoTrack = [[anAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; 

    AVAssetReaderTrackOutput *trackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:nil]; 
    [assetReader addOutput:trackOutput]; 

    [assetReader startReading]; 

    float numFrames = 0; 
    float keyFrames = 0; 

    while (numFrames < 600) { // If the video is long - only parse through 20 seconds worth. 
     CMSampleBufferRef sampleBuffer = [trackOutput copyNextSampleBuffer]; 
     if (sampleBuffer) { 
      // NB: not every sample buffer corresponds to a frame! 
      if (CMSampleBufferGetNumSamples(sampleBuffer) > 0) { 
       numFrames += 1; 

       NSArray *attachmentArray = ((NSArray*)CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false)); 
       if (attachmentArray) { 
        NSDictionary *attachment = attachmentArray[0]; 
        NSNumber *depends = attachment[(__bridge NSNumber*)kCMSampleAttachmentKey_DependsOnOthers]; 
        if (depends) { 
         if (depends.boolValue) { 
          keyFrames += 1; 
         } 
        } 
       } 
      } 
     } 
     else { 
      break; 
     } 
    } 

    needsProcessing = keyFrames/numFrames < 0.95f; // If more than 95% of the frames are keyframes - don't decompress. 

    return needsProcessing; 
} 
+0

对不起 - 我不知道你使用的是目标c,这个问题没有标记 –

+0

不用担心,对我来说语言无关紧要。感谢您的帮助。我有类似的东西,并通过大量的文档阅读,但不知道kCMSampleAttachmentKey_DependsOnOther可用于确定这一点。 – johnrechd