【Android RTMP】NV21 圖像旋轉處理 ( 問題描述 | 圖像順時針旋轉 90 度方案 | YUV 圖像旋轉細節 | 手機屏幕旋轉方向 )





安卓直播推流專欄博客總結



Android RTMP 直播推流技術專欄 :


0 . 資源和源碼地址 :


1. 搭建 RTMP 服務器 : 下面的博客中講解了如何在 VMWare 虛擬機中搭建 RTMP 直播推流服務器 ;

2. 準備視頻編碼的 x264 編碼器開源庫 , 和 RTMP 數據包封裝開源庫 :

3. 講解 RTMP 數據包封裝格式 :

4. 圖像數據採集 : 從 Camera 攝像頭中採集 NV21 格式的圖像數據 , 並預覽該數據 ;

5. NV21 格式的圖像數據編碼成 H.264 格式的視頻數據 :

6. 將 H.264 格式的視頻數據封裝到 RTMP 數據包中 :

7. 階段總結 : 阿里雲服務器中搭建 RTMP 服務器 , 並使用電腦軟件推流和觀看直播內容 ;

8. 處理 Camera 圖像傳感器導致的 NV21 格式圖像旋轉問題 :

9. 下面這篇博客比較重要 , 裏面有一個快速搭建 RTMP 服務器的腳本 , 強烈建議使用 ;

10. 編碼 AAC 音頻數據的開源庫 FAAC 交叉編譯與 Android Studio 環境搭建 :

11. 解析 AAC 音頻格式 :

12 . 將麥克風採集的 PCM 音頻採樣編碼成 AAC 格式音頻 , 並封裝到 RTMP 包中 , 推流到客戶端 :






Android 直播推流流程 : 手機採集視頻 / 音頻數據 , 視頻數據使用 H.264 編碼 , 音頻數據使用 AAC 編碼 , 最後將音視頻數據都打包到 RTMP 數據包中 , 使用 RTMP 協議上傳到 RTMP 服務器中 ;


Android 端中主要完成手機端採集視頻數據操作 , 並將視頻數據傳遞給 JNI , 在 NDK 中使用 x264 將圖像轉爲 H.264 格式的視頻 , 最後將 H.264 格式的視頻打包到 RTMP 數據包中 , 上傳到 RTMP 服務器中 ;


之前的博客中基本實現了 Camera 採集 NV21 格式圖像並使用 x264 編碼圖像爲 H.264 視頻 , 使用 RTMPDump 將 H.264 視頻幀信息打包爲 RTMP 數據包 , 推流到服務器端 ;


當前的問題是 , 推流到服務器端的 NV21 格式的圖像被逆時針旋轉了 90 度 ;





一、 NV21 圖像格式與 Camera圖像傳感器方向問題



1. Camera 採集畫面並預覽推流 : 這裏注意 , 之前圖像被逆時針旋轉了 90 度 , 設置了圖像傳感器角度後 , 預覽圖片糾正過來了 , 但是 Camera 的圖像傳感器採集的 NV21 格式的圖像還是被旋轉了 90 度 ;

在這裏插入圖片描述

2 . 電腦端觀看直播效果展示 : 屏幕畫面被逆時針旋轉了 90 度 , 這是因爲之前攝像頭傳感器只設置了將預覽畫面糾正過來 , 但是 NV21 格式的圖像數據還是被逆時針旋轉了 90 度的數據 ;

在這裏插入圖片描述

具體涉及到的圖像格式 , 以及圖像傳感器方向 , 屏幕方向的關係 , 參考博客 【Android RTMP】Android Camera 視頻數據採集預覽 ( 圖像傳感器方向設置 | Camera 使用流程 | 動態權限申請 )





二、 NV21 圖像格式視頻旋轉





1. 圖像旋轉問題及解決方案 ( 順時針旋轉 90 度 )


圖像旋轉問題及解決方案 :


① 問題描述 : 分析上面的畫面 , 可以看到視頻被逆時針旋轉了 90 度 , 即畫面圖像被逆時針旋轉了 90 度 ;

