最近在研究OBS源码,里面有一个很重要的模块是推流模块,OBS是使用RTMP进行推流的,源码里面也有RTMP的源码,翻了一下目前网上没有详细的RTMP源码注释,所以这里基于OBS项目,来详细讲一下RTMP源码包括内核数据结构、公共函数接口功能。关于具体的RTMP协议,网上有很多RTMP协议可以找到这里只做简单介绍,重点是代码的注释分析。关于RTMP源码的内核结构体,在代码中涉及的我会有标注,在另一个博文中具体分析了核心结构体注释。
接口比较多写的比较细,文章比较长,有些函数体中无效的代码(例如 log日志、容错代码我会省略)耐心看哈哈哈。
小弟写的比较辛苦给个关注吧
这里所有的实际测试推流操作均为向斗鱼上推流(因为我一直用它看直播哈)
RTMP协议是应用层协议,是要靠底层可靠的传输层协议(通常是TCP)来保证信息传输的可靠性的。在基于传输层协议的链接建立完成后,RTMP协议也要客户端和服务器通过“握手”来建立基于传输层链接。
当点击OBS开始推流时(后续在我的其他博客上会逐步讲解OBS推流过程和抓取数据过程),调用函数rtmp_stream_start(void *data)
增加线程connect_thread
开始进行网络连接。connect_thread(void *data)->try_connect(struct rtmp_stream *stream)、 RTMP_Init(RTMP *r)、 RTMP_SetupURL(RTMP *r, char *url、 RTMP_EnableWrite(RTMP *r)、 RTMP_AddStream(RTMP *r, const char *playpath)、 RTMP_Connect(RTMP *r, RTMPPacket *cp)、 RTMP_ConnectStream(RTMP *r, int seekTime)、 init_send(struct rtmp_stream *stream)
这些事主要的接口。
static int try_connect(struct rtmp_stream *stream)
//rtmp_stream 是obs定义的对接rtmp的结构体
{
//*******************************重点注释********************************
//stream->rtmp 是RTMP源码的内核结构体在另一个博文中有讲
//stream->path 流媒体服务器地址(例如:rtmp://sendtc3.douyu.com/live)
//stream->key 流媒体资源(推流码)(例如:309C23E747986)
//在OBS设置中写入,当下载一个斗鱼直播软件后打开会自动产生流媒体服务器地址和流媒体资源(推流码)
//一般斗鱼给的是推流码(一串数字)
//这个是关键,三次握手服务器资源识别是需要这个的
if (dstr_is_empty(&stream->path)) {
//检查服务器地址正确性
warn("URL is empty");
return OBS_OUTPUT_BAD_PATH;
}
//RTMP rtmp初始化(开辟内存空间+memset赋值“0”)
//小Tips:C语言常用写法 malloc+memset 开辟内存空间
RTMP_Init(&stream->rtmp);
//解析URL地址
//内部RTMP_ParseURL作用为赋值 r->Link.protocol= RTMP_PROTOCOL_RTMP
if (!RTMP_SetupURL(&stream->rtmp, stream->path.array))
return OBS_OUTPUT_BAD_PATH;
RTMP_EnableWrite(&stream->rtmp); //允许推送 r->Link.protocol |= RTMP_FEATURE_WRITE;
...
//拷贝一些参数,但由于初始化时memset为0;拷贝过去也是0
set_rtmp_dstr(&stream->rtmp.Link.pubUser, &stream->username);
set_rtmp_dstr(&stream->rtmp.Link.pubPasswd, &stream->password);
set_rtmp_dstr(&stream->rtmp.Link.flashVer, &stream->encoder_name);
stream->rtmp.Link.swfUrl = stream->rtmp.Link.tcUrl; // tcUrl服务器的URL地址
if (dstr_is_empty(&stream->bind_ip) ||
dstr_cmp(&stream->bind_ip, "default") == 0) {
memset(&stream->rtmp.m_bindIP, 0, sizeof(stream->rtmp.m_bindIP));
} else {
bool success = netif_str_to_addr(&stream->rtmp.m_bindIP.addr,
&stream->rtmp.m_bindIP.addrLen,
stream->bind_ip.array);
if (success) {
int len = stream->rtmp.m_bindIP.addrLen;
bool ipv6 = len == sizeof(struct sockaddr_in6);
info("Binding to IPv%d", ipv6 ? 6 : 4);
}
}
//初始化一个stream id 将流媒体资源(或推流码)绑定到一个stream id上
//path 流媒体服务器地址(例如:rtmp://sendtc3.douyu.com/live)
//key 流媒体资源(推流码)
RTMP_AddStream(&stream->rtmp, stream->key.array);
for (size_t idx = 1;; idx++) {
obs_encoder_t *encoder = obs_output_get_audio_encoder(
stream->output, idx);
const char *encoder_name;
if (!encoder)
break; //程序走到这跳出
encoder_name = obs_encoder_get_name(encoder);
RTMP_AddStream(&stream->rtmp, encoder_name);
}
stream->rtmp.m_outChunkSize = 4096; //chunk一次读取的(chunk data 即playload)赋默认值
stream->rtmp.m_bSendChunkSizeInfo = true; //发送消息的标志位
stream->rtmp.m_bUseNagle = true;
....
if (!RTMP_Connect(&stream->rtmp, NULL)) {
set_output_error(stream);
return OBS_OUTPUT_CONNECT_FAILED;
}
if (!RTMP_ConnectStream(&stream->rtmp, 0))
return OBS_OUTPUT_INVALID_STREAM;
info("Connection to %s successful", stream->path.array);
return init_send(stream); //RTMP rtmp的socket 已经connect成功 + RTMP_ConnectStream已经成功
}
重要接口详细注释
void RTMP_Init(RTMP *r) //RTMP *r: (&stream->rtmp)上文提到的
{
#ifdef CRYPTO
if