AVFoundation-资源和元数据

AVAsset 是一个抽象类和不可变类,它定义了媒体资源混合呈现的方式,将媒体资源的静态属性模块化为一个整体,包括标题、时长和元数据。AVAsset 提供了基本媒体格式的层抽象,隐藏了资源的位置信息。

1. 创建资源

通过 URL 创建一个 AVAsset

NSURL *assetUrl = // url
AVAsset *asset  =[AVAsset assetWithURL:assetUrl];

assetWithURL 方法生成的实际类是 AVAsset 的子类 AVURLAsset,它允许通过 options 字典来调整创建方式。

获取方式

常见的获取 AVAsset 的途径有

  • iOS Asset 库,即相册资源 —— Photo Framework
  • iOS iPod 库 —— MediaPlayer
  • MAC iTunes 库 —— iTunesLibrary 框架

这里只举 Photo Framework 的例子

                [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset * _Nullable obj, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
                    @strongify(self)
                    AVURLAsset *urlAsset = (AVURLAsset *)obj;
                    NSURL *url = urlAsset.URL;
                    NSData *data = [NSData dataWithContentsOfURL:url];
                }];

2. 异步加载

AVAsset 对资源属性实现了延迟加载特性,仅在请求时才载入,这样如果是同步请求属性,就可能因为属性没有预先载入而阻塞主线程,因此应采用异步加载属性的方法。

AVAsset 和 AVAssetTrack 都遵循了 AVAsynchronousKeyValueLoading 协议,此协议有两个方法

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

第一个方法用于确定当前属性是预先加载好了、尚未加载还是加载出错了,而第二个方法则是异步加载属性的方法。

        [self.targetAVAsset loadValuesAsynchronouslyForKeys:@[@"trackss"] completionHandler:^{
            NSError *error = nil;
            AVKeyValueStatus status = [self.targetAVAsset statusOfValueForKey:@"tracks" error:nil];
            switch (status) {
                case AVKeyValueStatusLoaded: {
                    NSLog(@"AVKeyValueStatusLoaded");
                    break;
                }
                case AVKeyValueStatusFailed: {
                    NSLog(@"AVKeyValueStatusFailed");
                    break;
                }
                case AVKeyValueStatusUnknown: {
                    NSLog(@"AVKeyValueStatusUnknown");
                    break;
                }
                case AVKeyValueStatusCancelled: {
                    NSLog(@"AVKeyValueStatusCancelled");
                    break;
                }
                default:
                    break;
            }
        }];

这里请求的参数可以是多个,但是当请求多个参数时,异步请求的回调只会调用一次,此时需要分别对各个属性调用 statusOfValueForKey 方法来判断是否加载完成。

3. 媒体元数据

3.1 元数据格式

在 Apple 环境下,最常见四种媒体类型,分别是 QuickTime(mov)、MPEG-4 video(mp4 和 m4v)、MPEG-4 audio(m4a)和 MPEG-Layer III Audio(mp3)。

QuickTime 是苹果公司开发的一种跨平台媒体架构,由一种称为 atoms 的数据结构组成,一个 atom 可以包含元数据,也可以包含其他 atom,但不能两者都包含。

MPEG-4 Part 14 定义了 MP4 文件格式的规范,MP4 直接派生于 QuickTime 文件格式,结构类似。要注意,MP4 有多种文件拓展名。.mp4 是标准扩展名,.m4v 是带有苹果公司针对 FairPlay 加密及 AC3-audio 扩展的 MPEG-4 视频格式,而 .m4a 针对音频,m4p 针对较旧的 itunes 音频格式,m4b 针对有声读物。

MP3 不是容器格式,使用编码音频数据,使用 ID3v2 格式保存元数据。由于专利限制,AVFoundation 只支持读取 MP3,不支持编码 MP3。

4. 使用元数据

