H264-AAC 格式解析

一、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保留。

nalu type

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的组成

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

 

 

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值