Android Audio System 之一:AudioTrack如何与AudioFlinger

Android Framework的音频子系统中,每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中 进行播放

TAG: AudioTrack  音频系统  AudioFlinger

引子

Android Framework的音频子系统中,每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中 进行播放,目前Android的Froyo版本设定了同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数 据流。

如何使用AudioTrack

AudioTrack的主要代码位于 frameworks/base/media/libmedia/audiotrack.cpp中。现在先通过一个例子来了解一下如何使用 AudioTrack,ToneGenerator是android中产生电话拨号音和其他音调波形的一个实现,我们就以它为例子:

ToneGenerator的初始化函数:

  1. bool ToneGenerator::initAudioTrack() {
  2. // Open audio track in mono, PCM 16bit
  3. //, default sampling rate, default buffer size
  4. mpAudioTrack = new AudioTrack();
  5. mpAudioTrack->set(mStreamType,
  6. 0,
  7. AudioSystem::PCM_16_BIT,
  8. AudioSystem::CHANNEL_OUT_MONO,
  9. 0,
  10. 0,
  11. audioCallback,
  12. this,
  13. 0,
  14. 0,
  15. mThreadCanCallJava);
  16. if (mpAudioTrack->initCheck() != NO_ERROR) {
  17. LOGE("AudioTrack->initCheck failed");
  18. goto initAudioTrack_exit;
  19. }
  20. mpAudioTrack->setVolume(mVolume, mVolume);
  21. mState = TONE_INIT;
  22. ......
  23. }

可见,创建步骤很简单,先new一个AudioTrack的实例,然后调用set成员函数完成参数的设置并注册到AudioFlinger中,然后 可以调 用其他诸如设置音量等函数进一步设置音频参数。其中,一个重要的参数是audioCallback,audioCallback是一个回调函数,负责响应 AudioTrack的通知,例如填充数据、循环播放、播放位置触发等等。回调函数的写法通常像这样:

  1. void ToneGenerator::audioCallback(int event, void* user, void *info) {
  2. if (event != AudioTrack::EVENT_MORE_DATA) return;
  3. AudioTrack::Buffer *buffer = static_cast<AudioTrack::Buffer *>(info);
  4. ToneGenerator *lpToneGen = static_cast<ToneGenerator *>(user);
  5. short *lpOut = buffer->i16;
  6. unsigned int lNumSmp = buffer->size/sizeof(short);
  7. const ToneDescriptor *lpToneDesc = lpToneGen->mpToneDesc;
  8. if (buffer->size == 0) return;
  9. // Clear output buffer: WaveGenerator accumulates into lpOut buffer
  10. memset(lpOut, 0, buffer->size);
  11. ......
  12. // 以下是产生音调数据的代码,略....
  13. }

该函数首先判断事件的类型是否是EVENT_MORE_DATA,如果是,则后续的代码会填充相应的音频数据后返回,当然你可以处理其他事件,以下是可用的事件类型:

  1. enum event_type {
  2. EVENT_MORE_DATA = 0,
  3. // Request to write more data to PCM buffer.
  4. EVENT_UNDERRUN = 1,
  5. // PCM buffer underrun occured.
  6. EVENT_LOOP_END = 2,
  7. // Sample loop end was reached; playback restarted from loop start if loop count was not 0.
  8. EVENT_MARKER = 3,
  9. // Playback head is at the specified marker position (See setMarkerPosition()).
  10. EVENT_NEW_POS = 4,
  11. // Playback head is at a new position (See setPositionUpdatePeriod()).
  12. EVENT_BUFFER_END = 5
  13. // Playback head is at the end of the buffer.
  14. };

开始播放:

  1. mpAudioTrack->start();

停止播放:

  1. mpAudioTrack->stop();

只要简单地调用成员函数start()和stop()即可。

AudioTrack和AudioFlinger的通信机制

通常,AudioTrack和AudioFlinger并不在同一个进程中,它们通过android中的binder机制建立联系。

AudioFlinger是android中的一个service,在android启动时就已经被加载。下面这张图展示了他们两个的关系:

图一 AudioTrack和AudioFlinger的关系

