【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);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章