Загрузка данных


pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
                                     pjsip_role_e role,
                                     int security_level,
                                     pj_pool_t *tmp_pool,
                                     const pjmedia_sdp_session *rem_sdp,
                                     int *sip_err_code,
                                     pj_bool_t async,
                                     pjsua_med_tp_state_cb cb)
{
    const pj_str_t STR_AUDIO = { "audio", 5 };
    const pj_str_t STR_VIDEO = { "video", 5 };
    const pj_str_t STR_TEXT  = { "text", 4 };
    pjsua_call *call = &pjsua_var.calls[call_id];
    pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
    pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
    unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
    unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx);
    pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
    unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
    unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx);
    pj_uint8_t mtxtidx[PJSUA_MAX_CALL_MEDIA];
    unsigned mtxtcnt = PJ_ARRAY_SIZE(mtxtidx);
    unsigned mtottxtcnt = PJ_ARRAY_SIZE(mtxtidx);
    unsigned mi;
    pj_bool_t pending_med_tp = PJ_FALSE;
    pj_bool_t reinit = PJ_FALSE;
    pj_status_t status;

    PJ_UNUSED_ARG(role);

    /*
     * Note: this function may be called when the media already exists
     * (e.g. in reinvites, updates, etc).
     */

    if (pjsua_get_state() != PJSUA_STATE_RUNNING) {
        if (sip_err_code)
            *sip_err_code = PJSIP_SC_SERVICE_UNAVAILABLE;
        return PJ_EBUSY;
    }

    if (async) {
        pj_pool_t *tmppool = (call->inv? call->inv->pool_prov:
                              call->async_call.dlg->pool);

        status = pj_mutex_create_simple(tmppool, NULL, &call->med_ch_mutex);
        if (status != PJ_SUCCESS) {
            if (sip_err_code)
                *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
            return status;
        }
    }

    if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED)
        reinit = PJ_TRUE;

    PJ_LOG(4,(THIS_FILE, "Call %d: %sinitializing media..",
                         call_id, (reinit?"re-":"") ));

    pj_log_push_indent();

    /* Init provisional media state */
    if (call->med_cnt == 0) {
        /* New media session, just copy whole from call media state. */
        pj_memcpy(call->media_prov, call->media, sizeof(call->media));
    } else {
        /* Clean up any unused transports. Note that when local SDP reoffer
         * is rejected by remote, there may be any initialized transports that
         * are not used by call media and currently there is no notification
         * from PJSIP level regarding the reoffer rejection.
         */
        pjsua_media_prov_clean_up(call_id);

        /* Updating media session, copy from call media state. */
        pj_memcpy(call->media_prov, call->media,
                  sizeof(call->media[0]) * call->med_cnt);
    }
    call->med_prov_cnt = call->med_cnt;

#if DISABLED_FOR_TICKET_1185
    /* Return error if media transport has not been created yet
     * (e.g. application is starting)
     */
    for (i=0; i<call->med_cnt; ++i) {
        if (call->media[i].tp == NULL) {
            status = PJ_EBUSY;
            goto on_error;
        }
    }
#endif

    /* Get media count for each media type */
    if (rem_sdp) {

        /* We are sending answer, check media count for each media type
         * from the remote SDP.
         */
        sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
                   maudidx, &maudcnt, &mtotaudcnt);

#if PJMEDIA_HAS_VIDEO
        sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
                   mvididx, &mvidcnt, &mtotvidcnt);
#else
        mvidcnt = mtotvidcnt = 0;
        PJ_UNUSED_ARG(STR_VIDEO);
