linux学习:音视频编程+alsa声音架构

目录

概念

采样

量化

编码

音频文件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_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_hw_params_set_format(handle, hwparams, pcm_format);
  • 设置音轨数目(本例中设置为双音轨,即立体声,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 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/7652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode:盛最多水的容器

文章收录于LeetCode专栏 盛最多水的容器 给你n个非负整数a1&#xff0c;a2&#xff0c;…&#xff0c;an&#xff0c;每个数代表坐标中的一个点(i, ai) 。在坐标内画 n 条垂直线&#xff0c;垂直线i的两个端点分别为(i, ai) 和 (i, 0)。找出其中的两条线&#xff0c;使得它们与…

24上半年软考时间安排已出!

正在备考上半年软考考试的宝子们注意啦~ 软考官网已经发布了24上半年考试时间及科目安排&#xff0c;大家仔细查看&#xff0c;确认好考试时间&#xff0c;以防错过考试。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 通知原文如下&#xff1a;↓↓↓…

利用 MyNodeQuery 搭建三网延迟监控 实时监控 VPS 网络情况

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 MyNodeQuery 是一款简洁好用的探针&#xff0c;可以作为 NodeQuery 关闭后的替代品&#xff0c;功能也基本参照 NodeQuery&#xff0c;支持节点离线通知、节点排序、自定义 Ping 节点&#xff0c;还有网速…

关于YOLO8学习(六)安卓部署ncnn模型--图片检测

前文 关于YOLO8学习(一)环境搭建,官方检测模型部署到手机 关于YOLO8学习(二)数据集收集,处理 关于YOLO8学习(三)训练自定义的数据集 关于YOLO8学习(四)模型转换为ncnn 关于YOLO8学习(五)安卓部署ncnn模型–视频检测 简介 前文第五章,讲述了部署自定义模型后,进…

【牛客】Tokitsukaze and Average of Substring

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 前缀和。 开一个int类型的前缀和数组pre[30][N]&#xff08;pre[i][j]表示某字符转成的数字 i 在一段区间的前缀个数。因为字母表有‘a’~z…

Nessus 部署实验

一、下载安装https://www.tenable.com/downloads/nessus 安装好之后&#xff0c;Nessus会自动打开浏览器&#xff0c;进入到初始化选择安装界面&#xff0c;这里我们要选择 Managed Scanner 点击继续&#xff0c;下一步选择Tenable.sc 点击继续&#xff0c;设置用户名和密码 等…

HSP_06章_Python_函数

文章目录 P67 函数入门1. 基本语法2. 函数的调用3. 函数的注意事项和使用细节 P71 递归机制1. 基本介绍2.递归能解决什么问题3. 递归的重要规则 P72 函数作为参数传递P73 lambda匿名函数P74 全局变量和局部变量 P67 函数入门 1. 基本语法 2. 函数的调用 3. 函数的注意事项和使…

浅谈操作系统中的重要概念——线程(3)——设计模式

文章目录 一、什么是设计模式&#xff1f;二、单例模式2.1、饿汉模式2.2、懒汉模式2.3、多线程情况下调用 饿汉模式与懒汉模式 谁是安全的&#xff1f;&#xff1f;&#xff08;重点&#xff09; 三、工厂模式 一、什么是设计模式&#xff1f; 设计模式就相当于菜谱&#xff0…

虚拟机文件夹共享操作(本地访问)

新建一个文件夹 右击文件夹点击属性 找到共享 点击共享 选择本地用户共享就可以了 本地winr 输入我们图片中的格式&#xff08;IP前加 “\\” &#xff09; 会弹一个窗口&#xff0c;输入虚拟机的入户名和密码就可以共享了&#xff08;一般默认用户名都是administrator&am…

C++笔记:类与对象(三)->多态

多态 虚函数 先来看一段代码&#xff1a; #include<iostream> using namespace std;class Animal { public :void run() {cout << "I dont know how to run" << endl;} };class Cat : public Animal{ public :void run() {cout << "I …

【毕业设计】基于微信小程序的校园快递平台系统设计与实现

1.项目介绍 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统校园快递平台系统信息管理难度大&#xff0c;容错率…

让我们把Domino变成SFTP服务器

大家好&#xff0c;才是真的好。 远程共享文件有很多办法&#xff0c;其中值得注意的是SFTP方式。SFTP即SSH文件传输协议&#xff0c;通过使用SSH传输层&#xff0c;SFTP可以通过Internet连接安全地访问和移动大量数据文件。 今天我们就介绍使用Domino中的HTTP OSGI方式来实现…

如何自己快速的制作流程图?6个软件教你快速进行流程图制作

如何自己快速的制作流程图&#xff1f;6个软件教你快速进行流程图制作 自己制作流程图可以是项目管理、流程设计或教学展示中的重要环节。以下是六款常用的流程图制作软件&#xff0c;它们都提供了快速、简单的方式来制作流程图&#xff1a; 迅捷画图&#xff1a;这是一款非…

echarts学习笔记:柱状图+雷达图+双环形图+地图可视化+数据传递关系图+关键词条图+数据总览图+AntV/G2/DataV

GitHub - lgd8981289/imooc-visualization: https://www.bilibili.com/video/BV1yu411E7cm/?vd_source391a8dc379e0da60c77490e3221f097a 课程源码 国内echarts镜像站&#xff1a;ISQQW.COM x ECharts 文档&#xff08;国内同步镜像&#xff09; - 配置项 echarts图表集&…

618挑选家用洗地机,需要注意哪些事项?有哪些家用洗地机值得买?

近年来&#xff0c;智能清洁家电越来越受到消费者的欢迎&#xff0c;洗地机作为清洁家电的新宠&#xff0c;凭借其集扫地、拖地、杀菌清洗于一体的强大功能&#xff0c;成为市场上的热销产品。那么&#xff0c;这类洗地机真的好用吗&#xff1f;怎么挑选到好用的家用的洗地机呢…

win10下,svn上传.so文件失败

问题&#xff1a;win10下使用TortoiseSVN&#xff0c;svn上传.so文件失败 解决&#xff1a;右键&#xff0c;选择Settings&#xff0c;Global ignore pattern中删除*.so&#xff0c;保存即可。

设置多用户远程登录windows server服务器

##设置多用户远程登录windows server服务器 ###1、远程登录windows server 2016 运行—>mstsc—>远程IP地址—>用户和密码 2、远程windows服务器设置多用户策略 运行—>gpedit.msc->计算机配置—管理模板—windows组件—远程桌面服务—远程桌面会话主机----连…

阿里巴巴1688商品详情API返回值深度剖析:精准获取商品信息的关键

在电子商务日益繁荣的今天&#xff0c;阿里巴巴1688作为中国领先的B2B平台&#xff0c;汇聚了海量的供应商和商品信息。对于商家、开发者以及希望深入了解商品数据的用户来说&#xff0c;如何通过有效的方式获取这些商品信息成为了一个重要议题。阿里巴巴1688商品详情API的出现…

Semi-decentralized Federated Ego Graph Learning for Recommendation

论文概况 本文是2023年WWW的一篇联邦推荐论文&#xff0c;提出了一个半去中心化的联合自我图学习框架。 Introduction 作者提出问题 现有的推荐方法收集所有用户的自我图来组成一个全局图&#xff0c;导致隐私风险。联合推荐系统已被提出来缓解隐私问题&#xff0c;但在客户…

zabbix监控方式(zabbix-trapper)

中文&#xff1a;zabbix采集器&#xff0c;即zabbix sender 。 Zabbix-Trapper 监控方式可以一次批量发送数据给Zabbix Server&#xff0c;与主动模式不同&#xff0c;Zabbix-Trapper 可以让用户控制数据的发送&#xff0c;而不用Zabbix-Agent进程控制&#xff0c;这意味着可以…