② 解決方案 : 將 Camera 採集的 NV21 格式的圖像順時針旋轉 90 度 , 即可解決上述問題 ;



2. NV21 圖像格式數旋轉方案


NV21 圖像格式數據排列 :4×44 \times 4 像素的圖片爲例 , 其有 1616 個 Y 數據 , UV 數據只有 44 組 , 共 88 個 ;



1 . 數據的排列格式如下矩陣 : 1616 個 Y 數據在前 , 然後 44 組 ( 88 個 ) VU 數據交替存放 ;

[y1y2y3y4y5y6y7y8y9y10y11y12y13y14y15y16v1u1v2u2v3u3v4u4]\begin{bmatrix} y1 & y2 & y3 & y4 \\\\ y5 & y6 & y7 & y8 \\\\ y9 & y10& y11& y12 \\\\ y13& y14& y15& y16 \\\\ v1 & u1 & v2 & u2 \\\\ v3 & u3 & v4 & u4\\ \end{bmatrix}



2 . 旋轉像素灰度值 Y : 像素值順時針 90 度旋轉後的樣式 ;


① 旋轉矩陣 :
在這裏插入圖片描述


② 旋轉後的最終 Y 灰度值 矩陣 :

[y13y9y5y1y14y10y6y2y15y11y7y3y16y12y8y4]\begin{bmatrix} y13 & y9 & y5 & y1 \\\\ y14 & y10 & y6 & y2 \\\\ y15 & y11& y7& y3 \\\\ y16& y12& y8& y4 \\ \end{bmatrix}




3. 旋轉圖像的 飽和度 色彩值 UV


旋轉圖像的 飽和度 色彩值 UV : UV 數據旋轉後 , 只是給出了 UV 數據的位置 , 還需要將 UV 數據按照順序排列 :


① 旋轉 UV 數據矩陣 : 該旋轉後只能代表 UV 數據組的位置 , 即 第一組 UV 數據 ( v3u3v3 \quad u3 ) 在左上角 , 第二組 UV 數據 ( v1u1v1 \quad u1 ) 在右上角 , 第三組 UV 數據 ( v4u4v4 \quad u4 ) 在左下角 , 第四組 UV 數據 ( v2u2v2 \quad u2 ) 在右下角 ;

在這裏插入圖片描述

② 旋轉後的最終 UV 色彩值 飽和度 矩陣 :

[v3u3v1u1v4u4v2u2]\begin{bmatrix} v3 & u3 & v1 & u1 \\\\ v4 & u4 & v2 & u2\\ \end{bmatrix}



4. 旋轉後的 NV21 格式


NV21 格式的圖像的 YUV 值順時針旋轉 90 度後的 YUV 矩陣爲 :


[y13y9y5y1y14y10y6y2y15y11y7y3y16y12y8y4v3u3v1u1v4u4v2u2]\begin{bmatrix} y13 & y9 & y5 & y1 \\\\ y14 & y10 & y6 & y2 \\\\ y15 & y11& y7& y3 \\\\ y16& y12& y8& y4 \\\\ v3 & u3 & v1 & u1 \\\\ v4 & u4 & v2 & u2\\ \end{bmatrix}





三、 Android 手機端屏幕旋轉方向





1. 獲取手機屏幕方向


獲取手機屏幕方向 : 調用下面的方法 , 可以獲取到 44 個手機屏幕方向 ;

mRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();

調用上述方法 , 獲取的手機屏幕方向是 Surface.ROTATION_0 , Surface.ROTATION_90 , Surface.ROTATION_180 , Surface.ROTATION_270 , 四個常量中的一個



2. Surface.ROTATION_0 正常豎屏方向


Surface.ROTATION_0 正常豎屏方向 :


① 常量含義 : ROTATION_0 常量代表手機自然方向逆時針旋轉 0 度, 豎屏 ;

② 方向說明 :

  • 頭部 ( 攝像頭的一邊 ) 在上邊
  • 尾部 ( Home / 返回 鍵的一邊 ) 在下邊

