AVFoundation 是 Objective-C 中建立及编辑视听媒体文件的几个框架之一,其提供了检查、建立、编辑或从新编码媒体文件的接口,也使得从设备获取的视频实时数据可操纵。可是,一般状况,简单的播放或者录像,直接使用 AVKit 框架或者 UIImagePickerController 类便可。另外,值得注意的是,在 AVFoundation 框架中使用的基本数据结构,如时间相关的或描述媒体数据的数据结构都声明在 CoreMedia 框架中。css
AVFoundation 框架包含视频相关的接口以及音频相关的接口,与音频相关的类有 AVAudioPlayer、AVAudioRecorder、AVAudioSession。java
AVFoundation 框架中最基本的类是 AVAsset ,它是一个或者多个媒体数据的集合,描述的是整个集合的属性,如标题、时长、大小等,而且没有特定的数据格式。集合的每个媒体数据都是统一的数据类型,称之为 track。简单的状况是一种数据是音频数据,一种是视频数据,而较复杂的状况是一种数据交织着音频和视频数据,而且 AVAsset 是可能有元数据的。算法
另外,须要明白的是在 AVFoundation 中,初始化了 asset 及 track 后,并不意味着资源已经可用,由于若资源自己并不携带自身信息时,那么系统须要本身计算相关信息,这个过程会阻塞线程,因此应该使用异步方式进行获取资源信息后的操做。数组
AVFoundation 提供了丰富的方法来管理视听资源的播放,为了支持这些方法,它将描述 asset 的状态与 asset 自己分离,这就使得在同一个时刻,以不一样的方式播放同一个 asset 中的不一样的媒体数据变得可能。对于 asset 的状态是由 player 管理的,而 asset 中的 track 的状态是由 player tracker 管理的。使用这两个状态管理对象,能够实现诸如设置 asset 中视频部分的大小、设置音频的混合参数及与视频的合成或者将 asset 中的某些媒体数据置为不可用。缓存
另外,还能够经过 player 将输出定位到 Core Animation 层中,或经过播放队列设置 player 集合的播放顺序。ruby
AVFoundation 提供了多种方法来建立 asset ,能够简单的重编码已经存在的 asset ,这个过程可使用 export session 或者使用 asset reader 和 asset writer 。markdown
若要生成视频的缩略图,可使用 asset 初始化一个 AVAssetImageGenerator 实例对象,它会使用默承认用的视频 tracks 来生成图片。cookie
AVFoundation 中可使用 compositions 将多个媒体数据(video/audio tracks)合成为一个 asset ,这个过程当中,能够添加或移除 tracks ,调整它们的顺序,或者设置音频的音量和变化坡度,视频容量等属性。这些媒体数据的集合保存在内存中,直到使用 export session 将它导出到本地文件中。另外,还可使用 asset writer 建立 asset 。网络
使用 capture session 协调从设备(如相机、麦克风)输入的数据和输出目标(如视频文件)。能够为 session 设置多个输入和输出,即便它正在工做,还能够经过它中止数据的流动。另外,还可使用 preview layer 将相机记录的影像实时展现给用户。session
在 AVFoundation 中的回调处理并不保证回调任务在某个特定的线程或队列中执行,其遵循两个原则,UI 相关的操做在主线程中执行,其余回调须要为其指定调用的队列。
建立 AVAsset 或其子类 AVURLAsset 时,须要提供资源的位置,方法以下:
NSURL *url = <#视听资源的 URL ,能够是本地文件地址,也能够是网页媒体连接#>; AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
上述方法的第二个参数是建立对象时的选择项,其中可能包含的选择项以下:
建立并初始化一个 AVAsset 实例对象后,并不意味着该对象的全部属性均可以获取使用了,由于其中的一些属性须要额外的计算才可以获得,那么当获取这些属性时,可能会阻塞当前线程,因此须要异步获取这些属性。
AVAsset 与 AVAssetTrack 都遵循 AVAsynchronousKeyValueLoading 协议,这个协议中有如下两个方法:
//获取指定属性的状态 - (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * _Nullable * _Nullable)outError; //异步加载指定的属性集合 - (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler;
一般,咱们使用上述第二个方法异步加载想要的属性,然后在加载完成的回调 block 中使用第一个方法判断属性是否加载成功,而后访问想要的属性,执行本身的操做,以下代码:
NSURL *url = <#资源路径#>; AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil]; NSArray *keys = @[@"duration",@"tracks"]; [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() { NSError *error = nil; AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"tracks" error:&error]; //根据相应的属性状态进行对应的处理 switch (tracksStatus) { case AVKeyValueStatusUnknown: //TODO break; case AVKeyValueStatusLoading: //TODO break; case AVKeyValueStatusLoaded: //TODO break; case AVKeyValueStatusFailed: //TODO break; case AVKeyValueStatusCancelled: //TODO break; } }];
使用 AVAssetImageGenerator 生成视频资源的缩略图,使用 AVAsset 对象建立 AVAssetImageGenerator 对象,可使用类方法或实例方法,以下:
+ (instancetype)assetImageGeneratorWithAsset:(AVAsset *)asset; - (instancetype)initWithAsset:(AVAsset *)asset NS_DESIGNATED_INITIALIZER;
固然,在此以前,最好调用 AVAsset 中的方法 - (NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;
来判断 asset 中是否有可视媒体数据。若是有,那么再建立 AVAssetImageGenerator 对象,然后再调用下面的方法,来获取一张或多张图片。
//获取一张图片,requestedTime 指定要获取视频中哪一个时刻的图片,actualTime 返回图片实际是视频的哪一个时刻,outError 返回错误信息 - (nullable CGImageRef)copyCGImageAtTime:(CMTime)requestedTime actualTime:(nullable CMTime *)actualTime error:(NSError * _Nullable * _Nullable)outError CF_RETURNS_RETAINED; //获取多张图片,每一次图片生成后,都会调用一次 handler - (void)generateCGImagesAsynchronouslyForTimes:(NSArray<NSValue *> *)requestedTimes completionHandler:(AVAssetImageGeneratorCompletionHandler)handler; //上述 handler 的类型以下,回调中的参数有图片的请求时刻和实际时刻,图片,状态(成功、失败、取消),错误信息 typedef void (^AVAssetImageGeneratorCompletionHandler)(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error);
使用 AVAssetExportSession 类对视频进行裁剪及转码,即将一个 AVAsset 类实例修改后保存为另外一个 AVAsset 类实例,最后保存到文件中。
在修改资源以前,为避免不兼容带来的错误,能够先调用下面的方法,检查预设置是否合理。
//获取与 asset 兼容的预设置 + (NSArray<NSString *> *)exportPresetsCompatibleWithAsset:(AVAsset *)asset; //判断提供的预设置和输出的文件类型是否与 asset 相兼容 + (void)determineCompatibilityOfExportPreset:(NSString *)presetName withAsset:(AVAsset *)asset outputFileType:(nullable NSString *)outputFileType completionHandler:(void (^)(BOOL compatible))handler NS_AVAILABLE(10_9, 6_0);
除了设置文件类型外,还能够设置文件的大小、时长、范围等属性,一切准备就绪后,调用方法:
- (void)exportAsynchronouslyWithCompletionHandler:(void (^)(void))handler;
进行文件的导出,导出结束后,会调用 handler 回调,在回调中应该检查 AVAssetExportSession 的 status 属性查看导出是否成功,若指定的文件保存地址在沙盒外,或在导出的过程当中有电话打入都会致使文件保存失败,以下例程:
- (void)exportVideo:(NSURL *)url { AVAsset *anAsset = [AVAsset assetWithURL:url]; [AVAssetExportSession determineCompatibilityOfExportPreset:AVAssetExportPresetHighestQuality withAsset:anAsset outputFileType:AVFileTypeMPEG4 completionHandler:^(BOOL compatible) { if (compatible){ AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:anAsset presetName:AVAssetExportPresetHighestQuality]; exportSession.outputFileType = AVFileTypeMPEG4; CMTime start = CMTimeMakeWithSeconds(1.0, 600); CMTime duration = CMTimeMakeWithSeconds(3.0, 600); CMTimeRange range = CMTimeRangeMake(start, duration); exportSession.timeRange = range; [exportSession exportAsynchronouslyWithCompletionHandler:^{ switch ([exportSession status]) { case AVAssetExportSessionStatusCompleted: NSLog(@"completed"); break; case AVAssetExportSessionStatusFailed: NSLog(@"failed"); break; case AVAssetExportSessionStatusCancelled: NSLog(@"canceled"); break; default: break; } }]; } }]; }
使用一个 AVPlayer 类实例能够管理一个 asset 资源,可是它的属性 currentItem 才是 asset 的实际管理者。currentItem 是 AVPlayerItem 类的实例,而它的属性 tracks 包含着的 AVPlayerItemTracker 实例对应着 asset 中的各个 track 。
那么,为了控制 asset 的播放,可使用 AVPlayer 类,在播放的过程当中,可使用 AVPlayerItem 实例管理整个 asset 的状态,使用 AVPlayerItemTracker 对象管理 asset 中每一个 track 的状态。另外,还可使用 AVPlayerLayer 类来显示播放的内容。
因此,在建立 AVPlayer 实例对象时,除了能够直接传递资源文件的路径进行建立外,还能够传递 AVPlayerItem 的实例对象,以下方法:
+ (instancetype)playerWithURL:(NSURL *)URL; + (instancetype)playerWithPlayerItem:(nullable AVPlayerItem *)item; - (instancetype)initWithURL:(NSURL *)URL; - (instancetype)initWithPlayerItem:(nullable AVPlayerItem *)item;
建立后,并非能够直接使用,还要对它的状态进行检查,只有 status 的值为 AVPlayerStatusReadyToPlay 时,才能进行播放,因此这里须要使用 KVO 模式对该状态进行监控,以决定什么时候能够进行播放。
若要管理多个资源的播放,则应使用 AVPlayer 的子类 AVQueuePlayer ,这个子类拥有的多个 AVPlayerItem 同各个资源相对应。
对于播放不一样类型的资源,须要进行的准备工做有所不一样,这主要取决于资源的来源。资源数据可能来自本地设备上文件的读取,也可能来自网络上数据流。
对于本地文件,可使用文件地址建立 AVAsset 对象,然后使用该对象建立 AVPlayerItem 对象,最后将这个 item 对象与 AVPlayer 对象相关联。以后,即是等待 status 的状态变为 AVPlayerStatusReadyToPlay ,即可以进行播放了。
对于网络数据的播放,不能使用地址建立 AVAsset 对象了,而是直接建立 AVPlayerItem 对象,将其同 AVPlayer 对象相关联,当 status 状态变为 AVPlayerStatusReadyToPlay 后,AVAsset 和 AVAssetTrack 对象将由 item 对象建立。
经过调用 player 的 play 、pause 、setRate: 方法,能够控制 item 的播放,这些方法都会改变 player 的属性 rate 的值,该值为 1 表示 item 按正常速率播放,为 0 表示 item 暂停播放,0~1 表示低速播放,大于 1 表示高速播放,小于 0 表示从后向前播放。
item 的属性 timeControlStatus 的值表示当前 item 的状态,有下面 3 个值:
item 的属性 actionAtItemEnd 的值表示当前 item 播放结束后的动做,有下面 3 个值:
item 的 currentTime 属性值表示当前 item 的播放时间,能够调用下面的方法指定 item 从何处进行播放。
//第二个方法可以进行更准确的跳转,可是须要进行额外的计算
- (void)seekToDate:(NSDate *)date; - (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter; (tolerance: 公差,先后公差) //这两个方法传入了一个回调,当一个时间跳转请求被新的请求或其余操做打断时,回调也会被执行可是此时 finished 参数值为 NO - (void)seekToTime:(CMTime)time completionHandler:(void (^)(BOOL finished))completionHandler NS_AVAILABLE(10_7, 5_0); - (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL finished))completionHandler NS_AVAILABLE(10_7, 5_0);
使用 AVQueuePlayer 管理多个 item 的播放,仍然能够经过调用 play 开始依次播放 item,调用 advanceToNextItem 方法播放下一个 item ,还能够经过下面的方法添加或移除 item 。
- (BOOL)canInsertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem; - (void)insertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem; - (void)removeItem:(AVPlayerItem *)item; - (void)removeAllItems;
可使用下面的方法监听播放时间的变化,须要强引用这两个方法返回的监听者。
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block; - (id)addBoundaryTimeObserverForTimes:(NSArray<NSValue *> *)times queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(void))block;
用上面的方法每注册一个监听者,就须要对应的使用下面的方法进行注销,而且在注销以前,要确保没有 block 被执行。
- (void)removeTimeObserver:(id)observer;
当 item 播放结束后,再次调用 player 的方法 play 不会使 item 从新播放,要实现重播,能够注册一个 AVPlayerItemDidPlayToEndTimeNotification 通知,当接收到这个通知时,能够调 seekToTime: 方法,传入 kCMTimeZero 参数,将 player 的播放时间重置。
AVFoundation 框架中提供了丰富的接口用于视听资源的编辑,其中的关键是 composition ,它将不一样的 asset 相结合并造成一个新的 asset 。使用 AVMutableComposition 类能够增删 asset 来将指定的 asset 集合到一块儿。除此以外,若想将集合到一块儿的视听资源以自定义的方式进行播放,须要使用 AVMutableAudioMix 和 AVMutableVideoComposition类对其中的资源进行协调管理。最终要使用 AVAssetExportSession 类将编辑的内容保存到文件中。
同 AVAsset 拥有多个 AVAssetTrack 同样,做为子类的 AVComposition 也拥有多个 AVCompositionTrack ,而 AVCompositionTrack 是 AVAssetTrack 的子类。因此,AVComposition 实例对象是多个 track 的集合,真正描述媒体属性的是 AVCompositionTrack 实例对象。而 AVCompositionTrack 又是媒体数据片断的集合,这些数据片断由 AVCompositionTrackSegment 类进行描述。
该类的相关属性和方法以下:
//获取 composition 中包含的 tracks @property (nonatomic, readonly) NSArray<AVCompositionTrack *> *tracks; //获取 composition 中可视媒体资源播放时在屏幕上显示的大小 @property (nonatomic, readonly) CGSize naturalSize; //获取 composition 生成 asset 时的指定配置 @property (nonatomic, readonly, copy) NSDictionary<NSString *, id> *URLAssetInitializationOptions NS_AVAILABLE(10_11, 9_0); //根据不一样的参数,获取 composition 中的 track - (nullable AVCompositionTrack *)trackWithTrackID:(CMPersistentTrackID)trackID; - (NSArray<AVCompositionTrack *> *)tracksWithMediaType:(NSString *)mediaType; - (NSArray<AVCompositionTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;
值得注意的是 AVComposition 类中并无提供初始化方法,通常咱们使用它的子类 AVMutableComposition ,进行各类操做后,再生成 AVComposition 实例以供查询,以下例程:
AVMutableComposition *mutableComposition = [AVMutableComposition composition]; //进行添加资源等操做 <#····#> //使用可变的 composition 生成一个不可变的 composition 以供使用 AVComposition *composition = [myMutableComposition copy]; AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:composition];
AVMutableComposition 是 AVComposition 的子类,其包含的 tracks 则是 AVCompositionTrack 的子类 AVMutableCompositionTrack 。
AVMutableComposition 中提供了两个类方法用来获取一个空的 AVMutableComposition 实例对象。
+ (instancetype)composition;
+ (instancetype)compositionWithURLAssetInitializationOptions:(nullable NSDictionary<NSString *, id> *)URLAssetInitializationOptions NS_AVAILABLE(10_11, 9_0);
对整个 composition 中的 tracks 的修改方法以下:
//将指定时间段的 asset 中的全部的 tracks 添加到 composition 中 startTime 处 //该方法可能会在 composition 中添加新的 track 以便 asset 中 timeRange 范围中的全部 tracks 都添加到 composition 中 - (BOOL)insertTimeRange:(CMTimeRange)timeRange ofAsset:(AVAsset *)asset atTime:(CMTime)startTime error:(NSError * _Nullable * _Nullable)outError; //向 composition 中的全部 tracks 添加空的时间范围 - (void)insertEmptyTimeRange:(CMTimeRange)timeRange; //从 composition 的全部 tracks 中删除一段时间,该操做不会删除 track ,而是会删除与该时间段相交的 track segment - (void)removeTimeRange:(CMTimeRange)timeRange; //改变 composition 中的全部的 tracks 的指定时间范围的时长,该操做会改变 asset 的播放速度 - (void)scaleTimeRange:(CMTimeRange)timeRange toDuration:(CMTime)duration;
从 composition 中获取 track 或向其中添加/移除 track 方法以下:
//向 composition 中添加一个空的 track ,而且指定媒体资源类型及 trackID 属性值 //若提供的参数 preferredTrackID 无效或为 kCMPersistentTrackID_Invalid ,那么惟一的 trackID 会自动生成 - (AVMutableCompositionTrack *)addMutableTrackWithMediaType:(NSString *)mediaType preferredTrackID:(CMPersistentTrackID)preferredTrackID; //从 composition 中删除一个指定的 track - (void)removeTrack:(AVCompositionTrack *)track; //获取一个与 asset track 相兼容的 composition track //为了更好的性能,composition track 的数量应保持最小,这个数量与必需并行播放的媒体数据段数量以及媒体数据的类型相关 //对于可以线性执行且类型相同的媒体数据应使用同一个 composition track ,即便这些数据来自不一样的 asset - (nullable AVMutableCompositionTrack *)mutableTrackCompatibleWithTrack:(AVAssetTrack *)track;
AVMutableComposition 中也提供了过滤 AVMutableCompositionTrack 的接口
- (nullable AVMutableCompositionTrack *)trackWithTrackID:(CMPersistentTrackID)trackID; - (NSArray<AVMutableCompositionTrack *> *)tracksWithMediaType:(NSString *)mediaType; - (NSArray<AVMutableCompositionTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;
AVCompositionTrack 类同其父类 AVAssetTrack 同样是媒体资源的管理者,它实际是媒体资源数据的集合,它的属性 segments 是 AVCompositionTrackSegment 类的实例对象集合,每一个对象描述一个媒体数据片断。类 AVCompositionTrack 并不经常使用,一般使用的是它的子类 AVMutableCompositionTrack 。
AVMutableCompositionTrack 中提供的属性以下:
//没有外部数值指定时,媒体1秒钟时间的粒度 @property (nonatomic) CMTimeScale naturalTimeScale; //当前 track 相关联的语言编码 @property (nonatomic, copy, nullable) NSString *languageCode; //当前 track 相关联的额外语言编码 @property (nonatomic, copy, nullable) NSString *extendedLanguageTag; //对于可显示的媒体数据应优先选择的仿射变换设置,默认值为 CGAffineTransformIdentity @property (nonatomic) CGAffineTransform preferredTransform; //应优先选择的音量,默认值为 1 @property (nonatomic) float preferredVolume; //当前track 所包含的全部的媒体数据片断,对于这些片断,它们构成了 track 的完整时间线, //因此他们的时间线不能够重叠,而且第一个数据片断的时间从 kCMTimeZero 开始,依次日后的时间必须连续不间断、不重叠 @property (nonatomic, copy, null_resettable) NSArray<AVCompositionTrackSegment *> *segments;
当咱们获取了一个 AVMutableCompositionTrack 实例对象后,即可以经过如下方法对其进行添加或移除数据片断
//将已存在的资源文件指定时间范围的媒体数据插入到当前 composition 的指定时间处
//若是 startTime 为 kCMTimeInvalid 值,那么数据被添加到 composition 的最后 - (BOOL)insertTimeRange:(CMTimeRange)timeRange ofTrack:(AVAssetTrack *)track atTime:(CMTime)startTime error:(NSError * _Nullable * _Nullable)outError; //这个方法与上述方法相似,只是能够批量操做,可是注意提供的时间范围不能重叠 - (BOOL)insertTimeRanges:(NSArray<NSValue *> *)timeRanges ofTracks:(NSArray<AVAssetTrack *> *)tracks atTime:(CMTime)startTime error:(NSError * _Nullable * _Nullable)outError NS_AVAILABLE(10_8, 5_0); //插入一个没有媒体数据的时间段,当这个范围以前的媒体资源播放结束后,不会马上播放以后的媒体数据,而是会静默一段时间 - (void)insertEmptyTimeRange:(CMTimeRange)timeRange; //移除一段时间范围的媒体数据,该方法不会致使该 track 从 composition 中移除,只是移除与时间范围相交的数据片断 - (void)removeTimeRange:(CMTimeRange)timeRange; //改变某个时间范围内的时间的时长,实质是改变了媒体数据的播放速率 //其速率是原时长与现时长的比值,总之,媒体数据是要按时长播放的 - (void)scaleTimeRange:(CMTimeRange)timeRange toDuration:(CMTime)duration; //判断数据片断的时间线是否重叠 - (BOOL)validateTrackSegments:(NSArray<AVCompositionTrackSegment *> *)trackSegments error:(NSError * _Nullable * _Nullable)outError;
媒体资源 AVAsset 中的集合 AVAssetTrack 管理着单条时间线上的媒体数据片断,而每一个数据片断则由 AVAssetTrackSegment 类进行描述。
AVAssetTrackSegment 有两个属性
timeMapping 描述的是数据片断在整个媒体文件中所处的时间范围
timeMapping 是一个结构体,拥有两个成员,对于编辑中的媒体数据片断,它们分别表示数据在源文件中的位置和目标文件中的位置
typedef struct { CMTimeRange source; CMTimeRange target; } CMTimeMapping;
在编辑媒体文件时,在描述数据时,使用的是 AVAssetTrackSegment 的子类 AVCompositionTrackSegment ,她的主要属性和方法以下:
//判断数据片断是否为空,若为空 timeMapping.target 可为有效值,其余为未定义值 @property (nonatomic, readonly, getter=isEmpty) BOOL empty; //片断数据所处的文件的地址 @property (nonatomic, readonly, nullable) NSURL *sourceURL; //片断数据所处文件的描述 asset track 的 ID @property (nonatomic, readonly) CMPersistentTrackID sourceTrackID; //建立对象,提供了数据片断所在的文件、文件的描述 asset track 的 ID 、源文件中的数据时间范围、目标文件中所处的时间范围 //sourceTimeRange 与 targetTimeRange 的时间长度若是不一致,那么播放的速率会改变 + (instancetype)compositionTrackSegmentWithURL:(NSURL *)URL trackID:(CMPersistentTrackID)trackID sourceTimeRange:(CMTimeRange)sourceTimeRange targetTimeRange:(CMTimeRange)targetTimeRange; - (instancetype)initWithURL:(NSURL *)URL trackID:(CMPersistentTrackID)trackID sourceTimeRange:(CMTimeRange)sourceTimeRange targetTimeRange:(CMTimeRange)targetTimeRange NS_DESIGNATED_INITIALIZER; //建立仅有时间范围而无实际媒体数据的实例 + (instancetype)compositionTrackSegmentWithTimeRange:(CMTimeRange)timeRange; - (instancetype)initWithTimeRange:(CMTimeRange)timeRange NS_DESIGNATED_INITIALIZER;
要在媒体资源播放的过程当中实现音频的自定义播放,须要用 AVMutableAudioMix 对不一样的音频进行编辑。这个类的实例对象的属性 inputParameters 是音量描述对象的集合,每一个对象都是对一个 audio track 的音量变化的描述,以下示例:
AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
AVMutableAudioMixInputParameters *mixParameters1 = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:compositionAudioTrack1];
[mixParameters1 setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration/2)]; [mixParameters1 setVolumeRampFromStartVolume:0.f toEndVolume:1.f timeRange:CMTimeRangeMake(mutableComposition.duration/2, mutableComposition.duration)]; AVMutableAudioMixInputParameters *mixParameters2 = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:compositionAudioTrack2]; [mixParameters2 setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)]; mutableAudioMix.inputParameters = @[mixParameters1, mixParameters2];
该类中有一个属性 inputParameters ,它是 AVAudioMixInputParameters 实例对象的集合,每一个实例都是对音频播放方式的描述。可见,AVAudioMix 并不直接改变音频播放的方式,其只是存储了音频播放的方式。
AVMutableAudioMix 是 AVAudioMix 的子类,它的方法 audioMix 返回一个 inputParameters 属性为空的实例。
这个类是音量变化的描述类,它同一个音频的 track 相关联,并设置音量随时间变化的算法,其获取音量变化的方法以下:
//获取的音量变化范围 timeRange 应包含指定的时刻 time 不然最终返回 NO //startVolume 获取音量开始变化时的初始音量 //endVolume 获取音量变化结束时的音量 //timeRang 是实际音量变化的范围,它应该包含指定的 time - (BOOL)getVolumeRampForTime:(CMTime)time startVolume:(nullable float *)startVolume endVolume:(nullable float *)endVolume timeRange:(nullable CMTimeRange *)timeRange;
AVMutableAudioMixInputParameters 是 AVAudioMixInputParameters 的子类,它提供了直接设置某个时刻或时间段的音量的方法。
//根据提供的 track 建立一个实例,此时的音量描述数据为空 + (instancetype)audioMixInputParametersWithTrack:(nullable AVAssetTrack *)track; //建立一个实例,此时的音量变化描述是空的,且 trackID 为 kCMPersistentTrackID_Invalid + (instancetype)audioMixInputParameters; //设置某个时间范围内的初始音量及结束音量 - (void)setVolumeRampFromStartVolume:(float)startVolume toEndVolume:(float)endVolume timeRange:(CMTimeRange)timeRange; //设置某个时刻的音量 - (void)setVolume:(float)volume atTime:(CMTime)time;
同音频的自定义播放同样,要实现视频的自定义播放,仅仅将视频资源集合到一块儿是不够的,须要使用 AVMutableVideoComposition 类来定义不一样的视频资源在不一样的时间范围内的播放方式。
AVVideoComposition 是 AVMutableVideoComposition 的父类,它的主要属性和方法以下:
//该类的构造类,提供自定义的构造类时,提供的类要遵照 AVVideoCompositing 协议 @property (nonatomic, readonly, nullable) Class<AVVideoCompositing> customVideoCompositorClass NS_AVAILABLE(10_9, 7_0); //视频每一帧的刷新时间 @property (nonatomic, readonly) CMTime frameDuration; //视频显示时的大小范围 @property (nonatomic, readonly) CGSize renderSize; //视频显示范围大小的缩放比例(仅仅对 iOS 有效) @property (nonatomic, readonly) float renderScale; //描述视频集合中具体视频播放方式信息的集合,其是遵循 AVVideoCompositionInstruction 协议的类实例对象 //这些视频播放信息构成一个完整的时间线,不能重叠,不能间断,而且在数组中的顺序即为相应视频的播放顺序 @property (nonatomic, readonly, copy) NSArray<id <AVVideoCompositionInstruction>> *instructions; //用于组合视频帧与动态图层的 Core Animation 的工具对象,能够为 nil @property (nonatomic, readonly, retain, nullable) AVVideoCompositionCoreAnimationTool *animationTool; //直接使用一个 asset 建立一个实例,建立的实例的各个属性会根据 asset 中的全部的 video tracks 的属性进行计算并适配,因此在调用该方法以前,确保 asset 中的属性已经加载 //返回的实例对象的属性 instructions 中的对象会对应每一个 asset 中的 track 中属性要求 //返回的实例对象的属性 frameDuration 的值是 asset 中 全部 track 的 nominalFrameRate 属性值最大的,若是这些值都为 0 ,默认为 30fps //返回的实例对象的属性 renderSize 的值是 asset 的 naturalSize 属性值,若是 asset 是 AVComposition 类的实例。不然,renderSize 的值将包含每一个 track 的 naturalSize 属性值 + (AVVideoComposition *)videoCompositionWithPropertiesOfAsset:(AVAsset *)asset NS_AVAILABLE(10_9, 6_0); //这三个属性设置了渲染帧时的颜色空间、矩阵、颜色转换函数,可能的值都在 AVVideoSetting.h 文件中定义 @property (nonatomic, readonly, nullable) NSString *colorPrimaries NS_AVAILABLE(10_12, 10_0); @property (nonatomic, readonly, nullable) NSString *colorYCbCrMatrix NS_AVAILABLE(10_12, 10_0); @property (nonatomic, readonly, nullable) NSString *colorTransferFunction NS_AVAILABLE(10_12, 10_0); //该方法返回一个实例,它指定的 block 会对 asset 中每个有效的 track 的每一帧进行渲染获得 CIImage 实例对象 //在 block 中进行每一帧的渲染,成功后应调用 request 的方法 finishWithImage:context: 并将获得的 CIImage 对象做为参数 //如果渲染失败,则应调用 finishWithError: 方法并传递错误信息 + (AVVideoComposition *)videoCompositionWithAsset:(AVAsset *)asset applyingCIFiltersWithHandler:(void (^)(AVAsynchronousCIImageFilteringRequest *request))applier NS_AVAILABLE(10_11, 9_0);
AVMutableVideoComposition 是 AVVideoComposition 的可变子类,它继承父类的属性能够改变,而且新增了下面的建立方法。
//这个方法建立的实例对象的属性的值都是 nil 或 0,可是它的属性都是能够进行修改的 + (AVMutableVideoComposition *)videoComposition;
在上述的两个类中,真正包含有视频播放方式信息的是 instructions 属性,这个集合中的对象都遵循 AVVideoCompositionInstruction 协议,若不使用自定义的类,那么可使用 AVFoundation 框架中的 AVVideoCompositionInstruction 类。
该类的相关属性以下:
//表示该 instruction 生效的时间范围 @property (nonatomic, readonly) CMTimeRange timeRange; //指定当前时间段的 composition 的背景色 //若是没有指定,那么使用默认的黑色 //若是渲染的像素没有透明度通道,那么这个颜色也会忽略透明度 @property (nonatomic, readonly, retain, nullable) __attribute__((NSObject)) CGColorRef backgroundColor; //AVVideoCompositionLayerInstruction 类实例对象的集合,描述各个视频资源帧的层级及组合关系 //按这个数组的顺序,第一个显示在第一层,第二个在第一层下面显示,以此类推 @property (nonatomic, readonly, copy) NSArray<AVVideoCompositionLayerInstruction *> *layerInstructions; //代表该时间段的视频帧是否须要后期处理 //若为 NO,后期图层的处理将跳过该时间段,这样可以提升效率 //为 YES 则按默认操做处理(参考 AVVideoCompositionCoreAnimationTool 类) @property (nonatomic, readonly) BOOL enablePostProcessing; //当前 instruction 中须要进行帧组合的全部的 track ID 的集合,由属性 layerInstructions 计算获得 @property (nonatomic, readonly) NSArray<NSValue *> *requiredSourceTrackIDs NS_AVAILABLE(10_9, 7_0); //若是当前的 instruction 在该时间段内的视频帧组合后,实质获得的是某个源视频的帧,那么就返回这个视频资源的 ID @property (nonatomic, readonly) CMPersistentTrackID passthroughTrackID NS_AVAILABLE(10_9, 7_0);
AVMutableVideoCompositionInstruction 是 AVVideoCompositionInstruction 的子类,其继承的父类的属性可进行修改,而且提供了建立属性值为 nil 或无效的实例的方法。
+ (instancetype)videoCompositionInstruction;
AVVideoCompositionLayerInstruction 是对给定的视频资源的不一样播放方式进行描述的类,经过下面的方法,能够获取仿射变化、透明度变化、裁剪区域变化的梯度信息。
//获取包含指定时间的仿射变化梯度信息 //startTransform、endTransform 用来接收变化过程的起始值与结束值 //timeRange 用来接收变化的持续时间范围 //返回值表示指定的时间 time 是否在变化时间 timeRange 内 - (BOOL)getTransformRampForTime:(CMTime)time startTransform:(nullable CGAffineTransform *)startTransform endTransform:(nullable CGAffineTransform *)endTransform timeRange:(nullable CMTimeRange *)timeRange; //获取包含指定时间的透明度变化梯度信息 //startOpacity、endOpacity 用来接收透明度变化过程的起始值与结束值 //timeRange 用来接收变化的持续时间范围 //返回值表示指定的时间 time 是否在变化时间 timeRange 内 - (BOOL)getOpacityRampForTime:(CMTime)time startOpacity:(nullable float *)startOpacity endOpacity:(nullable float *)endOpacity timeRange:(nullable CMTimeRange *)timeRange; //获取包含指定时间的裁剪区域的变化梯度信息 //startCropRectangle、endCropRectangle 用来接收变化过程的起始值与结束值 //timeRange 用来接收变化的持续时间范围 //返回值表示指定的时间 time 是否在变化时间 timeRange 内 - (BOOL)getCropRectangleRampForTime:(CMTime)time startCropRectangle:(nullable CGRect *)startCropRectangle endCropRectangle:(nullable CGRect *)endCropRectangle timeRange:(nullable CMTimeRange *)timeRange NS_AVAILABLE(10_9, 7_0);
AVMutableVideoCompositionLayerInstruction 是 AVVideoCompositionLayerInstruction 的子类,它能够改变 composition 中的 track 资源播放时的仿射变化、裁剪区域、透明度等信息。
相比于父类,该子类还提供了建立实例的方法:
//这两个方法的区别在于,前者返回的实例对象的属性 trackID 的值是 track 的 trackID 值 //而第二个方法的返回的实例对象的属性 trackID 的值为 kCMPersistentTrackID_Invalid + (instancetype)videoCompositionLayerInstructionWithAssetTrack:(AVAssetTrack *)track; + (instancetype)videoCompositionLayerInstruction;
该类的属性表示 instruction 所做用的 track 的 ID
@property (nonatomic, assign) CMPersistentTrackID trackID;
设置了 trackID 后,经过下面的方法,进行剃度信息的设置:
//设置视频中帧的仿射变化信息
//指定了变化的时间范围、起始值和结束值,其中坐标系的原点为左上角,向下向右为正方向
- (void)setTransformRampFromStartTransform:(CGAffineTransform)startTransform toEndTransform:(CGAffineTransform)endTransform timeRange:(CMTimeRange)timeRange; //设置 instruction 的 timeRange 范围内指定时间的仿射变换,该值会一直保持,直到被再次设置 - (void)setTransform:(CGAffineTransform)transform atTime:(CMTime)time; //设置透明度的梯度信息,提供的透明度初始值和结束值应在0~1之间 //变化的过程是线形的 - (void)setOpacityRampFromStartOpacity:(float)startOpacity toEndOpacity:(float)endOpacity timeRange:(CMTimeRange)timeRange; //设置指定时间的透明度,该透明度会一直持续到下一个值被设置 - (void)setOpacity:(float)opacity atTime:(CMTime)time; //设置裁剪矩形的变化信息 - (void)setCropRectangleRampFromStartCropRectangle:(CGRect)startCropRectangle toEndCropRectangle:(CGRect)endCropRectangle timeRange:(CMTimeRange)timeRange NS_AVAILABLE(10_9, 7_0); //设置指定时间的裁剪矩形 - (void)setCropRectangle:(CGRect)cropRectangle atTime:(CMTime)time NS_AVAILABLE(10_9, 7_0);
在自定义视频播放时,可能须要添加水印、标题或者其余的动画效果,须要使用该类。该类一般用来协调离线视频中图层与动画图层的组合(如使用 AVAssetExportSession 和 AVAssetReader 、AVAssetReader 类导出视频文件或读取视频文件时),而如果在线实时的视频播放,应使用 AVSynchronizedLayer 类来同步视频的播放与动画的效果。
在使用该类时,注意动画在整个视频的时间线上都可以被修改,因此,动画的开始时间应该设置为 AVCoreAnimationBeginTimeAtZero ,这个值其实比 0 大,属性值 removedOnCompletion 应该置为 NO,以防当动画执行结束后被移除,而且不该使用与任何的 UIView 相关联的图层。
做为视频组合的后期处理工具类,主要方法以下:
//向视频组合中添加一个动画图层,这个图层不能在任何图层树中 //提供的参数 trackID 应由方法 [AVAsset unusedTrackID] 获得,它不与任何视频资源的 trackID 相关 //AVVideoCompositionInstruction 的属性 layerInstructions 包含的 AVVideoCompositionLayerInstruction 实例对象中应该有 //该 trackID 一致的 AVVideoCompositionLayerInstruction 实例对象,而且为性能考虑,不该使用该对象设置 transform 的变化 //在 iOS 中,CALayer 做为 UIView 的背景图层,其内容的是否可以翻转,由方法 contentsAreFlipped 决定(若是全部的图层包括子图层,该方法返回的值为 YES 的个数为奇数个,表示能够图层中内容能够垂直翻转) //因此这里的 layer 若用来设置 UIView 的 layer 属性,或做为其中的子图层,其属性值 geometryFlipped 应设置为 YES ,这样则可以保持是否可以翻转的结果一致 + (instancetype)videoCompositionCoreAnimationToolWithAdditionalLayer:(CALayer *)layer asTrackID:(CMPersistentTrackID)trackID; //将放在图层 videoLayer 中的组合视频帧同动画图层 animationLayer 中的内容一块儿进行渲染,获得最终的视频帧 //一般,videoLayer 是 animationLayer 的子图层,而 animationLayer 则不在任何图层树中 + (instancetype)videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:(CALayer *)videoLayer inLayer:(CALayer *)animationLayer; //复制 videoLayers 中的每个图层,与 animationLayer一块儿渲染获得最中的帧 ////一般,videoLayers 中的图层都在 animationLayer 的图层树中,而 animationLayer 则不属于任何图层树 + (instancetype)videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayers:(NSArray<CALayer *> *)videoLayers inLayer:(CALayer *)animationLayer NS_AVAILABLE(10_9, 7_0);
当咱们通过编辑后获得一个视频资源 asset ,而且为该资源设置了自定义播放信息 video composition ,须要验证对于这个 asset 而言,video composition 是否有效,能够调用 AVVideoComposition 的校验方法。
/*
@param asset
设置第一个参数的校验内容,设置 nil 忽略这些校验
1. 该方法能够校验 AVVideoComposition 的属性 instructions 是否符合要求 2. 校验 instructions 中的每一个 AVVideoCompositionInstruction 对象的 layerInstructions 属性中的 每个 AVVideoCompositionLayerInstruction 对象 trackID 值是否对应 asset 中 track 的 ID 或 AVVideoComposition 的 animationTool 实例 3. 校验时间 asset 的时长是否与 instructions 中的时间范围相悖 @param timeRange 设置第二个参数的校验内容 1. 校验 instructions 的全部的时间范围是否在提供的 timeRange 的范围内, 若要忽略该校验,能够传参数 CMTimeRangeMake(kCMTimeZero, kCMTimePositiveInfinity) @param validationDelegate 设置遵循 AVVideoCompositionValidationHandling 协议的代理类,用来处理校验过程当中的报错,能够为 nil */ - (BOOL)isValidForAsset:(nullable AVAsset *)asset timeRange:(CMTimeRange)timeRange validationDelegate:(nullable id<AVVideoCompositionValidationHandling>)validationDelegate NS_AVAILABLE(10_8, 5_0);
设置的代理对象要遵循协议 AVVideoCompositionValidationHandling ,该对象在实现下面的协议方法时,若修改了传递的 composition 参数,上面的校验方法则会抛出异常。
该协议提供了如下回调方法,全部方法的返回值用来肯定是否继续进行校验以获取更多的错误。
//报告 videoComposition 中有无效的值 - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidValueForKey:(NSString *)key NS_AVAILABLE(10_8, 5_0); //报告 videoComposition 中有时间段没有相对应的 instruction - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingEmptyTimeRange:(CMTimeRange)timeRange NS_AVAILABLE(10_8, 5_0); //报告 videoComposition 中的 instructions 中 timeRange 无效的实例对象 //多是 timeRange 自己为 CMTIMERANGE_IS_INVALID //或者是该时间段同上一个的 instruction 的 timeRange 重叠 //也多是其开始时间比上一个的 instruction 的 timeRange 的开始时间要早 - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidTimeRangeInInstruction:(id<AVVideoCompositionInstruction>)videoCompositionInstruction NS_AVAILABLE(10_8, 5_0); //报告 videoComposition 中的 layer instruction 同调用校验方法时指定的 asset 中 track 的 trackID 不一致 //也不与 composition 使用的 animationTool 的trackID 一致 - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidTrackIDInInstruction:(id<AVVideoCompositionInstruction>)videoCompositionInstruction layerInstruction:(AVVideoCompositionLayerInstruction *)layerInstruction asset:(AVAsset *)asset NS_AVAILABLE(10_8, 5_0);
下面的例子给出了将两个视频资源和一个音频资源编辑组合为一个资源文件的步骤。
而后向 composition 中添加 AVMutableCompositionTrack 实例对象,为性能考虑,对于非同时播放且相同类型的资源,应使用一个 AVMutableCompositionTrack 实例对象,因此这里添加一个视频类型的 composition track 和一个音频类型的 composition track 便可。
AVMutableComposition *mutableComposition = [AVMutableComposition composition]; AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
得到了拥有 composition track 的 composition 后,下一步就是将具体的视听资源添加到组合中。
AVURLAsset *firstVideoAsset = [AVURLAsset URLAssetWithURL:firstVideoUrl options:nil]; AVURLAsset *secondVideoAsset = [AVURLAsset URLAssetWithURL:secondVideoUrl options:nil]; AVURLAsset *audioAsset = [AVURLAsset URLAssetWithURL:audioUrl options:nil]; //获取视听资源中的第一个 asset track AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]; //第一个视频插入的时间点是 kCMTimeZero [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil]; //第二个视频插入的时间点是第一个视频结束的时间 [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil]; //音频的持续时间是两个视频时间的总和 CMTime videoTotalDuration = CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration); [audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoTotalDuration) ofTrack: atTime:kCMTimeZero error:nil];
检查视频的镜头是不是横向模式,组合时,video track 老是被认为是横向模式,若是待组合的 video track 是纵向模式,那么最终的视频显示将不符合预想,并且没法将横向模式和纵向模式的的视频组合到一块儿。
//判断第一个视频的模式 BOOL isFirstVideoPortrait = NO; CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform; if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) { isFirstVideoPortrait = YES; } //判断第二个视频的模式 BOOL isSecondVideoPortrait = NO; CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform; if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) { isSecondVideoPortrait = YES; } //判断两个视频的模式是否一致 if ((isFirstVideoAssetPortrait && !isSecondVideoAssetPortrait) || (!isFirstVideoAssetPortrait && isSecondVideoAssetPortrait)) { UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil]; [incompatibleVideoOrientationAlert show]; return; }
当每一个视频的方向是兼容的,那么能够对每一个视频的图层进行必要的调整。
AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration); AVMutableVideoCompositionInstruction * secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration); AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack]; [firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero]; AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack]; [secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration]; firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction]; secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction]; AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition]; mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];
检查视频方向的兼容性以后,须要调整视频组合渲染区域的大小,设置视频帧的刷新频率,以兼容每个视频的播放。
//获取视频的原播放区域
CGSize naturalSizeFirst, naturalSizeSecond; if (isFirstVideoAssetPortrait) { naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width); naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width); } else { naturalSizeFirst = firstVideoAssetTrack.naturalSize; naturalSizeSecond = secondVideoAssetTrack.naturalSize; } //设置的渲染区域要能包含两个视频的播放区域 float renderWidth, renderHeight; if (naturalSizeFirst.width > naturalSizeSecond.width) { renderWidth = naturalSizeFirst.width; } else { renderWidth = naturalSizeSecond.width; } if (naturalSizeFirst.height > naturalSizeSecond.height) { renderHeight = naturalSizeFirst.height; } else { renderHeight = naturalSizeSecond.height; } mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight); //设置帧每一秒刷新30次 mutableVideoComposition.frameDuration = CMTimeMake(1,30);
最后将组合的视听资源导出到一个单独的文件中并保存到资源库。
static NSDateFormatter *kDateFormatter; if (!kDateFormatter) { kDateFormatter = [[NSDateFormatter alloc] init]; kDateFormatter.dateStyle = NSDateFormatterMediumStyle; kDateFormatter.timeStyle = NSDateFormatterShortStyle; } AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality]; exporter.outputURL = [[[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:@YES error:nil] URLByAppendingPathComponent:[kDateFormatter stringFromDate:[NSDate date]]] URLByAppendingPathExtension:CFBridgingRelease(UTTypeCopyPreferredTagWithClass((CFStringRef)AVFileTypeQuickTimeMovie, kUTTagClassFilenameExtension))]; exporter.outputFileType = AVFileTypeQuickTimeMovie; exporter.shouldOptimizeForNetworkUse = YES; exporter.videoComposition = mutableVideoComposition; //异步导出 [exporter exportAsynchronouslyWithCompletionHandler:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (exporter.status == AVAssetExportSessionStatusCompleted) { //保存文件到媒体库 ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init]; if ([assetsLibrary videoAtPathIsCompatibleWithSavedPhotosAlbum:exporter.outputURL]) { [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:exporter.outputURL completionBlock:NULL]; } } }); }];
经过麦克风、摄像机等设备,能够捕获外界的声音和影像。要处理设备捕获的数据,须要使用 AVCaptureDevice 类描述设备,使用 AVCaptureInput 配置数据从设备的输入,使用 AVCaptureOutput 类管理数据到文件的写入,而数据的输入到写出,须要使用 AVCaptureSession 类进行协调。此外,可使用 AVCaptureVideoPreviewLayer 类显示相机正在拍摄的画面。
一个设备能够有多个输入,使用 AVCaptureInputPort 类描述这些输入,用 AVCaptureConnection 类描述具体类型的输入与输出的关系,能够实现更精细的数据处理。
AVCaptureSession 是捕获视听数据的核心类,它协调数据的输入和输出。建立一个 AVCaptureSession 类的对象时,能够指定最终获得的视听数据的质量,固然这个质量与设备也有关系,一般在设置以前,能够调用方法判断 session 是否支持要设置的质量。
AVCaptureSession 类实例可设置的数据质量有 AVCaptureSessionPresetHigh 、AVCaptureSessionPresetMedium 、AVCaptureSessionPresetLow 、AVCaptureSessionPreset320x240 等。在进行设置以前,能够调用 AVCaptureSession 中的方法进行校验。
- (BOOL)canSetSessionPreset:(NSString*)preset;
设置好对象后,可调用下面的方法,添加、移除输入、输出。
- (BOOL)canAddInput:(AVCaptureInput *)input; - (void)addInput:(AVCaptureInput *)input; - (void)removeInput:(AVCaptureInput *)input; - (BOOL)canAddOutput:(AVCaptureOutput *)output; - (void)addOutput:(AVCaptureOutput *)output; - (void)removeOutput:(AVCaptureOutput *)output; - (void)addInputWithNoConnections:(AVCaptureInput *)input NS_AVAILABLE(10_7, 8_0); - (void)addOutputWithNoConnections:(AVCaptureOutput *)output NS_AVAILABLE(10_7, 8_0); - (BOOL)canAddConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 8_0); - (void)addConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 8_0); - (void)removeConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 8_0);
开始执行 session 或者结束执行,调用下面的方法。
- (void)startRunning; - (void)stopRunning;
对于正在执行中的 session ,要对其进行改变,所做出的改变,应放在下面两个方法之间。
- (void)beginConfiguration; - (void)commitConfiguration;
AVCaptureSession 开始执行、结束执行、执行过程当中出错或被打断时,都会发出通知,经过注册下面的通知,能够获取咱们感兴趣的信息。
AVCaptureDevice 是用来描述设备属性的类,要捕获视听数据,须要获取相应的设备,使用该类获取有效的设备资源。这个设备资源列表是随时变更的,其在变更时,会发送 AVCaptureDeviceWasConnectedNotification 或 AVCaptureDeviceWasDisconnectedNotification 通知,以告知有设备链接或断开。
在获取设备以前,要先肯定要获取的设备的类型 AVCaptureDeviceType ,设备的位置 AVCaptureDevicePosition ,也能够经过要获取的媒体数据类型进行设备的选择。
获取设备后,能够保存它的惟一标识、模型标识、名称等信息,以待下次用来获取设备。
+ (NSArray *)devices; + (NSArray *)devicesWithMediaType:(NSString *)mediaType; + (AVCaptureDevice *)defaultDeviceWithMediaType:(NSString *)mediaType; + (AVCaptureDevice *)deviceWithUniqueID:(NSString *)deviceUniqueID; @property(nonatomic, readonly) NSString *uniqueID; @property(nonatomic, readonly) NSString *modelID; @property(nonatomic, readonly) NSString *localizedName; //校验得到的设备可否提供相应的媒体数据类型 - (BOOL)hasMediaType:(NSString *)mediaType; //校验得到的设备可否支持相应的配置 - (BOOL)supportsAVCaptureSessionPreset:(NSString *)preset;
获取一个设备后,能够经过修改它的属性来知足本身的须要。
在修改这些属性时,应先判断当前设备是否支持要设置的属性值,而且全部的属性修改都要放在下面两个方法之间,以保证属性可以被正确设置。
- (BOOL)lockForConfiguration:(NSError **)outError; - (void)unlockForConfiguration;
在调用硬件设备以前,应先判断应用是否拥有相应的权限,其权限分为如下几种:
//校验权限 + (AVAuthorizationStatus)authorizationStatusForMediaType:(NSString *)mediaType NS_AVAILABLE_IOS(7_0); //请求权限,handler 处理会在任意线程中执行,因此须要在主线程中执行的处理由用户负责指定 + (void)requestAccessForMediaType:(NSString *)mediaType completionHandler:(void (^)(BOOL granted))handler NS_AVAILABLE_IOS(7_0);
AVCaptureDeviceInput 是 AVCaptureInput 的子类,使用一个 AVCaptureDevice 类实例建立该类的实例,其管理设备的输入。
在建立了实例对象后,将其添加到 session 中。
NSError *error; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; if (input && [session canAddInput:input]) { [captureSession addInput:captureDeviceInput]; }
AVCaptureOutput 是一个抽象类,一般使用的是它的子类。
在建立了具体的子类后,将它添加到 session 中。
AVCaptureMovieFileOutput *movieOutput = [[AVCaptureMovieFileOutput alloc] init]; if ([session canAddOutput:movieOutput]) { [session addOutput:movieOutput]; }
AVCaptureFileOutput 是 AVCaptureOutput 的子类,是 AVCaptureMovieFileOutput 、AVCaptureAudioFileOutput 的父类。这个类中定义了文件输出时的地址、时长、容量等属性。
//当前记录的数据的文件的地址 @property(nonatomic, readonly) NSURL *outputFileURL; //开始文件的记录,指定文件的地址,以及记录过程当中或结束时要通知的代理对象 //指定的 outputFileURL 必需是有效的且没有文件占用 - (void)startRecordingToOutputFileURL:(NSURL*)outputFileURL recordingDelegate:(id<AVCaptureFileOutputRecordingDelegate>)delegate; //该方法能够中止数据向文件中写入 //若是要中止一个文件的写入转而指定另外一个文件的写入,不该调用该方法,只需直接调用上面的方法 //当因该方法的调用、出错、或写入文件的变动致使当前文件开始中止写入时,最后传入的缓存数据仍会在后台被写入 //不管什么时候,要使用文件,都须要等指定的代理对象被告知文件的写入已经结束以后进行 - (void)stopRecording; //判断当前是否有数据被写入文件 @property(nonatomic, readonly, getter=isRecording) BOOL recording; //表示到目前为止,当前文件已经记录了多长时间 @property(nonatomic, readonly) CMTime recordedDuration; //表示到目前为止,当前文件已经记录了多少个字节 @property(nonatomic, readonly) int64_t recordedFileSize; /** 下面三个值对文件的记录进行了限制,若果达到限制,则会在回调方法 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: 中传递相应的错误 */ //表示当前文件可以记录的最长时间,kCMTimeInvalid 表示无时间限制 @property(nonatomic) CMTime maxRecordedDuration; //表示当前文件可以记录的最大字节数,0 表示无大小限制 @property(nonatomic) int64_t maxRecordedFileSize; //表示记录当前文件时须要保留的最小字节数 @property(nonatomic) int64_t minFreeDiskSpaceLimit; //在 Mac OS X 系统下,经过指定遵循 AVCaptureFileOutputDelegate 协议的代理对象,来实现缓存数据的精确记录 @property(nonatomic, assign) id<AVCaptureFileOutputDelegate> delegate NS_AVAILABLE(10_7, NA); /** 在 Mac OS X 系统下,这个属性和方法能够判断记录是否中止,以及控制数据向文件中的中止写入和从新开始写入 */ @property(nonatomic, readonly, getter=isRecordingPaused) BOOL recordingPaused NS_AVAILABLE(10_7, NA); - (void)pauseRecording NS_AVAILABLE(10_7, NA); - (void)resumeRecording NS_AVAILABLE(10_7, NA);
AVCaptureFileOutputRecordingDelegate 是文件记录过程当中须要用到的协议,它一般的做用是告知代理对象文件记录结束了。
//这个代理方法是遵循该协议的代理对象必需要实现的方法
//每个文件记录请求,最终都会调用这个方法,即便没有数据成功写入文件
//当 error 返回时,文件也可能成功保存了,应检查 error 中的 AVErrorRecordingSuccessfullyFinishedKey 信息,查看具体错误 - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error; //当数据写入文件后调用,若是数据写入失败,该方法可能不会被调用 - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections; /** 在 Mac OS X 系统下,当文件的记录被暂停或从新开始,会调用下面的方法,若是记录被终止,不会调用下面的方法 */ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didPauseRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections NS_AVAILABLE(10_7, NA); - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didResumeRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections NS_AVAILABLE(10_7, NA); //在 Mac OS X 系统下,当记录将被中止,不管是主动的仍是被动的,都会调用下面的方法 - (void)captureOutput:(AVCaptureFileOutput *)captureOutput willFinishRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections error:(NSError *)error NS_AVAILABLE(10_7, NA);
AVCaptureFileOutputDelegate 这个协议只用于 Mac OS X 系统下,它给了客户端精准操控数据的机会。
/** 在 Mac OS X 10.8 系统以前,实现代理方法 captureOutput:didOutputSampleBuffer:fromConnection: 后即可以在该方法中实现数据记录的准确开始或结束,而要实如今任一一个画面帧处开始或中止数据的记录,要对每收到的 帧数据进行预先处理,这个过程消耗电能、产生热量、占用 CPU 资源,因此在 Mac OS X 10.8 及其以后的系统,提供了 下面的代理方法,来肯定客户端需不须要随时进行记录的开始或中止。 若是这个方法返回 NO ,对数据记录的设置将在开启记录以后进行。 */ - (BOOL)captureOutputShouldProvideSampleAccurateRecordingStart:(AVCaptureOutput *)captureOutput NS_AVAILABLE(10_8, NA); /** 若是上面的方法返回了 YES ,那么客户端即可以使用下面的方法对每个视频帧数据或音频数据进行操做 为了提升性能,缓存池中的缓存变量的内存一般会被复用,若是长时间使用缓存变量,那么新的缓存数据没法复制到 相应的内存中便会被废弃,因此若须要长时间使用缓存数据 sampleBuffer ,应复制一份,使其自己可以被系统复用 */ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, NA);
AVCaptureMovieFileOutput 是 AVCaptureFileOutput 的子类,它实现了在 AVCaptureFileOutput 中声明的视频数据记录方法,而且能够设置数据的格式、写入元数据、编码方式等属性。
//若是视频数据按片断写入,该值指定片断的时长,默认值是 10 秒 //该值为 kCMTimeInvalid 表示不使用片断对视频进行记录,这样视频只能一次写入,不能被打断 //改变该值不影响当前正在写入的片断时长 @property(nonatomic) CMTime movieFragmentInterval; //向文件中添加的 AVMetadataItem 类元数据 @property(nonatomic, copy) NSArray *metadata; //获取记录 connection 中数据时,使用的设置 - (NSDictionary *)outputSettingsForConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 10_0); //设置记录 connection 中数据时,使用的设置 AVVideoSettings.h //outputSettings 为空,表示在将 connection 中的数据写入文件以前,其格式不作改变 //outputSettings 为 nil 时,其数据格式将由 session preset 决定 - (void)setOutputSettings:(NSDictionary *)outputSettings forConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 10_0); //在 iOS 系统下,获取有效的编码格式,做为 AVVideoCodecKey 的值,使用上面的方法进行设置 @property(nonatomic, readonly) NSArray *availableVideoCodecTypes NS_AVAILABLE_IOS(10_0); //设置文件记录过程当中,是否建立一个元数据对 connection 的 videoOrientation 和 videoMirrored 属性变化进行跟踪记录 //connection 的属性 mediaType 的值必需是 AVMediaTypeVideo //该值的设置只在记录开始以前有效,开始记录以后改变该值无效果 - (void)setRecordsVideoOrientationAndMirroringChanges:(BOOL)doRecordChanges asMetadataTrackForConnection:(AVCaptureConnection *)connection NS_AVAILABLE_IOS(9_0); //判断该类实例对象是否会在记录的过程当中建立一个 timed metadata track 记录 connection 的 videoOrientation 和 videoMirrored 属性变化状况 - (BOOL)recordsVideoOrientationAndMirroringChangesAsMetadataTrackForConnection:(AVCaptureConnection *)connection NS_AVAILABLE_IOS(9_0);
AVCaptureAudioFileOutput 是 AVCaptureFileOutput 的子类,该类用于将媒体数据记录为一个音频文件。
//返回该类支持的音频文件类型 + (NSArray *)availableOutputFileTypes; //开始记录音频文件 - (void)startRecordingToOutputFileURL:(NSURL*)outputFileURL outputFileType:(NSString *)fileType recordingDelegate:(id<AVCaptureFileOutputRecordingDelegate>)delegate; //要写入音频文件中的元数据 AVMetadataItem 集合 @property(nonatomic, copy) NSArray *metadata; //写入的音频文件的设置 AVAudioSettings.h @property(nonatomic, copy) NSDictionary *audioSettings;
AVCaptureVideoDataOutput 是 AVCaptureOutput 的子类,该类能够用来处理捕获的每个视频帧数据。建立一个该类的实例对象后,要调用下面的方法设置一个代理对象,及调用代理对象所实现的协议方法的队列。
- (void)setSampleBufferDelegate:(id<AVCaptureVideoDataOutputSampleBufferDelegate>)sampleBufferDelegate queue:(dispatch_queue_t)sampleBufferCallbackQueue;
指定的队列 sampleBufferCallbackQueue 必需是串行队列以保证传递的帧是按记录时间前后传递的。
//设置输出的视频要进行怎样的格式处理 //设置为空([NSDictionary dictionary])表示不改变输入时的视频格式 //设置为 nil 表示未压缩格式 @property(nonatomic, copy) NSDictionary *videoSettings; //获取 kCVPixelBufferPixelFormatTypeKey 的有效值 @property(nonatomic, readonly) NSArray *availableVideoCVPixelFormatTypes NS_AVAILABLE(10_7, 5_0); //获取 AVVideoCodecKey 的有效值 @property(nonatomic, readonly) NSArray *availableVideoCodecTypes NS_AVAILABLE(10_7, 5_0); //表示当回调队列阻塞时,是否马上丢弃新接收的帧数据 @property(nonatomic) BOOL alwaysDiscardsLateVideoFrames;
该协议用来处理接收的每个帧数据,或者提示客户端有帧数据被丢弃。
//接收到一个帧数据时,在指定的串行队列中调用该方法,携带帧数据并包含有其余帧信息
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection; //丢弃一个帧数据时,在指定的串行队列中调用该方法,sampleBuffer 只携带帧信息,具体帧数据并未携带 - (void)captureOutput:(AVCaptureOutput *)captureOutput didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 6_0);
AVCaptureVideoPreviewLayer 是 CALayer 的子类,使用该类能够实现捕获视频的显示。使用一个 session 建立一个该类对象,然后将该类对象插入到图层树中,从而显示捕获的视频。
//建立方法 + (instancetype)layerWithSession:(AVCaptureSession *)session; - (instancetype)initWithSession:(AVCaptureSession *)session;
修改 AVCaptureVideoPreviewLayer 的属性 videoGravity 值,能够选择显示捕获视频时的界面大小变化方式,它有如下可选值:
AVCaptureAudioDataOutput 是 AVCaptureOutput 的子类,该类能够处理接收到的音频数据。同 AVCaptureVideoDataOutput 相似,该类也提供了一个方法,用于设置代理对象,以及调用代理对象实现的协议方法时的队列。
- (void)setSampleBufferDelegate:(id<AVCaptureAudioDataOutputSampleBufferDelegate>)sampleBufferDelegate queue:(dispatch_queue_t)sampleBufferCallbackQueue;
该协议提供了一个方法,用来实现对音频数据的接收处理。
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
对于每一个设备,其支持播放或捕获的媒体资源都不相同,经过 AVCaptureDeviceFormat 类能够获取相关信息。
对媒体数据资源进行简单的转码或裁剪,使用 AVAssetExportSession 类便足够了,可是更深层次的修改媒体资源,便须要用到 AVAssetReader 类和 AVAssetWriter 类。
AVAssetReader 只能与一个资源 asset 相关联,且不能用来读取实时数据,在开始读取数据以前,须要为 reader 添加 AVAssetReaderOutput 的实例对象。这个实例对象描述的是待读取的数据资源来源类型,一般使用 AVAssetReaderAudioMixOutput 、AVAssetReaderTrackOutput 、AVAssetReaderVideoCompositionOutput 三种子类。
AVAssetWriter 能够未来自多个数据源的数据以指定的格式写入到一个指定的文件中,且其只能对应一个文件。在写文件以前,须要用每个 AVAssetWriterInput 类实例对象来描述相应的数据源。每个 AVAssetWriterInput 实例对象接收的数据都应是 CMSampleBufferRef 类型的变量。若是使用 AVAssetWriterInputPixelBufferAdaptor 类也能够直接将 CVPixelBufferRef 类型的变量数据添加到 writer input 中。
AVAssetReader 与 AVAssetWriter 结合起来使用,即可以对读取的数据进行相应的编辑修改,然后写入到一个文件中并保存。
使用该类读取媒体资源,其提供的初始化方法与一个 asset 相关联。
//对于提供的参数 asset ,若是是可被修改的,那么在开始读取操做后,对其进行了修改,以后的读取操做都是无效的 + (nullable instancetype)assetReaderWithAsset:(AVAsset *)asset error:(NSError * _Nullable * _Nullable)outError; - (nullable instancetype)initWithAsset:(AVAsset *)asset error:(NSError * _Nullable * _Nullable)outError NS_DESIGNATED_INITIALIZER; //当前读取操做的状态,可取值有 AVAssetReaderStatusUnknown 、AVAssetReaderStatusReading 、 AVAssetReaderStatusCompleted 、AVAssetReaderStatusFailed 、AVAssetReaderStatusCancelled @property (readonly) AVAssetReaderStatus status; //当 status 的值为 AVAssetReaderStatusFailed 时,描述错误信息 @property (readonly, nullable) NSError *error; //限制可读取的资源的时间范围 @property (nonatomic) CMTimeRange timeRange; //判断可否添加该数据源 - (BOOL)canAddOutput:(AVAssetReaderOutput *)output; //添加数据源 - (void)addOutput:(AVAssetReaderOutput *)output; //开始读取 - (BOOL)startReading; //结束读取 - (void)cancelReading;
AVAssetReaderOutput 是用来描述待读取的数据的抽象类,读取资源时,应建立该类的对象,并添加到相应的 AVAssetReader 实例对象中去。
//获取的媒体数据的类型 @property (nonatomic, readonly) NSString *mediaType; //是否拷贝缓存中的数据到客户端,默认 YES ,客户端能够随意修改数据,可是为优化性能,一般设为 NO @property (nonatomic) BOOL alwaysCopiesSampleData NS_AVAILABLE(10_8, 5_0); //同步获取下一个缓存数据,使用返回的数据结束后,应使用 CFRelease 函数将其释放 //当错误或没有数据可读取时,返回 NULL ,返回空后,应检查相关联的 reader 的状态 - (nullable CMSampleBufferRef)copyNextSampleBuffer CF_RETURNS_RETAINED; //是否支持从新设置数据的读取时间范围,即可否修改 reader 的 timeRange 属性 @property (nonatomic) BOOL supportsRandomAccess NS_AVAILABLE(10_10, 8_0); //设置从新读取的时间范围,这个时间范围集合中的每个时间范围的开始时间必需是增加的且各个时间范围不能重叠 //应在 reader 调用 copyNextSampleBuffer 方法返回 NULL 以后才可调用 - (void)resetForReadingTimeRanges:(NSArray<NSValue *> *)timeRanges NS_AVAILABLE(10_10, 8_0); //该方法调用后,上面的方法即不可再调用,同时 reader 的状态也不会被阻止变为 AVAssetReaderStatusCompleted 了 - (void)markConfigurationAsFinal NS_AVAILABLE(10_10, 8_0);
AVAssetReaderTrackOutput 是 AVAssetReaderOutput 的子类,它用来描述待读取的数据来自 asset track ,在读取前,还能够对数据的格式进行修改。
//初始化方法,参数中指定了 track 和 媒体的格式 //指定的 track 应在 reader 的 asset 中 + (instancetype)assetReaderTrackOutputWithTrack:(AVAssetTrack *)track outputSettings:(nullable NSDictionary<NSString *, id> *)outputSettings; - (instancetype)initWithTrack:(AVAssetTrack *)track outputSettings:(nullable NSDictionary<NSString *, id> *)outputSettings NS_DESIGNATED_INITIALIZER; //指定音频处理时的算法 @property (nonatomic, copy) NSString *audioTimePitchAlgorithm NS_AVAILABLE(10_9, 7_0);
AVAssetReaderAudioMixOutput 是 AVAssetReaderOutput 的子类,它用来描述待读取的数据来自音频组合数据。建立该类实例对象提供的参数 audioTracks 集合中的每个 asset track 都属于相应的 reader 中的 asset 实例对象,且类型为 AVMediaTypeAudio 。
参数 audioSettings 给出了音频数据的格式设置。
+ (instancetype)assetReaderAudioMixOutputWithAudioTracks:(NSArray<AVAssetTrack *> *)audioTracks audioSettings:(nullable NSDictionary<NSString *, id> *)audioSettings; - (instancetype)initWithAudioTracks:(NSArray<AVAssetTrack *> *)audioTracks audioSettings:(nullable NSDictionary<NSString *, id> *)audioSettings NS_DESIGNATED_INITIALIZER;
此外,该类的 audioMix 属性,描述了从多个 track 中读取的音频的音量变化状况。
@property (nonatomic, copy, nullable) AVAudioMix *audioMix;
AVAssetReaderVideoCompositionOutput 是 AVAssetReaderOutput 的子类,该类用来表示要读取的类是组合的视频数据。
同 AVAssetReaderAudioMixOutput 相似,该类也提供了两个建立实例的方法,须要提供的参数的 videoTracks 集合中每个 track 都是
与 reader 相关联的 asset 中的 track 。
+ (instancetype)assetReaderVideoCompositionOutputWithVideoTracks:(NSArray<AVAssetTrack *> *)videoTracks videoSettings:(nullable NSDictionary<NSString *, id> *)videoSettings; - (instancetype)initWithVideoTracks:(NSArray<AVAssetTrack *> *)videoTracks videoSettings:(nullable NSDictionary<NSString *, id> *)videoSettings NS_DESIGNATED_INITIALIZER;
该类的属性 videoComposition 一样描述了每一个 track 的帧的显示方式。
@property (nonatomic, copy, nullable) AVVideoComposition *videoComposition;
使用 AVOutputSettingsAssistant 类能够获取简单的编码设置