http://thinks.me/2016/09/13/audio_qcom_offload/
offload在音频系统里面,就是将对于音频文件的解码操作过载到DSP中去做,比如说mp3的解码操作不是在mediaserver中来做,而是直接将数据传递到dsp中,由DSP去解码和播放,就是所谓的硬解。硬解的优势是省功耗,除了DSP自身在处理音频格式的时候功耗相对较低,整个数据在安卓的音频系统中传递也会相对低些,前面那个好理解,后面这个我们结合代码来分析。
这次的场景,就是在高通平台通过mediaplayer进行播放mp3.其中audiotrack中采用到了callback的方式。
我们从代码的角度一步步分析。
摘取一些audiotrack中set
中的代码:
if (cbf != NULL) {
mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
// thread begins in paused state, and will not reference us until start()
}
这部分创建了AudioTrackThread,并且让它跑起来了。来看下线程的逻辑处理函数:
bool AudioTrack::AudioTrackThread::threadLoop()
{
{
AutoMutex _l(mMyLock);
// mPaused 外部触发的pause,将会在这边进行block
if (mPaused) {
mMyCond.wait(mMyLock);
// caller will check for exitPending()
return true;
}
// 如果忽略下一次的内部pause, 下面的内部pause将会被跳过
if (mIgnoreNextPausedInt) {
mIgnoreNextPausedInt = false;
mPausedInt = false;
}
// 内部pause,如果有内部pause的需求,将在这边进行等待。
if (mPausedInt) {
if (mPausedNs > 0) {
(void) mMyCond.waitRelative(mMyLock, mPausedNs);
} else {
mMyCond.wait(mMyLock);
}
mPausedInt = false;
return true;
}
}
// 如果线程结束,则返回false,停止循环
if (exitPending()) {
return false;
}
// 这边将进行获取数据,处理数据的操作,返回的ns决定将内部block多久,跟开头的那边对应
nsecs_t ns = mReceiver.processAudioBuffer();
switch (ns) {
case 0:
return true;
case NS_INACTIVE:
pauseInternal();
return true;
case NS_NEVER:
return false;
case NS_WHENEVER:
// Event driven: call wake() when callback notifications conditions change.
ns = INT64_MAX;
// fall through
default:
LOG_ALWAYS_FATAL_IF(ns < 0, "processAudioBuffer() returned %" PRId64, ns);
pauseInternal(ns);
return true;
}
}
上面函数大部分都在描述如何控制暂停,对于数据的处理在函数processAudioBuffer
中,我们可以回头来看下这个函数的实现:
nsecs_t AudioTrack::processAudioBuffer()
{
mLock.lock();
// 申请线程成实时线程,这边进行循环检测,是否申请成功,每次休眠的时间成指数增长,最多循环5次
if (mAwaitBoost) {
mAwaitBoost = false;
mLock.unlock();
static const int32_t kMaxTries = 5;
int32_t tryCounter = kMaxTries;
uint32_t pollUs = 10000;
do {
int policy = sched_getscheduler(0);
if (policy == SCHED_FIFO || policy == SCHED_RR) {
break;
}
usleep(pollUs);
pollUs <<= 1;
} while (tryCounter-- > 0);
if (tryCounter < 0) {
ALOGE("did not receive expected priority boost on time");
}
// Run again immediately
return 0;
}
// Can only reference mCblk while locked
int32_t flags = android_atomic_and(
~(CBLK_UNDERRUN | CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL | CBLK_BUFFER_END), &mCblk->mFlags);
// Check for track invalidation
if (flags & CBLK_INVALID) {
// 对于offload tracks的restoreTrack_l将只会更新sequence和清空AudioSystem缓存。
// 我们不会在这边直接退出,但是后面会调用callback来让上层recreate该track
// 对于其他类型的track,将在这边进行restore的操作,并且不会让他在这边退出,因为flag中还要处理
if (!isOffloadedOrDirect_l() || (mSequence == mObservedSequence)) {
status_t status __unused = restoreTrack_l("processAudioBuffer");
}
}
// 如果当前正在stopping状态的话,则设置waitStreamEnd为true
bool waitStreamEnd = mState == STATE_STOPPING;
// 判断当前该track的状态是否正在播放
bool active = mState == STATE_ACTIVE;
// Manage underrun callback, must be done under lock to avoid race with releaseBuffer()
bool newUnderrun = false;
if (flags & CBLK_UNDERRUN) {
if (!mInUnderrun) {
mInUnderrun = true;
newUnderrun = true;
}
}
// Get current position of server 更新server的position
size_t position = updateAndGetPosition_l();
// Cache other fields that will be needed soon
uint32_t sampleRate = mSampleRate;
float speed = mPlaybackRate.mSpeed;
const uint32_t notificationFrames = mNotificationFramesAct;
// 如果需要更新remaing则将notificationFrames更新到mRemainingFrames,一般在开始或者flush之后。
if (mRefreshRemaining) {
mRefreshRemaining = false;
mRemainingFrames = notificationFrames;
mRetryOnPartialBuffer = false;
}
size_t misalignment = mProxy->getMisalignment();
uint32_t sequence = mSequence;
sp proxy = mProxy;
mLock.unlock();
// get anchor time to account for callbacks.
const nsecs_t timeBeforeCallbacks = systemTime();
// 处理留马上结束的逻辑
if (waitStreamEnd) {
struct timespec timeout;
// 120s
timeout.tv_sec = WAIT_STREAM_END_TIMEOUT_SEC;
timeout.tv_nsec = 0;
// 这边正常情况下会进入休眠,等待最长时间为2分钟
status_t status = proxy->waitStreamEndDone(&timeout);
switch (status) {
case NO_ERROR:
case DEAD_OBJECT:
case TIMED_OUT:
// 告诉上层stream end的事件
mCbf(EVENT_STREAM_END, mUserData, NULL);
{
AutoMutex lock(mLock);
// 更新确认下是否还处在waitStreamEnd状态。如果是的话,切换state到stoped状态
waitStreamEnd = mState == STATE_STOPPING;
if (waitStreamEnd) {
mState = STATE_STOPPED;
mReleased = 0;
}
}
// 如果这个track没有出现什么异常,并且已经stream end了,就进入休眠了
if (waitStreamEnd && status != DEAD_OBJECT) {
return NS_INACTIVE;
}
// 如果不是,则直接跳出返回0,这样该函数再被调用一次。
break;
}
return 0;
}
if (flags & CBLK_BUFFER_END) {
mCbf(EVENT_BUFFER_END, mUserData, NULL);
}
// 如果当前处在非激活状态,则进入休眠等待到该track被重启
if (!active) {
return NS_INACTIVE;
}
// If > 0, poll periodically to recover from a stuck server. A good value is 2.
static const uint32_t kPoll = 0;
if (kPoll > 0 && mTransfer == TRANSFER_CALLBACK && kPoll * notificationFrames < minFrames) {
minFrames = kPoll * notificationFrames;
}
// This "fudge factor" avoids soaking CPU, and compensates for late progress by server
static const nsecs_t kWaitPeriodNs = WAIT_PERIOD_MS * 1000000LL;
const nsecs_t timeAfterCallbacks = systemTime();
// Convert frame units to time units
nsecs_t ns = NS_WHENEVER;
// If not supplying data by EVENT_MORE_DATA, then we're done
if (mTransfer != TRANSFER_CALLBACK) {
return ns;
}
// 阻塞时间的换算
struct timespec timeout;
const struct timespec *requested = &ClientProxy::kForever;
if (ns != NS_WHENEVER) {
timeout.tv_sec = ns / 1000000000LL;
timeout.tv_nsec = ns % 1000000000LL;
requested = &timeout;
}
// 开始进入获取buffer,填充buffer,release buffer的逻辑
while (mRemainingFrames > 0) {
Buffer audioBuffer;
audioBuffer.frameCount = mRemainingFrames;
size_t nonContig;
// 第一次进入该循环,obtain是个阻塞操作,后面就变成非阻塞操作。这个主要跟buffer的设计思路有关。
// avail buffer有可能包含两部分,一部分在前面,一部分在后面。而一开始我们只能获取后面部分的。
// 在循环一次将触发获取前面部分的数据。
status_t err = obtainBuffer(&audioBuffer, requested, NULL, &nonContig);
// 修改获取策略为nonBlocking
requested = &ClientProxy::kNonBlocking;
// 计算上次获取到的总的空间大小,此次只能填充audioBuffer.frameCount
size_t avail = audioBuffer.frameCount + nonContig;
size_t reqSize = audioBuffer.size;
// 问app要数据
mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);
size_t writtenSize = audioBuffer.size;
// 如果写入的数据为0,则返回给休眠的时间,省得多做无用功
if (writtenSize == 0) {
nsecs_t myns;
if (audio_is_linear_pcm(mFormat)) {
} else {
myns = kWaitPeriodNs;
}
if (ns > 0) { // account for obtain and callback time
const nsecs_t timeNow = systemTime();
ns = max((nsecs_t)0, ns - (timeNow - timeAfterCallbacks));
}
if (ns < 0 /* NS_WHENEVER */ || myns < ns) {
ns = myns;
}
return ns;
}
size_t releasedFrames = writtenSize / mFrameSize;
audioBuffer.frameCount = releasedFrames;
mRemainingFrames -= releasedFrames;
if (misalignment >= releasedFrames) {
misalignment -= releasedFrames;
} else {
misalignment = 0;
}
// 释放缓冲区,其实就是更新了下cblk的里面的参数
releaseBuffer(&audioBuffer);
// 写的数据小宇reqSize则循环再获取,填充等操作。
if (writtenSize < reqSize) {
continue;
}
// 如果buffer的前部分大于等于剩余的需要获取的数据量,则继续问app要来填充buffer
if (mRemainingFrames <= nonContig) {
continue;
}
}
// 更新mRemainingFrames为整个buffer大小。notificationFrames这个值在offload的场景下为buffer大小
mRemainingFrames = notificationFrames;
mRetryOnPartialBuffer = true;
return 0;
}
上面基本上就完成了怎么问上层要数据的逻辑了。下面代码说明mNotificationFramesAct值来自何处
下面摘取createTrack_l
中的部分代码:
// mNotificationFramesAct 等于buffer的大小
if (!audio_is_linear_pcm(mFormat)) {
if (mSharedBuffer != 0) {
// Same comment as below about ignoring frameCount parameter for set()
frameCount = mSharedBuffer->size();
} else if (frameCount == 0) {
frameCount = mAfFrameCount;
}
if (mNotificationFramesAct != frameCount) {
mNotificationFramesAct = frameCount;
}
} ...
mNotificationFramesAct
这个值我们之前有谈过了,这个值是用来确定该什么时候唤醒生产者往buffer里面填充数据,但是这个值如果大于mFrameCount
的一半的话,那通知的大小将会采用mFrameCount
的一半。
到这边,我们就先完成offload的audiotrack部分通过线程获取数据的逻辑了。obtainBuffer
和releaseBuffer
可以去参考下http://thinks.me/2016/03/18/audiotrack_write/这篇文章,里面有谈到这两个函数的实现。
上面讲了那么多,其实无非就是检查下buffer有没有空间,如果有就去填充数据,如此反复。。只是实现起来没那么容易才有这么大片大片的代码。当然上面并没有包括控制流,全是数据流相关。跟audioflinger部分的交互,可以参考下图片。
这部分就是生产者与消费者模式中的消费者了,我们将从线程创建开始分析,再看AsyncCallbackThread的逻辑,不过我们只看跟普通线程的区别的部分。但是其实消费者的主要的逻辑确实在threadloop
函数中,这个我们将只用图来描述,因为逻辑较为简单。
offloadThread创建
offloadThread线程的创建,其实依赖于audio_policy.conf中是否有配置,如果有配置,则才会有创建该线程的逻辑。
offload跟mixer线程的区别,其实主要是基本上没有mixer的处理,直接绕过audioflinger的采样率、format、采样精度、通道数等的转变,所以其实offload的逻辑是更简单的。
不过我们这篇重点在讲offload的callback的实现逻辑,直接引用PlaybackThread::readOutputParameters_l
中的部分代码,这边有创建callback的线程
if ((mOutput->flags & AUDIO_OUTPUT_FLAG_NON_BLOCKING) &&
(mOutput->stream->set_callback != NULL)) {
if (mOutput->stream->set_callback(mOutput->stream,
AudioFlinger::PlaybackThread::asyncCallback, this) == 0) {
mUseAsyncWrite = true;
mCallbackThread = new AudioFlinger::AsyncCallbackThread(this);
}
}
从上面的函数中,可以看到判断依据是audio_policy.conf中compressoffload的节点中的flag是否带有non_blocking,如果有还需要确认底层是否支持callback的场景,如果支持的话,则告诉hal层,我们这边的回调函数是哪个,并且创造callback的线程来。这个线程在这个场景中至关重要,它会等到底层有空间的时候来唤醒offload线程去通知app来填充数据。
AsyncCallbackThread逻辑
我们直接来看它的实现代码吧。
下面这个代码来自PlaybackThread::threadLoop_write
if (mUseAsyncWrite) {
// 将该值+2 | 1 保证末位为1
mWriteAckSequence += 2;
mWriteAckSequence |= 1;
// 将该值同步到callback线程中。在该函数中,这个sequence将会被左移1位。
mCallbackThread->setWriteBlocked(mWriteAckSequence);
}
bytesWritten = mOutput->write((char *)mSinkBuffer + offset, mBytesRemaining);
// 写完出来判断下写入的数据是否等于打算写入的值,如果不想等话,说明底层没有空间了。相等的话,则不需要进行block状态,可以再进行写操作。
if (mUseAsyncWrite &&
((bytesWritten < 0) || (bytesWritten == (ssize_t)mBytesRemaining))) {
// 清除末尾为1
mWriteAckSequence &= ~1;
mCallbackThread->setWriteBlocked(mWriteAckSequence);
}
这个是setWriteBlocked的操作,这个函数在threadloop_write
往hal层写数据的时候会被调用一次,退出write
操作之后,如果打算写到底层的数据量跟真实写入的值相等的话,还会再调用一次这个函数。
void AudioFlinger::AsyncCallbackThread::setWriteBlocked(uint32_t sequence)
{
Mutex::Autolock _l(mLock);
mWriteAckSequence = sequence << 1;
}
这个是线程的逻辑:
bool AudioFlinger::AsyncCallbackThread::threadLoop()
{
while (!exitPending()) {
uint32_t writeAckSequence;
uint32_t drainSequence;
{
Mutex::Autolock _l(mLock);
// 如果mWriteAckSequence末位为1 或者 mDrainSequence为1 并且已经被唤醒
// 当底层有空间的时候,这个线程就会被触发唤醒,继续走下面的逻辑
while (!((mWriteAckSequence & 1) ||
(mDrainSequence & 1) ||
exitPending())) {
mWaitWorkCV.wait(mLock);
}
// mWriteAckSequence、mDrainSequence 将末位进行清0,因为在threadloop_write的时候会将其+2 | 1 并且左移1位, 被唤醒之后,该值又被 |1 。
writeAckSequence = mWriteAckSequence;
// 对callback线程中的mWriteAckSequence、mDrainSequence进行末尾清零操作
mWriteAckSequence &= ~1;
drainSequence = mDrainSequence;
mDrainSequence &= ~1;
}
{
sp playbackThread = mPlaybackThread.promote();
if (playbackThread != 0) {
// 如果之前block住了,那就进行resetWriteBlocked,这个将会触发offloadThread线程从wait进入到wake up的状态。
if (writeAckSequence & 1) {
// 右移才是offloadThread中mWriteAckSequence的值
playbackThread->resetWriteBlocked(writeAckSequence >> 1);
}
if (drainSequence & 1) {
playbackThread->resetDraining(drainSequence >> 1);
}
}
}
}
return false;
}
看下resetWriteBlocked
的实现
void AudioFlinger::PlaybackThread::resetWriteBlocked(uint32_t sequence)
{
Mutex::Autolock _l(mLock);
// sequence这个值必须跟offloadThread中的mWriteAckSequence相等,才会触发唤醒操作。
if ((mWriteAckSequence & 1) && (sequence == mWriteAckSequence)) {
// 末尾清零,唤醒offloadThread线程
mWriteAckSequence &= ~1;
mWaitWorkCV.signal();
}
}
上面的sequence巧妙的利用二进制来实现开关的操作,表示进入和退出的效果,并且保证sequence的值一直处在递增可以排重。
下图描述了offloadThread跟asynccallbackThread之间的调用逻辑:
下面在贴几张高通的文档中比较粗泛的图视:
framework、hal、kernel的框图:
offload的初始化:
offload的playback:
offload的seek:
#Android #Audio #Offload