您的位置:首页 > 其它

RTMP协议分析及H.264打包原理

2017-01-13 14:25 1096 查看
转自:/Uploads/Images/Content/201910/29/7f98da3b4b0fcd471f6c6fd28ae48cbe

[align=center]RTMP协议分析及H.264打包原理
[/align]
 RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。RTMP是一种设计用来进行实时数据通信的网络协议,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。

RTMP协议是一个互联网五层体系结构中应用层的协议。RTMP协议中基本的数据单元称为消息(Message)。当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为块(Chunk)。

一.定义

Payload(载荷):包含于一个数据包中的数据,例如音频采样或者视频压缩数据。

Packet(数据包):一个数据包由固定头和载荷数据构成。一些底层协议可能会要求对数据包进行封装。

Port(端口):TCP/IP使用小的正整数对端口进行标识。OSI传输层使用的运输选择器 (TSEL) 相当于端口。

Transport address(传输地址):用以识别传输层端点的网络地址和端口的组合,例如一个IP地址和一个TCP端口。

Message stream(消息流):通信中消息流通的一个逻辑通道。

Message stream ID(消息流ID):每个消息有一个关联的ID,使用ID可以识别出该消息属于哪个消息流。

Chunk(块):消息的一段。消息在网络发送之前被拆分成很多小的部分。块按照时间戳的顺序进行端到端的传输。

Chunk stream(块流):通信中允许块流向一个特定方向的逻辑通道。块流可以从客户端流向服务器,也可以从服务器流向客户端。

Chunk stream ID(块流 ID):每个块有一个关联的ID,使用ID可以识别出该块属于哪个块流。

Multiplexing(合成):将独立的音频/视频数据合成为一个连续的音频/视频流,这样就可以同时发送视频和音频了。

DeMultiplexing(分解):Multiplexing 的逆向处理,将交叉的音频和视频数据还原成原始音频和视频数据的格式。

Remote Procedure Call(RPC 远程方法调用):允许客户端或服务器调用对端的一个子程序或者程序的请求。

Metadata(元数据):关于数据的描述。比如电影的 metadata 包括电影标题、持续时间、创建时间等等。

Application Instance (应用实例):应用实例运行于服务器上,客户端可连接这个实例并发送连接请求,连接服务器。

Action Message Format (AMF,操作消息格式):AMF是Adobe独家开发出来的通信协议,它采用二进制压缩,序列化、反序列化、传输数据,从而为Flash 播放器与Flash Remoting网关通信提供了一种轻量级的、高效能的通信方式。如下图所示。



AMF的初衷只是为了支持Flash ActionScript的数据类型,目前有两个版本:AMF0和AMF3。AMF从Flash MX时代的AMF0发展到现在的AMF3。AMF3用作Flash Playe 9的ActionScript 3.0的默认序列化格式,而AMF0则用作旧版的ActionScript 1.0和2.0的序列化格式。在网络传输数据方面,AMF3比AMF0更有效率。AMF3能将int和uint对象作为整数(integer)传输,并且能序列化
ActionScript 3.0才支持的数据类型, 比如ByteArray,XML和Iexternalizable。

二.握手

握手以客户端发送C0和C1块开始。

客户端必须等待接收到S1才能发送C2。

客户端必须等待接收到S2才能发送任何其他数据。

服务器端必须等待接收到C0才能发送S0和S1,也可以等待接收到C1再发送S0和S1。服务器端必须等待接收到C1才能发送S2。服务器端必须等待接收到C2才能发送任何其他数据。

1.C0和S0格式

C0和S0都是八位,即一个字节,如下所示:



Version(8bits):在C0中,它表示客户端的RTMP版本;在S0中,它表示服务器端的RTMP版本。RTMP规范目前将它定义为3。0—2用于早期的产品,已经被废弃。4—31保留,用于RTMP未来版本。32—255禁止使用。

2.C1和S1格式

C1和S1都是1536个字节,如下所示:



time(4字节):包含时间戳,该时间戳应该被用做本终端发送的块的起点。该字段可以为0或者其他任意值。

zero(4字节):该字段必须为0。

random data(1528字节):该字段可以包含任意值。终端需要区分出是它发起的握手还是对端发起的握手,所以这该字段应该发送一些足够随机的数。

