本文的部分內容參考來自DroidPhone的博客(http://blog.csdn.net/droidphone/article/details/6271122),關於ALSA寫得很不錯的文章,只是少了實例。本文就是結合實例來分析ALSA音頻驅動。
開發環境:ubuntu10.04
目標板:linux-2.6.37 (通過命令uname -r 查看linux內核版信息)
編譯器:arm-none-linux-gnueabi- (none 代表編譯器的制作者,比如:fsl代表飛思卡爾,內核里面談EABI,OABI,其實相對於系統調用的方式,當然我們所說的系統限於arm系統)
接下來,我們首先要了解的是ALSA整體架構,架構圖如下:

在內核設備驅動層,ALSA提供了alsa-driver,同時在應用層,ALSA為我們提供了alsa-lib,應用程序只要調用alsa-lib提供的API(本開發板/usr/lib/libasound.so.2 和 libasound.so.2.0.0 下alsa-lib庫asound),即可以完成對底層音頻硬件的控制。內核空間中,alsa-soc其實是對alsa-driver的進一步封裝,他針對嵌入式設備提供了一些列增強的功能。
接下來我們查看設備文件和sys系統接口:


我們可以看到以下設備文件:
controlC0 --> 用於聲卡1的控制,例如通道選擇,混音,麥克風的控制等
controlC1 --> 用於聲卡2的控制,例如通道選擇,混音,麥克風的控制等
midiC0D0 --> 用於播放midi音頻 (我的驅動不具有)
pcmC0D0c --> 用於聲卡1錄音的pcm設備(tvp5158音頻采集)
pcmC0D1c --> 用於聲卡1錄音的pcm設備(tlv320aic3x音頻采集)
pcmC0D1P --> 用於聲卡1播放的pcm設備(tlv320aic3x音頻輸出)
pcmC1D0p --> 用於聲卡2播放的pcm設備(hdmi音頻輸出)
seq --〉 音序器 (我的驅動不具有)
timer --〉 定時器
由此可以看出具有2個聲卡,聲卡1具有2個錄音設備和一個播放設備,聲卡2只具有一個播放設備
其中,C0D0代表的是聲卡0中的設備0,pcmC0D0c最后一個c代表capture,pcmC0D0p最后一個p代表playback,這些都是alsa-driver中的命名規則。
從上面的分析可以看出,我的聲卡下掛了7個設備(其實這里的設備是實際設備的邏輯分類),根據聲卡的實際能力,驅動實際上可以掛上更多種類的設備,在include/sound/core.h中,定義了以下設備類型:
typedef int __bitwise snd_device_type_t; #define SNDRV_DEV_TOPLEVEL ((__force snd_device_type_t) 0) #define SNDRV_DEV_CONTROL ((__force snd_device_type_t) 1) //控制類型 #define SNDRV_DEV_LOWLEVEL_PRE ((__force snd_device_type_t) 2) #define SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0x1000) #define SNDRV_DEV_PCM ((__force snd_device_type_t) 0x1001) //pcm類型 #define SNDRV_DEV_RAWMIDI ((__force snd_device_type_t) 0x1002) #define SNDRV_DEV_TIMER ((__force snd_device_type_t) 0x1003) //定時器類型 #define SNDRV_DEV_SEQUENCER ((__force snd_device_type_t) 0x1004) //音序器類型 #define SNDRV_DEV_HWDEP ((__force snd_device_type_t) 0x1005) #define SNDRV_DEV_INFO ((__force snd_device_type_t) 0x1006) #define SNDRV_DEV_BUS ((__force snd_device_type_t) 0x1007) #define SNDRV_DEV_CODEC ((__force snd_device_type_t) 0x1008) //解碼器類型 #define SNDRV_DEV_JACK ((__force snd_device_type_t) 0x1009) #define SNDRV_DEV_LOWLEVEL ((__force snd_device_type_t) 0x2000)
下面我們開始分析代碼:
首先我們有兩個聲卡,那這兩個聲卡怎么來的呢?
首先你應該知道聲卡的創建過程:
<1> 了解聲卡的結構體struct snd_card(snd_card的定義位於頭文件中:include/sound/core.h)
struct snd_card { int number; /* number of soundcard (index to snd_cards) */ char id[16]; /* id string of this card */ char driver[16]; /* driver name */ char shortname[32]; /* short name of this soundcard */ char longname[80]; /* name of this soundcard */ char mixername[80]; /* mixer name */ char components[128]; /* card components delimited with space */ struct module *module; /* top-level module */ void *private_data; /* private data for soundcard */ void (*private_free) (struct snd_card *card); /* callback for freeing of private data */ struct list_head devices; /* devices */ unsigned int last_numid; /* last used numeric ID */ struct rw_semaphore controls_rwsem; /* controls list lock */ rwlock_t ctl_files_rwlock; /* ctl_files list lock */ int controls_count; /* count of all controls */ int user_ctl_count; /* count of all user controls */ struct list_head controls; /* all controls for this card */ struct list_head ctl_files; /* active control files */ struct snd_info_entry *proc_root; /* root for soundcard specific files */ struct snd_info_entry *proc_id; /* the card id */ struct proc_dir_entry *proc_root_link; /* number link to real id */ struct list_head files_list; /* all files associated to this card */ struct snd_shutdown_f_ops *s_f_ops; /* file operations in the shutdown state */ spinlock_t files_lock; /* lock the files for this card */ int shutdown; /* this card is going down */ int free_on_last_close; /* free in context of file_release */ wait_queue_head_t shutdown_sleep; struct device *dev; /* device assigned to this card */ struct device *card_dev; /* cardX object for sysfs */ #ifdef CONFIG_PM unsigned int power_state; /* power state */ struct mutex power_lock; /* power lock */ wait_queue_head_t power_sleep; #endif #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) struct snd_mixer_oss *mixer_oss; int mixer_oss_change_count; #endif };
struct list_head devices 記錄該聲卡下所有邏輯設備的鏈表
struct list_head controls 記錄該聲卡下所有的控制單元的鏈表
void *private_data 聲卡的私有數據,可以在創建聲卡時通過參數指定數據的大小
<2> 創建一個聲卡的實例
在ASoC首先注冊平台驅動,等待平台設備的到來,當驅動發現相應的設備時,調用驅動的probe, 然后調用snd_soc_register_card去創建聲卡,聲卡的專用數據,設備驅動的ID的名字,創建聲卡的功能部件(如pcm, mixer, MIDI,control等),注冊聲卡。
1> 注冊平台驅動
/* ASoC platform driver */ static struct platform_driver soc_driver = { .driver = { .name = "soc-audio", .owner = THIS_MODULE, .pm = &soc_pm_ops, }, .probe = soc_probe, .remove = soc_remove, };
static int __init snd_soc_init(void) { #ifdef CONFIG_DEBUG_FS debugfs_root = debugfs_create_dir("asoc", NULL); if (IS_ERR(debugfs_root) || !debugfs_root) { printk(KERN_WARNING "ASoC: Failed to create debugfs directory\n"); debugfs_root = NULL; } if (!debugfs_create_file("codecs", 0444, debugfs_root, NULL, &codec_list_fops)) pr_warn("ASoC: Failed to create CODEC list debugfs file\n"); if (!debugfs_create_file("dais", 0444, debugfs_root, NULL, &dai_list_fops)) pr_warn("ASoC: Failed to create DAI list debugfs file\n"); if (!debugfs_create_file("platforms", 0444, debugfs_root, NULL, &platform_list_fops)) pr_warn("ASoC: Failed to create platform list debugfs file\n"); #endif return platform_driver_register(&soc_driver); } module_init(snd_soc_init); static void __exit snd_soc_exit(void) { #ifdef CONFIG_DEBUG_FS debugfs_remove_recursive(debugfs_root); #endif platform_driver_unregister(&soc_driver); } module_exit(snd_soc_exit);
snd_soc_init(sound/soc/soc-core.c)函數主要是創建debugfs文件系統接口和平台設備驅動的注冊(platform_driver_register)。
這里我們解釋下DebugFS,顧名思義,是一種用於內核調試的虛擬文件系統,內核開發者通過debugfs和用戶空間交換數據。類似的虛擬文件系統還有procfs和sysfs等,這幾種虛擬文件系統都並不實際存儲在硬盤上,而是Linux內核運行起來后才建立起來。用戶空間通過mount -t debugfs debugfs /a 掛載debugfs文件系統到a目錄,查看a目錄:

在目錄下我們發現了asoc這文件夾,在這個文件夾包含我們創建的幾個文件codecs, dais, platforms:

2> 注冊平台設備(跟具體的平台相關,我們TI達芬奇系列芯片)
static int __init ti81xx_dvr_soc_init(void) { int ret; ti81xx_pdev0 = platform_device_alloc("soc-audio", 0); if (!ti81xx_pdev0) return -ENOMEM; platform_set_drvdata(ti81xx_pdev0, &ti81xx_dvr_snd_card0); ret = platform_device_add(ti81xx_pdev0); if (ret) { printk(KERN_ERR "Can't add soc platform device\n"); platform_device_put(ti81xx_pdev0); return ret; } ti81xx_pdev1 = platform_device_alloc("soc-audio", 1); if (!ti81xx_pdev1) { platform_device_put(ti81xx_pdev0); return -ENOMEM; } platform_set_drvdata(ti81xx_pdev1, &ti81xx_dvr_snd_card1); ret = platform_device_add(ti81xx_pdev1); if (ret) { printk(KERN_ERR "Can't add soc platform device\n"); platform_device_put(ti81xx_pdev0); platform_device_put(ti81xx_pdev1); return ret; } return ret; } static void __exit ti81xx_dvr_soc_exit(void) { platform_device_unregister(ti81xx_pdev0); platform_device_unregister(ti81xx_pdev1); } module_init(ti81xx_dvr_soc_init); module_exit(ti81xx_dvr_soc_exit);
ti81xx_dvr_soc_init(sound/soc/davinci)函數主要是創建兩個平台設備。platform_device_alloc()函數為平台設備分配空間,platform_set_drvdata()函數設置平台設備的私有數據,platform_device_add()函數向平台總線增加平台設備。
3> probe的實現
先看上面兩段代碼發現都有"soc-audio" 這個字符串,這個字符串決定了驅動和設備的匹配,而且發現注冊了兩個平台設備。當平台驅動匹配一個平台設備,調用一次porbe, 因為注冊了兩個同名的平台設備,所有probe被調用了兩次。也就是申請兩個聲卡驅動。
/* probes a new socdev */ static int soc_probe(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); int ret = 0; /* Bodge while we unpick instantiation */ card->dev = &pdev->dev; INIT_LIST_HEAD(&card->dai_dev_list); INIT_LIST_HEAD(&card->codec_dev_list); INIT_LIST_HEAD(&card->platform_dev_list); printk(KERN_WARNING "soc audio probe!\n"); ret = snd_soc_register_card(card); if (ret != 0) { dev_err(&pdev->dev, "Failed to register card\n"); return ret; } return 0; }
4> 聲卡創建
主要分析snd_soc_register_card()函數。
