音频播放

本文说明本章节功能的接入方式、关键源码和应用参考,帮助开发者快速完成集成。

1. 应用例程

场景

文件

调用接口

AI App 同声传译音频采集和播放

ai_simi_audio.c

音频采集 / 播放回调

AI App 同声传译 WebSocket 音频服务

ai_simi_service.c

WebSocket 连接、音频数据收发回调

2. 使用前配置

音频功能的总开关为 AUDIO。其直接依赖 !SOC_SF32LB55X,默认关闭,因此 SF32LB55X 工程中不显示。SDK 音频配置由所有核的 SDK 配置树引入,完整路径为:menuconfig (Top) SiFli SDK configuration SiFli Built-in Components Audio (AUDIO);HCPU 和 PC Simulator 工程还会由 Solution 框架引入同一符号的第二个入口:menuconfig (Top) Audio config Audio (AUDIO)。两个入口配置的是同一个 AUDIO 符号,修改任一入口均生效;LCPU 工程没有 Audio config 菜单,应使用前一条 SDK 路径。

打开 AUDIO 后,按实际业务选择音频路径和功能;不需要的功能不要打开。

使用场景

必需宏

配置路径与说明

PCM 播放、录音、基础扬声器/麦克风通路

AUDIO

HCPU/PC Simulator 路径为:menuconfig (Top) Audio config Audio (AUDIO);LCPU 路径为:menuconfig (Top) SiFli SDK configuration SiFli Built-in Components Audio (AUDIO)。在该菜单按硬件选择 Select audio path Type、默认输入 Default Audio RX Path 与默认输出 Default Audio TX Path;这三个单选项均默认选择 HCI PATH、Codec Onchip 和 Codec Onchip。板载 codec 使用 Codec Onchip;外接 I2S 麦克风或功放使用对应的 I2S;PDM 麦克风使用 PDM

使用 audio_open()audio_write()audio_close(),或使用音频处理框架

AUDIOAUDIO_USING_AUDPROC

HCPU/PC Simulator 路径为:menuconfig (Top) Audio config Audio (AUDIO) Enable audio process framework (AUDIO_USING_AUDPROC);LCPU 路径为:menuconfig (Top) SiFli SDK configuration SiFli Built-in Components Audio (AUDIO) Enable audio process framework (AUDIO_USING_AUDPROC)AUDIO_USING_AUDPROC 无额外直接依赖,在 AUDIO 启用后显示,默认开启。

使用 mp3ctrl_open()mp3ctrl_open_buffer() 播放本地文件或内存中的 MP3/WAV

AUDIOAUDIO_USING_AUDPROCAUDIO_USING_MANAGERAUDIO_LOCAL_MUSIC

路径为:menuconfig (Top) Audio config Audio (AUDIO) Enable audio process framework (AUDIO_USING_AUDPROC) Enable local audio (AUDIO_LOCAL_MUSIC)AUDIO_USING_MANAGER 直接依赖 BF0_HCPU,默认开启;AUDIO_LOCAL_MUSIC 直接依赖 AUDIO_USING_MANAGER && BF0_HCPU,默认关闭,启用后自动选择 PKG_USING_LIBHELIX。因此本地音频仅能在 HCPU 工程配置。

MP3/WAV 边下载边播放

上一行全部宏,加 AUDIO_MP3_RINGBUFF_SUPPORT

路径为:menuconfig (Top) Audio config Audio (AUDIO) Enable audio process framework (AUDIO_USING_AUDPROC) Enable local audio (AUDIO_LOCAL_MUSIC) Enable MP3 ring buffer support (AUDIO_MP3_RINGBUFF_SUPPORT)。该项无额外直接 depends on,仅在 AUDIO_LOCAL_MUSIC 启用后显示,默认关闭;因此仅支持 HCPU。

蓝牙音频

AUDIOAUDIO_USING_AUDPROCAUDIO_BT_AUDIO

HCPU/PC Simulator 路径为:menuconfig (Top) Audio config Audio (AUDIO) Enable audio process framework (AUDIO_USING_AUDPROC) Enable BT audio (AUDIO_BT_AUDIO);LCPU 路径为:menuconfig (Top) SiFli SDK configuration SiFli Built-in Components Audio (AUDIO) Enable audio process framework (AUDIO_USING_AUDPROC) Enable BT audio (AUDIO_BT_AUDIO)AUDIO_BT_AUDIOAUDIO_USING_AUDPROC 启用后才可见,默认开启;HCPU 还必须启用 PERI_USING_BT,需先按蓝牙通用说明打开经典 BT 基础功能和对应 profile;LCPU 不要求 PERI_USING_BT

