Linux Audio开发-ALSA使用内存映射(MMAP)的录音实现(Ubuntu22)
# ALSA使用内存映射(MMAP)的录音实现(Ubuntu22)
在 ALSA 驱动中,mmap 的实现是一个复杂但高效的过程,它允许用户空间应用程序直接访问内核空间的 DMA 缓冲区。但需要硬件支持。
# 检查录音设备是否支持mmap
运行:
arecord -D default --dump-hw-params | grep ACCESS
1
输出:
Recording WAVE 'stdin' : Signed 16 bit Little Endian, Rate 8000 Hz, Mono
HW Params of device "default":
--------------------
ACCESS: RW_INTERLEAVED
FORMAT: U8 S16_LE S16_BE S24_LE S24_BE S32_LE S32_BE FLOAT_LE FLOAT_BE MU_LAW A_LAW S24_3LE S24_3BE
SUBFORMAT: STD
SAMPLE_BITS: [8 32]
FRAME_BITS: [8 1024]
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
关键参数解读:
ACCESS: RW_INTERLEAVED
...
1
2
2
这表明:
- 只支持
RW_INTERLEAVED
:这是标准的读/写访问模式 - 不支持任何 MMAP 模式:包括
MMAP_INTERLEAVED
、MMAP_NONINTERLEAVED
或MMAP_COMPLEX
- 硬件/驱动限制:问题出在声卡驱动或硬件本身,不支持 DMA 缓冲区映射
当确认硬件支持mmap之后,再做接下来的步骤
# 录音示例(alsa_record_mmap.c)
#include <alsa/asoundlib.h>
#include <signal.h>
#include <stdio.h>
unsigned int SAMPLE_RATE = 44100;
#define CHANNELS 2
#define FORMAT SND_PCM_FORMAT_S16_LE
#define BUFFER_FRAMES 1024
volatile sig_atomic_t stop_recording = 0;
void handle_signal(int sig) {
printf("停止录音... \n");
stop_recording = 1;
}
int main() {
signal(SIGINT, handle_signal);
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
FILE *file = fopen("audio.raw", "wb");
// 打开默认录音设备
if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0) < 0) {
fprintf(stderr, "无法打开PCM设备\n");
return 1;
}
// 分配硬件参数结构体
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(handle, params);
// 设置参数 - 特别指定MMAP模式
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, params, FORMAT);
snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
snd_pcm_hw_params_set_rate_near(handle, params, &SAMPLE_RATE, 0);
// 设置缓冲区大小
snd_pcm_uframes_t buffer_size = BUFFER_FRAMES;
snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
// 应用参数
if (snd_pcm_hw_params(handle, params) < 0) {
fprintf(stderr, "无法设置参数\n");
return 1;
}
// 准备音频设备
if (snd_pcm_prepare(handle) < 0) {
fprintf(stderr, "无法准备设备\n");
return 1;
}
printf("开始录音(使用MMAP模式)... \n");
while (!stop_recording) {
// 获取指向MMAP内存区域的指针
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t offset;
snd_pcm_uframes_t frames = BUFFER_FRAMES;
// 开始MMAP访问
int status = snd_pcm_mmap_begin(handle, &areas, &offset, &frames);
if (status < 0) {
// 错误处理
status = snd_pcm_recover(handle, status, 0);
if (status < 0) {
fprintf(stderr, "MMAP访问错误: %s\n", snd_strerror(status));
break;
}
continue;
}
// 检查是否有可用数据
if (frames == 0) {
// 没有数据,等待或继续
usleep(1000);
snd_pcm_mmap_commit(handle, offset, 0);
continue;
}
// 获取实际数据的指针
unsigned char *data_ptr = (unsigned char*)areas[0].addr + (areas[0].first >> 3);
// 计算偏移量和步长(每个样本的字节数)
int sample_bytes = snd_pcm_format_physical_width(FORMAT) / 8;
data_ptr += offset * (areas[0].step >> 3);
// 将音频数据写入文件
size_t bytes_to_write = frames * CHANNELS * sample_bytes;
fwrite(data_ptr, 1, bytes_to_write, file);
// 提交MMAP访问
status = snd_pcm_mmap_commit(handle, offset, frames);
if (status < 0 || (snd_pcm_sframes_t)frames != status) {
// 处理提交错误
if (snd_pcm_recover(handle, status, 0) < 0) {
fprintf(stderr, "MMAP提交错误\n");
break;
}
}
}
// 清理
snd_pcm_drop(handle);
snd_pcm_close(handle);
fclose(file);
printf("录音完成!保存为audio.raw\n");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# 编译和运行
编译命令:
gcc alsa_record_mmap.c -o alsa_record_mmap -lasound
1
运行:
./alsa_record_mmap
1
按下Ctrl+C
停止录音,录音数据将保存为audio.raw
# 关键代码说明
设置MMAP访问模式:
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
1添加设备准备步骤:
if (snd_pcm_prepare(handle) < 0) { fprintf(stderr, "无法准备设备\n"); return 1; }
1
2
3
4MMAP数据处理流程:
// 1. 开始MMAP访问 snd_pcm_mmap_begin(handle, &areas, &offset, &frames); // 2. 计算数据指针和大小 unsigned char *data_ptr = ...; size_t bytes_to_write = ...; // 3. 处理数据(保存到文件) fwrite(data_ptr, 1, bytes_to_write, file); // 4. 提交MMAP访问 snd_pcm_mmap_commit(handle, offset, frames);
1
2
3
4
5
6
7
8
9
10
11
12指针计算:
// 计算物理内存地址: data_ptr = (unsigned char*)areas[0].addr + (areas[0].first >> 3); data_ptr += offset * (areas[0].step >> 3);
1
2
3
这些计算涉及内存对齐和字节偏移,是MMAP访问的核心.
编辑 (opens new window)
- 02
- Linux Audio开发-ALSA API列表08-17
- 03
- Linux Audio开发-介绍和名词解释08-17