Linux 音频开发之入门篇

前言

      其实为什么要采用两种框架呢?这里就不做记录了。只关心不同的应用场景。

    从编程易用性上来讲,OSS倒是简便,然而发现ubuntu上默认不配置OSS,导致连相关设备节点都没有。因而不得不采用ALSA,此编程接口比较啰嗦。

      根据 arch OSS的wiki  Open Sound System - ArchWiki (archlinux.org)      两者的比较优缺点主要:(顺便说句arch的wiki资料比较齐全 )

OSS Advantages (users)

  • Per-application volume control.
  • Some legacy (i.e. before 2002) cards can have better support.

OSS Advantages (developers)

  • Support for drivers in userspace.
  • Cross-platform (OSS runs on BSDs and Solaris).
  • Smaller and easier to use API. (这点笔者在实际使用中感受很深,主要了解清楚哪些ioctl干啥的就可以,符合驱动的编写思路)

ALSA advantages over OSS

  • Better support for USB audio devices.
  • Support for Bluetooth audio devices.
  • Support for AC'97 and HD Audio dial-up soft-modems such as Si3055.
  • Better support for MIDI devices.
  • Support for suspend.
  • Better support for jack detection.
  • Better support for modern hardware.

Note:

  • OSS has experimental output support for USB audio devices, but no input.
  • OSS supports MIDI devices with the help of a software synthesizer such as Timidity or FluidSynth.

声道

     什么是声道? 其是个硬件概念还是软件概念?

     最终声道需要由物理接口输出。而数据的产生则由软件控制。

      声音的传播分为不同频率。一个声卡的声音输出可以支持不同的频率。例如前声道输入44K,后声道输出8K。数字数据交错存储在内存中,声卡在取出数据后,将数据分别送往不同的声道,产生不同的声音。

    写到这里,其实交错存储的数据,是否也存在这种情况?即由于采用频率不同,导致每个声道的数据长度不一致,进而占用的空间数量不同。例如下面这种情况: 三个L数据后,才是一个R数据?

   

硬件接口

音频的基本框架

编程接口

linux 下音频编程的接口分为两种OSS和ALSA,这是老生常谈的。

OSS接口

官网说明  OSS v4.x API reference - Developing applications for Open Sound System version 4.1

通过 open  write read mmap等系统调用进行访问。

 设备节点

 为了通过open 等相关接口对设备进行控制,首先要知道其设备节点。根据

OSS v4.x API reference - Device types supported by OSS (opensound.com)

 我们可以知道,OSS支持三种设备类型:

Open Sound System supports three kind of devices:

  • Audio devices are used to play and record digital audio such as WAV or MP3 files. See the "Audio Programming" chapter for more info. 控制音频的输入和输出。也就是数据通道!此类设备对应/dev/dsp  /dev/dsp1等。此处每次重新上电后,/dev/dsp  /dev/dsp1实际对应的物理设备会发生变化!在打开设备后,我们要先查询此设备的具体信息 ,再进行操作
  • Mixer devices are used to change volumes and all kind of "control panel" settings. See the "OSS Mixer Programming" chapter for more info. 用于调节音量,对应的设备节点为/dev/mixer等。
  • MIDI port devices are used to access musical instruments (keyboards, samplers and synthesizer modules) as well as to control various other devices such as mixers, effect processors, on stage lightning and pyrotecnics. See the "OSS MIDI Programming" chapter for more info

IOCTL接口

参考连接: OSS v4.x API reference - The ioctl() system call (opensound.com)

 连接中介绍各个ioctl,例如笔者想要确认当前卡的信息,可以采用

示例: OSS v4.x API reference - The ossinfo program that is included in the OSS package. (opensound.com)

在实际使用过程中,查阅是否有合适的接口。

ALSA接口

设备节点

ALSA Library API - AlsaProject (alsa-project.org)  根据ALSA wiki介绍,设备节点为:

he currently designed interfaces are listed below:

  1. Information Interface (/proc/asound)
  2. Control Interface (/dev/snd/controlCX)
  3. Mixer Interface (/dev/snd/mixerCXDX)
  4. PCM Interface (/dev/snd/pcmCXDX)
  5. Raw MIDI Interface (/dev/snd/midiCXDX)
  6. Sequencer Interface (/dev/snd/seq)
  7. Timer Interface (/dev/snd/timer

这里存在的问题,我们并不是是对这些设备节点进行访问来进行录音及播放,而是采用例如 default  或者 plughw:0,0 等作为参数,而这些具体对应哪个物理设备,是不是我们期望的设备?这就又增加了一层不友好的屏蔽。

例如在参考资料3)中的例程中,默认打开default设备,在x86虚拟机上运行正常;但是交叉编译到arm嵌入式环境就不能运行,打开设备错误。而改成plughw:0,0就能正常打开了。

----》》》可以通过lsof命令查看当前哪些进程会操作上述设备节点:

 lsof |grep snd/c

