You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(&params);
  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(&params);
  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(&params);
    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;
}

关键修正点说明

  1. 使用snd_pcm_hw_params_set_period_size_near:让ALSA自动选择设备支持的最接近目标值的period大小,避免设置不支持的period导致初始化失败。
  2. 单位统一:严格区分字节数和帧数,所有计算保持单位一致,指针偏移按实际写入的字节数计算。
  3. 处理返回值:捕获-EPIPE(underrun)错误,调用snd_pcm_prepare恢复设备;确保所有音频数据都被正确写入。
  4. 合理的period大小:选择4096、2048这类常见值,平衡内存占用和播放稳定性。

内容的提问来源于stack exchange,提问作者Dominik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 20:57:02