首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 平面设计 > 图形图像 >

iphone开发中的图像跟声音

2013-11-08 
iphone开发中的图像和声音媒体:图像和声音 ?本章内容q?? 访问和操作图像q?? 使用iPhone照相机q?? 播放声音

iphone开发中的图像和声音

媒体:图像和声音

?

本章内容

q?? 访问和操作图像

q?? 使用iPhone照相机

q?? 播放声音和视频

到目前为止,我们将iPhone编程的焦点主要集中在文本上。没错,我们显示过UIImage,比如说上一章中的山峰图像,但是我们只使用了最简单的方式。

iPhone实际上能提供更丰富、更引人入胜的体验。照相机、麦克风、完善的图片库和扬声器是内置在iPhone中的一些工具。本章我们将这些特性作为多媒体常规内容来介绍。我们将详细介绍图像,并简要介绍iPhone的媒体播放器以及在iPhone上播放声音。

更复杂的问题超出了本书范畴。我们将图像编辑的主题留到下一章介绍iPhone的图片库时再做介绍。对于更复杂的声音处理,我们将指引你去参考相关主题的大量Apple教程。

18.1 图像介绍

?

?

?

图18-1 图像可以显示在UIImageView

或UIView中

?

我们已经几次涉及图像使用,最早出现在第12章中,在第一个SDK例子中包含了图像。我们还总是在Interface Builder中创建UIImageView,将其附加到文件名,且无需考虑细节。

?

现在我们要考虑细节了。我们要来看Xcode中可用的一些选项,而不是依赖于Interface Builder的高度抽象。

深入到细节中,你会发现使用图像的过程分为两步。首先是将数据加载到UIImage中,然后通过一些方式使用UIImage。可通过两种主要的方式来使用UIImage,如图18-1所示。

本节我们来探讨比较简单的使用UIImageView来显示图像的方法,18.2节将探讨更为复杂的将图像绘制到UIView后层的方式。

18.1.1 加载UIImage

UIImage类提供7种不同的方式创建图像实例。其中的4个工厂方法可能最容易使用,参见表18-1。还有一些与之等同的init方法,你可以根据自己的喜好来使用。

表18-1 用于创建UIImage的工厂方法

?

?

工厂方法

?

概  述

?

imageNamed:

?

基于主软件包(main bundle)中的文件创建UIImage

?

imageWithCGImage:

?

从Quartz 2D对象创建UIImage,这与initWithCGImage:相同

?

imageWithContentsOfFile:

?

从你指定的完整文件路径创建UIImage,跟第16章中讨论的一样,这与initWithContentsOfFile:相同

?

imageWithData:

?

从NSData创建UIImage,这与initWithData:相同

?

图像数据可以是几种文件类型,包括BMP、CUR、GIF、JPEG、PNG和TIFF。本书中我们使用得最多的是JPEG(因为它们比较小)和PNG(因为它们在iPhone硬件上好看,且被加速)。也可以从Quartz 2D对象创建UIImage,这是iPhone的基本图形程序包,下一章将会介绍。创建UIImage时有一个隐含的限制:图像不应该大于1024×1024。

只要将图像导入程序中,就可以显示了。如果只愿意使用UIKit的简单方法,就会想要使用UIImageView类来显示图像。

18.1.2 绘制UIImageView

我们在显示图片时已经在程序中使用过UIImageView。现在要来讨论其工作的细节。

有两种方式初始化UIImageView。首先,可以使用initWithImage:方法,它允许你传递一个UIImage,如下所示:

另外,也可以使用基本的initWithFrame:方法,并手动修改对象的属性。表18-2展示了一些属性和方法,你对UIImageView做更多工作时很可能会用到它们。

表18-2 UIImageView的一些属性和方法

?

?

方法或属性

?

类  型

?

概  述

?

animationDuration

?

属性

?

指定多长时间运行一次动画循环

?

animationImages

?

属性

?

识别图像的NSArray,以加载到UIImageView中

?

animationRepeatCount

?

属性

?

指定运行多少次动画循环

?

image

?

属性

?

识别单个图像,以加载到UIImageView中

?

startAnimating

?

方法

?

开启动画

?

stopAnimating

?

方法

?

停止动画

