FFmpeg发送RTP封包的H264

        本文介绍FFmpeg发送RTP封包的H264,以及用VLC工具的验证方法,即是分享也做记录;

一、RTP协议简单说明

RTP标准定义了两个子协议 ,RTP和RTCP:

数据传输协议RTP,用于实时传输数据。该协议提供的信息包括:时间戳(用于同步)、序列号(用于丢包和重排序检测)、以及负载格式(用于说明数据的编码格式)。

控制协议RTCP,用于QoS反馈和同步媒体流。相对于RTP来说,RTCP所占的带宽非常小,通常只有5%。

RTP报文头格式(见RFC3550 RFC3550 中文翻译 中文RFC RFC文档 RFC翻译 RFC中文版):

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                           timestamp                           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           synchronization source (SSRC) identifier            |
   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
   |            contributing source (CSRC) identifiers             |
   |                             ....                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1).版本号(V):用来标志使用的RTP版本,占2位,当前协议版本号为2

2).填充位(P):填充标志,占1位,如果P=1,则该RTP包的尾部就包含附加的填充字节,在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。

3).扩展位(X):扩展标志,占1位,如果X=1,则在RTP固定头部后面就跟有一个扩展头部

4).CSRC计数器(CC):CSRC计数器,占4位,指示固定头部后面跟着的CSRC 标识符的个数

5).标记位(M):标记,占1位,该位的解释由配置文档(Profile)来承担,不同的有效载荷有不同的含义,一般而言,对于视频,标记一帧的结束;对于音频,标记会话的开始。

6).载荷类型(PayloadType): 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流的,这样便于客户端进行解析。

7).序列号(SN):占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。接收端可以据此检测丢包和重建包序列,当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包,同时出现网络抖动的情况可以用来对数据进行重新排序,序列号的初始值是随机的,同时音频包和视频包的sequence是分别记数的。

8).时间戳(Timestamp): 占32位,记录了该包中数据的第一个字节的采样时刻。在一次会话开始时,时间戳初始化成一个初始值。即使在没有信号发送时,时间戳的数值也要随时间而不断地增加(时间在流逝嘛)。时钟频率依赖于负载数据格式,并在描述文件(profile)中进行描述。一般而言,必须使用90 kHz 时钟频率。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。

9).同步源标识符(SSRC):占32位,用于标识同步信源,同步源就是指RTP包流的来源。在同一个RTP会话中不能有两个相同的SSRC值。该标识符是随机选取的,RFC1889推荐了MD5随机算法。       

10).贡献源列表(CSRC List):可以有0~15个,每个CSRC标识符占32位,用来标志对一个RTP混合器产生的新包有贡献的所有RTP包的源。由混合器将这些有贡献的SSRC标识符插入表中。SSRC标识符都被列出来,以便接收端能正确指出交谈双方的身份。

二、H264协议简单说明

H264 的两种码流格式,它们分别为:字节流格式和 RTP包格式

  • 字节流格式: 默认的输出格式。它的基本数据单位为 NAL 单元,也即 NALU。为了从字节流中提取出 NALU,协议规定,在每个 NALU 的前面加上起始码:0x000001 或 0x00000001(0x 代表十六进制)
  • RTP 包格式:一种数据传输格式,主要用于网络传送

字节流格式,也成称之为裸流;

1、H264字节流格式

Start_Code_Prefix + NALU + Start_Code_Prefix + NALU + ...

起始码与NALU

起始码:0x000001或者0x00000001;在两个起始码间的数据称之为NALU;

NALU

NALU单元的第一个byte:

1)第1位是禁止位:1语法错误

2)第2~3位是参考位,数值越大越重要

3)第4~8位是NAL类型

1

不分区,非IDR

2

片分区A

3

片分区B

4

片分区C

5

IDR图像中的片

6

SEI

7

SPS

8

PPS

9

分节符

10

序列结束

11

码流结束

12

填充

每个 NAL 单元包括一个原始字节序列负荷 (RBSP, Raw Byte Sequence Payload)、一组对应于视频编码的 NAL 头信息:

NALU = NALU头 + RBSP

三、通过ffmpeg命令发送数据

ffmpeg -re -i dump.h264 -vcodec copy -f rtp rtp://ip:port>test.sdp

参数说明:

-r 设定帧率,默认为25

-i 设定输入流

-vcodec 设定视频编码器

-f 设定输入格式

dump.h264 h264视频文件,ip和port是要推送的目标ip和端口,test.sdp推送视频产生的sdp格式(VLC工具要根据此格式取流);

四、代码实现rtp封包发送

#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#ifdef __cplusplus
};
#endif

int main()
{
    AVOutputFormat *outputfmt = NULL;
	AVFormatContext *inputfmt_ctx = NULL, *outputfmt_ctx = NULL;
	AVPacket pkt;
	const char *in_filename, *out_filename;
	int ret, i;
	int videoindex = -1;
	int frame_index = 0;
	int64_t start_time = 0;
	in_filename  = "dump.h264";
	out_filename = "rtp://192.168.1.1:1234";
	av_register_all();
	//Network
	avformat_network_init();
	//Input
	if ((ret = avformat_open_input(&inputfmt_ctx, in_filename, 0, 0)) < 0) {
		printf( "Could not open input file.");
		goto end;
	}

	if ((ret = avformat_find_stream_info(inputfmt_ctx, 0)) < 0) {
		printf( "Failed to retrieve input stream information");
		goto end;
	}
    //查找视频帧
	for(i=0; i<inputfmt_ctx->nb_streams; i++) 
		if(inputfmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
			videoindex=i;
			break;
		}

	av_dump_format(inputfmt_ctx, 0, in_filename, 0);

	//Output
	avformat_alloc_output_context2(&outputfmt_ctx, NULL, "rtp", out_filename);
	if (!outputfmt_ctx) {
		printf( "Could not create output context\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}

	outputfmt = outputfmt_ctx->oformat;
	for (i = 0; i < inputfmt_ctx->nb_streams; i++) {
		//Create output AVStream according to input AVStream
		AVStream *in_stream = inputfmt_ctx->streams[i];
		AVStream *out_stream = avformat_new_stream(outputfmt_ctx, in_stream->codec->codec);
		if (!out_stream) {
			printf( "Failed allocating output stream\n");
			ret = AVERROR_UNKNOWN;
			goto end;
		}
		//Copy the settings of AVCodecContext
		ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
		if (ret < 0) {
			printf( "Failed to copy context from input to output stream codec context\n");
			goto end;
		}
		out_stream->codec->codec_tag = 0;
		if (outputfmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
			out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}
	//Dump Format------------------
	av_dump_format(outputfmt_ctx, 0, out_filename, 1);
	//Open output URL
	if (!(outputfmt->flags & AVFMT_NOFILE)) {
		ret = avio_open(&outputfmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
		if (ret < 0) {
			printf( "Could not open output URL '%s'", out_filename);
			goto end;
		}
	}
	//Write file header
	ret = avformat_write_header(outputfmt_ctx, NULL);
	if (ret < 0) {
		printf( "Error occurred when opening output URL\n");
		goto end;
	}

	start_time=av_gettime();
	while (1) {
		AVStream *in_stream, *out_stream;
		//Get an AVPacket
		ret = av_read_frame(inputfmt_ctx, &pkt);
		if (ret < 0)
			break;
		//FIX:No PTS (Example: Raw H.264)
		//Simple Write PTS
		if(pkt.pts==AV_NOPTS_VALUE){
			//Write PTS
			AVRational time_base1=inputfmt_ctx->streams[videoindex]->time_base;
			//Duration between 2 frames (us)
			int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(inputfmt_ctx->streams[videoindex]->r_frame_rate);
			//Parameters
			pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
			pkt.dts=pkt.pts;
			pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
		}
		//Important:Delay
		if(pkt.stream_index==videoindex){
			AVRational time_base=inputfmt_ctx->streams[videoindex]->time_base;
			AVRational time_base_q={1,AV_TIME_BASE};
			int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
			int64_t now_time = av_gettime() - start_time;
			if (pts_time > now_time)
				av_usleep(pts_time - now_time);

		}

		in_stream  = inputfmt_ctx->streams[pkt.stream_index];
		out_stream = outputfmt_ctx->streams[pkt.stream_index];
		/* copy packet */
		//Convert PTS/DTS
		pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
		pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
		pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		pkt.pos = -1;
		//Print to Screen
		if(pkt.stream_index==videoindex){
			printf("Send %8d video frames to output URL\n",frame_index);
			frame_index++;
		}
		//ret = av_write_frame(outputfmt_ctx, &pkt);
		ret = av_interleaved_write_frame(outputfmt_ctx, &pkt);

		if (ret < 0) {
			printf( "Error muxing packet\n");
			break;
		}
		
		av_free_packet(&pkt);
		
	}
	//Write file trailer
	av_write_trailer(outputfmt_ctx);
end:
	avformat_close_input(&inputfmt_ctx);
	/* close output */
	if (outputfmt_ctx && !(outputfmt->flags & AVFMT_NOFILE))
		avio_close(outputfmt_ctx->pb);
	avformat_free_context(outputfmt_ctx);
	if (ret < 0 && ret != AVERROR_EOF) {
		printf( "Error occurred.\n");
		return -1;
	}

    return 0;
}

五、用VLC测试接收
VLC media player工具
媒体->打开网络串流->网络->网络协议中输入网络URL:rtp://192.168.1.1:1234;

如果出现如下错误:

请使用VLC media player工具打开如下内容的sdp文件(三、ffmpeg命令生成的内容修改)

v=0
m=video 1234 RTP/AVP 96
a=rtpmap:96 H264/90000
c=IN IP4 127.0.0.1

v:版本号
m:媒体格式信息

a:rtp属性信息

c:127.0.01可以修改为本地ip

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值