3.C2和S2格式

C2和S2也都是1536个字节,几乎是C1和S1的副本,如下所示:



time(4字节):包含时间戳,必须与C1或S1中的时间戳相同。

time2(4字节):包含时间戳,必须与前一个C1或S1中的时间戳相同。

random echo(1528字节):该字段必须与S1或者S2中的随机数相同。

4.握手示意图



三.块

握手之后,连接开始对一个或多个块流进行合并。每个块都有一个唯一ID对其进行关联,这个ID叫做chunk stream ID(块流ID)。这些块通过网络进行传输,在发送端,每个块必须被完全发送才可以发送下一块。在接收端,这些块根据块流ID被组装成消息。

每个块都是由块头和块数据体组成,而块头自身也是由三部分组成,块格式如下所示:



Basic Header(基本头,1—3字节):该字段编码了块流ID和块类型。块类型决定了Message Header(消息头)的编码格式。该字段长度完全取决于块流ID,因为块流ID是一个可变长度的字段。

Message Header(消息头,0、3、7或11字节):该字段编码了消息的相关信息,标识了块数据所属的消息。该字段的长度由块类型决定。

Extended Timestamp(扩展时间戳,0或4字节):该字段只在特定情况下出现。

Chunk Data(块数据,可变大小):块的载荷部分,取决于配置的最大块尺寸,一般为128字节。

1.Basic Header

块基本头对块类型(用fmt 字段表示,参见下图) 和块流ID(chunk stream ID)进行编码。fmt字段占2bits,取值范围时0—3。RTMP协议最多支持65597个流,流的ID范围是3—65599。ID值0、1和2被保留,0表示两字节形式,1表示三字节形式,2的块流ID被保留,用于下层协议控制消息和命令。

☆一字节形式



ID取值范围3—63,0、1和2用于标识多字节形式。

☆两字节形式



ID取值范围64—319,即第二个字节+64。

☆三字节形式



ID取值范围64—68899,即第三个字节*256+第二个字节+64。

2.Message Header

有四种类型的块消息头,由块基本头中的“fmt”字段决定。

☆类型0

由11个字节组成,必须用于块流的起始块或者流时间戳重置的时候。



timestamp(3字节):消息的绝对时间戳,如果大于等于16777215(0xFFFFFF),该字段仍为16777215,此时Extend Timestamp(扩展时间戳)字段存在,用于对溢出值进行扩展。否则,该字段标识整个时间戳,不需要扩展。

message length(3字节):通常与块载荷的长度不同,块载荷长度通常表示块的最大长度128字节(除了最后一个块)和最后一个块的剩余空间。

message type id(1字节):消息类型。

message stream id(4字节):该字段用小端模式保存。

☆类型1

由7个字节组成,不包括message stream ID(消息流ID),此时块与之前的块取相同的消息流ID。可变长度消息的流(例如,一些视频格式)应该在第一块之后使用这一格式表示之后的每个新块。



timestamp delta(3字节):前一个块时间戳与当前块时间戳的差值,即相对时间戳,如果大于等于16777215(0xFFFFFF),该字段仍为16777215,此时Extend Timestamp(扩展时间戳)字段存在,用于对溢出值进行扩展。否则,该字段标识整个差值,不需要扩展。

message length(3字节):通常与块载荷的长度不同,块载荷长度通常表示块的最大长度(除了最后一个块)和最后一个块的剩余空间。该长度是指为块载荷AMF编码后的长度。

message type id(1字节):消息类型。

☆类型2

由3个字节组成,既不包括message stream ID(消息流ID),也不包括message length(消息长度),此时块与之前的块取相同的消息流ID和消息长度。固定长度消息的流(例如,一些音频格式)应该在第一块之后使用这一格式表示之后的每个新块。



timestamp delta(3字节):前一个块时间戳与当前块时间戳的差值,如果大于等于16777215(0xFFFFFF),该字段仍为16777215,此时Extend Timestamp(扩展时间戳)字段存在,用于对溢出值进行扩展。否则,该字段标识整个差值,不需要扩展。

☆类型3

没有消息头,从之前具有相同块流ID的块中取相应的值。当一条消息被分割成多个块时,所有的块(除了第一个块)应该使用这种类型。

3.Extended timestamp(3字节)

