跳转至

播放音乐

一、演示功能概述

本篇文章演示的内容为: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 文件。

七、常见问题

后续在此扩展补充,敬请期待......