记录一款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視頻播放,使用文檔很詳細,但是此插件沒有源碼,不適合做以后的個性化開發。
鼎鼎大名的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 VR视频播放器插件的开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [转] libcurl异步方式使用总结(
- 下一篇: 从0到1实现Web端H.265播放器:视