luat_lib_multimedia_audio.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /*
  2. @module audio
  3. @summary 多媒体-音频
  4. @version 1.0
  5. @date 2022.03.11
  6. @demo multimedia
  7. @tag LUAT_USE_MEDIA
  8. */
  9. #include "luat_base.h"
  10. #include "luat_msgbus.h"
  11. #include "luat_zbuff.h"
  12. #define LUAT_LOG_TAG "audio"
  13. #include "luat_log.h"
  14. #include "luat_multimedia.h"
  15. #include "luat_audio.h"
  16. #include "luat_mem.h"
  17. #ifndef __BSP_COMMON_H__
  18. #include "c_common.h"
  19. #endif
  20. static luat_multimedia_cb_t multimedia_cbs[MAX_DEVICE_COUNT];
  21. int l_multimedia_raw_handler(lua_State *L, void* ptr) {
  22. (void)ptr;
  23. rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
  24. if (multimedia_cbs[msg->arg2].function_ref) {
  25. lua_geti(L, LUA_REGISTRYINDEX, multimedia_cbs[msg->arg2].function_ref);
  26. if (lua_isfunction(L, -1)) {
  27. lua_pushinteger(L, msg->arg2);
  28. lua_pushinteger(L, msg->arg1);
  29. lua_call(L, 2, 0);
  30. }
  31. }
  32. lua_pushinteger(L, 0);
  33. return 1;
  34. }
  35. /*
  36. 启动一个多媒体通道准备播放音频
  37. @api audio.start(id, audio_format, num_channels, sample_rate, bits_per_sample, is_signed)
  38. @int 多媒体播放通道号,0或者1
  39. @int 音频格式
  40. @int 声音通道数
  41. @int 采样频率
  42. @int 采样位数
  43. @boolean 是否有符号,默认true
  44. @return boolean 成功true, 失败false
  45. @usage
  46. audio.start(0, audio.PCM, 1, 16000, 16)
  47. */
  48. static int l_audio_start_raw(lua_State *L){
  49. int multimedia_id = luaL_checkinteger(L, 1);
  50. int audio_format = luaL_checkinteger(L, 2);
  51. int num_channels= luaL_checkinteger(L, 3);
  52. int sample_rate = luaL_checkinteger(L, 4);
  53. int bits_per_sample = luaL_checkinteger(L, 5);
  54. int is_signed = 1;
  55. if (lua_isboolean(L, 6))
  56. {
  57. is_signed = lua_toboolean(L, 6);
  58. }
  59. lua_pushboolean(L, !luat_audio_start_raw(multimedia_id, audio_format, num_channels, sample_rate, bits_per_sample, is_signed));
  60. return 1;
  61. }
  62. /**
  63. 往一个多媒体通道写入音频数据
  64. @api audio.write(id, data)
  65. @string or zbuff 音频数据
  66. @return boolean 成功返回true,否则返回false
  67. @usage
  68. audio.write(0, "xxxxxx")
  69. */
  70. static int l_audio_write_raw(lua_State *L) {
  71. int multimedia_id = luaL_checkinteger(L, 1);
  72. size_t len;
  73. const char *buf;
  74. if(lua_isuserdata(L, 2))
  75. {
  76. luat_zbuff_t *buff = ((luat_zbuff_t *)luaL_checkudata(L, 2, LUAT_ZBUFF_TYPE));
  77. len = buff->used;
  78. buf = (const char *)(buff->addr);
  79. }
  80. else
  81. {
  82. buf = lua_tolstring(L, 2, &len);//取出字符串数据
  83. }
  84. lua_pushboolean(L, !luat_audio_write_raw(multimedia_id, (uint8_t*)buf, len));
  85. return 1;
  86. }
  87. /**
  88. 停止指定的多媒体通道
  89. @api audio.stop(id)
  90. @int audio id,例如0
  91. @return boolean 成功返回true,否则返回false
  92. @usage
  93. audio.stop(0)
  94. */
  95. static int l_audio_stop_raw(lua_State *L) {
  96. int id = luaL_checkinteger(L, 1);
  97. lua_pushboolean(L, !luat_audio_stop_raw(id));
  98. return 1;
  99. }
  100. /**
  101. 暂停/恢复指定的多媒体通道
  102. @api audio.pause(id, pause)
  103. @int audio id,例如0
  104. @boolean onoff true 暂停,false 恢复
  105. @return boolean 成功返回true,否则返回false
  106. @usage
  107. audio.pause(0, true) --暂停通道0
  108. audio.pause(0, false) --恢复通道0
  109. */
  110. static int l_audio_pause_raw(lua_State *L) {
  111. int id = luaL_checkinteger(L, 1);
  112. lua_pushboolean(L, !luat_audio_pause_raw(id, lua_toboolean(L, 2)));
  113. return 1;
  114. }
  115. /**
  116. 注册audio播放事件回调
  117. @api audio.on(id, event, func)
  118. @int audio id, audio 0写0, audio 1写1
  119. @function 回调方法,回调时传入参数为1、int 通道ID 2、int 消息值,只有audio.MORE_DATA和audio.DONE
  120. @return nil 无返回值
  121. @usage
  122. audio.on(0, function(audio_id, msg)
  123. log.info("msg", audio_id, msg)
  124. end)
  125. */
  126. static int l_audio_raw_on(lua_State *L) {
  127. int multimedia_id = luaL_checkinteger(L, 1);
  128. if (multimedia_cbs[multimedia_id].function_ref != 0) {
  129. luaL_unref(L, LUA_REGISTRYINDEX, multimedia_cbs[multimedia_id].function_ref);
  130. multimedia_cbs[multimedia_id].function_ref = 0;
  131. }
  132. if (lua_isfunction(L, 2)) {
  133. lua_pushvalue(L, 2);
  134. multimedia_cbs[multimedia_id].function_ref = luaL_ref(L, LUA_REGISTRYINDEX);
  135. }
  136. return 0;
  137. }
  138. /*
  139. 播放或者停止播放一个文件,播放完成后,会回调一个audio.DONE消息,可以用pause来暂停或者恢复,其他API不可用。考虑到读SD卡速度比较慢而拖累luavm进程的速度,所以尽量使用本API
  140. @api audio.play(id, path, errStop)
  141. @int 音频通道
  142. @string/table 文件名,如果为空,则表示停止播放,如果是table,则表示连续播放多个文件,主要应用于云喇叭,目前只有EC618支持,并且会用到errStop参数
  143. @boolean 是否在文件解码失败后停止解码,只有在连续播放多个文件时才有用,默认true,遇到解码错误自动停止
  144. @return boolean 成功返回true,否则返回false
  145. @usage
  146. audio.play(0, "xxxxxx") --开始播放某个文件
  147. audio.play(0) --停止播放某个文件
  148. */
  149. static int l_audio_play(lua_State *L) {
  150. int multimedia_id = luaL_checkinteger(L, 1);
  151. size_t len = 0;
  152. int result = 0;
  153. const char *buf;
  154. uint8_t is_error_stop = 1;
  155. if (lua_istable(L, 2))
  156. {
  157. size_t len = lua_rawlen(L, 2); //返回数组的长度
  158. if (!len)
  159. {
  160. luat_audio_play_stop(multimedia_id);
  161. lua_pushboolean(L, 1);
  162. return 1;
  163. }
  164. uData_t *info = (uData_t *)luat_heap_malloc(len * sizeof(uData_t));
  165. for (size_t i = 0; i < len; i++)
  166. {
  167. lua_rawgeti(L, 2, 1 + i);
  168. info[i].value.asBuffer.buffer = (void*)lua_tolstring(L, -1, &info[i].value.asBuffer.length);
  169. info[i].Type = UDATA_TYPE_OPAQUE;
  170. lua_pop(L, 1); //将刚刚获取的元素值从栈中弹出
  171. }
  172. if (lua_isboolean(L, 3))
  173. {
  174. is_error_stop = lua_toboolean(L, 3);
  175. }
  176. result = luat_audio_play_multi_files(multimedia_id, info, len, is_error_stop);
  177. lua_pushboolean(L, !result);
  178. luat_heap_free(info);
  179. }
  180. else if (LUA_TSTRING == (lua_type(L, (2))))
  181. {
  182. buf = lua_tolstring(L, 2, &len);//取出字符串数据
  183. result = luat_audio_play_file(multimedia_id, buf);
  184. lua_pushboolean(L, !result);
  185. }
  186. else
  187. {
  188. luat_audio_play_stop(multimedia_id);
  189. lua_pushboolean(L, 1);
  190. }
  191. return 1;
  192. }
  193. #ifdef LUAT_USE_TTS
  194. /*
  195. TTS播放或者停止
  196. @api audio.tts(id, data)
  197. @int 音频通道
  198. @string/zbuff 需要播放的内容
  199. @return boolean 成功返回true,否则返回false
  200. @tag LUAT_USE_TTS
  201. @usage
  202. audio.tts(0, "测试一下") --开始播放
  203. audio.tts(0) --停止播放
  204. -- Air780E的TTS功能详细说明
  205. -- https://wiki.luatos.com/chips/air780e/tts.html
  206. */
  207. static int l_audio_play_tts(lua_State *L) {
  208. int multimedia_id = luaL_checkinteger(L, 1);
  209. size_t len = 0;
  210. int result = 0;
  211. const char *buf;
  212. if (LUA_TSTRING == (lua_type(L, (2))))
  213. {
  214. buf = lua_tolstring(L, 2, &len);//取出字符串数据
  215. result = luat_audio_play_tts_text(multimedia_id, (void*)buf, len);
  216. lua_pushboolean(L, !result);
  217. }
  218. else if(lua_isuserdata(L, 2))
  219. {
  220. luat_zbuff_t *buff = ((luat_zbuff_t *)luaL_checkudata(L, 2, LUAT_ZBUFF_TYPE));
  221. result = luat_audio_play_tts_text(multimedia_id, buff->addr, buff->used);
  222. lua_pushboolean(L, !result);
  223. }
  224. else
  225. {
  226. luat_audio_play_stop(multimedia_id);
  227. lua_pushboolean(L, 1);
  228. }
  229. return 1;
  230. }
  231. #endif
  232. /**
  233. 停止播放文件,和audio.play(id)是一样的作用
  234. @api audio.playStop(id)
  235. @int audio id,例如0
  236. @return boolean 成功返回true,否则返回false
  237. @usage
  238. audio.playStop(0)
  239. */
  240. static int l_audio_play_stop(lua_State *L) {
  241. lua_pushboolean(L, !luat_audio_play_stop(luaL_checkinteger(L, 1)));
  242. return 1;
  243. }
  244. /**
  245. 检查当前文件是否已经播放结束
  246. @api audio.isEnd(id)
  247. @int 音频通道
  248. @return boolean 成功返回true,否则返回false
  249. @usage
  250. audio.isEnd(0)
  251. */
  252. static int l_audio_play_wait_end(lua_State *L) {
  253. int multimedia_id = luaL_checkinteger(L, 1);
  254. lua_pushboolean(L, luat_audio_is_finish(multimedia_id));
  255. return 1;
  256. }
  257. /*
  258. 获取最近一次播放结果,不是所有平台都支持的,目前只有EC618支持
  259. @api audio.getError(id)
  260. @int 音频通道
  261. @return boolean 是否全部播放成功,true成功,false有文件播放失败
  262. @return boolean 如果播放失败,是否是用户停止,true是,false不是
  263. @return int 第几个文件失败了,从1开始
  264. @usage
  265. local result, user_stop, file_no = audio.getError(0)
  266. */
  267. static int l_audio_play_get_last_error(lua_State *L) {
  268. int multimedia_id = luaL_checkinteger(L, 1);
  269. int result = luat_audio_play_get_last_error(multimedia_id);
  270. lua_pushboolean(L, 0 == result);
  271. lua_pushboolean(L, result < 0);
  272. lua_pushinteger(L, result > 0?result:0);
  273. return 3;
  274. }
  275. /*
  276. 配置一个音频通道的特性,比如实现自动控制PA开关。注意这个不是必须的,一般在调用play的时候才需要自动控制,其他情况比如你手动控制播放时,就可以自己控制PA开关
  277. @api audio.config(id, paPin, onLevel, dacDelay, paDelay, dacPin, dacLevel, dacTimeDelay)
  278. @int 音频通道
  279. @int PA控制IO
  280. @int PA打开时的电平
  281. @int 在DAC启动前插入的冗余时间,单位100ms,一般用于外部DAC
  282. @int 在DAC启动后,延迟多长时间打开PA,单位1ms
  283. @int 外部dac电源控制IO,如果不填,则表示使用平台默认IO,比如Air780E使用DACEN脚,air105则不启用
  284. @int 外部dac打开时,电源控制IO的电平,默认拉高
  285. @int 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms,默认0ms
  286. @usage
  287. audio.config(0, pin.PC0, 1) --PA控制脚是PC0,高电平打开,air105用这个配置就可以用了
  288. audio.config(0, 25, 1, 6, 200) --PA控制脚是GPIO25,高电平打开,Air780E云喇叭板用这个配置就可以用了
  289. */
  290. static int l_audio_config(lua_State *L) {
  291. int id = luaL_checkinteger(L, 1);
  292. int pa_pin = luaL_optinteger(L, 2, 255);
  293. int level = luaL_optinteger(L, 3, 1);
  294. int dac_pre_delay = luaL_optinteger(L, 4, 5);
  295. int dac_last_delay = luaL_optinteger(L, 5, 200);
  296. int dac_power_pin = luaL_optinteger(L, 6, -1);
  297. int dac_power_level = luaL_optinteger(L, 7, 1);
  298. int pa_dac_delay = luaL_optinteger(L, 8, 0);
  299. if (pa_dac_delay < 0)
  300. pa_dac_delay = 0;
  301. if (dac_pre_delay < 0)
  302. dac_pre_delay = 0;
  303. if (dac_last_delay < 0)
  304. dac_last_delay = 0;
  305. luat_audio_config_pa(id, pa_pin, level, (uint32_t)dac_pre_delay, (uint32_t)dac_last_delay);
  306. luat_audio_config_dac(id, dac_power_pin, dac_power_level, (uint32_t)pa_dac_delay);
  307. return 0;
  308. }
  309. /*
  310. 配置一个音频通道的音量调节,直接将原始数据放大或者缩小,不是所有平台都支持,建议尽量用硬件方法去缩放
  311. @api audio.vol(id, value)
  312. @int 音频通道
  313. @int 音量,百分比,1%~1000%,默认100%,就是不调节
  314. @return int 当前音量
  315. @usage
  316. local result = audio.vol(0, 90) --通道0的音量调节到90%,result存放了调节后的音量水平,有可能仍然是100
  317. */
  318. static int l_audio_vol(lua_State *L) {
  319. int id = luaL_checkinteger(L, 1);
  320. int vol = luaL_optinteger(L, 2, 100);
  321. lua_pushinteger(L, luat_audio_vol(id, vol));
  322. return 1;
  323. }
  324. /*
  325. 配置一个音频通道的硬件输出总线,只有对应soc软硬件平台支持才设置对应类型
  326. @api audio.setBus(id, bus_type)
  327. @int 音频通道,例如0
  328. @int 总线类型, 例如 audio.BUS_SOFT_DAC
  329. @return nil 无返回值
  330. @usage
  331. audio.setBus(0, audio.BUS_SOFT_DAC) --通道0的硬件输出通道设置为软件DAC
  332. audio.setBus(0, audio.BUS_I2S) --通道0的硬件输出通道设置为I2S
  333. */
  334. static int l_audio_set_output_bus(lua_State *L) {
  335. int tp = luaL_checkinteger(L, 2);
  336. luat_audio_set_bus_type(tp);
  337. return 0;
  338. }
  339. LUAT_WEAK void luat_audio_set_debug(uint8_t on_off)
  340. {
  341. (void)on_off;
  342. }
  343. /*
  344. 配置调试信息输出
  345. @api audio.debug(on_off)
  346. @boolean true开 false关
  347. @return
  348. @usage
  349. audio.debug(true) --开启调试信息输出
  350. audio.debug(false) --关闭调试信息输出
  351. */
  352. static int l_audio_set_debug(lua_State *L) {
  353. luat_audio_set_debug(lua_toboolean(L, 1));
  354. return 0;
  355. }
  356. #include "rotable2.h"
  357. static const rotable_Reg_t reg_audio[] =
  358. {
  359. { "start" , ROREG_FUNC(l_audio_start_raw)},
  360. { "write" , ROREG_FUNC(l_audio_write_raw)},
  361. { "pause", ROREG_FUNC(l_audio_pause_raw)},
  362. { "stop", ROREG_FUNC(l_audio_stop_raw)},
  363. { "on", ROREG_FUNC(l_audio_raw_on)},
  364. { "play", ROREG_FUNC(l_audio_play)},
  365. #ifdef LUAT_USE_TTS
  366. { "tts", ROREG_FUNC(l_audio_play_tts)},
  367. #endif
  368. { "playStop", ROREG_FUNC(l_audio_play_stop)},
  369. { "isEnd", ROREG_FUNC(l_audio_play_wait_end)},
  370. { "config", ROREG_FUNC(l_audio_config)},
  371. { "vol", ROREG_FUNC(l_audio_vol)},
  372. { "getError", ROREG_FUNC(l_audio_play_get_last_error)},
  373. { "setBus", ROREG_FUNC(l_audio_set_output_bus)},
  374. { "debug", ROREG_FUNC(l_audio_set_debug)},
  375. //@const PCM number PCM格式,即原始ADC数据
  376. { "PCM", ROREG_INT(MULTIMEDIA_DATA_TYPE_PCM)},
  377. //@const MORE_DATA number audio.on回调函数传入参数的值,表示底层播放完一段数据,可以传入更多数据
  378. { "MORE_DATA", ROREG_INT(MULTIMEDIA_CB_AUDIO_NEED_DATA)},
  379. //@const DONE number audio.on回调函数传入参数的值,表示底层播放完全部数据了
  380. { "DONE", ROREG_INT(MULTIMEDIA_CB_AUDIO_DONE)},
  381. //@const BUS_DAC number 硬件输出总线,DAC类型
  382. { "BUS_DAC", ROREG_INT(MULTIMEDIA_AUDIO_BUS_DAC)},
  383. //@const BUS_I2S number 硬件输出总线,I2S类型
  384. { "BUS_I2S", ROREG_INT(MULTIMEDIA_AUDIO_BUS_I2S)},
  385. //@const BUS_SOFT_DAC number 硬件输出总线,软件模式DAC类型
  386. { "BUS_SOFT_DAC", ROREG_INT(MULTIMEDIA_AUDIO_BUS_SOFT_DAC)},
  387. { NULL, ROREG_INT(0)}
  388. };
  389. LUAMOD_API int luaopen_multimedia_audio( lua_State *L ) {
  390. luat_newlib2(L, reg_audio);
  391. return 1;
  392. }