pjsip视频

一、pjsip 2.0只支持ffmpeg中的视频编码器。如果要添加额外的编码器,可以参考
https://bitbucket.org/secollab/pjsip-mikey-sakke/commits/c865a04598b27e6eaafe4e16bf8610dd5bb38551
以添加vp8编码器为例的


二、呼叫是否使用视频,pjsua命令 vid enable改变的是app_config.vid.vid_cnt。这个值会影响call setting
在 make_call 的时候,
/* Update call setting */
pjsua_call_setting_default(&call_opt);
call_opt.aud_cnt = app_config.aud_cnt;
call_opt.vid_cnt = app_config.vid.vid_cnt;

在接电话的时候
    if (app_config.auto_answer > 0) {
pjsua_call_setting call_opt;

pjsua_call_setting_default(&call_opt);
call_opt.aud_cnt = app_config.aud_cnt;
call_opt.vid_cnt = app_config.vid.vid_cnt;

pjsua_call_answer2(call_id, &call_opt, app_config.auto_answer, NULL,
   NULL);
    }


三、编码器编码参数的设置,发生在
pjsua_media_channel_update 调用
pjmedia_vid_stream_info_from_sdp 调用 
get_video_codec_info_param 调用
pjmedia_vid_codec_mgr_get_default_param,获取codec默认编码参数。codec的默认编码参数是放在ffmpeg_codec_desc codec_desc这个数组中的。
然后根据remote sdp中的 b=TIAS 调整编码码率,使编码码率不超过对方的偏好获取后的编码参数放在pjmedia_vid_stream_info the_si结构体中,直至运行到pjmedia_vid_stream_create 的时候,参数会写入call_med->strm.v.stream(pjmedia_vid_stream_create的最后一个参数)。想修改视频编码的默认参数,可以参考vid_handle_menu


四、采集参数的设置
pjsua_media_channel_update 调用
pjsua_vid_channel_update 调用
pjmedia_vid_stream_get_port 获得call_med->strm.v.stream里面指定的video的format

在create_vid_win里面
if (fmt)
    vp_param.vidparam.fmt = *fmt;
media_port的fmt会覆盖掉采集器默认的参数

create_vid_win 调用 
pjmedia_vid_dev_stream_create 调用
f->op->create_stream 创建采集源


五、通过SDP,与对方解码能力相匹配
ffmpeg_codec_open 时会调用
open_ffmpeg_codec 调用
h264_preopen 调用
pjmedia_vid_codec_h264_apply_fmtp,更改自身的编码参数以适应对方SDP中声明的profile-level-id。

find_highest_res 负责具体计算编码的分辨率以适应profile level。先根据当前的设置的编码分辨率,计算出aspect ratio。然后根据当前的帧率和level的限制,计算出每帧最多可以有多少个mb。然后再根据mb的数量和原来的aspect ratio,推算出宽高

h264每个level对应的最大mbs,码率等参数,在 const h264_level_info_t level_info[] 表中定义
csipsimple在设置完分辨率参数之后,会自动设置profile-level-id,具体参考codec_h264_set_profile函数
http://code.google.com/p/csipsimple/source/browse/trunk/CSipSimple/jni/csipsimple-wrapper/src/csipsimple_codecs_utils.c


六、video port
一次视频通话需要创建三个video port。第一个是回显对方的视频,第二个是本地采集源,第三个是本地采集的预览。

vp->stream_role 分为主动型和被动型。主动型video port自己有回调函数,在获取到帧数据时可以自动回调strm->vid_cb.capture_cb,把采集的的帧传给pjsip。一般的采集源都有自己的线程在驱动,都属于主动型。只有colorbar、avi_dev是属于被动型采集源,因为帧都不是实时采集回来的,不需要独立的线程来采集


七、分辨率的转换
当采集源的分辨率或者图像格式与编码器不一致时,需要调用转换器进行格式转换。相关代码实现主要在vid_tee.c中,涉及的函数包括 pjmedia_vid_tee_add_dst_port2, tee_put_frame。目前pjsip后端只有一种converter,是使用ffmpeg的converter_libswscale.c
一个采集源一般要创建两个tee的dst port,一个创建在create_vid_win,连接到preview窗口;另一个创建调用在pjsua_vid_channel_update,连接到编码器上面


八、通话过程中通过INFO来请求对方发送I帧
发送方在 call_media_on_event ,接收到PJMEDIA_EVENT_KEYFRAME_MISSING事件后,通过调用 pjsua_call_send_request 会发送的INFO消息给对方,INFO里面带的数据是application/media_control+xml。接收方处理该事件是在 pjsua_call_on_tsx_state_changed,里面有一串条件判断,如果是INFO消息,而且是application/media_control+xml,会调用 pjsua_media_apply_xml_control。pjsua_media_apply_xml_control 里面调用 pjmedia_vid_stream_send_keyframe 控制发送I帧


九、获取通话中是否带有视频
判断来电是否包含视频,on_incoming_call里面
调用
    pjsua_call_get_info(call_id, &call_info);
获取通话信息 
if (call_info.rem_offerer && call_info.rem_vid_cnt) {
    // 有视频
}

判断被叫是否开启了视频,on_call_state 里面
    if (call_info.state == PJSIP_INV_STATE_CONFIRMED)
    {
        pj_bool_t has_video = PJ_FALSE;

        if (call_info.media_cnt == 2
            && call_info.media[1].type == PJMEDIA_TYPE_VIDEO  
            && call_info.media[1].dir != PJMEDIA_DIR_NONE)
        {
            has_video = PJ_TRUE;
        }
    }

十、解码
on_rx_rtp,当stream->dec_frame.size == 0时,也就是没有已解码好的帧时,会尝试调用decode_frame 解码一帧数据。从另一方面来说,pjsip解码和接收rtp数据是在同一个线程中完成的,因此解码速度不够快会导致丢包。

decode_frame 不断从jitter buf中取出帧数据,看是否jitter buf中是否包含两种不同时间戳的帧。如果有,认为前面一个时间戳的所有数据帧都已经发送完毕,开始解码。

ffmpeg_codec_decode 负责具体的解码。它会调用ffmpeg_unpacketize来对RFC 3984的RTP格式进行解码,重新提取出每个nal。

回显有自己一个独立的clock_thread。该线程最终会调用vid_stream.c中的get_frame,从buffer中取走解码完的数据帧,并重新把stream->dec_frame.size置0。

目前pjsip的实现,实际上不会出现FU-A的拆包或者STEP-A的聚包。因为h264_preopen函数里面,已经写死了
    /* Better always send in single NAL mode for better compatibility */
    pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL;
因此不会出现拆包的情况。但是它又是如何保证NAL的大小不会超过MTU的呢?具体在这里
/* Limit NAL unit size as we prefer single NAL unit packetization */
if (!AV_OPT_SET_INT(ctx->priv_data, "slice-max-size", ff->param.enc_mtu))
{
    PJ_LOG(3, (THIS_FILE, "Failed to set H264 max NAL size to %d",
       ff->param.enc_mtu));
}

你可能感兴趣的:(pjsip视频)