AVFoundation框架解析(二)AVAssetWriter
将媒体数据写入容器文件的对象。
AVAssetWriter 实例可以将媒体写入 QuickTime 影片文件格式或 MPEG-4 文件格式等新文件。AVAssetWriter 支持多个并发轨道的媒体数据自动交错, 以实现高效的播放和存储。。源媒体数据可以从 AVAssetReader 实例获取,用于一个或多个 Assets,也可以从 AVFoundation 之外的其他来源获取。
AVAssetWriter 实例可以在写入媒体样本时对其进行重新编码。AVAssetWriter 实例还可以选择将元数据集合写入输出文件。
单个 AVAssetWriter 实例只能用于写入单个文件。如果客户端希望多次写入文件,则每次都必须使用新的 AVAssetWriter 实例。
- 返回配置为以指定容器格式写入文件的 AVAssetWriter 实例。
如果指定的 URL 上已存在文件,则写入将失败。
可写入的容器格式的 UTI 在 AVMediaFormat.h 中声明。
+ (nullable instancetype)assetWriterWithURL:(NSURL *)outputURL fileType:(AVFileType)outputFileType error:(NSError * _Nullable * _Nullable)outError;
- 创建一个 AVAssetWriter 实例,配置为以指定的容器格式输出段数据。
想要通过 -assetWriter:didOutputSegmentData:segmentType:segmentReport: 或 -assetWriter:didOutputSegmentData:segmentType: 委托方法接收段数据的客户端应该使用此初始化程序而不是 -initWithURL:fileType:error:。
客户端可以使用 +typeWithIdentifier: 和 UTI 来创建 UTType 实例。请参阅 <UniformTypeIdentifiers/UTType.h>。
如果 AVMediaFormat.h 中未声明容器格式的输出内容类型 UTI,则此方法抛出异常。
- (instancetype)initWithContentType:(UTType *)outputContentType
@property (nonatomic, copy, readonly) NSURL *outputURL;
4 . @property (nonatomic, copy, readonly) AVFileType outputFileType;
- 可以向接收器添加输入的媒体类型。
某些媒体类型可能不被 AVAssetWriter 初始化的文件格式所接受。
@property (nonatomic, readonly) NSArray<AVMediaType> *availableMediaTypes;
- 将样本写入接收器输出文件的状态。
此属性的值是一个 AVAssetWriterStatus 类型,用于指示写入操作正在进行、已成功完成、已取消还是失败。AVAssetWriterInput 对象的客户端应在附加样本失败后检查此属性的值,以确定无法继续写入样本的原因。此属性是线程安全的。
@property (readonly) AVAssetWriterStatus status;
-
@property (readonly, nullable) NSError *error; -
要写入接收器输出文件的元数据集合。
此属性的值是 AVMetadataItem 对象的数组,表示要写入输出文件的顶级元数据的集合。
写入开始后无法设置此属性。
@property (nonatomic, copy) NSArray<AVMetadataItem *> *metadata;
- 指定是否应以更适合通过网络播放的方式写入输出文件
当此属性的值为 YES 时,输出文件将以这样的方式写入:只需下载少量文件即可开始播放。写入开始后无法设置此属性。
@property (nonatomic) BOOL shouldOptimizeForNetworkUse;
- 指定适合包含在写入资产过程中生成的临时文件的目录。
AVAssetWriter 可能需要在特定配置下写入临时文件,例如,当其一个或多个输入的 performMultiPassEncodingIfSupported 设置为 YES 时。此属性可用于控制在文件系统中创建这些临时文件的位置。当资源写入完成、取消或失败时,所有临时文件都将被删除。
当此属性值为 nil 时,资产写入器将在写入临时文件时选择合适的位置。默认值为 nil。
写入操作开始后无法设置此属性。如果无法在此目录中创建文件(例如,由于权限不足),资产写入操作将失败。
@property (nonatomic, copy, nullable) NSURL *directoryForTemporaryFiles
11 资产写入器AVAssetWriter从中接收媒体数据的输入。
此属性的值是一个包含 AVAssetWriterInput 具体实例的 NSArray。可以使用 addInput: 方法将输入添加到接收器。
@property (nonatomic, readonly) NSArray<AVAssetWriterInput *> *inputs;
12 测试接收器的文件格式是否支持特定媒体类型的输出设置。
此方法确定指定媒体类型的输出设置是否适用于接收方的文件格式。例如,指定 H.264 压缩的视频压缩设置与无法包含 H.264 压缩视频的文件格式不兼容。
尝试添加具有输出设置和媒体类型的输入(此方法返回 NO)将导致抛出异常。
- (BOOL)canApplyOutputSettings:(nullable NSDictionary<NSString *, id> *)outputSettings forMediaType:(AVMediaType)mediaType;
13 测试是否可以将输入添加到接收器。
无法添加接受与接收器不兼容的媒体数据类型或与接收器不兼容的输出设置的输入。
- (BOOL)canAddInput:(AVAssetWriterInput *)input;
14 向接收器添加输入。
输入端通过媒体类型和输出设置创建。这两者都必须与接收器兼容。
写入开始后无法添加输入。
如果满足以下任一条件,则此方法抛出异常:
--------该输入的媒体类型不适用于此资产写入器 (AVAssetWriter)
--------以特定格式写入未压缩的视频
--------passthrough* 文件(AVFileTypeQuickTimeMovie 除外)在 AVAssetWriterInput 初始化程序中缺少格式提示
--------此媒体/文件类型组合不支持 passthrough*(例如,AVFileTypeWAVE 仅支持 AVMediaTypeAudio)
当输入的输出设置为零时,表示直通。
- (void)addInput:(AVAssetWriterInput *)input;
15 准备接收器接受输入并将其输出写入其输出文件。
添加所有输入并设置其他配置属性后,必须调用此方法,以告知接收器准备写入。调用此方法后,客户端可以使用 startSessionAtSourceTime: 启动写入会话,并使用每个接收器输入提供的方法写入媒体样本。
如果无法开始写入,此方法将返回 NO。客户端可以检查 status 和 error 属性的值,以获取有关无法开始写入的更多信息。
在 iOS 上,如果客户端应用进入后台时 AVAssetWriter 的状态为 AVAssetWriterStatusWriting,则其状态将更改为 AVAssetWriterStatusFailed,并且附加到其任何输入的操作都将失败。您可能需要使用 -[UIApplication beginBackgroundTaskWithExpirationHandler:] 来避免在写入会话中途被中断,并完成已附加数据的写入。有关在后台执行代码的更多信息,请参阅《iOS 应用程序编程指南》。
- (BOOL)startWriting;
16 为接收者启动样本写作会话。
startTime: 在源样本的时间线中,样本写作会话的起始资产时间。
附加到资产写入器输入的样本数据序列被视为属于使用此方法启动的“样本写入会话”。因此,必须在写入操作开始(使用 -startWriting)之后、但在任何样本数据附加到接收器输入之前调用此方法。
每个写入会话都有一个起始时间,在写入文件格式允许的情况下,该起始时间定义了从源样本时间线到写入文件时间线的映射。对于 QuickTime 影片文件格式,第一个会话从影片时间 0 开始,因此附加时间戳为 T 的样本将在影片时间 (T-起始时间) 播放。时间戳早于起始时间的样本仍将添加到输出文件,但会被编辑掉(即在播放期间不会显示)。如果输入的最早附加样本的时间戳晚于起始时间,则会插入一个空编辑,以保持输出素材轨道之间的同步。
要结束使用此方法启动的会话,请使用 -endSessionAtSourceTime: 或 -finishWritingWithCompletionHandler:。连续两次调用 -startSessionAtSourceTime: 且两次调用之间未调用 -endSessionAtSourceTime: 则会导致错误。
注意:目前不支持多个样本写入会话。调用 -endSessionAtSourceTime: 后再次调用 -startSessionAtSourceTime: 会出错。
- (void)startSessionAtSourceTime:(CMTime)startTime;
17 结束样本写作会议。
endTime: 在源样本的时间线中,样本写作会话的结束资产时间。
endTime 定义了会话在源样本时间轴上的结束时刻。对于 QuickTime 影片文件格式,每个样本写入会话的 startTime...endTime 对对应着一个影片时间段,会话的样本将被插入到该时间段中。时间戳晚于会话结束时间的样本仍会添加到写入文件中,但会被编辑掉(即在播放期间不会显示)。因此,如果第一个会话的时长为 D1 = endTime - startTime,它将在时间 0 到 D1 之间插入到写入文件中;第二个会话将在时间 D1 到 D1+D2 之间插入到写入文件中,依此类推。没有样本的会话是合法的;这将导致创建一个具有规定时长的空编辑。
调用 -endSessionAtSourceTime: 不是强制性的;如果在没有首先调用 -endSessionAtSourceTime: 的情况下调用 -finishWritingWithCompletionHandler:,则会话的有效结束时间将是会话附加样本的最新结束时间戳(即,最后不会编辑任何样本)。
在样本写入会话之外附加样本是错误的。要在调用 -endSessionAtSourceTime: 后附加更多样本,必须先使用 -startSessionAtSourceTime: 启动新会话。
注意:目前不支持多个样本写入会话。调用 -endSessionAtSourceTime: 后再次调用 -startSessionAtSourceTime: 会出错。
如果会话在未启动的情况下结束,则此方法会引发异常。
- (void)endSessionAtSourceTime:(CMTime)endTime;
18 取消输出文件的创建。
如果接收器的状态为“失败”或“完成”,则 -cancelWriting 为空操作。否则,此方法将阻塞,直到写入操作被取消。
如果接收方在写入过程中创建了输出文件,-cancelWriting 将删除该文件。
此方法不应与 -[AVAssetWriterInput appendSampleBuffer:] 或 -[AVAssetWriterInputPixelBufferAdaptor appendPixelBuffer:withPresentationTime:] 同时调用。
- (void)cancelWriting;
19 将所有未完成的输入标记为已完成并完成输出文件的写入。
该方法立即返回并导致其工作异步执行。
当输出文件写入完成,或者在此期间发生失败或取消时,指定的处理程序将被调用以指示操作完成。要判断操作是否成功,处理程序可以检查 AVAssetWriter.status 的值。如果状态为 AVAssetWriterStatusFailed,AVAsset.error 将包含一个描述失败情况的 NSError 实例。
为了保证所有样本缓冲区都已成功写入,请确保在调用此方法之前对 -[AVAssetWriterInput appendSampleBuffer:] 或 -[AVAssetWriterInputPixelBufferAdaptor appendPixelBuffer:withPresentationTime:] 的所有调用都已返回。
- (void)finishWritingWithCompletionHandler:(void (^ NS_SWIFT_SENDABLE)(void))handler
@interface AVAssetWriter (AVAssetWriterFileTypeSpecificProperties)
20 对于支持影片片段的文件类型,指定写入影片片段的频率。
使用影片片段时,即使写入操作意外中断,部分写入的资源仍可成功打开并播放,播放时间最多为指定时间间隔的倍数。此属性的默认值为 kCMTimeInvalid,表示不应使用影片片段。
使用影片片段时,为了获得最佳的外部存储设备写入性能,请将 movieFragmentInterval 设置为 10 秒或更大。
写入开始后无法设置此属性。
@property (nonatomic) CMTime movieFragmentInterval;
21 对于支持影片片段的文件类型,指定写入初始影片片段的间隔。
如果未设置 movieFragmentInterval 属性,则此属性无关紧要。默认值为 kCMTimeInvalid,表示初始影片片段的间隔与 movieFragmentInterval 属性指定的间隔相同。
@property (nonatomic) CMTime initialMovieFragmentInterval
22 对于支持影片片段的文件类型,指定初始影片片段序列号。
该值必须等于或大于 1。默认值为1
请注意,如果将 AVAssetWriter 实例生成的影片片段与由 AVAssetWriter 的不同实例或其他方式生成的其他影片片段组合在一起,则必须确保影片片段序列号在整个组合集合中按时间顺序单调增加。
写入开始后无法设置此属性。
@property (nonatomic) NSInteger initialMovieFragmentSequenceNumber
23 对于支持分段式 MPEG-4 的文件类型,指定是否应以适合的方式生成影片片段,使其适合与一个或多个其他 AVAssetWriter 实例生成的影片片段组合成统一编码的单个片段流。
默认值为NO,
当使用 AVAssetWriter 的多个实例来生成相互补充的不同流时(例如创建 HLS 编码或比特率变体),无需将此属性设置为 YES。
写入开始后无法设置此属性。
@property (nonatomic) BOOL producesCombinableFragments
24 对于支持电影片段的文件类型,提供要写入的文件的最终时长的提示
此属性的值必须是非负的 CMTime 数字。如果此属性的值为无效的 CMTime(例如 kCMTimeInvalid),则不会将总时长提示写入文件。默认值为 kCMTimeInvalid。
如果影片片段未写入,则此属性当前会被忽略。请使用 movieFragmentInterval 属性来启用影片片段。
写入开始后无法设置此属性。
@property (nonatomic) CMTime overallDurationHint;
25 对于包含“moov”原子的文件类型(例如 QuickTime 电影文件),指定要使用的资产级时间尺度。
默认值为 0,表示接收方应该选择一个方便的值(如果适用)。
写入开始后无法设置此属性。
@property (nonatomic) CMTimeScale movieTimeScale
@interface AVAssetWriter (AVAssetWriterInputGroups)
25 测试是否可以将输入组添加到接收器。
如果 outputFileType 指定了不支持轨道之间互斥关系的容器格式,或者 AVAssetWriterInputGroup 的指定实例包含无法关联的媒体类型的输入,则无法将该组添加到 AVAssetWriter。
如果满足以下任一条件,此方法将抛出异常:
- 此写入器的输出文件类型不支持轨道之间的互斥关系(允许的类型为 AVFileTypeQuickTimeMovie、AVFileTypeAppleM4A、AVFileTypeAppleM4V、AVFileType3GPP、AVFileTypeMPEG4)
- 输入组中的任何 AVAssetWriterInput 也存在于已添加的输入组中
- (BOOL)canAddInputGroup:(AVAssetWriterInputGroup *)inputGroup
26 将 AVAssetWriterInputGroup 实例添加到 AVAssetWriter。如果输出容器格式支持轨道之间的互斥关系,则 AVAssetWriter 会将与分组输入关联的轨道标记为在播放或其他处理过程中相互排斥。
当输入组添加到 AVAssetWriter 时,marksOutputTrackAsEnabled 的值将自动设置为默认输入的 YES,并将其设置为组中所有其他输入的 NO。
写入开始后无法添加输入组。
- (void)addInputGroup:(AVAssetWriterInputGroup *)inputGroup
26 已添加到 AVAssetWriter 的 AVAssetWriterInputGroup 实例。
此属性的值是一个包含 AVAssetWriterInputGroup 具体实例的 NSArray。可以使用 addInputGroup: 方法将输入组添加到接收器。
@property (nonatomic, readonly) NSArray<AVAssetWriterInputGroup *> *inputGroups
@interface AVAssetWriter (AVAssetWriterSegmentation)
27 指定首选段间隔。
默认值为 kCMTimeInvalid,这意味着接收器将选择一个合适的默认值。该值可以设置为正数或 kCMTimeIndefinite。
如果值为 kCMTimeIndefinite,则每次客户端调用 -flushSegment 时,接收器都会输出一个段数据。
@property (nonatomic) CMTime preferredOutputSegmentInterval
28 指定初始段的开始时间。
如果 preferredOutputSegmentInterval 属性值为正数,则必须设置一个数值时间。如果不是,则此属性无关紧要。
@property (nonatomic) CMTime initialSegmentStartTime
29 为指定的文件类型指定文件类型配置文件。
默认值为 nil,这意味着接收方将根据指定的文件类型选择适当的默认配置文件。
想要通过 -assetWriter:didOutputSegmentData:segmentType:segmentReport: 或 -assetWriter:didOutputSegmentData:segmentType: 委托方法接收适合流式传输的段数据的客户端应设置 AVFileTypeProfileMPEG4AppleHLS 或 AVFileTypeProfileMPEG4CMAFCompliant 以要求使用 AVFileTypeMPEG4 文件类型特别符合 CMAF 格式的输出。
文件类型配置文件在 AVMediaFormat.h 中声明。
@property (nonatomic, copy, nullable) AVFileTypeProfile outputFileTypeProfile
30 @property (weak, nullable) id <AVAssetWriterDelegate> delegate
31 关闭当前段并将其输出到 -assetWriter:didOutputSegmentData:segmentType:segmentReport: 或 -assetWriter:didOutputSegmentData:segmentType: 委托方法。
如果未实现输出段数据的委托方法,或者 preferredOutputSegmentInterval 属性的值不是 kCMTimeIndefinite,则此方法抛出异常。
- (void)flushSegment
@protocol AVAssetWriterDelegate <NSObject>
输出段数据时调用的方法。
segmentData: 包含段数据的 NSData 实例。
segmentType: 段数据的段类型。段类型在 AVAssetSegmentReport.h 中声明。
segmentReport: AVAssetSegmentReport 实例。
如果实现了此方法,正常的文件写入将被抑制。AVAssetWriter 实例必须通过 -initWithContentType: 初始化方法进行初始化。
然后,客户端将媒体数据附加到已添加到接收器的 AVAssetWriterInputs 中,为每个输入调用 -markAsFinished 将其标记为已完成,并调用 -finishWritingWithCompletionHandler: 完成写入,就像正常的文件写入一样。
请勿使用 movieFragmentInterval 或 shouldOptimizeForNetworkUse 属性,因为这些属性在此操作模式下会被忽略。
需要在此方法范围之外引用 NSData 的客户端必须保留或复制它,并在使用完毕后释放它。
段报告 (segmentReport) 提供有关段数据的信息。如果没有可供报告的信息,段报告可能为 nil。
如果客户端不需要连续段数据的相关信息,则应实现 -assetWriter:didOutputSegmentData:segmentType: 方法,而不是此方法,以提高效率,因为这会通知接收方跳过段报告的准备工作。
有关 AVAssetSegmentReport 的更多详细说明,请参阅 AVAssetSegmentReport.h 文件。
如果文件类型为 AVFileTypeMPEG4,且 outputFileTypeProfile 为 AVFileTypeProfileMPEG4AppleHLS 或 AVFileTypeProfileMPEG4CMAFCompliant,则当 segmentsType 为 AVAssetSegmentTypeInitialization 时,该段包含一个“moov”原子,该原子不包含除样本描述之外的任何样本表,适合用作后续段数据序列的初始化段。
当 segmentsType 为 AVAssetSegmentTypeSeparable 时,该段包含一个“moof”原子,该原子包含一个“moof”原子,后跟一个“mdat”原子。
- 如果 preferredOutputSegmentInterval 属性值为正数,则当(样本的输出 PTS - InitialSegmentStartTime)>=(间隔 * N)(N = 1, 2, 3...)时,接收器会等待下一个同步样本,并在同步样本出现时将包含自上一个间隔以来附加的所有样本的片段数据输出到委托方法,以便下一个片段可以从同步样本开始。
在此配置下,可以使用直通(通过将 nil 传递给 AVAssetWriterInputs 的输出设置)和压缩。输入的媒体类型可以是 AVMediaTypeVideo 或 AVMediaTypeAudio。
每种媒体类型只能添加一个输入进行压缩,并且当(样本的输出 PTS - InitialSegmentStartTime)>=(间隔 * N)(N = 1, 2, 3...)时,样本将被强制编码为同步样本,以便当前片段立即关闭。
对于直通,只能添加一个输入。
- 如果 preferredOutputSegmentInterval 属性的值为 kCMTimeIndefinite,则每次客户端调用 -flushSegment 时,接收器都会输出一个包含自上次调用委托方法以来附加的所有样本的片段数据。
委托方法可以异步调用,在与调用 -flushSegment 的线程不同的线程上调用。
在此配置下,仅提供直通功能。输入的媒体类型可以是 AVMediaTypeVideo 或 AVMediaTypeAudio。
每种媒体类型只能添加一个输入。
客户端应在同步样本之前调用 -flushSegment,以便下一个片段可以从同步样本开始。否则,将出现错误。
- (void)assetWriter:(AVAssetWriter *)writer didOutputSegmentData:(NSData *)segmentData segmentType:(AVAssetSegmentType)segmentType segmentReport:(nullable AVAssetSegmentReport *)segmentReport;
此方法的使用方法与 -assetWriter:didOutputSegmentData:segmentType:segmentReport: 相同,只是此方法不传递 AVAssetSegmentReport。
如果客户端实现了 -assetWriter:didOutputSegmentData:segmentType:segmentReport: 方法,则会调用该方法而不是此方法。
- (void)assetWriter:(AVAssetWriter *)writer didOutputSegmentData:(NSData *)segmentData segmentType:(AVAssetSegmentType)segmentType;