首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【Android RTMP】x264 图像数据编码 ( NV21 格式中的 YUV 数据排列 | Y 灰度数据拷贝 | U 色彩值数据拷贝 | V 饱和度数据拷贝 | 图像编码操作 )

  • 25-02-16 05:01
  • 3428
  • 13835
blog.csdn.net

文章目录

  • 安卓直播推流专栏博客总结
  • 一、 NV21 图像数据中的 YUV 数据简介
  • 二、向 x264 编码图片
  • 三、 提取 NV21 数据中的灰度数据 Y
  • 四、 提取 NV21 数据中的饱和度数据 U 和 色彩值数据 V
  • 五、 图像编码操作
  • 六、 x264 视频数据编码代码示例





安卓直播推流专栏博客总结



Android RTMP 直播推流技术专栏 :


0 . 资源和源码地址 :

  • 资源下载地址 : 资源下载地址 , 服务器搭建 , x264 , faac , RTMPDump , 源码及交叉编译库 , 本专栏 Android 直播推流源码 ;
  • GitHub 源码地址 : han1202012 / RTMP_Pusher

1. 搭建 RTMP 服务器 : 下面的博客中讲解了如何在 VMWare 虚拟机中搭建 RTMP 直播推流服务器 ;

  • 【Android RTMP】RTMP 直播推流服务器搭建 ( Ubuntu 18.04.4 虚拟机 )

2. 准备视频编码的 x264 编码器开源库 , 和 RTMP 数据包封装开源库 :

  • 【Android RTMP】RTMPDumb 源码导入 Android Studio ( 交叉编译 | 配置 CMakeList.txt 构建脚本 )

  • 【Android RTMP】Android Studio 集成 x264 开源库 ( Ubuntu 交叉编译 | Android Studio 导入函数库 )

3. 讲解 RTMP 数据包封装格式 :

  • 【Android RTMP】RTMP 数据格式 ( FLV 视频格式分析 | 文件头 Header 分析 | 标签 Tag 分析 | 视频标签 Tag 数据分析 )

  • 【Android RTMP】RTMP 数据格式 ( FLV 视频格式分析 | AVC 序列头格式解析 )

4. 图像数据采集 : 从 Camera 摄像头中采集 NV21 格式的图像数据 , 并预览该数据 ;

  • 【Android RTMP】Android Camera 视频数据采集预览 ( 视频采集相关概念 | 摄像头预览参数设置 | 摄像头预览数据回调接口 )

  • 【Android RTMP】Android Camera 视频数据采集预览 ( NV21 图像格式 | I420 图像格式 | NV21 与 I420 格式对比 | NV21 转 I420 算法 )

  • 【Android RTMP】Android Camera 视频数据采集预览 ( 图像传感器方向设置 | Camera 使用流程 | 动态权限申请 )

5. NV21 格式的图像数据编码成 H.264 格式的视频数据 :

  • 【Android RTMP】x264 编码器初始化及设置 ( 获取 x264 编码参数 | 编码规格 | 码率 | 帧率 | B帧个数 | 关键帧间隔 | 关键帧解码数据 SPS PPS )

  • 【Android RTMP】x264 图像数据编码 ( Camera 图像数据采集 | NV21 图像数据传到 Native 处理 | JNI 传输字节数组 | 局部引用变量处理 | 线程互斥 )

  • 【Android RTMP】x264 图像数据编码 ( NV21 格式中的 YUV 数据排列 | Y 灰度数据拷贝 | U 色彩值数据拷贝 | V 饱和度数据拷贝 | 图像编码操作 )

6. 将 H.264 格式的视频数据封装到 RTMP 数据包中 :

  • 【Android RTMP】RTMPDump 封装 RTMPPacket 数据包 ( 封装 SPS / PPS 数据包 )

  • 【Android RTMP】RTMPDump 封装 RTMPPacket 数据包 ( 关键帧数据格式 | 非关键帧数据格式 | x264 编码后的数据处理 | 封装 H.264 视频数据帧 )

  • 【Android RTMP】RTMPDump 推流过程 ( 独立线程推流 | 创建推流器 | 初始化操作 | 设置推流地址 | 启用写出 | 连接 RTMP 服务器 | 发送 RTMP 数据包 )

