Linphone分析-中文

Linphone分析-中文
Linphone分析-中文

Linphone分析

一 linphone 架构及组成模块 (2)

二 linphone 系统框图 (3)

三 linphone 中各个模块说明 (3)

四 linphone 中数据结构说明 (7)

五 linphone 的初始化过程 (7)

六 linphone 建立通话过程说明 (10)

1 拨号call过程 (10)

2 等待响应 (16)

3 Answer过程分析 (21)

4 关于RTP及音视频流的网络传输 (22)

5 总结 (23)

七 linphone 会话执行过程log分析 (24)

八 linphone 使用参考 (40)

岳维功

基于linphone-3.3.2版本,新版本linphone-3.4.3支持同时有多路call,所以,相比之前版本会有不少变化。

一 linphone 架构及组成模块

Linphone是一款跨平台的可视电话客户端软件,同时支持视频通话功能。Linphone可以在Linux,windows等主流操作系统平台上运行。

Linphone基于开源软件构建,本身也是开源软件。Linphone架构中sip协议的处理基于osip以及exosip两个开源库实现,媒体数据的选择整合处理使用mediastream2完成,该软件使用ffmepg、speedx 等多款开源软件完成音视频的编解码,并通过ortp完成基于rtp协议的音视频数据传输。ortp是一款处理RTP会话的开源软件。

1 整体架构图如下:

整个软件分为两层,上层为用户接口前端(user interface frontends),下层为linphone核心引擎(linphone core engine)。

2 功能模块说明:

Liblinphone 核心引擎实现了linphone所有的功能函数,而且能够方便的添加音频和视频的呼叫功能。Liblinphone也提供高层的API,用来初始化,接收或者终止呼叫。Liblinphone依赖于下面三个组件:

1 Mediastreamer2

这是一个支持多种平台的轻量级的流技术引擎,主要适合于开发语音和视频电话应用程序。该引擎主要为 linphone 的多媒体流的收发,包括语音和视频的捕获、编码解码以及渲染。

2 ortp2

Ortp是一个RTP库。为基于RTP协议的媒体流传输提供支持。通过mediastream2编码的数据就

是使用ortp 库发送到网络的另一端。

3 eXosip2

Exosip2为sip 协议的实现。这部分实际上是由exosip2和osip2两个库共同完成的。使用sip 协议

完成路由、媒体协商以及会话的建立和管理,为直接的媒体流的传输提供基础。

二 linphone 系统框图 网络接口

用户界面

Sip msg

接收Sip msg 发送系统运行框图

Ict exec Exosip任务

Read msg

Ist exec Nict exec

Nist exec Ms/

ticker/

audio Linphone core

任务

iterate

process

Rtp recv filters Rtp send filters Hard-

read

Hard-read Ms/ticker/vudio Rtp recv filters Rtp send

filters hard hard Rtp 接收Rtp 发送Rtp 接收Rtp 发送Sip

sal_iterate

Audio stream

process Vedio stream process

关于上面框图的一些说明:

通话双方在通信前使用exosip 进行会话协商。上图左边部分展示这一部分的流程。Exosip 后台

任务完成数据的接收和发送,并通过事件队列通知linphone 底层的状态变化。

filter 的构建在会话协商成功建立后就顺带完成了,并且ticker 任务也跑起来了。此时按照filter

graphics 构建的通道,音视频流不断的从硬件设备上读取,并经过编码压缩送给RTP 会话,之后送

到对端,对端到达的音视频流也经过RTP 会话接收送到解码解压缩filter ,还原出原始的音视频流交

给硬件设备播放。媒体数据在这两路流中源源不断的流动,完成了双方的可视通话。

上层linphone 的core 任务也不断的对底层进行迭代检查。所做的基本工作如下:

对于sip 协议部分,core 一直等待从事件队列上拿事件。这些事件是exosip 任务在处理sip 消息

过程中添加到事件队列上的。每当得到新的事件后,core 就从应用层的角度出发,进行处理。

对于视频流:基本上只处理rtcp 数据包到达的事件。stream 上也有一个事件队列,用于保存该流

上的相关事件。对于rtcp 数据包事件,core 也只处理sr 类型rtcp 包,即发送端报告,得到jitter 和

包丢失率。如果设置了自适应比特率,则调用相关接口进行处理。此过程不断进行,直到当前事件

上的包处理完。

对于音频流,检查流是否还是活动的。通过比较RTP stats 中接收的数据包数目是否发生变化,

如果在超时时间到达后,接收的数据量还没有发生变化,则认为音频没有响应。

三 linphone 中各个模块说明

1 Linphone coreapi 中子模块说明:

Coreapi 中的各个模块就是上层的处理模块,包括configure 文件的处理接口,address 的处理接口,

chat 的处理接口,sal 的处理接口,proxy 的处理接口,

authorization 的处理接口,friends 的处理接口,callback 的处理接口,state 的处理接口,杂项处理接口等。这相当于高层的几个模块,提供给用户

的接口调用主要都在linphonecore.c中

1)Callback模块:

该模块下的回调函数都是用于sal模块调用的。当sal处理完sip协议的处理后,就会调用相应的callback函数继续后续的处理,包括启动一个音视频传输流,启动响铃等。也就是说这里的callback 完成了media媒体层的处理以及linphone上层的处理。

回调函数被保存在全局变量linphone_sal_callbacks中,在linphone初始化时调用sal_set_callbacks 设置到sal的callback上去的。

2)Genera_stat模块:

主要提供linphone全局状态的修改与设置的接口

3)Address模块:

调用sal提供的接口,进行与地址相关的处理,这里的地址主要是uri相关的处理。包括获取地址以及地址中的部分信息或者设置这些信息。在上层地址是一个字符串指针,但是在内部处理时都会强制转换为osip_from结构体来处理。实际上就是对linphone_address结构体的处理。

4)Authorization模块:

处理认证信息。各个认证用户的信息都被保存到linphone_auto结构体中兵串接在linphone_core 结构体上。这里的接口就是处理这些数据结构,提供设置和获取相关信息的接口。

5)Chat模块:

提供创建和销毁chat room,向chat room发送消息和从chat room接收消息的接口,以及设置和获取用户数据的接口。类似于authorization模块,所有的chat room信息也是保存在linphone_chat结构体中并串接在linphone_core结构体上的。

6)Friends模块:

提供处理friends相关信息的接口。所有的friends信息保存在linphone_friend结构体中并被串接在linphone_core结构体上,这样操作起来,包括设置,获取,添加以及移除都很方便。

7)Configure模块:

提供配置文件处理的相关接口,包括配置文件的解析,配置文件中信息的获取,写入,同步等。配置文件解析后便于程序处理的信息主要都保存在lpconfig结构体中,这与文本文件中便于编写和阅读的配置文件本身不同。

配置文件中的各个配置模块本身也按照section的方式进行了划分,各个section也都是挂接在lp_config的section链表上的。这个模块可以单独提取出来进行测试。

8)Offer_answer模块:

管理基于sdp的媒体协商。根据本地的支持能力和远端支持的能力,根据就低的原则,获得双方都可以支持的媒体信息。比如编解码格式等。

9)Presence模块:

提供与在线状态相关的处理。

10)Proxy模块:

处理代理相关的处理。代理相关的信息保存在linphone_proxy结构体上,但是该结构体只是代表了当前linphone_core使用的proxy。代理可能不止一个,所有的代理其信息都被串接在链表上,并被挂接在sip_conf的proxies上。添加一个代理,取得一个代理以及其他相关的操作接口也都在该模块中提供处理接口。

11)Sal模块:

Sal模块其实应该是最重要的,最核心的模块了。该模块对exosip进行了简单的封装,间接的对osip模块进行了封装,使用该模块的接口可以完成sip协议的处理以及媒体描述的处理。

Sal.c文件主要是对一些sal相关的结构体的操作,包括SalMediaDescription和sal_op。处理包括创建这些结构体的实例,获取或者设置其中的一些操作域。

Sal_exosip2_sdp.c基于osip库提供的sdp相关操作的接口,在sal层实现将其与sal相关的结构体关联起来操作。比如根据SalMediaDescription结构体信息将其转换为sdp结构体,或者反之。

Sal_exosip2_presence.c包括了对in和out的subscribe的操作。Text数据的发送(基于osip和exosip)。

Sal_exosip2.c sip这块比较重要的封装。包含了对sal_op结构体的创建和基本操作。对exosip重要结构体的封装,包括初始化和释放。包含了对sal结构体的创建和基本操作的封装,更重要的是包含了对sal和sal_op,sal_media_desc,sal_stream_desc这些上层结构体与底层osip_message,sip_message,sdp_message等数据结构之间数据的转换和共享,以及对底层相关接口的调用。这种调用主要包括跟据上层结构体中包含的信息设置底层结构体,并调用底层接口完成具体功能,以及根据底层结构体得到的数据设置上层结构体的相关信息。

一个基本的描述就是:sal作为signal abstract layer包含了上层所主要理解的交互信息,这些信息对于理解电话操作而言已经足够了,在底层,选择了osip和exosip来支持这项操作。所以实际上来说,可以用其他支持sip的库的接口来替代现有的,保留sal层接口的功能定义。在linphone中,虽然大部分使用了sal层的封装来完成sip交互过程,但是也调用osip和exosip库本身的其他接口,所以这层封装主要还是再次简化协议层的处理,使得功能更具体,而不是更单一。

几个关键数据结构之间的关系:

Sal一个基本的结构体,通过这个结构体可以搜寻到上层所需的所有sip协议相关的信息。具体的call,register等信息保存在sal_op这个结构体中,多个实体通过链表串接起来,挂在sal上。Sal_op 包含了sal_op_base结构体,这个结构体保存了一些通用的不变的信息,对多个实体而言,比如路由信息,本地媒体信息,远端媒体信息等。其root指针由返回指向了sal这个基础,所以通过sal_op 可以找到sal。另外,在媒体信息中包含了所有流的信息,所有这些类似一个树的组织结构,sal类似树根,通过它可以找到所有的枝叶及其上的信息。这些数据结构之间的关系如下图:

