播放音乐
一、演示功能概述
本篇文章演示的内容为:Air8000 整机开发板,接上喇叭,播放音频文件。
二、准备硬件环境
参考:Air8000 硬件环境清单,准备好硬件环境,本篇文档使用的是 Air8000 整机开发板,如下图所示:
三、准备软件环境
在开始实践本示例之前,先筹备一下软件环境:
1. Luatools工具;
2. 内核固件文件(底层core固件文件):LuatOS-SoC_V2003_Air8000;参考项目使用的内核固件;
3. luatos需要的脚本和资源文件
脚本和资源文件点我浏览所有文件
lib脚本文件:使用Luatools烧录时,勾选 添加默认lib 选项,使用默认lib脚本文件;
准备好软件环境之后,接下来查看如何烧录项目文件到Air8000核心板,将本篇文章中演示使用的项目文件烧录到Air8000核心板中
四、代码 API 和代码解析
4.1 代码 API
mcu.altfun(type, sn, pad_index, alt_fun, is_input)
IO 外设功能复用选择,注意普通 MCU 通常是以 GPIO 号为唯一 ID 号,但是专用 SOC,比如 CAT1 的,可能以 PAD 号或者模块 pin 脚号(pin.xxx 后续支持)为唯一 ID 号。本函数不是所有平台适用
参数
传入值类型 | 解释 |
int | 外设类型,目前有mcu.UART,mcu.I2C,mcu.SPI,mcu.PWM,mcu.GPIO,mcu.I2S,mcu.LCD,mcu.CAM,具体需要看平台 |
int | 总线序号,0~N,如果是mcu.GPIO,则是GPIO号。具体看平台的IOMUX复用表 |
int | 唯一ID号,如果留空不写,则表示清除配置,使用平台的默认配置。具体看平台的IOMUX复用表 |
int | 复用功能序号,0~N。具体看平台的IOMUX复用表 |
boolean | 是否是输入功能,true是,留空是false |
返回值
无
例子
mcu.altfun(mcu.I2C, 0, 13, 2, 0) --复用pin80为i2c0_scl
mcu.altfun(mcu.I2C, 0, 14, 2, 0) --复用pin81为i2c0_sda
audio.config(id, paPin, onLevel, dacDelay, paDelay, dacPin, dacLevel, dacTimeDelay)
配置一个音频通道的特性,比如实现自动控制 PA 开关。注意这个不是必须的,一般在调用 play 的时候才需要自动控制,其他情况比如你手动控制播放时,就可以自己控制 PA 开关
参数
传入值类型 | 解释 |
int | 音频通道 |
int | PA控制IO |
int | PA打开时的电平 |
int | 在DAC启动前插入的冗余时间,单位100ms,一般用于外部DAC |
int | 在DAC启动后,延迟多长时间打开PA,单位1ms |
int | 外部dac电源控制IO |
int | 外部dac打开时,电源控制IO的电平,默认拉高 |
int | 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms,默认0ms |
返回值
无
例子
audio.config(0, pin.PC0, 1) --PA控制脚是PC0,高电平打开
audio.config(0, 25, 1, 6, 200) --PA控制脚是GPIO25,高电平打开
audio.setBus(id, bus_type)
配置一个音频通道的硬件输出总线,只有对应 soc 软硬件平台支持才设置对应类型
参数
传入值类型 | 解释 |
int | 音频通道,例如0 |
int | 总线类型, 例如 audio.BUS_SOFT_DAC |
int | 硬件id, 例如 总线类型为audio.BUS_I2S时,硬件id即为i2s codec的i2c id |
返回值
返回值类型 | 解释 |
nil | 无返回值 |
例子
audio.setBus(0, audio.BUS_SOFT_DAC) --通道0的硬件输出通道设置为软件DAC
audio.setBus(0, audio.BUS_I2S) --通道0的硬件输出通道设置为I2S
audio.vol(id, value)
配置一个音频通道的音量调节,直接将原始数据放大或者缩小,不是所有平台都支持,建议尽量用硬件方法去缩放
参数
传入值类型 | 解释 |
int | 音频通道 |
int | 音量,百分比,1%~1000%,默认100%,就是不调节 |
返回值
返回值类型 | 解释 |
int | 当前音量 |
例子
local result = audio.vol(0, 90) --通道0的音量调节到90%,result存放了调节后的音量水平,有可能仍然是100
audio.micVol(id, value)
配置一个音频通道的 mic 音量调节
参数
传入值类型 | 解释 |
int | 音频通道 |
int | mic音量,百分比,1%~100%,默认100%,就是不调节 |
返回值
返回值类型 | 解释 |
int | 当前mic音量 |
例子
local result = audio.vol(0, 90) --通道0的音量调节到90%,result存放了调节后的音量水平,有可能仍然是100
audio.getError(id)
获取最近一次播放结果
参数
传入值类型 | 解释 |
int | 音频通道 |
返回值
返回值类型 | 解释 |
boolean | 是否全部播放成功,true成功,false有文件播放失败 |
boolean | 如果播放失败,是否是用户停止,true是,false不是 |
int | 第几个文件失败了,从1开始 |
例子
local result, user_stop, file_no = audio.getError(0)
audio.play(id, path, errStop)
播放或者停止播放一个文件,播放完成后,会回调一个 audio.DONE 消息,可以用 pause 来暂停或者恢复,其他 API 不可用。考虑到读 SD 卡速度比较慢而拖累 luavm 进程的速度,所以尽量使用本 API
参数
传入值类型 | 解释 |
int | 音频通道 |
string/table | 文件名,如果为空,则表示停止播放,如果是table,则表示连续播放多个文件,主要应用于云喇叭 |
boolean | 是否在文件解码失败后停止解码,只有在连续播放多个文件时才有用,默认true,遇到解码错误自动停止 |
返回值
返回值类型 | 解释 |
boolean | 成功返回true,否则返回false |
例子
audio.play(0, "xxxxxx") --开始播放某个文件
audio.isEnd(id)
检查当前文件是否已经播放结束
参数
传入值类型 | 解释 |
int | 音频通道 |
返回值
返回值类型 | 解释 |
boolean | 成功返回true,否则返回false |
例子
audio.isEnd(0)
audio.pm(id,pm_mode)
audio 休眠控制(一般会自动调用不需要手动执行)
参数
传入值类型 | 解释 |
int | 音频通道 |
int | 休眠模式 |
返回值
返回值类型 | 解释 |
boolean | true成功 |
例子
audio.pm(multimedia_id,audio.RESUME)
常量 类型 解释
audio.RESUME number PM模式 工作模式
audio.STANDBY number PM模式 待机模式,PA断电,codec待机状态,系统不能进低功耗状态,如果PA不可控,codec进入静音模式
audio.SHUTDOWN number PM模式 关机模式,PA断电,可配置的codec关机状态,不可配置的codec断电,系统能进低功耗状态
audio.POWEROFF number PM模式 断电模式,PA断电,codec断电,系统能进低功耗状态
4.2 代码解析
1.复用 pin80 和 pin81 为 i2c0
mcu.altfun(mcu.I2C, 0, 13, 2, 0) --复用pin80为i2c0_scl
mcu.altfun(mcu.I2C, 0, 14, 2, 0) --复用pin81为i2c0_sda
2.设置 i2s 和 audio 的一些参数,控制 8311 上电,然后设置 i2c,i2s,audio 的一些基础配置。
local i2c_id = 0 -- i2c_id 0
local pa_pin = 16 -- 喇叭pa功放脚
local power_pin = 8 -- es8311电源脚
local i2s_id = 0 -- i2s_id 0
local i2s_mode = 0 -- i2s模式 0 主机 1 从机
local i2s_sample_rate = 16000 -- 采样率
local i2s_bits_per_sample = 16 -- 数据位数
local i2s_channel_format = i2s.MONO_R -- 声道, 0 左声道, 1 右声道, 2 立体声
local i2s_communication_format = i2s.MODE_LSB -- 格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
local i2s_channel_bits = 16 -- 声道的BCLK数量
local multimedia_id = 0 -- 音频通道 0
local pa_on_level = 1 -- PA打开电平 1 高电平 0 低电平
local power_delay = 3 -- 在DAC启动前插入的冗余时间,单位100ms
local pa_delay = 100 -- 在DAC启动后,延迟多长时间打开PA,单位1ms
local power_on_level = 1 -- 电源控制IO的电平,默认拉高
local power_time_delay = 100 -- 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms
local voice_vol = 50 -- 喇叭音量
local mic_vol = 80 -- 麦克风音量
function audio_setup()
pm.power(pm.LDO_CTL, false) --开发板上ES8311由LDO_CTL控制上下电
sys.wait(100)
pm.power(pm.LDO_CTL, true) --开发板上ES8311由LDO_CTL控制上下电
i2c.setup(i2c_id,i2c.FAST) --设置i2c
i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,i2s_channel_bits) --设置i2s
audio.config(multimedia_id, pa_pin, pa_on_level, power_delay, pa_delay, power_pin, power_on_level, power_time_delay)
audio.setBus(multimedia_id, audio.BUS_I2S,{chip = "es8311",i2cid = i2c_id , i2sid = i2s_id, voltage = audio.VOLTAGE_1800}) --通道0的硬件输出通道设置为I2S
audio.vol(multimedia_id, voice_vol)
audio.micVol(multimedia_id, mic_vol)
sys.publish("AUDIO_READY")
end
-- 配置好audio外设
sys.taskInit(audio_setup)
3.设置音频的回调函数,根据播放结果,返回对应内容
local taskName = "task_audio"
local MSG_MD = "moreData" -- 播放缓存有空余
local MSG_PD = "playDone" -- 播放完成所有数据
audio.on(0, function(id, event)
--使用play来播放文件时只有播放完成回调
local succ,stop,file_cnt = audio.getError(0)
if not succ then
if stop then
log.info("用户停止播放")
else
log.info("第", file_cnt, "个文件解码失败")
end
end
sysplus.sendMsg(taskName, MSG_PD)
end)
4.等待配置初始化完毕,接收"AUDIO_READY"消息,播放烧录进去的 1.mp3 文件,如果播放成功,等待 audio.on 的返回内容,激活等待,做一个关闭的再次判断,如果没有关闭,则手动关闭,进入 PM 待机模式,打印内存,增加了一个 3 秒打印一次内存的定时器,用于实时监控播放音乐时候的内存
local function audio_task()
local result
sys.waitUntil("AUDIO_READY")
while true do
log.info("开始播放")
result = audio.play(0,"/luadb/1.mp3")
if result then
--等待音频通道的回调消息,或者切换歌曲的消息
while true do
msg = sysplus.waitMsg(taskName, nil)
if type(msg) == 'table' then
if msg[1] == MSG_PD then
log.info("播放结束")
break
end
else
log.error(type(msg), msg)
end
end
else
log.debug("解码失败!")
sys.wait(1000)
end
if not audio.isEnd(0) then
log.info("手动关闭")
audio.playStop(0)
end
if audio.pm then
audio.pm(0,audio.STANDBY) --PM模式 待机模式,PA断电,codec待机状态,系统不能进低功耗状态,如果PA不可控,codec进入静音模式
end
-- audio.pm(0,audio.SHUTDOWN) --低功耗可以选择SHUTDOWN或者POWEROFF,如果codec无法断电用SHUTDOWN
log.info("mem", "sys", rtos.meminfo("sys"))
log.info("mem", "lua", rtos.meminfo("lua"))
sys.wait(1000)
end
end
sys.timerLoopStart(function()
log.info("mem.lua", rtos.meminfo())
log.info("mem.sys", rtos.meminfo("sys"))
end, 3000)
sysplus.taskInitEx(audio_task, taskName, task_cb)
五、运行结果展示
5.1 完整代码
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "audio"
VERSION = "1.0.0"
--[[
本demo可直接在Air8000整机开发板上运行
]]
-- sys库是标配
_G.sys = require("sys")
_G.sysplus = require("sysplus")
gpio.setup(24, 1, gpio.PULLUP) -- i2c工作的电压域
mcu.altfun(mcu.I2C, 0, 13, 2, 0) --复用pin80为i2c0_scl
mcu.altfun(mcu.I2C, 0, 14, 2, 0) --复用pin81为i2c0_sda
local i2c_id = 0 -- i2c_id 0
local pa_pin = 16 -- 喇叭pa功放脚
local power_pin = 8 -- es8311电源脚
local i2s_id = 0 -- i2s_id 0
local i2s_mode = 0 -- i2s模式 0 主机 1 从机
local i2s_sample_rate = 16000 -- 采样率
local i2s_bits_per_sample = 16 -- 数据位数
local i2s_channel_format = i2s.MONO_R -- 声道, 0 左声道, 1 右声道, 2 立体声
local i2s_communication_format = i2s.MODE_LSB -- 格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
local i2s_channel_bits = 16 -- 声道的BCLK数量
local multimedia_id = 0 -- 音频通道 0
local pa_on_level = 1 -- PA打开电平 1 高电平 0 低电平
local power_delay = 3 -- 在DAC启动前插入的冗余时间,单位100ms
local pa_delay = 100 -- 在DAC启动后,延迟多长时间打开PA,单位1ms
local power_on_level = 1 -- 电源控制IO的电平,默认拉高
local power_time_delay = 100 -- 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms
local voice_vol = 50 -- 喇叭音量
local mic_vol = 80 -- 麦克风音量
function audio_setup()
pm.power(pm.LDO_CTL, false) --开发板上ES8311由LDO_CTL控制上下电
sys.wait(100)
pm.power(pm.LDO_CTL, true) --开发板上ES8311由LDO_CTL控制上下电
i2c.setup(i2c_id,i2c.FAST) --设置i2c
i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,i2s_channel_bits) --设置i2s
audio.config(multimedia_id, pa_pin, pa_on_level, power_delay, pa_delay, power_pin, power_on_level, power_time_delay)
audio.setBus(multimedia_id, audio.BUS_I2S,{chip = "es8311",i2cid = i2c_id , i2sid = i2s_id, voltage = audio.VOLTAGE_1800}) --通道0的硬件输出通道设置为I2S
audio.vol(multimedia_id, voice_vol)
audio.micVol(multimedia_id, mic_vol)
sys.publish("AUDIO_READY")
end
-- 配置好audio外设
sys.taskInit(audio_setup)
local taskName = "task_audio"
local MSG_MD = "moreData" -- 播放缓存有空余
local MSG_PD = "playDone" -- 播放完成所有数据
audio.on(0, function(id, event)
--使用play来播放文件时只有播放完成回调
local succ,stop,file_cnt = audio.getError(0)
if not succ then
if stop then
log.info("用户停止播放")
else
log.info("第", file_cnt, "个文件解码失败")
end
end
sysplus.sendMsg(taskName, MSG_PD)
end)
local function audio_task()
local result
sys.waitUntil("AUDIO_READY")
while true do
log.info("开始播放")
result = audio.play(0,"/luadb/1.mp3")
if result then
--等待音频通道的回调消息,或者切换歌曲的消息
while true do
msg = sysplus.waitMsg(taskName, nil)
if type(msg) == 'table' then
if msg[1] == MSG_PD then
log.info("播放结束")
break
end
else
log.error(type(msg), msg)
end
end
else
log.debug("解码失败!")
sys.wait(1000)
end
if not audio.isEnd(0) then
log.info("手动关闭")
audio.playStop(0)
end
if audio.pm then
audio.pm(0,audio.STANDBY) --PM模式 待机模式,PA断电,codec待机状态,系统不能进低功耗状态,如果PA不可控,codec进入静音模式
end
-- audio.pm(0,audio.SHUTDOWN) --低功耗可以选择SHUTDOWN或者POWEROFF,如果codec无法断电用SHUTDOWN
log.info("mem", "sys", rtos.meminfo("sys"))
log.info("mem", "lua", rtos.meminfo("lua"))
sys.wait(1000)
end
end
sys.timerLoopStart(function()
log.info("mem.lua", rtos.meminfo())
log.info("mem.sys", rtos.meminfo("sys"))
end, 3000)
sysplus.taskInitEx(audio_task, taskName, task_cb)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
5.2 结果演示
六、总结
本文演示如何在 Air8000 整机板外挂 es8311,播放烧录进去的 MP3 文件。
七、常见问题
后续在此扩展补充,敬请期待......