pulseaudi 1150                   gdm   32u      CHR              116,6      0t0        448 /dev/snd/controlC0
pulseaudi 1150                   gdm   39u      CHR              116,6      0t0        448 /dev/snd/controlC0
snapd-gli 1150 1202              gdm   32u      CHR              116,6      0t0        448 /dev/snd/controlC0
snapd-gli 1150 1202              gdm   39u      CHR              116,6      0t0        448 /dev/snd/controlC0
alsa-sink 1150 1338              gdm   32u      CHR              116,6      0t0        448 /dev/snd/controlC0
alsa-sink 1150 1338              gdm   39u      CHR              116,6      0t0        448 /dev/snd/controlC0
alsa-sour 1150 1356              gdm   32u      CHR              116,6      0t0        448 /dev/snd/controlC0
alsa-sour 1150 1356              gdm   39u      CHR              116,6      0t0        448 /dev/snd/controlC0

信息接口设备节点示例:

1)获取单板上的卡的数目及名称

 cat /proc/asound/cards
 0 [audiocodec     ]: audiocodec - audiocodec
                      audiocodec
 1 [sndhdmi        ]: sndhdmi - sndhdmi
                      sndhdmi

 2)获取某块声卡的软硬件参数。可以看到目前硬件参数没有配置,没有在使用此声卡。

 cat /proc/asound/card0/pcm0p/info  播放参数
card: 0
device: 0
subdevice: 0
stream: PLAYBACK
id: SUNXI-CODEC sun8iw11codec-0
name: 
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1
root@T3/A40i-Tronlong:~# cat /proc/asound/card0/pcm0p/sub0/
hw_params  info       status     sw_params  
root@T3/A40i-Tronlong:~# cat /proc/asound/card0/pcm0p/sub0/hw_params   硬件参数
closed

当运行speaker-test 命令再查看硬件参数信息如下:

cat /proc/asound/card0/pcm0p/sub0/status

state: RUNNING
owner_pid   : 3829             (当前进程信息都有)
trigger_time: 5889.590491875
tstamp      : 0.000000000
delay       : 32768
avail       : 0
avail_max   : 24576
-----
hw_ptr      : 15196160
appl_ptr    : 15228928


$ cat /proc/asound/card0/pcm0p/sub0/hw_params

access: RW_INTERLEAVED
format: S16_LE
subformat: STD
channels: 1
rate: 48000 (1572864000/32768)
period_size: 8192
buffer_size: 32768

设备名称

ALSA 分了几层,每层都有一个设备名称,通过配置文件指定其下一层的具体设备信息

