高通audio offload学习
文章目录
【注意】最后更新于 September 23, 2016,文中内容可能已过时,请谨慎使用。
简述
offload在音频系统里面,就是将对于音频文件的解码操作过载到DSP中去做,比如说mp3的解码操作不是在mediaserver中来做,而是直接将数据传递到dsp中,由DSP去解码和播放,就是所谓的硬解。硬解的优势是省功耗,除了DSP自身在处理音频格式的时候功耗相对较低,整个数据在安卓的音频系统中传递也会相对低些,前面那个好理解,后面这个我们结合代码来分析。
正文
这次的场景,就是在高通平台通过mediaplayer进行播放mp3.其中audiotrack中采用到了callback的方式。
audiotrack部分
我们从代码的角度一步步分析。
摘取一些audiotrack中set
中的代码:
1 2 3 4 5 |
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,并且让它跑起来了。来看下线程的逻辑处理函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
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
中,我们可以回头来看下这个函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
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<AudioTrackClientProxy> 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
中的部分代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 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部分的交互,可以参考下图片。
audioflinger部分
这部分就是生产者与消费者模式中的消费者了,我们将从线程创建开始分析,再看AsyncCallbackThread的逻辑,不过我们只看跟普通线程的区别的部分。但是其实消费者的主要的逻辑确实在threadloop
函数中,这个我们将只用图来描述,因为逻辑较为简单。
offloadThread创建
offloadThread线程的创建,其实依赖于audio_policy.conf中是否有配置,如果有配置,则才会有创建该线程的逻辑。 offload跟mixer线程的区别,其实主要是基本上没有mixer的处理,直接绕过audioflinger的采样率、format、采样精度、通道数等的转变,所以其实offload的逻辑是更简单的。
不过我们这篇重点在讲offload的callback的实现逻辑,直接引用PlaybackThread::readOutputParameters_l
中的部分代码,这边有创建callback的线程
1 2 3 4 5 6 7 8 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
操作之后,如果打算写到底层的数据量跟真实写入的值相等的话,还会再调用一次这个函数。
1 2 3 4 5 |
void AudioFlinger::AsyncCallbackThread::setWriteBlocked(uint32_t sequence) { Mutex::Autolock _l(mLock); mWriteAckSequence = sequence << 1; } |
这个是线程的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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<AudioFlinger::PlaybackThread> 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
的实现
1 2 3 4 5 6 7 8 9 10 |
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:
文章作者 Spreading
上次更新 2016-09-23