只有当块消息头中的普通时间戳设置为0x00ffffff时,本字段才被传送。如果普通时间戳的值小于0x00ffffff,那么本字段一定不能出现。如果普通时间戳字段不出现本字段也一定不能出现。

4.例子

☆不分割消息





从上面两个表中可以看出,从消息3开始,数据处理得到优化,此时Chunk除了载荷以外,只多了一个块基本头。

☆分割消息





当消息的载荷长度超过128字节时,需要将消息分割为若干个块进行传输。

从上面两个例子可以看出,块类型3有两种用法。一个是指定一个新消息的消息头可以派生自已经存在的状态数据(例一),另一个是指定消息的连续性(例二)。

四.消息

消息是RTMP协议中基本的数据单元。不同种类的消息包含不同的Message Type ID,代表不同的功能。RTMP协议中一共规定了十多种消息类型,分别发挥着不同的作用。例如,Message Type ID在1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需操作其中的数据。Message Type ID为8,9的消息分别用于传输音频和视频数据。Message Type ID为15-20的消息用于发送AMF编码的命令,负责用户与服务器之间的交互,比如播放,暂停等等。

1.消息头

消息头(Message Header)有四部分组成:标志消息类型的Message Type ID,标志载荷长度的Payload Length,标识时间戳的Timestamp,标识消息所属媒体流的Stream ID。消息的格式如下所示。



2.载荷

载荷是消息包含的实际数据,它们可能是音频采样数据或者是视频压缩数据。

由于端与端之间实际传输的是块,所以只需要将载荷加上块头封装成块。实际应用中,无扩展时间戳,一字节形式的块基本头就能满足要求,整个块头满足以下四种长度:

fmt=0:Basic Head+Message Head=1+11=12

fmt=1:Basic Head+Message Head=1+7=8

fmt=2:Basic Head+Message Head=1+3=4

fmt=3:Basic Head+Message Head=1+0=1

需要注意的是,当载荷为H.264数据时,要使用AMF3进行编码(即序列化),关于AMF3可以参考:AMF3中文版

五.打包H.264

如果整个打包过程都自己弄,是非常繁琐的,还好网上有大神开源了RTMP库,这里使用librtmp进行H.264数据的打包推送。

librtmp的编译可以参考:Win7(Windows 7)下用VS2012(Visual Studio 2012)编译librtmp

使用librtmp时,解析RTMP地址、握手、建立流媒体链接和AMF编码这块我们都不需要关心,但是数据是如何打包并通过int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue) 函数推送的还是得学习一下。

RTMPPacket类型的结构体定义如下,一个RTMPPacket对应RTMP协议规范里面的一个块(Chunk)

typedef struct RTMPPacket    
  {    
    uint8_t m_headerType;//块消息头的类型(4种)    
    uint8_t m_packetType;//消息类型ID(1-7协议控制;8,9音视频;10以后为AMF编码消息)    
    uint8_t m_hasAbsTimestamp;  //时间戳是绝对值还是相对值    
    int m_nChannel;         //块流ID    
    uint32_t m_nTimeStamp;  //时间戳  
    int32_t m_nInfoField2;  //last 4 bytes in a long header,消息流ID     
    uint32_t m_nBodySize;   //消息载荷大小    
    uint32_t m_nBytesRead;  //暂时没用到  
    RTMPChunk *m_chunk;     //<span style="font-family: Arial, Helvetica, sans-serif;">暂时没用到</span>  
    char *m_body;           //消息载荷,可分割为多个块载荷  
  } RTMPPacket;   

一些定义

[cpp]
view plain
copy





#define RTMP_DEFAULT_CHUNKSIZE  128//默认块大小  
  
#define RTMP_BUFFER_CACHE_SIZE (16*1024)//开辟16K字节空间  
  
#define RTMP_PACKET_TYPE_AUDIO 0x08//音频的消息类型  
#define RTMP_PACKET_TYPE_VIDEO 0x09//视频的消息类型  
  
#define RTMP_MAX_HEADER_SIZE 18//块基本头+块消息头+扩展时间戳=3+11+4=18  
  
