瑞芯微Rockchip开发者社区
直播中

刘敏

7年用户 1161经验值
私信 关注
[问答]

怎样去解决RK3328 Android 7.1录音出现偶现语音无法识别的问题

为什么RK3328 Android 7.1录音会出现偶现语音无法识别的情况呢?
怎样去解决RK3328 Android 7.1录音出现偶现语音无法识别的问题?

回帖(1)

韩禹

2022-3-9 13:37:41
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 #  tinycap ./test.wav -D 0 -d 0 Cc 2 Cr 44100 Cb 16 Cp 1024 Cn 3
Capturing sample: 2 ch, 44100 hz, 16 bit
Captured 483328 frames

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

因为使用“tinycap”命令抓取数据也有问题,并且“tinycap”命令已经是非常底层的操作了,所以从“tinycap”命令入手分析,“tinycap”命令源码目录:external/tinyalsa/tinycap.c
主要调用关系如下:main->capture_sample->pcm_read

int main(int argc, char **argv)
{
        ......

    /* install signal handler and begin capturing */
    signal(SIGINT, sigint_handler);
    frames = capture_sample(file, card, device, header.num_channels,
                            header.sample_rate, format,
                            period_size, period_count);
    printf("Captured %d framesn", 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 bitn", 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;

        //数据buffer的指针
    x.buf = data;
    //上面buffer的大小能放的帧数,对于2通道,16bit数据,帧数为 count / 4
    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;
            }
        }
        //这里从驱动拿到硬件数据,存放在 x.buf 里面
        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
            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判断里面的操作,就有点迷惑,问题就出在这里,下面分析
                //=========================================================
                //因为我们录音是2通道,这个条件会一直成立
        if(!(pcm->config.channels == 1))
        {
                //channalFlags变量初始值为 -1 ,所以,第一次调用,这里会执行一次
            if(channalFlags == -1 )
            {
                    //startCheckCount变量初始值为0,SAMPLECOUNT=441*5*2*2,所以这里,第一次调用也会执行
                if(startCheckCount < SAMPLECOUNT)
                {
                    startCheckCount += count;
                }
                //上面执行一两次之后,startCheckCount不满足上面条件,就会执行这里
                else
                {
                        //在这里改变了channalFlags的值,之后 if(channalFlags == -1 ) 就不会再执行了
                    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;//右声道和左声道交错放置,相差16bit
    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++;// = 0x01;
        }   
    }
        //如果有效数据的个数大于20个,则左声道有效
    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++;//= 0x02;
        }   
    }

    if(rightValid >20)
        rightValid = 0x02;
    else
        rightValid = 0;
        //返回值有4个,0,1,2,3,用来设置变量 channalFlags
        //0:左右声道都无效;1:左声道有效;2:右声道有效;3:左右都有效
    return leftValid|rightValid;
}

void channel_fixed(void * data, unsigned len, int chFlag)
{
        //这里判断的就是变量 channalFlags,只有单个声道有效的话,函数才起作用
    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,没有出现语音无法识别的问题了!
举报

更多回帖

发帖
×
20
完善资料,
赚取积分