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