Unity之获取Avpro视频画面并在本地创建缩略图

一、效果

获取StreamingAssets文件夹下的所有视频(包含其子文件夹),获取指定时间的视频画面,然后将图片保存到本地磁盘中。

二、关于Avpro的事件监听

当指定视频时间进度时会触发FinishedSeeking,代表加载完成这时我们在进行缩略图创建功能,否则视频帧未更新创建缩略图会出现问题。在第三步脚本中我还使用协程的方式判断视频帧是否加载完成。

cs 复制代码
//使用方法   
mediaPlayer.Events.AddListener(OnVideoEvent);


//监听
 private void OnVideoEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
    {
        switch (et)
        {
            case MediaPlayerEvent.EventType.MetaDataReady:
                Debug.Log("当元数据(宽度,持续时间等)可用时触发");
                
                break;
            case MediaPlayerEvent.EventType.ReadyToPlay:
                Debug.Log("可以播放");
                break;
            case MediaPlayerEvent.EventType.Started:
                Debug.Log("播放开始时触发");
                break;
            case MediaPlayerEvent.EventType.FirstFrameReady:
                Debug.Log("第一帧渲染完成");

                break;
            case MediaPlayerEvent.EventType.FinishedPlaying:
                Debug.Log("视频结束");
                break;
            case MediaPlayerEvent.EventType.Closing:
                Debug.Log("媒体关闭时触发");
                
                break;
            case MediaPlayerEvent.EventType.Error:
                Debug.Log("发生错误时触发");
                
                break;
            case MediaPlayerEvent.EventType.SubtitleChange:
                Debug.Log("字幕改变时触发");

                break;
            case MediaPlayerEvent.EventType.Stalled:
                Debug.Log("当介质停止时触发(例如。当失去与媒体流的连接时)");

                break;
            case MediaPlayerEvent.EventType.Unstalled:
                Debug.Log("当媒体从停止状态恢复时触发(例如。当失去的连接重新建立时)");

                break;
            case MediaPlayerEvent.EventType.ResolutionChanged:
                Debug.Log("当视频的分辨率发生变化(包括加载)时触发,用于自适应流");

                break;
            case MediaPlayerEvent.EventType.StartedSeeking:
                Debug.Log("搜索开始时触发");

                break;
            case MediaPlayerEvent.EventType.FinishedSeeking:
                Debug.Log("搜索完成时触发 Seek视频指定时间跳转结束后调用");
        

                break;
            case MediaPlayerEvent.EventType.StartedBuffering:
                Debug.Log("缓冲开始时触发");

                break;
            case MediaPlayerEvent.EventType.FinishedBuffering:
                Debug.Log("缓冲完成时触发");

                break;
            case MediaPlayerEvent.EventType.PropertiesChanged:
                Debug.Log("当任何属性被触发(例如立体声包装被改变)-这必须手动触发");

                break;
            case MediaPlayerEvent.EventType.PlaylistItemChanged:
                Debug.Log("当新项目在播放列表中播放时触发");

                break;
            case MediaPlayerEvent.EventType.PlaylistFinished:
                Debug.Log("当播放列表结束时触发");

                break;
            case MediaPlayerEvent.EventType.TextTracksChanged:
                Debug.Log("当添加或删除文本轨道时触发");

                break;
        	
          
        }
    }

三、脚本

cs 复制代码
using System.IO;
using UnityEngine;
using RenderHeads.Media.AVProVideo;
using System.Collections.Generic;
using System.Collections;

public class ThumbnailGenerator : MonoBehaviour
{

    public MediaPlayer mediaPlayer;
    public float thumbnailTime = 1.0f;  //时间戳,用于生成缩略图的位置

