记一次Android音频问题的解决--外挂音频codec与内部av音频输出的冲突
问题背景
由于中小型企业机顶盒市场的消亡,各个企业不得不另寻出路,寻找更好的产品,这里只举由其变形出来的一款新产品,Android系统soundbar,姑且土里土气地称之为智能电视音箱吧。由于涉及到音频部分的修改,着实也算是一项相当程度上专业的产品。针对音频部分的调试,问题的兼容,音效等等的要求也是在一定程度上考验一个企业的资源实力和应变实力。好了,再说下去话题就会有点跑偏了。
问题的产生
项目使用的是全志平台H系列型号板子,应客户需求需要I2S信号输出给其功放板音频输出。当时调试发现主板无法使用主模式,只能作为从模式跟全志平台自家音频IC,ac100的方式一样。打入ac100补丁后发现可以使用,因此直接用上这个补丁(曾经出现过语音助手无法识别的问题,后已被解决),这里便出现了模拟音频输出(AV输出)无声音的问题。
原因查找
a.对于一个全能的Android工程师,咳咳咳嗯,有时候打字也是能被噎到的。。。简单强调的意思是,全局的思想和查找问题有法有度对于系统问题的定位是很重要的。
因此第一步当然从原理图上查,发现现在主板上使用的AV通道是用的主控内部的声卡接口,而标准外挂的ac100板子(因为本次项目无ac100,因此对比了另外项目的原理图)是使用的ac100 Codec的AV接口。
由此进一步思路,猜想缺少了内部codex驱动的加载,查看内部codec的驱动是否没有加载,经查,确实如此。
由于移植ac100驱动的时候切换了编译指向,在目录lichee\linux-3.4\arch\arm\configs中由sun8iw7p1smp_android_defconfig切至sun8iw7p1smp_android_karaok_defconfig,有几个驱动没有被加载,其中注释的一个是因为代码后期被我移植到ac100驱动里面(此处主要是节点),编译进去之后,av声音有了!
至此,解决了关键的问题,接av线无声音的问题。嗯,事实证明脚踏实地,一步一步的检查问题的思路还是正确的,催眠一下自己,给自己一点前进的鼓励和希望。因为,接下来问题还没完。。。
b.此时虽然av输出声音有了,但是问题是ac100处hdmi声音无输出,如果是做过box或者是电视相关的产品的朋友们应该比较熟悉,这里的验证操作,无非就是进入设置,将声音部分的音频支持设备勾选,列表一般会有:CODEC,HDMI,SPDIF,这里先不谈及SPDIF。
这里情况就是,先前是切换电视机HDMI时候有声音,且av显示模式的时候无声音,现在av通道有声音之后,两者打开,却是切av有声音,切HDMI时无声音,这就很奇怪了,明明HDMI部分没有修改,其实这里的HDMI的声音输出,用的正是ac100这块IC的通信驱动,因此这里猜想是两者有什么冲突。
原因的继续查找
发现是由于av模拟输出和ac100是两个声卡设备,并且系统里面共用了一个节点(要么使用ac100,要么使用内部codec,个人猜想可能是全志当时设计的时候没打算让客户同时使用这两个声卡)
最终原因是在上图看出。
问题的最终解决
由于客户需求是要模拟通道声音av输出和HDMI声音(使用的ac100驱动的修改)同时出来,所以免去人为勾选,直接给默认勾选。题外话,这里也查找了上层的设置,但是Settings中的勾选是从数据库Settings.db中去读取,了解上层的人都知道这是在SettingsProvider中的default.xml可以配置,但是还必须有底层功能的支持才能有这样的节点配置项,因此还是老老实实将底层的通路打通吧。
解决的过程
在驱动中将内部codec的节点移至ac100驱动代码里面(当然,但从节点移植这个看,不移应该也没问题,只是没有验证),然后再同时注册两个声卡节点(以下是驱动代码修改补丁)
diff --git a/linux-3.4/arch/arm/configs/sun8iw7p1smp_android_karaok_defconfig b/linux-3.4/arch/arm/configs/sun8iw7p1smp_android_karaok_defconfig
index 9008005..5cb65a5 100755
--- a/linux-3.4/arch/arm/configs/sun8iw7p1smp_android_karaok_defconfig
+++ b/linux-3.4/arch/arm/configs/sun8iw7p1smp_android_karaok_defconfig
@@ -2194,9 +2194,9 @@ CONFIG_SND_USB_AUDIO=m
# CONFIG_SND_USB_6FIRE is not set
CONFIG_SND_SOC=y
CONFIG_SND_SOC_DMAENGINE_PCM=y
-# CONFIG_SND_SUNXI_SOC_AUDIOCODEC is not set
-# CONFIG_SND_SUNXI_SOC_PUBLUC_MACHINE is not set
-# CONFIG_SND_SUN8IW7_SNDCODEC is not set
+CONFIG_SND_SUNXI_SOC_AUDIOCODEC=y
+#CONFIG_SND_SUNXI_SOC_PUBLUC_MACHINE=y
+CONFIG_SND_SUN8IW7_SNDCODEC=y
CONFIG_SND_SUNXI_SOC_DAUDIO0_INTERFACE=y
CONFIG_SND_SUNXI_SOC_DAUDIO0_KARAOKE_MACHINE=y
CONFIG_SND_VIRBB_DAI=y
diff --git a/linux-3.4/sound/soc/sunxi/daudio0/h3_ac100.c b/linux-3.4/sound/soc/sunxi/daudio0/h3_ac100.c
index 2dd220b..e029662 100755
--- a/linux-3.4/sound/soc/sunxi/daudio0/h3_ac100.c
+++ b/linux-3.4/sound/soc/sunxi/daudio0/h3_ac100.c
@@ -41,6 +41,18 @@
#include "./../hdmiaudio/sunxi-hdmitdm.h"
#include "./../spdif/sunxi_spdif.h"
#include "../../codecs/ac100.h"
+//add by liu
+#include <sound/initval.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/io.h>
+//#include "../audiocodec/sunxi_codecdma.h"
+#include <sound/initval.h>
+#include <sound/soc.h>
+#define SUNXI_CODEC 0
+#define SUNXI_PCM_RATES (SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT)
+
+
static __audio_hdmi_func g_hdmi_func;
static hdmi_audio_t hdmi_para;
@@ -146,12 +158,14 @@ static int i2s_set_dma_daudio0_to_hdmi(int on)
}
snd_dmaengine_pcm_trigger_diy(down_prtdma, SNDRV_PCM_TRIGGER_START, 0, slave_config.direction, 1024, 512);
+ printk("----------<0> function:%s,--------snd_dmaengine_pcm_trigger_diy--------------\n", __func__);
} else {
if (!down_prtdma) {
printk("<0> function:%s, down_prtdma = null !!!!\n",__func__);
return -ENOMEM;
}
snd_dmaengine_pcm_trigger_diy(down_prtdma, SNDRV_PCM_TRIGGER_STOP, 0, 0, 1024, 512);
+ printk("----------<0> function:%s,--------snd_dmaengine_pcm_trigger_diy--------------\n", __func__);
snd_dmaengine_pcm_close_diy(down_prtdma);
kfree(down_prtdma);
}
@@ -589,6 +603,9 @@ static int sunxi_daudio_init(struct snd_soc_pcm_runtime *rtd)
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
struct snd_soc_card *card = rtd->card;
+ //add card2
+ struct snd_soc_card *card2 = rtd->card;
+ //add end
runtime = rtd;
snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTR");
@@ -649,8 +666,8 @@ printk("%s,line;%d\n",__func__,__LINE__);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
atomic_dec(&pcm_active);
if (atomic_read(&pcm_active) == 0) {
- tdm2_tx_enable(0, 0);
- spdif_txctrl_enable(0, 2, 0);
+ //tdm2_tx_enable(0, 0);
+ //spdif_txctrl_enable(0, 2, 0);
}
}
return 0;
@@ -661,8 +678,8 @@ printk("%s,line;%d\n",__func__,__LINE__);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
msleep(100);
atomic_inc(&pcm_active);
- tdm2_tx_enable(1, 1);
- spdif_txctrl_enable(1, 2, 1);
+ //tdm2_tx_enable(1, 1);
+ //spdif_txctrl_enable(1, 2, 1);
} else {
struct snd_pcm_runtime *runtime = substream->runtime;
if (runtime->status->state != SNDRV_PCM_STATE_XRUN) {
@@ -688,8 +705,43 @@ static struct snd_soc_ops sunxi_snddaudio1_ops = {
static struct snd_soc_ops phone_system_voice_ops = {
.hw_params = phone_system_voice_hw_params,
};
+#if SUNXI_CODEC
+static struct snd_soc_dai_driver sunxi_pcm_dai = {
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SUNXI_PCM_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SUNXI_PCM_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+ },
+
+ #if defined CONFIG_ARCH_SUN8IW5 || defined CONFIG_ARCH_SUN8IW9
+ .ops = &sunxi_i2s_dai_ops,
+ #endif
+};
+#endif
static struct snd_soc_dai_link sunxi_snddaudio_dai_link[] = {
+
+ /*{/*daudio0 to daudio2 IO-IO hdmi out*/
+ /* .name = "audiocodec",
+ .stream_name = "SUNXI-CODEC",
+ .cpu_dai_name = "sunxi-codec",
+ .codec_dai_name = "sndcodec",
+ .platform_name = "sunxi-pcm-codec-audio",
+ .codec_name = "sunxi-pcm-codec",
+
+ //#if defined CONFIG_ARCH_SUN8IW5 || defined CONFIG_ARCH_SUN8IW9
+ //.ops = &sunxi_snddaudio1_ops,
+ //.ops = &sunxi_snddaudio_ops,
+ //#endif
+ } ,*/
+
{
.name = "s_i2s1",
.cpu_dai_name = "pri_dai",
@@ -700,27 +752,56 @@ static struct snd_soc_dai_link sunxi_snddaudio_dai_link[] = {
.platform_name = "sunxi-daudio-pcm-audio.0",
.ops = &sunxi_snddaudio_ops,
},
- { /* Second DAI i/f */
- .name = "ac100 Voice",
+ /*{ /* Second DAI i/f */
+ /*.name = "ac100 Voice",
.stream_name = "Voice",
.cpu_dai_name = "pcm1",
.codec_dai_name = "ac100-aif2",
.codec_name = "ac100-codec",
.platform_name = "sunxi-daudio-pcm-audio1.0",
.ops = &sunxi_snddaudio1_ops,
- } ,
- {/*daudio0 to daudio2 IO-IO hdmi out*/
- .name = "VIR",
+ } ,*/
+ /* {/*daudio0 to daudio2 IO-IO hdmi out*/
+ /*.name = "VIR",
.cpu_dai_name = "bb-voice-dai",
.stream_name = "vir-dai",
.codec_dai_name = "ac100-aif1",
.codec_name = "ac100-codec",
.ops = &phone_system_voice_ops,
- } ,
+ } ,*/
+
};
+//add card2
+static struct snd_soc_dai_link sunxi_sndpcm_dai_link[] = {
+
+ {/*daudio0 to daudio2 IO-IO hdmi out*/
+ .name = "audiocodec",
+ .stream_name = "SUNXI-CODEC",
+ .cpu_dai_name = "sunxi-codec",
+ .codec_dai_name = "sndcodec",
+ .platform_name = "sunxi-pcm-codec-audio",
+ .codec_name = "sunxi-pcm-codec",
+
+ //#if defined CONFIG_ARCH_SUN8IW5 || defined CONFIG_ARCH_SUN8IW9
+ //.ops = &sunxi_snddaudio1_ops,
+ //.ops = &sunxi_snddaudio_ops,
+ //#endif
+ } ,
+
+};
+
+static struct snd_soc_card snd_soc_sunxi_sndpcm = {
+ .name = "audiocodec",
+ .owner = THIS_MODULE,
+ .dai_link = &sunxi_sndpcm_dai_link,
+ .num_links = 1,
+};
+//add end
+
static struct snd_soc_card snd_soc_sunxi_snddaudio = {
.name = "sndac100",
+ //.name = "audiocodec", //change from av
.owner = THIS_MODULE,
.dai_link = sunxi_snddaudio_dai_link,
.num_links = ARRAY_SIZE(sunxi_snddaudio_dai_link),
@@ -738,7 +819,9 @@ static int __devinit sunxi_snddaudio0_dev_probe(struct platform_device *pdev)
script_item_u val;
script_item_value_type_e type;
struct snd_soc_card *card = &snd_soc_sunxi_snddaudio;
-
+ //add card2
+ struct snd_soc_card *card2 = &snd_soc_sunxi_sndpcm;
+ //add end
type = script_get_item(TDM_NAME, "daudio_used", &val);
if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
pr_err("[daudio0]:%s,line:%d type err!\n", __func__, __LINE__);
@@ -785,6 +868,19 @@ static int __devinit sunxi_snddaudio0_dev_probe(struct platform_device *pdev)
mutex_init(&open_hdmi_mutex);
atomic_set(&pcm_active, 0);
+ //add card2
+ int ret2 = 0;
+ if(1){
+ card2->dev = &pdev->dev;
+
+ ret2 = snd_soc_register_card(card2);
+ if(ret2){
+ dev_err(&pdev->dev, "snd_soc_register_card(card2) failed: %d\n",
+ ret2);
+ }
+ }
+ //add end
+
if (daudio_used) {
card->dev = &pdev->dev;
@@ -797,22 +893,42 @@ static int __devinit sunxi_snddaudio0_dev_probe(struct platform_device *pdev)
pr_err("[daudio0]a83_ac100 cannot find any using configuration for controllers, return directly!\n");
return 0;
}
+
+ #if SUNXI_CODEC
+ int err = -1;
+ err = snd_soc_register_dai(&pdev->dev, &sunxi_pcm_dai);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to register DAI\n");
+ return err;
+ }
+ #endif
return ret;
}
static int __devexit sunxi_snddaudio0_dev_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
+ //add card2
+ struct snd_soc_card *card2 = platform_get_drvdata(pdev);
+ //add end
if (daudio_used) {
snd_soc_unregister_card(card);
}
+ if (1) {
+ snd_soc_unregister_card(card2);
+ }
+ #if SUNXI_CODEC
+ snd_soc_unregister_dai(&pdev->dev);
+ platform_set_drvdata(pdev, NULL);
+ #endif
return 0;
}
/*data relating*/
static struct platform_device sunxi_daudio_device = {
- .name = "snddaudio",
+ //.name = "snddaudio",
+ .name = "audiocodec", //change
.id = PLATFORM_DEVID_NONE,
};
@@ -821,7 +937,8 @@ static struct platform_driver sunxi_daudio_driver = {
.probe = sunxi_snddaudio0_dev_probe,
.remove = __exit_p(sunxi_snddaudio0_dev_remove),
.driver = {
- .name = "snddaudio",
+ //.name = "snddaudio",
+ .name = "audiocodec", //change
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
然后在Audio_hw.c中加入一个新的节点,替换掉原先ac100的节点名称,实质上是新增了一个声卡音频节点
路径如下/softwinner/common/hardware/audio/audio_hw.c
到这里,进入系统之后,设置中就会出现多一个AUDIO_CODECS选项,只需要在设置的声音输出设置中勾选新加的选项就可以了使用同时输出了。
最后的修改
最后只需要将设置中的选项默认勾选并且有效就可以了,这里可不能单单以为在配置中将选项添加就可以了,因为hal层做了处理,需要使哪个设备出声音,是有函数对对应的设备做操作的,这里用的函数是
set_audio_devices_active(adev, AUDIO_OUT, value);
因此我们在hal层中的audio_hw.c中添加以下代码:
声音通道都默认勾选,
至此,问题全部解决。
刚开始对于音频的架构和理解还不是特别熟悉,因此花了些时间来跟代码,查资料,当时看了很多对音频架构介绍的文档和博客,感谢各位大牛前辈的分享。同时在落实到实际问题查找的时候,确实觉得基础底子和思路尤为重要,因此希望借此可以将自己的一点思路和经验作为分享。
初次执笔,才疏学浅还望不吝指正。