无需可视化UI!LuatOS通话录音实操方案来袭
针对近期大家咨询较多的通话录音相关问题,本文将分享一套基于合宙LuatOS的实操性通话录音方案。
- AirUI可视化方案目前仍处于优化过程中,其轻量化、低成本、高可靠的特性,在工业场景中具备一定的应用价值。
- 对于需要快速实现通话录音功能、暂不依赖可视化UI的使用者,本文分享的方案可直接参考实操,该方案支持自动接听与自动录音功能。
一、方案功能简介
该方案基于合宙LuatOS开发,可适配多种型号核心板,能够实现低成本通话留痕,可应用于多种相关业务场景。
主要功能特色如下:
-
自动接听: 来电响铃2声后自动接听,无需手动操作。
-
自动录音: 通话接通后自动开始录音,对方挂断后自动停止。
-
SD卡存储: 录音文件以PCM格式保存到SD卡,支持自动挂载和空间检测。
-
数据优化: 只保存上行数据,避免下行数据造成的回声问题。
-
跨模组兼容: Air780EHM、Air780EGH、Air780EHV、Air8000系列核心板,均可通过外挂音频配件板和SD卡存储配件板实现。
简而言之:一套方案搞定多个硬件型号,极大提升开发效率。
二、主要硬件准备
方案一:Air8000/Air780EHV开发板
合宙开发板提供了丰富的音频接口资源,可通过开发板上的音频接口进行连接和测试。
如Air8000/Air780EHV开发板:


方案二:核心板+配件板
如果没有Air780EHV和Air8000系列Turnkey开发板,那么可使用Air780EHM、Air780EGH、Air780EHV、Air8000系列核心板,通过外挂音频配件板和存储配件板来实现通话录音功能。
-
AirAUDIO_1010音频配件板: 负责音频输入输出;
-
AirMICROSD_1010存储配件板: 提供SD卡存储功能。
连接小贴士: 如果搭配AirAUDIO_1010扩展板测试,需将扩展板中PA开关拨到OFF,让软件控制PA,避免pop音。
本文以Air780EGH为例:
接线方式参照下方图表对应连接(注意不同型号核心板具体引脚号差异):


