Android音视频【二】 H264码流结构

2024-03-10 19:48

本文主要是介绍Android音视频【二】 H264码流结构,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

人间观察
因为穷,人会放弃体面: 个人形象的体面,工作的体面,社交的体面,尊严的体面。

在分析H.264码流前,我们得得先获取一个H.264的码流,两种方法获取:一是自己写个代码编码为h264的码流(后续介绍),二是是直接从视频文件里抽取。我们这里采用方法二。当然也有其它方法。

快手抖音的短视频/直播,毫无疑问采取的编码方式肯定是H.264AAC生成的MP4封装格式的视频,我们下载一个mp4(可以看一下文件的简介中的编解码器是否是H.264,AAC),用如下ffmpeg命令抽取h264和aac:

// ffmpeg命令 抽取aac到文件
ffmpeg -i v0200f7b0000bq9dpgfiv42bsnt20920.MP4 -acodec copy -vn  1.aac
// ffmpeg命令 抽取h264到文件
ffmpeg -i v0200f7b0000bq9dpgfiv42bsnt20920.MP4  -c:v copy -bsf:v h264_mp4toannexb -an  1.h264

抽取的h264和aac可以播放吗?当然可以,我用的是mac,mac上可以用vlc播放。

ffmpeg命令可以从官网直接下载可执行的二进制
关于在Android中如何利用clang交叉编译ffmpeg后续文章介绍

H.264码流格式

h264的有两种码流格式:字节流格式和RTP包格式。

字节流格式

Annex-B Byte stream format,这个是官方h264协议文档中规定的格式,所以它是大多数编码器默认的编码后的输出格式。它的基本数据单位为NAL单元,简称NALU(Network Abstraction Layer Unit)。每个NALU的前面加上起始码:0x000001(3个字节)或0x00000001(4个字节)用于分割,后面会介绍。

RTP包格式

这种格式没有在h264中规定,这种格式不需要起始码分割NALU,而是在NALU开始的几个字节代表NALU的长度。这个我没有过多研究,应该是不常用的。

所以我们这里主要介绍的就是字节流格式的h264裸流。所谓的裸流就是经编码器编码后输出的数据,而没有经过传输协议(比如flv)封装的数据,这样的数据就叫做裸流。

H.264结构

码流分层

如上所说h264码流是由一个接一个的 NALU组成的,但是它按照功能分为

视频编码层:VCL(Video Coding Layer),编码器压缩处理后的压缩视频数据序列。

网络抽象层:NAL(Network Abstraction Layer),负责以网络要求的格式对数据进行打包和传送,是传输层。不管是本地保存还是在网络上传送,都需要通过这一层来传输。

也就是视频编码数据(VCL)在传输或存储(保存到文件)之前,会先被封装进NAL(也就是NALU)单元才可以。

NALU(NAL单元)

h264码流是一系列的NALU组成,用起始码分割每个。所以整体看码流的格式就是:

H264码流 = …Start_Code_Prefix + NALU + Start_Code_Prefix + NALU + …

Start_Code_Prefix 标示的就是起始码,起始码为:0x000001(3个字节)或0x00000001(4个字节),起始码中间的部分就是NALU的部分。

我们看下我们从抖音/快手提取的h264文件的开始部分(因为h264格式开始有SPS,PPS,SEI 分割较多,你可以搜索一下文件后后面的数据流也有):

起始码.png

NALU的主体是:NALU=NALU Header + EBSP

NALU的主体有细分:分别为EBSP、RBSP和SODB。其中EBSP完全等价于NALU主体,而且它们三个的结构关系为:

EBSP包含RBSP,RBSP包含SODB。

EBSP名字叫:扩展字节序列载荷(Encapsulated Byte Sequence Payload)

RBSP名字叫:原始字节序列载荷(Raw Byte Sequence Payload)

SODB(String Of Data Bits)就是最原始的编码数据。

后续介绍,先有个大概的概念区分,真的是概念非常多。

NALU Header

NALU Header 在每个的NALU中,占据一个字节也就是8位。分三部分,如下:

名称占据位数bit代表的意义
forbidden_zero_bit1bith264文档规定,这个值应该为0,当它不为0时,表示网络传输过程中,当前NALU中可能存在错误,解码器可以考虑不对这个NALU进行解码
nal_ref_idc2bit取值0~3,代表当前这个NALU的重要性,取值越大,代表当前NALU越重要
nal_unit_type5bitNALU的数据类型,比如是sps,pps,sei,idr等

我们主要看一下nal_unit_type在h264协议中定义如下:

nal_unit_type.png

nal_unit_type =1-5是VCL(视频编码层)单元。

6-代表当前NALU为辅助增强信息(SEI)。一般会埋入视频版权等信息。

7-代表当前NALU为序列参数集SPS,包括一个图像序列的所有信息,即两个 IDR 图像间的所有图像信息,如图像尺寸、视频格式等

8-代表当前NALU为图像参数集PPS,包括一个图像的所有分片的所有相关信息, 包括图像类型、序列号等

