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.mfbz.cn/a/600276.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

02-Fortran基础--Fortran操作符与控制结构

02-Fortran基础--Fortran操作符与控制结构 0 引言1 操作符1.1 数学运算符1.2 逻辑运算符1.3 关系运算符 2 控制流程2.1 条件结构2.2 循环结构2.3 分支结构 0 引言 运算符和控制流程对编程语言是必须的,Fortran的操作符和控制流程涉及到各种数学运算符、逻辑运算符以及控制结构。…

Backblaze发布2024 Q1硬盘故障质量报告-2

截至2024年第一季度末&#xff0c;我们正在跟踪279,572块正在运行的硬盘。硬盘型号在2024年第一季度末必须拥有500块或更多的硬盘&#xff0c;并在整个使用寿命期间累积超过100,000个硬盘工作日&#xff0c;达到这个条件的所有型号盘的故障率趋势表现如下&#xff1a; 除了三种…

Linux快速安装Nginx和重新添加模块

目录 一、Nginx快速安装1、下载Nginx2、配置Nginx模块 二、Ngnix重新编译和安装模块 一、Nginx快速安装 1、下载Nginx 直接进入Nginx官网下载Linux最新稳定版本&#xff0c;我之前下载的版本是1.23.0。 2、配置Nginx模块 下载完后我把源码压缩文件解压放在/opt/appl/nginx…

无卤素产品是什么?有什么作用?

无卤素产品&#xff0c;即在生产过程中完全不使用卤素元素——氟、氯、溴、碘等——的产品。 卤素元素&#xff0c;虽然在电子设备、材料等领域应用广泛&#xff0c;却也可能潜藏危害。其阻燃剂&#xff0c;一旦在产品生命周期结束后释放&#xff0c;将对土壤和水体造成污染&a…

参数配置不生效导致海思1151芯片TPC功率超大,引起性能恶化。

• 【Wi-Fi领域】【现网案例4】参数配置不生效导致海思1151芯片TPC功率超大&#xff0c;引起性能恶化。 【问题描述】XXX客户反馈OLT-HG8245W5-6T–Wi-Fi–WA8021V5-LAN-PC组网概率出现近距离测速只有20Mbps 【问题单】DTS2022101410914 【问题分析】 在客户反馈此问题后&#…

【MM32F3270 Micropython】pwm输出

文章目录 前言一、PWM脉宽调制技术介绍二、machine.PWM 类2.1 machine.PWM 类的构造对象2.2 PWM 对象初始化2.3 关闭PWM设备2.4 设置pwm的周期2.5 设置占空比 三、pwm示例代码总结 前言 MicroPython是一种精简的Python 3编程语言实现&#xff0c;旨在在微控制器和嵌入式系统上…

基于CLAHE算法的图像增强及评价

摘要&#xff1a; 本研究旨在探讨对比度限制自适应直方图均衡化&#xff08;CLAHE&#xff09;算法在数字图像处理中的应用。CLAHE算法通过在局部区域内进行直方图均衡化&#xff0c;有效地增强了图像的对比度&#xff0c;并在保持图像细节的同时避免了过度增强的问题。本文通过…

C语言判断字符旋转

前言 今天我们使用c语言来写代码来实现字符串选择的判断&#xff0c;我们来看题目 题目描述 写一个函数&#xff0c;判断一个字符串是否为另外一个字符串旋转之后的字符串。 例如&#xff1a;给定s1 AABCD和s2 BCDAA&#xff0c;返回1 给定s1abcd和s2ACBD&#xff0c;返回0. A…

关于获取邮件授权码

以网易邮箱为例: 第一步:登录之后点击设置 第二步:点击POP3/SMTP/IMAP 第三步:开启SMTP服务 开启哪个都可以 第四步: 扫描二维码开启服务 第五步: 使用手机扫面二维码发送短信 第六步: 得到授权码 将授权码写入配置文件

ADS基础教程10-多态性(动态模型选择)