通过配置文件建立关联。(配置文件示例及分层结构待补充

如果没有配置文件,则直接指定物理设备,类似于下面 -D后的参数:

aplay -t raw -r 8000 -q -D hw:/dev/snd/controlC0 -f u8 -c 1

open接口

  参数含义: 这里最难以填写的参数是 name。针对播放录音,很多例程都填写default,在我们实际测试中,也可以填写 plughw:0,0  和 hw:/dev/snd/controlC0。

    但是不能填写 hw:/dev/snd/pcmC0D0p,填写此设备节点时,运行时报错: 
ALSA lib pcm_hw.c:1713:(_snd_pcm_hw_open) Invalid value for card
unable to open pcm device: Inappropriate ioctl for device

这里理论上这个节点是pcm 的播放节点,理论上设置这个才OK的嘛!!一团糟糕的设计!!!

snd_pcm_open()
int snd_pcm_open	(	snd_pcm_t ** 	pcmp,
const char * 	name,
snd_pcm_stream_t 	stream,
int 	mode 
)		
Opens a PCM.

Parameters
pcmp	Returned PCM handle
name	ASCII identifier of the PCM handle
stream	Wanted stream
mode	Open mode (see SND_PCM_NONBLOCK, SND_PCM_ASYNC)
Returns
0 on success otherwise a negative error code
Examples
/test/latency.c, /test/pcm.c, and /test/pcm_min.c.

常见错误

      设备可以打开play ,但是打开record时失败,由如下代码知,错误信息为resource busy,即设备被占用。

if ((err = snd_pcm_open (&handle,"hw:/dev/snd/controlC0" , type, 0)) < 0) {
		fprintf (stderr, "cannot open  (%s)\n",
			 snd_strerror (err));
		return NULL;
	}

      这时,由于我们还不熟悉,以为第二个参数传递的有问题,实际上是由于代码中存在while死循环一直接收数据或者发生数据,在用户按ctrl+C时,没有调用接口释放 设备节点导致。

获取硬件参数能力值

   不能配置不在能力范围内的值。

  具体接口参数  待补充!!!!!!!!!!!!!!!

配置硬件参数

硬件参数包括(参考资料3 中描述)

采样率 Rate

 An analog-to-digital converter (ADC) converts the analog voltages into discrete values, called samples, at regular intervals in time, known as the sampling rate

 样本长度 Format

The size of the samples, expressed in bits, is one factor that determines how accurately the sound is represented in digital form

采用用多少位数据表示,部分宏定义如下图,详细的参见: ALSA project - the C library reference: PCM Interface (alsa-project.org)

参数需要硬件支持,例如在T3板子上配置U8,则提示配置不了。而在X86虚拟机上则配置正常。

强行配置U8,报如下错误:


cannot set sample format (Invalid argument)

那么通过哪个接口来获取当前硬件支持的情况呢?!!
 

问题:不同的采样率 是否需要不同的采样数据长度?例如44.1K的采样是否能用 8位数据表示?

 通道数 Channels

我们常说的左声道、右声道,可以理解为左右各来一个 mic 采样,左 mic 采样出来的样本就是左声道数据,右边 mic 采样出来的样本就是右声道数据。而一个音频既可以只有1个声道,也可以有左右两个声道,后者也称为立体声。而这个音频究竟有几个声道,就是我们说的通道数

 帧 Frame

我们每一次采样出来的结果,就是一帧。很明显,一帧数据有多大,取决于我们采样的精度以及通道数。

 交错模式 Interleaved

我们每一次采样出的音频帧,怎么保存呢?提供了两种保存思路,也就是我们说的交错模式和非交错模式。我们常用的也是交错模式。

周期 Period

我们总不可能一次处理1帧数据吧,太低效了,那就做成批量处理吧。而一次处理多少帧就是我们说的周期。

一次周期结束切到下一次周期,都是需要额外处理损耗的,就类似于进程切换。周期大,一次处理数据量就多,每次连续处理时间长,切换损耗就少,但也因为数据要满一个周期后才处理,导致数据处理延时长。反之,如果周期设置的小,延时短了,但周期切换更频繁,损耗就更大,更容易出现卡顿。

缓存大小 Buffer Size

这里说的是 alsa 底层 DMA 搬运数据的缓存大小,这是一个环形的缓存空间。我们设置 DMA 一次连续搬运 1 个周期的数据,搬运期间如果又来数据怎么办?我们就需要更大的缓存空间来保存更多的数据。缓存空间往往是周期的整数倍,例如设置了缓存 8 个周期,每个周期 6000 帧,那么最多可以缓存 8 * 6000 = 48000 帧的数据。

系统命令

OSS shell命令

ossinfo

ALSA shell命令

root执行报错问题

错误信息类似:

 ./x86csdn
MoTTY X11 proxy: Unsupported authorisation protocol
xcb_connection_has_error() returned true
XDG_RUNTIME_DIR (/run/user/1000) is not owned by us (uid 0), but by uid 1000! (This could e g happen if you try to connect to a non-root PulseAudio as a root user, over the native protocol. Don't do that.)
ALSA lib pcm_dsnoop.c:618:(snd_pcm_dsnoop_open) unable to open slave
cannot open for capture

pulseaudio来管理音频设备的,而pulseaudio不允许在root用户下运行,这样会导致安全问题。

解决方法:跨用户调用命令 【可用】

su - username -c "aplay sample.wav"

 同样的命令,root下直接./x86csdn报如上错误。而采用如下形式就可以正常运行了!! 

su - xiehj -c "/home/user/sound/x86csdn"

amixer

音量调控

1)amixer  info
Card default 'audiocodec'/'audiocodec'
  Mixer name    : ''
  Components    : ''
  Controls      : 48
  Simple ctrls  : 48

子命令

Available commands:
  scontrols       show all mixer simple controls
  scontents       show contents of all mixer simple controls (default command)
  sset sID P      set contents for one mixer simple control
  sget sID        get contents for one mixer simple control
  controls        show all controls for given card  (是否mixer simple controls只是controls中的一个类型?!
  contents        show contents of all controls for given card
  cset cID P      set control contents for one control
  cget cID        get control contents for one control
root@T3/A40i-Tronlong:~# amixer scontrols (此命令输出与controls命令输出类似)
Simple mixer control 'Headphone',0
Simple mixer control 'Headphone volume',0
Simple mixer control 'FM gain volume',0
Simple mixer control 'Phone Out Mixer LOMIX',0
Simple mixer control 'Phone Out Mixer MIC1 Boost',0
Simple mixer control 'Phone Out Mixer MIC2 Boost',0
Simple mixer control 'Phone Out Mixer ROMIX',0
Simple mixer control 'Phoneout Speaker',0
Simple mixer control 'ADC gain volume',0
Simple mixer control 'HPL Mux',0
Simple mixer control 'HPR Mux',0
Simple mixer control 'LINEIN Mixer volume',0
Simple mixer control 'LINEIN gain volume',0
Simple mixer control 'Left Input Mixer FML',0
Simple mixer control 'Left Input Mixer LINEINL',0
Simple mixer control 'Left Input Mixer LINEINLR',0
Simple mixer control 'Left Input Mixer LOMIX',0
Simple mixer control 'Left Input Mixer MIC1 Boost',0
Simple mixer control 'Left Input Mixer MIC2 Boost',0
Simple mixer control 'Left Input Mixer ROMIX',0
Simple mixer control 'Left Output Mixer DACL',0
Simple mixer control 'Left Output Mixer DACR',0
Simple mixer control 'Left Output Mixer FML',0
Simple mixer control 'Left Output Mixer LINEINL',0
Simple mixer control 'Left Output Mixer LINEINLR',0
Simple mixer control 'Left Output Mixer MIC1 Boost',0
Simple mixer control 'Left Output Mixer MIC2 Boost',0
Simple mixer control 'MIC gain volume',0
Simple mixer control 'MIC1 boost volume',0
Simple mixer control 'MIC2 Mux',0
Simple mixer control 'MIC2 boost volume',0
Simple mixer control 'Right Input Mixer FMR',0
Simple mixer control 'Right Input Mixer LINEINLR',0
Simple mixer control 'Right Input Mixer LINEINR',0
Simple mixer control 'Right Input Mixer LOMIX',0
Simple mixer control 'Right Input Mixer MIC1 Boost',0
Simple mixer control 'Right Input Mixer MIC2 Boost',0
Simple mixer control 'Right Input Mixer ROMIX',0
Simple mixer control 'Right Output Mixer DACL',0
Simple mixer control 'Right Output Mixer DACR',0
Simple mixer control 'Right Output Mixer FMR',0
Simple mixer control 'Right Output Mixer LINEINLR',0
Simple mixer control 'Right Output Mixer LINEINR',0
Simple mixer control 'Right Output Mixer MIC1 Boost',0
Simple mixer control 'Right Output Mixer MIC2 Boost',0
Simple mixer control 'codec hub mode',0
Simple mixer control 'digital volume',0
Simple mixer control 'phoneout volume',0
root@T3/A40i-Tronlong:~# amixer controls (如果支持其他iface,可能会和scontrols的输出有所不同。那么这些controls具体有物理器件对应?
numid=3,iface=MIXER,name='Headphone volume'
numid=47,iface=MIXER,name='Headphone Switch'
numid=5,iface=MIXER,name='FM gain volume'
numid=15,iface=MIXER,name='Phone Out Mixer LOMIX Switch'
numid=18,iface=MIXER,name='Phone Out Mixer MIC1 Boost Switch'
numid=17,iface=MIXER,name='Phone Out Mixer MIC2 Boost Switch'
numid=16,iface=MIXER,name='Phone Out Mixer ROMIX Switch'
numid=48,iface=MIXER,name='Phoneout Speaker Switch'
numid=11,iface=MIXER,name='ADC gain volume'
numid=13,iface=MIXER,name='HPL Mux'
numid=14,iface=MIXER,name='HPR Mux'
numid=4,iface=MIXER,name='LINEIN Mixer volume'
numid=6,iface=MIXER,name='LINEIN gain volume'
numid=28,iface=MIXER,name='Left Input Mixer FML Switch'
numid=29,iface=MIXER,name='Left Input Mixer LINEINL Switch'
numid=30,iface=MIXER,name='Left Input Mixer LINEINLR Switch'
numid=27,iface=MIXER,name='Left Input Mixer LOMIX Switch'
numid=32,iface=MIXER,name='Left Input Mixer MIC1 Boost Switch'
numid=31,iface=MIXER,name='Left Input Mixer MIC2 Boost Switch'
numid=26,iface=MIXER,name='Left Input Mixer ROMIX Switch'
numid=41,iface=MIXER,name='Left Output Mixer DACL Switch'
numid=40,iface=MIXER,name='Left Output Mixer DACR Switch'
numid=42,iface=MIXER,name='Left Output Mixer FML Switch'
numid=43,iface=MIXER,name='Left Output Mixer LINEINL Switch'
numid=44,iface=MIXER,name='Left Output Mixer LINEINLR Switch'
numid=46,iface=MIXER,name='Left Output Mixer MIC1 Boost Switch'
numid=45,iface=MIXER,name='Left Output Mixer MIC2 Boost Switch'
numid=7,iface=MIXER,name='MIC gain volume'
numid=9,iface=MIXER,name='MIC1 boost volume'
numid=12,iface=MIXER,name='MIC2 Mux'
numid=10,iface=MIXER,name='MIC2 boost volume'
numid=21,iface=MIXER,name='Right Input Mixer FMR Switch'
numid=23,iface=MIXER,name='Right Input Mixer LINEINLR Switch'
numid=22,iface=MIXER,name='Right Input Mixer LINEINR Switch'
numid=19,iface=MIXER,name='Right Input Mixer LOMIX Switch'
numid=25,iface=MIXER,name='Right Input Mixer MIC1 Boost Switch'
numid=24,iface=MIXER,name='Right Input Mixer MIC2 Boost Switch'
numid=20,iface=MIXER,name='Right Input Mixer ROMIX Switch'
numid=33,iface=MIXER,name='Right Output Mixer DACL Switch'
numid=34,iface=MIXER,name='Right Output Mixer DACR Switch'
numid=35,iface=MIXER,name='Right Output Mixer FMR Switch'
numid=37,iface=MIXER,name='Right Output Mixer LINEINLR Switch'
numid=36,iface=MIXER,name='Right Output Mixer LINEINR Switch'
numid=39,iface=MIXER,name='Right Output Mixer MIC1 Boost Switch'
numid=38,iface=MIXER,name='Right Output Mixer MIC2 Boost Switch'
numid=1,iface=MIXER,name='codec hub mode'
numid=2,iface=MIXER,name='digital volume'
numid=8,iface=MIXER,name='phoneout volume'

amixer 设备选择示例

   例如针对一个声卡设备,其设备节点为/dev/snd/controlC0,即支持front microphone 又支持rear microphone,也就是存在两个capture设备。那么我们如何选择其中一个呢?

    让我们再来看下mixer的功能:

  • Mixer interface: controls the devices on sound cards that route signals and control volume levels. It is built on top of the control interface.

     此处讲明了 mixer 除了控制音量的功能,还有一个控制信号路由的功能,用此功能,我们可以选择具体哪个设备对应 /dev/snd/controlC0的capture 功能。

    示例:

     1) 首先查询当前mixer的信息      

amixer contents   

通过如下命令的输出,我们可以看到  input source 对应又两个选项: front mic和rear mic
而values = 1,表示此时采用rear mic
numid=10,iface=MIXER,name='Input Source'
  ; type=ENUMERATED,access=rw------,values=1,items=2
  ; Item #0 'Front Mic'
  ; Item #1 'Rear Mic'
  : values=1



numid=17,iface=MIXER,name='Rear Mic Boost Volume'
  ; type=INTEGER,access=rw---R--,values=2,min=0,max=3,step=0
  : values=2,2
  | dBscale-min=0.00dB,step=10.00dB,mute=0


numid=16,iface=MIXER,name='Front Mic Boost Volume'
  ; type=INTEGER,access=rw---R--,values=2,min=0,max=3,step=0
  : values=0,0
  | dBscale-min=0.00dB,step=10.00dB,mute=0

2) 更改输入硬件源:

amixer cset numid=10 0   最后一个参数即选项的索引

numid=10,iface=MIXER,name='Input Source'
  ; type=ENUMERATED,access=rw------,values=1,items=2
  ; Item #0 'Front Mic'
  ; Item #1 'Rear Mic'
  : values=0

音量调节示例

  英文汉语

volume

音量
unmute取消静音

amixer -c 1 sset Line,0 80%,40% unmute cap

will set the second soundcard's left line input volume to 80% and right line input to 40%, unmute it, and select it as a source for capture (recording).

这里 Line,0 表示左侧输入音量,那么右侧输入音量是Line,1 ?

amixer -c 1 -- sset Master playback -20dB

will set the master volume of the second card to -20dB. If the master has multiple channels, all channels are set to the same value.

amixer -c 1 set PCM 2dB+

will increase the PCM volume of the second card with 2dB. When both playback and capture volumes exist, this is applied to both volumes.

amixer -c 2 cset iface=MIXER,name='Line Playback Volume",index=1 40%

will set the third soundcard's second line playback volume(s) to 40%

amixer -c 2 cset numid=34 40%

will set the 34th soundcard element to 40%

音量调节实战

环境基于ubuntu 虚拟机 x86

amixer info
Card default 'AudioPCI'/'Ensoniq AudioPCI ENS1371 at 0x2040, irq 16'
  Mixer name    : 'Cirrus Logic CS4297A rev 3'
  Components    : 'AC97a:43525913'
  Controls      : 26
  Simple ctrls  : 13