?

要加载普通图像,可以使用image属性,但是几乎没有理由使用它而不使用initWithImage:方法——除非你在动态改变图像。如果想要创建一组图像作为动画,那么有必要利用其他UIImage- View方法和属性。

可以将一组图像加载到UIImageView中,声明它们应该多长时间完成一次动画循环及运行的次数,并在你觉得合适的时候开始和停止循环。代码清单18-1中展示了一个简单的例子。

代码清单18-1 UIImageView允许动画循环图像

?

?

开启动画

?

?

创建UIView

?

?

加载图像

?

?

你可能想要手动加载图像,而不是通过Interface Builder进行加载,其中一个主要原因是为了利用UIImageView的动画功能。

18.1.3 在UIKit中修改图像

我们已经介绍了如何以编程方式创建图像并将它们加载到图像视图中。很显然,接下来的事情就是开始修改图像。

不幸的是,在处理UIImageView时只有有限的能力修改图像。可以基于视图的一些简单操作,做一些改变。例如,如果你调整UIImageView的大小,则它将自动调整其包含的图片的大小。同样,通过设置UIImageView的框而不是整个屏幕,来决定在哪里绘制UIImageView。你甚至可以通过使用多个UIImageView来分层多个图像。

这一切很快就会变得笨重不堪,而且你不能做任何更有趣的事情,比如说通过混合或alpha透明选项来变换图像或修改它们的堆放形式。要做这类工作(并开始堆放图片,而不只是堆放视图),需要了解Core Graphics。

UIImage提供一些简单的方式访问Core Graphics功能,无需去往Core Graphics框架(或者了解上下文或者其底层的其他复杂情况)。我们这里将简要介绍这些内容,但是Core Graphics基本上要等到下一章才介绍,那时我们主要介绍整个的Quartz 2D图形引擎。

18.2 利用Core Graphics绘制简单图像

尽管不能访问具有变换和其他复杂功能的整个Core Graphics库,但是UIImage类却包含5个简单的方法,可以充分利用Core Graphics的工作方式。表18-3中描述了这些方法。

表18-3 用于绘制UIImage的实例方法

?

?

方  法

?

概  述

?

drawAsPatternInRect:

?

在矩形中绘制图像,不缩放,但是在必要时平铺

?

drawAtPoint:

?

利用CGPoint作为左上角,绘制完整的不缩放的图像

?

drawAtPoint:blendMode:alpha:

?

drawAtPoint:的一种更复杂的形式

?

drawInRect:

?

在CGRect中绘制完整的图像,适当地缩放

?

drawInRect:blendMode:alpha:

?

drawInRect:的一种更复杂的形式

?

问题是,这些方法不能用作viewDidLoad:的一部分或者通常用来加载对象的任何其他方法的一部分。这是因为它们依赖于图形上下文而工作。下一章我们将更详细地谈论上下文,但是图形上下文其实就是你所绘制的东西要到达的目标,比如说窗口、PDF文件或者打印机。

在iPhone上,UIView自动创建图形上下文作为其CALayer的一部分,CALayer是与每个UIView相关的Core Animation层。可以通过为UIView(或者更确切地说,为你已经创建的新的子类)编写drawRect:方法来访问Core Animation层。要做这种类型的工作,通常必须获得一个特殊的上下文变量,UIView方法替你去做这件事,这样事情就变得简单了。

代码清单18-2展示了如何使用该方法拼合一些图片。

代码清单18-2 UIView的drawRect:允许你使用较低级别的绘图命令

注意,drawAtPoint:方法让你可以完成更复杂的事情,比如说混合图片(使用类似于Photoshop的选项,比如颜色减淡和强光)和让图片变得有些透明。这里使用的是普通混合,但是只有50%的透明度(因此使用drawAtPoint:方法)。其余代码很标准。使用这些单个绘图命令非常简单,比费力去创建多个UIImageView对象简单多了(可能还更为高效)。

在深入研究iPhone的Core Graphics框架之前,我们还有好多事情不能做,但是现在已经得到一些控件,已经足够满足大多数常见的多媒体需求了。如果需要更多的控件,请直接跳转到下一章。

