深圳百问网科技有限公司 深圳百问网科技有限公司
  • Linux学习路线
  • RTOS学习路线
  • NXP系列

    • IMX6ULL PRO开发板
    • IMX6ULL MINI开发板
    • IMX6ULL RT Pi开发板
  • ST系列

    • STM32MP157 PRO开发板
  • Mstar系列

    • 东山PI壹号开发板
  • Samsung系列

    • Jz2440开发板
    • Tinty4412开发板
  • 全志系列

    • M1 PLUS开发板
  • 瑞芯微系列

    • RK3568开发板
  • Amlogic系列

    • VIM3 A311D开发板
  • 海思系列

    • Hi3516EV200
  • 百问网所有直播课程

    • Linux在线培训
    • 鸿蒙直播录像
    • 现场答疑讲座
  • 嵌入式视频第1季之快速入门

    • 第1篇:视频介绍 资料下载
    • 第2篇:环境搭建 linux基本操作
    • 第3篇:开发板使用手册
    • 第4篇:嵌入式linux应用开发基础
    • 第5篇:嵌入式linux驱动开发基础
    • 第6篇:项目实战
    • 第7篇:驱动大全
  • 嵌入式Linux应用开发

    • LVGL UI开发
    • MQTT开发
  • 鸿蒙系统开发

    • 最小系统移植+驱动开发
  • 安卓系统开发

    • 韦东山Android系统开发【第四期】
    • 专家-安卓系列教程之ROM
    • 专家-安卓系列教程之Camera
  • 嵌入式Linux经典系列教程

    • 2440系列之【第1期】【第2期】【第3期】等
  • STM32F103 PRO开发板
  • STM32F103 MINI开发板
  • ESP32-C3开发板
  • STM32F103学习路线
  • 项目视频
答疑论坛 (opens new window)
  • 公司简介
【旧版】资料下载中心 (opens new window)
  • Linux学习路线
  • RTOS学习路线
  • NXP系列

    • IMX6ULL PRO开发板
    • IMX6ULL MINI开发板
    • IMX6ULL RT Pi开发板
  • ST系列

    • STM32MP157 PRO开发板
  • Mstar系列

    • 东山PI壹号开发板
  • Samsung系列

    • Jz2440开发板
    • Tinty4412开发板
  • 全志系列

    • M1 PLUS开发板
  • 瑞芯微系列

    • RK3568开发板
  • Amlogic系列

    • VIM3 A311D开发板
  • 海思系列

    • Hi3516EV200
  • 百问网所有直播课程

    • Linux在线培训
    • 鸿蒙直播录像
    • 现场答疑讲座
  • 嵌入式视频第1季之快速入门

    • 第1篇:视频介绍 资料下载
    • 第2篇:环境搭建 linux基本操作
    • 第3篇:开发板使用手册
    • 第4篇:嵌入式linux应用开发基础
    • 第5篇:嵌入式linux驱动开发基础
    • 第6篇:项目实战
    • 第7篇:驱动大全
  • 嵌入式Linux应用开发

    • LVGL UI开发
    • MQTT开发
  • 鸿蒙系统开发

    • 最小系统移植+驱动开发
  • 安卓系统开发

    • 韦东山Android系统开发【第四期】
    • 专家-安卓系列教程之ROM
    • 专家-安卓系列教程之Camera
  • 嵌入式Linux经典系列教程

    • 2440系列之【第1期】【第2期】【第3期】等
  • STM32F103 PRO开发板
  • STM32F103 MINI开发板
  • ESP32-C3开发板
  • STM32F103学习路线
  • 项目视频
答疑论坛 (opens new window)
  • 公司简介
