H264 推流到RTMP服务器

转自https://blog.csdn.net/u011003120/article/details/78378632

参考连接: 
基于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的结构,雷博的博文已经详细说明了,在这里可以参考一张图片 
 
由上面的图片可以看出,FLV的整体结构为Header + Body. 其中Body则由 Tag + PreviousTagSizeN的方式组成,每一个Tag由Tag Header + Tag Data组成。 
其中,在Video_File_Format_Specification_V10.pdf中,对每一个TAG的Tag Data有了更详细的说明(暂时只关注视频tag): 
 


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


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

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

扫描二维码关注公众号,回复: 4922721 查看本文章

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

AVCVIDEOPACKET
AVCPacketType == 0x00
CompositionTime == 0x000000
Data == AVCDecoderConfigurationRecord
1
2
3
4
5
6
7
8
9
接下来继续构造AVCDecoderConfigurationRecord,参考上面链接,AVCDecoderConfigurationRecord相关结构如下:

因此我们需要将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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2)H264普通数据包的构建
构建了上面的视频同步包后,我们需要将H264的荷载的数据即H264的I P B等数据封包进去。根据上面的图片,结构信息如下:

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

AVCVIDEOPACKET
AVCPacketType == 0x01
CompositionTime == 0x000000
Data == NALU
1
2
3
4
5
6
7
8
9
上面的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;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
四、小结
在刚开始接触H264 PUSH到RTMP时,一头雾水,不理解如何进行操作,然后通过查阅资料,得到了初步的了解,将H264推流到RTMP服务器,关键的也就是视频同步包的构造,和H264普通数据包的构造,以及在将这些数据封包进RTMP Packet时候,要注意时间戳之间的关系。除了这些,其他方面例如RTMP的初始化,建立连接等方面则和RTMPDUMP中的流程一致。
--------------------- 
作者:Stoneshen1211 
来源:CSDN 
原文:https://blog.csdn.net/u011003120/article/details/78378632 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/NBA_1/article/details/86470163