当前位置: 首页 > news >正文

Android14音频子系统-Linux音频子系统ALSA

文章目录

    • preface
    • 1、术语与基本概念
    • 2、ALSA框架图介绍
      • 1)preface
      • 2)框架图
      • 3)ALSA代码重点目录
      • 4)驱动流程全景图
    • 3、硬件介绍
      • 1、音频文件格式
      • 2、音频数据总线
        • 1)I2S
        • 2)PCI总线
      • 3、codec编解码芯片对比
        • 模拟功放-AD52050
        • AD82088 - 数字功放
        • 数字功放-UDA1340
        • 数字功放-wm8960
        • ALS300声卡
    • 4、声卡驱动设备节点介绍
    • 5、从芯片手册角度理解ALSA中的card\pcm\control设备
    • 6、ALSA代码分析
      • 1)ALSA的代码框架
        • 1)sound.c的作用
        • 2)sound_core.c的作用
      • 2)card设备的创建
      • 3)PCM设备的创建
        • 1、PCM逻辑设备架构被设计如下
        • 2、PCM中间层重要的数据结构体
        • 3、PCM设备创建代码流程图
          • 关键点分析
        • 4、struct snd_minor
        • 5、从tinyalsa到pcm过程
      • 4)control设备的创建
        • tinymix 使用control设备
        • 1)基本概念和基本流程
        • 2)control.h下的kcontrol相关结构体
        • 3)control设备的创建代码分析
        • 4)asound.h下的control相关结构体
        • 5)tinyalsa到control代码流程图

preface

1、ALSA是Advanced Linux Sound Architecture 的缩写,是音频驱动开发中linux内核提供的一种强大的驱动框架,类似的还有OSS,不过由于收费不再使用,ALSA目前已经成为了linux的主流音频体系结构
官网:http://www.alsa-project.org/。2、提供了怎样的便利?
在内核设备驱动层,ALSA提供了alsa-driver,只需要少量步骤即可完成驱动接口开发,同时在应用层,ALSA为我们提供了tinyalsa,应用程序只要调用tinyalsa提供的API,即可以完成对底层音频硬件的控制。3、一个系统的伟大意义?
可以兼容许多各异的设备,向上提供统一的接口,但由于从大量设备中抽象出共性 - 必然会增加其复杂度

1、术语与基本概念

1、linux音频子系统基本介绍
https://blog.csdn.net/g310773517/article/details/142262199
1)对音频设备的基本操作:录制、播放、混音; 
2)对音频数据的基本操作:编码、解码、音效处理、滤波等(开源库有FFmpeg[fast forward mpeg] 、GStreamer);2、一个典型的声卡通常包括三个部分:
CPU interface(I2S/PCI) -> Audio Circuits(功放/声卡) -> Connectors(SPDIF/EARPHONE/SPEAKER);1、oss : open sound system //收费,被废弃
2、alsa : advanced linux sound architecture
3、asoc : alsa system on chip
4、dai : digital audio interface5、PCM - Pulse Code Modulation 音频模拟信号转换成数字信号技术,但在音频系统不同层级上被引用成不同意思
1)音频数据格式:PCM格式
2)在数据传输接口:PCM是一种接口(信号时钟线、采样时钟线、数据信号线),对标I2S,相近PDM(MIC)
3)ALSA中,PCM代表一个具有playback/captrue的逻辑设备6、ac97 :audio codec 97 (48KHz,5.1声道),一种音频通信接口
7、acpi : advanced configuration and power interface8、sco : Synchronous connection-oriented 同步定向连接 - 实时双向音频数据
9、playback:播放
10、capture: 录音11、snd_pci_quirk
描述特殊行为或非标准特性疑问待解决:
13、
snd_soc_kcontrol_component
component 是什么?
kcontrol 是什么?
dpcm 是什么?
dapm 是什么?

2、ALSA框架图介绍

1)preface

面对多种多样的codec芯片和总线,音频子系统需要做的就是兼容所有设备,换做是你的话该怎么设计?1、ALSA如何统一规范?
1)ALSA驱动本质上是对字符设备驱动的进一步封装,由于抽象更统一的接口使其变得复杂一些;
2)封装字符设备的操作,向上统一提供接口,驱动程序必须使用其提供的API注册声卡设备;
>>三层file_operations结构:顶层->控制->playback/capture
3)使用ALSA的大部分是PCI设备,即个人电脑上的声卡;ALSA-ASOC(后面单独介绍)大部分是I2S设备,即常说的功放;

2)框架图

在这里插入图片描述

1)此图比较久远,但可以看出kernel层 音频子系统分为alsa-soc、alsa-driver两层,最开始只有alsa-driver,alsa-soc是Linux之后为嵌入式设备设计的框架,在alsa-driver上再封装一层;但仍有一些设备直接使用alsa-driver,如isa/pci接口

1、使用alsa-driver目录

android\kernel\fusion\4.19\sound\pci

android\kernel\fusion\4.19\sound\isa

2、使用alsa-soc目录

android\kernel\fusion\4.19\sound\soc\codecs

2)在Android 4.0之前是使用这alsa-lib接口,之后简化演变成更加简洁的tinyalsa

3)ALSA代码重点目录

ALSA驱动所有实现均在目录下:android\kernel\fusion\4.19\sound

├─ac97 //ac97总线实现
├─arm //arm平台
├─core //包含ALSA驱动的中间层,是整个ALSA核心部分
│ ├─oss //模拟旧oss架构的pcm和mixer模块
│ └─seq //音序器相关
├─drivers //放置一些与CPU和BUS架构无关的公用代码
├─i2c //Generic i2c interface for ALSA
├─isa //isa接口的codec驱动
├─oss //open sound system 已弃用
├─pci //pci接口的codec驱动
├─soc //system on chip 中间层代码,即asoc在ALSA进一步封装的代码
sound_core.c //ALSA核心实现

