Audio Codec

clipboard.png
clipboard1.png

在移动设备中,Codec的作用可以归结为4种,分别是:

1.对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
2.对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
3.对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
4.对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等

移动设备中的ALSA(ASoC)

ASoC--ALSA System on Chip ,是为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。ASoC不能单独存在,它建立在标准ALSA驱动之上,必须和标准的ALSA驱动框架相结合才能工作。

Machine

是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体.

Platform (Soc)

一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中.实际上,把Platform认为是某个SoC更好理解.

Codec

字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用.嵌入式Codec通常通过I2C对内部的寄存器进行控制.

在软件层面, ASoC也把嵌入式设备的音频系统同样分为3大部分, Machine, Platform和Codec

Machine驱动:

跟单板相关,绑定Platform和Codec驱动,即表明使用的是哪个Platform,哪个CPU DAI、DMA、Codec和Codec DAI。

Platform驱动:

它包含了该SoC平台的音频DMA和音频接口DAI的配置和控制( I2S, PCM等等), (DAI: Digital Audio Interface)

Codec驱动:

它包含了一些音频的控件( Controls),音频接口, DAMP(动态音频电源管理)的定义和某些Codec IO功能。所有的Codec驱动都要提供以下特性:
Codec DAI 和 PCM的配置信息;
Codec的IO控制方式( I2C, SPI等);
Mixer和其他的音频控件;
Codec的ALSA音频操作接口;

ASoC把声卡实现为一个Platform Device,然后利用Platform_device结构中的dev字段:dev.drvdata,它实际上指向一个snd_soc_device结构.可以认为snd_soc_device是整个ASoC数据结构的根本,

snd_soc_device结构引出了snd_soc_card
snd_soc_card又引出了snd_soc_platform、snd_soc_dai_link和snd_soc_codec结构
,
snd_soc_card代表着Machine驱动,
snd_soc_platform则代表着Platform驱动,
snd_soc_codec和soc_codec_device则代表了Codec驱动,

而snd_soc_dai_link则负责连接Platform和Codec.

ASoC架构中的Machine

Machine驱动,结构alc5623_card, 里面包含dai_link。

Machine驱动在一个重要的数据结构snd_soc_dai_link中,指定了Platform、 Codec、 codec_dai、 cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform, codec, dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来, Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。

ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。
ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上

  1. platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe( alc5623_probe())的调用,它正是整个ASoC驱动初始化的入口。

  2. 在soc_probe函数中会完成以下任务:
    调用标准的alsa函数创建声卡实例 (定义 alc5623_card ,注册 snd_soc_register_card))

  3. 挨个调用了codec, dai和platform驱动的probe函数

  4. 调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备

  5. 最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册

static struct snd_soc_dai_link alc5623_dai_link = {
    .name           = "ASOC-alc5623",
    .stream_name    = "alc5623 HiFi",
    .cpu_dai_name   = DEV_NAME_I2S,         /* nxp_snd_i2s_driver name */
    .platform_name  = DEV_NAME_PCM,         /* nxp_snd_pcm_driver name */
    .codec_dai_name = "alc5621-hifi",       /* alc5623_dai's name */
    .codec_name     = "alc562x-codec.0-001a",       /* alc5623_i2c_driver name + '.' + bus + '-' + address(7bit) */
    .ops            = &alc5623_ops,
    .symmetric_rates = 1,
    .init           = alc5623_dai_init,
    .ops            = &alc5623_ops,
};

static struct snd_soc_card alc5623_card = {
    .name           = "I2S-alc5623",        /* proc/asound/cards */
    .owner          = THIS_MODULE,
    .dai_link       = &alc5623_dai_link,
    .num_links      = 1,
    .suspend_pre    = &alc5623_suspend_pre,
    .resume_pre     = &alc5623_resume_pre,
    .resume_post    = &alc5623_resume_post,
};

ASoC架构中的Codec 是一个i2c_driver

描述Codec的最主要的几个数据结构分别是:
snd_soc_codec_driver,snd_soc_dai_driver

确定snd_soc_codec_driver和snd_soc_dai-driver的实例,并把它们注册到系统中,注册后的codec和dai才能为Machine驱动所用

其中的snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驱动中也会使用到, Platform和Codec的DAI通过snd_soc_dai_link结构,在Machine驱动中进行绑定连接。

Codec驱动的步骤:
定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用snd_soc_register_codec函数对Codec进行注册。

static struct snd_soc_dai_driver alc5632_dai = {
    .name = "alc5632-hifi",
    .playback = {
        .stream_name = "HiFi Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rate_min = 8000,
        .rate_max = 48000,
        .rates = SNDRV_PCM_RATE_8000_48000,
        .formats = ALC5632_FORMATS,},
    .capture = {
        .stream_name = "HiFi Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rate_min = 8000,
        .rate_max = 48000,
        .rates = SNDRV_PCM_RATE_8000_48000,
        .formats = ALC5632_FORMATS,},

    .ops = &alc5632_dai_ops,
    .symmetric_rates = 1,
};