12)Core模块:

上层API及其封装实现。通过这些API接口,可以快速构建基于sip的可视电话系统。

2 底层模块说明

1)Mediastream实现的说明:

从代码上来看mediastreamer库,它的构成非常结构化。在mediastream2中实现了大量的filter,包括声卡视频卡的filter,编解码器的filter,RTP传输与接收的filter等等。每个.c文件实现一个filter,而且每个filter的实现也是非常结构化的。首先需要定义一个filter结构体实例,对于实例的各个部分进行赋值,主要是包括定义filter私有数据,filter的methods,init,preprocess,process,postprocess 和uninit这几个函数的实现等。而关键的一些实现,比如编解码器的处理,是基于ffmpeg库提供的接口来完成的,而声卡视频卡摄像头数据的捕获由其他库实现,但是也是基于标准的驱动接口来完成的。对于RTP的接收和传输则是基于ortp库来完成的。

另外提供了两个文件,audiostream和vediostream,用来处理音视频流的filter连接,linphonecore 主要就是调用这几个文件提供的接口来完成媒体流的启动的。

其他的就是辅助数据结构,包括filter的定义注册,queue队列的操作,声卡和摄像头的handler 的操作,ticker的处理等。

考虑到设备描述符,实际上mediastream可以分为两部分来看,一部分就是之前主要的有关大量filter的操作定义部分,一部分就是后来的有关声卡设备和摄像头设备的设备描述符的定义和操作。关于摄像头部分,视频数据的捕获是通过创建一个新的任务来进行的,该任务源源不断的从设备上读取数据,当然除此之外也提供了配置设备的参数的接口,比如图形的大小格式等。而且对于video 模块,设备处理和读取数据是与V4lState这个结构体挂钩的,这个结构体实例又挂到filter的私有数据上,这样数据读取任务不断的从驱动读取数据,放到该结构体实例上,filter处理时从自己的私有数据结构指向的queue中取数据自然的就取到了摄像头捕获的数据,实际上这里包含了设备的操作(基于设备描述符)和filter的操作(基于filter的描述符)以及二者的结合。

在filter中webcam设备可能不止一个,有关这些设备的操作辅助接口就在webcam.c中实现。

之前说了摄像头部分,对于声卡部分也是极其类似的,管理辅助函数在mssndcard文件中实现,另外还配以几个声卡设备本身的描述符文件来描述,其与filter的关系类同于视频部分。还有就是这些设备的描述符结构体汇总在mscommon中,而filter的汇总在alldescs.h头文件中。

2)ortp

关于ortp模块的说明参见参考资料1

四 linphone 中数据结构说明

程序中定义了一个比较大的数据结构体——linphonecore,将其作为总的控制结构。通过该结构体,可以找到所需的大部分信息。这些信息要么是直接在该结构体中定义,要么是在其包含的模块相关的子结构体中。可以想象,内存中保存的该结构体,就像整体软件信息的总控点,通过该总控点,可以直接或者间接的得到系统相关的信息,这在一定程度上可以简化系统的架构和实现。(在许多开源软件中都可以看到这种代码架构方式。)下图展示了linphonecore结构体:

指针sal指向sal数据结构图,通过该指针,就可以找到sal模块用到的所有数据结构体。

Config部分指向linphone的配置信息,包括网络,sip协议,rtp传输,音视频参数以及编解码器信息。

call指针挂载了所有的通话,每一路通话都由linphonecall结构体表示。

Audiostream和videostream保存了媒体信息,a_rtp和a_rtcp保存了RTP相关的信息。

五 linphone 的初始化过程

首先初始化全局的状态:power为GST A TE_POWER_OFF,即关闭状态;reg为GST A TE_REG_NONE,没有注册;call为GST A TE_CALL_IDLE,空闲状态。

设置新的power状态为startup。

1)Ortp库初始化,调用ortp_init

在ortp_init中主要创建了payload type链表,所有支持的payload type都被创建在一起了,这里注册的payloadtype就是底层RTP传输所能够支持的。

添加payload type,先为音频,后为视频。

2)调用ms_init初始化mediastream库,该库封装了媒体处理接口,使得多媒体数据的处理变得简

单。

Ms_init中形成了三个全局链表,一个为filter描述符链表,所有media streamer支持的filter的描述符在这里被串接在一起了,包括编解码,音视频读写以及RTP发送和接收处理相关的filter。第二个为音频卡描述符链表,所有支持的音频卡相关的描述符也被串接在一起。第三个就是视频卡描述符链表,处理类似音频卡描述符。

3)调用sal_init进行sip协议栈的初始化。该过程将返回一个sal结构体。

Exosip全局结构体的创建以及初始化。

需要注意,在这里相当于有三层封装调用:一层为sal层的封装调用,一层为exosip层的封装调用,最底层为osip层的基本调用。

另外需要注意的是在这里没有创建exosip任务,而是在后面的读取并配置sip配置信息时才创建exosip任务,并监听特定端口。

将lc->sal的up指针指回linphone core全局结构体

设置sal上的回调函数,这些回调函数在对应的sip协议处理完后用于调用来处理外层有关call 与media流的一些处理。

如果配置文件中没有设置sip会话的过期时间,则在这时将其设置为200

将所有sip setup配置串联到registered_sip_setups全局链表上

4)读取配置文件中有关音频的设置并对声卡进行设置

根据配置文件中描述的声卡设备id将声卡设备配置到linphone core结构体的sound配置结构体对应声卡的描述项上。

设置用于响铃的音频文件的路径。该文件路径会被保存到sound configure结构体的local_ring项目上。

类似上面,设置用于提示远端响铃的音频文件的路径,最终保存到sound configure结构体的remote ring项目上。

检查系统中的声卡设备

配置是否使能回声消除

配置回声限制

配置增益

配置回放增益

5)读取配置文件中有关网络部分的配置并对网络参量进行配置

读取配置文件中有关带宽的设置,并对linphone中音视频使用的带宽进行配置。首先保存带宽值到net config结构体中,音频带宽配置为配置文件中读出的值与默认值的小者,视频带宽配置为读出值减去音频值减去10和0之间的较大者。这里10相当于是一个缓冲。

设置stun server

设置nat防火墙地址

配置是否使用防火墙策略

配置是否只对SDP进行nat转换

配置mtu值

设置信息分包时间

6)读取配置文件中有关RTP部分的配置并对该模块进行配置

首先设置音视频的RTP端口号

配置RTP音视频抖动补偿时间,默认为60毫秒

配置nortp超时时间,即没有RTP或者rtcp数据包时linphone认为对端crash或者网络中断的超时值

设置在静音时不进行RTP传送,默认为F ALSE

7)读取配置文件中有关编解码器相关的信息并设置到linphone的编码器信息结构体上

读取配置文件中的音频编解码器信息,如果有,则查找之前初始化ms模块时建立的全局过滤器描述符信息表,如果找到,则说明相应的编解码器支持,否则不支持配置文件中添加的编解码器。对于视频也类似。

通过上述操作,所有的配置文件中可支持的编解码器信息都被创建到audio_codecs和video_codecs两个链表上了,之后将它们添加到linphone的codecs_conf结构体上。

另外,在这步操作时,也将系统支持的其他编解码器添加到了codes_conf结构体上,基本原理如下:对于video,查找所有的RTP初始化时创建的payloadtype表,如果是video类型的payload,并且被mediastreamer支持,但是不在配置文件描述中,就将其添加到链表上。也就是系统初始化时支持的但是在配置文件中没有说明的编解码器也会被添加到配置结构上。

更新已分配的音频带宽值。找到需要最大带宽的音频编解码器,将其带宽需求除以1000作为linphone的audio_bw值,同时重新设置linphone网络配置中的上下行带宽值。

Desc_list上的编解码器信息与RTP初始化时设置的payload type信息的区别:

Desc_list将medie streamer支持的所有filter的信息都串联在一个链表上,不仅包括了编码器解码器,还包括了RTP发送和接收filter,以及音视频的读写filter。这些都是站在media streamer这个中间层的角度来考虑。Filter的构成也很规范,包括了一些必要的描述信息以及数据的读写处理接口。 Payload type链表是所有RTP传输中支持的payload的链表,每一个type的描述信息主要描述了该类型的type的时钟速率,正常比特率,采样比特等,可以看出这些信息与传输也是紧密相关的。除此之外,也描述了编码信息,这部分信息与desc_list中的编码器信息部分会存在交集。

8)读取配置文件中有关sip协议的相关信息,并以此来配置linphone的sip模块。

配置是否在发送数字时使用sip info信息。

配置是否在发送数字时使用rfc2833信息

配置是否使用ipv6

配置sip的传输端口信息。指定是使用随机值还是知名5060端口

将端口信息设置到linphone core中,并启动sip监听。这样,当sip协议数据到达时即可被处理。

首先调用sal_listen_port启动监听端口。在这里,协议层被选择和设置,一般情况下都是udp,这里为eXtl_udp。之后创建并启动_eXosip_thread任务,该任务处理sip协议数据的接收,协议的处理,状态机的处理,数据的发送等。即几乎所有与sip协议有关的处理都会在该任务中处理完。最后保存用户代理信息。

获取配置文件中的联系人信息,如果联系人为空,或者配置文件中联系人信息不为空,但在将其设置为主联系人信息时出错(比如格式错误),则基于环境变量中的host和user信息创建主联系人,否则将配置文件中的联系人信息设置到sip_conf结构体的联系人上

如果配置文件中设置了猜测主机名,则将该配置设置到linphone core的sipconfigure结构体上配置incoming call的超时时间,如果超过超时时间没有answer则terminate该call

