H264 推流到RTMP服务器

参考连接:
基于libRTMP的流媒体直播之 AAC、H264 推送:
http://billhoo.blog.51cto.com/2337751/1557646/
使用librtmp进行H264与AAC直播
http://www.codeman.net/2014/01/439.html
雷博CSDN博客

下面是我最近了解PUST H264流到RTMP服务器上的一些笔记,参考了上面的链接,看别人的不如自己动手实践一下,以下便是实验过程记录。

一、FLV结构简析

关于FLV的结构,雷博的博文已经详细说明了,在这里可以参考一张图片
H264 推流到RTMP服务器_第1张图片
由上面的图片可以看出,FLV的整体结构为Header + Body. 其中Body则由 Tag + PreviousTagSizeN的方式组成,每一个Tag由Tag Header + Tag Data组成。
其中,在Video_File_Format_Specification_V10.pdf中,对每一个TAG的Tag Data有了更详细的说明(暂时只关注视频tag):
H264 推流到RTMP服务器_第2张图片
H264 推流到RTMP服务器_第3张图片

关于上面对Tag Data结构的说明,以前在做本地FLV视频PUSH到RTMP的测试中,并没有关注,只是直接将Tag Data封包进RTMP Packet里面发送出去即可。

二、H264结构简析

H264的码流结构主要由SPS、 PPS、 IDR 帧(包含一个或多个 I-Slice)、 P 帧(包含一个或多个P-Slice)、 B 帧(包含一个或多个 B-Slice)等部分组成。

关于SPS和PPS,引用以下:

  • 将一个视频序列 (从 IDR 帧开始到下一个 IDR 帧之前的数据称为一个视频序列) 全部图像的共同特征抽取出来,放在 SPS 语法单元中。
  • 将各个图像的典型特征抽取出来,放在 PPS 语法单元中。
  • 只有视频序列之间才能切换 SPS,即只有 IDR 帧的第一个 slice 可以切换 SPS。
  • 只有图像之间才能切换 PPS,即只有每帧图像的第一个 slice 才能切换 PPS

H.264 的所有语法结构最终都被封装成 nalu。码流中的 nalu单元必须定义合适的分隔符,否则无法区分,因此采用前缀码“00 00 01”或者“00 00 00 01”来区分每一个nalu单元。在每个前缀码后面紧跟的一个字节为nalu的语法结构,由三部分组成forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)。
forbidden_bit:禁止位。
nal_reference_bit:当前NAL的优先级,值越大,该NAL越重要。
nal_unit_type :NAL类型。
其中,我们着重需要关注的是nal_unit_type ,因为它代表了这个NALU的具体荷载数据的类型,我们可以通过 nal_unit_type & 0x1f的方式来获取其类型,具体定义如下:
H264 推流到RTMP服务器_第4张图片

三、H264流的封装

在PUSH FLV到RTMP服务器的时候,我们只需将FLV 的Tag Data封装进RTMP Packet,然后调用RTMP_SendPacket()函数将数据发送出去。
因此,在将H264 PUSH给RTMP服务器的时候,我们也需要按照FLV的格式将H264数据进行封包。因此我们需要构造视频Tag,并且我们在发送第一包数据前,需要构造“AVC Sequence Header”,用于告诉RTMP服务器解码相关的信息。
再次呈上上面的两幅图:
H264 推流到RTMP服务器_第5张图片
H264 推流到RTMP服务器_第6张图片

上面两幅图为FLV 每一个TAG 中的Tag Data里面的组成,因此我们需要根据上面的结构构造视频同步包 和 H264码流包

1)视频同步包的构造[AVC Sequence Header]

视频同步包的构造,其实是需要我们将H264中的SPS PPS按照一定的格式发给RTMP服务器,供RTMP服务器解码器进行解码。根据上面的图片可知,当构造视频同步包的时候,各个结构如下:

VIDEODATA
FrameType == 1
CodecID == 7
VideoData == AVCVIDEOPACKET

