检测 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
我正在开发一个播放视频并允许用户在视频中前后滑动的应用程序。擦洗必须顺利进行,所以我们总是用 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