AVFoundation之AVAsset

96
丶丶夏天
2017.05.09 10:58* 字数 1583

AVAsset是AVFoundation最重要的类,框架把所有代码设计围绕资源(assert)进行,可以说AVFoundation就是面向资源开发的世界。

AVAsset属性和方法的简单介绍
//视频时长
@property (nonatomic, readonly) CMTime duration;
//默认的速度
@property (nonatomic, readonly) float preferredRate;
//默认音量
@property (nonatomic, readonly) float preferredVolume;
//commonMetadata属性中包含着当前视频常见格式类型的元数据
@property (nonatomic, readonly) NSArray<AVMetadataItem *> *commonMetadata;
//metadata属性中包含当前视频所有格式类型的元数据
@property (nonatomic, readonly) NSArray<AVMetadataItem *> *metadata;
//availableMetadataFormats属性中包含当前视频所有可用元数据的格式类型元数据的格式类型在AVMetadataFormat中定义了很多种,常见的有title、creator、subject、publisher等
@property (nonatomic, readonly) NSArray<NSString *> *availableMetadataFormats;
//此资源中包含的所有曲目(AVAssetTrack),AVAsset还可以通过标识符,媒体类型或媒体特征等信息找到相应的曲目.
@property (nonatomic, readonly) NSArray*tracks;
//通过trackID获得trackAVAssetTrack
-(nullable AVAssetTrack *)trackWithTrackID:(CMPersistentTrackID)trackID;
//通过指定的媒体类型返回一个AVAssetTrack数组,数组中包含着Asset中所有指定媒体类型的AVAssetTrack。如果Asset中没有这个媒体类型的AVAssetTrack,返回一个空数组
-(NSArray<AVAssetTrack *> *)tracksWithMediaType:(NSString *)mediaType;
//通过指定的媒体特征返回AVAssetTrack数组,数组的特性与-tracksWithMediaType:类似,如果Asset中没有这个媒体特征的AVAssetTrack,返回一个空数组。
-(NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;

AVAsset是一个抽象不可变类,把媒体所有静态属性模块化成为一个整体,比如:标题,时长,元数据等, AVAsset不管你从URL还是照片库获取到媒体,它都会统一管理。
我们使用 AVAssetTrack 类代表保存在资源中的统一类型媒体,并对每个资源建立相应的模型。AVAssetTrack 最常见的形态就是音频和视频流,但是它还可以表示文本、副标题或隐藏字幕等媒体类型。
资源创建:

NSURL *assetURL = [NSURL URLWithString:@""];
AVAsset *asset = [AVAsset assetWithURL:assetURL];

AVAsset 是一个抽象类,意味着不能直接被实例化。当创建实例时,实际上是创建了它子类的一个实例,子类名为 AVURLAsset。有的时候我们会直接使用 AVURLAsset 这个类,因为它允许通过字典来调整资源的创建方式。比如允许程序稍长一点的加载时间,获取更准确的时长和时间信息。

NSURL *assetURL = [NSURL URLWithString:@""];

NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @YES };

AVURLAsset *asset = [AVURLAsset URLAssetWithURL:assetURL options:options];
元数据格式

Apple 环境下的媒体类型主要有四种
QuickTime( mov )
MPEG-4 video( mp4 和 m4v )
MPEG-4 audio( m4a )
MPEG-Layer III audio( mp3 )

QuickTime 是由苹果公司开发的一种功能强大、跨平台的媒体架构。
mp4 是对 MPEG-4 媒体的标准扩展,m4v、m4a、m4p、m4b 这些变体都使用 MPEG-4 容器格式,但是包含了附加的拓展功能。m4v 文件是带有针对 FairPlay 加密和 AC3-audio 扩展的 MPEG-4 视频格式,如果不涉及这些 mp4 和 m4v 仅仅是扩展名不同而已。m4a 针对音频,让使用者知道该文件只带有音频资源。m4p 是苹果较旧的 iTunes 音频格式。m4b 用于有声读物,通常包含章节标签和书签功能。
mp3 文件和上面两种有显著区别,mp3 文件不使用容器格式,而使用编码音频数据。mp3 文件使用一种称为 ID3v2 格式来保存关于音频内容的描述信息,包含的数据有歌曲演唱者、所属唱片和音乐风格等。AV Foundation 支持读取 ID3v2 标签的所有版本,但是不支持写入,所以 AV Foundation 无法支持对 mp3 进行编码。

异步载入

AVAsset使用一种高效的设计方法,延迟载入资源的属性,什么时候使用,什么时候再加载.虽然这可以解决一些由于直接加载数据带来的问题,但是访问AVAsset的属性如果耗时较长,而又发生在主线程,就会阻塞主线程,使界面无法响应.属性的访问总是同步发生的,如果没有预先载入,程序就会阻塞(我测了一下查询资源的availableMetadataFormats大概花了2秒左右),所以这时候就需要异步查询资源的属性。
所以AVAsset和AVAssetTrack都遵守了AVAsynchronousKeyValueLoading协议,通过下面两个方法实现异步查询

   -(AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * __nullable * __nullable)outError;
 -(void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler;

通过statusOfValueForKey方法查询一个给定属性的状态,该方法会返回一个枚举类型的AVKeyValueStatus值,用于标识查询的属性的状态,如果状态不是AVKeyValueStatusLoaded,意味着此时请求该属性会导致程序卡顿。
通过调用loadValuesAsynchronouslyForKeys: completionHandler:方法异步查询属性,为其提供一个属性的数组,当资源处于回应请求状态就会调用completionHandler代码块

 AVAsset *asset = [AVAsset assetWithURL:assetUrl];
    NSArray *keys = @[@"tracks",
                      @"playable",
                      @"duration"];

    __weak typeof(asset) weakAsset = asset;
    __weak typeof(self) weakSelf = self;
    [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
            // check the keys
            for (NSString *key in keys) {
                NSError *error = nil;
                AVKeyValueStatus keyStatus = [weakAsset statusOfValueForKey:key error:&error];

                switch (keyStatus) {
                    case AVKeyValueStatusFailed:{
                        // failed
                        break;
                    }
                    case AVKeyValueStatusLoaded:{
                        // success
                        break;
                    }case AVKeyValueStatusCancelled:{
                        // cancelled
                        break;
                    }
                    default:
                        break;
                }
        }
    }];
  • 使用元数据

