本文主要内容来源于:AVFoundation Programming Guide
关于 AVFoundation
要想了解iOS音视频开发相关内容,首先需要了解 AVFoundation
这个框架。
AVFoundation
是用来播放和创建基于时间的视听媒体的几个框架之一。 它提供了一个Objective-C界面,用于使用基于时间的视听数据的详细级别。 例如,你可以使用它来检查、创建、编辑或重新编码媒体文件。 还可以在实时捕获和播放期间从设备获取输入流并操纵视频。
AVFoundation
显示在 iOS 的层级架构,如下图所示:
AVFoundation
显示OS X上的相应媒体架构,如下图所示:
你应该使用合适的最高级别抽象来执行你的任务:
- 如果你只是简单的想播放电影,请使用
AVKit
框架- 在iOS上,当您只需要最少的格式控制时录制视频,请使用
UIKit
框架(UIImagePickerController
)
需要注意的是,你在AVFoundation
中使用的一些原始数据结构(包括与时间相关的数据结构和不透明对象)携带和描述媒体数据 - 在Core Media框架中声明。
在 AVFoundation
框架中,用于表示媒体的最主要类别就是 AVAsset
,框架的设计在很大程度上取决于类别 ASAsset
。
AVAsset
实例表示一个或多个媒体数据(audio and video tracks)的集合。它提供关于整个集合的信息,例如:标题、时长、大小等。它不受特定数据格式限制。通常,在实际使用时我们可能会基于某个 URL 创建对应的媒体资源对象(AVURLAsset
),或者直接创建 compositions(AVComposition
),这些类都是AVAsset
的子类。
AVAsset
实例中的每一份媒体数据片段都是统一的类型,称为轨道(track
)。在典型的简单情况下,一个媒体文件中可能只有两个轨道,一个音频轨道,一个视频轨道。而在复杂的组合中,可能包含多个重叠的音频轨道和视频轨道。此外 AVAsset
也可能有元数据(metadata
)。
在 AVFoundation
中另外一个重要的概念,初始化一个 AVAsset 或 一个 AVAssetTrack 并不意味着它可以使用。因为需要一些时间来计算项目的时长(例如:MP3文件可能不包含摘要信息),在计算值时可能阻塞主线程,你可以通过使用块回调来异步返回。
我们可以从一个文件或者用户的相册中来创建 asset。获得一个视频 asset 时,我们可以从中提出静态图,对其进行转码,裁剪器内容。
创建 AVAsset
当使用一个URL来创建 asset 时,使用 AVURLAsset
:
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
设置 Asset 选项
可以看到当我们创建一个AVURLAsset
时,是可以设置一个对象的 options 的,这里可选的设置项包括:
AVURLAssetPreferPreciseDurationAndTimingKey
,对应的是个布尔值 ,默认为NO
,当设为YES
时表示 asset 应该提供精确的时长,并能根据时间准确地随机访问,提供这样的能力是需要开销更大的计算的。当你只是想播放视频时,你可以不设置这个选项,但是如果你想把这个 asset 添加到一个 composition(AVMutableComposition)中去做进一步编辑,你通常需要精确的随机访问,这时你最好设置这个选项为 YES
AVURLAssetReferenceRestrictionsKey
,对应的是AVAssetReferenceRestrictions
枚举值。有一些 asset 可以保护一些指向外部数据的引用,这个选项用来表示对外部数据访问的限制。具体含义参见AVAssetReferenceRestrictions
AVURLAssetHTTPCookiesKey
,这个选项用来设置 asset 通过 HTTP 请求发送的 HTTP cookies,当然 cookies 只能发给同站。具体参见文档
AVURLAssetAllowsCellularAccessKey
,这个选项对应的值是布尔值,默认YES
表示 asset 是否能使用移动网络资源。
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];
访问用户的 Asset
要访问由iPod库或Photos应用程序管理的资产,您需要获取所需资产的URL。
要访问用户相册的资源时,使用 ALAssetsLibrary
。
以下示例显示如何获取资源来表示已保存的照片相册中的第一个视频:
加载 Asset 使用
初始化一个 AVAsset
或 一个 AVAssetTrack
时并不一定意味着它可以立即使用。 因为这需要一段时间来做计算,而这个计算可能会阻塞当前线程,所以通常你可以选用异步的方式来初始化,并通过回调来得到异步返回。
这时你可以使用 AVAsynchronousKeyValueLoading
protocol 来获取加载 asset 的状态,并在对应的 completion handler 中做对应的处理。AVAsset
和AVAssetTrack
都是遵循AVAsynchronousKeyValueLoading
protocol 的。下面是一个示例:
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *asset = [[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;
case AVKeyValueStatusUnknown:
break;
case AVKeyValueStatusLoading:
break;
}
}];
需要注意的是:当你需要加载一个 asset 来点播,你应该加载它的tracks
属性。
获取视频截图
我们可以用 AVAssetImageGenerator
来获取视频中的截图。即使初始化时在 asset 中没有检查到视觉 track,AVAssetImageGenerator
的初始化也可能会成功,所以必要的情况下,你可以用tracksWithMediaCharacteristic:
方法去检查一下 asset 是否有可用的视觉 track。
AVAsset anAsset = <#Get an asset#>;
if ([[anAsset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
AVAssetImageGenerator *imageGenerator =
[AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
// Implementation continues...
}
你可以配置(图像生成器) AVAssetImageGenerator
,比如:用 maximumSize 和 apertureMode 来指定生成图像最大尺寸和光圈模式,然后,可以生成指定时间的一张图像或一系列图像。必须保证生成图像时对 AVAssetImageGenerator
保持强引用。
获取一张截图
你可以使用 copyCGImageAtTime:actualTime:error:
获取指定时间单个截图。
AVFoundation
可能无法精确地获取你指定时间内的截图,因此你需要传入一个 actualTime
参数来获得截图所对应的实际时间。
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);
}
获取一系列截图
你可以向 AVAssetImageGenerator
发送一个 generateCGImagesAsynchronouslyForTimes:completionHandler:
消息。
第一个参数是一组 NSValue
对象,每个对象包含一个 CMTime
结构,指定要生成图像的资产时间。 第二个参数是一个块,用作为生成的每个图像调用的回调。 块参数提供一个结果常量,用于告诉你图像是否已成功创建,或者操作是否已取消,以及如果适用:
- 图像
- 请求图像的时间和生成图像的实际时间
- 描述原因生成失败的错误对象
在执行块时,请检查结果常量以确定图像是否已创建。 另外,保证在生成图像时始终对 AVAssetImageGenerator
强引用。
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");
}
}];
我们还能使用cancelAllCGImageGeneration
接口来中断截图。
对视频进行裁剪和转码
我们可以使用 AVAssetExportSession
对象来对视频进行裁剪或格式转换。流程如下图所示:
AVAssetExportSession
实例用来控制异步的导出 asset。使用 export session 时,首先我们需要传入要导出的 asset 和对应的 preset 配置,我们可以用allExportPresets
接口来查看所有可用的 preset 配置。接着,你需要设置导出的 URL 和文件类型。此外,我们还能设置导出视频文件的 metadata 以及导出的是否应该针对网络访问优化。
在下面的示例代码中,我们用exportPresetsCompatibleWithAsset:
接口检查可用的预设定的 Asset,用outputURL
和outputFileType
接口设置导出 URL 和导出文件类型,通过timeRange
设置导出时间段。此外,我们还能用shouldOptimizeForNetworkUse
接口设置是否针对网络使用优化以方便秒开,用maxDuration
、fileLengthLimit
设置导入限制等等。
我们用exportAsynchronouslyWithCompletionHandler:
接口来开始导出。
AVAsset *anAsset = <#Get an asset#>;
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
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;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
default:
break;
}
}];
}
此外,我们还可以用cancelExport
接口来取消导出。
当我们想要覆盖已有的文件,或者向应用沙盒外写文件时,导出将失败。如果:
- 有来电
- 应用在后台,另一个应用开始播放
在这些情况下,需要通知用户导出失败,然后允许用户重新启动导出。