目录
概念
采样
量化
编码
音频文件wav 格式
标准音频接口 ALSA
录制音频
步骤
api
获取pcm设备句柄
设置 PCM 设备参数
代码
播放音频
步骤
代码
概念
信号都是模拟信号,不管是声音还是光线,这些模拟信号需要被 A/D 转换器转换成数字信号,才能被存储在计算机中,从概念上讲,可以将 A/D 转换视为三步完成的过程:采样、量化和编码
采样
用采样器每隔一段时间读取一次模拟信号,用这些离散的值来代表整个模拟信号的过程。单位时间内的采样值个数被称为采样频率。常用的采样频率是 11025Hz、22050Hz 和 44100Hz。当然,也可以是其他更高或者更低的频率
量化
对于每次采样得到的值,考虑使用多少个 bit 来存储它。如果使用 8 个 bit (即一个字节)来描述采样值,那么能表达的值的范围是 256,如果使用 16 个 bit 来描述, 范围就被扩展为 65536,描述一个采样值所使用的位数,也被称为分辨率。常用的量化步 长为 8 位、16 位或者 32 位。
编码
脉冲编码 调制就是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在 信道中传输。脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程
音频文件wav 格式
wav 是一种符合 RIFF 文档规范的文件格式,这种文档规范是一种以树形结构组织 数据的标准,以 wav 格式为例子,文档必须先包含“RIFF 数据块”,也就是下图左边部分的区域,其中 ID 固定为 RIFF 四个字符,而且是大端序。而 SIZE 是除了 ID 和 SIZE 之外本文档的总大小,FMT 则是 RIFF 规范下 DATA 的具体数据格式,wav 对应的是 WAVE。 剩下的 DATA 就是 RIFF 文档的内容,RIFF 文档的内容又可以由多个“数据块”组成,对于 wav 格式而言,它的组成如下图 中右边部分所示:包含两块,一个是 fmt 块,一个 data 块
标准音频接口 ALSA
安装 ALSA 库
- 下载最新版alsa源码
- 解压缩,进入源码目录中并依次执行./configure、make 和 make install
- 将安装之后的 ALSA 库所在路径(缺省是/usr/lib/i386-Linux-gnu/)添加到环境 变量 LD_LIBRARY_PATH 中
- 编译音频程序的时候,包含头文件,并且链接 alsa 库: 比如: gcc example.c -o example -lasound
- 如果要将 ALSA 库安装到基于 ARM 平台的开发板上时,除了第 2 步中的./configure 需要增加指定交叉工具链前缀(例如--host=arm-none-linux-gnueabi)的参数外,还 需要将编译好的 ALSA 库的全部文件放到开发板中,并且保持绝对路径完全一致。
录制音频
步骤
- 取得 PCM 设备的句柄
- snd_pcm_t *handle;
- 设置 PCM 流的方向(录制)
- snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE;
- 设置诸如数据 buffer 大小、采样频率、 量化级等
- snd_pcm_hw_params_t *hwparams;
api
获取pcm设备句柄
在 ALSA 中,可以使用 plughw 或者 hw 来代表 PCM 设备接口,使用 plughw 时我们 不需要关心所设置的各种 params 是否被声卡支持,因为如果不支持的话会自动使用默认 的值,如果使用 hw 的话就必须仔细检查声卡硬件的信息,确保设置的每一项都被支持。一 般而言使用 plughw 就可以了,具体而言,使用如下函数获得 PCM 设备句柄
snd_pcm_open(&handle, “plughw:0,0”, stream,0);
0,0 中的第一个 0 是系统中声卡的编号,第二个 0 是设备的 编号,而最后一个 0 代表标准打开模式,除此之外还可以是 SND_PCM_NONBLOCK 或者 SND_PCM_ASYNC,前者代表非阻塞读写 PCM 设备,后者代表声卡系统以异步方式工作: 每当一个周期(period)结束时,将触发一个
设置 PCM 设备参数
- 首先,给参数配置分配相应的空间,并且根据当前的 PCM 设备的具体情况初始化:
- snd_pcm_hw_params_t *hwparams;
- snd_pcm_hw_params_alloca(&hwparams);
- snd_pcm_hw_params_any(handle, hwparams);
- 设置访问模式为交错模式,这意味着采样点是帧连续的,而不是通道连续的
- snd_pcm_hw_params_set_access(handle,SND_PCM_ACCESS_RW_INTERLEAVED);
- SND_PCM_ACCESS_MMAP_INTERLEAVED:内存映射方式下的交错模式 SND_PCM_ACCESS_MMAP_NONINTERLEAVED:内存映射方式下的非交错模式 SND_PCM_ACCESS_RW_INTERLEAVED:直接 IO 方式下的交错模式 SND_PCM_ACCESS_RW_NONINTERLEAVED:直接 IO 方式下的非交错模
- snd_pcm_hw_params_set_access(handle,SND_PCM_ACCESS_RW_INTERLEAVED);
- 设置量化参数
- snd_pcm_format_t pcm_format =SND_PCM_FORMAT_S16_LE;
- SND_PCM_FORMAT_S8 有符号 8 位
SND_PCM_FORMAT_U8 无符号 8 位
SND_PCM_FORMAT_S16_LE 有符号 16 位,小端序 SND_PCM_FORMAT_S16_BE 有符号 16 位,大端序 SND_PCM_FORMAT_U16_LE 无符号 16 位,小端序 SND_PCM_FORMAT_U16_BE 无符号 16 位,大端序
SND_PCM_FORMAT_S24_LE 有符号 24 位,小端序 SND_PCM_FORMAT_S24_BE 有符号 24 位,大端序 SND_PCM_FORMAT_U24_LE 无符号 24 位,小端序 SND_PCM_FORMAT_U24_BE 无符号 24 位,大端序 SND_PCM_FORMAT_S32_LE 有符号 32 位,小端序 SND_PCM_FORMAT_S32_BE 有符号 32 位,大端序 SND_PCM_FORMAT_U32_LE 无符号 32 位,小端序 SND_PCM_FORMAT_U32_BE 无符号 32 位,大端序
- SND_PCM_FORMAT_S8 有符号 8 位
- snd_pcm_hw_params_set_format(handle, hwparams, pcm_format);
- snd_pcm_format_t pcm_format =SND_PCM_FORMAT_S16_LE;
- 设置音轨数目(本例中设置为双音轨,即立体声,1 为单音轨)
- uint16_t channels = 2;
- snd_pcm_hw_params_set_channels(handle, hwparams, channels);
- 设置采样频率,设备所支持的采用频率有规定的数值,本例中以 exact_rate 为基准,设置一个尽量接近该值的频率
- uint32_t exact_rate = 44100;
- snd_pcm_hw_params_set_rate_near(handle, hwparams, &exact_rate, 0);
- 采样频率的设置函数名字为 snd_pcm_hw_params_set_rate_near(); 从名字可 以看出一些端倪:声卡并非可以支持随意设置的采样频率,该函数的功能是,以 exac_rate 为基准,设置一个最接近该值的采样频率,再将最终的实际频率写入 exac_rate 中
- 设置 buffer_size 为声卡支持最大值(也可以设定为其他的值)
- snd_pcm_uframes_t buffer_size;
- snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size);
- snd_pcm_hw_params_set_buffer_size_near(handle, hwparams,&buffer_size);
- 根据 buffer_size 设置 period_size(比如将 period_size 设置为 buffer_size 的四分之一)
- snd_pcm_uframes_t period_size = buffer_size / 4;
- snd_pcm_hw_params_set_period_size_near(handle, hwparams,&period_size, 0);
- ALSA 系统中的 buffer 实际上是一个环形循环队列,可以被分割成若干个 period, 每当一个 period 被填满,则触发一个就绪事件或者一个 SIGIO 信号
- 安装这些 PCM 设备参数
- snd_pcm_hw_params(handle, hwparams);
- 从 PCM 设备中读取音频数据了,由于采用了直接 IO 方式的 帧连续的交错模式
- snd_pcm_readi(handle, p, frames);
代码
head4audio.h
1 #ifndef _HEAD4AUDIO_H_
2 #define _HEAD4AUDIO_H_
3
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <malloc.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <getopt.h>
12 #include <fcntl.h>
13 #include <ctype.h>
14 #include <errno.h>
15 #include <limits.h>
16 #include <time.h>
17 #include <locale.h>
18 #include <sys/unistd.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <alsa/asoundlib.h>
22
23 #define WAV_FMT_PCM 0x0001
24
25 #define MIN(a, b) \
26 ({ \
27 typeof(a) _a = a; \
28 typeof(b) _b = b; \
29 (void)(_a == _b); \
30 _a < _b ? _a : _b; \
31 })
32
33 typedef long long off64_t;
34
35 // ==================================== //
36
37 // 1: RIFF 块
38 struct wav_header
39 {
40 uint32_t id;// 固定为'RIFF'
41 uint32_t size; // 除了 id 和 size 之外,整个 WAV 文件的大小
42 uint32_t format;// fmt chunk 的格式,此处为'WAVE'
43 };
44
45 // 2: fmt 块
46 struct wav_fmt
47 {
48 uint32_t fmt_id; // 固定为'fmt '
49 uint32_t fmt_size; // 在 fmt 块的大小,固定为 16 字节
50 uint16_t fmt; // data 块中数据的格式代码
51 uint16_t channels; // 音轨数目:1 为单音轨,2 为立体声
52 uint32_t sample_rate; // 采样频率
53 uint32_t byte_rate; // 码率 = 采样率 * 帧大小
54 uint16_t block_align; // 帧大小 = 音轨数 * 量化级/8
55 uint16_t bits_per_sample; // 量化位数:典型值是 8、16、32
56 };
57
58 // 3: the data chunk
59 struct wav_data
60 {
61 uint32_t data_id; // 固定为'data'
62 uint32_t data_size; // 除了 WAV 格式头之外的音频数据大小
63 };
64
65 typedef struct
66 {
67 struct wav_header head;
68 struct wav_fmt format;
69 struct wav_data data;
70
71 }wav_format;
72
73 // ===================================== //
74
75 typedef struct
76 {
77 snd_pcm_t *handle; // PCM 设备操作句柄
78 snd_pcm_format_t format; // 数据格式
79
80 uint16_t channels;
81 size_t bits_per_sample; // 一个采样点内的位数(8 位、16 位)
82 size_t bytes_per_frame; // 一个帧内的字节个数
83
84 snd_pcm_uframes_t frames_per_period; // 一个周期内的帧个数
85 snd_pcm_uframes_t frames_per_buffer; // 系统 buffer 的帧个数
86
87 uint8_t *period_buf; // 存放从 WAV 文件中读取的一个周期的数据
88
89 }pcm_container;
90
91 #endi
capture.c
1 #include "head4audio.h"
2
3 // 根据本系统的具体字节序处理的存放格式
4 #if __BYTE_ORDER == __LITTLE_ENDIAN
5
6 #define RIFF ('F'<<24 | 'F'<<16 | 'I'<<8 | 'R'<<0)
7 #define WAVE ('E'<<24 | 'V'<<16 | 'A'<<8 | 'W'<<0)
8 #define FMT (' '<<24 | 't'<<16 | 'm'<<8 | 'f'<<0)
9 #define DATA ('a'<<24 | 't'<<16 | 'a'<<8 | 'd'<<0)
10
11 #define LE_SHORT(val) (val)
12 #define LE_INT(val) (val)
13
14 #elif __BYTE_ORDER == __BIG_ENDIAN
15
16 #define RIFF ('R'<<24 | 'I'<<16 | 'F'<<8 | 'F'<<0)
17 #define WAVE ('W'<<24 | 'A'<<16 | 'V'<<8 | 'E'<<0)
18 #define FMT ('f'<<24 | 'm'<<16 | 't'<<8 | ' '<<0)
19 #define DATA ('d'<<24 | 'a'<<16 | 't'<<8 | 'a'<<0)
20
21 #define LE_SHORT(val) bswap_16(val)
22 #define LE_INT(val) bswap_32(val)
23
24 #endif
25
26 #define DURATION_TIME 3
27
28 // 准备WAV格式的参数并填充到一个指定的wav_format结构体中
29 void prepare_wav_params(wav_format *wav)
30 {
31 wav->format.fmt_id = FMT;//设置 WAV 格式中的格式标识符为宏定义 FMT
32 wav->format.fmt_size = LE_INT(16);//设置 WAV 格式中的格式大小为 16
33 wav->format.fmt = LE_SHORT(WAV_FMT_PCM);//设置 WAV 格式为 PCM 格式
34 wav->format.channels = LE_SHORT(2); // 设置音频文件的声道数为 2
35 wav->format.sample_rate = LE_INT(44100); // 设置音频文件的采样频率为 44100 Hz
36 wav->format.bits_per_sample = LE_SHORT(16); // 设置音频文件的量化位数为 16 位
37 wav->format.block_align = LE_SHORT(wav->format.channels// 计算并设置音频文件的块对齐
38 * wav->format.bits_per_sample/8);// 计算并设置音频文件的字节率
39 wav->format.byte_rate = LE_INT(wav->format.sample_rate
40 * wav->format.block_align);
41 wav->data.data_id = DATA;// 设置 WAV 数据块的标识符为宏定义 DATA
42 wav->data.data_size = LE_INT(DURATION_TIME// 设置 WAV 数据块的大小为录制时长(DURATION_TIME)乘以字节率
43 * wav->format.byte_rate);
44 wav->head.id = RIFF;//设置 WAV 文件头的标识符为宏定义 RIF
45 wav->head.format = WAVE;//设置 WAV 文件头的格式为宏定义 WAV
46 wav->head.size = LE_INT(36 + wav->data.data_size);//设置 WAV 文件头的大小为 36 加上数据块大小
47 }
48
49 // 设置 WAV 格式参数
50 void set_wav_params(pcm_container *sound, wav_format *wav)
51 {
52 // 1:定义并分配一个硬件参数空间
53 snd_pcm_hw_params_t *hwparams;
54 snd_pcm_hw_params_alloca(&hwparams);
55
56 // 2:初始化硬件参数空间
57 snd_pcm_hw_params_any(sound->handle, hwparams);
58
59 // 3:设置访问模式为交错模式(即帧连续模式)
60 snd_pcm_hw_params_set_access(sound->handle, hwparams,
61 SND_PCM_ACCESS_RW_INTERLEAVED);
62 // 4:设置量化参数
63 snd_pcm_format_t pcm_format=SND_PCM_FORMAT_S16_LE;
64 snd_pcm_hw_params_set_format(sound->handle, 65 hwparams, pcm_format);
66 sound->format = pcm_format;
67
68 // 5:设置音轨数目
69 snd_pcm_hw_params_set_channels(sound->handle,
70 hwparams, LE_SHORT(wav->format.channels));
71 sound->channels = LE_SHORT(wav->format.channels);
72
73 // 6:设置采样频率
74 // 注意:最终被设置的频率被存放在来 exact_rate 中
75 uint32_t exact_rate = LE_INT(wav->format.sample_rate);
76 snd_pcm_hw_params_set_rate_near(sound->handle, 77 hwparams, &exact_rate, 0);
78
79 // 7:设置 buffer size 为声卡支持的最大值
80 snd_pcm_uframes_t buffer_size;
81 snd_pcm_hw_params_get_buffer_size_max(hwparams, 82 &buffer_size);
83 snd_pcm_hw_params_set_buffer_size_near(sound->handle, 84 hwparams, &buffer_size);
85
86 // 8:根据 buffer size 设置 period size
87 snd_pcm_uframes_t period_size = buffer_size / 4;
88 snd_pcm_hw_params_set_period_size_near(sound->handle, 89 hwparams, &period_size, 0);
90
91 // 9:安装这些 PCM 设备参数
92 snd_pcm_hw_params(sound->handle, hwparams);
93
94 // 10:获取 buffer size 和 period size
95 // 注意:他们均以 frame 为单位 (frame = 音轨数 * 量化级)
96 snd_pcm_hw_params_get_buffer_size(hwparams,
97 &sound->frames_per_buffer);
98 snd_pcm_hw_params_get_period_size(hwparams,
99 &sound->frames_per_period, 0);
100
101 // 11:保存一些参数
102 sound->bits_per_sample =
103 snd_pcm_format_physical_width(pcm_format);
104 sound->bytes_per_frame =
105 sound->bits_per_sample/8 * wav->format.channels;
106
107 // 12:分配一个周期数据空间
108 sound->period_buf =
109 (uint8_t *)calloc(1, 110 sound->frames_per_period * sound->bytes_per_frame);
111 }
112 // 从 PCM 设备中读取音频数据并存储到指定的缓冲区中
113 snd_pcm_uframes_t read_pcm_data(pcm_container *sound,
114 snd_pcm_uframes_t frames)
115 {
116 snd_pcm_uframes_t exact_frames = 0;//记录实际读取的帧数
117 snd_pcm_uframes_t n = 0;
118
119 uint8_t *p = sound->period_buf;
120 while(frames > 0)
121 {
122 n = snd_pcm_readi(sound->handle, p, frames);//从 PCM 设备中读取音频数据,将数据存储到缓冲区 p 中,读取的帧数由 frames 参数指定
123
124 frames -= n;//减去已读取的帧数 n,以便确定还需要读取多少帧
125 exact_frames += n;//将已读取的帧数 n 累加到 exact_frames 变量中
126 p += (n * sound->bytes_per_frame);//移动指针 p,使其指向下一个可存储数据的位置
127 }
128
129 return exact_frames;
130 }
131
132 // 从 PCM 设备录取音频数据并写入到指定的文件描述符中
133 void recorder(int fd, pcm_container *sound, wav_format *wav)
134 {
135 // 1:写 WAV 格式的文件头 将 WAV 文件头、格式块和数据块写入到指定文件描述符中。使用 write 函数将结构体的内容写入文件
136 write(fd, &wav->head, sizeof(wav->head));
137 write(fd, &wav->format, sizeof(wav->format));
138 write(fd, &wav->data, sizeof(wav->data));
139
140 // 2:写 PCM 数据
141 uint32_t total_bytes = wav->data.data_size;//记录要写入的 PCM 数据总字节数 即数据块的大小
142 // 当还有未写入的 PCM 数据时执行循环体
143 while(total_bytes > 0)
144 {
145 uint32_t total_frames =
146 total_bytes / (sound->bytes_per_frame);//计算本次循环要写入的 PCM 帧数
147 snd_pcm_uframes_t n =
148 MIN(total_frames, sound->frames_per_period);
149
150 uint32_t frames_read = read_pcm_data(sound, n);//从 PCM 设备中读取音频数据,返回实际读取的帧数
151 write(fd, sound->period_buf,
152 frames_read * sound->bytes_per_frame);//从 PCM 设备读取的数据写入到指定文件描述符中
153 total_bytes -=
154 (frames_read * sound->bytes_per_frame);//更新剩余要写入的字节数
155 }
156 }
157
158 int main(int argc, char **argv)
159 {
160 if(argc != 2)
161 {
162 printf("Usage: %s <wav-file>\n", argv[0]);
163 exit(1);
164 }
165
166 // 1:打开 WAV 格式文件
167 int fd = open(argv[1], O_CREAT|O_WRONLY|O_TRUNC, 0777);
168
169 // 2: 打开 PCM 设备文件
170 pcm_container *sound = calloc(1, sizeof(pcm_container));
171 snd_pcm_open(&sound->handle, "default",
172 SND_PCM_STREAM_CAPTURE, 0);//打开默认的PCM设备文件以捕获音频数据
173
174 // 3: 准备并设置 WAV 格式参数
175 wav_format *wav = calloc(1, sizeof(wav_format));
176 prepare_wav_params(wav);
177 set_wav_params(sound, wav);
178
179 // 4: 开始从 PCM 设备"plughw:0,0"录制音频数据
180 // 并且以 WAV 格式写到 fd 中
181 recorder(fd, sound, wav);
182
183 // 5: 释放相关资源
184 snd_pcm_drain(sound->handle);
185 close(fd);
186 snd_pcm_close(sound->handle);
187 free(sound->period_buf);
188 free(sound);
189 free(wav);
191 return 0;
192 }
播放音频
播放一个音频文件基本上跟录制一个音频文件是相反的过程,总体思路也不复杂:先检 查播放文件的格式,比如 wav、mp3、……等,然后根据具体的音频文件的格式以及音频 信息(比如采样频率、音轨数目等)设置音频设备参数,然后从音频文件读取数据写入音频 设备中。 如果播放器要支持各种音频格式文件,就必须考虑各种文件的详细格式。为了说明问题, 下面以 wav 格式作为例子,
步骤
- 准备好保存文件信息以及处理音频设备的结构体
- wav_format *wav = calloc(1, sizeof(wav_format));
- pcm_container *playback = calloc(1, sizeof(pcm_container));
- 此后,使用 wav 来保存即将要读取的音频文件的信息,根据这些信息我们可以判断该 文件是否是所支持的 wav 格式,也能根据其音频参数来设置音频设备
- 获取音频文件的格式信息(假设该音频文件名为 test.wav)
- int fd = open(“test.wav”, O_RDONLY);
- get_wav_header_info(fd, wav);
- 其中,get_wav_header_info()函数负责判断文件格式并收集格式信息
- 根据 get_wav_header_info()函数所收集的信息,设置音频设备
- snd_pcm_open(&playback->handle, "default",SND_PCM_STREAM_PLAYBACK, 0);
- set_params(playback, wav);
- 注意到,在 snd_pcm_open()中使用了 SND_PCM_STREAM_PLAYBACK,即将音 频流的方向设置为回放
- 将 test.wav 中的除文件格式信息之外的音频数据读出,并写入音频设备
- play_wav(playback, wav, fd);
- 最后,妥善地结束写入操作,并释放相关的内存资源
代码
获取并判断音频文件格式信息的函数实现
164 int check_wav_format(wav_format *wav) // 判断音频格式是否合法
165 {
166 if (wav->head.id!= RIFF ||
167 wav->head.format!= WAVE ||
168 wav->format.fmt_id!= FMT ||
169 wav->format.fmt_size != LE_INT(16) ||
170 (wav->format.channels != LE_SHORT(1) &&
171 wav->format.channels != LE_SHORT(2)) ||
172 wav->data.data_id!= DATA)
173 {
174 fprintf(stderr, "non standard wav file.\n");
175 return -1;
176 }
177
178 return 0;
179 }
180
181
182 int get_wav_header_info(int fd, wav_format *wav) //获取格式信息
183 {
184 int n1 = read(fd, &wav->head, sizeof(wav->head));
185 int n2 = read(fd, &wav->format, sizeof(wav->format));
186 int n3 = read(fd, &wav->data, sizeof(wav->data));
187
188 if(n1 != sizeof(wav->head) ||
189 n2 != sizeof(wav->format) ||
190 n3 != sizeof(wav->data))
191 {
192 fprintf(stderr, "get_wav_header_info() failed\n");
193 return -1;
194 }
195
196 if(check_wav_format(wav) < 0)
197 return -1;
198
199 return 0;
200 }
根据格式信息,设置音频设备参数的完整实现代码
97 int set_params(pcm_container *pcm, wav_format *wav)
98 {
99 snd_pcm_hw_params_t *hwparams;
100 uint32_t buffer_time, period_time;
101
102 // A) 分配参数空间
103 // 以 PCM 设备能支持的所有配置范围初始化该参数空间
104 snd_pcm_hw_params_alloca(&hwparams);
105 snd_pcm_hw_params_any(pcm->handle, hwparams);
106
107 // B) 设置访问方式为“帧连续交错方式”
108 snd_pcm_hw_params_set_access(pcm->handle, hwparams,
109 SND_PCM_ACCESS_RW_INTERLEAVED);
110
111 // C) 根据 WAV 文件的格式信息,设置量化参数
112 snd_pcm_format_t format;
113 get_bits_per_sample(wav, &format);
114 snd_pcm_hw_params_set_format(pcm->handle, hwparams,
115 format);
116 pcm->format = format;
117
118 // D) 根据 WAV 文件的格式信息,设置声道数
119 snd_pcm_hw_params_set_channels(pcm->handle, hwparams,
120 LE_SHORT(wav->format.channels));
121 pcm->channels = LE_SHORT(wav->format.channels);
122
123 // E) 根据 WAV 文件的格式信息,设置采样频率
124 // 如果声卡不支持 WAV 文件的采样频率,则
125 // 选择一个最接近的频率
126 uint32_t exact_rate = LE_INT(wav->format.sample_rate);
127 snd_pcm_hw_params_set_rate_near(pcm->handle,
128 hwparams, &exact_rate, 0);
129
130 // F) 设置 buffer 大小为声卡支持的最大值
131 // 并将处理周期设置为 buffer 的 1/4 的大小
132 snd_pcm_hw_params_get_buffer_size_max(hwparams,
133 &pcm->frames_per_buffer);
134
135 snd_pcm_hw_params_set_buffer_size_near(pcm->handle,
136 hwparams, &pcm->frames_per_buffer);
137
138 pcm->frames_per_period = pcm->frames_per_buffer / 4;
139 snd_pcm_hw_params_set_period_size(pcm->handle,
140 hwparams, pcm->frames_per_period, 0);
141 snd_pcm_hw_params_get_period_size(hwparams,
142 &pcm->frames_per_period, 0);
143
144 // G) 将所设置的参数安装到 PCM 设备中
145 snd_pcm_hw_params(pcm->handle, hwparams);
146
147 // H) 由所设置的 buffer 时间和周期
148 // 分配相应的大小缓冲区
149 pcm->bits_per_sample =
150 snd_pcm_format_physical_width(format);
151 pcm->bytes_per_frame = pcm->bits_per_sample/8 *
152 LE_SHORT(wav->format.channels);
153 pcm->period_buf =
154 (uint8_t *)malloc(pcm->frames_per_period *
155 pcm->bytes_per_frame);
156
157 return 0;
158 }
将 wav 文件的音频内容写入 PCM 设备中
199 ssize_t read_pcm_from_wav(int fd, void *buf, size_t count)
200 {
201 ssize_t result = 0, res;
202
203 while(count > 0)
204 {
205 if ((res = read(fd, buf, count)) == 0)
206 break;
207 if (res < 0)
208 return result > 0 ? result : res;
209 count -= res;
210 result += res;
211 buf = (char *)buf + res;
212 }
213 return result;
214 }
215
216
217 void play_wav(pcm_container *pcm, wav_format *wav, int fd)
218 {
219 int load, ret;
220 off64_t written = 0;
221 off64_t c;
222 off64_t total_bytes = LE_INT(wav->data.data_size);
223
224 uint32_t period_bytes =
225 pcm->frames_per_period * pcm->bytes_per_frame;
226
227 load = 0;
228 while (written < total_bytes)
229 {
230 // 一次循环地读取一个完整的周期数据
231 do
232 {
233 c = total_bytes - written;
234 if (c > period_bytes)
235 c = period_bytes;
236 c -= load;
237
238 if (c == 0)
239 break;
240 ret = read_pcm_from_wav(fd, 241 pcm->period_buf + load, c);
242
243 if(ret < 0)
244 {
245 fprintf(stderr, "read() failed.\n");
246 exit(-1);
247 }
248
249 if (ret == 0)
250 break;
251 load += ret;
252 } while ((size_t)load < period_bytes);
253
254 /* Transfer to size frame */
255 load = load / pcm->bytes_per_frame;
256 ret = write_pcm_to_device(pcm, load);
257 if (ret != load)
258 break;
259
260 ret = ret * pcm->bytes_per_frame;
261 written += ret;
262 load = 0
263 }
264 }