#define RTMP_PACKET_SIZE_LARGE    0//块消息头类型0  
#define RTMP_PACKET_SIZE_MEDIUM   1//块消息头类型1  
#define RTMP_PACKET_SIZE_SMALL    2//块消息头类型2  
#define RTMP_PACKET_SIZE_MINIMUM  3//块消息头类型3  

RTMP_SendPacket函数

[cpp]
view plain
copy





//queue:TRUE为放进发送队列,FALSE是不放进发送队列,直接发送  
int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue)    
{    
  const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel];    
  uint32_t last = 0;//上一个块的时间戳  
  int nSize;//消息载荷大小,可分割为多个块载荷大小  
  int hSize;//块头大小  
  int cSize;//块基本头大小增量  
  char *header;//指向块头起始位置    
  char *hptr;  
  char *hend;//指向块头结束位置   
  char hbuf[RTMP_MAX_HEADER_SIZE];   
  char c;    
  uint32_t t;//相对时间戳    
  char *buffer;//指向消息载荷  
  char *tbuf = NULL;  
  char *toff = NULL;    
  int nChunkSize;//块载荷大小   
  int tlen;    
  //不是完整块消息头(即不是11字节的块消息头)    
  if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)    
  {      
      //前一个块和这个块对比  
      //原理参考 例子—不分割消息  
      if (prevPacket->m_nBodySize == packet->m_nBodySize    
      && prevPacket->m_packetType == packet->m_packetType    
      && packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM)    
    packet->m_headerType = RTMP_PACKET_SIZE_SMALL;    
      //原理参考 例子—分割消息  
      if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp    
      && packet->m_headerType == RTMP_PACKET_SIZE_SMALL)    
    packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;    
      //上一个块的时间戳  
      last = prevPacket->m_nTimeStamp;    
  }    
  //非法  
  if (packet->m_headerType > 3)  
  {    
      RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.",    
      (unsigned char)packet->m_headerType);    
      return FALSE;    
  }    
  //nSize暂时设置为块头大小;packetSize[] = { 12, 8, 4, 1 }    
  nSize = packetSize[packet->m_headerType];   
  //块头大小初始化  
  hSize = nSize;  
  cSize = 0;    
  //相对时间戳,当块时间戳与上一个块时间戳的差值  
  t = packet->m_nTimeStamp - last;    
    
  if (packet->m_body)    
  {    
   
      //m_body是指向载荷数据首地址的指针,“-”号用于指针前移   
      //header:块头起始位置   
      header = packet->m_body - nSize;    
      //hend:块头结束位置  
      hend = packet->m_body;    
  }    
  else    
  {    
      header = hbuf + 6;    
      hend = hbuf + sizeof(hbuf);    
  }    
  //当块流ID大于319时    
  if (packet->m_nChannel > 319)    
    //块基本头是3个字节    
    cSize = 2;    
  //当块流ID大于63时    
  else if (packet->m_nChannel > 63)    
    //块基本头是2个字节    
    cSize = 1;    
  if (cSize)    
  {    
      //header指针指块头起始位置,“-”号用于指针前移   
      header -= cSize;    
      //当cSize不为0时,块头需要进行扩展,默认的块基本头为1字节,但是也可能是2字节或3字节  
      hSize += cSize;    
  }    
  //如果块消息头存在,且相对时间戳大于0xffffff,此时需要使用ExtendTimeStamp    
  if (nSize > 1 && t >= 0xffffff)    
  {    
      header -= 4;    
      hSize += 4;    
  }    
    
  hptr = header;    
  //把块基本头的fmt类型左移6位。   
  c = packet->m_headerType << 6;    
  switch (cSize)    
  {    
    //把块基本头的低6位设置成块流ID  
    case 0:    
      c |= packet->m_nChannel;    
      break;    
    //同理,但低6位设置成000000    
    case 1:    
      break;    
    //同理,但低6位设置成000001    
    case 2:    
      c |= 1;    
      break;    
  }    
  //可以拆分成两句*hptr=c;hptr++,此时hptr指向第2个字节    
  *hptr++ = c;    
  //cSize>0,即块基本头大于1字节    
  if (cSize)    
  {    
    //将要放到第2字节的内容tmp    
      int tmp = packet->m_nChannel - 64;    
    //获取低位存储于第2字节    
      *hptr++ = tmp & 0xff;    
    //块基本头是最大的3字节时    
      if (cSize == 2)    
    //获取高位存储于第三个字节(注意:排序使用大端序列,和主机相反)    
    *hptr++ = tmp >> 8;    
  }    
  //块消息头一共有4种,包含的字段数不同,nSize>1,块消息头存在。    
  if (nSize > 1)    
  {    
      //块消息头的最开始三个字节为时间戳,返回值hptr=hptr+3  
      hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);    
  }    
  //如果块消息头包括MessageLength+MessageTypeID(4字节)    
  if (nSize > 4)    
  {    
      //消息长度,为消息载荷AMF编码后的长度   
      hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);    
      //消息类型ID  
      *hptr++ = packet->m_packetType;    
  }    
  //消息流ID(4字节)    
  if (nSize > 8)    
    hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);    
      
  //如果块消息头存在,且相对时间戳大于0xffffff,此时需要使用ExtendTimeStamp     
  if (nSize > 1 && t >= 0xffffff)    
    hptr = AMF_EncodeInt32(hptr, hend, t);    
  //消息载荷大小   
  nSize = packet->m_nBodySize;    
  //消息载荷指针  
  buffer = packet->m_body;    
  //块大小,默认128字节    
  nChunkSize = r->m_outChunkSize;    
    
  RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_sb.sb_socket,    
      nSize);     
  //使用HTTP    
  if (r->Link.protocol & RTMP_FEATURE_HTTP)    
  {    
    //nSize:消息载荷大小;nChunkSize:块载荷大小   
    //例nSize:307,nChunkSize:128;    
    //可分为(307+128-1)/128=3个    
    //为什么减1?因为除法会只取整数部分!    
    int chunks = (nSize+nChunkSize-1) / nChunkSize;    
    //如果块的个数超过一个    
    if (chunks > 1)    
    {    
    //消息分n块后总的开销:    
    //n个块基本头,1个块消息头,1个消息载荷,这里是没有扩展时间戳的情况    
    //实际中只有第一个块是完整的,剩下的只有块基本头   
    tlen = chunks * (cSize + 1) + nSize + hSize;//这里其实多算了一个块基本头    
    //分配内存    
    tbuf = (char *) malloc(tlen);    
    if (!tbuf)    
       return FALSE;    
    toff = tbuf;    
    }    
  }    
  while (nSize + hSize)    
  {    
      int wrote;    
      //消息载荷小于块载荷(不用分块)    
      if (nSize < nChunkSize)    
      nChunkSize = nSize;    
    
      RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)header, hSize);    
      RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)buffer, nChunkSize);    
      if (tbuf)    
      {     
        memcpy(toff, header, nChunkSize + hSize);    
        toff += nChunkSize + hSize;    
      }    
      else    
      {    
        wrote = WriteN(r, header, nChunkSize + hSize);    
        if (!wrote)    
          return FALSE;    
      }    
      //消息载荷长度块载荷长度    
      nSize -= nChunkSize;    
      //Buffer指针前移1个块载荷长度    
      buffer += nChunkSize;    
      hSize = 0;    
          
      //如果消息没有发完    
      if (nSize > 0)    
      {    
        header = buffer - 1;    
        hSize = 1;    
        if (cSize)    
        {    
          header -= cSize;    
          hSize += cSize;    
        }    
        //块基本头第1个字节    
        *header = (0xc0 | c);    
        //如果块基本头大于1字节    
        if (cSize)    
        {    
          int tmp = packet->m_nChannel - 64;    
          header[1] = tmp & 0xff;    
          if (cSize == 2)    
          header[2] = tmp >> 8;    
        }    
       }    
  }    
  if (tbuf)    
  {    
      int wrote = WriteN(r, tbuf, toff-tbuf);    
      free(tbuf);    
      tbuf = NULL;    
      if (!wrote)    
        return FALSE;    
  }    
    
  /* we invoked a remote method */    
  if (packet->m_packetType == 0x14)    
  {    
      AVal method;    
      char *ptr;    
      ptr = packet->m_body + 1;    
      AMF_DecodeString(ptr, &method);    
      RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val);    
      /* keep it in call queue till result arrives */    
      if (queue)   
      {    
        int txn;    
        ptr += 3 + method.av_len;    
        txn = (int)AMF_DecodeNumber(ptr);    
        AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn);    
      }    
  }    
    
  if (!r->m_vecChannelsOut[packet->m_nChannel])    
    r->m_vecChannelsOut[packet->m_nChannel] = (RTMPPacket *) malloc(sizeof(RTMPPacket));    
  memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));    
  return TRUE;    
}   

