使用 ALSA (Advanced Linux Sound Architecture) 操作 PCM 裝置,主要分為硬體參數初始化與PCM 介面操作兩類函式。
snd_pcm_hw_params_malloc():配置 snd_pcm_hw_params_t 結構記憶體snd_pcm_hw_params_any():以裝置完整參數空間初始化snd_pcm_hw_params_set_access():設定存取模式(交錯/非交錯)snd_pcm_hw_params_set_format():設定取樣格式(S16_LE, S32_LE 等)snd_pcm_hw_params_set_channels():設定聲道數snd_pcm_hw_params_set_rate_near():設定最接近目標的取樣率snd_pcm_hw_params():提交參數配置snd_pcm_hw_params_free():釋放參數結構snd_pcm_open():開啟 PCM 裝置snd_pcm_prepare():準備裝置接收資料snd_pcm_writei():寫入交錯音訊資料snd_pcm_close():關閉裝置plughw:0,0:自動轉換格式的 plug 介面(推薦),hw:0,0 需手動確認硬體支援。兩數字分別為卡號與子裝置號。
SND_PCM_ACCESS_RW_INTERLEAVED:交錯模式,單幀包含所有聲道連續取樣(如 LRLR...)SND_PCM_ACCESS_RW_NONINTERLEAVED:非交錯模式,各聲道資料分開存放SND_PCM_FORMAT_S16_LE // 有號 16-bit Little Endian SND_PCM_FORMAT_S16_BE // 有號 16-bit Big Endian SND_PCM_FORMAT_U16_LE // 無號 16-bit Little Endian SND_PCM_FORMAT_S32_LE // 有號 32-bit Little Endian // ... 以此類推
int open_device(struct mad_header const *header) {
int err;
snd_pcm_hw_params_t *hw_params;
char *pcm_name = "plughw:0,0";
int rate = header->samplerate;
int channels = (header->mode == 0) ? 1 : 2;
if ((err = snd_pcm_open(&playback_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
fprintf(stderr, "cannot open audio device %s (%s)\n", pcm_name, snd_strerror(err));
return -1;
}
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) return -1;
if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0) return -1;
if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) return -1;
if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_S32_LE)) < 0) return -1;
if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &rate, 0)) < 0) return -1;
if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, channels)) < 0) return -1;
if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0) return -1;
snd_pcm_hw_params_free(hw_params);
if ((err = snd_pcm_prepare(playback_handle)) < 0) return -1;
return 0;
}
while (nsamples--) {
signed int sample = pcm->samples[0][j];
*(OutputPtr++) = sample & 0xff;
*(OutputPtr++) = (sample >> 8);
*(OutputPtr++) = (sample >> 16);
*(OutputPtr++) = (sample >> 24);
if (nchannels == 2) {
sample = pcm->samples[1][j];
*(OutputPtr++) = sample & 0xff;
*(OutputPtr++) = (sample >> 8);
*(OutputPtr++) = (sample >> 16);
*(OutputPtr++) = (sample >> 24);
}
j++;
}
if ((err = snd_pcm_writei(playback_handle, buf, samples)) < 0) {
err = xrun_recovery(playback_handle, err);
if (err < 0) { fprintf(stderr, "Write error: %s\n", snd_strerror(err)); return -1; }
}
int xrun_recovery(snd_pcm_t *handle, int err) {
if (err == -EPIPE) { // under-run
err = snd_pcm_prepare(handle);
if (err < 0) fprintf(stderr, "Can't recovery from underrun: %s\n", snd_strerror(err));
return 0;
} else if (err == -ESTRPIPE) { // suspend
while ((err = snd_pcm_resume(handle)) == -EAGAIN) sleep(1);
if (err < 0) {
err = snd_pcm_prepare(handle);
if (err < 0) fprintf(stderr, "Can't recovery from suspend: %s\n", snd_strerror(err));
}
return 0;
}
return err;
}
-EPIPE 表示應用程式未及時送資料導致底流;-ESTRPIPE 表示裝置被掛起。恢復策略分別呼叫 snd_pcm_prepare() 或 snd_pcm_resume()。
使用 mad_stream_buffer() 關聯 MP3 資料流,循環呼叫 mad_frame_decode() 取得 PCM 幀,處理 MAD_ERROR_BUFLEN 緩衝不足情況,最後送入上述寫入流程播放。