我们可以这样理解这张图的含义:

  • audio_track_cblk_t实现了一个环形FIFO;
  • AudioTrack是FIFO的数据生产者;
  • AudioFlinger是FIFO的数据消费者。

建立联系的过程

下面的序列图展示了AudioTrack和AudioFlinger建立联系的过程:

图二 AudioTrack和AudioFlinger建立联系

解释一下过程:

  • Framework或者Java层通过JNI,new AudioTrack();
  • 根据StreamType等参数,通过一系列的调用getOutput();
  • 如有必要,AudioFlinger根据StreamType打开不同硬件设备;
  • AudioFlinger为该输出设备创建混音线程: MixerThread(),并把该线程的id作为getOutput()的返回值返回给AudioTrack;
  • AudioTrack通过binder机制调用AudioFlinger的createTrack();
  • AudioFlinger注册该AudioTrack到MixerThread中;
  • AudioFlinger创建一个用于控制的TrackHandle,并以IAudioTrack这一接口作为createTrack()的返回值;
  • AudioTrack通过IAudioTrack接口,得到在AudioFlinger中创建的FIFO(audio_track_cblk_t);
  • AudioTrack创建自己的监控线程:AudioTrackThread;

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

  • 通过IAudioTrack接口控制该音轨的状态,例如start,stop,pause等等;
  • 通过对FIFO的写入,实现连续的音频播放
  • 监控线程监控事件的发生,并通过audioCallback回调函数与用户程序进行交互;

FIFO的管理

audio_track_cblk_t

audio_track_cblk_t这个结构是FIFO实现的关键,该结构是在createTrack的时候,由AudioFlinger申请相 应的内存,然后通过IMemory接口返回AudioTrack的,这样AudioTrack和AudioFlinger管理着同一个 audio_track_cblk_t,通过它实现了环形FIFO,AudioTrack向FIFO中写入音频数据,AudioFlinger从FIFO 中读取音频数据,经Mixer后送给AudioHardware进行播放。

audio_track_cblk_t的主要数据成员:

user             -- AudioTrack当前的写位置的偏移
    userBase     -- AudioTrack写偏移的基准位置,结合user的值方可确定真实的FIFO地址指针
    server          -- AudioFlinger当前的读位置的偏移
    serverBase  -- AudioFlinger读偏移的基准位置,结合server的值方可确定真实的FIFO地址指针

frameCount -- FIFO的大小,以音频数据的帧为单位,16bit的音频每帧的大小是2字节

buffers         -- 指向FIFO的起始地址

out               -- 音频流的方向,对于AudioTrack,out=1,对于AudioRecord,out=0

audio_track_cblk_t的主要成员函数:

framesAvailable_l()和framesAvailable()用于获取FIFO中可写的空闲空间的大小,只是加锁和不加锁的区别。

  1. uint32_t audio_track_cblk_t::framesAvailable_l()
  2. {
  3. uint32_t u = this->user;
  4. uint32_t s = this->server;
  5. if (out) {
  6. uint32_t limit = (s < loopStart) ? s : loopStart;
  7. return limit + frameCount - u;
  8. } else {
  9. return frameCount + u - s;
  10. }
  11. }

framesReady()用于获取FIFO中可读取的空间大小。

  1. uint32_t audio_track_cblk_t::framesReady()
  2. {
  3. uint32_t u = this->user;
  4. uint32_t s = this->server;
  5. if (out) {
  6. if (u < loopEnd) {
  7. return u - s;
  8. } else {
  9. Mutex::Autolock _l(lock);
  10. if (loopCount >= 0) {
  11. return (loopEnd - loopStart)*loopCount + u - s;
  12. } else {
  13. return UINT_MAX;
  14. }
  15. }
  16. } else {
  17. return s - u;
  18. }
  19. }

我们看看下面的示意图:

_____________________________________________

^                          ^                             ^                           ^

buffer_start              server(s)                 user(u)                  buffer_end

很明显,frameReady = u - s,frameAvalible = frameCount - frameReady = frameCount - u + s

可能有人会问,应为这是一个环形的buffer,一旦user越过了buffer_end以后,应该会发生下面的情况:

_____________________________________________

^                ^             ^                                                     ^

buffer_start     user(u)     server(s)                                   buffer_end

