从零开始写一个RTSP服务器系列
★我的开源项目-RtspServer
从零开始写一个RTSP服务器(一)RTSP协议讲解
从零开始写一个RTSP服务器(二)RTSP协议的实现
从零开始写一个RTSP服务器(三)RTP传输H.264
从零开始写一个RTSP服务器(四)一个传输H.264的RTSP服务器
从零开始写一个RTSP服务器(五)RTP传输AAC
从零开始写一个RTSP服务器(六)一个传输AAC的RTSP服务器
从零开始写一个RTSP服务器(七)多播传输RTP包
从零开始写一个RTSP服务器(八)一个多播的RTSP服务器
从零开始写一个RTSP服务器(九)一个RTP OVER RTSP/TCP的RTSP服务器
从零开始写一个RTSP服务器(三)RTP传输H.264
文章目录
本篇文章目标,使用vlc打开sdp文件后,可以观看到视频数据
一、RTP封装
1.1 RTP数据结构
RTP包格式前面已经比较详细的介绍过,参考从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解
看一张RTP头的格式图回忆一下
每个RTP包都包含这样一个RTP头部和RTP数据,为了方便,我将这个头部封装成一个结构体,还有发送包封装成一个函数,下面来看一看
- RTP头结构体
/*
* 作者:_JT_
* 博客:https://blog.csdn.net/weixin_42462202
*/
struct RtpHeader
{
/* byte 0 */
uint8_t csrcLen:4;
uint8_t extension:1;
uint8_t padding:1;
uint8_t version:2;
<span class="token comment">/* byte 1 */</span>
uint8_t payloadType<span class="token punctuation">:</span><span class="token number">7</span><span class="token punctuation">;</span>
uint8_t marker<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span>
<span class="token comment">/* bytes 2,3 */</span>
uint16_t seq<span class="token punctuation">;</span>
<span class="token comment">/* bytes 4-7 */</span>
uint32_t timestamp<span class="token punctuation">;</span>
<span class="token comment">/* bytes 8-11 */</span>
uint32_t ssrc<span class="token punctuation">;</span>
};
- 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
其中的:n
是一种位表示法,这个结构体跟RTP的头部一一对应
-
RTP的发包函数
RTP包
struct RtpPacket { struct RtpHeader rtpHeader; uint8_t payload[0]; };
- 1
- 2
- 3
- 4
- 5
这是我封装的一个RTP包,包含一个RTP头部和RTP载荷,
uint8_t payload[0]
并不占用空间,它表示rtp头部接下来紧跟着的地址RTP的发包函数
/* * 函数功能:发送RTP包 * 参数 socket:表示本机的udp套接字 * 参数 ip:表示目的ip地址 * 参数 port:表示目的的端口号 * 参数 rtpPacket:表示rtp包 * 参数 dataSize:表示rtp包中载荷的大小 * 放回值:发送字节数 */ int rtpSendPacket(int socket, char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize) { struct sockaddr_in addr; int ret;
addr<span class="token punctuation">.</span>sin_family <span class="token operator">=</span> AF_INET<span class="token punctuation">;</span> addr<span class="token punctuation">.</span>sin_port <span class="token operator">=</span> <span class="token function">htons</span><span class="token punctuation">(</span>port<span class="token punctuation">)</span><span class="token punctuation">;</span> addr<span class="token punctuation">.</span>sin_addr<span class="token punctuation">.</span>s_addr <span class="token operator">=</span> <span class="token function">inet_addr</span><span class="token punctuation">(</span>ip<span class="token punctuation">)</span><span class="token punctuation">;</span> rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq <span class="token operator">=</span> <span class="token function">htons</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq<span class="token punctuation">)</span><span class="token punctuation">;</span> rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp <span class="token operator">=</span> <span class="token function">htonl</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp<span class="token punctuation">)</span><span class="token punctuation">;</span> rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>ssrc <span class="token operator">=</span> <span class="token function">htonl</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>ssrc<span class="token punctuation">)</span><span class="token punctuation">;</span> ret <span class="token operator">=</span> <span class="token function">sendto</span><span class="token punctuation">(</span>socket<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">void</span><span class="token operator">*</span><span class="token punctuation">)</span>rtpPacket<span class="token punctuation">,</span> dataSize<span class="token operator">+</span>RTP_HEADER_SIZE<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">struct</span> sockaddr<span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&</span>addr<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>addr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq <span class="token operator">=</span> <span class="token function">ntohs</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq<span class="token punctuation">)</span><span class="token punctuation">;</span> rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp <span class="token operator">=</span> <span class="token function">ntohl</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp<span class="token punctuation">)</span><span class="token punctuation">;</span> rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>ssrc <span class="token operator">=</span> <span class="token function">ntohl</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>ssrc<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
- 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
仔细看这个函数你应该可以看懂
我们设置好一个包之后,就会调用这个函数发送指定目标
这个函数中多处使用htons
等函数,是因为RTP是采用网络字节序(大端模式),所以要将主机字节字节序转换为网络字节序
下面给出源码,rtp.h
和rtp.c
,这两个文件在后面讲经常使用
1.2 源码
rtp.h
/*
* 作者:_JT_
* 博客:https://blog.csdn.net/weixin_42462202
*/
#ifndef RTP_H
#define RTP_H
#include <stdint.h>
#define RTP_VESION 2
#define RTP_PAYLOAD_TYPE_H264 96
#define RTP_PAYLOAD_TYPE_AAC 97
#define RTP_HEADER_SIZE 12
#define RTP_MAX_PKT_SIZE 1400
/*
*
- 0 1 2 3
- 7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0
- ±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
- |V=2|P|X| CC |M| PT | sequence number |
- ±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
- | timestamp |
- ±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
- | synchronization source (SSRC) identifier |
- +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
- | contributing source (CSRC) identifiers |
- : … :
- ±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
/
struct RtpHeader
{
/ byte 0 */
uint8_t csrcLen:4;
uint8_t extension:1;
uint8_t padding:1;
uint8_t version:2;
<span class="token comment">/* byte 1 */</span>
uint8_t payloadType<span class="token punctuation">:</span><span class="token number">7</span><span class="token punctuation">;</span>
uint8_t marker<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span>
<span class="token comment">/* bytes 2,3 */</span>
uint16_t seq<span class="token punctuation">;</span>
<span class="token comment">/* bytes 4-7 */</span>
uint32_t timestamp<span class="token punctuation">;</span>
<span class="token comment">/* bytes 8-11 */</span>
uint32_t ssrc<span class="token punctuation">;</span>
};
struct RtpPacket
{
struct RtpHeader rtpHeader;
uint8_t payload[0];
};
void rtpHeaderInit(struct RtpPacket rtpPacket, uint8_t csrcLen, uint8_t extension,
uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
uint16_t seq, uint32_t timestamp, uint32_t ssrc);
int rtpSendPacket(int socket, char ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);
#endif //RTP_H
- 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
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
rtp.c
/*
* 作者:_JT_
* 博客:https://blog.csdn.net/weixin_42462202
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include “rtp.h”
void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
uint16_t seq, uint32_t timestamp, uint32_t ssrc)
{
rtpPacket->rtpHeader.csrcLen = csrcLen;
rtpPacket->rtpHeader.extension = extension;
rtpPacket->rtpHeader.padding = padding;
rtpPacket->rtpHeader.version = version;
rtpPacket->rtpHeader.payloadType = payloadType;
rtpPacket->rtpHeader.marker = marker;
rtpPacket->rtpHeader.seq = seq;
rtpPacket->rtpHeader.timestamp = timestamp;
rtpPacket->rtpHeader.ssrc = ssrc;
}
int rtpSendPacket(int socket, char ip, int16_t port, struct RtpPacket rtpPacket, uint32_t dataSize)
{
struct sockaddr_in addr;
int ret;
addr<span class="token punctuation">.</span>sin_family <span class="token operator">=</span> AF_INET<span class="token punctuation">;</span>
addr<span class="token punctuation">.</span>sin_port <span class="token operator">=</span> <span class="token function">htons</span><span class="token punctuation">(</span>port<span class="token punctuation">)</span><span class="token punctuation">;</span>
addr<span class="token punctuation">.</span>sin_addr<span class="token punctuation">.</span>s_addr <span class="token operator">=</span> <span class="token function">inet_addr</span><span class="token punctuation">(</span>ip<span class="token punctuation">)</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq <span class="token operator">=</span> <span class="token function">htons</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq<span class="token punctuation">)</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp <span class="token operator">=</span> <span class="token function">htonl</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp<span class="token punctuation">)</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>ssrc <span class="token operator">=</span> <span class="token function">htonl</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>ssrc<span class="token punctuation">)</span><span class="token punctuation">;</span>
ret <span class="token operator">=</span> <span class="token function">sendto</span><span class="token punctuation">(</span>socket<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">void</span><span class="token operator">*</span><span class="token punctuation">)</span>rtpPacket<span class="token punctuation">,</span> dataSize<span class="token operator">+</span>RTP_HEADER_SIZE<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span>
<span class="token punctuation">(</span><span class="token keyword">struct</span> sockaddr<span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&</span>addr<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>addr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq <span class="token operator">=</span> <span class="token function">ntohs</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq<span class="token punctuation">)</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp <span class="token operator">=</span> <span class="token function">ntohl</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp<span class="token punctuation">)</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>ssrc <span class="token operator">=</span> <span class="token function">ntohl</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>ssrc<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
- 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
二、H.264的RTP打包
2.1 H.264格式
H.264由一个一个的NALU组成,每个NALU之间使用00 00 00 01
或00 00 01
分隔开
每个NALU的第一次字节都有特殊的含义,其内容如下
位 | 描述 |
---|---|
bit[7] | 必须为0 |
bit[5-6] | 标记该NALU的重要性 |
bit[0-4] | NALU单元的类型 |
好,对于H.264格式了解这么多就够了,我们的目的是想从一个H.264的文件中将一个一个的NALU提取出来,然后封装成RTP包,下面介绍如何将NALU封装成RTP包
2.2 H.264的RTP打包方式
H.264可以由三种RTP打包方式
-
单NALU打包
一个RTP包包含一个完整的NALU
-
聚合打包
对于较小的NALU,一个RTP包可包含多个完整的NALU
-
分片打包
对于较大的NALU,一个NALU可以分为多个RTP包发送
注意:这里要区分好概念,每一个RTP包都包含一个RTP头部和RTP荷载,这是固定的。而H.264发送数据可支持三种RTP打包方式
比较常用的是单NALU打包
和分片打包
,本文也只介绍这两种
单NALU打包
所谓单NALU打包就是将一整个NALU的数据放入RTP包的载荷中
这是最简单的一种方式,无需过多的讲解
分片打包
每个RTP包都有大小限制的,因为RTP一般都是使用UDP发送,UDP没有流量控制,所以要限制每一次发送的大小,所以如果一个NALU的太大,就需要分成多个RTP包发送,如何分成多个RTP包,下面来好好讲一讲
首先要明确,RTP包的格式是绝不会变的,永远多是RTP头+RTP载荷
RTP头部是固定的,那么只能在RTP载荷中去添加额外信息来说明这个RTP包是表示同一个NALU
如果是分片打包的话,那么在RTP载荷开始有两个字节的信息,然后再是NALU的内容
-
第一个字节位
FU Indicator
,其格式如下
高三位:与NALU第一个字节的高三位相同Type:28,表示该RTP包一个分片,为什么是28?因为H.264的规范中定义的,此外还有许多其他Type,这里不详讲
-
第二个字节位
FU Header
,其格式如下S:标记该分片打包的第一个RTP包
E:比较该分片打包的最后一个RTP包
Type:NALU的Type
2.3 H.264 RTP包的时间戳计算
RTP包的时间戳起始值是随机的
RTP包的时间戳增量怎么计算?
假设时钟频率为90000,帧率为25
频率为90000表示一秒用90000点来表示
帧率为25,那么一帧就是1/25秒
所以一帧有90000*(1/25)=3600个点来表示
因此每一帧数据的时间增量为3600
2.4 源码
rtp_h264.c
这里给出rtp发送H.264的源码
/*
* 作者:_JT_
* 博客:https://blog.csdn.net/weixin_42462202
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include “rtp.h”
#define H264_FILE_NAME “test.h264”
#define CLIENT_IP “127.0.0.1”
#define CLIENT_PORT 9832
#define FPS 25
static inline int startCode3(char* buf)
{
if(buf[0] 0 && buf[1] 0 && buf[2] == 1)
return 1;
else
return 0;
}
static inline int startCode4(char* buf)
{
if(buf[0] 0 && buf[1] 0 && buf[2] 0 && buf[3] 1)
return 1;
else
return 0;
}
static char findNextStartCode(char buf, int len)
{
int i;
<span class="token keyword">if</span><span class="token punctuation">(</span>len <span class="token operator"><</span> <span class="token number">3</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token constant">NULL</span><span class="token punctuation">;</span>
<span class="token keyword">for</span><span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> len<span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token function">startCode3</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">startCode4</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> buf<span class="token punctuation">;</span>
<span class="token operator">++</span>buf<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token function">startCode3</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> buf<span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token constant">NULL</span><span class="token punctuation">;</span>
}
static int getFrameFromH264File(int fd, char frame, int size)
{
int rSize, frameSize;
char nextStartCode;
<span class="token keyword">if</span><span class="token punctuation">(</span>fd <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> fd<span class="token punctuation">;</span>
rSize <span class="token operator">=</span> <span class="token function">read</span><span class="token punctuation">(</span>fd<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> size<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">startCode3</span><span class="token punctuation">(</span>frame<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token operator">!</span><span class="token function">startCode4</span><span class="token punctuation">(</span>frame<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
nextStartCode <span class="token operator">=</span> <span class="token function">findNextStartCode</span><span class="token punctuation">(</span>frame<span class="token operator">+</span><span class="token number">3</span><span class="token punctuation">,</span> rSize<span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>nextStartCode<span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
<span class="token function">lseek</span><span class="token punctuation">(</span>fd<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token constant">SEEK_SET</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
frameSize <span class="token operator">=</span> rSize<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">else</span>
<span class="token punctuation">{<!-- --></span>
frameSize <span class="token operator">=</span> <span class="token punctuation">(</span>nextStartCode<span class="token operator">-</span>frame<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">lseek</span><span class="token punctuation">(</span>fd<span class="token punctuation">,</span> frameSize<span class="token operator">-</span>rSize<span class="token punctuation">,</span> <span class="token constant">SEEK_CUR</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> frameSize<span class="token punctuation">;</span>
}
static int createUdpSocket()
{
int fd;
int on = 1;
fd <span class="token operator">=</span> <span class="token function">socket</span><span class="token punctuation">(</span>AF_INET<span class="token punctuation">,</span> SOCK_DGRAM<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>fd <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
<span class="token function">setsockopt</span><span class="token punctuation">(</span>fd<span class="token punctuation">,</span> SOL_SOCKET<span class="token punctuation">,</span> SO_REUSEADDR<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&</span>on<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>on<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> fd<span class="token punctuation">;</span>
}
static int rtpSendH264Frame(int socket, char ip, int16_t port,
struct RtpPacket rtpPacket, uint8_t* frame, uint32_t frameSize)
{
uint8_t naluType; // nalu第一个字节
int sendBytes = 0;
int ret;
naluType <span class="token operator">=</span> frame<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>frameSize <span class="token operator"><=</span> RTP_MAX_PKT_SIZE<span class="token punctuation">)</span> <span class="token comment">// nalu长度小于最大包场:单一NALU单元模式</span>
<span class="token punctuation">{<!-- --></span>
<span class="token comment">/*
* 0 1 2 3 4 5 6 7 8 9
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |F|NRI| Type | a single NAL unit ... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/</span>
<span class="token function">memcpy</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>payload<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> frameSize<span class="token punctuation">)</span><span class="token punctuation">;</span>
ret <span class="token operator">=</span> <span class="token function">rtpSendPacket</span><span class="token punctuation">(</span>socket<span class="token punctuation">,</span> ip<span class="token punctuation">,</span> port<span class="token punctuation">,</span> rtpPacket<span class="token punctuation">,</span> frameSize<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq<span class="token operator">++</span><span class="token punctuation">;</span>
sendBytes <span class="token operator">+</span><span class="token operator">=</span> ret<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>naluType <span class="token operator">&</span> <span class="token number">0x1F</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">7</span> <span class="token operator">||</span> <span class="token punctuation">(</span>naluType <span class="token operator">&</span> <span class="token number">0x1F</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">8</span><span class="token punctuation">)</span> <span class="token comment">// 如果是SPS、PPS就不需要加时间戳</span>
<span class="token keyword">goto</span> out<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">else</span> <span class="token comment">// nalu长度小于最大包场:分片模式</span>
<span class="token punctuation">{<!-- --></span>
<span class="token comment">/*
* 0 1 2
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | FU indicator | FU header | FU payload ... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/</span>
<span class="token comment">/*
* FU Indicator
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |F|NRI| Type |
* +---------------+
*/</span>
<span class="token comment">/*
* FU Header
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |S|E|R| Type |
* +---------------+
*/</span>
<span class="token keyword">int</span> pktNum <span class="token operator">=</span> frameSize <span class="token operator">/</span> RTP_MAX_PKT_SIZE<span class="token punctuation">;</span> <span class="token comment">// 有几个完整的包</span>
<span class="token keyword">int</span> remainPktSize <span class="token operator">=</span> frameSize <span class="token operator">%</span> RTP_MAX_PKT_SIZE<span class="token punctuation">;</span> <span class="token comment">// 剩余不完整包的大小</span>
<span class="token keyword">int</span> i<span class="token punctuation">,</span> pos <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token comment">/* 发送完整的包 */</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> pktNum<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
rtpPacket<span class="token operator">-></span>payload<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">(</span>naluType <span class="token operator">&</span> <span class="token number">0x60</span><span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token number">28</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>payload<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> naluType <span class="token operator">&</span> <span class="token number">0x1F</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">//第一包数据</span>
rtpPacket<span class="token operator">-></span>payload<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">|</span><span class="token operator">=</span> <span class="token number">0x80</span><span class="token punctuation">;</span> <span class="token comment">// start</span>
<span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>remainPktSize <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&&</span> i <span class="token operator">==</span> pktNum <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">//最后一包数据</span>
rtpPacket<span class="token operator">-></span>payload<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">|</span><span class="token operator">=</span> <span class="token number">0x40</span><span class="token punctuation">;</span> <span class="token comment">// end</span>
<span class="token function">memcpy</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>payload<span class="token operator">+</span><span class="token number">2</span><span class="token punctuation">,</span> frame<span class="token operator">+</span>pos<span class="token punctuation">,</span> RTP_MAX_PKT_SIZE<span class="token punctuation">)</span><span class="token punctuation">;</span>
ret <span class="token operator">=</span> <span class="token function">rtpSendPacket</span><span class="token punctuation">(</span>socket<span class="token punctuation">,</span> ip<span class="token punctuation">,</span> port<span class="token punctuation">,</span> rtpPacket<span class="token punctuation">,</span> RTP_MAX_PKT_SIZE<span class="token operator">+</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq<span class="token operator">++</span><span class="token punctuation">;</span>
sendBytes <span class="token operator">+</span><span class="token operator">=</span> ret<span class="token punctuation">;</span>
pos <span class="token operator">+</span><span class="token operator">=</span> RTP_MAX_PKT_SIZE<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/* 发送剩余的数据 */</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>remainPktSize <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
rtpPacket<span class="token operator">-></span>payload<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">(</span>naluType <span class="token operator">&</span> <span class="token number">0x60</span><span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token number">28</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>payload<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> naluType <span class="token operator">&</span> <span class="token number">0x1F</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>payload<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">|</span><span class="token operator">=</span> <span class="token number">0x40</span><span class="token punctuation">;</span> <span class="token comment">//end</span>
<span class="token function">memcpy</span><span class="token punctuation">(</span>rtpPacket<span class="token operator">-></span>payload<span class="token operator">+</span><span class="token number">2</span><span class="token punctuation">,</span> frame<span class="token operator">+</span>pos<span class="token punctuation">,</span> remainPktSize<span class="token operator">+</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ret <span class="token operator">=</span> <span class="token function">rtpSendPacket</span><span class="token punctuation">(</span>socket<span class="token punctuation">,</span> ip<span class="token punctuation">,</span> port<span class="token punctuation">,</span> rtpPacket<span class="token punctuation">,</span> remainPktSize<span class="token operator">+</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>seq<span class="token operator">++</span><span class="token punctuation">;</span>
sendBytes <span class="token operator">+</span><span class="token operator">=</span> ret<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
out:
<span class="token keyword">return</span> sendBytes<span class="token punctuation">;</span>
}
int main(int argc, char argv[])
{
int socket;
int fd;
int fps = 25;
int startCode;
struct RtpPacket rtpPacket;
uint8_t* frame;
uint32_t frameSize;
fd <span class="token operator">=</span> <span class="token function">open</span><span class="token punctuation">(</span>H264_FILE_NAME<span class="token punctuation">,</span> O_RDONLY<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>fd <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"failed to open %s\n"</span><span class="token punctuation">,</span> H264_FILE_NAME<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
socket <span class="token operator">=</span> <span class="token function">createUdpSocket</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>socket <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"failed to create socket\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
rtpPacket <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">struct</span> RtpPacket<span class="token operator">*</span><span class="token punctuation">)</span><span class="token function">malloc</span><span class="token punctuation">(</span><span class="token number">500000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
frame <span class="token operator">=</span> <span class="token punctuation">(</span>uint8_t<span class="token operator">*</span><span class="token punctuation">)</span><span class="token function">malloc</span><span class="token punctuation">(</span><span class="token number">500000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">rtpHeaderInit</span><span class="token punctuation">(</span>rtpPacket<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> RTP_VESION<span class="token punctuation">,</span> RTP_PAYLOAD_TYPE_H264<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span>
<span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0x88923423</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">while</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
frameSize <span class="token operator">=</span> <span class="token function">getFrameFromH264File</span><span class="token punctuation">(</span>fd<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> <span class="token number">500000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>frameSize <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"read err\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">continue</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token function">startCode3</span><span class="token punctuation">(</span>frame<span class="token punctuation">)</span><span class="token punctuation">)</span>
startCode <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token keyword">else</span>
startCode <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span>
frameSize <span class="token operator">-</span><span class="token operator">=</span> startCode<span class="token punctuation">;</span>
<span class="token function">rtpSendH264Frame</span><span class="token punctuation">(</span>socket<span class="token punctuation">,</span> CLIENT_IP<span class="token punctuation">,</span> CLIENT_PORT<span class="token punctuation">,</span>
rtpPacket<span class="token punctuation">,</span> frame<span class="token operator">+</span>startCode<span class="token punctuation">,</span> frameSize<span class="token punctuation">)</span><span class="token punctuation">;</span>
rtpPacket<span class="token operator">-></span>rtpHeader<span class="token punctuation">.</span>timestamp <span class="token operator">+</span><span class="token operator">=</span> <span class="token number">90000</span><span class="token operator">/</span>FPS<span class="token punctuation">;</span>
<span class="token function">usleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token operator">*</span><span class="token number">1000</span><span class="token operator">/</span>fps<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">free</span><span class="token punctuation">(</span>rtpPacket<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">free</span><span class="token punctuation">(</span>frame<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 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
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
三、H.264 RTP打包的sdp描述
sdp文件有什么用?
sdp描述着媒体信息,当使用vlc打开这个sdp文件后,会根据这些信息做相应的操作(创建套接字…),然后等待接收RTP包
这里给出RTP打包H.264
的sdp文件,并描述每一行是什么意思
m=video 9832 RTP/AVP 96
a=rtpmap:96 H264/90000
a=framerate:25
c=IN IP4 127.0.0.1
- 1
- 2
- 3
- 4
这个一个媒体级的sdp描述,关于sdp文件描述详情可看从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解
-
m=video 9832 RTP/AVP 96
格式为 m=<媒体类型> <端口号> <传输协议> <媒体格式 >
媒体类型:video,表示这是一个视频流端口号:9832,表示UDP发送的目的端口为9832
传输协议:RTP/AVP,表示RTP OVER UDP,通过UDP发送RTP包
媒体格式:表示负载类型(payload type),一般使用96表示H.264
-
a=rtpmap:96 H264/90000
格式为a=rtpmap:<媒体格式><编码格式>/<时钟频率>
-
a=framerate:25
表示帧率
-
c=IN IP4 127.0.0.1
IN:表示internet
IP4:表示IPV4
127.0.0.1:表示UDP发送的目的地址为127.0.0.1
特别注意:这段sdp文件描述的udp发送的目的IP为127.0.0.1,目的端口为9832
四、测试
讲上面给出的源码rtp.c
、rtp.h
、rtp_h264.c
保存下来,然后编译运行
注意:该程序默认打开的是test.h264
,如果你没有视频源,可以从RtspServer的example目录下获取
# gcc rtp.c rtp_h264.c
# ./a.out
- 1
- 2
讲上面的sdp文件保存为rtp_h264.sdp
,使用vlc打开,即可观看到视频
# vlc rtp_h264.sdp
- 1
-
运行效果
至此,我们已经完成了RTSP协议交互和RTP打包H.264,下一篇文章就可以来实现一个播放H.264的RTSP服务器了