iOS平台编码音频流为Opus时出现无效帧大小错误
Opus编码无效帧大小错误的解决方案
问题现象
在实现音频流编码为Opus格式并通过WebSocket发送的逻辑时,持续触发以下错误:
Invalid frame size: 4800. Must be one of [120, 240, 480, 960, 1920, 2880]
问题根源
你在setupAudioEngine中指定了opusCompatibleBufferSize = 960,但AVAudioEngine并不保证严格按照该值返回音频缓冲区——系统会根据硬件性能、当前负载等实际情况调整返回的帧长度,导致出现4800这类不符合Opus要求的帧大小。
解决方案
需要实现帧缓冲队列,将接收到的零散音频数据累积,直到凑够Opus要求的有效帧大小再进行编码。具体修改步骤如下:
1. 添加帧缓冲属性
在AudioManager类中新增两个属性,用于缓存未达到编码要求的音频数据:
private var pendingBuffer: AVAudioPCMBuffer? // 缓存未凑够大小的音频帧 private let targetFrameSize: AVAudioFrameCount = 960 // 目标Opus有效帧大小
2. 重写bufferAudioData方法
替换原有逻辑,实现帧的累积、拆分与编码:
private func bufferAudioData(_ buffer: AVAudioPCMBuffer) { guard let encoder = opusEncoder else { print("Opus编码器未初始化") return } // 验证音频格式是否符合要求 if buffer.format.sampleRate != 48000 || buffer.format.channelCount != 1 { print("缓冲区格式不匹配:预期48000Hz单声道,实际\(buffer.format.sampleRate)Hz \(buffer.format.channelCount)声道") return } // 累积新接收的音频数据 if let pending = pendingBuffer { let totalFrames = pending.frameLength + buffer.frameLength guard let combinedBuffer = AVAudioPCMBuffer(pcmFormat: buffer.format, frameCapacity: totalFrames) else { print("无法创建合并缓冲区") pendingBuffer = nil return } pending.copy(to: combinedBuffer) buffer.copy(to: combinedBuffer, frameCount: buffer.frameLength, startingAt: pending.frameLength) pendingBuffer = combinedBuffer } else { pendingBuffer = buffer.copy() as? AVAudioPCMBuffer } guard let currentBuffer = pendingBuffer else { return } // 拆分出符合Opus要求的帧进行编码 while currentBuffer.frameLength >= targetFrameSize { guard let targetBuffer = AVAudioPCMBuffer(pcmFormat: currentBuffer.format, frameCapacity: targetFrameSize) else { print("无法创建目标大小的缓冲区") pendingBuffer = nil return } currentBuffer.copy(to: targetBuffer, frameCount: targetFrameSize) // 编码并发送数据 var opusData = Data() do { let bytesEncoded = try encoder.encode(targetBuffer, to: &opusData) print("编码成功,字节数:\(bytesEncoded)") if !opusData.isEmpty && canSendData { appManager?.webSocketManager.send(data: opusData) { print("Opus编码数据已发送") } } } catch let error as Opus.Error { print("编码失败:Opus错误 \(error.rawValue) - \(interpretOpusError(error))") } catch { print("编码失败:\(error)") } // 更新剩余未处理的音频帧 let remainingFrames = currentBuffer.frameLength - targetFrameSize if remainingFrames > 0 { guard let remainingBuffer = AVAudioPCMBuffer(pcmFormat: currentBuffer.format, frameCapacity: remainingFrames) else { pendingBuffer = nil return } currentBuffer.copy(to: remainingBuffer, frameCount: remainingFrames, startingAt: targetFrameSize) pendingBuffer = remainingBuffer } else { pendingBuffer = nil } } }
3. 清理停止录音时的缓冲
在stopRecording方法中添加缓冲清理逻辑,避免残留数据干扰下次录音:
func stopRecording() { print("停止录音...") canSendData = false appManager?.webSocketManager.send(string: "{\"command\": \"aq_stop\"}") { print("已发送停止录音指令") } audioEngine?.stop() pendingBuffer = nil // 清理剩余未处理的缓冲数据 print("录音已停止") }
关键说明
- Opus对输入帧大小有严格要求,48000Hz采样率下仅支持报错信息中列出的几个值
- AVAudioEngine的Tap返回的缓冲区大小是建议值,系统可动态调整,因此必须自行处理帧的累积与拆分
- 选择960作为目标帧大小是平衡延迟和编码效率的常用方案,你也可以根据需求换成其他有效值
内容的提问来源于stack exchange,提问作者narner
相关产品推荐
相关产品推荐

