导出

使用 AVFoundation 提供的导出 (export)API 可以对音视频资源操作. AVAssetExportSessionarrow-up-right 类提供的接口可以实现一些简单的 export 需求, 比如修改资源文件格式, 对资源进行删减. 对更复杂的需求, 需要使用 AVAssetReaderarrow-up-rightAVAssetWriterarrow-up-right.

在需要对 asset 内容进行操作时使用AVAssetReader. 例如, 需要读取 audio track 绘制音频波形图. 在需要将媒体 (比如 sample buffers 或者静态图像) 转换为一个 asset 时, 使用AVAssetWriter.

注意: 不应实时处理时用到这两个类. AVAssetReader不能用来读取 HTTP 直播流这样的实时资源. 如果在实时数据处理 (比如 AVCaptureOutputarrow-up-right) 中使用了AVAssetWriter, 需要将AVAssetWriter的属性 expectsMediaDataInRealTimearrow-up-right 设置为YES, 这样可以保证以正确的顺序写入文件.

读取Asset

每个AVAssetReader对象只能被关联到一个 asset, 但是这个 asset 可能包含多个 track. 因此, 在开始读取之前, 需要配置一个 AVAssetReaderOutputarrow-up-right 的子类来设置媒体数据的读取方式. AVAssetReaderOutput有三个子类可以用来读取 asset: AVAssetReaderTrackOutputarrow-up-right, AVAssetReaderAudioMixOutputarrow-up-rightAVAssetReaderVideoCompositionOutputarrow-up-right.

创建 Asset Reader

创建AVAssetReader对象需要一个 asset 对象:

NSError *outError;
AVAsset *someAsset = <#AVAsset that you want to read#>;
AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:someAsset error:&outError];
BOOL success = (assetReader != nil);

需要检查 assetReader 是否创建成功, 如果失败, error 会包含相关的错误信息.

设置 Asset Reader Output

成功创建 assetReader 后, 至少需要设置一个 output 来接收读取的媒体数据. 确保 output 的属性 alwaysCopiesSampleDataarrow-up-right 被设置为NO, 这样能提升性能. 本章所有的实例代码中, 该属性都设置为NO.

如果只是需要从一个或多个 track 中读取数据并修改其格式, 那么可以使用AVAssetReaderTrackOutput. 要解压一个 audio track 为 Linear PCM, 需要进行如下设置:

AVAsset *localAsset = assetReader.asset;
// Get the audio track to read.
AVAssetTrack *audioTrack = [[localAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
// Decompression settings for Linear PCM
NSDictionary *decompressionAudioSettings = @{ AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM] };
// Create the output with the audio track and decompression settings.
AVAssetReaderOutput *trackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:decompressionAudioSettings];
// Add the output to the reader if possible.
if ([assetReader canAddOutput:trackOutput])
    [assetReader addOutput:trackOutput];

要以存储时的格式读取数据, 将参数outputSettings设置为nil.

对于使用 AVAudioMixarrow-up-rightAVVideoCompositionarrow-up-right 处理过的 asset, 需要使用AVAssetReaderAudioMixOutputAVAssetReaderVideoCompositionOutput进行读取. 通常, 当从 AVCompositionarrow-up-right 对象中读取数据时, 会使用到这些 output 对象.

使用一个AVAssetReaderAudioMixOutput对象, 可以读取 asset 中的多个 audio track. 下面的代码展示了为 asset 中所有的 audio track 创建一个AVAssetReaderAudioMixOutput对象, 解压 audio track 为 Linear PCM, 并为 output 设置音频混合方式 (audio mix):

设置参数audioSettingsnil, 将返回未被压缩的样本数据. 对AVAssetReaderVideoCompositionOutput也一样.

AVAssetReaderVideoCompositionOutput 的使用方法大致与AVAssetReaderAudioMixOutput 相同, 可以从 asset 中读取多个 video track. 下面的代码示例了如何从多个 video track 中读取数据, 并解压为 ARGB:

读取 Asset 中的媒体数据

按需设置 outputs 之后, 调用 asset reader 的方法 startReadingarrow-up-right 开始读取数据. 然后使用方法 copyNextSampleBufferarrow-up-right 从 outputs 中获取媒体数据. 示例如下:

写入Asset

AVAssetWriterarrow-up-right 将多个来源的数据以指定格式写入到单个文件中. Asset writer 并不与一个特定的 asset 相关联, 但必须与要输出的文件相关联. 由于一个 asset writer 可以从多个来源获取数据, 所以需要为每个要写入的 track 创建对应的 AVAssetWriterInputarrow-up-right 对象. 每个AVAssetWriterInput对象接收 CMSampleBufferRefarrow-up-right 类型的数据, 如果想要添加 CVPixelBufferRefarrow-up-right 类型的数据, 可以使用 AVAssetWriterInputPixelBufferAdaptorarrow-up-right.

创建 AVAssetWriter

创建 AVAssetWriter 对象需要指定一个文件 URL 和文件格式. 下面的代码示例了如何初始化一个 AVAssetWriter 用来创建 QuickTime 电影.

设置 Asset Writer Inputs

要让 AVAssetWriter 能写入媒体数据, 必须至少设置一个 asset writer input. 例如要写入CMSampleBufferRef类型的数据, 需要使用AVAssetWriterInput. 下面的代码示例了将压缩的音频数据写入为 128 kbps 的 AAC 格式:

只有 asset writer 初始化时fileTypeAVFileTypeQuickTimeMoviearrow-up-right, 参数outputSettings才能为 nil, 意味着写入的文件格式为 QuickTime movie.

使用属性 metadataarrow-up-righttransformarrow-up-right 可以为指定的 track 设置 metadata 和 transform. 当输入源为 video track 时, 可以通过如下方式持有 video track 的原始 transform:

注意, 需要在开始写入之前设置这两个属性才会生效.

在写入文件时, 有时候可能会需要分配一个 pixel buffer, 这时可以使用AVAssetWriterInputPixelBufferAdaptor类. 为了提高效率, 可以直接使用 pixel buffer adaptor 提供的 pixel buffer pool. 下面的代码示例了创建了一个 pixel buffer 对象处理 RGB 色域:

注意, 所有的AVAssetWriterInputPixelBufferAdaptor对象都必须与一个 asset writer input 相关联 . 这个 asset writer input 对象必须接收AVMediaTypeVideo类型的数据.

写入媒体数据

当配置完 asset writer 之后, 就可以) 开始写入数据了. 调用方法 startWritingarrow-up-right 初始化写入过程. 然后调用方法 startSessionAtSourceTime:arrow-up-right 开启一个写入会话 (sample-writing session). Asset writer 的所有写入过程都通过这个 session 完成, 并且 sesion 的时间范围决定了源媒体数据中哪个时间范围内的数据会被写入到文件中. 例如, 只写入源数据的后一半的示例代码如下:

一般情况下, 方法 endSessionAtSourceTime:arrow-up-right 用来结束写入会话. 但是如果文件已经写入完毕, 则可以方法 finishWritingarrow-up-right 结束写入会话. 下面的代码示例了从一个输入源读取数据并写入所有读取到的数据:

重编码Assets

上面代码中的copyNextSampleBufferToWrite方法仅仅是一个存根 (stub). 这个 stub 需要实现一些逻辑用来返回要写入的CMSampleBufferRef对象. Sample buffers 可能来源于一个 asset reader output.

可以搭配使用 asset reader 和 asset writer 进行 asset 之间的转换. 相比于使用AVAssetExportSession, 使用这些对象可以更好的控制转换细节. 例如, 可以选择导出哪个 track, 可以指定导出的文件格式, 还可以指定导出的时间范围. 下面的代码片段示例了如何从一个 asset reader output 读取数据, 并使用 asset writer input 写入这些数据.

最终示例: 使用Asset Reader 和 Writer 对 Asset 进行重编码

下面的代码简要示例了使用 asset reader 和 writer 对一个 asset 中的第一个 video 和 audio track 进行重新编码并将结果数据写入到一个新文件中.

提示: 为了将注意力集中在核心代码上, 这份示例省略了某些内容.

初始化设置

在创建和配置 asset reader 和 writer 之前, 需要进行一些初始化设置. 首先需要为读写过程创建三个串行队列.

队列 mainSerializationQueue 用于 asset reader 和 writer 的启动, 停止和取消. 其他两个队列用于 output/input 的读取和写入.

接着, 加载 asset 中的 track, 并开始重编码.

剩下的工作就是实现取消的处理, 并实现三个自定义方法.

初始化 Asset Reader 和 Writer

自定义方法setupAssetReaderAndAssetWriter实现了 asset Reader 和 writer 的初始化和配置. 在这个示例中, audio 先被 asset reader 解压为 Linear PCM, 然后被 asset write 压缩为 128 kbps AAC. video 被 asset reader 解压为 YUV, 然后被 asset writer 压缩为 H.264:

重编码 Asset

方法startAssetReaderAndWriter负责读取和写入 asset:

在重编码过程中, 为了提升性能, 音频处理和视频处理在两个不同队列中进行. 但这两个队列在一个 dispatchGroup 中, 当每个队列的任务都完成后, 会调用readingAndWritingDidFinishSuccessfully,

处理编码结果

对重编码的结果进行处理并同步到 UI:

取消重编码

使用多个串行队列, 可以很轻松的取消对 asset 的重编码. 可以将下面的代码与 UI 上的 "取消" 按钮关联起来:

AVOutputSettingsAssistant介绍

AVOutputSettingsAssistantarrow-up-right 类的功能是为 asset reader 或 writer 创建设置信息. 这将简化初始化过程, 特别是在对一个高帧率的 H264 视频进行参数设置时. 下面的代码是AVOutputSettingsAssistant的使用示例:

Last updated