【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 × 4 4 \times 4 4×4 像素的图片为例 , 其有 16 16 16 个 Y 数据 , UV 数据只有 4 4 4 组 , 共 8 8 8 个 ;



1 . 数据的排列格式如下矩阵 : 16 16 16 个 Y 数据在前 , 然后 4 4 4 组 ( 8 8 8 个 ) VU 数据交替存放 ;

[ y 1 y 2 y 3 y 4 y 5 y 6 y 7 y 8 y 9 y 10 y 11 y 12 y 13 y 14 y 15 y 16 v 1 u 1 v 2 u 2 v 3 u 3 v 4 u 4 ] \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} y1y5y9y13v1v3y2y6y10y14u1u3y3y7y11y15v2v4y4y8y12y16u2u4



2 . 旋转像素灰度值 Y : 像素值顺时针 90 度旋转后的样式 ;


① 旋转矩阵 :
在这里插入图片描述


② 旋转后的最终 Y 灰度值 矩阵 :

[ y 13 y 9 y 5 y 1 y 14 y 10 y 6 y 2 y 15 y 11 y 7 y 3 y 16 y 12 y 8 y 4 ] \begin{bmatrix} y13 & y9 & y5 & y1 \\\\ y14 & y10 & y6 & y2 \\\\ y15 & y11& y7& y3 \\\\ y16& y12& y8& y4 \\ \end{bmatrix} y13y14y15y16y9y10y11y12y5y6y7y8y1y2y3y4




3. 旋转图像的 饱和度 色彩值 UV


旋转图像的 饱和度 色彩值 UV : UV 数据旋转后 , 只是给出了 UV 数据的位置 , 还需要将 UV 数据按照顺序排列 :


① 旋转 UV 数据矩阵 : 该旋转后只能代表 UV 数据组的位置 , 即 第一组 UV 数据 ( v 3 u 3 v3 \quad u3 v3u3 ) 在左上角 , 第二组 UV 数据 ( v 1 u 1 v1 \quad u1 v1u1 ) 在右上角 , 第三组 UV 数据 ( v 4 u 4 v4 \quad u4 v4u4 ) 在左下角 , 第四组 UV 数据 ( v 2 u 2 v2 \quad u2 v2u2 ) 在右下角 ;

在这里插入图片描述

② 旋转后的最终 UV 色彩值 饱和度 矩阵 :

[ v 3 u 3 v 1 u 1 v 4 u 4 v 2 u 2 ] \begin{bmatrix} v3 & u3 & v1 & u1 \\\\ v4 & u4 & v2 & u2\\ \end{bmatrix} v3v4u3u4v1v2u1u2



4. 旋转后的 NV21 格式


NV21 格式的图像的 YUV 值顺时针旋转 90 度后的 YUV 矩阵为 :


[ y 13 y 9 y 5 y 1 y 14 y 10 y 6 y 2 y 15 y 11 y 7 y 3 y 16 y 12 y 8 y 4 v 3 u 3 v 1 u 1 v 4 u 4 v 2 u 2 ] \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} y13y14y15y16v3v4y9y10y11y12u3u4y5y6y7y8v1v2y1y2y3y4u1u2





三、 Android 手机端屏幕旋转方向





1. 获取手机屏幕方向


获取手机屏幕方向 : 调用下面的方法 , 可以获取到 4 4 4 个手机屏幕方向 ;

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);
    }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值