持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
AVFoundation 是Apple iOS和OS X系统中用于处理基于时间的媒体数据的高级框架,通过开发所需的工具提供了强大的功能集,让开发者能够基于苹果平台创建当下最先进的媒体应用程序,其针对64位处理器设计,充分利用了多核硬件优势,会自动提供硬件加速操作,确保大部分设备能以最佳性能运行,是iOS开发接触音视频开发必学的框架之一。
参与掘金日新计划,持续记录AVFoundation学习,Demo学习地址,里面封装了一些工具类,可以直接使用,这篇文章主要讲述AVAssetReader的使用,并演示利用AVAssetReader、AVAssetWriter基本使用,其他类的相关用法可查看我的其他文章。
AVAssetReader
- AVAssetReader用于从AVAsset实例中读取媒体样本。通常会配置一个或多个AVAssetReaderOutput实例,通过copyNextSampleBuffer方法可以访问音频样本和视频帧。
- AVAssetReaderOutput是一个抽象类,系统提供了4个具体类供我们使用读取解码的媒体样本。
-
- AVAssetReaderTrackOutput 从AVAsset的单个AVAssetTrack中读取媒体数据
-
- AVAssetReaderAudioMixOutput 读取AVAsset的一个或多个AVAssetTracks的音频混合而成的音频样本
-
- AVAssetReaderVideoCompositionOutput 读取AVAsset的一个或多个AVAssetTracks中的帧合成到一起的视频帧
-
- AVAssetReaderSampleReferenceOutput 从AVAsset的单个AVAssetTrack中读取示例引用
- 这里需要注意,AVAssetReader只针对带有一个资源的样本,如果想要同时读取多个资源,需要用AVComposition
// 示例音频文件AVAssetTrack
let videoUrl: URL = Bundle.main.url(forResource: "hubblecast2", withExtension: "m4v")!
let asset: AVAsset = AVAsset(url: videoUrl)
let track: AVAssetTrack = asset.tracks(withMediaType: .video).first!
// 创建AVAssetReader,读取AVAsset实例
assetReader = try! AVAssetReader(asset: asset)
// 创建AVAssetReaderTrackOutput从资源的视频轨道读取样本,将视频帧解压缩为BGRA格式
let readerOutSettings: [String : Any] = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
let trackOutput: AVAssetReaderTrackOutput = AVAssetReaderTrackOutput(track: track, outputSettings: readerOutSettings)
// AVAssetReader添加AVAssetReaderTrackOutput
guard assetReader.canAdd(trackOutput) else { return }
// 添加输出到读取器、开始读取
assetReader.add(trackOutput)
assetReader.startReading()
- 读取每一帧
let samepleBuffer = trackOutput.copyNextSampleBuffer()
AVAssetWriter
它用于对媒体资源进行编码并将其写入到容器文件中,比如封装成一个MP4等音频视频文件。
- 由一个或多个AVAssCtWriterlnput对象配置,用于附加将包含要写入容器的媒体样本的CMSampleBuffer对象。
- AVAssetWriterinput被配置为可以处理指定的媒体类型,比如音频或视频,并且附加在其后的样本会在最终输出时生成一个独立的AVAssetTrack。
- AssetWriterInput默认接收CMSampelBufferRef格式的数据,可以通过AVAssetWriterInputPixelBufferAdaptor更改
- AvAsserWriter可以自动支持交叉媒体样本,可以同时写入视频音频Buffer
现在我们将上面Reader读取的Buffer重新封装成一个视频文件
// 创建AVAssetWriter AVAssetWriterInput
let outputPath = String.cq.documentsDirectory+"\("/testWriter").mp4"
let outputUrl: URL = URL(fileURLWithPath: outputPath)
try? FileManager.default.removeItem(at: outputUrl)
assetWriter = try! AVAssetWriter(outputURL: outputUrl, fileType: .mp4)
// 720p h264
let writeOutputSettings: [String: Any] = [AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: 1280,
AVVideoHeightKey: 720,
AVVideoCompressionPropertiesKey: [AVVideoMaxKeyFrameIntervalKey: 1,
AVVideoAverageBitRateKey: 10500000,
AVVideoProfileLevelKey: AVVideoProfileLevelH264Main31]]
let writerInput: AVAssetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: writeOutputSettings)
// AVAssetWriter添加AVAssetWriterInput
guard assetWriter.canAdd(writerInput) else { return }
assetWriter.add(writerInput)
assetWriter.startWriting()
// 处理写入
let dispathQueue: DispatchQueue = DispatchQueue(label: "writerQueue")
assetWriter.startSession(atSourceTime: CMTime.zero)
writerInput.requestMediaDataWhenReady(on: dispathQueue) {
print("开始写入")
var complete = false
var buffIndex = 1
// 如果可以写入更多的数据,继续写,如果不可以或者下一个buffer为空(或者有一个写入失败),则结束写 跳出循环
while writerInput.isReadyForMoreMediaData && !complete {
// copy一帧
let samepleBuffer = trackOutput.copyNextSampleBuffer()
if samepleBuffer != nil {
print("写入数据中...\(buffIndex)")
buffIndex += 1
let result: Bool = writerInput.append(samepleBuffer!)
complete = !result
} else {
// 添加操作已完成
writerInput.markAsFinished()
complete = true
}
}
if complete == true {
// 关闭写入会话
self.assetWriter.finishWriting {
let status: AVAssetWriter.Status = self.assetWriter.status
if status == .completed {
print("写入成功")
} else {
print("写入失败")
}
}
}
}
}