【AVFoundation】AVAsset资源和元数据

1,338 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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())}