AVFoundation(二):核心AVAsset

96
小笨狼
2015.12.09 13:23* 字数 2243

AVFoundation是一个对多媒体操作的库。多媒体一般以文件或者流的形式存在,显而易见,直接对多媒体进行操作并不是一件愉快的事,这需要我们了解很多底层多媒体方面的知识。AVFoundation为我们提供了一个多媒体的载体类:AVAsset,在AVAsset中有着统一并且友好的接口,我们不需要了解太多多媒体的知识(当然还是需要了解一些的),就能对其进行操作。

基本属性

我们将描述视频基本信息的属性称为基本属性。AVAsset的属性从根本上来说是多媒体文件(如视频文件)的属性,我们先来看看多媒体文件中有哪些属性。用十六进制编辑器打开一个视频文件是最完整的查看视频中信息的方法,不过这样并不利于我们的阅读,因为数据太多了。apple提供了一个很好的查看视频信息的工具Atom Inspector,它会将十六进制的数据归类,并提取出其中有用的信息,即有利于查阅信息,也可以很方便的查看视频完整的16进制,了解视频的结构。
用Atom Inspector打开一个视频文件


我们可以看到在moov的目录下有一个mvhd,mvhd也称为movie header,它是整个视频的描述部分,里面包含着视频的基本信息,如时长,创建时间等。这些信息就是视频文件的基本属性,他们对应到AVAsset中有:

//  Indicates the duration of the asset. If @"providesPreciseDurationAndTiming" is NO, a best-available estimate of the duration is returned. The degree of precision preferred for timing-related properties can be set at initialization time for assets initialized with URLs. See AVURLAssetPreferPreciseDurationAndTimingKey for AVURLAsset below.
@property (nonatomic, readonly) CMTime duration;

//  indicates the natural rate at which the asset is to be played; often but not always 1.0
@property (nonatomic, readonly) float preferredRate;

//  indicates the preferred volume at which the audible media of an asset is to be played; often but not always 1.0
@property (nonatomic, readonly) float preferredVolume;

// Indicates the creation date of the asset as an AVMetadataItem. May be nil. If a creation date has been stored by the asset in a form that can be converted to an NSDate, the dateValue property of the AVMetadataItem will provide an instance of NSDate. Otherwise the creation date is available only as a string value, via -[AVMetadataItem stringValue].
@property (nonatomic, readonly, nullable) AVMetadataItem *creationDate NS_AVAILABLE(10_8, 5_0);

首先duration属性是CMTime类型,CMTime是一个结构体

typedef struct
{
    CMTimeValue value;      // @field value The value of the CMTime. value/timescale = seconds.
    CMTimeScale timescale;  // @field timescale The timescale of the CMTime. value/timescale = seconds. 
    CMTimeFlags flags;      // @field flags The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. 
    CMTimeEpoch epoch;      // @field epoch Differentiates between equal timestamps that are actually different because of looping, multi-item sequencing, etc. Will be used during comparison: greater epochs happen after lesser ones. Additions/subtraction is only possible within a single epoch, however, since epoch length may be unknown/variable.
} CMTime;

它既包含了value,又包含了timescale,所以duration属性由视频中的duration和timescale共同组成。

一般QuickTime和MPEG-4格式的mvhd中都有duration和timescale字段。不过其他格式可能不存在这2个字段,这时duration的值就需要通过计算才能得出。
如果创建AVURLAsset时传入的AVURLAssetPreferPreciseDurationAndTimingKey值为NO(不传默认为NO),duration会取一个估计值,计算量比较小。反之如果为YES,duration需要返回一个精确值,计算量会比较大,耗时比较长。

preferredRatepreferredVolume属性分别表示视频默认的速度和音量,这两个属性直接从mvhd中取出来即可,一般情况下,他们的都是1。
creationDate属性表示视频的创建时间,对应着mvhd中的created。如果mvhd中没有创建时间,creationDate会返回nil。

AVAssetTrack

在mvhd下面,我们可以看到有3个轨道(track),一般的视频至少有2个轨道,一个播放声音,一个播放画面。AVFoundation中有一个专门的类承载多媒体中的track:AVAssetTrack。
打开Atom Inspector中的track,我们可以看到,track中有一个tkhd(track header),其中包含了track的基本信息:



跟mvhd类似,tkhd中包含了duration,rate,volume,created,除此之外,tkhd中还有一个很重要的字段:track id,这是视频中track的唯一标示符。在AVAsset中,可以通过trackId,获得特定的track

/*  Provides an instance of AVAssetTrack that represents the track of the specified trackID.    */
- (nullable AVAssetTrack *)trackWithTrackID:(CMPersistentTrackID)trackID;

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

//  Provides the array of AVAssetTracks contained by the asset
@property (nonatomic, readonly) NSArray<AVAssetTrack *> *tracks;

//  Provides an array of AVAssetTracks of the asset that present media of the specified media type.
- (NSArray<AVAssetTrack *> *)tracksWithMediaType:(NSString *)mediaType;

