You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:23:15