Asterisk1.8 sip编码协商分析

在开始分析之前,先对编码协商中可能涉及的asterisk数据结构和变量作些说明。
ast_channel:定义一个通用的通道数据结构

struct ast_channel {
    const struct ast_channel_tech *tech;        /*!< Technology (point to channel driver) */
    void *tech_pvt;                    /*!< Private data used by the technology driver */
    ...
}

其中tech和tech_pvt两个成员是与通道具体使用的技术相关的,tech是与一种通道技术(如SIP)对应的驱动的数据结构,tech的类型为 ast_channel_tech的结构体,通道驱动定义了通道类型、描述,基本的呼叫相关函数指针(call,hangup,answer,transfer,bridge,early-bridge),帧的读写函数指针,DTMF、文本、图像、HTML、视频的发送,通道状态指示函数指针(indicate)等,对通道的操作主要是在这里定义的,而tech_pvt则定义了具体通道技术的数据信息,如sip_pvt。这些都是依赖于通道所使用的技术的。而ast_channel中其他的成员则可认为是各种通道都具有的通用的数据信息。

保存默认编码偏好的两个全局变量sip_cfg.capability和default_prefs
sip_cfg.capability:
sip_cfg是保存sip.conf的general段配置的全局结构体。sip_cfg.capability则是sip general配置的asterisk支持的编码。在reload_config函数中对sip_cfg.capability进行初始化。首先,把 DEFAULT_CAPABILITY这个宏(定义在sip.h中)中的5种编码(ulaw | testlaw | alaw | gsm | h.263)加到sip_cfg.capability中,然后解析(ast_parse_allow_disallow函数)sip.conf中的general段中的disallow和allow两项配置,先剔除disallow中的编码,再将allow中的编码加到sip_cfg.capability中。

default_prefs:
default_prefs是保存默认音频编码偏好的全局结构体。
对default_prefs的初始化也是在reload_config中进行的。解析(ast_parse_allow_disallow函数)sip.conf中的general段中的disallow和allow两项配置,先剔除disallow中的编码,然后将allow中的音频编码加到default_prefs中。default_prefs中不包括视频编码,因为asterisk不能对视频编码进行转码,只得使用所提供(offer)的视频编码。

sip_pvt结构体p中关于编码的成员的说明
p->peercapability: 即user/peer对应的终端上支持的编码。
p->capability: 即user/peer对应的编码配置。初始化为sip.conf [general]中allow选项配置的编码,在check_peer_ok函数中重新赋值为对应user/peer的编码。
p->prefcodec: 只用于呼出呼叫(outbound call),由呼入通道以参数传递进来。
p->jointcapability: 对于呼入通道来说,指的是user/peer和终端都具有的编码。对于呼出通道来说,在发出invite还未收到带sdp的响应之前,指的是p->capability中能够与呼入通道传递的nativeformats(即p->prefcodec)进行互相转码的编码;收到终端带sdp的响应后,在处理sdp时,赋值为sdp中携带的编码。
p->prefs: 即user/peer对应的音频编码配置。在sip_alloc函数中被初始化为default_prefs这个全局结构体的值,在check_peer_ok函数中重新赋值为对应user/peer的编码中的音频编码。

呼入呼叫(Inbound Call)协商:
在load或reload chan_sip模块时,会调用build_peer函数从sip.conf或users.conf中读取配置,并把每一个帐号的配置保存到一个sip_peer的结构体中,在build_peer中调用set_peer_defaults来初始化这个结构体的成员(比如sippeer->capability = sip_cfg.capability; sippeer->prefs = default_prefs;),然后解析sip.conf(或users.conf),如果该帐号对应的context中有allow选项的话,就覆盖sippeer的capability和prefs成员的初始值,将allow中的所有编码赋值给sippeer->capability,将allow中的音频编码保持原顺序赋值给sippeer->prefs。

p->prefs、p->capability、呼入通道的nativeformats的初始化:
asterisk接收到sip请求消息时,调用handle_request_do->find_call->sip_alloc,在sip_alloc中为sip_pvt结构体p的capability和prefs成员初始化:p->capability = sip_cfg.capability;p->prefs = default_prefs。

