音频基础

、音频基础

声音是指压力波通过空气或者任何其他介质(例如气体、液体或者固体)传播的震动。

在数字音频系统中,麦克风将声音转换为模拟电信号,然后通常使用脉冲编码调制(PCM)的模数转换器(ADC)将模拟信号转换为数字信号。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。

对于人类而言,能接受声音的频率范围是20Hz-20KHz, 所以采样的频率44.1KHz 以及16bit的采样位数就可以有很好的保真能力CD格式的采样率和采样位数)常用的音频采样频率有8kHz11.025kHz22.05kHz16kHz37.8kHz44.1kHz48kHz等。

经常见到这样的描述: 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):采样点个数,不同编码方式采样点不同,AAC1024mp31152

通道数(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

帧大小:一帧对应的采样样本的个数 * 通道数* 数据位数/8AAC帧,采集数据为双通道,16位数据时,根据公式, 一帧数据量大小 = 1024*2 *16/8 = 4096

在采集采样率和编码采样率相同的情况下(就是不需要重采样),通道数,数据位数一般不会改变,那么采集的数据大小和编码的数据大小是一样的。也就是说,当采集了4096个字节的数据后,再送去给编码器编码一帧AAC帧,不同的采样率只是会改变每秒钟的AAC帧的数量。

采样率(sample rate)44100Hz1秒钟有:44100 / 1024 = 43.066

采样率为48000Hz1秒钟有: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

官网:www.alsa-project.org

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;
}
View Code

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(&params);

    /* 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;
}
View Code

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接口控制音量

HowTo access a mixer control

 

// 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(&params);
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

alsa 配置文件asound.conf

配置文件:/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查看音视频文件信息

 

参考:

1. Linux&音频】Alsa音频编程【精华】

2. Linux下音频设备的操作 程序

3. ffmpeg关于音频的总结()

4. 视音频数据处理入门:PCM音频采样数据处理 leixiaohua

5. Example to map USB Ports to ALSA card numbers and add each sound card to a combined, single interface device

Hotplugging USB audio devices (Howto)

6. arm linux利用alsa驱动并使用usb音频设备

7. 如何在Android平台上使用USB Audio设备

8. 修改ALSA支持的最大pcm device个数——一次usb声卡不识别问题

9. 音频参数解析

posted @ 2016-11-19 23:13  yuxi_o  阅读(4784)  评论(0编辑  收藏  举报