4)驱动流程全景图

在这里插入图片描述

1)从图中可以看出,原理上是我们熟悉的字符设备驱动,只不过关系更复杂些;
2)涉及很多概念,接下俩逐一介绍;

3、硬件介绍

1)ALSA本质上是对音频驱动的封装,我们先抛开ALSA框架,了解写一个音频驱动的一些基本硬件要素

1、音频文件格式

1)PCM (pulse-code modulation)

可以理解为最原始的数字音频数据

playback过程:应用程序读取音频数据(WAV/MP3/WMA/ACC…) -> 硬件/软件解码 -> PCM数据 -> 送到驱动程序中

capture过程:驱动程序收集MIC数据 -> PCM数据 -> 硬件/软件编码 -> MP3/WMA/ACC… ->应用程序储存

2)WAV (Waveform Audio File Format)

所有的音频文件格式一般有头部(control)+data部分,wav也不例外,如
在这里插入图片描述

RIFF(Resource interchange file format)是一种规范 - 任何一种音频文件格式都要遵循一定的规范才能普及!

详解:https://blog.csdn.net/wkd_007/article/details/134125746

2、音频数据总线

数字音频数据 需要 特定的音频数据总线来传输,常见有PCM/I2S/AC97/PCI/SPI等等

常用音频接口:https://mp.weixin.qq.com/s?__biz=MzI4MTEyNDU1MA==&mid=2651236244&idx=3&sn=fd2a460ed771d7f29112eed4942d4077

1)I2S

1)https://cloud.tencent.com/developer/article/2189680

I2S一般有一根系统CLK和三根信号线组成,相当于是加强版的I2C,同样由Philips发布的串行总线

MCLK:作用是为了系统间更好地同步

SCLK(BCLK):serial clk/bit clk,SCLK频率=声道数×采样频率×采样位数

LRCK :0为左声道,1为右声道

SDATA(SD):音频数据

2)PCI总线
1、对比I2S如何?
I2S专用于小型功放设备的音频数据传输,PCI用于PC平台的主要外设传输总线,不是一个等级的;
2、电气特性?
硬件连接:https://zhuanlan.zhihu.com/p/26172972
3、驱动如何写?
《全面深入 PCI 总线系统架构与应用》:https://blog.csdn.net/weixin_32047493/article/details/143871247

3、codec编解码芯片对比

基本术语:
ADC: Analog to Digital Converter
DAC: Digital to Analog Converter
Jack Detect : 检测音频接口是否接入(如麦克风、耳机插口)
Bootstrap:驱动电路,提高电源电压
left/right Boost mixer:放大/增加音频信号的混合电路,可以理解为音量控制
mixer:混合电路
MUX(Multiplexer):多路复用器,从多个输入选择一个进行输出
Stereo : 多声道
Mono :单声道
lineout:音频模拟信号输出
micbias : 偏置电源供应,为麦克风电路提供恒定电压或电流

1、codec泛指位于主芯片与喇叭之间的器件,功能是接收音频数据并驱动喇叭,称为声卡(个人电脑)或 功放(嵌入式),后续均用声卡或codec称呼

2、理解数字信号和模拟信号的概念,都是代表音频数据,只是存放的形式不同,驱动喇叭必然是放大后的模拟信号

1)模拟信号->模拟功放芯片放大->驱动喇叭

2)数字信号->数字功放转换为模拟信号并放大->驱动喇叭

模拟功放-AD52050

模拟功放虽然位于codec的位置,但不具备将数字音频转为模拟信号能力,即音频数据编解码能力,仅仅将主芯片编解码好的模拟数据放大作用,即主芯片集成codec数字音频解码器件,ALSA-driver将数据给到主芯片内部codec即可

1)应用电路
在这里插入图片描述

相对而言简单,给功放输入左右声道模拟信号,经过放大输出给到喇叭,SOC输出AMP_LOUT/AMP_ROUT给到功放,如下图

SOC端(lineout模拟信号输出):
在这里插入图片描述

功放端:
在这里插入图片描述

2)功能框图
在这里插入图片描述

1)看起来很复杂,对应驱动而言,只需关注输入输出即可;

AD82088 - 数字功放

1)功能框图
在这里插入图片描述

相较于模拟功放,数字功放一般功能更加强大,接口分为:

数据接口:I2S(SDATA/BCLK/LRCIN/MCLK)

控制接口(Control):I2C(SCL/SDA)

喇叭驱动:LA/LB、RA/RB

数字功放-UDA1340

在这里插入图片描述

数据接口:I2S (SDATA/BCLK/LRCIN/MCLK)

控制接口(Control):L3

L3DATA(数据线:用于传输数据)、L3MODE(模式线:用于选择模式)、L3CLOCK(时钟线:用于传输时钟)

数字功放-wm8960

功能更强大的数字功放

1)功能框图
在这里插入图片描述

1、多路模拟信号输入

2、数字输入 - I2S

3、控制接口 - I2C

4、左右声道喇叭输出、耳机输出(可选多声道stereo、单声道mono)

看起来功能更强大,也相对复杂,内部还有ADC/DAC,Mixer各种开关

ALS300声卡

1)基本介绍:https://www.dosdays.co.uk/topics/Manufacturers/avancelogic.php
在这里插入图片描述

2)支持接口:PCI/AC97/ISA

