2018年1月9日 星期二

從 BootAnimation 探索 SurfaceFlinger (二) 異世界亦有著供需市場

前情提要

勇者喵不小心落入了異世界, 身無分文的牠只好開始打工還債...

我們上回說到的 Producer 跟 Consumer 是一個很重要的結構,
這裡的 Producer 跟 Consumer 有時候不一定是單純扮演生產者的角色.
為什麼這麼說呢?

晚點你就會知道了 σ ゚∀ ゚) ゚∀゚)σ

我們先看看這整段在幹嘛:
void Layer::onFirstRef() {
    // Creates a custom BufferQueue for SurfaceFlingerConsumer to use
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
    BufferQueue::createBufferQueue(&producer, &consumer);
    mProducer = new MonitoredProducer(producer, mFlinger);
    mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName);
    mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(0));
    mSurfaceFlingerConsumer->setContentsChangedListener(this);
    mSurfaceFlingerConsumer->setName(mName);

    mSurfaceFlingerConsumer->setDefaultMaxBufferCount(3);

    const sp<const DisplayDevice> hw(mFlinger->getDefaultDisplayDevice());
    updateTransformHint(hw);
}

好的, 看起來很正常
不就是個生產者跟消費者嗎?

我們先從第三行展開看看:
void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
        sp<IGraphicBufferConsumer>* outConsumer,
        const sp<IGraphicBufferAlloc>& allocator) {

    sp<BufferQueueCore> core(new BufferQueueCore(allocator));

    sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core));

    sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));

    *outProducer = producer;
    *outConsumer = consumer;
}

因為沒有帶入 allocator, 預設會是 null
    static void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
            sp<IGraphicBufferConsumer>* outConsumer,
            const sp<IGraphicBufferAlloc>& allocator = NULL);

在BufferQueueCore裡面會再自動生成 allocator
BufferQueueCore::BufferQueueCore(const sp<IGraphicBufferAlloc>& allocator) :
    mAllocator(allocator),
    mMutex(),
    mIsAbandoned(false),
    mConsumerControlledByApp(false),
    mConsumerName(getUniqueName()),
    mConsumerListener(),
    mConsumerUsageBits(0),
    mConnectedApi(NO_CONNECTED_API),
    mConnectedProducerListener(),
    mSlots(),
    mQueue(),
    mFreeSlots(),
    mFreeBuffers(),
    mOverrideMaxBufferCount(0),
    mDequeueCondition(),
    mUseAsyncBuffer(true),
    mDequeueBufferCannotBlock(false),
    mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
    mDefaultWidth(1),
    mDefaultHeight(1),
    mDefaultBufferDataSpace(HAL_DATASPACE_UNKNOWN),
    mDefaultMaxBufferCount(2),
    mMaxAcquiredBufferCount(1),
    mBufferHasBeenQueued(false),
    mFrameCounter(0),
    mTransformHint(0),
    mIsAllocating(false),
    mIsAllocatingCondition(),
    mAllowAllocation(true),
    mBufferAge(0),
    mGenerationNumber(0)
{
    if (allocator == NULL) {
        sp<ISurfaceComposer> composer(ComposerService::getComposerService());
        mAllocator = composer->createGraphicBufferAlloc();
        if (mAllocator == NULL) {
            BQ_LOGE("createGraphicBufferAlloc failed");
        }
    }
    for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
        mFreeSlots.insert(slot);
    }
}

還記得 ComposerSerivce::getComposerService() 會取得的對象是 SurfaceFlinger 吧?
sp<IGraphicBufferAlloc> SurfaceFlinger::createGraphicBufferAlloc()
{
    sp<GraphicBufferAlloc> gba(new GraphicBufferAlloc());
    return gba;
}     

這個GraphicBufferAlloc現在不會做事, 不過它只有一個Function叫做createGraphicBuffer, 我們先貼出來看看:
GraphicBufferAlloc::GraphicBufferAlloc() {
}
            
