零基础入门多媒体音频(5)-alsa(1)

alsa是音频最重要的框架,没有之一。接下来一个月时间在总结工作知识的主线上。单开个音频支线讲解alsa。说实话,alsa这块我也不是很精通。只在过去的项目中增加 一路substream实现低延时。我打算从下面四个方面去学习alsa。
1.alsa官网。网址如下,大家也可以学习后在评论里 交流,互相进步。如果想偷懒也可以直接看我的总结。
https://www.alsa-project.org/wiki/Main_Page

2.阅读在工作项目中alsa的源码。

3.整理网上各类免费的alsa资源。

4.向开发驱动的同事沟通请教。

写PCI驱动的基本流程

定义PCI ID表 (其它类型的驱动应该也有类似的结构体,比如I2S。后续探究一下)
定义probe回调函数
定义remove回调函数
创建struct pci_driver类型的结构体指向上面的三个元素
定义init 函数,在函数中调用pci_register_driver() 来注册上面定义的pci_driver结构体
定义exit函数来执行pci_unregister_driver()

简单总结一下上面的步骤,其实就两部分:1.定义钩子函数。2.将新定义的模块(结构体+函数)加入/移出框架

下面是个示例代码,重点关注snd_mychip_probe函数和snd_mychip_remove。这两个函数怎么暴露到alsa框架调用呢?

#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/initval.h>

/* module parameters (see "Module Parameters") */
/* SNDRV_CARDS: maximum number of cards supported by this module */
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;

/* definition of the chip-specific record */
struct mychip {
        struct snd_card *card;
        /* the rest of the implementation will be in section
         * "PCI Resource Management"
         */
};

/* chip-specific destructor
 * (see "PCI Resource Management")
 */
static int snd_mychip_free(struct mychip *chip)
{
        .... /* will be implemented later... */
}

/* component-destructor
 * (see "Management of Cards and Components")
 */
static int snd_mychip_dev_free(struct snd_device *device)
{
        return snd_mychip_free(device->device_data);
}

/* chip-specific constructor
 * (see "Management of Cards and Components")
 */
static int snd_mychip_create(struct snd_card *card,
                             struct pci_dev *pci,
                             struct mychip **rchip)
{
        struct mychip *chip;
        int err;
        static const struct snd_device_ops ops = {
               .dev_free = snd_mychip_dev_free,
        };

        *rchip = NULL;

        /* check PCI availability here
         * (see "PCI Resource Management")
         */
        ....

        /* allocate a chip-specific data with zero filled */
        chip = kzalloc(sizeof(*chip), GFP_KERNEL);
        if (chip == NULL)
                return -ENOMEM;

        chip->card = card;

        /* rest of initialization here; will be implemented
         * later, see "PCI Resource Management"
         */
        ....

        err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
        if (err < 0) {
                snd_mychip_free(chip);
                return err;
        }

        *rchip = chip;
        return 0;
}
// 对外接口
/* constructor -- see "Driver Constructor" sub-section */
static int snd_mychip_probe(struct pci_dev *pci,
                            const struct pci_device_id *pci_id)
{
        static int dev;
        struct snd_card *card;
        struct mychip *chip;
        int err;

        /* (1) */
        if (dev >= SNDRV_CARDS)
                return -ENODEV;
        if (!enable[dev]) {
                dev++;
                return -ENOENT;
        }

        /* (2) */
        err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
                           0, &card);
        if (err < 0)
                return err;

        /* (3) */
        err = snd_mychip_create(card, pci, &chip);
        if (err < 0)
                goto error;

        /* (4) */
        strcpy(card->driver, "My Chip");
        strcpy(card->shortname, "My Own Chip 123");
        sprintf(card->longname, "%s at 0x%lx irq %i",
                card->shortname, chip->port, chip->irq);

        /* (5) */
        .... /* implemented later */

        /* (6) */
        err = snd_card_register(card);
        if (err < 0)
                goto error;

        /* (7) */
        pci_set_drvdata(pci, card);
        dev++;
        return 0;

error:
        snd_card_free(card);
        return err;
}

/* destructor -- see the "Destructor" sub-section */
static void snd_mychip_remove(struct pci_dev *pci)
{
        snd_card_free(pci_get_drvdata(pci));
}

驱动的构造函数

PCI驱动的真正构造函数时probe回调函数。probe回调函数和所有被probe调用的函数都不可以用__init作为前缀名。因为所有的PCI设备都可能是个热插拔设备。
在probe回调函数中,遵循下面的基本流程。

1)检查和增加device索引。(一个card可以支持多个devcie)

static int dev;
....
if (dev >= SNDRV_CARDS)
        return -ENODEV;
if (!enable[dev]) {
        dev++;
        return -ENOENT;
}

2)创建声卡实例。这个函数是alsa框架提供的。创建alsa的声卡类型。

struct snd_card *card;
int err;
....
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
                   0, &card);

3)创建核心组件(与实际业务关联的结构体),在实例中,分配了PCI资源。

struct mychip *chip;
....
err = snd_mychip_create(card, pci, &chip);
if (err < 0)
        goto error;

发生异常时,probe函数需要处理异常。在这个示例中,包含一个错误处理逻辑在函数末尾。

error:
        snd_card_free(card);
        return err;

4)设置驱动的ID和名字。(这里的结构体是通过alsa框架函数获取的。实现了类似将驱动注册到alsa框架的效果)

strcpy(card->driver, "My Chip");
strcpy(card->shortname, "My Own Chip 123");
sprintf(card->longname, "%s at 0x%lx irq %i",
        card->shortname, chip->port, chip->irq);

驱动的字段包含芯片的ID字段。alsa-lib的配置器会使用这个字段。这个字段尽量做到简单和唯一。即使是同一个驱动也可以使用不同的驱动ID。通过不同的ID来区别不同功能的芯片。shortname 字段是个更加冗余的名字。而longname 字段包含了显示在 /proc/asound/cards的信息。

5)创建其它的组件,包括mixer,MIDI,以及其他的接口。如果需要有proc信息的功能,也可以定义在这里。

6)将PCI驱动注册到alsa框架

err = snd_card_register(card);
if (err < 0)
        goto error;

7)把驱动特定数据设置到card结构体

pci_set_drvdata(pci, card);
dev++;
return 0;

在上面的代码中,card的数据被保存下来了。这个指针会在回调函数中使用。

析构函数

remove回调函数就是析构函数,它会释放card实例。ALSA中间层会自动释放所有绑定的组件。
比较常见的方法就是调用snd_card_free()函数。

static void snd_mychip_remove(struct pci_dev *pci)
{
        snd_card_free(pci_get_drvdata(pci));
}

上述代码的前提是card指针被设置到PCI的驱动数据中。

从声卡card角度看,初始化共进行了这些操作。
snd_card_new 分配声卡
snd_card_register 注册声卡
pci_set_drvdata 绑定声卡
这些都是alsa-core的接口,也就是我们的驱动通过这些接口与alsa框架建立链接。

snd_device_new()来生成一个snd_device实例,并把该实例链接到snd_card的devices链表中

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值