前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[ 音频篇 ] 29 - 调试智能音箱中音频通路的回采(Ref信号)

[ 音频篇 ] 29 - 调试智能音箱中音频通路的回采(Ref信号)

作者头像
程序手艺人
发布2020-11-03 10:35:17
3K0
发布2020-11-03 10:35:17
举报
文章被收录于专栏:程序手艺人程序手艺人

项目场景:

在这里插入图片描述
在这里插入图片描述

项目基于BCM6755平台为基础,通过一系列的语音算法完成实现语音交互场景。这次遇到的问题主要是AEC效果差,如上图所示,设备播放音乐的场景,会出现唤醒困难的想象。实际的抓取录音数据发现录音和回采之间的数据延迟高达100ms,远远超过算法要求<30ms的要求。接下来需要定位延迟的问题。


问题描述:

在这里插入图片描述
在这里插入图片描述

通道1 为录音数据,通道2为回采数据,发现回采数据比录音数据还延后20ms,理论上回采数据应该比录音数据提前才对的。 最初以为可能是重采样延迟造成的,但实际验证结果不是的。


原因分析:

通过Audacity 生成1k的信号,前1s为空数据,后3s秒为1k信号,方便对比延迟。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

通过NXP i.MX8M Mini 平台获取录音+回采数据如下:

在这里插入图片描述
在这里插入图片描述

通道1 为录音数据,通过Audacity可以看到开始时间为01s134ms。通道2为回采数据,可以看到开始时间为01s133ms。中间的延迟时间为1ms。满足AEC算法要求的时间。这样的录音数据是通过 ALSA multi plugs 1完成通道的合并。插件配置如下:

代码语言:javascript
复制
// multi 插件完成两个声卡数据的叠加合并,相当于plughw:3,0 是6ch的音频数据,而plughw:1,0是2ch的音频数据,而通过arecord -Dsub_input 是8ch的音频数据。而bindings参数可以控制通道数据的调整。
pcm.sub_input {
    type multi
    slaves.a.pcm "plughw:3,0"
    slaves.a.channels 6
    slaves.b.pcm "plughw:1,0"
    slaves.b.channels 2
    bindings.0.slave a
    bindings.0.channel 0
    bindings.1.slave a
    bindings.1.channel 1
    bindings.2.slave a
    bindings.2.channel 2
    bindings.3.slave a
    bindings.3.channel 3
    bindings.4.slave a
    bindings.4.channel 4
    bindings.5.slave a
    bindings.5.channel 5
    bindings.6.slave b
    bindings.6.channel 0
    bindings.7.slave b
    bindings.7.channel 1
}

通过Broadcom BCM6755 平台获取录音+回采数据如下:

在这里插入图片描述
在这里插入图片描述

通道1 为录音数据,通道2为回采数据,发现回采数据比录音数据还延后20ms,理论上回采数据应该比录音数据提前才对的。 最初以为可能是重采样延迟造成的,但实际验证结果不是的。

实际的测试数据是通过VoiceProcess 进程导出的数据,而不是通过apaly xxx.wav | arecord xxx.wav 这种方式或gst-play-1.0 xxx.wav | arecord xxx.wav 因为发现这样测试在BCM6755 平台上有一些问题。

代码语言:javascript
复制
# arecord -l 
card 1: MapleTree [MapleTree], device 0: Playback ad82584f-0 []
  Subdevices: 0/1
  Subdevice #0: subdevice #0
card 1: MapleTree [MapleTree], device 1: Capture ES7210 4CH ADC 0-1 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: Dummy [Dummy], device 0: DSP PCM [DSP PCM]
  Subdevices: 0/1
  Subdevice #0: subdevice #0

# aplay -l 
card 1: MapleTree [MapleTree], device 0: Playback ad82584f-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
# 设置系统音量
amixer -c 1 set Master 180 
# 一边播放一边录音
aplay -Dplughw:1,0  1ksine-48k-4s.wav | 
arecord  -D hw:1,0 -c 2 -r 48000 -f S32_LE -d 4 ref_48k_S32.wav

接下来是修改代码后的测试结果,可以发现回采数据比录音数据提前10ms,这样的数据就是比较正常的。

因为回采数据是走功放芯片ad82584f内部通路。而录音数据是通过麦克风采回来的。自然回采数据要快很多。

在这里插入图片描述
在这里插入图片描述

接下来看下代码的改动,主要还是ALSA API 初始化的参数配置2

