RTMP推流到SRS流媒体服务器消息处理

RTMP推流到SRS流媒体服务器消息处理

  1. SRS和客户端是怎么交换消息的?各个消息有什么作用?
  2. 握手成功后,SRS和客户端进行消息交换,对应wiresharek这部分截图:

image.png

  1. 流程图(之前画的,可能不够详细,后面补充):

srs和客户端建联流程,从connect开始.png

1. 客户端发送connect连接请求到SRS服务端

  1. connect命令,⽤于客户端向服务器发送连接请求。
  2. wiresharek截图:

image.png

1. connect消息解析
  1. Chunk Stream Id:接收端根据相同的chunk stream id拼装出message, 这⾥是3,对应ffmpeg的枚举RTMP_SYSTEM_CHANNEL。
  2. Type ID (即是message type id) ⽐如8⾳频,9视频,20命令消息,这⾥是20命令消息,对应ffmpeg的枚举RTMP_PT_INVOKE。
  3. Stream ID为0。
  4. RTMP body是⼀个connect 命令消息,这些信息是以AMF格式发送的,消息的结构如下:

image.png

  1. 第三个字段中的Command Object中会涉及到很多键值对,使⽤时可以参考协议的官⽅⽂档。消息的回应有两种,_result表示接受连接,_error表示连接失败。
  2. 以下是连接命令对象中使⽤的健值对的描述:

image.png

2. connect消息代码分析
1. 客户端生成connect消息发送到SRS服务器
  1. 对应FFmpeg代码在:
	gen_connect(s, rt)static int gen_connect(URLContext *s, RTMPContext *rt)//部分代码如下:ff_amf_write_string(&p, "connect");ff_amf_write_number(&p, ++rt->nb_invokes);ff_amf_write_object_start(&p);ff_amf_write_field_name(&p, "app");ff_amf_write_string2(&p, rt->app, rt->auth_params);
2. SRS服务端接收connect消息并解析
  1. 对应SRS代码在:
	rtmp->connect_app(req)) != srs_success)