AVFoundation 使用键空间作为将相关键组合在一起的方法,可以实现对 AVMetaDataItem 实例集合的筛选。Common 键空间用于定义所有支持的媒体类型的键,包括曲名、歌手、插图信息等。开发者可以通过查询资源或曲目的 commonMetadata 属性从 common 键空间获取元数据,这个属性会返回一个包含所有可用元数据的数组。

访问指定格式的元数据,需要对 AVAsset 对象调用 metadataForFormat 方法,此方法包含一个用于定义元数据格式的 NSString 对象并返回一个包含所有相关元数据信息的 NSArray。一个 AVAsset 所支持的资源格式可以通过 availableMetadataFormats 属性来获取。

        [self.targetAVAsset loadValuesAsynchronouslyForKeys:@[@"availableMetadataFormats"] completionHandler:^{
            NSError *error = nil;
            AVKeyValueStatus status = [self.targetAVAsset statusOfValueForKey:@"availableMetadataFormats" error:nil];
            switch (status) {
                case AVKeyValueStatusLoaded: {
                    for (NSString *format in self.targetAVAsset.availableMetadataFormats) {
                        NSLog(@"%@", format);
                        NSLog(@"%@", [self.targetAVAsset metadataForFormat:format]);
                    }
                    break;
                }
                case AVKeyValueStatusFailed: {
                    NSLog(@"AVKeyValueStatusFailed");
                    break;
                }
                case AVKeyValueStatusUnknown: {
                    NSLog(@"AVKeyValueStatusUnknown");
                    break;
                }
                case AVKeyValueStatusCancelled: {
                    NSLog(@"AVKeyValueStatusCancelled");
                    break;
                }
                default:
                    break;
            }
        }];

示例输出如下

com.apple.quicktime.mdta
(
    "<AVMetadataItem: 0x1c801f230, identifier=mdta/com.apple.quicktime.software, keySpace=mdta, key class = __NSCFString, key=com.apple.quicktime.software, commonKey=software, extendedLanguageTag=(null), dataType=com.apple.metadata.datatype.UTF-8, time={INVALID}, duration={INVALID}, startDate=(null), extras={\n    dataType = 1;\n    dataTypeNamespace = \"com.apple.quicktime.mdta\";\n}, value class=__NSCFString, value=video.vue.ios.280>",
    "<AVMetadataItem: 0x1c42004d0, identifier=mdta/com.apple.quicktime.description, keySpace=mdta, key class = __NSCFString, key=com.apple.quicktime.description, commonKey=description, extendedLanguageTag=(null), dataType=com.apple.metadata.datatype.UTF-8, time={INVALID}, duration={INVALID}, startDate=(null), extras={\n    dataType = 1;\n    dataTypeNamespace = \"com.apple.quicktime.mdta\";\n}, value class=__NSCFString, value=ewogICJzc2Z3IiA6IFsKICAgICIiCiAgXSwKICAic2RldiIgOiBbCiAgICAiIgogIF0sCiAgInNjIiA6IDEsCiAgInNsb2MiIDogWwogICAgIiIKICBdLAogICJzdWIiIDogWwogICAgIiIKICBdLAogICJ0cnMiIDogWwogICAgMCwKICAgIDAKICBdLAogICJzZHVyIiA6IFsKICAgIDEwCiAgXSwKICAiY2FwIiA6ICIgIiwKICAiZiIgOiBbCiAgICAiMSIKICBdCn0=>"
)

5. 实践

5.1 对 AVAsset 进行封装

通过 url 我们可以实例化一个 AVAsset,也可以获取到其中一些有效的元数据信息

        _url = url;
        _asset = [AVAsset assetWithURL:url];
        _filename = [url lastPathComponent];
        _filetype = [self fileTypeForURL:url];                              
        _editable = ![_filetype isEqualToString:AVFileTypeMPEGLayer3];

lastPathComponent 从 URL (例如 "file:///Users/yasic/Library/Application%20Support/MetaManager/01%20Demo%20AAC.m4a")的最后一个路径取出文件名,fileTypeForURL 具体实现如下

