记录一款Unity VR视频播放器插件的开发

小编 2026-06-24 阅读:161 评论:0
效果图先上一个效果图:背景公司最近在做VR直播平台,VR开发我们用到了Unity,而在Unity...

效果图

先上一个效果图:
记录一款Unity VR视频播放器插件的开发

背景

公司最近在做VR直播平台,VR开发我们用到了Unity,而在Unity中播放视频就需要一款视频插件,我们调研了几个视频插件,记录两个,如下:

Unity视频插件调研

网上搜了搜,最流行的有以下两款Unity插件:

  • AVPro 这个在Unity商店售价150$,最新release版本为1.6.15,功能包括:

Powerful cross-platform video playback solution for Unity.

Native video playback on Android, iOS, macOS and tvOS (Apple TV), WebGL, Windows, Windows Phone and UWP.

Features include:

  • New Unity 2017 supported
  • New New iOS video playback path that uses less memory
  • One API for video playback on all supported platforms
  • Unity 4.6 - 5.x supported
  • 8K video (on supported hardware)
  • VR Support (mono, stereo, equirectangular and cubemap)
  • Transparency support (native and packed)
  • Subtitles support (external SRT)
  • Fast flexible video playback
  • In-editor playback support for Windows and macOS
  • Free watermarked trial version available
  • Components for IMGUI, uGUI and NGUI
  • Over 64 PlayMaker actions included
  • Easy to use drag and drop components
  • Linear and Gamma colour spaces supported
  • Fast native Direct3D, OpenGL and Metal texture updates
  • Desktop support for Hap, Hap Alpha, Hap Q and Hap Q Alpha
  • Streaming video from URL (when supported by platform)

此插件支持HLS视频播放,使用文档很详细,但是此插件没有源码,不适合做以后的个性化开发。

Supported resolutions:

  • Android: General devices support up to 1920 * 1080.
    The latest device supports up to 4k.
  • iOS: General devices support up to 1920 * 1080.
    The latest device is support up to 2560 * 1440.
    iPhone 6s Plus supports up to 4k.
  • It also supports StreamingAssets, external storage, and streaming services.
  • Android streaming support list: http, HLS (http live streaming),rtsp
  • iOS streaming support list: http,HLS (http live streaming)
  • EasyMovieTexture requires Android 4.0 or above.
  • EasyMovieTexture requires iOS 6.0 or Above.
  • Unity 4.X requires an iOS Pro.
  • In Unity 5.X it does not require a Pro.
  • Supports multithreaded rendering options. (Only supports Unity 5.X.)

这个插件貌似是个人开发的,没有说明文档,有部分java源码,native code并没有给出。我们需要有源码的插件方便以后的个性化开发。

自己动手,风衣足食

综合以上调研结果,我们决定自己动手实现一个简单能满足我们要求的Unity播放器插件,有两个难点要突破:

  • 一个是找一个合适的开源播放器。
  • 另一个就是如何把播放视频画面映射到Unity中的物体表面,这个是最关键的。

寻找素材

从下面这个帖子中,找到了一些可以参考的资料。

unity 3d 中如何实现以物体的表面作为播放视频的位置,比如在墙面播放视频?

寻找开源播放器

本来打算使用VLC播放器的,但是同事发现有一个商用的开源播放器,并且使用的人数也不少,B站的ijkplayer。正好在上面的帖子中回复人也提到了这个播放器,我们决定使用这个播放器。

如何做视频画面映射

没有一点Unity开发经验,只能从头一点点学起,知乎的帖子里面,有个人回复可以参考OVR里面的例子。阅读了里面的代码,同时也参考了easyMovieTexture中的源码(easyMovie中只有java代码,关键的native code并没有给)。看的有些似懂非懂,尝试了之后,居然成功了。

最关键的一点我描述成下面的话:

将Ijkplayer的AndroidSurfaceTexture纹理ID和Unity中Texture2D的纹理ID分别同时绑定到不同的目标上。AndroidSurfaceTexture绑定到GL_TEXTURE_EXTERNAL_OES,Unity的纹理ID绑定到GL_TEXTURE_2D

从头到尾梳理一遍流程

初始化

  • Unity

Unity端初始化一个Texture2D纹理ID用于显示视频帧。

m_VideoTexture = new Texture2D (Call_GetVideoWidth (), Call_GetVideoHeight (), TextureFormat.RGB565, false);
  • OVR

这里使用了OVR里面的native code,OVR中初始化AndroidSurfaceTexture和相关的函数:

