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.
696 lines
24 KiB
696 lines
24 KiB
import Foundation
|
|
import AVFoundation
|
|
import MicrosoftCognitiveServicesSpeech
|
|
|
|
/// Azure语音识别辅助类,支持麦克风和外部音频源的单次和连续语音识别
|
|
class AzureAsrHelper {
|
|
private let tag = "AzureAsrHelper"
|
|
|
|
// 核心组件
|
|
private var speechConfig: SPXSpeechConfiguration?
|
|
private var recognizer: SPXSpeechRecognizer?
|
|
private var audioConfig: SPXAudioConfiguration?
|
|
|
|
// 状态管理
|
|
private var isContinuousRecognitionActive = false
|
|
|
|
// 配置参数
|
|
private var currentLanguage = "zh-CN"
|
|
private var supportedLanguages = ["zh-CN"]
|
|
private var isAutoDetectLanguage = false
|
|
private var subscriptionKey = ""
|
|
private var region = ""
|
|
|
|
// 音频源配置
|
|
private var audioSourceType = AudioSourceType.microphone
|
|
|
|
// 音频处理
|
|
private var microphoneStream: MicrophoneStream?
|
|
private var externalAudioStream: ExternalAudioPullStream?
|
|
|
|
// 连续识别回调
|
|
private var continuousRecognizeCallback: ContinuousRecognizeCallback?
|
|
|
|
/// 应用上下文
|
|
private let context: Any
|
|
|
|
/// 音频来源类型
|
|
enum AudioSourceType {
|
|
/// 使用设备麦克风
|
|
case microphone
|
|
|
|
/// 使用外部提供的音频数据
|
|
case external
|
|
}
|
|
|
|
/// 一次性识别回调
|
|
protocol RecognizeCallback {
|
|
/// 识别结果回调
|
|
/// - Parameters:
|
|
/// - text: 识别文本
|
|
/// - detectedLanguage: 检测到的语言
|
|
func onResult(text: String, detectedLanguage: String)
|
|
|
|
/// 识别错误回调
|
|
/// - Parameter error: 错误信息
|
|
func onError(error: String)
|
|
}
|
|
|
|
/// 连续识别回调
|
|
protocol ContinuousRecognizeCallback {
|
|
/// 识别中回调
|
|
/// - Parameters:
|
|
/// - recognizing: 识别中的文本
|
|
/// - detectedLanguage: 检测到的语言
|
|
func onRecognizing(recognizing: String, detectedLanguage: String)
|
|
|
|
/// 识别结果回调
|
|
/// - Parameters:
|
|
/// - text: 识别文本
|
|
/// - detectedLanguage: 检测到的语言
|
|
func onResult(text: String, detectedLanguage: String)
|
|
|
|
/// 会话开始回调
|
|
func onSessionStarted()
|
|
|
|
/// 会话结束回调
|
|
func onSessionStopped()
|
|
|
|
/// 识别取消回调
|
|
/// - Parameters:
|
|
/// - reason: 取消原因
|
|
/// - errorDetails: 错误详情
|
|
func onCanceled(reason: String, errorDetails: String)
|
|
|
|
/// 识别错误回调
|
|
/// - Parameter error: 错误信息
|
|
func onError(error: String)
|
|
}
|
|
|
|
/// 初始化Azure语音识别辅助类
|
|
/// - Parameter context: 应用上下文
|
|
init(_ context: Any) {
|
|
self.context = context
|
|
}
|
|
|
|
/// 初始化Azure语音服务
|
|
/// - Parameters:
|
|
/// - subscriptionKey: Azure 订阅密钥
|
|
/// - region: Azure 区域
|
|
/// - supportedLanguages: 支持的语言数组,默认为["zh-CN"]
|
|
/// - audioSourceType: 音频源类型,默认为麦克风
|
|
/// - Returns: 初始化是否成功
|
|
func initialize(
|
|
subscriptionKey: String,
|
|
region: String,
|
|
supportedLanguages: [String] = ["zh-CN"],
|
|
audioSourceType: AudioSourceType = .microphone
|
|
) -> Bool {
|
|
do {
|
|
print("\(tag): 初始化 Azure 语音服务, 音频源类型: \(audioSourceType)")
|
|
|
|
// 检查配置是否为空
|
|
if subscriptionKey.isEmpty || region.isEmpty {
|
|
print("\(tag): Azure 配置信息不完整")
|
|
return false
|
|
}
|
|
|
|
// 释放之前的资源
|
|
dispose()
|
|
|
|
// 保存配置
|
|
self.subscriptionKey = subscriptionKey
|
|
self.region = region
|
|
self.audioSourceType = audioSourceType
|
|
|
|
// 设置语言
|
|
if !supportedLanguages.isEmpty {
|
|
self.supportedLanguages = supportedLanguages
|
|
}
|
|
|
|
// 根据支持的语言数量决定是否启用自动语言检测
|
|
self.isAutoDetectLanguage = supportedLanguages.count >= 2
|
|
|
|
// 如果只有一种语言,设置为当前语言
|
|
if !isAutoDetectLanguage && !supportedLanguages.isEmpty {
|
|
self.currentLanguage = supportedLanguages[0]
|
|
}
|
|
|
|
// 创建语音配置
|
|
speechConfig = try SPXSpeechConfiguration(subscription: subscriptionKey, region: region)
|
|
|
|
if isAutoDetectLanguage {
|
|
// 启用语言检测模式
|
|
try speechConfig?.setPropertyTo("Continuous", byId: SPXPropertyId.speechServiceConnection_languageIdMode)
|
|
} else {
|
|
// 设置指定的识别语言
|
|
speechConfig?.speechRecognitionLanguage = currentLanguage
|
|
}
|
|
|
|
// 设置音频配置
|
|
return setupRecognizer()
|
|
|
|
} catch {
|
|
print("\(tag): 初始化失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 设置识别器
|
|
private func setupRecognizer() -> Bool {
|
|
do {
|
|
// 清理旧的识别器
|
|
recognizer = nil
|
|
|
|
// 设置音频配置
|
|
switch audioSourceType {
|
|
case .microphone:
|
|
// 检查是否连接了耳机
|
|
let isHeadsetConnected = isHeadphonesConnected()
|
|
|
|
if !isHeadsetConnected {
|
|
print("\(tag): 未检测到耳机,使用麦克风输入")
|
|
// 使用拉流模式进行回音消除
|
|
setupMicrophoneStream()
|
|
} else {
|
|
print("\(tag): 检测到耳机连接,使用默认麦克风输入")
|
|
// 使用默认麦克风输入
|
|
audioConfig = SPXAudioConfiguration()
|
|
}
|
|
|
|
case .external:
|
|
// 创建拉流
|
|
setupExternalAudioStream()
|
|
print("\(tag): 使用外部音频源(拉流模式)")
|
|
}
|
|
|
|
// 创建识别器
|
|
if isAutoDetectLanguage {
|
|
let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(sourceLanguages: supportedLanguages)
|
|
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, autoDetectSourceLanguageConfiguration: autoDetectConfig, audioConfiguration: audioConfig!)
|
|
} else {
|
|
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!)
|
|
}
|
|
|
|
print("\(tag): Azure 语音服务初始化成功")
|
|
return true
|
|
} catch {
|
|
print("\(tag): 创建识别器失败: \(error.localizedDescription)")
|
|
stopAudioProcessing()
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 设置麦克风流 - 使用拉流方式
|
|
private func setupMicrophoneStream() {
|
|
do {
|
|
// 创建麦克风流
|
|
microphoneStream = MicrophoneStream()
|
|
|
|
// 创建音频配置
|
|
audioConfig = try SPXAudioConfiguration(streamInput: microphoneStream!)
|
|
|
|
print("\(tag): 已设置麦克风流(拉流模式)")
|
|
} catch {
|
|
print("\(tag): 设置麦克风流失败: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
/// 设置外部音频流 - 使用拉流方式
|
|
private func setupExternalAudioStream() {
|
|
do {
|
|
// 创建外部音频拉流对象
|
|
externalAudioStream = ExternalAudioPullStream()
|
|
|
|
// 创建音频配置
|
|
audioConfig = try SPXAudioConfiguration(streamInput: externalAudioStream!)
|
|
|
|
print("\(tag): 已设置外部音频流(拉流模式)")
|
|
} catch {
|
|
print("\(tag): 设置外部音频流失败: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
/// 向音频流写入音频数据
|
|
/// 仅当音频源设置为external时有效
|
|
/// - Parameter data: 音频数据字节数组
|
|
func pushAudioData(_ data: Data) {
|
|
if audioSourceType != .external {
|
|
print("\(tag): 当前未使用外部音频源,忽略推送的音频数据")
|
|
return
|
|
}
|
|
|
|
// 使用拉流模式,将数据推入队列
|
|
externalAudioStream?.pushAudio(data)
|
|
}
|
|
|
|
/// 执行一次性语音识别
|
|
/// - Parameter callback: 识别结果回调
|
|
func recognizeOnce(callback: RecognizeCallback) {
|
|
if speechConfig == nil {
|
|
callback.onError(error: "语音服务未初始化")
|
|
return
|
|
}
|
|
|
|
// 确保不在连续识别中
|
|
if isContinuousRecognitionActive {
|
|
_ = stopContinuousRecognition()
|
|
}
|
|
|
|
// 重置识别器
|
|
if !setupRecognizer() {
|
|
callback.onError(error: "重置识别器失败")
|
|
return
|
|
}
|
|
|
|
// 启动音频处理
|
|
startAudioProcessing()
|
|
|
|
// 执行同步识别
|
|
do {
|
|
let result = try recognizer?.recognizeOnce()
|
|
|
|
// 停止音频处理
|
|
stopAudioProcessing()
|
|
|
|
if let result = result, result.reason == .recognizedSpeech {
|
|
let resultText = result.text ?? ""
|
|
var detectedLanguage = supportedLanguages[0]
|
|
|
|
// 尝试获取检测到的语言
|
|
if isAutoDetectLanguage, let autoDetectResult = result.properties?.getPropertyByName("SourceLanguage") {
|
|
detectedLanguage = autoDetectResult
|
|
}
|
|
|
|
callback.onResult(text: resultText, detectedLanguage: detectedLanguage)
|
|
} else {
|
|
callback.onError(error: "未能识别语音")
|
|
}
|
|
} catch {
|
|
stopAudioProcessing()
|
|
callback.onError(error: "识别异常: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
/// 开始连续语音识别
|
|
/// - Parameter callback: 连续识别结果回调
|
|
/// - Returns: 是否成功开始识别
|
|
func startContinuousRecognition(_ callback: ContinuousRecognizeCallback) -> Bool {
|
|
if speechConfig == nil {
|
|
callback.onError(error: "语音服务未初始化")
|
|
return false
|
|
}
|
|
|
|
if isContinuousRecognitionActive {
|
|
return true
|
|
}
|
|
|
|
// 重置识别器
|
|
if !setupRecognizer() {
|
|
callback.onError(error: "重置识别器失败")
|
|
return false
|
|
}
|
|
|
|
// 保存回调
|
|
continuousRecognizeCallback = callback
|
|
|
|
do {
|
|
// 设置各种事件监听
|
|
setupEventListeners(callback)
|
|
|
|
// 启动音频处理
|
|
startAudioProcessing()
|
|
|
|
// 开始连续识别
|
|
try recognizer?.startContinuousRecognition()
|
|
isContinuousRecognitionActive = true
|
|
|
|
return true
|
|
} catch {
|
|
isContinuousRecognitionActive = false
|
|
stopAudioProcessing()
|
|
callback.onError(error: "启动连续识别失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 设置事件监听器
|
|
private func setupEventListeners(_ callback: ContinuousRecognizeCallback) {
|
|
guard let recognizer = recognizer else { return }
|
|
|
|
// 识别中事件
|
|
recognizer.addRecognizing { [weak self] _, event in
|
|
guard let self = self else { return }
|
|
|
|
var detectedLanguage = ""
|
|
if self.isAutoDetectLanguage, let lang = event.result.properties?.getPropertyByName("SourceLanguage") {
|
|
detectedLanguage = lang
|
|
}
|
|
|
|
print("\(self.tag): 识别中: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
|
|
callback.onRecognizing(recognizing: event.result.text ?? "", detectedLanguage: detectedLanguage)
|
|
}
|
|
|
|
// 识别完成事件
|
|
recognizer.addRecognized { [weak self] _, event in
|
|
guard let self = self else { return }
|
|
|
|
if event.result.reason == .recognizedSpeech {
|
|
var detectedLanguage = ""
|
|
if self.isAutoDetectLanguage, let lang = event.result.properties?.getPropertyByName("SourceLanguage") {
|
|
detectedLanguage = lang
|
|
}
|
|
|
|
print("\(self.tag): 识别完成: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
|
|
callback.onResult(text: event.result.text ?? "", detectedLanguage: detectedLanguage)
|
|
}
|
|
}
|
|
|
|
// 会话开始事件
|
|
recognizer.addSessionStarted { [weak self] _, _ in
|
|
guard let self = self else { return }
|
|
|
|
print("\(self.tag): 识别会话已开始")
|
|
callback.onSessionStarted()
|
|
}
|
|
|
|
// 会话结束事件
|
|
recognizer.addSessionStopped { [weak self] _, _ in
|
|
guard let self = self else { return }
|
|
|
|
print("\(self.tag): 识别会话已结束")
|
|
self.isContinuousRecognitionActive = false
|
|
self.stopAudioProcessing()
|
|
callback.onSessionStopped()
|
|
}
|
|
|
|
// 取消事件
|
|
recognizer.addCanceled { [weak self] _, event in
|
|
guard let self = self else { return }
|
|
|
|
let errorDetails = event.errorDetails ?? "未知错误"
|
|
let reason = String(describing: event.reason)
|
|
|
|
print("\(self.tag): 识别取消: \(errorDetails)")
|
|
self.isContinuousRecognitionActive = false
|
|
self.stopAudioProcessing()
|
|
|
|
callback.onCanceled(reason: reason, errorDetails: errorDetails)
|
|
callback.onError(error: "识别取消: \(errorDetails)") // 兼容旧接口
|
|
}
|
|
}
|
|
|
|
/// 停止连续语音识别
|
|
/// - Returns: 是否成功停止
|
|
func stopContinuousRecognition() -> Bool {
|
|
if speechConfig == nil {
|
|
print("\(tag): 语音服务未初始化")
|
|
return false
|
|
}
|
|
|
|
if !isContinuousRecognitionActive {
|
|
print("\(tag): 未进行连续识别,忽略停止请求")
|
|
return true
|
|
}
|
|
|
|
do {
|
|
print("\(tag): 停止连续语音识别")
|
|
|
|
if recognizer == nil {
|
|
print("\(tag): 识别器为空,重置状态")
|
|
isContinuousRecognitionActive = false
|
|
return true
|
|
}
|
|
|
|
// 停止连续识别
|
|
try recognizer?.stopContinuousRecognition()
|
|
|
|
// 停止音频处理
|
|
stopAudioProcessing()
|
|
|
|
// 等待一段时间以确保资源正确释放
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
|
self?.isContinuousRecognitionActive = false
|
|
}
|
|
|
|
print("\(tag): 连续识别停止指令已发送")
|
|
return true
|
|
} catch {
|
|
// 强制重置状态
|
|
isContinuousRecognitionActive = false
|
|
print("\(tag): 停止连续识别失败: \(error.localizedDescription)")
|
|
|
|
// 停止音频处理
|
|
stopAudioProcessing()
|
|
|
|
// 尝试强制关闭识别器
|
|
recognizer = nil
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 检查连续识别是否活跃
|
|
/// - Returns: 是否活跃
|
|
func isContinuousRecognitionActive() -> Bool {
|
|
return isContinuousRecognitionActive
|
|
}
|
|
|
|
/// 释放所有资源
|
|
func dispose() {
|
|
// 如果正在进行连续识别,先停止
|
|
if isContinuousRecognitionActive {
|
|
_ = stopContinuousRecognition()
|
|
}
|
|
|
|
// 停止音频处理
|
|
stopAudioProcessing()
|
|
|
|
// 清理所有资源
|
|
recognizer = nil
|
|
speechConfig = nil
|
|
audioConfig = nil
|
|
microphoneStream = nil
|
|
externalAudioStream = nil
|
|
|
|
// 重置状态
|
|
isContinuousRecognitionActive = false
|
|
continuousRecognizeCallback = nil
|
|
}
|
|
|
|
/// 启动音频处理
|
|
private func startAudioProcessing() {
|
|
switch audioSourceType {
|
|
case .microphone:
|
|
// 拉流模式不需要额外启动,SDK会自动拉取数据
|
|
break
|
|
case .external:
|
|
// 外部音频数据模式下不需要启动处理,等待外部调用pushAudioData
|
|
break
|
|
}
|
|
}
|
|
|
|
/// 停止音频处理
|
|
private func stopAudioProcessing() {
|
|
microphoneStream?.close()
|
|
microphoneStream = nil
|
|
|
|
externalAudioStream?.close()
|
|
externalAudioStream = nil
|
|
}
|
|
|
|
/// 检查是否连接了耳机
|
|
private func isHeadphonesConnected() -> Bool {
|
|
let audioSession = AVAudioSession.sharedInstance()
|
|
let outputs = audioSession.currentRoute.outputs
|
|
|
|
// 检查是否存在耳机类型的输出
|
|
let headphoneTypes: [AVAudioSession.Port] = [
|
|
.headphones, .bluetoothA2DP, .bluetoothHFP, .bluetoothLE
|
|
]
|
|
|
|
for output in outputs {
|
|
if headphoneTypes.contains(output.portType) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/// 麦克风流 - 拉流模式
|
|
/// 实现SPXPullAudioInputStreamDelegate,为Azure SDK提供音频数据
|
|
class MicrophoneStream: NSObject, SPXPullAudioInputStreamDelegate {
|
|
private let tag = "MicrophoneStream"
|
|
private var audioEngine: AVAudioEngine?
|
|
private var audioBuffer = Data()
|
|
private let bufferLock = NSLock()
|
|
|
|
override init() {
|
|
super.init()
|
|
setupAudioEngine()
|
|
}
|
|
|
|
private func setupAudioEngine() {
|
|
do {
|
|
print("\(tag): 初始化音频引擎")
|
|
|
|
// 创建音频引擎
|
|
audioEngine = AVAudioEngine()
|
|
|
|
// 设置音频会话
|
|
let audioSession = AVAudioSession.sharedInstance()
|
|
try audioSession.setCategory(.record, mode: .default)
|
|
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
|
|
|
|
// 获取输入节点
|
|
guard let inputNode = audioEngine?.inputNode else {
|
|
print("\(tag): 无法获取输入节点")
|
|
return
|
|
}
|
|
|
|
// 创建16kHz, 16bit, 单声道格式
|
|
let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: true)
|
|
|
|
// 设置音频处理回调
|
|
let bufferSize = 4096
|
|
inputNode.installTap(onBus: 0, bufferSize: UInt32(bufferSize), format: format) { [weak self] (buffer, time) in
|
|
guard let self = self else { return }
|
|
|
|
// 将音频数据转换为Data
|
|
let audioBuffer = buffer.int16ChannelData?[0]
|
|
let audioBufferSize = Int(buffer.frameLength * buffer.format.streamDescription.pointee.mBytesPerFrame)
|
|
|
|
if let audioBuffer = audioBuffer {
|
|
let data = Data(bytes: audioBuffer, count: audioBufferSize)
|
|
self.bufferLock.lock()
|
|
self.audioBuffer.append(data)
|
|
self.bufferLock.unlock()
|
|
}
|
|
}
|
|
|
|
// 启动音频引擎
|
|
audioEngine?.prepare()
|
|
try audioEngine?.start()
|
|
|
|
print("\(tag): 音频引擎启动成功")
|
|
} catch {
|
|
print("\(tag): 设置音频引擎失败: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
// SPXPullAudioInputStreamDelegate协议方法
|
|
func read(_ buffer: UnsafeMutablePointer<UInt8>, size: UInt32) -> Int32 {
|
|
bufferLock.lock()
|
|
defer { bufferLock.unlock() }
|
|
|
|
let maxSize = Int(size)
|
|
|
|
if audioBuffer.count == 0 {
|
|
// 没有数据可读
|
|
return 0
|
|
}
|
|
|
|
let readSize = min(audioBuffer.count, maxSize)
|
|
audioBuffer.copyBytes(to: buffer, from: 0..<readSize)
|
|
|
|
if readSize < audioBuffer.count {
|
|
// 保留未读的数据
|
|
audioBuffer = audioBuffer.subdata(in: readSize..<audioBuffer.count)
|
|
} else {
|
|
// 清空缓冲区
|
|
audioBuffer = Data()
|
|
}
|
|
|
|
return Int32(readSize)
|
|
}
|
|
|
|
// 关闭音频资源
|
|
func close() {
|
|
print("\(tag): 关闭音频引擎")
|
|
|
|
// 停止音频引擎
|
|
if let inputNode = audioEngine?.inputNode {
|
|
inputNode.removeTap(onBus: 0)
|
|
}
|
|
|
|
audioEngine?.stop()
|
|
audioEngine = nil
|
|
|
|
// 关闭音频会话
|
|
do {
|
|
try AVAudioSession.sharedInstance().setActive(false)
|
|
} catch {
|
|
print("\(tag): 关闭音频会话失败: \(error.localizedDescription)")
|
|
}
|
|
|
|
// 清空缓冲区
|
|
bufferLock.lock()
|
|
audioBuffer = Data()
|
|
bufferLock.unlock()
|
|
|
|
print("\(tag): 音频资源已释放")
|
|
}
|
|
}
|
|
|
|
/// 外部音频拉流
|
|
/// 实现SPXPullAudioInputStreamDelegate,将外部推送的音频数据转换为SDK可拉取的形式
|
|
class ExternalAudioPullStream: NSObject, SPXPullAudioInputStreamDelegate {
|
|
private let tag = "ExternalAudioPullStream"
|
|
private let audioQueue = DispatchQueue(label: "com.yunqiinnovation.azure_speech.externalAudio")
|
|
private var audioBuffer = Data()
|
|
private let bufferLock = NSLock()
|
|
private var isClosed = false
|
|
|
|
// 外部调用:推送音频数据
|
|
func pushAudio(_ data: Data) {
|
|
if isClosed { return }
|
|
|
|
bufferLock.lock()
|
|
audioBuffer.append(data)
|
|
bufferLock.unlock()
|
|
}
|
|
|
|
// SPXPullAudioInputStreamDelegate协议方法
|
|
func read(_ buffer: UnsafeMutablePointer<UInt8>, size: UInt32) -> Int32 {
|
|
bufferLock.lock()
|
|
defer { bufferLock.unlock() }
|
|
|
|
if isClosed {
|
|
return 0
|
|
}
|
|
|
|
let maxSize = Int(size)
|
|
|
|
if audioBuffer.count == 0 {
|
|
// 没有数据可读,返回0表示需要等待
|
|
return 0
|
|
}
|
|
|
|
let readSize = min(audioBuffer.count, maxSize)
|
|
audioBuffer.copyBytes(to: buffer, from: 0..<readSize)
|
|
|
|
if readSize < audioBuffer.count {
|
|
// 保留未读的数据
|
|
audioBuffer = audioBuffer.subdata(in: readSize..<audioBuffer.count)
|
|
} else {
|
|
// 清空缓冲区
|
|
audioBuffer = Data()
|
|
}
|
|
|
|
return Int32(readSize)
|
|
}
|
|
|
|
// 关闭音频资源
|
|
func close() {
|
|
print("\(tag): 关闭外部音频流")
|
|
|
|
bufferLock.lock()
|
|
isClosed = true
|
|
audioBuffer = Data()
|
|
bufferLock.unlock()
|
|
|
|
print("\(tag): 外部音频流已关闭")
|
|
}
|
|
}
|
|
}
|