Linux音頻子系統(2) - ALSA Framework

  • 瞭解ALSA架構

1.Advanced Linux Sound Architecture (ALSA)

  The Advanced Linux Sound Architecture (ALSA) subsystem provides audio and MIDI capabilities to Linux systems, including a user space library to simplify application programming (alsa-lib) and support for the older Open Sound System (OSS) architecture through legacy compatibility modes. Specifically for system-on-chips, the architecture defines an ALSA system-on-chip (ASoC) layer which provides optimized support for embedded devices.

  ALSA 是一個完全開放源碼的音頻驅動程序集,是由志願者維護的開源項目,而 OSS 則是由公司提供的商業產品。ALSA 系統包括驅動包alsa-driver(集成在內核源碼),開發包 alsa-libs,開發包插件 alsalibplugins,設置管理工具包 alsa-utils,其他聲音相關處理小程序包alsa-tools,特殊音頻固件支持包 alsa-firmware,OSS 接口兼容模擬層工具 alsa-oss 共 7 個子項目,其中只有 alsa-driver 是必須的。除了 alsa-driver,ALSA 包含在用戶空間的 alsa-lib 函數庫,具有更加友好的編程接口,並且完全兼容於 OSS,開發者可以通過這些高級 API 使用驅動,不必直接與內核驅動 API 進行交互。

ALSA 主要有如下特點:

  • 支持多種聲卡設備、
  • 模塊化的內核驅動程序 、
  • 支持 SMP(對稱多處理)和多線程、
  • 提供應用開發函數庫
  • 兼容OSS應用程序

2.ALSA框架
在這裏插入圖片描述

  • User空間:主要由Alsa Libray API對應用程序提供統一的API接口,各個APP應用程序只要調用 alsa-lib 提供的 API接口來實現放音、錄音、控制。現在提供了兩套基本的庫,tinyalsa是一個簡化的alsa-lib庫,現在Android的系統中主要使用它。

  • Kernel空間:

    • ASOC Core:是 ALSA 的標準框架,是 ALSA-driver 的核心部分,提供了各種音頻設備驅動的通用方法和數據結構,爲 Audio driver提供 ALSA Driver API

    • ALSA CORE:alsa 核心層,向上提供邏輯設備(PCM/CTL/MIDI/TIMER/…)系統調用,向下驅動硬件設備。

    • Hardware Driver:音頻硬件設備驅動,由三大部分組成,分別是 Machine、Platform、Codec,提供的 ALSA Driver API 和相應音頻設備的初始化及工作流程,實現具體的功能組件,這也是驅動開發人員需要具體實現的部分;

      • Codec類: Codec即編解碼芯片的驅動,此Codec驅動是和平臺無關,包含的功能有: 音頻的控制接口,音頻讀寫IO接口,以及DAPM的定義等。如果需要的話,此Codec類可以在BT,FM,MODEM模塊中不做修改的使用。因此Codec就是一個可重複使用的模塊,同一個Codec在不同的SOC中可以使用。

      • Platform類: 可以理解爲某款SOC平臺,平臺驅動中包括音頻DMA引擎驅動,數字接口驅動(I2S, AC97, PCM)以及該平臺相關的任何音頻DSP驅動。同樣此Platform也可以重用,在不同的Machine中可以直接重複使用。

      • Machine類: Machine可以理解爲是一個橋樑,用於在Codec和Platform之間建立聯繫。此Machine指定了該機器使用那個Platform,那個Codec,最終會通過Machine建立兩者之間的聯繫。

