exaudio.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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 -- 0:左声道, 1:右声道, 2:立体声
  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 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. -- 默认配置参数
  31. local audio_setup_param = {
  32. model = "es8311", -- dac类型: "es8311","es8211"
  33. i2c_id = 0, -- i2c_id: 0,1
  34. pa_ctrl = 0, -- 音频放大器电源控制管脚
  35. dac_ctrl = 0, -- 音频编解码芯片电源控制管脚
  36. dac_delay = 3, -- DAC启动前冗余时间(100ms)
  37. pa_delay = 100, -- DAC启动后延迟打开PA的时间(ms)
  38. dac_time_delay = 600, -- 播放完毕后PA与DAC关闭间隔(ms)
  39. bits_per_sample = 16, -- 采样位数
  40. pa_on_level = 1 -- PA打开电平 1:高 0:低
  41. }
  42. local audio_play_param = {
  43. type = 0, -- 0:文件 1:TTS 2:流式
  44. content = nil, -- 内容/回调函数
  45. cbFnc = nil, -- 播放完毕回调
  46. priority = 0, -- 优先级(数值越大越高)
  47. sampling_Rate = 16000, -- 采样率(仅流式)
  48. sampling_Depth = 16, -- 采样位深(仅流式)
  49. signed_or_Unsigned = true -- PCM是否有符号(仅流式)
  50. }
  51. local audio_record_param = {
  52. format = 0, -- 录制格式
  53. time = 5, -- 录制时间(秒)
  54. path = nil, -- 文件路径或流式回调
  55. cbFnc = nil -- 录音完毕回调
  56. }
  57. -- 内部变量
  58. local pcm_buff0 = nil
  59. local pcm_buff1 = nil
  60. local voice_vol = 55
  61. local mic_vol = 80
  62. -- 工具函数:参数检查
  63. local function check_param(param, expected_type, name)
  64. if type(param) ~= expected_type then
  65. log.error(string.format("参数错误: %s 应为 %s 类型", name, expected_type))
  66. return false
  67. end
  68. return true
  69. end
  70. -- 音频回调处理
  71. local function audio_callback(id, event, point)
  72. -- log.info("audio_callback", "event:", event,
  73. -- "MORE_DATA:", audio.MORE_DATA,
  74. -- "DONE:", audio.DONE,
  75. -- "RECORD_DATA:", audio.RECORD_DATA,
  76. -- "RECORD_DONE:", audio.RECORD_DONE)
  77. if event == audio.MORE_DATA then
  78. if type(audio_play_param.content) == "function" then
  79. audio_play_param.content()
  80. end
  81. elseif event == audio.DONE then
  82. if type(audio_play_param.cbFnc) == "function" then
  83. audio_play_param.cbFnc(exaudio.PLAY_DONE)
  84. end
  85. sys.publish(MSG_PLAY_DONE)
  86. elseif event == audio.RECORD_DATA then
  87. if type(audio_record_param.path) == "function" then
  88. local buff, len = point == 0 and pcm_buff0 or pcm_buff1,
  89. point == 0 and pcm_buff0:used() or pcm_buff1:used()
  90. audio_record_param.path(buff, len)
  91. end
  92. elseif event == audio.RECORD_DONE then
  93. if type(audio_record_param.cbFnc) == "function" then
  94. audio_record_param.cbFnc(exaudio.RECORD_DONE)
  95. end
  96. end
  97. end
  98. -- 读取ES8311芯片ID
  99. local function read_es8311_id()
  100. -- 发送读取请求
  101. local send_ok = i2c.send(audio_setup_param.i2c_id, ES8311_ADDR, CHIP_ID_REG)
  102. if not send_ok then
  103. log.error("发送芯片ID读取请求失败")
  104. return false
  105. end
  106. -- 读取数据
  107. local data = i2c.recv(audio_setup_param.i2c_id, ES8311_ADDR, 1)
  108. if data and #data == 1 then
  109. return true
  110. end
  111. log.error("读取ES8311芯片ID失败")
  112. return false
  113. end
  114. -- 音频硬件初始化
  115. local function audio_setup()
  116. sys.wait(100)
  117. -- I2C配置
  118. if not i2c.setup(audio_setup_param.i2c_id, i2c.FAST) then
  119. log.error("I2C初始化失败")
  120. return false
  121. end
  122. -- 初始化I2S
  123. local result, data = i2s.setup(
  124. I2S_ID,
  125. I2S_MODE,
  126. I2S_SAMPLE_RATE,
  127. audio_setup_param.bits_per_sample,
  128. I2S_CHANNEL_FORMAT,
  129. I2S_COMM_FORMAT,
  130. I2S_CHANNEL_BITS
  131. )
  132. if not result then
  133. log.error("I2S设置失败")
  134. return false
  135. end
  136. -- 配置音频通道
  137. audio.config(
  138. MULTIMEDIA_ID,
  139. audio_setup_param.pa_ctrl,
  140. audio_setup_param.pa_on_level,
  141. audio_setup_param.dac_delay,
  142. audio_setup_param.pa_delay,
  143. audio_setup_param.dac_ctrl,
  144. 1, -- power_on_level
  145. audio_setup_param.dac_time_delay
  146. )
  147. -- 设置总线
  148. audio.setBus(
  149. MULTIMEDIA_ID,
  150. audio.BUS_I2S,
  151. {
  152. chip = audio_setup_param.model,
  153. i2cid = audio_setup_param.i2c_id,
  154. i2sid = I2S_ID,
  155. voltage = audio.VOLTAGE_1800
  156. }
  157. )
  158. -- 设置音量
  159. audio.vol(MULTIMEDIA_ID, voice_vol)
  160. audio.micVol(MULTIMEDIA_ID, mic_vol)
  161. audio.pm(MULTIMEDIA_ID, audio.RESUME)
  162. sys.wait(100)
  163. -- 检查芯片连接
  164. if audio_setup_param.model == "es8311" and not read_es8311_id() then
  165. log.error("ES8311通讯失败,请检查硬件")
  166. return false
  167. end
  168. -- 注册回调
  169. audio.on(MULTIMEDIA_ID, audio_callback)
  170. return true
  171. end
  172. -- 模块接口:初始化
  173. function exaudio.setup(audioConfigs)
  174. -- 检查必要参数
  175. if not audioConfigs or type(audioConfigs) ~= "table" then
  176. log.error("配置参数必须为table类型")
  177. return false
  178. end
  179. -- 检查codec型号
  180. if not audioConfigs.model or
  181. (audioConfigs.model ~= "es8311" and audioConfigs.model ~= "es8211") then
  182. log.error("请指定正确的codec型号(es8311或es8211)")
  183. return false
  184. end
  185. audio_setup_param.model = audioConfigs.model
  186. -- 针对ES8311的特殊检查
  187. if audioConfigs.model == "es8311" then
  188. if not check_param(audioConfigs.i2c_id, "number", "i2c_id") then
  189. return false
  190. end
  191. audio_setup_param.i2c_id = audioConfigs.i2c_id
  192. end
  193. -- 检查功率放大器控制管脚
  194. if audioConfigs.pa_ctrl == nil then
  195. log.error("pa_ctrl(功率放大器控制管脚)为必填项")
  196. return false
  197. end
  198. audio_setup_param.pa_ctrl = audioConfigs.pa_ctrl
  199. -- 处理可选参数
  200. local optional_params = {
  201. {name = "dac_ctrl", type = "number"},
  202. {name = "dac_delay", type = "number"},
  203. {name = "pa_delay", type = "number"},
  204. {name = "dac_time_delay", type = "number"},
  205. {name = "bits_per_sample", type = "number"},
  206. {name = "pa_on_level", type = "number"}
  207. }
  208. for _, param in ipairs(optional_params) do
  209. if audioConfigs[param.name] ~= nil then
  210. if check_param(audioConfigs[param.name], param.type, param.name) then
  211. audio_setup_param[param.name] = audioConfigs[param.name]
  212. else
  213. return false
  214. end
  215. end
  216. end
  217. -- 确保采样位数有默认值
  218. audio_setup_param.bits_per_sample = audio_setup_param.bits_per_sample or 16
  219. return audio_setup()
  220. end
  221. -- 模块接口:写入音频数据块
  222. function exaudio.write_datablock(data)
  223. if not data or type(data) ~= "string" then
  224. log.error("写入的数据必须为字符串类型")
  225. return false
  226. end
  227. return audio.write(MULTIMEDIA_ID, data)
  228. end
  229. -- 模块接口:开始播放
  230. function exaudio.play_start(playConfigs)
  231. if not playConfigs or type(playConfigs) ~= "table" then
  232. log.error("播放配置必须为table类型")
  233. return false
  234. end
  235. -- 检查播放类型
  236. if not check_param(playConfigs.type, "number", "type") then
  237. log.error("type必须为数值(0:文件,1:TTS,2:流式)")
  238. return false
  239. end
  240. audio_play_param.type = playConfigs.type
  241. -- 处理优先级
  242. if playConfigs.priority ~= nil then
  243. if check_param(playConfigs.priority, "number", "priority") then
  244. if playConfigs.priority >= audio_play_param.priority then
  245. audio.play(MULTIMEDIA_ID)
  246. sys.waitUntil(MSG_PLAY_DONE)
  247. audio_play_param.priority = playConfigs.priority
  248. end
  249. else
  250. return false
  251. end
  252. end
  253. -- 处理不同播放类型
  254. local play_type = audio_play_param.type
  255. if play_type == 0 then -- 文件播放
  256. if not playConfigs.content then
  257. log.error("文件播放需要指定content(文件路径或路径表)")
  258. return false
  259. end
  260. local content_type = type(playConfigs.content)
  261. if content_type == "table" then
  262. for _, path in ipairs(playConfigs.content) do
  263. if type(path) ~= "string" then
  264. log.error("播放列表元素必须为字符串路径")
  265. return false
  266. end
  267. end
  268. elseif content_type ~= "string" then
  269. log.error("文件播放content必须为字符串或路径表")
  270. return false
  271. end
  272. audio_play_param.content = playConfigs.content
  273. audio.play(MULTIMEDIA_ID, audio_play_param.content)
  274. elseif play_type == 1 then -- TTS播放
  275. if not check_param(playConfigs.content, "string", "content") then
  276. log.error("TTS播放content必须为字符串")
  277. return false
  278. end
  279. audio_play_param.content = playConfigs.content
  280. audio.tts(MULTIMEDIA_ID, audio_play_param.content)
  281. elseif play_type == 2 then -- 流式播放
  282. if not check_param(playConfigs.content, "function", "content") then
  283. log.error("流式播放content必须为回调函数")
  284. return false
  285. end
  286. if not check_param(playConfigs.sampling_Rate, "number", "sampling_Rate") then
  287. return false
  288. end
  289. if not check_param(playConfigs.sampling_Depth, "number", "sampling_Depth") then
  290. return false
  291. end
  292. audio_play_param.content = playConfigs.content
  293. audio_play_param.sampling_Rate = playConfigs.sampling_Rate
  294. audio_play_param.sampling_Depth = playConfigs.sampling_Depth
  295. if playConfigs.signed_or_Unsigned ~= nil then
  296. audio_play_param.signed_or_Unsigned = playConfigs.signed_or_Unsigned
  297. end
  298. audio.start(
  299. MULTIMEDIA_ID,
  300. audio.PCM,
  301. 1,
  302. playConfigs.sampling_Rate,
  303. playConfigs.sampling_Depth,
  304. audio_play_param.signed_or_Unsigned
  305. )
  306. -- 发送初始数据
  307. audio.write(MULTIMEDIA_ID, string.rep("\0", 512))
  308. end
  309. -- 处理回调函数
  310. if playConfigs.cbFnc ~= nil then
  311. if check_param(playConfigs.cbFnc, "function", "cbFnc") then
  312. audio_play_param.cbFnc = playConfigs.cbFnc
  313. else
  314. return false
  315. end
  316. else
  317. audio_play_param.cbFnc = nil
  318. end
  319. return true
  320. end
  321. -- 模块接口:停止播放
  322. function exaudio.play_stop()
  323. return audio.stop(MULTIMEDIA_ID)
  324. end
  325. -- 模块接口:检查播放是否结束
  326. function exaudio.isEnd()
  327. return audio.isEnd()
  328. end
  329. -- 模块接口:获取错误信息
  330. function exaudio.getError()
  331. return audio.getError()
  332. end
  333. -- 模块接口:开始录音
  334. function exaudio.record_start(recodConfigs)
  335. if not recodConfigs or type(recodConfigs) ~= "table" then
  336. log.error("录音配置必须为table类型")
  337. return false
  338. end
  339. -- 检查录音格式
  340. if recodConfigs.format == nil or type(recodConfigs.format) ~= "number" or recodConfigs.format > 5 then
  341. log.error("请指定正确的录音格式")
  342. return false
  343. end
  344. audio_record_param.format = recodConfigs.format
  345. -- 处理录音时间
  346. if recodConfigs.time ~= nil then
  347. if check_param(recodConfigs.time, "number", "time") then
  348. audio_record_param.time = recodConfigs.time
  349. else
  350. return false
  351. end
  352. else
  353. audio_record_param.time = 0
  354. end
  355. -- 处理存储路径/回调
  356. if not recodConfigs.path then
  357. log.error("必须指定录音路径或流式回调函数")
  358. return false
  359. end
  360. audio_record_param.path = recodConfigs.path
  361. -- 转换录音格式
  362. local recod_format, amr_quailty
  363. if audio_record_param.format == exaudio.AMR_NB then
  364. recod_format = audio.AMR_NB
  365. amr_quailty = 7
  366. elseif audio_record_param.format == exaudio.AMR_WB then
  367. recod_format = audio.AMR_WB
  368. amr_quailty = 8
  369. elseif audio_record_param.format == exaudio.PCM_8000 then
  370. recod_format = 8000
  371. elseif audio_record_param.format == exaudio.PCM_16000 then
  372. recod_format = 16000
  373. elseif audio_record_param.format == exaudio.PCM_24000 then
  374. recod_format = 24000
  375. elseif audio_record_param.format == exaudio.PCM_32000 then
  376. recod_format = 32000
  377. end
  378. -- 处理回调函数
  379. if recodConfigs.cbFnc ~= nil then
  380. if check_param(recodConfigs.cbFnc, "function", "cbFnc") then
  381. audio_record_param.cbFnc = recodConfigs.cbFnc
  382. else
  383. return false
  384. end
  385. else
  386. audio_record_param.cbFnc = nil
  387. end
  388. -- 开始录音
  389. local path_type = type(audio_record_param.path)
  390. if path_type == "string" then
  391. return audio.record(
  392. MULTIMEDIA_ID,
  393. recod_format,
  394. audio_record_param.time,
  395. amr_quailty,
  396. audio_record_param.path
  397. )
  398. elseif path_type == "function" then
  399. -- 初始化缓冲区
  400. if not pcm_buff0 or not pcm_buff1 then
  401. pcm_buff0 = zbuff.create(16000)
  402. pcm_buff1 = zbuff.create(16000)
  403. end
  404. return audio.record(
  405. MULTIMEDIA_ID,
  406. recod_format,
  407. audio_record_param.time,
  408. amr_quailty,
  409. nil,
  410. 3,
  411. pcm_buff0,
  412. pcm_buff1
  413. )
  414. end
  415. log.error("录音路径必须为字符串或函数")
  416. return false
  417. end
  418. -- 模块接口:停止录音
  419. function exaudio.record_stop()
  420. audio.recordStop(MULTIMEDIA_ID)
  421. end
  422. -- 模块接口:设置音量
  423. function exaudio.vol(number)
  424. if check_param(number, "number", "音量值") then
  425. voice_vol = number
  426. return audio.vol(MULTIMEDIA_ID, number)
  427. end
  428. return false
  429. end
  430. -- 模块接口:设置麦克风音量
  431. function exaudio.micVol(number)
  432. if check_param(number, "number", "麦克风音量值") then
  433. mic_vol = number
  434. return audio.micVol(MULTIMEDIA_ID, number)
  435. end
  436. return false
  437. end
  438. return exaudio