如何获取 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 处理。这假设_playerAVPlayer

- (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 中的视频

ffmpeg如何从url获取视频帧数据

如何从 Flutter 中的 Jitsi Meet Conference 获取视频帧

如何通过 OpenCV 和 Python 通过索引从视频中获取帧?

如何在播放 m3u8 视频流之前获取帧图像? iOS