You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
3.9 KiB
151 lines
3.9 KiB
import Foundation
|
|
import AVFoundation
|
|
import YbridOpus
|
|
|
|
@available(iOS 13.0, *)
|
|
class SwiftOpusAudioProcessor: NSObject {
|
|
|
|
protocol AudioDataCallback: AnyObject {
|
|
func onAudioDataReceived(data: Data)
|
|
}
|
|
|
|
private var isDecodingActive = false
|
|
private weak var callback: AudioDataCallback?
|
|
|
|
// Opus 解码器相关 - 使用 YbridOpus C API
|
|
private var decoder: OpaquePointer?
|
|
private var sampleRate: Int32 = 16000
|
|
private var channels: Int32 = 1
|
|
private let maxFrameSize: Int32 = 5760 // 120 ms @48 kHz 的最大值
|
|
|
|
override init() {
|
|
super.init()
|
|
}
|
|
|
|
deinit {
|
|
cleanup()
|
|
}
|
|
|
|
private func initializeOpusDecoder() -> Bool {
|
|
if decoder != nil {
|
|
return true
|
|
}
|
|
|
|
var error: Int32 = 0
|
|
decoder = opus_decoder_create(sampleRate, channels, &error)
|
|
|
|
guard error == OPUS_OK, decoder != nil else {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
private func destroyOpusDecoder() {
|
|
if let decoder = decoder {
|
|
opus_decoder_destroy(decoder)
|
|
self.decoder = nil
|
|
}
|
|
}
|
|
|
|
func setCallback(_ callback: AudioDataCallback?) {
|
|
self.callback = callback
|
|
}
|
|
|
|
func startDecoding(sampleRate: Int32 = 16000, channels: Int32 = 1, frameSize: Int32 = 640) -> Bool {
|
|
if isDecodingActive {
|
|
return true
|
|
}
|
|
|
|
self.sampleRate = sampleRate
|
|
self.channels = channels
|
|
|
|
guard initializeOpusDecoder() else {
|
|
return false
|
|
}
|
|
|
|
isDecodingActive = true
|
|
|
|
return true
|
|
}
|
|
|
|
func stopDecoding() -> Bool {
|
|
if !isDecodingActive {
|
|
return false
|
|
}
|
|
|
|
isDecodingActive = false
|
|
destroyOpusDecoder()
|
|
|
|
return true
|
|
}
|
|
|
|
func processAudioData(_ data: Data) {
|
|
guard isDecodingActive, let decoder = decoder, data.count > 0 else {
|
|
return
|
|
}
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
self?.decodeOpusPacket(data)
|
|
}
|
|
}
|
|
|
|
// 使用简单稳定的 Opus 解码方法
|
|
private func decodeOpusPacket(_ packet: Data) {
|
|
guard let decoder = decoder else { return }
|
|
|
|
// 使用简单的直接解码方式
|
|
let maxSamples = Int(maxFrameSize)
|
|
var pcmBuffer = [opus_int16](repeating: 0, count: maxSamples * Int(channels))
|
|
|
|
let decodedSamples = packet.withUnsafeBytes { packetPtr in
|
|
let uint8Ptr = packetPtr.bindMemory(to: UInt8.self).baseAddress
|
|
return opus_decode(
|
|
decoder,
|
|
uint8Ptr,
|
|
opus_int32(packet.count),
|
|
&pcmBuffer,
|
|
Int32(maxSamples),
|
|
0
|
|
)
|
|
}
|
|
|
|
guard decodedSamples > 0 else {
|
|
return
|
|
}
|
|
|
|
// 提取解码的音频数据
|
|
let samplesPerChannel = Int(decodedSamples)
|
|
let totalSamples = samplesPerChannel * Int(channels)
|
|
let outputPCM = Array(pcmBuffer[0..<totalSamples])
|
|
|
|
if !outputPCM.isEmpty {
|
|
processPCMData(outputPCM)
|
|
}
|
|
}
|
|
|
|
private func processPCMData(_ pcmData: [opus_int16]) {
|
|
// 转换为 Data
|
|
let data = Data(bytes: pcmData, count: pcmData.count * MemoryLayout<opus_int16>.size)
|
|
|
|
// 回调通知
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.callback?.onAudioDataReceived(data: data)
|
|
}
|
|
}
|
|
|
|
func isDecoding() -> Bool {
|
|
return isDecodingActive
|
|
}
|
|
|
|
func cleanup() {
|
|
stopDecoding()
|
|
}
|
|
|
|
// MARK: - 公共接口
|
|
|
|
/// 获取当前配置信息
|
|
func getCurrentConfig() -> (sampleRate: Int32, channels: Int32) {
|
|
return (sampleRate, channels)
|
|
}
|
|
}
|