RK3328 Android 7.1 录音左右声道分离的情况下,有时候会出现,右声道的声音和左声道一样的问题

RK3328 Android 7.1 录音左右声道分离的情况下,有时候会出现,右声道的声音和左声道一样的问题

问题现象:
产品有语音识别功能,需要回音消除,所以立体声录音需要左右声道分离,左声道为主MIC,右声道为回音消除MIC,产品偶现语音无法识别问题,查看出问题时候的PCM数据,左右声道数据一样,并且都是左声道数据,导致回音消除之后,软件以为没有说话!

RK3328 Android 7.1平台,默认左右声道是没有分离的,左右声道叠加在一起,需要注释掉宏”#define SPEEX_DENOISE_ENABLE”,在文件”hardware/rockchip/audio/tinyalsa_hal/audio_hw.h”里,来使能左右声道分离的功能!

为了排除APK和上层系统的原因,当问题出现的时候,使用”tinycap”命令在shell下抓取PCM数据,发现确实是左右声道一样,那么肯定和APK或者framework没有关系。

rk3328_box:/storage
Capturing sample: 2 ch, 44100 hz, 16 bit
Captured 483328 frames

为了排除codec传入的数据问题,当问题出现的时候,直接测量codec的I2S输出信号,发现左右声道数据是不一样的,所以,也不是codec硬件或者驱动的问题。

RK3328 Android 7.1 录音左右声道分离的情况下,有时候会出现,右声道的声音和左声道一样的问题
因为使用”tinycap”命令抓取数据也有问题,并且”tinycap”命令已经是非常底层的操作了,所以从”tinycap”命令入手分析,”tinycap”命令源码目录:external/tinyalsa/tinycap.c
主要调用关系如下:main->capture_sample->pcm_read
int main(int argc, char **argv)
{
    ......

    signal(SIGINT, sigint_handler);
    frames = capture_sample(file, card, device, header.num_channels,
                            header.sample_rate, format,
                            period_size, period_count);
    printf("Captured %d frames\n", frames);

    ......

    return 0;
}

unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device,
                            unsigned int channels, unsigned int rate,
                            enum pcm_format format, unsigned int period_size,
                            unsigned int period_count)
{
    ......

    pcm = pcm_open(card, device, PCM_IN, &config);

    ......

    printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,
           pcm_format_to_bits(format));

    while (capturing && !pcm_read(pcm, buffer, size)) {
        ......

    }

    ......

}

使用”pcm_open”函数打开驱动/dev/snd/下面的录音节点,然后用”pcm_read”函数录到数据,再写进文件里面。

“pcm_open”函数和”pcm_read”函数源码目录:external/tinyalsa/pcm.c
其中”pcm_read”函数如下:

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)) {
            pcm->prepared = 0;
            pcm->running = 0;
            if (errno == EPIPE) {

                pcm->underruns++;
                continue;
            }
            return oops(pcm, errno, "cannot read stream data");
        }

        if(!(pcm->config.channels == 1))
        {

            if(channalFlags == -1 )
            {

                if(startCheckCount < SAMPLECOUNT)
                {
                    startCheckCount += count;
                }

                else
                {

                    channalFlags = channel_check(data,count/2);
                }
            }

            channel_fixed(data,count/2, channalFlags);
        }

        return 0;
    }
}

当看到”pcm_read”函数时,发现了一段代码比较迷惑,就是上面等号分割的部分,几个变量初始值如下:

#define SAMPLECOUNT 441*5*2*2
int channalFlags = -1;
int startCheckCount = 0;

再看上面用到的两个函数”channel_check”和”channel_fixed”:

int channel_check(void * data, unsigned len)
{
    short * pcmLeftChannel = (short *)data;
    short * pcmRightChannel = pcmLeftChannel+1;
    unsigned index = 0;
    int leftValid = 0x0;
    int rightValid = 0x0;
    short checkValue = 0;

    checkValue = *pcmLeftChannel;

    for(index = 0; index < len; index += 2)
    {
        if((pcmLeftChannel[index] >= checkValue+50)||(pcmLeftChannel[index]  checkValue-50))
        {
            leftValid++;
        }
    }

    if(leftValid >20)
        leftValid = 0x01;
    else
        leftValid = 0;

    checkValue = *pcmRightChannel;

    for(index = 0; index < len; index += 2)
    {
        if((pcmRightChannel[index] >= checkValue+50)||(pcmRightChannel[index]  checkValue-50))
        {
            rightValid++;
        }
    }

    if(rightValid >20)
        rightValid = 0x02;
    else
        rightValid = 0;

    return leftValid|rightValid;
}

void channel_fixed(void * data, unsigned len, int chFlag)
{

    if(chFlag  0 || chFlag > 2 )
        return;

    short * pcmValid = (short *)data;
    short * pcmInvalid = pcmValid;

    if(chFlag == 1)
        pcmInvalid += 1;
    else if (chFlag == 2)
        pcmValid += 1;

    unsigned index ;

    for(index = 0; index < len; index += 2)
    {
        pcmInvalid[index] = pcmValid[index];
    }
    return;
}

分析到这里就知道了,那段比较迷惑的代码,是在开始录音的时候,判断每个通道的声音是否有效,判断完成之后,某一个声道无效的话,用另一个有效声道的数据覆盖!

结合实际现象,我们测试的时候,右声道并没有接硬件设备,并且录出来的错误声音数据,都是左声道有接设备的数据,所以,推测就是这里导致的这个问题的产生,为了确定是这里问题,加log打印,先打印一下从驱动读上来的buffer的左右声道前3个数据,再打印一下,”tinycap”命令写入文件时候的每次buffer的左右声道前3个数据,log如下:

pcm_read--> pcmLeftChannel=[-29],[-21],[23]; pcmRightChannel=[2],[-1],[-24];
capture_sample--> pcmLeftChannel=[-29],[-21],[23]; pcmRightChannel=[-29],[-21],[23];

可以看到,确实是右声道的数据被左声道覆盖了,然后屏蔽掉那段比较迷惑的代码,再打印log如下:

pcm_read--> pcmLeftChannel=[149],[87],[79]; pcmRightChannel=[-7],[-2],[-14];
capture_sample--> pcmLeftChannel=[149],[87],[79]; pcmRightChannel=[-7],[-2],[-14];

这下右声道就不会被覆盖了!
实际软件功能验证也OK,没有出现语音无法识别的问题了!

Original: https://blog.csdn.net/qq_21059825/article/details/112901386
Author: 杨涂涂
Title: RK3328 Android 7.1 录音左右声道分离的情况下,有时候会出现,右声道的声音和左声道一样的问题

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/515876/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球