精华内容
下载资源
问答
  • linux音频
    千次阅读
    2020-12-29 16:53:43

    Linux音频设置

    参考文档:https://wiki.archlinux.org/index.php/Advanced_Linux_Sound_Architecture

    PCM 即脉冲编码调制 (Pulse Code Modulation)。在PCM 过程中,将输入的模拟信号进行采样、量化和编码,用二进制进行编码的数来代表模拟信号的幅度 ;接收端再将这些编码还原为原来的模拟信号。即数字音频的 A/D 转换包括三个过程 :采样,量化,编码。
    PCH(intel的集成南桥)

    主配置文件:/usr/share/alsa/alsa.conf
    系统级别配置文件:/etc/asound.conf
    用户级别配置文件:~/.asoundrc

    alsa.conf 配置文件会读取 /etc/asound.conf 和 ~/.asoundrc 这两个文件的内容。

    测试
    # amixer sset Master unmute
    如果出现error:
    amixer: Unable to find simple control ‘Master’, 0
    表示不能设置,可能是找不到声卡设备或者没有装声卡驱动。
    
    # amixer scontrols
    这一步的输出中如果没有包含 Master等选项,则说明默认声卡不对。
    
    安装工具及驱动

    在ALSA-utils的软件包提供了systemd单元的配置文件alsa-restore.service和alsa-state.service默认。
    这些将在安装过程中自动安装和激活(通过提供的软件包链接到sound.target)。两者都不默认运行,它们由用户决定采用哪种方法。选项如下:
    alsa-restore.service/var/lib/alsa/asound.state在启动时读取并在关闭时写入更新的值,前提是已经通过运行alsactl store某个点而存在。
    alsa-state.service(重新)在用户alsactl daemon至少有意识地启动一次的情况下,以守护程序模式启动actact以连续跟踪并保持音量变化。
    两种方法互斥,您可以根据需要选择两种方法之一。

    您可以使用检查它们的状态systemctl。

    # pacman -S alsa-utils          # 包含alsamixer和amixer工具,alsamixer提供图形界面对音频设备进行配置,amixer是用于更改音频设置的shell命令。
    # pacman -S alsa-firmware       # alsa-firmware包含某些声卡的固件,
    

    较新的笔记本可能安装sof-firmware和alsa-ucm-conf,它们使用Sound Open Firmware项目提供的固件来实现其驱动程序。

    查看设备

    查看有几个声卡设备

    # cat /proc/asound/cards
    

    查看声卡的card number和device number

    # aplay -h      # 查看帮助
    # aplay -l      # 列出所有的声卡和数字音频设备
    
    设置默认声卡
    # nvim /etc/asound.conf
    defaults.pcm.card 1
    defaults.pcm.device 0
    defaults.ctl.card 1
    
    关闭自动静音

    方法一

    # amixer -h       # 查看帮助
    # amixer sset Master unmute
    # amixer sset Speaker unmute
    # amixer sset Headphone unmute
    # amixer set Master 10%+    # Master增加10%的音量
    # amixer set Master 10%-    # Master减少10%的音量
    # amixer set Master 70%     # Master音量设定为70%
    

    方法二

    # alsamixer
    
    使用alsamixer,设置 Auto-Mute Mode 为 disable 即可。
    通过F1~F6、ESC和方向键进行操作。
    例如:F3:播放设置(扬声器设置) ,F4:录音设置(麦克风设置),F6:选择声卡
    F5显示所有设置选项:
        Master:主音量
        Headphone:头戴式耳机音量
        Speaker:扬声器音量
        PCM:音频解析的清晰度
        Mic Boost:增大音量,打开会易有杂音,语音聊天听不清时可以打开
        Capture:麦克风音量
        Auto-Mute Mode:自动调整为静音,一般选择disable
        Lookback Mixing: 音频模拟,几乎用不上,一般选择disable
    (在 alsamixer 中,下方标有 MM 的声道是静音的,而标有 00 的通道已经启用。)
    
    保存alsamixer或amixer的配置内容
    # sudo alsactl store       # 必须要加sudo权限,因为需要将配置保存到/etc目录下的asound.state文件,否则不会生效
    
    播放音频

    音频文件:/usr/share/sounds/test.wav

    # aplay -D hw:1,0 /usr/share/sounds/test.wav        # hw后的的数字分别代表卡号和设备号
    
    设置快捷键
    请看DWM配置篇
    
    更多相关内容
  • Linux音频编程.pdf

    2021-09-27 13:14:10
    Linux音频编程.pdf
  •   I2S全称Inter-IC Sond Bus,是飞利浦在1986年定义(1996年修订)的数字音频传输标准,用于数字音频数据在系统内部器件之间传输,例如编解码器Codec、DSP、数字输入/输出接口、ADC、DAC和数字滤波器等。 2.1.1.I2S...
  • 用于将多种音频类型从一种格式转换为另一种格式的 Linux CLI 工具。 它支持以下音频格式:3G2、3GP、8SVX、AAC、AC3、ADTS、AIFF、AL、AMB、AMR、APE、AU、AVR、BONK、CAF、CDR、CVU、DAT、DTS、DVMS、F32、F64 , ...
  • Linux 音频驱动

    千次阅读 2022-01-22 11:38:52
    Linux 音频驱动 修改设备树文件 配置Linux图形化文件 移植alsa-lib和alsa-utils文件 1.获取源文件 2.解压文件 3.配置文件 4.编译文件 5.安装文件 6.移植文件

    Linux 音频驱动

    硬件介绍

    WM8960与IMX6ULL之间有两个通信接口:I2C和I2S
    其中I2C用于配置WM8960
    I2S用于音频数据传输
    

    修改设备树文件

    编写I2C子节点设备树

    codec: wm8960@1a { 
    	compatible = "wlf,wm8960";
    	reg = <0x1a>;
    	clocks = <&clks IMX6UL_CLK_SAI2>;
    	clock-names = "mclk"; 6 wlf,shared-lrclk; 
    };
    

    编写I2S子节点设备树

    sai2: sai@0202c000 { 
    	compatible = "fsl,imx6ul-sai", 
    	"fsl,imx6sx-sai"; 
    	reg = <0x0202c000 0x4000>;
    	interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
    	clocks = <&clks IMX6UL_CLK_SAI2_IPG>,
    	<&clks IMX6UL_CLK_DUMMY>,
    	<&clks IMX6UL_CLK_SAI2>,
    	<&clks 0>, <&clks 0>;
    	clock-names = "bus", "mclk0", "mclk1", "mclk2", "mclk3";
    	dma-names = "rx", "tx";
    	dmas = <&sdma 37 24 0>, <&sdma 38 24 0>;
    	status = "disabled";
    };
    
    &sai2 { 
    	pinctrl-names = "default"; 
    	pinctrl-0 = <&pinctrl_sai2
    	&pinctrl_sai2_hp_det_b>;
    	assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>,
    	<&clks IMX6UL_CLK_SAI2>;
    	assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
    	assigned-clock-rates = <0>, <12288000>;
    	status = "okay";
    };
    
    pinctrl_sai2: sai2grp { 
    	fsl,pins = < 
    		MX6UL_PAD_JTAG_TDI__SAI2_TX_BCLK 0x17088
    		MX6UL_PAD_JTAG_TDO__SAI2_TX_SYNC 0x17088
    		MX6UL_PAD_JTAG_TRST_B__SAI2_TX_DATA 0x11088
    		MX6UL_PAD_JTAG_TCK__SAI2_RX_DATA 0x11088
    		MX6UL_PAD_JTAG_TMS__SAI2_MCLK 0x17088
    	>;
    };
    
    pinctrl_sai2_hp_det_b: sai2_hp_det_grp {
    	fsl,pins = <
    		MX6ULL_PAD_SNVS_TAMPER4__GPIO5_IO04 0x17059
    	>;
    };
    
    sound { 
    	compatible = "fsl,imx6ul-evk-wm8960", 
    		"fsl,imx-audio-wm8960"; 
    	model = "wm8960-audio"; 
    	cpu-dai = <&sai2>;
    	audio-codec = <&codec>;
    	asrc-controller = <&asrc>;
    	codec-master; 
    	gpr = <&gpr 4 0x100000 0x100000>;
    	/*
    	* hp-det = <hp-det-pin hp-det-polarity>;
    	* hp-det-pin: JD1 JD2 or JD3
    	* hp-det-polarity = 0: hp detect high for headphone
    	* hp-det-polarity = 1: hp detect high for speaker
    	*/
    	hp-det = <3 0>;
    	/*hp-det-gpios = <&gpio5 4 0>;
    	mic-det-gpios = <&gpio5 4 0>;*/
    	audio-routing =
    		"Headphone Jack", "HP_L",
    		"Headphone Jack", "HP_R",
    		"Ext Spk", "SPK_LP",
    		"Ext Spk", "SPK_LN",
    		"Ext Spk", "SPK_RP",
    		"Ext Spk", "SPK_RN",
    		"LINPUT2", "Mic Jack",
    		"LINPUT3", "Mic Jack",
    		"RINPUT1", "Main MIC",
    		"RINPUT2", "Main MIC",
    		"Mic Jack", "MICB",
    		"Main MIC", "MICB",
    		"CPU-Playback", "ASRC-Playback",
    		"Playback", "CPU-Playback",
    		"ASRC-Capture", "CPU-Capture",
    		"CPU-Capture", "Capture";
    };
    

    配置Linux图形化文件

    取消ALSA模拟OSS API

    -> Device Drivers 
    	-> Sound card support (SOUND [=y]) 
    		-> Advanced Linux Sound Architecture (SND [=y]) 
    			-> <> OSS Mixer API //不选择
    			-> <> OSS PCM (digital audio) API //不选择
    

    使能WM8960驱动

    -> Device Drivers 
    	-> Sound card support (SOUND [=y]) 
    		-> Advanced Linux Sound Architecture (SND [=y]) 
    			-> ALSA for SoC audio support (SND_SOC [=y]) 
    				-> SoC Audio for Freescale CPUs
    					-> <*> Asynchronous Sample Rate Converter (ASRC) module support //选中
    					-> <*> SoC Audio support for i.MX boards with wm8960 //选中
    

    移植alsa-lib和alsa-utils文件

    1.获取源文件

    alsa-lib-1.2.2.tar.bz2 
    alsa-utils-1.2.2.tar.bz2
    

    2.解压文件

    tar -vxjf alsa-lib-1.2.2.tar.bz2
    tar -vxjf alsa-utils-1.2.2.tar.bz2
    

    3.配置文件

    //进入 alsa-lib 源码目录
    cd alsa-lib-1.2.2/ 
    ./configure 
    --host=arm-linux-gnueabihf 
    --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib 
    --with-configdir=/usr/share/arm-alsa
    
    //进入 alsa-utils源码目录
    cd alsa-utils-1.2.2/ 
    ./configure 
    --host=arm-linux-gnueabihf 
    --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/alsa-utils 
    --with-alsa-inc-prefix=/home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib/include/ 
    --with-alsaprefix=/home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib/lib/ 
    --disable-alsamixer 
    --disable-xmlto
    

    4.编译文件

    make
    

    5.安装文件

    sudo make install
    

    6.移植文件

    移植alsa-lib文件
    cd alsa-lib //进入 alsa-lib
    sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/lib/ -af
    cd /usr/share/arm-alsa //进入 arm-alsa 目录,拷贝配置文件
    sudo cp * /home/zuozhongkai/linux/nfs/rootfs/usr/share/arm-alsa/ -raf
    
    移植alsa-utils文件
    cd alsa-utils
    sudo cp bin/* /home/zuozhongkai/linux/nfs/rootfs/bin/ -rfa
    sudo cp sbin/* /home/zuozhongkai/linux/nfs/rootfs/sbin/ -rfa
    sudo cp share/* /home/zuozhongkai/linux/nfs/rootfs/usr/share/ -rfa
    在开发板根目录/etc/profile添加内容
    export ALSA_CONFIG_PATH=/usr/share/arm-alsa/alsa.conf
    

    移植zlib库和mplayer

    1.获取源文件

    zlib-1.2.11.tar.gz
    MPlayer-1.4.tar.gz
    

    2.解压文件

    tar -vxzf zlib-1.2.11.tar.gz
    tar -vxzf MPlayer-1.4.tar.gz
    

    3.配置文件

    cd zlib-1.2.11/ //进去 zlib 源码
    CC=arm-linux-gnueabihf-gcc 
    LD=arm-linux-gnueabihf-ld 
    AD=arm-linux-gnueabihf-as 
    ./configure 
    --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/zlib
    
    cd MPlayer-1.4/ //进去 mplayer 源码
    ./configure 
    --cc=arm-linux-gnueabihf-gcc 
    --host-cc=gcc 
    --target=arm-linux-gnueabihf 
    --disable-ossaudio 
    --enable-alsa 
    --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/mplayer 
    --extra-cflags=
    "-I /home/zuozhongkai/linux/IMX6ULL/tool/zlib/include -I /home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib/include"
     --extra-ldflags=
     "-L/home/zuozhongkai/linux/IMX6ULL/tool/zlib/lib -Iz -L/home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib/lib -lasound" 
     --enable-fbdev 
     --disable-mencoder
    

    4.编译文件

    make //编译
    

    5.安装文件

    make install
    

    6.移植文件

    将可执行文件拷贝到跟文件系统/bin目录下
    sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/lib/ -rfa
    
    展开全文
  • 本文针对Wi-Fi片上系统(SoC)AR9331和音频芯片WM8904组成的硬件平台,设计了基于ALSA架构的嵌入式Linux音频驱动程序,采用模块化的程序架构提高了驱动的可移植性,通过适配PCM接口减少了驱动代码量,设计环形DMA...
  • 里面含有Linux操作系统下利用C语言进行音频和视频处理的代码,包括音频的输入、处理、输出等,比较详细。
  • linux系统环境下实现录音放音功能的源码
  • Linux音频工具揽胜.pdf

    2021-09-07 00:53:16
    Linux音频工具揽胜.pdf
  • Linux 音频驱动(一) ASoC音频框架简介

    千次阅读 2021-02-19 15:49:22
    ASoC音频驱动构成3. PCM数据流4. 数据结构简介5. ASoC音频驱动注册流程 1. ALSA简介 Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制。 ALSA ...

    1. ALSA简介

    Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制。
    ALSA Library API:alsa 用户库接口,常见有 tinyalsa、alsa-lib。
    ALSA CORE:alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC)。
    ASoC CORE:asoc 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系。
    Hardware Driver:音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec。

    注1:注意区分逻辑设备、硬件设备
    注2:ASoC,ALSA System on Chip。

    在这里插入图片描述

    2. ASoC音频驱动构成

    ASoC音频驱动由三部分构成:platform,codec,machine。
    在这里插入图片描述

    Platform:Platform驱动程序包括音频DMA引擎驱动程序,数字音频接口(DAI)驱动程序(例如I2S,AC97,PCM)以及该平台的任何音频DSP驱动程序。

    1. cpu dai:在嵌入式系统里面通常指CPU的I2S、PCM总线控制器,负责将音频数据从I2S tx FIFO搬运到CODEC(回放的情形,录制则方向相反)。cpu_dai通过snd_soc_register_dai()来注册。

    注:DAI是Digital Audio Interface的缩写,分为cpu_dai和codec_dai,这两者通过I2S/PCM总线连接;
           AIF是Audio Interface的缩写,一般分为I2S和PCM接口。

    1. pcm dma:负责将dmabuffer中的音频数据搬运到I2S tx FIFO,这部分的逻辑比较复杂,以下几篇会对它详细阐述。音频dma驱动通过snd_soc_register_platform()来注册。值得留意的是:某些情形下是不需要dma操作的,比如Modem和CODEC直连,因为Modem本身已经把数据送到PCM FIFO了,这时只需启动codec_dai接收数据即可;该情形下,Machine驱动dai_link中需要指定.platform_name = “snd-soc-dummy”, 这是虚拟出来的platform驱动,实现见sound/soc/soc-utils.c。

    Codec:Codec驱动程序独立于平台,包含音频控件,音频接口功能,编解码器DAPM定义和编解码器IO功能。如果需要,该类可扩展至BT,FM和MODEM IC。Codec类驱动程序应该是可以在任何体系结构和机器上运行的通用代码。
    对于Playback来说,userspace送过来的PCM数据是经过抽样量化出来的数字信号,在codec经过DAC转换成模拟信号送到外放耳机输出,这样我们就可以听到声音了。Codec字面意思是编解码器,但芯片里面的功能部件很多,常见的有AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的codec芯片还有EQ、DSP、SRC、DRC、AGC、Echo canceller、Noise suppression等部件。

    注:DAPM,Dynamic Audio Power Management,动态音频电源管理,为移动Linux设备设计,使得音频系统任何时候都工作在最低功耗状态。

    Machine:Machine 驱动程序充当描述和绑定其他组件驱动程序以形成ALSA“声卡设备”的粘合剂。它可以处理任何机器特定的控制和机器级音频事件(例如,在播放开始时打开放大器)。
    Machine 可以理解为对开发板的抽象,开发板可能包括多个声卡,对应Machine部分包含多个link。
    Machine 指某一款机器,它把cpu_dai、codec_dai、modem_dai各个音频接口通过定义dai_link链结起来,然后注册snd_soc_card。和上面两个不一样,Platform和CODEC驱动一般是可以重用的,而Machine有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:DAIs之间的链结;通过某个GPIO打开Amplifier;通过某个GPIO检测耳机插拔;使用某个时钟如MCLK/External OSC作为I2S、CODEC模块的基准时钟源等等。

    dai_link:machine驱动中定义的音频数据链路,它指定用到的cpu_dai、codec_dai、platform、codec分别是什么。

    3. PCM数据流

    对于回放(Playback)的情形,PCM数据流向大致如下:

    1. 对于Linux来说,由于分为 user space 和kernel space,而且两者之间不能随便互相访问。因此用户如果播放音频,则需要调用copy_from_user()将用户数据从user space拷贝到kernel space (DMA Buffer)。
    2. DMA 负责将DMA Buffer中的音频数据搬运到I2S TX FIFO。
    3. 通过I2S总线,将音频数据传送到Codec。
    4. Codec内部经过DAC转换,将模拟信号传到扬声器SPK(头戴式耳机HP、耳塞式耳机Earp)
      在这里插入图片描述

    对于录音(Capture)的情形,PCM数据流向是方向如下:
    在这里插入图片描述

    4. 数据结构简介

    platform驱动: platform驱动为cpu部分的控制代码,其抽象出两个结构体snd_soc_dai_driver和snd_soc_platform_driver;

    codec驱动: codec驱动是编解码器部分的控制代码,其抽象出两个结构体分别为snd_soc_dai_driver和snd_soc_codec_driver;

    machine驱动: machine驱动是控制管理platform和codec之间的连接匹配,管理控件(controls)、部件(widgets)以及routes,其抽象的结构体为snd_soc_card。

    5. ASoC音频驱动注册流程

    在这里插入图片描述

    先贴出snd_soc_register_card()的流程图(点击放大或保存到本地),后面的文章再详细分析。其中关键的步骤做了注释和标红,阅读代码时应重点关注。
    在这里插入图片描述

    展开全文
  • 面向Wi-Fi音频应用的嵌入式Linux音频驱动设计.pdf
  • 基于SEP6200的LINUX音频驱动设计(英文).pdf
  • linux音频编程

    2012-06-21 10:00:45
    linux 音频编程 入门级,音频驱动程序接口介绍文档。
  • 用好Linux音频播放器——XMMS.pdf
  • Linux音频设备驱动

    2018-08-19 14:53:23
    linux音频驱动讲解的非常到为,写的非常到好,这是大神级别到
  • 本书对基于TLV320AIC23B的音频开发做了详细到讲解,相信能帮助很多初学者在驱动编程方面到学习。
  • Linux 音频驱动(一) ASoC音频框架简介中,我们给出了回放(Playback)PCM数据流示意图: 对于Linux来说,由于分为 user space 和kernel space,而且两者之间不能随便互相访问。因此用户如果播放音频,则需要调用...

    1. 前言

    本文,我们将以回放(Playback,播放音频)为例,讲解PCM Data是如何从用户空间到内核空间,最后传递到Codec。
    Linux 音频驱动(一) ASoC音频框架简介中,我们给出了回放(Playback)PCM数据流示意图:

    在这里插入图片描述

    1. 对于Linux来说,由于分为 user space 和kernel space,而且两者之间不能随便互相访问。因此用户如果播放音频,则需要调用copy_from_user()将用户数据从user space拷贝到kernel space (DMA Buffer)。
    2. DMA 负责将DMA Buffer中的音频数据搬运到I2S TX FIFO。
    3. 通过I2S总线,将音频数据传送到Codec。
    4. Codec内部经过DAC转换,将模拟信号传到扬声器SPK(头戴式耳机HP、耳塞式耳机Earp)。

    下面基于源码讲解PCM Data Flow。

    2. PCM Data Flow

    内核版本:Kernel 版本:3.10
    内核源码文件:
             ./kernel-3.10/sound/core/device.c
             ./kernel-3.10/sound/core/init.c
             ./kernel-3.10/sound/core/pcm.c
             ./kernel-3.10/sound/core/pcm_lib.c
             ./kernel-3.10/sound/core/pcm_native.c
             ./kernel-3.10/sound/soc/soc-pcm.c
    Tinyalsa源码文件:
             ./external/tinyalsa/pcm.c
             ./external/kernel-headers/original/uapi/sound/asound.h

    User Space

    在我的源码包里,用户空间应用程序使用的是 tinyalsa提供的接口write PCM Data,即播放音频文件。
    Write PCM逻辑设备是通过 ioctl() 函数完成的,即应用程序将需要播放的音频数据通过pcm_write() --> ioctl() 传递到内核。

    // ./external/kernel-headers/original/uapi/sound/asound.h, line 448
    struct snd_xferi {
    	snd_pcm_sframes_t result;
    	void __user *buf;
    	snd_pcm_uframes_t frames;
    };
    // ./external/tinyalsa/pcm.c, line 483
    int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
    {
        struct snd_xferi x;
        ......
        x.buf = (void*)data;
        x.frames = count / (pcm->config.channels *
                            pcm_format_to_bits(pcm->config.format) / 8);
        ......
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x);
        ......
    }
    

    音频数据中的几个重要概念:
    Format:样本长度(采样精度 or 采样深度),音频数据最基本的单位,常见的有 8 位和 16 位;
    Channel:声道数,分为单声道 mono 和立体声stereo;
    Frame:帧,构成一个完整的声音单元,Frame = Format * Channel;
    Rate:又称 sample rate:采样率,即每秒的采样次数,针对帧而言;
    Period size:周期,每次硬件中断处理音频数据的帧数,对于音频设备的数据读写,以此为单位;
    Buffer size:数据缓冲区大小,这里指runtime 的 buffer size,而不是结构体 snd_pcm_hardware 中定义的buffer_bytes_max;一般来说 buffer_size = period_size * period_count,period_count 相当于处理完一个 buffer 数据所需的硬件中断次数。

    为了通过系统调用ioctl()传递音频数据,定义了struct snd_xferi xx.buf指向本次要播放的音频数据,x.frames表示本次音频数据总共有多少帧(frame)。

    Kernel Space

    通过系统调用ioctl()传递数据到内核,在内核空间是PCM逻辑设备对应的snd_pcm_f_ops[0].unlocked_ioctl()

    // ./kernel-3.10/sound/core/pcm_native.c, line 3481
    const struct file_operations snd_pcm_f_ops[2] = {
    	{
    		.owner =		THIS_MODULE,
    		.write =		snd_pcm_write,
            ......
    		.unlocked_ioctl =	snd_pcm_playback_ioctl,
            ......
    	},
    	{
    		.owner =		THIS_MODULE,
    		.read =			snd_pcm_read,
            ......
    		.unlocked_ioctl =	snd_pcm_capture_ioctl,
            ......
    	}
    };
    

    snd_pcm_playback_ioctl() 直接调用了snd_pcm_playback_ioctl1()

    // ./kernel-3.10/sound/core/pcm_native.c, line 2784
    static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
    	struct snd_pcm_file *pcm_file;
    	pcm_file = file->private_data;
        ......
    	return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd, (void __user *)arg);
    }
    

    snd_pcm_playback_ioctl1()函数:
    a. 定义struct snd_xferi xferi,为了获取用户空间传来的arg;
    b. 调用put_user() 清除snd_xferi.result状态;
    c. 调用copy_from_user()将取用户空间的arg拷贝到内核空间,即拷贝音频数据存储空间的指针buf和音频数据帧数frames;
    d. 调用snd_pcm_lib_write()
    e. 调用put_user() 回填write结果_xferi->result。

    // ./kernel-3.10/sound/core/pcm_native.c, line 2624
    static int snd_pcm_playback_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
    {
        ......
    	switch (cmd) {
    	case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
    	{
    		struct snd_xferi xferi;
    		struct snd_xferi __user *_xferi = arg;
    		struct snd_pcm_runtime *runtime = substream->runtime;
    		snd_pcm_sframes_t result;
    		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
    			return -EBADFD;
    		if (put_user(0, &_xferi->result))
    			return -EFAULT;
    		if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
    			return -EFAULT;
    		result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
    		__put_user(result, &_xferi->result);
    		return result < 0 ? result : 0;
    	}
    	......
    }
    

    snd_pcm_lib_write()做一些参数检查后调用snd_pcm_lib_write1()。注意调用snd_pcm_lib_write1()时传入的最后一个参数snd_pcm_lib_write_transfer该函数完成音频数据从 kernel space 到 DMA Buffer 的传输

    // ./kernel-3.10/sound/core/pcm_lib.c, line 2101
    snd_pcm_sframes_t snd_pcm_lib_write(struct snd_pcm_substream *substream, const void __user *buf, snd_pcm_uframes_t size)
    {
    	struct snd_pcm_runtime *runtime;
    	int nonblock;
    	......
    	nonblock = !!(substream->f_flags & O_NONBLOCK);
    	......
    	return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer);
    }
    

    如下snd_pcm_lib_write1()代码段中,第40行调用transfer(),即调用snd_pcm_lib_write_transfer()将音频数据从 kernel space 拷贝到 DMA Buffer。然后,在第49行,调用snd_pcm_start()启动DMA传输,将音频数据从 DMA Buffer 拷贝到 I2S TX FIFO

    // ./kernel-3.10/sound/core/pcm_lib.c, line 1985
    static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream, unsigned long data,
    					    snd_pcm_uframes_t size, int nonblock, transfer_f transfer)
    {
    	struct snd_pcm_runtime *runtime = substream->runtime;
    	snd_pcm_uframes_t xfer = 0;
    	snd_pcm_uframes_t offset = 0;
    	snd_pcm_uframes_t avail;
    	int err = 0;
    
    	if (size == 0)
    		return 0;
        ......
    	runtime->twake = runtime->control->avail_min ? : 1;
    	if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
    		snd_pcm_update_hw_ptr(substream);
    	avail = snd_pcm_playback_avail(runtime);
    	while (size > 0) {
    		snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
    		snd_pcm_uframes_t cont;
    		if (!avail) {
    			if (nonblock) {
    				err = -EAGAIN;
    				goto _end_unlock;
    			}
    			runtime->twake = min_t(snd_pcm_uframes_t, size,
    					runtime->control->avail_min ? : 1);
    			err = wait_for_avail(substream, &avail);
    			if (err < 0)
    				goto _end_unlock;
    		}
    		frames = size > avail ? avail : size;
    		cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
    		if (frames > cont)
    			frames = cont;
    		......
    		appl_ptr = runtime->control->appl_ptr;
    		appl_ofs = appl_ptr % runtime->buffer_size;
    		snd_pcm_stream_unlock_irq(substream);
    		err = transfer(substream, appl_ofs, data, offset, frames);  //将音频数据从 kernel space 拷贝到 DMA Buffer
    		snd_pcm_stream_lock_irq(substream);
    		......
    		offset += frames;
    		size -= frames;
    		xfer += frames;
    		avail -= frames;
    		if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
    		    snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {
    			err = snd_pcm_start(substream);  //启动DMA传输,将音频数据从 DMA Buffer 拷贝到 I2S TX FIFO
    			if (err < 0)
    				goto _end_unlock;
    		}
    	}
        ......
    }
    

    snd_pcm_lib_write_transfer()中:
    a. 如果有设定过substream->ops->copy回调函数,则执行substream->ops->copy()将音频数据从 kernel space 拷贝到 DMA Buffer。
    b. 如果没有设定substream->ops->copy回调函数,则直接调用copy_from_user()将音频数据从 kernel space 拷贝到 DMA Buffer。

    // ./kernel-3.10/sound/core/pcm_lib.c, line 1962
    static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream, unsigned int hwoff,
    				      unsigned long data, unsigned int off, snd_pcm_uframes_t frames)
    {
    	struct snd_pcm_runtime *runtime = substream->runtime;
    	int err;
    	char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
    	if (substream->ops->copy) {
    		if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
    			return err;
    	} else {
    		char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
    		if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
    			return -EFAULT;
    	}
    	return 0;
    }
    

    注:本例中,substream->ops->copy回调函数是在soc_new_pcm()中设置的。在soc_new_pcm()中,如果有设定platform->driver->ops (即PCM DMA驱动操作函数集),则PCM逻辑设备的某些操作函数将会被platform->driver->ops中覆盖掉,如下代码段第26行。

    // ./kernel-3.10/sound/soc/soc-pcm.c, line 2005
    int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
    {
        ......
    	/* ASoC PCM operations */
    	if (rtd->dai_link->dynamic) {
    		rtd->ops.open		= dpcm_fe_dai_open;
    		rtd->ops.hw_params	= dpcm_fe_dai_hw_params;
    		rtd->ops.prepare	= dpcm_fe_dai_prepare;
    		rtd->ops.trigger	= dpcm_fe_dai_trigger;
    		rtd->ops.hw_free	= dpcm_fe_dai_hw_free;
    		rtd->ops.close		= dpcm_fe_dai_close;
    		rtd->ops.pointer	= soc_pcm_pointer;
    		rtd->ops.ioctl		= soc_pcm_ioctl;
    	} else {
    		rtd->ops.open		= soc_pcm_open;
    		rtd->ops.hw_params	= soc_pcm_hw_params;
    		rtd->ops.prepare	= soc_pcm_prepare;
    		rtd->ops.trigger	= soc_pcm_trigger;
    		rtd->ops.hw_free	= soc_pcm_hw_free;
    		rtd->ops.close		= soc_pcm_close;
    		rtd->ops.pointer	= soc_pcm_pointer;
    		rtd->ops.ioctl		= soc_pcm_ioctl;
    	}
    
    	if (platform->driver->ops) {
    		rtd->ops.ack		= platform->driver->ops->ack;
    		rtd->ops.copy		= platform->driver->ops->copy;
    		rtd->ops.silence	= platform->driver->ops->silence;
    		rtd->ops.page		= platform->driver->ops->page;
    		rtd->ops.mmap		= platform->driver->ops->mmap;
    	}
        ......
    }
    

    现在看一下snd_pcm_start()如何启动DMA传输的。
    snd_pcm_start()直接调用了snd_pcm_action()。此处要注意snd_pcm_action()的第一个参数snd_pcm_action_startsnd_pcm_action()在多个地方会被调用,要注意其被调用时的第一个参数是什么。

    // ./kernel-3.10/sound/core/pcm_native.c, line 891
    static struct action_ops snd_pcm_action_start = {
    	.pre_action = snd_pcm_pre_start,
    	.do_action = snd_pcm_do_start,
    	.undo_action = snd_pcm_undo_start,
    	.post_action = snd_pcm_post_start
    };
    // ./kernel-3.10/sound/core/pcm_native.c, line 904
    int snd_pcm_start(struct snd_pcm_substream *substream)
    {
    	return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
    }
    

    snd_pcm_action()中会调用snd_pcm_action_group()snd_pcm_action_single()。为简化讲解,我们关注snd_pcm_action_single()snd_pcm_action_single()函数非常简单,就是执行struct action_ops *ops指向的回调函数集,本例中为上文提到的snd_pcm_action_start

    // ./kernel-3.10/sound/core/pcm_native.c, line 785
    static int snd_pcm_action(struct action_ops *ops, struct snd_pcm_substream *substream, int state)
    {
    	int res;
    
    	if (snd_pcm_stream_linked(substream)) {
    		if (!spin_trylock(&substream->group->lock)) {
    			spin_unlock(&substream->self_group.lock);
    			spin_lock(&substream->group->lock);
    			spin_lock(&substream->self_group.lock);
    		}
    		res = snd_pcm_action_group(ops, substream, state, 1);
    		spin_unlock(&substream->group->lock);
    	} else {
    		res = snd_pcm_action_single(ops, substream, state);
    	}
    	return res;
    }
    // ./kernel-3.10/sound/core/pcm_native.c, line 765
    static int snd_pcm_action_single(struct action_ops *ops,
    				 struct snd_pcm_substream *substream,
    				 int state)
    {
    	int res;
    	
    	res = ops->pre_action(substream, state);
    	if (res < 0)
    		return res;
    	res = ops->do_action(substream, state);
    	if (res == 0)
    		ops->post_action(substream, state);
    	else if (ops->undo_action)
    		ops->undo_action(substream, state);
    	return res;
    }
    

    我们重点看一下ops->do_action(),即snd_pcm_do_start()。在前面soc_new_pcm()代码段中,我们看到substream->ops->trigger()被设定为soc_pcm_trigger(),该函数依次调用codec_dai driver的trigger函数、pcm_dma的trigger函数、cpu_dai driver的trigger函数

    // ./kernel-3.10/sound/core/pcm_native.c, line 862
    static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state)
    {
    	if (substream->runtime->trigger_master != substream)
    		return 0;
    	return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
    }
    
    // ./kernel-3.10/sound/core/soc-pcm.c, line 609
    static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
    {
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
    	struct snd_soc_platform *platform = rtd->platform;
    	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
    	struct snd_soc_dai *codec_dai = rtd->codec_dai;
    	int ret;
    
    	if (codec_dai->driver->ops->trigger) {
    		ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);  //调用codec_dai driver的trigger函数 (可选的)
    		if (ret < 0)
    			return ret;
    	}
    
    	if (platform->driver->ops && platform->driver->ops->trigger) {
    		ret = platform->driver->ops->trigger(substream, cmd);  //调用pcm_dma的trigger函数
    		if (ret < 0)
    			return ret;
    	}
    
    	if (cpu_dai->driver->ops->trigger) {
    		ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai);  //调用cpu_dai driver的trigger函数 (可选的)
    		if (ret < 0)
    			return ret;
    	}
    	return 0;
    }
    

    问题:为什么执行trigger函数的顺序是codec_dai --> pcm_dma --> cpu_dai 呢 ?是否可以调整顺序 ?

    思考:
    是可以调整的。(下面的思考是我个人想法,其实我倾向于相信这部分的设计是大神有意为之的,可能我还没理解吧。)
    首先,codec_dai和cpu_dai的trigger函数是可选的。查看struct snd_soc_dai_ops结构体的源码,可以看到其中PCM operation函数集注释说“ALSA PCM audio operations - all optional.”。
    其次,假设codec_dai和cpu_dai的trigger都有定义,个人认为相对理想的顺序应该是codec_dai --> cpu_dai --> pcm_dma。因为,当我们启动DMA将音频数据拷贝到cpu_dai(I2S TX Buffer)时,如果该硬件还没有ready,那DMA搬过去的数据可能会丢失。同理,如果cpu_dai开始传数据了,而codec_dai(I2S RX Buffer)没有ready,那数据也有可能丢失。理论上应该是在启动DMA之前,数据流下一接收装置应该已经ready。所以,我才会说相对理想的trigger顺序应该是codec_dai --> cpu_dai --> pcm_dma。
    (我目前使用的MTK平台没有定义codec_dai和cpu_dai的trigger函数,没有办法研究该顺序是否有意义。有条件的读者可以调整顺序,看是否会对音频播放造成问题。)

    下面介绍trigger函数的内容不同的SoC平台会有差异,本例基于MTK平台的源码分析。
    本例中,codec_dai driver的trigger函数、pcm_dma的trigger函数、cpu_dai driver的trigger函数分别如下:

    codec_dai driver的trigger函数mt6323_codec_trigger(),虽然该函数有定义,但是该函数没有做任何事情。

    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c, line 1070
    static struct snd_soc_dai_driver mtk_6331_dai_codecs[] =
    {
        {
            .name = MT_SOC_CODEC_TXDAI_NAME,
            .ops = &mt6323_aif1_dai_ops,
            ......
        },
        ......
    }
    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c, line 1063
    static const struct snd_soc_dai_ops mt6323_aif1_dai_ops =
    {
        .startup    = mt63xx_codec_startup,
        .prepare   = mt63xx_codec_prepare,
        .trigger     = mt6323_codec_trigger,
    };
    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c, line 1048
    static int mt6323_codec_trigger(struct snd_pcm_substream *substream , int command , struct snd_soc_dai *Daiport)
    {
        switch (command)
        {
            case SNDRV_PCM_TRIGGER_START:
            case SNDRV_PCM_TRIGGER_RESUME:
            case SNDRV_PCM_TRIGGER_STOP:
            case SNDRV_PCM_TRIGGER_SUSPEND:
                break;
        }
    
        return 0;
    }
    

    pcm_dma的trigger函数mtk_pcm_I2S0dl1_trigger()

    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_dl1_i2s0Dl1.c, line 730
    static struct snd_soc_platform_driver mtk_I2S0dl1_soc_platform =
    {
        .ops        = &mtk_I2S0dl1_ops,
        .pcm_new    = mtk_asoc_pcm_I2S0dl1_new,
        .probe      = mtk_afe_I2S0dl1_probe,
    };
    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_dl1_i2s0Dl1.c, line 715
    static struct snd_pcm_ops mtk_I2S0dl1_ops =
    {
        ......
        .trigger =  mtk_pcm_I2S0dl1_trigger,
        .pointer =  mtk_pcm_I2S0dl1_pointer,
        .copy =     mtk_pcm_I2S0dl1_copy,
        ......
    };
    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_dl1_i2s0Dl1.c, line 529
    static int mtk_pcm_I2S0dl1_trigger(struct snd_pcm_substream *substream, int cmd)
    {
        //printk("mtk_pcm_I2S0dl1_trigger cmd = %d\n", cmd);
    
        switch (cmd)
        {
            case SNDRV_PCM_TRIGGER_START:
            case SNDRV_PCM_TRIGGER_RESUME:
                return mtk_pcm_I2S0dl1_start(substream);  // 启动 dma 传输
            case SNDRV_PCM_TRIGGER_STOP:
            case SNDRV_PCM_TRIGGER_SUSPEND:
                return mtk_pcm_I2S0dl1_stop(substream);  // 停止 dma 传输
        }
        return -EINVAL;
    }
    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_dl1_i2s0Dl1.c, line 493
    static int mtk_pcm_I2S0dl1_start(struct snd_pcm_substream *substream)
    {
        struct snd_pcm_runtime *runtime = substream->runtime;
        printk("%s\n", __func__);
        // here start digital part
    
        SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I05, Soc_Aud_InterConnectionOutput_O00);
        SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I06, Soc_Aud_InterConnectionOutput_O01);
        SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I05, Soc_Aud_InterConnectionOutput_O03);
        SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I06, Soc_Aud_InterConnectionOutput_O04);
    
        SetIrqEnable(Soc_Aud_IRQ_MCU_MODE_IRQ1_MCU_MODE, true);
    
        SetSampleRate(Soc_Aud_Digital_Block_MEM_DL1, runtime->rate);
        SetChannels(Soc_Aud_Digital_Block_MEM_DL1, runtime->channels);
        SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_DL1, true);
    
        EnableAfe(true);
        ......
        return 0;
    }
    

    cpu_dai driver的trigger函数:本例中没有实现该函数

    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_dai_stub.c, line 98
    static struct snd_soc_dai_driver mtk_dai_stub_dai[] =
    {
        {
            ......
            .name = MT_SOC_DL1DAI_NAME,
            .ops = &mtk_dai_stub_ops,
        },
        ......
    }
    // ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_dai_stub.c, line 93
    static struct snd_soc_dai_ops mtk_dai_stub_ops =
    {
        .startup    = multimedia_startup,
    };
    

    3. 总结

    回放(Playback)PCM数据流示意图:

    在这里插入图片描述
    最后简单总结一下PCM write时的数据传递:
      i.  应用程序调用tinyalsa提供的接口pcm_write()-->ioctl()将需要回放的音频数据指针帧数传递给内核。
     ii.  内核在snd_pcm_lib_write_transfer()函数中使用copy_from_user()将音频数据从user space拷贝到kernel space,即从应用程序的buffer拷贝到DMA buffer
    iii.  内核在snd_pcm_start()中启动DMA传输,将音频数据从DMA buffer拷贝到I2S TX FIFO。(实质上是通过pcm_dma的trigger函数来做的。)

    展开全文
  • 基于SEP4020的嵌入式Linux音频驱动程序设计.pdf
  • Linux音频编程[定义].pdf
  • 基于Wi-Fi SoC的嵌入式Linux音频驱动设计与实现.pdf
  • 论述了Linux操作系统中声卡驱动程序的设计方法,主要介绍了基于OSS的声卡驱动设计原理以及Linux操作系统中声卡驱动程序的接口函数。针对具体硬件平台编写了相应的驱动程序,并介绍了在Linux操作系统中使用声卡设备的...
  • 1)实验平台:正点原子阿尔法Linux开发板 ...3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 ...音频是我们最常用到的功能,音频也是linux和安卓的重点应用场合。I
  • 嵌入式Linux音频驱动开发

    万次阅读 多人点赞 2017-12-29 13:52:00
    1.嵌入式音频系统硬件连接 ...ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构 在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了al
  • 个人总结的 linux 音频驱动ASoC流程图
  • linux音频子系统--概述

    千次阅读 2018-01-30 23:11:30
    ALSA是目前linux的主流音频体系架构; 是一个有社区维护的开源项目。 http://www.alsa-project.org/ 包括: 1.内核驱动包 alsa-driver 2.用户空间库 alsa-lib 3.附加库插件包 alsa-libplugins 4.音频...
  • Linux音频编程指南

    2011-09-09 09:12:13
    详细介绍了linux音频编程的方法,浅显易懂!
  • 在上小节我们分析了Adndroid系统音频的框架,这么一个复杂...在该课时接下来的所有小节都会讲解linux音频驱动程序。该小节先讲解一下alsa音频驱动的框架: 在编写应用程序的时候,我们都是使用标准的open,read,......
  • linux音频子系统 - pcm设备

    千次阅读 2017-12-11 17:15:21
    根据此原理,在音频领域的数字音频就用pcm设备来代表,pcm也是一种音频格式,可以自定义通道数,采样率,采样精度;我们经常采用的I2S格式其实属于pcm的一种,不过I2S规定了只有2通道。 音频的采样率(r

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 76,847
精华内容 30,738
关键字:

linux音频