GraphicBufferAlloc::~GraphicBufferAlloc() {
}
    
sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t width,
        uint32_t height, PixelFormat format, uint32_t usage, status_t* error) {
    sp<GraphicBuffer> graphicBuffer(
            new GraphicBuffer(width, height, format, usage));
    status_t err = graphicBuffer->initCheck();
    *error = err;
    if (err != 0 || graphicBuffer->handle == 0) {
        if (err == NO_MEMORY) {
            GraphicBuffer::dumpAllocationsToSystemLog();
        }
        ALOGE("GraphicBufferAlloc::createGraphicBuffer(w=%d, h=%d) "
             "failed (%s), handle=%p",
                width, height, strerror(-err), graphicBuffer->handle);
        return 0;
    }
    return graphicBuffer;
}

這裡會 new 出一個 GraphicBuffer 結構,
負責敘述長寬, 然後又透過了 GraphicBufferAllocator 來 alloc 一塊 buffer
(注意, 不是GraphicBufferAlloc, 是Allocator) (取這麼像是要害死誰)
GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage)
    : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
      mInitCheck(NO_ERROR), mId(getUniqueId())
{
    width  =
    height =
    stride =
    format =
    usage  = 0;
    handle = NULL;
    mInitCheck = initSize(inWidth, inHeight, inFormat, inUsage);
}

status_t GraphicBuffer::initSize(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage)
{
    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
    uint32_t outStride = 0;
    status_t err = allocator.alloc(inWidth, inHeight, inFormat, inUsage,
            &handle, &outStride);
    if (err == NO_ERROR) {
        width = static_cast<int>(inWidth);
        height = static_cast<int>(inHeight);
        format = inFormat;
        usage = static_cast<int>(inUsage);
        stride = static_cast<int>(outStride);
    }
    return err;
}   

前面都沒有真的去生成一塊 buffer, 現在總該是它了吧?
我們看一下 GraphicBufferAllocator 在幹嘛:
GraphicBufferAllocator::GraphicBufferAllocator()
    : mAllocDev(0)
{
    hw_module_t const* module;
    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
    ALOGE_IF(err, "FATAL: can't find the %s module", GRALLOC_HARDWARE_MODULE_ID);
    if (err == 0) {
        gralloc_open(module, &mAllocDev);
    }
}

status_t GraphicBufferAllocator::alloc(uint32_t width, uint32_t height,
        PixelFormat format, uint32_t usage, buffer_handle_t* handle,
        uint32_t* stride)
{
    ATRACE_CALL();

    // make sure to not allocate a N x 0 or 0 x N buffer, since this is
    // allowed from an API stand-point allocate a 1x1 buffer instead.
    if (!width || !height)
        width = height = 1;

    // we have a h/w allocator and h/w buffer is requested
    status_t err;

    // Filter out any usage bits that should not be passed to the gralloc module
    usage &= GRALLOC_USAGE_ALLOC_MASK;

    int outStride = 0;
    err = mAllocDev->alloc(mAllocDev, static_cast<int>(width),
            static_cast<int>(height), format, static_cast<int>(usage), handle,
            &outStride);
    *stride = static_cast<uint32_t>(outStride);

    ALOGW_IF(err, "alloc(%u, %u, %d, %08x, ...) failed %d (%s)",
            width, height, format, usage, err, strerror(-err));

    if (err == NO_ERROR) {
        Mutex::Autolock _l(sLock);
        KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
        uint32_t bpp = bytesPerPixel(format);
        alloc_rec_t rec;
        rec.width = width;
        rec.height = height;
        rec.stride = *stride;
        rec.format = format;
        rec.usage = usage;
        rec.size = static_cast<size_t>(height * (*stride) * bpp);
        list.add(*handle, rec);
    }

    return err;
}