2.1.Hardware Driver三者關係:
在這裏插入圖片描述

  • Platform
    指某款soc平臺的音頻模塊,比如qcom,omap,amlogic,atml等等。platform又可細分爲二個部分:

    • cpu dai:在嵌入式系統裏面通常指soc的i2s,pcm總線控制器,負責把音頻數據從I2S tx FIFO搬運到codec(playback,capture則相反)。cpu_dai通過 snd_soc_register_dai()來註冊。注:DAI是Digital Audio Interface的簡稱,分爲cpu_dai和codec_dai,這兩者通過i2s/pcm總線連接;AIF是Audio Interface母的簡稱,嵌入式系統中一般是I2S和PCM接口。
    • PCM dma:負責把dma buffer中的音頻數據搬運到i2s tx fifo。值得留意的是:某些情形下是不需要dma操作的,比如modem和codec直連,因爲modem本身已經把數據送到fifo了,這時只需要啓動codec_dai接收數據即可;該情形下,machine驅動dai_link中需要設定.platform_name = “snd_soc_dummy”,這是虛擬dma驅動,實現見sound/soc/soc-utils.c. 音頻dma驅動通過 snd_soc_register_platform()來註冊,故也常用platform來指代音頻dma驅動(這裏的platform需要與soc platfrom區分開)。
  • Codec:對於回放來說,userspace送過來的音頻數據是經過採樣量化的數字信號,在codec經過DAC轉換成模擬信號然後輸出到外放或耳機,這樣你就可以聽到聲音了,codec字面意思是編解碼器,但芯片(codec)裏面的功能部件很多,常見的有AIF,DAC,ADC,Mixer,PGA,line-in,line-out,有些高端的codec芯片還有EQ,DSP,SRC,DRC,AGC,Echo-Canceller,Noise-Suppression等部件。

  • Machine:指某款機器,通過配置dai_link把cpu_dai,codec_dai,modem_dai各個音頻接口給鏈結成一條條音頻鏈路,然後註冊snd_soc_card.和上面兩個不一樣,platform和codec驅動一般是可以重用的,而machine有它特定的硬件特性,幾乎是不可重用的。所謂的硬件特性指:Soc Platform與Codec的差異;DAIs之間的鏈結方式;通過某個GPIO打開Amplifier;通過某個GPIO檢測耳機插拔;使用某個時鐘如MCLK/External-OSC作爲i2s,CODEC的時鐘源等等。

2.2.以s3c24xx爲例:

Machine:
    s3c24xx_uda134x.c
    該文件是設備模型建立要執行的第一個文件。它聯繫了以上三個 文 件,導致了以 上三文件中的初始化函數的調用執行。填充了結構體 s3c24xx_uda134x_ops, s3c24xx_uda134x_dai_link,snd_soc_s3c24xx_uda134x, s3c24xx_uda134x, s3c24xx_uda134x_snd_devdata。添加了平臺設備 s3c24xx_uda134x_snd_device到內 核。註冊了與平臺設備"s3c24xx_uda134x"(移植時需要手動添加,還需要添加的是該設 備的platform_data)相匹配 的驅動 s3c24xx_uda134x_driver。   
    
Platform:
    s3c24xx-i2s.c  :該文件主要實現了配置cpu上iis接口寄存器的一些操作函數,填充了結構 體 s3c24xx_i2s_dai。
    dma.c 

Codec:
    uda134x.c 
    該文件主要實現了對編解碼芯片uda1341寄存器的設置,聲音調節,靜音設 置等操 作函數。填 充了  結  構 體 uda134x_dai。該文件還實現了一些重要的初始化,比如 創 建結構體類型爲snd_card的card實例,創建pcm實例等。

3.ALSA核心層

  核心層爲用戶空間提供邏輯設備接口, 同時爲驅動提供接口來驅動硬件設備, 主要位於sound/core目錄下。
在這裏插入圖片描述
分析是誰調用snd_register_device_for_dev函數來註冊sound設備的。
在這裏插入圖片描述

3.1.字符設備註冊

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)) {  
        snd_printk(KERN_ERR "unable to register native major device number %d/n", major);  
        return -EIO;  
    }  
    if (snd_info_init() < 0) {  
        unregister_chrdev(major, "alsa");  
        return -ENOMEM;  
    }  
    snd_info_minor_register();  
    return 0;  
}  

  註冊了主設備號爲116的字符設備文件,文件操作是snd_fops,snd_open接口比較重要,snd_open接口通過文件節點inode得到了次設備號,通過snd_minors數組得到對應的聲音邏輯設備的文件操作,調用對應的open接口,並調用fops_put替換成了對應的邏輯設備的文件操作(snd_minors裏維護)。

  snd_info_init創建proc目錄下的asound,並在該目錄下分別建立version EVM card0 cards device pcm timers,用來查看相關的信息。

static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
}

alsa驅動設備文件結構:

$ cd /dev/snd
$ ls -l