srs_error_t SrsRtmpServer::connect_app(SrsRequest* req) {srs_error_t err = srs_success;SrsCommonMessage* msg = NULL;SrsConnectAppPacket* pkt = NULL;if ((err = expect_message<SrsConnectAppPacket>(&msg, &pkt)) != srs_success) { //解析connect消息,从流式数据->chunk->message->packetreturn srs_error_wrap(err, "expect connect app response");}SrsAutoFree(SrsCommonMessage, msg);SrsAutoFree(SrsConnectAppPacket, pkt);SrsAmf0Any* prop = NULL;if ((prop = pkt->command_object->ensure_property_string("tcUrl")) == NULL) {return srs_error_new(ERROR_RTMP_REQ_CONNECT, "invalid request without tcUrl");}req->tcUrl = prop->to_str();if ((prop = pkt->command_object->ensure_property_string("pageUrl")) != NULL) {req->pageUrl = prop->to_str();}if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) {req->swfUrl = prop->to_str();}if ((prop = pkt->command_object->ensure_property_number("objectEncoding")) != NULL) {req->objectEncoding = prop->to_number();}if (pkt->args) {srs_freep(req->args);req->args = pkt->args->copy()->to_object();}srs_discovery_tc_url(req->tcUrl, req->schema, req->host, req->vhost, req->app, req->stream, req->port, req->param);req->strip();return err;
}
  1. 具体解析为packet代码为:
srs_error_t SrsProtocol::do_decode_message(SrsMessageHeader& header, SrsBuffer* stream, SrsPacket** ppacket)
{...        // decode command object.if (command == RTMP_AMF0_COMMAND_CONNECT) { //第一个接收到的message为"connect"消息*ppacket = packet = new SrsConnectAppPacket();return packet->decode(stream);} ...
}
  1. gdb显示调用栈为:
(gdb) bt
#0  SrsProtocol::do_decode_message (this=0xac1280, header=..., stream=0xadb560, ppacket=0xadb550) at src/protocol/srs_rtmp_stack.cpp:700
#1  0x00000000004655ec in SrsProtocol::decode_message (this=0xac1280, msg=0xade990, ppacket=0xadb5e0) at src/protocol/srs_rtmp_stack.cpp:430
#2  0x000000000047f8c0 in SrsProtocol::expect_message<SrsConnectAppPacket> (this=0xac1280, pmsg=0xadb670, ppacket=0xadb678) at src/protocol/srs_rtmp_stack.hpp:335
#3  0x000000000047dc7d in SrsRtmpServer::expect_message<SrsConnectAppPacket> (this=0xac1250, pmsg=0xadb670, ppacket=0xadb678) at src/protocol/srs_rtmp_stack.hpp:776
#4  0x000000000046df7b in SrsRtmpServer::connect_app (this=0xac1250, req=0xacab30) at src/protocol/srs_rtmp_stack.cpp:2310
#5  0x00000000004d3594 in SrsRtmpConn::do_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:170
#6  0x00000000004d1d99 in SrsConnection::cycle (this=0xac0f88) at src/app/srs_app_conn.cpp:171
#7  0x000000000050ab08 in SrsSTCoroutine::cycle (this=0xac11f0) at src/app/srs_app_st.cpp:198
#8  0x000000000050ab7d in SrsSTCoroutine::pfn (arg=0xac11f0) at src/app/srs_app_st.cpp:213
#9  0x00000000005bed1a in _st_thread_main () at sched.c:337
#10 0x00000000005bf492 in st_thread_create (start=0x5be696 <_st_vp_schedule+170>, arg=0x700000001, joinable=1, stk_size=1) at sched.c:616
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

2. SRS服务器发送Window Acknowledgement Size消息给客户端,默认2500000

1. Window Acknowledgement Size消息解析
  1. wiresharek截图:

image.png

  1. Type Id:0x5 对应ffmepg的RTMP_PT_WINDOW_ACK_SIZE
  2. Window Acknowledgement Size⽤于设置窗⼝确认⼤⼩,⽐如这⾥是服务器发送给客户端的,当客户端收到每次该size就要Acknowledgement (0x3)。
  3. 对于ffmpeg,在接收到Window Acknowledgement Size的⼀半后发送确认包(Acknowledgement),以确保对等⽅可以继续发送⽽不等待确认。
  4. 注意点:
    1. 客户端作为推流端时,⼀般即使没有收到服务器的ack,客户端也不会停⽌码流的推送。
    2. 当客户端作为拉流端时,⼀般即使拉流端不回应ack,服务器也不会停⽌码流的发送。
    3. 但彼此如果作为接收⽅时,收到1/2Windows size的数据后对会ack对⽅。
2. Window Acknowledgement Size消息代码分析
  1. SRS服务器发送Window Acknowledgement Size消息给客户端
rtmp->set_window_ack_size(out_ack_size)
srs_error_t SrsRtmpServer::set_window_ack_size(int ack_size){srs_error_t err = srs_success;SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket();pkt->ackowledgement_window_size = ack_size;if ((err = protocol->send_and_free_packet(pkt, 0)) != srs_success) {return srs_error_wrap(err, "send ack");}return err;
}
  1. FFmpeg客户端处理SRS服务器发送过来的
static int handle_window_ack_size(URLContext *s, RTMPPacket *pkt)
{RTMPContext *rt = s->priv_data;if (pkt->size < 4) {av_log(s, AV_LOG_ERROR,"Too short window acknowledgement size packet (%d)\n",pkt->size);return AVERROR_INVALIDDATA;}rt->receive_report_size = AV_RB32(pkt->data);if (rt->receive_report_size <= 0) {av_log(s, AV_LOG_ERROR, "Incorrect window acknowledgement size %d\n",rt->receive_report_size);return AVERROR_INVALIDDATA;}av_log(s, AV_LOG_DEBUG, "Window acknowledgement size = %d\n", rt->receive_report_size);// Send an Acknowledgement packet after receiving half the maximum// size, to make sure the peer can keep on sending without waiting// for acknowledgements.rt->receive_report_size >>= 1;return 0;
}

3. srs服务器发送Peer Bandiwdth消息给客户端,默认2500000

1. Peer Bandiwdth消息解析
  1. wiresharek截图:

image.png

  1. 客户端或服务器端发送此消息更新对端的输出带宽,作用是限制对端的输出带宽。和Window Acknowledgement Size相⽐,重点是更新
  2. Type ID:0x6 对应ffmpeg的RTMP_PT_SET_PEER_BW
  3. 如果消息中的Window ACK Size与上⼀次发送给发送端的size不同的话要回馈⼀个Window Acknowledgement Size的控制消息
    1. image.png
      1. Hard(Limit Type=0):接收端应该将Window Ack Size设置为消息中的值
      2. Soft(Limit Type=1):接收端可以将Window Ack Size设为消息中的值,也可以保存原来的值(前提是原来的Size⼩与该控制消息中的Window Ack Size)
      3. Dynamic(Limit Type=2):如果上次的Set Peer Bandwidth消息中的Limit Type为0,本次也按Hard处理,否则忽略本消息,不去设置Window Ack Size
2. Peer Bandiwdth消息代码分析
  1. SRS服务器发送 Peer Bandiwdth消息给客户端。
srs_error_t SrsRtmpServer::set_peer_bandwidth(int bandwidth, int type)
{srs_error_t err = srs_success;SrsSetPeerBandwidthPacket* pkt = new SrsSetPeerBandwidthPacket();pkt->bandwidth = bandwidth;pkt->type = type;if ((err = protocol->send_and_free_packet(pkt, 0)) != srs_success) {return srs_error_wrap(err, "send set peer bandwidth");}return err;
}

4. srs服务器发送Set Chunk Size消息给客户端

1. Set Chunk Size消息解析
  1. wiresharek截图:image.png
  2. Set Chunk Size(Message Type ID=1):设置chunk中Data字段所能承载的最⼤字节数,默认为128B,通信过程中可以通过发送该消息来设置chunk Size的⼤⼩(不得⼩于128B),⽽且通信双⽅会各⾃维护⼀个chunkSize,两端的chunkSize是独⽴的。
    1. ⽐如当A想向B发送⼀个200B的Message,但默认的chunkSize是128B,因此就要将该消息拆分为Data分别为128B和72B的两个chunk发送,如果此时先发送⼀个设置chunkSize为256B的消息,再发送Data为200B的chunk,本地不再划分Message,B接受到Set Chunk Size的协议控制消息时会调整的接受的chunk的Data的⼤⼩,也不⽤再将两个chunk组成为⼀个Message。
    2. 在实际中⼀般会把chunk size设置的很⼤,有的会设置为4096,FFMPEG推流的时候设置的是 60*1000,这样设置的好处是避免了频繁的拆包组包,占⽤过多的CPU。
  3. 以下为代表Set Chunk Size消息的chunk的Data:
    1. image.png
    2. 其中第⼀位必须为0,chunk Size占31个位,最⼤可代表2147483647=0x7FFFFFFF=2 -1,但实际上所有⼤于16777215=0xFFFFFF的值都⽤不上,因为chunk size不能⼤于Message的⻓度,表示Message的⻓度字段是⽤3个字节表示的,最⼤只能为0xFFFFFF。
2. Set Chunk Size消息代码分析
  1. SRS服务器向客户端发送Set Chunk Size消息
srs_error_t SrsRtmpServer::set_chunk_size(int chunk_size)
{srs_error_t err = srs_success;SrsSetChunkSizePacket* pkt = new SrsSetChunkSizePacket();pkt->chunk_size = chunk_size;if ((err = protocol->send_and_free_packet(pkt, 0)) != srs_success) {return srs_error_wrap(err, "send set chunk size");}return err;
}
  1. FFmpeg客户端处理SRS服务器发送过来的Set Chunk Size消息
static int handle_chunk_size(URLContext *s, RTMPPacket *pkt)
{RTMPContext *rt = s->priv_data;int ret;if (pkt->size < 4) {av_log(s, AV_LOG_ERROR,"Too short chunk size change packet (%d)\n",pkt->size);return AVERROR_INVALIDDATA;}if (!rt->is_input) {/* Send the same chunk size change packet back to the server,* setting the outgoing chunk size to the same as the incoming one. */if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,&rt->prev_pkt[1], &rt->nb_prev_pkt[1])) < 0)return ret;rt->out_chunk_size = AV_RB32(pkt->data);}rt->in_chunk_size = AV_RB32(pkt->data);if (rt->in_chunk_size <= 0) {av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n",rt->in_chunk_size);return AVERROR_INVALIDDATA;}av_log(s, AV_LOG_DEBUG, "New incoming chunk size = %d\n",rt->in_chunk_size);return 0;
}

