iOS音频开发之资源(AVAsset)与元数据,解决获取资源属性问题(三)
本文主要是的分析,程序成功解决获取媒体元数据的信息,还可以对除了mp3之外的所有资源写入元数据信息。再次回顾我们的问题。
先提出一个问题,生活中有很多的媒体格式,mp3,avi,rmvb等等,在苹果环境下主要的媒体格式有4种,quicktime(mov),mpeg-4 video(mp4,m4v),mpeg-4 audio(m4a),mpeg-layer iii audio(mp3),那么问题来了,假如给你一个mp3文件,比如歌曲《再见.mp3》张震岳,你是不是有办法读取里面的数据,比如读取它的歌名,演唱者,属于哪个专辑,专辑的封面,文件的长度等等信息???
这是个mac程序,用户ui并不是我要讨论的重点。下面的代码,可以获取资源的路径,从而开始我们分析元数据。
- (void)loadtable { nsfilemanager *filemanager = [nsfilemanager defaultmanager]; nsurl *rooturl = [nsurl fileurlwithpath:[filemanager applicationsupportdirectory]]; nsarray *items = [filemanager contentsofdirectoryaturl:rooturl includingpropertiesforkeys:@[nsurlnamekey, nsurleffectiveiconkey] options:nsdirectoryenumerationskipshiddenfiles error:nil]; [items enumerateobjectsusingblock:^(nsurl *url, nsuinteger idx, bool *stop) { [self.mediaitemscontroller addobject:[[thmediaitem alloc] initwithurl:url]]; }]; [self.tableview reloaddata]; }我们查看文件的组织结构,如下图,这里引出了thmediaitem类,我们的程序主要围绕这个类进行展开的。注意文件夹converters。
分析头文件,几个接口函数及几个属性函数。
typedef void(^thcompletionhandler)(bool complete); @interface thmediaitem : nsobject @property (strong, readonly) nsstring *filename; @property (strong, readonly) nsstring *filetype; @property (strong, readonly) thmetadata *metadata; @property (readonly, getter = iseditable) bool editable; - (id)initwithurl:(nsurl *)url; - (void)preparewithcompletionhandler:(thcompletionhandler)handler; - (void)savewithcompletionhandler:(thcompletionhandler)handler; @end
需要注意类thmetadata。editable 表示是否可以编辑,mp3格式是无法编辑的,所以要区别对待,因为mp3有专利上的问题,所以苹果不支持。前面说过,不同的媒体的元数据格式是不同的,所以有个filetype属性,下面代码能解决这个这个问题,有m4a,m4v,mov,mp4,这些格式。
- (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; }
获取元数据的信息主要是在函数
preparewithcompletionhandler:(thcompletionhandler)completionhandler
中实现,实现的代码如下
- (void)preparewithcompletionhandler:(thcompletionhandler)completionhandler { if (self.prepared) { // 1 completionhandler(self.prepared); return; } self.metadata = [[thmetadata alloc] init]; // 2 nsarray *keys = @[common_meta_key, available_meta_key]; [self.asset loadvaluesasynchronouslyforkeys:keys completionhandler:^{ avkeyvaluestatus commonstatus = [self.asset statusofvalueforkey:common_meta_key error:nil]; avkeyvaluestatus formatsstatus = [self.asset statusofvalueforkey:available_meta_key error:nil]; self.prepared = (commonstatus == avkeyvaluestatusloaded) && // 3 (formatsstatus == avkeyvaluestatusloaded); if (self.prepared) { for (avmetadataitem *item in self.asset.commonmetadata) { // 4 nslog(@"%@: %@ item commonkey : %@", item.keystring, item.value,item.commonkey); [self.metadata addmetadataitem:item withkey:item.commonkey]; } 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]; } } } } dispatch_async(dispatch_get_main_queue(), ^{ completionhandler(self.prepared); }); }]; }其中common_meta_key,available_meta_key是定义的宏,这个函数在上一篇文章中介绍过。下面是一些打印信息
2016-09-18 16:20:33.239 metamanager[2582:194668] tit2: 再见 item commonkey : title
2016-09-18 16:20:33.239 metamanager[2582:194668] the keymapping {
"@cmt" = comments;
"@gen" = genre;
"@grp" = grouping;
"@wrt" = composer;
com = comments;
comm = comments;
tbp = bpm;
tbpm = bpm;
tdrc = year;
tp2 = albumartist;
tpa = discnumber;
tpe2 = albumartist;
tpos = discnumber;
trck = tracknumber;
trk = tracknumber;
tye = year;
tyer = year;
aart = albumartist;
albumname = album;
artist = artist;
artwork = artwork;
"com.apple.quicktime.director" = composer;
"com.apple.quicktime.genre" = genre;
"com.apple.quicktime.producer" = artist;
"com.apple.quicktime.year" = year;
creationdate = year;
creator = composer;
description = comments;
disk = discnumber;
grup = grouping;
ldes = comments;
subject = grouping;
title = name;
tmpo = bpm;
trkn = tracknumber;
type = genre;
}
2016-09-18 16:20:33.239 metamanager[2582:194668] talb: ok item commonkey : albumname2016-09-18 16:20:33.240 metamanager[2582:194668] tpe1: 张震岳 item commonkey : artist2016-09-18 16:20:33.246 metamanager[2582:194668] apic:
0 fffe003c 43524541 544f523a 2067642d 6a706567 2076312e 30202875 73696e67 20494a47 204a5045 47207636 32292c20 7175616c 69747920 3d203130 300affdb 00430001 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 010101ff db004301 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 ffc00011 0800f000 f0030122 00021101 031101ff c4001f00 00000603 01010100 00000000 00000000 04050708 090a0306 0b020100 ffc40041 10000201 03040004 04040502 050303.......
是不是我们找到我们想到的信息,比如歌手,标题名字等等。现在要将目光转移到thmetadata类,她的主要作用是从相关的元数据元素中提取值,并将她们保存,现在我们应该猜到这个类里面有很多的属性了吧。请看下面的thmetadata类头文件文件的内容。
@class thgenre; @interface thmetadata : nsobject @property (copy) nsstring *name; @property (copy) nsstring *artist; @property (copy) nsstring *albumartist; @property (copy) nsstring *album; @property (copy) nsstring *grouping; @property (copy) nsstring *composer; //作曲家 @property (copy) nsstring *comments; @property (strong) nsimage *artwork;//图片 @property (strong) thgenre *genre; @property nsstring *year; @property nsnumber *bpm; @property nsnumber *tracknumber; @property nsnumber *trackcount; @property nsnumber *discnumber; @property nsnumber *disccount; - (void)addmetadataitem:(avmetadataitem *)item withkey:(id)key; /** * 获取所有当前保存展示内容,并将转化类转化为avmetadataitem * * @return */ - (nsarray *)metadataitems; @end
注意两个接口函数以及类thgenre。我们看看这个类的实现,首先是init函数
- (id)init { self = [super init]; if (self) { _keymapping = [self buildkeymapping]; // 1 _metadata = [nsmutabledictionary dictionary]; // 2 _converterfactory = [[thmetadataconverterfactory alloc] init]; // 3 } return self; }
引出了两个重要的属性,keymapping 和 converterfactory。还记得前面提到文件夹converterfactory?这些文件都是为这个属性服务的,她的主要作用是将元数据的值通过提取和转换为可以用于展示的格式,比如nsstring。要实现对不同的键空间的键和格式的标准化工作,需要创建一个从指定格式键到标准化键的映射。所以有了keymapping,我们回头看看前面打印的信息,是不是有个keymapping,这个就是我们建立的keymapping。建立keymapping代码如下。
- (nsdictionary *)buildkeymapping { return @{ // name mapping avmetadatacommonkeytitle : thmetadatakeyname, // artist mapping avmetadatacommonkeyartist : thmetadatakeyartist, avmetadataquicktimemetadatakeyproducer : thmetadatakeyartist, // album artist mapping avmetadataid3metadatakeyband : thmetadatakeyalbumartist, avmetadataitunesmetadatakeyalbumartist : thmetadatakeyalbumartist, @"tp2" : thmetadatakeyalbumartist, // album mapping avmetadatacommonkeyalbumname : thmetadatakeyalbum, // artwork mapping avmetadatacommonkeyartwork : thmetadatakeyartwork, // year mapping avmetadatacommonkeycreationdate : thmetadatakeyyear, avmetadataid3metadatakeyyear : thmetadatakeyyear, @"tye" : thmetadatakeyyear, avmetadataquicktimemetadatakeyyear : thmetadatakeyyear, avmetadataid3metadatakeyrecordingtime : thmetadatakeyyear, // bpm mapping avmetadataitunesmetadatakeybeatspermin : thmetadatakeybpm, avmetadataid3metadatakeybeatsperminute : thmetadatakeybpm, @"tbp" : thmetadatakeybpm, // grouping mapping avmetadataitunesmetadatakeygrouping : thmetadatakeygrouping, @"@grp" : thmetadatakeygrouping, avmetadatacommonkeysubject : thmetadatakeygrouping, // track number mapping avmetadataitunesmetadatakeytracknumber : thmetadatakeytracknumber, avmetadataid3metadatakeytracknumber : thmetadatakeytracknumber, @"trk" : thmetadatakeytracknumber, // composer mapping avmetadataquicktimemetadatakeydirector : thmetadatakeycomposer, avmetadataitunesmetadatakeycomposer : thmetadatakeycomposer, avmetadatacommonkeycreator : thmetadatakeycomposer, // disc number mapping avmetadataitunesmetadatakeydiscnumber : thmetadatakeydiscnumber, avmetadataid3metadatakeypartofaset : thmetadatakeydiscnumber, @"tpa" : thmetadatakeydiscnumber, // comments mapping @"ldes" : thmetadatakeycomments, avmetadatacommonkeydescription : thmetadatakeycomments, avmetadataitunesmetadatakeyusercomment : thmetadatakeycomments, avmetadataid3metadatakeycomments : thmetadatakeycomments, @"com" : thmetadatakeycomments, // genre mapping avmetadataquicktimemetadatakeygenre : thmetadatakeygenre, avmetadataitunesmetadatakeyusergenre : thmetadatakeygenre, avmetadatacommonkeytype : thmetadatakeygenre }; }像thmetadatakeyname,thmetadatakeyartist这些常亮是在另外文件定义好的。接下来的函数的重点。
- (void)addmetadataitem:(avmetadataitem *)item withkey:(id)key { nslog(@"the keymapping %@",self.keymapping); nsstring *normalizedkey = self.keymapping[key]; // 1 if (normalizedkey) { id converter = // 2 [self.converterfactory converterforkey:normalizedkey]; // extract the value from the avmetadataitem instance and // convert it into a format suitable for presentation. id value = [converter displayvaluefrommetadataitem:item]; // track and disc numbers/counts are stored as nsdictionary if ([value iskindofclass:[nsdictionary class]]) { // 3 nsdictionary *data = (nsdictionary *) value; for (nsstring *currentkey in data) { if (![data[currentkey] iskindofclass:[nsnull class]]) { [self setvalue:data[currentkey] forkey:currentkey]; } } } else { //唱片和曲目编号是一种特殊的型号 [self setvalue:value forkey:normalizedkey]; } // store the avmetadataitem away for later use self.metadata[normalizedkey] = item; // 4 } }
这个函数传入两个函数 avmetadataitem数据,指的是item的commonkey,看一段打印信息
}, value=张震岳>,
这里commonkey指的是artist,定义的常量为thmetadatakeyartist。现在注意这行代码
id converter = // 2 [self.converterfactory converterforkey:normalizedkey];终于联系到这个converterfactory函数了,这里有的协议protocol,thmetadataconverter,转换函数都是遵守这个协议的,请看下面代码寻找各自的类。
@implementation thmetadataconverterfactory - (id )converterforkey:(nsstring *)key { id converter = nil; if ([key isequaltostring:thmetadatakeyartwork]) { converter = [[thartworkmetadataconverter alloc] init]; } else if ([key isequaltostring:thmetadatakeytracknumber]) { converter = [[thtrackmetadataconverter alloc] init]; } else if ([key isequaltostring:thmetadatakeydiscnumber]) { converter = [[thdiscmetadataconverter alloc] init]; } else if ([key isequaltostring:thmetadatakeycomments]) { converter = [[thcommentmetadataconverter alloc] init]; } else if ([key isequaltostring:thmetadatakeygenre]) { converter = [[thgenremetadataconverter alloc] init]; } else { converter = [[thdefaultmetadataconverter alloc] init]; } return converter; } @end请看下面的这个factory的类文件组织结构。
看有这么多的格式要判断。回到那个协议,请看下面两个要实现的接口
@protocol thmetadataconverter /** * 分析avmetadataitem 将数据转化为可展示的的格式 */ - (id)displayvaluefrommetadataitem:(avmetadataitem *)item; /** * 将可展示的内容转化为之前的 avmetadataitem */ - (avmetadataitem *)metadataitemfromdisplayvalue:(id)value withmetadataitem:(avmetadataitem *)item; @end
这两个函数的主要功能就是上面的注释!在处理avmetaitem的时候,最复杂的就是后去value值,看看有些是比较简单,比如歌名,"张震岳"直接可以取得,但是想图片,artwork,comment(评论)这些数据是要分析的。下面给出获取普通的信息代码和图片信息的代码,不展开分析。
@implementation thdefaultmetadataconverter - (id)displayvaluefrommetadataitem:(avmetadataitem *)item { return item.value; } - (avmetadataitem *)metadataitemfromdisplayvalue:(id)value withmetadataitem:(avmetadataitem *)item { avmutablemetadataitem *metadataitem = [item mutablecopy]; metadataitem.value = value; return metadataitem; } @end
@implementation thartworkmetadataconverter - (id)displayvaluefrommetadataitem:(avmetadataitem *)item { nsimage *image = nil; if ([item.value iskindofclass:[nsdata class]]) { // 1 image = [[nsimage alloc] initwithdata:item.datavalue]; } else if ([item.value iskindofclass:[nsdictionary class]]) { // 2 nsdictionary *dict = (nsdictionary *)item.value; image = [[nsimage alloc] initwithdata:dict[@"data"]]; } return image; } - (avmetadataitem *)metadataitemfromdisplayvalue:(id)value withmetadataitem:(avmetadataitem *)item { avmutablemetadataitem *metadataitem = [item mutablecopy]; nsimage *image = (nsimage *)value; metadataitem.value = image.tiffrepresentation; // 3 return metadataitem; } @end
我还有很多的细节没有分享,但是总体的思路是这样的,分析源码学习了很多的东西,其实这个问题没有想象中这么简单,还有一个功能是保存元数据。
二.保存元数据
前面提到过,avasset是个不可以变化的类,我们不能修改,我们要使用avassetexportsession的类来导出一个新的资源副本以及元数据的改动。avassetexportsession是用于将avasset内容根据导出预设进行编码,并将导出资源写到磁盘中。有兴趣大家可以去研究下。
本文
完!
上一篇: iOS GCD多线程