精华内容
下载资源
问答
  • 全国计算机信息安全技术

    千次阅读 2019-06-11 21:31:45
    了解信息安全保障工作的总体思路和基本实践方法  2. 掌握信息安全技术的基本概念、原理、方法和技术  3. 熟练掌握计算机网络安全、系统软件安全和应用软件安全的基本知识和实践技能  4. 掌握信息安全设备的...

    考试大纲

      基本要求

      1. 了解信息安全保障工作的总体思路和基本实践方法

      2. 掌握信息安全技术的基本概念、原理、方法和技术

      3. 熟练掌握计算机网络安全、系统软件安全和应用软件安全的基本知识和实践技能

      4. 掌握信息安全设备的安装、配置和使用的基本方法

      5. 了解信息系统安全设施部署与管理基本技术

      6. 了解信息安全风险评估和等级保护原理与方法

      7. 了解信息安全相关的标准、法律法规和道德规范

      考试内容

      一、信息安全保障概述

      1. 信息安全保障的内涵和意义

      2. 信息安全保障的总体思路和基本实践方法

      二、信息安全基础技术与原理

      1. 密码技术

      (1)对称密码与非对称密码

      (2)哈希函数

      (3)数字签名

      (4)密钥管理

      2. 认证技术

      (1)消息认证

      (2)身份认证

      3. 访问控制技术

      (1)访问控制模型

      (2)访问控制技术

      4. 审计和监控技术

      (1)审计和监控基础

      (2)审计和监控技术

      三、系统安全

      1. 操作系统安全

      (1)操作系统安全基础

      (2)操作系统安全实践

      2. 数据库安全

      (1)数据库安全基础

      (2)数据库安全实践

      四、网络安全

      1. 网络安全基础

      2. 网络安全威胁技术

      3. 网络安全防护技术

      (1)防火墙

      (2)入侵检测系统与入侵防御系统

      (3)PKI

      (4)VPN

      (5)网络安全协议

      五、应用安全

      1. 软件漏洞概念与原理

      2. 软件安全开发

      3. 软件安全检测

      4. 软件安全保护

      5. 恶意程序

      6. Web 应用系统安全

      六、信息安全管理

      1. 信息安全管理体系

      2. 信息安全风险评估

      3. 信息安全管理措施

      七、信息安全标准与法规

      1. 信息安全标准

      2. 信息安全法律法规与国家政策

      3. 信息安全从业人员道德规范

      考试方式

      上机考试,考试时长 120 分钟,满分 100 分。 包含:选择题(60 分)、填空题(20 分)、综合应用题(20 分)。


    复习

    第一章信息安全保障概述

    1.1信息安全保障背景

    1.1.1信息技术及其发展阶段

    信息技术两个方面:生产:信息技术产业;应用:信息技术扩散

    信息技术核心:微电子技术,通信技术,计算机技术,网络技术

    第一阶段,电讯技术的发明;第二阶段,计算机技术的发展;第三阶段,互联网的使用

    1.1.2信息技术的影响

    积极:社会发展,科技进步,人类生活

    消极:信息泛滥,信息污染,信息犯罪

    1.2信息安全保障基础

    1.2.1信息安全发展阶段

    通信保密阶段(20世纪四十年代):机密性,密码学

    计算机安全阶段(20世纪六十和七十年代):机密性、访问控制与认证,公钥密码学(Diffie

    Hellman,DES),计算机安全标准化(安全评估标准)

    信息安全保障阶段:信息安全保障体系(IA),PDRR模型:保护(protection)、检测(detection)、响应(response)、恢复(restore),我国PWDRRC模型:保护、预警(warning)、监测、应急、恢复、反击(counter-attack),BS/ISO 7799标准(有代表性的信息安全管理体系标准):信息安全管理实施细则、信息安全管理体系规范

    1.2.2信息安全的含义

    一是运行系统的安全,二是系统信息的安全:口令鉴别、用户存取权限控制、数据存取权限方式控制、审计跟踪、数据加密等

    信息安全的基本属性:完整性、机密性、可用性、可控制性、不可否认性

    1.2.3信息系统面临的安全风险

    1.2.4信息安全问题产生的根源:信息系统的复杂性,人为和环境的威胁

    1.2.5信息安全的地位和作用

    1.2.6信息安全技术

    核心基础安全技术:密码技术

    安全基础设施技术:标识与认证技术,授权与访问控制技术

    基础设施安全技术:主机系统安全技术,网络系统安全技术

    应用安全技术:网络与系统安全攻击技术,网络与系统安全防护与响应技术,安全审计与责任认定技术,恶意代码监测与防护技术

    支撑安全技术:信息安全评测技术,信息安全管理技术

    1.3信息安全保障体系

    1.3.1信息安全保障体系框架

    生命周期:规划组织,开发采购,实施交付,运行维护,废弃

    保障要素:技术,管理,工程,人员

    安全特征:机密性,完整性,可用性

    1.3.2信息系统安全模型与技术框架

    P2DR安全模型:策略(policy),防护,检测,响应;防护时间大于检测时间加上响应时间,安全目标暴露时间=检测时间+响应时间,越小越好;提高系统防护时间,降低检测时间和响应时间

    信息保障技术框架(IATF):纵深防御策略():人员,技术,操作;技术框架焦点域:保护本地计算机,保护区域边界,保护网络及基础设施,保护支撑性基础设施

    1.4信息安全保障基本实践

    1.4.1国内外信息安全保障工作概况

    1.4.2信息安全保障工作的内容

    确定安全需求,设计和实施安全方案,进行信息安全评测,实施信息安全监控


    第二章信息安全基础技术与原理

    2.1密码技术

    2.1.1对称密码与非对称密码

    对称密钥密码体制:发送方和接收方使用相同的密钥

    非对称密钥密码体制:发送方和接收方使用不同的密钥

    对称密钥体制:

    加密处理速度快、保密度高,密钥管理分发复杂代价高、数字签名困难

    分组密码:一次加密一个明文分组:DES,IDEA,AES;序列密码:一次加密一位或者一个字符:RC4,SEAL

    加密方法:代换法:单表代换密码,多表代换;置换法

    安全性:攻击密码体制:穷举攻击法(对于密钥长度128位以上的密钥空间不再有效),密码分析学;典型的密码攻击:唯密文攻击,已知明文攻击,选择明文攻击(加密算法一般要能够抵抗选择明文攻击才认为是最安全的,分析方法:差分分析和线性分析),选择密文攻击

    基本运算:异或,加,减,乘,查表

    设计思想:扩散,混淆;乘积迭代:乘积密码,常见的乘积密码是迭代密码,DES,AES

    数据加密标准DES:基于Feistel网络,3DES,有效密钥位数:56

    国际数据加密算法IDEA:利用128位密钥对64位的明文分组,经连续加密产生64位的密文分组

    高级加密标准AES:SP网络

    分组密码:电子密码本模式ECB,密码分组链模式CBC,密码反馈模式CFB,输出反馈模式OFB,计数模式CTF

    非对称密码:

    基于难解问题设计密码是非对称密码设计的主要思想,NP问题NPC问题

    克服密钥分配上的困难、易于实现数字签名、安全性高,降低了加解密效率

    RSA:基于大合数因式分解难得问题设计;既可用于加密,又可用于数字签名;目前应用最广泛

    ElGamal:基于离散对数求解困难的问题设计

    椭圆曲线密码ECC:基于椭圆曲线离散对数求解困难的问题设计

    通常采用对称密码体制实现数字加密,公钥密码体制实现密钥管理的混合加密机制

    2.1.2哈希函数

    单向密码体制,从一个明文到密文的不可逆的映射,只有只有加密过程,没有解密过程

    可将任意长度的输入经过变换后得到固定长度的输出(原消息的散列或消息摘要)

    应用:消息认证(基于哈希函数的消息认证码),数字签名(对消息摘要进行数字签名口令的安全性,数据完整性)

    消息摘要算法MD5:128位

    安全散列算法SHA:160位

    SHA比MD5更安全,SHA比MD5速度慢了25%,SHA操作步骤较MD5更简单

    2.1.3数字签名

    通过密码技术实现,其安全性取决于密码体制的安全程度

    普通数字签名:RSA,ElGamal,椭圆曲线数字签名算法等

    特殊数字签名:盲签名,代理签名,群签名,不可否认签名,具有消息恢复功能得签名等

    常对信息的摘要进行签名

    美国数字签名标准DSS:签名算法DSA

    应用:鉴权:重放攻击;完整性:同形攻击;不可抵赖

    2.1.4密钥管理

    包括密钥的生成,存储,分配,启用与停用,控制,更新,撤销与销毁等诸多方面密钥的分配与存储最为关键

    借助加密,认证,签名,协议和公证等技术

    密钥的秘密性,完整性,真实性

    密钥产生:噪声源技术(基于力学,基于电子学,基于混沌理论的密钥产生技术);主密钥,加密密钥,会话密钥的产生

    密钥分配:

    分配手段:人工分发(物理分发),密钥交换协议动态分发

    密钥属性:秘密密钥分配,公开密钥分配

    密钥分配技术:基于对称密码体制的密钥分配,基于公钥密码体制的密钥分配

    密钥信息交换方式:人工密钥分发,给予中心密钥分发,基于认证密钥分发

    人工密钥分发:主密钥

    基于中心的密钥分发:利用公开密钥密码体制分配传统密码的密钥;可信第三方:密钥分发中心KDC,密钥转换中心KTC;拉模型,推模型;密钥交换协议:Diffie-Hellman算法

    公开密钥分配:公共发布;公用目录;公约授权:公钥管理机构;公钥证书:证书管理机构CA,目前最流行

    密钥存储:

    公钥存储

    私钥存储:用口令加密后存放在本地软盘或硬盘;存放在网络目录服务器中:私钥存储服务PKSS;智能卡存储;USB Key存储

    2.2认证技术

    2.2.1消息认证

    产生认证码的函数:

    消息加密:整个消息的密文作为认证码

    消息认证码(MAC):利用密钥对消息产生定长的值,并以该值作为认证码;基于DES的MAC算法

    哈希函数:将任意长的消息映射为定长的哈希值,并以该哈希值作为认证码

    2.2.2身份认证

    身份认证系统:认证服务器、认证系统客户端、认证设备

    系统主要通过身份认证协议(单向认证协议和双向认证协议)和认证系统软硬件进行实现

    认证手段:静态密码方式

    动态口令认证:动态短信密码,动态口令牌(卡)

    USB Key认证:挑战/应答模式,基于PKI体系的认证模式

    生物识别技术

    认证协议:基于口令的认证协议,基于对称密码的认证,基于公钥密码的认证

    2.3访问控制技术

    访问控制模型:

    自主访问控制(DAC):访问矩阵模型:访问能力表(CL),访问控制表(ACL);商业环境中,大多数系统,如主流操作系统、防火墙等

    强制访问控制(DAC):安全标签:具有偏序关系的等级分类标签,非等级分类标签,比较主体和客体的安全标签等级,,访问控制安全标签列表(ACSLL);访问级别:最高秘密级,秘密级,机密级,无级别及;Bell-Lapadula模型:只允许向下读、向上写,保证数据的保密性,Biba不允许向下读、向上写,保护数据完整性;Chinese Wall模型:多边安全系统中的模型,包括了MAC和DAC的属性

    基于角色的访问控制(RBAC):要素:用户,角色,许可;面向企业,大型数据库的权限管理;用户不能自主的将访问权限授权给别的用户;MAC基于多级安全需求,RBAC不是

    2.3.2访问控制技术

    集中访问控制:

    认证、授权、审计管理(AAA管理)

    拨号用户远程认证服务RADIUS:提供集中式AAA管理;客户端/服务器协议,运行在应用层,使用UDP协议;组合认证与授权服务

    终端访问控制器访问控制系统TACACS:TACACS+使用TCP;更复杂的认证步骤;分隔认证、授权、审计

    Diameter:协议的实现和RADIUS类似,采用TCP协议,支持分布式审计

    非集中式访问控制:

    单点登录SSO

    Kerberos:使用最广泛的身份验证协议;引入可信的第三方。Kerberos验证服务器;能提供网络信息的保密性和完整性保障;支持双向的身份认证

    SESAME:认证过程类似于Kerberos

    2.4审计和监控技术

    2.4.1审计和监控基础

    审计系统:日志记录器:收集数据,系统调用Syslog收集数据;分析器:分析数据;通告器:通报结果

    2.4.2审计和监控技术

    恶意行为监控:主机监测:可监测的地址空间规模有限;网络监测:蜜罐技术(软件honeyd),蜜网(诱捕网络):高交互蜜罐、低交互蜜罐、主机行为监视模块

    网络信息内容审计:方法:网络舆情分析:舆情分析引擎、自动信息采集功能、数据清理功能;技术:网络信息内容获取技术(嗅探技术)、网络内容还原分析技术;模型:流水线模型、分段模型;不良信息内容监控方法:网址、网页内容、图片过滤技术


    第三章系统安全

    3.1操作系统安全

    3.1.1操作系统安全基础

    基本安全实现机制:

    CPU模式和保护环:内核模式、用户模式

    进程隔离:使用虚拟地址空间达到该目的

    3.1.2操作系统安全实践

    UNIX/Linux系统:

    文件系统安全:所有的事物都是文件:正规文件、目录、特殊文件(/dev下设备文件)、链接、Sockets;文件系统安全基于i节点中的三层关键信息:UID、GID、模式;模式位,权限位的八进制数表示;设置SUID(使普通用户完成一些普通用户权限不能完成的事而设置)和SGID,体现在所有者或同组用户权限的可执行位上;chmod改变文件权限设置、chown、chgrp;unmask创建文件默认权限

    账号安全管理:/etc/passwd、/etc/shadow;伪用户账号;root账户管理:超级用户账户可不止一个,将UID和GID设置为0即可,使用可插入认证模块PAM进行认证登录

    日志与审计:日志系统:记录连接时间的日志:/var/log/wtmp、/var/run/utmp,进程统计:pacct与acct,错误日志:/var/log/messages

    Windows系统:

    Windows安全子系统:winlogon和图形化标识和验证GINA、本地安全认证、安全支持提供者的接口(SSPI)、认证包、安全支持提供者、网络登录服务、安全账号管理器(SAM)

    登录验证:Kerberos

    用户权力与权限:用户权限:目录权限、文件权限;共享权限

    日志与审计:系统日志、应用程序日志、安全日志

    安全策略:密码策略;锁定策略;审核策略;用户全力指派;安全选项;装载自定义安全模板;windows加密文件系统

    可信计算技术:

    可信计算平台联盟(TCPA),可信计算组织(TCG)

    可信PC,可新平台模块(TPM),可信软件栈(TSS),可信网络连接(TNC)

    可信平台模块(TPM):具有密码运算能力和存储能力,是一个含有密码运算部件和存储部件的小型片上系统;物理可信、管理可信的;

    可信密码模块(TCM):中国

    可信计算平台:三个层次:可信平台模块(信任根)、可信软件栈、可信平台应用软件;我国:可信密码模块、可信密码模块服务模块、安全应用

    可信网络连接(TNC):开放性、安全性

    3.2数据库安全

    3.2.1数据库安全基础

    统计数据库安全

    现代数据库运行环境:多层体系结构,中间层完成对数据库访问的封装

    数据库安全功能:

    用户标识和鉴定

    存取控制:自主存取控制:用户权限有两个要素组成:数据库对象和操作类型,GRANT语句向用户授予权限,REVOKE语句收回授予的权限,角色:权限的集合;强制存取控制:主体和客体,敏感度标记:许可证级别(主体)、密级(客体),首先要实现自主存取控制

    审计:用户级审计、系统审计;AUDIT设置审计功能,NOAUDIT取消审计功能

    数据加密

    视图与数据保密性:将视图机制与授权机制结合起来使用,首先用视图机制屏蔽一部分保密数据,然后在视图上再进一步定义存取权限

    数据完整性:

    语义完整性,参照完整性,实体完整性

    约束:优先于使用触发器、规则和默认值

    默认值:CREATEDEFAULT

    规则:CREATE RULE,USE EXEC sp_bindefault,DROP RULE

    事务处理:BEGAIN

    TRANSACTION,COMMIT,ROLLBACK;原子性、一致性、隔离性、持久性;自动处理事务、隐式事物、用户定义事物、分布式事务

    3.2.2数据库安全实践

    数据库十大威胁:

    过度的特权滥用;合法的特权滥用;特权提升;平台漏洞;SQL注入;不健全的审计;拒绝服务;数据库通信协议漏洞;不健全的认证;备份数据库暴露

    安全防护体系:事前检查,事中监控,事后审计

    数据库安全特性检查:

    端口扫描(服务发现):对数据库开放端口进行扫描;渗透测试:黑盒式的安全监测,攻击性测试,对象是数据库的身份验证系统和服务监听系统,监听器安全特性分析、用户名和密码渗透、漏洞分析;内部安全监测:安全员数据、内部审计、安全配置检查、漏洞检测、版本补丁检测

    数据库运行安全监控:网络嗅探器、数据库分析器、SQL分析器、安全审计


    第四章网络安全

    4.1网络安全基础

    4.1.1TCP/IP体系架构

    4.1.2网络协议

    数据链路层协议:地址解析协议(ARP),逆向地址解析协议(RARP)

    网络层协议:IP协议, Internet控制报文协议(ICMP):发送出错和控制消息,提供了一个错误侦测与回馈机制

    传输层协议:TCP协议,UDP协议

    应用层协议:HTTP,SMTP和POP3,DNS

    4.2网络安全威胁技术

    4.2.1扫描技术

    互联网信息搜集

    IP地址扫描:操作系统命令ping(网络故障诊断命令)、tracer,自动化的扫描工具Namp 、Superscan

    端口扫描:Namp软件;TCP全连接扫描,TCP SYN扫描,TCP FIN扫描,UDP的ICMP端口不可达扫描,ICMP扫描;乱序扫描和慢速扫描

    漏洞扫描:网络漏洞扫描:模拟攻击技术;主机漏洞扫描:漏洞特征匹配技术、补丁安装信息的检测

    弱口令扫描:基于字典攻击的弱口令扫描技术、基因穷举攻击的弱口令扫描技术

    综合漏洞扫描:Nessus

    扫描防范技术:防火墙,用安全监测工具对扫描行为进行监测

    4.2.2网络嗅探

    非主动类信息获取攻击技术

    防范:实现对网络传输数据的加密,VPN、SSL、SSH等加密和传输的技术和设备,利用网络设备的物理或者逻辑隔离的手段

    4.2.3网络协议欺骗

    IP地址欺骗:和其他攻击技术相结合

    ARP欺骗:中间人欺骗(局域网环境内实施),伪装成网关欺骗(主要针对局域网内部主机与外网通信的情况);防范:MAC地址与IP地址双向静态绑定

    TCP欺骗:将外部计算机伪装成合法计算机;非盲攻击:网络嗅探,已知目标主机的初始序列号,盲攻击:攻击者和目标主机不在同一个网络上

    DNS欺骗:基于DNS服务器的欺骗,基于用户计算机的DNS欺骗

    4.2.4诱骗式攻击

    网站挂马:

    攻击者成功入侵网站服务器,具有了网站中网页的修改权限

    技术:框架挂马:直接加在框架代码和框架嵌套挂马;JS脚本挂马;b ody挂马;伪装欺骗挂马

    防范:Web服务器,用户计算机

    诱骗下载:

    主要方式:多媒体类文件下载,网络游戏软件和插件下载,热门应用软件下载,电子书爱好者,P2P种子文件

    文件捆绑技术:多文件捆绑方式,资源融合捆绑方式,漏洞利用捆绑方式

    钓鱼网站

    社会工程

    4.2.5软件漏洞攻击利用技术

    软件漏洞:操作系统服务程序漏洞,文件处理软件漏洞,浏览器软件漏洞,其他软件漏洞

    软件漏洞攻击利用技术:直接网络攻击;诱骗式网络攻击:基于网站的诱骗式网络攻击,网络传播本地诱骗点击攻击

    4.2.6拒绝服务攻击

    实现方式:利用目标主机自身存在的拒绝服务性漏洞进行攻击,耗尽目标主机CPU和内存等计算机资源的攻击,耗尽目标主机网络带宽的攻击

    分类:IP层协议的攻击:发送ICMP协议的请求数据包,Smurf攻击;TCP协议的攻击:利用TCP本身的缺陷实施的攻击,包括SYN-Flood和ACK-Flood攻击,使用伪造的源IP地址,利用TCP全连接发起的攻击,僵尸主机;UDP协议的攻击;应用层协议的攻击:脚本洪水攻击

    分布式拒绝服务(DDos):攻击者,主控端,代理端,僵尸网络

    防范:支持DDos防御功能的防火墙

    4.2.7Web脚本攻击

    针对Web服务器端应用系统的攻击技术:

    注入攻击:SQL注入,代码注入,命令注入,LDAP注入,XPath注入;防范:遵循数据与代码分离的原则

    访问控制攻击,非授权的认证和会话攻击

    针对Web客户端的攻击技术:

    跨站脚本攻击(XSS):反射型XSS(非持久性的跨站脚本攻击),存储型XSS(持久型的跨站脚本攻击),DOM-based XSS(基于文档对象模型的跨站脚本攻击):从效果上来说属于反射型XSS

    跨站点请求伪造攻击(CSRF):伪造客户顿请求;防范:使用验证码,在用户会话验证信息中添加随机数

    点击劫持攻击

    4.2.8远程控制

    木马:

    具有远程控制、信息偷取、隐藏传输功能的恶意程序;通过诱骗的方式安装;一般没有病毒的的感染功能;特点:伪装性,隐藏性,窃密性,破坏性;

    连接方式:C/S结构;最初的网络连接方法;反弹端口技术:服务器端主动的发起连接请求,客户端被动的等待连接;木马隐藏技术:线程插入技术、DLL动态劫持技术、RootKit(内核隐藏技术)

    Wwbshell:用Web脚本写的木马后门,用于远程控制网站服务器;以ASP、PHP、ASPX、JSP等网页文件的形式存在;被网站管理员可利用进行网站管理、服务器管理等

    4.3网络安全防护技术

    4.3.1防火墙

    一般部署在网络边界,也可部署在内网中某些需要重点防护的部门子网的网络边界

    功能:在内外网之间进行数据过滤;对网络传输和访问的数据进行记录和审计;防范内外网之间的异常网络行为;通过配置NAT提高网络地址转换功能

    分类:硬件防火墙:X86架构的防火墙(中小企业),ASIC、NP架构的防火墙(电信运营商);软件防火墙(个人计算机防护)

    防火墙技术:

    包过滤技术:默认规则;主要在网络层和传输层进行过滤拦截,不能阻止应用层攻击,也不支持对用户的连接认证,不能防止IP地址欺骗

    状态检测技术(动态包过滤技术):增加了对数据包连接状态变化的额外考虑,有效阻止Dos攻击

    地址翻译技术:静态NAT,NAT池,端口地址转换PAT

    应用级网关(代理服务器):在应用层对数据进行安全规则过滤

    体系结构:

    双重宿主主机体系结构:至少有两个网络接口,在双重宿主主机上运行多种代理服务器,有强大的身份认证系统

    屏蔽主机体系结构:防火墙由一台包过滤路由器和一台堡垒主机组成,通过包过滤实现了网络层传输安全的同时,还通过代理服务器实现了应用层的安全

    屏蔽子网体系结构:由两个包过滤路由器和一台堡垒主机组成;最安全,支持网络层、传输层、应用层的防护功能;添加了额外的保护体系,周边网络(非军事区,DMZ)通常放置堡垒主机和对外开放的应用服务器;堡垒主机运行应用级网关

    防火墙的安全策略

    4.3.2入侵检测系统和入侵防御系统

    入侵检测系统(IDS):

    控制台:在内网中,探测器:连接交换机的网络端口

    分类:根据数据采集方式:基于网络的入侵检测系统(NIDS)、基于主机的入侵检测系统(HIDS);根据检测原理:误用检测型入侵检测系统、异常检测型入侵检测系统

    技术:误用检测技术:专家系统、模型推理、状态转换分析;异常检测技术:统计分析、神经网络;其他入侵检测技术:模式匹配、文件完整性检验、数据挖掘、计算机免疫方法

    体系结构:集中式结构:单一的中央控制台;分布式结构:建立树形分层结构

    部署:一个控制台可以管理多个探测器,控制台可以分层部署,主动控制台和被动控制台

    入侵防御系统(IPS):

    部署:网络设备:网络中需要保护的关键子网或者设备的网络入口处,控制台

    不足:可能造成单点故障,可能造成性能瓶颈,漏报和无保的影响

    4.3.3PKI

    公共密钥基础设施是创建、管理、存储、分布和作废数字证书的一场系列软件、硬件、人员、策略和过程的集合

    组成:数字证书是PKI的核心;安全策略;证书认证机构(CA);证书注册机构;证书分发机构;基于PKI的应用接口

    数字证书

    信任模式:单证书认证机构信任模式,层次信任模型,桥证书认证机构信任模型

    4.3.4VPN

    利用开放的物理链路和专用的安全协议实现逻辑上网络安全连接的技术

    网络连接类型:远程访问型VPN(Client-LAN)客户机和服务器都安装VPN软件;网络到网关类型的VPN(LAN-LAN)客户端和服务器各自在自己的网络边界部署硬件VPN网关设备

    VPN协议分类:网络隧道技术

    第二层隧道协:封装数据链路层数据包;介于二、三层之间的隧道协议;第三层隧道协议IPSec,通用路由封装协议(GRE);传输层的SSL VPN协议:SSL协议工作在TCP/IP和应用层之间

    4.3.5网络安全协议

    Internet安全协议(IPSec):引入加密算法、数据完整性验证和身份认证;网络安全协议:认证协议头(AH)、安全载荷封装(ESP,传输模式、隧道模式),密钥协商协议:互联网密钥交换协议(IKE)

    传输层安全协议(SSL):解决点到点数据安全传输和传输双方身份认证的网络安全传输协议;记录协议和握手协议

    应用层安全协议:

    Kerberos协议;SSH协议:加密所有传输的数据,能防止DNS欺骗和IP欺骗;安全超文本传输协议(SHTTP);安全多用途网际邮件扩充协议(S/MIME);安全电子交易协议(SET)

    展开全文
  • 基于PNG的格式 正确答案是:D 你的答案是:D 此题得分:2 33 2分 网络安全日志的数量庞大,为提高分析系统和生成报告的效率,通常将一些信息存入关系数据库,这些信息不包括( ) A.头信息;B.序号;C.消息体;D....

                                                                       试题总分:100分,时间:100分钟

                                                                                                                                                                                                     100

    NISP一级单选题(最新) (每小题2分,本题共50个小题,共100分,60及格)

    1

    2

    DES是一种使用密钥加密的块算法,其英文全称是( )

    A.Data Encryption StandardB.Dynamic Encryption StandardC.Dynamic Ellipse SystemD.Digital Ellipse System

    正确答案是:A  你的答案是:此题得分:2


    2

    2

    CIDFCommon Intrusion Detection Framework)致力于将入侵检测标准化,其全称为( )

    A.通用入侵检测框架;B.入侵检测数据标准草案;C.安全部件互动协议;D.入侵检测接口标准协议

    正确答案是:A  你的答案是:A   此题得分:2


    3

    2

    SQL是一种用于数据库访问的标准语言,具有查询、更新、管理数据库等功能,其英文全称为( )

    A.Structured Query LanguageB.Standard Query LanguageC.Security Query LanguageD.Standard Query Layer

    正确答案是:A  你的答案是:A   此题得分:2


    4

    2

    IP指网络之间互连的协议,其全称为( )

    A.Internet PositonB.Internet ProtocolC.Image ProtocolD.以上都不正确

    正确答案是:B  你的答案是:B   此题得分:2


    5

    2

    OSI把层与层之间交换的数据的单位称为SDUSDU的中文名称是( )

    A.信号数据单元;B.协议数据单元;C.服务数据单元;D.接口数据单元

    正确答案是:C  你的答案是:C   此题得分:2


    6

    2

    以下不属于数据库风险的来源的是( )

    A.超级管理用户saB.用户分配权限过小;C.启用网络协议过多;D.数据库使用默认端口

    正确答案是:B  你的答案是:B   此题得分:2


    7

    2

    RIP是一种分布式的基于距离向量的路由选择协议,它的英文全称为( )

    A.Routing Information ProtocolB.Routing Informercial ProtocolC.Routine Information ProtocolD.Routine Informercial Protocol

    正确答案是:A  你的答案是:A   此题得分:2


    8

    2

    按照数据结构来组织、存储和管理数据的仓库被称作数据库,在一个支持事务的数据库中,事务完成后,该事物对数据库做的修改将持久的保存在数据库中,这体现了数据库的哪一个性质?

    A.一致性;B.持久性;C.原子性;D.隔离性

    正确答案是:B  你的答案是:B   此题得分:2


    9

    2

    随着网络安全威胁日益凸显,人们越来越重视网络安全,其包括在网络环境中对( )提供安全防护措施

    A.信息处理及传输;B.信息存储及访问;C.信息载体;D.以上都是

    正确答案是:D  你的答案是:D   此题得分:2


    10

    2

    日志分为应用程序日志、安全日志和系统日志等,以下不属于安全日志的是?

    A.SQL Server数据库程序进行备份设定的日志;B.对系统进行登录成功信息;C.删除系统文件;D.创建系统文件

    正确答案是:A  你的答案是:A   此题得分:2


    11

    2

    以下属于网络安全设备的是?

    A.路由器;B.交换机;C.集线器;D.防火墙

    正确答案是:D  你的答案是:D   此题得分:2


    12

    2

    网络安全设备是保护网络安全的设施,以下不属于安全设备的是?

    A.防火墙 //FirewallB.虚拟专用网络 //VPN NetworkC.WEB应用防火墙 //WafD.摄像头

    正确答案是:D  你的答案是:D   此题得分:2


    13

    2

    Apache内建的有记录服务器活动的功能,以下对于Apache服务器日志叙述正确的是?

    A.其日志大致分为两类:访问日志、错误日志;B.其日志大致分为三类:访问日志、错误日志、警告日志;C.其日志只有访问日志;D.其日志只有错误日志

    正确答案是:A  你的答案是:A   此题得分:2


    14

    2

    数据库是按照数据结构储存管理数据的仓库,以下关于数据库的叙述不正确的是?

    A.一般都使用事务的工作模型运行;B.所有用户可同时存取数据库中的数据;C.OracleSqlserverApache都是数据库;D.数据库都具有事务日志

    正确答案是:C  你的答案是:C   此题得分:2


    15

    2

    数据库事务是指单个逻辑单元执行的一系列操作,以下关于事务的叙述不正确的是?

    A.事务在完成时,必须使所有的数据都保持一致状态;B.事务必须满足原子性,所封装的操作或者全做或者全不做;C.事务管理系统保证多个事务并发执行,满足ACID特性;D.数据库不必有事务日志

    正确答案是:D  你的答案是:D   此题得分:2


    16

    2

    FTP File Transfer Protocol的缩写,以下对于FTP的叙述错误的是?

    A.它是用于在网络上进行文件传输的一套标准协议;B.它使用客户/服务器工作模式;C.它只有一种传输模式;D.它用于Internet上的控制文件的双向传输

    正确答案是:C  你的答案是:C   此题得分:2


    17

    2

    采集系统日志的方法有很多,以下属于以文本方式采集系统日志方式的是?

    A.多媒体语音;B.微信;C.邮件;D.电话

    正确答案是:C  你的答案是:C   此题得分:2


    18

    2

    以下对于Web Service的叙述错误的是?

    A.是一个Web应用程序;B.不能跨平台;C.可使用开放的XML标准来描述;D.具有开放性

    正确答案是:B  你的答案是:B   此题得分:2


    19

    2

    以下对于XML的叙述错误的是?

    A.它不能实现各种数据的集成管理;B.XML严格地定义了可移植的结构化数据;C.它具有自描述性、可扩展性、层次性、异构系统间的信息互通性等特征;D.XML是一种Internet异构环境中的数据交换标准

    正确答案是:A  你的答案是:A   此题得分:2


    20

    2

    以下属于一对一递归关联的是?

    A.指同类对象之间是一对一的关系;B.指不同类对象中存在着一个实体对应关联多个实体;C.指同类实体中关联的关系是多对多;D.指同类实体中关联的关系是多对一

    正确答案是:A  你的答案是:A   此题得分:2


    21

    2

    以下属于多对多递归关联的是( )

    A.指同类对象之间是一对一的关系;B.指同一个类对象中存在着一个实体对应关联多个实体;C.指同类实体中关联的关系是多对多;D.指同类实体中关联的关系是多对一

    正确答案是:C  你的答案是:C   此题得分:2


    22

    2

    以下属于一对多递归关联的是( )

    A.指同类对象之间是一对一的关系;B.指同一个类对象中存在着一个实体对应关联多个实体;C.指同类实体中关联的关系是多对多;D.指不同类对象中存在着一个实体对应关联多个实体

    正确答案是:B  你的答案是:B   此题得分:2


    23

    2

    在关联规则的驱动下,( ) 引擎能够进行多种方式的事件关联

    A.递归关联分析;B.事件关联分析;C.统计关联分析;D.时序关联分析

    正确答案是:B  你的答案是:B   此题得分:2


    24

    2

    ( ) 技术可以更好地了解看似无关的但设备之间存在着理论相关性的关联分析

    A.递归关联;B.统计关联;C.时序关联;D.跨设备事件关联

    正确答案是:D  你的答案是:D   此题得分:2


    25

    2

    以下关于数据库数据查询描述有误的是( )

    A.普通的条件查询就是按照已知确定的条件进行查询;B.查询的功能是通过SQL语句在数据库中进行操作实现;C.用户通常需要查询表中所有数据行的信息;D.模糊查询则是通过一些已知但不完全确定的条件进行查询

    正确答案是:C  你的答案是:C   此题得分:2


    26

    2

    计算机系统一般有其相应的日志记录系统。其中,日志指系统所指定对象的某些操作和其操作结果按时间有序的集合,下列对其的叙述不正确的是( )

    A.它是由各种不同的实体产生的事件记录的集合;B.它可以记录系统产生的所有行为并按照某种规范将这些行为表达出来;C.日志信息可以帮助系统进行排错、优化系统的性能;D.日志只在维护系统稳定性方面起到非常重要的作用

    正确答案是:D  你的答案是:D   此题得分:2


    27

    2

    下列问题可能出现在原始日志信息中的有( )

    A.信息不全面;B.IP地址错误;C.重复记录;D.以上都是

    正确答案是:D  你的答案是:D   此题得分:2


    28

    2

    计算机系统一般具有相应的日志记录系统,并且其日志文件记录具有许多作用,以下关于日志文件记录功能的描述不正确的是( )

    A.可以提供监控系统资源;B.可以审计用户行为;C.可以确定入侵行为的范围;D.不能为计算机犯罪提供证据来源

    正确答案是:D  你的答案是:D   此题得分:2


    29

    2

    以下关于日志归一化的叙述中不正确的是( )

    A.它将不同格式的原始日志归一化为一种具有统一格式的日志;B.它降低了日志审计系统的审计效率;C.它方便了其他模块对日志数据的利用;D.它提高了日志数据的质量

    正确答案是:B  你的答案是:B   此题得分:2


    30

    2

    XML是可扩展标记语言的简称,以下选项中对其的描述不正确的是( )

    A.是一种用于标记电子文件的标记语言;B.具有良好的扩展性;C.具有良好的结构和约束机制;D.数据通过XML标记后表达方式更加复杂

    正确答案是:D  你的答案是:D   此题得分:2


    31

    2

    递归关联的表达方式不包括以下哪种?

    A.一对一递归关联;B.一对多递归关联;C.多对多递归关联;D.零对一递归关联

    正确答案是:D  你的答案是:D   此题得分:2


    32

    2

    日志的存储格式不包括以下哪种?

    A.基于文本的格式;B.基于二进制的格式;C.基于压缩文件的格式;D.基于PNG的格式

    正确答案是:D  你的答案是:D   此题得分:2


    33

    2

    网络安全日志的数量庞大,为提高分析系统和生成报告的效率,通常将一些信息存入关系数据库,这些信息不包括( )

    A.头信息;B.序号;C.消息体;D.分析和总结

    正确答案是:B  你的答案是:B   此题得分:2


    34

    2

    HDFSHadoop分布式文件系统的简称,被设计成适合运行于通用硬件。以下其的描述不正确的是( )

    A.HDFS的扩展性很弱;B.它是Hadoop实现的一个分布式文件系统;C.HDFS满足超大规模的数据集需求;D.HDFS支持流式的数据访问

    正确答案是:A  你的答案是:A   此题得分:2


    35

    2

    操作系统是用户和计算机的接口,同时也是计算机硬件和其他软件的接口。下列哪个选项不是计算机操作系统( )

    A.WindowsB.LinuxC.UnixD.Https

    正确答案是:D  你的答案是:D   此题得分:2


    36

    2

    以下哪个选项不属于安全开发生命周期(SDL)在实现阶段减少漏洞的措施?

    A.使用指定的工具;B.弃用不安全函数;C.不进行参数检查;D.静态分析

    正确答案是:C  你的答案是:C   此题得分:2


    37

    2

    软件保证成熟度模型(SAMM)的目标是( )

    A.创建明确定义和可衡量的目标;B.涉及到软件开发的任何业务;C.可用于小型、中型和大型组织;D.以上都是

    正确答案是:D  你的答案是:D   此题得分:2


    38

    2

    以下关于综合的轻量应用安全过程(CLASP)的描述,错误的是( )

    A.CLASP包括30个特定的活动和辅助资源;B.CLASP能够和多种软件开发模型结合使用;C.CLASP的安全活动必须是基于访问列表安排的;D.CLASP执行的安全活动及执行顺序的选择是开放的

    正确答案是:C  你的答案是:C   此题得分:2


    39

    2

    以下关于安全需求分析过程的描述,错误的是( )

    A.需求分析是一个持续的过程,跨越整个项目的生存周期;B.软件安全需求分析需要进行系统调查的过程;C.安全需求分析是一个一劳永逸的过程;D.可以采用概率统计的方法分析系统的脆弱点和安全威胁

    正确答案是:C  你的答案是:C   此题得分:2


    40

    2

    以下关于SQUARE过程模型的描述,错误的是( )

    A.使用SQUARE过程模型时,软件项目的安全开发过程不必考虑其运行环境;B.当项目发生变化时,应重新应用SQUARE过程模型分析安全需求;C.统一定义是安全需求工程的首要条件;D.专用检查方法和同行审查都可以用来检查安全需求

    正确答案是:A  你的答案是:A   此题得分:2


    41

    2

    以下关于安全关键单元的描述,错误的是( )

    A.安全关键单元的错误可能导致系统潜在严重危险;B.安全性关键单元包括产生对硬件进行自主控制信号的单元;C.安全关键的计时单元可以由程序控制,随意修改;D.安全关键单元至少受控于两个独立的单元

    正确答案是:C  你的答案是:C   此题得分:2


    42

    2

    以下关于危险建模过程的描述中,错误的是( )

    A.威胁建模有助于降低软件的攻击面;B.威胁建模可以一次性完成,不需要重复进行;C.威胁建模是一种风险管理模型;D.威胁建模在软件生命周期需求设计阶段就会介入

    正确答案是:B  你的答案是:B   此题得分:2


    43

    2

    缓解威胁常用的技术手段不包括( )

    A.验证系统输入;B.增大攻击面;C.进行模糊测试;D.采用访问控制手段

    正确答案是:B  你的答案是:B   此题得分:2


    44

    2

    缓冲区溢出作为一种较为普遍以及危害性较大的漏洞,在各操作系统以及应用软件中广泛存在,下列选项中对其描述错误的是( )

    A.缓冲区是存储数据的一组地址连续的内存单元;B.缓冲区溢出在软件的开发和测试阶段一定可以发现;C.并非所有的缓冲区溢出都会造成软件漏洞;D.著名的心脏流血漏洞是缓冲区漏洞

    正确答案是:B  你的答案是:B   此题得分:2


    45

    2

    C语言作为一种计算机编程语言,获得广泛应用。下列选项中对其描述错误的是( )

    A.C语言是面向对象的开发语言;B.C语言拥有强大的操控内存的能力;C.C语言可以应用于操作系统、浏览器和嵌入式开发等领域;D.C语言拥有强大的底层操作能力

    正确答案是:A  你的答案是:A   此题得分:2


    46

    2

    Java作为一种计算机编程语言,功能非常强大。以下不属于Java的特点的是( )

    A.跨平台;B.多线程;C.面向过程;D.面向对象

    正确答案是:C  你的答案是:C   此题得分:2


    47

    2

    以下关于路径遍历的描述,错误的是( )

    A.路径遍历漏洞允许攻击者访问受限的目录,获取系统文件及服务器的配置文件;B.Web服务器提供访问控制列表和根目录访问的安全机制;C.使用GET或是POST的请求方法可以获得输入;D.路径遍历漏洞没有任何危害

    正确答案是:D  你的答案是:D   此题得分:2


    48

    2

    哈希算法之所以被认为安全,主要是基于以下哪两种性质?

    A.无冲突和不可逆;B.冲突性和不可逆;C.冲突性和随机性;D.随机性和可逆性

    正确答案是:A  你的答案是:A   此题得分:2


    49

    2

    下列选项中关于Java语言的异常处理机制描述错误的是( )

    A.可以根据catch程序段的上下文抛出另一个适合的异常;B.在异常传递的过程中,应该对敏感信息进行过滤;C.尽量要在finally程序段非正常退出;D.记录日志时应避免异常

    正确答案是:C  你的答案是:C   此题得分:2


    50

    2

    下列选项中关于Java语言线程的描述,错误的是( )

    A.多线程是Java语言的特性之一;B.良好的线程调度,有助于发挥系统的性能;C.Thread Group中所有方法都是安全的,提倡使用;D.调用Threadstart方法可启动一个新线程

    正确答案是:C  你的答案是:C   此题得分:2

    展开全文
  • android多媒体框架学习

    千次阅读 2014-02-07 14:50:25
    Multimedia Framework overview(多媒体框架概述)--base on jellybean(一) jellybean 的多媒体跟以前的版本,通过对比没啥变化,最大的变化是google终于舍得给multimedia建个独立的git了(framework/av),等...

    一:多媒体框架概述


         jellybean 的多媒体跟以前的版本,通过对比没啥变化,最大的变化是google终于舍得给multimedia建个独立的git了(framework/av),等你好久了!也体现了media 在整个android系统中的重要性!framework/av下都是些C/C++代码(libmedia,libmediaplayerservice,libstagefright),jni和 java api 还是保留在原来的位置,改革还不够彻底,但还是迈出了这一步,以后维护能更好的进行了!但是对于从ics往jellybean升级就得费点劲了,打patch不好打了!还有一个大的变化时增加了可以直接调用codec的API,不需要通过stagefrigh引擎去调用,就像我们直接调用FFMPEG的codec一样,方便简单,不用绕那么多弯。具体的往后我们再具体了解吧,毕竟刚有的!
         android multimedia Framework 整体架构是一个很庞大的系统,我们该如何划分和去研究呢?大的分法就是video和audio。往细的分呢?也是我接下来要按顺序讲的:
        video 部分:
            1:video playback
            2:video streaming
            3:video recorder
         audio部分:
           1:audio playback
            2:audio streaming
            3:sound recorder
            4:audio flinger



    二:多媒体简介

         

    我们学习一种新事物必然首先都要对该事物要有个大体的了解,熟悉它的整体架构,然后进行划分归类,接下来才是各个击破,逐步学习乃至掌握。对于要学习android Multimedia的人来说也是如此,先来个总括吧 !我打算分三部分来讲解,请听我娓娓道来....
    1、多媒体简介
         为啥要讲多媒体的概念呢?可能很多人都对这个名称解释不怎么了解,所以在这普及普及。
         媒体(Media)就是人与人之间实现信息交流的中介,简单地说,就是信息的载体,也称为媒介。多媒体是计算机和视频技术的结合,实际上它是两个媒体;声音和图像,或者用现在的术语:音响和电视。多媒体本身有两个方面,和所有现代技术一样它是由硬件和软件,或机器和思想混合组成。可以将多媒体技术和功能在概念上区分为控制系统和信息。多媒体之所以能够实现是依靠数字技术。多媒体代表数

    字控制和数字媒体的汇合,电脑是数字控制系统,而数字媒体是当今音频和视频最先进的存储和传播形式。事实上有人就简单地认为多媒体是电脑和电视的结合。电脑的能力达到实时处理电视和声音数据流的水平,这时多媒体就诞生了。
          2、android多媒体框架演变历史
           android 的多媒体框架从android诞生以来,发生了天翻地覆的变化,包括引擎的更改,单独处理流媒体的播放器nuplayer的加入,到最新jellybean(android4.1)nuplayer逐步加入stagefrightplayer的功能,可能以后stagefight引擎会被nuplayer取代,那都是后话了。但是openomx(即引擎连接codec的纽带)一直都得到了保持。
           在Froyo2.2 以前,multimedia framework 的引擎是一直都是opencore,但为啥用stagefright替代呢,由于我没有开发过opencore,不便下结论,但从网上一些言论来看,估计是opencore太过庞大,不太好维护,具体真正原因就得问google了,如果你知道具体原因,可以给我留言,在此多谢了!
           Gingerbread  android2.3,加入了真正的支持流媒体的播发器nuplayer,如果你下有源码,可以用gitk \nuplayer,从gitk可以看到如下提交:Initial support for a true streaming player for mpeg2 transport streams. 2010.12。
          android 3.0 到android 4.0 ,总体框架没有多大变化。
          android4.1 (jellybean) 最大的变化是给c/c++部分的多媒体框架单独设立了一个framework/av的目录,给它开辟了一个git库,即从framework/base下的git库分离了出来,总算给多媒体找了个港湾。 
           3、jellybean多媒体架构
            multimedia framework 架构 由三大部分构成:供上层程序调用的java API,连接java和C/C++的jni部分,多媒体引擎(stagefright)和codec接口(openmax interface)。前面两部代码在framework/base/media 下,后一部分在framework/av文件夹下。如果你修改的是java API接口或加LOG后编译可以用如下命令:make framework ,JNI 部分 make media_jni,第三部分有三个libs组成:libmedia ,libmediaplayerservice, libstagefright,命令如下 make media ,make stagefright , make mediaplayerservice. 生成各自的.so文件,用adb push 到system/下就可以调试了。记得重启!讲了好多废话,还是没有看到总体架构,罪过,好吧,上图,更直观。
     


          

     

     


          从上两图,我们可以发现上层APK要播放视频,首先得获得一个player,而这个player的类型根据你媒体文件的类型来决定的,分配的任务由mediaplayerservice来完成,除了获得player外最主要的是到底选用哪种编码器进行编解码,这个过程由awesomeplayer和omxcodec来完成,至于声音和图像就交由audioflinger和surfaceflinger来完成了。具体的调用实现,下一篇videoplayerback将会慢慢讨论和学习。



    三:播放流程video playback(一)    

     

           上一篇我们讲了多媒体的总体框架,本章我们先来讨论媒体文件的本地播放,也是手机的基本功能。现在市面上的手机配置越来越高,支持高清视频(1920x1080P)已不在话下。那现在android主流播放器都支持哪些媒体格式呢?一般来说mp3,mp4,m4a,m4v,amr等大众格式都是支持的,具体支持成什么样这得看手机厂商和芯片厂商了。具体格式大全可以看framework/base/media/java/android/media/MediaFile.java。

          我们下面进入正题研究多媒体文件的本地播放(video playback),具体用到的工具有source insight,astah(免费的画流程图工具),android 4.1代码。代码如何获取可以到google source下下载:http://source.android.com/source/downloading.html

          一般上层应用要本地播放播放一个媒体文件,需要经过如下过程: 

        

    MediaPlayer  mMediaPlayer = new MediaPlayer( );

    mMediaPlayer.setDataSource(mContext, mUri);-

    mMediaPlayer.setDisplay(mSurfaceHolder);

    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

    mMediaPlayer.prepareAsync();

    mMediaPlayer.start();

     

      

        首先我们先来分析setDataSource方法,这个方法有两个功能:一个是根据文件类型获得相应的player,一个是创建相应文件类型的mediaExtractor,解析媒体文件,记录metadata的主要信息。

    代码如下:

    framework/av/media/libmedia/ MediaPlayer.cpp

    status_t MediaPlayer::setDataSource(

            const char *url, const KeyedVector<String8, String8> *headers)

    {

        ALOGV("setDataSource(%s)", url);

        status_t err = BAD_VALUE;

        if (url != NULL) {

            const sp<IMediaPlayerService>& service(getMediaPlayerService());

            if (service != 0) {

                sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));

                if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||

                    (NO_ERROR != player->setDataSource(url, headers))) {

                    player.clear();

                }

                err = attachNewPlayer(player);

            }

        }

        return err;

    }


        我们先来看下setDataSource方法中如何获取player。大体的流程图如下图:

     

        我们知道MediaplayerService是负责外部请求,针对每个APP player ,mediaplayerservice都会开辟一个client端来专门处理。

      client 定义如下:

    framework/av/media/libmediaplayerservice/ MediaPlayerService.h

     class Client : public BnMediaPlayer {...

     

     private:

            friend class MediaPlayerService;

                                    Client( const sp<MediaPlayerService>& service,

                                            pid_t pid,

                                            int32_t connId,

                                            const sp<IMediaPlayerClient>& client,

                                            int audioSessionId,

                                            uid_t uid);

    }

    }

     

      从代码看就是一个BnMediaplayer的子类(即local binder)。既然有了BnMediaplayer,客户端也应该有相应的BpMediaplayer。获取这个BpMediaplayer要分两步骤走:第一,获取BpMediaplayerService;第二就是在setDataSource方法中的:

        sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId)); 这个函数会返回一个BpMediaplayer。

      获取BpMediaplayerService,首先要去ServiceManager获取相应的服务Mediaplayer,里面的流程是这样检查是否存在要找的service,没有就创建,有就返回BpXX。

      有了BpMediaplayerService,我们就可以跟MediaplayerService通信了,自然就可以创建对应的client端来服务对应的BpMediaplayer(客户端):

     

    framework/av/media/libmediaplayerservice/ MediaPlayerService.cpp

    sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClient>& client,

            int audioSessionId)

    {

        int32_t connId = android_atomic_inc(&mNextConnId);

        sp<Client> c = new Client(

                this, pid, connId, client, audioSessionId,

                IPCThreadState::self()->getCallingUid());

     

        ALOGV("Create new client(%d) from pid %d, uid %d, ", connId, pid,

             IPCThreadState::self()->getCallingUid());

     

        wp<Client> w = c;

        {

            Mutex::Autolock lock(mLock);

            mClients.add(w);

        }

        return c;

    }

    到此我们的sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));

    变成了:sp<IMediaPlayer> player(Client); 那它是如何变成我们需要的BpMediaplayer呢,请看下面的定义原型,INTERFACE就是mediaplayer,大伙把宏取代下就知道了:

      frameworks/av/media/include/IMediapalyer.h

    class IMediaPlayer: public IInterface

    {

    public:

        DECLARE_META_INTERFACE(MediaPlayer);

    DECLARE_META_INTERFACE宏定义如下:

        #define DECLARE_META_INTERFACE(INTERFACE)                               \

        static const android::String16 descriptor;                          \

        static android::sp<I##INTERFACE> asInterface(                       \

                const android::sp<android::IBinder>& obj);                  \

        virtual const android::String16& getInterfaceDescriptor() const;    \

        I##INTERFACE();                                                     \

        virtual ~I##INTERFACE();  


    有了DECLARE IMediaplayer.cpp 必有 IMPLEMENT

     

    #define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \

        const android::String16 I##INTERFACE::descriptor(NAME);             \

        const android::String16&                                            \

                I##INTERFACE::getInterfaceDescriptor() const {              \

            return I##INTERFACE::descriptor;                                \

        }                                                                   \

        android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \

                const android::sp<android::IBinder>& obj)                   \

        {                                                                   \

            android::sp<I##INTERFACE> intr;                                 \

            if (obj != NULL) {                                              \

                intr = static_cast<I##INTERFACE*>(                          \

                    obj->queryLocalInterface(                               \

                            I##INTERFACE::descriptor).get());               \

                if (intr == NULL) {                                         \

                    intr = new Bp##INTERFACE(obj);                          \

                }                                                           \

            }                                                               \

            return intr;                                                    \

        }                                                                   \

        I##INTERFACE::I##INTERFACE() { }                                    \

        I##INTERFACE::~I##INTERFACE() { }                                   \


       通过如上方法 ,我们获得了BpMediaplayer(remoteBinder),我们就可以通过BpMediaplayer 跟BnMediaplayer通信了。两者的交互是IBinder。

    BpMediaplayer具体实现在哪呢?

    frameworks/av/media/libmedia/IMediaplayer.cpp:

     

    class BpMediaPlayer: public BpInterface<IMediaPlayer>

    {

    public:

        BpMediaPlayer(const sp<IBinder>& impl)

            : BpInterface<IMediaPlayer>(impl)

        {

        }

     

        // disconnect from media player service

        void disconnect()

        {

            Parcel data, reply;

            data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());

            remote()->transact(DISCONNECT, data, &reply);

        }

     

        status_t setDataSource(const char* url,

                const KeyedVector<String8, String8>* headers)

        {

            Parcel data, reply;

            data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());

            data.writeCString(url);

            if (headers == NULL) {

                data.writeInt32(0);

            } else {

                // serialize the headers

                data.writeInt32(headers->size());

                for (size_t i = 0; i < headers->size(); ++i) {

                    data.writeString8(headers->keyAt(i));

                    data.writeString8(headers->valueAt(i));

                }

            }

            remote()->transact(SET_DATA_SOURCE_URL, data, &reply);

            return reply.readInt32();

        }

     

    remote 就是一个IBinder, IBinder 通过transact 方法中的

    IPCThreadState::self()->transact(

                mHandle, code, data, reply, flags);

     

    通知相应BnMediaplayer(client)进行相应的处理。里面的如何打开binder,如何传到MediaplayerService::client就不具体说了,有兴趣可以跟下去看看。

     

    以上我们运用到了Binder的通信机制,如果大家对此不太了解可以看:

    Android系统进程间通信(IPC)机制Binder中的ServerClient获得Service Manager接口之路 .

     

    获得了BpMediaplayer ,我们就可以通过调用client端的setDataSource创建 player了:

     

    status_t MediaPlayerService::Client::setDataSource(

            const char *url, const KeyedVector<String8, String8> *headers)

    {….

    如果是url 以content://开头要转换为file descriptor

        if (strncmp(url, "content://", 10) == 0) {…

            int fd = android::openContentProviderFile(url16);

    ……….

            setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus

            close(fd);

            return mStatus;

        } else {

            player_type playerType = getPlayerType(url);…. createplayer前要判断是哪种类型

            LOGV("player type = %d", playerType);

     

     

            // create the right type of player

            sp<MediaPlayerBase> p = createPlayer(playerType);

            mStatus = p->setDataSource(url, headers);

    …       

    return mStatus;

        }

    }

    player_type getPlayerType(const char* url) ……………. 根据url的后缀名判断属于哪种playerType,默认是stagefright,我们现在研究的是本地播放,自然是stagefrightPlayer了

    {

        if (TestPlayerStub::canBeUsed(url)) {

            return TEST_PLAYER;

        }

     

     

        // use MidiFile for MIDI extensions

        int lenURL = strlen(url);

        for (int i = 0; i < NELEM(FILE_EXTS); ++i) {

            int len = strlen(FILE_EXTS[i].extension);

            int start = lenURL - len;

            if (start > 0) {

                if (!strncasecmp(url + start, FILE_EXTS[i].extension, len)) {

                    return FILE_EXTS[i].playertype;

                }

            }

        }

    ……………….

        return getDefaultPlayerType();

    }

     

     自此我们获得了想要的player了。这里最主要的知识点就是Binder的通信了,Binder的流程我们可以用下图来解释,大家可以好好琢磨:

     

     

    player已经取得,接下来就是setDataSource的第二步:获取相应的MediaExtractor并储存相应的数据。

     

    关于这一步,我也画了个时序图:

      

        紧接刚才我们获得player的步骤,我们实例话一个stagefrightPlayer的同时也实例话了一个AwesomePlayer,其实真正干实事的AwesomePlayer,stagefrightPlayer只是个对外的接口,

     代码如下:framework/av/media/libmediaplayerservice/ StagefrightPlayer.cpp

     

    static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,

            notify_callback_f notifyFunc) {

    …..

               case STAGEFRIGHT_PLAYER:

                ALOGV(" create StagefrightPlayer");

                p = new StagefrightPlayer;

                break;

    …….

    }

     

    创建stagefrightplayer实例也new了个AwesomePlayer(mPlayer)

    StagefrightPlayer::StagefrightPlayer()

        : mPlayer(new AwesomePlayer) {

        LOGV("StagefrightPlayer");

     

     

        mPlayer->setListener(this);

    }


     

    既然Awesomeplayer是干实事的,我们直接进去看看吧:

     

    frameworks/av/media/libstagefright/AwesomePlayer.cpp

    status_t AwesomePlayer::setDataSource_l(

            const sp<DataSource> &dataSource) {

        sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);…….创建对应的extractor

    …..

        return setDataSource_l(extractor);

    }

    status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {

    for (size_t i = 0; i < extractor->countTracks(); ++i) {

            sp<MetaData> meta = extractor->getTrackMetaData(i);.......获取相应track的元数据

            int32_t bitrate;

            if (!meta->findInt32(kKeyBitRate, &bitrate)) {

                const char *mime;

                CHECK(meta->findCString(kKeyMIMEType, &mime));

                ALOGV("track of type '%s' does not publish bitrate", mime);

     

                totalBitRate = -1;

                break;

            }

     

            totalBitRate += bitrate;

        }

    .........

            if (!haveVideo && !strncasecmp(mime, "video/", 6)) {

                setVideoSource(extractor->getTrack(i)); ………>mVideoTrack

                haveVideo = true;

            } else if (!haveAudio && !strncasecmp(mime, "audio/", 6)) {

                setAudioSource(extractor->getTrack(i));……….>mAudioTrack

                haveAudio = true;

        return OK;

    }


          关于MediaExtractor里面涉及到媒体文件格式的很多内容,比如track的构成,有几种track等等,我们将来在videoRecorder中再详细讲解。这里只有知道提取相关信息就行了。

        此方法调用完成意味着player进入了MEDIA_PLAYER_INITIALIZED状态。Player的状态有如下几种:   

        MEDIA_PLAYER_STATE_ERROR
        MEDIA_PLAYER_IDLE 
        MEDIA_PLAYER_INITIALIZED 
        MEDIA_PLAYER_PREPARING 
        MEDIA_PLAYER_PREPARED
        MEDIA_PLAYER_STARTED
        MEDIA_PLAYER_PAUSED
        MEDIA_PLAYER_STOPPED
        MEDIA_PLAYER_PLAYBACK_COMPLETE

         setDataSource我们已经讲完了,讲流程我们的目的是熟悉它的架构,希望大家很好好熟悉熟悉,在项目需要的时候根据我们自己的媒体格式,依葫芦画瓢进行改造,比如说支持多track,切换track,以达到KTV的功能等等。。。

        下一篇我们将讲解prepare的过程,这个工程主要是匹配codec,初始化codec等。



      码率:也叫比特率,表示经过压缩编码后的视音频数据每秒需要用多少个比特来表示,即把每秒显示的图像进行压缩后的数据量,一般采用的单位是kbps即千位每秒。一般来说码率越大,处理出来的文件就越接近原始文件,但文件体积与码率是成正比的,所以几乎所有的编码格式重视的都是如何用最低的码率达到最少的失真,围绕这个核心衍生出来的CBR(固定码率)与VBR(动态码率)。

             固定码率CBR(Constant Bitrate):指文件从头高位都是一种码率,这是以固定文件大小为前提的压缩方式。

      动态码率VBR(Variable Bitrate):指没有固定的码率,压缩时根据视音频数据即时确定使用什么码率,这是以质量为前提兼顾文件大小的压缩方式。

           【文件大小】(Byte字节)=【码率】(kbps)/8X【时间】(秒)

          1 byte (B) = 8 bits (b),我们计算机上文件的容量K/M,都是指B;

       1 Kilobyte(K/KB)=2^10 bytes=1,024 bytes 千字节 ;

       1 Megabyte(M/MB)=2^20 bytes=1,048,576 bytes 兆字节;

      所以如果用的bits/s的码流计算容量记得要乘8。

     

          视频分辨率:我们常说的视频多少乘多少,严格来说不是分辨率,而是视频的高/宽像素值。常见的屏幕比例其实只有三种:4:3、16:9和 16:10。

     

           采样率:(也称为采样速度或者采样频率)定义了每秒从连续信号中提取并组成离散信号的采样个数,单位用赫兹(Hz)来表示。采样频率的倒数是采样周期(也称为采样时间),它表示采样之间的时间间隔。

                

            帧率(Frame rate):是用于测量显示帧数的量度。所谓的测量单位为每秒显示帧数(Frames per Second,简称:FPS)或“赫兹”(Hz)。高的帧率可以得到更流畅、更逼真的动画。一般来说30fps就是可以接受的,但是将性能提升至60fps则可以明显提升交互感和逼真感,但是一般来说超过75fps一般就不容易察觉到有明显的流畅度提升了。

             刷新频率:即屏幕刷新的速度。刷新频率越低,图像闪烁和抖动的就越厉害,眼睛疲劳得就越快。

      采用70Hz以上的刷新频率时才能基本消除闪烁,显示器最好稳定工作在允许的最高频率下,一般是85Hz。



    四:播放流程video playback(二)


    上一篇我们讲了mediaplayer播放的第一步骤setdataSource,下面我们来讲解preparesync的流程,在prepare前我们还有setDisplay这一步,即获取surfacetexture来进行画面的展示

    setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)

    {

        sp<MediaPlayer> mp = getMediaPlayer(env, thiz);

    ………

        sp<ISurfaceTexture> new_st;

        if (jsurface) {

            sp<Surface> surface(Surface_getSurface(env, jsurface));

            if (surface != NULL) {

                new_st = surface->getSurfaceTexture();

                ---通过surface获取surfaceTexture

                new_st->incStrong(thiz);

    ……….

        }………….

        mp->setVideoSurfaceTexture(new_st);

    }

     

    为什么用surfaceTexture不用surface来展示呢?ICS之前都用的是surfaceview来展示video或者openGL的内容,surfacaview render在surface上,textureview render在surfaceTexture,textureview和surfaceview 这两者有什么区别呢?surfaceview跟应用的视窗不是同一个视窗,它自己new了一个window来展示openGL或者video的内容,这样做有一个好处就是不用重绘应用的视窗,本身就可以不停的更新,但这也带来一些局限性,surfaceview不是依附在应用视窗中,也就不能移动、缩放、旋转,应用ListView或者 ScrollView就比较费劲。Textureview就很好的解决了这些问题。它拥有surfaceview的一切特性外,它也拥有view的一切行为,可以当个view使用。

     

    获取完surfaceTexture,我们就可以prepare/prepareAsync了,先给大伙看个大体时序图吧:

     

    JNI的部分我们跳过,直接进入libmedia下的mediaplayer.cpp的 prepareAsync_l方法,prepare是个同步的过程,所以要加锁,prepareAsync_l后缀加_l就是表面是同步的过程。

    status_t MediaPlayer::prepareAsync_l()

    {

        if ( (mPlayer != 0) && ( mCurrentState & ( MEDIA_PLAYER_INITIALIZED | MEDIA_PLAYER_STOPPED) ) ) {

            mPlayer->setAudioStreamType(mStreamType);

            mCurrentState = MEDIA_PLAYER_PREPARING;

            return mPlayer->prepareAsync();

        }

        ALOGE("prepareAsync called in state %d", mCurrentState);

        return INVALID_OPERATION;

    }

     

    在上面的代码中,我们看到有个mPlayer,看过前一章的朋友都会记得,就是我们从Mediaplayerservice获得的BpMediaplayer.通过BpMediaplayer我们就可以长驱直入,直捣Awesomeplayer这条干实事的黄龙,前方的mediaplayerservice:client和stagefrightplayer都是些通风报信的料,不值得我们去深入研究,无非是些接口而已。进入了prepareAsync_l方法,我们的播放器所处的状态就是MEDIA_PLAYER_PREPARING了。好了,我们就来看看Awesomeplayer到底做了啥吧.

    代码定位于:frameworks/av/media/libstagefright/Awesomeplayer.cpp

    先看下prepareAsync_l吧:

    status_t AwesomePlayer::prepareAsync_l() {

        if (mFlags & PREPARING) {

            return UNKNOWN_ERROR;  // async prepare already pending

        }

     

        if (!mQueueStarted) {

            mQueue.start();

            mQueueStarted = true;

        }

     

        modifyFlags(PREPARING, SET);

        mAsyncPrepareEvent = new AwesomeEvent(

                this, &AwesomePlayer::onPrepareAsyncEvent);

     

        mQueue.postEvent(mAsyncPrepareEvent);

     

        return OK;

    }

     

      这里我们涉及到了TimeEventQueue,即时间事件队列模型,Awesomeplayer里面类似Handler的东西,它的实现方式是把事件响应时间和事件本身封装成一个queueItem,通过postEvent 插入队列,时间到了就会根据事件id进行相应的处理。

      首先我们来看下TimeEventQueue的start(mQueue.start();)方法都干了什么:

    frameworks/av/media/libstagefright/TimedEventQueue.cpp

    void TimedEventQueue::start() {

        if (mRunning) {

            return;

        }

    ……..

     

        pthread_create(&mThread, &attr, ThreadWrapper, this);

     

    ………

    }

     

    目的很明显就是在主线程创建一个子线程,可能很多没有写过C/C++的人对ptread_create这个创建线程的方法有点陌生,我们就来分析下:

    int pthread_create(pthread_t *thread, pthread_addr_t *arr,

               void* (*start_routine)(void *), void *arg);

     

     thread   :用于返回创建的线程的ID

    arr       : 用于指定的被创建的线程的属性

    start_routine   : 这是一个函数指针,指向线程被创建后要调用的函数

    arg      : 用于给线程传递参数

    分析完了,我们就看下创建线程后调用的函数ThreadWrapper吧:

    // static

    void *TimedEventQueue::ThreadWrapper(void *me) {

    ……

        static_cast<TimedEventQueue *>(me)->threadEntry();

     

        return NULL;

    }

    跟踪到threadEntry:

    frameworks/av/media/libstagefright/TimedEventQueue.cpp

    void TimedEventQueue::threadEntry() {

        prctl(PR_SET_NAME, (unsigned long)"TimedEventQueue", 0, 0, 0);

     

        for (;;) {

            int64_t now_us = 0;

            sp<Event> event;

     

            {

                Mutex::Autolock autoLock(mLock);

     

                if (mStopped) {

                    break;

                }

     

                while (mQueue.empty()) {

                    mQueueNotEmptyCondition.wait(mLock);

                }

     

                event_id eventID = 0;

                for (;;) {

                    if (mQueue.empty()) {

                        // The only event in the queue could have been cancelled

                        // while we were waiting for its scheduled time.

                        break;

                    }

     

                    List<QueueItem>::iterator it = mQueue.begin();

                    eventID = (*it).event->eventID();

    ……………………………

                    static int64_t kMaxTimeoutUs = 10000000ll; // 10 secs

                    ……………..

                    status_t err = mQueueHeadChangedCondition.waitRelative(

                            mLock, delay_us * 1000ll);

     

                    if (!timeoutCapped && err == -ETIMEDOUT) {

                        // We finally hit the time this event is supposed to

                        // trigger.

                        now_us = getRealTimeUs();

                        break;

                    }

                }

    ……………………….

                event = removeEventFromQueue_l(eventID);

            }

     

            if (event != NULL) {

                // Fire event with the lock NOT held.

                event->fire(this, now_us);

            }

        }

    }

     

    从代码我们可以了解到,主要目的是检查queue是否为空,刚开始肯定是为空了,等待队列不为空时的条件成立,即有queueIten进入进入队列中。这个事件应该就是

        mQueue.postEvent(mAsyncPrepareEvent);

    在讲postEvent前,我们先来看看mAsyncPrepareEvent这个封装成AwesomeEvent的Event。

    struct AwesomeEvent : public TimedEventQueue::Event {

        AwesomeEvent(

                AwesomePlayer *player,

                void (AwesomePlayer::*method)())

            : mPlayer(player),

              mMethod(method) {

        }

    从这个结构体我们可以知道当这个event被触发时将会执行Awesomeplayer的某个方法,我们看下mAsyncPrepareEvent:

    mAsyncPrepareEvent = new AwesomeEvent(

                this, &AwesomePlayer::onPrepareAsyncEvent);

     

    mAsyncPrepareEvent被触发时也就触发了onPrepareAsyncEvent方法。

    好了,回到我们的postEvent事件,我们开始说的TimeEventQueue,即时间事件队列模型,刚刚我们说了Event, 但是没有看到delay time啊?会不会在postEvent中加入呢?跟下去看看:

    TimedEventQueue::event_id TimedEventQueue::postEvent(const sp<Event> &event) {

        // Reserve an earlier timeslot an INT64_MIN to be able to post

        // the StopEvent to the absolute head of the queue.

        return postTimedEvent(event, INT64_MIN + 1);

    }

    终于看到delay时间了INT64_MIN + 1。重点在postTimedEvent,它把post过来的event和时间封装成queueItem加入队列中,并通知Queue为空的条件不成立,线程解锁,允许thread继续进行,经过delay time后pull event_id所对应的event。

    frameworks/av/media/libstagefright/TimedEventQueue.cpp

    TimedEventQueue::event_id TimedEventQueue::postTimedEvent(

            const sp<Event> &event, int64_t realtime_us) {

        Mutex::Autolock autoLock(mLock);

     

        event->setEventID(mNextEventID++);

     

        ………………….

        QueueItem item;

        item.event = event;

        item.realtime_us = realtime_us;

     

        if (it == mQueue.begin()) {

            mQueueHeadChangedCondition.signal();

        }

     

        mQueue.insert(it, item);

     

        mQueueNotEmptyCondition.signal();

     

        return event->eventID();

    }

     

    到此,我们的TimeEventQueue,即时间事件队列模型讲完了。实现机制跟handle的C/C++部分类似。

    在我们setdataSource实例化Awesomeplayer的时候,我们还顺带创建了如下几个event

        sp<TimedEventQueue::Event> mVideoEvent;

        sp<TimedEventQueue::Event> mStreamDoneEvent;

        sp<TimedEventQueue::Event> mBufferingEvent;

        sp<TimedEventQueue::Event> mCheckAudioStatusEvent;

        sp<TimedEventQueue::Event> mVideoLagEvent;

     

    具体都是实现了什么功能呢?我们在具体调用的时候再深入讲解。   

    接下来我们就来讲讲onPrepareAsyncEvent方法了。

    frameworks/av/media/libstagefight/AwesomePlayer.cpp

    void AwesomePlayer::onPrepareAsyncEvent() {

        Mutex::Autolock autoLock(mLock);

    …………………………

        if (mUri.size() > 0) {

            status_t err = finishSetDataSource_l();----这个不会走了,如果是本地文件的话

    …………………………

        if (mVideoTrack != NULL && mVideoSource == NULL) {

            status_t err = initVideoDecoder();-----------如果有videotrack初始化video的解码器

    …………………………

        if (mAudioTrack != NULL && mAudioSource == NULL) {

            status_t err = initAudioDecoder();---------------如果有audiotrack初始化audio解码器

    ……………………..

        modifyFlags(PREPARING_CONNECTED, SET);

     

        if (isStreamingHTTP()) {

            postBufferingEvent_l(); ------一般不会走了

        } else {

            finishAsyncPrepare_l();----------对外宣布prepare完成,并从timeeventqueue中移除该queueitem,mAsyncPrepareEvent=null

        }

    }

     

    我们终于知道prepare主要目的了,根据类型找到解码器并初始化对应的解码器。那我们首先就来看看有videotrack的媒体文件是如何找到并初始化解码器吧。

    先看图吧,了解大概步骤:

    看完图就开讲了:

    iniVideoDecoder目的是初始化解码器,取得已解码器的联系,解码数据输出格式等等。

    frameworks/av/media/libstagefright/Awesomeplayer.cpp

    status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {

    …………

    mVideoSource = OMXCodec::Create(

                mClient.interface(), mVideoTrack->getFormat(),

                false, // createEncoder

                mVideoTrack,

                NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);

    …………..

     

      status_t err = mVideoSource->start();

    }

    我们先来看create函数到底干了啥吧:

    frameworks/av/media/libstagefright/OMXCodec.cpp

    sp<MediaSource> OMXCodec::Create(

            const sp<IOMX> &omx,

            const sp<MetaData> &meta, bool createEncoder,

            const sp<MediaSource> &source,

            const char *matchComponentName,

            uint32_t flags,

            const sp<ANativeWindow> &nativeWindow) {

    …………..

    bool success = meta->findCString(kKeyMIMEType, &mime);

        ……………

       (1) findMatchingCodecs(

                mime, createEncoder, matchComponentName, flags,

                &matchingCodecs, &matchingCodecQuirks);

    ……….

    (2)  sp<OMXCodecObserver> observer = new OMXCodecObserver;

       (3) status_t err = omx->allocateNode(componentName, observer, &node);

     

    ……….

       (4) sp<OMXCodec> codec = new OMXCodec(

                        omx, node, quirks, flags,

                        createEncoder, mime, componentName,

                        source, nativeWindow);

     

              (5)  observer->setCodec(codec);

     

                (6)err = codec->configureCodec(meta);

     

    …………

    }

     

     

    首先看下findMatchingCodecs原来是根据mimetype找到匹配的解码组件,android4.1的寻找组件有了很大的变化,以前都是把codecinfo都写在代码上了,现在把他们都放到media_codec.xml文件中,full build 后会保存在“/etc/media_codecs.xml”,这个xml由各个芯片厂商来提供,这样以后添加起来就很方便,不用改代码了。一般是原生态的代码都是软解码。解码器的匹配方式是排名制,因为一般厂商的配置文件都有很多的同类型的编码器,谁排前面就用谁的。

    frameworks/av/media/libstagefright/OMXCodec.cpp

    void OMXCodec::findMatchingCodecs(

            const char *mime,

            bool createEncoder, const char *matchComponentName,

            uint32_t flags,

            Vector<String8> *matchingCodecs,

            Vector<uint32_t> *matchingCodecQuirks) {

    …………

    const MediaCodecList *list = MediaCodecList::getInstance();

    ………

    for (;;) {

            ssize_t matchIndex =

                list->findCodecByType(mime, createEncoder, index);

    ………………..

            matchingCodecs->push(String8(componentName));

    …………….

    }

    frameworks/av/media/libstagefright/MediaCodecList.cpp

    onst MediaCodecList *MediaCodecList::getInstance() {

       ..

        if (sCodecList == NULL) {

            sCodecList = new MediaCodecList;

        }

     

        return sCodecList->initCheck() == OK ? sCodecList : NULL;

    }

     

    MediaCodecList::MediaCodecList()

        : mInitCheck(NO_INIT) {

        FILE *file = fopen("/etc/media_codecs.xml", "r");

     

        if (file == NULL) {

            ALOGW("unable to open media codecs configuration xml file.");

            return;

        }

     

        parseXMLFile(file);

    }

     

    有了匹配的componentName,我们就可以创建ComponentInstance,这由allocateNode方法来实现。

    frameworks/av/media/libstagefright/omx/OMX.cpp

    status_t OMX::allocateNode(

            const char *name, const sp<IOMXObserver> &observer, node_id *node) {

       ……………………

        OMXNodeInstance *instance = new OMXNodeInstance(this, observer);

     

        OMX_COMPONENTTYPE *handle;

        OMX_ERRORTYPE err = mMaster->makeComponentInstance(

                name, &OMXNodeInstance::kCallbacks,

                instance, &handle);

    ……………………………

        *node = makeNodeID(instance);

        mDispatchers.add(*node, new CallbackDispatcher(instance));

     

        instance->setHandle(*node, handle);

     

        mLiveNodes.add(observer->asBinder(), instance);

        observer->asBinder()->linkToDeath(this);

        return OK;

    }

     

    在allocateNode,我们要用到mMaster来创建component,但是这个mMaster什么时候初始化了呢?我们看下OMX的构造函数:

    OMX::OMX()

        : mMaster(new OMXMaster),-----------原来在这呢!

          mNodeCounter(0) {

    }

    但是我们前面没有讲到OMX什么时候构造的啊?我们只能往回找了,原来我们在初始化Awesomeplayer的时候忽略掉了,罪过啊:

    AwesomePlayer::AwesomePlayer()

        : mQueueStarted(false),

          mUIDValid(false),

          mTimeSource(NULL),

          mVideoRendererIsPreview(false),

          mAudioPlayer(NULL),

          mDisplayWidth(0),

          mDisplayHeight(0),

          mVideoScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW),

          mFlags(0),

          mExtractorFlags(0),

          mVideoBuffer(NULL),

          mDecryptHandle(NULL),

          mLastVideoTimeUs(-1),

          mTextDriver(NULL) {

        CHECK_EQ(mClient.connect(), (status_t)OK) 这个就是创建的地方

    mClient是OMXClient,

    status_t OMXClient::connect() {

        sp<IServiceManager> sm = defaultServiceManager();

        sp<IBinder> binder = sm->getService(String16("media.player"));

        sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);---很熟悉吧,获得BpMediaplayerservice

     

        CHECK(service.get() != NULL);

     

        mOMX = service->getOMX();

        CHECK(mOMX.get() != NULL);

     

        if (!mOMX->livesLocally(NULL /* node */, getpid())) {

            ALOGI("Using client-side OMX mux.");

            mOMX = new MuxOMX(mOMX);

        }

     

        return OK;

    }

    好了,我们直接进入mediaplayerservice.cpp看个究竟吧:

     

    sp<IOMX> MediaPlayerService::getOMX() {

        Mutex::Autolock autoLock(mLock);

     

        if (mOMX.get() == NULL) {

            mOMX = new OMX;

        }

     

        return mOMX;

    }

    终于看到了OMX的创建了,哎以后得注意看代码才行!!!

    我们搞了那么多探究OMXMaster由来有什么用呢?

    OMXMaster::OMXMaster()

        : mVendorLibHandle(NULL) {

        addVendorPlugin();

        addPlugin(new SoftOMXPlugin);

    }

    void OMXMaster::addVendorPlugin() {

        addPlugin("libstagefrighthw.so");

    }

    原来是用来加载各个厂商的解码器(libstagefrighthw.so),还有就是把google本身的软解码器(SoftOMXPlugin)也加载了进来。那么这个libstagefrighthw.so在哪?我找了半天终于找到了,每个芯片厂商对应自己的libstagefrighthw

    hardware/XX/media/libstagefrighthw/xxOMXPlugin

    如何实例化自己解码器的component?我们以高通为例:

    void OMXMaster::addPlugin(const char *libname) {

        mVendorLibHandle = dlopen(libname, RTLD_NOW);

    …………………………….

        if (createOMXPlugin) {

            addPlugin((*createOMXPlugin)());-----创建OMXPlugin,并添加进我们的列表里

        }

    }

    hardware/qcom/media/libstagefrighthw/QComOMXPlugin.cpp

    OMXPluginBase *createOMXPlugin() {

        return new QComOMXPlugin;

    }

     

    QComOMXPlugin::QComOMXPlugin()

        : mLibHandle(dlopen("libOmxCore.so", RTLD_NOW)),----载入自己的omx API

          mInit(NULL),

          mDeinit(NULL),

          mComponentNameEnum(NULL),

          mGetHandle(NULL),

          mFreeHandle(NULL),

          mGetRolesOfComponentHandle(NULL) {

        if (mLibHandle != NULL) {

            mInit = (InitFunc)dlsym(mLibHandle, "OMX_Init");

            mDeinit = (DeinitFunc)dlsym(mLibHandle, "OMX_DeInit");

     

            mComponentNameEnum =

                (ComponentNameEnumFunc)dlsym(mLibHandle, "OMX_ComponentNameEnum");

     

            mGetHandle = (GetHandleFunc)dlsym(mLibHandle, "OMX_GetHandle");

            mFreeHandle = (FreeHandleFunc)dlsym(mLibHandle, "OMX_FreeHandle");

     

            mGetRolesOfComponentHandle =

                (GetRolesOfComponentFunc)dlsym(

                        mLibHandle, "OMX_GetRolesOfComponent");

     

            (*mInit)();

        }

    }

    以上我们就可以用高通的解码器了。我们在创建component的时候就可以创建高通相应的component实例了:

    OMX_ERRORTYPE OMXMaster::makeComponentInstance(

            const char *name,

            const OMX_CALLBACKTYPE *callbacks,

            OMX_PTR appData,

            OMX_COMPONENTTYPE **component) {

        Mutex::Autolock autoLock(mLock);

     

        *component = NULL;

     

        ssize_t index = mPluginByComponentName.indexOfKey(String8(name)); ----根据我们在media_codec.xml的解码器名字,在插件列表找到其索引

     

        OMXPluginBase *plugin = mPluginByComponentName.valueAt(index); --根据索引找到XXOMXPlugin

        OMX_ERRORTYPE err =

            plugin->makeComponentInstance(name, callbacks, appData, component);

    -----创建组件

       

        mPluginByInstance.add(*component, plugin);

     

        return err;

    }

    hardware/qcom/media/libstagefrighthw/QComOMXPlugin.cpp

    OMX_ERRORTYPE QComOMXPlugin::makeComponentInstance(

            const char *name,

            const OMX_CALLBACKTYPE *callbacks,

            OMX_PTR appData,

            OMX_COMPONENTTYPE **component) {

        if (mLibHandle == NULL) {

            return OMX_ErrorUndefined;

        }

     

        String8 tmp;

        RemovePrefix(name, &tmp);

        name = tmp.string();

     

        return (*mGetHandle)(

                reinterpret_cast<OMX_HANDLETYPE *>(component),

                const_cast<char *>(name),

                appData, const_cast<OMX_CALLBACKTYPE *>(callbacks));

    }

     

    哈哈,我们终于完成了app到寻找到正确解码器的工程了!!!

     

    ComponentInstance, OMXCodecObserver,omxcodec,omx的关系和联系,我写了篇文章,可以到链接进去看看:

    http://blog.csdn.net/tjy1985/article/details/7397752

    OMXcodec通过binder(IOMX)跟omx建立了联系,解码器则通过注册的几个回调事件OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = {

        &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone

    }往OMXNodeInstance这个接口上报消息,OMX通过消息分发机制往OMXCodecObserver发消息,它再给注册进observer的omxcodecobserver->setCodec(codec);进行最后的处理!

    stagefright 通过OpenOMX联通解码器的过程至此完毕。

    create最后一步就剩下configureCodec(meta),主要是设置下输出的宽高和initNativeWindow

    忘了个事,就是OMXCOdec的状态:

    enum State {

            DEAD,

            LOADED,

            LOADED_TO_IDLE,

            IDLE_TO_EXECUTING,

            EXECUTING,

            EXECUTING_TO_IDLE,

            IDLE_TO_LOADED,

            RECONFIGURING,

            ERROR

        };

     

    在我们实例化omxcodec的时候该状态处于LOADED状态。

    LOADER后应该就是LOADER_TO_IDLE,那什么时候进入该状态呢,就是我们下面讲的start方法:

    status_t err = mVideoSource->start();

     

    mVideoSource就是omxcodec,我们进入omxcodec.cpp探个究竟:

    status_t OMXCodec::start(MetaData *meta) {

    ….

    return init();

    }

    status_t OMXCodec::init() {

    ……..

            err = allocateBuffers();

            err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle);

            setState(LOADED_TO_IDLE);

    ……………………

    }

     

    start原来做了三件事啊,

    1:allocateBuffers给输入端放入缓存的数据,给输出端准备匹配的native window

    status_t OMXCodec::allocateBuffers() {

        status_t err = allocateBuffersOnPort(kPortIndexInput);

     

        if (err != OK) {

            return err;

        }

     

        return allocateBuffersOnPort(kPortIndexOutput);

    }

    2:分配完后通知解码器器端进入idle状态,sendCommand的流程可以参考http://blog.csdn.net/tjy1985/article/details/7397752

    emptyBuffer过程

    3:本身也处于IDLE。

    到此我们的initVideoDecoder就完成了,initAudioDecoder流程也差不多一致,这里就不介绍了,有兴趣的可以自己跟进去看看。

    prepare的最后一步finishAsyncPrepare_l(),对外宣布prepare完成,并从timeeventqueue中移除该queueitem,mAsyncPrepareEvent=null。

    费了很多的口舌和时间,我们终于完成了prepare的过程,各路信息通道都打开了,往下就是播放的过程了。



    五:播放流程video playback(三)



    前面两篇文章,我们分别讲了setdataSource和prepare的过程,获得了mVideoTrack,mAudioTrack,mVideoSourc,mAudioSource,前两个来自于setdataSource过程,后面两是prepare。

     

    status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {…

    if (!haveVideo && !strncasecmp(mime.string(), "video/", 6)) {

                setVideoSource(extractor->getTrack(i));}

    else if (!haveAudio && !strncasecmp(mime.string(), "audio/", 6)) {

                setAudioSource(extractor->getTrack(i));

    ……………..

    }

    }

    void AwesomePlayer::setVideoSource(sp<MediaSource> source) {

        CHECK(source != NULL);

        mVideoTrack = source;

    }

    void AwesomePlayer::setAudioSource(sp<MediaSource> source) {

        CHECK(source != NULL);

        mAudioTrack = source;

    }

     

    mVideoSource = OMXCodec::Create(

                mClient.interface(), mVideoTrack->getFormat(),

                false, // createEncoder

                mVideoTrack,

                NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);

    mAudioSource = OMXCodec::Create(

                    mClient.interface(), mAudioTrack->getFormat(),

                    false, // createEncoder

                    mAudioTrack);

    通过mVideoTrack,mAudioTrack我们找到了相应的解码器,并初始化了,下面我们就开讲mediaplayer如何播放了。前面的一些接口实现,我们就不讲了,不懂的可以回到setdataSource这一篇继续研究,我们直接看Awesomeplayer的实现。先看大体的时序图吧:

    status_t AwesomePlayer::play_l() {

        modifyFlags(SEEK_PREVIEW, CLEAR);

    …………

        modifyFlags(PLAYING, SET);

        modifyFlags(FIRST_FRAME, SET); ---设置PLAYING和FIRST_FRAME的标志位

    …………………..

        if (mAudioSource != NULL) {-----mAudioSource不为空时初始化Audioplayer

            if (mAudioPlayer == NULL) {

                if (mAudioSink != NULL) {

     

            (1)        mAudioPlayer = new AudioPlayer(mAudioSink, allowDeepBuffering, this);

                    mAudioPlayer->setSource(mAudioSource);

     

                    seekAudioIfNecessary_l();

                }

            }

            CHECK(!(mFlags & AUDIO_RUNNING));

     

            if (mVideoSource == NULL) {-----如果单是音频,直接播放

    ….

         (2)       status_t err = startAudioPlayer_l(

                        false /* sendErrorNotification */);

     

                    modifyFlags((PLAYING | FIRST_FRAME), CLEAR);

    …………..            

                    return err;

                }

            }

        }

       ……

        if (mVideoSource != NULL) {-----有视频时,发送eventqueue,等待处理

            // Kick off video playback

           (3) postVideoEvent_l();

     

            if (mAudioSource != NULL && mVideoSource != NULL) {----有视频,音频时,检查他们是否同步

           (4)     postVideoLagEvent_l();

            }

        }

        }

    …………..

     

        return OK;

    }

     

    在playe_l方法里,我们可以看到首先是实例化一个audioplayer来播放音频,如果单单是音频直接就播放,现在我们是本地视频播放,将不会走第二步,直接走第三和第四步。我们看下postVideoEvent_l()方法,跟我们在讲prepareAsync_l的类似:

    void AwesomePlayer::postVideoEvent_l(int64_t delayUs) {

    ……………

        mVideoEventPending = true;

        mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs);

    }

     

    mVideoEvent在我们构造awesomeplayer时已经定义:

    mVideoEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoEvent);

     

    所以我们看onVideoEvent方法:

    void AwesomePlayer::onVideoEvent() {

    if (!mVideoBuffer) {

    for (;;) {

             (1)   status_t err = mVideoSource->read(&mVideoBuffer, &options); ---mVideoSource(omxcodec)

                options.clearSeekTo();

                 ++mStats.mNumVideoFramesDecoded;

    }

    (2)  status_t err = startAudioPlayer_l();

     

    if ((mNativeWindow != NULL)

                && (mVideoRendererIsPreview || mVideoRenderer == NULL)) {

            mVideoRendererIsPreview = false;

     

         (3)   initRenderer_l();

        }

     

        if (mVideoRenderer != NULL) {

            mSinceLastDropped++;

         (4)   mVideoRenderer->render(mVideoBuffer);

        }

      (5)   postVideoEvent_l();

    }

     

    我们看到通过read方法去解码一个个sample,获取videobuffer,然后render到surfaceTexture。

    read 方法:

    status_t OMXCodec::read(

            MediaBuffer **buffer, const ReadOptions *options) {

    if (mInitialBufferSubmit) {

            mInitialBufferSubmit = false;

     

            if (seeking) {

                CHECK(seekTimeUs >= 0);

                mSeekTimeUs = seekTimeUs;

                mSeekMode = seekMode;

     

                // There's no reason to trigger the code below, there's

                // nothing to flush yet.

                seeking = false;

                mPaused = false;

            }

     

            drainInputBuffers();---对应emptybuffer,输入端

     

            if (mState == EXECUTING) {

                // Otherwise mState == RECONFIGURING and this code will trigger

                // after the output port is reenabled.

                fillOutputBuffers();--对应fillbuffer,输出端

            }

        }

    ….

      size_t index = *mFilledBuffers.begin();

        mFilledBuffers.erase(mFilledBuffers.begin());

     

        BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);

        CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);

        info->mStatus = OWNED_BY_CLIENT;

     

        info->mMediaBuffer->add_ref();

        if (mSkipCutBuffer != NULL) {

            mSkipCutBuffer->submit(info->mMediaBuffer);

        }

        *buffer = info->mMediaBuffer;

     

    }

    在讲read之前我们先来回顾下prepare时候的omxcodec::start方法,因为跟我们讲read有千丝万缕的关系,start方法:

    status_t OMXCodec::start(MetaData *meta) {

        Mutex::Autolock autoLock(mLock);

    ……….

        sp<MetaData> params = new MetaData;

        if (mQuirks & kWantsNALFragments) {

            params->setInt32(kKeyWantsNALFragments, true);

        }

        if (meta) {

            int64_t startTimeUs = 0;

            int64_t timeUs;

            if (meta->findInt64(kKeyTime, &timeUs)) {

                startTimeUs = timeUs;

            }

            params->setInt64(kKeyTime, startTimeUs);

        }

        status_t err = mSource->start(params.get()); ---我们以mp4为例,就是mpeg4source

     

        if (err != OK) {

            return err;

        }

     

        mCodecSpecificDataIndex = 0;

        mInitialBufferSubmit = true;

        mSignalledEOS = false;

        mNoMoreOutputData = false;

        mOutputPortSettingsHaveChanged = false;

        mSeekTimeUs = -1;

        mSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;

        mTargetTimeUs = -1;

        mFilledBuffers.clear();

        mPaused = false;

     

        return init();

    }

    status_t OMXCodec::init() {

    ….

         err = allocateBuffers();

    if (mQuirks & kRequiresLoadedToIdleAfterAllocation) {

            err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle);

            CHECK_EQ(err, (status_t)OK);

     

            setState(LOADED_TO_IDLE);  -------发送命令到component,让component处于Idle状态,经过两次回调后使component处于OMX_StateExecuting

        }

    ….

    }

    由于我们以MP4为例,所以mSource就是MPEG4Source,MPEG4Source在MPEG4Extractor.cpp,我们看下start方法做了什么:

    status_t MPEG4Source::start(MetaData *params) {

        Mutex::Autolock autoLock(mLock);

    …………..

        mGroup = new MediaBufferGroup;

     

        int32_t max_size;

        CHECK(mFormat->findInt32(kKeyMaxInputSize, &max_size));

     

        mGroup->add_buffer(new MediaBuffer(max_size));

     

        mSrcBuffer = new uint8_t[max_size];

     

        mStarted = true;

     

        return OK;

    }

    原来是设定输入的最大buffer.

    我再看看allocateBuffers();

    status_t OMXCodec::allocateBuffers() {

        status_t err = allocateBuffersOnPort(kPortIndexInput);----配置输入端的buffer总量,大小等OMX_PARAM_PORTDEFINITIONTYPE

     

        if (err != OK) {

            return err;

        }

     

        return allocateBuffersOnPort(kPortIndexOutput);---配置输出端,并dequeuebufferOMX

    }

    OMX_PARAM_PORTDEFINITIONTYPE component的配置信息。

    typedef struct OMX_PARAM_PORTDEFINITIONTYPE {

        OMX_U32 nSize;                 /**< Size of the structure in bytes */

        OMX_VERSIONTYPE nVersion;      /**< OMX specification version information */

        OMX_U32 nPortIndex;            /**< Port number the structure applies to */

        OMX_DIRTYPE eDir;              /**< Direction (input or output) of this port */

        OMX_U32 nBufferCountActual;    /**< The actual number of buffers allocated on this port */

        OMX_U32 nBufferCountMin;       /**< The minimum number of buffers this port requires */

        OMX_U32 nBufferSize;           /**< Size, in bytes, for buffers to be used for this channel */

        OMX_BOOL bEnabled;             /**< Ports default to enabled and are enabled/disabled by

                                            OMX_CommandPortEnable/OMX_CommandPortDisable.

                                            When disabled a port is unpopulated. A disabled port

                                            is not populated with buffers on a transition to IDLE. */

        OMX_BOOL bPopulated;           /**< Port is populated with all of its buffers as indicated by

                                            nBufferCountActual. A disabled port is always unpopulated.

                                            An enabled port is populated on a transition to OMX_StateIdle

                                            and unpopulated on a transition to loaded. */

        OMX_PORTDOMAINTYPE eDomain;    /**< Domain of the port. Determines the contents of metadata below. */

        union {

            OMX_AUDIO_PORTDEFINITIONTYPE audio;

            OMX_VIDEO_PORTDEFINITIONTYPE video;

            OMX_IMAGE_PORTDEFINITIONTYPE image;

            OMX_OTHER_PORTDEFINITIONTYPE other;

        } format;

        OMX_BOOL bBuffersContiguous;

        OMX_U32 nBufferAlignment;

    } OMX_PARAM_PORTDEFINITIONTYPE;

    OMX_PARAM_PORTDEFINITIONTYPE的参数从哪里来呢?原来来自解码器端,包括输入输出端的buffer大小,总数等信息。

    status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) {

        if (mNativeWindow != NULL && portIndex == kPortIndexOutput) {

            return allocateOutputBuffersFromNativeWindow();------当输出的时候走这里,给输出端分配内存空间,并dequeue buffer OMX

        }

        OMX_PARAM_PORTDEFINITIONTYPE def;

        InitOMXParams(&def);

        def.nPortIndex = portIndex;

     

        err = mOMX->getParameter(

                mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));---component获取OMX_PARAM_PORTDEFINITIONTYPE相关配置,具体哪些可以看上面的结构体

     

        if (err != OK) {

            return err;

        }

        size_t totalSize = def.nBufferCountActual * def.nBufferSize; ---getParameter获得的每个输入/输出端的buffer大小和总数

        mDealer[portIndex] = new MemoryDealer(totalSize, "OMXCodec");

     

        for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {

            sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);

            CHECK(mem.get() != NULL);

     

            BufferInfo info;

            info.mData = NULL;

            info.mSize = def.nBufferSize;

     

            IOMX::buffer_id buffer;

            if (portIndex == kPortIndexInput

                    && ((mQuirks & kRequiresAllocateBufferOnInputPorts)

                        || (mFlags & kUseSecureInputBuffers))) {

                if (mOMXLivesLocally) {

                    mem.clear();

     

                    err = mOMX->allocateBuffer(

                            mNode, portIndex, def.nBufferSize, &buffer,

                            &info.mData);-----给输入端分配内存空间,并使info.mData指向mNodeheader

    …………….

            info.mBuffer = buffer;

            info.mStatus = OWNED_BY_US;

            info.mMem = mem;

            info.mMediaBuffer = NULL;

     

          mPortBuffers[portIndex].push(info); ---BufferInfo 放到Vector<BufferInfo> mPortBuffers[2] mPortBuffers进行管理,到read的时候用,0是输入,1是输出。

    ………………………….

    }

     

    复习完start方法,我们就来讲reader方法了:

    status_t OMXCodec::read(

            MediaBuffer **buffer, const ReadOptions *options) {

    if (mInitialBufferSubmit) {

            mInitialBufferSubmit = false;

    ………….

      drainInputBuffers();

     

            if (mState == EXECUTING) {

                // Otherwise mState == RECONFIGURING and this code will trigger

                // after the output port is reenabled.

                fillOutputBuffers();

            }

    …………………..

        size_t index = *mFilledBuffers.begin();

        mFilledBuffers.erase(mFilledBuffers.begin());

     

        BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);

        CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);

        info->mStatus = OWNED_BY_CLIENT;

     

        info->mMediaBuffer->add_ref();

        if (mSkipCutBuffer != NULL) {

            mSkipCutBuffer->submit(info->mMediaBuffer);

        }

        *buffer = info->mMediaBuffer;

    }

    先看drainInputBuffers方法,主要是从mediasource读取数据元,

    void OMXCodec::drainInputBuffers() {

        CHECK(mState == EXECUTING || mState == RECONFIGURING);

        if (mFlags & kUseSecureInputBuffers) {

            Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput];---mPortBuffers是我们allocateBuffersOnPort方法存下来的对应的输入/输出bufferinfo数据

            for (size_t i = 0; i < buffers->size(); ++i) {---循环每次输入端能填充数据的buffer总数,这是由component的结构决定的,各个厂商的解码器配置不一样

                if (!drainAnyInputBuffer()-----buffer里面填元数据,给解码器解码

                        || (mFlags & kOnlySubmitOneInputBufferAtOneTime)) {

                    break;

                }

            }

        }

    ………………

    }

     

    bool OMXCodec::drainAnyInputBuffer() {

        return drainInputBuffer((BufferInfo *)NULL);

    }

     

    bool OMXCodec::drainInputBuffer(BufferInfo *info) {

    for (;;) {

            MediaBuffer *srcBuffer;

            if (mSeekTimeUs >= 0) {

                if (mLeftOverBuffer) {

                    mLeftOverBuffer->release();

                    mLeftOverBuffer = NULL;

                }

     

                MediaSource::ReadOptions options;

                options.setSeekTo(mSeekTimeUs, mSeekMode);

     

                mSeekTimeUs = -1;

                mSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;---seek模式

                mBufferFilled.signal();

     

                err = mSource->read(&srcBuffer, &options);---mediasource,我们以mpeg4为例,它的实现就在MPEG4Extrator.cpp(),根据seek模式和seek时间从sampletable里面找到meta_data。存到srcBuffer

     

    if (mFlags & kUseSecureInputBuffers) {

                info = findInputBufferByDataPointer(srcBuffer->data());---bufferinfomData指向元数据的data

                CHECK(info != NULL);

            }

          err = mOMX->emptyBuffer(

                mNode, info->mBuffer, 0, offset,

                flags, timestampUs); ----对应component的方法是OMX_EmptyThisBuffer,回调消息为:EmptyBufferDone

     

        if (err != OK) {

            setState(ERROR);

            return false;

        }

     

        info->mStatus = OWNED_BY_COMPONENT;----设置状态为OWNED_BY_COMPONENT

    }

     

     

    从上面的分析,我们得知emtyBuffer后在5msec之内会有个EmptyBufferDone回调,我们看下omxcodec对该回调的处理:

    void OMXCodec::on_message(const omx_message &msg) {

    case omx_message::EMPTY_BUFFER_DONE:

    ………………

    IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer;

     

                CODEC_LOGV("EMPTY_BUFFER_DONE(buffer: %p)", buffer);

     

                Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput];

                size_t i = 0;

                while (i < buffers->size() && (*buffers)[i].mBuffer != buffer) {

                    ++i;

                }

     

              BufferInfo* info = &buffers->editItemAt(i);

     -------------通过buffer_id找到Vector<BufferInfo> bufferInfo

                info->mStatus = OWNED_BY_US;-------设置info的状态为OWNED_BY_US

               info->mMediaBuffer->release();-----释放mediabuffer

               info->mMediaBuffer = NULL;

            

     

    …………….

    if (mState != ERROR

                        && mPortStatus[kPortIndexInput] != SHUTTING_DOWN) {

                    CHECK_EQ((int)mPortStatus[kPortIndexInput], (int)ENABLED);

     

                    if (mFlags & kUseSecureInputBuffers) {

                        drainAnyInputBuffer();----下一片段buffer移交给component

                    } else {

                        drainInputBuffer(&buffers->editItemAt(i));

                    }

    }

     

    emptybuffer后应该就是fillOutputBuffer:

    void OMXCodec::fillOutputBuffer(BufferInfo *info) {

        CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);

     

        if (mNoMoreOutputData) {

            CODEC_LOGV("There is no more output data available, not "

                 "calling fillOutputBuffer");--------------没有数据了退出

            return;

        }

     

        if (info->mMediaBuffer != NULL) {

            sp<GraphicBuffer> graphicBuffer = info->mMediaBuffer->graphicBuffer();

            if (graphicBuffer != 0) {

                // When using a native buffer we need to lock the buffer before

                // giving it to OMX.

                CODEC_LOGV("Calling lockBuffer on %p", info->mBuffer);

                int err = mNativeWindow->lockBuffer(mNativeWindow.get(),

                        graphicBuffer.get()); -------锁定该buffer,准备render图像

                if (err != 0) {

                    CODEC_LOGE("lockBuffer failed w/ error 0x%08x", err);

     

                    setState(ERROR);

                    return;

                }

            }

        }

     

        CODEC_LOGV("Calling fillBuffer on buffer %p", info->mBuffer);

        status_t err = mOMX->fillBuffer(mNode, info->mBuffer);---------填充输出端buffer

     

    ……….

        info->mStatus = OWNED_BY_COMPONENT;

    }

    fillbuffer后获得mVideoBuffer就可以在Awesomeplayer的onvideoEvent方法中的mVideoRenderer->render(mVideoBuffer);进行图像的显示了。

    以上我们就是播放的过程了。到此多媒体本地播放流程全部讲完了,里面很多细节的东西,还得大伙自己深入理解,往后有什么需要补充和添加的,我会再次补充上。

    六:MP4分析


    我们讲多媒体,涉及到的最多的就是MP4文件和MP3文件了,但是我们对这两个文件的格式了解多少呢,它的由有哪些部分部分组成呢?它的核心部件是哪些?它哪些部分是供解码器去解析的呢?带着这些疑问,我们首先来探索下MP4文件。

    我们首先用MP4Info这个工具来看下MP4的大貌:

     

    从上图我们可以看到MP4文件中的所有数据都装在box中,也就是说MP4文件由若干个box组成,每个box有类型和长度,可以将box理解为一个数据对象块。box中可以包含另一个box,这种box称为container box。一个MP4文件首先会有且只有一个“ftyp”类型的box,作为MP4格式的标志并包含关于文件的一些信息;之后会有且只有一个“moov”类型的box(Movie Box),它是一种container box,子box包含了媒体的metadata信息;一个moov可以由多个tracks组成。每个track就是一个随时间变化的媒体序列,例如,视频帧序列。track里的每个时间单位是一个sample,它可以是一帧视频,或者音频。sample按照时间顺序排列。注意,一帧音频可以分解成多个音频sample,所以音频一般用sample作为单位,而不用帧。MP4文件的媒体数据包含在“mdat”类型的box(Midia Data Box)中,该类型的box也是container box,可以有多个,也可以没有(当媒体数据全部引用其他文件时),媒体数据的结构由metadata进行描述。“free”类型的box,就是一些自由的信息,可以写,也可以不写。

    box中的字节序为网络字节序,也就是大端字节序(Big-Endian),简单的说,就是一个32位的4字节整数存储方式为高位字节在内存的低端。Box由header和body组成,其中header统一指明box的大小和类型,body根据类型有不同的意义和格式。

    BOX

    标准的box开头的4个字节(32位)为box size,该大小包括box header和box body整个box的大小,这样我们就可以在文件中定位各个box。如果size为1,则表示这个box的大小为large size,真正的size值要在largesize域上得到。(实际上只有“mdat”类型的box才有可能用到large size。)如果size为0,表示该box为文件的最后一个box,文件结尾即为该box结尾。(同样只存在于“mdat”类型的box中。)

    size后面紧跟的32位为box type,一般是4个字符,如“ftyp”、“moov”等,这些box type都是已经预定义好的,分别表示固定的意义。如果是“uuid”,表示该box为用户扩展类型。如果box type是未定义的,应该将其忽略。

    对应的代码片段为:framework/av/media/libstagefright/MPEG4Extrator.cpp

    status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {

        ALOGV("entering parseChunk %lld/%d", *offset, depth);

        uint32_t hdr[2];

        static const char* mQTMajorBrand = "qt  ";

        if (mDataSource->readAt(*offset, hdr, 8) < 8) {

            return ERROR_IO;

        }

        uint64_t chunk_size = ntohl(hdr[0]);---box size

        uint32_t chunk_type = ntohl(hdr[1]);---box type

        off64_t data_offset = *offset + 8;

     

        if (chunk_size == 1) {

            if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {---读取box size的大小

                return ERROR_IO;

            }

            chunk_size = ntoh64(chunk_size); ---将64位的网络字节转换为主机字节

            data_offset += 8;

    ……….

        char chunk[5];

        MakeFourCCString(chunk_type, chunk);  ----FOURCC全称Four-Character Codes,是在编程

    中非常常用的东西,一般用作标示符。它是一个32位的标示符,其实就是typedef unsigned long FOURCC

     

    }

    …………

    }

     

     

    static void MakeFourCCString(uint32_t x, char *s) {

        s[0] = x >> 24;

        s[1] = (x >> 16) & 0xff;

        s[2] = (x >> 8) & 0xff;

        s[3] = x & 0xff;

        s[4] = '\0';

    }

     

    File Type Box(ftyp)

    File Type Box(ftyp):该box有且只有1个,并且只能被包含在文件层,而不能被其他box包含。该box应该被放在文件的最开始,指示该MP4文件应用的相关信息。 “ftyp” body依次包括1个32位的major brand(4个字符),1个32位的minor version(整数)和1个以32位(4个字符)为单位元素的数组compatible brands。这些都是用来指示文件应用级别的信息。该box的字节实例如下:

    对应的的代码如下:

    framework/av/media/libstagefright/MPEG4Extrator.cpp

    status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {

    switch(chunk_type) {

            case FOURCC('f', 't', 'y', 'p'):

            {

                if (chunk_data_size < 4) {

                    return ERROR_MALFORMED;

                }

     

                uint32_t ftype;

                if (mDataSource->readAt(data_offset, &ftype, 4) < 4) {

                    return ERROR_IO;

                }

     

                MakeFourCCString(ntohl(ftype), mMajorBrand); -----major brand

     

                *offset += chunk_size;

                break;

            }

    }

     

    Movie Boxmoov

    该box包含了文件媒体的metadata信息,“moov”是一个container box,具体内容信息由子box诠释。同File Type Box一样,该box有且只有一个,且只被包含在文件层。一般情况下,
    “moov”会紧随“ftyp”出现。一般情况下, “moov”中会包含1个“mvhd”和若干个“trak”。其中“mvhd”为header box,一般作为“moov”的第一个子box出现(对于其他container box来说,header box都应作为首个子box出现)。“trak”包含了一个track的相关信息,是一个container box。结构如下图:

    Movie Header Boxmvhd

    字段

    字节数

    意义

    box size

    4

    box大小

    box type

    4

    box类型

    version

    1

    box版本,01,一般为0。(以下字节数均按version=0

    flags

    3

     

    creation time

    4

    创建时间(相对于UTC时间1904-01-01零点的秒数)

    modification time

    4

    修改时间

    time scale

    4

    文件媒体在1秒时间内的刻度值,可以理解为1秒长度的时间单元数

    duration

    4

    track的时间长度,用durationtime scale值可以计算track时长,比如audio tracktime scale = 8000, duration = 560128,时长为70.016video tracktime scale = 600, duration = 42000,时长为70

    rate

    4

    推荐播放速率,高16位和低16位分别为小数点整数部分和小数部分,即[16.16] 格式,该值为1.00x00010000)表示正常前向播放

    volume

    2

    rate类似,[8.8] 格式,1.00x0100)表示最大音量

    reserved

    10

    保留位

    matrix

    36

    视频变换矩阵

    pre-defined

    24

     

    next track id

    4

    下一个track使用的id

     

     

    status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {

        ALOGV("entering parseChunk %lld/%d", *offset, depth);

        uint32_t hdr[2];

        static const char* mQTMajorBrand = "qt  ";

        if (mDataSource->readAt(*offset, hdr, 8) < 8) {

            return ERROR_IO;

        }

        uint64_t chunk_size = ntohl(hdr[0]);---box size

        uint32_t chunk_type = ntohl(hdr[1]);---box type

    ………………….

     case FOURCC('m', 'v', 'h', 'd'):

            {

                if (chunk_data_size < 12) { //increase to 16?---

                    return ERROR_MALFORMED;

                }

     

                uint8_t header[16];

                if (mDataSource->readAt(

                            data_offset, header, sizeof(header))

                        < (ssize_t)sizeof(header)) {

                    return ERROR_IO;

                }

     

                int64_t creationTime;

                if (header[0] == 1) {

                    creationTime = U64_AT(&header[4]);

                    mFileMetaData->setInt64(kKeyEditOffset, 0 );

                } else if (header[0] != 0) {

                    return ERROR_MALFORMED;

                } else {

                    creationTime = U32_AT(&header[4]);-------创建时间,4个字节

                    int32_t mvTimeScale = U32_AT(&header[12]);---时间刻度,4个字节

     

                    mFileMetaData->setInt32(kKeyEditOffset, mvTimeScale );

                }

     

                String8 s;

                convertTimeToDate(creationTime, &s);

     

                mFileMetaData->setCString(kKeyDate, s.string());

     

                *offset += chunk_size;

                break;

            }

     

    Track Box(trak)

    “trak”也是一个container box,其子box包含了该track的媒体数据引用和描述(hint track除外)。一个MP4文件中的媒体可以包含多个track,且至少有一个track,这些track之间彼此独立,有自己的时间和空间信息。“trak”必须包含一个“tkhd”和一个“mdia”,此外还有很多可选的box其中“tkhd”为track header box,“mdia”为media box,该box是一个包含一些track媒体数据信息box的container box。

     

    status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {

        ALOGV("entering parseChunk %lld/%d", *offset, depth);

        uint32_t hdr[2];

        static const char* mQTMajorBrand = "qt  ";

        if (mDataSource->readAt(*offset, hdr, 8) < 8) {

            return ERROR_IO;

        }

        uint64_t chunk_size = ntohl(hdr[0]);---box size

        uint32_t chunk_type = ntohl(hdr[1]);---box type

    ……………………..

    if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {

                    isTrack = true;

                    Track *track = new Track; --- 如果是Track,new 个track

                    track->next = NULL;

                    if (mLastTrack) {

                        mLastTrack->next = track;

                    } else {

                        mFirstTrack = track;

                    }

                    mLastTrack = track;

     

                    track->meta = new MetaData;

                    track->includes_expensive_metadata = false;

                    track->skipTrack = false;

                    track->timescale = 0;

                    track->meta->setCString(kKeyMIMEType, "application/octet-stream");

                }

     

                off64_t stop_offset = *offset + chunk_size;

                *offset = data_offset;

                while (*offset < stop_offset) {

                    if (stop_offset - *offset >= 8) {

                        status_t err = parseChunk(offset, depth + 1);

                        if (err != OK) {

                            if(chunk_type == FOURCC('u', 'd', 't', 'a')){

                                ALOGW("error in udta atom, ignoring %llu bytes",stop_offset - *offset);

                                *offset = stop_offset;

                            } else {

                                return err;

                            }

                        }

                    }

    ………….

    }


    七、MP4分析(二)

          

    Sample Table Boxstbl

    stbl”几乎是普通的MP4文件中最复杂的一个box了。sample是媒体数据存储的单位,存储在mediachunk中,chunksample的长度均可互不相同。chunk是几个sample的集合。“stbl”包含了关于tracksample所有时间和位置的信息,以及sample的编解码等信息。利用这个表,可以解释sample的时序、类型、大小以及在各自存储容器中的位置。“stbl”是一个container box,其子box包括:sample description boxstsd)、time to sample boxstts)、sample size boxstszstz2)、sample to chunk boxstsc)、chunk offset boxstcoco64)、composition time to sample boxctts)、sync sample boxstss)等。“stsd”必不可少,且至少包含一个条目,该box包含了data reference box进行sample数据检索的信息。没有“stsd”就无法计算media sample的存储位置。“stsd”包含了编码的信息,其存储的信息随媒体类型不同而不同。

    if (chunk_type == FOURCC('s', 't', 'b', 'l')) {

                    ALOGV("sampleTable chunk is %d bytes long.", (size_t)chunk_size);

     

                    if (mDataSource->flags()

                            & (DataSource::kWantsPrefetching

                                | DataSource::kIsCachingDataSource)) {

                        sp<MPEG4DataSource> cachedSource =

                            new MPEG4DataSource(mDataSource);

     

                        if (cachedSource->setCachedRange(*offset, chunk_size) == OK) {

                            mDataSource = cachedSource;

                        }

                    }

     

                    mLastTrack->sampleTable = new SampleTable(mDataSource);----创建sampletable,每个track对应一个sampletable

                }

    Sample Description Box(stsd)

    box header和version字段后会有一个entry count字段,根据entry的个数,每个entry会有type信息,如“vide”、“sund”等,根据type不同sample description会提供不同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track会有“AudioSampleEntry”类型信息。视频的编码类型、宽高、长度,音频的声道、采样等信息都会出现在这个box中。

    case FOURCC('s', 't', 's', 'd'):

            {

     

    ,…………………………….

                uint32_t entry_count = U32_AT(&buffer[4]);

     

                off64_t stop_offset = *offset + chunk_size;

                *offset = data_offset + 8;

     

                if (entry_count > 1) {----针对3GPP,有可能有多个entry_count,但目前我们每个track支持单类型的media

                    // For 3GPP timed text, there could be multiple tx3g boxes contain

                    // multiple text display formats. These formats will be used to

                    // display the timed text.

                    const char *mime;

                    CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));

                    if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {

                         ALOGV("Text track found");

                         for (uint32_t i = 0; i < entry_count; ++i) {

                         status_t err = parseChunk(offset, depth + 1);

                            if (err != OK) {

                                return err;

                            }

                         }

                        // For now we only support a single type of media per track.

                    }

                    else {

                         status_t err = mLastTrack->sampleTable->setSampleDescParams(entry_count, *offset, chunk_data_size);

                         if (err != OK) {

                             return ERROR_IO;

                         }

                         //视频的编码类型、宽高、长度,音频的声道、采样等信息

                         mHasVideo = true;

                         uint8_t avc1[86];//(avc1-avcc) which is fixed

                         if (mDataSource->readAt(*offset, avc1, sizeof(avc1)) < (ssize_t)sizeof(avc1)) {

                             return ERROR_IO;

                         }

                         uint32_t chunk_type = U32_AT(&avc1[4]);

                         uint16_t data_ref_index = U16_AT(&avc1[14]);

                         uint16_t width = U16_AT(&avc1[32]);

                         uint16_t height = U16_AT(&avc1[34]);

     

                         mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));

                         mLastTrack->meta->setInt32(kKeyWidth, width);

                         mLastTrack->meta->setInt32(kKeyHeight, height);

     

                         uint8_t *avcc;

                         uint32_t avccSize;

                         mLastTrack->sampleTable->getSampleDescAtIndex(1, &avcc, &avccSize);

                         mLastTrack->meta->setData(kKeyAVCC, kTypeAVCC, avcc, avccSize);

                         *offset = stop_offset;

                    }

                } else {

                     for (uint32_t i = 0; i < entry_count; ++i) {

                         status_t err = parseChunk(offset, depth + 1);

                         if (err != OK) {

                            return err;

                         }

                     } // end of for

     

                }//end of entry count 1

     

                if (*offset != stop_offset) {

                    return ERROR_MALFORMED;

                }

                break;

            }

    Time To Sample Box(stts)

    stts”存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample。“stts”可以包含一个压缩的表来映射时间和sample序号,用其他的表来提供每个sample的长度和指针。表中每个条目提供了在同一个时间偏移量里面连续的sample序号,以及samples的偏移量。递增这些偏移量,就可以建立一个完整的time to sample表。

    case FOURCC('s', 't', 't', 's'):

            {

                status_t err =

                    mLastTrack->sampleTable->setTimeToSampleParams(---该方法在SampleTable.cpp,映射时间和sample序号

                            data_offset, chunk_data_size);

     

                if (err != OK) {

                    return err;

                }

     

                *offset += chunk_size;

                break;

            }

     

     Sample Size Box(stsz)

    stsz” 定义了每个sample的大小,包含了媒体中全部sample的数目和一张给出每个sample大小的表。这个box相对来说体积是比较大的。

    case FOURCC('s', 't', 's', 'z'):

            case FOURCC('s', 't', 'z', '2'):

            {

                status_t err =

                    mLastTrack->sampleTable->setSampleSizeParams(-----该方法在SampleTable.cpp,设置sample大小

                         chunk_type, data_offset, chunk_data_size);

     

                if (err != OK) {

                    return err;

                }

     

                size_t max_size;

                err = mLastTrack->sampleTable->getMaxSampleSize(&max_size);

     

                if (err != OK) {

                    return err;

                }

     

                // Assume that a given buffer only contains at most 10 fragments,

                // each fragment originally prefixed with a 2 byte length will

                // have a 4 byte header (0x00 0x00 0x00 0x01) after conversion,

                // and thus will grow by 2 bytes per fragment.

                mLastTrack->meta->setInt32(kKeyMaxInputSize, max_size + 10 * 2);

                *offset += chunk_size;

     

                // Calculate average frame rate.

                const char *mime;

                CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));

                if (!strncasecmp("video/", mime, 6)) {

                    size_t nSamples = mLastTrack->sampleTable->countSamples();

                    int64_t durationUs;

                    if (mLastTrack->meta->findInt64(kKeyDuration, &durationUs)) {

                        if (durationUs > 0) {

                            int32_t frameRate = (nSamples * 1000000LL +

                                        (durationUs >> 1)) / durationUs;

                            mLastTrack->meta->setInt32(kKeyFrameRate, frameRate);

                        }

                    }

                }

     

                break;

            }

    Sample To Chunk Box(stsc)

    chunk组织sample可以方便优化数据获取,一个chunk包含一个或多个sample。“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的chunk,从而找到这个sample。

    case FOURCC('s', 't', 's', 'c'):

            {

                status_t err =

                    mLastTrack->sampleTable->setSampleToChunkParams(该方法在SampleTable.cpp,映射samplechunk的关系,一个或多个sample组成一个chunk

     

                            data_offset, chunk_data_size);

     

                if (err != OK) {

                    return err;

                }

     

                *offset += chunk_size;

                break;

            }

    Sync Sample Box(stss)

    stss”确定media中的关键帧。对于压缩媒体数据,关键帧是一系列压缩序列的开始帧,其解压缩时不依赖以前的帧,而后续帧的解压缩将依赖于这个关键帧。“stss”可以非常紧凑的标记媒体内的随机存取点,它包含一个sample序号表,表内的每一项严格按照sample的序号排列,说明了媒体中的哪一个sample是关键帧。如果此表不存在,说明每一个sample都是一个关键帧,是一个随机存取点。

    如何查找关键帧呢?

    1:确定给定时间的sample序号检查sync sample atom来发现这个sample序号之后的key frame

    2:检查sample-to-chunk atom来发现对应该sample的chunk

    3:从chunk offset atom中提取该chunk的偏移量

    4:利用sample size atom找到sample在trunk内的偏移量和sample的大小

    case FOURCC('s', 't', 's', 's'):

            {

                status_t err =

                    mLastTrack->sampleTable->setSyncSampleParams(----设置关键帧

                            data_offset, chunk_data_size);

     

                if (err != OK) {

                    return err;

                }

     

                *offset += chunk_size;

                break;

            }

     

    Chunk Offset Box(stco)

    stco”定义了每个chunk在媒体流中的位置。位置有两种可能,32位的和64位的,后者对非常大的电影很有用。在一个表中只会有一种可能,这个位置是在整个文件中的,而不是在任何box中的,这样做就可以直接在文件中找到媒体数据,而不用解释 box。需要注意的是一旦前面的box有了任何改变,这张表都要重新建立,因为位置信息已经改变了。

    case FOURCC('s', 't', 'c', 'o'):

            case FOURCC('c', 'o', '6', '4'):

            {

                status_t err =

                    mLastTrack->sampleTable->setChunkOffsetParams(---设置chunk的偏移量

                            chunk_type, data_offset, chunk_data_size);

     

                if (err != OK) {

                    return err;

                }

     

                *offset += chunk_size;

                break;

            }

    Free Space Boxfreeskip

    free”中的内容是无关紧要的,可以被忽略。该box被删除后,不会对播放产生任何影响。

    Meida Data Boxmdat

     box包含于文件层,可以有多个,也可以没有(当媒体数据全部为外部文件引用时),用来存储媒体数据。

    下图为总的概括:

    参考资料:http://mpeg.chiariglione.org/standards/mpeg-4/mpeg-4.htm

    具体源码:frameworks/av/media/libstagefright/MPEG4Extractor.cpp

                          frameworks/av/media/libstagefright/sampletable.cpp

    好了,MP4文件格式已经介绍完了,video recoder也会用到这些知识,望大家好好研究研究。


    八、流媒体  


      从这篇开始我们将进入流媒体的环节,流媒体在android中有nuplayer来实现的,在开始讲解android流媒体前,我们先来讲讲流媒体传输协议,了解了基本协议,我们在看代码的过程中,就会有事半功倍的效果。我们将主要讲解RTSP,HTTP,HTTPS, SDP四种协议。

     一:RTSP协议简介
      实时流协议
    RTSP是一个应用层协议,用于控制具有实时特性的数据(例如多媒体流)的传送。

        RTSP协议一般与RTP/RTCP和RSVP等底层协议一起协同工作,提供基于Internet的整套的流服务。它可以选择发送通道(例如:UDP、组播UDP和TCP)和基于RTP的发送机制。它可以应用于组播和点播。RTP, RTCP,RSVP 定义如下:

      1. 实时传输协议RTP(Real-time Transport protocol)

      2. 实时传输控制协议RTCP(Real-time Transport Control protocol)

      3. 实时流协议RTSP(Real Time Streaming protocol)

      4. 资源预留协议RSVP(Resource Reserve Protocol)

    RTSP协议机理:

       客户机在向视频服务器请求视频服务之前,首先通过HTTP协议从Web服务器获取所请求视频服务的演示描述(Presentation description )文件,在RTSP中,每个演示(Presentation)及其所对应的媒体流都由一个RTSP URL标识。整个演示及媒体特性都在一个演示描述(Presentation description )文件中定义,该文件可能包括媒体编码方式、语言、RTSP URLs、目标地址、端口及其它参数。用户在向服务器请求某个连续媒体流的服务之前,必须首先从服务器获得该媒体流的演示描述(Presentation description )文件以得到必需的参数,演示描述文件的获取可采用HTTP、email或其他方法。利用该文件提供的信息定位视频服务地址(包括视频服务器地址和端口号)及视频服务的编码方式等信息。然后客户机根据上述信息向视频服务器请求视频服务。视频服务初始化完毕,视频服务器为该客户建立一个新的视频服务流,客户端与服务器运行实时流控制协议RTSP,以对该流进行各种VCR控制信号的交换,如播放(PLAY)、停止(PAUSE)、快进、快退等。当服务完毕,客户端提出拆线(TEARDOWN)请求。服务器使用RTP/UDP协议将媒体数据传输给客户端,一旦数据抵达客户端,客户端应用程序即可播放输出。在流式传输中,使用RTP/RTCP/UDP和RTSP/TCP两种不同的通信协议在客户端和服务器间建立联系。如下图:


         
    RTSP中的所有的操作都是通过服务器和客户方的消息应答来完成的,其消息包括请求(Request)和响应(Response)两种,RTSP正是通过服务器和客户端的消息应答来完成媒体流的创建、初始化(SETUP)、VCR控制(PLAY、PAUSE)以及拆线(TEARDOWN)等操作的。如下图:

    RSTP 一些基本方法及用途:

    OPTIONS  获得有效方法

    SETUP    建立传输

    ANNOUNCE 改变媒体文件的类型

    DESCRIBE 获得媒体文件的类型

    PLAY     播放

    RECORD   刻录

    REDIRECT  转换客户端到新的服务器

    PAUSE     暂停

    SET PARAMETER 设置设备,编码等参数

    TEARDOWN  移除状态

     

    完整的播放过程:

    GET 过程:

    C->W: GET /twister.sdp HTTP/1.1

    Host: www.example.com

    Accept: application/sdp

    W->C: HTTP/1.0 200 OK

    Content-Type: application/sdp

    v=0

    o=- 2890844526 2890842807 IN IP4 192.16.24.202

    s=RTSP Session

    m=audio 0 RTP/AVP 0

    a=control:rtsp://audio.com/twister/audio.en

    m=video 0 RTP/AVP 31

    a=control:rtsp://video.com/twister/video

    SETUP过程:

    C->A(audio): SETUP rtsp://audio.com/twister/audio.en RTSP/1.0

    CSeq: 1

    Transport: RTP/AVP/UDP;unicast

    ;client_port=3056-3057

    A->C: RTSP/1.0 200 OK

    CSeq: 1

    Session: 12345678

    Transport: RTP/AVP/UDP;unicast

    ;client_port=3056-3057;

    ;server_port=5000-5001

    C->V(video): SETUP rtsp://video.com/twister/video RTSP/1.0

    CSeq: 1

    Transport: RTP/AVP/UDP;unicast

    ;client_port=3058-3059

     

    V->C: RTSP/1.0 200 OK

    CSeq: 1

    Session: 23456789

    Transport: RTP/AVP/UDP;unicast

    ;client_port=3058-3059

    ;server_port=5002-5003

     

    PLAY 过程:

    C->V: PLAY rtsp://video.com/twister/video RTSP/1.0

    CSeq: 2

    Session: 23456789

    Range: smpte=0:10:00-

    V->C: RTSP/1.0 200 OK

    CSeq: 2

    Session: 23456789

    Range: smpte=0:10:00-0:20:00

    RTP-Info: url=rtsp://video.com/twister/video

    ;seq=12312232;rtptime=78712811

    C->A: PLAY rtsp://audio.com/twister/audio.en RTSP/1.0

    CSeq: 2

    Session: 12345678

    Range: smpte=0:10:00-

     

    A->C: RTSP/1.0 200 OK

    CSeq: 2

    Session: 12345678

    Range: smpte=0:10:00-0:20:00

    RTP-Info: url=rtsp://audio.com/twister/audio.en

    ;seq=876655;rtptime=1032181

    close 过程:

    C->A: TEARDOWN rtsp://audio.com/twister/audio.en RTSP/1.0

    CSeq: 3

    Session: 12345678

    A->C: RTSP/1.0 200 OK

    CSeq: 3

    C->V: TEARDOWN rtsp://video.com/twister/video RTSP/1.0

    CSeq: 3

    Session: 23456789

    V->C: RTSP/1.0 200 OK

    CSeq: 3


    关于RTSP的一些时间概念:

     

    normal play time (NPT): seconds, microseconds

    MPTE timestamps (seconds, frames)

    absolute time (for live events)

     
    二 HTTP协议简介

      HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。

    1:HTTP协议的主要特点可概括如下:

      1.支持客户/服务器模式。

      2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。

      由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

      3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

      4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

      5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

    2:HTTP协议的几个重要概念

      1.连接(Connection):一个传输层的实际环流,它是建立在两个相互通讯的应用程序之间。

      2.消息(Message):HTTP通讯的基本单位,包括一个结构化的八元组序列并通过连接传输。

      3.请求(Request):一个从客户端到服务器的请求信息包括应用于资源的方法、资源的标识符和协议的版本号

      4.响应(Response):一个从服务器返回的信息包括HTTP协议的版本号、请求的状态(例如“成功”或“没找到”)和文档的MIME类型。

      5.资源(Resource):由URI标识的网络数据对象或服务。

      6.实体(Entity):数据资源或来自服务资源的回映的一种特殊表示方法,它可能被包围在一个请求或响应信息中。一个实体包括实体头信息和实体的本身内容。

      7.客户机(Client):一个为发送请求目的而建立连接的应用程序。

      8.用户代理(User agent):初始化一个请求的客户机。它们是浏览器、编辑器或其它用户工具。

      9.服务器(Server):一个接受连接并对请求返回信息的应用程序。

      10.源服务器(Origin server):是一个给定资源可以在其上驻留或被创建的服务器。

      11.代理(Proxy):一个中间程序,它可以充当一个服务器,也可以充当一个客户机,为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的服务器中。一个代理在发送请求信息之前,必须解释并且如果可能重写它。

      代理经常作为通过防火墙的客户机端的门户,代理还可以作为一个帮助应用来通过协议处理没有被用户代理完成的请求。

      12.网关(Gateway):一个作为其它服务器中间媒介的服务器。与代理不同的是,网关接受请求就好象对被请求的资源来说它就是源服务器;发出请求的客户机并没有意识到它在同网关打交道。
      网关经常作为通过防火墙的服务器端的门户,网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。

      13.通道(Tunnel):是作为两个连接中继的中介程序。一旦激活,通道便被认为不属于HTTP通讯,尽管通道可能是被一个HTTP请求初始化的。当被中继的连接两端关闭时,通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。

      14.缓存(Cache):反应信息的局域存储。

    3:建立连接的方式

    HTTP支持2中建立连接的方式:非持久连接和持久连接(HTTP1.1默认的连接方式为持久连接)。

    1) 非持久连接

    让我们查看一下非持久连接情况下从服务器到客户传送一个Web页面的步骤。假设该贝面由1个基本HTML文件和10个JPEG图像构成,而且所有这些对象都存放在同一台服务器主机中。再假设该基本HTML文件的URL为:gpcuster.cnblogs.com/index.html。

    下面是具体步骡:

    1.HTTP客户初始化一个与服务器主机gpcuster.cnblogs.com中的HTTP服务器的TCP连接。HTTP服务器使用默认端口号80监听来自HTTP客户的连接建立请求。

    2.HTTP客户经由与TCP连接相关联的本地套接字发出—个HTTP请求消息。这个消息中包含路径名/somepath/index.html。

    3.HTTP服务器经由与TCP连接相关联的本地套接字接收这个请求消息,再从服务器主机的内存或硬盘中取出对象/somepath/index.html,经由同一个套接字发出包含该对象的响应消息。

    4.HTTP服务器告知TCP关闭这个TCP连接(不过TCP要到客户收到刚才这个响应消息之后才会真正终止这个连接)。

    5.HTTP客户经由同一个套接字接收这个响应消息。TCP连接随后终止。该消息标明所封装的对象是一个HTML文件。客户从中取出这个文件,加以分析后发现其中有10个JPEG对象的引用。

    6.给每一个引用到的JPEG对象重复步骡1-4。

    上述步骤之所以称为使用非持久连接,原因是每次服务器发出一个对象后,相应的TCP连接就被关闭,也就是说每个连接都没有持续到可用于传送其他对象。每个TCP连接只用于传输一个请求消息和一个响应消息。就上述例子而言,用户每请求一次那个web页面,就产生11个TCP连接。

    2) 持久连接

    非持久连接有些缺点。首先,客户得为每个待请求的对象建立并维护一个新的连接。对于每个这样的连接,TCP得在客户端和服务器端分配TCP缓冲区,并维持TCP变量。对于有可能同时为来自数百个不同客户的请求提供服务的web服务器来说,这会严重增加其负担。其次,如前所述,每个对象都有2个RTT的响应延长——一个RTT用于建立TCP连接,另—个RTT用于请求和接收对象。最后,每个对象都遭受TCP缓启动,因为每个TCP连接都起始于缓启动阶段。不过并行TCP连接的使用能够部分减轻RTT延迟和缓启动延迟的影响。

    在持久连接情况下,服务器在发出响应后让TCP连接继续打开着。同一对客户/服务器之间的后续请求和响应可以通过这个连接发送。整个Web页面(上例中为包含一个基本HTMLL文件和10个图像的页面)自不用说可以通过单个持久TCP连接发送:甚至存放在同一个服务器中的多个web页面也可以通过单个持久TCP连接发送。通常,HTTP服务器在某个连接闲置一段特定时间后关闭它,而这段时间通常是可以配置的。持久连接分为不带流水线(without pipelining)和带流水线(with pipelining)两个版本。如果是不带流水线的版本,那么客户只在收到前一个请求的响应后才发出新的请求。这种情况下,web页面所引用的每个对象(上例中的10个图像)都经历1个RTT的延迟,用于请求和接收该对象。与非持久连接2个RTT的延迟相比,不带流水线的持久连接已有所改善,不过带流水线的持久连接还能进一步降低响应延迟。不带流水线版本的另一个缺点是,服务器送出一个对象后开始等待下一个请求,而这个新请求却不能马上到达。这段时间服务器资源便闲置了。

    HTTP/1.1的默认模式使用带流水线的持久连接。这种情况下,HTTP客户每碰到一个引用就立即发出一个请求,因而HTTP客户可以一个接一个紧挨着发出各个引用对象的请求。服务器收到这些请求后,也可以一个接一个紧挨着发出各个对象。如果所有的请求和响应都是紧挨着发送的,那么所有引用到的对象一共只经历1个RTT的延迟(而不是像不带流水线的版本那样,每个引用到的对象都各有1个RTT的延迟)。另外,带流水线的持久连接中服务器空等请求的时间比较少。与非持久连接相比,持久连接(不论是否带流水线)除降低了1个RTT的响应延迟外,缓启动延迟也比较小。其原因在于既然各个对象使用同一个TCP连接,服务器发出第一个对象后就不必再以一开始的缓慢速率发送后续对象。相反,服务器可以按照第一个对象发送完毕时的速率开始发送下一个对象。

    4: 缓存的机制

    HTTP/1.1中缓存的目的是为了在很多情况下减少发送请求,同时在许多情况下可以不需要发送完整响应。前者减少了网络回路的数量;HTTP利用一个“过期(expiration)”机制来为此目的。后者减少了网络应用的带宽;HTTP用“验证(validation)”机制来为此目的。具体可以参考:

    http://www.chedong.com/tech/cache_docs.html

     

     RTSP协议与HTTP协议的联系与区别

         
     RTSP协议负责在服务器和客户端之间建立并控制一个或多个时间上同步的连续流媒体,其目标是象HTTP协议为用户提供文字和图形服务那样为用户提供连续媒体服务。因此,RTSP协议的设计在语法和操作上与HTTP协议很相似,这样,对于HTTP的大部分扩展也适用于RTSP。
      但是RTSP协议和HTTP协议在很多方面有着区别:
      1. HTTP是一个无状态协议,而RTSP协议是有状态的。
      2. HTTP本质上是一个非对称协议,客户端提出请求而服务器响应;而RTSP是对称的,服务器和客户端都可发送和响应请求。

     

    四  HTTPS传输协议

        HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议,它是一个安全通信通道,它基于HTTP开发,用于在客户计算机和服务器之间交换信息。它使用安全套接字层(SSL)进行信息交换,简单来说它是HTTP的安全版。
    它是由Netscape开发并内置于其浏览器中,用于对数据进行压缩和解压操作,并返回网络上传送回的结果。HTTPS实际上应用了Netscape的安全全套接字层(SSL)作为HTTP应用层的子层。(HTTPS使用端口443,而不是象HTTP那样使用端口80来和TCP/IP进行通信。)SSL使用40 位关键字作为RC4流加密算法,这对于商业信息的加密是合适的。HTTPS和SSL支持使用X.509数字认证,如果需要的话用户可以确认发送者是谁。

    HTTPS和HTTP的区别:

    1:http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
    2:https协议需要到ca申请证书,一般免费证书很少,需要交费。
    3:http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议
    4:http的连接很简单,是无状态的,而HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全

    HTTPS解决的问题:
    1 . 信任主机的问题. 采用https 的server 必须从CA 申请一个用于证明服务器用途类型的证书. 改证书只有用于对应的server 的时候,客户度才信任次主机. 所以目前所有的银行系统网站,关键部分应用都是https 的. 客户通过信任该证书,从而信任了该主机. 其实这样做效率很低,但是银行更侧重安全. 这一点对我们没有任何意义,我们的server ,采用的证书不管自己issue 还是从公众的地方issue, 客户端都是自己人,所以我们也就肯定信任该server.
    2 . 通讯过程中的数据的泄密和被窜改
    1. 一般意义上的https, 就是 server 有一个证书.
    a) 主要目的是保证server 就是他声称的server. 这个跟第一点一样.
    b) 服务端和客户端之间的所有通讯,都是加密的.

    i. 具体讲,是客户端产生一个对称的密钥,通过server 的证书来交换密钥. 一般意义上的握手过程.
    ii. 加下来所有的信息往来就都是加密的. 第三方即使截获,也没有任何意义.因为他没有密钥. 当然窜改也就没有什么意义了.

    2. 少许对客户端有要求的情况下,会要求客户端也必须有一个证书.
    a) 这里客户端证书,其实就类似表示个人信息的时候,除了用户名/密码, 还有一个CA 认证过的身份. 应为个人证书一般来说上别人无法模拟的,所有这样能够更深的确认自己的身份.
    b) 目前少数个人银行的专业版是这种做法,具体证书可能是拿U盘作为一个备份的载体.
    HTTPS 一定是繁琐的.

    a) 本来简单的http协议,一个get一个response. 由于https 要还密钥和确认加密算法的需要.单握手就需要6/7 个往返.
    i. 任何应用中,过多的round trip 肯定影响性能.
    b) 接下来才是具体的http协议,每一次响应或者请求, 都要求客户端和服务端对会话的内容做加密/解密.

    i. 尽管对称加密/解密效率比较高,可是仍然要消耗过多的CPU,为此有专门的SSL 芯片. 如果CPU 信能比较低的话,肯定会降低性能,从而不能serve 更多的请求.
    ii. 加密后数据量的影响. 所以,才会出现那么多的安全认证提示。

     

    五 SDP协议

     SDP会话描述协议:为会话通知、会话邀请和其它形式的多媒体会话初始化等目的提供了多媒体会话描述。会话目录用于协助多媒体会议的通告,并为会话参与者传送相关设置信息。 SDP 即用于将这种信息传输到接收端。 SDP 完全是一种会话描述格式――它不属于传输协议 ――它只使用不同的适当的传输协议,包括会话通知协议 (SAP) 、会话初始协议(SIP)、实时流协议 (RTSP)、 MIME 扩展协议的电子邮件以及超文本传输协议 (HTTP)。SDP 的设计宗旨是通用性,它可以应用于大范围的网络环境和应用程序,而不仅仅局限于组播会话目录。

    SDP是会话描述协议的缩写,是描述流媒体初始化参数的格式,由IETF作为RFC 4566颁布。流媒体是指在传输过程中看到或听到的内容,SDP包通常包括以下信息:

    1)会话信息· 会话名和目的

               · 会话活动时间

                  由于参与会话的资源是受限制的,因此包括以下附加信息是非常有用的

               · 会话使用的带宽信息

               · 会话负责人的联系信息

    2)媒体信息

               · 媒体类型,例如视频和音频

               · 传输协议,例如RTP/UDP/IP和H.320。

                 · 多播地址和媒体传输端口(IP多播会话)

               · 用于联系地址的媒体和传输端口的远端地址(IP单播会话)

    SDP描述由许多文本行组成,文本行的格式为<类型>=<值>,<类型>是一个字母,<值>是结构化的文本串,其格式依<类型>而定。

    SDP格式(带*为可选):

            Session description

              v=   (protocol version) //该行指示协议的版本

              o=   (owner/creator and session identifier)

    例如:    o=mhandley 2890844526 2890842807 IN IP4 126.16.64.4   //o行中包含与会话所有者有关的参数(1:第一个参数表明会话发起者的名称,该参数可不填写,如填写和SIP消息中,from消息头的内容一致:2:第二个参数为主叫方的会话标识符:3:第三个参数为主叫方会话的版本,会话数据有改变时,版本号递增:4:第四个参数定义了网络类型,IN表示Internet网络类型,目前仅定义该网络类型:5:第五个参数为地址类型,目前支持IPV4和IPV6两种地址类型:6:第六个参数为地址:表明会话发起者的IP地址,该地址为信令面的IP地址,信令PDP激活时为手机分配。)

              s=   (session name) //表明本次会话的标题,或会话的名称

              i=* (session information)

              u=* (URI of description)

              e=* (email address)

              p=* (phone number)

              c=* (connection information - not required if included in all media)

              b=* (zero or more bandwidth information lines)

              One or more time descriptions ("t=" and "r=" lines, see below)

              z=* (time zone adjustments)

              k=* (encryption key)

              a=* (zero or more session attribute lines)

              Zero or more media descriptions

           Time description

              t=   (time the session is active)

              r=* (zero or more repeat times)

           Media description, if present

              m=   (media name and transport address)

        例如: m=audio 3458  RTP/AVP  0   96   97   // m行又称媒体行,描述了发送方所支持的媒体类型等信息1: 第一个参数为媒体名称:表明支持音频类型。2: 第二个参数为端口号,表明UE在本地端口为3458上发送音频流。3: 第三个参数为传输协议,一般为RTP/AVP协议。4:四-七参数为所支持的四种净荷类型编号)

    m=video 3400 RTP/AVP 98  99 //m行又称媒体行,描述了发送方所支持的媒体类型等信息

              i=* (media title)

              c=* (connection information - optional if included at

                   session-level)

              b=* (zero or more bandwidth information lines)

              k=* (encryption key)

              a=* (zero or more media attribute lines)

     

     

    参考文档:
    http://www.cnblogs.com/tuyile006/archive/2011/02/22/1961679.html

    http://www.chedong.com/tech/cache_docs.html


    九:流媒体框架   

     

    android流媒体框架是从Gingerbread android2.3的时候加入的,其核心就是nuplayer。android 流媒体在4.1上资源文件主要分为httplivesource,rtspsource,genericsource.genericsource是4.1上加入的。其中Rtsp流和httplive流是最主要的,两者有本质的区别。

    RTSP source是客户机在向视频服务器请求视频服务之前,

    首先通过HTTP协议从Web服务器获取所请求视频服务的演示描述(Presentation description )文件,在RTSP中,每个演示(Presentation)及其所对应的媒体流都由一个RTSPURL标识。整个演示及媒体特性都在一个演示描述(Presentation description )文件中定义,该文件可能包括媒体编码方式、语言、RTSP URLs、目标地址、端口及其它参数。用户在向服务器请求某个连续媒体流的服务之前,必须首先从服务器获得该媒体流的演示描述(Presentationdescription )文件以得到必需的参数,演示描述文件的获取可采用HTTP、email或其他方法。利用该文件提供的信息定位视频服务地址(包括视频服务器地址和端口号)及视频服务的编码方式等信息。

    然后客户机根据上述信息向视频服务器请求视频服务。视频服务初始化完毕,视频服务器为该客户建立一个新的视频服务流,客户端与服务器运行实时流控制协议RTSP,以对该流进行各种VCR控制信号的交换,如播放(PLAY)、停止(PAUSE)、快进、快退等。当服务完毕,客户端提出拆线(TEARDOWN)请求。服务器使用RTP/UDP协议将媒体数据传输给客户端,一旦数据抵达客户端,客户端应用程序即可播放输出。在流式传输中,使用RTP/RTCP/UDP和RTSP/TCP两种不同的通信协议在客户端和服务器间建立联系。总体框架如下图:


    HTTP LiveStreaming(缩写是 HLS)是一个由苹果公司提出的基于HTTP流媒体 网络传输协议。是苹果公司QuickTime XiPhone软件系统的一部分。它的工作原理是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的extended M3U (m3u8) playlist文件,用于寻找可用的媒体流。该视频格式为.m3u8。Httplive在android上总体框架如下图:




    在android上,流媒体播放跟本地媒体播放是两个不同的架构体系,两者有啥区别呢?

    1:框架层创建的player不同,local playback用的是stagefrightplayer而流媒体是nuplayer

    2:跟OMX接口不一致,local playback用的是omxcodec,而流媒体用的是Acodec

    3:消息机制不同,localplayback用的是TimedEventQueue模型,而流媒体用的是AHandler消息机制,类似于我们熟悉的Handler。


    下一节我们讲讲流媒体的消息机制AHandler。


    十:流媒体AHandler机制

          


    为什么我们要谈论流媒体的消息机制呢?因为在流媒体中,类似于我们写APP的时候,为了不阻塞UI线程,我们把利用handler,把UI线程分开异步执行,使用handler去执行某项比较费时的操作,然后异步更新UI线程。流媒体中也是类似的,因为联网,codec都很费时,需要异步执行。handler是java的实现机制,而我们下面要讲的AHandler就是基于C++的实现了。

    我们知道handler消息机制,构成就必须包括一个Loop,message。那么对应的AHandler,也应该有对应的ALooper, AMessage。下面我们将以实例化NUplayerDrriver和setdataSource为例来具体讲述AHandler消息机制。

    首先看下NuplayerDriver的构造函数,这是流媒体初始化函数。

     

    static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,

            notify_callback_f notifyFunc){

    caseNU_PLAYER:

                ALOGV(" createNuPlayer");

                p = newNuPlayerDriver;

    }

     

    NuPlayerDriver::NuPlayerDriver()

        : mResetInProgress(false),

          mPrepareInProgress(false),

          mIsPrepared(false),

          mDurationUs(-1),

          mPositionUs(-1),

          mNumFramesTotal(0),

          mNumFramesDropped(0),

          mLooper(new ALooper),-----创建一个新的ALooper

          mState(UNINITIALIZED),

          mAtEOS(false),

          mStartupSeekTimeUs(-1) {

          mLooper->setName("NuPlayerDriverLooper");----给该Looper取名字,以便与AHandler一一对应

        mLooper->start(

                false, /* runOnCallingThread */

                true,  /* canCallJava */

                PRIORITY_AUDIO);-------------启动该Looper

     

        mPlayer = new NuPlayer;------------创建一个AHandlerNuplayer

        mLooper->registerHandler(mPlayer);-----把该AHandler注册到Looper中,具体的实现我们往后看

        mPlayer->setDriver(this);

    }

     

    看看ALooper的启动函数:

     

    status_t ALooper::start(

            bool runOnCallingThread, boolcanCallJava, int32_t priority) {

        if (runOnCallingThread) {------runOnCallingThread开始为false,不走这里

            …………

        }

     

        Mutex::Autolock autoLock(mLock);

     

        if (mThread != NULL || mRunningLocally) {

            return INVALID_OPERATION;

        }

     

        mThread = newLooperThread(this, canCallJava);----新建一个thread

     

        status_t err =mThread->run(

                mName.empty() ?"ALooper" : mName.c_str(), priority);----looper线程启动

        if (err != OK) {

            mThread.clear();

        }

     

        return err;

    }

    看下关键步骤注册Handler:

     

    ALooper::handler_idALooper::registerHandler(const sp<AHandler> &handler) {

        return gLooperRoster.registerHandler(this,handler);

    }

     

    ALooper::handler_idALooperRoster::registerHandler(

            const sp<ALooper> looper, constsp<AHandler> &handler) {

        Mutex::Autolock autoLock(mLock);

     

        if (handler->id() != 0) {

            CHECK(!"A handler must only beregistered once.");

            return INVALID_OPERATION;

        }

        HandlerInfo info;

        info.mLooper = looper;----- NuPlayerDriver Looper

        info.mHandler = handler;------nuplayer

        ALooper::handler_idhandlerID = mNextHandlerID++;

        mHandlers.add(handlerID, info);-------KeyedVector<ALooper::handler_id,HandlerInfo> mHandlers;

        handler->setID(handlerID);------设置handlerID,以便发送message时找到对应的handler

        return handlerID;

    }

    ALooperRoster::ALooperRoster()

        : mNextHandlerID(1),------------------1开始

          mNextReplyID(1) {

    }

     

     

    有了LOOPER,也有了对应的handler,看看如何发送消息给LOOPER,交个相应的handler去处理。我们以setdataSource方法为例:

    Nuplayer本身也是个AHandler,因为其继承自AHandler。

    structNuPlayer : public AHandler {

    }

    我们看看其父类AHandler:

    struct AHandler : public RefBase {

        AHandler()

            : mID(0){

        }

        ALooper::handler_id id() const {

            return mID;

        }

        sp<ALooper> looper();

    protected:

        virtual voidonMessageReceived(const sp<AMessage> &msg) = 0;---处理消息函数

    private:

        friend struct ALooperRoster;

        ALooper::handler_id mID;

        void setID(ALooper::handler_id id) {

            mID = id;

        }

        DISALLOW_EVIL_CONSTRUCTORS(AHandler);

    };

     以setdataSource为例看看如何传递message

    void NuPlayer::setDataSource(

            const char *url, constKeyedVector<String8, String8> *headers) {

       1 sp<AMessage> msg =new AMessage(kWhatSetDataSource, id());

        size_t len = strlen(url);

    ………..

    elseif ((!strncasecmp(url, "http://", 7) || !strncasecmp(url,"https://", 8))

                        && ((len >= 4&& !strcasecmp(".sdp", &url[len - 4]))

                        || strstr(url,".sdp?"))) {

            source = newRTSPSource(url, headers, mUIDValid, mUID, true);

            mSourceType = kRtspSource;

        }

    ……….

        2msg->setObject("source", source);

        3msg->post();

    }

     

    首先新建一个AMessage的实例,传入的参数为事件的名称以及处理该消息的Handlerid,该id在    mLooper->registerHandler(mPlayer);方法中设置上。

     

    我们看下AMessage:

     

    AMessage::AMessage(uint32_twhat, ALooper::handler_id target)

        : mWhat(what),

          mTarget(target),

          mNumItems(0) {

    }

     

    void AMessage::setObject(const char *name, const sp<RefBase> &obj) {

        setObjectInternal(name, obj, kTypeObject);

    }

    void AMessage::setObjectInternal(

            const char *name, constsp<RefBase> &obj, Type type) {

        Item *item = allocateItem(name);

        item->mType = type;

     

        if (obj != NULL) { obj->incStrong(this);}

        item->u.refValue = obj.get();

    }

     

    POST 过程:

    void AMessage::post(int64_t delayUs) {

        gLooperRoster.postMessage(this, delayUs);----调用ALooperRosterpostMessage函数

    }

     

    status_tALooperRoster::postMessage(

            const sp<AMessage> &msg,int64_t delayUs) {

        Mutex::Autolock autoLock(mLock);

        return postMessage_l(msg, delayUs);

    }

     

    status_t ALooperRoster::postMessage_l(

            const sp<AMessage> &msg,int64_t delayUs) {

        ssize_t index =mHandlers.indexOfKey(msg->target());--target即为Handler_id

     

        if (index < 0) {

            ALOGW("failed to post message.Target handler not registered.");

            return -ENOENT;

        }

     

        const HandlerInfo &info =mHandlers.valueAt(index);---根据handler_id找到HandlerInfo

     

        sp<ALooper>looper = info.mLooper.promote();----根据我们注册的HandlerInfo找到相应的ALooper,我们现在就是“NuPlayerDriver Looper

     

        if (looper == NULL) {

            ALOGW("failed to post message."

                 "Target handler %d stillregistered, but object gone.",

                 msg->target());

     

            mHandlers.removeItemsAt(index);

            return -ENOENT;

        }

     

        looper->post(msg,delayUs);---往“NuPlayerDriver Looper”里传递消息

     

        return OK;

    }

     

    void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {

        Mutex::Autolock autoLock(mLock);

     

        int64_t whenUs;

        if (delayUs > 0) {

            whenUs = GetNowUs() + delayUs;

        } else {

            whenUs = GetNowUs();

        }

        List<Event>::iterator it =mEventQueue.begin();

        while (it != mEventQueue.end() &&(*it).mWhenUs <= whenUs) {

            ++it;

        }

        Event event;

        event.mWhenUs = whenUs;

        event.mMessage = msg;

     

        if (it == mEventQueue.begin()) {

            mQueueChangedCondition.signal();

        }

     

        mEventQueue.insert(it,event);----往消息队列里插入消息

    }

     

    当队列里有消息时便会触发loop函数:

    bool ALooper::loop() {

        Event event;

     

        {

            Mutex::Autolock autoLock(mLock);

            if (mThread == NULL &&!mRunningLocally) {

                return false;

            }

            if (mEventQueue.empty()) {

                mQueueChangedCondition.wait(mLock);

                return true;

            }

            int64_t whenUs =(*mEventQueue.begin()).mWhenUs;

            int64_t nowUs = GetNowUs();

     

            if (whenUs > nowUs) {

                int64_t delayUs = whenUs - nowUs;

               mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);

     

                return true;

            }

     

            event = *mEventQueue.begin();

            mEventQueue.erase(mEventQueue.begin());

        }

     

        gLooperRoster.deliverMessage(event.mMessage);

        return true;

    }

     

     

    void ALooperRoster::deliverMessage(const sp<AMessage> &msg) {

        sp<AHandler> handler;

        {

            Mutex::Autolock autoLock(mLock);

            ssize_t index = mHandlers.indexOfKey(msg->target());

            if (index < 0) {

                ALOGW("failed to delivermessage. Target handler not registered.");

                return;

            }

            const HandlerInfo &info =mHandlers.valueAt(index);

            handler =info.mHandler.promote();

     

            if (handler == NULL) {

                ALOGW("failed to delivermessage. "

                     "Target handler %dregistered, but object gone.",

                     msg->target());

                mHandlers.removeItemsAt(index);

                return;

            }

        }

        handler->onMessageReceived(msg);------对应为Nuplayer

    }

    void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {

        switch (msg->what()) {

            case kWhatSetDataSource:

            {

    ………………………………………

                mSource = static_cast<Source*>(obj.get());

     

                sp<AMessage> notify = newAMessage(kWhatSourceNotify, id());

                mSource->setNotify(notify);

                mSource->connect();-------------RTSPSource

     

                break;

            }

    }

    至此我们的Ahandler的流程讲完了,大致就是启动一个threadLooper,监听looper的消息队列是否有变化,如有交个相应的Handler去处理。


    十一、流媒体具体流程(一)

          


    病了两周,一吃医生开的药就加重,NND以后不去那儿看病了,最近好多了但人也懒了,也好久没有更新博文了,难道我的计划要这样的搁浅了?NO!生命不息,笔耕不辍,哈哈,有点夸大了,嘚吧嘚吧啥,进入正题.

    上面我们把流媒体的框架和里面的消息机制讲了一遍,下面我们开搞流程了。我们首先探讨android里的主流支持的RTSP相关的流程。

    RTSP协议相关的,不了解的,可以回头去看看:http://blog.csdn.net/tjy1985/article/details/7996121

    我们知道,不管是播放本地媒体,还是流媒体,上层实现的方法都是一样的:

    1:创建mediaplayer

    2:setdataSource

    3:prepare

    4:start

    5:pause

    6:stop

    本质的区别在于framework层,Locateplayback选用stagefrighplayert+awesomeplayer来实现,流媒体用的是nuplayer。

    我们首先来看看,构造nuplayer和setdataSource都干了啥?

    nuplayer的构成过程:

    mediaplayerservice.cpp

     

    staticsp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,

            notify_callback_f notifyFunc)

    {

        void* handle;

        CreateMPQ_PlayerClientFunc funcHandle;

        sp<MediaPlayerBase> p;

        switch (playerType) {

     

    ………

            case NU_PLAYER:

                ALOGV(" createNuPlayer");

                p = newNuPlayerDriver;

                break;

    ……..

    }

     

    NuPlayerDriver.cpp

     

    NuPlayerDriver::NuPlayerDriver()

        : mResetInProgress(false),

          mPrepareInProgress(false),

          mIsPrepared(false),

          mDurationUs(-1),

          mPositionUs(-1),

          mNumFramesTotal(0),

          mNumFramesDropped(0),

          mLooper(new ALooper),

          mState(UNINITIALIZED),

          mAtEOS(false),

          mStartupSeekTimeUs(-1) {

        mLooper->setName("NuPlayerDriverLooper");

     

        mLooper->start(

                false, /* runOnCallingThread */

                true,  /* canCallJava */

                PRIORITY_AUDIO);

     

        mPlayer = new NuPlayer;

        mLooper->registerHandler(mPlayer);

     

        mPlayer->setDriver(this);

    }

     

    NuPlayer.cpp

     

    NuPlayer::NuPlayer()

        : mUIDValid(false),

          mVideoIsAVC(false),

          mAudioEOS(false),

          mVideoEOS(false),

          mDecoderEOS(false),

          mScanSourcesPending(false),

         mScanSourcesGeneration(0),

          mTimeDiscontinuityPending(false),

          mFlushingAudio(NONE),

          mFlushingVideo(NONE),

          mVideoSkipToIFrame(false),

          mResetInProgress(false),

          mResetPostponed(false),

          mSkipRenderingAudioUntilMediaTimeUs(-1ll),

         mSkipRenderingVideoUntilMediaTimeUs(-1ll),

          mVideoLateByUs(0ll),

          mNumFramesTotal(0ll),

          mNumFramesDropped(0ll),

          mPauseIndication(false),

          mSourceType(kDefaultSource),

          mStats(NULL),

          mBufferingNotification(false),

          mSRid(0) {

          mTrackName = new char[6];

    }

    构成nuplayer的过程,无非就是初始化一些状态,标志位,重要的是起了消息队列,也就是我们上篇写的AHandler消息机制:http://blog.csdn.net/tjy1985/article/details/8063484,我们也不多说了,直接进入setdataSource,先来个概图吧:


    setDataSource分三步来走:

    1:创建相应的消息

    2:根据URL创建对应的source

    3:onmessageReceive处理对应的消息

    voidNuPlayer::setDataSource(

            const char *url, constKeyedVector<String8, String8> *headers) {

        1sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());----构建一个kWhatSetDataSource的消息

     

        sp<Source> source;

        if (IsHTTPLiveURL(url)) {

         2   source = newHTTPLiveSource(url, headers, mUIDValid, mUID);----创建的HTTPLiveSource

        } else if (!strncasecmp(url,"rtsp://", 7)) {

            source = newRTSPSource(url, headers, mUIDValid, mUID);-----创建RTSPSource实例

        } else {

            source = new GenericSource(url,headers, mUIDValid, mUID);

        }

     

        msg->setObject("source",source);

        msg->post();-----post刚才构建的kWhatSetDataSource消息

    }

     

     

    voidNuPlayer::onMessageReceived(const sp<AMessage> &msg) {

        switch (msg->what()) {

       3     case kWhatSetDataSource:------------处理kWhatSetDataSource消息

            {

                ALOGV("kWhatSetDataSource");

                CHECK(mSource == NULL);

                sp<RefBase> obj;

               CHECK(msg->findObject("source", &obj));

     

                mSource = static_cast<Source*>(obj.get());

                break;

            }



    十二、流媒体具体流程(二)

      


    上篇我们讲了流媒体RTSP部分的setdataSource方法,prepare没有实质的东西,我们直接讲start方法, 这个方法是它的核心方法,比较复杂,我们先来看下整个start方法的时序图吧,让大家有个大概的了解:

     

     

    跟踪下代码,看看start里面有什么名堂?

    NuPlayer.cpp

    void NuPlayer::start() {

        (new AMessage(kWhatStart, id()))->post();

    }

     

    void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {

        switch (msg->what()) {

    case kWhatStart:

            {

                ALOGV("kWhatStart");

     

                mVideoIsAVC = false;

                mAudioEOS = false;

                mVideoEOS = false;

                mDecoderEOS = false;

                mSkipRenderingAudioUntilMediaTimeUs = -1;

                mSkipRenderingVideoUntilMediaTimeUs = -1;

                mVideoLateByUs = 0;

                mNumFramesTotal = 0;

                mNumFramesDropped = 0;

     

            (1)    mSource->start();-------RTSPSource

     

            (2)    mRenderer = new Renderer(

                        mAudioSink,

                        new AMessage(kWhatRendererNotify, id()));

     

             (3) postScanSources();

                break;

            }

    }

    从代码我们看到start分三步走:start(通过socket跟web服务器连接并通过HTTP协议从Web服务器获取所请求视频服务的演示描述等),创建Renderer(new Renderer),转载解码器并解码(posetScanSources).

    首先我们来探讨下mSource->start()mSource就是RTSPSource,

    先看下总的流程图吧(画得不怎么好,将就看吧):

     

    void NuPlayer::RTSPSource::start() {

        if (mLooper == NULL) {

            mLooper = new ALooper;

            mLooper->setName("rtsp");

            mLooper->start();

     

            mReflector = new AHandlerReflector<RTSPSource>(this);

            mLooper->registerHandler(mReflector);-------创建一个‘rtsp’looper

        }

     

        CHECK(mHandler == NULL);

     

        sp<AMessage> notify = new AMessage(kWhatNotify, mReflector->id());-----记住这个消息

     

        mHandler = new MyHandler(mURL.c_str(),notify, mUIDValid, mUID);

        mLooper->registerHandler(mHandler);-----MyHandler,‘rtsplooper连接起来

     

        CHECK_EQ(mState, (int)DISCONNECTED);

        mState = CONNECTING;

     

        mHandler->connect();-------调用myhandlerconnect方法

    }

     

    我们来看这个Myhandler的构造函数:

     

     

    MyHandler(

                const char *url,

                const sp<AMessage> &notify,

                bool uidValid = false, uid_t uid = 0)

            : mNotify(notify),

              mUIDValid(uidValid),

              mUID(uid),

              mNetLooper(new ALooper),

              mConn(new ARTSPConnection(mUIDValid, mUID)),-----创建ARTSPConnection,主要用来跟服务器连接

              mRTPConn(new ARTPConnection),

              ………………………..

              mKeepAliveGeneration(0) {

            mNetLooper->setName("rtsp net");

            mNetLooper->start(false /* runOnCallingThread */,

                              false /* canCallJava */,

                              PRIORITY_HIGHEST);-------自己创建一个looper

     

            ……………

        }

    在MyHandler中我们创建了ARTSPConnection,这将在我们的connect方法中会用到:

     

    void connect() {

            looper()->registerHandler(mConn);

            (1 ? mNetLooper : looper())->registerHandler(mRTPConn);

     

            sp<AMessage> notify = new AMessage('biny', id());

            mConn->observeBinaryData(notify);

     

            sp<AMessage> reply = new AMessage('conn', id());----记住这AMessage,这个将会传给ARTSPConnection,并传回来

            mConn->connect(mOriginalSessionURL.c_str(), reply);----mConn == ARTSPConnection

        }

     

    void ARTSPConnection::connect(const char *url, const sp<AMessage> &reply) {

        sp<AMessage> msg = new AMessage(kWhatConnect, id());

        msg->setString("url", url);

        msg->setMessage("reply", reply);

        msg->post();

    }

     

    void ARTSPConnection::onMessageReceived(const sp<AMessage> &msg) {

        switch (msg->what()) {

            case kWhatConnect:

                onConnect(msg);

                break;

    ………..

    }

     

    void ARTSPConnection::onConnect(const sp<AMessage> &msg) {

        ++mConnectionID;

    …………………

        AString url;

        CHECK(msg->findString("url", &url));

     

        sp<AMessage> reply;

        CHECK(msg->findMessage("reply", &reply));------reply == 'conn'

     

    ………………….

        mSocket = socket(AF_INET, SOCK_STREAM, 0); ------  建立一个socket

     

        if (mUIDValid) {

            HTTPBase::RegisterSocketUserTag(mSocket, mUID,

                                           (uint32_t)*(uint32_t*) "RTSP");

        }

     

        MakeSocketBlocking(mSocket, false);------设置socket为非阻

     

        struct sockaddr_in remote;

        memset(remote.sin_zero, 0, sizeof(remote.sin_zero));

        remote.sin_family = AF_INET;

        remote.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;

        remote.sin_port = htons(port);

     

        int err = ::connect(

                mSocket, (const struct sockaddr *)&remote, sizeof(remote));----连接

     

        reply->setInt32("server-ip", ntohl(remote.sin_addr.s_addr));

     

        if (err < 0) {

            if (errno == EINPROGRESS) {-----当非阻塞时,connect立刻返回-1,同时errno设置为EINPROGRESS。然后再检测socket是否可写,如果可写了,说明
     socket
    已经建立的连

                sp<AMessage> msg = new AMessage(kWhatCompleteConnection, id());

                msg->setMessage("reply", reply);

                msg->setInt32("connection-id", mConnectionID);

                msg->post();

                return;

            }

     

    ……………………….

        reply->post();

    }

     

    void ARTSPConnection::onCompleteConnection(const sp<AMessage> &msg) {

        sp<AMessage> reply;

        CHECK(msg->findMessage("reply", &reply));

     

        int32_t connectionID;

        CHECK(msg->findInt32("connection-id", &connectionID));

     

        if ((connectionID != mConnectionID) || mState != CONNECTING) {

            // While we were attempting to connect, the attempt was

            // cancelled.

            reply->setInt32("result", -ECONNABORTED);

            reply->post();

            return;

        }

     

        struct timeval tv;

        tv.tv_sec = 0;

        tv.tv_usec = kSelectTimeoutUs;-----超时时间

     

        fd_set ws;

        FD_ZERO(&ws);

        FD_SET(mSocket, &ws);

     

        int res = select(mSocket + 1, NULL, &ws, NULL, &tv);

    …………

        int err;

        socklen_t optionLen = sizeof(err);

        CHECK_EQ(getsockopt(mSocket, SOL_SOCKET, SO_ERROR, &err, &optionLen), 0);

        CHECK_EQ(optionLen, (socklen_t)sizeof(err));

     

        if (err != 0) {

            ALOGE("err = %d (%s)", err, strerror(err));

     

            reply->setInt32("result", -err);

     

            mState = DISCONNECTED;

            if (mUIDValid) {

                HTTPBase::UnRegisterSocketUserTag(mSocket);

            }

            close(mSocket);

            mSocket = -1;

        } else {

            reply->setInt32("result", OK);

            mState = CONNECTED;

            mNextCSeq = 1;

     

            postReceiveReponseEvent();------处理从服务器回来的reponse

        }

     

        reply->post();-----postmyhandler处理

     

    }

     

     

     

    又回到MyHandler.h,真够绕的啊!

     

    virtual void onMessageReceived(const sp<AMessage> &msg) {

            switch (msg->what()) {

                case 'conn':

                {

                    int32_t result;

                    CHECK(msg->findInt32("result", &result));

     

                    ALOGI("connection request completed with result %d (%s)",

                         result, strerror(-result));

     

                    if (result == OK) {

                        AString request;

                        request = "DESCRIBE ";----DESCRIBE获得媒体文件的类型的请求类型

                        request.append(mSessionURL);

                        request.append(" RTSP/1.0\r\n");

                        request.append("Accept: application/sdp\r\n");

                        request.append("\r\n");   -----建立连接后,发送获得媒体文件的类型的request

     

                        sp<AMessage> reply = new AMessage('desc', id());

                        mConn->sendRequest(request.c_str(), reply);

                    } else {

                        (new AMessage('disc', id()))->post();

                    }

                    break;

    }

    看到”DESRIBE”,我们可以回头看看流媒体的协议一张http://blog.csdn.net/tjy1985/article/details/7996121,在播放流媒体前,首先要从web服务器获取媒体文件的类型,要获取这些信息,就得往服务器发生“DESCRIBE”的请求,我们又得回到ARTSPConnection了:

     

    void ARTSPConnection::sendRequest(

            const char *request, const sp<AMessage> &reply) {

        sp<AMessage> msg = new AMessage(kWhatSendRequest, id());

        msg->setString("request", request);

        msg->setMessage("reply", reply);

        msg->post();

    }

     

     

    void ARTSPConnection::onMessageReceived(const sp<AMessage> &msg) {

        switch (msg->what()) {

    case kWhatSendRequest:

                onSendRequest(msg);

                break;

    }

     

    void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) {

        sp<AMessage> reply;

        CHECK(msg->findMessage("reply", &reply));

     

        if (mState != CONNECTED) {

            reply->setInt32("result", -ENOTCONN);

            reply->post();

            return;

        }

     

        …………………..

        size_t numBytesSent = 0;

        while (numBytesSent < request.size()) {

            ssize_t n =

                send(mSocket, request.c_str() + numBytesSent,

                     request.size() - numBytesSent, 0);-------通过sendrequest通过socket发送给服务器端

     

            if (n < 0 && errno == EINTR) {

                continue;

            }

     

            if (n <= 0) {

                performDisconnect();

     

                if (n == 0) {

                    // Server closed the connection.

                    ALOGE("Server unexpectedly closed the connection.");

     

                    reply->setInt32("result", ERROR_IO);

                    reply->post();

                } else {

                    ALOGE("Error sending rtsp request. (%s)", strerror(errno));

                    reply->setInt32("result", -errno);

                    reply->post();

                }

     

                return;

            }

     

            numBytesSent += (size_t)n;

        }

     

        mPendingRequests.add(cseq, reply);

    }

     

    在等待服务器的response后,我们又回到MyHandler.h的onMessageReceived函数:

     

      virtual void onMessageReceived(const sp<AMessage> &msg) {

            switch (msg->what()) {

                case 'desc':

                {

                    int32_t result;

                    CHECK(msg->findInt32("result", &result));

     

                    ALOGI("DESCRIBE completed with result %d (%s)",

                         result, strerror(-result));

     

                    if (result == OK) {

                        sp<RefBase> obj;

                        CHECK(msg->findObject("response", &obj));

                        sp<ARTSPResponse> response =

                            static_cast<ARTSPResponse *>(obj.get());

    …………………………….

                        if (response->mStatusCode != 200) {

                            result = UNKNOWN_ERROR;

                        } else {

                            mSessionDesc = new ASessionDescription; ---媒体流的演示描述,该文件提供的信息定位视频服务地址(包括视频服务器地址和端口号)及视频服务的编码方式等信息

                            mSessionDesc->setTo(

                                   response->mContent->data(),

                                   response->mContent->size());

     

    …………..                        

                                 if (mSessionDesc->countTracks() < 2) {

                                    // There's no actual tracks in this session.

                                    // The first "track" is merely session meta

                                    // data.

     

                                    ALOGW("Session doesn't contain any playable "

                                         "tracks. Aborting.");

                                    result = ERROR_UNSUPPORTED;

                                } else {

                                   setupTrack(1);--------此处到了我们RTSP中的所有的操作中SETUP步骤

                                }

                            }

                        }

                    }

                    if (result != OK) {

                        sp<AMessage> reply = new AMessage('disc', id());

                        mConn->disconnect(reply);

                    }

                    break;

                }

    }

     

    bool ASessionDescription::setTo(const void *data, size_t size) {

        mIsValid = parse(data, size);---解析该SessionDescription

     

        if (!mIsValid) {

            mTracks.clear();

            mFormats.clear();

        }

        return mIsValid;

    }

     

     

     

    到此我们连接上web服务器,并从web服务器获取sessionDescription分析完了,具体还得大伙慢慢琢磨。下篇我们将要开始跟流媒体服务打交道了!


    十三、流媒体具体流程(三)

          


    上一篇我们讲到了从web server 中获取了sessiondescription,并解析出了media server的路径和一些基本的媒体信息。下面我们开始讲述如何跟mediaserver建立连接并控制服务器端和客户端以达到播放,暂停,停止的目的。

    首先跟media server建立连接 SETUP:

    具体的格式如下(UDP):

    C->A(audio): SETUPrtsp://audio.com/twister/audio.en RTSP/1.0

    CSeq: 1

    Transport:RTP/AVP/UDP;unicast

    ;client_port=3056-3057

    具体到代码的话,我们看myHandler.h中的setupTrack函数:

       void setupTrack(size_t index) {

            sp<APacketSource> source =

                new APacketSource(mSessionDesc,index);

    ……………………….

            AString url;

            CHECK(mSessionDesc->findAttribute(index,"a=control", &url));

     

            AString trackURL;

            CHECK(MakeURL(mBaseURL.c_str(),url.c_str(), &trackURL));----检查session description中取出media serverURL是否正确

            …………

     

            AString request= "SETUP ";

           request.append(trackURL);

            request.append("RTSP/1.0\r\n");------拼接request字符

     

    选择TCP连接还是ARTP连接,

            if (mTryTCPInterleaving) {

                size_t interleaveIndex = 2 *(mTracks.size() - 1);

                info->mUsingInterleavedTCP =true;

                info->mRTPSocket =interleaveIndex;

                info->mRTCPSocket =interleaveIndex + 1;

     

               request.append("Transport: RTP/AVP/TCP;interleaved=");

               request.append(interleaveIndex);

               request.append("-");

               request.append(interleaveIndex + 1);

            } else {

                unsigned rtpPort;

                ARTPConnection::MakePortPair(

                        &info->mRTPSocket,&info->mRTCPSocket, &rtpPort);

     

                if (mUIDValid) {

                   HTTPBase::RegisterSocketUserTag(info->mRTPSocket, mUID,

                                                   (uint32_t)*(uint32_t*) "RTP_");

                   HTTPBase::RegisterSocketUserTag(info->mRTCPSocket, mUID,

                                                    (uint32_t)*(uint32_t*)"RTP_");

                }

     

                request.append("Transport:RTP/AVP/UDP;unicast;client_port=");

               request.append(rtpPort);

               request.append("-");

                request.append(rtpPort+ 1);

            }

     

            request.append("\r\n");

     

            if (index > 1) {

                request.append("Session:");

                request.append(mSessionID);

                request.append("\r\n");

            }

     

            request.append("\r\n");

     

            sp<AMessage> reply = newAMessage('setu', id());

            reply->setSize("index",index);

           reply->setSize("track-index", mTracks.size() - 1);

            mConn->sendRequest(request.c_str(),reply);-----发送给服务器端,等待回复,返回的Amessage是“setu

    }

       

     

    假设收到服务端的连接成功的消息,我们看看myHandler.h中onMessageReceived对应的”setu”如何处理,按道理应该回复回来的信息如下(UDP):

    A->C: RTSP/1.0200 OK

    CSeq: 1

    Session: 12345678

    Transport:RTP/AVP/UDP;unicast

    ;client_port=3056-3057;

    ;server_port=5000-5001

     

     

    virtualvoid onMessageReceived(const sp<AMessage> &msg) {

    ……

        case 'setu':

                {

                    ……………………….

                    int32_t result;

                   CHECK(msg->findInt32("result", &result));

     

                    ALOGI("SETUP(%d) completedwith result %d (%s)",

                         index, result,strerror(-result));

     

                    if (result == OK) {

                        CHECK(track != NULL);

     

                        sp<RefBase> obj;

                        CHECK(msg->findObject("response",&obj));

                        sp<ARTSPResponse>response =

                           static_cast<ARTSPResponse *>(obj.get());

     

                        if(response->mStatusCode != 200) {

                            result = UNKNOWN_ERROR;

                        } else {

                           ssize_t i = response->mHeaders.indexOfKey("session");-------查找session id

                            CHECK_GE(i, 0);

     

                           mSessionID = response->mHeaders.valueAt(i);

     

    ………………………..

     

                            i =mSessionID.find(";");

                            if (i >= 0) {

                                // Remove options,i.e. ";timeout=90"

                                mSessionID.erase(i,mSessionID.size() - i);

                            }

     

                            i = response->mHeaders.indexOfKey("server");---server

                            if (i >= 0) {

                                AString server =response->mHeaders.valueAt(i);

                                if(server.startsWith("XenonStreamer")

                                        ||server.startsWith("XTream")) {

                                    ALOGI("Usefake timestamps");

                                    mUseSR = false;

                                }

                            }

     

                            sp<AMessage>notify = new AMessage('accu', id());

                           notify->setSize("track-index", trackIndex);

     

                            i =response->mHeaders.indexOfKey("transport");---transport

                            CHECK_GE(i, 0);

     

                            if(track->mRTPSocket != -1 && track->mRTCPSocket != -1) {

                                if(!track->mUsingInterleavedTCP) {

                                    AStringtransport = response->mHeaders.valueAt(i);

     

     

    ……………….

                    ++index;

                    if (result == OK &&index < mSessionDesc->countTracks()) {

                        setupTrack(index);----一般有两条track,先是audio track然后是videotrack

                    } else if(mSetupTracksSuccessful) {

    建立完成后就可以“PLAY”了

                        ++mKeepAliveGeneration;

                        postKeepAlive();

     

                        AStringrequest = "PLAY ";---------发送”PLAY”请求给服务器端

                       request.append(mControlURL);

                       request.append(" RTSP/1.0\r\n");

     

                       request.append("Session: ");

                       request.append(mSessionID);

                        request.append("\r\n");

     

                       request.append("\r\n");

     

                       sp<AMessage> reply = new AMessage('play', id());

                       mConn->sendRequest(request.c_str(), reply);

                    } else {

                        sp<AMessage> reply = newAMessage('disc', id());

                       mConn->disconnect(reply);

                    }

                    break;

                }

     

    完成“SETUP”阶段就可以“PLAY”了,发送给服务器端的格式如下:

    C->V:PLAY rtsp://video.com/twister/video RTSP/1.0

    CSeq: 2

    Session:23456789

    Range:smpte=0:10:00-

    代码在myHandler.h中onMessageReceived对应的”setu”。

    下面我们分析下服务器端返回后客户端如何处理“PLAY”。还是在myHandler.h中onMessageReceived函数:

     

                case 'play':

                {

                    ………..

     

                    if (result == OK) {

                        sp<RefBase> obj;

                       CHECK(msg->findObject("response", &obj));

                        sp<ARTSPResponse>response =

                            static_cast<ARTSPResponse*>(obj.get());

     

                        if(response->mStatusCode != 200) {

                            result = UNKNOWN_ERROR;

                        } else {

                            parsePlayResponse(response);---解析response回来的数据

     

    ………………

                    }

     

                    if (result != OK) {

                        sp<AMessage> reply =new AMessage('disc', id());

                       mConn->disconnect(reply);

                    }

     

                    break;

                }

    response回来的格式一般如下:

    V->C:RTSP/1.0 200 OK

    CSeq: 2

    Session:23456789

    Range:smpte=0:10:00-0:20:00------------------播放从10分钟到20分钟时间段的视频

    RTP-Info:url=rtsp://video.com/twister/video

    ;seq=12312232;rtptime=78712811

     

     

    voidparsePlayResponse(const sp<ARTSPResponse> &response) {

            if (mTracks.size() == 0) {

                ALOGV("parsePlayResponse: latepackets ignored.");

                return;

            }

     

            mPlayResponseReceived = true;

     

            ssize_t i =response->mHeaders.indexOfKey("range");

    …………

            AString range = response->mHeaders.valueAt(i);

    ………………

     

            i =response->mHeaders.indexOfKey("rtp-info");

            CHECK_GE(i, 0);

     

            AString rtpInfo =response->mHeaders.valueAt(i);

            List<AString> streamInfos;

            SplitString(rtpInfo, ",",&streamInfos);

     

            int n = 1;

            for (List<AString>::iterator it =streamInfos.begin();

                 it != streamInfos.end(); ++it) {

                (*it).trim();

                ALOGV("streamInfo[%d] =%s", n, (*it).c_str());

     

                CHECK(GetAttribute((*it).c_str(),"url", &val));

     

                size_t trackIndex = 0;

                while (trackIndex <mTracks.size()) {

                    size_t startpos = 0;

                    if(mTracks.editItemAt(trackIndex).mURL.size() >= val.size()) {

                        startpos =mTracks.editItemAt(trackIndex).mURL.size() - val.size();

                    }

                    // Use AString::find in orderto allow the url in the RTP-Info to be a

                    // truncated variant (example:"url=trackID=1") of the complete SETUP url

                    if(mTracks.editItemAt(trackIndex).mURL.find(val.c_str(), startpos) == -1) {

                        ++trackIndex;

                    } else {

                        // Found track

                        break;

                    }

                }

                CHECK_LT(trackIndex,mTracks.size());

     

                char *end;

                unsigned long seq = 0;

                if (GetAttribute((*it).c_str(),"seq", &val)) {

                    seq = strtoul(val.c_str(),&end, 10);

                } else {

                   CHECK(GetAttribute((*it).c_str(), "rtptime", &val));

                }

     

                TrackInfo *info = &mTracks.editItemAt(trackIndex);

                info->mFirstSeqNumInSegment =seq;

                info->mNewSegment = true;

     

                uint32_t rtpTime = 0;

                if (GetAttribute((*it).c_str(),"rtptime", &val)) {

                    rtpTime = strtoul(val.c_str(),&end, 10);

                    mReceivedRTPTime = true;

                    ALOGV("track #%d:rtpTime=%u <=> npt=%.2f", n, rtpTime, npt1);

                } else {

                    ALOGV("no rtptime in playresponse: track #%d: rtpTime=%u <=> npt=%.2f", n,

                            rtpTime, npt1);

                   CHECK(GetAttribute((*it).c_str(), "seq", &val));

                }

     

                info->mRTPAnchor = rtpTime;

                mLastMediaTimeUs = (int64_t)(npt1 *1E6);

                mMediaAnchorUs = mLastMediaTimeUs;

     

                // Removing packets with old RTPtimestamps

                while (!info->mPackets.empty()){

                    sp<ABuffer> accessUnit =*info->mPackets.begin();

                    uint32_t firstRtpTime;

                   CHECK(accessUnit->meta()->findInt32("rtp-time", (int32_t*)&firstRtpTime));

                    if (firstRtpTime == rtpTime) {

                        break;

                    }

                   info->mPackets.erase(info->mPackets.begin());

                }

                ++n;

            }

       

     

    至此video source 和audiosource就可以通过RTP不断的往客户端发送,客户端拿到这些数据就可以通过相应的解码器解析播放了。

    我们的流媒体播放流程也讲得差不多了,如何关闭两端的流程就由大伙自己去看了。但是大家要注意一点有时候一些服务在关闭的时候没有发回“TEARDOWN”的response。


      转载请注明出处:太妃糖出品 http://blog.csdn.net/tjy1985/article/details/7894305


    展开全文
  • 《实用多媒体技术》课程习题解答第一章 多媒体计算机概述单项选择题1-6:1、请根据多媒体的特性判断以下哪些属于多媒体的范畴?(1)交互式视频游戏 (2)有声图书 (3)彩色画报 (4)彩色电视(A)仅(1) ...
    《实用多媒体技术》课程习题解答

    第一章  多媒体计算机概述

    单项选择题1-6:

    1、请根据多媒体的特性判断以下哪些属于多媒体的范畴?

    (1)交互式视频游戏   (2)有声图书   (3)彩色画报       (4)彩色电视

    (A)仅(1)  (B)(1)(2)    (C)(1)(2)(3)    (D)全部

    答:(B)      

    2、下列哪些不是多媒体核心软件?

    (1)AVSS      (2)AVK     (3)DOS       (4)Amiga Vision

    (A)(3)       (B)(4)    (C)(3)(4)    (D)(1)(2)

    答:(A)      

    3、要把一台普通的计算机变成多媒体计算机要解决的关键技术是:

    (1)视频音频信号的获取                 (2)多媒体数据压编码和解码技术

    (3)视频音频数据的实时处理和特技       (4)视频音频数据的输出技术

    (A)(1)(2)(3)       (B)(1)(2)(4)    (C)(1)(3)(4)    (D)全部

    答:(D)     

    4、Commodore公司在1985年率先在世界上推出了第一个多媒体计算机系统Amiga,其主要功能是:

    (1)用硬件显示移动数据,允许高速的动画制作;

    (2)显示同步协处理器;

    (3)控制25个通道的DMA,使CPU以最小的开销处理盘、声音和视频信息;

    (4)从28Hz震荡器产生系统时钟;

    (5)为视频RAM(VRAM)和扩展RAM卡提供所有的控制信号;

    (6)为VRAM和扩展RAM卡提供地址。

    (A)(1)(2)(3)       (B)(2)(3)(5)    (C)(4)(5)(6)    (D)全部

    答:(D)

    5、国际标准MPEG-II采用了分层的编码体系,提供了四种技术,它们是:

    (1)空间可扩展性;信噪比可扩充性;框架技术;等级技术。

    (2)时间可扩充性;空间可扩展性;硬件扩展技术;软件扩展技术。

    (3)数据分块技术;空间可扩展性;信噪比可扩充性;框架技术。

    (4)空间可扩展性;时间可扩充性;信噪比可扩充性;数据分块技术。

    (A)(1)       (B)(2)    (C)(3)    (D)(4)

    答:(D)

    6、多媒体技术未来发展的方向是:

    (1)高分辨率,提高显示质量;             (2)高速度化,缩短处理时间;

    (3)简单化,便于操作;                   (4)智能化,提高信息识别能力。

    (A)(1)(2)(3)       (B(1)(2)(4)    (C)(1)(3)(4)    (D)全部

    答:(D)

    7、简述多媒体计算机的关键技术及其主要应用领域?

    答:多媒体计算机的关键技术是:(1)视频音频信号获取技术;(2)多媒体数据压缩编码和解码技术;(3)视频音频数据的实时处理和特技;(4)视频音频数据的输出技术。

    多媒体技术促进了通信、娱乐和计算机的融合。多媒体计算机的主要应用领域有三个方面:(1)多媒体技术是解决常规电视数字化及高清晰度电视(HDTV)切实可行的方案。采用多媒体计算机技术制造HDTV,它可支持任意分辨率的输出,输入输出分辨率可以独立,输出分辨率可以任意变化,可以用任意窗口尺寸输出。与此同时,它还赋予HDTV很多新的功能,如图形功能,视频音频特技以及交互功能。多媒体计算机技术在常规电视和高清晰度电视的影视节目制作中的应用可分成两个层次:一是影视画面的制作:采用计算机软件生成二维、三维动画画面;摄象机在摄影真实的影视画面后采用数字图象处理技术制作影视特技画面,最后是采用计算机将生成和实时结合用图象处理技术制作影视特技画面。另一个层次是影视后期制作,如现在常用的数字式非线性编辑器,实质上是一台多媒体计算机,它需要有广播级质量的视频音频的获取和输出、压缩解压缩,实时处理和特技以及编辑功能。(2)用多媒体技术制作V-CD及影视音响卡拉OK机。多媒体数据压缩和解压缩技术是多媒体计算机系统中的关键技术,V-CD就是利用MPEG-I的音频编码技术将压缩到原来的六分之一。(3)采用多媒体技术创造PIC(个人信息通信中心),即采用多媒体技术使一台个人计算机具有录音电话机、可视电话机、图文传真机、立体声音响设备、电视机和录像机等多种功能,即完成通信、娱乐和计算机的功能。如果计算机再配备丰富的软件连接上网,还可以完成许多功能进一步提高用户的工作效率。

     

    第二章  音频信息的获取与处理

    单项选择题1-9:

    1、数字音频采样和量化过程所用的主要硬件是:

    (A)数字编码器                          (B)数字解码器  

    (C)模拟到数字的转换器(A/ D转换器)    (D)数字到模拟的转换器(D/ A转换器)

    答:(C)

    2、音频卡是按( )分类的。

    (A)采样频率       (B)声道数      (C)采样量化位数    (D)压缩方式

    答:(C)

    3、两分钟双声道,16位采样位数,22.05kHz采样频率声音的不压缩的数据量是:

    (A)5.05MB      (B)10.58MB      (C)10.35MB     (D)10.09MB

    答:(D)

    4、目前音频卡具备以下( )功能。

    (1)录制和回放数字音频文件          (2)混音

    (3)语音特征识别                    (4)实时解/压缩数字单频文件

    (A)(1)(3)(4)       (B)(1)(2)(4)    (C)(2)(3)(4)    (D)全部

    答:(B)      

    5、以下的采样频率中哪个是目前音频卡所支持的。

    (A)20kHz      (B)22.05 kHz      (C)100 kHz     (D)50 kHz

    答:(B)

    6、1984年公布的音频编码标准G.721,它采用的是( )编码。

    (A)均匀量化       (B)自适应量化      (C)自适应差分脉冲    (D)线性预测

    答:(C)

    7、AC-3数字音频编码提供了五个声道的频率范围是:

    (A)20Hz到2 kHz            (B)100Hz到1 kHz

    (C)20Hz到20 kHz           (D)20Hz到200 kHz

    答:(C))

    8、MIDI的音乐合成器有:

    (1)FM          (2)波表          (3)复音             (4)音轨

    (A)仅(1)       (B)(1)(2)    (C)(1)(2)(3)    (D)全部

    答:(B)      

    9、下列采集的波形声音质量最好的是:

    (A)单声道、8位量化、22.05 kHz采样频率

    (B) 双声道、8位量化、44.1 kHz采样频率

    (C)单声道、16位量化、22.05 kHz采样频率

    (D)  双声道、16位量化、44.1 kHz采样频率

    答:(D)

    10、简述音频编码的分类及常用编码算法和标准。

    答:音频编码分为:

    (1)基于音频数据的统计特性进行编码,其典型技术是波形编码。其目标是使重建语音波形保持原波形的形状,PCM(脉冲编码调制)是最简单的编码方法。还有差值量化(DPCM)、自适应量化(APCM)和自适应预测编码(ADPCM)等算法。

    (2)基于音频声学参数进行参数编码,可进一步降低数据率。其目标是使重建音频保持原音频特性。常用的音频参数有共振峰、线性预测系数、滤波器组等。这种编码技术的优点是数据率低,但还原信号的质量较差,自然度低。

    (3)基于人的听觉特性进行编码。从人的听觉系统出发,利用掩蔽效应设计心理学模型,从而实现更高效率的数字音频压缩。而最有代表性的是MPEG标准中的高频编码和Dolby AC-3。

    国际电报电话咨询委员会(CCITT)和国际标准化组织(ISO)提出了一系列有关音频编码算法和国际标准。如G.711 64Kbps(A)律PCM编码标准、G7. 21采用ADPCM数据率为32bps。还有G.722、G.723、G.727和G.728等。

     

    第三章  视频信号的获取与处理

    单项选择题1-10:

    1、国际上常用的视频制式有:

    (1)PAL制          (2)NTSC制       (3)SECAM制        (4)MPEG

    (A)(1)       (B)(1)(2)    (C)(1)(2)(3)    (D)全部

    答:(C)

    2、全电视信号主要由( )组成。

    (A)图象信号、同步信号、消隐信号          (B)图象信号、亮度信号、色度信号

    (C)图象信号、复合同步信号、复合消隐信号  (D)图象信号、复合同步信号、复合色度信号

    答:(C)

    3、视频卡的种类很多,主要包括:

    (1)视频捕获卡          (2)电影卡    (3)电视卡      (4)视频转换卡

    (A)(1)       (B)(1)(2)    (C)(1)(2)(3)    (D)全部

    答:(D)     

    4、在YUV彩色空间中数字化后Y:U:V是:

    (A)4:2:2       (B)8:4:2       (C)8:2:4        (D)8:4:4

    答:(D)     

    5、彩色全电视信号主要由( )组成。

    (A)          图象信号、亮度信号、色度信号、复合消隐信号

    (B) 亮度信号、色度信号、复合同步信号、复合消隐信号

    (C)          图象信号、复合同步信号、消隐信号、亮度信号

    (D)         亮度信号、同步信号、复合消隐信号、色度信号

    答:(B)

    6、数字视频编码的方式有哪些:

    (1)RGB视频          (2)YUV视频    (3)Y/C(S)视频      (4)复合视频

    (A)仅(1)       (B)(1)(2)    (C)(1)(2)(3)    (D)全部

    答:(D)     

    7、在多媒体计算机中常用的图象输入设备是:

    (1)数码照相机      2)彩色扫描仪    (3)视频信号数字化仪     (4)彩色摄象机

    (A)仅(1)       (B)(1)(2)    (C)(1)(2)(3)    (D)全部

    答:(D)     

    8、视频采集卡能支持多种视频源输入,下列哪些是视频采集卡支持的视频源:

    (1)放像机      2)摄象机     (3)影碟机     (4)CD-ROM

    (A)仅(1)       (B)(1)(2)    (C)(1)(2)(3)    (D)全部

    答:(C)      

    9、下列数字视频中哪个质量最好:

    (A)      240´180 分辨率、24位真彩色、15帧/秒的帧率

    (B)      320´240 分辨率、30位真彩色、25帧/秒的帧率

    (C)      320´240 分辨率、30位真彩色、30帧/秒的帧率

    (D)     640´480 分辨率、16位真彩色、15帧/秒的帧率

    答:(C)      

    10、视频采集卡中与VGA的数据连线有什么作用:

    (1)直接将视频信号送到VGA显示上显示

    (2)提供Overlay(覆盖)功能

    (3)与VGA交换数据               (4)没有什么作用,可连可不连。

    (A)仅(1)       (B)(1)(2)    (C)(1)(2)(3)    (D)仅(4)

    答:(C)

    11、简述视频信息获取的流程,并画出视频信息获取的流程框图。

    答:(1)视频信息获取的基本流程概述为:

    彩色全电视信号经过采集设备分解成模拟的R、G、B信号或Y、U、V信号,然后进行各个分量的A/D变换、解码、将模拟的R、G、B信号或Y、U、V信号变换成数字信号的R、G、B信号或Y、U、V信号,存入帧存储器。主机可通过总线对帧存储器中的图象数据进行处理,帧存储器的数据R、G、B信号或Y、U、V信号经过D/A变换转成模拟的R、G、B信号或Y、U、V信号,再经过编码器合成彩色电视信号,输出到显示器上。

     

    彩色全电

    视信号
     
    (2)视频信息获取流程框图如下:

        
     

     

     

     

     

     

     

     

     

     

    12、简述视频信号获取器的工作原理。

    答:视频信号获取器的工作原理概述如下:

    从彩色摄象机、录象机或其他视频信号源得到的彩色全电视信号首先到视频模拟输入端口,首先需要解决行同步信号和场同步信号(包括奇数场同步信号和偶数场同步信号)的分离问题,即采用限幅的方法,将场同步和行同步信号与图象信号分开,然后用积分和微分的方法获得场同步信号和行同步信号,再根据奇数场同步信号在一行的开始和偶数场同步信号在一行的中间,得到奇数场同步信号和偶数场同步信号。然后送到具有钳位电路和自动增益的运算放大器,最后经过A/D变换器将彩色全电视信号转换成8位数字信号,送给彩色多制式数字解码器。经过多制式数字解码器解码后得到Y、U、V数据,然后由视频窗口控制器对其进行剪裁,改变比例后存入帧存储器,帧存储器的内容在窗口控制器的控制下与VGA信号或视频编码器的同步信号同步,再送到D/A变换器,模拟彩空间变换矩阵,同时送到数字式视频编辑器进行视频编码,最后输出到VGA监视器及电视机或录象机。

     

    第四章  多媒体数据压缩编码技术

    单项选择题1-7:

    1、下列哪些说法是正确的:

    (1)冗余压缩法不会减少信息量,可以原样恢复原始数据。

    (2)冗余压缩法减少冗余,不能原样恢复原始数据。

    (3)冗余压缩法是有损压缩法。、

    (4)冗余压缩的压缩比一般都比较小。

    (A)(1)(3)       (B)(1)(4)    (C)(1)(3)(4)    (D)仅(3)

    答:(B)

    2、图象序列中的两幅相邻图象,后一幅图象与前一幅图象之间有较大的相关,这是:

    (A)空间冗余       (B)时间冗余    (C)信息熵冗余   (D)视觉冗余

    答:(B)

    3、下列哪一种说法是不正确的:

    (A)预测编码是一种只能针对空间冗余进行压缩的方法

    (B) 预测编码是根据某一种模型进行的

    (C)预测编码需将预测的误差进行存储或传输

    (D)         预测编码中典型的压缩方法有DPCM、ADPCM

    答:(A)

    4、下列哪一种说法是正确的:

    (A)信息量等于数据量与冗余量之和

    (B) 信息量等于信息熵与数据量之差

    (C)信息量等于数据量与冗余量之差

    (D)         信息量等于信息熵与冗余量之和

    答:(C)

    5、P´64K是视频通信编码标准,要支持通用中间格式CIF,要求P至少为:

    (A)1       (B)2      (C)4      (D)6

    答:(D)

    6、在MPEG中为了提高数据压缩比,采用了哪些方法:

    (A)运动补偿与运行估计                 (B)减少时域冗余与空间冗余

    (C)帧内图象数据与帧间图象数据压缩     (D)向前预测与向后预测

    答:(C)      

    7、在JPEG中使用了哪两种熵编码方法:

    (A)统计编码和算术编码                  (B)PCM编码和DPCM编码

    (C)预测编码和变换编码                  (D)哈夫曼编码和自适应二进制算术编码

    答:(D)

    8、简述MPEG和JPEG的主要差别。

    答:MPEG视频压缩技术是针对运动图象的数据压缩技术。为了提高压缩比,帧内图象数据和帧间图象数据压缩技术必须同时使用。

    MPEG通过帧运动补偿有效地压缩了数据的比特数,它采用了三种图象,帧内图、预测图和双向预测图。有效地减少了冗余信息。对于MPEG来说,帧间数据压缩、运动补偿和双向预测,这是和JPEG主要不同的地方。而JPEG和MPEG相同的地方均采用了DCT帧内图象数据压缩编码。

    在JPEG压缩算法中,针对静态图象对DCT系数采用等宽量化,而是MPEG中视频信号包含有静止画面(帧内图)和运动信息(帧间预测图)等不同的内容,量化器的设计不能采用等宽量化需要作特殊考虑。从两方面设计,一是量化器综合行程编码能使大部分数据得到压缩;另一方面是通过量化器、编码器使之输出一个与信道传输速率匹配的比特流。

    8、信源符号及其概率如下:

    a
        

    a1
        

    a2
        

    a3
        

    a4
        

    a5

    p(a)
        

    0.5
        

    0.25
        

    0.125
        

    0.0625
        

    0.0625

    求其Huffman编码,信息熵及平均码长。

    解:

    a1    0.5----------------------------------------------------------------0----------    0

    a2   0.25----------------------------------------------0------0.5------1          10

    a3  0.125--------------------------0------0.25-------1                     110

    a4  0.625-------0-----0.125------1                                 1110

    a5  0.625-------1                                              1111

    则:a1=0  a2=10  a3=110  a4=1110  a5=1111

    信息熵:

    a1-a5码长分别为1,2,3,4,4

    则平均码长

    10、详述JPEG静态图象压缩编码原理及实现技术。

    答:JPEG是由国际电报咨询委员会(CCITT)和国际标准化协会(OSI)联合组成的一个图象专家小组开发研制的连续色调、多级灰度、静止图象的数字图象压缩编码方法。JPEG适于静止图象的压缩,此外,电视图象序列的帧内图象的压缩编码也常采用JPEG压缩标准。JPEG数字图象压缩文件作为一种数据类型,如同文本和图形文件一样地存储和传输。基于离散余弦变换(DCT)的编码方法是JPEG算法的核心内容。算法的编解码过程如教材136页图4.25-4.26所示。编码处理过程包括原图象数据输入、正向DCT变换器、量化器、熵编码器和压缩图象数据的输出,除此之外还附有量化表和熵编码表(即哈夫曼表);接收端由信道收到压缩图象数据流后,经过熵解码器、逆量化器、逆变换(IDCT),恢复并重构出数字图象,量化表和熵编码表同发送端完全一致。编码原图象输入,可以是单色图象的灰度值,也可以是彩色图象的亮度分量或色差分量信号。DCT的变换压缩是对一系列8*8采样数据作块变换压缩处理,可以对一幅像,从左到右、从上到下、一块一块(8*8/块)地变换压缩,或者对多幅图轮流取8*8采样数据块压缩。解码输出数据,需按照编码时的分块顺序作重构处理,得到恢复数字图象。

    具体的实现技术如下:

    (1)首先把一幅图象分8*8的子块按图中的框图进行离散余弦正变换(FDCT)和离散余弦逆变换(IDCT)。

    在编码器的输入端,原始图象被分成一系列8*8的块,作为离散余弦正变换(FDCT)的输入。在解码器的输出端,离散余弦逆变换(IDCT)输出许多8*8的数据块,用以重构图象。8*8 FDCT和8*8 IDCT数学定义表达式如下:

    FDCT:

    IDCT:

    两式中,C(u),C(v)= , 当u=v=0

            C(u),C(v)=1  ,   其它情况

    离散余弦正变换(FDCT)可看作为一个谐波分析仪,把离散余弦逆变换(IDCT)看作一个谐波合成器。每个8*8二维原图象采样数据块,实际上是64点离散信号,该信号是空间二维参数x和y的函数。FDCT把这些信号作为输入,然后把它分解成64个正交基信号,每个正交信号对应于64个二维(2D)空间频率中的一个,这些空间频率是由输入信号的频谱组成。FDCT的输出是64个基信号的幅值(即DCT系数),每个系数值由64点输入信号唯一地确定,即离散余弦变换的变换系数。在频域平面上变换系数二维频域变量u和v的函数。对应于u=0,v=0的系数,称做直流分量(DC系数),其余63个系数称做交流分量(AC系数)。因为在一幅图象中像素之间的灰度或色差信号变化缓慢,在8*8子块中像素之间相关性很强,所以通过离散余弦正变换处理后,在空间频率低频范围内集中了数值大的系数,这样为数据压缩提供了可能。远离直流系数的高频交流系数大多为零或趋于零。如果FDCT和IDCT变换计算中计算精度足够高,并且DCT系数没有被量化,那么原始的64点信号就能精确地恢复。

    (2)量化

    为了达到压缩数据的目的,对DCT 系数F(u,v)需作量化处理。量化处理是一个多到一的映射它是造成DCT编解码信息损失的根源。在JPEG标准中采用线性均匀量化器。量化定义为,对 64个DCT变换系数F(u,v)除以量化步长Q(u,v)后四舍五入取整。即量化器步长是量化表的元素,量化表元素随DCT变换系数的位置而改变,同一像素的亮度量化表和色差量化表不同值,量化表的尺寸也是64,与64个变换系数一一对应。量化表中的每一个元素值为1至255之间的任意整数,其值规定了对应位置变换系数的量化器步长。在接收端要进行逆量化,逆量化的计算公式为:

    不同频率的余弦函数对视觉的影响不同,量化处理是在一定的主观保真度图像质量的前提下,可据不同频率的视觉阈值来选择量化表中的元素值的大小。根据心理视觉加权函数得到亮度化表和色度量化表。DCT变换系数F(u,v)除以量化表中对应位置的量化步长,其幅值下降,动态范围变窄,高频系数的零值数目增加。

    (3)熵编码

    为进一步达到压缩数据的目的,需对量化后的DC 系数和行程编码后的AC系数进行基于统计特性的熵编码。64个变换系数经量化后,坐标u=v=0的值是直流分量(即DC系数)。DC系数是64个图像采样平均值。因为相邻的8×8块之间有强的相关性,所以相邻块的DC系数值很接近,对量化后前后两块之间的DC系数差值进行编码,可以用较少的比特数。DC系数包含了整个图像能量的主要部分。经量化后的63个AC系数编码时从左上方AC(u=7,v=7)开始,沿箭头方向,以“Z”字形行程扫描,直到AC(u =7,v=7)扫描结束。量化后特编码的AC系数通常有许多零值,沿“Z”字形路径行进,可使零AC系数集中,便于使用行程编码方法。63个AC系数行程编码和码字,可用两个字节表示。JPEG建议使用两种熵编码方法:Huffman编码和自适应二进制算术编码。熵编码可分成两步进行,首先把DC和AC系数转换成一个中间格式的符号序列,第二步是给这些符号赋以变长码字。

     

    第五章  多媒体计算机硬件及软件系统结构

     单项选择题1-8:

    1、组成多媒体系统的途径有哪些:

    (1)直接设计和实现                     (2)增加多媒体升级套件进行扩展

    (3)CPU升级                          (4)增加CD-DA

    (A)仅(1)       (B)(1)(2)      (C)(1)(2)(3)      (D)全部

    答:(B)      

    2、下面硬件设备中哪些是多媒体硬件系统应包括的:

    (1)计算机最基本的硬件设备                     (2)CD-ROM

    (3)音频输入、输出和处理设备                   (4)多媒体通信传输设备

    (A)仅(1)       (B)(1)(2)      (C)(1)(2)(3)      (D)全部

    答:(C)

    3、MPC-2、MPC-3标准制定的时间分别是:

    (1)1992          (2)1993        (3)1994         (4)1995

    (A)(1)(3)       (B)(2)(4)      (C)(1)(4)      (D)都不是

    答:(B)

    4、下面哪些是MPC对音频处理能力的基本要求:

    (1)录入声波信号                     (2)处理声波信号

    (3)重放声波信号                     (4)用MIDI技术合成音乐

    (A)(1)(3)(4)       (B)(2)(3)(4)      (C)(1)(2)(3)      (D)全部

    答:(D)

    5、下面哪些是MPC对视频处理能力的基本要求:

    (1)播放已压缩好的较低质量的视频图象

    (2)实时采集视频图象

    (3)实时压缩视频图象

    (4)播放已压缩好的高质量分辨率的视频图象

    (A)仅(1)       (B)(1)(2)      (C)(1)(2)(3)      (D)全部

    答:(A)

    6、下面哪些是MMX技术的特点:

    (1)打包的数据类型                     (2)与IA结构安全兼容

    (3)64位的MMX寄存储器组            (4)增强的指令系统

    (A)(1)(3)(4)       (B)(2)(3)(4)      (C)(1)(2)(3)      (D)全部

    答:(D)

    7、下面哪些是称得上的多媒体操作系统:

    (1)Windows 98                     (2)Quick Time

    (3)AVSS                           (4)Authorware

    (A)(1)(3)       (B)(2)(4)      (C)(1)
    展开全文
  • 多媒体技术期末复习资料

    千次阅读 2019-01-11 22:02:32
    流媒体:流媒体(streaming media)是指...流媒体文件一般定义在bit层次结构,因此流数据包并不一定必须按照字节对齐,虽然通常的媒体文件都是按照这种字节对齐的方式打包的。流媒体的三大操作平台是微软公司、Real...
  • 带你走进多媒体世界:视频文件是怎么播放出来的

    千次阅读 多人点赞 2021-05-19 16:53:22
    维基百科的解释是:多媒体(Multimedia),在电脑应用系统中,组合两种或两种以上媒体的一种人机交互式资讯交流和传播媒体。使用的媒体包括文字、图片、照片、声音(包含音乐、语音旁白、特殊音效)、动画和影片,...
  • 兰州交通大学多媒体技术复习

    千次阅读 2016-01-04 12:11:50
    1. 概念1.1 媒体媒体是信息表示和传输的载体1.2 多媒体一般而言,不仅指多种媒体信息本身的有机组合,而且指处理和应用多媒体信息的相应技术。 因此,多媒体通常被当做多媒体技术的同义词。 通常可以把多媒体看做是...
  • 软件版本号如何定义

    千次阅读 2008-07-12 21:10:00
    Plus 属增强版,不过这种大部分是在程序界面及多媒体功能上增强。 Preview 预览版 Corporation & Enterprise 企业版 Standard 标准版 Mini 迷你版也叫精简版只有最基本的功能 Premium -- 贵价版 ...
  • 实用多媒体技术 课程习题及解答

    千次阅读 2018-11-13 04:58:09
    实用多媒体技术 课程习题及解答
  • IMS (IP多媒体子系统)

    千次阅读 2016-02-22 16:02:41
    IMS(IP Multimedia Subsystem)是IP多媒体子系统,是一种全新的多媒体业务形式,它能够满足现在的终端客户更新颖、更多样化多媒体业务的需求。 中文名 IP多媒体子系统 外文名 IMS(IP Multimedia Su
  • 第17章 多媒体通信系统技术

    千次阅读 2007-09-18 15:44:00
    第17章 多媒体通信系统技术 进入20世纪90年代以来,因特网对公众开放使人们使用电话线路的方式发生了明显的变化,这就是越来越多的人使用电话线路传输数据,而且增长的速度越来越快。公共网络正在将线路交换网络...
  • 10024.多媒体短信MMS

    万次阅读 2005-01-01 21:08:00
    MMS是Multimedia Messaging Service的缩写,中文意为多媒体短信业务,是按照3GPP的标准( 3GPP TS 23.140)和WAP论坛的标准(WAP-206和WAP-209)有关多媒体信息的标准开发的最新业务。它最大的特色就是支持多媒体...
  • 常用多媒体传输协议简介

    千次阅读 2019-03-08 16:39:20
    实时多媒体流应用程序需要及时传递信息,并且通常可以容忍一些数据包丢失以实现此目标。例如,音频应用中的分组丢失可能导致丢失一小部分音频数据,这可以通过适当的错误隐藏算法而变得不明显。[3]所述的传输控制...
  • 一:多媒体框架概述  jellybean 的多媒体跟以前的版本,通过对比没啥变化,最大的变化是google终于舍得给multimedia建个独立的git了(framework/av),等你好久了!也体现了media 在整个android系统中的重要...
  • 过去十年, IT变化日新月异。如果我们回溯到100年前的1900年代,也会感慨电对经济社会的改变同样如此之大,历史惊人的相似...未来软件将不断重新定义世界的万事万物,数据在软件冶炼工艺的作用下价值将不断被挖掘出来。
  • COM、COM+和DCOM的定义和区别

    万次阅读 2011-12-26 10:11:06
    解释COM、COM+和DCOM的定义和区别? COM是组件对象模型,是实现3/N层应用的基础,它的目的就是组件化,应用程序分层.DCOM是分布式的COM,也就说可以远程的创建,最初它利用远程自动化来实现,用注册VBR的方法来配置客户端...
  • 分类: Android2014-01-20 15:34 42人阅读 评论(0) 收藏 举报 智能手机mediaplayer源代码 ...为增强Android 多媒体系统的功能,...基于Android 多媒体系统的STagefright 框架,通过创建WMA 的文件解析单元和
  • 互连网络的定义

    千次阅读 2007-08-28 13:45:00
    IEEE802.2 在数据链路层的信息帧中定义了许多域,这些域使得多种高层协议可共享一个物理数据链路。数据链路层的介质访问控制网络介质的协议,IEEE MAC规则定义了MAC地址,以标识数据链路层中的多个设备。  网络层 ...
  • 电子商务的概念及定义

    万次阅读 2006-07-16 17:58:00
    电子商务的实质应该是一套完整的网络商务经营及管理信息系统。再具体一点,它是利用现有的计算机硬件设备、软件和网络基础设施,通过一定的协议连接起来的电子网络环境进行各种各样商务活动的方式。这是一个比较严格...
  • 银行终端ATM常见术语和定义

    千次阅读 2014-03-09 22:15:36
    ATM常见术语和定义 名词:   1. 银行卡 bank card 商业银行等金融机构及邮政储汇机构向社会发行的,具有消费信用、转账结算、存取现金等全部或部分功能的信用支付工具。   2. 磁条卡 magnetic stripe card ...