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/ac97c和sound/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>