使用AVAudioEngine时无法从URL创建AVAudioFile的问题求助
问题解决方案
问题根源
AVAudioFile(forReading:) 仅支持本地文件URL,不直接支持远程HTTP/HTTPS URL,这就是你遇到错误码2003334207(对应kExtAudioFileError_InvalidURL)的核心原因。
解决方案1:先下载音频到本地再播放
通过URLSession将远程音频下载到本地目录,再使用本地URL初始化AVAudioFile:
import UIKit import AVFoundation class ViewController: UIViewController { let audioEngine = AVAudioEngine() let audioPlayerNode = AVAudioPlayerNode() override func viewDidLoad() { super.viewDidLoad() streamAudioFromURL(urlString: "https://samplelib.com/lib/preview/mp3/sample-9s.mp3") } func streamAudioFromURL(urlString: String) { guard let url = URL(string: urlString) else { print("无效URL") return } // 下载音频到临时目录 let task = URLSession.shared.downloadTask(with: url) { [weak self] localURL, response, error in guard let self = self, let localURL = localURL else { print("下载失败: \(error?.localizedDescription ?? "未知错误")") return } // 将临时文件移动到可持久化位置(避免系统自动清理) let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let destinationURL = documentsDir.appendingPathComponent(response?.suggestedFilename ?? "temp_audio.mp3") do { if FileManager.default.fileExists(atPath: destinationURL.path) { try FileManager.default.removeItem(at: destinationURL) } try FileManager.default.moveItem(at: localURL, to: destinationURL) // 切换到主线程处理音频引擎操作 DispatchQueue.main.async { self.playLocalAudio(at: destinationURL) } } catch { print("文件移动失败: \(error.localizedDescription)") } } task.resume() } func playLocalAudio(at url: URL) { do { let audioFile = try AVAudioFile(forReading: url) // 重置音频引擎状态 if audioEngine.isRunning { audioPlayerNode.stop() audioEngine.stop() audioEngine.reset() } audioEngine.attach(audioPlayerNode) audioEngine.connect(audioPlayerNode, to: audioEngine.outputNode, format: audioFile.processingFormat) audioPlayerNode.scheduleFile(audioFile, at: nil, completionCallbackType: .dataPlayedBack) { _ in // 播放完成后可删除临时文件 try? FileManager.default.removeItem(at: url) } try audioEngine.start() audioPlayerNode.play() } catch { print("音频播放失败: \(error.localizedDescription)") } } }
解决方案2:流式播放(无需完全下载)
如果不需要本地保存音频,可使用AVAsset配合AVAudioEngine实现流式播放,无需等待完整下载:
import UIKit import AVFoundation class ViewController: UIViewController { let audioEngine = AVAudioEngine() let audioPlayerNode = AVAudioPlayerNode() var assetReader: AVAssetReader? var assetReaderOutput: AVAssetReaderTrackOutput? override func viewDidLoad() { super.viewDidLoad() streamAudioFromURL(urlString: "https://samplelib.com/lib/preview/mp3/sample-9s.mp3") } func streamAudioFromURL(urlString: String) { guard let url = URL(string: urlString) else { print("无效URL") return } let asset = AVURLAsset(url: url) guard let audioTrack = asset.tracks(withMediaType: .audio).first else { print("音频轨道不存在") return } do { assetReader = try AVAssetReader(asset: asset) let outputSettings: [String: Any] = [ AVFormatIDKey: kAudioFormatLinearPCM, AVLinearPCMBitDepthKey: 16, AVLinearPCMIsBigEndianKey: false, AVLinearPCMIsFloatKey: false ] assetReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: outputSettings) guard let reader = assetReader, let readerOutput = assetReaderOutput else { print("初始化AssetReader失败") return } if reader.canAdd(readerOutput) { reader.add(readerOutput) } // 配置音频引擎 audioEngine.attach(audioPlayerNode) let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: audioTrack.nominalSampleRate, channels: audioTrack.channelCount, interleaved: true)! audioEngine.connect(audioPlayerNode, to: audioEngine.outputNode, format: format) try audioEngine.start() audioPlayerNode.play() // 开始读取并推送音频数据 reader.startReading() processAssetReaderOutput() } catch { print("流式播放初始化失败: \(error.localizedDescription)") } } func processAssetReaderOutput() { guard let reader = assetReader, let readerOutput = assetReaderOutput else { return } while reader.status == .reading { if let sampleBuffer = readerOutput.copyNextSampleBuffer() { let buffer = AVAudioPCMBuffer(pcmFormat: audioPlayerNode.outputFormat(forBus: 0), sampleBuffer: sampleBuffer)! audioPlayerNode.scheduleBuffer(buffer, completionHandler: nil) CMSampleBufferInvalidate(sampleBuffer) } else { Thread.sleep(forTimeInterval: 0.01) } } // 处理读取完成或错误状态 switch reader.status { case .completed: print("播放完成") case .failed: print("读取失败: \(reader.error?.localizedDescription ?? "未知错误")") default: break } } }
额外注意事项
- 若使用非HTTPS的音频URL,需在
Info.plist中添加网络权限配置:<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> - 音频引擎的核心操作建议在主线程执行,避免线程安全问题。
- 临时文件使用完毕后及时删除,避免占用设备存储空间。
内容的提问来源于stack exchange,提问作者Jacob Cavin
相关产品推荐
相关产品推荐