//  Provides an array of AVAssetTracks of the asset that present media with the specified characteristic.
- (NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;

tracks中包含了当前Asset中的所有track,通过遍历我们可以获得想要的track.
-tracksWithMediaType:方法会根据指定的媒体类型返回一个track数组,数组中包含着Asset中所有指定媒体类型的track。如果Asset中没有这个媒体类型的track,返回一个空数组。AVMediaFormat中一共定义了8种媒体类型:

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);

-tracksWithMediaCharacteristic:方法会根据指定的媒体特征返回track数组,数组的特性与-tracksWithMediaType:类似,如果Asset中没有这个媒体特征的track,返回一个空数组。AVMediaFormat中一共定义了15种媒体特征:

AVF_EXPORT NSString *const AVMediaTypeMetadataObject NS_AVAILABLE_IOS(9_0);
AVF_EXPORT NSString *const AVMediaCharacteristicVisual      NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaCharacteristicAudible     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaCharacteristicLegible     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaCharacteristicFrameBased  NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaCharacteristicIsMainProgramContent NS_AVAILABLE(10_8, 5_0);
AVF_EXPORT NSString *const AVMediaCharacteristicIsAuxiliaryContent NS_AVAILABLE(10_8, 5_0);
AVF_EXPORT NSString *const AVMediaCharacteristicContainsOnlyForcedSubtitles NS_AVAILABLE(10_8, 5_0);
AVF_EXPORT NSString *const AVMediaCharacteristicTranscribesSpokenDialogForAccessibility NS_AVAILABLE(10_8, 5_0);
AVF_EXPORT NSString *const AVMediaCharacteristicDescribesMusicAndSoundForAccessibility NS_AVAILABLE(10_8, 5_0);
AVF_EXPORT NSString *const AVMediaCharacteristicEasyToRead NS_AVAILABLE(10_8, 6_0);
AVF_EXPORT NSString *const AVMediaCharacteristicDescribesVideoForAccessibility NS_AVAILABLE(10_8, 5_0);
AVF_EXPORT NSString *const AVMediaCharacteristicLanguageTranslation NS_AVAILABLE(10_11, 9_0);
AVF_EXPORT NSString *const AVMediaCharacteristicDubbedTranslation NS_AVAILABLE(10_11, 9_0);
AVF_EXPORT NSString *const AVMediaCharacteristicVoiceOverTranslation NS_AVAILABLE(10_11, 9_0);  

元数据

再往下看有一个meta(meta data)和udta(user data),里面都保存着视频的元数据,不过由于这个视频没有元数据,可能是因为国内正版视频不好找的原因,我找了几个其他的视频也没找到元数据。所以暂且使用AV Foundation开发秘籍中的图片。
用Atom Inspector打开《超人总动员》mov格式的视频,可以看到视频的结构:


在udta中我们可以看到版权(@cpy)持有者为Pixar公司,导演(@dir)是Brad Bird,另外在meta->ilst中电影的名字是the Incredibles,年份是2006年,类型是Kids & Family。这些数据都会存放在AVAsset的metadata中

/*  Provides access to an array of AVMetadataItems for each common metadata key for which a value is available; items can be filtered according to language via +[AVMetadataItem metadataItemsFromArray:filteredAndSortedAccordingToPreferredLanguages:] and according to identifier via +[AVMetadataItem metadataItemsFromArray:filteredByIdentifier:].        */
@property (nonatomic, readonly) NSArray<AVMetadataItem *> *commonMetadata;

//  Provides access to an array of AVMetadataItems for all metadata identifiers for which a value is available; items can be filtered according to language via +[AVMetadataItem metadataItemsFromArray:filteredAndSortedAccordingToPreferredLanguages:] and according to identifier via +[AVMetadataItem metadataItemsFromArray:filteredByIdentifier:].
@property (nonatomic, readonly) NSArray<AVMetadataItem *> *metadata NS_AVAILABLE(10_10, 8_0);

//  Provides an NSArray of NSStrings, each representing a metadata format that's available to the asset (e.g. ID3, iTunes metadata, etc.). Metadata formats are defined in AVMetadataFormat.h.
@property (nonatomic, readonly) NSArray<NSString *> *availableMetadataFormats;

commonMetadata属性中包含着当前视频常见格式类型的元数据
metadata属性中包含当前视频所有格式类型的元数据
availableMetadataFormats属性中包含当前视频所有可用元数据的格式类型
元数据的格式类型在AVMetadataFormat中定义了很多种,常见的有title、creator、subject、publisher等

// Metadata common keys
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 AVMetadataCommonKeyPublisher                                  NS_AVAILABLE(10_7, 4_0);

以上只是部分format,要了解更多,可以在apple文档中查阅

有了metadataFormat,apple提供了通过fromat获取特定格式类型元数据的方法:

- (NSArray<AVMetadataItem *> *)metadataForFormat:(NSString *)format;

章节元数据

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

//  The locales available for chapters in the asset.
@property (readonly) NSArray<NSLocale *> *availableChapterLocales NS_AVAILABLE(10_7, 4_3);