目录 一、多态性定义二、操作步骤&#xff11;.模型建立&#xff12;.模型选择&#xff13;.执行仿真 一、多态性定义 ADS中支持一个Symbol中&#xff0c;可以同时存在多个子图。在仿真时可以动态选择不同的子图继续宁仿真。 二、操作步骤 &#xff11;.模型建立 在上一章A…

路飞吃桃递归问题

在写代码之前&#xff0c;补充两个知识点 1.C语言递归的模版 2.递归是怎么工作的 好!话不多说让我们开始吧&#xff1a; 我们知道路飞吃了n天&#xff0c;每次都是吃一半&#xff0b;1&#xff0c;知道最后一天&#xff0c;只有一个桃子了&#xff0c;所以就可以列出式子&…

抖音小店个人店和个体店有什么不同?区别问题,新手必须了解!

哈喽~我是电商月月 新手开抖音小店入驻时会发现&#xff0c;选择入驻形式时有三个选择&#xff0c;个人店&#xff0c;个体店和企业店 其中&#xff0c;个人店和个体店只差了一个字&#xff0c;但个人店不需要营业执照&#xff0c;是不是入驻时选择个人店会更好一点呢&#x…

『 Linux 』基础IO/文件IO (万字)

文章目录 &#x1f984; 什么是IO&#x1f984; 文件IO(库级别)&#x1f47e; 文件的打开与关闭&#x1f47e; 当前路径&#x1f47e; 文件的读写 &#x1f984; 标准输入输出流&#x1f984; 文件IO(系统级别)&#x1f47e; 文件的打开&#x1f47e; 文件的关闭&#x1f47e; …

独立开发,做的页面不好看?我总结了一些工具与方法

前言 我有时候会自己开发一些项目,但是不比在公司里面,自己开发项目的时候没有设计稿,所以做出来的页面比较难看。 开发了几个项目之后,我也总结了以下的一些画页面的资源或者方法,希望对大家有帮助~ 颜色&字体 这一部分主要参考的是antd的方案,主要包括颜色与字…

LeetCode算法题:8.字符串转换整数 (atoi)

请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串并丢弃无用的前导空格检查下一个字符&#xff08;假设还未到字符末…

NumPy及Matplotlib基本用法

NumPy及Matplotlib基本用法 导语NumPy导入与生成算术运算N维数组广播元素访问 Matplotlib简单图案绘制多函数绘制图像显示参考文献 导语 深度学习中经常需要对图像和矩阵进行操作&#xff0c;好在python提供了Numpy和Matplotlib库&#xff0c;前者类似一个已经定义的数组类&am…

【负载均衡在线OJ项目日记】编译与日志功能开发

目录 日志功能开发 常见的日志等级 日志功能代码 编译功能开发 创建子进程和程序替换 重定向 编译功能代码 日志功能开发 日志在软件开发和运维中起着至关重要的作用&#xff0c;目前我们不谈运维只谈软件开发&#xff1b;日志最大的作用就是用于故障排查和调试&#x…

国货美妆进入新纪元之际,毛戈平打好“高端牌”了吗?

当前&#xff0c;国内美妆市场的格局已发生较大变化。 一边是国际品牌的“退场”&#xff0c;据统计&#xff0c;2023年退出中国市场的海外美妆品牌有20多个&#xff1b;一边是国内美妆品牌正在迎来自己的时代。 根据魔镜洞察数据&#xff0c;2024年一季度&#xff0c;国货彩…

论文辅助笔记:Tempo之modules/lora.py

1 LoRALayer 基类 2 Linear 2.1 __init__ 2.2 reset_parameter & train 2.3 forward 3 MergeLinear 3.1__init__ enable_lora指定了哪些输出特征使用lora 3.2 reset_parameters & zero_pad & merge_AB 3.3 train & forward

纯血鸿蒙APP实战开发——底部面板嵌套列表滑动案例

介绍 本示例主要介绍了利用panel实现底部面板内嵌套列表&#xff0c;分阶段滑动效果场景。 效果图预览 使用说明 点击底部“展开”&#xff0c;弹出panel面板。在panel半展开时&#xff0c;手指向上滑动panel高度充满页面&#xff0c;手指向下滑动panel隐藏。在panel完全展开…
最新文章