录音
一、录音概述
录音技术是通过麦克风将声波振动转换为模拟电信号,经模数转换(ADC)采样、量化后编码为数字音频(如 WAV、MP3 格式),并存储于磁带、光盘或闪存介质中的过程,核心环节包括声学采集(指向性麦克风)、信号放大、降噪处理(如 DSP 算法)、动态范围压缩及编解码优化,现代技术进一步融合高保真(Hi-Res)、空间音频(3D 录音)与云端同步能力,广泛应用于音乐制作、通信、安防及多媒体领域。
二、演示功能概述
本篇文章演示的内容为:Air780EHV 核心板,外接 AirAUDIO_1000 音频扩展板,接上喇叭,录制音频,提供了两种录制方式:第一种是录制成文件,第二种是录制到内存,然后进行录音的播放,然后提供了两种录音处理方式,第一种是发送到服务器,第二种是发送到串口。
三、准备硬件环境
参考:Air780EHV 硬件环境清单,准备好硬件环境,本篇文档使用的是 AirEHV 核心板和 AirAUDIO_1000 音频扩展板,如下图所示:
连接图:
780EHV 核心板 连接 AirAUDIO_1000
3/MIC+ ———————— MIC+
4/MIC- ————————MIC-
5/SPK+ ————————SPK+
6/SPK- ————————SPK-
20/GPIO22 ————————PA_EN
3V3 ————————VCC
GND ————————GND
四、准备软件环境
烧录工具:Luatools 工具
Air780EHV 烧录需要的固件和脚本文件:
LuatOS 运行所需要的 lib 文件:
使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件
五、代码 API 和代码解析
5.1 代码 API
API 链接:
audio
codec
5.2 代码解析
1.设置 audio.on 的回调函数,根据 event 对录音数据的 point 存在对应的 buff 里面,再根据 event 判断是否为录音完成,录音完成通过 sys.publish 发送内部消息,激活录音完成之后的等待,然后根据播放结果,返回对应内容
--代码提供了2种方式录音,对应recordmode的值
--1:直接录音到文件
--2:录音到内存,然后保存到文件
local recordmode = 1
--代码提供了2种方式对录音文件做处理
--1:发送到服务器
--2:发送到串口
local recordhandle = 1
local taskName = "task_audio"
local MSG_MD = "moreData" -- 播放缓存有空余
local MSG_PD = "playDone" -- 播放完成所有数据
-- amr数据存放buffer,尽可能地给大一些
amr_buff = zbuff.create(20 * 1024)
-- 创建一个amr的encoder
encoder = nil
pcm_buff0 = zbuff.create(16000)
pcm_buff1 = zbuff.create(16000)
audio.on(0,function(id, event, point)
-- 使用play来播放文件时只有播放完成回调
if event == audio.RECORD_DATA then -- 录音数据
if point == 0 then
log.info("buff", point, pcm_buff0:used())
codec.encode(encoder, pcm_buff0, amr_buff)
else
log.info("buff", point, pcm_buff1:used())
codec.encode(encoder, pcm_buff1, amr_buff)
end
elseif event == audio.RECORD_DONE then -- 录音完成
sys.publish("AUDIO_RECORD_DONE")
else
local succ, stop, file_cnt = audio.getError(0)
if not succ then
if stop then
log.info("用户停止播放")
else
log.info("第", file_cnt, "个文件解码失败")
end
end
-- log.info("播放完成一个音频")
sysplus.sendMsg(taskName, MSG_PD)
end
end
)
2.通过 HTTP 的 post 方式,利用 multipart/form-data 的方式,把录音文件传到服务器上。
---- MultipartForm上传文件
-- url string 请求URL地址
-- filename string 上传服务器的文件名
-- filePath string 待上传文件的路径
local function postMultipartFormData(url, filename, filePath)
local boundary = "----WebKitFormBoundary"..os.time()
local req_headers = {
["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
}
local body = {}
table.insert(body, "--"..boundary.."\r\nContent-Disposition: form-data; name=\"file\"; filename=\"".. filename .."\"\r\n\r\n")
table.insert(body, io.readFile(filePath))
table.insert(body, "\r\n")
table.insert(body, "--"..boundary.."--\r\n")
body = table.concat(body)
log.info("headers: ", "\r\n" .. json.encode(req_headers), type(body))
log.info("body: " .. body:len() .. "\r\n" .. body)
local code, headers, body = http.request("POST",url,
req_headers,
body
).wait()
log.info("http.post", code, headers, body)
end
-- 下面的演示是将音频文件发送到服务器上,如有需要,可以将下面代码注释打开,这里的url是合宙的文件上传测试服务器,上传的文件到http://tools.openluat.com/tools/device-upload-test查看
--[[
local timeTable = os.date("*t", os.time())
local nowTime = string.format("%4d%02d%02d_%02d%02d%02d", timeTable.year, timeTable.month, timeTable.day, timeTable.hour, timeTable.min, timeTable.sec)
local filename = mobile.imei() .. "_" .. nowTime .. ".amr"
postMultipartFormData("http://tools.openluat.com/api/site/device_upload_file", filename, recordPath)
]]
3.设置 i2s 和 audio 的一些参数,控制 8311 上电,拉高 PA 使能脚,然后设置 i2c,i2s,audio 的一些基础配置。
function audio_setup()
local i2c_id = 0 -- i2c_id 0
local pa_pin = gpio.AUDIOPA_EN -- 喇叭pa功放脚
local power_pin = 20 -- es8311电源脚
local i2s_id = 0 -- i2s_id 0
local i2s_mode = 0 -- i2s模式 0 主机 1 从机
local i2s_sample_rate = 16000 -- 采样率
local i2s_bits_per_sample = 16 -- 数据位数
local i2s_channel_format = i2s.MONO_R -- 声道, 0 左声道, 1 右声道, 2 立体声
local i2s_communication_format = i2s.MODE_LSB -- 格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
local i2s_channel_bits = 16 -- 声道的BCLK数量
local multimedia_id = 0 -- 音频通道 0
local pa_on_level = 1 -- PA打开电平 1 高电平 0 低电平
local power_delay = 3 -- 在DAC启动前插入的冗余时间,单位100ms
local pa_delay = 100 -- 在DAC启动后,延迟多长时间打开PA,单位1ms
local power_on_level = 1 -- 电源控制IO的电平,默认拉高
local power_time_delay = 100 -- 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms
local voice_vol = 80 -- 喇叭音量
local mic_vol = 80 -- 麦克风音量
gpio.setup(power_pin, 1, gpio.PULLUP) -- 设置ES83111电源脚
gpio.setup(pa_pin, 1, gpio.PULLUP) -- 设置功放PA脚
sys.wait(200)
i2c.setup(i2c_id, i2c.FAST) -- 设置i2c
i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,
i2s_channel_bits) -- 设置i2s
audio.config(multimedia_id, pa_pin, pa_on_level, power_delay, pa_delay, power_pin, power_on_level, power_time_delay)
audio.setBus(multimedia_id, audio.BUS_I2S, {
chip = "es8311",
i2cid = i2c_id,
i2sid = i2s_id,
}) -- 通道0的硬件输出通道设置为I2S
audio.vol(multimedia_id, voice_vol)
audio.micVol(multimedia_id, mic_vol)
sys.publish("AUDIO_READY")
end
-- 配置好audio外设
sys.taskInit(audio_setup)
4.本函数主要有两种录音的方式,第一种是直接录音到文件里面,然后等待录音完成的激活,然后播放音频,等待播放完成的激活,第二种方式是录音到内存,通过数据处理的方式保存成文件,等待录音完成的激活,然后播放音频,等待播放完成的激活,最后有两个对录音文件的扩展,第一个是上面已经讲解过的:利用 http 的方式把文件传到服务器上。第二个是:把录音文件通过串口 1,传到串口上。
local function audio_task()
sys.waitUntil("AUDIO_READY")
sys.wait(5000)
local result
-- 下面为录音demo,根据适配情况选择性开启
local recordPath = "/record.amr"
if recordmode == 1 then
-- -- 直接录音到文件
err = audio.record(0, audio.AMR, 5, 7, recordPath)
sys.waitUntil("AUDIO_RECORD_DONE")
log.info("record", "录音结束")
elseif recordmode == 2 then
-- 录音到内存自行编码
encoder = codec.create(codec.AMR, false, 7)
log.info("encoder", encoder)
log.info("开始录音")
err = audio.record(0, audio.AMR, 5, 7, nil, nil, pcm_buff0, pcm_buff1)
sys.waitUntil("AUDIO_RECORD_DONE")
log.info("record", "录音结束")
os.remove(recordPath)
io.writeFile(recordPath, "#!AMR\n")
io.writeFile(recordPath, amr_buff:query(), "a+b")
end
result = audio.play(0, {recordPath})
if result then
-- 等待音频通道的回调消息,或者切换歌曲的消息
while true do
msg = sysplus.waitMsg(taskName, nil)
if type(msg) == "table" then
if msg[1] == MSG_PD then
log.info("播放结束")
break
end
else
log.error(type(msg), msg)
end
end
else
log.debug("解码失败!")
sys.wait(1000)
end
-- 下面的演示是将音频文件发送到服务器上,如有需要,可以将下面代码注释打开,这里的url是合宙的文件上传测试服务器,上传的文件到http://tools.openluat.com/tools/device-upload-test查看
if recordhandle == 1 then
local timeTable = os.date("*t", os.time())
local nowTime =
string.format(
"%4d%02d%02d_%02d%02d%02d",
timeTable.year,
timeTable.month,
timeTable.day,
timeTable.hour,
timeTable.min,
timeTable.sec
)
local filename = mobile.imei() .. "_" .. nowTime .. ".amr"
postMultipartFormData("http://tools.openluat.com/api/site/device_upload_file", filename, recordPath)
elseif recordhandle == 2 then
-- 该方法为从串口1,把录音数据传给串口1
uart.setup(1, 115200) -- 开启串口1
uart.write(1, io.readFile(recordPath)) -- 向串口发送录音文件
end
end
sysplus.taskInitEx(audio_task, taskName)
六、运行结果展示
6.1 完整代码
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "record"
VERSION = "1.0.0"
--[[]
运行环境:Air780EHV核心板+AirAUDIO_1000配件板
最后修改时间:2025-6-17
使用了如下IO口:
[3, "MIC+", " PIN3脚, 用于麦克风正极"],
[4, "MIC-", " PIN4脚, 用于麦克风负极"],
[5, "spk+", " PIN5脚, 用于喇叭正极"],
[6, "spk-", " PIN6脚, 用于喇叭负极"],
[20, "AudioPA_EN", " PIN20脚, 用于PA使能脚"],
3.3V
GND
执行逻辑为:
设置i2s和音频参数,提供了两种录音方式,录音到文件区或者录音到内存,然后播放,然后
]]
-- sys库是标配
_G.sys = require("sys")
_G.sysplus = require("sysplus")
--代码提供了2种方式录音,对应recordmode的值
--1:直接录音到文件
--2:录音到内存,然后保存到文件
local recordmode = 1
--代码提供了2种方式对录音文件做处理
--1:发送到服务器
--2:发送到串口
local recordhandle = 1
local taskName = "task_audio"
local MSG_MD = "moreData" -- 播放缓存有空余
local MSG_PD = "playDone" -- 播放完成所有数据
-- amr数据存放buffer,尽可能地给大一些
amr_buff = zbuff.create(20 * 1024)
-- 创建一个amr的encoder
encoder = nil
pcm_buff0 = zbuff.create(16000)
pcm_buff1 = zbuff.create(16000)
audio.on(0,function(id, event, point)
-- 使用play来播放文件时只有播放完成回调
if event == audio.RECORD_DATA then -- 录音数据
if point == 0 then
log.info("buff", point, pcm_buff0:used())
codec.encode(encoder, pcm_buff0, amr_buff)
else
log.info("buff", point, pcm_buff1:used())
codec.encode(encoder, pcm_buff1, amr_buff)
end
elseif event == audio.RECORD_DONE then -- 录音完成
sys.publish("AUDIO_RECORD_DONE")
else
local succ, stop, file_cnt = audio.getError(0)
if not succ then
if stop then
log.info("用户停止播放")
else
log.info("第", file_cnt, "个文件解码失败")
end
end
-- log.info("播放完成一个音频")
sysplus.sendMsg(taskName, MSG_PD)
end
end
)
---- MultipartForm上传文件
-- url string 请求URL地址
-- filename string 上传服务器的文件名
-- filePath string 待上传文件的路径
local function postMultipartFormData(url, filename, filePath)
local boundary = "----WebKitFormBoundary" .. os.time()
local req_headers = {
["Content-Type"] = "multipart/form-data; boundary=" .. boundary
}
local body = {}
table.insert(
body,
"--" .. boundary .. '\r\nContent-Disposition: form-data; name="file"; filename="' .. filename .. '"\r\n\r\n'
)
table.insert(body, io.readFile(filePath))
table.insert(body, "\r\n")
table.insert(body, "--" .. boundary .. "--\r\n")
body = table.concat(body)
log.info("headers: ", "\r\n" .. json.encode(req_headers), type(body))
log.info("body: " .. body:len() .. "\r\n" .. body)
local code, headers, body = http.request("POST", url, req_headers, body).wait()
log.info("http.post", code, headers, body)
end
function audio_setup()
local i2c_id = 0 -- i2c_id 0
local pa_pin = gpio.AUDIOPA_EN -- 喇叭pa功放脚
local power_pin = 20 -- es8311电源脚
local i2s_id = 0 -- i2s_id 0
local i2s_mode = 0 -- i2s模式 0 主机 1 从机
local i2s_sample_rate = 16000 -- 采样率
local i2s_bits_per_sample = 16 -- 数据位数
local i2s_channel_format = i2s.MONO_R -- 声道, 0 左声道, 1 右声道, 2 立体声
local i2s_communication_format = i2s.MODE_LSB -- 格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
local i2s_channel_bits = 16 -- 声道的BCLK数量
local multimedia_id = 0 -- 音频通道 0
local pa_on_level = 1 -- PA打开电平 1 高电平 0 低电平
local power_delay = 3 -- 在DAC启动前插入的冗余时间,单位100ms
local pa_delay = 100 -- 在DAC启动后,延迟多长时间打开PA,单位1ms
local power_on_level = 1 -- 电源控制IO的电平,默认拉高
local power_time_delay = 100 -- 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms
local voice_vol = 80 -- 喇叭音量
local mic_vol = 80 -- 麦克风音量
gpio.setup(power_pin, 1, gpio.PULLUP) -- 设置ES83111电源脚
gpio.setup(pa_pin, 1, gpio.PULLUP) -- 设置功放PA脚
sys.wait(200)
i2c.setup(i2c_id, i2c.FAST) -- 设置i2c
i2s.setup(
i2s_id,
i2s_mode,
i2s_sample_rate,
i2s_bits_per_sample,
i2s_channel_format,
i2s_communication_format,
i2s_channel_bits
) -- 设置i2s
audio.config(multimedia_id, pa_pin, pa_on_level, power_delay, pa_delay, power_pin, power_on_level, power_time_delay)
audio.setBus(
multimedia_id,
audio.BUS_I2S,
{
chip = "es8311",
i2cid = i2c_id,
i2sid = i2s_id
}
) -- 通道0的硬件输出通道设置为I2S
audio.vol(multimedia_id, voice_vol)
audio.micVol(multimedia_id, mic_vol)
sys.publish("AUDIO_READY")
end
-- 配置好audio外设
sys.taskInit(audio_setup)
local function audio_task()
sys.waitUntil("AUDIO_READY")
sys.wait(5000)
local result
-- 下面为录音demo,根据适配情况选择性开启
local recordPath = "/record.amr"
if recordmode == 1 then
-- -- 直接录音到文件
err = audio.record(0, audio.AMR, 5, 7, recordPath)
sys.waitUntil("AUDIO_RECORD_DONE")
log.info("record", "录音结束")
elseif recordmode == 2 then
-- 录音到内存自行编码
encoder = codec.create(codec.AMR, false, 7)
log.info("encoder", encoder)
log.info("开始录音")
err = audio.record(0, audio.AMR, 5, 7, nil, nil, pcm_buff0, pcm_buff1)
sys.waitUntil("AUDIO_RECORD_DONE")
log.info("record", "录音结束")
os.remove(recordPath)
io.writeFile(recordPath, "#!AMR\n")
io.writeFile(recordPath, amr_buff:query(), "a+b")
end
result = audio.play(0, {recordPath})
if result then
-- 等待音频通道的回调消息,或者切换歌曲的消息
while true do
msg = sysplus.waitMsg(taskName, nil)
if type(msg) == "table" then
if msg[1] == MSG_PD then
log.info("播放结束")
break
end
else
log.error(type(msg), msg)
end
end
else
log.debug("解码失败!")
sys.wait(1000)
end
-- 下面的演示是将音频文件发送到服务器上,如有需要,可以将下面代码注释打开,这里的url是合宙的文件上传测试服务器,上传的文件到http://tools.openluat.com/tools/device-upload-test查看
if recordhandle == 1 then
local timeTable = os.date("*t", os.time())
local nowTime =
string.format(
"%4d%02d%02d_%02d%02d%02d",
timeTable.year,
timeTable.month,
timeTable.day,
timeTable.hour,
timeTable.min,
timeTable.sec
)
local filename = mobile.imei() .. "_" .. nowTime .. ".amr"
postMultipartFormData("http://tools.openluat.com/api/site/device_upload_file", filename, recordPath)
elseif recordhandle == 2 then
-- 该方法为从串口1,把录音数据传给串口1
uart.setup(1, 115200) -- 开启串口1
uart.write(1, io.readFile(recordPath)) -- 向串口发送录音文件
end
end
sysplus.taskInitEx(audio_task, taskName)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
6.2 结果演示
七、总结
本文演示如何 Air780EHV 核心板外接 AirAUDIO_1000 音频扩展板然后录音,提供了两种录音方式,
1:录音到文件,通过使用 audio.record,填写文件地址的方式,保存到文件
2:录音到内存,利用存到 zbuf 的方式录音,然后写入文件
然后编码存储到文件。然后进行播放,最后提供了两种扩展的应用方式
1:把录音文件发送到服务器
2:把录音文件发送到串口。
八、常见问题
后续在此扩展补充,敬请期待......