当前位置: 首页>C++>正文

手機VR播放器,Android VR Player(全景視頻播放器) [10]: VR全景視頻渲染播放的實現(exoplayer,glsurfaceview,o

手機VR播放器,Android VR Player(全景視頻播放器) [10]: VR全景視頻渲染播放的實現(exoplayer,glsurfaceview,o

前言

此博客的大部分內容來自我的畢業設計論文,因此語言上會偏正式一點,如果您有任何問題或建議,歡迎留言。在此感謝實驗室的聶師兄,全景視頻render部分的代碼設計主要參考了他所編寫的代碼來完成,他對視頻渲染過程的講解也讓我對此部分有了更好的理解!

為了能播放MEPG-DASH標準的視頻,我使用了ExoPlayer作為播放器,而非之前的MediaPlayer
,如有需要,請參考,ExoPlayer播放器MPEG-DASH視頻播放。

GLSurfaceView的使用介紹

針對VR視頻的播放需求,由于需要使用OpenGL ES 來完成視頻渲染工作,所以使用了GLSurfaceView。它是SurfaceView的實現,并使用特定的surface來展示OpenGL的渲染內容。它具有如下特點:

  1. 管理一個surface(一塊可以組合進Android view系統的特殊內存);
  2. 管理EGL顯示(即允許使用OpenGL進行渲染);
  3. 接受由用戶提供的渲染器完成渲染工作;開啟特定線程完成渲染;
  4. 支持按要求(on-demand rendering.)渲染和連續渲染(continuous rendering)兩種模式。

這些特點都說明,GLSurfaceView很適合用來進行渲染工作。
GLSurfaceView的使用包含下面這幾部分工作:

首先是初始化:即使用setRenderer(Renderer)來設置一個渲染器。具體的步驟為:

  1. 定制android.view.Surface:GLSurfaceView默認創建一個像素格式為PixelFormat.RGB_888的surface,根據具體的需求,可以選擇需要的像素格式,如透明格式,則需要調用getHolder().setFormat(PixelFormat.TRANSLUCENT)來設置。
  2. 選擇EGL配置:一個Android設備可能支持多種EGL配置,如通道(channels)數以及每個通道顏色位數不同則EGL配置也不同,所以,在渲染器之前必須指定EGL的配置。默認使用RGB通道和16位深度,在初始化GLSurfaceView時可以通過調用setEGLConfigChooser(EGLConfigChooser)方法來改變EGL配置。
  3. 調試選項(可選):可以通過調用setDebugFlags(int),和setGLWrapper(GLSurfaceView.GLWrapper)方法來指定GLSurfaceView的調試行為。
  4. 設置渲染器:初始化的最后一步是設置渲染器,通過調用setRenderer(GLSurfaceView.Renderer)來注冊一個GLSurfaceView渲染器。真正的OpenGL渲染工作將由渲染器負責完成。
  5. 渲染模式:如在GLSurfaceView的特點中所介紹的,它支持按要求(on-demand rendering.)渲染和連續渲染(continuous rendering)兩種模式,設定好渲染器,再使用setRenderMode(int)指定需要使用的渲染模式。

手機VR播放器,另外兩個部分是Activity生命周期和事件處理。

Activity生命周期:GLSurfaceView會在Activity窗口暫停(pause)或恢復(resume)時會收到通知,并調用它的的onPause方法和 onResume方法。這是因為GLSurfaceView是一個重量級的控件,恢復和暫停渲染進程是為了使它能及時釋放或重建OpenGL ES的資源。

事件處理:為了處理事件,和其他View的事件處理類似,即繼承GLSurfaceView類并重載它的事件方法。事件處理過程中可能涉及到和渲染對象所在的渲染線程的通信,使用queueEvent(Runnable)可以簡化這部分工作,當然也可以使用其他標準的進程通信機制中的方法。

VR視頻的渲染

VR視頻的渲染工作總結起來,主要是兩大部分,一是球體的繪制,二是進行球體的紋理貼圖工作。具體的實現細節較為復雜,如在完成貼圖后,為了能讓觀眾自由地切換視角,還需要使用投影和相機視圖,并進行窗口裁剪等工作。VR全景視頻完整的渲染播放流程如圖1所示:
圖1 VR視頻渲染播放的流程

創建 GLSurfaceView 對象

在GLSurfaceView的使用介紹中提到真正的OpenGL渲染工作由渲染器來完成,而GLSurfaceView本身所做的工作并不多。根據Android官網的開發者指導,可以直接使用GLSurfaceView,但為了進行事件處理,本應用必須創建一個自己的MyGLSurfaceView,它繼承自GLSurfaceView。

創建GLSurfaceView.Renderer類

GLSurfaceView.Renderer負責向GLSurfaceView的渲染工作,而渲染工作主要有下面這三個方法來完成:onSurfaceCreated():這個方法在GLSurfaceView被創建時,會調用一次,通常在這里進行一些初始化工作,如形狀初始化,著色器的編譯等;onDrawFrame():這個方法在每次繪制圖像時被調用,繪制主要在這里完成; onSurfaceChanged():當幾何圖形發送改變時,會調用這個方法,如屏幕大小發生變化時。

什么播放器支持全景聲,本應用創建一個SphereVideoRenderer,它是GLSurfaceView.Renderer的實現。按照OpenGL ES 2.0可編程管線,繪制一個圖形并最終展示在view(送入FrameBuffer中)的過程為:準備頂點(作為輸入頂點著色器的頂點數據),頂點著色器處理,圖元裝配Primitive Assembly,光柵化(rasterization),Per-Fragment Operations(逐片段操作)。下面將對VR視頻渲染的詳細步驟進行說明。

數據的準備

第一步工作為準備繪制球形的頂點信息,即獲取繪制一個球體所需的全部頂點的直角坐標。創建一個函數initSphereCoords(),它用來完成球體頂點坐標的計算,同時繪制球體的緩沖的準備也在此函數中進行。
圖2 球坐標和直角坐標系
根據球坐標和直角坐標之間的關系(如圖2所示),得到球坐標到直角坐標的公式

x=r?sinθcos?x = r * sin\theta cos\phix=r?sinθcos?
y=r?sinθsin?y = r * sin\theta sin\phiy=r?sinθsin?
z=r?cosθz = r * cos\thetaz=r?cosθ

由于在繪制時需要保證同一方向上的三角形被連續繪制,所以在OpenGL繪制方法中GLES20.glDrawArrays中指定繪制類型為GLES20.GL_TRIANGLE_STRIP,采用這種方法時,頂點緩沖中的頂點將按照V0V1V2,V1V2V3,V2V3V4…這樣的方式連接成一個個三角形。
圖3 球上頂點坐標計算示意
使用如下偽代碼所示的方法在initSphereCoords()中計算球體頂點的坐標(計算過程示意如圖3所示):

for(theta = 0;theta <= PAI;theta += thetaStep)for(phi = 0;phi <= 2*PAI;phi += phiStep)spherPoint[pointer++].x = r * sin(theta) * cos(phi);spherPoint[pointer++].y = r * sin(theta) * sin(phi);spherPoint[pointer++].z = r * cos(theta);spherPoint[pointer++].x = r * sin(theta+thetaStep) * cos(phi);spherPoint[pointer++].y = r * sin(theta+thetaStep) * sin(phi);spherPoint[pointer++].z = r * cos(theta+thetaStep);

其中r為繪制的球體的半徑,設置為5,theta,phi為球體表面一個點的球坐標中的的theta,phi值。thetaStep和phiStep為根據實際需求調整選擇的變化步長。在一個內循環中,計算的兩個點的坐標,如圖11所示的A,B,通過不斷改變theta,phi的值,“遍歷”整個球體表面,即可得到所要繪制的球體的頂點信息。然后為這些頂點開辟相應的緩沖區域(在OpenGL中稱為準備頂點緩沖對象VBO(Vertex Buffer Object)和頂點數組對象VAO(Vertex Array Object))。

在計算繪制球所需要的頂點坐標信息的同時,需要計算紋理的坐標,使用如下偽代碼所示的方法來計算紋理的坐標:

for(theta = 0;theta <= PAI;theta += thetaStep)for(phi = 0;phi <= 2*PAI;phi += phiStep)texturePoint[pointer++].x = phi / (2*PAI);texturePoint[pointer++].y = 1 - theta / PAI;texturePoint[pointer++].x = phi / (2*PAI);texturePoint[pointer++].y = 1 - (theta+thetaSetp) / PAI; 

360播放器?由于紋理的(0,0)坐標在左下角,所以使用theta / PAI來表示紋理的y坐標就不正確,因為隨著theta的遞增,y在減小,所以,用1 - theta / PAI表示,而x坐標則可以使用phi / (2*PAI)來表示。
圖4 貼圖坐標計算示意

接著是準備頂點著色器和片元著色器。可編程管線給開發者提供了更多的自由,但同時開發者必須自己完成原本在固定管線中由系統自動完成的許多計算工作。具體來說,開發者必須提供如下的圖形渲染管線細節:頂點著色器(vertex Shader):它用來繪制圖形的形狀;。頂點著色器采用著色器語言GLSL來編寫(片元著色器相同),它是一種和C語言語法很類似的語言,一個基本的著色器程序包括變量聲明,以及一個main函數。在頂點著色器中,完成的工作為指定球體頂點和紋理頂點。片元著色器(Fragment Shader) :它用來繪制圖形的顏色或者是紋理。本應用片元著色器完成的工作為指定繪制球體的紋理,在著色器的main()函數中,使用“gl_FragColor = texture2D(sTexture, v_TexCoordinate);”來實現,sTexture為由外部傳入的統一變量(使用uniform修飾符),v_TexCoordinate為紋理坐標,texture2D為著色器內建函數,這句話的作用為指定一個片元的顏色為v_TexCoordinate位置的sTexture紋理。

除了著色器外,還需要一個program,它是一個OpenGL ES的對象,其中包含了用來繪制一個或者多個形狀的著色器。在定義好頂點著色器和片元著色器后,需要將其編譯然后添加到program中,然后才能使用著色器。編譯和添加的著色器的任務可以通過創建一個工具類方法來實現。分為如下幾個步驟:創建program對象:GLES20.glCreateProgram();加載著色器 :SphereVideoRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode),片元著色器采用同樣的方法載入,不過將參數換成片元著色器的;添加著色器:GLES20.glAttachShader(mProgram, vertexShader),片元著色器采用同樣的方法添加,不過將參數換成片元著色器的;鏈接到program:GLES20.glLinkProgram(mProgram),使mProgram成為可執行的program對象;使用program:GLES20.glUseProgram(mProgram),將mProgram添加到OpenGL環境中;

在onSurfaceCreated()中使用GLES20.glGetAttribLocation(mProgram, “attrib_name”);和GLES20.glGetUniformLocation(mProgram, “uniform_name”)兩個方法可以分別獲取著色器中attribute和uniform類型的變量的句柄(handle)。以紋理ID為參數創建一個SurfaceTexture,并使用ExoPlayer.setSurface(surface)把這個surface作為參數傳遞給mExoPlayer(在MySurfaceView中創建ExoPlayer的實例,它調用ExoPlayer.setDataSource(videourl)來設置視頻數據來源)。

至此,數據的準備工作和OpenGL ES環境初始化基本完成,下一步為在onDrawFrame()中進行繪制工作。

開始繪制

使用OpengGL ES來繪制圖形需要調用較多的函數,而且會用到很多相關參數,一個常用的做法是創建一個繪制方法,本應用中這個繪制方法為drawSphere(),然后在onDrawFrame()中去調用該方法。準備繪制方法drawSphere():在drawSphere(),并不是簡單地直接使用 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);來繪制球體,為了能使得用戶觀看到的渲染效果接近真實世界的觀察效果,需要進行一些矩陣的計算。
計算機圖形學中的觀察是建立在虛擬相機的基礎上(《計算機圖形學》 圖5,6,7引用自此書),圖5則是對虛擬相機模型的抽象,其中投影線相交于投影中心(COP:Center of Projection),而COP就對應于人眼或者是相機鏡頭。

