引子

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. 微信小程序标签页切换

    WXML中: <view class="swiper-tab"> <view class="swiper-tab-list {{currentTab== ...

  2. c#字符串加载wpf控件模板代码 - 简书

    原文:c#字符串加载wpf控件模板代码 - 简书 ResourceManager resManagerA = new ResourceManager("cn.qssq666.Properti ...

  3. xpath基础

    XML:一种可扩展标记语言,HTML就是一种XML XPATH:也是一个W3C标准,在所有XML中均可使用 XPATH的路径规则 /表示跟节点 /html 表示html这个元素 /html/body ...

  4. Scrapy爬去哪儿~上海一日游门票并存入MongoDB数据库

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZwAAAGGCAYAAABPDDfEAAAgAElEQVR4nOy9C3Rb1Z3/+z1Hkm35mT

  5. 【ZABBIX】ZABBIX3.2升级3.4

    小贴士 1.停止zabbix服务 service zabbix_server stop service zabbix_agentd stop /usr/local/zabbix/sbin/zabbix ...

  6. 【Linux 运维】Linux 目录

    目录 [Linux 运维]Centos7初始化网络配置 [Linux 运维]linux系统修改主机名 [Linux 运维]linux系统关机.重启.注销命令 [Linux 运维]linux系统查看版本 ...

  7. java之接口开发-初级篇-http和https

    http协议util address(url地址),str(数据参数) private static HttpMethod getPostJsonMethodInRequestBody(String ...

  8. 数据库MySql在python中的使用

    随着需要存储数据的结构不断复杂化,使用数据库来存储数据是一个必须面临的问题.那么应该如何在python中使用数据库?下面就在本篇博客中介绍一下在python中使用mysql. 首先,本博客已经假定阅读 ...

  9. [leetcode-914-X of a Kind in a Deck of Cards]

    In a deck of cards, each card has an integer written on it. Return true if and only if you can choos ...

  10. ES6的新特性(21)——Proxy

    Proxy 概述 Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程. Proxy 可以理解成,在目标对 ...