http://blog.csdn.net/xuesen_lin/article/details/8805091

1.1.1 PlaybackThread的循环主体

当一个PlaybackThread进入主循环后(threadLoop),音频事务就正式开启了。仔细观察的话,我们会发现这个循环中会不断地调用以“threadLoop_”开头的若干接口,比如threadLoop_mix、threadLoop_sleepTime、threadLoop_standby等等。以这样的前缀开头,是因为这些函数都是在threadLoop这个主体里被调用的,可以说代表了这个PlaybackThread所需要完成的各个操作步骤。

从上一小节可以了解到,当程序执行到PlaybackThread::onFirstRef时会去真正启动一个线程承载运行threadLoop,接下来我们具体看下这个循环体的处理流程。

bool AudioFlinger::PlaybackThread::threadLoop()

{ …

while (!exitPending())/*Step1.*/

{   …

processConfigEvents();/*Step 2. */

{ /*把这段代码框起来的目的是限制自动锁变量_l的生命期,

从而灵活地实现了自动锁的控制范围*/

Mutex::Autolock  _l(mLock);

/*Step 3. Standby判断*/

if(CC_UNLIKELY((!mActiveTracks.size() && systemTime() > standbyTime)|| mSuspended> 0)) {

if (!mStandby){

threadLoop_standby();//调用设备的

mStandby =true;

mBytesWritten = 0;

}

}

/*Step 4.*/

mMixerStatus =prepareTracks_l(&tracksToRemove);

}

/*Step5.*/

if(CC_LIKELY(mMixerStatus == MIXER_TRACKS_READY)) {

threadLoop_mix();

} else {

threadLoop_sleepTime();

}

/*Step 6.*/

if (sleepTime == 0) {

threadLoop_write(); //不需要休眠,有数据要写

mStandby = false;

} else {

usleep(sleepTime);//进入休眠,时间长短是sleepTime

}

/*Step 7.*/

threadLoop_removeTracks(tracksToRemove);//移除相关Track

tracksToRemove.clear();…

}//while (!exitPending())结束

releaseWakeLock();

return false;

}

Step1@ PlaybackThread::threadLoop, 循环的条件是!exitPending()为true。这个函数属于Thread类,它主要通过判断内部变量mExitPending的值来得出是否要结束线程。变量mExitPending在Thread初始化时为fasle,如果后面有人通过requestExit()、requestExitAndWait等等来请求退出,这个值就会改变,从而使得PlaybackThread结束循环。

Step2@ PlaybackThread::threadLoop, 处理config事件。当有配置改变的事件发生时,可以通过sendConfigEvent来通知PlaybackThread。这个函数将把事件添加到mConfigEvents全局变量中,以供processConfigEvents进行处理。配置事件包括如下几种:

enum io_config_event {

OUTPUT_OPENED,//Output打开

OUTPUT_CLOSED,//Output关闭

OUTPUT_CONFIG_CHANGED,//Output配置改变

INPUT_OPENED, //Input打开

INPUT_CLOSED, //Input关闭

INPUT_CONFIG_CHANGED,//Input配置改变

STREAM_CONFIG_CHANGED,//Stream配置改变

NUM_CONFIG_EVENTS

};

Step3@ PlaybackThread::threadLoop,判断当前是否符合Standby的条件,如果是的话就调用threadLoop_standby。这个函数最终还是通过HAL层的接口来实现,如下:

mOutput->stream->common.standby(&mOutput->stream->common);

Step4@ PlaybackThread::threadLoop, 进行数据准备,prepareTracks_l这个函数非常长,我们先用伪代码的形式整理一下它所做的工作,如下所示:

AudioFlinger::PlaybackThread::mixer_stateAudioFlinger::MixerThread::prepareTracks_l(…)

{

/*Step 1. 当前活跃的Track数量*/

size_t  count = mActiveTracks.size();

/*Step 2. 循环处理每个Track,这是函数的核心*/

for (size_t i=0; i<count ; i++) {

Track* track =mActiveTracks[i];//伪代码没有考虑强指针

/*Step 3. FastTrack下的处理*/

if(track is FastTrack)

{

//dosomething;

}

/*Step 4. 准备数据,分为以下几个小部来完成*/

audio_track_cblk_t*cblk = track->cblk(); //Step 4.1 数据块准备

/*Step 4.2 要回放音频前,至少需要准备多少帧数据?*/

uint32_t  minFrames = 1;//初始化

//具体计算minFrames…

/*Step 4.3 如果数据已经准备完毕*/

//调整音量

//其它参数设置

}//for循环结束

/*Step 5. 后续判断*/

//返回结果,指明当前状态是否已经ready

}