5. SRS服务器发送response_connect_app消息给客户端

  1. SRS服务器是会发送response_connect_app消息给客户端用来响应客户端发送的connect消息,但这部分wiresharek是没有抓取到此包的。
  2. 通过tcpdump命令查看包信息:
sudo tcpdump -i any port 1935 -XX and dst 36.112.32.2 #不是公网地址
  1. 是有返回result消息的,只是wiresharek没有抓取到此包。

image.png

1. response_connect_app消息代码分析
  1. SRS服务器向客户端发送esponse_connect_app消息
srs_error_t SrsRtmpServer::response_connect_app(SrsRequest *req, const char* server_ip)
{srs_error_t err = srs_success;SrsConnectAppResPacket* pkt = new SrsConnectAppResPacket();// @remark For windows, there must be a space between const string and macro.pkt->props->set("fmsVer", SrsAmf0Any::str("FMS/" RTMP_SIG_FMS_VER));pkt->props->set("capabilities", SrsAmf0Any::number(127));pkt->props->set("mode", SrsAmf0Any::number(1));pkt->info->set(StatusLevel, SrsAmf0Any::str(StatusLevelStatus));pkt->info->set(StatusCode, SrsAmf0Any::str(StatusCodeConnectSuccess));pkt->info->set(StatusDescription, SrsAmf0Any::str("Connection succeeded"));pkt->info->set("objectEncoding", SrsAmf0Any::number(req->objectEncoding));SrsAmf0EcmaArray* data = SrsAmf0Any::ecma_array();pkt->info->set("data", data);data->set("version", SrsAmf0Any::str(RTMP_SIG_FMS_VER));data->set("srs_sig", SrsAmf0Any::str(RTMP_SIG_SRS_KEY));data->set("srs_server", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER));data->set("srs_license", SrsAmf0Any::str(RTMP_SIG_SRS_LICENSE));data->set("srs_url", SrsAmf0Any::str(RTMP_SIG_SRS_URL));data->set("srs_version", SrsAmf0Any::str(RTMP_SIG_SRS_VERSION));if (server_ip) {data->set("srs_server_ip", SrsAmf0Any::str(server_ip));}// for edge to directly get the id of client.data->set("srs_pid", SrsAmf0Any::number(getpid()));data->set("srs_id", SrsAmf0Any::number(_srs_context->get_id()));if ((err = protocol->send_and_free_packet(pkt, 0)) != srs_success) {return srs_error_wrap(err, "send connect app response");}return err;
}

