如何在iOS应用中通过TwilioVoiceSDK建立PSTN通话流程并传输音频?
解决方案:修复Twilio Voice PSTN通话流程与音频问题
一、解决流程混乱(先显示呼入再路由PSTN)的问题
1. 检查Access Token生成逻辑
日志出现呼入流程的核心原因是Access Token未正确配置呼出规则。生成Token时必须指定OutgoingApplicationSid(对应托管你TwiML端点的Twilio应用SID),这样Twilio才会在你调用connect()时触发呼出Webhook,而非误触发呼入流程。
示例Token生成代码(Python):
from twilio.jwt.access_token import AccessToken from twilio.jwt.access_token.grants import VoiceGrant account_sid = "你的Twilio Account SID" api_key = "你的Twilio API Key" api_secret = "你的Twilio API Secret" outgoing_app_sid = "关联TwiML端点的Twilio应用SID" identity = "用户唯一标识" token = AccessToken(account_sid, api_key, api_secret, identity=identity) voice_grant = VoiceGrant( outgoing_application_sid=outgoing_app_sid, incoming_allow=True # 无需呼入可设为False ) token.add_grant(voice_grant) print(token.to_jwt())
2. 修正客户端状态跟踪
完善CallDelegate实现,区分不同阶段的连接状态,避免日志混淆:
extension YourCallManager: CallDelegate { func callDidConnect(_ call: Call) { print("客户端已连接至Twilio平台") } func callDidReceiveAudio(_ call: Call) { print("音频通道已建立(PSTN端已接通)") } func callDidFailToConnect(_ call: Call, error: Error) { print("通话连接失败:\(error.localizedDescription)") } func callDidDisconnect(_ call: Call, error: Error?) { print("通话断开:\(error?.localizedDescription ?? "无异常")") } }
二、修复音频传输异常问题
1. 确保麦克风权限配置
在Info.plist中添加权限描述:
<key>NSMicrophoneUsageDescription</key> <string>需要访问麦克风以进行语音通话</string>
调用connect()前主动请求权限:
import AVFoundation func requestMicrophonePermission(completion: @escaping (Bool) -> Void) { AVAudioSession.sharedInstance().requestRecordPermission { granted in DispatchQueue.main.async { completion(granted) } } }
2. 正确配置音频会话
Twilio Voice SDK依赖AVAudioSession的正确配置,建议在通话启动前设置:
func setupAudioSession() { let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playAndRecord, mode: .voiceChat, options: .defaultToSpeaker) try session.setActive(true) } catch { print("音频会话配置失败:\(error)") } }
通话结束后关闭音频会话:
func teardownAudioSession() { do { try AVAudioSession.sharedInstance().setActive(false) } catch { print("音频会话关闭失败:\(error)") } }
3. 验证Caller ID有效性
确保Dial组件的caller_id是Twilio验证过的号码(不能用未验证号码或客户端Identity),否则PSTN端可能无法正常接收音频。
三、代码优化建议
iOS端优化
在performVoiceCall中先检查权限,再发起通话:
func performVoiceCall(uuid: UUID, phoneNumber: String, token: String, completionHandler: @escaping (Bool) -> Void) { requestMicrophonePermission { [weak self] granted in guard granted, let self = self else { completionHandler(false) return } self.setupAudioSession() let connectOptions = ConnectOptions(accessToken: token) { builder in builder.params = ["To": phoneNumber] builder.uuid = uuid } let call = TwilioVoiceSDK.connect(options: connectOptions, delegate: self) completionHandler(true) } }
TwiML端点优化
增加参数校验和错误响应:
@https_fn.on_request() def twiml_response(req: https_fn.Request) -> https_fn.Response: response = VoiceResponse() to_param = req.form.get("To") if not to_param: response.say("未指定目标号码,请检查输入。", voice="alice") return https_fn.Response(str(response), status=400, mimetype="application/xml") if to_param.startswith("+"): dial = Dial(caller_id="+162xxxxxxxx") dial.number(to_param) response.append(dial) else: response.say("无效的目标号码,请使用以+开头的有效PSTN号码。", voice="alice") return https_fn.Response(str(response), status=200, mimetype="application/xml")
四、正确的通话流程梳理
- iOS端请求麦克风权限并配置音频会话;
- 调用
TwilioVoiceSDK.connect(),携带目标号码参数; - Twilio根据Access Token中的
OutgoingApplicationSid触发你的TwiML端点; - TwiML返回
Dial指令,Twilio发起对PSTN号码的呼叫; - PSTN端接听后,Twilio建立客户端与PSTN的音频通道;
- 通话结束后,iOS端关闭音频会话。
内容的提问来源于stack exchange,提问作者Umair Suraj
相关产品推荐
相关产品推荐