呀...終於跟 HW 牽扯上了一些關係,
這裡去開啟了你的 Gralloc HW module, 這個 module 會負責去獲取 vendor拿來處理影像 的buffer
這是因為處理影像使用到的硬件通常是跟 vendor 具有非常強的相關性,
且獲取到的 memory 並不一定是可以被 user space access 到的.
在獲取 memory 時, 我們帶入的 usage flags 會包含下列幾點:

  • 這塊 Memory 有多常會被 Software (CPU) 存取
  • 這塊 Memory 有多常會被 Hardware (GPU) 存取
  • 這塊 Memory 會被作為 OpenGL ES(GLES) 的紋理 (texture) 使用嗎?
  • 這塊 Memory 會被 Video Encoder 使用嗎?

舉例來說, 我們帶入 format 為 RBGA 8888, 並且表明這塊 buffer 會被 Software (ex. 你的Application) 使用,
那麼你的 Allocator 就一定要生出一塊 buffer, 提供 4 bytes per pixels in R-G-B-A order.
反過來說, 假設你表明這塊 buffer 只會被 Hardware 並且作為 GLES texture 使用, 那麼 allocator 就可以做任何 GLES driver 想要的 ...
例如把 順序改為 BGRA ordering, non-linear swizzled layout, 或者是支援更多的 color formats.
(讓 Hardware 使用它 preferred 的格式通常可以帶來更高的 performance)

好, 有點扯遠了. 我們先回到前面的 BufferQueueCore, 才剛講到
mAllocator = composer->createGraphicBufferAlloc();
這裡生出了一個 gba, 但它甚至還沒有去獲取 buffer
下面的 mFreeSlots 目前不知道做甚麼的, 不過看起來就是做一下初始化,
這個 mFreeSlots 是一個 set, 先把每一個 Slots (0~63) 都先塞入了 mFreeSlots
應該是表示目前的 Queue 都是空的.

再回到上一層的 BufferQueue::createBufferQueue(), 接下來要處理 producer & consumer

    sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core));
    
    sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));

這個 sp 指標可以想成是 IGraphicBufferProducer *producer, 只是多了智慧管理memory來防止memory leak
BufferQueueProducer & BufferQueueConsumer 的建構子如下:

BufferQueueProducer::BufferQueueProducer(const sp<BufferQueueCore>& core) :
    mCore(core),
    mSlots(core->mSlots),
    mConsumerName(),
    mStickyTransform(0),
    mLastQueueBufferFence(Fence::NO_FENCE),
    mCallbackMutex(),
    mNextCallbackTicket(0),
    mCurrentCallbackTicket(0),
    mCallbackCondition() {}

BufferQueueConsumer::BufferQueueConsumer(const sp<BufferQueueCore>& core) :
    mCore(core),
    mSlots(core->mSlots),
    mConsumerName() {}

看得出來比較重要的應該是 mCore 跟 mSlots 對象
查找一下 mSlots 的說明如下:
BufferQueueDefs::SlotsType mSlots;
它是一個 buffer slots 的陣列, 是 producer side 的鏡象.
這個陣列使得 producer 跟 consumer 可以切換 buffer 的所有權,
不需要透過 Binder 來交換 GraphicBuffer.
一開始全部的 array 會被初始化成 NULL,
並且會在呼叫 requestBuffer 的時候根據 slot index 生成.
這邊的 requestBuffer 當然是 BufferQueueProducer 呼叫到的, 稍後我們會看到.

這裡 createBufferQueue 的工作就結束了, 這裡我們先回顧一下:
上一篇的最後, BootAnimation 透過 SurfaceComposerClient 對 SurfaceFlinger 說:
幫我生個孩子吧
嗯抱歉, 是幫我生塊 Surface 吧 (createSurface)
之後一路呼叫到了 SurfaceFlinger::createNormalLayer.
對 Layer (Application) 來說, 它並不是那麼在意 buffer 是怎麼生成的,
對它來說最終拿到的會是 producer & consumer.

接下來, Layer 又把 producer 跟 consumer 分別丟給了兩位社畜