crw-rw----+ 1 root audio 116, 8 2011-02-23 21:38 controlC0
crw-rw----+ 1 root audio 116, 4 2011-02-23 21:38 midiC0D0
crw-rw----+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c
crw-rw----+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p
crw-rw----+ 1 root audio 116, 5 2011-02-23 21:38 pcmC0D1p
crw-rw----+ 1 root audio 116, 3 2011-02-23 21:38 seq
crw-rw----+ 1 root audio 116, 2 2011-02-23 21:38 timer
  • controlC0 :用於聲卡的控制,例如通道選擇,混音,麥克風的控制等
  • midiC0D0:用於播放midi音頻
  • pcmC0D0c:用於錄音的pcm設備
  • pcmC0D0p:用於播放的pcm設備
  • seq :音序器
  • timer :定時器

  其中, C0D0代表的是聲卡0中的設備0, pcmC0D0c最後一個c代表capture, pcmC0D0p最後一個p代表playback,這些都是alsa-driver中的命名規則。

/proc/asound

dr-xr-xr-x 6 root root 0 Oct  8 00:04 card0
-r–r–r– 1 root root 0 Oct  8 00:04 cards
-r–r–r– 1 root root 0 Oct  8 00:04 devices
-r–r–r– 1 root root 0 Oct  8 00:04 modules
dr-xr-xr-x 2 root root 0 Oct  8 00:04 oss
-r–r–r– 1 root root 0 Oct  8 00:04 pcm
dr-xr-xr-x 2 root root 0 Oct  8 00:04 seq
-r–r–r– 1 root root 0 Oct  8 00:04 timers
-r–r–r– 1 root root 0 Oct  8 00:04 version
  • cards : 可顯示系統中存在多少個聲卡
  • card0 : 代表某個聲卡
  • devices : 可顯示系統中存在多少個邏輯設備

/proc/asound/card0:該節點提供該card的一些info.

-r–r–r– 1 root root 0 Oct  7 19:57 audiopci
dr-xr-xr-x 2 root root 0 Oct  7 19:57 codec97#0
-r–r–r– 1 root root 0 Oct  7 19:57 id
-r–r–r– 1 root root 0 Oct  7 19:57 midi0
dr-xr-xr-x 3 root root 0 Oct  7 19:57 pcm0c
dr-xr-xr-x 3 root root 0 Oct  7 19:57 pcm0p
dr-xr-xr-x 3 root root 0 Oct  7 19:57 pcm1p

3.2 數據結構
在這裏插入圖片描述

該層包含的主要數據結構包括:

  • 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.3 APIs

該層主要接口如下:

/* 創建和初始化聲卡結構體 */
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);

4.card的創建與註冊

  struct snd_card可以說是整個ALSA音頻驅動最頂層的一個結構, 整個聲卡的軟件邏輯結構開始於該結構, 幾乎所有與聲音相關的邏輯設備都是在snd_card的管理之下, 聲卡驅動的第一個動作通常就是創建一個snd_card結構體。

4.1.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對應文件系統

4.2.snd_card_register

  函數的主要目的是註冊card下面掛載的所有device; 另外只有該函數成功返回後, 用戶空間才能通過control interface來訪問底層。
在這裏插入圖片描述
4.3.struct snd_device

  一個struct snd_device用於描述一個邏輯設備,每個邏輯設備都有一個對應的snd_device_ops, 我們在新增一個邏輯設備時, 需要爲此設備準備好該數據結構。

4.3.1.snd_device_new

  該API用於創建一個邏輯設備, 主要內容是分配一個struct snd_device空間, 初始化相關字段, 並把該邏輯設備添加到card->devices鏈表下。注意在添加新設備到card->devices鏈表時, 採用的是有序插入方式: type小的在前、type大的在後。

4.3.2.snd_device_register

  一般在註冊聲卡時(snd_card_register)會自動調用此處的API; 不過也可以在card註冊完畢後, 在手動調用此API註冊一個新的邏輯設備。

  該API的實現很簡單: 首先回調snd_device_ops->dev_register, 然後把核心層的狀態標記爲SNDRV_DEV_REGISTERED。

refer to

  • https://www.cnblogs.com/hzl6255/p/9979377.html
  • https://cloud.tencent.com/developer/article/1603864
  • https://cloud.tencent.com/developer/article/1541259
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章