7. 阶段总结 : 阿里云服务器中搭建 RTMP 服务器 , 并使用电脑软件推流和观看直播内容 ;

  • 【Android RTMP】RTMP 直播推流 ( 阿里云服务器购买 | 远程服务器控制 | 搭建 RTMP 服务器 | 服务器配置 | 推流软件配置 | 直播软件配置 | 推流直播效果展示 )

  • 【Android RTMP】RTMP 直播推流阶段总结 ( 服务器端搭建 | Android 手机端编码推流 | 电脑端观看直播 | 服务器状态查看 )

8. 处理 Camera 图像传感器导致的 NV21 格式图像旋转问题 :

  • 【Android RTMP】NV21 图像旋转处理 ( 问题描述 | 图像顺时针旋转 90 度方案 | YUV 图像旋转细节 | 手机屏幕旋转方向 )

  • 【Android RTMP】NV21 图像旋转处理 ( 图像旋转算法 | 后置摄像头顺时针旋转 90 度 | 前置摄像头顺时针旋转 90 度 )

9. 下面这篇博客比较重要 , 里面有一个快速搭建 RTMP 服务器的脚本 , 强烈建议使用 ;

  • 【Android RTMP】NV21 图像旋转处理 ( 快速搭建 RTMP 服务器 Shell 脚本 | 创建 RTMP 服务器镜像 | 浏览器观看直播 | 前置 / 后置摄像头图像旋转效果展示 )

10. 编码 AAC 音频数据的开源库 FAAC 交叉编译与 Android Studio 环境搭建 :

  • 【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )

  • 【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )

11. 解析 AAC 音频格式 :

  • 【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )

12 . 将麦克风采集的 PCM 音频采样编码成 AAC 格式音频 , 并封装到 RTMP 包中 , 推流到客户端 :

  • 【Android RTMP】音频数据采集编码 ( FAAC 音频编码参数设置 | FAAC 编码器创建 | 获取编码器参数 | 设置 AAC 编码规格 | 设置编码器输入输出参数 )

  • 【Android RTMP】音频数据采集编码 ( FAAC 编码器编码 AAC 音频解码信息 | 封装 RTMP 音频数据头 | 设置 AAC 音频数据类型 | 封装 RTMP 数据包 )

  • 【Android RTMP】音频数据采集编码 ( FAAC 编码器编码 AAC 音频采样数据 | 封装 RTMP 音频数据头 | 设置 AAC 音频数据类型 | 封装 RTMP 数据包 )






Android 直播推流流程 : 手机采集视频 / 音频数据 , 视频数据使用 H.264 编码 , 音频数据使用 AAC 编码 , 最后将音视频数据都打包到 RTMP 数据包中 , 使用 RTMP 协议上传到 RTMP 服务器中 ;


Android 端中主要完成手机端采集视频数据操作 , 并将视频数据传递给 JNI , 在 NDK 中使用 x264 将图像转为 H.264 格式的视频 , 最后将 H.264 格式的视频打包到 RTMP 数据包中 , 上传到 RTMP 服务器中 ;


本篇博客中介绍如下内容 , Java 层将 Camera 采集的 NV21 格式的数据传入 JNI 层 , 在 JNI 中使用 x264 编码器将 NV21 图像数据编码为 H.264 视频数据 ;





一、 NV21 图像数据中的 YUV 数据简介



Camera 采集的数据是 NV21 格式的 ;

NV21 是 YUV 格式中的一种 , Y 代表灰度 , U 代表色彩值 , V 代表色彩的饱和度 ;


NV21 格式数据在内存中的表示 : 以 4 × 4 4 \times 4 4×4 大小的图片为例 , 先存放 16 16 16 个像素的灰度值 Y 数据 , 然后 4 4 4 组色彩值 V 数据和饱和度 U 数据交替存放 ;