void Layer::onFirstRef() {
    // Creates a custom BufferQueue for SurfaceFlingerConsumer to use
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
    BufferQueue::createBufferQueue(&producer, &consumer);
    mProducer = new MonitoredProducer(producer, mFlinger);
    mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName);
    mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(0));
    mSurfaceFlingerConsumer->setContentsChangedListener(this);
    mSurfaceFlingerConsumer->setName(mName);
    
    mSurfaceFlingerConsumer->setDefaultMaxBufferCount(3);

    const sp<const DisplayDevice> hw(mFlinger->getDefaultDisplayDevice());
    updateTransformHint(hw);
}

MonitoredProducer 看起來只是封裝了一層介面去呼叫 producer,
最大的差別應該是他在 destructure 裡面封裝了一個 Message 去通知 SurfaceFlinger 清除掉 mProducer

MonitoredProducer::MonitoredProducer(const sp<IGraphicBufferProducer>& producer,
        const sp<SurfaceFlinger>& flinger) :
    mProducer(producer),
    mFlinger(flinger) {}

MonitoredProducer::~MonitoredProducer() {
    // Remove ourselves from SurfaceFlinger's list. We do this asynchronously
    // because we don't know where this destructor is called from. It could be
    // called with the mStateLock held, leading to a dead-lock (it actually
    // happens).
    class MessageCleanUpList : public MessageBase {
    public:
        MessageCleanUpList(const sp<SurfaceFlinger>& flinger,
                const wp<IBinder>& producer)
            : mFlinger(flinger), mProducer(producer) {}

        virtual ~MessageCleanUpList() {}
    
        virtual bool handler() {
            Mutex::Autolock _l(mFlinger->mStateLock);
            mFlinger->mGraphicBufferProducerList.remove(mProducer);
            return true;
        }

    private:
        sp<SurfaceFlinger> mFlinger;
        wp<IBinder> mProducer;
    };

    mFlinger->postMessageAsync(new MessageCleanUpList(mFlinger, asBinder(this)));
}
(但是怎麼不放在 Layer  的解構就好了...ಠ_ಠ? 你媽知道你在這裡耍廢嗎)

另外一邊, SurfaceFlingerConsumer 看起來就有點料了.
它是一個繼承自 GLConsumer 的 class

GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex,
        uint32_t texTarget, bool useFenceSync, bool isControlledByApp) :
    ConsumerBase(bq, isControlledByApp),
    mCurrentTransform(0),
    mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
    mCurrentFence(Fence::NO_FENCE),
    mCurrentTimestamp(0),
    mCurrentFrameNumber(0),
    mDefaultWidth(1),
    mDefaultHeight(1),
    mFilteringEnabled(true),
    mTexName(tex),
    mUseFenceSync(useFenceSync),
    mTexTarget(texTarget),
    mEglDisplay(EGL_NO_DISPLAY),
    mEglContext(EGL_NO_CONTEXT), 
    mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
    mAttached(true)
{         
    GLC_LOGV("GLConsumer");
    
    memcpy(mCurrentTransformMatrix, mtxIdentity,
            sizeof(mCurrentTransformMatrix));

    mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
}

GLConsumer 又繼承自 ConsumerBase

ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :
        mAbandoned(false),
        mConsumer(bufferQueue) {
    // Choose a name using the PID and a process-unique ID.
    mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());

    // Note that we can't create an sp<...>(this) in a ctor that will not keep a
    // reference once the ctor ends, as that would cause the refcount of 'this'
    // dropping to 0 at the end of the ctor.  Since all we need is a wp<...>
    // that's what we create.
    wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
    sp<IConsumerListener> proxy = new BufferQueue::ProxyConsumerListener(listener);

    status_t err = mConsumer->consumerConnect(proxy, controlledByApp);
    if (err != NO_ERROR) {
        CB_LOGE("ConsumerBase: error connecting to BufferQueue: %s (%d)",
                strerror(-err), err);
    } else {
        mConsumer->setConsumerName(mName);
    }
}

