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





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



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


本篇博客中介绍如下内容 , 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 , 
}

【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;


3 . x264 编码图片使用 :


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

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

② 初始化 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);

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

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




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



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

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

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);




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



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

// 灰色值的个数, 单位字节
YByteCount = width * height;
// U 色彩值, V 饱和度 个数
UVByteCount = YByteCount / 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 . 图片编码 :


① 普通帧 : 一般情况下, 一张图像编码出一帧数据 , 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 );

① 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);





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值