DSP

Linux Audio Stack & ALSA

2019-07-13 17:26发布

目录(?)[-]
  1. Linux Audio Stack
    1. oss
    2. ALSA
    3. PulseAudio
  2. ALSA Project
    1. ALSA体系结构
    2. 环境搭建
    3. 测试
    4. ALSA混音
    5. ALSA编程

Linux Audio Stack

linux_audio_stack

oss

  • OSS借用了UNIX里”一切都是文件”的概念,。 
    把声卡模拟成一个/dev/dsp设备, 多块声卡就是dsp0, dsp1…
  • 要播放声音?打开dsp设备.往里面write数据就可以了.
  • 设置比特率?用ioctl设置即可.
  • 最简单的接口, 也是最没用的接口.因为应用程序完全没法对声音的播放进行控制.
  • OSS是个阻塞的接口, write后, 要声音播放完毕才返回. 
    显然很糟糕, 因为在write和下一次write的微小时间间隔内, 声音出现了不连续.解决办法就是使用异步的write. write完了, 用ioctl轮询, 看还剩下多少数据.注意, 是轮询.轮询到还剩下不到1ms就播放玩了, 马上再write一个buffer过去.这就是OSS接口下的播放.

ALSA

  • 当然OSS这个声音接口实在是太简单了, 于是他们搞了一个ALSA接口.ALSA先进了一些, 有了PLL接口.除了可以向声卡发送数据,
  • 还可以为数据附带时钟信息.注意, 可以为数据附带时钟信息.意味着你可以不用精确的等待到一个时间再送出数据, 
    而是指定一块数据在指定的时间播放.还可以设定播放到某个byte的时候, 产生时钟信号通知程序, 好让程序能继续准备下一块buffer继续播放.
  • 然后asla又提供了一个高级的接口, libasound.so, libasound.so可以读取/etc/asound.conf/usr/share/alsa/alsa.conf~/.asound.conf 
    配置文件, 还可以加载各种插件, 插件又可以通过配置文件配置.相当的高级.可以说, alsa是为专业的音频应用准备的.
  • 不论是OSS还是ALSA, 在民用声卡上都有一个致命的缺点:他们不支持混音.OSS和ALSA都只是声卡的接口.如果声卡本身不支持硬件混音, 
    那么程序就只能独占声卡.表现为只能有一个程序出声.好在alsa有一个补救措施, 那就是libasound.so.
  • alsa不推荐直接使用alsa的内核接口编程,而是使用libasound.so的接口.如果使用了libasound.so的接口, 用户可以配置一个叫”dmix”的插件.然后把dmix模拟出来的声卡作为默认声卡.大家都输出到dmix, dmix再收集多个程序的声音, 一起混音然后输出给硬件.
  • 这个问题看似解决了.实际上没有解决.受限于alsa的接口, dmix的实现质量相当的糟糕.首先, dmix为了混音, 强制重采样.重采样导致了音质的劣化. 
    其次, dmix没有C/S结构.通过共享内存协作完成混音和共享声卡, 一个程序的dmix出问题, 会导致所有正在发声的程序崩溃.
  • ALSA的另一个缺点, 在使用了多个声卡后(蓝牙和HDMI普及了啊), 也开始暴露.那就是不能动态切换输出声卡. 
    我希望我带上蓝牙耳机后, 声音能从扬声器直接转移到耳机里播放.但是ALSA的先天缺陷导致无法实现这个功能. 
    播放器必须重新打开蓝牙声卡.也就是说, 把切换输出的责任全部交给播放器实现.播放器可以实现, 
    问题是所有的播放器都必须实现.另一个问题是, 用户必须到各个播放器下依次调节.非常麻烦.

PulseAudio

  • 为了解决ALSA固有缺陷的目的, PulseAudio又出现了. 
    PulseAudio解决办法也是提供一个混音服务.混音服务后台执行, 独占声卡的访问权限.其他程序要播放声音的, 
    都需要将声音数据交给pulseaudio.由pulseaudio执行混音.
  • 那么, 重采样问题怎么解决呢? 
    pulseaudio提供了一个动态采样率模式.简单说明一下工作原理
    1. A程序播放44100hz的声音, 提交给pulseaudio
    2. pulseaudio查询到声卡支持44100hz采样率播放, 直接输出
    3. A程序接着播放48000hz声音, 提交给pulseaudio
    4. pulseaudio查询到声卡是否支持48000hz采样率播放, 不支持则重采样到44100后输出. 
      如果声卡支持, 则直接输出.那么, 现在支持, 好, 设定声卡为48000模式, 然后直接输出.
    5. A程序播放48000声音的同时, B程序开始播放44100的声音.
    6. pulseaudio将B程序的声音重采样到48000, 然后混音输出.
    7. A程序退出, B程序继续播放44100的声音
    8. pulseaudio将B程序的声音重采样到48000, 然后混音输出.
    9. B程序暂停或停止播放, 然后重新或继续播放44100的声音
    10. pulseaudio将设定声卡为44100模式, 然后直接输出.
    11. A程序又播放48000声音
    12. pulseaudio将A程序的声音重采样到44100, 然后混音输出.
    13. 用户切换到蓝牙耳机.只支持32000hz采样率.
    14. pulseaudio将A程序的48000hz重采样到32000hz, B的44100重采样到32000hz, 混音后输出给蓝牙耳机.
  • 同样是系统混音, pulseaudio支持动态采样率调节.依据当前声卡支持的采样率和当前程序提交的数据, 
    动态修改内部混音的采样率.尽量减小不必要的重采样过程
  • 顺便提一下:google的Android不使用pulseaudio, 自己搞了个AudioFlinger, 
    不支持动态采样率, 还是会强制重采样到44100hz.

