RTMP视频流格式解析

Posted 贺二公子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RTMP视频流格式解析相关的知识,希望对你有一定的参考价值。

原文地址:https://blog.csdn.net/zero_sama/article/details/69802783


文章目录

FLV 格式解析

FLV是由一个FLV Header 和 若干tag(Video Tag, Audio Tag, Script Tag三种,分别代表视频流,音频流和脚本流)组成的二进制文件。

FLV Header示意图

FLV Header:

  • 文件类型: 固定为 “FLV” (3 bytes)
  • 版本信息: 一般为0x01 (1 byte)
  • 流信息: 0000 0101 此flv文件包含视音频, 0000 0001 此flv文件包含视频 0000 0100 包含音频 (1 byte)
  • 头长度: FLV文件头长度,一般为 3+1+1+4=9 bytes (4bytes)

FLV Body:

Body由一系列pre tag length 和 tag组成。

+----------------------------------------------------------------------+  
| Pre Tag Length | Tag Header | Tag Data | .... | Pre Tag Length | ... |
+----------------------------------------------------------------------+
  • Pre Tag Length: 前一个tag的长度 4 bytes
  • Tag Header: 1 + 3 + 3 +1 + 3 = 11 bytes

Tag:

tag header (11 bytes)

+-------------------------------------------------------------------------------------------------------------+
| Tag Type(1 byte) | Tag Data Length(3 bytes) | Timestap(3 bytes) | TimestapExt(1 byte)  |  StreamID(3 bytes) |
+-------------------------------------------------------------------------------------------------------------+
字段名长度取值
Tag Type
Tag 类型
1 byte0x08 音频
0x09 视频
0x12 脚本
Tag Data Length:
Tag Data 长度
3 bytes
Timestamp
时间戳(单位ms)
3 bytes
TimestampExt
扩展时间戳
1 byte
StreamID
流ID 总是0
3 bytes

tag data

tag data如果是音频数据

第一个byte记录audio信息。前4bits表示音频格式(全部格式请看官方文档):

hexcomment
0未压缩
1ADPCM
2MP3
4Nellymoser 16-kHz mono
5Nellymoser 8-kHz mono
10AAC

下面两个bits表示samplerate:

hexcomment
05.5KHz
111kHz
222kHz
344kHz

下面1bit表示采样长度:

hexcomment
0snd8Bit
1snd16Bit

下面1bit表示类型:

hexcomment
0sndMomo
1sndStereo

之后是数据。

tag data如果是视频数据

第一个byte记录video信息。前4bits表示视频帧类型:

hexcomment
1keyframe
2inner frame
3disposable inner frame (h.263 only)
4generated keyframe

后4bits表示解码器ID:

hexcomment
2seronson h.263
3screen video
4On2 VP6
5On2 VP6 with alpha channel
6Screen video version 2
7AVC (h.264)

FLV文件解析

数据说明
00 00 00 00前一个tag的长度,由于是第一个tag所以全为0.
12表示tag类型为脚本
00 00 F6表示tag data长度为0xF6个字节
00 00 00时间戳
00扩展时间戳
00 00 00流ID

Video Tag

Audio Tag

RTMP抓包的视频流:


RTMP视频流格式与flv很相似,就是video tag 和 audio tag的tag data一个接一个的发送(不含tag header 和 pre tag length)。

  • audio /video信息:1 字节 ,这里0x17 表示I帧 AVC
  • AVC packet type :1字节
    • 0x00:AVC Sequence Header
    • 0x01:AVC NALU
  • composition time : 3字节, AVC时无意义,全为0

当AVC packet type为AVC Sequence Header时:

接下来就是AVCDecoderConfigurationRecord的内容:

typelength(byte)valuecomment
configurationVersion10x01版本
AVCProfileIndication10x4dsps[1]
profile_compatibility10x00sps[2]
AVCLevelIndication10x2asps[3]
lengthSizeMinusOne10xffFLV中NALU包长数据所使用的字节数,
包长= (lengthSizeMinusOne & 3) + 1
numOfSequenceParameterSets10xe1SPS个数,通常为0xe1
个数= numOfSequenceParameterSets & 01F
sequenceParameterSetLength20x0014SPS长度
sequenceParameterSetNALUnitsSPS内容
numOfPictureParameterSets10x01PPS个数,通常为0x01
pictureParameterSetLength20x0004PPS长度
pictureParameterSetNALUnitsPPS内容

当AVC packet type为AVC NALU(0x01)时:

接下来就是NALU的格式,如下:

NALU length:(lengthSizeMinusOne & 3) + 1字节 NALU长度
NALU Data:
NALU length:
NALU Data:
…

NALU Data包含H264编码数据,详解如下:

H264 SPS(Sequence Parameter Set) 和PPS(Picture Parameter Set) 数据结构,有如下H264bitstream

/* h.264 bitstreams */
const uint8_t sps[] =
0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2; //0x00 00 00 01 或者 0x00 00 01 是分隔符
const uint8_t pps[] =
0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80;

代码中SPS 2进制如下:

SPS各位详述如下:

Parameter NameType
u:unsigned bit
ue:指数哥伦布码
ValueComments
forbidden_zero_bitu(1)0Despite being forbidden, it must be set to 0!
nal_ref_idcu(2)33 means it is “important” (this is an SPS)
nal_unit_typeu(5)7Indicates this is a sequence parameter set
profile_idcu(8)66Baseline profile
constraint_set0_flagu(1)0We’re not going to honor constraints
constraint_set1_flagu(1)0We’re not going to honor constraints
constraint_set2_flagu(1)0We’re not going to honor constraints
constraint_set3_flagu(1)0We’re not going to honor constraints
reserved_zero_4bitsu(4)0Better set them to zero
level_idcu(8)10Level 1, sec A.3.1
seq_parameter_set_idue(v)0We’ll just use id 0.
log2_max_frame_num_minus4ue(v)0Let’s have as few frame numbers as possible
pic_order_cnt_typeue(v)0Keep things simple
log2_max_pic_order_cnt_lsb_minus4ue(v)0Fewer is better.
num_ref_framesue(v)0We will only send I slices
gaps_in_frame_num_value_allowed_flagu(1)0We will have no gaps
pic_width_in_mbs_minus_1ue(v)7SQCIF is 8 macroblocks wide
pic_height_in_map_units_minus_1ue(v)5SQCIF is 6 macroblocks high
frame_mbs_only_flagu(1)1We will not to field/frame encoding
direct_8x8_inference_flagu(1)0Used for B slices. We will not send B slices
frame_cropping_flagu(1)0We will not do frame cropping
vui_prameters_present_flagu(1)0We will not send VUI data
rbsp_stop_one_bitu(1)1Stop bit. I missed this at first and it caused me much trouble.

贴几段代码:将RTSP回调的H264裸流转换并通过RTMP协议发送到客户端.

// pData:   H264裸流数据
// 将H264裸流转换成适用于RTMP协议的流并发送
void CRTMP::_DataCallBack( LONG nChannel, char* pData, LONG nSize, RTP_HEAD* pHead, RTP_FRAME_TYPE FrameType )

    char* p    = pData;
    char* pEnd = pData + nSize;
    if (m_bNeedUpdateMetadata)
       // 第一次收到I帧时,获取sps pps并保存
        if (FrameType != RTP_FRAME_TYPE_I)
        
            return;
        
        Nalu sps, pps;
        p = ReadOneNalu(pData, nSize, sps); ASSERT(p != NULL);
        p = ReadOneNalu(p, pEnd - p, pps);  ASSERT(p != NULL);
        h264_decode_sps((BYTE*)sps.data, sps.size, m_nWitdh, m_nHeight, m_nFps);

        m_MetaData.nSpsLen    = sps.size;
        m_MetaData.Sps        = (unsigned char*)calloc(sps.size, 1); memcpy(m_MetaData.Sps, sps.data, sps.size);
        m_MetaData.nPpsLen    = pps.size;
        m_MetaData.Pps        = (unsigned char *)calloc(pps.size, 1); memcpy(m_MetaData.Pps, pps.data, pps.size);
        m_MetaData.nWidth     = m_nWitdh;
        m_MetaData.nHeight    = m_nHeight;
        m_MetaData.nFrameRate = m_nFps;

        m_bNeedUpdateMetadata = false;
    
    unsigned int tick_gap = 1000 / m_MetaData.nFrameRate; // 1000ms/fps
    Nalu idr;
    while ((p = ReadOneNalu(p, pEnd - p, idr)) != NULL)
    
        if (idr.type == 0x07 || idr.type == 0x08)
           // 忽略重复的sps pps
            continue;
        

        if (!SendH264Packet(idr, m_tick)) 
        
            cout << "SendH264Packet Failed" << WSAGetLastError() << endl;
        
    

    m_tick += tick_gap; // 更新时间戳

// 从缓存读取一个nalu
// 返回指向下一个nalu起始位置的指针或者NULL
static char* ReadOneNalu(char* pBuf, int nSize, Nalu& NaluUnit)

    char Sep1[3], Sep2[4];
    Sep1[0] = (char)(0x1 >> 16 & 0xFF);
    Sep1[1] = (char)(0x1 >> 8 & 0xFF);
    Sep1[2] = (char)(0x1 & 0xFF);

    Sep2[0] = (char)(0x1 >> 24 & 0xFF);
    Sep2[1] = (char)(0x1 >> 16 & 0xFF);
    Sep2[2] = (char)(0x1 >> 8 & 0xFF);
    Sep2[3] = (char)(0x1 & 0xFF);

    bool bFindHead = false;
    bool bFindTail = false;
    char* pStart   = pBuf;
    char* pEnd     = pBuf + nSize;
