voiceCall功能应用

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网络