我们谈论过很多有关图像的话题,但是一直假设你是从项目的软件包加载图像。而如果你想让用户来选择照片呢?这就是我们下一节的主题。

18.3 访问照片

可以使用SDK从iPhone的照片库或相机卷访问图片。也可以允许用户拍摄新照片。这都是用UIImagePickerController完成的,它是另一个模式控制器,负责管理一个相当复杂的图形界面,无需你的干预。图18-2显示了它的样子。

图18-2 图像选取器是另一个供你使用的预编程的控制器

18.3.1 使用图像选取器

通过创建对象、设置一些变量并将它呈现为模式视图控制器来加载UIImagePickerController。默认情况下,图像选取器控制器将允许用户访问(以及有选择性地编辑)他们的照片库中的图片:

一旦创建了图像选取器控制器,你就需要有其委托来响应以下两个方法的委托响应:image- PickerController:didFinishPickingImage:editingInfo:和imagePickerController- DidCancel:。对于第一个方法,你应该解散模式视图控制器,并适当地响应用户的图片选择。而对于第二个方法,则只需要解散控制器。

总之,图像选取器控制器很容易使用,因为你主要是对被选中的图片做出反应。下一节给出了一个使用它的完整例子。

18.3.2 拍照

我们前面提到过,UIImagePickerController具有三个可能的源,由以下常量表示:

q?? UIImagePickerControllerSourceTypePhotoLibrary,照片库中的一个图片。

q?? UIImagePickerControllerSourceTypeSavedPhotosAlbum,相机卷中的一个图片。

q?? UIImagePickerControllerSourceTypeCamera,照相机拍摄的新图片。

应该总是确保,在启动图像选取器控制器之前,源是可用的,尽管这对于照相机来说最重要。可以用isSourceTypeAvailable:类方法确定源的存在:

一旦验证了源的存在,你就可以告诉图像选取器,结合sourceType属性一起使用这个源。例如,要使用照相机,可以这样做:

注意,一个程序中拍摄的图片只在该程序中可用。如果你想要这些图片进入相册,那么程序必须将它们保存到那里(我们马上就会讨论到)。

以我们的经验,照相机是很占用资源的。我们在测试期间曾几次让它停下来。起码这意味着,你在使用照相机的时候需要考虑保存程序的状态,因为使用照相机会导致内存不够用。

18.4节中的例子中就会用到照相机。

18.3.3 保存到相册

你可能希望将新照片保存到相册,或者希望将程序创建的图形存放到相册。不管出于哪种情况,都使用UIImageWriteToSavedPhotosAlbum函数。该函数具有4个变量:第一个列出图像,其他三个引用一个可选的异步通知函数,以在完成保存时调用。通常可以像下面这样调用该函数:

如果你想要利用异步通知,请查阅UIKit函数参考,那里解释了该函数,或者也可以参考下一章的例子。

可以使用该函数将UIView的CALayer保存到相册,例如,相册允许你保存那些你以前直接写到CALayer的绘图命令。这也同样依赖于图形上下文,下一章我们将会解释,但是这里要展示怎么做这件事:

为了该函数正确工作,你必须链接到Quartz Core框架。

现在已经介绍了图像的所有基本知识,可以将它们组合到本章的“大”例子中了,这个例子是一个程序,它拼合多个图片,首先是利用UIImagePickerController选择这些图片,然后利用UIImageView允许它们移动,最后将它们绘制到一个CALayer中以便保存。

18.4 拼合:一个图像例子

拼合(collage)程序依赖于三个对象。跟往常一样,collageViewController完成大部分工作。它写出到collageView对象,该对象主要作为一个CALayer存在。最后,你将有一个tempImageView对象,它是用户在图像被选中但是没有永久放置之前临时放置图像的地方。

18.4.1 拼合视图控制器

拼合视图控制器建立在一些Interface Builder对象之上,这些对象是:视图控制器本身;一个叫做myTools的工具,它在编程过程中将被填充;collageView UIView类,它作为自己的类文件而存在,并作为self.view引用到程序中。你还需要向项目中添加Quartz Core框架,因为你将使用我们刚才讨论的保存图片技巧。

代码清单18-3展示了完整的视图控制器,这是整个程序中作用最大的文件。

代码清单18-3 视图控制器负责大部分的拼合任务