3)集成DRAM/EEPROM

4、声卡驱动设备节点介绍

声卡的接口介绍: https://blog.csdn.net/weixin_45905650/article/details/123233476

一个声卡可以有多个device,一个device有playback/capture,比如带麦克风的耳机(headset)
在这里插入图片描述

pcmC0D0c : card0 device0 capture 节点

pcmC0D0p : card0 device0 playback 节点

controlC0 : card0 配置节点

1、在音频子系统代码中 pcm 代表一个逻辑设备device(有playback/capture),所谓逻辑设备即是抽象出来的,非实体设备,先看看两个场景:

1)一个 个人电脑上的声卡可能支持多路输入和多路输出,

2)在嵌入式领域中,由于成本要求的原因,往往一个功放只支持一路输入和输出

软件中抽象出逻辑设备-pcm代表一路输出输入功能,使之兼容多种产品场景,高层级更加统一稳定

2、从应用层看,任何音频设置都通过这些 标准统一的设备节点去操作,完成播放和路由,这就是ALSA框架所带来的便利!

3、接下来研究这些设备节点是怎么生成

5、从芯片手册角度理解ALSA中的card\pcm\control设备

1)WM8960
在这里插入图片描述

1、一个card代表一款codec芯片

2、pcm是一个抽象设备,属于card的一个”部件“,下属有两个逻辑子设备playback/capture,比如播放音频时,可以通过厂商构造好的palyback,通过I2S写数据给到codec

3、control同样也是逻辑设备,用户如需要设置codec寄存器,统一通过control设备节点下发命令

1)control下挂着一个个snd_kcontrol_new,一个snd_kcontrol_new则代表一个控制器寄存器,比如RIGHT MIXER
在这里插入图片描述

2)注意有的codec没有control节点,比如als300

6、ALSA代码分析

1)ALSA的代码框架

1、ALSA中的card/pcm/control模块的实现比较独立,最后通过数组或链表把他们联立起来,这里可以先单独分析,最后看下如何联结在一起;
2、研究ALSA,可以找PCI接口的例子(als300.c),这里还没有封装为ASOC-ALSA,更好地体会ALSA,也尽可能降低学习复杂度,循序渐进;3、alsa音频驱动框架主要文件
sound/core/sound.c       实现了最顶层的file_operations,它起中转作用
sound/core/control.c     实现了控制接口的file_operations
sound/core/pcm_native.c  实现了playback, capture的file_operations
这些file_operations规定了统一的ALSA接口4、怎么写驱动? 实现硬件相关的代码即可,参照任一codec(如als300.c)写即可,实现重要的probe函数:
分配、设置、注册snd_card结构体:
a. snd_card_create // 创建card设备,里面会顺便创建control设备
b. snd_pcm_new     // 里面会构造playback, capture设备
c. snd_card_register //创建设备节点5、以PCI接口的als300.c作为研究例子
android\kernel\fusion\4.19\sound\pci\als300.c
1)驱动入口
module_pci_driver(als300_driver);static struct pci_driver als300_driver = {.name = KBUILD_MODNAME,.id_table = snd_als300_ids,.probe = snd_als300_probe,.remove = snd_als300_remove,.driver = {.pm = SND_ALS300_PM_OPS,},
};2)声卡设备的创建主要在probe函数完成
粗略看下创建ALSA声卡 大体流程
static int snd_als300_probe(struct pci_dev *pci,const struct pci_device_id *pci_id)
{1.创建card设备,分配资源snd_card_new()--snd_ctl_create(card); //顺带把control设备也创建,挂到card里面去集中管理2.snd_als300_create()--snd_als300_new_pcm() //创建pcm设备,挂到card里面去集中管理--snd_device_new(card, SNDRV_DEV_LOWLEVEL,chip, &ops) //将声卡创建为lowlevel设备(可以自动回收内存资源)3.注册card设备,会调用snd_pcm_dev_register/snd_ctl_dev_register去创建设备节点snd_card_register()
}6、其它
1)内核语法-大量的链表操作
/* insert the entry in an incrementally sorted list */list_for_each_prev(p, &card->devices) { //反向查找链表节点struct snd_device *pdev = list_entry(p, struct snd_device, list); //获取当前节点信息if ((unsigned int)pdev->type <= (unsigned int)type)break;}
2)内核语法-两种ioctrl
static const struct file_operations snd_ctl_f_ops = {// ....llseek = no_llseek,                 // 禁用文件寻址.unlocked_ioctl = snd_ctl_ioctl,     // 处理64位程序的ioctl.compat_ioctl = snd_ctl_ioctl_compat,// 处理32位程序的ioctl(需CONFIG_COMPAT)
};3)重要的头文件
android\kernel\fusion\4.19\include\uapi\sound\asound.h
//位于uapi - user api
1)Digital Audio (PCM) interface - 针对/dev/snd/pcm??的接口
2)Section for driver control interface - 针对/dev/snd/control? 的接口4)device component 指的是逻辑设备,比如pcm/control
1)sound.c的作用
1、作用是实现顶层的file_ops
android\kernel\fusion\4.19\sound\core\sound.c
static const struct file_operations snd_fops =
{.owner =	THIS_MODULE,//作为总的open入口(当应用层open打开设备节点pcmC0D0c/pcmC0D0p/controlC0,统一跳转至snd_open).open =		snd_open,.llseek =	noop_llseek,
};static int __init alsa_sound_init(void)
{register_chrdev(major, "alsa", &snd_fops)
}subsys_initcall(alsa_sound_init); //与各个声卡驱动,als300 互为独立
module_exit(alsa_sound_exit);2、当应用层open打开设备节点pcmC0D0c/pcmC0D0p/controlC0,统一跳转至snd_open,如何实现的?
1)一个主设备号对应一个file_opetations结构体(主设备号标识设备对应的驱动程序),次设备号对应一个内核设备节点文件
2)在ALSA-driver中,通过多个设备节点共享主设备号,将open操作的统一接口设为snd_open,然后转发3、顶层的snd_fops怎么与pcm/control节点的file operation关联起来?通过静态数组snd_minors实现
1.android\kernel\fusion\4.19\sound\core\sound.c
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];
static DEFINE_MUTEX(sound_mutex);
2.android\kernel\fusion\4.19\include\sound\core.h
struct snd_minor {int type;			/* SNDRV_DEVICE_TYPE_XXX */int card;			/* card number */int device;			/* device number */const struct file_operations *f_ops;	/* file operations */void *private_data;		/* private data for f_ops->open */struct device *dev;		/* device for sysfs */struct snd_card *card_ptr;	/* assigned card instance */
};
3.对外提供接口
int snd_register_device(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data, struct device *device)
{int minor;struct snd_minor *preg;preg->f_ops = f_ops;minor = snd_find_free_minor(type, card, dev);device->devt = MKDEV(major, minor);snd_minors[minor] = preg;
}
EXPORT_SYMBOL(snd_register_device);
4.其它设备模块注册时调用 即可链接到顶层file operation
2)sound_core.c的作用
我们看到设备节点都创建在/dev/snd目录,在哪里决定的? 
为了避免每创建一个设备都需要单独设置,ALSA在sound_core.c里提供了统一的接口 供开发者使用1、android\kernel\fusion\4.19\sound\
static int __init init_soundcore(void){1.在这里决定ALSA sysfs系统文件统一在/sys/class/sound/ 目录下创建sound_class = class_create(THIS_MODULE, "sound"); 2.devnode成员表示设备文件所挂载的目录,达到目录分类效果sound_class->devnode = sound_devnode;  
}/sys/class/sound/目录文件示例
console:/ # ls /sys/class/sound/
adsp  card0     dsp      pcmC0D0p pcmC0D1p pcmC0D2p timer
audio controlC0 pcmC0D0c pcmC0D1c pcmC0D2c pcmC0D7c
console:/ #static char *sound_devnode(struct device *dev, umode_t *mode)
{if (MAJOR(dev->devt) == SOUND_MAJOR)return NULL;2.在这里决定ALSA中的设备文件统一在/dev/snd 目录创建return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev)); 
}
subsys_initcall(init_soundcore);/dev/snd/ 目录示例
console:/ # ls /dev/snd/
adsp  controlC0 pcmC0D0c pcmC0D1c pcmC0D2c pcmC0D7c
audio dsp       pcmC0D0p pcmC0D1p pcmC0D2p timer
1、audio、dsp为兼容旧版OSS应用
2、adsp : audio dsp音频DSP接口(处理音效相关,回声消除、噪声音质、语音唤醒等功能)
3、pcm* : alsa驱动设备android\kernel\fusion\4.19\sound\core\init.c
创建设备时将构造好的sound_class赋值给dev的class成员
void snd_device_initialize(struct device *dev, struct snd_card *card)
{device_initialize(dev);if (card)dev->parent = &card->card_dev;dev->class = sound_class;dev->release = default_release;
}
EXPORT_SYMBOL_GPL(snd_device_initialize);

