ANDROID音頻系統散記之三:resample-2 (SRC)

這篇是承接上一篇提到的底層resample處理,以Samsung的mini alsa-lib爲例說明。

http://blog.csdn.net/sepnic/article/details/6899903

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

    LOGV("pcm_open(0x%08x)",flags);

    pcm = calloc(1, sizeof(struct pcm));
    if (!pcm)
        return &bad_pcm;

    if (flags & PCM_IN) {
        dname = "/dev/snd/pcmC0D0c"; //capture設備節點
    } else {
        dname = "/dev/snd/pcmC0D0p"; //playback設備節點
    }
    
    ...
    pcm->flags = flags;
    pcm->fd = open(dname, O_RDWR);
    if (pcm->fd < 0) {
        oops(pcm, errno, "cannot open device '%s'");
        return pcm;
    }

    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
        oops(pcm, errno, "cannot get info - %s");
        goto fail;
    }
    ...
}

這裏不多考究這些接口實現。alsa_pcm.c中有個函數挺有趣的:

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

其中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. };  
#define SNDRV_MASK_MAX 256

struct snd_mask {
 __u32 bits[(SNDRV_MASK_MAX+31)/32];
};
結合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;  
  6. }  
    param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, 44100);

    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
        oops(pcm, errno, "cannot set hw params");
        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. };  
struct _coeff_div {
	u32 mclk;
	u32 rate;
	u16 fs;
	u8 sr;
	u8 bclk_div;
};

/* codec hifi mclk clock divider coefficients */
static const struct _coeff_div coeff_div[] = {
	/* 8k */
	{12288000, 8000, 1536, 0x4, 0x0},
	/* 11.025k */
	{11289600, 11025, 1024, 0x8, 0x0},
	/* 16k */
	{12288000, 16000, 768, 0x5, 0x0},
	/* 22.05k */
	{11289600, 22050, 512, 0x9, 0x0},
	/* 32k */
	{12288000, 32000, 384, 0x7, 0x0},
	/* 44.1k */
	{11289600, 44100, 256, 0x6, 0x07},
	/* 48k */
	{12288000, 48000, 256, 0x0, 0x07},
	/* 96k */
	{12288000, 96000, 128, 0x1, 0x04},
};

其中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. {  
  5.     if (pFormat == 0 || *pFormat != AUDIO_HW_IN_FORMAT) {  
  6.         *pFormat = AUDIO_HW_IN_FORMAT; //AudioSystem::PCM_16_BIT   
  7.         return BAD_VALUE;  
  8.     }  
  9.     if (pRate == 0) {  
  10.         return BAD_VALUE;  
  11.     }  
  12.       
  13.     //getInputSampleRate:取得與參數sampleRate最接近的且被支持的採樣率   
  14.     //支持的採樣率有:8000, 11025, 16000, 22050, 44100   
  15.     //事實上,這裏傳入來的sampleRate必須是被支持的,否則返回BAD_VALUE   
  16.     uint32_t rate = AudioHardware::getInputSampleRate(*pRate);  
  17.     if (rate != *pRate) {  
  18.         *pRate = rate;  
  19.         return BAD_VALUE;  
  20.     }  
  21.   
  22.     if (pChannels == 0 || (*pChannels != AudioSystem::CHANNEL_IN_MONO &&  
  23.         *pChannels != AudioSystem::CHANNEL_IN_STEREO)) {  
  24.         *pChannels = AUDIO_HW_IN_CHANNELS; //AudioSystem::CHANNEL_IN_MONO   
  25.         return BAD_VALUE;  
  26.     }  
  27.   
  28.     mHardware = hw;  
  29.   
  30.     LOGV("AudioStreamInALSA::set(%d, %d, %u)", *pFormat, *pChannels, *pRate);  
  31.   
  32.     //getBufferSize:根據採樣率和聲道數確定buffer的大小   
  33.     //popCount:計算參數u有多少個非0位,其實現很有趣,大家可以研究下它的算法   
  34.     mBufferSize = getBufferSize(*pRate, AudioSystem::popCount(*pChannels));  
  35.     mDevices = devices;  
  36.     mChannels = *pChannels;  
  37.     mChannelCount = AudioSystem::popCount(mChannels);  
  38.     mSampleRate = rate;  
  39.       
  40.     //檢查mSampleRate是否與AUDIO_HW_OUT_SAMPLERATE(44.1khz)一致,否則需要down resample   
  41.     if (mSampleRate != AUDIO_HW_OUT_SAMPLERATE) {  
  42.         mDownSampler = new AudioHardware::DownSampler(mSampleRate,  
  43.                                                   mChannelCount,  
  44.                                                   AUDIO_HW_IN_PERIOD_SZ,  
  45.                                                   this);  
  46.         status_t status = mDownSampler->initCheck();  
  47.         if (status != NO_ERROR) {  
  48.             delete mDownSampler;  
  49.             LOGW("AudioStreamInALSA::set() downsampler init failed: %d", status);  
  50.             return status;  
  51.         }  
  52.   
  53.         mPcmIn = new int16_t[AUDIO_HW_IN_PERIOD_SZ * mChannelCount];  
  54.     }  
  55.     return NO_ERROR;  
  56. }  
