三、资源和元数据 AVAsset
简介
AV Foundation 最重要的类就是AVAsset,也是AV Foundation设计的核心。AVAsset是一个抽象类和不可变类,定义了媒体资源混合呈现的方式,讲媒体资源的静态属性模块化成一个整体,如标题、时长、元数据、作者等。
AVAsset由一个或多个带有描述自身元数据的媒体组成。AVAssetTrack类是某种类型的媒体,如音频、视频流等。以下是AVAssetTrack的AVMediaType属性,可以看出还可以表示文本、子标题等。
AVF_EXPORT AVMediaType const AVMediaTypeVideo NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMediaType const AVMediaTypeAudio NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMediaType const AVMediaTypeText NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMediaType const AVMediaTypeClosedCaption NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMediaType const AVMediaTypeSubtitle NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMediaType const AVMediaTypeTimecode NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMediaType const AVMediaTypeMetadata NS_AVAILABLE(10_8, 6_0);
AVF_EXPORT AVMediaType const AVMediaTypeMuxed NS_AVAILABLE(10_7, 4_0);
AVAsset与AVAssetTrack关系如图所示:
AVAsset_track.png
异步载入
如果想要获取一个媒体的某个属性,可能需要读取整个媒体才能获取,如果在主线程就可能造成阻塞。为了提高载入效率。AVAsset可以延迟载入资源的属性,当请求属性时才载入相应的元数据或媒体。
AVAsset和AVAssetTrack都遵循AVAsynchronousKeyValueLoading协议,该协议提供了异步查询属性的接口。
@protocol AVAsynchronousKeyValueLoading
@required
- (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * _Nullable * _Nullable)outError;
- (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler;
@end
statusOfValueForKey::查询一个给定属性的状态,同时返回一个枚举值,表示当前要请求的属性的状态,如果该属性状态不为AVKeyValueStatusLoaded,则需要调用loadValuesAsynchronouslyForKeys:completionHandler: 异步载入一组给定的属性。当资源加载完成时触发完成block。
需要注意的是:完成回调坑你会在任意一个队列中调用,因此更新UI前需要回到主队列;一次请求多个属性时,完成回调只会触发一次,并且每个属性返回的状态可能都不一样;
媒体文件结构
元数据格式
一般在Apple环境下媒体类型主要有4中,分别是QuickTime(.mov) 、MPEG-4 video(.mp4/.m4v)、 MPEG-4 audio(.m4a) 、MPEG-Layer III audio(.mp3)。
下面以QuickTime为例,我们可以用Atom Inspector工具打开.mov文件,查看文件结构。如图:
QuickTime文件由一种称为atoms的数据结构组成。一个atom包含了描述媒体资源某一方面的数据或者包含其他atom。
在moov目录下有一个mvhd,即movie header atom,里面包含有整个视频的描述,如duration、rate、volume、created等基本属性,在AVAsset中都有对应的属性。
@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;
下面两个track,说明该视频文件有两个轨道。
track.png
tkhd中包含有duration、flags、created等属性。其中track id是视频文件中的唯一标识码。AVAsset可以通过该id获取AVAssetTrack.
- (nullable AVAssetTrack *)trackWithTrackID:(CMPersistentTrackID)trackID;
另外还可以通过其他方法获取AVAssetTrack。
@property (nonatomic, readonly) NSArray<AVAssetTrack *> *tracks;
- (NSArray<AVAssetTrack *> *)tracksWithMediaType:(AVMediaType)mediaType;
- (NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(AVMediaCharacteristic)mediaCharacteristic;
MP3是比较特殊的文件。MP3文件不使用容器格式,而是使用编码音频数据,其格式为ID3v2。AV Foundation支持所有ID3v2标签格式的读取操作,但是不支持写入。MP3格式收到专利限制,所以AV Foundation无法支持对MP3或ID3数据进行编码。
使用元数据
获取元数据
AVAsset和AVAssetTrack提种方法可以获取相关的元数据。读取具体元数据的接口由名为AVMetadataItem的类提供,AVMetadataItem其实就是数据模型类,保存这作者时长、创建时间等。获取元数据可通过一下三种方式获取。
/* 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:当前视频所有可用元数据的格式类型;
metadataForFormat:查询特定格式的元数据集合。
元数据类型格式可以在AVMetadataFormat.h中查询。如下所示:
// QuickTimeUserData
AVF_EXPORT AVMetadataFormat const AVMetadataFormatQuickTimeUserData
// ISO UserData
AVF_EXPORT AVMetadataFormat const AVMetadataFormatISOUserData
// iTunesMetadata
AVF_EXPORT AVMetadataFormat const AVMetadataFormatiTunesMetadata
// ID3Metadata
AVF_EXPORT AVMetadataFormat const AVMetadataFormatID3Metadata
// QuickTimeMetadata
AVF_EXPORT AVMetadataFormat const AVMetadataFormatQuickTimeMetadata
// HTTP Live Streaming metadata
AVF_EXPORT AVMetadataFormat const AVMetadataFormatHLSMetadata
// Metadata format for AVMetadataItems of unknown provenance. This can occur when metadata is provided generically by an intermediate interface, such as AudioToolbox's AudioFile interface.
AVF_EXPORT AVMetadataFormat const AVMetadataFormatUnknown
查询元数据内容
如果想要查询元数据中的某个字段,可按照下面的方法获取:
//获取演奏者和唱片元数据
NSArray *items = asset.metadata;
NSString *keySpace = AVMetadataKeySpaceiTunes;
NSString *artitsKey = AVMetadataiTunesMetadataKeyArtist;
NSString *albumKey = AVMetadataiTunesMetadataKeyAlbum;
NSArray *artistData = [AVMetadataItem metadataItemsFromArray:items withKey:artitsKey keySpace:keySpace];
NSArray *albumData = [AVMetadataItem metadataItemsFromArray:items withKey:albumKey keySpace:keySpace];
if (artistData.count > 0) {
NSLog(@"%@", artistData.firstObject);
}
if (albumData.count > 0) {
NSLog(@"%@", albumData.firstObject);
}
AVMetadataItem最基本的形式是一个封装键值对的封装器,可以通过查询key/commonKey和value获取内容。
/* provides the value of the metadata item */
@property (nonatomic, readonly, copy, nullable) id<NSObject, NSCopying> value;
/* indicates the key of the metadata item */
@property (nonatomic, readonly, copy, nullable) id<NSObject, NSCopying> key;
/* indicates the common key of the metadata item */
@property (nonatomic, readonly, copy, nullable) AVMetadataKey commonKey;
可以看到key和value是id<NSObject, NSCopying>类型。如果提前知道value的类型,那么可以直接调用对应方法转换:
/* provides the value of the metadata item as a string; will be nil if the value cannot be represented as a string */
@property (nonatomic, readonly, nullable) NSString *stringValue;
/* provides the value of the metadata item as an NSNumber. If the metadata item's value can't be coerced to a number, @"numberValue" will be nil. */
@property (nonatomic, readonly, nullable) NSNumber *numberValue;
/* provides the value of the metadata item as an NSDate. If the metadata item's value can't be coerced to a date, @"dateValue" will be nil. */
@property (nonatomic, readonly, nullable) NSDate *dateValue;
/* provides the raw bytes of the value of the metadata item */
@property (nonatomic, readonly, nullable) NSData *dataValue;
这里需要注意的是,如果直接打印key,可能并不会得到预期结果,有可能是一个数字,这时我们需要做转换。打印出来可能是这样的效果:
- (NSString *)keyString {
if ([self.key isKindOfClass:[NSString class]]) { // 1
return (NSString *)self.key;
}
else if ([self.key isKindOfClass:[NSNumber class]]) {
UInt32 keyValue = [(NSNumber *) self.key unsignedIntValue]; // 2
// Most, but not all, keys are 4 characters ID3v2.2 keys are
// only be 3 characters long. Adjust the length if necessary.
size_t length = sizeof(UInt32); // 3
if ((keyValue >> 24) == 0) --length;
if ((keyValue >> 16) == 0) --length;
if ((keyValue >> 8) == 0) --length;
if ((keyValue >> 0) == 0) --length;
long address = (unsigned long)&keyValue;
address += (sizeof(UInt32) - length);
// keys are stored in big-endian format, swap
keyValue = CFSwapInt32BigToHost(keyValue); // 4
char cstring[length]; // 5
strncpy(cstring, (char *) address, length);
cstring[length] = '\0';
// Replace '©' with '@' to match constants in AVMetadataFormat.h
if (cstring[0] == '\xA9') { // 6
cstring[0] = '@';
}
return [NSString stringWithCString:(char *) cstring // 7
encoding:NSUTF8StringEncoding];
}
else {
return @"<<unknown>>";
}
}