当前位置:   article > 正文

数字MIC(es7202 PDM协议)MIC录音声音较小

pdm协议

问题:我司有个项目,android 11 rk3566 的项目,该项目带audio 模块,项目MIC 使用的es7202(ADC),该芯片是一个编码芯片,没有解码功能,该模块的录音的增益已经调到最大,但录入的MIC音量还是很小,硬件也没有解决的办法,该数字MIC 利用的是PDM 协议,而PDM数字信号较为复杂。PDM信号在我的其它文章中有介绍。

rk3566 android11 配置声卡(es7202 ADC)_android不会飞的博客-CSDN博客_android 声卡

分析:MIC给到CPU为ES7202输出数字MIC信号,请确认CPU内对数字MIC信号的放大处理是否有?理论上是有的,CPU PDM 数据信号默认为可接受低幅值输入,而PDM 信号在CPU 中的处理是相对复杂的。基于PDM接口的应用降低了发送设备的复杂性,由于作为接收设备的CODEC内部集成抽取滤波器,因此系统整体复杂度大大降低。而PDM则使用远高于PCM采样率的时钟采样调制模拟分量,只有1位输出,要么为0,要么为1。因此通过PDM方式表示的数字音频也被称为Oversampled 1-bit Audio。相比PDM一连串的0和1,PCM的量化结果更为直观简单。以PDM方式作为模数转换的接收端,需要用到抽取滤波器(Decimation Filter),将密密麻麻的0和1代表的密度分量转换为幅值分量,而PCM方式得到的已经是幅值分量了。

解决方向:由于我们的es7202 MIC 增益已经调增到最大,但还是没有好的效果,无法实现增大MIC录音的效果。直接去处理PDM数据信号,由于 DATA MIC录入的数据,而PDM的数据复杂度较高我们不好去处理相关的数据,所以我们采用第三种,就是在tinyalsa 中处理,上层android应用在调用底层驱动时,是同过HAL层来调用底层驱动,通过驱动硬件工作,而在HAL层中的处理会调用,由于我们使用的是audio ALSA 架构,上层调用驱动会经过相关的pcm处理函数,相信使用过tinycap ,tinyplay 可能比较了解,如果不太了解的话,可以去看一下相关的源码,在我们调试过程中时,用的比较多相关命令,继续分析...... 而我们音频采样,PCM流是什么样的呢,如果是单声道,那每个采样的数据,这里我们就把数据看成一个个整型数据,它是一连串的,每个整数占据2个字节(16-bit),9个采样也就是18字节的数据。每个采样的整数大小最小为 -32768,最大为 32768(注意:这里的数据类型是short 类型的) 。根据采样数据的位置和值画一个图的话,就会得到像播放器上那样的波浪形图。而立体声,其实就是双通道采样的,其数据也看成一串数据的话,它其实就是左右声道的数据交叉存放,每一个frame是一个16-bit的采样点。所以我们可以在alsa调用底层驱动时,获取到的PCM原始数据进行处理操作,而我们所要做的操作就是将它放大。而这就解决了我们的问题,其原理就是将MIC 录入的PCM原始数据看成一个正弦波,而我们要做的就是将该正弦波的振幅放大,这就相当于增加了PCM数据的音量了,只需要将每一个采样的数据乘以一个系数就行了。

