Changeset 3206


Ignore:
Timestamp:
Jun 15, 2010 9:56:39 AM (9 years ago)
Author:
nanang
Message:

Fix #476:

  • Added lock codec feature to make sure that only one codec is active, by updating media session using UPDATE (if remote supports it) or re-INVITE.
  • Added few SIPp test scenarios.
Location:
pjproject/trunk
Files:
4 added
2 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h

    r3144 r3206  
    9191    char    last_text_buf_[128];    /**< Buffer for last_text.              */ 
    9292 
     93    struct { 
     94        pj_timer_entry   reinv_timer;/**< Reinvite retry timer.             */ 
     95        pjmedia_sdp_session *new_sdp;/**< The new SDP offer.                */ 
     96    } lock_codec;                    /**< Data for codec locking when answer 
     97                                          contains multiple codecs.         */ 
     98 
    9399} pjsua_call; 
    94100 
  • pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c

    r3196 r3206  
    2323 
    2424#define THIS_FILE               "pjsua_call.c" 
     25 
     26 
     27/* Retry interval of sending re-INVITE for locking a codec when remote 
     28 * SDP answer contains multiple codec, in milliseconds. 
     29 */ 
     30#define LOCK_CODEC_RETRY_INTERVAL   200 
    2531 
    2632 
     
    15121518        pjsip_dlg_dec_lock(dlg); 
    15131519        return status; 
     1520    } 
     1521 
     1522    /* Stop lock codec timer, if it is active */ 
     1523    if (call->lock_codec.reinv_timer.id) { 
     1524        pjsip_endpt_cancel_timer(pjsua_var.endpt,  
     1525                                 &call->lock_codec.reinv_timer); 
     1526        call->lock_codec.reinv_timer.id = PJ_FALSE; 
    15141527    } 
    15151528 
     
    29142927 
    29152928 
     2929/* Timer callback to close sound device */ 
     2930static void reinv_timer_cb(pj_timer_heap_t *th, 
     2931                           pj_timer_entry *entry) 
     2932{ 
     2933    pjsua_call_id call_id = (pjsua_call_id)(pj_size_t)entry->user_data; 
     2934    pjsip_dialog *dlg; 
     2935    pjsua_call *call; 
     2936    pjsip_tx_data *tdata; 
     2937    pj_status_t status; 
     2938 
     2939    PJ_UNUSED_ARG(th); 
     2940 
     2941    pjsua_var.calls[call_id].lock_codec.reinv_timer.id = PJ_FALSE; 
     2942 
     2943    status = acquire_call("reinv_timer_cb()", call_id, &call, &dlg); 
     2944    if (status != PJ_SUCCESS) 
     2945        return; 
     2946 
     2947    /* Verify if another SDP negotiation is in progress, e.g: session timer 
     2948     * or another re-INVITE. 
     2949     */ 
     2950    if (call->inv==NULL || call->inv->neg==NULL || 
     2951        pjmedia_sdp_neg_get_state(call->inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) 
     2952    { 
     2953        goto on_return; 
     2954    } 
     2955 
     2956    /* Verify if another SDP negotiation has been completed by comparing 
     2957     * the SDP version. 
     2958     */ 
     2959    { 
     2960        const pjmedia_sdp_session *sdp; 
     2961 
     2962        status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp); 
     2963        if (status == PJ_SUCCESS && 
     2964            sdp->origin.version > call->lock_codec.new_sdp->origin.version) 
     2965        { 
     2966            goto on_return; 
     2967        } 
     2968    } 
     2969 
     2970    /* Create re-INVITE with the new offer */ 
     2971    status = pjsip_inv_reinvite(call->inv, NULL, call->lock_codec.new_sdp, 
     2972                                &tdata); 
     2973    if (status == PJ_EINVALIDOP) { 
     2974        /* Ups, let's reschedule again */ 
     2975        pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL}; 
     2976        call->lock_codec.reinv_timer.id = PJ_TRUE; 
     2977        pjsip_endpt_schedule_timer(pjsua_var.endpt,  
     2978                                   &call->lock_codec.reinv_timer, &delay); 
     2979    } else if (status != PJ_SUCCESS) { 
     2980        pjsua_perror(THIS_FILE, "Failed creating re-INVITE in lock codec", 
     2981                     status); 
     2982    } 
     2983 
     2984    /* Send the UPDATE/re-INVITE request */ 
     2985    status = pjsip_inv_send_msg(call->inv, tdata); 
     2986    if (status != PJ_SUCCESS) { 
     2987        pjsua_perror(THIS_FILE, "Failed sending re-INVITE in lock codec", 
     2988                     status); 
     2989    } 
     2990 
     2991on_return: 
     2992    pjsip_dlg_dec_lock(dlg); 
     2993} 
     2994 
     2995 
     2996/* Check if the specified format can be skipped in counting codecs */ 
     2997static pj_bool_t is_non_av_fmt(const pjmedia_sdp_media *m, 
     2998                                  const pj_str_t *fmt) 
     2999{ 
     3000    unsigned pt; 
     3001 
     3002    pt = pj_strtoul(fmt); 
     3003 
     3004    /* Check for comfort noise */ 
     3005    if (pt == PJMEDIA_RTP_PT_CN) 
     3006        return PJ_TRUE; 
     3007 
     3008    /* Dynamic PT, check the format name */ 
     3009    if (pt >= 96) { 
     3010        pjmedia_sdp_attr *a; 
     3011        pjmedia_sdp_rtpmap rtpmap; 
     3012 
     3013        /* Get the format name */ 
     3014        a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); 
     3015        if (a && pjmedia_sdp_attr_get_rtpmap(a, &rtpmap)==PJ_SUCCESS) { 
     3016            /* Check for telephone-event */ 
     3017            if (pj_stricmp2(&rtpmap.enc_name, "telephone-event")==0) 
     3018                return PJ_TRUE; 
     3019        } else { 
     3020            /* Invalid SDP, should not reach here */ 
     3021            pj_assert(!"SDP should have been validated!"); 
     3022            return PJ_TRUE; 
     3023        } 
     3024    } 
     3025 
     3026    return PJ_FALSE; 
     3027} 
     3028 
     3029 
     3030/* Check if remote answerer has given us more than one codecs. If so, 
     3031 * create another offer with one codec only to lock down the codec. 
     3032 */ 
     3033static pj_status_t lock_codec(pjsua_call *call) 
     3034{ 
     3035    const pj_str_t st_update = {"UPDATE", 6}; 
     3036    pjsip_inv_session *inv = call->inv; 
     3037    const pjmedia_sdp_session *local_sdp; 
     3038    const pjmedia_sdp_session *remote_sdp; 
     3039    const pjmedia_sdp_media *rem_m; 
     3040    pjmedia_sdp_session *new_sdp; 
     3041    pjmedia_sdp_media *m; 
     3042    pjsip_tx_data *tdata; 
     3043    unsigned i, codec_cnt = 0; 
     3044    pj_status_t status; 
     3045 
     3046    if (!pjmedia_sdp_neg_was_answer_remote(inv->neg)) 
     3047        return PJ_SUCCESS; 
     3048 
     3049    status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); 
     3050    if (status != PJ_SUCCESS) 
     3051        return status; 
     3052    status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); 
     3053    if (status != PJ_SUCCESS) 
     3054        return status; 
     3055 
     3056    PJ_ASSERT_RETURN(call->audio_idx>=0 && 
     3057                     call->audio_idx < (int)remote_sdp->media_count, 
     3058                     PJ_EINVALIDOP); 
     3059 
     3060    rem_m = remote_sdp->media[call->audio_idx]; 
     3061 
     3062    /* Check if media is disabled or only one format in the answer. */ 
     3063    if (rem_m->desc.port==0 || rem_m->desc.fmt_count==1) 
     3064        return PJ_SUCCESS; 
     3065 
     3066    /* Count the formats in the answer. */ 
     3067    for (i=0; i<rem_m->desc.fmt_count && codec_cnt <= 1; ++i) { 
     3068        if (!is_non_av_fmt(rem_m, &rem_m->desc.fmt[i])) 
     3069            ++codec_cnt; 
     3070    } 
     3071 
     3072    if (codec_cnt <= 1) { 
     3073        /* Answer contains single codec. */ 
     3074        return PJ_SUCCESS; 
     3075    } 
     3076 
     3077    PJ_LOG(3, (THIS_FILE, "Got answer with multiple codecs, start " 
     3078                          "updating media session to use only one codec..")); 
     3079 
     3080    /* Clone the offer */ 
     3081    new_sdp = pjmedia_sdp_session_clone(inv->pool_prov, local_sdp); 
     3082    /* Note that the usage of pool_prov above is risky when locking codec 
     3083     * delays the re-INVITE (using timer) and there are two SDP negotiations 
     3084     * done before the re-INVITE. 
     3085     */ 
     3086 
     3087    /* Update the new offer so it contains only a codec. Note that formats 
     3088     * order in the offer should have been matched to the answer, so we can 
     3089     * just directly update the offer without looking-up the answer. 
     3090     */ 
     3091    m = new_sdp->media[call->audio_idx]; 
     3092    codec_cnt = 0; 
     3093    i = 0; 
     3094    while (i < m->desc.fmt_count) { 
     3095        pjmedia_sdp_attr *a; 
     3096        pj_str_t *fmt = &m->desc.fmt[i]; 
     3097 
     3098        if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) { 
     3099            ++i; 
     3100            continue; 
     3101        } 
     3102 
     3103        /* Remove format */ 
     3104        a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); 
     3105        if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); 
     3106        a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "fmtp", fmt); 
     3107        if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); 
     3108        pj_array_erase(m->desc.fmt, sizeof(m->desc.fmt[0]),  
     3109                       m->desc.fmt_count, i); 
     3110        --m->desc.fmt_count; 
     3111    } 
     3112 
     3113    /* Send new SDP offer via UPDATE or re-INVITE */ 
     3114    if (pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &st_update)== 
     3115        PJSIP_DIALOG_CAP_SUPPORTED) 
     3116    { 
     3117        /* Create UPDATE with the new offer */ 
     3118        status = pjsip_inv_update(inv, NULL, new_sdp, &tdata); 
     3119        if (status != PJ_SUCCESS) 
     3120            return status; 
     3121 
     3122    } else { 
     3123        /* Create re-INVITE with the new offer */ 
     3124        status = pjsip_inv_reinvite(inv, NULL, new_sdp, &tdata); 
     3125        if (status == PJ_EINVALIDOP) { 
     3126            /* Current INVITE transaction is pending, reschedule re-INVITE. */ 
     3127            pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL}; 
     3128 
     3129            call->lock_codec.new_sdp = new_sdp; 
     3130            pj_timer_entry_init(&call->lock_codec.reinv_timer, PJ_TRUE, 
     3131                                (void*)(pj_size_t)call->index, 
     3132                                &reinv_timer_cb); 
     3133            pjsip_endpt_schedule_timer(pjsua_var.endpt,  
     3134                                       &call->lock_codec.reinv_timer, &delay); 
     3135            return PJ_SUCCESS; 
     3136 
     3137        } else if (status != PJ_SUCCESS) 
     3138            return status; 
     3139    } 
     3140 
     3141    /* Send the UPDATE/re-INVITE request */ 
     3142    status = pjsip_inv_send_msg(inv, tdata); 
     3143    if (status != PJ_SUCCESS) 
     3144        return status; 
     3145 
     3146    return PJ_SUCCESS; 
     3147} 
     3148 
    29163149/* 
    29173150 * This callback receives notification from invite session when the 
     
    29473180        case PJSIP_INV_STATE_CONFIRMED: 
    29483181            pj_gettimeofday(&call->conn_time); 
     3182 
     3183            /* Ticket #476, locking a codec in the media session. */ 
     3184            { 
     3185                pj_status_t status; 
     3186                status = lock_codec(call); 
     3187                if (status != PJ_SUCCESS) { 
     3188                    pjsua_perror(THIS_FILE, "Unable to lock codec", status); 
     3189                } 
     3190            } 
     3191 
    29493192            break; 
    29503193        case PJSIP_INV_STATE_DISCONNECTED: 
     
    29663209                           sizeof(call->last_text_buf_)); 
    29673210            } 
     3211 
     3212            /* Stop lock codec timer, if it is active */ 
     3213            if (call->lock_codec.reinv_timer.id) { 
     3214                pjsip_endpt_cancel_timer(pjsua_var.endpt,  
     3215                                         &call->lock_codec.reinv_timer); 
     3216                call->lock_codec.reinv_timer.id = PJ_FALSE; 
     3217            } 
    29683218            break; 
    29693219        default: 
     
    31703420    const pjmedia_sdp_session *local_sdp; 
    31713421    const pjmedia_sdp_session *remote_sdp; 
     3422    const pj_str_t st_update = {"UPDATE", 6}; 
    31723423 
    31733424    PJSUA_LOCK(); 
     
    32413492    } 
    32423493 
     3494    /* Ticket #476, handle the case of early media and remote support UPDATE */ 
     3495    if (inv->state == PJSIP_INV_STATE_EARLY &&  
     3496        pjmedia_sdp_neg_was_answer_remote(inv->neg) && 
     3497        pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &st_update)== 
     3498        PJSIP_DIALOG_CAP_SUPPORTED) 
     3499    { 
     3500        status = lock_codec(call); 
     3501        if (status != PJ_SUCCESS) { 
     3502            pjsua_perror(THIS_FILE, "Unable to lock codec", status); 
     3503        } 
     3504    } 
    32433505 
    32443506    /* Call application callback, if any */ 
Note: See TracChangeset for help on using the changeset viewer.