1 前言
在本篇中,我們將關注Codec 2.0以下幾個問題:
1.從頂而下,一個解碼組件是如何創建的
2.組件的接口有哪些,分別是什麼含義
3.組件是如何運行的,輸入與輸出的數據流是怎樣的
2 組件的創建
CCodec在allocate中,通過CreateComponentByName創建了具體的解碼組件。
//android/frameworks/av/media/codec2/sfplguin/CCodec.cpp
void CCodec::allocate(const sp<MediaCodecInfo> &codecInfo) {
...
AString componentName = codecInfo->getCodecName();
std::shared_ptr<Codec2Client> client;
// set up preferred component store to access vendor store parameters
//從CCodec調用到component是通過HAL層服務的,默認谷歌的原生服務爲
//android.hardware.media.c2@IComponentStore/software,默認廠商的服務爲
//android.hardware.media.c2@IComponentStore/default,在android小機shell中通過lshal|grep media可以查詢
//到正在運行的codec2服務,如果廠商已支持codec2,則可以查詢到default服務。如果CCodec中能夠創建到default
//服務,則可以將該服務設置爲Preferred Codec2 ComponentStore,也就是將其作爲目標組件。
client = Codec2Client::CreateFromService("default");
if (client) {
ALOGI("setting up '%s' as default (vendor) store", client->getServiceName().c_str());
SetPreferredCodec2ComponentStore(
std::make_shared<Codec2ClientInterfaceWrapper>(client));
}
//創建具體的解碼組件或者編碼組件,譬如c2.android.avc.decoder
//所有omx與codec2的編解碼組件支持列表可以在libstagefright/data目錄下的xml中查詢得到,它們的加載與
//排序情況可以在libstagefright/MediaCodecList.cpp中追蹤
std::shared_ptr<Codec2Client::Component> comp =
Codec2Client::CreateComponentByName(
componentName.c_str(),
mClientListener,
&client);
...
ALOGI("Created component [%s]", componentName.c_str());
mChannel->setComponent(comp);
auto setAllocated = [this, comp, client] {
Mutexed<State>::Locked state(mState);
if (state->get() != ALLOCATING) {
state->set(RELEASED);
return UNKNOWN_ERROR;
}
state->set(ALLOCATED);
state->comp = comp;
mClient = client;
return OK;
};
...
// initialize config here in case setParameters is called prior to configure
Mutexed<Config>::Locked config(mConfig);
status_t err = config->initialize(mClient, comp);
...
config->queryConfiguration(comp);
mCallback->onComponentAllocated(componentName.c_str());
}
繼續追蹤Codec2Client::CreateComponentByName接口。
//android/frameworks/av/media/codec2/hidl/client/client.cpp
std::shared_ptr<Codec2Client::Component>
Codec2Client::CreateComponentByName(
const char* componentName,
const std::shared_ptr<Listener>& listener,
std::shared_ptr<Codec2Client>* owner,
size_t numberOfAttempts) {
std::string key{"create:"};
key.append(componentName);
std::shared_ptr<Component> component;
c2_status_t status = ForAllServices(
key,
numberOfAttempts,
[owner, &component, componentName, &listener](
const std::shared_ptr<Codec2Client> &client)
-> c2_status_t {
//調用Codec2Client類的createComponent接口,獲取component
c2_status_t status = client->createComponent(componentName,
listener,
&component);
...
return status;
});
...
return component;
}
追蹤Codec2Client類的createComponent接口。
\\av\media\codec2\hidl\client\client.cpp
c2_status_t Codec2Client::createComponent(
const C2String& name,
const std::shared_ptr<Codec2Client::Listener>& listener,
std::shared_ptr<Codec2Client::Component>* const component) {
c2_status_t status;
sp<Component::HidlListener> hidlListener = new Component::HidlListener{};
hidlListener->base = listener;
//這裏的mBase是什麼?這裏調用的是IComponentStore的createComponent接口
Return<void> transStatus = mBase->createComponent(
name,
hidlListener,
ClientManager::getInstance(),
[&status, component, hidlListener](
Status s,
const sp<IComponent>& c) {
status = static_cast<c2_status_t>(s);
if (status != C2_OK) {
return;
}
*component = std::make_shared<Codec2Client::Component>(c);
hidlListener->component = *component;
});
...
return status;
}
我們先看一下IComponentStore的createComponent接口。
\\av\media\codec2\hidl\1.0\utils\include\codec2\hidl\1.0\ComponentStore.h
struct ComponentStore : public IComponentStore {
ComponentStore(const std::shared_ptr<C2ComponentStore>& store);
virtual ~ComponentStore() = default;
// Methods from ::android::hardware::media::c2::V1_0::IComponentStore.
virtual Return<void> createComponent(
const hidl_string& name,
const sp<IComponentListener>& listener,
const sp<IClientManager>& pool,
createComponent_cb _hidl_cb) override;
virtual Return<void> createInterface(
const hidl_string& name,
createInterface_cb _hidl_cb) override;、
...
}
該接口的實現爲:
\\av\media\codec2\hidl\1.0\utils\ComponentStore.cpp
// Methods from ::android::hardware::media::c2::V1_0::IComponentStore
Return<void> ComponentStore::createComponent(
const hidl_string& name,
const sp<IComponentListener>& listener,
const sp<IClientManager>& pool,
createComponent_cb _hidl_cb) {
sp<Component> component;
std::shared_ptr<C2Component> c2component;
//C2PlatformComponentStore的createComponent調用
//調用C2PlatformComponentStore的createComponent接口,返回的是一個C2Component對象
//譬如,這個對象可以是C2SoftAvcDec Component對象,也可以是VendorHwAvcDec Component對象
Status status = static_cast<Status>(
mStore->createComponent(name, &c2component));
if (status == Status::OK) {
onInterfaceLoaded(c2component->intf());
//把前面創建的C2SoftAvcDec“裝載”到Component類中,Client調用Component
//Component內部會調用到C2SoftAvcDec
//Component相當於對原生編解碼組件/廠商編解碼組件的統一封裝
component = new Component(c2component, listener, this, pool);
if (!component) {
status = Status::CORRUPTED;
} else {
reportComponentBirth(component.get());
if (component->status() != C2_OK) {
status = static_cast<Status>(component->status());
} else {
component->initListener(component);
if (component->status() != C2_OK) {
status = static_cast<Status>(component->status());
}
}
}
}
_hidl_cb(status, component);
return Void();
}
關於C2PlatformComponentStore的createComponent調用,它的實現在C2Store.cpp中,它繼承於C2ComponentStore類,有幾個重要成員對象,ComponentModule,ComponentLoader,有幾個重要的接口,listComponents(),createComponent(),createInterface()。ComponentLoader包含ComponentModule對象,而ComponentModule主要提供兩個接口,createComponent()與createInterface(),內部也包含着C2ComponentFactory成員以及它的創建與銷燬接口,分別是C2ComponentFactory::CreateCodec2FactoryFunc,C2ComponentFactory::DestroyCodec2FactoryFunc。
\\av\media\codec2\vndk\C2Store.cpp
class C2PlatformComponentStore : public C2ComponentStore {
public:
virtual std::vector<std::shared_ptr<const C2Component::Traits>> listComponents() override;
...
virtual c2_status_t createInterface(
C2String name, std::shared_ptr<C2ComponentInterface> *const interface) override;
virtual c2_status_t createComponent(
C2String name, std::shared_ptr<C2Component> *const component) override;
virtual ~C2PlatformComponentStore() override = default;
private:
/**
* An object encapsulating a loaded component module.
*/
struct ComponentModule : public C2ComponentFactory,
public std::enable_shared_from_this<ComponentModule> {
virtual c2_status_t createComponent(
c2_node_id_t id, std::shared_ptr<C2Component> *component,
ComponentDeleter deleter = std::default_delete<C2Component>()) override;
virtual c2_status_t createInterface(
c2_node_id_t id, std::shared_ptr<C2ComponentInterface> *interface,
InterfaceDeleter deleter = std::default_delete<C2ComponentInterface>()) override;
...
protected:
...
void *mLibHandle; ///< loaded library handle
C2ComponentFactory::CreateCodec2FactoryFunc createFactory; ///< loaded create function
C2ComponentFactory::DestroyCodec2FactoryFunc destroyFactory; ///< loaded destroy function
C2ComponentFactory *mComponentFactory; ///< loaded/created component factory
};
/**
* An object encapsulating a loadable component module.
*/
struct ComponentLoader {
/**
* Load the component module.
*
* This method simply returns the component module if it is already currently loaded, or
* attempts to load it if it is not.
*/
c2_status_t fetchModule(std::shared_ptr<ComponentModule> *module) {
c2_status_t res = C2_OK;
std::lock_guard<std::mutex> lock(mMutex);
std::shared_ptr<ComponentModule> localModule = mModule.lock();
if (localModule == nullptr) {
localModule = std::make_shared<ComponentModule>();
res = localModule->init(mLibPath);
if (res == C2_OK) {
mModule = localModule;
}
}
*module = localModule;
return res;
}
/**
* Creates a component loader for a specific library path (or name).
*/
ComponentLoader(std::string libPath)
: mLibPath(libPath) {}
private:
std::weak_ptr<ComponentModule> mModule; ///< weak reference to the loaded module
};
struct Interface : public C2InterfaceHelper {
...
};
/**
* Retrieves the component module for a component.
*/
c2_status_t findComponent(C2String name, std::shared_ptr<ComponentModule> *module);
/**
* Loads each component module and discover its contents.
*/
void visitComponents();
std::map<C2String, ComponentLoader> mComponents; ///< path -> component module
std::map<C2String, C2String> mComponentNameToPath; ///< name -> path
std::vector<std::shared_ptr<const C2Component::Traits>> mComponentList;
...
};
C2PlatformComponentStore::createComponent調用findComponent(name, &module)找到擁有component的ComponentModule,再通過module->createComponent(0, component)調用,找到相應的component。
\\av\media\codec2\vndk\C2Store.cpp
c2_status_t C2PlatformComponentStore::createComponent(
C2String name, std::shared_ptr<C2Component> *const component) {
// This method SHALL return within 100ms.
component->reset();
std::shared_ptr<ComponentModule> module;
c2_status_t res = findComponent(name, &module);
if (res == C2_OK) {
// TODO: get a unique node ID
res = module->createComponent(0, component);
}
return res;
}
findComponent(name, &module)有兩步,先通過visitComponents()列舉出所有可用的components,再調用ComponentLoader的fetchModule(),找到擁有component的ComponentModule。module可以看作是組件,加載某個module,也就是加載對應的組件,module提供的 createComponent()接口就是用來創建具體component的,譬如C2SoftAvcDec。
\\av\media\codec2\vndk\C2Store.cpp
c2_status_t C2PlatformComponentStore::findComponent(
C2String name, std::shared_ptr<ComponentModule> *module) {
(*module).reset();
visitComponents();
auto pos = mComponentNameToPath.find(name);
if (pos != mComponentNameToPath.end()) {
return mComponents.at(pos->second).fetchModule(module);
}
return C2_NOT_FOUND;
}
visitComponents()訪問mComponents對象(這是一個map對象,將path與component module映射關聯,這一映射工作在C2PlatformComponentStore初始化時進行),遍歷所有的mComponents,即pathAndLoader對象,如果一個對象的loader能夠加載成功,則添加到mComponentNameToPath對象中。
\\av\media\codec2\vndk\C2Store.cpp
void C2PlatformComponentStore::visitComponents() {
std::lock_guard<std::mutex> lock(mMutex);
if (mVisited) {
return;
}
//參考定義 std::map<C2String, ComponentLoader> mComponents; ///< path -> component module
for (auto &pathAndLoader : mComponents) {
const C2String &path = pathAndLoader.first;
ComponentLoader &loader = pathAndLoader.second;
std::shared_ptr<ComponentModule> module;
if (loader.fetchModule(&module) == C2_OK) {
std::shared_ptr<const C2Component::Traits> traits = module->getTraits();
if (traits) {
mComponentList.push_back(traits);
mComponentNameToPath.emplace(traits->name, path);
for (const C2String &alias : traits->aliases) {
mComponentNameToPath.emplace(alias, path);
}
}
}
}
mVisited = true;
}
loader.fetchModule(&module)這個函數定義在ComponentLoader類中,在這裏再貼一次代碼。
\\av\media\codec2\vndk\C2Store.cpp
c2_status_t fetchModule(std::shared_ptr<ComponentModule> *module) {
c2_status_t res = C2_OK;
std::lock_guard<std::mutex> lock(mMutex);
std::shared_ptr<ComponentModule> localModule = mModule.lock();
if (localModule == nullptr) {
localModule = std::make_shared<ComponentModule>();
res = localModule->init(mLibPath);
if (res == C2_OK) {
mModule = localModule;
}
}
*module = localModule;
return res;
}
對於module,會調用初始化函數,初始化成功就算是fetch到了。初始化作了什麼工作,參見C2PlatformComponentStore::ComponentModule::init函數,也就是對編解碼庫dlopen成功,可獲得相應的函數地址,譬如,C2SoftAvcDec.cpp中的C2ComponentFactory* CreateCodec2Factory()與void DestroyCodec2Factory()。當然還有其他,不面面俱道了。
\\av\media\codec2\vndk\C2Store.cpp
c2_status_t C2PlatformComponentStore::ComponentModule::init(
std::string libPath) {
ALOGV("in %s", __func__);
ALOGV("loading dll");
mLibHandle = dlopen(libPath.c_str(), RTLD_NOW|RTLD_NODELETE);
createFactory =
(C2ComponentFactory::CreateCodec2FactoryFunc)dlsym(mLibHandle, "CreateCodec2Factory");
LOG_ALWAYS_FATAL_IF(createFactory == nullptr,
"createFactory is null in %s", libPath.c_str());
destroyFactory =
(C2ComponentFactory::DestroyCodec2FactoryFunc)dlsym(mLibHandle, "DestroyCodec2Factory");
LOG_ALWAYS_FATAL_IF(destroyFactory == nullptr,
"destroyFactory is null in %s", libPath.c_str());
mComponentFactory = createFactory();
...
std::shared_ptr<C2ComponentInterface> intf;
c2_status_t res = createInterface(0, &intf);
...
return mInit;
}
那麼問題來了,爲什麼谷歌對它自己的codec2插件組C2PlatformComponentStore設計得這麼複雜,能不能簡化一點。
3 組件接口
在codec2/components目錄下,有base, avc, aom, hevc, aac等文件夾,base目錄下是SimpleC2Component.cpp與SimpleC2Interface.cpp以及對應的頭文件,avc目錄下是C2SoftAvcDec.cpp,C2SoftAvcEnc.cpp以及對應的頭文件,其他編解碼器文件夾亦同樣道理。C2SoftAvcDec,C2SoftHevcDec等編解碼器類都是繼承於SimpleC2Component類的,也就是說,SimpleC2Component是components的頂層類,它對接了component類的接口,實現了編解碼器的公共流程部分,C2SoftAvcDec,C2SoftHevcDec等子類繼承SimpleC2Component的一些接口,實現各自的編解碼操作。
SimpleC2Component實現的component的接口如下:
\\av\media\codec2\components\base\include\SimpleC2Component.h
// C2Component
// From C2Component
//設置回調
virtual c2_status_t setListener_vb(
const std::shared_ptr<Listener> &listener, c2_blocking_t mayBlock) override;
//送數據到component,數據打包成某種對象,叫C2Work,這個對象很關鍵,它包含input與output
virtual c2_status_t queue_nb(std::list<std::unique_ptr<C2Work>>* const items) override;
//暫時沒有多大用處,不管它
virtual c2_status_t announce_nb(const std::vector<C2WorkOutline> &items) override;
//跳播使用,將當前數據沖刷掉
virtual c2_status_t flush_sm(
flush_mode_t mode, std::list<std::unique_ptr<C2Work>>* const flushedWork) override;
//渲染可用的幀
virtual c2_status_t drain_nb(drain_mode_t mode) override;
virtual c2_status_t start() override;
virtual c2_status_t stop() override;
virtual c2_status_t reset() override;
virtual c2_status_t release() override;
virtual std::shared_ptr<C2ComponentInterface> intf() override;
而C2SoftAvcDec,C2SoftHevcDec等子類繼承SimpleC2Component的接口如下:
\\av\media\codec2\components\base\include\SimpleC2Component.h
virtual c2_status_t onInit() = 0;
virtual c2_status_t onStop() = 0;
virtual void onReset() = 0;
virtual void onRelease() = 0;
virtual c2_status_t onFlush_sm() = 0;
//最重要的處理函數,處理的對象是C2Work,它包含着輸入輸出,交互配置方面的類。
virtual void process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) = 0;
virtual c2_status_t drain(
uint32_t drainMode,
const std::shared_ptr<C2BlockPool> &pool) = 0;
4 組件運行原理
SimpleC2Component有一個成員對象WorkHandler,這個類繼承於AHandler,也就是說,SimpleC2Component內部運行一個線程,來自上層的接口調用,都可以發送消息到onMessageReceived中排隊處理,譬如初始化、停止、重置、釋放以及數據處理等工作,都在隊列中排隊處理,相應的處理都是調用到子類的實現,譬如,onInit(),onStop(),onReset(),onRelease(),以及processQueue()。
我們可以看一下onMessageReceived的實現。
\\av\media\codec2\components\base\SimpleC2Component.cpp
void SimpleC2Component::WorkHandler::onMessageReceived(const sp<AMessage> &msg) {
std::shared_ptr<SimpleC2Component> thiz = mThiz.lock();
...
switch (msg->what()) {
case kWhatProcess: {
if (mRunning) {
if (thiz->processQueue()) {
(new AMessage(kWhatProcess, this))->post();
}
} else {
ALOGV("Ignore process message as we're not running");
}
break;
}
case kWhatInit: {
int32_t err = thiz->onInit();
Reply(msg, &err);
[[fallthrough]];
}
case kWhatStart: {
mRunning = true;
break;
}
case kWhatStop: {
int32_t err = thiz->onStop();
Reply(msg, &err);
break;
}
case kWhatReset: {
thiz->onReset();
mRunning = false;
Reply(msg);
break;
}
case kWhatRelease: {
thiz->onRelease();
mRunning = false;
Reply(msg);
break;
}
default: {
ALOGD("Unrecognized msg: %d", msg->what());
break;
}
}
}
我們看一下AVC解碼器內部是如何處理輸入與輸出數據的,在這個process中,處理完輸入,解碼,處理輸出,在處理output buffer時,process的思路是這樣的:從內存池申請一個GraphicBlock,對應地設置給解碼器Buffer地址以供解碼輸出,如果解碼後有幀輸出,則將當前的GraphicBlock轉換爲C2Buffer對象,返回給上層。類似於FFMPEG,你給它一個output frame,它就將解碼圖片填充到frame,你取走顯示。可以推斷,軟解碼器內部應該也有申請一個隊列的buffer,這個隊列維護着解碼所需要的參考圖像。
\\av\media\codec2\components\avc\C2SoftAvcDec.cpp
//省略了部分不影響理解主要流程的代碼
void C2SoftAvcDec::process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) {
// Initialize output work
work->result = C2_OK;
work->workletsProcessed = 0u;
work->worklets.front()->output.flags = work->input.flags;
size_t inOffset = 0u;
size_t inSize = 0u;
uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
C2ReadView rView = mDummyReadView;
if (!work->input.buffers.empty()) {
//爲了得到輸入數據,層層訪問,真正放數據的地址在rView.data()[]中
//把work這個對象用思維導圖畫出來,我們可以更容易的理解work,到底擁有哪些成員,如何訪問
rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
inSize = rView.capacity();
...
}
bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
bool hasPicture = false;
ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
inSize, (int)work->input.ordinal.timestamp.peeku(),
(int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
size_t inPos = 0;
while (inPos < inSize) {
//ensureDecoderState會從內存池中fetch一個GraphicBlock
//實質上也就是調用Gralloc接口取得一個output buffer
if (C2_OK != ensureDecoderState(pool)) {
mSignalledError = true;
work->workletsProcessed = 1u;
work->result = C2_CORRUPTED;
return;
}
ivd_video_decode_ip_t s_decode_ip;
ivd_video_decode_op_t s_decode_op;
{
//mOutBlock即是上述fetch到的output buffer,通過map映射可以得到一個wView,類似於rView
//wView.data()[]指向out buffer的真正地址
//wView.data()[C2PlanarLayout::PLANE_Y]就是要存在Y變量的地址
//wView.data()[C2PlanarLayout::PLANE_U]就是要存在U變量的地址
C2GraphicView wView = mOutBlock->map().get();
...
//setDecodeArgs所作的主要工作是,告訴解碼器,輸入數據的地址是什麼,輸出地址包括Y/U/V
//分量的地址是什麼,輸入數據的長度是多少
if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView,
inOffset + inPos, inSize - inPos, workIndex)) {
mSignalledError = true;
work->workletsProcessed = 1u;
work->result = C2_CORRUPTED;
return;
}
if (false == mHeaderDecoded) {
/* Decode header and get dimensions */
setParams(mStride, IVD_DECODE_HEADER);
}
//解碼器庫是用了第三方的,已經被谷歌收購
(void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
}
if (s_decode_op.i4_reorder_depth >= 0 && mOutputDelay != s_decode_op.i4_reorder_depth) {
//目前不清楚把這個重排序長度告訴上層有什麼作用,TODO
mOutputDelay = s_decode_op.i4_reorder_depth;
ALOGV("New Output delay %d ", mOutputDelay);
C2PortActualDelayTuning::output outputDelay(mOutputDelay);
std::vector<std::unique_ptr<C2SettingResult>> failures;
c2_status_t err =
mIntf->config({&outputDelay}, C2_MAY_BLOCK, &failures);
if (err == OK) {
work->worklets.front()->output.configUpdate.push_back(
C2Param::Copy(outputDelay));
}
continue;
}
if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) {
if (mHeaderDecoded == false) {
mHeaderDecoded = true;
setParams(ALIGN64(s_decode_op.u4_pic_wd), IVD_DECODE_FRAME);
}
if (s_decode_op.u4_pic_wd != mWidth || s_decode_op.u4_pic_ht != mHeight) {
mWidth = s_decode_op.u4_pic_wd;
mHeight = s_decode_op.u4_pic_ht;
CHECK_EQ(0u, s_decode_op.u4_output_present);
C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight);
std::vector<std::unique_ptr<C2SettingResult>> failures;
c2_status_t err = mIntf->config({&size}, C2_MAY_BLOCK, &failures);
if (err == OK) {
work->worklets.front()->output.configUpdate.push_back(
C2Param::Copy(size));
}
continue;
}
}
(void)getVuiParams();
hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag);
if (s_decode_op.u4_output_present) {
//通過createGraphicBuffer調用,將mOutBlock"轉換"成C2Buffer對象
//把C2Buffer添加到work對象的輸出隊列中
//通過listener->onWorkDone_nb回調,可以將work返回到CCodec層
//以上是這個函數以及其內部調用的主要實現內容,內部調用的finish()函數屬於SimpleC2Component
finishWork(s_decode_op.u4_ts, work);
}
inPos += s_decode_op.u4_num_bytes_consumed;
}
if (eos) {
drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
mSignalledOutputEos = true;
} else if (!hasPicture) {
fillEmptyWork(work);
}
work->input.buffers.clear();
}
在Component中,輸入與輸出對象都封裝在work對象中,甚至上下層的配置交互對象也包括在work對象中,與OMX是不一樣的,OMX的數據對象是BufferHeader,輸入是一個Input BufferHeader,輸出是一個Output BufferHeader,對象中包括buffer地址,分配的buffer大小,有效數據長度,有效數據長度的偏移量,buffer標誌等。 那麼,work對象也應該會包括類似的成員。
我們來看兩張思維導圖,全局觀察work對象。
C2SoftAvcDec::process中有一句代碼,從work中訪問rView。
rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
從上述兩圖中,我們可以追蹤這一條訪問線路,訪問C2Work對象的成員C2FrameData,繼續訪問C2FrameData對外的成員vector linearBlocks(),C2ConstLinearBlock有一個方法C2Acquirable map(),這個映射方法返回一個C2ReadView對象,這個C2ReadView對象有一個data()[]數組,指向了Y/U/V的向量地址,也就是真正存放解碼數據的內存地址。而Input與Output都是以C2FrameData來描述,Output並非像Input一樣,直接作爲C2Work的成員,而是作爲C2Work->worklets的成員。worklet是一個list類型,C2SoftAvcDec在存放output buffer的時候,總是存放在第一個worklets的output中,參見思維導圖,output是C2FrameData類型,它擁有一個C2Buffer容器,C2SoftAvcDec總是將新的output buffer丟進容器中,它可以一次丟很多個output buffer,然後一次性通過work回送到上層,上層可以一次性從work中取到多個output buffer去作渲染。C2WorkOrdinalStruct ordinal包括着buffer的pts與frameIndex信息。這裏有個疑問待解決,爲什麼output buffer總是存放在第一個worklets的output中,worklets作爲一個隊列對象,有什麼其他的意義?
上面我們分析了兩個點,一個是模塊的消息處理機制,另一個是如何送數據到解碼器再取出幀數據回送到上層,接下來看第三點,CCodec每次送多少輸入數據下來,component每次處理多少數據,回送輸出數據給CCodec作渲染在哪些地方。
上層是調用SimpleC2Component::queue_nb接口送數據下來的。
\\av\media\codec2\components\base\SimpleC2Component.cpp
c2_status_t SimpleC2Component::queue_nb(std::list<std::unique_ptr<C2Work>> * const items) {
{
Mutexed<ExecState>::Locked state(mExecState);
if (state->mState != RUNNING) {
return C2_BAD_STATE;
}
}
bool queueWasEmpty = false;
{
Mutexed<WorkQueue>::Locked queue(mWorkQueue);
queueWasEmpty = queue->empty();
while (!items->empty()) {
queue->push_back(std::move(items->front()));
items->pop_front();
}
}
if (queueWasEmpty) {
(new AMessage(WorkHandler::kWhatProcess, mHandler))->post();
}
return C2_OK;
}
觀察上面的代碼,入參是一個列表對象,也就是說,每次送多個work,一個work可以包括一個C2Buffer容器,碼流都是放在容器的第一個元素,雖然一個容器可以放多個C2Buffer,但它就只放了一個C2Buffer。我們可以從下面的代碼中發現,每一次的process,都只從work中取一個C2Buffer。
\\av\media\codec2\components\avc\C2SoftAvcDec.cpp
void C2SoftAvcDec::process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) {
...
uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
C2ReadView rView = mDummyReadView;
if (!work->input.buffers.empty()) {
//關注buffers[0]
rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
inSize = rView.capacity();
}
}
SimpleC2Component::processQueue()每次只處理一個work,處理完就把work回送上去。
\\av\media\codec2\components\base\SimpleC2Component.cpp
bool SimpleC2Component::processQueue() {
....
ALOGV("start processing frame #%" PRIu64, work->input.ordinal.frameIndex.peeku());
//處理work
process(work, mOutputBlockPool);
ALOGV("processed frame #%" PRIu64, work->input.ordinal.frameIndex.peeku());
Mutexed<WorkQueue>::Locked queue(mWorkQueue);
if (work->workletsProcessed != 0u) {
queue.unlock();
Mutexed<ExecState>::Locked state(mExecState);
ALOGV("returning this work");
std::shared_ptr<C2Component::Listener> listener = state->mListener;
state.unlock();
//回送work
listener->onWorkDone_nb(shared_from_this(), vec(work));
}
...
}
在沒有新送下來的work需要處理的時候,processQueue()會調用drain接口作“渲染”操作,它會看解碼器是否有幀數據生成,有的話,就填充到work中回送到上層。
\\av\media\codec2\components\base\SimpleC2Component.cpp
bool SimpleC2Component::processQueue() {
....
if (!work) {
c2_status_t err = drain(drainMode, mOutputBlockPool);
if (err != C2_OK) {
Mutexed<ExecState>::Locked state(mExecState);
std::shared_ptr<C2Component::Listener> listener = state->mListener;
state.unlock();
listener->onError_nb(shared_from_this(), err);
}
return hasQueuedWork;
}
...
}
另一個渲染的地方是在process()中,解碼完發現有幀數據的時候,就調用finishWork()將work回送。
\\av\media\codec2\components\avc\C2SoftAvcDec.cpp
void C2SoftAvcDec::process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) {
...
if (s_decode_op.u4_output_present) {
finishWork(s_decode_op.u4_ts, work);
}
...
}
5 小結
Component內部的邏輯還是比較好理解的,重點在於它是如何申請buffer的,如何將buffer“送”給解碼器,解碼完後是如何取得buffer並返回上層,難點在於work對象層層封裝,當你要訪問實際內存地址時,如何訪問,如果要取得內存的handle,又要如何訪問,這一點通過將work對象一層一層的“繪製”出來,就好懂得多。接下來問題來了,在OMX中,上下層的交互配置是通過setParamerter/getParamerter等接口進行的,那麼在Codec2中是如何進行的?Codec2中到底有沒有像OMX一樣的BufferCountActual設計?Codec2在調用nativewindow的setMaxDequeuedBufferCount時是如何確定maxDequeueBufferCount的?GraphicBuffer的生命週期是如何控制的?