amixer scontents
Simple mixer control 'Master',0
  Capabilities: pvolume pswitch pswitch-joined
  Playback channels: Front Left - Front Right
  Limits: Playback 0 - 63
  Mono:
  Front Left: Playback 63 [100%] [0.00dB] [on]
  Front Right: Playback 63 [100%] [0.00dB] [on]
Simple mixer control 'PCM',0
  Capabilities: pvolume pswitch pswitch-joined
  Playback channels: Front Left - Front Right
  Limits: Playback 0 - 63
  Mono:
  Front Left: Playback 48 [76%] [37.50dB] [on]
  Front Right: Playback 48 [76%] [37.50dB] [on]
Simple mixer control 'Line',0
  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
  Capture exclusive group: 0
  Playback channels: Front Left - Front Right
  Capture channels: Front Left - Front Right
  Limits: Playback 0 - 63
  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]

初始音量信息

 

更改音量操作

在图形界面更改音量,然后通过命令查询

amixer scontents,通过命令可以看到pcm的音量值发生变化,
Simple mixer control 'Master',0
  Capabilities: pvolume pswitch pswitch-joined
  Playback channels: Front Left - Front Right
  Limits: Playback 0 - 63
  Mono:
  Front Left: Playback 63 [100%] [0.00dB] [on]
  Front Right: Playback 63 [100%] [0.00dB] [on]
Simple mixer control 'PCM',0
  Capabilities: pvolume pswitch pswitch-joined
  Playback channels: Front Left - Front Right
  Limits: Playback 0 - 63
  Mono:
  Front Left: Playback 56 [89%] [49.50dB] [on]
  Front Right: Playback 56 [89%] [49.50dB] [on]
 

输入音量的控制

注意下面这个图,和上面输入音量的截图相比,设备也更换了,因为microphone这个设备调节了后,命令查不出改变。

Simple mixer control 'Capture',0
  Capabilities: cvolume cswitch cswitch-joined
  Capture channels: Front Left - Front Right
  Limits: Capture 0 - 15
  Front Left: Capture 4 [27%] [6.00dB] [on]
  Front Right: Capture 4 [27%] [6.00dB] [on]
 

经过上述的实战,我们依然不能明了当前系统使用的是哪个设备!!

不过对于某开发板:

 其丝印标识 H/P OUT  和LINE IN ,看起来可以和amixer 获取到的上图信息对应。

master、PCM等的关系

 调节不同节点影响的范围是怎么样的?

alsactl  monitor

