您的位置:首页 > 其它

ALSA之codec分析

2013-10-31 14:13 453 查看
ALSA: Advanced Linux Sound Architecture,它包括内核驱动集合、API库和工具。用户层程序直接调用libsound的API库,不需要打开设备等操作,因此编程者不需要了解底层细节。

里不分析ALSA的核心代码core,也不阐述如何在用户层进行声卡编程,仅仅简要介绍在ALSA的架构上添加一个声卡驱动,即上图中的Sound
Driver。其实文档《wirte an alsa
driver》很详尽的介绍如何写一个ALSA驱动,但是那是以PCI声卡为例的。在嵌入式中,音频数据传输一般用I2S接口,控制一般用I2c或SPI
接口。如下仅以嵌入式声卡为例,其驱动代码一般放在sound/soc下面。
以数据结构为线索,简要解析其过程。每个重要结构体旁边有个类似的标号[XX],[xx]为[0]时,表明这个结构体是一个大类,包含标号为[1]的结构体……;[xx]为[EXT]时,表明该结构体不在本模块使用。

CODEC
驱动代码位于sound/soc/codec下,如uda134x.c。
struct snd_soc_dai [0]
/*

 * Digital Audio Interface runtime data.

 *

 * Holds runtime data for a DAI.

 */

struct snd_soc_dai {

    /* DAI description */

    char *name;

    unsigned int id;

    int ac97_control;

    struct device *dev;

    void *ac97_pdata;    /* platform_data for the ac97 codec */

    /* DAI callbacks */

    int (*probe)(struct platform_device *pdev,

         struct snd_soc_dai *dai);

    void (*remove)(struct platform_device *pdev,

         struct snd_soc_dai *dai);

    int (*suspend)(struct snd_soc_dai *dai);

    int (*resume)(struct snd_soc_dai *dai);

    /* ops */

    struct snd_soc_dai_ops *ops;

    /* DAI capabilities */

    struct snd_soc_pcm_stream capture;

    struct snd_soc_pcm_stream playback;

    unsigned int symmetric_rates:1;

    /* DAI runtime info */

    struct snd_pcm_runtime *runtime;

    struct snd_soc_codec *codec;

    unsigned int active;

    unsigned char pop_wait:1;

    void *dma_data;

    /* DAI private data */

    void *private_data;

    /* parent platform */

    struct snd_soc_platform *platform;

    struct list_head list;

};
/*
* Digital Audio Interface runtime data.
*
* Holds runtime data for a DAI.
*/
struct snd_soc_dai {
/* DAI description */
char *name;
unsigned int id;
int ac97_control;

struct device *dev;
void *ac97_pdata; /* platform_data for the ac97 codec */

/* DAI callbacks */
int (*probe)(struct platform_device *pdev,
struct snd_soc_dai *dai);
void (*remove)(struct platform_device *pdev,
struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);

/* ops */
struct snd_soc_dai_ops *ops;

/* DAI capabilities */
struct snd_soc_pcm_stream capture;
struct snd_soc_pcm_stream playback;
unsigned int symmetric_rates:1;

/* DAI runtime info */
struct snd_pcm_runtime *runtime;
struct snd_soc_codec *codec;
unsigned int active;
unsigned char pop_wait:1;
void *dma_data;

/* DAI private data */
void *private_data;

/* parent platform */
struct snd_soc_platform *platform;

struct list_head list;
};模块初始化函数中都需要调用snd_soc_register_dai()将定义好的结构体snd_soc_dai注册到ALSA中。而对于结构体snd_soc_dai有几个成员是非常重要的,如name、capture、playback、ops。name
指定模块声卡名称;capture是录音参数设定,playback是播放参数设定,均包含channel数目、PCM_RATE和PCM_FMTBIT
等信息;ops是声卡操作函数集合指针,这个操作函数集合详见struct
snd_soc_dai_ops定义,主要实现的有hw_params(硬件参数设定)、digital_mute(静音操作)、set_fmt(格式配
置)等,这些函数的实现均与硬件相关,根据硬件的数据手册来实现。
以下是struct snd_soc_dai的定义范例:
struct snd_soc_dai uda134x_dai = {

    .name = "UDA134X",

    /* playback capabilities */

    .playback = {

        .stream_name = "Playback",

        .channels_min = 1,

        .channels_max = 2,

        .rates = UDA134X_RATES,

        .formats = UDA134X_FORMATS,

    },

