luat_lib_multimedia_audio.c 12 KB

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