【旧版】资料下载中心 (opens new window)
  • 全新Linux系列教程

  • 直播视频

  • 鸿蒙最小系统开发

  • Linux应用开发

    • Framebuffer应用开发
    • 图像处理应用开发
    • 输入系统应用开发
    • Linux进程间通信应用开发
    • 线程编程应用开发
    • 网络编程应用开发
    • 摄像头V4L2编程应用开发
    • ALSA应用开发
      • 8.1 音频相关概念
        • 8.1.1 采样频率
        • 8.1.2 量化位数
      • 8.2 ALSA架构
        • 8.2.1 ALSA架构介绍
      • 8.3 移植ALSA库及工具
        • 8.3.1 ALSA库下载
        • 8.3.2 ALSA Lib编译
        • 8.3.3 ALSA Util编译
        • 8.3.4 ALSA库和工具移植入嵌入式平台
      • 8.4 ALSA的调试
        • 8.4.1 amixer
        • 8.4.2 aplay
        • 8.4.3 arecord
      • 8.5 常用接口说明
        • 8.5.1 PCM接口
      • 8.6 基于ALSA的音量控制程序设计
        • 8.6.1 程序设计
        • 8.6.2 AlsaVolume 类的定义
        • 8.6.3 AlsaVolume类中成员函数的实现
      • 8.7 ALSA基类的设计
        • 8.7.1 程序设计
        • 8.7.2 AlsaBase类中成员函数的实现
      • 8.8 基于ALSA音频的播放
        • 8.8.1 程序设计
        • 8.1.2 AlsaPlay类的定义
        • 8.1.3 AlsaPlayback类中成员函数的实现
      • 8.9 基于ALSA音频的录制
        • 8.9.1 程序设计
        • 8.9.2 AlsaPlay类的定义
        • 8.9.3 AlsaCapture类中成员函数的实现
    • GPIO编程应用开发
    • RTC应用开发
    • PWM编程应用开发
    • I2C编程应用开发
    • CAN编程应用开发
    • 存储设备应用开发
    • MQTT协议分析应用开发
  • GUI开发教程

  • LinuxVideo
  • Linux应用开发
100askTeam
2021-12-08

ALSA应用开发

# ALSA应用开发

# 8.1 音频相关概念

​ 音频信号是一种连续变化的模拟信号,但计算机只能处理和记录二进制的数字信号,由自然音源得到的音频信号必须经过一定的变换,成为数字音频信号之后,才能送到计算机中作进一步的处理。

​ 数字音频系统通过将声波的波型转换成一系列二进制数据,来实现对原始声音的重现,实现这一步骤的设备常被称为(A/D)。A/D转换器以每秒钟上万次的速率对声波进行采样,每个采样点都记录下了原始模拟声波在某一时刻的状态,通常称之为样本(sample),而每一秒钟所采样的数目则称为采样频率,通过将一串连续的样本连接起来,就可以在计算机中描述一段声音了。对于采样过程中的每一个样本来说,数字音频系统会分配一定存储位来记录声波的振幅,一般称之为采样分辩率或者采样精度,采样精度越高,声音还原时就会越细腻。

​ 数字音频涉及到的概念非常多,对于在Linux下进行音频编程的程序员来说,最重要的是7406解声音数字化的两个关键步骤:采样和量化。

  • 采样就是每隔一定时间就读一次声音信号的幅度,从本质上讲,采样是时间上的数字化。

  • 量化则是将采样得到的声音信号幅度转换为数字值,从本质上讲,量化则是幅度上的数字化。

# 8.1.1 采样频率

​ 采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数。采样频率的选择应该遵循奈奎斯特(Harry Nyquist)采样理论:如果对某一模拟信号进行采样,则采样后可还原的最高信号频率只有采样频率的一半,或者说只要采样频率高于输入信号最高频率的两倍,就能从采样信号系列重构原始信号。

​ 如上图所示 用40KHz的频率去采样20KHz的信号可以正确捕捉到原始信号。用30KHz的频率去采样20KHz的信号会出现混淆信号。

​ 一般重建音乐信号时采用的最低采样频率为44.1KHz。在许多高品质的系统中,采用的48KHz的采样频率。

系统 采样频率
电话 8000Hz
CD 44100Hz
专业音频 48000Hz
DVD音频 96000Hz

