<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Camera 和 MediaCodec 對視頻旋轉(zhuǎn)角度的處理

          共 7542字,需瀏覽 16分鐘

           ·

          2023-11-01 10:17

          Surface 和 GLConsumer 對視頻旋轉(zhuǎn)角度的處理

          最近遇到一個(gè)有趣的問題:通過MediaCodec解碼帶旋轉(zhuǎn)角度的視頻時(shí),如果Output Surface是TextureView或者SurfaceView提供的,那么屏幕上的視頻幀可以正常展示(處理了旋轉(zhuǎn)角度);如果Surface是由SurfaceTexture創(chuàng)建而來,那么通過SurfaceTexture獲取的OES紋理,是未處理旋轉(zhuǎn)角度的,需要自己兼容下角度問題。

          此外,我也測試了Camera,首先通過Camera.setDisplayOrientation設(shè)置順時(shí)針旋轉(zhuǎn)角度,然后設(shè)置Output Surface接收Camera預(yù)覽視頻幀。不出意外,也得到了同樣的結(jié)論。

          視頻旋轉(zhuǎn)角度的處理

          既然都是通過Surface接收視頻幀,那為什么會存在這種差異那?MediaCodecCamera生產(chǎn)視頻幀,并且展示到屏幕上的流程如下所示:

          1. 首先,MediaCodecCamera會通過native_window_set_buffers_transform函數(shù)向Surface(ANativeWindow)設(shè)置Transform Flag,保存在Surface.mTransform變量。

          2. 接著,生產(chǎn)端MediaCodecCamera通過Surface持有的BufferQueueProducer向BufferQueue入隊(duì)BufferItem時(shí),會把Surface.mTransform賦值給BufferItem.mTransform。

          3. 然后,消費(fèi)端GLConsumer通過BufferQueueComsumer獲取BufferItem后,根據(jù)BufferItem.mTransform,還原出一個(gè)4*4的紋理變換矩陣,保存在GLConsumer.mCurrentTransformMatrix變量中,GLConsumer的使用者可以通過GLConsumer.getTransformMatrix方法獲取這個(gè)紋理變換矩陣。

          4. 最后,GLConsumer的使用者負(fù)責(zé)把紋理變換矩陣作用到紋理上,以正確展示視頻幀。

          GLConsumer負(fù)責(zé)把BufferItem.mGraphicBuffer轉(zhuǎn)換為紋理,并且根據(jù)BufferItem.mTransform計(jì)算出紋理變換矩陣,此時(shí)的紋理是原始狀態(tài)(未應(yīng)用紋理矩陣)。獲取紋理變換矩陣,并且應(yīng)用到紋理,是GLConsumer(紋理)使用方的責(zé)任。

          不管Surface是由TextureView提供還是通過SurfaceTexture創(chuàng)建,上述前3步都是相同的流程。導(dǎo)致上述差異的根本原因是第4步:GLConsumer的使用者是否把紋理變換矩陣應(yīng)用到紋理

          當(dāng)SurfaceTextureView提供時(shí),GLConsumer的使用者是DeferredLayerUpdater,它獲取紋理變換矩陣后,會填充到Layer.texTransform變量(frameworks\base\libs\hwui\Layer.h),后續(xù)在硬件加速的異步渲染線程中,OpenGLRenderer渲染DrawLayerOp(基于上面的Layer創(chuàng)建而來)時(shí),會應(yīng)用該紋理變換矩陣。

          當(dāng)SurfaceSurfaceView提供時(shí),Surface是獨(dú)立窗口,GLConsumer的使用者是frameworks/native/services/surfaceflinger/Layer,它獲取紋理變換矩陣后,會填充到Layer.mTexture.mTextureMatrix中,后續(xù)SurfaceFlinger合成Layer時(shí),會應(yīng)用該紋理變換矩陣。

          可見,當(dāng)Output Surface由TextureView或者SurfaceView提供時(shí),GLConsumer的使用者主動獲取并使用了紋理變換矩陣,所以我們在屏幕上看到的視頻幀才是正常的。

          而當(dāng)Output Surface由我們基于OES紋理ID創(chuàng)建的SurfaceTexture創(chuàng)建而來時(shí),GLConsumer(紋理)的使用者是業(yè)務(wù)方,所以需要業(yè)務(wù)方通過SurfaceTexture.getTransformMatrix主動獲取紋理變換矩陣,并把它應(yīng)用到OES紋理上。即:我們拿到的OES紋理本就是原始紋理,需要應(yīng)用紋理變換矩陣后,才能正常展示。

          業(yè)務(wù)方要怎么使用紋理變換矩陣那?

          1. 首先,通過SurfaceTexture.getTransformMatrix獲取紋理變換矩陣,該矩陣是列優(yōu)先次序存儲,可以直接通過glUniformMatrix4fv函數(shù)上傳到頂點(diǎn)著色器。

          2. 在頂點(diǎn)著色器中,使用紋理變換矩陣左乘紋理坐標(biāo),并把最新的紋理坐標(biāo)傳遞給片元著色器。

          3. 在片元著色器中,正常使用紋理坐標(biāo)從紋理中取色就可以了(視頻幀正確展示)。

          視頻旋轉(zhuǎn)角度的流轉(zhuǎn)流程

          我們看下旋轉(zhuǎn)角度MediaCodecCamera中的流轉(zhuǎn)流程。

          MediaCodec

          通過MediaCodec解碼視頻時(shí),需要通過MediaFormat設(shè)置解碼參數(shù),例如:SPS、PPS等。當(dāng)視頻存在旋轉(zhuǎn)角度時(shí),需要通過MediaFormat.KEY_ROTATION配置旋轉(zhuǎn)角度,表示視頻幀需要順時(shí)針旋轉(zhuǎn)多少度,才能正確展示。但是要注意的是:只有當(dāng)MediaCodec直接解碼到Surface時(shí),旋轉(zhuǎn)角度才有效。

          旋轉(zhuǎn)角度在MediaCodec中的流轉(zhuǎn)流程如下所示:

          1. MediaCodec::configure配置解碼器,MediaFormat作為參數(shù)。

          2. MediaCodec發(fā)送kWhatConfigure消息,在自有線程配置解碼器。

          3. 調(diào)用ACodec-&gt;initiateConfigureComponent(MediaFormat為參數(shù))配置ACodec。

          4. ACodec發(fā)送kWhatConfigureComponent消息在自有線程配置ACodec。

          5. 接著是ACodec::LoadedState::onConfigureComponent方法。

          6. 然后是ACodec.configureCodec方法,負(fù)責(zé)通過MediaFormat配置ACodec,其中從format中取出了"rotation-degrees",保存在ACodec.mRotationDegrees變量中。

          7. 最后,ACodec.setupNativeWindowSizeFormatAndUsage中調(diào)用全局函數(shù)setNativeWindowSizeFormatAndUsage為Surface(ANativeWindow)設(shè)置transform flag。核心代碼在setNativeWindowSizeFormatAndUsage函數(shù)中:

          // 根據(jù)旋轉(zhuǎn)角度獲得transform flag
          int transform = 0;
          if ((rotation % 90) == 0) {
              switch ((rotation / 90) & 3) {
                  case 1:  transform = HAL_TRANSFORM_ROT_90;  break;
                  case 2:  transform = HAL_TRANSFORM_ROT_180; break;
                  case 3:  transform = HAL_TRANSFORM_ROT_270; break;
                  default: transform = 0;                     break;
              }
          }
          // 為Surface(ANativeWindow)設(shè)置transform
          err = native_window_set_buffers_transform(nativeWindow, transform);

          native_window_set_buffers_transform函數(shù)會觸發(fā)ANativeWindow子類Surface的perform方法處理NATIVE_WINDOW_SET_BUFFERS_TRANSFORM消息,接著是dispatchSetBuffersTransform -> setBuffersTransform,最后把transform保存在了Surface.mTransform變量中。

          至此,旋轉(zhuǎn)角度已經(jīng)設(shè)置到了Surface.mTransform,后面就是Surface生產(chǎn)圖像數(shù)據(jù)時(shí)負(fù)責(zé)使用它了。

          Surface內(nèi)部通過BufferQueueProducer入隊(duì)圖像數(shù)據(jù)時(shí),會把Surface.mTransform賦值給BufferItem.mTransform,BufferQueue的元素就是BufferItem。

          然后,消費(fèi)端GLConsumer通過BufferQueueComsumer獲取BufferItem后,根據(jù)BufferItem.mTransform,還原出一個(gè)4*4的紋理變換矩陣,保存在GLConsumer.mCurrentTransformMatrix變量中,GLConsumer的使用者可以調(diào)用GLConsumer.getTransformMatrix方法獲取這個(gè)紋理變換矩陣,然后在使用對應(yīng)紋理時(shí),把該矩陣應(yīng)用到紋理上。

          Camera

          旋轉(zhuǎn)角度在Camera中的流轉(zhuǎn)流程如下所示:

          1. Camera.setDisplayOrientation設(shè)置預(yù)覽顯示時(shí)的順時(shí)針旋轉(zhuǎn)角度。

          2. 對應(yīng)Native方法android_hardware_Camera_setDisplayOrientation。

          3. 接著走到Camera.sendCommand方法,這里的Camera是客戶端,會通過Binder調(diào)用到服務(wù)端CameraClient::sendCommand。

          4. 接著走到CameraClient::sendCommand設(shè)置orientation。

          5. 接著繼續(xù)走到CameraHardwareInterface.setPreviewTransform

          6. 然后通過native_window_set_buffers_transform為Camera的Preview Window(Surface)設(shè)置旋轉(zhuǎn)角度。

          7. 最后就是Surface負(fù)責(zé)使用Surface.mTransform了,這部分流程與MediaCodec類似。

          應(yīng)該說MediaCodec和Camera設(shè)置旋轉(zhuǎn)角度的流程不同,但是最終都是把旋轉(zhuǎn)角度設(shè)置到Surface.mTransform,后面就是Surface和GLConsumer的事情了。

          總結(jié)

          不管是MediaCodec還是Camera,都是Surface的圖像數(shù)據(jù)來源,只不過這個(gè)源圖像數(shù)據(jù),可能有一定的旋轉(zhuǎn)角度或者鏡像。這種情況下,MediaCodecCamera就會通過native_window_set_buffers_transform為Surface設(shè)置Transform Flag,保存在Surface.mTransform變量中,表示生成的圖像數(shù)據(jù),必須經(jīng)過Transform變換之后才能正常顯示??捎玫腡ransform Flag如下所示:

          // Transform Flag
          typedef enum android_transform {
              // 水平鏡像
              HAL_TRANSFORM_FLIP_H    = 0x01,
              // 垂直鏡像
              HAL_TRANSFORM_FLIP_V    = 0x02,
              // 順時(shí)針旋轉(zhuǎn)90度
              HAL_TRANSFORM_ROT_90    = 0x04,
              // 順時(shí)針旋轉(zhuǎn)180度
              HAL_TRANSFORM_ROT_180   = 0x03,
              // 順時(shí)針旋轉(zhuǎn)270度
              HAL_TRANSFORM_ROT_270   = 0x07,
              // don't use. see system/window.h
              HAL_TRANSFORM_RESERVED  = 0x08,
          android_transform_t;

          生產(chǎn)端Surface通過BufferQueueProducer添加圖像數(shù)據(jù)時(shí),會把Surface.mTransform賦值給BufferItem.mTransform,然后入隊(duì)到BufferQueue。

          消費(fèi)端GLConsumer通過BufferQueueComsumer獲取BufferItem后,根據(jù)BufferItem.mTransform Flag,還原出一個(gè)4*4的紋理變換矩陣,保存在GLConsumer.mCurrentTransformMatrix變量中,GLConsumer的使用者調(diào)用GLConsumer.getTransformMatrix獲取這個(gè)紋理變換矩陣,然后作用到對應(yīng)紋理上,就可以正確展示視頻幀了。

          原文鏈接: https://juejin.cn/post/6844904195535929351


          -- END --


          進(jìn)技術(shù)交流群,掃碼添加我的微信:Byte-Flow 



          獲取相關(guān)資料和源碼


          推薦:

          Android FFmpeg 實(shí)現(xiàn)帶濾鏡的微信小視頻錄制功能

          全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了

          一文掌握 YUV 圖像的基本處理

          抖音傳送帶特效是怎么實(shí)現(xiàn)的?

          所有你想要的圖片轉(zhuǎn)場效果,都在這了

          面試官:如何利用 Shader 實(shí)現(xiàn) RGBA 到 NV21 圖像格式轉(zhuǎn)換?

          我用 OpenGL ES 給小姐姐做了幾個(gè)抖音濾鏡


          項(xiàng)目疑難問題解答、大廠內(nèi)部推薦、面試指導(dǎo)、簡歷指導(dǎo)、代碼指導(dǎo)、offer 選擇建議、學(xué)習(xí)路線規(guī)劃,可以點(diǎn)擊找我一對一解答。

          瀏覽 731
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99操天天干 | 国产色999 | 91久久久无码国产一区二区三区 | 小h片| 青青草超碰在线观看 |