现在我们针对上面的步骤来做“填空”。

Step1@ MixerThread::prepareTracks_l, mActiveTracks的数据类型是SortedVector,用于记录当前活跃的Track。它会随着新的AudioTrack的加入而扩大,也会在必要的情况下(AudioTrack工作结束、或者出错等等)remove相应的Track。

Step2&3@MixerThread::prepareTracks_l, 循环的条件就是要逐个处理该PlaybackThread中包含的Track。假如当前是一个FastTrack,我们还要做一些其它准备,这里就暂时不去涉及具体细节了。

Step4@ MixerThread::prepareTracks_l, 这一步是准备工作中最重要的,那就是缓冲数据。在学习代码细节前,我们先来了解数据传输时容易出现的underrun情况。

什么是BufferUnderrun呢?

当两个设备或进程间形成“生产者-消费者”关系时,如果生产的速度不及消费者消耗的速度,就会出现Underrun。以音频回放为例,此时用户听到的声音就可能是断断续续的,或者是重复播放当前buffer中的数据(取决于具体的实现)。

如何避免这种异常的发生?这也是Step4所要解决的问题,以下分为几个小步骤来看AudioFlinger是如何做到的。

Ø  Step4.1,取得数据块

audio_track_cblk_t*cblk = track->cblk();

关于audio_track_cblk_t的更多描述,可以参见后面数据流小节。

Ø  Step4.2 计算正确回放音频所需的最少帧数,初始值为1。

uint32_tminFrames = 1;