# 8.1.2 量化位数

​ 量化位数是对模拟音频信号的幅度进行数字化,它决定了模拟信号数字化以后的动态范围,常用的有8位、12位和16位。量化位越高,信号的动态范围越大,数字化后的音频信号就越可能接近原始信号,但所需要的存贮空间也越大。

​ 音频应用中常用的数字表示方法为脉冲编码调制(Pulse-Code-Modulated,PCM)信号。在这种表示方法中,每个采样周期用一个数字电平对模拟信号的幅度进行编码。得到的数字波形是一组采样自输入模拟波形的近似值。由于所有A/D转换器的分辨率都是有限的,所以在数字音频系统中,A/D转换器带来的量化噪声是不可避免的。

# 8.2 ALSA架构

​ ALSA全称是Advanced Linux Sound Architecture,中文音译是Linux高级声音体系。ALSA 是Linux内核2.6后续版本中支持音频系统的标准接口程序,由ALSA库、内核驱动和相关测 试开发工具组成,更好的管理Linux中音频系统。

​ 本小节将介绍ALSA的架构。

# 8.2.1 ALSA架构介绍

​ ALSA是Linux系统中为声卡提供驱动的内核组件。它提供了专门的库函数来简化相应应用程序的编写。相较于OSS的编程接口,ALSA的函数库更加便于使用。

​ 对应用程序而言ALSA无疑是一个更佳的选择,因为它具有更加友好的编程接口,并且完全兼容于OSS。

​ ALSA系统包括7个子项目:

  • 驱动包alsa-driver
  • 开发包alsa-libs
  • 开发包插件alsa-libplugins
  • 设置管理工具包alsa-utils
  • OSS接口兼容模拟层工具alsa-oss
  • 特殊音频固件支持包alsa-finnware
  • 其他声音相关处理小程序包alsa-tools

ALSA声卡驱动与用户空间体系结构交互如下图所示:

# 8.3 移植ALSA库及工具

移植ALSA主要是移植alsa-Ub和alsa-utils。

  • alsa-lib:用户空间函数库, 封装驱动提供的抽象接口, 通过文件libasound.so提供API给应用程序使用。

  • alsa-utils:实用工具包,通过调用alsa-lib实现播放音频(aplay)、录音(arecord) 等工具。

​ ALSA Util是纯应用层的软件,相当于ALSA设备的测试程序,ALSA-Lib则是支持应用API的中间层程序,ALSA-Util中的应用程序中会调用到ALSA-Lib中的接口来操作到我们的音频编解码芯片的寄存器,而lib中接口就是依赖于最底层驱动代码,因此移植ALSA程序的顺序就是先后移植Driver,Lib,Util。

# 8.3.1 ALSA库下载

​ ALSA首先需要在ALSA的官网上下载官网http://www.alsa-project.org下载alsa-lib和alsa-utils。

如上图所示我们下载的版本为:

  • alsa-lib-1.2.2.tar.bz2
  • alsa-utils-1.2.2.tar.bz2

# 8.3.2 ALSA Lib编译

​ ALSA Lib移植不需要修改源码,只需要重新编译库代码以支持自己的平台。

tar -xvf alsa-lib-1.0.27.2.tar.bz2 
cd alsa-lib-1.0.27.2  
CC=arm-none-linux-gnueabi-gcc
./configure --host=arm-linux  --prefix=/home/m/3rd/alsa/install/  
make  
make install 
1
2
3
4
5
6

​ 在上述命令中./configure配置的几个重要的配置选项解释如下:

  • --host指定编译器,这里指定为交叉编译器,运行本配置命令前务必保证编译器已经可以在Shell下可以直接执行了。

  • --prefix指定编译后文件的安装路径,这样安装命令就还会指定的这个目录中创建lib和include两个目录。

# 8.3.3 ALSA Util编译

​ ALSA Util可以生成用于播放,录制,配置音频的应用可执行文件,测试驱动代码时用处很大,编译过程如下:

tar -xvf alsa-utils-1.0.27.2.tar.bz2  
cd alsa-utils-1.0.27.2  
CC=arm-none-linux-gnueabi-gcc 
./configure --prefix=/home/m/3rd/alsa/install/ --host=arm-linux --with-alsa-inc-prefix=/home/m/3rd/alsa/install/include --with-alsa-prefix=/home/m/3rd/alsa/install/lib --disable-alsamixer --disable-xmlto --disable-nls  
make  
1
2
3
4
5

# 8.3.4 ALSA库和工具移植入嵌入式平台

​ ALSA库和测试工具的移植就是将相应库文件和可执行文件放在目标板上,以下文件 必须被拷贝至对应位置 :

(1)ALSA Lib文件,放在/lib/中。

(2)配置文件放在/usr/local/share中,与编译时指定的目录相同。

(3)测试应用文件,ALSA Util能产生aplay、amixer、arecord,我们可以把这些可执行文件放在/usr/sbin中。

(4)内核目录中保证有/dev/snd/目录,这个目录下存放controlC0,pcmC0D0,/usr/sbintimer,timer这些设备文件,如果这些设备文件已经在/dev目录下,可手动拷贝到/snd目录中。

​ 在LINUX系统中,每个设备文件都是文件。音频设备也是一样,它的设备文件被放在/dev/snd目录下,我们来看下这些设备文件:

ls /dev/snd -l
crw-rw----+ 1 root audio 116,  2 5月  19 21:24 controlC0     用于声卡的
crw-rw----+ 1 root audio 116,  4 6月   6 19:31 pcmC0D0c
crw-rw----+ 1 root audio 116,  3 6月  11 11:53 pcmC0D0p
crw-rw----+ 1 root audio 116, 33 5月  19 21:24 timer
1
2
3
4
5

(1)controlC0:音频控制设备文件,例如通道选择,混音,麦克风的控制等;

(2)pcmC0D0c:声卡0设备0的录音设备,c表示capter;

(3)pcmC0D0p:声卡0设备0的播音设备,p表示play;

(4)timer:定时器设置。

# 8.4 ALSA的调试

​ 本小节将着重讲解tinyalsa工具使用,tinyalsa 是 alsa-lib 的一个简化版。它提供了 pcm 和 control 的基本接口;没有太多太复杂的操作、功能。可以按需使用接口。 tinyalsa-utils 是基于 tinyalsa 的一些工具,下面对几个常用的工具作介绍。

# 8.4.1 amixer

​ 与 amixer 作用类似,用于操作 mixer control。

使用方法:

  • 常用选项
选项 功能
-D,--device 指定声卡设备, 默认使用card0
  • 常用命令
命令 功能
controls 列出指定声卡的所有控件
contents 列出指定声卡的所有控件的具体信息
get 获取指定控件的信息
set 设定指定控件的值

举例:

获取audiocodec声卡的所有控件名
amixer -Dhw:audiocodec controls
获取当前硬件音量
amixer -Dhw:audiocodec cget name='LINEOUT volume'
设置当前硬件音量
amixer -Dhw:audiocodec cget name='LINEOUT volume' 25 
1
2
3
4
5
6

# 8.4.2 aplay

​ aplay 是命令行的 ALSA 声卡驱动的播放工具,用于播放功能。 使用方法:

选项 功能
-D,--device 指定声卡设备, 默认使用 default
-l,--list-devices 列出当前所有声卡
-t,--file-type 指定播放文件的格式, 如 voc,wav,raw, 不指定的情况下会去读取文件头部作识别
-c,--channels 指定通道数
-f,--format 指定采样格式
-r,--rate 采样率
-d,--duration 指定播放的时间
--period-size 指定 period size
--buffer-size 指定 buffer size

举例:

aplay -Dhw:audiocodec /mnt/UDISK/test.wav
1

# 8.4.3 arecord

​ arecord 是命令行的 ALSA 声卡驱动的录音工具,用于录音功能。 使用方法:

选项 功能
-D,--device 指定声卡设备, 默认使用 default
-l,--list-devices 列出当前所有声卡
-t,--file-type 指定播放文件的格式, 如 voc,wav,raw, 不指定的情况下会去读取文件头部作识别
-c,--channels 指定通道数
-f,--format 指定采样格式
-r,--rate 采样率
-d,--duration 指定播放的时间
--period-size 指定 period size
--buffer-size 指定 buffer size

举例:

录制5s,通道数为2, 采样率为16000, 采样精度为16bit, 保存为wav文件
arecord -Dhw:audiocodec -f S16_LE -r 16000 -c 2 -d 5 /mnt/UDISK/test.wav
1
2

# 8.5 常用接口说明

​ 从代码角度体现了alsa-lib和alsa-driver及hardwared的交互关系。用户层的alsa-lib通过操作alsa-driver创建的设备文件/dev/snd/pcmC0D0p等对内核层进行访问。内核层的alsa-drivier驱动再经由sound core对硬件声卡芯片进行访问。

# 8.5.1 PCM接口

​ 为了方便操作访问, alsa-lib 中封装了相关接口, 通过 pcmCXDXp/pcmCXDXc 节点 (/dev/snd/pcmCXDXx) 去实现播放、录音功能。

​ 主要涉及到的接口:

函数名 解释
snd_pcm_open
snd_pcm_info
snd_pcm_hw_params_any
snd_pcm_hw_params_set_access
snd_pcm_hw_params_set_format
snd_pcm_hw_params_set_channels
snd_pcm_hw_params_set_rate_near
snd_pcm_hw_params_set_buffer_size_near
snd_pcm_hw_params
snd_pcm_sw_params_current
snd_pcm_sw_params
snd_pcm_readi
snd_pcm_writei
snd_pcm_close

​ 详细 pcm 接口说明请查阅:

https://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html

# 8.6 基于ALSA的音量控制程序设计

# 8.6.1 程序设计

  • 文件列表:
序号 文件名 描述
1 AlsaVolume.h 音量控制头文件
2 AlsaVolume.cpp 音量控制程序
  • 成员函数设计:
序号 函数名 参数 参数描述 函数描述
1 setMasterVolume long volume 音量值 设置音量
2 getCurrentVolume 无 无 获取当前音量
3 increaseVolume 无 无 单步减小音量接口函数
4 decreaseVolume 无 无 单步增加音量接口函数
  • 成员变量设计:
序号 成员变量名 类型 描述
1 _VOLUMECHANGE const float 音量调节步进大小
2 handle snd_mixer_t* Mixer handle
3 element_handle snd_mixer_elem_t* Mixer element handle
4 minVolume long 最小音量
5 maxVolume long 最大音量

# 8.6.2 AlsaVolume 类的定义

#pragma once
#include <alsa/asoundlib.h>
namespace rv1108_audio{
class AlsaVolume
{
  public:
    AlsaVolume();
    ~AlsaVolume();
    int setMasterVolume(long volume); 
    long getCurrentVolume();
    long increaseVolume();
    long decreaseVolume();
  protected:
    const float _VOLUMECHANGE = 5; 
  private:
    snd_mixer_t* handle = nullptr;
    snd_mixer_elem_t* element_handle = nullptr;
    long minVolume,maxVolume;
};
}// namespace rv1108_camera
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 8.6.3 AlsaVolume类中成员函数的实现

  • AlsaVolume类的构造函数
