Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
ZLMediaKit
概览
Overview
Details
Activity
Cycle Analytics
版本库
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
问题
0
Issues
0
列表
Board
标记
里程碑
合并请求
0
Merge Requests
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
Snippets
成员
Collapse sidebar
Close sidebar
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
张翔宇
ZLMediaKit
Commits
0b355759
Commit
0b355759
authored
Sep 18, 2022
by
ziyue
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
整理webrtc相关代码命名空间
parent
15affeff
隐藏空白字符变更
内嵌
并排
正在显示
22 个修改的文件
包含
170 行增加
和
112 行删除
+170
-112
api/source/mk_webrtc_private.h
+2
-0
server/main.cpp
+1
-1
tests/test_server.cpp
+1
-1
webrtc/Nack.cpp
+11
-9
webrtc/Nack.h
+15
-12
webrtc/RtpExt.cpp
+4
-1
webrtc/RtpExt.h
+4
-2
webrtc/Sdp.cpp
+8
-4
webrtc/Sdp.h
+16
-13
webrtc/TwccContext.cpp
+4
-1
webrtc/TwccContext.h
+3
-1
webrtc/WebRtcEchoTest.cpp
+6
-2
webrtc/WebRtcEchoTest.h
+4
-1
webrtc/WebRtcPlayer.cpp
+5
-1
webrtc/WebRtcPlayer.h
+10
-8
webrtc/WebRtcPusher.cpp
+9
-6
webrtc/WebRtcPusher.h
+18
-15
webrtc/WebRtcSession.cpp
+4
-2
webrtc/WebRtcSession.h
+3
-0
webrtc/WebRtcTransport.cpp
+22
-19
webrtc/WebRtcTransport.h
+16
-12
webrtc/readme.md
+4
-1
没有找到文件。
api/source/mk_webrtc_private.h
查看文件 @
0b355759
...
...
@@ -24,6 +24,8 @@
#include "../webrtc/WebRtcPusher.h"
#include "../webrtc/WebRtcTransport.h"
using
namespace
mediakit
;
namespace
API
{
typedef
enum
{
NotFound
=
-
500
,
//未找到
...
...
server/main.cpp
查看文件 @
0b355759
...
...
@@ -286,7 +286,7 @@ int start_main(int argc,char *argv[]) {
}
return
Socket
::
createSocket
(
new_poller
,
false
);
});
uint16_t
rtcPort
=
mINI
::
Instance
()[
R
TC
::
kPort
];
uint16_t
rtcPort
=
mINI
::
Instance
()[
R
tc
::
kPort
];
#endif//defined(ENABLE_WEBRTC)
...
...
tests/test_server.cpp
查看文件 @
0b355759
...
...
@@ -83,7 +83,7 @@ onceToken token1([](){
}
// namespace mediakit
#define REALM "realm_zlmed
ai
kit"
#define REALM "realm_zlmed
ia
kit"
static
map
<
string
,
FlvRecorder
::
Ptr
>
s_mapFlvRecorder
;
static
mutex
s_mtxFlvRecorder
;
...
...
webrtc/Nack.cpp
查看文件 @
0b355759
...
...
@@ -12,7 +12,8 @@
using
namespace
std
;
using
namespace
toolkit
;
using
namespace
mediakit
;
namespace
mediakit
{
static
constexpr
uint32_t
kMaxNackMS
=
5
*
1000
;
static
constexpr
uint32_t
kRtpCacheCheckInterval
=
100
;
...
...
@@ -92,7 +93,6 @@ int64_t NackList::getRtpStamp(uint16_t seq) {
return
it
->
second
->
getStampMS
(
false
);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void
NackContext
::
received
(
uint16_t
seq
,
bool
is_rtx
)
{
...
...
@@ -101,7 +101,7 @@ void NackContext::received(uint16_t seq, bool is_rtx) {
}
if
(
is_rtx
||
(
seq
<
_last_max_seq
&&
!
(
seq
<
1024
&&
_last_max_seq
>
UINT16_MAX
-
1024
)))
{
//重传包或
//seq回退,且非回环,那么这个应该是重传包
//
seq回退,且非回环,那么这个应该是重传包
onRtx
(
seq
);
return
;
}
...
...
@@ -127,7 +127,7 @@ void NackContext::received(uint16_t seq, bool is_rtx) {
_seq
.
clear
();
_last_max_seq
=
max_seq
;
}
else
{
//seq不连续,有丢包
//
seq不连续,有丢包
if
(
min_seq
==
_last_max_seq
+
1
)
{
//前面部分seq是连续的,未丢包,移除之
eraseFrontSeq
();
...
...
@@ -135,7 +135,7 @@ void NackContext::received(uint16_t seq, bool is_rtx) {
//有丢包,丢包从_last_max_seq开始
auto
nack_rtp_count
=
FCI_NACK
::
kBitSize
;
if
(
max_seq
>
nack_rtp_count
+
_last_max_seq
)
{
if
(
max_seq
>
nack_rtp_count
+
_last_max_seq
)
{
vector
<
bool
>
vec
;
vec
.
resize
(
FCI_NACK
::
kBitSize
,
false
);
for
(
size_t
i
=
0
;
i
<
nack_rtp_count
;
++
i
)
{
...
...
@@ -170,7 +170,7 @@ void NackContext::eraseFrontSeq() {
//前面部分seq是连续的,未丢包,移除之
for
(
auto
it
=
_seq
.
begin
();
it
!=
_seq
.
end
();)
{
if
(
*
it
!=
_last_max_seq
+
1
)
{
//seq不连续,丢包了
//
seq不连续,丢包了
break
;
}
_last_max_seq
=
*
it
;
...
...
@@ -187,9 +187,9 @@ void NackContext::onRtx(uint16_t seq) {
_nack_send_status
.
erase
(
it
);
if
(
rtt
>=
0
)
{
//rtt不肯小于0
//
rtt不肯小于0
_rtt
=
rtt
;
//InfoL << "rtt:" << rtt;
//
InfoL << "rtt:" << rtt;
}
}
...
...
@@ -230,7 +230,7 @@ uint64_t NackContext::reSendNack() {
//更新nack发送时间戳
it
->
second
.
update_stamp
=
now
;
if
(
++
(
it
->
second
.
nack_count
)
==
kNackMaxCount
)
{
//nack次数太多,移除之
//
nack次数太多,移除之
it
=
_nack_send_status
.
erase
(
it
);
continue
;
}
...
...
@@ -269,3 +269,5 @@ uint64_t NackContext::reSendNack() {
//重传间隔不得低于5ms
return
max
(
_rtt
,
5
);
}
}
// namespace mediakit
webrtc/Nack.h
查看文件 @
0b355759
...
...
@@ -14,38 +14,39 @@
#include "Rtsp/Rtsp.h"
#include "Rtcp/RtcpFCI.h"
namespace
mediakit
{
class
NackList
{
public
:
NackList
()
=
default
;
~
NackList
()
=
default
;
void
pushBack
(
mediakit
::
RtpPacket
::
Ptr
rtp
);
void
forEach
(
const
mediakit
::
FCI_NACK
&
nack
,
const
std
::
function
<
void
(
const
mediakit
::
RtpPacket
::
Ptr
&
rtp
)
>
&
cb
);
void
pushBack
(
RtpPacket
::
Ptr
rtp
);
void
forEach
(
const
FCI_NACK
&
nack
,
const
std
::
function
<
void
(
const
RtpPacket
::
Ptr
&
rtp
)
>
&
cb
);
private
:
void
popFront
();
uint32_t
getCacheMS
();
int64_t
getRtpStamp
(
uint16_t
seq
);
mediakit
::
RtpPacket
::
Ptr
*
getRtp
(
uint16_t
seq
);
RtpPacket
::
Ptr
*
getRtp
(
uint16_t
seq
);
private
:
uint32_t
_cache_ms_check
=
0
;
std
::
deque
<
uint16_t
>
_nack_cache_seq
;
std
::
unordered_map
<
uint16_t
,
mediakit
::
RtpPacket
::
Ptr
>
_nack_cache_pkt
;
std
::
unordered_map
<
uint16_t
,
RtpPacket
::
Ptr
>
_nack_cache_pkt
;
};
class
NackContext
{
public
:
using
Ptr
=
std
::
shared_ptr
<
NackContext
>
;
using
onNack
=
std
::
function
<
void
(
const
mediakit
::
FCI_NACK
&
nack
)
>
;
using
onNack
=
std
::
function
<
void
(
const
FCI_NACK
&
nack
)
>
;
//最大保留的rtp丢包状态个数
static
constexpr
auto
kNackMaxSize
=
2048
;
//rtp丢包状态最长保留时间
//
rtp丢包状态最长保留时间
static
constexpr
auto
kNackMaxMS
=
3
*
1000
;
//nack最多请求重传10次
//
nack最多请求重传10次
static
constexpr
auto
kNackMaxCount
=
10
;
//nack重传频率,rtt的倍数
//
nack重传频率,rtt的倍数
static
constexpr
auto
kNackIntervalRatio
=
1
.
0
f
;
NackContext
()
=
default
;
...
...
@@ -57,8 +58,8 @@ public:
private
:
void
eraseFrontSeq
();
void
doNack
(
const
mediakit
::
FCI_NACK
&
nack
,
bool
record_nack
);
void
recordNack
(
const
mediakit
::
FCI_NACK
&
nack
);
void
doNack
(
const
FCI_NACK
&
nack
,
bool
record_nack
);
void
recordNack
(
const
FCI_NACK
&
nack
);
void
onRtx
(
uint16_t
seq
);
private
:
...
...
@@ -67,12 +68,14 @@ private:
std
::
set
<
uint16_t
>
_seq
;
uint16_t
_last_max_seq
=
0
;
struct
NackStatus
{
struct
NackStatus
{
uint64_t
first_stamp
;
uint64_t
update_stamp
;
int
nack_count
=
0
;
};
std
::
map
<
uint16_t
/*seq*/
,
NackStatus
>
_nack_send_status
;
std
::
map
<
uint16_t
/*seq*/
,
NackStatus
>
_nack_send_status
;
};
}
// namespace mediakit
#endif //ZLMEDIAKIT_NACK_H
webrtc/RtpExt.cpp
查看文件 @
0b355759
...
...
@@ -17,7 +17,8 @@
using
namespace
std
;
using
namespace
toolkit
;
using
namespace
mediakit
;
namespace
mediakit
{
//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
//https://tools.ietf.org/html/rfc5285
...
...
@@ -644,3 +645,4 @@ void RtpExtContext::onGetRtp(uint8_t pt, uint32_t ssrc, const string &rid){
}
}
}
//
namespace
mediakit
\ No newline at end of file
webrtc/RtpExt.h
查看文件 @
0b355759
...
...
@@ -17,6 +17,7 @@
#include "Common/macros.h"
#include "Rtsp/Rtsp.h"
namespace
mediakit
{
#define RTP_EXT_MAP(XX) \
XX(ssrc_audio_level, "urn:ietf:params:rtp-hdrext:ssrc-audio-level") \
...
...
@@ -53,7 +54,7 @@ public:
friend
class
RtpExtContext
;
~
RtpExt
()
=
default
;
static
std
::
map
<
uint8_t
/*id*/
,
RtpExt
/*data*/
>
getExtValue
(
const
mediakit
::
RtpHeader
*
header
);
static
std
::
map
<
uint8_t
/*id*/
,
RtpExt
/*data*/
>
getExtValue
(
const
RtpHeader
*
header
);
static
RtpExtType
getExtType
(
const
std
::
string
&
url
);
static
const
std
::
string
&
getExtUrl
(
RtpExtType
type
);
static
const
char
*
getExtName
(
RtpExtType
type
);
...
...
@@ -122,7 +123,7 @@ public:
void
setOnGetRtp
(
OnGetRtp
cb
);
std
::
string
getRid
(
uint32_t
ssrc
)
const
;
void
setRid
(
uint32_t
ssrc
,
const
std
::
string
&
rid
);
RtpExt
changeRtpExtId
(
const
mediakit
::
RtpHeader
*
header
,
bool
is_recv
,
std
::
string
*
rid_ptr
=
nullptr
,
RtpExtType
type
=
RtpExtType
::
padding
);
RtpExt
changeRtpExtId
(
const
RtpHeader
*
header
,
bool
is_recv
,
std
::
string
*
rid_ptr
=
nullptr
,
RtpExtType
type
=
RtpExtType
::
padding
);
private
:
void
onGetRtp
(
uint8_t
pt
,
uint32_t
ssrc
,
const
std
::
string
&
rid
);
...
...
@@ -137,4 +138,5 @@ private:
std
::
unordered_map
<
uint32_t
/*simulcast ssrc*/
,
std
::
string
/*rid*/
>
_ssrc_to_rid
;
};
}
//namespace mediakit
#endif //ZLMEDIAKIT_RTPEXT_H
webrtc/Sdp.cpp
查看文件 @
0b355759
...
...
@@ -14,9 +14,10 @@
using
namespace
std
;
using
namespace
toolkit
;
using
namespace
mediakit
;
namespace
RTC
{
namespace
mediakit
{
namespace
Rtc
{
#define RTC_FIELD "rtc."
const
string
kPreferredCodecA
=
RTC_FIELD
"preferredCodecA"
;
const
string
kPreferredCodecV
=
RTC_FIELD
"preferredCodecV"
;
...
...
@@ -1404,7 +1405,7 @@ void RtcConfigure::RtcTrackConfigure::setDefaultSetting(TrackType type){
switch
(
type
)
{
case
TrackAudio
:
{
//此处调整偏好的编码格式优先级
GET_CONFIG_FUNC
(
vector
<
CodecId
>
,
s_preferred_codec
,
R
TC
::
kPreferredCodecA
,
toCodecArray
);
GET_CONFIG_FUNC
(
vector
<
CodecId
>
,
s_preferred_codec
,
R
tc
::
kPreferredCodecA
,
toCodecArray
);
CHECK
(
!
s_preferred_codec
.
empty
(),
"rtc音频偏好codec不能为空"
);
preferred_codec
=
s_preferred_codec
;
...
...
@@ -1423,7 +1424,7 @@ void RtcConfigure::RtcTrackConfigure::setDefaultSetting(TrackType type){
}
case
TrackVideo
:
{
//此处调整偏好的编码格式优先级
GET_CONFIG_FUNC
(
vector
<
CodecId
>
,
s_preferred_codec
,
R
TC
::
kPreferredCodecV
,
toCodecArray
);
GET_CONFIG_FUNC
(
vector
<
CodecId
>
,
s_preferred_codec
,
R
tc
::
kPreferredCodecV
,
toCodecArray
);
CHECK
(
!
s_preferred_codec
.
empty
(),
"rtc视频偏好codec不能为空"
);
preferred_codec
=
s_preferred_codec
;
...
...
@@ -1811,3 +1812,5 @@ void RtcConfigure::onSelectPlan(RtcCodecPlan &plan, CodecId codec) const {
plan
.
fmtp
[
kMode
]
=
mode
.
empty
()
?
"0"
:
mode
;
}
}
}
//
namespace
mediakit
\ No newline at end of file
webrtc/Sdp.h
查看文件 @
0b355759
...
...
@@ -18,6 +18,8 @@
#include "Extension/Frame.h"
#include "Common/Parser.h"
namespace
mediakit
{
//https://datatracker.ietf.org/doc/rfc4566/?include_text=1
//https://blog.csdn.net/aggresss/article/details/109850434
//https://aggresss.blog.csdn.net/article/details/106436703
...
...
@@ -189,7 +191,7 @@ class SdpMedia : public SdpItem {
public
:
// 5.14. Media Descriptions ("m=")
// m=<media> <port> <proto> <fmt> ...
mediakit
::
TrackType
type
;
TrackType
type
;
uint16_t
port
;
//RTP/AVP:应用场景为视频/音频的 RTP 协议。参考 RFC 3551
//RTP/SAVP:应用场景为视频/音频的 SRTP 协议。参考 RFC 3711
...
...
@@ -374,7 +376,7 @@ class SdpAttrFmtp : public SdpItem {
public
:
//fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
uint8_t
pt
;
std
::
map
<
std
::
string
/*key*/
,
std
::
string
/*value*/
,
mediakit
::
StrCaseCompare
>
fmtp
;
std
::
map
<
std
::
string
/*key*/
,
std
::
string
/*value*/
,
StrCaseCompare
>
fmtp
;
void
parse
(
const
std
::
string
&
str
)
override
;
std
::
string
toString
()
const
override
;
const
char
*
getKey
()
const
override
{
return
"fmtp"
;}
...
...
@@ -600,7 +602,7 @@ public:
uint32_t
channel
=
0
;
//rtcp反馈
std
::
set
<
std
::
string
>
rtcp_fb
;
std
::
map
<
std
::
string
/*key*/
,
std
::
string
/*value*/
,
mediakit
::
StrCaseCompare
>
fmtp
;
std
::
map
<
std
::
string
/*key*/
,
std
::
string
/*value*/
,
StrCaseCompare
>
fmtp
;
std
::
string
getFmtp
(
const
char
*
key
)
const
;
};
...
...
@@ -608,7 +610,7 @@ public:
//rtc 媒体描述
class
RtcMedia
{
public
:
mediakit
::
TrackType
type
{
mediakit
::
TrackType
::
TrackInvalid
};
TrackType
type
{
TrackType
::
TrackInvalid
};
std
::
string
mid
;
uint16_t
port
{
0
};
SdpConnection
addr
;
...
...
@@ -675,8 +677,8 @@ public:
void
checkValid
()
const
;
std
::
string
toString
()
const
;
std
::
string
toRtspSdp
()
const
;
const
RtcMedia
*
getMedia
(
mediakit
::
TrackType
type
)
const
;
bool
supportRtcpFb
(
const
std
::
string
&
name
,
mediakit
::
TrackType
type
=
mediakit
::
TrackType
::
TrackVideo
)
const
;
const
RtcMedia
*
getMedia
(
TrackType
type
)
const
;
bool
supportRtcpFb
(
const
std
::
string
&
name
,
TrackType
type
=
TrackType
::
TrackVideo
)
const
;
bool
supportSimulcast
()
const
;
bool
isOnlyDatachannel
()
const
;
...
...
@@ -706,10 +708,10 @@ public:
std
::
set
<
std
::
string
>
rtcp_fb
;
std
::
map
<
RtpExtType
,
RtpDirection
>
extmap
;
std
::
vector
<
mediakit
::
CodecId
>
preferred_codec
;
std
::
vector
<
CodecId
>
preferred_codec
;
std
::
vector
<
SdpAttrCandidate
>
candidate
;
void
setDefaultSetting
(
mediakit
::
TrackType
type
);
void
setDefaultSetting
(
TrackType
type
);
void
enableTWCC
(
bool
enable
=
true
);
void
enableREMB
(
bool
enable
=
true
);
};
...
...
@@ -719,19 +721,19 @@ public:
RtcTrackConfigure
application
;
void
setDefaultSetting
(
std
::
string
ice_ufrag
,
std
::
string
ice_pwd
,
RtpDirection
direction
,
const
SdpAttrFingerprint
&
fingerprint
);
void
addCandidate
(
const
SdpAttrCandidate
&
candidate
,
mediakit
::
TrackType
type
=
mediakit
::
TrackInvalid
);
void
addCandidate
(
const
SdpAttrCandidate
&
candidate
,
TrackType
type
=
TrackInvalid
);
std
::
shared_ptr
<
RtcSession
>
createAnswer
(
const
RtcSession
&
offer
)
const
;
void
setPlayRtspInfo
(
const
std
::
string
&
sdp
);
void
enableTWCC
(
bool
enable
=
true
,
mediakit
::
TrackType
type
=
mediakit
::
TrackInvalid
);
void
enableREMB
(
bool
enable
=
true
,
mediakit
::
TrackType
type
=
mediakit
::
TrackInvalid
);
void
enableTWCC
(
bool
enable
=
true
,
TrackType
type
=
TrackInvalid
);
void
enableREMB
(
bool
enable
=
true
,
TrackType
type
=
TrackInvalid
);
private
:
void
matchMedia
(
const
std
::
shared_ptr
<
RtcSession
>
&
ret
,
const
RtcMedia
&
media
)
const
;
bool
onCheckCodecProfile
(
const
RtcCodecPlan
&
plan
,
mediakit
::
CodecId
codec
)
const
;
void
onSelectPlan
(
RtcCodecPlan
&
plan
,
mediakit
::
CodecId
codec
)
const
;
bool
onCheckCodecProfile
(
const
RtcCodecPlan
&
plan
,
CodecId
codec
)
const
;
void
onSelectPlan
(
RtcCodecPlan
&
plan
,
CodecId
codec
)
const
;
private
:
RtcCodecPlan
::
Ptr
_rtsp_video_plan
;
...
...
@@ -748,5 +750,6 @@ private:
~
SdpConst
()
=
delete
;
};
}
// namespace mediakit
#endif //ZLMEDIAKIT_SDP_H
webrtc/TwccContext.cpp
查看文件 @
0b355759
...
...
@@ -11,7 +11,7 @@
#include "TwccContext.h"
#include "Rtcp/RtcpFCI.h"
using
namespace
mediakit
;
namespace
mediakit
{
enum
class
ExtSeqStatus
:
int
{
normal
=
0
,
...
...
@@ -121,3 +121,5 @@ void TwccContext::clearStatus() {
void
TwccContext
::
setOnSendTwccCB
(
TwccContext
::
onSendTwccCB
cb
)
{
_cb
=
std
::
move
(
cb
);
}
}
//
namespace
mediakit
\ No newline at end of file
webrtc/TwccContext.h
查看文件 @
0b355759
...
...
@@ -16,6 +16,8 @@
#include <functional>
#include "Util/TimeTicker.h"
namespace
mediakit
{
class
TwccContext
{
public
:
using
onSendTwccCB
=
std
::
function
<
void
(
uint32_t
ssrc
,
std
::
string
fci
)
>
;
...
...
@@ -44,5 +46,5 @@ private:
onSendTwccCB
_cb
;
};
}
// namespace mediakit
#endif //ZLMEDIAKIT_TWCCCONTEXT_H
webrtc/WebRtcEchoTest.cpp
查看文件 @
0b355759
...
...
@@ -10,6 +10,8 @@
#include "WebRtcEchoTest.h"
namespace
mediakit
{
WebRtcEchoTest
::
Ptr
WebRtcEchoTest
::
create
(
const
EventPoller
::
Ptr
&
poller
)
{
WebRtcEchoTest
::
Ptr
ret
(
new
WebRtcEchoTest
(
poller
),
[](
WebRtcEchoTest
*
ptr
)
{
ptr
->
onDestory
();
...
...
@@ -48,4 +50,6 @@ void WebRtcEchoTest::onCheckSdp(SdpType type, RtcSession &sdp) {
}
}
}
}
\ No newline at end of file
}
}
//
namespace
mediakit
\ No newline at end of file
webrtc/WebRtcEchoTest.h
查看文件 @
0b355759
...
...
@@ -13,6 +13,8 @@
#include "WebRtcTransport.h"
namespace
mediakit
{
class
WebRtcEchoTest
:
public
WebRtcTransportImp
{
public
:
using
Ptr
=
std
::
shared_ptr
<
WebRtcEchoTest
>
;
...
...
@@ -26,7 +28,7 @@ protected:
void
onRtp
(
const
char
*
buf
,
size_t
len
,
uint64_t
stamp_ms
)
override
;
void
onRtcp
(
const
char
*
buf
,
size_t
len
)
override
;
void
onRecvRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
mediakit
::
RtpPacket
::
Ptr
rtp
)
override
{};
void
onRecvRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
RtpPacket
::
Ptr
rtp
)
override
{};
void
onBeforeEncryptRtp
(
const
char
*
buf
,
int
&
len
,
void
*
ctx
)
override
{};
void
onBeforeEncryptRtcp
(
const
char
*
buf
,
int
&
len
,
void
*
ctx
)
override
{};
...
...
@@ -34,4 +36,5 @@ private:
WebRtcEchoTest
(
const
EventPoller
::
Ptr
&
poller
);
};
}
// namespace mediakit
#endif //ZLMEDIAKIT_WEBRTCECHOTEST_H
webrtc/WebRtcPlayer.cpp
查看文件 @
0b355759
...
...
@@ -11,7 +11,8 @@
#include "WebRtcPlayer.h"
using
namespace
std
;
using
namespace
mediakit
;
namespace
mediakit
{
WebRtcPlayer
::
Ptr
WebRtcPlayer
::
create
(
const
EventPoller
::
Ptr
&
poller
,
const
RtspMediaSource
::
Ptr
&
src
,
...
...
@@ -88,3 +89,5 @@ void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const {
configure
.
audio
.
direction
=
configure
.
video
.
direction
=
RtpDirection
::
sendonly
;
configure
.
setPlayRtspInfo
(
_play_src
->
getSdp
());
}
}
//
namespace
mediakit
\ No newline at end of file
webrtc/WebRtcPlayer.h
查看文件 @
0b355759
...
...
@@ -13,30 +13,32 @@
#include "WebRtcTransport.h"
namespace
mediakit
{
class
WebRtcPlayer
:
public
WebRtcTransportImp
{
public
:
using
Ptr
=
std
::
shared_ptr
<
WebRtcPlayer
>
;
~
WebRtcPlayer
()
override
=
default
;
static
Ptr
create
(
const
EventPoller
::
Ptr
&
poller
,
const
mediakit
::
RtspMediaSource
::
Ptr
&
src
,
const
mediakit
::
MediaInfo
&
info
);
static
Ptr
create
(
const
EventPoller
::
Ptr
&
poller
,
const
RtspMediaSource
::
Ptr
&
src
,
const
MediaInfo
&
info
);
protected
:
///////WebRtcTransportImp override///////
void
onStartWebRTC
()
override
;
void
onDestory
()
override
;
void
onRtcConfigure
(
RtcConfigure
&
configure
)
const
override
;
void
onRecvRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
mediakit
::
RtpPacket
::
Ptr
rtp
)
override
{};
void
onRecvRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
RtpPacket
::
Ptr
rtp
)
override
{};
private
:
WebRtcPlayer
(
const
EventPoller
::
Ptr
&
poller
,
const
mediakit
::
RtspMediaSource
::
Ptr
&
src
,
const
mediakit
::
MediaInfo
&
info
);
WebRtcPlayer
(
const
EventPoller
::
Ptr
&
poller
,
const
RtspMediaSource
::
Ptr
&
src
,
const
MediaInfo
&
info
);
private
:
//媒体相关元数据
mediakit
::
MediaInfo
_media_info
;
MediaInfo
_media_info
;
//播放的rtsp源
mediakit
::
RtspMediaSource
::
Ptr
_play_src
;
RtspMediaSource
::
Ptr
_play_src
;
//播放rtsp源的reader对象
mediakit
::
RtspMediaSource
::
RingType
::
RingReader
::
Ptr
_reader
;
RtspMediaSource
::
RingType
::
RingReader
::
Ptr
_reader
;
};
#endif //ZLMEDIAKIT_WEBRTCPLAYER_H
}
// namespace mediakit
#endif //
ZLMEDIAKIT_WEBRTCPLAYER_H
webrtc/WebRtcPusher.cpp
查看文件 @
0b355759
...
...
@@ -11,13 +11,14 @@
#include "WebRtcPusher.h"
using
namespace
std
;
using
namespace
mediakit
;
namespace
mediakit
{
WebRtcPusher
::
Ptr
WebRtcPusher
::
create
(
const
EventPoller
::
Ptr
&
poller
,
const
RtspMediaSourceImp
::
Ptr
&
src
,
const
std
::
shared_ptr
<
void
>
&
ownership
,
const
MediaInfo
&
info
,
const
mediakit
::
ProtocolOption
&
option
)
{
const
ProtocolOption
&
option
)
{
WebRtcPusher
::
Ptr
ret
(
new
WebRtcPusher
(
poller
,
src
,
ownership
,
info
,
option
),
[](
WebRtcPusher
*
ptr
)
{
ptr
->
onDestory
();
delete
ptr
;
...
...
@@ -30,7 +31,7 @@ WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller,
const
RtspMediaSourceImp
::
Ptr
&
src
,
const
std
::
shared_ptr
<
void
>
&
ownership
,
const
MediaInfo
&
info
,
const
mediakit
::
ProtocolOption
&
option
)
:
WebRtcTransportImp
(
poller
)
{
const
ProtocolOption
&
option
)
:
WebRtcTransportImp
(
poller
)
{
_media_info
=
info
;
_push_src
=
src
;
_push_src_ownership
=
ownership
;
...
...
@@ -142,7 +143,7 @@ void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const {
configure
.
audio
.
direction
=
configure
.
video
.
direction
=
RtpDirection
::
recvonly
;
}
float
WebRtcPusher
::
getLossRate
(
MediaSource
&
sender
,
mediakit
::
TrackType
type
){
float
WebRtcPusher
::
getLossRate
(
MediaSource
&
sender
,
TrackType
type
){
return
WebRtcTransportImp
::
getLossRate
(
type
);
}
...
...
@@ -155,4 +156,6 @@ void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport
void
WebRtcPusher
::
onRtcpBye
(){
_push_src
=
nullptr
;
WebRtcTransportImp
::
onRtcpBye
();
}
\ No newline at end of file
}
}
//
namespace
mediakit
\ No newline at end of file
webrtc/WebRtcPusher.h
查看文件 @
0b355759
...
...
@@ -13,19 +13,21 @@
#include "WebRtcTransport.h"
class
WebRtcPusher
:
public
WebRtcTransportImp
,
public
mediakit
::
MediaSourceEvent
{
namespace
mediakit
{
class
WebRtcPusher
:
public
WebRtcTransportImp
,
public
MediaSourceEvent
{
public
:
using
Ptr
=
std
::
shared_ptr
<
WebRtcPusher
>
;
~
WebRtcPusher
()
override
=
default
;
static
Ptr
create
(
const
EventPoller
::
Ptr
&
poller
,
const
mediakit
::
RtspMediaSourceImp
::
Ptr
&
src
,
const
std
::
shared_ptr
<
void
>
&
ownership
,
const
mediakit
::
MediaInfo
&
info
,
const
mediakit
::
ProtocolOption
&
option
);
static
Ptr
create
(
const
EventPoller
::
Ptr
&
poller
,
const
RtspMediaSourceImp
::
Ptr
&
src
,
const
std
::
shared_ptr
<
void
>
&
ownership
,
const
MediaInfo
&
info
,
const
ProtocolOption
&
option
);
protected
:
///////WebRtcTransportImp override///////
void
onStartWebRTC
()
override
;
void
onDestory
()
override
;
void
onRtcConfigure
(
RtcConfigure
&
configure
)
const
override
;
void
onRecvRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
mediakit
::
RtpPacket
::
Ptr
rtp
)
override
;
void
onRecvRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
RtpPacket
::
Ptr
rtp
)
override
;
void
onRtcpBye
()
override
;
//// dtls相关的回调 ////
void
OnDtlsTransportClosed
(
const
RTC
::
DtlsTransport
*
dtlsTransport
)
override
;
...
...
@@ -33,35 +35,36 @@ protected:
protected
:
///////MediaSourceEvent override///////
// 关闭
bool
close
(
mediakit
::
MediaSource
&
sender
)
override
;
bool
close
(
MediaSource
&
sender
)
override
;
// 播放总人数
int
totalReaderCount
(
mediakit
::
MediaSource
&
sender
)
override
;
int
totalReaderCount
(
MediaSource
&
sender
)
override
;
// 获取媒体源类型
mediakit
::
MediaOriginType
getOriginType
(
mediakit
::
MediaSource
&
sender
)
const
override
;
MediaOriginType
getOriginType
(
MediaSource
&
sender
)
const
override
;
// 获取媒体源url或者文件路径
std
::
string
getOriginUrl
(
mediakit
::
MediaSource
&
sender
)
const
override
;
std
::
string
getOriginUrl
(
MediaSource
&
sender
)
const
override
;
// 获取媒体源客户端相关信息
std
::
shared_ptr
<
SockInfo
>
getOriginSock
(
mediakit
::
MediaSource
&
sender
)
const
override
;
std
::
shared_ptr
<
SockInfo
>
getOriginSock
(
MediaSource
&
sender
)
const
override
;
// 获取丢包率
float
getLossRate
(
mediakit
::
MediaSource
&
sender
,
mediakit
::
TrackType
type
)
override
;
float
getLossRate
(
MediaSource
&
sender
,
TrackType
type
)
override
;
private
:
WebRtcPusher
(
const
EventPoller
::
Ptr
&
poller
,
const
mediakit
::
RtspMediaSourceImp
::
Ptr
&
src
,
const
std
::
shared_ptr
<
void
>
&
ownership
,
const
mediakit
::
MediaInfo
&
info
,
const
mediakit
::
ProtocolOption
&
option
);
WebRtcPusher
(
const
EventPoller
::
Ptr
&
poller
,
const
RtspMediaSourceImp
::
Ptr
&
src
,
const
std
::
shared_ptr
<
void
>
&
ownership
,
const
MediaInfo
&
info
,
const
ProtocolOption
&
option
);
private
:
bool
_simulcast
=
false
;
//断连续推延时
uint32_t
_continue_push_ms
=
0
;
//媒体相关元数据
mediakit
::
MediaInfo
_media_info
;
MediaInfo
_media_info
;
//推流的rtsp源
mediakit
::
RtspMediaSourceImp
::
Ptr
_push_src
;
RtspMediaSourceImp
::
Ptr
_push_src
;
//推流所有权
std
::
shared_ptr
<
void
>
_push_src_ownership
;
//推流的rtsp源,支持simulcast
std
::
unordered_map
<
std
::
string
/*rid*/
,
mediakit
::
RtspMediaSource
::
Ptr
>
_push_src_sim
;
std
::
unordered_map
<
std
::
string
/*rid*/
,
RtspMediaSource
::
Ptr
>
_push_src_sim
;
std
::
unordered_map
<
std
::
string
/*rid*/
,
std
::
shared_ptr
<
void
>
>
_push_src_sim_ownership
;
};
}
// namespace mediakit
#endif //ZLMEDIAKIT_WEBRTCPUSHER_H
webrtc/WebRtcSession.cpp
查看文件 @
0b355759
...
...
@@ -12,7 +12,8 @@
#include "Util/util.h"
using
namespace
std
;
using
namespace
mediakit
;
namespace
mediakit
{
static
string
getUserName
(
const
Buffer
::
Ptr
&
buffer
)
{
auto
buf
=
buffer
->
data
();
...
...
@@ -85,7 +86,7 @@ void WebRtcSession::onError(const SockException &err) {
}
void
WebRtcSession
::
onManager
()
{
GET_CONFIG
(
float
,
timeoutSec
,
R
TC
::
kTimeOutSec
);
GET_CONFIG
(
float
,
timeoutSec
,
R
tc
::
kTimeOutSec
);
if
(
!
_transport
&&
_ticker
.
createdTime
()
>
timeoutSec
*
1000
)
{
shutdown
(
SockException
(
Err_timeout
,
"illegal webrtc connection"
));
return
;
...
...
@@ -96,5 +97,6 @@ void WebRtcSession::onManager() {
}
}
}
// namespace mediakit
webrtc/WebRtcSession.h
查看文件 @
0b355759
...
...
@@ -16,6 +16,8 @@
#include "IceServer.hpp"
#include "WebRtcTransport.h"
namespace
mediakit
{
class
WebRtcSession
:
public
UdpSession
{
public
:
WebRtcSession
(
const
Socket
::
Ptr
&
sock
);
...
...
@@ -35,5 +37,6 @@ private:
std
::
shared_ptr
<
WebRtcTransportImp
>
_transport
;
};
}
// namespace mediakit
#endif //ZLMEDIAKIT_WEBRTCSESSION_H
webrtc/WebRtcTransport.cpp
查看文件 @
0b355759
...
...
@@ -8,15 +8,18 @@
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "WebRtcTransport.h"
#include <iostream>
#include <srtp2/srtp.h>
#include "RtpExt.h"
#include "Rtcp/Rtcp.h"
#include "Rtcp/RtcpFCI.h"
#include "RtpExt.h"
#include "Rtsp/RtpReceiver.h"
#include "WebRtcTransport.h"
#include
<srtp2/srtp.h>
#include
<iostream>
#include
"WebRtcEchoTest.h"
#include "WebRtcPlayer.h"
#include
"WebRtcPusher.h"
#define RTP_SSRC_OFFSET 1
#define RTX_SSRC_OFFSET 2
...
...
@@ -26,10 +29,11 @@
#define RTP_MSID RTP_MSLABEL " " RTP_LABEL
using
namespace
std
;
using
namespace
mediakit
;
namespace
mediakit
{
// RTC配置项目
namespace
R
TC
{
namespace
R
tc
{
#define RTC_FIELD "rtc."
// rtp和rtcp接受超时时间
const
string
kTimeOutSec
=
RTC_FIELD
"timeoutSec"
;
...
...
@@ -246,7 +250,7 @@ void WebRtcTransport::setRemoteDtlsFingerprint(const RtcSession &remote) {
void
WebRtcTransport
::
onRtcConfigure
(
RtcConfigure
&
configure
)
const
{
// 开启remb后关闭twcc,因为开启twcc后remb无效
GET_CONFIG
(
size_t
,
remb_bit_rate
,
R
TC
::
kRembBitRate
);
GET_CONFIG
(
size_t
,
remb_bit_rate
,
R
tc
::
kRembBitRate
);
configure
.
enableTWCC
(
!
remb_bit_rate
);
}
...
...
@@ -368,7 +372,7 @@ void WebRtcTransportImp::onCreate() {
registerSelf
();
weak_ptr
<
WebRtcTransportImp
>
weak_self
=
static_pointer_cast
<
WebRtcTransportImp
>
(
shared_from_this
());
GET_CONFIG
(
float
,
timeoutSec
,
R
TC
::
kTimeOutSec
);
GET_CONFIG
(
float
,
timeoutSec
,
R
tc
::
kTimeOutSec
);
_timer
=
std
::
make_shared
<
Timer
>
(
timeoutSec
/
2
,
[
weak_self
]()
{
...
...
@@ -511,7 +515,7 @@ void WebRtcTransportImp::onStartWebRTC() {
void
WebRtcTransportImp
::
onCheckAnswer
(
RtcSession
&
sdp
)
{
// 修改answer sdp的ip、端口信息
GET_CONFIG_FUNC
(
std
::
vector
<
std
::
string
>
,
extern_ips
,
R
TC
::
kExternIP
,
[](
string
str
)
{
GET_CONFIG_FUNC
(
std
::
vector
<
std
::
string
>
,
extern_ips
,
R
tc
::
kExternIP
,
[](
string
str
)
{
std
::
vector
<
std
::
string
>
ret
;
if
(
str
.
length
())
{
ret
=
split
(
str
,
","
);
...
...
@@ -525,7 +529,7 @@ void WebRtcTransportImp::onCheckAnswer(RtcSession &sdp) {
m
.
rtcp_addr
.
reset
();
m
.
rtcp_addr
.
address
=
m
.
addr
.
address
;
GET_CONFIG
(
uint16_t
,
local_port
,
R
TC
::
kPort
);
GET_CONFIG
(
uint16_t
,
local_port
,
R
tc
::
kPort
);
m
.
rtcp_addr
.
port
=
local_port
;
m
.
port
=
m
.
rtcp_addr
.
port
;
sdp
.
origin
.
address
=
m
.
addr
.
address
;
...
...
@@ -592,9 +596,9 @@ makeIceCandidate(std::string ip, uint16_t port, uint32_t priority = 100, std::st
void
WebRtcTransportImp
::
onRtcConfigure
(
RtcConfigure
&
configure
)
const
{
WebRtcTransport
::
onRtcConfigure
(
configure
);
GET_CONFIG
(
uint16_t
,
local_port
,
R
TC
::
kPort
);
GET_CONFIG
(
uint16_t
,
local_port
,
R
tc
::
kPort
);
// 添加接收端口candidate信息
GET_CONFIG_FUNC
(
std
::
vector
<
std
::
string
>
,
extern_ips
,
R
TC
::
kExternIP
,
[](
string
str
)
{
GET_CONFIG_FUNC
(
std
::
vector
<
std
::
string
>
,
extern_ips
,
R
tc
::
kExternIP
,
[](
string
str
)
{
std
::
vector
<
std
::
string
>
ret
;
if
(
str
.
length
())
{
ret
=
split
(
str
,
","
);
...
...
@@ -698,7 +702,7 @@ std::shared_ptr<RtpChannel> MediaTrack::getRtpChannel(uint32_t ssrc) const {
return
it_chn
->
second
;
}
float
WebRtcTransportImp
::
getLossRate
(
mediakit
::
TrackType
type
)
{
float
WebRtcTransportImp
::
getLossRate
(
TrackType
type
)
{
for
(
auto
&
pr
:
_ssrc_to_track
)
{
auto
ssrc
=
pr
.
first
;
auto
&
track
=
pr
.
second
;
...
...
@@ -942,7 +946,7 @@ void WebRtcTransportImp::onSortedRtp(MediaTrack &track, const string &rid, RtpPa
sendRtcpPli
(
rtp
->
getSSRC
());
// 开启remb,则发送remb包调节比特率
GET_CONFIG
(
size_t
,
remb_bit_rate
,
R
TC
::
kRembBitRate
);
GET_CONFIG
(
size_t
,
remb_bit_rate
,
R
tc
::
kRembBitRate
);
if
(
remb_bit_rate
&&
_answer_sdp
->
supportRtcpFb
(
SdpConst
::
kRembRtcpFb
))
{
sendRtcpRemb
(
rtp
->
getSSRC
(),
remb_bit_rate
);
}
...
...
@@ -1121,10 +1125,6 @@ void WebRtcPluginManager::getAnswerSdp(
it
->
second
(
sender
,
offer
,
args
,
cb
);
}
#include "WebRtcEchoTest.h"
#include "WebRtcPlayer.h"
#include "WebRtcPusher.h"
void
echo_plugin
(
Session
&
sender
,
const
string
&
offer
,
const
WebRtcArgs
&
args
,
const
WebRtcPluginManager
::
onCreateRtc
&
cb
)
{
cb
(
*
WebRtcEchoTest
::
create
(
EventPollerPool
::
Instance
().
getPoller
()));
...
...
@@ -1227,3 +1227,5 @@ static onceToken s_rtc_auto_register([]() {
WebRtcPluginManager
::
Instance
().
registerPlugin
(
"push"
,
push_plugin
);
WebRtcPluginManager
::
Instance
().
registerPlugin
(
"play"
,
play_plugin
);
});
}
//
namespace
mediakit
\ No newline at end of file
webrtc/WebRtcTransport.h
查看文件 @
0b355759
...
...
@@ -27,8 +27,10 @@
#include "TwccContext.h"
#include "SctpAssociation.hpp"
namespace
mediakit
{
//RTC配置项目
namespace
R
TC
{
namespace
R
tc
{
extern
const
std
::
string
kPort
;
extern
const
std
::
string
kTimeOutSec
;
}
//namespace RTC
...
...
@@ -200,7 +202,7 @@ public:
//for send rtp
NackList
nack_list
;
mediakit
::
RtcpContext
::
Ptr
rtcp_context_send
;
RtcpContext
::
Ptr
rtcp_context_send
;
//for recv rtp
std
::
unordered_map
<
std
::
string
/*rid*/
,
std
::
shared_ptr
<
RtpChannel
>
>
rtp_channel
;
...
...
@@ -211,13 +213,13 @@ struct WrappedMediaTrack {
MediaTrack
::
Ptr
track
;
explicit
WrappedMediaTrack
(
MediaTrack
::
Ptr
ptr
)
:
track
(
ptr
)
{}
virtual
~
WrappedMediaTrack
()
{}
virtual
void
inputRtp
(
const
char
*
buf
,
size_t
len
,
uint64_t
stamp_ms
,
mediakit
::
RtpHeader
*
rtp
)
=
0
;
virtual
void
inputRtp
(
const
char
*
buf
,
size_t
len
,
uint64_t
stamp_ms
,
RtpHeader
*
rtp
)
=
0
;
};
struct
WrappedRtxTrack
:
public
WrappedMediaTrack
{
explicit
WrappedRtxTrack
(
MediaTrack
::
Ptr
ptr
)
:
WrappedMediaTrack
(
std
::
move
(
ptr
))
{}
void
inputRtp
(
const
char
*
buf
,
size_t
len
,
uint64_t
stamp_ms
,
mediakit
::
RtpHeader
*
rtp
)
override
;
void
inputRtp
(
const
char
*
buf
,
size_t
len
,
uint64_t
stamp_ms
,
RtpHeader
*
rtp
)
override
;
};
class
WebRtcTransportImp
;
...
...
@@ -229,7 +231,7 @@ struct WrappedRtpTrack : public WrappedMediaTrack {
,
_transport
(
t
)
{}
TwccContext
&
_twcc_ctx
;
WebRtcTransportImp
&
_transport
;
void
inputRtp
(
const
char
*
buf
,
size_t
len
,
uint64_t
stamp_ms
,
mediakit
::
RtpHeader
*
rtp
)
override
;
void
inputRtp
(
const
char
*
buf
,
size_t
len
,
uint64_t
stamp_ms
,
RtpHeader
*
rtp
)
override
;
};
class
WebRtcTransportImp
:
public
WebRtcTransport
{
...
...
@@ -243,7 +245,7 @@ public:
uint64_t
getDuration
()
const
;
bool
canSendRtp
()
const
;
bool
canRecvRtp
()
const
;
void
onSendRtp
(
const
mediakit
::
RtpPacket
::
Ptr
&
rtp
,
bool
flush
,
bool
rtx
=
false
);
void
onSendRtp
(
const
RtpPacket
::
Ptr
&
rtp
,
bool
flush
,
bool
rtx
=
false
);
void
createRtpChannel
(
const
std
::
string
&
rid
,
uint32_t
ssrc
,
MediaTrack
&
track
);
...
...
@@ -262,14 +264,14 @@ protected:
void
onCreate
()
override
;
void
onDestory
()
override
;
void
onShutdown
(
const
SockException
&
ex
)
override
;
virtual
void
onRecvRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
mediakit
::
RtpPacket
::
Ptr
rtp
)
=
0
;
virtual
void
onRecvRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
RtpPacket
::
Ptr
rtp
)
=
0
;
void
updateTicker
();
float
getLossRate
(
mediakit
::
TrackType
type
);
float
getLossRate
(
TrackType
type
);
void
onRtcpBye
()
override
;
private
:
void
onSortedRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
mediakit
::
RtpPacket
::
Ptr
rtp
);
void
onSendNack
(
MediaTrack
&
track
,
const
mediakit
::
FCI_NACK
&
nack
,
uint32_t
ssrc
);
void
onSortedRtp
(
MediaTrack
&
track
,
const
std
::
string
&
rid
,
RtpPacket
::
Ptr
rtp
);
void
onSendNack
(
MediaTrack
&
track
,
const
FCI_NACK
&
nack
,
uint32_t
ssrc
);
void
onSendTwcc
(
uint32_t
ssrc
,
const
std
::
string
&
twcc_fci
);
void
registerSelf
();
...
...
@@ -343,4 +345,6 @@ private:
private
:
mutable
std
::
mutex
_mtx_creator
;
std
::
unordered_map
<
std
::
string
,
Plugin
>
_map_creator
;
};
\ No newline at end of file
};
}
//
namespace
mediakit
\ No newline at end of file
webrtc/readme.md
查看文件 @
0b355759
...
...
@@ -15,7 +15,10 @@
-
srtp相关功能:
-
SrtpSession.cpp
-
SrtpSession.hpp
-
datachannel相关功能:
-
SctpAssociation.cpp
-
SctpAssociation.hpp
以上源码有一定的修改和裁剪,感谢MediaSoup开源项目及作者,
用户在使用本项目的同时,应该同时遵循MediaSoup的开源协议。
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论