網上有很多關于pos機收銀系統(tǒng)源碼,SRS4.0源代碼分析之WebRTC推流端處理的知識,也有很多人為大家解答關于pos機收銀系統(tǒng)源碼的問題,今天pos機之家(www.rcqwhg.com)為大家整理了關于這方面的知識,讓我們一起來看下吧!
本文目錄一覽:
pos機收銀系統(tǒng)源碼
1、目標:上一節(jié)分析了SRS4.0中WebRTC模塊的總體架構和軟件處理流程。接下來分析SRS4.0 WebRTC模塊針對客戶端推流連接上各種協議報文的軟件處理邏輯。
2、內容:WebRTC模塊在啟動過程中: 1、創(chuàng)建SrsUDPMuxListener監(jiān)聽對象,監(jiān)聽指定的UDP端口(默認配置8000端口)。
srs_error_t SrsRtcServer::listen_udp() { // 創(chuàng)建監(jiān)聽對象 SrsUdpMuxListener* listener = new SrsUdpMuxListener(this, ip, port); listener->listen(); }srs_error_t SrsUdpMuxListener::listen() { // bind監(jiān)聽端口,并啟動監(jiān)聽協程 srs_udp_listen(ip, port, &lfd); trd = new SrsSTCoroutine("udp", this, cid); trd->start(); }
2、啟動此對象內部的SrsUdpMuxListener::cycle()協程,從UDP監(jiān)聽端口接收數據。
srs_error_t SrsUdpMuxListener::cycle() { // 此協程從監(jiān)聽端口讀取數據 ...... SrsUdpMuxsocket skt(lfd); while (true) { // 以阻塞方式從監(jiān)聽端口讀取數據, // 在skt.recvfrom內部使用對端地址的port(16bit)+IPv4(32bit)構成一個fast_id_(64bit) // 同理,使用對端地址的ipv4+port構成一個字符串類型的peer_id_ skt.recvfrom(SRS_UTIME_NO_TIMEOUT); handler->on_udp_packet(&skt); // 這里實際是調用SrsRtcServer::on_udp_packet() }}int SrsUdpMuxSocket::recvfrom(srs_utime_t timeout){ nread = srs_recvfrom(lfd, buf, nb_buf, (sockaddr*)&from, &fromlen, timeout);}
根據上一節(jié)的介紹,推流客戶端通過API接口完成SDP交換,再從服務器的SDP信息中,獲取服務器的IP地址+端口號,并按照WebRTC協議的要求,向服務器端口依次發(fā)送各種協議報文,完成客戶端與服務器的連接建立、安全認證和RTP報文加密傳輸。
所以,WebRTC客戶端與服務器的連接建立過程中大概涉及四種主要的協議處理
客戶端和服務端通過STUN協議和ICE機制建立連接客戶端和服務端通過DTLS協議報文完成安全認證并生成SRTP加解密所需的密鑰客戶端和服務端之間通過SRTP算法實現RTP報文的加解密客戶端和服務端之間通過RTCP報文完成音視頻數據的Qos處理srs_error_t SrsRtcServer::on_udp_packet(SrsUdpMuxSocket* skt) { // 查找udp客戶端對應的SrsRtcConnection session = (SrsRtcConnection*)_srs_rtc_manager->find_by_fast_id(fast_id); session = (SrsRtcConnection*)_srs_rtc_manager->find_by_id(peer_id); // STUN協議報文處理 if (srs_is_stun((uint8_t*)data, size)) { // ping.decode()內部檢查接收到的必須是合法STUN報文,否則返回錯誤信息 if ((err = ping.decode(data, size)) != srs_success) { return srs_error_wrap(err, "decode stun packet failed"); } return session->on_stun(skt, &ping); } // RTP協議報文處理 if (is_rtp_or_rtcp && !is_rtcp) { return session->on_rtp(data, size); } // RTCP協議報文處理 if (is_rtp_or_rtcp && is_rtcp) { return session->on_rtcp(data, size); } // DTLS協議報文處理 if (srs_is_dtls((uint8_t*)data, size)) { return session->on_dtls(data, size); }}3.1 STUN報文格式與Lite-ICE協商
由于IPv4地址不足以及網絡架構的原因,一般用戶的電腦或手機總是在一個局域網中,通過連接NAT網關接入公網Internet網絡。
一般情況下,同一個局域網的設備之間通過私網IP地址進行通信,局域網設備通過NAT網關獲取一個公網IP+端口實現與公網服務器之間的通信。
不同局域網中的設備,因為互相之間不知道對端設備的公網IP,所以一般情況下,無法直接通信。
STUN協議簡單說,就是讓一種私網設備獲取自身公網IP地址的方法,它的運行原理很簡單,如下所示:
\u2002\u2002\u2002\u20021、處于局域網的私網設備向公網STUN服務器發(fā)送STUN Binding request請求報文。
\u2002\u2002\u2002\u20022、請求報文經過NAT網關時,請求報文中的源IP和源端口號被NAT網關修改為網關出口的公網IP+端口號。
\u2002\u2002\u2002\u20023、STUN服務器接收到請求報文后,返回一個STUN Binding Response 響應報文,并將服務器所看到的設備公網IP地址+端口信息(這個地址也被稱為服務器反射地址server reflex address),放到響應報文的凈荷中一起返回給私網設備。
所以,如上過程所示,SRS4.0的WebRTC模塊首先要實現一個簡單的STUN服務,即接收客戶端發(fā)送的STUN Binding Request請求報文,并返回一個STUN Binding Response 響應報文。代碼如下:
1、SrsStunPacket::decode()函數用于校驗客戶端發(fā)送的Binding Request請求報文是否正確,并得到報文各字段信息
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) { // 校驗STUN報文長度一定不能少于20個字節(jié) return srs_error_new(ERROR_RTC_STUN, "invalid stun packet, size=%d", stream->size()); } // 按STUN報文格式依次讀取各個字段,用于報文格式校驗 message_type = stream->read_2bytes(); uint16_t message_len = stream->read_2bytes(); // STUN報文除去頭部以后的凈荷長度 string magic_cookie = stream->read_string(4); transcation_id = stream->read_string(12); // 如果STUN報文的凈荷長度+20字節(jié)的STUN報文頭不等于UDP數據包長度,則數據包不是STUN報文,直接丟棄 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報文凈荷部分的各個Attributes信息 while (stream->left() >= 4) { uint16_t type = stream->read_2bytes(); uint16_t len = stream->read_2bytes(); ...... }}
STUN報文格式總是由一個20字節(jié)的STUN報文頭+若干個TLV格式的STUN屬性(attributes)字段組成,如下: STUN報文頭格式(固定20個字節(jié),所以軟件校驗時首先判斷報文長度小于20字節(jié)的都不是STUN報文)
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 || |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+根據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報文屬性(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) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+常見的屬性類型: 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 */
通過wireshark抓取STUN Binding Request報文,報文各字段如下所示:
2、服務端Lite-ICE工作原理:
1)服務端只處理Binding Request請求,并返回Binding Response響應報文。
2)把session狀態(tài)設置為DOING_DTLS_HANDSHAKE狀態(tài),再調用SrsSecurityTransport::start_active_handshake()啟動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); // 構造Binding響應報文 ...... sendonly_skt->sendto(stream->data(), stream->pos(), 0); // 發(fā)送Binding響應報文 if (state_ == WAITING_STUN) { state_ = DOING_DTLS_HANDSHAKE; transport_->start_active_handshake(); // 啟動DTLS協議握手,實際passive端啥也沒做 }}
通過wireshark抓取STUN Binding Response報文,報文各字段如下所示:
STUN協議涉及內容較多,但Lite-ICE模式下,主要只涉及上面兩種報文,要全面了解STUN協議可參考以下鏈接:
STUN協議簡要介紹_stun tcp_嫩草終結者的博客-CSDN博客 STUN協議簡要介紹
WebRTC STUN | Short-term 消息認證-阿里云開發(fā)者社區(qū) WebRTC STUN | Short-term 消息認證
C++音視頻學習資料免費獲取方法:關注音視頻開發(fā)T哥,點擊「鏈接」即可免費獲取2023年最新C++音視頻開發(fā)進階獨家免費學習大禮包!
2.2 DTLS原理與協商過程借用一份WebRTC協議棧的網圖可知,當底層UDP通道連接建立后,接下來需要完成DTLS握手。DTLS本身很復雜,對于初學者我們大概需要明白幾個關鍵點:
1、為了安全,公網上傳輸的數據必須加密。TLS是針對TCP協議的安全加密協議,而DTLS則是針對UDP協議的安全加密協議。
2、從性能的角度看,對于大批量數據傳輸,只能使用對稱加密方式。但是,對稱加密的密鑰本身需要先通過非對稱加密后,再經過網絡傳輸到對端。DTLS和TLS內部已經包含了這兩種加密方式。
3、WebRTC中,DTLS只是為DataChannel / SCTP提供加密服務,而音視頻數據實際上是通過SRTP協議加密的,DTLS此時的工作只是為SRTP生成并導出密鑰。
所以,啟動DTLS握手協商之前,必須為DTLS生成密鑰和證書,這部分代碼在SrsDtlsCertificate::initialize()函數中,相關函數的詳細說明可以參考OpenSSL接口文檔。
srs_error_t SrsDtlsCertificate::initialize(){ srtp_init(); // 初始化SRTP加密協議庫 ...... // 使用RSA或ECDSA算法為DTLS生成公鑰和私鑰(缺省使用ECDSA算法) ...... dtls_cert = X509_new(); // 創(chuàng)建509格式的證書 X509_set_pubkey(dtls_cert, dtls_pkey); // 將公鑰放入證書用于對外發(fā)布 X509_sign(dtls_cert, dtls_pkey, EVP_sha1(); // 使用私鑰對證書簽名,防止證書被篡改 // 生成證書的摘要信息,并作為SDP的fingerprint屬性字段,隨SDP一起與對端進行交換 // 參與DTLS握手的雙方,根據這個對端證書簽名驗證對端證書的有效性,最終完成DTLS握手 X509_digest(dtls_cert, EVP_sha256(), md, &n); }
為每條session(SrsRtcConnection對象)創(chuàng)建SSL_CTX數據結構和SSL數據結構,并將證書和密鑰導入SSL_CTX數據結構。
srs_error_t SrsRtcConnection::initialize(SrsRequest* r, bool dtls, bool srtp, string username) { ...... transport_->initialize(cfg); // session對象傳輸層初始化}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") { // 這個role在SDP報文中也有體現,主動發(fā)起DTLS協商的一般是客戶端 impl = new SrsDtlsClientImpl(callback_); } else { impl = new SrsDtlsServerImpl(callback_); // 服務端缺省創(chuàng)建這個對象 } 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); // 設置Dtls工作在服務端模式 return err;}srs_error_t SrsDtlsImpl::initialize(std::string version, std::string role){ dtls_ctx = srs_build_dtls_ctx(version_, role); // 此函數用于創(chuàng)建SSL_CTX數據結構 dtls = SSL_new(dtls_ctx); // 創(chuàng)建SSL數據結構,作為DTLS算法模塊 ...... DTLS_set_timer_cb(dtls, dtls_timer_cb); // 定時器回調函數用于UDP報文重發(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對象,協助dtls算法模塊讀寫數據}
另外,如上最后一個函數可知,這里還引入了BIO數據結構,用于協助DTLS算法模塊收發(fā)數據,具體如下:
WebRTC模塊SrsUdpMuxListener::cycle()協程接收到dtls報文,并通過SrsRtcConnection::on_dtls()函數將報文轉給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); // 將從網絡接收的DTLS數據通過BIO寫入SSL do_handshake(); // 處理DTLS握手 // do_handshake()函數用于完成握手,下面的處理大概是讀取BIO中生成的應答數據,再通過網絡發(fā)送給對端 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; // 設置DTLS握手成功標志 ...... srtp_initialize(); // 接下來完成SRTP初始化 ...... return session_->on_connection_established(); // 通知session對象,WebRTC連接建立}
DTLS協商完成后,會自動生成SRTP發(fā)送和接收報文時需要的密鑰,使用此密鑰完成SRTP初始化:
srs_error_t SrsSecurityTransport::srtp_initialize(){ dtls_->get_srtp_key(recv_key, send_key); // 從DTLS中得到SRTP發(fā)送和接收報文時需要的密鑰 srtp_->initialize(recv_key, send_key); // 將收發(fā)報文所需的密鑰寫入SRTP協議模塊 // 此函數內部通過調用srtp_create創(chuàng)建收發(fā)加密報文的上下文句柄 return err;}
Session對象(SrsRtcConnection)接收到WebRTC連接建立的通知,啟動屬于此連接的推流端接收應答協程SrsRtcPLIWorker::cycle()或拉流端發(fā)送協程SrsRtcPlayStream::cycle()
srs_error_t SrsRtcConnection::on_connection_established(){ { SrsRtcPublishStream* publisher = it->second; publisher->start(); } 或 { SrsRtcPlayStream* player = it->second; player->start(); }}3.3 連接建立,服務端處理RTP報文
DTLS協商成功后,客戶端與服務器之間的WebRTC連接真正建立,服務端接下來開始處理RTP報文: 1)根據接收的RTP報文的IP找到SrsRtcConnection對象 2)根據接收的RTP報文的SSRC找到SrsRtcPublishStream對象 3)根據配置,對RTP報文做相應的特殊處理 4)調用srtp_unprotect()函數對SRTP報文進行解密 5)最終調用SrsRtcPublishStream::do_on_rtp_plaintext()函數處理RTP報文
srs_error_t SrsRtcConnection::on_rtp() { ...... find_publisher(data, nb_data, &publisher); // 根據報文頭部的SSRC找到對應的流對象 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; } // 如果設置了模擬丟包觸發(fā)NACK,則直接返回 if (twcc_id_) { // 如果開啟了TWCC,RTP包會帶擴展信息,增加預處理防止后續(xù)SRTP解密報文時出錯 srs_rtp_fast_parse_twcc(data, nb_data, twcc_id_, twcc_sn); } if (pt_to_drop_) { // 如果某些類型的報文在配置文件中設置為丟棄,這里需要識別并丟棄 uint8_t pt = srs_rtp_fast_parse_pt(data, nb_data); if (pt_to_drop_ == pt) { return err; } } // 最終調用srtp_unprotect()函數對SRTP報文進行解密 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);}
此函數內部一邊將報文放入消費者隊列,一邊根據接收報文的序列號調整NACK隊列
srs_error_t SrsRtcPublishStream::do_on_rtp_plaintext(SrsRtpPacket*& pkt, SrsBuffer* buf) { ...... pkt->decode(buf); // SrsRtpPacket數據包在這里被直接放入SrsRtcConsumer對象的RTP報文緩存隊列 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使能,當判斷到網絡發(fā)生擁塞時,則停止發(fā)送RTP數據包 if (_srs_circuit_breaker->hybrid_critical_water_level()) { return err;} // 如果NACK使能,根據接收報文的序列號調整接收對象SrsRtcRecvTrack內部的NACK隊列 if (nack_enabled_) { audio_track->on_nack(&pkt); video_track->on_nack(&pkt); // 這里兩個函數其實都是SrsRtcRecvTrack::on_nack()函數 }}
根據接收報文的序列號調整NACK隊列,用于后續(xù)指導NACK報文的發(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隊列中刪除已接收到的序列號 return err; } // 將接收到的序列號加入NACK隊列,并重新計算NACK隊列最新的起始序列號和結束序列號 rtp_queue_->update(seq, nack_first, nack_last); // 將最新的起始序列號和結束序列號寫入NACK隊列,并通過check_queue_size()檢測如果隊列滿時清空隊列 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包放入一個環(huán)形緩沖區(qū)}3.4 服務端RTCP報文處理
RTP報文封裝音視頻數據,底層采用UDP協議發(fā)送,本身屬于不可靠傳輸。通過引入RTCP協議,周期性的發(fā)送RTCP報文描述本端的接收和發(fā)送狀態(tài),可以提升音視頻數據的可靠傳輸、流量控制和擁塞控制等服務質量保證。
WebRTC服務模塊對于RTCP報文主要有兩種處理邏輯: 1)根據服務端狀態(tài)主動發(fā)送RTCP報文 2)接收并處理客戶端發(fā)送的RTCP報文
3.4.1 NACK報文收發(fā)原理:1)首先創(chuàng)建一個NACK定時器對象,它的內部工作原理就是以觀察者模式訂閱了一個20毫秒周期的系統(tǒng)定時器
SrsRtcConnectionNackTimer::SrsRtcConnectionNackTimer(SrsRtcConnection* p) : p_(p){ _srs_hybrid->timer20ms()->subscribe(this);}
2)20毫秒定時器超時,周期性調用SrsRtcPublishStream::check_send_nacks()檢測推流接收端是否需要發(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(); // 檢測推流接收端是否需要發(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)構造RTCP類型的NACK報文并發(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)報文收發(fā)原理:
當視頻接收端向發(fā)送端反饋一個PLI報文時,發(fā)送方的編碼器會重新生成關鍵幀并發(fā)送給接收端。
SRS4.0 WebRTC模塊中推流端接收對象SrsRtcPublishStream和拉流端發(fā)送對象SrsRtcPlayStream在建立連接的時候,都會創(chuàng)建各自的SrsRtcPLIWorker::cycle()協程。
1)推流端接收對象SrsRtcPublishStream創(chuàng)建的SrsRtcPLIWorker::cycle()協程,此協程等待條件變量喚醒后,遍歷plis_隊列,發(fā)送PLI請求報文,
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_隊列和條件變量喚醒的操作,都是在SrsRtcSource對象的定時器超時函數中處理的。
SrsRtcSource繼承ISrsFastTimer類,再通過SrsRtcSource::on_publish()函數,以觀察者模式訂閱了一個100毫秒周期的系統(tǒng)定時器
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()超時函數以100毫秒的周期被執(zhí)行,以默認6秒為周期調用一次publish_stream->request_keyframe()函數,插入plis隊列,并觸發(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 接收并處理對端發(fā)送的RTCP報文
srs_error_t SrsRtcConnection::on_rtcp() { SrsSecurityTransport::unprotect_rtp(); // 使用srtp_unprotect_rtcp解密RTCP報文 SrsRtcpCompound rtcp_compound; rtcp_compound.decode(buffer); // 解析得到具體的RTCP報文 // FIR/SR/RR/SDES/BYE/APP/RTPFB/PSFB/XR while(NULL != (rtcp = rtcp_compound.get_next_rtcp())) { dispatch_rtcp(rtcp); // 此函數為各類RTCP報文提供處理路由 }}srs_error_t SrsRtcConnection::dispatch_rtcp(SrsRtcpCommon* rtcp){ // 分類處理各類RTCP報文}4、總結:
本章以WebRTC推流端連接建立的過程為線索,分析了過程中各種協議報文的基本處理流程:
1、Lite-ICE機制、STUN Binding Request和STUN Binding Response報文格式
2、DTLS工作原理、為SRTP獲取密鑰并完成初始化
3、音視頻RTP報文接收并放入對應的拉流端消費者隊列,以及更新NACK隊列
4、定時發(fā)送RTCP報文與接收處理對端發(fā)送的RTCP報文。
原文鏈接:10、SRS4.0源代碼分析之WebRTC推流端處理_黑板報的博客-CSDN博客
以上就是關于pos機收銀系統(tǒng)源碼,SRS4.0源代碼分析之WebRTC推流端處理的知識,后面我們會繼續(xù)為大家整理關于pos機收銀系統(tǒng)源碼的知識,希望能夠幫助到大家!