2)card设备的创建

一、声卡创建
1、snd_card结构体可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声
音相关的逻辑设备都是在snd_card的管理之下,声卡驱动的第一个动作通常就是创建一个snd_card结构体。2、snd_card的定义
android\kernel\fusion\4.19\include\sound\core.h
struct snd_card {struct module *module;		/* top-level module */void *private_data;		/* private data for soundcard */struct list_head devices;	/* devices */ -挂接注册的所有逻辑设备struct device ctl_dev;		/* control device */struct list_head controls;	/* all controls for this card */ -挂接注册的所有控制设备struct device *dev;		/* device assigned to this card */struct device card_dev;		/* cardX object for sysfs */
}2、android\kernel\fusion\4.19\sound\pci\als300.c
snd_als300_probe(){1.创建card相关的资源,创建control设备snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,0, &card); 2.创建pcm设备		   snd_als300_create(card, pci, chip_type, &chip) --snd_als300_new_pcm() //创建pcm设备--snd_device_new(card, SNDRV_DEV_LOWLEVEL) //card设备注册为低阶设备,为了声卡被销毁时,其挂载的内存资源可以被自动释放3.将挂载card->devices下的设备逐一注册(设备一般就是pcmplayback\pcmcapture\control)snd_card_register(card); 
}3、细看snd_card_new
android\kernel\fusion\4.19\sound\core\init.c
int snd_card_new(struct device *parent, int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)
{1.创建control设备kobject_set_name(&card->card_dev.kobj, "card%d", idx); snd_ctl_create(card);2.构造info用于生成/proc/asound/card0snd_info_card_create(card);
}4、细看snd_card_register
android\kernel\fusion\4.19\sound\core\device.c
snd_card_register
--snd_device_register_all
----__snd_device_register
------dev->ops->dev_register(dev);//调用各个设备自身的file_operation下的dev_register去注册dev_register函数指针在哪里赋值挂载的?
pcm : _snd_pcm_new()
control : snd_ctl_create()

构造好的card 挂载的逻辑设备(也可以称为功能部件)如下
在这里插入图片描述

3)PCM设备的创建

1、PCM逻辑设备架构被设计如下