AVAsset 和 AVAssetTrack 都可以实现查询相关元数据的功能。一般使用 AVAsset 提供的元数据,当涉及获取曲目一级元数据等情况时会使用 AVAssestTrack。读取具体资源元数据的接口由 AVMetadataItem 的类提供。


17E.png

上图中common部分表示,歌名,歌手,插图等信息,AVFoundation使用键空间(key spaces)作为将相关键组合在一起的方法,比如上图common它表示的歌手歌名就是每种格式的文件都会有的信息。

  • 我们可以通过查询资源(AVAsset)或曲目(AVAssetTrack)的commonMetadata属性从Common键空间获取元数据(歌手歌名等信息),这个属性会返回一个包含所有可用元数据的数组.
  • 通过availableMetedataFormats键来查询资源中所有的元数据的格式(mp4,mp3,mov等),比如com.apple.itunes代表itunes类型文件(.mp4 m4v .m4a)。并给每个格式调用metadataForFormat:方法获指定取元数据,这个方法返回一个包含所有相关元数据信息的NSArray.
    看一个例子
AVAsset *asset = [AVAsset assetWithURL:url];
    [asset loadValuesAsynchronouslyForKeys:@[@"availableMetadataFormats"] completionHandler:^{
        NSMutableArray *arr = [NSMutableArray array];
        //如果直接调用这个asset.availableMetadataFormats是同步的,会卡界面,上面的loadValuesAsynchronouslyForKeys:方法就是先异步加载好这个availableMetadataFormats属性
        for (NSString *str in asset.availableMetadataFormats) {
            [arr addObject:[asset metadataForFormat:str]];
            NSLog(@"%@",[asset metadataForFormat:str]);
        }
    }];

查找元数据

使用metadataItemsFromArray: withKey: keySpace:从一堆元数据数组中筛选出需要的一个,虽然返回是一个数组,但是其实里面通常只有一个元素
当我们拿到一个元数据的数组时,使用AVMetadataItem获取具体元数据的值。
例如:想要拿到一个m4a音频文件的歌手信息,需要用如下方法:

    NSArray *artistMetadata = [AVMetadataItem metadataItemsFromArray:arr2[0] withKey:AVMetadataiTunesMetadataKeyArtist keySpace:AVMetadataKeySpaceiTunes];

使用AVMetadataItem

AVMetadataItem最基本的形式其实是一个封装键值对的封装器
可通过它查询key或commonKey,查询其是否存在于Common键空间中,最重要的是它对应的value.
value属性被定义成id<NSObject,NSCopying>形式,不过它可能是NSString,NSNumber,NSData或者NSDictionary等.

    AVAsset *asset = [AVAsset assetWithURL:url];
    NSArray *arr = @[@"availableMetadataFormats"];
    NSMutableArray *arr2 = [NSMutableArray array];
    [asset loadValuesAsynchronouslyForKeys:arr completionHandler:^{
        NSError *error=nil;
        //如果直接调用这个asset.availableMetadataFormats是同步的,会卡界面,上面的loadValuesAsynchronouslyForKeys:方法就是先异步加载好这个availableMetadataFormats属性,然后现在就可以直接读了。这是apple的优化。
        AVKeyValueStatus state=[asset statusOfValueForKey:@"availableMetadataFormats" error:&error];
        switch (state) {
            case AVKeyValueStatusLoaded:
               for (NSString *format in asset.availableMetadataFormats)
                {
                    
                    NSArray *meatataArray=[asset metadataForFormat:format];
                    NSArray *artisArray=[AVMetadataItem metadataItemsFromArray:meatataArray withKey:AVMetadataCommonKeyArtist keySpace:AVMetadataKeySpaceCommon];
                    for (AVMetadataItem *item in artisArray)
                    {
                        NSLog(@"key=%@,value=%@",item.key,item.value);
                        dispatch_async(dispatch_get_main_queue(), ^{
//                            
                        });
                        
                    }
                }
                
                NSLog(@"%@",@"加载成功");
                break;
            case AVKeyValueStatusFailed:
                NSLog(@"%@",@"加载失败");
                break;
            default:
                break;
        }
    }];

大部分开发者第一次遇到AVMetadataItem都会碰到一个常见问题是:如何理解该对象的key属性,打开AVMetadaFormat.h文件,一看就容易明白。

保存元数据

前面提到过,AVAsset是个不可以变化的类,我们不能修改,我们要使用AVAssetExportSession的类来导出一个新的资源副本以及元数据的改动。AVAssetExportSession是用于将AVAsset内容根据导出预设进行编码,并将导出资源写到磁盘中。有兴趣大家可以去研究下。

日记本
Web note ad 1