2009年4月16日星期四

MPEG2 PS和TS流格式

MPEG2 PS和TS流格式


    应该说真正了解TS,还是看了朋友推荐的《数字电视业务信息及其编码》一书之后,MPEG2 TS和数字电视是紧密不可分割的,值得总结一下其中的一些关系。



    ISO/IEC-13818-1:
系统部分;ISO/IEC-13818-2:视频;ISO/IEC-13818-3:音频;ISO/IEC-13818-4:一致性测试;ISO
/IEC-13818-5:软件部分;ISO/IEC-13818-6:数字存储媒体命令与控制;ISO/IEC-13818-7:高级音频编码;ISO
/IEC-13818-8:系统解码实时接口;



    MPEG2系统任务包括:1. 规定以包传输数据的协议;2. 规定收发两端数据流同步的协议;3. 提供多个数据流的复用和解复用协议;3. 提供数据流加密的协议。以包形式存储和传送数据流是MPEG2系统之要点。



    ES是直接从编码器出来的数据流,可以是编码过的视频数据流,音频数据流,或其他编码数据流的统称。ES流经过PES打包器之后,被转换成PES包。PES包由包头和payload组成,具体格式摘录如下:





   

    可以看到PTS/DTS是打在PES包里面的,这两个parameters是解决视音频同步显示,防止解码器输入缓存上溢或下溢的关键。PTS表示显示单元
出现在系统目标解码器(STD: system target
decoder)的时间,DTS表示将存取单元全部字节从STD的ES解码缓存器移走的时刻。每个IPB帧的包头都有一个PTSDTS,但PTSDTSB帧都是一样的,无须标出B帧的DTS。对I帧和P帧,显示前一定要存储于视频解码器的重新排序缓存器中,经过延迟(重新排序)后再显示,一定要分别标明PTSDTS


    


    上节介绍过,ES首先需打包成PES流包,然后PES根据需要打包成PS或TS包进行存储或传输。其每路ES只包含一路信源的编码数据流,所以每路PES也只包含相对应信源的数据流。



    对
PS流而言,每个PES包头含有PTS和DTS,流识别码,用于区别不同性质ES。然后通过PS复用器将PES包复用成PS包。实际上是将PES包分解为
更细小的PS包。在解码的时候,解复用器将PS分解成一个个PES包,拆包器然后将PES包拆成视频和音频的ES,最后输入至各自解码器进行解码。一个问
题是:各个ES在解码时,如何保证视音频的同步呢?除了PTS和DTS的配合工作外,还有一个重要的参数是SCR(system clock
reference)。在编码的时候,PTS,DTS和SCR都是由STC(system time
clock)生成的,在解码时,STC会再生,并通过锁相环路(PLL-phase lock
loop),用本地SCR相位与输入的瞬时SCR相位锁相比较,以确定解码过程是否同步,若不同步,则用这个瞬时SCR调整27MHz的本地时钟频率。最
后,PTS,DTS和SCR一起配合,解决视音频同步播放的问题。PS格式摘录如下:





    PS包的长度比较长且可变,主要用于无误码环境里,因为越长的话,同步越困难,且在丢包的情况下,重组也越困难。所以,PS适合于节目信息的编辑和本地内容应用的application。


    

    TS流也是由一个或多个PES组合而来的,他们可以具有相同的时间基准,也可以不同。其基本的复用思想是,对具有相同时间基准的多个PES现进行节目复用,然后再对相互有独立时间基准的各个PS进行传输复用,最终产生出TS。

 

    TS包由包头和包数据2部分组成,其中包头还可以包括扩展的自适用区。包头长度占4bytes,自使用区和包数据共占184bytes,整个TS包长度相当于4个ATM包长。TS包的包头由如下图摘录所示的同步字节、传输误码指示符、有效载荷单元起始指示符、传输优先、包识别(PID-Packet Identification)、传输加扰控制、自适应区控制和连续计数器8个部分组成。



 

    其中,可用同步字节位串的自动相关特性,检测数据流中的包限制,建立包同步;传输误码指示符,是指有不能消除误码时,采用误码校正解码器可表示1bit 的误码,但无法校正;有效载荷单元起始指示符,表示该数据包是否存在确定的起始信息;传输优先,是给TS包分配优先权;PID值是由用户确定的,解码器根据PIDTS上从不同ES来的TS包区别出来,以重建原来的ES;传输加扰控制,可指示数据包内容是否加扰,但包头和自适应区永远不加扰;自适应区控制,用2 bit表示有否自适应区,即(01)表示有有用信息无自适应区,(10)表示无有用信息有自适应区,(11)表示有有用信息有自适应区,(00)无定义;连续计数器可对PID包传送顺序计数,据计数器读数,接收端可判断是否有包丢失及包传送顺序错误。显然,包头对TS包具有同步、识别、检错及加密功能。



    TS包自适应区由自适应区长、各种标志指示符、与插入标志有关的信息和填充数据4部分组成。其中标志部分由间断指示符、随机存取指示符、ES优化指示符、PCR标志、接点标志、传输专用数据标志、原始PCR标志、自适应区扩展标志8个部分组成。重要的是标志部分的PCR字段,可给编解码器的27MHz时钟提供同步资料,进行同步。其过程是,通过PLL,用解码时本地用PCR相位与输入的瞬时PCR相位锁相比较,确定解码过程是否同步,若不同步,则用这个瞬时PCR调整时钟频率。因为,数字图像采用了复杂而不同的压缩编码算法,造成每幅图像的数据各不相同,使直接从压缩编码图像数据的开始部分获取时钟信息成为不可能。为此,选择了某些(而非全部)TS包的自适应区来传送定时信息。于是,被选中的TS包的自适应区,可用于测定包信息的控制bit和重要的控制信息。自适应区无须伴随每个包都发送,发送多少主要由选中的TS包的传输专用时标参数决定。标志中的随机存取指示符和接点标志,在节目变动时,为随机进入I帧压缩的数据流提供随机进入点,也为插入当地节目提供方便。自适应区中的填充数据是由于PES包长不可能正好转为TS包的整数倍,最后的TS包保留一小部分有用容量,通过填充字节加以填补,这样可以防止缓存器下溢,保持总码率恒定不变。




    前面3节总结了MPEG2
TS的基本格式,其中包括PES,PS和TS,以及相关字段的介绍。那么作为一种传输流,TS将内容进行打包/复用,让其媒体内容变成TS传输,并最终在
解码端解码。简单来看,TS是一个传输层的协议栈,它可以承载各种内容的传输,比如MPEG,WMV,H264,甚至是IP,那么其中的传输规范是如何定
义的呢?这个即是PSI(节目特定信息)要做的事情。



    PSI
由四张表构成:PAT,PMT,CAT和NIT,这四张表分别描述了一个TS所包括的所有ES流的传输结构。首先的一个概念是,TS是以包形式传播,在编
解码端都需要以一定的包ID来标识TS流里承载的内容,比如,PAT表会存在于一个或多个TS包里,所以要用一个特别的包ID来表示,另外,不同的ES流
也需要不同的包ID来标识。我们有了PAT和PMT这两种表,解码器就可以根据PID,将TS上从不同ES来的TS包区分出来进行解码。



    TS的解码分两步进行,其一,是从PID为0
的TS包里,解析出PAT表,然后从PAT表里找到各个节目源的PID,一般此类节目源都由若干个ES流组成,并描述在PMT表里面,然后通过节目源的
PID,就可以在PMT表里检索到各个ES的PID。其二,解码器根据PMT表里的ES流的PID,将TS流上的包进行区分,并按不同的ES流进行解码。
所以,TS是经过节目复用和传输复用两层完成的,即在节目复用时,加入了PMT,在传输复用时,加入了PAT。同样在节目解复用时,可以得到PMT,在传
输解复用时,可以得到PAT。下图很好地概述了其思想。





    TS是支持多路复用的,所以它可用来传输经复用后的多层节目。在复用过程中,要注意的是,解码过程中所需要面对的时间参考和同步问题,因为解复用是需要各种信息同步进行的,所以在复用过程中,就需要插入相关的时间信息:PTS,DTS,PCR。


    在TS形成过程中,PTS和DTS是在ES打包成PES时,根据STC的参考,将其时钟信息注入PES包中的,而之后在PES切成TS时,再将PID和
