Crtmp Server接收rtmp音视频流,并实现音视频并发,可以作为直播后台的服务。整套代码量并不大,算是轻量级的服务。

花了些时间研究源码,现将研究的结果,记录下来,方便以后查阅。

先不从架构上分析,直接看代码。我是将crtmp运行在windows环境下,所以代码分析以windows参考。

//这个方法在一个while循环里执行
bool IOHandlerManager::Pulse() {
if (_isShuttingDown)
return false;
//1. Create a copy of all fd sets
FD_ZERO(&_readFdsCopy);
FD_ZERO(&_writeFdsCopy);
FD_ZERO(&_writeFdsCopy);
FD_COPY(&_readFds, &_readFdsCopy);
FD_COPY(&_writeFds, &_writeFdsCopy); //2. compute the max fd
if (_activeIOHandlers.size() == )
return true; //3. do the select
  //检查读写fd集合是否有变化。
RESET_TIMER(_timeout, , );
int32_t count = select(MAP_KEY(--_fdState.end()) + , &_readFdsCopy, &_writeFdsCopy, NULL, &_timeout);
if (count < ) {
FATAL("Unable to do select: %u", (uint32_t) LASTSOCKETERROR);
return false;
} _pTimersManager->TimeElapsed(time(NULL)); if (count == ) {
return true;
} //4. Start crunching the sets
// _activeIOHandlers 是以IOHandler为父类的类集合。这些类在构造函数里将自己
  //添加到_activeIOHandlers 中。没有外来连接请求来时 _activeIOHandlers
//已经存在IOHandler子类,要不下面的内容永远执行不到。
  //这些IOHandler子类时在configure moudle创建的
FOR_MAP(_activeIOHandlers, uint32_t, IOHandler *, i) {
if (FD_ISSET(MAP_VAL(i)->GetInboundFd(), &_readFdsCopy)) {
_currentEvent.type = SET_READ;
if (!MAP_VAL(i)->OnEvent(_currentEvent))
EnqueueForDelete(MAP_VAL(i));
}
if (FD_ISSET(MAP_VAL(i)->GetOutboundFd(), &_writeFdsCopy)) {
_currentEvent.type = SET_WRITE;
if (!MAP_VAL(i)->OnEvent(_currentEvent))
EnqueueForDelete(MAP_VAL(i));
}
} return true;
}

  configure module

bool Module::BindAcceptors() {
//accpetors 来自lua脚本。如下图1所示,脚本中有3个acceptor。
FOR_MAP(config[CONF_ACCEPTORS], string, Variant, i) {
if (!BindAcceptor(MAP_VAL(i))) {
FATAL("Unable to configure acceptor:\n%s", STR(MAP_VAL(i).ToString()));
return false;
}
}
return true;
} bool Module::BindAcceptor(Variant &node) {
//1. Get the chain
vector<uint64_t> chain;
  // CONF_PROTOCOL 表示 "protocol",ResolveProtocolChain代码在下面,该方法
//执行后的返回值chain 包含的内容是PT_TCP,PT_INBOUND_RTMP.
chain = ProtocolFactoryManager::ResolveProtocolChain(node[CONF_PROTOCOL]);
if (chain.size() == ) {
WARN("Invalid protocol chain: %s", STR(node[CONF_PROTOCOL]));
return true;
} //2. Is it TCP or UDP based?
if (chain[] == PT_TCP) {
//3. This is a tcp acceptor. Instantiate it and start accepting connections
     //创建TCP Acceptor,以图1第一组数据来看,ip为0.0.0.0,port 为1935,node为图1第一组数据
//chain包含的内容有PT_TCP,PT_INBOUND_RTM。
TCPAcceptor *pAcceptor = new TCPAcceptor(node[CONF_IP],
node[CONF_PORT], node, chain);
       //调用Bind方法,具体方法内容在下面
if (!pAcceptor->Bind()) {
FATAL("Unable to fire up acceptor from this config node: %s",
STR(node.ToString()));
return false;
}
ADD_VECTOR_END(acceptors, pAcceptor);
return true;
} else if (chain[] == PT_UDP) {
//4. Ok, this is an UDP acceptor. Because of that, we can instantiate
//the full stack. Get the stack first
BaseProtocol *pProtocol = ProtocolFactoryManager::CreateProtocolChain(
chain, node);
if (pProtocol == NULL) {
FATAL("Unable to instantiate protocol stack %s", STR(node[CONF_PROTOCOL]));
return false;
} //5. Create the carrier and bind it
UDPCarrier *pUDPCarrier = UDPCarrier::Create(node[CONF_IP], node[CONF_PORT],
pProtocol);
if (pUDPCarrier == NULL) {
FATAL("Unable to instantiate UDP carrier on %s:%hu",
STR(node[CONF_IP]), (uint16_t) node[CONF_PORT]);
pProtocol->EnqueueForDelete();
return false;
}
pUDPCarrier->SetParameters(node);
ADD_VECTOR_END(acceptors, pUDPCarrier); //6. We are done
return true;
} else {
FATAL("Invalid carrier type");
return false;
}
}

