工程提交
This commit is contained in:
544
4G/tools/resource/soc_script/v2025.12.31.22/lib/exaudio.lua
Normal file
544
4G/tools/resource/soc_script/v2025.12.31.22/lib/exaudio.lua
Normal file
@@ -0,0 +1,544 @@
|
||||
--[[
|
||||
@module exaudio
|
||||
@summary exaudio扩展库
|
||||
@version 1.1
|
||||
@date 2025.09.01
|
||||
@author 梁健
|
||||
@usage
|
||||
]]
|
||||
local exaudio = {}
|
||||
|
||||
-- 常量定义
|
||||
local I2S_ID = 0
|
||||
local I2S_MODE = 0 -- 0:主机 1:从机
|
||||
local I2S_SAMPLE_RATE = 16000
|
||||
local I2S_CHANNEL_FORMAT = i2s.MONO_R
|
||||
local I2S_COMM_FORMAT = i2s.MODE_LSB -- 可选MODE_I2S, MODE_LSB, MODE_MSB
|
||||
local I2S_CHANNEL_BITS = 16
|
||||
local MULTIMEDIA_ID = 0
|
||||
local EX_MSG_PLAY_DONE = "playDone"
|
||||
local ES8311_ADDR = 0x18 -- 7位地址
|
||||
local CHIP_ID_REG = 0x00 -- 芯片ID寄存器地址
|
||||
|
||||
-- 模块常量
|
||||
exaudio.PLAY_DONE = 1 -- 音频播放完毕的事件之一
|
||||
exaudio.RECORD_DONE = 1 -- 音频录音完毕的事件之一
|
||||
exaudio.AMR_NB = 0
|
||||
exaudio.AMR_WB = 1
|
||||
exaudio.PCM_8000 = 2
|
||||
exaudio.PCM_16000 = 3
|
||||
exaudio.PCM_24000 = 4
|
||||
exaudio.PCM_32000 = 5
|
||||
exaudio.PCM_48000 = 6
|
||||
|
||||
|
||||
-- 默认配置参数
|
||||
local audio_setup_param = {
|
||||
model = "es8311", -- dac类型: "es8311","es8211"
|
||||
i2c_id = 0, -- i2c_id: 0,1
|
||||
pa_ctrl = 0, -- 音频放大器电源控制管脚
|
||||
dac_ctrl = 0, -- 音频编解码芯片电源控制管脚
|
||||
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 audio_play_param = {
|
||||
type = 0, -- 0:文件 1:TTS 2:流式
|
||||
content = nil, -- 播放内容
|
||||
cbfnc = nil, -- 播放完毕回调
|
||||
priority = 0, -- 优先级(数值越大越高)
|
||||
sampling_rate = 16000, -- 采样率(仅流式)
|
||||
sampling_depth = 16, -- 采样位深(仅流式)
|
||||
signed_or_unsigned = true -- PCM是否有符号(仅流式)
|
||||
}
|
||||
|
||||
local audio_record_param = {
|
||||
format = 0, -- 录制格式,支持exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000,exaudio.PCM_48000
|
||||
time = 5, -- 录制时间(秒)
|
||||
path = nil, -- 文件路径或流式回调
|
||||
cbfnc = nil -- 录音完毕回调
|
||||
}
|
||||
|
||||
-- 内部变量
|
||||
local pcm_buff0 = nil
|
||||
local pcm_buff1 = nil
|
||||
local voice_vol = 55
|
||||
local mic_vol = 80
|
||||
|
||||
-- 定义全局队列表
|
||||
local audio_play_queue = {
|
||||
data = {}, -- 存储字符串的数组
|
||||
sequenceIndex = 1 -- 用于跟踪插入顺序的索引
|
||||
}
|
||||
|
||||
-- 向队列中添加字符串(按调用顺序插入)
|
||||
local function audio_play_queue_push(str)
|
||||
if type(str) == "string" then
|
||||
-- 存储格式: {index = 顺序索引, value = 字符串值}
|
||||
table.insert(audio_play_queue.data, {
|
||||
index = audio_play_queue.sequenceIndex,
|
||||
value = str
|
||||
})
|
||||
audio_play_queue.sequenceIndex = audio_play_queue.sequenceIndex + 1
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 从队列中取出最早插入的字符串(按顺序取出)
|
||||
local function audio_play_queue_pop()
|
||||
if #audio_play_queue.data > 0 then
|
||||
-- 取出并移除第一个元素
|
||||
local item = table.remove(audio_play_queue.data, 1)
|
||||
return item.value -- 返回值
|
||||
end
|
||||
return nil
|
||||
end
|
||||
-- 清空队列中所有数据
|
||||
function audio_queue_clear()
|
||||
-- 清空数组
|
||||
audio_play_queue.data = {}
|
||||
-- 重置顺序索引
|
||||
audio_play_queue.sequenceIndex = 1
|
||||
return true
|
||||
end
|
||||
|
||||
-- 工具函数:参数检查
|
||||
local function check_param(param, expected_type, name)
|
||||
if type(param) ~= expected_type then
|
||||
log.error(string.format("参数错误: %s 应为 %s 类型", name, expected_type))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- 音频回调处理
|
||||
local function audio_callback(id, event, point)
|
||||
-- log.info("audio_callback", "event:", event,
|
||||
-- "MORE_DATA:", audio.MORE_DATA,
|
||||
-- "DONE:", audio.DONE,
|
||||
-- "RECORD_DATA:", audio.RECORD_DATA,
|
||||
-- "RECORD_DONE:", audio.RECORD_DONE)
|
||||
|
||||
if event == audio.MORE_DATA then
|
||||
audio.write(MULTIMEDIA_ID,audio_play_queue_pop())
|
||||
elseif event == audio.DONE then
|
||||
if type(audio_play_param.cbfnc) == "function" then
|
||||
audio_play_param.cbfnc(exaudio.PLAY_DONE)
|
||||
end
|
||||
audio_queue_clear() -- 清空流式播放数据队列
|
||||
sys.publish(EX_MSG_PLAY_DONE)
|
||||
|
||||
elseif event == audio.RECORD_DATA then
|
||||
if type(audio_record_param.path) == "function" then
|
||||
local buff, len = point == 0 and pcm_buff0 or pcm_buff1,
|
||||
point == 0 and pcm_buff0:used() or pcm_buff1:used()
|
||||
audio_record_param.path(buff, len)
|
||||
end
|
||||
|
||||
elseif event == audio.RECORD_DONE then
|
||||
if type(audio_record_param.cbfnc) == "function" then
|
||||
audio_record_param.cbfnc(exaudio.RECORD_DONE)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 读取ES8311芯片ID
|
||||
local function read_es8311_id()
|
||||
|
||||
|
||||
-- 发送读取请求
|
||||
local send_ok = i2c.send(audio_setup_param.i2c_id, ES8311_ADDR, CHIP_ID_REG)
|
||||
if not send_ok then
|
||||
log.error("发送芯片ID读取请求失败")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 读取数据
|
||||
local data = i2c.recv(audio_setup_param.i2c_id, ES8311_ADDR, 1)
|
||||
if data and #data == 1 then
|
||||
return true
|
||||
end
|
||||
|
||||
log.error("读取ES8311芯片ID失败")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 音频硬件初始化
|
||||
local function audio_setup()
|
||||
-- I2C配置
|
||||
if not i2c.setup(audio_setup_param.i2c_id, i2c.FAST) then
|
||||
log.error("I2C初始化失败")
|
||||
return false
|
||||
end
|
||||
-- 初始化I2S
|
||||
local result, data = i2s.setup(
|
||||
I2S_ID,
|
||||
I2S_MODE,
|
||||
I2S_SAMPLE_RATE,
|
||||
audio_setup_param.bits_per_sample,
|
||||
I2S_CHANNEL_FORMAT,
|
||||
I2S_COMM_FORMAT,
|
||||
I2S_CHANNEL_BITS
|
||||
)
|
||||
|
||||
if not result then
|
||||
log.error("I2S设置失败")
|
||||
return false
|
||||
end
|
||||
-- 配置音频通道
|
||||
audio.config(
|
||||
MULTIMEDIA_ID,
|
||||
audio_setup_param.pa_ctrl,
|
||||
audio_setup_param.pa_on_level,
|
||||
audio_setup_param.dac_delay,
|
||||
audio_setup_param.pa_delay,
|
||||
audio_setup_param.dac_ctrl,
|
||||
1, -- power_on_level
|
||||
audio_setup_param.dac_time_delay
|
||||
)
|
||||
-- 设置总线
|
||||
audio.setBus(
|
||||
MULTIMEDIA_ID,
|
||||
audio.BUS_I2S,
|
||||
{
|
||||
chip = audio_setup_param.model,
|
||||
i2cid = audio_setup_param.i2c_id,
|
||||
i2sid = I2S_ID,
|
||||
voltage = audio.VOLTAGE_1800
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
-- 设置音量
|
||||
audio.vol(MULTIMEDIA_ID, voice_vol)
|
||||
audio.micVol(MULTIMEDIA_ID, mic_vol)
|
||||
audio.pm(MULTIMEDIA_ID, audio.RESUME)
|
||||
|
||||
-- 检查芯片连接
|
||||
if audio_setup_param.model == "es8311" and not read_es8311_id() then
|
||||
log.error("ES8311通讯失败,请检查硬件")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 注册回调
|
||||
audio.on(MULTIMEDIA_ID, audio_callback)
|
||||
return true
|
||||
end
|
||||
|
||||
-- 模块接口:初始化
|
||||
function exaudio.setup(audioConfigs)
|
||||
-- 检查必要参数
|
||||
if not audio then
|
||||
log.error("不支持audio 库,请选择支持audio 的core")
|
||||
return false
|
||||
end
|
||||
if not audioConfigs or type(audioConfigs) ~= "table" then
|
||||
log.error("配置参数必须为table类型")
|
||||
return false
|
||||
end
|
||||
-- 检查codec型号
|
||||
if not audioConfigs.model or
|
||||
(audioConfigs.model ~= "es8311" and audioConfigs.model ~= "es8211") then
|
||||
log.error("请指定正确的codec型号(es8311或es8211)")
|
||||
return false
|
||||
end
|
||||
audio_setup_param.model = audioConfigs.model
|
||||
-- 针对ES8311的特殊检查
|
||||
if audioConfigs.model == "es8311" then
|
||||
if not check_param(audioConfigs.i2c_id, "number", "i2c_id") then
|
||||
return false
|
||||
end
|
||||
audio_setup_param.i2c_id = audioConfigs.i2c_id
|
||||
end
|
||||
|
||||
-- 检查功率放大器控制管脚
|
||||
if audioConfigs.pa_ctrl == nil then
|
||||
log.warn("pa_ctrl(功率放大器控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
|
||||
end
|
||||
audio_setup_param.pa_ctrl = audioConfigs.pa_ctrl
|
||||
|
||||
-- 检查功率放大器控制管脚
|
||||
if audioConfigs.dac_ctrl == nil then
|
||||
log.warn("dac_ctrl(音频编解码控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
|
||||
end
|
||||
audio_setup_param.dac_ctrl = audioConfigs.dac_ctrl
|
||||
|
||||
|
||||
-- 处理可选参数
|
||||
local optional_params = {
|
||||
{name = "dac_delay", type = "number"},
|
||||
{name = "pa_delay", type = "number"},
|
||||
{name = "dac_time_delay", type = "number"},
|
||||
{name = "bits_per_sample", type = "number"},
|
||||
{name = "pa_on_level", type = "number"}
|
||||
}
|
||||
|
||||
for _, param in ipairs(optional_params) do
|
||||
if audioConfigs[param.name] ~= nil then
|
||||
if check_param(audioConfigs[param.name], param.type, param.name) then
|
||||
audio_setup_param[param.name] = audioConfigs[param.name]
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 确保采样位数有默认值
|
||||
audio_setup_param.bits_per_sample = audio_setup_param.bits_per_sample or 16
|
||||
return audio_setup()
|
||||
end
|
||||
|
||||
-- 模块接口:开始播放
|
||||
function exaudio.play_start(playConfigs)
|
||||
if not playConfigs or type(playConfigs) ~= "table" then
|
||||
log.error("播放配置必须为table类型")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 检查播放类型
|
||||
if not check_param(playConfigs.type, "number", "type") then
|
||||
log.error("type必须为数值(0:文件,1:TTS,2:流式)")
|
||||
return false
|
||||
end
|
||||
audio_play_param.type = playConfigs.type
|
||||
|
||||
-- 处理优先级
|
||||
if playConfigs.priority ~= nil then
|
||||
if check_param(playConfigs.priority, "number", "priority") then
|
||||
if playConfigs.priority > audio_play_param.priority then
|
||||
log.error("是否完成播放",audio.isEnd(MULTIMEDIA_ID))
|
||||
if not audio.isEnd(MULTIMEDIA_ID) then
|
||||
if audio.play(MULTIMEDIA_ID) ~= true then
|
||||
return false
|
||||
end
|
||||
sys.waitUntil(EX_MSG_PLAY_DONE)
|
||||
end
|
||||
audio_play_param.priority = playConfigs.priority
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- 处理不同播放类型
|
||||
local play_type = audio_play_param.type
|
||||
if play_type == 0 then -- 文件播放
|
||||
if not playConfigs.content then
|
||||
log.error("文件播放需要指定content(文件路径或路径表)")
|
||||
return false
|
||||
end
|
||||
|
||||
local content_type = type(playConfigs.content)
|
||||
if content_type == "table" then
|
||||
for _, path in ipairs(playConfigs.content) do
|
||||
if type(path) ~= "string" then
|
||||
log.error("播放列表元素必须为字符串路径")
|
||||
return false
|
||||
end
|
||||
end
|
||||
elseif content_type ~= "string" then
|
||||
log.error("文件播放content必须为字符串或路径表")
|
||||
return false
|
||||
end
|
||||
|
||||
audio_play_param.content = playConfigs.content
|
||||
if audio.play(MULTIMEDIA_ID, audio_play_param.content) ~= true then
|
||||
return false
|
||||
end
|
||||
|
||||
elseif play_type == 1 then -- TTS播放
|
||||
if not audio.tts then
|
||||
log.error("本固件不支持TTS,请更换支持TTS 的固件")
|
||||
return false
|
||||
end
|
||||
if not check_param(playConfigs.content, "string", "content") then
|
||||
log.error("TTS播放content必须为字符串")
|
||||
return false
|
||||
end
|
||||
audio_play_param.content = playConfigs.content
|
||||
if audio.tts(MULTIMEDIA_ID, audio_play_param.content) ~= true then
|
||||
return false
|
||||
end
|
||||
|
||||
elseif play_type == 2 then -- 流式播放
|
||||
if not check_param(playConfigs.sampling_rate, "number", "sampling_rate") then
|
||||
return false
|
||||
end
|
||||
if not check_param(playConfigs.sampling_depth, "number", "sampling_depth") then
|
||||
return false
|
||||
end
|
||||
|
||||
audio_play_param.content = playConfigs.content
|
||||
audio_play_param.sampling_rate = playConfigs.sampling_rate
|
||||
audio_play_param.sampling_depth = playConfigs.sampling_depth
|
||||
|
||||
if playConfigs.signed_or_unsigned ~= nil then
|
||||
audio_play_param.signed_or_unsigned = playConfigs.signed_or_unsigned
|
||||
end
|
||||
|
||||
audio.start(
|
||||
MULTIMEDIA_ID,
|
||||
audio.PCM,
|
||||
1,
|
||||
playConfigs.sampling_rate,
|
||||
playConfigs.sampling_depth,
|
||||
audio_play_param.signed_or_unsigned
|
||||
)
|
||||
-- 发送初始数据
|
||||
if audio.write(MULTIMEDIA_ID, string.rep("\0", 512)) ~= true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- 处理回调函数
|
||||
if playConfigs.cbfnc ~= nil then
|
||||
if check_param(playConfigs.cbfnc, "function", "cbfnc") then
|
||||
audio_play_param.cbfnc = playConfigs.cbfnc
|
||||
else
|
||||
return false
|
||||
end
|
||||
else
|
||||
audio_play_param.cbfnc = nil
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- 模块接口:流式播放数据写入
|
||||
function exaudio.play_stream_write(data)
|
||||
audio_play_queue_push(data)
|
||||
return true
|
||||
end
|
||||
|
||||
-- 模块接口:停止播放
|
||||
function exaudio.play_stop()
|
||||
return audio.play(MULTIMEDIA_ID)
|
||||
end
|
||||
|
||||
-- 模块接口:检查播放是否结束
|
||||
function exaudio.is_end()
|
||||
return audio.isEnd(MULTIMEDIA_ID)
|
||||
end
|
||||
|
||||
-- 模块接口:获取错误信息
|
||||
function exaudio.get_error()
|
||||
return audio.getError(MULTIMEDIA_ID)
|
||||
end
|
||||
|
||||
-- 模块接口:开始录音
|
||||
function exaudio.record_start(recodConfigs)
|
||||
if not recodConfigs or type(recodConfigs) ~= "table" then
|
||||
log.error("录音配置必须为table类型")
|
||||
return false
|
||||
end
|
||||
-- 检查录音格式
|
||||
if recodConfigs.format == nil or type(recodConfigs.format) ~= "number" or recodConfigs.format > 6 then
|
||||
log.error("请指定正确的录音格式")
|
||||
return false
|
||||
end
|
||||
audio_record_param.format = recodConfigs.format
|
||||
|
||||
-- 处理录音时间
|
||||
if recodConfigs.time ~= nil then
|
||||
if check_param(recodConfigs.time, "number", "time") then
|
||||
audio_record_param.time = recodConfigs.time
|
||||
else
|
||||
return false
|
||||
end
|
||||
else
|
||||
audio_record_param.time = 0
|
||||
end
|
||||
|
||||
-- 处理存储路径/回调
|
||||
if not recodConfigs.path then
|
||||
log.error("必须指定录音路径或流式回调函数")
|
||||
return false
|
||||
end
|
||||
audio_record_param.path = recodConfigs.path
|
||||
|
||||
-- 转换录音格式
|
||||
local recod_format, amr_quailty
|
||||
if audio_record_param.format == exaudio.AMR_NB then
|
||||
recod_format = audio.AMR_NB
|
||||
amr_quailty = 7
|
||||
elseif audio_record_param.format == exaudio.AMR_WB then
|
||||
recod_format = audio.AMR_WB
|
||||
amr_quailty = 8
|
||||
elseif audio_record_param.format == exaudio.PCM_8000 then
|
||||
recod_format = 8000
|
||||
elseif audio_record_param.format == exaudio.PCM_16000 then
|
||||
recod_format = 16000
|
||||
elseif audio_record_param.format == exaudio.PCM_24000 then
|
||||
recod_format = 24000
|
||||
elseif audio_record_param.format == exaudio.PCM_32000 then
|
||||
recod_format = 32000
|
||||
elseif audio_record_param.format == exaudio.PCM_48000 then
|
||||
recod_format = 48000
|
||||
end
|
||||
|
||||
-- 处理回调函数
|
||||
if recodConfigs.cbfnc ~= nil then
|
||||
if check_param(recodConfigs.cbfnc, "function", "cbfnc") then
|
||||
audio_record_param.cbfnc = recodConfigs.cbfnc
|
||||
else
|
||||
return false
|
||||
end
|
||||
else
|
||||
audio_record_param.cbfnc = nil
|
||||
end
|
||||
-- 开始录音
|
||||
local path_type = type(audio_record_param.path)
|
||||
if path_type == "string" then
|
||||
return audio.record(
|
||||
MULTIMEDIA_ID,
|
||||
recod_format,
|
||||
audio_record_param.time,
|
||||
amr_quailty,
|
||||
audio_record_param.path
|
||||
)
|
||||
elseif path_type == "function" then
|
||||
-- 初始化缓冲区
|
||||
if not pcm_buff0 or not pcm_buff1 then
|
||||
pcm_buff0 = zbuff.create(16000)
|
||||
pcm_buff1 = zbuff.create(16000)
|
||||
end
|
||||
return audio.record(
|
||||
MULTIMEDIA_ID,
|
||||
recod_format,
|
||||
audio_record_param.time,
|
||||
amr_quailty,
|
||||
nil,
|
||||
3,
|
||||
pcm_buff0,
|
||||
pcm_buff1
|
||||
)
|
||||
end
|
||||
log.error("录音路径必须为字符串或函数")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 模块接口:停止录音
|
||||
function exaudio.record_stop()
|
||||
return audio.recordStop(MULTIMEDIA_ID)
|
||||
end
|
||||
|
||||
-- 模块接口:设置音量
|
||||
function exaudio.vol(play_volume)
|
||||
if check_param(play_volume, "number", "音量值") then
|
||||
return audio.vol(MULTIMEDIA_ID, play_volume)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 模块接口:设置麦克风音量
|
||||
function exaudio.mic_vol(record_volume)
|
||||
if check_param(record_volume, "number", "麦克风音量值") then
|
||||
return audio.micVol(MULTIMEDIA_ID, record_volume)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return exaudio
|
||||
Reference in New Issue
Block a user