Android音频系统之AudioFlinger(三)
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(三)的更多相关文章
- Android音频系统之AudioFlinger(一)
1.1 AudioFlinger 在上面的框架图中,我们可以看到AudioFlinger(下面简称AF)是整个音频系统的核心与难点.作为Android系统中的音频中枢,它同时也是一个系统服务,启到承上 ...
- Android音频系统之AudioFlinger(二)
1.1.1 音频设备的管理 虽然AudioFlinger实体已经成功创建并初始化,但到目前为止它还是一块静态的内存空间,没有涉及到具体的工作. 从职能分布上来讲,AudioPolicyService是 ...
- Android音频系统之AudioFlinger(四)
http://blog.csdn.net/xuesen_lin/article/details/8805096 1.1.1 AudioMixer 每一个MixerThread都有一个唯一对应的Audi ...
- Android音频系统之音频框架
1.1 音频框架 转载请注明,From LXS, http://blog.csdn.net/uiop78uiop78/article/details/8796492 Android的音频系统在很长一段 ...
- Android音频系统之AudioPolicyService
地址:http://blog.csdn.net/edmond999/article/details/18599327 1.1 AudioPolicy Service 在AudioFlinger小节,我 ...
- 转:ANDROID音频系统散记之四:4.0音频系统HAL初探
昨天(2011-11-15)发布了Android4.0的源码,今天download下来,开始挺进4.0时代.简单看了一下,发现音频系统方面与2.3的有较多地方不同,下面逐一描述. 一.代码模块位置 1 ...
- Android音频系统
1 分析思路 Thread如何创建? AudioPolicyService是策略的制定者,AudioFlinger是策略的执行者, 所以: AudioPolicyService根据配置文件使唤Audi ...
- Android 音频系统得框架
http://www.mamicode.com/info-detail-1790053.html http://blog.csdn.net/lushengchu_luis/article/detail ...
- Android窗口系统第三篇---WindowManagerService中窗口的组织方式
Android窗口系统第一篇—Window的类型与Z-Order确定 Android窗口系统第二篇—Window的添加过程 上面文章梳理了一个窗口的添加过程,系统中有很多应用,每个应用有多个Activ ...
随机推荐
- 一些网站的meta标签的作用
转载:https://www.cnblogs.com/Lily-nercel/p/6693293.html <!DOCTYPE html> <html lang="en&q ...
- openvpn 使用账户密码认证
OpenVPN使用user/passwd完成验证登录 OpenVPN使用user/passwd完成验证登录1,为什么要使用user/passwd?比常规openvpn管理方便,删除用户只需要在pwd. ...
- Unity塔防游戏源码Warfront Defenders Playmaker Kit v1.7
Warfront Defenders Playmaker Kit, is a World War 2 based Tower Defense game project made using Huton ...
- HDU 3251 Being a Hero(最小割+输出割边)
Problem DescriptionYou are the hero who saved your country. As promised, the king will give you some ...
- 如何成功再次安装MYSQL
以前安过,后来再安装就是停在启动项就是过不去,无响应 弄了两天,期待奇迹,网上各种教程试了个遍就是不行,大体就是删除INI,清理注册表,以下是新的发现:(转载) 如果你的电脑里装过MySQL,想再重新 ...
- swift - 3D 视图,截图,关键字搜索
1.xib 上的 3D效果 按钮 2. import UIKit //1.导入框架 import MapKit class ViewController: UIViewController { @IB ...
- 2017-2018-2 20165315 实验二《Java面向对象程序设计》实验报告
2017-2018-2 20165315 实验二<Java面向对象程序设计>实验报告 一.实验内容及步骤 1.初步掌握单元测试和TDD 单元测试 任务一:三种代码 用程序解决问题时,要学会 ...
- mysql 数据库名称,中间带有中划线问题
插入数据时候,引用了数据库名,数据库名中有横线,会提示错误: You have an error in your SQL syntax; check the manual that correspon ...
- will not be exported or published. Runtime ClassNotFoundExceptions may result.
在eclipse中加入某个jar包时,会出现Classpath entry XXX.jar will not be exported or published. Runtime ClassNotFou ...
- 移动端IOS和androi及浏览器js判断[转载]
转载自:http://www.niutifa.com/?p=561 移动端IOS和androi及浏览器js判断: <script type="text/javascript" ...