licode学习之erizo篇--Pipeline_handle

erizo的pipeline的handle,是媒体数据处理的基本操作,handle分为3类:IN,OUT,BOTH

IN:数据进入handle,handle需要read数据并传递给下一级

OUT:数据进入handle,handle需要write数据并传递给下一级

BOTH:可以同时进行read和write

在宏观语义上,IN的目标是输出rtp裸数据;OUT的目标是封装rtp裸数据

pipeline的handle的继承体系如下:

handle有三种:InboundHandler、Handler、OutboundHandler;他们分别对应一个Context

InboundHandler==>InboundContextImpl

Handler==>ContextImpl

OutboundHandler==>OutboundContextImpl

他们的工作方式差不多,就挑一个InboundHandler来进行说明。pipeline有addFront方法,该方法是注册Handler的,先看看这个方法的相关实现:

template <class H>
PipelineBase& PipelineBase::addFront(std::shared_ptr<H> handler) {
  typedef typename ContextType<H>::type Context;
  return addHelper(
      std::make_shared<Context>(shared_from_this(), std::move(handler)),
      true);
}
template <class Context>
PipelineBase& PipelineBase::addHelper(
    std::shared_ptr<Context>&& ctx,  // NOLINT
    bool front) {
  ctxs_.insert(front ? ctxs_.begin() : ctxs_.end(), ctx);
  if (Context::dir == HandlerDir_BOTH || Context::dir == HandlerDir_IN) {
    inCtxs_.insert(front ? inCtxs_.begin() : inCtxs_.end(), ctx.get());
  }
  if (Context::dir == HandlerDir_BOTH || Context::dir == HandlerDir_OUT) {
    outCtxs_.insert(front ? outCtxs_.begin() : outCtxs_.end(), ctx.get());
  }
  return *this;
}

addFront的功能,就是创建一个Context的对象,并将之传递给addHelper为参数。Context的构造还被传递了Handler自己的指针,让Context能够知道handler

addHelper将Context存储起来,并且判断类型,分别放到inCtxs_和outCtxs_里面,待用

Context的实际类型是啥呢?

template <class Handler>
struct ContextType {
  typedef typename std::conditional<
    Handler::dir == HandlerDir_BOTH,
    ContextImpl<Handler>,
    typename std::conditional<
      Handler::dir == HandlerDir_IN,
      InboundContextImpl<Handler>,
      OutboundContextImpl<Handler>
    >::type>::type
  type;
};

Context的实际类型,要依托于Handler::dir成员的值,这个值是一个常量,是每个Handler都有的。

HandlerDir_IN:Context为InboundContextImpl

HandlerDir_BOTH:Context为ContextImpl

HandlerDir_其他:Context为OutboundContextImpl

所以,pipeline的addFront方法,实际上是创建HandlerContext的实例,并且将之存储的过程。

pipeline的数据链路建立,是怎么样的呢

void Pipeline::finalize() {
  front_ = nullptr;
  if (!inCtxs_.empty()) {
    front_ = dynamic_cast<InboundLink*>(inCtxs_.front());
    for (size_t i = 0; i < inCtxs_.size() - 1; i++) {
      inCtxs_[i]->setNextIn(inCtxs_[i+1]);
    }
    inCtxs_.back()->setNextIn(nullptr);
  }

  back_ = nullptr;
  if (!outCtxs_.empty()) {
    back_ = dynamic_cast<OutboundLink*>(outCtxs_.back());
    for (size_t i = outCtxs_.size() - 1; i > 0; i--) {
      outCtxs_[i]->setNextOut(outCtxs_[i-1]);
    }
    outCtxs_.front()->setNextOut(nullptr);
  }

  if (!front_) {
    // detail::logWarningIfNotUnit<R>(
    //     "No inbound handler in Pipeline, inbound operations will throw "
    //     "std::invalid_argument");
  }
  if (!back_) {
    // detail::logWarningIfNotUnit<W>(
    //     "No outbound handler in Pipeline, outbound operations will throw "
    //     "std::invalid_argument");
  }

  for (auto it = ctxs_.rbegin(); it != ctxs_.rend(); it++) {
    (*it)->attachPipeline();
  }

  for (auto it = service_ctxs_.rbegin(); it != service_ctxs_.rend(); it++) {
    (*it)->attachPipeline();
  }

  notifyUpdate();
}

