[AVFoundation]使用Assets

原文:AVFoundation Programming Guide

写在前面

简单翻译一下AVFoundation的相关文档,本人英语水平一般,有不对的欢迎大家留言指正。

Assets可以来自一个文件或者用户的iPod媒体库或照片库中的媒体。当你创建一个asset对象的时候,所有你想要获得的关于那个item的信息并不是立即可用的。当你有一个movie asset的时候,你可以提取图片,转换为其它格式,或者修改内容。

创建Asset对象

你可以使用URL来创建一个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]。
  • 如果你想将Asset添加到组件里面AVMutableComposition,你通常需要精确地随机存取。使用字典[AVURLAssetPreferPreciseDurationAndTimingKeykey:YES]。
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库或者相册管理的Assets,你需要获取你想访问的asset的URL。

  • 为了访问iPod库,你需要创建一个MPMediaQuery实例来找到的内容,使用MPMediaItemPropertyAssetURL得到它的URL。
    有关Media Library的更多内容可以看Multimedia Programming Guide(没找到=.=)
  • 为了访问相册管理的assets,你可以使用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的总时长(例如一个MP3文件,可能没有包含概要信息)。你应该使用AVAsynchronousKeyValueLoading协议来请求这个值然后在回调的block中获取这个数据,而不是阻塞一个当前线程直到这个值被计算出来。(AVAssetAVAssetTrack实现了AVAsynchronousKeyValueLoading协议)。

检查加载是否完成可以使用statusOfValueForKey:error:。当一个asset第一次被加载的时候,大多数情况下值是AVKeyValueStatusUnknown。为了获取更多的状态值,可以调用loadValuesAsynchronouslyForKeys:completionHandler:。在completion handler中,你可以根据不同的状态进行相应的处理。你应该处理加载失败的情况,因为失败有很多原因,如网络无法访问,或者请求被取消。

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;
   }
}];

如果你想为播放准备一个asset,你需要加载它的tracks属性,更多的关于playing assets,请看Playback

从视频中获得静止图像

从asset获取静态图片,如略缩图,你可以使用 AVAssetImageGenerator 对象。为你的asset初始化一个image generator。初始化可能成功,即使在初始化的时候asset没有visual tracks,因此,你应该使用 tracksWithMediaCharacteristic:.来检测asset是否有visual characteristic tracks

AVAsset anAsset = <#Get an asset#>;
if ([[anAsset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
    AVAssetImageGenerator *imageGenerator =
        [AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
    // Implementation continues...
}

你可以配置一些关于图片生成器(image generator)的特性,如使用 maximumSize 和 apertureMode来分别设置图片的最大尺寸以及光圈模式(aperture mode)。你可以在一个指定的时间生成一个或一组图片。你必须确保你对图片生成器(image generator)保持了强引用,一直到生成所有的图片。

生成单张图片

你可以使用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 结构,指定了你想要生成图片的时间。第二个参数是一个block,会在每一个图片生成的时候回调。block的参数提供了一个result参数用来告诉你图片是否生成成功,还是操作被取消了。
block参数:

  • image
  • 你请求的时间和实际生成图片的时间
  • error 生成失败的原因

在你实现的block中,你需要检查返回的result来判断图片是否生成了。另外,你需要确保你对image generator持有了一个强引用,直到生成图片结束。

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给image generator.

电影剪辑和转码

你可以使用AVAssetExportSession对象来对电影进行转码和剪辑。流程如图Figure 1-1。一个export session是用来管理asset异步输出的对象。使用你想导出的Asset和一个输出选项的名称来初始化session(参考allExportPresets)。你可以配置输出会话来制定输出的URL和文件格式,以及其他可选设置,例如元数据和是否为网络使用而优化。

[AVFoundation]使用Assets_第1张图片
Figure 1-1 The export session workflow

你可以使用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。在你的实现中,你应该检查session的status,这个status决定了这个输出是否成功,失败,或者取消。

    [exportSession exportAsynchronouslyWithCompletionHandler:^{
 
        switch ([exportSession status]) {
            case AVAssetExportSessionStatusFailed:
                NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
                break;
            case AVAssetExportSessionStatusCancelled:
                NSLog(@"Export canceled");
                break;
            default:
                break;
        }
    }];

你可以通过对session发送cancelExport消息来取消输出。

当你尝试覆盖一个已经存在的文件或者把文件写入到这个应用的沙盒外的时候,这个输出将会失败。
以下情况也有可能导致失败:

  • 电话接入
  • 你的应用在后台时其他应用开始开始了播放(playback)。

在这些情况下,你应该通知用户导出失败,并允许用户重新导出。

你可能感兴趣的:([AVFoundation]使用Assets)