6. 客户端发送Set Chunk Size消息给SRS服务器

  1. wiresharek截图:

image.png

  1. 客户端也会发送Set Chunk Size消息给SRS服务器,值为60000。

7. 客户端发送releaseStream、FCPublish、CreateStream和checkbw消息给SRS服务器

  1. 客户端还需要发送 releaseStream , FCPublish,CreateStream和checkbw消息给SRS服务器,具体可以参考ffmpeg的handle_invoke_result函数和gen_check_bw函数。
  2. handle_invoke_result函数部分代码:
static int handle_invoke_result(URLContext *s, RTMPPacket *pkt)
{...if (!strcmp(tracked_method, "connect")) {if (!rt->is_input) { //输出if ((ret = gen_release_stream(s, rt)) < 0) //生成releaseStream发送给服务器goto fail;if ((ret = gen_fcpublish_stream(s, rt)) < 0)//生成FCPublish发送给服务器goto fail;} else {    //输入if ((ret = gen_window_ack_size(s, rt)) < 0)goto fail;}if ((ret = gen_create_stream(s, rt)) < 0)//生成createStream发送给服务器goto fail;...} else if (!strcmp(tracked_method, "createStream")) {...}fail:av_free(tracked_method);return ret;
}
1. 客户端发送releaseStream消息给srs服务器
  1. 对应的值liveStream是要处理的流,作用是生成’releaseStream’调用并发送到服务器,让服务器为媒体流释放一些通道。
  2. wiresharek截图:

image.png

1. releaseStream消息代码分析
  1. 客户端生成releaseStream消息发送给SRS服务端,代码:
static int gen_release_stream(URLContext *s, RTMPContext *rt)
{RTMPPacket pkt;uint8_t *p;int ret;if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,0, 29 + strlen(rt->playpath))) < 0)return ret;av_log(s, AV_LOG_DEBUG, "Releasing stream...\n");p = pkt.data;ff_amf_write_string(&p, "releaseStream");ff_amf_write_number(&p, ++rt->nb_invokes);ff_amf_write_null(&p);ff_amf_write_string(&p, rt->playpath);return rtmp_send_packet(rt, &pkt, 1);
}
  1. SRS服务端接收到后会解析成对应的packet,然后返回_result消息。如下wiresharek所示:

image.png

  1. SRS服务器解析releaseStream消息代码:
    // SrsProtocol::do_decode_message函数下} else if (command == RTMP_AMF0_COMMAND_RELEASE_STREAM) {*ppacket = packet = new SrsFMLEStartPacket(); // 处理"releaseStream"消息,rtmp推流时是这个包return packet->decode(stream);}
  1. 调用栈:
    1. 是从SrsRtmpServer::identify_client函数进去的
Breakpoint 3, SrsProtocol::do_decode_message (this=0xac1280, header=..., stream=0xadb540, ppacket=0xadb530) at src/protocol/srs_rtmp_stack.cpp:712
712	            *ppacket = packet = new SrsFMLEStartPacket();
(gdb) bt
#0  SrsProtocol::do_decode_message (this=0xac1280, header=..., stream=0xadb540, ppacket=0xadb530) at src/protocol/srs_rtmp_stack.cpp:712
#1  0x00000000004655ec in SrsProtocol::decode_message (this=0xac1280, msg=0xadce30, ppacket=0xadb5c8) at src/protocol/srs_rtmp_stack.cpp:430
#2  0x00000000004702bb in SrsRtmpServer::identify_client (this=0xac1250, stream_id=1, type=@0xacab08: SrsRtmpConnUnknown, stream_name="", duration=@0xacac88: -1) at src/protocol/srs_rtmp_stack.cpp:2521
#3  0x00000000004d519a in SrsRtmpConn::stream_service_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:440
#4  0x00000000004d4ddf in SrsRtmpConn::service_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:388
#5  0x00000000004d3ba7 in SrsRtmpConn::do_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:209
#6  0x00000000004d1d99 in SrsConnection::cycle (this=0xac0f88) at src/app/srs_app_conn.cpp:171
#7  0x000000000050ab08 in SrsSTCoroutine::cycle (this=0xac11f0) at src/app/srs_app_st.cpp:198
#8  0x000000000050ab7d in SrsSTCoroutine::pfn (arg=0xac11f0) at src/app/srs_app_st.cpp:213
#9  0x00000000005bed1a in _st_thread_main () at sched.c:337
#10 0x00000000005bf492 in st_thread_create (start=0x5be696 <_st_vp_schedule+170>, arg=0x700000001, joinable=1, stk_size=1) at sched.c:616
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
  1. 根据packet类型返回对应响应,ReleaseStream/PublishStream/FCPublish/FCUnpublish都是用SrsRtmpServer::identify_fmle_publish_client函数返回_result消息。
