检测 AVAsset 中的当前关键帧间隔

Detect current Keyframe interval in AVAsset

我正在开发一个播放视频并允许用户在视频中前后滑动的应用程序。擦洗必须顺利进行,所以我们总是用 SDAVAssetExportSession 和视频压缩 属性 AVVideoMaxKeyFrameIntervalKey:@1 重写视频,这样每一帧都是关键帧并允许平滑的反向擦洗。这很好用并提供流畅的播放。该应用程序使用来自各种来源的视频,可以在 android 或 iOS 设备上录制,甚至可以从 Web 下载并添加到应用程序中,因此我们最终得到了完全不同的编码,其中一些已经适合擦洗(每个帧都是关键帧)。有没有办法检测视频文件的关键帧间隔,从而避免不必要的视频处理?我浏览了很多 AVFoundation 的文档,但没有找到获取此信息的明显方法。感谢您对此的任何帮助。

如果您可以通过创建 AVAssetReaderTrackOutput 和 nil 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

这是同一答案的 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;
}

使用 kCMSampleAttachmentKey_DependsOnOthers 在某些情况下会给我 0 个关键帧,而 ffprobe 会 return 关键帧。 为了获得与 ffprobe 显示相同数量的关键帧,我使用了:

if attachment[CMSampleBuffer.PerSampleAttachmentsDictionary.Key.notSync] == nil {
                            keyFrames += 1
                        }

在 CoreMedia header 中说:

/// Boolean (absence of this key implies Sync)
public static let notSync: CMSampleBuffer.PerSampleAttachmentsDictionary.Key

对于 dependsOnOthers 键,它说:

/// `true` (e.g., non-I-frame), `false` (e.g. I-frame), or absent if
/// unknown
public static let dependsOnOthers: CMSampleBuffer.PerSampleAttachmentsDictionary.Key