音频下溢时候的造成的pop音问题是音频常见的一种问题,如果能找到根本原因避免这种下溢是最好的解决方案,但一个系统任何时候都不会有下溢产生我想也不大可能,系统的瞬时负载过高,或者一些不规范的码流也容易造成一种难以避免的下溢,这时候就经常会遇到pop音问题了。
以下提供一种在codec层优化pop音的方案,当然这个前提是系统硬件使用了数字功放,且是遵循了linux alsa框架的标准驱动。
在codec 驱动注册codec dai接口时,其中的ops的类型是 struct snd_soc_dai_ops,定义了DAI驱动的操作接口
struct snd_soc_dai_ops {
/*
* DAI clocking configuration, all optional.
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_sysclk)(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
/*
* DAI format configuration
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
int (*xlate_tdm_slot_mask)(unsigned int slots,
unsigned int *tx_mask, unsigned int *rx_mask);
int (*set_tdm_slot)(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width);
int (*set_channel_map)(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot);
int (*get_channel_map)(struct snd_soc_dai *dai,
unsigned int *tx_num, unsigned int *tx_slot,
unsigned int *rx_num, unsigned int *rx_slot);
int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
int (*set_sdw_stream)(struct snd_soc_dai *dai,
void *stream, int direction);
/*
* DAI digital mute - optional.
* Called by soc-core to minimise any pops.
*/
int (*digital_mute)(struct snd_soc_dai *dai, int mute);
int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);
/*
* ALSA PCM audio operations - all optional.
* Called by soc-core during audio PCM operations.
*/
int (*startup)(struct snd_pcm_substream *,
struct snd_soc_dai *);
void (*shutdown)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*hw_params)(struct snd_pcm_substream *,
struct snd_pcm_hw_params *, struct snd_soc_dai *);
int (*hw_free)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*prepare)(struct snd_pcm_substream *,
struct snd_soc_dai *);
/*
* NOTE: Commands passed to the trigger function are not necessarily
* compatible with the current state of the dai. For example this
* sequence of commands is possible: START STOP STOP.
* So do not unconditionally use refcounting functions in the trigger
* function, e.g. clk_enable/disable.
*/
int (*trigger)(struct snd_pcm_substream *, int,
struct snd_soc_dai *);
int (*bespoke_trigger)(struct snd_pcm_substream *, int,
struct snd_soc_dai *);
/*
* For hardware based FIFO caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
struct snd_soc_dai *);
};
这些ops接口中的trigger会由alsa core回调
int (*trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *);
音频下溢 alsa underun的时候alsa core会回调trigger 触发stop和start的开关动作,我们可以在这里
添加mute、unmute codec的动作以避免下溢的pop音,以tas5805的驱动为例:
static const struct snd_soc_dai_ops tas5805m_dai_ops = {
.trigger = tas5805m_trigger,
};
static int tas5805m_mute(struct snd_soc_component *component, bool mute)
{
//struct tas5805m_priv *tas5805m = snd_soc_component_get_drvdata(component);
u8 reg03_value = 0;
u8 reg35_value = 0;
if (mute) {
//mute both left & right channels
reg03_value = 0x0b;
reg35_value = 0x00;
} else {
//unmute
reg03_value = 0x03;
reg35_value = 0x11;
}
snd_soc_component_write(component, TAS5805M_REG_00, TAS5805M_PAGE_00);
snd_soc_component_write(component, TAS5805M_REG_7F, TAS5805M_BOOK_00);
snd_soc_component_write(component, TAS5805M_REG_00, TAS5805M_PAGE_00);
snd_soc_component_write(component, TAS5805M_REG_03, reg03_value);
snd_soc_component_write(component, TAS5805M_REG_35, reg35_value);
//tas5805m->mute = mute;
return 0;
}
static int tas5805m_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *codec_dai)
{
struct tas5805m_priv *tas5805m = snd_soc_dai_get_drvdata(codec_dai);
struct snd_soc_component *component = tas5805m->component;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (!tas5805m->mute)
tas5805m_mute(component, 0);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (!tas5805m->mute)
tas5805m_mute(component, 1);
break;
}
}
return 0;
}
其他的数字功放驱动都可以使用这种方式来进行优化。
如果系统使用的模拟功放那么也可以在soc platform层cpu dai以及采用同样的方式,在trigger回调中添加platform提供的mute输出的接口。
注意的是这里的操作是原子的,不能有耗时的操作。