1. 程式人生 > >RTMP視訊流格式解析

RTMP視訊流格式解析

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 byte
                0x08    音訊
                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表示音訊格式(全部格式請看官方文件):
hex comment
0 未壓縮
1 ADPCM
2 MP3
4 Nellymoser 16-kHz mono
5 Nellymoser 8-kHz mono
10 AAC

下面兩個bits表示samplerate:

hex comment
0 5.5KHz
1 11kHz
2 22kHz
3 44kHz

下面1bit表示取樣長度:

hex comment
0 snd8Bit
1 snd16Bit

下面1bit表示型別:

hex comment
0 sndMomo
1 sndStereo

之後是資料。

        如果是視訊資料,第一個byte記錄video資訊:
hex comment
1 keyframe
2 inner frame
3 disposable inner frame (h.263 only)
4 generated keyframe

後4bits表示解碼器ID:

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

這裡寫圖片描述

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 type1位元組
            0x00: AVC Sequence Header
            0x01: AVC NALU
    composition time3位元組, AVC時無意義,全為0
當AVC packet type為AVC Sequence Header時,接下來就是AVCDecoderConfigurationRecord的內容:
type length(byte) value comment
configurationVersion 1 0x01 版本
AVCProfileIndication 1 0x4d sps[1]
profile_compatibility 1 0x00 sps[2]
AVCLevelIndication 1 0x2a sps[3]
lengthSizeMinusOne 1 0xff FLV中NALU包長資料所使用的位元組數,包長= (lengthSizeMinusOne & 3) + 1
numOfSequenceParameterSets 1 0xe1 SPS個數,通常為0xe1 個數= numOfSequenceParameterSets & 01F
sequenceParameterSetLength 2 0x0014 SPS長度
sequenceParameterSetNALUnits SPS內容
numOfPictureParameterSets 1 0x01 PPS個數,通常為0x01
pictureParameterSetLength 2 0x0004 PPS長度
pictureParameterSetNALUnits PPS內容

當AVC packet type為AVC NALU(0x01)時:
audio / video 資訊:1位元組
AVC packet type:AVC NALU
後跟1個或多個NALU
composition time:3位元組,AVC時無意義,全為0
NALU length:(lengthSizeMinusOne & 3) + 1位元組 NALU長度
NALU Data:
NALU length:
NALU Data:

NALU:

H264 SPS 和PPS 資料結構
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};
const uint8_t pps[] =
{0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80};

0x00 00 00 01 或者 0x 00 00 01 作為分隔符

SPS 2進位制:

這裡寫圖片描述

u:unsigned bit
ue:指數哥倫布碼

Parameter Name Type Value Comments
forbidden_zero_bit u(1) 0 Despite being forbidden, it must be set to 0!
nal_ref_idc u(2) 3 3 means it is “important” (this is an SPS)
nal_unit_type u(5) 7 Indicates this is a sequence parameter set
profile_idc u(8) 66 Baseline profile
constraint_set0_flag u(1) 0 We’re not going to honor constraints
constraint_set1_flag u(1) 0 We’re not going to honor constraints
constraint_set2_flag u(1) 0 We’re not going to honor constraints
constraint_set3_flag u(1) 0 We’re not going to honor constraints
reserved_zero_4bits u(4) 0 Better set them to zero
level_idc u(8) 10 Level 1, sec A.3.1
seq_parameter_set_id ue(v) 0 We’ll just use id 0.
log2_max_frame_num_minus4 ue(v) 0 Let’s have as few frame numbers as possible
pic_order_cnt_type ue(v) 0 Keep things simple
log2_max_pic_order_cnt_lsb_minus4 ue(v) 0 Fewer is better.
num_ref_frames ue(v) 0 We will only send I slices
gaps_in_frame_num_value_allowed_flag u(1) 0 We will have no gaps
pic_width_in_mbs_minus_1 ue(v) 7 SQCIF is 8 macroblocks wide
pic_height_in_map_units_minus_1 ue(v) 5 SQCIF is 6 macroblocks high
frame_mbs_only_flag u(1) 1 We will not to field/frame encoding
direct_8x8_inference_flag u(1) 0 Used for B slices. We will not send B slices
frame_cropping_flag u(1) 0 We will not do frame cropping
vui_prameters_present_flag u(1) 0 We will not send VUI data
rbsp_stop_one_bit u(1) 1 Stop 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);
    }