问题

发现了个奇怪的问题,部分视频不清楚是什么原因导致的,会发生有有声无画的情况。

查看AVPRO的加载回调,是MediaPlayerEvent.EventType.FirstFrameReady 这个类型没有返回,基本可以得知了视频画面是没有准备好的。

排查

这个基本上就是VideoAPI设置的问题,毕竟AVPRO不能做到调普通播放器那样,支持直接调用ffmpeg解码。参考官方的 Demo场景,是可以看到使用的是MediaFoundation 这个VideoAPI。原先的DirectShow 比较旧,可能对一些新技术兼容性不是很好。具体里面的差异就比较深奥了。

MediaFoundation :Windows 的新一代视频 API,现代化、效率更高。

  • 优点:硬件加速支持好,系统支持视频格式清晰。

  • 注意:有时对容器封装非常敏感(如 faststart header)。

解决

就像如上所说的,解决方案就是把VideoAPI改为MediaFoundation 即可解决问题。此时要注意,如果Audio output 设置的是Unity,一定要挂载AVPRO的Audio Output 组件,才会有声音输出。不知为何DirectShow 时就不需要。

题外话

这里做了个AVPRO的UniTask异步加载拓展,可以做等待视频加载和播放完成。其核心解决方案就是 MediaPlayer.Events.AddListener 这个回调,基于状态设置等待结束。

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using RenderHeads.Media.AVProVideo;
using UnityEngine;
using UnityEngine.UI;

public static class AVProVideoUniTaskExtensions
{
    // 等待视频播放完成(到结尾)
    public static async UniTask WaitForCompletion(this MediaPlayer player, 
                                                  CancellationToken cancellationToken = default)
    {
        if (player == null)
        {
            throw new ArgumentNullException(nameof(player));
        }

        // 如果视频已停止,直接返回
        if (!player.Control.IsPlaying())
        {
            return;
        }

        // 创建任务源,用于在事件触发时完成任务
        var tcs = new UniTaskCompletionSource();
        
        // 定义事件处理方法
        void EventHandler(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
        {
            switch (et)
            {
                case MediaPlayerEvent.EventType.FinishedPlaying:
                    player.Events.RemoveListener(EventHandler);
                    tcs.TrySetResult();
                    break;
                
                case MediaPlayerEvent.EventType.Error:
                    player.Events.RemoveListener(EventHandler);
                    tcs.TrySetException(new Exception($"视频播放错误: {errorCode}"));
                    break;
            }
        }

        // 添加事件监听
        player.Events.AddListener(EventHandler);

        try
        {
            // 等待任务完成或取消
            await tcs.Task.AttachExternalCancellation(cancellationToken);
        }
        finally
        {
            // 确保移除事件监听
            player.Events.RemoveListener(EventHandler);
        }
    }

    /// <summary>
    /// 等待视频准备就绪(ReadyToPlay)
    /// </summary>
    /// <param name="player"></param>
    /// <param name="cancellationToken"></param>
    /// <exception cref="ArgumentNullException"></exception>
    public static async UniTask WaitForReady(this MediaPlayer player, MediaPath path,bool isOpen=true,
        CancellationToken cancellationToken = default)
    {
        if (player == null||path==null)
        {
            throw new ArgumentNullException(nameof(player));
        }

        // 创建任务源,用于在事件触发时完成任务
        var tcs = new UniTaskCompletionSource();
        
        // 定义事件处理方法
        void EventHandler(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
        {
            switch (et)
            {
                //case MediaPlayerEvent.EventType.ReadyToPlay:
                case MediaPlayerEvent.EventType.FirstFrameReady:
                case MediaPlayerEvent.EventType.Started:
                    player.Events.RemoveListener(EventHandler);
                    tcs.TrySetResult();
                    break;
                
                case MediaPlayerEvent.EventType.Error:
                    player.Events.RemoveListener(EventHandler);
                    tcs.TrySetException(new Exception($"视频播放错误: {errorCode}"));
                    break;
            }
        }

        // 添加事件监听
        player.Events.AddListener(EventHandler);
        player.OpenMedia(path, isOpen);

        try
        {
            // 等待任务完成或取消
            await tcs.Task.AttachExternalCancellation(cancellationToken);
        }
        finally
        {
            // 确保移除事件监听
            player.Events.RemoveListener(EventHandler);
        }
    }
}