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

news2024/11/16 15:36:02

一、效果 

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

 

二、关于Avpro的事件监听

当指定视频时间进度时会触发FinishedSeeking,代表加载完成这时我们在进行缩略图创建功能,否则视频帧未更新创建缩略图会出现问题。

//使用方法   
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;
        	
          
        }
    }

三、脚本

下面我使用了两种方式创建缩略图

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脚本

原脚本查看方法:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2104539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[论文笔记]RAFT: Adapting Language Model to Domain Specific RAG

引言 今天带来一篇结合RAG和微调的论文&#xff1a;RAFT: Adapting Language Model to Domain Specific RAG。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 本文介绍了检索增强微调(Retrieval Augmented Fine Tunin…

CNN-LSTM用于时间序列预测,发二区5分+没问题!

为了进一步提高时序预测的性能&#xff0c;研究者们组合了CNN和LSTM的特点&#xff0c;提出了CNN-LSTM混合架构。 这种架构因为独特的结构设计&#xff0c;能同时处理时空数据、提取丰富的特征、并有效解决过拟合问题&#xff0c;实现对时间序列数据的高效、准确预测&#xff…

跨平台RTSP播放器之VLC Media Player还是SmartPlayer?

好多开发者纠结&#xff0c;RTSP流播放&#xff0c;到底是用开源的VLC Media Player还是大牛直播SDK的SmartPlayer&#xff1f;针对此&#xff0c;本文做个简单的技术探讨&#xff0c;方便开发者根据实际需要&#xff0c;做适合自己场景的选择&#xff1a; VLC Media Player …

oracle 数据库 day0823

ok了家人们&#xff0c;今天学习了orcle的基本用法&#xff0c;一日不见&#xff0c;如隔三秋啊&#xff0c; 一.多表联合查询 和之前学习的MySQL数据库一样的用法&#xff0c; 1.1 笛卡尔积查询 SELECT * FROM A表,B表 查询员工表和部门表 select * from emp e, dept d; e…

虚拟系统VS

定义 虚拟系统VS&#xff08;Virtual System&#xff09;是指将一台物理设备PS&#xff08;Physical System&#xff09;虚拟成多个相互隔离的逻辑系统。每个VS独立工作&#xff0c;在业务功能上等同于一台独立的传统物理设备&#xff0c;如图2-1所示。 目的 随着网络规模的不…

PNAS亮点文章!浙江大学/东北林业大学/深圳华大生命科学研究院等联合揭示大熊猫种群演化历史,提出物种保护新见解!

本文首发于“生态学者”微信公众号&#xff01;作者投稿系列 全新世以来&#xff0c;尤其是工业革命以来&#xff0c;资源的过度开发利用和环境污染等人类活动&#xff0c;导致野生动物栖息地严重破碎化&#xff0c;甚至丧失&#xff0c;形成了大量隔离小种群&#xff0c;致使…

TESSY导入导出测试用例

TESSY支持测试用例的导入和导出&#xff0c;下面我们以tessy5.1为例&#xff0c;给大家展示。 1、导入测试用例 因为导入测试用例&#xff0c;需要先创建测试集&#xff0c;这部分不熟悉的&#xff0c;可以参考一下&#xff1a; https://blog.csdn.net/u012568663/article/det…

网络编程(学习)2024.8.30

目录 IO多路复用 select、poll、epoll IO多路复用机制 一.select 1.函数 2.流程 3.案例使用select创建全双工客户端 4.并发服务器 5.案例使用select创建全双工服务端 二.poll 1.函数 2.流程 3.案例使用poll创建全双工客户端 4.案例使用poll创建全双工服务端 三、…

国产统信UOS桌面操作系统安装网络打印机

国产统信UOS桌面操作系统安装网络打印机比较麻烦&#xff0c;本文件记录了一些打印机的安装方法。 厂商机型驱动方式打印情况柯尼卡美能达KONICA MINOLTA bizhub c360i加装驱动正常惠普HP Color LaserJet Pro MFP M479fdw默认驱动正常东芝TOSHIBA e-STUDIO2020AC CSHM14177加装…

九、制作卡牌预制体

文章目录 制作预制体Physic 2D Raycaster 射线检测 Physic 2D Raycaster 一、制作预制体 使用两个空物体作为父子级&#xff0c;分别挂UI 设置图层front、从上到下为1-6 父物体挂在一个Sorting组件&#xff0c;因为卡牌可以制作成为一个整体&#xff0c; 所以在父物体上挂载…

