跳转至

播放TTS

一、TTS 概述

TTS(文本转语音)是一种将书面文本转换为人类可听语音的技术,通过算法和模型模拟人类发声,实现机器“说话”。其核心目标是生成自然、流畅且富有表现力的语音。

二、演示功能概述

本篇文章演示的内容为:Air780EHV 核心板,外接 AirAUDIO_1000 音频扩展板,接上喇叭,播放千字文。

三、准备硬件环境

参考:Air780EHV 硬件环境清单,准备好硬件环境,本篇文档使用的是 AirEHV 核心板和 AirAUDIO_1000 音频扩展板,如下图所示:

连接图:

780EHV 核心板 连接 AirAUDIO_1000

5/SPK+ ———————— SPK+

6/SPK- ———————— SPK-

20/GPIO22 ————————PA_EN

3V3 ———————— VCC

GND ———————— GND

四、准备软件环境

烧录工具:Luatools 工具

Air780EHV 烧录需要的固件和脚本文件:

LuatOS 运行所需要的 lib 文件:

使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件

五、代码 API 和代码解析

5.1 代码 API

API 链接:audio

5.2 代码解析

1.设置 i2s 和 audio 的一些参数,控制 8311 上电,拉高 PA 功放脚,然后设置 i2c,i2s,audio 的一些基础配置,然后配置完成之后用 sys.publish 唤醒下面的 audio_task()的配置等待完毕。

local i2c_id = 0 -- i2c_id 0

local pa_pin = gpio.AUDIOPA_EN -- 喇叭pa功放脚
local power_pin = 20 -- 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 -- 麦克风音量

gpio.setup(power_pin, 1, gpio.PULLUP)   -- 设置ES83111电源脚
gpio.setup(pa_pin, 1, gpio.PULLUP)      -- 设置功放PA脚

function audio_setup()
    log.info("audio_setup")
    sys.wait(200)

    i2c.setup(i2c_id, i2c.FAST)
    i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,
        i2s_channel_bits)

    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
    }) -- 通道0的硬件输出通道设置为I2S

    audio.vol(multimedia_id, voice_vol)
    audio.micVol(multimedia_id, mic_vol)
    sys.publish("AUDIO_READY")
end

-- 配置好audio外设
sys.taskInit(audio_setup)

2.设置音频的回调函数,根据播放结果,返回对应内容

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)

3.等待配置初始化完毕,接收"AUDIO_READY"消息,从模块里面烧录的 qianzw.txt 文件,读取该文件的一行,如果读取失败,则写入固定的内容,去除头尾空格,播放内容,如果播放成功,等待 audio.on 的返回内容,激活等待,做一个关闭的再次判断,如果没有关闭,则手动关闭,进入 PM 待机模式,打印内存

local function audio_task()
    local result
    sys.waitUntil("AUDIO_READY")

    -- 本例子是按行播放 "千字文", 文本来源自wiki百科
    local fd = nil
    local line = nil
    while true do
        log.info("开始播放")
        line = nil
        if not fd then
            fd = io.open("/luadb/qianzw.txt")
        end
        if fd then
            line = fd:read("*l")
            if line == nil then
                fd:close()
                fd = nil
            end
        end
        if line == nil then
            line =
                "一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十"
        end
        line = line:trim()
        log.info("播放内容", line)
        result = audio.tts(0, line)
        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)
        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
    sysplus.taskDel(taskName)
end

sysplus.taskInitEx(audio_task, taskName)

六、运行结果展示

6.1 完整代码

-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "testtts"
VERSION = "1.0.0"

--[[]
运行环境:Air780EHV核心板+AirAUDIO_1000配件板
最后修改时间:2025-6-17
使用了如下IO口:
[5, "spk+", " PIN5脚, 用于喇叭正极"],
[6, "spk-", " PIN6脚, 用于喇叭负极"],
[20, "AudioPA_EN", " PIN20脚, 用于PA使能脚"],
3.3V
GND
执行逻辑为:
设置i2s和音频参数,读取文件qianzw.txt里面的内容,然后播放出来
]]

-- sysplus库是可选的,可以用sys库代替
-- sys库是标配
_G.sys = require("sys")
_G.sysplus = require("sysplus")

local i2c_id = 0 -- i2c_id 0

local pa_pin = gpio.AUDIOPA_EN -- 喇叭pa功放脚
local power_pin = 20 -- 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 -- 麦克风音量

gpio.setup(power_pin, 1, gpio.PULLUP)   -- 设置ES83111电源脚
gpio.setup(pa_pin, 1, gpio.PULLUP)      -- 设置功放PA脚

function audio_setup()
    log.info("audio_setup")
    sys.wait(200)

    i2c.setup(i2c_id, i2c.FAST)
    i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,
        i2s_channel_bits)

    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
    }) -- 通道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")

    -- 本例子是按行播放 "千字文", 文本来源自wiki百科
    local fd = nil
    local line = nil
    while true do
        log.info("开始播放")
        line = nil
        if not fd then
            fd = io.open("/luadb/qianzw.txt")
        end
        if fd then
            line = fd:read("*l")
            if line == nil then
                fd:close()
                fd = nil
            end
        end
        if line == nil then --如果没有找到千字文,播放以下文本
            line =
                "一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十"
        end
        line = line:trim()
        log.info("播放内容", line)
        result = audio.tts(0, line)
        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)
        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
    sysplus.taskDel(taskName)
end

sysplus.taskInitEx(audio_task, taskName)

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

6.2 结果演示

七、总结

本文演示如何用 Air780EHV 核心板外接 AirAUDIO_1000 音频扩展板,读取模块烧录的 txt 文件,播放千字文。

八、常见问题

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