精华内容
下载资源
问答
  • MySQL 面试题

    万次阅读 多人点赞 2019-09-02 16:03:33
    1、普通索引:最基本的索引,没有任何约束。 2、唯一索引:与普通索引类似,但具有唯一性约束。 3、主键索引:特殊的唯一索引,不允许有空值。 4、复合索引:将多个列组合在一起创建索引,可以...

    MySQL 面试题

    MySQL 涉及的内容非常非常非常多,所以面试题也容易写的杂乱。当年,我们记着几个一定要掌握的重心:

    重点的题目添加了【重点】前缀。

    1. 索引。
    2. 锁。
    3. 事务和隔离级别。

    因为 MySQL 还会有部分内容和运维相关度比较高,所以本文我们分成两部分【开发】【运维】两部分。

    • 对于【开发】部分,我们需要掌握。
    • 对于【运维】部分,更多考验开发的知识储备情况,当然能回答出来是比较好的,特别是对于高级开发工程师、架构师等。

    开发

    为什么互联网公司一般选择 MySQL 而不是 Oracle?

    免费、流行、够用。

    ? 当然,这个回答要稍微润色下。不过一般,很少问这个问题了。

    数据库的三范式是什么?什么是反模式?

    艿艿:重点在于反模式的回答。实际开发中,不会严格遵守三范式。

    胖友直接看 《服务端指南 数据存储篇 | MySQL(07) 范式与反模式》

    MySQL 有哪些数据类型?

    MySQL 支持多种类型,大致可以分为三类:数值、日期/时间和字符串(字符)类型。具体可以看看 《MySQL 数据类型》 文档。

    • 正确的使用数据类型,对数据库的优化是非常重要的。

    ? MySQL 中 varchar 与 char 的区别?varchar(50) 中的 50 代表的涵义?

    • 1、varchar 与 char 的区别,char 是一种固定长度的类型,varchar 则是一种可变长度的类型。
    • 2、varchar(50) 中 50 的涵义最多存放 50 个字符。varchar(50) 和 (200) 存储 hello 所占空间一样,但后者在排序时会消耗更多内存,因为 ORDER BY col 采用 fixed_length 计算 col 长度(memory引擎也一样)。所以,实际场景下,选择合适的 varchar 长度还是有必要的。

    ? int(11) 中的 11 代表什么涵义?

    int(11) 中的 11 ,不影响字段存储的范围,只影响展示效果。具体可以看看 《MySQL 中 int 长度的意义》 文章。

    ? 金额(金钱)相关的数据,选择什么数据类型?

    • 方式一,使用 int 或者 bigint 类型。如果需要存储到分的维度,需要 *100 进行放大。
    • 方式二,使用 decimal 类型,避免精度丢失。如果使用 Java 语言时,需要使用 BigDecimal 进行对应。

    ? 一张表,里面有 ID 自增主键,当 insert 了 17 条记录之后,删除了第 15,16,17 条记录,再把 MySQL 重启,再 insert 一条记录,这条记录的 ID 是 18 还是 15?

    • 一般情况下,我们创建的表的类型是 InnoDB ,如果新增一条记录(不重启 MySQL 的情况下),这条记录的 ID 是18 ;但是如果重启 MySQL 的话,这条记录的 ID 是 15 。因为 InnoDB 表只把自增主键的最大 ID 记录到内存中,所以重启数据库或者对表 OPTIMIZE 操作,都会使最大 ID 丢失。
    • 但是,如果我们使用表的类型是 MyISAM ,那么这条记录的 ID 就是 18 。因为 MyISAM 表会把自增主键的最大 ID 记录到数据文件里面,重启 MYSQL 后,自增主键的最大 ID 也不会丢失。

    最后,还可以跟面试官装个 x ,生产数据,不建议进行物理删除记录。

    ? 表中有大字段 X(例如:text 类型),且字段 X 不会经常更新,以读为为主,请问您是选择拆成子表,还是继续放一起?写出您这样选择的理由

    • 拆带来的问题:连接消耗 + 存储拆分空间。

      如果能容忍拆分带来的空间问题,拆的话最好和经常要查询的表的主键在物理结构上放置在一起(分区) 顺序 IO ,减少连接消耗,最后这是一个文本列再加上一个全文索引来尽量抵消连接消耗。

    • 不拆可能带来的问题:查询性能。

      如果能容忍不拆分带来的查询性能损失的话,上面的方案在某个极致条件下肯定会出现问题,那么不拆就是最好的选择。

    实际场景下,例如说商品表数据量比较大的情况下,会将商品描述单独存储到一个表中。即,使用拆的方案。

    MySQL 有哪些存储引擎?

    MySQL 提供了多种的存储引擎:

    • InnoDB
    • MyISAM
    • MRG_MYISAM
    • MEMORY
    • CSV
    • ARCHIVE
    • BLACKHOLE
    • PERFORMANCE_SCHEMA
    • FEDERATED

    具体每种存储引擎的介绍,可以看看 《数据库存储引擎》

    ? 如何选择合适的存储引擎?

    提供几个选择标准,然后按照标准,选择对应的存储引擎即可,也可以根据 常用引擎对比 来选择你使用的存储引擎。使用哪种引擎需要根据需求灵活选择,一个数据库中多个表可以使用不同的引擎以满足各种性能和实际需求。使用合适的存储引擎,将会提高整个数据库的性能。

    1. 是否需要支持事务。

    2. 对索引和缓存的支持。

    3. 是否需要使用热备。

    4. 崩溃恢复,能否接受崩溃。

    5. 存储的限制。

    6. 是否需要外键支持。

      艿艿:目前开发已经不考虑外键,主要原因是性能。具体可以看看 《从 MySQL 物理外键开始的思考》 文章。

    目前,MySQL 默认的存储引擎是 InnoDB ,并且也是最主流的选择。主要原因如下:

    • 【最重要】支持事务。
    • 支持行级锁和表级锁,能支持更多的并发量。
    • 查询不加锁,完全不影响查询。
    • 支持崩溃后恢复。

    在 MySQL5.1 以及之前的版本,默认的存储引擎是 MyISAM ,但是目前已经不再更新,且它有几个比较关键的缺点:

    • 不支持事务。
    • 使用表级锁,如果数据量大,一个插入操作锁定表后,其他请求都将阻塞。

    艿艿:也就是说,我们不需要花太多力气在 MyISAM 的学习上。

    ? 请说明 InnoDB 和 MyISAM 的区别

    InnoDBMyISAM
    事务支持不支持
    存储限制64TB
    锁粒度行锁表锁
    崩溃后的恢复支持不支持
    外键支持不支持
    全文检索5.7 版本后支持支持

    更完整的对比,可以看看 《数据库存储引擎》「常用引擎对比」 小节。

    ? 请说说 InnoDB 的 4 大特性?

    艿艿:貌似我面试没被问过…反正,我是没弄懂过~~

    • 插入缓冲(insert buffer)
    • 二次写(double write)
    • 自适应哈希索引(ahi)
    • 预读(read ahead)

    ? 为什么 SELECT COUNT(*) FROM table 在 InnoDB 比 MyISAM 慢?

    对于 SELECT COUNT(*) FROM table 语句,在没有 WHERE 条件的情况下,InnoDB 比 MyISAM 可能会慢很多,尤其在大表的情况下。因为,InnoDB 是去实时统计结果,会全表扫描;而 MyISAM 内部维持了一个计数器,预存了结果,所以直接返回即可。

    详细的原因,胖友可以看看 《高性能 MySQL 之 Count 统计查询》 博客。

    ? 各种不同 MySQL 版本的 Innodb 的改进?

    艿艿:这是一个选择了解的问题。

    MySQL5.6 下 Innodb 引擎的主要改进:

    1. online DDL
    2. memcached NoSQL 接口
    3. transportable tablespace( alter table discard/import tablespace)
    4. MySQL 正常关闭时,可以 dump 出 buffer pool 的( space, page_no),重启时 reload,加快预热速度
    5. 索引和表的统计信息持久化到 mysql.innodb_table_stats 和 mysql.innodb_index_stats,可提供稳定的执行计划
    6. Compressed row format 支持压缩表

    MySQL5.7 下 Innodb 引擎的主要改进:

    • 1、修改 varchar 字段长度有时可以使用

      这里的“有时”,指的是也有些限制。可见 《MySQL 5.7 online ddl 的一些改进》

    • 2、Buffer pool 支持在线改变大小

    • 3、Buffer pool 支持导出部分比例

    • 4、支持新建 innodb tablespace,并可以在其中创建多张表

    • 5、磁盘临时表采用 innodb 存储,并且存储在 innodb temp tablespace 里面,以前是 MyISAM 存储

    • 6、透明表空间压缩功能

    重点】什么是索引?

    索引,类似于书籍的目录,想找到一本书的某个特定的主题,需要先找到书的目录,定位对应的页码。

    MySQL 中存储引擎使用类似的方式进行查询,先去索引中查找对应的值,然后根据匹配的索引找到对应的数据行。

    ? 索引有什么好处?

    1. 提高数据的检索速度,降低数据库IO成本:使用索引的意义就是通过缩小表中需要查询的记录的数目从而加快搜索的速度。
    2. 降低数据排序的成本,降低CPU消耗:索引之所以查的快,是因为先将数据排好序,若该字段正好需要排序,则正好降低了排序的成本。

    ? 索引有什么坏处?

    1. 占用存储空间:索引实际上也是一张表,记录了主键与索引字段,一般以索引文件的形式存储在磁盘上。
    2. 降低更新表的速度:表的数据发生了变化,对应的索引也需要一起变更,从而减低的更新速度。否则索引指向的物理数据可能不对,这也是索引失效的原因之一。

    ? 索引的使用场景?

    • 1、对非常小的表,大部分情况下全表扫描效率更高。

    • 2、对中大型表,索引非常有效。

    • 3、特大型的表,建立和使用索引的代价随着增长,可以使用分区技术来解决。

      实际场景下,MySQL 分区表很少使用,原因可以看看 《互联网公司为啥不使用 MySQL 分区表?》 文章。

      对于特大型的表,更常用的是“分库分表”,目前解决方案有 Sharding Sphere、MyCAT 等等。

    ? 索引的类型?

    索引,都是实现在存储引擎层的。主要有六种类型:

    • 1、普通索引:最基本的索引,没有任何约束。

    • 2、唯一索引:与普通索引类似,但具有唯一性约束。

    • 3、主键索引:特殊的唯一索引,不允许有空值。

    • 4、复合索引:将多个列组合在一起创建索引,可以覆盖多个列。

    • 5、外键索引:只有InnoDB类型的表才可以使用外键索引,保证数据的一致性、完整性和实现级联操作。

    • 6、全文索引:MySQL 自带的全文索引只能用于 InnoDB、MyISAM ,并且只能对英文进行全文检索,一般使用全文索引引擎。

      常用的全文索引引擎的解决方案有 Elasticsearch、Solr 等等。最为常用的是 Elasticsearch 。

    具体的使用,可以看看 《服务端指南 数据存储篇 | MySQL(03) 如何设计索引》

    ? MySQL 索引的“创建”原则?

    注意,是“创建”噢。

    • 1、最适合索引的列是出现在 WHERE 子句中的列,或连接子句中的列,而不是出现在 SELECT 关键字后的列。

    • 2、索引列的基数越大,索引效果越好。

      具体为什么,可以看看如下两篇文章:

    • 3、根据情况创建复合索引,复合索引可以提高查询效率。

      因为复合索引的基数会更大。

    • 4、避免创建过多的索引,索引会额外占用磁盘空间,降低写操作效率。

    • 5、主键尽可能选择较短的数据类型,可以有效减少索引的磁盘占用提高查询效率。

    • 6、对字符串进行索引,应该定制一个前缀长度,可以节省大量的索引空间。

    ? MySQL 索引的“使用”注意事项?

    注意,是“使用”噢。

    • 1、应尽量避免在 WHERE 子句中使用 !=<> 操作符,否则将引擎放弃使用索引而进行全表扫描。优化器将无法通过索引来确定将要命中的行数,因此需要搜索该表的所有行。

      注意,column IS NULL 也是不可以使用索引的。

    • 2、应尽量避免在 WHERE 子句中使用 OR 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:SELECT id FROM t WHERE num = 10 OR num = 20

    • 3、应尽量避免在 WHERE 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。

    • 4、应尽量避免在 WHERE 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。

    • 5、不要在 WHERE 子句中的 = 左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

    • 6、复合索引遵循前缀原则。

    • 7、如果 MySQL 评估使用索引比全表扫描更慢,会放弃使用索引。如果此时想要索引,可以在语句中添加强制索引。

    • 8、列类型是字符串类型,查询时一定要给值加引号,否则索引失效。

    • 9、LIKE 查询,% 不能在前,因为无法使用索引。如果需要模糊匹配,可以使用全文索引。

    关于这块,可以看看 《服务端指南 数据存储篇 | MySQL(04) 索引使用的注意事项》 文章,写的更加细致。

    ? 以下三条 SQL 如何建索引,只建一条怎么建?

    WHERE a = 1 AND b = 1
    WHERE b = 1
    WHERE b = 1 ORDER BY time DESC
    
    
    • 以顺序 b , a, time 建立复合索引,CREATE INDEX table1_b_a_time ON index_test01(b, a, time)
    • 对于第一条 SQL ,因为最新 MySQL 版本会优化 WHERE 子句后面的列顺序,以匹配复合索引顺序。

    ? 想知道一个查询用到了哪个索引,如何查看?

    EXPLAIN 显示了 MYSQL 如何使用索引来处理 SELECT 语句以及连接表,可以帮助选择更好的索引和写出更优化的查询语句。

    使用方法,在 SELECT 语句前加上 EXPLAIN 就可以了。 《MySQL explain 执行计划详细解释》

    【重点】MySQL 索引的原理?

    解释 MySQL 索引的原理,篇幅会比较长,并且网络上已经有靠谱的资料可以看,所以艿艿这里整理了几篇,胖友可以对照着看。

    下面,艿艿对关键知识做下整理,方便胖友回顾。

    几篇好一点的文章:

    《MySQL索引背后的数据结构及算法原理》

    《MySQL 索引原理》

    《深入理解 MySQL 索引原理和实现 —— 为什么索引可以加速查询?》

    MySQL 有哪些索引方法?

    在 MySQL 中,我们可以看到两种索引方式:

    什么是 B-Tree 索引?

    B-Tree 是为磁盘等外存储设备设计的一种平衡查找树。因此在讲 B-Tree 之前先了解下磁盘的相关知识。

    • 系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
    • InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎中默认每个页的大小为 16 KB,可通过参数 innodb_page_size 将页的大小设置为 4K、8K、16K ,在 MySQL 中可通过如下命令查看页的大小:
    mysql> show variables like 'innodb_page_size';
    
    • 而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB 。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘 I/O 次数,提高查询效率。

    B-Tree 结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组 [key, data] ,key 为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key值互不相同。

    一棵 m 阶的 B-Tree 有如下特性:

    1. 每个节点最多有 m 个孩子。
      • 除了根节点和叶子节点外,其它每个节点至少有 Ceil(m/2) 个孩子。
      • 若根节点不是叶子节点,则至少有 2 个孩子。
    2. 所有叶子节点都在同一层,且不包含其它关键字信息。
    3. 每个非叶子节点包含 n 个关键字信息(P0,P1,…Pn, k1,…kn)
      • 关键字的个数 n 满足:ceil(m/2)-1 <= n <= m-1
      • ki(i=1,…n) 为关键字,且关键字升序排序。
      • Pi(i=0,…n) 为指向子树根节点的指针。P(i-1) 指向的子树的所有节点关键字均小于 ki ,但都大于 k(i-1) 。

    B-Tree 中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3 阶的 B-Tree:

    B-Tree 的结构

    • 每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的 key 和三个指向子树根节点的 point ,point 存储的是子节点所在磁盘块的地址。两个 key 划分成的三个范围域,对应三个 point 指向的子树的数据的范围域。
    • 以根节点为例,key 为 17 和 35 ,P1 指针指向的子树的数据范围为小于 17 ,P2 指针指向的子树的数据范围为 [17~35] ,P3 指针指向的子树的数据范围为大于 35 。

    模拟查找 key 为 29 的过程:

    • 1、根据根节点找到磁盘块 1 ,读入内存。【磁盘I/O操作第1次】
    • 2、比较 key 29 在区间(17,35),找到磁盘块 1 的指针 P2 。
    • 3、根据 P2 指针找到磁盘块 3 ,读入内存。【磁盘I/O操作第2次】
    • 4、比较 key 29 在区间(26,30),找到磁盘块3的指针P2。
    • 5、根据 P2 指针找到磁盘块 8 ,读入内存。【磁盘I/O操作第3次】
    • 6、在磁盘块 8 中的 key 列表中找到 eky 29 。

    分析上面过程,发现需要 3 次磁盘 I/O 操作,和 3 次内存查找操作。由于内存中的 key 是一个有序表结构,可以利用二分法查找提高效率。而 3 次磁盘 I/O 操作是影响整个 B-Tree 查找效率的决定因素。B-Tree 相对于 AVLTree 缩减了节点个数,使每次磁盘 I/O 取到内存的数据都发挥了作用,从而提高了查询效率。

    什么是 B+Tree 索引?

    B+Tree 是在 B-Tree 基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用 B+Tree 实现其索引结构。

    从上一节中的 B-Tree 结构图中可以看到,每个节点中不仅包含数据的 key 值,还有 data 值。而每一个页的存储空间是有限的,如果 data 数据较大时将会导致每个节点(即一个页)能存储的 key 的数量很小,当存储的数据量很大时同样会导致 B-Tree 的深度较大,增大查询时的磁盘 I/O 次数,进而影响查询效率。在 B+Tree 中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储 key 值信息,这样可以大大加大每个节点存储的 key 值数量,降低 B+Tree 的高度。

    B+Tree 相对于 B-Tree 有几点不同:

    • 非叶子节点只存储键值信息。
    • 所有叶子节点之间都有一个链指针。
    • 数据记录都存放在叶子节点中。

    将上一节中的 B-Tree 优化,由于 B+Tree 的非叶子节点只存储键值信息,假设每个磁盘块能存储 4 个键值及指针信息,则变成 B+Tree 后其结构如下图所示:

    B+Tree 的结构

    • 通常在 B+Tree 上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对 B+Tree 进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。

    可能上面例子中只有 22 条数据记录,看不出 B+Tree 的优点,下面做一个推算:

    • InnoDB 存储引擎中页的大小为 16KB,一般表的主键类型为 INT(占用4个字节) 或 BIGINT(占用8个字节),指针类型也一般为 4 或 8 个字节,也就是说一个页(B+Tree 中的一个节点)中大概存储 16KB/(8B+8B)=1K 个键值(因为是估值,为方便计算,这里的 K 取值为〖10〗^3)。也就是说一个深度为 3 的 B+Tree 索引可以维护10^3 *10^3 *10^3 = 10亿 条记录。
    • 实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree 的高度一般都在 2~4 层。MySQL 的 InnoDB 存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要 1~3 次磁盘 I/O 操作。

    B+Tree 有哪些索引类型?

    在 B+Tree 中,根据叶子节点的内容,索引类型分为主键索引非主键索引

    • 主键索引的叶子节点存的数据是整行数据( 即具体数据 )。在 InnoDB 里,主键索引也被称为聚集索引(clustered index)。
    • 非主键索引的叶子节点存的数据是整行数据的主键,键值是索引。在 InnoDB 里,非主键索引也被称为辅助索引(secondary index)。

    辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,需要进过两步:

    • 首先,InnoDB 存储引擎会遍历辅助索引找到主键。
    • 然后,再通过主键在聚集索引中找到完整的行记录数据。

    另外,InnoDB 通过主键聚簇数据,如果没有定义主键,会选择一个唯一的非空索引代替,如果没有这样的索引,会隐式定义个主键作为聚簇索引。

    再另外,可能有胖友有和艿艿的一样疑惑,在辅助索引如果相同的索引怎么存储?最终存储到 B+Tree 非子节点中时,它们对应的主键 ID 是不同的,所以妥妥的。如下图所示:

    相同的索引怎么存储

    聚簇索引的注意点有哪些?

    聚簇索引表最大限度地提高了 I/O 密集型应用的性能,但它也有以下几个限制:

    • 1、插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于 InnoDB 表,我们一般都会定义一个自增的 ID 列为主键。

      关于这一点,可能面试官会换一个问法。例如,为什么主键需要是自增 ID ,又或者为什么主键需要带有时间性关联。

    • 2、更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB 表,我们一般定义主键为不可更新。

      MySQL 默认情况下,主键是允许更新的。对于 MongoDB ,其 主键是不允许更新的。

    • 3、二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

      当然,有一种情况可以无需二次查找,基于非主键索引查询,但是查询字段只有主键 ID ,那么在二级索引中就可以查找到。

    • 4、主键 ID 建议使用整型。因为,每个主键索引的 B+Tree 节点的键值可以存储更多主键 ID ,每个非主键索引的 B+Tree 节点的数据可以存储更多主键 ID 。

    什么是索引的最左匹配特性?

    当 B+Tree 的数据项是复合的数据结构,比如索引 (name, age, sex) 的时候,B+Tree 是按照从左到右的顺序来建立搜索树的。

    • 比如当 (张三, 20, F) 这样的数据来检索的时候,B+Tree 会优先比较 name 来确定下一步的所搜方向,如果 name 相同再依次比较 age 和 sex ,最后得到检索的数据。
    • 但当 (20, F) 这样的没有 name 的数据来的时候,B+Tree 就不知道下一步该查哪个节点,因为建立搜索树的时候 name 就是第一个比较因子,必须要先根据 name 来搜索才能知道下一步去哪里查询。
    • 比如当 (张三, F) 这样的数据来检索时,B+Tree 可以用 name 来指定搜索方向,但下一个字段 age 的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是 F 的数据了。

    这个是非常重要的性质,即索引的最左匹配特性。

    MyISAM 索引实现?

    MyISAM 索引的实现,和 InnoDB 索引的实现是一样使用 B+Tree ,差别在于 MyISAM 索引文件和数据文件是分离的,索引文件仅保存数据记录的地址

    MyISAM 索引与 InnoDB 索引的区别?

    • InnoDB 索引是聚簇索引,MyISAM 索引是非聚簇索引。
    • InnoDB 的主键索引的叶子节点存储着行数据,因此主键索引非常高效。
    • MyISAM 索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。
    • InnoDB 非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。

    【重点】请说说 MySQL 的四种事务隔离级别?

    • 1、插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于 InnoDB 表,我们一般都会定义一个自增的 ID 列为主键。

      关于这一点,可能面试官会换一个问法。例如,为什么主键需要是自增 ID ,又或者为什么主键需要带有时间性关联。

    • 2、更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB 表,我们一般定义主键为不可更新。

      MySQL 默认情况下,主键是允许更新的。对于 MongoDB ,其 主键是不允许更新的。

    • 3、二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

      当然,有一种情况可以无需二次查找,基于非主键索引查询,但是查询字段只有主键 ID ,那么在二级索引中就可以查找到。

    • 4、主键 ID 建议使用整型。因为,每个主键索引的 B+Tree 节点的键值可以存储更多主键 ID ,每个非主键索引的 B+Tree 节点的数据可以存储更多主键 ID 。

    • 1、插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于 InnoDB 表,我们一般都会定义一个自增的 ID 列为主键。

      关于这一点,可能面试官会换一个问法。例如,为什么主键需要是自增 ID ,又或者为什么主键需要带有时间性关联。

    • 2、更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB 表,我们一般定义主键为不可更新。

      MySQL 默认情况下,主键是允许更新的。对于 MongoDB ,其 主键是不允许更新的。

    • 3、二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

      当然,有一种情况可以无需二次查找,基于非主键索引查询,但是查询字段只有主键 ID ,那么在二级索引中就可以查找到。

    • 4、主键 ID 建议使用整型。因为,每个主键索引的 B+Tree 节点的键值可以存储更多主键 ID ,每个非主键索引的 B+Tree 节点的数据可以存储更多主键 ID 。

    • 1、插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于 InnoDB 表,我们一般都会定义一个自增的 ID 列为主键。

      关于这一点,可能面试官会换一个问法。例如,为什么主键需要是自增 ID ,又或者为什么主键需要带有时间性关联。

    • 2、更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB 表,我们一般定义主键为不可更新。

      MySQL 默认情况下,主键是允许更新的。对于 MongoDB ,其 主键是不允许更新的。

    • 3、二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

      当然,有一种情况可以无需二次查找,基于非主键索引查询,但是查询字段只有主键 ID ,那么在二级索引中就可以查找到。

    • 4、主键 ID 建议使用整型。因为,每个主键索引的 B+Tree 节点的键值可以存储更多主键 ID ,每个非主键索引的 B+Tree 节点的数据可以存储更多主键 ID 。

    事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。

    这样可以防止出现脏数据,防止数据库数据出现问题。

    事务的特性指的是?

    指的是 ACID ,如下图所示:

    事务的特性

    1. 原子性 Atomicity :一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
    2. 一致性 Consistency :在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束触发器级联回滚等。
    3. 隔离性 Isolation :数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
    4. 持久性 Durability :事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

    事务的并发问题?

    实际场景下,事务并不是串行的,所以会带来如下三个问题:

    • 1、脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据。
    • 2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致。
    • 3、幻读:系统管理员 A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等级,但是系统管理员 B 就在这个时候插入了一条具体分数的记录,当系统管理员 A 改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

    小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

    MySQL 事务隔离级别会产生的并发问题?

    • READ UNCOMMITTED(未提交读):事务中的修改,即使没有提交,对其他事务也都是可见的。

      会导致脏读。

    • READ COMMITTED(提交读):事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。

      会导致不可重复读。

      这个隔离级别,也可以叫做“不可重复读”。

    • REPEATABLE READ(可重复读):一个事务按相同的查询条件读取以前检索过的数据,其他事务插入了满足其查询条件的新数据。产生幻行。

      会导致幻读。

    • SERIALIZABLE(可串行化):强制事务串行执行。

    事务隔离级别脏读不可重复读幻读
    读未提交(read-uncommitted)
    读已提交(read-committed)
    可重复读(repeatable-read)是(x)
    串行化(serializable)
    • MySQL 默认的事务隔离级别为可重复读(repeatable-read) 。
    • 上图的 <X> 处,MySQL 因为其间隙锁的特性,导致其在可重复读(repeatable-read)的隔离级别下,不存在幻读问题。也就是说,上图 <X> 处,需要改成“否”!!!!
    • ? 记住这个表的方式,我们会发现它是自左上向右下是一个对角线。当然,最好是去理解。
    • 具体的实验,胖友可以看看 《MySQL 的四种事务隔离级别》

    【重点】请说说 MySQL 的锁机制?

    表锁是日常开发中的常见问题,因此也是面试当中最常见的考察点,当多个查询同一时刻进行数据修改时,就会产生并发控制的问题。MySQL 的共享锁和排他锁,就是读锁和写锁。

    • 共享锁:不堵塞,多个用户可以同时读一个资源,互不干扰。
    • 排他锁:一个写锁会阻塞其他的读锁和写锁,这样可以只允许一个用户进行写入,防止其他用户读取正在写入的资源。

    ? 锁的粒度?

    • 表锁:系统开销最小,会锁定整张表,MyIsam 使用表锁。
    • 行锁:最大程度的支持并发处理,但是也带来了最大的锁开销,InnoDB 使用行锁。

    ? 什么是悲观锁?什么是乐观锁?

    1)悲观锁

    它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

    在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。

    2)乐观锁

    相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

    而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

    什么是死锁?

    多数情况下,可以认为如果一个资源被锁定,它总会在以后某个时间被释放。而死锁发生在当多个进程访问同一数据库时,其中每个进程拥有的锁都是其他进程所需的,由此造成每个进程都无法继续下去。简单的说,进程 A 等待进程 B 释放他的资源,B 又等待 A 释放他的资源,这样就互相等待就形成死锁。

    虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件:

    • 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
    • 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
    • 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
    • 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合 {P0,P1,P2,•••,Pn} 中的 P0 正在等待一个 P1 占用的资源;P1 正在等待 P2 占用的资源,……,Pn 正在等待已被 P0 占用的资源。

    下列方法有助于最大限度地降低死锁:

    • 设置获得锁的超时时间。

      通过超时,至少保证最差最差最差情况下,可以有退出的口子。

    • 按同一顺序访问对象。

      这个是最重要的方式。

    • 避免事务中的用户交互。

    • 保持事务简短并在一个批处理中。

    • 使用低隔离级别。

    • 使用绑定连接。

    ? MySQL 中 InnoDB 引擎的行锁是通过加在什么上完成(或称实现)的?为什么是这样子的??

    InnoDB 是基于索引来完成行锁。例如:SELECT * FROM tab_with_index WHERE id = 1 FOR UPDATE

    • FOR UPDATE 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么 InnoDB 将完成表锁,并发将无从谈起。

    【重要】MySQL 查询执行顺序?

    MySQL 查询执行的顺序是:

    (1)     SELECT
    (2)     DISTINCT <select_list>
    (3)     FROM <left_table>
    (4)     <join_type> JOIN <right_table>
    (5)     ON <join_condition>
    (6)     WHERE <where_condition>
    (7)     GROUP BY <group_by_list>
    (8)     HAVING <having_condition>
    (9)     ORDER BY <order_by_condition>
    (10)    LIMIT <limit_number>
    

    具体的,可以看看 《SQL 查询之执行顺序解析》 文章。

    【重要】聊聊 MySQL SQL 优化?

    可以看看如下几篇文章:

    另外,除了从 SQL 层面进行优化,也可以从服务器硬件层面,进一步优化 MySQL 。具体可以看看 《MySQL 数据库性能优化之硬件优化》

    编写 SQL 查询语句的考题合集

    MySQL 数据库 CPU 飙升到 500% 的话,怎么处理?

    当 CPU 飙升到 500% 时,先用操作系统命令 top 命令观察是不是 mysqld 占用导致的,如果不是,找出占用高的进程,并进行相关处理。

    如果此时是 IO 压力比较大,可以使用 iostat 命令,定位是哪个进程占用了磁盘 IO 。

    如果是 mysqld 造成的,使用 show processlist 命令,看看里面跑的 Session 情况,是不是有消耗资源的 SQL 在运行。找出消耗高的 SQL ,看看执行计划是否准确, index 是否缺失,或者实在是数据量太大造成。一般来说,肯定要 kill 掉这些线程(同时观察 CPU 使用率是否下降),等进行相应的调整(比如说加索引、改 SQL 、改内存参数)之后,再重新跑这些 SQL。

    也可以查看 MySQL 慢查询日志,看是否有慢 SQL 。

    也有可能是每个 SQL 消耗资源并不多,但是突然之间,有大量的 Session 连进来导致 CPU 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等。

    ? 在 MySQL 服务器运行缓慢的情况下输入什么命令能缓解服务器压力?

    1)检查系统的状态

    通过操作系统的一些工具检查系统的状态,比如 CPU、内存、交换、磁盘的利用率,根据经验或与系统正常时的状态相比对,有时系统表面上看起来看空闲,这也可能不是一个正常的状态,因为 CPU 可能正等待IO的完成。除此之外,还应观注那些占用系统资源(CPU、内存)的进程。

    • 使用 sar 来检查操作系统是否存在 IO 问题。
    • 使用 vmstat 监控内存 CPU 资源。
    • 磁盘 IO 问题,处理方式:做 raid10 提高性能 。
    • 网络问题,telnet 一下 MySQL 对外开放的端口。如果不通的话,看看防火墙是否正确设置了。另外,看看 MySQ L是不是开启了 skip-networking 的选项,如果开启请关闭。

    2)检查 MySQL 参数

    • max_connect_errors
    • connect_timeout
    • skip-name-resolve
    • slave-net-timeout=seconds
    • master-connect-retry

    3)检查 MySQL 相关状态值

    • 关注连接数
    • 关注下系统锁情况
    • 关注慢查询(slow query)日志

    Innodb 的事务与日志的实现方式

    ? 有多少种日志?

    • redo 日志
    • undo 日志

    ? 日志的存放形式?

    • redo:在页修改的时候,先写到 redo log buffer 里面, 然后写到 redo log 的文件系统缓存里面(fwrite),然后再同步到磁盘文件(fsync)。
    • undo:在 MySQL5.5 之前,undo 只能存放在 ibdata* 文件里面, 5.6 之后,可以通过设置 innodb_undo_tablespaces 参数把 undo log 存放在 ibdata* 之外。

    ? 事务是如何通过日志来实现的,说得越深入越好

    艿艿:这个流程的理解还是比较简单的,实际思考实现感觉还是蛮复杂的。

    基本流程如下:

    • 因为事务在修改页时,要先记 undo ,在记 undo 之前要记 undo 的 redo, 然后修改数据页,再记数据页修改的 redo。 redo(里面包括 undo 的修改)一定要比数据页先持久化到磁盘。
    • 当事务需要回滚时,因为有 undo,可以把数据页回滚到前镜像的状态。
    • 崩溃恢复时,如果 redo log 中事务没有对应的 commit 记录,那么需要用 undo 把该事务的修改回滚到事务开始之前。如果有 commit 记录,就用 redo 前滚到该事务完成时并提交掉。

    MySQL binlog 的几种日志录入格式以及区别

    ? 各种日志格式的涵义

    binlog 有三种格式类型,分别如下:

    1)Statement

    每一条会修改数据的 SQL 都会记录在 binlog 中。

    • 优点:不需要记录每一行的变化,减少了 binlog 日志量,节约了 IO,提高性能。(相比 row 能节约多少性能与日志量,这个取决于应用的 SQL 情况,正常同一条记录修改或者插入 row 格式所产生的日志量还小于 Statement 产生的日志量,但是考虑到如果带条件的 update 操作,以及整表删除,alter 表等操作,ROW 格式会产生大量日志,因此在考虑是否使用 ROW 格式日志时应该跟据应用的实际情况,其所产生的日志量会增加多少,以及带来的 IO 性能问题。)

    • 缺点:由于记录的只是执行语句,为了这些语句能在 slave 上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在 slave 得到和在 master 端执行时候相同 的结果。另外 MySQL 的复制,像一些特定函数功能,slave 可与 master 上要保持一致会有很多相关问题(如 sleep() 函数,last_insert_id(),以及 user-defined functions(udf) 会出现问题)。

    • 使用以下函数的语句也无法被复制:

      • LOAD_FILE()

      • UUID()

      • USER()

      • FOUND_ROWS()

      • SYSDATE() (除非启动时启用了 --sysdate-is-now 选项)

        同时在 INSERT …SELECT 会产生比 RBR 更多的行级锁 。

    2)Row

    不记录 SQL 语句上下文相关信息,仅保存哪条记录被修改。

    • 优点:binlog 中可以不记录执行的 SQL 语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以 rowlevel 的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或 function ,以及 trigger 的调用和触发无法被正确复制的问题。
    • 缺点:所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如一条 Update 语句,修改多条记录,则 binlog 中每一条修改都会有记录,这样造成 binlog 日志量会很大,特别是当执行 alter table 之类的语句的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中。

    3)Mixedlevel

    是以上两种 level 的混合使用。

    • 一般的语句修改使用 Statement 格式保存 binlog 。
    • 如一些函数,statement 无法完成主从复制的操作,则采用 Row 格式保存 binlog 。

    MySQL 会根据执行的每一条具体的 SQL 语句来区分对待记录的日志形式,也就是在 Statement 和 Row 之间选择 一种。

    新版本的 MySQL 中对 row level 模式也被做了优化,并不是所有的修改都会以 row level 来记录。

    • 像遇到表结构变更的时候就会以 Statement 模式来记录。
    • 至于 Update 或者 Delete 等修改数据的语句,还是会记录所有行的变更,即使用 Row 模式。

    ? 适用场景?

    在一条 SQL 操作了多行数据时, Statement 更节省空间,Row 更占用空间。但是, Row 模式更可靠。

    因为,互联网公司,使用 MySQL 的功能相对少,基本不使用存储过程、触发器、函数的功能,选择默认的语句模式,Statement Level(默认)即可。

    ? 结合第一个问题,每一种日志格式在复制中的优劣?

    • Statement 可能占用空间会相对小一些,传送到 slave 的时间可能也短,但是没有 Row 模式的可靠。
    • Row 模式在操作多行数据时更占用空间,但是可靠。

    所以,这是在占用空间和可靠之间的选择。

    如何在线正确清理 MySQL binlog?

    MySQL 中的 binlog 日志记录了数据中的数据变动,便于对数据的基于时间点和基于位置的恢复。但日志文件的大小会越来越大,占用大量的磁盘空间,因此需要定时清理一部分日志信息。

    # 首先查看主从库正在使用的binlog文件名称
    show master(slave) status
    
    # 删除之前一定要备份
    purge master logs before'2017-09-01 00:00:00'; # 删除指定时间前的日志
    purge master logs to'mysql-bin.000001'; # 删除指定的日志文件
    
    # 自动删除:通过设置binlog的过期时间让系统自动删除日志
    show variables like 'expire_logs_days'; # 查看过期时间
    set global expire_logs_days = 30; # 设置过期时间
    

    MySQL 主从复制的流程是怎么样的?

    MySQL 的主从复制是基于如下 3 个线程的交互(多线程复制里面应该是 4 类线程):

    • 1、Master 上面的 binlog dump 线程,该线程负责将 master 的 binlog event 传到 slave 。
    • 2、Slave 上面的 IO 线程,该线程负责接收 Master 传过来的 binlog,并写入 relay log 。
    • 3、Slave 上面的 SQL 线程,该线程负责读取 relay log 并执行。
    • 4、如果是多线程复制,无论是 5.6 库级别的假多线程还是 MariaDB 或者 5.7 的真正的多线程复制, SQL 线程只做 coordinator ,只负责把 relay log 中的 binlog 读出来然后交给 worker 线程, woker 线程负责具体 binlog event 的执行。

    ? MySQL 如何保证复制过程中数据一致性?

    • 1、在 MySQL5.5 以及之前, slave 的 SQL 线程执行的 relay log 的位置只能保存在文件( relay-log.info)里面,并且该文件默认每执行 10000 次事务做一次同步到磁盘, 这意味着 slave 意外 crash 重启时, SQL 线程执行到的位置和数据库的数据是不一致的,将导致复制报错,如果不重搭复制,则有可能会导致数据不一致。
      • MySQL 5.6 引入参数 relay_log_info_repository,将该参数设置为 TABLE 时, MySQL 将 SQL 线程执行到的位置存到 mysql.slave_relay_log_info 表,这样更新该表的位置和 SQL 线程执行的用户事务绑定成一个事务,这样 slave 意外宕机后,slave 通过 innodb 的崩溃恢复可以把 SQL 线程执行到的位置和用户事务恢复到一致性的状态。
    • 2、MySQL 5.6 引入 GTID 复制,每个 GTID 对应的事务在每个实例上面最多执行一次, 这极大地提高了复制的数据一致性。
    • 3、MySQL 5.5 引入半同步复制, 用户安装半同步复制插件并且开启参数后,设置超时时间,可保证在超时时间内如果 binlog 不传到 slave 上面,那么用户提交事务时不会返回,直到超时后切成异步复制,但是如果切成异步之前用户线程提交时在 master 上面等待的时候,事务已经提交,该事务对 master 上面的其他 session 是可见的,如果这时 master 宕机,那么到 slave 上面该事务又不可见了,该问题直到 5.7 才解决。
    • 4、MySQL 5.7 引入无损半同步复制,引入参 rpl_semi_sync_master_wait_point,该参数默认为 after_sync,指的是在切成半同步之前,事务不提交,而是接收到 slave 的 ACK 确认之后才提交该事务,从此,复制真正可以做到无损的了。
    • 5、可以再说一下 5.7 的无损复制情况下, master 意外宕机,重启后发现有 binlog 没传到 slave 上面,这部分 binlog 怎么办???分 2 种情况讨论, 1 宕机时已经切成异步了, 2 是宕机时还没切成异步??? 这个怎么判断宕机时有没有切成异步呢??? 分别怎么处理???

    ? MySQL 如何解决主从复制的延时性?

    5.5 是单线程复制,5.6 是多库复制(对于单库或者单表的并发操作是没用的),5.7 是真正意义的多线程复制,它的原理是基于 group commit, 只要 master 上面的事务是 group commit 的,那 slave 上面也可以通过多个 worker线程去并发执行。 和 MairaDB10.0.0.5 引入多线程复制的原理基本一样。

    ? 工作遇到的复制 bug 的解决方法?

    5.6 的多库复制有时候自己会停止,我们写了一个脚本重新 start slave 。

    ? 你是否做过主从一致性校验,如果有,怎么做的,如果没有,你打算怎么做?

    主从一致性校验有多种工具 例如 checksum、mysqldiff、pt-table-checksum 等。

    聊聊 MySQL 备份方式?备份策略是怎么样的?

    具体的,胖友可以看看 《MySQL 高级备份策略》 。主要有几个知识点:

    • 数据的备份类型

      • 【常用】完全备份

        这是大多数人常用的方式,它可以备份整个数据库,包含用户表、系统表、索引、视图和存储过程等所有数据库对象。但它需要花费更多的时间和空间,所以,一般推荐一周做一次完全备份。

      • 增量备份

        它是只备份数据库一部分的另一种方法,它不使用事务日志,相反,它使用整个数据库的一种新映象。它比最初的完全备份小,因为它只包含自上次完全备份以来所改变的数据库。它的优点是存储和恢复速度快。推荐每天做一次差异备份。

      • 【常用】事务日志备份

        事务日志是一个单独的文件,它记录数据库的改变,备份的时候只需要复制自上次备份以来对数据库所做的改变,所以只需要很少的时间。为了使数据库具有鲁棒性,推荐每小时甚至更频繁的备份事务日志。

      • 文件备份

        数据库可以由硬盘上的许多文件构成。如果这个数据库非常大,并且一个晚上也不能将它备份完,那么可以使用文件备份每晚备份数据库的一部分。由于一般情况下数据库不会大到必须使用多个文件存储,所以这种备份不是很常用。

    • 备份数据的类型

      • 热备份
      • 温备份
      • 冷备份
    • 备份工具

      • cp
      • mysqldump
      • xtrabackup
      • lvm2 快照

    MySQL 几种备份方式?

    MySQL 一般有 3 种备份方式。

    1)逻辑备份

    使用 MySQL 自带的 mysqldump 工具进行备份。备份成sql文件形式。

    • 优点:最大好处是能够与正在运行的 MySQL 自动协同工作,在运行期间可以确保备份是当时的点,它会自动将对应操作的表锁定,不允许其他用户修改(只能访问)。可能会阻止修改操作。SQL 文件通用方便移植。
    • 缺点:备份的速度比较慢。如果是数据量很多的时候,就很耗时间。如果数据库服务器处在提供给用户服务状态,在这段长时间操作过程中,意味着要锁定表(一般是读锁定,只能读不能写入数据),那么服务就会影响的。

    2)物理备份

    艿艿:因为现在主流是 InnoDB ,所以基本不再考虑这种方式。

    直接拷贝只适用于 MyISAM 类型的表。这种类型的表是与机器独立的。但实际情况是,你设计数据库的时候不可能全部使用 MyISAM 类型表。你也不可能因为 MyISAM 类型表与机器独立,方便移植,于是就选择这种表,这并不是选择它的理由。

    • 缺点:你不能去操作正在运行的 MySQL 服务器(在拷贝的过程中有用户通过应用程序访问更新数据,这样就无法备份当时的数据),可能无法移植到其他机器上去。

    3)双机热备份。

    当数据量太大的时候备份是一个很大的问题,MySQL 数据库提供了一种主从备份的机制,也就是双机热备。

    • 优点:适合数据量大的时候。现在明白了,大的互联网公司对于 MySQL 数据备份,都是采用热机备份。搭建多台数据库服务器,进行主从复制。

    数据库不能停机,请问如何备份? 如何进行全备份和增量备份?

    可以使用逻辑备份和双机热备份。

    • 完全备份:完整备份一般一段时间进行一次,且在网站访问量最小的时候,这样常借助批处理文件定时备份。主要是写一个批处理文件在里面写上处理程序的绝对路径然后把要处理的东西写在后面,即完全备份数据库。
    • 增量备份:对 ddl 和 dml 语句进行二进制备份。且 5.0 无法增量备份,5.1 后可以。如果要实现增量备份需要在 my.ini 文件中配置备份路径即可,重启 MySQL 服务器,增量备份就启动了。

    ? 你的备份工具的选择?备份计划是怎么样的?

    视库的大小来定,一般来说 100G 内的库,可以考虑使用 mysqldump 来做,因为 mysqldump 更加轻巧灵活,备份时间选在业务低峰期,可以每天进行都进行全量备份(mysqldump 备份出来的文件比较小,压缩之后更小)。

    100G 以上的库,可以考虑用 xtrabackup 来做,备份速度明显要比 mysqldump 要快。一般是选择一周一个全备,其余每天进行增量备份,备份时间为业务低峰期。

    备份恢复时间是多长?

    物理备份恢复快,逻辑备份恢复慢。

    这里跟机器,尤其是硬盘的速率有关系,以下列举几个仅供参考:

    • 20G 的 2 分钟(mysqldump)
    • 80G 的 30分钟(mysqldump)
    • 111G 的 30分钟(mysqldump)
    • 288G 的 3 小时(xtrabackup)
    • 3T 的 4 小时(xtrabackup)

    逻辑导入时间一般是备份时间的 5 倍以上。

    备份恢复失败如何处理?

    首先在恢复之前就应该做足准备工作,避免恢复的时候出错。比如说备份之后的有效性检查、权限检查、空间检查等。如果万一报错,再根据报错的提示来进行相应的调整。

    ? mysqldump 和 xtrabackup 实现原理?

    1)mysqldump

    mysqldump 是最简单的逻辑备份方式。

    • 在备份 MyISAM 表的时候,如果要得到一致的数据,就需要锁表,简单而粗暴。
    • 在备份 InnoDB 表的时候,加上 –master-data=1 –single-transaction 选项,在事务开始时刻,记录下 binlog pos 点,然后利用 MVCC 来获取一致的数据,由于是一个长事务,在写入和更新量很大的数据库上,将产生非常多的 undo ,显著影响性能,所以要慎用。
    • 优点:简单,可针对单表备份,在全量导出表结构的时候尤其有用。
    • 缺点:简单粗暴,单线程,备份慢而且恢复慢,跨 IDC 有可能遇到时区问题

    2)xtrabackup

    xtrabackup 实际上是物理备份+逻辑备份的组合。

    • 在备份 InnoDB 表的时候,它拷贝 ibd 文件,并一刻不停的监视 redo log 的变化,append 到自己的事务日志文件。在拷贝 ibd 文件过程中,ibd文件本身可能被写”花”,这都不是问题,因为在拷贝完成后的第一个 prepare 阶段,xtrabackup 采用类似于 Innodb 崩溃恢复的方法,把数据文件恢复到与日志文件一致的状态,并把未提交的事务回滚。
    • 如果同时需要备份 MyISAM 表以及 InnoDB 表结构等文件,那么就需要用 flush tables with lock 来获得全局锁,开始拷贝这些不再变化的文件,同时获得 binlog 位置,拷贝结束后释放锁,也停止对 redo log 的监视。

    如何从 mysqldump 产生的全库备份中只恢复某一个库、某一张表?

    具体可见 《MySQL 全库备份中恢复某个库和某张表以及 mysqldump 参数 –ignore-table 介绍》 文章。

    聊聊 MySQL 集群?

    艿艿:这块艿艿懂的少,主要找了一些网络上的资料。

    ? 对于简历中写有熟悉 MySQL 高可用方案?

    我一般先问他现在管理的数据库架构是什么,如果他只说出了主从,而没有说任何 HA 的方案,那么我就可以判断出他没有实际的 HA 经验。

    不过这时候也不能就是断定他不懂 MySQL 高可用,也许是没有实际机会去使用,那么我就要问 MMM 以及 MHA 以及 MM + keepalived 等的原理、实现方式以及它们之间的优势和不足了,一般这种情况下,能说出这个的基本没有。

    • MMM 那东西好像不靠谱,据说不稳定,但是有人在用的,和 mysql-router 比较像,都是指定可写的机器和只读机器。
    • MHA 的话一句话说不完,可以搜索下相关博客。

    聊聊 MySQL 安全?

    感兴趣的胖友,可以看看:

    MySQL 有哪些日志?

    • 错误日志:记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。

    • 二进制文件:记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,不包括数据查询语句。语句以“事件”的形式保存,它描述了数据的更改过程。(定期删除日志,默认关闭)。

      就是我们上面看到的 MySQL binlog 日志。

    • 查询日志:记录了客户端的所有语句,格式为纯文本格式,可以直接进行读取。(log 日志中记录了所有数据库的操作,对于访问频繁的系统,此日志对系统性能的影响较大,建议关闭,默认关闭)。

    • 慢查询日志:慢查询日志记录了包含所有执行时间超过参数long_query_time(单位:秒)所设置值的 SQL 语句的日志。(纯文本格式)

      重要,一定要开启。

    另外,错误日志和慢查询日志的详细解释,可以看看 《MySQL 日志文件之错误日志和慢查询日志详解》 文章。

    聊聊 MySQL 监控?

    你是如何监控你们的数据库的?

    监控的工具有很多,例如 Zabbix ,Lepus ,我这里用的是 Lepus

    对一个大表做在线 DDL ,怎么进行实施的才能尽可能降低影响?

    使用 pt-online-schema-change ,具体可以看看 《MySQL 大表在线 DML 神器–pt-online-schema-change》 文章。

    另外,还有一些其它的工具,胖友可以搜索下。

    展开全文
  • 软件测试面试题汇总

    万次阅读 多人点赞 2018-09-27 12:31:09
    转载自: ... 软件测试面试题汇总 测试技术面试题 ...........................................................................................................
    转载自: https://blog.csdn.net/koudaidai/article/details/7394126

    软件测试面试题汇总

    测试技术面试题

    ........................................................................................................................................................................ 5

    1、什么是兼容性测试?兼容性测试侧重哪些方面?.................................................................................... 5

    2、我现在有个程序,发现在Windows上运行得很慢,怎么判别是程序存在问题还是软硬件系统存在问题?       5

    3、测试的策略有哪些?................................................................................................................................. 5

    4、正交表测试用例设计方法的特点是什么?............................................................................................... 5

    5、描述使用bugzilla缺陷管理工具对软件缺陷(BUG)跟踪的管理的流程?.......................................... 5

    6、你觉得bugzilla在使用的过程中,有什么问题?................................................................................. 5

    7、描述测试用例设计的完整过程?.............................................................................................................. 6

    8、单元测试的策略有哪些?......................................................................................................................... 6

    9、LoadRunner分哪三部分?....................................................................................................................... 6

    10、LoadRunner进行测试的流程?................................................................................................................ 6

    什么是并发?在lordrunner中,如何进行并发的测试?集合点失败了会怎么样?.................................. 6

    12、使用QTP做功能测试,录制脚本的时候,要验证多个用户的登录情况/查询情况,如何操作?......... 6

    13、QTP中的Action有什么作用?有几种?................................................................................................. 6

    14、TestDirector有些什么功能,如何对软件测试过程进行管理?............................................................... 7

    15、你所熟悉的软件测试类型都有哪些?请试着分别比较这些不同的测试类型的区别与联系(如功能测试、性能测试……)?........................................................................................................................................... 7

    16、条软件缺陷(或者叫Bug)记录都包含了哪些内容?如何提交高质量的软件缺陷(Bug)记录?...... 8

    17、Beta测试与Alpha测试有什么区别?...................................................................................................... 8

    18、软件的评审一般由哪些人参加?其目的是什么?.................................................................................. 8

    19、测试活动中,如果发现需求文档不完善或者不准确,怎么处理?........................................................ 8

    20、阶段评审与项目评审有什么区别?......................................................................................................... 8

    21、阐述工作版本的定义?............................................................................................................................ 8

    22、什么是桩模块?什么是驱动模块?......................................................................................................... 8

    23、什么是扇入?什么是扇出?.................................................................................................................... 8

    24、你认为做好测试计划工作的关键是什么?............................................................................................. 8

    25、你认为做好测试用例工作的关键是什么?............................................................................................. 9

    26、简述一下缺陷的生命周期?.................................................................................................................... 9

    27、软件的安全性应从哪几个方面去测试?................................................................................................. 9

    28、软件配置管理工作开展的情况和认识?................................................................................................. 9

    29、你觉得软件测试通过的标准应该是什么样的?.................................................................................... 10

    30、引入测试管理的含义?......................................................................................................................... 10

    31、一套完整的测试应该由哪些阶段组成?............................................................................................... 10

    32、单元测试的主要内容?......................................................................................................................... 10

    33、集成测试也叫组装测试或者联合测试,请简述集成测试的主要内容?.............................................. 10

    34、简述集成测试与系统测试关系?.......................................................................................................... 10

    35、软件测试的文档测试应当贯穿于软件生命周期的全过程,其中用户文档是文档测试的重点。那么软件系统的用户文档包括哪些?.............................................................................................................................. 10

    36、软件系统中除用户文档之外,文档测试还应该关注哪些文档?.......................................................... 10

    37、简述软件系统中用户文档的测试要点?............................................................................................... 11

    38、单元测试主要内容是什么?.................................................................................................................. 11

    39、如何理解强度测试?............................................................................................................................. 13

    40、如何理解压力、负载、性能测试测试?............................................................................................... 13

    41、什么是系统瓶颈?................................................................................................................................. 13

    42、文档测试主要包含什么内容?.............................................................................................................. 13

    43、功能测试用例需要详细到什么程度才是合格的?................................................................................ 14

    44、配置和兼容性测试的区别是什么?....................................................................................................... 14

    45、软件文档测试主要包含什么?.............................................................................................................. 15

    46、没有产品说明书和需求文档地情况下能够进行黑盒测试吗?............................................................. 15

    47、测试中的“杀虫剂怪事”是指什么?................................................................................................... 15

    48、在配置测试中,如何判断发现的缺陷是普通问题还是特定的配置问题?........................................... 15

    49、为什么尽量不要让时间有富裕的员工去做一些测试?......................................................................... 16

    50、完全测试程序是可能的吗?.................................................................................................................. 16

    51、软件测试的风险主要体现在哪里?....................................................................................................... 16

    52、发现的缺陷越多,说明软件缺陷越多吗?........................................................................................... 16

    53、所有的软件缺陷都能修复吗?所有的软件缺陷都要修复吗?............................................................. 17

    54、软件测试人员就是QA吗?.................................................................................................................... 17

    55、如何减少测试人员跳槽带来的损失?................................................................................................... 17

    56、测试产品与测试项目的区别是什么?................................................................................................... 17

    57、和用户共同测试(UAT测试)的注意点有哪些?................................................................................. 18

    58、如何编写提交给用户的测试报告?....................................................................................................... 18

    59、测试工具在测试工作中是什么地位?................................................................................................... 18

    60、什么是软件测试,软件测试的目的?................................................................................................... 18

    61、简述负载测试与压力测试的区别。....................................................................................................... 19

    62、写出bug报告流转的步骤,每步的责任人及主要完成的工作。.......................................................... 19

    63、写出bug报告当中一些必备的内容。................................................................................................... 19

    64、开发人员老是犯一些低级错误怎么解决?........................................................................................... 20

    65、画出软件测试的V模型图。.................................................................................................................. 20

    66、为什么要在一个团队中开展软件测试工作?........................................................................................ 20

    67、您在以往的测试工作中都曾经具体从事过哪些工作?其中最擅长哪部分工作?............................... 20

    68、您所熟悉的软件测试类型都有哪些?请试着分别比较这些不同的测试类型的区别与联系(如功能测试、性能测试……)............................................................................................................................................. 20

    69、您认为做好测试用例设计工作的关键是什么?.................................................................................... 21

    70、请试着比较一下黑盒测试、白盒测试、单元测试、集成测试、系统测试、验收测试的区别与联系。21

    71、测试计划工作的目的是什么?测试计划工作的内容都包括什么?其中哪些是最重要的?................. 22

    72、您所熟悉的测试用例设计方法都有哪些?请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。................................................................................................................................................................ 22

    73、请以您以往的实际工作为例,详细的描述一次测试用例设计的完整的过程。.................................... 23

    74、您以往是否曾经从事过性能测试工作?如果有,请尽可能的详细描述您以往的性能测试工作的完整过程。................................................................................................................................................................ 23

    75、你对测试最大的兴趣在哪里?为什么?................................................................................................ 23

    76、你以前工作时的测试流程是什么?....................................................................................................... 24

    77、当开发人员说不是BUG时,你如何应付?.......................................................................................... 24

    78、软件的构造号与版本号之间的区别?BVT(BuildVerificationTest)............................................... 24

    79、您以往的工作中,一条软件缺陷(或者叫Bug)记录都包含了哪些内容?如何提交高质量的软件缺陷(Bug)记录?.................................................................................................................................................... 25

    80、您以往所从事的软件测试工作中,是否使用了一些工具来进行软件缺陷(Bug)的管理?如果有,请结合该工具描述软件缺陷(Bug)跟踪管理的流程。.......................................................................................... 25

    81、您认为性能测试工作的目的是什么?做好性能测试工作的关键是什么?........................................... 25

    82、单元测试、集成测试、系统测试的侧重点是什么?............................................................................. 25

    83、集成测试通常都有那些策略?............................................................................................................... 25

    84、一个缺陷测试报告的组成...................................................................................................................... 25

    85、基于WEB信息管理系统测试时应考虑的因素有哪些?......................................................................... 25

    86、软件测试项目从什么时候开始,?为什么?........................................................................................ 26

    87、需求测试注意事项有哪些?.................................................................................................................. 26

    88、简述一下缺陷的生命周期...................................................................................................................... 26

    89、你在你所在的公司是怎么开展测试工作的?是如何组织的?............................................................. 26

    90、你认为理想的测试流程是什么样子?................................................................................................... 26

    91、您在从事性能测试工作时,是否使用过一些测试工具?如果有,请试述该工具的工作原理,并以一个具体的工作中的例子描述该工具是如何在实际工作中应用的。...................................................................... 26

    92、软件测试活动的生命周期是什么?....................................................................................................... 26

    93、请画出软件测试活动的流程图?.......................................................................................................... 26

    94、针对缺陷采取怎样管理措施?.............................................................................................................. 26

    95、什么是测试评估?测试评估的范围是什么?........................................................................................ 26

    96、如果能够执行完美的黑盒测试,还需要进行白盒测试吗?为什么?.................................................. 26

    97、测试结束的标准是什么?...................................................................................................................... 26

    98、软件验收测试除了alpha ,beta测试以外,还有哪一种?.................................................................... 26

    99、做测试多久了?以前做过哪些项目?你们以前测试的流程是怎样的?用过哪些测试工具?............. 27

    100、请就如何在开发中进行软件质量控制说说你的看法.......................................................................... 27

    101、一套完整的测试应该由哪些阶段组成?分别阐述一下各个阶段。.................................................... 27

    102、软件测试的类型有那些?分别比较这些不同的测试类型的区别与联系。......................................... 27

    103、测试用例通常包括那些内容?着重阐述编制测试用例的具体做法.................................................... 27

    104、在分别测试winform的C/S结构与测试WEB结构的软件是,应该采取什么样的方法分别测试?他们存在什么样的区别与联系?.................................................................................................................................. 27

    105、在测试winform的C/S结构软件时,发现这个软件的运行速度很慢,您会认为是什么原因?您会采取哪些方法去检查这个原因?.............................................................................................................................. 27

    106、描述使用bugzilla缺陷管理工具对软件缺陷(BUG)跟踪的管理的流程........................................ 27

    107、你都用什么测试方法 针对不同的产品或者系统或者模块,有不同的测试方法。总体而言有白盒测试和黑盒测试。.................................................................................................................................................... 27

    108、怎么编写案例 案例的编写与测试阶段的定义有很大的关系。系统测试和unit测试的案例可能不同。总体而言测试案例根据系统的需求而定。....................................................................................................... 27

    109、怎么才能够全面的测试到每一个点 测试的全面性主要需要在设计测试计划的时候考虑,从测试策略,产品需求等等多个角度考虑从而定义全部的测试点。................................................................................. 27

    110、谈谈软件测试技术,以及如何提高..................................................................................................... 27

    111、谈谈软件测试职业发展,以及个人的打算......................................................................................... 27

    112、谈谈软件测试在企业的地位,也可以结合软件生命周期来谈........................................................... 27

    113、一般公司里实际的软件测试流程是什么样的?你们公司又是怎样的?............................................ 27

    114、软件工程师要具有那些素质?............................................................................................................ 27

    115、你会哪些测试工具?怎么操作?........................................................................................................ 27

    116、你能不能说下你的3到5年的职业计划(规划)............................................................................... 27

    117、你觉得你来应聘有那些优势?............................................................................................................ 27

    其他问题:(有可能清晰的思路比确切的答案更重要)............................................................................. 27

     

    开发及环境搭建类面试题

    ....................................................................................................................................................................... 28

    1、描述软件产生内存泄露的原因以及检查方式。(可以结合一种开发语言进行描述)............................ 28

    2、简述什么是值传递,什么是地址传递,两者区别是什么?................................................................... 28

    3、结构化程序设计和面向对象程序设计各自的特点及优缺点是什么?.................................................... 28

    4、简述什么是存储过程和触发器?............................................................................................................. 28

    5、使用C语言编写一个函数,用于交换两个变量的值(地址传递)。...................................................... 29

    6、请简述DNS、活动目录、域的概念。..................................................................................................... 29

    7、描述TCP/IP协议的层次结构,以及每一层中重要协议。...................................................................... 29

    8、简述子网掩码的用途。............................................................................................................................ 29

    9、说出4种以上常用的操作系统及其主要的应用范围(微软的操作系统除外)。.................................... 29

    10、在Linux系统中,一个文件的访问权限是755,其含义是什么?......................................................... 29

    11、Windows操作系统中PATH环境变量的作用是什么?.......................................................................... 30

    12、Ghost的主要用途和常用方法?........................................................................................................... 30

    13、在RedHat中,从root用户切到userl用户,一般用什么命令?..................................................... 30

    14、Linux中,一般怎么隐藏文件?........................................................................................................... 30

    15、如何将自己的本地磁盘(D)做成FTP供远端主机使用?................................................................... 30

    16、对RUP.CMM,CMMI,XP,PSP.TSP的认识?............................................................................................. 30

    17、DNS是什么,它是如何工作的?............................................................................................................... 31

    18、防火墙如何保证安全的?主要有哪些?............................................................................................... 31

    19、目前流行的操作的系统有哪些?请举例说明安装操作系统的注意事项?........................................... 33

    20、简述一下c/s模式或者b/s模式?....................................................................................................... 33

    21、TCP/UDP有哪些区别?.......................................................................................................................... 34

    22、ISO模型?HUB、tch、Router是ISO的第几层设备?....................................................................... 34

    23、内存有哪几种存储组织结构.请分别加以说明?.................................................................................. 34

     

    人力资源面试题

    ....................................................................................................................................................................... 34

    1、你的测试职业发展是什么?你自认为做测试的优势在哪里?................................................................ 34

    2、你为什么想离开目前的职务?................................................................................................................ 34

    3、你对我们公司了解有多少?.................................................................................................................... 34

    4、你找工作时,最重要的考虑因素为何?................................................................................................. 34

    5、为什么我们应该录取你?........................................................................................................................ 34

    6、请谈谈你个人的最大特色。.................................................................................................................... 34

    7、一个测试工程师应具备那些素质和技能?.............................................................................................. 35

    8、您认为在测试人员同开发人员的沟通过程中,如何提高沟通的效率和改善沟通的效果?维持测试人员同开发团队中其他成员良好的人际关系的关键是什么?..................................................................................... 35

    9、在您以往的测试工作中,最让您感到不满意或者不堪回首的事情是什么?您是如何来对待这些事情的?35

    10、在即将完成这次笔试前,您是否愿意谈一些自己在以往的学习和工作中获得的工作经验和心得体会?(可以包括软件测试、过程改进、软件开发或者与此无关的其他方面)....................................................... 35

    11、为什么选择测试这行?......................................................................................................................... 35

    12、你的工作通常能在时限内完成吗.(我想问一下就是她问这个问题的动机是什么).......................... 35

    13、通常你对于别人批评你会有什么样的反应........................................................................................... 35

    14、如果明知这样做不对,你还会依主管的指过去做吗?......................................................................... 35

    15、如果你接到一个客户抱怨的电话,你确知无法解决他的问题,你会怎么处理?............................... 35

    16、请就软件测试人员应该具备什么样的基本素质说说你的看法。.......................................................... 36

    17、你在五年内的个人目标和职业目标分别是什么?................................................................................ 36

    18、你怎样做出自己的职业选择?.............................................................................................................. 36

     

     

     

     

     

     

     

     

    测试技术面试题

    1、什么是兼容性测试?兼容性测试侧重哪些方面?

    参考答案:

    兼容测试主要是检查软件在不同的硬件平台、软件平台上是否可以正常的运行,即是通常说的软件的可移植性。

    兼容的类型,如果细分的话,有平台的兼容,网络兼容,数据库兼容,以及数据格式的兼容。

    兼容测试的重点是,对兼容环境的分析。通常,是在运行软件的环境不是很确定的情况下,才需要做兼容。根据软件运行的需要,或者根据需求文档,一般都能够得出用户会在什么环境下使用该软件,把这些环境整理成表单,就得出做兼容测试的兼容环境了。

    兼容和配置测试的区别在于,做配置测试通常不是Clean OS下做测试,而兼容测试多是在Clean OS的环境下做的。

    2、我现在有个程序,发现在Windows上运行得很慢,怎么判别是程序存在问题还是软硬件系统存在问题?

    参考答案:

    1、检查系统是否有中毒的特征;

    2、检查软件/硬件的配置是否符合软件的推荐标准;

    3、确认当前的系统是否是独立,即没有对外提供什么消耗CPU资源的服务;

    4、如果是C/S或者B/S结构的软件,需要检查是不是因为与服务器的连接有问题,或者访问有问题造成的;

    5、在系统没有任何负载的情况下,查看性能监视器,确认应用程序对CPU/内存的访问情况。

    3、测试的策略有哪些?

    参考答案:

    黑盒/白盒,静态/动态,手工/自动,冒烟测试,回归测试,公测(Beta测试的策略)

    4、正交表测试用例设计方法的特点是什么?

    参考答案:

    用最少的实验覆盖最多的操作,测试用例设计很少,效率高,但是很复杂;

    对于基本的验证功能,以及二次集成引起的缺陷,一般都能找出来;但是更深的缺陷,更复杂的缺陷,还是无能为力的;

    具体的环境下,正交表一般都很难做的。大多数,只在系统测试的时候使用此方法。

    5、描述使用bugzilla缺陷管理工具对软件缺陷(BUG)跟踪的管理的流程?

    参考答案:

    就是Bugzilla的状态转换图。

    6、你觉得bugzilla在使用的过程中,有什么问题?

    参考答案:

    界面不稳定;

    根据需要配置它的不同的部分,过程很烦琐。

    流程控制上,安全性不好界定,很容易对他人的Bug进行误操作;

    没有综合的评分指标,不好确认修复的优先级别。

    7、描述测试用例设计的完整过程?

    参考答案:

    需求分析 + 需求变更的维护工作;

    根据需求 得出测试需求;

    设计测试方案,评审测试方案;

    方案评审通过后,设计测试用例,再对测试用例进行评审;

    8、单元测试的策略有哪些?

    参考答案:

    逻辑覆盖、循环覆盖、同行评审、桌前检查、代码走查、代码评审、景泰数据流分析

    9、LoadRunner分哪三部分?

    参考答案:

    用户动作设计;

    场景设计;

    测试数据分析;

    10、LoadRunner进行测试的流程?

    参考答案:

    1、 测试测试

    2、 创建虚拟用户脚本

    3、 创建运行场景

    4、 运行测试脚本

    5、 监视场景

    6、 分析测试的结果

    以上,最好是结合一个案例,根据以上流程来介绍。

    什么是并发?在lordrunner中,如何进行并发的测试?集合点失败了会怎么样?

    参考答案:

    在同一时间点,支持多个不同的操作。

    LoadRunner中提供IP伪装,集合点,配合虚拟用户的设计,以及在多台电脑上设置,可以比较好的模拟真实的并发。

    集合点,即是多个用户在某个时刻,某个特定的环境下同时进行虚拟用户的操作的。集合点失败,则集合点的才操作就会取消,测试就不能进行。

    12、使用QTP做功能测试,录制脚本的时候,要验证多个用户的登录情况/查询情况,如何操作?

    参考答案:

    分析用户登录的基本情况,得出一组数据,通过性测试/失败性测试的都有(根据TC来设计这些数据),然后录制登录的脚本,将关键的数据参数化,修改脚本,对代码进行加强,调试脚本。

    13、QTP中的Action有什么作用?有几种?

    参考答案:

    Action的作用

    用Action可以对步骤集进行分组

    步骤重组,然后被整体调用

    拥有自己的sheet

    组合有相同需求的步骤,整体操作

    具有独立的对象仓库

    Action的种类

    可复用Action

    不可复用Action

    外部Action

    14、TestDirector有些什么功能,如何对软件测试过程进行管理?

    参考答案:

     需求管理

    定义测试范围

    定义需求树

    描述需求树的功能点

    测试计划

    定义测试目标和测试策略。

    分解应用程序,建立测试计划树。

    确定每个功能点的测试方法。

    将每个功能点连接到需求上,使测试计划覆盖全部的测试需求。

    描述手工测试的测试步骤

    指明需要进行自动测试的功能点

    测试执行

    定义测试集合。

    为每个测试人员制定测试任务和测试日程安排。

    运行自动测试。

    缺陷跟踪

    记录缺陷

    查看新增缺陷,并确定哪些是需要修正的

    相关技术人员修改缺陷

    回归测试

    分析缺陷统计图表,分析应用程序的开发质量。

    15、你所熟悉的软件测试类型都有哪些?请试着分别比较这些不同的测试类型的区别与联系(如功能测试、性能测试……)?

    参考答案:Compatibility Testing(兼容性测试),也称“Configuration testing(配置测试)”,测试软件是否和系统的其它与之交互的元素之间兼容,如:浏览器、操作系统、硬件等。验证测试对象在不同的软件和硬件配置中的运行情况。

     

    Functional testing (功能测试),也称为behavioral testing(行为测试),根据产品特征、操作描述和用户方案,测试一个产品的特性和可操作行为以确定它们满足设计需求。本地化软件的功能测试,用于验证应用程序或网站对目标用户能正确工作。使用适当的平台、浏览器和测试脚本,以保证目标用户的体验将足够好,就像应用程序是专门为该市场开发的一样。

    Performance testing(性能测试),评价一个产品或组件与性能需求是否符合的测试。包括负载测试、强度测试、数据库容量测试、基准测试等类型。

    16、软件缺陷(或者叫Bug)记录都包含了哪些内容?如何提交高质量的软件缺陷(Bug)记录?

    参考答案:5C标准

    17、Beta测试与Alpha测试有什么区别?

    参考答案:Beta testing(β测试),测试是软件的多个用户在一个或多个用户的实际使用环境下进行的测试。开发者通常不在测试现场

    Alpha testing (α测试),是由一个用户在开发环境下进行的测试,也可以是公司内部的用户在模拟实际操作环境下进行的受控测试

    18、软件的评审一般由哪些人参加?其目的是什么?

    参考答案:

    在正式的会议上将软件项目的成果(包括各阶段的文档、产生的代码等)提交给用户、客户或有关部门人员对软件产品进行评审和批准。其目的是找出可能影响软件产品质量、开发过程、维护工作的适用性和环境方面的设计缺陷,并采取补救措施,以及找出在性能、安全性和经济方面的可能的改进。

    人员:用户、客户或有关部门开发人员,测试人员,需求分析师都可以,就看处于评审那个阶段

    19、测试活动中,如果发现需求文档不完善或者不准确,怎么处理?

    参考答案:

    测试需求分析发现需求文档不完善或者不准确,应该立即和相关人员进行协调交流。

    20、阶段评审与项目评审有什么区别?

    参考答案:

    阶段评审对项目各阶段评审:对阶段成果和工作

    项目评审对项目总体评审:对工作和产品

    21、阐述工作版本的定义?

    参考答案:

    构造号: BUILD

    22、什么是桩模块?什么是驱动模块?

    参考答案:

    桩模块:被测模块调用模块

    驱动模块调用被测模块

    23、什么是扇入?什么是扇出?

    参考答案:

    扇入:被调次数,扇出:调其它模块数目

    24、你认为做好测试计划工作的关键是什么?

    参考答案:

    软件测试计划就是在软件测试工作正式实施之前明确测试的对象,并且通过对资源、时间、风险、测试范围和预算等方面的综合分析和规划,保证有效的实施软件测试;

    做好测试计划工作的关键:目的,管理,规范

    1. 明确测试的目标,增强测试计划的实用性

    编写软件测试计划得重要目的就是使测试过程能够发现更多的软件缺陷,因此软件测试计划的价值取决于它对帮助管理测试项目,并且找出软件潜在的缺陷。因此,软件测试计划中的测试范围必须高度覆盖功能需求,测试方法必须切实可行,测试工具并且具有较高的实用性,便于使用,生成的测试结果直观、准确

    2.坚持“5W”规则,明确内容与过程

    “5W”规则指的是“What(做什么)”、“Why(为什么做)”、“When(何时做)”、“Where(在哪里)”、“How(如何做)”。利用“5W”规则创建软件测试计划,可以帮助测试团队理解测试的目的(Why),明确测试的范围和内容(What),确定测试的开始和结束日期(When),指出测试的方法和工具(How),给出测试文档和软件的存放位置(Where)。

    3.采用评审和更新机制,保证测试计划满足实际需求

    测试计划写作完成后,如果没有经过评审,直接发送给测试团队,测试计划内容的可能不准确或遗漏测试内容,或者软件需求变更引起测试范围的增减,而测试计划的内容没有及时更新,误导测试执行人员。

    4. 分别创建测试计划与测试详细规格、测试用例

    应把详细的测试技术指标包含到独立创建的测试详细规格文档,把用于指导测试小组执行测试过程的测试用例放到独立创建的测试用例文档或测试用例管理数据库中。测试计划和测试详细规格、测试用例之间是战略和战术的关系,测试计划主要从宏观上规划测试活动的范围、方法和资源配置,而测试详细规格、测试用例是完成测试任务的具体战术。

    25、你认为做好测试用例工作的关键是什么?

    参考答案:

     需求和设计文档的理解程度,对系统的熟悉程度

    26、简述一下缺陷的生命周期?

    参考答案:提交->确认->分配->修复->验证->关闭

    27、软件的安全性应从哪几个方面去测试?

    参考答案:

    (1)用户认证机制:如数据证书、智能卡、双重认证、安全电子交易协议

    (2)加密机制

    (3)安全防护策略:如安全日志、入侵检测、隔离防护、漏洞扫描

    (4)数据备份与恢复手段:存储设备、存储优化、存储保护、存储管理

    (5)防病毒系统

    28、软件配置管理工作开展的情况和认识?

    参考答案:

    软件配置管理贯穿于软件开发、测试活动的始终,覆盖了开发、测试活动的各个环节,它的重要作用之一就是要全面的管理保存各个配置项,监控各配置项的状态,并向项目经理及相关的人员报告,从而实现对软件过程的控制。

    软件测试配置管理包括4个最基本的活动:

    配置项标识

    配置项控制

    配置项状态报告

    配置审计

           软件配置管理通常借助工具来辅助,主要有MS SourceSafe、Rational ClearCase等

    29、你觉得软件测试通过的标准应该是什么样的?

    参考答案:

        缺陷密度值达到客户的要求

    30、引入测试管理的含义?

    参考答案:风险分析,进度控制、角色分配、质量控制

    31、一套完整的测试应该由哪些阶段组成?

    参考答案:测试计划、测试设计与开发、测试实施、测试评审与测试结论

    32、单元测试的主要内容?

    参考答案:

     模块接口测试、局部数据结构测试、路径测试、错误处理测试、边界测试

    33、集成测试也叫组装测试或者联合测试,请简述集成测试的主要内容?

    参考答案:

    (1)在把各个模块连接起来的时候,穿越模块接口的数据是否会丢失;

     (2)一个模块的功能是否会对另一个模块的功能产生不利的影响;

     (3)各个子功能组合起来,能否达到预期要求的父功能;

     (4)全局数据结构是否有问题;

     (5)单个模块的误差累积起来,是否会放大,从而达到不能接受的程度。

    34、简述集成测试与系统测试关系?

    参考答案:

     (1)集成测试的主要依据概要设计说明书,系统测试的主要依据是需求设计说明书;

     (2)集成测试是系统模块的测试,系统测试是对整个系统的测试,包括相关的软硬件平台、网络以及相关外设的测试。

    35、软件测试的文档测试应当贯穿于软件生命周期的全过程,其中用户文档是文档测试的重点。那么软件系统的用户文档包括哪些?

    参考答案:

      用户手册

      安装和设置指导

      联机帮助

      指南、向导

      样例、示例和模板

      授权/注册登记表

    最终用户许可协议

    36、软件系统中除用户文档之外,文档测试还应该关注哪些文档?

    参考答案:

    开发文档

    软件需求说明书

        数据库设计说明书

        概要设计说明书

        详细设计说明书

        可行性研究报告

    管理文档

        项目开发计划

        测试计划

        测试报告

        开发进度月报

        开发总结报告

    37、简述软件系统中用户文档的测试要点?

    参考答案:

     (1)读者群。文档面向的读者定位要明确。对于初级用户、中级用户以及高级用户应该有不同的定位

     (2)术语。文档中用到的术语要适用与定位的读者群,用法一致,标准定义与业界规范相吻合。

     (3)正确性。测试中需检查所有信息是否真实正确,查找由于过期产品说明书和销售人员夸大事实而导致的错误。检查所有的目录、索引和章节引用是否已更新,尝试链接是否准确,产品支持电话、地址和邮政编码是否正确。

     (4)完整性。对照软件界面检查是否有重要的分支没有描述到,甚至是否有整个大模块没有描述到。

     (5)一致性。按照文档描述的操作执行后,检查软件返回的结果是否与文档描述的相同。

     (6)易用性。对关键步骤以粗体或背景色给用户以提示,合理的页面布局、适量的图表都可以给用户更高的易用性。需要注意的是文档要有助于用户排除错误。不但描述正确操作,也要描述错误处理办法。文档对于用户看到的错误信息应当有更详细的文档解释。

     (7)图表与界面截图。检查所有图表与界面截图是否与发行版本相同。

     (8)样例与示例。像用户一样载入和使用样例。如果是一段程序,就输入数据并执行它。以每一个模块制作文件,确认它们的正确性。

     (9)语言。不出现错别字,不要出现有二义性的说法。特别要注意的是屏幕截图或绘制图形中的文字。

     (10)印刷与包装。检查印刷质量;手册厚度与开本是否合适;包装盒的大小是否合适;有没有零碎易丢失的小部件等等。

    38、单元测试主要内容是什么?

    参考答案:

    单元测试大多数由开发人员来完成,测试人员技术背景较好或者开发系统软件时可能会安排测试人员进行单元测试,大多数进行的单元测试都是开发人员调试程序或者开发组系统联合调试的过程。讨论这个问题主要是扩充一下读者的视野。

    单元测试一般包括五个方面的测试:

    (1)模块接口测试:模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。模块接口测试也是集成测试的重点,这里进行的测试主要是为后面打好基础。测试接口正确与否应该考虑下列因素:

    -输入的实际参数与形式参数的个数是否相同;

    -输入的实际参数与形式参数的属性是否匹配;

    -输入的实际参数与形式参数的量纲是否一致;

    -调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;

    -调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;

    -调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;

    -调用预定义函数时所用参数的个数、属性和次序是否正确;

    -是否存在与当前入口点无关的参数引用;

    -是否修改了只读型参数;

    -对全程变量的定义各模块是否一致;

    -是否把某些约束作为参数传递。

    如果模块功能包括外部输入输出,还应该考虑下列因素:

    -文件属性是否正确;

    -OPEN/CLOSE语句是否正确;

    -格式说明与输入输出语句是否匹配;

    -缓冲区大小与记录长度是否匹配;

    -文件使用前是否已经打开;

    -是否处理了文件尾;

    -是否处理了输入/输出错误;

    -输出信息中是否有文字性错误。

    -局部数据结构测试;

    -边界条件测试;

    -模块中所有独立执行通路测试;

    (2)局部数据结构测试:检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确,局部功能是整个功能运行的基础。重点是一些函数是否正确执行,内部是否运行正确。局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误:

    -不合适或不相容的类型说明;

    -变量无初值;

    -变量初始化或省缺值有错;

    -不正确的变量名(拼错或不正确地截断);

    -出现上溢、下溢和地址异常。

    (3)边界条件测试:边界条件测试是单元测试中最重要的一项任务。众所周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。边界条件测试是一项基础测试,也是后面系统测试中的功能测试的重点,边界测试执行的较好,可以大大提高程序健壮性。

    (4)模块中所有独立路径测试:在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。测试目的主要是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。具体做法就是程序员逐条调试语句。常见的错误包括:

    -误解或用错了算符优先级;

    -混合类型运算;

    -变量初值错;

    -精度不够;

    -表达式符号错。

    比较判断与控制流常常紧密相关,测试时注意下列错误:

    -不同数据类型的对象之间进行比较;

    -错误地使用逻辑运算符或优先级;

    -因计算机表示的局限性,期望理论上相等而实际上不相等的两个量相等;

    -比较运算或变量出错;

    -循环终止条件或不可能出现;

    -迭代发散时不能退出;

    -错误地修改了循环变量。

    模块的各条错误处理通路测试:程序在遇到异常情况时不应该退出,好的程序应能预见各种出错条件,并预设各种出错处理通路。如果用户不按照正常操作,程序就退出或者停止工作,实际上也是一种缺陷,因此单元测试要测试各种错误处理路径。一般这种测试着重检查下列问题:

    -输出的出错信息难以理解;

    -记录的错误与实际遇到的错误不相符;

    -在程序自定义的出错处理段运行之前,系统已介入;

    -异常处理不当;

    -错误陈述中未能提供足够的定位出错信息。

    39、如何理解强度测试?

    参考答案:

    强度测试是为了确定系统在最差工作环境的工作能力,也可能是用于验证在标准工作压力下的各种资源的最下限指标。

    它和压力测试的目标是不同的,压力测试是在标准工作环境下,不断增加系统负荷,最终测试出该系统能力达到的最大负荷(稳定和峰值),而强度测试则是在非标准工作环境下,甚至不断人为降低系统工作环境所需要的资源,如网络带宽,系统内存,数据锁等等,以测试系统在资源不足的情况下的工作状态,通过强度测试,可以确定本系统正常工作的最差环境.

    强度测试和压力测试的测试指标相近,大多都是与时间相关的指标,如并发量(吞吐量),延迟(最大\最小\平均)以及顺序指标等

    强度测试需要对系统的结构熟悉,针对系统的特征设计强度测试的方法

    40、如何理解压力、负载、性能测试测试?

    参考答案:

    性能测试是一个较大的范围,实际上性能测试本身包含了性能、强度、压力、负载等多方面的测试内容。

    压力测试是对服务器的稳定性以及负载能力等方面的测试,是一种很平常的测试。增大访问系统的用户数量、或者几个用户进行大数据量操作都是压力测试。而负载测试是压力相对较大的测试,主要是测试系统在一种或者集中极限条件下的相应能力,是性能测试的重要部分。100个用户对系统进行连续半个小时的访问可以看作压力测试,那么连续访问8个小时就可以认为负载测试,1000个用户连续访问系统1个小时也可以看作是负载测试。

    实际上压力测试和负载测试没有明显的区分。测试人员应该站在关注整体性能的高度上来对系统进行测试。

    41、什么是系统瓶颈?

    参考答案:

    瓶颈主要是指整个软硬件构成的软件系统某一方面或者几个方面能力不能满足用户的特定业务要求,“特定”是指瓶颈会在某些条件下会出现,因为毕竟大多数系统在投入前。

    严格的从技术角度讲,所有的系统都会有瓶颈,因为大多数系统的资源配置不是协调的,例如CPU使用率刚好达到100%时,内存也正好耗尽的系统不是很多见。因此我们讨论系统瓶颈要从应用的角度讨论:关键是看系统能否满足用户需求。在用户极限使用系统的情况下,系统的响应仍然正常,我们可以认为改系统没有瓶颈或者瓶颈不会影响用户工作。

    因此我们测试系统瓶颈主要是实现下面两个目的:

    -发现“表面”的瓶颈。主要是模拟用户的操作,找出用户极限使用系统时的瓶颈,然后解决瓶颈,这是性能测试的基本目标。

    -发现潜在的瓶颈并解决,保证系统的长期稳定性。主要是考虑用户在将来扩展系统或者业务发生变化时,系统能够适应变化。满足用户目前需求的系统不是最好的,我们设计系统的目标是在保证系统整个软件生命周期能够不断适应用户的变化,或者通过简单扩展系统就可以适应新的变化。

    42、文档测试主要包含什么内容?

    参考答案:

    在国内软件开发管理中,文档管理几乎是最弱的一项,因而在测试工作中特别容易忽略文档测试也就不足为奇了。要想给用户提供完整的产品,文档测试是必不可少的。文档测试一般注重下面几个方面:

    文档的完整性:主要是测试文档内容的全面性与完整性,从总体上把握文档的质量。例如用户手册应该包括软件的所有功能模块。

    描述与软件实际情况的一致性:主要测试软件文档与软件实际的一致程度。例如用户手册基本完整后,我们还要注意用户手册与实际功能描述是否一致。因为文档往往跟不上软件版本的更新速度。

    易理解性:主要是检查文档对关键、重要的操作有无图文说明,文字、图表是否易于理解。对于关键、重要的操作仅仅只有文字说明肯定是不够的,应该附有图表使说明更为直观和明了。

    文档中提供操作的实例:这项检查内容主要针对用户手册。对主要功能和关键操作提供的应用实例是否丰富,提供的实例描述是否详细。只有简单的图文说明,而无实例的用户手册看起来就像是软件界面的简单拷贝,对于用户来说,实际上没有什么帮助。

    印刷与包装质量:主要是检查软件文档的商品化程度。有些用户手册是简单打印、装订而成,过于粗糙,不易于用户保存。优秀的文档例如用户手册和技术白皮书,应提供商品化包装,并且印刷精美。

    43、功能测试用例需要详细到什么程度才是合格的?

    参考答案:

    这个问题也是测试工程师经常问的问题。有人主张测试用例详细到每个步骤执行什么都要写出来,目的是即使一个不了解系统的新手都可以按照测试用例来执行工作。主张这类写法的人还可以举出例子:欧美、日本等软件外包文档都是这样做的。

    另外一种观点就是主张写的粗些,类似于编写测试大纲。主张这种观点的人是因为软件开发需求管理不规范,变动十分频繁,因而不能按照欧美的高标准来编写测试用例。这样的测试用例容易维护,可以让测试执行人员有更大的发挥空间。

    实际上,软件测试用例的详细程度首先要以覆盖到测试点为基本要求。举个例子:“用户登陆系统”的测试用例可以不写出具体的执行数据,但是至少要写出五种以上情况(),如果只用一句话覆盖了这个功能是不合格的测试用例。覆盖功能点不是指列出功能点,而是要写出功能点的各个方面(如果组合情况较多时可以采用等价划分)。

    另一个影响测试用例的就是组织的开发能力和测试对象特点。如果开发力量比较落后,编写较详细的测试用例是不现实的,因为根本没有那么大的资源投入,当然这种情况很随着团队的发展而逐渐有所改善。测试对象特点重点是指测试对象在进度、成本等方面的要求,如果进度较紧张的情况下,是根本没有时间写出高质量的测试用例的,甚至有些时候测试工作只是一种辅助工作,因而不编写测试用例。

    因此,测试用例的编写要根据测试对象特点、团队的执行能力等各个方面综合起来决定编写策略。最后要注意的是测试人员一定不能抱怨,力争在不断提高测试用例编写水平的同时,不断地提高自身能力。

    44、配置和兼容性测试的区别是什么?

    参考答案:

    配置测试的目的是保证软件在其相关的硬件上能够正常运行,而兼容性测试主要是测试软件能否与不同的软件正确协作。

    配置测试的核心内容就是使用各种硬件来测试软件的运行情况,一般包括:

    (1)软件在不同的主机上的运行情况,例如Dell和Apple;

    (2)软件在不同的组件上的运行情况,例如开发的拨号程序要测试在不同厂商生产的Modem上的运行情况;

    (3)不同的外设;

    (4)不同的接口;

    (5)不同的可选项,例如不同的内存大小;

    兼容性测试的核心内容:

    (1)测试软件是否能在不同的操作系统平台上兼容;

    (2)测试软件是否能在同一操作系统平台的不同版本上兼容;

    (3)软件本身能否向前或者向后兼容;

    (4)测试软件能否与其它相关的软件兼容;

    (5)数据兼容性测试,主要是指数据能否共享;

    配置和兼容性测试通称对开发系统类软件比较重要,例如驱动程序、操作系统、数据库管理系统等。具体进行时仍然按照测试用例来执行。

    45、软件文档测试主要包含什么?

    参考答案:

    随着软件文档系统日益庞大,文档测试已经成为软件测试的重要内容。文档测试对象主要如下:

    -包装文字和图形;

    -市场宣传材料、广告以及其它插页;

    -授权、注册登记表;

    -最终用户许可协议;

    -安装和设置向导;

    -用户手册;

    -联机帮助;

    -样例、示范例子和模板;

    -……

    文档测试的目的是提高易用性和可靠性,降低支持费用,因为用户通过文档就可以自己解决问题。因文档测试的检查内容主要如下:

    -读者对象——主要是文档的内容是否能让该级别的读者理解;

    -术语——主要是检查术语是否适合读者;

    -内容和主题——检查主题是否合适、是否丢失、格式是否规范等;

    -图标和屏幕抓图——检查图表的准确度和精确度;

    -样例和示例——是否与软件功能一致;

    -拼写和语法;

    -文档的关联性——是否与其它相关文档的内容一致,例如与广告信息是否一致;

    文档测试是相当重要的一项测试工作,不但要给予充分的重视,更要要认真的完成,象做功能测试一样来对待文档测试。

    46、没有产品说明书和需求文档地情况下能够进行黑盒测试吗?

    参考答案:

    这个问题是国内测试工程师经常遇到的问题,根源就是国内软件开发文档管理不规范,对变更的管理方法就更不合理了。实际上没有任何文档的时候,测试人员是能够进行黑盒测试的,这种测试方式我们可以称之为探索测试,具体做法就是测试工程师根据自己的专业技能、领域知识等不断的深入了解测试对象、理解软件功能,进而发现缺陷。

    在这种做法基本上把软件当成了产品说明书,测试过程中要和开发人员不断的进行交流。尤其在作项目的时候,进度压力比较大,可以作为加急测试方案。最大的风险是不知道有些特性是否被遗漏。

    47、测试中的“杀虫剂怪事”是指什么?

    参考答案:

    “杀虫剂怪事”一词由BorisBeizer在其编著的《软件测试技术》第二版中提出。用于描述测试人员对同一测试对象进行的测试次数越多,发现的缺陷就会越来越少的现象。就像老用一种农药,害虫就会有免疫力,农药发挥不了效力。这种现象的根本原因就是测试人员对测试软件过于熟悉,形成思维定势。

    为了克服这种现象,测试人员需要不断编写新的测试程序或者测试用例,对程序的不同部分进行测试,以发现更多的缺陷。也可以引用新人来测试软件,刚刚进来的新手往往能发现一些意想不到的问题。

    48、在配置测试中,如何判断发现的缺陷是普通问题还是特定的配置问题?

    参考答案:

    在进行配置测试时,测试工程师仍然会发现一些普通的缺陷,也就是与配置环境无关的缺陷。因此判断新发现的问题,需要在不同的配置中重新执行发现软件缺陷的步骤,如果软件缺陷不出现了,就可能是配置缺陷;如果在所有的配置中都出现,就可能是普通缺陷。

    需要注意的是,配置问题可以在一大类配置中出现。例如,拨号程序可能在所有的外置Modem中都存在问题,而内置的Modem不会有任何问题。

    49、为什么尽量不要让时间有富裕的员工去做一些测试?

    参考答案:

    表面上看这体现了管理的效率和灵活性,但实际上也体现了管理者对测试的轻视。测试和测试的人有很大关系。测试工作人员应该是勤奋并富有耐心,善于学习、思考和发现问题,细心有条理,总结问题,如果具备这样的优点,做其它工作同样也会很出色,因此这里还有一个要求,就是要喜欢测试这项工作。如果他是专职的,那么肯定更有经验和信心。国内的小伙子好象都喜欢做程序员,两者工作性质不同,待遇不同,地位不同,对自我实现的价值的认识也不同,这是行业的一个需要改善的问题。如果只是为了完成任务而完成任务,或者发现了几个问题就觉得满意了,这在任何其它工作中都是不行的。

    50、完全测试程序是可能的吗?

    参考答案:

    软件测试初学者可能认为拿到软件后需要进行完全测试,找到全部的软件缺陷,使软件“零缺陷”发布。实际上完全测试是不可能的。主要有以下一个原因:

    -完全测试比较耗时,时间上不允许;

    -完全测试通常意味着较多资源投入,这在现实中往往是行不通的;

    -输入量太大,不能一一进行测试;

    -输出结果太多,只能分类进行验证;

    -软件实现途径太多;

    -软件产品说明书没有客观标准,从不同的角度看,软件缺陷的标准不同;

    因此测试的程度要根据实际情况确定。

    51、软件测试的风险主要体现在哪里?

    参考答案:

    我们没有对软件进行完全测试,实际就是选择了风险,因为缺陷极有可能存在没有进行测试的部分。举个例子,程序员为了方便,在调试程序时会弹出一些提示信息框,而这些提示只在某种条件下会弹出,碰巧程序发布前这些代码中的一些没有被注释掉。在测试时测试工程师又没有对其进行测试。如果客户碰到它,这将是代价昂贵的缺陷,因为交付后才被客户发现。

    因此,我们要尽可能的选择最合适的测试量,把风险降低到最小。

    52、发现的缺陷越多,说明软件缺陷越多吗?

    参考答案:

    这是一个比较常见的现象。测试工程师在没有找到缺陷前会绞尽脑汁的思考,但是找到一个后,会接二连三的发现很多缺陷,颇有个人成就感。其中的原因主要如下:

    -代码复用、拷贝代码导致程序员容易犯相同的错误。类的继承导致所有的子类会包含基类的错误,反复拷贝同一代码意味可能也复制了缺陷。

    -程序员比较劳累是可以导致某些连续编写的功能缺陷较多。程序员加班是一种司空见惯的现象,因此体力不只时容易编写一些缺陷较多的程序。而这些连续潜伏缺陷恰恰时测试工程师大显身手的地方。

    “缺陷一个连着一个”不是一个客观规律,只是一个常见的现象。如果软件编写的比较好,这种现象就不常见了。测试人员只要严肃认真的测试程序就可以了。

    53、所有的软件缺陷都能修复吗?所有的软件缺陷都要修复吗?

    参考答案:

    从技术上讲,所有的软件缺陷都是能够修复的,但是没有必要修复所有的软件缺陷。测试人员要做的是能够正确判断什么时候不能追求软件的完美。对于整个项目团队,要做的是对每一个软件缺陷进行取舍,根据风险决定那些缺陷要修复。发生这种现象的主要原因如下:

    -没有足够的时间资源。在任何一个项目中,通常情况下开发人员和测试人员都是不够用的,而且在项目中没有预算足够的回归测试时间,再加上修改缺陷可能引入新的缺陷,因此在交付期限的强大压力下,必须放弃某些缺陷的修改。

    -有些缺陷只是特殊情况下出现,这种缺陷处于商业利益考虑,可以在以后升级中进行修复。

    -不是缺陷的缺陷。我们经常会碰到某些功能方面的问题被当成缺陷来处理,这类问题可以以后有时间时考虑再处理。

    最后要说的是,缺陷是否修改要由软件测试人员、项目经理、程序员共同讨论来决定是否修复,不同角色的人员从不同的角度来思考,以做出正确的决定。

    54、软件测试人员就是QA吗?

    参考答案:

    软件测试人员的职责是尽可能早的找出软件缺陷,确保得以修复。而质量保证人员(QA)主要职责是创建或者制定标准和方法,提高促进软件开发能力和减少软件缺陷。测试人员的主要工作是测试,质量保证人员日常工作重要内容是检查与评审,测试工作也是测试保证人员的工作对象。

    软件测试和质量是相辅相成的关系,都是为了提高软件质量而工作。

    55、如何减少测试人员跳槽带来的损失?

    参考答案:

    在IT行业里跳槽已经是一种司空见惯的现象,而且跳槽无论给公司还是给个人都会带来一定的损失。测试队伍也无疑会面临跳槽的威胁,作为测试经理管理者,只有从日常工作中开始做起,最能最大限度的减少损失。建议我们从以下两个方面做起:

    -加强部门内员工之间的互相学习,互相学习是建立学习型组织的基本要求,是知识互相转移的过程。在此基础上,可以把个人拥有的技术以知识的形式沉积下来,也就完成了隐性知识到显性知识的转化。

    -通常情况下,企业能为员工提供足够大的发展空间时,如果不是待遇特别低,员工都不会主动离开企业。因此我们要想留住员工,管理者就应该把员工的个人成长和企业的发展联系起来,为员工设定合理发展规划并付诸实现。不过这项要求做起来比较,要有比较好的企业文化为依托。

    56、测试产品与测试项目的区别是什么?

    参考答案:

    习惯上把开发完成后进行商业化、几乎不进行代码修改就可以售给用户使用的软件成为软件产品,也就是可以买“卖拷贝”的软件,例如Windows2000。而通常把针对一个或者几个特定的用户而开发的软件成为软件项目,软件项目是一种个性化的产品,可以是按照用户要求全部重新开发,也可以修改已有的软件产品来满足特定的用户需求。项目和产品的不同特点,决定我们测试产品和测试项目仍然会有很多不同的地方:

    -质量要求不同。通常产品的质量要高一些,修复发布后产品的缺陷成本较高,甚至会带来很多负面的影响。而做项目通常面向某一用户,虽然质量越高越好,但是一般只要满足用户要求就可以了。

    -测试资源投入多少不同。做软件产品通常是研发中心来开发,进度压力要小些。同时由于质量要求高,因此会投入较多的人力、物力资源。

    -项目最后要和用户共同验收测试,这是产品测试不具有的特点。

    此外,测试产品与测试项目在缺陷管理方面、测试策略制定都会有很大不同,测试管理者应该结合具体的环境,恰如其分的完成工作。

    57、和用户共同测试(UAT测试)的注意点有哪些?

    参考答案:

    软件产品在投产前,通常都会进行用户验收测试。如果用户验收测试没有通过,直接结果就是那不到“Money”,间接影响是损害了公司的形象,而后者的影响往往更严重。根据作者的经验,用户验收测试一定要让用户满意。

    实际上用户现场测试更趋于是一种演示。在不欺骗用户的前提下,我们向用户展示我们软件的优点,最后让“上帝”满意并欣然掏出“银子”才是我们的目标。因此用户测试要注意下面的事项:

    (1)用户现场测试不可能测试全部功能,因此要测试核心功能。这需要提前做好准备,这些核心功能一定要预先经过测试,证明没有问题才可以和用户共同进行测试。测试核心模块的目的是建立用户对软件的信心。当然如果这些模块如果问题较多,不应该进行演示。

    (2)如果某些模块确实有问题,我们可以演示其它重要的业务功能模块,必要时要向用户做成合理的解释。争得时间后,及时修改缺陷来弥补。

    (3)永远不能欺骗用户,蒙混过关。道理很简单,因为软件是要给用户用的,问题早晚会暴露出来,除非你可以马上修改。

    和用户进行测试还要注意各种交流技巧,争取不但短期利益得到了满足,还要为后面得合作打好基础。

    58、如何编写提交给用户的测试报告?

    参考答案:

    随着测试工作越来越受重视,开发团队向客户提供测试文档是不可避免的事情。很多人会问:“我们可以把工作中的测试报告提供给客户吗?”答案是否定的。因为提供内部测试报告,可能会让客户失去信心,甚至否定项目。

    测试报告一般分为内部测试报告和外部测试报告。内部报告是我们在测试工作中的项目文档,反映了测试工作的实施情况,这里不过多讨论,读者可以参考相关教材。这里主要讨论一下外部测试报告的写法,一般外部测试报告要满足下面几个要求:

    -根据内部测试报告进行编写,一般可以摘录;

    -不可以向客户报告严重缺陷,即使是已经修改的缺陷,开发中的缺陷也没有必要让客户知道;

    -报告上可以列出一些缺陷,但必须是中级的缺陷,而且这些缺陷必须是修复的;

    -报告上面的内容尽量要真实可靠;

    -整个测试报告要仔细审阅,力争不给项目带来负面作用,尤其是性能测试报告。

    总之,外部测试报告要小心谨慎的编写。

    59、测试工具在测试工作中是什么地位?

    参考答案:

    国内的很多测试工程师对测试工具相当迷恋,尤其是一些新手,甚至期望测试工具可以取代手工测试。测试工具在测试工作中起的是辅助作用,一般用来提高测试效率。自动化测试弥补了手工测试的不足,减轻一定的工作量。实际上测试工具是无法替代大多数手工测试的,而一些诸如性能测试等自动化测试也是手工所不能完成的。

    对于自动测试技术,应当依据软件的不同情况来分别对待,一般自动技术会应用在引起大量重复性工作的地方、系统的压力点、以及任何适合使用程序解决大批量输入数据的地方。然后再寻找合适的自动测试工具,或者自己开发测试程序。一定不要为了使用测试工具而使用。

    60、什么是软件测试,软件测试的目的?

    参考答案:

    61、简述负载测试与压力测试的区别。

    参考答案:

        压力测试(Stress Testing)

    压力测试的主要任务就是获取系统正确运行的极限,检查系统在瞬间峰值负荷下正确执行的能力。例如,对服务器做压力测试时就可以增加并发操作的用户数量;或者不停地向服务器发送请求;或一次性向服务器发送特别大的数据等。看看服务器保持正常运行所能达到的最大状态。人们通常使用测试工具来完成压力测试,如模拟上万个用户从终端同时登录,这是压力测试中常常使用的方法。

    负载测试(Volume Testing)

    用于检查系统在使用大量数据的时候正确工作的能力,即检验系统的能力最高能达到什么程度。例如,对于信息检索系统,让它使用频率达到最大;对于多个终端的分时系统,让它所有的终端都开动。在使整个系统的全部资源达到“满负荷”的情形下,测试系统的承受能力。

    62、写出bug报告流转的步骤,每步的责任人及主要完成的工作。

    参考答案:(要结合自己实际的工作经验进行回答,不同公司略有区别)

        测试人员提交新的Bug入库,错误状态为New。

    高级测试员/测试经理验证错误,如果确认是错误,分配给开发组。设置状态为Open。如果不是错误,则拒绝,设置为Declined状态。

    开发经理分配bug至对应的模块开发人员。

    开发人员查询状态为Open的Bug,如果不是错误,则置状态为Declined;如果是Bug则修复并置状态为Fixed。不能解决的Bug,要留下文字说明及保持Bug为Open状态。

    对于不能解决和延期解决的Bug,不能由开发人员自己决定,一般要通过某种会议(评审会)通过才能认可。

    测试人员查询状态为Fixed的Bug,然后验证Bug是否已解决,如解决,置Bug的状态为Closed,如没有解决,置bug状态为Reopen。

    63、写出bug报告当中一些必备的内容。

    参考答案:

           硬件平台和操作系统

           测试应用的硬件平台(Platform),通常选择“PC”。

           测试应用的操作系统平台(OS)。

    a)        版本

           提交缺陷报告时通过该字段标识此缺陷存在于被测试软件的哪个版本。

    b)        Bug报告优先级

    c)         Bug状态

    d)        Bug的编号

    e)         发现人

    f)         提交人

    g)        指定处理人

    h)        概述

    i)          从属关系

    j)         详细描述

    k)        严重程度

    l)          所属模块

    m)      附件

    n)        提交日期

    64、开发人员老是犯一些低级错误怎么解决?

    参考答案:

    这种现象在开发流程不规范的团队里特别常见,尤其是一些“作坊式”的团队里。解决这种问题一般从两个方面入手:

    一方面从开发管理入手,也就是从根源来解决问题。可以制定规范的开发流程,甚至可以制定惩罚制度,还有就是软件开发前做好规划设计。

    另一方面就是加强测试,具体做法就是加强开发人员的自己测试,把这些问题“消灭”在开发阶段,这是比较好的做法,读者可以参考第13章试案例分析的“13.1.2缺陷反复出现,谁的责任”小节,13.1.2专门讨论了这类问题的方法。

    此外,还可以通过规范的缺陷管理来对开发人员进行控制,比如测试部门整理出常见的缺陷,让开发人员自己对照进行检查,以减少这类低级错误的发生。

    开发人员犯错误是正常的现象,作为测试人员一定不能抱怨,要认认真真的解决问题才是上策。

    65、画出软件测试的V模型图。

      参考答案:

            

    66、为什么要在一个团队中开展软件测试工作?

    参考答案:

    因为没有经过测试的软件很难在发布之前知道该软件的质量,就好比ISO质量认证一样,测试同样也需要质量的保证,这个时候就需要在团队中开展软件测试的工作。在测试的过程发现软件中存在的问题,及时让开发人员得知并修改问题,在即将发布时,从测试报告中得出软件的质量情况。

    67、您在以往的测试工作中都曾经具体从事过哪些工作?其中最擅长哪部分工作?

    参考答案:(根据项目经验不同,灵活回答即可)

    我曾经做过web测试,后台测试,客户端软件,其中包括功能测试性能测试,用户体验测试。最擅长的是功能测试

    68、您所熟悉的软件测试类型都有哪些?请试着分别比较这些不同的测试类型的区别与联系(如功能测试、性能测试……)

    参考答案:

    测试类型有:功能测试,性能测试,界面测试。

      功能测试在测试工作中占的比例最大,功能测试也叫黑盒测试。是把测试对象看作一个黑盒子。利用黑盒测试法进行动态测试时,需要测试软件产品的功能,不需测试软件产品的内部结构和处理过程。采用黑盒技术设计测试用例的方法有:等价类划分、边界值分析、错误推测、因果图和综合策略。

      性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。负载测试和压力测试都属于性能测试,两者可以结合进行。通过负载测试,确定在各种工作负载下系统的性能,目标是测试当负载逐渐增加时,系统各项性能指标的变化情况。压力测试是通过确定一个系统的瓶颈或者不能接收的性能点,来获得系统能提供的最大服务级别的测试。

      界面测试,界面是软件与用户交互的最直接的层,界面的好坏决定用户对软件的第一印象。而且设计良好的界面能够引导用户自己完成相应的操作,起到向导的作用。同时界面如同人的面孔,具有吸引用户的直接优势。设计合理的界面能给用户带来轻松愉悦的感受和成功的感觉,相反由于界面设计的失败,让用户有挫败感,再实用强大的功能都可能在用户的畏惧与放弃中付诸东流。

      区别在于,功能测试关注产品的所有功能上,要考虑到每个细节功能,每个可能存在的功能问题。性能测试主要关注于产品整体的多用户并发下的稳定性和健壮性。界面测试更关注于用户体验上,用户使用该产品的时候是否易用,是否易懂,是否规范(快捷键之类的),是否美观(能否吸引用户的注意力),是否安全(尽量在前台避免用户无意输入无效的数据,当然考虑到体验性,不能太粗鲁的弹出警告)?做某个性能测试的时候,首先它可能是个功能点,首先要保证它的功能是没问题的,然后再考虑该功能点的性能测试

    69、您认为做好测试用例设计工作的关键是什么?

    参考答案:

           白盒测试用例设计的关键是以较少的用例覆盖尽可能多的内部程序逻辑结果

    黑盒法用例设计的关键同样也是以较少的用例覆盖模块输出和输入接口。不可能做到完全测试,以最少的用例在合理的时间内发现最多的问题

    70、请试着比较一下黑盒测试、白盒测试、单元测试、集成测试、系统测试、验收测试的区别与联系。

    参考答案:

           黑盒测试:已知产品的功能设计规格,可以进行测试证明每个实现了的功能是否符合要求。

      白盒测试:已知产品的内部工作过程,可以通过测试证明每种内部操作是否符合设计规格要求,所有内部成分是否以经过检查。

      软件的黑盒测试意味着测试要在软件的接口处进行。这种方法是把测试对象看做一个黑盒子,测试人员完全不考虑程序内部的逻辑结构和内部特性,只依据程序的需求规格说明书,检查程序的功能是否符合它的功能说明。因此黑盒测试又叫功能测试或数据驱动测试。黑盒测试主要是为了发现以下几类错误:

      1、是否有不正确或遗漏的功能?

      2、在接口上,输入是否能正确的接受?能否输出正确的结果?

      3、是否有数据结构错误或外部信息(例如数据文件)访问错误?

      4、性能上是否能够满足要求?

      5、是否有初始化或终止性错误?

      软件的白盒测试是对软件的过程性细节做细致的检查。这种方法是把测试对象看做一个打开的盒子,它允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序所有逻辑路径进行测试。通过在不同点检查程序状态,确定实际状态是否与预期的状态一致。因此白盒测试又称为结构测试或逻辑驱动测试。白盒测试主要是想对程序模块进行如下检查:

      1、对程序模块的所有独立的执行路径至少测试一遍。

      2、对所有的逻辑判定,取“真”与取“假”的两种情况都能至少测一遍。

      3、在循环的边界和运行的界限内执行循环体。

      4、测试内部数据结构的有效性,等等。

      单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。

      单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。

      集成测试(也叫组装测试,联合测试)是单元测试的逻辑扩展。它的最简单的形式是:两个已经测试过的单元组合成一个组件,并且测试它们之间的接口。从这一层意义上讲,组件是指多个单元的集成聚合。在现实方案中,许多单元组合成组件,而这些组件又聚合成程序的更大部分。方法是测试片段的组合,并最终扩展进程,将您的模块与其他组的模块一起测试。最后,将构成进程的所有模块一起测试。

      系统测试是将经过测试的子系统装配成一个完整系统来测试。它是检验系统是否确实能提供系统方案说明书中指定功能的有效方法。(常见的联调测试)

      系统测试的目的是对最终软件系统进行全面的测试,确保最终软件系统满足产品需求并且遵循系统设计。

      验收测试是部署软件之前的最后一个测试操作。验收测试的目的是确保软件准备就绪,并且可以让最终用户将其用于执行软件的既定功能和任务。

    验收测试是向未来的用户表明系统能够像预定要求那样工作。经集成测试后,已经按照设计把所有的模块组装成一个完整的软件系统,接口错误也已经基本排除了,接着就应该进一步验证软件的有效性,这就是验收测试的任务,即软件的功能和性能如同用户所合理期待的那样。

    71、测试计划工作的目的是什么?测试计划工作的内容都包括什么?其中哪些是最重要的?

    参考答案:

           软件测试计划是指导测试过程的纲领性文件,包含了产品概述、测试策略、测试方法、测试区域、测试配置、测试周期、测试资源、测试交流、风险分析等内容。借助软件测试计划,参与测试的项目成员,尤其是测试管理人员,可以明确测试任务和测试方法,保持测试实施过程的顺畅沟通,跟踪和控制测试进度,应对测试过程中的各种变更。

    测试计划和测试详细规格、测试用例之间是战略和战术的关系,测试计划主要从宏观上规划测试活动的范围、方法和资源配置,而测试详细规格、测试用例是完成测试任务的具体战术。所以其中最重要的是测试测试策略和测试方法(最好是能先评审)

    72、您所熟悉的测试用例设计方法都有哪些?请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。

    参考答案:

           1.等价类划分

      划分等价类: 等价类是指某个输入域的子集合.在该子集合中,各个输入数据对于揭露程序中的错误都是等效的.并合理地假定:测试某等价类的代表值就等于对这一类其它值的测试.因此,可以把全部输入数据合理划分为若干等价类,在每一个等价类中取一个数据作为测试的输入条件,就可以用少量代表性的测试数据.取得较好的测试结果.等价类划分可有两种不同的情况:有效等价类和无效等价类.

      2.边界值分析法

      边界值分析方法是对等价类划分方法的补充。测试工作经验告诉我,大量的错误是发生在输入或输出范围的边界上,而不是发生在输入输出范围的内部.因此针对各种边界情况设计测试用例,可以查出更多的错误.

      使用边界值分析方法设计测试用例,首先应确定边界情况.通常输入和输出等价类的边界,就是应着重测试的边界情况.应当选取正好等于,刚刚大于或刚刚小于边界的值作为测试数据,而不是选取等价类中的典型值或任意值作为测试数据.

        3.错误推测法

      基于经验和直觉推测程序中所有可能存在的各种错误, 从而有针对性的设计测试用例的方法.

      错误推测方法的基本思想: 列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据他们选择测试用例. 例如, 在单元测试时曾列出的许多在模块中常见的错误. 以前产品测试中曾经发现的错误等, 这些就是经验的总结. 还有, 输入数据和输出数据为0的情况. 输入表格为空格或输入表格只有一行. 这些都是容易发生错误的情况. 可选择这些情况下的例子作为测试用例.

        4.因果图方法

      前面介绍的等价类划分方法和边界值分析方法,都是着重考虑输入条件,但未考虑输入条件之间的联系, 相互组合等. 考虑输入条件之间的相互组合,可能会产生一些新的情况. 但要检查输入条件的组合不是一件容易的事情, 即使把所有输入条件划分成等价类,他们之间的组合情况也相当多. 因此必须考虑采用一种适合于描述对于多种条件的组合,相应产生多个动作的形式来考虑设计测试用例. 这就需要利用因果图(逻辑模型). 因果图方法最终生成的就是判定表. 它适合于检查程序输入条件的各种组合情况.

    73、请以您以往的实际工作为例,详细的描述一次测试用例设计的完整的过程。

    参考答案:

           就说最近的这次网站功能的测试吧

      首先:得到相关文档(需求文档和设计文档),理解需求和设计设计思想后,想好测试策略(测试计划简单点就OK了),考虑到测试环境,测试用例,测试时间等问题。

      第二步:设计测试用例,测试策略是:把网站部分的功能点测试完,然后在进行系统测试(另外个模块呢有另一个测试人员负责,可以进行联调测试),网站模块的测试基本是功能测试和界面测试(用户并发的可能性很小,所以不考虑):这次的网站的输入数据呢是使用数据库中的某张表记录,如果表中某一数据记录中新加进来的(还没有被处理的,有个标志位),网站启动后会立刻去刷那张表,得到多条数据,然后在进行处理。处理过程中,会经历3个步骤,网站才算完成了它的任务。有3个步骤呢,就可以分别对  这3个步骤进行测试用例的设计,尽量覆盖到各种输入情况(包括数据库中的数据,用户的输入等),得出了差不多50个用例。界面测试,也就是用户看的到的地方,包括发送的邮件和用户填写资料的页面展示。

      第三步:搭建测试环境(为什么这个时候考虑测试环境呢?因为我对网站环境已经很熟了,只有有机器能空于下来做该功能测试就可以做了),因为网站本身的环境搭建和其他的系统有点不同,它需要的测试环境比较麻烦,需要web服务器(Apache,tomcat),不过这次需求呢,网站部分只用到了tomcat,所以只要有tomcat即可

      第四步:执行测试

    74、您以往是否曾经从事过性能测试工作?如果有,请尽可能的详细描述您以往的性能测试工作的完整过程。

    参考答案:(以自己最熟悉的性能测试项目为例)

           是的,曾经做过网站方面的性能测试,虽然做的时间并不久(2个月吧),当时呢,是有位网站性能测试经验非常丰富的前辈带着我一起做。

    性能测试类型包括负载测试,强度测试,容量测试等

      负载测试:负载测试是一种性能测试指数据在超负荷环境中运行,程序是否能够承担。

      强度测试:强度测试是一种性能测试,他在系统资源特别低的情况下软件系统运行情况

      容量测试:确定系统可处理同时在线的最大用户数   

      在网站流量逐渐加大的情况下,开始考虑做性能测试了,首先要写好性能测试计划,根据运营数据得出流量最大的页面(如果是第一次的话,一般是首页,下载页,个人帐户页流量最大,而且以某种百分比),

      Web服务器指标指标:

      * Avg Rps: 平均每秒钟响应次数=总请求时间 / 秒数;

      * Successful Rounds:成功的请求;

      * Failed Rounds :失败的请求;

      * Successful Hits :成功的点击次数;

      * Failed Hits :失败的点击次数;

      * Hits Per Second :每秒点击次数;

      * Successful Hits Per Second :每秒成功的点击次数;

      * Failed Hits Per Second :每秒失败的点击次数;

      * Attempted Connections :尝试链接数; 

    75、你对测试最大的兴趣在哪里?为什么?

    参考答案:

           最大的兴趣就是测试有难度,有挑战性!做测试越久越能感觉到做好测试有多难。曾经在无忧测试网上看到一篇文章,是关于如何做好一名测试工程师。一共罗列了11,12点,有部分是和人的性格有关,有部分需要后天的努力。但除了性格有关的1,2点我没有把握,其他点我都很有信心做好它。

      刚开始进入测试行业时,对测试的认识是从无忧测试网上了解到的一些资料,当时是冲着做测试需要很多技能才能做的好,虽然入门容易,但做好很难,比开发更难,虽然当时我很想做开发(学校专业课我基本上不缺席,因为我喜欢我的专业),但看到测试比开发更难更有挑战性,想做好测试的意志就更坚定了。

      不到一年半的测试工作中,当时的感动和热情没有减退一点(即使环境问题以及自身经验,技术的不足,做测试的你一定也能理解)。

      我觉得做测试整个过程中有2点让我觉得很有难度(对我来说,有难度的东西我就非常感兴趣),第一是测试用例的设计,因为测试的精华就在测试用例的设计上了,要在版本出来之前,把用例写好,用什么测试方法写?(也就是测试计划或测试策略),如果你刚测试一个新任务时,你得花一定的时间去消化业务需求和技术基础,业务需求很好理解(多和产品经理和开发人员沟通就能达到目的),而技术基础可就没那么简单了,这需要你自觉的学习能力,比如说网站吧,最基本的技术知识你要知道网站内部是怎么运作的的,后台是怎么响应用户请求的?测试环境如何搭建?这些都需要最早的学好。至少在开始测试之前能做好基本的准备,可能会遇到什么难题?需求细节是不是没有确定好?这些问题都能在设计用例的时候发现。

      第二是发现BUG的时候了,这应该是测试人员最基本的任务了,一般按测试用例开始测试就能发现大部分的bug,还有一部分bug需要测试的过程中更了解所测版本的情况获得更多信息,补充测试用例,测试出bug。还有如何发现bug?这就需要在测试用例有效的情况下,通过细心和耐心去发现bug了,每个用例都有可能发现bug,每个地方都有可能出错,所以测试过程中思维要清晰(测试过程数据流及结果都得看仔细了,bug都在里面发现的)。如何描述bug也很有讲究,bug在什么情况下会产生,如果条件变化一点点,就不会有这个bug,以哪些最少的操作步骤就能重现这个bug,这个bug产生的规律是什么?如果你够厉害的话,可以帮开发人员初步定位问题。

    76、你以前工作时的测试流程是什么?

    参考答案:(灵活回答)

    公司对测试流程没有规定如何做,但每个测试人员都有自己的一套测试流程。我说下我1年来不断改正(自己总结,吸取同行的方法)后的流程吧。需求评审(有开发人员,产品经理,测试人员,项目经理)->需求确定(出一份确定的需求文档)->开发设计文档(开发人员在开始写代码前就能输出设计文档)->想好测试策略,写出测试用例->发给开发人员和测试经理看看(非正式的评审用例)->接到测试版本->执行测试用例(中间可能会补充用例)->提交bug(有些bug需要开发人员的确定(严重级别的,或突然发现的在测试用例范围之外的,难以重现的),有些可以直接录制进TD)->开发人员修改(可以在测试过程中快速的修改)->回归测试(可能又会发现新问题,再按流程开始跑)。

    77、当开发人员说不是BUG时,你如何应付?

    参考答案:

      开发人员说不是bug,有2种情况,一是需求没有确定,所以我可以这么做,这个时候可以找来产品经理进行确认,需不需要改动,3方商量确定好后再看要不要改。二是这种情况不可能发生,所以不需要修改,这个时候,我可以先尽可能的说出是BUG的依据是什么?如果被用户发现或出了问题,会有什么不良结果?程序员可能会给你很多理由,你可以对他的解释进行反驳。如果还是不行,那我可以给这个问题提出来,跟开发经理和测试经理进行确认,如果要修改就改,如果不要修改就不改。其实有些真的不是bug,我也只是建议的方式写进TD中,如果开发人员不修改也没有大问题。如果确定是bug的话,一定要坚持自己的立场,让问题得到最后的确认。

    78、软件的构造号与版本号之间的区别?BVT(BuildVerificationTest)

    参考答案:版本控制命名格式: 主版本号.子版本号[.修正版本号[.编译版本号 ]]

    Major.Minor [.Revision[.Build]]

          应根据下面的约定使用这些部分:

    Major :具有相同名称但不同主版本号的程序集不可互换。例如,这适用于对产品的大量重写,这些重写使得无法实现向后兼容性。

    Minor :如果两个程序集的名称和主版本号相同,而次版本号不同,这指示显著增强,但照顾到了向后兼容性。例如,这适用于产品的修正版或完全向后兼容的新版本。

    Build :内部版本号的不同表示对相同源所作的重新编译。这适合于更改处理器、平台或编译器的情况。

    Revision :名称、主版本号和次版本号都相同但修订号不同的程序集应是完全可互换的。这适用于修复以前发布的程序集中的安全漏洞。

    BVT(BuildVerificationTest):

    作为Build的一部分,主要是通过对基本功能、特别是关键功能的测试,保证新增代码没有导致功能失效,保证版本的持续稳定。实现BVT方式是有以下几种:1、测试人员手工验证关键功能实现的正确性。特点:这是传统开发方法中,通常采用的方式。无需维护测试脚本的成本,在测试人力资源充足,测试人员熟悉业务、并对系统操作熟练情况下效率很高,比较灵活快速。缺点:人力成本较高;对测试人员能力有一定要求;测试人员面对重复的工作,容易产生疲倦懈怠,从而影响测试质量。2、借助基于GUI的自动化功能测试工具来完成,将各基本功能操作录制成测试脚本,每次回放测试脚本验证功能实现的正确性。特点:能够模拟用户操作完成自动的测试,从UI入口到业务实现,每一层的代码实现都经过验证;节约人力成本;降低测试人员重复劳动的工作量,机器不会疲倦;缺点:对于UI变动比较频繁的系统来说,这种方式的维护成本很高,实施起来非常困难。另外,在项目周期较短且后续无延续性或继承的情况下,也不推荐使用此方式。3、由开发人员通过自动化测试工具完成业务层的BVT测试。特点:通过对业务层关键功能的持续集成测试,保证系统功能的持续稳定。可以结合DailyBuild,做为Build的一部分,自动实现并输入BVT报告。缺点:仅对业务规则实现的正确性进行了测试,对表现层无法测试到,对于诸如:前台页面控件各种事件响应、页面元素变化等方面的问题无法保证。

    79、您以往的工作中,一条软件缺陷(或者叫Bug)记录都包含了哪些内容?如何提交高质量的软件缺陷(Bug)记录?

    参考答案:

    80、您以往所从事的软件测试工作中,是否使用了一些工具来进行软件缺陷(Bug)的管理?如果有,请结合该工具描述软件缺陷(Bug)跟踪管理的流程。

    参考答案:

    81、您认为性能测试工作的目的是什么?做好性能测试工作的关键是什么?

    参考答案:

    82、单元测试、集成测试、系统测试的侧重点是什么?

           参考答案:

    83、集成测试通常都有那些策略?

    参考答案:

    84、一个缺陷测试报告的组成

    参考答案:

    85、基于WEB信息管理系统测试时应考虑的因素有哪些?

    参考答案:

    86、软件测试项目从什么时候开始,?为什么?

    参考答案:

    87、需求测试注意事项有哪些?

    参考答案:

    88、简述一下缺陷的生命周期

    参考答案:

    89、你在你所在的公司是怎么开展测试工作的?是如何组织的?

    参考答案:

    90、你认为理想的测试流程是什么样子?

    参考答案:

    91、您在从事性能测试工作时,是否使用过一些测试工具?如果有,请试述该工具的工作原理,并以一个具体的工作中的例子描述该工具是如何在实际工作中应用的。

    参考答案:        

    92、软件测试活动的生命周期是什么?

    参考答案:

    93、请画出软件测试活动的流程图?

    参考答案:

    94、针对缺陷采取怎样管理措施?

    参考答案:

    95、什么是测试评估?测试评估的范围是什么?

    参考答案:

    96、如果能够执行完美的黑盒测试,还需要进行白盒测试吗?为什么?

    参考答案:

    97、测试结束的标准是什么?

    参考答案:

    98、软件验收测试除了alpha ,beta测试以外,还有哪一种?

    参考答案:

    99、做测试多久了?以前做过哪些项目?你们以前测试的流程是怎样的?用过哪些测试工具?

    参考答案:

    100、请就如何在开发中进行软件质量控制说说你的看法

    参考答案:

    101、一套完整的测试应该由哪些阶段组成?分别阐述一下各个阶段。

    102、软件测试的类型有那些?分别比较这些不同的测试类型的区别与联系。

    103、测试用例通常包括那些内容?着重阐述编制测试用例的具体做法

    104、在分别测试winform的C/S结构与测试WEB结构的软件是,应该采取什么样的方法分别测试?他们存在什么样的区别与联系?

    105、在测试winform的C/S结构软件时,发现这个软件的运行速度很慢,您会认为是什么原因?您会采取哪些方法去检查这个原因?

    106、描述使用bugzilla缺陷管理工具对软件缺陷(BUG)跟踪的管理的流程

    107、你都用什么测试方法

    针对不同的产品或者系统或者模块,有不同的测试方法。总体而言有白盒测试和黑盒测试。

    108、怎么编写案例

    案例的编写与测试阶段的定义有很大的关系。系统测试和unit测试的案例可能不同。总体而言测试案例根据系统的需求而定。

    109、怎么才能够全面的测试到每一个点

    测试的全面性主要需要在设计测试计划的时候考虑,从测试策略,产品需求等等多个角度考虑从而定义全部的测试点。

    110、谈谈软件测试技术,以及如何提高

    111、谈谈软件测试职业发展,以及个人的打算

    112、谈谈软件测试在企业的地位,也可以结合软件生命周期来谈

    113、一般公司里实际的软件测试流程是什么样的?你们公司又是怎样的?

    114、软件工程师要具有那些素质?

    115、你会哪些测试工具?怎么操作?

    116、你能不能说下你的3到5年的职业计划(规划)

    117、你觉得你来应聘有那些优势?

    其他问题:(有可能清晰的思路比确切的答案更重要)

    对测试的理解——考查点:基本的测试知识,对测试是否认可

    谈一谈过去自己的工作——考查点:了解经历、提供进一步提问的素材,表达能力、测试技能

    测试设计的方法并举例说明——考查点:测试技术的使用

    测试工具——考查点:熟悉程度,能否与当前工作匹配?

    如何做计划?如何跟踪计划?——考查点:日常工作能力

    如果开发人员提供的版本不满足测试的条件,如何做?——考查点:与开发人员协作的能力

    熟悉unix系统、oracle数据库吗?——考查点:是否具备系统知识

    做过开发吗?写过哪些代码?——考查点:开发技能

    阅读英语文章,给出理解说明?——考查点:部分英语能力

    文档的意义——考查点:是否善于思考?(最简单的概念,不同层次的理解)

    假如进入我们公司,对我们哪些方面会有帮助?——考查点:讲讲自己的特长

    随便找一件物品,让其测试——考查点:测试的实际操作能力

    有一个新的软件,假如你是测试工程师,该如何做——考查点:实际项目经验、是否有带领测试团队的经验和潜力

    开发及环境搭建类面试题

    1、描述软件产生内存泄露的原因以及检查方式。(可以结合一种开发语言进行描述)

    参考答案:

    内存泄露的原因,主要是由于开发过程当中申请了计算机资源(例如对象、内存等),但是使用资源完成以后没有及时释放资源导致的。例如在C语言当中使用了malloc申请了内存,但是未使用free来释放内存。

    2、简述什么是值传递,什么是地址传递,两者区别是什么?

    参考答案:

    值传递主调函数传递给被调函数的是值的拷贝,不是原值;地址传递主调函数传递给被调函数的是值的地址。区别是值传递被调函数中的操作不改变主调函数的值,而地址传递则不同。

    3、结构化程序设计和面向对象程序设计各自的特点及优缺点是什么?

    参考答案:(不需要回答如此复杂)

    结构化程序设计思想采用了模块分解与功能抽象和自顶向下、分而治之的方法,从而有效地将一个较复杂的程序系统设计任务分解成许多易于控制和处理的子程序,便于开发和维护。它的重点在于把功能进行分解。但是由于在实际开发过程当中需求会经常发生变化,因此,它不能很好的适应需求变化的开发过程。结构化程序设计是面向过程的。

    面向对象程序设计以需求当中的数据作为中心,来进行设计,具有良好的代码重用性。

    封装性:也叫数据隐藏,用户无需知道内部工作流程,只要知道接口和操作就可以的,C++中一般用类来实现封装。

    继承性:一种支持重用的思想,在现有的类型派生出新的子类,例如新型电视机在原有型号的电视机上增加若干中功能而得到,新型电视机是原有电视机的派生,继承了原有电视机的属性,并增加了新的功能。

    多态性:指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。

    动态联编:指一个计算机程序自身彼此关联的过程,按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。

    4、简述什么是存储过程和触发器?

    参考答案:

    存储过程:是数据库中的一个对象,Transact-SQL 语句的预编译集合,这些语句在一个名称下存储并作为一个单元进行处理。(可以理解为C语言中的函数,有参数、返回值等函数特性)

    触发器是一种特殊类型的存储过程,当使用下面的一种或多种数据修改操作在指定表中对数据进行修改时,触发器会生效:UPDATE、INSERT 或 DELETE。

    5、使用C语言编写一个函数,用于交换两个变量的值(地址传递)。

           参考答案:

           void Swap(int *a,int *b)

    {

                  int temp;

                  int temp=*a;

                  int *a=*b;

                  int *b=temp;

    }

    6、请简述DNS、活动目录、域的概念。

    参考答案:

    DNS:域名服务,作用是将网络域名解析成IP地址;

    活动目录:微软提供的目录服务的一种,它存储有关网络上的对象信息,并使管理员和用户更方便的查找和使用这类信息;

    域:网络系统的一个安全边界,在一个域当中,计算机和用户共享一些列的安全信息。

    7、描述TCP/IP协议的层次结构,以及每一层中重要协议。

    参考答案:(可以回答五层结构)

    TCP/IP

    协议

    应用层/Application

    HTTP、SMTP、FTP

    传输层/Transport

    TCP、UDP

    网络层/Network

    IP

    链路层/Link

    ARP、RARP

          

     

     

     

     

     

    8、简述子网掩码的用途。

    参考答案:

        子网掩码主要用来判断两个IP地址是否处在同一个局域网当中;子网掩码是由连续的2进制1组成的。子网掩码和IP地址进行按位与运算后,结果一致,表示处于一个局域网当中,如果不一致,表示不再一个局域网当中,需要寻找路由。

    9、说出4种以上常用的操作系统及其主要的应用范围(微软的操作系统除外)。

    参考答案:

    Linux(Red Hat、SUSE、Debian、Trubo Linux):主要用于搭建各类服务器

    MAC OS:苹果机的操作系统,用于图像处理

    Unix(AIX:IBM服务器的专用操作系统;

    Solaris:Sun操作系统;FreeBSD、NetBSD)

    10、在Linux系统中,一个文件的访问权限是755,其含义是什么?

    参考答案:

           755表示该文件所有者对该文件具有读、写、执行权限,该文件所有者所在组用户及其他用户对该文件具有读和执行权限。

    11、Windows操作系统中PATH环境变量的作用是什么?

    参考答案:

           PATH是Windows操作系统环境变量,PATH作用是用户在命令行窗口执行一个命令,则在PATH变量设置的目录下依次寻找该命令或对应的执行文件,若找到,则执行,若没有找到,则命令行窗口返回无效命令。

    12、Ghost的主要用途和常用方法?

    参考答案:

    Ghost是一个非常著名的硬盘克隆工具。该工具的主要作用是可以将一个硬盘或硬盘中的某个分区原封不动的复制到另一个硬盘或其他的分区中。如果你需要备份启动分区或者是需要在多台机器上安装相应的系统和应用程序,都可以通过Ghost来实现,相信通过这个工具备份,恢复速度和硬盘安装速度会成倍的提高。

    Norton Ghost有一个很大的特点,就是在克隆硬盘时不会改变任何文件信息,程序可以很好的支持FAT16、FAT32以及NTFS格式的文件分配结构(其中包括Windows 2000的文件分配格式),虽然是DOS环境下运行的程序,但工具可支持Win 9x的长文件名特性。

    常用方法包括:硬盘克隆、分区克隆、硬盘或分区克隆成镜像文件等。

    13、在RedHat中,从root用户切到userl用户,一般用什么命令?

    参考答案:su

    su user1  切换到user1,但切换后的当前目录还是root访问的目录

    su – user1 切换到user1,并且当前目录切换到user1的根目录下(/home/user1/)

    14、Linux中,一般怎么隐藏文件?

    参考答案:文件名以一个.开头

    15、如何将自己的本地磁盘(D)做成FTP供远端主机使用?

    参考答案:Windows下安装FTP服务,并将FTP的根目录指向D盘即可。

    16、对RUP.CMM,CMMI,XP,PSP.TSP的认识?

    参考答案:软件过程标准:CMMI、PSP、TSP、RUP、软件工程规范国家标准;(AP、XP、ASD等开发过程思想好像还不能称其为标准)

    RUP(Rational Unified Process)是Rational公司提出的一套开发过程模型,它是一个面向对象软件工程的通用业务流程。它描述了一系列相关的软件工程流程,它们具有相同的结构,即相同的流程构架。RUP 为在开发组织中分配任务和职责提供了一种规范方法,其目标是确保在可预计的时间安排和预算内开发出满足最终用户需求的高品质的软件。RUP具有两个轴,一个轴是时间轴,这是动态的。另一个轴是工作流轴,这是静态的。在时间轴上,RUP划分了四个阶段:初始阶段、细化阶段、构造阶段和发布阶段。每个阶段都使用了迭代的概念。在工作流轴上,RUP设计了六个核心工作流程和三个核心支撑工作流程,核心工作流轴包括:业务建模工作流、需求工作流、分析设计工作流、实现工作流、测试工作流和发布工作流。核心支撑工作流包括:环境工作流、项目管理工作流和配置与变更管理工作流。RUP 汇集现代软件开发中多方面的最佳经验,并为适应各种项目及组织的需要提供了灵活的形式。作为一个商业模型,它具有非常详细的过程指导和模板。但是同样由于该模型比较复杂,因此在模型的掌握上需要花费比较大的成本。尤其对项目管理者提出了比较高的要求。

    CMM(Capability Maturity Model能力成熟度模型) 由美国卡内基-梅隆大学的软件工程研究所(简称SEI)受美国国防部委托,于1991年研究制定,初始的主要目的是为了评价美国国防部的软件合同承包组织的能力,后因为在软件企业应用CMM模型实施过程改进取得较大的成功,所以在全世界范围内被广泛使用,SEI同时建立了主任评估师评估制度,CMM的评估方法为CBA-IPI。CMM的本质是软件管理工程的一个部分。它是对于软件组织在定义,实现,度量,控制和改善其软件过程的进程中各个发展阶段的描述。他通过5个不断进化的层次来评定软件生产的历史与现状:初始层是混沌的过程;可重复层是经过训练的软件过程;定义层是标准一致的软件过程;管理层是可预测的软件过程;优化层是能持续改善的软件过程。

    CMM/PSP/TSP即软件能力成熟度模型/ 个体软件过程/群组软件过程,是1987年美国 Carnegie Mellon 大学软件工程研究所(CMU/SEI)以W.S.Humphrey为首的研究组发表的研究成果"承制方软件工程能力的评估方法"。

    CMMI是SEI于2000年发布的CMM的新版本。CMMI不但包括了软件开发过程改进,还包含系统集成、软硬件采购等方面的过程改进内容。

    CMMI纠正了CMM存在的一些缺点,使其更加适用企业的过程改进实施。CMMI适用SCAMPI评估方法。需要注意的是,SEI没有废除CMM模型,只是停止了CMM评估方法:CBA-IPI。现在如要进行CMM评估,需使用SCAMPI方法。但CMMI模型最终代替CMM模型的趋势不可避免。

    XP (极限编程)规定了一组核心价值和方法,可以让软件开发人员发挥他们的专长:编写代码。XP 消除了大多数重量型过程的不必要产物,通过减慢开发速度、耗费开发人员的精力(例如干特图、状态报告,以及多卷需求文档)从目标偏离。

    XP 的核心价值:交流、简单、反馈、勇气。

    17、DNS是什么,它是如何工作的?

    参考答案:域名解析服务。用于将域名解析为IP,或反和将IP解析为域名。

    客户机可指定DNS服务器来解析,或用本机hosts文件进行解析。

    Windows下配置DNS服务器在《搭建Windows测试环境》中有。

    18、防火墙如何保证安全的?主要有哪些?

    参考答案:防火墙分类1

    从防火墙的软、硬件形式来分的话,防火墙可以分为软件防火墙和硬件防火墙以及芯片级防火墙。

    第一种:软件防火墙

    软件防火墙运行于特定的计算机上,它需要客户预先安装好的计算机操作系统的支持,一般来说这台计算机就是整个网络的网关。俗称“个人防火墙”。软件防火墙就像其它的软件产品一样需要先在计算机上安装并做好配置才可以使用。防火墙厂商中做网络版软件防火墙最出名的莫过于Checkpoint。使用这类防火墙,需要网管对所工作的操作系统平台比较熟悉。

    第二种:硬件防火墙

    这里说的硬件防火墙是指“所谓的硬件防火墙”。之所以加上"所谓"二字是针对芯片级防火墙说的了。它们最大的差别在于是否基于专用的硬件平台。目前市场上大多数防火墙都是这种所谓的硬件防火墙,他们都基于PC架构,就是说,它们和普通的家庭用的PC没有太大区别。在这些PC架构计算机上运行一些经过裁剪和简化的操作系统,最常用的有老版本的Unix、Linux和FreeBSD系统。值得注意的是,由于此类防火墙采用的依然是别人的内核,因此依然会受到OS(操作系统)本身的安全性影响。

    传统硬件防火墙一般至少应具备三个端口,分别接内网,外网和DMZ区(非军事化区),现在一些新的硬件防火墙往往扩展了端口,常见四端口防火墙一般将第四个端口做为配置口、管理端口。很多防火墙还可以进一步扩展端口数目。

    第三种:芯片级防火墙

    芯片级防火墙基于专门的硬件平台,没有操作系统。专有的ASIC芯片促使它们比其他种类的防火墙速度更快,处理能力更强,性能更高。做这类防火墙最出名的厂商有NetScreen、FortiNet、Cisco等。这类防火墙由于是专用OS(操作系统),因此防火墙本身的漏洞比较少,不过价格相对比较高昂。

    防火墙技术虽然出现了许多,但总体来讲可分为“包过滤型”和“应用代理型”两大类。前者以以色列的Checkpoint防火墙和美国Cisco公司的PIX防火墙为代表,后者以美国NAI公司的Gauntlet防火墙为代表。

    (1). 包过滤(Packet filtering)型

    包过滤型防火墙工作在OSI网络参考模型的网络层和传输层,它根据数据包头源地址,目的地址、端口号和协议类型等标志确定是否允许通过。只有满足过滤条件的数据包才被转发到相应的目的地,其余数据包则被从数据流中丢弃。

    包过滤方式是一种通用、廉价和有效的安全手段。之所以通用,是因为它不是针对各个具体的网络服务采取特殊的处理方式,适用于所有网络服务;之所以廉价,是因为大多数路由器都提供数据包过滤功能,所以这类防火墙多数是由路由器集成的;之所以有效,是因为它能很大程度上满足了绝大多数企业安全要求。

    在整个防火墙技术的发展过程中,包过滤技术出现了两种不同版本,称为“第一代静态包过滤”和“第二代动态包过滤”。

    ●第一代静态包过滤类型防火墙

    这类防火墙几乎是与路由器同时产生的,它是根据定义好的过滤规则审查每个数据包,以便确定其是否与某一条包过滤规则匹配。过滤规则基于数据包的报头信息进行制订。报头信息中包括IP源地址、IP目标地址、传输协议(TCP、UDP、ICMP等等)、TCP/UDP目标端口、ICMP消息类型等。

    ●第二代动态包过滤类型防火墙

    这类防火墙采用动态设置包过滤规则的方法,避免了静态包过滤所具有的问题。这种技术后来发展成为包状态监测(Stateful Inspection)技术。采用这种技术的防火墙对通过其建立的每一个连接都进行跟踪,并且根据需要可动态地在过滤规则中增加或更新条目。

    包过滤方式的优点是不用改动客户机和主机上的应用程序,因为它工作在网络层和传输层,与应用层无关。但其弱点也是明显的:过滤判别的依据只是网络层和传输层的有限信息,因而各种安全要求不可能充分满足;在许多过滤器中,过滤规则的数目是有限制的,且随着规则数目的增加,性能会受到很大地影响;由于缺少上下文关联信息,不能有效地过滤如UDP、RPC(远程过程调用)一类的协议;另外,大多数过滤器中缺少审计和报警机制,它只能依据包头信息,而不能对用户身份进行验证,很容易受到“地址欺骗型”攻击。对安全管理人员素质要求高,建立安全规则时,必须对协议本身及其在不同应用程序中的作用有较深入的理解。因此,过滤器通常是和应用网关配合使用,共同组成防火墙系统。

     (2). 应用代理(Application Proxy)型

    应用代理型防火墙是工作在OSI的最高层,即应用层。其特点是完全"阻隔"了网络通信流,通过对每种应用服务编制专门的代理程序,实现监视和控制应用层通信流的作用。其典型网络结构如图所示。

    在代理型防火墙技术的发展过程中,它也经历了两个不同的版本,即:第一代应用网关型代理防火和第二代自适应代理防火墙。

    第一代应用网关(Application Gateway)型防火墙

    这类防火墙是通过一种代理(Proxy)技术参与到一个TCP连接的全过程。从内部发出的数据包经过这样的防火墙处理后,就好像是源于防火墙外部网卡一样,从而可以达到隐藏内部网结构的作用。这种类型的防火墙被网络安全专家和媒体公认为是最安全的防火墙。它的核心技术就是代理服务器技术。

    第二代自适应代理(Adaptive proxy)型防火墙

    它是近几年才得到广泛应用的一种新防火墙类型。它可以结合代理类型防火墙的安全性和包过滤防火墙的高速度等优点,在毫不损失安全性的基础之上将代理型防火墙的性能提高10倍以上。组成这种类型防火墙的基本要素有两个:自适应代理服务器(Adaptive Proxy Server)与动态包过滤器(Dynamic Packet filter)。

    在“自适应代理服务器”与“动态包过滤器”之间存在一个控制通道。在对防火墙进行配置时,用户仅仅将所需要的服务类型、安全级别等信息通过相应Proxy的管理界面进行设置就可以了。然后,自适应代理就可以根据用户的配置信息,决定是使用代理服务从应用层代理请求还是从网络层转发包。如果是后者,它将动态地通知包过滤器增减过滤规则,满足用户对速度和安全性的双重要求。

    代理类型防火墙的最突出的优点就是安全。由于它工作于最高层,所以它可以对网络中任何一层数据通信进行筛选保护,而不是像包过滤那样,只是对网络层的数据进行过滤。

    另外代理型防火墙采取是一种代理机制,它可以为每一种应用服务建立一个专门的代理,所以内外部网络之间的通信不是直接的,而都需先经过代理服务器审核,通过后再由代理服务器代为连接,根本没有给内、外部网络计算机任何直接会话的机会,从而避免了入侵者使用数据驱动类型的攻击方式入侵内部网。

    代理防火墙的最大缺点就是速度相对比较慢,当用户对内外部网络网关的吞吐量要求比较高时,代理防火墙就会成为内外部网络之间的瓶颈。那因为防火墙需要为不同的网络服务建立专门的代理服务,在自己的代理程序为内、外部网络用户建立连接时需要时间,所以给系统性能带来了一些负面影响,但通常不会很明显。

    防火墙分类3

    从防火墙结构上分,防火墙主要有:单一主机防火墙、路由器集成式防火墙和分布式防火墙三种。

    单一主机防火墙是最为传统的防火墙,独立于其它网络设备,它位于网络边界。

    这种防火墙其实与一台计算机结构差不多(如下图),同样包括CPU、内存、硬盘等基本组件,当然主板更是不能少了,且主板上也有南、北桥芯片。它与一般计算机最主要的区别就是一般防火墙都集成了两个以上的以太网卡,因为它需要连接一个以上的内、外部网络。其中的硬盘就是用来存储防火墙所用的基本程序,如包过滤程序和代理服务器程序等,有的防火墙还把日志记录也记录在此硬盘上。虽然如此,但我们不能说它就与我们平常的PC机一样,因为它的工作性质,决定了它要具备非常高的稳定性、实用性,具备非常高的系统吞吐性能。正因如此,看似与PC机差不多的配置,价格甚远。

    随着防火墙技术的发展及应用需求的提高,原来作为单一主机的防火墙现在已发生了许多变化。最明显的变化就是现在许多中、高档的路由器中已集成了防火墙功能,还有的防火墙已不再是一个独立的硬件实体,而是由多个软、硬件组成的系统,这种防火墙,俗称“分布式防火墙”。

    原来单一主机的防火墙由于价格非常昂贵,仅有少数大型企业才能承受得起,为了降低企业网络投资,现在许多中、高档路由器中集成了防火墙功能。如Cisco IOS防火墙系列。但这种防火墙通常是较低级的包过滤型。这样企业就不用再同时购买路由器和防火墙,大大降低了网络设备购买成本。

    分布式防火墙再也不是只是位于网络边界,而是渗透于网络的每一台主机,对整个内部网络的主机实施保护。在网络服务器中,通常会安装一个用于防火墙系统管理软件,在服务器及各主机上安装有集成网卡功能的PCI防火墙卡,这样一块防火墙卡同时兼有网卡和防火墙的双重功能。这样一个防火墙系统就可以彻底保护内部网络。各主机把任何其它主机发送的通信连接都视为“不可信”的,都需要严格过滤。而不是传统边界防火墙那样,仅对外部网络发出的通信请求“不信任”。

    防火墙分类4

    如果按防火墙的应用部署位置分,可以分为边界防火墙、个人防火墙和混合防火墙三大类。

    边界防火墙是最为传统的那种,它们于内、外部网络的边界,所起的作用的对内、外部网络实施隔离,保护边界内部网络。这类防火墙一般都是硬件类型的,价格较贵,性能较好。

    个人防火墙安装于单台主机中,防护的也只是单台主机。这类防火墙应用于广大的个人用户,通常为软件防火墙,价格最便宜,性能也最差。

    混合式防火墙可以说就是“分布式防火墙”或者“嵌入式防火墙”,它是一整套防火墙系统,由若干个软、硬件组件组成,分布于内、外部网络边界和内部各主机之间,既对内、外部网络之间通信进行过滤,又对网络内部各主机间的通信进行过滤。它属于最新的防火墙技术之一,性能最好,价格也最贵。

    防火墙分类5

    如果按防火墙的性能来分可以分为百兆级防火墙和千兆级防火墙两类。

    因为防火墙通常位于网络边界,所以不可能只是十兆级的。这主要是指防火的通道带宽(Bandwidth),或者说是吞吐率。当然通道带宽越宽,性能越高,这样的防火墙因包过滤或应用代理所产生的延时也越小,对整个网络通信性能的影响也就越小。

    19、目前流行的操作的系统有哪些?请举例说明安装操作系统的注意事项?

    参考答案:MS Windows系列:win 98、windows 2000系列、win XP、win 2003 Server、win Vista等等。

    UNIX类:SVRx、FreeBSD、OpenBSD、NetBSD、Solaris、各种Linux等等。Mac OS……

    多重引导时,一般先安装win操作系统,从低版本到高,再安装Linux

    20、简述一下c/s模式或者b/s模式?

    参考答案:C/S模式:客户端/服务器模式。工作原理:Client向Server提交一个请求;Server则使用一些方法处理这个请求,并将效果返回给Client。

    B/S结构,即Browser/Server(浏览器/服务器)结构,是随着Internet技术的兴起,对C/S结构的一种变化或者改进的结构。在这种结构下,用户界面完全通过WWW浏览器实现,一部分事务逻辑在前端实现,但是主要事务逻辑在服务器端实现,形成所谓3-tier结构。B/S结构,主要是利用了不断成熟的WWW浏览器技术,结合浏览器的多种Script语言(VBScript、JavaScript…)和ActiveX技术,用通用浏览器就实现了原来需要复杂专用软件才能实现的强大功能,并节约了开发成本,是一种全新的软件系统构造技术。

    21、TCP/UDP有哪些区别?

    参考答案:TCP-有连接,所以握手过程会消耗资源,过程为可靠连接,不会丢失数据,适合大数据量交换
    UDP-非可靠连接,会丢包,没有校验,速度快,无须握手过程

     
    TCP
    UDP
    是否连接
    面向连接
    面向非连接
    传输可靠性
    可靠的
    不可靠的
    应用场合
    传输大量数据
    少量数据
    速度

    22、ISO模型?HUB、tch、Router是ISO的第几层设备?

    参考答案:从底向上:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层

    HUB:1层(物理层);Switch:2层(数据链路层);Router:3层(网络层)

    23、内存有哪几种存储组织结构.请分别加以说明?

    参考答案:

    人力资源面试题

    1、你的测试职业发展是什么?你自认为做测试的优势在哪里?

    参考答案:

           测试经验越多,测试能力越高。所以我的职业发展是需要时间累积的,一步步向着高级测试工程师奔去。而且我也有初步的职业规划,前3年累积测试经验,按如何做好测试工程师的要求自己,不断的更新自己改正自己,做好测试任务。

      优势在于我对测试坚定不移的信心和热情,虽然经验还不够,但测试需要的基本技能我有信心在工作中得以发挥。

    2、你为什么想离开目前的职务?

    参考答案:

    3、你对我们公司了解有多少?

    参考答案:

    4、你找工作时,最重要的考虑因素为何?

    参考答案:工作的性质和内容是否能让我发挥所长,并不断成长。

    5、为什么我们应该录取你?

    参考答案:您可以由我过去的工作表现所呈现的客观数据,明显地看出我全力以赴的工作态度。

    6、请谈谈你个人的最大特色。

    参考答案:我的坚持度很高,事情没有做到一个令人满意的结果,绝不罢手。

    7、一个测试工程师应具备那些素质和技能?

    参考答案:

    8、您认为在测试人员同开发人员的沟通过程中,如何提高沟通的效率和改善沟通的效果?维持测试人员同开发团队中其他成员良好的人际关系的关键是什么?

    参考答案:

    9、在您以往的测试工作中,最让您感到不满意或者不堪回首的事情是什么?您是如何来对待这些事情的?

    参考答案:

    10、在即将完成这次笔试前,您是否愿意谈一些自己在以往的学习和工作中获得的工作经验和心得体会?(可以包括软件测试、过程改进、软件开发或者与此无关的其他方面)

    参考答案:

    11、为什么选择测试这行?

    参考答案:

      它是一个新兴的行业,有发展潜力,而且很锻炼人,需要掌握更多的技能,比做开发要更难

      为什么值得他们公司雇用?如果我雇用你,你能给部门带来什么贡献?

        如果明知这样做不对,你还会依主管的指过去做吗

      如果你接到一个客户抱怨的电话,你确知无法解决他的问题,你会怎么处理

      你觉得什么样的人最难相处

      为什么值得他们公司雇用?

        帮助公司提高软件质量和测试部门的技术水平

      如果我雇用你,你能给部门带来什么贡献?

        分享我的测试经验和测试技能,提高测试部门技术水平

      如何从工作中看出你是个自动自觉的人

            自动自觉范围太广

         1. 工作成果

         2. 工作质量  

    12、你的工作通常能在时限内完成吗.(我想问一下就是她问这个问题的动机是什么)

    参考答案:

        在有足够的资源和合理的工作量的情况下,完全可以按时完成,并能比一般人做的更好

    13、通常你对于别人批评你会有什么样的反应

    参考答案:有错即改,无错勉之

    14、如果明知这样做不对,你还会依主管的指过去做吗?

           参考答案:

    15、如果你接到一个客户抱怨的电话,你确知无法解决他的问题,你会怎么处理?

    参考答案:

        弄清楚客户为什么抱怨?是怎么样的问题?

      如果是客服问题,提交客服部门解决

      如果是质量问题,分析原因,下一版本改进

    16、请就软件测试人员应该具备什么样的基本素质说说你的看法。

    参考答案:

    17、你在五年内的个人目标和职业目标分别是什么?

    参考答案:

    分析这个问题是用来了解你的计划能力的,通过这个问题,面试人同时还可以知道你的目标是否符合企业对你的安排。

      错误回答我想在将来的某个时候考虑这个问题。如今企业的领导者更换频繁,我认为做太多的个人计划是荒谬可笑的,不是吗?

      评论这种回答属于令人反感的一类。首先,当有人想了解你的目标时,"将来的某个时候"这种通俗说法并不奏效。其次,认为企业很脆弱,领导者更换频繁,这种说法毫无疑问会令人反感,而且也是不合理的。最后,认为做计划可笑,看不起这个问题,而且反问面试人,这些都注定了这样的求职者最终会失败。

      正确回答从现在起的五年之内,我希望能够在一个很好的职位上待几年,而且最好有一次晋升,然后就期待着下一步。不管是向上提升,还是在企业内横向调动,对我个人来说,我希望找到一家企业——一家愿意做相互投入的企业——待上一段时间。

      评论这个问题没有回答得过分具体(那样可能会产生漏洞),而且它表明你有雄心,并且思考过在企业中的成长方式。通过表达横向调动和向上提升的愿望,表明你是一个有灵活性的人。

    18、你怎样做出自己的职业选择?

    参考答案:

           分析 面试人提出这个问题是为了了解求职者的动机,看看他(她)应聘这份工作是否有什么历史渊源,是否有职业规划,是不是仅仅在漫无目的地申请很多工作。

      错误回答 我一直都想在企业界工作。自孩提时代起,我就梦想自己至少也要成为大企业的副总裁。

      评论 除了难以令人相信之外,这种回答还存在一个问题:它表明求职者会对副总裁以下的职位不感兴趣。

      正确回答 在上大学四年级前的那个夏天,我决定集中精力在某一领域谋求发展。尽管我是学商业的,但是我不知道自己最终会从事哪一行业的工作。我花了一定的时间考虑自己的目标,想清楚了自己擅长做的事情以及想从工作中得到的东西,最后我得出了一个坚定的结论,那就是这个行业是最适合我的。

      评论 这种回答表明,求职者认真地做过一些计划,缩小了自己的关注点,而且也认准了前进的方向。这种回答还表明,求职者理解个人职业规划的重要性,并且有能力做出认真的个人决策。

     

     

    展开全文
  • DDR,DDR2,DDR3,DDR4,LPDDR区别

    万次阅读 多人点赞 2019-12-22 22:47:42
    由于在一般情况下对应用环境稳定程度要求并不太高,只要存在差分 DQS时就基本可以保证同步的准确性, 而且 OCD 的调整对其他操作也有一定影响, 因此 OCD 功能在普通台式机上并没有什么作用,其优点主要体现在对数据...

    DDR,DDR2,DDR3,DDR4,LPDDR区别
    作者:AirCity 2019.12.17
    Aircity007@sina.com 本文所有权归作者Aircity所有

    1 什么是DDR

    DDR是Double Data Rate的缩写,即“双比特翻转”。DDR是一种技术,中国大陆工程师习惯用DDR称呼用了DDR技术的SDRAM,而在中国台湾以及欧美,工程师习惯用DRAM来称呼。

    DDR的核心要义是在一个时钟周期内,上升沿和下降沿都做一次数据采样,这样400MHz的主频可以实现800Mbps的数据传输速率。

    RAM,ROM,SRAM,DRAM,SDRAM具体是什么以及区别是什么,详见我的另一篇文章《RAM,ROM,SRAM,DRAM,SDRAM的区别》

    2 每一代DDR的基本区别

    在这里插入图片描述

    3 关键技术解释

    3.1 VTT

    VTT为DDR的地址线,控制线等信号提供上拉电源,上拉电阻是50Ω左右。VTT=1/2VDDQ,并且VTT要跟随VDDQ,因此需要专用的电源同时提供VDDQ和VTT。例如芯片TPS51206DSQT,LP2996。用专门的电源芯片,还有一个重要的原因,在Fly-by的拓扑中,VTT提供电流,增强DDR信号线的驱动能力。

    DDR的接收器是一个比较器,其中一端是VREF,另一端是信号,例如地址线A2在有VTT上拉的时候,A2的信号在0和1.8V间跳动,当A2电压高于VTT时,电流流向VTT。当A2低于VTT时,VTT流向DDR。因此VTT需要有提供电流和吸收电流的能力,一般的开关电源不能作为VTT的提供者。此外,VTT电源相当于DDR接收器信号输入端的直流偏执,且这个偏执等于VREF,因此VTT的噪声要越小越好,否则当A2的状态为高阻态时,DDR接收器的比较器容易产生误触发。

    上文说过,VTT相当于DDR接收器的直流偏执,其实如果没有VTT,这个直流偏执也存在,它在芯片的内部,提供电流的能力很弱。如果只有1个或2个DDR芯片,走Fly-by拓扑,那么不需要外部的VTT上拉。如果有2个以上的DDR芯片,则一定需要VTT上拉。

    3.2 Prefetch

    Prefetch字面意思就是预存取,每一代的DDR预存取大小不同,详见第2章中表格。以DDR3为例,它的Prefetch=8n,相当于DDR的每一个IO都有一个宽度为8的buffer,从IO进来8个数据后,在第8个数据进来后,才把这8个数据一次性的写入DDR内部的存储单元。下图是一个形象的解释,同时我们关注一下几个速率。DDR3的时钟是800MHz,Data Rate是1600Mbps,由于这个Buffer的存在,DDR内部的时钟只需要200MHz就可以了(注意DDR内部不是双比特翻转采样)。
    在这里插入图片描述
    我们来做一个频率对照表,如下
    在这里插入图片描述
    DDR内部的最小存储单元(1bit)是一个晶体管+一个电容,电容会放电,需要不断的“刷新”(充电)才能保持正常的工作状态,由于电容充放电需要时间,DDR内部的频率受限于此,很难提高,目前技术一般在100~200MHz。因此需要用Prefetch技术来提内部数据高吞吐率(其实就是串并转换原理)。Prefetch位宽的提高,是DDR2,3,4非常显著的变化。

    第一段提到,对于DDR3,在第8个数据进来后,FIFO满了,然后才把这8个数据一次性的写入DDR内部的存储单元,那么必须要求DDR的内部时钟和外部时钟有一定的约束关系,FIFO满的时候一定是以DQS下降沿采样结束的,数据手册中对DQS的下降沿与clk有一个建立时间和保持时间的约束要求的目的原来是这样。

    3.3 SSTL

    SSTL(Stub Series Terminated Logic)接口标准也是JEDEC所认可的标准之一。该标准专门针对高速内存(特别是SDRAM)接口。SSTL规定了开关特点和特殊的端接方案。
    SSTL标准规定了IC供电,IO的DC和AC输入输出门限,差分信号门限,Vref电压等。SSTL_3是3.3V标准,SSTL_2是2.5V标准,SSTL_18是1.8V标准,SSTL_15是1.5V。
    SSTL最大的特点是需要终端匹配电阻,也叫终端终结电阻,上拉到VTT(1/2VDDQ)。这个短接电阻最大的作用是为了信号完整性,特别是在1拖多的Fly-by走线拓扑下,还能增强驱动能力。
    在这里插入图片描述

    3.4 Bank

    以下图为例,一个Bank中包含若干个Array,Array相当于一个表单,选中“行地址”和“列地址”后,表单中的一个单元格就被选中,这个单元格就是一个bit。Bank中的所有Array的行地址是连在一起的,列地址也是。那么选中“行地址”和“列地址”后,将一起选中所有Array的bit。有多少个array,就有多少个bit被选中。以DDR3为例,Data线宽度是32,prefetch是8,那么Array就有32x8=256.内部一次操作会选中256bit的数据。
    在这里插入图片描述
    Bank数量越多,需要的Bank选择线越多,DDR3有8个bank,需要3个BA信号BA0~2。BA,行地址,列地址共同组成了存储单元的访问地址,缺一不可。

    3.5 DDR的容量计算

    下图是DDR3 1Gb的寻址配置,以其中128Mbx8为例说明,其中x8表示IO数据(DQ)位宽度。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    我的理解是,这个page size更像是逻辑上的一个页,并不是一个bank中,一行的所有bit,因为一行的所有bit要考虑prefetch宽度。
    上表是JESD-3D中的表格,Row Address和Column Address都是真实需要寻址的地址,其他用途的地址比如A10,A12或者A11等并没有计算在内。在计算时,不要因为有A13,就认为Column Address就是A0~A13。

    3.6 Burst

    Burst字面意思是突发,DDR的访问都是以突发的方式连续访问同一行的相邻几个单元。进行Brust时,需要有几个参数:
    Burst Length:一次突发访问几个列地址。
    Read/Write: 是读还是写
    Starting Column:从哪一列开始Burst
    Burst:突发的顺序。
    下图是DDR3中突发类型和顺序,Burst是通过A12/BC#选择的。但对于DDR,DDR2和DDR4,不一定就是通过A12/BC#,详见PIN定义章节。
    在这里插入图片描述

    3.7 DDR的tRDC,CL,tAC

    在实际工作中,Bank地址与相应的行地址是同时发出的,此时这个命令称之为“行激活”(Row Active)。在此之后,将发送列地址寻址命令与具体的操作命令(是读还是写),这两个命令也是同时发出的,所以一般都会以“读/写命令”来表示列寻址。根据相关的标准,从行有效到读/写命令发出之间的间隔被定义为tRCD,即RAS to CAS Delay(RAS至CAS延迟,RAS就是行地址选通脉冲,CAS就是列地址选通脉冲),我们可以理解为行选通周期。tRCD是DDR的一个重要时序参数,广义的tRCD以时钟周期(tCK,Clock Time)数为单位,比如tRCD=3,就代表延迟周期为两个时钟周期,具体到确切的时间,则要根据时钟频率而定,DDR3-800,tRCD=3,代表30ns的延迟。
    接下来,相关的列地址被选中之后,将会触发数据传输,但从存储单元中输出到真正出现在内存芯片的 I/O 接口之间还需要一定的时间(数据触发本身就有延迟,而且还需要进行信号放大),这段时间就是非常著名的 CL(CAS Latency,列地址脉冲选通潜伏期)。CL 的数值与 tRCD 一样,以时钟周期数表示。如 DDR3-800,时钟频率为 100MHz,时钟周期为 10ns,如果 CL=2 就意味着 20ns 的潜伏期。不过CL只是针对读取操作。
    由于芯片体积的原因,存储单元中的电容容量很小,所以信号要经过放大来保证其有效的识别性,这个放大/驱动工作由S-AMP负责,一个存储体对应一个S- AMP通道。但它要有一个准备时间才能保证信号的发送强度(事前还要进行电压比较以进行逻辑电平的判断),因此从数据I/O总线上有数据输出之前的一个时钟上升沿开始,数据即已传向S-AMP,也就是说此时数据已经被触发,经过一定的驱动时间最终传向数据I/O总线进行输出,这段时间我们称之为 tAC(Access Time from CLK,时钟触发后的访问时间)。
    目前内存的读写基本都是连续的,因为与CPU交换的数据量以一个Cache Line(即CPU内Cache的存储单位)的容量为准,一般为64字节。而现有的Rank位宽为8字节(64bit),那么就要一次连续传输8次,这就涉及到我们也经常能遇到的突发传输的概念。突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输的周期数就是突发长度(Burst Lengths,简称BL)。
    在进行突发传输时,只要指定起始列地址与突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。这样,除了第一笔数据的传输需要若干个周期(主要是之前的延迟,一般的是tRCD+CL)外,其后每个数据只需一个周期的即可获得。
    突发连续读取模式:只要指定起始列地址与突发长度,后续的寻址与数据的读取自动进行,而只要控制好两段突发读取命令的间隔周期(与BL相同)即可做到连续的突发传输。

    谈到了突发长度时。如果BL=4,那么也就是说一次就传送4×64bit的数据。但是,如果其中的第二笔数据是不需要的,怎么办?还都传输吗?为了屏蔽不需要的数据,人们采用了数据掩码(Data I/O Mask,简称DQM)技术。通过DQM,内存可以控制I/O端口取消哪些输出或输入的数据。这里需要强调的是,在读取时,被屏蔽的数据仍然会从存储体传出,只是在“掩码逻辑单元”处被屏蔽。DQM由北桥控制,为了精确屏蔽一个P-Bank位宽中的每个字节,每个DIMM有8个DQM 信号线,每个信号针对一个字节。这样,对于4bit位宽芯片,两个芯片共用一个DQM信号线,对于8bit位宽芯片,一个芯片占用一个DQM信号,而对于 16bit位宽芯片,则需要两个DQM引脚。
    在数据读取完之后,为了腾出读出放大器以供同一Bank内其他行的寻址并传输数据,内存芯片将进行预充电的操作来关闭当前工作行。还是以上面那个Bank示意图为例。当前寻址的存储单元是B1、R2、C6。如果接下来的寻址命令是B1、R2、C4,则不用预充电,因为读出放大器正在为这一行服务。但如果地址命令是B1、R4、C4,由于是同一Bank的不同行,那么就必须要先把R2关闭,才能对R4寻址。从开始关闭现有的工作行,到可以打开新的工作行之间的间隔就是tRP(Row Precharge command Period,行预充电有效周期),单位也是时钟周期数。

    3.8 ODT

    ODT是内建核心的终结电阻,它的功能是让一些信号在终结电阻处消耗完,防止这些信号在电路上形成反射。换句话说就是在片内设置合适的上下拉电阻,以获得更好的信号完整性。被ODT校准的信号包括:

    • DQ, DQS, DQS# and DM for x4 configuration
    • DQ, DQS, DQS#, DM, TDQS and TDQS# for X8 configuration
    • DQU, DQL, DQSU, DQSU#, DQSL, DQSL#, DMU and DML for X16 configuration
      当一个CPU挂了很多个DDR芯片的时候,他们是共用控制线,地址线的,走线肯定要分叉,如果没有中端匹配电阻,肯定会产生信号完整性问题。那么如果只有一个DDR芯片的时候,需不需要呢?正常情况下,走线很短,有符合规则,是不需要的。
      下图是DDR中的IO上下拉电阻,RON是DDR的输出结构的上下拉电阻,RTT是DDR输入结构的上下拉电阻。这两个电阻的阻值都是可调的。
      在这里插入图片描述
      在这里插入图片描述
      下图是RON的调节,注意这不是ODT的任务,调节是通过寄存器实现。
      在这里插入图片描述
      下图是RTT的调节,是ODT要做的事情,而且RTT的档位要多,也是通过寄存器调节的。
      在这里插入图片描述
      注意,DDR3的PIN定义上有一个引脚是ODT,如果ODT=0,DRAM Termination State功能关闭;ODT=1,DRAM Termination State的功能参考寄存器设置。如下是一个真值表。因为DRAM Termination State非常耗电,所以不用的时候最好不要打开。

    在这里插入图片描述

    3.9 DDR3的ZQ

    ZQ信号在DDR3时代开始引入,要求在ZQ引脚放置一个240Ω±1%的高精度电阻到地,注意必须是高精度。而且这个电阻是必须的,不能省略的。进行ODT时,是以这个引脚上的阻值为参考来进行校准的。
    校准需要调整内部电阻,以获得更好的信号完整性,但是内部电阻随着温度会有些细微的变化,为了将这个变化纠正回来,就需要一个外部的精确电阻作为参考。详细来讲,就是为RTT和RON提供参考电阻。

    3.10 OCD

    OCD 是在 DDR-II 开始加入的新功能,而且这个功能是可选的,有的资料上面又叫离线驱动调整。OCD的主要作用在于调整 I/O 接口端的电压,来补偿上拉与下拉电阻值, 从而调整DQS 与 DQ 之间的同步确保信号的完整与可靠性。 调校期间,分别测试 DQS 高电平和 DQ高电平,以及 DQS 低电平和 DQ 高电平的同步情况。 如果不满足要求,则通过设定突发长度的地址线来传送上拉 / 下拉电阻等级(加一档或减一档),直到测试合格才退出 OCD 操作,通过 OCD 操作来减少 DQ 、 DQS的倾斜从而提高信号的完整性及控制电压来提高信号品质。由于在一般情况下对应用环境稳定程度要求并不太高,只要存在差分 DQS时就基本可以保证同步的准确性, 而且 OCD 的调整对其他操作也有一定影响, 因此 OCD 功能在普通台式机上并没有什么作用,其优点主要体现在对数据完整性非常敏感的服务器等高端产品领域。
    在这里插入图片描述

    4. DDR3的PIN定义

    下面是三星K4B4G0446Q/K4B4G0846Q的PIN定义,每一个都有很详细的解释。

    在这里插入图片描述
    以x8的配置为例,如下是其Ball Map。
    在这里插入图片描述

    • 一对时钟线CK和CKn
    • 数据线DQ0~DQ7共8位。
    • 一对差分对DQS和DQSn
    • 地址线A0~A15,其中,A10和A12有特殊用途。
    • 行选中信号RASn
    • 列选中信号CASn
    • 写使能Wen
    • 片选CSn
    • Bank选择BA0~2
    • 一个Reset信号,是DDR3新增的一项重要功能,并为此专门准备了一个引脚。这一引脚将使DDR3的初始化处理变得简单。当Reset命令有效时,DDR3 内存将停止所有的操作,并切换至最少量活动的状态,以节约电力。在Reset期间,DDR3内存将关闭内在的大部分功能,所有数据接收与发送器都将关闭,且所有内部的程序装置将复位,DLL(延迟锁相环路)与时钟电路将停止工作,甚至不理睬数据总线上的任何动静。这样一来,该功能将使DDR3达到最节省电力的目的
    • ZQ和ODT PIN上文已经说明。

    5 DDR的走线规则

    DDR的信号线需要分组:
    -数据线一组(DQ,DQS,DQM),误差控制在20mil以内;
    -控制线一组(Address,控制线,时钟),以时钟为中心,误差控制在100mil以内。

    参考文章:
    https://blog.csdn.net/jackle_zheng/article/details/7460547
    https://www.cnblogs.com/jacklong-yin/p/9640485.html

    展开全文
  • ,你可以设置被播放器呈现的项目中可视的那一部分,设置音频的混合参数以及被应用于播放期间的视频组合设定,或者播放期间的禁用组件。 You play player items using a player object, and direct the output ...

    新博客:
    完整版 - AVFoundation Programming Guide

    分章节版:
    – 第1章:About AVFoundation - AVFoundation概述
    – 第2章:Using Assets - 使用Assets
    – 第3章:Playback - 播放
    – 第4章:Editing - 编辑
    – 第5章:Still and Video Media Capture - 静态视频媒体捕获
    – 第6章:Export - 输出
    – 第7章:Time and Media Representations 时间和媒体表现

    版权声明:本文为博主原创翻译,如需转载请注明出处。

    苹果源文档地址 - 点击这里

    About AVFoundation - AVFoundation概述

    AVFoundation is one of several frameworks that you can use to play and create time-based audiovisual media. It provides an Objective-C interface you use to work on a detailed level with time-based audiovisual data. For example, you can use it to examine, create, edit, or reencode media files. You can also get input streams from devices and manipulate video during realtime capture and playback. Figure I-1 shows the architecture on iOS.

    AVFoundation 是可以用它来播放和创建基于时间的视听媒体的几个框架之一。它提供了基于时间的视听数据的详细级别上的Objective-C接口。例如,你可以用它来检查,创建,编辑或重新编码媒体文件。您也可以从设备得到输入流和在实时捕捉回放过程中操控视频。图I-1显示了iOS上的架构。


    Figure I-1  AVFoundation stack on iOS

    Figure I-2 shows the corresponding media architecture on OS X.

    图1-2显示了OS X上相关媒体的架构:


    Figure I-2  AVFoundation stack on OS X

    You should typically use the highest-level abstraction available that allows you to perform the tasks you want.

    • If you simply want to play movies, use the AVKit framework.

    • On iOS, to record video when you need only minimal control over format, use the UIKit framework(UIImagePickerController)

    Note, however, that some of the primitive data structures that you use in AV Foundation—including time-related data structures and opaque objects to carry and describe media data—are declared in the Core Media framework.

    通常,您应该使用可用的最高级别的抽象接口,执行所需的任务。

    • 如果你只是想播放电影,使用 AVKit 框架。

    • 在iOS上,当你在格式上只需要最少的控制,使用UIKit框架录制视频。(UIImagePickerController).

    但是请注意,某些在AV Foundation 中使用的原始数据结构,包括时间相关的数据结构和不透明数据对象的传递和描述媒体数据是在Core Media framework声明的。

    At a Glance - 摘要

    There are two facets to the AVFoundation framework—APIs related to video and APIs related just to audio. The older audio-related classes provide easy ways to deal with audio. They are described in the Multimedia Programming Guide, not in this document.

    You can also configure the audio behavior of your application using AVAudioSession; this is described in Audio Session Programming Guide.

    AVFoundation框架包含视频相关的APIs和音频相关的APIs。旧的音频相关类提供了简便的方法来处理音频。他们在Multimedia Programming Guide,中介绍,不在这个文档中。

    您还可以使用 AVAudioSession 来配置应用程序的音频行为;这是在 Audio Session Programming Guide 文档中介绍的。

    Representing and Using Media with AVFoundation - 用AVFoundation 表示和使用媒体

    The primary class that the AV Foundation framework uses to represent media is AVAsset. The design of the framework is largely guided by this representation. Understanding its structure will help you to understand how the framework works. An AVAssetinstance is an aggregated representation of a collection of one or more pieces of media data (audio and video tracks). It provides information about the collection as a whole, such as its title, duration, natural presentation size, and so on. AVAsset is not tied to particular data format. AVAsset is the superclass of other classes used to create asset instances from media at a URL (see Using Assets) and to create new compositions (see Editing).

    AV Foundation框架用来表示媒体的主要类是 AVAsset。框架的设计主要是由这种表示引导。了解它的结构将有助于您了解该框架是如何工作的。一个 AVAsset 实例的媒体数据的一个或更多个(音频和视频轨道)的集合的聚集表示。它规定将有关集合的信息作为一个整体,如它的名称,时间,自然呈现大小等的信息。 AVAsset 是不依赖于特定的数据格式。 AVAsset是常常从URL中的媒体创建资产实例的这种类父类(请参阅 Using Assets),并创造新的成分(见 Editing)。

    Each of the individual pieces of media data in the asset is of a uniform type and called a track. In a typical simple case, one track represents the audio component, and another represents the video component; in a complex composition, however, there may be multiple overlapping tracks of audio and video. Assets may also have metadata.

    Asset中媒体数据的各个部分,每一个都是一个统一的类型,把这个类型称为“轨道”。在一个典型简单的情况下,一个轨道代表这个音频组件,另一个代表视频组件。然而复杂的组合中,有可能是多个重叠的音频和视频轨道。Assets也可能有元数据。

    A vital concept in AV Foundation is that initializing an asset or a track does not necessarily mean that it is ready for use. It may require some time to calculate even the duration of an item (an MP3 file, for example, may not contain summary information). Rather than blocking the current thread while a value is being calculated, you ask for values and get an answer back asynchronously through a callback that you define using a block.

    AV Foundation 中一个非常重要的概念是:初始化一个 asset 或者一个轨道并不一定意味着它已经准备好可以被使用。这可能需要一些时间来计算一个项目的持续时间(例如一个MP3文件,其中可能不包含摘要信息)。而不是当一个值被计算的时候阻塞当前线程,你访问这个值,并且通过调用你定义的一个 block 来得到异步返回。

    Relevant Chapters: Using AssetsTime and Media Representations

    相关章节:Using AssetsTime and Media Representations

    Playback - 播放

    AVFoundation allows you to manage the playback of asset in sophisticated ways. To support this, it separates the presentation state of an asset from the asset itself. This allows you to, for example, play two different segments of the same asset at the same time rendered at different resolutions. The presentation state for an asset is managed by a player item object; the presentation state for each track within an asset is managed by a player item track object. Using the player item and player item tracks you can, for example, set the size at which the visual portion of the item is presented by the player, set the audio mix parameters and video composition settings to be applied during playback, or disable components of the asset during playback.

    AVFoundation允许你用一种复杂的方式来管理asset的播放。为了支持这一点,它将一个asset的呈现状态从asset自身中分离出来。例如允许你在不同的分辨率下同时播放同一个asset中的两个不同的片段。一个asset的呈现状态是由player item对象管理的。Asset中的每个轨道的呈现状态是由player item track对象管理的。例如使用player itemplayer item tracks,你可以设置被播放器呈现的项目中可视的那一部分,设置音频的混合参数以及被应用于播放期间的视频组合设定,或者播放期间的禁用组件。

    You play player items using a player object, and direct the output of a player to the Core Animation layer. You can use a player queue to schedule playback of a collection of player items in sequence.

    你可以使用一个 player 对象来播放播放器项目,并且直接输出一个播放器给核心动画层。你可以使用一个 player queue(player对象的队列)去给队列中player items集合中的播放项目安排序列。

    Relevant Chapter: Playback

    相关章节:Playback

    Reading, Writing, and Reencoding Assets - 读取,写入和重新编码Assets

    AVFoundation allows you to create new representations of an asset in several ways. You can simply reencode an existing asset, or—in iOS 4.1 and later—you can perform operations on the contents of an asset and save the result as a new asset.

    AVFoundation 允许你用几种方式创建新的 asset 的表现形式。你可以简单将已经存在的 asset 重新编码,或者在iOS4.1以及之后的版本中,你可以在一个 asset 的目录中执行一些操作并且将结果保存为一个新的 asset

    You use an export session to reencode an existing asset into a format defined by one of a small number of commonly-used presets. If you need more control over the transformation, in iOS 4.1 and later you can use an asset reader and asset writer object in tandem to convert an asset from one representation to another. Using these objects you can, for example, choose which of the tracks you want to be represented in the output file, specify your own output format, or modify the asset during the conversion process.

    你可以使用 export session 将一个现有的asset重新编码为一个小数字,这个小数字是常用的预先设定好的一些小数字中的一个。如果在转换中你需要更多的控制,在iOS4.1已经以后的版本中,你可以使用 asset reader 和 asset writer 对象串联的一个一个的转换。例如你可以使用这些对象选择在输出的文件中想要表示的轨道,指定你自己的输出格式,或者在转换过程中修改这个asset

    To produce a visual representation of the waveform, you use an asset reader to read the audio track of an asset.

    为了产生波形的可视化表示,你可以使用asset reader去读取asset中的音频轨道。

    Relevant Chapter: Using Assets

    相关章节:Using Assets

    Thumbnails - 缩略图

    To create thumbnail images of video presentations, you initialize an instance of AVAssetImageGenerator using the asset from which you want to generate thumbnails. AVAssetImageGenerator uses the default enabled video tracks to generate images.

    创建视频演示图像的缩略图,使用想要生成缩略图的asset初始化一个 AVAssetImageGenerator 的实例。AVAssetImageGenerator 使用默认启用视频轨道来生成图像。

    Relevant Chapter: Using Assets

    相关章节:Using Assets

    Editing - 编辑

    AVFoundation uses compositions to create new assets from existing pieces of media (typically, one or more video and audio tracks). You use a mutable composition to add and remove tracks, and adjust their temporal orderings. You can also set the relative volumes and ramping of audio tracks; and set the opacity, and opacity ramps, of video tracks. A composition is an assemblage of pieces of media held in memory. When you export a composition using an export session, it’s collapsed to a file.

    AVFoundation 使用 compositions 去从现有的媒体片段(通常是一个或多个视频和音频轨道)创建新的 assets 。你可以使用一个可变成分去添加和删除轨道,并调整它们的时间排序。你也可以设置相对音量和增加音频轨道;并且设置不透明度,浑浊坡道,视频跟踪。一种组合物,是一种在内存中存储的介质的组合。当年你使用 export session 导出一个成份,它会坍塌到一个文件中。

    You can also create an asset from media such as sample buffers or still images using an asset writer.

    你也可以从媒体上创建一个asset,比如使用asset writer.的示例缓冲区或静态图像。

    Relevant Chapter: Editing

    相关章节:Editing

    Still and Video Media Capture - 静态和视频媒体捕获

    Recording input from cameras and microphones is managed by a capture session. A capture session coordinates the flow of data from input devices to outputs such as a movie file. You can configure multiple inputs and outputs for a single session, even when the session is running. You send messages to the session to start and stop data flow.

    从相机和麦克风记录输入是由一个 capture session 管理的。一个 capture session 协调从输入设备到输出的数据流,比如一个电影文件。你可以为一个单一的 session 配置多个输入和输出,甚至 session 正在运行的时候也可以。你将消息发送到 session 去启动和停止数据流。

    In addition, you can use an instance of a preview layer to show the user what a camera is recording.

    此外,你可以使用 preview layer 的一个实例来向用户显示一个相机是正在录制的。

    Relevant Chapter: Still and Video Media Capture

    相关章节:Still and Video Media Capture

    Concurrent Programming with AVFoundation - AVFoundation并发编程

    Callbacks from AVFoundation—invocations of blocks, key-value observers, and notification handlers—are not guaranteed to be made on any particular thread or queue. Instead, AVFoundation invokes these handlers on threads or queues on which it performs its internal tasks.

    AVFoundation 回调,比如块的调用、键值观察者以及通知处理程序,都不能保证在任何特定的线程或队列进行。相反,AVFoundation 在线程或者执行其内部任务的队列上调用这些处理程序。

    There are two general guidelines as far as notifications and threading:

    • UI related notifications occur on the main thread.
    • Classes or methods that require you create and/or specify a queue will return notifications on that queue.

    Beyond those two guidelines (and there are exceptions, which are noted in the reference documentation) you should not assume that a notification will be returned on any specific thread.

    下面是两个有关通知和线程的准则

    • 在主线程上发生的与用户界面相关的通知。
    • 需要创建并且/或者 指定一个队列的类或者方法将返回该队列的通知。

    除了这两个准则(当然是有一些例外,在参考文档中会被指出),你不应该假设一个通知将在任何特定的线程返回。

    If you’re writing a multithreaded application, you can use the NSThread method isMainThread or [[NSThread currentThread] isEqual:<#A stored thread reference#>] to test whether the invocation thread is a thread you expect to perform your work on. You can redirect messages to appropriate threads using methods such as performSelectorOnMainThread:withObject:waitUntilDone: and performSelector:onThread:withObject:waitUntilDone:modes:. You could also use dispatch_async to “bounce” to your blocks on an appropriate queue, either the main queue for UI tasks or a queue you have up for concurrent operations. For more about concurrent operations, see Concurrency Programming Guide; for more about blocks, see Blocks Programming Topics. The AVCam-iOS: Using AVFoundation to Capture Images and Movies sample code is considered the primary example for all AVFoundation functionality and can be consulted for examples of thread and queue usage with AVFoundation.

    如果你在写一个多线程的应用程序,你可以使用 NSThread  方法 isMainThread 或者 [[NSThread currentThread] isEqual:<#A stored thread reference#>] 去测试是否调用了你期望执行你任务的线程。你可以使用方法重定向 消息给适合的线程,比如 performSelectorOnMainThread:withObject:waitUntilDone: 以及  performSelector:onThread:withObject:waitUntilDone:modes:.你也可以使用 dispatch_async弹回到适当队列的 blocks 中,无论是在主界面的任务队列还是有了并发操作的队列。更多关于并行操作,请查看 Concurrency Programming Guide;更多关于块,请查看 Blocks Programming Topics. AVCam-iOS: Using AVFoundation to Capture Images and Movies 示例代码是所有 AVFoundation 功能最主要的例子,可以对线程和队列使用 AVFoundation 实例参考。

    Prerequisites - 预备知识

    AVFoundation is an advanced Cocoa framework. To use it effectively, you must have:

    • A solid understanding of fundamental Cocoa development tools and techniques
    • A basic grasp of blocks
    • A basic understanding of key-value coding and key-value observing
    • For playback, a basic understanding of Core Animation (see Core Animation Programming Guide or, for basic playback, the AVKit Framework Reference.

    AVFoundation 是一种先进的 Cocoa 框架,为了有效的使用,你必须掌握下面的知识:

    • 扎实的了解基本的 Cocoa 开发工具和框架
    • 对块有基本的了解
    • 了解基本的键值编码(key-value coding)和键值观察(key-value observing
    • 对于播放,对核心动画的基本理解 (see Core Animation Programming Guide )或者,对于基本播放, 请看 AVKit Framework Reference.

    See Also - 参考

    There are several AVFoundation examples including two that are key to understanding and implementation Camera capture functionality:

    • AVCam-iOS: Using AVFoundation to Capture Images and Movies is the canonical sample code for implementing any program that uses the camera functionality. It is a complete sample, well documented, and covers the majority of the functionality showing the best practices.
    • AVCamManual: Extending AVCam to Use Manual Capture API is the companion application to AVCam. It implements Camera functionality using the manual camera controls. It is also a complete example, well documented, and should be considered the canonical example for creating camera applications that take advantage of manual controls.
    • RosyWriter is an example that demonstrates real time frame processing and in particular how to apply filters to video content. This is a very common developer requirement and this example covers that functionality.
    • AVLocationPlayer: Using AVFoundation Metadata Reading APIs demonstrates using the metadata APIs.

    有几个 AVFoundation 的例子,包括两个理解和实现摄像头捕捉功能的关键点:

    • AVCam-iOS: Using AVFoundation to Capture Images and Movies 是实现任何想使用摄像头功能的程序的典型示例代码。它是一个完整的样本,以及记录,并涵盖了大部分主要的功能。
    • AVCamManual: Extending AVCam to Use Manual Capture API 是AVCam相对应的应用程序。它使用手动相机控制实现相机功能。它也是一个完成的例子,以及记录,并且应该被视为利用手动控制创建相机应用程序的典型例子。
    • RosyWriter 是一个演示实时帧处理的例子,特别是如果过滤器应用到视频内容。这是一个非常普遍的开发人员的需求,这个例子涵盖了这个功能。
    • AVLocationPlayer: 使用 AVFoundation Metadata Reading APIs 演示使用 the metadata APIs.

    Using Assets - 使用Assets

    Assets can come from a file or from media in the user’s iPod library or Photo library. When you create an asset object all the information that you might want to retrieve for that item is not immediately available. Once you have a movie asset, you can extract still images from it, transcode it to another format, or trim the contents.

    Assets 可以来自文件或者媒体用户的iPod库、图片库。当你创建一个 asset 对象时,所有你可能想要检索该项目的信息不是立即可用的。一旦你有了一个电影 asset ,你可以从里面提取静态图像,转换到另一个格式,或者对内容就行修剪。

    Creating an Asset Object - 创建一个Asset对象

    To create an asset to represent any resource that you can identify using a URL, you use AVURLAsset. The simplest case is creating an asset from a file:

    为了创建一个 asset ,去代表任何你能用一个 URL 识别的资源,你可以使用 AVURLAsset .最简单的情况是从一个文件创建一个 asset

    NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
    AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];

    Options for Initializing an Asset - 初始化一个Asset的选择

    The AVURLAsset initialization methods take as their second argument an options dictionary. The only key used in the dictionary is AVURLAssetPreferPreciseDurationAndTimingKey. The corresponding value is a Boolean (contained in an NSValue object) that indicates whether the asset should be prepared to indicate a precise duration and provide precise random access by time.

    AVURLAsset 初始化方法作为它们的第二个参数选项字典。本字典中唯一被使用的 keyAVURLAssetPreferPreciseDurationAndTimingKey. 相应的值是一个布尔值(包含在一个 NSValue 对象中),这个布尔值指出是否该 asset 应该准备标出一个精确的时间和提供一个以时间为种子的随机存取。

    Getting the exact duration of an asset may require significant processing overhead. Using an approximate duration is typically a cheaper operation and sufficient for playback. Thus:

    • If you only intend to play the asset, either pass nil instead of a dictionary, or pass a dictionary that contains the AVURLAssetPreferPreciseDurationAndTimingKey key and a corresponding value of NO (contained in an NSValue object).
    • If you want to add the asset to a composition (AVMutableComposition), you typically need precise random access. Pass a dictionary that contains theAVURLAssetPreferPreciseDurationAndTimingKey key and a corresponding value of YES (contained in an NSValue object—recall that NSNumberinherits from NSValue):

    获得一个asset的确切持续时间可能需要大量的处理开销。使用一个近似的持续时间通常是一个更便宜的操作并且对于播放已经足够了。因此:

    • 如果你只打算播放这个 asset, 要么传递一个 nil 代替 dictionary ,或者传递一个字典,这个字典包含 AVURLAssetPreferPreciseDurationAndTimingKeykey和相应 NO(包含在一个 NSValue 对象) 的值。
    • 如果你想要把 asset 添加给一个 composition (AVMutableComposition), 通常你需要精确的随机存取。传递一个字典(这个字典包含 AVURLAssetPreferPreciseDurationAndTimingKey key) 和一个相应的 YES 的值(YES 包含在一个 NSValue 对象中,回忆一下继承自 NSValueNSNmuber
    NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
    NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @YES };
    AVURLAsset *anAssetToUseInAComposition = [[AVURLAsset alloc] initWithURL:url options:options];

    Accessing the User’s Assets - 访问用户的Assets

    To access the assets managed by the iPod library or by the Photos application, you need to get a URL of the asset you want.

    • To access the iPod Library, you create an MPMediaQuery instance to find the item you want, then get its URL using MPMediaItemPropertyAssetURL.For more about the Media Library, see Multimedia Programming Guide.
    • To access the assets managed by the Photos application, you use ALAssetsLibrary.

    The following example shows how you can get an asset to represent the first video in the Saved Photos Album.

    为了访问由 iPod 库或者照片应用程序管理的 assets ,你需要得到你想要 asset 的一个 URL

    下面的例子展示了如何获得一个 asset 来保存照片相册中的第一个视频。

    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    
    // Enumerate just the photos and videos group by using ALAssetsGroupSavedPhotos.
    [library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
    
    // Within the group enumeration block, filter to enumerate just videos.
    [group setAssetsFilter:[ALAssetsFilter allVideos]];
    
    // For this example, we're only interested in the first item.
    [group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:0]
                            options:0
                         usingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop) {
    
                             // The end of the enumeration is signaled by asset == nil.
                             if (alAsset) {
                                 ALAssetRepresentation *representation = [alAsset defaultRepresentation];
                                 NSURL *url = [representation url];
                                 AVAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
                                 // Do something interesting with the AV asset.
                             }
                         }];
                     }
                     failureBlock: ^(NSError *error) {
                         // Typically you should handle an error more gracefully than this.
                         NSLog(@"No groups");
                     }];

    Preparing an Asset for Use - 将 Asset 准备好使用

    Initializing an asset (or track) does not necessarily mean that all the information that you might want to retrieve for that item is immediately available. It may require some time to calculate even the duration of an item (an MP3 file, for example, may not contain summary information). Rather than blocking the current thread while a value is being calculated, you should use the AVAsynchronousKeyValueLoading protocol to ask for values and get an answer back later through a completion handler you define using a block. (AVAsset and AVAssetTrack conform to the AVAsynchronousKeyValueLoading protocol.)

    初始化一个 asset (或者轨道)并不意味着你可能想要检索该项的所有信息是立即可用的。这可能需要一些时间来计算一个项目的持续时间(例如一个 MP3 文件可能不包含摘要信息)。当一个值被计算的时候不应该阻塞当前线程,你应该使用AVAsynchronousKeyValueLoading 协议去请求值,通过完成处理你定义使用的一个 block 后得到答复。(AVAsset and AVAssetTrack 遵循 AVAsynchronousKeyValueLoading 协议.)

    You test whether a value is loaded for a property using statusOfValueForKey:error:. When an asset is first loaded, the value of most or all of its properties is AVKeyValueStatusUnknown. To load a value for one or more properties, you invoke loadValuesAsynchronouslyForKeys:completionHandler:. In the completion handler, you take whatever action is appropriate depending on the property’s status. You should always be prepared for loading to not complete successfully, either because it failed for some reason such as a network-based URL being inaccessible, or because the load was canceled.

    测试一个值是否是使用 statusOfValueForKey:error: 加载为一个属性。当 asset 被首次加载时,大部分的或全部属性值是 AVKeyValueStatusUnknown。为一个或多个属性加载一个值,调用loadValuesAsynchronouslyForKeys:completionHandler:。在完成处理程序中,你采取的行动是否恰当,取决于属性的状态。你应该总是准备加载不会完全成功,它可能有一些原因,比如基于网络的 URL是无法访问的,或者因为负载被取消。

    NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
    AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
    NSArray *keys = @[@"duration"];
    
    [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
    
        NSError *error = nil;
        AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"duration" error:&error];
        switch (tracksStatus) {
            case AVKeyValueStatusLoaded:
                [self updateUserInterfaceForDuration];
                break;
            case AVKeyValueStatusFailed:
                [self reportError:error forAsset:asset];
                break;
            case AVKeyValueStatusCancelled:
                // Do whatever is appropriate for cancelation.
                break;
       }
    }];

    If you want to prepare an asset for playback, you should load its tracks property. For more about playing assets, see Playback.

    如果你想准备一个 asset 去播放,你应该加载它的轨道属性。更多有关播放 assets,请看 Playback

    Getting Still Images From a Video - 从视频中获取静态图像

    To get still images such as thumbnails from an asset for playback, you use an AVAssetImageGenerator object. You initialize an image generator with your asset. Initialization may succeed, though, even if the asset possesses no visual tracks at the time of initialization, so if necessary you should test whether the asset has any tracks with the visual characteristic using tracksWithMediaCharacteristic:.

    为了从一个准备播放的 asset 中得到静态图像,比如缩略图,可以使用 AVAssetImageGenerator 对象。用你的 asset 初始化一个图像发生器。不过即使 asset 进程在初始化的时候没有视觉跟踪,也可以成功,所以如果有必要,你应该测试一下, asset 是否有轨道有使用 tracksWithMediaCharacteristic 的视觉特征。

    AVAsset anAsset = <#Get an asset#>;
    if ([[anAsset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
        AVAssetImageGenerator *imageGenerator =
            [AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
        // Implementation continues...
    }

    You can configure several aspects of the image generator, for example, you can specify the maximum dimensions for the images it generates and the aperture mode using maximumSize and apertureMode respectively.You can then generate a single image at a given time, or a series of images. You must ensure that you keep a strong reference to the image generator until it has generated all the images.

    你可以配置几个图像发生器的部分,例如,可以指定生成的图像采用最大值,并且光圈的模式分别使用 maximumSizeapertureMode 。然后可以在给定的时间生成一个单独的图像,或者一系列图像。你必须确定,在生成所有图像之前,必须对图像生成器保持一个强引用。

    Generating a Single Image - 生成一个单独的图像

    You use copyCGImageAtTime:actualTime:error: to generate a single image at a specific time. AVFoundation may not be able to produce an image at exactly the time you request, so you can pass as the second argument a pointer to a CMTime that upon return contains the time at which the image was actually generated.

    使用 copyCGImageAtTime:actualTime:error: 方法在指定时间生成一个图像。AVFoundation 在你要求的确切时间可能无法产生一个图像,所以你可以将一个指向 CMTime 的指针当做第二个参数穿过去,这个指针返回的时候包含图像被实际生成的时间。

    AVAsset *myAsset = <#An asset#>];
    AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:myAsset];
    
    Float64 durationSeconds = CMTimeGetSeconds([myAsset duration]);
    CMTime midpoint = CMTimeMakeWithSeconds(durationSeconds/2.0, 600);
    NSError *error;
    CMTime actualTime;
    
    CGImageRef halfWayImage = [imageGenerator copyCGImageAtTime:midpoint actualTime:&actualTime error:&error];
    
    if (halfWayImage != NULL) {
    
        NSString *actualTimeString = (NSString *)CMTimeCopyDescription(NULL, actualTime);
        NSString *requestedTimeString = (NSString *)CMTimeCopyDescription(NULL, midpoint);
        NSLog(@"Got halfWayImage: Asked for %@, got %@", requestedTimeString, actualTimeString);
    
        // Do something interesting with the image.
        CGImageRelease(halfWayImage);
    }
    

    Generating a Sequence of Images - 生成一系列图像

    To generate a series of images, you send the image generator a generateCGImagesAsynchronouslyForTimes:completionHandler: message. The first argument is an array of NSValue objects, each containing a CMTime structure, specifying the asset times for which you want images to be generated. The second argument is a block that serves as a callback invoked for each image that is generated. The block arguments provide a result constant that tells you whether the image was created successfully or if the operation was canceled, and, as appropriate:

    • The image
    • The time for which you requested the image and the actual time for which the image was generated
    • An error object that describes the reason generation failed

    In your implementation of the block, check the result constant to determine whether the image was created. In addition, ensure that you keep a strong reference to the image generator until it has finished creating the images.

    生成一系列图像,可以给图像生成器发送 generateCGImagesAsynchronouslyForTimes:completionHandler: 消息。第一个参数是一个 NSValue 对象的数组,每个都包含一个 CMTime 结构体,指定了图像想要被生成的 asset 时间。block 参数提供了一个结果,这个结果包含了告诉你是否图像被成功生成,或者操作某些情况下被取消。结果:

    • 图像
    • 你要求的图像和图像生成的实际时间
    • 一个 error 对象,描述了生成失败的原因

    block 的实现中,检查结果常数,来确定图像是否被创建。此外,在完成创建图像之前,确保保持一个强引用给图像生成器。

    AVAsset *myAsset = <#An asset#>];
    // Assume: @property (strong) AVAssetImageGenerator *imageGenerator;
    self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:myAsset];
    
    Float64 durationSeconds = CMTimeGetSeconds([myAsset duration]);
    CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 600);
    CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 600);
    CMTime end = CMTimeMakeWithSeconds(durationSeconds, 600);
    NSArray *times = @[NSValue valueWithCMTime:kCMTimeZero],
                      [NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird],
                      [NSValue valueWithCMTime:end]];
    
    [imageGenerator generateCGImagesAsynchronouslyForTimes:times
                    completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,
                                        AVAssetImageGeneratorResult result, NSError *error) {
    
                    NSString *requestedTimeString = (NSString *)
                        CFBridgingRelease(CMTimeCopyDescription(NULL, requestedTime));
                    NSString *actualTimeString = (NSString *)
                        CFBridgingRelease(CMTimeCopyDescription(NULL, actualTime));
                    NSLog(@"Requested: %@; actual %@", requestedTimeString, actualTimeString);
    
                    if (result == AVAssetImageGeneratorSucceeded) {
                        // Do something interesting with the image.
                    }
    
                    if (result == AVAssetImageGeneratorFailed) {
                        NSLog(@"Failed with error: %@", [error localizedDescription]);
                    }
                    if (result == AVAssetImageGeneratorCancelled) {
                        NSLog(@"Canceled");
                    }
      }];
    

    You can cancel the generation of the image sequence by sending the image generator a cancelAllCGImageGeneration message.

    你发送给图像生成器一个 cancelAllCGImageGeneration 消息,可以取消队列中的图像生成。

    Trimming and Transcoding a Movie - 微调和转化为一个电影

    You can transcode a movie from one format to another, and trim a movie, using an AVAssetExportSession object. The workflow is shown in Figure 1-1. An export session is a controller object that manages asynchronous export of an asset. You initialize the session using the asset you want to export and the name of a export preset that indicates the export options you want to apply (see allExportPresets). You then configure the export session to specify the output URL and file type, and optionally other settings such as the metadata and whether the output should be optimized for network use.

    asset一律使用“资产”代码,切换还要加“略麻烦

    你可以使用 AVAssetExportSession 对象,将一个电影的编码进行转换,并且对电影进行微调。工作流程如图1-1所示。一个 export session 是一个控制器对象,管理一个资产的异步导出。使用想要导出的资产初始化一个 session 和输出设定的名称,这个输出设定表明你想申请的导出选项(allExportPresets)。然后配置导出会话去指定输出的 URL 和文件类型,以及其他可选的设定,比如元数据,是否将输出优化用于网络使用。


    Figure 1-1  The export session workflow

    You can check whether you can export a given asset using a given preset using exportPresetsCompatibleWithAsset: as illustrated in this example:

    你可以检查你能否用给定的预设导出一个给定的资产,使用 exportPresetsCompatibleWithAsset: 作为示例。

    AVAsset *anAsset = <#Get an asset#>;
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
    if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
            initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
        // Implementation continues.
    }

    You complete the configuration of the session by providing the output URL (The URL must be a file URL.) AVAssetExportSession can infer the output file type from the URL’s path extension; typically, however, you set it directly using outputFileType. You can also specify additional properties such as the time range, a limit for the output file length, whether the exported file should be optimized for network use, and a video composition. The following example illustrates how to use the timeRange property to trim the movie:

    完成会话的配置,是由输出的 URL (URL 必须是文件的 URL)控制的。AVAssetExportSession可以从 URL 的路径延伸推断输出文件的类型。然而通常情况下,直接使用 outputFileType 设定。还可以指定附加属性,如时间范围、输出文件长度的限制、导出的文件是否应该为了网络使用而优化、还有一个视频的构成。下面的示例展示了如果使用 timeRange 属性修剪电影。

        exportSession.outputURL = <#A file URL#>;
        exportSession.outputFileType = AVFileTypeQuickTimeMovie;
    
        CMTime start = CMTimeMakeWithSeconds(1.0, 600);
        CMTime duration = CMTimeMakeWithSeconds(3.0, 600);
        CMTimeRange range = CMTimeRangeMake(start, duration);
        exportSession.timeRange = range;

    To create the new file, you invoke exportAsynchronouslyWithCompletionHandler:. The completion handler block is called when the export operation finishes; in your implementation of the handler, you should check the session’s status value to determine whether the export was successful, failed, or was canceled:

    调用 exportAsynchronouslyWithCompletionHandler: 创建新的文件。当导出操作完成的时候完成处理的 block 被调用,你应该检查会话的 status 值,去判断导出是否成功、失败或者被取消。

        [exportSession exportAsynchronouslyWithCompletionHandler:^{
    
            switch ([exportSession status]) {
                case AVAssetExportSessionStatusFailed:
                    NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
                    break;
                case AVAssetExportSessionStatusCancelled:
                    NSLog(@"Export canceled");
                    break;
                default:
                    break;
            }
        }];

    You can cancel the export by sending the session a cancelExport message.

    The export will fail if you try to overwrite an existing file, or write a file outside of the application’s sandbox. It may also fail if:

    • There is an incoming phone call
    • Your application is in the background and another application starts playback

    In these situations, you should typically inform the user that the export failed, then allow the user to restart the export.

    你可以通过给会话发送一个 cancelExport 消息来取消导出。

    如果你尝试覆盖一个现有的文件或者在应用程序的沙盒外部写一个文件,都将会是导出失败。如果发生下面情况也可能失败:

    • 有一个来电
    • 你的应用程序在后台并且另一个程序开始播放

    在这种情况下,你通常应该通知用户导出失败,然后允许用户重新启动导出。

    Playback - 播放

    To control the playback of assets, you use an AVPlayer object. During playback, you can use an AVPlayerItem instance to manage the presentation state of an asset as a whole, and an AVPlayerItemTrack object to manage the presentation state of an individual track. To display video, you use an AVPlayerLayer object.

    使用 AVPlayer 对象控制资产的播放。在播放期间,可以使用一个 AVPlayerItem 实例去管理资产作为一个整体的显示状态,AVPlayerItemTrack 对象来管理一个单独轨道的显示状态。使用 AVPlayerLayer 显示视频。

    Playing Assets - 播放资产

    A player is a controller object that you use to manage playback of an asset, for example starting and stopping playback, and seeking to a particular time. You use an instance of AVPlayer to play a single asset. You can use an AVQueuePlayer object to play a number of items in sequence (AVQueuePlayer is a subclass of AVPlayer). On OS X you have the option of the using the AVKit framework’s AVPlayerView class to play the content back within a view.

    播放器是一个控制器对象,使用这个控制器对象去管理一个资产的播放,例如开始和停止播放,并且追踪一个特定的时间。使用 AVPlayer 的实例去播放单个资产。可以使用 AVQueuePlayer 对象去播放在一些在队列的项目(AVQueuePlayerAVPlayer 的子类)。在 OS X 系统中,可以选择使用 AVKit 框架的 AVPlayerView 类去播放一个视图的内容。

    A player provides you with information about the state of the playback so, if you need to, you can synchronize your user interface with the player’s state. You typically direct the output of a player to a specialized Core Animation layer (an instance of AVPlayerLayer or AVSynchronizedLayer). To learn more about layers, see Core Animation Programming Guide.

    播放器提供了关于播放状态的信息,因此如果需要,可以将用户界面与播放器的状态同步。通常将播放器的输出指向专门的动画核心层(AVPlayerLayer 或者 AVSynchronizedLayer 的一个实例)。想要了解更多关于 layers,请看 Core Animation Programming Guide

    Multiple player layers: You can create many AVPlayerLayer objects from a single AVPlayer instance, but only the most recently created such layer will display any video content onscreen.

    多个播放器层:可以从一个单独的 AVPlayer 实例创建许多 AVPlayerLayer 对象,但是只有最近被创建的那一层将会屏幕上显示视频的内容。

    Although ultimately you want to play an asset, you don’t provide assets directly to an AVPlayer object. Instead, you provide an instance of AVPlayerItem. A player item manages the presentation state of an asset with which it is associated. A player item contains player item tracks—instances of AVPlayerItemTrack—that correspond to the tracks in the asset. The relationship between the various objects is shown in Figure 2-1.

    虽然最终想要播放一个资产,但又没有直接给提供资产一个 AVPlayer 对象。相反,提供一个 AVPlayerItem 的实例。一个 player item 管理与它相关的资产的显示状态。一个player item包含了播放器项目轨道 – AVPlayerItemTrack—that 的实例,对应资产内的轨道。各个对象之间的关系如图2-1所示。


    Figure 2-1  Playing an asset

    This abstraction means that you can play a given asset using different players simultaneously, but rendered in different ways by each player. Figure 2-2 shows one possibility, with two different players playing the same asset, with different settings. Using the item tracks, you can, for example, disable a particular track during playback (for example, you might not want to play the sound component).

    这个摘要意味着可以同时使用不同的播放器播放一个给定的资产,但每个播放器都以不同的方式呈现。图2-2显示了一种可能性,同一个资产有两个不同的播放器,并且有不同的设定。可以使用不同的项目轨道,在播放期间禁用一个特定的轨道(例如,你可能不想播放这个声音组件)。


    Figure 2-2  Playing the same asset in different ways

    You can initialize a player item with an existing asset, or you can initialize a player item directly from a URL so that you can play a resource at a particular location (AVPlayerItem will then create and configure an asset for the resource). As with AVAsset, though, simply initializing a player item doesn’t necessarily mean it’s ready for immediate playback. You can observe (using key-value observing) an item’s status property to determine if and when it’s ready to play.

    可以用现有的资产初始化一个播放器项目,或者可以直接从一个 URL 初始化播放器项目,为了可以在一个特定位置播放一个资源(AVPlayerItem 将为资源创建和配置资产)。即使带着 AVAsset 简单地初始化一个播放器项目并不一定意味着它已经准备可以立即播放了。可以观察(使用 key-value observing])一个项目的 status 属性,以确定是否可以播放并且当已经准备好去播放。

    Handling Different Types of Asset - 处理不同类型的资产

    The way you configure an asset for playback may depend on the sort of asset you want to play. Broadly speaking, there are two main types: file-based assets, to which you have random access (such as from a local file, the camera roll, or the Media Library), and stream-based assets (HTTP Live Streaming format).

    配置一个准备播放的资产的方法可能取决于你想播放的资产的顺序。概括地说,主要由两种类型:基于文件的资产,可以随机访问(例如从一个本地文件,相机胶卷,或者媒体库),和基于流的资产(HTTP直播流媒体格式)。

    To load and play a file-based asset. There are several steps to playing a file-based asset:

    • Create an asset using AVURLAsset.
    • Create an instance of AVPlayerItem using the asset.
    • Associate the item with an instance of AVPlayer.
    • Wait until the item’s status property indicates that it’s ready to play (typically you use key-value observing to receive a notification when the status changes).

    This approach is illustrated in Putting It All Together: Playing a Video File Using AVPlayerLayer.

    To create and prepare an HTTP live stream for playback. Initialize an instance of AVPlayerItem using the URL. (You cannot directly create an AVAsset instance to represent the media in an HTTP Live Stream.)

    加载和播放一个基于文件的资产,播放基于文件的资产有几个步骤:

    • 使用 AVURLAsset 创建一个资产
    • 使用资产创建一个 AVPlayerItem 的实例
    • AVPlayer 的实例与项目联结
    • 等待,直到项目的 status 属性表明已经准备好播放了(通常当状态改变时,使用 key-value observing 接受通知)

    该方法的说明都在:Putting It All Together: Playing a Video File Using AVPlayerLayer

    创建和编写能够播放的HTTP直播流媒体。使用 URL 初始化一个 AVPlayerItem 的实例。(你不能直接创建一个 AVAsset 的实例去代表媒体在HTTP直播流中)

    NSURL *url = [NSURL URLWithString:@"<#Live stream URL#>];
    // You may find a test stream at <http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8>.
    self.playerItem = [AVPlayerItem playerItemWithURL:url];
    [playerItem addObserver:self forKeyPath:@"status" options:0 context:&ItemStatusContext];
    self.player = [AVPlayer playerWithPlayerItem:playerItem];

    When you associate the player item with a player, it starts to become ready to play. When it is ready to play, the player item creates the AVAsset and AVAssetTrack instances, which you can use to inspect the contents of the live stream.

    To get the duration of a streaming item, you can observe the duration property on the player item. When the item becomes ready to play, this property updates to the correct value for the stream.

    当你把播放项目和播放器联结起来时,它开始准备播放。当它准备播放时,播放项目创建 AVAssetAVAssetTrack 实例,可以用它来检查直播流的内容。

    获取一个流项目的持续时间,可以观察播放项目的 duration 属性。当项目准备就绪时,这个属性更新为流的正确值。

    Note: Using the duration property on the player item requires iOS 4.3 or later. An approach that is compatible with all versions of iOS involves observing the status property of the player item. When the status becomes AVPlayerItemStatusReadyToPlay, the duration can be fetched with the following line of code:

    注意:在播放项目里使用 duration 属性要求 iOS4.3 ,或者更高的版本。一种方法是所有版本的iOS兼容包括播放项目的 status 属性。当 status 变成 AVPlayerItemStatusReadyToPlay,持续时间可以被下面的代码获取到:

    [[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];

    If you simply want to play a live stream, you can take a shortcut and create a player directly using the URL use the following code:

    如果你只是想播放一个直播流,你可以采取一种快捷方式,并使用 URL 直接创建一个播放器,代码如下:

    self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
    [player addObserver:self forKeyPath:@"status" options:0 context:&PlayerStatusContext];

    As with assets and items, initializing the player does not mean it’s ready for playback. You should observe the player’s status property, which changes to AVPlayerStatusReadyToPlay when it is ready to play. You can also observe the currentItem property to access the player item created for the stream.

    作为资产和项目,初始化播放器并不意味着它已经准备就绪可以播放。你应该观察播放器的 status 属性,当准备就绪的时候改变 AVPlayerStatusReadyToPlay 。也可以观察 currentItem 属性去访问被流所创建播放项目。

    If you don’t know what kind of URL you have, follow these steps:

    • Try to initialize an AVURLAsset using the URL, then load its tracks key.
      If the tracks load successfully, then you create a player item for the asset.
    • If 1 fails, create an AVPlayerItem directly from the URL.
      Observe the player’s status property to determine whether it becomes playable.

    If either route succeeds, you end up with a player item that you can then associate with a player.

    如果你不知道现有的 URL 是什么类型的,按照下面步骤:

    • 尝试用 URL 初始化一个 AVURLAsset ,然后将其加载为轨道的 key
    • 如果上一步失败,直接从 URL 创建一个 AVPlayerItem 。观察这个播放器的 status 属性来决定它是否是可播放的。

    如果两个都可以成功,你最终用可以联结给一个播放器的播放项目。

    Playing an Item - 播放一个项目

    To start playback, you send a play message to the player.

    发送一个播放消息给播放器,开始播放:

    - (IBAction)play:sender {
        [player play];
    }

    In addition to simply playing, you can manage various aspects of the playback, such as the rate and the location of the playhead. You can also monitor the play state of the player; this is useful if you want to, for example, synchronize the user interface to the presentation state of the asset—see Monitoring Playback.

    除了简单的播放,可以管理播放的各个方面,如速度和播放头的位置。也可以监视播放器的播放状态;这是很有用的,例如如果你想将用户界面同步到资产的呈现状态 – 详情看:Monitoring Playback.

    Changing the Playback Rate - 改变播放的速率

    You change the rate of playback by setting the player’s rate property.

    通过发送播放器的 rate 属性来改变播放速率。

    aPlayer.rate = 0.5;
    aPlayer.rate = 2.0;

    A value of 1.0 means “play at the natural rate of the current item”. Setting the rate to 0.0 is the same as pausing playback—you can also use pause.

    值如果是 1.0 意味着“当前项目按正常速率播放”。将速率设置为 0.0 就和暂停播放一样了 – 也可以使用 pause

    Items that support reverse playback can use the rate property with a negative number to set the reverse playback rate. You determine the type of reverse play that is supported by using the playerItem properties canPlayReverse (supports a rate value of -1.0), canPlaySlowReverse (supports rates between 0.0 and 1.0) and canPlayFastReverse (supports rate values less than -1.0).

    支持逆向播放的项目可以使用带有负数 rate 属性,负数可以设置反向播放速率。确定反向播放的类型,通过使用 playerItem 属性 canPlayReverse (支持一个速率值 -1.0),canPlaySlowReverse (速率支持0.01.0)和 canPlayFastReverse (速率值可以小于 -1.0)。

    Seeking—Repositioning the Playhead - 寻找-重新定位播放头

    To move the playhead to a particular time, you generally use seekToTime: as follows:

    通常使用 seekToTime: 把播放头移动到一个指定的时间,示例:

    CMTime fiveSecondsIn = CMTimeMake(5, 1);
    [player seekToTime:fiveSecondsIn];

    The seekToTime: method, however, is tuned for performance rather than precision. If you need to move the playhead precisely, instead you use seekToTime:toleranceBefore:toleranceAfter: as in the following code fragment:

    然而 seekToTime: 方法是为了性能的调试,而不是精度。如果你需要精确的移动播放头,你需要使用 seekToTime:toleranceBefore:toleranceAfter: 代替,示例代码:

    CMTime fiveSecondsIn = CMTimeMake(5, 1);
    [player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];

    Using a tolerance of zero may require the framework to decode a large amount of data. You should use zero only if you are, for example, writing a sophisticated media editing application that requires precise control.

    After playback, the player’s head is set to the end of the item and further invocations of play have no effect. To position the playhead back at the beginning of the item, you can register to receive an AVPlayerItemDidPlayToEndTimeNotification notification from the item. In the notification’s callback method, you invoke seekToTime: with the argument kCMTimeZero.

    使用一个零的限制可能需要框架来解码大量的数据。例如应该只是用零编写一个复杂的需要精确控制的媒体编辑应用。

    播放之后,播放器的头被设置在项目的结尾处,接着进行播放的调用没有任何影响。将播放头放置在项目的开始位置,可以注册从项目接收一个 AVPlayerItemDidPlayToEndTimeNotification 消息。在消息的回调方法中,调用带着参数 kCMTimeZeroseekToTime: 方法。

    // Register with the notification center after creating the player item.
        [[NSNotificationCenter defaultCenter]
            addObserver:self
            selector:@selector(playerItemDidReachEnd:)
            name:AVPlayerItemDidPlayToEndTimeNotification
            object:<#The player item#>];
    
    - (void)playerItemDidReachEnd:(NSNotification *)notification {
        [player seekToTime:kCMTimeZero];
    }
    

    Playing Multiple Items - 播放多个项目

    You can use an AVQueuePlayer object to play a number of items in sequence. The AVQueuePlayer class is a subclass of AVPlayer. You initialize a queue player with an array of player items.

    可以使用 AVQueuePlayer 对象去播放队列中的一些项目。AVQueuePlayer 类是 AVPlayer 的子类。初始化一个带着播放项目数组的队列播放器:

    NSArray *items = <#An array of player items#>;
    AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];

    You can then play the queue using play, just as you would an AVPlayer object. The queue player plays each item in turn. If you want to skip to the next item, you send the queue player an advanceToNextItem message.

    可以使用 play 播放队列,就像你是一个 AVPlayer 对象。队列播放器依次播放每个项目。如果想要跳过这一项,给队列播放器发送一个 advanceToNextItem 信息。

    You can modify the queue using insertItem:afterItem:, removeItem:, and removeAllItems. When adding a new item, you should typically check whether it can be inserted into the queue, using canInsertItem:afterItem:. You pass nil as the second argument to test whether the new item can be appended to the queue.

    可以使用 insertItem:afterItem:removeItem:removeAllItems 这三个方法修改队列。当添加一个新项目,通常应该检查它是否可以被插入到队列中,使用 canInsertItem:afterItem:。传 nil 作为第二个参数去测试是否将新项目添加到队列中。

    AVPlayerItem *anItem = <#Get a player item#>;
    if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
        [queuePlayer insertItem:anItem afterItem:nil];
    }
    

    Monitoring Playback - 监视播放

    You can monitor a number of aspects of both the presentation state of a player and the player item being played. This is particularly useful for state changes that are not under your direct control. For example:

    • If the user uses multitasking to switch to a different application, a player’s rate property will drop to 0.0.
    • If you are playing remote media, a player item’s loadedTimeRanges and seekableTimeRanges properties will change as more data becomes available.

    These properties tell you what portions of the player item’s timeline are available.

    • A player’s currentItem property changes as a player item is created for an HTTP live stream.
    • A player item’s tracks property may change while playing an HTTP live stream.

    This may happen if the stream offers different encodings for the content; the tracks change if the player switches to a different encoding.

    • A player or player item’s status property may change if playback fails for some reason.

    You can use key-value observing to monitor changes to values of these properties.

    可以监视播放器的演示状态和正在播放的播放项目的很多方面的情况。状态的改变并不是在你的直接控制下,监视是非常有用的。例如:

    • 如果用户使用多任务处理切换到另一个应用程序,播放器的 rate 属性将下降到 0.0
    • 如果正在播放远程媒体,播放项目的 loadedTimeRangesseekableTimeRanges 属性将会改变使得更多的数据成为可用的。

    这些属性告诉你,播放项目时间轴的那一部分是可用的。

    • 播放器的 currentItem 属性变化,随着播放项目被 HTTP 直播流创建。
    • 当播放 HTTP 直播流时,播放项目的 tracks 属性可能会改变。

    如果流的内容提供了不同的编码上述情况就可能发生;如果用户切换到不同的编码轨道就改变了。

    • 如果因为一些原因播放失败,播放器或者播放项目的 status 属性可能会改变。

    可以使用 key-value observing 去监视这些属性值的改变。

    Important: You should register for KVO change notifications and unregister from KVO change notifications on the main thread. This avoids the possibility of receiving a partial notification if a change is being made on another thread. AV Foundation invokes observeValueForKeyPath:ofObject:change:context: on the main thread, even if the change operation is made on another thread.

    重要的是:你应该对 KVO 改变通知登记,从主线程中 KVO 改变通知而注销。如果在另一个线程上正在更改,这避免了只接受到部分通知的可能性。AV Foundation 在主线程中调用 observeValueForKeyPath:ofObject:change:context: ,即使改变操作是在另一个线程中。

    Responding to a Change in Status - 响应状态的变化

    When a player or player item’s status changes, it emits a key-value observing change notification. If an object is unable to play for some reason (for example, if the media services are reset), the status changes to AVPlayerStatusFailed or AVPlayerItemStatusFailed as appropriate. In this situation, the value of the object’s error property is changed to an error object that describes why the object is no longer be able to play.

    当一个播放器或者播放项目的 status 改变,它会发出一个 key-value observing 改变通知。如果一个对象由于一些原因不能播放(例如,如果媒体服务器复位),status 适当的改变为 AVPlayerStatusFailed 或者 AVPlayerItemStatusFailed。在这种情况下,对象的 error 属性的值被更改为一个错误对象,该对象描述了为什么对象不能播放了。

    AV Foundation does not specify what thread that the notification is sent on. If you want to update the user interface, you must make sure that any relevant code is invoked on the main thread. This example uses dispatch_async to execute code on the main thread.

    AV Foundation 没有指定通知发送的是什么线程。如果要更新用户界面,必须确保相关的代码都是在主线程被调用的。这个例子使用了 dispatch_async 去执行在主线程中的代码。

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                            change:(NSDictionary *)change context:(void *)context {
    
        if (context == <#Player status context#>) {
            AVPlayer *thePlayer = (AVPlayer *)object;
            if ([thePlayer status] == AVPlayerStatusFailed) {
                NSError *error = [<#The AVPlayer object#> error];
                // Respond to error: for example, display an alert sheet.
                return;
            }
            // Deal with other status change if appropriate.
        }
        // Deal with other change notifications if appropriate.
        [super observeValueForKeyPath:keyPath ofObject:object
               change:change context:context];
        return;
    }
    

    Tracking Readiness for Visual Display - 为视觉展示做追踪准备

    You can observe an AVPlayerLayer object’s readyForDisplay property to be notified when the layer has user-visible content. In particular, you might insert the player layer into the layer tree only when there is something for the user to look at and then perform a transition from.

    可以观察一个 AVPlayerLayer 对象的 readyForDisplay 属性,当层有了用户可见的内容时属性可以被通知。特别是,可能将播放器层插入到层树,只有当有东西给用户看的时候,在从里面执行一个转变。

    Tracking Time - 追踪时间

    To track changes in the position of the playhead in an AVPlayer object, you can use addPeriodicTimeObserverForInterval:queue:usingBlock: or addBoundaryTimeObserverForTimes:queue:usingBlock:. You might do this to, for example, update your user interface with information about time elapsed or time remaining, or perform some other user interface synchronization.

    • With addPeriodicTimeObserverForInterval:queue:usingBlock:, the block you provide is invoked at the interval you specify, if time jumps, and when playback starts or stops.
    • With addBoundaryTimeObserverForTimes:queue:usingBlock:, you pass an array of CMTime structures contained in NSValue objects. The block you provide is invoked whenever any of those times is traversed.

    追踪一个 AVPlayer 对象中播放头位置的变化,可以使用 addPeriodicTimeObserverForInterval:queue:usingBlock: 或者 addBoundaryTimeObserverForTimes:queue:usingBlock: 。可以这样做,例如更新用户界面与时间消耗或者剩余时间的有关信息,或者执行一些其他用户界面的同步。

    Both of the methods return an opaque object that serves as an observer. You must keep a strong reference to the returned object as long as you want the time observation block to be invoked by the player. You must also balance each invocation of these methods with a corresponding call to removeTimeObserver:.

    With both of these methods, AV Foundation does not guarantee to invoke your block for every interval or boundary passed. AV Foundation does not invoke a block if execution of a previously invoked block has not completed. You must make sure, therefore, that the work you perform in the block does not overly tax the system.

    这两种方法都返回一个作为观察者的不透明对象。只要你希望播放器调用时间观察的块,就必须对返回的对象保持一个强引用。你也必须平衡每次调用这些方法,与相应的调用 removeTimeObserver:.

    有了这两种方法, AV Foundation 不保证每个间隔或者通过边界时都调用你的块。如果以前调用的块执行没有完成,AV Foundation不会调用块。因此必须确保你在该块中执行的工作不会对系统过载。

    // Assume a property: @property (strong) id playerObserver;
    
    Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
    CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
    CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
    NSArray *times = @[[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird]];
    
    self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
    
        NSString *timeDescription = (NSString *)
            CFBridgingRelease(CMTimeCopyDescription(NULL, [self.player currentTime]));
        NSLog(@"Passed a boundary at %@", timeDescription);
    }];

    Reaching the End of an Item - 到达一个项目的结束

    You can register to receive an AVPlayerItemDidPlayToEndTimeNotification notification when a player item has completed playback.

    当一个播放项目已经完成播放的时候,可以注册接收一个 AVPlayerItemDidPlayToEndTimeNotification 通知。

    [[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
                                             selector:@selector(<#The selector name#>)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:<#A player item#>];
    

    Putting It All Together: Playing a Video File Using AVPlayerLayer - 总而言之,使用 AVPlayerLayer 播放视频文件

    This brief code example illustrates how you can use an AVPlayer object to play a video file. It shows how to:

    • Configure a view to use an AVPlayerLayer layer
    • Create an AVPlayer object
    • Create an AVPlayerItem object for a file-based asset and use key-value observing to observe its status
    • Respond to the item becoming ready to play by enabling a button
    • Play the item and then restore the player’s head to the beginning

    这个简短的代码示例演示如何使用一个 AVPlayer 对象播放一个视频文件。它显示了如何:

    • 使用 AVPlayerLayer 层配置视图
    • 创建一个 AVPlayer 对象
    • 创建一个基于文件资产的 AVPlayerItem 对象和使用 key-value observing 去观察它的状态
    • 通过启用按钮来响应项目准备就绪播放
    • 播放项目,然后将播放器的头重置到开始位置

    Note: To focus on the most relevant code, this example omits several aspects of a complete application, such as memory management and unregistering as an observer (for key-value observing or for the notification center). To use AV Foundation, you are expected to have enough experience with Cocoa to be able to infer the missing pieces.

    注意:关注最相关的代码,这个例子中省略了一个完整应用程序的几个方面,比如内存管理和注销观察者(key-value observing 或者 notification center)。为了使用 AV Foundation ,你应该有足够的 Cocoa 经验,有能力去推断出丢失的碎片。

    For a conceptual introduction to playback, skip to Playing Assets.

    对于播放的概念性的介绍,跳去看 Playing Assets

    The Player View - 播放器视图

    To play the visual component of an asset, you need a view containing an AVPlayerLayer layer to which the output of an AVPlayer object can be directed. You can create a simple subclass of UIView to accommodate this:

    播放一个资产的可视化部分,需要一个包含了 AVPlayerLayer 层的视图,AVPlayerLayer 层可以直接输出 AVPlayer 对象。可以创建一个 UIView 的简单子类来容纳:

    #import <UIKit/UIKit.h>
    #import <AVFoundation/AVFoundation.h>
    
    @interface PlayerView : UIView
    @property (nonatomic) AVPlayer *player;
    @end
    
    @implementation PlayerView
    + (Class)layerClass {
        return [AVPlayerLayer class];
    }
    - (AVPlayer*)player {
        return [(AVPlayerLayer *)[self layer] player];
    }
    - (void)setPlayer:(AVPlayer *)player {
        [(AVPlayerLayer *)[self layer] setPlayer:player];
    }
    @end
    

    A Simple View Controller - 一个简单的 View Controller

    Assume you have a simple view controller, declared as follows:

    假设你有一个简单的 view controller,声明如下:

    @class PlayerView;
    @interface PlayerViewController : UIViewController
    
    @property (nonatomic) AVPlayer *player;
    @property (nonatomic) AVPlayerItem *playerItem;
    @property (nonatomic, weak) IBOutlet PlayerView *playerView;
    @property (nonatomic, weak) IBOutlet UIButton *playButton;
    - (IBAction)loadAssetFromFile:sender;
    - (IBAction)play:sender;
    - (void)syncUI;
    @end

    The syncUI method synchronizes the button’s state with the player’s state:

    syncUI 方法同步按钮状态和播放器的状态:

    - (void)syncUI {
        if ((self.player.currentItem != nil) &&
            ([self.player.currentItem status] == AVPlayerItemStatusReadyToPlay)) {
            self.playButton.enabled = YES;
        }
        else {
            self.playButton.enabled = NO;
        }
    }

    You can invoke syncUI in the view controller’s viewDidLoad method to ensure a consistent user interface when the view is first displayed.

    当视图第一次显示的时候,可以在视图控制器的 viewDidLoad 方法中调用 invoke 去确保用户界面的一致性。

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self syncUI];
    }

    The other properties and methods are described in the remaining sections.

    在其余章节描述其他属性和方法。

    Creating the Asset - 创建一个资产

    You create an asset from a URL using AVURLAsset. (The following example assumes your project contains a suitable video resource.)

    使用 AVURLAsset 从一个 URL 创建一个资产。(下面的例子假设你的工程包含了一个合适的视频资源)

    - (IBAction)loadAssetFromFile:sender {
    
        NSURL *fileURL = [[NSBundle mainBundle]
            URLForResource:<#@"VideoFileName"#> withExtension:<#@"extension"#>];
    
        AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
        NSString *tracksKey = @"tracks";
    
        [asset loadValuesAsynchronouslyForKeys:@[tracksKey] completionHandler:
         ^{
             // The completion block goes here.
         }];
    }

    In the completion block, you create an instance of AVPlayerItem for the asset and set it as the player for the player view. As with creating the asset, simply creating the player item does not mean it’s ready to use. To determine when it’s ready to play, you can observe the item’s status property. You should configure this observing before associating the player item instance with the player itself.

    You trigger the player item’s preparation to play when you associate it with the player.

    在完成块中,为资产创建一个 AVPlayerItem 的实例,并设置它为播放页面的播放器。与创建资产一样,简单地创建播放器项目并不意味着它已经准备好使用。为了确定它已经准备好了,可以观察项目的 status 属性。你应该在该播放器项目实例与播放器本身关联之前,配置这个 observing

    当你将它与播放器连接时,就是触发播放项目的播放准备。

    // Define this constant for the key-value observation context.
    static const NSString *ItemStatusContext;
    
    // Completion handler block.
             dispatch_async(dispatch_get_main_queue(),
                ^{
                    NSError *error;
                    AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];
    
                    if (status == AVKeyValueStatusLoaded) {
                        self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
                         // ensure that this is done before the playerItem is associated with the player
                        [self.playerItem addObserver:self forKeyPath:@"status"
                                    options:NSKeyValueObservingOptionInitial context:&ItemStatusContext];
                        [[NSNotificationCenter defaultCenter] addObserver:self
                                                                  selector:@selector(playerItemDidReachEnd:)
                                                                      name:AVPlayerItemDidPlayToEndTimeNotification
                                                                    object:self.playerItem];
                        self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
                        [self.playerView setPlayer:self.player];
                    }
                    else {
                        // You should deal with the error appropriately.
                        NSLog(@"The asset's tracks were not loaded:\n%@", [error localizedDescription]);
                    }
                });

    Responding to the Player Item’s Status Change - 相应播放项目的状态改变

    When the player item’s status changes, the view controller receives a key-value observing change notification. AV Foundation does not specify what thread that the notification is sent on. If you want to update the user interface, you must make sure that any relevant code is invoked on the main thread. This example uses dispatch_async to queue a message on the main thread to synchronize the user interface.

    当播放项目的状态改变时,视图控制器接收一个 key-value observing 改变通知。AV Foundation 没有指定通知发送的是什么线程。如果你想更新用户界面,必须确保任何相关的代码都要在主线程中调用。这个例子使用 dispatch_async 让主线程同步用户界面的消息进入队列。

    Playing the Item - 播放项目

    Playing the item involves sending a play message to the player.

    播放项目涉及到想播放器发送一个播放消息。

    - (IBAction)play:sender {
        [player play];
    }

    The item is played only once. After playback, the player’s head is set to the end of the item, and further invocations of the play method will have no effect. To position the playhead back at the beginning of the item, you can register to receive an AVPlayerItemDidPlayToEndTimeNotification from the item. In the notification’s callback method, invoke seekToTime: with the argument kCMTimeZero.

    该项目只播放一次。播放之后,播放器的头被设置在项目的结束位置,播放方法进一步调用将没有效果。将播放头放在项目的开始,可以注册从项目去接收 AVPlayerItemDidPlayToEndTimeNotification。在通知的回调方法,调用带着参数 kCMTimeZeroseekToTime: 方法。

    // Register with the notification center after creating the player item.
        [[NSNotificationCenter defaultCenter]
            addObserver:self
            selector:@selector(playerItemDidReachEnd:)
            name:AVPlayerItemDidPlayToEndTimeNotification
            object:[self.player currentItem]];
    
    - (void)playerItemDidReachEnd:(NSNotification *)notification {
        [self.player seekToTime:kCMTimeZero];
    }

    Editing - 编辑

    The AVFoundation framework provides a feature-rich set of classes to facilitate the editing of audio visual assets. At the heart of AVFoundation’s editing API are compositions. A composition is simply a collection of tracks from one or more different media assets. The AVMutableComposition class provides an interface for inserting and removing tracks, as well as managing their temporal orderings. Figure 3-1 shows how a new composition is pieced together from a combination of existing assets to form a new asset. If all you want to do is merge multiple assets together sequentially into a single file, that is as much detail as you need. If you want to perform any custom audio or video processing on the tracks in your composition, you need to incorporate an audio mix or a video composition, respectively.

    AVFoundation 框架提供了一个功能丰富的类集合去帮助音视频资产的编辑。 AVFoundation
    编辑 API 的核心是一些组合。一种组合物是简单的一个或者多个不同媒体资产的轨道的集合。AVMutableComposition 类提供一个可以插入和移除轨道的接口,以及管理它们的时间序列。图3-1显示了一个新的组合是怎样从一些现有的资产拼凑起来,形成新的资产。如果你想做的是将多个资产合并为一个单一的文件,这里有尽可能多的你需要掌握的细节。如果你想在你的作品中的轨道上执行任何自定义音频或视频处理,你需要分别将一个音频组合或者视频组成。


    Figure 3-1  AVMutableComposition assembles assets together

    Using the AVMutableAudioMix class, you can perform custom audio processing on the audio tracks in your composition, as shown in Figure 3-2. Currently, you can specify a maximum volume or set a volume ramp for an audio track.

    使用 AVMutableAudioMix 类,可以在你作品的音频轨道中执行自定义处理,如图3-2所示。目前,你可以指定一个最大音量或设置一个音频轨道的音量斜坡


    Figure 3-2  AVMutableAudioMix performs audio mixing

    You can use the AVMutableVideoComposition class to work directly with the video tracks in your composition for the purposes of editing, shown in Figure 3-3. With a single video composition, you can specify the desired render size and scale, as well as the frame duration, for the output video. Through a video composition’s instructions (represented by the AVMutableVideoCompositionInstruction class), you can modify the background color of your video and apply layer instructions. These layer instructions (represented by the AVMutableVideoCompositionLayerInstruction class) can be used to apply transforms, transform ramps, opacity and opacity ramps to the video tracks within your composition. The video composition class also gives you the ability to introduce effects from the Core Animation framework into your video using the animationTool property.

    可以使用 AVMutableVideoComposition 类直接在视频中跟踪你想编辑的部分,如图3-3所示。一个单一的视频组件,可以为输出视频指定所需的渲染大小和规模,以及帧的持续时间。通过视频组件的指令(以 AVMutableVideoCompositionInstruction 类为代表),你可以修改视频的背景颜色和应用层的指令。这些层的指令(以 AVMutableVideoCompositionLayerInstruction 类为代表)可以可应用于应用变换,变换坡道,不透明度以及不透明度的坡道到你的组件中的视频轨道。视频组件类也能让你做一些事,从核心动画框架到使用 animationTool 属性的视频。


    Figure 3-3  AVMutableVideoComposition

    To combine your composition with an audio mix and a video composition, you use an AVAssetExportSession object, as shown in Figure 3-4. You initialize the export session with your composition and then simply assign your audio mix and video composition to the audioMix and videoComposition properties respectively.

    将音频和视频的成分组合,可以使用 AVAssetExportSession 对象,如图3-4所所示。初始化导出会话,然后简单的分别将音频部分和视频组件分配给 audioMixvideoComposition 属性。


    Figure 3-4  Use AVAssetExportSession to combine media elements into an output file

    Creating a Composition - 创建组件

    To create your own composition, you use the AVMutableComposition class. To add media data to your composition, you must add one or more composition tracks, represented by the AVMutableCompositionTrack class. The simplest case is creating a mutable composition with one video track and one audio track:

    使用 AVMutableComposition 类创建自己的组件。在你的组件中添加媒体数据,必须添加一个或者多个组件轨道,以 AVMutableCompositionTrack 类为代表。最简单的例子创建一个有一个音频轨道和一个视频轨道的可变组件。

    AVMutableComposition *mutableComposition = [AVMutableComposition composition];
    // Create the video composition track.
    AVMutableCompositionTrack *mutableCompositionVideoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    // Create the audio composition track.
    AVMutableCompositionTrack *mutableCompositionAudioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    

    Options for Initializing a Composition Track - 初始化组件轨道的选项

    When adding new tracks to a composition, you must provide both a media type and a track ID. Although audio and video are the most commonly used media types, you can specify other media types as well, such as AVMediaTypeSubtitle or AVMediaTypeText.

    Every track associated with some audiovisual data has a unique identifier referred to as a track ID. If you specify kCMPersistentTrackID_Invalid as the preferred track ID, a unique identifier is automatically generated for you and associated with the track.

    当给轨道添加一个新的轨道时,必须提供媒体类型和轨道 ID 。虽然音频和视频是最常用的媒体类型,你可以指定其他媒体类型,比如 AVMediaTypeSubtitle 或者 AVMediaTypeText

    每个和视听数据相关联的轨道都有一个唯一的标示符,叫做 track ID。如果你指定了 kCMPersistentTrackID_Invalid 作为首先的 track ID,将会为你生成一个唯一的标示符并且与轨道相关联。

    Adding Audiovisual Data to a Composition - 将视听数据添加到一个组件中

    Once you have a composition with one or more tracks, you can begin adding your media data to the appropriate tracks. To add media data to a composition track, you need access to the AVAsset object where the media data is located. You can use the mutable composition track interface to place multiple tracks with the same underlying media type together on the same track. The following example illustrates how to add two different video asset tracks in sequence to the same composition track:

    一旦有带着一个或多个轨道的组件,就可以把你的媒体数据添加到适当的轨道中。为了将媒体数据添加到组件轨道,需要访问媒体数据所在位置的 AVAsset 对象。可以使用可变组件轨道接口将有相同基础的媒体类型的多个轨道放置到一个轨道上。下面的示例演示了如何将一个队列中两个不同的音频资产轨道添加到同一个组件轨道中。

    // You can retrieve AVAssets from a number of places, like the camera roll for example.
    AVAsset *videoAsset = <#AVAsset with at least one video track#>;
    AVAsset *anotherVideoAsset = <#another AVAsset with at least one video track#>;
    // Get the first video track from each asset.
    AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVAssetTrack *anotherVideoAssetTrack = [[anotherVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    // Add them both to the composition.
    [mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];
    [mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,anotherVideoAssetTrack.timeRange.duration) ofTrack:anotherVideoAssetTrack atTime:videoAssetTrack.timeRange.duration error:nil];
    

    Retrieving Compatible Composition Tracks - 检索兼容的组件轨道

    Where possible, you should have only one composition track for each media type. This unification of compatible asset tracks leads to a minimal amount of resource usage. When presenting media data serially, you should place any media data of the same type on the same composition track. You can query a mutable composition to find out if there are any composition tracks compatible with your desired asset track:

    在可能的情况下,每个媒体类型应该只有一个组件轨道。这种统一兼容的资产轨道可以达到最小的资源使用量。当串行显示媒体数据时,应该将相同类型的媒体数据放置在相同的组件轨道上。你可以查询一个可变组件,找出是否有组件轨道与你想要的资产轨道兼容。

    AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:<#the AVAssetTrack you want to insert#>];
    if (compatibleCompositionTrack) {
        // Implementation continues.
    }

    Note: Placing multiple video segments on the same composition track can potentially lead to dropping frames at the transitions between video segments, especially on embedded devices. Choosing the number of composition tracks for your video segments depends entirely on the design of your app and its intended platform.

    注意:在相同的组件轨道放置多个视频片段,可能会导致在视频片段之间的转换会掉帧,尤其是在嵌入式设备下。你的视频片段的组件轨道数量取决于你的应用程序预期和它的平台设计。

    Generating a Volume Ramp - 生成一个音量坡度

    A single AVMutableAudioMix object can perform custom audio processing on all of the audio tracks in your composition individually. You create an audio mix using the audioMix class method, and you use instances of the AVMutableAudioMixInputParameters class to associate the audio mix with specific tracks within your composition. An audio mix can be used to vary the volume of an audio track. The following example displays how to set a volume ramp on a specific audio track to slowly fade the audio out over the duration of the composition:

    一个单独的 AVMutableAudioMix 对象可以分别执行自定义音频,处理组件中的所有轨道。可以使用 audioMix 类方法创建一个音频混合,使用 AVMutableAudioMixInputParameters 类的实例将混合音频与组件中指定的轨道联结起来。一个混合音频可以用来改变音频轨道的音量。下面的例子展示了,如何在一个指定的音频轨道设置一个音量坡度,使得在组件的持续时间让音频缓慢淡出:

    AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
    // Create the audio mix input parameters object.
    AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mutableCompositionAudioTrack];
    // Set the volume ramp to slowly fade the audio out over the duration of the composition.
    [mixParameters setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)];
    // Attach the input parameters to the audio mix.
    mutableAudioMix.inputParameters = @[mixParameters];
    

    Performing Custom Video Processing - 执行自定义配置

    As with an audio mix, you only need one AVMutableVideoComposition object to perform all of your custom video processing on your composition’s video tracks. Using a video composition, you can directly set the appropriate render size, scale, and frame rate for your composition’s video tracks. For a detailed example of setting appropriate values for these properties, see Setting the Render Size and Frame Duration.

    作为一个混合音频,只需要一个 AVMutableVideoComposition 对象就可以执行组件音频轨道中的所有自定义音频配置。使用一个音频组件,可以直接为组件音频轨道设置适当的渲染大小,规模以及帧速率。有一个设置这些属性值的详细的示例,请看 Setting the Render Size and Frame Duration

    Changing the Composition’s Background Color - 改变组件的背景颜色

    All video compositions must also have an array of AVVideoCompositionInstruction objects containing at least one video composition instruction. You use the AVMutableVideoCompositionInstruction class to create your own video composition instructions. Using video composition instructions, you can modify the composition’s background color, specify whether post processing is needed or apply layer instructions.

    The following example illustrates how to create a video composition instruction that changes the background color to red for the entire composition.

    所有的视频组件必须有一个 AVVideoCompositionInstruction 对象的数组,每个对象至少包含一个视频组件指令。使用 AVMutableVideoCompositionInstruction 类去创建自己的视频组件指令。使用视频组件指令,可以修改组件的背景颜色,指定是否需要处理推迟处理或者应用到层指令。

    下面的例子展示了如果创建一个视频组件指令,将整个组件的背景颜色改为红色。

    AVMutableVideoCompositionInstruction *mutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    mutableVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition.duration);
    mutableVideoCompositionInstruction.backgroundColor = [[UIColor redColor] CGColor];
    

    Applying Opacity Ramps - 应用不透明的坡道

    Video composition instructions can also be used to apply video composition layer instructions. An AVMutableVideoCompositionLayerInstruction object can apply transforms, transform ramps, opacity and opacity ramps to a certain video track within a composition. The order of the layer instructions in a video composition instruction’s layerInstructions array determines how video frames from source tracks should be layered and composed for the duration of that composition instruction. The following code fragment shows how to set an opacity ramp to slowly fade out the first video in a composition before transitioning to the second video:

    视频组件指令可以用于视频组件层指令。一个 AVMutableVideoCompositionLayerInstruction 对象可以应用转换,转换坡道,不透明度和坡道的不透明度到某个组件内的视频轨道。视频组件指令的 layerInstructions 数组中 层指令的顺序决定了组件指令期间,资源轨道中的视频框架应该如何被应用和组合。下面的代码展示了如何设置一个不透明的坡度使得第二个视频之前,让第一个视频慢慢淡出:

    AVAsset *firstVideoAssetTrack = <#AVAssetTrack representing the first video segment played in the composition#>;
    AVAsset *secondVideoAssetTrack = <#AVAssetTrack representing the second video segment played in the composition#>;
    // Create the first video composition instruction.
    AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    // Set its time range to span the duration of the first video track.
    firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
    // Create the layer instruction and associate it with the composition video track.
    AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
    // Create the opacity ramp to fade out the first video track over its entire duration.
    [firstVideoLayerInstruction setOpacityRampFromStartOpacity:1.f toEndOpacity:0.f timeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration)];
    // Create the second video composition instruction so that the second video track isn't transparent.
    AVMutableVideoCompositionInstruction *secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    // Set its time range to span the duration of the second video track.
    secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
    // Create the second layer instruction and associate it with the composition video track.
    AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
    // Attach the first layer instruction to the first video composition instruction.
    firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
    // Attach the second layer instruction to the second video composition instruction.
    secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
    // Attach both of the video composition instructions to the video composition.
    AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
    mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

    Incorporating Core Animation Effects - 结合核心动画效果

    A video composition can add the power of Core Animation to your composition through the animationTool property. Through this animation tool, you can accomplish tasks such as watermarking video and adding titles or animating overlays. Core Animation can be used in two different ways with video compositions: You can add a Core Animation layer as its own individual composition track, or you can render Core Animation effects (using a Core Animation layer) into the video frames in your composition directly. The following code displays the latter option by adding a watermark to the center of the video:

    一个视频组件可以通过 animationTool 属性将核心动画的力量添加到你的组件中。通过这个动画制作工具,可以完成一些任务,例如视频水印,添加片头或者动画覆盖。核心动画可以有两种不同的方式被用于视频组件:可以添加一个核心动画层到自己的个人组件轨道,或者可以渲染核心动画效果(使用一个核心动画层)直接进入组件的视频框架。下面的代码展示了在视频中央添加一个水印显示出来的效果。

    CALayer *watermarkLayer = <#CALayer representing your desired watermark image#>;
    CALayer *parentLayer = [CALayer layer];
    CALayer *videoLayer = [CALayer layer];
    parentLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
    videoLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
    [parentLayer addSublayer:videoLayer];
    watermarkLayer.position = CGPointMake(mutableVideoComposition.renderSize.width/2, mutableVideoComposition.renderSize.height/4);
    [parentLayer addSublayer:watermarkLayer];
    mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
    

    Putting It All Together: Combining Multiple Assets and Saving the Result to the Camera Roll -

    This brief code example illustrates how you can combine two video asset tracks and an audio asset track to create a single video file. It shows how to:

    • Create an AVMutableComposition object and add multiple AVMutableCompositionTrack objects
    • Add time ranges of AVAssetTrack objects to compatible composition tracks
    • Check the preferredTransform property of a video asset track to determine the video’s orientation
    • Use AVMutableVideoCompositionLayerInstruction objects to apply transforms to the video tracks within - a composition
    • Set appropriate values for the renderSize and frameDuration properties of a video composition
    • Use a composition in conjunction with a video composition when exporting to a video file
    • Save a video file to the Camera Roll

    这个简短的代码示例说明了如何将两个视频资产轨道和一个音频资产轨道结合起来,创建一个单独的视频文件。有下面几个方面:

    Note: To focus on the most relevant code, this example omits several aspects of a complete app, such as memory management and error handling. To use AVFoundation, you are expected to have enough experience with Cocoa to infer the missing pieces.

    注意:关注最相关的代码,这个例子省略了一个完整应用程序的几个方面,如内存处理和错误处理。利用 AVFoundation ,希望你有足够的使用 Cocoa 的经验去判断丢失的碎片

    Creating the Composition - 创建组件

    To piece together tracks from separate assets, you use an AVMutableComposition object. Create the composition and add one audio and one video track.

    使用 AVMutableComposition 对象将分离的资产拼凑成轨道。创建组件并且添加一个音频轨道和一个视频轨道。

    AVMutableComposition *mutableComposition = [AVMutableComposition composition];
    AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    

    Adding the Assets - 添加资产

    An empty composition does you no good. Add the two video asset tracks and the audio asset track to the composition.

    一个空的资产并不是好。往组件中添加两个视频资产轨道和音频资产轨道。

    AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil];
    [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil];
    [audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration)) ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];

    Note: This assumes that you have two assets that contain at least one video track each and a third asset that contains at least one audio track. The videos can be retrieved from the Camera Roll, and the audio track can be retrieved from the music library or the videos themselves.

    注意:这里假定你有两个资产,每个资产中都至少包含一个视频轨道,第三个资产至少包含一个音频轨道。视频可以从相机胶卷中检索到,音频轨道可以从音乐库或者视频本身检索到。

    Checking the Video Orientations - 检查视频的方向

    Once you add your video and audio tracks to the composition, you need to ensure that the orientations of both video tracks are correct. By default, all video tracks are assumed to be in landscape mode. If your video track was taken in portrait mode, the video will not be oriented properly when it is exported. Likewise, if you try to combine a video shot in portrait mode with a video shot in landscape mode, the export session will fail to complete.

    一旦给组件添加了音频和视频轨道,你需要确保两个视频轨道的方向都是正确的。默认情况下,所有的视频轨道都被假定为横向模式。如果你的视频轨道是在纵向模式下拍摄的,当它被导出的时候方向将出现错误。同样,如果你尝试将横向模式下拍摄的视频与纵向的视频结合在一起,导出会话将无法完成。

    BOOL isFirstVideoPortrait = NO;
    CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform;
    // Check the first video track's preferred transform to determine if it was recorded in portrait mode.
    if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) {
        isFirstVideoPortrait = YES;
    }
    BOOL isSecondVideoPortrait = NO;
    CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform;
    // Check the second video track's preferred transform to determine if it was recorded in portrait mode.
    if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) {
        isSecondVideoPortrait = YES;
    }
    if ((isFirstVideoAssetPortrait && !isSecondVideoAssetPortrait) || (!isFirstVideoAssetPortrait && isSecondVideoAssetPortrait)) {
        UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
        [incompatibleVideoOrientationAlert show];
        return;
    }

    Applying the Video Composition Layer Instructions - 视频组件层指令的应用

    Once you know the video segments have compatible orientations, you can apply the necessary layer instructions to each one and add these layer instructions to the video composition.

    如果你知道视频片段对方向有兼容性,可以将必要的层指令应用到每个视频片段,并将这些层指令添加到视频组合中。

    AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    // Set the time range of the first instruction to span the duration of the first video track.
    firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
    AVMutableVideoCompositionInstruction * secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    // Set the time range of the second instruction to span the duration of the second video track.
    secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
    AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
    // Set the transform of the first layer instruction to the preferred transform of the first video track.
    [firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero];
    AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
    // Set the transform of the second layer instruction to the preferred transform of the second video track.
    [secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration];
    firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
    secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
    AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
    mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

    All AVAssetTrack objects have a preferredTransform property that contains the orientation information for that asset track. This transform is applied whenever the asset track is displayed onscreen. In the previous code, the layer instruction’s transform is set to the asset track’s transform so that the video in the new composition displays properly once you adjust its render size.

    所有的 AVAssetTrack 对象都有一个 preferredTransform 属性,包含了资产轨道的方向信息。当资产轨道被展示到屏幕上时就进行这些转换。在之前的代码中,层指令信息的转换被设置为资产轨道的转换,使得一旦你调整了它的渲染大小,视频在新的组件中都能正确的显示。

    Setting the Render Size and Frame Duration - 设置渲染大小和帧周期

    To complete the video orientation fix, you must adjust the renderSize property accordingly. You should also pick a suitable value for the frameDuration property, such as 1/30th of a second (or 30 frames per second). By default, the renderScale property is set to 1.0, which is appropriate for this composition.

    为了完成视频方向的固定,必须调整相应的 renderSize 属性。也应该给 frameDuration 属性设置一个合适的值,比如 1/30th of a second (或者每秒30帧)。默认情况下,renderScale 属性设置 1.0,对于组件是比较合适的。

    CGSize naturalSizeFirst, naturalSizeSecond;
    // If the first video asset was shot in portrait mode, then so was the second one if we made it here.
    if (isFirstVideoAssetPortrait) {
    // Invert the width and height for the video tracks to ensure that they display properly.
        naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width);
        naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width);
    }
    else {
    // If the videos weren't shot in portrait mode, we can just use their natural sizes.
        naturalSizeFirst = firstVideoAssetTrack.naturalSize;
        naturalSizeSecond = secondVideoAssetTrack.naturalSize;
    }
    float renderWidth, renderHeight;
    // Set the renderWidth and renderHeight to the max of the two videos widths and heights.
    if (naturalSizeFirst.width > naturalSizeSecond.width) {
        renderWidth = naturalSizeFirst.width;
    }
    else {
        renderWidth = naturalSizeSecond.width;
    }
    if (naturalSizeFirst.height > naturalSizeSecond.height) {
        renderHeight = naturalSizeFirst.height;
    }
    else {
        renderHeight = naturalSizeSecond.height;
    }
    mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight);
    // Set the frame duration to an appropriate value (i.e. 30 frames per second for video).
    mutableVideoComposition.frameDuration = CMTimeMake(1,30);
    

    Exporting the Composition and Saving it to the Camera Roll - 导出组件并存到相机胶卷

    The final step in this process involves exporting the entire composition into a single video file and saving that video to the camera roll. You use an AVAssetExportSession object to create the new video file and you pass to it your desired URL for the output file. You can then use the ALAssetsLibrary class to save the resulting video file to the Camera Roll.

    这个过程的最后一步,是将整个组件导出到一个单独的视频文件,并且将视频存到相机胶卷中。使用 AVAssetExportSession 对象去创建新的视频文件,并且给输出文件传递一个期望的 URL 。然后可以使用 ALAssetsLibrary 类去将视频文件结果保存到相机胶卷。

    // Create a static date formatter so we only have to initialize it once.
    static NSDateFormatter *kDateFormatter;
    if (!kDateFormatter) {
        kDateFormatter = [[NSDateFormatter alloc] init];
        kDateFormatter.dateStyle = NSDateFormatterMediumStyle;
        kDateFormatter.timeStyle = NSDateFormatterShortStyle;
    }
    // Create the export session with the composition and set the preset to the highest quality.
    AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality];
    // Set the desired output URL for the file created by the export process.
    exporter.outputURL = [[[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:@YES error:nil] URLByAppendingPathComponent:[kDateFormatter stringFromDate:[NSDate date]]] URLByAppendingPathExtension:CFBridgingRelease(UTTypeCopyPreferredTagWithClass((CFStringRef)AVFileTypeQuickTimeMovie, kUTTagClassFilenameExtension))];
    // Set the output file type to be a QuickTime movie.
    exporter.outputFileType = AVFileTypeQuickTimeMovie;
    exporter.shouldOptimizeForNetworkUse = YES;
    exporter.videoComposition = mutableVideoComposition;
    // Asynchronously export the composition to a video file and save this file to the camera roll once export completes.
    [exporter exportAsynchronouslyWithCompletionHandler:^{
        dispatch_async(dispatch_get_main_queue(), ^{
            if (exporter.status == AVAssetExportSessionStatusCompleted) {
                ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
                if ([assetsLibrary videoAtPathIsCompatibleWithSavedPhotosAlbum:exporter.outputURL]) {
                    [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:exporter.outputURL completionBlock:NULL];
                }
            }
        });
    }];

    Still and Video Media Capture - 静态视频媒体捕获。

    To manage the capture from a device such as a camera or microphone, you assemble objects to represent inputs and outputs, and use an instance of AVCaptureSession to coordinate the data flow between them. Minimally you need:

    • An instance of AVCaptureDevice to represent the input device, such as a camera or microphone
    • An instance of a concrete subclass of AVCaptureInput to configure the ports from the input device
    • An instance of a concrete subclass of AVCaptureOutput to manage the output to a movie file or still image
    • An instance of AVCaptureSession to coordinate the data flow from the input to the output

    从一个设备,例如照相机或者麦克风管理捕获,组合对象来表示输入和输出,并使用 AVCaptureSession 的实例来协调它们之间的数据流。你需要最低限度的了解:

    To show the user a preview of what the camera is recording, you can use an instance of AVCaptureVideoPreviewLayer (a subclass of CALayer).

    You can configure multiple inputs and outputs, coordinated by a single session, as shown in Figure 4-1

    为了向用户展示照相机之前记录的预览,可以使用 AVCaptureVideoPreviewLayer 的实例(CALayer 的一个子类)

    可以配置多个输入和输出,由一个单独的会话协调。如图4-1所示:


    Figure 4-1  A single session can configure multiple inputs and outputs

    For many applications, this is as much detail as you need. For some operations, however, (if you want to monitor the power levels in an audio channel, for example) you need to consider how the various ports of an input device are represented and how those ports are connected to the output.

    对于大多数程序,这有尽可能多的你需要知道的细节。然而对于某些操作(例如如果你想监视音频信道中的功率水平),需要考虑输入设备的各种端口如何表示,以及这些端口是如何连接到输出的。

    A connection between a capture input and a capture output in a capture session is represented by an AVCaptureConnection object. Capture inputs (instances of AVCaptureInput) have one or more input ports (instances of AVCaptureInputPort). Capture outputs (instances of AVCaptureOutput) can accept data from one or more sources (for example, an AVCaptureMovieFileOutput object accepts both video and audio data).

    捕获输入和捕获输出的会话之间的连接表现为 AVCaptureConnection 对象。捕获输入(AVCaptureInput的实例)有一个或多个输入端口(AVCaptureInputPort的实例)。捕获输出(AVCaptureOutput的实例)可以从一个或多个资源接受数据(例如,AVCaptureMovieFileOutput 对象接受音频和视频数据)。

    When you add an input or an output to a session, the session forms connections between all the compatible capture inputs’ ports and capture outputs, as shown in Figure 4-2. A connection between a capture input and a capture output is represented by an AVCaptureConnection object.

    当给会话添加一个输入或者一个输出时,会话构成了所有可兼容的捕获输入端口和捕获输出端口的连接,如图4-2所示。捕获输入与捕获输出之间的连接是由 AVCaptureConnection 对象表示。


    Figure 4-2  AVCaptureConnection represents a connection between an input and output

    You can use a capture connection to enable or disable the flow of data from a given input or to a given output. You can also use a connection to monitor the average and peak power levels in an audio channel.

    可以使用捕获连接来启用或者禁用给定输入或给定输出的数据流。也可以使用连接来监视音频信道中的平均和峰值功率水平。

    Note: Media capture does not support simultaneous capture of both the front-facing and back-facing cameras on iOS devices.

    注意:媒体捕获不支持iOS设备上的前置摄像头和后置摄像头的同时捕捉。

    Use a Capture Session to Coordinate Data Flow - 使用捕捉会话来协调数据流

    An AVCaptureSession object is the central coordinating object you use to manage data capture. You use an instance to coordinate the flow of data from AV input devices to outputs. You add the capture devices and outputs you want to the session, then start data flow by sending the session a startRunning message, and stop the data flow by sending a stopRunning message.

    AVCaptureSession 对象是你用来管理数据捕获的中央协调对象。使用一个实例来协调从 AV 输入设备到输出的数据流。添加捕获设备并且输出你想要的会话,然后发送一个 startRunning 消息启动数据流,发送 stopRunning 消息来停止数据流。

    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    // Add inputs and outputs.
    [session startRunning];
    

    Configuring a Session - 配置会话

    You use a preset on the session to specify the image quality and resolution you want. A preset is a constant that identifies one of a number of possible configurations; in some cases the actual configuration is device-specific:

    使用会话上的 preset 来指定图像的质量和分辨率。预设是一个常数,确定了一部分可能的配置中的一个;在某些情况下,设计的配置是设备特有的:

    | Symbol | Resolution | Comments |
    | AVCaptureSessionPresetHigh | High | Highest recording quality.This varies per device.|
    | AVCaptureSessionPresetMedium | Medium | Suitable for Wi-Fi sharing.The actual values may change.|
    | AVCaptureSessionPresetLow | Low | Suitable for 3G sharing.The actual values may change. |
    | AVCaptureSessionPreset640x480 | 640x480 | VGA |
    | AVCaptureSessionPreset1280x720 | 1280x720 | 720p HD. |
    | AVCaptureSessionPresetPhoto | Photo | Full photo resolution.This is not supported for video output. |

    If you want to set a media frame size-specific configuration, you should check whether it is supported before setting it, as follows:

    如果要设置媒体帧特定大小的配置,应该在设置之前检查是否支持被设定,如下所示:

    if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
        session.sessionPreset = AVCaptureSessionPreset1280x720;
    }
    else {
        // Handle the failure.
    }

    If you need to adjust session parameters at a more granular level than is possible with a preset, or you’d like to make changes to a running session, you surround your changes with the beginConfiguration and commitConfiguration methods. The beginConfiguration and commitConfiguration methods ensure that devices changes occur as a group, minimizing visibility or inconsistency of state. After calling beginConfiguration, you can add or remove outputs, alter the sessionPreset property, or configure individual capture input or output properties. No changes are actually made until you invoke commitConfiguration, at which time they are applied together.

    如果需要比预设情况,更加精细的水平调整会话参数,或者想给一个正在运行的会话做些改变,用 beginConfigurationcommitConfiguration 方法。beginConfigurationcommitConfiguration 方法确保设备作为一个群体在变化,降低状态的清晰度或者不协调性。调用 beginConfiguration 之后,可以添加或者移除输出,改变 sessionPreset 属性,或者单独配置捕获输入或输出属性。在你调用 commitConfiguration 之前实际上是没有变化的,调用的时候它们才被应用到一起。

    [session beginConfiguration];
    // Remove an existing capture device.
    // Add a new capture device.
    // Reset the preset.
    [session commitConfiguration];
    

    Monitoring Capture Session State - 监视捕获会话状态

    A capture session posts notifications that you can observe to be notified, for example, when it starts or stops running, or when it is interrupted. You can register to receive an AVCaptureSessionRuntimeErrorNotification if a runtime error occurs. You can also interrogate the session’s running property to find out if it is running, and its interrupted property to find out if it is interrupted. Additionally, both the running and interrupted properties are key-value observing compliant and the notifications are posted on the main thread.

    捕获会话发出你能观察并被通知到的 notifications,例如,当它开始或者停止运行,或者当它被中断。你可以注册,如果发生了运行阶段的错误,可以接收 AVCaptureSessionRuntimeErrorNotification 。也可以询问会话的 running 属性去发现它正在运行的状态,并且它的 interrupted 属性可以找到它是否被中断了。此外, runninginterrupted 属性是遵从key-value observing ,并且在通知都是在主线程上发布的。

    An AVCaptureDevice Object Represents an Input Device - 一个 AVCaptureDevice 对象代表一个输入设备

    An AVCaptureDevice object abstracts a physical capture device that provides input data (such as audio or video) to an AVCaptureSession object. There is one object for each input device, for example, two video inputs—one for the front-facing the camera, one for the back-facing camera—and one audio input for the microphone.

    一个 AVCaptureDevice 对象抽象出物理捕获设备,提供了输入数据(比如音频或者视频)给 AVCaptureSession 对象。例如每个输入设备都有一个对象,两个视频输入,一个用于前置摄像头,一个用于后置摄像头,一个用于麦克风的音频输入。

    You can find out which capture devices are currently available using the AVCaptureDevice class methods devices and devicesWithMediaType:. And, if necessary, you can find out what features an iPhone, iPad, or iPod offers (see Device Capture Settings). The list of available devices may change, though. Current input devices may become unavailable (if they’re used by another application), and new input devices may become available, (if they’re relinquished by another application). You should register to receive AVCaptureDeviceWasConnectedNotification and AVCaptureDeviceWasDisconnectedNotification notifications to be alerted when the list of available devices changes.

    使用 AVCaptureDevice 类方法 devicesdevicesWithMediaType: 可以找出哪一个捕获设备当前是可用的。而且如果有必要,可以找出 iPhoneiPad 或者 iPod 提供了什么功能(详情看:Device Capture Settings)。虽然可用设备的列表可能会改变。当前输入设备可能会变得不可用(如果他们被另一个应用程序使用),新的输入设备可能成为可用的,(如果他们被另一个应用程序让出)。应该注册,当可用设备列表改变时接收 AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification 通知。

    You add an input device to a capture session using a capture input (see Use Capture Inputs to Add a Capture Device to a Session).

    使用捕获输入将输入设备添加到捕获会话中(详情请看:Use Capture Inputs to Add a Capture Device to a Session

    Device Characteristics - 设备特点

    You can ask a device about its different characteristics. You can also test whether it provides a particular media type or supports a given capture session preset using hasMediaType: and supportsAVCaptureSessionPreset: respectively. To provide information to the user, you can find out the position of the capture device (whether it is on the front or the back of the unit being tested), and its localized name. This may be useful if you want to present a list of capture devices to allow the user to choose one.

    你可以问一个有关设备的不同特征。你也可以使用 hasMediaType: 测试它是否提供了一个特定的媒体类型,或者使用 supportsAVCaptureSessionPreset: 支持一个给定捕捉会话的预设状态。为了给用户提供信息,可以找到捕捉设备的位置(无论它是在正被测试单元的前面还是后面),以及本地化名称。这是很有用的,如果你想提出一个捕获设备的列表,让用户选择一个。

    Figure 4-3 shows the positions of the back-facing (AVCaptureDevicePositionBack) and front-facing (AVCaptureDevicePositionFront) cameras.

    图4-3显示了后置摄像头(AVCaptureDevicePositionBack)和前置摄像头(AVCaptureDevicePositionFront)的位置。

    Note: Media capture does not support simultaneous capture of both the front-facing and back-facing cameras on iOS devices.

    注意:媒体捕获在iOS设备上不支持前置摄像头和后置摄像头同时捕捉。


    Figure 4-3  iOS device front and back facing camera positions

    The following code example iterates over all the available devices and logs their name—and for video devices, their position—on the unit.

    下面的代码示例遍历了所有可用的设备并且记录了它们的名字,视频设备,在装置上的位置。

    NSArray *devices = [AVCaptureDevice devices];
    
    for (AVCaptureDevice *device in devices) {
    
        NSLog(@"Device name: %@", [device localizedName]);
    
        if ([device hasMediaType:AVMediaTypeVideo]) {
    
            if ([device position] == AVCaptureDevicePositionBack) {
                NSLog(@"Device position : back");
            }
            else {
                NSLog(@"Device position : front");
            }
        }
    }

    In addition, you can find out the device’s model ID and its unique ID.

    此外,你可以找到该设备的 model ID 和它的 unique ID

    Device Capture Settings

    Different devices have different capabilities; for example, some may support different focus or flash modes; some may support focus on a point of interest.

    例如不同设备有不同的功能,一些可能支持不同的聚焦或者闪光模式;一些可能会支持聚焦在一个兴趣点。

    The following code fragment shows how you can find video input devices that have a torch mode and support a given capture session preset:

    下面的代码片段展示了如何找到有一个 torch 模式的视频输入设备,并且支持一个捕捉会话预设。

    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
    
    for (AVCaptureDevice *device in devices) {
        [if ([device hasTorch] &&
             [device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
            [torchDevices addObject:device];
        }
    }

    If you find multiple devices that meet your criteria, you might let the user choose which one they want to use. To display a description of a device to the user, you can use its localizedName property.

    如果找到多个设备满足标准,你可能会让用户选择一个他们想使用的。给用户显示一个设备的描述,可以使用它的 localizedName 属性。

    You use the various different features in similar ways. There are constants to specify a particular mode, and you can ask a device whether it supports a particular mode. In several cases, you can observe a property to be notified when a feature is changing. In all cases, you should lock the device before changing the mode of a particular feature, as described in Configuring a Device.

    用类似的方法使用各种不同的功能。有常量来指定一个特定的模式,也可以问设备是否支持特定的模式。在一些情况下,当功能改变的时候可以观察到要通知的属性。在所有情况下,你应该改变特定功能的模式之前锁定设备,如在设备配置中描述。

    Note: Focus point of interest and exposure point of interest are mutually exclusive, as are focus mode and exposure mode.

    注意:兴趣的焦点和兴趣的曝光点是相互排斥的,因为是聚焦模式和曝光模式。

    Focus Modes - 聚焦模式

    There are three focus modes:

    • AVCaptureFocusModeLocked: The focal position is fixed.
      This is useful when you want to allow the user to compose a scene then lock the focus.
    • AVCaptureFocusModeAutoFocus: The camera does a single scan focus then reverts to locked.
      This is suitable for a situation where you want to select a particular item on which to focus and then maintain focus on that item even if it is not the center of the scene.
    • AVCaptureFocusModeContinuousAutoFocus: The camera continuously autofocuses as needed.

    有3个聚焦模式:

    You use the isFocusModeSupported: method to determine whether a device supports a given focus mode, then set the mode using the focusMode property.

    使用 isFocusModeSupported: 方法来决定设备是否支持给定的聚焦模式,然后使用 focusMode 属性设置模式。

    In addition, a device may support a focus point of interest. You test for support using focusPointOfInterestSupported. If it’s supported, you set the focal point using focusPointOfInterest. You pass a CGPoint where {0,0} represents the top left of the picture area, and {1,1} represents the bottom right in landscape mode with the home button on the right—this applies even if the device is in portrait mode.

    此外,设备可能支持一个兴趣焦点。使用 focusPointOfInterestSupported 进行支持测试。如果支持,使用 focusPointOfInterest 设置焦点。传一个 CGPoing,横向模式下(就是 home 键在右边)图片的左上角是 {0, 0},右下角是 {1, 1}, – 即使设备是纵向模式也适用。

    You can use the adjustingFocus property to determine whether a device is currently focusing. You can observe the property using key-value observing to be notified when a device starts and stops focusing.

    你可以使用 adjustingFocus 属性来确定设备是否正在聚焦。当设备开始、停止聚焦时可以使用 key-value observing 观察,接收通知。

    If you change the focus mode settings, you can return them to the default configuration as follows:

    如果改变聚焦模式设置,可以将其返回到默认配置,如下所示:

    if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
        CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
        [currentDevice setFocusPointOfInterest:autofocusPoint];
        [currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
    }
    

    Exposure Modes - 曝光模式

    There are two exposure modes:

    • AVCaptureExposureModeContinuousAutoExposure: The device automatically adjusts the exposure level as needed.
    • AVCaptureExposureModeLocked: The exposure level is fixed at its current level.

    You use the isExposureModeSupported: method to determine whether a device supports a given exposure mode, then set the mode using the exposureMode property.

    有两种曝光模式:

    使用 isExposureModeSupported: 方法来确定设备是否支持给定的曝光模式,然后使用 exposureMode 属性设置模式。

    In addition, a device may support an exposure point of interest. You test for support using exposurePointOfInterestSupported. If it’s supported, you set the exposure point using exposurePointOfInterest. You pass a CGPoint where {0,0} represents the top left of the picture area, and {1,1} represents the bottom right in landscape mode with the home button on the right—this applies even if the device is in portrait mode.

    此外,一个设备支持一个曝光点。使用 exposurePointOfInterestSupported 测试支持。如果支持,使用 exposurePointOfInterest 设置曝光点。传一个 CGPoing,横向模式下(就是 home 键在右边)图片的左上角是 {0, 0},右下角是 {1, 1}, – 即使设备是纵向模式也适用。

    You can use the adjustingExposure property to determine whether a device is currently changing its exposure setting. You can observe the property using key-value observing to be notified when a device starts and stops changing its exposure setting.

    可以使用 adjustingExposure 属性来确定设备当前是否改变它的聚焦设置。当设备开始、停止聚焦时可以使用 key-value observing 观察,接收通知。

    If you change the exposure settings, you can return them to the default configuration as follows:

    如果改变曝光设置,可以将其返回到默认配置,如下所示:

    if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
        CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
        [currentDevice setExposurePointOfInterest:exposurePoint];
        [currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
    }

    Flash Modes - 闪光模式

    There are three flash modes:

    • AVCaptureFlashModeOff: The flash will never fire.
    • AVCaptureFlashModeOn: The flash will always fire.
    • AVCaptureFlashModeAuto: The flash will fire dependent on the ambient light conditions.

    You use hasFlash to determine whether a device has a flash. If that method returns YES, you then use the isFlashModeSupported: method, passing the desired mode to determine whether a device supports a given flash mode, then set the mode using the flashMode property.

    有3种闪光模式:

    使用 hasFlash 来确定设备是否有闪光灯。如果这个方法返回 YES ,然后使用 isFlashModeSupported: 方法确定设备是否支持给定的闪光模式,然后使用 flashMode 属性设置模式。

    Torch Mode - 手电筒模式

    In torch mode, the flash is continuously enabled at a low power to illuminate a video capture. There are three torch modes:

    • AVCaptureTorchModeOff: The torch is always off.
    • AVCaptureTorchModeOn: The torch is always on.
    • AVCaptureTorchModeAuto: The torch is automatically switched on and off as needed.

    You use hasTorch to determine whether a device has a flash. You use the isTorchModeSupported: method to determine whether a device supports a given flash mode, then set the mode using the torchMode property.

    For devices with a torch, the torch only turns on if the device is associated with a running capture session.

    在手电筒模式下,闪光灯在一个低功率下一直开启,以照亮对视频捕获。有3个手电筒模式:

    使用 hasTorch 来确定设备是否有闪光灯。使用 isTorchModeSupported: 方法来确定设备是否支持给定的闪光模式,然后使用 torchMode 属性来设置模式。

    对于一个有手电筒的设备,只有当该设备与一个运行时捕捉会话关联时,才能打开手电筒。

    Video Stabilization - 视频稳定性

    Cinematic video stabilization is available for connections that operate on video, depending on the specific device hardware. Even so, not all source formats and video resolutions are supported.

    Enabling cinematic video stabilization may also introduce additional latency into the video capture pipeline. To detect when video stabilization is in use, use the videoStabilizationEnabled property. The enablesVideoStabilizationWhenAvailable property allows an application to automatically enable video stabilization if it is supported by the camera. By default automatic stabilization is disabled due to the above limitations.

    电影视频的稳定化可用于连接视频上的操作,这取决于具体的硬件。尽管如此,不是所有的源格式和视频分辨率都被支持。

    使用电影视频稳定化也可能会对视频采集管道引起额外的延迟。正在使用视频稳定化时,使用 videoStabilizationEnabled 属性可以检测。enablesVideoStabilizationWhenAvailable 属性允许应用程序自动使视频稳定化可用,如果它是被摄像头支持的话。由于以上限制,默认自动稳定化是禁用的。

    White Balance - 白平衡

    There are two white balance modes:

    • AVCaptureWhiteBalanceModeLocked: The white balance mode is fixed.
    • AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance: The camera continuously adjusts the white balance as needed.

    You use the isWhiteBalanceModeSupported: method to determine whether a device supports a given white balance mode, then set the mode using the whiteBalanceMode property.

    You can use the adjustingWhiteBalance property to determine whether a device is currently changing its white balance setting. You can observe the property using key-value observing to be notified when a device starts and stops changing its white balance setting.

    有两个白平衡模式:

    使用 isWhiteBalanceModeSupported: :方法来确定设备是否支持给定的白平衡模式,然后使用 whiteBalanceMode 属性设置模式。

    可以使用 adjustingWhiteBalance 属性来确定设备是否正在改变白平衡设置。当设备开始或者停止改变它的白平衡设置时,可以使用 key-value observing 观察属性,接收通知。

    Setting Device Orientation - 设置设备方向

    You set the desired orientation on a AVCaptureConnection to specify how you want the images oriented in the AVCaptureOutput (AVCaptureMovieFileOutput, AVCaptureStillImageOutput and AVCaptureVideoDataOutput) for the connection.

    Use the AVCaptureConnectionsupportsVideoOrientation property to determine whether the device supports changing the orientation of the video, and the videoOrientation property to specify how you want the images oriented in the output port. Listing 4-1 shows how to set the orientation for a AVCaptureConnection to AVCaptureVideoOrientationLandscapeLeft:

    AVCaptureConnection 设置期望的方向,来指定你想要的图像在 AVCaptureOutputAVCaptureMovieFileOutputAVCaptureStillImageOutput, AVCaptureVideoDataOutput)中的方向,为了连接。

    使用 AVCaptureConnectionsupportsVideoOrientation 属性来确定设备是否支持改变视频的方向,videoOrientation 属性指定你想要的图像在输出端口的方向。列表4-1显示了如何设置方向,为 AVCaptureConnection 设置 AVCaptureVideoOrientationLandscapeLeft

    Listing 4-1 Setting the orientation of a capture connection

    AVCaptureConnection *captureConnection = <#A capture connection#>;
    if ([captureConnection isVideoOrientationSupported])
    {
        AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
        [captureConnection setVideoOrientation:orientation];
    }

    Configuring a Device - 配置设备

    To set capture properties on a device, you must first acquire a lock on the device using lockForConfiguration:. This avoids making changes that may be incompatible with settings in other applications. The following code fragment illustrates how to approach changing the focus mode on a device by first determining whether the mode is supported, then attempting to lock the device for reconfiguration. The focus mode is changed only if the lock is obtained, and the lock is released immediately afterward.

    在设备上设置捕获属性,必须先使用 lockForConfiguration: 获得设备锁。这样就避免了在其他应用程序中可能与设置不兼容的更改。下面的代码段演示了首先如何通过确定模式是否被支持的方式改变一个设备上的焦点模式,然后视图锁定设备重新配置。只有当锁被获取到,焦点模式才会被改变,并且锁被释放后立即锁定。

    if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
        NSError *error = nil;
        if ([device lockForConfiguration:&error]) {
            device.focusMode = AVCaptureFocusModeLocked;
            [device unlockForConfiguration];
        }
        else {
            // Respond to the failure as appropriate.

    You should hold the device lock only if you need the settable device properties to remain unchanged. Holding the device lock unnecessarily may degrade capture quality in other applications sharing the device.

    只有在需要设置设备属性保持不变的时候才应该使设备锁保持。没必要的保持设备所,可能会在其他应用程序共享设备时降低捕获质量。

    Switching Between Devices - 切换装置

    Sometimes you may want to allow users to switch between input devices—for example, switching from using the front-facing to to the back-facing camera. To avoid pauses or stuttering, you can reconfigure a session while it is running, however you should use beginConfiguration and commitConfiguration to bracket your configuration changes:

    有时,你可能想允许用户在输入设备之间进行切换,比如使用前置摄像头到后置摄像头的切换。为了避免暂停或者卡顿,可以在运行时配置一个会话,但是你应该使用 beginConfigurationcommitConfiguration 支持你的配置改变:

    AVCaptureSession *session = <#A capture session#>;
    [session beginConfiguration];
    
    [session removeInput:frontFacingCameraDeviceInput];
    [session addInput:backFacingCameraDeviceInput];
    
    [session commitConfiguration];

    When the outermost commitConfiguration is invoked, all the changes are made together. This ensures a smooth transition.

    当最外面的 commitConfiguration 被调用,所有的改变都是一起做的。这保证了平稳过渡。

    Use Capture Inputs to Add a Capture Device to a Session - 使用捕获输入将捕获设备添加到会话中

    To add a capture device to a capture session, you use an instance of AVCaptureDeviceInput (a concrete subclass of the abstract AVCaptureInput class). The capture device input manages the device’s ports.

    添加一个捕获装置到捕获会话中,使用 AVCaptureDeviceInput (AVCaptureInput 抽象类的具体子类)的实例。捕获设备输入管理设备的端口。

    NSError *error;
    AVCaptureDeviceInput *input =
            [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
    if (!input) {
        // Handle the error appropriately.
    }

    You add inputs to a session using addInput:. If appropriate, you can check whether a capture input is compatible with an existing session using canAddInput:.

    使用 addInput: 给会话添加一个输入。如果合适的话,可以使用 canAddInput: 检查是否有输入捕获与现有会话是兼容的。

    AVCaptureSession *captureSession = <#Get a capture session#>;
    AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;
    if ([captureSession canAddInput:captureDeviceInput]) {
        [captureSession addInput:captureDeviceInput];
    }
    else {
        // Handle the failure.
    }

    See Configuring a Session for more details on how you might reconfigure a running session.

    An AVCaptureInput vends one or more streams of media data. For example, input devices can provide both audio and video data. Each media stream provided by an input is represented by an AVCaptureInputPort object. A capture session uses an AVCaptureConnection object to define the mapping between a set of AVCaptureInputPort objects and a single AVCaptureOutput.

    有关如果配置一个正在运行的会话,更多细节请查看 Configuring a Session .

    AVCaptureInput 声明一个或者多个媒体数据流。例如,输入设备可以提供音频和视频数据。输入提供的每个媒体流都被一个 AVCaptureInputPort 所表示。一个捕获会话使用 AVCaptureConnection 对象来定义一个 一组 AVCaptureInputPort 对象和一个 AVCaptureOutput 之间的映射。

    Use Capture Outputs to Get Output from a Session - 使用捕获输出从会话得到输出

    To get output from a capture session, you add one or more outputs. An output is an instance of a concrete subclass of AVCaptureOutput. You use:

    • AVCaptureMovieFileOutput to output to a movie file
    • AVCaptureVideoDataOutput if you want to process frames from the video being captured, for example, - to create your own custom view layer
    • AVCaptureAudioDataOutput if you want to process the audio data being captured
    • AVCaptureStillImageOutput if you want to capture still images with accompanying metadata

    You add outputs to a capture session using addOutput:. You check whether a capture output is compatible with an existing session using canAddOutput:. You can add and remove outputs as required while the session is running.

    要从捕获会话得到输出,可以添加一个或多个输出。一个输出是 AVCaptureOutput 的具体子类的实例。下面几种使用:

    使用 addOutput: 把输出添加到捕获会话中。使用 canAddOutput: 检查是否一个捕获输出与现有的会话是兼容的。可以在会话正在运行的时候添加和删除所需的输出。

    AVCaptureSession *captureSession = <#Get a capture session#>;
    AVCaptureMovieFileOutput *movieOutput = <#Create and configure a movie output#>;
    if ([captureSession canAddOutput:movieOutput]) {
        [captureSession addOutput:movieOutput];
    }
    else {
        // Handle the failure.
    }
    

    Saving to a Movie File - 保存电影文件

    You save movie data to a file using an AVCaptureMovieFileOutput object. (AVCaptureMovieFileOutput is a concrete subclass of AVCaptureFileOutput, which defines much of the basic behavior.) You can configure various aspects of the movie file output, such as the maximum duration of a recording, or its maximum file size. You can also prohibit recording if there is less than a given amount of disk space left.

    使用 AVCaptureMovieFileOutput 对象保存电影数据到文件中。(AVCaptureMovieFileOutputAVCaptureFileOutput 的具体子类,定义了大量的基本行为。)可以电影文件输出的各个方面,如记录的最大时间,或它的最大文件的大小。也可以禁止记录,如果有小于给定磁盘空间的数量。

    AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
    CMTime maxDuration = <#Create a CMTime to represent the maximum duration#>;
    aMovieFileOutput.maxRecordedDuration = maxDuration;
    aMovieFileOutput.minFreeDiskSpaceLimit = <#An appropriate minimum given the quality of the movie format and the duration#>;

    The resolution and bit rate for the output depend on the capture session’s sessionPreset. The video encoding is typically H.264 and audio encoding is typically AAC. The actual values vary by device.

    输出的分辨率和比特率取决于捕获会话的 sessionPreset 。视频编码通常是 H.264 ,音频编码通常是 AAC 。实际值因设备而异。

    Starting a Recording - 开始记录

    You start recording a QuickTime movie using startRecordingToOutputFileURL:recordingDelegate:. You need to supply a file-based URL and a delegate. The URL must not identify an existing file, because the movie file output does not overwrite existing resources. You must also have permission to write to the specified location. The delegate must conform to the AVCaptureFileOutputRecordingDelegate protocol, and must implement the captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: method.

    使用 startRecordingToOutputFileURL:recordingDelegate: 开始记录一个 QuickTime 电影。需要提供一个基于 URLdelegate 的文件。URL 决不能指向一个已经存在的文件,因为电影文件输出不会覆盖存在的资源。你还必须有权限能写入指定的位置。 delegate 必须符合 AVCaptureFileOutputRecordingDelegate 协议,并且必须实现 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: 方法。

    AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
    NSURL *fileURL = <#A file URL that identifies the output location#>;
    [aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The delegate#>];

    In the implementation of captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:, the delegate might write the resulting movie to the Camera Roll album. It should also check for any errors that might have occurred.

    captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: 的实现中,代理可以将结果电影写入到相机胶卷专辑中。它也应该可能发生的任何错误。

    Ensuring That the File Was Written Successfully - 确保文件被成功写入

    To determine whether the file was saved successfully, in the implementation of captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: you check not only the error but also the value of the AVErrorRecordingSuccessfullyFinishedKey in the error’s user info dictionary:

    为了确定文件是否成功被写入,在 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: 的实现中,不仅要检查错误,还要在错误的用户信息字典中,检查 AVErrorRecordingSuccessfullyFinishedKey 的值。

    - (void)captureOutput:(AVCaptureFileOutput *)captureOutput
            didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
            fromConnections:(NSArray *)connections
            error:(NSError *)error {
    
        BOOL recordedSuccessfully = YES;
        if ([error code] != noErr) {
            // A problem occurred: Find out if the recording was successful.
            id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
            if (value) {
                recordedSuccessfully = [value boolValue];
            }
        }
        // Continue as appropriate...

    You should check the value of the AVErrorRecordingSuccessfullyFinishedKeykey in the user info dictionary of the error, because the file might have been saved successfully, even though you got an error. The error might indicate that one of your recording constraints was reached—for example, AVErrorMaximumDurationReached or AVErrorMaximumFileSizeReached. Other reasons the recording might stop are:

    The disk is full—AVErrorDiskFull
    The recording device was disconnected—AVErrorDeviceWasDisconnected
    The session was interrupted (for example, a phone call was received)—AVErrorSessionWasInterrupted

    应该在用户的错误信息字典中检查 AVErrorRecordingSuccessfullyFinishedKeykey 的值,因为即使得到了一个错误信息,文件可能已经被成功保存了。这种错误可能表明你的一个记录约束被延迟了,例如 AVErrorMaximumDurationReached 或者 AVErrorMaximumFileSizeReached 。记录可能停止的其他原因是:

    Adding Metadata to a File - 将元数据添加到文件中

    You can set metadata for the movie file at any time, even while recording. This is useful for situations where the information is not available when the recording starts, as may be the case with location information. Metadata for a file output is represented by an array of AVMetadataItem objects; you use an instance of its mutable subclass, AVMutableMetadataItem, to create metadata of your own.

    可以在任何时间设置电影文件的元数据,即使在记录的时候。这是有用的,当记录开始,信息室不可用的,因为可能是位置信息的情况下。一个输出文件的元数据是由 AVMetadataItem 对象的数组表示;使用其可变子类(AVMutableMetadataItem)的实例,去创建属于你自己的元数据。

    AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
    NSArray *existingMetadataArray = aMovieFileOutput.metadata;
    NSMutableArray *newMetadataArray = nil;
    if (existingMetadataArray) {
        newMetadataArray = [existingMetadataArray mutableCopy];
    }
    else {
        newMetadataArray = [[NSMutableArray alloc] init];
    }
    
    AVMutableMetadataItem *item = [[AVMutableMetadataItem alloc] init];
    item.keySpace = AVMetadataKeySpaceCommon;
    item.key = AVMetadataCommonKeyLocation;
    
    CLLocation *location - <#The location to set#>;
    item.value = [NSString stringWithFormat:@"%+08.4lf%+09.4lf/"
        location.coordinate.latitude, location.coordinate.longitude];
    
    [newMetadataArray addObject:item];
    
    aMovieFileOutput.metadata = newMetadataArray;
    

    Processing Frames of Video - 处理视频的帧

    An AVCaptureVideoDataOutput object uses delegation to vend video frames. You set the delegate using setSampleBufferDelegate:queue:. In addition to setting the delegate, you specify a serial queue on which they delegate methods are invoked. You must use a serial queue to ensure that frames are delivered to the delegate in the proper order. You can use the queue to modify the priority given to delivering and processing the video frames. See SquareCam for a sample implementation.

    一个 AVCaptureVideoDataOutput 对象使用委托来声明视频帧。使用 setSampleBufferDelegate:queue: 设置代理。除了设置代理,还要制定一个调用它们代理方法的串行队列。必须使用一个串行队列以确保帧以适当的顺序传递给代理。可以使用队列来修改给定传输的优先级和处理视频帧的优先级。查看 SquareCam 有一个简单的实现。

    The frames are presented in the delegate method, captureOutput:didOutputSampleBuffer:fromConnection:, as instances of the CMSampleBufferRef opaque type (see Representations of Media). By default, the buffers are emitted in the camera’s most efficient format. You can use the videoSettings property to specify a custom output format. The video settings property is a dictionary; currently, the only supported key is kCVPixelBufferPixelFormatTypeKey. The recommended pixel formats are returned by the availableVideoCVPixelFormatTypes property , and the availableVideoCodecTypes property returns the supported values. Both Core Graphics and OpenGL work well with the BGRA format:

    在代理方法中(captureOutput:didOutputSampleBuffer:fromConnection:CMSampleBufferRef 不透明类型的实例,详情见 Representations of Media),帧是被露出来的。默认情况下,被放出的缓冲区是相机最有效的格式。可以使用 videoSettings 属性指定自定义输出格式。视频设置属性是一个字典;目前,唯一支持的 keykCVPixelBufferPixelFormatTypeKey。推荐的像素格式是由 availableVideoCVPixelFormatTypes 属性返回的,并且 availableVideoCodecTypes 属性返回支持的值。Core GraphicsOpenGL 都很好的使用 BGRA 格式:

    AVCaptureVideoDataOutput *videoDataOutput = [AVCaptureVideoDataOutput new];
    NSDictionary *newSettings =
                    @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
    videoDataOutput.videoSettings = newSettings;
    
     // discard if the data output queue is blocked (as we process the still image
    [videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];)
    
    // create a serial dispatch queue used for the sample buffer delegate as well as when a still image is captured
    // a serial dispatch queue must be used to guarantee that video frames will be delivered in order
    // see the header doc for setSampleBufferDelegate:queue: for more information
    videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
    [videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue];
    
    AVCaptureSession *captureSession = <#The Capture Session#>;
    
    if ( [captureSession canAddOutput:videoDataOutput] )
         [captureSession addOutput:videoDataOutput];
    
    

    Performance Considerations for Processing Video - 处理视频的性能考虑

    You should set the session output to the lowest practical resolution for your application. Setting the output to a higher resolution than necessary wastes processing cycles and needlessly consumes power.

    应该将会话输出设置为应用程序的最低分辨率。设置输出超过必要废物处理周期,达到更高的分辨率,从而不必要消耗功率。

    You must ensure that your implementation of captureOutput:didOutputSampleBuffer:fromConnection: is able to process a sample buffer within the amount of time allotted to a frame. If it takes too long and you hold onto the video frames, AV Foundation stops delivering frames, not only to your delegate but also to other outputs such as a preview layer.

    必须确保 captureOutput:didOutputSampleBuffer:fromConnection: 的实现,能够处理大量时间内的样品缓冲,分配到一个帧中。如果它需要很久,你要一直抓住视频帧,AV Foundation 会停止给,你的代理,还有其他输出例如 preview layer ,提供帧。

    You can use the capture video data output’s minFrameDuration property to be sure you have enough time to process a frame — at the cost of having a lower frame rate than would otherwise be the case. You might also make sure that the alwaysDiscardsLateVideoFrames property is set to YES (the default). This ensures that any late video frames are dropped rather than handed to you for processing. Alternatively, if you are recording and it doesn’t matter if the output fames are a little late and you would prefer to get all of them, you can set the property value to NO. This does not mean that frames will not be dropped (that is, frames may still be dropped), but that they may not be dropped as early, or as efficiently.

    可以使用捕获视频数据输出的 minFrameDuration 属性来确保你有足够时间来处理帧 – 在具有较低的帧速率比其他情况下的成本。也可以确保 alwaysDiscardsLateVideoFrames 属性被设为 YES (默认)。这确保任何后期视频的帧都被丢弃,而不是交给你处理。或者,如果你是记录,更想得到它们全部,不介意输出帧稍微晚一点的话,可以设置该属性的值为 NO 。这并不意味着不会丢失帧(即,帧仍有可能丢失),但它们不可能像之前那样减少,或者说是有点效果的。

    Capturing Still Images - 捕获静止图像

    You use an AVCaptureStillImageOutput output if you want to capture still images with accompanying metadata. The resolution of the image depends on the preset for the session, as well as the device.

    如果你想捕获带着元数据的静止图像,可以使用 AVCaptureStillImageOutput 输出。图像的分辨率取决于会话的预设,以及设备的设置。

    Pixel and Encoding Formats - 像素和编码格式

    Different devices support different image formats. You can find out what pixel and codec types are supported by a device using availableImageDataCVPixelFormatTypes and availableImageDataCodecTypes respectively. Each method returns an array of the supported values for the specific device. You set the outputSettings dictionary to specify the image format you want, for example:

    不同的设备支持不同的图像格式。使用 availableImageDataCVPixelFormatTypes 可以找到什么样的像素被支持,使用 availableImageDataCodecTypes 可以找到什么样的编解码器类型被支持。每一种方法都返回一个特定设备的支持的值的数组。设置 outputSettings 字典来指定你想要的图像格式,例如:

    AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
    NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
    [stillImageOutput setOutputSettings:outputSettings];

    If you want to capture a JPEG image, you should typically not specify your own compression format. Instead, you should let the still image output do the compression for you, since its compression is hardware-accelerated. If you need a data representation of the image, you can use jpegStillImageNSDataRepresentation: to get an NSData object without recompressing the data, even if you modify the image’s metadata.

    如果你想捕获一个 JPEG 图像,通常应该不要指定自己的压缩格式。相反,应该让静态图像输出为你做压缩,因为它的压缩是硬件加速的。如果你需要图像的表示数据,可以使用 jpegStillImageNSDataRepresentation: 得到未压缩数据的NSDate 对象,即使你修改修改图像的元数据。

    Capturing an Image - 捕获图像

    When you want to capture an image, you send the output a captureStillImageAsynchronouslyFromConnection:completionHandler: message. The first argument is the connection you want to use for the capture. You need to look for the connection whose input port is collecting video:

    当你想捕获图像,给输出发送一个 captureStillImageAsynchronouslyFromConnection:completionHandler: 消息。第一个参数是用于想要捕获使用的连接。你需要寻找输入端口是收集视频的连接。

    AVCaptureConnection *videoConnection = nil;
    for (AVCaptureConnection *connection in stillImageOutput.connections) {
        for (AVCaptureInputPort *port in [connection inputPorts]) {
            if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
                videoConnection = connection;
                break;
            }
        }
        if (videoConnection) { break; }
    }

    The second argument to captureStillImageAsynchronouslyFromConnection:completionHandler: is a block that takes two arguments: a CMSampleBuffer opaque type containing the image data, and an error. The sample buffer itself may contain metadata, such as an EXIF dictionary, as an attachment. You can modify the attachments if you want, but note the optimization for JPEG images discussed in Pixel and Encoding Formats.

    captureStillImageAsynchronouslyFromConnection:completionHandler: 的第二个参数是一个 blockblock 有两个参数:一个包含图像数据的 CMSampleBuffer 不透明类型,一个 error。样品缓冲自身可能包含元数据,例如 EXIF 字典作为附件。如果你想的话,可以修改附件,但是注意 JPEG 图像进行像素和编码格式的优化。

    [stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:
        ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
            CFDictionaryRef exifAttachments =
                CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
            if (exifAttachments) {
                // Do something with the attachments.
            }
            // Continue as appropriate.
        }];

    Showing the User What’s Being Recorded - 显示用户正在被记录什么

    You can provide the user with a preview of what’s being recorded by the camera (using a preview layer) or by the microphone (by monitoring the audio channel).

    可以为用户提供一个预览,关于正在被相机(使用 perview layer)记录什么,或者被麦克风(通过监控音频信道)记录什么。

    Video Preview - 视频预览

    You can provide the user with a preview of what’s being recorded using an AVCaptureVideoPreviewLayer object. AVCaptureVideoPreviewLayer is a subclass ofCALayer (see Core Animation Programming Guide. You don’t need any outputs to show the preview.

    使用 对象可以给用户提供一个正在被记录的预览。 AVCaptureVideoPreviewLayerCALayer 的子类。(详情见 Core Animation Programming Guide),不需要任何输出去显示预览。

    Using the AVCaptureVideoDataOutput class provides the client application with the ability to access the video pixels before they are presented to the user.

    使用 AVCaptureVideoDataOutput 类提供的访问视频像素才呈现给用户的客户端应用程序的能力。

    Unlike a capture output, a video preview layer maintains a strong reference to the session with which it is associated. This is to ensure that the session is not deallocated while the layer is attempting to display video. This is reflected in the way you initialize a preview layer:

    与捕获输出不同的是,视频预览层与它关联的会话有一个强引用。这是为了确保会话还没有被释放,layer 就尝试去显示视频。这反映在,你初始化一个预览层的方式上:

    AVCaptureSession *captureSession = <#Get a capture session#>;
    CALayer *viewLayer = <#Get a layer from the view in which you want to present the preview#>;
    
    AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
    [viewLayer addSublayer:captureVideoPreviewLayer];

    In general, the preview layer behaves like any other CALayer object in the render tree (see Core Animation Programming Guide). You can scale the image and perform transformations, rotations, and so on just as you would any layer. One difference is that you may need to set the layer’s orientation property to specify how it should rotate images coming from the camera. In addition, you can test for device support for video mirroring by querying the supportsVideoMirroring property. You can set the videoMirrored property as required, although when the automaticallyAdjustsVideoMirroring property is set to YES (the default), the mirroring value is automatically set based on the configuration of the session.

    在一般情况下,预览层行为就像渲染树中任何其他 CALayer 对象(见 Core Animation Programming Guide)。可以缩放图像和执行转换、旋转等,就像你可以在任何层。一个不同点是,你可能需要设置层的 orientation 属性来指定它应该如何从相机中旋转图像。此外,可以通过查询 supportsVideoMirroring 属性来测试设备对于视频镜像的支持。可以根据需要设置 videoMirrored 属性,虽然当 automaticallyAdjustsVideoMirroring 属性被设置为 YES (默认情况下), mirroring 值是自动的基于会话配置进行设置。

    Video Gravity Modes - 视屏重力模式

    The preview layer supports three gravity modes that you set using videoGravity:

    • AVLayerVideoGravityResizeAspect: This preserves the aspect ratio, leaving black bars where the - video does not fill the available screen area.
    • AVLayerVideoGravityResizeAspectFill: This preserves the aspect ratio, but fills the available - screen area, cropping the video when necessary.
    • AVLayerVideoGravityResize: This simply stretches the video to fill the available screen area, even if doing so distorts the image.

    预览层支持3种重力模式,使用 videoGravity 设置:

    Using “Tap to Focus” with a Preview - 使用“点击焦点”预览

    You need to take care when implementing tap-to-focus in conjunction with a preview layer. You must account for the preview orientation and gravity of the layer, and for the possibility that the preview may be mirrored. See the sample code project AVCam-iOS: Using AVFoundation to Capture Images and Movies for an implementation of this functionality.

    需要注意的是,在实现点击时要注意结合预览层。必须考虑到该层的预览方向和重力,并考虑预览变为镜像显示的可能性。请看示例代码项目:AVCam-iOS: Using AVFoundation to Capture Images and Movies,有关这个功能的实现。

    Showing Audio Levels - 显示音频等级

    To monitor the average and peak power levels in an audio channel in a capture connection, you use an AVCaptureAudioChannel object. Audio levels are not key-value observable, so you must poll for updated levels as often as you want to update your user interface (for example, 10 times a second).

    在捕获连接中检测音频信道的平均值和峰值功率水平,可以使用 AVCaptureAudioChannel 对象。音频等级不是 key-value 可观察的,所以当你想更新你的用户界面(比如10秒一次),必须调查最新的等级。

    AVCaptureAudioDataOutput *audioDataOutput = <#Get the audio data output#>;
    NSArray *connections = audioDataOutput.connections;
    if ([connections count] > 0) {
        // There should be only one connection to an AVCaptureAudioDataOutput.
        AVCaptureConnection *connection = [connections objectAtIndex:0];
    
        NSArray *audioChannels = connection.audioChannels;
    
        for (AVCaptureAudioChannel *channel in audioChannels) {
            float avg = channel.averagePowerLevel;
            float peak = channel.peakHoldLevel;
            // Update the level meter user interface.
        }
    }

    Putting It All Together: Capturing Video Frames as UIImage Objects - 总而言之:捕获视频帧用作 UIImage 对象

    This brief code example to illustrates how you can capture video and convert the frames you get to UIImage objects. It shows you how to:

    • Create an AVCaptureSession object to coordinate the flow of data from an AV input device to an - output
    • Find the AVCaptureDevice object for the input type you want
    • Create an AVCaptureDeviceInput object for the device
    • Create an AVCaptureVideoDataOutput object to produce video frames
    • Implement a delegate for the AVCaptureVideoDataOutput object to process video frames
    • Implement a function to convert the CMSampleBuffer received by the delegate into a UIImage object

    这个简短的代码示例演示了如何捕捉视频和将帧转化为 UIImage 对象,下面说明方法:

    Note: To focus on the most relevant code, this example omits several aspects of a complete application, including memory management. To use AV Foundation, you are expected to have enough experience with Cocoa to be able to infer the missing pieces.

    注意:关注最相关的代码,这个例子省略了一个完成程序的几部分,包括内存管理。为了使用 AV Foundation,你应该有足够的 Cocoa 经验,有能力推断出丢失的碎片。

    Create and Configure a Capture Session - 创建和配置捕获会话

    You use an AVCaptureSession object to coordinate the flow of data from an AV input device to an output. Create a session, and configure it to produce medium-resolution video frames.

    使用 AVCaptureSession 对象去协调从 AV 输入设备到输出的数据流。创建一个会话,并将其配置产生中等分辨率的视频帧。

    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    session.sessionPreset = AVCaptureSessionPresetMedium;
    

    Create and Configure the Device and Device Input - 创建和配置设备记忆设备输入

    Capture devices are represented by AVCaptureDevice objects; the class provides methods to retrieve an object for the input type you want. A device has one or more ports, configured using an AVCaptureInput object. Typically, you use the capture input in its default configuration.

    Find a video capture device, then create a device input with the device and add it to the session. If an appropriate device can not be located, then the deviceInputWithDevice:error: method will return an error by reference.

    AVCaptureDevice 对象表示捕获设备;类提供你想要的输入类型对象的方法。一个设备具有一个或者多个端口,使用 AVCaptureInput 对象配置。通常情况下,在它的默认配置中使用捕获输入。

    找到一个视频捕获设备,然后创建一个带着设备的设备输入,并将其添加到会话中,如果合适的设备无法定位,然后 deviceInputWithDevice:error: 方法将会通过引用返回一个错误。

    AVCaptureDevice *device =
            [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    NSError *error = nil;
    AVCaptureDeviceInput *input =
            [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
    if (!input) {
        // Handle the error appropriately.
    }
    [session addInput:input];
    

    Create and Configure the Video Data Output - 创建和配置视频数据输出

    You use an AVCaptureVideoDataOutput object to process uncompressed frames from the video being captured. You typically configure several aspects of an output. For video, for example, you can specify the pixel format using the videoSettings property and cap the frame rate by setting the minFrameDuration property.

    Create and configure an output for video data and add it to the session; cap the frame rate to 15 fps by setting the minFrameDuration property to 1/15 second:

    使用 AVCaptureVideoDataOutput 对象去处理视频捕获过程中未被压缩的帧。通常配置输出的几个方面。例如视频,可以使用 videoSettings 属性指定像素格式,通过设置 minFrameDuration 属性覆盖帧速率。

    为视频数据创建和配置输出,并将其添加到会话中;通过设置 minFrameDuration 属性为每秒 1/15,将帧速率覆盖为 15 fps

    AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
    [session addOutput:output];
    output.videoSettings =
                    @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
    output.minFrameDuration = CMTimeMake(1, 15);

    The data output object uses delegation to vend the video frames. The delegate must adopt the AVCaptureVideoDataOutputSampleBufferDelegate protocol. When you set the data output’s delegate, you must also provide a queue on which callbacks should be invoked.

    数据输出对象使用委托来声明一个视频帧。代理必须 AVCaptureVideoDataOutputSampleBufferDelegate 协议。当你设置了数据输出的代理,还必须提供一个回调时应该被调用的队列。

    dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
    [output setSampleBufferDelegate:self queue:queue];
    dispatch_release(queue);

    You use the queue to modify the priority given to delivering and processing the video frames.

    使用队列去修改给定传输和处理视频帧的优先级。

    Implement the Sample Buffer Delegate Method - 实现示例缓冲代理方法

    In the delegate class, implement the method (captureOutput:didOutputSampleBuffer:fromConnection:) that is called when a sample buffer is written. The video data output object delivers frames as CMSampleBuffer opaque types, so you need to convert from the CMSampleBuffer opaque type to a UIImage object. The function for this operation is shown in Converting CMSampleBuffer to a UIImage Object.

    在代理类,实现方法(captureOutput:didOutputSampleBuffer:fromConnection:),当样本缓冲写入时被调用。视频数据输出对象传递了 CMSampleBuffer 不透明类型的帧,所以你需要从 CMSampleBuffer 不透明类型转化为一个 UIImage 对象。这个操作的功能在 Converting CMSampleBuffer to a UIImage Object 中展示。

    - (void)captureOutput:(AVCaptureOutput *)captureOutput
             didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
             fromConnection:(AVCaptureConnection *)connection {
    
        UIImage *image = imageFromSampleBuffer(sampleBuffer);
        // Add your code here that uses the image.
    }

    Remember that the delegate method is invoked on the queue you specified in setSampleBufferDelegate:queue:; if you want to update the user interface, you must invoke any relevant code on the main thread.

    记住,代理方法是在 setSampleBufferDelegate:queue: 中你指定的队列中调用;如果你想要更新用户界面,必须在主线程上调用任何相关代码。

    Starting and Stopping Recording - 启动和停止录制

    After configuring the capture session, you should ensure that the camera has permission to record according to the user’s preferences.

    在配置捕获会话后,应该确保相机根据用户的首相选具有录制的权限。

    NSString *mediaType = AVMediaTypeVideo;
    
    [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
        if (granted)
        {
            //Granted access to mediaType
            [self setDeviceAuthorized:YES];
        }
        else
        {
            //Not granted access to mediaType
            dispatch_async(dispatch_get_main_queue(), ^{
            [[[UIAlertView alloc] initWithTitle:@"AVCam!"
                                        message:@"AVCam doesn't have permission to use Camera, please change privacy settings"
                                       delegate:self
                              cancelButtonTitle:@"OK"
                              otherButtonTitles:nil] show];
                    [self setDeviceAuthorized:NO];
            });
        }
    }];

    If the camera session is configured and the user has approved access to the camera (and if required, the microphone), send a startRunning message to start the recording.

    如果相机会话被配置,用户批准访问摄像头(如果需要,麦克风),发送 startRunning 消息开始录制。

    Important: The startRunning method is a blocking call which can take some time, therefore you should perform session setup on a serial queue so that the main queue isn’t blocked (which keeps the UI responsive). See AVCam-iOS: Using AVFoundation to Capture Images and Movies for the canonical implementation example.

    重点:startRunning 方法正在阻塞调用时,可能需要一些时间,因此你应该在串行队列执行会话建立,为了主队列不被堵塞(使UI相应)。见 AVCam-iOS: Using AVFoundation to Capture Images and Movies ,典型实现的例子。

    [session startRunning];

    To stop recording, you send the session a stopRunning message.

    要停止录制,给会话发送一个 stopRunning 消息。

    High Frame Rate Video Capture - 高帧速率视频捕获

    iOS 7.0 introduces high frame rate video capture support (also referred to as “SloMo” video) on selected hardware. The full AVFoundation framework supports high frame rate content.

    You determine the capture capabilities of a device using the AVCaptureDeviceFormat class. This class has methods that return the supported media types, frame rates, field of view, maximum zoom factor, whether video stabilization is supported, and more.

    • Capture supports full 720p (1280 x 720 pixels) resolution at 60 frames per second (fps) including - video stabilization and droppable P-frames (a feature of H264 encoded movies, which allow the - movies to play back smoothly even on slower and older hardware.)
    • Playback has enhanced audio support for slow and fast playback, allowing the time pitch of the - audio can be preserved at slower or faster speeds.
    • Editing has full support for scaled edits in mutable compositions.
    • Export provides two options when supporting 60 fps movies. The variable frame rate, slow or fast motion, can be preserved, or the movie and be converted to an arbitrary slower frame rate such as 30 frames per second.

    The SloPoke sample code demonstrates the AVFoundation support for fast video capture, determining whether hardware supports high frame rate video capture, playback using various rates and time pitch algorithms, and editing (including setting time scales for portions of a composition).

    iOS 7 在特定的硬件中,引入了高帧速率的视频捕获支持(也被称为 “SloMo” 视频)。所有的 AVFoundation 框架都支持高帧速率内容。

    使用 AVCaptureDeviceFormat 类确定设备的捕获能力。该类有一个方法,返回支持媒体类型、帧速率、视图因子、最大缩放因子,是否支持视频稳定性等等。

    • 捕获完全支持每秒60帧的 720p (1280 x 720像素)分辨率,包括视频稳定性和可弃用的帧间编码( H264编码特征的电影,使得电影甚至在更慢更老的硬件也能很顺畅的播放)
    • 播放增强了对于慢速和快速播放的音频支持,允许音频的时间间距可以被保存在较慢或者更快的速度。
    • 编辑已全面支持规模可变的组成编辑。
    • 当支持60fps电影,出口提供了两种选择。可变的帧速率,缓慢或者快速的移动,可以保存,或者电影可以被转换为一个任意的较慢的帧速率,比如每秒30帧。

    SloPoke 示例代码演示了 AVFoundation 支持快速视频捕获,确定硬件是否支持高帧速率视频采集,使用不同速率和时间间距算法播放、编辑(包括设置为一个组件一部分的时间尺度)。

    Playback - 播放

    An instance of AVPlayer manages most of the playback speed automatically by setting the setRate: method value. The value is used as a multiplier for the playback speed. A value of 1.0 causes normal playback, 0.5 plays back at half speed, 5.0 plays back five times faster than normal, and so on.

    AVPlayer 的实例通过设置 setRate: 方法值,自动管理了大部分的播放速度。值被当做播放速度的乘法器使用。值为 1.0 是正常播放,0.5 是播放速度的一半,5.0 表示播放速度是正常速度的5倍,等等。

    The AVPlayerItem object supports the audioTimePitchAlgorithm property. This property allows you to specify how audio is played when the movie is played at various frame rates using the Time Pitch Algorithm Settings constants.

    AVPlayerItem 对象支持 audioTimePitchAlgorithm 属性。此属性允许你指定在使用时距算法设置常量播放不同的帧速率的电影时,音频的播放方式。

    The following table shows the supported time pitch algorithms, the quality, whether the algorithm causes the audio to snap to specific frame rates, and the frame rate range that each algorithm supports.

    下表显示了支持的时距算法、质量,该算法是否会导致音频突然跳到特定的帧速率,以及每个算法支持的帧速率范围。

    | Time pitch algorithm | Quality | Snaps to specific frame rate | Rate range |
    | AVAudioTimePitchAlgorithmLowQualityZeroLatency | Low quality, suitable for fast-forward, rewind, or low quality voice. | YES | 0.5, 0.666667, 0.8, 1.0, 1.25, 1.5, 2.0 rates. |
    | AVAudioTimePitchAlgorithmTimeDomain | Modest quality, less expensive computationally, suitable for voice. | NO | 0.5–2x rates. |
    | AVAudioTimePitchAlgorithmSpectral | Highest quality, most expensive computationally, preserves the pitch of the original item. | NO | 1/32–32 rates. |
    | AVAudioTimePitchAlgorithmVarispeed | High-quality playback with no pitch correction. | NO | 1/32–32 rates. |

    Editing - 编辑

    When editing, you use the AVMutableComposition class to build temporal edits.

    • Create a new AVMutableComposition instance using the composition class method.
    • Insert your video asset using the insertTimeRange:ofAsset:atTime:error: method.
    • Set the time scale of a portion of the composition using scaleTimeRange:toDuration:

    当编辑时,使用 AVMutableComposition 类去建立时间编辑。

    Export - 出口

    Exporting 60 fps video uses the AVAssetExportSession class to export an asset. The content can be exported using two techniques:

    Use the AVAssetExportPresetPassthrough preset to avoid reencoding the movie. It retimes the media with the sections of the media tagged as section 60 fps, section slowed down, or section sped up.

    Use a constant frame rate export for maximum playback compatibility. Set the frameDuration property of the video composition to 30 fps. You can also specify the time pitch by using setting the export session’s audioTimePitchAlgorithm property.

    使用 AVAssetExportSession 类将 60fps 的视频导出到资产。该内容可以使用两种技术导出:

    使用 AVAssetExportPresetPassthrough 预设,避免将电影重新编码。它重新定时媒体,将媒体部分标记为 60fps 的部分,缓慢的部分或者加速的部分。

    使用恒定的帧速率导出最大播放兼容性。设置视频组件的 frameDuration 属性为 30fps 。也可以通过设置导出会话的 audioTimePitchAlgorithm 属性指定时间间距。

    Recording - 录制

    You capture high frame rate video using the AVCaptureMovieFileOutput class, which automatically supports high frame rate recording. It will automatically select the correct H264 pitch level and bit rate.

    To do custom recording, you must use the AVAssetWriter class, which requires some additional setup.

    使用 AVCaptureMovieFileOutput 类捕获高帧速率的视频,该类自动支持高帧率录制。它会自动选择正确的 H264 的高音和比特率。

    做定制的录制,必须使用 AVAssetWriter 类,这需要一些额外的设置。

    assetWriterInput.expectsMediaDataInRealTime=YES;

    This setting ensures that the capture can keep up with the incoming data.

    此设置确保捕获可以跟上传入的数据。

    Export - 输出

    To read and write audiovisual assets, you must use the export APIs provided by the AVFoundation framework. The AVAssetExportSession class provides an interface for simple exporting needs, such as modifying the file format or trimming the length of an asset (see Trimming and Transcoding a Movie). For more in-depth exporting needs, use the AVAssetReader and AVAssetWriter classes.

    必须使用 AVFoundation 框架提供的导出 APIs 去读写音视频资产。AVAssetExportSession 类为简单输出需要,提供了一个接口,例如修改文件格式或者削减资产的长度(见 Trimming and Transcoding a Movie)。为了更深入的导出需求,使用 AVAssetReaderAVAssetWriter 类。

    Use an AVAssetReader when you want to perform an operation on the contents of an asset. For example, you might read the audio track of an asset to produce a visual representation of the waveform. To produce an asset from media such as sample buffers or still images, use an AVAssetWriter object.

    当你想对一项资产的内容进行操作时,使用 AVAssetReader 。例如,可以读取一个资产的音频轨道,以产生波形的可视化表示。为了从媒体(比如样品缓冲或者静态图像)生成资产,使用 AVAssetWriter 对象。

    Note: The asset reader and writer classes are not intended to be used for real-time processing. In fact, an asset reader cannot even be used for reading from a real-time source like an HTTP live stream. However, if you are using an asset writer with a real-time data source, such as an AVCaptureOutput object, set the expectsMediaDataInRealTime property of your asset writer’s inputs to YES. Setting this property to YES for a non-real-time data source will result in your files not being interleaved properly.

    注意:资产 readerwriter 类不打算用到实时处理。实际上,一个资产读取器甚至不能用于从一个类似 HTTP 直播流的实时资源中读取。然而,如果你使用带着实时数据资源的资产写入器,比如 AVCaptureOutput 对象,设置资产写入器入口的 expectsMediaDataInRealTime 属性为 YES。将此属性设置为 YES 的非实时数据源将导致你的文件不能被正确的扫描。

    Reading an Asset - 读取资产

    Each AVAssetReader object can be associated only with a single asset at a time, but this asset may contain multiple tracks. For this reason, you must assign concrete subclasses of the AVAssetReaderOutput class to your asset reader before you begin reading in order to configure how the media data is read. There are three concrete subclasses of the AVAssetReaderOutput base class that you can use for your asset reading needs: AVAssetReaderTrackOutput, AVAssetReaderAudioMixOutput, and AVAssetReaderVideoCompositionOutput.

    每个 AVAssetReader 对象只能与单个资产有关,但这个资产可能包含多个轨道。为此,你必须指定 AVAssetReaderOutput 类的具体子类给你的资产读取器,在你开始按顺序访问你的资产以配置如何读取数据之前。有 AVAssetReaderOutput 基类的3个具体子类,可以使用你的资产访问需求 AVAssetReaderTrackOutputAVAssetReaderAudioMixOutputAVAssetReaderVideoCompositionOutput

    Creating the Asset Reader - 创建资产读取器

    All you need to initialize an AVAssetReader object is the asset that you want to read.

    所有你需要去初始化 AVAssetReader 对象是你想要访问的资产。

    NSError *outError;
    AVAsset *someAsset = <#AVAsset that you want to read#>;
    AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:someAsset error:&outError];
    BOOL success = (assetReader != nil);

    Note: Always check that the asset reader returned to you is non-nil to ensure that the asset reader was initialized successfully. Otherwise, the error parameter (outError in the previous example) will contain the relevant error information.

    注意:总是要资产读取器是否返回给你的时 non-nil ,以确保资产读取器已经成功被初始化。否则,错误参数(之前的例子中 outError)将会包含有关错误的信息。

    Setting Up the Asset Reader Outputs - 建立资产读取器出口

    After you have created your asset reader, set up at least one output to receive the media data being read. When setting up your outputs, be sure to set the alwaysCopiesSampleData property to NO. In this way, you reap the benefits of performance improvements. In all of the examples within this chapter, this property could and should be set to NO.

    在你创建了资产读取器之后,至少设置一个出口以接收正在读取的媒体数据。当建立你的出口,确保设置 alwaysCopiesSampleData 属性为 NO。这样,你就收获了性能改进的好处。这一章的所有例子中,这个属性可以并且应该被设置为 NO

    If you want only to read media data from one or more tracks and potentially convert that data to a different format, use the AVAssetReaderTrackOutput class, using a single track output object for each AVAssetTrack object that you want to read from your asset. To decompress an audio track to Linear PCM with an asset reader, you set up your track output as follows:

    如果你只想从一个或多个轨道读取媒体数据,潜在的数据转换为不同的格式,使用 AVAssetReaderTrackOutput 类,每个你想从你的资产中读取 AVAssetTrack 对象都使用单轨道出口对象。将音频轨道解压缩为有资产读取器的 Linear PCM ,建立轨道出口如下:

    AVAsset *localAsset = assetReader.asset;
    // Get the audio track to read.
    AVAssetTrack *audioTrack = [[localAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
    // Decompression settings for Linear PCM
    NSDictionary *decompressionAudioSettings = @{ AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM] };
    // Create the output with the audio track and decompression settings.
    AVAssetReaderOutput *trackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:decompressionAudioSettings];
    // Add the output to the reader if possible.
    if ([assetReader canAddOutput:trackOutput])
        [assetReader addOutput:trackOutput];

    Note: To read the media data from a specific asset track in the format in which it was stored, pass nil to the outputSettings parameter.

    注意:从一个特定的资产轨道读取媒体数据,以它被存储的格式,传 niloutputSettings 参数。

    You use the AVAssetReaderAudioMixOutput and AVAssetReaderVideoCompositionOutput classes to read media data that has been mixed or composited together using an AVAudioMix object or AVVideoComposition object, respectively. Typically, these outputs are used when your asset reader is reading from an AVComposition object.

    使用 AVAssetReaderAudioMixOutputAVAssetReaderVideoCompositionOutput 类来读取媒体数据,这些媒体数据是分别使用 AVAudioMix 对象或者 AVVideoComposition 对象混合或者组合在一起。通常情况下,当你的资产读取器正在从 AVComposition 读取时,才使用这些出口。

    With a single audio mix output, you can read multiple audio tracks from your asset that have been mixed together using an AVAudioMix object. To specify how the audio tracks are mixed, assign the mix to the AVAssetReaderAudioMixOutput object after initialization. The following code displays how to create an audio mix output with all of the audio tracks from your asset, decompress the audio tracks to Linear PCM, and assign an audio mix object to the output. For details on how to configure an audio mix, see Editing.

    一个单一音频混合出口,可以从 已经使用 AVAudioMix 对象混合在一起的资产中读取多个音轨。指定音轨是如何被混合在一起的,将混合后的 AVAssetReaderAudioMixOutput 对象初始化。下面的代码显示了如何从资产中创建一个带着所有音轨的音频混合出口,将音轨解压为 Linear PCM,并指定音频混合对象到出口。有如何配置音频混合的细节,请参见 Editing

    AVAudioMix *audioMix = <#An AVAudioMix that specifies how the audio tracks from the AVAsset are mixed#>;
    // Assumes that assetReader was initialized with an AVComposition object.
    AVComposition *composition = (AVComposition *)assetReader.asset;
    // Get the audio tracks to read.
    NSArray *audioTracks = [composition tracksWithMediaType:AVMediaTypeAudio];
    // Get the decompression settings for Linear PCM.
    NSDictionary *decompressionAudioSettings = @{ AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM] };
    // Create the audio mix output with the audio tracks and decompression setttings.
    AVAssetReaderOutput *audioMixOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:audioTracks audioSettings:decompressionAudioSettings];
    // Associate the audio mix used to mix the audio tracks being read with the output.
    audioMixOutput.audioMix = audioMix;
    // Add the output to the reader if possible.
    if ([assetReader canAddOutput:audioMixOutput])
        [assetReader addOutput:audioMixOutput];

    Note: Passing nil for the audioSettings parameter tells the asset reader to return samples in a convenient uncompressed format. The same is true for the AVAssetReaderVideoCompositionOutput class.

    注意:给 audioSettings 参数传递 nil ,告诉资产读取器返回一个方便的未压缩格式的样本。对于 AVAssetReaderVideoCompositionOutput 类同样是可以的。

    The video composition output behaves in much the same way: You can read multiple video tracks from your asset that have been composited together using an AVVideoComposition object. To read the media data from multiple composited video tracks and decompress it to ARGB, set up your output as follows:

    视频合成输出行为有许多同样的方式:可以从资产(已经被使用 AVVideoComposition 对象合并在一起)读取多个视频轨道。从多个复合视频轨道读取媒体数据,解压缩为 ARGB ,建立出口如下:

    AVVideoComposition *videoComposition = <#An AVVideoComposition that specifies how the video tracks from the AVAsset are composited#>;
    // Assumes assetReader was initialized with an AVComposition.
    AVComposition *composition = (AVComposition *)assetReader.asset;
    // Get the video tracks to read.
    NSArray *videoTracks = [composition tracksWithMediaType:AVMediaTypeVideo];
    // Decompression settings for ARGB.
    NSDictionary *decompressionVideoSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32ARGB], (id)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary] };
    // Create the video composition output with the video tracks and decompression setttings.
    AVAssetReaderOutput *videoCompositionOutput = [AVAssetReaderVideoCompositionOutput assetReaderVideoCompositionOutputWithVideoTracks:videoTracks videoSettings:decompressionVideoSettings];
    // Associate the video composition used to composite the video tracks being read with the output.
    videoCompositionOutput.videoComposition = videoComposition;
    // Add the output to the reader if possible.
    if ([assetReader canAddOutput:videoCompositionOutput])
        [assetReader addOutput:videoCompositionOutput];

    Reading the Asset’s Media Data - 读取资产媒体数据

    To start reading after setting up all of the outputs you need, call the startReading method on your asset reader. Next, retrieve the media data individually from each output using the copyNextSampleBuffer method. To start up an asset reader with a single output and read all of its media samples, do the following:

    开始读取后建立所有你需要的出口,在你的资产读取器中调用 startReading 方法。下一步,使用 copyNextSampleBuffer 方法从每个出口分别获取媒体数据。以一个出口启动一个资产读取器,并读取它的所有媒体样本,跟着下面做:

    // Start the asset reader up.
    [self.assetReader startReading];
    BOOL done = NO;
    while (!done)
    {
      // Copy the next sample buffer from the reader output.
      CMSampleBufferRef sampleBuffer = [self.assetReaderOutput copyNextSampleBuffer];
      if (sampleBuffer)
      {
        // Do something with sampleBuffer here.
        CFRelease(sampleBuffer);
        sampleBuffer = NULL;
      }
      else
      {
        // Find out why the asset reader output couldn't copy another sample buffer.
        if (self.assetReader.status == AVAssetReaderStatusFailed)
        {
          NSError *failureError = self.assetReader.error;
          // Handle the error here.
        }
        else
        {
          // The asset reader output has read all of its samples.
          done = YES;
        }
      }
    }

    Writing an Asset - 写入资产

    The AVAssetWriter class to write media data from multiple sources to a single file of a specified file format. You don’t need to associate your asset writer object with a specific asset, but you must use a separate asset writer for each output file that you want to create. Because an asset writer can write media data from multiple sources, you must create an AVAssetWriterInput object for each individual track that you want to write to the output file. Each AVAssetWriterInput object expects to receive data in the form of CMSampleBufferRef objects, but if you want to append CVPixelBufferRef objects to your asset writer input, use the AVAssetWriterInputPixelBufferAdaptor class.

    AVAssetWriter 类从多个源将媒体数据写入到指定文件格式的单个文件中。不需要将你的资产写入器与一个特定的资产联系起来,但你必须为你要创建的每个输出文件 使用一个独立的资产写入器。因为一个资产写入器可以从多个来源写入媒体数据,你必须为你想写入输出文件的每个独立的轨道创建一个 AVAssetWriterInput 对象。每个 AVAssetWriterInput 对象预计以 CMSampleBufferRef 对象的形成接收数据,但如果你想给你的资产写入器入口 附加 CVPixelBufferRef 对象,使用 AVAssetWriterInputPixelBufferAdaptor 类。

    Creating the Asset Writer - 创建资产写入器

    To create an asset writer, specify the URL for the output file and the desired file type. The following code displays how to initialize an asset writer to create a QuickTime movie:

    为了创建一个资产写入器,为出口文件指定 URL 和所需的文件类型。下面的代码显示了如何初始化一个资产写入器来创建一个 QuickTime 影片:

    NSError *outError;
    NSURL *outputURL = <#NSURL object representing the URL where you want to save the video#>;
    AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:outputURL
                                                          fileType:AVFileTypeQuickTimeMovie
                                                             error:&outError];
    BOOL success = (assetWriter != nil);
    

    Setting Up the Asset Writer Inputs - 建立资产写入器入口

    For your asset writer to be able to write media data, you must set up at least one asset writer input. For example, if your source of media data is already vending media samples as CMSampleBufferRef objects, just use the AVAssetWriterInput class. To set up an asset writer input that compresses audio media data to 128 kbps AAC and connect it to your asset writer, do the following:

    为你的资产写入器能够写入媒体数据,必须至少设置一个资产写入器入口。例如,如果你的媒体数据源已经以 CMSampleBufferRef 对象声明了声明了媒体样本,只使用 AVAssetWriterInput 类。建立一个资产写入器入口,将音频媒体数据压缩到 128 kbps AAC 并且将它与你的资产写入器连接,跟着下面做:

    // Configure the channel layout as stereo.
    AudioChannelLayout stereoChannelLayout = {
        .mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
        .mChannelBitmap = 0,
        .mNumberChannelDescriptions = 0
    };
    
    // Convert the channel layout object to an NSData object.
    NSData *channelLayoutAsData = [NSData dataWithBytes:&stereoChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
    
    // Get the compression settings for 128 kbps AAC.
    NSDictionary *compressionAudioSettings = @{
        AVFormatIDKey         : [NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC],
        AVEncoderBitRateKey   : [NSNumber numberWithInteger:128000],
        AVSampleRateKey       : [NSNumber numberWithInteger:44100],
        AVChannelLayoutKey    : channelLayoutAsData,
        AVNumberOfChannelsKey : [NSNumber numberWithUnsignedInteger:2]
    };
    
    // Create the asset writer input with the compression settings and specify the media type as audio.
    AVAssetWriterInput *assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:compressionAudioSettings];
    // Add the input to the writer if possible.
    if ([assetWriter canAddInput:assetWriterInput])
        [assetWriter addInput:assetWriterInput];

    Note: If you want the media data to be written in the format in which it was stored, pass nil in the outputSettings parameter. Pass nil only if the asset writer was initialized with a fileType of AVFileTypeQuickTimeMovie.

    注意:如果你想让媒体数据以它被存储的格式写入,给 outputSettings 参数传 nil。只有资产写入器曾用 AVFileTypeQuickTimeMoviefileType 初始化,才传nil

    Your asset writer input can optionally include some metadata or specify a different transform for a particular track using the metadata and transform properties respectively. For an asset writer input whose data source is a video track, you can maintain the video’s original transform in the output file by doing the following:

    你的资产写入器入口可以选择性的包含一些元数据 或者 分别使用 metadatatransform 属性为特定的轨道指定不同的变换。对于一个资产写入器的入口,其数据源是一个视频轨道,可以通过下面示例来在输出文件中维持视频的原始变换:

    AVAsset *videoAsset = <#AVAsset with at least one video track#>;
    AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    assetWriterInput.transform = videoAssetTrack.preferredTransform;

    Note: Set the metadata and transform properties before you begin writing with your asset writer for them to take effect.

    注意:在开始用资产写入器写入生效之前,先设置 metadatatransform 属性。

    When writing media data to the output file, sometimes you may want to allocate pixel buffers. To do so, use the AVAssetWriterInputPixelBufferAdaptor class. For greatest efficiency, instead of adding pixel buffers that were allocated using a separate pool, use the pixel buffer pool provided by the pixel buffer adaptor. The following code creates a pixel buffer object working in the RGB domain that will use CGImage objects to create its pixel buffers.

    当将媒体数据写入输出文件时,有时你可能要分配像素缓冲区。这样做:使用 AVAssetWriterInputPixelBufferAdaptor 类。为了最大的效率,使用由像素缓冲适配器提供的像素缓冲池,代替添加被分配使用一个单独池的像素缓冲区。下面的代码创建一个像素缓冲区对象,在 RGB 色彩下工作,将使用 CGImage 对象创建它的像素缓冲。

    NSDictionary *pixelBufferAttributes = @{
         kCVPixelBufferCGImageCompatibilityKey : [NSNumber numberWithBool:YES],
         kCVPixelBufferCGBitmapContextCompatibilityKey : [NSNumber numberWithBool:YES],
         kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_32ARGB]
    };
    AVAssetWriterInputPixelBufferAdaptor *inputPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.assetWriterInput sourcePixelBufferAttributes:pixelBufferAttributes];

    Note: All AVAssetWriterInputPixelBufferAdaptor objects must be connected to a single asset writer input. That asset writer input must accept media data of type AVMediaTypeVideo.

    注:所有的 AVAssetWriterInputPixelBufferAdaptor 对象必须连接到一个单独的资产写入器入口。资产写入器入口必须接受 AVMediaTypeVideo 类型的媒体数据。

    Writing Media Data - 写入媒体数据

    When you have configured all of the inputs needed for your asset writer, you are ready to begin writing media data. As you did with the asset reader, initiate the writing process with a call to the startWriting method. You then need to start a sample-writing session with a call to the startSessionAtSourceTime: method. All writing done by an asset writer has to occur within one of these sessions and the time range of each session defines the time range of media data included from within the source. For example, if your source is an asset reader that is supplying media data read from an AVAsset object and you don’t want to include media data from the first half of the asset, you would do the following:

    当你已经为资产写入器配置所有需要的入口时,这时已经准备好开始写入媒体数据。正如在资产读取器所做的,调用 startWriting 方法发起写入过程。然后你需要启动一个样本 – 调用 startSessionAtSourceTime: 方法的写入会话。资产写入器的所有写入都必须在这些会话中发生,并且每个会话的时间范围 定义 包含在来源内媒体数据的时间范围。例如,如果你的来源是一个资产读取器(它从 AVAsset 对象读取到供应的媒体数据),并且你不想包含来自资产的前半部分的媒体数据,你可以像下面这样做:

    CMTime halfAssetDuration = CMTimeMultiplyByFloat64(self.asset.duration, 0.5);
    [self.assetWriter startSessionAtSourceTime:halfAssetDuration];
    //Implementation continues.

    Normally, to end a writing session you must call the endSessionAtSourceTime: method. However, if your writing session goes right up to the end of your file, you can end the writing session simply by calling the finishWriting method. To start up an asset writer with a single input and write all of its media data, do the following:

    通常,必须调用 endSessionAtSourceTime: 方法结束写入会话。然而,如果你的写入会话正确走到了你的文件末尾,可以简单地通过调用 finishWriting 方法来结束写入会话。要启动一个有单一入口的资产写入器并且写入所有媒体数据。下面示例:

    // Prepare the asset writer for writing.
    [self.assetWriter startWriting];
    // Start a sample-writing session.
    [self.assetWriter startSessionAtSourceTime:kCMTimeZero];
    // Specify the block to execute when the asset writer is ready for media data and the queue to call it on.
    [self.assetWriterInput requestMediaDataWhenReadyOnQueue:myInputSerialQueue usingBlock:^{
         while ([self.assetWriterInput isReadyForMoreMediaData])
         {
              // Get the next sample buffer.
              CMSampleBufferRef nextSampleBuffer = [self copyNextSampleBufferToWrite];
              if (nextSampleBuffer)
              {
                   // If it exists, append the next sample buffer to the output file.
                   [self.assetWriterInput appendSampleBuffer:nextSampleBuffer];
                   CFRelease(nextSampleBuffer);
                   nextSampleBuffer = nil;
              }
              else
              {
                   // Assume that lack of a next sample buffer means the sample buffer source is out of samples and mark the input as finished.
                   [self.assetWriterInput markAsFinished];
                   break;
              }
         }
    }];

    The copyNextSampleBufferToWrite method in the code above is simply a stub. The location of this stub is where you would need to insert some logic to return CMSampleBufferRef objects representing the media data that you want to write. One possible source of sample buffers is an asset reader output.

    上述代码中的 copyNextSampleBufferToWrite 方法仅仅是一个 stub。这个 stub 的位置就是你需要插入一些逻辑 去返回 CMSampleBufferRef 对象 表示你想要写入的媒体数据。示例缓冲区的可能来源是一个资产读取器出口。

    Reencoding Assets - 重新编码资产

    You can use an asset reader and asset writer object in tandem to convert an asset from one representation to another. Using these objects, you have more control over the conversion than you do with an AVAssetExportSession object. For example, you can choose which of the tracks you want to be represented in the output file, specify your own output format, or modify the asset during the conversion process. The first step in this process is just to set up your asset reader outputs and asset writer inputs as desired. After your asset reader and writer are fully configured, you start up both of them with calls to the startReading and startWriting methods, respectively. The following code snippet displays how to use a single asset writer input to write media data supplied by a single asset reader output:

    可以使用资产读取器和资产写入器对象,以一个表现转换到另一个表现的资产。使用这些对象,你必须比用 AVAssetExportSession 对象有更多的控制转换。例如,你可以选择输出文件中想要显示的轨道,指定你自己的输出格式,或者在转换过程中修改该资产。这个过程中第一步是按需建立你的资产读取器出口和资产写入器入口。资产读取器和写入器充分配置后,分别调用 startReadingstartWriting 方法启动它们。下面的代码片段显示了如何使用一个单一的资产写入器入口去写入 由一个单一的资产读取器出口提供的媒体数据:

    NSString *serializationQueueDescription = [NSString stringWithFormat:@"%@ serialization queue", self];
    
    // Create a serialization queue for reading and writing.
    dispatch_queue_t serializationQueue = dispatch_queue_create([serializationQueueDescription UTF8String], NULL);
    
    // Specify the block to execute when the asset writer is ready for media data and the queue to call it on.
    [self.assetWriterInput requestMediaDataWhenReadyOnQueue:serializationQueue usingBlock:^{
         while ([self.assetWriterInput isReadyForMoreMediaData])
         {
              // Get the asset reader output's next sample buffer.
              CMSampleBufferRef sampleBuffer = [self.assetReaderOutput copyNextSampleBuffer];
              if (sampleBuffer != NULL)
              {
                   // If it exists, append this sample buffer to the output file.
                   BOOL success = [self.assetWriterInput appendSampleBuffer:sampleBuffer];
                   CFRelease(sampleBuffer);
                   sampleBuffer = NULL;
                   // Check for errors that may have occurred when appending the new sample buffer.
                   if (!success && self.assetWriter.status == AVAssetWriterStatusFailed)
                   {
                        NSError *failureError = self.assetWriter.error;
                        //Handle the error.
                   }
              }
              else
              {
                   // If the next sample buffer doesn't exist, find out why the asset reader output couldn't vend another one.
                   if (self.assetReader.status == AVAssetReaderStatusFailed)
                   {
                        NSError *failureError = self.assetReader.error;
                        //Handle the error here.
                   }
                   else
                   {
                        // The asset reader output must have vended all of its samples. Mark the input as finished.
                        [self.assetWriterInput markAsFinished];
                        break;
                   }
              }
         }
    }];
    

    Putting It All Together: Using an Asset Reader and Writer in Tandem to Reencode an Asset - 总结:使用资产读取器和写入器串联重新编码资产

    This brief code example illustrates how to use an asset reader and writer to reencode the first video and audio track of an asset into a new file. It shows how to:

    • Use serialization queues to handle the asynchronous nature of reading and writing audiovisual data
    • Initialize an asset reader and configure two asset reader outputs, one for audio and one for video
    • Initialize an asset writer and configure two asset writer inputs, one for audio and one for video
    • Use an asset reader to asynchronously supply media data to an asset writer through two different - output/input combinations
    • Use a dispatch group to be notified of completion of the reencoding process
    • Allow a user to cancel the reencoding process once it has begun

    这个剪短的代码示例说明如何使用资产读取器和写入器将一个资产的第一个视频和音频轨道重新编码 到一个新文件。它展示了:

    • 使用序列化队列来处理读写视听数据的异步性
    • 初始化一个资产读取器,并配置两个资产读取器出口,一个用于音频,一个用于视频
    • 初始化一个资产写入器,并配置两个资产写入器入口,一个用于音频,一个用于视频
    • 使用一个资产读取器,通过两个不同的 输出/输入组合来异步向资产写入器提供媒体数据
    • 使用一个调度组接收重新编码过程的完成的通知
    • 一旦开始,允许用户取消重新编码过程

    Note: To focus on the most relevant code, this example omits several aspects of a complete application. To use AVFoundation, you are expected to have enough experience with Cocoa to be able to infer the missing pieces.

    注:关注最相关的代码,这个例子中省略了一个完成应用程序的几个方面。为了使用 AVFoundation ,希望你有足够的 Cocoa 经验,能够推断缺少的代码。

    Handling the Initial Setup - 处理初始设置

    Before you create your asset reader and writer and configure their outputs and inputs, you need to handle some initial setup. The first part of this setup involves creating three separate serialization queues to coordinate the reading and writing process.

    在创建资产读取器和写入器和配置它们的出口和入口之前,你需要处理一下初始设置。此设置的第一部分包括创建3个独立的序列化队列来协调读写过程。

    NSString *serializationQueueDescription = [NSString stringWithFormat:@"%@ serialization queue", self];
    
    // Create the main serialization queue.
    self.mainSerializationQueue = dispatch_queue_create([serializationQueueDescription UTF8String], NULL);
    NSString *rwAudioSerializationQueueDescription = [NSString stringWithFormat:@"%@ rw audio serialization queue", self];
    
    // Create the serialization queue to use for reading and writing the audio data.
    self.rwAudioSerializationQueue = dispatch_queue_create([rwAudioSerializationQueueDescription UTF8String], NULL);
    NSString *rwVideoSerializationQueueDescription = [NSString stringWithFormat:@"%@ rw video serialization queue", self];
    
    // Create the serialization queue to use for reading and writing the video data.
    self.rwVideoSerializationQueue = dispatch_queue_create([rwVideoSerializationQueueDescription UTF8String], NULL);

    The main serialization queue is used to coordinate the starting and stopping of the asset reader and writer (perhaps due to cancellation) and the other two serialization queues are used to serialize the reading and writing by each output/input combination with a potential cancellation.

    主序列队列用于协调资产读取器和写入器(可能是由于注销)的启动和停止,其他两个序列队列用于序列化读取器和写入器,通过每一个有潜在注销的输入/输出组合。

    Now that you have some serialization queues, load the tracks of your asset and begin the reencoding process.

    现在你有一些序列化队列,加载你的资产轨道,并开始重新编码过程。

    self.asset = <#AVAsset that you want to reencode#>;
    self.cancelled = NO;
    self.outputURL = <#NSURL representing desired output URL for file generated by asset writer#>;
    // Asynchronously load the tracks of the asset you want to read.
    [self.asset loadValuesAsynchronouslyForKeys:@[@"tracks"] completionHandler:^{
         // Once the tracks have finished loading, dispatch the work to the main serialization queue.
         dispatch_async(self.mainSerializationQueue, ^{
              // Due to asynchronous nature, check to see if user has already cancelled.
              if (self.cancelled)
                   return;
              BOOL success = YES;
              NSError *localError = nil;
              // Check for success of loading the assets tracks.
              success = ([self.asset statusOfValueForKey:@"tracks" error:&localError] == AVKeyValueStatusLoaded);
              if (success)
              {
                   // If the tracks loaded successfully, make sure that no file exists at the output path for the asset writer.
                   NSFileManager *fm = [NSFileManager defaultManager];
                   NSString *localOutputPath = [self.outputURL path];
                   if ([fm fileExistsAtPath:localOutputPath])
                        success = [fm removeItemAtPath:localOutputPath error:&localError];
              }
              if (success)
                   success = [self setupAssetReaderAndAssetWriter:&localError];
              if (success)
                   success = [self startAssetReaderAndWriter:&localError];
              if (!success)
                   [self readingAndWritingDidFinishSuccessfully:success withError:localError];
         });
    }];

    When the track loading process finishes, whether successfully or not, the rest of the work is dispatched to the main serialization queue to ensure that all of this work is serialized with a potential cancellation. Now all that’s left is to implement the cancellation process and the three custom methods at the end of the previous code listing.

    当轨道加载过程结束后,无论成功与否,剩下的工作就是被分配到主序列队列以确保所有的工作都是有潜在注销的序列化。现在,剩下就是实现注销进程和前面的代码清单的结尾处的3个自定义方法。

    Initializing the Asset Reader and Writer - 初始化资产读取器和写入器

    The custom setupAssetReaderAndAssetWriter: method initializes the reader and writer and configures two output/input combinations, one for an audio track and one for a video track. In this example, the audio is decompressed to Linear PCM using the asset reader and compressed back to 128 kbps AAC using the asset writer. The video is decompressed to YUV using the asset reader and compressed to H.264 using the asset writer.

    自定义 setupAssetReaderAndAssetWriter: 方法初始化读取器和写入器,并且配置两个输入/输出组合,一个用于音频轨道,一个用于视频轨道。在这个例子中,使用资产读取器音频被解压缩到 Linear PCM ,使用资产写入器压缩回 128 kbps AAC 。使用资产读取器将视频解压缩到 YUV ,使用资产写入器压缩为 H.264

    
    - (BOOL)setupAssetReaderAndAssetWriter:(NSError **)outError
    {
        // Create and initialize the asset reader.
        self.assetReader = [[AVAssetReader alloc] initWithAsset:self.asset error:outError];
        BOOL success = (self.assetReader != nil);
        if (success)
        {
            // If the asset reader was successfully initialized, do the same for the asset writer.
            self.assetWriter = [[AVAssetWriter alloc] initWithURL:self.outputURL
                                                         fileType:AVFileTypeQuickTimeMovie
                                                            error:outError];
            success = (self.assetWriter != nil);
        }
    
        if (success)
        {
            // If the reader and writer were successfully initialized, grab the audio and video asset tracks that will be used.
            AVAssetTrack *assetAudioTrack = nil, *assetVideoTrack = nil;
            NSArray *audioTracks = [self.asset tracksWithMediaType:AVMediaTypeAudio];
            if ([audioTracks count] > 0)
                assetAudioTrack = [audioTracks objectAtIndex:0];
            NSArray *videoTracks = [self.asset tracksWithMediaType:AVMediaTypeVideo];
            if ([videoTracks count] > 0)
                assetVideoTrack = [videoTracks objectAtIndex:0];
    
            if (assetAudioTrack)
            {
                // If there is an audio track to read, set the decompression settings to Linear PCM and create the asset reader output.
                NSDictionary *decompressionAudioSettings = @{ AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM] };
                self.assetReaderAudioOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:assetAudioTrack
                                                                                         outputSettings:decompressionAudioSettings];
                [self.assetReader addOutput:self.assetReaderAudioOutput];
                // Then, set the compression settings to 128kbps AAC and create the asset writer input.
                AudioChannelLayout stereoChannelLayout = {
                    .mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
                    .mChannelBitmap = 0,
                    .mNumberChannelDescriptions = 0
                };
                NSData *channelLayoutAsData = [NSData dataWithBytes:&stereoChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
                NSDictionary *compressionAudioSettings = @{
                                                           AVFormatIDKey         : [NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC],
                                                           AVEncoderBitRateKey   : [NSNumber numberWithInteger:128000],
                                                           AVSampleRateKey       : [NSNumber numberWithInteger:44100],
                                                           AVChannelLayoutKey    : channelLayoutAsData,
                                                           AVNumberOfChannelsKey : [NSNumber numberWithUnsignedInteger:2]
                                                           };
                self.assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:[assetAudioTrack mediaType]
                                                                                outputSettings:compressionAudioSettings];
                [self.assetWriter addInput:self.assetWriterAudioInput];
            }
    
            if (assetVideoTrack)
            {
                // If there is a video track to read, set the decompression settings for YUV and create the asset reader output.
                NSDictionary *decompressionVideoSettings = @{
                                                             (id)kCVPixelBufferPixelFormatTypeKey     : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8],
                                                             (id)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary]
                                                             };
                self.assetReaderVideoOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:assetVideoTrack
                                                                                         outputSettings:decompressionVideoSettings];
                [self.assetReader addOutput:self.assetReaderVideoOutput];
                CMFormatDescriptionRef formatDescription = NULL;
                // Grab the video format descriptions from the video track and grab the first one if it exists.
                NSArray *videoFormatDescriptions = [assetVideoTrack formatDescriptions];
                if ([videoFormatDescriptions count] > 0)
                    formatDescription = (__bridge CMFormatDescriptionRef)[formatDescriptions objectAtIndex:0];
                CGSize trackDimensions = {
                    .width = 0.0,
                    .height = 0.0,
                };
                // If the video track had a format description, grab the track dimensions from there. Otherwise, grab them direcly from the track itself.
                if (formatDescription)
                    trackDimensions = CMVideoFormatDescriptionGetPresentationDimensions(formatDescription, false, false);
                else
                    trackDimensions = [assetVideoTrack naturalSize];
                NSDictionary *compressionSettings = nil;
                // If the video track had a format description, attempt to grab the clean aperture settings and pixel aspect ratio used by the video.
                if (formatDescription)
                {
                    NSDictionary *cleanAperture = nil;
                    NSDictionary *pixelAspectRatio = nil;
                    CFDictionaryRef cleanApertureFromCMFormatDescription = CMFormatDescriptionGetExtension(formatDescription, kCMFormatDescriptionExtension_CleanAperture);
                    if (cleanApertureFromCMFormatDescription)
                    {
                        cleanAperture = @{
                                          AVVideoCleanApertureWidthKey            : (id)CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureWidth),
                                          AVVideoCleanApertureHeightKey           : (id)CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureHeight),
                                          AVVideoCleanApertureHorizontalOffsetKey : (id)CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureHorizontalOffset),
                                          AVVideoCleanApertureVerticalOffsetKey   : (id)CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureVerticalOffset)
                                          };
                    }
                    CFDictionaryRef pixelAspectRatioFromCMFormatDescription = CMFormatDescriptionGetExtension(formatDescription, kCMFormatDescriptionExtension_PixelAspectRatio);
                    if (pixelAspectRatioFromCMFormatDescription)
                    {
                        pixelAspectRatio = @{
                                             AVVideoPixelAspectRatioHorizontalSpacingKey : (id)CFDictionaryGetValue(pixelAspectRatioFromCMFormatDescription, kCMFormatDescriptionKey_PixelAspectRatioHorizontalSpacing),
                                             AVVideoPixelAspectRatioVerticalSpacingKey   : (id)CFDictionaryGetValue(pixelAspectRatioFromCMFormatDescription, kCMFormatDescriptionKey_PixelAspectRatioVerticalSpacing)
                                             };
                    }
                    // Add whichever settings we could grab from the format description to the compression settings dictionary.
                    if (cleanAperture || pixelAspectRatio)
                    {
                        NSMutableDictionary *mutableCompressionSettings = [NSMutableDictionary dictionary];
                        if (cleanAperture)
                            [mutableCompressionSettings setObject:cleanAperture forKey:AVVideoCleanApertureKey];
                        if (pixelAspectRatio)
                            [mutableCompressionSettings setObject:pixelAspectRatio forKey:AVVideoPixelAspectRatioKey];
                        compressionSettings = mutableCompressionSettings;
                    }
                }
                // Create the video settings dictionary for H.264.
                NSMutableDictionary *videoSettings = (NSMutableDictionary *) @{
                                                                               AVVideoCodecKey  : AVVideoCodecH264,
                                                                               AVVideoWidthKey  : [NSNumber numberWithDouble:trackDimensions.width],
                                                                               AVVideoHeightKey : [NSNumber numberWithDouble:trackDimensions.height]
                                                                               };
                // Put the compression settings into the video settings dictionary if we were able to grab them.
                if (compressionSettings)
                    [videoSettings setObject:compressionSettings forKey:AVVideoCompressionPropertiesKey];
                // Create the asset writer input and add it to the asset writer.
                self.assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:[videoTrack mediaType]
                                                                                outputSettings:videoSettings];
                [self.assetWriter addInput:self.assetWriterVideoInput];
            }
        }
        return success;
    }
    

    Reencoding the Asset - 重新编码资产

    Provided that the asset reader and writer are successfully initialized and configured, the startAssetReaderAndWriter: method described in Handling the Initial Setup is called. This method is where the actual reading and writing of the asset takes place.

    如果资产读取器和写入器成功地初始化和配置,在 Handling the Initial Setup 中发现调用 startAssetReaderAndWriter: 方法。这个方法实际上是资产读写发生的地方。

    - (BOOL)startAssetReaderAndWriter:(NSError **)outError
    {
         BOOL success = YES;
         // Attempt to start the asset reader.
         success = [self.assetReader startReading];
         if (!success)
              *outError = [self.assetReader error];
         if (success)
         {
              // If the reader started successfully, attempt to start the asset writer.
              success = [self.assetWriter startWriting];
              if (!success)
                   *outError = [self.assetWriter error];
         }
    
         if (success)
         {
              // If the asset reader and writer both started successfully, create the dispatch group where the reencoding will take place and start a sample-writing session.
              self.dispatchGroup = dispatch_group_create();
              [self.assetWriter startSessionAtSourceTime:kCMTimeZero];
              self.audioFinished = NO;
              self.videoFinished = NO;
    
              if (self.assetWriterAudioInput)
              {
                   // If there is audio to reencode, enter the dispatch group before beginning the work.
                   dispatch_group_enter(self.dispatchGroup);
                   // Specify the block to execute when the asset writer is ready for audio media data, and specify the queue to call it on.
                   [self.assetWriterAudioInput requestMediaDataWhenReadyOnQueue:self.rwAudioSerializationQueue usingBlock:^{
                        // Because the block is called asynchronously, check to see whether its task is complete.
                        if (self.audioFinished)
                             return;
                        BOOL completedOrFailed = NO;
                        // If the task isn't complete yet, make sure that the input is actually ready for more media data.
                        while ([self.assetWriterAudioInput isReadyForMoreMediaData] && !completedOrFailed)
                        {
                             // Get the next audio sample buffer, and append it to the output file.
                             CMSampleBufferRef sampleBuffer = [self.assetReaderAudioOutput copyNextSampleBuffer];
                             if (sampleBuffer != NULL)
                             {
                                  BOOL success = [self.assetWriterAudioInput appendSampleBuffer:sampleBuffer];
                                  CFRelease(sampleBuffer);
                                  sampleBuffer = NULL;
                                  completedOrFailed = !success;
                             }
                             else
                             {
                                  completedOrFailed = YES;
                             }
                        }
                        if (completedOrFailed)
                        {
                             // Mark the input as finished, but only if we haven't already done so, and then leave the dispatch group (since the audio work has finished).
                             BOOL oldFinished = self.audioFinished;
                             self.audioFinished = YES;
                             if (oldFinished == NO)
                             {
                                  [self.assetWriterAudioInput markAsFinished];
                             }
                             dispatch_group_leave(self.dispatchGroup);
                        }
                   }];
              }
    
              if (self.assetWriterVideoInput)
              {
                   // If we had video to reencode, enter the dispatch group before beginning the work.
                   dispatch_group_enter(self.dispatchGroup);
                   // Specify the block to execute when the asset writer is ready for video media data, and specify the queue to call it on.
                   [self.assetWriterVideoInput requestMediaDataWhenReadyOnQueue:self.rwVideoSerializationQueue usingBlock:^{
                        // Because the block is called asynchronously, check to see whether its task is complete.
                        if (self.videoFinished)
                             return;
                        BOOL completedOrFailed = NO;
                        // If the task isn't complete yet, make sure that the input is actually ready for more media data.
                        while ([self.assetWriterVideoInput isReadyForMoreMediaData] && !completedOrFailed)
                        {
                             // Get the next video sample buffer, and append it to the output file.
                             CMSampleBufferRef sampleBuffer = [self.assetReaderVideoOutput copyNextSampleBuffer];
                             if (sampleBuffer != NULL)
                             {
                                  BOOL success = [self.assetWriterVideoInput appendSampleBuffer:sampleBuffer];
                                  CFRelease(sampleBuffer);
                                  sampleBuffer = NULL;
                                  completedOrFailed = !success;
                             }
                             else
                             {
                                  completedOrFailed = YES;
                             }
                        }
                        if (completedOrFailed)
                        {
                             // Mark the input as finished, but only if we haven't already done so, and then leave the dispatch group (since the video work has finished).
                             BOOL oldFinished = self.videoFinished;
                             self.videoFinished = YES;
                             if (oldFinished == NO)
                             {
                                  [self.assetWriterVideoInput markAsFinished];
                             }
                             dispatch_group_leave(self.dispatchGroup);
                        }
                   }];
              }
              // Set up the notification that the dispatch group will send when the audio and video work have both finished.
              dispatch_group_notify(self.dispatchGroup, self.mainSerializationQueue, ^{
                   BOOL finalSuccess = YES;
                   NSError *finalError = nil;
                   // Check to see if the work has finished due to cancellation.
                   if (self.cancelled)
                   {
                        // If so, cancel the reader and writer.
                        [self.assetReader cancelReading];
                        [self.assetWriter cancelWriting];
                   }
                   else
                   {
                        // If cancellation didn't occur, first make sure that the asset reader didn't fail.
                        if ([self.assetReader status] == AVAssetReaderStatusFailed)
                        {
                             finalSuccess = NO;
                             finalError = [self.assetReader error];
                        }
                        // If the asset reader didn't fail, attempt to stop the asset writer and check for any errors.
                        if (finalSuccess)
                        {
                             finalSuccess = [self.assetWriter finishWriting];
                             if (!finalSuccess)
                                  finalError = [self.assetWriter error];
                        }
                   }
                   // Call the method to handle completion, and pass in the appropriate parameters to indicate whether reencoding was successful.
                   [self readingAndWritingDidFinishSuccessfully:finalSuccess withError:finalError];
              });
         }
         // Return success here to indicate whether the asset reader and writer were started successfully.
         return success;
    }

    During reencoding, the audio and video tracks are asynchronously handled on individual serialization queues to increase the overall performance of the process, but both queues are contained within the same dispatch group. By placing the work for each track within the same dispatch group, the group can send a notification when all of the work is done and the success of the reencoding process can be determined.

    重新编码期间,音频和视频轨道是在各自的串行队形上异步处理,来增加进程的整体性能,但两个队列包含在同一调度组中。为同一调度组内的每个轨道安排工作,当所有的工作完成,并能够确定重新编码过程的成功,该组可以发送一个通知。

    Handling Completion - 处理完成

    To handle the completion of the reading and writing process, the readingAndWritingDidFinishSuccessfully: method is called—with parameters indicating whether or not the reencoding completed successfully. If the process didn’t finish successfully, the asset reader and writer are both canceled and any UI related tasks are dispatched to the main queue.

    处理读写进程的完成,readingAndWritingDidFinishSuccessfully: 方法被调用,带着参数,指出重新编码是否成功完成。如果进程没有成功完成,该资产读取器和写入器都被取消,任何 UI 相关的任何都被发送到主队列中。

    - (void)readingAndWritingDidFinishSuccessfully:(BOOL)success withError:(NSError *)error
    {
         if (!success)
         {
              // If the reencoding process failed, we need to cancel the asset reader and writer.
              [self.assetReader cancelReading];
              [self.assetWriter cancelWriting];
              dispatch_async(dispatch_get_main_queue(), ^{
                   // Handle any UI tasks here related to failure.
              });
         }
         else
         {
              // Reencoding was successful, reset booleans.
              self.cancelled = NO;
              self.videoFinished = NO;
              self.audioFinished = NO;
              dispatch_async(dispatch_get_main_queue(), ^{
                   // Handle any UI tasks here related to success.
              });
         }
    }

    Handling Cancellation - 处理注销

    Using multiple serialization queues, you can allow the user of your app to cancel the reencoding process with ease. On the main serialization queue, messages are asynchronously sent to each of the asset reencoding serialization queues to cancel their reading and writing. When these two serialization queues complete their cancellation, the dispatch group sends a notification to the main serialization queue where the cancelled property is set to YES. You might associate the cancel method from the following code listing with a button on your UI.

    使用多个序列化队列,你可以提供方便,让你的应用程序的用户取消重新编码进程。在主串行队列,消息被异步发送到每个资产重编码序列化队列,来取消它们的读写。当这两个序列化队列完成它们的注销,调度组向主序列化队列(cancelled 属性被设置为 YES)发送一个通知.你可能从下面的代码将 cancel 方法与 UI 上的按钮关联起来。

    - (void)cancel
    {
         // Handle cancellation asynchronously, but serialize it with the main queue.
         dispatch_async(self.mainSerializationQueue, ^{
              // If we had audio data to reencode, we need to cancel the audio work.
              if (self.assetWriterAudioInput)
              {
                   // Handle cancellation asynchronously again, but this time serialize it with the audio queue.
                   dispatch_async(self.rwAudioSerializationQueue, ^{
                        // Update the Boolean property indicating the task is complete and mark the input as finished if it hasn't already been marked as such.
                        BOOL oldFinished = self.audioFinished;
                        self.audioFinished = YES;
                        if (oldFinished == NO)
                        {
                             [self.assetWriterAudioInput markAsFinished];
                        }
                        // Leave the dispatch group since the audio work is finished now.
                        dispatch_group_leave(self.dispatchGroup);
                   });
              }
    
              if (self.assetWriterVideoInput)
              {
                   // Handle cancellation asynchronously again, but this time serialize it with the video queue.
                   dispatch_async(self.rwVideoSerializationQueue, ^{
                        // Update the Boolean property indicating the task is complete and mark the input as finished if it hasn't already been marked as such.
                        BOOL oldFinished = self.videoFinished;
                        self.videoFinished = YES;
                        if (oldFinished == NO)
                        {
                             [self.assetWriterVideoInput markAsFinished];
                        }
                        // Leave the dispatch group, since the video work is finished now.
                        dispatch_group_leave(self.dispatchGroup);
                   });
              }
              // Set the cancelled Boolean property to YES to cancel any work on the main queue as well.
              self.cancelled = YES;
         });
    }

    Asset Output Settings Assistant - 资产出口设置助手

    The AVOutputSettingsAssistant class aids in creating output-settings dictionaries for an asset reader or writer. This makes setup much simpler, especially for high frame rate H264 movies that have a number of specific presets. Listing 5-1 shows an example that uses the output settings assistant to use the settings assistant.

    AVOutputSettingsAssistant 类在创建出口时能帮上忙 – 为资产读取器或者写入器设置字典。这使得设置更简单,特别是对于有一些具体的预设的高帧速率 H264 影片。 Listing 5-1 显示了使用输出设置助手去使用设置助手的例子。

    Listing 5-1 AVOutputSettingsAssistant sample

    AVOutputSettingsAssistant *outputSettingsAssistant = [AVOutputSettingsAssistant outputSettingsAssistantWithPreset:<some preset>];
    CMFormatDescriptionRef audioFormat = [self getAudioFormat];
    
    if (audioFormat != NULL)
        [outputSettingsAssistant setSourceAudioFormat:(CMAudioFormatDescriptionRef)audioFormat];
    
    CMFormatDescriptionRef videoFormat = [self getVideoFormat];
    
    if (videoFormat != NULL)
        [outputSettingsAssistant setSourceVideoFormat:(CMVideoFormatDescriptionRef)videoFormat];
    
    CMTime assetMinVideoFrameDuration = [self getMinFrameDuration];
    CMTime averageFrameDuration = [self getAvgFrameDuration]
    
    [outputSettingsAssistant setSourceVideoAverageFrameDuration:averageFrameDuration];
    [outputSettingsAssistant setSourceVideoMinFrameDuration:assetMinVideoFrameDuration];
    
    AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:<some URL> fileType:[outputSettingsAssistant outputFileType] error:NULL];
    AVAssetWriterInput *audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[outputSettingsAssistant audioSettings] sourceFormatHint:audioFormat];
    AVAssetWriterInput *videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:[outputSettingsAssistant videoSettings] sourceFormatHint:videoFormat];

    Time and Media Representations - 时间和媒体表现

    Time-based audiovisual data, such as a movie file or a video stream, is represented in the AV Foundation framework by AVAsset. Its structure dictates much of the framework works. Several low-level data structures that AV Foundation uses to represent time and media such as sample buffers come from the Core Media framework.

    基于视听资料的时间,比如一个电影文件或视频流,在AV Foundation 框架中是由 AVAsset 代表的。它的结构决定了大部分的框架工程。一些低层的数据结构(AV Foundation 使用来表示时间和媒体,比如样本缓冲区)来自 Core Media framework

    Representation of Assets - 资产的表示

    AVAsset is the core class in the AV Foundation framework. It provides a format-independent abstraction of time-based audiovisual data, such as a movie file or a video stream. The primary relationships are shown in Figure 6-1. In many cases, you work with one of its subclasses: You use the composition subclasses when you create new assets (see Editing), and you use AVURLAsset to create a new asset instance from media at a given URL (including assets from the MPMedia framework or the Asset Library framework—see Using Assets).

    AVAssetAV Foundation 框架的核心类。它提供了一个格式 – 与基于时间的视听数据的抽象无关,比如电影文件或视频流。主要的关系如图 6-1所示。在很多情况下,你都与它的一个子类一起工作:当你创建新的资产(见 Editing)使用组件的子类,并使用 AVURLAsset 从给定 URL 的媒体来创建一个新的资产实例。(包括来自 MPMedia 框架或者 Asset Library framework 的资产,见Using Assets


    Figure 6-1  AVAsset provides an abstraction of time-based audiovisual data

    An asset contains a collection of tracks that are intended to be presented or processed together, each of a uniform media type, including (but not limited to) audio, video, text, closed captions, and subtitles. The asset object provides information about whole resource, such as its duration or title, as well as hints for presentation, such as its natural size. Assets may also have metadata, represented by instances of AVMetadataItem.

    资产包含了一组轨道,旨在被一起呈现或一起处理,每一个统一的媒体类型,包括(但不仅限于)音频、视频、文本、隐藏式字幕,以及字幕。资产对象提供关于整个资源的信息,比如它的持续时间或标题,以及用于呈现提示的信息,例如它的自然尺寸。资产也有可能拥有元数据,通过 AVMetadataItem 的实例表示。

    A track is represented by an instance of AVAssetTrack, as shown in Figure 6-2. In a typical simple case, one track represents the audio component and another represents the video component; in a complex composition, there may be multiple overlapping tracks of audio and video.

    轨道由 AVAssetTrack 的实例表示,如图 6-2所示。在一个典型简单的情况下,一个轨道代表代表音频组件,另一个代表视频组件;在复杂的组成中,可以存在音频和视频的多个重叠的轨道。


    Figure 6-2  AVAssetTrack

    A track has a number of properties, such as its type (video or audio), visual and/or audible characteristics (as appropriate), metadata, and timeline (expressed in terms of its parent asset). A track also has an array of format descriptions. The array contains CMFormatDescription objects (see CMFormatDescriptionRef), each of which describes the format of media samples referenced by the track. A track that contains uniform media (for example, all encoded using to the same settings) will provide an array with a count of 1.

    轨道有许多属性,比如它的类型(视频或者音频),视觉和/或听觉特性(根据需要),元数据和时间轴(在其父资产表示)。一个轨道也有格式描述的数组。数组包含 CMFormatDescription 对象(见 CMFormatDescriptionRef),其中每一个都描述了轨道引用的媒体样本的格式。包含了统一媒体的轨道(例如,所有使用相同设置的编码)将提供计数为 1 的数组。

    A track may itself be divided into segments, represented by instances of AVAssetTrackSegment. A segment is a time mapping from the source to the asset track timeline.

    轨道自身可以被分成几段,由 AVAssetTrackSegment 的实例表示。一个片段是一个时间映射,从资源到资产轨道时间轴的映射。

    Representations of Time - 时间的表示

    Time in AV Foundation is represented by primitive structures from the Core Media framework.

    AV Foundation 中的时间是由来自 Core Media framework 的原始结构体表示的。

    CMTime Represents a Length of Time - CMTime 表示时间的长度

    CMTime is a C structure that represents time as a rational number, with a numerator (an int64_t value), and a denominator (an int32_t timescale). Conceptually, the timescale specifies the fraction of a second each unit in the numerator occupies. Thus if the timescale is 4, each unit represents a quarter of a second; if the timescale is 10, each unit represents a tenth of a second, and so on. You frequently use a timescale of 600, because this is a multiple of several commonly used frame rates: 24 fps for film, 30 fps for NTSC (used for TV in North America and Japan), and 25 fps for PAL (used for TV in Europe). Using a timescale of 600, you can exactly represent any number of frames in these systems.

    CMTime 是一个C语言的结构体,以一个有理数表示时间,有一个分子(一个 int64_t 值)和一个分母(一个 int32_t 时间刻度)。在概念上讲,时间刻度指定一秒中每个单元占据的分数。因此如果时间刻度为 4,每个单元代表一秒的四分之一;如果时间刻度为 10,每个单元代表一秒的十分之一,等等。经常使用时间刻度为 600,因为这是因为这是几种常用帧速率的倍数:24 fps的电影, 30 fpsNTSC(用在北美洲和日本的电视),25 fpsPAL(用于欧洲电视)。使用 600的时间刻度,可以在这些系统中精确的表示任意数量的帧。

    In addition to a simple time value, a CMTime structure can represent nonnumeric values: +infinity, -infinity, and indefinite. It can also indicate whether the time been rounded at some point, and it maintains an epoch number.

    除了简单的时间值,CMTime 结构体可以表示非数字的值:正无穷大、负无穷大,不确定的。它也可以表示时间在哪一位约等于,并且它能保持一个纪元数字。

    Using CMTime - 使用 CMTime

    You create a time using CMTimeMake or one of the related functions such as CMTimeMakeWithSeconds (which allows you to create a time using a float value and specify a preferred timescale). There are several functions for time-based arithmetic and for comparing times, as illustrated in the following example:

    使用 CMTimeMake 或一个相关功能的 来创建一个时间,例如 CMTimeMakeWithSeconds (它允许你使用浮点值来创建一个时间,并指定一个首选时间刻度)。有基于时间算术的和比较时间的几个功能,如下面的示例所示:

    CMTime time1 = CMTimeMake(200, 2); // 200 half-seconds
    CMTime time2 = CMTimeMake(400, 4); // 400 quarter-seconds
    
    // time1 and time2 both represent 100 seconds, but using different timescales.
    if (CMTimeCompare(time1, time2) == 0) {
        NSLog(@"time1 and time2 are the same");
    }
    
    Float64 float64Seconds = 200.0 / 3;
    CMTime time3 = CMTimeMakeWithSeconds(float64Seconds , 3); // 66.66... third-seconds
    time3 = CMTimeMultiply(time3, 3);
    // time3 now represents 200 seconds; next subtract time1 (100 seconds).
    time3 = CMTimeSubtract(time3, time1);
    CMTimeShow(time3);
    
    if (CMTIME_COMPARE_INLINE(time2, ==, time3)) {
        NSLog(@"time2 and time3 are the same");
    }

    For a list of all the available functions, see CMTime Reference.

    有关所有可用的功能列表,请参阅 CMTime Reference

    Special Values of CMTime - CMTime 的特殊值

    Core Media provides constants for special values: kCMTimeZero, kCMTimeInvalid, kCMTimePositiveInfinity, and kCMTimeNegativeInfinity. There are many ways in which a CMTime structure can, for example, represent a time that is invalid. To test whether a CMTime is valid, or a nonnumeric value, you should use an appropriate macro, such as CMTIME_IS_INVALID, CMTIME_IS_POSITIVE_INFINITY, or CMTIME_IS_INDEFINITE.

    Core Media 提供了特殊值的常量:kCMTimeZerokCMTimeInvalidkCMTimePositiveInfinity,以及 kCMTimeNegativeInfinity。有许多方法,例如,其中 CMTime 结构体可以表示一个无效的时间。为了测试CMTime 是否是无效的,或者是一个非数字值,应该使用一个适当的宏,比如 CMTIME_IS_INVALIDCMTIME_IS_POSITIVE_INFINITY,或者 CMTIME_IS_INDEFINITE

    CMTime myTime = <#Get a CMTime#>;
    if (CMTIME_IS_INVALID(myTime)) {
        // Perhaps treat this as an error; display a suitable alert to the user.
    }

    You should not compare the value of an arbitrary CMTime structure with kCMTimeInvalid.

    你不应该将一个任意的 CMTime 结构体的值与 kCMTimeInvalid 比较。

    Representing CMTime as an Object - CMTime表示为一个对象

    If you need to use CMTime structures in annotations or Core Foundation containers, you can convert a CMTime structure to and from a CFDictionary opaque type (see CFDictionaryRef) using the CMTimeCopyAsDictionary and CMTimeMakeFromDictionary functions, respectively. You can also get a string representation of a CMTime structure using the CMTimeCopyDescription function.

    如果你需要在注释或者 Core Foundation 容器中使用 CMTime 结构体,可以使用 CMTimeCopyAsDictionaryCMTime 结构体转换,使用 CMTimeMakeFromDictionary 从一个 CFDictionary 不透明的类型(见 CFDictionaryRef)。使用 CMTimeCopyDescription 函数可以得到一个 CMTime 结构体的字符串表示。

    Epochs - 纪元

    The epoch number of a CMTime structure is usually set to 0, but you can use it to distinguish unrelated timelines. For example, the epoch could be incremented through each cycle using a presentation loop, to differentiate between time N in loop 0 and time N in loop 1.

    CMTime 结构体的纪元数量通常设置为 0,但是你可以用它来区分不相关的时间轴。例如,纪元可以通过使用演示循环每个周期递增,区分循环0中的时间 N与循环1中的时间 N

    CMTimeRange Represents a Time Range - CMTimeRange表示一个时间范围

    CMTimeRange is a C structure that has a start time and duration, both expressed as CMTime structures. A time range does not include the time that is the start time plus the duration.

    You create a time range using CMTimeRangeMake or CMTimeRangeFromTimeToTime. There are constraints on the value of the CMTime epochs:

    • CMTimeRange structures cannot span different epochs.
    • The epoch in a CMTime structure that represents a timestamp may be nonzero, but you can only - perform range operations (such as CMTimeRangeGetUnion) on ranges whose start fields have the - same epoch.
    • The epoch in a CMTime structure that represents a duration should always be 0, and the value must be nonnegative.

    CMTimeRange 是一个 C语言结构体,有开始时间和持续时间,即表示为 CMTime 结构体。时间范围不包括开始时间加上持续时间。

    使用 CMTimeRangeMake 或者 CMTimeRangeFromTimeToTime 创建一个时间范围。有关 CMTime 纪元的值,有一些约束:

    • CMTimeRange 结构体不能跨越不同的纪元。
    • CMTime 结构体中的纪元,表示一个时间戳可能是非零,但你只能在其开始字段具有相同纪元的范围内执行范围操作(比如 CMTimeRangeGetUnion)。
    • CMTime 结构体中的纪元,表示持续时间应该总是为 0,并且值必须是非负数。

    Working with Time Ranges - 与时间范围工作

    Core Media provides functions you can use to determine whether a time range contains a given time or other time range, to determine whether two time ranges are equal, and to calculate unions and intersections of time ranges, such as CMTimeRangeContainsTime, CMTimeRangeEqual, CMTimeRangeContainsTimeRange, and CMTimeRangeGetUnion.

    Core Media 提供了一些功能,可用于确定一个时间范围是否包含一个特定的时间或其他时间范围,确定两个时间范围是否相等,并计算时间范围的接口和相交范围,比如 CMTimeRangeContainsTimeCMTimeRangeEqualCMTimeRangeContainsTimeRange,以及 CMTimeRangeGetUnion

    Given that a time range does not include the time that is the start time plus the duration, the following expression always evaluates to false:

    由于时间范围不包括开始时间加上持续时间,下面的表达式的结果总是为 false

    CMTimeRangeContainsTime(range, CMTimeRangeGetEnd(range))

    For a list of all the available functions, see CMTimeRange Reference.

    有关所有可用功能的列表,请参阅 CMTimeRange Reference

    Special Values of CMTimeRange - CMTimeRange 的特殊值

    Core Media provides constants for a zero-length range and an invalid range, kCMTimeRangeZero and kCMTimeRangeInvalid, respectively. There are many ways, though in which a CMTimeRange structure can be invalid, or zero—or indefinite (if one of the CMTime structures is indefinite. If you need to test whether a CMTimeRange structure is valid, zero, or indefinite, you should use an appropriate macro: CMTIMERANGE_IS_VALID, CMTIMERANGE_IS_INVALID, CMTIMERANGE_IS_EMPTY, or CMTIMERANGE_IS_EMPTY.

    Core Media 分别提供一个长度为0的范围和一个无效范围,就是kCMTimeRangeZerokCMTimeRangeInvalid。有很多种方法,尽管 CMTimeRange 结构可以是无效的,或为零,或是不确定的(如果CMTime 结构是不确定的)。如果你需要测试 `CMTimeRange 结构体是否是有效的,零,或者不确定,你应该使用适当的宏:CMTIMERANGE_IS_VALIDCMTIMERANGE_IS_INVALIDCMTIMERANGE_IS_EMPTY,或者 CMTIMERANGE_IS_INDEFINITE

    CMTimeRange myTimeRange = <#Get a CMTimeRange#>;
    if (CMTIMERANGE_IS_EMPTY(myTimeRange)) {
        // The time range is zero.
    }

    You should not compare the value of an arbitrary CMTimeRange structure with kCMTimeRangeInvalid.

    你不应该将任意的 CMTimeRange 结构体的值与 kCMTimeRangeInvalid进行比较。

    Representing a CMTimeRange Structure as an Object - 将 CMTimeRange 结构体表示为对象

    If you need to use CMTimeRange structures in annotations or Core Foundation containers, you can convert a CMTimeRange structure to and from a CFDictionary opaque type (see CFDictionaryRef) using CMTimeRangeCopyAsDictionary and CMTimeRangeMakeFromDictionary, respectively. You can also get a string representation of a CMTime structure using the CMTimeRangeCopyDescription function.

    如果你需要在注释或 Core Foundation 容器中使用 CMTimeRange 结构,可以使用 CMTimeRangeCopyAsDictionary 转换一个 CMTimeRange ,使用 CMTimeRangeMakeFromDictionary 从一个 CFDictionary 不透明类型 (见 CFDictionaryRef)。也可以 CMTimeRangeCopyDescription 功能得到 CMTime 结构的一个字符串表示。

    Representations of Media - 媒体的表示

    Video data and its associated metadata are represented in AV Foundation by opaque objects from the Core Media framework. Core Media represents video data using CMSampleBuffer (see CMSampleBufferRef). CMSampleBuffer is a Core Foundation-style opaque type; an instance contains the sample buffer for a frame of video data as a Core Video pixel buffer (see CVPixelBufferRef). You access the pixel buffer from a sample buffer using CMSampleBufferGetImageBuffer:

    视频数据和其相关的元数据都是被 AV Foundation 中来自 Core Media framework的不透明对象表示。Core Media 表示视频数据 使用 CMSampleBuffer(见 CMSampleBufferRef)。CMSampleBufferCore Foundation 风格的不透明类型;实例包含了用于作为Core Video 像素缓冲(见CVPixelBufferRef)的视频数据的单帧样品缓冲区。使用 CMSampleBufferGetImageBuffer 从一个样本缓冲区访问像素缓冲。

    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(<#A CMSampleBuffer#>);

    From the pixel buffer, you can access the actual video data. For an example, see Converting CMSampleBuffer to a UIImage Object.

    In addition to the video data, you can retrieve a number of other aspects of the video frame:

    • Timing information. You get accurate timestamps for both the original presentation time and - the decode time using CMSampleBufferGetPresentationTimeStamp and - CMSampleBufferGetDecodeTimeStamp respectively.
    • Format information. The format information is encapsulated in a CMFormatDescription object (- see CMFormatDescriptionRef). From the format description, you can get for example the pixel - type and video dimensions using CMVideoFormatDescriptionGetCodecType and - CMVideoFormatDescriptionGetDimensions respectively.
    • Metadata. Metadata are stored in a dictionary as an attachment. You use CMGetAttachment to retrieve the dictionary:

    从像素缓冲区,可以访问实际的视频数据。有个例子,请参阅 Converting CMSampleBuffer to a UIImage Object

    除了视频数据之外,可以从数据帧中检索多个其他方面的信息:

    CMSampleBufferRef sampleBuffer = <#Get a sample buffer#>;
    CFDictionaryRef metadataDictionary =
        CMGetAttachment(sampleBuffer, CFSTR("MetadataDictionary", NULL);
    if (metadataDictionary) {
        // Do something with the metadata.
    }

    Converting CMSampleBuffer to a UIImage Object - 将 CMSampleBuffer 转化为 UIImage 对象

    The following code shows how you can convert a CMSampleBuffer to a UIImage object. You should consider your requirements carefully before using it. Performing the conversion is a comparatively expensive operation. It is appropriate to, for example, create a still image from a frame of video data taken every second or so. You should not use this as a means to manipulate every frame of video coming from a capture device in real time.

    下面的代码展示了如何将一个 CMSampleBuffer 转化为一个 UIImage 对象。在使用它之前,应该仔细考虑你的要求。执行转换是一个相对昂贵的操作。例如,比较合适的是 从每一秒左右的视频数据的一帧创建一个静态图像。你不应该使用这个作为一种手段 去操作来自实时捕获设备的视频的每一帧。

    // Create a UIImage from sample buffer data
    - (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
    {
        // Get a CMSampleBuffer's Core Video image buffer for the media data
        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        // Lock the base address of the pixel buffer
        CVPixelBufferLockBaseAddress(imageBuffer, 0);
    
        // Get the number of bytes per row for the pixel buffer
        void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
    
        // Get the number of bytes per row for the pixel buffer
        size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
        // Get the pixel buffer width and height
        size_t width = CVPixelBufferGetWidth(imageBuffer);
        size_t height = CVPixelBufferGetHeight(imageBuffer);
    
        // Create a device-dependent RGB color space
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    
        // Create a bitmap graphics context with the sample buffer data
        CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
          bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
        // Create a Quartz image from the pixel data in the bitmap graphics context
        CGImageRef quartzImage = CGBitmapContextCreateImage(context);
        // Unlock the pixel buffer
        CVPixelBufferUnlockBaseAddress(imageBuffer,0);
    
        // Free up the context and color space
        CGContextRelease(context);
        CGColorSpaceRelease(colorSpace);
    
        // Create an image object from the Quartz image
        UIImage *image = [UIImage imageWithCGImage:quartzImage];
    
        // Release the Quartz image
        CGImageRelease(quartzImage);
    
        return (image);
    }

    后记:2016年8月7日,16:38,翻译至此结束:本文翻译的版本是官方文档2015-06-30版,也就是现在的最新版,翻译成果中还有许多需要校对的地方,希望查阅的小伙伴遇到问题能反馈给我。我也会在接下来的几天写 demo的同时,再次进行校对。感谢导师和leader,给我机会完成这项工作。

    展开全文
  • 我们之前设置了允许“匿名身份验证”,但每次连接服务器还是要求我们以管理员身份登录,这是为什么呢? 点击编辑“匿名身份验证”,可以发现匿名用户其实也有用户名IUSR,因此我们要将该匿名访问用户添加进来...
  • CRNN论文翻译——中英文对照

    万次阅读 2017-08-29 16:14:06
    结果是一个大的训练模型中有很多类,这很难泛化到其它类型的类序列对象,如中文文本,音乐配乐等,因为这种序列的基本组合数目可能大于100万。总之,目前基于DCNN的系统不能直接用于基于图像的序列识别。 ...
  • BIOS中英文对照

    千次阅读 2015-01-30 03:25:15
    BIOS中英文对照表: Time/System Time 时间/系统时间 Date/System Date 日期/系统日期 Level 2 Cache 二级缓存 System Memory 系统内存 Video Controller 视频控制器 Panel Type 液晶屏型号 Audio ...
  • LDPC编译码

    万次阅读 多人点赞 2018-03-07 19:28:32
    低密度校验码(LDPC码)是一种前向纠错码,LDPC码最早在20世纪60年代由Gallager在他的博士论文中提出,但限于当时的技术条件,缺乏可行的译码算法,此后的35年间基本上被人们忽略,其间由Tanner在1981年推广了LDPC码...
  • ResNet论文翻译——中英文对照

    千次阅读 2017-08-16 18:42:24
    文章作者:Tyan 博客:noahsnail.com &nbsp;|&nbsp; CSDN &nbsp;|&nbsp; 简书 ...声明:作者翻译论文仅为学习,如有侵权请联系作者删除博文,谢谢!...Deep Residual Learning for Ima...
  • 嵌入式基本知识必备

    万次阅读 多人点赞 2016-08-18 10:24:16
    电路的阈值电平,基本上是二分之一的电源电压值,但要保证稳定的输出,则必须要求输入高电平> Vih,输入低电平对于一般的逻辑电平,以上参数的关系如下:  Voh > Vih > Vt > Vil > Vol  6:Ioh:...
  • 《医院信息系统基本功能规范》 《医院信息系统基本功能规范》修订说明  卫生部于一九九七年印发公布的《医院信息系统软件基本功能规范》(以下简称《功能规范》),对于加快卫生信息化基础设施建设,规范管理,...
  • 金融tag对照

    千次阅读 2016-05-24 07:46:51
    包含在CDOL数据中要求送给卡片,由TDOL表示的数据作哈希运算的结果。 99 交易PIN数据 持卡人输入的PIN数据 9A 交易日期 ...
  • Squeeze-and-Excitation Networks论文翻译——中英文对照

    万次阅读 多人点赞 2017-11-22 16:51:28
    SE构建块的基本结构如图1所示。对于任何给定的变换 F t r : X → U F t r : X → U \mathbf{F}_{tr} : \mathbf{X} \rightarrow \mathbf{U} , X ∈ ℝ W ′ × H ′ × C ′ , U ∈ ℝ W × H × C X ∈ R W ′ × H...
  • 但是,在我们的设置中,1 × 1卷积有两个目的:最关键的是,它们主要是用来作为降维模块来移除卷积瓶颈,否则将会限制我们网络的大小。这不仅允许了深度的增加,而且允许我们网络的宽度增加但没有明显的性能损失。 ...
  • 然后在第二步,我们可以设置一些项目要求,比如原文能不能改动之类的,主要是这个选项,其他的都是翻译工作用的 第三步如图所示,我们开始翻译资源设置,也就是翻译记忆库设置,这里我们暂时采用本地记忆库,选择...
  • ImageNet包含各种分辨率的图像,而我们的系统要求不变的输入维度。因此,我们将图像进行下采样到固定的 256×256 分辨率。给定一个矩形图像,我们首先缩放图像短边长度为256,然后从结果图像中裁剪中心的 256×256 ...
  • 常用业务对照

    千次阅读 2016-04-27 10:11:37
    表的要求 表中使用的字段请尽量参照各模块的SAP字段标准使用习惯;  例:“ZXSLRZX销售组织对应的利润中心”中的销售组织应该使用VKORG、利润中心应该使用PRCTR。 根据表的用途,需确定是否属于配置目的、...
  • 第三步:接线及基本设置完成,电机已经可以正常运行,行驶过程中指示灯(LED)状态说明: 当油门摇杆处于中点区域时,红色和绿色LED均熄灭。 前进和倒车时,红色LED恒亮,当油门处于正向(前进)最高点时,绿色LED也...
  • 转载请标注 ... # =================================================================== ...设置时,优先于其他会话设置。 spring.mail.jndi-name= # Session JNDI name. When set, takes precedence ...
  • 网页背景设置的方法总结

    千次阅读 2016-05-14 18:53:49
    网页背景设置的方法总结本文会列出网页背景设置的三种方法,在目前学习初期看来,基本覆盖了所有需求,并且提供一些优秀的资源网站,方便图片的查找。根据需求不同,我将网页背景设置分为三种: 1. 直接单纯设置...
  • 常用对照

    千次阅读 2019-10-03 10:41:04
    HTTP Content-type: 文件扩展名Content-Type(Mime-Type)文件扩展名Content-Type(Mime-Type) .*( 二进制流,不知道下载文件类型) application/octet-stream .tif image/tiff ...applica...
  • 【论文翻译】ResNet论文中英对照翻译--(Deep Residual Learning for Image Recognition) 【中文译名】深度残差学习在图像识别中的应用 【论文链接】https://arxiv.org/pdf/1512.03385.pdf   【补充】 1)...
  • LedgerPageSetup Ldg页面设置表 Ldg页面设置表 49 t_Organization 客户表 客户资料 50 t_Kds_Report 二次开发平台报表 存放二次开发平台的报表 51 t_Kds_ReportClass 报表类别 报表存放的类别 52 t_Kds_...
  • 网络游戏专业术语中英文对照

    千次阅读 2012-12-06 17:45:51
    中英对照的网络游戏术语 AC – Armor Class,盔甲等级、级别 Account – 账号,与密码Password相对 Add – 一只玩家加入到组队中,如果请求别人组队,可说Add me pls. AOE – Area Effect Damage,区域作用魔法...
  • 文法和语言的基本知识

    千次阅读 2016-08-11 11:29:22
    表达式是描述数据运算的基本结构,它通常含有数据引用、算符和函数调用。 自下而上看上述层次结构 :我们希望通过对下层成分的理解来掌握上层成分,从而掌握整个程序。在下节中我们将综述程序语言各层次的结构和意义...
  • 1.知识点:可以对照下面的录屏进行阅读 SQL> --录屏工具spool,开始录制,并指定保存路径为c:\基本查询.txt SQL>spool c:\基本查询.txt SQL> --清屏 SQL> host cls SQL> --查看当前用户 SQL> show user USER 为 ...
  • BIOS 选项中英文对照

    千次阅读 2017-04-05 11:45:05
    BIOS中英文对照表: (来自IT天空) Time/System Time 时间/系统时间  Date/System Date 日期/系统日期  Level 2 Cache 二级缓存  System Memory 系统内存  Video Controller 视频控制器  Panel Type 液晶...
  • Oracle 错误代码(ORA)对照

    万次阅读 2019-04-10 15:24:43
    ORA-00017: 请求会话以设置跟踪事件ORA-00018: 超出最大会话数ORA-00019: 超出最大会话许可数ORA-00020: 超出最大进程数 ()ORA-00021: 会话附属于其它某些进程;无法转换会话ORA-00022: 无效的会话 ID;访问被拒绝...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 25,971
精华内容 10,388
关键字:

对照设置的基本要求