ALSA编程实践之libasound库的使用——PCM篇

说明

在Linux平台, 设计到音频的开发,必然离不开liasound,无论是混音器的参数设置,还是录音、播放音频等,liasound都提供了支持。这篇文章介绍的时播放音频部分,即 PCM 部分

预备知识

音频相关知识

    采样率(每秒采样次数)帧率 字节率(每秒采样字节数) 通道数(1: 单通道, 2: 立体声) 单位采样数据位数(8位无符号或16位有符号)

知识点说明

  1. 采样率, 表示每一秒对声音的波形模拟量取样的次数,频率越高,音质越好
  2. 字节率, 与采样率、通道数和单位采样字节数综合相关, 实际上等于这三个数据的乘积除以8 (8位代表1字节)
  3. 通道数, 就是我们平常听音乐时使用的耳机,如果是单通道数据, 我们会听到两个耳机内声音时一模一样, 如果是双通道,一些音质比较高的音乐或者影视甚至游戏,在两个耳机里面听到的声音会不一样,能给人一种有距离的感觉,所以叫立体声, 一些音质更高的播放设备和音乐文件中也会包含双通道以外的音频信息, 是播放出来的音频效果更逼真
  4. 单位采样数据位数, 指的是每次对声音波形的模拟量采样记录时, 用来记录数据的数据位数, 数据位数越高,数据表示的模拟量越精细, 音质越好

PCM (脉冲编码调制)

自行百度或者谷歌去了解

ALSA编程接入

头文件

#include <alsa/asoundlib.h>

需要用到的几个结构体

snd_pcm_t
snd_pcm_hw_params_t
snd_pcm_sw_params_t

如何使用

1. 首先使用snd_pcm_t来打开音频设备,用来播放音频或者录音

int main(int agc, char **argv)
{
          
   
	snd_pcm_t *pcm;
	snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0); // 这里是以音频播放模式打开
	// or 
	// snd_pcm_open(&pcm, "default", SND_PCM_STREAM_CAPTURE, 0); 以录音模式打开
	//...
	return 0;
}

2. 使用snd_pcm_hw_params_t配置硬件参数

int main(int agc, char **argv)
{
          
   
	//...
	snd_pcm_hw_params_t *hwparams;
	snd_pcm_hw_params_malloc(&hwparams); // 或者 snd_pcm_hw_params_alloca(&hwparams); 
	snd_pcm_hw_params_any(pcm, hwparams); // 使用pcm设备初始化hwparams

	snd_pcm_hw_params_set_xxx(pcm, hwparams, ...); // 通过一系列的set函数来设置硬件参数中的基本参数
												// 如, 通道数,采样率, 采样格式等等
	// ...
	snd_pcm_hw_params(pcm, hwparams);
	snd_pcm_hw_params_free(hwparams); // 释放不再使用的hwparams空间
	//...
	return 0;
}

3. 使用snd_pcm_sw_params_t配置一些高级软件参数, 比如使用中断模式等等

使用方法与snd_pcm_hw_params_t基本差不多, 这里不再赘述

4. 调用snd_pcm_prepare(pcm)来使音频设备准备好接收pcm数据

int main(int argc, char **argv) 
{
          
   
	// ...
	if(snd_pcm_prepare(pcm) < 0)  
	{
          
   
		snd_pcm_close(pcm);
		exit(1);
	}
	// ...
	return 0;
}

5. 循环调用snd_pcm_writei(pcm, buffer, frame)来往音频设备写数据

循环写数据时需要注意以下几个问题:**

  1. snd_pcm_writei(pcm, buffer, frame)中使用的frame不是buffer数据的字节数, 而是帧数(采样数), 返回值也是帧数
  2. 帧数与字节数之间需要通过 snd_pcm_bytes_to_frames(bytes)和snd_pcm_frames_to_bytes(frames)来做转换
  3. 每次调用完snd_pcm_writei(pcm, buffer, frames)后, 需要做适当延时, 延时时间为, 理论上已经写入帧数所需时间与实际调用该函数所花时间的差值
  4. 每次调用snd_pcm_writei(pcm, buffer, frames)返回后, 下次buffer的起始地址需要向前进 (frame * 通道数 * 采样位数 / 8)字节,而剩余的帧数只需要在上一次总帧数基础上减去已写入的帧数
  5. 建议每次准备1秒的数据到缓冲区, 太大会浪费缓冲区, 太小又需要频繁请求数据

综合使用案例

    2018-05-30 完成wav格式音频文件的播放
经验分享 程序员 微信小程序 职场和发展