PCR信息注入到TS包中,当多路TS再进行复用的时候,各路TS的PCR将会被提取出来,再进行分析,然后再根据统一的STC参考,将新的PCR生成并
注入到TS中去,最后,因为原来PAT表信息不在适用,所以新的PAT表需要再生成,并附加到新的TS流中去。经过这多层的复用之后,新的TS流即可以进
入调制,传输阶段。过程可参见下图:





    解码过程要面对的问题是:解复用,视音频的同步,解码缓存器无上下溢。解复用即是将TS在同一信道里不同时序进行传输的节目分离出来;视音频同步由DTS,
PTS和PCR三者协调完成,并且PCR是重建系统时间基准的绝对时标,而DTS和PTS是解码和重现时刻的相对时标;对解码缓存器无上下溢的问题,必须
借助于系统目标解码器(STD)模型来对其进行实现,基本思想如下:



  1. TS流进入解码器后,首先由换向器,按照一定的时序关系,将各种ES流分解出来(其中也包括PSI信息流)。
  2. 分解过后的ES流会进入各自的传输缓存器,通过之后,其PES流进入各自的主存储器,注意的是:PSI信息流会进入系统缓存器,最后也到达主存储器。
  3. 最后,解码器根据DTS信息,从各个主存储器分别提取媒体或系统信息,进行解码,并根据PTS信息,将媒体内容进行显示处理。


其过程可参见下图:









MPEG-2 学习笔记

  最近有点时间,看了一部分MPEG-2 的规范,看后想总结点东西,算是做了点作业,另外希望能和大家讨论讨论,请大家指点。


中文版很多概念翻译得很模糊,不易理解,但总体来说还算是不错,适合像我这种入门级别的看,不过建议和英文版对照看,对一些概念能比较准确的理解。整个规范包括三部分:系统,视频编码,音频编码。对应的标准号分别为ISO/IEC 13818-1ISO/IEC 13818-2ISO/IEC 13818-3,在规范中经常可以看到这几个字符串。


第一部分“系统”和我们现在的工作关系较紧密,我也主要学习了第一部分。后面两部分主要是讲解编码过程,编码部分看了实在让人犯晕,先偷一下懒吧,把第一部分搞清楚了再看去啃难啃的骨头吧。


下面进入正题了。


 


一、概念


规范中讲述的概念很多,容易让人糊涂,所以先把一些概念理清,弄清楚它们之间的关系,再看后面的就可提高很多的效率。


(1)ES- Elementary Streams (原始流),对视频、音频信号及其他数据进行编码压缩后


的数据流称为原始流。原始流包括访问单元,比如视频原始流的访问单元就是一副图像的编


码数据。


(2) PES- Packetized Elementary Streams (分组的原始流),原始流形成的分组称为PES分组,是用来传递原始流的一种数据结构


(3)节目是节目元素的集合。节目元素可能是原始流,这些原始流有共同的时间基点,用来做同步显示。


(4)传输流和节目流


TS-Transport Stream 翻译为传输流


PS-Program Stream  翻译为节目流


PS用来传输和保存一道节目的编码数据或其他数据。PS的组成单位是PES分组。


TS用来传输和保存多道节目的编码数据或其他数据,TS的组成单位是节目。


PS适用于不容易发生错误的环境,以及涉及到软件处理的应用,典型应用如DVD光盘的文件存储


TS适用于容易发生错误的环境,典型应用就是数字电视信号的传输。


TSPS是可以互相转换的,比如从TS中抽取一道节目的内容并产生有效的PS是可能。


 (5)传输流分组和PES分组


原始流分成很多PES分组,保持串行顺序,一个PES分组只包含一个原始流的编码数据。PES分组长度很大,最大可为64K字节。


PES分组分为分组首部(header)”有效负载(payload)”有效负载指跟随在首部字节之后的字节。首部的前4个字节构成分组的起始码,标识了该分组所属原始流的类型和ID号。


 


TS分组也就是传输流数据形成的数据包。每个TS分组长度为188字节,包括分组首部有效负载,前4个字节是分组首部,包含了这个分组的一些信息。有些情况下需要更多的信息时,需在后面添加调整字段(adaption field)”


 





两者之间的关系:

    PES分组是插入到TS分组中的,每个PES分组首部的第一字节就是TS分组有效负载的第一字节。一个PID值的TS分组只带有来自一个原始流的数据。




 


(5)PSI


    全称Program Specific Information,意为节目专用信息。传输流中是多路节目复用的,那么,怎么知道这些节目在传输流中的位置,区分属于不同节目呢?所以就还需要一些附加信息,这就是PSIPSI也是插入到TS分组中的,它们的PID是特定值。


         MPEG-2中规定了4PSI,包括PAT(节目关联表)CAT(条件访问表)PMT(节目映射表)


NIT(网络信息表),这些PSI包含了进行多路解调和显示节目的必要的和足够的信


息。                            


         具体的应用中可能包括更多的信息,比如DVB-T中定义了SDT(服务描述表),EIT(环境信息表),BAT(节目组相关表),TDT(时间日期表)等,统称为DVB-SI(服务信息)


    



l    PSIPID是特定的,含PSI的数据包必须周期性的出现在传输流中。


PMT (Program Map Table )节目映射表


PMT所在分组的PIDPAT指定,所以要先解出PAT,再解PMT


PMT中包含了属于同一节目的视频、音频和数据原始流的PID


找到了PMT,解多路复用器就可找到一道节目对应的每个原始流的PID,再根据原始流


PID,去获取原始流。如下图:PID1和PID2分别对应某道节目的视频原始流和音频原始流


的PID。



 



l      PAT (Program Association  Table )节目关联表

l      PAT所在分组的PID=0


      PAT中列出了传输流中存在的节目流

l

l      PAT指定了传输流中每个节目对应PMT所在分组的PID

l      PAT的第一条数据指定了NIT所在分组的PID ,其他数据指定了PMT所在分组的PID,如下图所示:

 



 

 



lCAT (Conditional Access Table )条件访问表

lCAT所在分组的PID=1

lCAT中列出了条件控制信息(ECM)和条件管理信息(EMM)所在分组的PID

lCAT用于节目的加密和解密

 

lNIT( Network Information Table)网络信息表

lNIT所在分组的PIDPAT指定

lNIT提供一组传输流的相关信息,以及于网络自身特性相关的信息,比如网络名称传输参数(如频率,调制方式等)

lNIT一般是解码器内部使用的数据当然也可以做为EPG的一个显示数据提供给用户做为参考

 

 

几种PSI之间的关系,如下图所示:首先PAT中指定了传输流中所存在的节目,及每个节目对应的PMT的PID号。 比如Program 1对应的PMT 的PID=22,然后找到PID=22的TS分组,解出PMT,得到这个节目中包含的原始流的PID,再根据原始流的PID去找相应的TS分组,获取原始流的数据,然后就可以送入解码器解码了。




二、数据结构


1TS分组


前面提到,TS分组由188个字节构成,其结构如下: 

 






transport_packet()

{  
 sync_byte                                                                    // 8 
 transport_error_indicator                                          //1 
 payload_unit_start_indicator                                    //1 
 transport_priority                                                       // 1  

PID                                                                             //13 
 transport_scrambling_control                                  // 2
 adaptation_field_control                                            //2 
 continuity_counter                                                      //4 
 if(adaptation_field_control=='10'  || adaptation_field_control=='11'){  
  adaptation_field()  
 }  
 if(adaptation_field_control=='01' || adaptation_field_control=='11') {  
  for (i=0;i<N;i++){  
   data_byte                                                                   //8 
  }  
 }  
}
 

 

前面32bit的数据即TS分组首部,它指出了这个分组的属性。

sync_byte  同步字节,固定为0x47 ,表示后面的是一个TS分组,当然,后面包中的数据是不会出现0x47


transport_error_indicator 传输错误标志位,一般传输错误的话就不会处理这个包了


payload_unit_start_indicator 这个位功能有点复杂,字面意思是有效负载的开始标志,根据后面有效负载的内容不同功能也不同,后面用到的时候再说。


transport_priority  传输优先级位,1表示高优先级,传输机制可能用到,解码好像用不着。


