【AVFoundation】AVAssetReader、AVAssetWriter基本使用

1,376 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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("写入失败")
                    }
                }
            }
        }    
    }