//  Returns an array of chapters with a given title locale and containing specified keys.
- (NSArray<AVTimedMetadataGroup *> *)chapterMetadataGroupsWithTitleLocale:(NSLocale *)locale containingItemsWithCommonKeys:(nullable NSArray<NSString *> *)commonKeys NS_AVAILABLE(10_7, 4_3);

//  Returns an array of chapters whose locale best matches the the list of preferred languages.
- (NSArray<AVTimedMetadataGroup *> *)chapterMetadataGroupsBestMatchingPreferredLanguages:(NSArray<NSString *> *)preferredLanguages NS_AVAILABLE(10_8, 6_0);

availableChapterLocales属性表示当前Asset中可用的章节Locale(感觉翻译成地域或者区域在这里都很别扭,所以还是用英文)。数组类型,里面包含NSLocale对象
-chapterMetadataGroupsWithTitleLocale:containingItemsWithCommonKeys:方法通过locale和元数据的commonkey筛选出特定的元数据,这些元数据只在当前章节的时间段有效。
-chapterMetadataGroupsBestMatchingPreferredLanguages:方法通过指定一种语言,返回一个章节元数据数组。数组中越匹配指定语言的元数据,位置越靠前。

媒体选择

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

//  Provides an NSArray of NSStrings, each NSString indicating a media characteristic for which a media selection option is available.
@property (nonatomic, readonly) NSArray<NSString *> *availableMediaCharacteristicsWithMediaSelectionOptions NS_AVAILABLE(10_8, 5_0);

//  Provides an instance of AVMediaSelectionGroup that contains one or more options with the specified media characteristic.
- (nullable AVMediaSelectionGroup *)mediaSelectionGroupForMediaCharacteristic:(NSString *)mediaCharacteristic NS_AVAILABLE(10_8, 5_0);

//  Provides an instance of AVMediaSelection with default selections for each of the receiver's media selection groups.
@property (nonatomic, readonly) AVMediaSelection *preferredMediaSelection NS_AVAILABLE(10_11, 9_0);

availableMediaCharacteristicsWithMediaSelectionOptions属性表示当前asset中有效的媒体特征选项。数组类型,里面包含着代表相应媒体特征的string.
-mediaSelectionGroupForMediaCharacteristic:方法通过传入一个媒体特征类型,返回可供选择的媒体选项集合。例如传入字幕的媒体特征类型,返回当前Asset的可供选择的字幕选项集合。
preferredMediaSelection属性是AVMediaSelection类型,他的作用是主要是为各个媒体选项集合提供默认选项。
这里的属性都不是直接的基本属性,可能不是那么容易理解。下面举个简单的例子,以便于理解。打印出当前Asset中默认的媒体选项。

for (NSString *characteristic in asset.availableMediaCharacteristicsWithMediaSelectionOptions) {
        AVMediaSelectionGroup *group = [asset mediaSelectionGroupForMediaCharacteristic:characteristic];
        AVMediaSelectionOption *option = [asset.preferredMediaSelection selectedMediaOptionInMediaSelectionGroup:group];
        NSLog(@"对应媒体特征%@的默认媒体选项是%@",characteristic,option);
    }

懒惰加载

由于多媒体文件一般比较大,获取或计算出Asset中的属性非常耗时,apple对Asset的属性采用了懒惰加载模式。在创建AVAsset的时候,只生成一个实例,并不初始化属性。只有当第一次访问属性时,系统才会根据多媒体中的数据初始化这个属性。
由于不用同时加载所有属性,耗时问题得到了一定缓解。但是属性加载在计算量比较大的时候仍旧可能会阻塞线程。为了解决这个问题,AVFoundation提供了AVAsynchronousKeyValueLoading协议,可以异步加载属性:

@protocol AVAsynchronousKeyValueLoading
@required   
//  Directs the target to load the values of any of the specified keys that are not already loaded.
- (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler;

//  Reports whether the value for a key is immediately available without blocking.
- (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * __nullable * __nullable)outError;

-loadValuesAsynchronouslyForKeys:completionHandler:方法用来异步加载属性,通过keys传入要加载的key数组,在handler中做加载完成的操作。
-statusOfValueForKey:error:方法可以获得属性的加载状态,如果是AVKeyValueStatusLoaded状态,表示已经加载完成。
除此之外,Asset也提供了取消加载的API:

//  Cancels the loading of all values for all observers.
- (void)cancelLoading;

当需要的时候我们可以通过这个API终止加载属性。另外在AVAsset释放的时候会暗中取消所有的加载请求。

End

除了已经介绍的API之外,还有一些BOOL值类型的标识属性,这些属性都比较简单,根据名字就能明白其中的意思。这里就不多介绍了。
最近刚开始研究AVFoundation,可能是玩这个的人不多,网上这方面的资料比较少,所以将最近研究的结果写成博客,以供大家参考,如果有什么不对的地方,希望能多多指教
如果你也正在学习AVFoundation,可以关注我的微博,大家互相学习,共同进步

Reference

MP4文件格式详解——元数据moov
AV Foundation开发秘籍
AVAsset Class Reference

春色满园藏不住
Web note ad 1