接着handle_request_do->handle_incoming->handle_request_invite->check_user_full->check_peer_ok,在check_peer_ok中将找到的sip_peer的编码成员分别赋值给p->capability,p->jointcapability,p->prefs,重新对p->capability、p->prefs进行初始化,对p->jointcapability进行初始化。

        p->capability = peer->capability;
        p->prefs = peer->prefs;
        p->jointcapability = peer->capability;

然后handle_request_do->handle_incoming->handle_request_invite->sip_new,在sip_new中创建sip通道结构体tmp,并为tmp的nativeformats成员赋值:首先从tmp通道对应的sip_pvt结构体成员i->prefs(这里的i实际上就是sip_alloc中分配的sip_pvt结构体p作为参数传进来)的编码中按顺序从最前面开始选出(ast_codec_choose)属于sip_pvt结构体capability的一种编码作为nativeformats的值,如果没找到,就调用ast_best_codec来选择一种编码,该函数内部定义了一个名为prefs的音频编码数组,按顺序遍历该数组,直到找到一个属于sip_pvt结构体capability并且为音频的编码。然后或上视频和文本编码能力,赋值给tmp->nativeformats。

    /* Select our native format based on codec preference until we receive
       something from another device to the contrary. */
    if (i->jointcapability) {     /* The joint capabilities of us and peer */
        what = i->jointcapability;
        video = i->jointcapability & AST_FORMAT_VIDEO_MASK;
        text = i->jointcapability & AST_FORMAT_TEXT_MASK;
    } else if (i->capability) {        /* Our configured capability for this peer */
        what = i->capability;
        video = i->capability & AST_FORMAT_VIDEO_MASK;
        text = i->capability & AST_FORMAT_TEXT_MASK;
    } else {
        what = sip_cfg.capability;    /* Global codec support */
        video = sip_cfg.capability & AST_FORMAT_VIDEO_MASK;
        text = sip_cfg.capability & AST_FORMAT_TEXT_MASK;
    }

    /* Set the native formats for audio  and merge in video */
    tmp->nativeformats = ast_codec_choose(&i->prefs, what, 1) | video | text;

