精华内容
下载资源
问答
  • [obs 集成rtmp] rtmp 拾遗

    2021-03-15 11:26:52
    G:\GERRIT\src\livecloud\obs-studio-24.0.6\plugins\obs-outputs\librtmp\rtmp.c c 代码也容易集成到c++ 项目中来。 obsrtmp 插件,是推流用的。因此调用了encoder 编码 通过循环缓存给到rtmp 复用 av , rtmp...

    • 由于一直在用srs的librtmp 模块
    • RTMPDUMP的librtmp 没仔细看过。
    • obs使用的是这个 ,发现纯c的风格实际上比srs的要容易理解。
      • G:\GERRIT\src\livecloud\obs-studio-24.0.6\plugins\obs-outputs\librtmp\rtmp.c
    • c 代码也容易集成到c++ 项目中来。
    • obs的rtmp 插件,是推流用的。因此调用了encoder 编码
    • 通过循环缓存给到rtmp 复用 av ,
    • rtmp传输相关的码率控制 丢帧控制在rtmp-stream里 ,在这篇文章

    RTMP

    在这里插入图片描述

    RTMP_LNK

    • 支持多个流
      RTMP_Stream streams[RTMP_MAX_STREAMS];
      int nStreams;  
    展开全文
  • OBS-rtmp源码剖析之rtmp常用结构体介绍(一)OBS-rtmp源码剖析之rtmp客户端通信介绍(二)OBS-rtmp源码剖析之rtmp客户端通信介绍(三)OBS-rtmp源码剖析之rtmp网络数据流读写操作(四)OBS-rtmp源码剖析之rtmp网络...
    展开全文
  • 最近在研究OBS源码,里面有一个很重要的模块是推流模块,OBS是使用RTMP进行推流的,源码里面也有RTMP的源码,翻了一下目前网上没有详细的RTMP源码注释,所以这里基于OBS项目,来详细讲一下RTMP源码包括内核数据结构...

    最近在研究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 (!RTMP_TLS_ctx)
            RTMP_TLS_Init();
    #endif
        memset(r, 0, sizeof(RTMP)); //将所有值赋值为0;
        r->m_sb.sb_socket = -1;  //socket套接字结构体 SOCKET(RTMP是在TCP协议基础上开发的)
        r->m_inChunkSize=RTMP_DEFAULT_CHUNKSIZE;//chunk一次读取的(chunk data 即playload)大小 默认(128)
        r->m_outChunkSize=RTMP_DEFAULT_CHUNKSIZE;//chunk一次输出的(chunk data 即playload)大小 默认(128)
        r->m_bSendChunkSizeInfo = 1;
        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.curStreamIdx = 0;
        r->Link.nStreams = 0;
        r->Link.timeout = 30;
        r->Link.swfAge = 30;
    }
    
    //初始化一个stream id  当有推流码时将流媒体资源推流码绑定到一个stream id上 
    //这里要说明一下,一般PC客户端只有一个推流码也就是说 stream id只有一个
    
        typedef struct RTMP_Stream {
            int id; // CreatStream后收到服务器返回值
    		AVal playpath; //流媒体(或推流码)资源路径
        } RTMP_Stream;
        
    int RTMP_AddStream(RTMP *r, const char *playpath) //playpath为资源地址也就是 直播平台给的推流码
    {
        int idx = -1;
        
        //AVal 是AMF协议中的结构体
        AVal pp = { (char*)playpath, playpath?(int)strlen(playpath):0 };
    
        //此函数解析流媒体(或推流码)资源路径
        //此函数解析流媒体资源路径(当有推流码时,此接口无效),因为它会判断资源文件是什么格式的例如 MP4\flv
        //r->Link.nStreams 现有stream的总数 RTMP_AddStream会使nStreams 加一
        RTMP_ParsePlaypath(&pp, &r->Link.streams[r->Link.nStreams].playpath);
        r->Link.streams[r->Link.nStreams].id = -1;
        idx = r->Link.nStreams; //第一次初始化时为0;
        r->Link.nStreams++;
        return idx;
    }
    
    //创建m_sb.sb_socket套接字 + connect()函数 进行TCP三次握手
    //创建套接字 实际上是填充sockaddr结构体
    int RTMP_Connect0(RTMP *r, struct sockaddr * service, socklen_t addrlen)
    {
        int on = 1;
        r->m_sb.sb_timedout = FALSE;
        r->m_pausing = 0;
        r->m_fDuration = 0.0;
    
        //best to be explicit, we need overlapped socket
    #ifdef _WIN32
        r->m_sb.sb_socket = WSASocket(service->sa_family, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    #else
        r->m_sb.sb_socket = socket(service->sa_family, SOCK_STREAM, IPPROTO_TCP);
    #endif
        if (r->m_sb.sb_socket != INVALID_SOCKET)
        {
            if(r->m_bindIP.addrLen)
            {
               ....
            }
            uint64_t connect_start = os_gettime_ns();
    
            //windows的API connect()作用是进行三次握手
            if (connect(r->m_sb.sb_socket, service, addrlen) < 0)
            {
                int err = GetSockError();
                .....
                return FALSE;
            }
            ...
            if (r->Link.socksport)
            {
                // 使用WriteN()接口和ReadN()接口 收发8个字节,查看收发字节数是否相等
                if (!SocksNegotiate(r)) //检测是否能发出去
                {
                    RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__);
                    RTMP_Close(r);
                    return FALSE;
                }
            }
        }
        else
        {
            ......
            return FALSE;
        }
        ......
        //m_bUseNagle为0;
        if(!r->m_bUseNagle)
            setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on));
    
        return TRUE;
    }
    
    int RTMP_Connect1(RTMP *r, RTMPPacket *cp)
    {
        if (r->Link.protocol & RTMP_FEATURE_SSL)
        {
          .....
        }
        if (r->Link.protocol & RTMP_FEATURE_HTTP) //http协议
        {
            .....
        }
        RTMP_Log(RTMP_LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__);
        if (!HandShake(r, TRUE)) //rtmp 握手 判断收发的内容是否一样大小
        {
            ......
            return FALSE;
        }
        RTMP_Log(RTMP_LOGDEBUG, "%s, handshaked", __FUNCTION__);
    
    	if (!SendConnectPacket(r, cp))  //发送 RTMP_PACKET_TYPE_CHUNK_SIZE    connect命令
        {
            RTMP_Log(RTMP_LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
            RTMP_Close(r);
            return FALSE;
        }
        return TRUE;
    }
    
    //握手 判断收发的内容是否一样大小
    static int HandShake(RTMP *r, int FP9HandShake)
    {
        int i;
        uint32_t uptime, suptime;
        int bMatch;
        char type;
        char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf + 1;
        char serversig[RTMP_SIG_SIZE];
    
        clientbuf[0] = 0x03;	// C0 初始化第一个字节 	/* not encrypted */
    
        uptime = htonl(RTMP_GetTime());
        memcpy(clientsig, &uptime, 4);
        memset(&clientsig[4], 0, 4);
    
    #ifdef _DEBUG
        for (i = 8; i < RTMP_SIG_SIZE; i++)
            clientsig[i] = 0xff;
    #else
        for (i = 8; i < RTMP_SIG_SIZE; i++)
            clientsig[i] = (char)(rand() % 256);
    #endif
    
        if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1)) //将4个字节的时间和剩余0xff发往服务端共1537个字节 C0+C1一起发的
            return FALSE;
    
        if (ReadN(r, &type, 1) != 1)	//尝试读取 C0 第一个字节看看能否收到 /* 0x03 or 0x06 */
            return FALSE;
    
        if (type != clientbuf[0])
            RTMP_Log(RTMP_LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d",
                     __FUNCTION__, clientbuf[0], type);
    
        if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) //读取剩余的字节数是否和发出的相等读C1
            return FALSE;
    
        memcpy(&suptime, serversig, 4);
        suptime = ntohl(suptime); //赋值4个字节的时间 为了写出log
    
        /* 2nd part of handshake */
        if (!WriteN(r, serversig, RTMP_SIG_SIZE))  //第二次握手 发送1536个字节 发送C2
            return FALSE;
    
        if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) //读取剩余的字节数是否和发出的相等 读取C2
            return FALSE;
    
        bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0); //判断收发内容是否相等
        if (!bMatch)
        {
            RTMP_Log(RTMP_LOGWARNING, "%s, client signature does not match!", __FUNCTION__);
        }
    
        /* er, totally unused? */
        (void)FP9HandShake;
        return TRUE;
    }
    

    接下来是重点接口啦。
    我们知道RTMP协议是以 Message(消息)这种机制来定义的比如: 修改服务端和客户端传输大小参数的命令消息(也叫信令消息),音视频的元数据消息,音视频的数据消息(最大的消息)等,后面会逐个介绍。但是TCP传输有限,一般链路层只有1500字节,所以Message会被拆分成Chunk形式,也就是说RTMP传输只有Chunk的概念,没有Message的概念,这是重点,
    RTMPPacket 这个数据结构就是按照Chunk来定义的

    //RTNP协议在发送或接受数据之前会发送一些命令比如下面的发送"connect"命令,为了传输一些参数到服务端
    //流媒体的暂停和播放也是通过发送命令到服务端这种方式进行的
    //这就很像RPC远程调用控制方式
    //RTMP_SendPacket() 发送一个message若需要拆分则自动分成多个chunk ,此接口在后面文章的详细介绍
    //RTMPPacket是根据chunk定义的 一个chunk相当于一个RTMPPacket
    //Tips: RTMP_MAX_HEADER_SIZE这个是18  3+11(当 fmt=0 时 Chunk Msg Head)+4(Extend Timestamp)=18 最大的chunk大小
    
    *****************************重点结构体***************************
    //RTMPPacket是根据chunk定义的 一个chunk相当于一个RTMPPacket
     typedef struct RTMPPacket  //一个Tag Data的数据结构(这里是包含了由flv封装的Tag Data head)或者是"命令"数据,根据chunk结构定义的
        {
            uint8_t m_headerType;// chunk type id (2bit)fmt 对应message head {0,3,7,11} + (6bit)chunk stream id  /*大部分情况是一个字节
            uint8_t m_packetType;// Message type ID(1-7协议控制;8,9音视频;10以后为AMF编码消息)/*一个字节
            uint8_t m_hasAbsTimestamp;	//绝对时间戳/* timestamp absolute or relative? */
    		int m_nChannel; //chunk stream id(chunk basic header)字段
            uint32_t m_nTimeStamp;	/* timestamp */
    		int32_t m_nInfoField2;	//message stream id  /* last 4 bytes in a long header 
            uint32_t m_nBodySize;  //经过AMF编码组包后,message的大小 (如果是音视频数据 即FLV格式一个Tag中Tag Data大小)
            uint32_t m_nBytesRead; //需要发送一个Tag Data的数据大小或者是已读取数据大小
            RTMPChunk *m_chunk;
            char *m_body;//Tag Data数据  Tag data的起始指针因为会循环读取,给m_body所在内存赋值,直到一个Tag Data读完
                         //当chunk中的内容为命令时,m_body为chunk body(data)的起始指针
        } RTMPPacket;
    
    static int SendConnectPacket(RTMP *r, RTMPPacket *cp) 
    {
        RTMPPacket packet;
        //一个整体chunk(head+data)头指针pbuf  尾指针pend   pbuf初始化4096是因为刚开始初始化时为4096
        char pbuf[4096], *pend = pbuf + sizeof(pbuf);
        char *enc; //临时变量,经过AMF编码chunk data指针,每次填完数据后enc自动向后移动相应的大小
    
        //cp一般为0或NULL
        if (cp) 
            //发送一个message若需要拆分则自动分成多个chunk ,此接口在后面文章的详细介绍
            return RTMP_SendPacket(r, cp, TRUE);
    
    	//组包过程(组装chunk)
        if((r->Link.protocol & RTMP_FEATURE_WRITE) && r->m_bSendChunkSizeInfo)//m_bSendChunkSizeInfo为1
        {
            packet.m_nChannel = 0x02; //chunk stream id 协议消息(一般用作控制类消息)
            packet.m_headerType = RTMP_PACKET_SIZE_LARGE;//0
            packet.m_packetType = RTMP_PACKET_TYPE_CHUNK_SIZE;//0x01
            packet.m_nTimeStamp = 0;
            packet.m_nInfoField2 = 0;  //message stream ID必须为0(代表控制消息流)
            packet.m_hasAbsTimestamp = 0;
            packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;//chunk 头指针后移18个字节
       //Tips: RTMP_MAX_HEADER_SIZE这个是18  3+11(当 fmt=0 时 Chunk Msg Head)+4(Extend Timestamp)=18 最大的chunk大小
            packet.m_nBodySize = 4; //message大小4个字节
            enc = packet.m_body; //将packet.m_body作为chunk data(body)的起始指针暂存一下,为了后续计算message的大小
            
            //大小端转换,,AMF封装好的接口转换成大端模式4个字节,给服务器发送chunk大小
            AMF_EncodeInt32(enc, pend, r->m_outChunkSize); 
            if(!RTMP_SendPacket(r, &packet, FALSE)) //发送消息
                return 0;
        }
    
        packet.m_nChannel = 0x03;	//chunk stream id 协议消息(一般用作invoke远程调用)/* control channel (invoke) */
        packet.m_headerType = RTMP_PACKET_SIZE_LARGE;//0
        packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;//0x14
        packet.m_nTimeStamp = 0; //chunk msg head 中的时间戳
        packet.m_nInfoField2 = 0;  //message stream ID必须为0(代表控制消息流)
        packet.m_hasAbsTimestamp = 0;
        packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;//chunk 头指针后移18个字节
    
        //AMF协议有封装好的接口,分别用于转换 字符串、数字、对象等
        //这里就采用了RPC方式的远程调用,例如将发出的字符串av_connect进行注册接口(保存av_connect),用于当收到服务区回复时判断,原先发出的命令是av_connect,从而进行相应的接口处理
        enc = packet.m_body;
        enc = AMF_EncodeString(enc, pend, &av_connect); //转换字符串av_connect
        enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);//转换 Transaction ID
        *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;
        }
        if (r->Link.swfUrl.av_len)
        {
            enc = AMF_EncodeNamedString(enc, pend, &av_swfUrl, &r->Link.swfUrl); 
            if (!enc)
                return FALSE;
        }
        if (r->Link.tcUrl.av_len)
        {
            enc = AMF_EncodeNamedString(enc, pend, &av_tcUrl, &r->Link.tcUrl);// 服务器的URL地址
            if (!enc)
                return FALSE;
        }
        if (!(r->Link.protocol & RTMP_FEATURE_WRITE))
        {
            enc = AMF_EncodeNamedBoolean(enc, pend, &av_fpad, FALSE);
            if (!enc)
                return FALSE;
            enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 15.0);
            if (!enc)
                return FALSE;
            enc = AMF_EncodeNamedNumber(enc, pend, &av_audioCodecs, r->m_fAudioCodecs);//支持音频编码
            if (!enc)
                return FALSE;
            enc = AMF_EncodeNamedNumber(enc, pend, &av_videoCodecs, r->m_fVideoCodecs);//支持视频编码
            if (!enc)
                return FALSE;
            enc = AMF_EncodeNamedNumber(enc, pend, &av_videoFunction, 1.0);
            if (!enc)
                return FALSE;
            if (r->Link.pageUrl.av_len)
            {
                enc = AMF_EncodeNamedString(enc, pend, &av_pageUrl, &r->Link.pageUrl);
                if (!enc)
                    return FALSE;
            }
        }
        if (r->m_fEncoding != 0.0 || r->m_bSendEncoding)
        {
            /* AMF0, AMF3 not fully supported yet */
            enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding);
            if (!enc)
                return FALSE;
        }
        if (enc + 3 >= pend)
            return FALSE;
        *enc++ = 0;
        *enc++ = 0;			/* end of object - 0x00 0x00 0x09 */
        *enc++ = AMF_OBJECT_END;//结束是 必须为0x00 0x00 0x09
    
        /* add auth string */
        if (r->Link.auth.av_len) //增加权限
        {
            enc = AMF_EncodeBoolean(enc, pend, r->Link.lFlags & RTMP_LF_AUTH);
            if (!enc)
                return FALSE;
            enc = AMF_EncodeString(enc, pend, &r->Link.auth);
            if (!enc)
                return FALSE;
        }
        if (r->Link.extras.o_num)
        {
            int i;
            for (i = 0; i < r->Link.extras.o_num; i++)
            {
                enc = AMFProp_Encode(&r->Link.extras.o_props[i], enc, pend);
                if (!enc)
                    return FALSE;
            }
        }
        
        //计算AMF编码组包后,message的大小
        //packet.m_body为chunk body(data)的起始指针,enc为填充数据完成后chunk body(data)的尾指针
        packet.m_nBodySize = enc - packet.m_body; //计算AMF编码组包后,message的大小
    
        return RTMP_SendPacket(r, &packet, TRUE); //发送消息
    }
    

    RTMP_ConnectStream接口作用是解析服务端的数据在基于OBS的RTMP源码分析之RTMP_ReadPacket(rtmp读取服务端数据并处理)一章中有介绍、init_send是OBS的接口主要作用是设置套接字和发送元数据。RTMP_SendPacket接口在基于OBS的RTMP源码分析之RTMP_Write(rtmp发送音视频数据或命令)中有介绍
    至此,在推流时,RTMP的网络连接包括 TCP连接、RTMP握手向服务端发送命令功能介绍完了。下面将介绍,处理服务端发送的消息功能接口。

    展开全文
  • 使用OBSRTMP

    2021-03-19 17:42:40
    第一步,下载OBS: 官网地址:https://obsproject.com/ 点击Download, 进行下载,安装。 第二步:部署服务器 此处忽略,交给服务端处理。 第三步:配置OBS 点击底部设置按钮,进行服务地址等内容的设置。 ...

    第一步,下载OBS:

    官网地址:https://obsproject.com/

    点击Download, 进行下载,安装。

    第二步:部署服务器

    此处忽略,交给服务端处理。

    第三步:配置OBS

    点击底部设置按钮,进行服务地址等内容的设置。

    选择左侧的推流,服务选择自定义,服务器填写自己的IP地址,串流密钥填写流名,填写完后点击确定。

    第四步:设置媒体源,点击开始推流。

    当底部出现视频信息后,说明推流成功。可以在VLC里面进行观看,进行验证。

    第五步:验证

    打开VLC, 点击媒体,打开网络串流,在“请输入网络URL”中输入在OBS中设置的服务器地址+'/'+串流密钥的拼接,点击播放按钮。

    等待2-3s播出方可播出画面。

     

     

     

    展开全文
  • obsrtmp流卡顿丢帧策略 1.检测队列buffer的长度,p帧超过900ms,b帧超过700毫秒开始丢帧。 2.丢帧策略新建一个circlebuf,音频不丢,视频按照优先级丢弃(i帧优先级为3,p帧优先级为2,b帧优先级为1),b帧超过700...
  • 最近在研究OBS源码,里面有一个很重要的模块是推流模块,OBS是使用RTMP进行推流的,源码里面也有RTMP的源码,翻了一下目前网上没有详细的RTMP源码注释,所以这里基于OBS项目,来详细讲一下RTMP源码包括内核数据结构...
  • 最近在研究OBS源码,里面有一个很重要的模块是推流模块,OBS是使用RTMP进行推流的,源码里面也有RTMP的源码,翻了一下目前网上没有详细的RTMP源码注释,所以这里基于OBS项目,来详细讲一下RTMP源码包括内核数据结构...
  • 最近在看obs-studio的源码,这里将源码学习的东西记录...rtmp.c文件位于obs-studio工程中obs-outputs中: 1、rtmp.h中常用结构体如下 typedef struct RTMPChunk { int c_headerSize; int c_chunkSize; char...
  • rtmp的网络 1、rtmp结构体设置RTMP_LNK #define DEF_VERSTR OSS &quot; 10,0,32,18&quot; static const char DEFAULT_FLASH_VER[] = DEF_VERSTR; const AVal RTMP_DefaultFlashVer = { (char *)DEFAULT_...
  • 这里主要接着上一篇继续网络连接部分的讲解。...SocksNegotiate(RTMP *r) { unsigned long addr; struct sockaddr_storage service; socklen_t addrlen = 0; int socket_error = 0; memset(&amp;amp;...
  • 1、读取rtmp数据包 static int ReadN(RTMP *r, char *buffer, int n) { int nOriginalSize = n; int avail; char *ptr; r-&amp;amp;gt;m_sb.sb_timedout = FALSE; #ifdef _DEBUG memset(buffer, 0, n); ...
  • 这里主要讲解了音频、视频包的发送,包括函数init_send()、send_thread...static int init_send(struct rtmp_stream *stream) { int ret; size_t idx = 0; bool next = true; #if defined(_WIN32) adjust_sndbu...
  • 这里主要讲解rtmp_stream是怎样调用rtmp.c中的函数进行发送元数据、音频包头和视频包头。包括函数send_meta_data()、send_audio_header()、send_video_header()、send_headers() static bool send_meta_data(struct...
  • 1、RTMP读取函数 static const char flvHeader[] = { 'F', 'L', 'V', 0x01, 0x00, /* 0x04 == audio, 0x01 == video */ 0x00, 0x00, ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,151
精华内容 860
关键字:

obs支持rtmp