網(wǎng)上有很多關(guān)于pos機(jī)收銀系統(tǒng)源碼,SRS4.0源代碼分析之WebRTC推流端處理的知識(shí),也有很多人為大家解答關(guān)于pos機(jī)收銀系統(tǒng)源碼的問(wèn)題,今天pos機(jī)之家(www.afbey.com)為大家整理了關(guān)于這方面的知識(shí),讓我們一起來(lái)看下吧!
本文目錄一覽:
pos機(jī)收銀系統(tǒng)源碼
1、目標(biāo):上一節(jié)分析了SRS4.0中WebRTC模塊的總體架構(gòu)和軟件處理流程。接下來(lái)分析SRS4.0 WebRTC模塊針對(duì)客戶端推流連接上各種協(xié)議報(bào)文的軟件處理邏輯。
2、內(nèi)容:WebRTC模塊在啟動(dòng)過(guò)程中: 1、創(chuàng)建SrsUDPMuxListener監(jiān)聽(tīng)對(duì)象,監(jiān)聽(tīng)指定的UDP端口(默認(rèn)配置8000端口)。
srs_error_t SrsRtcServer::listen_udp() { // 創(chuàng)建監(jiān)聽(tīng)對(duì)象 SrsUdpMuxListener* listener = new SrsUdpMuxListener(this, ip, port); listener->listen(); }srs_error_t SrsUdpMuxListener::listen() { // bind監(jiān)聽(tīng)端口,并啟動(dòng)監(jiān)聽(tīng)協(xié)程 srs_udp_listen(ip, port, &lfd); trd = new SrsSTCoroutine("udp", this, cid); trd->start(); }
2、啟動(dòng)此對(duì)象內(nèi)部的SrsUdpMuxListener::cycle()協(xié)程,從UDP監(jiān)聽(tīng)端口接收數(shù)據(jù)。
srs_error_t SrsUdpMuxListener::cycle() { // 此協(xié)程從監(jiān)聽(tīng)端口讀取數(shù)據(jù) ...... SrsUdpMuxsocket skt(lfd); while (true) { // 以阻塞方式從監(jiān)聽(tīng)端口讀取數(shù)據(jù), // 在skt.recvfrom內(nèi)部使用對(duì)端地址的port(16bit)+IPv4(32bit)構(gòu)成一個(gè)fast_id_(64bit) // 同理,使用對(duì)端地址的ipv4+port構(gòu)成一個(gè)字符串類型的peer_id_ skt.recvfrom(SRS_UTIME_NO_TIMEOUT); handler->on_udp_packet(&skt); // 這里實(shí)際是調(diào)用SrsRtcServer::on_udp_packet() }}int SrsUdpMuxSocket::recvfrom(srs_utime_t timeout){ nread = srs_recvfrom(lfd, buf, nb_buf, (sockaddr*)&from, &fromlen, timeout);}
根據(jù)上一節(jié)的介紹,推流客戶端通過(guò)API接口完成SDP交換,再?gòu)姆?wù)器的SDP信息中,獲取服務(wù)器的IP地址+端口號(hào),并按照WebRTC協(xié)議的要求,向服務(wù)器端口依次發(fā)送各種協(xié)議報(bào)文,完成客戶端與服務(wù)器的連接建立、安全認(rèn)證和RTP報(bào)文加密傳輸。
所以,WebRTC客戶端與服務(wù)器的連接建立過(guò)程中大概涉及四種主要的協(xié)議處理
客戶端和服務(wù)端通過(guò)STUN協(xié)議和ICE機(jī)制建立連接客戶端和服務(wù)端通過(guò)DTLS協(xié)議報(bào)文完成安全認(rèn)證并生成SRTP加解密所需的密鑰客戶端和服務(wù)端之間通過(guò)SRTP算法實(shí)現(xiàn)RTP報(bào)文的加解密客戶端和服務(wù)端之間通過(guò)RTCP報(bào)文完成音視頻數(shù)據(jù)的Qos處理srs_error_t SrsRtcServer::on_udp_packet(SrsUdpMuxSocket* skt) { // 查找udp客戶端對(duì)應(yīng)的SrsRtcConnection session = (SrsRtcConnection*)_srs_rtc_manager->find_by_fast_id(fast_id); session = (SrsRtcConnection*)_srs_rtc_manager->find_by_id(peer_id); // STUN協(xié)議報(bào)文處理 if (srs_is_stun((uint8_t*)data, size)) { // ping.decode()內(nèi)部檢查接收到的必須是合法STUN報(bào)文,否則返回錯(cuò)誤信息 if ((err = ping.decode(data, size)) != srs_success) { return srs_error_wrap(err, "decode stun packet failed"); } return session->on_stun(skt, &ping); } // RTP協(xié)議報(bào)文處理 if (is_rtp_or_rtcp && !is_rtcp) { return session->on_rtp(data, size); } // RTCP協(xié)議報(bào)文處理 if (is_rtp_or_rtcp && is_rtcp) { return session->on_rtcp(data, size); } // DTLS協(xié)議報(bào)文處理 if (srs_is_dtls((uint8_t*)data, size)) { return session->on_dtls(data, size); }}3.1 STUN報(bào)文格式與Lite-ICE協(xié)商
由于IPv4地址不足以及網(wǎng)絡(luò)架構(gòu)的原因,一般用戶的電腦或手機(jī)總是在一個(gè)局域網(wǎng)中,通過(guò)連接NAT網(wǎng)關(guān)接入公網(wǎng)Internet網(wǎng)絡(luò)。
一般情況下,同一個(gè)局域網(wǎng)的設(shè)備之間通過(guò)私網(wǎng)IP地址進(jìn)行通信,局域網(wǎng)設(shè)備通過(guò)NAT網(wǎng)關(guān)獲取一個(gè)公網(wǎng)IP+端口實(shí)現(xiàn)與公網(wǎng)服務(wù)器之間的通信。
不同局域網(wǎng)中的設(shè)備,因?yàn)榛ハ嘀g不知道對(duì)端設(shè)備的公網(wǎng)IP,所以一般情況下,無(wú)法直接通信。
STUN協(xié)議簡(jiǎn)單說(shuō),就是讓一種私網(wǎng)設(shè)備獲取自身公網(wǎng)IP地址的方法,它的運(yùn)行原理很簡(jiǎn)單,如下所示:
\u2002\u2002\u2002\u20021、處于局域網(wǎng)的私網(wǎng)設(shè)備向公網(wǎng)STUN服務(wù)器發(fā)送STUN Binding request請(qǐng)求報(bào)文。
\u2002\u2002\u2002\u20022、請(qǐng)求報(bào)文經(jīng)過(guò)NAT網(wǎng)關(guān)時(shí),請(qǐng)求報(bào)文中的源IP和源端口號(hào)被NAT網(wǎng)關(guān)修改為網(wǎng)關(guān)出口的公網(wǎng)IP+端口號(hào)。
\u2002\u2002\u2002\u20023、STUN服務(wù)器接收到請(qǐng)求報(bào)文后,返回一個(gè)STUN Binding Response 響應(yīng)報(bào)文,并將服務(wù)器所看到的設(shè)備公網(wǎng)IP地址+端口信息(這個(gè)地址也被稱為服務(wù)器反射地址server reflex address),放到響應(yīng)報(bào)文的凈荷中一起返回給私網(wǎng)設(shè)備。
所以,如上過(guò)程所示,SRS4.0的WebRTC模塊首先要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的STUN服務(wù),即接收客戶端發(fā)送的STUN Binding Request請(qǐng)求報(bào)文,并返回一個(gè)STUN Binding Response 響應(yīng)報(bào)文。代碼如下:
1、SrsStunPacket::decode()函數(shù)用于校驗(yàn)客戶端發(fā)送的Binding Request請(qǐng)求報(bào)文是否正確,并得到報(bào)文各字段信息
srs_error_t SrsStunPacket::decode(const char* buf, const int nb_buf){ SrsBuffer* stream = new SrsBuffer(const_cast<char*>(buf), nb_buf); if (stream->left() < 20) { // 校驗(yàn)STUN報(bào)文長(zhǎng)度一定不能少于20個(gè)字節(jié) return srs_error_new(ERROR_RTC_STUN, "invalid stun packet, size=%d", stream->size()); } // 按STUN報(bào)文格式依次讀取各個(gè)字段,用于報(bào)文格式校驗(yàn) message_type = stream->read_2bytes(); uint16_t message_len = stream->read_2bytes(); // STUN報(bào)文除去頭部以后的凈荷長(zhǎng)度 string magic_cookie = stream->read_string(4); transcation_id = stream->read_string(12); // 如果STUN報(bào)文的凈荷長(zhǎng)度+20字節(jié)的STUN報(bào)文頭不等于UDP數(shù)據(jù)包長(zhǎng)度,則數(shù)據(jù)包不是STUN報(bào)文,直接丟棄 if (nb_buf != 20 + message_len) { return srs_error_new(ERROR_RTC_STUN, "invalid stun packet, message_len=%d, nb_buf=%d", message_len, nb_buf); } // 按照TLV方式,依次解析STUN報(bào)文凈荷部分的各個(gè)Attributes信息 while (stream->left() >= 4) { uint16_t type = stream->read_2bytes(); uint16_t len = stream->read_2bytes(); ...... }}
STUN報(bào)文格式總是由一個(gè)20字節(jié)的STUN報(bào)文頭+若干個(gè)TLV格式的STUN屬性(attributes)字段組成,如下: STUN報(bào)文頭格式(固定20個(gè)字節(jié),所以軟件校驗(yàn)時(shí)首先判斷報(bào)文長(zhǎng)度小于20字節(jié)的都不是STUN報(bào)文)
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| STUN Message Type | Message Length |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Magic Cookie 固定值0x2112A442 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| || Transaction ID 12Byte || |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+根據(jù)RFC5766,常用的 STUN Message Type定義如下: BINDING REQUEST(0x0001) / RESPONSE(0x0101) / ERROR_RESPONSE(0x0111) / INDICATION(0x0011) SHARED_SECRET REQUEST(0x0002) / RESPONSE(0x0102) / ERROR_RESPONSE(0x0112) ALLOCATE REQUEST(0x0003) / RESPONSE(0x0103) / ERROR_RESPONSE(0x0113) REFRESH REQUEST(0x0004) / RESPONSE(0x0104) / ERROR_RESPONSE(0x0114) SEND INDICATION(0x0016) DATA INDICATION(0x0017) CREATE_PERM REQUEST(0x0008) / RESPONSE(0x0108) / ERROR_RESPONSE(0x0118) CHANNEL_BIND REQUEST(0x0009) / RESPONSE(0x0109) / ERROR_RESPONSE(0x0119)
STUN報(bào)文屬性(attributes)字段格式
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Type | Length |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Value(variable) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+常見(jiàn)的屬性類型: STUN_ATTR_MAPPED_ADDR = 0x0001,/**MAPPED-ADDRESS */ STUN_ATTR_USERNAME = 0x0006,/**USERNAME attribute */ STUN_ATTR_PASSWORD = 0x0007,/**PASSWORD attribute */ STUN_ATTR_MESSAGE_INTEGRITY = 0x0008,/**MESSAGE-INTEGRITY */ STUN_ATTR_ERROR_CODE = 0x0009,/**ERROR-CODE */ STUN_ATTR_REALM = 0x0014,/**REALM attribute */ STUN_ATTR_NONCE = 0x0015,/**NONCE attribute */ STUN_ATTR_XOR_RELAYED_ADDR = 0x0016,/**TURN XOR-RELAYED-ADDRESS */ STUN_ATTR_XOR_MAPPED_ADDR = 0x0020,/**XOR-MAPPED-ADDRESS */
通過(guò)wireshark抓取STUN Binding Request報(bào)文,報(bào)文各字段如下所示:
2、服務(wù)端Lite-ICE工作原理:
1)服務(wù)端只處理Binding Request請(qǐng)求,并返回Binding Response響應(yīng)報(bào)文。
2)把session狀態(tài)設(shè)置為DOING_DTLS_HANDSHAKE狀態(tài),再調(diào)用SrsSecurityTransport::start_active_handshake()啟動(dòng)DTLS握手。
srs_error_t SrsRtcConnection::on_stun(SrsUdpMuxSocket* skt, SrsStunPacket* r) { if (!r->is_binding_request()) { return err; } update_sendonly_socket(skt); on_binding_request(r);}srs_error_t SrsRtcConnection::on_binding_request(SrsStunPacket* r){ SrsStunPacket stun_binding_response; stun_binding_response.set_message_type(BindingResponse); // 構(gòu)造Binding響應(yīng)報(bào)文 ...... sendonly_skt->sendto(stream->data(), stream->pos(), 0); // 發(fā)送Binding響應(yīng)報(bào)文 if (state_ == WAITING_STUN) { state_ = DOING_DTLS_HANDSHAKE; transport_->start_active_handshake(); // 啟動(dòng)DTLS協(xié)議握手,實(shí)際passive端啥也沒(méi)做 }}
通過(guò)wireshark抓取STUN Binding Response報(bào)文,報(bào)文各字段如下所示:
STUN協(xié)議涉及內(nèi)容較多,但Lite-ICE模式下,主要只涉及上面兩種報(bào)文,要全面了解STUN協(xié)議可參考以下鏈接:
STUN協(xié)議簡(jiǎn)要介紹_stun tcp_嫩草終結(jié)者的博客-CSDN博客 STUN協(xié)議簡(jiǎn)要介紹
WebRTC STUN | Short-term 消息認(rèn)證-阿里云開(kāi)發(fā)者社區(qū) WebRTC STUN | Short-term 消息認(rèn)證
C++音視頻學(xué)習(xí)資料免費(fèi)獲取方法:關(guān)注音視頻開(kāi)發(fā)T哥,點(diǎn)擊「鏈接」即可免費(fèi)獲取2023年最新C++音視頻開(kāi)發(fā)進(jìn)階獨(dú)家免費(fèi)學(xué)習(xí)大禮包!
2.2 DTLS原理與協(xié)商過(guò)程借用一份WebRTC協(xié)議棧的網(wǎng)圖可知,當(dāng)?shù)讓覷DP通道連接建立后,接下來(lái)需要完成DTLS握手。DTLS本身很復(fù)雜,對(duì)于初學(xué)者我們大概需要明白幾個(gè)關(guān)鍵點(diǎn):
1、為了安全,公網(wǎng)上傳輸?shù)臄?shù)據(jù)必須加密。TLS是針對(duì)TCP協(xié)議的安全加密協(xié)議,而DTLS則是針對(duì)UDP協(xié)議的安全加密協(xié)議。
2、從性能的角度看,對(duì)于大批量數(shù)據(jù)傳輸,只能使用對(duì)稱加密方式。但是,對(duì)稱加密的密鑰本身需要先通過(guò)非對(duì)稱加密后,再經(jīng)過(guò)網(wǎng)絡(luò)傳輸?shù)綄?duì)端。DTLS和TLS內(nèi)部已經(jīng)包含了這兩種加密方式。
3、WebRTC中,DTLS只是為DataChannel / SCTP提供加密服務(wù),而音視頻數(shù)據(jù)實(shí)際上是通過(guò)SRTP協(xié)議加密的,DTLS此時(shí)的工作只是為SRTP生成并導(dǎo)出密鑰。
所以,啟動(dòng)DTLS握手協(xié)商之前,必須為DTLS生成密鑰和證書,這部分代碼在SrsDtlsCertificate::initialize()函數(shù)中,相關(guān)函數(shù)的詳細(xì)說(shuō)明可以參考OpenSSL接口文檔。
srs_error_t SrsDtlsCertificate::initialize(){ srtp_init(); // 初始化SRTP加密協(xié)議庫(kù) ...... // 使用RSA或ECDSA算法為DTLS生成公鑰和私鑰(缺省使用ECDSA算法) ...... dtls_cert = X509_new(); // 創(chuàng)建509格式的證書 X509_set_pubkey(dtls_cert, dtls_pkey); // 將公鑰放入證書用于對(duì)外發(fā)布 X509_sign(dtls_cert, dtls_pkey, EVP_sha1(); // 使用私鑰對(duì)證書簽名,防止證書被篡改 // 生成證書的摘要信息,并作為SDP的fingerprint屬性字段,隨SDP一起與對(duì)端進(jìn)行交換 // 參與DTLS握手的雙方,根據(jù)這個(gè)對(duì)端證書簽名驗(yàn)證對(duì)端證書的有效性,最終完成DTLS握手 X509_digest(dtls_cert, EVP_sha256(), md, &n); }
為每條session(SrsRtcConnection對(duì)象)創(chuàng)建SSL_CTX數(shù)據(jù)結(jié)構(gòu)和SSL數(shù)據(jù)結(jié)構(gòu),并將證書和密鑰導(dǎo)入SSL_CTX數(shù)據(jù)結(jié)構(gòu)。
srs_error_t SrsRtcConnection::initialize(SrsRequest* r, bool dtls, bool srtp, string username) { ...... transport_->initialize(cfg); // session對(duì)象傳輸層初始化}srs_error_t SrsSecurityTransport::initialize(SrsSessionConfig* cfg){ return dtls_->initialize(cfg->dtls_role, cfg->dtls_version);}srs_error_t SrsDtls::initialize(std::string role, std::string version){ if (role == "active") { // 這個(gè)role在SDP報(bào)文中也有體現(xiàn),主動(dòng)發(fā)起DTLS協(xié)商的一般是客戶端 impl = new SrsDtlsClientImpl(callback_); } else { impl = new SrsDtlsServerImpl(callback_); // 服務(wù)端缺省創(chuàng)建這個(gè)對(duì)象 } return impl->initialize(version, role);}srs_error_t SrsDtlsServerImpl::initialize(std::string version, std::string role){ SrsDtlsImpl::initialize(version, role); SSL_set_accept_state(dtls); // 設(shè)置Dtls工作在服務(wù)端模式 return err;}srs_error_t SrsDtlsImpl::initialize(std::string version, std::string role){ dtls_ctx = srs_build_dtls_ctx(version_, role); // 此函數(shù)用于創(chuàng)建SSL_CTX數(shù)據(jù)結(jié)構(gòu) dtls = SSL_new(dtls_ctx); // 創(chuàng)建SSL數(shù)據(jù)結(jié)構(gòu),作為DTLS算法模塊 ...... DTLS_set_timer_cb(dtls, dtls_timer_cb); // 定時(shí)器回調(diào)函數(shù)用于UDP報(bào)文重發(fā) bio_in = BIO_new(BIO_s_mem(); bio_out = BIO_new(BIO_s_mem(); SSL_set_bio(dtls, bio_in, bio_out); // 創(chuàng)建BIO對(duì)象,協(xié)助dtls算法模塊讀寫數(shù)據(jù)}
另外,如上最后一個(gè)函數(shù)可知,這里還引入了BIO數(shù)據(jù)結(jié)構(gòu),用于協(xié)助DTLS算法模塊收發(fā)數(shù)據(jù),具體如下:
WebRTC模塊SrsUdpMuxListener::cycle()協(xié)程接收到dtls報(bào)文,并通過(guò)SrsRtcConnection::on_dtls()函數(shù)將報(bào)文轉(zhuǎn)給DTLS算法模塊。
srs_error_t SrsRtcConnection::on_dtls(char* data, int nb_data){ return transport_->on_dtls(data, nb_data);}srs_error_t SrsSecurityTransport::on_dtls(char* data, int nb_data){ return dtls_->on_dtls(data, nb_data);}srs_error_t SrsDtls::on_dtls(char* data, int nb_data){ return impl->on_dtls(data, nb_data);}srs_error_t SrsDtlsImpl::on_dtls(char* data, int nb_data){ do_on_dtls(data, nb_data)) != srs_success) }srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data){ BIO_reset(bio_in); BIO_reset(bio_out); BIO_write(bio_in, data, nb_data); // 將從網(wǎng)絡(luò)接收的DTLS數(shù)據(jù)通過(guò)BIO寫入SSL do_handshake(); // 處理DTLS握手 // do_handshake()函數(shù)用于完成握手,下面的處理大概是讀取BIO中生成的應(yīng)答數(shù)據(jù),再通過(guò)網(wǎng)絡(luò)發(fā)送給對(duì)端 for (int i = 0; i < 1024 && BIO_ctrl_pending(bio_in) > 0; i++) { int r0 = SSL_read(dtls, buf, sizeof(buf)); int r1 = SSL_get_error(dtls, r0); if (r0 <= 0) { if (r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE) { break; } int size = BIO_get_mem_data(bio_out, (char**)&data); callback_->write_dtls_data(data, size); } }}srs_error_t SrsDtlsImpl::do_handshake(){ int r0 = SSL_do_handshake(dtls); int r1 = SSL_get_error(dtls, r0); if (r1 == SSL_ERROR_NONE) { // 如果返回值是SSL_ERROR_NONE,則表示DTLS握手成功 handshake_done_for_us = true; } ...... if (handshake_done_for_us) { err = on_handshake_done(); // 向外通知DTLS握手成功 }}srs_error_t SrsDtlsServerImpl::on_handshake_done() { callback_->on_dtls_handshake_done();}srs_error_t SrsSecurityTransport::on_dtls_handshake_done(){ handshake_done = true; // 設(shè)置DTLS握手成功標(biāo)志 ...... srtp_initialize(); // 接下來(lái)完成SRTP初始化 ...... return session_->on_connection_established(); // 通知session對(duì)象,WebRTC連接建立}
DTLS協(xié)商完成后,會(huì)自動(dòng)生成SRTP發(fā)送和接收?qǐng)?bào)文時(shí)需要的密鑰,使用此密鑰完成SRTP初始化:
srs_error_t SrsSecurityTransport::srtp_initialize(){ dtls_->get_srtp_key(recv_key, send_key); // 從DTLS中得到SRTP發(fā)送和接收?qǐng)?bào)文時(shí)需要的密鑰 srtp_->initialize(recv_key, send_key); // 將收發(fā)報(bào)文所需的密鑰寫入SRTP協(xié)議模塊 // 此函數(shù)內(nèi)部通過(guò)調(diào)用srtp_create創(chuàng)建收發(fā)加密報(bào)文的上下文句柄 return err;}
Session對(duì)象(SrsRtcConnection)接收到WebRTC連接建立的通知,啟動(dòng)屬于此連接的推流端接收應(yīng)答協(xié)程SrsRtcPLIWorker::cycle()或拉流端發(fā)送協(xié)程SrsRtcPlayStream::cycle()
srs_error_t SrsRtcConnection::on_connection_established(){ { SrsRtcPublishStream* publisher = it->second; publisher->start(); } 或 { SrsRtcPlayStream* player = it->second; player->start(); }}3.3 連接建立,服務(wù)端處理RTP報(bào)文
DTLS協(xié)商成功后,客戶端與服務(wù)器之間的WebRTC連接真正建立,服務(wù)端接下來(lái)開(kāi)始處理RTP報(bào)文: 1)根據(jù)接收的RTP報(bào)文的IP找到SrsRtcConnection對(duì)象 2)根據(jù)接收的RTP報(bào)文的SSRC找到SrsRtcPublishStream對(duì)象 3)根據(jù)配置,對(duì)RTP報(bào)文做相應(yīng)的特殊處理 4)調(diào)用srtp_unprotect()函數(shù)對(duì)SRTP報(bào)文進(jìn)行解密 5)最終調(diào)用SrsRtcPublishStream::do_on_rtp_plaintext()函數(shù)處理RTP報(bào)文
srs_error_t SrsRtcConnection::on_rtp() { ...... find_publisher(data, nb_data, &publisher); // 根據(jù)報(bào)文頭部的SSRC找到對(duì)應(yīng)的流對(duì)象 return publisher->on_rtp(data, nb_data);}srs_error_t SrsRtcPublishStream::on_rtp(char* data, int nb_data){ if (nn_simulate_nack_drop) { return err; } // 如果設(shè)置了模擬丟包觸發(fā)NACK,則直接返回 if (twcc_id_) { // 如果開(kāi)啟了TWCC,RTP包會(huì)帶擴(kuò)展信息,增加預(yù)處理防止后續(xù)SRTP解密報(bào)文時(shí)出錯(cuò) srs_rtp_fast_parse_twcc(data, nb_data, twcc_id_, twcc_sn); } if (pt_to_drop_) { // 如果某些類型的報(bào)文在配置文件中設(shè)置為丟棄,這里需要識(shí)別并丟棄 uint8_t pt = srs_rtp_fast_parse_pt(data, nb_data); if (pt_to_drop_ == pt) { return err; } } // 最終調(diào)用srtp_unprotect()函數(shù)對(duì)SRTP報(bào)文進(jìn)行解密 session_->transport_->unprotect_rtp(plaintext, &nb_plaintext); on_rtp_plaintext(); // 處理SRTP解密后的RTP明文}srs_error_t SrsRtcPublishStream::on_rtp_plaintext(char* plaintext, int nb_plaintext) { do_on_rtp_plaintext(pkt, &buf);}
此函數(shù)內(nèi)部一邊將報(bào)文放入消費(fèi)者隊(duì)列,一邊根據(jù)接收?qǐng)?bào)文的序列號(hào)調(diào)整NACK隊(duì)列
srs_error_t SrsRtcPublishStream::do_on_rtp_plaintext(SrsRtpPacket*& pkt, SrsBuffer* buf) { ...... pkt->decode(buf); // SrsRtpPacket數(shù)據(jù)包在這里被直接放入SrsRtcConsumer對(duì)象的RTP報(bào)文緩存隊(duì)列 SrsRtcAudioRecvTrack* audio_track = get_audio_track(ssrc); SrsRtcVideoRecvTrack* video_track = get_video_track(ssrc); audio_track->on_rtp(source, pkt); video_track->on_rtp(source, pkt); // 如果circuit-breaker使能,當(dāng)判斷到網(wǎng)絡(luò)發(fā)生擁塞時(shí),則停止發(fā)送RTP數(shù)據(jù)包 if (_srs_circuit_breaker->hybrid_critical_water_level()) { return err;} // 如果NACK使能,根據(jù)接收?qǐng)?bào)文的序列號(hào)調(diào)整接收對(duì)象SrsRtcRecvTrack內(nèi)部的NACK隊(duì)列 if (nack_enabled_) { audio_track->on_nack(&pkt); video_track->on_nack(&pkt); // 這里兩個(gè)函數(shù)其實(shí)都是SrsRtcRecvTrack::on_nack()函數(shù) }}
根據(jù)接收?qǐng)?bào)文的序列號(hào)調(diào)整NACK隊(duì)列,用于后續(xù)指導(dǎo)NACK報(bào)文的發(fā)送
srs_error_t SrsRtcRecvTrack::on_nack(SrsRtpPacket** ppkt) { uint16_t seq = pkt->header.get_sequence(); if (nack_receiver_->find(seq)) { nack_receiver_->remove(seq); // 從NACK隊(duì)列中刪除已接收到的序列號(hào) return err; } // 將接收到的序列號(hào)加入NACK隊(duì)列,并重新計(jì)算NACK隊(duì)列最新的起始序列號(hào)和結(jié)束序列號(hào) rtp_queue_->update(seq, nack_first, nack_last); // 將最新的起始序列號(hào)和結(jié)束序列號(hào)寫入NACK隊(duì)列,并通過(guò)check_queue_size()檢測(cè)如果隊(duì)列滿時(shí)清空隊(duì)列 if (srs_rtp_seq_distance(nack_first, nack_last) > 0) { nack_receiver_->insert(nack_first, nack_last); nack_receiver_->check_queue_size(); } rtp_queue_->set(seq, pkt->copy()); // 將收到的RTP包放入一個(gè)環(huán)形緩沖區(qū)}3.4 服務(wù)端RTCP報(bào)文處理
RTP報(bào)文封裝音視頻數(shù)據(jù),底層采用UDP協(xié)議發(fā)送,本身屬于不可靠傳輸。通過(guò)引入RTCP協(xié)議,周期性的發(fā)送RTCP報(bào)文描述本端的接收和發(fā)送狀態(tài),可以提升音視頻數(shù)據(jù)的可靠傳輸、流量控制和擁塞控制等服務(wù)質(zhì)量保證。
WebRTC服務(wù)模塊對(duì)于RTCP報(bào)文主要有兩種處理邏輯: 1)根據(jù)服務(wù)端狀態(tài)主動(dòng)發(fā)送RTCP報(bào)文 2)接收并處理客戶端發(fā)送的RTCP報(bào)文
3.4.1 NACK報(bào)文收發(fā)原理:1)首先創(chuàng)建一個(gè)NACK定時(shí)器對(duì)象,它的內(nèi)部工作原理就是以觀察者模式訂閱了一個(gè)20毫秒周期的系統(tǒng)定時(shí)器
SrsRtcConnectionNackTimer::SrsRtcConnectionNackTimer(SrsRtcConnection* p) : p_(p){ _srs_hybrid->timer20ms()->subscribe(this);}
2)20毫秒定時(shí)器超時(shí),周期性調(diào)用SrsRtcPublishStream::check_send_nacks()檢測(cè)推流接收端是否需要發(fā)送NACK
srs_error_t SrsRtcConnectionNackTimer::on_timer(srs_utime_t interval){ ...... std::map<std::string, SrsRtcPublishStream*>::iterator it; for (it = p_->publishers_.begin(); it != p_->publishers_.end(); it++) { SrsRtcPublishStream* publisher = it->second; publisher->check_send_nacks(); // 檢測(cè)推流接收端是否需要發(fā)送NACK } return err;}srs_error_t SrsRtcPublishStream::check_send_nacks(){ for (int i = 0; i < (int)video_tracks_.size(); ++i) { track->check_send_nacks(); } for (int i = 0; i < (int)audio_tracks_.size(); ++i) { track->check_send_nacks()) != srs_success) } return err;}srs_error_t SrsRtcRecvTrack::do_check_send_nacks(uint32_t& timeout_nacks){ session_->check_send_nacks(nack_receiver_, track_desc_->ssrc_, sent_nacks, timeout_nacks); return err;}
3)構(gòu)造RTCP類型的NACK報(bào)文并發(fā)送
void SrsRtcConnection::check_send_nacks(SrsRtpNackForReceiver* nack, uint32_t ssrc, uint32_t& sent_nacks, uint32_t& timeout_nacks){ SrsRtcpNack rtcpNack(ssrc); rtcpNack.set_media_ssrc(ssrc); nack->get_nack_seqs(rtcpNack, timeout_nacks); if(rtcpNack.empty()){ return; } char buf[kRtcpPacketSize]; SrsBuffer stream(buf, sizeof(buf)); rtcpNack.encode(&stream); int nb_protected_buf = stream.pos(); transport_->protect_rtcp(stream.data(), &nb_protected_buf); sendonly_skt->sendto(stream.data(), nb_protected_buf, 0);}3.4.2 PLI(Picture Loss Indication)報(bào)文收發(fā)原理:
當(dāng)視頻接收端向發(fā)送端反饋一個(gè)PLI報(bào)文時(shí),發(fā)送方的編碼器會(huì)重新生成關(guān)鍵幀并發(fā)送給接收端。
SRS4.0 WebRTC模塊中推流端接收對(duì)象SrsRtcPublishStream和拉流端發(fā)送對(duì)象SrsRtcPlayStream在建立連接的時(shí)候,都會(huì)創(chuàng)建各自的SrsRtcPLIWorker::cycle()協(xié)程。
1)推流端接收對(duì)象SrsRtcPublishStream創(chuàng)建的SrsRtcPLIWorker::cycle()協(xié)程,此協(xié)程等待條件變量喚醒后,遍歷plis_隊(duì)列,發(fā)送PLI請(qǐng)求報(bào)文,
srs_error_t SrsRtcPLIWorker::cycle(){ while (true) { while (!plis_.empty()) { ...... plis.swap(plis_); for (map<uint32_t, SrsContextId>::iterator it = plis.begin(); it != plis.end(); ++it) { ...... handler_->do_request_keyframe(ssrc, cid); } } srs_cond_wait(wait_); } return err;}
其中plis_隊(duì)列和條件變量喚醒的操作,都是在SrsRtcSource對(duì)象的定時(shí)器超時(shí)函數(shù)中處理的。
SrsRtcSource繼承ISrsFastTimer類,再通過(guò)SrsRtcSource::on_publish()函數(shù),以觀察者模式訂閱了一個(gè)100毫秒周期的系統(tǒng)定時(shí)器
class SrsRtcSource : public ISrsFastTimer srs_error_t SrsRtcSource::on_publish(){ if (bridger_) { // The PLI interval for RTC2RTMP. pli_for_rtmp_ = _srs_config->get_rtc_pli_for_rtmp(req->vhost); // @see SrsRtcSource::on_timer() _srs_hybrid->timer100ms()->subscribe(this); }}
最終SrsRtcSource::on_timer()超時(shí)函數(shù)以100毫秒的周期被執(zhí)行,以默認(rèn)6秒為周期調(diào)用一次publish_stream->request_keyframe()函數(shù),插入plis隊(duì)列,并觸發(fā)條件變量。
srs_error_t SrsRtcSource::on_timer(srs_utime_t interval){ ...... if (!publish_stream_) { return err; } // Request PLI and reset the timer. if (true) { pli_elapsed_ += interval; if (pli_elapsed_ < pli_for_rtmp_) { return err; } pli_elapsed_ = 0; } for (int i = 0; i < (int)stream_desc_->video_track_descs_.size(); i++) { SrsRtcTrackDescription* desc = stream_desc_->video_track_descs_.at(i); publish_stream_->request_keyframe(desc->ssrc_); } return err;}void SrsRtcPublishStream::request_keyframe(uint32_t ssrc) { SrsContextId sub_cid = _srs_context->get_id(); pli_worker_->request_keyframe(ssrc, sub_cid);}void SrsRtcPLIWorker::request_keyframe(uint32_t ssrc, SrsContextId cid) { plis_.insert(make_pair(ssrc, cid)); srs_cond_signal(wait_);}3.4.3 接收并處理對(duì)端發(fā)送的RTCP報(bào)文
srs_error_t SrsRtcConnection::on_rtcp() { SrsSecurityTransport::unprotect_rtp(); // 使用srtp_unprotect_rtcp解密RTCP報(bào)文 SrsRtcpCompound rtcp_compound; rtcp_compound.decode(buffer); // 解析得到具體的RTCP報(bào)文 // FIR/SR/RR/SDES/BYE/APP/RTPFB/PSFB/XR while(NULL != (rtcp = rtcp_compound.get_next_rtcp())) { dispatch_rtcp(rtcp); // 此函數(shù)為各類RTCP報(bào)文提供處理路由 }}srs_error_t SrsRtcConnection::dispatch_rtcp(SrsRtcpCommon* rtcp){ // 分類處理各類RTCP報(bào)文}4、總結(jié):
本章以WebRTC推流端連接建立的過(guò)程為線索,分析了過(guò)程中各種協(xié)議報(bào)文的基本處理流程:
1、Lite-ICE機(jī)制、STUN Binding Request和STUN Binding Response報(bào)文格式
2、DTLS工作原理、為SRTP獲取密鑰并完成初始化
3、音視頻RTP報(bào)文接收并放入對(duì)應(yīng)的拉流端消費(fèi)者隊(duì)列,以及更新NACK隊(duì)列
4、定時(shí)發(fā)送RTCP報(bào)文與接收處理對(duì)端發(fā)送的RTCP報(bào)文。
原文鏈接:10、SRS4.0源代碼分析之WebRTC推流端處理_黑板報(bào)的博客-CSDN博客
以上就是關(guān)于pos機(jī)收銀系統(tǒng)源碼,SRS4.0源代碼分析之WebRTC推流端處理的知識(shí),后面我們會(huì)繼續(xù)為大家整理關(guān)于pos機(jī)收銀系統(tǒng)源碼的知識(shí),希望能夠幫助到大家!