vector<uint64_t> DefaultProtocolFactory::ResolveProtocolChain(string name) {
 vector<uint64_t> result;
 if (false) {


}
#ifdef HAS_PROTOCOL_DNS
 else if (name == CONF_PROTOCOL_INBOUND_DNS) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_DNS);
 } else if (name == CONF_PROTOCOL_OUTBOUND_DNS) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_OUTBOUND_DNS);
 }
#endif /* HAS_PROTOCOL_DNS */
#ifdef HAS_PROTOCOL_RTMP
 else if (name == CONF_PROTOCOL_INBOUND_RTMP) {

//图1的第一组数据对应添加的协议类型在这里
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_RTMP);
 } else if (name == CONF_PROTOCOL_OUTBOUND_RTMP) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_OUTBOUND_RTMP);
 } else if (name == CONF_PROTOCOL_INBOUND_RTMPS) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_SSL);
  ADD_VECTOR_END(result, PT_INBOUND_RTMPS_DISC);
 }
#ifdef HAS_PROTOCOL_HTTP
 else if (name == CONF_PROTOCOL_INBOUND_RTMPT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_HTTP);
  ADD_VECTOR_END(result, PT_INBOUND_HTTP_FOR_RTMP);
 }
#endif /* HAS_PROTOCOL_HTTP */
#endif /* HAS_PROTOCOL_RTMP */
#ifdef HAS_PROTOCOL_TS
 else if (name == CONF_PROTOCOL_INBOUND_TCP_TS) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_TS);
 } else if (name == CONF_PROTOCOL_INBOUND_UDP_TS) {
  ADD_VECTOR_END(result, PT_UDP);
  ADD_VECTOR_END(result, PT_INBOUND_TS);
 }
#endif /* HAS_PROTOCOL_TS */
#ifdef HAS_PROTOCOL_RTP
 else if (name == CONF_PROTOCOL_INBOUND_RTSP) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_RTSP);
 } else if (name == CONF_PROTOCOL_RTSP_RTCP) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_RTSP);
  ADD_VECTOR_END(result, PT_RTCP);
 } else if (name == CONF_PROTOCOL_UDP_RTCP) {
  ADD_VECTOR_END(result, PT_UDP);
  ADD_VECTOR_END(result, PT_RTCP);
 } else if (name == CONF_PROTOCOL_INBOUND_RTSP_RTP) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_RTSP);
  ADD_VECTOR_END(result, PT_INBOUND_RTP);
 } else if (name == CONF_PROTOCOL_INBOUND_UDP_RTP) {
  ADD_VECTOR_END(result, PT_UDP);
  ADD_VECTOR_END(result, PT_INBOUND_RTP);
 } else if (name == CONF_PROTOCOL_RTP_NAT_TRAVERSAL) {
  ADD_VECTOR_END(result, PT_UDP);
  ADD_VECTOR_END(result, PT_RTP_NAT_TRAVERSAL);
 }
