LE Audio 是 Bluetooth 5.2 引入的低功耗音频解决方案,支持广播音频、多音频流和 LC3 编解码器。
| 特性 | 描述 |
|---|---|
| LC3 编解码器 | 更高效的音频编解码器 |
| 广播音频 | 一对多音频传输 |
| 立体声 | 同步双声道 |
| 助听器 | 无障碍支持 |
| Auracast | 公共广播分享 |
┌─────────────────────────────────────────────┐
│ Application │
├─────────────────────────────────────────────┤
│ Audio Framework │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ LC3 Encoder │ │ LC3 Decoder │ │
│ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────┤
│ Audio Streams │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Isochronous│ │ CIS/BIS │ │
│ │ Channel │ │ Channel │ │
│ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────┤
│ GATT Services │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ ASCS │ │ PACS │ │
│ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────┤
│ Bluetooth 5.2+ │
└─────────────────────────────────────────────┘
| 术语 | 描述 |
|---|---|
| CIS | Connected Isochronous Stream |
| BIS | Broadcast Isochronous Stream |
| CIG | Connected Isochronous Group |
| BIG | Broadcast Isochronous Group |
| LC3 | Low Complexity Communication Codec |
| ASCS | Audio Stream Control Service |
| PACS | Published Audio Capabilities Service |
| AS | Audio Stream |
| BAP | Basic Audio Profile |
/* 音频位置定义 */
#define BT_AUDIO_LOCATION_FRONT_LEFT BIT(0)
#define BT_AUDIO_LOCATION_FRONT_RIGHT BIT(1)
#define BT_AUDIO_LOCATION_FRONT_CENTER BIT(2)
#define BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_1 BIT(3)
#define BT_AUDIO_LOCATION_BACK_LEFT BIT(4)
#define BT_AUDIO_LOCATION_BACK_RIGHT BIT(5)
#define BT_AUDIO_LOCATION_BACK_CENTER BIT(6)
#define BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_2 BIT(7)
/* 音频场景类型 */
#define BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED BIT(0)
#define BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL BIT(1)
#define BT_AUDIO_CONTEXT_TYPE_MEDIA BIT(2)
#define BT_AUDIO_CONTEXT_TYPE_GAME BIT(3)
#define BT_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL BIT(4)
#define BT_AUDIO_CONTEXT_TYPE_VOICE_ASSISTANTS BIT(5)
#define BT_AUDIO_CONTEXT_TYPE_LIVE BIT(6)
#define BT_AUDIO_CONTEXT_TYPE_SOUND_EFFECTS BIT(7)
LE Audio 相关配置通常围绕这些能力展开:
CONFIG_BT=y
CONFIG_BT_AUDIO=y
CONFIG_BT_ISO=y
# 角色按实际场景选择
CONFIG_BT_BAP_UNICAST_CLIENT=y
# CONFIG_BT_BAP_UNICAST_SERVER=y
# CONFIG_BT_BAP_BROADCAST_SOURCE=y
# CONFIG_BT_BAP_BROADCAST_SINK=y
# 能力发布 / 流控制
CONFIG_BT_PACS=y
CONFIG_BT_ASCS=y
CONFIG_BT_AUDIO_* 数值直接当成通用模板LC3 是 LE Audio 默认编解码器。工程里通常不会自己手写一整套“内部结构体定义”,而是直接使用 Zephyr 当前公开的 Bluetooth Audio API、preset 宏和 sample/application 的配置。
/* 编解码器能力结构 */
struct bt_audio_codec_cap {
uint8_t id;
uint16_t vendor_id;
uint16_t vendor_codec_id;
/* 支持的采样率 */
uint32_t sample_frequencies;
/* 支持的帧时长 */
uint8_t frame_durations;
/* 支持的声道配置 */
uint8_t channel_counts;
/* 每帧字节数范围 */
uint16_t min_octets_per_frame;
uint16_t max_octets_per_frame;
/* 块 */
uint8_t max_blocks_per_sdu;
};
/* 创建 LC3 能力 */
static struct bt_audio_codec_cap lc3_cap = {
.id = BT_AUDIO_CODEC_LC3_ID,
.sample_frequencies = BT_AUDIO_SAMPLE_RATE_16000 |
BT_AUDIO_SAMPLE_RATE_24000 |
BT_AUDIO_SAMPLE_RATE_48000,
.frame_durations = BT_AUDIO_FRAME_DURATION_7500US |
BT_AUDIO_FRAME_DURATION_10000US,
.channel_counts = 1 | 2,
.min_octets_per_frame = 20,
.max_octets_per_frame = 155,
.max_blocks_per_sdu = 1,
};
音频流管理的关键点是:
nrf5340_audio 应用/* 流状态机 */
enum bt_bap_state {
BT_BAP_STATE_IDLE, /* 空闲 */
BT_BAP_STATE_CONFIG, /* 配置中 */
BT_BAP_STATE_QOS, /* QoS 配置中 */
BT_BAP_STATE_ENABLING, /* 启用中 */
BT_BAP_STATE_STREAMING, /* 传输中 */
BT_BAP_STATE_PAUSING, /* 暂停中 */
BT_BAP_STATE_PAUSED, /* 已暂停 */
BT_BAP_STATE_DISABLING, /* 禁用中 */
BT_BAP_STATE_RELEASING, /* 释放中 */
};
/* ISO QoS 配置 */
struct bt_iso_qos {
struct {
uint16_t interval; /* ISO 间隔 (1250us - 4s) */
uint16_t latency; /* 延迟 (10ms - 255ms) */
uint8_t sdu; /* SDU 大小 */
uint8_t ft; /* 填充数 */
uint8_t burst; /* 突发数 */
} cig;
struct {
uint16_t max_sdu; /* 最大 SDU */
uint8_t rtn; /* 重传次数 */
uint8_t max_latency; /* 最大延迟 */
} cis;
};
/* 预设 QoS 配置 */
static const struct bt_iso_qos qos_preset = {
.cig = {
.interval = 10000, /* 10ms */
.latency = 10, /* 10ms */
.sdu = 80, /* 80 字节 */
.ft = 1, /* 1 帧/子事件 */
.burst = 1, /* 1 突发 */
},
.cis = {
.max_sdu = 80,
.rtn = 2,
.max_latency = 14,
},
};
/* ASCS 特征值 UUID */
#define BT_UUID_ASCS_ASE BT_UUID_128_ENCODE(0x2B3E, 0x1C5A, 0x3E4F, 0x9A2B, 0x8B6C, 0x4D5E, 0x7F8A, 0x9B0C)
#define BT_UUID_ASCS_CONTROL BT_UUID_128_ENCODE(0x2B4E, 0x1C5A, 0x3E4F, 0x9A2B, 0x8B6C, 0x4D5E, 0x7F8A, 0x9B0C)
/* ASE 状态 */
struct bt_ascs_ase_state {
enum {
ASE_STATE_IDLE, /* 空闲 */
ASE_STATE_CFP, /* Codec 配置待定 */
ASE_STATE_CONFIG, /* 配置中 */
ASE_STATE_READY, /* 就绪 */
ASE_STATE_STREAMING, /* 传输中 */
ASE_STATE_RELEASING, /* 释放中 */
} state;
uint8_t ase_id;
uint8_t ase_state;
uint16_t ase_state_control;
struct bt_audio_codec_cfg codec;
struct bt_iso_qos iso_qos;
};
/* ASCS 回调 */
static void ascs_discover_cb(struct bt_conn *conn, int err,
struct bt_ascs_ase *ase, uint8_t num)
{
printk("Discovered %d ASEs\n", num);
}
static void ascs_state_cb(struct bt_ascs_ase *ase,
enum bt_ascs_ase_state state)
{
printk("ASE %d state: %d\n", ase->id, state);
}
static struct bt_ascs_cb ascs_cbs = {
.discover = ascs_discover_cb,
.state = ascs_state_cb,
};
/* PACS 服务 */
static const struct bt_audio_pac_record pac_record = {
/* 麦克风能力 */
.codec = &lc3_cap,
.contexts = BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL,
.meta = BT_AUDIO_META_NONE,
};
/* 发布能力 */
static int publish_capabilities(void)
{
int err;
err = bt_pacs_capability_register(BT_AUDIO_DIR_SOURCE,
&pac_record);
if (err) {
printk("Failed to register capabilities: %d\n", err);
return err;
}
printk("Capabilities registered\n");
return 0;
}
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/ascs.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/audio/bap.h>
/* Audio Stream */
static struct bt_bap_stream audio_stream;
static struct bt_bap_stream_ops stream_ops;
/* ASCS 回调 */
static void stream_started(struct bt_bap_stream *stream)
{
printk("Audio stream started\n");
}
static void stream_stopped(struct bt_bap_stream *stream, int err)
{
printk("Audio stream stopped: %d\n", err);
}
static void stream_recv(struct bt_bap_stream *stream,
const struct bt_audio_recv_info *info,
struct net_buf *buf)
{
/* 处理接收的音频数据 */
int16_t *pcm_data = (int16_t *)buf->data;
size_t pcm_count = buf->len / sizeof(int16_t);
/* 音频处理/播放 */
audio_playback(pcm_data, pcm_count);
}
static void stream_released(struct bt_bap_stream *stream)
{
printk("Audio stream released\n");
}
static struct bt_bap_stream_ops stream_ops = {
.started = stream_started,
.stopped = stream_stopped,
.recv = stream_recv,
.released = stream_released,
};
/* 初始化 Audio */
int audio_init(void)
{
int err;
/* 注册流回调 */
bt_bap_stream_cb_register(&audio_stream, &stream_ops);
/* 发布音频能力 */
err = bt_pacs_set_location(BT_AUDIO_LOCATION_FRONT_LEFT |
BT_AUDIO_LOCATION_FRONT_RIGHT);
if (err) {
printk("Failed to set location: %d\n", err);
return err;
}
/* 发布支持的上下文 */
err = bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE,
BT_AUDIO_CONTEXT_TYPE_MEDIA |
BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL);
if (err) {
printk("Failed to set contexts: %d\n", err);
return err;
}
printk("Audio initialized\n");
return 0;
}
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Audio │───▶│ LC3 │───▶│ ISO │
│ PCM Data │ │ Encoder │ │ Stream │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Bluetooth │
│ Radio │
└─────────────┘
/* 编码并发送音频 */
static int audio_send(struct bt_bap_stream *stream,
const int16_t *pcm_data,
size_t pcm_count)
{
struct net_buf *buf;
uint8_t encoded_data[200];
size_t encoded_len;
int err;
/* LC3 编码 */
err = lc3_encode(encoder, pcm_data, pcm_count,
encoded_data, &encoded_len);
if (err) {
printk("LC3 encode failed: %d\n", err);
return err;
}
/* 分配缓冲区 */
buf = net_buf_alloc(iso_pool, K_FOREVER);
net_buf_add_mem(buf, encoded_data, encoded_len);
/* 发送 */
err = bt_bap_stream_send(stream, buf, seq_num++, 0);
if (err) {
printk("Failed to send audio: %d\n", err);
net_buf_unref(buf);
return err;
}
return 0;
}
/* 定时发送音频 */
static void audio_timer_handler(struct k_timer *timer)
{
static uint16_t seq_num;
/* 读取麦克风 */
int16_t mic_data[128];
size_t count = mic_read(mic_data, 128);
/* 发送 */
audio_send(&audio_stream, mic_data, count);
}
K_TIMER_DEFINE(audio_timer, audio_timer_handler, NULL);
采集侧通常通过 I2S / audio pipeline 获取 PCM 数据,然后进入 LC3 编码和 ISO 发送链路。具体驱动细节与板卡和音频前端强相关,不建议把某个 i2s_read() 片段当通用 LE Audio 模板。
广播源常见流程:
广播接收常见流程:
/* 左右声道流 */
static struct bt_bap_stream stream_left;
static struct bt_bap_stream stream_right;
/* 立体声配置 */
static const struct bt_audio_codec_cfg stereo_codec = {
.id = BT_AUDIO_CODEC_LC3_ID,
.data = {
LC3_SAMPLE_RATE, 2, 0x03, 0x00, /* 48kHz */
LC3_FRAME_DURATION, 1, 0x07, /* 7.5ms */
LC3_CHANNEL_COUNT, 1, 0x02, /* 2 声道 */
},
};
/* 发送立体声 */
static int send_stereo(const int16_t *left,
const int16_t *right,
size_t frames)
{
int err;
/* 交错数据 */
int16_t interleaved[frames * 2];
for (size_t i = 0; i < frames; i++) {
interleaved[i * 2] = left[i];
interleaved[i * 2 + 1] = right[i];
}
/* 发送到左右流 */
err = audio_send(&stream_left, interleaved, frames);
if (err) return err;
err = audio_send(&stream_right, interleaved + frames, frames);
if (err) return err;
return 0;
}
/* Auracast 广播参数 */
static const struct bt_audio_data auracast_adv_data[] = {
BT_AUDIO_DATA(BT_AUDIO_DATA_URI, "urn:btlink:channel:public", 26),
BT_AUDIO_DATA(BT_AUDIO_DATA_NAME, "Auracast Demo", 12),
};
/* 广播元数据 */
static const struct bt_audio_data auracast_meta[] = {
BT_AUDIO_DATA(BT_AUDIO_DATA_VENDOR,
"\x01\x02\x03\x04", 4),
};
/* 创建 Auracast 广播 */
static int create_auracast(void)
{
/* 类似广播源,但添加元数据 */
}
CONFIG_BT_DEBUG_LOG=y
# 具体子模块调试选项以当前 Bluetooth Audio Kconfig 为准
# nRF Connect for Mobile
# 扫描 LE Audio 设备
# 测试音频流
# 广播音频
nrf/applications/nrf5340_audioLE Audio 在 NCS 里通常是完整应用级工程,不建议把 USB 音频、I2S、LC3、BAP 全都拆成几行孤立配置后直接套用。
┌──────────┐ ┌──────────┐
│ Phone │ CIS │ TWS │
│ (SRC) │────────▶│ Earbuds │
└──────────┘ └──────────┘
│
┌─────┴─────┐
│ │
Left Right
Stream Stream
┌──────────┐ ┌──────────┐
│ Phone │ CIS │ Hearing │
│ (SRC) │────────▶│ Aid │
└──────────┘ └──────────┘
┌──────────┐ ┌──────────┐
│ Broadcast│ BIS │ Multiple │
│ Source │────────▶│ Listeners│
└──────────┘ └──────────┘
学习日期: 2026-03-21 笔记编号: #54 作者: 小白 🤖