主要功能监控 alsa ,通过再界面操作,我们可以了解到界面具体操控了哪些设备,这样我们就可以用amixer等命令进行配置了
node hw:0, #4 (2,0,0,Headphone Playback Switch,0) VALUE
node hw:0, #3 (2,0,0,Headphone Playback Volume,0) VALUE
node hw:0, #18 (2,0,0,Master Playback Volume,0) VALUE
node hw:0, #62 (2,0,0,PCM Playback Volume,0) VALUE
node hw:0, #4 (2,0,0,Headphone Playback Switch,0) VALUE
node hw:0, #4 (2,0,0,Headphone Playback Switch,0) VALUE
node hw:0, #3 (2,0,0,Headphone Playback Volume,0) VALUE
node hw:0, #18 (2,0,0,Master Playback Volume,0) VALUE
node hw:0, #62 (2,0,0,PCM Playback Volume,0) VALUE

编程实践

OSS编程实践

声卡信息获取

   为什么我们需要这个功能,因为我们要区分哪个设备节点为我们期望操作的?

音量调节

  为什么我们需要这个功能,因为不同的音量导致输出 、输入数据的变化。 

输入输出

 为什么我们需要这个功能,因为这个是音频的数据通道。

ALSA编程实践

录音与播放

      分离的录音与播放功能,参见 参考资料3 中的示例,比较清晰。同时,参考资料3 中的另外一篇实现了边播边放,本质是将 设备节点作为play 、capture同时打开,然后分别对play和capture句柄分别进行写 、读操作。

实验要求及代码

     以上的示例中,通常都将rate 设置为44.1K, channel 为2,以及采用精度设置为S16。

    本文实验将rate改成8K, channel改为1,精度改为U8。示例代码如下,主体代码为参考资料3中,主要修改 硬件参数进行测试。


#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>

snd_pcm_t *open_sound_dev(snd_pcm_stream_t type)
{
	int err;
	snd_pcm_t *handle;
	snd_pcm_hw_params_t *hw_params;
	//unsigned int rate = 44100;
	unsigned int rate = 8000;
/*
int snd_pcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode)

pcmp 打开的pcm句柄
name 要打开的pcm设备名字,默认default,或者从asound.conf或者asoundrc里面选择所要打开的设备
stream SND_PCM_STREAM_PLAYBACK 或 SND_PCM_STREAM_CAPTURE,分别表示播放和录音的PCM流
mode 打开pcm句柄时的一些附加参数 SND_PCM_NONBLOCK 非阻塞打开(默认阻塞打开), SND_PCM_ASYNC 异步模式打开
返回值 0 表示打开成功,负数表示失败,对应错误码 "plughw:0,0"
*/

	if ((err = snd_pcm_open (&handle,"hw:/dev/snd/controlC0" , type, 0)) < 0) {
		fprintf (stderr, "cannot open  (%s)\n",
			 snd_strerror (err));
		return NULL;
	}
/*
snd_pcm_hw_params_malloc( ) 在栈中分配 snd_pcm_hw_params_t 结构的空间,然后使用 snd_pcm_hw_params_any( ) 函数用声卡的全配置空间参数初始化已经分配的 snd_pcm_hw_params_t 结构。snd_pcm_hw_params_set_access ( ) 设置访问类型,常用访问类型的宏定义有:

SND_PCM_ACCESS_RW_INTERLEAVED 
交错访问。在缓冲区的每个 PCM 帧都包含所有设置的声道的连续的采样数据。比如声卡要播放采样长度是 16-bit 的 PCM 立体声数据,表示每个 PCM 帧中有 16-bit 的左声道数据,然后是 16-bit 右声道数据。

SND_PCM_ACCESS_RW_NONINTERLEAVED 
非交错访问。每个 PCM 帧只是一个声道需要的数据,如果使用多个声道,那么第一帧是第一个声道的数据,第二帧是第二个声道的数据,依此类推。

函数 snd_pcm_hw_params_set_format() 设置数据格式,主要控制输入的音频数据的类型、无符号还是有符号、是 little-endian 还是 bit-endian。比如对于 16-bit 长度的采样数据可以设置为:	
*/   
	if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
		fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
			 snd_strerror (err));
		return NULL;
	}
			 
	if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0) {
		fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
			 snd_strerror (err));
		return NULL;
	}

	if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {// 交错访问
		fprintf (stderr, "cannot set access type (%s)\n",
			 snd_strerror (err));
		return NULL;
	}

	if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_U8)) < 0) {  // SND_PCM_FORMAT_U8  SND_PCM_FORMAT_S16_LE      有符号16 bit Little Endian
		fprintf (stderr, "cannot set sample format (%s)\n",
			 snd_strerror (err));
		return NULL;
	}

	if ((err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &rate, 0)) < 0) {    //snd_pcm_hw_params_set_rate_near () 函数设置音频数据的最接近目标的采样率
		fprintf (stderr, "cannot set sample rate (%s)\n",
			 snd_strerror (err));
		return NULL;
	}

	if ((err = snd_pcm_hw_params_set_channels (handle, hw_params, 1)) < 0) {
		fprintf (stderr, "cannot set channel count (%s)\n",
			 snd_strerror (err));
		return NULL;
	}