?

?

激活图像选取器

?

?

设置对象

?

?

?

?

调整图片大小

?

?

响应选取器取消

?

?

响应图像选择

?

?

激活照相机

?

?

?

?

将图片添加到CALayer

?

?

?

?

保存拼合

?

?

伸缩图像

?

?

代码有点长,但是很容易理解。一开始是viewDidLoad:,它设置UIToolBar 。我们早就赞美过Interface Builder,但是也说过,在创建更加动态的项目时,它可能会不足以胜任。这里就是这种情况。你不能在Interface Builder中有效地填充UIToolBar,因为你将基于程序的状态改变它。你要在工具栏上放置按钮以调用三个方法:choosePic:、takePic:(当照相机可用时)和savePic:。

choosePic: 和takePic: 是类似的方法。它们都调用图像选取器控制器,但是第一个方法访问照片库,第二个方法让用户拍摄新图片。这些模式控制器的非凡之处在于,从你创建选取器到用户选择图片或者取消,这期间无需你做任何事情。

当用户选择图片时,imagePickerControl:didFinishPickingImage:editingInfo会被调用 ,将控制返回给程序。这里你要做以下4件事情。

q?? 解散模式视图控制器。

q?? 查看交给你的图片,调整图片大小,以填满四分之一或更少的屏幕。

q?? 将图像实例化为一个tempImageView对象,该对象是UIImageView的子类。

q?? 改变工具栏,以便有一个Done按钮可用,还有一个滑块。

此时,用户能够做以下3件事情。

q?? 使用UITouch移动图像视图(图像视图包括在tempImageView类中,因为这是触摸发生的地方,就像我们在第14章中看到的一样)。

q??

?

?

图18-3 使用拼合程序同时

显示很多照片

?

使用滑块改变图片大小。

?

q?? 单击Done按钮接受图像大小和位置。

产生的最终结果如图18-3所示。

注意,如果用户取消了图像选取器,那么你的imageP- ickerControllerDidCancel:方法会正确地关闭模式控制器 。

UISlider被挂接到rescalePic:方法 。它重新绘制UIImageView的框,这将在内部自动调整图片大小。同时,Done按钮激活finishPic:方法 。这会发送一个特殊的addPic: at:消息给collageView。CALayer绘制就是在collageView中完成的,我们马上就会返回来介绍它。finishPic:也解散UISlider和tempImageView,并重新设置工具栏回到初始设置。

初始工具栏还有一个我们还没介绍的按钮:Save。它激活savePic:方法 ,该方法将CALayer保存到照片库。注意,该方法在执行过程中会临时隐藏工具栏。由于工具栏是UIView的子视图,如果不这样做的话,它会被包含在图片中。

最后一个方法scaleImage: 是设置每个图像以填充大约四分之一屏幕的工具。

代码中有两个不确定的部分:tempImageView中的方法(它们允许用户移动UIImageView)和collageView中的方法(它们稍后将图像绘制到CALayer中)。

18.4.2 拼合临时图像视图

tempImageView类只有一个目的:截取UITouch。UITouch表示用户想要将新图像移动到拼合的不同部分。代码很简单,如代码清单18-4所示。

代码清单18-4 临时图像可通过触摸被移动

?

?

确定在视图中的位置

?

?

计算整体位置

?

?

这类似于你在第14章中编写的触摸代码,无需做更多的解释。回想一下,locationInView: 给出视图坐标系统内的一个CGPoint,需要转换成 应用程序的全局坐标系统。

我们在测试中发现,当运行在iPhone(而不是iPhone仿真器)上时,结果有时会超出边界,所以你在移动临时图像视图之前,需要仔细检查坐标。

18.4.3 拼合视图

最后,我们具有了collageView本身,它是背景UIView,需要响应addPic:at:消息并用drawRect:绘制到CALayer上。代码清单18-5展示了做这件事的代码。

代码清单18-5 一旦设置一个图像,背景视图就会管理低级别的绘制

?

?

保存到数组中

?

?

?

?

绘制到CALayer上

?

?