AVCVIDEOPACKET
AVCPacketType == 0x00
CompositionTime == 0x000000
Data == AVCDecoderConfigurationRecord

接下来继续构造AVCDecoderConfigurationRecord,参考上面链接,AVCDecoderConfigurationRecord相关结构如下:

H264 推流到RTMP服务器_第7张图片

因此我们需要将H264中的SPS PPS信息按照上面的结构进行填充,代码如下:

int send_sps_pps(unsigned char* index_body,int index_body_len)
{
    unsigned char *sps,*pps;
    int sps_temp,pps_temp;
    int sps_len,pps_len;
    int i,temp;
    unsigned char body[1024];
    memset(body,0,1024);

    /* find *sps *pps */
    temp = find_sps_pps(index_body,index_body_len,&sps_temp,&pps_temp,&sps_len,&pps_len);
    if(temp < 0)
        return -1;
    sps = index_body + sps_temp;
    pps = index_body + pps_temp;

    /*去掉帧界定符*/ 
    sps += 4;
    pps += 4;
    sps_len -= 4;
    pps_len -= 4;

    /*AVC head*/
    i = 0;
    body[i++] = 0x17;
    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;

    /*send rtmp packet*/
    if(rtmp_packet_send(body,i,RTMP_PACKET_TYPE_VIDEO,0) < 0)
        return -2;
    return temp;
}

2)H264普通数据包的构建

构建了上面的视频同步包后,我们需要将H264的荷载的数据即H264的I P B等数据封包进去。根据上面的图片,结构信息如下:

VIDEODATA
FrameType ==(I frame ? 1 :2)
CodecID == 7
VideoData == AVCVIDEOPACKET

AVCVIDEOPACKET
AVCPacketType == 0x01
CompositionTime == 0x000000
Data == NALU

上面的Data == H264 NALU Size + NALU Raw Data

代码如下:

int send_rtmp_video(unsigned char* index_body,int index_body_len,unsigned int timestamp)
{
    unsigned char *body;
    int type;

    /*去掉帧界定符*/  
    if (index_body[2] == 0x00) { /*00 00 00 01*/  
            index_body += 4;  
            index_body_len -= 4;  
    } else if (index_body[2] == 0x01){ /*00 00 01*/  
            index_body += 3;  
            index_body_len -= 3;  
    }  

    /*申请发送数据空间*/
    body = (unsigned char*)malloc(index_body_len+9); 

     /*send video packet*/   
    memset(body,0,index_body_len+9);  

    /*key frame*/  
    body[0] = 0x27;  
    type = index_body[0]&0x1f;  
    if (type == NAL_SLICE_IDR)   
      body[0] = 0x17;  

     /*nal unit*/  
    body[1] = 0x01;  
    body[2] = 0x00;  
    body[3] = 0x00;  
    body[4] = 0x00;  

    //data length
    body[5] = (index_body_len >> 24) & 0xff;  
    body[6] = (index_body_len >> 16) & 0xff;  
    body[7] = (index_body_len >>  8) & 0xff;  
    body[8] = (index_body_len ) & 0xff;  

    /*copy data*/  
    memcpy(&body[9],index_body,index_body_len);  

    /*send rtmp packet*/
    if(rtmp_packet_send(body,index_body_len + 9,RTMP_PACKET_TYPE_VIDEO,timestamp) < 0)
        return -1;


    /*释放发送数据空间*/
    free(body);

    return 0;

}

四、小结

在刚开始接触H264 PUSH到RTMP时,一头雾水,不理解如何进行操作,然后通过查阅资料,得到了初步的了解,将H264推流到RTMP服务器,关键的也就是视频同步包的构造,和H264普通数据包的构造,以及在将这些数据封包进RTMP Packet时候,要注意时间戳之间的关系。除了这些,其他方面例如RTMP的初始化,建立连接等方面则和RTMPDUMP中的流程一致。

你可能感兴趣的:(音视频相关)