如何获取 AVPlayer 的视频帧?
Posted
技术标签:
【中文标题】如何获取 AVPlayer 的视频帧?【英文标题】:How to get video frame of the AVPlayer? 【发布时间】:2012-08-23 12:12:15 【问题描述】:我有用于显示 AVPlayer 播放的 PlayerView 类。来自documentation的代码。
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface PlayerView : UIView
@property (nonatomic) AVPlayer *player;
@end
@implementation PlayerView
+ (Class)layerClass
return [AVPlayerLayer class];
- (AVPlayer*)player
return [(AVPlayerLayer *)[self layer] player];
- (void)setPlayer:(AVPlayer *)player
[(AVPlayerLayer *)[self layer] setPlayer:player];
@end
我在此 PlayerView(frame.size.width = 100,frame.size.height = 100)中设置了我的 AVPlayer(包含大小为 320x240 的视频资产),并且调整了我的视频大小。在 PlayerView 中添加后如何获取视频大小?
【问题讨论】:
【参考方案1】:在 iOS 7.0 中添加了新功能:
AVPlayerLayer 具有属性 videoRect。
【讨论】:
对我来说,videoRect 只是返回 CGRectZero,即使我可以在屏幕上看到视频。我正在使用 ios 8 等待currentItem.status为== AVPlayerStatusReadyToPlay,然后就可以拿到frame了 这行不通,甚至我已经添加了KVO等待状态为AVPlayerStatusReadyToPlay。 videoRect 仍然返回全零 再试一次,成功了。所以你需要对 AVPlayerItem 进行 KVO,而不是 AVPlayer,并且要检查的状态是 AVPlayerItemStatus.ReadyToPlay,而不是 AVPlayerStatusReadyToPlay。这种做法是正确的,但@Yocoh 的评论有点误导。 这适用于方面fit
,但不适用于方面fill
。根据docs,这是“视频图像的显示矩形在层边界内”【参考方案2】:
这对我有用。当您没有 AVPLayerLayer 时。
- (CGRect)videoRect
// @see http://***.com/a/6565988/1545158
AVAssetTrack *track = [[self.player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
if (!track)
return CGRectZero;
CGSize trackSize = [track naturalSize];
CGSize videoViewSize = self.videoView.bounds.size;
CGFloat trackRatio = trackSize.width / trackSize.height;
CGFloat videoViewRatio = videoViewSize.width / videoViewSize.height;
CGSize newSize;
if (videoViewRatio > trackRatio)
newSize = CGSizeMake(trackSize.width * videoViewSize.height / trackSize.height, videoViewSize.height);
else
newSize = CGSizeMake(videoViewSize.width, trackSize.height * videoViewSize.width / trackSize.width);
CGFloat newX = (videoViewSize.width - newSize.width) / 2;
CGFloat newY = (videoViewSize.height - newSize.height) / 2;
return CGRectMake(newX, newY, newSize.width, newSize.height);
【讨论】:
很好的解决方案!【参考方案3】:找到解决办法:
添加到 PlayerView 类:
- (CGRect)videoContentFrame
AVPlayerLayer *avLayer = (AVPlayerLayer *)[self layer];
// AVPlayerLayerContentLayer
CALayer *layer = (CALayer *)[[avLayer sublayers] objectAtIndex:0];
CGRect transformedBounds = CGRectApplyAffineTransform(layer.bounds, CATransform3DGetAffineTransform(layer.sublayerTransform));
return transformedBounds;
【讨论】:
它在 Xcode4.5、iOS6 sdk、构建目标 4.3 中对我不起作用。获取大小(0,0)。它现在还为你工作吗?【参考方案4】:这是对我有用的解决方案,它考虑了 AVPlayer 在视图中的定位。我刚刚将它添加到 PlayerView 自定义类中。我必须解决这个问题,因为 videoRect 在 10.7 中似乎没有工作。
- (NSRect) videoRect
NSRect theVideoRect = NSMakeRect(0,0,0,0);
NSRect theLayerRect = self.playerLayer.frame;
NSSize theNaturalSize = NSSizeFromCGSize([[[self.movie asset] tracksWithMediaType:AVMediaTypeVideo][0] naturalSize]);
float movieAspectRatio = theNaturalSize.width/theNaturalSize.height;
float viewAspectRatio = theLayerRect.size.width/theLayerRect.size.height;
if (viewAspectRatio < movieAspectRatio)
theVideoRect.size.width = theLayerRect.size.width;
theVideoRect.size.height = theLayerRect.size.width/movieAspectRatio;
theVideoRect.origin.x = 0;
theVideoRect.origin.y = (theLayerRect.size.height/2) - (theVideoRect.size.height/2);
else if (viewAspectRatio > movieAspectRatio)
theVideoRect.size.width = movieAspectRatio * theLayerRect.size.height;
theVideoRect.size.height = theLayerRect.size.height;
theVideoRect.origin.x = (theLayerRect.size.width/2) - (theVideoRect.size.width/2);
theVideoRect.origin.y = 0;
return theVideoRect;
【讨论】:
videoRect 在 iOS 13 中无法使用,但谢谢!【参考方案5】:这是移植到 iOS 的Eric Badros' answer。我还添加了preferredTransform 处理。这假设_player
是AVPlayer
- (CGRect) videoRect
CGRect theVideoRect = CGRectZero;
// Replace this with whatever frame your AVPlayer is playing inside of:
CGRect theLayerRect = self.playerLayer.frame;
AVAssetTrack *track = [_player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo][0];
CGSize theNaturalSize = [track naturalSize];
theNaturalSize = CGSizeApplyAffineTransform(theNaturalSize, track.preferredTransform);
theNaturalSize.width = fabs(theNaturalSize.width);
theNaturalSize.height = fabs(theNaturalSize.height);
CGFloat movieAspectRatio = theNaturalSize.width / theNaturalSize.height;
CGFloat viewAspectRatio = theLayerRect.size.width / theLayerRect.size.height;
// Note change this *greater than* to a *less than* if your video will play in aspect fit mode (as opposed to aspect fill mode)
if (viewAspectRatio > movieAspectRatio)
theVideoRect.size.width = theLayerRect.size.width;
theVideoRect.size.height = theLayerRect.size.width / movieAspectRatio;
theVideoRect.origin.x = 0;
theVideoRect.origin.y = (theLayerRect.size.height - theVideoRect.size.height) / 2;
else if (viewAspectRatio < movieAspectRatio)
theVideoRect.size.width = movieAspectRatio * theLayerRect.size.height;
theVideoRect.size.height = theLayerRect.size.height;
theVideoRect.origin.x = (theLayerRect.size.width - theVideoRect.size.width) / 2;
theVideoRect.origin.y = 0;
return theVideoRect;
【讨论】:
谢谢@tettoffensive【参考方案6】:这是一个基于 Andrey Banshchikov's answer 的 Swift 解决方案。当您无法访问AVPLayerLayer
时,他的解决方案特别有用。
func currentVideoFrameSize(playerView: AVPlayerView, player: AVPlayer) -> CGSize
// See https://***.com/a/40164496/1877617
let track = player.currentItem?.asset.tracks(withMediaType: .video).first
if let track = track
let trackSize = track.naturalSize
let videoViewSize = playerView.bounds.size
let trackRatio = trackSize.width / trackSize.height
let videoViewRatio = videoViewSize.width / videoViewSize.height
var newSize: CGSize
if videoViewRatio > trackRatio
newSize = CGSize(width: trackSize.width * videoViewSize.height / trackSize.height, height: videoViewSize.height)
else
newSize = CGSize(width: videoViewSize.width, height: trackSize.height * videoViewSize.width / trackSize.width)
return newSize
return CGSize.zero
【讨论】:
以上是关于如何获取 AVPlayer 的视频帧?的主要内容,如果未能解决你的问题,请参考以下文章
想要在 AVPlayer 中播放视频时逐帧提取视频中的所有图像
从 Swift 3 中的 API 获取 AVPlayer 中的视频
如何从 Flutter 中的 Jitsi Meet Conference 获取视频帧