源码地址: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的一些理解,主要也是为了自用,后面随着代码的更新,也会逐步更新。
来源:oschina
链接:https://my.oschina.net/u/4285706/blog/4520488