读取并配置代理信息,所有的代理者信息都被添加到sip configure的代理者链表上

读取并配置默认代理者信息。默认代理者会从所有代理者中挑选,根据配置文件,然后放到

linphone core结构体的default_proxy上

读取并配置认证信息。首先从配置文件中读取usrname,userid,password,ha1,realm等信息,并根据这些信息创建一个新的认证信息结构体,将其添加到linphone core的auth_info链表上。同时,查找所有处于pending状态的待认证事件,如果linphone core中能找到一致的认证信息结构体,则对其进行认证。

根据配置文件对sip_conf的其他变量进行设置

9)读取配置文件中有关video模块的配置并将其设置到linphone core中

首先读取所有的video设备,将其添加到video_conf的cams项上

读取配置文件中video部分有关device的设置,在系统中查找是否已经有该设备。如果没有找到,则使用default设备配置。如果原来保存的设备不为空,并且与新设备不一样,则基于新设备重新触发preview预览。如果linphone已经准备好了,并且video_conf上的设备不为空,则将该设备的描述串写入配置文件,也即覆盖之前的配置。另外,如果如果描述串中有static picture则描述串在被重新写到配置文件之前会设置为空。

根据配置文件设置视频size

根据配置文件设置其他配置项,包括是否进行capture,是否display,是否self_view等。

10)设置linphone之前和当前的模式都为在线状态。设置最大call logs为15

11)读取并配置ui

读取配置文件friends section部分的信息,创建friends结构体并保存配置。将所有的friends添加到friends链表上。并额外做一些其他处理。

最后读取call logs 信息,并创建call logs结构体保存logs信息,将所有logs添加到call logs链表上。

12)显示ready。

全局状态配置为power on

将sip_conf中的auto_net_state_mon设置到linphone core上

Linphone core的Ready设置为TRUE

至此,整个初始化过程完成。初始化后的内存数据结构体及状态:

首先需要创建linphone顶层最全局结构体linphonecore也即程序中多处用到的lc。对于phone的很多相关操作,该对象是主要的handler,在整个程序运行过程中在内存中只有一个实例存在。

初始化ortp库,加载支持的音视频RTP payload类型到全局结构体av_profile上。同时全局结构体linphone_payload_types也指向这些payload元素列表。

初始化ms库。Desc_list全局指针挂载了所有支持的filter描述符信息。加载所支持的声卡设备的描述符到全局变量scm上,加载支持的所有视频捕获设备的描述符到视频全局变量scm上。

初始化sal模块,在此过程中初始化exosip库,在exosip初始化过程中初始化了osip库。在此过程中,从下到上,osip全局状态机被加载,osip全局结构体对象也被创建,其上的事务链表,callback 等子项也被设置,exosip全局结构体对象也被创建,其上底层协议处理部分以及内存分配部分也部分的被初始化,osip对象被挂载到了exosip对象上。Sal结构体对象被分配内存。

Linphone core被挂载到sal上了,sal的回调处理函数被加载。

配置文件中的配置项被一步步的加载,同时linphone的配置部分也被不断的在内存中创建出。状态被更新,整个初始化过程也就此完成。

六 linphone 建立通话过程说明

1 拨号call过程

用户执行呼叫,调用call命令

Linphone中调用该命令对应的执行函数lpc_cmd_call

如果lc-call不为空,也就是说linphone全局结构体上当前的会话还存在,则输出打印,要求用户先关闭当前call;否则调用linphone_care_invite处理call命令。(该版本只支持一个call存在)在linphone_core_invite中:

首先调用linphone_core_interpret_usl解析URL地址。输入的地址可能是一串字符,通过该函数将其中的关键元素解析出来,主要是按照osip_from结构体的格式解析出来。

然后调用linphone_core_invite_address进一步的以osip_from格式的地址为参数进行处理,完了释放地址参数。

在linphone_core_invite_address中:

在该函数中对地址信息进行进一步的简单处理,获取到对外的本地信息,然后调用linphone_call_new_outgoing发起会话请求。

linphone_call_new_outgoing中,

首先创建linphone call内存对象实例。方向设置为call outgoing,创建一个salop对象实例,其root 指针指向初始化时创建的全局sal结构体上,并对op的一些基本域进行设置。在会话过程中,与sal 相关的操作主要还是基于salop的。另外,userpointer指针指向call本身。

调用linphone_core_get_local_ip获取本地的ip地址。如果设置了nat防火墙策略,则就用nat防火墙地址为本地ip地址,否则,如果配置了ipv6,则使用ipv6地址。如果仍然没有得到结果,则调用sal_get_default_local_ip接口来获取。该接口使用底层exosip提供的方法,即利用socket的gethostbyname接口来在与网关建立connected连接后从socket中获取本地ip地址信息。

调用create_local_media_description创建本地媒体描述结构体。在该接口中,创建一个SalMediaDescription对象实例,并用之前获取的信息对其进行设置,包括流数量为1,本地ip地址,用户名,带宽。针对其携带的流,流的地址为本地ip地址,流的端口为配置的RTP音频端口,proto 为SalProtoRtpAvp,类型为audio,ptime为netconfig中的down_ptime。对于payload的配置逻辑如下:从编解码器配置项中读取所有的音频编解码器payload,如果是enabled,并且linphone当前支持该编解码器,带宽也允许,则将该payload添加到streamer的payload链表上。目前payload的带宽是通过payload的Bitrate计算出来的。计算方法:包大小为ip4头+UDP头+RTP头+Bitrate除以50*8,即除以400。因为Bitrate为基于比特单位,除以8变为字节,但是这里除以50是为什么?貌似是包的数量,也就是一秒钟采样的数据用50个包来传送。此时计算出来的为包的大小,将其再乘以8乘以50变为包含ip、UDP及RTP包头的情况下的Bitrate,此值除以1000作为linphonecore中的音频带宽值。基于上述计算得出来的音频带宽值和网络配置中配置的上下行带宽值更新linphonecore的带宽配置的方法如下:如果给的带宽值为0,即表示无穷,音视频的下载带宽设置为-1,否则,计算出来的音频带宽值和给定带宽值中较小者作为音频下载带宽,给定带宽值与音频下载带宽的差值减去10与0的较大者作为视频下载带宽值,也就是视频带宽或者为0,或者为可用带宽减去音频所用的带宽(优先保证音频)再减去10作为缓冲界限后的值。上传带宽的配置方法也一致。如果当前配置的带宽值中的较小者(上行和下行选择)大于当前编解码器Bitrate所占用的带宽,则将其clone一份放到streamer的payload链表上,否则不添加。在处理完配置的音频编解码器payload 后,从全局av_profile中找到telephone-event也将其添加到streamer的payload链表上。Streamer的带宽值被设置为音频下载带宽值,应该小于等于media的带宽配置。如果视频也被允许,那么streamer1将作为视频流的描述符,同样从RTP配置信息中得到视频端口赋给该描述符,proto同音频部分,类型此时为SalVideo。类似音频部分,将codec配置中的符合带宽限制的视频编解码器payload添加到该streamer的payload链表上。如果视频下载带宽不为0,则该streamer的带宽值被设置为视频下载带宽值。至此,media描述符就创建完成。Media描述符当前是被挂载到call的localdesc上。

调用linphone_call_init_common对call的其他域进行设置,状态为LCStateInit,start_time为当前时间,media_start_time为0,创建call_log实例记录拨号记录,通知所有的friends我们当前的onthephone状态。如果是设置了stun服务器,则调用linphone_core_run_stun_tests测试stun服务器,并配置streamer的endpoint candidate的端口和地址。

调用discover_mtu获取当前的mtu值,这只在当前netconfig中的mtu设置为0时才进行。发现mtu的过程也是通过向对端发送数据,然后根据socket的options操作来查看,比如根据收到的ICMP 包信息,根据获取到的mtu值重新设置mediastream的mtu。

综上,在new outgoing的过程中,我们创建了call实例,salop实例,media实例同时包含streamer 实例。基本关系为call-->salop,call-->media-->stream。本质上来讲还是在初始化call,但在此过程中也初始化了需要的salop,以及media。

上一步通过linphone_call_new_outgoing为发起一个新的会话做好了准备,包括创建了需要的call 实例,salop实例等,这些相关信息保存到call对象中,该对象在此时被挂载到linphonecore上。

如果目的代理不为空,或者sipconfi的ping_with_options为F ALSE,则掉用linphone_core_start_invite发起会话请求,否则,call的状态被设置为LCStatePreEstablishing,该状态指示稍后,即ping完后继续发起invite请求。为了完成ping操作,先创建一个用于ping的salop,调用sal_ping基于该op以及from和real_url参数发送sip的ping数据,重新将call的start_time设置为当前时间。

对于linphone_core_start_invite调用:

在调用该接口前,我们已经创建了linphone core 实例,并在发起invite请求的准备过程中创建了call实例,以及得到了dest_proxy地址信息。在这些准备工作完成的前提下,系统进一步处理invite 相关的后续操作:

首先调用get_fixed_contact来获取联系人信息。

如果当前设置了防火墙,并配置了nat地址,则从linphone core结构体实例中获取首要的contact 信息。这些信息基本上是从sip_conf中拿取的。

如果上一步失败,并且call上的salop结构体实例已经被创建了,并且其上的contact信息不为空,则返回空,表明不需要修改contact信息。

如果上一步失败,则判断ping_op操作是否成功完成,如果是,则使用ping_op上的contact信息。

如果上一步失败,并使用了代理,则使用register时的contact信息

如果还失败,则使用本地ip地址和配置到linphone core上的端口信息组合出contact信息返回如果在上一步获取sip_conf中的contact信息时返回失败,则该接口此时返回空

通过上面调用,如果获取到了contact信息,则将其设置到call 的op的contact域上。

