长生栈 长生栈
首页
  • 编程语言

    • C语言
    • C++
    • Java
    • Python
  • 数据结构和算法

    • 全排列算法实现
    • 动态规划算法
  • CMake
  • gitlab 安装和配置
  • docker快速搭建wordpress
  • electron+react开发和部署
  • Electron-创建你的应用程序
  • ImgUI编译环境
  • 搭建图集网站
  • 使用PlantUml画时序图
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Living Team

编程技术分享
首页
  • 编程语言

    • C语言
    • C++
    • Java
    • Python
  • 数据结构和算法

    • 全排列算法实现
    • 动态规划算法
  • CMake
  • gitlab 安装和配置
  • docker快速搭建wordpress
  • electron+react开发和部署
  • Electron-创建你的应用程序
  • ImgUI编译环境
  • 搭建图集网站
  • 使用PlantUml画时序图
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 计算机视觉

  • ESP32开发

  • Linux系统移植

  • 快速开始

  • 多媒体(Mutimedia)

  • 音频开发(Audio)

    • Linux Audio开发-介绍和名词解释
    • Linux Audio开发-ALSA API列表
    • Linux Audio开发-使用ALSA驱动进行录音和播放(Ubuntu22)
    • Linux Audio开发-ALSA使用内存映射(MMAP)的录音实现(Ubuntu22)
      • 检查录音设备是否支持mmap
      • 录音示例(alsarecordmmap.c)
        • 编译和运行
        • 关键代码说明
  • 编程小知识

  • 技术
  • 音频开发(Audio)
DC Wang
2025-08-17
目录

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

关键参数解读:

ACCESS:  RW_INTERLEAVED
...
1
2

这表明:

  1. 只支持 RW_INTERLEAVED:这是标准的读/写访问模式
  2. 不支持任何 MMAP 模式:包括 MMAP_INTERLEAVED、MMAP_NONINTERLEAVED或 MMAP_COMPLEX
  3. 硬件/驱动限制:问题出在声卡驱动或硬件本身,不支持 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(&params);
    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

# 编译和运行

编译命令:

gcc alsa_record_mmap.c -o alsa_record_mmap -lasound
1

运行:

./alsa_record_mmap
1

按下Ctrl+C停止录音,录音数据将保存为audio.raw

# 关键代码说明

  1. 设置MMAP访问模式:

    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
    
    1
  2. 添加设备准备步骤:

    if (snd_pcm_prepare(handle) < 0) {
        fprintf(stderr, "无法准备设备\n");
        return 1;
    }
    
    1
    2
    3
    4
  3. MMAP数据处理流程:

    // 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
  4. 指针计算:

    // 计算物理内存地址:
    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)
#Audio
Linux Audio开发-使用ALSA驱动进行录音和播放(Ubuntu22)
YUV图片格式

← Linux Audio开发-使用ALSA驱动进行录音和播放(Ubuntu22) YUV图片格式→

最近更新
01
Linux Audio开发-使用ALSA驱动进行录音和播放(Ubuntu22)
08-17
02
Linux Audio开发-ALSA API列表
08-17
03
Linux Audio开发-介绍和名词解释
08-17
更多文章>
Theme by Vdoing | Copyright © 2019-2025 DC Wang All right reserved | 辽公网安备21029602001058号 | 吉ICP备20001966号-2
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式