Audio Codec
2018年08月30日 16:57:40 gbmaotai 閱讀數:545
在移動設備中,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都在註冊時連接到這三個全局鏈表上
platform總線會匹配這兩個名字相同的device和driver,同時會觸發soc_probe( alc5623_probe())的調用,它正是整個ASoC驅動初始化的入口。
在soc_probe函數中會完成以下任務:
調用標準的alsa函數創建聲卡實例 (定義 alc5623_card ,註冊 snd_soc_register_card))
挨個調用了codec, dai和platform驅動的probe函數
調用了soc_new_pcm()函數用於創建標準alsa驅動的pcm邏輯設備
最後則是調用標準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 ....