Osheep

时光不回头,当下最重要。

AVFoundation-02资源

概述

AVFoundation 是一个可以用来使用和创建基于时间的视听媒体数据的框架。AVFoundation 的构建考虑到了目前的硬件环境和应用程序,其设计过程高度依赖多线程机制。充分利用了多核硬件的优势并大量使用block和GCD机制,将复杂的计算机进程放到了后台线程运行。会自动提供硬件加速操作,确保在大部分设备上应用程序能以最佳性能运行。该框架就是针对64位处理器设计的,可以发挥64位处理器的所有优势。

《AVFoundation-02资源》

iOS 媒体环境.png

AVAsset

AVFondation 是一个非常强大且可扩展的框架,包括对媒体的捕捉、组合、播放和处理等广泛功能,同时它还有别于传统面向文件的音频类,框架把所有的代码设计围绕着 “资源” 进行。资源最重要的类是 AVAsset,它是 AVFoundation 设计的核心,在几乎所有特性和功能的开发中扮演着至关重要的角色。AVAsset 是一个抽象类,定义了媒体资源混合呈现的方式,将媒体的静态属性模块化成一个整体,比它们的标题、时长、元数据。
AVAsset 不需要考虑媒体资源所具有的两个重要范畴。第一是它提供了对基本媒体格式的层抽象。也就是说无论是处理影片、音频,对开发者和框架而言面对的只有资源这个概念,让开发者面对不同格式的内容的时候有统一的处理方法,不用考虑许多编解码的细节。第二是它隐藏了资源的位置信息。当我们处理资源的时候,可以通过URL来创建资源,这个地址可能在本地、也可能在远程服务器上。

AVAssetTrack

AVAsset 本身不是媒体资源,但是它可以作为时基媒体的容器。它由一个或多个带有描述自身元数据的媒体组成。我们使用 AVAssetTrack 类代表保存在资源中统一类型媒体,并对每个资源建立相应的模型。AVAssetTrack 常见的形态是音频和视频流,但是它还可以表示文本、副标题、隐藏字幕等媒体类型。

《AVFoundation-02资源》

AVAsset 的组成.png

在 AVAsset 中,可以通过 TrackID,获得特定的 AVAssetTrack。

- (nullable AVAssetTrack *)trackWithTrackID:(CMPersistentTrackID)trackID;

除了通过trackID获得track之外,AVAsset中还提供了其他3中方式获得track

@property (nonatomic, readonly) NSArray<AVAssetTrack *> *tracks;
- (NSArray<AVAssetTrack *> *)tracksWithMediaType:(NSString *)mediaType;
- (NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;

tracks中包含了当前 AVAsset 中的所有track,通过遍历我们可以获得想要的track。- (NSArray<AVAssetTrack *> *)tracksWithMediaType:(NSString *)mediaType; 方法会根据指定的媒体类型返回一个track数组,数组中包含着Asset中所有指定媒体类型的track。如果Asset中没有这个媒体类型的track,返回一个空数组。AVMediaFormat 中有以下几种媒体类型:

AVF_EXPORT NSString *const AVMediaTypeVideo                 NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeAudio                 NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeText                  NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeClosedCaption         NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeSubtitle              NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeTimecode              NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeMetadata              NS_AVAILABLE(10_8, 6_0);
AVF_EXPORT NSString *const AVMediaTypeMuxed                 NS_AVAILABLE(10_7, 4_0);

- (NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic; 方法会根据指定的媒体特征返回track数组。如果 AVAsset 中没有这个媒体特征的track,返回空数组。AVMediaFormat中一共有以下几种媒体特征:

 NSString *const AVMediaTypeMetadataObject;
 NSString *const AVMediaCharacteristicVisual;
 NSString *const AVMediaCharacteristicAudible;
 NSString *const AVMediaCharacteristicLegible;
 NSString *const AVMediaCharacteristicFrameBased;
 NSString *const AVMediaCharacteristicIsMainProgramContent;
 NSString *const AVMediaCharacteristicIsAuxiliaryContent;
 NSString *const AVMediaCharacteristicContainsOnlyForcedSubtitles;
 NSString *const AVMediaCharacteristicTranscribesSpokenDialogForAccessibility;
 NSString *const AVMediaCharacteristicDescribesMusicAndSoundForAccessibility;
 NSString *const AVMediaCharacteristicEasyToRead;
 NSString *const AVMediaCharacteristicDescribesVideoForAccessibility;
 NSString *const AVMediaCharacteristicLanguageTranslation;
 NSString *const AVMediaCharacteristicDubbedTranslation;
 NSString *const AVMediaCharacteristicVoiceOverTranslation;

创建资源

AVAsset 是一个抽象类,不能直接实例化。当使用 assetWithURL: 方法创建实例的时候,实际上创建的是它的子类,子类为AVURLAsset 。

NSURL *mp3URL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"mp3"]];
AVAsset *asset = [AVAsset assetWithURL:mp3URL];