360度全景視頻播放器?圖5 虛擬相機模型

為了使得觀察過程更加靈活,通常的做法是把對虛擬相機的控制分解成設置相機的位置和方向和應用投影矩陣兩個基本的操作。其中,設置照相機的位置和方向由模-視變換來完成,頂點經過該變換之后會位于相機坐標系中。然后再將指定的投影矩陣應用于頂點,進行投影變換。并將視見體內部對象變換到指定的裁剪立方體的內部。變換的流程如圖6所示:

圖6 虛擬相機模型中的坐標變換

在實際開發中,矩陣計算往往直接使用android.opengl.Matrix提供的方法即可,而不用開發者自己完成復雜的矩陣計算工作。
使用Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ)方法可以設置相機在空間中的位置,其中eye參數為相機坐標,look參數為觀察的目標的坐標,up參數為相機正上方向量。 在本應用中,各個參數設置為Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 0, 0, 0, -1, 0, 1, 0)。通常情況,將相機的初始方向指向z軸負方向,這是因為這樣才能看見位于相機前方的觀察對象;僅指定相機的空間位置還不能將相機唯一確定下來下來,因為此時相機還可以進行旋轉,通過指定相機正上方向量來將相機確定下來。

下一步是投影矩陣的設置。它的作用是設置一個視見體(如圖7所示),用以裁剪形狀(即在視見體內部的形狀才能被投影到投影平面上,其余部分則被裁剪掉)。棱臺視見體是定義視見體的常用方法,它由左右裁剪平面(left和right),上下裁剪平面(top和bottom),遠近裁剪平面(near和far)來決定。