AlsaVolume::AlsaVolume()
{
    snd_mixer_selem_id_t* sid = NULL;
    const char* card = "default";
    const char* selem_name = "Playback";
    //1. 打开混音设备
    auto res = snd_mixer_open(&handle, 0);

    //2. attach HCTL to open mixer
    res = snd_mixer_attach(handle, card);

    //3. Register mixer simple element class.
snd_mixer_selem_register(handle, NULL, NULL);

    //4. 取得第一個 element,也就是 Master
snd_mixer_load(handle);

    //5. allocate an invalid snd_mixer_selem_id_t using standard alloca
snd_mixer_selem_id_alloca(&sid);

    //6. 设置元素ID的位置
snd_mixer_selem_id_set_index(sid, 0);

    //7. 设置元素ID的名字
    snd_mixer_selem_id_set_name(sid, selem_name);

    //8. 查找元素
    element_handle = snd_mixer_find_selem(handle, sid);

    res = snd_mixer_selem_get_playback_volume_range(element_handle, 
                                                                           &minVolume, 
                                                                           &maxVolume);
}
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
  • 设置音量函数
int AlsaVolume::setMasterVolume(long volume)
{
    long alsaVolume = volume * (maxVolume - minVolume) / 100 ;

    if(snd_mixer_selem_set_playback_volume_all(element_handle, alsaVolume) < 0){
        if(handle)
        snd_mixer_close(handle);
        return -1;
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
  • 获取当前音量函数
long AlsaVolume::getCurrentVolume()
{
    long alsaVolume;
if(snd_mixer_selem_get_playback_volume(element_handle, SND_MIXER_SCHN_MONO, &alsaVolume) < 0){
    if(handle)
        snd_mixer_close(handle);
        return -1;
      }
    return (alsaVolume*100)/(maxVolume - minVolume);
}
1
2
3
4
5
6
7
8
9
10
  • 音量步进减少函数
long AlsaVolume::decreaseVolume()
{
    long newVolume = 0;
if (getCurrentVolume() >= 0 + _VOLUMECHANGE) // check that we won't go below minimum volume
        newVolume = getCurrentVolume() - _VOLUMECHANGE;
    else
        newVolume = 0;
    setMasterVolume(newVolume);
    return newVolume;
}
1
2
3
4
5
6
7
8
9
10
  • 音量步进增加函数
long AlsaVolume::increaseVolume()
{
    long newVolume = 0;
if (getCurrentVolume() <= 100 - _VOLUMECHANGE) // check that we don't go above the max volume
        newVolume = getCurrentVolume() + _VOLUMECHANGE;
    else
        newVolume = 100;
    setMasterVolume(newVolume);
    return newVolume;
}
1
2
3
4
5
6
7
8
9
10

# 8.7 ALSA基类的设计

# 8.7.1 程序设计

  • 文件列表:
序号 文件名 描述
1 AlsaBase.h ALSA基类头文件
2 AlsaBase.cpp 基类的实现程序
  • public成员变量:
序号 成员变量名 类型 描述
1 rate int 码率
2 channels int 通道数
3 bits_per_frame mutable int 每帧数据大小
4 default_output_buffer_size int 默认输出缓存大小
5 frames snd_pcm_uframes_t 帧数
6 buffer_size snd_pcm_uframes_t 缓存大小
7 buffer_frames snd_pcm_uframes_t 缓存大小
8 period_size snd_pcm_uframes_t 时间段大小
9 period_frames snd_pcm_uframes_t
10 period_time unsigned int
11 buffer_time unsigned int
12 bits_per_sample size_t
  • protected成员变量:
序号 成员变量名 类型 描述
1 device const char *
2 handle snd_pcm_t *
3 params snd_pcm_hw_params_t *
4 format snd_pcm_format_t
5 access_type snd_pcm_access_t
6 DEVICE_OPENED bool
7 PARAMS_SETED bool

# 8.7.2 AlsaBase类中成员函数的实现

  • AlsaBase类的构造函数
AlsaBase::AlsaBase(const std::string &dev)
{
device = dev.c_str();
        rate = 8000;
        channels = 2;
        format = SND_PCM_FORMAT_S16_LE;
        access_type = SND_PCM_ACCESS_RW_INTERLEAVED;
        frames = 480;

        DEVICE_OPENED = false;
        PARAMS_SETED = false;

        bits_per_sample = snd_pcm_format_physical_width(format);
        bits_per_frame = (bits_per_sample >> 3) * channels;
        
        default_output_buffer_size = frames * bits_per_frame / 8; // in byte

        buffer_frames = frames * 8;
        buffer_time = 0;
        
        period_frames = buffer_frames / 4;
        period_time = 0;
}

AlsaBase::~AlsaBase()
{
    if (DEVICE_OPENED){
        if((err = snd_pcm_close(handle)) < 0){
            ;
        }else{
            ;
        }

    }
}

int AlsaBase::set_params()
{

    if (!DEVICE_OPENED)
        return -1;
    // 分配硬件参数空间
    snd_pcm_hw_params_alloca(&params);

    //1、以默认值填充硬件参数
    if ((err = snd_pcm_hw_params_any(handle, params)) < 0) {
        return err;
    }

    //2、 Restrict a configuration space to contain only real hardware rates.
    if ((err = snd_pcm_hw_params_set_rate_resample(handle, params, 0)) < 0) {
        return err;
    }

    //3、设置存取方式
    if ((err = snd_pcm_hw_params_set_access(handle, params, access_type)) < 0) {
        return err;
    }

    //4、设置格式,S16_LE等
    if ((err = snd_pcm_hw_params_set_format(handle, params, format)) < 0) {
        return err;
    }

    //5 设置通道
    if ((err = snd_pcm_hw_params_set_channels(handle, params, channels)) < 0) {
        return err;
    }

    //6 设置码率
    unsigned int rrate;
    rrate =rate;
    if ((err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, NULL)) < 0) 	   {
        return err;
    }
    //7
    if (buffer_time == 0 && buffer_frames == 0)
    {
        err = snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0);
        assert(err >= 0);
        if (buffer_time > 500000)
            buffer_time = 500000;
    }
    //8
    if (period_time == 0 && period_frames == 0)
    {
        if (buffer_time > 0)
        period_time = buffer_time / 4;
    else
        period_frames = buffer_frames / 4;
    }
    //9
    if (period_time > 0)
    {
        err = snd_pcm_hw_params_set_period_time_near(handle,
                                                     params,
                                                     &period_time,
                                                     0);
    }                                                 
    else
    {
        err = snd_pcm_hw_params_set_period_size_near(handle,
                                                     params,
                                                     &period_frames,
                                                     0);
    }                                                 
    assert(err >= 0);
    //10
    if (buffer_time > 0)
    {
        err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
                                                     &buffer_time,
                                                     0);
    }
    else
    {
        err = snd_pcm_hw_params_set_buffer_size_near(handle, params,
                                                     &buffer_frames);
    }
    assert(err >= 0);

    // 将参数写入设备
    if ((err = snd_pcm_hw_params(handle, params)) < 0)
    {
        return -1;
    }
    else
    {
        PARAMS_SETED = true;
    }
    snd_pcm_uframes_t t_buffer_frames;
    snd_pcm_hw_params_get_buffer_size(params, &t_buffer_frames);
    buffer_frames = t_buffer_frames;

    snd_pcm_uframes_t t_period_frames;
    snd_pcm_hw_params_get_period_size(params, &t_period_frames, 0);
    period_frames = t_period_frames;

    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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