网络音频还需要按实际承载完成 Wi-Fi、4G 或 BT PAN 配置,详见网络访问与文件下载

3. 音频来源与接口选择

网络音频不要只按“从网络来”分类,而要按“数据到达设备时的形态”选择接口。

音频数据形态

推荐接口

适用场景

文件系统中的 mp3/wav

mp3ctrl_open() + mp3ctrl_play()

已下载完成的文件、本地音乐

内存中的 mp3/wav

mp3ctrl_open_buffer() + mp3ctrl_play()

HTTP 下载完再播放的短音频

ringbuffer 中的 mp3/wav

mp3ctrl_open_ringbuffer() + rt_ringbuffer_put()

边下载边播放,需要 AUDIO_MP3_RINGBUFF_SUPPORT

PCM 流

audio_open() + audio_write()

解码后的 PCM,或服务器直接返回 PCM

Opus/AI TTS 流

Opus 解码 + audio_write()

小智 AI、大模型 TTS 流

系统默认支持 mp3、wav、aac、opus,其中 wav 只支持 16 位 PCM。mp3/wav 可由 audio_mp3ctrl 提供自动 demux 和播放;aac 与 opus 的格式识别、解码和缓存需要业务层按音频来源处理。

4. 播放本地音频文件

适合已经下载到文件系统中的 mp3/wav 文件。

#include <rtthread.h>
#include <rtdevice.h>
#include "audio_mp3ctrl.h"

static mp3ctrl_handle g_file_player;

int demo_play_local_file(const char *path)
{
    if (!path)
    {
        return -1;
    }

    g_file_player = mp3ctrl_open(AUDIO_TYPE_LOCAL_MUSIC, path, NULL, NULL);
    if (!g_file_player)
    {
        rt_kprintf("mp3ctrl_open failed: %s\n", path);
        return -1;
    }

    mp3ctrl_play(g_file_player);
    return 0;
}

void demo_stop_local_file(void)
{
    if (g_file_player)
    {
        mp3ctrl_close(g_file_player);
        g_file_player = NULL;
    }
}

5. 播放内存中的音频

适合 HTTP 下载到内存后再播放的短音频。注意:播放是异步过程,data 在播放结束前不能释放;如果需要自动释放,建议在 mp3ctrl_open_buffer() 的播放结束 callback 中释放。

#include <rtthread.h>
#include "audio_mp3ctrl.h"

static mp3ctrl_handle g_buffer_player;

int demo_play_audio_buffer(uint8_t *data, uint32_t len)
{
    if (!data || len == 0)
    {
        return -1;
    }

    g_buffer_player = mp3ctrl_open_buffer(
        AUDIO_TYPE_LOCAL_MUSIC,
        (const char *)data,
        len,
        NULL,
        NULL
    );
    if (!g_buffer_player)
    {
        rt_kprintf("mp3ctrl_open_buffer failed\n");
        return -1;
    }

    mp3ctrl_play(g_buffer_player);
    return 0;
}

void demo_stop_audio_buffer(void)
{
    if (g_buffer_player)
    {
        mp3ctrl_close(g_buffer_player);
        g_buffer_player = NULL;
    }
}

6. 下载完成后播放

6.1 下载到内存后播放短音频

可以复用 网络访问与文件下载 中的 demo_http_get_to_memory(),先下载到内存,再调用 demo_play_audio_buffer() 播放。

/* demo_mem_file_t 和 demo_http_get_to_memory() 来自网络访问与文件下载文档 */
static demo_mem_file_t g_voice_file;

int demo_download_voice_to_memory_and_play(const char *uri)
{
    if (demo_http_get_to_memory(uri, &g_voice_file) != 0)
    {
        return -1;
    }

    return demo_play_audio_buffer(g_voice_file.data, g_voice_file.size);
}

播放结束后再释放 g_voice_file.data。如果需要自动释放,可以在 mp3ctrl_open_buffer() 的 callback 中处理。

6.2 下载到文件系统后播放大音频

大音频建议保存到文件系统,再通过文件路径播放。

#include <rtthread.h>
#include <webclient.h>
#include "audio_mp3ctrl.h"

int demo_download_voice_to_file_and_play(const char *uri, const char *path)
{
    if (webclient_get_file(uri, path) != 0)
    {
        rt_kprintf("download failed: %s\n", uri);
        return -1;
    }

    return demo_play_local_file(path);
}