//snd_pcm_hw_params( ) 从设备配置空间选择一个配置,让函数 snd_pcm_prepare() 准备好 PCM 设备,以便写入 PCM 数据。
	if ((err = snd_pcm_hw_params (handle, hw_params)) < 0) {
		fprintf (stderr, "cannot set parameters (%s)\n",
			 snd_strerror (err));
		return NULL;
	}

	snd_pcm_hw_params_free (hw_params);

	return handle;
}

void close_sound_dev(snd_pcm_t *handle)
{
	snd_pcm_close (handle);
}

snd_pcm_t *open_playback(void)
{
    return open_sound_dev(SND_PCM_STREAM_PLAYBACK);
}

snd_pcm_t *open_capture(void)
{
    return open_sound_dev(SND_PCM_STREAM_CAPTURE);
}


int main (int argc, char *argv[])
{
	int err;
	unsigned char buf[512];
	unsigned char recv_buf[512];
	snd_pcm_t *playback_handle;
	snd_pcm_t *capture_handle;
	int i=0;


    playback_handle = open_playback();
    if (!playback_handle)
    {
		fprintf (stderr, "cannot open for playback\n");
        return -1;
    }
    //snd_pcm_close (playback_handle);

    capture_handle = open_capture();
    if (!capture_handle)
    {
		fprintf (stderr, "cannot open for capture\n");
		snd_pcm_close (playback_handle);

        return -1;
    }

	if ((err = snd_pcm_prepare (playback_handle)) < 0) {
		fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
			 snd_strerror (err));
			 snd_pcm_close (playback_handle);
	snd_pcm_close (capture_handle);
		return -1;
	}

	if ((err = snd_pcm_prepare (capture_handle)) < 0) {
		fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
			 snd_strerror (err));
		return -1;
	}
#if 1
	while (1) {
		if ((err = snd_pcm_readi (capture_handle, recv_buf, 128)) != 128) {
			fprintf (stderr, "read from audio interface failed (%s)\n",
				 snd_strerror (err));
				 snd_pcm_close (playback_handle);
	snd_pcm_close (capture_handle);
			return -1;
		}
	  printf("recv data:");
	  for(i=0;i<64;i++)
	      printf("0x%x ",recv_buf[i]);
     printf("\n");
     
    memset(buf,0x55,128);
    memset(buf,0x0,128);
		if ((err = snd_pcm_writei (playback_handle, buf, 128)) != 128) {   //snd_pcm_writei() 用来把交错的音频数据写入到音频设备。
			fprintf (stderr, "write to audio interface failed (%s)\n",
				 snd_strerror (err));
				 snd_pcm_close (playback_handle);
	snd_pcm_close (capture_handle);
			return -1;
		}
	}
#endif 
	snd_pcm_close (playback_handle);
	snd_pcm_close (capture_handle);
    return 0;
}


运行结果及问题

运行后,很快出现 broken pipe,而原先未修改硬件配置参数的代码则未出现此问题。异常信息如下:

./x86csdn
MoTTY X11 proxy: Unsupported authorisation protocol
xcb_connection_has_error() returned true
XDG_RUNTIME_DIR (/run/user/1000) is not owned by us (uid 0), but by uid 1000! (This could e g happen if you try to connect to a non-root PulseAudio as a root user, over the native protocol. Don't do that.)
recv data:0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9
recv data:0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9
write to audio interface failed (Broken pipe)

问题分析 之 设备恢复及统计

 如下,通过接口pcm_prepare恢复设备状态。并通过增加计数,我们看到在发生16或者20次后,会进入一次broken pipe 异常

if ((err = snd_pcm_writei (playback_handle, buf, 128)) != 128) {   
			fprintf (stderr, "write to audio interface failed (%s) %d\n",
				 snd_strerror (err),count); //这里打印正常输出了多少次count
				// snd_pcm_close (playback_handle);
	//snd_pcm_close (capture_handle);
	  snd_pcm_prepare(playback_handle); // 这里恢复设备状态
		//	return -1;
		count = 0;
		}
	}