    int index;//当前视频地址
    List<string> filesPath = new List<string>();
    void Start()
    {
        string[] videoFiles = Directory.GetFiles(Application.streamingAssetsPath, "*.*", SearchOption.AllDirectories);

        foreach (string videoFile in videoFiles)
        {
            string extension = Path.GetExtension(videoFile).ToLower();
            if (extension == ".mp4" || extension == ".avi" || extension == ".mov")  // 检查支持的视频格式
                filesPath.Add(videoFile);//添加所有视频地址
        }

        //方法一 协程创建 
        //StartCoroutine(SeekAndWaitForLoad(mediaPlayer, thumbnailTime, filesPath[0]));

        //方法二 视频状态监听 创建
        mediaPlayer.Events.AddListener(OnVideoEvent);
        mediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, filesPath[0], false);
        mediaPlayer.Control.Seek(thumbnailTime);// 执行Seek操作

    }


    #region 方法一 使用协程创建

    /// <summary>
    /// 创建视频缩略图
    /// </summary>
    /// <param name="mp">视频播放器</param>
    /// <param name="time">视频指定时间</param>
    /// <param name="path">视频地址</param>
    /// <returns></returns>
    private IEnumerator SeekAndWaitForLoad(MediaPlayer mp, float time, string path)
    {
        mp.OpenMedia(MediaPathType.AbsolutePathOrURL, path, false);
        // 执行Seek操作
        mp.Control.Seek(time);

        // 等待寻址完成
        yield return new WaitUntil(() => !mp.Control.IsSeeking());

        // 确保播放时间已经跳转到期望的时间点
        yield return new WaitUntil(() => Mathf.Abs((float)mp.Control.GetCurrentTime() - time) < 0.1f);

        // 可选:等待视频帧更新完成 (确保画面已经渲染)
        yield return new WaitForEndOfFrame();

        Debug.Log("寻找完整的和视频帧加载时间: " + mp.Control.GetCurrentTime());

        //获取视频 RenderTexture
        RenderTexture renderTexture = GetVideoRenderTexture(mp);

        //将RenderTexture转换成texture2D
        Texture2D texture2D = RenderTexture2Texture2D(renderTexture);

        //将Texture2D写入本地
        string previewPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)) + ".png";
        Texture2dWriteLocal(texture2D, previewPath);

        //创建下一个缩略图
        index++;
        if (index < filesPath.Count)
            StartCoroutine(SeekAndWaitForLoad(mediaPlayer, thumbnailTime, filesPath[0]));
    }
    #endregion

    #region 方法二 视频状态监听
    private void OnVideoEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
    {
        string path = mp.MediaPath.Path;
        switch (et)
        {
            case MediaPlayerEvent.EventType.FinishedSeeking:
                Debug.Log("搜索完成时触发");

                //获取视频 RenderTexture
                RenderTexture renderTexture = GetVideoRenderTexture(mp);

                //将RenderTexture转换成texture2D
                Texture2D texture2D = RenderTexture2Texture2D(renderTexture);

                //将Texture2D写入本地
                string previewPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)) + ".png";
                Texture2dWriteLocal(texture2D, previewPath);

                //创建下一个缩略图
                index++;
                if (index < filesPath.Count)
                {
                    mp.OpenMedia(MediaPathType.AbsolutePathOrURL, filesPath[index], false);
                    mp.Control.Seek(thumbnailTime);// 执行Seek操作
                }
                break;
        }

    }

    #endregion



    #region 获取视频 RenderTexture
    Material mt;
    RenderTexture GetVideoRenderTexture(MediaPlayer mp)
    {
        if (mt == null)
        {
            mt = new Material(Shader.Find("AVProVideo/Internal/Resolve"));
            mt.color = Color.white;//颜色设置
            mt.DisableKeyword("USE_HSBC");//禁用USE_HSBC关键字
        }
        VideoRender.SetupMaterialForMedia(mt, mp, -1); //设置材质贴图等

        VideoRender.ResolveFlags resolveFlags = (VideoRender.ResolveFlags.ColorspaceSRGB | VideoRender.ResolveFlags.Mipmaps | VideoRender.ResolveFlags.PackedAlpha | VideoRender.ResolveFlags.StereoLeft);//播放器标志

        return VideoRender.ResolveVideoToRenderTexture(mt, null, mp.TextureProducer, resolveFlags);//将视频解析为RenderTexture
    }
    #endregion

    #region 将RenderTexture转换为Texture2D
    private static Texture2D RenderTexture2Texture2D(RenderTexture renderTexture)
    {
        int width = renderTexture.width;
        int height = renderTexture.height;
        Texture2D texture2D = new Texture2D(width, height, TextureFormat.ARGB32, false);
        RenderTexture.active = renderTexture;
        texture2D.ReadPixels(new Rect(0, 0, width, height), 0, 0);
        texture2D.wrapMode = TextureWrapMode.Clamp;
        texture2D.Apply();
        return texture2D;
    }
    #endregion

    #region 将Texture2d写入本地
    void Texture2dWriteLocal(Texture2D texture2D, string localPath)
    {
        File.WriteAllBytes(localPath, texture2D.EncodeToPNG());
    }
    #endregion

}

在使用avpro制作缩略图时,我尝试使用mediaPlayer.TextureProducer.GetTexture();方法获取画面Texture,然后将其写入RenderTexture,在转换Texture2D写入本地会发现缩略图颜色泛白,经过排查MediaPlayer原脚本,发现在转换RenderTexture时需要使用avpro的指定shader处理才会显示正确的材质,感兴趣的朋友可以打开MediaPlayer脚本进行查看
改版

MediaPlayer脚本

原脚本查看方法:

注意:添加的MediaPlayer需要将AutoOpen和AutoPlay关掉

相关推荐
逐·風26 分钟前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
_oP_i2 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
代码盗圣6 小时前
GODOT 4 不用scons编译cpp扩展的方法
游戏引擎·godot
我喜欢就喜欢6 小时前
基于qt vs下的视频播放
开发语言·qt·音视频
安步当歌7 小时前
【WebRTC】视频采集模块中各个类的简单分析
音视频·webrtc·视频编解码·video-codec
EasyGBS7 小时前
国标GB28181公网直播EasyGBS国标GB28181软件管理解决方案
大数据·网络·音视频·媒体·视频监控·gb28181
Johnstons10 小时前
AnaTraf | 网络性能监控系统保障音视频质量的秘籍
网络·音视频·网络流量监控·网络流量分析·npmd
lrlianmengba10 小时前
推荐一款非常好用的视频编辑软件:Movavi Video Editor Plus
音视频
SZ17011023110 小时前
ffplay 实现视频流中音频的延迟
音视频·延迟
Leoysq11 小时前
【UGUI】实现点击注册按钮跳转游戏场景
游戏·unity·游戏引擎·ugui