    /* capture capabilities */

    .capture = {

        .stream_name = "Capture",

        .channels_min = 1,

        .channels_max = 2,

        .rates = UDA134X_RATES,

        .formats = UDA134X_FORMATS,

    },

    /* pcm operations */

    .ops = &uda134x_dai_ops,

};
struct snd_soc_dai uda134x_dai = {
.name = "UDA134X",
/* playback capabilities */
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = UDA134X_RATES,
.formats = UDA134X_FORMATS,
},
/* capture capabilities */
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = UDA134X_RATES,
.formats = UDA134X_FORMATS,
},
/* pcm operations */
.ops = &uda134x_dai_ops,
};小结:以上的结构体看起来复杂,实现上基本结构是非常简单易懂的。所要做的工作有:1/定义snd_soc_dai和
snd_soc_dai_ops这两个结构体,前者设置好capture &
playback的参数和声卡函数操作集合指针,该指针指向snd_soc_dai_ops结构体;2/根据硬件数据手册编写相关操作函数如
hw_params、set_fmt和digital_mute等;3/编写模块初始化函数uda134x_init(),调用
snd_soc_register_dai()注册之前定义好的snd_soc_dai。注:关于2的相关操作函数,之前也提过控制一般用
I2C或SPI接口的。但是在操作函数里,我们可以使用<struct snd_soc_codec
*>codec->hw_write()来操作。当然在probe函数中,hw_write是在probe初始化好的,如
codec->hw_write = (hw_write_t)i2c_master_send;,这就使得控制接口抽象起来。
struct snd_soc_codec_device [EXT]

下来有一个结构体snd_soc_codec_device要留意的,一般来说,这个结构体是在codec下定义,但是注册操作是在另外一个文件中进行
的,以2410的UDA134X为例是在sound/soc/s3c24xx/s3c24xx_uda134x.c。这些留到以后分析,这里只是需要将这
个结构体EXPORT出来就行了如:EXPORT_SYMBOL_GPL(soc_codec_dev_uda134x);。
先看snd_soc_codec_device结构体定义:
/* codec device */

struct snd_soc_codec_device {

    int (*probe)(struct platform_device *pdev);

    int (*remove)(struct platform_device *pdev);

    int (*suspend)(struct platform_device *pdev, pm_message_t state);

    int (*resume)(struct platform_device *pdev);

};
/* codec device */
struct snd_soc_codec_device {
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
int (*suspend)(struct platform_device *pdev, pm_message_t state);
int (*resume)(struct platform_device *pdev);
};根据结构体定义,我们可以按部就班编写uda134x_probe、uda134x_remove、
uda134x_suspend和uda134x_resume函数,然后将这个结构体EXPORT出来,使其在之后的模块中注册。Probe指声卡的探
测与初始化,remove指声卡的卸载,suspend指声卡的休眠,resume指声卡从休眠状态下恢复。详细介绍probe函数。先看一下probe的代码脉络:
static int uda134x_soc_probe(struct platform_device *pdev)

{

    //获得snd_soc_device结构体

    struct snd_soc_device *socdev = platform_get_drvdata(pdev);

    struct snd_soc_codec *codec;

    …

    //为codec分配内存

    socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);

    if (socdev->card->codec == NULL)

        return ret;

    codec = socdev->card->codec;

    …

    //初始化codec

    codec->name = "uda134x";

    codec->owner = THIS_MODULE;

    codec->dai = &uda134x_dai; //指向上面定义好的dai

    codec->num_dai = 1;

    codec->read = uda134x_read_reg_cache; //控制接口—读

    codec->write = uda134x_write; //控制接口—写

    …

    mutex_init(&codec->mutex);

    INIT_LIST_HEAD(&codec->dapm_widgets);

    INIT_LIST_HEAD(&codec->dapm_paths);

    

    …

    /* register pcms */

    ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);

    …

    ret = snd_soc_add_controls(codec, uda134x_snd_controls,

                    ARRAY_SIZE(uda134x_snd_controls));

    …

    /* register card */

    ret = snd_soc_init_card(socdev);

}
static int uda134x_soc_probe(struct platform_device *pdev)
{
//获得snd_soc_device结构体
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;

//为codec分配内存
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (socdev->card->codec == NULL)
return ret;

codec = socdev->card->codec;



//初始化codec
codec->name = "uda134x";
codec->owner = THIS_MODULE;
codec->dai = &uda134x_dai; //指向上面定义好的dai
codec->num_dai = 1;
codec->read = uda134x_read_reg_cache; //控制接口—读
codec->write = uda134x_write; //控制接口—写



mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);



