美文网首页AVPlayerios进阶iOS
AVFoundation(二):核心AVAsset

AVFoundation(二):核心AVAsset

作者: 小笨狼 | 来源:发表于2015-12-09 13:23 被阅读12897次

    AVFoundation是一个对多媒体操作的库。多媒体一般以文件或者流的形式存在,显而易见,直接对多媒体进行操作并不是一件愉快的事,这需要我们了解很多底层多媒体方面的知识。AVFoundation为我们提供了一个多媒体的载体类:AVAsset,在AVAsset中有着统一并且友好的接口,我们不需要了解太多多媒体的知识(当然还是需要了解一些的),就能对其进行操作。

    基本属性

    我们将描述视频基本信息的属性称为基本属性。AVAsset的属性从根本上来说是多媒体文件(如视频文件)的属性,我们先来看看多媒体文件中有哪些属性。用十六进制编辑器打开一个视频文件是最完整的查看视频中信息的方法,不过这样并不利于我们的阅读,因为数据太多了。apple提供了一个很好的查看视频信息的工具Atom Inspector,它会将十六进制的数据归类,并提取出其中有用的信息,即有利于查阅信息,也可以很方便的查看视频完整的16进制,了解视频的结构。
    用Atom Inspector打开一个视频文件


    我们可以看到在moov的目录下有一个mvhd,mvhd也称为movie header,它是整个视频的描述部分,里面包含着视频的基本信息,如时长,创建时间等。这些信息就是视频文件的基本属性,他们对应到AVAsset中有:
    //  Indicates the duration of the asset. If @"providesPreciseDurationAndTiming" is NO, a best-available estimate of the duration is returned. The degree of precision preferred for timing-related properties can be set at initialization time for assets initialized with URLs. See AVURLAssetPreferPreciseDurationAndTimingKey for AVURLAsset below.
    @property (nonatomic, readonly) CMTime duration;
    
    //  indicates the natural rate at which the asset is to be played; often but not always 1.0
    @property (nonatomic, readonly) float preferredRate;
    
    //  indicates the preferred volume at which the audible media of an asset is to be played; often but not always 1.0
    @property (nonatomic, readonly) float preferredVolume;
    
    // Indicates the creation date of the asset as an AVMetadataItem. May be nil. If a creation date has been stored by the asset in a form that can be converted to an NSDate, the dateValue property of the AVMetadataItem will provide an instance of NSDate. Otherwise the creation date is available only as a string value, via -[AVMetadataItem stringValue].
    @property (nonatomic, readonly, nullable) AVMetadataItem *creationDate NS_AVAILABLE(10_8, 5_0);
    

    首先duration属性是CMTime类型,CMTime是一个结构体

    typedef struct
    {
        CMTimeValue value;      // @field value The value of the CMTime. value/timescale = seconds.
        CMTimeScale timescale;  // @field timescale The timescale of the CMTime. value/timescale = seconds. 
        CMTimeFlags flags;      // @field flags The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. 
        CMTimeEpoch epoch;      // @field epoch Differentiates between equal timestamps that are actually different because of looping, multi-item sequencing, etc. Will be used during comparison: greater epochs happen after lesser ones. Additions/subtraction is only possible within a single epoch, however, since epoch length may be unknown/variable.
    } CMTime;
    

    它既包含了value,又包含了timescale,所以duration属性由视频中的duration和timescale共同组成。

    一般QuickTime和MPEG-4格式的mvhd中都有duration和timescale字段。不过其他格式可能不存在这2个字段,这时duration的值就需要通过计算才能得出。
    如果创建AVURLAsset时传入的AVURLAssetPreferPreciseDurationAndTimingKey值为NO(不传默认为NO),duration会取一个估计值,计算量比较小。反之如果为YES,duration需要返回一个精确值,计算量会比较大,耗时比较长。

    preferredRatepreferredVolume属性分别表示视频默认的速度和音量,这两个属性直接从mvhd中取出来即可,一般情况下,他们的都是1。
    creationDate属性表示视频的创建时间,对应着mvhd中的created。如果mvhd中没有创建时间,creationDate会返回nil。

    AVAssetTrack

    在mvhd下面,我们可以看到有3个轨道(track),一般的视频至少有2个轨道,一个播放声音,一个播放画面。AVFoundation中有一个专门的类承载多媒体中的track:AVAssetTrack。
    打开Atom Inspector中的track,我们可以看到,track中有一个tkhd(track header),其中包含了track的基本信息:



    跟mvhd类似,tkhd中包含了duration,rate,volume,created,除此之外,tkhd中还有一个很重要的字段:track id,这是视频中track的唯一标示符。在AVAsset中,可以通过trackId,获得特定的track

    /*  Provides an instance of AVAssetTrack that represents the track of the specified trackID.    */
    - (nullable AVAssetTrack *)trackWithTrackID:(CMPersistentTrackID)trackID;
    

    除了通过trackID获得track之外,AVAsset中还提供了其他3中方式获得track

    //  Provides the array of AVAssetTracks contained by the asset
    @property (nonatomic, readonly) NSArray<AVAssetTrack *> *tracks;
    
    //  Provides an array of AVAssetTracks of the asset that present media of the specified media type.
    - (NSArray<AVAssetTrack *> *)tracksWithMediaType:(NSString *)mediaType;
    
    //  Provides an array of AVAssetTracks of the asset that present media with the specified characteristic.
    - (NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;
    

    tracks中包含了当前Asset中的所有track,通过遍历我们可以获得想要的track.
    -tracksWithMediaType:方法会根据指定的媒体类型返回一个track数组,数组中包含着Asset中所有指定媒体类型的track。如果Asset中没有这个媒体类型的track,返回一个空数组。AVMediaFormat中一共定义了8种媒体类型:

    AVF_EXPORT NSString *const AVMediaTypeVideo                 NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaTypeAudio                 NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaTypeText                  NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaTypeClosedCaption         NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaTypeSubtitle              NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaTypeTimecode              NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaTypeMetadata              NS_AVAILABLE(10_8, 6_0);
    AVF_EXPORT NSString *const AVMediaTypeMuxed                 NS_AVAILABLE(10_7, 4_0);
    

    -tracksWithMediaCharacteristic:方法会根据指定的媒体特征返回track数组,数组的特性与-tracksWithMediaType:类似,如果Asset中没有这个媒体特征的track,返回一个空数组。AVMediaFormat中一共定义了15种媒体特征:

    AVF_EXPORT NSString *const AVMediaTypeMetadataObject NS_AVAILABLE_IOS(9_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicVisual      NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicAudible     NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicLegible     NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicFrameBased  NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicIsMainProgramContent NS_AVAILABLE(10_8, 5_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicIsAuxiliaryContent NS_AVAILABLE(10_8, 5_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicContainsOnlyForcedSubtitles NS_AVAILABLE(10_8, 5_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicTranscribesSpokenDialogForAccessibility NS_AVAILABLE(10_8, 5_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicDescribesMusicAndSoundForAccessibility NS_AVAILABLE(10_8, 5_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicEasyToRead NS_AVAILABLE(10_8, 6_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicDescribesVideoForAccessibility NS_AVAILABLE(10_8, 5_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicLanguageTranslation NS_AVAILABLE(10_11, 9_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicDubbedTranslation NS_AVAILABLE(10_11, 9_0);
    AVF_EXPORT NSString *const AVMediaCharacteristicVoiceOverTranslation NS_AVAILABLE(10_11, 9_0);  
    

    元数据

    再往下看有一个meta(meta data)和udta(user data),里面都保存着视频的元数据,不过由于这个视频没有元数据,可能是因为国内正版视频不好找的原因,我找了几个其他的视频也没找到元数据。所以暂且使用AV Foundation开发秘籍中的图片。
    用Atom Inspector打开《超人总动员》mov格式的视频,可以看到视频的结构:


    在udta中我们可以看到版权(@cpy)持有者为Pixar公司,导演(@dir)是Brad Bird,另外在meta->ilst中电影的名字是the Incredibles,年份是2006年,类型是Kids & Family。这些数据都会存放在AVAsset的metadata中
    /*  Provides access to an array of AVMetadataItems for each common metadata key for which a value is available; items can be filtered according to language via +[AVMetadataItem metadataItemsFromArray:filteredAndSortedAccordingToPreferredLanguages:] and according to identifier via +[AVMetadataItem metadataItemsFromArray:filteredByIdentifier:].        */
    @property (nonatomic, readonly) NSArray<AVMetadataItem *> *commonMetadata;
    
    //  Provides access to an array of AVMetadataItems for all metadata identifiers for which a value is available; items can be filtered according to language via +[AVMetadataItem metadataItemsFromArray:filteredAndSortedAccordingToPreferredLanguages:] and according to identifier via +[AVMetadataItem metadataItemsFromArray:filteredByIdentifier:].
    @property (nonatomic, readonly) NSArray<AVMetadataItem *> *metadata NS_AVAILABLE(10_10, 8_0);
    
    //  Provides an NSArray of NSStrings, each representing a metadata format that's available to the asset (e.g. ID3, iTunes metadata, etc.). Metadata formats are defined in AVMetadataFormat.h.
    @property (nonatomic, readonly) NSArray<NSString *> *availableMetadataFormats;
    

    commonMetadata属性中包含着当前视频常见格式类型的元数据
    metadata属性中包含当前视频所有格式类型的元数据
    availableMetadataFormats属性中包含当前视频所有可用元数据的格式类型
    元数据的格式类型在AVMetadataFormat中定义了很多种,常见的有title、creator、subject、publisher等

    // Metadata common keys
    AVF_EXPORT NSString *const AVMetadataCommonKeyTitle                                      NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMetadataCommonKeyCreator                                    NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMetadataCommonKeySubject                                    NS_AVAILABLE(10_7, 4_0);
    AVF_EXPORT NSString *const AVMetadataCommonKeyPublisher                                  NS_AVAILABLE(10_7, 4_0);
    

    以上只是部分format,要了解更多,可以在apple文档中查阅

    有了metadataFormat,apple提供了通过fromat获取特定格式类型元数据的方法:

    - (NSArray<AVMetadataItem *> *)metadataForFormat:(NSString *)format;
    

    章节元数据

    Asset中有一种特殊的元数据:章节。它是AVTimedMetadataGroup类型,这种类型表示一个只在特定时间段有效的元数据集合,也就是说章节中所包含的元数据只在当前章节的时间段有效。AVAsset中有3个章节相关的API:

    //  The locales available for chapters in the asset.
    @property (readonly) NSArray<NSLocale *> *availableChapterLocales NS_AVAILABLE(10_7, 4_3);
    
    //  Returns an array of chapters with a given title locale and containing specified keys.
    - (NSArray<AVTimedMetadataGroup *> *)chapterMetadataGroupsWithTitleLocale:(NSLocale *)locale containingItemsWithCommonKeys:(nullable NSArray<NSString *> *)commonKeys NS_AVAILABLE(10_7, 4_3);
    
    //  Returns an array of chapters whose locale best matches the the list of preferred languages.
    - (NSArray<AVTimedMetadataGroup *> *)chapterMetadataGroupsBestMatchingPreferredLanguages:(NSArray<NSString *> *)preferredLanguages NS_AVAILABLE(10_8, 6_0);
    

    availableChapterLocales属性表示当前Asset中可用的章节Locale(感觉翻译成地域或者区域在这里都很别扭,所以还是用英文)。数组类型,里面包含NSLocale对象
    -chapterMetadataGroupsWithTitleLocale:containingItemsWithCommonKeys:方法通过locale和元数据的commonkey筛选出特定的元数据,这些元数据只在当前章节的时间段有效。
    -chapterMetadataGroupsBestMatchingPreferredLanguages:方法通过指定一种语言,返回一个章节元数据数组。数组中越匹配指定语言的元数据,位置越靠前。

    媒体选择

    一个多媒体文件中相同的媒体特征的东西可能会有很多,比如一个视频中可能会有2种字幕。对于类似选择哪个字幕的问题,Asset提供了3个API:

    //  Provides an NSArray of NSStrings, each NSString indicating a media characteristic for which a media selection option is available.
    @property (nonatomic, readonly) NSArray<NSString *> *availableMediaCharacteristicsWithMediaSelectionOptions NS_AVAILABLE(10_8, 5_0);
    
    //  Provides an instance of AVMediaSelectionGroup that contains one or more options with the specified media characteristic.
    - (nullable AVMediaSelectionGroup *)mediaSelectionGroupForMediaCharacteristic:(NSString *)mediaCharacteristic NS_AVAILABLE(10_8, 5_0);
    
    //  Provides an instance of AVMediaSelection with default selections for each of the receiver's media selection groups.
    @property (nonatomic, readonly) AVMediaSelection *preferredMediaSelection NS_AVAILABLE(10_11, 9_0);
    

    availableMediaCharacteristicsWithMediaSelectionOptions属性表示当前asset中有效的媒体特征选项。数组类型,里面包含着代表相应媒体特征的string.
    -mediaSelectionGroupForMediaCharacteristic:方法通过传入一个媒体特征类型,返回可供选择的媒体选项集合。例如传入字幕的媒体特征类型,返回当前Asset的可供选择的字幕选项集合。
    preferredMediaSelection属性是AVMediaSelection类型,他的作用是主要是为各个媒体选项集合提供默认选项。
    这里的属性都不是直接的基本属性,可能不是那么容易理解。下面举个简单的例子,以便于理解。打印出当前Asset中默认的媒体选项。

    for (NSString *characteristic in asset.availableMediaCharacteristicsWithMediaSelectionOptions) {
            AVMediaSelectionGroup *group = [asset mediaSelectionGroupForMediaCharacteristic:characteristic];
            AVMediaSelectionOption *option = [asset.preferredMediaSelection selectedMediaOptionInMediaSelectionGroup:group];
            NSLog(@"对应媒体特征%@的默认媒体选项是%@",characteristic,option);
        }
    

    懒惰加载

    由于多媒体文件一般比较大,获取或计算出Asset中的属性非常耗时,apple对Asset的属性采用了懒惰加载模式。在创建AVAsset的时候,只生成一个实例,并不初始化属性。只有当第一次访问属性时,系统才会根据多媒体中的数据初始化这个属性。
    由于不用同时加载所有属性,耗时问题得到了一定缓解。但是属性加载在计算量比较大的时候仍旧可能会阻塞线程。为了解决这个问题,AVFoundation提供了AVAsynchronousKeyValueLoading协议,可以异步加载属性:

    @protocol AVAsynchronousKeyValueLoading
    @required   
    //  Directs the target to load the values of any of the specified keys that are not already loaded.
    - (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler;
    
    //  Reports whether the value for a key is immediately available without blocking.
    - (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * __nullable * __nullable)outError;
    

    -loadValuesAsynchronouslyForKeys:completionHandler:方法用来异步加载属性,通过keys传入要加载的key数组,在handler中做加载完成的操作。
    -statusOfValueForKey:error:方法可以获得属性的加载状态,如果是AVKeyValueStatusLoaded状态,表示已经加载完成。
    除此之外,Asset也提供了取消加载的API:

    //  Cancels the loading of all values for all observers.
    - (void)cancelLoading;
    

    当需要的时候我们可以通过这个API终止加载属性。另外在AVAsset释放的时候会暗中取消所有的加载请求。

    End

    除了已经介绍的API之外,还有一些BOOL值类型的标识属性,这些属性都比较简单,根据名字就能明白其中的意思。这里就不多介绍了。
    最近刚开始研究AVFoundation,可能是玩这个的人不多,网上这方面的资料比较少,所以将最近研究的结果写成博客,以供大家参考,如果有什么不对的地方,希望能多多指教
    如果你也正在学习AVFoundation,可以关注我的微博,大家互相学习,共同进步

    Reference

    MP4文件格式详解——元数据moov
    AV Foundation开发秘籍
    AVAsset Class Reference

    相关文章

      网友评论

      • 羽之_HB:大佬,我音视频合成低于5分钟的没啥问题,但是高于5分钟的几乎都是失败,能指导一下么
      • MC3571:你好,文章中的截图是什么书?或者有与音频相关的比较好的书推荐吗?多谢
        小笨狼:AVFoundation的一本书,你去京东搜索AVFoundation就能找到
      • 豆宝的老公:你好,如何从视频中提取出音频文件呢?
      • 豆宝的老公:你好,如何从视频中提取出音频呢?有什么思路吗?谢谢
      • 海的原滋味:牛逼,能不能给个demo?
      • lp_马建成:- (CGSize)naturalSize {
        NSArray<AVAssetTrack *> *tracks = [_player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo];
        AVAsset * asset = _player.currentItem.asset;
        CGAffineTransform form = asset.preferredTransform;
        if (tracks.count == 0) {
        return CGSizeZero;
        } else {
        AVAssetTrack *track = tracks.firstObject;

        NSArray<NSString *> *availableMetadataFormats = track.availableMetadataFormats;
        NSArray<AVMetadataItem *> *commonMetadata = track.commonMetadata;
        NSArray<AVMetadataItem *> *metadata = track.metadata;
        return tracks.firstObject.naturalSize;
        }
        }

        想取视频的尺寸做自适应大小,结果本地视频上传导致,videosize反了;
        想取Metadata中的ratation,结果Metadata数组返回均为空,作者有测试过么 ?
      • e30f664859d3:请教下2个问题 1比如我拖动进度条 到之前已经缓存的部分 ,怎么用上缓存的数据 avplayer 里面 和avplayeritem 里面貌似没有这样的代理去加载自己的缓存的部分视频
        2 看视频的时候 中间拖动了一进度条 这样缓存的时候中间就有一部分空白,这种情况 怎么重复利用这种缓存数据?
      • 伯wen:刚开始深入学习AVFoundation, 这边文章给了我很大帮助, 毕竟直接生啃书还是有点难度的 :+1:
      • ismilesky:楼主,将字幕放进视频里面需要怎样处理啊??
        零纪年:@ismilesky 附上链接: https://developer.apple.com/library/ios/samplecode/AVSimpleEditoriOS/Introduction/Intro.html
        零纪年:@ismilesky apple官方有个处理视频音频的demo
      • ismilesky:楼主,用AVFoundation怎么样将字幕合成进视频啊?
      • f02af05509d3:楼主,Atom Inspector这个在 https://developer.apple.com/download/ 搜索不到,有什么办法么
        lp_马建成:用MediaInfo也可以
      • Tmec:文章里介绍的大部分都是获取moov box里的讯息,请问怎么获取mdta box里的数据?
      • 桐生一猫:方便加个Q交流一下吗,942797982 :pray:
      • puppySweet:笨狼在么 。Avwriteasset 录制视频时 变形啊怎么办 我用960x540拍(系统没有1:1的)。写入的size是480x480 比特率是256x1024……压出来的视频变形了 求解答 如果你写过 求 demo 火速需要
      • HelloGeekBand:Atom Inspector链接好像失效了,我搜了搜,也没找到,请问哪里能下载呢
        HelloGeekBand:@小笨狼 对了最近我有一个学习上的疑问,关于AVAudioPlayer和AVPlayer,在AVAudioPlayer对象的duration是NSTimeInterval的,这个属性我知道其实是double,双精度,精确到秒 但是AVPlayer对象.duration是CMTime,我能理解CMTime作为表示视频帧媒体与时间特殊的关系,所以有CMTime。

        但是!!有一点我没想通啊..我翻阅的书籍和资料,都有说明CMTime在精度上高于double,这我就不理解了,一个视频难道有必要精确到纳秒、毫秒吗??? 在时间精度上,我的理解是CMTime和NSTimeInterval应该是一样的。 关于视频的累计误差你能给我解释一下嘛..谢了
        HelloGeekBand:@小笨狼 谢谢
        小笨狼:@HelloGeekBand https://developer.apple.com/downloads/ 里面搜索Atom Inspector
      • 8c57f78d7ad6:谢谢作者分享,满满的干货,这些天我也在研究AVFoundation的内容,多交流
        小笨狼:@8c57f78d7ad6 多多交流
      • 子达如何:请问作者有没有做过视频直播方面的研究?
        小笨狼:@子达如何 这个没有,没做过这方面的项目
      • 曾樑:高产:fist:

      本文标题:AVFoundation(二):核心AVAsset

      本文链接:https://www.haomeiwen.com/subject/ckoehttx.html