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

news2024/11/27 2:18:41

目录

概念

采样

量化

编码

音频文件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.coloradmin.cn/o/1650716.html

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

相关文章

RK3576芯片规格,以及与RK3588对比

瑞芯微RK3576是一款高性能、低功耗的SoC&#xff08;系统级芯片&#xff09;处理器&#xff0c;适用于基于ARM的PC、边缘计算设备、个人移动互联网设备等多种应用场景。它采用Arm架构的八核心CPU&#xff0c;集成了GPU、MCU、NPU、VPU等多种计算核心&#xff0c;并具有丰富的外…

我的256天之创作纪念日

目录 时光 数据的一些变化 开心的事 憧憬 时光 自上次CSDN的消息推送&#xff0c;又一个128天过去了&#xff0c;整天的工作和生活都在忙忙碌碌中度过&#xff0c;每到能静下来片刻&#xff0c;都倍感珍惜。因为一些原因&#xff0c;能够陪伴家人的时间越来越少&#xff…

【LLM 论文】OpenAI 基于对比学习微调 LLM 得到嵌入模型

论文&#xff1a;Text and Code Embeddings by Contrastive Pre-Training ⭐⭐⭐⭐ OpenAI 一、论文速读 这篇论文基于大型生成式 LLM 通过对比学习来微调得到一个高质量的 text 和 code 的 embedding 模型。 训练数据的格式&#xff1a;是一堆 ( x i , y i ) (x_i, y_i) (x…

前端nginx(windows操作系统)学习配置开发验证

Nginx概述 Nginx 作为负载均衡在 Linux 系统上具备很好的并发性能&#xff0c;并且占用极小的内存。但是在 Windows 系统上并不支撑较高并发&#xff0c;所以在Windows系统上选用Nginx作为负载均衡&#xff0c;需要考虑并发情况。 若并发需求低于 300&#xff0c;部署集群仅以…

Spring的基本应用

概述&#xff1a;Spring是由Rod Johnson组织开发的一个分层的java SE/EE一站式的轻量级开源框架&#xff0c;以IOC(控制反转)和AOP&#xff08;面向切面&#xff09;为核心&#xff0c;的开发模式。 注&#xff1a;喜欢的朋友可以关注公众号“JAVA学习课堂”系统学习相关技术&a…

ElasticSearch知识点汇总

1、ES中的​​​​​​​倒排索引是什么。 倒排索引&#xff0c;是通过分词策略&#xff0c;形成了词和文章的映射关系表&#xff0c;这种词典映射表即为倒排索引 2、ES是如何实现master选举的。 选举过程主要包括以下几个步骤&#xff1a; 心跳检测&#xff1a; 每个节点…

【消息队列】MQ介绍

MQ MQ&#xff08;MessageQueue&#xff09;&#xff0c;中文是消息队列&#xff0c;就是存放消息的队列&#xff0c;也是下面提到的事件驱动架构中的Broker 同步调用的优点&#xff1a; 时效性强&#xff0c;可以立即得到结果 同步调用的问题&#xff1a; 耦合度高性能和吞吐…

基于 llama2 的提示词工程案例2

优化大型语言模型&#xff08;LLMs&#xff09; 优化大型语言模型&#xff08;LLMs&#xff09;中的提示词&#xff08;prompts&#xff09;是提高模型性能和输出相关性的重要手段。以下是一些优化提示词的方向&#xff1a; 明确性&#xff1a;确保提示词清晰明确&#xff0c;…

【Java 刷题记录】前缀和

前缀和 25. 一维前缀和 示例1&#xff1a; 输入&#xff1a; 3 2 1 2 4 1 2 2 3输出&#xff1a; 3 6import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner in new Scanner(S…

【强化学习】公平性Actor-Critic算法

Bringing Fairness to Actor-Critic Reinforcement Learning for Network Utility Optimization 阅读笔记 Problem FormulationLearning AlgorithmLearning with Multiplicative-Adjusted RewardsSolving Fairness Utility Optimization Evaluations 在网络优化问题中&#xff…

PyGame 文字显示问题及解决方法

在 Pygame 中显示文字时可能会遇到一些问题&#xff0c;例如文字显示不清晰、字体不正确或者文字位置不准确等。以下是一些常见的问题及其解决方法&#xff0c;具体情况可以看看情况。 1、问题背景 一位用户在使用 PyGame 库进行游戏开发时&#xff0c;遇到了一个问题&#xf…

[AI OpenAI-doc] 迁移指南 Beta

我们已经改变了助手 API 在 beta 的 v1 版本和 v2 版本之间工具和文件的工作方式。今天&#xff0c;通过 API&#xff0c;两个 beta 版本仍然可以访问&#xff0c;但我们建议尽快迁移到我们 API 的最新版本。我们将在 2024 年底之前废弃 beta 的 v1 版本。 如果您今天不使用助手…

套管外径测量仪 多尺寸型号 规格全可定制

套管&#xff08;bushing&#xff09;是一种将带电导体引入电气设备或穿过墙壁的一种绝缘装置。前者称为电器套管&#xff0c;后者称为穿墙套管。套管通常用在建筑地下室&#xff0c;是用来保护管道或者方便管道安装的铁圈。套管的分类有刚性套管、柔性防水套管、钢管套管及铁皮…

SpringBoot中HandlerInterceptor拦截器的构建详细教程

作用范围&#xff1a;拦截器主要作用于Spring MVC的DispatcherServlet处理流程中&#xff0c;针对进入Controller层的请求进行拦截处理。它基于Java的反射机制&#xff0c;通过AOP&#xff08;面向切面编程&#xff09;的思想实现&#xff0c;因此它能够访问Spring容器中的Bean…

Python-VBA函数之旅-property函数

目录 一、property函数的常见应用场景 二、property函数使用注意事项 三、如何用好property函数&#xff1f; 1、property函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a;神奇夜光杯-CSDN博客 一、prop…

【北京迅为】《iTOP-3588开发板源码编译手册》-第4章 Android12/Linux设备树简介

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

4.【Orangepi Zero2】Linux定时器(signal、setitimer),软件PWM驱动舵机(SG90)

Linux定时器&#xff08;signal、setitimer&#xff09;&#xff0c;软件PWM驱动舵机&#xff08;SG90&#xff09; signalsetitimer示例 软件PWM驱动舵机&#xff08;SG90&#xff09; signal 详情请看Linux 3.进程间通信&#xff08;shmget shmat shmdt shmctl 共享内存、si…

帆软报表实现填报报表

我们拿emp表举例 登记信息表 设计一个报表实现对emp表员工的登记 &#xff08;emp表为ORACLE自带用户scott下的一个表&#xff09; 首先&#xff0c;我们设计好填报界面&#xff0c;新建一个普通报表&#xff0c;将emp表中需要的输入一一回应填写进表中。 如下图所示&#xf…

代码随想录Day 40|Leetcode|Python|139.单词拆分 ● 关于多重背包,你该了解这些! ● 背包问题总结篇!

139.单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 解题思路&#xff1a; 确定dp数组含义…

如何去官网下载windows10操作系统iso镜像

文章目录 一、先从微软中国官网https://www.microsoft.com/zh-cn/进去二、然后按图示一步步点进去三、点击下载工具这个工具会帮你生成windows操作系统iso文件四、下载好后一步步按图示要求成功操作 一、先从微软中国官网https://www.microsoft.com/zh-cn/进去 二、然后按图示一…