/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);



ret = snd_soc_add_controls(codec, uda134x_snd_controls,
ARRAY_SIZE(uda134x_snd_controls));



/* register card */
ret = snd_soc_init_card(socdev);
}开始看到socdev =
platform_get_drvdata(pdev)这句不免有点疑惑,到底pdev是在哪里初始化好了?答案是sound/soc
/<SOC>目录下的文件中,如sound/soc/s3c24xx/s3c24xx_uda134x.c。在声卡的初始化过程中,其实首先
是调用sound/soc/<SOC>下的相关驱动的probe函数,在probe有platform_set_drvdata()的操作,
这里有个将指针类型的转换:(struct snd_soc_device *s) à (struct platform_device
*),Linux驱动抽象模型无处不在。snd_soc_add_controls()将操作集合挂到card->control链
表上来,这个集合实现了音频播放时各个参数的设置,主要有.info、.get和.set。如playback volume
control:SOC_DOUBLE_R_TLV("Playback Volume", SNDCARD_REG_L_GAIN,
SNDCARD_REG_R_GAIN, 0, 192, 0,
digital_tlv),其中SNDCARD_REG_L_GAIN和SNDCARD_REG_R_GAIN分别是左右声道音量增益寄存器偏移。最终要
调用的函数都是在soc-core.c里面的,这里只是提供一些跟硬件相关的参数,大为增加了代码的复用性。
对于函数
snd_soc_new_pcms()是这样描述的:Create a new sound card based upon the codec
and interface pcms.这个函数非常重要,用于创建一个PCM实例以便播放数据流。函数里重要的是如下两句:
ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card);

ret = soc_new_pcm(socdev, &card->dai_link[i], i);
ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card);
ret = soc_new_pcm(socdev, &card->dai_link[i], i);前
者create and initialize a soundcard
structure,然后这个codec->card会接下的snd_soc_init_card()进行初始化。后者创建播放流/录音流的子流,
将所有播放流/录音流的子流操作函数设为soc_pcm_ops。
小结:这一部分有点复杂,要实现probe、remove、
suspend和resume,还有一系列的snd_kcontrol_new参数设置函数数组。另外有个地方要清楚的:初始化的过程是
SOC_soc_init()->platform_device_add()->soc_codec_dev_uda134x.probe
即uda134x_probe()。【对于s3c24xx_uda134x
是:s3c24xx_uda134x_probe()->platform_device_add()->soc_codec_dev_uda134x.probe
即uda134x_soc_probe()】

SOC
驱动代码位于sound/soc/<SOC>下,如s3c24xx_uda134x.c。这部分的probe是先于CODEC中的probe调用的。
struct snd_soc_device [0]
/* SoC Device - the audio subsystem */

struct snd_soc_device {

    struct device *dev;

    struct snd_soc_card *card;

    struct snd_soc_codec_device *codec_dev;

    void *codec_data;

};
/* SoC Device - the audio subsystem */
struct snd_soc_device {
struct device *dev;
struct snd_soc_card *card;
struct snd_soc_codec_device *codec_dev;
void *codec_data;
};这个结构体用于向内核注册一个device。初始化一般如下:
static struct snd_soc_device SOC_SNDCARD_snd_devdata = {
.card = &snd_soc_s3c24xx_uda134x,
.codec_dev = &soc_codec_dev_uda134x,//就是CODEC定义的snd_soc_codec_device结构体
.codec_data = &s3c24xx_uda134x, //私有数据,一般存放SNDCARD控制接口信息,如I2C从设备地址等
};对于module_init,其实用platform_driver_register注册一个platform_driver结构体的方式也好,还是直接写一个init也好,都问题不大。前者更贴近Linux的驱动模型。Probe的一般过程如下:
static struct snd_soc_device SOC_SNDCARD_snd_devdata = {

    .card = &snd_soc_s3c24xx_uda134x,

    .codec_dev = &soc_codec_dev_uda134x,//就是CODEC定义的snd_soc_codec_device结构体