#endif /* HAS_PROTOCOL_RTP */
#ifdef HAS_PROTOCOL_HTTP
 else if (name == CONF_PROTOCOL_OUTBOUND_HTTP) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_OUTBOUND_HTTP);
 }
#endif /* HAS_PROTOCOL_HTTP */
#ifdef HAS_PROTOCOL_LIVEFLV
 else if (name == CONF_PROTOCOL_INBOUND_LIVE_FLV) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_LIVE_FLV);
 }
#endif /* HAS_PROTOCOL_LIVEFLV */
#ifdef HAS_PROTOCOL_VAR
 else if (name == CONF_PROTOCOL_INBOUND_XML_VARIANT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_XML_VAR);
 } else if (name == CONF_PROTOCOL_INBOUND_BIN_VARIANT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_BIN_VAR);
 } else if (name == CONF_PROTOCOL_OUTBOUND_XML_VARIANT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_XML_VAR);
 } else if (name == CONF_PROTOCOL_OUTBOUND_BIN_VARIANT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_BIN_VAR);
 }
#ifdef HAS_PROTOCOL_HTTP
 else if (name == CONF_PROTOCOL_INBOUND_HTTP_XML_VARIANT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_HTTP);
  ADD_VECTOR_END(result, PT_XML_VAR);
 } else if (name == CONF_PROTOCOL_INBOUND_HTTP_BIN_VARIANT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_HTTP);
  ADD_VECTOR_END(result, PT_BIN_VAR);
 } else if (name == CONF_PROTOCOL_OUTBOUND_HTTP_XML_VARIANT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_OUTBOUND_HTTP);
  ADD_VECTOR_END(result, PT_XML_VAR);
 } else if (name == CONF_PROTOCOL_OUTBOUND_HTTP_BIN_VARIANT) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_OUTBOUND_HTTP);
  ADD_VECTOR_END(result, PT_BIN_VAR);
 }
#endif /* HAS_PROTOCOL_HTTP */
#endif /* HAS_PROTOCOL_VAR */
#ifdef HAS_PROTOCOL_CLI
 else if (name == CONF_PROTOCOL_INBOUND_CLI_JSON) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_JSONCLI);
 }
#ifdef HAS_PROTOCOL_HTTP
 else if (name == CONF_PROTOCOL_INBOUND_HTTP_CLI_JSON) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_HTTP);
  ADD_VECTOR_END(result, PT_HTTP_4_CLI);
  ADD_VECTOR_END(result, PT_INBOUND_JSONCLI);
 }
#endif /* HAS_PROTOCOL_HTTP */
#endif /* HAS_PROTOCOL_CLI */
#ifdef HAS_PROTOCOL_MMS
 else if (name == CONF_PROTOCOL_OUTBOUND_MMS) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_OUTBOUND_MMS);
 }
#endif /* HAS_PROTOCOL_MMS */
#ifdef HAS_PROTOCOL_RAWHTTPSTREAM
 else if (name == CONF_PROTOCOL_INBOUND_RAW_HTTP_STREAM) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_RAW_HTTP_STREAM);
 } else if (name == CONF_PROTOCOL_INBOUND_RAW_HTTPS_STREAM) {
  ADD_VECTOR_END(result, PT_TCP);
  ADD_VECTOR_END(result, PT_INBOUND_SSL);
  ADD_VECTOR_END(result, PT_INBOUND_RAW_HTTP_STREAM);
 }
#endif /* HAS_PROTOCOL_RAWHTTPSTREAM */
 else {
  FATAL("Invalid protocol chain: %s.", STR(name));
 }
 return result;
}

 

