欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > ALSA音频用户态APP

ALSA音频用户态APP

2025/6/14 5:29:51 来源:https://blog.csdn.net/weixin_42532643/article/details/148547670  浏览:    关键词:ALSA音频用户态APP

参考链接:小白快速入门Linux ALSA应用编程
ALSA官网:https://www.alsa-project.org/alsa-doc/alsa-lib/index.html
alsa官网提供对API和概念的理解,提供小例子帮助新手入门,很有帮助。

1. 一些基础

如果想要开始ALSA应用编程,需要一些音频基础,因为编程的时候需要知道概念。

采样精度 = 样本长度 (8bit, 16bit, 32bit)
通道数:Channels。
一帧字节数 = 采样精度/8 * Channels
周期:一次处理一帧数据太低效率啦,所以一次处理的数据帧数。可以包含多帧。
缓存大小 Buffer Size:空间大小是周期空间的倍数。
采样率: 常见的 44100,48000。
数据格式:S32_LE,S16_LE

如果知道周期和采样频率就可以知道一个周期的音频数据需要播放多长时间了。
因为:周期为帧的个数,频率为每秒能采集帧的个数。比如:
周期 6000 帧,采样率是 48 Khz,那么一个周期的数据能播放 125 ms。
周期 6000 帧,采样率是 44.1 Khz,那么一个周期的数据能播放 125 ms。
频率是441000,那么一帧数据能播放22.7us
频率是441000,那么60帧数据能播放1.452us

2. 如何编译

因为alsa框架涉及到user space, kernel space.版本还得匹配,并不是在ALSA直接下载一个alsa user lib 就行了,一般芯片厂商提供交叉编译环境:

$CC -o pcm.out pcm2.c  -lasound

3. 用户空间API如何交互

Alsa 用户空间的 API 库主要通过 open/read/write/ioctl 操作 /dev/snd/xxx 下的设备文件实现与内核交互。常见设备文件接口:

Information Interface (/proc/asound)	# 提供 ALSA 设备的文本信息,用于查询系统中可用的音频设备及其配置。
Control Interface 	(/dev/snd/controlCX)# 控制声卡的硬件参数(如音量、声道平衡、输入源切换)。
Mixer Interface 	(/dev/snd/mixerCXDX)# 控制音频输入 / 输出路径和音量混合(较旧的接口,部分被控制接口替代)
PCM Interface 		(/dev/snd/pcmCXDX)	# 处理原始音频数据的输入 / 输出(播放或录音)。通过 ALSA API(如snd_pcm_open())访问,需配置采样率、位深、声道数等参数。
Raw MIDI Interface 	(/dev/snd/midiCXDX) # 传输MIDI(乐器数字接口)数据,用于控制电子乐器或合成器
Sequencer Interface (/dev/snd/seq)		# 提供高级 MIDI 功能,支持多轨 MIDI 序列、时间同步。支持软件合成器(如 FluidSynth),可同时处理多个 MIDI 设备和事件。
Timer Interface 	(/dev/snd/timer)	# 提供高精度时间同步,确保音频数据的精确播放 / 录制。用于多设备同步,音视频同步。
但是我的imx95上没有Mixer和Raw MIDI和Sequencer接口,其他都有。

命名规则显而易见, pcm表示设备类型, C0 表示声卡0, D0表示设备0, c/p分别表示录音、播放功能。

4. 使用ALSA标准命令行程序

这些命令行程序还是通过ALSA用户空间API进行控制的。

# 列出音频设备
aplay -l
# 播放指定文件
aplay -Dhw:wm8962audio -c 2 -r 48000 -f S16_LE /usr/share/sounds/alsa/Front_Left.wav
# 转发
arecord -Dhw:wm8962audio -c 2 -r 48000 -f S16_LE | aplay -Dhw:wm8962audio -c 2 -r 48000 -f S16_LE

5. 使用ALSA 用户态lib编程

简单了解大致过程,先打开,配置,然后读或写。一个小例子,对面设备使用arecord和aplay组合成的loopback。