7. MP3/WAV 边下载边播放

如果服务器返回 mp3/wav 连续数据,不想等整个 HTTP 文件下载完成后再播放,可以使用 ringbuffer 播放接口。需要打开 AUDIOAUDIO_USING_AUDPROCAUDIO_USING_MANAGERAUDIO_LOCAL_MUSICAUDIO_MP3_RINGBUFF_SUPPORT;完整路径和依赖关系见使用前配置。mp3/aac/opus 等具体解码库同样依赖 AUDIO

#include <rtthread.h>
#include <rtdevice.h>
#include "audio_mp3ctrl.h"

#define DEMO_AUDIO_RB_SIZE  (64 * 1024)

static struct rt_ringbuffer *g_stream_rb;
static mp3ctrl_handle g_stream_player;

int demo_stream_player_start(uint32_t file_len)
{
#ifdef AUDIO_MP3_RINGBUFF_SUPPORT
    g_stream_rb = rt_ringbuffer_create(DEMO_AUDIO_RB_SIZE);
    if (!g_stream_rb)
    {
        rt_kprintf("create ringbuffer failed\n");
        return -1;
    }

    g_stream_player = mp3ctrl_open_ringbuffer(
        AUDIO_TYPE_LOCAL_MUSIC,
        g_stream_rb,
        file_len,
        NULL,
        NULL
    );
    if (!g_stream_player)
    {
        rt_ringbuffer_destroy(g_stream_rb);
        g_stream_rb = RT_NULL;
        return -1;
    }

    mp3ctrl_play(g_stream_player);
    return 0;
#else
    rt_kprintf("AUDIO_MP3_RINGBUFF_SUPPORT is not enabled\n");
    return -1;
#endif
}

int demo_stream_player_feed(uint8_t *data, uint32_t len)
{
#ifdef AUDIO_MP3_RINGBUFF_SUPPORT
    uint32_t offset = 0;

    if (!g_stream_rb || !data || len == 0)
    {
        return -1;
    }

    while (offset < len)
    {
        uint32_t put_len = rt_ringbuffer_put(g_stream_rb, data + offset, len - offset);
        if (put_len == 0)
        {
            rt_thread_mdelay(10);
            continue;
        }

        offset += put_len;
    }

    return 0;
#else
    return -1;
#endif
}

void demo_stream_player_stop(void)
{
#ifdef AUDIO_MP3_RINGBUFF_SUPPORT
    if (g_stream_player)
    {
        mp3ctrl_close(g_stream_player);
        g_stream_player = RT_NULL;
    }

    if (g_stream_rb)
    {
        rt_ringbuffer_destroy(g_stream_rb);
        g_stream_rb = RT_NULL;
    }
#endif
}

网络下载线程中可按下面方式投喂:

/* webclient_session_create() 和 webclient_get() 已成功 */
while (1)
{
    int n = webclient_read(session, read_buf, read_buf_size);
    if (n < 0)
    {
        break;
    }
    if (n == 0)
    {
        break;
    }

    demo_stream_player_feed(read_buf, n);
}

说明:如果 HTTP 响应没有 Content-Length,file_len 的取值需要结合当前 mp3ctrl ringbuffer 实现验证;无法确认总长度时,应在项目中实测 chunked 下载和播放结束条件。

8. PCM 流式播放

如果服务器直接返回 PCM,或者业务层已经把 mp3/aac/opus 解码成 PCM,可以直接通过 audio_write() 写入播放缓存。

#include <rtthread.h>
#include "audio_server.h"

static audio_client_t g_pcm_client;

int demo_pcm_stream_start(uint32_t sample_rate, uint8_t channels)
{
    audio_parameter_t pa = {0};

    pa.write_samplerate = sample_rate;
    pa.write_channnel_num = channels;
    pa.write_bits_per_sample = 16;
    pa.write_cache_size = 4096;

    g_pcm_client = audio_open(
        AUDIO_TYPE_LOCAL_MUSIC,
        AUDIO_TX,
        &pa,
        NULL,
        NULL
    );
    if (!g_pcm_client)
    {
        rt_kprintf("audio_open failed\n");
        return -1;
    }

    return 0;
}