ALSA Project

ALSA 高级Linux音频架构

ALSA体系结构

  • alsa-lib 
    • Control Interface (/dev/snd/controlCX)控制接口:提供管理声卡注册和请求可用设备的通用功能
    • PCM Interface (/dev/snd/pcmCXDX) PCM接口:管理数字音频回放(playback)和录音(capture)的接口。它是开发数字音频程序最常用到的接口。
    • Raw MIDI Interface (/dev/snd/midiCXDX) Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器。这些API提供对声卡上MIDI总线的访问。这个原始接口基于MIDI事件工作,由程序员负责管理协议以及时间处理。
    • Timer Interface (/dev/snd/timer) 定时器(Timer)接口:为同步音频事件提供对声卡上时间处理硬件的访问。
    • Sequencer Interface (/dev/snd/seq)时序器(Sequencer)接口
    • Mixer Interface (/dev/snd/mixerCXDX) 混音器(Mixer)接口
  • alsa-utils 一些常用有用的工具 
    • amixer:可以配置底层提供的alsa接口。 
      • amixer contents 可以查看所有的控制接口和当前值
      • amixer controls 可以列出所有的接口
      • amixer cget (接口名) 可以获取指定接口值
      • amixer cset (接口名) 可以设置指定接口值
    • aplay/arecord 用于放音和录音的应用
    • alsactl: 用于保存alsa配置
    • alsamixer: alsamixer程序是amixer的ncurses的版本。
    • aconnect, aseqnet 是进行MIDI连接和查看连接端口列表。
  • alsa-plugins
  • alsa-tools

环境搭建

kernel基于mozart linux-3.0.8 [speaker-master, commit id:8334430651460305e884496d9356b8b6f132ebaa] 
板子使用 SP_M150_V2.0
  • Sound card support 
    • [*] Advanced Linux Sound Architecture —> 
      • [*] ALSA for SoC audio support —>
      • [*] ASoC support for Ingenic —> 
        • jz board type select —>
        • soc 4775 codec type select (Audio support for wattle with internal codec) = Audio support for wattle with internal codec
        • [*] Support DMIC for record
        • [*] JZ audio dma clear auto dirty memor
        • [*] JZ audio dma cyclic dma with hrtimer callback mode
    • [] Open Sound System (DEPRECATED) —>

测试

  • 先使用amixer contents查看接口, 并用amixer cset 设置对应的值
  • 使用aplay播放wav音频, 只支持wav格式
  • 使用arecord 录音

ALSA混音

Alsa本身也提供混音的plugin,dmix. 
应用程序不需要修改,添加配置dmix的/etc/asound.conf配置文件即可 
效果是可以有多个应用同时放音 
结构如下: App1 App2 ------------------------------------- Alsa-lib (dmix) User Space ------------------------------------- Alsa Kernel Space ------------------------------------- sound driver ------------------------------------- Hardware
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

ALSA编程

内核Alsa代码进行改动时会影响到应用程序的代码编写 
ALSA放音 
详细实例见: mozart_sdk/sources/application/molib_src/trunk/file-manager/player/mp3_utils.c
snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0); snd_pcm_hw_params_alloca(&params); //分配params结构体 snd_pcm_hw_params_any(handle, params);//初始化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); //设置16位采样精度 snd_pcm_hw_params_set_channels(handle, params, channels); //设置声道,1表示单声道,2表示立体声 snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir); //设置>频率 snd_pcm_hw_params(handle, params); while(flag){ ... snd_pcm_writei (handle, Output, n); } snd_pcm_drain(handle); snd_pcm_close(handle);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
ALSA录音 
详细实例见: mozart_sdk/sources/application/molib_src/trunk/utils/librecord/alsa/record.c ALSA配置参数 
有关调节音量,设置静音,详细实例见: /mozart_sdk/sources/application/molib_src/trunk/utils/volume/volume_alsa.c
char *card = "default"; snd_ctl_t *handle = NULL; snd_ctl_elem_value_t *ctl_elem_value; snd_ctl_open(&handle, card, 0); snd_ctl_elem_value_malloc (&ctl_elem_value); snd_ctl_elem_value_set_numid (ctl_elem_value, 11); snd_ctl_elem_value_set_interface (ctl_elem_value, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_value_set_name (ctl_elem_value, "Digital Playback mute"); snd_ctl_elem_value_set_integer(ctl_elem_value, 0, val); snd_ctl_elem_write(handle, ctl_elem_value); snd_ctl_close(handle); snd_ctl_elem_value_free (ctl_elem_value);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
参考