ANDROID音频系统散记之三:resample-2

落鹤生 发布于 2012-03-04 12:19 点击:958次 
这篇是承接上一篇提到的底层resample处理,以Samsung的mini alsa-lib为例说明。
TAG: ALSA   Resample   音频系统  

这篇是承接上一篇提到的底层resample处理,以Samsung的mini alsa-lib为例说明。

mini alsa-lib

这个mini alsa-lib位于android2.3.1-gingerbread/device/samsung/crespo/libaudio中。如之前所说 alsa-lib实现了太多plugin的功能,显得复杂臃肿。因此我建议如果想了解alsa在上层调用过程,最好从这个mini alsa-lib入手,就两个源文件:alsa_pcm.c和alsa_mixer.c,前者是pcm回放录音接口,后者是mixer controls的控制接口。

alsa-lib其实也是通过操作/dev目录的设备节点来调用内核空间的音频驱动接口,这点跟平常的字符设备的调用方法一样的。如open:

  
  
  
  
  1. struct pcm *pcm_open(unsigned flags) 
  2.     const char *dname; 
  3.     struct pcm *pcm; 
  4.     struct snd_pcm_info info; 
  5.     struct snd_pcm_hw_params params; 
  6.     struct snd_pcm_sw_params sparams; 
  7.     unsigned period_sz; 
  8.     unsigned period_cnt; 
  9.  
  10.     LOGV("pcm_open(0x%08x)",flags); 
  11.  
  12.     pcm = calloc(1, sizeof(struct pcm)); 
  13.     if (!pcm) 
  14.         return &bad_pcm; 
  15.  
  16.     if (flags & PCM_IN) { 
  17.         dname = "/dev/snd/pcmC0D0c"//capture设备节点 
  18.     } else { 
  19.         dname = "/dev/snd/pcmC0D0p"//playback设备节点 
  20.     } 
  21.      
  22.     ... 
  23.     pcm->flags = flags; 
  24.     pcm->fd = open(dname, O_RDWR); 
  25.     if (pcm->fd < 0) { 
  26.         oops(pcm, errno, "cannot open device '%s'"); 
  27.         return pcm; 
  28.     } 
  29.  
  30.     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { 
  31.         oops(pcm, errno, "cannot get info - %s"); 
  32.         goto fail; 
  33.     } 
  34.     ... 

这里不多考究这些接口实现。alsa_pcm.c中有个函数挺有趣的:

  
  
  
  
  1. static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned bit) 
  2.     if (bit >= SNDRV_MASK_MAX) 
  3.         return
  4.     if (param_is_mask(n)) { 
  5.         struct snd_mask *m = param_to_mask(p, n); 
  6.         m->bits[0] = 0; 
  7.         m->bits[1] = 0; 
  8.         m->bits[bit >> 5] |= (1 << (bit & 31)); 
  9.     } 

其中SNDRV_MASK_MAX和snd_mask的定义分别如下:

  
  
  
  
  1. #define SNDRV_MASK_MAX 256 
  2.  
  3. struct snd_mask { 
  4.  __u32 bits[(SNDRV_MASK_MAX+31)/32]; 
  5. }; 

结合SNDRV_MASK_MAX和snd_mask来理解:可以mask的位数高达256,但是我们计算机字长是32位,因此用8个32位的数组来构成一个256位的掩码,param_set_mask函数就是这个掩码进行设置。

其中m->bits[bit >> 5] |= (1 << (bit & 31));为核心语句,bit>>5其实就是bit除以32(即数组元素长度)取得数组下标,1 << (bit & 31)是掩码位在数组元素中的偏移量。如bit=255时,则数组下标是7,即数组bits最后一个元素,偏移量是1<<31,这时整个 bits数据就是这样:

bits[7:0] = 0x80000000:0x00000000:0x00000000:0x00000000

:0x00000000:0x00000000:0x00000000:0x00000000

这个256位的掩码的最高位就置1了。当然在实际应用中并不会用到那么高位的掩码,这里应该是为了方便以后扩展使用的,因此也只需要 m->bits[0] = 0; m->bits[1] = 0,看来仅仅最多用到64位掩码。

ADCLRC约束条件

