You've already forked FFmpeg
							
							
				mirror of
				https://github.com/FFmpeg/FFmpeg.git
				synced 2025-10-30 23:18:11 +02:00 
			
		
		
		
	rtsp: Add listen mode
This makes the RTSP demuxer act as a server, listening for an incoming connection. Signed-off-by: Martin Storsjö <martin@martin.st>
This commit is contained in:
		
				
					committed by
					
						 Martin Storsjö
						Martin Storsjö
					
				
			
			
				
	
			
			
			
						parent
						
							6e71c1202b
						
					
				
				
					commit
					a8ad6ffafe
				
			| @@ -31,6 +31,7 @@ version <next>: | ||||
| - join audio filter | ||||
| - audio channel mapping filter | ||||
| - Microsoft ATC Screen decoder | ||||
| - RTSP listen mode | ||||
|  | ||||
|  | ||||
| version 0.8: | ||||
|   | ||||
| @@ -347,6 +347,8 @@ Flags for @code{rtsp_flags}: | ||||
| @table @option | ||||
| @item filter_src | ||||
| Accept packets only from negotiated peer address and port. | ||||
| @item listen | ||||
| Act as a server, listening for an incoming connection. | ||||
| @end table | ||||
|  | ||||
| When receiving data over UDP, the demuxer tries to reorder received packets | ||||
| @@ -379,6 +381,12 @@ To send a stream in realtime to a RTSP server, for others to watch: | ||||
| avconv -re -i @var{input} -f rtsp -muxdelay 0.1 rtsp://server/live.sdp | ||||
| @end example | ||||
|  | ||||
| To receive a stream in realtime: | ||||
|  | ||||
| @example | ||||
| avconv -rtsp_flags listen -i rtsp://ownaddress/live.sdp @var{output} | ||||
| @end example | ||||
|  | ||||
| @section sap | ||||
|  | ||||
| Session Announcement Protocol (RFC 2974). This is not technically a | ||||
|   | ||||
| @@ -63,7 +63,8 @@ | ||||
|  | ||||
| #define RTSP_FLAG_OPTS(name, longname) \ | ||||
|     { name, longname, OFFSET(rtsp_flags), AV_OPT_TYPE_FLAGS, {0}, INT_MIN, INT_MAX, DEC, "rtsp_flags" }, \ | ||||
|     { "filter_src", "Only receive packets from the negotiated peer IP", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" } | ||||
|     { "filter_src", "Only receive packets from the negotiated peer IP", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" }, \ | ||||
|     { "listen", "Wait for incoming connections", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_LISTEN}, 0, 0, DEC, "rtsp_flags" } | ||||
|  | ||||
| #define RTSP_MEDIATYPE_OPTS(name, longname) \ | ||||
|     { name, longname, OFFSET(media_type_mask), AV_OPT_TYPE_FLAGS, { (1 << (AVMEDIA_TYPE_DATA+1)) - 1 }, INT_MIN, INT_MAX, DEC, "allowed_media_types" }, \ | ||||
| @@ -83,6 +84,7 @@ const AVOption ff_rtsp_options[] = { | ||||
|     RTSP_MEDIATYPE_OPTS("allowed_media_types", "Media types to accept from the server"), | ||||
|     { "min_port", "Minimum local UDP port", OFFSET(rtp_port_min), AV_OPT_TYPE_INT, {RTSP_RTP_PORT_MIN}, 0, 65535, DEC|ENC }, | ||||
|     { "max_port", "Maximum local UDP port", OFFSET(rtp_port_max), AV_OPT_TYPE_INT, {RTSP_RTP_PORT_MAX}, 0, 65535, DEC|ENC }, | ||||
|     { "timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies flag listen", OFFSET(initial_timeout), AV_OPT_TYPE_INT, {-1}, INT_MIN, INT_MAX, DEC }, | ||||
|     { NULL }, | ||||
| }; | ||||
|  | ||||
| @@ -1714,14 +1716,24 @@ static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, | ||||
|             } | ||||
| #if CONFIG_RTSP_DEMUXER | ||||
|             if (tcp_fd != -1 && p[0].revents & POLLIN) { | ||||
|                 RTSPMessageHeader reply; | ||||
|  | ||||
|                 ret = ff_rtsp_read_reply(s, &reply, NULL, 0, NULL); | ||||
|                 if (ret < 0) | ||||
|                     return ret; | ||||
|                 /* XXX: parse message */ | ||||
|                 if (rt->state != RTSP_STATE_STREAMING) | ||||
|                     return 0; | ||||
|                 if (rt->rtsp_flags & RTSP_FLAG_LISTEN) { | ||||
|                     if (rt->state == RTSP_STATE_STREAMING) { | ||||
|                         if (!ff_rtsp_parse_streaming_commands(s)) | ||||
|                             return AVERROR_EOF; | ||||
|                         else | ||||
|                             av_log(s, AV_LOG_WARNING, | ||||
|                                    "Unable to answer to TEARDOWN\n"); | ||||
|                     } else | ||||
|                         return 0; | ||||
|                 } else { | ||||
|                     RTSPMessageHeader reply; | ||||
|                     ret = ff_rtsp_read_reply(s, &reply, NULL, 0, NULL); | ||||
|                     if (ret < 0) | ||||
|                         return ret; | ||||
|                     /* XXX: parse message */ | ||||
|                     if (rt->state != RTSP_STATE_STREAMING) | ||||
|                         return 0; | ||||
|                 } | ||||
|             } | ||||
| #endif | ||||
|         } else if (n == 0 && ++timeout_cnt >= MAX_TIMEOUTS) { | ||||
|   | ||||
| @@ -372,11 +372,17 @@ typedef struct RTSPState { | ||||
|      * Minimum and maximum local UDP ports. | ||||
|      */ | ||||
|     int rtp_port_min, rtp_port_max; | ||||
|  | ||||
|     /** | ||||
|      * Timeout to wait for incoming connections. | ||||
|      */ | ||||
|     int initial_timeout; | ||||
| } RTSPState; | ||||
|  | ||||
| #define RTSP_FLAG_FILTER_SRC  0x1    /**< Filter incoming UDP packets - | ||||
|                                           receive packets only from the right | ||||
|                                           source address and port. */ | ||||
| #define RTSP_FLAG_LISTEN 0x2         /**< Wait for incoming connections. */ | ||||
|  | ||||
| /** | ||||
|  * Describe a single stream, as identified by a single m= line block in the | ||||
| @@ -528,6 +534,12 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply); | ||||
|  */ | ||||
| int ff_rtsp_setup_output_streams(AVFormatContext *s, const char *addr); | ||||
|  | ||||
| /** | ||||
|  * Parse RTSP commands (OPTIONS, PAUSE and TEARDOWN) during streaming in | ||||
|  * listen mode. | ||||
|  */ | ||||
| int ff_rtsp_parse_streaming_commands(AVFormatContext *s); | ||||
|  | ||||
| /** | ||||
|  * Parse an SDP description of streams by populating an RTSPState struct | ||||
|  * within the AVFormatContext; also allocate the RTP streams and the | ||||
|   | ||||
| @@ -37,4 +37,18 @@ RTSP_STATUS_SERVICE         =503, /**< Service Unavailable */ | ||||
| RTSP_STATUS_VERSION         =505, /**< RTSP Version not supported */ | ||||
| }; | ||||
|  | ||||
| enum RTSPMethod { | ||||
|     DESCRIBE, | ||||
|     ANNOUNCE, | ||||
|     OPTIONS, | ||||
|     SETUP, | ||||
|     PLAY, | ||||
|     PAUSE, | ||||
|     TEARDOWN, | ||||
|     GET_PARAMETER, | ||||
|     SET_PARAMETER, | ||||
|     REDIRECT, | ||||
|     RECORD, | ||||
|     UNKNOWN = -1, | ||||
| }; | ||||
| #endif /* AVFORMAT_RTSPCODES_H */ | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
| #include "libavutil/avstring.h" | ||||
| #include "libavutil/intreadwrite.h" | ||||
| #include "libavutil/mathematics.h" | ||||
| #include "libavutil/random_seed.h" | ||||
| #include "avformat.h" | ||||
|  | ||||
| #include "internal.h" | ||||
| @@ -31,11 +32,30 @@ | ||||
| #include "rdt.h" | ||||
| #include "url.h" | ||||
|  | ||||
| static const struct RTSPStatusMessage { | ||||
|     enum RTSPStatusCode code; | ||||
|     const char *message; | ||||
| } status_messages[] = { | ||||
|     { RTSP_STATUS_OK,             "OK"                               }, | ||||
|     { RTSP_STATUS_METHOD,         "Method Not Allowed"               }, | ||||
|     { RTSP_STATUS_BANDWIDTH,      "Not Enough Bandwidth"             }, | ||||
|     { RTSP_STATUS_SESSION,        "Session Not Found"                }, | ||||
|     { RTSP_STATUS_STATE,          "Method Not Valid in This State"   }, | ||||
|     { RTSP_STATUS_AGGREGATE,      "Aggregate operation not allowed"  }, | ||||
|     { RTSP_STATUS_ONLY_AGGREGATE, "Only aggregate operation allowed" }, | ||||
|     { RTSP_STATUS_TRANSPORT,      "Unsupported transport"            }, | ||||
|     { RTSP_STATUS_INTERNAL,       "Internal Server Error"            }, | ||||
|     { RTSP_STATUS_SERVICE,        "Service Unavailable"              }, | ||||
|     { RTSP_STATUS_VERSION,        "RTSP Version not supported"       }, | ||||
|     { 0,                          "NULL"                             } | ||||
| }; | ||||
|  | ||||
| static int rtsp_read_close(AVFormatContext *s) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
|  | ||||
|     ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL); | ||||
|     if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN)) | ||||
|         ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL); | ||||
|  | ||||
|     ff_rtsp_close_streams(s); | ||||
|     ff_rtsp_close_connections(s); | ||||
| @@ -45,6 +65,429 @@ static int rtsp_read_close(AVFormatContext *s) | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static inline int read_line(AVFormatContext *s, char *rbuf, const int rbufsize, | ||||
|                             int *rbuflen) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
|     int idx       = 0; | ||||
|     int ret       = 0; | ||||
|     *rbuflen      = 0; | ||||
|  | ||||
|     do { | ||||
|         ret = ffurl_read_complete(rt->rtsp_hd, rbuf + idx, 1); | ||||
|         if (ret < 0) | ||||
|             return ret; | ||||
|         if (rbuf[idx] == '\r') { | ||||
|             /* Ignore */ | ||||
|         } else if (rbuf[idx] == '\n') { | ||||
|             rbuf[idx] = '\0'; | ||||
|             *rbuflen  = idx; | ||||
|             return 0; | ||||
|         } else | ||||
|             idx++; | ||||
|     } while (idx < rbufsize); | ||||
|     av_log(s, AV_LOG_ERROR, "Message too long\n"); | ||||
|     return AVERROR(EIO); | ||||
| } | ||||
|  | ||||
| static int rtsp_send_reply(AVFormatContext *s, enum RTSPStatusCode code, | ||||
|                            const char *extracontent, uint16_t seq) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
|     char message[4096]; | ||||
|     int index = 0; | ||||
|     while (status_messages[index].code) { | ||||
|         if (status_messages[index].code == code) { | ||||
|             snprintf(message, sizeof(message), "RTSP/1.0 %d %s\r\n", | ||||
|                      code, status_messages[index].message); | ||||
|             break; | ||||
|         } | ||||
|         index++; | ||||
|     } | ||||
|     if (!status_messages[index].code) | ||||
|         return AVERROR(EINVAL); | ||||
|     av_strlcatf(message, sizeof(message), "CSeq: %d\r\n", seq); | ||||
|     av_strlcatf(message, sizeof(message), "Server: %s\r\n", LIBAVFORMAT_IDENT); | ||||
|     if (extracontent) | ||||
|         av_strlcat(message, extracontent, sizeof(message)); | ||||
|     av_strlcat(message, "\r\n", sizeof(message)); | ||||
|     av_dlog(s, "Sending response:\n%s", message); | ||||
|     ffurl_write(rt->rtsp_hd, message, strlen(message)); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static inline int check_sessionid(AVFormatContext *s, | ||||
|                                   RTSPMessageHeader *request) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
|     unsigned char *session_id = rt->session_id; | ||||
|     if (!session_id[0]) { | ||||
|         av_log(s, AV_LOG_WARNING, "There is no session-id at the moment\n"); | ||||
|         return 0; | ||||
|     } | ||||
|     if (strcmp(session_id, request->session_id)) { | ||||
|         av_log(s, AV_LOG_ERROR, "Unexpected session-id %s\n", | ||||
|                request->session_id); | ||||
|         rtsp_send_reply(s, RTSP_STATUS_SESSION, NULL, request->seq); | ||||
|         return AVERROR_STREAM_NOT_FOUND; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static inline int rtsp_read_request(AVFormatContext *s, | ||||
|                                     RTSPMessageHeader *request, | ||||
|                                     const char *method) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
|     char rbuf[1024]; | ||||
|     int rbuflen, ret; | ||||
|     do { | ||||
|         ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen); | ||||
|         if (ret) | ||||
|             return ret; | ||||
|         if (rbuflen > 1) { | ||||
|             av_dlog(s, "Parsing[%d]: %s\n", rbuflen, rbuf); | ||||
|             ff_rtsp_parse_line(request, rbuf, rt, method); | ||||
|         } | ||||
|     } while (rbuflen > 0); | ||||
|     if (request->seq != rt->seq + 1) { | ||||
|         av_log(s, AV_LOG_ERROR, "Unexpected Sequence number %d\n", | ||||
|                request->seq); | ||||
|         return AVERROR(EINVAL); | ||||
|     } | ||||
|     if (rt->session_id[0] && strcmp(method, "OPTIONS")) { | ||||
|         ret = check_sessionid(s, request); | ||||
|         if (ret) | ||||
|             return ret; | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int rtsp_read_announce(AVFormatContext *s) | ||||
| { | ||||
|     RTSPState *rt             = s->priv_data; | ||||
|     RTSPMessageHeader request = { 0 }; | ||||
|     char sdp[4096]; | ||||
|     int  ret; | ||||
|  | ||||
|     ret = rtsp_read_request(s, &request, "ANNOUNCE"); | ||||
|     if (ret) | ||||
|         return ret; | ||||
|     rt->seq++; | ||||
|     if (strcmp(request.content_type, "application/sdp")) { | ||||
|         av_log(s, AV_LOG_ERROR, "Unexpected content type %s\n", | ||||
|                request.content_type); | ||||
|         rtsp_send_reply(s, RTSP_STATUS_SERVICE, NULL, request.seq); | ||||
|         return AVERROR_OPTION_NOT_FOUND; | ||||
|     } | ||||
|     if (request.content_length && request.content_length < sizeof(sdp) - 1) { | ||||
|         /* Read SDP */ | ||||
|         if (ffurl_read_complete(rt->rtsp_hd, sdp, request.content_length) | ||||
|             < request.content_length) { | ||||
|             av_log(s, AV_LOG_ERROR, | ||||
|                    "Unable to get complete SDP Description in ANNOUNCE\n"); | ||||
|             rtsp_send_reply(s, RTSP_STATUS_INTERNAL, NULL, request.seq); | ||||
|             return AVERROR(EIO); | ||||
|         } | ||||
|         sdp[request.content_length] = '\0'; | ||||
|         av_log(s, AV_LOG_VERBOSE, "SDP: %s\n", sdp); | ||||
|         ret = ff_sdp_parse(s, sdp); | ||||
|         if (ret) | ||||
|             return ret; | ||||
|         rtsp_send_reply(s, RTSP_STATUS_OK, NULL, request.seq); | ||||
|         return 0; | ||||
|     } | ||||
|     av_log(s, AV_LOG_ERROR, | ||||
|            "Content-Length header value exceeds sdp allocated buffer (4KB)\n"); | ||||
|     rtsp_send_reply(s, RTSP_STATUS_INTERNAL, | ||||
|                     "Content-Length exceeds buffer size", request.seq); | ||||
|     return AVERROR(EIO); | ||||
| } | ||||
|  | ||||
| static int rtsp_read_options(AVFormatContext *s) | ||||
| { | ||||
|     RTSPState *rt             = s->priv_data; | ||||
|     RTSPMessageHeader request = { 0 }; | ||||
|     int ret                   = 0; | ||||
|  | ||||
|     /* Parsing headers */ | ||||
|     ret = rtsp_read_request(s, &request, "OPTIONS"); | ||||
|     if (ret) | ||||
|         return ret; | ||||
|     rt->seq++; | ||||
|     /* Send Reply */ | ||||
|     rtsp_send_reply(s, RTSP_STATUS_OK, | ||||
|                     "Public: ANNOUNCE, PAUSE, SETUP, TEARDOWN, RECORD\r\n", | ||||
|                     request.seq); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int rtsp_read_setup(AVFormatContext *s, char* host, char *controlurl) | ||||
| { | ||||
|     RTSPState *rt             = s->priv_data; | ||||
|     RTSPMessageHeader request = { 0 }; | ||||
|     int ret                   = 0; | ||||
|     char url[1024]; | ||||
|     RTSPStream *rtsp_st; | ||||
|     char responseheaders[1024]; | ||||
|     int localport    = -1; | ||||
|     int transportidx = 0; | ||||
|     int streamid     = 0; | ||||
|  | ||||
|     ret = rtsp_read_request(s, &request, "SETUP"); | ||||
|     if (ret) | ||||
|         return ret; | ||||
|     rt->seq++; | ||||
|     if (!request.nb_transports) { | ||||
|         av_log(s, AV_LOG_ERROR, "No transport defined in SETUP\n"); | ||||
|         return AVERROR_INVALIDDATA; | ||||
|     } | ||||
|     for (transportidx = 0; transportidx < request.nb_transports; | ||||
|          transportidx++) { | ||||
|         if (!request.transports[transportidx].mode_record || | ||||
|             (request.transports[transportidx].lower_transport != | ||||
|              RTSP_LOWER_TRANSPORT_UDP && | ||||
|              request.transports[transportidx].lower_transport != | ||||
|              RTSP_LOWER_TRANSPORT_TCP)) { | ||||
|             av_log(s, AV_LOG_ERROR, "mode=record/receive not set or transport" | ||||
|                    " protocol not supported (yet)\n"); | ||||
|             return AVERROR_INVALIDDATA; | ||||
|         } | ||||
|     } | ||||
|     if (request.nb_transports > 1) | ||||
|         av_log(s, AV_LOG_WARNING, "More than one transport not supported, " | ||||
|                "using first of all\n"); | ||||
|     for (streamid = 0; streamid < rt->nb_rtsp_streams; streamid++) { | ||||
|         if (!strcmp(rt->rtsp_streams[streamid]->control_url, | ||||
|                     controlurl)) | ||||
|             break; | ||||
|     } | ||||
|     if (streamid == rt->nb_rtsp_streams) { | ||||
|         av_log(s, AV_LOG_ERROR, "Unable to find requested track\n"); | ||||
|         return AVERROR_STREAM_NOT_FOUND; | ||||
|     } | ||||
|     rtsp_st   = rt->rtsp_streams[streamid]; | ||||
|     localport = rt->rtp_port_min; | ||||
|  | ||||
|     if (request.transports[0].lower_transport == RTSP_LOWER_TRANSPORT_TCP) { | ||||
|         rt->lower_transport = RTSP_LOWER_TRANSPORT_TCP; | ||||
|         if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) { | ||||
|             rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq); | ||||
|             return ret; | ||||
|         } | ||||
|         rtsp_st->interleaved_min = request.transports[0].interleaved_min; | ||||
|         rtsp_st->interleaved_max = request.transports[0].interleaved_max; | ||||
|         snprintf(responseheaders, sizeof(responseheaders), "Transport: " | ||||
|                  "RTP/AVP/TCP;unicast;mode=receive;interleaved=%d-%d" | ||||
|                  "\r\n", request.transports[0].interleaved_min, | ||||
|                  request.transports[0].interleaved_max); | ||||
|     } else { | ||||
|         do { | ||||
|             ff_url_join(url, sizeof(url), "rtp", NULL, host, localport, NULL); | ||||
|             av_dlog(s, "Opening: %s", url); | ||||
|             ret = ffurl_open(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE, | ||||
|                              &s->interrupt_callback, NULL); | ||||
|             if (ret) | ||||
|                 localport += 2; | ||||
|         } while (ret || localport > rt->rtp_port_max); | ||||
|         if (localport > rt->rtp_port_max) { | ||||
|             rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq); | ||||
|             return ret; | ||||
|         } | ||||
|  | ||||
|         av_dlog(s, "Listening on: %d", | ||||
|                 ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle)); | ||||
|         if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) { | ||||
|             rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq); | ||||
|             return ret; | ||||
|         } | ||||
|  | ||||
|         localport = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle); | ||||
|         snprintf(responseheaders, sizeof(responseheaders), "Transport: " | ||||
|                  "RTP/AVP/UDP;unicast;mode=receive;source=%s;" | ||||
|                  "client_port=%d-%d;server_port=%d-%d\r\n", | ||||
|                  host, request.transports[0].client_port_min, | ||||
|                  request.transports[0].client_port_max, localport, | ||||
|                  localport + 1); | ||||
|     } | ||||
|  | ||||
|     /* Establish sessionid if not previously set */ | ||||
|     /* Put this in a function? */ | ||||
|     /* RFC 2326: session id must be at least 8 digits */ | ||||
|     while (strlen(rt->session_id) < 8) | ||||
|         av_strlcatf(rt->session_id, 512, "%u", av_get_random_seed()); | ||||
|  | ||||
|     av_strlcatf(responseheaders, sizeof(responseheaders), "Session: %s\r\n", | ||||
|                 rt->session_id); | ||||
|     /* Send Reply */ | ||||
|     rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq); | ||||
|  | ||||
|     rt->state = RTSP_STATE_PAUSED; | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int rtsp_read_record(AVFormatContext *s) | ||||
| { | ||||
|     RTSPState *rt             = s->priv_data; | ||||
|     RTSPMessageHeader request = { 0 }; | ||||
|     int ret                   = 0; | ||||
|     char responseheaders[1024]; | ||||
|  | ||||
|     ret = rtsp_read_request(s, &request, "RECORD"); | ||||
|     if (ret) | ||||
|         return ret; | ||||
|     ret = check_sessionid(s, &request); | ||||
|     if (ret) | ||||
|         return ret; | ||||
|     rt->seq++; | ||||
|     snprintf(responseheaders, sizeof(responseheaders), "Session: %s\r\n", | ||||
|              rt->session_id); | ||||
|     rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq); | ||||
|  | ||||
|     rt->state = RTSP_STATE_STREAMING; | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static inline int parse_command_line(AVFormatContext *s, const char *line, | ||||
|                                      int linelen, char *uri, int urisize, | ||||
|                                      char *method, int methodsize, | ||||
|                                      enum RTSPMethod *methodcode) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
|     const char *linept, *searchlinept; | ||||
|     linept = strchr(line, ' '); | ||||
|     if (linept - line > methodsize - 1) { | ||||
|         av_log(s, AV_LOG_ERROR, "Method string too long\n"); | ||||
|         return AVERROR(EIO); | ||||
|     } | ||||
|     memcpy(method, line, linept - line); | ||||
|     method[linept - line] = '\0'; | ||||
|     linept++; | ||||
|     if (!strcmp(method, "ANNOUNCE")) | ||||
|         *methodcode = ANNOUNCE; | ||||
|     else if (!strcmp(method, "OPTIONS")) | ||||
|         *methodcode = OPTIONS; | ||||
|     else if (!strcmp(method, "RECORD")) | ||||
|         *methodcode = RECORD; | ||||
|     else if (!strcmp(method, "SETUP")) | ||||
|         *methodcode = SETUP; | ||||
|     else if (!strcmp(method, "PAUSE")) | ||||
|         *methodcode = PAUSE; | ||||
|     else if (!strcmp(method, "TEARDOWN")) | ||||
|         *methodcode = TEARDOWN; | ||||
|     else | ||||
|         *methodcode = UNKNOWN; | ||||
|     /* Check method with the state  */ | ||||
|     if (rt->state == RTSP_STATE_IDLE) { | ||||
|         if ((*methodcode != ANNOUNCE) && (*methodcode != OPTIONS)) { | ||||
|             av_log(s, AV_LOG_ERROR, "Unexpected command in Idle State %s\n", | ||||
|                    line); | ||||
|             return AVERROR_PROTOCOL_NOT_FOUND; | ||||
|         } | ||||
|     } else if (rt->state == RTSP_STATE_PAUSED) { | ||||
|         if ((*methodcode != OPTIONS) && (*methodcode != RECORD) | ||||
|             && (*methodcode != SETUP)) { | ||||
|             av_log(s, AV_LOG_ERROR, "Unexpected command in Paused State %s\n", | ||||
|                    line); | ||||
|             return AVERROR_PROTOCOL_NOT_FOUND; | ||||
|         } | ||||
|     } else if (rt->state == RTSP_STATE_STREAMING) { | ||||
|         if ((*methodcode != PAUSE) && (*methodcode != OPTIONS) | ||||
|             && (*methodcode != TEARDOWN)) { | ||||
|             av_log(s, AV_LOG_ERROR, "Unexpected command in Streaming State" | ||||
|                    " %s\n", line); | ||||
|             return AVERROR_PROTOCOL_NOT_FOUND; | ||||
|         } | ||||
|     } else { | ||||
|         av_log(s, AV_LOG_ERROR, "Unexpected State [%d]\n", rt->state); | ||||
|         return AVERROR_BUG; | ||||
|     } | ||||
|  | ||||
|     searchlinept = strchr(linept, ' '); | ||||
|     if (searchlinept == NULL) { | ||||
|         av_log(s, AV_LOG_ERROR, "Error parsing message URI\n"); | ||||
|         return AVERROR_INVALIDDATA; | ||||
|     } | ||||
|     if (searchlinept - linept > urisize - 1) { | ||||
|         av_log(s, AV_LOG_ERROR, "uri string length exceeded buffer size\n"); | ||||
|         return AVERROR(EIO); | ||||
|     } | ||||
|     memcpy(uri, linept, searchlinept - linept); | ||||
|     uri[searchlinept - linept] = '\0'; | ||||
|     if (strcmp(rt->control_uri, uri)) { | ||||
|         char host[128], path[512], auth[128]; | ||||
|         int port; | ||||
|         char ctl_host[128], ctl_path[512], ctl_auth[128]; | ||||
|         int ctl_port; | ||||
|         av_url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host), &port, | ||||
|                      path, sizeof(path), uri); | ||||
|         av_url_split(NULL, 0, ctl_auth, sizeof(ctl_auth), ctl_host, | ||||
|                      sizeof(ctl_host), &ctl_port, ctl_path, sizeof(ctl_path), | ||||
|                      rt->control_uri); | ||||
|         if (strcmp(host, ctl_host)) | ||||
|             av_log(s, AV_LOG_INFO, "Host %s differs from expected %s\n", | ||||
|                    host, ctl_host); | ||||
|         if (strcmp(path, ctl_path) && *methodcode != SETUP) | ||||
|             av_log(s, AV_LOG_WARNING, "WARNING: Path %s differs from expected" | ||||
|                    " %s\n", path, ctl_path); | ||||
|         if (*methodcode == ANNOUNCE) { | ||||
|             av_log(s, AV_LOG_INFO, | ||||
|                    "Updating control URI to %s\n", uri); | ||||
|             strcpy(rt->control_uri, uri); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     linept = searchlinept + 1; | ||||
|     if (!av_strstart(linept, "RTSP/1.0", NULL)) { | ||||
|         av_log(s, AV_LOG_ERROR, "Error parsing protocol or version\n"); | ||||
|         return AVERROR_PROTOCOL_NOT_FOUND; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| int ff_rtsp_parse_streaming_commands(AVFormatContext *s) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
|     unsigned char rbuf[4096]; | ||||
|     unsigned char method[10]; | ||||
|     char uri[500]; | ||||
|     int ret; | ||||
|     int rbuflen               = 0; | ||||
|     RTSPMessageHeader request = { 0 }; | ||||
|     enum RTSPMethod methodcode; | ||||
|  | ||||
|     ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen); | ||||
|     if (ret < 0) | ||||
|         return ret; | ||||
|     ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri), method, | ||||
|                              sizeof(method), &methodcode); | ||||
|     if (ret) { | ||||
|         av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n"); | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     ret = rtsp_read_request(s, &request, method); | ||||
|     if (ret) | ||||
|         return ret; | ||||
|     rt->seq++; | ||||
|     if (methodcode == PAUSE) { | ||||
|         rt->state = RTSP_STATE_PAUSED; | ||||
|         ret       = rtsp_send_reply(s, RTSP_STATUS_OK, NULL , request.seq); | ||||
|         // TODO: Missing date header in response | ||||
|     } else if (methodcode == OPTIONS) { | ||||
|         ret = rtsp_send_reply(s, RTSP_STATUS_OK, | ||||
|                               "Public: ANNOUNCE, PAUSE, SETUP, TEARDOWN, " | ||||
|                               "RECORD\r\n", request.seq); | ||||
|     } else if (methodcode == TEARDOWN) { | ||||
|         rt->state = RTSP_STATE_IDLE; | ||||
|         ret       = rtsp_send_reply(s, RTSP_STATUS_OK, NULL , request.seq); | ||||
|         return 0; | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| static int rtsp_read_play(AVFormatContext *s) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
| @@ -157,6 +600,67 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply) | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int rtsp_listen(AVFormatContext *s) | ||||
| { | ||||
|     RTSPState *rt = s->priv_data; | ||||
|     char host[128], path[512], auth[128]; | ||||
|     char uri[500]; | ||||
|     int port; | ||||
|     char tcpname[500]; | ||||
|     unsigned char rbuf[4096]; | ||||
|     unsigned char method[10]; | ||||
|     int rbuflen = 0; | ||||
|     int ret; | ||||
|     enum RTSPMethod methodcode; | ||||
|  | ||||
|     /* extract hostname and port */ | ||||
|     av_url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host), &port, | ||||
|                  path, sizeof(path), s->filename); | ||||
|  | ||||
|     /* ff_url_join. No authorization by now (NULL) */ | ||||
|     ff_url_join(rt->control_uri, sizeof(rt->control_uri), "rtsp", NULL, host, | ||||
|                 port, "%s", path); | ||||
|     /* Create TCP connection */ | ||||
|     ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, host, port, | ||||
|                 "?listen&listen_timeout=%d", rt->initial_timeout * 1000); | ||||
|  | ||||
|     if (ret = ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE, | ||||
|                          &s->interrupt_callback, NULL)) { | ||||
|         av_log(s, AV_LOG_ERROR, "Unable to open RTSP for listening\n"); | ||||
|         return ret; | ||||
|     } | ||||
|     rt->state       = RTSP_STATE_IDLE; | ||||
|     rt->rtsp_hd_out = rt->rtsp_hd; | ||||
|     for (;;) { /* Wait for incoming RTSP messages */ | ||||
|         ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen); | ||||
|         if (ret < 0) | ||||
|             return ret; | ||||
|         ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri), method, | ||||
|                                  sizeof(method), &methodcode); | ||||
|         if (ret) { | ||||
|             av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n"); | ||||
|             return ret; | ||||
|         } | ||||
|  | ||||
|         if (methodcode == ANNOUNCE) { | ||||
|             ret       = rtsp_read_announce(s); | ||||
|             rt->state = RTSP_STATE_PAUSED; | ||||
|         } else if (methodcode == OPTIONS) { | ||||
|             ret = rtsp_read_options(s); | ||||
|         } else if (methodcode == RECORD) { | ||||
|             ret = rtsp_read_record(s); | ||||
|             if (!ret) | ||||
|                 return 0; // We are ready for streaming | ||||
|         } else if (methodcode == SETUP) | ||||
|             ret = rtsp_read_setup(s, host, uri); | ||||
|         if (ret) { | ||||
|             ffurl_close(rt->rtsp_hd); | ||||
|             return AVERROR_INVALIDDATA; | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int rtsp_probe(AVProbeData *p) | ||||
| { | ||||
|     if (av_strstart(p->filename, "rtsp:", NULL)) | ||||
| @@ -169,23 +673,32 @@ static int rtsp_read_header(AVFormatContext *s) | ||||
|     RTSPState *rt = s->priv_data; | ||||
|     int ret; | ||||
|  | ||||
|     ret = ff_rtsp_connect(s); | ||||
|     if (ret) | ||||
|         return ret; | ||||
|     if (rt->initial_timeout > 0) | ||||
|         rt->rtsp_flags |= RTSP_FLAG_LISTEN; | ||||
|  | ||||
|     rt->real_setup_cache = !s->nb_streams ? NULL : | ||||
|                            av_mallocz(2 * s->nb_streams * sizeof(*rt->real_setup_cache)); | ||||
|     if (!rt->real_setup_cache && s->nb_streams) | ||||
|         return AVERROR(ENOMEM); | ||||
|     rt->real_setup = rt->real_setup_cache + s->nb_streams; | ||||
|  | ||||
|     if (rt->initial_pause) { | ||||
|          /* do not start immediately */ | ||||
|     if (rt->rtsp_flags & RTSP_FLAG_LISTEN) { | ||||
|         ret = rtsp_listen(s); | ||||
|         if (ret) | ||||
|             return ret; | ||||
|     } else { | ||||
|          if (rtsp_read_play(s) < 0) { | ||||
|             ff_rtsp_close_streams(s); | ||||
|             ff_rtsp_close_connections(s); | ||||
|             return AVERROR_INVALIDDATA; | ||||
|         ret = ff_rtsp_connect(s); | ||||
|         if (ret) | ||||
|             return ret; | ||||
|  | ||||
|         rt->real_setup_cache = !s->nb_streams ? NULL : | ||||
|             av_mallocz(2 * s->nb_streams * sizeof(*rt->real_setup_cache)); | ||||
|         if (!rt->real_setup_cache && s->nb_streams) | ||||
|             return AVERROR(ENOMEM); | ||||
|         rt->real_setup = rt->real_setup_cache + s->nb_streams; | ||||
|  | ||||
|         if (rt->initial_pause) { | ||||
|             /* do not start immediately */ | ||||
|         } else { | ||||
|             if (rtsp_read_play(s) < 0) { | ||||
|                 ff_rtsp_close_streams(s); | ||||
|                 ff_rtsp_close_connections(s); | ||||
|                 return AVERROR_INVALIDDATA; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -349,20 +862,22 @@ retry: | ||||
|     } | ||||
|     rt->packets++; | ||||
|  | ||||
|     /* send dummy request to keep TCP connection alive */ | ||||
|     if ((av_gettime() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2 || | ||||
|          rt->auth_state.stale) { | ||||
|         if (rt->server_type == RTSP_SERVER_WMS || | ||||
|            (rt->server_type != RTSP_SERVER_REAL && | ||||
|             rt->get_parameter_supported)) { | ||||
|             ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL); | ||||
|         } else { | ||||
|             ff_rtsp_send_cmd_async(s, "OPTIONS", "*", NULL); | ||||
|     if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN)) { | ||||
|         /* send dummy request to keep TCP connection alive */ | ||||
|         if ((av_gettime() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2 || | ||||
|             rt->auth_state.stale) { | ||||
|             if (rt->server_type == RTSP_SERVER_WMS || | ||||
|                 (rt->server_type != RTSP_SERVER_REAL && | ||||
|                  rt->get_parameter_supported)) { | ||||
|                 ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL); | ||||
|             } else { | ||||
|                 ff_rtsp_send_cmd_async(s, "OPTIONS", "*", NULL); | ||||
|             } | ||||
|             /* The stale flag should be reset when creating the auth response in | ||||
|              * ff_rtsp_send_cmd_async, but reset it here just in case we never | ||||
|              * called the auth code (if we didn't have any credentials set). */ | ||||
|             rt->auth_state.stale = 0; | ||||
|         } | ||||
|         /* The stale flag should be reset when creating the auth response in | ||||
|          * ff_rtsp_send_cmd_async, but reset it here just in case we never | ||||
|          * called the auth code (if we didn't have any credentials set). */ | ||||
|         rt->auth_state.stale = 0; | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
|   | ||||
| @@ -30,7 +30,7 @@ | ||||
| #include "libavutil/avutil.h" | ||||
|  | ||||
| #define LIBAVFORMAT_VERSION_MAJOR 54 | ||||
| #define LIBAVFORMAT_VERSION_MINOR  6 | ||||
| #define LIBAVFORMAT_VERSION_MINOR  7 | ||||
| #define LIBAVFORMAT_VERSION_MICRO  0 | ||||
|  | ||||
| #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user