/* Copyright (c) 2012, Broadcom Europe Ltd All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #define CONTAINER_IS_BIG_ENDIAN //#define ENABLE_CONTAINERS_LOG_FORMAT //#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE #define CONTAINER_HELPER_LOG_INDENT(a) 0 #include "containers/core/containers_private.h" #include "containers/core/containers_io_helpers.h" #include "containers/core/containers_utils.h" #include "containers/core/containers_logging.h" #include "containers/core/containers_list.h" #include "containers/core/containers_uri.h" /****************************************************************************** Configurable defines and constants. ******************************************************************************/ /** Maximum number of tracks allowed in an RTSP reader */ #define RTSP_TRACKS_MAX 4 /** Space for sending requests and receiving responses */ #define COMMS_BUFFER_SIZE 2048 /** Largest allowed RTSP URI. Must be substantially smaller than COMMS_BUFFER_SIZE * to allow for the headers that may be sent. */ #define RTSP_URI_LENGTH_MAX 1024 /** Maximum allowed length for the Session: header recevied in a SETUP response, * This is to ensure comms buffer is not overflowed. */ #define SESSION_HEADER_LENGTH_MAX 100 /** Number of milliseconds to block trying to read from the RTSP stream when no * data is available from any of the tracks */ #define DATA_UNAVAILABLE_READ_TIMEOUT_MS 1 /** Size of buffer for each track to use when receiving packets */ #define UDP_READ_BUFFER_SIZE 520000 /* Arbitrary number of different dynamic ports to try */ #define DYNAMIC_PORT_ATTEMPTS_MAX 16 /****************************************************************************** Defines and constants. ******************************************************************************/ #define RTSP_SCHEME "rtsp:" #define RTP_SCHEME "rtp" /** The RTSP PKT scheme is used with test pkt files */ #define RTSP_PKT_SCHEME "rtsppkt:" #define RTSP_NETWORK_URI_START "rtsp://" #define RTSP_NETWORK_URI_START_LENGTH (sizeof(RTSP_NETWORK_URI_START)-1) /** Initial capacity of header list */ #define HEADER_LIST_INITIAL_CAPACITY 16 /** Format of the first line of an RTSP request */ #define RTSP_REQUEST_LINE_FORMAT "%s %s RTSP/1.0\r\n" /** Format string for common headers used with all request methods. * Note: includes double new line to terminate headers */ #define TRAILING_HEADERS_FORMAT "CSeq: %u\r\nConnection: Keep-Alive\r\nUser-Agent: Broadcom/1.0\r\n\r\n" /** Format for the Transport: header */ #define TRANSPORT_HEADER_FORMAT "Transport: RTP/AVP;unicast;client_port=%hu-%hu;mode=play\r\n" /** Format for including Session: header. */ #define SESSION_HEADER_FORMAT "Session: %s\r\n" /** \name RTSP methods, used as the first item in the request line * @{ */ #define DESCRIBE_METHOD "DESCRIBE" #define SETUP_METHOD "SETUP" #define PLAY_METHOD "PLAY" #define TEARDOWN_METHOD "TEARDOWN" /* @} */ /** \name Names of headers used by the code * @{ */ #define CONTENT_PSEUDOHEADER_NAME ":" #define CONTENT_LENGTH_NAME "Content-Length" #define CONTENT_BASE_NAME "Content-Base" #define CONTENT_LOCATION_NAME "Content-Location" #define RTP_INFO_NAME "RTP-Info" #define SESSION_NAME "Session" /* @} */ /** Supported RTSP major version number */ #define RTSP_MAJOR_VERSION 1 /** Supported RTSP minor version number */ #define RTSP_MINOR_VERSION 0 /** Lowest successful status code value */ #define RTSP_STATUS_OK 200 /** Next failure status code after the set of successful ones */ #define RTSP_STATUS_MULTIPLE_CHOICES 300 /** Maximum size of a decimal string representation of a uint16_t, plus NUL */ #define PORT_BUFFER_SIZE 6 /** Start of private / dynamic port region */ #define FIRST_DYNAMIC_PORT 0xC000 /** End of private / dynamic port region */ #define LAST_DYNAMIC_PORT 0xFFF0 /** Format of RTP track file extension */ #define RTP_PATH_EXTENSION_FORMAT ".t%u.pkt" /** Extra space need for creating an RTP track file name from an RTSP URI path */ #define RTP_PATH_EXTRA 17 /** \name RTP URI parameter names * @{ */ #define PAYLOAD_TYPE_NAME "rtppt" #define MIME_TYPE_NAME "mime-type" #define SAMPLE_RATE_NAME "rate" #define CHANNELS_NAME "channels" /* @} */ /** Largest signed 64-bit integer */ #define MAXIMUM_INT64 (int64_t)((1ULL << 63) - 1) /****************************************************************************** Type definitions ******************************************************************************/ typedef int (*PARSE_IS_DELIMITER_FN_T)(int char_to_test); typedef struct rtsp_header_tag { const char *name; char *value; } RTSP_HEADER_T; typedef struct VC_CONTAINER_TRACK_MODULE_T { VC_CONTAINER_T *reader; /**< RTP reader for track */ VC_URI_PARTS_T *reader_uri; /**< URI built up from SDP and used to open reader */ char *control_uri; /**< URI used to control track playback */ char *session_header; /**< Session header to be used when sending control requests */ char *payload_type; /**< RTP payload type for track */ char *media_type; /**< MIME type for track */ VC_CONTAINER_PACKET_T info; /**< Latest track packet info block */ unsigned short rtp_port; /**< UDP listener port being used in RTP reader */ } VC_CONTAINER_TRACK_MODULE_T; typedef struct VC_CONTAINER_MODULE_T { VC_CONTAINER_TRACK_T *tracks[RTSP_TRACKS_MAX]; char *comms_buffer; /**< Buffer used for sending and receiving RTSP messages */ VC_CONTAINERS_LIST_T *header_list; /**< Parsed response headers, pointing into comms buffer */ uint32_t cseq_value; /**< CSeq header value for next request */ uint16_t next_rtp_port; /**< Next RTP port to use when opening track reader */ uint16_t media_item; /**< Current media item number during initialization */ bool uri_has_network_info; /**< True if the RTSP URI contains network info */ int64_t ts_base; /**< Base value for dts and pts */ VC_CONTAINER_TRACK_MODULE_T *current_track; /**< Next track to be read, to keep info/data on same track */ } VC_CONTAINER_MODULE_T; /****************************************************************************** Function prototypes ******************************************************************************/ static int rtsp_header_comparator(const RTSP_HEADER_T *first, const RTSP_HEADER_T *second); VC_CONTAINER_STATUS_T rtsp_reader_open( VC_CONTAINER_T * ); /****************************************************************************** Local Functions ******************************************************************************/ /**************************************************************************//** * Trim whitespace from the end and start of the string * * \param str String to be trimmed * \return Trimmed string */ static char *rtsp_trim( char *str ) { char *trim = str + strlen(str); /* Search backwards for first non-whitespace */ while (--trim >= str && isspace((int)*trim)) ; /* Everything done in the while */ trim[1] = '\0'; /* Now move start of string forwards to first non-whitespace */ trim = str; while (isspace((int)*trim)) trim++; return trim; } /**************************************************************************//** * Send out the data in the comms buffer. * * @param p_ctx The reader context. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_send( VC_CONTAINER_T *p_ctx ) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; uint32_t to_write; uint32_t written; const char *buffer = module->comms_buffer; /* When reading from a captured file, do not attempt to send data */ if (!module->uri_has_network_info) return VC_CONTAINER_SUCCESS; to_write = strlen(buffer); while (to_write) { written = vc_container_io_write(p_ctx->priv->io, buffer, to_write); if (!written) break; to_write -= written; buffer += written; } return p_ctx->priv->io->status; } /**************************************************************************//** * Send a DESCRIBE request to the RTSP server. * * @param p_ctx The reader context. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_send_describe_request( VC_CONTAINER_T *p_ctx ) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; char *uri = p_ctx->priv->io->uri; if (strlen(uri) > RTSP_URI_LENGTH_MAX) { LOG_ERROR(p_ctx, "RTSP: URI is too long (%d>%d)", strlen(uri), RTSP_URI_LENGTH_MAX); return VC_CONTAINER_ERROR_URI_OPEN_FAILED; } ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, DESCRIBE_METHOD, uri); if (ptr < end) ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); vc_container_assert(ptr < end); return rtsp_send(p_ctx); } /**************************************************************************//** * Send a SETUP request to the RTSP server. * * @param p_ctx The reader context. * @param t_module The track module relating to the SETUP. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_send_setup_request( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_MODULE_T *t_module) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; char *uri = t_module->control_uri; if (strlen(uri) > RTSP_URI_LENGTH_MAX) { LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)", strlen(uri), RTSP_URI_LENGTH_MAX); return VC_CONTAINER_ERROR_URI_OPEN_FAILED; } ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, SETUP_METHOD, uri); if (ptr < end) ptr += snprintf(ptr, end - ptr, TRANSPORT_HEADER_FORMAT, t_module->rtp_port, t_module->rtp_port + 1); if (ptr < end) ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); vc_container_assert(ptr < end); return rtsp_send(p_ctx); } /**************************************************************************//** * Send a PLAY request to the RTSP server. * * @param p_ctx The reader context. * @param t_module The track module relating to the PLAY. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_send_play_request( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_MODULE_T *t_module ) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; char *uri = t_module->control_uri; if (strlen(uri) > RTSP_URI_LENGTH_MAX) { LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)", strlen(uri), RTSP_URI_LENGTH_MAX); return VC_CONTAINER_ERROR_URI_OPEN_FAILED; } ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, PLAY_METHOD, uri); if (ptr < end) ptr += snprintf(ptr, end - ptr, SESSION_HEADER_FORMAT, t_module->session_header); if (ptr < end) ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); vc_container_assert(ptr < end); return rtsp_send(p_ctx); } /**************************************************************************//** * Send a TEARDOWN request to the RTSP server. * * @param p_ctx The reader context. * @param t_module The track module relating to the SETUP. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_send_teardown_request( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_MODULE_T *t_module ) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; char *uri = t_module->control_uri; if (strlen(uri) > RTSP_URI_LENGTH_MAX) { LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)", strlen(uri), RTSP_URI_LENGTH_MAX); return VC_CONTAINER_ERROR_URI_OPEN_FAILED; } ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, TEARDOWN_METHOD, uri); if (ptr < end) ptr += snprintf(ptr, end - ptr, SESSION_HEADER_FORMAT, t_module->session_header); if (ptr < end) ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); vc_container_assert(ptr < end); return rtsp_send(p_ctx); } /**************************************************************************//** * Check a response status line to see if the response is usable or not. * Reasons for invalidity include: * - Incorrectly formatted * - Unsupported version * - Status code is not in the 2xx range * * @param p_ctx The reader context. * @param status_line The response status line. * @return The resulting status of the function. */ static bool rtsp_successful_response_status( VC_CONTAINER_T *p_ctx, const char *status_line) { unsigned int major_version, minor_version, status_code; /* coverity[secure_coding] String is null-terminated */ if (sscanf(status_line, "RTSP/%u.%u %u", &major_version, &minor_version, &status_code) != 3) { LOG_ERROR(p_ctx, "RTSP: Invalid response status line:\n%s", status_line); return false; } if (major_version != RTSP_MAJOR_VERSION || minor_version != RTSP_MINOR_VERSION) { LOG_ERROR(p_ctx, "RTSP: Unexpected response RTSP version: %u.%u", major_version, minor_version); return false; } if (status_code < RTSP_STATUS_OK || status_code >= RTSP_STATUS_MULTIPLE_CHOICES) { LOG_ERROR(p_ctx, "RTSP: Response status unsuccessful:\n%s", status_line); return false; } return true; } /**************************************************************************//** * Get the content length header from the response headers as an unsigned * 32-bit integer. * If the content length header is not found or badly formatted, zero is * returned. * * @param header_list The response headers. * @return The content length. */ static uint32_t rtsp_get_content_length( VC_CONTAINERS_LIST_T *header_list ) { unsigned int content_length = 0; RTSP_HEADER_T header; header.name = CONTENT_LENGTH_NAME; if (header_list && vc_containers_list_find_entry(header_list, &header)) /* coverity[secure_coding] String is null-terminated */ sscanf(header.value, "%u", &content_length); return content_length; } /**************************************************************************//** * Get the session header from the response headers. * If the session header is not found, the empty string is returned. * * @param header_list The response headers. * @return The session header. */ static const char *rtsp_get_session_header(VC_CONTAINERS_LIST_T *header_list) { RTSP_HEADER_T header; header.name = SESSION_NAME; if (header_list && vc_containers_list_find_entry(header_list, &header)) return header.value; return ""; } /**************************************************************************//** * Returns pointer to the string with any leading whitespace trimmed and * terminated by a delimiter, as determined by is_delimiter_fn. The delimiter * character replaced by NUL (to terminate the string) is returned in the * variable pointed at by p_delimiter_replaced, if it is not NULL. * * The parse_str pointer is moved on to the character after the delimiter, or * the end of the string if it is reached first. * * @param parse_str Pointer to the string pointer to parse. * @param is_delimiter_fn Function to test if a character is a delimiter or not. * @param p_delimiter_replaced Pointer to variable to receive delimiter character, or NULL. * @return Pointer to extracted string. */ static char *rtsp_parse_extract(char **parse_str, PARSE_IS_DELIMITER_FN_T is_delimiter_fn, char *p_delimiter_replaced) { char *ptr; char *result; vc_container_assert(parse_str); vc_container_assert(*parse_str); vc_container_assert(is_delimiter_fn); ptr = *parse_str; while (isspace((int)*ptr)) ptr++; result = ptr; while (*ptr && !(*is_delimiter_fn)(*ptr)) ptr++; if (p_delimiter_replaced) *p_delimiter_replaced = *ptr; if (*ptr) *ptr++ = '\0'; *parse_str = ptr; return result; } /**************************************************************************//** * Specialised form of rtsp_parse_extract() where the delimiter is whitespace. * Returns pointer to the string with any leading whitespace trimmed and * terminated by further whitespace. * * The parse_str pointer is moved on to the character after the delimiter, or * the end of the string if it is reached first. * * @param parse_str Pointer to the string pointer to parse. * @return Pointer to extracted string. */ static char *rtsp_parse_extract_ws(char **parse_str) { char *ptr; char *result; vc_container_assert(parse_str); vc_container_assert(*parse_str); ptr = *parse_str; while (isspace((int)*ptr)) ptr++; result = ptr; while (*ptr && !isspace((int)*ptr)) ptr++; if (*ptr) *ptr++ = '\0'; *parse_str = ptr; return result; } /**************************************************************************//** * Returns whether the given character is a parameter name delimiter or not. * * @param char_to_test The character under test. * @return True if the character is a name delimiter, false if not. */ static int name_delimiter_fn(int char_to_test) { switch (char_to_test) { case ' ': case '\t': case '=': case ';': return true; default: return false; } } /**************************************************************************//** * Returns whether the given character is a parameter value delimiter or not. * * @param char_to_test The character under test. * @return True if the character is a value delimiter, false if not. */ static int value_delimiter_fn(int char_to_test) { switch (char_to_test) { case ' ': case '\t': case ';': return true; default: return false; } } /**************************************************************************//** * Extract a name/value pair from a given string. * Each pair consists of a name, optionally followed by '=' and a value, with * optional whitespace around either or both name and value. The parameter is * terminated by a semi-colon, ';'. * * The parse_str pointer is moved on to the next parameter, or the end of the * string if that is reached first. * * Name can be empty if there are two consecutive semi-colons, or a trailing * semi-colon. * * @param parse_str Pointer to the string pointer to be parsed. * @param p_name Pointer to where name string pointer shall be written. * @param p_value Pointer to where value string pointer shall be written. * @return True if the name is not empty. */ static bool rtsp_parse_extract_parameter(char **parse_str, char **p_name, char **p_value) { char delimiter; vc_container_assert(parse_str); vc_container_assert(*parse_str); vc_container_assert(p_name); vc_container_assert(p_value); /* General form of each parameter: * [=] * but allow for spaces before and after name and value */ *p_name = rtsp_parse_extract(parse_str, name_delimiter_fn, &delimiter); if (isspace((int)delimiter)) { /* Skip further spaces after parameter name */ do { delimiter = **parse_str; if (delimiter) (*parse_str)++; } while (isspace((int)delimiter)); } if (delimiter == '=') { /* Parameter value present (although may be empty) */ *p_value = rtsp_parse_extract(parse_str, value_delimiter_fn, &delimiter); if (isspace((int)delimiter)) { /* Skip spaces after parameter value */ do { delimiter = **parse_str; if (delimiter) (*parse_str)++; } while (isspace((int)delimiter)); } } else { *p_value = NULL; } return (**p_name != '\0'); } /**************************************************************************//** * Parses RTP-Info header and stores relevant parts. * * @param header_list The response header list. * @param t_module The track module relating to the response headers. */ static void rtsp_store_rtp_info(VC_CONTAINERS_LIST_T *header_list, VC_CONTAINER_TRACK_MODULE_T *t_module ) { RTSP_HEADER_T header; char *ptr; header.name = RTP_INFO_NAME; if (!vc_containers_list_find_entry(header_list, &header)) return; ptr = header.value; while (ptr && *ptr) { char *name; char *value; if (!rtsp_parse_extract_parameter(&ptr, &name, &value)) continue; if (strcasecmp(name, "rtptime") == 0) { unsigned int timestamp_base = 0; /* coverity[secure_coding] String is null-terminated */ if (sscanf(value, "%u", ×tamp_base) == 1) (void)vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_SET_TIMESTAMP_BASE, timestamp_base); } else if (strcasecmp(name, "seq") == 0) { unsigned short int sequence_number = 0; /* coverity[secure_coding] String is null-terminated */ if (sscanf(value, "%hu", &sequence_number) == 1) (void)vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_SET_NEXT_SEQUENCE_NUMBER, (uint32_t)sequence_number); } } } /**************************************************************************//** * Reads an RTSP response and parses it into headers and content. * The headers and content remain stored in the comms buffer, but referenced * by the module's header list. Content uses a special header name that cannot * occur in the real headers. * * @param p_ctx The RTSP reader context. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_read_response( VC_CONTAINER_T *p_ctx ) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; VC_CONTAINER_IO_T *p_ctx_io = p_ctx->priv->io; char *next_read = module->comms_buffer; uint32_t space_available = COMMS_BUFFER_SIZE - 1; /* Allow for a NUL */ uint32_t received; char *ptr = next_read; bool found_content = false; RTSP_HEADER_T header; vc_containers_list_reset(module->header_list); /* Response status line doesn't need to be stored, just checked */ header.name = NULL; header.value = next_read; while (space_available) { received = vc_container_io_read(p_ctx_io, next_read, space_available); if (p_ctx_io->status != VC_CONTAINER_SUCCESS) break; next_read += received; space_available -= received; while (!found_content && ptr < next_read) { switch (*ptr) { case ':': if (header.value) { /* Just another character in the value */ ptr++; } else { /* End of name, expect value next */ *ptr++ = '\0'; header.value = ptr; } break; case '\n': if (header.value) { /* End of line while parsing the value part of the header, add name/value pair to list */ *ptr++ = '\0'; header.value = rtsp_trim(header.value); if (header.name) { if (!vc_containers_list_insert(module->header_list, &header, false)) { LOG_ERROR(p_ctx, "RTSP: Failed to add <%s> header to list", header.name); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } } else { /* Check response status line */ if (!rtsp_successful_response_status(p_ctx, header.value)) return VC_CONTAINER_ERROR_FORMAT_INVALID; } /* Ready for next header */ header.name = ptr; header.value = NULL; } else { uint32_t content_length; /* End of line while parsing the name of a header */ *ptr++ = '\0'; if (*header.name && *header.name != '\r') { /* A non-empty name is invalid, so fail */ LOG_ERROR(p_ctx, "RTSP: Invalid name in header - no colon:\n%s", header.name); return VC_CONTAINER_ERROR_FORMAT_INVALID; } /* An empty name signifies the start of the content has been found */ found_content = true; /* Make a pseudo-header for the content and add it to the list */ header.name = CONTENT_PSEUDOHEADER_NAME; header.value = ptr; if (!vc_containers_list_insert(module->header_list, &header, false)) { LOG_ERROR(p_ctx, "RTSP: Failed to add content pseudoheader to list"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } /* Calculate how much content there is left to read, based on Content-Length header */ content_length = rtsp_get_content_length(module->header_list); if (ptr + content_length < next_read) { /* Final content byte already present, with extra data after it */ space_available = 0; } else { uint32_t content_to_read = content_length - (next_read - ptr); if (content_to_read >= space_available) { LOG_ERROR(p_ctx, "RTSP: Not enough room to read content"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } /* Restrict further reading to the number of content bytes left */ space_available = content_to_read; } } break; default: /* Just another character in either the name or the value */ ptr++; } } } if (!space_available) { if (found_content) { /* Terminate content region */ *next_read = '\0'; } else { /* Ran out of buffer space and never found the content */ LOG_ERROR(p_ctx, "RTSP: Response header section too big / content missing"); return VC_CONTAINER_ERROR_FORMAT_INVALID; } } return p_ctx_io->status; } /**************************************************************************//** * Creates a new track from an SDP media field. * Limitation: only the first payload type of the field is used. * * @param p_ctx The RTSP reader context. * @param media The media field. * @param p_track Pointer to the variable to receive the new track pointer. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_create_track_for_media_field(VC_CONTAINER_T *p_ctx, char *media, VC_CONTAINER_TRACK_T **p_track ) { VC_CONTAINER_TRACK_T *track = NULL; VC_CONTAINER_TRACK_MODULE_T *t_module = NULL; char *ptr = media; char *media_type; char *rtp_port; char *transport_type; char *payload_type; *p_track = NULL; if (p_ctx->tracks_num == RTSP_TRACKS_MAX) { LOG_DEBUG(p_ctx, "RTSP: Too many media items in SDP data, only %d are supported.", RTSP_TRACKS_MAX); return VC_CONTAINER_ERROR_FORMAT_INVALID; } /* Format of media item: * m= * Only RTP/AVP transport and the first payload type are supported */ media_type = rtsp_parse_extract_ws(&ptr); rtp_port = rtsp_parse_extract_ws(&ptr); transport_type = rtsp_parse_extract_ws(&ptr); payload_type = rtsp_parse_extract_ws(&ptr); if (!*media_type || !*rtp_port || strcmp(transport_type, "RTP/AVP") || !*payload_type) { LOG_ERROR(p_ctx, "RTSP: Failure to parse media field"); return VC_CONTAINER_ERROR_FORMAT_INVALID; } track = vc_container_allocate_track(p_ctx, sizeof(VC_CONTAINER_TRACK_MODULE_T)); if (!track) goto out_of_memory_error; t_module = track->priv->module; /* If the port specifier is invalid, treat it as if it were zero */ /* coverity[secure_coding] String is null-terminated */ sscanf(rtp_port, "%hu", &t_module->rtp_port); t_module->payload_type = payload_type; t_module->media_type = media_type; t_module->reader_uri = vc_uri_create(); if (!t_module->reader_uri) goto out_of_memory_error; if (!vc_uri_set_scheme(t_module->reader_uri, RTP_SCHEME)) goto out_of_memory_error; if (!vc_uri_add_query(t_module->reader_uri, PAYLOAD_TYPE_NAME, payload_type)) goto out_of_memory_error; p_ctx->tracks[p_ctx->tracks_num++] = track; *p_track = track; return VC_CONTAINER_SUCCESS; out_of_memory_error: if (track) { if (t_module->reader_uri) vc_uri_release(t_module->reader_uri); vc_container_free_track(p_ctx, track); } LOG_ERROR(p_ctx, "RTSP: Memory allocation failure creating track"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } /**************************************************************************//** * Returns whether the given character is a slash or not. * * @param char_to_test The character under test. * @return True if the character is a slash, false if not. */ static int slash_delimiter_fn(int char_to_test) { return char_to_test == '/'; } /**************************************************************************//** * Parse an rtpmap attribute and store values in the related track. * * @param p_ctx The RTSP reader context. * @param track The track relating to the rtpmap. * @param attribute The rtpmap attribute value. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_parse_rtpmap_attribute( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, char *attribute ) { VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; char *ptr = attribute; char *payload_type; char *mime_sub_type; char *sample_rate; char *full_mime_type; char *channels; /* rtpmap attribute format: * /[/] * Payload type must match the one used in the media field */ payload_type = rtsp_parse_extract_ws(&ptr); if (strcmp(payload_type, t_module->payload_type)) { /* Ignore any unsupported secondary payload type attributes */ LOG_DEBUG(p_ctx, "RTSP: Secondary payload type attribute - not supported"); return VC_CONTAINER_SUCCESS; } mime_sub_type = rtsp_parse_extract(&ptr, slash_delimiter_fn, NULL); if (!*mime_sub_type) { LOG_ERROR(p_ctx, "RTSP: rtpmap: MIME type missing"); return VC_CONTAINER_ERROR_FORMAT_INVALID; } sample_rate = rtsp_parse_extract(&ptr, slash_delimiter_fn, NULL); if (!*sample_rate) { LOG_ERROR(p_ctx, "RTSP: rtpmap: sample rate missing"); return VC_CONTAINER_ERROR_FORMAT_INVALID; } full_mime_type = (char *)malloc(strlen(t_module->media_type) + strlen(mime_sub_type) + 2); if (!full_mime_type) { LOG_ERROR(p_ctx, "RTSP: Failed to allocate space for full MIME type"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } /* coverity[secure_coding] String has been allocated of the right size */ sprintf(full_mime_type, "%s/%s", t_module->media_type, mime_sub_type); if (!vc_uri_add_query(t_module->reader_uri, MIME_TYPE_NAME, full_mime_type)) { free(full_mime_type); LOG_ERROR(p_ctx, "RTSP: Failed to add MIME type to URI"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } free(full_mime_type); if (!vc_uri_add_query(t_module->reader_uri, SAMPLE_RATE_NAME, sample_rate)) { LOG_ERROR(p_ctx, "RTSP: Failed to add sample rate to URI"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } /* Optional channels specifier */ channels = rtsp_parse_extract_ws(&ptr); if (*channels) { if (!vc_uri_add_query(t_module->reader_uri, CHANNELS_NAME, channels)) { LOG_ERROR(p_ctx, "RTSP: Failed to add channels to URI"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } } return VC_CONTAINER_SUCCESS; } /**************************************************************************//** * Parse an fmtp attribute and store values in the related track. * * @param p_ctx The RTSP reader context. * @param track The track relating to the fmtp. * @param attribute The fmtp attribute value. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_parse_fmtp_attribute( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, char *attribute ) { VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; char *ptr = attribute; char *payload_type; /* fmtp attribute format: * * The payload type must match the first one in the media field, parameters * are semi-colon separated and may have additional whitespace around them. */ payload_type = rtsp_parse_extract_ws(&ptr); if (strcmp(payload_type, t_module->payload_type)) { /* Ignore any unsupported secondary payload type attributes */ LOG_DEBUG(p_ctx, "RTSP: Secondary payload type attribute - not supported"); return VC_CONTAINER_SUCCESS; } while (*ptr) { char *name; char *value; /* Only add the parameter if the name was not empty. This avoids problems with * strings like ";;", ";" or ";=value;" */ if (rtsp_parse_extract_parameter(&ptr, &name, &value)) { if (!vc_uri_add_query(t_module->reader_uri, name, value)) { if (value) LOG_ERROR(p_ctx, "RTSP: Failed to add <%s>=<%s> query to URI", name, value); else LOG_ERROR(p_ctx, "RTSP: Failed to add <%s> query to URI", name); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } } } return VC_CONTAINER_SUCCESS; } /**************************************************************************//** * Merge base URI and relative URI strings into a merged URI string. * Always creates a new string, even if the relative URI is actually absolute. * * @param p_ctx The RTSP reader context. * @param base_uri_str The base URI string. * @param relative_uri_str The relative URI string. * @param p_merged_uri_str Pointer to where to put the merged string pointer. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_merge_uris( VC_CONTAINER_T *p_ctx, const char *base_uri_str, const char *relative_uri_str, char **p_merged_uri_str) { VC_URI_PARTS_T *base_uri = NULL; VC_URI_PARTS_T *relative_uri = NULL; VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; uint32_t merged_size; *p_merged_uri_str = NULL; relative_uri = vc_uri_create(); if (!relative_uri) goto tidy_up; if (!vc_uri_parse(relative_uri, relative_uri_str)) { status = VC_CONTAINER_ERROR_FORMAT_INVALID; goto tidy_up; } if (vc_uri_scheme(relative_uri) != NULL) { /* URI is absolute, not relative, so return it as the merged URI */ size_t len = strlen(relative_uri_str); *p_merged_uri_str = (char *)malloc(len + 1); if (!*p_merged_uri_str) goto tidy_up; strncpy(*p_merged_uri_str, relative_uri_str, len); status = VC_CONTAINER_SUCCESS; goto tidy_up; } base_uri = vc_uri_create(); if (!base_uri) goto tidy_up; if (!vc_uri_parse(base_uri, base_uri_str)) { status = VC_CONTAINER_ERROR_FORMAT_INVALID; goto tidy_up; } /* Build up merged URI in relative_uri, using base_uri as necessary */ if (!vc_uri_merge(base_uri, relative_uri)) goto tidy_up; merged_size = vc_uri_build(relative_uri, NULL, 0) + 1; *p_merged_uri_str = (char *)malloc(merged_size); if (!*p_merged_uri_str) goto tidy_up; vc_uri_build(relative_uri, *p_merged_uri_str, merged_size); status = VC_CONTAINER_SUCCESS; tidy_up: if (base_uri) vc_uri_release(base_uri); if (relative_uri) vc_uri_release(relative_uri); if (status != VC_CONTAINER_SUCCESS) LOG_ERROR(p_ctx, "RTSP: Error merging URIs: %d", (int)status); return status; } /**************************************************************************//** * Parse a control attribute and store it as an absolute URI. * * @param p_ctx The RTSP reader context. * @param attribute The control attribute value. * @param base_uri_str The base URI string. * @param p_control_uri_str Pointer to where to put the control string pointer. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_parse_control_attribute( VC_CONTAINER_T *p_ctx, const char *attribute, const char *base_uri_str, char **p_control_uri_str) { VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; /* control attribute format: * * The control URI is either absolute or relative to the base URI. If the * control URI is just an asterisk, the control URI matches the base URI. */ if (!*attribute || strcmp(attribute, "*") == 0) { size_t len = strlen(base_uri_str); *p_control_uri_str = (char *)malloc(len + 1); if (!*p_control_uri_str) { LOG_ERROR(p_ctx, "RTSP: Failed to allocate control URI"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } strncpy(*p_control_uri_str, base_uri_str, len); } else { status = rtsp_merge_uris(p_ctx, base_uri_str, attribute, p_control_uri_str); } return status; } /**************************************************************************//** * Open a reader for the track using the URI that has been generated. * * @param p_ctx The RTSP reader context. * @param t_module The track module for which a reader is needed. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_open_track_reader( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_MODULE_T *t_module ) { VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; uint32_t uri_buffer_size; char *uri_buffer; uri_buffer_size = vc_uri_build(t_module->reader_uri, NULL, 0) + 1; uri_buffer = (char *)malloc(uri_buffer_size); if (!uri_buffer) { LOG_ERROR(p_ctx, "RTSP: Failed to build RTP URI"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } vc_uri_build(t_module->reader_uri, uri_buffer, uri_buffer_size); t_module->reader = vc_container_open_reader(uri_buffer, &status, NULL, NULL); free(uri_buffer); return status; } /**************************************************************************//** * Open a reader for the track using the network URI that has been generated. * * @param p_ctx The RTSP reader context. * @param t_module The track module for which a reader is needed. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_open_network_reader( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_MODULE_T *t_module ) { char port[PORT_BUFFER_SIZE] = {0}; if (!t_module->rtp_port) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; t_module->rtp_port = module->next_rtp_port; if (t_module->rtp_port > LAST_DYNAMIC_PORT) { LOG_ERROR(p_ctx, "RTSP: Out of dynamic ports"); return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; } module->next_rtp_port += 2; } snprintf(port, sizeof(port)-1, "%hu", t_module->rtp_port); if (!vc_uri_set_port(t_module->reader_uri, port)) { LOG_ERROR(p_ctx, "RTSP: Failed to set track reader URI port"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } return rtsp_open_track_reader(p_ctx, t_module); } /**************************************************************************//** * Open a reader for the track using the file URI that has been generated. * * @param p_ctx The RTSP reader context. * @param t_module The track module for which a reader is needed. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_open_file_reader( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_MODULE_T *t_module ) { VC_CONTAINER_STATUS_T status; VC_URI_PARTS_T *rtsp_uri = NULL; const char *rtsp_path; int len; char *new_path = NULL; char *extension; /* Use the RTSP URI's path, with the extension changed to ".t.pkt" * where is the zero-based media item number. */ rtsp_uri = vc_uri_create(); if (!rtsp_uri) { LOG_ERROR(p_ctx, "RTSP: Failed to create RTSP URI"); status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto tidy_up; } if (!vc_uri_parse(rtsp_uri, p_ctx->priv->io->uri)) { LOG_ERROR(p_ctx, "RTSP: Failed to parse RTSP URI <%s>", p_ctx->priv->io->uri); status = VC_CONTAINER_ERROR_FORMAT_INVALID; goto tidy_up; } rtsp_path = vc_uri_path(rtsp_uri); if (!rtsp_path || !*rtsp_path) { LOG_ERROR(p_ctx, "RTSP: RTSP URI path missing <%s>", p_ctx->priv->io->uri); status = VC_CONTAINER_ERROR_FORMAT_INVALID; goto tidy_up; } len = strlen(rtsp_path); new_path = (char *)calloc(1, len + RTP_PATH_EXTRA + 1); if (!rtsp_uri) { LOG_ERROR(p_ctx, "RTSP: Failed to create buffer for RTP path"); status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto tidy_up; } strncpy(new_path, rtsp_path, len); extension = strrchr(new_path, '.'); /* Find extension, to replace it */ if (!extension) extension = new_path + strlen(new_path); /* No extension, so append instead */ snprintf(extension, len + RTP_PATH_EXTRA - (extension - new_path), RTP_PATH_EXTENSION_FORMAT, p_ctx->priv->module->media_item); if (!vc_uri_set_path(t_module->reader_uri, new_path)) { LOG_ERROR(p_ctx, "RTSP: Failed to store RTP path <%s>", new_path); status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto tidy_up; } free(new_path); vc_uri_release(rtsp_uri); return rtsp_open_track_reader(p_ctx, t_module); tidy_up: if (new_path) free(new_path); if (rtsp_uri) vc_uri_release(rtsp_uri); return status; } /**************************************************************************//** * Copy track information from the encapsulated track reader's track to the * RTSP track. * * @param p_ctx The RTSP reader context. * @param track The RTSP track requiring its information to be filled in. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_copy_track_data_from_reader( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track ) { VC_CONTAINER_T *reader = track->priv->module->reader; VC_CONTAINER_ES_FORMAT_T *src_format, *dst_format; if (reader->tracks_num != 1) { LOG_ERROR(p_ctx, "RTSP: Expected track reader to have one track, has %d", reader->tracks_num); return VC_CONTAINER_ERROR_FORMAT_INVALID; } if (reader->tracks[0]->meta_num) { LOG_DEBUG(p_ctx, "RTSP: Track reader has meta data - not supported"); } src_format = reader->tracks[0]->format; dst_format = track->format; /* Copy fields individually to avoid problems with pointers (type and extradata). */ dst_format->es_type = src_format->es_type; dst_format->codec = src_format->codec; dst_format->codec_variant = src_format->codec_variant; *dst_format->type = *src_format->type; dst_format->bitrate = src_format->bitrate; memcpy(dst_format->language, src_format->language, sizeof(dst_format->language)); dst_format->group_id = src_format->group_id; dst_format->flags = src_format->flags; if (src_format->extradata) { VC_CONTAINER_STATUS_T status; uint32_t extradata_size = src_format->extradata_size; status = vc_container_track_allocate_extradata(p_ctx, track, extradata_size); if (status != VC_CONTAINER_SUCCESS) return status; memcpy(dst_format->extradata, src_format->extradata, extradata_size); dst_format->extradata_size = extradata_size; } track->is_enabled = reader->tracks[0]->is_enabled; return VC_CONTAINER_SUCCESS; } /**************************************************************************//** * Finalise the creation of the RTSP track by opening its reader and filling in * the track information. * * @param p_ctx The RTSP reader context. * @param track The RTSP track being finalised. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_complete_track( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track ) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; if (!t_module->control_uri) { LOG_ERROR(p_ctx, "RTSP: Track control URI is missing"); return VC_CONTAINER_ERROR_FORMAT_INVALID; } if (module->uri_has_network_info) { int ii; if (!vc_uri_set_host(t_module->reader_uri, "")) { LOG_ERROR(p_ctx, "RTSP: Failed to set track reader URI host"); return VC_CONTAINER_ERROR_OUT_OF_MEMORY; } status = rtsp_open_network_reader(p_ctx, t_module); for (ii = 0; status == VC_CONTAINER_ERROR_URI_OPEN_FAILED && ii < DYNAMIC_PORT_ATTEMPTS_MAX; ii++) { /* Reset port to pick up next dynamic port */ t_module->rtp_port = 0; status = rtsp_open_network_reader(p_ctx, t_module); } /* Change I/O to non-blocking, so that tracks can be polled */ if (status == VC_CONTAINER_SUCCESS) status = vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, 0); /* Set a large read buffer, to avoid dropping bursts of large packets (e.g. hi-def video) */ if (status == VC_CONTAINER_SUCCESS) status = vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE, UDP_READ_BUFFER_SIZE); } else { status = rtsp_open_file_reader(p_ctx, t_module); } vc_uri_release(t_module->reader_uri); t_module->reader_uri = NULL; if (status == VC_CONTAINER_SUCCESS) status = rtsp_copy_track_data_from_reader(p_ctx, track); return status; } /**************************************************************************//** * Returns whether the given character is an attribute name delimiter or not. * * @param char_to_test The character under test. * @return True if the character is an attribute name delimiter, false if not. */ static int attribute_name_delimiter_fn(int char_to_test) { return (char_to_test == ':'); } /**************************************************************************//** * Create RTSP tracks from media fields in SDP formatted data. * * @param p_ctx The RTSP reader context. * @param sdp_buffer The SDP data. * @param base_uri The RTSP base URI. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_create_tracks_from_sdp( VC_CONTAINER_T *p_ctx, char *sdp_buffer, char *base_uri ) { VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; VC_CONTAINER_TRACK_T *track = NULL; char *session_base_uri = base_uri; char *ptr; char *next_line_ptr; char *attribute; for (ptr = sdp_buffer; status == VC_CONTAINER_SUCCESS && *ptr; ptr = next_line_ptr) { /* Find end of line */ char field = *ptr; next_line_ptr = ptr; while (*next_line_ptr && *next_line_ptr != '\n') next_line_ptr++; /* Terminate line */ if (*next_line_ptr) *next_line_ptr++ = '\0'; /* The format of the line has to be "=" where is a single * character. Ignore anything else. */ if (ptr[1] != '=') continue; ptr = rtsp_trim(ptr + 2); switch (field) { case 'm': /* Start of media item */ if (track) { /* Finish previous track */ status = rtsp_complete_track(p_ctx, track); track = NULL; p_ctx->priv->module->media_item++; if (status != VC_CONTAINER_SUCCESS) break; } status = rtsp_create_track_for_media_field(p_ctx, ptr, &track); break; case 'a': /* Attribute (either session or media level) */ /* Attributes of the form "a=:" */ attribute = rtsp_parse_extract(&ptr, attribute_name_delimiter_fn, NULL); if (track) { /* Media level attribute */ /* Look for known attributes */ if (strcmp(attribute, "rtpmap") == 0) status = rtsp_parse_rtpmap_attribute(p_ctx, track, ptr); else if (strcmp(attribute, "fmtp") == 0) status = rtsp_parse_fmtp_attribute(p_ctx, track, ptr); else if (strcmp(attribute, "control") == 0) { char **track_control_uri = &track->priv->module->control_uri; if (*track_control_uri) { free(*track_control_uri); *track_control_uri = NULL; } status = rtsp_parse_control_attribute(p_ctx, ptr, session_base_uri, track_control_uri); } /* Any other attributes are ignored */ } else { /* Session level attribute */ if (strcmp(attribute, "control") == 0) { /* Only need to change the session_base_uri if it differs from the base URI */ ptr = rtsp_trim(ptr); if (session_base_uri != base_uri) { free(session_base_uri); session_base_uri = base_uri; } if (strcmp(ptr, base_uri) != 0) status = rtsp_parse_control_attribute(p_ctx, ptr, base_uri, &session_base_uri); } } break; default: /* Ignore any other field names */ ; } } if (session_base_uri && session_base_uri != base_uri) free(session_base_uri); /* Having no media fields is an error, since there will be nothing to play back */ if (status == VC_CONTAINER_SUCCESS) { if (!p_ctx->tracks_num) status = VC_CONTAINER_ERROR_FORMAT_INVALID; else if (track) { /* Finish final track */ status = rtsp_complete_track(p_ctx, track); p_ctx->priv->module->media_item++; } } return status; } /**************************************************************************//** * Create RTSP tracks from the response to a DESCRIBE request. * The response must have already been filled into the comms buffer and header * list. * * @param p_ctx The RTSP reader context. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_create_tracks_from_response( VC_CONTAINER_T *p_ctx ) { VC_CONTAINERS_LIST_T *header_list = p_ctx->priv->module->header_list; RTSP_HEADER_T header; char *base_uri; char *content; header.name = CONTENT_PSEUDOHEADER_NAME; if (!vc_containers_list_find_entry(header_list, &header)) { LOG_ERROR(p_ctx, "RTSP: Content missing"); return VC_CONTAINER_ERROR_FORMAT_INVALID; } content = header.value; /* The control URI may be relative to a base URI which is the first of these * that is available: * 1. Content-Base header * 2. Content-Location header * 3. Request URI */ header.name = CONTENT_BASE_NAME; if (vc_containers_list_find_entry(header_list, &header)) base_uri = header.value; else { header.name = CONTENT_LOCATION_NAME; if (vc_containers_list_find_entry(header_list, &header)) base_uri = header.value; else base_uri = p_ctx->priv->io->uri; } return rtsp_create_tracks_from_sdp(p_ctx, content, base_uri); } /**************************************************************************//** * Header comparison function. * Compare two header structures and return whether the first is less than, * equal to or greater than the second. * * @param first The first structure to be compared. * @param second The second structure to be compared. * @return Negative if first is less than second, positive if first is greater * and zero if they are equal. */ static int rtsp_header_comparator(const RTSP_HEADER_T *first, const RTSP_HEADER_T *second) { return strcasecmp(first->name, second->name); } /**************************************************************************//** * Make a DESCRIBE request to the server and create tracks from the response. * * @param p_ctx The RTSP reader context. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_describe( VC_CONTAINER_T *p_ctx ) { VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; /* Send DESCRIBE request and get response */ status = rtsp_send_describe_request(p_ctx); if (status != VC_CONTAINER_SUCCESS) return status; status = rtsp_read_response(p_ctx); if (status != VC_CONTAINER_SUCCESS) return status; /* Create tracks from SDP content */ status = rtsp_create_tracks_from_response(p_ctx); return status; } /**************************************************************************//** * Make a SETUP request to the server and get session from response. * * @param p_ctx The RTSP reader context. * @param t_module The track module to be set up. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_setup( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_MODULE_T *t_module ) { VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; const char *session_header; size_t session_header_len; status = rtsp_send_setup_request(p_ctx, t_module); if (status != VC_CONTAINER_SUCCESS) return status; status = rtsp_read_response(p_ctx); if (status != VC_CONTAINER_SUCCESS) return status; session_header = rtsp_get_session_header(module->header_list); session_header_len = strlen(session_header); if (session_header_len > SESSION_HEADER_LENGTH_MAX) return VC_CONTAINER_ERROR_FORMAT_INVALID; t_module->session_header = (char *)malloc(session_header_len + 1); if (!t_module->session_header) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; strncpy(t_module->session_header, session_header, session_header_len); return status; } /**************************************************************************//** * Make a SETUP request to the server and get session from response. * * @param p_ctx The RTSP reader context. * @param t_module The track module to be set up. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_play( VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_MODULE_T *t_module ) { VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; status = rtsp_send_play_request(p_ctx, t_module); if (status != VC_CONTAINER_SUCCESS) return status; status = rtsp_read_response(p_ctx); if (status != VC_CONTAINER_SUCCESS) return status; rtsp_store_rtp_info(module->header_list, t_module); return status; } /**************************************************************************//** * Blocking read/skip data from a container. * Can also be used to query information about the next block of data. * * @pre The container is set to non-blocking. * @post The container is set to non-blocking. * * @param p_ctx The reader context. * @param p_packet The container packet information, or NULL. * @param flags The container read flags. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_blocking_track_read(VC_CONTAINER_T *p_ctx, VC_CONTAINER_PACKET_T *p_packet, uint32_t flags) { VC_CONTAINER_STATUS_T status; status = vc_container_read(p_ctx, p_packet, flags); /* The ..._ABORTED status corresponds to a timeout waiting for data */ if (status == VC_CONTAINER_ERROR_ABORTED) { /* So switch to blocking temporarily to wait for some */ (void)vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, VC_CONTAINER_READ_TIMEOUT_BLOCK); status = vc_container_read(p_ctx, p_packet, flags); (void)vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, 0); } return status; } /**************************************************************************//** * Update the cached packet info blocks for all tracks. * If one or more of the tracks has data, set the current track to the one with * the earliest decode timestamp. * * @pre The track readers must not block when data is requested from them. * * @param p_ctx The RTSP reader context. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_update_track_info( VC_CONTAINER_T *p_ctx ) { VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; uint32_t tracks_num = p_ctx->tracks_num; uint32_t track_idx; int64_t earliest_dts = MAXIMUM_INT64; VC_CONTAINER_TRACK_MODULE_T *earliest_track = NULL; /* Reset current track to unknown */ p_ctx->priv->module->current_track = NULL; /* Collect each track's info and return the one with earliest timestamp. */ for (track_idx = 0; track_idx < tracks_num; track_idx++) { VC_CONTAINER_TRACK_MODULE_T *t_module = p_ctx->tracks[track_idx]->priv->module; VC_CONTAINER_PACKET_T *info = &t_module->info; /* If this track has no data available, request more */ if (!info->size) { /* This is a non-blocking read, so status will be ..._ABORTED if nothing available */ status = vc_container_read(t_module->reader, info, VC_CONTAINER_READ_FLAG_INFO); /* Adjust track index to be the RTSP index instead of the RTP one */ info->track = track_idx; } if (status == VC_CONTAINER_SUCCESS) { if (info->dts < earliest_dts) { earliest_dts = info->dts; earliest_track = t_module; } } else if (status != VC_CONTAINER_ERROR_ABORTED) { /* Not a time-out failure, so abort */ return status; } } p_ctx->priv->module->current_track = earliest_track; return VC_CONTAINER_SUCCESS; } /***************************************************************************** Functions exported as part of the Container Module API *****************************************************************************/ /**************************************************************************//** * Read/skip data from the container. * Can also be used to query information about the next block of data. * * @param p_ctx The reader context. * @param p_packet The container packet information, or NULL. * @param flags The container read flags. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_reader_read( VC_CONTAINER_T *p_ctx, VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; VC_CONTAINER_TRACK_MODULE_T *current_track = module->current_track; VC_CONTAINER_PACKET_T *info; if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) { vc_container_assert(p_packet); vc_container_assert(p_packet->track < p_ctx->tracks_num); current_track = p_ctx->tracks[p_packet->track]->priv->module; module->current_track = current_track; if (!current_track->info.size) { status = rtsp_blocking_track_read(current_track->reader, ¤t_track->info, VC_CONTAINER_READ_FLAG_INFO); if (status != VC_CONTAINER_SUCCESS) goto error; } } else if (!current_track || !current_track->info.size) { status = rtsp_update_track_info(p_ctx); if (status != VC_CONTAINER_SUCCESS) goto error; while (!module->current_track) { /* Check RTSP stream to see if it has closed */ status = rtsp_read_response(p_ctx); if (status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_ABORTED) { /* No data from any track yet, so keep checking */ status = rtsp_update_track_info(p_ctx); } if (status != VC_CONTAINER_SUCCESS) goto error; } current_track = module->current_track; } info = ¤t_track->info; vc_container_assert(info->size); if (flags & VC_CONTAINER_READ_FLAG_INFO) { vc_container_assert(p_packet); memcpy(p_packet, info, sizeof(*info)); } else { status = rtsp_blocking_track_read(current_track->reader, p_packet, flags); if (status != VC_CONTAINER_SUCCESS) goto error; if (p_packet) { p_packet->track = info->track; if (flags & VC_CONTAINER_READ_FLAG_SKIP) { info->size = 0; } else { vc_container_assert(info->size >= p_packet->size); info->size -= p_packet->size; } } else { info->size = 0; } } if (p_packet) { /* Adjust timestamps to be relative to zero */ if (!module->ts_base) module->ts_base = p_packet->dts; p_packet->dts -= module->ts_base; p_packet->pts -= module->ts_base; } error: STREAM_STATUS(p_ctx) = status; return status; } /**************************************************************************//** * Seek over data in the container. * * @param p_ctx The reader context. * @param p_offset The seek offset. * @param mode The seek mode. * @param flags The seek flags. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_reader_seek( VC_CONTAINER_T *p_ctx, int64_t *p_offset, VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) { VC_CONTAINER_PARAM_UNUSED(p_ctx); VC_CONTAINER_PARAM_UNUSED(p_offset); VC_CONTAINER_PARAM_UNUSED(mode); VC_CONTAINER_PARAM_UNUSED(flags); /* RTSP is a non-seekable container */ return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; } /**************************************************************************//** * Close the container. * * @param p_ctx The reader context. * @return The resulting status of the function. */ static VC_CONTAINER_STATUS_T rtsp_reader_close( VC_CONTAINER_T *p_ctx ) { VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; unsigned int i; for(i = 0; i < p_ctx->tracks_num; i++) { VC_CONTAINER_TRACK_MODULE_T *t_module = p_ctx->tracks[i]->priv->module; if (t_module->control_uri && t_module->session_header) { /* Send the teardown message and wait for a response, although it * isn't important whether it was successful or not. */ if (rtsp_send_teardown_request(p_ctx, t_module) == VC_CONTAINER_SUCCESS) (void)rtsp_read_response(p_ctx); } if (t_module->reader) vc_container_close(t_module->reader); if (t_module->reader_uri) vc_uri_release(t_module->reader_uri); if (t_module->control_uri) free(t_module->control_uri); if (t_module->session_header) free(t_module->session_header); vc_container_free_track(p_ctx, p_ctx->tracks[i]); /* Also need to close track's reader */ } p_ctx->tracks = NULL; p_ctx->tracks_num = 0; if (module) { if (module->comms_buffer) free(module->comms_buffer); if (module->header_list) vc_containers_list_destroy(module->header_list); free(module); } p_ctx->priv->module = 0; return VC_CONTAINER_SUCCESS; } /**************************************************************************//** * Open the container. * Uses the I/O URI and/or data to configure the container. * * @param p_ctx The reader context. * @return The resulting status of the function. */ VC_CONTAINER_STATUS_T rtsp_reader_open( VC_CONTAINER_T *p_ctx ) { VC_CONTAINER_MODULE_T *module = 0; VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; uint32_t ii; /* Check the URI scheme looks valid */ if (!vc_uri_scheme(p_ctx->priv->uri) || (strcasecmp(vc_uri_scheme(p_ctx->priv->uri), RTSP_SCHEME) && strcasecmp(vc_uri_scheme(p_ctx->priv->uri), RTSP_PKT_SCHEME))) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; /* Allocate our context */ if ((module = (VC_CONTAINER_MODULE_T *)malloc(sizeof(VC_CONTAINER_MODULE_T))) == NULL) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } memset(module, 0, sizeof(*module)); p_ctx->priv->module = module; p_ctx->tracks = module->tracks; module->next_rtp_port = FIRST_DYNAMIC_PORT; module->cseq_value = 0; module->uri_has_network_info = (strncasecmp(p_ctx->priv->io->uri, RTSP_NETWORK_URI_START, RTSP_NETWORK_URI_START_LENGTH) == 0); module->comms_buffer = (char *)calloc(1, COMMS_BUFFER_SIZE+1); if (!module->comms_buffer) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } /* header_list will contain pointers into the response_buffer, so take care in re-use */ module->header_list = vc_containers_list_create(HEADER_LIST_INITIAL_CAPACITY, sizeof(RTSP_HEADER_T), (VC_CONTAINERS_LIST_COMPARATOR_T)rtsp_header_comparator); if (!module->header_list) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } status = rtsp_describe(p_ctx); for (ii = 0; status == VC_CONTAINER_SUCCESS && ii < p_ctx->tracks_num; ii++) status = rtsp_setup(p_ctx, p_ctx->tracks[ii]->priv->module); for (ii = 0; status == VC_CONTAINER_SUCCESS && ii < p_ctx->tracks_num; ii++) status = rtsp_play(p_ctx, p_ctx->tracks[ii]->priv->module); if (status != VC_CONTAINER_SUCCESS) goto error; /* Set the RTSP stream to block briefly, to allow polling for closure as well as to avoid spinning CPU */ vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, DATA_UNAVAILABLE_READ_TIMEOUT_MS); p_ctx->priv->pf_close = rtsp_reader_close; p_ctx->priv->pf_read = rtsp_reader_read; p_ctx->priv->pf_seek = rtsp_reader_seek; if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) goto error; return VC_CONTAINER_SUCCESS; error: if(status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_EOS) status = VC_CONTAINER_ERROR_FORMAT_INVALID; LOG_DEBUG(p_ctx, "error opening RTSP stream (%i)", status); rtsp_reader_close(p_ctx); return status; } /******************************************************************************** Entrypoint function ********************************************************************************/ #if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) # pragma weak reader_open rtsp_reader_open #endif