Call的state设置为LCStateInit

调用linphone_core_init_media_streams初始化媒体流。参数为linphone core实例和call实例

首先从call实例的localdesc上拿到media描述符

基于media描述符上的stream[0]也就是音频流的端口调用audio_stream_new创建audiostream。

在audio_stream_new中,创建了一个audiostream结构体实例,stream的session域被初始化为一个RTP session,这是通过调用create_duplex_rtpsession创建的。在该接口中,创建了一个全双工的RTP session结构体实例。首先通过调用rtp_session_new为RTP session实例分配内存,并调用rtp_session_init对这个session进行初始化。这里的初始化包括设置session的mode(send or recv or send_and_recv)。并根据mode设置session的flags。如果可以发送,发送ssrc初始化为一个随机值。并设置默认的源描述信息for rtcp,挂在session的sd上。设置session的rcv和snd的profile,此处都设置为av_profile全局变量了。初始化rtp和rtcp的socket都为-1,配置默认的接收和发送socket buffer大小。

从rtp_session_init中出来接着配置RTP session的一些类似全局的参数。Recv_buff_size配置为MAX_RTP_SIZE,调度模式为关闭状态,阻塞模式为不使用该模式,自适应平衡抖动补偿,对称RTP,设置本地地址,此时会创建rtp和rtcpsocket,并对socket的参数进行配置。比如是否reuse address,设置socket buffer size等。注册timestamp和ssrc_changed事件的回调函数,以及ssrc changed的触发阈。最后返回创建的RTP session 结构体实例。

返回的RTP session 实例被挂到了stream的session域上。接着调用ms_filter_new创建了RTP send filter结构体实例。这被挂载到了stream的RTP send域上了。最后对流的相关参数进行了初始化。在ms_filter_new中,会遍历系统最初初始化时创建的filter 描述符链表desc_list,从其上找到id 与当前要创建的id一致的描述符,然后调用ms_filter_new_from_desc基于该描述符创建filter。在这个接口中,首先初始化了一个msfilter的结构体实例,然后把当前找到的描述符挂到该filter的desc 域上,调用描述符的init接口对filter进行初始化,最后返回这个filter。

Audiostream创建成功后被挂载到了linphone core结构体实例的audiostream域上。

基于初始化linphone core config时,从配置文件及系统中获取的对sound部分的配置信息对audiostream进行实际使用上的配置。也就是针对具体使用实例的配置。之前可能就是全局的系统参数级别的配置。这包括增益的配置,回音消除的配置,echo limiter的配置,自动增益的配置,噪音的相关配置等。

如果在linphone core初始化时已经初始化了rtp_transport,则将audiostream上的RTP session上的rtp和rtcp上的tr指向linphone core的 a_rtp和a_rtcp上。相应的,这两个结构体的session指针指向这里的session。

至此,音频流的初始化工作基本完成。

如果系统定义了视频流的支持,则开始进行视频流的初始化。这是通过video_stream_new接口完成的,参数类似音频部分,只不过这里是用的media的stream[1]描述符上保存的视频RTP端口。

在video_stream_new中,创建了一个videostream结构体实例。类似与音频部分,通过调用create_duplex_rtpsession接口创建一个全双工的视频RTP会话,并将其挂载到videostream的session 域上。

在create_duplex_rtpsession接口中,创建全双工的RTP session 结构体实例。并作初始化工作,这步同音频部分。

调用ortp_ev_queue_new创建一个ortp event queue,事件队列。

调用ms_filter_new创建一个RTP send filter,在该接口中会创建一个msfilter实例。初始化工作同音频部分。

调用rtp_session_register_event_queue同时将流的事件队列注册到RTP会话上。也就是session 的eventqs也指向stream的evs。

设置视频的高度和宽度,返回视频流videostream实例。

从上面的初始化工作中可以看出,音频流和视频流的RTP session和filter的创建是一致的,调用相同的接口完成,因此可以看出是通用的,只是相应的挂载到了音频流和视频流上。至此基于call 的音视频流的初始化工作就完成了。

如果在sip_conf中没有设置sdp_200_ack,则将call的media_pending设置为TRUE。另外,将call的localdesc描述符设置给call的op,这是通过sal_call_set_local_media_description接口来完成的。在该接口中,首先增加localdesc的引用计数,同时递减op上的localdesc的引用计数,如果减一后为零,则释放其资源,同时将参数中给出的localdesc给该op。

从call的log实例上获取from和to地址,分别作为发起call会话的from和正式url即real_url。至此,基本的初始化的工作做得差不多了,调用sal_call发起sip协议请求。Sip的Invite消息报文在

此时才真正的发给对端。

在sal_call中:

会话操作基于call的op实例发起。从参数中获取from和to地址,将其设置到op的from和to 域中,并调用sal_exosip_fix_route来检查路由。

在sal_exosip_fix_route接口中,首先判断op的route是否为空,如果是空,就不做任何处理,说明没有配置路由,否则,继续。

调用osip_route_init分配并初始化一个osip_from结构体实例。然后调用osip_route_parse将op中的字符串形式的route解析为这里osip_from格式的结构体,如果失败,说明route配置有误,将op 中的route重新该为null,否则从uri列表中查找是否有名为lr的节点,如果没有则添加一个,值为null,并将此修改更新到op的route项中。最后释放操作过程中为临时变量分配的内存。

Route检查了,现在调用eXosip_call_build_initial_invite接口创建一个invite消息。在该接口中,我们首先将to参数指定的字符串形式的目的地址解析到osip_from格式的结构体中,如果解析失败,返回。接着调用generating_request_out_out_dialog创建一个最小的out_of_dialog的request请求消息体。

在generating_request_out_out_dialog中,此时我们已经操作到sip底层协议部分了,主要是调用exosip和osip库来进行相关操作。这里我们首先检查exosip全局变量下的extl是否为空,也就是底层网络协议的支持,默认是UDP。如果这块为空,说明底层协议支持没有完成,后续将不能收发数据包,直接返回错误。接着根据extl中proto_family指定的协议簇通过socket接口来猜测主机的ip 地址,如果失败则返回错误。接着调用osip_message_init分配并初始化一个osip_message结构体实例。到时候sip数据包中按照协议规定的格式的各个位置的数据会被解析到osip_message结构体中,进而在程序中传递和处理。接着准备invite类型sip包的请求行,包括method,invite,协议2.0,status_code为0,reason_phrase为null。如果method为register,则调用osip_uri_init为request分配并初始化uri结构体实例,并将proxy参数中的信息解析到该结构体中。基本按照同样的方法将from 参数的数据解析到request的to的uri中。

对于非register类型的请求,操作则如下:此时将to参数解析并设置到request的to的uri中,如果成功,并且其uri不为空,则将uri上headersl链上的元素一个一个取出来。如果这个原始不是from,to,call-id,creq,via,contact则将其拷贝给request。这步首先调用osip_header_init分配并初始化一个osip_header的结构体实例,然后将之前取出来的header的name和value拷贝给这个结构体,最后将其挂载到request的headers的链表上,也就是osip结构体的headers的链表上。无论是不是上述拷贝的header,最后都从原来的链表上移除并释放内存。也就是从最初的从to参数解析到osip_from 结构体实例上的。对于非register请求,如果存在代理,调用osip_route_init分配并初始化一个osip_from结构体实例,将proxy参数数据解析到该结构体中,类似之前,从该结构体中查找名称为lr的url_params,将osip上的to上的url拷贝到req_uri中,并将解析后的proxy添加到osip的routes 上。否则,就将proxy上的url给osip的req_uri。释放proxy本身的内存。此时调用osip_message_set_route为osip的router分配内存并将to参数的数据解析赋值给它。如果不存在代理,则直接将osip的to的url给osip的req_uri。

至此,register和非register类型的请求的不同处理完成了。调用osip_message_set_from将参数from 解析并配置到osip结构体中。调用osip_from_set_tag将新生产的一个随机数作为from的tag。分配并初始化osip_call_id类型的结构体实例,生成随机数配置其number,并将其挂到request的call_id 上。分配并初始化osip_creq类型的结构体实例,如果是register请求,则其number设置为1否则为20,然后也将其挂载到osip的cseq上。

调用_eXosip_request_add_via配置request的via。

设置max-forward为70。如果method为options,则配置request的accept为“application/sdp”。用eXosip全局变量上的user_agent配置request请求中的user_agent域。最后返回osip的request消

息结构体。

调用_eXosip_dialog_add_contact添加联系人信息,最后挂载到osip的contact域上。

调用osip_message_set_subject添加subject信息,这个是sip头的subject信息。

最后调用osip_message_set_expires设置sip头的expires为120秒。意思就是说如果经过120秒对端还不响应就取消当前的invite请求的发送。

在通过上述操作创建osip基本消息结构体实例后,在外层继续对其进行一些初始化。这包括设置allow,即允许的options方法。如果op的base中包含了contact信息,则将初始化osip结构体时添加的contact信息清除,重新将这里的contact信息设置进去,因为初始化时这些都是根据系统信息猜测的,在没有进行任何配置的情况下使用。如果op中session_expires不为零,则将invite的头部添加session_expires,值为200。并添加timer的support。如果op中本地的媒体信息也已经被配置了,则将op的sdp_offering设置为TRUE,将本地媒体信息的配置通过调用接口set_sdp_from_desc 设置到请求osip结构体中。在上述接口中首先将媒体描述信息转换为sdp_message格式的结构体,在sdp_message中间格式的sdp数据转化为串行的字符串,最后将其挂载到osip结构体的bodies域上。

至此完成了基本的设置,调用eXosip_lock()和eXosip_unlock()保护eXosip底层的发送数据函数,这里为eXosip_call_send_initial_invite。最后保存call的id到op的cid上,并调用sal_add_call将当前op挂载到根sal的calls链表上。所有创建成功的op都会被挂载到sal的calls上,程序通过sal实例就可以遍历所有的与sip协议处理有关的op。

