usb audio hal层进行重采样——记录一次解决问题的思路历程

作为码农,一直都觉得无论什么bug,都是发现问题,分析问题,解决问题的过程,解决的问题的思路,都是想方设法获得更多信息加以猜想验证,方法论拓展起来也是一门学问,我说不上来太多总结的东西,倒是最近遇到的一个bug,觉得很具有分析问题的代表性,记录下来也算有意义。

问题是客户使用一个第三方开发的语音识别apk,使用一款usb摄像头在我们平台无法唤醒,而在对比机器上可以唤醒,而另外一款同品牌不同型号的摄像头两个平台都可以唤醒。

这个问题给到我,第一反应看看什么方式录音,录音线程从audioflinger到hal到alsa流程是否正常,hal的数据是否正常

dump了audiofinger的信息,发现录音的record thread运行正常,cat 声卡设备信息也正常

那么与正常的摄像头差别在哪呢,于是拿到正常的摄像头,也同样dump audioflinger的信息,对比发现,可以正常识别的是以16000采样率1ch打开,而不能识别的摄像头是以48000采样率2ch的打开,audioflinger以什么参数打开这是设备本身支持不支持决定的

为了证明这一点,我们看看这个摄像头是否可以使用16K和1ch的参数打开,于是我们退出apk,使用tinycap实验,果然打开失败了。那么对比机为何能识别呢,我们猜想这个apk只能识别16k的音频数据,48K的数据应用层没有做重采样,对比方案是自己做了重采样了。猜测也得到apk方反馈的证实,无奈本来apk方做的事由于对比方案做好了,那么这个活也得我们来干。

接着在hal层实现重采样,48K转16K就用了一个非常简单的3取1的办法实现了,在hal层in_read接口里实现并添加dump发现已经成功将48k 2ch的pcm数据转成16K 1ch的pcm数据

 ret = proxy_read(&in->proxy, read_buff, num_read_buff_bytes);
    if (ret == 0) {
        if (num_device_channels != num_req_channels) {
             //ALOGV("chans dev:%d req:%d", num_device_channels, num_req_channels);

            out_buff = buffer;
            /* Num Channels conversion */
            if (num_device_channels != num_req_channels) {
                audio_format_t audio_format = in_get_format(&(in->stream.common));
                unsigned sample_size_in_bytes = audio_bytes_per_sample(audio_format);

                num_read_buff_bytes =
                    adjust_channels(read_buff, num_device_channels,
                                    out_buff, num_req_channels,
                                    sample_size_in_bytes, num_read_buff_bytes);
            }
	}
   						
	if (need_downsample) {
		audio_format_t audio_format = in_get_format(&(in->stream.common));
		unsigned sample_size_in_bytes = audio_bytes_per_sample(audio_format);
		out_buff = read_buff;
		num_read_buff_bytes =
					adjust_channels(read_buff, 2,
									out_buff, 1,
									sample_size_in_bytes, num_read_buff_bytes);
		num_read_buff_bytes =  downsample_48Kto16K(read_buff,buffer,num_read_buff_bytes,2);
   }

简单的降采样如下:

#define DIFF_SR 3
#define SPEAKER_CHANNEL 2
static int downsample_48Kto16K(void *input, void *output, int input_size, int sample_size) {
    int i;
    int output_frame = input_size / (sample_size * SPEAKER_CHANNEL * DIFF_SR);

    if (sample_size == sizeof(int16_t)) {
        int16_t *in = input;
        int16_t *out = output;
        for (i = 0; i < output_frame; i++) {
            *out++ = in[i*DIFF_SR*SPEAKER_CHANNEL];
           *out++ = in[i*DIFF_SR*SPEAKER_CHANNEL + 1];
        }
    } else if (sample_size == sizeof(int32_t)) {
        int32_t *in = input;
        int32_t *out = output;
        for (i = 0; i < output_frame; i++) {
            *out++ = in[i*DIFF_SR*SPEAKER_CHANNEL];
            *out++ = in[i*DIFF_SR*SPEAKER_CHANNEL + 1];
        }
    }
    return output_frame * sample_size * SPEAKER_CHANNEL;
}

在我这边调试ok后,交给客户测试反馈仍然有问题,于是让apk层hal层同时dump录音数据发现hal的pcm数据正常,apk层数据却是错的,于是询问apk方采用什么接口录得音,apk方答复采用一个第三方的开源库去录的音,然后把github的地址甩过来然我们自己去研究,都这么忙,谁有工夫去研究第三方的开源库呢。

没有时间也不想去研究第三方开源库,也不是没法debug了,无论第三方开源库封装了什么api,到了native层仍然还是Audiorecord和Audioflinger再到hal的read以及tinyalsa的pcm_read。而Audiorecord的读数据的方法要么就是read,要么就是callback,于是在native的AudioRecord::read(void* buffer, size_t userSize, bool blocking)以及AudioRecord::processAudioBuffer内添加dump。思路是这里dump的数据没问题,那么问题就解决了。毕竟这是个生产者消费者模式,两边不统一可能造成读数据的overrun,难点是apk对我们来说是个黑盒子,不知道里面是怎么调用的用的什么参数。

接下来就有个想法audioflinger使用什么采样率完全是hal层通知上去的,在hal层返回给audioflinger的全部是16K 和1ch的参数,那么audiofliner创建录音线程时就会以这个参数跑,而实际我们还是以48000 2ch的参数去打开声卡,这样apk来说完全不知道底下到底是什么设备,它只管接收数据就好了,按照这个思路修改,同时发现apk是以callback的形式录音,那么就在processAudioBuffer里添加dump,调试后这一层dump的数据已经正确是16k 1ch的,再运行apk,果然可以唤醒。

最后在加个措施只针对这款摄像头去修改,通过声卡关键字来识别是否为该摄像头

#define ALSASOUND_CARD_PATH  "/proc/asound/cards"
#define READ_BUFFER_SIZE     (512)
static int is_Alpha_usbcard_exists()
{
	FILE *mCardFile = NULL;

	int ret = 0;

	mCardFile = fopen(ALSASOUND_CARD_PATH, "r");
	if (mCardFile) {
		char tempbuffer[READ_BUFFER_SIZE];
		ALOGD("card open success");
		while (!feof(mCardFile)) {
			fgets(tempbuffer, READ_BUFFER_SIZE, mCardFile);

			if (strstr(tempbuffer, "Alpha Imaging Tech")) {
				ret = 1;
			}
			memset((void *)tempbuffer, 0, READ_BUFFER_SIZE);
		}
		ALOGD("reach EOF");
		fclose(mCardFile);
	} else
		ALOGE("Pcm open fail errno %d", errno);

    return  ret;
}

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值