在pcm_open中,有

  
  
  
  
  1. param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, 44100); 
  2.  
  3. if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { 
  4.     oops(pcm, errno, "cannot set hw params"); 
  5.     goto fail; 

可见,无论放音还是录音,都是设置44.1khz的采样率的。在我们的底层I2S驱动中,放音录音也是固定一个采样率44.1khz。为什么这样 做?放音就罢了,Android由于需要混合各个track的数据,故把放音采样率固定在44.1khz,而录音为什么也固定用44.1khz?注:这里 的采样率直接对应硬件信号ADCLRC/DACLRC频率。

首先需要了解一下I2S协议方面的知识。放音采样率DACLRC,录音采样率ADCLRC都是通过同一个主时钟MCLK分频出来的。在底层音频驱动中,一般有如下的结构体:

  
  
  
  
  1. struct _coeff_div { 
  2.     u32 mclk; 
  3.     u32 rate; 
  4.     u16 fs; 
  5.     u8 sr; 
  6.     u8 bclk_div; 
  7. }; 
  8.  
  9. /* codec hifi mclk clock divider coefficients */ 
  10. static const struct _coeff_div coeff_div[] = { 
  11.     /* 8k */ 
  12.     {12288000, 8000, 1536, 0x4, 0x0}, 
  13.     /* 11.025k */ 
  14.     {11289600, 11025, 1024, 0x8, 0x0}, 
  15.     /* 16k */ 
  16.     {12288000, 16000, 768, 0x5, 0x0}, 
  17.     /* 22.05k */ 
  18.     {11289600, 22050, 512, 0x9, 0x0}, 
  19.     /* 32k */ 
  20.     {12288000, 32000, 384, 0x7, 0x0}, 
  21.     /* 44.1k */ 
  22.     {11289600, 44100, 256, 0x6, 0x07}, 
  23.     /* 48k */ 
  24.     {12288000, 48000, 256, 0x0, 0x07}, 
  25.     /* 96k */ 
  26.     {12288000, 96000, 128, 0x1, 0x04}, 
  27. }; 

其中MCLK有两个可配频率,分别是12288000和11289600,前者用于8k、16k、32k、48k、96khz的分频,后者用于 11.025k、22.05k、44.1khz的分频。具体算式是rate=mclk/fs,如44100=11289600/256。

看出问题了没有?如果录音采样率设置为8khz,则MCLK必须转变为12288000,此时DACLRC就会被改变(放音声音会变得尖锐),不利 于同时放音录音。因此录音采样率是受其约束的,其实也不是一定是44.1khz,是11.025khz的倍数即可,能保证是可以从同一个MCLK分频。

DownSampler

在android2.3.1-gingerbread/device/samsung/crespo/libaudio中,除了mini alsa-lib外,就是Samsung为Android写的AudioHAL了,如AudioHardware.cpp,这相当于alsa_sound 中的文件。这个HAL有很大的通用性,移植到无通话功能的MID上都可以正常工作的,当然也保留Samsung的一些专用性,主要是通话语音通道处理。这 里不详述这个音频HAL文件,如果对AudioFlinger和alsa_sound比较熟悉的话,会很快上手掌握。

如上个章节所说,底层录音采样率ADCLRC固定是44.1khz,那么上层如果想要其他的采样率如8khz,怎么办?resample无疑。由于 这里支持的录音采样率有:8000, 11025, 16000, 22050, 44100,都低于或等于44.1khz,则只需要downsample(同理从低采样率转换到高采样率叫upsample)。如下是简单的分析:

  
  
  
  
  1. status_t AudioHardware::AudioStreamInALSA::set( 
  2.     AudioHardware* hw, uint32_t devices, int *pFormat, 
  3.     uint32_t *pChannels, uint32_t *pRate, AudioSystem::audio_in_acoustics acoustics) 
  4.     if (pFormat == 0 || *pFormat != AUDIO_HW_IN_FORMAT) { 
  5.         *pFormat = AUDIO_HW_IN_FORMAT; //AudioSystem::PCM_16_BIT 
  6.         return BAD_VALUE; 
  7.     } 
  8.     if (pRate == 0) { 
  9.         return BAD_VALUE; 
  10.     } 
  11.      
  12. //getInputSampleRate:取得与参数sampleRate最接近的且被支持的采样率 
  13. //支持的采样率有:8000, 11025, 16000, 22050, 44100 
  14. //事实上,这里传入来的sampleRate必须是被支持的,否则返回BAD_VALUE 
  15.     uint32_t rate = AudioHardware::getInputSampleRate(*pRate); 
  16.     if (rate != *pRate) { 
  17.         *pRate = rate; 
  18.         return BAD_VALUE; 
  19.     } 
  20.  
  21.     if (pChannels == 0 || (*pChannels != AudioSystem::CHANNEL_IN_MONO && 
  22.         *pChannels != AudioSystem::CHANNEL_IN_STEREO)) { 
  23.         *pChannels = AUDIO_HW_IN_CHANNELS; //AudioSystem::CHANNEL_IN_MONO 
  24.         return BAD_VALUE; 
  25.     } 
  26.  
  27.     mHardware = hw; 
  28.  
  29.     LOGV("AudioStreamInALSA::set(%d, %d, %u)", *pFormat, *pChannels, *pRate); 
  30.  
  31. //getBufferSize:根据采样率和声道数确定buffer的大小 
  32. //popCount:计算参数u有多少个非0位,其实现很有趣,大家可以研究下它的算法 
  33.     mBufferSize = getBufferSize(*pRate, AudioSystem::popCount(*pChannels)); 
  34.     mDevices = devices; 
  35.     mChannels = *pChannels; 
  36.     mChannelCount = AudioSystem::popCount(mChannels); 
  37.     mSampleRate = rate; 
  38.      
  39. //检查mSampleRate是否与AUDIO_HW_OUT_SAMPLERATE(44.1khz)一致,否则需要down resample 
  40.     if (mSampleRate != AUDIO_HW_OUT_SAMPLERATE) { 
  41.         mDownSampler = new AudioHardware::DownSampler(mSampleRate, 
  42.                                                   mChannelCount, 
  43.                                                   AUDIO_HW_IN_PERIOD_SZ, 
  44.                                                   this); 
  45.         status_t status = mDownSampler->initCheck(); 
  46.         if (status != NO_ERROR) { 
  47.             delete mDownSampler; 
  48.             LOGW("AudioStreamInALSA::set() downsampler init failed: %d", status); 
  49.             return status; 
  50.         } 
  51.  
  52.         mPcmIn = new int16_t[AUDIO_HW_IN_PERIOD_SZ * mChannelCount]; 
  53.     } 
  54.     return NO_ERROR; 

以上是set方法,检查参数format、samplerate和channelcount的合法性,检查samplerate是否与ADCLRC一致,如果不一致,则创建一个DownSampler。

我们再看看read方法代码片段:

  
  
  
  
  1. ssize_t AudioHardware::AudioStreamInALSA::read(void* buffer, ssize_t bytes) 
  2.         ...... 
  3.         //检查是否创建了DownSampler 
  4.         if (mDownSampler != NULL) { 
  5.             size_t frames = bytes / frameSize(); 
  6.             size_t framesIn = 0; 
  7.             mReadStatus = 0; 
  8.             do { 
  9.                 size_t outframes = frames - framesIn; 
  10. //调用DownSampler的resample方法,该方法从音频接口读取pcm数据,然后对这些数据resample 
  11.                 mDownSampler->resample( 
  12.                         (int16_t *)buffer + (framesIn * mChannelCount), 
  13.                         &outframes); 
  14.                 framesIn += outframes; 
  15.             } while ((framesIn < frames) && mReadStatus == 0); 
  16.             ret = mReadStatus; 
  17.             bytes = framesIn * frameSize(); 
  18.         } else { 
  19.             TRACE_DRIVER_IN(DRV_PCM_READ) 
  20.             //并未创建DownSampler,直接读取pcm数据送到缓冲区 
  21.             ret = pcm_read(mPcm, buffer, bytes); 
  22.             TRACE_DRIVER_OUT 
  23.         } 
  24.         ...... 

可知,当上层需要的samplerate与44.1khz不符时,会转入DownSampler::resample处理:

1、调用AudioHardware::AudioStreamInALSA::getNextBuffer方法,获取音频pcm数据,存放到buffer,并计算下一次buffer的地址;

2、将buffer中的数据分解成各个声道的数据并保存到mInLeft和mInRight;

3、由于原始的音频pcm数据采样率是44.1khz的,调用resample_2_1将数据转为22.05khz采样率;

4、1) 如果上层需要的samplerate=11.025khz,调用resample_2_1将数据采样率从22.05khz转换到11.025khz;

      2) 如果上层需要的samplerate=8khz,调用resample_441_320将数据采样率从11.025khz转换到8khz;

5、如果上层需要的samplerate=16khz,调用resample_441_320将数据采样率从22.05khz转换到16khz。

可见真正的resample处理是在resample_2_1()和resample_441_320()这两个函数中。前者是对倍数2的采样率进 行resample的,如44100->22050, 22050->11025, 16000->8000等;后者是对比率为441/320的采样率进行resample的,如44100->32000, 22050->16000, 11025->8000等。


本文: http://www.rosoo.net/a/201202/15745.html

你可能感兴趣的:(ANDROID音频系统散记之三:resample-2)