Linux 音频的基石: ALSA
🎵 什么是 ALSA?
如果你是 Linux 用户,无论是听音乐、看电影还是进行语音聊天,你的系统都在幕后默默地使用着一个强大的音频框架——ALSA (Advanced Linux Sound Architecture)
。ALSA 是 Linux 内核中的一个核心组件,它提供了声卡驱动程序和一套 API (应用程序编程接口),让应用程序能够与声卡硬件进行高效、灵活的交互。
简单来说,ALSA 就是 Linux 系统中管理和处理所有声音输入和输出的标准方式。它取代了早期的 OSS (Open Sound System)
,带来了更多高级特性,如多声道支持、硬件混音、MIDI 功能以及更低的延迟。
ALSA 架构概览 🏗️
ALSA 并不是一个单一的程序,而是一个分层、模块化的架构,主要由以下几部分组成:
- 内核模块:硬件的直接对话者 : 这是 ALSA 的最底层,直接与你的声卡硬件打交道。
- 声卡驱动: 每个声卡(无论是板载的、PCIe 接口的还是 USB 声卡)都有专门的 ALSA 驱动模块(例如
snd_hda_intel、snd_usb_audio
)。它们负责初始化声卡,配置硬件寄存器,并高效地传输音频数据。 - ALSA 核心: 提供一个通用的接口层,让不同的声卡驱动可以“插入”进来,并向上层提供统一的服务。
- 用户空间库 (alsa-lib):应用程序的桥梁 : 这部分是应用程序开发者最常接触的。alsa-lib 库(通常是 libasound.so 文件)封装了复杂的内核 API,提供了一个更高级、更友好的编程接口。应用程序调用这些库函数来播放、录制音频或控制声卡设置。
- PCM 设备: 用于处理数字音频流,也就是我们常说的 PCM 数据。例如,播放 MP3 或 WAV 文件、录制麦克风输入,都涉及到 PCM 设备。
- 控制设备: 管理声卡的各种设置,比如调节音量、选择输入源、静音等。
- 其他设备: 还包括 MIDI 设备(用于音乐合成和控制)和定时器设备(用于音频同步)。
- ALSA 插件层:灵活的音频处理能力 🔌 :ALSA 强大的灵活性体现在其插件系统上。这些插件在用户空间运行,可以对音频流进行实时处理和转换,弥补硬件功能的不足或提供额外特性。
- plug (插件): 最常用的插件之一。当应用程序请求的音频格式(如采样率、声道数、位深)与硬件不完全匹配时,plug 插件会自动进行转换。例如,如果你的声卡只支持 48kHz,而你播放 44.1kHz 的音频,plug 就会自动进行重采样。
- dmix (设备混音): 允许多个应用程序同时播放音频到同一个硬件设备,而无需声卡本身提供硬件混音功能。
- hw (硬件): 这不是一个插件,而是一个特殊的设备名,表示直接访问声卡硬件,不经过任何软件转换或混音。通常用于追求最低延迟或最高保真度的场景,但要求应用程序提供的音频格式必须与硬件原生支持的完全一致。
- 实用工具 (alsa-utils):命令行好帮手 🛠️:ALSA 提供了一系列命令行工具,方便用户直接管理和测试音频系统。
aplay / arecord
:播放和录制音频文件(例如 .wav)。amixer / alsamixer
:用于调节混音器设置、音量等。alsamixer 提供了一个用户友好的 TUI (文本用户界面)。alsactl
:用于保存和恢复声卡配置。
如何使用 ALSA:实践入门 🚀
1. 查找你的声卡设备
在 Linux 中,声卡设备通常以 hw:C,D
的形式表示,其中 C
是声卡编号,D
是该声卡上的设备编号。
运行以下命令可以列出你的所有播放设备:
aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC294 Analog [ALC294 Analog]Subdevices: 1/1Subdevice #0: subdevice #0
card 1: Device [USB Audio Device], device 0: USB Audio [USB Audio]Subdevices: 1/1Subdevice #0: subdevice #0
这里,card 0
是板载声卡,card 1
是一个 USB 声卡。它们的设备名分别是 hw:0,0
和 hw:1,0
。
2. 播放音频文件
使用 aplay 可以快速播放 WAV 文件:
aplay -D hw:1,0 your_audio.wav
-D hw:1,0:
指定使用 hw:1,0
这个设备进行播放。如果你不指定,aplay
会使用默认设备。
your_audio.wav
:要播放的 WAV 文件。
如果你遇到“采样率不支持”之类的警告或错误,可以尝试使用 plug 插件:
aplay -D plughw:1,0 your_audio.wav
plughw
会自动帮你处理音频格式转换。
3. 调节音量和混音器
使用 alsamixer
可以方便地通过命令行调节音量和混音器设置:
alsamixer
进入界面后,使用方向键 ← →
选择控制项,↑ ↓
调节音量。按M
可以静音/取消静音。按Esc
退出。
ALSA 编程入门 (C 语言示例) 🧑💻
在 C 语言程序中直接与 ALSA 交互,你需要使用 libasound 库。以下是一个极简的例子,演示如何打开一个 ALSA 设备并播放原始 PCM 数据:
#include <alsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>int main() {snd_pcm_t *handle; // PCM 设备句柄snd_pcm_hw_params_t *params; // 硬件参数结构体int err;// 假设你的USB声卡是 hw:1,0const char *device_name = "plughw:1,0"; // 使用 plughw 确保兼容性unsigned int sample_rate = 44100;int channels = 2;snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; // 16位有符号小端// 1. 打开 PCM 设备if ((err = snd_pcm_open(&handle, device_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {fprintf(stderr, "无法打开音频设备 %s: %s\n", device_name, snd_strerror(err));return 1;}// 2. 分配硬件参数结构体snd_pcm_hw_params_alloca(¶ms);// 3. 填充默认硬件参数if ((err = snd_pcm_hw_params_any(handle, params)) < 0) {fprintf(stderr, "无法初始化硬件参数: %s\n", snd_strerror(err));goto cleanup;}// 4. 设置访问模式 (交错模式)if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {fprintf(stderr, "无法设置访问模式: %s\n", snd_strerror(err));goto cleanup;}// 5. 设置采样格式 (16位有符号小端)if ((err = snd_pcm_hw_params_set_format(handle, params, format)) < 0) {fprintf(stderr, "无法设置采样格式: %s\n", snd_strerror(err));goto cleanup;}// 6. 设置声道数 (双声道)if ((err = snd_pcm_hw_params_set_channels(handle, params, channels)) < 0) {fprintf(stderr, "无法设置声道数: %s\n", snd_strerror(err));goto cleanup;}// 7. 设置采样率 (44100 Hz)unsigned int actual_rate = sample_rate;if ((err = snd_pcm_hw_params_set_rate_near(handle, params, &actual_rate, 0)) < 0) {fprintf(stderr, "无法设置采样率: %s\n", snd_strerror(err));goto cleanup;}if (actual_rate != sample_rate) {fprintf(stderr, "警告: 无法设置请求的采样率 %u Hz, 使用 %u Hz 代替。\n", sample_rate, actual_rate);}// 8. 设置周期和缓冲区大小(根据你的硬件能力调整,这里仅为示例)snd_pcm_uframes_t frames = 1024; // 每次写入的帧数if ((err = snd_pcm_hw_params_set_period_size_near(handle, params, &frames, 0)) < 0) {fprintf(stderr, "无法设置周期大小: %s\n", snd_strerror(err));goto cleanup;}snd_pcm_uframes_t buffer_size = frames * 4; // 缓冲区是周期的倍数if ((err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size)) < 0) {fprintf(stderr, "无法设置缓冲区大小: %s\n", snd_strerror(err));goto cleanup;}// 9. 将参数写入驱动if ((err = snd_pcm_hw_params(handle, params)) < 0) {fprintf(stderr, "无法设置 PCM 参数: %s\n", snd_strerror(err));goto cleanup;}// 10. 准备 PCM 设备if ((err = snd_pcm_prepare(handle)) < 0) {fprintf(stderr, "无法准备音频接口: %s\n", snd_strerror(err));goto cleanup;}// 11. (此处省略音频数据读取和写入循环)// 实际应用中,你需要从文件或其他来源读取音频数据,// 然后循环调用 snd_pcm_writei(handle, buffer, frames_to_write) 将数据写入声卡。printf("ALSA 设备 %s 配置成功,准备播放。\n", device_name);// 假设播放完毕snd_pcm_drain(handle); // 等待所有缓冲数据播放完毕cleanup:snd_pcm_close(handle); // 关闭设备return err < 0 ? 1 : 0;
}
注意: 实际播放需要一个数据读取循环,这里只是演示了 ALSA 设备的配置过程。
解决音频问题的小贴士 ✨
在使用 ALSA 时,你可能会遇到各种音频问题,例如声音断断续续、没有声音或音质不佳。以下是一些常见的问题诊断和解决技巧:
-
欠运行 (Underrun) / XRUN
: 声音卡顿、断续通常是由于程序写入音频数据的速度慢于声卡播放速度。 -
增大缓冲区大小: 在 C 代码中,尝试增大
period_size
和buffer_size
的值。使用_near
函数时,要查看实际输出的周期和缓冲区大小。 -
检查系统负载: 运行 top 或 htop,看是否有其他程序占用过多 CPU。
-
提高程序优先级: 使用 nice 或 chrt 命令提高音频播放程序的运行优先级。
-
采样率不匹配:使用 plughw, 如果硬件不支持你想要的采样率,plughw 插件会自动进行转换。
-
USB 声卡配置 (Gadget):
如果你的 Linux 设备本身被配置为 USB 声卡(UAC gadget),那么其音频功能会受到 ConfigFS 中参数的严格限制。你需要查阅内核文档,看是否有 ConfigFS 属性可以调整 USB Gadget 声卡的内部缓冲区或周期设置。
ALSA C 语言编程核心 API 详解 🎶
在 Linux 下使用 ALSA 库 (libasound) 进行音频编程时,你需要掌握一系列关键的 API 函数。这些函数通常以 snd_pcm_ 或 snd_ 开头,用于操作 PCM(脉冲编码调制)设备,设置硬件参数,以及进行数据的读写。
snd_pcm_open()
int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
-
作用: 这个函数用于打开一个 PCM 设备,它是所有音频操作的起点。成功打开设备后,你会得到一个 snd_pcm_t 类型的句柄,后续的所有操作都将围绕这个句柄进行。
-
参数:snd_pcm_t **pcm: 这是一个指向 snd_pcm_t 指针的指针。函数会在这里返回一个新创建的 PCM 设备句柄(成功打开设备的标识)。
const char *name: PCM 设备的名称,例如 “hw:0,0”(直接访问第一个声卡的第一个设备)或 “plughw:1,0”(使用插件层访问第二个声卡的第一个设备)。“default” 也是一个常用的名称,它会尝试使用系统默认的声卡和设置。
snd_pcm_stream_t stream: 指定你希望进行的操作类型。
SND_PCM_STREAM_PLAYBACK: 用于播放音频。
SND_PCM_STREAM_CAPTURE: 用于录音/捕获音频。
int mode: 打开设备的模式。通常设置为 0 表示阻塞模式(函数会等待操作完成),其他值如 SND_PCM_NONBLOCK 用于非阻塞模式。对于简单的播放,0 即可。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_hw_params_alloca()
void snd_pcm_hw_params_alloca(snd_pcm_hw_params_t **ptr);
-
作用: 这个函数在栈上分配一个 snd_pcm_hw_params_t 结构体实例。这个结构体用于存储和管理 PCM 设备的硬件能力以及你希望设置的参数。
_alloca 后缀表示它是在栈上分配内存,这意味着你不需要手动 free() 释放它,当函数返回时内存会自动回收。这比在堆上分配更方便且安全。 -
参数:snd_pcm_hw_params_t **ptr: 指向 snd_pcm_hw_params_t 指针的指针。函数会把新分配的结构体地址存到这里。
-
返回值: 无。
snd_pcm_hw_params_any()
int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
-
作用: 这个函数用声卡硬件的默认或可用参数来初始化 snd_pcm_hw_params_t 结构体。在设置自定义参数之前,通常都需要调用它来获取一个有效的初始状态。这会填充结构体中所有可以配置的硬件参数的范围信息。
-
参数:snd_pcm_t *pcm: 之前通过 snd_pcm_open() 获取的 PCM 设备句柄。
snd_pcm_hw_params_t *params: 指向由 snd_pcm_hw_params_alloca() 分配的硬件参数结构体。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_hw_params_set_access()
int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access);
-
作用: 设置数据访问模式。这决定了你的应用程序如何向声卡驱动传递(或从声卡驱动接收)音频数据。
-
参数:snd_pcm_t *pcm: PCM 设备句柄。
snd_pcm_hw_params_t *params: 硬件参数结构体。
snd_pcm_access_t access: 访问模式。
SND_PCM_ACCESS_RW_INTERLEAVED: 读写交错模式。这是最常用的模式。数据以帧(frame)为单位进行读写,每个帧包含所有声道在同一时刻的样本,声道之间的数据是交错排列的(例如,立体声数据格式是 L1 R1 L2 R2 …)。你的代码中就是用这种模式,并通过 snd_pcm_writei() 写入数据。
SND_PCM_ACCESS_RW_NONINTERLEAVED: 读写非交错模式。所有第一声道的数据一起传输,然后所有第二声道的数据一起传输,以此类推。较少用。
SND_PCM_ACCESS_MMAP_INTERLEAVED: 内存映射交错模式。应用程序直接写入声卡驱动映射的内存区域,可以减少数据拷贝,适合低延迟应用。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_hw_params_set_format()
int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t format);
-
作用: 设置音频数据的采样格式(位深和字节序)。这是非常重要的参数,必须与你要播放的音频数据的实际格式匹配。
-
参数:
snd_pcm_t *pcm: PCM 设备句柄。
snd_pcm_hw_params_t *params: 硬件参数结构体。
snd_pcm_format_t format: 采样格式。常用的包括:
SND_PCM_FORMAT_S16_LE: 16 位有符号整数,小端序。这是 CD 质量音频和许多 WAV 文件常见的格式。
SND_PCM_FORMAT_S16_BE: 16 位有符号整数,大端序。
SND_PCM_FORMAT_U8: 8 位无符号整数。
SND_PCM_FORMAT_S24_LE: 24 位有符号整数,小端序。
SND_PCM_FORMAT_FLOAT_LE: 浮点数格式。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_hw_params_set_channels()
int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int channels);
-
作用: 设置音频流的声道数量。
-
参数:
snd_pcm_t *pcm: PCM 设备句柄。
snd_pcm_hw_params_t *params: 硬件参数结构体。
unsigned int channels: 声道数。
1: 单声道 (Mono)。
2: 立体声 (Stereo)。
其他值:多声道。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_hw_params_set_rate_near()
cint snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *rate, int *dir);
-
作用: 设置音频流的采样率。_near 后缀表示如果声卡不能精确支持请求的采样率,它会尝试找到并设置一个最接近且声卡支持的采样率。
-
参数:
snd_pcm_t *pcm: PCM 设备句柄。
snd_pcm_hw_params_t *params: 硬件参数结构体。
unsigned int *rate: 输入/输出参数。作为输入,它是你请求的采样率;作为输出,它会被实际设置的采样率所覆盖。
int *dir: 用于指示实际采样率与请求采样率的相对关系(例如,0 表示相等,1 表示实际值大于请求值,-1 表示实际值小于请求值)。通常可以设置为 NULL 或指向 0。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_hw_params_set_period_size_near()
int snd_pcm_hw_params_set_period_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
-
作用: 设置音频流的周期大小。周期是 ALSA 驱动每次处理(或应用程序每次写入)的最小音频数据块。较小的周期可以降低延迟,但会增加 CPU 开销(更频繁的上下文切换);较大的周期则相反。_near 同样表示会选择最接近的可用值。
-
参数:
snd_pcm_t *pcm: PCM 设备句柄。
snd_pcm_hw_params_t *params: 硬件参数结构体。
snd_pcm_uframes_t *val: 输入/输出参数。作为输入,是你请求的周期帧数;作为输出,是实际设置的周期帧数。snd_pcm_uframes_t 是一个无符号帧数类型。
int *dir: 同 set_rate_near。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_hw_params_set_buffer_size_near()
int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
-
作用: 设置声卡驱动的总缓冲区大小。总缓冲区是声卡在播放前能存储的音频数据的总量。缓冲区越大,系统就有更多的时间来填充数据,从而减少欠运行(XRUN)的可能性,但会增加延迟。_near 同样表示会选择最接近的可用值。
-
参数:
snd_pcm_t *pcm: PCM 设备句柄。
snd_pcm_hw_params_t *params: 硬件参数结构体。
snd_pcm_uframes_t *val: 输入/输出参数。作为输入,是你请求的总缓冲区帧数;作为输出,是实际设置的总缓冲区帧数。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_hw_params()
int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
-
作用: 这是关键的一步。在调用了一系列 snd_pcm_hw_params_set_* 函数来设置所需的硬件参数后,这个函数将这些参数应用并写入到声卡驱动中。如果参数组合不兼容或硬件不支持,这个函数会返回错误。
-
参数:
snd_pcm_t *pcm: PCM 设备句柄。
snd_pcm_hw_params_t *params: 已经配置好的硬件参数结构体。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_prepare()
snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t frames);
-
作用: 用于向 PCM 设备写入交错式音频数据进行播放。
-
参数:
snd_pcm_t *pcm: PCM 设备句柄。
const void *buffer: 指向包含要写入的音频数据的内存缓冲区的指针。
snd_pcm_uframes_t frames: 要写入的帧数。一帧包含所有声道在同一时刻的样本(例如,立体声 16 位格式中,一帧是 4 字节)。 -
返回值: 成功返回实际写入的帧数。如果返回负数,则表示错误,特别是 EPIPE 错误码表示发生了 XRUN,此时需要调用 snd_pcm_prepare() 来恢复。
snd_pcm_drain()
int snd_pcm_drain(snd_pcm_t *pcm);
-
作用: 用于等待所有缓冲中的音频数据播放完毕。在程序结束播放前调用此函数,可以确保所有已写入到 ALSA 缓冲区的音频数据都被送达到声卡并播放出来,避免数据丢失或声音被截断。
-
参数:
snd_pcm_t *pcm: PCM 设备句柄。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_pcm_close()
int snd_pcm_close(snd_pcm_t *pcm);
-
作用: 关闭一个 PCM 设备,释放所有与之相关的资源。这是在程序结束或不再需要设备时必须调用的函数。
-
参数:
snd_pcm_t *pcm: 要关闭的 PCM 设备句柄。 -
返回值: 成功返回 0,失败返回负数错误码。
snd_strerror()
const char *snd_strerror(int errnum)
-
作用: 将 ALSA 函数返回的负数错误码转换为可读的错误字符串。这对于调试和用户提示非常有用。
-
参数:
int errnum: ALSA 函数返回的负数错误码。 -
返回值: 指向错误描述字符串的指针。
测试demo
从wav文件中读取音频数据直接播放
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <alsa/asoundlib.h>
#include <sys/stat.h> // For stat() and S_ISREG()// 播放原始音频数据到ALSA设备
int play_raw_audio(const char *device_name, FILE *audio_file, long file_size);int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s <raw_audio_file>\n", argv[0]);return EXIT_FAILURE;}const char *raw_filename = argv[1];const char *alsa_device = "plughw:6,0"; // 指定USB声卡设备,请根据实际情况修改!// 获取文件大小,以便知道要读取多少数据struct stat st;if (stat(raw_filename, &st) == -1) {perror("Error getting file size");return EXIT_FAILURE;}long file_size = st.st_size;FILE *raw_fp = fopen(raw_filename, "rb");if (!raw_fp) {perror("Error opening raw audio file");return EXIT_FAILURE;}printf("Attempting to play raw audio from %s to ALSA device: %s\n", raw_filename, alsa_device);printf("Assuming format: Sample Rate = 44100 Hz, Channels = 2, Bits per Sample = 16 (S16_LE)\n");printf("File size: %ld bytes\n", file_size);if (play_raw_audio(alsa_device, raw_fp, file_size) != 0) {fprintf(stderr, "Error playing raw audio.\n");fclose(raw_fp);return EXIT_FAILURE;}printf("Raw audio playback finished.\n");fclose(raw_fp);return EXIT_SUCCESS;
}// 播放原始音频数据到ALSA设备
int play_raw_audio(const char *device_name, FILE *audio_file, long file_size) {snd_pcm_t *handle;snd_pcm_hw_params_t *params;int err;// 直接指定所需的参数,因为我们不从文件头解析unsigned int sample_rate = 44100;int num_channels = 2;int bits_per_sample = 16;snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; // 16位有符号小端// 打开PCM设备if ((err = snd_pcm_open(&handle, device_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {fprintf(stderr, "Cannot open audio device %s: %s\n", device_name, snd_strerror(err));return -1;}// 分配硬件参数结构体snd_pcm_hw_params_alloca(¶ms);// 填充默认值if ((err = snd_pcm_hw_params_any(handle, params)) < 0) {fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err));goto cleanup;}// 设置访问模式为交错模式 (Interleaved)if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err));goto cleanup;}// 设置采样格式if ((err = snd_pcm_hw_params_set_format(handle, params, format)) < 0) {fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err));goto cleanup;}// 设置声道数if ((err = snd_pcm_hw_params_set_channels(handle, params, num_channels)) < 0) {fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err));goto cleanup;}// 设置采样率unsigned int actual_rate = sample_rate;if ((err = snd_pcm_hw_params_set_rate_near(handle, params, &actual_rate, 0)) < 0) {fprintf(stderr, "Cannot set sample rate: %s\n", snd_strerror(err));goto cleanup;}printf("Actual sample rate: %d\n", actual_rate);if (actual_rate != sample_rate) {fprintf(stderr, "Warning: Rate %u Hz not supported by hardware, using %u Hz instead.\n", sample_rate, actual_rate);}// 设置缓冲区大小 (period size)snd_pcm_uframes_t frames;frames = 10240; // 每次写入的帧数,可以根据需要调整if ((err = snd_pcm_hw_params_set_period_size_near(handle, params, &frames, 0)) < 0) {fprintf(stderr, "Cannot set period size: %s\n", snd_strerror(err));goto cleanup;}printf("realy period size: %lu\n", frames);// 设置缓冲区总大小snd_pcm_uframes_t buffer_size = frames * 64; // 例如,缓冲区是period的4倍if ((err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size)) < 0) {fprintf(stderr, "Cannot set buffer size: %s\n", snd_strerror(err));goto cleanup;}printf("realy buffer size: %lu\n", buffer_size);// 将参数写入驱动if ((err = snd_pcm_hw_params(handle, params)) < 0) {fprintf(stderr, "Cannot set parameters: %s\n", snd_strerror(err));goto cleanup;}// 准备PCM设备if ((err = snd_pcm_prepare(handle)) < 0) {fprintf(stderr, "Cannot prepare audio interface for use: %s\n", snd_strerror(err));goto cleanup;}// 读取音频数据并写入声卡// //one audioFrame Byte=Sample_bits * Channels / 8// 一帧音频数据所占的字节大小 = 采样位数 * 通道数 / 8;char *buffer;snd_pcm_uframes_t period_size;snd_pcm_hw_params_get_period_size(params, &period_size, 0);printf("snd_pcm_hw_params_get_period_size size: %lu\n", period_size);size_t bytes_per_frame = (size_t)bits_per_sample / 8 * num_channels;size_t buffer_bytes = period_size * bytes_per_frame;buffer = (char *)malloc(buffer_bytes);if (!buffer) {fprintf(stderr, "Failed to allocate audio buffer.\n");goto cleanup;}long remaining_bytes = file_size; // 使用整个文件大小while (remaining_bytes > 0) {size_t bytes_to_read = (remaining_bytes < buffer_bytes) ? remaining_bytes : buffer_bytes;size_t bytes_read = fread(buffer, 1, bytes_to_read, audio_file);if (bytes_read == 0) {fprintf(stderr, "End of raw audio file or read error.\n");break;}snd_pcm_uframes_t frames_to_write = bytes_read / bytes_per_frame;snd_pcm_sframes_t frames_written = snd_pcm_writei(handle, buffer, frames_to_write);if (frames_written < 0) {if (frames_written == -EPIPE) { // XRUN occurred (underrun or overrun)fprintf(stderr, "ALSA XRUN, attempting to recover...\n");snd_pcm_prepare(handle);} else {fprintf(stderr, "Error writing to audio interface: %s\n", snd_strerror(frames_written));goto cleanup_buffer;}} else if (frames_written != frames_to_write) {fprintf(stderr, "Short write to ALSA: expected %lu frames, wrote %ld\n", frames_to_write, frames_written);}remaining_bytes -= (frames_written * bytes_per_frame);}// 等待所有缓冲数据播放完毕snd_pcm_drain(handle);cleanup_buffer:free(buffer);
cleanup:snd_pcm_close(handle);return err < 0 ? -1 : 0;
}
编译
aarch64-none-linux-gnu-gcc audio_playWav.c -o playWav.app --sysroot=/path/to/build/sysroot -lavformat -lavcodec -lavutil -lswscale -lm -lasound -lsndfile