一般h264的码流最开始都是SEI,SPS,PPS,IDR(I帧)…,SPS,PPS,IDR(I帧). 一般在IDR(I帧)前有SPS,PPS,也就是每一组图像(GOP序列,图片组)都给予了图像参数集(PPS)和这个序列参数集SPS(SPS)。我们看下最开始提取的抖音的h264文件(也就是上面启始码的后一字节)。

// 这里只贴了关键字节,省略其它的
// 16进制打开,每2位数是一个字节byte=8位(bit)
// 1F的二进制位的后五位为:11111
0000 0001 0605 ffff e1dc 45e9 bde6 d948  SEI  06&1F取该字节的后五位=63d31 3a31 2e30 3000 8000 0000 0167 6400  SPS  67&1F取该字节的后五位=70303 c0f1 8319 a000 0000 0168 e978 b2c8  PPS  68&1F取该字节的后五位=8b000 0001 6588 8400 4ffe 841f c0a5 9f35  IDR  65&1F取该字节的后五位=571b9 4cd3 13c1 0000 0001 419a 246c 47ff  slice(片)  41&1F取该字节的后五位=1

视频的宽高就是在SPS中取出来的。

EBSP和RBSP

NALU的起始码为0x0000010x00000001,但是有一种在NALU的内部也有0x0000010x00000001的数据怎么办?H264采用了一种方法如果NALU内部出现了编码器就在最后一个字节前,插入一个新的字节:0x03。做了如下4种情况的处理:

0x000000  插入x03  0x00000300
0x000001  插入x03  0x00000301
0x000002  插入x03  0x00000302
0x000003  插入x03  0x00000303

0x000003是为了防止NALU内部本来就有0x000003这样的数据。
所以说EBSP相较于RBSP,多了防止冲突的一个字节:0x03。当使用EBSP时,就需要检测EBSP内是否有序列:0x000003,如果有,则去掉其中的0x03。这样一来,我们就能得到原始字节序列载荷:RBSP。

我们用提取的抖音的h264文件找下:

3d31 3a31 2e30 3000 8000 0000 0167 6400
1fac d980 b40a 1b01 1000 0003 0010 0000
// 比如67=SPS 的NALU就有一个0303
0303 c0f1 8319 a000 0000 0168 e978 b2c8
b000 0001 6588 8400 4ffe 841f c0a5 9f35
11fe 06cb d3bf 26e6 9d1f ff2c e1b1 aaf2

RBSP和SODB

原始编码数据SODB(String Of Data Bits)他们2个的关系是:

RBSP = SODB + RBSP尾部

RBSP尾部

H264协议文档中有两种尾部表示,如下:

RBSP尾部.png

尾部特RBSP语法

RBSP最后一个字节的最后一个比特为rbsp_stop_one_bit,其值为1,并且当rbsp_stop_one_bit不是最后一个比特时,用一个或多个rbsp_alignment_zero_bit,其值为0,补齐以形成一个字节对齐。

条带RBSP尾部

nal_unit_type等于1~5时采用这种尾部。在尾部特RBSP语法的基础上,如果当entropy_coding_mode_flag值为1,也即当前采用的熵编码为CABAC,而且more_rbsp_trailing_data返回为true,也即RBSP中有更多数据时,添加一个或多个0x0000

H264的码流结构

所以整体H.264的Annex-B码流格式从概念上来看就是,SODB里就是原始的编码数据。

H.264 Annex-B 码流格式.png

如有描述不准确欢迎指正。

H.264的协议文档

http://www.itu.int/rec/T-REC-H.264

http://www.itu.int/rec/T-REC-H.264-200503-S/en

这篇关于Android音视频【二】 H264码流结构的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/795328

相关文章

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Python+PyQt5实现文件夹结构映射工具

《Python+PyQt5实现文件夹结构映射工具》在日常工作中,我们经常需要对文件夹结构进行复制和备份,本文将带来一款基于PyQt5开发的文件夹结构映射工具,感兴趣的小伙伴可以跟随小编一起学习一下... 目录概述功能亮点展示效果软件使用步骤代码解析1. 主窗口设计(FolderCopyApp)2. 拖拽路径

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

Android 实现一个隐私弹窗功能

《Android实现一个隐私弹窗功能》:本文主要介绍Android实现一个隐私弹窗功能,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 效果图如下:1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来res/l

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

Android 12解决push framework.jar无法开机的方法小结

《Android12解决pushframework.jar无法开机的方法小结》:本文主要介绍在Android12中解决pushframework.jar无法开机的方法,包括编译指令、框架层和s... 目录1. android 编译指令1.1 framework层的编译指令1.2 替换framework.ja

Android开发环境配置避坑指南

《Android开发环境配置避坑指南》本文主要介绍了Android开发环境配置过程中遇到的问题及解决方案,包括VPN注意事项、工具版本统一、Gerrit邮箱配置、Git拉取和提交代码、MergevsR... 目录网络环境:VPN 注意事项工具版本统一:android Studio & JDKGerrit的邮

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

Android使用ImageView.ScaleType实现图片的缩放与裁剪功能

《Android使用ImageView.ScaleType实现图片的缩放与裁剪功能》ImageView是最常用的控件之一,它用于展示各种类型的图片,为了能够根据需求调整图片的显示效果,Android提... 目录什么是 ImageView.ScaleType?FIT_XYFIT_STARTFIT_CENTE