AVPlayerItem出现AVStatusFailed和错误代码“Cannot Decode”的故障。

36

我遇到了一个奇怪的问题,希望有人能够提供帮助。

在我的iOS应用程序中,我使用MutableComposition将用户照片库中的视频和应用程序包中的音频文件结合起来,创建具有自定义配乐的视频。然后我使用AVPlayerAVPlayerItem播放器,使用我自己创建的自定义视频播放器向用户播放视频。

每次创建新的合成时,都会清除、释放资产、播放器和合成,并将其恢复到干净、初始化状态。

一切正常,直到成功创建四个这种方式的视频后,每隔一段时间尝试创建播放器就会失败,并显示错误无法解码。它不关心是否重新创建相同的视频,也与视频或音频文件的大小/长度无关,只是始终在第五次尝试时失败,就像钟表一样准确。一旦它失败了,它就会一直失败下去!

这很奇怪,因为它刚刚解码了相同的视频四次,没有任何问题,那么突然失败了?所以,如果有人有线索,请告诉我。


抱歉...我脑中有一些问题:
  1. “无法解码”错误的原因是什么?
  2. 是否有人遇到过类似的情况?如果有,解决方案是什么?
  3. 经过一些调查,我发现即使只使用视频资产(而不是整个合成),它也会失败,所以问题必须要么在资产初始化中出错,要么在播放器初始化中出错。可能出了什么问题?(我在堆上分配它们,在制作新电影时释放它们)
  4. 总的来说,有什么想法吗?
谢谢。
- user1112293
为什么你不添加相关代码,或者至少是那些你不确定的部分呢?否则我们只能猜测。 - Till
5个回答

90

大家好,我从苹果那里得到了答案。我使用了我的开发者TSI生命线来询问问题,现在我将总结回复。

AVFoundation会限制并发视频播放器的数量,这是由于iOS硬件的限制所致。当前设备的限制为4个播放器。如果创建第5个播放器,您将收到“无法解码”的错误提示。这不是AVPlayer或AVPlayerItem实例数量的限制,而是将AVPlayerItem与AVPlayer关联创建“渲染流水线”,您仅限于4条这样的渲染流水线。例如,以下代码将会创建新的渲染流水线:

AVPlayer *player = [AVPlayer playerWithPlayerItem:somePlayerItem];  
// assuming the AVPlayerItem is ready to go with an AVAsset that has been loaded

我还被警告说,你不能假设你将拥有4个可用的管道。 另一个应用程序可能正在使用一个或多个。 的确,在iPad上我见过这种情况发生,但不清楚哪个应用程序正在使用管道。

所以,就是这样,完全没有记录,但这就是故事。


4
你是如何解决这个问题的?你是创建了一个包含多个项目的播放器并在它们之间切换吗?还是每次播放视频前都要创建新的播放器和项目? - Piotr
9
对于任何有兴趣的人,我已在tvOS上确认了此问题。然而,在实践中,我能够达到约18条流水线,然后出现问题。最大计数必须是平台/硬件相关的。此外,当我达到限制并且播放失败时,没有错误:AVKit没有日志,也没有任何播放器发出错误(错误属性为nil,状态良好)。 - jaredsinclair
2
玩家物品确实会发出错误,但您必须键值观察玩家物品状态变量,并在其更改为“ .error”时打印与玩家物品相关联的错误变量。 - Josh Bernfeld
当我遇到这个问题时,我的应用程序因为内存问题而崩溃了,导致有太多的播放器。https://stackoverflow.com/q/64485366/4833705。事实证明,不是因为有太多的播放器,正如@one09jason所说,是因为与播放器关联的AVPlayerItems太多了。https://stackoverflow.com/a/64485645/4833705 - Lance Samaria