梳理PCM设备创建流程先来看整体设计架构
在这里插入图片描述

通常嵌入式设备中只有一个pcm,对应一个playback stream和substream、一个capture steam和substream

2、PCM中间层重要的数据结构体

在这里插入图片描述

1)与PCM逻辑设备相对应;

2)snd_pcm_runtime 保存substream的一些重要的运行环境和参数(上下文);

3)snd_pcm_substream 挂载着ops(驱动需要实现的方法),播放/录音以substream为基本操作单位;

4)struct snd_pcm_str {} //str(stream), 代表playback/capture

3、PCM设备创建代码流程图

在这里插入图片描述

关键点分析
1、B-module : Build-in module
2、第一步创建card,用于挂接pcm设备
snd_card_create()
3、调用PCM中间件创建PCM设备
android\kernel\fusion\4.19\sound\core\pcm.c
int snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count, struct snd_pcm **rpcm)
{return _snd_pcm_new(card, id, device, playback_count, capture_count,false, rpcm);
}1)device表示该声卡下第几个device, 第一个pcm设备从0开始
2)**rpcm : 挂接构造好的pcm结构体4、设备节点在哪里生成?(/dev/snd/pcmC0D0p)
1)android\kernel\fusion\4.19\sound\core\pcm.c
static int snd_pcm_dev_register(struct snd_device *device){}
--snd_register_device() 
----snd_pcm_dev_register() //最终注册生成设备节点2)snd_pcm_dev_register在哪里被挂载?
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count, bool internal,struct snd_pcm **rpcm)
{struct snd_pcm *pcm;int err;static struct snd_device_ops ops = {.dev_free = snd_pcm_dev_free,.dev_register =	snd_pcm_dev_register,.dev_disconnect = snd_pcm_dev_disconnect,};
}
4、struct snd_minor
1、snd_minor结构体保存声卡下某个逻辑设备的上下文信息他在逻辑设备建立阶段被填充,在逻辑设备被使用时就可以从该结构体中得到相应的信息。
android\kernel\fusion\4.19\include\sound\core.h
struct snd_minor {int type;			/* SNDRV_DEVICE_TYPE_XXX */int card;			/* card number */int device;			/* device number */const struct file_operations *f_ops;	/* file operations */void *private_data;		/* private data for f_ops->open */struct device *dev;		/* device for sysfs */struct snd_card *card_ptr;	/* assigned card instance */
};android\kernel\fusion\4.19\sound\core\sound.c
#define SNDRV_OS_MINORS			256
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];2、在哪里构造?
android\kernel\fusion\4.19\sound\core\pcm.c
static int snd_pcm_dev_register(struct snd_device *device)
{/* register pcm */snd_register_device()
}android\kernel\fusion\4.19\sound\core\sound.c
int snd_register_device(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data, struct device *device)
{int minor;int err = 0;struct snd_minor *preg;if (snd_BUG_ON(!device))return -EINVAL;preg = kmalloc(sizeof *preg, GFP_KERNEL);if (preg == NULL)return -ENOMEM;preg->type = type;  //开始构造snd_minorpreg->card = card ? card->number : -1;preg->device = dev;preg->f_ops = f_ops;preg->private_data = private_data;preg->card_ptr = card;mutex_lock(&sound_mutex);minor = snd_find_free_minor(type, card, dev);if (minor < 0) {err = minor;goto error;}preg->dev = device;device->devt = MKDEV(major, minor); err = device_add(device); //创建设备if (err < 0)goto error;snd_minors[minor] = preg;  //挂到全局数组snd_minors中error:mutex_unlock(&sound_mutex);if (err < 0)kfree(preg);return err;
}
EXPORT_SYMBOL(snd_register_device);
5、从tinyalsa到pcm过程

代码流程图:
在这里插入图片描述

重点讲解

1、android\kernel\fusion\4.19\sound\core\sound.c
static int __init alsa_sound_init(void)
{snd_major = major;snd_ecards_limit = cards_limit;if (register_chrdev(major, "alsa", &snd_fops)) {pr_err("ALSA core: unable to register native major device number %d\n", major);return -EIO;}if (snd_info_init() < 0) {unregister_chrdev(major, "alsa");return -ENOMEM;}
#ifndef MODULEpr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
#endifreturn 0;
}如何与设备节点/dev/snd/card0pcm0p、card0pcm0c等挂接起来?通过共享主设备号实现2、
static int snd_open(struct inode *inode, struct file *file)
{unsigned int minor = iminor(inode);struct snd_minor *mptr = NULL;const struct file_operations *new_fops;mutex_lock(&sound_mutex);mptr = snd_minors[minor]; //从snd_minors数据取出pcm ops信息new_fops = fops_get(mptr->f_ops);if (file->f_op->open)err = file->f_op->open(inode, file);return err;
}snd_pcm_f_ops[1].read()=snd_pcm_read()
snd_pcm_f_ops[0].write()=snd_pcm_write()
snd_pcm_f_ops[0].ioctl()=snd_pcm_ioctl()
snd_pcm_f_ops[1].ioctl()=snd_pcm_ioctl()snd_ctl_f_ops.read()=snd_ctl_read()
snd_ctl_f_ops.ioctl()=snd_ctl_ioctl()

4)control设备的创建