status_t AudioHardware::AudioStreamInALSA::set(
    AudioHardware* hw, uint32_t devices, int *pFormat,
    uint32_t *pChannels, uint32_t *pRate, AudioSystem::audio_in_acoustics acoustics)
{
    if (pFormat == 0 || *pFormat != AUDIO_HW_IN_FORMAT) {
        *pFormat = AUDIO_HW_IN_FORMAT; //AudioSystem::PCM_16_BIT
        return BAD_VALUE;
    }
    if (pRate == 0) {
        return BAD_VALUE;
    }
    
    //getInputSampleRate:取得與參數sampleRate最接近的且被支持的採樣率
    //支持的採樣率有:8000, 11025, 16000, 22050, 44100
    //事實上,這裏傳入來的sampleRate必須是被支持的,否則返回BAD_VALUE
    uint32_t rate = AudioHardware::getInputSampleRate(*pRate);
    if (rate != *pRate) {
        *pRate = rate;
        return BAD_VALUE;
    }

    if (pChannels == 0 || (*pChannels != AudioSystem::CHANNEL_IN_MONO &&
        *pChannels != AudioSystem::CHANNEL_IN_STEREO)) {
        *pChannels = AUDIO_HW_IN_CHANNELS; //AudioSystem::CHANNEL_IN_MONO
        return BAD_VALUE;
    }

    mHardware = hw;

    LOGV("AudioStreamInALSA::set(%d, %d, %u)", *pFormat, *pChannels, *pRate);

    //getBufferSize:根據採樣率和聲道數確定buffer的大小
    //popCount:計算參數u有多少個非0位,其實現很有趣,大家可以研究下它的算法
    mBufferSize = getBufferSize(*pRate, AudioSystem::popCount(*pChannels));
    mDevices = devices;
    mChannels = *pChannels;
    mChannelCount = AudioSystem::popCount(mChannels);
    mSampleRate = rate;
    
    //檢查mSampleRate是否與AUDIO_HW_OUT_SAMPLERATE(44.1khz)一致,否則需要down resample
    if (mSampleRate != AUDIO_HW_OUT_SAMPLERATE) {
        mDownSampler = new AudioHardware::DownSampler(mSampleRate,
                                                  mChannelCount,
                                                  AUDIO_HW_IN_PERIOD_SZ,
                                                  this);
        status_t status = mDownSampler->initCheck();
        if (status != NO_ERROR) {
            delete mDownSampler;
            LOGW("AudioStreamInALSA::set() downsampler init failed: %d", status);
            return status;
        }

        mPcmIn = new int16_t[AUDIO_HW_IN_PERIOD_SZ * mChannelCount];
    }
    return NO_ERROR;
}

以上是set方法,檢查參數format、samplerate和channelcount的合法性,檢查samplerate是否與ADCLRC一致,如果不一致,則創建一個DownSampler。

我們再看看read方法代碼片段:

  1. ssize_t AudioHardware::AudioStreamInALSA::read(void* buffer, ssize_t bytes)  
  2. {  
  3.         ......  
  4.         //檢查是否創建了DownSampler   
  5.         if (mDownSampler != NULL) {  
  6.             size_t frames = bytes / frameSize();  
  7.             size_t framesIn = 0;  
  8.             mReadStatus = 0;  
  9.             do {  
  10.                 size_t outframes = frames - framesIn;  
  11.                 //調用DownSampler的resample方法,該方法從音頻接口讀取pcm數據,然後對這些數據resample   
  12.                 mDownSampler->resample(  
  13.                         (int16_t *)buffer + (framesIn * mChannelCount),  
  14.                         &outframes);  
  15.                 framesIn += outframes;  
  16.             } while ((framesIn < frames) && mReadStatus == 0);  
  17.             ret = mReadStatus;  
  18.             bytes = framesIn * frameSize();  
  19.         } else {  
  20.             TRACE_DRIVER_IN(DRV_PCM_READ)  
  21.             //並未創建DownSampler,直接讀取pcm數據送到緩衝區   
  22.             ret = pcm_read(mPcm, buffer, bytes);  
  23.             TRACE_DRIVER_OUT  
  24.         }  
  25.         ......  
  26. }  
ssize_t AudioHardware::AudioStreamInALSA::read(void* buffer, ssize_t bytes)
{
        ......
        //檢查是否創建了DownSampler
        if (mDownSampler != NULL) {
            size_t frames = bytes / frameSize();
            size_t framesIn = 0;
            mReadStatus = 0;
            do {
                size_t outframes = frames - framesIn;
                //調用DownSampler的resample方法,該方法從音頻接口讀取pcm數據,然後對這些數據resample
                mDownSampler->resample(
                        (int16_t *)buffer + (framesIn * mChannelCount),
                        &outframes);
                framesIn += outframes;
            } while ((framesIn < frames) && mReadStatus == 0);
            ret = mReadStatus;
            bytes = framesIn * frameSize();
        } else {
            TRACE_DRIVER_IN(DRV_PCM_READ)
            //並未創建DownSampler,直接讀取pcm數據送到緩衝區
            ret = pcm_read(mPcm, buffer, bytes);
            TRACE_DRIVER_OUT
        }
        ......
}
可知,當上層需要的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等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章