RK3568 ALSA音频架构深度解析
ALSAAdvanced Linux Sound Architecture是Linux标准的音频框架。RK3568有多个音频接口配置比较复杂ALSA架构、RK音频驱动、asound.conf配置、应用开发、调试技巧。第一部分ALSA架构树形分析ALSA音频架构树形图 │ ├── 1. 用户空间 │ │ │ ├── 应用程序层 │ │ ├── 直接ALSA API: aplay, arecord, speaker-test │ │ ├── 高级音频服务: PulseAudio, PipeWire │ │ ├── 多媒体框架: GStreamer, FFmpeg │ │ └── 应用层: Qt Multimedia, SDL │ │ │ ├── libasound (alsa-lib) │ │ ├── 插件系统: rate, plug, dmix, dsnoop │ │ ├── PCM接口: snd_pcm_open, snd_pcm_readi, snd_pcm_writei │ │ ├── 控制接口: snd_ctl_open, snd_ctl_elem_info │ │ ├── 混音器: snd_mixer_open, snd_mixer_selem_* │ │ └── 配置文件: /etc/asound.conf, ~/.asoundrc │ │ │ └── ALSA工具 │ ├── aplay: 播放WAV文件 │ ├── arecord: 录制音频 │ ├── amixer: 控制音量、通路 │ ├── alsamixer: 交互式混音器 │ ├── speaker-test: 扬声器测试 │ └── alsactl: 保存/恢复音量设置 │ ├── 2. 内核空间 │ │ │ ├── ALSA核心层 │ │ ├── PCM核心: pcm.c, pcm_native.c │ │ ├── 控制核心: control.c │ │ ├── 混音器核心: mixer.c │ │ ├── 计时器: timer.c │ │ └── 序列器: seq.c │ │ │ ├── ASoC层 (ALSA System on Chip) │ │ ├── 平台驱动: rockchip_i2s.c │ │ ├── 编解码驱动: es8316.c, rt5651.c │ │ ├── 机器驱动: rk3568_es8316.c │ │ ├── DMA引擎: rockchip_pcm.c │ │ └── DAPM (动态音频电源管理) │ │ │ └── 硬件抽象层 │ ├── I2S控制器: I2S0, I2S1, I2S2 │ ├── PDM接口: 数字麦克风 │ ├── SPDIF: 光纤音频 │ └── HDMI音频: HDMI ARC │ └── 3. 硬件层 │ ├── 音频Codec │ ├── ES8316: 立体声Codec │ ├── RT5651: 多通道Codec │ └── 内置Codec: RK3568内置音频 │ ├── 音频接口 │ ├── 3.5mm耳机接口 │ ├── 麦克风输入 │ ├── 扬声器输出 │ └── 蓝牙音频 │ └── 数字音频接口 ├── I2S总线 ├── PDM └── SPDIF第二部分RK3568音频驱动树形分析RK3568音频驱动树形图 │ ├── 1. I2S控制器驱动 (platform driver) │ │ │ ├── 设备树节点 │ │ └── i2s0_8ch: i2sfe400000 │ │ │ ├── 驱动文件 │ │ ├── sound/soc/rockchip/rockchip_i2s.c │ │ ├── sound/soc/rockchip/rockchip_i2s_tdm.c │ │ └── sound/soc/rockchip/rockchip_pcm.c │ │ │ └── 关键配置 │ ├── 采样率: 8kHz-192kHz │ ├── 格式: S16_LE, S24_LE, S32_LE │ ├── 通道数: 2/8通道 │ └── 时钟: 主模式/从模式 │ ├── 2. Codec驱动 (codec driver) │ │ │ ├── ES8316驱动 │ │ ├── sound/soc/codecs/es8316.c │ │ ├── 支持采样率: 8k, 16k, 32k, 44.1k, 48k, 96k │ │ ├── 音量控制: DAC/ADC增益 │ │ ├── 通路控制: 耳机/喇叭/麦克风 │ │ └── 功耗管理: 动态关闭未用模块 │ │ │ └── 设备树配置 │ └── es8316: codec11 │ ├── 3. 机器驱动 (machine driver) │ │ │ ├── 驱动文件 │ │ └── sound/soc/rockchip/rk3568_es8316.c │ │ │ ├── 作用 │ │ ├── 连接platform和codec │ │ ├── 定义音频通路 (DAPM) │ │ ├── 初始化时钟 │ │ └── 处理耳机插拔检测 │ │ │ └── 设备树配置 │ └── sound: sound0 │ └── 4. DMA引擎驱动 │ ├── 驱动文件: rockchip_pcm.c ├── 支持环形DMA缓冲 ├── 支持多通道传输 └── 硬件指针更新设备树配置完整示例/* * RK3568音频设备树配置 */ /* 1. I2S控制器配置 */ i2s0_8ch { status okay; rockchip,capture-channels 2; rockchip,playback-channels 2; pinctrl-names default; pinctrl-0 i2s0_lrck_tx i2s0_bclk_tx i2s0_sdi0 i2s0_sdo0; }; /* 2. I2C配置 (Codec控制接口) */ i2c1 { status okay; pinctrl-names default; pinctrl-0 i2c1m0_xfer; es8316: codec11 { compatible everest,es8316; reg 0x11; clocks cru I2S0_MCLKOUT; clock-names mclk; #sound-dai-cells 0; /* 电源配置 */ AVDD-supply vcc_3v3; DVDD-supply vcc_1v8; /* 耳机检测 */ hp-det-gpio gpio1 RK_PC0 GPIO_ACTIVE_LOW; /* 麦克风偏置 */ mic-bias-voltage 3; }; }; /* 3. 声卡配置 (machine driver) */ sound { compatible simple-audio-card; simple-audio-card,name rk3568-es8316; simple-audio-card,format i2s; simple-audio-card,bitclock-master master; simple-audio-card,frame-master master; simple-audio-card,mclk-fs 256; master: simple-audio-card,cpu { sound-dai i2s0_8ch; system-clock-frequency 12288000; }; codec: simple-audio-card,codec { sound-dai es8316; }; }; /* 4. 时钟配置 */ cru { assigned-clocks cru I2S0_MCLKOUT; assigned-clock-rates 12288000; };第三部分asound.conf配置树形分析asound.conf配置树形图 │ ├── 1. 配置文件位置 │ ├── /etc/asound.conf (系统级) │ ├── ~/.asoundrc (用户级) │ └── /usr/share/alsa/alsa.conf (全局默认) │ ├── 2. 核心配置类型 │ │ │ ├── pcm类型: PCM设备定义 │ │ ├── hw: 硬件设备直通 │ │ ├── plughw: 自动格式转换 │ │ ├── dmix: 多流播放混合 │ │ ├── dsnoop: 多流录制共享 │ │ ├── dshare: 多流共享 │ │ ├── asym: 不对称 (不同输入输出) │ │ └── file: 文件IO │ │ │ ├── ctl类型: 控制设备定义 │ │ ├── hw: 硬件控制 │ │ └── card: 卡片控制 │ │ │ └── hooks: 钩子脚本 │ └── 3. 典型配置示例3.1 基础硬件配置# /etc/asound.conf - 基础配置 # 定义默认声卡 defaults.pcm.card 0 defaults.ctl.card 0 # 定义硬件PCM pcm.hw { type hw card 0 device 0 } # 定义控制设备 ctl.hw { type hw card 0 }3.2 格式转换配置 (plughw)# plughw: 自动进行采样率、格式转换 pcm.plughw { type plug slave { pcm hw:0,0 rate 48000 # 强制48kHz采样率 format S16_LE # 强制16bit格式 channels 2 # 强制双声道 } }3.3 多流混音配置 (dmix)# dmix: 允许多个应用同时播放 pcm.dmix { type dmix ipc_key 1024 # 共享内存标识 ipc_perm 0666 # 访问权限 slave { pcm hw:0,0 period_time 0 period_size 1024 buffer_size 8192 rate 48000 format S16_LE channels 2 } } # 设置默认PCM为dmix pcm.!default { type plug slave.pcm dmix }3.4 多流录音配置 (dsnoop)# dsnoop: 允许多个应用同时录音 pcm.dsnoop { type dsnoop ipc_key 2048 ipc_perm 0666 slave { pcm hw:0,0 period_time 0 period_size 1024 buffer_size 8192 rate 16000 format S16_LE channels 1 } }3.5 完整声卡配置 (带路由)# 定义完整的声卡配置 pcm.snd_card { type hw card 0 } # 播放设备: 使用dmix pcm.playback { type dmix ipc_key 1024 slave { pcm hw:0,0 rate 48000 format S16_LE channels 2 } } # 录音设备: 使用dsnoop pcm.capture { type dsnoop ipc_key 2048 slave { pcm hw:0,0 rate 16000 format S16_LE channels 1 } } # 不对称设备: 播放用dmix录音用dsnoop pcm.duplex { type asym playback.pcm playback capture.pcm capture } # 默认使用双工设备 pcm.!default { type plug slave.pcm duplex }3.6 蓝牙音频配置# 蓝牙音频设备配置 pcm.btheadset { type bluetooth device 00:11:22:33:44:55 profile a2dp } pcm.btsco { type bluetooth device 00:11:22:33:44:55 profile sco }3.7 多声卡切换配置# 定义多个声卡 pcm.card0 { type hw card 0 } pcm.card1 { type hw card 1 } # 根据应用选择声卡 pcm.!default { type plug slave.pcm { type softvol slave.pcm card0 control { name Master card 0 } } }第四部分ALSA应用开发技法ALSA开发技法树形图 │ ├── 1. 基础播放流程 │ ├── snd_pcm_open() → 打开设备 │ ├── snd_pcm_hw_params() → 设置硬件参数 │ ├── snd_pcm_sw_params() → 设置软件参数 │ ├── snd_pcm_prepare() → 准备设备 │ ├── snd_pcm_writei() → 写入数据 │ └── snd_pcm_close() → 关闭设备 │ ├── 2. 基础录音流程 │ ├── snd_pcm_open() → 打开设备 │ ├── snd_pcm_hw_params() → 设置硬件参数 │ ├── snd_pcm_prepare() → 准备设备 │ ├── snd_pcm_readi() → 读取数据 │ └── snd_pcm_close() → 关闭设备 │ ├── 3. 高级技法 │ ├── 非阻塞模式: SND_PCM_NONBLOCK │ ├── 异步通知: snd_pcm_async() │ ├── 多通道处理: 交错/非交错模式 │ ├── 硬件指针查询: snd_pcm_htimestamp() │ ├── 延迟测量: snd_pcm_delay() │ └── 欠载/溢出恢复: snd_pcm_recover() │ └── 4. 混音器控制 ├── snd_mixer_open() → 打开混音器 ├── snd_mixer_attach() → 附加声卡 ├── snd_mixer_selem_*() → 控制音量/开关 └── snd_mixer_close() → 关闭混音器基础播放示例代码#include stdio.h #include alsa/asoundlib.h int play_audio(const char *device, const char *filename) { snd_pcm_t *handle; snd_pcm_hw_params_t *params; snd_pcm_uframes_t frames; FILE *fp; char *buffer; int size; int rc; // 1. 打开PCM设备 rc snd_pcm_open(handle, device, SND_PCM_STREAM_PLAYBACK, 0); if (rc 0) { fprintf(stderr, 无法打开设备 %s: %s\n, device, snd_strerror(rc)); return -1; } // 2. 分配硬件参数对象 snd_pcm_hw_params_alloca(params); // 3. 初始化硬件参数 snd_pcm_hw_params_any(handle, params); // 4. 设置访问类型 (交错模式) snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); // 5. 设置采样格式 (16bit) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); // 6. 设置声道数 snd_pcm_hw_params_set_channels(handle, params, 2); // 7. 设置采样率 unsigned int rate 44100; snd_pcm_hw_params_set_rate_near(handle, params, rate, 0); // 8. 设置周期大小 frames 1024; snd_pcm_hw_params_set_period_size_near(handle, params, frames, 0); // 9. 应用硬件参数 rc snd_pcm_hw_params(handle, params); if (rc 0) { fprintf(stderr, 无法设置硬件参数: %s\n, snd_strerror(rc)); snd_pcm_close(handle); return -1; } // 10. 获取实际周期大小 snd_pcm_hw_params_get_period_size(params, frames, 0); size frames * 4; // 2通道 × 2字节 4字节/帧 // 11. 分配缓冲区 buffer malloc(size); if (!buffer) { fprintf(stderr, 内存分配失败\n); snd_pcm_close(handle); return -1; } // 12. 打开音频文件 fp fopen(filename, rb); if (!fp) { fprintf(stderr, 无法打开文件 %s\n, filename); free(buffer); snd_pcm_close(handle); return -1; } // 13. 准备设备 snd_pcm_prepare(handle); // 14. 播放循环 while (!feof(fp)) { int len fread(buffer, 1, size, fp); if (len 0) break; // 写入数据 rc snd_pcm_writei(handle, buffer, frames); if (rc -EPIPE) { // 欠载恢复 fprintf(stderr, 欠载恢复中...\n); snd_pcm_prepare(handle); } else if (rc 0) { fprintf(stderr, 写入错误: %s\n, snd_strerror(rc)); break; } } // 15. 等待播放完成 snd_pcm_drain(handle); // 16. 清理 fclose(fp); free(buffer); snd_pcm_close(handle); return 0; }高级技法非阻塞模式与异步通知#include stdio.h #include fcntl.h #include poll.h #include alsa/asoundlib.h /* 非阻塞模式播放 */ int play_nonblock(const char *device) { snd_pcm_t *handle; int rc; // 使用非阻塞标志打开设备 rc snd_pcm_open(handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (rc 0) { fprintf(stderr, 打开失败: %s\n, snd_strerror(rc)); return -1; } // 配置硬件参数... // 使用poll等待设备就绪 struct pollfd fds[1]; fds[0].fd snd_pcm_poll_descriptor(handle); fds[0].events POLLOUT; while (1) { rc poll(fds, 1, 1000); if (rc 0) break; if (fds[0].revents POLLOUT) { // 设备就绪可以写入 rc snd_pcm_writei(handle, buffer, frames); if (rc -EAGAIN) { // 设备忙继续等待 continue; } } } snd_pcm_close(handle); return 0; } /* 延迟测量 */ int measure_latency(snd_pcm_t *handle) { snd_pcm_sframes_t delay; snd_timestamp_t tstamp; // 获取当前延迟和硬件时间戳 snd_pcm_delay(handle, delay); snd_pcm_htimestamp(handle, delay, tstamp); printf(延迟: %ld 帧, 时间戳: %ld.%06ld\n, delay, tstamp.tv_sec, tstamp.tv_usec); // 转换为毫秒 float latency_ms (float)delay * 1000 / 44100; printf(延迟: %.2f ms\n, latency_ms); return 0; } /* 欠载恢复 */ int recover_from_xrun(snd_pcm_t *handle, int err) { if (err -EPIPE) { // 欠载 (播放) 或 溢出 (录音) fprintf(stderr, XRUN detected, recovering...\n); err snd_pcm_prepare(handle); if (err 0) { fprintf(stderr, 恢复失败: %s\n, snd_strerror(err)); } return 0; } return err; }混音器控制示例#include alsa/asoundlib.h /* 控制音量 */ int set_volume(int volume_percent) { snd_mixer_t *handle; snd_mixer_elem_t *elem; long min, max, vol; int rc; // 1. 打开混音器 rc snd_mixer_open(handle, 0); if (rc 0) return -1; // 2. 附加声卡 rc snd_mixer_attach(handle, default); if (rc 0) { snd_mixer_close(handle); return -1; } // 3. 注册设备 rc snd_mixer_selem_register(handle, NULL, NULL); if (rc 0) { snd_mixer_close(handle); return -1; } // 4. 加载混音器元素 rc snd_mixer_load(handle); if (rc 0) { snd_mixer_close(handle); return -1; } // 5. 查找主音量控制 elem snd_mixer_first_elem(handle); while (elem) { if (snd_mixer_selem_has_playback_volume(elem)) { // 找到音量控制 snd_mixer_selem_get_playback_volume_range(elem, min, max); vol min (max - min) * volume_percent / 100; snd_mixer_selem_set_playback_volume_all(elem, vol); break; } elem snd_mixer_elem_next(elem); } // 6. 关闭混音器 snd_mixer_close(handle); return 0; } /* 切换耳机/扬声器输出 */ int switch_output(const char *output) { snd_mixer_t *handle; snd_mixer_elem_t *elem; int rc; snd_mixer_open(handle, 0); snd_mixer_attach(handle, default); snd_mixer_selem_register(handle, NULL, NULL); snd_mixer_load(handle); elem snd_mixer_first_elem(handle); while (elem) { const char *name snd_mixer_selem_get_name(elem); if (strcmp(name, Headphone) 0) { if (strcmp(output, headphone) 0) { snd_mixer_selem_set_playback_switch_all(elem, 1); } else { snd_mixer_selem_set_playback_switch_all(elem, 0); } } if (strcmp(name, Speaker) 0) { if (strcmp(output, speaker) 0) { snd_mixer_selem_set_playback_switch_all(elem, 1); } else { snd_mixer_selem_set_playback_switch_all(elem, 0); } } elem snd_mixer_elem_next(elem); } snd_mixer_close(handle); return 0; }第五部分调试技巧树形分析ALSA调试技法树形图 │ ├── 1. 设备信息查询 │ ├── cat /proc/asound/cards → 查看声卡列表 │ ├── cat /proc/asound/card0/pcm0p/info → PCM播放信息 │ ├── cat /proc/asound/card0/pcm0c/info → PCM录音信息 │ └── cat /proc/asound/card0/codec#0 → Codec寄存器状态 │ ├── 2. 命令行测试 │ ├── aplay -l → 列出播放设备 │ ├── arecord -l → 列出录音设备 │ ├── speaker-test -t wav -c 2 → 测试扬声器 │ ├── speaker-test -t sine -f 1000 → 正弦波测试 │ └── arecord -f S16_LE -r 16000 -d 5 test.wav → 录音测试 │ ├── 3. 调试日志 │ ├── amixer contents → 查看所有混音器控件 │ ├── amixer cget nameMaster → 获取特定控件 │ ├── amixer cset nameMaster 80% → 设置控件 │ └── alsamixer → 交互式调试 │ ├── 4. 内核日志 │ ├── dmesg | grep snd → 音频驱动日志 │ ├── dmesg | grep asoc → ASoC框架日志 │ └── echo 1 /sys/module/snd_soc_rockchip_i2s/parameters/debug → 开启调试 │ └── 5. 性能分析 ├── perf record -e cycles -- aplay test.wav ├── perf report → 分析CPU占用 └── latencytop → 分析延迟常用调试命令汇总# 1. 查看声卡信息 cat /proc/asound/cards aplay -l arecord -l # 2. 测试播放 speaker-test -t wav -c 2 -D hw:0,0 aplay -D hw:0,0 test.wav # 3. 测试录音 arecord -D hw:0,0 -f S16_LE -r 16000 -d 5 test.wav aplay test.wav # 播放验证 # 4. 音量控制 amixer scontrols # 列出所有控件 amixer sset Master 80% # 设置主音量 amixer sset Headphone 90% # 设置耳机音量 amixer sset Capture 70% # 设置录音增益 # 5. 通路控制 amixer sset Headphone on # 打开耳机输出 amixer sset Speaker off # 关闭扬声器 amixer sset MIC on # 打开麦克风 # 6. 调试信息 echo 1 /sys/module/snd_soc_rockchip_i2s/parameters/debug dmesg | grep -i audio dmesg | grep -i asoc # 7. 性能测试 time aplay -D hw:0,0 test.wav # 测量播放时间 time arecord -D hw:0,0 -d 10 test.wav # 测量录音时间常见问题排查# 问题1: 没有声音 amixer sset Master 100% on amixer sset Headphone 100% on amixer sset Speaker 100% on speaker-test -t wav -c 2 # 问题2: 录音没声音 amixer sset Capture 100% on amixer sset MIC on arecord -D hw:0,0 -f S16_LE -r 16000 -d 5 test.wav # 问题3: 声音卡顿 # 检查周期大小 cat /proc/asound/card0/pcm0p/sub0/hw_params # 增加缓冲区 aplay -D plughw:0,0 --buffer-time50000 test.wav # 问题4: 声道错误 # 检查声道映射 amixer cget nameChannel Map amixer cset nameChannel Map 0,1 # 左右声道