exaudio.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. --[[
  2. @module exaudio
  3. @summary exaudio扩展库
  4. @version 1.1
  5. @date 2025.09.01
  6. @author 梁健
  7. @usage
  8. ]]
  9. local exaudio = {}
  10. -- 常量定义
  11. local I2S_ID = 0
  12. local I2S_MODE = 0 -- 0:主机 1:从机
  13. local I2S_SAMPLE_RATE = 16000
  14. local I2S_CHANNEL_FORMAT = i2s.MONO_R
  15. local I2S_COMM_FORMAT = i2s.MODE_LSB -- 可选MODE_I2S, MODE_LSB, MODE_MSB
  16. local I2S_CHANNEL_BITS = 16
  17. local MULTIMEDIA_ID = 0
  18. local EX_MSG_PLAY_DONE = "playDone"
  19. local ES8311_ADDR = 0x18 -- 7位地址
  20. local CHIP_ID_REG = 0x00 -- 芯片ID寄存器地址
  21. -- 模块常量
  22. exaudio.PLAY_DONE = 1 -- 音频播放完毕的事件之一
  23. exaudio.RECORD_DONE = 1 -- 音频录音完毕的事件之一
  24. exaudio.AMR_NB = 0
  25. exaudio.AMR_WB = 1
  26. exaudio.PCM_8000 = 2
  27. exaudio.PCM_16000 = 3
  28. exaudio.PCM_24000 = 4
  29. exaudio.PCM_32000 = 5
  30. exaudio.PCM_48000 = 6
  31. -- 默认配置参数
  32. local audio_setup_param = {
  33. model = "es8311", -- dac类型: "es8311","es8211"
  34. i2c_id = 0, -- i2c_id: 0,1
  35. pa_ctrl = 0, -- 音频放大器电源控制管脚
  36. dac_ctrl = 0, -- 音频编解码芯片电源控制管脚
  37. dac_delay = 3, -- DAC启动前冗余时间(100ms)
  38. pa_delay = 100, -- DAC启动后延迟打开PA的时间(ms)
  39. dac_time_delay = 600, -- 播放完毕后PA与DAC关闭间隔(ms)
  40. bits_per_sample = 16, -- 采样位数
  41. pa_on_level = 1 -- PA打开电平 1:高 0:低
  42. }
  43. local audio_play_param = {
  44. type = 0, -- 0:文件 1:TTS 2:流式
  45. content = nil, -- 播放内容
  46. cbfnc = nil, -- 播放完毕回调
  47. priority = 0, -- 优先级(数值越大越高)
  48. sampling_rate = 16000, -- 采样率(仅流式)
  49. sampling_depth = 16, -- 采样位深(仅流式)
  50. signed_or_unsigned = true -- PCM是否有符号(仅流式)
  51. }
  52. local audio_record_param = {
  53. format = 0, -- 录制格式,支持exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000,exaudio.PCM_48000
  54. time = 5, -- 录制时间(秒)
  55. path = nil, -- 文件路径或流式回调
  56. cbfnc = nil -- 录音完毕回调
  57. }
  58. -- 内部变量
  59. local pcm_buff0 = nil
  60. local pcm_buff1 = nil
  61. local voice_vol = 55
  62. local mic_vol = 80
  63. -- 定义全局队列表
  64. local audio_play_queue = {
  65. data = {}, -- 存储字符串的数组
  66. sequenceIndex = 1 -- 用于跟踪插入顺序的索引
  67. }
  68. -- 向队列中添加字符串(按调用顺序插入)
  69. local function audio_play_queue_push(str)
  70. if type(str) == "string" then
  71. -- 存储格式: {index = 顺序索引, value = 字符串值}
  72. table.insert(audio_play_queue.data, {
  73. index = audio_play_queue.sequenceIndex,
  74. value = str
  75. })
  76. audio_play_queue.sequenceIndex = audio_play_queue.sequenceIndex + 1
  77. return true
  78. end
  79. return false
  80. end
  81. -- 从队列中取出最早插入的字符串(按顺序取出)
  82. local function audio_play_queue_pop()
  83. if #audio_play_queue.data > 0 then
  84. -- 取出并移除第一个元素
  85. local item = table.remove(audio_play_queue.data, 1)
  86. return item.value -- 返回值
  87. end
  88. return nil
  89. end
  90. -- 清空队列中所有数据
  91. function audio_queue_clear()
  92. -- 清空数组
  93. audio_play_queue.data = {}
  94. -- 重置顺序索引
  95. audio_play_queue.sequenceIndex = 1
  96. return true
  97. end
  98. -- 工具函数:参数检查
  99. local function check_param(param, expected_type, name)
  100. if type(param) ~= expected_type then
  101. log.error(string.format("参数错误: %s 应为 %s 类型", name, expected_type))
  102. return false
  103. end
  104. return true
  105. end
  106. -- 音频回调处理
  107. local function audio_callback(id, event, point)
  108. -- log.info("audio_callback", "event:", event,
  109. -- "MORE_DATA:", audio.MORE_DATA,
  110. -- "DONE:", audio.DONE,
  111. -- "RECORD_DATA:", audio.RECORD_DATA,
  112. -- "RECORD_DONE:", audio.RECORD_DONE)
  113. if event == audio.MORE_DATA then
  114. audio.write(MULTIMEDIA_ID,audio_play_queue_pop())
  115. elseif event == audio.DONE then
  116. if type(audio_play_param.cbfnc) == "function" then
  117. audio_play_param.cbfnc(exaudio.PLAY_DONE)
  118. end
  119. audio_queue_clear() -- 清空流式播放数据队列
  120. sys.publish(EX_MSG_PLAY_DONE)
  121. elseif event == audio.RECORD_DATA then
  122. if type(audio_record_param.path) == "function" then
  123. local buff, len = point == 0 and pcm_buff0 or pcm_buff1,
  124. point == 0 and pcm_buff0:used() or pcm_buff1:used()
  125. audio_record_param.path(buff, len)
  126. end
  127. elseif event == audio.RECORD_DONE then
  128. if type(audio_record_param.cbfnc) == "function" then
  129. audio_record_param.cbfnc(exaudio.RECORD_DONE)
  130. end
  131. end
  132. end
  133. -- 读取ES8311芯片ID
  134. local function read_es8311_id()
  135. -- 发送读取请求
  136. local send_ok = i2c.send(audio_setup_param.i2c_id, ES8311_ADDR, CHIP_ID_REG)
  137. if not send_ok then
  138. log.error("发送芯片ID读取请求失败")
  139. return false
  140. end
  141. -- 读取数据
  142. local data = i2c.recv(audio_setup_param.i2c_id, ES8311_ADDR, 1)
  143. if data and #data == 1 then
  144. return true
  145. end
  146. log.error("读取ES8311芯片ID失败")
  147. return false
  148. end
  149. -- 音频硬件初始化
  150. local function audio_setup()
  151. -- I2C配置
  152. if not i2c.setup(audio_setup_param.i2c_id, i2c.FAST) then
  153. log.error("I2C初始化失败")
  154. return false
  155. end
  156. -- 初始化I2S
  157. local result, data = i2s.setup(
  158. I2S_ID,
  159. I2S_MODE,
  160. I2S_SAMPLE_RATE,
  161. audio_setup_param.bits_per_sample,
  162. I2S_CHANNEL_FORMAT,
  163. I2S_COMM_FORMAT,
  164. I2S_CHANNEL_BITS
  165. )
  166. if not result then
  167. log.error("I2S设置失败")
  168. return false
  169. end
  170. -- 配置音频通道
  171. audio.config(
  172. MULTIMEDIA_ID,
  173. audio_setup_param.pa_ctrl,
  174. audio_setup_param.pa_on_level,
  175. audio_setup_param.dac_delay,
  176. audio_setup_param.pa_delay,
  177. audio_setup_param.dac_ctrl,
  178. 1, -- power_on_level
  179. audio_setup_param.dac_time_delay
  180. )
  181. -- 设置总线
  182. audio.setBus(
  183. MULTIMEDIA_ID,
  184. audio.BUS_I2S,
  185. {
  186. chip = audio_setup_param.model,
  187. i2cid = audio_setup_param.i2c_id,
  188. i2sid = I2S_ID,
  189. voltage = audio.VOLTAGE_1800
  190. }
  191. )
  192. -- 设置音量
  193. audio.vol(MULTIMEDIA_ID, voice_vol)
  194. audio.micVol(MULTIMEDIA_ID, mic_vol)
  195. audio.pm(MULTIMEDIA_ID, audio.RESUME)
  196. -- 检查芯片连接
  197. if audio_setup_param.model == "es8311" and not read_es8311_id() then
  198. log.error("ES8311通讯失败,请检查硬件")
  199. return false
  200. end
  201. -- 注册回调
  202. audio.on(MULTIMEDIA_ID, audio_callback)
  203. return true
  204. end
  205. -- 模块接口:初始化
  206. function exaudio.setup(audioConfigs)
  207. -- 检查必要参数
  208. if not audio then
  209. log.error("不支持audio 库,请选择支持audio 的core")
  210. return false
  211. end
  212. if not audioConfigs or type(audioConfigs) ~= "table" then
  213. log.error("配置参数必须为table类型")
  214. return false
  215. end
  216. -- 检查codec型号
  217. if not audioConfigs.model or
  218. (audioConfigs.model ~= "es8311" and audioConfigs.model ~= "es8211") then
  219. log.error("请指定正确的codec型号(es8311或es8211)")
  220. return false
  221. end
  222. audio_setup_param.model = audioConfigs.model
  223. -- 针对ES8311的特殊检查
  224. if audioConfigs.model == "es8311" then
  225. if not check_param(audioConfigs.i2c_id, "number", "i2c_id") then
  226. return false
  227. end
  228. audio_setup_param.i2c_id = audioConfigs.i2c_id
  229. end
  230. -- 检查功率放大器控制管脚
  231. if audioConfigs.pa_ctrl == nil then
  232. log.warn("pa_ctrl(功率放大器控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
  233. end
  234. audio_setup_param.pa_ctrl = audioConfigs.pa_ctrl
  235. -- 检查功率放大器控制管脚
  236. if audioConfigs.dac_ctrl == nil then
  237. log.warn("dac_ctrl(音频编解码控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
  238. end
  239. audio_setup_param.dac_ctrl = audioConfigs.dac_ctrl
  240. -- 处理可选参数
  241. local optional_params = {
  242. {name = "dac_delay", type = "number"},
  243. {name = "pa_delay", type = "number"},
  244. {name = "dac_time_delay", type = "number"},
  245. {name = "bits_per_sample", type = "number"},
  246. {name = "pa_on_level", type = "number"}
  247. }
  248. for _, param in ipairs(optional_params) do
  249. if audioConfigs[param.name] ~= nil then
  250. if check_param(audioConfigs[param.name], param.type, param.name) then
  251. audio_setup_param[param.name] = audioConfigs[param.name]
  252. else
  253. return false
  254. end
  255. end
  256. end
  257. -- 确保采样位数有默认值
  258. audio_setup_param.bits_per_sample = audio_setup_param.bits_per_sample or 16
  259. return audio_setup()
  260. end
  261. -- 模块接口:开始播放
  262. function exaudio.play_start(playConfigs)
  263. if not playConfigs or type(playConfigs) ~= "table" then
  264. log.error("播放配置必须为table类型")
  265. return false
  266. end
  267. -- 检查播放类型
  268. if not check_param(playConfigs.type, "number", "type") then
  269. log.error("type必须为数值(0:文件,1:TTS,2:流式)")
  270. return false
  271. end
  272. audio_play_param.type = playConfigs.type
  273. -- 处理优先级
  274. if playConfigs.priority ~= nil then
  275. if check_param(playConfigs.priority, "number", "priority") then
  276. if playConfigs.priority > audio_play_param.priority then
  277. log.error("是否完成播放",audio.isEnd(MULTIMEDIA_ID))
  278. if not audio.isEnd(MULTIMEDIA_ID) then
  279. if audio.play(MULTIMEDIA_ID) ~= true then
  280. return false
  281. end
  282. sys.waitUntil(EX_MSG_PLAY_DONE)
  283. end
  284. audio_play_param.priority = playConfigs.priority
  285. end
  286. else
  287. return false
  288. end
  289. end
  290. -- 处理不同播放类型
  291. local play_type = audio_play_param.type
  292. if play_type == 0 then -- 文件播放
  293. if not playConfigs.content then
  294. log.error("文件播放需要指定content(文件路径或路径表)")
  295. return false
  296. end
  297. local content_type = type(playConfigs.content)
  298. if content_type == "table" then
  299. for _, path in ipairs(playConfigs.content) do
  300. if type(path) ~= "string" then
  301. log.error("播放列表元素必须为字符串路径")
  302. return false
  303. end
  304. end
  305. elseif content_type ~= "string" then
  306. log.error("文件播放content必须为字符串或路径表")
  307. return false
  308. end
  309. audio_play_param.content = playConfigs.content
  310. if audio.play(MULTIMEDIA_ID, audio_play_param.content) ~= true then
  311. return false
  312. end
  313. elseif play_type == 1 then -- TTS播放
  314. if not audio.tts then
  315. log.error("本固件不支持TTS,请更换支持TTS 的固件")
  316. return false
  317. end
  318. if not check_param(playConfigs.content, "string", "content") then
  319. log.error("TTS播放content必须为字符串")
  320. return false
  321. end
  322. audio_play_param.content = playConfigs.content
  323. if audio.tts(MULTIMEDIA_ID, audio_play_param.content) ~= true then
  324. return false
  325. end
  326. elseif play_type == 2 then -- 流式播放
  327. if not check_param(playConfigs.sampling_rate, "number", "sampling_rate") then
  328. return false
  329. end
  330. if not check_param(playConfigs.sampling_depth, "number", "sampling_depth") then
  331. return false
  332. end
  333. audio_play_param.content = playConfigs.content
  334. audio_play_param.sampling_rate = playConfigs.sampling_rate
  335. audio_play_param.sampling_depth = playConfigs.sampling_depth
  336. if playConfigs.signed_or_unsigned ~= nil then
  337. audio_play_param.signed_or_unsigned = playConfigs.signed_or_unsigned
  338. end
  339. audio.start(
  340. MULTIMEDIA_ID,
  341. audio.PCM,
  342. 1,
  343. playConfigs.sampling_rate,
  344. playConfigs.sampling_depth,
  345. audio_play_param.signed_or_unsigned
  346. )
  347. -- 发送初始数据
  348. if audio.write(MULTIMEDIA_ID, string.rep("\0", 512)) ~= true then
  349. return false
  350. end
  351. end
  352. -- 处理回调函数
  353. if playConfigs.cbfnc ~= nil then
  354. if check_param(playConfigs.cbfnc, "function", "cbfnc") then
  355. audio_play_param.cbfnc = playConfigs.cbfnc
  356. else
  357. return false
  358. end
  359. else
  360. audio_play_param.cbfnc = nil
  361. end
  362. return true
  363. end
  364. -- 模块接口:流式播放数据写入
  365. function exaudio.play_stream_write(data)
  366. audio_play_queue_push(data)
  367. return true
  368. end
  369. -- 模块接口:停止播放
  370. function exaudio.play_stop()
  371. return audio.play(MULTIMEDIA_ID)
  372. end
  373. -- 模块接口:检查播放是否结束
  374. function exaudio.is_end()
  375. return audio.isEnd(MULTIMEDIA_ID)
  376. end
  377. -- 模块接口:获取错误信息
  378. function exaudio.get_error()
  379. return audio.getError(MULTIMEDIA_ID)
  380. end
  381. -- 模块接口:开始录音
  382. function exaudio.record_start(recodConfigs)
  383. if not recodConfigs or type(recodConfigs) ~= "table" then
  384. log.error("录音配置必须为table类型")
  385. return false
  386. end
  387. -- 检查录音格式
  388. if recodConfigs.format == nil or type(recodConfigs.format) ~= "number" or recodConfigs.format > 6 then
  389. log.error("请指定正确的录音格式")
  390. return false
  391. end
  392. audio_record_param.format = recodConfigs.format
  393. -- 处理录音时间
  394. if recodConfigs.time ~= nil then
  395. if check_param(recodConfigs.time, "number", "time") then
  396. audio_record_param.time = recodConfigs.time
  397. else
  398. return false
  399. end
  400. else
  401. audio_record_param.time = 0
  402. end
  403. -- 处理存储路径/回调
  404. if not recodConfigs.path then
  405. log.error("必须指定录音路径或流式回调函数")
  406. return false
  407. end
  408. audio_record_param.path = recodConfigs.path
  409. -- 转换录音格式
  410. local recod_format, amr_quailty
  411. if audio_record_param.format == exaudio.AMR_NB then
  412. recod_format = audio.AMR_NB
  413. amr_quailty = 7
  414. elseif audio_record_param.format == exaudio.AMR_WB then
  415. recod_format = audio.AMR_WB
  416. amr_quailty = 8
  417. elseif audio_record_param.format == exaudio.PCM_8000 then
  418. recod_format = 8000
  419. elseif audio_record_param.format == exaudio.PCM_16000 then
  420. recod_format = 16000
  421. elseif audio_record_param.format == exaudio.PCM_24000 then
  422. recod_format = 24000
  423. elseif audio_record_param.format == exaudio.PCM_32000 then
  424. recod_format = 32000
  425. elseif audio_record_param.format == exaudio.PCM_48000 then
  426. recod_format = 48000
  427. end
  428. -- 处理回调函数
  429. if recodConfigs.cbfnc ~= nil then
  430. if check_param(recodConfigs.cbfnc, "function", "cbfnc") then
  431. audio_record_param.cbfnc = recodConfigs.cbfnc
  432. else
  433. return false
  434. end
  435. else
  436. audio_record_param.cbfnc = nil
  437. end
  438. -- 开始录音
  439. local path_type = type(audio_record_param.path)
  440. if path_type == "string" then
  441. return audio.record(
  442. MULTIMEDIA_ID,
  443. recod_format,
  444. audio_record_param.time,
  445. amr_quailty,
  446. audio_record_param.path
  447. )
  448. elseif path_type == "function" then
  449. -- 初始化缓冲区
  450. if not pcm_buff0 or not pcm_buff1 then
  451. pcm_buff0 = zbuff.create(16000)
  452. pcm_buff1 = zbuff.create(16000)
  453. end
  454. return audio.record(
  455. MULTIMEDIA_ID,
  456. recod_format,
  457. audio_record_param.time,
  458. amr_quailty,
  459. nil,
  460. 3,
  461. pcm_buff0,
  462. pcm_buff1
  463. )
  464. end
  465. log.error("录音路径必须为字符串或函数")
  466. return false
  467. end
  468. -- 模块接口:停止录音
  469. function exaudio.record_stop()
  470. return audio.recordStop(MULTIMEDIA_ID)
  471. end
  472. -- 模块接口:设置音量
  473. function exaudio.vol(play_volume)
  474. if check_param(play_volume, "number", "音量值") then
  475. return audio.vol(MULTIMEDIA_ID, play_volume)
  476. end
  477. return false
  478. end
  479. -- 模块接口:设置麦克风音量
  480. function exaudio.mic_vol(record_volume)
  481. if check_param(record_volume, "number", "麦克风音量值") then
  482. return audio.micVol(MULTIMEDIA_ID, record_volume)
  483. end
  484. return false
  485. end
  486. return exaudio