music_demo.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. --用2*2行列键盘实现切换歌曲,选择上一首,选择下一首,暂停/恢复
  2. --用软件行列键盘,底层已经做好防抖了,如果用单独按键,自行处理
  3. --当前选择上/下一首不会立刻生效,按切换歌曲才会生效,如果需要立刻生效,就取消相应注释即可
  4. --[[
  5. 接线要求:
  6. SPI 使用常规4线解法
  7. Air105开发板 TF模块
  8. PC9 CS
  9. PB12(SPI0_CLK) CLK
  10. PB14(SPI0_MISO) MOSI
  11. PB15(SPI0_MISO) MISO
  12. 5V VCC
  13. GND GND
  14. ]]
  15. -- music文件夾,可以換成你自己的目录,不用加/sd/,demo里固定用TF卡目录,如果要本地目录,自行修改
  16. local musicDir = "/music/"
  17. local taskName = "task_audio"
  18. local playList = {}
  19. local curPlay = 0
  20. local prePlay = 0
  21. local nextPlay = 1
  22. local tFiles = 0
  23. local isPause = false
  24. local MSG_MD = "moreData" -- 播放缓存有空余
  25. local MSG_PD = "playDone" -- 播放完成所有数据
  26. local MSG_NEW = "audioNew" -- 播放新的歌曲
  27. audio.on(0, function(id, event)
  28. if event == audio.MORE_DATA then
  29. sysplus.sendMsg(taskName, MSG_MD)
  30. else
  31. sysplus.sendMsg(taskName, MSG_PD)
  32. end
  33. end)
  34. local function moveNext()
  35. prePlay = curPlay
  36. curPlay = curPlay + 1
  37. if curPlay >= tFiles then
  38. curPlay = 0
  39. end
  40. nextPlay = curPlay + 1
  41. if nextPlay >= tFiles then
  42. nextPlay = 0
  43. end
  44. log.info("选择下一首")
  45. log.info("上一首", prePlay, playList[prePlay])
  46. log.info("选择", curPlay, playList[curPlay])
  47. log.info("下一首", nextPlay, playList[nextPlay])
  48. end
  49. local function movePre()
  50. nextPlay = curPlay
  51. curPlay = prePlay
  52. if prePlay > 0 then
  53. prePlay = prePlay - 1
  54. else
  55. prePlay = tFiles - 1
  56. end
  57. log.info("选择上一首")
  58. log.info("上一首", prePlay, playList[prePlay])
  59. log.info("选择", curPlay, playList[curPlay])
  60. log.info("下一首", nextPlay, playList[nextPlay])
  61. end
  62. local function task_cb(msg)
  63. log.info(msg[1], msg[2], msg[3], msg[4])
  64. end
  65. -- 用软件行列键盘,底层已经做好防抖了,如果用单独按键,自行处理
  66. sys.subscribe("SOFT_KB_INC", function(port, data, state)
  67. -- log.info(data, state)
  68. -- 按下起效
  69. if state > 0 then
  70. if data == 0 then
  71. -- 开始播放或者立刻结束当前歌曲并播放下一首
  72. sysplus.sendMsg(taskName, MSG_NEW)
  73. elseif data == 1 then
  74. movePre()
  75. -- 如果需要立刻就切换歌曲就取消注释
  76. -- sysplus.sendMsg(taskName, MSG_NEW)
  77. elseif data == 16 then
  78. moveNext()
  79. -- 如果需要立刻就切换歌曲就取消注释
  80. -- sysplus.sendMsg(taskName, MSG_NEW)
  81. elseif data == 17 then
  82. isPause = not isPause
  83. audio.pause(0, isPause)
  84. log.info("暂停状态", isPause)
  85. end
  86. end
  87. end)
  88. local function audio_task()
  89. softkb.init(0, {pin.PD12, pin.PD13}, {pin.PE1, pin.PE2})
  90. local tagLen = 0
  91. local frameLen = 1152 * 4
  92. local spiId = 2
  93. local nowPlay
  94. local result = spi.setup(
  95. spiId,--串口id
  96. 255, -- 不使用默认CS脚
  97. 0,--CPHA
  98. 0,--CPOL
  99. 8,--数据宽度
  100. 400*1000 -- 初始化时使用较低的频率
  101. )
  102. local isMP3 = false
  103. local TF_CS = pin.PB3
  104. gpio.setup(TF_CS, 1)
  105. -- fatfs.debug(1) -- 若挂载失败,可以尝试打开调试信息,查找原因
  106. fatfs.mount("SD", spiId, TF_CS, 24000000)
  107. -- fatfs.mount("SD", 0, TF_CS, 24000000)
  108. local data, err = fatfs.getfree("SD")
  109. local buff = zbuff.create(1024)
  110. local in_buff = zbuff.create(frameLen * 3 + 512)
  111. local isRun = false
  112. local result, AudioFormat, NumChannels, SampleRate, ByteRate, BlockAlign, BitsPerSample, is_signed
  113. if data then
  114. log.info("fatfs", "getfree", json.encode(data))
  115. local dir_nums, dir_info = fatfs.lsdir(musicDir)
  116. for k,v in pairs(dir_info) do
  117. if k:find(".mp3") or k:find(".MP3") or k:find(".wav") or k:find(".WAV") then
  118. log.info("找到",k)
  119. playList[tFiles] = k
  120. tFiles = tFiles + 1
  121. end
  122. end
  123. log.info("总共", tFiles)
  124. prePlay = tFiles - 1
  125. log.info("等待切换歌曲")
  126. sysplus.waitMsg(taskName, MSG_NEW)
  127. while true do
  128. isRun = false
  129. isMP3 = false
  130. nowPlay = curPlay
  131. log.info("开始播放")
  132. log.info("上一首", prePlay, playList[prePlay])
  133. log.info("当前", curPlay, playList[curPlay])
  134. log.info("下一首", nextPlay, playList[nextPlay])
  135. f = io.open("/sd".. musicDir .. playList[curPlay], "rb")
  136. if f then
  137. if playList[curPlay]:find(".mp3") or playList[curPlay]:find(".MP3") then
  138. -- 解析MP3的必要信息
  139. isMP3 = true
  140. in_buff:del()
  141. data = f:read(10)
  142. if data:sub(1, 3) == 'ID3' then
  143. in_buff:copy(nil, data)
  144. tagLen = ((in_buff:query(6, 1, true) & 0x7f) << 21) + ((in_buff:query(7, 1, true) & 0x7f) << 14) + ((in_buff:query(8, 1, true) & 0x7f) << 7) + (in_buff:query(9, 1, true) & 0x7f)
  145. log.info("jump head", tagLen)
  146. f:seek(SEEK_SET, tagLen)
  147. end
  148. in_buff:del()
  149. data = f:read(frameLen)
  150. codecr = codec.create(codec.MP3)
  151. result, AudioFormat, NumChannels, SampleRate, BitsPerSample, is_signed = codec.get_audio_info(codecr, data)
  152. if result then
  153. log.info("mp3 info", NumChannels, SampleRate, BitsPerSample)
  154. buff:resize(65536)
  155. in_buff:copy(nil, data)
  156. result = codec.get_audio_data(codecr, in_buff, buff)
  157. log.debug("start", audio.start(0, AudioFormat, NumChannels, SampleRate, BitsPerSample, is_signed))
  158. audio.write(0, buff)
  159. data = f:read(frameLen)
  160. in_buff:copy(nil, data)
  161. result = codec.get_audio_data(codecr, in_buff, buff)
  162. audio.write(0, buff)
  163. data = f:read(frameLen)
  164. isRun = true
  165. else
  166. log.debug("mp3解码失败!")
  167. end
  168. else
  169. isMP3 = false
  170. data = nil
  171. buff:del()
  172. -- 解析WAV的必要信息
  173. buff:copy(0, f:read(12))
  174. if buff:query(0, 4) == 'RIFF' and buff:query(8, 4) == 'WAVE' then
  175. local total = buff:query(4, 4, false)
  176. buff:copy(0, f:read(8))
  177. if buff:query(0, 4) == 'fmt ' then
  178. buff:copy(0, f:read(16))
  179. buff:seek(0, zbuff.SEEK_SET)
  180. result, AudioFormat, NumChannels, SampleRate, ByteRate, BlockAlign, BitsPerSample = buff:unpack("<HHIIHH")
  181. log.debug("find fmt info", AudioFormat, NumChannels, SampleRate, ByteRate, BlockAlign, BitsPerSample)
  182. buff:copy(0, f:read(8))
  183. if buff:query(0, 4) ~= 'data' then
  184. buff:copy(0, buff:query(4, 4, false))
  185. buff:copy(0, f:read(8))
  186. end
  187. log.debug("start", audio.start(0, AudioFormat, NumChannels, SampleRate, BitsPerSample))
  188. SampleRate = (SampleRate * BlockAlign // 8) & ~(3)
  189. log.info("size", SampleRate)
  190. data = f:read(SampleRate)
  191. audio.write(0, data)
  192. data = f:read(SampleRate)
  193. audio.write(0, data)
  194. data = f:read(SampleRate)
  195. isRun = true
  196. end
  197. else
  198. log.debug("不正确的RIFF头", buff:query(0, 4), buff:query(8, 4))
  199. end
  200. end
  201. if isRun then
  202. while true do
  203. local msg = sysplus.waitMsg(taskName, nil, 2000)
  204. if type(msg) == 'table' then
  205. if msg[1] == MSG_MD and isRun then
  206. if isMP3 then
  207. if in_buff:used() >= frameLen * 2 then
  208. isRun = codec.get_audio_data(codecr, in_buff, buff)
  209. audio.write(0, buff)
  210. else
  211. data = f:read(frameLen)
  212. in_buff:copy(nil, data)
  213. if #data ~= frameLen then
  214. isRun = codec.get_audio_data(codecr, in_buff, buff, false)
  215. log.info("解码结束")
  216. isRun = false
  217. else
  218. isRun = codec.get_audio_data(codecr, in_buff, buff)
  219. end
  220. audio.write(0, buff)
  221. end
  222. else
  223. audio.write(0, data)
  224. data = f:read(SampleRate)
  225. if not data or #data == 0 then
  226. log.info("没有数据了")
  227. isRun = false
  228. end
  229. end
  230. elseif msg[1] == MSG_PD then
  231. log.info("播放结束")
  232. break
  233. elseif msg[1] == MSG_NEW then
  234. log.info("切换歌曲")
  235. break
  236. end
  237. else
  238. if not isPause then
  239. log.error(type(msg), msg)
  240. end
  241. end
  242. end
  243. end
  244. if isMP3 then
  245. codec.release(codecr)
  246. end
  247. audio.stop(0)
  248. f:close()
  249. end
  250. -- 如果有底层的播放消息,也释放掉
  251. sysplus.waitMsg(taskName, MSG_MD, 10)
  252. sysplus.waitMsg(taskName, MSG_PD, 10)
  253. isPause = false
  254. log.info(rtos.meminfo("sys"))
  255. log.info(rtos.meminfo("lua"))
  256. if nowPlay == curPlay then
  257. prePlay = curPlay
  258. curPlay = curPlay + 1
  259. if curPlay >= tFiles then
  260. curPlay = 0
  261. end
  262. nextPlay = curPlay + 1
  263. if nextPlay >= tFiles then
  264. nextPlay = 0
  265. end
  266. end
  267. end
  268. else
  269. log.info("fatfs", "err", err)
  270. end
  271. sysplus.taskDel(taskName)
  272. end
  273. sysplus.taskInitEx(audio_task, taskName, task_cb)