该代码分为两部分。addPic:at:方法 将其信息保存到一个实例变量中,将myPics词典添加到NSMutableArray。注意,你必须将值转换为NSNumber,以便可以将它们放置到词典中。该方法然后在视图上调用setNeedsDisplay。应该从来不直接调用drawRect:。相反,当想要它执行时,可调用setNeedsDisplay方法,其他所有事情都不用你操心。

drawRect:后来很快就被调用 。它读取整个NSMutableArray,将之分解,并用我们前面学习到的技术将每个图像绘制到CALayer上。

我们还没有展示头文件和未更改的应用程序委托,但是这是编写完整的拼合程序都需要的重要内容。

18.4.4 扩展这个例子

这是我们较长的例子之一,但是仍然需要进行一些扩展,才能变成功能全面的应用程序。

首先,它对内存有点不友好。如果能引用文件名,而不是到处使用UIImage,那么可能会更好。此外,CALayer所来自的NSArray应该保存到文件,以便在内存不够时不会丢失。但是程序只要存在就应该工作得很好。

程序应该变得更加可用。裁剪图片的选项很好,但是可能需要访问Core Graphics函数。一个在图片锁定之后移动图片的选项相对简单:你可以在collageView中测试触摸,并向后读取NSArray,以找到用户正在触摸哪个对象。将它重新实例化为UIImageView很简单。

不管怎样,我们已经展示了所有这些图形基本要素如何一起工作,现在我们开始介绍多媒体的另外两种主要类型:音频和视频。

18.5 使用Media Player框架

音频和视频都比图像更加复杂。幸运的是,有一个高级别框架——Media Player——允许你访问音频和视频。如果你不需要音频或视频紧密地集成到应用程序的其余部分中,那么它是一个很好的选择——对于视频通常工作得很好,但是对于音频是一个不太理想的选择。

Media Player框架包含两个类:MPMoviePlayerController和MPVolumeView。它们管理整个页面的音频或视频播放器,无需你控制其如何工作,但是让你很容易访问音频或视频文件。

要使用任何一个媒体播放器类,你都应该向你的项目添加Media Player框架和MediaPlayer/ MediaPlayer.h头文件。

18.5.1 媒体播放器类

为了使用媒体播放器,你需要用将要调用的文件的URL字符串初始化一个MPMoviePlayer- Controller对象。这可能是任何.mp3、.mp4、.mov或.3gp文件,或者是iPhone支持的任何其他东西。启动媒体播放器的方式有两种,可以立即播放(这会导致iPhone的转盘旋转得快一点,当它做好准备时),或者等到接收了文件已加载的通知后再启动。

使用媒体播放器时你需要注意的通知有三个,表18-4中描述了它们。

表18-4 告诉你媒体播放器正在做什么的通知

?

?

通  知

?

概??? 述

?

MPMoviePlayerContentPreloadDidFinishNotification

?

文件已加载

?

MPMoviePlayerPlaybackDidFinishNotification

?

重放完成

?

(续)

?

?

通  知

?

概??? 述

?

MPMoviePlayerScalingModeDidChangeNotification

?

播放器的缩放模式改变

?

1. 调用媒体播放器

代码清单18-6显示了播放器的一个简单调用。该程序的构造开始于Interface Builder中,包括一个UITextField(用于URL的输入)、一个UILabel(用于报告状态和错误)和一个UIActivity- IndicatorView(在加载期间展示活动)。它依赖于通知来跟踪媒体播放器是如何工作的。

代码清单18-6 媒体播放器的一个简单调用

?

?

准备文本字段

?

?

解散键盘

?

?

?

?

设置播放器

控制模式

?

?

在完成时刷新屏幕

?

?

在加载完成时开始播放电影

?

?

安抚用户

?

?

请求媒体通知

?

?

启动媒体播放器

?

?

加载媒体文件

?

?

你的项目开始很简单,就是设置UITextField。这涉及设置Return键 和编写它的主委托方法textFieldShouldReturn: ,前面已经这么做过好几次了。

项目是从文本字段真正开始的。当文本数据被输入时,chooseFile:方法被调用 ,就是该方法加载播放器。假设你被传递一个URL(因为它简单,我们曾经使用过,尽管我们马上就会讨论本地文件),将之转换成NSURL,然后创建播放器 。有少量的属性可以设置来指定播放器如何工作 ,这些属性都列出在类参考中。你使用前面看到的过程链接到播放器的通知中 。让用户知道你怎样进行处理是个不错的主意,所以最后几行代码更新状态信息并启动一个活动指示器,表示正在进行处理中 。