if((track->sharedBuffer() == 0) && !track->isStopped() &&!track->isPausing() &&

(mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {

if(t->sampleRate() == (int)mSampleRate) {

minFrames = mNormalFrameCount;

} else {

minFrames =(mNormalFrameCount * t->sampleRate()) / mSampleRate + 1 + 1;

minFrames +=mAudioMixer->getUnreleasedFrames(track->name());

ALOG_ASSERT(minFrames <= cblk->frameCount);

}

}

当track->sharedBuffer()为0时,说明AudioTrack不是STATIC模式的,否则数据就是一次性传送的,可以参见AudioTrack小节的描述。全局变量mSampleRate 是通过mOutput->stream->common.get_sample_rate获得的,它是由HAL提供的,代表的是设备的Sampling rate。

如果两者一致的话,就采用mNormalFrameCount,这个值在readOutputParameters函数中进行初始化。如果两者不一致的话,就要预留多余的量做rounding(+1)和interpolation(+1)。另外,还需要考虑未释放的空间大小,也就是getUnreleasedFrames得到的。得出的minFrames必需小于数据块的总大小,因而最后有个ASSERT。通常情况下frameCount分配的是一个buffer的两倍,可以参见AudioTrack小节的例子。

Ø  Step4.3 数据是否准备就绪了?

上一步我们计算出了数据的最小帧值,即minFrames,接下来就该判断目前的情况是否符合这一指标了,代码如下所示:

if ((track->framesReady() >=minFrames) && track->isReady() &&!track->isPaused()&& !track->isTerminated())

{//数据准备就绪,并处于ready状态

mixedTracks++; //需要mix的Track数量增加1

/*计算音量值*/

uint32_t vl, vr,va; //三个变量分别表示左、右声道、Aux level音量

if(track->isMuted() || track->isPausing()||mStreamTypes[track->streamType()].mute) {

vl = vr = va =0; //当静音时,变量直接赋0

if (track->isPausing()) {

track->setPaused();

}

} else {

/*这里获得的是针对每个stream类型设置的音量值,也就是后面“音量调节”小节里最

后执行到的地方,在这里就起到作用了*/

float typeVolume =mStreamTypes[track->streamType()].volume;

float v =masterVolume * typeVolume; //主音量和类型音量的乘积

uint32_t  vlr = cblk->getVolumeLR(); //这里得到的vlr必须经过验证是否在合理范围内

vl = vlr &0xFFFF; //vlr的高低位分别表示vr和vl

vr = vlr>> 16;

if (vl >MAX_GAIN_INT) { //对vl进行合理值判断

ALOGV("Track left volume out of range: %04X", vl);

vl =MAX_GAIN_INT;

}

if (vr >MAX_GAIN_INT) {//对vr进行合理值判断

ALOGV("Track right volume out of range: %04X", vr);

vr =MAX_GAIN_INT;

}

// now applythe master volume and stream type volume

vl =(uint32_t)(v * vl) << 12;

vr =(uint32_t)(v * vr) << 12;

uint16_tsendLevel = cblk->getSendLevel_U4_12();

// send levelcomes from shared memory and so may be corrupt

if (sendLevel> MAX_GAIN_INT) {

ALOGV("Track send level out of range:%04X", sendLevel);

sendLevel= MAX_GAIN_INT;

}

va =(uint32_t)(v * sendLevel);

} …

mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, (void*)vl);

mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, (void*)vr);

mAudioMixer->setParameter(name, param, AudioMixer::AUXLEVEL, (void*)va);

} else {//数据未准备就绪,略过。。。

对于音量的设置还有很多细节,大家有兴趣的可以深入研究下。在得到vl、vr和va的值后,还需要把它们应用到AudioMixer中去,不过在prepareTracks_l中还只是调用mAudioMixer->setParameter设置了这些参数,真正的实现是在threadLoop_mix中,我们后面会讲到这个函数。

Step5@ MixerThread::prepareTracks_l, 通过对每个Track执行上述的处理后,最后要返回一个结果,这通常取决于:

①是否有activetrack

②active track的数据是否已经准备就绪

返回的最终值将影响到threadLoop的下一步操作。

完成了prepareTracks_l的分析,我们再回到前面的threadLoop。

Step5@ PlaybackThread::threadLoop, 如果上一步的数据准备工作已经完成(即返回值是MIXER_TRACKS_READY),就开始进行真正的混音操作,即threadLoop_mix,否则会休眠一定的时间——如此循环往复直到退出循环体。

void AudioFlinger::MixerThread::threadLoop_mix()

{

int64_t pts;

mAudioMixer->process(pts);

}

这样就进入AudioMixer的处理了,我们放在下一小节做统一分析。

假如数据还没有准备就绪,那么AudioFlinger将调用threadLoop_sleepTime来计算需要休眠多长时间(变量sleepTime),并在threadLoop主循环的末尾(在remove track之前)执行usleep进入休眠。

Step6@ PlaybackThread::threadLoop, 将数据写到HAL中,从而逐步写入到硬件设备中。

void AudioFlinger::PlaybackThread::threadLoop_write()

{

mLastWriteTime =systemTime();

mInWrite = true;

int bytesWritten;

if (mNormalSink != 0) {

ssize_t framesWritten= mNormalSink->write(mMixBuffer, count);

} else {

bytesWritten =(int)mOutput->stream->write(mOutput->stream, mMixBuffer,mixBufferSize);

}

if (bytesWritten > 0)mBytesWritten += mixBufferSize;

mNumWrites++;

mInWrite = false;

}

分为两种情况:

Ø  如果是采用了NBAIO(Non-blocking AudioI/O),即mNormalSink不为空,则通过它写入HAL

Ø  否则使用普通的AudioStreamOut(即mOutput变量)将数据输出

Step7@ PlaybackThread::threadLoop, 移除tracksToRemove中指示的Tracks。是否移除一个Track是在prepareTracks_l中判断中,可以概括为以下几种情况:

Ø  对于Fast Track,如果它的状态(mState)是STOPPING_2、PAUSED、TERMINATED、STOPPED、FLUSHED,或者状态是ACTIVE但underrun的次数超过限额(mRetryCount),则会被加入tracksToRemove列表中

Ø  当前的Track数据未准备就绪的情况下,且是STATICTRACK或者已经停止/暂停,也会被加入tracksToRemove列表中

在tracksToRemove列表中的Track,与其相关的output将收到stop请求(由AudioSystem::stopOutput发起)。

关于AudioFlinger中与AudioTrack、AudioPolicyService有交互的部分,我们还将在后续小节进行阐述。

Android音频系统之AudioFlinger(三)的更多相关文章

  1. Android音频系统之AudioFlinger(一)

    1.1 AudioFlinger 在上面的框架图中,我们可以看到AudioFlinger(下面简称AF)是整个音频系统的核心与难点.作为Android系统中的音频中枢,它同时也是一个系统服务,启到承上 ...

  2. Android音频系统之AudioFlinger(二)

    1.1.1 音频设备的管理 虽然AudioFlinger实体已经成功创建并初始化,但到目前为止它还是一块静态的内存空间,没有涉及到具体的工作. 从职能分布上来讲,AudioPolicyService是 ...

  3. Android音频系统之AudioFlinger(四)

    http://blog.csdn.net/xuesen_lin/article/details/8805096 1.1.1 AudioMixer 每一个MixerThread都有一个唯一对应的Audi ...

  4. Android音频系统之音频框架

    1.1 音频框架 转载请注明,From LXS, http://blog.csdn.net/uiop78uiop78/article/details/8796492 Android的音频系统在很长一段 ...

  5. Android音频系统之AudioPolicyService

    地址:http://blog.csdn.net/edmond999/article/details/18599327 1.1 AudioPolicy Service 在AudioFlinger小节,我 ...

  6. 转:ANDROID音频系统散记之四:4.0音频系统HAL初探

    昨天(2011-11-15)发布了Android4.0的源码,今天download下来,开始挺进4.0时代.简单看了一下,发现音频系统方面与2.3的有较多地方不同,下面逐一描述. 一.代码模块位置 1 ...

  7. Android音频系统

    1 分析思路 Thread如何创建? AudioPolicyService是策略的制定者,AudioFlinger是策略的执行者, 所以: AudioPolicyService根据配置文件使唤Audi ...

  8. Android 音频系统得框架

    http://www.mamicode.com/info-detail-1790053.html http://blog.csdn.net/lushengchu_luis/article/detail ...

  9. Android窗口系统第三篇---WindowManagerService中窗口的组织方式

    Android窗口系统第一篇—Window的类型与Z-Order确定 Android窗口系统第二篇—Window的添加过程 上面文章梳理了一个窗口的添加过程,系统中有很多应用,每个应用有多个Activ ...

随机推荐

  1. 一些网站的meta标签的作用

    转载:https://www.cnblogs.com/Lily-nercel/p/6693293.html <!DOCTYPE html> <html lang="en&q ...

  2. openvpn 使用账户密码认证

    OpenVPN使用user/passwd完成验证登录 OpenVPN使用user/passwd完成验证登录1,为什么要使用user/passwd?比常规openvpn管理方便,删除用户只需要在pwd. ...

  3. Unity塔防游戏源码Warfront Defenders Playmaker Kit v1.7

    Warfront Defenders Playmaker Kit, is a World War 2 based Tower Defense game project made using Huton ...

  4. HDU 3251 Being a Hero(最小割+输出割边)

    Problem DescriptionYou are the hero who saved your country. As promised, the king will give you some ...

  5. 如何成功再次安装MYSQL

    以前安过,后来再安装就是停在启动项就是过不去,无响应 弄了两天,期待奇迹,网上各种教程试了个遍就是不行,大体就是删除INI,清理注册表,以下是新的发现:(转载) 如果你的电脑里装过MySQL,想再重新 ...

  6. swift - 3D 视图,截图,关键字搜索

    1.xib 上的 3D效果 按钮 2. import UIKit //1.导入框架 import MapKit class ViewController: UIViewController { @IB ...

  7. 2017-2018-2 20165315 实验二《Java面向对象程序设计》实验报告

    2017-2018-2 20165315 实验二<Java面向对象程序设计>实验报告 一.实验内容及步骤 1.初步掌握单元测试和TDD 单元测试 任务一:三种代码 用程序解决问题时,要学会 ...

  8. mysql 数据库名称,中间带有中划线问题

    插入数据时候,引用了数据库名,数据库名中有横线,会提示错误: You have an error in your SQL syntax; check the manual that correspon ...

  9. will not be exported or published. Runtime ClassNotFoundExceptions may result.

    在eclipse中加入某个jar包时,会出现Classpath entry XXX.jar will not be exported or published. Runtime ClassNotFou ...

  10. 移动端IOS和androi及浏览器js判断[转载]

    转载自:http://www.niutifa.com/?p=561 移动端IOS和androi及浏览器js判断: <script type="text/javascript" ...