现在看看在接口eXosip_call_send_initial_invite中做了哪些操作:首先调用eXosip_call_init初始化一个exosip_call结构体实例,并为其分配内存。接着调用exosip_transaction_init初始化一个osip_transaction结构体实例。事务的初始化信息大部分都是来自之前初始化的osip_message类型的request结构体的。初始化完成之后就将transaction挂载到全局osip结构体的对应类型事务的链表上,同时,也将其挂载到exosip_call的out事务域上。根据request信息,new一个out的sipevent。这时osip_event类型的,不同于exosip_event。为了关联具体的事物,events的事务ID设置为之前创建的事务。设置事务的your_instance域,这个域用来保存一些有用的东西,这里使用jinfo_t类型的结构体将exosip_dialog,exosip_call,exosip_subscribe以及exosip_notify关联起来,交给事务的your_instance。不过此时还只有exosip_call的信息,其他的实例还没有创建。下一步将之前创建的sip_event挂载到事务上,并将创建的exosip_call实例挂载到exosip的j_calls域上。调用exosip_update 更新exosip实例,调用exosip_wakeup唤醒exosip任务的处理。最后返回刚创建的call的id。因为我们在exosip的osip的事务队列上添加了一个事件,之后exosip任务会处理该事件,并根据事件描述发送sip的invite消息。

此时再判断sip的配置中sdp_200_ack是否设置了。和之前不同,如果设置了,则将call的media_pending设置为TRUE,并同意将call中的local media 描述设置到call的op上。感觉在sal_call 中并没有改变call的media description。调用linphonecore的vtable函数显示正在连接远端客户端。

如果之前调用sal_call时返回失败,则显示无法建立call提示,并stop媒体流,这是linphonecore 上的媒体流,同时清除call上有关RTP profile的描述。最后调用linphone_call_destroy销毁之前创建的call。其实这里可以看出通过顶层的call可以找到底层创建的所有实例。

如果之前调用成功了,则修改linphonecore上的状态为call_out_invite。最后返回。至此,顶层的call命令处理完了。

从上面的发起会话请求的初始化过程来看,系统基本上是沿着从外层到内层,从上层到下层的次序来初始化的,外到内是说,先是call,在到call上的op,再到op上的media desc,再到其上的stream desc。这几步初始化了对媒体流的控制结构体,因为主要都是desc即描述信息。接着道stream本身,

到其上的RTP session,到filter,基本上就与流本身的传输相关了。从上到下是说,先初始化上层协议相关的结构体,比如sip协议相关的数据结构的初始化,接着是媒体和RTP等下层相关协议的初始化。

2 等待响应

此时invite请求包发送给对端了,客户端的相关状态也设置好了,就等待对端的响应并进行处理。

之前已经说明,call调用是在main里面处理的,处理完成后调用相关接口将请求和数据交给exosip 任务去实际的发送数据。之后main仍然每间隔一秒进行迭代操作,而exosip任务则处理数据包的收发和相关底层的超时操作。因为此时call的状态为init,这样在linphone core迭代中不会去处理call 相关的操作。

当对端有响应之后,该消息最先被exosip任务捕获到。Exosip在调用exosip_read_message时会从UDP套接字接口上读取到对端的响应数据包。接着调用_exosip_handle_incoming_message对缓冲在缓冲区的数据进行处理。

在该接口中,首先调用osip_parse解析缓冲区中的sip信息。在osip_parse中,创建一个osip_event 类型的结构体实例,将buf中的sip数据解析到该结构体的sip域中,根据消息中的类型设置events 的类型。对于输入的message,有如下类型的定义,invite请求,ack请求,除此之外的请求,以及1开头、2开头以及3456开头的状态响应。之后将该events返回。

调用osip_message_fix_last_via_header根据host和port信息检查sip中的via域。

调用osip_find_transaction_and_add_event查找该响应对应的事务,并将由此生出的events添加到事务的事件队列上。在该接口及子接口的调用中,首先根据响应消息的类型找到osip全局变量上其所属的transactons队列,再到队列中查找所有的事务,找到与当前给的events匹配的事务。这有点类似于二级过滤。如果找到了,就将刚才创建的events添加到该事务的事件队列上。

如果事件添加成功,则返回,否则,说明事件没有对应的事务,继续处理。如果消息是request 消息,调用exosip_process_newrequest处理,否则调用exosip_process_response_out_of_transaction处理。在newrequest中,会创建一个新的transaction,将事件挂到该事务上,并将事务挂接到osip全局变量对应的事务链表上。之后针对事件的类型,进行一些对应的处理,比如发送响应等。否则,将事务加载到exosip全局变量的事务队列上,返回。对于response,说明收到了out_of_transactions 的响应,在exosip上进行查找做一些处理。

对响应消息处理完后,或者是对消息直接做了响应,或者将需要处理的放到了事务队列的某个事务的事件队列上。接着,exosip迭代检查处理事务队列上的所有事务。在处理过程中,如果需要传给上层处理,就会构造一个exosip_event结构体实例,添加到exosip的事件队列上,由linphone_core 任务也就是main主线程去处理。

假设此时底层进行了协议的协商处理,基本顺序为send(invite)-->recv(180ring)-->recv(200 ok)-->send(ack)会话建立。当处理到第三步,也就是主机准备走第四步时,对端的200 ack我们收到了,此时我们再发送自己的ack之前需要处理对端的媒体类型,并为媒体流做好进一步的准备。此时callback的处理顺序为首先进入cb_rcv2xx回调,在该回调处理中调用cb_rcv2xxx_4invite回调接口。在该接口中最终会调用report_call_event将exosip_call_answered类型的事件交给linphone_core 任务。

Linphone_core任务在sal_iterate处理中处理底层协议交上来的事件时,如果判断为上述事件就调用call_accepted接口。在call_accepted中:

首先调用find_op从sal全局结构体的call上查找call_id等于events的cid的op,如果找到就返回。然后将op的did赋值为events的did。调用exosip_get_sdp_info从对端最后一次的with session

description的200ack中取出有关sdp的信息。实际上在该接口中会将字符串形式的从对端收到的sdp 信息解析到sdp_message结构体中并返回。如果获取成功,则根据该sdp创建主机端对远端客户的媒体描述,基本步骤如下:

调用sal_media_description_new创建远端的media_desc结构体实例,将其添加到op的base的remote_media上。调用sdp_to_media_description将之前解析出来的sdp信息配置到media_desc上。如果配置成功,则调用sdp_process处理音视频流处理。

在sdp_processs中,创建一个新的media_desc结构体实例,放到op的results上,如果op的sdp_offering被设置,则调用offer_answer_intiate_outgoing接口创建一个基于本地的offers和远端响应的能力的流。否则,调用offer_answer_initiate_incoming接口创建一个流基于本地的能力和远端的offers。这个流会作为一个answer发送给远端的offers。对于outgoing,会遍历本地offers的所有streamer,取出其protocol和type,然后在远端answer中查找,如果找到,就使用这两个stream_desc 调用initiate_outgoing设置results的流描述。在该接口中,取出二者的payloads的交集,端口,addr,带宽和ptime使用remote的answer,最后media描述的addr也设置为remote的answer的值。对于incoming,遍历远端的offers的所有streamer,取出其proto和type,在本地能力中找到匹配项。类似的,在incoming_initiate中,取出二者payloads的交集,而此时端口,adr,带宽,ptime等则使用本地的。这几步实际上是找出共同支持的媒体描述,交给results保存。另外,如果是基于远端offers 来适配的话,将根据results构造转换出sdp_message结构体的sdp_answer给op,这在后续会被用来反馈给对端,表明我们支持的能力。

Sdp_process处理完后,调用exosip_call_build_ack将协议规范中需要的ack发送给对端。在该接口中首先调用exosip_call_dialog_find在exosip的jcall上查找同样dialog_id的call及其上的dialog。如果没有找到,则返回错误,没有call对应,否则继续调用exosip_find_last_invite基于之前找到的exosip_dialog在其上再查找携带invite消息类型的事务。这里会将incoming和outgoing类型的事务都找一下,如果只存在一个,则取其之,否则,比较两个的birth_time,取离当前时间点更接近的一个。找到会话后,调用exosip_build_request_within_dialog构造需要回复的osip_message类型的ack 结构体实例及其部分内容。初始化的部分信息直接从dialog上获取,因为它保存了部分有关会话的关键信息。另一部分信息是从事务上来获取的,调用exosip_call_reuse_contact,从事务的orig_request 上确定osip_message类型的信息,将其拷贝到这里创建的osip_message类型的ack上。之后再对其进行一些其他方面的配置。这样,build ack的工作就完成了。

在call_accepted里对ack还有一些其他设置,包括将op上的contact信息设置到这里的ack上,另外,如果op的sdp_answer存在,调用set_sdp将其配置到ack上,实际就是配置到其sdp message body上。最后调用exosip_call_send_ack将该ack发送给对端。与之前有所不同,这里是直接调用底层发送接口cb_snd_message将响应送给对端,而不是作为一个事务添加到事务队列上再交给exosip 任务去处理。

之后调用sal上注册的回调函数call_accepted来基于op进行处理。

在回调函数call_accepted中,首先判断call的状态,如果是lcstateavrunning,则说明是已经accepted 了,直接返回就可以了,否则继续处理。

如果linphone_core上audiostream的ticker不为空,说明在其他地方之前已经启动媒体传输了,先调用linphone_core_stop_media_streams停掉媒体流,再调用linphone_core_init_media_streams重新创建一个媒体流。

如果call的resultdesc不为空,则先释放它,通过递减引用计数来做到。之后将之前协商的存在于op上的resultdesc拷贝给这里的call。之前是通过localmedia和remotemedia来协商最终二者都能兼容的媒体类型的。如果拷贝成功,则增加该resultdesc的引用计数,并将call的media_pending设置为F ALSE。