在handle_request_invite中调用process_sdp(注:process_sdp并不处理outbound call中的invite,process_sdp会在有incoming的invite和200OK时被调用),将invite中sdp携带的编码逐个解析出来,将这些编码对应的code相或并赋值给p->peercapability,将p->capability & p->peercapability的结果赋值给p->jointcapability.

    /* Scan media stream (m=) specific parameters loop */
    while (!ast_strlen_zero(nextm)) {
        int audio = FALSE;
        int video = FALSE;
        int image = FALSE;
        int text = FALSE;
        char protocol[5] = {0,};
        int x;

        numberofports = 1;
        len = -1;
        start = next;
        m = nextm;
        iterator = next;
        nextm = get_sdp_iterate(&next, req, "m");

        /* Search for audio media definition */
        /* 处理SDP中的m(音频媒体属性,如: m: audio 13422 RTP/AVP 0 3 101) */
        if ((sscanf(m, "audio %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0 && x) ||
            (sscanf(m, "audio %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0 && x)) {
            if (!strcmp(protocol, "SAVP")) {
                secure_audio = 1;
            } else if (strcmp(protocol, "AVP")) {
                ast_log(LOG_WARNING, "unknown SDP media protocol in offer: %s\n", protocol);
                continue;
            }
            if (p->offered_media[SDP_AUDIO].order_offered) {
                ast_log(LOG_WARNING, "Multiple audio streams are not supported\n");
                return -3;
            }
            audio = TRUE;
            p->offered_media[SDP_AUDIO].order_offered = ++numberofmediastreams;
            portno = x;

            /* Scan through the RTP payload types specified in a "m=" line: */
            codecs = m + len;
            ast_copy_string(p->offered_media[SDP_AUDIO].codecs, codecs, sizeof(p->offered_media[SDP_AUDIO].codecs));
            for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) {
                if (sscanf(codecs, "%30u%n", &codec, &len) != 1) {
                    ast_log(LOG_WARNING, "Error in codec string ‘%s‘\n", codecs);
                    return -1;
                }
                if (debug)
                    ast_verbose("Found RTP audio format %d\n", codec);

                ast_rtp_codecs_payloads_set_m_type(&newaudiortp, NULL, codec);
            }
        /* Search for video media definition */
        /* 处理SDP中的m(视频媒体属性,如: m: video 12036 RTP/AVP 34 98 99 ) */
        } else if ((sscanf(m, "video %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0 && x) ||
               (sscanf(m, "video %30u RTP/%4s %n", &x, protocol, &len) == 2 && len >= 0 && x)) {
            if (!strcmp(protocol, "SAVP")) {
                secure_video = 1;
            } else if (strcmp(protocol, "AVP")) {
                ast_log(LOG_WARNING, "unknown SDP media protocol in offer: %s\n", protocol);
                continue;
            }
            if (p->offered_media[SDP_VIDEO].order_offered) {
                ast_log(LOG_WARNING, "Multiple video streams are not supported\n");
                return -3;
            }
            video = TRUE;
            p->novideo = FALSE;
            p->offered_media[SDP_VIDEO].order_offered = ++numberofmediastreams;
            vportno = x;

            /* Scan through the RTP payload types specified in a "m=" line: */
            codecs = m + len;
            ast_copy_string(p->offered_media[SDP_VIDEO].codecs, codecs, sizeof(p->offered_media[SDP_VIDEO].codecs));
            for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) {
                if (sscanf(codecs, "%30u%n", &codec, &len) != 1) {
                    ast_log(LOG_WARNING, "Error in codec string ‘%s‘\n", codecs);
                    return -1;
                }
                if (debug)
                    ast_verbose("Found RTP video format %d\n", codec);
                ast_rtp_codecs_payloads_set_m_type(&newvideortp, NULL, codec);
            }
        /* Search for text media definition */
                }
        /*
            ......
        */

        /* Media stream specific parameters */
        while ((type = get_sdp_line(&iterator, next - 1, req, &value)) != ‘\0‘) {
            int processed = FALSE;

            switch (type) {
            case ‘c‘:
                if (audio) {
                    if (process_sdp_c(value, &audiosa)) {
                        processed = TRUE;
                        sa = &audiosa;
                    }
                } else if (video) {
                    if (process_sdp_c(value, &videosa)) {
                        processed = TRUE;
                        vsa = &videosa;
                    }
                } else if (text) {
                    if (process_sdp_c(value, &textsa)) {
                        processed = TRUE;
                        tsa = &textsa;
                    }
                } else if (image) {
                    if (process_sdp_c(value, &imagesa)) {
                        processed = TRUE;
                        isa = &imagesa;
                    }
                }
                break;
            // 处理SDP中的a(媒体属性,如: a: rtpmap:0 PCMU/8000)
            case ‘a‘:
                /* Audio specific scanning */
                if (audio) {
                    if (process_sdp_a_sendonly(value, &sendonly))
                        processed = TRUE;
                    else if (process_crypto(p, p->rtp, &p->srtp, value))
                        processed = TRUE;
                    /* 在process_sdp_a_audio中调用ast_rtp_codecs_payloads_set_rtpmap_type_rate,根据编码的code(如:AST_FORMAT_G726)和
                    payload将对应的编码类型加到newaudiortp中 */
                    else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec))
                        processed = TRUE;
                }
                /* Video specific scanning */
                else if (video) {
                    if (process_sdp_a_sendonly(value, &vsendonly))
                        processed = TRUE;
                    else if (process_crypto(p, p->vrtp, &p->vsrtp, value))
                        processed = TRUE;
                    /* 在process_sdp_a_video中调用ast_rtp_codecs_payloads_set_rtpmap_type_rate,根据编码的code(如:AST_FORMAT_H264)和
                    payload将对应的编码类型加到newvideortp中 */
                    else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec))
                        processed = TRUE;
                }
                /* Text (T.140) specific scanning */
                else if (text) {
                    if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec))
                        processed = TRUE;
                    else if (process_crypto(p, p->trtp, &p->tsrtp, value))
                        processed = TRUE;
                }
                /* Image (T.38 FAX) specific scanning */
                else if (image) {
                    if (process_sdp_a_image(value, p))
                        processed = TRUE;
                }
                break;
            }

            ast_debug(3, "Processing media-level (%s) SDP %c=%s... %s\n",
                    (audio == TRUE)? "audio" : (video == TRUE)? "video" : "image",
                    type, value,
                    (processed == TRUE)? "OK." : "UNSUPPORTED.");
        }
    }

    /*
        ......
    */

    /* Now gather all of the codecs that we are asked for: */
    /* 把newaudiortp中的asterisk编码加到peercapability中,非asterisk编码(AST_RTP_CN、AST_RTP_DTMF、AST_RTP_CISCO_DTMF)加到
    peernoncodeccapability中 */
    ast_rtp_codecs_payload_formats(&newaudiortp, &peercapability, &peernoncodeccapability);
    ast_rtp_codecs_payload_formats(&newvideortp, &vpeercapability, &vpeernoncodeccapability);
    ast_rtp_codecs_payload_formats(&newtextrtp, &tpeercapability, &tpeernoncodeccapability);

    newjointcapability = p->capability & (peercapability | vpeercapability | tpeercapability);
    newpeercapability = (peercapability | vpeercapability | tpeercapability);
    newnoncodeccapability = p->noncodeccapability & peernoncodeccapability;

    /*
        ......
    */

    if (portno != -1 || vportno != -1 || tportno != -1) {
        /* We are now ready to change the sip session and p->rtp and p->vrtp with the offered codecs, since
           they are acceptable */
        /* 为p->jointcapability和p->peercapability赋值*/
        p->jointcapability = newjointcapability;                /* Our joint codec profile for this call */
        p->peercapability = newpeercapability;                  /* The other sides capability in latest offer */
        p->jointnoncodeccapability = newnoncodeccapability;     /* DTMF capabilities */

        /* respond with single most preferred joint codec, limiting the other side‘s choice */
        if (ast_test_flag(&p->flags[1], SIP_PAGE2_PREFERRED_CODEC)) {
            p->jointcapability = ast_codec_choose(&p->prefs, p->jointcapability, 1);
        }
    }