pipeline里面的finalize方法为In和Out的Context设置任务链,并且设置头结点(front_,back_)之后,每个HandlerContext就知道自己的下一级任务是什么了

还需要了解一下触发方式:

MediaStream的OnTransportData获得packet数据

void MediaStream::onTransportData(std::shared_ptr<DataPacket> incoming_packet, Transport *transport) {
  if ((audio_sink_ == nullptr && video_sink_ == nullptr && fb_sink_ == nullptr)) {
    return;
  }

  std::shared_ptr<DataPacket> packet = std::make_shared<DataPacket>(*incoming_packet);

  if (transport->mediaType == AUDIO_TYPE) {
    packet->type = AUDIO_PACKET;
  } else if (transport->mediaType == VIDEO_TYPE) {
    packet->type = VIDEO_PACKET;
  }
  auto stream_ptr = shared_from_this();

  worker_->task([stream_ptr, packet]{
    if (!stream_ptr->pipeline_initialized_) {
      ELOG_DEBUG("%s message: Pipeline not initialized yet.", stream_ptr->toLog());
      return;
    }

    char* buf = packet->data;
    RtpHeader *head = reinterpret_cast<RtpHeader*> (buf);
    RtcpHeader *chead = reinterpret_cast<RtcpHeader*> (buf);
    if (!chead->isRtcp()) {
      uint32_t recvSSRC = head->getSSRC();
      if (stream_ptr->isVideoSourceSSRC(recvSSRC)) {
        packet->type = VIDEO_PACKET;
      } else if (stream_ptr->isAudioSourceSSRC(recvSSRC)) {
        packet->type = AUDIO_PACKET;
      }
    }

    if (stream_ptr->pipeline_) {
      stream_ptr->pipeline_->read(std::move(packet));
    }
  });
}

在里面调用了pipeline的read方法,还需要知道read里面做什么了

void Pipeline::read(std::shared_ptr<DataPacket> packet) {
  if (!front_) {
    return;
  }
  front_->read(std::move(packet));
}

调用了front的read,front是一个HandlerContext对象,并且是处理链的头结点

  void read(std::shared_ptr<DataPacket> packet) override {
    auto guard = this->pipelineWeak_.lock();
    this->handler_->read(this, std::move(packet));
  }

在HandlerContext里面调用了handler_的read方法,并且把自己作为参数也同时传递给了handler。

之后找一个真正的handler对象,看一下它的read实现:

void LayerDetectorHandler::read(Context *ctx, std::shared_ptr<DataPacket> packet) {
  RtcpHeader *chead = reinterpret_cast<RtcpHeader*>(packet->data);
  if (!chead->isRtcp() && enabled_ && packet->type == VIDEO_PACKET) {
    if (packet->codec == "VP8") {
      parseLayerInfoFromVP8(packet);
    } else if (packet->codec == "VP9") {
      parseLayerInfoFromVP9(packet);
    } else if (packet->codec == "H264") {
      parseLayerInfoFromH264(packet);
    }
  }
  ctx->fireRead(std::move(packet));
}

在handler的read里面,再次调用了ctx的fireRead方法,并且把packet进行传递

  void fireRead(std::shared_ptr<DataPacket> packet) override {
    auto guard = this->pipelineWeak_.lock();
    if (this->nextIn_) {
      this->nextIn_->read(std::move(packet));
    }
  }

在fireRead中,调用了nextIn_的read方法,nextIn_是下一个HandlerContext。

这样就形成了一个任务链。

ContextRead-->HanderRead-->Context fireRead-->Context next read-->ContextRead.........

形成一个read的任务链。

write的基本原理也是相似的。

总结:erizo的pipeline的handler是负责实际数据处理的,通过处理链路,将之串联起来

原文地址:https://www.cnblogs.com/limedia/p/licode_erizo_pipeline_handler.html

时间: 2024-11-13 06:56:45

licode学习之erizo篇--Pipeline_handle的相关文章

licode学习之编译篇--3