圖7 棱臺視見體

全景視頻播放器、使用Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far)方法來設置投影矩陣ProjectionMatrix。最后兩個參數near,far,根據實際的渲染測試效果,可固定為1.2和5.0,而前面的 left, right, bottom, top幾個參數則可能需要在onSurfaceChanged中進行修改,以確保屏幕改變時,GLSurfaceView的顯示效果仍然是開發者所需要的。

然后需要一個供用戶轉換視角的旋轉矩陣,同樣使用android.opengl.Matrix提供的方法來完成變換矩陣的計算。 如Matrix.setRotateM(mRotationMatrix, 0, angle, 0.0f, -1.0f, 0),為了能通過屏幕觸摸,陀螺儀變化等外部事件來改變視角(即進行旋轉),可以暴露一個設置旋轉角度的方法出來,供其他類調用。

最后通過之前拿到的著色器變量的句柄,將矩陣變換應用于頂點,并調用 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount)方法進行繪制。在onDrawFrame()方法中調用drawSphere()進行繪制:開始繪制之前,調用GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT |GLES20.GL_DEPTH_BUFFER_BIT)方法來清除屏幕。為了用戶能使用VR眼鏡等工具獲得立體觀影效果,需要作分屏(設置視口)處理,即將屏幕分成左右兩個小屏幕,在兩個小屏幕中分別進行繪制。使用 GLES20.glViewport(GLint x,GLint y,GLsizei width,GLsizei height)方法來進行分屏,然后調用drawSphere()進行繪制。