#include <alsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <sched.h>#define TX_SIZE_BYTES 512       // 发送数据大小(字节)
#define RX_SIZE_BYTES 512     // 接收数据大小(字节)
#define PCM_DEVICE "hw:3,0"   // 声卡设备
#define RATE 44100
#define CHANNELS 4/* 方便调试 */
#undef DBG
#if 0#define DBG(fmt, args...) fprintf(stderr, fmt, ##args)
#else#define DBG(fmt, args...)
#endif/* 指定对齐 */
#define RET_ALIGN_FLOOR(val, align) \(typeof(val))((val) & (~((typeof(val))((align) - 1))))
#define RET_ALIGN_CEIL(val, align) \RET_ALIGN_FLOOR((val) + ((typeof(val))((align) - 1)), align)static snd_pcm_t *playback_handle;
static snd_pcm_t *capture_handle;
static volatile int running = 1;/* 信号处理函数 */
static void signal_handler(int signum) {running = 0;
}int main(void) {int err;snd_pcm_hw_params_t *hw_params_capture;snd_pcm_hw_params_t *hw_params_playback;snd_pcm_format_t format = SND_PCM_FORMAT_S32_LE;unsigned int rate = RATE;       	// 采样率(需与声卡匹配)unsigned int channels = CHANNELS;	// 声道数snd_pcm_uframes_t frames_per_buffer; //帧数snd_pcm_uframes_t period_size;  	// 1个周期的帧数char *tx_buffer, *tx_buffer_raw;char *rx_buffer, *rx_buffer_raw;struct timespec start_time, end_time;long long latency_ns;// 分配音频缓冲区,64字节对齐tx_buffer_raw = (char *)malloc(TX_SIZE_BYTES + 64);tx_buffer = (void *)RET_ALIGN_CEIL((uint64_t)(tx_buffer_raw), 64);printf("tx_buffer_raw: %p, tx_buffer = %p\n", tx_buffer_raw, tx_buffer);rx_buffer_raw = (char *)malloc(RX_SIZE_BYTES + 64);rx_buffer = (void *)RET_ALIGN_CEIL((uint64_t)(rx_buffer_raw), 64);printf("rx_buffer_raw: %p, rx_buffer = %p\n", rx_buffer_raw, rx_buffer);// 初始化随机数据srand(time(NULL));for (int i = 0; i < TX_SIZE_BYTES; i++) {tx_buffer[i] = rand() & 0xFF;}// 设置实时优先级(可选,需root权限)struct sched_param param = {.sched_priority = 99};if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {fprintf(stderr, "警告: 无法设置实时优先级\n");}// 注册信号处理signal(SIGINT, signal_handler);signal(SIGTERM, signal_handler);// 打开播放设备if ((err = snd_pcm_open(&playback_handle, PCM_DEVICE,SND_PCM_STREAM_PLAYBACK, 0)) < 0) {fprintf(stderr, "打开播放设备失败: %s\n", snd_strerror(err));return 1;}// 打开捕获设备if ((err = snd_pcm_open(&capture_handle, PCM_DEVICE,SND_PCM_STREAM_CAPTURE, 0)) < 0) {fprintf(stderr, "打开捕获设备失败: %s\n", snd_strerror(err));snd_pcm_close(playback_handle);return 1;}// 配置公共硬件参数snd_pcm_hw_params_alloca(&hw_params_playback);if ((err = snd_pcm_hw_params_any(playback_handle, hw_params_playback)) < 0) {fprintf(stderr, "初始化硬件参数失败: %s\n", snd_strerror(err));goto cleanup;}// 设置参数(格式、声道、采样率)if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params_playback, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0 ||(err = snd_pcm_hw_params_set_format(playback_handle, hw_params_playback, format)) < 0 ||(err = snd_pcm_hw_params_set_channels(playback_handle, hw_params_playback, channels)) < 0 ||(err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params_playback, &rate, NULL)) < 0) {fprintf(stderr, "设置参数失败: %s\n", snd_strerror(err));goto cleanup;}// 计算每帧字节数(16位立体声:2字节/样本 × 2声道 = 4字节/帧)int bytes_per_frame = channels * snd_pcm_format_physical_width(format) / 8;frames_per_buffer = TX_SIZE_BYTES / bytes_per_frame;  // 240字节对应60帧(4字节/帧)snd_pcm_uframes_t min_buf, max_buf;
snd_pcm_hw_params_get_buffer_size_min(hw_params_playback, &min_buf);
snd_pcm_hw_params_get_buffer_size_max(hw_params_playback, &max_buf);
printf("INFO:支持的缓冲区大小范围: %lu - %lu 帧\n", min_buf, max_buf);
snd_pcm_uframes_t min_period, max_period;
snd_pcm_hw_params_get_period_size_min(hw_params_playback, &min_period, NULL);
snd_pcm_hw_params_get_period_size_max(hw_params_playback, &max_period, NULL);
printf("INFO:支持的周期大小范围: %lu - %lu 帧\n", min_period, max_period);DBG("debug: line:%d \n",__LINE__);// 设置缓冲区大小(需为硬件支持的最小周期整数倍)if ((err = snd_pcm_hw_params_set_buffer_size_near(playback_handle, hw_params_playback, &frames_per_buffer)) < 0) {fprintf(stderr, "设置缓冲区大小失败: %s\n", snd_strerror(err));goto cleanup;}DBG("debug: line:%d \n",__LINE__);period_size = frames_per_buffer;if ((err = snd_pcm_hw_params_set_period_size_near(playback_handle, hw_params_playback, &period_size, 0)) < 0) {fprintf(stderr, "无法设置播放周期大小: %s\n", snd_strerror(err));goto cleanup;}DBG("debug: line:%d \n",__LINE__);//第二步: 填充捕获设备的硬件参数snd_pcm_hw_params_alloca(&hw_params_capture);if ((err = snd_pcm_hw_params_any(capture_handle, hw_params_capture)) < 0) {fprintf(stderr, "无法初始化捕获硬件参数: %s\n", snd_strerror(err));goto cleanup;}DBG("debug: line:%d \n",__LINE__);if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params_capture,SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {fprintf(stderr, "无法设置捕获访问类型: %s\n", snd_strerror(err));goto cleanup;}if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params_capture, format)) < 0) {fprintf(stderr, "无法设置捕获样本格式: %s\n", snd_strerror(err));goto cleanup;}if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params_capture, &rate, 0)) < 0) {fprintf(stderr, "无法设置捕获采样率: %s\n", snd_strerror(err));goto cleanup;}if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params_capture, channels)) < 0) {fprintf(stderr, "无法设置捕获声道数: %s\n", snd_strerror(err));goto cleanup;}// 设置缓冲区大小和周期大小if ((err = snd_pcm_hw_params_set_buffer_size_near(capture_handle, hw_params_capture, &frames_per_buffer)) < 0) {fprintf(stderr, "无法设置捕获缓冲区大小: %s\n", snd_strerror(err));goto cleanup;}period_size = frames_per_buffer;if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params_capture, &period_size, 0)) < 0) {fprintf(stderr, "无法设置捕获周期大小: %s\n", snd_strerror(err));goto cleanup;}// 应用参数到播放和捕获设备if ((err = snd_pcm_hw_params(playback_handle, hw_params_playback)) < 0 ||(err = snd_pcm_hw_params(capture_handle, hw_params_capture)) < 0) {fprintf(stderr, "应用硬件参数失败: %s\n", snd_strerror(err));goto cleanup;}// 准备设备if ((err = snd_pcm_prepare(playback_handle)) < 0 ||(err = snd_pcm_prepare(capture_handle)) < 0) {fprintf(stderr, "设备准备失败: %s\n", snd_strerror(err));goto cleanup;}// 记录开始时间(发送前) CLOCK_MONOTONIC 为单调递增时钟,不可调整if (clock_gettime(CLOCK_MONOTONIC, &start_time) == -1) {perror("获取开始时间失败");goto cleanup;}
DBG("debug: line:%d \n",__LINE__);// 发送数据snd_pcm_sframes_t written = snd_pcm_writei(playback_handle, tx_buffer, frames_per_buffer);if (written < 0) {written = snd_pcm_recover(playback_handle, written, 0);if (written < 0) {fprintf(stderr, "发送失败: %s\n", snd_strerror(written));goto cleanup;}}DBG("debug: line:%d \n",__LINE__);// 等待数据发送完成snd_pcm_drain(playback_handle);// 接收数据snd_pcm_sframes_t read = snd_pcm_readi(capture_handle, rx_buffer, frames_per_buffer);if (read < 0) {read = snd_pcm_recover(capture_handle, read, 0);if (read < 0) {fprintf(stderr, "接收失败: %s\n", snd_strerror(read));goto cleanup;}}// 记录结束时间(接收后)if (clock_gettime(CLOCK_MONOTONIC, &end_time) == -1) {perror("获取结束时间失败");goto cleanup;}// 计算延时latency_ns = (end_time.tv_sec - start_time.tv_sec) * 1e9 + (end_time.tv_nsec - start_time.tv_nsec);printf("环回延时: %.3f 毫秒 (%.0lld 纳秒)\n", (double)latency_ns / 1e6, latency_ns);cleanup:snd_pcm_close(playback_handle);snd_pcm_close(capture_handle);free(tx_buffer_raw);free(rx_buffer_raw);printf("操作完成\n");return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词