Cnstream源码剖析----Module基类

霸气de小男生 提交于 2020-09-26 12:06:59

源码地址: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的一些理解,主要也是为了自用,后面随着代码的更新,也会逐步更新。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!