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

相关文章

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

结构体和联合体的区别及说明

《结构体和联合体的区别及说明》文章主要介绍了C语言中的结构体和联合体,结构体是一种自定义的复合数据类型,可以包含多个成员,每个成员可以是不同的数据类型,联合体是一种特殊的数据结构,可以在内存中共享同一... 目录结构体和联合体的区别1. 结构体(Struct)2. 联合体(Union)3. 联合体与结构体的

通过C#和RTSPClient实现简易音视频解码功能

《通过C#和RTSPClient实现简易音视频解码功能》在多媒体应用中,实时传输协议(RTSP)用于流媒体服务,特别是音视频监控系统,通过C#和RTSPClient库,可以轻松实现简易的音视... 目录前言正文关键特性解决方案实现步骤示例代码总结最后前言在多媒体应用中,实时传输协议(RTSP)用于流媒体服

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

PostgreSQL如何查询表结构和索引信息

《PostgreSQL如何查询表结构和索引信息》文章介绍了在PostgreSQL中查询表结构和索引信息的几种方法,包括使用`d`元命令、系统数据字典查询以及使用可视化工具DBeaver... 目录前言使用\d元命令查看表字段信息和索引信息通过系统数据字典查询表结构通过系统数据字典查询索引信息查询所有的表名可

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s