音频开发与 ALSA
目标平台: RK3576 | NPU 算力: 6 TOPS | 文档版本: V1.0
适用板型: C-3576BQ 核心板 + M4-R2 底板
最后更新: 2026-10-23
1. 概述
1.1 功能简介
RK3576 集成丰富的音频接口资源。M4-R2 底板提供 1 路 3.5mm 耳机接口(支持左右双声道,支持 MIC 录音)和 1 路 2pin/1.25mm MIC 接口(用于外置麦克风)。此外,RK3576 支持 HDMI 音频输出,可通过 HDMI Type-A 接口将数字音频信号传输至外部显示设备。Linux 系统通过 ALSA 框架提供标准的音频设备接口。
说明:RK3576 芯片具体的 I2S/PDM/SPDIF 控制器数量、通道数与寄存器基地址以实际发布的 RK3576 SDK 为准(待板上确认)。
1.2 应用场景
- 高保真音频播放:通过 3.5mm 耳机接口输出立体声
- 语音采集:通过 3.5mm 耳机 MIC 或板载 MIC 接口录音
- HDMI 音频输出:通过 HDMI 接口传输至电视、投影仪或 AV 功放
- 语音交互:语音识别、语音助手、智能音箱等场景
1.3 工作原理
RK3576 音频子系统层次:
应用层(aplay/arecord/GStreamer/自定义应用程序)
|
alsa-lib 用户空间库
|
ALSA 内核驱动(sound/soc/rockchip/)
|-- Machine Driver(关联 CPU DAI 和 Codec)
|-- CPU DAI Driver(I2S 控制器驱动)
|-- Codec Driver(外部音频 Codec 驱动)
|-- HDMI Audio Driver(HDMI 音频输出驱动)
+-- DMA Engine(音频数据传输)RK3576 音频接口硬件资源:
| 接口 | 设备树节点 | 基地址 | 说明 |
|---|---|---|---|
| I2S | i2s@<base_addr> | 以实际 SDK 为准 | 主 I2S 控制器,支持 TDM 模式 |
| HDMI Audio | hdmi@<base_addr> | 以实际 SDK 为准 | HDMI 数字音频输出 |
上表节点名为通用结构示例,RK3576 实际的 I2S/HDMI 设备树节点名与寄存器基地址以实际发布的 RK3576 SDK 为准(待板上确认)。
2. 硬件准备
2.1 硬件清单
| 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 核心板 | C-3576BQ | 1 | RK3576 SoC |
| 底板 | ShimetaPi M4-R2 V10 | 1 | 含音频 Codec 及外设接口 |
| 调试串口线 | USB 转 TTL(3.3V) | 1 | 波特率 1500000 |
| 电源 | 5V/2A(Type-C 或 6pin) | 1 | M4-R2 供电 |
| 带麦克风耳机 | 3.5mm 四段式 | 1 | 可选,连接耳机/MIC 接口 |
| 外置麦克风 | 驻极体麦克风 | 1 | 可选,连接 MIC 接口 |
| HDMI 线缆 | HDMI Type-A | 1 | 可选,HDMI 音频输出测试 |
2.2 音频接口连接
M4-R2 底板提供以下音频接口(参考 S-01、S-04):
| 接口 | 规格 | 功能说明 |
|---|---|---|
| 耳机接口 | 3.5mm 耳机座 | 支持左右双声道输出,三/四段耳机插入,支持 MIC 录音 |
| MIC 接口 | 2pin/1.25mm | 外置麦克风接口,MIC+ / MIC- |
注意: MIC 正负极请勿反接;MIC 线尽量不要太长,走线尽量避开高速信号线和电源线,避免干扰声音。
2.3 硬件确认
cat /proc/asound/cards # 确认声卡已注册
ls /dev/snd/ # 确认 I2S 控制器设备节点存在
dmesg | grep -i "i2s\|sound\|codec\|audio\|asoc"3. 软件准备
3.1 内核配置项
确保以下内核配置在 Linux 6.1 内核中已启用(配置项名称以实际发布的 RK3576 SDK 内核为准):
CONFIG_SND=y
CONFIG_SND_SOC=y
CONFIG_SND_SOC_ROCKCHIP=y
CONFIG_SND_SOC_ROCKCHIP_I2S_TDM=y
CONFIG_SND_SOC_ROCKCHIP_HDMI=y
CONFIG_SND_SOC_HDMI_CODEC=y
CONFIG_SND_SOC_ROCKCHIP_PCM=y可通过 make menuconfig 路径确认:Device Drivers > Sound card support > Advanced Linux Sound Architecture > ALSA for SoC audio support
3.2 设备树配置
以下为 RK3576 连接音频 Codec 的设备树配置通用示例(节点名称、时钟与引脚以实际发布的 RK3576 SDK 为准):
/* I2S 控制器配置(示例) */
&i2s0_8ch {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2s0_lrck &i2s0_sclk &i2s0_sdi0 &i2s0_sdo0 &i2s0_mclk>;
};
/* I2C 总线配置(Codec 挂载,总线编号以实际 DTS 为准) */
&i2c3 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c3m0_xfer>;
codec: codec@10 {
compatible = "everest,es8388"; /* 具体 Codec 型号以底板原理图为准 */
reg = <0x10>;
clocks = <&cru I2S1_MCLKOUT>;
clock-names = "mclk";
#sound-dai-cells = <0>;
status = "okay";
};
};
/* 音频 Machine Driver(板级绑定,示例) */
sound {
compatible = "rockchip,rk3576-es8388";
rockchip,format = "i2s";
rockchip,bitclock-master = <&codec>;
rockchip,frame-master = <&codec>;
rockchip,card-name = "m4r2-codec";
rockchip,cpu = <&i2s0_8ch>;
rockchip,codec = <&codec>;
status = "okay";
};待板上验证: 上述设备树节点名称、I2C 总线编号和 MCLK 源时钟名称需根据实际 SDK 版本和底板原理图确认。
3.3 ALSA 工具安装
sudo apt update
sudo apt install -y alsa-utils alsa-tools pulseaudio pulseaudio-utils sox
aplay --version
arecord --version4. 操作步骤
4.1 ALSA 配置
cat /proc/asound/cards
# 预期输出示例(声卡编号以实际为准):
# 0 [rockchiphdmi ]: rockchip_hdmi - rockchip,hdmi
# 1 [m4r2codec ]: m4r2-codec - m4r2-codec
aplay -l
amixer -c 1 controls4.2 播放测试
aplay test.wav
aplay -D hw:1,0 test.wav # 指定声卡和设备
aplay -D plughw:1,0 test.wav # 自动采样率/格式转换
speaker-test -t sine -f 1000 -l 1 -p 1 -D hw:1,0 # 1kHz 正弦波
speaker-test -t wav -c 2 -D hw:1,0 # 声道测试4.3 录音测试
arecord -D hw:1,0 -f S16_LE -r 44100 -c 2 -d 10 record.wav
arecord -D hw:1,0 -f S16_LE -r 16000 -c 1 -d 10 voice.wav # 语音场景
# 回环测试
arecord -D hw:1,0 -f S16_LE -r 44100 -c 2 -t raw - | aplay -D hw:1,0 -f S16_LE -r 44100 -c 2 -t raw -
tinycap record.wav -D hw:1,0 -r 44100 -c 2 -b 16
tinypcminfo -D hw:1,0具体采样率/位深支持取决于底板 Codec 型号,需板端实测(待板上验证)。
4.4 HDMI 音频输出
RK3576 支持通过 HDMI Type-A 接口输出数字音频:
cat /proc/asound/cards | grep hdmi
aplay -D hw:0,0 test.wav
pulseaudio --start
pactl list sinks short
pactl set-default-sink <hdmi_sink_id>ALSA 配置文件示例(/etc/asound.conf):
pcm.hdmi { type hw; card 0; device 0 }
pcm.!default {
type plug
slave { pcm "hdmi"; format S16_LE; rate 48000; channels 2 }
}4.5 音量控制
amixer -c 1 set 'Master' 80%
amixer -c 1 set 'Master' unmute
amixer -c 1 set 'Headphone' 70%
amixer -c 1 set 'DAC' 80%
amixer -c 1 set 'ADC' 60%
alsactl store
alsactl restore具体控件名称取决于底板 Codec 型号,以
amixer -c N controls实际输出为准。
5. 代码详解
5.1 ALSA PCM API 编程
ALSA 用户空间 API 核心函数:
| 函数 | 说明 |
|---|---|
snd_pcm_open() | 打开 PCM 设备 |
snd_pcm_hw_params_any() | 初始化硬件参数 |
snd_pcm_hw_params_set_access() | 设置访问模式(RW/MMAP) |
snd_pcm_hw_params_set_format() | 设置采样格式(S16_LE/S32_LE等) |
snd_pcm_hw_params_set_channels() | 设置通道数 |
snd_pcm_hw_params_set_rate() | 设置采样率 |
snd_pcm_hw_params() | 应用硬件参数 |
snd_pcm_writei() | 写入音频数据(播放) |
snd_pcm_readi() | 读取音频数据(录制) |
snd_pcm_drain() | 等待播放完成 |
snd_pcm_close() | 关闭 PCM 设备 |
5.2 音频播放示例(C 语言)
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <alsa/asoundlib.h>
#define SAMPLE_RATE 48000
#define CHANNELS 2
#define DURATION_SEC 5
#define FREQUENCY 1000
int main(void)
{
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
snd_pcm_uframes_t period_size = 1024;
snd_pcm_uframes_t buffer_size = 4096;
unsigned int rate = SAMPLE_RATE;
snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(handle, params);
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S32_LE);
snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0);
snd_pcm_hw_params_set_period_size_near(handle, params, &period_size, NULL);
snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
snd_pcm_hw_params(handle, params);
int frames = SAMPLE_RATE * DURATION_SEC;
int32_t *buffer = malloc(frames * CHANNELS * sizeof(int32_t));
for (int i = 0; i < frames; i++) {
int32_t sample = (int32_t)(sin(2.0 * M_PI * FREQUENCY * i / SAMPLE_RATE) * 1000000000LL);
buffer[i * CHANNELS] = sample;
buffer[i * CHANNELS + 1] = sample;
}
int written = 0;
while (written < frames) {
int ret = snd_pcm_writei(handle, buffer + written * CHANNELS, frames - written);
if (ret < 0) snd_pcm_recover(handle, ret, 0);
else written += ret;
}
snd_pcm_drain(handle);
free(buffer);
snd_pcm_close(handle);
return 0;
}编译:aarch64-linux-gnu-gcc -o play_audio play_audio.c -lasound -lm
5.3 音频录制示例(C 语言)
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#define SAMPLE_RATE 48000
#define CHANNELS 2
#define RECORD_SEC 5
int main(void)
{
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
snd_pcm_uframes_t period_size = 1024;
unsigned int rate = SAMPLE_RATE;
snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(handle, params);
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0);
snd_pcm_hw_params_set_period_size_near(handle, params, &period_size, NULL);
snd_pcm_hw_params(handle, params);
int frames = SAMPLE_RATE * RECORD_SEC;
short *buffer = malloc(frames * CHANNELS * sizeof(short));
int read_frames = 0;
while (read_frames < frames) {
int ret = snd_pcm_readi(handle, buffer + read_frames * CHANNELS,
frames - read_frames);
if (ret < 0) snd_pcm_recover(handle, ret, 0);
else read_frames += ret;
}
FILE *fp = fopen("record.wav", "wb");
if (fp) {
int data_size = read_frames * CHANNELS * sizeof(short);
int file_size = data_size + 36;
fwrite("RIFF", 1, 4, fp); fwrite(&file_size, 4, 1, fp);
fwrite("WAVEfmt ", 1, 8, fp); int fmt_size = 16;
fwrite(&fmt_size, 4, 1, fp);
short fmt_tag = 1, nch = CHANNELS, bps = 16;
short block_align = CHANNELS * sizeof(short);
int sr = SAMPLE_RATE, br = sr * nch * sizeof(short);
fwrite(&fmt_tag, 2, 1, fp); fwrite(&nch, 2, 1, fp);
fwrite(&sr, 4, 1, fp); fwrite(&br, 4, 1, fp);
fwrite(&block_align, 2, 1, fp); fwrite(&bps, 2, 1, fp);
fwrite("data", 1, 4, fp); fwrite(&data_size, 4, 1, fp);
fwrite(buffer, sizeof(short), read_frames * CHANNELS, fp);
fclose(fp);
}
free(buffer);
snd_pcm_close(handle);
return 0;
}5.4 Python pyaudio 编程示例
#!/usr/bin/env python3
"""RK3576 ALSA 音频播放示例(pyaudio)"""
import pyaudio, struct, math
CHANNELS, RATE, DURATION, FREQUENCY, CHUNK = 2, 48000, 5, 1000, 1024
def generate_sine(freq, dur, sr, ch):
samples = []
for i in range(int(sr * dur)):
v = int(math.sin(2.0 * math.pi * freq * i / sr) * 1000000000)
samples.append(struct.pack('<ii', v, v))
return b''.join(samples)
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt32, channels=CHANNELS, rate=RATE,
output=True, frames_per_buffer=CHUNK)
audio = generate_sine(FREQUENCY, DURATION, RATE, CHANNELS)
for i in range(0, len(audio), CHUNK * CHANNELS * 4):
stream.write(audio[i:i + CHUNK * CHANNELS * 4])
stream.stop_stream(); stream.close(); p.terminate()5.5 音频 Pipeline 概念
录制路径: 麦克风 -> Codec ADC -> I2S TX -> DMA -> 内存 -> 应用程序
播放路径: 应用程序 -> 内存 -> DMA -> I2S RX -> Codec DAC -> 扬声器/耳机
HDMI路径: 应用程序 -> 内存 -> DMA -> HDMI Audio -> HDMI TX -> 外部设备5.6 Codec 驱动结构
Linux 内核中 RK3576 音频驱动文件位置(以实际 SDK 为准):
sound/soc/codecs/es8388.c
sound/soc/codecs/rk817_codec.c
sound/soc/rockchip/rockchip_i2s_tdm.c
sound/soc/rockchip/rockchip_pdm.cMachine Driver 通过 snd_soc_dai_link 绑定 CPU DAI 和 Codec:
static struct snd_soc_dai_link rk3576_codec_dai_link = {
.name = "ES8388",
.stream_name = "ES8388 HiFi",
.codec_dai_name = "es8388-hifi",
.codec_name = "es8388",
.cpu_dai_name = "rockchip,i2s",
.platform_name = "rockchip,i2s",
.ops = &rk3576_codec_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
};6. 常见问题
6.1 无声音输出
现象: aplay 播放正常但耳机无声音
解决方案:
cat /proc/asound/cards
amixer -c 1 set 'Master' unmute
amixer -c 1 set 'Master' 80%
amixer -c 1 set 'Headphone' on
dmesg | grep -i "codec\|i2s"6.2 声音失真或杂音
现象: 播放声音有杂音、破音或明显失真
解决方案:
amixer -c 1 set 'Master' 50%
aplay -D plughw:1,0 test.wav
cat /sys/kernel/debug/clk/clk_summary | grep -i "i2s\|mclk"6.3 XRUN 错误(缓冲区欠载/溢出)
现象: 出现 "underrun occurred" 或 "overrun occurred"
解决方案:
aplay -D hw:1,0 --buffer-size=16384 test.wav
# 代码中设置更大的 period size(如 2048)6.4 HDMI 音频无声
现象: HDMI 连接电视/功放后无音频输出
解决方案:
cat /proc/asound/cards | grep hdmi
cat /sys/class/drm/card0-HDMI-A1/status
aplay -D hw:0,0 test.wav
dmesg | grep -i "hdmi.*audio\|hdmi.*sound"6.5 录音无声音
现象: arecord 录音文件为空或无有效音频数据
解决方案:
arecord -l
amixer -c 1 set 'ADC' on
amixer -c 1 set 'Mic' 80%
arecord -D hw:1,0 -f S16_LE -r 44100 -c 2 -d 5 -vvv /dev/null
# 检查 MIC 接口正负极接线(M4-R2 MIC 接口 2pin/1.25mm,MIC+/MIC-)7. 相关文档
| 文档 | 说明 | 路径 |
|---|---|---|
| 视频编解码 (MPP) | 音视频多媒体开发框架 | 视频编解码(MPP) |
| 摄像头应用 | 摄像头采集与 ISP | 摄像头应用 |
| 内核配置与裁剪 | 内核配置方法及音频相关配置项 | 内核配置与裁剪 |
| 设备树详解与实践 | 设备树语法及 RK3576 音频设备树配置 | 设备树详解与实践 |
| C/C++ 应用开发入门 | 编译环境与基础开发 | C/C++应用开发入门 |