srs_error_t SrsRtmpServer::identify_fmle_publish_client(SrsFMLEStartPacket* req, SrsRtmpConnType& type, string& stream_name)
{srs_error_t err = srs_success;type = SrsRtmpConnFMLEPublish;stream_name = req->stream_name;// releaseStream responseif (true) {SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(req->transaction_id);if ((err = protocol->send_and_free_packet(pkt, 0)) != srs_success) {return srs_error_wrap(err, "send releaseStream response");}}return err;
}
2. 客户端发送FCPublish消息给srs服务器
  1. FCPublish消息作用是使服务器为接收媒体流做好准备。
  2. wiresharek截图:

image.png

1. FCPublish消息代码解析
  1. 客户端生成FCPublish消息发送给SRS服务器:
static int gen_fcpublish_stream(URLContext *s, RTMPContext *rt)
{RTMPPacket pkt;uint8_t *p;int ret;if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,0, 25 + strlen(rt->playpath))) < 0)return ret;av_log(s, AV_LOG_DEBUG, "FCPublish stream...\n");p = pkt.data;ff_amf_write_string(&p, "FCPublish");ff_amf_write_number(&p, ++rt->nb_invokes);ff_amf_write_null(&p);ff_amf_write_string(&p, rt->playpath);return rtmp_send_packet(rt, &pkt, 1);
}
  1. SRS服务器解析FCPublish消息:
    } else if (command == RTMP_AMF0_COMMAND_FC_PUBLISH) {*ppacket = packet = new SrsFMLEStartPacket(); //第四个接收到的message为"FCPublish"return packet->decode(stream);}
  1. 调用栈:
    1. FCPublish、createStream、publish、onFCPublish、onStatus都是通过SrsRtmpServer::start_fmle_publish函数进行处理
Breakpoint 4, SrsProtocol::do_decode_message (this=0xac1280, header=..., stream=0xadb480, ppacket=0xadb470) at src/protocol/srs_rtmp_stack.cpp:715
715	            *ppacket = packet = new SrsFMLEStartPacket();
(gdb) bt
#0  SrsProtocol::do_decode_message (this=0xac1280, header=..., stream=0xadb480, ppacket=0xadb470) at src/protocol/srs_rtmp_stack.cpp:715
#1  0x00000000004655ec in SrsProtocol::decode_message (this=0xac1280, msg=0xadeca0, ppacket=0xadb500) at src/protocol/srs_rtmp_stack.cpp:430
#2  0x000000000047fd40 in SrsProtocol::expect_message<SrsFMLEStartPacket> (this=0xac1280, pmsg=0xadb588, ppacket=0xadb590) at src/protocol/srs_rtmp_stack.hpp:335
#3  0x000000000047dfd9 in SrsRtmpServer::expect_message<SrsFMLEStartPacket> (this=0xac1250, pmsg=0xadb588, ppacket=0xadb590) at src/protocol/srs_rtmp_stack.hpp:776
#4  0x000000000047190c in SrsRtmpServer::start_fmle_publish (this=0xac1250, stream_id=1) at src/protocol/srs_rtmp_stack.cpp:2714
#5  0x00000000004d5e81 in SrsRtmpConn::stream_service_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:530
#6  0x00000000004d4ddf in SrsRtmpConn::service_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:388
#7  0x00000000004d3ba7 in SrsRtmpConn::do_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:209
#8  0x00000000004d1d99 in SrsConnection::cycle (this=0xac0f88) at src/app/srs_app_conn.cpp:171
#9  0x000000000050ab08 in SrsSTCoroutine::cycle (this=0xac11f0) at src/app/srs_app_st.cpp:198
#10 0x000000000050ab7d in SrsSTCoroutine::pfn (arg=0xac11f0) at src/app/srs_app_st.cpp:213
#11 0x00000000005bed1a in _st_thread_main () at sched.c:337
#12 0x00000000005bf492 in st_thread_create (start=0x5be696 <_st_vp_schedule+170>, arg=0x700000001, joinable=1, stk_size=1) at sched.c:616
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
  1. SRS服务器发送_result消息给客户端进行响应FCPublish消息,见如上代码:SrsRtmpServer::identify_fmle_publish_client
3. 客户端发送CreateStream消息给srs服务器
  1. wiresharek截图:

image.png

  1. Create Stream:创建传递具体信息的通道,从⽽可以在这个流中传递具体信息,传输信息单元为Chunk。
  2. 当发送完createStream消息之后,解析服务器返回的消息会得到⼀个stream ID,这个ID也就是以后和服务器通信的 message stream ID,⼀般返回的是1,不固定。
1. CreateStream消息代码解析
  1. 客户端生成CreateStream消息发送给SRS服务器:
static int gen_create_stream(URLContext *s, RTMPContext *rt)
{RTMPPacket pkt;uint8_t *p;int ret;av_log(s, AV_LOG_DEBUG, "Creating stream...\n");if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,0, 25)) < 0)return ret;p = pkt.data;ff_amf_write_string(&p, "createStream");ff_amf_write_number(&p, ++rt->nb_invokes);ff_amf_write_null(&p);return rtmp_send_packet(rt, &pkt, 1);
}
  1. SRS服务器解析CreateStream消息:
        } else if (command == RTMP_AMF0_COMMAND_CREATE_STREAM) {*ppacket = packet = new SrsCreateStreamPacket(); //第五个接收到的message为"createStream"消息return packet->decode(stream);}
  1. SRS服务器发送_result消息给客户端进行响应CreateStream消息,见如上代码:SrsRtmpServer::identify_fmle_publish_client
4. 客户端发送checkbw消息给srs服务器
  1. 客户端生成检查带宽消息发送给服务器。
  2. wiresharek截图:

image.png

1. checkbw消息代码解析
  1. 客户端生成checkbw消息发送给SRS服务器:
static int gen_check_bw(URLContext *s, RTMPContext *rt)
{RTMPPacket pkt;uint8_t *p;int ret;if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,0, 21)) < 0)return ret;p = pkt.data;ff_amf_write_string(&p, "_checkbw");ff_amf_write_number(&p, ++rt->nb_invokes);ff_amf_write_null(&p);return rtmp_send_packet(rt, &pkt, 1);
}

