音频基础
一、音频基础
声音是指压力波通过空气或者任何其他介质(例如气体、液体或者固体)传播的震动。
在数字音频系统中,麦克风将声音转换为模拟电信号,然后通常使用脉冲编码调制(PCM)的模数转换器(ADC)将模拟信号转换为数字信号。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。
对于人类而言,能接受声音的频率范围是20Hz-20KHz, 所以采样的频率44.1KHz 以及16bit的采样位数就可以有很好的保真能力(CD格式的采样率和采样位数)。常用的音频采样频率有8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等。
经常见到这样的描述: 44100HZ 16bit stereo 或者 22050HZ 8bit mono 等等。44100HZ 16bit stereo: 每秒钟有 44100 次采样, 采样数据用 16 位(2字节)记录, 双声道(立体声);22050HZ 8bit mono: 每秒钟有 22050 次采样, 采样数据用 8 位(1字节)记录, 单声道;
采样率(rate):每秒钟采样次数。声音信号在“模->数”转换过程中单位时间内采样的次数。
采样值:每一次采样周期内声音模拟信号的积分值。对于单声道声音文件,采样数据为8位的短整形;而对于双声道立体声音文件,每次采样数据为一个16位的整数,高8位(左声道)和低8位(右声道)分表代表两个声道。
采样位数:一个采样信号的位数,也是对采样精度的变现。
样本:采样点,样本是记录音频数据最基本的单位,常见的有8位和16位。
样本长度(sample):采样点个数,不同编码方式采样点不同,AAC为1024,mp3为1152。
通道数(channel):该参数为1表示单声道,2则是立体声。
桢(frame):桢记录了一个声音单元,其长度为样本长度与通道数的乘积。
周期(period):音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。
交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。而在非交错模式(planner模式)下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。
帧播放时间:一帧对应的采样样本的个数 / 采样频率(单位为s)。
采样率(sample rate)为 44100Hz,表示每秒 44100个采样点, 一帧的播放时间 = 1024 * 1000/44100= 23.22ms(单位为ms)
采样率为48000Hz,一帧的播放时间 = 1024 * 1000/48000= 21.33ms(单位为ms)。
反过来,当想通过音频缓冲多少ms来计算实际应缓冲多少个音频帧时,计算:
比如对48K缓冲300ms需要多少个buffer,
buffer = 一秒内能产生多少个音频帧(48000/1024) 乘以 时间比例(300/1000) = (48000*300)/(1024*1000) = 14.0625个。
音频帧率即1s多少帧或数据包,采样率/一帧采样样本个数,如48000/1024=46.875。
帧大小:一帧对应的采样样本的个数 * 通道数* 数据位数/8。AAC帧,采集数据为双通道,16位数据时,根据公式, 一帧数据量大小 = 1024*2 *16/8 = 4096。
在采集采样率和编码采样率相同的情况下(就是不需要重采样),通道数,数据位数一般不会改变,那么采集的数据大小和编码的数据大小是一样的。也就是说,当采集了4096个字节的数据后,再送去给编码器编码一帧AAC帧,不同的采样率只是会改变每秒钟的AAC帧的数量。
采样率(sample rate)为 44100Hz,1秒钟有:44100 / 1024 = 43.066帧
采样率为48000Hz,1秒钟有:48000 / 1024 = 48000 / 1024 = 46.875帧
16LE:“16”代表采样位数是16bit。由于1Byte=8bit,所以一个声道的一个采样值占用2Byte。“LE”,Little Endian,代表2 Byte采样值的存储方式为高位存在高地址中。
二、ALSA声音编程介绍
ALSA表示高级Linux声音体系结构(Advanced Linux Sound Architecture)。它由一系列内核驱动,应用程序编程接口(API)以及支持Linux下声音的实用程序组成。这篇文章里,将简单介绍 ALSA项目的基本框架以及它的软件组成。主要集中介绍PCM接口编程,包括您可以自动实践的程序示例。
您使用ALSA的原因可能就是因为它很新,但它并不是唯一可用的声音API。如果您想完成低级的声音操作,以便能够最大化地控制声音并最大化地提高性能,或者如果您使用其它声音API没有的特性,那么ALSA是很好的选择。如果您已经写了一个音频程序,你可能想要为ALSA声卡驱动添加本地支持。如果您对音频不感兴趣,只是想播放音频文件,那么高级的API将是更好的选择,比如SDL,OpenAL以及那些桌面环境提供的工具集。另外,您只能在有ALSA 支持的Linux环境中使用ALSA。
当一个声卡活动时,数据总是连续地在硬件缓存区和应用程序缓存区间传输。但是也有例外。在录音例子中,如果应用程序读取数据不够快,循环缓存区将会被新的数据覆盖。这种数据的丢失被称为overrun。在回放例子中,如果应用程序写入数据到缓存区中的速度不够快,缓存区将会"饿死"。这样的错误被称 为"underrun"。在ALSA文档中,有时将这两种情形统称为"XRUN"。适当地设计应用程序可以最小化XRUN并且可以从中恢复过来。
alsa-lib官网:https://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html
API: alsa-c
tutorial: A Tutorial on Using the ALSA Audio API 中文-苍月
alsa把录音(capture)和播放(playback)分别处理。
ALSA 是 Advanced Linux Sound Architecture 的缩写,目前已经成为了 Linux 的主流音频体系结构。它的主要特点有:① 支持多种声卡设备;② 模块化的内核驱动程序;③ 支持 SMP 和多线程;④ 提供应用开发函数库(alsa-lib)以简化应用程序开发;⑤ 支持 OSS API,兼容 OSS 应用程序等。
在内核设备驱动层,ALSA 提供了 alsa-driver,同时在应用层,ALSA 为我们提供了 alsa-lib,应用程序只要调用 alsa-lib 提供的 API,即可以完成对底层音频硬件的控制。
三、ALSA体系结构
ALSA API可以分解成以下几个主要的接口:
1 控制接口:提供管理声卡注册和请求可用设备的通用功能
2 PCM接口:管理数字音频回放(playback)和录音(capture)的接口。本文后续总结重点放在这个接口上,因为它是开发数字音频程序最常用到的接口。
3 Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器。这些API提供对声卡上MIDI总线的访问。这个原始接口基于MIDI事件工作,由程序员负责管理协议以及时间处理。
4 定时器(Timer)接口:为同步音频事件提供对声卡上时间处理硬件的访问。
5 时序器(Sequencer)接口
6 混音器(Mixer)接口
alsa数据结构
- snd_card 表示一个声卡实例, 包含多个声卡设备
- snd_device 表示一个声卡设备部件
- snd_pcm 表示一个 PCM 设备, 声卡设备的一种, 用于播放和录音
- snd_control 表示 Control 设备, 声卡设备的一种, 用于控制声卡
- snd_pcm_str 表示 PCM 流, 分为 Playback 和 Capture
- snd_pcm_substream PCM 子流, 用于音频的播放或录制
- snd_pcm_ops PCM 流操作集
示例1:获取PCM信息。
#include <alsa/asoundlib.h> int main() { int val; printf("ALSA library version: %s\n", SND_LIB_VERSION_STR); printf("\nPCM stream types:\n"); for (val = 0; val <= SND_PCM_STREAM_LAST; val++) printf(" %s\n", snd_pcm_stream_name((snd_pcm_stream_t)val)); printf("\nPCM access types:\n"); for (val = 0; val <= SND_PCM_ACCESS_LAST; val++) printf(" %s\n", snd_pcm_access_name((snd_pcm_access_t)val)); printf("\nPCM formats:\n"); for (val = 0; val <= SND_PCM_FORMAT_LAST; val++){ if (snd_pcm_format_name((snd_pcm_format_t)val)!= NULL) printf(" %s (%s)\n", snd_pcm_format_name((snd_pcm_format_t)val), snd_pcm_format_description((snd_pcm_format_t)val)); } printf("\nPCM subformats:\n"); for (val = 0; val <= SND_PCM_SUBFORMAT_LAST;val++) printf(" %s (%s)\n", snd_pcm_subformat_name((snd_pcm_subformat_t)val), snd_pcm_subformat_description((snd_pcm_subformat_t)val)); printf("\nPCM states:\n"); for (val = 0; val <= SND_PCM_STATE_LAST; val++) printf(" %s\n", snd_pcm_state_name((snd_pcm_state_t)val)); return 0; }
gcc test.c -Wall -lasound
ALSA library version: 1.1.3 PCM stream types: PLAYBACK CAPTURE PCM access types: MMAP_INTERLEAVED MMAP_NONINTERLEAVED MMAP_COMPLEX RW_INTERLEAVED RW_NONINTERLEAVED PCM formats: S8 (Signed 8 bit) U8 (Unsigned 8 bit) S16_LE (Signed 16 bit Little Endian) S16_BE (Signed 16 bit Big Endian) U16_LE (Unsigned 16 bit Little Endian) U16_BE (Unsigned 16 bit Big Endian) S24_LE (Signed 24 bit Little Endian) S24_BE (Signed 24 bit Big Endian) U24_LE (Unsigned 24 bit Little Endian) U24_BE (Unsigned 24 bit Big Endian) S32_LE (Signed 32 bit Little Endian) S32_BE (Signed 32 bit Big Endian) U32_LE (Unsigned 32 bit Little Endian) U32_BE (Unsigned 32 bit Big Endian) FLOAT_LE (Float 32 bit Little Endian) FLOAT_BE (Float 32 bit Big Endian) FLOAT64_LE (Float 64 bit Little Endian) FLOAT64_BE (Float 64 bit Big Endian) IEC958_SUBFRAME_LE (IEC-958 Little Endian) IEC958_SUBFRAME_BE (IEC-958 Big Endian) MU_LAW (Mu-Law) A_LAW (A-Law) IMA_ADPCM (Ima-ADPCM) MPEG (MPEG) GSM (GSM) SPECIAL (Special) S24_3LE (Signed 24 bit Little Endian in 3bytes) S24_3BE (Signed 24 bit Big Endian in 3bytes) U24_3LE (Unsigned 24 bit Little Endian in 3bytes) U24_3BE (Unsigned 24 bit Big Endian in 3bytes) S20_3LE (Signed 20 bit Little Endian in 3bytes) S20_3BE (Signed 20 bit Big Endian in 3bytes) U20_3LE (Unsigned 20 bit Little Endian in 3bytes) U20_3BE (Unsigned 20 bit Big Endian in 3bytes) S18_3LE (Signed 18 bit Little Endian in 3bytes) S18_3BE (Signed 18 bit Big Endian in 3bytes) U18_3LE (Unsigned 18 bit Little Endian in 3bytes) U18_3BE (Unsigned 18 bit Big Endian in 3bytes) G723_24 (G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes) G723_24_1B (G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte) G723_40 (G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes) G723_40_1B (G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte) DSD_U8 (Direct Stream Digital, 1-byte (x8), oldest bit in MSB) DSD_U16_LE (Direct Stream Digital, 2-byte (x16), little endian, oldest bits in MSB) DSD_U32_LE (Direct Stream Digital, 4-byte (x32), little endian, oldest bits in MSB) DSD_U16_BE (Direct Stream Digital, 2-byte (x16), big endian, oldest bits in MSB) DSD_U32_BE (Direct Stream Digital, 4-byte (x32), big endian, oldest bits in MSB) PCM subformats: STD (Standard) PCM states: OPEN SETUP PREPARED RUNNING XRUN DRAINING PAUSED SUSPENDED DISCONNECTED
示例2:读一个采样周期(32帧)数据,双声道,16位。
/* This example reads from the default PCM device and writes to standard output for 5 seconds of data. */ /* Use the newer ALSA API */ #define ALSA_PCM_NEW_HW_PARAMS_API #include <alsa/asoundlib.h> int main() { long loops; int rc; int size; snd_pcm_t *handle; snd_pcm_hw_params_t *params; unsigned int val; int dir; snd_pcm_uframes_t frames; char *buffer; /* Open PCM device for recording (capture). */ rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0); if (rc < 0) { fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc)); exit(1); } /* Allocate a hardware parameters object. */ snd_pcm_hw_params_alloca(¶ms); /* Fill it in with default values. */ snd_pcm_hw_params_any(handle, params); /* Set the desired hardware parameters. */ /* Interleaved mode */ snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); /* Signed 16-bit little-endian format */ snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); /* Two channels (stereo) */ snd_pcm_hw_params_set_channels(handle, params, 2); /* 44100 bits/second sampling rate (CD quality) */ val = 44100; snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); /* Set period size to 32 frames. */ frames = 32; snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); /* Write the parameters to the driver */ rc = snd_pcm_hw_params(handle, params); if (rc < 0) { fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc)); exit(1); } /* Use a buffer large enough to hold one period */ snd_pcm_hw_params_get_period_size(params, &frames, &dir); size = frames * 4; /* 2 bytes/sample, 2 channels */ buffer = (char *) malloc(size); /* We want to loop for 5 seconds */ snd_pcm_hw_params_get_period_time(params, &val, &dir); loops = 5000000 / val; //while (loops > 0) { { loops--; rc = snd_pcm_readi(handle, buffer, frames); if (rc == -EPIPE) { /* EPIPE means overrun */ fprintf(stderr, "overrun occurred\n"); snd_pcm_prepare(handle); } else if (rc < 0) { fprintf(stderr, "error from read: %s\n", snd_strerror(rc)); } else if (rc != (int)frames) { fprintf(stderr, "short read, read %d frames\n", rc); } rc = write(1, buffer, size); if (rc != size) fprintf(stderr, "short write: wrote %d bytes\n", rc); } snd_pcm_drain(handle); snd_pcm_close(handle); free(buffer); return 0; }
gcc test.c -Wall -lasound
$ ./a.out > sound.pcm $ ls -al sound.pcm -rw-r--r-- 1 wang wang 128 4月 7 23:42 sound.pcm $ hexdump -C sound.pcm 00000000 00 00 00 00 00 00 00 00 ff ff ff ff 00 00 ff ff |................| 00000010 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00000020 ff ff ff ff ff ff ff ff 00 00 ff ff ff ff 00 00 |................| 00000030 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 00 00 |................| 00000040 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff ff ff |................| 00000050 00 00 fe ff fe ff 00 00 02 00 fc ff 0a 00 fc ff |................| 00000060 03 00 02 00 05 00 01 00 03 00 02 00 04 00 01 00 |................| 00000070 04 00 02 00 04 00 01 00 04 00 01 00 04 00 01 00 |................| 00000080
设置音量
aplay -Dplughw:0,1 -c 2 -r 48000 -f S16_LE /usr/share/sounds/alsa/Front_Left.wav
aplay -Dhw:0,1 -c 2 -r 48000 -f S16_LE /usr/share/sounds/alsa/Front_Left.wav
aplay -Dplughw:0,1 /usr/share/sounds/alsa/Front_Left.wav
#amixer contents
#amixer controls
# control volume
#amixer cset numid=48,iface=MIXER,name='PCM Playback Volume' 255,255
#amixer cset numid=1,iface=MIXER,name='Master Playback Volume' 15,15
numid=1,iface=MIXER,name='Master Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=31,step=0
: values=15,15
| dBscale-min=-46.50dB,step=1.50dB,mute=0
#aplay -d 10 music.wav
# amixer cset name='PCM Playback Volume' 255,255
# amixer cset name='Master Playback Volume' 15,15
# amixer -D hw:usnd_play sget 'Master'
# amixer -D hw:usnd_play sset 'Master' 20,31
# control mic
#amixer cset numid=8,iface=MIXER,name='Mic Boost Volume' 3,3
numid=8,iface=MIXER,name='Mic Boost Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=3,step=0
: values=3,3
| dBscale-min=0.00dB,step=10.00dB,mute=0
#amixer -D hw:usnd_robot contents
#amixer -D hw:usnd_robot cset numid=3,iface=MIXER,name='Mic Capture Volume' 30,30
#arecord -Dhw:usnd_robot -d 10 -f cd -r 44100 -c 1 -t wav test.wav
#aplay test.wav
通过snd_hctl_t接口或mixer接口控制音量
// set playback volume compile: g++ *.cpp -lasound -o set_playback_volume
#include <stdio.h>
#include <string.h>
#include <alsa/asoundlib.h>
// 使用 amixer scontrols中的选项
int set_playback_volume(int volume)
{
bool elem_flag = false;
int ret = 0;
long vol_min = 0, vol_max = 0, vol_left = 0, vol_right = 0;
snd_mixer_t *handle = NULL;
snd_mixer_elem_t *elem = NULL;
// adjust volume
if(volume > 100){
volume = 100;
} else if(volume < 0){
volume = 0;
}
if ((ret = snd_mixer_open(&handle, 0)) < 0) {
printf("snd_mixer open error\n");
return ret;
}
snd_mixer_attach(handle, "default");
snd_mixer_selem_register(handle, NULL, NULL);
snd_mixer_load(handle);
#if 0
elem = snd_mixer_first_elem(handle);
while(elem){
printf("--elem name: %s\n", snd_mixer_selem_get_name(elem));
elem = snd_mixer_elem_next(elem);
}
#endif
elem = snd_mixer_first_elem(handle);
while(elem){
if (strcmp("Master", snd_mixer_selem_get_name(elem)) == 0){
break;
}
elem = snd_mixer_elem_next(elem);
}
if (!elem) {
printf("snd_mixer not found [Master] element\n");
snd_mixer_close(handle);
handle = NULL;
return -1;
}
snd_mixer_selem_set_playback_switch_all(elem, 1);
snd_mixer_selem_set_playback_volume_range(elem, 0, 100);
snd_mixer_selem_get_playback_volume_range(elem, &vol_min, &vol_max);
// snd_mixer_handle_events(handle);
snd_mixer_selem_set_playback_volume_all(elem, volume);
snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &vol_left);
snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &vol_right);
if(vol_left == volume){
printf("Playback volume set OK: left = %ld%%, right = %ld%%\n", vol_left, vol_right);
} else {
printf("Playback volume set failed, want(%d%%)!\n", volume);
ret = -1;
}
snd_mixer_close(handle);
handle = NULL;
return ret;
}
int main(int argc, char *argv[])
{
int ret = 0;
int volume = 50;
if(argc == 2){
volume = atoi(argv[1]);
}
ret = set_playback_volume(volume);
return ret;
}
打开pcm获取参数
#include <iostream>
#include <alsa/asoundlib.h>
int main(int argc, char *argv[])
{
int rc;
snd_pcm_t* handle;
snd_pcm_hw_params_t*params;
unsigned int val, val2;
int dir;
snd_pcm_uframes_t frames;
char *snd_device = NULL;
if(argc >= 2){
snd_device = argv[1];
} else {
snd_device = "default";
}
set_playback_volume(snd_device, atoi(argv[2]));
printf("\n--------------------\n");
if ( (rc = snd_pcm_open(&handle, snd_device, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
{
std::cerr << "unable to open pcm devices: " << snd_strerror(rc) << std::endl;
exit(1);
}
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(handle, params);
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(handle, params, 2);
val = 44100;
snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
if ( (rc = snd_pcm_hw_params(handle, params)) < 0)
{
std::cerr << "unable to set hw parameters: "
<< snd_strerror(rc) << std::endl;
exit(1);
}
std::cout << "PCM handle name = "
<< snd_pcm_name(handle) << std::endl;
std::cout << "PCM state = "
<< snd_pcm_state_name(snd_pcm_state(handle)) << std::endl;
snd_pcm_hw_params_get_access(params, (snd_pcm_access_t *)&val);
std::cout << "access type = "
<< snd_pcm_access_name((snd_pcm_access_t)val) << std::endl;
snd_pcm_hw_params_get_format(params, (snd_pcm_format_t*)(&val));
std::cout << "format = << snd_pcm_format_name((snd_pcm_format_t)val) << ("
<< snd_pcm_format_description((snd_pcm_format_t)val) << ")" << std::endl;
snd_pcm_hw_params_get_subformat(params, (snd_pcm_subformat_t *)&val);
std::cout << "subformat = << snd_pcm_subformat_name((snd_pcm_subformat_t)val) << ("
<< snd_pcm_subformat_description((snd_pcm_subformat_t)val) << ")" << std::endl;
snd_pcm_hw_params_get_channels(params, &val);
std::cout << "channels = " << val << std::endl;
snd_pcm_hw_params_get_rate(params, &val, &dir);
std::cout << "rate = " << val << " bps" << std::endl;
snd_pcm_hw_params_get_period_time(params, &val, &dir);
std::cout << "period time = " << val << " us" << std::endl;
snd_pcm_hw_params_get_period_size(params, &frames, &dir);
std::cout << "period size = " << static_cast<int>(frames) << " frames" << std::endl;
snd_pcm_hw_params_get_buffer_time(params, &val, &dir);
std::cout << "buffer time = " << val << " us" << std::endl;
snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val);
std::cout << "buffer size = " << val << " frames" << std::endl;
snd_pcm_hw_params_get_periods(params, &val, &dir);
std::cout << "periods per buffer = " << val << " frames" << std::endl;
snd_pcm_hw_params_get_rate_numden(params, &val, &val2);
std::cout << "exact rate = " << val/val2 << " bps" << std::endl;
val = snd_pcm_hw_params_get_sbits(params);
std::cout << "significant bits = " << val << std::endl;
snd_pcm_hw_params_get_tick_time(params, &val, &dir);
std::cout << "tick time = " << val << " us" << std::endl;
val = snd_pcm_hw_params_is_batch(params);
std::cout << "is batch = " << val << std::endl;
val = snd_pcm_hw_params_is_block_transfer(params);
std::cout << "is block transfer = " << val << std::endl;
val = snd_pcm_hw_params_is_double(params);
std::cout << "is double = " << val << std::endl;
val = snd_pcm_hw_params_is_half_duplex(params);
std::cout << "is half duplex = " << val << std::endl;
val = snd_pcm_hw_params_is_joint_duplex(params);
std::cout << "is joint duplex = " << val << std::endl;
val = snd_pcm_hw_params_can_overrange(params);
std::cout << "can overrange = " << val << std::endl;
val = snd_pcm_hw_params_can_mmap_sample_resolution(params);
std::cout << "can mmap = " << val << std::endl;
val = snd_pcm_hw_params_can_pause(params);
std::cout << "can pause = " << val << std::endl;
val = snd_pcm_hw_params_can_resume(params);
std::cout << "can resume = " << val << std::endl;
val = snd_pcm_hw_params_can_sync_start(params);
std::cout << "can sync start = " << val << std::endl;
snd_pcm_close(handle);
return 0;
}
四、设备命名
API库使用逻辑设备名而不是设备文件。设备名字可以是真实的硬件名字也可以是插件名字。硬件名字使用hw:i,j这样的格式。其中i是卡号,j是这块声卡上的设备号。
第一个声音设备是hw:0,0.这个别名默认引用第一块声音设备并且在本文示例中一真会被用到。
插件使用另外的唯一名字,比如 plughw:,表示一个插件,这个插件不提供对硬件设备的访问,而是提供像采样率转换这样的软件特性,硬件本身并不支持这样的特性。
hw: accesses the hardware device directly. // 直接访问硬件
plughw: inserts sample rate and format conversion plugins, if needed. //经过采样率和格式转换插件。
注:通过udev修改声卡名称,参考:Example to map USB Ports to ALSA card numbers and add each sound card to a combined, single interface device
alsa驱动的设备文件
$ ls /dev/snd
by-id controlC1 hwC2D2 pcmC2D0c pcmC2D3p pcmC2D9p
by-path controlC2 pcmC0D0c pcmC2D0p pcmC2D7p seq
controlC0 hwC2D0 pcmC1D0c pcmC2D10p pcmC2D8p timer
controlC0 --> 用于声卡的控制,例如通道选择,混音,麦克风的控制等
pcmC2D0c --> 用于录音的 pcm 设备
pcmC2D0p --> 用于播放的 pcm 设备
seq --> 音序器
timer --> 定时器
通常,我们更关心的是 pcm 和 control 这两种设备。
五、配置文件
参考:asoundrc
配置文件:/usr/share/alsa/asound.conf,文件在调用snd_pcm_open这个api函数时,会被加载同时解析。
asound.conf允许对声卡或者设备进行更高级的控制,提供访问alsa-lib中的pcm插件方法,允许你做更多的复杂的控制,比如可以把声卡组合成一个或者多声卡访问多个I/O。
/usr/share/alsa/asound.conf中定义default音频设备:card0,device0。
defaults.ctl.card 0
defaults.pcm.card 0
defaults.pcm.device 0
pcm.hw{ default {default {name defaults.pcm.card}}}
ctl.default{ default {name defaults.ctl.card}}
六、常用命令
查看音频设备
如果系统有/proc/asound/cards路径,alsa驱动已经安装。
# cat /proc/asound/cards
0 [PCH ]: HDA-Intel - HDA Intel PCH
HDA Intel PCH at 0xa3530000 irq 148
1 [usnd01 ]: USB-Audio - USB 2.0 Camera
Sonix Technology Co., Ltd. USB 2.0 Camera at usb-0000:00:14.0-3, high speed
2 [usnd00 ]: USB-Audio - USB 2.0 Camera
Sonix Technology Co., Ltd. USB 2.0 Camera at usb-0000:00:14.0-4, high speed
共3个音频采集卡,采集卡应用名称可为hw:1,0, hw:2,0或hw:usnd01, hw:usnd02
# cat /proc/asound/devices
0: [ 0] : control
16: [ 0- 0]: digital audio playback
17: [ 0- 1]: digital audio playback
24: [ 0- 0]: digital audio capture
25: [ 0- 1]: digital audio capture
33: : timer
arecord录音
# arecord -l // 查看音频输入设备
**** List of CAPTURE Hardware Devices ****
card 0: wm8960audio [wm8960-audio], device 0: HiFi wm8960-hifi-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: wm8960audio [wm8960-audio], device 1: HiFi-ASRC-FE (*) []
Subdevices: 1/1
Subdevice #0: subdevice #0
通过上面的命令可以得到可用于录音的设备,比如card x device x
arecord -Dhw:0,1 -d 10 -f cd -r 44100 -c 2 -t wav test.wav
参数解析
-D 指定了录音设备,0,1 是card 0 device 1的意思,也就是HiFi-ASRC-FE
-d 指定录音的时长,单位时秒
-f 指定录音格式,通过上面的信息知道只支持 cd cdr dat
-r 指定了采样率,单位时Hz
-c 指定channel 个数
-t 指定生成的文件格式
aplay播放
# aplay -l // 查看音频输出设备(扬声器、speaker)
**** List of PLAYBACK Hardware Devices ****
card 0: wm8960audio [wm8960-audio], device 0: HiFi wm8960-hifi-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: wm8960audio [wm8960-audio], device 1: HiFi-ASRC-FE (*) []
Subdevices: 1/1
Subdevice #0: subdevice #0
通过如上命令可以得到可用于播放声音的设备,比如card x device x
# aplay test.wav
Playing WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
直接输入aplay test.wav 就可以播放wav音频文件了,不需要指定太多的参数,因为wav文件的头部会保存一些声音格式信息,比如pcm格式 ,采样率,channel个数等,所以不指定这些参数也能播放。
如果是直接播放pcm数据,则需要指定pcm格式 、采样率、channel个数等。
aplay -Dhw:0,1 -c 2 -r 48000 -f S16_LE /usr/share/sounds/alsa/Front_Left.wav
aplay -Dplughw:0,1 -c 2 -r 48000 -f S16_LE /usr/share/sounds/alsa/Front_Left.wav
amixer设置音频接口
amixer 是用指令控制音频设备, alsamixer 则提供一套图形界面来控制音频设备,可以用键盘方向键来控制增减音量,打开或者关闭等。
amixer contents ;获取控制内容和值 amixer controls ;获取控制内容 amixer cget ... ;获取某一控制项值 amixer cset ... value ;设置某一控制项值 // 控制音响音量 amixer cset numid=1,iface=MIXER,name='Master Playback Volume' 15,15 // 控制设备名为Device的mic捕获音量 amixer -D hw:Device contents amixer -D hw:Device cset numid=3,iface=MIXER,name='Mic Capture Volume' 30,30 arecord -Dhw:Device -d 10 -f cd -r 44100 -c 1 -t wav test.wav
mediainfo查看音视频文件信息
参考:
4. 视音频数据处理入门:PCM音频采样数据处理 leixiaohua
Hotplugging USB audio devices (Howto)
6. arm linux利用alsa驱动并使用usb音频设备
8. 修改ALSA支持的最大pcm device个数——一次usb声卡不识别问题
9. 音频参数解析