首先我们先来看一下HAL层的调用函数代码:

  1. static int get_next_buffer(struct resampler_buffer_provider *buffer_provider,
  2. struct resampler_buffer* buffer)
  3. {
  4. struct stream_in *in;
  5. size_t i,size;
  6. if (buffer_provider == NULL || buffer == NULL)
  7. return -EINVAL;
  8. in = (struct stream_in *)((char *)buffer_provider -
  9. offsetof(struct stream_in, buf_provider));
  10. if (in->pcm == NULL) {
  11. buffer->raw = NULL;
  12. buffer->frame_count = 0;
  13. in->read_status = -ENODEV;
  14. return -ENODEV;
  15. }
  16. if (in->frames_in == 0) {
  17. size = pcm_frames_to_bytes(in->pcm,pcm_get_buffer_size(in->pcm)
  18. in->read_status = pcm_read(in->pcm,//这里就调用了pcm_read,来起一个过度作用,其实是使用了tinyalsa,来获取原始pcm数据,(void*)in->buffer,中装入了PCM 原始数据。
  19. (void*)in->buffer,pcm_frames_to_bytes(in->pcm, in->config->period_size));
  20. if (in->read_status != 0) {
  21. ALOGE("get_next_buffer() pcm_read error %d", in->read_status);
  22. buffer->raw = NULL;
  23. buffer->frame_count = 0;
  24. return in->read_status;
  25. }
  26. //fwrite(in->buffer,pcm_frames_to_bytes(in->pcm,pcm_get_buffer_size(in->pcm)),1,in_debug);
  27. in->frames_in = in->config->period_size;
  28. /* Do stereo to mono conversion in place by discarding right channel */
  29. if ((in->channel_mask == AUDIO_CHANNEL_IN_MONO)
  30. &&(in->config->channels == 2)) {
  31. //ALOGE("channel_mask = AUDIO_CHANNEL_IN_MONO");
  32. for (i = 0; i < in->frames_in; i++)
  33. in->buffer[i] = in->buffer[i * 2];
  34. }
  35. }
  36. //ALOGV("pcm_frames_to_bytes(in->pcm,pcm_get_buffer_size(in->pcm)):%d",size);
  37. buffer->frame_count = (buffer->frame_count > in->frames_in) ?
  38. in->frames_in : buffer->frame_count;
  39. buffer->i16 = in->buffer +
  40. (in->config->period_size - in->frames_in) *
  41. audio_channel_count_from_in_mask(in->channel_mask);
  42. return in->read_status;
  43. }

再来看一下tinyalsa,中的代码,我这里主要给的是输入流的相关代码,即录音时,而播放流与其相反。

  1. int pcm_read(struct pcm *pcm, void *data, unsigned int count)
  2. {
  3. struct snd_xferi x;
  4. if (!(pcm->flags & PCM_IN))
  5. return -EINVAL;
  6. x.buf = data;
  7. x.frames = count / (pcm->config.channels *
  8. pcm_format_to_bits(pcm->config.format) / 8);
  9. for (;;) {
  10. if (!pcm->running) {
  11. if (pcm_start(pcm) < 0) {
  12. fprintf(stderr, "start error");
  13. return -errno;
  14. }
  15. }
  16. if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {//用户层通过ioctl的方式来调用kernel,//读出入初始化数据,即录音。
  17. pcm->prepared = 0;
  18. pcm->running = 0;
  19. if (errno == EPIPE) {
  20. /* we failed to make our window -- try to restart */
  21. pcm->underruns++;
  22. continue;
  23. }
  24. return oops(pcm, errno, "cannot read stream data");
  25. }
  26. if (pcm->config.channels == 2) {
  27. if (channalFlags == -1 ) {
  28. if (startCheckCount < SAMPLECOUNT) {
  29. startCheckCount += count;
  30. } else {
  31. channalFlags = channel_check(data,count / 2);
  32. }
  33. }
  34. channel_fixed(data,count / 2, channalFlags);
  35. }
  36. return 0;
  37. }
  38. }

kernel层的实现,在内核中发起系统调用,执行本应用户空间发起的fops函数集。

kernel 内核版本,4.19.172

  1. PCM逻辑设备文件操作函数集:snd_pcm_f_ops[]
  2. PCM逻辑设备文件操作函数集对于Playback和Capture是分开定义的,该操作函数集如下:
  3. const struct file_operations snd_pcm_f_ops[2] = {
  4. {
  5. .owner = THIS_MODULE,
  6. .write = snd_pcm_write,//用于单通道音频信号写
  7. .write_iter = snd_pcm_writev,
  8. .open = snd_pcm_playback_open,
  9. .release = snd_pcm_release,
  10. .llseek = no_llseek,
  11. .poll = snd_pcm_poll,
  12. .unlocked_ioctl = snd_pcm_ioctl,//用于多通道音频信号写
  13. .compat_ioctl = snd_pcm_ioctl_compat,
  14. .mmap = snd_pcm_mmap,
  15. .fasync = snd_pcm_fasync,
  16. .get_unmapped_area = snd_pcm_get_unmapped_area,
  17. },
  18. {
  19. .owner = THIS_MODULE,
  20. .read = snd_pcm_read,//用于单通道音频信号读
  21. .read_iter = snd_pcm_readv,
  22. .open = snd_pcm_capture_open,
  23. .release = snd_pcm_release,
  24. .llseek = no_llseek,
  25. .poll = snd_pcm_poll,
  26. .unlocked_ioctl = snd_pcm_ioctl,//用于多通道音频信号读
  27. .compat_ioctl = snd_pcm_ioctl_compat,
  28. .mmap = snd_pcm_mmap,
  29. .fasync = snd_pcm_fasync,
  30. .get_unmapped_area = snd_pcm_get_unmapped_area,
  31. }
  32. };

 

        先来看一下我们es7202 的硬件电路图,以及框架图与相关的调节增益的相关寄存器。

es7202 内部框架图:

es7202外围电路,相对较少:

 

es7202内部调制MIC 增益的寄存器: 

 

        所以从整体来说,使用数字MIC来讲,整体的优势还是比较明显的,外围电路比较少,录入的MIC数据同CLK 的采集混入PDM DATA 通过PDM数据接口直接送入CPU内部,进行处理。 

下面给到修改的patch,目录在external/tinyalsa/pcm.c

  1. diff --git a/pcm.c b/pcm.c
  2. index 0b97bbc..01d777e 100644
  3. --- a/pcm.c
  4. +++ b/pcm.c
  5. @@ -635,13 +635,36 @@ void channel_fixed(void *data, unsigned len, int chFlag)
  6. return;
  7. }
  8. +static short out_vol = 4.0;//扩大音量倍数
  9. +static void volume_process(const void *buffer, size_t length, short volume) {
  10. +
  11. +short * buffer_end = (short*)buffer + length/2;
  12. + short * pcmData = (short *)buffer;
  13. + int i = 0;
  14. + int pcmval;
  15. +
  16. + while (pcmData < buffer_end) {
  17. + pcmval = (short)*pcmData * volume;
  18. + if (pcmval < 32768 && pcmval > -32768) {
  19. + *pcmData = pcmval;
  20. + } else if (pcmval > 32767) {
  21. + *pcmData = 32767; //限制最大幅度
  22. + } else if (pcmval < -32767) {
  23. + *pcmData = -32767;//限制最小幅度
  24. + }
  25. +
  26. + ++pcmData;
  27. + i++;
  28. + }
  29. +}
  30. +
  31. int pcm_read(struct pcm *pcm, void *data, unsigned int count)
  32. {
  33. struct snd_xferi x;
  34. if (!(pcm->flags & PCM_IN))
  35. return -EINVAL;
  36. -
  37. + memset(data, 0, count);
  38. x.buf = data;
  39. x.frames = count / (pcm->config.channels *
  40. pcm_format_to_bits(pcm->config.format) / 8);
  41. @@ -676,6 +699,7 @@ int pcm_read(struct pcm *pcm, void *data, unsigned int count)
  42. channel_fixed(data,count / 2, channalFlags);
  43. }
  44. + volume_process(x.buf, count , out_vol);
  45. return 0;
  46. }
  47. }

 理论上来说,该patch也可解决其它codec引起的输入MIC录入音量较小的问题。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/82874
推荐阅读
相关标签
  

闽ICP备14008679号