这时候u在s的前面,用上面的公式计算就会错误,但是android使用了一些技巧,保证了上述公式一直成立。我们先看完下面三个函数的代码再分析:

  1. uint32_t audio_track_cblk_t::stepUser(uint32_t frameCount)
  2. {
  3. uint32_t u = this->user;
  4. u += frameCount;
  5. ......
  6. if (u >= userBase + this->frameCount) {
  7. userBase += this->frameCount;
  8. }
  9. this->user = u;
  10. ......
  11. return u;
  12. }
  1. bool audio_track_cblk_t::stepServer(uint32_t frameCount)
  2. {
  3. // the code below simulates lock-with-timeout
  4. // we MUST do this to protect the AudioFlinger server
  5. // as this lock is shared with the client.
  6. status_t err;
  7. err = lock.tryLock();
  8. if (err == -EBUSY) { // just wait a bit
  9. usleep(1000);
  10. err = lock.tryLock();
  11. }
  12. if (err != NO_ERROR) {
  13. // probably, the client just died.
  14. return false;
  15. }
  16. uint32_t s = this->server;
  17. s += frameCount;
  18. // 省略部分代码
  19. // ......
  20. if (s >= serverBase + this->frameCount) {
  21. serverBase += this->frameCount;
  22. }
  23. this->server = s;
  24. cv.signal();
  25. lock.unlock();
  26. return true;
  27. }
  1. void* audio_track_cblk_t::buffer(uint32_t offset) const
  2. {
  3. return (int8_t *)this->buffers + (offset - userBase) * this->frameSize;
  4. }

stepUser()和stepServer的作用是调整当前偏移的位置,可以看到,他们仅仅是把成员变量user或server的值加上需要移动 的数量,user和server的值并不考虑FIFO的边界问题,随着数据的不停写入和读出,user和server的值不断增加,只要处理得 当,user总是出现在server的后面,因此frameAvalible()和frameReady()中的算法才会一直成立。根据这种算 法,user和server的值都可能大于FIFO的大小:framCount,那么,如何确定真正的写指针的位置呢?这里需要用到userBase这一 成员变量,在stepUser()中,每当user的值越过(userBase+frameCount),userBase就会增加 frameCount,这样,映射到FIFO中的偏移总是可以通过(user-userBase)获得。因此,获得当前FIFO的写地址指针可以通过成员 函数buffer()返回:

p = mClbk->buffer(mclbk->user);

在AudioTrack中,封装了两个函数:obtainBuffer()和releaseBuffer()操作 FIFO,obtainBuffer()获得当前可写的数量和写指针的位置,releaseBuffer()则在写入数据后被调用,它其实就是简单地调用 stepUser()来调整偏移的位置。

IMemory接口

在createTrack的过程中,AudioFlinger会根据传入的frameCount参数,申请一块内存,AudioTrack可以通过 IAudioTrack接口的getCblk()函数获得指向该内存块的IMemory接口,然后AudioTrack通过该IMemory接口的 pointer()函数获得指向该内存块的指针,这块内存的开始部分就是audio_track_cblk_t结构,紧接着是大小为frameSize的 FIFO内存。

IMemory->pointer() ---->|_______________________________________________________

|__audio_track_cblk_t__|_______buffer of FIFO(size==frameCount)____|

看看AudioTrack的createTrack()的代码就明白了:

  1. sp<IAudioTrack> track = audioFlinger->createTrack(getpid(),
  2. streamType,
  3. sampleRate,
  4. format,
  5. channelCount,
  6. frameCount,
  7. ((uint16_t)flags) << 16,
  8. sharedBuffer,
  9. output,
  10. &status);
  11. // 得到IMemory接口
  12. sp<IMemory> cblk = track->getCblk();
  13. mAudioTrack.clear();
  14. mAudioTrack = track;
  15. mCblkMemory.clear();
  16. mCblkMemory = cblk;
  17. // 得到audio_track_cblk_t结构
  18. mCblk = static_cast<audio_track_cblk_t*>(cblk->pointer());
  19. // 该FIFO用于输出
  20. mCblk->out = 1;
  21. // Update buffer size in case it has been limited by AudioFlinger during track creation
  22. mFrameCount = mCblk->frameCount;
  23. if (sharedBuffer == 0) {
  24. // 给FIFO的起始地址赋值
  25. mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t);
  26. } else {
  27. ..........
  28. }