现在要解决的是如何给结构体RTMPPacket中的消息载荷m_body赋值,即如何将H.264的NALU打包进消息载荷。

1.sps和pps的打包

sps和pps是需要在其他NALU之前打包推送给服务器。由于RTMP推送的音视频流的封装形式和FLV格式相似,向FMS等流媒体服务器推送H264和AAC直播流时,需要首先发送"AVC sequence header"和"AAC sequence header"(这两项数据包含的是重要的编码信息,没有它们,解码器将无法解码),因此这里的"AVC sequence header"就是用来打包sps和pps的。

AVC sequence header其实就是AVCDecoderConfigurationRecord结构,该结构在标准文档“ISO/IEC-14496-15:2004”的5.2.4.1章节中有详细说明,如下所示:



用表格表示如下:



FLV 是一个二进制文件,简单来说,其是由一个文件头(FLV header)和很多 tag 组成(FLV body)。tag 又可以分成三类: audio, video, script,分别代表音频流,视频流,脚本流,而每个 tag 又由 tag header 和 tag data 组成。

然后参照“Video File Format Specification Version 10”中The FLV File Format的Video tags章节,如下所示:



上表中tag header为两个4bits,即一个字节,其他的是tag data。inter frame即P frame。



AVC时,3字节CompositionTime无意义,通常设置为0。