一旦文件被加载,movieDidLoad:方法就会被通知 。它清除你的更新信息,然后播放器开始播放。跟我们在前面章节中看到的模式视图控制器一样,此时由媒体播放器负责,直到其额外的通知回来之前无需你操心任何事情。在本例中,当它完成之后 ,你做一些最后的清除。

2. 从文件加载

如果你愿意从文件而不是从互联网加载,你就可以包含媒体文件作为软件包的一部分。使用我们在第16章中讨论的方法,创建一个到这些本地文件的路径,并且用fileURLWithPath:工厂方法创建NSURL:

在编写本书时,加载文件比从互联网加载更为可靠。从互联网加载时,音乐文件偶尔会无缘由地被破坏,流视频似乎根本不能工作。我们期望这些问题很快得到纠正,可能就在本书出版之际。

媒体播放器还支持另一个功能:你可以允许用户在播放器之外设置音量。

18.5.2 音量视图

通过调用MPVolumeView条目,你可以允许用户调整音量,调用方法如下:

你不需要做任何后台工作,当用户更改音量控件时,系统音量将立即更改。如果你愿意使用另外的方法,有三个一般函数可用来调用音量警报。参见表18-5。

表18-5 用警报设置音量

?

?

函??? 数

?

概??? 述

?

MPVolumeSettingsAlertShow

?

显示音量警报

?

MPVolumeSettingsAlertHide

?

隐藏音量警报

?

MPVolumeSettingsAlertIsVisible

?

返回一个布尔值,以显示音量警报的状态

?

注意,这些是函数,不是方法。它们不与任何类捆绑在一起,而一旦你加载了Media Player框架,则这些函数通常是可用的。

警告???? 在编写本书时,音量控制在iPhone仿真器中不起作用。

18.5.3 更好地集成媒体播放器

媒体播放器最大的问题是,它调用一个单独的屏幕。因此,很难用它将音乐或视频直接集成到程序中。

对于音乐来说,这个问题目前还难以一下子就解决了。如图18-4所示,当播放音乐时,屏幕被一个大的QuickTime标志占据着。我们希望,SDK的未来版本能够给予你选项,来定义播放声音时的背景(或者更好一点,允许你保持在普通视图中,从而能够真正集成媒体播放器的音频功能)。

图 18-4

对于播放视频来说,最大的问题是控件,因为当你将视频用作cut scene时,不希望用户操作它。通过将MPMoviePlayerController的movieControlMode属性设置为 MPMovieControl- ModeVolumeOnly(这只允许使用音量控件)或者MPMovieControlModeHidden(这不允许用户访问任何控件),可以解决这个问题。

有了这种隐藏电影控件的能力,媒体播放器应该成了你显示视频所需要的工具,但是它还是不能使用音频,迫使你去寻求另外的方法。不幸的是,还没有高级别的框架用于播放音频,所以你自己必须做不少的工作。大量具体的细节由于比较复杂,超出了本书范畴,但是我们将从媒体播放器之外的处理音频所用最简单方法开始介绍。

18.6 手动播放声音

没有用于iPhone音频的高级别框架这种说法并不完全正确。确实有,叫做Celestial。不幸的是,Celestial是iPhone上的很多“私有框架”之一,这意味着它已在Apple内部使用,但是还没有对外部的开发人员可用。我们决定在本书中不讨论用来访问私有框架的破解方法,因为私有框架随时可能改变,使用它们会让程序很容易受到OS升级的影响。相反,我们需要依靠Apple官方提供的框架。

有很多官方提供的框架!iPhone的Core Audio系统包含半打以上的框架,让你可以在低级别访问音频文件。这些框架包括Audio Queue Services、Audio File Stream Services、Audio File Services、OpenAL、Audio Session Services等。要深入了解这些框架,请参考Apple参考库的“Audio & Video”部分,从“Getting Started with Audio & Video” 和“Core Audio Overview”开始。