我们也可以直接创建 AVURLAsset 实例,我们可以传递更多的参数,来更精确地获取计时相关的信息。当然这样做可能需要加载更长的时间,以便获取更准确的时长及时间信息。

 NSDictionary *dict = @{
                           AVURLAssetPreferPreciseDurationAndTimingKey : @(YES)
                           };
 NSURL *mp3URL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"mp3"]];
 AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:mp3URL options:dict];

照片库

用户使用相机或者第三方视频捕捉程序捕捉的视频,它们通常被保存在用户的照片库中。我们可以通过 AssetsLibrary 来访问照片,并创建 AVAsset 对象。

ALAssetsLibrary *assetLib = [[ALAssetsLibrary alloc] init];
[assetLib enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                        usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
                            [group setAssetsFilter:[ALAssetsFilter allVideos]];

                            [group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:0]
                                                    options:0
                                                 usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
                                                     if (result) {
                                                         NSURL *url = [[result defaultRepresentation] url];
                                                         AVAsset *asset = [AVAsset assetWithURL:url];

                                                     }
                            }];

                        } failureBlock:^(NSError *error) {
                          NSLog(@"%@", [error localizedDescription]);
                      }];

异步载入

AVAsset 具有多种有用的方法和属性,可以提供有关的资源信息,比如时长、创建日期和元数据。当创建资源的时候,是对媒体文件的处理。为了高效加载资源,AVAsset 使用了延迟加载资源属性的方案。不过属性的访问总是同步发生,如果正在请求的属性没有预先加载,程序就会阻塞。不过 AVAsset 和 AVAssetTrack 提供了异步加载资源属性的方案。AVAsset 和 AVAssetTrack 都实现了 AVAsynchronousKeyValueLoading 协议,可以通过相关的接口进行异步查询资源的属性。

// 查询给定属性的状态
- (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * _Nullable * _Nullable)outError;
// 异步载入一个给定的属性 
- (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler;
NSURL *mp3URL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"mp3"]];
AVURLAsset *asset = [AVURLAsset assetWithURL:mp3URL];