如果resultdesc存在了,并且其上的流描述信息不为空,则将linphonecore的state设置为

gstate_call_out_connected,然后调用linphone_connect_incoming继续处理来打开媒体流的传输。

在linphone_connect_incoming接口中,首先显示connected状态,然后将call的状态设置为lcstateavrunning,如果linphonecore的ringstream不为空,则先调用ring_stop停止流,然后调用linphone_core_start_media_streams开启流媒体的传输。

在start_media_streams接口中,首先将rtp的jitter补偿设置为声卡延迟和rtp中有关audio的jitter 配置二者大的一方。设置call的media_start_time。

首先处理音频部分,调用sal_media_description_find_stream从call的resultdesc中找proto是salprotortpavp类型是salaudio的stream,实际上是stream描述符。如找到了并且端口不为0,则继续处理,否则表明没有响应媒体的定义,出错返回。有说明在本地支持前提下,在和对端协商结果下,可以进行音频流的处理和传输。此时,调用make_profile基于媒体描述和流的描述创建会话需要的rtpprofile。在该接口中先调用rtp_profile_new创建一个rtpprofile类型的结构体,名称为call profile,遍历之前找到的straem流的描述符,取出其上支持的所有的payloadtype,将他们添加到刚创建的call profile中。另外,还有其他一些操作,包括保存了第一个payload的序号,根据带宽的调整,配置了相应的payloadtype。通过该接口创建的rtpprofile被赋值给call供当前会话使用。之前在初始化时已经创建了全部profile,av_profile,这是在RTP初始化过程中进行的,而这里创建的应该是该profile 的一个子集,针对其上的payloadtype来说。如果linphonecore的use_files没有被设置,则取出音频播放设备和捕获设备的描述信息,调用接口audio_stream_start_now将这两个描述符作为参数传递进去启动音频传输。

audio_stream_start_now接口进一步调用audio_stream_start_full进行处理。这几个接口是mediastream库提供的。在start_full中,第一步调用rtp_session_set_profile将profile设置到流的rtpsession上。如果参数远程端口大于零,则调用rtp_session_set_remote_addr_full将远程ip地址,远程端口以及远程rtcp端口号设置到session上。这里不仅将这些参数设置到rtpsession上的相关域保存起来了,而且也调用了socket的connect与远端同时建立连接了。之后会将已建立连接的标识rtp_socket_connected设置到session的flags上。接着将payload号和jitter补偿分别设置到rtpsession 上。

如果远端端口号大于零,调用ms_filter_call_method调用filter的methods,id为ms_rtp_send_set_session。在这里,filter下面有filter-desc,filter_desc上面有methods,可以用来对filter 进行配置。Ms_rtp_send_set_session这个id将调用函数sender_set_session这个接口,在该接口中根据rtpsession中的send上的profile和payload number得到payload type。如果payload type的mime_type 为g722,senderdata数据结构体上的rate设置为8000,否则根据payload type的clock_rate来设置。最后将senderdata的session指向rtpsession。Senderdata结构体实例是send filter的data指针指向其私有的数据,包含了与RTP发送有关的信息。对此,其他filter也类似,也就是说每个filter都有其私有的数据,用于其在process中使用,比如recordfile对应的filter的私有数据为文件指针,也就是保存数据的文件指针,这样就很好理解了。

调用ms_filter_new创建一个新的filter,根据id ms_rtp_recv_id从desc_list上取得id一致的filter 描述符,然后基于该描述符创建新的filter,并将filter的desc指针指向该描述符。接着类似发送,调用filter的methods,id为ms_rtp_recv_set_session配置rtpsesson的recvsession。该id会调用接口receiver_set_session。在该接口中的操作同样类似于sender中的。

调用ms_filter_new创建dtmf的filter放到stream的dtmfgen上。

调用rtp_session_signal_connect接口注册回调函数,如果有电话音,telephone events调用on_dtmf_received,payload type changed调用payload_type_changed接口。

接着配置音频读取和播放卡设备。如果captcard不为空,则基于该描述符调用ms_snd_card_create_reader创建音频数据读取的filter。这是通过该描述符的create_reader函数完成的。如果为空,则调用ms_filter_new创建一个ms_file_player_id的filter,交给audio_stream的soundread。

另外,创建一个ms_resample_id的filter给audio_stream的read_resampler。

如果infile不为空,调用audio_stream_play基于stream播放该文件。

如果playcard不空,则调用ms_snd_card_create_writer基于该card创建soundwrite,及音频数据的发送filter。否则,则调用ms_filter_new创建ms_file_rec_id的filter,并在outfile参数不为空的情况下调用audio_stream_record记录接收的音频数据保存到outfile中。

到这里声卡设备的filter都已经创建了。接着创建编码和解码器的filter。首先调用rtp_profile_get_payload从profile和payload参数中取出payload type。接着调用ms_filter_create_encoder 和ms_filter_create_decoder接口基于payload type的mime_type创建解码器和编码器的filter,并保存到stream的encoder和decoder两个filter域上。实际上在这个接口中,都是根据mime_type查找desc_list全局链表,找到匹配的desc,再创建新的filter结构体实例并把desc放到filter上。

如果支持回身消除,则创建ms_speex_ec_id的filter放到streame上,调用ms_filter_set_sample_rate 对应的methods设置采样码率。如果stream上ec_tail_len,ec_delay,ec_framesize等不为空,就调用filter的对应的方法将这些配置值设置到filter上去。这些值可能是在初始化时或者协商媒体时得到的,当时只有转而配置到当前用的filter上才能具体的在本次通话过程中其作用。

如果stream的el_type不等于elinactive或者use_gc或者use_ng被置位了,则分别为volsend和vlorecv创建两个filter,这可能是用来远程传输音量信息的。如果el_type不等于ELInactive,则说明?调用filter的MS_VOLUME_SET_PEER对应的methods将volsend和volrecv联系起来。接着判断如果等于ELControlFull则调用filter methods配置volrecv的MS_VOLUME_ENABLE_NOISE_GA TE。如果use_ng被设置了,调用methods将volsend的filter的MS_VOLUME_ENABLE_NOISE_GA TE 使能。

如果use_agc被配置了,调用volsend该filter的MS_VOLUME_ENABLE_AGC对应的methods 使能agc

根据之前获得的payload type的clockrate信息调用soundread和soundwrite filter的MS_FIL TER_SET_SAMPLE_RA TE方法将这些配置信息设置到filter中。另外,如果read_resampler 和write_resampler如果为空,则调用ms_filter_new基于MS_RESAMPLE_ID创建两个filter。

基于MS_FIL TER_SET_NCHANNELS方法设置soundwirte该filter。

基于payload type中clockrate的配置来设置encoder编码器filter的采样率,通过MS_FIL TER_SET_SAMPLE_RA TE对应的methods。用normal_bitrate配置通过MS_FIL TER_SET_BITRA TE配置编码器filter的Bitrate。

基于clock_rate配置解码器filter的采样率,id为MS_FIL TER_SET_SAMPLE_RA TE的方法完成。

如果send_fmtp不为空,将其添加到编码器filter上,如果recv_fmtp不为空,将其添加到解码器filter上。

调用ms_filter_new基于MS_EQUALIZER_ID创建平衡filter,放到stream的equalizer上。并通过MS_EQUALIZER_SET_ACTIVE方法将eq_active设置到该filter上。

如果read_resampler和write_resampler不为空,调用audio_stream_configure_resampler用声卡filter 和RTPfilter来配置均衡filter。该接口将声卡或者RTP的采样率取出来,配置到resample中。对于read_resample,使用soundread配置from的rate,rtpsend配置to的rate,对于writer_resample,rtprecv 和soundwrite分别为from和to的rate。

下一步将所有的filter连接起来。通过调用ms_connection_helper_start和ms_connection_helper_link 两个接口完成。

对于sending graph,为:

Soundread-->read_resampler-->ec-->volsend-->encoder-->rtpsend

对于recv graph,为:

Rtprecv-->decoder-->dtmfgen-->equalizer-->volrecv-->ec-->write_resampler-->soundwrite

连接后的数据结构体间的关系如下图所示,以sending为例

除了filter连接在一起外,还需要一个发动机来推动数据在所有filter间流动,之前是建好了通路,现在需要一个泵来推数据在这个通路中流动。在代码中就是需要创建一个ticker。这时通过ms_ticker_new接口来完成的,挂载在stream上。通过该接口也同时创建了ticker需要的thread,并启动了该thread。接着调用ms_ticker_attach将soundread和rtprecv两个filter绑定到ticker上。在该接口中,我们首先遍历filter链上的所有filter,调用preprocess接口,即将所有预先要处理的操作通过preprocess接口来完成,这在构造filter时需要注意。接着将要绑定的filter连到ticker的execution_list 上。实际上后续ticker任务在跑动的时候,就是遍历execution_list链表,从各个graph的filter头开始将链上的各个filter的process接口调用执行一遍,这样反复循环,数据就从一个filter传输到下一个filter了。

至此audio_stream_start_full接口处理就完成了。

Ticker任务运行的函数为ms_ticker_run,在该接口中就是调用run_graphs接口将graph的通路走一遍的。在run_graphs中,遍历ticker的execution_list列表,对其上的filter头执行run_graph。在run_graph接口中,如果filter的last_ticks不等于ticker的ticks才执行filter的处理。该ticker值可以用来表示该filter是否是刚处理过的filter,避免接连的处理。如果需要处理,首先调用filter_can_process 查看该filter之前是否有一个已经在运行了。这一步是通过如下方式判断的:遍历filter的所有input 对应的msqueue,如果该queue的prev指针对应的filter的last_tick不等于ticker上的ticks,则返回失败,说明该filter之前的filter还在处理中,所以当前filter不可处理,也就是说该filter的所有input 还没有完全被其之前的filter填充完,还有input没有接收完其前面filter的数据,因此将其放到未调用filter链表上。否则返回成功,处理该filter。

