一、H264的一些概念
本文章不在于写多么专业的知识理论,只是为了记录自己的所见所闻并让初学者能从很简单快捷的认识到H264,不至于一说这些东西都高大上不容易理解。有句话说无人教入门很难,有人教三分钟即会。
1.脱壳操作
在编码时,每遇到两个字节连续为0,就插入一个字节的0x03。解码时将0x03去掉。 也称为脱壳操作。
2.分层
H.264的功能分为两层,视频编码层(VCL) 和网络提取层 (NAL)
VCL:通俗的讲就是编码后输出的数据,但是VCL数据是没法在网络中直接传送的,这时候就需要网络提取层NAL
NAL:在VCL数据传输或存储之前,需要封装成NAL单元再在网络中传送
二、NAL单元格式:
NAL单元序列是由一系列的NAL组成
NAL分为三部分:StartCode、NAL头与RBSP
NAL头:1字节,也由三部分组成
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
禁止位 | 重要标识 | 载荷数据类型 |
1.forbidder_bit(1bit) :禁止位
编码中默认值为0,当网络识别此单元中存在比特错误时,可将其设为1,以便接收方丢掉该单元,主要 用于适应不同种类的网络环境(比如有线无线相结合的环境)。
2.nal_reference_bit(2bit):优先级
用于在重构过程中标记一个NAL单元的重要性,值越大,越重要。值为0表示这个NAL单元没有用于预测,因此可被解码器抛弃而不会有错误扩散;值高于0表示此NAL单元要用于无漂移重构,且值越高,对此NAL单元丢失的影响越大。
3.nal_unit_type(5bit):类型
可以表示NALU的32种不同类型特征,类型1~12是H.264定义的,类型24~31是用于H.264以外的,RTP负荷规范使用这其中的一些值来定义包聚合和分裂,其他值为H.264保留。
nal_unit_type | NAL类型 |
0 | 未使用 |
1 | 不分区、非IDR图像的片 |
2 | 片分区A |
3 | 片分区B |
4 | 片分区C |
5 | IDR图像的片 |
6 | 补充增强信息单元(SEI) |
7 | 序列参数集 |
8 | 图像参数集 |
9 | 分解符 |
10 | 序列结束 |
11 | 码流结束 |
12 | 填充 |
13..23 | 保留 |
24..31 | 未使用 |
RBSP的组成
SPS | SEI | PPS | I片 | 图像定界符 | P片 | P片 |
RBSP序列举例 |
常见的H264的数据格式
00 00 00 01 67 (SPS)
00 00 00 01 68 (PPS)
00 00 00 01 65 ( IDR 帧,也就是常说的I帧)
00 00 00 01 61 (P帧)
00 00 00 01 41 (不分区、非IDR图像的片)
我遇到的H264一般都是 67 68 65 41 41 41 ....
67的各种进制表示
禁止位:0
优先级位:11
类型:00111 十进制位7也就是SPS
- 7为序列参数集(SPS),
- 8为图像参数集(PPS),
- 5代表I帧。
- 1代表非I帧。
由此可知,61和41其实都是P帧(type值为1),只是重要级别不一样(它们的NRI一个是11BIN,一个是10BIN)
实际电脑摄像头数据分析
我的电脑摄像头采集的都是67 68 65 65 41 41 ... 67 68 65 65 41 41 ...
分割符都是四个字节 00 00 00 01,发现67 68 的分隔符都是4个字节,但是65 与 41 的分隔符就有可能是3个字节即 00 00 01
解析SPS,PPS
/// <summary>
/// 获取关键帧中的sps,pps,该方法自动解析了分隔符长度
/// </summary>
/// <param name="rawData">收到的实际数据</param>
/// <returns>这个返回值也可以作为判断关键帧来使用,因为sps,pps应该算关键帧里的</returns>
public static true GetSPS_PPS(byte[] rawData, out byte[] sps, out byte[] pps)
{
try
{
int i = 0;
int spsIndex = 0; //sps的索引位置(即67的索引位置)
int ppsIndex = 0; //pps的索引位置(即68的索引位置)
int keyIndex = 0; //关键帧的索引位置(即65的索引位置)
int ppsSeparatorLen = 3; //SPS,PPS的分隔符长度
int keySeparatorLen = 3; //数据的分隔符长度
while (i < rawData.Length - 3)
{
if (rawData[i] == 0 && rawData[i + 1] == 0 && rawData[i + 2] == 1)
{
if ((rawData[i + 3] & 0x1f) == 5 || (rawData[i + 3] & 0x1f) == 6) //
{
keyIndex = i + 3;
if (rawData[i - 1] == 0)
{
keySeparatorLen = 4;
}
break;
}
if ((rawData[i + 3] & 0x1f) == 7)
{
spsIndex = i + 3;
}
else if ((rawData[i + 3] & 0x1f) == 8)
{
ppsIndex = i + 3;
if (rawData[i - 1] == 0)
{
ppsSeparatorLen = 4;
}
}
}
i++;
}
sps = new byte[ppsIndex - spsIndex - ppsSeparatorLen];
Array.Copy(rawData, spsIndex, sps, 0, sps.Length);
pps = new byte[keyIndex - ppsIndex - keySeparatorLen];
Array.Copy(rawData, ppsIndex, pps, 0, pps.Length);
return true;
}
catch (Exception e)
{
Console.WriteLine(e);
}
sps = new byte[0];
pps = new byte[0];
return false;
}
三、AAC 音频
AAC有两种格式一种是ASIF格式另一种是ADTS格式,ASIF格式只有一个信息头,所以在解码的时候只能从头开始,改格式一般用在磁盘中。ADTS格式是每帧都带有消息头,所以呢可以从ADTS格式的任意帧开始解码。ADTS是帧序列,本身具备流特征,在音频流的传输与处理方面更加合适。
ADTS结构
AAC的前7个字节也就是ADTS头
实际数据: adts:FF F1 54 40 40 01 00
都是以FFF开头,也特别好认,可以直接把收到的AAC音频存到文件里用播放器就可以播放
ADTS分为两部分:
第一部分:adts fixed header
采样率
根据sampling frequency index 采样率下标在下面可以找到对应的采样率
- 0: 96000 Hz
- 1: 88200 Hz
- 2: 64000 Hz
- 3: 48000 Hz
- 4: 44100 Hz
- 5: 32000 Hz
- 6: 24000 Hz
- 7: 22050 Hz
- 8: 16000 Hz
- 9: 12000 Hz
- 10: 11025 Hz
- 11: 8000 Hz
- 12: 7350 Hz
- 13: Reserved
- 14: Reserved
- 15: frequency is written explictly
channel configuration: 声道数
- 0: Defined in AOT Specifc Config
- 1: 1 channel: front-center
- 2: 2 channels: front-left, front-right
- 3: 3 channels: front-center, front-left, front-right
- 4: 4 channels: front-center, front-left, front-right, back-center
- 5: 5 channels: front-center, front-left, front-right, back-left, back-right
- 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
- 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
- 8-15: Reserved
第二部分 adts variable header
解析adts header
#region Get information form AAC Header
/// <summary>
/// 解析AAC头,从中拿到如下几个播放信息
/// </summary>
/// <param name="adtsHeader">adts Header,音频的前7个字节</param>
/// <param name="id">MPEG Version: 0 for MPEG-4, 1 for MPEG-2</param>
/// <param name="profile">表示使用哪个级别的AAC,有些芯片只支持AAC LC 。在MPEG-2 AAC中定义了3种:</param>
/// <param name="acquisitionRate">表示使用的采样率下标,通过这个下标在 Sampling Frequencies[ ]数组中查找得知采样率的值</param>
/// <param name="channel">表示声道数 </param>
/// <param name="frame_length"> 一个ADTS帧的长度包括ADTS头和AAC原始流.</param>
public void AnalysisAACHeader(byte[] adtsHeader, out int id, out int profile, out int acquisitionRate, out int channel, out int frameLength)
{
id = (int)((adtsHeader[1] & 0x08) >> 3);
profile = (int)((adtsHeader[2] & 0xc0) >> 6);
acquisitionRate = (int)((adtsHeader[2] & 0x3c) >> 2);
GetacquisitionRate(ref acquisitionRate);
channel = (int)(adtsHeader[2] & 0x01) << 2 | (int)((adtsHeader[3] & 0xc0) >> 6);
frameLength = ((int)(adtsHeader[3] & 0x03) << 8) | ((int)(adtsHeader[4] & 0xe0) << 3) | ((int)(adtsHeader[4] & 0x1f) << 3) | (int)((adtsHeader[5] & 0xe0) >> 5);
}
/// <summary>
/// 解析AAC头,从中拿到如下几个播放信息
/// </summary>
/// <param name="acquisitionRate">采样率索引</param>
public void GetacquisitionRate(out int acquisitionRate)
{
acquisitionRate = 0;
switch (acquisitionRate)
{
case 0:
acquisitionRate = 96000;
break;
case 1:
acquisitionRate = 88200;
break;
case 2:
acquisitionRate = 64000;
break;
case 3:
acquisitionRate = 48000;
break;
case 4:
acquisitionRate = 44100;
break;
case 5:
acquisitionRate = 32000;
break;
case 6:
acquisitionRate = 24000;
break;
case 7:
acquisitionRate = 22050;
break;
case 8:
acquisitionRate = 16000;
break;
case 9:
acquisitionRate = 12000;
break;
case 10:
acquisitionRate = 11025;
break;
case 11:
acquisitionRate = 8000;
break;
}
}
#endregion