PID  这个比较重要,指出了这个包的有效负载数据的类型,告诉我们这个包传输的是什么内容。前面已经叙述过。


       transport_scrambling_control 加密标志位,表示TS分组有效负载的加密模式。TS分组首部(也就是前面这32bit)是不应被加密的,00表示未加密。


        adaption_field_control  翻译为“调整字段控制”,表示TS分组首部后面是否跟随有调整字段和有效负载。01仅含有效负载,10仅含调整字段,11含有调整字段和有效负载。为00的话解码器不进行处理。空分组没有调整字段。


       continuity_counter   一个4bit的计数器,范围0-15,具有相同的PIDTS分组传输时每次加1,到15后清0。不过,有些情况下是不计数的。如下:(1)TS分组无有效负载(2)复制的TS分组和原分组这个值一样(3)后面讲到的一个标志discontinuity_indicator1

      adaptation_field()        调整字段的处理

      data_byte                      有效负载的剩余部分,可能为PES分组,PSI,或一些自定义的数

                                            据。

(2)PAT


   PAT数据结构如下:






program_association_section() { 
 table_id                                                                    // 8
 section_syntax_indicator                                      //1
 '0'                                                                           //1
 reserved                                                              // 2
 section_length                                                    //12
 transport_stream_id                                         // 16
 reserved                                                           // 2
 version_number                                                 // 5
 current_next_indicator                                     //1
 section_number                                                //8
 last_section_number                                         // 8
 for (i=0; i<N;i++) { 
  program_number                                              // 16
  reserved                                                          // 3
  if(program_number == '0') { 
   network_PID                                                  //  13
  } 
  else { 
   program_map_PID                                           // 13
  } 
 } 
 CRC_32                                                            //  32

table_id  固定为0x00 ,标志是该表是PAT


section_syntax_indicator 段语法标志位,固定为1


section_length         表示这个字节后面有用的字节数,包括CRC32。假如后面的字节加上前面的字节数少于188,后面会用0XFF填充。假如这个数值比较大,则PAT会分成几部分来传输。


transport_stream_id    该传输流的ID,区别于一个网络中其它多路复用的流。


 


version_number       范围0-31,表示PAT的版本号,标注当前节目的版本.这是个非常有用的参数,当检测到这个字段改变时,说明TS流中的节目已经变化了,程序必须重新搜索节目.


current_next_indicator 表示发送的PAT是当前有效还是下一个PAT有效。


       section_number       分段的号码。PAT可能分为多段传输,第一段为00,以后每个分段


1,最多可能有256个分段


       last_section_number   最后一个分段的号码


       program_number  节目号


 


       network_PID   网络信息表(NIT)的PID,网络信息表提供了该物理网络的一些信息,和电视台相关的。节目号为0时对应的PIDnetwork_PID


program_map_PID  节目映射表的PID,节目号大于0时对应的PID,每个节目对应一个


CRC_32   CRC32校验码


上面program_numbernetwork_PIDprogram_map_PID 是循环出现的。program_number等于0时对应network_PIDprogram_number等于其它值时对应program_map_PID


举个例子,下述流为带PATTS分组:


47 40 00 1c 00 00 b0 15 13 f6 e7 00 00 00 00 e0 10 00 01 e0 20 00 02 e0 21 1a 34 b4 77 ff…………..ff


其中红色的四个字节是TS分组头部,用数据结构解出首部,得到PID=0x00,表示为该分组的有效负载是PAT。蓝色的00称为“指针域”----Pointer field,表示了一个偏移量,即从后面第几个字节开始是PAT部分。为00表示后面紧接着的就是PAT00 b0 15 13 f6 e7 00 00 00 00 e0 10 00 01 e0 20 00 02 e0 21 1a 34 b4 77


再利用PAT的数据结构解出PAT,得到如下信息:


---------------PAT Information-------------


table_id: 00


section_syntax_indicator: 01


section_length: 0015


transport_stream_id: 13f6


version_number: 13


current_next_indicator: 01


section_number: 00


last_section_number: 00


program_number: 0000


network_PID: 0010


program_number: 0001


program_map_PID: 0020


program_number: 0002


program_map_PID: 0021


CRC_32: 1a34b477


 


 


可以看出,此PAT只有一段,包含了三个节目,节目号0000对应于network_PID=0010 ,节目号0001对应于program_map_PID =0020,节目号0002对应于program_map_PID =0021,从实际的角度,我们应该把这三个节目号理解为三个频道,第一个频道中的内容是网络信息,第二、三个频道包含了节目信息。在数字电视中,一个频道即对应于一个频点,如498MHZ一个频道上可以有多个节目,后面的PMT即是告诉了我们某个频道中所有节目对应的PID


于是现在就搜寻PID=0x0020TS分组,即是频道2对应的PMT信息。




(其实楼主的理解不是完全正确,transport_stream_id标识了一个唯一的传输流(每一个传输流对应一个频点,如498MHz),一个PAT表表示一个流里面的信息。
上面的三个节目号理解为三个频道是正确的,但在数字电视中,一个频道对应的也是一个节目,而不是一个频点(当然节目号为0时对应的是NIT的PID)。
)



(3)PMT


   PMT数据结构如下:





TS_program_map_section() { 
 table_id                                                                              // 8
 section_syntax_indicator                                                  //1
 '0'                                                                                      //  1
 reserved                                                                           //    2
 section_length                                                                   //  12
 program_number                                                               //16
 reserved                                                                              // 2
 version_number                                                                  //5
 current_next_indicator                                                       //1
 section_number                                                                  // 8
 last_section_number                                                          //8
 reserved                                                                               //3
 PCR_PID                                                                             //13
 reserved 4
 program_info_length                                                          //12
 for (i=0; i<N; i++) { 
  descriptor() 
 } 
 for (i=0;i<N1;i++) { 
  stream_type                                                                        //8
  reserved                                                                               //3
  elementary_PID                                                                  //13
  reserved                                                                               //4
  ES_info_length                                                                   //12
  for (i=0; i<N2; i++) { 
   descriptor() 
  } 
 } 
 CRC_32                                                                               //32
}

table_id  固定为0x02 ,标志是该表是PMT


section_syntax_indicator


section_length     


version_number      


current_next_indicator  以上四个字段意思和PAT相同,可参考上面解释


       section_number      


       last_section_number   以上两个字段意思和PAT相同,不过值都固定为0x00,我觉得这样的原因可能是因为PMT不需要有先后顺序,因为先定义哪个节目都是无所谓。


       program_number  节目号,表示该PMT对应的节目


       PCR_PID            PCR(节目时钟参考)所在TS分组的PID,根据PID可以去搜索相应的TS分组,解出PCR信息。


program_info_length  该节目的信息长度,在此字段之后可能会有一些字节描述该节目的信息


stream_type          指示了PIDelementary_PIDPES分组中原始流的类型,比如视频流,音频流等,见后面的表


elementary_PID      该节目中包括的视频流,音频流等对应的TS分组的PID


ES_info_length       该节目相关原始流的描述符的信息长度。


 

stream_type对应的类型:

 


还是举个例子,下述是一个包含PMTTS分组,


   47 40 20 1c 00 02 b0 1f 00 01 e7 00 00 e1 00 f0 00 02 e1 00 f0 05 02 03 b2 44 5f 04 e1 10 f0 03 03 01 67 c9 ab c8 d2


红色的四个字节是TS分组头部,蓝色的00是“指针域”,意义同PAT中的指针域。所以下面的数据就是PMT的内容:02 b0 1f 00 01 e7 00 00 e1 00 f0 00 02 e1 00 f0 05 02 03 b2 44 5f 04 e1 10 f0 03 03 01 67 c9 ab c8 d2


再解出PMT,得到下列信息:


table_id: 02


section_syntax_indicator: 01


section_length: 01f


program_number: 0001


version_number: 13


current_next_indicator: 01


section_number: 00


last_section_number: 00


PCR_PID: 0100


program_info_length: 000


descriptor:


steam_type: 00


elementary_PID: 0001


ES_info_length: 000


descriptor:


steam_type: 02


elementary_PID: 0001


ES_info_length: 005


descriptor: 02 03 b2 44 5f


steam_type: 04


elementary_PID: 0011


ES_info_length: 003


descriptor: 03 01 67


CRC_32: c9abc8d2


 


可以看出,该节目号0001包含了三个流的信息,流类型分别为00020400的流为保留值,可以不考虑,02表示原始流为视频流,其elementary_PID000104表示原始流为音频流,其elementary_PID0011,两个流分别还带有descriptor(描述符),说明了该原始流的一些信息。


得到了这个elementary_PID,再从后面的传输流中找到PID为这个值的TS分组,其有效负载即为这个原始流的数据,获取数据送到解码器,即可还原这个视频或音频了。




三、总结


上面的都是一些零散的知识,跟我们实际应用有什么关系呢?下面就是一个简易的应用过程---搜台。搜台过程大致如下:


先调整高频头到一个固定的频率(498MHZ),如果此频率有数字信号,相关芯片会自动把TS流数据传送给MPEG- 2 decoder. MPEG-2 decoder先进行数据的同步,也就是等待完整的Packet的到来.然后循环查找是否出现PID== 0x0000Packet,如果出现了,则马上进入分析PAT的处理,获取了所有的PMTPID.接着循环查找是否出现PMT,如果发现了,则自动进 PMT分析,获取该频段所有的频道数据并保存.如果没有发现PAT或者没有发现PMT,说明该频段没有信号,进入下一个频率扫描


上述过程主要涉及到PATPMT的一些解码和解复用知识,这也是目前我学习到的,当然,数字电视涉及到的知识远远不止这些,解码方面就还包括调整字段的处理,SI(业务信息)应用,时钟的处理,CA加密解MI系统等,还需要继续的学习和实践。






DVB系统测量标准中错误指示的三个等级


TR101-290: DVB 系统测量标准。TR 101-290 定义的三个优先级,是码流监测的一项主要内容。通过这三个优先级的监测,可以检验被监测的码流是否符合MPEG-2 和DVB标准。这三个优先级都包含许多不同的参数。


PSI/SI:
数字电视业务信息,由PSI和SI两部分构成。PSI是MPEG-2规定的,它由PAT、PMT、CAT和NIT
4个表构成,其中PAT、PMT表最为重要。SI是DVB标准规定的,它由BAT、SDT、EIT、RST、TDT、TOT、ST、SIT和DIT
9个表构成,其中 BAT、SDT、EIT 和 TDT 是强制性的。PSI/SI 由“表”和“描述符”构成。表是PSI/SI
的基本结构,针对特定用途,PSI/SI 中规定了一系列表来实现它;表由变量和描述符组成。描述符提供了更多的描述功能。


星座图: 星座显示是矢量示波器显示的数字等价形式,它可显示 QAM 信号的同相(I)分量和正交(Q)分量。符号是一个特定调制系统中所传输的最小信息成分。对于 QAM-64,一个符号代表 6 个位,在图上绘制为一个点。


BER位误码率:位误码率是发生误码的位数与传输的总位数之比。


MER:调制误差比, MER 将接收符号(代表调制图案中的一个数字值)的实际位置与其理想位置进行比较。当信号质量降低时,接收符号距离理想位置更远,MER 测量值将会减小。


EVM:误差矢量幅度, EVM测量类似于 MER,但表达形式不同。EVM 表达为 RMS
误差矢量幅度与最大符号幅度的百分比值。信号缺陷增加时,EVM 将会增大,而 MER 则会减小。EVM
是在IQ(同相与正交)星座图上检测到的载波与其理论上的准确位置之间的距离,是“误差信号矢量”与“最大信号幅度”之比,表达为 RMS 百分比值。



RS错误:表示经过Reed Solomon纠错(简称RS)后,出现错误bit的包数 。



频率偏移
:实际中心频率和理论中心频率的偏移。


SR偏移:实际符码率和理论符码率的偏移。



频道IQ反转:星座图IQ向量是否反转。


__________________________________________________________________________




根据DVB最新的TR101290测试标准将DVB/MPEG-2 TS流的测试错误指示分为3个等级,

第一等级是可正确解码所必须的几个参数;

第二等级是达到同步后可连续工作必须的参数和需要周期监测的参数;

第三等级是依赖于应用的几个参数


第一级共 6种错误,包括:同步丢失错误、同步字节错误、PAT 错误、连续计数错误、PMT错误及 PID错误。


(1)传送码流同步丢失:连续检测到连续 5 个正常同步视为同步,连续检测到 2 个以上不正确同步则为同步丢失错误。传输流失去同步,标志着传输过程中会有一部分数据丢失,直接影响解码后的画面的质量。


(2)同步字节错误:同步字节值不是 0X47。同步字节错误和同步丢失错误的区别在于同步字节错误传输数据仍是 188或 204 包长,但同步字头的 0X47被其他数字代替。这表明传输的部分数据有错误,严重时会导致解码器解不出信号。


(3)PAT 错误:标识节目相关表 PAT 的 PID 为 0x0000,PAT 错误包括标识 PAT 的 PID 没有至少 0.5
s出现一次,或者 PID为 0x0000 的包中无内容,或者 PID为 0x0000 的包的包头中的加密控制段不为 0。PAT
丢失或被加密,则解码器无法搜索到相应节目;PAT
超时,解码器工作时间
延。                                                                              


(4)连续计数错误:TS包头中的连续计数器是为了随着每个具有相同 PID的 TS包的增加而增加,为解码器确定正确的解码顺序。TS包头连续计数不正确,表明当前传输流有丢包、包重叠、包顺序错现象,会导致解码器不能正确解码。


(5)PMT 错误:节目映射表 PMT 标识并指示了组成每路业务的流的位置,及每路业务的节目时钟参考(PCR)字段的位置。PMT
错误包括标识 PMT 的 PID 没有达到至少 0.5 s 出现一次,或者所有包含PMT 表的 PID的包的包头中的加密控制段不为 0。PMT
被加密,则解码器无法搜索到相应节目;PMT 超时,影响解码器切换节目时间。


(6)PID错误:检查是否每一个 PID都有码流,没有 PID就不能完成该路业务的解码。


第二级共 6 种错误,包括:传输错误、CRC 错误、PCR 间隔错误、PCR 抖动错误、PTS 错误及CAT 错误。                                                                

(1)传输错误:TS包头中的传送包错误指示为“1”,表示在相关的传送包中至少有 1 个不可纠正的错误位,只有在错误被纠正之后,该位才能被重新置 0。而一旦有传送包错,就不再从错包中得出其他错误指示。


(2)CRC 错误:在 PSI和 SI的各种表中出现循环冗余检测码 CRC 出错,说明这些表中的信息有错,这时不再从出现错误的表中得出其他错误信息


(3)PCR 间隔错误:PCR 用于恢复接收端解码本地的 27 MHz 系统时钟,如果在没有特别指明的情况下,PCR 不连续发送时间一次超过 100 ms 或 PCR 整个发送间隔超过 40 ms,则导致接收端时钟抖动或者漂移,影响画面显示时间。


(4)PCR 抖动错误:PCR 的精度必须高于 500 ns 或 PCR 抖动量不得大于±500 ns。PCR抖动过大,会影响到解码时钟抖动甚至失锁。


(5)PTS错误:播出时间标记 PTS重复发送时间大于 70 ms,则对帧图像正确显示产生影响。PTS只有在 TS未加扰时方能接收。


(6)CAT 错误:TS 包头中的加密控制段不为 0,但却没有相应的 PID 为 0x0001 的条件接收表 CAT,或在 PID 为
0x0001 的包中发现非 CAT 表。CAT 表将指出授权管理信息 EMM包的 PID并控制接收机的正确接收,如果
CAT表不正确,就不能正确接收。



第三级共 10种错误,包括:NIT 错误、SI重复率错误、缓冲器错误、非指定 PID错误、SDT错误、EIT 错误、RST 错误、TDT 错误、空缓冲器错误及数据延迟错误。


第三等级错误并非是TS传输流的致命错误,但会影响一些具体应用的正确实施。


NIT 标识错误或传输超时,会导致解码器无法正确显示网络状态信息。



SDT 标识错误或传输超时,会导致解码器无法正确显示信道节目的信息。



EIT 标识错误或传输超时,会导致解码器无法正确显示每套节目的相关服务信息。




2009年4月1日星期三

mSpaces 网站配置架构

点击进入mSpaces 网站配置架构

Android开发_第三章




第三章 开发应用





第一节 实现用户接口





第一小节 屏幕条目的分层结构


Android应用的基本功能单元是activity,一个android.app.Activity类对象。一个activity能实现很多事情,但是他本身没有屏幕呈现功能。实现屏幕呈现和设计UI,可以使用views和viewgroups,Android平台的基本用户接口单元。



Views

一个view是一个基类android.view.View的对象,它是一个数据结构,它保存了一块特定方形屏幕区域的布局和内容。一个View对象处理测量、放置、画布、焦点改变、滚动条和键盘/姿态响应。



view类是widgets(完全实现屏幕交互条目的类集)的一个基类。Widgets处理自己色测量、画布,因此你能更快的建立自己的UI。Widgets包括Text, EditText, InputMethod, MovementMethod, Button, RadioButton, CheckBox和ScrollView。



Viewgroups

一个viewgroup是android.view.Viewgroup的对象。见词释义,一个viewgroup是一些特定类型view对象的集合,viewgroup能给你的UI增加架构,并且为单个实体建立复杂的屏幕条目。



viewgroup类是layout(完全实现屏幕布局的类集)的一个基类。这些布局能够用来实现view集的架构。



树形结构UI

在Android平台上,用view或viewgroup定义一个activity的UI,象下图所示。这个树可以是很简单或很复杂的,你可以用widget集和布局或客户化的view类型来建立它。






要把这个树放到屏幕上渲染,activity调用setContentView()方法,传递一个参考给根节点。一旦android具有根节点对象的参考,就可以直接和节点失效、测量和画这个树。根节点接着要求子节点画他们自己,当然,树上的每个veiwgroup直接画它的子节点。



就象之前所说的,每个viewgroup测量它的有效的空间,布置它的子节点,让每个子节点调用Draw()方法渲染自己。每个子节点需要在父节点的大小和位置,但是父对象有最后的决定权决定每个子节点放在哪个位置、占多大空间。



LayoutParams

每个viewgroup用嵌套的扩展了ViewGroup.LayoutParams的类。这些子类定义了儿子的大小和位置。




注意每个LayoutParams子类有它自己的语法。每个子条目必须定义LayoutParams相应它的父母,虽然它也许为它的儿子定义不同的LayoutParams。



所有的viewgroup都包括高度和宽度,许多包括边界空白和界宽。你可以指定宽度和高度,虽然你也许不想这样做。更多时候也许高速你的view去适配它的内容边界大小,或者变成它所包括的对象一样大。






第二小节 常用的布局对象


FrameLayout

FrameLayout是一个简单的布局对象,在你的屏幕上保留一部分空白的空间,以后你可以用简单对象在填充这个空间。所有的子条目都定位于屏幕的左上角,你不能指定FrameLayout儿子的位置。后来的子对象将简单的部分或全部的覆盖更早的对象,或者模糊化它(假定新的对象是透明的)。



LinearLayout

一个LinearLayout分配所有的子对象在单个方向-水平或垂直,取决于你在LinearLayout上设置的属性。所有子对象都是一个跟着一个堆叠,因此一个垂直列表每行只有一个子对象,不管他们占多宽,一个水平列表只有一行的高度(它的高度就是最高的子对象的高度加上填充)。LinearLayout保持子对象之间的边界,和gravity(子对象的对齐方式:左对齐、右对齐、中心对齐等)。



LinearLayout也支持分配一个权重给子对象。这个值允许子对象扩展它自己来填充任何屏幕上的空白。这样可以防止一群小对象被绑定在大屏幕的边角上,允许他们扩展空间。子对象指定一个权重值,任何余下的空间将按比例分配给这些子对象。缺省权重是0,例如,假定有三个文本框,其中两个权重为1,则这两个将等比例的扩展来填充余下的空间,第三个将不会有任何变化。



下面两个表单呈现了LinearLayout布局:一个Button,一些Label,一些TextBox,加上一些填充值。TextBox设置它的宽度为FILL_PARENT,其它的设置为WRAP_CONTENT。对齐点,缺省在左边,左边的表单没有设置权重(就是缺省值0),右边表单的comments文本框权重设置为1。加入Name文本框也被设置成1,那么Name文本框和comments文本框具有相同的高度。




在水平的LinearLayout,这些子对象会以文本基线对齐。这样可以方便使用者撇一眼文本的时候,不至于对不齐而看走眼。当然这个也可以通过设置布局xml文件的android:baselineAligned="false"来关闭。



TableLayout

TableLayout以行列方式定位它的子对象,一个tablelayout有一个或多个TableRow对象组成,每个TableRow对象定义一行(其实,你可以有其它子对象)。TableLayout容器并不显示行、列、单元格的边界,每行有0个或多个单元格,每个单元格可放一个一个View对象。一个表有许多列,一个表可以有空单元格。单元格不能产生列,跟在HTML里面一样。下面图片显示了TableLayout,带有不可见的点线边界。




列可以被隐藏,能够延展来填充整个屏幕空间,也可以收缩来适应适应屏幕。



AbsoluteLayout

AbsoluteLayout必须指定屏幕上的x/y坐标,(0,0)为屏幕的左上角,坐标值随着向右向下而增加。边界空白不支持,重叠允许(但不建议)。通常不建议使用AbsoluteLayout,除非必须,因为它是刚性的,可能在不同设备上工作不一致。



RelativeLayout

RelativeLayout子对象指定相对于其它对象的相对位置。因此可以分配两个对象在右边界,或者让一个在另一个下面,或者处于屏幕中心。这些条目按照给定顺序渲染,假如第一个条目在屏幕中心,其它相对于它的条目将会相对于屏幕中心。假如用xml文件指定布局,必须首先指定一个参考条目。



这是个例子,包括可见和不可见条目。




本图显示了屏幕对象的类名,以及他们的属性。这些属性有些被这些条目直接支持,一些通过LayoutParams成员支持(屏幕上所有对象都是RelativeLayout的子条目)。RelativeLayout参数是宽度、高度、在下面、在顶上、在左边、填充和左边界。注意这些参数中的一些是相对于其它子对象,包括在左边、在顶上、在下面等等。



一些重要的视图组





































































描述


AbsoluteLayout


绝对位置布局


FrameLayout


框架布局


Gallery


水平滚动布局


GridView


网格布局


LinearLayout


线性布局


ListView


列表布局


RelativeLayout


相对布局


ScrollView


垂直滚动布局


Spinner


在一行文本框里一次显示一个条目


SurfaceView


直接访问画布


TabHost


标签列表布局


TableLayout


表格布局


ViewFlipper


幻灯式布局


ViewSwitcher


同上ViewFlipper








第三小节 和AdapterView工作(绑定数据)


一些视图组有UI,这些对象具有典型子类AdapterView。例子包括Gallery和ListView,这些对象通常要做两个工作:



  • 用数据填充布局




  • 处理用户选择




用数据填充布局

绑定类到Adapter,Adapter从代码得到数据,或者从设备数据库查询到结果。

// Get a Spinner and bind it to an ArrayAdapter that 
// references a String array.
Spinner s1 = (Spinner) findViewById(R.id.spinner1);
ArrayAdapter adapter = ArrayAdapter.createFromResource(
this, R.array.colors, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s1.setAdapter(adapter);

// Load a Spinner and bind it to a data query.
private static String[] PROJECTION = new String[] {
People._ID, People.NAME
};

Spinner s2 = (Spinner) findViewById(R.id.spinner2);
Cursor cur = managedQuery(People.CONTENT_URI, PROJECTION, null, null);

SimpleCursorAdapter adapter2 = new SimpleCursorAdapter(this,
android.R.layout.simple_spinner_item, // Use a template
// that displays a
// text view
cur, // Give the cursor to the list adatper
new String[] {People.NAME}, // Map the NAME column in the
// people database to...
new int[] {android.R.id.text1}); // The "text1" view defined in
// the XML template

adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s2.setAdapter(adapter2);

注意:在CursorAdapter的PROJECTION里面的People._ID是必须的,否则会得到一个异常。



处理用户选择

通过设置类的AdapterView.OnItemClickListener成员到监听器,能够捕获选择变化。

// Create a message handling object as an anonymous class.
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
public void onItemClick(AdapterView parent, View v, int position, long id)
{
// Display a messagebox.
Toast.makeText(mContext,"You've got an event",Toast.LENGTH_SHORT).show();
}
};

// Now hook into our object and set its onItemClickListener member
// to our class handler object.
mHistoryView = (ListView)findViewById(R.id.history);
mHistoryView.setOnItemClickListener(mMessageClickedHandler);






第四小节 用XML设计屏幕布局


因为在代码里设计屏幕布局是繁笨的,Android支持通过XML来设计屏幕。Android定义了大量的客户化条目,每个条目代表一个特定的Android View子类。可以象建立HTML文件一样设计屏幕布局,用一系列的标记,保存在应用的res/layout/目录的XML文件里。要学习XML文件格式和哪些条目被使用,请参考布局资源章节。每个文件描述一个单个的android.view.View,也可以是一些简单的可视化的条目,或者包括子对象的布局条目。当Android编译应用时,它编译每个文件到android.view.View资源,可以用setContentView(R.layout.layout_file_name)代码在Activity.onCreate()实现里面来装载。



每个XML文件有Android GUI类的一些标记组成,这些标记有类方法对应的属性(例如,EditText有text属性对应于EditText.setText)。



注意:类和方法名字之间、条目和属性名字之间不是刚好1:1的关系,虽然大部分情况下是1:1关系。



Android趋向于按照呈现在XML文件里面的顺序绘制各个条目。因此,假如条目有重叠,在XML文件的最后一个绘制在顶层。



每个XML文件被编译成一颗树,树根是单个的View或ViewGroup对象,因此必须有单个的根标记。



命名为layout_something的属性是LayoutParams成员。



下面这些值可以用来作度量衡:



  • px (pixels) 点


  • dip (device independent pixels) 独立于设备的点


  • sp (scaled pixels — best for text size) 扩展点


  • pt (points) 点


  • in (inches) 英寸


  • mm (millimeters) 毫米


例如:android:layout_width="25px"



下面的XML文件建立了屏幕布局,注意屏幕顶部的文本由Activity.setTitle设置。注意,参考相对条目的属性用ID相关资源来参考(@id/id_number)。

<?xml version="1.0" encoding="utf-8"?>
<!-- Demonstrates using a relative layout to create a form -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/blue"
android:padding="10px">

<TextView id="@+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Type here:"/>

<EditText id="@+id/entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_below="@id/label"/>

<Button id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/entry"
android:layout_alignParentRight="true"
android:layout_marginLeft="10px"
android:text="OK" />

<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ok"
android:layout_alignTop="@id/ok"
android:text="Cancel" />
</RelativeLayout>

装载XML资源

装载这些布局资源非常容易,只要简单调用onCreate()方法:

protected void onCreate(Bundle savedValues)
{
// Be sure to call the super class.
super.onCreate(savedValues);

// Load the compiled layout resource into the window's
// default ViewGroup.
// The source file is res/layout/hello_activity.xml
setContentView(R.layout.hello_activity);

// Retrieve any important stored values.
restoreValues(savedValues);
}


第五小节 屏幕条目钩子


可以通过Activity.findViewById得到屏幕条目句柄,可以用这个句柄设置或恢复任何值:

TextView msgTextView = (TextView)findViewById(R.id.msg);
msgTextView.setText(R.string.push_me);




第六小节 UI通知的监听


一些UI通知自动地被Android触发。实例,Activity的onKeyDown和onKeyUp,和Widget的onFocusChanged(boolean,int,Rect)。但是呢,一些重要的回调函数,例如按钮点击,不能自动通知,必须手工注册。

public class SendResult extends Activity
{
/**
* Initialization of the Screen after it is first created. Must at least
* call setContentView() to
* describe what is to be displayed in the screen.
*/
protected void onCreate(Bundle savedValues)
{
...

// Listen for button clicks.
Button button = (Button)findViewById(R.id.corky);
button.setOnClickListener(mCorkyListener);
}

// Create an anonymous class to act as a button click listener.
private OnClickListener mCorkyListener = new OnClickListener()
{
public void onClick(View v)
{
// To send a result, simply call setResult() before your
// activity is finished, building an Intent with the data
// you wish to send.
Intent data = new Intent();
data.setAction("Corky!");
setResult(RESULT_OK, data);
finish();
}
};


第七小节 在应用上使用主题


假如你不明确指定应用主题,Android将使用缺省的由android.R.style.Theme定义的主题。多数情况下你想使用不同的主题(如Theme.Light)或建立自己的主题。



为了在XML里面设置主题,在AndroidManifest.xml文件里用theme属性来指定,可以使用<application>标记来指定缺省主题,用<activity>控制部分主题。

<!-- AndroidManifest.xml--> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.home">
<application android:theme="@android:style/Theme.Dark" >
<activity class=".Home"
...
</activity>
</application>
</manifest>

也可以用编程方式来实现主题,确保在建立任何view之前设置主题,以便正确的主题被用于你的所有UI条目。

 protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
...
setTheme(android.R.style.Theme_Light);
setContentView(R.layout.linear_layout_3);
}




第八小节 UI条目和概念术语


Activity:

Android应用的标准屏幕。



View:

屏幕上的一个矩形区域。



ViewGroup:

包含多个View对象。



Widget:

格式化条目。



Drawable:

能够装进其它UI的可视化条目。



Panel:

面板



Dialog:

对话框



Window:

窗口



Surface:

表面,能装载画布对象。



SurfaceView:

表面视图



Canvas:

画布



OpenGL ES:

实现3D图形的库。




第二节 Android建筑模块


如果认为Android应用是一个各种类型的组件集,这些组件大部分都是松耦合,你可以精确的描述这些组件的联合而不是简单的描述一个应用。



通常,这些组件都运行在一个相同的系统进程里面,当然可以在这个进程里面建立多个线程,也可以建立完全独立的子进程,这种情况并不通用,因为Android进程对用户透明。



这些是Android API最重要的部分:




AndroidManifest.xml:


它是应用的控制文件,告诉系统你的顶级组件(activities, services, intent receivers, content providers)要做些什么,例如,"glue"指定了你的应用所接收的Intent。




Activities:


它是具有生命周期的一个基本对象,实现一些主要的代码工作,如果需要,显示一个UI给用户。典型的,你会设计一个activity作为你应用的入口点。




Views:


一个view是一个对象,知道自己应该怎样显示在屏幕上,Android用户接口由view树组成,假如你想执行一些客户化的图形技术(比如建立游戏,或者一些不常用的UI widget),你就要建立一个view。




Intents:


表示一个简单的消息对象,能够处理一些感兴趣的事情。例如,如果你的应用想要显示一些web页,它通过intent传递URI,系统定位一些能够处理这个intent事情的代码,并且运行它。Intent也能用于广播一些系统感兴趣的事件。




Services:


它是运行在后台的一块代码,能运行在自己的进程里,也可以运行在其它应用的进程里。其它组件绑定到service,通过远程过程调用来执行它。例如,媒体播放器,当用户退出媒体内容选择UI,用户可能还要继续播放,这时候service就可以实现这样的功能。




Notifications:


它是显示在状态条的小图标,用户能够和这个图标交互来获得信息,最好的例子是SMS消息、呼叫历史和语音邮件。当然应用可以建立自己的notification。Notification是一个有力的机制,用来通知用户有些事情需要处理或注意。



ContentProviders:

它是数据仓库,提供数据给设备来访问,经典的例子是用它来访问联系人列表。你的应用能够通过contentProvider访问其它应用给出的数据,也可以定义自己的contentProvider给自己提供数据。




第一小节 AndroidManifest.xml


任何应用都需要AndroidManifest.xml文件,他放在应用的根目录下,描述一些全局值,包括哪些应用组件、每个组件的实现类、哪些类型数据能处理,他们能从哪里装载。



这个文件的一个重要方面是intent过滤,这些过滤器描述了activity能从哪里、什么时候开始。当activity想去执行一个行为,例如打开一个web页,或者打开一个联系人选择条目,它建立一个intent对象。这个对象能够保持几个描述器描述你所想做的,你想处理什么数据,数据的类型和其它信息位。Android为每个应用在intent对象和intent过滤器里比较,找到最适合处理这个数据的应用,或者实现调用者的行为。



除了宣布应用的activity, content provider, service和intent receiver,还可以在androidmanifest.xml里面指定一些权限和指令。



例子,简单的androidmanifest.xml:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.my_domain.app.helloactivity">

<application android:label="@string/app_name">

<activity android:name=".HelloActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

</application>

</manifest>

一些常用条目:



  • 每个androidManifest.xml文件包括名称空间宣布xmlns:android="http://schemas.android.com/apk/res/android,它处于第一个的位置。这个使得大量的标准android属性有效,它能提供大量的数据和条目。


  • 大多数的manifest包括单个<application>条目,定义了包里的所有应用级别的组件和属性。




  • 呈现给用户的任何包作为一个顶级应用是有效的,需要包括至少一个activity组件支持MAIN行为和LAUNCHER范围。




这是一些androidmanifest.xml的细节轮廓,有效的标记:


<manifest>

文件的根节点,在它下面可以放置:


    <uses-permission>


    请求安全性允许,为了使你的包能正确工作。一个manifest能包含0个或多个本条目。


    <permission>


    安全性允许,用来限制包里面的哪些应用能够访问哪些组件和特性。一个manifest能包含0个或多个本条目。


    <instrumentation>


    用来测试本包或其它包的功能性。一个manifest能包含0个或多个本条目。




    <application>


应用级别的根条目,包括应用的全局的或缺省的属性。例如label, icon, theme, 需要的允许等等。一个manifest包括0个或1个本条目,在它下面可方0个或多个下面的条目:


        <activity>


activity是用户和应用交互的基本工具,用户装载应用后首先看到的就是activity,更多其它的屏幕将被独立的activity来实现。



一个activity必须有一个<activity>标记。假如一个activity在manifest里面没有匹配的标记,你就不能装载它。



        另外,可以包含一个或多个<intent-filter>标记。


            <intent-filter>


            用IntentFilter的格式定义组件所支持的intent集。


                <action>


                组件支持的Intent行为。


                <category>


                组件支持的Intent范围。


                <data>


组件支持的Intent data MIME type, Intent data URI scheme, Intent data URI authority, Intent data URI path。



            你可以让activity跟一些meta data通信:




            <meta-data>


            给activity增加新的meta data,客户端能够通过ComponentInfo.metaData恢复。


        <receiver>


一个BroadcastReceiver允许应用得到数据或行为改变的通知,甚至当前它没有运行。和<activity>标记一起,你可以包含一个或多个receiver支持的<intent-filter>条目或<meta-data>值。


        <service>


一个service是一个能运行在后台一定时间的组件,和<activity>标记一起,你可以包含一个或多个receiver支持的<intent-filter>条目或<meta-data>值。


        <provider>


一个ContentProvider是一个管理稳定数据,并且发布数据给其它应用使用的组件。可以使用一个或多个<meta-data>条目。







第三节 保存、恢复和披露数据




一个典型的桌面操作系统提供一个通用的文件系统,任何应用能够存取其它应用的文件,可能需要一些存取控制。Android提供不同的系统:所有的应用数据都是私有的,当然也提供一个标准的方法披露它的数据给其它应用。这一章描述很多不同的方法供应用保存和恢复数据,提供数据给其它应用,怎么请求其它应用的数据。



Preferences:

一个轻量级的方法提供元数据类型的键、值对。典型的用来保存应用的参数。



Files:

可以保存文件在设备或可移动存取介质上,缺省情况下其它应用不能访问这些文件。



Databases:

支持SQLite,应用可以建立自己的私有SQLite数据库。



Content Providers:

内容提供者是可选的应用组件,它对于应用的私有数据披露其读写访问,当然可以加一些访问限制。内容提供者提供标准数据请求语法,和一个标准的访问机制。Androis实现了一些标准数据类型的内容提供者,比如个人的联系人信息。



Network:

也可以使用网络来保存和恢复数据。




第一小节 使用应用Preferences


保存诸如缺省问候语、文本字体之类的应用参数。调用Context.getSharedPreferences()来读写数据。假如想跟同一个包里的其它组件共享这些数据,需要给参数集一个名字,或者用Activity.getPreferences()不带名字保持对调用activity的私有。不能跨包共享参数集,下面是个例子:

public class Calc extends Activity {
public static final String PREFS_NAME = "MyPrefsFile";
...

@Override
protected void onCreate(Bundle state){
super.onCreate(state);

...

// Restore preferences
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
boolean silent = settings.getBoolean("silentMode", false);
setSilent(silent);
}

@Override
protected void onStop(){
super.onStop();

// Save user preferences. We need an Editor object to
// make changes. All objects are from android.context.Context
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("silentMode", mSilentMode);

// Don't forget to commit your edits!!!
editor.commit();
}
}


第二小节 使用Files


Android提供本地文件流的读写访问给应用。调用Context.openFileOutput()和Context.openFileInput()带本地名字和路径来读写文件。如果从其它应用用相同的名字、路径来调用则不会成功;只能访问本地文件。



假如你的应用在编译的时候有静态文件,可以保存你的文件在你的项目res/raw/<mydatafile>,接着用Resources.openRawResource(R.raw.mydatafile)来读取。




第三小节 使用SQLite Databases


建立数据库用SQLiteOpenHelper。Android装备了sqlite3数据库工具,能够浏览表内容,运行SQL命令并且执行其它有用的功能。



所有的数据库,SQLite,保存在/data/data/<package_name>/database。



Android并没有在标准SQLITE上做限制,不过建议每个表增加一个自动增加的关键值作为索引,对于私有数据并不是必须的。但是假如你实现一个content provider,则它是必须的。




第四小节 访问Content Providers


假如想发布数据,可以建立content provider,这个方法能够让所有应用存取某个应用的数据。这个是跨包之间共享数据的唯一方法。Android提供公用数据类型的一些content provider(声音、视频、图像、个人联系信息等等)。在provider包里面有一些Android的基本content provider。



所有的content provider必须实现通用的查询数据方法和返回结果方法。当然,content provider能实现客户化的helper功能,使数据的保存、恢复更简单。



用content provider保存和恢复数据



Android提供各种数据类型供content provider使用,从音乐图片到电话号码,从android.provider包content provider提供的数据。



Android的content provider是很松的连接到他们的客户端,每个content provider披露一个统一的URI标记它处理的数据类型,客户必须使用这个URI来保存和恢复这个类型的数据。



查询数据



每个content provider披露一个统一公共的URI,用来客户端查询、增加、更新、删除数据。URI有两种格式,一种指示这个类型的所有值,一种指示这个类型的一条特定的记录。

content://contacts/people: 返回所有联系人名字。

content://contacts/people/23: 返回单个结果,ID为23的联系人。



一个应用发送一个查询给设备,指定一个通用条目类型(如所有电话号码),或一个特定条目(小马的电话号码)。Android返回结果集的一个游标,下面一个假想的查询字符集和结果集:

query = content://contacts/people/
结果:


















































_ID

_COUNT

NUMBER

NUMBER_KEY

LABEL

NAME

TYPE

13

4

(425) 555 6677

425 555 6677

California office

Bully Pulpit

Work

44

4

(212) 555-1234

212 555 1234

NY apartment

Alan Vain

Home

45

4

(212) 555-6657

212 555 6657

Downtown office

Alan Vain

Work

53

4

201.555.4433

201 555 4433

Love Nest

Rex Cars

Home



注意查询字符串不是标准的SQL查询串,而是一个URI描述了返回结果的类型,这个URI由三个部分组成:开始串content://,接着哪种数据类型要恢复,最后加一个可选的特定条目的ID。例子:



  • content://media/internal/images


  • content://media/external/images


  • content://contacts/people


  • content://contacts/people/23


虽然这是通用格式,这些查询有些任意和迷惑,因此,android在android.provider包里面提供了helper类列表,它定义了这些查询串,因此应该不需要知道不同数据类型的实际URI值。这些helper类定义了叫CONTENT_URI的串。



典型的,你用定义的CONTENT_URI对象来做查询,而不是自己写URI。因此上面每个查询能够用下面URI来参考:



  • MediaStore.Images.Media.INTERNAL_CONTENT_URI


  • MediaStore.Images.Media.EXTERNAL_CONTENT_URI


  • Contacts.People.CONTENT_URI


去查询特定的记录,你用相同的CONTENT_URI,但是要增加特定的ID值,例子:

// Get the base URI for contact with _ID=23.
// This is same as Uri.parse("content://contacts/people/23");
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
// Query for this record.
Cursor cur = managedQuery(myPerson, null, null, null);

TIP:可以用withAppendedPath(Uri, String)增加串到URI。



查询返回数据记录的游标,本次返回什么记录,下次会有什么记录。



应当用Activity.managedQuery()方法恢复游标,游标可以准确处理自己的位置。可以调用Activity.startManagingCursor()方法管理一个未管理的游标。



下面是一个例子,用来恢复联系人列表和他们基本的电话号码:

// An array specifying which columns to return. 
string[] projection = new string[] {
People._ID,
People.NAME,
People.NUMBER,
};

// Get the base URI for People table in Contacts content provider.
// ie. content://contacts/people/
Uri mContacts = People.CONTENT_URI;

// Best way to retrieve a query; returns a managed query.
Cursor managedCursor = managedQuery( mContacts,
projection, //Which columns to return.
null, // WHERE clause--we won't specify.
People.NAME + " ASC"); // Order-by clause.

查询返回什么



查询返回0个或多个数据库记录,列名字、顺序和类型给指定给content provider,每个查询都有一个名字为_id的列,假如查询返回二进制数据,例如位图或声音文件,将返回一个随机名字的列,它给出URI格式(content://URI)让你得到这些数据。看下图:

























_id

name

number

44

Alan Vain

212 555 1234

13

Bully Pulpit

425 555 6677

53

Rex Cars

201 555 4433

这个结果显示一个子集被返回,可选的子集列表放在查询的projection参数里面,内容管理者应该列出支持哪些列,需要描述每一列(参考Contacts.People.Phone,他扩展了BaseColumns,PhonesColumns和PeopleColumns),或者列出常量列名字。为了访问数据,需要知道列数据类型。



通过游标恢复的数据能够在结果集里面前后查找,可以用游标读、修改、删除行,增加新行需要不同的对象。



注意,每个记录集包括一个列名为_id的列,和一个_count域,表示当前结果集的记录数,这些域名字由BaseColumns定义。



查询文件



返回结果的文件域典型的是文件路径,但是,调用者从来不应该直接打开或读取这些文件,相反,应该调用ContentProvider.openInputStream()/ContentResolver.openOutputStream(),或helper功能。



读恢复的数据





游标对象提供了访问结果记录集的方法,假如你查询一条特定记录,你将得到仅一个值,否则,可以包含多个值。可以从这些记录读取特定的数据,当然你必须知道这个数据的类型,游标让你从索引请求列名字,或从列名取得索引号。







假如你正在读2进制数据,例如图像文件,应该调用ContentResolver.openOutputStream()从列名content://URI。







下面片段显示了读取名字和电话号码:


private void getColumnData(Cursor cur){ 
if (cur.moveToFirst()) {

String name;
String phoneNumber;
int nameColumn = cur.getColumnIndex(People.NAME);
int phoneColumn = cur.getColumnIndex(People.NUMBER);
String imagePath;

do {
// Get the field values
name = cur.getString(nameColumn);
phoneNumber = cur.getString(phoneColumn);

// Do something with the values.
...

} while (cur.moveToNext());

}
}

修改数据








批量更新一组数据,用列和值调用ContentResolver.update()方法。







增加新记录



要增加新记录,调用ContentResolver.insert()带URI来增加,将返回新记录的URI,包括记录号,可以用来查询和得到新记录的游标:
ContentValues values = new ContentValues();
Uri phoneUri = null;
Uri emailUri = null;

values.put(Contacts.People.NAME, "New Contact");
//1 = the new contact is added to favorites
//0 = the new contact is not added to favorites
values.put(Contacts.People.STARRED,1);

//Add Phone Numbers
Uri uri = getContentResolver().insert(Contacts.People.CONTENT_URI, values);

//The best way to add Contacts data like Phone, email, IM is to
//get the CONTENT_URI of the contact just inserted from People's table,
//and use withAppendedPath to construct the new Uri to insert into.
phoneUri = Uri.withAppendedPath(uri, Contacts.People.Phones.CONTENT_DIRECTORY);

values.clear();
values.put(Contacts.Phones.TYPE, Phones.TYPE_MOBILE);
values.put(Contacts.Phones.NUMBER, "1233214567");
getContentResolver().insert(phoneUri, values);

//Add Email
emailUri = Uri.withAppendedPath(uri, ContactMethods.CONTENT_DIRECTORY);

values.clear();
//ContactMethods.KIND is used to distinguish different kinds of
//contact data like email, im, etc.
values.put(ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(ContactMethods.DATA, "test@example.com");
values.put(ContactMethods.TYPE, ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);
可以调用带URI的ContentResolver().openOutputStream()来保存文件:
// Save the name and description in a map. Key is the content provider's
// column name, value is the value to save in that record field.
ContentValues values = new ContentValues(3);
values.put(MediaStore.Images.Media.DISPLAY_NAME, "road_trip_1");
values.put(MediaStore.Images.Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

// Add a new record without the bitmap, but with the values.
// It returns the URI of the new record.
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

try {
// Now get a handle to the file for that record, and save the data into it.
// sourceBitmap is a Bitmap object representing the file to save to the database.
OutputStream outStream = getContentResolver().openOutputStream(uri);
sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
outStream.close();
} catch (Exception e) {
Log.e(TAG, "exception while writing image", e);
}
删除记录

用ContentResolver.delete()带URI来删除记录。
要删除多行,调用ContentResolver.delete()带记录类型的URI(例如,android.provider.Contacts.People.CONTENT_URI)和SQL WHERE字句。

建立Content Provider

  1. 扩展ContentProvider。
  2. 定义名叫CONTENT_URI的public static final URI,这是带有content://格式字符串。必须定义一个统一的字符串,最好的方法是用完全合格的小写的类名字格式,例如:public static final Uri CONTENT_URI=Uri.parse("content://com.google.codelab.rssprovider");
  3. 建立系统保存数据,最通用的方法使用文件或SQLite数据库,当然可以用任何其它方式保存数据。
  4. 定义需要返回给客户端的列名,假如用基本的数据库,这些列名就是SQL数据库的列名。任何情况下,你需要定义一个整数列名_id来定义一列。如果使用SQLite数据库,这个类型应该是INTEGER PRIMARY KEY AUTOINCREMENT。AUTOINCREMENT是可选的,缺省情况下,SQLite会自动增加。假如删除最后一行,再增加一行,则增加的这行跟删除的一行具有相同的_id。为了避免这种情况,你需要定义成INTEGER PRIMARY KEY AUTOINCREMENT。Android提供SQLiteOpenHelper类帮助建立和管理你的数据库版本。
  5. 假如披露字节型数据,例如位图,他实际是个类似content://字符串,这是客户端用来恢复数据用的。content provider为这种类型应该实现列名为_data的记录。_data域列出设备上的文件路径,这个域不能给客户端直接读,但可以让ContentResolver读。客户调用ContentResolver.openOutputStream()读取这个内容。ContentResolver请求记录的_data域,因为它比客户端有更高的权限,应该可以直接访问这个文件,并且返回给客户端。
  6. 声明public static Strings供客户返回,或者从游标返回值,
  7. 在应答查询时返回记录集的游标对象,意味着实现了query(),update(), insert()和delete()方法。一般情况下,也许调用ContentResolver.notifyChange()来通知监听器来更新信息。
  8. 在AndroidManifest.xml里面增加一个<provider>标记,用它的authorities属性来定义内容类型的鉴权部分。例如,假如类型类型是content://com.example.autos/auto请求所有自动化的列表,那么authorities应当是com.example.autos。假如数据不需要在多个版本之间同步,需要设置multiprocess属性为true。
  9. 假如你正在处理新的数据类型,必须定义新的MIME类型android.ContentProvider.getType(url)。MIME有两种格式:一是为特定的记录,一是为多记录。下面是通用格式:

  • vnd.android.cursor.item/vnd.yourcompanyname.contenttype
    为单记录,例如,为列车122记录:
    content://com.example.transportationprovider/trains/122

    也许返回MIME类型
    vnd.android.cursor.item/vnd.example.rail
  • vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
    为多记录,例如,为所有列车记录:
    content://com.example.transportationprovider/trains

    也许返回MIME类型
    vnd.android.cursor.dir/vnd.example.rail

一个私有的content provider实现例子,看notepad例子的NodePadProvider类。

下面是各个URI部分的解释:
A. 标准的前缀,不能修改。
B. 鉴权部分,为第三方应用,包括一个类,相应的值在<provider>属性里面:
<provider class="TransportationProvider" authorities="com.example.transportationProvider" />
C. 决定哪种类型的数据被请求,可以0个或多个分段:假如content provider仅仅披露一种类型数据,这项可以不需要写;假如提供几种类型,包括子类型,可以是几个条目:"land/bus,land/train,sea/ship,sea/submarine。
D. 假如一个特定记录被请求,就是一个特定_id记录值被请求,假如特定类型的所有记录被请求,则可以省略。例如:content://com.example.transportationProvider/trans。




第五小节 网络访问


除了上面这些在设备上的存取方式,也可以从网络保存和恢复数据,如果要实现网络操作,必须用下面的包:



  • java.net.*


  • android.net.*




第四节 安全模型