tinymix 使用control设备
1、tinymix的使用
1)查看tinymix可控的寄存器列表
root@# tinymix
Mixer name: 'audiocodec'
Number of controls: 16
ctl type num name          value
1   INT  1  "digital volume" 0
2   INT  1  "LINEIN to output mixer gain control" 3
3   BOOL 1  "LINEOUT Switch" On
...2)设置音量
tinymix "LINEOUT volume" "2">>control根据name "LINEOUT volume匹配对应的snd_control,最终将value "2" 设置到对应的寄存器
1)基本概念和基本流程
1、AC97的接口比较特殊,数据中包含控制信息,ALSA已经为其定义的完整接口模型,不走control设备节点控制2、mixer的多层含义
1)在tinyalsa层代表功能控制,即对应alsa中的control部分;
2)在framework层代表 音频数据的混音组装;
3)在alsa中对应硬件的mixer混音部件;2、control的名字需要遵循一些规范:源-方向-功能
1)源:可以理解为该control的输入端,alsa已经预定义了一些常用的源,例如:Master,PCM,CD,Line等等。
2)方向:代表该control的数据流向,例如:Playback,Capture,Bypass,Bypass Capture等等,也可以不
定义方向,这时表示该Control是双向的(playback和capture)。
3)功能:根据control的功能,可以是以下字符串:Switch,Volume,Route等等。
control的作用是按照名字来归类的,ALSA已经定义了一些control的名字3、control中间层有什么作用?
1)不同厂家的芯片设计、功能寄存器各式各样,ALSA需要统一向上提供稳定接口(给tinymix调用),由强大的control中间件实现;
2)我们要做就是读芯片手册,确定寄存器的addr/bit/位数等信息,构造snd_kcontrol_new4、带着疑问来看control设备的创建
1)怎么构造kcontrol? >> 每个厂商实现都不太一致,了解大概流程即可,细节应根据数据手册来填充!
2)怎么使用kcontrol? >> 调用流程card0control -> control.c -> kcontrol的put/get/info
3)元数据(Metadata - tlv?)
TLV : 是一种通用的数据编码格式,用于表示结构化数据:type[0]-length[1]-value[2..n],在音频领域,TLV 主要用于传递音频参数的元数据,如音量范围、增益曲线等。
2)control.h下的kcontrol相关结构体

讨论control设备创建,先熟悉control.h下的结构体
在这里插入图片描述

