嵌入式驱动开发之---Linux ALSA音频驱动(一)

摘要:
本文就是结合实例来分析ALSA音频驱动。开发环境:ubuntu10.04目标板:linux-2.6.37编译器:arm-none-linux-gnueabi-接下来,我们首先要了解的是ALSA整体架构,架构图如下:在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制。内核空间中,alsa-soc其实是对alsa-driver的进一步封装,他针对嵌入式设备提供了一些列增强的功能。

本文的部分内容参考来自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整体架构,架构图如下:

嵌入式驱动开发之---Linux ALSA音频驱动(一)第1张

在内核设备驱动层,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系统接口:

嵌入式驱动开发之---Linux ALSA音频驱动(一)第2张

嵌入式驱动开发之---Linux ALSA音频驱动(一)第3张

我们可以看到以下设备文件:
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> 了解声卡的结构体structsnd_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
");
        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
");
    if (!debugfs_create_file("dais", 0444, debugfs_root, NULL,
                 &dai_list_fops))
        pr_warn("ASoC: Failed to create DAI list debugfs file
");
    if (!debugfs_create_file("platforms", 0444, debugfs_root, NULL,
                 &platform_list_fops))
        pr_warn("ASoC: Failed to create platform list debugfs file
");
#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目录:

嵌入式驱动开发之---Linux ALSA音频驱动(一)第12张

在目录下我们发现了asoc这文件夹,在这个文件夹包含我们创建的几个文件codecs, dais, platforms:

嵌入式驱动开发之---Linux ALSA音频驱动(一)第13张

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
");
        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
");
        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!
");
    ret = snd_soc_register_card(card);
    if (ret != 0) {
        dev_err(&pdev->dev, "Failed to register card
");
        return ret;
    }
    return 0;
}
复制代码

4> 声卡创建

主要分析snd_soc_register_card()函数。

http://www.cnblogs.com/cslunatic/archive/2013/05/15/3079383.html

免责声明:文章转载自《嵌入式驱动开发之---Linux ALSA音频驱动(一)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇pyspark获取和处理RDD数据windows下开多个CMD窗口多个进程输出下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

【华为云技术分享】Linux内核发展史 (1)

openEuler是基于Linux 内核的企业级Linux 服务器操作系统平台的开源社区发行版。openEuler支持鲲鹏架构,可运行在TaiShan服务器上。本技术连载将会从理论基础、源码分析和实操方法三个方面来比较全面地介绍内核编程与应用编程的基础知识,到2020年8月之前主要介绍内核编程部分。通过本连载的介绍,您将对openEuler内核编程和应用编...

mtd-utils 的 使用

关于编译可以查看文章:《Arm-Linux 移植 mtd-utils 1.x》 查看信息 使用命令前用cat /proc/mtd 查看一下mtdchar字符设备;或者用ls -l /dev/mtd* cat /proc/mtddev: size erasesize name mtd0: 00c00000 00020000 "ROOTFS"mtd...

PHP苹果推送实现(APNS)

以下资料网上收集整理得来 转自:https://www.cnblogs.com/ymms/p/3865913.html 1、在ios dev center制作相关证书和文件用客户端实现(不再赘述,网上很多,)网上教程:http://blog.csdn.net/lizhenning87/article/details/8259270官方文档:https://...

Halcon一维测量官方案例解析

下面的例子简要介绍了如何使用HALCON的一维测量工具。最长的部分是预处理和后处理;测量本身只包括两个操作符调用。 测量保险丝-fuse 预处理主要是测量线的生成。在示例程序中,这个步骤是通过将测量对象的参数分配给变量来完成的。 read_image (Fuse, 'fuse') Row := 297 Column := 545 Length1 := 80...

hadoop集群的搭建与配置(1)

前言             首先hadoop是在linux系统上进行搭建的,我们首先要在自己电脑上装上虚拟机然后装linux系统,因为我们是测试:在本地虚拟机上装两个linux分别搭建好hadoop环境能实现简单的数据处理就可以了,最终的效果类似于我们c#中的hello world!能做到这里我们的搭建测试环境就算基本上完成了。虚拟机以及linux系统...

Linux源码Kconfig文件语法分析

Kconfig是我们进行内核配置的关键文件,用于生成menuconfig的界面并生成最终确定编译选项的.config文件。关于Kconfig文件的编写规则,在Documentation/kbuild/kconfig-language.txt有详尽的叙述。这里主要用实例进行语法分析。 config 确定了条目前面是否有选项,menuconfig界面中的条目中...