FFmpeg RTP推流源码分析

原因:由于需要涉及到RTP的推流分析,故调试查看FFmpeg内部实现流程

文件类型:.264,.aac

概况:FFmpeg首先通过sdp文件的创建,然后主要通过rtpenc.c文件和rtpenc_h264_hevc.c针对264文件进行内部进行格式封装。然后通过udp.c文件中进行发送.

命令行如下:

ffmpeg -re -i q.264 -vcodec copy -f rtp rtp://127.0.0.1:1234

ffplay -protocol_whitelist “file,udp,rtp” -i rtp://127.0.0.1:1234

sdp文件创建主要实现在sdp.c文件中.sdp的生成文件信息大体如下,ffplay通过sdp信息进行数据拉流与解析,主要的实现方法为av_sdp_create,sdp_write_header,sdp_write_media_attributes等.

SDP:
v=0

o=- 0 0 IN IP4 127.0.0.1

s=No Name

c=IN IP4 127.0.0.1

t=0 0

a=tool:libavformat 57.72.101

m=video 1234 RTP/AVP 96

a=rtpmap:96 H264/90000

a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QADazZQUH6EAAAPpAADqYA8UKZYA==,aOvjyyLA; profile-level-id=64000D

FFmpeg每次获取一个avpacket然后调用av_interleaved_write_frame方法,该方法通过oformat->write_packet方法找到对应的rtpenc.c中rtp_write_packet方法,然后通过avio.c文件中retry_transfer_wrapper方法进行upd发送。

接下来伪代码实现如下:首先打开输入文件获取输入文件的AVFormatContext信息,然后创建输出文件的AVForamtContext,类型为rtp_mpegts,然后将输入文件的steram信息赋值给输出,然后调用avio_open创建和初始化一个AVIOContext对象,用于数据写入.可以简单看出AVIOContext对象内部具有一个AVClass对象,可以简单理解为一个io管道.avio_open内部简单实现了upd的创建等,然后首先avformat_write_header方法,然后通过转换avpaket中的pts为输出时间单位。然后调用av_interleaved_write_frame传递packet,最后调用av_write_trailer.流程重点为输出AVForamtContext内部成员赋值,以及时间戳的本地和远端转换.

char *path_v = "q.264";
char *path_a = "q.aac";

char *url_v = "rtp://127.0.0.1:1234";
char *url_a = "rtp://127.0.0.1:1236";

int re = avformat_open_input(&pFormatCtxV, path_v, nullptr, nullptr);

re = avformat_find_stream_info(pFormatCtxV, nullptr);

re = avformat_alloc_output_context2(&pFormatCtxA_O, nullptr, "rtp_mpegts", nullptr);

re = avio_open(&pFormatCtxA_O->pb, url_a, AVIO_FLAG_WRITE);

re = avformat_write_header(pFormatCtxV_O, nullptr);

av_interleaved_write_frame(pFormatCtxV_O, &pkt);

av_write_trailer(pFormatCtxA_O);

时间戳转换代码如下:首先通过av_compare_ts方法判断当前发送更新时间视频是否大于音频,如果视频大于音频则发送音频,如果小于音频则发送视频.如果发送视频则此时具有三个重要的时间戳,第一个streams->time_base(表示AVStream的时间单位1:1200000,该单位表示当前流所使用的基本单位),streams->r_frame_rate(以帧率为单位的时间戳单位,一般都是帧率表示600000:2002),time_base_q为当前时间为单位的时间戳(1:1000000表示1s.).首先计算每帧数据之间的间隔时间calc_duation= 1s时长/帧率 ;然后将当前时间戳计算出来的时间转换成AVSteam的时间单位。最终都要将时间转换成对端的时间戳单位(1:900000),这样对端接收到数据后可以根据自己的时间戳单位换算成当前的时间.

av_compare_ts(cur_pts_v, pFormatCtxV->streams[iIndex_v]->time_base, cur_pts_a, pFormatCtxA->streams[iIndex_a]->time_base);

AVRational time_base = pFormatCtxV->streams[iIndex_v]->time_base;
AVRational r_famerate = pFormatCtxV->streams[iIndex_v]->r_frame_rate;
AVRational time_base_q = {1,AV_TIME_BASE};

int64_t calc_duation = double(AV_TIME_BASE) / av_q2d(r_famerate);

pkt.pts = av_rescale_q(iFrame_index *calc_duation, time_base_q, time_base);
pkt.dts = pkt.pts;
pkt.duration = av_rescale_q(calc_duation, time_base_q, time_base);
				
cur_pts_v = pkt.pts;

iFrame_index++;


pkt.pts = av_rescale_q_rnd(pkt.pts, time_base, pFormatCtxV_O->streams[0]->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, time_base, pFormatCtxV_O->streams[0]->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration,time_base, pFormatCtxV_O->streams[0]->time_base);

发送音频时间戳主要有如下:AVStream对应的基本时间戳(1:2822400),然后本地时间戳(1:1000000),首先获取音频帧间隔时间=1s * nb_sample/采样率.此时由于为aac编码,故为1024字节.然后将当前时间转成AVStream时间.最终转换成对端时间戳时间(1:90000).

AVRational time_base = pFormatCtxA->streams[iIndex_a]->time_base;
AVRational time_base_q = {1,AV_TIME_BASE};
				
double frame_size = (double)pFormatCtxA->streams[iIndex_a]->codecpar->frame_size;
double frame_rate = (double)pFormatCtxA->streams[iIndex_a]->codecpar->sample_rate;
				
int64_t calc_duration = (double)(AV_TIME_BASE) *(frame_size / frame_rate);

pkt.pts = av_rescale_q(iAFrame_index *calc_duration, time_base_q,time_base);
pkt.dts = pkt.pts;
pkt.duration = av_rescale_q(calc_duration, time_base_q, time_base);

iAFrame_index++;
cur_pts_a = pkt.pts;
			
pkt.pts = av_rescale_q_rnd(pkt.pts, pFormatCtxA->streams[iIndex_a]->time_base, pFormatCtxA_O->streams[0]->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, pFormatCtxA->streams[iIndex_a]->time_base, pFormatCtxA_O->streams[0]->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, pFormatCtxA->streams[iIndex_a]->time_base, pFormatCtxA_O->streams[0]->time_base);

av_interleaved_write_frame(pFormatCtxA_O, &pkt);

总结:ffmpeg利用udp实现rtp推流,重点为输出AVForamtContext的赋值以及时间戳转换.代码后续上传

你可能感兴趣的:(FFmpeg)