简述

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部分通过线程获取数据的逻辑了。obtainBufferreleaseBuffer可以去参考下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之间的调用逻辑:

offload write

下面在贴几张高通的文档中比较粗泛的图视:

framework、hal、kernel的框图: offload write offload的初始化: offload write

offload的playback: offload write offload的seek: offload write