android vlc 画面不动,vlc播放rtsp over tcp画面突然卡住问题
繼完成rtmp服務器開發后,最近也寫完了rtsp服務器,可以將國標ps流以及其他格式協議碼流轉rtsp協議輸出。中間開發過程用了許多播放器測試,最常用的就是vlc。使用vlc測試過程,遇到了許多問題。今天就記錄一個比較奇怪的問題。
使用rtp over udp模式播放時,沒出現問題,但是使用rtp over tcp模式時,vlc播放幾十秒后畫面突然卡住不動了,看了vlc 的debug message沒發現異常。用ffplay,live555,potplayer測了都沒異常。后面換了不同版本vlc測試,更奇怪了,vlc3.0.0以及之前,3.0.5以及之后版本都正常。應該是vlc對rtp over tcp做了特殊處理。此時抓包分析rtsp交互數據,發現出現問題版本的vlc每隔一定時間除了會發送OPTIONS命令,然后還有以'$'開頭的一串特殊字節,發送完這個播放畫面就卡住了。為什么會卡住不播放了呢?只能看vlc源碼查找問題了。
通過閱讀相關源碼,終于定位到了原因。這個是vlc的keep-alive機制造成的。由于vlc使用了live555做rtsp處理,所以對應處理代碼在modules/access/live555.cpp這個文件里。下面結合代碼說下原因。
C++
static void TimeoutPrevention( void *p_data )
{
demux_t *p_demux = (demux_t *) p_data;
demux_sys_t *p_sys = (demux_sys_t *)p_demux->p_sys;
char *bye = NULL;
if( var_GetBool( p_demux, "rtsp-tcp" ) )
return;
/* Protect Live555 from us calling their functions simultaneously
with Demux() or Control() */
vlc::threads::mutex_locker locker( p_sys->timeout_mutex );
/* If the timer fires while the demuxer owns the lock, and the demuxer
* then torns the session down, the pointers will become NULL. By the time
* this timer callback obtains the callback, either a new session was
* created and the timer is rescheduled, or the pointers are still NULL
* and the timer is descheduled. In the second case, bail out (then wait
* for the timer to be rescheduled or destroyed). In the first case, this
* might send an early refresh - that′s harmless but suboptimal (FIXME). */
if( p_sys->rtsp == NULL || p_sys->ms == NULL )
return;
bool use_get_param = p_sys->b_get_param;
/* Use GET_PARAMETERS if supported. wmserver dialect supports
* it, but does not report this properly. */
if( var_GetBool( p_demux, "rtsp-wmserver" ) )
use_get_param = true;
if( use_get_param )
p_sys->rtsp->sendGetParameterCommand( *p_sys->ms,
default_live555_callback, bye );
else
p_sys->rtsp->sendOptionsCommand( default_live555_callback, NULL );
if( !wait_Live555_response( p_demux ) )
{
msg_Err( p_demux, "keep-alive failed: %s",
p_sys->env->getResultMsg() );
/* Just continue, worst case is we get timed out later */
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
staticvoidTimeoutPrevention(void*p_data)
{
demux_t*p_demux=(demux_t*)p_data;
demux_sys_t*p_sys=(demux_sys_t*)p_demux->p_sys;
char*bye=NULL;
if(var_GetBool(p_demux,"rtsp-tcp"))
return;
/* Protect Live555 from us calling their functions simultaneously
with Demux() or Control() */
vlc::threads::mutex_lockerlocker(p_sys->timeout_mutex);
/* If the timer fires while the demuxer owns the lock, and the demuxer
* then torns the session down, the pointers will become NULL. By the time
* this timer callback obtains the callback, either a new session was
* created and the timer is rescheduled, or the pointers are still NULL
* and the timer is descheduled. In the second case, bail out (then wait
* for the timer to be rescheduled or destroyed). In the first case, this
* might send an early refresh - that′s harmless but suboptimal (FIXME). */
if(p_sys->rtsp==NULL||p_sys->ms==NULL)
return;
booluse_get_param=p_sys->b_get_param;
/* Use GET_PARAMETERS if supported. wmserver dialect supports
* it, but does not report this properly. */
if(var_GetBool(p_demux,"rtsp-wmserver"))
use_get_param=true;
if(use_get_param)
p_sys->rtsp->sendGetParameterCommand(*p_sys->ms,
default_live555_callback,bye);
else
p_sys->rtsp->sendOptionsCommand(default_live555_callback,NULL);
if(!wait_Live555_response(p_demux))
{
msg_Err(p_demux,"keep-alive failed: %s",
p_sys->env->getResultMsg());
/* Just continue, worst case is we get timed out later */
}
}
如上函數是vlc的rtsp超時處理代碼,出現問題的vlc版本沒有
if( var_GetBool( p_demux, "rtsp-tcp" ) )
return;
1
2
if(var_GetBool(p_demux,"rtsp-tcp"))
return;
這兩行代碼,我們先把這兩行代碼注釋,分析下為什么會出現播放畫面突然不動的現象。
1)rtsp交互開始vlc客戶端會發送OPTIONS請求,我們服務器需要回應支持的方法。如果我們服務器回應包括GET_PARAMETER方法(可選),use_get_param就為true,然后keep-alive機制就會定時sendGetParameterCommand,否則sendOptionsCommand,我這邊服務沒去做GET_PARAMETER方法的支持,所以會定時收到vlc發的OPTIONS命令請求。vlc發送完OPTIONS請求命令后,開始wait_Live555_response(p_demux)。看下這個函數:
C++
/* return true if the RTSP command succeeded */
static bool wait_Live555_response( demux_t *p_demux, int i_timeout = 0 /* ms */ )
{
TaskToken task;
demux_sys_t * p_sys = (demux_sys_t *)p_demux->p_sys;
p_sys->event_rtsp = 0;
if( i_timeout > 0 )
{
/* Create a task that will be called if we wait more than timeout ms */
task = p_sys->scheduler->scheduleDelayedTask( i_timeout*1000,
TaskInterruptRTSP,
p_demux );
}
p_sys->event_rtsp = 0;
p_sys->b_error = true;
p_sys->i_live555_ret = 0;
p_sys->scheduler->doEventLoop( &p_sys->event_rtsp );
//here, if b_error is true and i_live555_ret = 0 we didn't receive a response
if( i_timeout > 0 )
{
/* remove the task */
p_sys->scheduler->unscheduleDelayedTask( task );
}
return !p_sys->b_error;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* return true if the RTSP command succeeded */
staticboolwait_Live555_response(demux_t*p_demux,inti_timeout=0/* ms */)
{
TaskTokentask;
demux_sys_t*p_sys=(demux_sys_t*)p_demux->p_sys;
p_sys->event_rtsp=0;
if(i_timeout>0)
{
/* Create a task that will be called if we wait more than timeout ms */
task=p_sys->scheduler->scheduleDelayedTask(i_timeout*1000,
TaskInterruptRTSP,
p_demux);
}
p_sys->event_rtsp=0;
p_sys->b_error=true;
p_sys->i_live555_ret=0;
p_sys->scheduler->doEventLoop(&p_sys->event_rtsp);
//here, if b_error is true and i_live555_ret = 0 we didn't receive a response
if(i_timeout>0)
{
/* remove the task */
p_sys->scheduler->unscheduleDelayedTask(task);
}
return!p_sys->b_error;
}
傳入的參數中i_timeout為默認值0,所以沒有超時時間,會一直等服務器響應請求。
2)我這邊服務器有個命令解析類,只處理標準的命令(OPTIONS,DESCRIBE,PLAY等)。由于vlc會定時發送'$'開頭數據,跟OPTIONS請求數據混在一起送到我的命令解析里,導致我這邊沒能正確解析,所以也沒有回應vlc keep-alive機制的OPTIONS請求。我們再看下TimeoutPrevention函數,該函數進入后會:
C++
vlc::threads::mutex_locker locker( p_sys->timeout_mutex );
1
vlc::threads::mutex_lockerlocker(p_sys->timeout_mutex);
由于我的服務器沒有回應OPTIONS請求,所以這個鎖會一直阻塞,我們看下這個鎖用在哪個地方:
C++
/*****************************************************************************
* Demux:
*****************************************************************************/
static int Demux( demux_t *p_demux )
{
demux_sys_t *p_sys = (demux_sys_t *)p_demux->p_sys;
TaskToken task;
bool b_send_pcr = true;
int i;
/* Protect Live555 from simultaneous calls in TimeoutPrevention()
during pause */
vlc::threads::mutex_locker locker( p_sys->timeout_mutex );
for( i = 0; i < p_sys->i_track; i++ )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*****************************************************************************
* Demux:
*****************************************************************************/
staticintDemux(demux_t*p_demux)
{
demux_sys_t*p_sys=(demux_sys_t*)p_demux->p_sys;
TaskTokentask;
boolb_send_pcr=true;
inti;
/* Protect Live555 from simultaneous calls in TimeoutPrevention()
during pause */
vlc::threads::mutex_lockerlocker(p_sys->timeout_mutex);
for(i=0;ii_track;i++)
可知由于TimeoutPrevention一直阻塞,所以Demux過程不能執行了,所以播放畫面不動了。
新版vlc已經通過
C++
if( var_GetBool( p_demux, "rtsp-tcp" ) )
return;
1
2
if(var_GetBool(p_demux,"rtsp-tcp"))
return;
取消了rtp over tcp的keep-alive機制,所以3.0.5以及之后版本沒有出現問題。我的rtsp服務器后面也針對'$'開頭數據做了處理,測了下,一切都正常了。
'$'開頭數據是做什么的呢?在我服務器發RTCP數據時才用到,沒想到客戶端也有類似機制。在rfc2326中,'$'(0x24)開頭數據叫做:Embedded (Interleaved) Binary Data,稱為嵌入式二進制數據。測試的那么多播放器,只有vlc實現了這個。而且這個Embedded (Interleaved) Binary Data只工作在rtp over tcp下。這個數據有什么作用呢?rfx2326 10.12這么介紹的:
10.12 Embedded (Interleaved) Binary Data
Certain firewall designs and other circumstances may force a server
to interleave RTSP methods and stream data. This interleaving should
generally be avoided unless necessary since it complicates client and
server operation and imposes additional overhead. Interleaved binary
data SHOULD only be used if RTSP is carried over TCP.
Stream data such as RTP packets is encapsulated by an ASCII dollar
sign (24 hexadecimal), followed by a one-byte channel identifier,
followed by the length of the encapsulated binary data as a binary,
two-byte integer in network byte order. The stream data follows
immediately afterwards, without a CRLF, but including the upper-layer
protocol headers. Each $ block contains exactly one upper-layer
protocol data unit, e.g., one RTP packet.
The channel identifier is defined in the Transport header with the
interleaved parameter(Section 12.39).
When the transport choice is RTP, RTCP messages are also interleaved
by the server over the TCP connection. As a default, RTCP packets are
sent on the first available channel higher than the RTP channel. The
client MAY explicitly request RTCP packets on another channel. This
is done by specifying two channels in the interleaved parameter of
the Transport header(Section 12.39).
RTCP is needed for synchronization when two or more streams are
interleaved in such a fashion. Also, this provides a convenient way
to tunnel RTP/RTCP packets through the TCP control connection when
required by the network configuration and transfer them onto UDP
when possible.
C->S: SETUP rtsp://foo.com/bar.file RTSP/1.0
CSeq: 2
Transport: RTP/AVP/TCP;interleaved=0-1
S->C: RTSP/1.0 200 OK
CSeq: 2
Date: 05 Jun 1997 18:57:18 GMT
Transport: RTP/AVP/TCP;interleaved=0-1
Schulzrinne, et. al. Standards Track [Page 40]
RFC 2326 Real Time Streaming Protocol April 1998
Session: 12345678
C->S: PLAY rtsp://foo.com/bar.file RTSP/1.0
CSeq: 3
Session: 12345678
S->C: RTSP/1.0 200 OK
CSeq: 3
Session: 12345678
Date: 05 Jun 1997 18:59:15 GMT
RTP-Info: url=rtsp://foo.com/bar.file;
seq=232433;rtptime=972948234
S->C: $\000{2 byte length}{"length" bytes data, w/RTP header}
S->C: $\000{2 byte length}{"length" bytes data, w/RTP header}
S->C: $\001{2 byte length}{"length" bytes RTCP packet}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
10.12Embedded(Interleaved)BinaryData
Certainfirewalldesignsandothercircumstancesmayforceaserver
tointerleaveRTSPmethodsandstreamdata.Thisinterleavingshould
generallybeavoidedunlessnecessarysinceitcomplicatesclientand
serveroperationandimposesadditionaloverhead.Interleavedbinary
dataSHOULDonlybeusedifRTSPiscarriedoverTCP.
StreamdatasuchasRTPpacketsisencapsulatedbyanASCIIdollar
sign(24hexadecimal),followedbyaone-bytechannelidentifier,
followedbythelengthoftheencapsulatedbinarydataasabinary,
two-byteintegerinnetworkbyteorder.Thestreamdatafollows
immediatelyafterwards,withoutaCRLF,butincludingtheupper-layer
protocolheaders.Each$blockcontainsexactlyoneupper-layer
protocoldataunit,e.g.,oneRTPpacket.
ThechannelidentifierisdefinedintheTransportheaderwiththe
interleavedparameter(Section12.39).
WhenthetransportchoiceisRTP,RTCPmessagesarealsointerleaved
bytheserverovertheTCPconnection.Asadefault,RTCPpacketsare
sentonthefirstavailablechannelhigherthantheRTPchannel.The
clientMAYexplicitlyrequestRTCPpacketsonanotherchannel.This
isdonebyspecifyingtwochannelsintheinterleavedparameterof
theTransportheader(Section12.39).
RTCPisneededforsynchronizationwhentwoormorestreamsare
interleavedinsuchafashion.Also,thisprovidesaconvenientway
totunnelRTP/RTCPpacketsthroughtheTCPcontrolconnectionwhen
requiredbythenetworkconfigurationandtransferthemontoUDP
whenpossible.
C->S:SETUPrtsp://foo.com/bar.file RTSP/1.0
CSeq:2
Transport:RTP/AVP/TCP;interleaved=0-1
S->C:RTSP/1.0200OK
CSeq:2
Date:05Jun199718:57:18GMT
Transport:RTP/AVP/TCP;interleaved=0-1
Schulzrinne,et.al.StandardsTrack[Page40]
RFC2326RealTimeStreamingProtocolApril1998
Session:12345678
C->S:PLAYrtsp://foo.com/bar.file RTSP/1.0
CSeq:3
Session:12345678
S->C:RTSP/1.0200OK
CSeq:3
Session:12345678
Date:05Jun199718:59:15GMT
RTP-Info:url=rtsp://foo.com/bar.file;
seq=232433;rtptime=972948234
S->C:$\000{2bytelength}{"length"bytesdata,w/RTPheader}
S->C:$\000{2bytelength}{"length"bytesdata,w/RTPheader}
S->C:$\001{2bytelength}{"length"bytesRTCPpacket}
rtp over tcp模式下,就一個socket端口進行命令控制以及流傳輸,不像rtp over udp,另開udp socket傳輸數據。由于防火墻以及其他外部因素,可能造成rtsp方法與rtp流數據交織混在一起。為了避免這個,才有這個設計。通過:
'$'+信道編號(0或1)+數據
1
'$'+信道編號(0或1)+數據
對控制信息以及流數據進行區分。具體介紹可以參考:
RTP over RTSP包混合發送的解決辦法:https://blog.csdn.net/myslq/article/details/79819179
由于Embedded (Interleaved) Binary Data是在是在服務器回應PLAY推流后vlc才這樣處理的,我這邊沒注意到,所以導致解析出現錯誤。不過除了vlc,其他播放器都沒支持Embedded (Interleaved) Binary Data,因為推流是服務器端,前面命令交互完,服務器就開始推流了,對于客戶端我覺得用處不大。
總結
以上是生活随笔為你收集整理的android vlc 画面不动,vlc播放rtsp over tcp画面突然卡住问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国智能电饭煲行业市场供需与战略研究报告
- 下一篇: 雷军需要讲好新故事,小米需要新风口