AVCDecoderConfigurationRecord结构的表格中可以看出,由于NALUnitLength-1=3,因此每个NALU包都有NALUnitLength=4个字节来描述它的长度。这4个字节需要添加到每个NALU的前面,因此上表中Data的结构实际上如下所示:





一个典型的打包示例如下所示:

[cpp]
view plain
copy





body = (unsigned char *)packet->m_body;  
i = 0;  
body[i++] = 0x17;// 1:Iframe  7:AVC ,元数据当做keyframe发送</span>  
body[i++] = 0x00;  
  
body[i++] = 0x00;  
body[i++] = 0x00;  
body[i++] = 0x00;  
  
//AVCDecoderConfigurationRecord  
body[i++] = 0x01;  
body[i++] = sps[1];  
body[i++] = sps[2];  
body[i++] = sps[3];  
body[i++] = 0xff;  
  
/*sps*/  
body[i++]   = 0xe1;  
body[i++] = (sps_len >> 8) & 0xff;  
body[i++] = sps_len & 0xff;  
memcpy(&body[i],sps,sps_len);  
i +=  sps_len;  
  
/*pps*/  
body[i++]   = 0x01;  
body[i++] = (pps_len >> 8) & 0xff;  
body[i++] = (pps_len) & 0xff;  
memcpy(&body[i],pps,pps_len);  
i +=  pps_len;  

2.其它NALU的打包
一个典型的打包示例如下所示:

[cpp]
view plain
copy





int i = 0;   
if(bIsKeyFrame)  
{    
    body[i++] = 0x17;// 1:Iframe  7:AVC     
    body[i++] = 0x01;// AVC NALU     
    body[i++] = 0x00;    
    body[i++] = 0x00;    
    body[i++] = 0x00;    
  
  
    // NALU size     
    body[i++] = size>>24 &0xff;    
    body[i++] = size>>16 &0xff;    
    body[i++] = size>>8 &0xff;    
    body[i++] = size&0xff;  
    // NALU data     
    memcpy(&body[i],data,size);    
}  
else  
{    
    body[i++] = 0x27;// 2:Pframe  7:AVC     
    body[i++] = 0x01;// AVC NALU     
    body[i++] = 0x00;    
    body[i++] = 0x00;    
    body[i++] = 0x00;    
  
  
    // NALU size     
    body[i++] = size>>24 &0xff;    
    body[i++] = size>>16 &0xff;    
    body[i++] = size>>8 &0xff;    
    body[i++] = size&0xff;  
    // NALU data     
    memcpy(&body[i],data,size);    


一个具体的例子:Qt基于librtmp推送H.264

参考链接:

http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf

http://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: