【问题标题】:iOS: AVPlayer - getting a snapshot of the current frame of a videoiOS:AVPlayer - 获取视频当前帧的快照
【发布时间】:2016-09-17 20:33:25
【问题描述】:

我花了一整天的时间浏览了很多 SO 答案、Apple 参考资料、文档等,但没有成功。

我想要一个简单的事情:我正在使用 AVPlayer 播放视频,我想暂停它并将当前帧作为UIImage。就是这样。

我的视频是网上的m3u8文件,在AVPlayerLayer可以正常播放,没有任何问题。

我尝试了什么:

  1. AVAssetImageGenerator. 不起作用,copyCGImageAtTime:actualTime: error: 方法返回空图像参考。根据答案here AVAssetImageGenerator 不适用于流媒体视频。
  2. 拍摄玩家视图的快照。我首先在AVPlayerLayer 上尝试了renderInContext:,但后来我意识到它并没有渲染这种“特殊”图层。然后我发现了 iOS 7 中引入的一种新方法 - drawViewHierarchyInRect:afterScreenUpdates:,它应该也可以渲染特殊图层,但没有运气,仍然得到了显示视频的带有空白黑色区域的 UI 快照。
  3. AVPlayerItemVideoOutput. 我为AVPlayerItem 添加了视频输出,但是每当我调用hasNewPixelBufferForItemTime: 时,它都会返回NO。我猜这个问题又是流视频和I am not alone 有这个问题。
  4. AVAssetReader.我本来想试试的,但在找到相关问题here后决定不耽误时间。

那么有没有什么方法可以获取我现在在屏幕上看到的东西的快照?我简直不敢相信。

【问题讨论】:

    标签: ios objective-c video avfoundation avplayer


    【解决方案1】:

    AVPlayerItemVideoOutput 在 m3u8 上对我来说很好用。也许是因为我没有咨询hasNewPixelBufferForItemTime,而只是打电话给copyPixelBufferForItemTime?此代码生成CVPixelBuffer 而不是UIImage,但有describe how to do that 的答案。

    这个答案大多抄自here

    #import "ViewController.h"
    #import <AVFoundation/AVFoundation.h>
    
    @interface ViewController ()
    
    @property (nonatomic) AVPlayer *player;
    @property (nonatomic) AVPlayerItem *playerItem;
    @property (nonatomic) AVPlayerItemVideoOutput *playerOutput;
    
    @end
    
    @implementation ViewController
    - (void)setupPlayerWithLoadedAsset:(AVAsset *)asset {
        NSDictionary* settings = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
        self.playerOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:settings];
        self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
        [self.playerItem addOutput:self.playerOutput];
        self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
    
        AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
        playerLayer.frame = self.view.frame;
        [self.view.layer addSublayer:playerLayer];
    
        [self.player play];
    }
    
    - (IBAction)grabFrame {
        CVPixelBufferRef buffer = [self.playerOutput copyPixelBufferForItemTime:[self.playerItem currentTime] itemTimeForDisplay:nil];
        NSLog(@"The image: %@", buffer);
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
    
        NSURL *someUrl = [NSURL URLWithString:@"http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8"];
        AVURLAsset *asset = [AVURLAsset URLAssetWithURL:someUrl options:nil];
    
        [asset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"tracks"] completionHandler:^{
    
            NSError* error = nil;
            AVKeyValueStatus status = [asset statusOfValueForKey:@"tracks" error:&error];
            if (status == AVKeyValueStatusLoaded)
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self setupPlayerWithLoadedAsset:asset];
                });
            }
            else
            {
                NSLog(@"%@ Failed to load the tracks.", self);
            }
        }];
    }
    
    @end
    

    【讨论】:

    • 我现在就试试,让你知道。一个简短的问题:我应该从一开始就设置AVPlayerItemVideoOutput 对象吗?我的代码正在播放没有添加输出的视频,然后,每当我需要快照时,我都会快速创建一个AVPlayerItemVideoOutput 对象,将其添加到播放器项目并尝试读取像素缓冲区。我还尝试过早一点添加输出 - 每当我的特殊快照手势开始触摸但尚未识别时。这很重要吗?
    • 我认为您必须从一开始就设置AVPlayerItemVideoOutput,可能在开始播放之前。
    • 感谢您的解决方案,我刚刚检查过,它有效!正如你所说,诀窍是在我开始玩之前添加AVPlayerItemVideoOutput。在未来某个地方只为一个屏幕截图添加视频输出似乎有点效率低下,在大多数情况下甚至不会被拍摄,但至少它有效!
    • 不客气。我想你是对的 - 将ARGB AVPlayerItemVideoOutput 附加到很可能是YUV 流可能会很昂贵。我从来没有想过。
    • 此解决方案是否适用于受 FairPlay 保护的 HLS?我已经尝试过 copyPixelBufferForItemTime,它适用于不受保护的流,但是一旦你使用 FairPlay,它就会返回 NULL。 stackoverflow.com/questions/42839831/…
    【解决方案2】:

    AVAssetImageGenerator 是拍摄视频的最佳方式,此方法异步返回UIImage

    import AVFoundation
    
    // ...
    
    var player:AVPlayer? = // ...
    
    func screenshot(handler:@escaping ((UIImage)->Void)) {
        guard let player = player ,
            let asset = player.currentItem?.asset else {
                return
        }
    
        let imageGenerator = AVAssetImageGenerator(asset: asset)
        imageGenerator.appliesPreferredTrackTransform = true
        let times = [NSValue(time:player.currentTime())]
    
        imageGenerator.generateCGImagesAsynchronously(forTimes: times) { _, image, _, _, _ in
            if image != nil {
                handler(UIImage(cgImage: image!))
            }
        }
    }
    

    (它是 Swift 4.2)

    【讨论】:

    • @Axel Guilmin 这仅捕获 Avplayer。如果我想同时截取 Avplayer 和 UiView 的屏幕截图怎么办?
    • 我认为我的答案不是捕获 UIView 的正确方法。我没有测试它,但这个答案似乎更好:stackoverflow.com/a/4334902/1327557
    • @Axel Guilmin 谢谢你的回复。看到这是我的问题:stackoverflow.com/questions/42085479/...
    • @Anessence 你能从直播中找到答案吗?
    猜你喜欢
    • 1970-01-01
    • 2016-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-19
    • 2017-10-06
    • 1970-01-01
    相关资源
    最近更新 更多