这些框架都足够老了,还没有脱离Core Foundation,所以你必须依赖于从这些较老的编程风格中学到的经验。

我们只简要介绍音频。我们将提供一些关于如何播放简单声音和振动iPhone的例子,但是对于更复杂的Audio Queue Services,我们将概述过程,并将你引向关于该主题的Apple扩展教程。

18.6.1 播放简单声音

System Sound Services是一个C接口,它让你播放简单声音和振动iPhone。它是Audio Toolbox框架的一部分,在AudioToolbox/AudioServices.h中声明。

该接口只可以用来播放30秒以下的.aif、.caf或.wav格式的短音频文件。要使用System Sound Services,则从文件创建一个系统声音ID,可选地为声音完成播放时创建一个回调,并启动它。表18-6展示了主要的函数。

表18-6 System Sound Services的主要函数

?

?

函??? 数

?

参??? 数

?

概??? 述

?

AudioServicesCreateSystemSoundID

?

URL、&SSID

?

从URL创建声音

?

AudioServicesDisposeSystemSoundID

?

SSID

?

完成时删除声音

?

AudioServicesAddSystemSoundCompletion

?

SSID、run loop、run loop mode、routine、data

?

为声音播放完成记录一个回调

?

AudioServicesRemoveSystemSoundCompletion

?

SSID

?

完成时删除回调

?

AudioServicesPlaySystemSound

?

SSID

?

播放声音

?

有一些额外的函数处理系统声音属性,可以在System Sound Services参考中找到这些函数。代码清单18-7展示了如何使用最重要的函数。

代码清单18-7 Audio Toolbox支持简短音频的播放

?

?

准备URL

?

?

?

?

播放声音

?

?

添加回调

?

?

创建声音

?

?

?

?

清除声音

?

?

跟媒体播放器一样,启用System Sound Services接口首先是构建一个到文件的路径(使用在第16章中学到的知识),然后将其转换成一个URL 。完成之后,就可以创建系统声音了 ,这要求将NSURL *桥接到CFURLRef并传递一个指向系统声音ID的指针。

添加回调函数是可选的 ,但是如果你想要在声音播放完成时发生一些事情,则一定要这么做。

一旦有了系统声音ID,播放就很简单了 ,但是你应该检查系统声音ID创建得是否正确,跟我们这里做的一样。

跟平常一样,完成之后应该清空内存。这就是本例中回调函数所做的事情 。

18.6.2 振动iPhone

System Sound Services接口中还隐含着一个很酷的小特性,你可以使用它来振动用户的iPhone。这通过传递一个预先定义的系统声音ID来实现,如代码清单18-8所示。

代码清单18-8 振动iPhone需要一行代码

对于有时难以使用的音频系统来说,这是它本身的简易之处。我们现在已经介绍了所有容易的音频工作,不幸的是,就只有这些了。

结束之前,我们下面要概述播放不满足System Sound Services接口要求的音频文件(可能是太长了,也可能是文件类型不符)时的操作步骤。

18.6.3 播放复杂的声音

要播放长于30秒的声音,需要依赖于Audio Queue Services。它将允许你播放较长的声音,播放System Sound Services支持的有限音频类型之外其他类型的声音,甚至记录声音。Apple提供两组极好的关于Audio Queue Services的教程代码,你可以复制过来自己使用,所以我们这里就不赘述了。“Apple Queue Services Programming Guide”提供了一个全面的例子,介绍如何在过程化的面向C的环境中编写播放器。“SpeakHere”示例代码展示了如何使用最初面向对象的Objective-C代码执行类似的任务。播放器出现在AudioPlayer.m文件中。

为了阐述Apple的示例代码,表18-7列出了播放复杂声音需要遵循的标准步骤。它依赖于如下这些核心思想。

q?? 音频文件ID类似于前面遇到过的系统声音ID,它指向文件的音频内容。

q?? 音频队列包含大量缓冲区——通常至少包含3个。这些缓冲区被填充了音频内容(通常来自文件),一次填充一个,然后被分派给播放器(通常是扬声器)。

q?? 音频队列缓冲区是通过队列传递的单个声音单元。每个音频队列缓冲区具有用户设置的大小。你在用数据填充这些缓冲区时会给它们编制队列,然后它们会按队列进行播放。

