Changeset 3349


Ignore:
Timestamp:
Oct 20, 2010 5:31:08 AM (11 years ago)
Author:
bennylp
Message:

Fixed #1149 (Crash when holding the call after receiving SDP answer with multiple codecs (thanks Cyril GY for the report)):

  • avoid using pre-created SDP, but rather use timer and create SDP right when the UPDATE/re-INVITE is about to be sent, to avoid the use of stale pool
  • also fixed bug in the old code when the lock codec feature is not activated after the call is confirmed
Location:
pjproject/trunk/pjsip
Files:
2 edited

Legend:

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

    r3330 r3349  
    9494    struct { 
    9595        pj_timer_entry   reinv_timer;/**< Reinvite retry timer.             */ 
    96         pjmedia_sdp_session *new_sdp;/**< The new SDP offer.                */ 
     96        pj_uint32_t      sdp_ver;    /**< SDP version of the bad answer     */ 
     97        int              retry_cnt;  /**< Retry count.                      */ 
    9798    } lock_codec;                    /**< Data for codec locking when answer 
    9899                                          contains multiple codecs.         */ 
  • pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c

    r3334 r3349  
    3030#define LOCK_CODEC_RETRY_INTERVAL   200 
    3131 
     32/* 
     33 * Max UPDATE/re-INVITE retry to lock codec 
     34 */ 
     35#define LOCK_CODEC_MAX_RETRY         5 
    3236 
    3337/* This callback receives notification from invite session when the 
     
    29892993} 
    29902994 
    2991  
    2992 /* Timer callback to close sound device */ 
     2995/* Proto */ 
     2996static pj_status_t perform_lock_codec(pjsua_call *call); 
     2997 
     2998/* Timer callback to send re-INVITE or UPDATE to lock codec */ 
    29932999static void reinv_timer_cb(pj_timer_heap_t *th, 
    29943000                           pj_timer_entry *entry) 
     
    29973003    pjsip_dialog *dlg; 
    29983004    pjsua_call *call; 
    2999     pjsip_tx_data *tdata; 
    30003005    pj_status_t status; 
    30013006 
     
    30073012    if (status != PJ_SUCCESS) 
    30083013        return; 
     3014 
     3015    status = perform_lock_codec(call); 
     3016 
     3017    pjsip_dlg_dec_lock(dlg); 
     3018} 
     3019 
     3020 
     3021/* Check if the specified format can be skipped in counting codecs */ 
     3022static pj_bool_t is_non_av_fmt(const pjmedia_sdp_media *m, 
     3023                                  const pj_str_t *fmt) 
     3024{ 
     3025    const pj_str_t STR_TEL = {"telephone-event", 15}; 
     3026    unsigned pt; 
     3027 
     3028    pt = pj_strtoul(fmt); 
     3029 
     3030    /* Check for comfort noise */ 
     3031    if (pt == PJMEDIA_RTP_PT_CN) 
     3032        return PJ_TRUE; 
     3033 
     3034    /* Dynamic PT, check the format name */ 
     3035    if (pt >= 96) { 
     3036        pjmedia_sdp_attr *a; 
     3037        pjmedia_sdp_rtpmap rtpmap; 
     3038 
     3039        /* Get the format name */ 
     3040        a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); 
     3041        if (a && pjmedia_sdp_attr_get_rtpmap(a, &rtpmap)==PJ_SUCCESS) { 
     3042            /* Check for telephone-event */ 
     3043            if (pj_stricmp(&rtpmap.enc_name, &STR_TEL)==0) 
     3044                return PJ_TRUE; 
     3045        } else { 
     3046            /* Invalid SDP, should not reach here */ 
     3047            pj_assert(!"SDP should have been validated!"); 
     3048            return PJ_TRUE; 
     3049        } 
     3050    } 
     3051 
     3052    return PJ_FALSE; 
     3053} 
     3054 
     3055 
     3056/* Send re-INVITE or UPDATE with new SDP offer to select only one codec 
     3057 * out of several codecs presented by callee in his answer. 
     3058 */ 
     3059static pj_status_t perform_lock_codec(pjsua_call *call) 
     3060{ 
     3061    const pj_str_t STR_UPDATE = {"UPDATE", 6}; 
     3062    const pjmedia_sdp_session *local_sdp = NULL, *new_sdp; 
     3063    const pjmedia_sdp_media *rem_m; 
     3064    pjmedia_sdp_media *m; 
     3065    unsigned i, codec_cnt = 0; 
     3066    pj_bool_t rem_can_update; 
     3067    pjsip_tx_data *tdata; 
     3068    pj_status_t status; 
     3069 
     3070    PJ_ASSERT_RETURN(call->lock_codec.reinv_timer.id==PJ_FALSE, 
     3071                     PJ_EINVALIDOP); 
    30093072 
    30103073    /* Verify if another SDP negotiation is in progress, e.g: session timer 
     
    30143077        pjmedia_sdp_neg_get_state(call->inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) 
    30153078    { 
    3016         goto on_return; 
     3079        return PJ_SUCCESS; 
    30173080    } 
    30183081 
     
    30203083     * the SDP version. 
    30213084     */ 
     3085    status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); 
     3086    if (status != PJ_SUCCESS) 
     3087        return status; 
     3088    if (local_sdp->origin.version > call->lock_codec.sdp_ver) 
     3089        return PJMEDIA_SDP_EINVER; 
     3090 
     3091    PJ_LOG(3, (THIS_FILE, "Updating media session to use only one codec..")); 
     3092 
     3093    /* Update the new offer so it contains only a codec. Note that formats 
     3094     * order in the offer should have been matched to the answer, so we can 
     3095     * just directly update the offer without looking-up the answer. 
     3096     */ 
     3097    new_sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, local_sdp); 
     3098    rem_m = local_sdp->media[call->audio_idx]; 
     3099    m = new_sdp->media[call->audio_idx]; 
     3100    codec_cnt = 0; 
     3101    i = 0; 
     3102    while (i < m->desc.fmt_count) { 
     3103        pjmedia_sdp_attr *a; 
     3104        pj_str_t *fmt = &m->desc.fmt[i]; 
     3105 
     3106        if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) { 
     3107            ++i; 
     3108            continue; 
     3109        } 
     3110 
     3111        /* Remove format */ 
     3112        a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); 
     3113        if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); 
     3114        a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "fmtp", fmt); 
     3115        if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); 
     3116        pj_array_erase(m->desc.fmt, sizeof(m->desc.fmt[0]), 
     3117                       m->desc.fmt_count, i); 
     3118        --m->desc.fmt_count; 
     3119    } 
     3120 
     3121    /* Last check if SDP trully needs to be udpated. It is possible that OA 
     3122     * negotiations have completed and SDP has changed but remote didn't 
     3123     * increase it's SDP version. 
     3124     */ 
     3125    if (rem_m->desc.fmt_count == m->desc.fmt_count || 
     3126        rem_m->desc.port == 0 || m->desc.port == 0) 
    30223127    { 
    3023         const pjmedia_sdp_session *sdp; 
    3024  
    3025         status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp); 
    3026         if (status == PJ_SUCCESS && 
    3027             sdp->origin.version > call->lock_codec.new_sdp->origin.version) 
    3028         { 
    3029             goto on_return; 
    3030         } 
    3031     } 
    3032  
    3033     /* Create re-INVITE with the new offer */ 
    3034     status = pjsip_inv_reinvite(call->inv, NULL, call->lock_codec.new_sdp, 
    3035                                 &tdata); 
    3036     if (status == PJ_EINVALIDOP) { 
     3128        return PJ_SUCCESS; 
     3129    } 
     3130 
     3131    /* Send UPDATE or re-INVITE */ 
     3132    rem_can_update = pjsip_dlg_remote_has_cap(call->inv->dlg, 
     3133                                              PJSIP_H_ALLOW, 
     3134                                              NULL, &STR_UPDATE) == 
     3135                                                PJSIP_DIALOG_CAP_SUPPORTED; 
     3136    if (rem_can_update) { 
     3137        status = pjsip_inv_update(call->inv, NULL, new_sdp, &tdata); 
     3138    } else { 
     3139        status = pjsip_inv_reinvite(call->inv, NULL, new_sdp, &tdata); 
     3140    } 
     3141 
     3142    if (status==PJ_EINVALIDOP && 
     3143        ++call->lock_codec.retry_cnt <= LOCK_CODEC_MAX_RETRY) 
     3144    { 
    30373145        /* Ups, let's reschedule again */ 
    30383146        pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL}; 
     3147        pj_time_val_normalize(&delay); 
    30393148        call->lock_codec.reinv_timer.id = PJ_TRUE; 
    3040         pjsip_endpt_schedule_timer(pjsua_var.endpt,  
     3149        pjsip_endpt_schedule_timer(pjsua_var.endpt, 
    30413150                                   &call->lock_codec.reinv_timer, &delay); 
     3151        return status; 
    30423152    } else if (status != PJ_SUCCESS) { 
    3043         pjsua_perror(THIS_FILE, "Failed creating re-INVITE in lock codec", 
     3153        pjsua_perror(THIS_FILE, "Error creating UPDATE/re-INVITE to lock codec", 
    30443154                     status); 
     3155        return status; 
    30453156    } 
    30463157 
     
    30483159    status = pjsip_inv_send_msg(call->inv, tdata); 
    30493160    if (status != PJ_SUCCESS) { 
    3050         pjsua_perror(THIS_FILE, "Failed sending re-INVITE in lock codec", 
     3161        pjsua_perror(THIS_FILE, "Error sending UPDATE/re-INVITE in lock codec", 
    30513162                     status); 
    3052     } 
    3053  
    3054 on_return: 
    3055     pjsip_dlg_dec_lock(dlg); 
    3056 } 
    3057  
    3058  
    3059 /* Check if the specified format can be skipped in counting codecs */ 
    3060 static pj_bool_t is_non_av_fmt(const pjmedia_sdp_media *m, 
    3061                                   const pj_str_t *fmt) 
    3062 { 
    3063     unsigned pt; 
    3064  
    3065     pt = pj_strtoul(fmt); 
    3066  
    3067     /* Check for comfort noise */ 
    3068     if (pt == PJMEDIA_RTP_PT_CN) 
    3069         return PJ_TRUE; 
    3070  
    3071     /* Dynamic PT, check the format name */ 
    3072     if (pt >= 96) { 
    3073         pjmedia_sdp_attr *a; 
    3074         pjmedia_sdp_rtpmap rtpmap; 
    3075  
    3076         /* Get the format name */ 
    3077         a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); 
    3078         if (a && pjmedia_sdp_attr_get_rtpmap(a, &rtpmap)==PJ_SUCCESS) { 
    3079             /* Check for telephone-event */ 
    3080             if (pj_stricmp2(&rtpmap.enc_name, "telephone-event")==0) 
    3081                 return PJ_TRUE; 
    3082         } else { 
    3083             /* Invalid SDP, should not reach here */ 
    3084             pj_assert(!"SDP should have been validated!"); 
    3085             return PJ_TRUE; 
    3086         } 
    3087     } 
    3088  
    3089     return PJ_FALSE; 
    3090 } 
    3091  
     3163        return status; 
     3164    } 
     3165 
     3166    return status; 
     3167} 
    30923168 
    30933169/* Check if remote answerer has given us more than one codecs. If so, 
     
    30963172static pj_status_t lock_codec(pjsua_call *call) 
    30973173{ 
    3098     const pj_str_t st_update = {"UPDATE", 6}; 
    30993174    pjsip_inv_session *inv = call->inv; 
    31003175    const pjmedia_sdp_session *local_sdp; 
    31013176    const pjmedia_sdp_session *remote_sdp; 
    31023177    const pjmedia_sdp_media *rem_m; 
    3103     pjmedia_sdp_session *new_sdp; 
    3104     pjmedia_sdp_media *m; 
    3105     pjsip_tx_data *tdata; 
    3106     unsigned i, codec_cnt = 0; 
     3178    unsigned codec_cnt=0, i; 
     3179    pj_time_val delay = {0, 0}; 
    31073180    pj_status_t status; 
    31083181 
     
    31383211    } 
    31393212 
    3140     PJ_LOG(3, (THIS_FILE, "Got answer with multiple codecs, start " 
     3213    PJ_LOG(3, (THIS_FILE, "Got answer with multiple codecs, scheduling " 
    31413214                          "updating media session to use only one codec..")); 
    31423215 
    3143     /* Clone the offer */ 
    3144     new_sdp = pjmedia_sdp_session_clone(inv->pool_prov, local_sdp); 
    3145     /* Note that the usage of pool_prov above is risky when locking codec 
    3146      * delays the re-INVITE (using timer) and there are two SDP negotiations 
    3147      * done before the re-INVITE. 
     3216    call->lock_codec.sdp_ver = local_sdp->origin.version; 
     3217    call->lock_codec.retry_cnt = 0; 
     3218 
     3219    /* Stop lock codec timer, if it is active */ 
     3220    if (call->lock_codec.reinv_timer.id) { 
     3221        pjsip_endpt_cancel_timer(pjsua_var.endpt, 
     3222                                 &call->lock_codec.reinv_timer); 
     3223        call->lock_codec.reinv_timer.id = PJ_FALSE; 
     3224    } 
     3225 
     3226    /* Can't send UPDATE or re-INVITE now, so just schedule it immediately. 
     3227     * See: https://trac.pjsip.org/repos/ticket/1149 
    31483228     */ 
    3149  
    3150     /* Update the new offer so it contains only a codec. Note that formats 
    3151      * order in the offer should have been matched to the answer, so we can 
    3152      * just directly update the offer without looking-up the answer. 
    3153      */ 
    3154     m = new_sdp->media[call->audio_idx]; 
    3155     codec_cnt = 0; 
    3156     i = 0; 
    3157     while (i < m->desc.fmt_count) { 
    3158         pjmedia_sdp_attr *a; 
    3159         pj_str_t *fmt = &m->desc.fmt[i]; 
    3160  
    3161         if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) { 
    3162             ++i; 
    3163             continue; 
    3164         } 
    3165  
    3166         /* Remove format */ 
    3167         a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); 
    3168         if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); 
    3169         a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "fmtp", fmt); 
    3170         if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); 
    3171         pj_array_erase(m->desc.fmt, sizeof(m->desc.fmt[0]),  
    3172                        m->desc.fmt_count, i); 
    3173         --m->desc.fmt_count; 
    3174     } 
    3175  
    3176     /* Send new SDP offer via UPDATE or re-INVITE */ 
    3177     if (pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &st_update)== 
    3178         PJSIP_DIALOG_CAP_SUPPORTED) 
    3179     { 
    3180         /* Create UPDATE with the new offer */ 
    3181         status = pjsip_inv_update(inv, NULL, new_sdp, &tdata); 
    3182         if (status != PJ_SUCCESS) 
    3183             return status; 
    3184  
    3185     } else { 
    3186         /* Create re-INVITE with the new offer */ 
    3187         status = pjsip_inv_reinvite(inv, NULL, new_sdp, &tdata); 
    3188         if (status == PJ_EINVALIDOP) { 
    3189             /* Current INVITE transaction is pending, reschedule re-INVITE. */ 
    3190             pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL}; 
    3191  
    3192             call->lock_codec.new_sdp = new_sdp; 
    3193             pj_timer_entry_init(&call->lock_codec.reinv_timer, PJ_TRUE, 
    3194                                 (void*)(pj_size_t)call->index, 
    3195                                 &reinv_timer_cb); 
    3196             pjsip_endpt_schedule_timer(pjsua_var.endpt,  
    3197                                        &call->lock_codec.reinv_timer, &delay); 
    3198             return PJ_SUCCESS; 
    3199  
    3200         } else if (status != PJ_SUCCESS) 
    3201             return status; 
    3202     } 
    3203  
    3204     /* Send the UPDATE/re-INVITE request */ 
    3205     status = pjsip_inv_send_msg(inv, tdata); 
    3206     if (status != PJ_SUCCESS) 
    3207         return status; 
     3229    pj_timer_entry_init(&call->lock_codec.reinv_timer, PJ_TRUE, 
     3230                        (void*)(pj_size_t)call->index, 
     3231                        &reinv_timer_cb); 
     3232    pjsip_endpt_schedule_timer(pjsua_var.endpt, 
     3233                               &call->lock_codec.reinv_timer, &delay); 
    32083234 
    32093235    return PJ_SUCCESS; 
     
    35653591            pjsua_perror(THIS_FILE, "Unable to lock codec", status); 
    35663592        } 
     3593    } else if (inv->state == PJSIP_INV_STATE_CONFIRMED && 
     3594               call->media_st == PJSUA_CALL_MEDIA_ACTIVE) 
     3595    { 
     3596        /* https://trac.pjsip.org/repos/ticket/1149 */ 
     3597        status = lock_codec(call); 
     3598        if (status != PJ_SUCCESS) { 
     3599            pjsua_perror(THIS_FILE, "Unable to lock codec", status); 
     3600        } 
    35673601    } 
    35683602 
Note: See TracChangeset for help on using the changeset viewer.