Cnstream源码剖析----Module基类
源码地址:https://github.com/Cambricon/CNStream/blob/master/framework/core/src/cnstream_module.cpp
关于Module基类,是cnstream的代码合集的核心部分。是用户和开发者,设计一个新的数据处理模块,绕不开的地方。
详细的介绍可以阅读CNstream用户手册:http://forum.cambricon.com/index.php?m=content&c=index&a=lists&catid=85
我们来看一下在这个基类的代码中,详细的做了什么:
1 static SpinLock module_id_spinlock_; 2 static uint64_t module_id_mask_ = 0; 3 static size_t _GetId() { 4 SpinLockGuard guard(module_id_spinlock_); 5 for (size_t i = 0; i < sizeof(module_id_mask_) * 8; i++) { 6 if (!(module_id_mask_ & ((uint64_t)1 << i))) { 7 module_id_mask_ |= (uint64_t)1 << i; 8 return i; 9 } 10 } 11 return INVALID_MODULE_ID; 12 }
首先开头的一部分代码中我们看到了_GetId() 这个函数,作用顾名思义,为新创建的模块获得唯一的一个标识符ID。其实其本质是用来在友元类Pipleine中提供一个标识信息。其中代码中用到了独热编码的方法,对于64位的机器中,
ModuleId其每一个置一的位置,都是一个模块载宣誓者主权。
同时在这段代码中我们发现了一个新的SpinLockGuard类型--自旋锁。是一种线程安全类型的锁,其目的也是为了互斥的进行资源访问:
class SpinLock { public: void lock() { while (lock_.test_and_set(std::memory_order_acquire)) { } // 未访问到互斥资源时,循环check } void unlock() { lock_.clear(std::memory_order_release); } private: std::atomic_flag lock_ = ATOMIC_FLAG_INIT; }; //旋锁是一种轻量级的互斥锁,可以更高效的对互斥资源进行保护。
既然有获得模块ID的函数,那当然也有载释放资源时,返回模块ID的方式(当然这个函数定义主要用于单元测试),因为在实际使用的过程中,当一个模块析构的时候也意味着整个处理过程的结束。
void Module::SetContainer(Pipeline* container) { if (container) { { RwLockWriteGuard guard(container_lock_); container_ = container; } GetId(); } else { RwLockWriteGuard guard(container_lock_); container_ = nullptr; id_ = INVALID_MODULE_ID; } } size_t Module::GetId() { if (id_ == INVALID_MODULE_ID) { RwLockReadGuard guard(container_lock_); if (container_) { id_ = container_->GetModuleIdx(); } else { #ifdef UNIT_TEST id_ = _GetId(); #endif } } return id_; }
接下来定义的两个方法,一个是SetContainer在模块注册时由Pipeline进行调用,将指向Pipeline的指针赋值给模块内部对应的数据成员。而GetId也是模块在注册时获得一个唯一的标识符,该标识符象征者该模块在Pipeline中的身份。
随后的两个向总线发送事件消息的函数相对比较简单。在此不再多说。我们先来看一下下面有关函数功能实现的函数:
int Module::DoProcess(std::shared_ptr<CNFrameInfo> data) { RecordTime(data, false); if (!HasTransmit()) { int ret = 0; /* Process() for normal module does not need to handle EOS*/ if (!data->IsEos()) { ret = Process(data); RecordTime(data, true); } RwLockReadGuard guard(container_lock_); if (container_) { if (container_->ProvideData(this, data) != true) { return -1; } } return ret; } return Process(data); }
这个函数的主要作用是,查看输入的数据流帧是否为EOS帧,其中EOS帧为一个为0的空帧,象征着该视频流路的结束。首先我们先记录该数据传入的事件的时间,再判断该帧是否需要自己传送,如果不需要自己传送,调用CNFrameInfo类中的IsEos的方法判断该帧是否为EOS帧。不是EOS
直接调用Process方法(该方法为纯虚函数需要在子类中重定义),进行处理数据。处理完再将该数据帧通过Pipeline的ProvideData方法传给下一个模块。如果是需要自己传送数据的模块,直接进行Process处理即可。
后面则是实现自己传送数据的功能TransmitData(),但是实际上,还是调用了总线的接口进行操作处理。相对比较简单。
而接下来两个函数则是与模块的性能统计相关:
void Module::RecordTime(std::shared_ptr<CNFrameInfo> data, bool is_finished) { std::shared_ptr<PerfManager> manager = GetPerfManager(data->stream_id); if (!data->IsEos() && manager) { manager->Record(is_finished, PerfManager::GetDefaultType(), this->GetName(), data->timestamp); if (!is_finished) { manager->Record(PerfManager::GetDefaultType(), PerfManager::GetPrimaryKey(), std::to_string(data->timestamp), this->GetName() + "_th", "'" + GetThreadName(pthread_self()) + "'"); } } } std::shared_ptr<PerfManager> Module::GetPerfManager(const std::string& stream_id) { std::unordered_map<std::string, std::shared_ptr<PerfManager>> managers; RwLockReadGuard guard(container_lock_); if (container_) { managers = container_->GetPerfManagers(); if (managers.find(stream_id) != managers.end()) { return managers[stream_id]; } } return nullptr; }
看到RecoderTime的第一段代码,我们就应该先去看GetPerManager方法。这个方法中,调用了Pipeline的GetPerManage方法,生成了一个由Stream_id唯一标识的性能统计观察者,用于统计每个流的性能信息。
而上面RecordTIme,也是对性能统计提供支持的一个方法。判断是否为EOS帧,是否生成了Permanager。随后调用manager中的Record方法,对于目前的事件戳,性能进行统计。其中有个Is_finished数据成员,是标识着该数据帧是否完成。
如果完成则计算性能,将信息选择打印出来。
以上呢就是关于基类Module的一些理解,主要也是为了自用,后面随着代码的更新,也会逐步更新。
浙公网安备 33010602011771号