三、开源示例与教程
基于LuatOS开发的通话录音示例已上传Gitee开源仓库,即便是新接触LuatOS开发的朋友,也可以根据合宙资料中心提供配套实操教程快速上手。
最新示例源码:
实操教程详见:
https://docs.openluat.com/air780egh/luatos/app/volte/call_save_file/
核心功能模块包括SD卡挂载、通话状态机、录音数据回调等,完整示例代码详见源码仓库最新文件。
--[[
录音功能特性:
- 录音文件保存为PCM格式:/sd/record_call.pcm
- 只保存上行数据(包含本地声音和网络回声)
- 下行数据自动跳过,避免重复存储
- 支持SD卡自动挂载和空间检测
-- ====================== 录音功能 ======================
-- 创建音频数据缓冲区
local up1 = zbuff.create(BUFFER_SIZE,0) -- 上行数据保存区1
local up2 = zbuff.create(BUFFER_SIZE,0) -- 上行数据保存区2
local down1 = zbuff.create(BUFFER_SIZE,0) -- 下行数据保存区1
local down2 = zbuff.create(BUFFER_SIZE,0) -- 下行数据保存区2
-- 打开录音文件
local function open_record_file()
-- 先挂载SD卡
if not mount_sd_card() then
log.error("录音文件", "SD卡挂载失败,无法进行录音")
return false
end
log.info("录音文件", "SD卡挂载成功,录音文件将保存到SD卡")
-- 关闭已打开的文件
if record_file then
record_file:close()
record_file = nil
end
-- 删除旧录音文件
if io.exists(RECORD_FILE_PATH) then
os.remove(RECORD_FILE_PATH)
log.info("录音文件", "删除旧录音文件:", RECORD_FILE_PATH)
end
-- 创建录音文件
record_file = io.open(RECORD_FILE_PATH, "wb")
if record_file then
log.info("录音文件", "创建录音文件成功:", RECORD_FILE_PATH)
record_start_time = mcu.ticks()
is_recording_to_file = true
return true
else
log.error("录音文件", "创建录音文件失败:", RECORD_FILE_PATH)
return false
end
end
-- 关闭录音文件
local function close_record_file()
if record_file then
record_file:close()
record_file = nil
local file_size = io.fileSize(RECORD_FILE_PATH)
record_duration = (mcu.ticks() - record_start_time) / 1000 -- 转换为秒
log.info("录音文件", "录音完成", "文件大小:", file_size, "字节", "录音时长:", string.format("%.1f", record_duration), "秒", "路径:", RECORD_FILE_PATH)
is_recording_to_file = false
record_start_time = 0
record_duration = 0
end
end
-- 写入录音数据到文件
local function write_record_data(buff, is_downlink)
if not record_file or not is_recording_to_file then
return false
end
-- 保存数据
if not is_downlink then
local data_size = buff:used()
if data_size > 0 then
local start_time = mcu.ticks()
-- 写入数据到文件
record_file:write(buff:query())
local end_time = mcu.ticks()
local write_time = end_time - start_time
local write_speed = data_size / (write_time / 1000) -- 字节/秒
log.info("录音写入",
"数据大小:", data_size, "字节,",
"写入耗时:", string.format("%.2f", write_time), "ms,",
"写入速度:", string.format("%.2f", write_speed / 1024), "KB/s")
return true
end
else
-- 下行数据不保存,只记录日志
-- 写入下行数据会导致文件内有回声
local data_size = buff:used()
if data_size > 0 then
log.info("录音写入", "下行数据跳过", "数据大小:", data_size, "字节")
end
end
return false
end
-- 音频数据回调函数
local function recordCallback(is_dl, point)
if is_dl then
log.info("录音", "下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
-- 处理下行数据
if point == 0 then
write_record_data(down1, true)
down1:del() -- 清空缓冲区
else
write_record_data(down2, true)
down2:del() -- 清空缓冲区
end
else
log.info("录音", "上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
-- 处理上行数据
if point == 0 then
write_record_data(up1, false)
up1:del() -- 清空缓冲区
else
write_record_data(up2, false)
up2:del() -- 清空缓冲区
end
end
log.info("通话质量", cc.quality())
end
-- 启用通话录音
local function enableRecording()
cc.record(true, up1, up2, down1, down2)
cc.on("record", recordCallback)
log.info("cc_app", "通话录音已启用")
end
-- 开始通话录音到文件
local function start_call_recording()
if open_record_file() then
log.info("通话录音", "开始录音到文件:", RECORD_FILE_PATH)
return true
else
log.error("通话录音", "无法开始录音到文件,请检查SD卡")
return false
end
end
-- 停止通话录音到文件
local function stop_call_recording()
close_record_file()
log.info("通话录音", "停止录音到文件")
end
-- 获取所有缓冲区
local function getRecordingBuffers()
return {
up1 = up1,
up2 = up2,
down1 = down1,
down2 = down2
}
end
-- 获取录音文件信息
local function get_record_file_info()
if io.exists(RECORD_FILE_PATH) then
local file_size = io.fileSize(RECORD_FILE_PATH)
return {
path = RECORD_FILE_PATH,
size = file_size,
duration = record_duration,
exists = true
}
else
return {
path = RECORD_FILE_PATH,
size = 0,
duration = 0,
exists = false
}
end
end
-- 呼入自动接听,等待对方挂断
local function handle_scenario(status)
if status == "INCOMINGCALL" then
-- 获取来电号码
caller_number = cc.lastNum() or "未知号码"
call_counter = call_counter + 1
log.info("收到来电,号码:", caller_number, "响铃次数:", call_counter)
-- 响铃2声后自动接听
if call_counter >= 2 then
log.info("自动接听来电")
cc.accept(0)
call_counter = 0 -- 重置计数器
end
elseif status == "SPEECH_START" then
-- 语音通话真正开始
log.info("电话已接通,电话号码:", caller_number)
-- 开始通话录音到文件
start_call_recording()
elseif status == "DISCONNECTED" then
-- 对方挂断通话
log.info("通话结束对方挂断")
-- 停止通话录音到文件
stop_call_recording()
call_counter = 0 -- 重置计数器
end
end
-- ====================== 主事件处理器 ======================
sys.subscribe("CC_IND", function(status)
log.info("CC状态", status)
handle_scenario(status)
-- 需要处理的通用状态
if status == "READY" then
sys.publish("CC_READY") -- 发布系统就绪事件
elseif status == "HANGUP_CALL_DONE" or status == "MAKE_CALL_FAILED" or status == "DISCONNECTED" then
exaudio.pm(audio.SHUTDOWN) --主动进入低功耗模式
end
end)
-- ====================== 电话系统初始化 ======================
local function init_cc()
-- 先尝试挂载SD卡
mount_sd_card()
-- 初始化音频设备
audio_drv.initAudioDevice()
-- 等待电话系统就绪
sys.waitUntil("CC_READY")
-- 初始化电话功能
cc.init(audio_drv.getMultimediaId())
-- 启用通话录音(录音功能在cc_app中)
enableRecording()
log.info("cc_app", "电话系统初始化完成")
end
-- 启动初始化任务
sys.taskInit(init_cc)
四、使用注意事项
必须插入SD卡才能使用录音功能,因为录音文件较大无法存入内存;缓冲区大小必须是640的倍数,否则可能导致录音异常。
录音文件保存在SD卡的/sd/record_call.pcm路径下,可以通过读卡器在电脑上查看;录音文件为原始PCM格式,需要使用专用播放器(如Audacity)播放。


今天的内容就先分享到这儿了~
合宙LuatOS提供一站式物联网高效解决方案,集成高速通信、外设驱动、UI交互及视觉处理,助力行业客户快速落地产品,同时实现极致低功耗——相较于传统的“串口屏+DTU方案”或安卓方案,功耗减半,成本不足三分之一。更多开发资料,详见合宙资料中心:docs.openluat.com

浙公网安备 33010602011771号