    .codec_data = &s3c24xx_uda134x, //私有数据,一般存放SNDCARD控制接口信息,如I2C从设备地址等

};
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
s3c24xx_uda134x_snd_devdata.codec_dev = &soc_codec_dev_uda134x;
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
platform_set_drvdata(s3c24xx_uda134x_snd_device,
& s3c24xx_uda134x_snd_devdata);
s3c24xx_uda134x_snd_devdata.dev = & s3c24xx_uda134x_snd_device->dev;
platform_device_add(s3c24xx_uda134x_snd_device);
}可以看到CODEC定义的struct snd_soc_codec_device
soc_codec_dev_uda134x在这里进行platform_device_add的,随后便执行
soc_codec_dev_uda134x.probe,其过程可以复习CODEC中的uda134x_soc_probe()。接下来是s3c24xx_uda134x_snd_devdata.card的来历。
struct snd_soc_card [1]

/* SoC card */

struct snd_soc_card {

    char *name;

    struct device *dev;

    struct list_head list;

    int instantiated;

    int (*probe)(struct platform_device *pdev);

    int (*remove)(struct platform_device *pdev);

    /* the pre and post PM functions are used to do any PM work before and

     * after the codec and DAI's do any PM work. */

    int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);

    int (*suspend_post)(struct platform_device *pdev, pm_message_t state);

    int (*resume_pre)(struct platform_device *pdev);

    int (*resume_post)(struct platform_device *pdev);

    /* callbacks */

    int (*set_bias_level)(struct snd_soc_card *,

             enum snd_soc_bias_level level);

    /* CPU <--> Codec DAI links */

    struct snd_soc_dai_link *dai_link;

    int num_links;

    struct snd_soc_device *socdev;

    struct snd_soc_codec *codec;

    struct snd_soc_platform *platform;

    struct delayed_work delayed_work;

    struct work_struct deferred_resume_work;

};
/* SoC card */
struct snd_soc_card {
char *name;
struct device *dev;

struct list_head list;

int instantiated;

int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);

/* the pre and post PM functions are used to do any PM work before and
* after the codec and DAI's do any PM work. */
int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);
int (*suspend_post)(struct platform_device *pdev, pm_message_t state);
int (*resume_pre)(struct platform_device *pdev);
int (*resume_post)(struct platform_device *pdev);

/* callbacks */
int (*set_bias_level)(struct snd_soc_card *,
enum snd_soc_bias_level level);

/* CPU <--> Codec DAI links */
struct snd_soc_dai_link *dai_link;
int num_links;

struct snd_soc_device *socdev;

struct snd_soc_codec *codec;

struct snd_soc_platform *platform;
struct delayed_work delayed_work;
struct work_struct deferred_resume_work;
};定义这个结构体是让snd_soc_register_card()注册一个card的。初始化范例:static struct snd_soc_card snd_soc_s3c24xx_uda134x = {

    .name = "S3C24XX_UDA134X",

    .platform = &s3c24xx_soc_platform,

    .dai_link = &s3c24xx_uda134x_dai_link,

    .num_links = 1,

};
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name = "S3C24XX_UDA134X",
.platform = &s3c24xx_soc_platform,
.dai_link = &s3c24xx_uda134x_dai_link,
.num_links = 1,
};s3c24xx_soc_platform是定义在PCM中的,包含一系列pcm_ops操作函数集合等信息,这里不谈。成员.dai_link是本模块中的另外一个主角。
/* SoC machine DAI configuration, glues a codec and cpu DAI together */

struct snd_soc_dai_link {

    char *name;            /* Codec name */

    char *stream_name;        /* Stream name */

    /* DAI */

    struct snd_soc_dai *codec_dai;

    struct snd_soc_dai *cpu_dai;

    /* machine stream operations */

    struct snd_soc_ops *ops;

    /* codec/machine specific init - e.g. add machine controls */

    int (*init)(struct snd_soc_codec *codec);

    /* Symmetry requirements */

    unsigned int symmetric_rates:1;

    /* Symmetry data - only valid if symmetry is being enforced */

    unsigned int rate;

    /* DAI pcm */

    struct snd_pcm *pcm;

};

