我正在开发一个播放视频并允许用户在视频中向前和向后滑动的应用程序。SDAVAssetExportSession
擦洗必须顺利进行,因此我们总是使用视频压缩属性重新编写视频AVVideoMaxKeyFrameIntervalKey:@1
这样每一帧都将是一个关键帧,并允许平滑的反向擦洗。这很好用,并提供流畅的播放。该应用程序使用来自各种来源的视频,可以在 android 或 iOS 设备上录制,甚至可以从 Web 下载并添加到应用程序中,因此我们最终会得到完全不同的编码,其中一些已经适合擦洗(每帧是关键帧)。有没有办法检测视频文件的关键帧间隔,这样我就可以避免不必要的视频处理?我浏览了很多 AVFoundation 的文档,并没有看到明显的方法来获取这些信息。感谢您对此的任何帮助。
问问题
1500 次
3 回答
2
如果您可以通过创建一个AVAssetReaderTrackOutput
with 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)")
注意这仅适用于本地文件资产。
ps你没有说你是如何擦洗或玩的。一个AVPlayerViewController
和一个AVPlayer
?
于 2017-04-11T23:18:18.643 回答
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;
}
于 2017-04-14T22:45:41.390 回答
0
在某些情况下,当 ffprobe 返回关键帧时,使用kCMSampleAttachmentKey_DependsOnOthers会给我 0 个关键帧。为了获得与 ffprobe 显示的相同数量的关键帧,我使用了:
if attachment[CMSampleBuffer.PerSampleAttachmentsDictionary.Key.notSync] == nil {
keyFrames += 1
}
在 CoreMedia 标题中它说:
/// 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
于 2020-08-16T14:50:04.207 回答