ALSA音频编程中period的帧大小应设置为多少?
关于ALSA音频播放的两个问题:超大Period的合规性与循环播放实现
我是音频编程新手,参考《Linux Journal - Introduction to Sound Programming with ALSA》和网络资料开发,需求是播放已读取的WAV文件所有样本。
当前实现是打开默认播放设备,按音频文件格式设置参数,把period设为包含所有样本,调用一次snd_pcm_writei完成播放,之后调用snd_pcm_drain和snd_pcm_close退出,能正常运行,但不确定这种方式是否合规。
尝试设置更小的period,通过循环连续播放样本时,出现输出不同步、失真或播放一段时间后产生噪音的问题。
当前可行代码
int audio_play(unsigned int frequency, int bits_per_sample, int channels_count, char *samples, long samples_count) { snd_pcm_t *handle; snd_pcm_uframes_t frames; int dir; int write_result; snd_pcm_hw_params_t *params; if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {printf("\naudio_play failed: snd_pcm_open failed.\n"); return 2;} 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); if (bits_per_sample == 8) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_U8); else if (bits_per_sample == 16) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16); else if (bits_per_sample == 24) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S24); else if (bits_per_sample == 32) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S32); if (channels_count == 1) snd_pcm_hw_params_set_channels(handle, params, 1); else if (channels_count == 2) snd_pcm_hw_params_set_channels(handle, params, 2); snd_pcm_hw_params_set_rate(handle, params, frequency, 0); frames = samples_count; snd_pcm_hw_params_set_period_size(handle, params, frames, 0); if (snd_pcm_hw_params(handle, params) < 0) {printf("\naudio_play: snd_pcm_hw_params_failed.\n"); return 1;} snd_pcm_writei(handle, samples, frames); snd_pcm_drain(handle); snd_pcm_close(handle); return 0; }
尝试的错误实现代码
int audio_play(unsigned int frequency, int bits_per_sample, int channels_count, char *samples, long samples_count) { snd_pcm_t *handle; snd_pcm_uframes_t frames; int dir; int write_result; snd_pcm_hw_params_t *params; if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {printf("\naudio_play failed: snd_pcm_open failed.\n"); return 2;} 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); if (bits_per_sample == 8) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_U8); else if (bits_per_sample == 16) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16); else if (bits_per_sample == 24) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S24); else if (bits_per_sample == 32) snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S32); if (channels_count == 1) snd_pcm_hw_params_set_channels(handle, params, 1); else if (channels_count == 2) snd_pcm_hw_params_set_channels(handle, params, 2); snd_pcm_hw_params_set_rate(handle, params, frequency, 0); frames = samples_count / (bits_per_sample / 8 * channels_count); snd_pcm_hw_params_set_period_size(handle, params, frames, 0); if (snd_pcm_hw_params(handle, params) < 0) {printf("\naudio_play: snd_pcm_hw_params_failed.\n"); return 1;} while (samples_count > 0) { snd_pcm_writei(handle, samples, frames); samples_count -= frames; samples += frames; } snd_pcm_drain(handle); snd_pcm_close(handle); return 0; }
测试所用音频文件参数
音频文件1
file type: RIFF file format: WAVE block format: fmt data block id: data channels count: 2 frequency: 44100 bytes per sec: 176400 bytes per block: 4 bits per sample: 16 samples count: 262094
音频文件2
file type: RIFF file format: WAVE block format: fmt data block id: data channels count: 1 frequency: 22257 bytes per sec: 22257 bytes per block: 1 bits per sample: 8 samples count: 91240
问题解答
一、设置超大Period的潜在问题
直接把period设为整个音频的样本数,虽然能运行,但存在不少隐患:
- 内存压力:音频设备需要一次性分配足够内存容纳所有样本,对于大文件(比如几十分钟的音频),可能导致内存分配失败,或占用过多系统资源。
- 设备兼容性:部分音频硬件或驱动对period size有最大限制,超过限制会导致
snd_pcm_hw_params调用失败,无法初始化设备。 - 响应性差:如果播放过程中需要暂停、停止或调整音频,这种方式无法及时响应,必须等整个音频播放完成才能处理。
二、正确的循环播放实现
你的错误实现有三个核心问题:单位不统一、未处理snd_pcm_writei返回值、指针偏移错误。修正后的代码如下:
int audio_play(unsigned int frequency, int bits_per_sample, int channels_count, char *samples, long samples_bytes) { snd_pcm_t *handle; snd_pcm_uframes_t period_frames = 4096; // 选择设备兼容的常见period大小 snd_pcm_uframes_t frames_to_write; int dir; int write_result; snd_pcm_hw_params_t *params; int bytes_per_frame = (bits_per_sample / 8) * channels_count; if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) { printf("\naudio_play failed: snd_pcm_open failed.\n"); return 2; } 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); // 设置采样格式 switch (bits_per_sample) { case 8: snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_U8); break; case 16: snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16); break; case 24: snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S24); break; case 32: snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S32); break; default: printf("\naudio_play: unsupported bits per sample.\n"); snd_pcm_close(handle); return 3; } // 设置通道数 if (channels_count != 1 && channels_count != 2) { printf("\naudio_play: unsupported channels count.\n"); snd_pcm_close(handle); return 4; } snd_pcm_hw_params_set_channels(handle, params, channels_count); // 设置采样率 if (snd_pcm_hw_params_set_rate(handle, params, frequency, 0) < 0) { printf("\naudio_play: failed to set sample rate.\n"); snd_pcm_close(handle); return 5; } // 让ALSA自动调整到设备支持的period大小 if (snd_pcm_hw_params_set_period_size_near(handle, params, &period_frames, &dir) < 0) { printf("\naudio_play: failed to set period size.\n"); snd_pcm_close(handle); return 6; } if (snd_pcm_hw_params(handle, params) < 0) { printf("\naudio_play: snd_pcm_hw_params_failed.\n"); snd_pcm_close(handle); return 1; } long remaining_bytes = samples_bytes; char *current_samples = samples; while (remaining_bytes > 0) { // 计算本次可写入的最大帧数 frames_to_write = period_frames; long max_frames = remaining_bytes / bytes_per_frame; if (frames_to_write > max_frames) { frames_to_write = max_frames; } write_result = snd_pcm_writei(handle, current_samples, frames_to_write); if (write_result < 0) { // 处理underrun错误,尝试恢复设备 if (write_result == -EPIPE) { printf("\naudio_play: underrun occurred, attempting recovery.\n"); snd_pcm_prepare(handle); continue; } else { printf("\naudio_play: snd_pcm_writei failed with error: %d\n", write_result); snd_pcm_close(handle); return 7; } } // 更新剩余字节数和当前数据指针 remaining_bytes -= write_result * bytes_per_frame; current_samples += write_result * bytes_per_frame; } snd_pcm_drain(handle); snd_pcm_close(handle); return 0; }
关键修正点说明
- 使用
snd_pcm_hw_params_set_period_size_near:让ALSA自动选择设备支持的最接近目标值的period大小,避免设置不支持的period导致初始化失败。 - 单位统一:严格区分字节数和帧数,所有计算保持单位一致,指针偏移按实际写入的字节数计算。
- 处理返回值:捕获
-EPIPE(underrun)错误,调用snd_pcm_prepare恢复设备;确保所有音频数据都被正确写入。 - 合理的period大小:选择4096、2048这类常见值,平衡内存占用和播放稳定性。
内容的提问来源于stack exchange,提问作者Dominik
相关产品推荐
相关产品推荐

