alsa的snd_pcm_readi()函数和snd_pcm_writei()

本文介绍了ALSA库中的snd_pcm_readi()和snd_pcm_writei()函数,用于从PCM进行交错帧读取和写入。snd_pcm_readi()用于录音,当选择阻塞行为时,会等待所有请求的帧填充;非阻塞模式下则不等待。snd_pcm_writei()用于播放,非阻塞模式下也不会等待。理解这两个函数对于理解和操作ALSA录音和播放至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

snd_pcm_readi ()

 

 snd_pcm_sframes_t snd_pcm_readi ( snd_pcm_t *pcm,

                                                             void*     buffer,

 

                                                             snd_pcm_uframes_

<think>我们正在讨论如何使用ALSA的`snd_pcm_writei`函数来写入PCM音频数据。这通常是音频播放或实时音频流处理中的关键步骤。以下将详细解释该函数的用法,并结合之前的问题(播放48kHz单声道16位WAV音频)中的代码片段进行说明。 ### `snd_pcm_writei`函数详解 函数原型: ```c snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size); ``` - **参数说明**: - `pcm`: 已打开的PCM设备句柄。 - `buffer`: 指向音频数据缓冲区的指针。 - `size`: 要写入的帧数(注意:一帧等于每个通道的一个样本,例如单声道一帧就是一个样本,立体声一帧包含左右两个样本)。 - **返回值**: - 成功时返回实际写入的帧数(通常等于请求的帧数)。 - 失败时返回负的错误码(例如`-EPIPE`表示缓冲区欠载,`-ESTRPIPE`表示设备被挂起等)。 ### 使用步骤 在播放音频时,通常在一个循环中不断调用`snd_pcm_writei`,直到所有音频数据都写入完成。具体步骤如下: 1. **打开设备并设置参数**(如之前所述): ```c snd_pcm_t *pcm_handle; snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); // 设置参数:格式、通道数、采样率等 ``` 2. **准备数据缓冲区**: 根据音频格式分配缓冲区。例如,对于16位单声道音频,每个样本用`int16_t`表示。缓冲区大小通常以帧为单位,例如`#define BUFFER_FRAMES 1024`。 3. **循环读取音频数据并写入**: ```c int16_t buffer[BUFFER_FRAMES]; // 单声道,一帧就是一个int16_t size_t frames_to_write; while ((frames_to_write = fread(buffer, sizeof(int16_t), BUFFER_FRAMES, file)) > 0) { snd_pcm_sframes_t frames_written = snd_pcm_writei(pcm_handle, buffer, frames_to_write); // 错误处理 if (frames_written == -EPIPE) { // 发生欠载,重新准备设备 snd_pcm_prepare(pcm_handle); } else if (frames_written < 0) { // 其他错误 fprintf(stderr, "写入错误: %s\n", snd_strerror(frames_written)); break; } else if (frames_written != frames_to_write) { // 部分写入(通常由于设备暂时不可用) fprintf(stderr, "部分写入: %ld/%ld\n", frames_written, frames_to_write); } } ``` 4. **结束播放**: 在数据写入完成后,调用`snd_pcm_drain`等待所有数据播放完毕,然后关闭设备: ```c snd_pcm_drain(pcm_handle); snd_pcm_close(pcm_handle); ``` ### 关键注意事项 1. **帧与样本的区别**: - 对于单声道音频,一帧包含一个样本。 - 对于立体声音频,一帧包含两个样本(左声道右声道)。 - 因此,在分配缓冲区时,单声道需要的缓冲区大小为`帧数 * 1`,立体声则为`帧数 * 2`。 2. **数据格式**: - `snd_pcm_writei`要求数据格式与之前设置的硬件参数一致。例如,如果设置了`SND_PCM_FORMAT_S16_LE`(16位小端有符号整数),则缓冲区数据必须按此格式排列。 3. **错误处理**: - `-EPIPE`(欠载)表示音频设备消耗数据的速度快于应用程序提供数据的速度。通常通过调用`snd_pcm_prepare`恢复设备。 - `-ESTRPIPE`表示设备被挂起(例如系统休眠),需要调用`snd_pcm_resume`恢复。 ### 完整示例片段 结合之前播放WAV文件的代码,我们重点看播放循环部分: ```c // 分配缓冲区(单声道,16位,所以每帧2字节) int16_t *buffer = malloc(BUFFER_SIZE * sizeof(int16_t)); // 循环读取并播放 while ((frames_read = fread(buffer, sizeof(int16_t), BUFFER_SIZE, audio_file)) > 0) { snd_pcm_sframes_t frames_written = snd_pcm_writei(pcm_handle, buffer, frames_read); // ... 错误处理 } ``` ### 性能优化建议 - **缓冲区大小**:适当增大缓冲区(如4096帧)可以减少欠载发生,但会增加延迟。 - **周期大小**:通过`snd_pcm_hw_params_set_period_size_near`设置周期大小(一个周期是硬件每次中断处理的帧数),较小的周期可降低延迟,但增加CPU负担。 - **使用异步通知**:对于低延迟应用,可以使用ALSA的异步回调机制,在硬件准备好数据时通知应用程序写入。 ### 常见问题 1. **写入返回0**:表示没有数据被写入,通常因为设备未准备好,可重试或等待。 2. **数据格式错误**:如果格式不匹配(如大端写入小端设备),会听到噪音或静音。务必检查格式设置。 ### 引用说明 在ALSA中,`snd_pcm_writei`是用于交错模式(interleaved)音频数据写入的核心函数,适用于大多数播放场景[^1][^3][^4]。对于非交错模式(如独立声道),则需使用`snd_pcm_writen`函数。 §§相关问题§§ 1. 如何设置ALSA的硬件参数以最小化播放延迟? 2. 如何处理ALSA中的缓冲区欠载(underrun)溢出(overrun)? 3. ALSA支持哪些音频数据格式?如何选择最佳格式?</think># 使用`snd_pcm_writei`在ALSA中写入PCM音频数据 `snd_pcm_writei`是ALSA库中用于播放音频的核心函数,它负责将PCM音频数据写入音频设备进行播放。下面我将详细解释如何使用这个函数,并结合之前的音频播放实现进行说明。 ## `snd_pcm_writei`函数详解 ### 函数原型 ```c snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size); ``` ### 参数说明 - `pcm`: ALSA PCM设备句柄 - `buffer`: 包含音频数据的缓冲区指针 - `size`: 要写入的帧数(注意:帧数 = 样本数 × 通道数) ### 返回值 - 成功时返回实际写入的帧数 - 错误时返回负的错误代码 ## 使用`snd_pcm_writei`的完整流程 ### 1. 打开PCM设备 ```c snd_pcm_t *pcm_handle; int err; if ((err = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf(stderr, "无法打开PCM设备: %s\n", snd_strerror(err)); return -1; } ``` ### 2. 配置硬件参数 ```c snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca(&params); snd_pcm_hw_params_any(pcm_handle, params); // 设置参数:48kHz, 单声道, 16位小端 snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(pcm_handle, params, 1); unsigned int sample_rate = 48000; snd_pcm_hw_params_set_rate_near(pcm_handle, params, &sample_rate, 0); ``` ### 3. 分配音频缓冲区 ```c #define BUFFER_FRAMES 1024 // 缓冲区大小(帧数) int16_t *buffer = malloc(BUFFER_FRAMES * sizeof(int16_t)); ``` ### 4. 使用`snd_pcm_writei`写入数据 ```c size_t frames_read; while ((frames_read = fread(buffer, sizeof(int16_t), BUFFER_FRAMES, audio_file)) > 0) { snd_pcm_sframes_t frames_written = snd_pcm_writei(pcm_handle, buffer, frames_read); // 错误处理 if (frames_written == -EPIPE) { // 缓冲区欠载处理 fprintf(stderr, "缓冲区欠载\n"); snd_pcm_prepare(pcm_handle); // 重新准备设备 } else if (frames_written < 0) { fprintf(stderr, "写入错误: %s\n", snd_strerror(frames_written)); break; } else if ((size_t)frames_written != frames_read) { // 部分写入处理 fprintf(stderr, "部分写入: %ld/%ld帧\n", frames_written, frames_read); } } ``` ## 关键注意事项 ### 1. 帧与样本的关系 - 对于单声道音频:1帧 = 1个样本 - 对于立体声音频:1帧 = 2个样本(左 + 右) - 16位音频:每个样本占2字节 ### 2. 缓冲区管理 - 缓冲区大小应匹配硬件能力,可通过`snd_pcm_hw_params_get_buffer_size`查询 - 较小的缓冲区减少延迟但增加欠载风险 - 较大的缓冲区减少欠载但增加延迟 ### 3. 错误处理 常见错误代码: - `-EPIPE`:缓冲区欠载(XRUN) - `-ESTRPIPE`:设备挂起 - `-EAGAIN`:设备暂时不可用 处理XRUN的推荐方法: ```c if (frames_written == -EPIPE) { snd_pcm_prepare(pcm_handle); // 重新准备设备 // 可选择重新写入丢失的数据 frames_written = snd_pcm_writei(pcm_handle, buffer, frames_read); } ``` ## 完整示例:播放48kHz单声道16位WAV ```c #include <alsa/asoundlib.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #define PCM_DEVICE "default" #define BUFFER_FRAMES 2048 int play_audio(const char *filename) { snd_pcm_t *pcm_handle; FILE *audio_file; int16_t *buffer; int err; // 打开音频文件 if (!(audio_file = fopen(filename, "rb"))) { perror("无法打开音频文件"); return -1; } // 跳过WAV文件头(假设格式已知) fseek(audio_file, 44, SEEK_SET); // 打开PCM设备 if ((err = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf(stderr, "无法打开PCM设备: %s\n", snd_strerror(err)); fclose(audio_file); return -1; } // 配置硬件参数 snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca(&params); snd_pcm_hw_params_any(pcm_handle, params); snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(pcm_handle, params, 1); unsigned int rate = 48000; snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0); snd_pcm_hw_params(pcm_handle, params); // 准备缓冲区 buffer = malloc(BUFFER_FRAMES * sizeof(int16_t)); // 播放循环 size_t frames_read; while ((frames_read = fread(buffer, sizeof(int16_t), BUFFER_FRAMES, audio_file)) > 0) { snd_pcm_sframes_t frames_written; // 使用snd_pcm_writei写入数据 frames_written = snd_pcm_writei(pcm_handle, buffer, frames_read); // 错误处理 if (frames_written == -EPIPE) { fprintf(stderr, "缓冲区欠载,重新准备设备\n"); snd_pcm_prepare(pcm_handle); } else if (frames_written < 0) { fprintf(stderr, "写入错误: %s\n", snd_strerror(frames_written)); break; } else if ((size_t)frames_written != frames_read) { fprintf(stderr, "部分写入: %ld/%ld帧\n", frames_written, frames_read); } } // 结束播放 snd_pcm_drain(pcm_handle); snd_pcm_close(pcm_handle); free(buffer); fclose(audio_file); return 0; } int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "用法: %s <WAV文件>\n", argv[0]); return 1; } return play_audio(argv[1]); } ``` ## 性能优化建议 1. **设置合适的缓冲区大小**: ```c snd_pcm_uframes_t buffer_size = 8192; // 较大的缓冲区减少欠载 snd_pcm_uframes_t period_size = 1024; // 较小的周期减少延迟 snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &buffer_size); snd_pcm_hw_params_set_period_size_near(pcm_handle, params, &period_size, 0); ``` 2. **使用内存映射**减少数据拷贝: ```c snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_MMAP_INTERLEAVED); ``` 3. **实时优先级**提升: ```c snd_pcm_sw_params_t *swparams; snd_pcm_sw_params_current(pcm_handle, swparams); snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, period_size); snd_pcm_sw_params(pcm_handle, swparams); ``` ## 常见问题解决 1. **写入返回-EAGAIN**: - 表示设备暂时不可用,应稍后重试 - 可使用`snd_pcm_wait`等待设备准备好 2. **部分写入问题**: ```c size_t remaining = frames_read; while (remaining > 0) { snd_pcm_sframes_t written = snd_pcm_writei(pcm_handle, buffer + (frames_read - remaining), remaining); if (written > 0) remaining -= written; // 错误处理... } ``` 3. **时间戳同步**: ```c snd_pcm_uframes_t avail; snd_pcm_avail(pcm_handle); // 获取可用帧数 snd_pcm_delay(pcm_handle, &delay); // 获取当前延迟 ``` `snd_pcm_writei`是ALSA音频播放的核心,正确使用需要考虑缓冲区管理、错误处理实时性要求[^1][^3][^4]。对于高性能应用,建议结合硬件参数优化实时调度策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值