1、android\kernel\fusion\4.19\include\sound\control.h
主要用于内核驱动层的数据构造1)info/get/put方法 - 相当于ops,有驱动开发者提供,根据自家的器件去实现
struct snd_kcontrol;
typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo);
typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
typedef int (snd_kcontrol_tlv_rw_t)(struct snd_kcontrol *kcontrol,int op_flag, /* SNDRV_CTL_TLV_OP_XXX */unsigned int size,unsigned int __user *tlv);2)提供给驱动开发者的kcontrol结构体
struct snd_kcontrol_new {snd_ctl_elem_iface_t iface;	/* interface identifier */unsigned int device;		/* device/client number */unsigned int subdevice;		/* subdevice (substream) number */const unsigned char *name;	/* ASCII name of item */unsigned int index;		/* index of item */unsigned int access;		/* access rights */unsigned int count;		/* count of same elements */snd_kcontrol_info_t *info;snd_kcontrol_get_t *get;snd_kcontrol_put_t *put;union {snd_kcontrol_tlv_rw_t *c;const unsigned int *p;} tlv;unsigned long private_value; //供给info/get/put使用的数据
};3)
struct snd_kcontrol {struct list_head list;		/* list of controls */struct snd_ctl_elem_id id;unsigned int count;		/* count of same elements */snd_kcontrol_info_t *info;snd_kcontrol_get_t *get;snd_kcontrol_put_t *put;union {snd_kcontrol_tlv_rw_t *c;const unsigned int *p;} tlv;unsigned long private_value;void *private_data;void (*private_free)(struct snd_kcontrol *kcontrol);struct snd_kcontrol_volatile vd[0];	/* volatile data */
}
为什么有两个相近的结构体snd_kcontrol_new和snd_kcontrol_new?
snd_kcontrol才是ALSA-driver中的实例,根据snd_kcontrol_new(可以称为模版)来进行创建,由于snd_kcontrol构造相对复杂,ALSA-driver进一步封装起来,提供接口snd_ctl_new1和snd_ctl_add便于驱动开发者使用。代码流程:
snd_soc_cnew(control, data, control->name, prefix) //将snd_kcontrol_new转换成snd_kcontrol_new
snd_ctl_add(card, snd_soc_cnew(control, data, control->name, prefix)); //添加到card下的kcontrol列表中去
--__snd_ctl_add()
----list_add_tail(&kcontrol->list, &card->controls);3)
snd_ctl_file作为ioctl中的struct file *file->private_data
struct snd_ctl_file {struct list_head list;		/* list of all control files */struct snd_card *card;struct pid *pid;int preferred_subdevice[SND_CTL_SUBDEV_ITEMS];wait_queue_head_t change_sleep;spinlock_t read_lock;struct fasync_struct *fasync;int subscribed;			/* read interface is activated */struct list_head events;	/* waiting events for read */
};
3)control设备的创建代码分析
一、control设备的隐式创建
1、android\kernel\fusion\4.19\sound\core\init.c
int snd_card_new(struct device *parent, int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)
{err = snd_ctl_create(card);
}2、android\kernel\fusion\4.19\sound\core\control.c
snd_ctl_create(){static struct snd_device_ops ops = { //control file operation.dev_free = snd_ctl_dev_free,.dev_register =	snd_ctl_dev_register, //后面会统一调用register注册.dev_disconnect = snd_ctl_dev_disconnect,};dev_set_name(&card->ctl_dev, "controlC%d", card->number);err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); //创建control设备节点
}3、android\kernel\fusion\4.19\sound\core\device.c
snd_device_new(){list_for_each_prev(p, &card->devices) { //添加到card的devices里管控起来struct snd_device *pdev = list_entry(p, struct snd_device, list);if ((unsigned int)pdev->type <= (unsigned int)type)break;}
}4、register
static int snd_ctl_dev_register(struct snd_device *device)
{struct snd_card *card = device->device_data;return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,&snd_ctl_f_ops, card, &card->ctl_dev);
}int snd_register_device(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data, struct device *device)
{int minor;int err = 0;struct snd_minor *preg;if (snd_BUG_ON(!device))return -EINVAL;preg = kmalloc(sizeof *preg, GFP_KERNEL);if (preg == NULL)return -ENOMEM;preg->type = type;preg->card = card ? card->number : -1;preg->device = dev;preg->f_ops = f_ops;preg->private_data = private_data;preg->card_ptr = card;mutex_lock(&sound_mutex);minor = snd_find_free_minor(type, card, dev);if (minor < 0) {err = minor;goto error;}preg->dev = device;device->devt = MKDEV(major, minor);err = device_add(device);if (err < 0)goto error;snd_minors[minor] = preg;error:mutex_unlock(&sound_mutex);if (err < 0)kfree(preg);return err;
}
EXPORT_SYMBOL(snd_register_device);一个简单的例子
static struct snd_kcontrol_new my_control {.iface = SNDRV_CTL_ELEM_IFACE_MIXER,.name = "PCM Playback Switch",.index = 0,.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,.private_value = 0xffff,.info = my_control_info,.get = my_control_get,.put = my_control_put
};  实际驱动中例子
4、kcontrol挂接到card,供给control节点调用,这部分跟codec芯片寄存器有关,比较晦涩难懂
android\kernel\fusion\4.19\sound\pci\asihpi\asihpi.c //以asihpi为例子
snd_kcontrol_new的构造
static int  snd_asihpi_mux_add(struct snd_card_asihpi *asihpi,struct hpi_control *hpi_ctl)
{struct snd_card *card = asihpi->card;struct snd_kcontrol_new snd_control;asihpi_ctl_init(&snd_control, hpi_ctl, "Route");snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;snd_control.info = snd_asihpi_mux_info;snd_control.get = snd_asihpi_mux_get;snd_control.put = snd_asihpi_mux_put;return ctl_add(card, &snd_control, asihpi);}//构造snd_kcontrol_new
static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control,struct hpi_control *hpi_ctl,char *name)
{char *dir;memset(snd_control, 0, sizeof(*snd_control));snd_control->name = hpi_ctl->name;snd_control->private_value = hpi_ctl->h_control;snd_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER;snd_control->index = 0;if (hpi_ctl->src_node_type + HPI_SOURCENODE_NONE == HPI_SOURCENODE_CLOCK_SOURCE)dir = ""; /* clock is neither capture nor playback */else if (hpi_ctl->dst_node_type + HPI_DESTNODE_NONE == HPI_DESTNODE_ISTREAM)dir = "Capture ";  /* On or towards a PCM capture destination*/else if ((hpi_ctl->src_node_type + HPI_SOURCENODE_NONE != HPI_SOURCENODE_OSTREAM) &&(!hpi_ctl->dst_node_type))dir = "Capture "; /* On a source node that is not PCM playback */else if (hpi_ctl->src_node_type &&(hpi_ctl->src_node_type + HPI_SOURCENODE_NONE != HPI_SOURCENODE_OSTREAM) &&(hpi_ctl->dst_node_type))dir = "Monitor Playback "; /* Between an input and an output */elsedir = "Playback "; /* PCM Playback source, or  output node */if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type)sprintf(hpi_ctl->name, "%s %d %s %d %s%s",asihpi_src_names[hpi_ctl->src_node_type],hpi_ctl->src_node_index,asihpi_dst_names[hpi_ctl->dst_node_type],hpi_ctl->dst_node_index,dir, name);else if (hpi_ctl->dst_node_type) {sprintf(hpi_ctl->name, "%s %d %s%s",asihpi_dst_names[hpi_ctl->dst_node_type],hpi_ctl->dst_node_index,dir, name);} else {sprintf(hpi_ctl->name, "%s %d %s%s",asihpi_src_names[hpi_ctl->src_node_type],hpi_ctl->src_node_index,dir, name);}/* printk(KERN_INFO "Adding %s %d to %d ",  hpi_ctl->name,hpi_ctl->wSrcNodeType, hpi_ctl->wDstNodeType); */
}//构造好的snd_kcontrol_new给谁记录?
通过control中间层api snd_ctl_new1和snd_ctl_add创建一个ctl
static inline int ctl_add(struct snd_card *card, struct snd_kcontrol_new *ctl,struct snd_card_asihpi *asihpi)
{int err;err = snd_ctl_add(card, snd_ctl_new1(ctl, asihpi));if (err < 0)return err;else if (mixer_dump)dev_info(&asihpi->pci->dev, "added %s(%d)\n", ctl->name, ctl->index);return 0;
}最终追加到card链表里面去,control会根据control->id找到对应的kcontrol调用
list_add_tail(&kcontrol->list, &card->controls);
4)asound.h下的control相关结构体

讨论用户态与内核态的交互之前,先来asound.h下的结构体
在这里插入图片描述