[asset loadValuesAsynchronouslyForKeys:@[@"tracks"] completionHandler:^{

    NSError *error;
    AVKeyValueStatus status = [asset statusOfValueForKey:@"tracks" error:&error];

    switch (status) {
        case AVKeyValueStatusLoaded:

            break;
        case AVKeyValueStatusLoading:

            break;
        case AVKeyValueStatusUnknown:

            break;
        case AVKeyValueStatusFailed:

            break;
        case AVKeyValueStatusCancelled:

            break;
        default:
            break;
    }
}];
  • 每次调用 - (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler; 只调用一次 completionHandler 块,调用该方法的次数并不是根据传递给这个方法的键的个数而定的。
  • 需要为每个请求的属性调用 - (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * _Nullable * _Nullable)outError; 方法。不能假设所有的属性都返回相同的状态值。

元数据

AVAsset 和 AVAssetTrack 都可以实现相关元数据的查询功能。大部分情况下我们会使用 AVAsset 提供的元数据,不过涉及获取曲目一级元数据等情况时也会使用 AVAssetTrack。读取具体资源元数据的接口名为 AVMetadataItem 的类提供。

// 属性中包含着当前视频常见格式类型的元数据
@property (nonatomic, readonly) NSArray<AVMetadataItem *> *commonMetadata;

// 属性中包含当前视频所有格式类型的元数据
@property (nonatomic, readonly) NSArray<AVMetadataItem *> *metadata NS_AVAILABLE(10_10, 8_0);

// 属性中包含当前视频所有可用元数据的格式类型
元数据的格式类型在AVMetadataFormat中定义了很多种,常见的有title、creator、subject、publisher等
@property (nonatomic, readonly) NSArray<NSString *> *availableMetadataFormats;

// 通过format获取特定格式类型元数据
- (NSArray<AVMetadataItem *> *)metadataForFormat:(NSString *)format;

AVMetadata 的相关 Key 值。

AVF_EXPORT NSString *const AVMetadataCommonKeyTitle                                      NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyCreator                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeySubject                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyDescription                                NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyPublisher                                  NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyContributor                                NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyCreationDate                               NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyLastModifiedDate                           NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyType                                       NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyFormat                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyIdentifier                                 NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeySource                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyLanguage                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyRelation                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyLocation                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyCopyrights                                 NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyAlbumName                                  NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyAuthor                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyArtist                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyArtwork                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyMake                                       NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeyModel                                      NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMetadataCommonKeySoftware

章节元数据

Asset中有一种特殊的元数据:章节。它是AVTimedMetadataGroup类型,这种类型表示一个只在特定时间段有效的元数据集合,也就是说章节中所包含的元数据只在当前章节的时间段有效。

// 表示当前Asset中可用的章节Locale
@property (readonly) NSArray<NSLocale *> *availableChapterLocales ;

// 方法通过locale和元数据的commonkey筛选出特定的元数据,这些元数据只在当前章节的时间段有效
- (NSArray<AVTimedMetadataGroup *> *)chapterMetadataGroupsWithTitleLocale:(NSLocale *)locale containingItemsWithCommonKeys:(nullable NSArray<NSString *> *)commonKeys NS_AVAILABLE(10_7, 4_3);

// 方法通过指定一种语言,返回一个章节元数据数组。数组中越匹配指定语言的元数据,位置越靠前。
- (NSArray<AVTimedMetadataGroup *> *)chapterMetadataGroupsBestMatchingPreferredLanguages:(NSArray<NSString *> *)preferredLanguages NS_AVAILABLE(10_8, 6_0);

媒体选择

一个多媒体文件中相同的媒体特征的东西可能会有很多,比如一个视频中可能会有2种字幕。对于类似选择哪个字幕的问题,有以下几个API:

// 当前asset中有效的媒体特征选项。数组类型,里面包含着代表相应媒体特征的string.
@property (nonatomic, readonly) NSArray<NSString *> *availableMediaCharacteristicsWithMediaSelectionOptions NS_AVAILABLE(10_8, 5_0);

// 通过传入一个媒体特征类型,返回可供选择的媒体选项集合。例如传入字幕的媒体特征类型,返回当前Asset的可供选择的字幕选项集合。
- (nullable AVMediaSelectionGroup *)mediaSelectionGroupForMediaCharacteristic:(NSString *)mediaCharacteristic NS_AVAILABLE(10_8, 5_0);

// 主要是为各个媒体选项集合提供默认选项。
@property (nonatomic, readonly) AVMediaSelection *preferredMediaSelection NS_AVAILABLE(10_11, 9_0);

参考

AVFoundation开发秘籍:实践掌握iOS & OSX应用的视听处理技术

源码地址:AVFoundation开发 https://github.com/QinminiOS/AVFoundation

点赞