Linux ALSA音頻驅動(一)


本文的部分內容參考來自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_initsound/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()函數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM