ANDROID音频系统散记之五:如何绕开多媒体音轨的重采样

时间:2024-03-31 08:21:15

两年前,Android智能手机的音质还广受诟病,那时不仅不能与专业影音设备相提并论,连48KHz采样率的声音都要强制成转换成44.1KHz输出,这种非线性重采样极大地损坏了音质,加剧互调失真。对于此的较完整分析见:http://www.soomal.com/doc/10100002164.htm

后来,android智能手机竞争越来越大,同质化也越来越严重。因此,音质的提升成了一个重大卖点,在此环境影响之下,一些厂家开始积极探求Android音质,如步步高、魅族。另外Soolma数码多由于对音质的执着追求和专业测评也成了非常有力的推动者,Android音质有今天,其功不可没。

音质的提升,不只是堆高性能指标的音频DAC就能轻松达成的,还要有合适的音效处理,除此外还需要解决一些Android音频系统的缺陷,包括上面提到的重采样(当然这只是个开始,还有192KHz/24bit的支持)。本章简单阐述怎么解决放音的非线性重采样,实现音源的原始采样率输出(即对放音不做重采样处理)。

在此之前,我们先思考下:Android为什么要把所有音轨数据都重采样到一个固定的采样率(44.1KHz/48KHz)输出?

音频系统中可能存在多个音轨,而每个音轨的原始采样率可能是不一致的。比如在播放音乐的过程中,来了一个提示音,就需要把音乐和提示音都混合到codec输出,音乐的原始采样率和提示音的原始采样率可能是不一致的。问题来了,如果codec的采样率设置为音乐的原始采样率的话,那么提示音就会失真。

因此最简单见效的解决方法是:codec的采样率固定一个值(44.1KHz/48KHz),所有音轨都重采样到这个采样率,然后才送到codec,保证所有音轨听起来都不失真。

从上面的思考来看:对于我们面临的工作而言,并不能做到所有音轨都按照各自的原始采样率输出到codec上,对I2S和PCM音频接口而言,一个音频接口在一个时刻,只能支持一个采样率设置。

因此有个策略,到底哪个音轨优先?毫无疑问是媒体音(即Android音频系统中的STREAM_MUSIC类型),如音乐和视频音轨。注意Android原生系统中一个时刻内只有一个STREAM_MUSIC类型的音轨,不会有音乐和视频同时播放的情形。当然有厂家修改了Android多媒体系统,可支持多个STREAM_MUSIC音轨同时播放,这种情形就另定策略了。


What is AudioFlinger


AudioFlinger是Android音频系统两大服务之一,系统启动时由MediaServer加载,见:frameworks/av/media/mediaserver/main_mediaserver.cpp。

AudioFlinger作用:

1.        向上提供IAudioFlinger接口,供AudioTrack/AudioRecord/AudoSystem/AudioPolicyService等类调用;

2.        向下访问Audio HAL,实现音频数据的混音/输入/输出;

因此AudioFlinger是承接多媒体/音频策略与音频硬件抽象层之间的一个中间层,战略地位非常重要。

ANDROID音频系统散记之五:如何绕开多媒体音轨的重采样


Android4.4的AudioFlinger代码位置:frameworks/av/services/audioflinger,相比于之前版本更加模块化,基本把各个模块代码都抽取出来独立成文件,如下:

AudioResampler.cpp:重采样处理,可进行采样率转换和声道转换;由录音线程AudioFlinger::RecordThread调用;

AudioMixer.cpp:混音处理,包括重采样、音量调节、声道转换等,其中的重采样复用了AudioResampler的代码;由放音线程AudioFlinger::MixerThread调用;

Effects.cpp:音效处理;

Tracks.cpp:放音情形:AudioFlinger::PlaybackThread::Track是音轨数据的消费者,对应的生产者是AudioTrack.cpp,每个音轨对应着各自的AudioTrack实例,AudioTrack实例在创建时会注册到放音线程AudioFlinger::MixerThread中;录音情形类似;

Threads.cpp:放音和录音线程实现;放音线程负责把音轨数据按照相关参数(采样率/声道数/音量大小/音效等)混音处理后,回调Audio HAL的out_write()方法把处理后的数据送给底层硬件;放音线程由AudioFlinger:: openOutput()创建,录音线程由AudioFlinger::openInput()创建;

AudioFlinger.cpp:实现AudioFlinger的接口整合,之前版本Effects/Tracks/Threads均被包含在这个文件中,因此显得极为庞大;4.4版本AudioFlinger只包含对外提供的接口,Effects/Tracks/Threads则作为私有模块独立出来,架构看起来更清晰了。


下面简单说明声音播放的完整过程:

  1. public void testSetPlaybackRate() throws Exception {  
  2.     // constants for test  
  3.     final String TEST_NAME = "testSetPlaybackRate";  
  4.     final int TEST_SR = 22050// 音频格式:采样率  
  5.     final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO; // 音频格式:双声道  
  6.     final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; // 音频格式:采样位数  
  7.     final int TEST_MODE = AudioTrack.MODE_STREAM;  
  8.     final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; // 流类型:媒体音  
  9.       
  10.     //-------- initialization --------------  
  11.     int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); // 根据音频格式计算缓冲区大小  
  12.     AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,   
  13.             minBuffSize, TEST_MODE); // --> 1. 创建一个AudioTrack实例  
  14.     byte data[] = new byte[minBuffSize/2];  
  15.     //--------    test        --------------  
  16.     track.write(data, 0, data.length); // --> 2. 往track的缓冲区写入数据  
  17.     track.write(data, 0, data.length);  
  18.     assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);  
  19.     track.play();                         // --> 3. 启动播放  
  20.     assertTrue(TEST_NAME, track.setPlaybackRate((int)(TEST_SR/2)) == AudioTrack.SUCCESS);  
  21.     //-------- tear down      --------------  
  22.     track.release();            // --> 4. 停止播放  
  23. }  

上面的几个接口都对应着AudioTrack.cpp中的接口,其中的JNI调用就不多涉及了,直接到AudioTrack.cpp一探究竟。

AudioTrack::AudioTrack()

1.        根据streamType/sampleRate/format/channelMask等参数调用

AudioSystem::getOutput()->

IAudioPolicyService:: getOutput()->…

AudioPolicyManagerBase::getOutput()

如果指定音频设备已打开,直接返回该设备的audio_io_handle_t;

如果指定音频设备未打开,则调用AudioFlinger::openOutput():打开音频设备,并创建混音线程MixerThread;

2.        创建监控线程AudioTrack::AudioTrackThread;

3.        通过binder机制调用

AudioFlinger::createTrack()->

AudioFlinger::PlaybackThread::createTrack_l()

创建一个AudioFlinger::PlaybackThread::Track实例,并把该实例添加到放音线程;

4.        AudioFlinger::createTrack()最后会创建一个控制用的TrackHandle:trackHandle = new TrackHandle(track); 并以IAudioTrack接口实例作为返回值给AudioTrack;

5.        AudioTrack得到IAudioTrack实例后,这样就可以得到了AudioFlinger创建的FIFO(sp<IMemory>iMem = track->getCblk());

6.        AudioTrack::start()对应于AudioFlinger::PlaybackThread::Track::start();其stop/pause接口也类似;

自此,AudioTrack建立了和AudioFlinger的全部联系工作,接下来AudioTrack可以:

1.        通过IAudioTrack接口控制该音轨的状态,例如start/stop/pause等;

2.        通过对FIFO的写入,实现连续的音频播放;

3.        监控线程监控事件的发生,并通过audioCallback回调函数与用户程序进行交互。

以上是AudioTrack流程分析,FIFO的管理细节参考:http://blog.csdn.net/droidphone/article/details/5941344


AudioTrack和AudioFlinger实际操作的都是Track实例,AudioTrack通过它来控制播放开始/结束(start/stop)以及数据写入(write),AudioFlinger管理多个track的数据,如调用AudioMixer来混音。

对AudioTrack而言,start操作是把对应的Track实例纳入到AudioFlinger的管理,stop操作是把对应的Track实例从AudioFlinger的管理中移除。


Resampling in AudioFlinger 


我们从原生代码入手,分析Android音频重采样过程。Android2.3时代,曾对这方面有过简略分析:http://blog.csdn.net/azloong/article/details/6859767。现在系统已经上到4.4,原理及基本流程都没有变化,只是实现看起来复杂了很多,下面回溯下。

放音和录音重采样大致框架如下:

ANDROID音频系统散记之五:如何绕开多媒体音轨的重采样

录音

1.        AudioFlinger::RecordThread是录音线程类,每当有录音请求时,进入AudioFlinger::openInput()创建这个线程;

2.        在创建这个线程的同时,调用readInputParameters(),检查上层传过来的录音采样率是否与音频设备设置的录音采样率一致;如果不一致,则调用AudioResampler::create()创建一个resampler;

3.        AudioFlinger::RecordThread::ThreadLoop(),当录音线程启动后,会不断循环如下的过程:

1) 读取底层音频设备获取录音数据mBytesRead = mInput->stream->read(mInput->stream, readInto,mBufferSize);

2) 对录音数据做重采样mResampler->resample(mRsmpOutBuffer,framesOut, this);


放音

1.        AudioFlinger::MixerThread是默认的放音线程,派生自PlaybackThread,由AudioFlinger::openOutput()负责创建;MixerThread创建时:

1) 调用readOutputParameters获取底层音频设备设置的放音采样率;

2) 初始化一个AudioMixer实例,该AudioMixer输出采样率就是上面获取的音频设备的放音采样率;

2.        AudioFlinger::PlaybackThread::threadLoop(),当放音线程启动后,会不断循环如下的过程:

1) checkForNewParameters_l()检查音频参数是否有变;最典型的一个例子是:播放过程中,音频设备从外放切换到耳机,此时会回调Audio HAL的out_set_paramters()方法切换底层音频通路;

2) prepareTracks_l()根据每个track的通道数/采样率/音量值等参数,调用AudioMixer::setParameter()设置,AudioMixer中会根据track->name找到每个track;个人理解:Android原生系统中主要是为了音量控制,因为音轨的通道数/采样率一般是不变的,只有音量可能随时被改变;

3) mAudioMixer->process()将各个track数据混音;

4) 回调Audio HAL的out_write()方法把混音处理后的数据送到音频设备输出。