音频开发与 ALSA
文档版本: v1.0 适用系统: Linux 6.1(RK3588 Linux SDK) 适用硬件: C-3588LQ V1.0 核心板 + SHIMETAPI-M5-R2A V1.0 底板 更新日期: 2026-08-19
1. 概述
1.1 功能简介
RK3588 集成了丰富的音频接口资源,包括 4 路 I2S 控制器(I2S 是芯片之间传输数字音频的常用总线)、2 路 SPDIF 数字音频接口(消费级数字音频接口,常用于同轴和光纤输出)和 2 路 PDM 麦克风阵列接口(PDM 是数字麦克风的常用输出格式)。通过 I2S 接口连接外部音频 Codec 芯片(Codec 负责模拟信号与数字信号的互相转换,如 ES8388、RK817 等),可实现高质量的音频播放和录制。I2S 控制器支持最高 8 通道、192kHz 采样率、32bit 位宽。此外,RK3588 支持 HDMI 音频输出,可通过 HDMI 2.1 TX 接口将数字音频信号传输至外部显示设备。Linux 系统通过 ALSA 框架(Advanced Linux Sound Architecture,Linux 的标准音频框架)提供标准的音频设备接口。
1.2 应用场景
- 高保真音频播放:最高 192kHz/32bit 音频输出
- 多声道音频输出:I2S TDM 模式支持 4~8 声道
- HDMI 音频输出:通过 HDMI 接口传输至电视、投影仪或 AV 功放
- 麦克风阵列采集:PDM 接口连接多路数字麦克风
- 语音交互:语音识别、语音助手、智能音箱等场景
1.3 工作原理
RK3588 音频子系统层次:
应用层(aplay/arecord/GStreamer/自定义应用程序)
|
alsa-lib 用户空间库
|
ALSA 内核驱动(sound/soc/rockchip/)
|-- Machine Driver(关联 CPU DAI 和 Codec)
|-- CPU DAI Driver(I2S/I2S-TDM 控制器驱动)
|-- Codec Driver(外部音频 Codec 驱动)
|-- HDMI Audio Driver(HDMI 音频输出驱动)
|-- SPDIF Driver(SPDIF 数字音频驱动)
|-- PDM Driver(PDM 麦克风阵列驱动)
+-- DMA Engine(音频数据传输)RK3588 音频接口硬件资源:
| 接口 | 设备树节点 | 基地址 | 通道数 | 说明 |
|---|---|---|---|---|
| I2S0 | i2s@fe510000 | 0xFE510000 | 最高 8ch | 主 I2S 控制器,支持 TDM 模式 |
| I2S1 | i2s@fe520000 | 0xFE520000 | 最高 8ch | 第二路 I2S 控制器 |
| SPDIF | spdif@fe460000 | 0xFE460000 | 2ch | SPDIF 数字音频输出 |
| PDM | pdm@fe440000 | 0xFE440000 | 多路 | PDM 麦克风阵列接口 |
RK3588 I2S 控制器特性:支持 8 通道 I2S/TDM 格式,主/从模式,采样率 8kHz~192kHz,位宽 16/24/32 bits,支持 MCLK 输出。
2. 硬件准备
2.1 硬件清单
| 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 核心板 | C-3588LQ V1.0 | 1 | RK3588 SoC,LGA 660pin |
| 底板 | SHIMETAPI-M5-R2A V1.0 | 1 | 含音频 Codec 及外设接口 |
| 调试串口线 | USB 转 TTL(3.3V) | 1 | 波特率 1500000 |
| 电源适配器 | 4V/5A(误差 ±5%) | 1 | 核心板供电 |
| 扬声器 | 3W/4Ω 或 8Ω 喇叭 | 1 | 可选,连接 Codec SPK 输出 |
| 带麦克风耳机 | 3.5mm 四段式 | 1 | 可选,连接 Codec HP/MIC 接口 |
| HDMI 线缆 | HDMI 2.0/2.1 | 1 | 可选,HDMI 音频输出测试 |
2.2 音频 Codec 连接
SHIMETAPI-M5-R2A 底板板载音频 Codec(ES8388 或 RK817),通过 I2C 总线控制,通过 I2S 总线传输音频数据。典型连接:
| 核心板信号 | Codec 引脚 | 功能说明 |
|---|---|---|
| I2S0_SDO0 | DAC DIN | 音频数据输出至 Codec |
| I2S0_SDI0 | ADC DOUT | Codec 音频数据输入 |
| I2S0_LRCK | LRCK | 左右声道帧同步时钟 |
| I2S0_SCLK | BCLK | 位时钟 |
| I2S0_MCLK | MCLK | 主时钟(256fs 或 512fs) |
| I2C_x_SCL/SDA | SCL/SDA | Codec I2C 控制 |
注意:具体 I2C 总线编号和 I2S 引脚复用关系请以底板原理图为准。
2.3 硬件确认
上电进入系统后,用下面的四条命令确认声卡已注册、Codec 可达、设备节点存在:
cat /proc/asound/cards # 确认声卡已注册
i2cdetect -y 3 # 确认 I2C 总线上 Codec 芯片可达
ls /dev/snd/ # 确认 I2S 控制器设备节点存在
dmesg | grep -i "i2s\|sound\|codec\|audio\|asoc"3. 软件准备
3.1 内核配置项
确保以下内核配置在 Linux 6.1 内核中已启用:
CONFIG_SND=y
CONFIG_SND_SOC=y
CONFIG_SND_SOC_ROCKCHIP=y
CONFIG_SND_SOC_ROCKCHIP_I2S_TDM=y
CONFIG_SND_SOC_ROCKCHIP_SPDIF=y
CONFIG_SND_SOC_ROCKCHIP_PDM=y
CONFIG_SND_SOC_ROCKCHIP_HDMI=y
CONFIG_SND_SOC_HDMI_CODEC=y
CONFIG_SND_SOC_ES8388=y
CONFIG_SND_SOC_RK817=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 设备树配置
以下为 RK3588 连接 ES8388 Codec 的设备树配置示例:
/* I2S0 控制器配置 */
&i2s0_8ch {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2s0_lrck &i2s0_sclk &i2s0_sdi0 &i2s0_sdo0 &i2s0_mclk>;
};
/* SPDIF 接口配置 */
&spdif {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spdif_tx>;
};
/* PDM 麦克风阵列接口配置 */
&pdm {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pdm_clkm &pdm_sdi0 &pdm_sdi1>;
};
/* I2C 总线配置(Codec 挂载) */
&i2c3 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c3m0_xfer>;
es8388: es8388@10 {
compatible = "everest,es8388";
reg = <0x10>;
clocks = <&cru I2S1_MCLKOUT>;
clock-names = "mclk";
#sound-dai-cells = <0>;
status = "okay";
};
};
/* 音频 Machine Driver(板级绑定) */
sound {
compatible = "rockchip,rk3588-es8388";
rockchip,format = "i2s";
rockchip,bitclock-master = <&es8388>;
rockchip,frame-master = <&es8388>;
rockchip,card-name = "shimetapi-es8388";
rockchip,cpu = <&i2s0_8ch>;
rockchip,codec = <&es8388>;
status = "okay";
};待板上验证:上述设备树节点名称和 MCLK 源时钟名称需根据实际 SDK 版本和底板原理图确认。
3.3 ALSA 工具安装
装好 alsa-utils 等工具,并确认 aplay/arecord 可用:
sudo apt update
sudo apt install -y alsa-utils alsa-tools pulseaudio pulseaudio-utils sox
aplay --version
arecord --version4. 操作步骤
4.1 ALSA 配置
ALSA 给每块声卡编号(card0、card1 等),先弄清本机的卡号和各自的能力,后面所有命令都依赖它:
cat /proc/asound/cards
# 输出(注意:ES8388 是 card0,HDMI 是 card1,与老版本镜像相反!):
# 0 [rockchipes8388 ]: rockchip-es8388 - rockchip-es8388
# rockchip-es8388
# 1 [rockchiphdmi1 ]: rockchip-hdmi1 - rockchip-hdmi1
# rockchip-hdmi1
aplay -l
# card0 ES8388 只有 1 个双子设备(capture+playback 同设备 0)
# card1 HDMI 只有 playback
amixer -c 0 controls # ES8388 的控制项(37 号起:Headphone Jack、
# Headset Mic Jack、PCM Volume、Mono Mux 等)4.2 播放测试
# 卡号按0=ES8388(耳机口),1=HDMI
aplay test.wav
aplay -D hw:0,0 test.wav # ES8388 耳机口
aplay -D plughw:1,0 test.wav # HDMI(自动采样率/格式转换)
speaker-test -t sine -f 1000 -l 1 -p 1 -D plughw:1,0 # 1kHz 正弦波→HDMI用生成的 440Hz/48kHz 单声道 WAV ,耳机口和 HDMI 两个输出都正常:
$ aplay -D plughw:1,0 /var/tmp/test440.wav
Playing WAVE '/var/tmp/test440.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono
RC=0 ← HDMI 输出成功
$ aplay -D plughw:0,0 /var/tmp/test440.wav
Playing WAVE '/var/tmp/test440.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono
RC=0 ← ES8388 耳机口输出成功
$ speaker-test -t sine -f 1000 -l 1 -p 1 -D plughw:1,0
was set period_size = 16
was set buffer_size = 64
0 - Front Left
Time per period = 2.9986394.3 录音测试
# 录音只能用 card0(ES8388)——HDMI card1 无 capture 设备
arecord -D hw:0,0 -f CD -d 3 record.wav # 16bit/44.1k/立体声 3 秒
arecord -D hw:0,0 -f S16_LE -r 16000 -c 1 -d 10 voice.wav # 语音场景在没接 mic/line-in 输入源的情况下
$ arecord -D hw:0,0 -f CD -d 3 /var/tmp/rec3s.wav
Recording WAVE '/var/tmp/rec3s.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
$ ls -la rec3s.wav
-rw-r--r-- 1 linaro linaro 529244 ... rec3s.wav ← 3s 立体声数据流正常
# 电平分析(peak=72/32767 = 0.22% 底噪)
# → 通道工作正常,但 Headset Mic 座没插输入源时录到的是静音勘误:
tinycap / tinypcminfo本机未安装(那是 Android/tinyalsa 的工具);Linux 用 arecord。回环测试命令同理把卡号改为 0。
4.4 I2S TDM 多声道配置
RK3588 的 I2S 控制器支持 TDM 模式(TDM 即时分复用,把多个声道排进同一条数据线传输),可实现多声道音频传输:
speaker-test -D hw:1,0 -c 8 -t wav
arecord -D hw:0,0 -f S16_LE -r 48000 -c 8 -d 5 -t raw > 8ch_record.raw # 录音卡=0(ES8388)设备树中启用 8 声道 TDM 模式(需额外数据线):
&i2s0_8ch {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2s0_lrck &i2s0_sclk
&i2s0_sdi0 &i2s0_sdi1
&i2s0_sdo0 &i2s0_sdo1 &i2s0_sdo2 &i2s0_sdo3
&i2s0_mclk>;
};4.5 HDMI 音频输出
RK3588 支持通过 HDMI 2.1 TX 接口输出数字音频:
cat /proc/asound/cards | grep hdmi
# 1 [rockchiphdmi1]: rockchip-hdmi1 - rockchip-hdmi1
aplay -D plughw:1,0 test.wav # 可用(卡号 1)出厂无 pulseaudio/pactl(
which无输出)。本机默认音频服务是 PipeWire(/usr/bin/pipewire,XFCE 桌面用),纯 ALSA 直接指定卡号即可。如需 PulseAudio 接口:sudo apt install pulseaudio后按原命令操作。
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.6 音量控制
音量控制先用 scontrols 列出这块声卡实际的控制名,再按名字设置,不能照搬其他声卡的 Master/DAC 之类:
# ES8388(card0) 的简单控制:Headphone/Speaker(开关)、PCM(音量)、
# Capture Digital、Capture Mute 等 —— 没有 Master/DAC/ADC 这些名字!
amixer -c 0 scontrols # 先列出实际控制名(见上)
amixer -c 0 set 'PCM' 80% # 音量(set 'PCM' 192 = 100% 成功)
amixer -c 0 set 'Headphone' on # 开关( pswitch 类型,set 70% 报 Invalid command!)
amixer -c 0 set 'Capture Mute' off
alsactl store # 保存到 /var/lib/alsa/asound.state
alsactl restore$ amixer -c 0 get Headphone
Capabilities: pswitch pswitch-joined ← 是开关不是音量
Mono: Playback [on]
$ amixer -c 0 set 'PCM' 192
Front Left: 192 [100%] [0.00dB]
Front Right: 192 [100%] [0.00dB]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
"""RK3588 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 -> 外部设备
PDM路径: 数字麦克风阵列 -> PDM -> DMA -> 内存 -> 语音处理5.6 Codec 驱动结构
Linux 内核中 RK3588 音频驱动文件位置:
sound/soc/codecs/es8388.c
sound/soc/codecs/rk817_codec.c
sound/soc/rockchip/rockchip_i2s_tdm.c
sound/soc/rockchip/rockchip_spdif.c
sound/soc/rockchip/rockchip_pdm.cMachine Driver 通过 snd_soc_dai_link 绑定 CPU DAI 和 Codec:
static struct snd_soc_dai_link rk3588_es8388_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 = &rk3588_es8388_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%
i2cdetect -y 3 # 根据实际 I2C 总线编号修改
amixer -c 1 set 'DAC Playback' on
amixer -c 1 set 'SPK OUT' on
dmesg | grep -i "es8388\|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%
cat /sys/kernel/debug/asoc/codec/dapm 2>/dev/null
arecord -D hw:0,0 -f S16_LE -r 44100 -c 2 -d 5 -vvv /dev/null # 录音卡=06.6 PDM 麦克风阵列不工作
现象:PDM 接口连接数字麦克风后无法采集音频
解决方法:
lsmod | grep pdm
dmesg | grep -i pdm
cat /proc/device-tree/pdm/status
cat /sys/kernel/debug/clk/clk_summary | grep pdm6.7 多声道播放异常
现象:8 声道 TDM 模式播放时部分声道无声或声道映射错误
解决方法:
cat /proc/asound/card1/pcm0p/info
speaker-test -D plughw:1,0 -c 8 -t wav
# 检查设备树中 i2s0_sdi0~sdi1 和 i2s0_sdo0~sdo3 均已配置 pinctrl7. 相关文档
| 文档 | 说明 | 路径 |
|---|---|---|
| 音频驱动开发 | RK3588 音频驱动架构、DAPM 配置、Codec 驱动详解 | 音频驱动开发 |
| I2C 驱动开发 | I2C 总线驱动及 Codec I2C 通信 | 03_peripheral-drivers/ch02-i2c-driver.md |
| GStreamer 多媒体开发 | GStreamer 音视频播放框架 | 04_application-development/ch04-gstreamer-multimedia-development.md |
| HDMI 显示驱动 | HDMI 2.1 TX 驱动及 HDMI 音频输出 | 03_peripheral-drivers/ch15-hdmi-display-driver.md |
| 内核配置与裁剪 | 内核配置方法及音频相关配置项 | 内核配置与裁剪 |
| 设备树详解与实践 | 设备树语法及 RK3588 音频设备树配置 | 设备树详解与实践 |
