从零开始成为GStreamer专家——RTSP播放开发
RTSP不同于本地播放,也不同于http,它需要动态创建source的srcpad,不可以直接将source和typefind相连,涉及到的元素按创建时间的先后顺序有:
rtspsrc |
rtspwms |
rtspreal |
udpsrc |
udpsrc |
rtpbin |
rtpsession |
rtpssrcdemux |
rtpstorage |
udpsink |
fakesrc |
udpsink |
rtpjitterbuffer |
rtpptdemux |
层次关系如下:
数据流向:GstUdpSrc->GstRtpSession->GstRtpStorge->GstRtpSsrcDemux->GstRtpJitterBuffer->GstRtpDemux->GstTypeFindElement
UdpSrc可能有多个线程:
UdpSrc用gst_base_src_get_range来获取buffer数据并且产生buffer的pts,dts。gst_base_src_do_sync函数产生buffer的pts。注意,如果basesrc没有clock,那么,pts和dts则不会正确产生出来,为-1。PTS,DTS异常的buffer,被投递到RtpJitterBuffer之后,会被丢弃,产生错误,因此,需要为basesrc配置时钟,就是配置元素的clock。
RTSP调用gst_rtspsrc_stream_configure_udp函数,在UdpSrc加放到RTSP这个bin当中时,由gstbin的gst_bin_add_func函数配置clock,这个clock也就是RTSP bin的一个clock。注意,RTSP这个sorce是一个bin。而RTSP bin的时钟是由pipeline提供的,在GST_STATE_CHANGE_PAUSED_TO_PLAYING状态切换时,gst_pipeline_change_state函数用gst_element_provide_clock产生一个clock,也就是说,clock得等到playing状态时才有。RTSP的动态pad得在playing状态产生。
和typefind相连的动态产生的source,这个source的pad开放出去流程如下:
source_new_pad subprojects\gst-plugins-base\gst\playback\gsturidecodebin.c(2170)
……
g_signal_emit subprojects\glib\gobject\gsignal.c(3555)
gst_element_add_pad subprojects\gstreamer\gst\gstelement.c(803)
new_manager_pad subprojects\gst-plugins-good\gst\rtsp\gstrtspsrc.c(3535)
……
g_signal_emit subprojects\glib\gobject\gsignal.c(3555)
gst_element_add_pad subprojects\gstreamer\gst\gstelement.c(803)
expose_recv_src_pad subprojects\gst-plugins-good\gst\rtpmanager\gstrtpbin.c(3724)
……
g_signal_emit subprojects\glib\gobject\gsignal.c(3555)
gst_rtp_pt_demux_chain subprojects\gst-plugins-good\gst\rtpmanager\gstrtpptdemux.c(522)
gst_pad_chain_data_unchecked subprojects\gstreamer\gst\gstpad.c(4449)
gst_pad_push_data subprojects\gstreamer\gst\gstpad.c(4714)
gst_pad_push subprojects\gstreamer\gst\gstpad.c(4832)
pop_and_push_next subprojects\gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(3606)
gst_rtp_jitter_buffer_loop subprojects\gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(4222)
gst_task_func subprojects\gstreamer\gst\gsttask.c(384)
可以提前手动创建一个clock来满足时钟需求,而不用全部转入到PLAYING状态。如下:
static GstElement *
setup_jitterbuffer (gint num_buffers)
{
......
jitterbuffer = gst_check_setup_element ("rtpjitterbuffer");
/* we need a clock here */
clock = gst_system_clock_obtain ();
gst_element_set_clock (jitterbuffer, clock);
gst_object_unref (clock);
......
}
或者这样配置:
static void
setup_rtpdtmfsrc (void)
{
......
testclock = gst_test_clock_new ();
dtmfsrc = gst_check_setup_element ("rtpdtmfsrc");
fail_unless (gst_element_set_clock (dtmfsrc, testclock));
......
}
这个clock并不能完全满足播放需求,由于RTP时钟和产生时钟的巨大差异,会导致某些影片提前退出播放,具体原因在后面描述,此处贴出解决方案:
void
set_element_clock(GstElement *element)
{ /* we need a clock here for live play */
GstClock *clock = gst_element_provide_clock(element);
if (!clock) {
clock = gst_system_clock_obtain();
}
/* Config pipeline 4s delay */
GstClockTime delay = 4 * GST_SECOND;
GstClockTime now, start_time;
now = gst_clock_get_time(clock);
g_return_if_fail(gst_element_set_clock(element, clock));
start_time = GST_ELEMENT_START_TIME(element);
if (start_time != GST_CLOCK_TIME_NONE && now != GST_CLOCK_TIME_NONE) {
GstClockTime new_base_time = now - start_time + delay;
gst_element_set_base_time(element, new_base_time);
}
}
动态PAD产生出来之后,就可以创建PIPELINE播放节目了,typefind输出与通常直接的码流播放还不一样,这里面的输出包含了RTP的一些头信息,所以在typefind之后需要接上depayloader,然后再与demux相连。需要特别提一下的是,有的工具可通是将音频与视频分开推流的,也就是说动态产生的source可能不止一个,可能是两个或者多个(A/V/S),这时,每个source都需要连接一个typefind,然后deplayloader,demux。下图给一个3路source的示例:
RTSP EOS事件
RTSP有个timer,会发送EOS事件,rtp_timer_queue_set_eos函数会将timer的seqnum设置成-1,所以timer对应的事件很快就会发生。RTP_TIMER_EOS timer出现的时候,会产生GST_EVENT_EOS事件结束播放,rtp eos timer产生流程如下:
1. 设置Timer
rtp_timer_queue_set_timer gst-plugins-good\gst\rtpmanager\rtptimerqueue.c(608)
rtp_timer_queue_set_eos gst-plugins-good\gst\rtpmanager\rtptimerqueue.c(664)
update_estimated_eos gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(3493)
pop_and_push_next gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(3554)
gst_rtp_jitter_buffer_loop gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(4223)
来看一下关键函数,术语相关的信息可通参考
https://www.rfc-editor.org/rfc/rfc2326
https://www.ietf.org/rfc/rfc2326.txt
https://cgit.freedesktop.org/gstreamer/gst-plugins-good/tree/gst/rtp/README
这些网站,有一些变量需要解释一下:
/* 1. Normal play time (NPT),表示当前码流与播放开始的相对时间,比如世界杯已经播出了35分钟,观众才接入,那么当前流相对与播放开始时间就是35分钟。快快退时,这个时间相应变快,有now,range,时分秒等参数。是播放时,客户端给服务端的响应,如Range: npt=73.4-4311。
* 2. rtptime 可以理解成收到第一个RTP包与此次会话开始的相对时间,比如会话开始在0秒,收到第一个RTP包是在10秒,第一个包的rtptime就是10。ext_timestamp就是上一个RTP包的rtptime, 由于rtptime是32位容易反转,而ext_timestamp是64位,是用来处理时间戳翻转,保证时间的有效性。初始为-1。
* 3. clock_base就是第一个RTP包对应的rtptime,用来记录服务器已经发送了多长时间数据,可能为-1。
* 4. clock_rate 用来转换rtptime和GStreamer time,以确定buffer中的数据量。
* 5. GStreamer时间戳和RTP timestamp 转换关系如下:
RTP = ((RT - RT-base) * clock-rate / GST_SECOND) + RTP-offset
RTP : 流的32bits的RTP时间戳
RT : RTP包时对应的GStreamer时间。
RT-base : 第一个RTP包的GStreamer时间。
clock-rate: the clock-rate of the stream
RTP-offset: a random RTP offset
clock-base就是RT为RT-base时的RTP时间戳,原文:The RTP timestamp corresponding to RT-base is the clock-base.
*/
举列某CAPS如下:
application/x-rtp, media=(string)video, payload=(int)96, clock-rate=(int)90000, encoding-name=(string)H264, packetization-mode=(string)1, profile-level-id=(string)64001F, sprop-parameter-sets=(string)"Z2QAH6w04sC0EW/8AFgAPEAAAAMAQAAADKPGDGLg\,aO68sA\=\=", a-tool=(string)"LIVE555\ Streaming\ Media\ v2014.07.18", a-type=(string)broadcast, x-qt-text-nam=(string)"Matroska\ video+audio+\(optional\)subtitles\,\ streamed\ by\ the\ LIVE555\ Media\ Server", x-qt-text-inf=(string)007_X264_6audio_5sub.mkv, clock-base=(uint)1121939676, seqnum-base=(uint)50933, npt-start=(guint64)0, npt-stop=(guint64)4807600000000, play-speed=(double)1, play-scale=(double)1, onvif-mode=(boolean)false, ssrc=(uint)948598643
ext SR 1122694431, base -1, clock-rate 90000, clock-base 1121939676, last-rtptime -1
ext SR 1122694431, base 1121939676, clock-rate 90000, clock-base 1121939676, last-rtptime 1121939676
ext SR 1123026880, base 1121939676, clock-rate 90000, clock-base 1121939676, last-rtptime 1121939676
其中,npt-start=(guint64)0, npt-stop就是这个MKV文件的时长,clock-base取的是第一个rtptime的时间。
又如:
ext SR 247494882, base -1, clock-rate 90000, clock-base -1, last-rtptime -1
ext SR 247494882, base 246437702, clock-rate 90000, clock-base -1, last-rtptime 246437702
ext SR 247947492, base 246437702, clock-rate 90000, clock-base -1, last-rtptime 246437702
ext SR 248397942, base 246437702, clock-rate 90000, clock-base -1, last-rtptime 246437702
ext SR 248848842, base 246437702, clock-rate 90000, clock-base -1, last-rtptime 246437702
static void
update_estimated_eos (GstRtpJitterBuffer * jitterbuffer,
RTPJitterBufferItem * item)
{
……
GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
/* npt_stop是流的结束时间,npt_start是流的开始时间,以下参数分别表示上一个RTP包的时间戳,第一个RTP包,以及时钟频率都没有,表明未收到流的信息及RTP包,返回。 */
if (priv->npt_stop == -1 || priv->ext_timestamp == -1
|| priv->clock_base == -1 || priv->clock_rate <= 0)
return;
/* compute the elapsed time,计算的是RTP从session建立以来已经经历了多长时间,elapsed = ext_time - priv->clock_base,gst_rtp_buffer_ext_timestamp 用于获取RTP扩展时间戳(timestamp),扩展时间戳通常用于指示媒体流中的数据包的绝对时间(而非相对时间戳),以便更准确地进行同步和回放。 */
elapsed = compute_elapsed (jitterbuffer, item);
/* do nothing if elapsed time doesn't increment,3个RTP包,两次传输之间时间差不大 */
if (priv->last_elapsed && elapsed <= priv->last_elapsed)
return;
priv->last_elapsed = elapsed;
/* this is the total time we need to play */
total = priv->npt_stop - priv->npt_start;
GST_LOG_OBJECT (jitterbuffer, "total %" GST_TIME_FORMAT,
GST_TIME_ARGS (total));
/* this is how much time there is left,剩下多长时间没有播放 */
if (total > elapsed)
left = total - elapsed;
else
left = 0;
/* if we have less time left that the size of the buffer, we will not
* be able to keep it filled, disabled buffering then余下没有播放的时长太短,取消buffering */
if (left < rtp_jitter_buffer_get_delay (priv->jbuf)) {
GST_DEBUG_OBJECT (jitterbuffer, "left %" GST_TIME_FORMAT
", disable buffering close to EOS", GST_TIME_ARGS (left));
rtp_jitter_buffer_disable_buffering (priv->jbuf, TRUE);
}
/* this is the current time as running-time */
out_time = item->pts;
/* estimated = item->pts * total / elapsed ,正常情况下item->pts约等于elapsed,也就是EOS的时间约为total
* 也就是说,EOS的Timer只应该有一个,且是在total时间过后被触发
* 但如果elapsed因故过多,则estimated则会变小,EOS提前,基本上可以理解成结束需要大约多久,正常应为duration
* EOS剩下时间估算时间的方式为estimated = out_time * total / elapsed
* 其中out_time已经播放的时间(单位为纳秒),total是总时间,elapsed是播放经过的绝对系统时间。
*/
if (elapsed > 0)
estimated = gst_util_uint64_scale (out_time, total, elapsed);
else {
/* if there is almost nothing left,
* we may never advance enough to end up in the above case */
if (total < GST_SECOND)
estimated = GST_SECOND;
else
estimated = -1;
}
GST_LOG_OBJECT (jitterbuffer, "elapsed %" GST_TIME_FORMAT ", estimated %"
GST_TIME_FORMAT "eos %" GST_TIME_FORMAT, GST_TIME_ARGS (elapsed),
GST_TIME_ARGS (estimated), GST_TIME_ARGS(priv->estimated_eos));
if (estimated != -1 && priv->estimated_eos != estimated) {
rtp_timer_queue_set_eos (priv->timers, estimated,
timeout_offset (jitterbuffer));
priv->estimated_eos = estimated;
}
}
2.取出Timer
取出Timer并将EOS事件放到RTPJitterBuffer队列中,这个timer要么按它最可能发生的时间放在队列中,要么按最小seqnum优先的顺序被放到队列中。流程如下:
queue_do_insert gst-plugins-good\gst\rtpmanager\rtpjitterbuffer.c(1156)
rtp_jitter_buffer_insert gst-plugins-good\gst\rtpmanager\rtpjitterbuffer.c(1156)
rtp_jitter_buffer_append_event gst-plugins-good\gst\rtpmanager\rtpjitterbuffer.c(1150)
queue_event gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(1825)
do_eos_timeout gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(4034)
do_timeout gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(4034)
wait_next_timeout gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(4119)
3.
取出RTPJitterBuffer队列中的事件,EOS事件在如下线程中被取出来,发送给下游后续的元素,结束播放:
gst_pad_push_event gstreamer\gst\gstpad.c(5679)
pop_and_push_next gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(3623)
gst_rtp_jitter_buffer_loop gst-plugins-good\gst\rtpmanager\gstrtpjitterbuffer.c(4223)
取出Timer的依据如下:
/* 取得当前时间 */
if (priv->eos) {
now = GST_CLOCK_TIME_NONE;
} else if (GST_ELEMENT_CLOCK (jitterbuffer)) {
now =
gst_clock_get_time (GST_ELEMENT_CLOCK (jitterbuffer)) -
GST_ELEMENT_CAST (jitterbuffer)->base_time;
}
/* 遍历所有Timer,取出比当前时间小的Timer,因为时间已经过了 */
while ((timer = rtp_timer_queue_pop_until (priv->timers, now)))
do_timeout (jitterbuffer, timer, now, &events);
如果在VC调试的时候,不能断住,日志打印可以行,Gstreamer里面,gstinfo.c中可以找出void gst_debug_print_stack_trace (void)和gchar *gst_debug_get_stack_trace (GstStackTraceFlags flags)函数,用来打印调用栈信息。
说明:
1. gst_rtp_jitter_buffer_loop处理传入的RTP 数据包,将其缓冲直到可以按正确顺序播放出。由于网络拥塞或其他因素,RTP 数据包有时会以不规则的间隔到达。GstRtpJitterBuffer将数据包保留直到可以按正确顺序排序和发送,确保音频或视频流畅播放,没有任何跳跃或故障。
2. pop_and_push_next(GstRtpJitterBuffer *jitterbuffer, guint seqnum)用于删除队列中指定序列号的数据包,当该函数被调用时,它会查找第二个参数指定序列号的数据包并删除它,然后将下一个数据包推到队列的前面。
3. rtp_jitter_buffer_pop(RTPJitterBuffer *jbuf, gint *percent) 用于从缓冲区中取出第一个数据包,并计算缓冲区的占用百分比。percent变量的指针用于存储缓冲区的占用百分比。如果缓冲区中没有数据包可用,则函数返回 NULL。在这种情况下,percent 会被设置为 0。通过计算缓冲区的占用百分比,可以确定缓冲区是否已满或空,并做出相应的处理。
4. update_estimated_eos用于更新媒体流的预估结束时间戳 (EOS)。预估的 EOS 时间戳用于确定媒体流预计何时结束,函数计算媒体流中已处理的最大时间戳,即时长。然后,基于假设不会再接收到时间戳大于最大时间戳的缓冲区,使用该最大时间戳来预估流的结束时间。
在需要知道流预计何时结束的应用中使用 update_estimated_eos 函数。通过定期更新预估的 EOS 时间戳,这些应用程序可以准确地确定何时停止播放或记录流。
5. RTST中有一个专门的线程,用来取超时的timer,即wait_next_timeout。
wait_next_timeout (GstRtpJitterBuffer *jitterbuffer) 用于等待下一个超时事件。函数从timer的队列中取出timer,已经超时,就执行超时timer对应的类型函数处理;如果没有超时,则会调用
gst_clock_id_wait来等待特定的时钟 ID 到达指定的时间。该函数等待的时间以纳秒为单位,会阻塞当前线程,直到时钟 ID 的时间等于或超过指定的时间,函数继续循环执行超时事件,所以说有Timer,没超时也会等到超时。如果等待时间过长(例如,超过了指定的时间),函数将返回 GST_CLOCK_UNSCHEDULED。如果等待成功,则返回 GST_CLOCK_OK。
6. 发送方的时间差体现在rtp_time上,接收方的时间差体现在base_time上。
7. rtp_jitter_buffer_calculate_pts 会重置RTPJitterBuffer,包括重置base_time,base_extrtp,base_rtptime等等。原因是:
1. 收到第一个RTP数据包时
2. RTP时间戳回绕或者小于base_rtp时间戳时,
3. 是当 RTP 数据包之间的时间戳间隔为0时,就会产生一个间隔为0的 gap。如果不重置 ext_rtptime,就有可能会导致 PTS 计算错误,从而影响音视频的同步性和流畅性。
4. 当 RTP 时间戳和系统时间之间存在较大差别时重置。
WMS服务器支持
RTSP连接命令用gst_rtspsrc_init_request来初始化,每个RTP的session都包括两个sink,一个是rtp的,另外一个是rtcp的。RTP的session数据源于理底层的UDP/TCP source,同样,每个RTP的session都包括两个TCP/UDP Source和当前的rtp/rtcp sink连接。客户端连接服务器发送RTSP描述请求(DESCRIBE request),服务器反馈SDP(Session DescriptionProtocol)信息,包括流数量、媒体类型等。客户端分析SDP描述,为RTSP SRC中每条流调用gst_rtspsrc_create_stream建立strem结构,发送RTSP连接建立请求(SETUPrequest)并告诉Server接收数据的端口,Server响应该请求(SETUP response)并建立连接之后,传送媒体流(RTP包)到客户端。
每条流通过gst_rtspsrc_setup_streams_start来创建session,流程如下:
gst_rtspsrc_open->gst_rtspsrc_open_from_sdp->gst_rtspsrc_setup_streams_start->gst_rtsp_src_setup_stream_from_response->gst_rtspsrc_stream_configure_transport->gst_rtspsrc_stream_configure_manager->gst_element_request_pad_simple->_gst_element_request_pad->gst_rtp_bin_request_new_pad->create_recv_rtp->create_session
客户端发送终止请求(TEARDOWN request)来结束流媒体会话。
new_ssrc_pad_found回调函数用来产生GstRtpBinStream。
对于Windows Media Services (WMS) 流需要特别注意:
1 application streams只能用UDP传输
2 所有的流都在一个UDP数据连接上面传输
3 SDP中的RTX流不需要
4 第一条流建立成功之后,其余的2,3……条流建立连接会返回461协议错误。
虽然如此,但是在SETUP阶段,所有流都是需要的,如果忘记SETUP audio,那么audio将不会被传输。一个有三条流,rtx/a/v的例子如下:
OPTIONS rtsp://10.48.144.87:554/live_video RTSP/1.0
CSeq: 1
User-Agent: Lavf58.29.100
DESCRIBE rtsp://10.48.144.87:554/live_video RTSP/1.0
Accept: application/sdp
CSeq: 2
User-Agent: Lavf58.29.100
SETUP rtsp://10.48.144.87:554/live_video/rtx RTSP/1.0
Transport: RTP/AVP/UDP;unicast;client_port=21826-21827;mode=play
CSeq: 3
User-Agent: Lavf58.29.100
--
SETUP rtsp://10.48.144.87:554/live_video/video RTSP/1.0
Transport: RTP/AVP/UDP;unicast;client_port=21828;mode=play
CSeq: 4
User-Agent: Lavf58.29.100
Session: 5861654706171040746
--
SETUP rtsp://10.48.144.87:554/live_video/audio RTSP/1.0
Transport: RTP/AVP/UDP;unicast;client_port=21828;mode=play
CSeq: 5
User-Agent: Lavf58.29.100
Session: 5861654706171040746
--
PLAY rtsp://10.48.144.87:554/live_video/ RTSP/1.0
Range: npt=0.000-
CSeq: 6
User-Agent: Lavf58.29.100
Session: 5861654706171040746
--
[asf demuxer @ 0DDD3540] key:1 stream:0 seq:154 offset:0 replic_size:8 num:81 packet_property 5D
[asf demuxer @ 0DDD3540] key:0 stream:1 seq:173 offset:0 replic_size:8 num:2 packet_property 5D
[asf demuxer @ 0DDD3540] key:1 stream:-1 seq:61569 offset:-431887785 replic_size:0 num:CC packet_property 2C
Realmedia-style server也是需要特殊处理的。
常用的视频直播协议有MP2T/RTP/UDP和MP2T/UDP,MP2T是mpeg2-ts,使用UDP的主要原因是直播视频不需要重传。将7个MP2T包打包为一个RTP,每个RTP包打包为一个UDP。或将7个MP2T包直接打包为一个UDP包。
对于Windows Media Services,Gstreamer播放有一个坑,直播但是却携带了时长信息及播放了多久用于计算elapsed的信息,_RTPJitterBufferItem中的rtp time带上了当前服务器已经播放的时间,elapsed会很大,见如下日志,这个时候,明明才开始播放,由于这个坑,计算出来的estimated很小,播放会很快结束。只要当前已经播放的a/v小于elapsed的时间,estimated下降的很快,从而会导致提前EOS。
[16:17:32 158] [I] [rtpsession][rtpsession0] pushing received RTP packet
[16:17:32 158] [I] [rtpjitterbuffer][rtpjitterbuffer0] total 0:15:40.133000000
[16:17:32 158] [I] [rtpssrcdemux][rtpssrcdemux0] received buffer of SSRC d5c47369
[16:17:32 158] [I] [rtpjitterbuffer][rtpjitterbuffer0] elapsed 0:02:21.166000000 estimated 0:00:00.219772388 eos 99:99:99.999999999 time 0:00:00.033000000 left 0:13:18.967000000
推流工具
推流工具可以选用live555MediaServer,EasyDarwin,EasyDarwin使用说明见下面URL:Releases · EasyDarwin/EasyDarwin · GitHub
这里有一小段批处理脚本来实现推流功能:
::rem ffmpeg -re -i F:\clip\mkv\007_X264_6audio_5sub.mkv -rtsp_transport tcp -vcodec copy -f rtsp rtsp://10.48.144.135/rtsp
set CURR_DIR=%cd%
SET PATH=%PATH%;%CURR_DIR%\EasyDarwin-windows-8.1.0-1901141151
start EasyDarwin.exe
::必须先等待EasyDarwin软件准备好后再用FFMPEG推流
sleep 20
goto:start
:start
ffmpeg -re -stream_loop -100 -i F:\clip\mkv\007_X264_6audio_5sub.mkv -rtsp_transport udp -codec copy -f rtsp rtsp://10.48.144.135/rtsp
if %errorlevel% EQ 0 goto :start
顺便总结一下RTMP直播流的推流服务器建立:
1. 下载nginx ,笔者下载了 http://nginx-win.ecsds.eu/download/nginx 1.7.11.3 Gryphon.zip这个版本。
A. 下载后解压随意放到一个文件夹下,注意,这个文件夹的名字最好修改一下,如修改成nginx_1_7_11_3_Gryphon,不要含有空格和“."这些字符,以免有的系统不识别。
B. 在nginx_1_7_11_3_Gryphon根目录下创建tmp/hls两级目录
nginx充当了反向代理商服务器的功能, 客户端只需要将请求发送到反向代理服务器,反向代理服务器去选择目标服务器,获取数据后再返回给客户端,客户端不需要任何配置。
2.下载https://github.com/arut/nginx-rtmp-module/, 并将nginx-rtmp-module放置到nginx的根目录下。
3.复制nginx-win-rtmp.conf成nginx.conf,并在其中加入RTMP相关内容,如下:
4.打开cmd终端,cd到nginx_1_7_11_3_Gryphon目录下,执行nginx.exe即可。
可以打开http://localhost/ 查看nginx工作是否正常。
5.用FFMPEG推流,命令如下:
ffmpeg -re -stream_loop -1 -i F:\clip\flv\Oh_MV_AVC1_AAC_1080.f4v -codec copy -f flv rtmp://10.48.144.135:1935/rtmp
6.用其他播放器打开地址 rtmp://10.48.144.135:1935/live/rtmp即可播放。
来一个脚本支持推流:文章来源:https://www.toymoban.com/news/detail-660617.html
set CURR_DIR=%cd%
SET PATH=%PATH%;%CURR_DIR%\nginx_1.7.11.3_Gryphon
nginx.exe -s stop
nginx.exe -s quit
taskkill /F /IM nginx.exe
start nginx.exe
sleep 4
:start
ffmpeg -re -stream_loop -1 -i F:\clip\flv\Oh_MV_AVC1_AAC_1080.f4v -codec copy -f flv rtmp://10.48.144.135:1935/live/rtmp
goto :start
错误1:nginx: [emerg] CreateDirectory() "/tmp/hls" failed (3: The system cannot find the path specified)
可以将 /tmp/hls 修改成 ./tmp/hls 即可解决问题
错误2:ffmpeg推流报I/O错误 或者 Cannot open connection tcp,记得将 nginx.conf 文件中的application 后面的名称用到直播流的第一级目录上,见上述图片描述。文章来源地址https://www.toymoban.com/news/detail-660617.html
到了这里,关于从零开始成为GStreamer专家——RTSP播放开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!