基本信息
- 阅读内容:uri-of-source-code
- 阅读者:李北宸
- 阅读时间:2018.07.24
- 阅读目的:了解
kmsbasertpendpoint
的工作原理
阅读记录
类结构定义
struct _KmsBaseRtpEndpointPrivate
{
KmsBaseRtpSession *sess;//kms定义的session,管理一个AUDIO流和一个VIDEO流
GstElement *rtpbin;//处理rtp报文的主要element
KmsMediaState media_state;//
GstSDPDirection offer_dir;
gboolean support_fec;//是否支持fec
gboolean rtcp_mux;//是否支持rtcp_mux
gboolean rtcp_nack;//是否支持nack
gboolean rtcp_remb;//是否支持remb
RtpMediaConfig *audio_config;//本地的音频流的信息,包括ssrc、是否为active流
RtpMediaConfig *video_config;//本地的视频流的信息
gint32 target_bitrate;
guint min_video_recv_bw;
guint min_video_send_bw;
guint max_video_send_bw;
/* Medias protected by ulpfec */
KmsList *prot_medias;//使用fec的媒体
/* REMB */
GstStructure *remb_params;//配置remb的参数
KmsRembLocal *rl;//管理本地REMB的数据结构,具体可以参考kmsremb.md
KmsRembRemote *rm;//管理对端REMB的数据结构
/* Port range */
guint min_port;
guint max_port;
/* RTP statistics */
KmsBaseRTPStats stats;//一些统计信息
/* Timestamps */
gssize init_stats;
FILE *stats_file;//记录统计信息的文件指针
/* Synchronization */
KmsRtpSynchronizer *sync_audio;//设置音频流pts的synchronizer
KmsRtpSynchronizer *sync_video;//设置视频流pts的synchronizer
gboolean perform_video_sync;//似乎是指是否要让音频流和视频流之间同步
};
typedef struct _HdrExtData
{
GstPad *pad;
/* Useful to make buffers writable when needed. */
gboolean add_hdr;
gboolean set_time;
gint abs_send_time_id;
} HdrExtData;
当一个RTP报文头部的x位为1时,表明RTP报文头部后边会有一个头部扩展。RTP协议并不要求任何头部扩展,只有上层协议有需要时才会添加头部扩展。
HdrExtData是为了方便给RTP报文添加扩展头而写的struct。
pad:对流经哪个pad的RTP包添加扩展头。
add_hdr:是否需要给流经pad的RTP包添加扩展头。
set_time:如果RTP包中已经有了绝对时间扩展头,那是否要更新它。
abs_send_time_id:需要添加扩展头的媒体的净荷类型码。
typedef struct _ExtData
{
KmsRefStruct ref;
gint ulpfec_pt;
gint red_pt;
} ExtData;
ref:相当以这个struct的ref count,用来释放内存,对算法没什么意义。
ulpfec_pt:创建ulpfecdec、ulpfecend时使用的净荷类型码。
red_pt:创建reddec、redenc时使用的净荷类型码。
函数功能
static gboolean is_proto (const gchar * term, const gchar * opt, const gchar * proto)
term是否匹配正则表达式“(opt)?proto”,用来判断term是否为一个表示protocal的字符串。
static HdrExtData *hdr_ext_data_new (GstPad * pad, gboolean add_hdr, gboolean set_time,gint abs_send_time_id)
创建一个HdrExtData,用输入的参数给它赋值。
static void hdr_ext_data_destroy (HdrExtData * data)
释放一个HdrExtData类型的对象data。
static void hdr_ext_data_destroy_pointer (gpointer data)
调用hdr_ext_data_destroy
,利用指针data释放其对应的HdrExtData对象。
static void kms_base_rtp_endpoint_rtp_hdr_ext_set_time (guint8 * data)
获得绝对时间(ms),把它转换成大端模式存在data中。
static void kms_base_rtp_endpoint_add_rtp_hdr_ext (HdrExtData * data, GstBuffer * buffer)
主要作用:向一个RTP报文buffer中添加data所代表的扩展头,这个扩展头具体内容为一个包发送的绝对时间(abs_send_time)。
if (data->add_hdr) {
map_flags = GST_MAP_WRITE;
} else {
map_flags = GST_MAP_READ;
}
data->add_hdr表示是否要向RTP增加扩展头部,要加入扩展头就要以写方式打开。
if (!gst_rtp_buffer_map (buffer, map_flags, &rtp)) {
GST_WARNING_OBJECT (data->pad, "Can not map RTP buffer");
return;
}
把buffer作为rtp包映射到rtp上。
if (!gst_rtp_buffer_get_extension_onebyte_header (&rtp,
id, 0, (gpointer) & time, &size)) {
GST_TRACE_OBJECT (data->pad,
"RTP hdrext abs-send-time with id '%d' not found", id);
根据RFC5285的规定,RTP扩展头部有两种格式:one byte header和two byte header。
one byte header的格式:4 bit的id(就是净荷类型码),4 bit的length(byte),之后是length长的数据部分。
gst_rtp_buffer_get_extension_onebyte_header
函数的参数“id”和“0”表示寻找rtp中第一个使用id的头部扩展,把它的数据存在time中,它的大小为size(byte)。
如果这个函数返回FALSE说明没有这个扩展头部。
if (data->add_hdr) {
//需要添加头部扩展
GST_TRACE_OBJECT (data->pad, " Adding new one.");
} else {
GST_WARNING_OBJECT (data->pad, "Cannot add new one: not writable");
goto end;
}
time = g_malloc0 (RTP_HDR_EXT_ABS_SEND_TIME_SIZE);
if (data->set_time) {
//需要设定time
kms_base_rtp_endpoint_rtp_hdr_ext_set_time (time);
}
if (!gst_rtp_buffer_add_extension_onebyte_header (&rtp,
id, time, RTP_HDR_EXT_ABS_SEND_TIME_SIZE)) {
//给rtp加入头部扩展
GST_WARNING_OBJECT (data->pad, "RTP hdrext abs-send-time not added");
}
g_free (time);
没有对应的头部扩展时,根据data的参数决定是否加入头部扩展。
else if (data->set_time) {
if (size != RTP_HDR_EXT_ABS_SEND_TIME_SIZE) {
//size和time类头部扩展的大小不匹配
GST_WARNING_OBJECT (data->pad,
"RTP hdrext abs-send-time size with id '%d' not matching", id);
} else {
//更新头部扩展
GST_TRACE_OBJECT (data->pad,
"RTP hdrext abs-send-time with id '%d' found. Update time.", id);
kms_base_rtp_endpoint_rtp_hdr_ext_set_time (time);
}
}
在rtp中已经有对应的头部扩展。
static gboolean kms_base_rtp_endpoint_add_rtp_hdr_ext_bufflist (GstBuffer ** buf, guint idx, HdrExtData * data)
对buffer list的第一个元素*buf调用kms_base_rtp_endpoint_add_rtp_hdr_ext
添加扩展头。
static GstPadProbeReturn kms_base_rtp_endpoint_add_rtp_hdr_ext_probe (GstPad * pad, GstPadProbeInfo * info, gpointer gp)
主要作用:给buffer添加扩展头的probe。
HdrExtData *data = (HdrExtData *) gp;
gp实际上就是一个HdrExtData。
if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER) {
//buffer
GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);
if (data->add_hdr) {
buffer = gst_buffer_make_writable (buffer);
}
kms_base_rtp_endpoint_add_rtp_hdr_ext (data, buffer);
GST_PAD_PROBE_INFO_DATA (info) = buffer;
}
如果data->add_hdr为TRUE,调用kms_base_rtp_endpoint_add_rtp_hdr_ext
添加扩展头。
else if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER_LIST) {
//buffer list
GstBufferList *bufflist = GST_PAD_PROBE_INFO_BUFFER_LIST (info);
if (data->add_hdr) {
bufflist = gst_buffer_list_make_writable (bufflist);
}
gst_buffer_list_foreach (bufflist,
(GstBufferListFunc) kms_base_rtp_endpoint_add_rtp_hdr_ext_bufflist,
data);
GST_PAD_PROBE_INFO_DATA (info) = bufflist;
}
对buffer list中的每个buffer,调用kms_base_rtp_endpoint_add_rtp_hdr_ext_bufflist
添加扩展头。
static void kms_base_rtp_endpoint_config_rtp_hdr_ext (KmsBaseRtpEndpoint * self, const GstSDPMedia * media, GstElement * payloader)
主要作用:向payloader的一个src pad上添加probe,使得经过这个pad的所有RTP包打上一个扩展头部,内容为这个包发送的绝对时间。
取media的净荷类型码。扩展头部的“id”需要使用这个净荷类型码。
pad = gst_element_get_static_pad (payloader, "src");
if (pad == NULL) {
GST_WARNING_OBJECT (self, "No RTP pad to configure hdrext probe.");
return;
}
取payloader的src pad。
payloader是给RTP报文添加净荷类型码的element。
data = hdr_ext_data_new (pad, TRUE, FALSE, abs_send_time_id);
gst_pad_add_probe (pad,
GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST,
kms_base_rtp_endpoint_add_rtp_hdr_ext_probe, data,
hdr_ext_data_destroy_pointer);
创建一个HdrExtData,设定自动给流经此pad的RTP包添加扩展头的probe。
static GstSDPDirection on_offer_media_direction (KmsSdpMediaDirectionExt * ext, KmsBaseRtpEndpoint * self)
取self的offer_dir属性,返回。
这个函数的意义就是,需要提供media direction时,提供自己的direction。
on_answer_media_direction (KmsSdpMediaDirectionExt * ext, GstSDPDirection dir, KmsBaseRtpEndpoint * self)
获得和dir相对的方向,返回。
这个函数的意义就是,对端给出了offer,我们要给出和offer的direction相对的direction。
相对的方向:
Sendonly对应Recvonly,SendRecv对应SendRecv,Inactive对应Inactive。
static gboolean on_offered_ulp_fec_cb (KmsSdpUlpFecExt * ext, guint pt, guint clock_rate, gpointer user_data)
user_data实际上是ExtData类型的。
设置user_data->ulpfec_pt为pt。
返回值为TRUE。(重复调用)
static gboolean on_offered_redundancy_cb (KmsSdpUlpFecExt * ext, guint pt, guint clock_rate, gpointer user_data)
user_data是一个ExtData。
设置user_data->red_pt为pt。
static void kms_base_rtp_configure_extensions (KmsBaseRtpEndpoint * self, const gchar * media, KmsSdpMediaHandler * handler)
主要作用:给media创建一个direction ext,加入handler中;如果支持前向纠错(self->priv->support_fec == TURE),还要创建ulpfec ext和red ext,加入handler中。
简单来说,就是给meida的handler添加extension。
mediadirext = kms_sdp_media_direction_ext_new ();
g_signal_connect (mediadirext, "on-offer-media-direction",
G_CALLBACK (on_offer_media_direction), self);
g_signal_connect (mediadirext, "on-answer-media-direction",
G_CALLBACK (on_answer_media_direction), self);
创建一个KmsSdpMediaDirectionExt,给他的信号绑定回调函数,根据收到的信号自动判断本地媒体描述的direction。
kms_sdp_media_handler_add_media_extension (handler,
KMS_I_SDP_MEDIA_EXTENSION (mediadirext));
把mediadirext加入handler->priv->extensions中。
if (!self->priv->support_fec) {
return;
}
判断是否支持前向纠错。后边都是对fec的配置。
edata = ext_data_new ();
kms_list_append (self->priv->prot_medias, g_strdup (media), edata);
创建一个ExtData,把它以<g_strdup (media),edata>(<索引,值>)插入self->priv->prot_medias(一个KmsList,保存所有使用fec的miedia)中。
ulpfecext = kms_sdp_ulp_fec_ext_new ();
redext = kms_sdp_redundant_ext_new ();
g_signal_connect_data (redext, "on-offered-redundancy",
G_CALLBACK (on_offered_redundancy_cb),
kms_ref_struct_ref (KMS_REF_STRUCT_CAST (edata)),
(GClosureNotify) kms_ref_struct_unref, 0);
g_signal_connect_data (ulpfecext, "on-offered-ulp-fec",
G_CALLBACK (on_offered_ulp_fec_cb),
kms_ref_struct_ref (KMS_REF_STRUCT_CAST (edata)),
(GClosureNotify) kms_ref_struct_unref, 0);
创建两个ext,分别给他们的信号绑定回调函数,在收到信号时分别设置edata->red_pt和edata->ulpfec_pt。
把两个ext加入handler。
static void kms_base_rtp_create_media_handler (KmsBaseSdpEndpoint * base_sdp, const gchar * media, KmsSdpMediaHandler ** handler)
主要作用:给handler的rtcp-mu、nack、goog-remb赋值,并配置它的direction ext、ulpfec ext、red ext。
g_object_set (G_OBJECT (*handler), "rtcp-mux", self->priv->rtcp_mux, NULL);
给handler的属性rtcp-mux赋值为self->priv->rtcp_mux。
if (KMS_IS_SDP_RTP_AVPF_MEDIA_HANDLER (*handler)) {
g_object_set (G_OBJECT (*handler),
"nack", self->priv->rtcp_nack,
"goog-remb", self->priv->rtcp_remb, NULL);
}
如果handler支持nack和remb,这两个也同样赋值。
h_avp = KMS_SDP_RTP_AVP_MEDIA_HANDLER (*handler);
kms_sdp_rtp_avp_media_handler_add_extmap (h_avp, RTP_HDR_EXT_ABS_SEND_TIME_ID,
RTP_HDR_EXT_ABS_SEND_TIME_URI, &err);
把<RTP_HDR_EXT_ABS_SEND_TIME_ID,RTP_HDR_EXT_ABS_SEND_TIME_URI>插入handler->extmap(一个GHash)。
RTP_HDR_EXT_ABS_SEND_TIME_ID是3;RTP_HDR_EXT_ABS_SEND_TIME_URI是“http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time”
kms_base_rtp_configure_extensions (self, media, *handler);
调用上一个函数kms_base_rtp_configure_extensions
,配置direction ext、ulpfec ext、red ext。
static ConnectPayloaderData *connect_payloader_data_new (KmsBaseRtpEndpoint * self, GstElement * payloader, KmsElementPadType type)
创建一个ConnectPayloaderData data,并给它的一些域赋值:data->self = self、data->payloader = payloader、data->type = type。
static KmsSSRCStats *ssrc_stats_new (guint ssrc, GstElement * jitter_buffer)
创建一个KmsSSRCStats stats,并赋值:stats->jitter_buffer = gst_object_ref (jitter_buffer)、stats->ssrc = ssrc。
static KmsRTPSessionStats *rtp_session_stats_new (GObject * rtp_session, GstSDPDirection direction)
创建一个KmsRTPSessionStats,并给它的rtp_session、direction域赋值。
static gboolean kms_base_rtp_endpoint_is_video_rtcp_nack (KmsBaseRtpEndpoint * self)
主要作用:判断视频流是否使用了nack。
KmsBaseSdpEndpoint *base_endpoint = KMS_BASE_SDP_ENDPOINT (self);
const GstSDPMessage *sdp =
kms_base_sdp_endpoint_get_first_negotiated_sdp (base_endpoint);
取base_endpoint->first_neg_sdp。这是这个endpoint的会话描述。
for (i = 0; i < len; i++) {
const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
const gchar *media_str = gst_sdp_media_get_media (media);
if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) {
return sdp_utils_media_has_rtcp_nack (media);
}
}
遍历会话描述的所有媒体描述(其实作用是找到media_str为“video”的媒体描述,即视频流)。判断它是否使用了nack。