#endif

        sort_media(rem_sdp, &STR_TEXT, acc->cfg.use_srtp,
                   mtxtidx, &mtxtcnt, &mtottxtcnt);

        if (maudcnt + mvidcnt + mtxtcnt == 0) {
            /* Expecting media in the offer */
            if (sip_err_code)
                *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
            status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
            goto on_error;
        }

        /* Update media count only when remote add any media, this media count
         * must never decrease. Also note that we shouldn't apply the media
         * count setting (of the call setting) before the SDP negotiation.
         */
        if (call->med_prov_cnt < rem_sdp->media_count)
            call->med_prov_cnt = PJ_MIN(rem_sdp->media_count,
                                        PJSUA_MAX_CALL_MEDIA);

        call->rem_offerer = PJ_TRUE;
        call->rem_aud_cnt = maudcnt;
        call->rem_vid_cnt = mvidcnt;
        call->rem_txt_cnt = mtxtcnt;

    } else {

        /* If call is already established, adjust the existing call media list
         * to media count setting in call setting, e.g: re-enable/disable/add
         * media from existing media.
         * Otherwise, apply media count from the call setting directly.
         */
        if (reinit) {
            pj_bool_t sort_check_tp;

            /* Media sorting below will check transport, i.e: media without
             * transport will have lower priority. If PJSUA_CALL_REINIT_MEDIA
             * is set, we must not check transport.
             */
            sort_check_tp = !(call->opt.flag & PJSUA_CALL_REINIT_MEDIA);

            /* We are sending reoffer, check media count for each media type
             * from the existing call media list.
             */
            sort_media2(call->media_prov, sort_check_tp, call->med_prov_cnt,
                        PJMEDIA_TYPE_AUDIO, maudidx, &maudcnt, &mtotaudcnt);

            /* No need to assert if there's no media. */
            //pj_assert(maudcnt > 0);

            sort_media2(call->media_prov, sort_check_tp, call->med_prov_cnt,
                        PJMEDIA_TYPE_VIDEO, mvididx, &mvidcnt, &mtotvidcnt);

            sort_media2(call->media_prov, sort_check_tp, call->med_prov_cnt,
                        PJMEDIA_TYPE_TEXT, mtxtidx, &mtxtcnt, &mtottxtcnt);

            /* Call setting may add or remove media. Adding media is done by
             * enabling any disabled/port-zeroed media first, then adding new
             * media whenever needed. Removing media is done by disabling
             * media with the lowest 'quality'.
             */

            /* Check if we need to add new audio */
            if (maudcnt < call->opt.aud_cnt &&
                mtotaudcnt < call->opt.aud_cnt)
            {
                for (mi = 0; mi < call->opt.aud_cnt - mtotaudcnt; ++mi)
                    maudidx[maudcnt++] = (pj_uint8_t)call->med_prov_cnt++;
                
                mtotaudcnt = call->opt.aud_cnt;
            }
            maudcnt = call->opt.aud_cnt;

            /* Check if we need to add new video */
            if (mvidcnt < call->opt.vid_cnt &&
                mtotvidcnt < call->opt.vid_cnt)
            {
                for (mi = 0; mi < call->opt.vid_cnt - mtotvidcnt; ++mi)
                    mvididx[mvidcnt++] = (pj_uint8_t)call->med_prov_cnt++;

                mtotvidcnt = call->opt.vid_cnt;
            }
            mvidcnt = call->opt.vid_cnt;

            /* Check if we need to add new text */
            if (mtxtcnt < call->opt.txt_cnt &&
                mtottxtcnt < call->opt.txt_cnt)
            {
                for (mi = 0; mi < call->opt.txt_cnt - mtottxtcnt; ++mi)
                    mtxtidx[mtxtcnt++] = (pj_uint8_t)call->med_prov_cnt++;

                mtottxtcnt = call->opt.txt_cnt;
            }
            mtxtcnt = call->opt.txt_cnt;

            /* In case of media reinit, 'med_prov_cnt' may be decreased
             * because the new call->opt says so. As media count should
             * never decrease, we should verify 'med_prov_cnt' to be
             * at least equal to 'med_cnt' (see also #1987).
             */
            if ((call->opt.flag & PJSUA_CALL_REINIT_MEDIA) &&
                call->med_prov_cnt < call->med_cnt)
            {
                call->med_prov_cnt = call->med_cnt;
            }

        } else {

            maudcnt = mtotaudcnt = call->opt.aud_cnt;
            for (mi=0; mi<maudcnt; ++mi) {
                maudidx[mi] = (pj_uint8_t)mi;
            }
            mvidcnt = mtotvidcnt = call->opt.vid_cnt;
            for (mi=0; mi<mvidcnt; ++mi) {
                mvididx[mi] = (pj_uint8_t)(maudcnt + mi);
            }
            mtxtcnt = mtottxtcnt = call->opt.txt_cnt;
            for (mi=0; mi<mtxtcnt; ++mi) {
                mtxtidx[mi] = (pj_uint8_t)(maudcnt + mvidcnt + mi);
            }
            call->med_prov_cnt = maudcnt + mvidcnt + mtxtcnt;

            /* Need to publish supported media? */
            if (call->opt.flag & PJSUA_CALL_INCLUDE_DISABLED_MEDIA) {
                if (mtotaudcnt == 0) {
                    mtotaudcnt = 1;
                    maudidx[0] = (pj_uint8_t)call->med_prov_cnt++;
                }
#if PJMEDIA_HAS_VIDEO
                if (mtotvidcnt == 0) {
                    mtotvidcnt = 1;
                    mvididx[0] = (pj_uint8_t)call->med_prov_cnt++;
                }
#endif
                if (mtottxtcnt == 0) {
                    mtottxtcnt = 1;
                    mtxtidx[0] = (pj_uint8_t)call->med_prov_cnt++;
                }
            }
        }

        call->rem_offerer = PJ_FALSE;
    }

    if (call->med_prov_cnt == 0) {
        /* Expecting at least one media */
        if (sip_err_code)
            *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
        status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
        goto on_error;
    }

    if (async) {
        call->med_ch_cb = cb;
    }

    if (rem_sdp && call->inv) {
        call->async_call.rem_sdp =
            pjmedia_sdp_session_clone(call->inv->pool_prov, rem_sdp);
    } else {
        call->async_call.rem_sdp = NULL;
    }

    call->async_call.pool_prov = tmp_pool;

    /* Initialize each media line */
    for (mi=0; mi < call->med_prov_cnt; ++mi) {
        pjsua_call_media *call_med = &call->media_prov[mi];
        pj_bool_t enabled = PJ_FALSE;
        pjmedia_type media_type = PJMEDIA_TYPE_UNKNOWN;

        if (pj_memchr(maudidx, mi, mtotaudcnt * sizeof(maudidx[0]))) {
            media_type = PJMEDIA_TYPE_AUDIO;
            if (call->opt.aud_cnt &&
                pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0])))
            {
                enabled = PJ_TRUE;
            }
        } else if (pj_memchr(mvididx, mi, mtotvidcnt * sizeof(mvididx[0]))) {
            media_type = PJMEDIA_TYPE_VIDEO;
            if (call->opt.vid_cnt &&
                pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0])))
            {
                enabled = PJ_TRUE;
            }
        } else if (pj_memchr(mtxtidx, mi, mtottxtcnt * sizeof(mtxtidx[0]))) {
            media_type = PJMEDIA_TYPE_TEXT;
            if (call->opt.txt_cnt &&
                pj_memchr(mtxtidx, mi, mtxtcnt * sizeof(mtxtidx[0])))
            {
                enabled = PJ_TRUE;
            }
        }

        if (call->opt.flag & PJSUA_CALL_SET_MEDIA_DIR) {
            call_med->def_dir = call->opt.media_dir[mi];
            PJ_LOG(4,(THIS_FILE, "Call %d: setting media direction "
                                 "#%d to %d.", call_id, mi,
                                 call_med->def_dir));
        } else if (!reinit || mi >= call->med_cnt) {
            /* Initialize default media direction as bidirectional,
             * for initial media or newly added media.
             */
            call_med->def_dir = PJMEDIA_DIR_ENCODING_DECODING;
        }

        if (enabled) {
            call_med->enable_rtcp_mux = acc->cfg.enable_rtcp_mux;

            status = pjsua_call_media_init(call_med, media_type, rem_sdp,
                                           &acc->cfg.rtp_cfg,
                                           security_level, sip_err_code,
                                           async,
                                           (async? &media_channel_init_cb:
                                            NULL));
            if (status == PJ_EPENDING) {
                pending_med_tp = PJ_TRUE;
            } else if (status != PJ_SUCCESS) {
                if (pending_med_tp) {
                    /* Save failure information. */
                    call_med->tp_ready = status;
                    pj_bzero(&call->med_ch_info, sizeof(call->med_ch_info));
                    call->med_ch_info.status = status;
                    call->med_ch_info.state = call_med->tp_st;
                    call->med_ch_info.med_idx = call_med->idx;
                    if (sip_err_code)
                        call->med_ch_info.sip_err_code = *sip_err_code;

                    /* We will return failure in the callback later. */
                    return PJ_EPENDING;
                }

                /* Revert back provisional media. */
                pjsua_media_prov_revert(call_id);

                goto on_error;
            }

            /* Find and save "a=mid". Currently this is for trickle ICE.
             * Trickle ICE match media in SDP of SIP INFO by comparing this
             * attribute, so remote SDP must be received first before remote
             * SDP in SIP INFO can be processed.
             */
            if (rem_sdp && call_med->rem_mid.slen == 0) {
                const pjmedia_sdp_media *m = rem_sdp->media[mi];
                pjmedia_sdp_attr *a;

                a = pjmedia_sdp_media_find_attr2(m, "mid", NULL);
                if (a)
                    call_med->rem_mid = a->value;
            }

        } else {
            /* By convention, the media is disabled if transport is NULL 
             * or transport state is PJSUA_MED_TP_DISABLED.
             */
            if (call_med->tp) {
                // Don't close transport here, as SDP negotiation has not been
                // done and stream may be still active. Once SDP negotiation
                // is done (channel_update() invoked), this transport will be
                // closed there.
                //pjmedia_transport_close(call_med->tp);
                //call_med->tp = NULL;
                pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT || 
                          call_med->tp_st == PJSUA_MED_TP_RUNNING);
                pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
            }

            /* Put media type just for info if not yet defined */
            if (call_med->type == PJMEDIA_TYPE_NONE)
                call_med->type = media_type;
        }
    }

    if (maudcnt > 0) {
        call->audio_idx = maudidx[0];

        PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
                  call->audio_idx, call->index));
    } else {
        call->audio_idx = -1;
    }

    if (pending_med_tp) {
        /* We shouldn't use temporary pool anymore. */
        call->async_call.pool_prov = NULL;
        /* We have a pending media transport initialization. */
        pj_log_pop_indent();
        return PJ_EPENDING;
    }

    /* Media transport initialization completed immediately, so 
     * we don't need to call the callback.
     */
    call->med_ch_cb = NULL;

    status = media_channel_init_cb(call_id, NULL);
    if (status != PJ_SUCCESS && sip_err_code) {
        if (call->med_ch_info.sip_err_code)
            *sip_err_code = call->med_ch_info.sip_err_code;
        else
            *sip_err_code = PJSIP_ERRNO_TO_SIP_STATUS(status);
    }

    pj_log_pop_indent();
    return status;

on_error:
    if (call->med_ch_mutex) {
        pj_mutex_destroy(call->med_ch_mutex);
        call->med_ch_mutex = NULL;
    }

    if (sip_err_code && *sip_err_code == 0)
        *sip_err_code = PJSIP_ERRNO_TO_SIP_STATUS(status);

    pj_log_pop_indent();
    return status;
}