static struct snd_soc_codec_driver soc_codec_device_alc5632 = {
    .probe = alc5632_probe,
    .remove = alc5632_remove,
    .suspend = alc5632_suspend,
    .resume = alc5632_resume,
    .set_bias_level = alc5632_set_bias_level,
    .controls = alc5632_snd_controls,
    .num_controls = ARRAY_SIZE(alc5632_snd_controls),
    .dapm_widgets = alc5632_dapm_widgets,
    .num_dapm_widgets = ARRAY_SIZE(alc5632_dapm_widgets),
    .dapm_routes = alc5632_dapm_routes,
    .num_dapm_routes = ARRAY_SIZE(alc5632_dapm_routes),
};


    ret = snd_soc_register_codec(&client->dev,
        &soc_codec_device_alc5632, &alc5632_dai, 1);

在snd_soc_register_codec函数中,
传入参数snd_soc_codec_driver和snd_soc_dai_driver, 并创建snd_soc_codec和snd_soc_dai。
它申请了一个snd_soc_codec结构的实例:确定codec的名字,这个名字很重要, Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较("alc5623-hifi"),从而找到该Codec的!然后初始化snd_soc_codec结构的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例。
通过snd_soc_register_dais函数对本Codec的dai进行注册
在snd_soc_register_dais函数中为每个snd_soc_dai实例分配内存,确定dai的名字,用snd_soc_dai_driver实例的字段对它进行必要初始化
最后,它把codec实例链接到全局链表codec_list中,把dai链接到全局链表dai_list中,并且调snd_soc_instantiate_cards函数触发Machine驱动进行一次匹配绑定操作

/* SoC Audio Codec device */  
struct snd_soc_codec {  
    const char *name;  /* Codec的名字*/  
    struct device *dev; /* 指向Codec设备的指针 */  
    const struct snd_soc_codec_driver *driver; /* 指向该codec的驱动的指针 */  
    struct snd_soc_card *card;    /* 指向Machine驱动的card实例 */  
    int num_dai; /* 该Codec数字接口的个数,目前越来越多的Codec带有多个I2S或者是PCM接口 */    
    /* runtime */  
    ......  
    /* codec IO */  
    void *control_data; /* 该指针指向的结构用于对codec的控制,通常和read,write字段联合使用 */  
    enum snd_soc_control_type control_type;/* 可以是SND_SOC_SPI,SND_SOC_I2C,SND_SOC_REGMAP中的一种 */  
    unsigned int (*read)(struct snd_soc_codec *, unsigned int);  /* 读取Codec寄存器的函数 */  
    int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);  /* 写入Codec寄存器的函数 */  
    /* dapm */  
    struct snd_soc_dapm_context dapm;  /* 用于DAPM控件 */  
};  
/* codec driver */  
struct snd_soc_codec_driver {  
    /* driver ops */  
    int (*probe)(struct snd_soc_codec *);  /* codec驱动的probe函数,由snd_soc_instantiate_card回调 */  
    int (*remove)(struct snd_soc_codec *);    
    int (*suspend)(struct snd_soc_codec *);  /* 电源管理 */  
    int (*resume)(struct snd_soc_codec *);  /* 电源管理 */  
....
}

/*  
 * Digital Audio Interface runtime data.  
 *  
 * Holds runtime data for a DAI.  
 */  
struct snd_soc_dai {  
    const char *name;  /* dai的名字 */  
    struct device *dev;  /* 设备指针 */  
  
    /* driver ops */  
    struct snd_soc_dai_driver *driver;  /* 指向dai驱动结构的指针 */  
  
    /* DAI runtime info */  
    unsigned int capture_active:1;      /* stream is in use */  
    unsigned int playback_active:1;     /* stream is in use */  
  
    /* DAI DMA data */  
    void *playback_dma_data;  /* 用于管理playback dma */  
    void *capture_dma_data;  /* 用于管理capture dma */  
  
    /* parent platform/codec */  
    union {  
        struct snd_soc_platform *platform;  /* 如果是cpu dai,指向所绑定的平台 */  
        struct snd_soc_codec *codec;  /* 如果是codec dai指向所绑定的codec */  
    };  
    struct snd_soc_card *card;  /* 指向Machine驱动中的crad实例 */  
};  

/*  
 * Digital Audio Interface Driver.  
 *  
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97  
 * operations and capabilities. Codec and platform drivers will register this  
 * structure for every DAI they have.  
 *  
 * This structure covers the clocking, formating and ALSA operations for each  
 * interface.  
 */  
struct snd_soc_dai_driver {  
    /* DAI description */  
    const char *name;  /* dai驱动名字 */  
  
    /* DAI driver callbacks */  
    int (*probe)(struct snd_soc_dai *dai);  /* dai驱动的probe函数,由snd_soc_instantiate_card回调 */  
    int (*remove)(struct snd_soc_dai *dai);    
    int (*suspend)(struct snd_soc_dai *dai);  /* 电源管理 */  
    int (*resume)(struct snd_soc_dai *dai);    
  
    /* ops */  
    const struct snd_soc_dai_ops *ops;  /* 指向本dai的snd_soc_dai_ops结构 */  
  
