QuecPython的语音通话功能模块voiceCall,能够实现基本通话,DTMF,呼叫转移等,下面具体介绍这些功能的使用。
下面基于voiceCall的voLTE通话演示手机和模组进行语音通话的场景。以下操作中,模组端的操作都基于QPYcom工具,先打开QPYcom,并选择模组的python交互端口,进入如下界面:
为了方便看到整个语音通话过程中的所有事件,比如电话呼入、接通、挂断等事件。以下功能演示均提前注册回调函数,见4.4通话回调注册。
下面所有示例中的“xxxxxxxxxxx”表示的是手机号码,实际测试时,显示的是真实的手机号码。
基本电话功能#
呼叫、接听#
模组给手机打电话,手机端接听,然后挂断电话。
>>> import voiceCall
>>> voiceCall.callStart('xxxxxxxxxxx') # 模组主动拨打电话给手机
0
>>>
args:(14, 1, 0, 3, 0, 0, 'xxxxxxxxxxx', 129, 0) # 回调通知当前正在呼出中
args:(11, 1, 0, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 手机端接听,回调通知电话接通事件
args:(12, 1, 0, 6, 0, 0, 'xxxxxxxxxxx', 129, 0) # 手机端挂断,回调通知电话挂断事件
用手机拨打电话给模组,当看到上述注册的回调触发并显示呼入电话的信息时,使用如下接口来接听电话:
>>> import voiceCall
>>>
args:(10, 1, 1, 4, 0, 0, 'xxxxxxxxxxx', 129, 0) # 电话呼入时,触发回调
>>> voiceCall.callAnswer() # 接听电话
0
>>>
args:(11, 1, 1, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 接通后,触发回调
自动接听#
手机给模组打电话,模组端自动接听,然后主动挂断电话。
设置自动应答
>>> import voiceCall
>>> voiceCall.setAutoAnswer(3) # 设置3s后自动接听电话
0
>>>
args:(10, 1, 1, 4, 0, 0, 'xxxxxxxxxxx', 129, 0) # 电话呼入时,触发回调
args:(11, 1, 1, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 自动接听后,触发回调
挂断电话#
用手机拨打电话给模组,当看到上述注册的回调触发并显示呼入电话的信息时,使用如下接口来接听电话,随后挂断电话:
>>> import voiceCall
>>>
args:(10, 1, 1, 4, 0, 0, 'xxxxxxxxxxx', 129, 0) # 电话呼入时,触发回调
>>> voiceCall.callAnswer() # 接听电话
0
>>>
args:(11, 1, 1, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 接通后,触发回调
>>> voiceCall.callEnd() # 挂断电话
args:(12, 1, 1, 6, 0, 0, 'xxxxxxxxxxx', 129, 0) # 通话挂断后,触发回调
0
>>>
自动挂断#
用手机拨打电话给模组,当模块设置不会自动挂断时,会正常收到回调;当设置模块为自动挂断时,模块不会触发回调,也没有任何反应,反应在手机上,会听到电话已接通,随后快速挂掉。
>>> import voiceCall
#手机呼叫模块,默认不会自动挂断
>>> voiceCall.getAutoCancelStatus()
0
#设置自动挂断功能,手机呼叫模块,默认自动挂断
>>> voiceCall.setAutoCancel(1)
0
>>> voiceCall.getAutoCancelStatus()
1
DTMF功能#
QuecPython的语音通话功能模块voiceCall,能够实现设置和接收DTMF功能,下面是介绍DTMF的具体实现。
设置DTMF#
使用voiceCall.startDtmf(dtmf, duration)能够设置DTMF,其中:
dtmf:表示DTMF字符串,有效字符数有:0-9、A、B、C、D、*、#
duration:表示 持续时间,单位:毫秒。
仅在语音通话过程中使用生效。
>>> import voiceCall
>>> voiceCall.startDtmf('8',100) #等效于手机在按键输入‘8’
0
DTMF识别功能配置#
使用voiceCall.dtmfDetEnable(enable)可开启或关闭DTMF识别功能,默认不开启DTMF识别。当开启DTMF识别功能后,可通过注册DTMF识别功能回调函数查看识别到的DTMF字符串。
>>> import voiceCall
>>> voiceCall.dtmfDetEnable(0) #关闭DTMF识别功能
>>> voiceCall.dtmfDetEnable(1) #开启DTMF识别功能
DTMF识别功能回调使用#
使用voiceCall.dtmfSetCb(dtmfFun)可注册DTMF识别功能的回调函数,用户识别对端输入的DTMF字符串。
模组给手机打电话,手机端接听后,手机分别输入1,8,9 ,然后挂断电话。
>>> import voiceCall
>>> def cb(args):
... print('DTMF:{}'.format(args))
>>> voiceCall.dtmfSetCb(cb)
0
>>> voiceCall.dtmfDetEnable(1)
0
>>> voiceCall.callStart('xxxxxxxxxxx') # 模组主动拨打电话给手机,
0
args:(14, 1, 0, 3, 0, 0, 'xxxxxxxxxxx', 129, 0) # 回调通知当前正在呼出中, 触发通话状态回调,
args:(11, 1, 0, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 手机端接听,回调通知电话接通事件, 触发通话状态回调,
>>>
DTMF:1 #手机端按下1,回调函数中会收到按下的字符“1”, 触发DTMF识别功能回调,
DTMF:8 #手机端按下8, 触发DTMF识别功能回调,
DTMF:9 #手机端按下9, 触发DTMF识别功能回调。
args:(12, 1, 0, 6, 0, 0, 'xxxxxxxxxxx', 129, 0) # 手机端挂断,回调通知电话挂断事件,触发通话状态回调。
其他电话功能#
呼叫转移#
使用voiceCall.setFw(reason, fwmode, phonenum)可以控制呼叫转移业务,当收到来电时根据呼叫转移的条件reason,控制是否转移fwmode到目标号码phonenum。
用手机拨打电话给模组,当看到上述注册的回调触发并显示呼入电话的信息时,使用如下接口来接听电话,随后呼叫转移至另一个号码:
>>>
args:(10, 1, 1, 4, 0, 0, 'xxxxxxxxxxx', 129, 0) # 电话呼入时,触发回调
>>> voiceCall.callAnswer() # 接听电话
0
>>>
args:(11, 1, 1, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 接通后,触发回调
>>> voiceCall.setFw(0, 1, 'YYYYYYYYYYY') # 无条件呼叫转移至另一个号码
音量设置#
使用voiceCall.setVolume(volume)可以设置音量等级volume ,范围(0 ~ 11),数值越大,音量越大。
用手机拨打电话给模组,使用接口来接听电话,随后查询音量,设置音量,最后挂断:
>>>
args:(10, 1, 1, 4, 0, 0, 'xxxxxxxxxxx', 129, 0) # 电话呼入时,触发回调
>>> voiceCall.callAnswer() # 接听电话
0
>>>
args:(11, 1, 1, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 接通后,触发回调
>>> voiceCall.getVolume() # 查询音量
8
>>> voiceCall.setVolume(6) # 设置音量
0
>>> voiceCall.getVolume() # 查询音量
6
>>> voiceCall.callEnd() # 挂断电话
args:(12, 1, 1, 6, 0, 0, 'xxxxxxxxxxx', 129, 0) # 通话挂断后,触发回调
0
>>>
通话声音输出通道配置#
使用voiceCall.setChannel(device)可以设置通话时的声音输出通道,默认是通道0,即听筒。
>>> voiceCall.setChannel(2) #切换到喇叭通道
0
>>> voiceCall.setChannel(1) #切换到耳机通道
0
>>> voiceCall.setChannel(0) #切换回听筒通道
0
通话录音#
使用voiceCall录音接口,可以使用通话录音功能,包含录音到文件,和流式录音。
开启自动录音功能,用手机拨打电话给模组,使用接口来接听电话,自动录音,最后挂断:
>>> voiceCall.setAutoRecord(1,0,2,'U:/test.amr') # 开启自动录音功能
0
>>>
args:(10, 1, 1, 4, 0, 0, 'xxxxxxxxxxx', 129, 0) # 电话呼入时,触发回调
>>> voiceCall.callAnswer() # 接听电话
0
>>>
args:(11, 1, 1, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 接通后,触发回调
# 自动录音到文件中...
>>> voiceCall.callEnd() # 挂断电话
args:(12, 1, 1, 6, 0, 0, 'xxxxxxxxxxx', 129, 0) # 通话挂断后,触发回调
0
>>>
(1)录音到文件#
用手机拨打电话给模组,自动接听电话,开启录音到文件,停止录音,最后挂断:
>>> voiceCall.setAutoAnswer(3) # 设置3s后自动接听电话
0
>>>
args:(10, 1, 1, 4, 0, 0, 'xxxxxxxxxxx', 129, 0) # 电话呼入时,触发回调
args:(11, 1, 1, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 自动接听后,触发回调
>>> voiceCall.startRecord(0,2,'U:/test.amr') # 开启录音到文件
0
# 录音到文件中...
>>> voiceCall.stopRecord() # 停止通话录音
0
>>> voiceCall.callEnd() # 挂断电话
args:(12, 1, 1, 6, 0, 0, 'xxxxxxxxxxx', 129, 0) # 通话挂断后,触发回调
0
>>>
(2)流式录音#
模组给手机打电话,手机端接听后,开始通话录音(流形式),然后挂断电话,播放之前的录音:
>>> import voiceCall
>>> import audio
>>> f=open('usr/mia.amr','w')
# 用于接收流形式的录音,写入文件中
>>> def cb(para):
... if(para[2] == 1):
... read_buf = bytearray(para[1])
... voiceCall.readRecordStream(read_buf,para[1])
... f.write(read_buf,para[1])
... del read_buf
... elif(para[2] == 3):
... f.close()
...
>>> voiceCall.callStart('xxxxxxxxxxx')
0
args:(14, 1, 0, 3, 0, 0, 'xxxxxxxxxxx', 129, 0) # 回调通知当前正在呼出中,触发通话状态回调,
args:(11, 1, 0, 0, 0, 0, 'xxxxxxxxxxx', 129, 0) # 手机端接听,回调通知电话接通事件,触发通话状态回调,
>>> voiceCall.startRecordStream(0,2,cb) # 开始通话录音(流形式)
0
# 录音中...
>>> voiceCall.callEnd() # 挂断电话(MO/MT侧挂断都可以)
args:(12, 1, 1, 6, 0, 0, 'xxxxxxxxxxx', 129, 0) # 通话挂断后,触发回调
0
# 查看生成的录音文件,播放录音
>>> uos.listdir('usr')
['system_config.json', 'mia.amr']
>>> aud=audio.Audio(0)
>>> aud.setVolume(11)
0
>>> aud.play(2,1,'U:/mia.amr')
0
通话回调注册#
使用voiceCall.setCallback(voicecallFun)可注册回调函数。监听不同的通话状态并通过回调反馈给用户,方便看到整个语音通话过程中的所有事件,比如电话呼入、接通、挂断等事件。
在前面的示例中均提前注册了此回调函数。
示例如下:
>>> import voiceCall
>>> def cb_fun(args):
... print('args:{}'.format(args))
...
>>>
>>> voiceCall.setCallback(cb_fun) # 注册回调函数
0
实际使用为了区分不同电话状态的回调,可以使用下面示例:
# -*- coding: UTF-8 -*-
import voiceCall
class QuecVoiceCall():
def __init__(self):
self.__enable_log = False
self.voice_call_set_enable_log()
self.voice_call = voiceCall
self.voice_call.setCallback(self.__voice_Call_back)
def voice_call_set_enable_log(self,flag=True):
self.__enable_log = flag
def __log(self,args):
if self.__enable_log:
print("QuecSMS_LOG: {}".format(args))
def __voice_Call_back(args):
"""通话状态回调"""
self._log("voicecall callback args {}".format(args))
if args[0] == 1:
self._log("voicecall init")
elif args[0] == 2:
# 来电通知
self._log("Call in: {}, {}".format(args[1], args[2]))
elif args[0] == 3:
# 电话接通
self._log("Call answer: {}, {}".format(args[1], args[2]))
elif args[0] == 4:
# 通话挂断
self._log("Call end: {}, {}".format(args[1], args[2]))
elif args[0] == 5:
# 通话挂断
self._log("Call error")
elif args[0] == 6:
# 呼叫等待
self._log("Call wait")
self._log(args)
elif args[0] == 7:
# 呼出中
self._log("Call out")
elif args[0] == 8:
# 呼出中
self._log("Call out fail")
self._log(args)
elif args[0] == 9:
# 呼出中
self._log("wait")
self._log(args)
elif args[0] == 10:
# 来电通知(volte通话)
self._log("volte call in")
self._log(args)
elif args[0] == 11:
# 通话接通(volte通话)
self._log("volte call answer")
self._log(args)
elif args[0] == 12:
# 通话挂断(volte通话)
self._log("volte call end")
self._log(args)
elif args[0] == 13:
# 呼叫等待(volte通话)
self._log("volte call wait")
self._log(args)
elif args[0] == 14:
# 呼出中(volte通话)
self._log("volte call out")
self._log(args)
elif args[0] == 15:
# 呼出中,对方未响铃(volte通话)
self._log("volte call out 2")
self._log(args)
elif args[0] == 16:
# 等待(volte通话)
self._log("volte wait")
self._log(args)
else:
self._log("voicecall error")
def call_start(self, topic=None, phonenum=None):
"""拨打电话"""
print(" call start phone:",phonenum)
if phonenum:
try:
state = self.voice_call.callStart(phonenum)
self._log("call_start state {}".format(state))
return state
except Exception:
self._log("call_start error")
return False
def call_answer(self, topic=None, data=None):
"""接听电话"""
try:
state = self.voice_call.callAnswer()
self._log("call_answer state {}".format(state))
except Exception:
self._log("call_answer error")
def call_end(self, topic=None, data=None):
"""挂断电话"""
try:
state = self.voice_call.callEnd()
self._log("call_end state {}".format(state))
except Exception:
self._log("call_end error")
if __name__ == '__main__':
VoiceCall = QuecVoiceCall()
# 主动拨打电话
VoiceCall.call_start('xxxxxxxxxxx')
# 接通后,挂断电话
VoiceCall.call_end()
在CS通话和VoLTE通话下回调消息args是不同的,具体看下面说明。
CS通话的回调消息#
在CS通话的回调消息中回调函数的参数个数并不是固定的,而是根据第一个参数(触发事件)args[0]来决定,如下表:
| 值 | 参数个数 | 触发事件args[0]值说明 | 其他参数说明 |
|---|---|---|---|
| 1 | 1 | voicecall初始化成功 | |
| 2 | 3 | 来电通知,响铃 | args[1]:呼叫识别号码args[2]:电话号码 |
| 3 | 3 | 通话接通 | args[1]:呼叫识别号args[2]:电话号码 |
| 4 | 3 | 通话挂断 | args[1]:呼叫识别号码args[2]:通话挂断原因 |
| 5 | 1 | 未知错误 | |
| 6 | 5 | 呼叫等待 | args[1]:呼叫识别号码args[2]:电话号码args[3]:号码类型[129/145],129:非国际号码,145:国际号码args[4]:CLI状态 |
| 7 | 1 | 呼出中 | |
| 8 | 4 | 呼出失败 | args[1]:呼叫识别号码args[2]:呼叫失败原因args[3]:指示是否可以从网络端获取in-band tones |
| 9 | 3 | 等待 | args[1]:呼叫识别号码args[2]:电话号码 |
VoLTE通话的回调消息#
在VoLTE通话的回调消息中回调函数的参数个数是固定8个,第一个参数args[0]为触发事件,如下表:
以下是该表格的 Markdown 格式,已合并多行信息并在重复引用处补全内容,使表格结构清晰完整:
| 参数 | 参数个数 | 触发事件 args[0] 值说明 | 其他参数说明 |
|---|---|---|---|
| 10 | 8 | 来电通知、响铃 (volte通话) | args[1]:呼叫识别号码args[2]:呼叫访问 (MO/MT)args[3]:通信状态args[4]:业务类型(一般为 0,表示 voice call 普通话业务)args[5]:多方通信标志,0:非多方通信,1:多方通信args[6]:电话号码args[7]:号码类型 [129/145],129: 非国际号码,145: 国际号码 |
| 11 | 8 | 通话接通 (volte通话) | args[1]:呼叫识别号码args[2]:呼叫访问 (MO/MT)args[3]:通信状态args[4]:业务类型(一般为 0,表示 voice call 普通话业务)args[5]:多方通信标志,0:非多方通信,1:多方通信args[6]:电话号码args[7]:号码类型 [129/145],129: 非国际号码,145: 国际号码 |
| 12 | 8 | 通话挂断 (volte通话) | args[1]:呼叫识别号码args[2]:呼叫访问 (MO/MT)args[3]:通信状态args[4]:业务类型(一般为 0,表示 voice call 普通话业务)args[5]:多方通信标志,0:非多方通信,1:多方通信args[6]:电话号码args[7]:号码类型 [129/145],129: 非国际号码,145: 国际号码 |
| 13 | 8 | 呼叫等待 (volte通话) | args[1]:呼叫识别号码args[2]:呼叫访问 (MO/MT)args[3]:通信状态args[4]:业务类型(一般为 0,表示 voice call 普通话业务)args[5]:多方通信标志,0:非多方通信,1:多方通信args[6]:电话号码args[7]:号码类型 [129/145],129: 非国际号码,145: 国际号码 |
| 14 | 8 | 呼出中 (volte通话) | args[1]:呼叫识别号码args[2]:呼叫访问 (MO/MT)args[3]:通信状态args[4]:业务类型(一般为 0,表示 voice call 普通话业务)args[5]:多方通信标志,0:非多方通信,1:多方通信args[6]:电话号码args[7]:号码类型 [129/145],129: 非国际号码,145: 国际号码 |
| 15 | 8 | 呼出中,对方未响铃 (volte通话) | args[1]:呼叫识别号码args[2]:呼叫访问 (MO/MT)args[3]:通信状态args[4]:业务类型(一般为 0,表示 voice call 普通话业务)args[5]:多方通信标志,0:非多方通信,1:多方通信args[6]:电话号码args[7]:号码类型 [129/145],129: 非国际号码,145: 国际号码 |
| 16 | 8 | 等待 (volte通话) | args[1]:呼叫识别号码args[2]:呼叫访问 (MO/MT)args[3]:通信状态args[4]:业务类型(一般为 0,表示 voice call 普通话业务)args[5]:多方通信标志,0:非多方通信,1:多方通信args[6]:电话号码args[7]:号码类型 [129/145],129: 非国际号码,145: 国际号码 |
关于args[0]的值为10-16时,args[1]-args[8]的详细说明见WiKI:
常见问题解答#
(1)模组打电话,对SIM卡有什么要求?
如果模组仅支持4G网络,语音通话时,只能使用4G网络来进行语音通话,并且模组的固件必须支持VoLTE功能。
如果模组本身支持2G和4G,使用的SIM卡既支持2G也支持4G,但是使用的固件不支持VoLTE功能,则语音通话时,默认使用2G网络,会使用CSFB的电话方案。
下表是进行语言通话时需要对sim卡、模组、固件的具体要求:
| CS/VoLTE通话 | SIM卡要求 | 模组要求 | 固件要求 |
|---|---|---|---|
| 2G CS通话 | SIM卡需要支持2G网络 | 模组需要支持2G网络 | 无 |
| 4G VoLTE通话 | SIM卡需要支持4G网络 | 模组需要支持4G网络 | 需要支持VoLTE功能的固件 |
(2)中国境内,哪些运营商还支持2G/3G电话功能?
中国境内,自从2020年5月,工信部就正式发布了《关于深入推进移动物联网全面发展的通知》,其首次以公开发文形式正式提出2G/3G要迁移转网,明确中低速物联网应用要向NB-IoT和Cat.1网络迁移。
目前中国境内四大运营商对2G/3G的支持情况如下表:
| 运营商 | 目前2G/3G支持情况 | 备注 |
|---|---|---|
| 中国移动 | 支持 | 正在逐步减少2G用户 |
| 中国联通 | 不支持 | 已经在2022年底完成2G网络的退网 |
| 中国电信 | 部分地区支持 | 计划在2024年全面停止2G网络 |
| 中国广电 | 不支持 | 不支持2G、3G网络 |
