【Android RTMP】Android Camera 視頻數據採集預覽 ( 圖像傳感器方向設置 | Camera 使用流程 | 動態權限申請 )





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



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 服務器中 ;


本篇博客中主要講解 Android 端數據採集 , Camera 攝像頭獲取 NV21 數據後 , 此時預覽數據方向是錯誤的 , 因爲 Camera 圖像傳感器採集數據時 , 始終以手機右上角爲原點 , NV21 數據的圖像信息也是倒着的 ;





一、 Camera 傳感器方向簡介



1 . Camera 採集 NV21 圖像數據 : 手機 Camera 採集的圖像數據完畢後 , 通過 PreviewCallback 接口的 onPreviewFrame 回調方法獲取 NV21 圖像數據 ;


2 . NV21 圖像數據來源 : 該數據的最底層來源是手機 Camera 硬件的圖像傳感器 ;


3 . 圖像傳感器採集圖像機制 :


① 圖像傳感器座標原點 : 圖像傳感器取景時有一個座標原點 , 就是手機的右上角 ;

② 圖像傳感器座標方向 : 從圖像傳感器原點 / 手機右上角 ( 0 , 0 ) 向右下角方向是 X 增加方向 , 從圖像傳感器原點 / 手機右上角 ( 0 , 0 ) 向左上角方向是 Y 增加方向 ;

在這裏插入圖片描述





二、 Camera 圖像傳感器橫向顯示數據



1 . 向左橫向 : 當手機向左橫放時 , 圖像傳感器原點及方向 , 屏幕的原點及方向如下 ;


① 傳感器原點和方向 : 圖像傳感器 ( 手機右上角 ) 原點 (0,0)( 0 , 0 ) , 向右 X 增加 , 向下 Y 增加 ;

② 屏幕原點和方向 : 手機屏幕當前左上角 ( 手機的右上角 ) 是屏幕原點 , 向右 X 增加 , 向下 Y 增加 ;

在這裏插入圖片描述



2 . 圖像顯示 : 屏幕傳感器的方向與屏幕方向一致 , 此時沒有顯示圖像傳感器 : 橫向界面的 Camera 採集的圖像數據是正常的 ;
在這裏插入圖片描述

注意 : 這是向左橫向顯示的數據 , 如果向右橫向 , 數據整個都倒過來了 ;





三、 Camera 圖像傳感器縱向顯示數據



1 . 正常豎屏 : 此時還是以右上角爲原點 , 採集橫向圖像 ,


① 傳感器原點和方向 : 圖像傳感器 ( 手機右上角 ) 原點 (0,0)( 0 , 0 ) , 向右 X 增加 , 向下 Y 增加 ;

② 屏幕原點和方向 : 手機屏幕當前左上角 ( 手機的右上角 ) 是屏幕原點 , 向右 X 增加 , 向下 Y 增加 ;

在這裏插入圖片描述



2 . 圖像顯示 : 屏幕傳感器的方向與屏幕方向不一致 , 此時沒有顯示圖像傳感器 , 縱向數據是不正常的 , 此時垂直方向顯示界面時 , 顯示的拍照信息還是橫向的 , 只是 Camera 採集的圖像逆時針旋轉了 90 度 ;
在這裏插入圖片描述

注意 : 這是向上縱向顯示的數據 , 如果向下縱向 , 數據整個都倒過來了 ;





四、 設置 Camera 預覽數據方向



1 . 糾正圖像預覽方向 : Google 官方提供了設置 Camera 預覽方向的方式 , 以下代碼定義在 Camera#setDisplayOrientation 文檔註釋中 , 爲 Camera 設置了以下參數後 , 就不會有上述預覽圖像錯誤的問題產生 ;


2 . NV21 數據方向 : NV21 格式的圖像數據的的實際方向還是錯誤的方向 , 需要用戶自己使用時糾正 ;


    /**
     * 設置 Camera 預覽方向
     * 如果不設置, 視頻是顛倒的
     * 該方法內容拷貝自 {@link Camera#setDisplayOrientation} 註釋, 這是 Google Docs 提供的
     * @param parameters
     */
    private void setCameraPreviewOrientation(Camera.Parameters parameters) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraFacing, info);
        mRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (mRotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                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);
    }





五、 Camera 使用流程



1 . 開啓 Camera 攝像頭 :

    /**
     * 開啓 Camera 攝像頭
     */
    private void startCameraNV21DataPreview() {
        try {
            Log.i("octopus", "startCameraNV21DataPreview");
            // 1. 打開指定方向的 Camera 攝像頭
            mCamera = Camera.open(mCameraFacing);
            // 2. 獲取 Camera 攝像頭參數, 之後需要修改配置該參數
            Camera.Parameters parameters = mCamera.getParameters();
            // 3. 設置 Camera 採集後預覽圖像的數據格式 ImageFormat.NV21
            parameters.setPreviewFormat(ImageFormat.NV21);
            // 4. 設置攝像頭預覽尺寸
            setPreviewSize(parameters);
            // 5. 設置圖像傳感器參數
            setCameraPreviewOrientation(parameters);
            mCamera.setParameters(parameters);
            // 6. 計算出 NV21 格式圖像 mWidth * mHeight 像素數據大小
            mNv21DataBuffer = new byte[mWidth * mHeight * 3 / 2];
            // 7. 設置 Camera 預覽數據緩存區
            mCamera.addCallbackBuffer(mNv21DataBuffer);
            // 8. 設置 Camera 數據採集回調函數, 採集完數據後
            //    就會回調此 PreviewCallback 接口的
            //    void onPreviewFrame(byte[] data, Camera camera) 方法
            mCamera.setPreviewCallbackWithBuffer(this);
            // 9. 設置預覽圖像畫面的 SurfaceView 畫布
            mCamera.setPreviewDisplay(mSurfaceHolder);

            // 11. 開始預覽
            mCamera.startPreview();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

2 . 釋放 Camera 攝像頭 :

    /**
     * 釋放 Camera 攝像頭
     */
    private void stopCameraNV21DataPreview() {
        if (mCamera != null) {
            // 下面的 API 都是 Android 提供的
            
            // 1. 設置預覽回調接口, 這裏設置 null 即可
            mCamera.setPreviewCallback(null);
            // 2. 停止圖像數據預覽
            mCamera.stopPreview();
            // 3. 釋放 Camera
            mCamera.release();
            mCamera = null;
        }
    }




六、 Camera 動態權限申請



1 . Android 6.0 以下靜態設置權限 : AndroidManifest.xml 設置靜態權限 ;

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

2 . Android 6.0 以上動態獲取權限 :

    /**
     * 需要獲取的權限列表
     */
    private String[] permissions = new String[]{
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.INTERNET,
            Manifest.permission.MODIFY_AUDIO_SETTINGS,
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.CAMERA
    };

    /**
     * 動態申請權限的請求碼
     */
    private static final int PERMISSION_REQUEST_CODE = 888;

    /**
     * 動態申請權限
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void initPermissions() {
        if (isLacksPermission()) {
            //動態申請權限 , 第二參數是請求嗎
            requestPermissions(permissions, PERMISSION_REQUEST_CODE);
        }
    }

    /**
     * 判斷是否有 permissions 中的權限
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public boolean isLacksPermission() {
        for (String permission : permissions) {
            if(checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED){
                return true;
            }
        }
        return false;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章