一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
AVFoundation 是Apple iOS和OS X系统中用于处理基于时间的媒体数据的高级框架,通过开发所需的工具提供了强大的功能集,让开发者能够基于苹果平台创建当下最先进的媒体应用程序,其针对64位处理器设计,充分利用了多核硬件优势,会自动提供硬件加速操作,确保大部分设备能以最佳性能运行,是iOS开发接触音视频开发必学的框架之一。
参与掘金日新计划,持续记录AVFoundation学习,Demo学习地址,这篇文章主要讲述AVAsset资源和元数据,其他类的相关用法可查看我的其他文章。
AVAsset
AVAsset是AVFoundation最重要的类,AVFoundation设计的核心,在所有的特性和功能开发中扮演着至关重要的角色。AVAsset是一个抽象类和不可变类,定义了媒体资源混合呈现的方式,将媒体资源的静态属性模块化成一个整体,比如他们的标题、时长、元数据等。
AVAsset不需要考虑媒体资源所具有的封装格式、位置信息等。
- 它提供了对基本媒体格式层的抽象,无论你处理M4V视频或MP3音频,对你和框架而言,面对的只有资源这个概念,不需要考虑多种编解码器和容器格式不同带来的困扰
- AVAsset隐藏了位置信息,当通过URL创建一个资源,可以是本地path URL,或者服务器上的视频流URL。
创建资源
- 可以用本地URL也可以用网络URL创建
- 实际创建的是AVAsset的子类
AVURLAsset - AVURLAssetPreferPreciseDurationAndTimingKey可以获得更准确的时长计算,但加载时间也会长一些
let assetURL = URL(fileURLWithPath: Bundle.main.path(forResource: "01 Demo AAC", ofType: "m4a")!)
// 更精确的时长和计时信息,但加载时间也会长一些
let options = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
let asset: AVURLAsset = AVURLAsset(url: assetURL, options: options)
利用MediaPlayer框架,查询iPod资源创建AVAsset
// 查询条件
let artistPredicate: MPMediaPropertyPredicate = MPMediaPropertyPredicate(value: "Foo Fighters", forProperty: MPMediaItemPropertyArtist)
let albumPredicate: MPMediaPropertyPredicate = MPMediaPropertyPredicate(value: "In Your Honor", forProperty: MPMediaItemPropertyTitle)
let songPredicate: MPMediaPropertyPredicate = MPMediaPropertyPredicate(value: "Best of You", forProperty: MPMediaItemPropertyTitle)
// 添加查询条件
let query: MPMediaQuery = MPMediaQuery()
query.addFilterPredicate(artistPredicate)
query.addFilterPredicate(albumPredicate)
query.addFilterPredicate(songPredicate)
// 查询结果
let results = query.items
if let tempResults = results, tempResults.count > 0 {
let item: MPMediaItem = tempResults.first!
if let assetURL: URL = item.value(forProperty: MPMediaItemPropertyAssetURL) as? URL {
CQLog(assetURL)
// 获取ipod内资源url并创建AVAsset
let asset: AVAsset = AVAsset(url: assetURL)
}
}
利用Phtots框架,获取用户相册资源创建AVAsset
// 利用Phtots框架获取用户相册的资源
let allPhotoOptions = PHFetchOptions()
// 按日期倒序
// allPhotoOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let allPhotos: PHFetchResult = PHAsset.fetchAssets(with: allPhotoOptions)
allPhotos.enumerateObjects { asset, index, stop in
if asset.mediaType == .video {
// 视频资源
let options = PHVideoRequestOptions()
options.version = .current
options.deliveryMode = .automatic
PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { avAsset, audioMix, info in
if let urlAsset: AVURLAsset = avAsset as? AVURLAsset {
CQLog(urlAsset.url)
}
}
} else if asset.mediaType == .audio {
// 音频资源
} else if asset.mediaType == .image {
// 图片资源
let options: PHImageRequestOptions = PHImageRequestOptions()
options.resizeMode = .exact
options.isNetworkAccessAllowed = true
options.isSynchronous = true
PHImageManager.default().requestImageData(for: asset, options: options) { data, uti, orientation, info in
if uti == "com.compuserve.fig" {
}
if uti == "public.heif" || uti == "public.heic" {
}
}
} else if asset.mediaType == .unknown {
// 未知资源
}
}
异步载入Asset的属性
AVAsset有多种方法和属性,可以提供资源的信息,比如时长、创建日期、元数据等。AVAsset在创建时,只是对基础媒体文件的处理,延迟载入资源的属性,直到请求时才载入。这样就可以快速创建资源,而不用考虑因为立即载入相关媒体或元数据所带来的的问题,而属性的访问总是同步发生的,如果正在请求的属性没有预先载入,程序就会阻塞,显然这可能带来一定的问题。 AVAsset和AVAsset都遵循AVAsynchronousKeyValueLoading协议,可以通过statusOfValueForKey:error:和loadValuesAsynchronouslyforKeys异步查询属性
/**
这里需要注意,即便通过keys传了多个key,但也只会回调一次
回调有可能不在主线程,如果要做UI操作,需要异步回主线程
需要为每个key调用statusOfValue函数,不能假设所有的属性都返回相同的状态值
*/
let keys = ["tracks", "availableMetadataFormats"]
asset.loadValuesAsynchronously(forKeys: keys) {
var error: NSError? = nil
let tracksStatus = asset.statusOfValue(forKey: "tracks", error: &error)
switch tracksStatus {
case .loaded:
// CQLog(asset.tracks)
break
case .loading: break
case .cancelled: break
case .failed: break
case .unknown: break
@unknown default: break
}
}
查询媒体元数据
查询媒体资源的元数据,作者年份等
var metadataItems: [**AVMetadataItem**] = []
for format: AVMetadataFormat in asset.availableMetadataFormats {
metadataItems.append(contentsOf: asset.metadata(forFormat: format))
}
CQLog("\(metadataItems.last!.key!)"+"--"+"\(metadataItems.last!.keyString())"+"--"+"\(metadataItems.last!.value!)")
// 根据键空间和键筛选AVMetadataItem
let keySpace: AVMetadataKeySpace = AVMetadataKeySpace.audioFile
let artisKey = AVMetadataKey.iTunesMetadataKeyArtist
let albumKey = AVMetadataKey.iTunesMetadataKeyAlbum
let artistMetadata = AVMetadataItem.metadataItems(from: metadataItems, withKey: artisKey, keySpace: keySpace)
let albumMetadata = AVMetadataItem.metadataItems(from: metadataItems, withKey: albumKey, keySpace: keySpace)
let artistItem, albumItem : **AVMetadataItem**?
artistItem = artistMetadata.first
albumItem = albumMetadata.first
if artistItem != nil {CQLog(artistItem!)}
if albumItem != nil {CQLog(albumItem!)}
// 根据标识符筛选AVMetadataItem
let nameKeyId: AVMetadataIdentifier = AVMetadataIdentifier.iTunesMetadataSongName
let nameMetadata = AVMetadataItem.metadataItems(from: metadataItems, filteredByIdentifier: nameKeyId)
let nameItem: AVMetadataItem? = nameMetadata.first
if nameItem != nil {CQLog(nameItem!.keyString())}