1、android\kernel\fusion\4.19\include\uapi\sound\asound.h
主要定义一些结构体和枚举常量,用于用户空间与内核空间交互使用1)内核枚举类型定义-提供安全的类型检查
typedef int __bitwise snd_ctl_elem_type_t;
#define	SNDRV_CTL_ELEM_TYPE_NONE	((__force snd_ctl_elem_type_t) 0) /* invalid */
#define	SNDRV_CTL_ELEM_TYPE_BOOLEAN	((__force snd_ctl_elem_type_t) 1) /* boolean type */snd_ctl_elem_type_t //寄存器值的类型
SNDRV_CTL_ELEM_TYPE_BOOLEAN
SNDRV_CTL_ELEM_TYPE_INTEGERsnd_ctl_elem_iface_t //哪个设备节点
SNDRV_CTL_ELEM_IFACE_CARD //control节点
SNDRV_CTL_ELEM_IFACE_PCM  //pcm节点2)
ioctl指令集
#define SNDRV_CTL_IOCTL_CARD_INFO	_IOR('U', 0x01, struct snd_ctl_card_info)
'U' //幻数
_IOR //用户空间读取内核空间,数据结构为snd_ctl_card_info
用户空间程序示例:
struct snd_ctl_card_info info;
ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &info);
printf("Card: %s\n", info.name); // 输出: "HD Audio Controller"3)定义用户空间与内核空间交互的结构体
snd_ctl_elem_list
snd_ctl_elem_info
snd_ctl_elem_value
5)tinyalsa到control代码流程图

代码流程图
在这里插入图片描述

tinyalsa通过ioctl() 最终调用某个snd_kcontrol中的info/get/put函数

1、android\external\tinyalsa\tinymix.c
static void usage (void) {fprintf(stderr,
"tinymix [options] [control name/#] [value to set]\n"
"    options:\n"
"    --device|-D <card#>   - use the given card # instead of 0.\n"
"    --all-values|-a       - show all possible values/ranges for control.\n"
"    --tabs-only|-t        - separate all output columns/values with tabs.\n"
"    --value-only|-v       - show only the value for the selected control.\n");
}1、中间转换过程相对繁琐
直接看
android\external\tinyalsa\mixer_hw.c
static struct mixer_ops mixer_hw_ops = {.close = mixer_hw_close,.get_poll_fd = NULL,.read_event = mixer_hw_read_event,.ioctl = mixer_hw_ioctl,
};2、
int mixer_hw_open(unsigned int card, void **data,struct mixer_ops **ops)
{snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);fd = open(fn, O_RDWR); //返回control fd,后续可以直接操作
}3、tinyalsa中的命令如何构造,怎么与ALSA驱动保持一致?
与ALSA驱动共用asound.h头文件  - “ Section for driver control interface - /dev/snd/control? ”
android\external\tinyalsa\mixer.c
#include <sound/asound.h>     //android\kernel\fusion\4.19\include\uapi\sound\asound.h
#include <tinyalsa/asoundlib.h>  //android\external\tinyalsa\include\tinyalsa\asoundlib.h4、从control设备节点到kcontrol的ops
android\kernel\fusion\4.19\sound\core\control.c
1)
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{case SNDRV_CTL_IOCTL_ELEM_WRITE: //写入return snd_ctl_elem_write_user(ctl, argp);
}snd_ctl_elem_write_user
--snd_ctl_elem_writestatic int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,struct snd_ctl_elem_value *control)
{struct snd_kcontrol *kctl;struct snd_kcontrol_volatile *vd;kctl = snd_ctl_find_id(card, &control->id);result = kctl->put(kctl, control);
}4)control name是如何匹配的? 字符串匹配
http://www.lqws.cn/news/513451.html

相关文章:

  • 微信小程序 / UNIAPP --- 阻止小程序返回(顶部导航栏返回、左 / 右滑手势、安卓物理返回键和调用 navigateBack 接口)
  • 服务器性能优化通用方案
  • 文档处理控件Aspose.Words教程:在.NET中将多页文档转换为单个图像
  • 【开源解析】基于PyQt5的智能费用报销管理系统开发全解:附完整源码
  • Golang单例实现
  • LVS-NAT负载均衡群集实战:原理、部署与问题排查
  • 小程序快速获取url link方法,短信里面快速打开链接
  • Spark Streaming 与 Flink 实时数据处理方案对比与选型指南
  • Flink2.0 配置 historyserver
  • 15个AI模拟面试平台 和 简历修改 / 真人面试平台
  • 云计算产业链
  • 用wordpress建日语外贸网站的优势
  • C# Avalonia 绑定模式 Mode 的区别,它们的应用场景
  • spring中的@Cacheable缓存
  • MicroPython网络编程:AP模式与STA模式详解
  • 【笔记——李沐动手学深度学习】2.3 线性代数
  • 【Python练习】012. 使用字符串的upper()方法将字符串转换为大写
  • 基于开源AI大模型、AI智能名片与S2B2C商城小程序的美食菜单社交化营销创新研究
  • 音频转换芯片DP7344兼容CS4344双通道24位DA转换器技术资料
  • 宠物养成小游戏流量主微信小程序开源
  • 小米互联应用曝高危漏洞,攻击者可绕过认证获取设备完全控制权
  • 使用GithubActions和腾讯CloudBase自动发布静态网页
  • 暴雨信创电脑代理商成功中标长沙市中医康复医院
  • 019 高校心理教育辅导系统技术解析:构建心理健康守护平台
  • aspose.word在IIS后端DLL中高并发运行,线程安全隔离
  • HarmonyNext动画大全02-显式动画
  • 从数据到决策:UI前端如何利用数字孪生技术提升管理效率?
  • 计算机网络 网络层:数据平面(二)
  • LeetCode 142题解 | 环形链表Ⅱ
  • 【MCP服务】蓝耘元生代 | MCP平台:部署时间服务器MCP,开启大模型交互新体验