librtmp是我们平常工作中进行推拉流开发的重要工具,官方提供的版本是基于C/C++技术栈的,但是有不少的其它高级语言技术栈也都提供了相应的包装或移植版本。
RTMP协议非常复杂,网上又鲜有较为完整的文档,再加上它的一些设计理念比较奇特(奇特的事件会剌激我们的反向思维,让我们对现有认知产生否定),以至于我们完全理解它需要耗费非常多的时间和脑力。
从RTMP的实现版本也侧面验证了这点,现有的主流实现仍然遵循TLV范式(不了解的话请百度),协议层面基本抛弃了消息头的建议,链路层复用只使用块头,块头之上就是负载,对指令来说负载就是AMF,对媒体来说负载就是编码帧,清爽干净!
本文不讲解RTMP协议规范的相关知识,读者朋友们请自行百度。本文的主要目的是配合大家一起阅读librtmp的核心源码,但是在开始之前建议大家有一些RTMP协议的基本认识,我希望在完成本文的阅读之后,大家对librtmp的使用能上一个更高的层次。
下载源码:
在阅读源码前,请大家到官方网址下载:http://rtmpdump.mplayerhq.hu/download
最新的是2.3版本,作者已经很多年没有更新了,说明还是比较稳定的。
下源源码后,解压,文件树结构如下,部分文件我做了说明:
.
├── ChangeLog
├── COPYING
├── librtmp // rtmp协议的一个客户端库实现
│?? ├── amf.c // AMF序列化实现
│?? ├── amf.h // AMF序列化头文件
│?? ├── bytes.h
│?? ├── COPYING
│?? ├── dhgroups.h
│?? ├── dh.h // DH非对称加密算法调用封装
│?? ├── handshake.h // 加密版本的握手实现
│?? ├── hashswf.c // SWF哈希校验相关实现
│?? ├── http.h // HTTP请求头文件定义
│?? ├── librtmp.3
│?? ├── librtmp.3.html
│?? ├── librtmp.pc.in
│?? ├── log.c // 日志输出实现
│?? ├── log.h // 日志输出定义
│?? ├── Makefile
│?? ├── parseurl.c // RTMP网址解析实现
│?? ├── rtmp.c // RTMP主逻辑实现
│?? ├── rtmp.h // RTMP主逻辑头文件
│?? └── rtmp_sys.h
├── Makefile
├── README
├── rtmpdump.1
├── rtmpdump.1.html
├── rtmpdump.c // 一个很强大的FLV文件解析、拉流的例子
├── rtmpgw.8
├── rtmpgw.8.html
├── rtmpgw.c // 一个HTTP服务代理的实现
├── rtmpsrv.c // 一个简单的RTMP服务器的实现的基本框架
├── rtmpsuck.c
├── thread.c // 线程封装
└── thread.h // 线程封装头文件
编译的话一般执行make就可以了,需要openssl依赖,生成的库在librtmp子目录下。
主要结构定义:
在rtmp.h中(以下除非特殊说明,均指rtmp.h或rtmp.c),定义了4种块头格式:
#define RTMP_PACKET_SIZE_LARGE 0 // 对应于基本块头的0格式
#define RTMP_PACKET_SIZE_MEDIUM 1 // 对应于基本块头的1格式
#define RTMP_PACKET_SIZE_SMALL 2 // 对应于基本块头的2格式
#define RTMP_PACKET_SIZE_MINIMUM 3 // 对应于基本块头的3格式
4种格式的块头大小:
static const int packetSize[] = { 12, 8, 4, 1 };
整个块头的大小是不固定的,最小为1字节(基本块头为格式3,且块流ID大于1小于64时),最大为18字节(基本块头为格式0,且块流ID大于319,且时间戳大于0x00ffffff时)。这里packetSize定义的是4种块头格式的消息块头大小+1字节的基本块头。
块头结构定义:
typedef struct RTMPPacket
{
uint8_t m_headerType; // 块头格式
uint8_t m_packetType; // 命令类型
uint8_t m_hasAbsTimestamp; // 是否绝对时间
int m_nChannel; // 块流ID
uint32_t m_nTimeStamp; // 时间戳
int32_t m_nInfoField2; // 特殊字段,通常用于保存0x14号远程调用时的流ID
uint32_t m_nBodySize; // 负载大小
uint32_t m_nBytesRead; // 当前读取的负载大小,合包处理时使用
RTMPChunk *m_chunk; // 保存原始的chunk数据流
char *m_body; // 负载数据指针
} RTMPPacket;
RTMPPacket非常强大,它负责处理发送和接收过程中的协议解析、分包、合包等复杂逻辑。
RTMP套接字上下文:
typedef struct RTMPSockBuf
{
int sb_socket; // 套接字
int sb_size; // 缓冲区可读大小
char *sb_start; // 缓冲区读取位置
char sb_buf[RTMP_BUFFER_CACHE_SIZE]; // 套接字读取缓冲区
int sb_timedout; // 超时标志
void *sb_ssl; // TLS上下文
} RTMPSockBuf;
RTMP在与底层套接口通讯时,使用了这个与逻辑无关的读缓冲。
RTMP协议层连接上下文:
typedef struct RTMP_LNK
{
AVal hostname; // 目标主机地址
AVal sockshost; // socks代理地址
// 连接和推拉流涉及的一些参数信息
AVal playpath0; /* parsed from URL */
AVal playpath; /* passed in explicitly */
AVal tcUrl;
AVal swfUrl;
AVal pageUrl;
AVal app;
AVal auth;
AVal flashVer;
AVal subscribepath;
AVal token;
AMFObject extras;
int edepth;
int seekTime; // 播放流的开始时间
int stopTime; // 播放流的停止时间
#define RTMP_LF_AUTH 0x0001 /* using auth param */
#define RTMP_LF_LIVE 0x0002 /* stream is live */
#define RTMP_LF_SWFV 0x0004 /* do SWF verification */
#define RTMP_LF_PLST 0x0008 /* send playlist before play */
#define RTMP_LF_BUFX 0x0010 /* toggle stream on BufferEmpty msg */
#define RTMP_LF_FTCU 0x0020 /* free tcUrl on close */
int lFlags;
int swfAge;
int protocol; // 连接使用的协议
int timeout; // 连接超时时间
unsigned short socksport; // socks代理端口
unsigned short port; // 目标主机端口
#ifdef CRYPTO
#define RTMP_SWF_HASHLEN 32
void *dh; /* for encryption */
void *rc4keyIn;
void *rc4keyOut;
uint32_t SWFSize;
uint8_t SWFHash[RTMP_SWF_HASHLEN];
char SWFVerificationResponse[RTMP_SWF_HASHLEN+10];
#endif
} RTMP_LNK;
RTMP_LNK包含了连接的服务器地址,以及推拉流所需的各种参数信息,一些需要在连接前进行设置。
RTMP_Read()操作的附加上下文:
typedef struct RTMP_READ
{
char *buf; // 读取缓冲区
char *bufpos; // 缓冲区读取位置
unsigned int buflen; // 当前缓冲区数据的长度
uint32_t timestamp; // 读取的最新时间戳
uint8_t dataType; // 读取到的元数据媒体类型
uint8_t flags; // 读取标志集合
#define RTMP_READ_HEADER 0x01
#define RTMP_READ_RESUME 0x02
#define RTMP_READ_NO_IGNORE 0x04
#define RTMP_READ_GOTKF 0x08
#define RTMP_READ_GOTFLVK 0x10
#define RTMP_READ_SEEKING 0x20
int8_t status; // 当前读取的状态
#define RTMP_READ_COMPLETE -3
#define RTMP_READ_ERROR -2
#define RTMP_READ_EOF -1
#define RTMP_READ_IGNORE 0
/* if bResume == TRUE */
uint8_t initialFrameType;
uint32_t nResumeTS;
char *metaHeader;
char *initialFrame;
uint32_t nMetaHeaderSize;
uint32_t nInitialFrameSize;
uint32_t nIgnoredFrameCounter;
uint32_t nIgnoredFlvFrameCounter;
} RTMP_READ;
RTMP_READ结构定义了RTMP_Read()函数工作时需要的附加上下文和缓冲区。
RTMP_Read()与RTMP_ReadPacket()的主要区别是,RTMP_Read()返回的是FLV格式的流,它需要做两层操作,首先是解RTMP协议,其次是编码为FLV格式,而RTMP_ReadPacket()只需要执行一步。
0x14命令远程过程调用队列子项:
typedef struct RTMP_METHOD
{
AVal name; // 当前的调用过程名称
int num; // 操作流水号,初使为1,自增
} RTMP_METHOD;
处理所有RTMP操作的连接上下文:
typedef struct RTMP
{
int m_inChunkSize; // 最大接收块大小
int m_outChunkSize; // 最大发送块大小
int m_nBWCheckCounter; // 带宽检测计数器
int m_nBytesIn; // 接收数据计数器
int m_nBytesInSent; // 当前数据已回应计数器
int m_nBufferMS; // 当前缓冲的时间长度,以MS为单位
int m_stream_id; // 当前连接的流ID
int m_mediaChannel; // 当前连接媒体使用的块流ID
uint32_t m_mediaStamp; // 当前连接媒体最新的时间戳
uint32_t m_pauseStamp; // 当前连接媒体暂停时的时间戳
int m_pausing; // 是否暂停状态
int m_nServerBW; // 服务器带宽
int m_nClientBW; // 客户端带宽
uint8_t m_nClientBW2; // 客户端带宽调节方式
uint8_t m_bPlaying; // 当前是否推流或连接中
uint8_t m_bSendEncoding; // 连接服务器时发送编码
uint8_t m_bSendCounter; // 设置是否向服务器发送接收字节应答
int m_numInvokes; // 0x14命令远程过程调用计数
int m_numCalls; // 0x14命令远程过程请求队列数量
RTMP_METHOD *m_methodCalls; // 远程过程调用请求队列
RTMPPacket *m_vecChannelsIn[RTMP_CHANNELS]; // 对应块流ID上一次接收的报文
RTMPPacket *m_vecChannelsOut[RTMP_CHANNELS]; // 对应块流ID上一次发送的报文
int m_channelTimestamp[RTMP_CHANNELS]; // 对应块流ID媒体的最新时间戳
double m_fAudioCodecs; // 音频编码器代码
double m_fVideoCodecs; // 视频编码器代码
double m_fEncoding; /* AMF0 or AMF3 */
double m_fDuration; // 当前媒体的时长
int m_msgCounter; // 使用HTTP协议发送请求的计数器
int m_polling; // 使用HTTP协议接收消息主体时的位置
int m_resplen; // 使用HTTP协议接收消息主体时的未读消息计数
int m_unackd; // 使用HTTP协议处理时无响应的计数
AVal m_clientID; // 使用HTTP协议处理时的身份ID
RTMP_READ m_read; // RTMP_Read()操作的上下文
RTMPPacket m_write; // RTMP_Write()操作使用的可复用报文对象
RTMPSockBuf m_sb; // RTMP_ReadPacket()读包操作的上下文
RTMP_LNK Link; // RTMP连接上下文
} RTMP;
RTMP做为整个推拉流操作的上下文,从握手开始到关闭连接,它惯穿了整个会话的生存期。
主要函数实现:
报文操作:
RTMPPacket是librtmp收发报文的关键结构,基中m_body缓冲区是块头和负载公用的,这里有个技巧,请看代码:
// 为报文结构分配指定负载大小的内存
int
RTMPPacket_Alloc(RTMPPacket *p, int nSize)
{
// 这里多分配了18个字节的内存
char *ptr = calloc(1, nSize + RTMP_MAX_HEADER_SIZE);
if (!ptr)
return FALSE;
// 让负载指向内存的第19字节
p->m_body = ptr + RTMP_MAX_HEADER_SIZE;
p->m_nBytesRead = 0;
return TRUE;
}
// 释放负载内存
void
RTMPPacket_Free(RTMPPacket *p)
{
if (p->m_body)
{
free(p->m_body - RTMP_MAX_HEADER_SIZE);
p->m_body = NULL;
}
}
// 重置除负载内存以外的其它字段
void
RTMPPacket_Reset(RTMPPacket *p)
{
p->m_headerType = 0;
p->m_packetType = 0;
p->m_nChannel = 0;
p->m_nTimeStamp = 0;
p->m_nInfoField2 = 0;
p->m_hasAbsTimestamp = FALSE;
p->m_nBodySize = 0;
p->m_nBytesRead = 0;
}
RTMPPacket_Alloc()多分配18个字节的内存,其好处在于,发送报文时,头部和负载可以序列化在一段连续的缓冲区,理想情况下只需要执行一个Write调用。
RTMP上下文的初使化操作:
librtmp提供RTMP上下文内存分配、初使化、释放的函数,虽然也不复杂,但使用标准接口是个好习惯。
// 分配RTMP内存
RTMP *
RTMP_Alloc()
{
return calloc(1, sizeof(RTMP));
}
// 释放RTMP内存
void
RTMP_Free(RTMP *r)
{
free(r);
}
// 初使化RTMP内存
void
RTMP_Init(RTMP *r)
{
#ifdef CRYPTO
if (!RTMP_TLS_ctx)
RTMP_TLS_Init();
#endif
memset(r, 0, sizeof(RTMP)); // 这里将所有的内存置0
r->m_sb.sb_socket = -1;
r->m_inChunkSize = RTMP_DEFAULT_CHUNKSIZE; // 默认最大接收块限制128字节
r->m_outChunkSize = RTMP_DEFAULT_CHUNKSIZE; // 默认最大发送块限制128字节
r->m_nBufferMS = 30000; // 默认最大时长缓冲设置,需通知服务器
r->m_nClientBW = 2500000; // 默认最大客户端带宽
r->m_nClientBW2 = 2; // 默认最大客户端带宽的调整方式
r->m_nServerBW = 2500000; // 默认最大服务器带宽
r->m_fAudioCodecs = 3191.0;
r->m_fVideoCodecs = 252.0;
r->Link.timeout = 30; // 默认连接超时
r->Link.swfAge = 30;
}
参数设置:
大多数参数设置函数必须在连接服务器即RTMP_Connect()之前调用,否则可能不会生效。
// 设置推流操作选项,这样在RTMP_ConnectStream()操作内部,将使用推流请求代替拉流请求。
void
RTMP_EnableWrite(RTMP *r)
{
r->Link.protocol |= RTMP_FEATURE_WRITE;
}
// 设置服务器缓存的流时间长度
void
RTMP_SetBufferMS(RTMP *r, int size)
{
r->m_nBufferMS = size;
}
// 设置RTMP推拉流的完整地址
int RTMP_SetupURL(RTMP *r, char *url)
{
......
// 解析流地址
ret = RTMP_ParseURL(url, &r->Link.protocol, &r->Link.hostname,
&port, &r->Link.playpath0, &r->Link.app);
if (!ret)
return ret;
r->Link.port = port;
r->Link.playpath = r->Link.playpath0;
......
// 解析其他KV参数
while (ptr) {
*ptr++ = '\0';
p1 = ptr;
p2 = strchr(p1, '=');
if (!p2)
break;
opt.av_val = p1;
opt.av_len = p2 - p1;
*p2++ = '\0';
arg.av_val = p2;
ptr = strchr(p2, ' ');
if (ptr) {
*ptr = '\0';
arg.av_len = ptr - p2;
/* skip repeated spaces */
while(ptr[1] == ' ')
*ptr++ = '\0';
} else {
arg.av_len = strlen(p2);
}
arg.av_len = p2 - arg.av_val;
ret = RTMP_SetOpt(r, &opt, &arg);
if (!ret)
return ret;
}
......
}
连接和握手操作:
在完成选项设置后,接下来调用RTMP_Connect()进行RTMP的握手操作。握手完成后,客户端还需要主动发送connect远程过程调用,这些操作都封装在RTMP_Connect(),请看代码:
// 对用户开放的RTMP连接接口
int
RTMP_Connect(RTMP *r, RTMPPacket *cp)
{
struct sockaddr_in service;
if (!r->Link.hostname.av_len)
return FALSE;
memset(&service, 0, sizeof(struct sockaddr_in));
service.sin_family = AF_INET;
// 设置直接连接的服务器地址
if (r->Link.socksport)
{
/* Connect via SOCKS */
if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport))
return FALSE;
}
else
{
/* Connect directly */
if (!add_addr_info(&service, &r->Link.hostname, r->Link.port))
return FALSE;
}
// 发起网络连接
if (!RTMP_Connect0(r, (struct sockaddr *)&service))
return FALSE;
r->m_bSendCounter = TRUE;
// 发起握手协商
return RTMP_Connect1(r, cp);
}
// 执行基础网络连接
int
RTMP_Connect0(RTMP *r, struct sockaddr * service)
{
int on = 1;
r->m_sb.sb_timedout = FALSE;
r->m_pausing = 0;
r->m_fDuration = 0.0;
// 创建套接字
r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (r->m_sb.sb_socket != -1)
{
// 连接对端
if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0)
{
int err = GetSockError();
RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket. %d (%s)",
__FUNCTION__, err, strerror(err));
RTMP_Close(r);
return FALSE;
}
// 执行Socks协商
if (r->Link.socksport)
{
RTMP_Log(RTMP_LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__);
if (!SocksNegotiate(r))
{
RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
}
}
else
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__,
GetSockError());
return FALSE;
}
// 设置接收网络超时
{
SET_RCVTIMEO(tv, r->Link.timeout);
if (setsockopt
(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)))
{
RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
__FUNCTION__, r->Link.timeout);
}
}
setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on));
return TRUE;
}
// 继续执行SSL或HTTP协商,以及RTMP握手
int
RTMP_Connect1(RTMP *r, RTMPPacket *cp)
{
// SSL握手处理
if (r->Link.protocol & RTMP_FEATURE_SSL)
{
#if defined(CRYPTO) && !defined(NO_SSL)
TLS_client(RTMP_TLS_ctx, r->m_sb.sb_ssl);
TLS_setfd(r->m_sb.sb_ssl, r->m_sb.sb_socket);
if (TLS_connect(r->m_sb.sb_ssl) < 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
#else
RTMP_Log(RTMP_LOGERROR, "%s, no SSL/TLS support", __FUNCTION__);
RTMP_Close(r);
return FALSE;
#endif
}
// HTTP代理协商
if (r->Link.protocol & RTMP_FEATURE_HTTP)
{
r->m_msgCounter = 1;
r->m_clientID.av_val = NULL;
r->m_clientID.av_len = 0;
HTTP_Post(r, RTMPT_OPEN, "", 1);
HTTP_read(r, 1);
r->m_msgCounter = 0;
}
RTMP_Log(RTMP_LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__);
// RTMP握手
if (!HandShake(r, TRUE))
{
RTMP_Log(RTMP_LOGERROR, "%s, handshake failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
RTMP_Log(RTMP_LOGDEBUG, "%s, handshaked", __FUNCTION__);
// 发送第一个连接报文
if (!SendConnectPacket(r, cp))
{
RTMP_Log(RTMP_LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
return TRUE;
}
// SOCKS协商处理
static int
SocksNegotiate(RTMP *r)
{
unsigned long addr;
struct sockaddr_in service;
memset(&service, 0, sizeof(struct sockaddr_in));
add_addr_info(&service, &r->Link.hostname, r->Link.port);
addr = htonl(service.sin_addr.s_addr);
{
char packet[] = {
4, 1, /* SOCKS 4, connect */
(r->Link.port >> 8) & 0xFF,
(r->Link.port) & 0xFF,
(char)(addr >> 24) & 0xFF, (char)(addr >> 16) & 0xFF,
(char)(addr >> 8) & 0xFF, (char)addr & 0xFF,
0
}; /* NULL terminate */
WriteN(r, packet, sizeof packet);
if (ReadN(r, packet, 8) != 8)
return FALSE;
if (packet[0] == 0 && packet[1] == 90)
{
return TRUE;
}
else
{
RTMP_Log(RTMP_LOGERROR, "%s, SOCKS returned error code %d", packet[1]);
return FALSE;
}
}
}
connect远程调用:
在前面RTMP_Connect1()的最后一步,调用了SendConnectPacket()这个函数,实际上的用途就是发起0x14命令connect远程调用,代码摘录并简化如下:
static int
SendConnectPacket(RTMP *r, RTMPPacket *cp)
{
RTMPPacket packet;
char pbuf[4096], *pend = pbuf + sizeof(pbuf);
char *enc;
// 填写块头字段
packet.m_nChannel = 0x03; /* control channel (invoke) */
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = 0x14; /* INVOKE */
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
enc = packet.m_body;
// 压入connect命令和操作流水号
enc = AMF_EncodeString(enc, pend, &av_connect);
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_OBJECT;
// 压入对象的各个属性
enc = AMF_EncodeNamedString(enc, pend, &av_app, &r->Link.app);
if (!enc)
return FALSE;
if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
enc = AMF_EncodeNamedString(enc, pend, &av_type, &av_nonprivate);
if (!enc)
return FALSE;
}
if (r->Link.flashVer.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_flashVer, &r->Link.flashVer);
if (!enc)
return FALSE;
}
......
// 压入属性结束标记
*enc++ = 0;
*enc++ = 0; /* end of object - 0x00 0x00 0x09 */
*enc++ = AMF_OBJECT_END;
packet.m_nBodySize = enc - packet.m_body;
// 发送报文,并记入应答队列
return RTMP_SendPacket(r, &packet, TRUE);
}
然后,我们再看看RTMP_SendPacket()的实现,代码摘录并简化如下(相关逻辑的解释添加在源码中):
int
RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue)
{
// 取出对应块流ID上一次发送的报文
const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
uint32_t last = 0;
int nSize;
int hSize, cSize;
char *header, *hptr, *hend, hbuf[RTMP_MAX_HEADER_SIZE], c;
......
// 尝试对非LARGE报文进行字段压缩
if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
{
// MEDIUM报文可以尝试压缩为SMALL报文
if (prevPacket->m_nBodySize == packet->m_nBodySize
&& prevPacket->m_packetType == packet->m_packetType
&& packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM)
packet->m_headerType = RTMP_PACKET_SIZE_SMALL;
// MALL报文可以尝试压缩为MINIMUM报文
if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp
&& packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;
last = prevPacket->m_nTimeStamp;
}
......
// 根据压缩后的报文类型预设报头大小
nSize = packetSize[packet->m_headerType];
hSize = nSize; cSize = 0;
t = packet->m_nTimeStamp - last;
// 预设报头的缓冲区
if (packet->m_body)
{
header = packet->m_body - nSize;
hend = packet->m_body;
}
else
{
header = hbuf + 6;
hend = hbuf + sizeof(hbuf);
}
// 计算基本头的扩充大小
if (packet->m_nChannel > 319)
cSize = 2;
else if (packet->m_nChannel > 63)
cSize = 1;
if (cSize)
{
header -= cSize;
hSize += cSize;
}
// 根据时间戳计算是否需要扩充头大小
if (nSize > 1 && t >= 0xffffff)
{
header -= 4;
hSize += 4;
}
// 向缓冲区压入基本头
hptr = header;
c = packet->m_headerType << 6;
switch (cSize)
{
case 0:
c |= packet->m_nChannel;
break;
case 1:
break;
case 2:
c |= 1;
break;
}
*hptr++ = c;
if (cSize)
{
int tmp = packet->m_nChannel - 64;
*hptr++ = tmp & 0xff;
if (cSize == 2)
*hptr++ = tmp >> 8;
}
// 向缓冲区压入时间戳
if (nSize > 1)
{
hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
}
// 向缓冲区压入负载大小和报文类型
if (nSize > 4)
{
hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);
*hptr++ = packet->m_packetType;
}
// 向缓冲区压入流ID
if (nSize > 8)
hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);
// 向缓冲区压入扩展时间戳
if (nSize > 1 && t >= 0xffffff)
hptr = AMF_EncodeInt32(hptr, hend, t);
nSize = packet->m_nBodySize;
buffer = packet->m_body;
nChunkSize = r->m_outChunkSize;
// 当数据未发送完成时
while (nSize + hSize)
{
int wrote;
// 一次发送的最大负载限制为块大小
if (nSize < nChunkSize)
nChunkSize = nSize;
// 发送一个块
wrote = WriteN(r, header, nChunkSize + hSize);
if (!wrote)
return FALSE;
// 可能有分块,只有部分负载发送成功
nSize -= nChunkSize;
buffer += nChunkSize;
hSize = 0;
// 若只有部分负载发送成功,则需继续构造块再次发送
if (nSize > 0)
{
// 只需要构造3号类型的块头
header = buffer - 1;
hSize = 1;
if (cSize)
{
header -= cSize;
hSize += cSize;
}
*header = (0xc0 | c);
if (cSize)
{
int tmp = packet->m_nChannel - 64;
header[1] = tmp & 0xff;
if (cSize == 2)
header[2] = tmp >> 8;
}
}
}
// 如果是0x14远程调用,则需要解出调用名称,加入等待响应的队列中
if (packet->m_packetType == 0x14)
{
AVal method;
char *ptr;
ptr = packet->m_body + 1;
AMF_DecodeString(ptr, &method);
if (queue) {
int txn;
ptr += 3 + method.av_len;
txn = (int)AMF_DecodeNumber(ptr);
AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn);
}
}
// 记录这个块流ID刚刚发送的报文,但是应忽略负载
if (!r->m_vecChannelsOut[packet->m_nChannel])
r->m_vecChannelsOut[packet->m_nChannel] = malloc(sizeof(RTMPPacket));
memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));
return TRUE;
}
connect远程调用响应_result:
connect报文发出后,这时客户端会陷入等待状态,必须接收到服务器的_result响应才能执行后继的流程。librtmp库将等待响应的过程封装了,对使用者展现一个RTMP_ConnectStream()函数调用,代码如下:
int
RTMP_ConnectStream(RTMP *r, int seekTime)
{
RTMPPacket packet = { 0 };
// 设置起始时间定位
if (seekTime > 0)
r->Link.seekTime = seekTime;
r->m_mediaChannel = 0;
// 循环读取报文并等待完成推流或拉流的交互准备工作
while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet))
{
// 报文可读
if (RTMPPacket_IsReady(&packet))
{
if (!packet.m_nBodySize)
continue;
// 在所有的交互操作准备好之前,过滤非法的音视频报文
if ((packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) ||
(packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) ||
(packet.m_packetType == RTMP_PACKET_TYPE_INFO))
{
RTMP_Log(RTMP_LOGWARNING, "Received FLV packet before play()! Ignoring.");
RTMPPacket_Free(&packet);
continue;
}
// 进行准备工作期间的报文的分派处理
RTMP_ClientPacket(r, &packet);
RTMPPacket_Free(&packet);
}
}
// 返回是否准备好推拉流
return r->m_bPlaying;
}
在RTMP_ConnectStream()处理交互准备的过程中,有两个重要函数:RTMP_ReadPacket()负责接收报文,RTMP_ClientPacket()负责逻辑的分派处理。先看RTMP_ReadPacket()代码:
int
RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)
{
uint8_t hbuf[RTMP_MAX_HEADER_SIZE] = { 0 };
char *header = (char *)hbuf;
int nSize, hSize, nToRead, nChunk;
int didAlloc = FALSE;
// 读取基本块头的首个字节
if (ReadN(r, (char *)hbuf, 1) == 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__);
return FALSE;
}
// 解析基本块头的首个字节,取得报头类型和块流ID
packet->m_headerType = (hbuf[0] & 0xc0) >> 6;
packet->m_nChannel = (hbuf[0] & 0x3f);
header++;
if (packet->m_nChannel == 0)
{
// 2字节基本头,继续读取1个字节
if (ReadN(r, (char *)&hbuf[1], 1) != 1)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 2nd byte",
__FUNCTION__);
return FALSE;
}
packet->m_nChannel = hbuf[1];
packet->m_nChannel += 64;
header++;
}
else if (packet->m_nChannel == 1)
{
// 3字节基本头,继续读取2个字节
int tmp;
if (ReadN(r, (char *)&hbuf[1], 2) != 2)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 3nd byte",
__FUNCTION__);
return FALSE;
}
tmp = (hbuf[2] << 8) + hbuf[1];
packet->m_nChannel = tmp + 64;
RTMP_Log(RTMP_LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel);
header += 2;
}
// 根据报头类型取得块头长度
nSize = packetSize[packet->m_headerType];
// 如果是标准大头,设置时间戳为绝对的
if (nSize == RTMP_LARGE_HEADER_SIZE)
packet->m_hasAbsTimestamp = TRUE;
// 如果非标准大头,首次尝试拷贝上一次的报头
else if (nSize < RTMP_LARGE_HEADER_SIZE)
{
// 这里的拷贝操作有可能取得上次的分块报文,然后继续后续块的接收合并工作
if (r->m_vecChannelsIn[packet->m_nChannel])
memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel],
sizeof(RTMPPacket));
}
// 计算消息块头(主体块头)大小
nSize--;
// 读取消息块头
if (nSize > 0 && ReadN(r, header, nSize) != nSize)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header. type: %x",
__FUNCTION__, (unsigned int)hbuf[0]);
return FALSE;
}
// 计算基本块头+消息块头的大小
hSize = nSize + (header - (char *)hbuf);
if (nSize >= 3)
{
// 解析时间戳
packet->m_nTimeStamp = AMF_DecodeInt24(header);
if (nSize >= 6)
{
// 解析负载长度
packet->m_nBodySize = AMF_DecodeInt24(header + 3);
packet->m_nBytesRead = 0;
RTMPPacket_Free(packet);
if (nSize > 6)
{
// 解析包类型
packet->m_packetType = header[6];
// 解析流ID
if (nSize == 11)
packet->m_nInfoField2 = DecodeInt32LE(header + 7);
}
}
// 读取扩展时间戳并解析
if (packet->m_nTimeStamp == 0xffffff)
{
if (ReadN(r, header + nSize, 4) != 4)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read extended timestamp",
__FUNCTION__);
return FALSE;
}
packet->m_nTimeStamp = AMF_DecodeInt32(header + nSize);
hSize += 4;
}
}
// 负载非0,需要分配内存,或第一个分块的初使化工作
if (packet->m_nBodySize > 0 && packet->m_body == NULL)
{
if (!RTMPPacket_Alloc(packet, packet->m_nBodySize))
{
RTMP_Log(RTMP_LOGDEBUG, "%s, failed to allocate packet", __FUNCTION__);
return FALSE;
}
didAlloc = TRUE;
packet->m_headerType = (hbuf[0] & 0xc0) >> 6;
}
// 准备读取的数据和块大小
nToRead = packet->m_nBodySize - packet->m_nBytesRead;
nChunk = r->m_inChunkSize;
if (nToRead < nChunk)
nChunk = nToRead;
// 如果packet->m_chunk非空,拷贝当前块的相关信息
if (packet->m_chunk)
{
packet->m_chunk->c_headerSize = hSize;
memcpy(packet->m_chunk->c_header, hbuf, hSize);
packet->m_chunk->c_chunk = packet->m_body + packet->m_nBytesRead;
packet->m_chunk->c_chunkSize = nChunk;
}
// 读取负载到缓冲区中
if (ReadN(r, packet->m_body + packet->m_nBytesRead, nChunk) != nChunk)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet body. len: %lu",
__FUNCTION__, packet->m_nBodySize);
return FALSE;
}
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)packet->m_body + packet->m_nBytesRead, nChunk);
packet->m_nBytesRead += nChunk;
// 保存当前块流ID最新的报文,与RTMP_SendPacket()不同的是,负载部分也被保存了,以应对不完整的分块报文
if (!r->m_vecChannelsIn[packet->m_nChannel])
r->m_vecChannelsIn[packet->m_nChannel] = malloc(sizeof(RTMPPacket));
memcpy(r->m_vecChannelsIn[packet->m_nChannel], packet, sizeof(RTMPPacket));
// 若报文负载接收完整
if (RTMPPacket_IsReady(packet))
{
// 处理增量时间戳
if (!packet->m_hasAbsTimestamp)
packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel];
// 保存当前块流ID的时间戳
r->m_channelTimestamp[packet->m_nChannel] = packet->m_nTimeStamp;
// 清理上下文中当前块流ID最新的报文的负载信息
r->m_vecChannelsIn[packet->m_nChannel]->m_body = NULL;
r->m_vecChannelsIn[packet->m_nChannel]->m_nBytesRead = 0;
r->m_vecChannelsIn[packet->m_nChannel]->m_hasAbsTimestamp = FALSE;
}
else
{
// 若报文不完整,不将分片负载向上抛给应用,以免引起使用误解
packet->m_body = NULL;
}
return TRUE;
}
再看RTMP_ClientPacket()源码:
int
RTMP_ClientPacket(RTMP *r, RTMPPacket *packet)
{
// 根据命令类型进行分派处理
int bHasMediaPacket = 0;
switch (packet->m_packetType)
{
case 0x01:
// 更新接收处理时的块限制
HandleChangeChunkSize(r, packet);
break;
case 0x03:
// 对端反馈的已读大小
RTMP_Log(RTMP_LOGDEBUG, "%s, received: bytes read report", __FUNCTION__);
break;
case 0x04:
// 处理对端发送的控制报文
HandleCtrl(r, packet);
break;
case 0x05:
// 处理对端发送的应答窗口大小,这里由服务器发送,即告之客户端收到对应大小的数据后应发送反馈
HandleServerBW(r, packet);
break;
case 0x06:
// 处理对端发送的设置发送带宽大小,这里由服务器发送,即设置客户端的发送带宽
HandleClientBW(r, packet);
break;
case 0x08:
// 处理音频数据
HandleAudio(r, packet);
bHasMediaPacket = 1;
if (!r->m_mediaChannel)
r->m_mediaChannel = packet->m_nChannel;
if (!r->m_pausing)
r->m_mediaStamp = packet->m_nTimeStamp;
break;
case 0x09:
// 处理视频数据
HandleVideo(r, packet);
bHasMediaPacket = 1;
if (!r->m_mediaChannel)
r->m_mediaChannel = packet->m_nChannel;
if (!r->m_pausing)
r->m_mediaStamp = packet->m_nTimeStamp;
break;
case 0x12:
// 处理媒体元数据
if (HandleMetadata(r, packet->m_body, packet->m_nBodySize))
bHasMediaPacket = 1;
break;
case 0x14:
// 处理远程调用
if (HandleInvoke(r, packet->m_body, packet->m_nBodySize) == 1)
bHasMediaPacket = 2;
break;
}
// 返回值为1表示推拉流正在正作中,为2表示已经停止
return bHasMediaPacket;
}
在RTMP_ConnectStream()中,RTMP_ReadPacket()接收的报文,交给RTMP_ClientPacket()进行分派。connect远程调用发出后的_result响应,也属于0x14命令,我们需要继续解析HandleInvoke()这个函数,代码如下:
static int
HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
{
AMFObject obj;
AVal method;
int txn;
int ret = 0, nRes;
// 确保响应报文是0x14的命令字
if (body[0] != 0x02)
{
RTMP_Log(RTMP_LOGWARNING, "%s, Sanity failed. no string method in invoke packet",
__FUNCTION__);
return 0;
}
// 将各参数以无名称的对象属性方式进行解析
nRes = AMF_Decode(&obj, body, nBodySize, FALSE);
if (nRes < 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, error decoding invoke packet", __FUNCTION__);
return 0;
}
AMF_Dump(&obj);
// 获取过程名称和流水号
AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method);
txn = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 1));
RTMP_Log(RTMP_LOGDEBUG, "%s, server invoking <%s>", __FUNCTION__, method.av_val);
// 过程名称为_result
if (AVMATCH(&method, &av__result))
{
AVal methodInvoked = {0};
int i;
// 删除请求队列中的流水项
for (i=0; im_numCalls; i++) {
if (r->m_methodCalls[i].num == txn) {
methodInvoked = r->m_methodCalls[i].name;
AV_erase(r->m_methodCalls, &r->m_numCalls, i, FALSE);
break;
}
}
if (!methodInvoked.av_val) {
RTMP_Log(RTMP_LOGDEBUG, "%s, received result id %d without matching request",
__FUNCTION__, txn);
goto leave;
}
RTMP_Log(RTMP_LOGDEBUG, "%s, received result for method call <%s>", __FUNCTION__,
methodInvoked.av_val);
// 找到了连接请求,确认是连接响应
if (AVMATCH(&methodInvoked, &av_connect))
{
// 客户端推流
if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
// 通知服务器释放流通道和清理推流资源
SendReleaseStream(r);
SendFCPublish(r);
}
// 客户端拉流
else
{
// 设置服务器的应答窗口大小
RTMP_SendServerBW(r);
RTMP_SendCtrl(r, 3, 0, 300);
}
// 发送创建流通道请求
RTMP_SendCreateStream(r);
}
// 找到了创建流请求,确认是创建流的响应
else if (AVMATCH(&methodInvoked, &av_createStream))
{
// 从响应中取流ID
r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));
// 客户端推流
if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
// 发送推流点
SendPublish(r);
}
// 客户端拉流
else
{
// 发送拉流点
SendPlay(r);
// 发送拉流缓冲时长
RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS);
}
}
// 找到了推流和拉流请求,确认是它们的响应
else if (AVMATCH(&methodInvoked, &av_play) ||
AVMATCH(&methodInvoked, &av_publish))
{
// 标识已经进入流状态
r->m_bPlaying = TRUE;
}
free(methodInvoked.av_val);
}
// 过程名称为ping
else if (AVMATCH(&method, &av_ping))
{
// 发送pong响应
SendPong(r, txn);
}
// 过程名称为_error
else if (AVMATCH(&method, &av__error))
{
RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
}
// 过程名称为close
else if (AVMATCH(&method, &av_close))
{
RTMP_Log(RTMP_LOGERROR, "rtmp server requested close");
RTMP_Close(r);
}
// 过程名称为onStatus
else if (AVMATCH(&method, &av_onStatus))
{
// 获取返回对象及其主要属性
AMFObject obj2;
AVal code, level;
AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code);
AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level);
RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val);
// 出错返回
if (AVMATCH(&code, &av_NetStream_Failed)
|| AVMATCH(&code, &av_NetStream_Play_Failed)
|| AVMATCH(&code, &av_NetStream_Play_StreamNotFound)
|| AVMATCH(&code, &av_NetConnection_Connect_InvalidApp))
{
r->m_stream_id = -1;
RTMP_Close(r);
RTMP_Log(RTMP_LOGERROR, "Closing connection: %s", code.av_val);
}
// 启动拉流成功
else if (AVMATCH(&code, &av_NetStream_Play_Start))
{
int i;
r->m_bPlaying = TRUE;
for (i = 0; i < r->m_numCalls; i++)
{
if (AVMATCH(&r->m_methodCalls[i].name, &av_play))
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
break;
}
}
}
// 启动推流成功
else if (AVMATCH(&code, &av_NetStream_Publish_Start))
{
int i;
r->m_bPlaying = TRUE;
for (i = 0; i < r->m_numCalls; i++)
{
if (AVMATCH(&r->m_methodCalls[i].name, &av_publish))
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
break;
}
}
}
// 通知流完成或结束
else if (AVMATCH(&code, &av_NetStream_Play_Complete)
|| AVMATCH(&code, &av_NetStream_Play_Stop)
|| AVMATCH(&code, &av_NetStream_Play_UnpublishNotify))
{
RTMP_Close(r);
ret = 1;
}
// 通知流暂停
else if (AVMATCH(&code, &av_NetStream_Pause_Notify))
{
if (r->m_pausing == 1 || r->m_pausing == 2)
{
RTMP_SendPause(r, FALSE, r->m_pauseStamp);
r->m_pausing = 3;
}
}
}
leave:
AMF_Reset(&obj);
return ret;
}
上面的注释显示,在HandleInvoke()函数的处理分支中,有涉及connect调用的_result处理。在收到_result响应后,又根据推流或拉流的标志,继续后续的流程。
HandleInvoke()函数非常强大,除了对各种_result进行处理外,它还支持onStatus操作,后续的createStream响应,publish和play推拉流的状态反馈,都会集中在这里处理。
connect远程调用的其它交互:
connect和_result交互是必须的,事实上,在_result返回之前,服务器还可以先返回一些可选的设置报文,例如:
服务器设置客户端的应答窗口大小:
int
RTMP_ClientPacket(RTMP *r, RTMPPacket *packet)
{
......
switch (packet->m_packetType)
{
......
case 0x05:
/* server bw */
HandleServerBW(r, packet);
break;
......
}
static void
HandleServerBW(RTMP *r, const RTMPPacket *packet)
{
r->m_nServerBW = AMF_DecodeInt32(packet->m_body);
RTMP_Log(RTMP_LOGDEBUG, "%s: server BW = %d", __FUNCTION__, r->m_nServerBW);
}
服务器设置客户端的发送带宽大小:
int
RTMP_ClientPacket(RTMP *r, RTMPPacket *packet)
{
......
switch (packet->m_packetType)
{
......
case 0x06:
/* client bw */
HandleClientBW(r, packet);
break;
......
}
static void
HandleClientBW(RTMP *r, const RTMPPacket *packet)
{
r->m_nClientBW = AMF_DecodeInt32(packet->m_body);
if (packet->m_nBodySize > 4)
r->m_nClientBW2 = packet->m_body[4];
else
r->m_nClientBW2 = -1;
RTMP_Log(RTMP_LOGDEBUG, "%s: client BW = %d %d", __FUNCTION__, r->m_nClientBW,
r->m_nClientBW2);
}
服务器设置客户端的接收块大小:
int
RTMP_ClientPacket(RTMP *r, RTMPPacket *packet)
{
......
switch (packet->m_packetType)
{
case 0x01:
/* chunk size */
HandleChangeChunkSize(r, packet);
break;
......
}
static void
HandleChangeChunkSize(RTMP *r, const RTMPPacket *packet)
{
if (packet->m_nBodySize >= 4)
{
r->m_inChunkSize = AMF_DecodeInt32(packet->m_body);
RTMP_Log(RTMP_LOGDEBUG, "%s, received: chunk size change to %d", __FUNCTION__,
r->m_inChunkSize);
}
}
createStream远程调用:
在成功处理connect的_result响应之后,即表示服务器接收了客户端的第一步的地址请求,接下来客户端需要根据推流或拉流的场景,发起后续的请求了,精简HandleInvoke()后的代码如下:
static int
HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
{
if (AVMATCH(&method, &av__result))
{
......
if (AVMATCH(&methodInvoked, &av_connect))
{
// 判断推流标志是否设置
if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
// 推流准备(必须)
SendReleaseStream(r);
SendFCPublish(r);
}
else
{
// 拉流准备(可选)
RTMP_SendServerBW(r);
RTMP_SendCtrl(r, 3, 0, 300);
}
// 创建流通道
RTMP_SendCreateStream(r);
}
}
......
}
上面的代码和注释显示,无论是推流还是拉流,都需要创建流通道,但是在之前,有一些不同的额外操作要设置。
推流的额外操作:
推流之前,需要先释放掉当前的推流点的资源,并且准备好新的推流点。
// 发送释放推流点请求
static int
SendReleaseStream(RTMP *r)
{
RTMPPacket packet;
char pbuf[1024], *pend = pbuf + sizeof(pbuf);
char *enc;
packet.m_nChannel = 0x03; /* control channel (invoke) */
packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet.m_packetType = 0x14; /* INVOKE */
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
// 压入远程过程调用的参数,尤其是推流点
enc = packet.m_body;
enc = AMF_EncodeString(enc, pend, &av_releaseStream);
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_NULL;
enc = AMF_EncodeString(enc, pend, &r->Link.playpath); // 推流点
if (!enc)
return FALSE;
packet.m_nBodySize = enc - packet.m_body;
return RTMP_SendPacket(r, &packet, FALSE);
}
// 发送准备推流点请求
static int
SendFCPublish(RTMP *r)
{
RTMPPacket packet;
char pbuf[1024], *pend = pbuf + sizeof(pbuf);
char *enc;
packet.m_nChannel = 0x03; /* control channel (invoke) */
packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet.m_packetType = 0x14; /* INVOKE */
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
// 压入远程过程调用的参数,尤其是推流点
enc = packet.m_body;
enc = AMF_EncodeString(enc, pend, &av_FCPublish);
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_NULL;
enc = AMF_EncodeString(enc, pend, &r->Link.playpath); // 推流点
if (!enc)
return FALSE;
packet.m_nBodySize = enc - packet.m_body;
return RTMP_SendPacket(r, &packet, FALSE);
}
拉流的额外操作:
通知服务器收到指定大小的客户端数据后,需要发送应答。拉流时客户端基本不发送流量,个人感觉这步有些多余。
int
RTMP_SendServerBW(RTMP *r)
{
RTMPPacket packet;
char pbuf[256], *pend = pbuf + sizeof(pbuf);
packet.m_nChannel = 0x02; /* control channel (invoke) */
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = 0x05; /* Server BW */
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
packet.m_nBodySize = 4;
// 压入4字节带宽
AMF_EncodeInt32(packet.m_body, pend, r->m_nServerBW);
return RTMP_SendPacket(r, &packet, FALSE);
}
// 控制报文的含义比较多,具体也需要查看手册,对应拉流来说,主要是设置服务器的缓存时间。
int
RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime)
{
RTMPPacket packet;
char pbuf[256], *pend = pbuf + sizeof(pbuf);
int nSize;
char *buf;
packet.m_nChannel = 0x02; /* control channel (ping) */
packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet.m_packetType = 0x04; /* ctrl */
packet.m_nTimeStamp = 0; /* RTMP_GetTime(); */
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
switch(nType) {
case 0x03: nSize = 10; break; /* buffer time */
case 0x1A: nSize = 3; break; /* SWF verify request */
case 0x1B: nSize = 44; break; /* SWF verify response */
default: nSize = 6; break;
}
packet.m_nBodySize = nSize;
buf = packet.m_body;
buf = AMF_EncodeInt16(buf, pend, nType);
if (nType == 0x1B)
{
......
}
else if (nType == 0x1A)
{
......
}
else
{
if (nSize > 2)
buf = AMF_EncodeInt32(buf, pend, nObject);
if (nSize > 6)
buf = AMF_EncodeInt32(buf, pend, nTime);
}
return RTMP_SendPacket(r, &packet, FALSE);
}
创建流通道操作:
// 发送创建流请求
int
RTMP_SendCreateStream(RTMP *r)
{
RTMPPacket packet;
char pbuf[256], *pend = pbuf + sizeof(pbuf);
char *enc;
packet.m_nChannel = 0x03; /* control channel (invoke) */
packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet.m_packetType = 0x14; /* INVOKE */
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
// 压入远程过程调用的参数
enc = packet.m_body;
enc = AMF_EncodeString(enc, pend, &av_createStream);
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_NULL; /* NULL */
packet.m_nBodySize = enc - packet.m_body;
return RTMP_SendPacket(r, &packet, TRUE);
}
createStream远程调用响应:
服务器收到createStream请求后,若无错误,则返回流ID,有了流ID客户端就可以进行后续的推流或拉流了,逻辑如下:
static int
HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
{
if (AVMATCH(&method, &av__result))
{
......
else if (AVMATCH(&methodInvoked, &av_createStream))
{
// 保存流ID
r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));
if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
// 开始推流
SendPublish(r);
}
else
{
// 开始拉流
SendPlay(r);
// 控制该流的缓冲时长
RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS);
}
}
......
}
}
上面的代码和注释显示,推流和拉流,分别进行不同的逻辑处理。
推流处理publish调用:
对推流来说,客户端需要请求服务器将流ID和推送点进行关联。请求代码如下:
static int
SendPublish(RTMP *r)
{
RTMPPacket packet;
char pbuf[1024], *pend = pbuf + sizeof(pbuf);
char *enc;
packet.m_nChannel = 0x04; /* source channel (invoke) */
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = 0x14; /* INVOKE */
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = r->m_stream_id; // 指定流ID
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
enc = packet.m_body;
enc = AMF_EncodeString(enc, pend, &av_publish);
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_NULL;
enc = AMF_EncodeString(enc, pend, &r->Link.playpath); // 指定推送点
if (!enc)
return FALSE;
/* FIXME: should we choose live based on Link.lFlags & RTMP_LF_LIVE? */
enc = AMF_EncodeString(enc, pend, &av_live);
if (!enc)
return FALSE;
packet.m_nBodySize = enc - packet.m_body;
return RTMP_SendPacket(r, &packet, TRUE); // 需要反馈
}
拉流处理play调用:
对拉流来说,客户端需要请求服务器将流ID和播放点进行关联。请求代码如下:
static int
SendPlay(RTMP *r)
{
RTMPPacket packet;
char pbuf[1024], *pend = pbuf + sizeof(pbuf);
char *enc;
packet.m_nChannel = 0x08; /* we make 8 our stream channel */
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = 0x14; /* INVOKE */
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = r->m_stream_id; /*0x01000000; */ // 指定流ID
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
enc = packet.m_body;
enc = AMF_EncodeString(enc, pend, &av_play);
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_NULL;
RTMP_Log(RTMP_LOGDEBUG, "%s, seekTime=%d, stopTime=%d, sending play: %s",
__FUNCTION__, r->Link.seekTime, r->Link.stopTime,
r->Link.playpath.av_val);
enc = AMF_EncodeString(enc, pend, &r->Link.playpath); // 指定推送点
if (!enc)
return FALSE;
// 指定开始时间
/* Optional parameters start and len.
*
* start: -2, -1, 0, positive number
* -2: looks for a live stream, then a recorded stream,
* if not found any open a live stream
* -1: plays a live stream
* >=0: plays a recorded streams from 'start' milliseconds
*/
if (r->Link.lFlags & RTMP_LF_LIVE)
enc = AMF_EncodeNumber(enc, pend, -1000.0);
else
{
if (r->Link.seekTime > 0.0)
enc = AMF_EncodeNumber(enc, pend, r->Link.seekTime); /* resume from here */
else
enc = AMF_EncodeNumber(enc, pend, 0.0); /*-2000.0);*/ /* recorded as default, -2000.0 is not reliable since that freezes the
player if the stream is not found */
}
if (!enc)
return FALSE;
// 指点播放时长
/* len: -1, 0, positive number
* -1: plays live or recorded stream to the end (default)
* 0: plays a frame 'start' ms away from the beginning
* >0: plays a live or recoded stream for 'len' milliseconds
*/
/*enc += EncodeNumber(enc, -1.0); */ /* len */
if (r->Link.stopTime)
{
enc = AMF_EncodeNumber(enc, pend, r->Link.stopTime - r->Link.seekTime);
if (!enc)
return FALSE;
}
packet.m_nBodySize = enc - packet.m_body;
return RTMP_SendPacket(r, &packet, TRUE); // 需要反馈
}
publish或play状态反馈:
在前面完成publish或play过程调用后,客户端需要等待响应或结果反馈。继续看HandleInvoke()函数精简后的相关代码:
static int
HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
{
......
else if (AVMATCH(&method, &av_onStatus))
{
AMFObject obj2;
AVal code, level;
AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code);
AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level);
RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val);
// 判断是否出错,出错需中止流程
if (AVMATCH(&code, &av_NetStream_Failed)
|| AVMATCH(&code, &av_NetStream_Play_Failed)
|| AVMATCH(&code, &av_NetStream_Play_StreamNotFound)
|| AVMATCH(&code, &av_NetConnection_Connect_InvalidApp))
{
r->m_stream_id = -1;
RTMP_Close(r);
RTMP_Log(RTMP_LOGERROR, "Closing connection: %s", code.av_val);
}
......
// 启动推流成功
else if (AVMATCH(&code, &av_NetStream_Publish_Start))
{
int i;
r->m_bPlaying = TRUE; // 设置可以推流标志
for (i = 0; i < r->m_numCalls; i++)
{
if (AVMATCH(&r->m_methodCalls[i].name, &av_publish))
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
break;
}
}
}
// 启动拉流成功
else if (AVMATCH(&code, &av_NetStream_Play_Start))
{
int i;
r->m_bPlaying = TRUE; // 设置可以拉流标志
for (i = 0; i < r->m_numCalls; i++)
{
if (AVMATCH(&r->m_methodCalls[i].name, &av_play))
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
break;
}
}
}
......
}
......
}
让我们回到RTMP_ConnectStream()函数,我们留意其中的m_bPlaying标志,由于此时标志为TRUE,循环条件不成立,函数退出。简化代码如下:
int
RTMP_ConnectStream(RTMP *r, int seekTime)
{
......
while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet))
{
......
}
return r->m_bPlaying; // 返回推流或拉流准许的状态
}
当RTMP_ConnectStream()返回TRUE的时候,librtmp库的使用者,就可以在自己的代码层次,进行后续的推流或拉流操作了。
推流操作代码节选:
// 初使化RTMP报文
RTMPPacket packet;
RTMPPacket_Reset(&packet);
packet.m_body = NULL;
packet.m_chunk = NULL;
packet.m_nInfoField2 = pRTMP->m_stream_id;
uint32_t starttime = RTMP_GetTime();
while (true)
{
// 读取TAG头
uint8_t type = 0;
if (!ReadU8(&type, pFile))
break;
uint32_t datalen = 0;
if (!ReadU24(&datalen, pFile))
break;
uint32_t timestamp = 0;
if (!ReadTime(×tamp, pFile))
break;
uint32_t streamid = 0;
if (!ReadU24(&streamid, pFile))
break;
/*
// 跳过0x12 Script
if (type != 0x08 && type != 0x09)
{
fseek(pFile, datalen + 4, SEEK_CUR);
continue;
}
*/
RTMPPacket_Alloc(&packet, datalen);
if (fread(packet.m_body, 1, datalen, pFile) != datalen)
break;
// 组织报文并发送
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = type;
packet.m_hasAbsTimestamp = 0;
packet.m_nChannel = 6;
packet.m_nTimeStamp = timestamp;
packet.m_nBodySize = datalen;
if (!RTMP_SendPacket(pRTMP, &packet, 0))
{
printf("Send Error! \n");
break;
}
printf("send type:[%d] timestamp:[%d] datasize:[%d] \n", type, timestamp, datalen);
// 跳过PreTag
uint32_t pretagsize = 0;
if (!ReadU32(&pretagsize, pFile))
break;
// 延时,避免发送太快
uint32_t timeago = (RTMP_GetTime() - starttime);
if (timestamp > 1000 && timeago < timestamp - 1000)
{
printf("sleep...\n");
usleep(100000);
}
RTMPPacket_Free(&packet);
}
上面这段代码片断演示了使用librtmp推流,读取FLV文件,并向上推流的例子。
拉流操作代码节选:
bool bSaveMP3 = true;
FILE* pFile = fopen(bSaveMP3 ? "testrtmp.mp3" : "testrtmp.flv", "wb");
while (RTMP_IsConnected(pRTMP))
{
if (bSaveMP3)
{
RTMPPacket packet;
RTMPPacket_Reset(&packet);
packet.m_body = NULL;
packet.m_chunk = NULL;
b = RTMP_ReadPacket(pRTMP, &packet);
if (!b)
break;
if (!RTMPPacket_IsReady(&packet))
continue;
printf("\t headerType:[%d] \n", packet.m_headerType);
printf("\t packetType:[%d] \n", packet.m_packetType);
printf("\t hasAbsTimestamp:[%d] \n", packet.m_hasAbsTimestamp);
printf("\t nChannel:[%d] \n", packet.m_nChannel);
printf("\t nTimeStamp:[%d] \n", packet.m_nTimeStamp);
printf("\t nInfoField2:[%d] \n", packet.m_nInfoField2);
printf("\t nBodySize:[%d] \n", packet.m_nBodySize);
printf("\t nBytesRead:[%d] \n", packet.m_nBytesRead);
if (packet.m_packetType == 0x08)
{
fwrite(packet.m_body + 1, 1, packet.m_nBodySize - 1, pFile);
}
RTMPPacket_Free(&packet);
}
else
{
char sBuf[4096] = {0};
int bytes = RTMP_Read(pRTMP, sBuf, sizeof(sBuf));
printf("RTMP_Read() ret:[%d] \n", bytes);
if (bytes <= 0)
break;
fwrite(sBuf, 1, bytes, pFile);
}
}
上面这段代码片断演示了使用librtmp拉流,并将音频保存为MP3,或者将音视频保存为FLV的例子。
小结:
源代码解析到这一步,我想应该大部分的关键流程都带过一遍了,但是签于我的时间和水平的关系,很多细节目前都还是浅尝辄止,后面若有机会再慢慢补上吧,希望朋友们理解。