呼出呼叫(Outbound Call)协商:
dial_exec->dial_exec_full->ast_request->sip_request_call,把呼入通道的nativeformats通过ast_request传给sip_request_call,在sip_request_call中调用sip_alloc分配呼出通道对应的sip_pvt结构体q(说明:下面代码中的sip_pvt结构体p实际上就是这里的q),sip_request_call->create_addr->find_peer,在find_peer中,通过被叫的peer name来查找对应的sip_peer数据结构,对于非realtime模式,find_peer查找sip_peer有两种方式,一种是通过peer name,另一种是通过ip地址,在outbound call时,采用的是第一种(传给find_peer的addr参数为NULL),在inbound call时,对于type=peer类型,则是采用第二种。sip_request_call->create_addr->create_addr_from_peer,将找到的被叫sip_peer对应的编码配置赋值给q->capability,调用dialog_initialize_rtp来初始化rtp,是否需要初始化视频rtp有两种情况:
1,peer/user的videosupport选项为always(SIP_PAGE2_VIDEOSUPPORT_ALWAYS);
2,peer/user的videosupport选项为真(SIP_PAGE2_VIDEOSUPPORT),并且q->capability中有视频编码。
这两种情况下都会初始化视频rtp。然后返回到sip_request_call中,把呼入通道的nativeformats赋值给q->prefcodec,将q->jointcapability初始化为q->prefcodec & q->capability.

dial_exec->dial_exec_full->ast_call->sip_call,在sip_call中,调用ast_rtp_instance_available_formats,查找p->capability中的编码与p->prefcodec中的编码是否有可用的转码路径,把p->capability中能与p->prefcodec进行互相转码或者相同的编码赋值给p->jointcapability.

    p->jointcapability = ast_rtp_instance_available_formats(p->rtp, p->capability, p->prefcodec);