此时将ticker的ticks赋值给filter的last_tick,后续判断二者相等即可表明该filter已经被处理了。接着调用call_process处理。在该接口中,首先判断如果该filter的desc指示的filter的ninputs为零,表明该filter只有一个输入,或者desc指示的标识flags为MS_FIL TER_IS_PUMP,则调用ms_filter_process直接处理。否则,检查filter的所有input端口对应的queue,如果有数据就调用ms_filter_process处理。至于将多个input端口的处理分开来,主要是考虑到在一次调用中需要将filter 的所有input处理完,因此如果有多个input,接口将多次调用ms_filter_process直到每个input都被处理了。

在ms_filter_process中,调用filter的desc的process接口函数指针进行处理。实际上每个filter

中文文本挖掘预处理流程总结

中文文本挖掘预处理流程总结 2017-09-22 12:14 编程派 0 0 阅读 15 作者:刘建平 来源:https://www.360docs.net/doc/385252850.html,/pinard/p/6744056.html 在对文本做数据分析时,我们一大半的时间都会花在文本预处理上,而中文和英文的预处理流程稍有不同,本文就对中文文本挖掘的预处理流程做一个总结。 1. 中文文本挖掘预处理特点 首先我们看看中文文本挖掘预处理和英文文本挖掘预处理相比的一些特殊点。 首先,中文文本是没有像英文的单词空格那样隔开的,因此不能直接像英文一样可以直接用最简单的空格和标点符号完成分词。所以一般我们需要用分词算法来完成分词,在文本挖掘的分词原理中,我们已经讲到了中文的分词原理,这里就不多说。 第二,中文的编码不是utf8,而是unicode。这样会导致在分词的时候,和英文相比,我们要处理编码的问题。 这两点构成了中文分词相比英文分词的一些不同点,后面我们也会重点讲述这部分的处理。当然,英文分词也有自己的烦恼,这个我们在以后再讲。了解了中文预处理的一些特点后,我们就言归正传,通过实践总结下中文文本挖掘预处理流程。 2. 中文文本挖掘预处理一:数据收集 在文本挖掘之前,我们需要得到文本数据,文本数据的获取方法一般有两种:使用别人做好的语料库和自己用爬虫去在网上去爬自己的语料数据。 对于第一种方法,常用的文本语料库在网上有很多,如果大家只是学习,则可以直接下载下来使用,但如果是某些特殊主题的语料库,比如"机器学习"相关的语料库,则这种方法行不通,需要我们自己用第

对于第二种使用爬虫的方法,开源工具有很多,通用的爬虫我一般使用beautifulsoup。但是我们我们需要某些特殊的语料数据,比如上面提到的"机器学习"相关的语料库,则需要用主题爬虫(也叫聚焦爬虫)来完成。这个我一般使用ache。 ache允许我们用关键字或者一个分类算法来过滤出我们需要的主题语料,比较强大。 3. 中文文本挖掘预处理二:除去数据中非文本部分 这一步主要是针对我们用爬虫收集的语料数据,由于爬下来的内容中有很多html的一些标签,需要去掉。少量的非文本内容的可以直接用Python的正则表达式(re)删除, 复杂的则可以用beautifulsoup来去除。去除掉这些非文本的内容后,我们就可以进行真正的文本预处理了。 4. 中文文本挖掘预处理三:处理中文编码问题 由于Python2不支持unicode的处理,因此我们使用Python2做中文文本预处理时需要遵循的原则是,存储数据都用utf8,读出来进行中文相关处理时,使用GBK之类的中文编码,在下面一节的分词时,我们再用例子说明这个问题。 5. 中文文本挖掘预处理四:中文分词 常用的中文分词软件有很多,个人比较推荐结巴分词。安装也很简单,比如基于Python的,用"pip install jieba"就可以完成。下面我们就用例子来看看如何中文分词。 首先我们准备了两段文本,这两段文本在两个文件中。两段文本的内容分别是nlp test0.txt和 nlp test2.txt: 1. 沙瑞金赞叹易学习的胸怀,是金山的百姓有福,可是这件事对李达康的触动很大。易学习又回忆起他们三人分开的前一晚,大家一起喝酒话别,易 学习被降职到道口县当县长,王大路下海经商,李达康连连赔礼道歉,觉得对不起大家,他最对不起的是王大路,就和易学习一起给王大路凑了5万块钱,王大路自己东挪西撮了5万块,开始下海经商。没想到后来王大路竟然做得风生水起。沙瑞金觉得他们三人,在困难时期还能以沫相助,很不容易。 沙瑞金向毛娅打听他们家在京州的别墅,毛娅笑着说,王大路事业有成之后,要给欧阳菁和她公司的股权,她们没有要,王大路就在京州帝豪园买了三套别墅,可是李达康和易学习都不要,这些房子都在王

文本素材处理

第2章文本素材处理 学习指南:本章介绍文本素材采集、编辑、加工处理的有关知识。主要内容有:文本素材的基础知识,文本素材的采集与处理方法,文本素材创作实例。学习本章,要求掌握以下知识: 掌握文本在计算机中的表示方法,了解文本素材的主要特点; 熟悉常见的文本文件的格式,并能正确地选择文本文件的存储格式; 了解常用的文本素材采集方式,熟悉扫描仪+OCR文字识别输入方法; 了解常用的文字处理软件,掌握Word文字处理的方法; 会用相关的文字处理软件制作多媒体作品中需要的文本素材。 在多媒体作品中,文本是最基本也是最常用的素材。一些说明、介绍、作品中的文字资料都会用到文本,作为多媒体系统的组成元素,它和其它素材同样重要。文本素材处理包含文本的采集、录入、编辑等加工处理,本章将介绍文本素材处理的相关知识。 2.1 文本素材概述 文本是人们早已熟知的信息表示方式,如一篇文章、一段程序、一个文件都可用文本描述。它通常以字、句子、段落、节、章为单位,记录自然现象、表述思想感情、传达某种信息。人们在阅读时,通常是一字一句、一行一页顺序地浏览。 文本是文字、字母、数字和各种功能符号的集合。在现实生活中,人们对事情的讲述、逻辑的推理、数学公式的表述等都主要用文字和数字来准确的表达。在多媒体应用系统中,虽然有图形、声音、视频影像等多种媒体形式,但是对于一些复杂而抽象的事件,文本表达却有它不可替代的独到之处。 2.1.2 文本素材基础知识 在多媒体应用系统中,文本作为重要的基本素材而被广泛应用,它具有信息表达清楚、计算机处理方便、存储容易、传输快捷等优势。具体来说: (1)编码形式简单 在计算机中,西文字符最常用的编码是ASCII码,即American Standard Code For Information Interchange(美国信息交换标准代码)。它用7位二进制数进行编码,可以表示27即128个字符,其中包括数字字符0~9、大小写英文字符、运算符号、标点符号、标识符号和一些控制符号。这些字符种类大致能够满足各种计算机语言、西方文字、常见命令的需要。一个ASCII码字符在内存中占一个字节。 汉字字符在计算机中也是以编码形式处理的,汉字输入用输入编码,汉字存储用机内码,汉字输出用字型码。在计算机中存储时,一个汉字占2个字节。 (2)易于获取,存储、处理和传输容易 多媒体计算机系统中,文本资料可以用多种方式获取,可采用多种输入编码录入,还

如何做好文字校对工作

为保证校对工作的有序进行,减少出版印刷错误,提高校对质量,应从以下几个方面把握:―、明确校对目的 校对是保证学报质量的重要环节,是对编辑工作的继续和补充。校对必须高度负责,认 真细致,树立严谨周密,一丝不苟的作风。 1.根据原稿,核对并清除校样上的差错。 2?改正在政治思想上和科学性上遗留的不准确的提法和词句。 3?清除语法修辞上遗留的差错和毛病。 4 ?清除错别字。 5.解决和消除任何疑点。 二、把握校对标准 1 ?编辑负责校对、印刷工作的组织和实施,及时送取稿件和校样,做好与印刷厂的业 务联系。 2 ?校对以原稿为准,不得在校样上随意增补、删减,发现原稿错误及编辑处理的疏漏 和失误做出标示,由编辑对原稿、校样予以处理。若作者提出修改时,要尽量坚持不动版面、不动字数的原则,减少改版的麻烦。 3 ?准确使用校对符号,消灭错字,补齐遗漏,纠正版式错误,严格执行三校加点校制 度,保证期刊质量。 4-校对以对校、折校为主,根据实际情况,部分稿件由作者校对一次,校后由编辑对 格式、质量复校一次。 5 ?校对时要注意版面的规范、美观,排版的合理。校对差错率要保持在万分之二以下。

三、遵循校对的程序,交叉三校制 1.—校(作者、责任编辑各校一次):侧重对原稿校对,力求校样与原稿的一致,纠 正版式错误,对有疑问处作出标示。校后通读一遍。要求作者不能对原稿作大的改动。 2 ?二校(责任编辑、执行编辑各校一次):校对时要确定一校校出错误已改正,纠正版式错误,并对文稿中的疑问予以处理,填补遗缺,统一体例。 3 ?三校(执行编辑校一次):校对时要确定二校校出错误已改正,对校样进行综合检查,清理差错,确定版面格式。 4 .点校:对三校校出错误予以核对,并对文章、版式作最后通校,确保清样无差错。 5 .校对签名。校对者应在每次校样上签名,并标明校次,以防差错。 6 ?责任编辑甩开原稿和三校样,对清样进行阅读,寻找差错。在读样后,进行总体扫描,检查有无错字、漏字、表格与插图是否合乎规范,字体、字号使用是否正确等。 四、明确校对内容 1 ?检查多、漏、错文字及标点、符号错误;核对标题、署名,文中人名、地名、数字、公式。 2 ?检查版面、格式,图表位置及表题、图题,字体、字号、字距和行距。 3 .检查标题位置、层次及转行,注释、参考文献及序号,核对中英文目录及页码与文章的一致。 4 ?检查目录及对应的页码、刊眉、页码顺序、版权,封面、封二、封三、封底的文字、刊号、年月期号。