bool TCPAcceptor::Bind() {

//创建socket
 _inboundFd = _outboundFd = (int) socket(PF_INET, SOCK_STREAM, 0);
 if (_inboundFd < 0) {
  int err = LASTSOCKETERROR;
  FATAL("Unable to create socket: %s(%d)", strerror(err), err);
  return false;
 }


if (!setFdOptions(_inboundFd)) {
  FATAL("Unable to set socket options");
  return false;
 }

//将创建socket绑定到ip 为0.0.0.0,port为1935的socket address中。

if (bind(_inboundFd, (sockaddr *) & _address, sizeof (sockaddr)) != 0) {
  int error = LASTSOCKETERROR;
  FATAL("Unable to bind on address: tcp://%s:%hu; Error was: %s (%d)",
    inet_ntoa(((sockaddr_in *) & _address)->sin_addr),
    ENTOHS(((sockaddr_in *) & _address)->sin_port),
    strerror(error),
    error);
  return false;
 }


if (_port == 0) {
  socklen_t tempSize = sizeof (sockaddr);
  if (getsockname(_inboundFd, (sockaddr *) & _address, &tempSize) != 0) {
   FATAL("Unable to extract the random port");
   return false;
  }
  _parameters[CONF_PORT] = (uint16_t) ENTOHS(_address.sin_port);
 }

//监听新创建的socket fd,什么时候执行Accept是通过select模型来实现的,但需要将fd添加到select监控

//的fd集合中,这是在activate acceptor中完成的。具体代码在见下面。

if (listen(_inboundFd, 100) != 0) {
  FATAL("Unable to put the socket in listening mode");
  return false;
 }


_enabled = true;
 return true;
}

bool BaseClientApplication::ActivateAcceptor(IOHandler *pIOHandler) {
 switch (pIOHandler->GetType()) {
  case IOHT_ACCEPTOR:
  {
   TCPAcceptor *pAcceptor = (TCPAcceptor *) pIOHandler;
   pAcceptor->SetApplication(this);
   return pAcceptor->StartAccept();
  }
  case IOHT_UDP_CARRIER:
  {
   UDPCarrier *pUDPCarrier = (UDPCarrier *) pIOHandler;
   pUDPCarrier->GetProtocol()->GetNearEndpoint()->SetApplication(this);
   return pUDPCarrier->StartAccept();
  }
  default:
  {
   FATAL("Invalid acceptor type");
   return false;
  }
 }
}

bool TCPAcceptor::StartAccept() {

//该方法将TCPAcceptor创建的fd添加到全局的fd集合中

//当有连接请求进来时,端口号1935上来了连接请求,主循环中select方法会返回,

//在fd集合中根据相应的fd找到它归属的TCPAcceptor,并调用TCPAcceptor的ONEvent方法
 return IOHandlerManager::EnableAcceptConnections(this);
}

bool TCPAcceptor::OnEvent(select_event &event) {
 if (!OnConnectionAvailable(event))
  return IsAlive();
 else
  return true;
}

bool TCPAcceptor::OnConnectionAvailable(select_event &event) {
 if (_pApplication == NULL)
  return Accept();
 return _pApplication->AcceptTCPConnection(this);
}