/* SoC machine DAI configuration, glues a codec and cpu DAI together */
struct snd_soc_dai_link {
char *name; /* Codec name */
char *stream_name; /* Stream name */

/* DAI */
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai;

/* machine stream operations */
struct snd_soc_ops *ops;

/* codec/machine specific init - e.g. add machine controls */
int (*init)(struct snd_soc_codec *codec);

/* Symmetry requirements */
unsigned int symmetric_rates:1;

/* Symmetry data - only valid if symmetry is being enforced */
unsigned int rate;

/* DAI pcm */
struct snd_pcm *pcm;
};因为一个平台可以运行多个音频设备,snd_soc_dai_link的作用也在这,将CODEC定义的
snd_soc_dai挂到一个链表上。我这里没往下深究。.name指定codec名称;.codec_dai指向CODEC定义的
snd_soc_dai结构体;.cpu_dai指向I2S定义的snd_soc_dai结构体;.ops接下来分析。一般来说,初始化以上几个成员就行
了。struct snd_soc_ops [3]
/* SoC audio ops */

struct snd_soc_ops {

    int (*startup)(struct snd_pcm_substream *);

    void (*shutdown)(struct snd_pcm_substream *);

    int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);

    int (*hw_free)(struct snd_pcm_substream *);

    int (*prepare)(struct snd_pcm_substream *);

    int (*trigger)(struct snd_pcm_substream *, int);

};
/* SoC audio ops */
struct snd_soc_ops {
int (*startup)(struct snd_pcm_substream *);
void (*shutdown)(struct snd_pcm_substream *);
int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
int (*hw_free)(struct snd_pcm_substream *);
int (*prepare)(struct snd_pcm_substream *);
int (*trigger)(struct snd_pcm_substream *, int);
};在这里,一般需要实现.hw_paras、.startup和.shutdown,这些均是对pcm substream进行操作的。例如在s3c24xx_soc_hw_params()中,有:snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S);//设置为I2S mode

snd_soc_dai_set_sysclk(codec_dai, 0, 11289600, SND_SOC_CLOCK_IN);//设置主时钟MCLK频率
snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S);//设置为I2S mode
snd_soc_dai_set_sysclk(codec_dai, 0, 11289600, SND_SOC_CLOCK_IN);//设置主时钟MCLK频率注:
如果留意到CODEC小结中的2/,那么就会明白,在这里定义的操作最终会调用到CODEC(或许还有PCM、I2S中的)里定义好
的<snd_soc_dai_ops>.ops.set_fmt、<snd_soc_dai_ops>.ops.set_sysclk
等。
小结:这一层的重要的结构体是一脉相承的,并没有复杂的分支,除了<snd_soc_device>.codec_dev
是从CODEC Export过来,<snd_soc_card>.platform从PCM
Export过来,<snd_soc_dai_link>.cpu_dai从I2S
Export过来。函数主要是module_init()和一个snd_soc_ops操作函数集合。这一层负责将音频设备的几部分模块与CPU平台挂起
来。
注:底层硬件操作—
Codec--控制接口及芯片基本初始化
Pcm  --pcm dma操作
I2s  --i2s配置操作

后i2s和pcm其实都跟codec差不多了,只需要理解alsa-core、<soc>、<codec、pcm、i2s>三层
的关系。其中codec、pcm、i2s可以看做同层的,分别对于音频设备的control、dma、i2s接口;<codec、pcm、
i2s>会分别export相关结构体给<soc>层,<soc>层将音频设备三部分与CPU
Spec联结起来,其probe顺序是<SOC>.probe-><codec, pcm,
i2s>.probe;另外<codec、pcm、i2s>在各自的module_init中将自身注册到alsa-core中。
有空再写一些pcm dma方面的。

阅读(566) | 评论(0) | 转发(0) |

0
上一篇:ALSA之PCM分析

下一篇:安装Ubuntu10.10的vm tools

相关热门文章
便宜卖TDS2024C TDS2024C TDS2...

本科毕业设计(论文)选题...

vim插件clang_complete的安装...

ADP服务数据为王的时代 经销商...

宁隆贵金属交易所现货白银合作...

linux守护进程的几个关键地方...

stagefright与opencore对比

嵌入式Linux之我行——u-boot-...

嵌入式Linux之我行——内核、...

CodeNavigator -- 程序员必备...

ChinaUnix & ITPUB社区12周年...

ssh连接出现以下提示,求解...

如何扩展MYSQL

准备做一个大型门户,用户什么...

gbk or utf8

给主人留下些什么吧!~~

评论热议
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: