如何使用Swift将加载到AVAsset中的视频音轨写入AVAudioPCMBuffer
Swift 实现从AVAsset提取音频轨道写入AVAudioPCMBuffer
实现逻辑
核心依赖AVFoundation框架原生API实现,无需调用C语言接口,流程如下:
- 通过
AVAssetReader读取asset中的音频轨道 - 配置线性PCM格式的轨道输出,与
AVAudioPCMBuffer格式兼容 - 循环读取
CMSampleBuffer并将数据写入目标AVAudioPCMBuffer
完整实现代码
import AVFoundation func readAudioBuffer(from asset: AVAsset) async throws -> AVAudioPCMBuffer { // 读取asset的首个音频轨道 guard let audioTrack = try await asset.loadTracks(withMediaType: .audio).first else { throw NSError(domain: "AudioReaderError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Asset无可用音频轨道"]) } // 初始化资源读取器 let assetReader = try AVAssetReader(asset: asset) // 配置输出为兼容AVAudioPCMBuffer的线性PCM格式 let outputSettings: [String: Any] = [ AVFormatIDKey: kAudioFormatLinearPCM, AVSampleRateKey: 44100, AVNumberOfChannelsKey: 2, AVLinearPCMBitDepthKey: 16, AVLinearPCMIsBigEndianKey: false, AVLinearPCMIsFloatKey: false, AVLinearPCMIsNonInterleavedKey: false ] let trackOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: outputSettings) guard assetReader.canAdd(trackOutput) else { throw NSError(domain: "AudioReaderError", code: -2, userInfo: [NSLocalizedDescriptionKey: "无法添加音频轨道输出"]) } assetReader.add(trackOutput) // 启动读取 guard assetReader.startReading() else { throw NSError(domain: "AudioReaderError", code: -3, userInfo: [NSLocalizedDescriptionKey: "读取器启动失败: \(assetReader.error?.localizedDescription ?? "未知错误")"]) } // 初始化目标PCMBuffer let audioFormat = AVAudioFormat(settings: outputSettings)! let duration = try await asset.load(.duration) let totalFrames = AVAudioFrameCount(duration.seconds * 44100) guard let buffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: totalFrames) else { throw NSError(domain: "AudioReaderError", code: -4, userInfo: [NSLocalizedDescriptionKey: "PCMBuffer初始化失败"]) } // 循环读取采样数据写入buffer while assetReader.status == .reading { guard let sampleBuffer = trackOutput.copyNextSampleBuffer() else { break } defer { CMSampleBufferInvalidate(sampleBuffer) } guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer) else { continue } let dataLength = CMBlockBufferGetDataLength(blockBuffer) let targetOffset = Int(buffer.frameLength) * Int(audioFormat.streamDescription.pointee.mBytesPerFrame) // 将采样数据写入buffer CMBlockBufferCopyDataBytes( blockBuffer, atOffset: 0, dataLength: dataLength, destination: buffer.audioBufferList.pointee.mBuffers.mData!.advanced(by: targetOffset) ) buffer.frameLength += AVAudioFrameCount(dataLength / Int(audioFormat.streamDescription.pointee.mBytesPerFrame)) } // 处理读取异常 if assetReader.status == .failed { throw assetReader.error! } return buffer }
注意事项
- 示例代码使用iOS 15+推出的
AVAsset异步加载API,如需兼容更低版本,可替换为传统的闭包式加载接口 - 音频输出格式可根据实际需求调整,需保证输出格式与
AVAudioPCMBuffer的格式完全匹配,避免写入失败 - 大体积音视频的读取操作建议放在子线程执行,避免阻塞主线程
- 读取失败时可通过
AVAssetReader.error获取具体错误信息定位问题
内容的提问来源于stack exchange,提问作者Hoerbarium
相关产品推荐
相关产品推荐

