1 | /* |
2 | Copyright (c) 2012, Broadcom Europe Ltd |
3 | All rights reserved. |
4 | |
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the following conditions are met: |
7 | * Redistributions of source code must retain the above copyright |
8 | notice, this list of conditions and the following disclaimer. |
9 | * Redistributions in binary form must reproduce the above copyright |
10 | notice, this list of conditions and the following disclaimer in the |
11 | documentation and/or other materials provided with the distribution. |
12 | * Neither the name of the copyright holder nor the |
13 | names of its contributors may be used to endorse or promote products |
14 | derived from this software without specific prior written permission. |
15 | |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | #include <stdlib.h> |
28 | #include <stdio.h> |
29 | #include <string.h> |
30 | #include <ctype.h> |
31 | |
32 | #define CONTAINER_IS_BIG_ENDIAN |
33 | //#define ENABLE_CONTAINERS_LOG_FORMAT |
34 | //#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE |
35 | #define CONTAINER_HELPER_LOG_INDENT(a) 0 |
36 | #include "containers/core/containers_private.h" |
37 | #include "containers/core/containers_io_helpers.h" |
38 | #include "containers/core/containers_utils.h" |
39 | #include "containers/core/containers_logging.h" |
40 | #include "containers/core/containers_list.h" |
41 | #include "containers/core/containers_uri.h" |
42 | |
43 | /****************************************************************************** |
44 | Configurable defines and constants. |
45 | ******************************************************************************/ |
46 | |
47 | /** Maximum number of tracks allowed in an RTSP reader */ |
48 | #define RTSP_TRACKS_MAX 4 |
49 | |
50 | /** Space for sending requests and receiving responses */ |
51 | #define COMMS_BUFFER_SIZE 2048 |
52 | |
53 | /** Largest allowed RTSP URI. Must be substantially smaller than COMMS_BUFFER_SIZE |
54 | * to allow for the headers that may be sent. */ |
55 | #define RTSP_URI_LENGTH_MAX 1024 |
56 | |
57 | /** Maximum allowed length for the Session: header recevied in a SETUP response, |
58 | * This is to ensure comms buffer is not overflowed. */ |
59 | #define 100 |
60 | |
61 | /** Number of milliseconds to block trying to read from the RTSP stream when no |
62 | * data is available from any of the tracks */ |
63 | #define DATA_UNAVAILABLE_READ_TIMEOUT_MS 1 |
64 | |
65 | /** Size of buffer for each track to use when receiving packets */ |
66 | #define UDP_READ_BUFFER_SIZE 520000 |
67 | |
68 | /* Arbitrary number of different dynamic ports to try */ |
69 | #define DYNAMIC_PORT_ATTEMPTS_MAX 16 |
70 | |
71 | /****************************************************************************** |
72 | Defines and constants. |
73 | ******************************************************************************/ |
74 | |
75 | #define RTSP_SCHEME "rtsp:" |
76 | #define RTP_SCHEME "rtp" |
77 | |
78 | /** The RTSP PKT scheme is used with test pkt files */ |
79 | #define RTSP_PKT_SCHEME "rtsppkt:" |
80 | |
81 | #define RTSP_NETWORK_URI_START "rtsp://" |
82 | #define RTSP_NETWORK_URI_START_LENGTH (sizeof(RTSP_NETWORK_URI_START)-1) |
83 | |
84 | /** Initial capacity of header list */ |
85 | #define 16 |
86 | |
87 | /** Format of the first line of an RTSP request */ |
88 | #define RTSP_REQUEST_LINE_FORMAT "%s %s RTSP/1.0\r\n" |
89 | |
90 | /** Format string for common headers used with all request methods. |
91 | * Note: includes double new line to terminate headers */ |
92 | #define "CSeq: %u\r\nConnection: Keep-Alive\r\nUser-Agent: Broadcom/1.0\r\n\r\n" |
93 | |
94 | /** Format for the Transport: header */ |
95 | #define "Transport: RTP/AVP;unicast;client_port=%hu-%hu;mode=play\r\n" |
96 | |
97 | /** Format for including Session: header. */ |
98 | #define "Session: %s\r\n" |
99 | |
100 | /** \name RTSP methods, used as the first item in the request line |
101 | * @{ */ |
102 | #define DESCRIBE_METHOD "DESCRIBE" |
103 | #define SETUP_METHOD "SETUP" |
104 | #define PLAY_METHOD "PLAY" |
105 | #define TEARDOWN_METHOD "TEARDOWN" |
106 | /* @} */ |
107 | |
108 | /** \name Names of headers used by the code |
109 | * @{ */ |
110 | #define ":" |
111 | #define CONTENT_LENGTH_NAME "Content-Length" |
112 | #define CONTENT_BASE_NAME "Content-Base" |
113 | #define CONTENT_LOCATION_NAME "Content-Location" |
114 | #define RTP_INFO_NAME "RTP-Info" |
115 | #define SESSION_NAME "Session" |
116 | /* @} */ |
117 | |
118 | /** Supported RTSP major version number */ |
119 | #define RTSP_MAJOR_VERSION 1 |
120 | /** Supported RTSP minor version number */ |
121 | #define RTSP_MINOR_VERSION 0 |
122 | |
123 | /** Lowest successful status code value */ |
124 | #define RTSP_STATUS_OK 200 |
125 | /** Next failure status code after the set of successful ones */ |
126 | #define RTSP_STATUS_MULTIPLE_CHOICES 300 |
127 | |
128 | /** Maximum size of a decimal string representation of a uint16_t, plus NUL */ |
129 | #define PORT_BUFFER_SIZE 6 |
130 | /** Start of private / dynamic port region */ |
131 | #define FIRST_DYNAMIC_PORT 0xC000 |
132 | /** End of private / dynamic port region */ |
133 | #define LAST_DYNAMIC_PORT 0xFFF0 |
134 | |
135 | /** Format of RTP track file extension */ |
136 | #define RTP_PATH_EXTENSION_FORMAT ".t%u.pkt" |
137 | /** Extra space need for creating an RTP track file name from an RTSP URI path */ |
138 | #define 17 |
139 | |
140 | /** \name RTP URI parameter names |
141 | * @{ */ |
142 | #define PAYLOAD_TYPE_NAME "rtppt" |
143 | #define MIME_TYPE_NAME "mime-type" |
144 | #define SAMPLE_RATE_NAME "rate" |
145 | #define CHANNELS_NAME "channels" |
146 | /* @} */ |
147 | |
148 | /** Largest signed 64-bit integer */ |
149 | #define MAXIMUM_INT64 (int64_t)((1ULL << 63) - 1) |
150 | |
151 | /****************************************************************************** |
152 | Type definitions |
153 | ******************************************************************************/ |
154 | |
155 | typedef int (*PARSE_IS_DELIMITER_FN_T)(int char_to_test); |
156 | |
157 | typedef struct |
158 | { |
159 | const char *; |
160 | char *; |
161 | } ; |
162 | |
163 | typedef struct VC_CONTAINER_TRACK_MODULE_T |
164 | { |
165 | VC_CONTAINER_T *reader; /**< RTP reader for track */ |
166 | VC_URI_PARTS_T *reader_uri; /**< URI built up from SDP and used to open reader */ |
167 | char *control_uri; /**< URI used to control track playback */ |
168 | char *; /**< Session header to be used when sending control requests */ |
169 | char *payload_type; /**< RTP payload type for track */ |
170 | char *media_type; /**< MIME type for track */ |
171 | VC_CONTAINER_PACKET_T info; /**< Latest track packet info block */ |
172 | unsigned short rtp_port; /**< UDP listener port being used in RTP reader */ |
173 | } VC_CONTAINER_TRACK_MODULE_T; |
174 | |
175 | typedef struct VC_CONTAINER_MODULE_T |
176 | { |
177 | VC_CONTAINER_TRACK_T *tracks[RTSP_TRACKS_MAX]; |
178 | char *comms_buffer; /**< Buffer used for sending and receiving RTSP messages */ |
179 | VC_CONTAINERS_LIST_T *; /**< Parsed response headers, pointing into comms buffer */ |
180 | uint32_t cseq_value; /**< CSeq header value for next request */ |
181 | uint16_t next_rtp_port; /**< Next RTP port to use when opening track reader */ |
182 | uint16_t media_item; /**< Current media item number during initialization */ |
183 | bool uri_has_network_info; /**< True if the RTSP URI contains network info */ |
184 | int64_t ts_base; /**< Base value for dts and pts */ |
185 | VC_CONTAINER_TRACK_MODULE_T *current_track; /**< Next track to be read, to keep info/data on same track */ |
186 | } VC_CONTAINER_MODULE_T; |
187 | |
188 | /****************************************************************************** |
189 | Function prototypes |
190 | ******************************************************************************/ |
191 | static int rtsp_header_comparator(const RTSP_HEADER_T *first, const RTSP_HEADER_T *second); |
192 | |
193 | VC_CONTAINER_STATUS_T rtsp_reader_open( VC_CONTAINER_T * ); |
194 | |
195 | /****************************************************************************** |
196 | Local Functions |
197 | ******************************************************************************/ |
198 | |
199 | /**************************************************************************//** |
200 | * Trim whitespace from the end and start of the string |
201 | * |
202 | * \param str String to be trimmed |
203 | * \return Trimmed string |
204 | */ |
205 | static char *rtsp_trim( char *str ) |
206 | { |
207 | char *trim = str + strlen(str); |
208 | |
209 | /* Search backwards for first non-whitespace */ |
210 | while (--trim >= str && isspace((int)*trim)) |
211 | ; /* Everything done in the while */ |
212 | trim[1] = '\0'; |
213 | |
214 | /* Now move start of string forwards to first non-whitespace */ |
215 | trim = str; |
216 | while (isspace((int)*trim)) |
217 | trim++; |
218 | |
219 | return trim; |
220 | } |
221 | |
222 | /**************************************************************************//** |
223 | * Send out the data in the comms buffer. |
224 | * |
225 | * @param p_ctx The reader context. |
226 | * @return The resulting status of the function. |
227 | */ |
228 | static VC_CONTAINER_STATUS_T rtsp_send( VC_CONTAINER_T *p_ctx ) |
229 | { |
230 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
231 | uint32_t to_write; |
232 | uint32_t written; |
233 | const char *buffer = module->comms_buffer; |
234 | |
235 | /* When reading from a captured file, do not attempt to send data */ |
236 | if (!module->uri_has_network_info) |
237 | return VC_CONTAINER_SUCCESS; |
238 | |
239 | to_write = strlen(buffer); |
240 | |
241 | while (to_write) |
242 | { |
243 | written = vc_container_io_write(p_ctx->priv->io, buffer, to_write); |
244 | if (!written) |
245 | break; |
246 | to_write -= written; |
247 | buffer += written; |
248 | } |
249 | |
250 | return p_ctx->priv->io->status; |
251 | } |
252 | |
253 | /**************************************************************************//** |
254 | * Send a DESCRIBE request to the RTSP server. |
255 | * |
256 | * @param p_ctx The reader context. |
257 | * @return The resulting status of the function. |
258 | */ |
259 | static VC_CONTAINER_STATUS_T rtsp_send_describe_request( VC_CONTAINER_T *p_ctx ) |
260 | { |
261 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
262 | char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; |
263 | char *uri = p_ctx->priv->io->uri; |
264 | |
265 | if (strlen(uri) > RTSP_URI_LENGTH_MAX) |
266 | { |
267 | LOG_ERROR(p_ctx, "RTSP: URI is too long (%d>%d)" , strlen(uri), RTSP_URI_LENGTH_MAX); |
268 | return VC_CONTAINER_ERROR_URI_OPEN_FAILED; |
269 | } |
270 | |
271 | ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, DESCRIBE_METHOD, uri); |
272 | if (ptr < end) |
273 | ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); |
274 | vc_container_assert(ptr < end); |
275 | |
276 | return rtsp_send(p_ctx); |
277 | } |
278 | |
279 | /**************************************************************************//** |
280 | * Send a SETUP request to the RTSP server. |
281 | * |
282 | * @param p_ctx The reader context. |
283 | * @param t_module The track module relating to the SETUP. |
284 | * @return The resulting status of the function. |
285 | */ |
286 | static VC_CONTAINER_STATUS_T rtsp_send_setup_request( VC_CONTAINER_T *p_ctx, |
287 | VC_CONTAINER_TRACK_MODULE_T *t_module) |
288 | { |
289 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
290 | char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; |
291 | char *uri = t_module->control_uri; |
292 | |
293 | if (strlen(uri) > RTSP_URI_LENGTH_MAX) |
294 | { |
295 | LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)" , strlen(uri), RTSP_URI_LENGTH_MAX); |
296 | return VC_CONTAINER_ERROR_URI_OPEN_FAILED; |
297 | } |
298 | |
299 | ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, SETUP_METHOD, uri); |
300 | if (ptr < end) |
301 | ptr += snprintf(ptr, end - ptr, TRANSPORT_HEADER_FORMAT, t_module->rtp_port, t_module->rtp_port + 1); |
302 | if (ptr < end) |
303 | ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); |
304 | vc_container_assert(ptr < end); |
305 | |
306 | return rtsp_send(p_ctx); |
307 | } |
308 | |
309 | /**************************************************************************//** |
310 | * Send a PLAY request to the RTSP server. |
311 | * |
312 | * @param p_ctx The reader context. |
313 | * @param t_module The track module relating to the PLAY. |
314 | * @return The resulting status of the function. |
315 | */ |
316 | static VC_CONTAINER_STATUS_T rtsp_send_play_request( VC_CONTAINER_T *p_ctx, |
317 | VC_CONTAINER_TRACK_MODULE_T *t_module ) |
318 | { |
319 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
320 | char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; |
321 | char *uri = t_module->control_uri; |
322 | |
323 | if (strlen(uri) > RTSP_URI_LENGTH_MAX) |
324 | { |
325 | LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)" , strlen(uri), RTSP_URI_LENGTH_MAX); |
326 | return VC_CONTAINER_ERROR_URI_OPEN_FAILED; |
327 | } |
328 | |
329 | ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, PLAY_METHOD, uri); |
330 | if (ptr < end) |
331 | ptr += snprintf(ptr, end - ptr, SESSION_HEADER_FORMAT, t_module->session_header); |
332 | if (ptr < end) |
333 | ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); |
334 | vc_container_assert(ptr < end); |
335 | |
336 | return rtsp_send(p_ctx); |
337 | } |
338 | |
339 | /**************************************************************************//** |
340 | * Send a TEARDOWN request to the RTSP server. |
341 | * |
342 | * @param p_ctx The reader context. |
343 | * @param t_module The track module relating to the SETUP. |
344 | * @return The resulting status of the function. |
345 | */ |
346 | static VC_CONTAINER_STATUS_T rtsp_send_teardown_request( VC_CONTAINER_T *p_ctx, |
347 | VC_CONTAINER_TRACK_MODULE_T *t_module ) |
348 | { |
349 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
350 | char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; |
351 | char *uri = t_module->control_uri; |
352 | |
353 | if (strlen(uri) > RTSP_URI_LENGTH_MAX) |
354 | { |
355 | LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)" , strlen(uri), RTSP_URI_LENGTH_MAX); |
356 | return VC_CONTAINER_ERROR_URI_OPEN_FAILED; |
357 | } |
358 | |
359 | ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, TEARDOWN_METHOD, uri); |
360 | if (ptr < end) |
361 | ptr += snprintf(ptr, end - ptr, SESSION_HEADER_FORMAT, t_module->session_header); |
362 | if (ptr < end) |
363 | ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); |
364 | vc_container_assert(ptr < end); |
365 | |
366 | return rtsp_send(p_ctx); |
367 | } |
368 | |
369 | /**************************************************************************//** |
370 | * Check a response status line to see if the response is usable or not. |
371 | * Reasons for invalidity include: |
372 | * - Incorrectly formatted |
373 | * - Unsupported version |
374 | * - Status code is not in the 2xx range |
375 | * |
376 | * @param p_ctx The reader context. |
377 | * @param status_line The response status line. |
378 | * @return The resulting status of the function. |
379 | */ |
380 | static bool rtsp_successful_response_status( VC_CONTAINER_T *p_ctx, |
381 | const char *status_line) |
382 | { |
383 | unsigned int major_version, minor_version, status_code; |
384 | |
385 | /* coverity[secure_coding] String is null-terminated */ |
386 | if (sscanf(status_line, "RTSP/%u.%u %u" , &major_version, &minor_version, &status_code) != 3) |
387 | { |
388 | LOG_ERROR(p_ctx, "RTSP: Invalid response status line:\n%s" , status_line); |
389 | return false; |
390 | } |
391 | |
392 | if (major_version != RTSP_MAJOR_VERSION || minor_version != RTSP_MINOR_VERSION) |
393 | { |
394 | LOG_ERROR(p_ctx, "RTSP: Unexpected response RTSP version: %u.%u" , major_version, minor_version); |
395 | return false; |
396 | } |
397 | |
398 | if (status_code < RTSP_STATUS_OK || status_code >= RTSP_STATUS_MULTIPLE_CHOICES) |
399 | { |
400 | LOG_ERROR(p_ctx, "RTSP: Response status unsuccessful:\n%s" , status_line); |
401 | return false; |
402 | } |
403 | |
404 | return true; |
405 | } |
406 | |
407 | /**************************************************************************//** |
408 | * Get the content length header from the response headers as an unsigned |
409 | * 32-bit integer. |
410 | * If the content length header is not found or badly formatted, zero is |
411 | * returned. |
412 | * |
413 | * @param header_list The response headers. |
414 | * @return The content length. |
415 | */ |
416 | static uint32_t rtsp_get_content_length( VC_CONTAINERS_LIST_T * ) |
417 | { |
418 | unsigned int content_length = 0; |
419 | RTSP_HEADER_T ; |
420 | |
421 | header.name = CONTENT_LENGTH_NAME; |
422 | if (header_list && vc_containers_list_find_entry(header_list, &header)) |
423 | /* coverity[secure_coding] String is null-terminated */ |
424 | sscanf(header.value, "%u" , &content_length); |
425 | |
426 | return content_length; |
427 | } |
428 | |
429 | /**************************************************************************//** |
430 | * Get the session header from the response headers. |
431 | * If the session header is not found, the empty string is returned. |
432 | * |
433 | * @param header_list The response headers. |
434 | * @return The session header. |
435 | */ |
436 | static const char *(VC_CONTAINERS_LIST_T *) |
437 | { |
438 | RTSP_HEADER_T ; |
439 | |
440 | header.name = SESSION_NAME; |
441 | if (header_list && vc_containers_list_find_entry(header_list, &header)) |
442 | return header.value; |
443 | |
444 | return "" ; |
445 | } |
446 | |
447 | /**************************************************************************//** |
448 | * Returns pointer to the string with any leading whitespace trimmed and |
449 | * terminated by a delimiter, as determined by is_delimiter_fn. The delimiter |
450 | * character replaced by NUL (to terminate the string) is returned in the |
451 | * variable pointed at by p_delimiter_replaced, if it is not NULL. |
452 | * |
453 | * The parse_str pointer is moved on to the character after the delimiter, or |
454 | * the end of the string if it is reached first. |
455 | * |
456 | * @param parse_str Pointer to the string pointer to parse. |
457 | * @param is_delimiter_fn Function to test if a character is a delimiter or not. |
458 | * @param p_delimiter_replaced Pointer to variable to receive delimiter character, or NULL. |
459 | * @return Pointer to extracted string. |
460 | */ |
461 | static char *(char **parse_str, |
462 | PARSE_IS_DELIMITER_FN_T is_delimiter_fn, |
463 | char *p_delimiter_replaced) |
464 | { |
465 | char *ptr; |
466 | char *result; |
467 | |
468 | vc_container_assert(parse_str); |
469 | vc_container_assert(*parse_str); |
470 | vc_container_assert(is_delimiter_fn); |
471 | |
472 | ptr = *parse_str; |
473 | |
474 | while (isspace((int)*ptr)) |
475 | ptr++; |
476 | |
477 | result = ptr; |
478 | |
479 | while (*ptr && !(*is_delimiter_fn)(*ptr)) |
480 | ptr++; |
481 | if (p_delimiter_replaced) |
482 | *p_delimiter_replaced = *ptr; |
483 | if (*ptr) |
484 | *ptr++ = '\0'; |
485 | |
486 | *parse_str = ptr; |
487 | return result; |
488 | } |
489 | |
490 | /**************************************************************************//** |
491 | * Specialised form of rtsp_parse_extract() where the delimiter is whitespace. |
492 | * Returns pointer to the string with any leading whitespace trimmed and |
493 | * terminated by further whitespace. |
494 | * |
495 | * The parse_str pointer is moved on to the character after the delimiter, or |
496 | * the end of the string if it is reached first. |
497 | * |
498 | * @param parse_str Pointer to the string pointer to parse. |
499 | * @return Pointer to extracted string. |
500 | */ |
501 | static char *(char **parse_str) |
502 | { |
503 | char *ptr; |
504 | char *result; |
505 | |
506 | vc_container_assert(parse_str); |
507 | vc_container_assert(*parse_str); |
508 | |
509 | ptr = *parse_str; |
510 | |
511 | while (isspace((int)*ptr)) |
512 | ptr++; |
513 | |
514 | result = ptr; |
515 | |
516 | while (*ptr && !isspace((int)*ptr)) |
517 | ptr++; |
518 | if (*ptr) |
519 | *ptr++ = '\0'; |
520 | |
521 | *parse_str = ptr; |
522 | return result; |
523 | } |
524 | |
525 | /**************************************************************************//** |
526 | * Returns whether the given character is a parameter name delimiter or not. |
527 | * |
528 | * @param char_to_test The character under test. |
529 | * @return True if the character is a name delimiter, false if not. |
530 | */ |
531 | static int name_delimiter_fn(int char_to_test) |
532 | { |
533 | switch (char_to_test) |
534 | { |
535 | case ' ': |
536 | case '\t': |
537 | case '=': |
538 | case ';': |
539 | return true; |
540 | default: |
541 | return false; |
542 | } |
543 | } |
544 | |
545 | /**************************************************************************//** |
546 | * Returns whether the given character is a parameter value delimiter or not. |
547 | * |
548 | * @param char_to_test The character under test. |
549 | * @return True if the character is a value delimiter, false if not. |
550 | */ |
551 | static int value_delimiter_fn(int char_to_test) |
552 | { |
553 | switch (char_to_test) |
554 | { |
555 | case ' ': |
556 | case '\t': |
557 | case ';': |
558 | return true; |
559 | default: |
560 | return false; |
561 | } |
562 | } |
563 | |
564 | /**************************************************************************//** |
565 | * Extract a name/value pair from a given string. |
566 | * Each pair consists of a name, optionally followed by '=' and a value, with |
567 | * optional whitespace around either or both name and value. The parameter is |
568 | * terminated by a semi-colon, ';'. |
569 | * |
570 | * The parse_str pointer is moved on to the next parameter, or the end of the |
571 | * string if that is reached first. |
572 | * |
573 | * Name can be empty if there are two consecutive semi-colons, or a trailing |
574 | * semi-colon. |
575 | * |
576 | * @param parse_str Pointer to the string pointer to be parsed. |
577 | * @param p_name Pointer to where name string pointer shall be written. |
578 | * @param p_value Pointer to where value string pointer shall be written. |
579 | * @return True if the name is not empty. |
580 | */ |
581 | static bool (char **parse_str, char **p_name, char **p_value) |
582 | { |
583 | char delimiter; |
584 | |
585 | vc_container_assert(parse_str); |
586 | vc_container_assert(*parse_str); |
587 | vc_container_assert(p_name); |
588 | vc_container_assert(p_value); |
589 | |
590 | /* General form of each parameter: |
591 | * <name>[=<value>] |
592 | * but allow for spaces before and after name and value */ |
593 | *p_name = rtsp_parse_extract(parse_str, name_delimiter_fn, &delimiter); |
594 | if (isspace((int)delimiter)) |
595 | { |
596 | /* Skip further spaces after parameter name */ |
597 | do { |
598 | delimiter = **parse_str; |
599 | if (delimiter) |
600 | (*parse_str)++; |
601 | } while (isspace((int)delimiter)); |
602 | } |
603 | |
604 | if (delimiter == '=') |
605 | { |
606 | /* Parameter value present (although may be empty) */ |
607 | *p_value = rtsp_parse_extract(parse_str, value_delimiter_fn, &delimiter); |
608 | if (isspace((int)delimiter)) |
609 | { |
610 | /* Skip spaces after parameter value */ |
611 | do { |
612 | delimiter = **parse_str; |
613 | if (delimiter) |
614 | (*parse_str)++; |
615 | } while (isspace((int)delimiter)); |
616 | } |
617 | } else { |
618 | *p_value = NULL; |
619 | } |
620 | |
621 | return (**p_name != '\0'); |
622 | } |
623 | |
624 | /**************************************************************************//** |
625 | * Parses RTP-Info header and stores relevant parts. |
626 | * |
627 | * @param header_list The response header list. |
628 | * @param t_module The track module relating to the response headers. |
629 | */ |
630 | static void rtsp_store_rtp_info(VC_CONTAINERS_LIST_T *, |
631 | VC_CONTAINER_TRACK_MODULE_T *t_module ) |
632 | { |
633 | RTSP_HEADER_T ; |
634 | char *ptr; |
635 | |
636 | header.name = RTP_INFO_NAME; |
637 | if (!vc_containers_list_find_entry(header_list, &header)) |
638 | return; |
639 | |
640 | ptr = header.value; |
641 | while (ptr && *ptr) |
642 | { |
643 | char *name; |
644 | char *value; |
645 | |
646 | if (!rtsp_parse_extract_parameter(&ptr, &name, &value)) |
647 | continue; |
648 | |
649 | if (strcasecmp(name, "rtptime" ) == 0) |
650 | { |
651 | unsigned int timestamp_base = 0; |
652 | |
653 | /* coverity[secure_coding] String is null-terminated */ |
654 | if (sscanf(value, "%u" , ×tamp_base) == 1) |
655 | (void)vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_SET_TIMESTAMP_BASE, timestamp_base); |
656 | } |
657 | else if (strcasecmp(name, "seq" ) == 0) |
658 | { |
659 | unsigned short int sequence_number = 0; |
660 | |
661 | /* coverity[secure_coding] String is null-terminated */ |
662 | if (sscanf(value, "%hu" , &sequence_number) == 1) |
663 | (void)vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_SET_NEXT_SEQUENCE_NUMBER, (uint32_t)sequence_number); |
664 | } |
665 | } |
666 | } |
667 | |
668 | /**************************************************************************//** |
669 | * Reads an RTSP response and parses it into headers and content. |
670 | * The headers and content remain stored in the comms buffer, but referenced |
671 | * by the module's header list. Content uses a special header name that cannot |
672 | * occur in the real headers. |
673 | * |
674 | * @param p_ctx The RTSP reader context. |
675 | * @return The resulting status of the function. |
676 | */ |
677 | static VC_CONTAINER_STATUS_T rtsp_read_response( VC_CONTAINER_T *p_ctx ) |
678 | { |
679 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
680 | VC_CONTAINER_IO_T *p_ctx_io = p_ctx->priv->io; |
681 | char *next_read = module->comms_buffer; |
682 | uint32_t space_available = COMMS_BUFFER_SIZE - 1; /* Allow for a NUL */ |
683 | uint32_t received; |
684 | char *ptr = next_read; |
685 | bool found_content = false; |
686 | RTSP_HEADER_T ; |
687 | |
688 | vc_containers_list_reset(module->header_list); |
689 | |
690 | /* Response status line doesn't need to be stored, just checked */ |
691 | header.name = NULL; |
692 | header.value = next_read; |
693 | |
694 | while (space_available) |
695 | { |
696 | received = vc_container_io_read(p_ctx_io, next_read, space_available); |
697 | if (p_ctx_io->status != VC_CONTAINER_SUCCESS) |
698 | break; |
699 | |
700 | next_read += received; |
701 | space_available -= received; |
702 | |
703 | while (!found_content && ptr < next_read) |
704 | { |
705 | switch (*ptr) |
706 | { |
707 | case ':': |
708 | if (header.value) |
709 | { |
710 | /* Just another character in the value */ |
711 | ptr++; |
712 | } else { |
713 | /* End of name, expect value next */ |
714 | *ptr++ = '\0'; |
715 | header.value = ptr; |
716 | } |
717 | break; |
718 | |
719 | case '\n': |
720 | if (header.value) |
721 | { |
722 | /* End of line while parsing the value part of the header, add name/value pair to list */ |
723 | *ptr++ = '\0'; |
724 | header.value = rtsp_trim(header.value); |
725 | if (header.name) |
726 | { |
727 | if (!vc_containers_list_insert(module->header_list, &header, false)) |
728 | { |
729 | LOG_ERROR(p_ctx, "RTSP: Failed to add <%s> header to list" , header.name); |
730 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
731 | } |
732 | } else { |
733 | /* Check response status line */ |
734 | if (!rtsp_successful_response_status(p_ctx, header.value)) |
735 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
736 | } |
737 | /* Ready for next header */ |
738 | header.name = ptr; |
739 | header.value = NULL; |
740 | } else { |
741 | uint32_t content_length; |
742 | |
743 | /* End of line while parsing the name of a header */ |
744 | *ptr++ = '\0'; |
745 | if (*header.name && *header.name != '\r') |
746 | { |
747 | /* A non-empty name is invalid, so fail */ |
748 | LOG_ERROR(p_ctx, "RTSP: Invalid name in header - no colon:\n%s" , header.name); |
749 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
750 | } |
751 | |
752 | /* An empty name signifies the start of the content has been found */ |
753 | found_content = true; |
754 | |
755 | /* Make a pseudo-header for the content and add it to the list */ |
756 | header.name = CONTENT_PSEUDOHEADER_NAME; |
757 | header.value = ptr; |
758 | if (!vc_containers_list_insert(module->header_list, &header, false)) |
759 | { |
760 | LOG_ERROR(p_ctx, "RTSP: Failed to add content pseudoheader to list" ); |
761 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
762 | } |
763 | |
764 | /* Calculate how much content there is left to read, based on Content-Length header */ |
765 | content_length = rtsp_get_content_length(module->header_list); |
766 | if (ptr + content_length < next_read) |
767 | { |
768 | /* Final content byte already present, with extra data after it */ |
769 | space_available = 0; |
770 | } else { |
771 | uint32_t content_to_read = content_length - (next_read - ptr); |
772 | |
773 | if (content_to_read >= space_available) |
774 | { |
775 | LOG_ERROR(p_ctx, "RTSP: Not enough room to read content" ); |
776 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
777 | } |
778 | |
779 | /* Restrict further reading to the number of content bytes left */ |
780 | space_available = content_to_read; |
781 | } |
782 | } |
783 | break; |
784 | |
785 | default: |
786 | /* Just another character in either the name or the value */ |
787 | ptr++; |
788 | } |
789 | } |
790 | } |
791 | |
792 | if (!space_available) |
793 | { |
794 | if (found_content) |
795 | { |
796 | /* Terminate content region */ |
797 | *next_read = '\0'; |
798 | } else { |
799 | /* Ran out of buffer space and never found the content */ |
800 | LOG_ERROR(p_ctx, "RTSP: Response header section too big / content missing" ); |
801 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
802 | } |
803 | } |
804 | |
805 | return p_ctx_io->status; |
806 | } |
807 | |
808 | /**************************************************************************//** |
809 | * Creates a new track from an SDP media field. |
810 | * Limitation: only the first payload type of the field is used. |
811 | * |
812 | * @param p_ctx The RTSP reader context. |
813 | * @param media The media field. |
814 | * @param p_track Pointer to the variable to receive the new track pointer. |
815 | * @return The resulting status of the function. |
816 | */ |
817 | static VC_CONTAINER_STATUS_T rtsp_create_track_for_media_field(VC_CONTAINER_T *p_ctx, |
818 | char *media, |
819 | VC_CONTAINER_TRACK_T **p_track ) |
820 | { |
821 | VC_CONTAINER_TRACK_T *track = NULL; |
822 | VC_CONTAINER_TRACK_MODULE_T *t_module = NULL; |
823 | char *ptr = media; |
824 | char *media_type; |
825 | char *rtp_port; |
826 | char *transport_type; |
827 | char *payload_type; |
828 | |
829 | *p_track = NULL; |
830 | if (p_ctx->tracks_num == RTSP_TRACKS_MAX) |
831 | { |
832 | LOG_DEBUG(p_ctx, "RTSP: Too many media items in SDP data, only %d are supported." , RTSP_TRACKS_MAX); |
833 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
834 | } |
835 | |
836 | /* Format of media item: |
837 | * m=<media type> <port> <transport> <payload type(s)> |
838 | * Only RTP/AVP transport and the first payload type are supported */ |
839 | media_type = rtsp_parse_extract_ws(&ptr); |
840 | rtp_port = rtsp_parse_extract_ws(&ptr); |
841 | transport_type = rtsp_parse_extract_ws(&ptr); |
842 | payload_type = rtsp_parse_extract_ws(&ptr); |
843 | if (!*media_type || !*rtp_port || strcmp(transport_type, "RTP/AVP" ) || !*payload_type) |
844 | { |
845 | LOG_ERROR(p_ctx, "RTSP: Failure to parse media field" ); |
846 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
847 | } |
848 | |
849 | track = vc_container_allocate_track(p_ctx, sizeof(VC_CONTAINER_TRACK_MODULE_T)); |
850 | if (!track) goto out_of_memory_error; |
851 | t_module = track->priv->module; |
852 | |
853 | /* If the port specifier is invalid, treat it as if it were zero */ |
854 | /* coverity[secure_coding] String is null-terminated */ |
855 | sscanf(rtp_port, "%hu" , &t_module->rtp_port); |
856 | t_module->payload_type = payload_type; |
857 | t_module->media_type = media_type; |
858 | |
859 | t_module->reader_uri = vc_uri_create(); |
860 | if (!t_module->reader_uri) goto out_of_memory_error; |
861 | if (!vc_uri_set_scheme(t_module->reader_uri, RTP_SCHEME)) goto out_of_memory_error; |
862 | if (!vc_uri_add_query(t_module->reader_uri, PAYLOAD_TYPE_NAME, payload_type)) goto out_of_memory_error; |
863 | |
864 | p_ctx->tracks[p_ctx->tracks_num++] = track; |
865 | *p_track = track; |
866 | return VC_CONTAINER_SUCCESS; |
867 | |
868 | out_of_memory_error: |
869 | if (track) |
870 | { |
871 | if (t_module->reader_uri) |
872 | vc_uri_release(t_module->reader_uri); |
873 | vc_container_free_track(p_ctx, track); |
874 | } |
875 | LOG_ERROR(p_ctx, "RTSP: Memory allocation failure creating track" ); |
876 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
877 | } |
878 | |
879 | /**************************************************************************//** |
880 | * Returns whether the given character is a slash or not. |
881 | * |
882 | * @param char_to_test The character under test. |
883 | * @return True if the character is a slash, false if not. |
884 | */ |
885 | static int slash_delimiter_fn(int char_to_test) |
886 | { |
887 | return char_to_test == '/'; |
888 | } |
889 | |
890 | /**************************************************************************//** |
891 | * Parse an rtpmap attribute and store values in the related track. |
892 | * |
893 | * @param p_ctx The RTSP reader context. |
894 | * @param track The track relating to the rtpmap. |
895 | * @param attribute The rtpmap attribute value. |
896 | * @return The resulting status of the function. |
897 | */ |
898 | static VC_CONTAINER_STATUS_T rtsp_parse_rtpmap_attribute( VC_CONTAINER_T *p_ctx, |
899 | VC_CONTAINER_TRACK_T *track, |
900 | char *attribute ) |
901 | { |
902 | VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; |
903 | char *ptr = attribute; |
904 | char *payload_type; |
905 | char *mime_sub_type; |
906 | char *sample_rate; |
907 | char *full_mime_type; |
908 | char *channels; |
909 | |
910 | /* rtpmap attribute format: |
911 | * <payload type> <MIME type>/<sample rate>[/<channels>] |
912 | * Payload type must match the one used in the media field */ |
913 | payload_type = rtsp_parse_extract_ws(&ptr); |
914 | if (strcmp(payload_type, t_module->payload_type)) |
915 | { |
916 | /* Ignore any unsupported secondary payload type attributes */ |
917 | LOG_DEBUG(p_ctx, "RTSP: Secondary payload type attribute - not supported" ); |
918 | return VC_CONTAINER_SUCCESS; |
919 | } |
920 | |
921 | mime_sub_type = rtsp_parse_extract(&ptr, slash_delimiter_fn, NULL); |
922 | if (!*mime_sub_type) |
923 | { |
924 | LOG_ERROR(p_ctx, "RTSP: rtpmap: MIME type missing" ); |
925 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
926 | } |
927 | |
928 | sample_rate = rtsp_parse_extract(&ptr, slash_delimiter_fn, NULL); |
929 | if (!*sample_rate) |
930 | { |
931 | LOG_ERROR(p_ctx, "RTSP: rtpmap: sample rate missing" ); |
932 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
933 | } |
934 | |
935 | full_mime_type = (char *)malloc(strlen(t_module->media_type) + strlen(mime_sub_type) + 2); |
936 | if (!full_mime_type) |
937 | { |
938 | LOG_ERROR(p_ctx, "RTSP: Failed to allocate space for full MIME type" ); |
939 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
940 | } |
941 | /* coverity[secure_coding] String has been allocated of the right size */ |
942 | sprintf(full_mime_type, "%s/%s" , t_module->media_type, mime_sub_type); |
943 | if (!vc_uri_add_query(t_module->reader_uri, MIME_TYPE_NAME, full_mime_type)) |
944 | { |
945 | free(full_mime_type); |
946 | LOG_ERROR(p_ctx, "RTSP: Failed to add MIME type to URI" ); |
947 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
948 | } |
949 | free(full_mime_type); |
950 | |
951 | if (!vc_uri_add_query(t_module->reader_uri, SAMPLE_RATE_NAME, sample_rate)) |
952 | { |
953 | LOG_ERROR(p_ctx, "RTSP: Failed to add sample rate to URI" ); |
954 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
955 | } |
956 | |
957 | /* Optional channels specifier */ |
958 | channels = rtsp_parse_extract_ws(&ptr); |
959 | if (*channels) |
960 | { |
961 | if (!vc_uri_add_query(t_module->reader_uri, CHANNELS_NAME, channels)) |
962 | { |
963 | LOG_ERROR(p_ctx, "RTSP: Failed to add channels to URI" ); |
964 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
965 | } |
966 | } |
967 | |
968 | return VC_CONTAINER_SUCCESS; |
969 | } |
970 | |
971 | /**************************************************************************//** |
972 | * Parse an fmtp attribute and store values in the related track. |
973 | * |
974 | * @param p_ctx The RTSP reader context. |
975 | * @param track The track relating to the fmtp. |
976 | * @param attribute The fmtp attribute value. |
977 | * @return The resulting status of the function. |
978 | */ |
979 | static VC_CONTAINER_STATUS_T rtsp_parse_fmtp_attribute( VC_CONTAINER_T *p_ctx, |
980 | VC_CONTAINER_TRACK_T *track, |
981 | char *attribute ) |
982 | { |
983 | VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; |
984 | char *ptr = attribute; |
985 | char *payload_type; |
986 | |
987 | /* fmtp attribute format: |
988 | * <payload type> <parameters> |
989 | * The payload type must match the first one in the media field, parameters |
990 | * are semi-colon separated and may have additional whitespace around them. */ |
991 | |
992 | payload_type = rtsp_parse_extract_ws(&ptr); |
993 | if (strcmp(payload_type, t_module->payload_type)) |
994 | { |
995 | /* Ignore any unsupported secondary payload type attributes */ |
996 | LOG_DEBUG(p_ctx, "RTSP: Secondary payload type attribute - not supported" ); |
997 | return VC_CONTAINER_SUCCESS; |
998 | } |
999 | |
1000 | while (*ptr) |
1001 | { |
1002 | char *name; |
1003 | char *value; |
1004 | |
1005 | /* Only add the parameter if the name was not empty. This avoids problems with |
1006 | * strings like ";;", ";" or ";=value;" */ |
1007 | if (rtsp_parse_extract_parameter(&ptr, &name, &value)) |
1008 | { |
1009 | if (!vc_uri_add_query(t_module->reader_uri, name, value)) |
1010 | { |
1011 | if (value) |
1012 | LOG_ERROR(p_ctx, "RTSP: Failed to add <%s>=<%s> query to URI" , name, value); |
1013 | else |
1014 | LOG_ERROR(p_ctx, "RTSP: Failed to add <%s> query to URI" , name); |
1015 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1016 | } |
1017 | } |
1018 | } |
1019 | |
1020 | return VC_CONTAINER_SUCCESS; |
1021 | } |
1022 | |
1023 | /**************************************************************************//** |
1024 | * Merge base URI and relative URI strings into a merged URI string. |
1025 | * Always creates a new string, even if the relative URI is actually absolute. |
1026 | * |
1027 | * @param p_ctx The RTSP reader context. |
1028 | * @param base_uri_str The base URI string. |
1029 | * @param relative_uri_str The relative URI string. |
1030 | * @param p_merged_uri_str Pointer to where to put the merged string pointer. |
1031 | * @return The resulting status of the function. |
1032 | */ |
1033 | static VC_CONTAINER_STATUS_T rtsp_merge_uris( VC_CONTAINER_T *p_ctx, |
1034 | const char *base_uri_str, |
1035 | const char *relative_uri_str, |
1036 | char **p_merged_uri_str) |
1037 | { |
1038 | VC_URI_PARTS_T *base_uri = NULL; |
1039 | VC_URI_PARTS_T *relative_uri = NULL; |
1040 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1041 | uint32_t merged_size; |
1042 | |
1043 | *p_merged_uri_str = NULL; |
1044 | relative_uri = vc_uri_create(); |
1045 | if (!relative_uri) goto tidy_up; |
1046 | if (!vc_uri_parse(relative_uri, relative_uri_str)) |
1047 | { |
1048 | status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
1049 | goto tidy_up; |
1050 | } |
1051 | |
1052 | if (vc_uri_scheme(relative_uri) != NULL) |
1053 | { |
1054 | /* URI is absolute, not relative, so return it as the merged URI */ |
1055 | size_t len = strlen(relative_uri_str); |
1056 | |
1057 | *p_merged_uri_str = (char *)malloc(len + 1); |
1058 | if (!*p_merged_uri_str) goto tidy_up; |
1059 | |
1060 | strncpy(*p_merged_uri_str, relative_uri_str, len); |
1061 | status = VC_CONTAINER_SUCCESS; |
1062 | goto tidy_up; |
1063 | } |
1064 | |
1065 | base_uri = vc_uri_create(); |
1066 | if (!base_uri) goto tidy_up; |
1067 | if (!vc_uri_parse(base_uri, base_uri_str)) |
1068 | { |
1069 | status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
1070 | goto tidy_up; |
1071 | } |
1072 | |
1073 | /* Build up merged URI in relative_uri, using base_uri as necessary */ |
1074 | if (!vc_uri_merge(base_uri, relative_uri)) goto tidy_up; |
1075 | |
1076 | merged_size = vc_uri_build(relative_uri, NULL, 0) + 1; |
1077 | *p_merged_uri_str = (char *)malloc(merged_size); |
1078 | if (!*p_merged_uri_str) goto tidy_up; |
1079 | |
1080 | vc_uri_build(relative_uri, *p_merged_uri_str, merged_size); |
1081 | |
1082 | status = VC_CONTAINER_SUCCESS; |
1083 | |
1084 | tidy_up: |
1085 | if (base_uri) vc_uri_release(base_uri); |
1086 | if (relative_uri) vc_uri_release(relative_uri); |
1087 | if (status != VC_CONTAINER_SUCCESS) |
1088 | LOG_ERROR(p_ctx, "RTSP: Error merging URIs: %d" , (int)status); |
1089 | return status; |
1090 | } |
1091 | |
1092 | /**************************************************************************//** |
1093 | * Parse a control attribute and store it as an absolute URI. |
1094 | * |
1095 | * @param p_ctx The RTSP reader context. |
1096 | * @param attribute The control attribute value. |
1097 | * @param base_uri_str The base URI string. |
1098 | * @param p_control_uri_str Pointer to where to put the control string pointer. |
1099 | * @return The resulting status of the function. |
1100 | */ |
1101 | static VC_CONTAINER_STATUS_T rtsp_parse_control_attribute( VC_CONTAINER_T *p_ctx, |
1102 | const char *attribute, |
1103 | const char *base_uri_str, |
1104 | char **p_control_uri_str) |
1105 | { |
1106 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1107 | |
1108 | /* control attribute format: |
1109 | * <control URI> |
1110 | * The control URI is either absolute or relative to the base URI. If the |
1111 | * control URI is just an asterisk, the control URI matches the base URI. */ |
1112 | |
1113 | if (!*attribute || strcmp(attribute, "*" ) == 0) |
1114 | { |
1115 | size_t len = strlen(base_uri_str); |
1116 | |
1117 | *p_control_uri_str = (char *)malloc(len + 1); |
1118 | if (!*p_control_uri_str) |
1119 | { |
1120 | LOG_ERROR(p_ctx, "RTSP: Failed to allocate control URI" ); |
1121 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1122 | } |
1123 | strncpy(*p_control_uri_str, base_uri_str, len); |
1124 | } else { |
1125 | status = rtsp_merge_uris(p_ctx, base_uri_str, attribute, p_control_uri_str); |
1126 | } |
1127 | |
1128 | return status; |
1129 | } |
1130 | |
1131 | /**************************************************************************//** |
1132 | * Open a reader for the track using the URI that has been generated. |
1133 | * |
1134 | * @param p_ctx The RTSP reader context. |
1135 | * @param t_module The track module for which a reader is needed. |
1136 | * @return The resulting status of the function. |
1137 | */ |
1138 | static VC_CONTAINER_STATUS_T rtsp_open_track_reader( VC_CONTAINER_T *p_ctx, |
1139 | VC_CONTAINER_TRACK_MODULE_T *t_module ) |
1140 | { |
1141 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1142 | uint32_t uri_buffer_size; |
1143 | char *uri_buffer; |
1144 | |
1145 | uri_buffer_size = vc_uri_build(t_module->reader_uri, NULL, 0) + 1; |
1146 | uri_buffer = (char *)malloc(uri_buffer_size); |
1147 | if (!uri_buffer) |
1148 | { |
1149 | LOG_ERROR(p_ctx, "RTSP: Failed to build RTP URI" ); |
1150 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1151 | } |
1152 | vc_uri_build(t_module->reader_uri, uri_buffer, uri_buffer_size); |
1153 | |
1154 | t_module->reader = vc_container_open_reader(uri_buffer, &status, NULL, NULL); |
1155 | free(uri_buffer); |
1156 | |
1157 | return status; |
1158 | } |
1159 | |
1160 | /**************************************************************************//** |
1161 | * Open a reader for the track using the network URI that has been generated. |
1162 | * |
1163 | * @param p_ctx The RTSP reader context. |
1164 | * @param t_module The track module for which a reader is needed. |
1165 | * @return The resulting status of the function. |
1166 | */ |
1167 | static VC_CONTAINER_STATUS_T rtsp_open_network_reader( VC_CONTAINER_T *p_ctx, |
1168 | VC_CONTAINER_TRACK_MODULE_T *t_module ) |
1169 | { |
1170 | char port[PORT_BUFFER_SIZE] = {0}; |
1171 | |
1172 | if (!t_module->rtp_port) |
1173 | { |
1174 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
1175 | |
1176 | t_module->rtp_port = module->next_rtp_port; |
1177 | if (t_module->rtp_port > LAST_DYNAMIC_PORT) |
1178 | { |
1179 | LOG_ERROR(p_ctx, "RTSP: Out of dynamic ports" ); |
1180 | return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; |
1181 | } |
1182 | |
1183 | module->next_rtp_port += 2; |
1184 | } |
1185 | |
1186 | snprintf(port, sizeof(port)-1, "%hu" , t_module->rtp_port); |
1187 | if (!vc_uri_set_port(t_module->reader_uri, port)) |
1188 | { |
1189 | LOG_ERROR(p_ctx, "RTSP: Failed to set track reader URI port" ); |
1190 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1191 | } |
1192 | |
1193 | return rtsp_open_track_reader(p_ctx, t_module); |
1194 | } |
1195 | |
1196 | /**************************************************************************//** |
1197 | * Open a reader for the track using the file URI that has been generated. |
1198 | * |
1199 | * @param p_ctx The RTSP reader context. |
1200 | * @param t_module The track module for which a reader is needed. |
1201 | * @return The resulting status of the function. |
1202 | */ |
1203 | static VC_CONTAINER_STATUS_T rtsp_open_file_reader( VC_CONTAINER_T *p_ctx, |
1204 | VC_CONTAINER_TRACK_MODULE_T *t_module ) |
1205 | { |
1206 | VC_CONTAINER_STATUS_T status; |
1207 | VC_URI_PARTS_T *rtsp_uri = NULL; |
1208 | const char *rtsp_path; |
1209 | int len; |
1210 | char *new_path = NULL; |
1211 | char *extension; |
1212 | |
1213 | /* Use the RTSP URI's path, with the extension changed to ".t<track>.pkt" |
1214 | * where <track> is the zero-based media item number. */ |
1215 | |
1216 | rtsp_uri = vc_uri_create(); |
1217 | if (!rtsp_uri) |
1218 | { |
1219 | LOG_ERROR(p_ctx, "RTSP: Failed to create RTSP URI" ); |
1220 | status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1221 | goto tidy_up; |
1222 | } |
1223 | |
1224 | if (!vc_uri_parse(rtsp_uri, p_ctx->priv->io->uri)) |
1225 | { |
1226 | LOG_ERROR(p_ctx, "RTSP: Failed to parse RTSP URI <%s>" , p_ctx->priv->io->uri); |
1227 | status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
1228 | goto tidy_up; |
1229 | } |
1230 | |
1231 | rtsp_path = vc_uri_path(rtsp_uri); |
1232 | if (!rtsp_path || !*rtsp_path) |
1233 | { |
1234 | LOG_ERROR(p_ctx, "RTSP: RTSP URI path missing <%s>" , p_ctx->priv->io->uri); |
1235 | status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
1236 | goto tidy_up; |
1237 | } |
1238 | |
1239 | len = strlen(rtsp_path); |
1240 | new_path = (char *)calloc(1, len + RTP_PATH_EXTRA + 1); |
1241 | if (!rtsp_uri) |
1242 | { |
1243 | LOG_ERROR(p_ctx, "RTSP: Failed to create buffer for RTP path" ); |
1244 | status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1245 | goto tidy_up; |
1246 | } |
1247 | |
1248 | strncpy(new_path, rtsp_path, len); |
1249 | extension = strrchr(new_path, '.'); /* Find extension, to replace it */ |
1250 | if (!extension) |
1251 | extension = new_path + strlen(new_path); /* No extension, so append instead */ |
1252 | |
1253 | snprintf(extension, len + RTP_PATH_EXTRA - (extension - new_path), |
1254 | RTP_PATH_EXTENSION_FORMAT, p_ctx->priv->module->media_item); |
1255 | if (!vc_uri_set_path(t_module->reader_uri, new_path)) |
1256 | { |
1257 | LOG_ERROR(p_ctx, "RTSP: Failed to store RTP path <%s>" , new_path); |
1258 | status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1259 | goto tidy_up; |
1260 | } |
1261 | |
1262 | free(new_path); |
1263 | vc_uri_release(rtsp_uri); |
1264 | |
1265 | return rtsp_open_track_reader(p_ctx, t_module); |
1266 | |
1267 | tidy_up: |
1268 | if (new_path) free(new_path); |
1269 | if (rtsp_uri) vc_uri_release(rtsp_uri); |
1270 | return status; |
1271 | } |
1272 | |
1273 | /**************************************************************************//** |
1274 | * Copy track information from the encapsulated track reader's track to the |
1275 | * RTSP track. |
1276 | * |
1277 | * @param p_ctx The RTSP reader context. |
1278 | * @param track The RTSP track requiring its information to be filled in. |
1279 | * @return The resulting status of the function. |
1280 | */ |
1281 | static VC_CONTAINER_STATUS_T rtsp_copy_track_data_from_reader( VC_CONTAINER_T *p_ctx, |
1282 | VC_CONTAINER_TRACK_T *track ) |
1283 | { |
1284 | VC_CONTAINER_T *reader = track->priv->module->reader; |
1285 | VC_CONTAINER_ES_FORMAT_T *src_format, *dst_format; |
1286 | |
1287 | if (reader->tracks_num != 1) |
1288 | { |
1289 | LOG_ERROR(p_ctx, "RTSP: Expected track reader to have one track, has %d" , reader->tracks_num); |
1290 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
1291 | } |
1292 | |
1293 | if (reader->tracks[0]->meta_num) |
1294 | { |
1295 | LOG_DEBUG(p_ctx, "RTSP: Track reader has meta data - not supported" ); |
1296 | } |
1297 | |
1298 | src_format = reader->tracks[0]->format; |
1299 | dst_format = track->format; |
1300 | |
1301 | /* Copy fields individually to avoid problems with pointers (type and extradata). */ |
1302 | dst_format->es_type = src_format->es_type; |
1303 | dst_format->codec = src_format->codec; |
1304 | dst_format->codec_variant = src_format->codec_variant; |
1305 | *dst_format->type = *src_format->type; |
1306 | dst_format->bitrate = src_format->bitrate; |
1307 | memcpy(dst_format->language, src_format->language, sizeof(dst_format->language)); |
1308 | dst_format->group_id = src_format->group_id; |
1309 | dst_format->flags = src_format->flags; |
1310 | |
1311 | if (src_format->extradata) |
1312 | { |
1313 | VC_CONTAINER_STATUS_T status; |
1314 | uint32_t = src_format->extradata_size; |
1315 | |
1316 | status = vc_container_track_allocate_extradata(p_ctx, track, extradata_size); |
1317 | if (status != VC_CONTAINER_SUCCESS) |
1318 | return status; |
1319 | |
1320 | memcpy(dst_format->extradata, src_format->extradata, extradata_size); |
1321 | dst_format->extradata_size = extradata_size; |
1322 | } |
1323 | |
1324 | track->is_enabled = reader->tracks[0]->is_enabled; |
1325 | |
1326 | return VC_CONTAINER_SUCCESS; |
1327 | } |
1328 | |
1329 | /**************************************************************************//** |
1330 | * Finalise the creation of the RTSP track by opening its reader and filling in |
1331 | * the track information. |
1332 | * |
1333 | * @param p_ctx The RTSP reader context. |
1334 | * @param track The RTSP track being finalised. |
1335 | * @return The resulting status of the function. |
1336 | */ |
1337 | static VC_CONTAINER_STATUS_T rtsp_complete_track( VC_CONTAINER_T *p_ctx, |
1338 | VC_CONTAINER_TRACK_T *track ) |
1339 | { |
1340 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
1341 | VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; |
1342 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1343 | |
1344 | if (!t_module->control_uri) |
1345 | { |
1346 | LOG_ERROR(p_ctx, "RTSP: Track control URI is missing" ); |
1347 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
1348 | } |
1349 | |
1350 | if (module->uri_has_network_info) |
1351 | { |
1352 | int ii; |
1353 | |
1354 | if (!vc_uri_set_host(t_module->reader_uri, "" )) |
1355 | { |
1356 | LOG_ERROR(p_ctx, "RTSP: Failed to set track reader URI host" ); |
1357 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1358 | } |
1359 | |
1360 | status = rtsp_open_network_reader(p_ctx, t_module); |
1361 | |
1362 | for (ii = 0; status == VC_CONTAINER_ERROR_URI_OPEN_FAILED && ii < DYNAMIC_PORT_ATTEMPTS_MAX; ii++) |
1363 | { |
1364 | /* Reset port to pick up next dynamic port */ |
1365 | t_module->rtp_port = 0; |
1366 | status = rtsp_open_network_reader(p_ctx, t_module); |
1367 | } |
1368 | |
1369 | /* Change I/O to non-blocking, so that tracks can be polled */ |
1370 | if (status == VC_CONTAINER_SUCCESS) |
1371 | status = vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, 0); |
1372 | /* Set a large read buffer, to avoid dropping bursts of large packets (e.g. hi-def video) */ |
1373 | if (status == VC_CONTAINER_SUCCESS) |
1374 | status = vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE, UDP_READ_BUFFER_SIZE); |
1375 | } else { |
1376 | status = rtsp_open_file_reader(p_ctx, t_module); |
1377 | } |
1378 | |
1379 | vc_uri_release(t_module->reader_uri); |
1380 | t_module->reader_uri = NULL; |
1381 | |
1382 | if (status == VC_CONTAINER_SUCCESS) |
1383 | status = rtsp_copy_track_data_from_reader(p_ctx, track); |
1384 | |
1385 | return status; |
1386 | } |
1387 | |
1388 | /**************************************************************************//** |
1389 | * Returns whether the given character is an attribute name delimiter or not. |
1390 | * |
1391 | * @param char_to_test The character under test. |
1392 | * @return True if the character is an attribute name delimiter, false if not. |
1393 | */ |
1394 | static int attribute_name_delimiter_fn(int char_to_test) |
1395 | { |
1396 | return (char_to_test == ':'); |
1397 | } |
1398 | |
1399 | /**************************************************************************//** |
1400 | * Create RTSP tracks from media fields in SDP formatted data. |
1401 | * |
1402 | * @param p_ctx The RTSP reader context. |
1403 | * @param sdp_buffer The SDP data. |
1404 | * @param base_uri The RTSP base URI. |
1405 | * @return The resulting status of the function. |
1406 | */ |
1407 | static VC_CONTAINER_STATUS_T rtsp_create_tracks_from_sdp( VC_CONTAINER_T *p_ctx, |
1408 | char *sdp_buffer, |
1409 | char *base_uri ) |
1410 | { |
1411 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1412 | VC_CONTAINER_TRACK_T *track = NULL; |
1413 | char *session_base_uri = base_uri; |
1414 | char *ptr; |
1415 | char *next_line_ptr; |
1416 | char *attribute; |
1417 | |
1418 | for (ptr = sdp_buffer; status == VC_CONTAINER_SUCCESS && *ptr; ptr = next_line_ptr) |
1419 | { |
1420 | /* Find end of line */ |
1421 | char field = *ptr; |
1422 | |
1423 | next_line_ptr = ptr; |
1424 | while (*next_line_ptr && *next_line_ptr != '\n') |
1425 | next_line_ptr++; |
1426 | |
1427 | /* Terminate line */ |
1428 | if (*next_line_ptr) |
1429 | *next_line_ptr++ = '\0'; |
1430 | |
1431 | /* The format of the line has to be "<field>=<value>" where <field> is a single |
1432 | * character. Ignore anything else. */ |
1433 | if (ptr[1] != '=') |
1434 | continue; |
1435 | ptr = rtsp_trim(ptr + 2); |
1436 | |
1437 | switch (field) |
1438 | { |
1439 | case 'm': |
1440 | /* Start of media item */ |
1441 | if (track) |
1442 | { |
1443 | /* Finish previous track */ |
1444 | status = rtsp_complete_track(p_ctx, track); |
1445 | track = NULL; |
1446 | p_ctx->priv->module->media_item++; |
1447 | if (status != VC_CONTAINER_SUCCESS) |
1448 | break; |
1449 | } |
1450 | |
1451 | status = rtsp_create_track_for_media_field(p_ctx, ptr, &track); |
1452 | break; |
1453 | case 'a': /* Attribute (either session or media level) */ |
1454 | /* Attributes of the form "a=<name>:<value>" */ |
1455 | attribute = rtsp_parse_extract(&ptr, attribute_name_delimiter_fn, NULL); |
1456 | |
1457 | if (track) |
1458 | { |
1459 | /* Media level attribute */ |
1460 | |
1461 | /* Look for known attributes */ |
1462 | if (strcmp(attribute, "rtpmap" ) == 0) |
1463 | status = rtsp_parse_rtpmap_attribute(p_ctx, track, ptr); |
1464 | else if (strcmp(attribute, "fmtp" ) == 0) |
1465 | status = rtsp_parse_fmtp_attribute(p_ctx, track, ptr); |
1466 | else if (strcmp(attribute, "control" ) == 0) |
1467 | { |
1468 | char **track_control_uri = &track->priv->module->control_uri; |
1469 | |
1470 | if (*track_control_uri) |
1471 | { |
1472 | free(*track_control_uri); |
1473 | *track_control_uri = NULL; |
1474 | } |
1475 | status = rtsp_parse_control_attribute(p_ctx, ptr, session_base_uri, track_control_uri); |
1476 | } |
1477 | /* Any other attributes are ignored */ |
1478 | } else { |
1479 | /* Session level attribute */ |
1480 | if (strcmp(attribute, "control" ) == 0) |
1481 | { |
1482 | /* Only need to change the session_base_uri if it differs from the base URI */ |
1483 | ptr = rtsp_trim(ptr); |
1484 | if (session_base_uri != base_uri) |
1485 | { |
1486 | free(session_base_uri); |
1487 | session_base_uri = base_uri; |
1488 | } |
1489 | if (strcmp(ptr, base_uri) != 0) |
1490 | status = rtsp_parse_control_attribute(p_ctx, ptr, base_uri, &session_base_uri); |
1491 | } |
1492 | } |
1493 | break; |
1494 | default: /* Ignore any other field names */ |
1495 | ; |
1496 | } |
1497 | } |
1498 | |
1499 | if (session_base_uri && session_base_uri != base_uri) |
1500 | free(session_base_uri); |
1501 | |
1502 | /* Having no media fields is an error, since there will be nothing to play back */ |
1503 | if (status == VC_CONTAINER_SUCCESS) |
1504 | { |
1505 | if (!p_ctx->tracks_num) |
1506 | status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
1507 | else if (track) |
1508 | { |
1509 | /* Finish final track */ |
1510 | status = rtsp_complete_track(p_ctx, track); |
1511 | p_ctx->priv->module->media_item++; |
1512 | } |
1513 | } |
1514 | |
1515 | return status; |
1516 | } |
1517 | |
1518 | /**************************************************************************//** |
1519 | * Create RTSP tracks from the response to a DESCRIBE request. |
1520 | * The response must have already been filled into the comms buffer and header |
1521 | * list. |
1522 | * |
1523 | * @param p_ctx The RTSP reader context. |
1524 | * @return The resulting status of the function. |
1525 | */ |
1526 | static VC_CONTAINER_STATUS_T rtsp_create_tracks_from_response( VC_CONTAINER_T *p_ctx ) |
1527 | { |
1528 | VC_CONTAINERS_LIST_T * = p_ctx->priv->module->header_list; |
1529 | RTSP_HEADER_T ; |
1530 | char *base_uri; |
1531 | char *content; |
1532 | |
1533 | header.name = CONTENT_PSEUDOHEADER_NAME; |
1534 | if (!vc_containers_list_find_entry(header_list, &header)) |
1535 | { |
1536 | LOG_ERROR(p_ctx, "RTSP: Content missing" ); |
1537 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
1538 | } |
1539 | content = header.value; |
1540 | |
1541 | /* The control URI may be relative to a base URI which is the first of these |
1542 | * that is available: |
1543 | * 1. Content-Base header |
1544 | * 2. Content-Location header |
1545 | * 3. Request URI |
1546 | */ |
1547 | header.name = CONTENT_BASE_NAME; |
1548 | if (vc_containers_list_find_entry(header_list, &header)) |
1549 | base_uri = header.value; |
1550 | else { |
1551 | header.name = CONTENT_LOCATION_NAME; |
1552 | if (vc_containers_list_find_entry(header_list, &header)) |
1553 | base_uri = header.value; |
1554 | else |
1555 | base_uri = p_ctx->priv->io->uri; |
1556 | } |
1557 | |
1558 | return rtsp_create_tracks_from_sdp(p_ctx, content, base_uri); |
1559 | } |
1560 | |
1561 | /**************************************************************************//** |
1562 | * Header comparison function. |
1563 | * Compare two header structures and return whether the first is less than, |
1564 | * equal to or greater than the second. |
1565 | * |
1566 | * @param first The first structure to be compared. |
1567 | * @param second The second structure to be compared. |
1568 | * @return Negative if first is less than second, positive if first is greater |
1569 | * and zero if they are equal. |
1570 | */ |
1571 | static int (const RTSP_HEADER_T *first, const RTSP_HEADER_T *second) |
1572 | { |
1573 | return strcasecmp(first->name, second->name); |
1574 | } |
1575 | |
1576 | /**************************************************************************//** |
1577 | * Make a DESCRIBE request to the server and create tracks from the response. |
1578 | * |
1579 | * @param p_ctx The RTSP reader context. |
1580 | * @return The resulting status of the function. |
1581 | */ |
1582 | static VC_CONTAINER_STATUS_T rtsp_describe( VC_CONTAINER_T *p_ctx ) |
1583 | { |
1584 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1585 | |
1586 | /* Send DESCRIBE request and get response */ |
1587 | status = rtsp_send_describe_request(p_ctx); |
1588 | if (status != VC_CONTAINER_SUCCESS) return status; |
1589 | status = rtsp_read_response(p_ctx); |
1590 | if (status != VC_CONTAINER_SUCCESS) return status; |
1591 | |
1592 | /* Create tracks from SDP content */ |
1593 | status = rtsp_create_tracks_from_response(p_ctx); |
1594 | |
1595 | return status; |
1596 | } |
1597 | |
1598 | /**************************************************************************//** |
1599 | * Make a SETUP request to the server and get session from response. |
1600 | * |
1601 | * @param p_ctx The RTSP reader context. |
1602 | * @param t_module The track module to be set up. |
1603 | * @return The resulting status of the function. |
1604 | */ |
1605 | static VC_CONTAINER_STATUS_T rtsp_setup( VC_CONTAINER_T *p_ctx, |
1606 | VC_CONTAINER_TRACK_MODULE_T *t_module ) |
1607 | { |
1608 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1609 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
1610 | const char *; |
1611 | size_t ; |
1612 | |
1613 | status = rtsp_send_setup_request(p_ctx, t_module); |
1614 | if (status != VC_CONTAINER_SUCCESS) return status; |
1615 | status = rtsp_read_response(p_ctx); |
1616 | if (status != VC_CONTAINER_SUCCESS) return status; |
1617 | |
1618 | session_header = rtsp_get_session_header(module->header_list); |
1619 | session_header_len = strlen(session_header); |
1620 | if (session_header_len > SESSION_HEADER_LENGTH_MAX) return VC_CONTAINER_ERROR_FORMAT_INVALID; |
1621 | |
1622 | t_module->session_header = (char *)malloc(session_header_len + 1); |
1623 | if (!t_module->session_header) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1624 | strncpy(t_module->session_header, session_header, session_header_len); |
1625 | |
1626 | return status; |
1627 | } |
1628 | |
1629 | /**************************************************************************//** |
1630 | * Make a SETUP request to the server and get session from response. |
1631 | * |
1632 | * @param p_ctx The RTSP reader context. |
1633 | * @param t_module The track module to be set up. |
1634 | * @return The resulting status of the function. |
1635 | */ |
1636 | static VC_CONTAINER_STATUS_T rtsp_play( VC_CONTAINER_T *p_ctx, |
1637 | VC_CONTAINER_TRACK_MODULE_T *t_module ) |
1638 | { |
1639 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1640 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
1641 | |
1642 | status = rtsp_send_play_request(p_ctx, t_module); |
1643 | if (status != VC_CONTAINER_SUCCESS) return status; |
1644 | status = rtsp_read_response(p_ctx); |
1645 | if (status != VC_CONTAINER_SUCCESS) return status; |
1646 | |
1647 | rtsp_store_rtp_info(module->header_list, t_module); |
1648 | |
1649 | return status; |
1650 | } |
1651 | |
1652 | /**************************************************************************//** |
1653 | * Blocking read/skip data from a container. |
1654 | * Can also be used to query information about the next block of data. |
1655 | * |
1656 | * @pre The container is set to non-blocking. |
1657 | * @post The container is set to non-blocking. |
1658 | * |
1659 | * @param p_ctx The reader context. |
1660 | * @param p_packet The container packet information, or NULL. |
1661 | * @param flags The container read flags. |
1662 | * @return The resulting status of the function. |
1663 | */ |
1664 | static VC_CONTAINER_STATUS_T rtsp_blocking_track_read(VC_CONTAINER_T *p_ctx, |
1665 | VC_CONTAINER_PACKET_T *p_packet, |
1666 | uint32_t flags) |
1667 | { |
1668 | VC_CONTAINER_STATUS_T status; |
1669 | |
1670 | status = vc_container_read(p_ctx, p_packet, flags); |
1671 | |
1672 | /* The ..._ABORTED status corresponds to a timeout waiting for data */ |
1673 | if (status == VC_CONTAINER_ERROR_ABORTED) |
1674 | { |
1675 | /* So switch to blocking temporarily to wait for some */ |
1676 | (void)vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, VC_CONTAINER_READ_TIMEOUT_BLOCK); |
1677 | status = vc_container_read(p_ctx, p_packet, flags); |
1678 | (void)vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, 0); |
1679 | } |
1680 | |
1681 | return status; |
1682 | } |
1683 | |
1684 | /**************************************************************************//** |
1685 | * Update the cached packet info blocks for all tracks. |
1686 | * If one or more of the tracks has data, set the current track to the one with |
1687 | * the earliest decode timestamp. |
1688 | * |
1689 | * @pre The track readers must not block when data is requested from them. |
1690 | * |
1691 | * @param p_ctx The RTSP reader context. |
1692 | * @return The resulting status of the function. |
1693 | */ |
1694 | static VC_CONTAINER_STATUS_T rtsp_update_track_info( VC_CONTAINER_T *p_ctx ) |
1695 | { |
1696 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1697 | uint32_t tracks_num = p_ctx->tracks_num; |
1698 | uint32_t track_idx; |
1699 | int64_t earliest_dts = MAXIMUM_INT64; |
1700 | VC_CONTAINER_TRACK_MODULE_T *earliest_track = NULL; |
1701 | |
1702 | /* Reset current track to unknown */ |
1703 | p_ctx->priv->module->current_track = NULL; |
1704 | |
1705 | /* Collect each track's info and return the one with earliest timestamp. */ |
1706 | for (track_idx = 0; track_idx < tracks_num; track_idx++) |
1707 | { |
1708 | VC_CONTAINER_TRACK_MODULE_T *t_module = p_ctx->tracks[track_idx]->priv->module; |
1709 | VC_CONTAINER_PACKET_T *info = &t_module->info; |
1710 | |
1711 | /* If this track has no data available, request more */ |
1712 | if (!info->size) |
1713 | { |
1714 | /* This is a non-blocking read, so status will be ..._ABORTED if nothing available */ |
1715 | status = vc_container_read(t_module->reader, info, VC_CONTAINER_READ_FLAG_INFO); |
1716 | /* Adjust track index to be the RTSP index instead of the RTP one */ |
1717 | info->track = track_idx; |
1718 | } |
1719 | |
1720 | if (status == VC_CONTAINER_SUCCESS) |
1721 | { |
1722 | if (info->dts < earliest_dts) |
1723 | { |
1724 | earliest_dts = info->dts; |
1725 | earliest_track = t_module; |
1726 | } |
1727 | } |
1728 | else if (status != VC_CONTAINER_ERROR_ABORTED) |
1729 | { |
1730 | /* Not a time-out failure, so abort */ |
1731 | return status; |
1732 | } |
1733 | } |
1734 | |
1735 | p_ctx->priv->module->current_track = earliest_track; |
1736 | |
1737 | return VC_CONTAINER_SUCCESS; |
1738 | } |
1739 | |
1740 | /***************************************************************************** |
1741 | Functions exported as part of the Container Module API |
1742 | *****************************************************************************/ |
1743 | |
1744 | /**************************************************************************//** |
1745 | * Read/skip data from the container. |
1746 | * Can also be used to query information about the next block of data. |
1747 | * |
1748 | * @param p_ctx The reader context. |
1749 | * @param p_packet The container packet information, or NULL. |
1750 | * @param flags The container read flags. |
1751 | * @return The resulting status of the function. |
1752 | */ |
1753 | static VC_CONTAINER_STATUS_T rtsp_reader_read( VC_CONTAINER_T *p_ctx, |
1754 | VC_CONTAINER_PACKET_T *p_packet, |
1755 | uint32_t flags ) |
1756 | { |
1757 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
1758 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1759 | VC_CONTAINER_TRACK_MODULE_T *current_track = module->current_track; |
1760 | VC_CONTAINER_PACKET_T *info; |
1761 | |
1762 | if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) |
1763 | { |
1764 | vc_container_assert(p_packet); |
1765 | vc_container_assert(p_packet->track < p_ctx->tracks_num); |
1766 | current_track = p_ctx->tracks[p_packet->track]->priv->module; |
1767 | module->current_track = current_track; |
1768 | |
1769 | if (!current_track->info.size) |
1770 | { |
1771 | status = rtsp_blocking_track_read(current_track->reader, ¤t_track->info, VC_CONTAINER_READ_FLAG_INFO); |
1772 | if (status != VC_CONTAINER_SUCCESS) |
1773 | goto error; |
1774 | } |
1775 | } |
1776 | else if (!current_track || !current_track->info.size) |
1777 | { |
1778 | status = rtsp_update_track_info(p_ctx); |
1779 | if (status != VC_CONTAINER_SUCCESS) |
1780 | goto error; |
1781 | |
1782 | while (!module->current_track) |
1783 | { |
1784 | /* Check RTSP stream to see if it has closed */ |
1785 | status = rtsp_read_response(p_ctx); |
1786 | if (status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_ABORTED) |
1787 | { |
1788 | /* No data from any track yet, so keep checking */ |
1789 | status = rtsp_update_track_info(p_ctx); |
1790 | } |
1791 | if (status != VC_CONTAINER_SUCCESS) |
1792 | goto error; |
1793 | } |
1794 | |
1795 | current_track = module->current_track; |
1796 | } |
1797 | |
1798 | info = ¤t_track->info; |
1799 | vc_container_assert(info->size); |
1800 | |
1801 | if (flags & VC_CONTAINER_READ_FLAG_INFO) |
1802 | { |
1803 | vc_container_assert(p_packet); |
1804 | memcpy(p_packet, info, sizeof(*info)); |
1805 | } else { |
1806 | status = rtsp_blocking_track_read(current_track->reader, p_packet, flags); |
1807 | if (status != VC_CONTAINER_SUCCESS) |
1808 | goto error; |
1809 | |
1810 | if (p_packet) |
1811 | { |
1812 | p_packet->track = info->track; |
1813 | |
1814 | if (flags & VC_CONTAINER_READ_FLAG_SKIP) |
1815 | { |
1816 | info->size = 0; |
1817 | } else { |
1818 | vc_container_assert(info->size >= p_packet->size); |
1819 | info->size -= p_packet->size; |
1820 | } |
1821 | } else { |
1822 | info->size = 0; |
1823 | } |
1824 | } |
1825 | |
1826 | if (p_packet) |
1827 | { |
1828 | /* Adjust timestamps to be relative to zero */ |
1829 | if (!module->ts_base) |
1830 | module->ts_base = p_packet->dts; |
1831 | p_packet->dts -= module->ts_base; |
1832 | p_packet->pts -= module->ts_base; |
1833 | } |
1834 | |
1835 | error: |
1836 | STREAM_STATUS(p_ctx) = status; |
1837 | return status; |
1838 | } |
1839 | |
1840 | /**************************************************************************//** |
1841 | * Seek over data in the container. |
1842 | * |
1843 | * @param p_ctx The reader context. |
1844 | * @param p_offset The seek offset. |
1845 | * @param mode The seek mode. |
1846 | * @param flags The seek flags. |
1847 | * @return The resulting status of the function. |
1848 | */ |
1849 | static VC_CONTAINER_STATUS_T rtsp_reader_seek( VC_CONTAINER_T *p_ctx, |
1850 | int64_t *p_offset, |
1851 | VC_CONTAINER_SEEK_MODE_T mode, |
1852 | VC_CONTAINER_SEEK_FLAGS_T flags) |
1853 | { |
1854 | VC_CONTAINER_PARAM_UNUSED(p_ctx); |
1855 | VC_CONTAINER_PARAM_UNUSED(p_offset); |
1856 | VC_CONTAINER_PARAM_UNUSED(mode); |
1857 | VC_CONTAINER_PARAM_UNUSED(flags); |
1858 | |
1859 | /* RTSP is a non-seekable container */ |
1860 | return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; |
1861 | } |
1862 | |
1863 | /**************************************************************************//** |
1864 | * Close the container. |
1865 | * |
1866 | * @param p_ctx The reader context. |
1867 | * @return The resulting status of the function. |
1868 | */ |
1869 | static VC_CONTAINER_STATUS_T rtsp_reader_close( VC_CONTAINER_T *p_ctx ) |
1870 | { |
1871 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
1872 | unsigned int i; |
1873 | |
1874 | for(i = 0; i < p_ctx->tracks_num; i++) |
1875 | { |
1876 | VC_CONTAINER_TRACK_MODULE_T *t_module = p_ctx->tracks[i]->priv->module; |
1877 | |
1878 | if (t_module->control_uri && t_module->session_header) |
1879 | { |
1880 | /* Send the teardown message and wait for a response, although it |
1881 | * isn't important whether it was successful or not. */ |
1882 | if (rtsp_send_teardown_request(p_ctx, t_module) == VC_CONTAINER_SUCCESS) |
1883 | (void)rtsp_read_response(p_ctx); |
1884 | } |
1885 | |
1886 | if (t_module->reader) |
1887 | vc_container_close(t_module->reader); |
1888 | if (t_module->reader_uri) |
1889 | vc_uri_release(t_module->reader_uri); |
1890 | if (t_module->control_uri) |
1891 | free(t_module->control_uri); |
1892 | if (t_module->session_header) |
1893 | free(t_module->session_header); |
1894 | vc_container_free_track(p_ctx, p_ctx->tracks[i]); /* Also need to close track's reader */ |
1895 | } |
1896 | p_ctx->tracks = NULL; |
1897 | p_ctx->tracks_num = 0; |
1898 | if (module) |
1899 | { |
1900 | if (module->comms_buffer) |
1901 | free(module->comms_buffer); |
1902 | if (module->header_list) |
1903 | vc_containers_list_destroy(module->header_list); |
1904 | free(module); |
1905 | } |
1906 | p_ctx->priv->module = 0; |
1907 | return VC_CONTAINER_SUCCESS; |
1908 | } |
1909 | |
1910 | /**************************************************************************//** |
1911 | * Open the container. |
1912 | * Uses the I/O URI and/or data to configure the container. |
1913 | * |
1914 | * @param p_ctx The reader context. |
1915 | * @return The resulting status of the function. |
1916 | */ |
1917 | VC_CONTAINER_STATUS_T rtsp_reader_open( VC_CONTAINER_T *p_ctx ) |
1918 | { |
1919 | VC_CONTAINER_MODULE_T *module = 0; |
1920 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
1921 | uint32_t ii; |
1922 | |
1923 | /* Check the URI scheme looks valid */ |
1924 | if (!vc_uri_scheme(p_ctx->priv->uri) || |
1925 | (strcasecmp(vc_uri_scheme(p_ctx->priv->uri), RTSP_SCHEME) && |
1926 | strcasecmp(vc_uri_scheme(p_ctx->priv->uri), RTSP_PKT_SCHEME))) |
1927 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
1928 | |
1929 | /* Allocate our context */ |
1930 | if ((module = (VC_CONTAINER_MODULE_T *)malloc(sizeof(VC_CONTAINER_MODULE_T))) == NULL) |
1931 | { |
1932 | status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1933 | goto error; |
1934 | } |
1935 | |
1936 | memset(module, 0, sizeof(*module)); |
1937 | p_ctx->priv->module = module; |
1938 | p_ctx->tracks = module->tracks; |
1939 | module->next_rtp_port = FIRST_DYNAMIC_PORT; |
1940 | module->cseq_value = 0; |
1941 | module->uri_has_network_info = |
1942 | (strncasecmp(p_ctx->priv->io->uri, RTSP_NETWORK_URI_START, RTSP_NETWORK_URI_START_LENGTH) == 0); |
1943 | module->comms_buffer = (char *)calloc(1, COMMS_BUFFER_SIZE+1); |
1944 | if (!module->comms_buffer) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } |
1945 | |
1946 | /* header_list will contain pointers into the response_buffer, so take care in re-use */ |
1947 | module->header_list = vc_containers_list_create(HEADER_LIST_INITIAL_CAPACITY, sizeof(RTSP_HEADER_T), |
1948 | (VC_CONTAINERS_LIST_COMPARATOR_T)rtsp_header_comparator); |
1949 | if (!module->header_list) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } |
1950 | |
1951 | status = rtsp_describe(p_ctx); |
1952 | for (ii = 0; status == VC_CONTAINER_SUCCESS && ii < p_ctx->tracks_num; ii++) |
1953 | status = rtsp_setup(p_ctx, p_ctx->tracks[ii]->priv->module); |
1954 | for (ii = 0; status == VC_CONTAINER_SUCCESS && ii < p_ctx->tracks_num; ii++) |
1955 | status = rtsp_play(p_ctx, p_ctx->tracks[ii]->priv->module); |
1956 | if (status != VC_CONTAINER_SUCCESS) |
1957 | goto error; |
1958 | |
1959 | /* Set the RTSP stream to block briefly, to allow polling for closure as well as to avoid spinning CPU */ |
1960 | vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, DATA_UNAVAILABLE_READ_TIMEOUT_MS); |
1961 | |
1962 | p_ctx->priv->pf_close = rtsp_reader_close; |
1963 | p_ctx->priv->pf_read = rtsp_reader_read; |
1964 | p_ctx->priv->pf_seek = rtsp_reader_seek; |
1965 | |
1966 | if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) goto error; |
1967 | return VC_CONTAINER_SUCCESS; |
1968 | |
1969 | error: |
1970 | if(status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_EOS) |
1971 | status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
1972 | LOG_DEBUG(p_ctx, "error opening RTSP stream (%i)" , status); |
1973 | rtsp_reader_close(p_ctx); |
1974 | return status; |
1975 | } |
1976 | |
1977 | /******************************************************************************** |
1978 | Entrypoint function |
1979 | ********************************************************************************/ |
1980 | |
1981 | #if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) |
1982 | # pragma weak reader_open rtsp_reader_open |
1983 | #endif |
1984 | |