一般的豎屏操作方式, 也是最常用的方式 ;

在這裏插入圖片描述



3. Surface.ROTATION_90 正常豎屏方向


Surface.ROTATION_90 正常豎屏方向 :


① 常量含義 : ROTATION_90 常量代表手機自然方向逆時針旋轉 90 度, 橫屏 ;

② 方向說明 :

  • 頭部 ( 攝像頭的一邊 ) 在左邊
  • 尾部 ( Home / 返回 鍵的一邊 ) 在右邊

一般橫屏操作方式 ;

在這裏插入圖片描述



4. Surface.ROTATION_180 正常豎屏方向


Surface.ROTATION_180 正常豎屏方向 :


① 常量含義 : ROTATION_180 常量代表手機自然方向逆時針旋轉 180 度, 豎屏 ;

② 方向說明 :

  • 頭部 ( 攝像頭的一邊 ) 在下邊
  • 尾部 ( Home / 返回 鍵的一邊 ) 在上邊

一般很少這樣操作 ;

在這裏插入圖片描述



5. Surface.ROTATION_270 正常豎屏方向


Surface.ROTATION_270 正常豎屏方向 :


① 常量含義 : ROTATION_270 常量代表手機自然方向逆時針旋轉 270 度, 橫屏 ;

② 方向說明 :

  • 頭部 ( 攝像頭的一邊 ) 在右邊
  • 尾部 ( Home / 返回 鍵的一邊 ) 在左邊

一般橫屏操作方式 ;

在這裏插入圖片描述





四、 Android 手機端屏幕方向獲取代碼示例



Android 手機端屏幕方向獲取代碼示例 :

    /**
     * 設置 Camera 預覽方向
     * 如果不設置, 視頻是顛倒的
     * 該方法內容拷貝自 {@link Camera#setDisplayOrientation} 註釋, 這是 Google Docs 提供的
     * @param parameters
     */
    private void setCameraPreviewOrientation(Camera.Parameters parameters) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraFacing, info);

        /*
            獲取屏幕相對於自然方向的角度
            自然方向就是正常的豎屏方向, 攝像頭在上, Home 鍵在下, 對應 Surface.ROTATION_0

            ROTATION_0 是自然方向逆時針旋轉 0 度, 豎屏
            頭部 ( 攝像頭的一邊 ) 在上邊
            尾部 ( Home / 返回 鍵的一邊 ) 在下邊
            一般豎屏操作方式, 也是最常用的方式

            ROTATION_90 是自然方向逆時針旋轉 90 度, 橫屏
            頭部 ( 攝像頭的一邊 ) 在左邊
            尾部 ( Home / 返回 鍵的一邊 ) 在右邊
            一般橫屏操作方式

            ROTATION_180 是自然方向逆時針旋轉 180 度, 豎屏
            頭部 ( 攝像頭的一邊 ) 在下邊
            尾部 ( Home / 返回 鍵的一邊 ) 在上邊
            一般很少這樣操作

            ROTATION_270 是自然方向逆時針旋轉 270 度, 橫屏
            頭部 ( 攝像頭的一邊 ) 在右邊
            尾部 ( Home / 返回 鍵的一邊 ) 在左邊
            一般很少這樣操作

            博客中配合截圖說明這些方向
         */
        mRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();

        int degrees = 0;
        switch (mRotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                /*
                    Camera 圖像傳感器採集的數據是按照豎屏採集的
                    原來設置的圖像的寬高是 800 x 400
                    如果屏幕豎過來, 其寬高就變成 400 x 800, 寬高需要交換一下
                    這裏需要通知 Native 層的 x264 編碼器, 修改編碼參數 , 按照 400 x 800 的尺寸進行編碼
                    需要重新設置 x264 的編碼參數
                 */
                mOnChangedSizeListener.onChanged(mHeight, mWidth);
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360; // compensate the mirror
        } else { // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        mCamera.setDisplayOrientation(result);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章