事件處理

在GLSurfaceView的使用介紹的事件處理部分中提到,使用queueEvent(Runnable)可以簡化事件處理所在線程與渲染線程之間的通信工作[10]。為了保證應用的正常運行,必須在GLSurfaceView對的窗口暫停(pause)或恢復(resume)這兩個事件進行處理。其中onResume()在窗口恢復時調用。而onPause()在窗口暫停時調用:在此回調方法中調用父類的onPause()方法和ExoPlayer.release()釋放相關資源。

視頻播放控制

為了順利進行視頻的播放和控制,并且保證應用程序的健壯性,需要對ExoPlayer的線程模型(threading model)有較好的了解,確保在合適的時間點調用相應的方法,并在完成播放后及時釋放相關資源。下面主要對視頻播放/暫停,進度條拖動播放以及根據陀螺儀改變視角進行說明。

視頻播放/暫停,進度條拖動播放

安卓提供了一個視頻的播放控制MediaController組件,但是它不太適合本系統的要求,所以需要重新定義一個播放控制類MyMediaController,它繼承自FrameLayout,使用時作為一個自定義組件來使用,主要完成視頻播放/暫停,進度條拖動播放的控制。

播放暫停功能比較簡單,在MediaController的布局中添加一個播放暫停的按鈕,然后在播放Activity添加按鈕的點擊事件,通過調用ExoPlayer.setPlayWhenReady(true)和ExoPlayer.setPlayWhenReady(false)來實現播放和暫停功能。

進度條拖動播放的控制是通過對SeekBar的拖動事件的監聽結合ExoPlayer.seekTo()方法來實現。在SeekBar的OnSeekBarChangeListener的onProgressChanged回調中,可以獲取到SeekBar進度的改變;當onStopTrackingTouch(SeekBar bar)方法被調用時,說明拖動停止,此時視頻應該跳轉到指定的進度。ExoPlayer.seekTo()接受的參數為毫秒為單位的視頻時間點,因此調用Progress.setMax(1000)方法,將進度條最大值設置為1000,視頻應該跳轉到的進度則可以表示為(Duration * bar.getProgress()) / 1000。

MyMediaController的顯示和隱藏。在playerLayout(視頻播放Activity的布局)中調用addView即可將自定義組件MyMediaController的view添加到視頻播放view上,而要實現MyMediaController的自動隱藏(即在幾秒鐘無操作的情況下,播放控制組件會自動消失)和觸摸呼出,需要使用到Handler和控件的setVisibility()方法。