byte[] data = {
	y1 , y2 , y3 , y4 ,
	y5 , y6 , y7 , y8 ,
	y9 , y10, y11, y12,
	y13, y14, y15, y16,
	v1 , u1 , v2 , u2 , 
	v3 , u3 , v4 , u4 , 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

【Android RTMP】Android Camera 视频数据采集预览 ( NV21 图像格式 | I420 图像格式 | NV21 与 I420 格式对比 | NV21 转 I420 算法 ) 博客中详细介绍了 NV21 数据中的 YUV 数据格式 ;





二、向 x264 编码图片



1 . x264 编码图片引入 : x264 编码器对图像数据进行编码 , 要先将 NV21 的图像数据中的 YUV 数据分别存储到 x264 编码图片中 ;


2 . x264_picture_t 结构体 : 该结构体代表了 x264 编码图片 , 该结构体定义在 x264.h 中 ;

typedef struct x264_picture_t
{
	// ...
	// 存储要编码的图片数据
	x264_image_t img;
	// ...
} x264_picture_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7


3 . x264 编码图片使用 :


① 声明 x264_picture_t 指针变量 : C++ 堆内存中的对象必须使用指针接收 ;

// x264 需要编码的图片
x264_picture_t *x264EncodePicture = 0;
  • 1
  • 2

② 初始化 x264_picture_t 对象 : 首先创建 x264_picture_t 对象 , 设置编码方式为 I420 , 以及图片的宽度 x264Param.i_width , 和图片高度 x264Param.i_height ;

// 初始化 x264 编码图片
x264EncodePicture = new x264_picture_t;
// 为 x264 编码图片分配内存
x264_picture_alloc(x264EncodePicture, X264_CSP_I420, x264Param.i_width, x264Param.i_height);
  • 1
  • 2
  • 3
  • 4

③ 释放 x264_picture_t 对象 : 调用 x264_picture_clean 方法释放资源 , 然后销毁对象 ;

// 只要调用该方法, x264_picture_t 必须重新进行初始化
// 因为图片大小改变了, 那么对应的图片不能再使用原来的参数了
// 释放原来的 x264_picture_t 图片, 重新进行初始化
// 析构函数中也要进行释放
if (x264EncodePicture) {
    x264_picture_clean(x264EncodePicture);
    delete x264EncodePicture;
    x264EncodePicture = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9




三、 提取 NV21 数据中的灰度数据 Y



1 . 计算灰度数据的个数 : 灰度数据的个数 , 就是像素的个数 , 每个像素点都有一个灰度数据 ;

// 灰色值的个数, 单位字节
YByteCount = width * height;
  • 1
  • 2

2 . 将灰度数据存储到 x264_picture_t 中 : 在 NV21 格式的图像数据中 , 前 YByteCount 个数据是 YByteCount 个像素点的灰度数据 , 将这些灰度数据拷贝到 x264 编码图像中 ;


3 . 数据接收方 : x264_picture_t* x264EncodePicture 图像的 img 成员的 plane[0] 指针指向的地址 , 接收 YByteCount 个灰度数据 ;


4 . 代码示例 :

    // 从 Camera 采集的 NV21 格式的 data 数据中
    // 将 YUV 中的 Y 灰度值数据, U 色彩值数据, V 色彩饱和度数据提取出来
    memcpy(x264EncodePicture->img.plane[0], data, YByteCount);
  • 1
  • 2
  • 3




四、 提取 NV21 数据中的饱和度数据 U 和 色彩值数据 V



1 . 计算饱和度数据 U 的个数 : 饱和度数据 U 的个数 , 与色彩值数据 V 的个数相同 , 是灰度值数据 Y 个数的 1 4 \cfrac{1}{4} 41​ ;

// 灰色值的个数, 单位字节
YByteCount = width * height;
// U 色彩值, V 饱和度 个数
UVByteCount = YByteCount / 4;
  • 1
  • 2
  • 3
  • 4

2 . 将灰度数据存储到 x264_picture_t 中 : 在 NV21 格式的图像数据中 , 色彩值数据 V , 饱和度数据 U , 交替存储 , V 在前 ( 偶数位置 ), U 在后 ( 奇数位置 ) ;


① U 色相 / 色彩值数据 : 存储在 YByteCount 后的奇数索引位置

② V 色彩饱和度数据 : 存储在 YByteCount 后的偶数索引位置


3 . 代码示例 :

    // 取出 NV21 数据中交替存储的 VU 数据
    // V 在前 ( 偶数位置 ), U 在后 ( 奇数位置 ), 交替存储
    for(int i = 0; i < UVByteCount; i ++){
        // U 色相 / 色彩值数据, 存储在 YByteCount 后的奇数索引位置
        *(x264EncodePicture->img.plane[1] + i) = *(data + YByteCount + i * 2 + 1);

        // V 色彩饱和度数据, 存储在 YByteCount 后的偶数索引位置
        *(x264EncodePicture->img.plane[2] + i) = *(data + YByteCount + i * 2);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9




五、 图像编码操作



1 . 图片编码 :


① 普通帧 : 一般情况下, 一张图像编码出一帧数据 , pp_nal 是一帧数据, pi_nal 表示帧数为 1

② 关键帧 : 如果这个帧是关键帧, 那么 pp_nal 将会编码出 3 帧数据 , pi_nal 表示帧数为 3

③ 关键帧数据 : SPS 帧, PPS 帧, 画面帧 ;



2 . 编码方法函数原型 :

int x264_encoder_encode( x264_t *, x264_nal_t **pp_nal, int *pi_nal, 
						 x264_picture_t *pic_in, x264_picture_t *pic_out );
  • 1
  • 2

① x264_t * 参数 : x264 视频编码器

② x264_nal_t **pp_nal 参数 : 编码后的帧数据, 可能是 1 帧, 也可能是 3 帧

③ int *pi_nal 参数 : 编码后的帧数, 1 或 3

④ x264_picture_t *pic_in 参数 : 输入的 NV21 格式的图片数据

⑤ x264_picture_t *pic_out 参数 : 输出的图片数据



3 . 图像编码代码示例 :

    // 编码后的数据, 这是一个帧数据, 1 帧或 3帧
    x264_nal_t *pp_nal;
    // 编码后的数据个数, 帧的个数, 1 或 3
    int pi_nal;
    // 输出的图片数据
    x264_picture_t pic_out;
    // 编码核心操作
    x264_encoder_encode(x264VedioCodec, &pp_nal, &pi_nal, x264EncodePicture, &pic_out);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9




六、 x264 视频数据编码代码示例



x264 编码器将 NV21 图像数据编码为 H.264 代码 :

/**
 * 视频数据编码
 * 接收 int8_t 类型的原因是, 这里处理的是 jbyte* 类型参数
 * jbyte 类型就是 int8_t 类型
 * @param data 视频数据指针
 */
void VedioChannel::encodeCameraData(int8_t *data) {
    // 加锁, 设置视频编码参数 与 编码互斥
    pthread_mutex_lock(&mMutex);

    // 参数中的 data 是 NV21 格式的
    // 前面 YByteCount 字节个 Y 灰度数据
    // 之后是 UVByteCount 字节个 VU 数据交替存储
    // UVByteCount 字节 V 数据, UVByteCount 字节 U 数据

    // 从 Camera 采集的 NV21 格式的 data 数据中
    // 将 YUV 中的 Y 灰度值数据, U 色彩值数据, V 色彩饱和度数据提取出来
    memcpy(x264EncodePicture->img.plane[0], data, YByteCount);

    // 取出 NV21 数据中交替存储的 VU 数据
    // V 在前 ( 偶数位置 ), U 在后 ( 奇数位置 ), 交替存储
    for(int i = 0; i < UVByteCount; i ++){
        // U 色相 / 色彩值数据, 存储在 YByteCount 后的奇数索引位置
        *(x264EncodePicture->img.plane[1] + i) = *(data + YByteCount + i * 2 + 1);

        // V 色彩饱和度数据, 存储在 YByteCount 后的偶数索引位置
        *(x264EncodePicture->img.plane[2] + i) = *(data + YByteCount + i * 2);
    }

    // 下面两个是编码时需要传入的参数, 这两个参数地址, x264 编码器会想这两个地址写入值

    // 编码后的数据, 这是一个帧数据
    x264_nal_t *pp_nal;
    // 编码后的数据个数, 帧的个数
    int pi_nal;
    // 输出的图片数据
    x264_picture_t pic_out;

    /*
        int x264_encoder_encode( x264_t *, x264_nal_t **pp_nal, int *pi_nal,
                                 x264_picture_t *pic_in, x264_picture_t *pic_out );
        函数原型 :
            x264_t * 参数 : x264 视频编码器
            x264_nal_t **pp_nal 参数 : 编码后的帧数据, 可能是 1 帧, 也可能是 3 帧
            int *pi_nal 参数 : 编码后的帧数, 1 或 3
            x264_picture_t *pic_in 参数 : 输入的 NV21 格式的图片数据
            x264_picture_t *pic_out 参数 : 输出的图片数据

        普通帧 : 一般情况下, 一张图像编码出一帧数据, pp_nal 是一帧数据, pi_nal 表示帧数为 1
        关键帧 : 如果这个帧是关键帧, 那么 pp_nal 将会编码出 3 帧数据, pi_nal 表示帧数为 3
        关键帧数据 : SPS 帧, PPS 帧, 画面帧

     */
    x264_encoder_encode(x264VedioCodec, &pp_nal, &pi_nal, x264EncodePicture, &pic_out);

	// 后续还有操作, 本博客中暂时省略 ... 

    // 解锁, 设置视频编码参数 与 编码互斥
    pthread_mutex_unlock(&mMutex);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
注:本文转载自blog.csdn.net的韩曙亮的文章"https://blog.csdn.net/shulianghan/article/details/106733480"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top