int demo_pcm_stream_write(uint8_t *pcm, uint32_t len)
{
    uint32_t offset = 0;

    if (!g_pcm_client || !pcm || len == 0)
    {
        return -1;
    }

    while (offset < len)
    {
        int written = audio_write(g_pcm_client, pcm + offset, len - offset);
        if (written < 0)
        {
            rt_kprintf("audio_write failed: %d\n", written);
            return -1;
        }
        if (written == 0)
        {
            rt_thread_mdelay(10);
            continue;
        }

        offset += written;
    }

    return 0;
}

void demo_pcm_stream_stop(void)
{
    if (g_pcm_client)
    {
        audio_close(g_pcm_client);
        g_pcm_client = RT_NULL;
    }
}

audio_write() 的返回值是实际写入的字节数,可能小于请求写入长度,业务代码需要继续写剩余数据。返回 -1 表示当前音频服务优先级被音频打断,返回 -2 表示参数错误。

9. AI TTS / Opus 播放

小智 AI 组件已经提供了 Opus 流式音频链路,参考代码位于:

solution/components/xiaozhi/xiaozhi_audio.c

小智使用的音频参数为:

format: opus
sample_rate: 16000
channels: 1
frame_duration: 60 ms

典型播放流程为:

WebSocket/UDP 收到 Opus 数据
Opus decoder 解码
得到 16 kHz 单声道 PCM
audio_write() 写入 speaker client
扬声器播放

如果客户对接第三方大模型 TTS,也建议使用同样的分层:网络线程只负责接收压缩帧,解码线程负责 Opus/AAC/MP3 转 PCM,播放线程通过 audio_write() 连续写入。

10. 播放过程中的息屏和 AOD

播放音频时需要先明确产品策略:

场景

推荐方式

适用场景

允许音频继续播放但关闭屏幕

SLEEP_CLOSE_DISPLAY_ONLY

后台音乐、AI 语音、长音频

播放期间不允许系统进入睡眠

播放前申请 PM lock,播放结束释放

播放时需要保持 UI、网络或动画实时运行

息屏时仍需要显示 AOD

进入 AOD 并注册 AOD 响应函数

表盘和固定提示类页面

10.1 只关闭屏幕但继续播放音频

SLEEP_CLOSE_DISPLAY_ONLY 的定义如下:

SLEEP_CLOSE_DISPLAY_ONLY = 0x08, /**< Close lcd */

在当前实现中会禁止系统进入真正睡眠,只关闭显示。该方式适合音频后台播放的场景。

#include "app_pm.h"

void demo_audio_allow_display_off(void)
{
    app_gui_sleep_type_set(SLEEP_CLOSE_DISPLAY_ONLY);
}

10.2 播放期间禁止睡眠

如果播放时必须保持系统活跃,应在播放开始申请 PM lock,在播放结束释放。注意申请和释放必须成对调用。

#include <rtthread.h>
#include <rtdevice.h>

void demo_audio_pm_lock(void)
{
#ifdef BSP_USING_PM
    rt_pm_request(PM_SLEEP_MODE_IDLE);
#endif
}

void demo_audio_pm_unlock(void)
{
#ifdef BSP_USING_PM
    rt_pm_release(PM_SLEEP_MODE_IDLE);
#endif
}

结合 mp3ctrl 播放结束回调:

#include "audio_mp3ctrl.h"

static int demo_player_callback(audio_server_callback_cmt_t cmd,
                                void *callback_userdata,
                                uint32_t reserved)
{
    if (cmd == as_callback_cmd_play_to_end)
    {
        demo_audio_pm_unlock();
    }

    return 0;
}

int demo_play_file_keep_awake(const char *path)
{
    mp3ctrl_handle player;

    demo_audio_pm_lock();

    player = mp3ctrl_open(AUDIO_TYPE_LOCAL_MUSIC, path, demo_player_callback, NULL);
    if (!player)
    {
        demo_audio_pm_unlock();
        return -1;
    }

    mp3ctrl_play(player);
    return 0;
}

10.3 息屏状态下唤醒 UI

网络线程、播放线程或 AI 线程需要在息屏时唤醒 UI,可发送带 NEED_WAKEUP_UI 的 GUI 消息。该机制用于明确需要点亮或唤醒界面的业务,例如 OTA、通话和重要提醒。

send_msg_to_gui_thread(data,
                       len,
                       process_in_gui_thread_cb,
                       msg_id,
                       NEED_WAKEUP_UI);

AOD 场景下不要滥用 NEED_WAKEUP_UI,因为唤醒 UI 会退出 AOD。AOD 框架中注册显示逻辑请参考 AOD

11. 与其他文档的分工