audio.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. --- 模块功能:音频播放.
  2. -- 支持MP3、amr文件播放;
  3. -- 支持本地TTS播放、通话中TTS播放到对端(需要使用支持TTS功能的core软件)
  4. -- @module audio
  5. -- @author openLuat
  6. -- @license MIT
  7. -- @copyright openLuat
  8. -- @release 2018.3.19
  9. require "common"
  10. require "misc"
  11. require "utils"
  12. module(..., package.seeall)
  13. local req = ril.request
  14. local stopCbFnc
  15. --tts速度,默认50
  16. local ttsSpeed = 50
  17. --喇叭音量和mic音量等级
  18. local sVolume,sMicVolume = 4,1
  19. local sCallVolume = 4
  20. --音频播放的协程ID
  21. local taskID
  22. --播放和停止请求队列,用于存储通过调用audio.play和audio.stop接口允许播放和停止播放的请求项
  23. --每个播放请求项为table类型,数据结构如下(参考本文件中的play接口注释)
  24. --priority:播放优先级
  25. --type:播放类型
  26. --path:播放音频内容
  27. --vol:播放音量
  28. --cbFnc:播放结束后的回调函数
  29. --dup:是否重复播放
  30. --dupInterval:重复播放的间隔,单位毫秒
  31. --每个停止请求项为table类型,数据结构如下(参考本文件中的stop接口注释)
  32. --type:固定为"STOP"
  33. --cbFnc:停止播放后的回调函数
  34. local audioQueue = {}
  35. --sStrategy:优先级相同时的播放策略,0(表示继续播放正在播放的音频,忽略请求播放的新音频),1(表示停止正在播放的音频,播放请求播放的新音频)
  36. local sStrategy
  37. local function isTtsStopResultValid()
  38. local version = string.match(rtos.get_version(),"(%d+)_RDA")
  39. if version then
  40. return tonumber(version)>=8
  41. else
  42. return false
  43. end
  44. end
  45. local function handleCb(item,result)
  46. log.info("audio.handleCb",item.cbFnc,result)
  47. if item.cbFnc then item.cbFnc(result) end
  48. table.remove(audioQueue,1)
  49. end
  50. local function handlePlayInd(item,key,value)
  51. log.info("audio.handlePlayInd",key,value)
  52. --播放结束
  53. if key=="RESULT" then
  54. --播放成功
  55. if value then
  56. if item.dup then
  57. if item.dupInterval>0 then
  58. log.info("audio.handlePlayInd",item.type,"dup wait LIB_AUDIO_PLAY_IND or timeout",item.dupInterval)
  59. local result,reason = sys.waitUntil("LIB_AUDIO_PLAY_IND",item.dupInterval)
  60. log.info("audio.handlePlayInd",item.type,"dup wait",reason or "timeout")
  61. if result then
  62. log.warn("audio.handlePlayInd",item.type,"dup wait error",reason)
  63. handleCb(item,reason=="NEW" and 4 or 5)
  64. end
  65. end
  66. else
  67. handleCb(item,0)
  68. end
  69. --播放失败
  70. else
  71. log.warn("audio.handlePlayInd",item.type,"play cnf error")
  72. handleCb(item,1)
  73. end
  74. --新的优先级更高的播放请求
  75. elseif key=="NEW" then
  76. log.warn("audio.handlePlayInd",item.type,"priority error")
  77. handleCb(item,4)
  78. --主动调用audio.stop
  79. elseif key=="STOP" then
  80. log.warn("audio.handlePlayInd",item.type,"stop error",result)
  81. handleCb(item,5)
  82. end
  83. end
  84. local ttsEngineInited
  85. local audioTaskWaitPlayEntry
  86. local function audioTask()
  87. while true do
  88. if #audioQueue==0 then
  89. log.info("audioTask","wait LIB_AUDIO_PLAY_ENTRY")
  90. audioTaskWaitPlayEntry = true
  91. sys.waitUntil("LIB_AUDIO_PLAY_ENTRY")
  92. audioTaskWaitPlayEntry = false
  93. end
  94. local item = audioQueue[1]
  95. log.info("audioTask",item.type,"#audioQueue",#audioQueue)
  96. if item.type=="FILE" then
  97. --队列中有优先级高的请求等待处理
  98. if #audioQueue>1 then
  99. log.warn("audioTask",item.type,"priority low")
  100. local behind = audioQueue[2]
  101. handleCb(item,behind.type=="STOP" and 5 or 4)
  102. else
  103. setVolume(item.vol)
  104. local result
  105. if type(item.path)=="table" then
  106. if (item.path[1]):match("%.amr$") or (item.path[1]):match("%.AMR$") then
  107. local dataBuf = {}
  108. for i=1,#item.path do
  109. table.insert(dataBuf,(io.readFile(item.path[i])):sub(i==1 and 1 or 7,-1))
  110. end
  111. result = audiocore.playdata(table.concat(dataBuf),audiocore.AMR)
  112. elseif (item.path[1]):match("%.pcm$") or (item.path[1]):match("%.PCM$") then
  113. local dataBuf = {}
  114. for i=1,#item.path do
  115. table.insert(dataBuf,io.readFile(item.path[i]))
  116. end
  117. result = audiocore.playdata(table.concat(dataBuf),audiocore.PCM)
  118. elseif (item.path[1]):match("%.mp3$") or (item.path[1]):match("%.MP3$") then
  119. local dataBuf = {}
  120. for i=1,#item.path do
  121. table.insert(dataBuf,io.readFile(item.path[i]))
  122. end
  123. result = audiocore.playdata(table.concat(dataBuf),audiocore.MP3)
  124. elseif (item.path[1]):match("%.wav$") or (item.path[1]):match("%.WAV$") then
  125. local dataBuf = {}
  126. for i=1,#item.path do
  127. table.insert(dataBuf,io.readFile(item.path[i]))
  128. end
  129. result = audiocore.playdata(table.concat(dataBuf),audiocore.WAV)
  130. else
  131. result = false
  132. end
  133. --result = audiocore.play(unpack(item.path))
  134. else
  135. if item.path:match("%.wav$") or item.path:match("%.WAV$") then
  136. result = audiocore.playdata(io.readFile(item.path),audiocore.WAV)
  137. else
  138. result =audiocore.play(item.path)
  139. end
  140. end
  141. if result then
  142. --等待三种消息(播放结束、主动调用audio.stop、新的优先级更高的播放请求)
  143. log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
  144. local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
  145. log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
  146. audiocore.stop()
  147. handlePlayInd(item,key,value)
  148. else
  149. log.warn("audioTask",item.type,"audiocore.play error")
  150. audiocore.stop()
  151. handleCb(item,1)
  152. end
  153. end
  154. elseif item.type=="TTS" or item.type=="TTSCC" then
  155. --队列中有优先级高的请求等待处理
  156. if #audioQueue>1 then
  157. log.warn("audioTask",item.type,"priority low")
  158. local behind = audioQueue[2]
  159. handleCb(item,behind.type=="STOP" and 5 or 4)
  160. else
  161. setVolume(item.vol)
  162. if item.type=="TTS" then
  163. if not ttsEngineInited then
  164. ttsply.initEngine()
  165. ttsEngineInited = true
  166. end
  167. ttsply.setParm(0,ttsSpeed)
  168. --队列中有优先级高的请求等待处理
  169. if #audioQueue>1 then
  170. log.warn("audioTask",item.type,"priority low1")
  171. if isTtsStopResultValid() then
  172. if ttsply.stop() then
  173. sys.waitUntil("LIB_AUDIO_PLAY_IND",2000)
  174. end
  175. else
  176. ttsply.stop()
  177. sys.waitUntil("LIB_AUDIO_PLAY_IND",500)
  178. end
  179. local behind = audioQueue[2]
  180. handleCb(item,behind.type=="STOP" and 5 or 4)
  181. else
  182. ttsply.play(common.utf8ToGb2312(item.path))
  183. --等待三种消息(播放结束、主动调用audio.stop、新的优先级更高的播放请求)
  184. log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
  185. local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
  186. log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
  187. if item.type=="TTS" then
  188. if isTtsStopResultValid() then
  189. --log.info("tts 1")
  190. if ttsply.stop() then
  191. --log.info("tts 2")
  192. sys.waitUntil("LIB_AUDIO_PLAY_IND",2000)
  193. end
  194. --log.info("tts 3")
  195. else
  196. ttsply.stop()
  197. sys.waitUntil("LIB_AUDIO_PLAY_IND",500)
  198. end
  199. else
  200. end
  201. handlePlayInd(item,key,value)
  202. end
  203. else
  204. end
  205. end
  206. elseif item.type=="RECORD" then
  207. --队列中有优先级高的请求等待处理
  208. if #audioQueue>1 then
  209. log.warn("audioTask",item.type,"priority low")
  210. local behind = audioQueue[2]
  211. handleCb(item,behind.type=="STOP" and 5 or 4)
  212. else
  213. setVolume(item.vol)
  214. f,d=record.getSize()
  215. req("AT+AUDREC=1,0,2,"..item.path..","..d*1000)
  216. --等待三种消息(播放结束、主动调用audio.stop、新的优先级更高的播放请求)
  217. log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
  218. local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
  219. log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
  220. req("AT+AUDREC=1,0,3,"..item.path..","..d*1000)
  221. sys.waitUntil("LIB_AUDIO_RECORD_STOP_RESULT")
  222. handlePlayInd(item,key,value)
  223. end
  224. elseif item.type=="STOP" then
  225. if item.cbFnc then item.cbFnc(0) end
  226. table.remove(audioQueue,1)
  227. end
  228. end
  229. end
  230. --- 播放音频
  231. -- @number priority 音频优先级,数值越大,优先级越高
  232. -- 优先级高的播放请求会终止优先级低的播放
  233. -- 相同优先级的播放请求,播放策略参考:audio.setStrategy接口
  234. -- @string type 音频类型,目前仅支持"FILE"、"TTS"
  235. -- @string path 音频文件路径,跟typ有关
  236. -- typ为"FILE"时:表示音频文件路径
  237. -- typ为"TTS"时:表示要播放的UTF8编码格式的数据
  238. -- @number[opt=4] vol 播放音量,取值范围0到7,0为静音
  239. -- @function[opt=nil] cbFnc 音频播放结束时的回调函数,回调函数的调用形式如下:
  240. -- cbFnc(result)
  241. -- result表示播放结果:
  242. -- 0-播放成功结束;
  243. -- 1-播放出错
  244. -- 2-播放优先级不够,没有播放
  245. -- 3-传入的参数出错,没有播放
  246. -- 4-被新的播放请求中止
  247. -- 5-调用audio.stop接口主动停止
  248. -- @bool[opt=nil] dup 是否循环播放,true循环,false或者nil不循环
  249. -- @number[opt=0] dupInterval 循环播放间隔(单位毫秒),dup为true时,此值才有意义
  250. -- @return result,bool或者nil类型,同步调用成功返回true,否则返回false
  251. -- @usage audio.play(0,"FILE","/lua/call.mp3")
  252. -- @usage audio.play(0,"FILE","/lua/call.mp3",7)
  253. -- @usage audio.play(0,"FILE","/lua/call.mp3",7,cbFnc)
  254. -- @usage 更多用法参考demo/audio/testAudio.lua
  255. function play(priority,type,path,vol,cbFnc,dup,dupInterval)
  256. log.info("audio.play",priority,type,path,vol,cbFnc,dup,dupInterval)
  257. if not taskID then
  258. taskID = sys.taskInit(audioTask)
  259. end
  260. local item = {priority=priority,type=type,path=path,vol=vol or 4,cbFnc=cbFnc,dup=dup,dupInterval=dupInterval or 0}
  261. if #audioQueue==0 then
  262. table.insert(audioQueue,item)
  263. sys.publish("LIB_AUDIO_PLAY_ENTRY")
  264. else
  265. local front = audioQueue[#audioQueue]
  266. if front.type=="STOP" then
  267. table.insert(audioQueue,item)
  268. else
  269. if priority>front.priority or (priority==front.priority and sStrategy==1) then
  270. table.insert(audioQueue,item)
  271. if not audioTaskWaitPlayEntry then
  272. sys.publish("LIB_AUDIO_PLAY_IND","NEW")
  273. end
  274. else
  275. log.warn("audio.play","priority error")
  276. if cbFnc then cbFnc(2) end
  277. end
  278. end
  279. end
  280. return true
  281. end
  282. --- 停止音频播放
  283. -- @function[opt=nil] cbFnc 停止音频播放的回调函数(停止结果通过此函数通知用户),回调函数的调用形式为:
  284. -- cbFnc(result)
  285. -- result:number类型
  286. -- 0表示停止成功
  287. -- @return nil
  288. -- @usage audio.stop()
  289. function stop(cbFnc)
  290. log.info("audio.stop",cbFnc)
  291. if #audioQueue==0 then
  292. if cbFnc then cbFnc(0) end
  293. else
  294. table.insert(audioQueue,{type="STOP",cbFnc=cbFnc})
  295. sys.publish("LIB_AUDIO_PLAY_IND","STOP")
  296. end
  297. end
  298. local function audioMsg(msg)
  299. --log.info("audio.MSG_AUDIO",msg.play_end_ind,msg.play_error_ind)
  300. sys.publish("LIB_AUDIO_PLAY_IND","RESULT",msg.play_end_ind)
  301. end
  302. --注册core上报的rtos.MSG_AUDIO消息的处理函数
  303. rtos.on(rtos.MSG_AUDIO,audioMsg)
  304. rtos.on(rtos.MSG_TTSPLY_STATUS, function() log.info("rtos.MSG_TTSPLY_STATUS") sys.publish("LIB_AUDIO_PLAY_IND","RESULT",true) end)
  305. rtos.on(rtos.MSG_TTSPLY_ERROR, function() log.info("rtos.MSG_TTSPLY_ERROR") sys.publish("LIB_AUDIO_PLAY_IND","RESULT",false) end)
  306. --- 设置喇叭音量等级
  307. -- @number vol 音量值为0-7,0为静音
  308. -- @return bool result,设置成功返回true,失败返回false
  309. -- @usage audio.setVolume(7)
  310. function setVolume(vol)
  311. local result = audiocore.setvol(vol)
  312. if result == 1 then
  313. result = true
  314. elseif result == 0 then
  315. result = false
  316. end
  317. if result then sVolume = vol end
  318. return result
  319. end
  320. --- 设置通话音量等级
  321. -- @number vol 音量值为0-7,0为静音
  322. -- @return bool result,设置成功返回true,失败返回false
  323. -- @usage audio.setCallVolume(7)
  324. function setCallVolume(vol)
  325. --local result = audiocore.setsphvol(vol)
  326. --if result then sCallVolume = vol end
  327. --return result
  328. audiocore.setsphvol(vol)
  329. sCallVolume = vol
  330. return true
  331. end
  332. -- 设置麦克音量等级
  333. -- @number vol,音量值为0-15,0为静音
  334. -- @return bool result,设置成功返回true,失败返回false
  335. -- @usage audio.setMicVolume(14)
  336. function setMicVolume(vol)
  337. ril.request("AT+CMIC="..audiocore.LOUDSPEAKER..","..vol)
  338. return true
  339. end
  340. ril.regRsp("+CMIC",function(cmd,success)
  341. if success then
  342. sMicVolume = tonumber(cmd:match("CMIC=%d+,(%d+)"))
  343. end
  344. end)
  345. --- 设置mic增益等级
  346. -- 通话时mic增益在通话建立成功之后设置才有效
  347. -- 录音mic增益设置后实时生效
  348. -- @string mode 增益类型
  349. -- "call"表示通话中mic增益
  350. -- "record"表示录音mic增益
  351. -- @number level 增益等级,取值为0-7
  352. -- @return bool result,设置成功返回true,失败返回false
  353. -- @usage audio.setMicGain("record",7),设置录音时mic增益为7级
  354. function setMicGain(mode, level)
  355. if (mode ~= "call" and mode ~= "record") or (level > 7 and level < 0) then
  356. return false
  357. else
  358. local gainHex
  359. if level == 7 then
  360. gainHex = string.format("%02X%02X%02X%02X", 7, 0, 15, 0)
  361. else
  362. gainHex = string.format("%02X%02X%02X%02X", level, 0, level * 2, 0)
  363. end
  364. if mode == "call" then
  365. ril.request("AT+CACCP=5,1,0," .. gainHex)
  366. ril.request("AT+CACCP=0,1,0," .. gainHex)
  367. elseif mode == "record" then
  368. ril.request("AT+CACCP=2,1,6," .. gainHex)
  369. end
  370. return true
  371. end
  372. end
  373. --- 获取喇叭音量等级
  374. -- @return number vol,喇叭音量等级
  375. -- @usage audio.getVolume()
  376. function getVolume()
  377. return sVolume
  378. end
  379. --- 获取通话音量等级
  380. -- @return number vol,通话音量等级
  381. -- @usage audio.getCallVolume()
  382. function getCallVolume()
  383. return sCallVolume
  384. end
  385. -- 获取麦克音量等级
  386. -- @return number vol,麦克音量等级
  387. -- @usage audio.getMicVolume()
  388. function getMicVolume()
  389. return sMicVolume
  390. end
  391. --- 设置优先级相同时的播放策略
  392. -- @number strategy 优先级相同时的播放策略;
  393. -- 0:表示继续播放正在播放的音频,忽略请求播放的新音频
  394. -- 1:表示停止正在播放的音频,播放请求播放的新音频
  395. -- @return nil
  396. -- @usage audio.setStrategy(0)
  397. -- @usage audio.setStrategy(1)
  398. function setStrategy(strategy)
  399. sStrategy=strategy
  400. end
  401. --- 设置TTS朗读速度
  402. -- @number speed 速度范围为0-100,默认50
  403. -- @return bool result,设置成功返回true,失败返回false
  404. -- @usage audio.setTTSSpeed(70)
  405. function setTTSSpeed(speed)
  406. if type(speed) == "number" and speed >= 0 and speed <= 100 then
  407. ttsSpeed = speed
  408. return true
  409. end
  410. end
  411. --- 设置音频输入、输出通道
  412. -- 设置后实时生效
  413. -- @number[opt=2] output 0:earphone听筒,1:headphone耳机,2:speaker喇叭
  414. -- @number[opt=0] input 0:主mic,3:耳机mic
  415. -- @return nil
  416. -- @usage
  417. -- 设置为听筒输出:audio.setChannel(0)
  418. -- 设置为耳机输出:audio.setChannel(1)
  419. -- 设置为喇叭输出:audio.setChannel(2)
  420. -- 设置为喇叭输出、耳机mic输入:audio.setChannel(2,3)
  421. function setChannel(output, input)
  422. local version = string.match(rtos.get_version(), "(%d+)_RDA")
  423. if not version or tonumber(version) >= 9 then --匹配不到,兼容其它版本 或者大于版本9
  424. audiocore.setchannel(output or 2, input or 0)
  425. else
  426. ril.request("AT+AUDCH="..(output==1 and 1 or 2))
  427. end
  428. end
  429. --默认音频通道设置为LOUDSPEAKER,因为目前的模块只支持LOUDSPEAKER通道
  430. audiocore.setchannel(audiocore.LOUDSPEAKER)
  431. --默认音量等级设置为4级,4级是中间等级,最低为0级,最高为7级
  432. setVolume(sVolume)
  433. setCallVolume(sCallVolume)
  434. --默认MIC音量等级设置为1级,最低为0级,最高为15级
  435. setMicVolume(sMicVolume)