谷歌提供一个机制, 就是 OutputConfiguration 的 enableSurfaceSharing
, 这个功能打开之后, 该 OutputConfiguration 之内的 若干个 Surface, 就可以使用同一个 Buffer 进行显示, 也就是说这几个 Surface 会显示相同的内容。
这个机制是怎么实现的呢? 就是通过 Camera3SharedOutputStream
来实现的。 我们来看一下具体流程。
首先有几个概念需要牢记:
- 每个 OutputConfiguration 都会 对应一路流, 对应一个 StreamId, 不论这个 OutputConfiguration里面包含多少个Surface, 对应的都是同一个StreamId.
也就是说同一个StreamId 会对应多个 SurfaceId, 这种对应关系在createStream
的时候会被保存起来。 另外在getBufferLocked
和returnBufferLocked
的时候也会携带上这种对应关系。
首先在 Camera3Device
的 createStream
方法中, 在创建各种 OutputStream 的时候会进行判断, 如果打开了 isSharing
, 就会创建 Camera3SharedOutputStream
, 默认情况下会创建 Camera3OutputStream
.
Camera3SharedOutputStream
是 Camera3OutputStream
的子类, 只重写了几个关键方法, 最重要的就是这两个方法:getBufferLocked
, queueBufferToConsumer
, 一个是获取Buffer交给Provider生产, 一个是把生产好的Buffer返回给BufferQueue.
Camera3SharedOutputStream 的主要工作都是交给 Camera3StreamSplitter
来做的。
总体的思路是: 在 Camera3StreamSpliter 里面新建一个 BQ, 从这个BQ中取出 Buffer 交给Provider生产。 在生产好之后, 把这个Buffer 给 attach 到所有的 surface_ids
中。
为了方便, 把 新建的 BQ 成为 Inner BQ, 把 OutputConfiguration 中的 BQ 称为 Outter BQ.
下面讲一下详细流程(假设使用了Hal Buffer Manager)
- Camera3SharedOutputStream 在
configureQueueLocked
中调用connectStreamSplitterLocked
, 最终调用到 Camera3StreamSplitter 的connect
方法。 -
Camera3StreamSplitter
的connect
方法会 创建一个新的 BQ, 并且保存起来 BQ 的生产端 mProducer 和 消费端 mConsumer. 并且会把 mProducer 生成 sp赋值给Camera3OutputStream 的 mConsumer
变量。 这里我们一定要记住的是mConsumer
是 Camera3OutputStream 里面非常重要的一个变量, Camera3OutputStream 使用这个变量来dequeueBuffer
和queueBuffer
.
由于我们把Inner BQ 的 mProducer 赋值给了 Camera3OutputStream 的 mConsumer 变量, 所以 在getBufferLocked
就会从 Inner BQ 中获取 Buffer. - 接下来在 Camera3SharedOutputStream 中 的
getBufferLocked
方法中, 会从 Inner BQ中申请出来一个 Buffer, 交给 Provider 去生产。 - 接下来就是在
queueBufferToConsumer
中, 调用attachBufferToSplitterLocked
方法, 然后调用到Camera3StreamSplitter的attachBufferToOutputs
, 这个方法的作用就是 把 这个 Buffer 给 attach 到每一个 Outter BQ 中。
我们先详细讲一下attachBufferToOutputs
的流程, Splitter 里面有几个数据结构要注意:
- BufferTracker, 记录 Buffer 和 Outter Surface的对应关系
- mOutputs 记录 surfaceId 和 Outter Surface的对应关系
- mOutputSlots 记录 Outter Surface 和 占用的 Slot(这是个Vector) 的对应关系
- mBuffers: 记录 Buffer 和 BufferTracker 的对应关系
- mDetachedBuffers 记录所有需要Detach的Buffer.
- mInputSlots 记录Buffer Id 和 BufferItem 的对应关系
记下来看attachBufferToOutputs
的流程:
4.1 生成一个 BufferTrakcer, 记录了该 Buffer 以及需要该Buffer的所有 Outter Surface.
4.2 遍历每一个Outter Surface 调用getSlotForOutputLocked
, 该方法的作用是:看一下 mOutputSlots 中 该 Outter Surface 是否保存过该 Buffer, 如果保存过该Buffer, 就返回对应的Vector下标, 否则就返回 INVALID_BUFFER_SLOT.
4.3 如果Outter Surface已经保存过这个Buffer了, 那么就遍历下一个Outter Surface(这说明该Buffer已经在到 Outter BQ中了)。 如果没有保存过, 就把该 Buffer 给 attachBuffer
到 Outter BQ 中, 然后会返回一个 slot, 这个slot是 Outter Buffer 中对应的槽位。
4.4 看一下返回的这个槽位 在 Outter Surface 对应的 Vector 中是否有 GB, 如果有GB, 说明什么? 说明原来 Vector 对应的 槽位上是一个旧Buffer, 调用 decrementBufRefCountLocked
(这个方法稍后会讲)去清理一下和旧Buffer相关的数据结构, 然后把这个槽位上更新为新的Buffer.
4.5 然后把这个 buffer 和对应的 BufferTracker 保存到 mBuffers 中。
我们在看一下decrementBufRefCountLocked
, 这个方法的主要作用是:
4.6 首先调用对应 BufferTracker 的 decrementReferenceCountLocked
方法, 这个方法的主要作用 就是 BufferTracker 中移除这个SurfaceId, 并降低计数。
BufferTracker
的作用 就是 记录同一个Buffer, 还没有被哪些 Outter Surface消费。 每个 Outter Surface消费完都需要从 BufferTracker 中清除自己。
4.7 如果 BufferTracker
的计数已经到 0 了,说明这个 Buffer 已经被所有的 OutterSurface 消费完毕了, 这个 BufferTracker 要从 mBuffers
中移除。
4.8 看一下 mInputSlots 中是否包含这个 buffer id。 如果没有包含就说明这个Buffer 应该是一个新的Buffer, 啥都不需要做。 如果包含了, 就说明这个 Buffer 和前面某一次Provider传过来的Buffer是同一个, 这个时候就需要做一些清理工作了: 看一下 mDetachedBuffers 中是否包含这个Buffer, 如果包含(如果一个Shared Surface被移除了,就会把这个Surface对应的 mOutputSlots里面的所有Buffer都用Outter Surface给 detachBuffer掉, 如果detachBuffer失败, 就放入mDetachedBuffers中), 就把 这个Buffer 从 mDetachedBuffers 和 mInputSlots 中移除,并且 调用 Inner Consumer 的 detachBuffer
(如果mDetachedBuffers中包含)或者releaseBuffer
方法。
这里就是问题了, 如果 Inner Consumer调用了 detachBuffer, 并且我们重写了onBufferDetached, 把 Inner Surface中的引用清空, 会出现什么结果?
主要思想就是: 当有新的Buffer从Provider过来的时候, 尝试去往每个 Outter Surface中attach,
如果之前这个Buffer没有被Outter Surface attach过, 就可以成功。 如果attach过(根据mOutputSlots来判断), 就不会再次attach(这个时候Outter BQ中已经有这个Buffer了, Buffer的内容肯定被更新成最新的了。为啥以前一个Buffer被attach到OutterBQ中,但直到新的同一个Buffer到来, 同一个Buffer还一直在 mOutputSlots中呢?)。 attach成功之后, 看一下这个 槽位 是不是已经在 mOutputSlots 里面了, 如果在的话, 说明复用了原来的槽位(可能Outter BQ已经消费过这个 槽位了, 又重新复用了这个槽位。 但是为啥消费过了, 这个槽位还在mOutputSlots中呢?)
这样就把 所有和旧Buffer相关的数据结构的内容的清理了(mInputSlots
和 mDetachedBuffers
), 或者给更新成新内容了(mOutputSlots
, mBuffers
)
- 然后 Inner Surface 会调用
queueBuffer
把 该 Buffer 放入 Inner BQ 中, 这个时候Camera3StreamSplitter
的onFrameAvailable
就会被调用。 然后 Inner Consumer会调用acquireBuffer
获取到这个 Buffer, 也就是 BufferItem. 然后把这个 BufferItem 放入 mInputSlots 中。 - 对于每个 Outter Surface, 调用
outputBufferLocked
方法, 下面详细讲一下outputBufferLocked
方法
6.1 先调用getSlotForOutputLocked
得到 Slot, 还记得我们在4.2
中attachBuffer
之前会先得到一个Slot吗, 这个Slot 是 Outter BQ 中的槽点。
6.2 调用 Outter Producer 的queueBuffer
, 将 Buffer 放入对应的槽点。如果失败了就调用decrementBufRefCountLocked
看一下要不要清理
这样每一个 Outter Consusmer 都可以消费这个 Buffer了。
看一下消费完这个 Buffer 之后的处理。
每一个 Outter Consumer 消费完之后, 会回调到Camera3StreamSplitter
中的 OutputListener
的 onBufferReleased
方法中, 最终调用到 onBufferReleasedByOutput
.
接下来是很诡异的一个操作, 百思不得其解。它会先 调用 Outter Producer 的 dequeueBuffer
出来, 得到这个Buffer的Slot. 为什么要先 dequeue 一个槽位? 由于 Outter BQ不会去开辟Buffer, 这个 dequeueBuffer
仅仅是把一个槽位设置为 Dequeued
状态, 而且这个槽位 肯定是从 mFreeBuffers
中来的
然后调用returnOutputBufferLocked
, 我们看一下这个方法
7.1 首先从 mOutputSlots
中取出对应 Outter Producer所有的 Buffer. 还记的这些Buffer是从哪里来的吗? 是 Inner Consumer在调用 attachBufferToOutputs
中放入的, 也就是说这些Buffer其实都是从 Inner BQ来的, 是被填充过的。
这里没有对这个Buffer进行任何非空判断, 就直接使用了, 这是为什么呢?
我们在梳理一下流程, 在 Outter Consumer 调用了release方法之后, 这个 Buffer 会被放到 Outter BQ的 mFreeBuffers
中, 而dequeueBuffer
会先从 mFreeBuffers
中取出来Buffer, 所以会取到。
那会不会出现, dequeueBuffer
从mFreeSlots
中取值的情况呢?
因为 对于 Outter BQ来说, 采用的是attach&queue
的方式,dequeueBuffer
是在release
的时候才会调用, 所以 dequeueBuffer
的调用肯定是小于 attach&queue
的, 不可能出现 attach&queue
一次, 但是dequeueBuffer
两次的情况。
7.2 从mOutputSlots
中取出来 Buffer之后, 然后继续从 mBuffers
中取出来 BufferTrack
.
还记的我们在attach&queue
流程中更新了两个数据结构,就是mBuffers
和mOutputSlots
,
接下来就是要对这两个进行清理了。
7.3 如果 mDetachedBuffers 中包含了这个 Buffer, 说明这个 Outter Surface被从 OutputConfiguration中移除了, 那么我们就 调用 Outter Producer的 detachBuffer
, 然后清空一下mOutputSlots
.
为啥这种情况下会调用一下detachBuffer
呢? 因为detachBuffer
会把 Buffer 放到 mFreeSlots
中, 并且清空这个Buffer.
7.4 调用 decrementBufferRefCountLocked
, 这个前面已经讲过了。
这里最后为什么要dequeueBuffer
一下呢? 这样可以确保 这个 Buffer 不会被 Outter BQ复用,防止在 Buffer 的生命周期旗舰, 某一个 Outter BQ 乱用这个 Buffer, 导致出问题。
这样会导致 Outter BQ 中多出一个处于 Dequeued
状态的 Buffer, 是的, 拿什么时候多出的这个Dequeue 状态的Buffer会被清除呢?
当所有的 Outter BQ 都用完这个 Buffer 之后? 具体流程没有找到。。。。应该是 4.3 那一步有关系。
从上面的解析来看, enableSurfaceSharing是完全不考虑帧率的...