bool TCPAcceptor::Accept() {
 sockaddr address;
 memset(&address, 0, sizeof (sockaddr));
 socklen_t len = sizeof (sockaddr);
 int32_t fd;
 int32_t error;

//1. Accept the connection

//OnEvent方法会调用这个间接调用改方法
 fd = accept(_inboundFd, &address, &len);
 error = LASTSOCKETERROR;
 if (fd < 0) {
  FATAL("Unable to accept client connection: %s (%d)", strerror(error), error);
  return false;
 }
 if (!_enabled) {
  CLOSE_SOCKET(fd);
  _droppedCount++;
  WARN("Acceptor is not enabled. Client dropped: %s:%hu -> %s:%hu",
    inet_ntoa(((sockaddr_in *) & address)->sin_addr),
    ENTOHS(((sockaddr_in *) & address)->sin_port),
    STR(_ipAddress),
    _port);
  return true;
 }
 INFO("Client connected: %s:%hu -> %s:%hu",
   inet_ntoa(((sockaddr_in *) & address)->sin_addr),
   ENTOHS(((sockaddr_in *) & address)->sin_port),
   STR(_ipAddress),
   _port);

if (!setFdOptions(fd)) {
  FATAL("Unable to set socket options");
  CLOSE_SOCKET(fd);
  return false;
 }

//4. Create the chain

//创建协议,以图1中第一组数据为例,_protocolChain中包含PT_TCP,PT_INBOUND_RTMP

//这里共创建两个协议,tcp Protocol和 inbound rtmp,且tcp协议的near Protocol指向

//inbound rtmp 返回 inbound rtmp协议
 BaseProtocol *pProtocol = ProtocolFactoryManager::CreateProtocolChain(
   _protocolChain, _parameters);
 if (pProtocol == NULL) {
  FATAL("Unable to create protocol chain");
  CLOSE_SOCKET(fd);
  return false;
 }

//5. Create the carrier and bind it
 TCPCarrier *pTCPCarrier = new TCPCarrier(fd);

//pProtocol->GetFarEndpoint()指向tcp protocol。
 pTCPCarrier->SetProtocol(pProtocol->GetFarEndpoint());
 pProtocol->GetFarEndpoint()->SetIOHandler(pTCPCarrier);

//6. Register the protocol stack with an application
 if (_pApplication != NULL) {
  pProtocol = pProtocol->GetNearEndpoint();
  pProtocol->SetApplication(_pApplication);
 }

//调用tcp protocol 相应的方法

if (pProtocol->GetNearEndpoint()->GetOutputBuffer() != NULL)
  pProtocol->GetNearEndpoint()->EnqueueForOutbound();

_acceptedCount++;

//7. Done
 return true;
}

Variant & TCPAcceptor::GetParameters() {
 return _parameters;
}


           图1 acceptors

有需要讨论的加群 流媒体/Ffmpeg/音视频 127903734,QQ350197870

Crtmp 源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

随机推荐

  1. responseXML 属性

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs ...

  2. 为php安装memcached扩展连接memcached服务器

    首先必须完成必要的软件安装,memcached是php连接memcached服务器的php扩展 以前有个叫memcache也是php连接memcached服务器的扩展 php的memcache和mem ...

  3. php中empty(), is_null(), isset()函数区别

    empty(), is_null(), isset()真值表(区别) 我们先来看看这3个函数的功能描述 www.111cn.net isset 判断变量是否已存在,如果变量存在则返回 TRUE,否则返 ...

  4. mysql 修改字段长度

    mysql 修改字段长度 alter table news  modify column title varchar(130); alter table 表名 modify column 字段名 类型 ...

  5. python网络画图——networkX

    networkX tutorial 绘制基本网络图 用matplotlib绘制网络图 基本流程: 1. 导入networkx,matplotlib包 2. 建立网络 3. 绘制网络 nx.draw() ...

  6. RAC本地数据文件迁移至ASM的方法--非归档模式

    系统环境:rhel6.2_x64+Oracle RAC11g 操作过程: 1.非归档模式 SQL> archive log list; Database log mode No Archive ...

  7. STM32F4_TIM基本延时(计数原理)

    Ⅰ.概述 STM32的TIM定时器分为三类:基本定时器.通用定时器和高级定时器.从分类来看就知道STM32的定时器功能是非常强大的,但是,功能强大了,软件配置定时器就相对复杂多了.很多初学者甚至工作了 ...

  8. goto语句 switch语句

    goto语句 #include <iostream> using namespace std; int main() { int i = 1; number: i++; std::cout ...

  9. [转]Oracle_ProC编程

    1.引言 由于PL/SQL不能用来开发面向普通用户的应用程序,必须借助其他语言或开发工具. 在Linux操作系统下应该用什么语言或开发工具来进行Oracle数据库应用的开发呢?本文将介绍2种方案:Pr ...

  10. 菜鸟学习Spring——60s配置XML方法实现简单AOP

    一.概述. 上一篇博客讲述了用注解的形式实现AOP现在讲述另外一种AOP实现的方式利用XML来实现AOP. 二.代码演示. 准备工作参照上一篇博客<菜鸟学习Spring--60s使用annota ...