static const char * className = "android/graphics/SurfaceTexture";    const jclass surfaceTextureClass = jni->FindClass(className);    if ( surfaceTextureClass == 0 ) {        FAIL( "FindClass( %s ) failed", className );    }    // find the constructor that takes an int    const jmethodID constructor = jni->GetMethodID( surfaceTextureClass, "<init>", "(I)V" );    if ( constructor == 0 ) {        FAIL( "GetMethodID( <init> ) failed" );    }    jobject obj = jni->NewObject( surfaceTextureClass, constructor, textureId );    if ( obj == 0 ) {        FAIL( "NewObject() failed" );    }    javaObject = jni->NewGlobalRef( obj );    if ( javaObject == 0 ) {        FAIL( "NewGlobalRef() failed" );    }    // Now that we have a globalRef, we can free the localRef    jni->DeleteLocalRef( obj );    updateTexImageMethodId = jni->GetMethodID( surfaceTextureClass, "updateTexImage", "()V" );    if ( !updateTexImageMethodId ) {        FAIL( "couldn't get updateTexImageMethodId" );    }    getTimestampMethodId = jni->GetMethodID( surfaceTextureClass, "getTimestamp", "()J" );    if ( !getTimestampMethodId ) {        FAIL( "couldn't get getTimestampMethodId" );    }    setDefaultBufferSizeMethodId = jni->GetMethodID( surfaceTextureClass, "setDefaultBufferSize", "(II)V" );    if ( !setDefaultBufferSizeMethodId ) {        FAIL( "couldn't get setDefaultBufferSize" );    }    // jclass objects are localRefs that need to be freed    jni->DeleteLocalRef( surfaceTextureClass );

初始化纹理ID,并将其绑定到目标GL_TEXTURE_2D上:

glGenTextures( 1, &textureId );    glBindTexture( GL_TEXTURE_EXTERNAL_OES, textureId );    glTexParameterf( GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR );    glTexParameterf( GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR );    glTexParameterf( GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );    glTexParameterf( GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );    glBindTexture( GL_TEXTURE_EXTERNAL_OES, 0 );

将Unity的纹理ID传递到OVR中,用于绑定到目标GL_TEXTURE_EXTERNAL_OES上:

jobject OVR_Media_Surface( void * texPtr, int const width, int const height ){    GLuint texId = (GLuint)(size_t)(texPtr);    LOG( "OVR_Media_Surface(%i, %i, %i)", texId, width, height );    return _msp.VideoSurface.Bind( texId, width, height );}
  • Ijkplayer

创建一个播放器,注意这里我们使用OVR中已经实例化的AndroidMovieTexture来初始化播放器。

 m_IjkMediaPlayer.setSurface(m_Surface);

刷新

刷新操作由Unity中的Update函数触发,最终在OVR中执行,首先调用AndroidMovieTexture中的Update函数,接下来就是绑定纹理操作,Ijkplayer的纹理ID每刷新一次绑定一次。而Unity的纹理ID只有在视频图像长度或者宽度发生变化才会绑定。

void MediaSurface::Update(){    if ( !AndroidSurfaceTexture )    {        LOG( "!AndroidSurfaceTexture" );        return;    }    if ( TexId <= 0 )    {        //LOG( "TexId <= 0" );        return;    }    AndroidSurfaceTexture->Update();    if ( AndroidSurfaceTexture->GetNanoTimeStamp() == LastSurfaceTexNanoTimeStamp )    {        //LOG( "No new surface!" );        return;    }    LastSurfaceTexNanoTimeStamp = AndroidSurfaceTexture->GetNanoTimeStamp()   // If the SurfaceTexture has changed dimensions, we need to    // reallocate the texture and FBO.    glActiveTexture( GL_TEXTURE0 );    glBindTexture( GL_TEXTURE_EXTERNAL_OES, AndroidSurfaceTexture->GetTextureId() );    if ( TexIdWidth != BoundWidth || TexIdHeight != BoundHeight )    {        LOG( "New surface size: %ix%i", BoundWidth, BoundHeight );        TexIdWidth = BoundWidth;        TexIdHeight = BoundHeight;        if ( Fbo )        {            glDeleteFramebuffers( 1, &Fbo );        }        glActiveTexture( GL_TEXTURE1 );        glBindTexture( GL_TEXTURE_2D, TexId );        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA,                TexIdWidth, TexIdHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );        glBindTexture( GL_TEXTURE_2D, 0 );        glActiveTexture( GL_TEXTURE0 );        glGenFramebuffers( 1, &Fbo );        glBindFramebuffer( GL_FRAMEBUFFER, Fbo );        glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,                TexId, 0 );        glBindFramebuffer( GL_FRAMEBUFFER, 0 );    }}

最后的结果可能是这个样子的:Ijkplayer负责推动视频不停向前播放,播放器的纹理也会不停刷新,这会带动Unity纹理跟着刷新,最终显示在Unity的Material上。


作者:HarlanC

博客地址:http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表