    /* DAI capabilities */  
    struct snd_soc_pcm_stream capture;  /* 描述capture的能力 */  
    struct snd_soc_pcm_stream playback;  /* 描述playback的能力 */  
};  
soc-core.c
int snd_soc_register_codec(struct device *dev,
               const struct snd_soc_codec_driver *codec_drv,
               struct snd_soc_dai_driver *dai_drv,
               int num_dai)
{
    size_t reg_size;
    struct snd_soc_codec *codec;
    int ret, i;

    dev_dbg(dev, "codec register %s\n", dev_name(dev));

    codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
    if (codec == NULL)
        return -ENOMEM;

    /* create CODEC component name */
    codec->name = fmt_single_name(dev, &codec->id);
    if (codec->name == NULL) {
        kfree(codec);
        return -ENOMEM;
    }

    if (codec_drv->compress_type)
        codec->compress_type = codec_drv->compress_type;
    else
        codec->compress_type = SND_SOC_FLAT_COMPRESSION;

    codec->write = codec_drv->write;
    codec->read = codec_drv->read;
    codec->volatile_register = codec_drv->volatile_register;
    codec->readable_register = codec_drv->readable_register;
    codec->writable_register = codec_drv->writable_register;
    codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time;
    codec->dapm.bias_level = SND_SOC_BIAS_OFF;
    codec->dapm.dev = dev;
    codec->dapm.codec = codec;
    codec->dapm.seq_notifier = codec_drv->seq_notifier;
    codec->dapm.stream_event = codec_drv->stream_event;
    codec->dev = dev;
    codec->driver = codec_drv;
    codec->num_dai = num_dai;
    mutex_init(&codec->mutex);

    /* allocate CODEC register cache */
    if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
        reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
        codec->reg_size = reg_size;
        /* it is necessary to make a copy of the default register cache
         * because in the case of using a compression type that requires
         * the default register cache to be marked as __devinitconst the
         * kernel might have freed the array by the time we initialize
         * the cache.
         */
        if (codec_drv->reg_cache_default) {
            codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,
                              reg_size, GFP_KERNEL);
            if (!codec->reg_def_copy) {
                ret = -ENOMEM;
                goto fail;
            }
        }
    }

    if (codec_drv->reg_access_size && codec_drv->reg_access_default) {
        if (!codec->volatile_register)
            codec->volatile_register = snd_soc_default_volatile_register;
        if (!codec->readable_register)
            codec->readable_register = snd_soc_default_readable_register;
        if (!codec->writable_register)
            codec->writable_register = snd_soc_default_writable_register;
    }

    for (i = 0; i < num_dai; i++) {
        fixup_codec_formats(&dai_drv[i].playback);
        fixup_codec_formats(&dai_drv[i].capture);
    }

    /* register any DAIs */
    if (num_dai) {
        ret = snd_soc_register_dais(dev, dai_drv, num_dai);
        if (ret < 0)
            goto fail;
    }

    mutex_lock(&client_mutex);
    list_add(&codec->list, &codec_list);
    snd_soc_instantiate_cards();
    mutex_unlock(&client_mutex);

ASoC架构中的Platform

Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口( cpu_dai)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。

在具体实现上, ASoC有把Platform驱动分为两个部分: snd_soc_platform_driver和snd_soc_dai_driver。其中, platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中, dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

snd_soc_platform_driver

platform驱动一般平台相关(nxp-pcm.c)

driver:
ASoC把snd_soc_platform_driver注册为一个系统的platform_driver
           snd_soc_register_platform()
device:
platform_device_register(&pcm_device);
dai_driver

主要完成cpu一侧的dai的参数配置
cpu dai 驱动也定义为platform_device
dai驱动通常对应cpu的一个或几个I2S/PCM接口

driver:   
nxp-i2s.c platform_driver_register(&i2s_driver);

定义一个snd_soc_dai_driver结构的实例;
在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者snd_soc_register_dais,注册snd_soc_dai实例;
实现snd_soc_dai_driver结构中的probe、suspend等回调;
实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;

device : S5p4418/Devices.c   platform_device i2s_device_ch0 ....
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 一.声音参数基本概念: 声音是连续模拟量,计算机将它离散化之后用数字表示,就有了以下几个名词术语。 样本长度(sa...
    cs1001阅读 2,700评论 0 2
  • 前言 今天跟王总聊天,他狠狠地(形容词)叼了我,他说你为什么不写一些比较牛逼的文章,为什么总是写一些教别人如何安装...
    da4ac7fb9bd5阅读 4,923评论 0 49
  • 一.声音参数基本概念: 声音是连续模拟量,计算机将它离散化之后用数字表示,就有了以下几个名词术语。 样本长度(sa...
    cs1001阅读 5,376评论 0 3
  • 程序总体结构 以sound/soc/pxa/corgi.c为例来分析 为了描述声卡,定义snd_soc_card实...
    郑俊飞阅读 3,022评论 0 1
  • 有的人生来就有温暖的保护壳,他做了什么觉得困难的事,不想干了,又可以缩回壳里,如果他什么都不想做,依然可以安稳的过...
    RY麦布阅读 133评论 0 0