Android Audio System 之一:AudioTrack如何与AudioFlinger

时间: 2024-08-06 04:02:03

Android Audio System 之一:AudioTrack如何与AudioFlinger的相关文章

转---Android Audio System 之一:AudioTrack如何与AudioFlinger交换音频数据

引子 Android Framework的音频子系统中,每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中 进行播放,目前Android的Froyo版本设定了同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数 据流. 如何使用AudioTrack AudioTra

android audio开发的一些专用术语(待翻译)

Audio Terminology IN THIS DOCUMENT Generic Terms Digital Audio Hardware and Accessories Audio Signal Path Android-Specific Terms Sample Rate Conversion This document provides a glossary of audio-related terminology, including a list of widely used, g

Android -- Audio Native服务之启动流程分析(一)

Android -- Audio Native服务之启动流程分析(一) Android中的Audio系统是比较庞大.繁杂的一部分内容, 其中会涉及较多的音频编解码.多媒体制式与Android Audio HAL设备管理的知识.随着Android的发展,其所支持的音频设备也变得越来丰富,如扬声器.耳机.听筒等等:这种变化也为Android管理如此丰富的音频设备以及如何正确.合理地切换音频输出提出了更高的要求.面对如此繁杂的管理要求,我们分析Android Audio服务的历程想必也不会轻松.接下来

关于android audio路由策略的修改

关于android audio路由策略的修改 在工作时,往往有这样一些需求:1)希望针对不同的audio stream type(例如music.tts), 有输出到不同的输出设备(例如speaker.headset.BT.usb audio等)的需求,另外还有输出设备的优先级的需求.例如AUX(headset) > BT > FMTX > speaker.即例如BT连接了,那么音频就输出到BT,而不是FM发射.2)针对不同的audio stream type和不同的输出设备,有不同的输

Android音频: 如何使用AudioTrack播放一个WAV格式文件?

翻译 By Long Luo 原文链接:Android Audio: Play a WAV file on an AudioTrack 译者注: 1. 由于这是技术文章,所以有些词句使用原文,表达更准确. 2. 由于水平有效,有些地方可能翻译的不够准确,如有不当之处,敬请批评指正. 3. 针对某些语句,适当补充了上下文及更适合中文阅读,尽量做到信达雅. 如果你已经成功地了解了关于AudioTrack的一些话题,那么你可能享受它带来的好处,例如低延迟(在STATIC(静态)模式),能够生成流式音频

【转】Android Building System 总结 - 一醉千年 - CSDN博客

原文网址:http://www.360doc.com/content/15/0314/23/1709014_455175716.shtml Android Building System 总结 收藏 花了一个月的时间来看Android Make,在网上总是看到某某大虾说一天就把Android Make overview了一下,不得不感叹现在大虾的强大和咱那连蜗牛都可以鄙视一下的进度.不过总算是彻底看清的Android make这个系统,不得不当初架构出这套机制的神人顶礼膜拜一下,虔诚地烧三柱高香

【转】Android ROM研究---Android build system增加模块

原文网址:http://hualang.iteye.com/blog/1141315 Android build system就是编译系统的意思 在我们需要向自己编译的源代码中增加模块的时候,需要一些规则,当然这个规则都是类似的. Android.mk文件解析 让我们来看一个 Android.mk 文件的样子 Java代码 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE :=Hello LOCAL_SRC_FILES

Android 添加System权限

很多应用需要系统权限,放到/system/app 只是打包进系统.若可以进行.mk编译,则可以如下几步加入系统权限: Android 获取System 权限可以用如下几步:1. 在应用程序的AndroidManifest.xml中的manifest节点中加入android:sharedUserId="android.uid.system"这个属性.2. 修改Android.mk文件,加入LOCAL_CERTIFICATE := platform这一行 3. 使用mm命令来编译,生成的a

Gradle: The New Android Build System

Gradle: The New Android Build System Google selected Gradle as the foundation of the Android SDK build system because it provides flexibility along with the ability to define common standards for Android builds. With Gradle, Android developers can us