电子说
文章目录
8 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类中成员函数的实现
8 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
在上述命令中./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
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)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
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
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
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 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
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); }
设置音量函数
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; }
获取当前音量函数
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); }
音量步进减少函数
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; }
音量步进增加函数
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; }
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(¶ms); //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; }
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; }; }
8.1.3 AlsaPlayback类中成员函数的实现
AlsaPlayback类的构造函数
AlsaPlayback::AlsaPlayback(const std::string &dev) : AlsaBase(dev) { if (!DEVICE_OPENED) open_device(); }
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; }
playback函数的实现
int AlsaPlayback::playback(const char *_input_buffer, const long input_buffer_size) const { int res = -1; char *input_buffer = const_cast(_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; }
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; }; }
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)); }
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; }
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; } } } 审核编辑 黄昊宇
全部0条评论
快来发表一下你的评论吧 !