創建一個內部類Handler來處理與MyMediaController之間的異步線程消息。MyMediaController顯示時,調用Handler.sendMessageDelayed來發送一條延遲消息(這里可以指定延遲時間,即MyMediaController的顯示時間),重寫Handler的handleMessage方法來處理消息,在一定延遲后,Handler收到了消息,如果當前狀態不是拖動進度條并且MyMediaController是顯示著的話,就發送隱藏MyMediaController的消息給MyMediaController,它將調用setVisibility(View.GONE)方法來進行隱藏;而觸摸呼出MyMediaController的方法則是通過對屏幕觸摸事件的監聽來實現的,當檢測到觸摸事件時,調用setVisibility(View.VISIBLE)即可顯示MyMediaController。

根據陀螺儀改變視角

本部分的工作為獲取陀螺儀的數據,用來調整播放視角。在 開始繪制 中的矩陣變換部分提到 SphereVideoRender提供了設置旋轉角度的方法出來,供其他類調用。所以,只需要獲取陀螺儀的數據并將其轉換為相應的旋轉角度,傳入SphereVideoRender提供的方法即可實現視角變換的功能。

PlayerActivity中注冊傳感器事件監聽器SensorEventListener,由于本應用只需要陀螺儀的數據,所以,使用一個條件判斷對事件進行過濾:
if(event.sensor.getType() == Sensor.TYPE_GYROSCOPE)

對于陀螺儀,從事件監聽返回的結果是x、y、z三個軸方向上的角速度(弧度/秒),可以分別從values[0]、values[1]、values[2]獲取到。取得上述的角速度的數據,計算兩次改變之間的時間差,由角速度乘以時間的公式即可計算出各個軸上改變的角度。然后調用Math.toDegrees()方法將弧度至轉換成角度制。

https://www.nshth.com/cplus/338442.html
>

相关文章:

  • 手機VR播放器
  • 什么播放器支持全景聲
  • 360播放器
  • 360度全景視頻播放器
  • 全景視頻播放器
  • vr視頻播放器哪個最好
  • 樹莓派的控制方法,第二篇 樹莓派基本外設基礎篇
  • 手機如何連接外設,iOS連接外設的幾種方式
  • switch可以外接鍵鼠嗎,別再給手機外接OTG鍵鼠玩刺激戰場了:其實還能這樣操作
  • [阿發你好]C/C++學習指南
  • 輸入法哪個最好用,wsl2中安裝中文輸入法
  • 字符串中引入變量方法,字符串處理、變量初始值處理、擴展的腳本技巧、正則表達式
  • 某計算機內存容量是512kb,某計算機主存容量為512kb,Cache容量為16kb,每塊有16個字,每字32位。 (1...
  • 中國工商網商標查詢,工商局爬蟲 商標網爬蟲
  • iOS真機調試TestFlight安裝及提交App Store審核教程
  • 蘋果app上架流程,小白如何在ios中安裝ios上架
  • 蘋果彈出提交表格是什么,蘋果TestFlight測試操作圖文教程(測試后提交App Store審核)
  • 四門外語傍身:外語,讓我的大學如此完美
  • D3D Surface/Texture SDL DDraw渲染視頻的區別和疑問
  • 手機VR播放器,Android VR Player(全景視頻播放器) [10]: VR全景視頻渲染播放的實現(exoplayer,glsurfaceview,o
  • Qt渲染視頻常見問題(視頻渲染窗口上子窗口設置透明出現陰影問題、主窗口縮放導致視頻渲染窗口部分出現視頻閃爍問題)
  • 視頻解析網站源碼,ijkplayer源碼分析 視頻渲染流程
  • 一分鐘的視頻渲染要多久,基礎教程|如何在數分鐘時間內渲染超清精美視頻?
  • Metal(六) 案例之視頻文件的渲染
  • flutter開發小程序,最強整理!寫給程序員的Flutter詳細教程,大廠直通車!
  • c++黑客編程揭秘與防范,C/C++截獲騰訊QQ網絡聊天系統內容和登錄密碼,教你做一個黑客!
  • 支付行業具體做什么,做支付需要了解哪些行業知識
  • 5大底層邏輯,淺談HyperLogLog底層算法邏輯
  • c++實現復數的加減乘除,【C++】輔助C++計算復數(代碼解釋的很清楚)
  • nlogn的算法有哪些,算法運行時間1、logN、N、NlogN 、N^2、N^3、2^n之間的比較
  • 開源圖片庫,幾種常用圖像處理開源庫簡介及使用總結
  • 圖像處理和計算機視覺,《圖像處理與計算機視覺算法及應用》讀后感
  • gps定位,側邊欄固定定位到版心兩側
  • css版心怎么設置,[css]版心和布局流程
  • 瀏覽器多個窗口怎么設置在一個頁面,網頁多種版心適應多屏幕技巧
  • 前端學習之版心和布局流程