sip_call->transmit_invite->add_sdp,在add_sdp中将编码添加到invite请求的sdp.首先,如果p->prefcodec与p->jointcapability有相同的音频编码时,将p->prefcodec中的音频编码加到sdp中(p->prefcodec中的音频编码只有一种);然后,将p->jointcapability中包含在p->prefs中的编码加到sdp,p->prefs中只包含音频编码;最后,把p->jointcapability中其他的编码包括视频编码等加到sdp中。

    capability = p->jointcapability;

    /*
        ......
    */

    /*     首先,如果p->prefcodec与capability(即p->jointcapability)有相同的编码时,将p->prefcodec中的音频编码加到sdp中(p->prefcodec中的
    音频编码只有一种) */
    //if (capability & p->prefcodec) {
    if (capability & p->prefcodec & AST_FORMAT_AUDIO_MASK) {
    /*    当capability与p->prefcodec有相同的视频编码但是却没有相同的音频编码时会导致协商错误,eg:
            p->jointcapability: AST_FORMAT_ULAW | AST_FORMAT_ALAW | AST_FORMAT_H264
            p->prefcodec: AST_FORMAT_GSM | AST_FORMAT_H264
        上述情况下会把GSM编码加到sdp中去,显然是不对的,应该把条件改为 if (capability & p->prefcodec & AST_FORMAT_AUDIO_MASK)
    */

        format_t codec = p->prefcodec & AST_FORMAT_AUDIO_MASK;// p->prefcodec中的音频编码只有一种

        add_codec_to_sdp(p, codec, &m_audio, &a_audio, debug, &min_audio_packet_size);
        alreadysent |= codec;
    }

    /* 然后,将capability(即p->jointcapability)中包含在全局结构体default_prefs中的编码加到sdp,default_prefs中只包含音频编码 */
    /* Start by sending our preferred audio/video codecs */
    for (x = 0; x < 64; x++) {         format_t codec;         if (!(codec = ast_codec_pref_index(&p->prefs, x)))
            break;

        if (!(capability & codec))
            continue;

        if (alreadysent & codec)
            continue;

        add_codec_to_sdp(p, codec, &m_audio, &a_audio, debug, &min_audio_packet_size);
        alreadysent |= codec;
    }

    /* 最后,把capability(即p->jointcapability)中其他的编码包括视频编码等加到sdp中*/
    /* Now send any other common audio and video codecs, and non-codec formats: */
    for (x = 1ULL; x         if (!(capability & x))    /* Codec not requested */
            continue;

        if (alreadysent & x)    /* Already added to SDP */
            continue;

        if (x & AST_FORMAT_AUDIO_MASK)
            add_codec_to_sdp(p, x, &m_audio, &a_audio, debug, &min_audio_packet_size);
        else if (x & AST_FORMAT_VIDEO_MASK)
            add_vcodec_to_sdp(p, x, &m_video, &a_video, debug, &min_video_packet_size);
        else if (x & AST_FORMAT_TEXT_MASK)
            add_tcodec_to_sdp(p, x, &m_text, &a_text, debug, &min_text_packet_size);
    }
时间: 2024-07-31 14:35:13

Asterisk1.8 sip编码协商分析的相关文章

Asterisk1.8 转码策略分析

最近在修改asterisk转码和编码协商的问题,发现asterisk的转码策略的选择还是有些问题的(基于1.8.9.3版本).——————————————相关的CLI命令转码路径的调试命令:core show channelscore show channel ${CHANNEL} 查看不同编码之间进行转换的时间开销:core show translation 查看某种编码转换为其它编码的路径:core show translation paths {codec}eg: core show tr

BASE64编码原理分析脚本实现及逆向案例

BASE64编码原理分析脚本实现及逆向案例 0x01 简单介绍 数据传送时并不支持所有的字符,很多时候只支持可见字符的传送.但是数据传送不可能只传送可见字符为解决这个问题就诞生了base64编码.base64编码将所有待编码字符转换成64个可见字符表中的字符. 0x02 编码原理 被Base64编码之后所得到的所有字符都是在以下这个表当中的. 上表中总共有64个字符,2^6=64所以只需要6个bit位就足够描述所有的表中字符了.计算机中1个字节8个bit,一个ASCII码占1个字节.因此多出来的

SIP基本场景分析

