先上一個效果圖:
java
公司最近在作VR直播平臺,VR開發咱們用到了Unity,而在Unity中播放視頻就須要一款視頻插件,咱們調研了幾個視頻插件,記錄兩個,以下:android
網上搜了搜,最流行的有如下兩款Unity插件:git
Powerful cross-platform video playback solution for Unity.github
Native video playback on Android, iOS, macOS and tvOS (Apple TV), WebGL, Windows, Windows Phone and UWP.less
Features include:ide
此插件支持HLS視頻播放,使用文檔很詳細,可是此插件沒有源碼,不適合作之後的個性化開發。函數
Supported resolutions:flex
這個插件貌似是我的開發的,沒有說明文檔,有部分java源碼,native code並無給出。咱們須要有源碼的插件方便之後的個性化開發。ui
綜合以上調研結果,咱們決定本身動手實現一個簡單能知足咱們要求的Unity播放器插件,有兩個難點要突破:spa
從下面這個帖子中,找到了一些能夠參考的資料。
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端初始化一個Texture2D紋理ID用於顯示視頻幀。
m_VideoTexture = new Texture2D (Call_GetVideoWidth (), Call_GetVideoHeight (), TextureFormat.RGB565, false);
這裏使用了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 ); }
建立一個播放器,注意這裏咱們使用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上。