# 8.8 基于ALSA音频的播放

# 8.8.1 程序设计

  • 文件列表
序号 文件名 描述
1 AlsaPlayback.h 音频播放控制头文件
2 AlsaPlayback.cpp 音频播放程序
  • 成员函数设计
序号 函数名 参数 参数描述 函数描述
1 playback const char *input_buffer
const long input_buffer_size
播放音频

# 8.1.2 AlsaPlay类的定义

#pragma once
#include "AlsaBase.h"
namespace rv1108_audio{
    
class AlsaPlayback : public AlsaBase
{
    public:
    AlsaPlayback(const std::string &dev);
~AlsaPlayback();

    int open_device();
    int playback(const char *input_buffer, const long input_buffer_size) const;
    private:
    int err;
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 8.1.3 AlsaPlayback类中成员函数的实现

  • AlsaPlayback类的构造函数
AlsaPlayback::AlsaPlayback(const std::string &dev) : AlsaBase(dev)
{
if (!DEVICE_OPENED)
	open_device();
}
1
2
3
4
5
int AlsaPlayback::open_device()
{
        if(snd_pcm_open(&handle,
                        device,
                        SND_PCM_STREAM_PLAYBACK,
                        0) < 0)
        {
            DEVICE_OPENED = false;
        }
        else
        {
            DEVICE_OPENED = true;
        }
        return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • playback函数的实现
int AlsaPlayback::playback(const char *_input_buffer, const long input_buffer_size) const
{
        int res = -1;
        char *input_buffer = const_cast<char *>(_input_buffer);
        long r = input_buffer_size / bits_per_frame * 8;
        AUDIO_DEV_LOCK;
        while (r > 0)
        {
                snd_pcm_wait(handle, 100);
                do
                {
                        res = snd_pcm_writei(handle, input_buffer, frames);
                        if (res == -EPIPE){
                                AUDIO_DEV_UNLOCK;
                                snd_pcm_prepare(handle);
                                continue;
                        }
                }while (res < 0);
                r -= err;
                input_buffer += res * bits_per_frame / 8;
        }
        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

# 8.9 基于ALSA音频的录制

# 8.9.1 程序设计

  • 文件列表
序号 文件名 描述
1 AlsaCapture.h 音频录制头文件
2 AlsaCapture.cpp 音频录制程序
  • 成员函数设计
序号 函数名 参数 参数描述 函数描述
1 capture 无 录制音频
  • 成员变量设计
序号 成员变量名 类型 描述
1 _VOLUMECHANGE const float 音量调节步进大小
2 handle snd_mixer_t* Mixer handle
3 element_handle snd_mixer_elem_t* Mixer element handle
4 minVolume long 最小音量
5 maxVolume long 最大音量

# 8.9.2 AlsaPlay类的定义

#pragma once
#include "AlsaBase.h"
namespace rv1108_audio{

class AlsaCapture : public AlsaBase
{
  public:
    // 输出数据缓存
    char *output_buffer;
    // 输出缓存大小
    unsigned int output_buffer_size;
    // int frames_to_read;
    // 用于返回已读的帧数
    int frames_readed;

    AlsaCapture(const std::string &dev);
    ~AlsaCapture();
    int open_device();
    int capture();
  private:
    int err;
};

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 8.9.3 AlsaCapture类中成员函数的实现

  • AlsaCapture类的构造函数
AlsaCapture::AlsaCapture(const std::string &dev) : AlsaBase(dev)
{
    if (!DEVICE_OPENED)
        open_device();
    if (!PARAMS_SETED)
        set_params();

    output_buffer_size = default_output_buffer_size;
    output_buffer = (char *)calloc(output_buffer_size, sizeof(char));
}
1
2
3
4
5
6
7
8
9
10
int AlsaCapture::open_device()
{
    if ((err = snd_pcm_open(&handle,
                            device,
                            SND_PCM_STREAM_CAPTURE,
                            0)) < 0)
    { 
        DEVICE_OPENED = false;
        return -1;
    }
    else
    {
        DEVICE_OPENED = true;
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • AlsaCapture类的构造函数
int AlsaCapture::capture()
{
    while (1)
    {
        int err;

        if ((frames_readed = snd_pcm_readi(handle, output_buffer, frames)) < 0)
        {
            // Overrun happened
            if (frames_readed == -EPIPE)
            {
                snd_pcm_prepare(handle);
                continue;
            }
            return -1;
        }
        else
        {
            return frames_readed;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
快来参与 帮助我们改进页面吧! (opens new window)
上次更新: 2021/12/09, 13:14:00
摄像头V4L2编程应用开发
GPIO编程应用开发

← 摄像头V4L2编程应用开发 GPIO编程应用开发→

| Copyright © 2021-2022 深圳百问网科技有限公司 | 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式