period size大小的影响

  /* Set period size to 32 frames. */
  capture_frames = 64;
  snd_pcm_hw_params_set_period_size_near(capture_handle,
                              capture_params, &capture_frames, &dir);

  /* Write the parameters to the driver */
  rc = snd_pcm_hw_params(capture_handle, capture_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(capture_params, &capture_frames,
                                    &dir);
  capture_size = capture_frames* 4;// * 4; /* 2 bytes/sample, 2 channels */
  capture_buffer = (char *) malloc(capture_size);

       在音频的编程中,period size大小是比较重要的,也就是一次发送或者接收的帧数。

       设置的不合适,容易出现 underrun或者overrun的情况。通过如下的设备节点,可以看到当前采用的参数,尤其注意,查看这些参数需要音频程序在运行时查看,否则查到的信息为设备closed

      

cat /proc/asound/card0/pcm2c/sub0/hw_params
access: RW_INTERLEAVED
format: S16_LE
subformat: STD
channels: 2
rate: 44100 (44100/1)
period_size: 32
buffer_size: 1024


cat /proc/asound/card0/pcm2c/sub0/sw_params
tstamp_mode: NONE
period_step: 1
avail_min: 32
start_threshold: 1
stop_threshold: 1024
silence_threshold: 0
silence_size: 0
boundary: 4611686018427387904

        此外,注意代码中是否存在 阻塞等待的接口。例如在作为capture时,默认是阻塞的,如果没有读到数据,则进程会等待,导致后续发送数据的接口没有运行,进而出现,underrun的信息。

        将capture 打开时,改为非阻塞可以解决此问题,只是在处理后续的读取操作时,要针对返回值进行异常处理     

 rc = snd_pcm_open(&capture_handle, "hw:/dev/snd/controlC0" ,
                    SND_PCM_STREAM_CAPTURE, SND_CTL_NONBLOCK); //  采用非阻塞模式,不然在下面写的时候,堵塞导致写的过程很慢,而INTEL HDA速率快很多,导致一直 underrun.
                    

period 时长

这个单板相关,通过如下接口进行获取,其中第二个为输出参数,单位为us,即period的长度

 

 snd_pcm_hw_params_get_period_time()
int snd_pcm_hw_params_get_period_time	(	const snd_pcm_hw_params_t * 	params,
unsigned int * 	val,
int * 	dir 
)		
Extract period time from a configuration space.

Parameters
params	Configuration space
val	Returned approximate period duration in us  //period的时长,以us为单位
dir	Sub unit direction

如果我们以此为基准进行一些计算,则要注意单板的差异。例如,笔者采用T3评估板,采用频率设置为8K,查询此参数为1000 000 ,即1s,也就是1s钟才完成一次数据的发送或者接收。

参考资料

Open Sound System - ArchWiki (archlinux.org)  arch oss wiki百科信息,shell命令等的介绍都源自于此。

2)OSS v4.x API reference - The open() system call (opensound.com)OSS 编程接口。接口参数等不清楚的时候,都可以查阅此文档。

3)ALSA 编程入门 ,这篇写的非常详细,优质文章。只是唯一缺点是 播放和录音是分开的,没有合入到一起。Introduction to Sound Programming with ALSA | Linux Journal

边播边放的代码:(9条消息) alsa框架编写应用层,实现边播放边录音_aningxiaoxixi的博客-CSDN博客_alsa 边录边播 5.ALSA录放音 - 简书 (jianshu.com)

官网简化版本例程,短小精悍,结构清晰: ALSA project - the C library reference: /test/pcm_min.c (alsa-project.org) 

4)ALSA wiki : AlsaProject (alsa-project.org)

5)arch ALSA wiki: Advanced Linux Sound Architecture - ArchWiki (archlinux.org)

6) amixer 命令行解释及示例: amixer: command-line mixer for ALSA soundcard driver - Linux Man Pages (1) (systutorials.com) 

7) ALSA 官方API 接口说明: ALSA project - the C library reference: Index, Preamble and License (alsa-project.org)

8) ALSA 的设计哲学图谱  ALSA topology - AlsaProject (alsa-project.org)

9)设备名称: DeviceNames - AlsaProject (alsa-project.org) 

10) mixer 命令help信息及示例  amixer: command-line mixer for ALSA soundcard driver - Linux Man Pages (1) (systutorials.com)

11) 输入接口 Line in 和mic in 的区别: Line In vs Mic In (Line Level Explained For Dummies) (producerhive.com) 

(9条消息) MICIN、LINEIN、LINEOUT、HPOUT、麦克风、耳机、扬声器一次说明白_【ql君】qlexcel的博客-CSDN博客_hpout

12)硬件配图不错  ALSA overview - stm32mpu (stmicroelectronics.cn) 

  • 1
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux音频驱动开发涉及到配置和编译内核,以及设备树的配置。首先,需要重新编译Linux内核,并使用新的zImage和.dtb文件启动系统。在系统启动过程中,如果设备树和驱动都使能的话,会打印出ALSA设备列表,其中包括"wm8960-audio"这个声卡。此外,在/dev/snd目录下会有一些与ALSA音频驱动框架对应的设备文件。 要使能内核自带的WM8960驱动,可以通过图形化界面配置。使用命令"make menuconfig"打开Linux内核的图形化配置界面。在配置界面中,需要取消ALSA模拟OSS API的选择,并使能I.MX6ULL的WM8960驱动。具体路径如下: - 取消ALSA模拟OSS API:Device Drivers -> Sound card support -> Advanced Linux Sound Architecture -> <> OSS Mixer API //不选择 -> <> OSS PCM (digital audio) API //不选择 - 使能I.MX6ULL的WM8960驱动:Device Drivers -> Sound card support -> Advanced Linux Sound Architecture -> ALSA for SoC audio support -> SoC Audio for Freescale CPUs -> <*> Asynchronous Sample Rate Converter (ASRC) module support //选中 -> <*> SoC Audio support for i.MX boards with wm8960 //选中 完成内核和设备树的重新编译后,可以在开发板根文件系统的/etc/profile文件中加入以下内容来指定ALSA的配置文件路径: export ALSA_CONFIG_PATH=/usr/share/arm-alsa/alsa.conf 至此,完成了音频驱动的配置和编译。接下来可以进行音频驱动的测试,包括声卡设置和测试。 #### 引用[.reference_title] - *1* *2* *3* [Linux驱动开发|音频驱动](https://blog.csdn.net/Chuangke_Andy/article/details/122494425)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

proware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值