上一篇中,提示找不到NICE库,先看看CMakList里面吧 [[email protected] erizo]# pwd /home/test/licode-master/erizo [[email protected] erizo]# vim src/CMakeLists.txt #global variable set(THIRD_PARTY_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/../../build/libdeps/build/include/&qu

licode学习之编译篇--1

最近学习licode开源项目,越发觉得世界发展如此之快.菜鸟哥突然意识到,再不紧跟,就要被落伍淘汰了,下定决心要好好学习. licode是一个封装webrtc的server,是一个开源项目.感谢licode的team为我们提供了优秀的代码,架构. 学习licode的主要内容是学习源码,学习使用的技术,架构,编译方法,实现细节. 在开始之前,需要下载licode源码:https://github.com/lynckia/licode. 我的学习思路是,先下载源码,再编译源码,再学习源码. 下载后,

Caffe学习系列——工具篇:神经网络模型结构可视化

Caffe学习系列--工具篇:神经网络模型结构可视化 在Caffe中,目前有两种可视化prototxt格式网络结构的方法: 使用Netscope在线可视化 使用Caffe提供的draw_net.py 本文将就这两种方法加以介绍 1. Netscope:支持Caffe的神经网络结构在线可视化工具 Netscope是个支持prototxt格式描述的神经网络结构的在线可视工具,网址:  http://ethereon.github.io/netscope/quickstart.html  它可以用来可

Vue学习笔记入门篇——组件的使用

本文为转载,原文:Vue学习笔记入门篇--组件的使用 组件定义 组件 (Component) 是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展. 组件使用 注册 注册一个全局组件,你可以使用 Vue.component(tagName, options).组件在注册之后,便可以在父实例的模块中以自定义元素 的形式使用.

Vue学习笔记入门篇——组件的内容分发(slot)

本文为转载,原文:Vue学习笔记入门篇--组件的内容分发(slot) 介绍 为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板.这个过程被称为 内容分发 (或 "transclusion" 如果你熟悉 Angular).Vue.js 实现了一个内容分发 API,使用特殊的 'slot' 元素作为原始内容的插槽. 编译作用域 在深入内容分发 API 之前,我们先明确内容在哪个作用域里编译.假定模板为: <child-component> {{ messa

angularjs入门学习【指令篇】

一.首先我们来了解下指令API 属性 含义 restrict 申明标识符在模版中作为元素,属性,类,注释或组合,如何使用 priority 设置模版中相对于其他标识符的执行顺序 Template 指定一个字符串式的内嵌模版,如果你指定了模版是一个URL,那么是不会使用的 tempateUrl 指定URL加载的模版,如果你已经指定了内嵌的模版字符串,那么它不会使用的 Replace 如果为真,替换当前元素,如果是假或未指定,拼接到当前元素 Transclude 移动一个标识符的原始字节带你到一个新

Vue学习笔记入门篇——组件的通讯

本文为转载,原文:Vue学习笔记入门篇--组件的通讯 组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B.它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件.然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的.这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性.在 Vue 中,父子组件的关系可以总结为 props down, events up.父组件通过 props 向下传递

ASP.NET MVC学习之过滤器篇(2)

下面我们继续之前的ASP.NET MVC学习之过滤器篇(1)进行学习. 3.动作过滤器 顾名思义,这个过滤器就是在动作方法调用前与调用后响应的.我们可以在调用前更改实际调用的动作,也可以在动作调用完成之后更改最终返回的结果,当然很多人一定不太明白这个到底可以干什么, 下面我们举一个比较实际的例子: 相信理解过网站的安全的一定知道跨站请求(CSRF具体可以自行百度,这里我就不去解释了),当然也有解决方案,那就是给页面中增加一个识别码,当页面进行POST请求时,首先判断识别码是否正确, 如果正确则继

Qt学习总结-ui篇(二)

qccs定义圆角 border-radius:10px; 如果想给特定位置定义圆角,如: 左上角:border-left-top-radius:10px; 右下角色:border-right-bottom-rasius:10px; 半透明效果 只需要在css中使用rgba(100,100,100,40)这种形式来表示颜色即可. 为可执行文件添加图标 1.新建文件:finename.rc 文件名无所谓,只要后缀为rc就可以. 2.编辑新建的文件,输入以下内容: IDI_ICON1 ICON DIS