中文文本预处理

1中文文本预处理 1.1分词软件调用(中科院分词系统) 1.1.1软件下载:https://www.360docs.net/doc/385252850.html,/ 1.1.2软件包目录&介绍 | Readme.txt-------------------------->介绍 | +---bin | +---DocExtractor----------->文档篇章语义抽取系统 | | DocExtractor.bat-->批处理,可以针对指定的文件夹进行语义抽取 | | DocExtractor.dll-->支撑的动态链接库,基于分词基础上 | | DocExtractorSample.exe-->应用程序 | | | \---ICTCLAS2015----------->分词系统 | ICTCLAS-tools.exe-->分词的支撑工具,可用于测试,本处主要用来做用户词典导入 | importuserdict.bat-->可将用户词典自动导入到系统内 | NLPIR.dll-->Win32下的支撑动态链接库,其他环境的库,可以访问lib对应环境的库文件 | NLPIR.lib | NLPIR_WinDemo.exe-->Win32下的演示程序,在Win8 32位下编译而成,部分环境可能不支持,或者显示异常 | userdic.txt-->用户词典,用户可以自行编辑 | +---Data-->系统核心词库 | \---English-->英文处理的支持知识库,如果不需要英文处理的功能,可以不加载本库。 | +---doc-->相关文档支持 | ICTPOS3.0.doc-->我们的词性标注集说明 | NLPIR-ICTCLAS2015分词系统开发手册.pdf-->开发使用手册 | +---include-->系统头文件 | NLPIR.h | +---lib-->不同环境下的支撑库,每一种库,同时支持C/C++/C#/Java库。其他小众化的环境支持,请联系我们 | +---linux32-->Linux 32bit操作系统下的支持库 | | libNLPIR.so | | | +---linux64-->Linux 64bit操作系统下的支持库 | | libNLPIR.so | | Readme.txt | |

第三章 中文文字处理软件Word

第三章中文文字处理软件Word 2000 一、判断题 1.在Word中,必须先选定操作的内容,然后才能对选定的对象进行操作。( ) 2.Word文档中的工具栏可由用户根据需要显示或隐藏。( ) 3.在“打印预览”窗口中,通过浏览文档可以观察文章段落在页面上的整体布局,但不能对其进行编辑。( ) 4.在Word文档中,通常先选定操作对象,再右击它可弹出快捷菜单。( ) 5.把选定的文本删除掉,可以按Delete键。( ) 6.剪切板上的内容可粘贴到文挡中的多个位置。( ) 7.Word是一种所见即所得的文字处理软件。( ) 8.保存一个新建的Word文档时,默认的文档扩展名是doc。( ) 9.Word 2000软件既可以用于文字处理,也可以进行表格处理,因而又称为电子表格软件。() 10.Word只用于文字处理,在文字中无法插入图形或表格。( ) 11.用Word进行文字编辑有多种方法,其中包括使用剪贴板。( ) 12.Word的视图工具栏总是出现在文档编辑区的左下角,不能任意移动它的位置。( ) 13.在编辑一个旧文档的过程中单击“保存”按钮,会弹出“保

存”对话框,设置文件的位置、文件名和扩展名。( ) 14.在使用Word的“查找”功能查找文档中的字串时,可以使用通配符。( ) 15.在Word的替换对话框中,可以同时替换所有找到的字串。( ) 16.设置字符的字号时,当要设置的字号列表中没有时,可以在“字号”组合框中输入字号数字。( ) 17.在Word的字符格式化中,可以把选定的文本设置成上标或下标的效果。( ) 18.新建一个Word文档可以从“文件”菜单中选择“新建”,也可以点击“常用”工具栏上的“新建”按钮。( ) 19.如果所选定的文本中包含了英文字体,而且设置字体格式时都设置为中文字体,则文本中的英文字符将显示不出来。( ) 20.文档的页面设置一般不是只指当前页面,而是指整个文档的所有页面。( ) 21.在页面上插入页码,可以放在页面的页眉位置或页脚位置。( ) 22.在Word页面设置中可以设置装订线的位置。( ) 23.在Word中不但可以编辑文字,还可以插入图形,编辑表格,直到打印出文稿。( ) 24.段落缩进的距离是从打印纸的纸边到文字的距离。( ) 25.段落的首行缩进就是指段落的第一行向里缩进一定的距离。

中文信息处理

中文信息处理技术浅谈 摘要:随着科学技术的发展,中文信息处理已经深入到了社会生活的各方面。广泛的应用对中文信息处理技术也提出了较高的要求。本文从主流技术、新技术展望等,对中文信息处理技术进行了初步探索。 关键词:中文信息处理N元模型语音识别词性标注 中文信息处理是中文(包括汉语和少数民族语言)语言学和信息技术的融合,它是一门用计算机对汉语(包括口语和书面语)进行转换、传输、存贮、分析等加工的科学。中文信息处理与语言学、计算机科学、心理学、数学、控制论、信息论、声学、自动化技术等多种学科相联系,是自然语言信息处理的一个分支,需要以大量的语言知识、背景知识为依据,对中文信息的人脑处理过程进行模拟。其中,“中文”是指中国通用的所有语言种类,包括汉语及其他少数民族的语言:但一般都是指汉语。“信息”是指能通过视觉、听觉、嗅觉、味觉、触觉等器官或仪器获取,并有一定交际功能的东西,“信息”是不确定性的减少,是负熵。所谓“处理”,是指用计算机对信息进行各种加工,主要的是图像信息和语言信息的识别、模拟、分析、转换和传输。 一、中文信息处理的特点及难点 中文信息处理在许多方面有自己的特点。 1、汉字的特殊性 西方语言只有几十个字母。而汉字由于数量大且字形复杂,也给计算机处理带来了困难。汉字信息处理是中文信息处理的关键和基础,包括汉字信息的输入、汉字信息的加工和汉字信息的输出等方面,其难点是汉字编码问题。根据在汉字信息处理过程中的不同要求,汉字有多种编码,主要可以分为四类,即汉字输入编码,汉字标准编码,汉字内码和汉字形码。 2、书面汉语的特殊性 书面汉语中,词跟记号之间没有分隔标记,自动分词成为书面汉语分析的第一道难关。分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段可以通过明显的分界符来简单划界,唯独词没有一个形式上的分界符,虽然英文也同样存在短语的划分问题,但是在词这一层上,中文比之英文要复杂的多、困难的多。 3、汉语语音的特殊性 汉语语音的特点是音节结构简单,音节界限分明,但有声调和变调等问题,对于语音识别和语音合成来说,既有有利的一面, 也有不利的一面。 4、汉语语法的特殊性 汉语形态贫乏,难以凭借形态来确定词的句法功能,词序和虚词是主要的语法手段,句法歧义特别复杂,使得汉语语句自动分析这一关键技术迟迟不能取得

文本素材处理

文本素材处理 学习指南:本章介绍文本素材采集、编辑、加工处理的有关知识。主要内容有:文本素材的基础知识,文本素材的采集与处理方法,文本素材创作实例。学习本章,要求掌握以下知识: 掌握文本在计算机中的表示方法,了解文本素材的主要特点; 熟悉常见的文本文件的格式,并能正确地选择文本文件的存储格式; 了解常用的文本素材采集方式,熟悉扫描仪+OCR文字识别输入方法; 了解常用的文字处理软件,掌握Word文字处理的方法; 会用相关的文字处理软件制作多媒体作品中需要的文本素材。 在多媒体作品中,文本是最基本也是最常用的素材。一些说明、介绍、作品中的文字资料都会用到文本,作为多媒体系统的组成元素,它和其它素材同样重要。文本素材处理包含文本的采集、录入、编辑等加工处理,本章将介绍文本素材处理的相关知识。 2.1 文本素材概述 文本是人们早已熟知的信息表示方式,如一篇文章、一段程序、一个文件都可用文本描述。它通常以字、句子、段落、节、章为单位,记录自然现象、表述思想感情、传达某种信息。人们在阅读时,通常是一字一句、一行一页顺序地浏览。 文本是文字、字母、数字和各种功能符号的集合。在现实生活中,人们对事情的讲述、逻辑的推理、数学公式的表述等都主要用文字和数字来准确的表达。在多媒体应用系统中,虽然有图形、声音、视频影像等多种媒体形式,但是对于一些复杂而抽象的事件,文本表达却有它不可替代的独到之处。 2.1.2 文本素材基础知识 在多媒体应用系统中,文本作为重要的基本素材而被广泛应用,它具有信息表达清楚、计算机处理方便、存储容易、传输快捷等优势。具体来说: (1)编码形式简单 在计算机中,西文字符最常用的编码是ASCII码,即American Standard Code For Information Interchange(美国信息交换标准代码)。它用7位二进制数进行编码,可以表示27即128个字符,其中包括数字字符0~9、大小写英文字符、运算符号、标点符号、标识符号和一些控制符号。这些字符种类大致能够满足各种计算机语言、西方文字、常见命令的需要。一个ASCII码字符在内存中占一个字节。 汉字字符在计算机中也是以编码形式处理的,汉字输入用输入编码,汉字存储用机内码,汉字输出用字型码。在计算机中存储时,一个汉字占2个字节。 (2)易于获取,存储、处理和传输容易 多媒体计算机系统中,文本资料可以用多种方式获取,可采用多种输入编码录入,还

相关文档
最新文档