q?? 音频队列回调是一个特殊的函数,被调用来处理音频队列。回调发生在数据被拖入音频队列时。它需要填充缓冲区,然后将它们编制队列。

q?? 定制音频结构是一个用户创建的结构,其中包含回调需要知道的所有关于音频文件状态和队列状态的数据。它作为函数调用的一部分被传递给回调。

图18-5以图形方式描述了这些概念。

?

?

?

?

?

?

输出

?

?

输入

?

?

音频缓冲区队列

?

?

缓冲区

?

?

缓冲区

?

?

缓冲区

?

?

图18-5 音频从输入设备移动到输出设备以便进行重放的管道图

有了这些定义之后,应该就能够解析Apple的代码了。表18-7列出了必要的步骤。

表18-7 从音频队列进行播放的步骤

?

?

步??? 骤

?

概??? 述

?

1. 链接到框架中

?

链接到Audio Toolbox框架和AudioToolbox/AudioQueue.h文件中

?

2. 准备音频文件

?

创建到音频文件的URL引用。调用AudioFileCreateWithURL以返回AudioID

?

3. 创建音频队列

?

使用AudioQueueNewOutput来准备队列

向队列添加3个或更多缓冲区。每次,运行AudioQueueAllocateBuffer以设置它的大小,然后再运行回调函数

?

4. 准备定制音频结构

?

定义结构,该结构将用来传递所有队列和音频数据到回调函数

?

5. 编写回调函数

?

编写回调函数,以从音频文件读取数据到缓冲区,然后给缓冲区编制队列。一旦数据已在队列中,重放就会自动发生

?

6. 开始重放

?

利用AudioQueueStart开始重放

?

7. 结束重放

?

一旦音频文件被清空,回调函数就应该结束重放。这利用AudioFileClose和AudioQueueStop来实现

?

关于音频队列的大量描述来自于它的设置,该设置过程由表18-7中的步骤2到5组成。一旦完成这些步骤,应该就具有了一个很好的可重复的方式,来用音频队列播放音频。我们提到过,Apple提供该代码的一些完整例子,我们不做详述,只把代码复制出来。

一旦有了音频队列,就可以使用各种函数来操作它,表18-8中列出了这些函数。

表18-8? 用来控制音频队列的主要函数

?

?

函??? 数

?

概??? 述

?

AudioQueuePrime

?

为接下来的使用准备一个队列(可选)

?

AudioQueueStart

?

开始重放(或录音)

?

AudioQueuePause

?

暂停重放(或录音),用AudioQueueStart重新开始

?

AudioQueueFlush

?

确保在最后一个缓冲区被编进队列后所有缓冲区都被清空(可选)

?

AudioQueueStop

?

调用AudioQueueReset,然后结束重放(或录音)

?

AudioQueueReset

?

从队列删除缓冲区,并重设为默认状态

?

尽管我们这里将重点放在重放上,但是录音与它相同。麦克风会将数据放到音频队列中。数据插入到缓冲区的所有过程都是自动完成的。然后你需要为后台编写一个回调函数,负责将数据从缓冲区中取出,将数据写入到文件(或者其他输出设备),并将缓冲区添加回到队列的末尾,它最终会被再次填充数据。

18.6.4 其他音频框架

我们对System Sound Services框架和Audio Queue Services框架的介绍对于iPhone声音来说只是冰山一角。如果你很重视声音,可能还想了解OpenAL,这是一个用于声音定位的API。

关于多媒体数字信号编解码器、插件、音频图形之类的更多信息,可以在我们提到过的“Core Audio Overview”中找到。

18.7 小结

在iPhone上处理媒体是一个巨大的主题,其本身就可以写成一本书。幸运的是,有很多相对容易(如果有限制)的方式来利用每种媒体。对于图像可以使用UIImageView,对于视频可以使用MPMoviePlayer,对于声音可以使用System Sound Services。

如果你想要深入学习这方面的内容,则必须进一步了解iPhone OS。对于视频,没有更多的事情可做,但是对于音频,可以使用更复杂的程序包,比如Audio Queue Services或OpenAL。对于图像,可以利用Quartz 2D做更复杂的工作,这是下一章的主题。

热点排行