zoukankan      html  css  js  c++  java
  • Linux ALSA介绍

    1. 介绍

    ALSA(即Advanced Linux Sound Architecture), 是目前Linux的主流音频体系结构, 提供了音频和MIDI的支持, 其架构图如下所示

    TIP: 笔者的代码分析基于linux-4.14.19

    2. 初始化

    系统启动中ALSA初始化过程如下

    alsa_sound_init()
      /* 注册alsa字符设备 */
      register_chrdev(116, "alsa", &snd_fops)
      /* 创建/proc/asound目录及下属version、devices、cards、modules等文件 */
      snd_info_init()
      
    const struct file_operations snd_fops =
    {
    	.owner =	THIS_MODULE,
    	.open =		snd_open,
    	.llseek =	noop_llseek,
    };
    

    从用户空间打开PCM设备过程如下

    snd_pcm_open("default", SND_PCM_STREAM_PLAYBACK)  // alsa-lib接口
      open("/dev/snd/controlC0")         // 打开控制设备; 主设备116, 次设备0
      open("/dev/snd/pcmC0D0p")          // 打开PCM设备; 主设备116, 次设备16
        snd_open()                       // 根据主设备号找到该入口
          snd_minors[minor]              // 根据次设备号找到对应操作集
            snd_ctl_f_ops::open()        // 控制设备打开方法
              snd_ctl_open()
            snd_pcm_f_ops::open()        // PCM设备打开方法
              snd_pcm_playback_open()
                snd_lookup_minor_data()  // 根据次设备号查找对应PCM设备(snd_pcm)
                snd_pcm_open()           // 打开PCM播放子流
    

    3. 核心层

    核心层为用户空间提供逻辑设备接口, 同时为驱动提供接口来驱动硬件设备, 主要位于sound/core目录下

    3.1 数据结构

    该层包含的主要数据结构包括

    - snd_card      表示一个声卡实例, 包含多个声卡设备
    - snd_device    表示一个声卡设备部件
    - snd_pcm       表示一个PCM设备, 声卡设备的一种, 用于播放和录音
    - snd_control   表示Control设备, 声卡设备的一种, 用于控制声卡
    - snd_pcm_str   表示PCM流, 分为playback和capture
    - snd_pcm_substream PCM子流, 用于音频的播放或录制
    - snd_pcm_ops   PCM流操作集
    

    各结构体之间主要关系图如下所示

    snd_card主要字段如下

    struct snd_card {
        int number;             /* 索引 */
        char id[16];            /* 标识符 */
    
        char driver[16];        /* 驱动名称 */
        char shortname[32];     /* 短名 */
        char longname[80];      /* 名字 */
    
        void *private_data;     /* 声卡私有数据*/
        void (*private_free) (struct snd_card *); /* 私有数据释放回调 */
    
        struct list_head devices;     /* 该声卡下所有设备*/
        struct list_head controls;    /* 该声卡下所有控制设备*/
    
        struct list_head files_list;  /* 声卡管理文件 */
        struct device *dev;           /* 声卡相关的device */
        struct device card_dev;       /* 用于sysfs, 代表该声卡 */
        bool registered;              /* 是否注册标记 */
    };
    

    snd_device主要字段如下

    struct snd_device {
        struct list_head list;        /* 所有注册的声卡设备链表 */
        struct snd_card *card;        /* 设备所属声卡 */
        enum snd_device_state state;  /* 设备状态*/
        enum snd_device_type type;    /* 设备类型*/
        void *device_data;            /* 指向具体的声卡设备, 如snd_pcm */
        struct snd_device_ops *ops;   /* 设备操作集*/
    };
    

    snd_pcm主要字段如下

    struct snd_pcm {
        struct snd_card *card;   /* 该PCM设备所属声卡*/
        struct list_head list;   /* 所有注册的PCM设备链表 */
        int device;              /* PCM索引 */
        unsigned int info_flags; /* SNDRV_PCM_INFO_ */
        char id[64];             /* PCM设备标识 */
        char name[80];           /* PCM设备名 */
        struct snd_pcm_str streams[2];  /* 指向PCM设备的capture(1)和playback(0)流 */
        void *private_data;      /* PCM设备私有数据*/
        void (*private_free) (struct snd_pcm *); /* 私有数据释放回调 */
    };
    

    3.2 接口

    该层主要接口如下

    /* 创建和初始化声卡结构体 */
    int snd_card_new(struct device *parent, int idx, const char *xid, struct module *, int extra_size, struct snd_card **card_ret);
    /* 释放声卡结构体 */
    int snd_card_free(struct snd_card * card);
    /* 注册声卡 */
    int snd_card_register(struct snd_card * card);
    
    /* 创建声卡设备部件, 通常由snd_pcm_new和snd_card_new自动完成 */
    int snd_device_new(struct snd_card *, enum snd_device_type type, void *device_data, struct snd_device_ops *ops);
    /* 注册声卡设备部件, 通常由snd_card_register自动完成 */
    int snd_device_register(struct snd_card *card, void *device_data);
    
    /* 创建PCM设备 */
    int snd_pcm_new(struct snd_card *, const char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm);
    /* 创建PCM流, 通常snd_pcm_new会自动创建capture和playback两个PCM流 */
    int snd_pcm_new_stream(struct snd_pcm * pcm, int stream, int substream_count);
    /* 设置PCM设备操作集 */
    void snd_pcm_set_ops(struct snd_pcm *, int direction, const struct snd_pcm_ops *ops);
    

    snd_card_new完成了如下事宜
    1. 分配snd_card+extra_size空间大小
    2. 如果extra_size大于0,将private_data指向extra_size所在首地址
    3. 如果指定了xid, 将其拷贝至snd_card::id中, 即声卡标识符
    4. 根据idx获取可用的声卡索引并赋值给snd_card::number
    5. 分别将parent、module赋值给snd_card::dev、snd_card::module
    6. 初始化链表snd_card::devices、snd_card::controls、snd_card::ctl_files、snd_card::files_list
    7. 调用device_initialize()初始化snd_card::card_dev, 并设置snd_card::card_dev相关成员变量, 用于sysfs
    8. 调用snd_ctl_create()创建控制接口
    8.1 调用snd_device_initialize初始化snd_card::ctl_dev, 并设置相关成员变量, 用于sysfs
    8.2 调用snd_device_new(SNDRV_DEV_CONTROL, ops)创建声卡控制设备部件

    	static struct snd_device_ops ops = {
    		.dev_free = snd_ctl_dev_free,
    		.dev_register =	snd_ctl_dev_register,
    		.dev_disconnect = snd_ctl_dev_disconnect,
        };
    

    9. 调用snd_info_card_create()创建proc对应文件系统

    snd_card_register完成了如下事宜
    1. 如果声卡未注册(snd_card::registered), 调用device_add(snd_card::card_dev)将声卡添加到sysfs
    2. 调用snd_device_register_all(snd_card)注册该声卡下所有声卡设备(即snd_card::devices链表), 即完成snd_device_register相同的功能
    2.1 遍历snd_card::devices链表, 依次调用__snd_device_register注册声卡设备
    2.1.1 调用snd_device::snd_device_ops::dev_register注册该设备, 对于Control设备, 即snd_ctl_dev_register; 对于PCM设备, 即snd_pcm_dev_register; 最终则都会调用snd_register_device
    *2.1.1.1 snd_ctl_dev_register: 调用snd_register_device(snd_ctl_f_ops)注册该Control设备 *
    *2.1.1.2 snd_pcm_dev_register: 调用snd_pcm_add将该PCM设备添加至全局PCM链表snd_pcm_devices中, 然后调用snd_register_device(snd_pcm_f_ops)注册该PCM设备 *
    2.1.1.x.1 snd_register_device: 分配snd_minor空间, 设置type、card、device、f_ops、card_ptr等成员变量; 通过snd_find_free_minor找到合适的minor并通过MKDEV(116, minor)创建设备节点, 然后通过device_add向系统添加该设备; 最后将该声卡设备添加至全局声卡主设备的次设备数组snd_minors中
    3. 将该声卡放入全局静态声卡数组snd_cards中
    4. 调用init_info_for_card()向proc文件系统注册该声卡

    snd_pcm_new完成了如下事宜
    1. 分配snd_pcm空间, 并设置snd_pcm::card、snd_pcm::device等成员变量
    2. 调用snd_pcm_new_stream(SNDRV_PCM_STREAM_PLAYBACK)创建playback_count个子流用于播放
    3. 调用snd_pcm_new_stream(SNDRV_PCM_STREAM_CAPTURE)创建capture_count个子流用于录制
    4. 调用snd_device_new(SNDRV_DEV_PCM, ops)添加PCM设备

    	static struct snd_device_ops ops = {
    		.dev_free = snd_pcm_dev_free,
    		.dev_register =	snd_pcm_dev_register,
    		.dev_disconnect = snd_pcm_dev_disconnect,
    	};
    

    snd_pcm_new_stream完成了如下事宜
    1. 设置snd_pcm::stream[playback or catpure]对应stream、pcm、substream_count成员变量
    2. 调用snd_device_initialize()初始化snd_pcm::stream::dev, 并设置相关成员变量, 用于sysfs
    3. 调用snd_pcm_stream_proc_init(snd_pcm_str)初始化对应proc文件系统
    4. 分配substream_count个snd_pcm_substream并进行相应初始化

    3.3 实现

    核心驱动的一般实现步骤如下
    1. 调用snd_card_create创建声卡实例(struct snd_card)
    2. 定义声卡的私有结构体用于存放该声卡的一些资源信息, 如中断资源、IO资源、DMA资源等
    3. 硬件初始化, 包括数字音频接口初始化、DMA控制器初始化、编解码器初始化
    4. 调用snd_pcm_new创建逻辑设备, 并实现其操作集snd_pcm_ops
    5. 调用snd_card_register注册声卡实例及声卡设备

    具体实例可参考sound/atmel/ac97csound/spi/at73c213的实现

    4. ASOC层

    在移动设备中, 为了更好的提供ALSA支持, 在核心层的基础上出现了ASOC(ALSA System on Chip)层
    ASOC层代码位于sound/soc/*, 主要由如下三部分组成
    - Codec: 负责配置编解码器提供音频捕获和回放功能
    - Platform: 主要负责SoC平台音频DMA和音频接口的配置和控制, 包括时钟、DMA、I2S、PCM等
    - Machine: Codec、Platform、输入输出设备提供了一个载体

    4.1 DAI

    DAI(Digital Audio Interfaces), 即数字音频接口
    ASOC支持三种主流DAI: AC97、I2S和PCM

    AC97: 通常用于PC声卡, 为5线接口, 每个AC97帧为21uS长, 被分为13个时隙
    - BCLK: 由AC97驱动, 为12.288 MHz
    - SYNC: 同步信号, 由Controler驱动, 为48 kHz
    - SDATDIN: 用于capture, AC97->Controler
    - SDATAOUT: 用于playback, Controler->AC97
    - RESET: 由Controler生成, 用于唤醒AC97

    I2S是HiFi、STB和便携式设备中常用的4线DAI
    - SCLK: 串行时钟
    - LRCK: 也称WS, 声道选择线
    - Tx: 用于传输音频数据
    - Rx: 用于接收音频数据

    PCM是另一种4线接口, 与I2S非常相似, 可以支持更灵活的协议
    - BCLK: 位时钟, 根据采样率而变化
    - SYNC: 同步信号
    - Tx: 用于传输音频数据
    - Rx: 用于接收音频数据

    4.2 Codec

    Codec驱动应该实现为通用与硬件无关的,用于配置编解码器、FM、MODEM、BT或外部DSP, 以提供playback和capture, 这部分代码通常位于sound/soc/codecs/*

    每个Codec驱动必须提供如下功能
    1. Codec DAI和PCM配置
    2. 使用RegMap实现的Codec控制IO
    3. Mixers和Audio控制
    4. Codec音频操作
    5. DAPM描述
    6. DAPM事件处理
    7. DAC静音控制(可选)

    4.2.1 数据结构

    Codec层主要结构体包括snd_soc_codec、snd_soc_codec_driver、snd_soc_dai、snd_soc_dai_driver

    snd_soc_codec代表一个Codec设备, 其主要字段如下

    struct snd_soc_codec {
    	struct device *dev;        /* 指向Codec设备的指针 */
    	const struct snd_soc_codec_driver *driver;  /* 该Codec对应的驱动 */
    	struct list_head list;
    
    	/* runtime */
    	unsigned int cache_init:1; /* 指示Codec cache是否初始化 */
    
    	/* codec IO */
    	void *control_data;        /* 控制IO数据 */
    	hw_write_t hw_write;       /* 控制IO函数 */
    	void *reg_cache;
    
    	/* component */
    	struct snd_soc_component component;
    };
    

    snd_soc_codec_driver代表一个Codec驱动, 其主要字段如下

    struct snd_soc_codec_driver {
    	/* 操作集 */
    	int (*probe)(struct snd_soc_codec *);
    	int (*remove)(struct snd_soc_codec *);
    	int (*suspend)(struct snd_soc_codec *);
    	int (*resume)(struct snd_soc_codec *);
    	struct snd_soc_component_driver component_driver;
    
    	/* codec wide operations */
    	int (*set_sysclk)(struct snd_soc_codec *codec,
    			  int clk_id, int source, unsigned int freq, int dir);
    	int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
    		unsigned int freq_in, unsigned int freq_out);
    	int (*set_jack)(struct snd_soc_codec *codec,
    			struct snd_soc_jack *jack,  void *data);
    
    	/* Codec IO相关函数 */
    	struct regmap *(*get_regmap)(struct device *);
    	unsigned int (*read)(struct snd_soc_codec *, unsigned int);
    	int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
    	
    	/* 偏置电压配置函数 */
    	int (*set_bias_level)(struct snd_soc_codec *,
    			      enum snd_soc_bias_level level);
    };
    

    snd_soc_dai代表DAI运行时数据, 其主要字段如下

    struct snd_soc_dai {
    	const char *name;    /* 名称 */
    	int id;              /* 索引 */
    	struct device *dev;  /* DAI设备 */
    
    	/* 驱动操作集 */
    	struct snd_soc_dai_driver *driver;
    
    	/* DAI运行时信息 */
    	unsigned int capture_active:1;
    	unsigned int playback_active:1;
    	unsigned int symmetric_rates:1;
    	unsigned int symmetric_channels:1;
    	unsigned int symmetric_samplebits:1;
    	unsigned int probed:1;
    
    	unsigned int active;
    
    	struct snd_soc_dapm_widget *playback_widget;
    	struct snd_soc_dapm_widget *capture_widget;
    
    	/* DAI DMA data */
    	void *playback_dma_data;  /* 用于管理playback DMA */
    	void *capture_dma_data;   /* 用于管理capture DMA */
    
    	/* Symmetry data - only valid if symmetry is being enforced */
    	unsigned int rate;
    	unsigned int channels;
    	unsigned int sample_bits;
    
    	/* parent platform/codec */
    	struct snd_soc_codec *codec;           /* 绑定的Codec */
    	struct snd_soc_component *component;   /* 绑定的platform */
    
    	struct list_head list;
    };
    

    snd_soc_dai_driver代表一个DAI驱动, 其主要字段如下

    struct snd_soc_dai_driver {
    	/* DAI描述 */
    	const char *name;
    	unsigned int id;
    	unsigned int base;
    	struct snd_soc_dobj dobj;
    
    	/* DAI驱动回调 */
    	int (*probe)(struct snd_soc_dai *dai);
    	int (*remove)(struct snd_soc_dai *dai);
    	int (*suspend)(struct snd_soc_dai *dai);
    	int (*resume)(struct snd_soc_dai *dai);
    	/* compress dai */
    	int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
    	/* Optional Callback used at pcm creation*/
    	int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
    		       struct snd_soc_dai *dai);
    	/* DAI is also used for the control bus */
    	bool bus_control;
    
    	/* 操作集 */
    	const struct snd_soc_dai_ops *ops;
    	const struct snd_soc_cdai_ops *cops;
    
    	/* DAI能力 */
    	struct snd_soc_pcm_stream capture;
    	struct snd_soc_pcm_stream playback;
    	unsigned int symmetric_rates:1;
    	unsigned int symmetric_channels:1;
    	unsigned int symmetric_samplebits:1;
    
    	/* probe ordering - for components with runtime dependencies */
    	int probe_order;
    	int remove_order;
    };
    

    4.2.2 接口

    int snd_soc_register_codec(struct device *dev, const struct snd_soc_codec_driver *, struct snd_soc_dai_driver *, int num_dai);
    

    snd_soc_register_codec完成了如下事宜
    1. 分配snd_soc_codec空间
    2. 调用snd_soc_component_initialize()初始化snd_soc_codec::snd_soc_component
    3. snd_soc_codec::snd_soc_component操作集初始化
    4. DAPM相关初始化
    5. 调用snd_soc_register_dais()注册num_dai个DAI
    6. 将该Codec添加至全局Codec链表codec_list中

    4.2.3 实现

    Codec的一般实现步骤如下
    1. 获取Codec设备资源
    2. 实现snd_soc_codec_driver结构体
    3. 实现snd_soc_dai_driver结构体
    4. 实现snd_soc_dai_ops结构体, 并赋值给snd_soc_dai_driver::ops
    5. 调用snd_soc_register_codec()注册Codec

    4.3 Platform

    Platform驱动可分为三个部分: 音频DMA驱动、SoC DAI驱动和DSP驱动
    这些驱动代码应该只和SoC CPU有关而和Board无关

    FIXME: Later

    4.4 Machine

    Machine/Board驱动用来将所有的部件驱动(Codecs、Platforms和DAIs)进行关联

    FIXME: Later

    参考:
    <内核Alsa之ASoC>
    <Linux音频子系统>
    <Linux Sound Subsystem Documentation>

  • 相关阅读:
    KindEditor限制输入字数
    datepicker日历控件使用
    复合注解的解析
    jdk包结构及用途分析
    深入理解BIO、NIO、AIO
    MiniCat:手写Http服务器
    基于TCP协议的Socket编程
    手写一个最迷你的Web服务器
    手写一个Web服务器,极简版Tomcat
    java网络编程socket的使用
  • 原文地址:https://www.cnblogs.com/hzl6255/p/9979377.html
Copyright © 2011-2022 走看看