在 ConsumerBase 裡面, 實作了一個 listener 的機制
這裡會再跟 BufferQueue 牽扯上關係, 呼叫的是 ProxyConsumerListener,
並將自己轉化為 listener 對象作為參數帶入.

我們看一下 ProxyConsumerListener 是做甚麼用的:
BufferQueue::ProxyConsumerListener::ProxyConsumerListener(
        const wp<ConsumerListener>& consumerListener):
        mConsumerListener(consumerListener) {}

void BufferQueue::ProxyConsumerListener::onFrameAvailable(
        const BufferItem& item) {
    sp<ConsumerListener> listener(mConsumerListener.promote());
    if (listener != NULL) {
        listener->onFrameAvailable(item);
    }
}

看起來只是呼叫了 ConsumerBase, 這裡我們列出來

void ConsumerBase::onFrameAvailable(const BufferItem& item) {
    CB_LOGV("onFrameAvailable");

    sp<FrameAvailableListener> listener;
    { // scope for the lock
        Mutex::Autolock lock(mMutex);
        listener = mFrameAvailableListener.promote();
    }

    if (listener != NULL) {
        CB_LOGV("actually calling onFrameAvailable");
        listener->onFrameAvailable(item);
    }
}

// mListener 是 setFrameAvailableListener 帶入的
void ConsumerBase::setFrameAvailableListener(
        const wp<FrameAvailableListener>& listener) {
    CB_LOGV("setFrameAvailableListener");
    Mutex::Autolock lock(mMutex);
    mFrameAvailableListener = listener;
}

那麼 ProxyComuserListener 又是怎麼觸發的呢?
我們先回到 ConsumerBase, 這裡呼叫的是
status_t err = mConsumer->consumerConnect(proxy, controlledByApp);

而 mConsumer 是誰呢?
就是我們一開始透過 BufferQueue::createBufferQueue(&producer, &consumer); 所生成 consumer 啦 (BufferQueueConsumer)

回頭看一下 BufferQueueConsumer
virtual status_t consumerConnect(const sp<IConsumerListener>& consumer,
        bool controlledByApp) {
    return connect(consumer, controlledByApp);
}

status_t BufferQueueConsumer::connect(
        const sp<IConsumerListener>& consumerListener, bool controlledByApp) {
    ATRACE_CALL();

    if (consumerListener == NULL) {
        BQ_LOGE("connect(C): consumerListener may not be NULL");
        return BAD_VALUE;
    }
    
    BQ_LOGV("connect(C): controlledByApp=%s",
            controlledByApp ? "true" : "false");
    
    Mutex::Autolock lock(mCore->mMutex);

    if (mCore->mIsAbandoned) {
        BQ_LOGE("connect(C): BufferQueue has been abandoned");
        return NO_INIT;
    }

    mCore->mConsumerListener = consumerListener;
    mCore->mConsumerControlledByApp = controlledByApp;

    return NO_ERROR;
}

只是把 mCore->mConsumerListener 設置成了 consumerListener
那應該會有人來呼叫 consumerListener 對吧?
到底是誰呢?

聰明的你, 一定猜到了對吧 (*ˇωˇ*人)
沒錯, 就是 BufferQueueProducer 啦

回頭搜尋一下, 你會看到在 status_t BufferQueueProducer::queueBuffer() 這個函數裡面 (我們先不管到底是誰去呼叫它的)
// 抱歉這函數真的超長, 容我先列出一小段
status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {
    ...
    {
        frameAvailableListener = mCore->mConsumerListener;
    }
    ...
    {
     ...
        if (frameAvailableListener != NULL) {
            frameAvailableListener->onFrameAvailable(item);
        } else if (frameReplacedListener != NULL) {
            frameReplacedListener->onFrameReplaced(item);
        }
        ...
    }
}

這裡的 frameAvailableListener, 實際上是 proxy 對象喔 (BufferQueue::ProxyConsumerListener)
接著 proxy 對象會呼叫它自己的 mConsumerListener, 也就是 ConsumerBase
ConsumerBase::onFrameAvailable() 又呼叫了 mFrameAvailableListener->onFrameAvailable()

