引子

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, default sampling rate, default buffer size
  3. mpAudioTrack = new AudioTrack();
  4. mpAudioTrack->set(mStreamType,
  5. 0,
  6. AudioSystem::PCM_16_BIT,
  7. AudioSystem::CHANNEL_OUT_MONO,
  8. 0,
  9. 0,
  10. audioCallback,
  11. this,
  12. 0,
  13. 0,
  14. mThreadCanCallJava);
  15. if (mpAudioTrack->initCheck() != NO_ERROR) {
  16. LOGE("AudioTrack->initCheck failed");
  17. goto initAudioTrack_exit;
  18. }
  19. mpAudioTrack->setVolume(mVolume, mVolume);
  20. mState = TONE_INIT;
  21. ......
  22. }

可见,创建步骤很简单,先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,        // Request to write more data to PCM buffer.
  3. EVENT_UNDERRUN = 1,         // PCM buffer underrun occured.
  4. EVENT_LOOP_END = 2,         // Sample loop end was reached; playback restarted from loop start if loop count was not 0.
  5. EVENT_MARKER = 3,           // Playback head is at the specified marker position (See setMarkerPosition()).
  6. EVENT_NEW_POS = 4,          // Playback head is at a new position (See setPositionUpdatePeriod()).
  7. EVENT_BUFFER_END = 5        // Playback head is at the end of the buffer.
  8. };

开始播放:

  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交换音频数据的更多相关文章

  1. Android Audio System 之一:AudioTrack如何与AudioFlinger

    Android Framework的音频子系统中,每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinge ...

  2. Android 音视频开发(二):使用 AudioRecord 采集音频数据并保存到文件

    版权声明:转载请说明出处:http://www.cnblogs.com/renhui/p/7457321.html 一.AudioRecord API详解 AudioRecord是Android系统提 ...

  3. Android Audio Play Out Channel

    1: 7嘴8舌 扬声器, 耳机, 和听筒 就是通过: audiomanager.setmode(AudioManager.MODE_IN_COMMUNICATION)audiomanager.setS ...

  4. Android Audio遇到播放无声时的分析

    在Android Audio开发过程中,有遇到播放ringtone时无声,但播放Music可以听到声音,关于无声问题的分析,在此做个笔记,方便以后回顾. 分析方向: 1:在音量控制面板中确认该音频流对 ...

  5. [Android][Audio] audio_policy.conf文件分析

    不同的Android产品在音频的设计上通常是存在差异的,而这些差异可以同过Audio的配置文件audio_policy.conf来获得.在Android系统中音频配置文件存放路径有两处,存放地址可以从 ...

  6. 使用AudioTrack播放PCM音频数据(android)

    众所周知,Android的MediaPlayer包含了Audio和video的播放功能,在Android的界面上,Music和Video两个应用程序都是调用MediaPlayer实现的.MediaPl ...

  7. 转载:android audio flinger

    https://blog.csdn.net/innost/article/details/6142812 https://blog.csdn.net/zyuanyun/article/details/ ...

  8. 转载:android audio policy

    Audio policy basic:https://www.cnblogs.com/CoderTian/p/5705742.html Set volume flow:https://blog.csd ...

  9. Android Build System

    归类一些Android build system 相关的知识. http://elinux.org/Android_Build_System make <local_module> - m ...

随机推荐

  1. 20155328 2016-2017-2 《Java程序设计》第二周学习总结

    20155328 2006-2007-2 <Java程序设计>第2周学习总结 教材学习内容总结 基本类型: 整数:short整数(占2字节).int整数(占4字节).long整数(占8字节 ...

  2. 20155337 2016-2017-2 《Java程序设计》第三周学习总结

    20155337 2016-2017-2 <Java程序设计>第死周学习总结 教材学习内容总结 第六章 •何谓继承: 面向对象中,为避免多个类间重复定义共同行为.(简单说就是将相同的程序代 ...

  3. Mybatis JPA-集成方案+源码

    2018-04-18 update 当前文章已过时,请访问代码仓库查看当前版本wiki. github https://github.com/cnsvili/mybatis-jpa gitee htt ...

  4. 我们一起学习WCF 第四篇单通讯和双向通讯

    前言:由于个人原因很久没有更新这个系列了,我会继续的更新这系列的文章.这一章是单向和双向通讯.所谓的单向就是只有发送却没有回复,双向是既有发送还有回复.就是有来无往代表单向,礼尚往来表示双向.下面我用 ...

  5. selenium+python 搭建自动化环境

    一.以搭建windows平台为例 准备工具如下: 1)下载Python 2)安装,配置环境变量 3)安装selenium,通过pip安装,命令如下:  pip install selenium 方式二 ...

  6. Linux 安装Redis<准备>(使用Mac远程访问)

    阅读本文需要一定的Linux基础 一 Redis简介 redis是用c语言编写的一款开源的高性能键值对(key-value)数据库 它通过提供多种键值数据类型来适应不同场景下的存储需求 二 Redis ...

  7. CentOS7使用阿里源安装最新版Docker

    卸载已经安装的Docker sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker ...

  8. Paper Reading - Long-term Recurrent Convolutional Networks for Visual Recognition and Description ( CVPR 2015 )

    Link of the Paper: https://arxiv.org/abs/1411.4389 Main Points: A novel Recurrent Convolutional Arch ...

  9. Amazon Headlines Update on Activity in US West Coast Ports

    According to news reports, freighter cargo may not be offloaded at U.S. West Coast ports from Februa ...

  10. java处理大文本2G以上

    面试中经常碰到类似问题,问题的关键我觉得是用设置一个缓冲区 还有一个思路 是通过Linux split 命令将文件直接切割成小文件,再进行处理再汇总. 或者jdk7提供的 forkjoin 框架,利用 ...