记录一个Unity播放器插件的开发
效果图##
先上一个效果图:

背景
公司最近在做VR直播平台,VR开发我们用到了Unity,而在Unity中播放视频就需要一款视频插件,我们调研了几个视频插件,记录两个,如下:
Unity视频插件调研
网上搜了搜,最流行的有以下两款Unity插件:
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视频播放,使用文档很详细,但是此插件没有源码,不适合做以后的个性化开发。
- 鼎鼎大名的EasyMovieTexture.售价65$,支持功能如下:
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上。
记录一个Unity播放器插件的开发的更多相关文章
- 记录一款Unity VR视频播放器插件的开发
效果图 先上一个效果图: 背景 公司最近在做VR直播平台,VR开发我们用到了Unity,而在Unity中播放视频就需要一款视频插件,我们调研了几个视频插件,记录两个,如下: Unity视频插件调研 网 ...
- 搭建rtmp直播流服务之4:videojs和ckPlayer开源播放器二次开发(播放rtmp、hls直播流及普通视频)
前面几章讲解了使用 nginx-rtmp搭建直播流媒体服务器; ffmpeg推流到nginx-rtmp服务器; java通过命令行调用ffmpeg实现推流服务; 从数据源获取,到使用ffmpeg推流, ...
- 一个MP3播放的插件jPlayer
Jplayer小样 最近应公司要求需要一个MP3播放的插件,网上找了很多插件,看来看去还是jPlayer用着最舒服也最容易扩展.所以就找了个资料研究了下,简单做了个小DEMO.支持实时控制列表,常 ...
- Android VLC播放器二次开发2——CPU类型检查+界面初始化
上一篇讲了VLC整个程序的模块划分和界面主要使用的技术,今天分析一下VLC程序初始化过程,主要是初始化界面.加载解码库的操作.今天主要分析一下org.videolan.vlc.gui.MainActi ...
- Wavesurfer.js音频播放器插件的使用教程
Wavesurfer.js是一款基于HTML5 canvas和Web Audio的音频播放器插件,本文主要记录它及其视觉效果插件Regions插件的使用方法. 1.创建实例 引入插件 import W ...
- 10个免费开源的JS音乐播放器插件
点这里 音乐播放器在网页设计中有时候会用到,比如一些时尚类.音乐或影视类等项目,但这些 网页播放器 插件比较少见,所以这里为大家整理一个集合,也许会有用到的时候. 下面整理的播放器有些是支持自适应的, ...
- 简约的HTML5音乐播放器插件
从我刚开始接触前端的时候就想写一个能播放音乐的小程序,刚开始写的时候虽然可以放,但是确实很慢,很卡,有很多可以优化的地方.最近在前一个版本的基础上重写了一个音乐播放器的插件,速度还可以吧 因为追求简约 ...
- 师兄写的一个JAVA播放器的源代码(转)
师兄写的一个JAVA播放器的源代码 MediaPlayer.java------------------------------------------------------------------ ...
- 小型音乐播放器插件APlayer.js的简单使用例子
本篇博客将会给出一个小型音乐播放器插件APlayer.js的使用例子.关于APlayer.js的具体介绍和Github地址,可以参考: https://github.com/MoePlayer/A ...
随机推荐
- Backtrack下的dns爆破工具的目录
直接可以切换到 /pentest/enumeration/dns#
- 201521123111《Java程序设计》第10周学习总结
1. 本章学习总结 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容 线程不是程序 线程必须在程序对应的进程中运行 一个进程中可以同时执行多个线程,这些线程可以完成不同的功能. 2. 书面 ...
- 201521123080《Java程序设计》第11周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 多线程: 操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己 ...
- 201521123045 《JAVA程序设计》 第14周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...
- 201521123016 《Java学习笔记》 第11周学习总结
1. 本周学习总结 2. 书面作业 本次PTA作业题集多线程 1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) 1.1 除了使用synchronized修饰方法实现互斥同步访问, ...
- oracle 建表空间->创建用户并把表空间分配给用户->给用户授权->导库
首先注意:我参考网上使用的sysdba模式(normal)登陆的,其他的模式建不了用户(个人没有进行其他模式的表空间尝试,如有人尝试欢迎补充,感激不尽) 表空间相当于表的容器(一下所有的操作都适用于o ...
- java实现oracle数据库基本操作
import java.sql.*; import java.util.ArrayList; import java.util.List; //使用jdbc连接 public class TestOr ...
- js对象详解(JavaScript对象深度剖析,深度理解js对象)
js对象详解(JavaScript对象深度剖析,深度理解js对象) 这算是酝酿很久的一篇文章了. JavaScript作为一个基于对象(没有类的概念)的语言,从入门到精通到放弃一直会被对象这个问题围绕 ...
- 用vue开发一个app(1,基础环境配置)
在今天之前,我没有很系统的看过,学习过vue.也是第一次尝试用vue写个小应用 现在开始研究配环境 这边参考的是https://cn.vuejs.org/v2/guide/installation.h ...
- Local Binary Convolutional Neural Networks ---卷积深度网络移植到嵌入式设备上?
前言:今天他给大家带来一篇发表在CVPR 2017上的文章. 原文:LBCNN 原文代码:https://github.com/juefeix/lbcnn.torch 本文主要内容:把局部二值与卷积神 ...