Mac 数据恢复技巧:恢复 Mac 上已删除的文件

尝试过许多 Mac 数据恢复工具&#xff0c;但发现没有一款能达到宣传的效果&#xff1f;我们重点介绍最好的 Mac 数据恢复软件。 没有 Mac 用户愿意担心数据丢失&#xff0c;但您永远不知道什么时候会发生这种情况。无论是意外删除 Mac 上的重要文件、不小心弄湿了 Mac、感染病…

pdf转word格式乱了怎么调整?2024帮助你快速进行pdf格式调整的软件

pdf转word格式乱了怎么调整&#xff1f;2024帮助你快速进行pdf格式调整的软件 将PDF文件转换为Word格式时&#xff0c;可能会遇到格式混乱的问题。这通常是由于PDF文件的复杂排版、字体嵌入、图像和表格布局等因素造成的。不过&#xff0c;有一些软件可以帮助你更好地保持原有…

测开必备知识:线程安全和线程不安全

什么是线程安全 线程安全指的是在多线程环境下&#xff0c;一个对象或者数据结构能够保证在并发访问时依然能够维持其预期的行为&#xff0c;不会出现数据不一致或者其他意外情况。 反之就是线程不安全。 多线程环境下可能产生的问题 当多个线程同时访问共享的资源&#xf…

QT笔记 - QProcess读取外部程序(进程)消息

简要介绍 QProcess可用于在当前程序中启动独立的外部程序(进程)&#xff0c;并进行通讯&#xff0c;通讯原理是通过程序的输入或输出流&#xff0c;即通过c中的printf()和或c的std::cout等。 函数 void QProcess::start(const QString & program, const QStringList &am…

直播美颜SDK开发方案详解:如何打造智能化主播美颜工具?

在竞争激烈的市场环境下&#xff0c;如何通过直播美颜SDK打造智能化的主播美颜工具&#xff0c;已成为技术开发者和产品经理们关注的焦点。本篇文章将从核心技术、开发流程和优化策略等方面&#xff0c;详细解析如何构建一款智能化的主播美颜工具。 一、核心技术解析 1.人脸识…

第10讲 后端2

主要目标&#xff1a;理解滑动窗口法、位姿图优化、带IMU紧耦合的优化、掌握g2o位姿图。 第9讲介绍了以为BA为主的图优化。BA能精确优化每个相机位姿与特征点位置。不过在更大的场景中&#xff0c;大量特征点的存在会严重降低计算效率&#xff0c;导致计算量越来越大&#xff0…

图纸安全管理措施有哪些?这九大措施全方位保护图纸安全

图纸安全管理措施是一个综合性的体系&#xff0c;旨在通过技术手段和管理措施确保图纸的机密性、完整性和可用性。以下是一些关键的图纸安全管理措施&#xff0c;以及软件安企神的应用。 一、保密协议与意识教育 签订保密协议&#xff1a;与所有接触图纸的员工签署保密协议&am…

计算方法——插值法程序实现二(牛顿法)

例题 给出的函数关系表&#xff0c;分别利用牛顿插值法计算的近似值。 0.10.20.30.40.51.1051711.2214031.3498591.4918251.648721 参考代码一&#xff1a;Python代码实现&#xff08;自编码&#xff09; import math """ :difference_quotient差商函数 &quo…

基于AppBuilder自定义组件开发大模型应用

AppBuilder简介&#xff1a; 如果大家不了解AppBuilder的话&#xff0c;可以先到这里了解一下&#xff1a; https://cloud.baidu.com/doc/AppBuilder/s/6lq7s8lli 一句话简介&#xff1a; 千帆AppBuilder&#xff08;以下简称AppBuilder&#xff09;是基于大模型搭建AI原生应…

MySQL进阶篇2

三、SQL优化 3.1 插入数据 批量插入推荐每次在 500 ~ 1000 条数据时进行使用。如果大于1000&#xff0c;可以考虑分批次进行插入。 大批量插入数据(100万、1000万) 主键顺序插入的性能比乱序要高 3.2 主键优化 数据组织方式 页分裂 主键顺序插入 主键乱序插入 页合并 (del…