_FIND:
    while (pStart <= pEnd - 4)
       // nalu 以00 00 01 或者00 00 00 01作为分隔符
        // look for head
        if (!bFindHead 
            &&(::memcmp(Sep1, pStart, 3) == 0))
        
            pStart       += 3;
            NaluUnit.data = pStart;
            NaluUnit.type = NaluUnit.data[0] & 0x1F;
            bFindHead     = true;
        
        else if (!bFindHead
            && (::memcmp(Sep2, pStart, 4) == 0))
        
            pStart       += 4;
            NaluUnit.data = pStart;
            NaluUnit.type = NaluUnit.data[0] & 0x1F;
            bFindHead     = true;
        
        // look for tail
        if (bFindHead)
        
            if (pEnd - pStart < 3)
            
                break;
            
            if (::memcmp(Sep1, pStart, 3) == 0)
            
                NaluUnit.size = pStart - NaluUnit.data;
                bFindTail     = true;
                break;
            
            else if (::memcmp(Sep2, pStart, 4) == 0)
            
                NaluUnit.size = pStart - NaluUnit.data;
                bFindTail     = true;
                break;
            
        

        ++pStart;
    
    if (!bFindTail)
    
        NaluUnit.size = pEnd - NaluUnit.data;
    

    if ((NaluUnit.type & 0x1F) == 0x06)
       // sei 跳过
        bFindHead = false;
        bFindTail = false;
        goto _FIND;
    

    return bFindHead ? NaluUnit.data + NaluUnit.size : NULL;

// 将nalu封包并发送
// 大端法存储
bool CRTMP::SendH264Packet(Nalu nalunit, unsigned int timestamp)

    CAMFBuffer buf(nalunit.size + 9);
    RTMP_Packet p;
    if (nalunit.type == 5)
    
        buf.WriteByte(0x17);    // I 帧
        buf.WriteByte(0x1);     // nalu
        buf.WriteInt24(0x0);
        buf.WriteInt32(nalunit.size);
        buf.WriteBuffer(nalunit.data, nalunit.size);
        SendSpsPpsInfo(m_MetaData.Pps, m_MetaData.nPpsLen, m_MetaData.Sps, m_MetaData.nSpsLen);
    
    else
    
        buf.WriteByte(0x27);    // p、b 帧
        buf.WriteByte(0x1);     // nalu
        buf.WriteInt24(0x0);
        buf.WriteInt32(nalunit.size);
        buf.WriteBuffer(nalunit.data, nalunit.size);
    

    return SendPacket(buf, 0x9, timestamp);

// 发送sps,pps信息
bool CRTMP::SendSpsPpsInfo(unsigned char *pps,int pps_len,unsigned char * sps,int sps_len)
    
        CAMFBuffer buffer(pps_len + sps_len + 16);
        buffer.WriteByte(0x17);
        buffer.WriteByte(0x0);  // AVCSequenceHeader
        buffer.WriteInt24(0x0); // composition time
        buffer.WriteByte(0x01);
        buffer.WriteByte(sps[1]);
        buffer.WriteByte(sps[2]);
        buffer.WriteByte(sps[3]);
        buffer.WriteByte((char)0xFF);       // lengthSizeMinusOne NALU包长所用字节数=(lengthSizeMinusOne & 3) + 1
        buffer.WriteByte((char)0xE1);       // numOfSequenceParameterSet SPS个数=numOfSequenceParameterSet & 0x1F
        buffer.WriteInt16(sps_len);         // SPS 长度
        buffer.WriteBuffer((char*)sps, sps_len);
        buffer.WriteByte((char)0x01);       // numOfPictureParameterSet PPS个数=numOfPictureParameterSet & 0x1F
        buffer.WriteInt16(pps_len);         // PPS 长度
        buffer.WriteBuffer((char*)pps, pps_len);

        return SendPacket(buffer, 0x9, 0);
    

以上是关于RTMP视频流格式解析的主要内容,如果未能解决你的问题,请参考以下文章

rtmp直播视频流播放器(ckplayer)使用方法

我们可以只通过 RTMP 流式传输 Flash 视频吗?

流媒体:RTMP 协议完全解析

RTMP 视频数据格式

如何在移动浏览器上通过 RTMP 显示 H.264 格式的视频直播?

前端页面播放 rtmp 流与 flv 格式视频文件