mFrameAvailableListener 對象是透過 setFrameAvailableListener 帶進來的
在我們的案例裡就是 SurfaceFlingerConsumer::setContentsChangedListener
void SurfaceFlingerConsumer::setContentsChangedListener(
        const wp<ContentsChangedListener>& listener) {
    setFrameAvailableListener(listener);
    Mutex::Autolock lock(mMutex);
    mContentsChangedListener = listener;
}

往上看一下 Layer::onFirstRef(), 這裡的 listener, 實際上就是 Layer 自己
收到了 onFrameAvailable 之後, Layer 會通知 SurfaceFlinger 說要更新啦

void Layer::onFrameAvailable(const BufferItem& item) {
    // Add this buffer from our internal queue tracker
    { // Autolock scope
        Mutex::Autolock lock(mQueueItemLock);

        // Reset the frame number tracker when we receive the first buffer after
        // a frame number reset
        if (item.mFrameNumber == 1) {
            mLastFrameNumberReceived = 0;
        }

        // Ensure that callbacks are handled in order
        while (item.mFrameNumber != mLastFrameNumberReceived + 1) {
            status_t result = mQueueItemCondition.waitRelative(mQueueItemLock,
                    ms2ns(500));
            if (result != NO_ERROR) {
                ALOGE("[%s] Timed out waiting on callback", mName.string());
            }
        }

        mQueueItems.push_back(item);
        android_atomic_inc(&mQueuedFrames);

        // Wake up any pending callbacks
        mLastFrameNumberReceived = item.mFrameNumber;
        mQueueItemCondition.broadcast();
    }

    mFlinger->signalLayerUpdate();
}

...所以這些人到底在幹嘛?
 (」・ω・)」SAN値!(/・ω・)/ピンチ!

再來回顧一下, 差不多該喝杯茶了

Layer:
1.) 透過 BufferQueue 生成了 producer (BufferQueueProducer) 跟 consumer (BufferQueueConsumer)
2.) 把 producer 交給 MonitoredProducer, 它自己的 mProducer 是 MonitoredProducer
3.) 把 consumer 交給 SurfaceFlingerConsumer
4.) 把自己作為 listener 設給 SurfaceFlingerConsumer

BufferQueueCore:
負責跟 Gralloc HW Module 要 buffer, 作為 Producer 跟 Consumer 的橋樑

BufferQueueProducer:
目前只知道他會透過 mSlots 跟 BufferQueueConsumer 溝通

MonitoredProducer:
在解構子封裝一層移除 Producer 的功能, 沒惹

SurfaceFlingerConsumer:
繼承了 GLConsumer, 帶入 consumer

GLConsumer:
繼承了 ConsumerBase, 帶入 consumer

ConsumerBase:
1.) 生成一個 BufferQueue::ProxyConsumerListener, 並把 this (SurfaceFlingerConsumer) 帶入
2.) 將 proxy 交給 BufferQueueConsumer 作為它的 mListener
3.) 收到 onFrameAvailable 時, 通知自己的 mFrameAvailableListner 對象 (Layer)

BufferQueueConsumer:
當 BufferQueueProducer::queueBuffer 時, 通知它的 mConsumerListener
(BufferQueue::ProxyConsumerListener)

BufferQueue::ProxyConsumerListener:
收到 onFrameAvailable 時, 通知自己的 mConsumerListener 對象
(SurfaceFlingerConsumer)
(備註, 其實我不太懂為什麼要多一層 ProxyConsumerListener, 感覺可以直接把 ConsumerBase 的 this 丟給 BufferQueueConsumer 就好...)

下一篇, 我們再來看看 BufferQueueProducer 是怎麼推動的
(¦3[▓▓]

沒有留言:

張貼留言

不定參數印 log

From the UNIXProcess_md.c #ifdef DEBUG_PROCESS   /* Debugging process code is difficult; where to write debug output? */ static void deb...