ALSA compress devices block with eachother

最近遇到了一个断音的问题,最终查到的原因比较有意义,标记一下。

音频的大致流程如下图,绿色线 是es 流,黄色线是pcm 流。

从user space 分析到断音的原因是,

1)Playback_render device write blocked,write blocked 的原因是opu module buffer full.

按理说,opu buffer full ,不会导致断音才对,关键是playback_render device 写进来的content 已经被加了mute data,此mute data 正是听起来断音的原因。

哪里加了mute data 呢?

2)dump audio hal 的data ,有mute data ,但是 dump capture device data 没有mute data,再加上AudioFlinger 的相关日志,证实上述mute data 是AudioFlinger 加的。

AudioFlinger 为何会加mute data 呢?

3)AudioFlinger 有个thread ,检测到20ms 没有data 进来,就会20ms 的mute data.

根据这个信息,推测capture device 拿到的pcm data 不够及时。

4)capture and playback device 写data 的地方确认日志,证实是playback device 写es 进来的时机比较晚,导致capture device 输出pcm data 不够及时。

es 晚,接下来有2个方向, apk 写es 晚?playback device 写的晚?

5)通过User space日志发现疑点,playback device  明明call 了write function ,但是write function 耗时过长。

6)  在driver 中,同时打开playback device/capture device/ playback_render device 的write function (实际是.copy 实现function),通过观察发现,每次playback write 调用间隔时间长,都伴随着playback_render device 的write block,因此有个疑问,难道因为playback_render device write 被Block ,所以User Space 无法真正调用到 playback device write function(实际User Space 已经call write)?

 7) 在这个疑点下,就要去check kernel code 是怎么实现?

通过查询kernel code ,发现还真如猜测,对于同一张card ,此card 上的devices 共用一个mutex ,因此,当某个 device block 时,其他devices 拿不到mutex ,因此,无法真正完成调用。

这也正好印证了我们看到的异常:User Space 明明已经调用了write ,但是driver 看到的是间隔较长时间才被调用,User Space 看到的是,较长时间才write 返回。

 贴上kernel code:

soc-compress.c - sound/soc/soc-compress.c - Linux source code (v5.4) - Bootlin
static int soc_compr_copy(struct snd_compr_stream *cstream,
			  char __user *buf, size_t count)
{
	struct snd_soc_pcm_runtime *rtd = cstream->private_data;
	struct snd_soc_component *component;
	struct snd_soc_rtdcom_list *rtdcom;
	int ret = 0;

	mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);

	for_each_rtdcom(rtd, rtdcom) {
		component = rtdcom->component;

		if (!component->driver->compr_ops ||
		    !component->driver->compr_ops->copy)
			continue;

		ret = component->driver->compr_ops->copy(cstream, buf, count);
		break;
	}

	mutex_unlock(&rtd->card->pcm_mutex);
	return ret;
}

在snd_soc_register_card 的时候,初始化了pcm_mutex

/**
 * snd_soc_register_card - Register a card with the ASoC core
 *
 * @card: Card to register
 *
 */
int snd_soc_register_card(struct snd_soc_card *card)
{
    if (!card->name || !card->dev)
        return -EINVAL;

    dev_set_drvdata(card->dev, card);

    INIT_LIST_HEAD(&card->widgets);
    INIT_LIST_HEAD(&card->paths);
    INIT_LIST_HEAD(&card->dapm_list);
    INIT_LIST_HEAD(&card->aux_comp_list);
    INIT_LIST_HEAD(&card->component_dev_list);
    INIT_LIST_HEAD(&card->list);
    INIT_LIST_HEAD(&card->dai_link_list);
    INIT_LIST_HEAD(&card->rtd_list);
    INIT_LIST_HEAD(&card->dapm_dirty);
    INIT_LIST_HEAD(&card->dobj_list);

    card->num_rtd = 0;
    card->instantiated = 0;
    mutex_init(&card->mutex);
    mutex_init(&card->dapm_mutex);
    mutex_init(&card->pcm_mutex);
    spin_lock_init(&card->dpcm_lock);

    return snd_soc_bind_card(card);
}

备注:同一张card 的不同device 公用一个mutex 是在kernel 5.4 才有的特性。

5.4 之前的实现为:

soc-compress.c - sound/soc/soc-compress.c - Linux source code (v5.3.18) - Bootlin

static int soc_compr_copy(struct snd_compr_stream *cstream,
              char __user *buf, size_t count)
{
    struct snd_soc_pcm_runtime *rtd = cstream->private_data;
    struct snd_soc_component *component;
    struct snd_soc_rtdcom_list *rtdcom;
    int ret = 0;

    mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);

    for_each_rtdcom(rtd, rtdcom) {
        component = rtdcom->component;

        if (!component->driver->compr_ops ||
            !component->driver->compr_ops->copy)
            continue;

        ret = component->driver->compr_ops->copy(cstream, buf, count);
        break;
    }

    mutex_unlock(&rtd->pcm_mutex);
    return ret;
}

https://elixir.bootlin.com/linux/v5.3.18/source/sound/soc/soc-core.c#L1398
static int soc_post_component_init(struct snd_soc_pcm_runtime *rtd,
    const char *name)
{
    int ret = 0;

    /* register the rtd device */
    rtd->dev = kzalloc(sizeof(struct device), GFP_KERNEL);
    if (!rtd->dev)
        return -ENOMEM;
    device_initialize(rtd->dev);
    rtd->dev->parent = rtd->card->dev;
    rtd->dev->release = rtd_release;
    rtd->dev->groups = soc_dev_attr_groups;
    dev_set_name(rtd->dev, "%s", name);
    dev_set_drvdata(rtd->dev, rtd);
    mutex_init(&rtd->pcm_mutex);
    INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients);
    INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients);
    INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].fe_clients);
    INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].fe_clients);
    ret = device_add(rtd->dev);
    if (ret < 0) {
        /* calling put_device() here to free the rtd->dev */
        put_device(rtd->dev);
        dev_err(rtd->card->dev,
            "ASoC: failed to register runtime device: %d\n", ret);
        return ret;
    }
    rtd->dev_registered = 1;
    return 0;
}

8)基于以上分析,最终solution 是,在hal 给playback_render device 写data 时,加入了限流的机制,也就是当检测到opu buffer 已经有足够的pcm data 时,就先不写data 给playback_render device,这样减少playback_render device write block 对playback 的影响。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值