赞
踩
问题:我司有个项目,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层的调用函数代码:
- static int get_next_buffer(struct resampler_buffer_provider *buffer_provider,
- struct resampler_buffer* buffer)
- {
- struct stream_in *in;
- size_t i,size;
-
- if (buffer_provider == NULL || buffer == NULL)
- return -EINVAL;
-
- in = (struct stream_in *)((char *)buffer_provider -
- offsetof(struct stream_in, buf_provider));
-
- if (in->pcm == NULL) {
- buffer->raw = NULL;
- buffer->frame_count = 0;
- in->read_status = -ENODEV;
- return -ENODEV;
- }
-
- if (in->frames_in == 0) {
- size = pcm_frames_to_bytes(in->pcm,pcm_get_buffer_size(in->pcm)
- in->read_status = pcm_read(in->pcm,//这里就调用了pcm_read,来起一个过度作用,其实是使用了tinyalsa,来获取原始pcm数据,(void*)in->buffer,中装入了PCM 原始数据。
- (void*)in->buffer,pcm_frames_to_bytes(in->pcm, in->config->period_size));
- if (in->read_status != 0) {
- ALOGE("get_next_buffer() pcm_read error %d", in->read_status);
- buffer->raw = NULL;
- buffer->frame_count = 0;
- return in->read_status;
- }
-
- //fwrite(in->buffer,pcm_frames_to_bytes(in->pcm,pcm_get_buffer_size(in->pcm)),1,in_debug);
- in->frames_in = in->config->period_size;
-
- /* Do stereo to mono conversion in place by discarding right channel */
- if ((in->channel_mask == AUDIO_CHANNEL_IN_MONO)
- &&(in->config->channels == 2)) {
- //ALOGE("channel_mask = AUDIO_CHANNEL_IN_MONO");
- for (i = 0; i < in->frames_in; i++)
- in->buffer[i] = in->buffer[i * 2];
- }
- }
-
- //ALOGV("pcm_frames_to_bytes(in->pcm,pcm_get_buffer_size(in->pcm)):%d",size);
- buffer->frame_count = (buffer->frame_count > in->frames_in) ?
- in->frames_in : buffer->frame_count;
- buffer->i16 = in->buffer +
- (in->config->period_size - in->frames_in) *
- audio_channel_count_from_in_mask(in->channel_mask);
-
- return in->read_status;
-
- }
再来看一下tinyalsa,中的代码,我这里主要给的是输入流的相关代码,即录音时,而播放流与其相反。
- int pcm_read(struct pcm *pcm, void *data, unsigned int count)
- {
- struct snd_xferi x;
-
- if (!(pcm->flags & PCM_IN))
- return -EINVAL;
-
- x.buf = data;
- x.frames = count / (pcm->config.channels *
- pcm_format_to_bits(pcm->config.format) / 8);
-
- for (;;) {
- if (!pcm->running) {
- if (pcm_start(pcm) < 0) {
- fprintf(stderr, "start error");
- return -errno;
- }
- }
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {//用户层通过ioctl的方式来调用kernel,//读出入初始化数据,即录音。
- pcm->prepared = 0;
- pcm->running = 0;
- if (errno == EPIPE) {
- /* we failed to make our window -- try to restart */
- pcm->underruns++;
- continue;
- }
- return oops(pcm, errno, "cannot read stream data");
- }
-
- if (pcm->config.channels == 2) {
- if (channalFlags == -1 ) {
- if (startCheckCount < SAMPLECOUNT) {
- startCheckCount += count;
- } else {
- channalFlags = channel_check(data,count / 2);
- }
- }
-
- channel_fixed(data,count / 2, channalFlags);
- }
-
- return 0;
- }
- }
kernel层的实现,在内核中发起系统调用,执行本应用户空间发起的fops函数集。
kernel 内核版本,4.19.172
- PCM逻辑设备文件操作函数集:snd_pcm_f_ops[]
- PCM逻辑设备文件操作函数集对于Playback和Capture是分开定义的,该操作函数集如下:
-
- const struct file_operations snd_pcm_f_ops[2] = {
- {
- .owner = THIS_MODULE,
- .write = snd_pcm_write,//用于单通道音频信号写
- .write_iter = snd_pcm_writev,
- .open = snd_pcm_playback_open,
- .release = snd_pcm_release,
- .llseek = no_llseek,
- .poll = snd_pcm_poll,
- .unlocked_ioctl = snd_pcm_ioctl,//用于多通道音频信号写
- .compat_ioctl = snd_pcm_ioctl_compat,
- .mmap = snd_pcm_mmap,
- .fasync = snd_pcm_fasync,
- .get_unmapped_area = snd_pcm_get_unmapped_area,
- },
- {
- .owner = THIS_MODULE,
- .read = snd_pcm_read,//用于单通道音频信号读
- .read_iter = snd_pcm_readv,
- .open = snd_pcm_capture_open,
- .release = snd_pcm_release,
- .llseek = no_llseek,
- .poll = snd_pcm_poll,
- .unlocked_ioctl = snd_pcm_ioctl,//用于多通道音频信号读
- .compat_ioctl = snd_pcm_ioctl_compat,
- .mmap = snd_pcm_mmap,
- .fasync = snd_pcm_fasync,
- .get_unmapped_area = snd_pcm_get_unmapped_area,
- }
- };
先来看一下我们es7202 的硬件电路图,以及框架图与相关的调节增益的相关寄存器。
es7202 内部框架图:
es7202外围电路,相对较少:
es7202内部调制MIC 增益的寄存器:
所以从整体来说,使用数字MIC来讲,整体的优势还是比较明显的,外围电路比较少,录入的MIC数据同CLK 的采集混入PDM DATA 通过PDM数据接口直接送入CPU内部,进行处理。
下面给到修改的patch,目录在external/tinyalsa/pcm.c
- diff --git a/pcm.c b/pcm.c
- index 0b97bbc..01d777e 100644
- --- a/pcm.c
- +++ b/pcm.c
- @@ -635,13 +635,36 @@ void channel_fixed(void *data, unsigned len, int chFlag)
- return;
- }
-
- +static short out_vol = 4.0;//扩大音量倍数
- +static void volume_process(const void *buffer, size_t length, short volume) {
- +
- +short * buffer_end = (short*)buffer + length/2;
- + short * pcmData = (short *)buffer;
- + int i = 0;
- + int pcmval;
- +
- + while (pcmData < buffer_end) {
- + pcmval = (short)*pcmData * volume;
- + if (pcmval < 32768 && pcmval > -32768) {
- + *pcmData = pcmval;
- + } else if (pcmval > 32767) {
- + *pcmData = 32767; //限制最大幅度
- + } else if (pcmval < -32767) {
- + *pcmData = -32767;//限制最小幅度
- + }
- +
- + ++pcmData;
- + i++;
- + }
- +}
- +
- int pcm_read(struct pcm *pcm, void *data, unsigned int count)
- {
- struct snd_xferi x;
-
- if (!(pcm->flags & PCM_IN))
- return -EINVAL;
- -
- + memset(data, 0, count);
- x.buf = data;
- x.frames = count / (pcm->config.channels *
- pcm_format_to_bits(pcm->config.format) / 8);
- @@ -676,6 +699,7 @@ int pcm_read(struct pcm *pcm, void *data, unsigned int count)
- channel_fixed(data,count / 2, channalFlags);
- }
-
- + volume_process(x.buf, count , out_vol);
- return 0;
- }
- }
理论上来说,该patch也可解决其它codec引起的输入MIC录入音量较小的问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。