8. 客户端向SRS服务器发送publish消息以及SRS服务器向客户端发送onFCPublish消息和onStatue消息

1. publish消息解析
  1. 推流客户端使用publish消息向SRS服务器端发布一个命名的流,发布之后,任意客户端都可以通过该名称请求视频、音频和数据。
  2. wiresharek截图:

image.png

1. publish消息代码解析
  1. 客户端生成publish消息并发送到SRS服务器:
static int gen_publish(URLContext *s, RTMPContext *rt)
{RTMPPacket pkt;uint8_t *p;int ret;av_log(s, AV_LOG_DEBUG, "Sending publish command for '%s'\n", rt->playpath);if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,0, 30 + strlen(rt->playpath))) < 0)return ret;pkt.extra = rt->stream_id;p = pkt.data;ff_amf_write_string(&p, "publish");ff_amf_write_number(&p, ++rt->nb_invokes);ff_amf_write_null(&p);ff_amf_write_string(&p, rt->playpath);ff_amf_write_string(&p, "live");return rtmp_send_packet(rt, &pkt, 1);
}
  1. SRS服务器解析publish消息:
    } else if (command == RTMP_AMF0_COMMAND_PUBLISH) { //message为"publish"*ppacket = packet = new SrsPublishPacket();return packet->decode(stream);}
  1. 调用栈:
(gdb) bt
#0  SrsProtocol::do_decode_message (this=0xac1280, header=..., stream=0xadb480, ppacket=0xadb470) at src/protocol/srs_rtmp_stack.cpp:718
#1  0x00000000004655ec in SrsProtocol::decode_message (this=0xac1280, msg=0xaded60, ppacket=0xadb500) at src/protocol/srs_rtmp_stack.cpp:430
#2  0x00000000004801c0 in SrsProtocol::expect_message<SrsPublishPacket> (this=0xac1280, pmsg=0xadb588, ppacket=0xadb590) at src/protocol/srs_rtmp_stack.hpp:335
#3  0x000000000047e35b in SrsRtmpServer::expect_message<SrsPublishPacket> (this=0xac1250, pmsg=0xadb588, ppacket=0xadb590) at src/protocol/srs_rtmp_stack.hpp:776
#4  0x0000000000471c42 in SrsRtmpServer::start_fmle_publish (this=0xac1250, stream_id=1) at src/protocol/srs_rtmp_stack.cpp:2757
#5  0x00000000004d5e81 in SrsRtmpConn::stream_service_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:530
#6  0x00000000004d4ddf in SrsRtmpConn::service_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:388
#7  0x00000000004d3ba7 in SrsRtmpConn::do_cycle (this=0xac0f10) at src/app/srs_app_rtmp_conn.cpp:209
#8  0x00000000004d1d99 in SrsConnection::cycle (this=0xac0f88) at src/app/srs_app_conn.cpp:171
#9  0x000000000050ab08 in SrsSTCoroutine::cycle (this=0xac11f0) at src/app/srs_app_st.cpp:198
#10 0x000000000050ab7d in SrsSTCoroutine::pfn (arg=0xac11f0) at src/app/srs_app_st.cpp:213
#11 0x00000000005bed1a in _st_thread_main () at sched.c:337
#12 0x00000000005bf492 in st_thread_create (start=0x5be696 <_st_vp_schedule+170>, arg=0x700000001, joinable=1, stk_size=1) at sched.c:616
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
  1. 解析成功后SRS服务器会生成onFCPublish消息返回给客户端。
2. onFCPublish消息解析
  1. wiresharek截图:

image.png

  1. onFCPublish消息是回应publish消息。
1. onFCPublish消息代码解析
  1. SRS服务器生成onFCPublish消息并发送到客户端:
    // publish response onFCPublish(NetStream.Publish.Start)if (true) { //客户端发送publish消息,服务端返回onFCPublish消息。SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_PUBLISH;pkt->data->set(StatusCode, SrsAmf0Any::str(StatusCodePublishStart));pkt->data->set(StatusDescription, SrsAmf0Any::str("Started publishing stream."));if ((err = protocol->send_and_free_packet(pkt, stream_id)) != srs_success) { //onFCPublishreturn srs_error_wrap(err, "send NetStream.Publish.Start");}}
3. onStatue消息解析
  1. SRS服务器还会生成onStatus消息向客户端发送,描述的状态内容中code为NetStream.Publish.Start,description为Start publishing,目的就是告诉推流客户端,现在可以推流了。
1. onStatue消息代码解析
  1. SRS服务器生成onStatue消息并发送到客户端:
    // publish response onStatus(NetStream.Publish.Start)if (true) { //服务器发送onStatus消息给客户端,wiresharek无法解析SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();pkt->data->set(StatusLevel, SrsAmf0Any::str(StatusLevelStatus));pkt->data->set(StatusCode, SrsAmf0Any::str(StatusCodePublishStart));pkt->data->set(StatusDescription, SrsAmf0Any::str("Started publishing stream."));pkt->data->set(StatusClientId, SrsAmf0Any::str(RTMP_SIG_CLIENT_ID));if ((err = protocol->send_and_free_packet(pkt, stream_id)) != srs_success) {return srs_error_wrap(err, "send NetStream.Publish.Start");}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/38267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Linux (Ubuntu 16) 下安装LabVIEW

用户尝试在Ubuntu 16操作系统上安装LabVIEW&#xff0c;但找不到合适的安装文件来支持Ubuntu。已经下载了运行时文件&#xff0c;并尝试将.rpm包转换为.deb包并安装在Ubuntu上。然而&#xff0c;安装完成后&#xff0c;没有在应用程序中看到LabVIEW的图标。 用户希望能够在Ubu…

【操作系统】内存管理——页面分配策略(个人笔记)

学习日期&#xff1a;2024.6.28 内容摘要&#xff1a;页面分配策略和内存映射文件&#xff0c;内存映射文件 页面分配置换策略 基本概念 驻留集&#xff0c;指请求分页存储管理中给进程分配的物理块的集合&#xff0c;在采用了虚拟存储技术的系统中&#xff0c;驻留集大小一…

springcloud第4季 分布式事务seata实现AT模式案例2【经典案例】

一 seata案例 1.1 背景说明 本案例使用seata的at模式&#xff0c;模拟分布式事务场景&#xff1a;【下订单&#xff0c;减库存&#xff0c;扣余额&#xff0c;改状态】 AT模式原理&#xff1a;是2pc方案的演变&#xff0c; 一阶段&#xff1a;业务数据和回滚日志记录在同一…

Android studio 打包低版本的Android项目报错

一、报错内容 Execution failed for task :app:packageRelease. > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade> com.android.ide.common.signing.KeytoolException: Failed to read key key0 from store "…

static修饰的对象在内存中的存储及其用法

一、static修饰的变量在内存中的存储位置 static关键字无论是在C语言还是C中都有着极其重要的作用&#xff0c;那么对于static来说&#xff0c;它修饰的对象是存储在内存的哪个位置呢&#xff1f;它的作用与它在内存中的位置有什么联系&#xff1f;还有它都适用于哪些场景&…

15.数据库简介+MySQl使用+SQL语句

文章目录 数据库简述一.数据库简介DB1.定义:2.DBMS数据库管理系统3.数据库分类 二.MySQL的安装1.安装步骤2.MySQL数据库图形管理工具3.mysql程序常用命令4.MySQL字符集及字符序5.Navicat快捷键操作 三.MySQL数据库基本操作 .........................................表管理一.…

RPC远程过程调用--Thrift

RPC远程过程调用–Thrift 简介 Thrift是一个由Facebook开发的轻量级、跨语言的远程服务调用框架&#xff0c;后进入Apache开源项目。支持通过自身接口定义语言IDL定义RPC接口和数据类型&#xff0c;然后通过编译器生成不同语言代码&#xff0c;用于构建抽象易用、可互操作的R…

黄子韬揭秘徐艺洋与EXO的不解之缘

黄子韬揭秘&#xff1a;徐艺洋与EXO的不解之缘在娱乐圈的繁华与喧嚣中&#xff0c;总有一些不为人知的故事&#xff0c;它们或温馨、或励志&#xff0c;或是感叹命运的奇妙。近日&#xff0c;黄子韬在一档热门综艺节目中意外爆料&#xff0c;揭开了徐艺洋与EXO之间鲜为人知的秘…

ffmpeg使用bmp编码器把bgr24编码为bmp图像

version #define LIBAVCODEC_VERSION_MAJOR 60 #define LIBAVCODEC_VERSION_MINOR 15 #define LIBAVCODEC_VERSION_MICRO 100 note 不使用AVOutputFormat code void CFfmpegOps::EncodeBGR24ToBMP(const char* infile, const char* width_str, const char* height_str…

IT之家最新科技热点

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

使用Java连接数据库并且执行数据库操作和创建用户登录图形化界面(3)专栏里有上两步的源代码

创建用户登录程序&#xff0c;验证用户账号和密码信息是否在数据库student中的用户表tb_account中存在。用户登录界面如下图所示&#xff1a; 当单击“登录”按钮时&#xff0c;处理以下几种情况&#xff1a; &#xff08;1&#xff09;用户名未输入&#xff0c;提示用户名不能…

业务模型扩展字段存储

构建业务模型时&#xff0c;通常模型会设置扩展信息&#xff0c;存储上一般使用JSON格式存储到db中。JSON虽然有较好的扩展性&#xff0c;但并没有结构化存储的类型和非空等约束&#xff0c;且强依赖代码中写入/读取时进行序列化/反序列化操作&#xff0c; 当扩展信息结构简单且…

代码随想录第37天|动态规划

01背包理论基础 参考 01背包: 每个物品只有一个, 只要选或不选两个选项 暴力解法: 回溯法枚举 dp[i][j]: i 表示 0 ~ i 的物品, j 表示容量, 数值表示当前的最大价值递推公式: max(dp[i-1][j], dp[i-1][j-weight[i]] value[i])初始化: j 0 时, 无法放任何有价值的物品, d…

ASP.Net.WebAPI和工具PostMan

1.WebAPI概述 1.1 WebAPI WebAPI 是一种传统的方式&#xff0c;用于构建和暴露 RESTUI风格的Web服务。它提供了丰富的功能和灵活性&#xff0c;可以处理各种HTTP请求&#xff0c;并支持各种数据格式&#xff0c;如JSON、XML等。 WebAPI使用控制器(Controllers)和动作方法(Ac…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验10 IPv4地址 — 构造超网(无分类编址)

一、实验目的 1.加深对构造超网的理解&#xff1b; 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 1.构建网络拓扑&#xff1b; 2.根据各网络所指定的地址块完成以下工作&#…

携手亚马逊云科技,维塑科技推出运动健康领域首个AI大模型

导读&#xff1a;生成式AI运动健康&#xff0c;将如何改变我们的生活&#xff1f; 人工智能技术正不断重塑着我们的工作、生活方式。那么&#xff0c;AI能否改善我们的健康状况呢&#xff1f;AI大模型在运动健康领域的探索为我们带来新的想象。 2023年&#xff0c;全球领先的AI…

java之命令执行审计思路

1 漏洞原理 因用户输入未过滤或净化不完全&#xff0c;导致Web应用程序接收用户输入&#xff0c;拼接到要执行的系统命令中执行。一旦攻击者可以在目标服务器中执行任意系统命令&#xff0c;就意味着服务器已被非法控制。 2 审计中常用函数 一旦攻击者可以在目标服务器中执行…

Redis发布、订阅模式(Pub/Sub)详解

Redis发布、订阅模式&#xff08;PUB-SUB&#xff09;详解 Redis的发布订阅&#xff08;Pub/Sub&#xff09;机制是一种消息通信模式&#xff0c;用于消息的广播。它允许多个客户端订阅&#xff08;Subscribe&#xff09;特定的频道&#xff08;Channel&#xff09;&#xff0c…

Docker镜像拉去不了解决方案

原理&工具 使用海外的服务器拉去镜像&#xff0c;压缩为tar包&#xff0c;传输到本地在本地运行 服务器&#xff1a;这里我使用的是AWS的服务器&#xff0c;新用户注册免费使用1年&#xff08;流量超了就不免费了&#xff0c;一般用不完&#xff0c;还有使用 Cloudflare …

【ARM CoreLink 系列 7.2 -- TZC-400 错误状态寄存器使用详细介绍】

文章目录 TZC-400 错误信息使用Fail address low registerFail address high registerFail control registerFail ID registerTZC-400 错误信息使用 Fail address low register 在 ARM TZC-400 设备中,每个过滤单元都有一个 fail_address_low_<x> 寄存器,其中 <x&g…