8 cc
作者:陈媛媛
一、概述
VoLTE(Voice over LTE)是基于 LTE 网络的高质量语音通话技术。cc 库提供了 VoLTE 通话的相关功能,包括拨打电话、接听电话、挂断电话、通话录音等功能。
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/cc
VoLTE 通话功能示例
--[[
本示例演示自动接听电话功能:
1. 使用exaudio模块初始化音频硬件设备
2. 实现呼入自动接听功能(响2声后自动接听)
3. 通话10秒后自动挂断
4. 通话过程录音
硬件要求:
Air8000整机开发板或者Air8000核心板+AirAUDIO_1010 音频扩展板
]]
-- 引入exaudio模块
exaudio = require("exaudio")
-- 定义硬件引脚常量
local PIN_I2C_POWER = 24 -- I2C电源控制脚
local PIN_ES8311_POWER = 164 -- ES8311电源脚
local PIN_SPEAKER_PA = 162 -- 喇叭PA功放脚
local PIN_I2C0_SCL = 80 -- I2C0_SCL复用引脚
local PIN_I2C0_SDA = 81 -- I2C0_SDA复用引脚
-- 音频配置参数
local audio_configs = {
model = "es8311", -- dac类型
i2c_id = 0, -- i2c_id
pa_ctrl = PIN_SPEAKER_PA, -- 音频放大器电源控制管脚
dac_ctrl = PIN_ES8311_POWER, -- 音频编解码芯片电源控制管脚
dac_delay = 3, -- DAC启动前冗余时间(100ms)
pa_delay = 100, -- DAC启动后延迟打开PA的时间(ms)
dac_time_delay = 600, -- 播放完毕后PA与DAC关闭间隔(ms)
bits_per_sample = 16, -- 采样位数
pa_on_level = 1 -- PA打开电平 1:高 0:低
}
-- 全局状态变量
local call_counter = 0 -- 响铃计数器
local caller_number = "" -- 来电号码
local hangup_timer_id = nil -- 挂断定时器ID
local is_connected = false -- 通话连接状态标志
-- 创建音频数据缓冲区
local up1 = zbuff.create(6400,0) -- 上行数据保存区1
local up2 = zbuff.create(6400,0) -- 上行数据保存区2
local down1 = zbuff.create(6400,0) -- 下行数据保存区1
local down2 = zbuff.create(6400,0) -- 下行数据保存区2
-- ====================== 硬件初始化函数 ======================
-- 初始化GPIO引脚
local function initPins()
-- 初始化GPIO输出引脚
gpio.setup(PIN_I2C_POWER, 1, gpio.PULLUP)
gpio.setup(PIN_ES8311_POWER, 1, gpio.PULLUP)
gpio.setup(PIN_SPEAKER_PA, 1, gpio.PULLUP)
gpio.setup(24, 1, gpio.PULLUP)
sys.wait(300)
gpio.setup(147, 1, gpio.PULLUP)
sys.wait(2000)
-- 配置引脚复用
pins.setup(PIN_I2C0_SCL, "I2C0_SCL")
pins.setup(PIN_I2C0_SDA, "I2C0_SDA")
log.info("exaudio_demo", "GPIO引脚初始化完成")
end
-- 初始化音频设备
local function initAudioDevice()
-- 音频芯片电源复位
pm.power(pm.LDO_CTL, false) -- 关闭电源
sys.wait(100) -- 等待100MS
pm.power(pm.LDO_CTL, true) -- 重新上电
-- 使用exaudio初始化音频设备
if exaudio.setup(audio_configs) then
log.info("exaudio_demo", "音频设备初始化成功")
else
log.error("exaudio_demo", "音频设备初始化失败")
return false
end
log.info("exaudio_demo", "exaudio音频设备初始化完成")
return true
end
-- ====================== 通话处理函数 ======================
-- 挂断回调函数
local function hangupCallback()
log.info("exaudio_demo", "10秒通话结束,主动挂断")
cc.hangUp(0)
hangup_timer_id = nil
is_connected = false
end
-- 录音回调函数
local function recordCallback(is_dl, point)
if is_dl then
log.info("录音", "下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
else
log.info("录音", "上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
end
log.info("通话质量", cc.quality())
end
-- 启用通话录音
local function enableRecording()
cc.record(true, up1, up2, down1, down2)
cc.on("record", recordCallback)
log.info("exaudio_demo", "通话录音已启用")
end
-- 通话状态处理函数
local function handleCallStatus(status)
log.info("通话状态", status)
if status == "INCOMINGCALL" then
-- 获取来电号码
caller_number = cc.lastNum() or "未知号码"
call_counter = call_counter + 1
log.info("exaudio_demo", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
-- 响铃2声后自动接听
if call_counter >= 2 then
log.info("exaudio_demo", "自动接听来电")
cc.accept(0)
call_counter = 0
end
elseif status == "ANSWER_CALL_DONE" then
log.info("exaudio_demo", "接听完成,等待通话建立")
elseif status == "SPEECH_START" then
-- 语音通话真正开始
if not is_connected then
log.info("exaudio_demo", "通话已建立,开始计时")
is_connected = true
-- 创建10秒后挂断的定时器
hangup_timer_id = sys.timerStart(hangupCallback, 10000)
if hangup_timer_id then
log.info("exaudio_demo", "10秒挂断定时器创建成功")
else
log.error("exaudio_demo", "10秒挂断定时器创建失败")
end
end
elseif status == "HANGUP_CALL_DONE" or status == "DISCONNECTED" then
log.info("exaudio_demo", "通话结束")
is_connected = false
-- 取消挂断定时器
if hangup_timer_id then
sys.timerStop(hangup_timer_id)
hangup_timer_id = nil
log.info("exaudio_demo", "已取消挂断定时器")
end
call_counter = 0
-- 设置音频芯片待机模式
exaudio.set_work_mode(exaudio.STANDBY)
end
end
-- 电话系统就绪处理函数
local function handleSystemReady()
sys.publish("CC_READY")
-- 设置音频芯片待机模式
exaudio.set_work_mode(exaudio.STANDBY)
end
-- ====================== 系统初始化 ======================
-- 电话系统初始化
local function initCallSystem()
-- 初始化硬件引脚
initPins()
-- 初始化音频设备
if not initAudioDevice() then
log.error("exaudio_demo", "音频设备初始化失败")
return
end
-- 等待电话系统就绪
sys.waitUntil("CC_READY")
-- 初始化电话功能
cc.init(0) -- 使用默认音频通道0
-- 启用通话录音
enableRecording()
log.info("exaudio_demo", "电话系统初始化完成")
end
-- 注册电话状态回调
sys.subscribe("CC_IND", function(status)
handleCallStatus(status)
-- 通用状态处理
if status == "READY" then
handleSystemReady()
end
end)
-- 启动初始化任务
sys.taskInit(initCallSystem)
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
3.1 "CC_IND" 通话状态事件详解
常量含义:系统消息的一种,专门用于通知通话状态的变化。
当通话状态发生变化时(如来电、接通、挂断等),系统会发布CC_IND消
息,并附带状态字符串。
该消息携带的参数取值范围包括:
"READY" - 通话模块准备就绪,可以拨打电话或接听来电
"INCOMINGCALL" - 有来电呼入
"CONNECTED" - 电话已接通
"DISCONNECTED" - 通话连接断开
"SPEECH_START" - 语音通话开始
"MAKE_CALL_OK" - 拨号请求成功
"MAKE_CALL_FAILED" - 拨号请求失败
"ANSWER_CALL_DONE" - 接听完成
"HANGUP_CALL_DONE" - 挂断完成
"PLAY" - 开始有音频输出
什么是系统消息:系统消息是 LuatOS 操作系统内部的事件通知机制,
用于在不同模块之间传递状态变化信息。当系统内部发生特定事件时
(如网络状态变化、通话状态变化等),系统会自动发布相应的消息,
应用程序可以通过订阅这些消息来获知事件发生。
更多系统消息见 [sys系统消息](https://docs.openluat.com/osapi/sys_pub/?h=cc_ind#cc_ind)
数据类型:string;
示例代码:-- 定义通话状态处理函数
local function handleCallStatus(status)
if status == "READY" then
-- 状态:通话模块准备就绪
log.info("通话状态", "当前状态:通话模块已就绪")
elseif status == "INCOMINGCALL" then
-- 状态:有来电呼入
log.info("通话状态", "当前状态:有来电呼入")
end
end
-- 订阅CC_IND通话状态消息
sys.subscribe("CC_IND", handleCallStatus)
-- 初始化通话模块
cc.init()
3.1.1 "READY"
**常量含义**:通话模块准备就绪,表示通话功能已初始化完成,可以正常进行拨打电话或接收来电操作;
**数据类型**:string;
**注意事项:**必须在cc.init()之前订阅CC_IND事件
**示例代码**:-- 必须在cc.init()之前订阅CC_IND事件
local function handleCallStatus(status)
if status == "READY" then-- 状态:通话模块准备就绪
log.info("通话状态", "当前状态:通话模块已就绪,可以拨打电话")
end
end
-- 订阅事件
sys.subscribe("CC_IND", handleCallStatus)
3.1.2 "INCOMINGCALL"
**常量含义**:有来电呼入,表示检测到有电话正在呼叫本设备;
**数据类型**:string;
**示例代码**:local function handleCallStatus(status)
if status == "INCOMINGCALL" then
-- 状态:有来电呼入
log.info("通话状态", "当前状态:有来电呼入")
-- 可以在此处添加来电逻辑,如自动接听、挂断等
end
end
-- 订阅事件
sys.subscribe("CC_IND", handleCallStatus)
3.1.3 "CONNECTED"
**常量含义**:电话已接通,表示通话连接已成功建立,双方可以进行语音通信;
**数据类型**:string;
**示例代码**:local function handleCallStatus(status)
if status == "CONNECTED" then
-- 状态:电话已接通
log.info("通话状态", "当前状态:通话已接通")
-- 可以在此处开始录音或执行通话中的业务逻辑
end
end
3.1.4 "DISCONNECTED"
**常量含义**:通话连接断开,表示当前通话已被终止;
**数据类型**:string;
**触发条件**: 对方主动挂断电话
来电被拒接
来电无人接听(超时)
网络异常导致通话中断
**注意事项:**对于未接听的情况(包括拒接和无人接听),
系统会在完整的提示音播放完毕后
(如"您拨打的电话正在通话中"或"您拨打的电话暂时无人接听")
才触发此状态,无法通过此状态区分具体的未接听原因(拒接或 无人接听)。
**示例代码:**local function handleCallStatus(status)
if status == "DISCONNECTED" then
-- 状态:通话连接断开
log.info("通话状态", "当前状态:通话已断开")
-- 可以在此处停止录音或清理通话相关资源
end
end
3.1.5 "SPEECH_START"
**常量含义**:通话开始,表示语音通道已建立,可以开始传输语音数据;
**数据类型**:string;
**示例代码:**local function handleCallStatus(status)
if status == "SPEECH_START" then
-- 状态:语音通话开始
log.info("通话状态", "当前状态:语音通话开始")
end
end
3.1.6 "MAKE_CALL_OK"
**常量含义**:拨打电话请求成功,表示拨号指令已成功发送,正在等待对方接听;
**数据类型**:string;
**示例代码**:local function handleCallStatus(status)
if status == "MAKE_CALL_OK" then
-- 状态:拨号请求成功
log.info("通话状态", "当前状态:拨号成功,等待接听")
end
end
3.1.7 "MAKE_CALL_FAILED"
**常量含义**:拨打电话请求失败,表示拨号操作未能成功执行;
**数据类型**:string;
**示例代码**:local function handleCallStatus(status)
if status == "MAKE_CALL_FAILED" then
-- 状态:拨号失败
log.info("通话状态", "当前状态:拨号失败")
-- 可以在此处进行重拨或错误处理
end
end
3.1.8 "ANSWER_CALL_DONE"
**常量含义**:接听电话请求完成,表示接听操作已执行完成;
**数据类型**:string;
**示例代码:**local function handleCallStatus(status)
if status == "ANSWER_CALL_DONE" then
-- 状态:接听完成
log.info("通话状态", "当前状态:接听完成")
end
end
3.1.9 "HANGUP_CALL_DONE"
**常量含义**:挂断电话请求完成,表示挂断操作已执行完成;
**数据类型**:string;
**示例代码**:local function handleCallStatus(status)
if status == "HANGUP_CALL_DONE" then
-- 状态:挂断完成
log.info("通话状态", "当前状态:挂断完成")
end
end
3.1.10 "PLAY"
**常量含义**:开始有音频输出,表示音频播放已开始;
**数据类型**:string;
**示例代码**:local function handleCallStatus(status)
if status == "PLAY" then
-- 状态:开始音频输出
log.info("通话状态", "当前状态:开始音频播放")
end
end
3.2 "CC_READY"
**常量含义**:通话模块准备就绪,表示通话控制模块已初始化完成,可以进行通话相关操作;
**数据类型**:string;
**触发时机**:
通话硬件初始化完成;
通话控制模块准备就绪。
**示例代码**:local function handleCallStatus(status)
-- 状态:通话模块硬件初始化完成
log.info("通话模块", "当前状态:通话硬件已就绪")
-- 可以安全调用通话相关的初始化配置
end
3.3 CC_READY 和 READY 的区别
“CC_READY” 通话硬件初始化完成的通知,表示通话模块的基础硬件已经准备好,可以进行基本的通话配置。
“READY”(作为 CC_IND 消息的状态)是通话功能完全就绪的通知,表示整个通话系统已经完全准备好,可以立即进行拨打电话或接听来电等实际操作。
简单理解:
- “CC_READY ”= 硬件就绪(可以开始配置)
- “READY ”= 功能就绪(可以开始使用)
时序关系: 先有“CC_READY”(硬件准备),后有“READY”(功能就绪)。
四、函数详解
4.1 cc.lastNum()
功能 获取最后一次通话的号码
参数 无
返回值
local last_number = cc.lastNum()
有一个返回值 last_number
last_number
含义说明:最后一次通话的号码
数值类型:string
取值范围:有效的电话号码字符串
注意事项:如果没有通话记录,返回空字符串
返回示例:"10086"
示例
-- 获取最后一次通话号码
local last_call_number = cc.lastNum()
if last_call_number and last_call_number ~= "" then
log.info("最后一次通话号码", last_call_number)
else
log.info("通话记录", "暂无通话记录或获取失败")
end
4.2 cc.dial(sim_id, number)
功能 拨打电话
参数
sim_id
参数含义:SIM卡ID
数据类型:number
取值范围:0或1,表示不同的SIM卡槽
是否必选:是
注意事项:具体取值取决于设备支持的SIM卡数量
参数示例:cc.dial(0, "10086") -- 使用SIM0拨打电话
cc.dial(1, "10086") -- 使用SIM1拨打电话
number
参数含义:电话号码
数据类型:string
取值范围:有效的电话号码字符串
是否必选:是
注意事项:电话号码应符合国际标准格式
参数示例:cc.dial(0, "10086") -- 拨打客服电话
cc.dial(0, "+8613812345678") -- 拨打国际格式号码
返回值
local dial_result = cc.dial(sim_id, number)
有一个返回值 dial_result
dial_result
含义说明:拨打电话成功与否
数值类型:boolean
取值范围:true/false
注意事项:返回值仅表示指令发送状态,不代表通话已建立;
实际的拨打结果(接通、失败等)可以通过异步消息 "CC_IND" 来获取;
应用程序需要在 "CC_IND" 的回调函数或阻塞等待的task中处理实际的
通话状态,详细处理逻辑参考"常量详解"章节。
返回示例:true
示例
local result = cc.dial(0, "10086")
if result then
log.info("拨打电话", "指令发送成功")
else
log.error("拨打电话", "指令发送失败")
end
4.3 cc.hangUp(sim_id)
功能 挂断电话
参数
sim_id
参数含义:SIM卡ID
数据类型:number
取值范围:通常为0或1,表示不同的SIM卡槽
是否必选:是
注意事项:具体取值取决于设备支持的SIM卡数量
参数示例:cc.hangUp(0) -- 挂断SIM0的通话
cc.hangUp(1) -- 挂断SIM1的通话
返回值 无返回值
示例
cc.hangUp(0) -- 挂断当前通话
log.info("挂断电话", "已执行")
4.4 cc.accept(sim_id)
功能 接听电话
参数
sim_id
参数含义:SIM卡ID
数据类型:number
取值范围:通常为0或1,表示不同的SIM卡槽
是否必选:是
注意事项:具体取值取决于设备支持的SIM卡数量
返回值
local accept_result = cc.accept(sim_id)
有一个返回值 accept_result
accept_result
含义说明:接听电话成功与否
数值类型:boolean
取值范围:true/false
注意事项:返回true仅表示接听指令发送成功,不代表通话已建立。实际的拨打结果
(接通、失败等)可以通过异步消息 "CC_IND" 来获取;
应用程序需要在 "CC_IND" 的回调函数或阻塞等待的task中处理实际的
通话状态,详细处理逻辑参考"常量详解"章节。
返回示例:true
示例
if call_counter >= 2 then
log.info("自动接听来电")
cc.accept(0)
call_counter = 0 -- 重置计数器
end
4.5 cc.init(multimedia_id)
功能 初始化电话功能
参数
multimedia_id
参数含义:多媒体ID
数据类型:number
取值范围:取决于设备支持的多媒体通道数量,一般设备至少支持通道0。
是否必选:是
注意事项:如果不确定,请使用0。如果设备支持多路,可以参考硬件手册选择不同的
通道用于不同的音频任务。
返回值
local init_result = cc.init(multimedia_id)
有一个返回值 init_result
init_result
含义说明:初始化成功与否
数值类型:boolean
取值范围:true/false
注意事项:初始化失败可能由于资源占用或硬件问题
返回示例:true
示例
cc.init(multimedia_id) --初始化电话功能
sys.waitUntil("CC_READY")
cc.dial(0,"158xxxxxxxx") --拨打电话
4.6 cc.record(on_off, upload_zbuff1, upload_zbuff2, download_zbuff1, download_zbuff2)
功能 配置通话录音功能
通话录音功能通过双缓冲机制实现连续录音:
- 音频流向:系统同时录制上行(你说话)和下行(对方说话)两个方向的音频
- 缓冲机制:每个方向使用两个缓冲区轮换工作,确保录音不间断
- 数据保存:当缓冲区满时,应用程序需要及时保存数据并清空缓冲区
参数
on_off
参数含义:开启或关闭通话录音功能
数据类型:boolean
取值范围:false/nil表示关闭,其他值表示开启
是否必选:是
注意事项:开启录音前需要确保处于通话状态。
为了录制完整的通话内容,建议:
在收到 "CONNECTED" 通话接通消息后立即开启录音;
在收到 "DISCONNECTED" 通话结束消息后关闭录音;
这样可以从通话一开始就录制,直到通话完全结束,确保不遗漏任何通话内容。
参数示例:-- 定义通话状态处理函数
local function handleCallStatus(status)
if status == "CONNECTED" then
-- 通话接通,立即开始录音
cc.record(true, buff1, buff2, buff3, buff4)
elseif status == "DISCONNECTED" then
-- 通话结束,停止录音
cc.record(false)
end
end
-- 订阅通话状态事件
sys.subscribe("CC_IND", handleCallStatus)
upload_zbuff1
参数含义:上行数据保存区1
数据类型:zbuff
取值范围:有效的zbuff对象,空间容量必须是640的倍数
是否必选:是
注意事项:与upload_zbuff2组成双缓冲区
参数示例:
buff1 = zbuff.create(6400, 0, zbuff.HEAP_AUTO)
upload_zbuff2
参数含义:上行数据保存区2
数据类型:zbuff
取值范围:有效的zbuff对象,空间容量必须是640的倍数
是否必选:是
注意事项:与upload_zbuff1组成双缓冲区
参数示例:
buff2 = zbuff.create(6400, 0, zbuff.HEAP_AUTO)
download_zbuff1
参数含义:下行数据保存区1
数据类型:zbuff
取值范围:有效的zbuff对象,空间容量必须是640的倍数
是否必选:是
注意事项:与download_zbuff2组成双缓冲区
参数示例:
buff3 = zbuff.create(6400, 0, zbuff.HEAP_AUTO)
download_zbuff2
参数含义:下行数据保存区2
数据类型:zbuff
取值范围:有效的zbuff对象,空间容量必须是640的倍数
是否必选:是
注意事项:与download_zbuff1组成双缓冲区
参数示例:
buff4 = zbuff.create(6400, 0, zbuff.HEAP_AUTO)
返回值
有一个返回值 record_result
record_result
含义说明:配置录音操作成功与否
数值类型:boolean
取值范围:true/false
注意事项:如果处于非通话状态,开启录音会失败
返回示例:true
示例
buff1 = zbuff.create(6400,0,zbuff.HEAP_AUTO)
buff2 = zbuff.create(6400,0,zbuff.HEAP_AUTO)
buff3 = zbuff.create(6400,0,zbuff.HEAP_AUTO)
buff4 = zbuff.create(6400,0,zbuff.HEAP_AUTO)
cc.on("record", function(event, buff_point)
log.info(event, buff_point) -- type==true是下行数据,false是上行数据 buff_point指示双缓存中返回了哪一个
end)
cc.record(true, buff1, buff2, buff3, buff4)
4.7 cc.quality()
功能 获取当前通话质量
参数 无
返回值
local quality = cc.quality()
有一个返回值 quality
quality
含义说明:通话质量等级
数值类型:number
取值范围:0: 没有在通话
1: 低音质(8K)
2: 高音质(16K)
注意事项:只有在通话中才能获取到有效的质量信息
返回示例:2
示例
local function recordCallback(is_dl, point)
if is_dl then -- is_dl 是 record 函数的第一个参数,它是一个布尔值,true: 表示下行数据
log.info("录音", "下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
else
log.info("录音", "上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
end
log.info("通话质量", cc.quality())
-- 可以在初始化串口后,通过uart.tx来发送走对应的zbuff即可
end
4.8 cc.on(event, func)
功能 注册通话录音回调函数
参数
event
参数含义:事件名称
数据类型:string
取值范围:音频录音数据为"record"
是否必选:是
参数示例:cc.on("record", callback_func) -- 注册录音回调
func
参数含义:回调函数,回调函数参数详细说明如下:
{
--type
--参数含义:音频数据类型标识,用于区分音频数据的传输方向;
--数据类型:boolean;
--取值范围:boolean类型时,true表示下行数据,false表示上行数据;
--是否必选:是;
--注意事项:true:下行数据(从对方到本地,即对方说话的声音);
false:上行数据(从本地到对方,即本地说话的声音);
在实际应用中,可以根据此参数决定是否保存某个方向的音频数据;
--参数示例:true;
--buff_point
--参数含义:缓冲区指示,标识当前可用的录音数据缓冲区;
--数据类型:number;
--取值范围:1或2;
--是否必选:是;
--注意事项:指示双缓存中哪一个缓冲区已填满并可读取。
系统采用双缓冲机制确保录音连续不中断。
当缓冲区1满时触发回调并指示1,此时应处理缓冲区1的数据。
当缓冲区2满时触发回调并指示2,此时应处理缓冲区2的数据。
处理完数据后需要及时清空相应缓冲区以供下次使用。
缓冲区大小由cc.record()函数中传入的zbuff对象决定,必须为640的倍数;
** **--参数示例:1;
}
数据类型:function
是否必选:是
参数示例:local function record_callback(event, buff_point)
log.info("录音回调", event, buff_point)
end
cc.on("record", record_callback)
返回值 无返回值
示例
-- 注册录音回调函数
cc.on("record", function(type, buff_point)
if type then
log.info("录音数据", "下行数据", "缓冲区:", buff_point)
else
log.info("录音数据", "上行数据", "缓冲区:", buff_point)
end
end)
五、产品支持说明
支持 LuatOS 开发的所有产品都支持 cc 核心库。