- (NSString *)fileTypeForURL:(NSURL *)url {
    NSString *ext = [[self.url lastPathComponent] pathExtension];
    NSString *type = nil;
    if ([ext isEqualToString:@"m4a"]) {
        type = AVFileTypeAppleM4A;
    } else if ([ext isEqualToString:@"m4v"]) {
        type = AVFileTypeAppleM4V;
    } else if ([ext isEqualToString:@"mov"]) {
        type = AVFileTypeQuickTimeMovie;
    } else if ([ext isEqualToString:@"mp4"]) {
        type = AVFileTypeMPEG4;
    } else {
        type = AVFileTypeMPEGLayer3;
    }
    return type;
}

5.2 获取元数据

对于通用键空间,可以通过 commonMetadata 属性获取到元数据对应的 AVMetadataItem 对象。

    NSArray *keys = @[COMMON_META_KEY];
    [self.asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
        AVKeyValueStatus commonStatus =
            [self.asset statusOfValueForKey:COMMON_META_KEY error:nil];
        self.prepared = (commonStatus == AVKeyValueStatusLoaded);

        if (self.prepared) {
            for (AVMetadataItem *item in self.asset.commonMetadata) {
                //NSLog(@"%@: %@", item.keyString, item.value);
                [self.metadata addMetadataItem:item withKey:item.commonKey];
            }
        }
    }];

对于其他格式的元数据,先获取到可用格式的数组,然后分别进行元数据获取

    NSArray *keys = @[AVAILABLE_META_KEY];
    [self.asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
        AVKeyValueStatus formatsStatus = [self.asset statusOfValueForKey:AVAILABLE_META_KEY error:nil];
        self.prepared = (formatsStatus == AVKeyValueStatusLoaded);

        if (self.prepared) {
            for (id format in self.asset.availableMetadataFormats) {        // 5
                if ([self.acceptedFormats containsObject:format]) {
                    NSArray *items = [self.asset metadataForFormat:format];
                    for (AVMetadataItem *item in items) {
                        //NSLog(@"%@: %@", item.keyString, item.value);
                        [self.metadata addMetadataItem:item
                                               withKey:item.keyString];
                    }
                }
            }
        }
    }];

获取到的元数据有些是可以直接阅读的,有些则不够语义化,需要进行不同方式的转化,包括 Artwork、注释、音轨数据、唱片数据、风格数据等类型的数据,这里不再赘述。

5.3 保存元数据

对于元数据的修改不能直接操作原 AVAsset,需要使用 AVAssetExportSession 导出一个新的资源副本来覆盖原本的资源对象。

    NSString *presetName = AVAssetExportPresetPassthrough;
    AVAssetExportSession *session =
        [[AVAssetExportSession alloc] initWithAsset:self.asset
                                         presetName:presetName];

AVAssetExportSession 的初始化需要一个待修改的 AVAsset 对象,以及一个预设值,AVAssetExportPresetPassthrough 预设值能够在不重新编码媒体的前提下实现写入元数据功能,但不能添加新的元数据。

    NSURL *outputURL = [self tempURL];
    session.outputURL = outputURL;
    session.outputFileType = self.filetype;
    session.metadata = [self.metadata metadataItems];

指定导出副本的存储位置,配置到 session 上。

    [session exportAsynchronouslyWithCompletionHandler:^{
        AVAssetExportSessionStatus status = session.status;
        BOOL success = (status == AVAssetExportSessionStatusCompleted);
        if (success) {
            NSURL *sourceURL = self.url;
            NSFileManager *manager = [NSFileManager defaultManager];
            [manager removeItemAtURL:sourceURL error:nil];
            [manager moveItemAtURL:outputURL toURL:sourceURL error:nil];
        }
    }];

导出需要用到 exportAsynchronouslyWithCompletionHandler 方法,完成后对状态进行判断,获知导出是否成功,然后通过 NSFileManager 的 moveItemAtURL 方法来覆盖原始的媒体资源。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容