1.SIP业务基本知识 1.1 业务介绍 会话初始协议(Session Initiation Protocol)是一种信令协议,用于初始.管理和终止网络中的语音和视频会话,具体地说就是用来生成.修改和终结一个或多个参与者之间的会话.SIP的业务模式是一个点对点协议,其中有两个要素--SIP用户代理和SIP网络服务器.用户代理是呼叫的终端系统元素,而SIP服务器是处理与多个呼叫相关联信令的网络设备.用户代理本身具有一客户机元素(用户代理客户机UAC)和一服务器元素(用户代理服务器UAS).客户机元

【转】【协议学习】SIP基本场景分析

1.SIP业务基本知识 1.1 业务介绍 会话初始协议(Session Initiation Protocol)是一种信令协议,用于初始.管理和终止网络中的语音和视频会话,具体地说就是用来生成.修改和终结一个或多个参与者之间的会话.SIP的业务模式是一个点对点协议,其中有两个要素——SIP用户代理和SIP网络服务器.用户代理是呼叫的终端系统元素,而SIP服务器是处理与多个呼叫相关联信令的网络设备.用户代理本身具有一客户机元素(用户代理客户机UAC)和一服务器元素(用户代理服务器UAS).客户机元

视频格式与编码问题分析

一.什么是视频格式? 视频格式是一种封装格式,就是把编码后的比特流进行封装,不同的视频格式封装方法不同.目前视频都是由音频流+视频流组成.格式只是封装容器,例如rmvb,mkv,MP4,mpg,ts等等.常见的视频流有MPEG2,MPEG4,H264,VC1等等,MPEG2是比较老式的视频编码,压缩率很低.MPEG4是比较新的编码,常见于DVD影片等等,压缩率一般,最新的MPEG4视频编码是Xvid,还有比较老的DivX.H264(X264是H264的一种)是新生的视频编码,常见于高清视频中,压

SIP包的分析

今天工作碰到了两个装置之间SIP包的抓取和分析,然后结合RFC3261的文档说明,记录下今天所理解的. 1.SIP协议:SIP的详细RFC文档可见:rfc3261Session Initiation(会话初始协议),允许使用Internet端点(用户代理)来寻找参与者并且允许建立一个可共享的会话描述.SIP允许创建基础的 networkhosts(叫做代理服务器),并且允许终端用户注册上去,发出会话邀请,或者发出其他请求.可以用来创建,修改和终止会话,它独立运作于通讯协议之下,并且不依赖建立的会

基于GBT28181:SIP协议组件开发-----------第三篇SIP注册流程分析实现

上两章节简要的讲解了SIP组件开发接口和开发环境的搭建.在本节将实现Linux 32平台的UAS和UAC,当然该UAS和UAC只实现了注册功能,并且是基于自主开发SIP组件libGBT28181SipComponent.so的,没有这个组件是运行不了的.其他功能在后续章节中讲解. 首先简单讲解一下GBT28181关于注册描述 一. GBT28181注册的流程如下图 电力系统注册稍微复杂点,但原来基本相同.多了个刷新注册的过程. 二.GBT28181关于注册的解释如下 三.SIP协议简介 一个合法

Python2 编码问题分析

本文浅显易懂,绿色纯天然,手工制作,请放心阅读. 编码问题是一个很大很杂的话题,要向彻底的讲明白可以写一本书了.导致乱码的原因很多,系统平台.编程语言.多国语言.软件程序支持.用户选择等都可能导致无法正确的解析编码. 导致乱码的主要原因可以简单归结于文本的编码方式和解码方式不同导致的.本文将通过在win7(zh-cn)系统下分析python2.7的编解码问题来简单窥探一下编码的冰山一角. 今后遇到编码问题时能够多一点分析解决思路,要是能起到一个抛砖引玉的作用,那就再好不过了. 1.为什么需要编码

JS对URL字符串进行编码/解码分析

虽然escape().encodeURI().encodeURIComponent()三种方法都能对一些影响URL完整性的特殊 字符进行过滤.但后两者是将字符串转换为UTF-8的方式来传输,解决了页面编码不一至导致的乱码问 题.例如:发送页与接受页的编码格式(Charset)不一致(假设发送页面是GB2312而接收页面编码是 UTF-8),使用escape()转换传输中文字串就会出现乱码问题. 以下是JS下对URL进行编/解码的各种方法: escape 方法:返回一个可在所有计算机上读取的编码