AVFoundation编程指南03-Assets使用
写在前面
喜欢AVFoundation资料的同学可以关注我的专题:《AVFoundation》专辑
也可以关注我的简书账号
正文
Asset
可以来自用户的iPod
库或照片库中的文件或媒体。创建asset
对象时,你可能无法立即获取可能要为该项检索的所有信息。拥有电影资源后,你可以从中提取静态图像,将其转码为其他格式或修剪内容。
创建一个Asset对象
要创建Asset
以表示你可以使用URL
识别的任何Asset
,你可以使用AVURLAsset
。最简单的情况是从文件创建asset
:
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
初始化Asset的选项
AVURLAsset
初始化方法将选项字典作为其第二个参数。字典中使用的唯一键是AVURLAssetPreferPreciseDurationAndTimingKey。相应的值是布尔值(包含在NSValue
对象中),指示asset
是否应准备好指示精确的持续时间并按时间提供精确的随机访问。
获得asset
的确切持续时间可能需要大量的处理开销。使用近似持续时间通常是更便宜的操作并且足以用于回放。从而:
-
如果你只打算播放
asset
,则传递nil
而不是字典,或者传递包含AVURLAssetPreferPreciseDurationAndTimingKey
键和相应值NO
(包含在NSValue
对象中)的字典。 -
如果要将
asset
添加到合成(AVMutableComposition),通常需要精确的随机访问。传递包含AVURLAssetPreferPreciseDurationAndTimingKey
键和相应值YES
的字典(包含在NSValue
对象中 -recall
NSNumber从NSValue
继承):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]
访问用户的Assets
要访问由iPod
库或照片应用程序管理的asset
,你需要获取所需Asset
的URL
。
-
要访问
iPod
库,你需要创建一个MPMediaQuery实例来查找所需的item
,然后使用MPMediaItemPropertyAssetURL获取其URL
。
有关媒体库的更多信息,请参阅Multimedia Programming Guide。 -
要访问
Photos
应用程序管理的Asset
,请使用ALAssetsLibrary。
以下示例显示如何获取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");
}];
准备可供使用的Asset
初始化asset
(或track
)并不一定意味着你可能要为该item
检索的所有信息都可立即使用。甚至可能需要一些时间来计算item
的持续时间(MP3
文件,例如,可能不包含摘要信息)。你应该使用AVAsynchronousKeyValueLoading协议来询问值,然后通过使用block
获得回调,而不是在计算值时阻塞当前线程。 (AVAsset
和AVAssetTrack
符合AVAsynchronousKeyValueLoading
协议。)
你可以测试是否为使用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;
}
}];
如果要准备要播放的资源,则应加载其track
属性。有关播放资源的更多信息,请参阅Playback。
从视频中获取静态图片
要从资源中获取静态图像(如缩略图)以进行播放,请使用 [AVAssetImageGenerator](https://developer.apple.com/documentation/avfoundation/avassetimagegenerator)
对象。你使用asset
初始化图像生成器。但是,初始化可能会成功,即使asset
在初始化时没有视觉轨迹,因此如果有必要,你应该使用tracksWithMediaCharacteristic:来测试asset
是否具有具有视觉特征的track
。
AVAsset anAsset = <#Get an asset#>;
if ([[anAsset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
AVAssetImageGenerator *imageGenerator =
[AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
// Implementation continues...
}
你可以配置图像生成器的多个方面,例如,你可以分别使用maximumSize和apertureMode指定它生成的图像的最大尺寸和aperture
模式。然后,你可以在给定时间生成单个图像,或者一系列图片。你必须确保在生成所有图像之前保留对图像生成器的强引用。
生成单个图像
你使用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);
}
生成图像序列
要生成一系列图像,需要向图像生成器发送generateCGImagesAsynchronouslyForTimes:completionHandler:消息。第一个参数是NSValue对象数组,每个对象包含一个CMTime
结构,指定要为其生成图像的asset
时间。第二个参数是一个block
,用作为生成的每个图像调用的回调。block
参数提供结果常量,告诉你图像是否已成功创建或操作是否已取消,并且视情况而定:
- 图像
- 你请求图像的时间以及生成图像的实际时间
- 描述生成失败原因的错误对象
在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");
}
}];
你可以通过向图像生成器发送cancelAllCGImageGeneration消息来取消生成图像序列。
修剪和转码视频
你可以使用AVAssetExportSession对象将影片从一种格式转码为另一种格式,并修剪影片。工作流程如图1-1
所示。导出会话是管理asset
异步导出的控制器对象。你可以使用要导出的asset
和导出预设的名称初始化会话,该导出预设指示要应用的导出选项(请参阅allExportPresets)。然后,配置导出会话以指定输出URL
和文件类型,以及可选的其他设置,例如元数据以及是否应针对网络使用优化输出。
你可以使用exportPresetsCompatibleWithAsset:检查是否可以使用给定预设导出给定asset
:如下例所示:
AVAsset *anAsset = <#Get an asset#>;
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
// Implementation continues.
}
你可以通过提供输出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;
要创建新文件,请调用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;
}
}];
你可以通过向会话发送cancelExport消息来取消导出。
如果你尝试覆盖现有文件,或者在应用程序的沙箱之外写入文件,则导出将失败。它可能也会失败,如果:
- 有一个来电
- 你的应用程序在后台,另一个应用程序开始播放
在这些情况下,你通常应该通知用户导出失败,然后允许用户重新启动导出。
上一章 | 目录 | 下一章 |
---|