代码语言:javascript
复制
snd_pcm_t *Open(std::string name, uint32_t rate, uint8_t channels,snd_pcm_format_t format,snd_pcm_stream_t streamType){
    uint32_t pcm;
    //snd_pcm_format_t format_;
    //uint8_t channels_;
    snd_pcm_t * handle_;
    snd_pcm_hw_params_t * hwParams_;
    snd_pcm_sw_params_t * swParams_;
    //snd_pcm_uframes_t buffer_size_ = BUFFER_SIZE;
    if( (pcm = snd_pcm_open(&handle_, name.c_str(), streamType, 0)) < 0)
    {
        std::cout << "ERROR! Cannot open " << name << std::endl;
        return nullptr;
    }
    snd_pcm_hw_params_alloca(&hwParams_);
    snd_pcm_sw_params_alloca(&swParams_);
    snd_pcm_sw_params_current(handle_, swParams_);
    snd_pcm_hw_params_any(handle_, hwParams_);

    if( (pcm = snd_pcm_hw_params_set_access(handle_, hwParams_, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
    {
        std::cout << "ERROR! Cannot set interleaved mode" << std::endl;
        return nullptr;
    }

    //format_ = format;
    if( (pcm = snd_pcm_hw_params_set_format(handle_, hwParams_, format)) < 0)
    {
        std::cout << "ERROR! Cannot set format" << std::endl;
        return nullptr;
    }

    //channels_ = channels;
    if( (pcm = snd_pcm_hw_params_set_channels(handle_, hwParams_, channels)) < 0)
    {
        std::cout << "ERROR! Cannot set number of channels" << std::endl;
        return nullptr;
    }

    if( (pcm = snd_pcm_hw_params_set_rate_near(handle_, hwParams_, &rate , 0)) < 0)
    {
        std::cout << "ERROR! Cannot set format" << std::endl;
        return nullptr;
    }

    /*  // 延迟的主要原因
    if( (pcm = snd_pcm_hw_params_set_buffer_size_near( handle_,hwParams_, &buffer_size_ )) < 0)
    {
        std::cout << "ERROR! Cannot set buffer" << std::endl;
        return nullptr;
    }
    */
    int dir;
    snd_pcm_uframes_t frames = (SAMPLE_RATE * PERIOD_TIME_MS)/ MS_IN_ONE_SECOND;
    snd_pcm_hw_params_set_period_size_near(handle_, hwParams_, &frames, &dir);
    if( (pcm  = snd_pcm_hw_params(handle_, hwParams_)) < 0)
    {
        std::cout << "ERROR! Cannot set hw parameters" << std::endl;
        return nullptr;
    }
    //frame_size_  = (format_ == SND_PCM_FORMAT_S32_LE ? 4 : 2) * channels_;
    return handle_;
}

进一步分析函数的原形:

代码语言:javascript
复制
# alsa-lib-1.1.3/src/pcm/pcm.c
5652 /**
5653  * \brief Restrict a configuration space to have buffer size nearest to a target
5654  * \param pcm PCM handle
5655  * \param params Configuration space
5656  * \param val approximate target buffer size in frames / returned chosen approximate target buffer size in frames
5657  * \return 0 otherwise a negative error code if configuration space is empty
5658  */
5659 #ifndef DOXYGEN
5660 int INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
5661 #else
5662 int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
5663 #endif
5664 {
5665 >---unsigned int _val = *val;
5666 >---int err = snd_pcm_hw_param_set_near(pcm, params, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
5667 >---if (err >= 0)
5668 >--->---*val = _val;
5669 >---return err;
5670 }

0926 早上老化发现,长时间过后回采的数据还是比录音慢,对比数据看有35ms。

在这里插入图片描述
在这里插入图片描述

录音数据

回采数据

延迟

13.243s

12.277s

34ms

16.224s

16.259s

35ms

17.480.s

17.515ms

35ms

23.108s

23.137s

29ms

问题的根本原因隐藏的越来越深了,继续寻找问题的根本原因。

提示:这里填写问题的分析: 例如:Handler 发送消息有两种方式,分别是 Handler.obtainMessage()和 Handler.sendMessage(),其中 obtainMessage 方式当数据量过大时,由于 MessageQuene 大小也有限,所以当 message 处理不及时时,会造成先传的数据被覆盖,进而导致数据丢失。


解决方案:

提示:这里填写该问题的具体解决方案: 例如:新建一个 Message 对象,并将读取到的数据存入 Message,然后 mHandler.obtainMessage(READ_DATA, bytes, -1, buffer).sendToTarget();换成 mHandler.sendMessage()。

ALSA POLL 实现

Reading Microphone Data by Polling using ALSA [or V4L2]

check-alsa-poll.c

参考

Alsa音频编程【精华】 ALSA PCM Timestamping Audio Synchronization ALSA - PCM接口

Linux ALSA 音频系统:逻辑设备篇


  1. pcm_plugins ↩︎
  2. FramesPeriods ↩︎
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-10-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 项目场景:
  • 问题描述:
  • 原因分析:
  • 解决方案:
  • ALSA POLL 实现
  • 参考
相关产品与服务
前端性能监控
前端性能监控(Real User Monitoring,RUM)是一站式前端监控解决方案,专注于 Web、小程序等场景监控。前端性能监控聚焦用户页面性能(页面测速,接口测速,CDN 测速等)和质量(JS 错误,Ajax 错误等),并且联动腾讯云应用性能监控实现前后端一体化监控。用户只需要安装 SDK 到自己的项目中,通过简单配置化,即可实现对用户页面质量的全方位守护,真正做到低成本使用和无侵入监控。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档