Android上的resample处理
默认的情况下,Android放音的采样率固定为44.1khz,录音的采样率固定为8khz,因此底层的音频设备驱动只需设置好这两个固定的采样率。如果上层传过来的采样率与其不符的话,则Android Framework层会对音频流做resample(重采样)处理。
Resample的大致流程如下:
AudioResample作为最基本的类,回放和录音resample最终都会调用到这个类;有兴趣可以研究下resample的算法和实现,这里不阐述。
AudioMixer仅仅提供给回放使用的,这个类功能不仅仅是resample了,混音、音量设置都由这个类实现的。
录音:
1、 AudioFlinger::RecordThread是录音线程类,每当有录音请求时,进入AudioFlinger::openInput创建这个线程;
2、 在创建这个线程的同时,调用readInputParameters,检查上层传过来的录音采样率是否与底层音频接口固定的录音采样率一致;如果不一致,则调用AudioResampler::create创建一个resampler;
3、 关于AudioFlinger::RecordThread::ThreadLoop,当录音工作线程启动后,会不断循环该ThreadLoop方法,主要是:1)读取底层音频设备获取录音数据mBytesRead = mInput->read(mRsmpInBuffer, mInputBytes); 2)对录音数据做重采样 mResampler->resample(mRsmpOutBuffer, framesOut, this);
放音:
1、 AudioFlinger::MixerThread是默认的放音线程,派生自PlaybackThread,由AudioFlinger::openOutput负责创建;
2、 MixerThread创建时,1)调用readOutputParameters获取底层音频接口固定的放音采样率;2)创建一个AudioMixer;
3、 关于AudioFlinger::MixerThread:: ThreadLoop,当放音工作线程启动后,会不断循环该TreadLoop方法,主要是:1)混合各track音频数据mAudioMixer->process(); 2)将混合后的音频数据写到底层音频设备int bytesWritten = (int)mOutput->write(mMixBuffer, mixBufferSize);
Android默认的采样率
之前有提及Android默认情况下,放音采样率是44.1khz,录音采样率是8khz,分别通过AudioStreamIn::sampleRate()和AudioStreamOut::sampleRate()获得。
一步一步跟下去,会发现是alsa_default.cpp里面固定好的:
-
- static alsa_handle_t _defaultsOut = {
- module : 0,
- devices : AudioSystem::DEVICE_OUT_ALL,
- curDev : 0,
- curMode : 0,
- handle : 0,
- format : SND_PCM_FORMAT_S16_LE,
- channels : 2,
- sampleRate : DEFAULT_SAMPLE_RATE,
- latency : 200000,
- bufferSize : DEFAULT_SAMPLE_RATE / 5,
- modPrivate : 0,
- };
-
-
-
- static alsa_handle_t _defaultsIn = {
- module : 0,
- devices : AudioSystem::DEVICE_IN_ALL,
- curDev : 0,
- curMode : 0,
- handle : 0,
- format : SND_PCM_FORMAT_S16_LE,
- channels : 1,
- sampleRate : AudioRecord::DEFAULT_SAMPLE_RATE,
- latency : 250000,
- bufferSize : 2048,
- modPrivate : 0,
- };
网上有谈到Android这种音频resample方式导致音质变差,我想修改起来应该也不是很难,以后找机会实践一下。思路:先尝试用上层传过来的录音采样率来设置底层音频设备,如果设置成功则不需要resample,不成功才使用默认的采样率。放音暂不能这样改,因为放音可能要混合多个track的数据,而各个track的采样率不一定是一样的。
ALSA中resample处理??
这章节我是用问号的,因为在这里我的确困惑了,先细细道来。
一般来说,resample的流程是这样:
- +---------------------+ +-------------------------------+
- | app sample rate | | Android Framework sample rate |
- | 16khz | <--resample-- | 8khz | <--ALSA interface
- +---------------------+ +-------------------------------+
那么ALSA interface用8khz的采样率进行录音就好。
但事实上我发现底层音频设备用其他的采样率也是可以的。比如说44.1khz,我测量到I2S的ADCLRC是44.1khz并且CODEC的寄存器也是设置为44.1khz录音采样率,就这样Android应用录出来的声音也是正常的。
可明明在alsa_default.cpp中设置的采样率是8khz的,而且设置成功:
- err = snd_pcm_hw_params_set_rate_near(handle->handle, hardwareParams,
- &requestedRate, 0);
返回来的requestedRate是8000,这是Android Framework固定的录音采样率。
那么到底在哪里进行了44.1khz->8khz的resample处理?唯一是在alsa-lib或alsa-driver里面了,到目前为止我还未找到相关代码。
----
2011/10/21
近期琐事颇多,找房子租房子搬家一团糟,昨天终于全部搞定了。
回到正题,有关alsa的resample处理,应该是在alsa-lib中实现的。摘录别人的分析,如下:
- snd_pcm_mmap_write_areas()函数循环写入数据,直到数据为空,首先将找到映射内存pcm->running_areas的地址,然后调用snd_pcm_areas_copy()进行数据转换,如sample rate、channels等(如果源数据和硬件支持格式一致,就简单地通过memcpy拷贝数据),转换成硬件支持格式对应的数据后,调用snd_pcm_mmap_commit()将转换后的数据写入映射内存。写入由snd_pcm_dmix_mmap_commit()完成,在对数据进行混音(do_mix_areas)同时,写入映射内存。
我不能保证以上分析是否完全正确(事实上我跟踪了一下,是有些出入)。由于alsa-lib太过臃肿复杂,我也懒得去细细分析,以后如果有空去研究的话,会对这里的说法作一些更正。
抛弃alsa-lib,其实我们还是有其他选择的,还记得ANDROID2.3音频系统HAL提到的mini alsa-lib(android2.3.1-gingerbread/device/samsung/crespo/libaudio)吗?我们可以从这里分析,由于篇幅可能比较长,就独立成章吧,待整理...
这篇是承接上一篇提到的底层resample处理,以Samsung的tiny alsa-lib为例说明。
tiny alsa-lib
这个tiny alsa-lib位于android2.3.1-gingerbread/device/samsung/crespo/libaudio中。如之前所说alsa-lib实现了太多plugin的功能,显得复杂臃肿。因此我建议如果想了解alsa在上层调用过程,最好从这个tiny alsa-lib入手,就两个源文件:alsa_pcm.c和alsa_mixer.c,前者是pcm回放录音接口,后者是mixer controls的控制接口。
alsa-lib其实也是通过操作/dev目录的设备节点来调用内核空间的音频驱动接口,这点跟平常的字符设备的调用方法一样的。如open:
- 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";
- } else {
- dname = "/dev/snd/pcmC0D0p";
- }
-
- ...
- 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中有个函数挺有趣的:
- 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的定义分别如下:
- #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中,有
- 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分频出来的。在底层音频驱动中,一般有如下的结构体:
- struct _coeff_div {
- u32 mclk;
- u32 rate;
- u16 fs;
- u8 sr;
- u8 bclk_div;
- };
-
-
- static const struct _coeff_div coeff_div[] = {
-
- {12288000, 8000, 1536, 0x4, 0x0},
-
- {11289600, 11025, 1024, 0x8, 0x0},
-
- {12288000, 16000, 768, 0x5, 0x0},
-
- {11289600, 22050, 512, 0x9, 0x0},
-
- {12288000, 32000, 384, 0x7, 0x0},
-
- {11289600, 44100, 256, 0x6, 0x07},
-
- {12288000, 48000, 256, 0x0, 0x07},
-
- {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中,除了tiny 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)。如下是简单的分析:
- 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;
- return BAD_VALUE;
- }
- if (pRate == 0) {
- return 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;
- return BAD_VALUE;
- }
-
- mHardware = hw;
-
- LOGV("AudioStreamInALSA::set(%d, %d, %u)", *pFormat, *pChannels, *pRate);
-
-
-
- mBufferSize = getBufferSize(*pRate, AudioSystem::popCount(*pChannels));
- mDevices = devices;
- mChannels = *pChannels;
- mChannelCount = AudioSystem::popCount(mChannels);
- mSampleRate = rate;
-
-
- 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方法代码片段:
- ssize_t AudioHardware::AudioStreamInALSA::read(void* buffer, ssize_t bytes)
- {
- ......
-
- if (mDownSampler != NULL) {
- size_t frames = bytes / frameSize();
- size_t framesIn = 0;
- mReadStatus = 0;
- do {
- size_t outframes = frames - framesIn;
-
- 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)
-
- 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等。