在 exportAsynchronouslyWithCompletionHandler 中创建 AVPlayer 时,看到视频之前的长时间延迟

Posted

技术标签:

【中文标题】在 exportAsynchronouslyWithCompletionHandler 中创建 AVPlayer 时,看到视频之前的长时间延迟【英文标题】:Long delay before seeing video when AVPlayer created in exportAsynchronouslyWithCompletionHandler 【发布时间】:2015-07-04 22:28:12 【问题描述】:

播放从AVAssetExportSession 导出的视频时,您会在看到视频之前很久就听到音频。音频立即播放,但视频仅在录制循环数次(即开始和结束)后出现。换句话说,在看到任何图像之前,您会多次听到视频中的音频。

我们在 ios 8 上使用 AutoLayout。

使用以下测试,我们将问题隔离到exportAsynchronouslyWithCompletionHandler。在这两个代码块中,我们播放一个现有的视频——与导出无关——因此导出过程已作为一个变量被消除。

代码 1 在开始时同时播放视频和音频,而 代码 2 仅在开始时播放音频并在延迟 10-60 秒后显示视频(在视频循环多次)。

这两个代码块的唯一区别是一个使用exportAsynchronouslyWithCompletionHandler播放视频而另一个不使用。

帮助?音频是否有可能先导出并准备好在视频之前播放?与发生在不同线程上的导出有关吗?

func initPlayer(videoURL: NSURL) 
    // Create player
    player = AVPlayer(URL: videoURL)
    let playerItem = player.currentItem
    let asset = playerItem.asset
    playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = videoView.frame
    view.layer.addSublayer(playerLayer)
    player.seekToTime(kCMTimeZero)
    player.actionAtItemEnd = .None
    player.play()

    // Get notified when video done for looping purposes
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: playerItem)

    // Log status
    println("Initialized video player: \(CMTimeGetSeconds(asset.duration)) seconds & \(asset.tracks.count) tracks for \(videoURL)")


func playExistingVideo() 
    let filename = "/ChopsticksVideo.mp4"
    let allPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    let docsPath = allPaths[0] as! NSString
    let exportPath = docsPath.stringByAppendingFormat(filename)
    let exportURL = NSURL.fileURLWithPath(exportPath as String)!

    initPlayer(exportURL)

代码 1:

    // Create exporter
    let exporter = AVAssetExportSession(asset: mainComposition, presetName: AVAssetExportPresetHighestQuality)
    exporter.videoComposition = videoComposition
    exporter.outputFileType = AVFileTypeMPEG4
    exporter.outputURL = exportURL
    exporter.shouldOptimizeForNetworkUse = true

    playExistingVideo()

代码 2:

    // Create exporter
    let exporter = AVAssetExportSession(asset: mainComposition, presetName: AVAssetExportPresetHighestQuality)
    exporter.videoComposition = videoComposition
    exporter.outputFileType = AVFileTypeMPEG4
    exporter.outputURL = exportURL
    exporter.shouldOptimizeForNetworkUse = true

    // -- Export video
    exporter.exportAsynchronouslyWithCompletionHandler(
        self.playExistingVideo()
    )

【问题讨论】:

@matt 感谢您的评论。是的,异步很好理解,但你能解释为什么音频在视频之前播放得很好吗?如果你仔细阅读这个问题,那就是问题的症结所在,消极性是不受欢迎的。如果这仅仅是一个异步延迟的问题,人们会期望音频和视频都会延迟。在这种情况下,只有视频会延迟,而音频会立即播放...... @matt 恕我直言,如果您拒绝仔细阅读问题,那么挑战某人并留下刻薄的话似乎是不合理的。关于“docsPath.stringByAppendingFormat”,这是在多个 SO 帖子中发现的文件名构造方法。由于我通常是 Swift 和 iOS 的新手,并且在早期使用文件 URL 时遇到了麻烦,所以我使用这种方法,因为有几个 SO 答案使用了这种格式。同样,如果您阅读了这个问题,“exportURL”是不相关的,因为我们在完成处理程序期间所做的只是调用 playExistingVideo。 @matt 虽然感谢您抽出宝贵时间发表评论,但我不欣赏这种消极情绪。即使假设我不理解异步的含义,我相信可能会有更具建设性的回应。不可否认,你是 SO 的支柱,比我重要得多,但也因为你的地位和频繁的贡献,我认为你承担了额外的负担来促进像我这样不那么重要的成员会努力效仿的尊重、文明的文化.无论如何,感谢您的其他贡献并帮助我们变得如此出色。 @matt 这些都是很好的问题。对困惑感到抱歉。这段代码是从一个更大的功能中提取的,在将事情归结为一个问题时,我一定是过于简单化了。目标:让用户录制一段 10 秒的视频并在视频上叠加文字。为此,我使用AVAssetExportSession,但在播放视频供用户查看时,音频在图像出现之前很久就播放了。经过数小时的调试,我发现即使使用现有视频,问题仍然存在,并且似乎与使用exportAsynchronouslyWithCompletionHandler.有关 @matt 希望这能解释两个代码块:它将问题隔离到exportAsynchronouslyWithCompletionHandler,,这意味着我可以通过调用playExistingVideo 到@987654331 来重现播放问题(即,音频早于视频) @ 如果有帮助,这里是完整的功能(它提供了更多的上下文,但代码似乎不会直接导致问题,只会使问题更长的阅读时间而不增加太多): 【参考方案1】:

我会建议问题出在这里:

    // Create player
    player = AVPlayer(URL: videoURL)
    let playerItem = player.currentItem
    let asset = playerItem.asset
    playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = videoView.frame
    view.layer.addSublayer(playerLayer)
    player.seekToTime(kCMTimeZero)
    player.actionAtItemEnd = .None
    player.play()

您会看到,当您从视频 URL 创建 AVPlayer 时,它会出现尚未准备好播放。它通常可以很快开始播放音频,但准备视频需要更长的时间。这可以解释延迟看到任何东西。

好吧,您不必等待视频准备好,而是直接说play()。这是我的建议。我建议你做的是我explain in my book(这是实际代码的链接):创建播放器和图层,然后设置 KVO 以便在播放器准备好显示时通知你,并且 然后添加图层并开始播放。

另外,我还有一个建议。在我看来,您正在运行该代码,设置您的界面(与层)并说play()在后台线程上,这似乎存在危险。这肯定会导致各种延误。您似乎在假设 exportAsynchronouslyWithCompletionHandler: 的完成处理程序正在主线程上被调用 - 您正在继续并调用下一个方法,因此继续设置您的界面。这是一个非常冒险的假设。根据我的经验,您永远不应该假设 any AVFoundation 完成处理程序位于主线程上。您应该在完成处理程序中使用dispatch_async 跳出主线程并仅从那里继续。如果您查看我链接到的代码,您会发现我这样做很小心。

【讨论】:

是的,我已经开始研究 KVO 作为一种解决方案(不幸的是,它并不像我们想象的那么简单),但是为什么视频延迟只发生在 exportAsynchronouslyWithCompletionHandler 上?这就是令人费解的地方。再次感谢您的帮助!我已经从你的 SO 帖子中学到了很多关于 Swift 的知识。 :) 刚刚看到您的编辑:将尝试使用 dispatch_async。由于异步完成处理程序,我不认为它是必需的,但我会看看你的代码。再次感谢! 你的阅读速度比我继续编辑我的答案要快,但我想我已经说出了我能想到的任何内容。 :) 抱歉,非常急于解决这个问题:)再次感谢您的帮助。 没问题,我想我已经完成了。对不起,我写得太慢了。【参考方案2】:

对于那些后来偶然发现这个问题的人,答案在接受答案的 cmets 中。关键 dispatch_async 部分如下:

[exporter exportAsynchronouslyWithCompletionHandler:^(void)
    dispatch_async(dispatch_get_main_queue(), ^
        switch (exporter.status) 
            case AVAssetExportSessionStatusCompleted:
                NSLog(@"Video Merge Successful");
                break;
            case AVAssetExportSessionStatusFailed:
                NSLog(@"Failed:%@", exporter.error.description);
                break;
            case AVAssetExportSessionStatusCancelled:
                NSLog(@"Canceled:%@", exporter.error);
                break;
            case AVAssetExportSessionStatusExporting:
                NSLog(@"Exporting!");
                break;
            case AVAssetExportSessionStatusWaiting:
                NSLog(@"Waiting");
                break;
            default:
                break;
        
    );

];

【讨论】:

【参考方案3】:

您可以在导出过程中播放视频合成。

playerItem = AVPlayerItem(asset: videoComposition)
player = AVPlayer(playerItem: playerItem)

Apple Docs

AVPlayerItem:

convenience init(asset: AVAsset)

【讨论】:

以上是关于在 exportAsynchronouslyWithCompletionHandler 中创建 AVPlayer 时,看到视频之前的长时间延迟的主要内容,如果未能解决你的问题,请参考以下文章

秋的潇洒在啥?在啥在啥?

上传的数据在云端的怎么查看,保存在啥位置?

在 React 应用程序中在哪里转换数据 - 在 Express 中还是在前端使用 React?

存储在 plist 中的数据在模拟器中有效,但在设备中无效

如何在保存在 Mongoose (ExpressJS) 之前在模型中格式化数据

如何在保存在 Mongoose (ExpressJS) 之前在模型中格式化数据