9
我创建了4个AVPlayer实例后遇到了相同的错误消息,在我的情况下修复方法并不完全相同。也许这会帮助其他遇到此问题的人。
最终我发现,当我认为它们已经被释放时,AVPlayers并没有被释放。在我的情况下,我将AVPlayer视图控制器推入导航控制器中。即使我一次只创建一个AVPlayer实例,当View Controllers从nav控制器中弹出时,它们并没有立即被释放。然后很容易达到4个AVPlayer实例,直到旧的View Controllers被清理之前。
直到我确保先前的播放器被释放才解决了这个问题。为了完整起见,在释放之前我释放了AVPlayerItem、AVPlayer并将AVPlayerLayer上的播放器设置为nil。
我不得不想知道是否有AVPlayer实例的某些限制,无意或有意的。来自文档的相关信息: https://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html

多个播放器图层:您可以从单个AVPlayer实例创建任意多个AVPlayerLayer对象,但仅最近创建的该类图层将在屏幕上显示任何视频内容。


3

在我弄明白之前,这个问题真的让我很头疼。通过参考本帖和其他一些帖子,我找到了一些线索。在我的代码中,最大的问题是每次想播放视频时都要实例化视频播放器控制器。现在,在主控制器(在这种情况下是我的DetailViewContoller)中它只会被实例化一次:

@interface DetailViewController () {
    VideoPlayerViewController *videoPlayerViewController;
}

- (void) viewDidLoad
{
    [super viewDidLoad];

    videoPlayerViewController = [[VideoPlayerViewController alloc] initWithNibName: nil bundle: nil];
}

当我想要展示一个视频时,我会调用我的DetailViewController的startVideoPlayback方法:

- (void) startVideoPlayback: (NSString *)videoUID
{
    videoPlayerViewController.videoUID = videoUID;
    [self presentModalViewController: videoPlayerViewController animated: YES];
}

注意:我将传递它的“videoUID” -- 一个在应用程序的其他部分用于创建视频的唯一标识符。

在VideoPlayerViewController中(这主要是从苹果的AVPlayerDemo示例中抄袭的),一次性屏幕设置(初始化AVPlayer、设置工具栏等)在viewDidLoad中完成--现在只调用一次,所有针对每个视频的设置都在viewWillAppear中完成,然后调用prepareToPlay

- (void) prepareToPlay
{
    [self initScrubberTimer];   
    [self syncPlayPauseButtons];
    [self syncScrubber];

    NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    //*** Retrieve and play video at associated with this videoUID
    NSString *destinationPath = [documentsDirectory stringByAppendingFormat: @"/%@.mov", videoUID];
    if ([self fileExists: destinationPath]) {

        //*** Show the activity indicator spinny thing
        [pleaseWait startAnimating];
        [self setURL: [NSURL fileURLWithPath: destinationPath]];
        //*** Get things going with the first video in this session
        if (isFirst) {
            isFirst = NO;
        //*** Subseqeunt videos replace the first one
        } else {
            [self.mPlayer replaceCurrentItemWithPlayerItem: [AVPlayerItem playerItemWithURL: [NSURL fileURLWithPath: destinationPath]]];
        }
    }
}

2

好的,我找到了一个解决方案,希望这对于可能遇到类似问题的人有所帮助。

在我的情况下,解决方案是在主线程上初始化AVPlayer和AVPlayerItem的资产,并确保在playerItem和player对象返回“ReadyToPlay”状态之前不创建实际的AVPlayerLayer。

这证明是很棘手的隔离问题,我仍然不知道为什么它在前4次工作并且在第5次一直失败。

虽然我无法包含代码,但这不是单行或几个函数的问题。 这是一个我无法开始隔离的复杂问题。 感谢评论。


我完全理解这一点。通常的做法是将其简化为最小实现,可能会找到问题。往往足够了,最小实现突然就起作用了,这可以确保问题出在其他地方,在你从未想过的一个角落里:D...圣诞快乐! - Till

1
似乎这个问题可能由任何解码任务引起,不仅限于实际播放器。
当我实现一个后台任务来从当前正在播放的视频中提取帧时,我随机遇到了这个问题,使用了generateCGImagesAsynchronously
我需要在屏幕上显示4个视频,竞争条件有时会导致帧提取在视频开始播放之前就开始,并且我将永远等待isReadyForDisplay
如果无法避免这种情况,不确定一个好的恢复策略是什么,我可能会尝试replaceCurrentItem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接