├── .MODULE_NAME_librtsp ├── .MODULE_LICENSE_BSD ├── AUTHORS ├── README.md ├── .clang-format ├── COPYING ├── atom.mk ├── include └── rtsp │ ├── rtsp.h │ ├── server.h │ ├── common.h │ └── client.h ├── src ├── rtsp_client_priv.h ├── rtsp_server_request.c ├── rtsp_server_priv.h ├── rtsp_priv.h ├── rtsp_client_session.c ├── rtsp_server_session.c ├── internal │ └── rtsp │ │ └── rtsp_internal.h └── rtsp_client.c └── tests ├── rtsp_server_test.c └── rtsp_client_test.c /.MODULE_NAME_librtsp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.MODULE_LICENSE_BSD: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Parrot Drones SAS 2 | Copyright (c) 2017 Aurelien Barre -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Aurelien Barre 2 | Original author 3 | Parrot Drones SAS 4 | Maintainer 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # librtsp - Real Time Streaming Protocol library 2 | 3 | librtsp is a C library implementing a Real Time Streaming Protocol (RTSP) 4 | client and server (see RFC2326). 5 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -8 4 | AlignAfterOpenBracket: true 5 | AlignEscapedNewlinesLeft: false 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortFunctionsOnASingleLine: Empty 10 | AllowShortIfStatementsOnASingleLine: false 11 | AllowShortLoopsOnASingleLine: false 12 | AlwaysBreakBeforeMultilineStrings: true 13 | BinPackArguments: false 14 | BinPackParameters: false 15 | BreakBeforeBinaryOperators: None 16 | BreakBeforeBraces: WebKit 17 | BreakConstructorInitializers: AfterColon 18 | BreakStringLiterals: false 19 | ContinuationIndentWidth: 8 20 | ConstructorInitializerIndentWidth: 16 21 | IndentCaseLabels: false 22 | IndentPPDirectives: AfterHash 23 | IndentWidth: 8 24 | Language: Cpp 25 | MaxEmptyLinesToKeep: 2 26 | SortIncludes: true 27 | SpaceAfterCStyleCast: false 28 | UseTab: Always 29 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Parrot Drones SAS 2 | Copyright (c) 2017 Aurelien Barre 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the copyright holders nor the names of its 12 | contributors may be used to endorse or promote products derived from 13 | this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /atom.mk: -------------------------------------------------------------------------------- 1 | 2 | LOCAL_PATH := $(call my-dir) 3 | 4 | include $(CLEAR_VARS) 5 | LOCAL_MODULE := librtsp 6 | LOCAL_CATEGORY_PATH := libs 7 | LOCAL_DESCRIPTION := Real Time Streaming Protocol library 8 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include 9 | LOCAL_CFLAGS := -DRTSP_API_EXPORTS -fvisibility=hidden -std=gnu99 -D_GNU_SOURCE 10 | LOCAL_SRC_FILES := \ 11 | src/rtsp.c \ 12 | src/rtsp_client.c \ 13 | src/rtsp_client_session.c \ 14 | src/rtsp_server.c \ 15 | src/rtsp_server_request.c \ 16 | src/rtsp_server_session.c 17 | LOCAL_LIBRARIES := \ 18 | libfutils \ 19 | libpomp \ 20 | libulog 21 | ifeq ("$(TARGET_OS)","windows") 22 | LOCAL_CFLAGS += -D_WIN32_WINNT=0x0600 23 | LOCAL_LDLIBS += -lws2_32 24 | endif 25 | include $(BUILD_LIBRARY) 26 | 27 | 28 | include $(CLEAR_VARS) 29 | 30 | LOCAL_MODULE := librtsp-internal 31 | LOCAL_DESCRIPTION := Real Time Streaming Protocol library internal headers 32 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/src/internal/ 33 | 34 | include $(BUILD_CUSTOM) 35 | 36 | 37 | include $(CLEAR_VARS) 38 | LOCAL_MODULE := rtsp-server-test 39 | LOCAL_CATEGORY_PATH := multimedia 40 | LOCAL_DESCRIPTION := Real Time Streaming Protocol library server test program 41 | LOCAL_SRC_FILES := \ 42 | tests/rtsp_server_test.c 43 | LOCAL_LIBRARIES := \ 44 | libfutils \ 45 | libpomp \ 46 | librtsp \ 47 | libsdp \ 48 | libulog 49 | include $(BUILD_EXECUTABLE) 50 | 51 | 52 | include $(CLEAR_VARS) 53 | LOCAL_MODULE := rtsp-client-test 54 | LOCAL_CATEGORY_PATH := multimedia 55 | LOCAL_DESCRIPTION := Real Time Streaming Protocol library client test program 56 | LOCAL_SRC_FILES := \ 57 | tests/rtsp_client_test.c 58 | LOCAL_LIBRARIES := \ 59 | libpomp \ 60 | librtsp \ 61 | libsdp \ 62 | libulog 63 | include $(BUILD_EXECUTABLE) 64 | -------------------------------------------------------------------------------- /include/rtsp/rtsp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #ifndef _RTSP_H_ 29 | #define _RTSP_H_ 30 | 31 | #include 32 | #include 33 | 34 | #endif /* !_RTSP_H_ */ 35 | -------------------------------------------------------------------------------- /src/rtsp_client_priv.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #ifndef _RTSP_CLIENT_PRIV_H_ 29 | #define _RTSP_CLIENT_PRIV_H_ 30 | 31 | #include "rtsp_priv.h" 32 | 33 | 34 | #define RTSP_CLIENT_DEFAULT_SOFTWARE_NAME "librtsp_client" 35 | #define RTSP_CLIENT_SESSION_ID_LENGTH 8 36 | #define RTSP_CLIENT_MAX_FAILED_REQUESTS 5 37 | #define RTSP_CLIENT_MAX_FAILED_KEEP_ALIVE 3 38 | 39 | 40 | enum rtsp_client_state { 41 | RTSP_CLIENT_STATE_IDLE = 0, 42 | RTSP_CLIENT_STATE_OPTIONS_WAITING_REPLY, 43 | RTSP_CLIENT_STATE_OPTIONS_OK, 44 | RTSP_CLIENT_STATE_DESCRIBE_WAITING_REPLY, 45 | RTSP_CLIENT_STATE_DESCRIBE_OK, 46 | RTSP_CLIENT_STATE_SETUP_WAITING_REPLY, 47 | RTSP_CLIENT_STATE_SETUP_OK, 48 | RTSP_CLIENT_STATE_PLAY_WAITING_REPLY, 49 | RTSP_CLIENT_STATE_PLAY_OK, 50 | RTSP_CLIENT_STATE_PAUSE_WAITING_REPLY, 51 | RTSP_CLIENT_STATE_PAUSE_OK, 52 | RTSP_CLIENT_STATE_TEARDOWN_WAITING_REPLY, 53 | RTSP_CLIENT_STATE_TEARDOWN_OK, 54 | RTSP_CLIENT_STATE_KEEPALIVE_WAITING_REPLY, 55 | }; 56 | 57 | 58 | struct rtsp_client_session_media { 59 | struct rtsp_client_session *session; 60 | char *path; 61 | void *userdata; 62 | 63 | struct list_node node; 64 | }; 65 | 66 | 67 | struct rtsp_client_session { 68 | char *id; 69 | struct pomp_timer *timer; 70 | struct rtsp_client *client; 71 | char *content_base; 72 | unsigned int timeout_ms; 73 | unsigned int failed_keep_alive; 74 | int keep_alive_in_progress; 75 | int internal_teardown; 76 | 77 | /* Medias */ 78 | unsigned int media_count; 79 | struct list_node medias; 80 | 81 | struct list_node node; 82 | }; 83 | 84 | 85 | struct rtsp_client { 86 | struct pomp_loop *loop; 87 | struct pomp_ctx *ctx; 88 | struct rtsp_client_cbs cbs; 89 | void *cbs_userdata; 90 | char *software_name; 91 | uint16_t port; 92 | 93 | /* States */ 94 | enum rtsp_client_conn_state conn_state; 95 | char *addr; 96 | struct sockaddr_in remote_addr_in; 97 | unsigned int cseq; 98 | uint32_t methods_allowed; 99 | struct list_node sessions; 100 | unsigned int failed_requests; 101 | 102 | struct { 103 | struct rtsp_request_header header; 104 | struct pomp_buffer *buf; 105 | int is_pending; 106 | int is_internal; 107 | char *uri; 108 | char *content_base; 109 | void *userdata; 110 | struct pomp_timer *timer; 111 | } request; 112 | 113 | struct { 114 | struct pomp_buffer *buf; 115 | } response; 116 | 117 | struct rtsp_message_parser_ctx parser_ctx; 118 | }; 119 | 120 | 121 | struct rtsp_client_session *rtsp_client_get_session(struct rtsp_client *client, 122 | const char *session_id, 123 | int add); 124 | 125 | 126 | int rtsp_client_remove_session_internal(struct rtsp_client *client, 127 | const char *session_id, 128 | int status_code, 129 | int nexist_ok); 130 | 131 | 132 | void rtsp_client_remove_all_sessions(struct rtsp_client *client); 133 | 134 | 135 | struct rtsp_client_session *rtsp_client_session_find(struct rtsp_client *client, 136 | const char *session_id); 137 | 138 | 139 | struct rtsp_client_session_media * 140 | rtsp_client_session_media_add(struct rtsp_client *client, 141 | struct rtsp_client_session *session, 142 | const char *path); 143 | 144 | 145 | int rtsp_client_session_media_remove(struct rtsp_client *client, 146 | struct rtsp_client_session *session, 147 | struct rtsp_client_session_media *media); 148 | 149 | 150 | struct rtsp_client_session_media * 151 | rtsp_client_session_media_find(struct rtsp_client *client, 152 | struct rtsp_client_session *session, 153 | const char *path); 154 | 155 | 156 | void rtsp_client_pomp_timer_cb(struct pomp_timer *timer, void *userdata); 157 | 158 | 159 | #endif /* !_RTSP_CLIENT_PRIV_H_ */ 160 | -------------------------------------------------------------------------------- /src/rtsp_server_request.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #include "rtsp_server_priv.h" 29 | 30 | #define ULOG_TAG rtsp_server 31 | #include 32 | 33 | 34 | struct rtsp_server_pending_request * 35 | rtsp_server_pending_request_add(struct rtsp_server *server, 36 | struct pomp_conn *conn, 37 | unsigned int timeout) 38 | { 39 | struct timespec cur_ts = {0, 0}; 40 | struct rtsp_server_pending_request *request = NULL; 41 | 42 | ULOG_ERRNO_RETURN_VAL_IF(server == NULL, EINVAL, NULL); 43 | 44 | request = calloc(1, sizeof(*request)); 45 | ULOG_ERRNO_RETURN_VAL_IF(request == NULL, ENOMEM, NULL); 46 | list_node_unref(&request->node); 47 | request->conn = conn; 48 | request->request_first_reply = 1; 49 | list_init(&request->medias); 50 | 51 | time_get_monotonic(&cur_ts); 52 | time_timespec_to_us(&cur_ts, &request->timeout); 53 | request->timeout = 54 | (timeout > 0) ? request->timeout + (uint64_t)timeout * 1000 : 0; 55 | 56 | /* Add to the list */ 57 | list_add_before(&server->pending_requests, &request->node); 58 | server->pending_request_count++; 59 | 60 | return request; 61 | } 62 | 63 | 64 | int rtsp_server_pending_request_remove( 65 | struct rtsp_server *server, 66 | struct rtsp_server_pending_request *request) 67 | { 68 | int found = 0; 69 | struct rtsp_server_pending_request *_request = NULL; 70 | struct rtsp_server_pending_request_media *media = NULL; 71 | struct rtsp_server_pending_request_media *tmp_media = NULL; 72 | 73 | ULOG_ERRNO_RETURN_ERR_IF(server == NULL, EINVAL); 74 | ULOG_ERRNO_RETURN_ERR_IF(request == NULL, EINVAL); 75 | 76 | list_walk_entry_forward(&server->pending_requests, _request, node) 77 | { 78 | if (_request == request) { 79 | found = 1; 80 | break; 81 | } 82 | } 83 | 84 | if (!found) { 85 | ULOGE("%s: pending request not found", __func__); 86 | return -ENOENT; 87 | } 88 | 89 | /* Remove from the list */ 90 | list_del(&request->node); 91 | server->pending_request_count--; 92 | 93 | /* Remove all medias */ 94 | list_walk_entry_forward_safe(&request->medias, media, tmp_media, node) 95 | { 96 | rtsp_server_pending_request_media_remove( 97 | server, request, media); 98 | } 99 | 100 | rtsp_request_header_clear(&request->request_header); 101 | rtsp_response_header_clear(&request->response_header); 102 | free(request); 103 | 104 | return 0; 105 | } 106 | 107 | 108 | int rtsp_server_pending_request_find( 109 | struct rtsp_server *server, 110 | struct rtsp_server_pending_request *request) 111 | { 112 | int found = 0; 113 | struct rtsp_server_pending_request *_request = NULL; 114 | 115 | ULOG_ERRNO_RETURN_ERR_IF(server == NULL, EINVAL); 116 | ULOG_ERRNO_RETURN_ERR_IF(request == NULL, EINVAL); 117 | 118 | list_walk_entry_forward(&server->pending_requests, _request, node) 119 | { 120 | if (_request == request) { 121 | found = 1; 122 | break; 123 | } 124 | } 125 | 126 | return (found) ? 0 : -ENOENT; 127 | } 128 | 129 | 130 | struct rtsp_server_pending_request_media *rtsp_server_pending_request_media_add( 131 | struct rtsp_server *server, 132 | struct rtsp_server_pending_request *request, 133 | struct rtsp_server_session_media *media) 134 | { 135 | struct rtsp_server_pending_request_media *m = NULL; 136 | 137 | ULOG_ERRNO_RETURN_VAL_IF(server == NULL, EINVAL, NULL); 138 | ULOG_ERRNO_RETURN_VAL_IF(request == NULL, EINVAL, NULL); 139 | ULOG_ERRNO_RETURN_VAL_IF(media == NULL, EINVAL, NULL); 140 | 141 | m = calloc(1, sizeof(*m)); 142 | ULOG_ERRNO_RETURN_VAL_IF(m == NULL, ENOMEM, NULL); 143 | list_node_unref(&m->node); 144 | m->media = media; 145 | 146 | /* Add to the list */ 147 | list_add_before(&request->medias, &m->node); 148 | request->media_count++; 149 | 150 | return m; 151 | } 152 | 153 | 154 | int rtsp_server_pending_request_media_remove( 155 | struct rtsp_server *server, 156 | struct rtsp_server_pending_request *request, 157 | struct rtsp_server_pending_request_media *media) 158 | { 159 | int found = 0; 160 | struct rtsp_server_pending_request_media *_media = NULL; 161 | 162 | ULOG_ERRNO_RETURN_ERR_IF(server == NULL, EINVAL); 163 | ULOG_ERRNO_RETURN_ERR_IF(request == NULL, EINVAL); 164 | ULOG_ERRNO_RETURN_ERR_IF(media == NULL, EINVAL); 165 | 166 | list_walk_entry_forward(&request->medias, _media, node) 167 | { 168 | if (_media == media) { 169 | found = 1; 170 | break; 171 | } 172 | } 173 | 174 | if (!found) { 175 | ULOGE("%s: media not found", __func__); 176 | return -ENOENT; 177 | } 178 | 179 | /* Remove from the list */ 180 | list_del(&media->node); 181 | request->media_count--; 182 | 183 | free(media); 184 | 185 | return 0; 186 | } 187 | -------------------------------------------------------------------------------- /src/rtsp_server_priv.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #ifndef _RTSP_SERVER_PRIV_H_ 29 | #define _RTSP_SERVER_PRIV_H_ 30 | 31 | #include "rtsp_priv.h" 32 | 33 | 34 | #define RTSP_SERVER_DEFAULT_SOFTWARE_NAME "librtsp_server" 35 | #define RTSP_SERVER_SESSION_ID_LENGTH 8 36 | #define RTSP_SERVER_DEFAULT_REPLY_TIMEOUT_MS 1000 37 | #define RTSP_SERVER_DEFAULT_SESSION_TIMEOUT_MS 60000 38 | 39 | 40 | struct rtsp_server_session_media { 41 | struct rtsp_server_session *session; 42 | char *uri; 43 | char *path; 44 | void *userdata; 45 | bool is_tearing_down; 46 | 47 | struct list_node node; 48 | }; 49 | 50 | 51 | struct rtsp_server_session { 52 | struct rtsp_server *server; 53 | char *session_id; 54 | char *uri; 55 | unsigned int timeout_ms; 56 | struct pomp_timer *timer; 57 | int playing; 58 | struct rtsp_range range; 59 | float scale; 60 | 61 | /* Operation in progress */ 62 | enum rtsp_method_type op_in_progress; 63 | 64 | /* Medias */ 65 | unsigned int media_count; 66 | struct list_node medias; 67 | 68 | struct list_node node; 69 | }; 70 | 71 | 72 | struct rtsp_server_pending_request_media { 73 | struct rtsp_server_session_media *media; 74 | int replied; 75 | 76 | struct list_node node; 77 | }; 78 | 79 | 80 | struct rtsp_server_pending_request { 81 | struct pomp_conn *conn; 82 | struct rtsp_request_header request_header; 83 | struct rtsp_response_header response_header; 84 | uint64_t timeout; 85 | int request_first_reply; 86 | int in_callback; 87 | int replied; 88 | 89 | /* Medias */ 90 | unsigned int media_count; 91 | struct list_node medias; 92 | 93 | struct list_node node; 94 | }; 95 | 96 | 97 | struct rtsp_server { 98 | struct sockaddr_in listen_addr_in; 99 | struct pomp_loop *loop; 100 | struct pomp_ctx *pomp; 101 | struct pomp_timer *timer; 102 | unsigned int max_msg_size; 103 | struct rtsp_server_cbs cbs; 104 | void *cbs_userdata; 105 | 106 | char *software_name; 107 | 108 | int pending_content_length; 109 | int reply_timeout_ms; 110 | 111 | /* Sessions */ 112 | int session_timeout_ms; 113 | unsigned int session_count; 114 | struct list_node sessions; 115 | 116 | /* Pending requests */ 117 | struct pomp_buffer *request_buf; 118 | unsigned int pending_request_count; 119 | struct list_node pending_requests; 120 | 121 | /* Announce requests */ 122 | unsigned int cseq; 123 | 124 | struct rtsp_message_parser_ctx parser_ctx; 125 | }; 126 | 127 | 128 | struct rtsp_server_session *rtsp_server_session_add(struct rtsp_server *server, 129 | unsigned int timeout_ms, 130 | const char *uri); 131 | 132 | 133 | int rtsp_server_session_remove(struct rtsp_server *server, 134 | struct rtsp_server_session *session); 135 | 136 | 137 | void rtsp_server_session_remove_idle(void *userdata); 138 | 139 | 140 | int rtsp_server_session_reset_timeout(struct rtsp_server_session *session); 141 | 142 | 143 | struct rtsp_server_session *rtsp_server_session_find(struct rtsp_server *server, 144 | const char *session_id); 145 | 146 | 147 | struct rtsp_server_session_media * 148 | rtsp_server_session_media_add(struct rtsp_server *server, 149 | struct rtsp_server_session *session, 150 | const char *uri, 151 | const char *path); 152 | 153 | 154 | int rtsp_server_session_media_remove(struct rtsp_server *server, 155 | struct rtsp_server_session *session, 156 | struct rtsp_server_session_media *media); 157 | 158 | 159 | struct rtsp_server_session_media * 160 | rtsp_server_session_media_find(struct rtsp_server *server, 161 | struct rtsp_server_session *session, 162 | const char *path); 163 | 164 | 165 | struct rtsp_server_pending_request * 166 | rtsp_server_pending_request_add(struct rtsp_server *server, 167 | struct pomp_conn *conn, 168 | unsigned int timeout); 169 | 170 | 171 | int rtsp_server_pending_request_remove( 172 | struct rtsp_server *server, 173 | struct rtsp_server_pending_request *request); 174 | 175 | 176 | int rtsp_server_pending_request_find( 177 | struct rtsp_server *server, 178 | struct rtsp_server_pending_request *request); 179 | 180 | 181 | struct rtsp_server_pending_request_media *rtsp_server_pending_request_media_add( 182 | struct rtsp_server *server, 183 | struct rtsp_server_pending_request *request, 184 | struct rtsp_server_session_media *media); 185 | 186 | 187 | int rtsp_server_pending_request_media_remove( 188 | struct rtsp_server *server, 189 | struct rtsp_server_pending_request *request, 190 | struct rtsp_server_pending_request_media *media); 191 | 192 | 193 | void rtsp_server_session_timer_cb(struct pomp_timer *timer, void *userdata); 194 | 195 | 196 | #endif /* !_RTSP_SERVER_PRIV_H_ */ 197 | -------------------------------------------------------------------------------- /include/rtsp/server.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #ifndef _RTSP_SERVER_H_ 29 | #define _RTSP_SERVER_H_ 30 | 31 | #include 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif /* __cplusplus */ 36 | 37 | 38 | /* Reason which brings a RTSP teardown */ 39 | enum rtsp_server_teardown_reason { 40 | RTSP_SERVER_TEARDOWN_REASON_CLIENT_REQUEST = 0, 41 | RTSP_SERVER_TEARDOWN_REASON_SESSION_TIMEOUT, 42 | RTSP_SERVER_TEARDOWN_REASON_FORCED_TEARDOWN, 43 | }; 44 | 45 | 46 | struct rtsp_server; 47 | 48 | 49 | struct rtsp_server_cbs { 50 | void (*socket_cb)(int fd, void *userdata); 51 | 52 | void (*describe)(struct rtsp_server *server, 53 | const char *server_address, 54 | const char *path, 55 | const struct rtsp_header_ext *ext, 56 | size_t ext_count, 57 | void *request_ctx, 58 | void *userdata); 59 | 60 | void (*setup)(struct rtsp_server *server, 61 | const char *path, 62 | const char *session_id, 63 | const struct rtsp_header_ext *ext, 64 | size_t ext_count, 65 | void *request_ctx, 66 | void *media_ctx, 67 | enum rtsp_delivery delivery, 68 | enum rtsp_lower_transport lower_transport, 69 | const char *src_address, 70 | const char *dst_address, 71 | uint16_t dst_stream_port, 72 | uint16_t dst_control_port, 73 | void *userdata); 74 | 75 | void (*play)(struct rtsp_server *server, 76 | const char *session_id, 77 | const struct rtsp_header_ext *ext, 78 | size_t ext_count, 79 | void *request_ctx, 80 | void *media_ctx, 81 | const struct rtsp_range *range, 82 | float scale, 83 | void *stream_userdata, 84 | void *userdata); 85 | 86 | void (*pause)(struct rtsp_server *server, 87 | const char *session_id, 88 | const struct rtsp_header_ext *ext, 89 | size_t ext_count, 90 | void *request_ctx, 91 | void *media_ctx, 92 | const struct rtsp_range *range, 93 | void *stream_userdata, 94 | void *userdata); 95 | 96 | void (*teardown)(struct rtsp_server *server, 97 | const char *path, 98 | const char *session_id, 99 | enum rtsp_server_teardown_reason reason, 100 | const struct rtsp_header_ext *ext, 101 | size_t ext_count, 102 | void *request_ctx, 103 | void *media_ctx, 104 | void *stream_userdata, 105 | void *userdata); 106 | 107 | void (*request_timeout)(struct rtsp_server *server, 108 | void *request_ctx, 109 | enum rtsp_method_type method, 110 | void *userdata); 111 | }; 112 | 113 | 114 | RTSP_API int rtsp_server_new(const char *software_name, 115 | uint16_t port, 116 | int reply_timeout_ms, 117 | int session_timeout_ms, 118 | struct pomp_loop *loop, 119 | const struct rtsp_server_cbs *cbs, 120 | void *userdata, 121 | struct rtsp_server **ret_obj); 122 | 123 | 124 | RTSP_API int rtsp_server_destroy(struct rtsp_server *server); 125 | 126 | 127 | RTSP_API int rtsp_server_reply_to_describe(struct rtsp_server *server, 128 | void *request_ctx, 129 | int status, 130 | const struct rtsp_header_ext *ext, 131 | size_t ext_count, 132 | char *session_description); 133 | 134 | 135 | RTSP_API int rtsp_server_reply_to_setup(struct rtsp_server *server, 136 | void *request_ctx, 137 | void *media_ctx, 138 | int status, 139 | uint16_t src_stream_port, 140 | uint16_t src_control_port, 141 | int ssrc_valid, 142 | uint32_t ssrc, 143 | const struct rtsp_header_ext *ext, 144 | size_t ext_count, 145 | void *stream_userdata); 146 | 147 | 148 | RTSP_API int rtsp_server_reply_to_play(struct rtsp_server *server, 149 | void *request_ctx, 150 | void *media_ctx, 151 | int status, 152 | struct rtsp_range *range, 153 | float scale, 154 | int seq_valid, 155 | uint16_t seq, 156 | int rtptime_valid, 157 | uint32_t rtptime, 158 | const struct rtsp_header_ext *ext, 159 | size_t ext_count); 160 | 161 | 162 | RTSP_API int rtsp_server_reply_to_pause(struct rtsp_server *server, 163 | void *request_ctx, 164 | void *media_ctx, 165 | int status, 166 | struct rtsp_range *range, 167 | const struct rtsp_header_ext *ext, 168 | size_t ext_count); 169 | 170 | 171 | RTSP_API int rtsp_server_reply_to_teardown(struct rtsp_server *server, 172 | void *request_ctx, 173 | void *media_ctx, 174 | int status, 175 | const struct rtsp_header_ext *ext, 176 | size_t ext_count); 177 | 178 | 179 | RTSP_API int rtsp_server_announce(struct rtsp_server *server, 180 | char *uri, 181 | const struct rtsp_header_ext *ext, 182 | size_t ext_count, 183 | char *session_description); 184 | 185 | 186 | RTSP_API int rtsp_server_force_teardown(struct rtsp_server *server, 187 | const char *session_id, 188 | const char *path, 189 | const struct rtsp_header_ext *ext, 190 | size_t ext_count); 191 | 192 | 193 | RTSP_API const char * 194 | rtsp_server_teardown_reason_str(enum rtsp_server_teardown_reason val); 195 | 196 | 197 | #ifdef __cplusplus 198 | } 199 | #endif /* __cplusplus */ 200 | 201 | #endif /* !_RTSP_SERVER_H_ */ 202 | -------------------------------------------------------------------------------- /include/rtsp/common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #ifndef _RTSP_COMMON_H_ 29 | #define _RTSP_COMMON_H_ 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif /* __cplusplus */ 34 | 35 | /* To be used for all public API */ 36 | #ifdef RTSP_API_EXPORTS 37 | # ifdef _WIN32 38 | # define RTSP_API __declspec(dllexport) 39 | # else /* !_WIN32 */ 40 | # define RTSP_API __attribute__((visibility("default"))) 41 | # endif /* !_WIN32 */ 42 | #else /* !RTSP_API_EXPORTS */ 43 | # define RTSP_API 44 | #endif /* !RTSP_API_EXPORTS */ 45 | 46 | #include 47 | #include 48 | 49 | #include 50 | 51 | 52 | /* RTSP methods */ 53 | enum rtsp_method_type { 54 | RTSP_METHOD_TYPE_UNKNOWN = 0, 55 | RTSP_METHOD_TYPE_OPTIONS, 56 | RTSP_METHOD_TYPE_DESCRIBE, 57 | RTSP_METHOD_TYPE_ANNOUNCE, 58 | RTSP_METHOD_TYPE_SETUP, 59 | RTSP_METHOD_TYPE_PLAY, 60 | RTSP_METHOD_TYPE_PAUSE, 61 | RTSP_METHOD_TYPE_TEARDOWN, 62 | RTSP_METHOD_TYPE_GET_PARAMETER, 63 | RTSP_METHOD_TYPE_SET_PARAMETER, 64 | RTSP_METHOD_TYPE_REDIRECT, 65 | RTSP_METHOD_TYPE_RECORD, 66 | }; 67 | 68 | 69 | #define RTSP_METHOD_FLAG_OPTIONS 0x00000001UL 70 | #define RTSP_METHOD_FLAG_DESCRIBE 0x00000002UL 71 | #define RTSP_METHOD_FLAG_ANNOUNCE 0x00000004UL 72 | #define RTSP_METHOD_FLAG_SETUP 0x00000008UL 73 | #define RTSP_METHOD_FLAG_PLAY 0x00000010UL 74 | #define RTSP_METHOD_FLAG_PAUSE 0x00000020UL 75 | #define RTSP_METHOD_FLAG_TEARDOWN 0x00000040UL 76 | #define RTSP_METHOD_FLAG_GET_PARAMETER 0x00000080UL 77 | #define RTSP_METHOD_FLAG_SET_PARAMETER 0x00000100UL 78 | #define RTSP_METHOD_FLAG_REDIRECT 0x00000200UL 79 | #define RTSP_METHOD_FLAG_RECORD 0x00000400UL 80 | 81 | 82 | /** 83 | * Transport definitions 84 | */ 85 | 86 | enum rtsp_delivery { 87 | RTSP_DELIVERY_MULTICAST = 0, 88 | RTSP_DELIVERY_UNICAST, 89 | }; 90 | 91 | enum rtsp_lower_transport { 92 | RTSP_LOWER_TRANSPORT_UDP = 0, 93 | RTSP_LOWER_TRANSPORT_TCP, 94 | RTSP_LOWER_TRANSPORT_MUX, 95 | }; 96 | 97 | 98 | /** 99 | * RTSP Range header definitions 100 | * see RFC 2326 chapter 12.29 101 | */ 102 | 103 | enum rtsp_time_format { 104 | RTSP_TIME_FORMAT_UNKNOWN = 0, 105 | RTSP_TIME_FORMAT_NPT, 106 | RTSP_TIME_FORMAT_SMPTE, 107 | RTSP_TIME_FORMAT_ABSOLUTE, 108 | }; 109 | 110 | /* RTSP Normal Play Time (NPT), see RFC 2326 chapter 3.6 */ 111 | struct rtsp_time_npt { 112 | int now; 113 | int infinity; 114 | uint64_t sec; 115 | uint32_t usec; 116 | }; 117 | 118 | /* RTSP SMPTE Relative Timestamps, see RFC 2326 chapter 3.5 */ 119 | struct rtsp_time_smpte { 120 | int infinity; 121 | uint64_t sec; 122 | unsigned int frames; 123 | }; 124 | 125 | /* RTSP Absolute Time (UTC, ISO 8601), see RFC 2326 chapter 3.7 */ 126 | struct rtsp_time_absolute { 127 | int infinity; 128 | uint64_t sec; 129 | uint32_t usec; 130 | }; 131 | 132 | struct rtsp_time { 133 | enum rtsp_time_format format; 134 | union { 135 | struct rtsp_time_npt npt; 136 | struct rtsp_time_smpte smpte; 137 | struct rtsp_time_absolute absolute; 138 | }; 139 | }; 140 | 141 | struct rtsp_range { 142 | struct rtsp_time start; 143 | struct rtsp_time stop; 144 | uint64_t time; 145 | }; 146 | 147 | 148 | /** 149 | * RTSP header extensions 150 | */ 151 | 152 | struct rtsp_header_ext { 153 | const char *key; 154 | const char *value; 155 | }; 156 | 157 | #define RTSP_HEADER_EXT_PARROT_PROXY_SESSION "X-com-parrot-proxy-session" 158 | #define RTSP_HEADER_EXT_PARROT_LINK_TYPE "X-com-parrot-link-type" 159 | 160 | 161 | /** 162 | * Parse an RTSP URL to extract the host, port and path components. 163 | * Note: the url parameter will be modified by the function. 164 | * @param url: string containing the URL (will be modified) 165 | * @param host: pointer to a string containing the host (optional, output) 166 | * @param port: pointer to a string containing the port (optional, output) 167 | * @param path: pointer to a string containing the path (optional, output) 168 | * @return 0 on success, negative errno value in case of error 169 | */ 170 | RTSP_API int 171 | rtsp_url_parse(char *url, char **host, uint16_t *port, char **path); 172 | 173 | 174 | RTSP_API const char *rtsp_method_type_str(enum rtsp_method_type val); 175 | 176 | 177 | RTSP_API const char *rtsp_delivery_str(enum rtsp_delivery val); 178 | 179 | 180 | RTSP_API const char *rtsp_lower_transport_str(enum rtsp_lower_transport val); 181 | 182 | 183 | RTSP_API const char *rtsp_time_format_str(enum rtsp_time_format val); 184 | 185 | 186 | RTSP_API int rtsp_range_get_duration_us(const struct rtsp_range *range, 187 | int64_t *duration); 188 | 189 | 190 | static inline int rtsp_time_us_to_npt(uint64_t time_us, 191 | struct rtsp_time_npt *time_npt) 192 | { 193 | if (time_npt == NULL) 194 | return -EINVAL; 195 | 196 | time_npt->now = 0; 197 | time_npt->infinity = 0; 198 | time_npt->sec = time_us / 1000000; 199 | time_npt->usec = time_us - time_npt->sec * 1000000; 200 | 201 | return 0; 202 | } 203 | 204 | 205 | static inline int rtsp_time_npt_to_us(const struct rtsp_time_npt *time_npt, 206 | uint64_t *time_us) 207 | { 208 | if ((time_npt == NULL) || (time_us == NULL)) 209 | return -EINVAL; 210 | if ((time_npt->now) || (time_npt->infinity)) 211 | return -EINVAL; 212 | 213 | *time_us = time_npt->sec * 1000000 + time_npt->usec; 214 | 215 | return 0; 216 | } 217 | 218 | 219 | #ifdef __cplusplus 220 | } 221 | #endif /* __cplusplus */ 222 | 223 | #endif /* !_RTSP_COMMON_H_ */ 224 | -------------------------------------------------------------------------------- /src/rtsp_priv.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #ifndef _RTSP_PRIV_H_ 29 | #define _RTSP_PRIV_H_ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #ifdef _WIN32 41 | # include 42 | # include 43 | # define PIPE_BUF 4096 44 | # include 45 | # undef OPAQUE 46 | # undef near 47 | # undef far 48 | # define IPTOS_PREC_INTERNETCONTROL 0xc0 49 | # define IPTOS_PREC_FLASHOVERRIDE 0x80 50 | #else /* !_WIN32 */ 51 | # include 52 | # include 53 | # include 54 | # include 55 | #endif /* !_WIN32 */ 56 | 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | #include "internal/rtsp/rtsp_internal.h" 63 | 64 | #define RTSP_DEFAULT_PORT 554 65 | 66 | #define RTSP_SCHEME_TCP "rtsp://" 67 | #define RTSP_SCHEME_UDP "rtspu://" 68 | 69 | 70 | /** 71 | * Function prototypes 72 | */ 73 | 74 | void rtsp_status_get(int status, int *code, const char **str); 75 | 76 | 77 | int rtsp_status_to_errno(int status); 78 | 79 | 80 | const char *rtsp_status_str(int status); 81 | 82 | 83 | int rtsp_allow_header_write(uint32_t methods, struct rtsp_string *str); 84 | 85 | 86 | int rtsp_allow_header_read(char *str, uint32_t *methods); 87 | 88 | 89 | int rtsp_public_header_write(uint32_t methods, struct rtsp_string *str); 90 | 91 | 92 | int rtsp_public_header_read(char *str, uint32_t *methods); 93 | 94 | 95 | int rtsp_range_header_write(const struct rtsp_range *range, 96 | struct rtsp_string *str); 97 | 98 | 99 | int rtsp_range_header_read(char *str, struct rtsp_range *range); 100 | 101 | 102 | int rtsp_session_header_write(char *session_id, 103 | unsigned int session_timeout, 104 | struct rtsp_string *str); 105 | 106 | 107 | int rtsp_session_header_read(char *str, 108 | char **session_id, 109 | unsigned int *session_timeout); 110 | 111 | 112 | struct rtsp_rtp_info_header *rtsp_rtp_info_header_new(void); 113 | 114 | 115 | int rtsp_rtp_info_header_free(struct rtsp_rtp_info_header **rtp_info); 116 | 117 | 118 | int rtsp_rtp_info_header_copy(const struct rtsp_rtp_info_header *src, 119 | struct rtsp_rtp_info_header *dst); 120 | 121 | 122 | int rtsp_rtp_info_header_write(struct rtsp_rtp_info_header *const *rtp_info, 123 | unsigned int count, 124 | struct rtsp_string *str); 125 | 126 | 127 | int rtsp_rtp_info_header_read(char *str, 128 | struct rtsp_rtp_info_header **rtp_info, 129 | unsigned int max_count, 130 | unsigned int *count); 131 | 132 | 133 | struct rtsp_transport_header *rtsp_transport_header_new(void); 134 | 135 | 136 | int rtsp_transport_header_free(struct rtsp_transport_header **transport); 137 | 138 | 139 | int rtsp_transport_header_copy(const struct rtsp_transport_header *src, 140 | struct rtsp_transport_header *dst); 141 | 142 | 143 | int rtsp_transport_header_write(struct rtsp_transport_header *const *transport, 144 | unsigned int count, 145 | struct rtsp_string *str); 146 | 147 | 148 | int rtsp_transport_header_read(char *str, 149 | struct rtsp_transport_header **transport, 150 | unsigned int max_count, 151 | unsigned int *count); 152 | 153 | 154 | int rtsp_request_header_clear(struct rtsp_request_header *header); 155 | 156 | 157 | int rtsp_request_header_copy(const struct rtsp_request_header *src, 158 | struct rtsp_request_header *dst); 159 | 160 | 161 | int rtsp_request_header_copy_ext(struct rtsp_request_header *header, 162 | const struct rtsp_header_ext *ext, 163 | size_t ext_count); 164 | 165 | 166 | int rtsp_request_header_read(char *str, 167 | size_t len, 168 | struct rtsp_request_header *header, 169 | char **body); 170 | 171 | 172 | int rtsp_response_header_clear(struct rtsp_response_header *header); 173 | 174 | 175 | int rtsp_response_header_copy(const struct rtsp_response_header *src, 176 | struct rtsp_response_header *dst); 177 | 178 | 179 | int rtsp_response_header_copy_ext(struct rtsp_response_header *header, 180 | const struct rtsp_header_ext *ext, 181 | size_t ext_count); 182 | 183 | 184 | int rtsp_response_header_read(char *msg, 185 | size_t len, 186 | struct rtsp_response_header *header, 187 | char **body); 188 | 189 | 190 | #define CHECK_FUNC(_func, _ret, _on_err, ...) \ 191 | do { \ 192 | _ret = _func(__VA_ARGS__); \ 193 | if (_ret < 0) { \ 194 | ULOG_ERRNO(#_func, -_ret); \ 195 | _on_err; \ 196 | } \ 197 | } while (0) 198 | 199 | 200 | /* clang-format off */ 201 | __attribute__((__format__(__printf__, 2, 3))) 202 | static inline int rtsp_sprintf(struct rtsp_string *str, const char *fmt, ...) 203 | /* clang-format on */ 204 | { 205 | int len; 206 | if (str->len >= str->max_len) 207 | return -ENOBUFS; 208 | va_list args; 209 | va_start(args, fmt); 210 | len = vsnprintf( 211 | str->str + str->len, str->max_len - str->len, fmt, args); 212 | va_end(args); 213 | if (len < 0) 214 | return len; 215 | if (len >= (signed)(str->max_len - str->len)) 216 | return -ENOBUFS; 217 | str->len += len; 218 | return 0; 219 | } 220 | 221 | 222 | static inline void xfree(void **ptr) 223 | { 224 | if (ptr) { 225 | free(*ptr); 226 | *ptr = NULL; 227 | } 228 | } 229 | 230 | 231 | static inline char *xstrdup(const char *s) 232 | { 233 | return s == NULL ? NULL : strdup(s); 234 | } 235 | 236 | 237 | static inline void get_time_with_ms_delay(struct timespec *ts, 238 | unsigned int delay) 239 | { 240 | struct timeval tp; 241 | gettimeofday(&tp, NULL); 242 | 243 | struct timespec ts2; 244 | time_timeval_to_timespec(&tp, &ts2); 245 | time_timespec_add_us(&ts2, (int64_t)delay * 1000, ts); 246 | } 247 | 248 | 249 | static inline char get_last_char(const char *str) 250 | { 251 | return str[strlen(str) - 1]; 252 | } 253 | 254 | 255 | #endif /* !_RTSP_PRIV_H_ */ 256 | -------------------------------------------------------------------------------- /src/rtsp_client_session.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #include "rtsp_client_priv.h" 29 | 30 | #define ULOG_TAG rtsp_client 31 | #include 32 | 33 | 34 | struct rtsp_client_session *rtsp_client_get_session(struct rtsp_client *client, 35 | const char *session_id, 36 | int add) 37 | { 38 | struct rtsp_client_session *session; 39 | 40 | ULOG_ERRNO_RETURN_VAL_IF(client == NULL, EINVAL, NULL); 41 | ULOG_ERRNO_RETURN_VAL_IF(session_id == NULL, EINVAL, NULL); 42 | 43 | /* Search for a session with the same id */ 44 | session = rtsp_client_session_find(client, session_id); 45 | if (session != NULL) 46 | return session; 47 | 48 | /* If not found and no add requested, just return NULL */ 49 | if (!add) 50 | return NULL; 51 | 52 | /* Otherwise create and add the session */ 53 | session = calloc(1, sizeof(struct rtsp_client_session)); 54 | if (!session) { 55 | ULOG_ERRNO("calloc", ENOMEM); 56 | return NULL; 57 | } 58 | session->id = xstrdup(session_id); 59 | if (!session->id) { 60 | ULOG_ERRNO("xstrdup", ENOMEM); 61 | goto error; 62 | } 63 | session->client = client; 64 | session->timer = pomp_timer_new( 65 | client->loop, rtsp_client_pomp_timer_cb, session); 66 | if (!session->timer) { 67 | ULOG_ERRNO("pomp_timer_new", ENOMEM); 68 | goto error; 69 | } 70 | 71 | list_node_unref(&session->node); 72 | list_init(&session->medias); 73 | list_add_before(&client->sessions, &session->node); 74 | 75 | ULOGI("client session %s added", session->id); 76 | 77 | return session; 78 | error: 79 | free(session->id); 80 | free(session); 81 | return NULL; 82 | } 83 | 84 | 85 | int rtsp_client_remove_session_internal(struct rtsp_client *client, 86 | const char *session_id, 87 | int status_code, 88 | int nexist_ok) 89 | { 90 | struct rtsp_client_session *session; 91 | struct rtsp_client_session_media *media = NULL, *tmp_media = NULL; 92 | int status = 0; 93 | 94 | if (!client || !session_id) 95 | return -EINVAL; 96 | 97 | session = rtsp_client_session_find(client, session_id); 98 | if (session == NULL) 99 | return nexist_ok ? 0 : -ENOENT; 100 | 101 | /* Remove all medias */ 102 | list_walk_entry_forward_safe(&session->medias, media, tmp_media, node) 103 | { 104 | rtsp_client_session_media_remove(client, session, media); 105 | } 106 | 107 | /* Convert RTSP status code to errno */ 108 | status = rtsp_status_to_errno(status_code); 109 | 110 | ULOGI("client session %s removed", session->id); 111 | 112 | (*client->cbs.session_removed)( 113 | client, session->id, status, client->cbs_userdata); 114 | 115 | list_del(&session->node); 116 | pomp_timer_clear(session->timer); 117 | pomp_timer_destroy(session->timer); 118 | free(session->content_base); 119 | free(session->id); 120 | free(session); 121 | return 0; 122 | } 123 | 124 | 125 | void rtsp_client_remove_all_sessions(struct rtsp_client *client) 126 | { 127 | struct rtsp_client_session *session, *tmp; 128 | 129 | if (!client) 130 | return; 131 | 132 | list_walk_entry_forward_safe(&client->sessions, session, tmp, node) 133 | { 134 | rtsp_client_remove_session_internal(client, session->id, 0, 0); 135 | } 136 | } 137 | 138 | 139 | struct rtsp_client_session *rtsp_client_session_find(struct rtsp_client *client, 140 | const char *session_id) 141 | { 142 | int found = 0; 143 | struct rtsp_client_session *session = NULL; 144 | 145 | ULOG_ERRNO_RETURN_VAL_IF(client == NULL, EINVAL, NULL); 146 | ULOG_ERRNO_RETURN_VAL_IF(session_id == NULL, EINVAL, NULL); 147 | 148 | list_walk_entry_forward(&client->sessions, session, node) 149 | { 150 | if (session->id != NULL && 151 | strncmp(session->id, 152 | session_id, 153 | RTSP_CLIENT_SESSION_ID_LENGTH) == 0) { 154 | found = 1; 155 | break; 156 | } 157 | } 158 | 159 | return (found) ? session : NULL; 160 | } 161 | 162 | 163 | struct rtsp_client_session_media * 164 | rtsp_client_session_media_add(struct rtsp_client *client, 165 | struct rtsp_client_session *session, 166 | const char *path) 167 | { 168 | struct rtsp_client_session_media *media = NULL, *_media; 169 | 170 | ULOG_ERRNO_RETURN_VAL_IF(client == NULL, EINVAL, NULL); 171 | ULOG_ERRNO_RETURN_VAL_IF(session == NULL, EINVAL, NULL); 172 | ULOG_ERRNO_RETURN_VAL_IF(path == NULL, EINVAL, NULL); 173 | 174 | _media = rtsp_client_session_media_find(client, session, path); 175 | ULOG_ERRNO_RETURN_VAL_IF(_media != NULL, EEXIST, NULL); 176 | 177 | media = calloc(1, sizeof(*media)); 178 | ULOG_ERRNO_RETURN_VAL_IF(media == NULL, ENOMEM, NULL); 179 | list_node_unref(&media->node); 180 | media->session = session; 181 | media->path = strdup(path); 182 | 183 | /* Add to the list */ 184 | list_add_before(&session->medias, &media->node); 185 | session->media_count++; 186 | 187 | ULOGI("client session %s media '%s' added", session->id, path); 188 | 189 | return media; 190 | } 191 | 192 | 193 | int rtsp_client_session_media_remove(struct rtsp_client *client, 194 | struct rtsp_client_session *session, 195 | struct rtsp_client_session_media *media) 196 | { 197 | int found = 0; 198 | struct rtsp_client_session_media *_media = NULL; 199 | 200 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 201 | ULOG_ERRNO_RETURN_ERR_IF(session == NULL, EINVAL); 202 | ULOG_ERRNO_RETURN_ERR_IF(media == NULL, EINVAL); 203 | 204 | list_walk_entry_forward(&session->medias, _media, node) 205 | { 206 | if (_media == media) { 207 | found = 1; 208 | break; 209 | } 210 | } 211 | 212 | if (!found) { 213 | ULOGE("%s: media not found", __func__); 214 | return -ENOENT; 215 | } 216 | 217 | /* Remove from the list */ 218 | list_del(&media->node); 219 | session->media_count--; 220 | 221 | ULOGI("client session %s media '%s' removed", session->id, media->path); 222 | 223 | free(media->path); 224 | free(media); 225 | 226 | return 0; 227 | } 228 | 229 | 230 | struct rtsp_client_session_media * 231 | rtsp_client_session_media_find(struct rtsp_client *client, 232 | struct rtsp_client_session *session, 233 | const char *path) 234 | { 235 | int found = 0; 236 | struct rtsp_client_session_media *media = NULL; 237 | 238 | ULOG_ERRNO_RETURN_VAL_IF(client == NULL, EINVAL, NULL); 239 | ULOG_ERRNO_RETURN_VAL_IF(session == NULL, EINVAL, NULL); 240 | ULOG_ERRNO_RETURN_VAL_IF(path == NULL, EINVAL, NULL); 241 | 242 | list_walk_entry_forward(&session->medias, media, node) 243 | { 244 | if (strcmp(media->path, path) == 0) { 245 | found = 1; 246 | break; 247 | } 248 | } 249 | 250 | return (found) ? media : NULL; 251 | } 252 | -------------------------------------------------------------------------------- /include/rtsp/client.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #ifndef _RTSP_CLIENT_H_ 29 | #define _RTSP_CLIENT_H_ 30 | 31 | #include 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif /* __cplusplus */ 36 | 37 | 38 | #define RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS 4000 39 | 40 | 41 | struct rtsp_client; 42 | 43 | 44 | enum rtsp_client_conn_state { 45 | RTSP_CLIENT_CONN_STATE_DISCONNECTED = 0, 46 | RTSP_CLIENT_CONN_STATE_CONNECTING, 47 | RTSP_CLIENT_CONN_STATE_CONNECTED, 48 | RTSP_CLIENT_CONN_STATE_DISCONNECTING, 49 | }; 50 | 51 | 52 | /* Request status */ 53 | enum rtsp_client_req_status { 54 | /* Request succeeded */ 55 | RTSP_CLIENT_REQ_STATUS_OK = 0, 56 | /* Request canceled by the user */ 57 | RTSP_CLIENT_REQ_STATUS_CANCELED, 58 | /* Request failed */ 59 | RTSP_CLIENT_REQ_STATUS_FAILED, 60 | /* Request aborted by disconnection, no more requests can be sent */ 61 | RTSP_CLIENT_REQ_STATUS_ABORTED, 62 | /* No response to request received */ 63 | RTSP_CLIENT_REQ_STATUS_TIMEOUT, 64 | }; 65 | 66 | 67 | struct rtsp_client_cbs { 68 | void (*socket_cb)(int fd, void *userdata); 69 | 70 | /* Called only for states CONNECTED and DISCONNECTED */ 71 | void (*connection_state)(struct rtsp_client *client, 72 | enum rtsp_client_conn_state state, 73 | void *userdata); 74 | 75 | void (*session_removed)(struct rtsp_client *client, 76 | const char *session_id, 77 | int status, 78 | void *userdata); 79 | 80 | void (*options_resp)(struct rtsp_client *client, 81 | enum rtsp_client_req_status req_status, 82 | int status, 83 | uint32_t methods, 84 | const struct rtsp_header_ext *ext, 85 | size_t ext_count, 86 | void *userdata, 87 | void *req_userdata); 88 | 89 | void (*describe_resp)(struct rtsp_client *client, 90 | enum rtsp_client_req_status req_status, 91 | int status, 92 | const char *content_base, 93 | const struct rtsp_header_ext *ext, 94 | size_t ext_count, 95 | const char *sdp, 96 | void *userdata, 97 | void *req_userdata); 98 | 99 | void (*setup_resp)(struct rtsp_client *client, 100 | const char *session_id, 101 | enum rtsp_client_req_status req_status, 102 | int status, 103 | uint16_t src_stream_port, 104 | uint16_t src_control_port, 105 | int ssrc_valid, 106 | uint32_t ssrc, 107 | const struct rtsp_header_ext *ext, 108 | size_t ext_count, 109 | void *userdata, 110 | void *req_userdata); 111 | 112 | void (*play_resp)(struct rtsp_client *client, 113 | const char *session_id, 114 | enum rtsp_client_req_status req_status, 115 | int status, 116 | const struct rtsp_range *range, 117 | float scale, 118 | int seq_valid, 119 | uint16_t seq, 120 | int rtptime_valid, 121 | uint32_t rtptime, 122 | const struct rtsp_header_ext *ext, 123 | size_t ext_count, 124 | void *userdata, 125 | void *req_userdata); 126 | 127 | void (*pause_resp)(struct rtsp_client *client, 128 | const char *session_id, 129 | enum rtsp_client_req_status req_status, 130 | int status, 131 | const struct rtsp_range *range, 132 | const struct rtsp_header_ext *ext, 133 | size_t ext_count, 134 | void *userdata, 135 | void *req_userdata); 136 | 137 | void (*teardown_resp)(struct rtsp_client *client, 138 | const char *session_id, 139 | enum rtsp_client_req_status req_status, 140 | int status, 141 | const struct rtsp_header_ext *ext, 142 | size_t ext_count, 143 | void *userdata, 144 | void *req_userdata); 145 | 146 | void (*announce)(struct rtsp_client *client, 147 | const char *content_base, 148 | const struct rtsp_header_ext *ext, 149 | size_t ext_count, 150 | const char *sdp, 151 | void *userdata); 152 | 153 | void (*teardown)(struct rtsp_client *client, 154 | const char *path, 155 | const char *session_id, 156 | const struct rtsp_header_ext *ext, 157 | size_t ext_count, 158 | void *userdata); 159 | }; 160 | 161 | 162 | RTSP_API int rtsp_client_new(struct pomp_loop *loop, 163 | const char *software_name, 164 | const struct rtsp_client_cbs *cbs, 165 | void *userdata, 166 | struct rtsp_client **ret_obj); 167 | 168 | 169 | RTSP_API int rtsp_client_destroy(struct rtsp_client *client); 170 | 171 | 172 | RTSP_API int rtsp_client_connect(struct rtsp_client *client, const char *addr); 173 | 174 | 175 | RTSP_API int rtsp_client_disconnect(struct rtsp_client *client); 176 | 177 | 178 | RTSP_API int rtsp_client_options(struct rtsp_client *client, 179 | const struct rtsp_header_ext *ext, 180 | size_t ext_count, 181 | void *req_userdata, 182 | unsigned int timeout_ms); 183 | 184 | 185 | RTSP_API int rtsp_client_describe(struct rtsp_client *client, 186 | const char *path, 187 | const struct rtsp_header_ext *ext, 188 | size_t ext_count, 189 | void *req_userdata, 190 | unsigned int timeout_ms); 191 | 192 | 193 | RTSP_API int rtsp_client_setup(struct rtsp_client *client, 194 | const char *content_base, 195 | const char *resource_url, 196 | const char *session_id, 197 | enum rtsp_delivery delivery, 198 | enum rtsp_lower_transport lower_transport, 199 | uint16_t dst_stream_port, 200 | uint16_t dst_control_port, 201 | const struct rtsp_header_ext *ext, 202 | size_t ext_count, 203 | void *req_userdata, 204 | unsigned int timeout_ms); 205 | 206 | 207 | RTSP_API int rtsp_client_play(struct rtsp_client *client, 208 | const char *session_id, 209 | const struct rtsp_range *range, 210 | float scale, 211 | const struct rtsp_header_ext *ext, 212 | size_t ext_count, 213 | void *req_userdata, 214 | unsigned int timeout_ms); 215 | 216 | 217 | RTSP_API int rtsp_client_pause(struct rtsp_client *client, 218 | const char *session_id, 219 | const struct rtsp_range *range, 220 | const struct rtsp_header_ext *ext, 221 | size_t ext_count, 222 | void *req_userdata, 223 | unsigned int timeout_ms); 224 | 225 | 226 | RTSP_API int rtsp_client_teardown(struct rtsp_client *client, 227 | const char *resource_url, 228 | const char *session_id, 229 | const struct rtsp_header_ext *ext, 230 | size_t ext_count, 231 | void *req_userdata, 232 | unsigned int timeout_ms); 233 | 234 | 235 | RTSP_API int rtsp_client_remove_session(struct rtsp_client *client, 236 | const char *session_id); 237 | 238 | 239 | RTSP_API int rtsp_client_cancel(struct rtsp_client *client); 240 | 241 | 242 | RTSP_API const char * 243 | rtsp_client_conn_state_str(enum rtsp_client_conn_state val); 244 | 245 | 246 | RTSP_API const char * 247 | rtsp_client_req_status_str(enum rtsp_client_req_status val); 248 | 249 | 250 | #ifdef __cplusplus 251 | } 252 | #endif /* __cplusplus */ 253 | 254 | #endif /* !_RTSP_CLIENT_H_ */ 255 | -------------------------------------------------------------------------------- /src/rtsp_server_session.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #include "rtsp_server_priv.h" 29 | 30 | #define ULOG_TAG rtsp_server 31 | #include 32 | 33 | 34 | struct rtsp_server_session *rtsp_server_session_add(struct rtsp_server *server, 35 | unsigned int timeout_ms, 36 | const char *uri) 37 | { 38 | int ret; 39 | int found; 40 | struct rtsp_server_session *session = NULL, *_session = NULL; 41 | 42 | ULOG_ERRNO_RETURN_VAL_IF(server == NULL, EINVAL, NULL); 43 | 44 | session = calloc(1, sizeof(*session)); 45 | ULOG_ERRNO_RETURN_VAL_IF(session == NULL, ENOMEM, NULL); 46 | list_node_unref(&session->node); 47 | list_init(&session->medias); 48 | session->server = server; 49 | session->timeout_ms = timeout_ms; 50 | if (session->timeout_ms > 0) { 51 | session->timer = pomp_timer_new(server->loop, 52 | &rtsp_server_session_timer_cb, 53 | (void *)session); 54 | if (!session->timer) { 55 | ULOG_ERRNO("pomp_timer_new", ENOMEM); 56 | goto error; 57 | } 58 | ret = rtsp_server_session_reset_timeout(session); 59 | if (ret < 0) 60 | goto error; 61 | } 62 | 63 | do { 64 | /* Generate a session id */ 65 | uint64_t id64; 66 | ret = futils_random64(&id64); 67 | if (ret < 0) { 68 | ULOG_ERRNO("futils_random64", -ret); 69 | goto error; 70 | } 71 | ret = asprintf(&session->session_id, "%016" PRIx64, id64); 72 | if ((ret < 0) || (session->session_id == NULL)) { 73 | ULOG_ERRNO("asprintf", ENOMEM); 74 | goto error; 75 | } 76 | 77 | /* Check that this session id does not already exist */ 78 | found = 0; 79 | list_walk_entry_forward(&server->sessions, _session, node) 80 | { 81 | if (strncmp(_session->session_id, 82 | session->session_id, 83 | RTSP_SERVER_SESSION_ID_LENGTH) == 0) { 84 | found = 1; 85 | xfree((void **)&session->session_id); 86 | break; 87 | } 88 | } 89 | } while (found); 90 | 91 | /* store the URI */ 92 | session->uri = xstrdup(uri); 93 | 94 | /* Add to the list */ 95 | list_add_before(&server->sessions, &session->node); 96 | server->session_count++; 97 | 98 | ULOGI("server session %s added (URI='%s')", 99 | session->session_id, 100 | session->uri); 101 | 102 | return session; 103 | 104 | error: 105 | if (session) { 106 | if (session->timer != NULL) { 107 | ret = pomp_timer_destroy(session->timer); 108 | if (ret < 0) 109 | ULOG_ERRNO("pomp_timer_destroy", -ret); 110 | } 111 | free(session->session_id); 112 | free(session); 113 | } 114 | 115 | return NULL; 116 | } 117 | 118 | 119 | int rtsp_server_session_remove(struct rtsp_server *server, 120 | struct rtsp_server_session *session) 121 | { 122 | int found = 0, ret; 123 | struct rtsp_server_session *_session = NULL; 124 | struct rtsp_server_session_media *media = NULL, *tmp_media = NULL; 125 | 126 | ULOG_ERRNO_RETURN_ERR_IF(server == NULL, EINVAL); 127 | ULOG_ERRNO_RETURN_ERR_IF(session == NULL, EINVAL); 128 | 129 | list_walk_entry_forward(&server->sessions, _session, node) 130 | { 131 | if (_session == session) { 132 | found = 1; 133 | break; 134 | } 135 | } 136 | 137 | if (!found) { 138 | ULOGE("%s: session not found", __func__); 139 | return -ENOENT; 140 | } 141 | 142 | /* Remove all medias */ 143 | list_walk_entry_forward_safe(&session->medias, media, tmp_media, node) 144 | { 145 | rtsp_server_session_media_remove(server, session, media); 146 | } 147 | 148 | ULOGI("server session %s removed (URI='%s')", 149 | session->session_id, 150 | session->uri); 151 | 152 | /* Remove from the list */ 153 | list_del(&session->node); 154 | server->session_count--; 155 | 156 | if (session->timer != NULL) { 157 | ret = pomp_timer_destroy(session->timer); 158 | if (ret < 0) 159 | ULOG_ERRNO("pomp_timer_destroy", -ret); 160 | } 161 | ret = pomp_loop_idle_remove( 162 | server->loop, &rtsp_server_session_remove_idle, session); 163 | if (ret < 0) 164 | ULOG_ERRNO("pomp_loop_idle_remove", -ret); 165 | free(session->session_id); 166 | free(session->uri); 167 | free(session); 168 | 169 | return 0; 170 | } 171 | 172 | 173 | int rtsp_server_session_reset_timeout(struct rtsp_server_session *session) 174 | { 175 | int ret; 176 | 177 | ULOG_ERRNO_RETURN_ERR_IF(session == NULL, EINVAL); 178 | 179 | /* Set the timer to >= 20% more than the advertised session timeout 180 | * because some players (like VLC) will only send GET_PARAMETER 181 | * request every 'timeout_ms' ms, which can cause timeouts here 182 | * otherwise due to latency */ 183 | ret = pomp_timer_set(session->timer, 184 | ((12 * session->timeout_ms) + 9) / 10); 185 | if (ret < 0) 186 | ULOG_ERRNO("pomp_timer_set", -ret); 187 | 188 | return ret; 189 | } 190 | 191 | 192 | struct rtsp_server_session *rtsp_server_session_find(struct rtsp_server *server, 193 | const char *session_id) 194 | { 195 | int found = 0; 196 | struct rtsp_server_session *session = NULL; 197 | 198 | ULOG_ERRNO_RETURN_VAL_IF(server == NULL, EINVAL, NULL); 199 | ULOG_ERRNO_RETURN_VAL_IF(session_id == NULL, EINVAL, NULL); 200 | 201 | list_walk_entry_forward(&server->sessions, session, node) 202 | { 203 | if (session->session_id != NULL && 204 | strncmp(session->session_id, 205 | session_id, 206 | RTSP_SERVER_SESSION_ID_LENGTH) == 0) { 207 | found = 1; 208 | break; 209 | } 210 | } 211 | 212 | return (found) ? session : NULL; 213 | } 214 | 215 | 216 | struct rtsp_server_session_media * 217 | rtsp_server_session_media_add(struct rtsp_server *server, 218 | struct rtsp_server_session *session, 219 | const char *uri, 220 | const char *path) 221 | { 222 | struct rtsp_server_session_media *media = NULL, *_media; 223 | 224 | ULOG_ERRNO_RETURN_VAL_IF(server == NULL, EINVAL, NULL); 225 | ULOG_ERRNO_RETURN_VAL_IF(session == NULL, EINVAL, NULL); 226 | ULOG_ERRNO_RETURN_VAL_IF(path == NULL, EINVAL, NULL); 227 | 228 | _media = rtsp_server_session_media_find(server, session, path); 229 | ULOG_ERRNO_RETURN_VAL_IF(_media != NULL, EEXIST, NULL); 230 | 231 | media = calloc(1, sizeof(*media)); 232 | ULOG_ERRNO_RETURN_VAL_IF(media == NULL, ENOMEM, NULL); 233 | list_node_unref(&media->node); 234 | media->session = session; 235 | media->uri = strdup(uri); 236 | media->path = strdup(path); 237 | 238 | /* Add to the list */ 239 | list_add_before(&session->medias, &media->node); 240 | session->media_count++; 241 | 242 | ULOGI("server session %s media '%s' added", session->session_id, path); 243 | 244 | return media; 245 | } 246 | 247 | 248 | int rtsp_server_session_media_remove(struct rtsp_server *server, 249 | struct rtsp_server_session *session, 250 | struct rtsp_server_session_media *media) 251 | { 252 | int found = 0; 253 | struct rtsp_server_session_media *_media = NULL; 254 | 255 | ULOG_ERRNO_RETURN_ERR_IF(server == NULL, EINVAL); 256 | ULOG_ERRNO_RETURN_ERR_IF(session == NULL, EINVAL); 257 | ULOG_ERRNO_RETURN_ERR_IF(media == NULL, EINVAL); 258 | 259 | list_walk_entry_forward(&session->medias, _media, node) 260 | { 261 | if (_media == media) { 262 | found = 1; 263 | break; 264 | } 265 | } 266 | 267 | if (!found) { 268 | ULOGE("%s: media not found", __func__); 269 | return -ENOENT; 270 | } 271 | 272 | /* Remove from the list */ 273 | list_del(&media->node); 274 | session->media_count--; 275 | 276 | ULOGI("server session %s media '%s' removed", 277 | session->session_id, 278 | media->path); 279 | 280 | free(media->uri); 281 | free(media->path); 282 | free(media); 283 | 284 | return 0; 285 | } 286 | 287 | 288 | struct rtsp_server_session_media * 289 | rtsp_server_session_media_find(struct rtsp_server *server, 290 | struct rtsp_server_session *session, 291 | const char *path) 292 | { 293 | int found = 0; 294 | struct rtsp_server_session_media *media = NULL; 295 | 296 | ULOG_ERRNO_RETURN_VAL_IF(server == NULL, EINVAL, NULL); 297 | ULOG_ERRNO_RETURN_VAL_IF(session == NULL, EINVAL, NULL); 298 | ULOG_ERRNO_RETURN_VAL_IF(path == NULL, EINVAL, NULL); 299 | 300 | list_walk_entry_forward(&session->medias, media, node) 301 | { 302 | if (strcmp(media->path, path) == 0) { 303 | found = 1; 304 | break; 305 | } 306 | } 307 | 308 | return (found) ? media : NULL; 309 | } 310 | -------------------------------------------------------------------------------- /tests/rtsp_server_test.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #define ULOG_TAG rtsp_server_test 36 | #include 37 | ULOG_DECLARE_TAG(rtsp_server_test); 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | /* Win32 stubs */ 45 | #ifdef _WIN32 46 | static inline const char *strsignal(int signum) 47 | { 48 | return "??"; 49 | } 50 | #endif /* _WIN32 */ 51 | 52 | 53 | #define RESOURCE_PATH "live" 54 | #define MEDIA1_PATH "stream=0" 55 | #define MEDIA2_PATH "stream=1" 56 | 57 | 58 | static const struct rtsp_header_ext header_ext[] = { 59 | { 60 | .key = "X-com-parrot-test", 61 | .value = "server-test", 62 | }, 63 | }; 64 | 65 | 66 | static int s_stopping; 67 | struct pomp_loop *s_loop; 68 | struct rtsp_server *s_server; 69 | 70 | 71 | static void sighandler(int signum) 72 | { 73 | ULOGI("signal %d(%s) received, stopping", signum, strsignal(signum)); 74 | s_stopping = 1; 75 | if (s_loop) 76 | pomp_loop_wakeup(s_loop); 77 | signal(SIGINT, SIG_DFL); 78 | } 79 | 80 | 81 | static void socket_cb(int fd, void *userdata) 82 | { 83 | ULOGI("socket_cb called with fd=%d", fd); 84 | } 85 | 86 | 87 | static void describe_cb(struct rtsp_server *server, 88 | const char *server_address, 89 | const char *path, 90 | const struct rtsp_header_ext *ext, 91 | size_t ext_count, 92 | void *request_ctx, 93 | void *userdata) 94 | { 95 | int ret = 0, err; 96 | struct sdp_session *session = NULL; 97 | struct sdp_media *media1 = NULL, *media2 = NULL; 98 | char *sdp = NULL; 99 | 100 | if ((server_address == NULL) || (server_address[0] == '\0')) { 101 | ULOGE("%s: invalid server address", __func__); 102 | ret = -EINVAL; 103 | goto out; 104 | } 105 | if ((path == NULL) || (path[0] == '\0')) { 106 | ULOGE("%s: invalid path", __func__); 107 | ret = -EINVAL; 108 | goto out; 109 | } 110 | if (strcmp(path, RESOURCE_PATH) != 0) { 111 | ULOGE("%s: not found", __func__); 112 | ret = -ENOENT; 113 | goto out; 114 | } 115 | 116 | session = sdp_session_new(); 117 | if (session == NULL) { 118 | ret = -ENOMEM; 119 | ULOG_ERRNO("sdp_session_new", -ret); 120 | goto out; 121 | } 122 | session->session_id = 123456789; 123 | session->session_version = 1; 124 | session->server_addr = strdup(server_address); 125 | session->session_name = strdup("TestSession"); 126 | session->connection_addr = strdup("0.0.0.0"); 127 | session->control_url = strdup("*"); 128 | session->start_mode = SDP_START_MODE_RECVONLY; 129 | session->tool = strdup("RTSP server test"); 130 | session->type = strdup("broadcast"); 131 | 132 | ret = sdp_session_media_add(session, &media1); 133 | if (ret < 0) { 134 | ULOG_ERRNO("sdp_session_media_add", -ret); 135 | goto out; 136 | } 137 | media1->type = SDP_MEDIA_TYPE_VIDEO; 138 | media1->media_title = strdup("DefaultVideo"); 139 | media1->connection_addr = strdup("239.255.42.1"); 140 | media1->control_url = strdup(MEDIA1_PATH); 141 | media1->payload_type = 96; 142 | media1->encoding_name = strdup("H264"); 143 | media1->clock_rate = 90000; 144 | 145 | ret = sdp_session_media_add(session, &media2); 146 | if (ret < 0) { 147 | ULOG_ERRNO("sdp_session_media_add", -ret); 148 | goto out; 149 | } 150 | media2->type = SDP_MEDIA_TYPE_VIDEO; 151 | media2->media_title = strdup("SecondVideo"); 152 | media2->connection_addr = strdup("239.255.42.1"); 153 | media2->control_url = strdup(MEDIA2_PATH); 154 | media2->payload_type = 96; 155 | media2->encoding_name = strdup("H264"); 156 | media2->clock_rate = 90000; 157 | 158 | ret = sdp_description_write(session, &sdp); 159 | if (ret < 0) { 160 | ULOG_ERRNO("sdp_description_write", -ret); 161 | goto out; 162 | } 163 | 164 | out: 165 | err = rtsp_server_reply_to_describe( 166 | server, request_ctx, ret, header_ext, 1, sdp); 167 | if (err < 0) 168 | ULOG_ERRNO("rtsp_server_reply_to_describe", -err); 169 | if (session) 170 | sdp_session_destroy(session); 171 | free(sdp); 172 | } 173 | 174 | 175 | static void setup_cb(struct rtsp_server *server, 176 | const char *path, 177 | const char *session_id, 178 | const struct rtsp_header_ext *ext, 179 | size_t ext_count, 180 | void *request_ctx, 181 | void *media_ctx, 182 | enum rtsp_delivery delivery, 183 | enum rtsp_lower_transport lower_transport, 184 | const char *src_address, 185 | const char *dst_address, 186 | uint16_t dst_stream_port, 187 | uint16_t dst_control_port, 188 | void *userdata) 189 | { 190 | int ret = 0, err; 191 | uint32_t ssrc32 = 0; 192 | 193 | if ((path == NULL) || (path[0] == '\0')) { 194 | ULOGE("%s: invalid path", __func__); 195 | ret = -EINVAL; 196 | goto out; 197 | } 198 | if ((strcmp(path, RESOURCE_PATH "/" MEDIA1_PATH) != 0) && 199 | (strcmp(path, RESOURCE_PATH "/" MEDIA2_PATH) != 0)) { 200 | ULOGE("%s: not found", __func__); 201 | ret = -ENOENT; 202 | goto out; 203 | } 204 | if ((session_id == NULL) || (session_id[0] == '\0')) { 205 | ULOGE("%s: invalid session id", __func__); 206 | ret = -EINVAL; 207 | goto out; 208 | } 209 | if ((src_address == NULL) || (src_address[0] == '\0')) { 210 | ULOGE("%s: invalid destination address", __func__); 211 | ret = -EINVAL; 212 | goto out; 213 | } 214 | if ((dst_address == NULL) || (dst_address[0] == '\0')) { 215 | ULOGE("%s: invalid destination address", __func__); 216 | ret = -EINVAL; 217 | goto out; 218 | } 219 | if ((dst_stream_port == 0) || (dst_control_port == 0)) { 220 | ULOGE("%s: invalid client ports", __func__); 221 | ret = -EINVAL; 222 | goto out; 223 | } 224 | 225 | if (delivery != RTSP_DELIVERY_UNICAST) { 226 | ULOGE("%s: unsupported delivery", __func__); 227 | ret = -ENOSYS; 228 | goto out; 229 | } 230 | if (lower_transport != RTSP_LOWER_TRANSPORT_UDP) { 231 | ULOGE("%s: unsupported lower transport", __func__); 232 | ret = -ENOSYS; 233 | goto out; 234 | } 235 | 236 | ret = futils_random32(&ssrc32); 237 | if (ret < 0) { 238 | ULOG_ERRNO("futils_random32", -ret); 239 | goto out; 240 | } 241 | 242 | out: 243 | err = rtsp_server_reply_to_setup(server, 244 | request_ctx, 245 | media_ctx, 246 | ret, 247 | 5004, 248 | 5005, 249 | 1, 250 | ssrc32, 251 | header_ext, 252 | 1, 253 | (void *)((intptr_t)ssrc32)); 254 | if (err < 0) 255 | ULOG_ERRNO("rtsp_server_reply_to_setup", -err); 256 | } 257 | 258 | 259 | static void play_cb(struct rtsp_server *server, 260 | const char *session_id, 261 | const struct rtsp_header_ext *ext, 262 | size_t ext_count, 263 | void *request_ctx, 264 | void *media_ctx, 265 | const struct rtsp_range *range, 266 | float scale, 267 | void *stream_userdata, 268 | void *userdata) 269 | { 270 | int ret = 0, err; 271 | struct rtsp_range _range; 272 | uint16_t seq16 = 0; 273 | uint32_t time32 = 0; 274 | 275 | memset(&_range, 0, sizeof(struct rtsp_range)); 276 | 277 | if ((session_id == NULL) || (session_id[0] == '\0')) { 278 | ULOGE("%s: invalid session id", __func__); 279 | ret = -EINVAL; 280 | goto out; 281 | } 282 | if (range == NULL) { 283 | ULOGE("%s: invalid range pointer", __func__); 284 | ret = -EINVAL; 285 | goto out; 286 | } 287 | if (stream_userdata == NULL) { 288 | ULOGE("%s: invalid stream userdata pointer", __func__); 289 | ret = -EINVAL; 290 | goto out; 291 | } 292 | 293 | if (range->start.format != RTSP_TIME_FORMAT_NPT) { 294 | ULOGE("%s: unsupported range format", __func__); 295 | ret = -ENOSYS; 296 | goto out; 297 | } 298 | if (scale == 0.) 299 | scale = 1.; 300 | _range = *range; 301 | 302 | ret = futils_random32(&time32); 303 | if (ret < 0) { 304 | ULOG_ERRNO("futils_random32", -ret); 305 | goto out; 306 | } 307 | ret = futils_random16(&seq16); 308 | if (ret < 0) { 309 | ULOG_ERRNO("futils_random16", -ret); 310 | goto out; 311 | } 312 | 313 | out: 314 | err = rtsp_server_reply_to_play(server, 315 | request_ctx, 316 | media_ctx, 317 | ret, 318 | &_range, 319 | scale, 320 | 1, 321 | seq16, 322 | 1, 323 | time32, 324 | header_ext, 325 | 1); 326 | if (err < 0) 327 | ULOG_ERRNO("rtsp_server_reply_to_play", -err); 328 | } 329 | 330 | 331 | static void pause_cb(struct rtsp_server *server, 332 | const char *session_id, 333 | const struct rtsp_header_ext *ext, 334 | size_t ext_count, 335 | void *request_ctx, 336 | void *media_ctx, 337 | const struct rtsp_range *range, 338 | void *stream_userdata, 339 | void *userdata) 340 | { 341 | int ret = 0, err; 342 | struct rtsp_range _range; 343 | 344 | if ((session_id == NULL) || (session_id[0] == '\0')) { 345 | ULOGE("%s: invalid session id", __func__); 346 | ret = -EINVAL; 347 | goto out; 348 | } 349 | if (range == NULL) { 350 | ULOGE("%s: invalid range pointer", __func__); 351 | ret = -EINVAL; 352 | goto out; 353 | } 354 | if (stream_userdata == NULL) { 355 | ULOGE("%s: invalid stream userdata pointer", __func__); 356 | ret = -EINVAL; 357 | goto out; 358 | } 359 | 360 | _range = *range; 361 | 362 | out: 363 | err = rtsp_server_reply_to_pause( 364 | server, request_ctx, media_ctx, ret, &_range, header_ext, 1); 365 | if (err < 0) 366 | ULOG_ERRNO("rtsp_server_reply_to_pause", -err); 367 | } 368 | 369 | 370 | static void teardown_cb(struct rtsp_server *server, 371 | const char *path, 372 | const char *session_id, 373 | enum rtsp_server_teardown_reason reason, 374 | const struct rtsp_header_ext *ext, 375 | size_t ext_count, 376 | void *request_ctx, 377 | void *media_ctx, 378 | void *stream_userdata, 379 | void *userdata) 380 | { 381 | int ret = 0, err; 382 | 383 | if ((session_id == NULL) || (session_id[0] == '\0')) { 384 | ULOGE("%s: invalid session id", __func__); 385 | ret = -EINVAL; 386 | goto out; 387 | } 388 | if (stream_userdata == NULL) { 389 | ULOGE("%s: invalid stream userdata pointer", __func__); 390 | ret = -EINVAL; 391 | goto out; 392 | } 393 | 394 | out: 395 | if (request_ctx != NULL) { 396 | err = rtsp_server_reply_to_teardown( 397 | server, request_ctx, media_ctx, ret, header_ext, 1); 398 | if (err < 0) 399 | ULOG_ERRNO("rtsp_server_reply_to_teardown", -err); 400 | } 401 | } 402 | 403 | 404 | static void request_timeout_cb(struct rtsp_server *server, 405 | void *request_ctx, 406 | enum rtsp_method_type method, 407 | void *userdata) 408 | { 409 | ULOGI("%s", __func__); 410 | return; 411 | } 412 | 413 | 414 | static const struct rtsp_server_cbs cbs = { 415 | .socket_cb = &socket_cb, 416 | .describe = &describe_cb, 417 | .setup = &setup_cb, 418 | .play = &play_cb, 419 | .pause = &pause_cb, 420 | .teardown = &teardown_cb, 421 | .request_timeout = &request_timeout_cb, 422 | }; 423 | 424 | 425 | static void welcome(char *prog_name) 426 | { 427 | printf("\n%s - Real Time Streaming Protocol library " 428 | "server test program\n" 429 | "Copyright (c) 2017 Parrot Drones SAS\n" 430 | "Copyright (c) 2017 Aurelien Barre\n\n", 431 | prog_name); 432 | } 433 | 434 | 435 | static void usage(char *prog_name) 436 | { 437 | printf("Usage: %s \n", prog_name); 438 | } 439 | 440 | 441 | int main(int argc, char **argv) 442 | { 443 | int status = EXIT_SUCCESS, err; 444 | uint16_t port = 0; 445 | 446 | s_stopping = 0; 447 | s_loop = NULL; 448 | s_server = NULL; 449 | 450 | welcome(argv[0]); 451 | 452 | if (argc < 2) { 453 | usage(argv[0]); 454 | exit(EXIT_FAILURE); 455 | } 456 | 457 | port = atoi(argv[1]); 458 | 459 | signal(SIGINT, sighandler); 460 | 461 | s_loop = pomp_loop_new(); 462 | if (!s_loop) { 463 | ULOGE("pomp_loop_new() failed"); 464 | status = EXIT_FAILURE; 465 | goto cleanup; 466 | } 467 | 468 | err = rtsp_server_new(NULL, port, 0, 0, s_loop, &cbs, NULL, &s_server); 469 | if (err < 0) { 470 | ULOG_ERRNO("rtsp_server_new", -err); 471 | status = EXIT_FAILURE; 472 | goto cleanup; 473 | } 474 | 475 | printf("Server listening on port %d\n", port); 476 | printf("Connect to URL 'rtsp://:%d/%s'\n", port, RESOURCE_PATH); 477 | 478 | while (!s_stopping) 479 | pomp_loop_wait_and_process(s_loop, -1); 480 | 481 | printf("Server stopped\n"); 482 | 483 | cleanup: 484 | if (s_server) { 485 | err = rtsp_server_destroy(s_server); 486 | if (err) 487 | ULOG_ERRNO("rtsp_server_destroy", -err); 488 | } 489 | 490 | if (s_loop) { 491 | err = pomp_loop_destroy(s_loop); 492 | if (err) 493 | ULOG_ERRNO("pomp_loop_destroy", -err); 494 | } 495 | 496 | printf("%s\n", (status == EXIT_SUCCESS) ? "Done!" : "Failed!"); 497 | exit(status); 498 | } 499 | -------------------------------------------------------------------------------- /src/internal/rtsp/rtsp_internal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #ifndef _RTSP_INTERNAL_H_ 29 | #define _RTSP_INTERNAL_H_ 30 | 31 | #include 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif /* __cplusplus */ 36 | 37 | 38 | #define RTSP_VERSION "RTSP/1.0" 39 | 40 | #define RTSP_CRLF "\r\n" 41 | 42 | /* clang-format off */ 43 | #define RTSP_HEADER_ACCEPT "Accept" /* opt. */ 44 | #define RTSP_HEADER_ALLOW "Allow" /* opt. */ 45 | #define RTSP_HEADER_CONNECTION "Connection" /* TODO */ 46 | #define RTSP_HEADER_CONTENT_BASE "Content-Base" /* opt. */ 47 | #define RTSP_HEADER_CONTENT_ENCODING "Content-Encoding" 48 | #define RTSP_HEADER_CONTENT_LANGUAGE "Content-Language" 49 | #define RTSP_HEADER_CONTENT_LENGTH "Content-Length" 50 | #define RTSP_HEADER_CONTENT_LOCATION "Content-Location" /* opt. */ 51 | #define RTSP_HEADER_CONTENT_TYPE "Content-Type" 52 | #define RTSP_HEADER_CSEQ "CSeq" 53 | #define RTSP_HEADER_DATE "Date" /* opt. */ 54 | #define RTSP_HEADER_PROXY_REQUIRE "Proxy-Require" /* TODO */ 55 | #define RTSP_HEADER_PUBLIC "Public" /* opt. */ 56 | #define RTSP_HEADER_RANGE "Range" /* opt. */ 57 | #define RTSP_HEADER_REQUIRE "Require" /* TODO */ 58 | #define RTSP_HEADER_RTP_INFO "RTP-Info" 59 | #define RTSP_HEADER_SESSION "Session" 60 | #define RTSP_HEADER_SCALE "Scale" /* opt. */ 61 | #define RTSP_HEADER_SERVER "Server" /* opt. */ 62 | #define RTSP_HEADER_TRANSPORT "Transport" 63 | #define RTSP_HEADER_UNSUPPORTED "Unsupported" /* TODO */ 64 | #define RTSP_HEADER_USER_AGENT "User-Agent" /* opt. */ 65 | #define RTSP_HEADER_EXT "X-" /* opt. */ 66 | /* clang-format on */ 67 | 68 | #define RTSP_SESSION_TIMEOUT "timeout" 69 | 70 | #define RTSP_CONTENT_TYPE_SDP "application/sdp" 71 | 72 | #define RTSP_DATE_FORMAT_RFC1123 "%a, %d %b %Y %T %Z" 73 | 74 | 75 | /** 76 | * RTSP Range header 77 | * see RFC 2326 chapter 12.29 78 | */ 79 | 80 | #define RTSP_RANGE_TIME "time" 81 | 82 | #define RTSP_TIME_NPT "npt" 83 | #define RTSP_TIME_NPT_NOW "now" 84 | #define RTSP_TIME_SMPTE "smpte" 85 | #define RTSP_TIME_SMPTE_30_DROP "smpte-30-drop" 86 | #define RTSP_TIME_SMPTE_25 "smpte-25" 87 | #define RTSP_TIME_ABSOLUTE "clock" 88 | #define RTSP_TIME_FORMAT_ISO8601 "%Y%m%dT%H%M%SZ" 89 | 90 | 91 | /** 92 | * RTSP RTP-Info header 93 | * see RFC 2326 chapter 12.33 94 | */ 95 | 96 | #define RTSP_RTP_INFO_MAX_COUNT 10 97 | 98 | #define RTSP_RTP_INFO_URL "url" 99 | #define RTSP_RTP_INFO_SEQ "seq" 100 | #define RTSP_RTP_INFO_RTPTIME "rtptime" 101 | 102 | struct rtsp_rtp_info_header { 103 | char *url; 104 | int seq_valid; 105 | uint16_t seq; 106 | int rtptime_valid; 107 | uint32_t rtptime; 108 | }; 109 | 110 | 111 | /** 112 | * RTSP Transport header 113 | * see RFC 2326 chapter 12.39 114 | */ 115 | 116 | #define RTSP_TRANSPORT_MAX_COUNT 5 117 | 118 | /* clang-format off */ 119 | #define RTSP_TRANSPORT_PROTOCOL_RTP "RTP" 120 | #define RTSP_TRANSPORT_PROFILE_AVP "AVP" 121 | #define RTSP_TRANSPORT_LOWER_UDP "UDP" 122 | #define RTSP_TRANSPORT_LOWER_TCP "TCP" /* not supported */ 123 | #define RTSP_TRANSPORT_LOWER_MUX "MUX" /* Parrot-specific */ 124 | #define RTSP_TRANSPORT_UNICAST "unicast" 125 | #define RTSP_TRANSPORT_MULTICAST "multicast" 126 | #define RTSP_TRANSPORT_DESTINATION "destination" 127 | #define RTSP_TRANSPORT_SOURCE "source" 128 | #define RTSP_TRANSPORT_LAYERS "layers" 129 | #define RTSP_TRANSPORT_MODE "mode" 130 | #define RTSP_TRANSPORT_MODE_PLAY "PLAY" 131 | #define RTSP_TRANSPORT_MODE_RECORD "RECORD" 132 | #define RTSP_TRANSPORT_APPEND "append" 133 | #define RTSP_TRANSPORT_INTERLEAVED "interleaved" /* not supported */ 134 | #define RTSP_TRANSPORT_TTL "ttl" 135 | #define RTSP_TRANSPORT_PORT "port" 136 | #define RTSP_TRANSPORT_CLIENT_PORT "client_port" 137 | #define RTSP_TRANSPORT_SERVER_PORT "server_port" 138 | #define RTSP_TRANSPORT_SSRC "ssrc" 139 | /* clang-format on */ 140 | 141 | enum rtsp_transport_method { 142 | RTSP_TRANSPORT_METHOD_UNKNOWN = 0, 143 | RTSP_TRANSPORT_METHOD_PLAY, 144 | RTSP_TRANSPORT_METHOD_RECORD, 145 | }; 146 | 147 | struct rtsp_transport_header { 148 | char *transport_protocol; 149 | char *transport_profile; 150 | enum rtsp_lower_transport lower_transport; 151 | enum rtsp_delivery delivery; 152 | char *destination; 153 | char *source; 154 | unsigned int layers; 155 | enum rtsp_transport_method method; 156 | int append; 157 | unsigned int ttl; 158 | uint16_t src_stream_port; 159 | uint16_t src_control_port; 160 | uint16_t dst_stream_port; 161 | uint16_t dst_control_port; 162 | int ssrc_valid; 163 | uint32_t ssrc; 164 | }; 165 | 166 | 167 | /** 168 | * RTSP Request 169 | * see RFC 2326 chapter 6 170 | */ 171 | 172 | #define RTSP_METHOD_OPTIONS "OPTIONS" 173 | #define RTSP_METHOD_DESCRIBE "DESCRIBE" 174 | #define RTSP_METHOD_ANNOUNCE "ANNOUNCE" 175 | #define RTSP_METHOD_SETUP "SETUP" 176 | #define RTSP_METHOD_PLAY "PLAY" 177 | #define RTSP_METHOD_PAUSE "PAUSE" 178 | #define RTSP_METHOD_TEARDOWN "TEARDOWN" 179 | #define RTSP_METHOD_GET_PARAMETER "GET_PARAMETER" 180 | #define RTSP_METHOD_SET_PARAMETER "SET_PARAMETER" 181 | #define RTSP_METHOD_REDIRECT "REDIRECT" 182 | #define RTSP_METHOD_RECORD "RECORD" 183 | 184 | struct rtsp_request_header { 185 | /* Request line */ 186 | enum rtsp_method_type method; 187 | char *uri; 188 | 189 | /* General header */ 190 | int cseq; 191 | uint64_t date; 192 | char *session_id; 193 | unsigned int session_timeout; 194 | struct rtsp_transport_header *transport[RTSP_TRANSPORT_MAX_COUNT]; 195 | unsigned int transport_count; 196 | char *content_type; 197 | float scale; 198 | 199 | /* Request header */ 200 | char *user_agent; 201 | char *server; 202 | char *accept; 203 | struct rtsp_range range; 204 | 205 | /* Entity header */ 206 | int content_length; 207 | 208 | /* Header extensions */ 209 | struct rtsp_header_ext *ext; 210 | size_t ext_count; 211 | }; 212 | 213 | 214 | /** 215 | * RTSP Response 216 | * see RFC 2326 chapter 7 217 | */ 218 | 219 | #define RTSP_STATUS_CLASS_INFORMATIONAL 1 220 | #define RTSP_STATUS_CLASS_SUCCESS 2 221 | #define RTSP_STATUS_CLASS_REDIRECTION 3 222 | #define RTSP_STATUS_CLASS_CLIENT_ERROR 4 223 | #define RTSP_STATUS_CLASS_SERVER_ERROR 5 224 | 225 | #define RTSP_STATUS_CLASS(_status) ((_status) / 100) 226 | #define RTSP_STATUS_CODE(_status) (RTSP_STATUS_CODE_##_status) 227 | #define RTSP_STATUS_STRING(_status) (RTSP_STATUS_STRING_##_status) 228 | 229 | #define RTSP_STATUS_CODE_CONTINUE 100 230 | #define RTSP_STATUS_STRING_CONTINUE "Continue" 231 | #define RTSP_STATUS_CODE_OK 200 232 | #define RTSP_STATUS_STRING_OK "OK" 233 | #define RTSP_STATUS_CODE_CREATED 201 234 | #define RTSP_STATUS_STRING_CREATED "Created" 235 | #define RTSP_STATUS_CODE_LOW_ON_STORAGE 250 236 | #define RTSP_STATUS_STRING_LOW_ON_STORAGE "Low On Storage Space" 237 | #define RTSP_STATUS_CODE_MULTIPLE_CHOICES 300 238 | #define RTSP_STATUS_STRING_MULTIPLE_CHOICES "Multiple Choices" 239 | #define RTSP_STATUS_CODE_MOVED_PERMANENTLY 301 240 | #define RTSP_STATUS_STRING_MOVED_PERMANENTLY "Moved Permanently" 241 | #define RTSP_STATUS_CODE_MOVED_TEMPORARITY 302 242 | #define RTSP_STATUS_STRING_MOVED_TEMPORARITY "Moved Temporarily" 243 | #define RTSP_STATUS_CODE_SEE_OTHER 303 244 | #define RTSP_STATUS_STRING_SEE_OTHER "See Other" 245 | #define RTSP_STATUS_CODE_NOT_MODIFIED 304 246 | #define RTSP_STATUS_STRING_NOT_MODIFIED "Not Modified" 247 | #define RTSP_STATUS_CODE_USE_PROXY 305 248 | #define RTSP_STATUS_STRING_USE_PROXY "Use Proxy" 249 | #define RTSP_STATUS_CODE_BAD_REQUEST 400 250 | #define RTSP_STATUS_STRING_BAD_REQUEST "Bad Request" 251 | #define RTSP_STATUS_CODE_UNAUTHORIZED 401 252 | #define RTSP_STATUS_STRING_UNAUTHORIZED "Unauthorized" 253 | #define RTSP_STATUS_CODE_PAYMENT_REQUIRED 402 254 | #define RTSP_STATUS_STRING_PAYMENT_REQUIRED "Payment Required" 255 | #define RTSP_STATUS_CODE_FORBIDDEN 403 256 | #define RTSP_STATUS_STRING_FORBIDDEN "Forbidden" 257 | #define RTSP_STATUS_CODE_NOT_FOUND 404 258 | #define RTSP_STATUS_STRING_NOT_FOUND "Not Found" 259 | #define RTSP_STATUS_CODE_METHOD_NOT_ALLOWED 405 260 | #define RTSP_STATUS_STRING_METHOD_NOT_ALLOWED "Method Not Allowed" 261 | #define RTSP_STATUS_CODE_NOT_ACCEPTABLE 406 262 | #define RTSP_STATUS_STRING_NOT_ACCEPTABLE "Not Acceptable" 263 | #define RTSP_STATUS_CODE_PROXY_AUTHENTICATION_REQUIRED 407 264 | #define RTSP_STATUS_STRING_PROXY_AUTHENTICATION_REQUIRED \ 265 | "Proxy Authentication Required" 266 | #define RTSP_STATUS_CODE_REQUEST_TIMEOUT 408 267 | #define RTSP_STATUS_STRING_REQUEST_TIMEOUT "Request Time-out" 268 | #define RTSP_STATUS_CODE_GONE 410 269 | #define RTSP_STATUS_STRING_GONE "Gone" 270 | #define RTSP_STATUS_CODE_LENGTH_REQUIRED 411 271 | #define RTSP_STATUS_STRING_LENGTH_REQUIRED "Length Required" 272 | #define RTSP_STATUS_CODE_PRECONDITION_FAILED 412 273 | #define RTSP_STATUS_STRING_PRECONDITION_FAILED "Precondition Failed" 274 | #define RTSP_STATUS_CODE_REQUEST_ENTITY_TOO_LARGE 413 275 | #define RTSP_STATUS_STRING_REQUEST_ENTITY_TOO_LARGE "Request Entity Too Large" 276 | #define RTSP_STATUS_CODE_REQUEST_URI_TOO_LARGE 414 277 | #define RTSP_STATUS_STRING_REQUEST_URI_TOO_LARGE "Request-URI Too Large" 278 | #define RTSP_STATUS_CODE_UNSUPPORTED_MEDIA_TYPE 415 279 | #define RTSP_STATUS_STRING_UNSUPPORTED_MEDIA_TYPE "Unsupported Media Type" 280 | #define RTSP_STATUS_CODE_PARMETER_NOT_UNDERSTOOD 451 281 | #define RTSP_STATUS_STRING_PARMETER_NOT_UNDERSTOOD "Parameter Not Understood" 282 | #define RTSP_STATUS_CODE_CONFERENCE_NOT_FOUND 452 283 | #define RTSP_STATUS_STRING_CONFERENCE_NOT_FOUND "Conference Not Found" 284 | #define RTSP_STATUS_CODE_NOT_ENOUGH_BANDWIDTH 453 285 | #define RTSP_STATUS_STRING_NOT_ENOUGH_BANDWIDTH "Not Enough Bandwidth" 286 | #define RTSP_STATUS_CODE_SESSION_NOT_FOUND 454 287 | #define RTSP_STATUS_STRING_SESSION_NOT_FOUND "Session Not Found" 288 | #define RTSP_STATUS_CODE_METHOD_NOT_VALID 455 289 | #define RTSP_STATUS_STRING_METHOD_NOT_VALID "Method Not Valid In This State" 290 | #define RTSP_STATUS_CODE_HEADER_FIELD_NOT_VALID 456 291 | #define RTSP_STATUS_STRING_HEADER_FIELD_NOT_VALID \ 292 | "Header Field Not Valid For Resource" 293 | #define RTSP_STATUS_CODE_INVALID_RANGE 457 294 | #define RTSP_STATUS_STRING_INVALID_RANGE "Invalid Range" 295 | #define RTSP_STATUS_CODE_PARAMETER_READ_ONLY 458 296 | #define RTSP_STATUS_STRING_PARAMETER_READ_ONLY "Parameter Is Read-Only" 297 | #define RTSP_STATUS_CODE_AGGREGATE_OPERATION_NOT_ALLOWED 459 298 | #define RTSP_STATUS_STRING_AGGREGATE_OPERATION_NOT_ALLOWED \ 299 | "Aggregate Operation Not Allowed" 300 | #define RTSP_STATUS_CODE_ONLY_AGGREGATE_OPERATION_ALLOWED 460 301 | #define RTSP_STATUS_STRING_ONLY_AGGREGATE_OPERATION_ALLOWED \ 302 | "Only Aggregate Operation Allowed" 303 | #define RTSP_STATUS_CODE_UNSUPPORTED_TRANSPORT 461 304 | #define RTSP_STATUS_STRING_UNSUPPORTED_TRANSPORT "Unsupported Transport" 305 | #define RTSP_STATUS_CODE_DESTINATION_UNREACHABLE 462 306 | #define RTSP_STATUS_STRING_DESTINATION_UNREACHABLE "Destination Unreachable" 307 | #define RTSP_STATUS_CODE_INTERNAL_SERVER_ERROR 500 308 | #define RTSP_STATUS_STRING_INTERNAL_SERVER_ERROR "Internal Server Error" 309 | #define RTSP_STATUS_CODE_NOT_IMPLEMENTED 501 310 | #define RTSP_STATUS_STRING_NOT_IMPLEMENTED "Not Implemented" 311 | #define RTSP_STATUS_CODE_BAD_GATEWAY 502 312 | #define RTSP_STATUS_STRING_BAD_GATEWAY "Bad Gateway" 313 | #define RTSP_STATUS_CODE_SERVICE_UNAVAILABLE 503 314 | #define RTSP_STATUS_STRING_SERVICE_UNAVAILABLE "Service Unavailable" 315 | #define RTSP_STATUS_CODE_GATEWAY_TIMEOUT 504 316 | #define RTSP_STATUS_STRING_GATEWAY_TIMEOUT "Gateway Time-out" 317 | #define RTSP_STATUS_CODE_RTSP_VERSION_NOT_SUPPORTED 505 318 | #define RTSP_STATUS_STRING_RTSP_VERSION_NOT_SUPPORTED \ 319 | "RTSP Version Not Supported" 320 | #define RTSP_STATUS_CODE_OPTION_NOT_SUPPORTED 551 321 | #define RTSP_STATUS_STRING_OPTION_NOT_SUPPORTED "Option Not Supported" 322 | 323 | struct rtsp_response_header { 324 | /* Status line */ 325 | int status_code; 326 | char *status_string; 327 | 328 | /* General header */ 329 | int cseq; 330 | uint64_t date; 331 | char *session_id; 332 | unsigned int session_timeout; 333 | struct rtsp_transport_header *transport; 334 | char *content_type; 335 | float scale; 336 | 337 | /* Response header */ 338 | uint32_t public_methods; 339 | uint32_t allowed_methods; 340 | struct rtsp_rtp_info_header *rtp_info[RTSP_RTP_INFO_MAX_COUNT]; 341 | unsigned int rtp_info_count; 342 | char *server; 343 | struct rtsp_range range; 344 | 345 | /* Entity header */ 346 | int content_length; 347 | char *content_encoding; 348 | char *content_language; 349 | char *content_base; 350 | char *content_location; 351 | 352 | /* Header extensions */ 353 | struct rtsp_header_ext *ext; 354 | size_t ext_count; 355 | }; 356 | 357 | 358 | /** 359 | * RTSP message 360 | */ 361 | 362 | enum rtsp_message_type { 363 | RTSP_MESSAGE_TYPE_UNKNOWN = 0, 364 | RTSP_MESSAGE_TYPE_REQUEST, 365 | RTSP_MESSAGE_TYPE_RESPONSE, 366 | }; 367 | 368 | struct rtsp_message { 369 | union { 370 | struct rtsp_request_header req; 371 | struct rtsp_response_header resp; 372 | } header; 373 | enum rtsp_message_type type; 374 | 375 | char *body; 376 | size_t body_len; 377 | size_t total_len; 378 | }; 379 | 380 | struct rtsp_message_parser_ctx { 381 | struct rtsp_message msg; 382 | size_t header_len; 383 | }; 384 | 385 | 386 | struct rtsp_string { 387 | char *str; 388 | size_t len; 389 | size_t max_len; 390 | }; 391 | 392 | 393 | RTSP_API int rtsp_get_next_message(struct pomp_buffer *data, 394 | struct rtsp_message *msg, 395 | struct rtsp_message_parser_ctx *ctx); 396 | 397 | 398 | RTSP_API void rtsp_buffer_remove_first_bytes(struct pomp_buffer *buffer, 399 | size_t count); 400 | 401 | 402 | RTSP_API void rtsp_message_clear(struct rtsp_message *msg); 403 | 404 | 405 | RTSP_API int rtsp_request_header_write(const struct rtsp_request_header *header, 406 | struct rtsp_string *str); 407 | 408 | 409 | RTSP_API int 410 | rtsp_response_header_write(const struct rtsp_response_header *header, 411 | struct rtsp_string *str); 412 | 413 | 414 | #ifdef __cplusplus 415 | } 416 | #endif /* __cplusplus */ 417 | 418 | #endif /* !_RTSP_INTERNAL_H_ */ 419 | -------------------------------------------------------------------------------- /tests/rtsp_client_test.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #define ULOG_TAG rtsp_client_test 36 | #include 37 | ULOG_DECLARE_TAG(rtsp_client_test); 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | /* Win32 stubs */ 44 | #ifdef _WIN32 45 | static inline const char *strsignal(int signum) 46 | { 47 | return "??"; 48 | } 49 | #endif /* _WIN32 */ 50 | 51 | 52 | static const struct rtsp_header_ext header_ext[] = { 53 | { 54 | .key = "X-com-parrot-test", 55 | .value = "client-test", 56 | }, 57 | }; 58 | 59 | 60 | struct app { 61 | struct pomp_loop *loop; 62 | struct pomp_timer *timer; 63 | int stopped; 64 | struct rtsp_client *client; 65 | const char *session_id; 66 | char *addr; 67 | char *path; 68 | struct { 69 | int cancel_enable; 70 | int pause_enable; 71 | int failed_enable; 72 | } tests; 73 | }; 74 | 75 | static struct app s_app; 76 | 77 | 78 | static void sig_handler(int signum) 79 | { 80 | ULOGI("signal %d(%s) received, stopping", signum, strsignal(signum)); 81 | s_app.stopped = 1; 82 | if (s_app.loop != NULL) 83 | pomp_loop_wakeup(s_app.loop); 84 | } 85 | 86 | 87 | static void options_req(struct app *app) 88 | { 89 | int res = 0; 90 | 91 | ULOGI("request options"); 92 | 93 | res = rtsp_client_options(app->client, 94 | header_ext, 95 | 1, 96 | NULL, 97 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 98 | if (res < 0) 99 | ULOG_ERRNO("rtsp_client_options", -res); 100 | } 101 | 102 | 103 | static void describe_req(struct app *app) 104 | { 105 | int res = 0; 106 | 107 | ULOGI("request description"); 108 | 109 | res = rtsp_client_describe(app->client, 110 | app->path, 111 | header_ext, 112 | 1, 113 | NULL, 114 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 115 | if (res < 0) 116 | ULOG_ERRNO("rtsp_client_describe", -res); 117 | 118 | if (app->tests.cancel_enable) { 119 | app->tests.cancel_enable = 0; 120 | ULOGI("cancel request"); 121 | res = rtsp_client_cancel(app->client); 122 | if (res < 0) 123 | ULOG_ERRNO("rtsp_client_cancel", -res); 124 | } 125 | } 126 | 127 | 128 | static void 129 | setup_req(struct app *app, const char *content_base, struct sdp_media *media) 130 | { 131 | int res = 0; 132 | const char *url = NULL; 133 | 134 | if (app->tests.failed_enable) { 135 | app->tests.failed_enable = 0; 136 | url = "fake"; 137 | } else { 138 | url = media->control_url; 139 | } 140 | 141 | ULOGI("request setup: url='%s' stream_port=%d control_port=%d", 142 | url, 143 | media->dst_stream_port, 144 | media->dst_control_port); 145 | 146 | res = rtsp_client_setup(app->client, 147 | content_base, 148 | url, 149 | app->session_id, 150 | RTSP_DELIVERY_UNICAST, 151 | RTSP_LOWER_TRANSPORT_UDP, 152 | 55004, 153 | 55005, 154 | header_ext, 155 | 1, 156 | NULL, 157 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 158 | if (res < 0) 159 | ULOG_ERRNO("rtsp_client_setup", -res); 160 | } 161 | 162 | 163 | static void play_req(struct app *app) 164 | { 165 | int res = 0; 166 | struct rtsp_range range; 167 | 168 | memset(&range, 0, sizeof(range)); 169 | range.start.format = RTSP_TIME_FORMAT_NPT; 170 | range.start.npt.now = 1; 171 | range.stop.format = RTSP_TIME_FORMAT_NPT; 172 | range.stop.npt.infinity = 1; 173 | 174 | ULOGI("request play"); 175 | 176 | res = rtsp_client_play(app->client, 177 | app->session_id, 178 | &range, 179 | 1.0, 180 | header_ext, 181 | 1, 182 | NULL, 183 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 184 | if (res < 0) 185 | ULOG_ERRNO("rtsp_client_play", -res); 186 | } 187 | 188 | 189 | static void pause_req(struct app *app) 190 | { 191 | int res = 0; 192 | struct rtsp_range range; 193 | 194 | memset(&range, 0, sizeof(range)); 195 | range.start.format = RTSP_TIME_FORMAT_NPT; 196 | range.start.npt.now = 1; 197 | 198 | ULOGI("request pause"); 199 | 200 | res = rtsp_client_pause(app->client, 201 | app->session_id, 202 | &range, 203 | header_ext, 204 | 1, 205 | NULL, 206 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 207 | if (res < 0) 208 | ULOG_ERRNO("rtsp_client_play failed", -res); 209 | } 210 | 211 | 212 | static void teardown_req(struct app *app) 213 | { 214 | int res = 0; 215 | 216 | ULOGI("request teardown"); 217 | 218 | res = rtsp_client_teardown(app->client, 219 | NULL, 220 | app->session_id, 221 | header_ext, 222 | 1, 223 | NULL, 224 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 225 | if (res < 0) 226 | ULOG_ERRNO("rtsp_client_teardown", -res); 227 | } 228 | 229 | 230 | static void connection_state_cb(struct rtsp_client *client, 231 | enum rtsp_client_conn_state state, 232 | void *userdata) 233 | { 234 | struct app *app = userdata; 235 | if (!app) 236 | return; 237 | 238 | ULOGI("connection state: %s", rtsp_client_conn_state_str(state)); 239 | 240 | if (state == RTSP_CLIENT_CONN_STATE_CONNECTED) 241 | options_req(app); 242 | } 243 | 244 | 245 | static void session_removed_cb(struct rtsp_client *client, 246 | const char *session_id, 247 | int status, 248 | void *userdata) 249 | { 250 | ULOGI("session %s removed, status=%d(%s)", 251 | session_id, 252 | -status, 253 | strerror(-status)); 254 | } 255 | 256 | 257 | static void options_resp_cb(struct rtsp_client *client, 258 | enum rtsp_client_req_status req_status, 259 | int status, 260 | uint32_t methods, 261 | const struct rtsp_header_ext *ext, 262 | size_t ext_count, 263 | void *userdata, 264 | void *req_userdata) 265 | { 266 | struct app *app = userdata; 267 | if (!app) 268 | return; 269 | 270 | if (req_status == RTSP_CLIENT_REQ_STATUS_FAILED) { 271 | ULOGE("options_resp: %s err=%d(%s)", 272 | rtsp_client_req_status_str(req_status), 273 | -status, 274 | strerror(-status)); 275 | return; 276 | } else if (req_status != RTSP_CLIENT_REQ_STATUS_OK) { 277 | ULOGW("options_resp: %s", 278 | rtsp_client_req_status_str(req_status)); 279 | return; 280 | } 281 | 282 | ULOGI("options_resp: methods allowed: 0x%08X", methods); 283 | 284 | describe_req(userdata); 285 | } 286 | 287 | 288 | static void describe_resp_cb(struct rtsp_client *client, 289 | enum rtsp_client_req_status req_status, 290 | int status, 291 | const char *content_base, 292 | const struct rtsp_header_ext *ext, 293 | size_t ext_count, 294 | const char *sdp, 295 | void *userdata, 296 | void *req_userdata) 297 | { 298 | int res; 299 | struct sdp_session *sdp_session = NULL; 300 | struct sdp_media *media = NULL; 301 | struct list_node *node = NULL; 302 | struct app *app = userdata; 303 | if (!app) 304 | return; 305 | 306 | if (req_status == RTSP_CLIENT_REQ_STATUS_FAILED) { 307 | ULOGE("describe_resp: %s err=%d(%s)", 308 | rtsp_client_req_status_str(req_status), 309 | -status, 310 | strerror(-status)); 311 | return; 312 | } else if (req_status == RTSP_CLIENT_REQ_STATUS_CANCELED) { 313 | ULOGI("describe_resp: %s, retry", 314 | rtsp_client_req_status_str(req_status)); 315 | describe_req(app); 316 | return; 317 | } else if (req_status != RTSP_CLIENT_REQ_STATUS_OK) { 318 | ULOGW("describe_resp: %s", 319 | rtsp_client_req_status_str(req_status)); 320 | return; 321 | } 322 | 323 | ULOGI("describe_resp: sdp:\n%s", sdp); 324 | 325 | res = sdp_description_read(sdp, &sdp_session); 326 | if (res < 0) { 327 | ULOG_ERRNO("sdp_description_read", -res); 328 | return; 329 | } 330 | if (sdp_session->deletion) 331 | ULOGW("sdp refers to a no longer existing session"); 332 | 333 | node = list_first(&sdp_session->medias); 334 | if (node == NULL) 335 | goto out; 336 | 337 | media = list_entry(node, struct sdp_media, node); 338 | if (media == NULL) 339 | goto out; 340 | 341 | setup_req(app, content_base, media); 342 | 343 | out: 344 | sdp_session_destroy(sdp_session); 345 | } 346 | 347 | 348 | static void setup_resp_cb(struct rtsp_client *client, 349 | const char *session_id, 350 | enum rtsp_client_req_status req_status, 351 | int status, 352 | uint16_t src_stream_port, 353 | uint16_t src_control_port, 354 | int ssrc_valid, 355 | uint32_t ssrc, 356 | const struct rtsp_header_ext *ext, 357 | size_t ext_count, 358 | void *userdata, 359 | void *req_userdata) 360 | { 361 | struct app *app = userdata; 362 | if (!app) 363 | return; 364 | 365 | if (req_status == RTSP_CLIENT_REQ_STATUS_FAILED) { 366 | ULOGE("setup_resp: %s err=%d(%s)", 367 | rtsp_client_req_status_str(req_status), 368 | -status, 369 | strerror(-status)); 370 | app->stopped = 1; 371 | return; 372 | } else if (req_status != RTSP_CLIENT_REQ_STATUS_OK) { 373 | ULOGW("setup_resp: %s", rtsp_client_req_status_str(req_status)); 374 | app->stopped = 1; 375 | return; 376 | } 377 | 378 | app->session_id = strdup(session_id); 379 | 380 | ULOGI("setup_resp: src_stream_port=%" PRIu16 381 | " src_control_port=%" PRIu16 " ssrc_valid=%d ssrc=%" PRIu32, 382 | src_stream_port, 383 | src_control_port, 384 | ssrc_valid, 385 | ssrc); 386 | 387 | if (req_status == RTSP_CLIENT_REQ_STATUS_FAILED) { 388 | ULOGI("retry description after have failed"); 389 | describe_req(app); 390 | } 391 | 392 | play_req(app); 393 | } 394 | 395 | 396 | static void play_resp_cb(struct rtsp_client *client, 397 | const char *session_id, 398 | enum rtsp_client_req_status req_status, 399 | int status, 400 | const struct rtsp_range *range, 401 | float scale, 402 | int seq_valid, 403 | uint16_t seq, 404 | int rtptime_valid, 405 | uint32_t rtptime, 406 | const struct rtsp_header_ext *ext, 407 | size_t ext_count, 408 | void *userdata, 409 | void *req_userdata) 410 | { 411 | struct app *app = userdata; 412 | if (!app) 413 | return; 414 | 415 | if (req_status == RTSP_CLIENT_REQ_STATUS_FAILED) { 416 | ULOGE("play_resp: %s err=%d(%s)", 417 | rtsp_client_req_status_str(req_status), 418 | -status, 419 | strerror(-status)); 420 | return; 421 | } else if (req_status != RTSP_CLIENT_REQ_STATUS_OK) { 422 | ULOGW("play_resp: %s", rtsp_client_req_status_str(req_status)); 423 | return; 424 | } 425 | 426 | ULOGI("play_resp: scale=%.2f seq_valid=%d seq=%" PRIu16 427 | " rtptime_valid=%d rtptime=%" PRIu32, 428 | scale, 429 | seq_valid, 430 | seq, 431 | rtptime_valid, 432 | rtptime); 433 | 434 | ULOGI("waiting for 5s..."); 435 | pomp_timer_set(app->timer, 5000); 436 | } 437 | 438 | 439 | static void pause_resp_cb(struct rtsp_client *client, 440 | const char *session_id, 441 | enum rtsp_client_req_status req_status, 442 | int status, 443 | const struct rtsp_range *range, 444 | const struct rtsp_header_ext *ext, 445 | size_t ext_count, 446 | void *userdata, 447 | void *req_userdata) 448 | { 449 | struct app *app = userdata; 450 | if (!app) 451 | return; 452 | 453 | if (req_status == RTSP_CLIENT_REQ_STATUS_FAILED) { 454 | ULOGE("pause_resp: %s err=%d(%s)", 455 | rtsp_client_req_status_str(req_status), 456 | -status, 457 | strerror(-status)); 458 | return; 459 | } else if (req_status != RTSP_CLIENT_REQ_STATUS_OK) { 460 | ULOGW("pause_resp: %s", rtsp_client_req_status_str(req_status)); 461 | return; 462 | } 463 | 464 | ULOGI("pause_resp"); 465 | 466 | ULOGI("waiting for 5s..."); 467 | pomp_timer_set(app->timer, 5000); 468 | } 469 | 470 | 471 | static void teardown_resp_cb(struct rtsp_client *client, 472 | const char *session_id, 473 | enum rtsp_client_req_status req_status, 474 | int status, 475 | const struct rtsp_header_ext *ext, 476 | size_t ext_count, 477 | void *userdata, 478 | void *req_userdata) 479 | { 480 | struct app *app = userdata; 481 | if (!app) 482 | return; 483 | 484 | if (req_status == RTSP_CLIENT_REQ_STATUS_FAILED) { 485 | ULOGE("teardown_resp: %s err=%d(%s)", 486 | rtsp_client_req_status_str(req_status), 487 | -status, 488 | strerror(-status)); 489 | return; 490 | } else if (req_status != RTSP_CLIENT_REQ_STATUS_OK) { 491 | ULOGW("teardown_resp: %s", 492 | rtsp_client_req_status_str(req_status)); 493 | return; 494 | } 495 | 496 | free((void *)app->session_id); 497 | app->session_id = NULL; 498 | 499 | ULOGI("teardown_resp"); 500 | 501 | app->stopped = 1; 502 | } 503 | 504 | 505 | static void announce_cb(struct rtsp_client *client, 506 | const char *path, 507 | const struct rtsp_header_ext *ext, 508 | size_t ext_count, 509 | const char *sdp, 510 | void *userdata) 511 | { 512 | ULOGI("announce: sdp:\n%s", sdp); 513 | } 514 | 515 | 516 | static const struct rtsp_client_cbs cbs = { 517 | .connection_state = &connection_state_cb, 518 | .session_removed = &session_removed_cb, 519 | .options_resp = &options_resp_cb, 520 | .describe_resp = &describe_resp_cb, 521 | .setup_resp = &setup_resp_cb, 522 | .play_resp = &play_resp_cb, 523 | .pause_resp = &pause_resp_cb, 524 | .teardown_resp = &teardown_resp_cb, 525 | .announce = &announce_cb, 526 | }; 527 | 528 | 529 | static void timer_cb(struct pomp_timer *timer, void *userdata) 530 | { 531 | struct app *app = userdata; 532 | if (!app) 533 | return; 534 | 535 | if (app->tests.pause_enable) { 536 | app->tests.pause_enable = 0; 537 | pause_req(userdata); 538 | } else { 539 | teardown_req(app); 540 | } 541 | } 542 | 543 | 544 | static void welcome(char *prog_name) 545 | { 546 | printf("\n%s - Real Time Streaming Protocol library " 547 | "client test program\n" 548 | "Copyright (c) 2017 Parrot Drones SAS\n" 549 | "Copyright (c) 2017 Aurelien Barre\n\n", 550 | prog_name); 551 | } 552 | 553 | 554 | static void usage(char *prog_name) 555 | { 556 | printf("Usage: %s [] \n" 557 | "\n" 558 | "Options:\n" 559 | "-h | --help Print this message\n" 560 | " --test-cancel Test request cancel\n" 561 | " --test-pause Test pause request\n" 562 | " --test-failed Test failed request\n", 563 | prog_name); 564 | } 565 | 566 | 567 | int main(int argc, char **argv) 568 | { 569 | int status = EXIT_SUCCESS; 570 | int res = 0; 571 | int argidx = 0; 572 | char *url = NULL; 573 | char *tmp; 574 | 575 | memset(&s_app, 0, sizeof(s_app)); 576 | 577 | welcome(argv[0]); 578 | 579 | /* Parse parameters */ 580 | for (argidx = 1; argidx < argc; argidx++) { 581 | if (argv[argidx][0] != '-') { 582 | /* End of options */ 583 | break; 584 | } else if (strcmp(argv[argidx], "-h") == 0 || 585 | strcmp(argv[argidx], "--help") == 0) { 586 | /* Help */ 587 | usage(argv[0]); 588 | exit(EXIT_SUCCESS); 589 | } else if (strcmp(argv[argidx], "--test-cancel") == 0) { 590 | s_app.tests.cancel_enable = 1; 591 | } else if (strcmp(argv[argidx], "--test-pause") == 0) { 592 | s_app.tests.pause_enable = 1; 593 | } else if (strcmp(argv[argidx], "--test-failed") == 0) { 594 | s_app.tests.failed_enable = 1; 595 | } 596 | } 597 | 598 | /* URL */ 599 | if (argc - argidx < 1) { 600 | usage(argv[0]); 601 | exit(EXIT_FAILURE); 602 | } 603 | url = argv[argidx]; 604 | if (url == NULL) { 605 | ULOGE("missing URL"); 606 | exit(EXIT_FAILURE); 607 | } 608 | 609 | /* Split addr/path for URL */ 610 | if (strncmp(url, "rtsp://", 7) != 0) { 611 | ULOGE("bad URL scheme, expected 'rtsp://'"); 612 | exit(EXIT_FAILURE); 613 | } 614 | s_app.addr = url; 615 | tmp = strchr(&url[7], '/'); 616 | if (tmp == NULL) { 617 | ULOGE("missing path in URL"); 618 | exit(EXIT_FAILURE); 619 | } 620 | *tmp = '\0'; 621 | s_app.path = tmp + 1; 622 | 623 | /* Setup signal handlers */ 624 | signal(SIGINT, &sig_handler); 625 | signal(SIGTERM, &sig_handler); 626 | #ifndef _WIN32 627 | signal(SIGPIPE, SIG_IGN); 628 | #endif 629 | 630 | /* Create loop */ 631 | s_app.loop = pomp_loop_new(); 632 | if (s_app.loop == NULL) { 633 | ULOG_ERRNO("pomp_loop_new", ENOMEM); 634 | status = EXIT_FAILURE; 635 | goto cleanup; 636 | } 637 | 638 | s_app.timer = pomp_timer_new(s_app.loop, &timer_cb, &s_app); 639 | if (s_app.timer == NULL) { 640 | ULOG_ERRNO("pomp_timer_new", ENOMEM); 641 | status = EXIT_FAILURE; 642 | goto cleanup; 643 | } 644 | 645 | /* Create RTSP client */ 646 | res = rtsp_client_new(s_app.loop, NULL, &cbs, &s_app, &s_app.client); 647 | if (res < 0) { 648 | ULOG_ERRNO("rtsp_client_new", -res); 649 | status = EXIT_FAILURE; 650 | goto cleanup; 651 | } 652 | 653 | /* Connect RTSP client */ 654 | printf("Connect client to URL '%s/%s'\n", s_app.addr, s_app.path); 655 | res = rtsp_client_connect(s_app.client, s_app.addr); 656 | if (res < 0) { 657 | ULOG_ERRNO("rtsp_client_connect", -res); 658 | status = EXIT_FAILURE; 659 | goto cleanup; 660 | } 661 | 662 | /* Run the loop */ 663 | while (!s_app.stopped) 664 | pomp_loop_wait_and_process(s_app.loop, -1); 665 | 666 | res = pomp_timer_clear(s_app.timer); 667 | if (res < 0) 668 | ULOG_ERRNO("pomp_timer_clear", -res); 669 | 670 | /* Disconnect RTSP client */ 671 | rtsp_client_disconnect(s_app.client); 672 | if (res < 0) { 673 | ULOG_ERRNO("rtsp_client_disconnect", -res); 674 | status = EXIT_FAILURE; 675 | goto cleanup; 676 | } 677 | 678 | cleanup: 679 | /* Cleanup */ 680 | res = pomp_timer_destroy(s_app.timer); 681 | if (res < 0) 682 | ULOG_ERRNO("pomp_timer_destroy", -res); 683 | 684 | res = rtsp_client_destroy(s_app.client); 685 | if (res < 0) 686 | ULOG_ERRNO("rtsp_client_destroy", -res); 687 | 688 | res = pomp_loop_destroy(s_app.loop); 689 | if (res < 0) 690 | ULOG_ERRNO("pomp_loop_destroy", -res); 691 | 692 | printf("%s\n", (status == EXIT_SUCCESS) ? "Done!" : "Failed!"); 693 | exit(status); 694 | } 695 | -------------------------------------------------------------------------------- /src/rtsp_client.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Parrot Drones SAS 3 | * Copyright (c) 2017 Aurelien Barre 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 holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 | 28 | #include "rtsp_client_priv.h" 29 | 30 | #define ULOG_TAG rtsp_client 31 | #include 32 | ULOG_DECLARE_TAG(rtsp_client); 33 | 34 | 35 | static void pomp_socket_cb(struct pomp_ctx *ctx, 36 | int fd, 37 | enum pomp_socket_kind kind, 38 | void *userdata) 39 | { 40 | struct rtsp_client *client = userdata; 41 | 42 | ULOG_ERRNO_RETURN_IF(client == NULL, EINVAL); 43 | 44 | int tos = IPTOS_PREC_FLASHOVERRIDE; 45 | int err = setsockopt( 46 | fd, IPPROTO_IP, IP_TOS, (const void *)&tos, sizeof(tos)); 47 | if (err < 0) { 48 | ULOGW("failed to set class selector for socket: err=%d(%s)", 49 | err, 50 | strerror(-err)); 51 | } 52 | 53 | if (client->cbs.socket_cb) 54 | (*client->cbs.socket_cb)(fd, client->cbs_userdata); 55 | } 56 | 57 | 58 | static char *make_uri(struct rtsp_client *client, const char *path) 59 | { 60 | char *tmp; 61 | int ret; 62 | 63 | if (!client) 64 | return NULL; 65 | if (!path) 66 | return xstrdup(client->addr); 67 | 68 | ret = asprintf(&tmp, "%s/%s", client->addr, path); 69 | if (ret <= 0) { 70 | ret = -ENOMEM; 71 | ULOG_ERRNO("asprintf", -ret); 72 | return NULL; 73 | } 74 | return tmp; 75 | } 76 | 77 | 78 | static int format_request_uri(struct rtsp_client *client, 79 | const char *content_base, 80 | const char *control_url, 81 | char **ret_uri) 82 | { 83 | int res = 0; 84 | 85 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 86 | ULOG_ERRNO_RETURN_ERR_IF(control_url == NULL, EINVAL); 87 | ULOG_ERRNO_RETURN_ERR_IF(ret_uri == NULL, EINVAL); 88 | 89 | /* Full request URI */ 90 | if (strncmp(control_url, RTSP_SCHEME_TCP, strlen(RTSP_SCHEME_TCP)) == 91 | 0) { 92 | *ret_uri = strdup(control_url); 93 | return 0; 94 | } 95 | 96 | ULOG_ERRNO_RETURN_ERR_IF(content_base == NULL, EINVAL); 97 | 98 | /* Format from content_base */ 99 | res = asprintf(ret_uri, 100 | "%s%s%s", 101 | content_base, 102 | (get_last_char(content_base) == '/') ? "" : "/", 103 | control_url); 104 | if (res <= 0) { 105 | res = -ENOMEM; 106 | ULOG_ERRNO("asprintf", -res); 107 | return res; 108 | } 109 | 110 | return 0; 111 | } 112 | 113 | 114 | static char *client_uri_to_content_base(struct rtsp_client *client, 115 | const char *uri) 116 | { 117 | int ret; 118 | int needSlash; 119 | char *content_base; 120 | 121 | if (!client || !uri) 122 | return NULL; 123 | 124 | /* If URI is an absolute rtsp:// URI, return it */ 125 | if (strncmp(uri, RTSP_SCHEME_TCP, strlen(RTSP_SCHEME_TCP)) == 0) 126 | return strdup(uri); 127 | 128 | /* Otherwise, alloc a content_base in the form client->addr/uri 129 | * without duplicate '/' */ 130 | needSlash = client->addr[strlen(client->addr) - 1] != '/'; 131 | if (uri[0] == '/') 132 | uri = &uri[1]; 133 | if (needSlash) 134 | ret = asprintf(&content_base, "%s/%s", client->addr, uri); 135 | else 136 | ret = asprintf(&content_base, "%s%s", client->addr, uri); 137 | if (ret <= 0) { 138 | ret = -ENOMEM; 139 | ULOG_ERRNO("asprintf", -ret); 140 | return NULL; 141 | } 142 | return content_base; 143 | } 144 | 145 | 146 | static void set_connection_state(struct rtsp_client *client, 147 | enum rtsp_client_conn_state new_state) 148 | { 149 | if (client->conn_state == new_state) 150 | return; 151 | 152 | ULOGD("connection_state: %s to %s", 153 | rtsp_client_conn_state_str(client->conn_state), 154 | rtsp_client_conn_state_str(new_state)); 155 | 156 | client->conn_state = new_state; 157 | 158 | (*client->cbs.connection_state)( 159 | client, client->conn_state, client->cbs_userdata); 160 | } 161 | 162 | 163 | static int send_request(struct rtsp_client *client, unsigned int timeout_ms) 164 | { 165 | int res = 0; 166 | struct rtsp_string str; 167 | 168 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 169 | 170 | ULOGI("send RTSP request %s: cseq=%d session=%s", 171 | rtsp_method_type_str(client->request.header.method), 172 | client->request.header.cseq, 173 | client->request.header.session_id 174 | ? client->request.header.session_id 175 | : "-"); 176 | 177 | memset(&str, 0, sizeof(str)); 178 | res = pomp_buffer_get_data( 179 | client->request.buf, (void **)&str.str, NULL, &str.max_len); 180 | if (res < 0) { 181 | ULOG_ERRNO("pomp_buffer_get_data", -res); 182 | return res; 183 | } 184 | 185 | res = rtsp_request_header_write(&client->request.header, &str); 186 | if (res < 0) 187 | return res; 188 | 189 | /* Set buffer length */ 190 | res = pomp_buffer_set_len(client->request.buf, str.len); 191 | if (res < 0) { 192 | ULOG_ERRNO("pomp_buffer_set_len", -res); 193 | return res; 194 | } 195 | 196 | /* Send the request */ 197 | res = pomp_ctx_send_raw_buf(client->ctx, client->request.buf); 198 | if (res < 0) { 199 | ULOG_ERRNO("pomp_ctx_send_raw_buf", -res); 200 | return res; 201 | } 202 | 203 | /* Set a timer for response timeout */ 204 | if (timeout_ms > 0) { 205 | res = pomp_timer_set(client->request.timer, timeout_ms); 206 | if (res < 0) { 207 | ULOG_ERRNO("pomp_timer_set", -res); 208 | return res; 209 | } 210 | } 211 | 212 | return 0; 213 | } 214 | 215 | static int clear_pending_keep_alive_timer(struct rtsp_client *client) 216 | { 217 | struct rtsp_client_session *session; 218 | 219 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 220 | 221 | list_walk_entry_forward(&client->sessions, session, node) 222 | { 223 | if (!session->keep_alive_in_progress) 224 | continue; 225 | 226 | /* Clear the response timeout timer related to 227 | * keep alive */ 228 | int err = pomp_timer_clear(client->request.timer); 229 | if (err < 0) 230 | ULOG_ERRNO("pomp_timer_clear", -err); 231 | return 1; 232 | } 233 | 234 | return 0; 235 | } 236 | 237 | static int reset_keep_alive_timer(struct rtsp_client_session *session, 238 | int timer_msec) 239 | { 240 | int res; 241 | 242 | ULOG_ERRNO_RETURN_ERR_IF(session == NULL, EINVAL); 243 | 244 | if (timer_msec == 0) 245 | return 0; 246 | 247 | if (timer_msec > 0) { 248 | res = pomp_timer_set(session->timer, timer_msec); 249 | if (res < 0) 250 | ULOG_ERRNO("pomp_timer_set", -res); 251 | return res; 252 | } else { 253 | res = pomp_timer_clear(session->timer); 254 | if (res < 0) 255 | ULOG_ERRNO("pomp_timer_clear", -res); 256 | return res; 257 | } 258 | 259 | return res; 260 | } 261 | 262 | 263 | static int send_keep_alive(struct rtsp_client *client, 264 | struct rtsp_client_session *session, 265 | unsigned int timeout_ms) 266 | { 267 | int res = 0; 268 | 269 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 270 | ULOG_ERRNO_RETURN_ERR_IF(session == NULL, EINVAL); 271 | ULOG_ERRNO_RETURN_ERR_IF((client->methods_allowed != 0) && 272 | !(client->methods_allowed & 273 | RTSP_METHOD_FLAG_GET_PARAMETER), 274 | ENOSYS); 275 | 276 | if (client->conn_state != RTSP_CLIENT_CONN_STATE_CONNECTED) { 277 | /* If we are still not reconnected when trying to send 278 | * a keep-alive, retry but after several failed attempts, 279 | * remove the session */ 280 | ULOGI("trying to send a keep-alive while not connected"); 281 | session->failed_keep_alive++; 282 | if (session->failed_keep_alive >= 283 | RTSP_CLIENT_MAX_FAILED_KEEP_ALIVE) { 284 | ULOGW("%d failed keep alive attempts, removing session", 285 | session->failed_keep_alive); 286 | res = -EPIPE; 287 | rtsp_client_remove_session_internal( 288 | client, session->id, res, 0); 289 | return res; 290 | } else { 291 | reset_keep_alive_timer( 292 | session, 293 | session->timeout_ms / 294 | RTSP_CLIENT_MAX_FAILED_KEEP_ALIVE); 295 | return -EAGAIN; 296 | } 297 | } 298 | 299 | /* Keep alive already in progress (waiting for reply) */ 300 | if (session->keep_alive_in_progress) 301 | return -EBUSY; 302 | 303 | /* Another request is pending, retry later */ 304 | if (client->request.is_pending) { 305 | reset_keep_alive_timer(session, session->timeout_ms / 2); 306 | return -EBUSY; 307 | } 308 | 309 | /* Set request header */ 310 | rtsp_request_header_clear(&client->request.header); 311 | client->request.header.method = RTSP_METHOD_TYPE_GET_PARAMETER; 312 | client->request.header.uri = xstrdup(session->content_base); 313 | client->request.header.cseq = client->cseq; 314 | client->request.header.user_agent = xstrdup(client->software_name); 315 | client->request.header.session_id = xstrdup(session->id); 316 | 317 | /* Send the request */ 318 | res = send_request(client, timeout_ms); 319 | if (res < 0) 320 | return res; 321 | 322 | session->keep_alive_in_progress = 1; 323 | client->request.is_pending = 1; 324 | client->cseq++; 325 | 326 | return 0; 327 | } 328 | 329 | 330 | void rtsp_client_pomp_timer_cb(struct pomp_timer *timer, void *userdata) 331 | { 332 | int res; 333 | struct rtsp_client_session *session = userdata; 334 | 335 | ULOG_ERRNO_RETURN_IF(session == NULL, EINVAL); 336 | 337 | res = send_keep_alive( 338 | session->client, session, RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 339 | if (res < 0) 340 | ULOG_ERRNO("send_keep_alive", -res); 341 | } 342 | 343 | 344 | static int send_teardown(struct rtsp_client *client, 345 | const char *resource_url, 346 | const char *session_id, 347 | const struct rtsp_header_ext *ext, 348 | size_t ext_count, 349 | void *req_userdata, 350 | unsigned int timeout_ms, 351 | int internal) 352 | { 353 | int res = 0; 354 | int keep_alive_in_progress = 0; 355 | struct rtsp_client_session *session; 356 | 357 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 358 | ULOG_ERRNO_RETURN_ERR_IF(session_id == NULL, EINVAL); 359 | ULOG_ERRNO_RETURN_ERR_IF( 360 | client->methods_allowed != 0 && 361 | !(client->methods_allowed & RTSP_METHOD_FLAG_TEARDOWN), 362 | ENOSYS); 363 | 364 | if (client->conn_state != RTSP_CLIENT_CONN_STATE_CONNECTED) 365 | return -EPIPE; 366 | keep_alive_in_progress = clear_pending_keep_alive_timer(client); 367 | if (keep_alive_in_progress < 0) 368 | return keep_alive_in_progress; 369 | 370 | /* If there is a pending request that is not a keep alive, remain in 371 | * busy state */ 372 | if (client->request.is_pending && !keep_alive_in_progress) 373 | return -EBUSY; 374 | 375 | /* Make sure that we know the session */ 376 | session = rtsp_client_get_session(client, session_id, 0); 377 | if (session == NULL) { 378 | ULOGE("%s: session not found", __func__); 379 | return -ENOENT; 380 | } 381 | 382 | /* Set request header */ 383 | rtsp_request_header_clear(&client->request.header); 384 | client->request.userdata = req_userdata; 385 | client->request.header.method = RTSP_METHOD_TYPE_TEARDOWN; 386 | 387 | if (resource_url != NULL) { 388 | res = format_request_uri(client, 389 | session->content_base, 390 | resource_url, 391 | &client->request.header.uri); 392 | if (res < 0) 393 | return res; 394 | } else { 395 | client->request.header.uri = xstrdup(session->content_base); 396 | } 397 | 398 | client->request.header.cseq = client->cseq; 399 | client->request.header.user_agent = xstrdup(client->software_name); 400 | client->request.header.session_id = xstrdup(session_id); 401 | res = rtsp_request_header_copy_ext( 402 | &client->request.header, ext, ext_count); 403 | if (res < 0) 404 | return res; 405 | 406 | /* Send the request */ 407 | res = send_request(client, timeout_ms); 408 | if (res < 0) 409 | return res; 410 | 411 | session->internal_teardown = internal; 412 | client->request.is_internal = internal; 413 | client->request.is_pending = 1; 414 | client->cseq++; 415 | return 0; 416 | } 417 | 418 | 419 | static void setup_request_complete(struct rtsp_client *client, 420 | struct rtsp_client_session *session, 421 | const char *session_id, 422 | const char *req_uri, 423 | enum rtsp_client_req_status status, 424 | int status_code, 425 | struct rtsp_response_header *resp_h, 426 | void *req_userdata) 427 | { 428 | int err; 429 | char *uri = xstrdup(req_uri); 430 | char *path = NULL; 431 | if (status == RTSP_CLIENT_REQ_STATUS_OK) { 432 | struct rtsp_client_session_media *media = NULL; 433 | (*client->cbs.setup_resp)( 434 | client, 435 | session_id, 436 | status, 437 | rtsp_status_to_errno(resp_h->status_code), 438 | resp_h->transport->src_stream_port, 439 | resp_h->transport->src_control_port, 440 | resp_h->transport->ssrc_valid, 441 | resp_h->transport->ssrc, 442 | resp_h->ext, 443 | resp_h->ext_count, 444 | client->cbs_userdata, 445 | req_userdata); 446 | err = rtsp_url_parse(uri, NULL, NULL, &path); 447 | if (err < 0) 448 | goto out; 449 | media = rtsp_client_session_media_add(client, session, path); 450 | if (media == NULL) { 451 | err = -EPROTO; 452 | goto out; 453 | } 454 | } else { 455 | (*client->cbs.setup_resp)( 456 | client, 457 | session_id, 458 | status, 459 | rtsp_status_to_errno(resp_h->status_code), 460 | 0, 461 | 0, 462 | 0, 463 | 0, 464 | NULL, 465 | 0, 466 | client->cbs_userdata, 467 | req_userdata); 468 | } 469 | 470 | out: 471 | free(uri); 472 | } 473 | 474 | 475 | static void play_request_complete(struct rtsp_client *client, 476 | const char *session_id, 477 | enum rtsp_client_req_status status, 478 | int status_code, 479 | struct rtsp_response_header *resp_h, 480 | void *req_userdata) 481 | { 482 | static struct rtsp_rtp_info_header rtp_info_default = { 483 | .url = NULL, 484 | .seq_valid = 0, 485 | .seq = 0, 486 | .rtptime_valid = 0, 487 | .rtptime = 0, 488 | }; 489 | struct rtsp_rtp_info_header *rtp_info; 490 | 491 | if (status != RTSP_CLIENT_REQ_STATUS_OK) { 492 | (*client->cbs.play_resp)(client, 493 | session_id, 494 | status, 495 | rtsp_status_to_errno(status_code), 496 | NULL, 497 | 0.0, 498 | 0, 499 | 0, 500 | 0, 501 | 0, 502 | NULL, 503 | 0, 504 | client->cbs_userdata, 505 | req_userdata); 506 | return; 507 | } 508 | 509 | rtp_info = (resp_h->rtp_info_count == 0) ? &rtp_info_default 510 | : resp_h->rtp_info[0]; 511 | (*client->cbs.play_resp)(client, 512 | session_id, 513 | status, 514 | rtsp_status_to_errno(status_code), 515 | &resp_h->range, 516 | resp_h->scale, 517 | rtp_info->seq_valid, 518 | rtp_info->seq, 519 | rtp_info->rtptime_valid, 520 | rtp_info->rtptime, 521 | resp_h->ext, 522 | resp_h->ext_count, 523 | client->cbs_userdata, 524 | req_userdata); 525 | } 526 | 527 | 528 | static void teardown_request_complete(struct rtsp_client *client, 529 | struct rtsp_client_session *session, 530 | const char *session_id, 531 | const char *req_uri, 532 | enum rtsp_client_req_status status, 533 | int status_code, 534 | struct rtsp_response_header *resp_h, 535 | void *req_userdata, 536 | int *session_removed) 537 | { 538 | int err; 539 | char *uri = NULL; 540 | char *base_uri = NULL; 541 | char *path = NULL, *base_path = NULL; 542 | struct rtsp_client_session_media *media = NULL; 543 | 544 | if (session == NULL) 545 | goto error; 546 | 547 | uri = xstrdup(req_uri); 548 | base_uri = xstrdup(session->content_base); 549 | err = rtsp_url_parse(uri, NULL, NULL, &path); 550 | if (err < 0) 551 | goto out; 552 | err = rtsp_url_parse(base_uri, NULL, NULL, &base_path); 553 | if (err < 0) 554 | goto out; 555 | media = rtsp_client_session_media_find(client, session, path); 556 | if (media == NULL) { 557 | if (strcmp(path, base_path) != 0) { 558 | ULOGE("%s: media '%s' not found", __func__, path); 559 | goto out; 560 | } 561 | } 562 | 563 | if (!session->internal_teardown) { 564 | (*client->cbs.teardown_resp)( 565 | client, 566 | session->id, 567 | status, 568 | rtsp_status_to_errno(resp_h->status_code), 569 | resp_h->ext, 570 | resp_h->ext_count, 571 | client->cbs_userdata, 572 | req_userdata); 573 | session->internal_teardown = 0; 574 | client->request.is_internal = 0; 575 | } 576 | 577 | if (media != NULL) { 578 | err = rtsp_client_session_media_remove(client, session, media); 579 | if (err < 0) 580 | ULOG_ERRNO("rtsp_client_session_media_remove", -err); 581 | *session_removed = (session->media_count == 0); 582 | } else { 583 | *session_removed = true; 584 | } 585 | 586 | goto out; 587 | 588 | error: 589 | if (!client->request.is_internal) { 590 | (*client->cbs.teardown_resp)( 591 | client, 592 | session_id, 593 | status, 594 | rtsp_status_to_errno(resp_h->status_code), 595 | resp_h->ext, 596 | resp_h->ext_count, 597 | client->cbs_userdata, 598 | req_userdata); 599 | client->request.is_internal = 0; 600 | } 601 | 602 | out: 603 | free(uri); 604 | free(base_uri); 605 | } 606 | 607 | 608 | static int request_complete(struct rtsp_client *client, 609 | struct rtsp_response_header *resp_h, 610 | char *body, 611 | size_t body_len, 612 | enum rtsp_client_req_status status) 613 | { 614 | int res = 0, err, session_removed = 0, internal_teardown = 0; 615 | enum rtsp_method_type method; 616 | char *req_uri; 617 | char *req_session_id; 618 | char *content_base; 619 | void *req_userdata; 620 | const char *method_str; 621 | char *body_with_null; 622 | const char *session_id = NULL; 623 | 624 | struct rtsp_client_session *session = NULL; 625 | 626 | struct rtsp_response_header dummy; 627 | 628 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 629 | 630 | if (!client->request.is_pending) 631 | return 0; 632 | 633 | req_session_id = xstrdup(client->request.header.session_id); 634 | 635 | if (!resp_h) { 636 | memset(&dummy, 0, sizeof(dummy)); 637 | dummy.session_id = req_session_id; 638 | if (status == RTSP_CLIENT_REQ_STATUS_TIMEOUT) 639 | dummy.status_code = RTSP_STATUS_CODE_REQUEST_TIMEOUT; 640 | resp_h = &dummy; 641 | } 642 | 643 | /* Save current request info */ 644 | method = client->request.header.method; 645 | req_userdata = client->request.userdata; 646 | 647 | /* Reset request/response buffer */ 648 | req_uri = xstrdup(client->request.header.uri); 649 | rtsp_request_header_clear(&client->request.header); 650 | client->request.is_pending = 0; 651 | free(client->request.uri); 652 | client->request.uri = NULL; 653 | client->request.userdata = NULL; 654 | 655 | /* Clear the response timeout timer */ 656 | err = pomp_timer_clear(client->request.timer); 657 | if (err < 0) 658 | ULOG_ERRNO("pomp_timer_clear", -err); 659 | 660 | /* Set status */ 661 | if ((status == RTSP_CLIENT_REQ_STATUS_OK) && 662 | (RTSP_STATUS_CLASS(resp_h->status_code) != 663 | RTSP_STATUS_CLASS_SUCCESS)) 664 | status = RTSP_CLIENT_REQ_STATUS_FAILED; 665 | 666 | if (resp_h->status_code == RTSP_STATUS_CODE_SESSION_NOT_FOUND) 667 | session_removed = 1; 668 | 669 | session_id = req_session_id; 670 | if (resp_h->session_id) 671 | session_id = resp_h->session_id; 672 | 673 | ULOGI("response to RTSP request %s: " 674 | "status=%d(%s) cseq=%d session=%s req_status=%s", 675 | rtsp_method_type_str(method), 676 | resp_h->status_code, 677 | resp_h->status_string ? resp_h->status_string : "-", 678 | resp_h->cseq, 679 | session_id ? session_id : "-", 680 | rtsp_client_req_status_str(status)); 681 | 682 | if (session_id) { 683 | session = rtsp_client_get_session( 684 | client, 685 | session_id, 686 | (method == RTSP_METHOD_TYPE_SETUP) ? 1 : 0); 687 | if (!session) { 688 | if (method == RTSP_METHOD_TYPE_SETUP) { 689 | ULOGE("%s: cannot create session", __func__); 690 | res = -ENOMEM; 691 | } else { 692 | ULOGE("%s: session not found", __func__); 693 | res = -ENOENT; 694 | } 695 | goto no_session; 696 | } 697 | session->keep_alive_in_progress = 0; 698 | 699 | /* Ensure our keep alive probes are received before the server 700 | times out by sending a probe at 80% of server's timeout, 701 | it will cope with a half-RTT jitter at most 20% of the 702 | server's timeout. 703 | To deal with more jitter, latency spike, eg. for one 704 | GET_PARAMS to be delayed on server side, double the frequency 705 | at which the client side emits them, so that it will cope 706 | with a half-RTT jitter equal to 60% of the server's timeout 707 | */ 708 | session->timeout_ms = 709 | (resp_h->session_timeout > 0) 710 | ? (800 * resp_h->session_timeout) 711 | : RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS; 712 | session->timeout_ms /= 2; 713 | 714 | err = reset_keep_alive_timer(session, session->timeout_ms); 715 | if (err < 0) 716 | ULOG_ERRNO("reset_keep_alive_timer", -err); 717 | 718 | /* Save content base to session if given */ 719 | if (client->request.content_base && !session->content_base) 720 | session->content_base = 721 | xstrdup(client->request.content_base); 722 | xfree((void **)&client->request.content_base); 723 | } 724 | 725 | no_session: 726 | switch (method) { 727 | case RTSP_METHOD_TYPE_OPTIONS: 728 | client->methods_allowed = resp_h->public_methods; 729 | (*client->cbs.options_resp)( 730 | client, 731 | status, 732 | rtsp_status_to_errno(resp_h->status_code), 733 | client->methods_allowed, 734 | resp_h->ext, 735 | resp_h->ext_count, 736 | client->cbs_userdata, 737 | req_userdata); 738 | break; 739 | case RTSP_METHOD_TYPE_DESCRIBE: 740 | body_with_null = calloc(body_len + 1, 1); 741 | if (!body_with_null) { 742 | res = -ENOMEM; 743 | goto exit; 744 | } 745 | memcpy(body_with_null, body, body_len); 746 | if (resp_h->content_base) 747 | content_base = resp_h->content_base; 748 | else if (resp_h->content_location) 749 | content_base = resp_h->content_location; 750 | else 751 | content_base = req_uri; 752 | (*client->cbs.describe_resp)( 753 | client, 754 | status, 755 | rtsp_status_to_errno(resp_h->status_code), 756 | content_base, 757 | resp_h->ext, 758 | resp_h->ext_count, 759 | body_with_null, 760 | client->cbs_userdata, 761 | req_userdata); 762 | free(body_with_null); 763 | break; 764 | case RTSP_METHOD_TYPE_SETUP: 765 | setup_request_complete(client, 766 | session, 767 | session_id, 768 | req_uri, 769 | status, 770 | resp_h->status_code, 771 | resp_h, 772 | req_userdata); 773 | break; 774 | case RTSP_METHOD_TYPE_PLAY: 775 | play_request_complete(client, 776 | session_id, 777 | status, 778 | resp_h->status_code, 779 | resp_h, 780 | req_userdata); 781 | break; 782 | case RTSP_METHOD_TYPE_PAUSE: 783 | if (status == RTSP_CLIENT_REQ_STATUS_OK) { 784 | (*client->cbs.pause_resp)( 785 | client, 786 | session_id, 787 | status, 788 | rtsp_status_to_errno(resp_h->status_code), 789 | &resp_h->range, 790 | resp_h->ext, 791 | resp_h->ext_count, 792 | client->cbs_userdata, 793 | req_userdata); 794 | } else { 795 | (*client->cbs.pause_resp)( 796 | client, 797 | session_id, 798 | status, 799 | rtsp_status_to_errno(resp_h->status_code), 800 | NULL, 801 | resp_h->ext, 802 | resp_h->ext_count, 803 | client->cbs_userdata, 804 | req_userdata); 805 | } 806 | break; 807 | case RTSP_METHOD_TYPE_TEARDOWN: 808 | teardown_request_complete(client, 809 | session, 810 | session_id, 811 | req_uri, 812 | status, 813 | resp_h->status_code, 814 | resp_h, 815 | req_userdata, 816 | &session_removed); 817 | break; 818 | case RTSP_METHOD_TYPE_GET_PARAMETER: 819 | if (session == NULL) 820 | break; 821 | /* Timeout on a keep alive: retry a new keep alive and after 822 | * several failed attempts, initiate a teardown */ 823 | if (status == RTSP_CLIENT_REQ_STATUS_TIMEOUT) { 824 | session->failed_keep_alive++; 825 | if (session->failed_keep_alive >= 826 | RTSP_CLIENT_MAX_FAILED_KEEP_ALIVE) { 827 | ULOGW("%d failed keep alive attempts, " 828 | "sending teardown request", 829 | session->failed_keep_alive); 830 | internal_teardown = 1; 831 | /* Disarm keep alive timer */ 832 | reset_keep_alive_timer(session, -1); 833 | } else { 834 | err = send_keep_alive( 835 | session->client, 836 | session, 837 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 838 | if (err < 0) 839 | ULOG_ERRNO("send_keep_alive", -err); 840 | } 841 | } else { 842 | session->failed_keep_alive = 0; 843 | } 844 | break; 845 | default: 846 | method_str = rtsp_method_type_str(method); 847 | if (method_str != NULL) 848 | ULOGE("unsupported request: %s", method_str); 849 | else 850 | ULOGE("unknown request: %d", method); 851 | break; 852 | } 853 | 854 | if (internal_teardown) { 855 | err = send_teardown(client, 856 | NULL, 857 | session_id, 858 | NULL, 859 | 0, 860 | NULL, 861 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS, 862 | 1); 863 | if (err < 0) { 864 | ULOG_ERRNO("send_teardown", -err); 865 | session_removed = 1; 866 | } 867 | } 868 | if (session_removed) { 869 | if (status == RTSP_CLIENT_REQ_STATUS_TIMEOUT) { 870 | err = rtsp_client_remove_session_internal( 871 | client, session_id, -ETIMEDOUT, 0); 872 | } else { 873 | err = rtsp_client_remove_session_internal( 874 | client, session_id, resp_h->status_code, 0); 875 | } 876 | if (err < 0) 877 | ULOG_ERRNO("rtsp_client_remove_session_internal", -err); 878 | } 879 | if (status == RTSP_CLIENT_REQ_STATUS_TIMEOUT) { 880 | client->failed_requests++; 881 | if (client->failed_requests >= 882 | RTSP_CLIENT_MAX_FAILED_REQUESTS) { 883 | ULOGW("%d failed requests (timeout), " 884 | "reconnecting to %s...", 885 | client->failed_requests, 886 | client->addr); 887 | err = pomp_ctx_stop(client->ctx); 888 | if (err < 0) 889 | ULOG_ERRNO("pomp_ctx_stop", -err); 890 | set_connection_state(client, 891 | RTSP_CLIENT_CONN_STATE_CONNECTING); 892 | err = pomp_ctx_connect(client->ctx, 893 | (const struct sockaddr *)&client 894 | ->remote_addr_in, 895 | sizeof(client->remote_addr_in)); 896 | if (err < 0) 897 | ULOG_ERRNO("pomp_ctx_connect", -err); 898 | } 899 | } else { 900 | client->failed_requests = 0; 901 | } 902 | 903 | exit: 904 | free(req_session_id); 905 | free(req_uri); 906 | return res; 907 | } 908 | 909 | 910 | static void rtsp_client_pomp_event_cb(struct pomp_ctx *ctx, 911 | enum pomp_event event, 912 | struct pomp_conn *conn, 913 | const struct pomp_msg *msg, 914 | void *userdata) 915 | { 916 | int res; 917 | struct rtsp_client *client = userdata; 918 | struct rtsp_client_session *session; 919 | 920 | ULOG_ERRNO_RETURN_IF(client == NULL, EINVAL); 921 | 922 | switch (event) { 923 | case POMP_EVENT_CONNECTED: 924 | ULOGI("client connected"); 925 | set_connection_state(client, RTSP_CLIENT_CONN_STATE_CONNECTED); 926 | 927 | /* If a session already exists, send a keep-alive 928 | * right away; this allows quickly seeing if a 929 | * session timeout has occurred on the server side 930 | * during the time disconnected */ 931 | list_walk_entry_forward(&client->sessions, session, node) 932 | { 933 | res = send_keep_alive( 934 | session->client, 935 | session, 936 | RTSP_CLIENT_DEFAULT_RESP_TIMEOUT_MS); 937 | if (res < 0) 938 | ULOG_ERRNO("send_keep_alive", -res); 939 | } 940 | break; 941 | 942 | case POMP_EVENT_DISCONNECTED: 943 | if (client->conn_state == 944 | RTSP_CLIENT_CONN_STATE_DISCONNECTING) { 945 | /* Disconnetion initiated by the user */ 946 | ULOGI("client disconnected"); 947 | 948 | xfree((void **)&client->addr); 949 | res = request_complete(client, 950 | NULL, 951 | NULL, 952 | 0, 953 | RTSP_CLIENT_REQ_STATUS_ABORTED); 954 | if (res < 0) 955 | ULOG_ERRNO("request_complete", -res); 956 | 957 | set_connection_state( 958 | client, RTSP_CLIENT_CONN_STATE_DISCONNECTED); 959 | } else if (client->conn_state == 960 | RTSP_CLIENT_CONN_STATE_CONNECTED) { 961 | /* Disconnetion by the network, auto reconnect*/ 962 | ULOGI("client disconnected, waiting for reconnection"); 963 | 964 | res = request_complete(client, 965 | NULL, 966 | NULL, 967 | 0, 968 | RTSP_CLIENT_REQ_STATUS_ABORTED); 969 | if (res < 0) 970 | ULOG_ERRNO("request_complete", -res); 971 | 972 | set_connection_state(client, 973 | RTSP_CLIENT_CONN_STATE_CONNECTING); 974 | } 975 | 976 | break; 977 | 978 | default: 979 | case POMP_EVENT_MSG: 980 | /* Never received for raw context */ 981 | break; 982 | } 983 | } 984 | 985 | 986 | static int teardown_request_process(struct rtsp_client *client, 987 | struct pomp_conn *conn, 988 | struct rtsp_message *msg, 989 | int *status_code, 990 | const char **status_string) 991 | { 992 | int ret, err; 993 | int session_removed = 0; 994 | char *uri = xstrdup(msg->header.req.uri); 995 | char *path = NULL; 996 | struct rtsp_client_session *session = NULL; 997 | struct rtsp_client_session_media *media = NULL; 998 | 999 | *status_code = RTSP_STATUS_CODE_OK; 1000 | *status_string = RTSP_STATUS_STRING_OK; 1001 | 1002 | session = 1003 | rtsp_client_get_session(client, msg->header.req.session_id, 0); 1004 | if (session == NULL) { 1005 | ret = -ENOENT; 1006 | ULOGW("%s: session not found", __func__); 1007 | *status_code = RTSP_STATUS_CODE_SESSION_NOT_FOUND; 1008 | goto out; 1009 | } 1010 | 1011 | ret = rtsp_url_parse(uri, NULL, NULL, &path); 1012 | if (ret < 0) { 1013 | ULOG_ERRNO("rtsp_url_parse", -ret); 1014 | goto out; 1015 | } else if (path == NULL) { 1016 | ret = -EINVAL; 1017 | ULOGE("invalid RTSP URL: '%s', path is missing", 1018 | msg->header.req.uri); 1019 | goto out; 1020 | } else if (strcmp(msg->header.req.uri, session->content_base) != 0) { 1021 | media = rtsp_client_session_media_find(client, session, path); 1022 | if (media == NULL) 1023 | ULOGW("%s: media not found", __func__); 1024 | } 1025 | 1026 | (*client->cbs.teardown)(client, 1027 | media ? media->path : path, 1028 | msg->header.req.session_id, 1029 | msg->header.req.ext, 1030 | msg->header.req.ext_count, 1031 | client->cbs_userdata); 1032 | 1033 | if (media != NULL) { 1034 | err = rtsp_client_session_media_remove(client, session, media); 1035 | if (err < 0) 1036 | ULOG_ERRNO("rtsp_client_session_media_remove", -err); 1037 | session_removed = (session->media_count == 0); 1038 | } 1039 | if (media == NULL || session_removed) { 1040 | ret = rtsp_client_remove_session_internal( 1041 | client, 1042 | msg->header.req.session_id, 1043 | RTSP_STATUS_CODE_OK, 1044 | 0); 1045 | if (ret < 0) 1046 | ULOG_ERRNO("rtsp_client_remove_session_internal", -ret); 1047 | } 1048 | 1049 | ret = 0; 1050 | 1051 | out: 1052 | free(uri); 1053 | return ret; 1054 | } 1055 | 1056 | 1057 | static int rtsp_client_request_process(struct rtsp_client *client, 1058 | struct pomp_conn *conn, 1059 | struct rtsp_message *msg) 1060 | { 1061 | int ret = 0; 1062 | int not_implem = 0; 1063 | ssize_t res; 1064 | int status_code; 1065 | const char *status_string; 1066 | char *body_with_null; 1067 | char *content_base; 1068 | struct rtsp_message resp; 1069 | struct pomp_buffer *resp_buf = NULL; 1070 | struct rtsp_string resp_str; 1071 | struct timespec cur_ts = {0, 0}; 1072 | 1073 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1074 | ULOG_ERRNO_RETURN_ERR_IF(conn == NULL, EINVAL); 1075 | ULOG_ERRNO_RETURN_ERR_IF(msg == NULL, EINVAL); 1076 | 1077 | memset(&resp, 0, sizeof(resp)); 1078 | memset(&resp_str, 0, sizeof(resp_str)); 1079 | 1080 | ULOGI("received RTSP request %s: cseq=%d session=%s", 1081 | rtsp_method_type_str(msg->header.req.method), 1082 | msg->header.req.cseq, 1083 | msg->header.req.session_id ? msg->header.req.session_id : "-"); 1084 | 1085 | switch (msg->header.req.method) { 1086 | case RTSP_METHOD_TYPE_ANNOUNCE: 1087 | body_with_null = calloc(msg->body_len + 1, 1); 1088 | ULOG_ERRNO_RETURN_ERR_IF(body_with_null == NULL, ENOMEM); 1089 | memcpy(body_with_null, msg->body, msg->body_len); 1090 | content_base = 1091 | client_uri_to_content_base(client, msg->header.req.uri); 1092 | (*client->cbs.announce)(client, 1093 | content_base, 1094 | msg->header.req.ext, 1095 | msg->header.req.ext_count, 1096 | body_with_null, 1097 | client->cbs_userdata); 1098 | free(content_base); 1099 | free(body_with_null); 1100 | status_code = RTSP_STATUS_CODE_OK; 1101 | status_string = RTSP_STATUS_STRING_OK; 1102 | break; 1103 | case RTSP_METHOD_TYPE_GET_PARAMETER: 1104 | if (msg->body_len == 0) { 1105 | status_code = RTSP_STATUS_CODE_OK; 1106 | status_string = RTSP_STATUS_STRING_OK; 1107 | } else { 1108 | ULOGW("non-empty get parameter in RTSP client"); 1109 | status_code = RTSP_STATUS_CODE_NOT_IMPLEMENTED; 1110 | status_string = RTSP_STATUS_STRING_NOT_IMPLEMENTED; 1111 | } 1112 | break; 1113 | case RTSP_METHOD_TYPE_TEARDOWN: 1114 | ret = teardown_request_process( 1115 | client, conn, msg, &status_code, &status_string); 1116 | if (ret < 0) 1117 | goto out; 1118 | break; 1119 | default: 1120 | ULOGW("received unhandled %s request in RTSP client", 1121 | rtsp_method_type_str(msg->header.req.method)); 1122 | status_code = RTSP_STATUS_CODE_NOT_IMPLEMENTED; 1123 | status_string = RTSP_STATUS_STRING_NOT_IMPLEMENTED; 1124 | not_implem = 1; 1125 | break; 1126 | } 1127 | 1128 | resp.header.resp.cseq = msg->header.req.cseq; 1129 | resp.header.resp.status_code = status_code; 1130 | resp.header.resp.status_string = strdup(status_string); 1131 | time_get_monotonic(&cur_ts); 1132 | resp.header.resp.date = cur_ts.tv_sec; 1133 | 1134 | ULOGI("send RTSP response to %s: status=%d(%s) cseq=%d session=%s", 1135 | rtsp_method_type_str(msg->header.req.method), 1136 | resp.header.resp.status_code, 1137 | resp.header.resp.status_string ? resp.header.resp.status_string 1138 | : "-", 1139 | resp.header.resp.cseq, 1140 | msg->header.req.session_id ? msg->header.req.session_id : "-"); 1141 | 1142 | resp_buf = pomp_buffer_new(PIPE_BUF - 1); 1143 | if (resp_buf == NULL) { 1144 | ret = -ENOMEM; 1145 | goto out; 1146 | } 1147 | 1148 | ret = pomp_buffer_get_data( 1149 | resp_buf, (void **)&resp_str.str, NULL, &resp_str.max_len); 1150 | if (ret < 0) 1151 | goto out; 1152 | 1153 | res = rtsp_response_header_write(&resp.header.resp, &resp_str); 1154 | if (res < 0) { 1155 | ret = res; 1156 | goto out; 1157 | } 1158 | ret = pomp_buffer_set_len(resp_buf, resp_str.len); 1159 | if (ret < 0) { 1160 | ULOG_ERRNO("pomp_buffer_set_len", -ret); 1161 | goto out; 1162 | } 1163 | 1164 | ret = pomp_conn_send_raw_buf(conn, resp_buf); 1165 | if (ret < 0) 1166 | ULOG_ERRNO("pomp_conn_send_raw_buf", -ret); 1167 | 1168 | out: 1169 | rtsp_response_header_clear(&resp.header.resp); 1170 | if (resp_buf != NULL) 1171 | pomp_buffer_unref(resp_buf); 1172 | if (ret == 0 && not_implem) 1173 | ret = -ENOSYS; 1174 | return ret; 1175 | } 1176 | 1177 | 1178 | static int rtsp_client_response_process(struct rtsp_client *client, 1179 | struct rtsp_message *msg) 1180 | { 1181 | struct rtsp_client_session *session; 1182 | 1183 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1184 | ULOG_ERRNO_RETURN_ERR_IF(msg == NULL, EINVAL); 1185 | 1186 | /* Note: VLC server doesn't repeat the cseq in error case */ 1187 | if (msg->header.resp.cseq != client->request.header.cseq) { 1188 | 1189 | list_walk_entry_forward(&client->sessions, session, node) 1190 | { 1191 | if (!session->keep_alive_in_progress) 1192 | continue; 1193 | 1194 | /* We suppose that this is in fact the response to a 1195 | * pending keep alive; drop the response */ 1196 | session->keep_alive_in_progress = 0; 1197 | ULOGW("%s: dropping RTSP response cseq=%d session=%s" 1198 | " (probably %s)", 1199 | __func__, 1200 | msg->header.resp.cseq, 1201 | session->id, 1202 | rtsp_method_type_str( 1203 | RTSP_METHOD_TYPE_GET_PARAMETER)); 1204 | 1205 | /* Try to send a keep alive later as this one has been 1206 | * dropped. Moreover the following response may not 1207 | * be related to this session. */ 1208 | reset_keep_alive_timer(session, 1209 | session->timeout_ms / 2); 1210 | return 0; 1211 | } 1212 | 1213 | ULOGE("%s: unexpected CSeq (req: %d, resp: %d)", 1214 | __func__, 1215 | client->request.header.cseq, 1216 | msg->header.resp.cseq); 1217 | return -EPROTO; 1218 | } 1219 | 1220 | return request_complete(client, 1221 | &msg->header.resp, 1222 | msg->body, 1223 | msg->body_len, 1224 | RTSP_CLIENT_REQ_STATUS_OK); 1225 | } 1226 | 1227 | 1228 | static void rtsp_client_pomp_raw_cb(struct pomp_ctx *ctx, 1229 | struct pomp_conn *conn, 1230 | struct pomp_buffer *buf, 1231 | void *userdata) 1232 | { 1233 | struct rtsp_client *client = userdata; 1234 | int res, err; 1235 | size_t len = 0; 1236 | const void *cdata = NULL; 1237 | struct rtsp_message msg; 1238 | memset(&msg, 0x0, sizeof(msg)); 1239 | 1240 | ULOG_ERRNO_RETURN_IF(client == NULL, EINVAL); 1241 | 1242 | /* Get the message data */ 1243 | res = pomp_buffer_get_cdata(buf, &cdata, &len, NULL); 1244 | if (res < 0) { 1245 | ULOG_ERRNO("pomp_buffer_get_cdata", -res); 1246 | return; 1247 | } 1248 | 1249 | /* Add the data to the buffer */ 1250 | res = pomp_buffer_append_data(client->response.buf, cdata, len); 1251 | if (res < 0) { 1252 | ULOG_ERRNO("pomp_buffer_append_data", -res); 1253 | return; 1254 | } 1255 | 1256 | /* Iterate over complete messages */ 1257 | while ((res = rtsp_get_next_message(client->response.buf, 1258 | &msg, 1259 | &client->parser_ctx)) == 0) { 1260 | if (msg.type == RTSP_MESSAGE_TYPE_REQUEST) { 1261 | err = rtsp_client_request_process(client, conn, &msg); 1262 | if (err < 0) 1263 | ULOG_ERRNO("rtsp_client_request_process", -err); 1264 | } else { 1265 | err = rtsp_client_response_process(client, &msg); 1266 | if (err < 0) { 1267 | ULOG_ERRNO("rtsp_client_response_process", 1268 | -err); 1269 | } 1270 | } 1271 | 1272 | rtsp_buffer_remove_first_bytes(client->response.buf, 1273 | msg.total_len); 1274 | } 1275 | 1276 | if (res != -EAGAIN) 1277 | ULOG_ERRNO("rtsp_get_next_message", -res); 1278 | 1279 | rtsp_buffer_remove_first_bytes(client->response.buf, msg.total_len); 1280 | } 1281 | 1282 | 1283 | static void rtsp_client_resp_timeout_cb(struct pomp_timer *timer, 1284 | void *userdata) 1285 | { 1286 | struct rtsp_client *client = userdata; 1287 | int ret = 0; 1288 | enum rtsp_method_type method; 1289 | 1290 | ULOG_ERRNO_RETURN_IF(client == NULL, EINVAL); 1291 | 1292 | method = client->request.header.method; 1293 | 1294 | ret = request_complete( 1295 | client, NULL, NULL, 0, RTSP_CLIENT_REQ_STATUS_TIMEOUT); 1296 | if (ret < 0) 1297 | ULOG_ERRNO("request_complete", -ret); 1298 | } 1299 | 1300 | 1301 | int rtsp_client_new(struct pomp_loop *loop, 1302 | const char *software_name, 1303 | const struct rtsp_client_cbs *cbs, 1304 | void *userdata, 1305 | struct rtsp_client **ret_obj) 1306 | { 1307 | int res = 0; 1308 | struct rtsp_client *client = NULL; 1309 | 1310 | ULOG_ERRNO_RETURN_ERR_IF(loop == NULL, EINVAL); 1311 | ULOG_ERRNO_RETURN_ERR_IF(cbs == NULL, EINVAL); 1312 | ULOG_ERRNO_RETURN_ERR_IF(cbs->connection_state == NULL, EINVAL); 1313 | ULOG_ERRNO_RETURN_ERR_IF(cbs->session_removed == NULL, EINVAL); 1314 | ULOG_ERRNO_RETURN_ERR_IF(cbs->options_resp == NULL, EINVAL); 1315 | ULOG_ERRNO_RETURN_ERR_IF(cbs->describe_resp == NULL, EINVAL); 1316 | ULOG_ERRNO_RETURN_ERR_IF(cbs->setup_resp == NULL, EINVAL); 1317 | ULOG_ERRNO_RETURN_ERR_IF(cbs->play_resp == NULL, EINVAL); 1318 | ULOG_ERRNO_RETURN_ERR_IF(cbs->pause_resp == NULL, EINVAL); 1319 | ULOG_ERRNO_RETURN_ERR_IF(cbs->teardown_resp == NULL, EINVAL); 1320 | ULOG_ERRNO_RETURN_ERR_IF(cbs->announce == NULL, EINVAL); 1321 | ULOG_ERRNO_RETURN_ERR_IF(ret_obj == NULL, EINVAL); 1322 | 1323 | /* Allocate structure */ 1324 | client = calloc(1, sizeof(*client)); 1325 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, ENOMEM); 1326 | 1327 | /* Initialize structure */ 1328 | client->loop = loop; 1329 | client->cbs = *cbs; 1330 | client->cbs_userdata = userdata; 1331 | client->cseq = 1; 1332 | 1333 | list_init(&client->sessions); 1334 | 1335 | /* Create a timer for response timeout */ 1336 | client->request.timer = 1337 | pomp_timer_new(loop, &rtsp_client_resp_timeout_cb, client); 1338 | if (client->request.timer == NULL) { 1339 | res = -ENOMEM; 1340 | ULOG_ERRNO("pomp_timer_new", -res); 1341 | goto error; 1342 | } 1343 | 1344 | client->software_name = 1345 | (software_name) ? strdup(software_name) 1346 | : strdup(RTSP_CLIENT_DEFAULT_SOFTWARE_NAME); 1347 | if (client->software_name == NULL) { 1348 | res = -ENOMEM; 1349 | ULOG_ERRNO("strdup", -res); 1350 | goto error; 1351 | } 1352 | 1353 | client->ctx = pomp_ctx_new_with_loop( 1354 | &rtsp_client_pomp_event_cb, client, client->loop); 1355 | if (client->ctx == NULL) { 1356 | res = -ENOMEM; 1357 | ULOG_ERRNO("pomp_ctx_new_with_loop", -res); 1358 | goto error; 1359 | } 1360 | 1361 | res = pomp_ctx_setup_keepalive(client->ctx, 0, 0, 0, 0); 1362 | if (res < 0) { 1363 | ULOG_ERRNO("pomp_ctx_setup_keepalive", -res); 1364 | goto error; 1365 | } 1366 | 1367 | res = pomp_ctx_set_socket_cb(client->ctx, &pomp_socket_cb); 1368 | if (res < 0) { 1369 | ULOG_ERRNO("pomp_ctx_set_socket_cb", -res); 1370 | goto error; 1371 | } 1372 | 1373 | res = pomp_ctx_set_raw(client->ctx, &rtsp_client_pomp_raw_cb); 1374 | if (res < 0) { 1375 | ULOG_ERRNO("pomp_ctx_set_raw", -res); 1376 | goto error; 1377 | } 1378 | 1379 | /* Initialize request */ 1380 | client->request.buf = pomp_buffer_new(PIPE_BUF - 1); 1381 | if (client->request.buf == NULL) { 1382 | res = -ENOMEM; 1383 | ULOG_ERRNO("pomp_buffer_new", -res); 1384 | goto error; 1385 | } 1386 | 1387 | /* Initialize response */ 1388 | client->response.buf = pomp_buffer_new(PIPE_BUF - 1); 1389 | if (client->response.buf == NULL) { 1390 | res = -ENOMEM; 1391 | ULOG_ERRNO("pomp_buffer_new", -res); 1392 | goto error; 1393 | } 1394 | 1395 | *ret_obj = client; 1396 | return 0; 1397 | error: 1398 | rtsp_client_destroy(client); 1399 | *ret_obj = NULL; 1400 | return res; 1401 | } 1402 | 1403 | 1404 | int rtsp_client_destroy(struct rtsp_client *client) 1405 | { 1406 | int err; 1407 | 1408 | if (client == NULL) 1409 | return 0; 1410 | 1411 | if (client->request.timer != NULL) { 1412 | err = pomp_timer_clear(client->request.timer); 1413 | if (err < 0) 1414 | ULOG_ERRNO("pomp_timer_clear", -err); 1415 | err = pomp_timer_destroy(client->request.timer); 1416 | if (err < 0) 1417 | ULOG_ERRNO("pomp_timer_destroy", -err); 1418 | } 1419 | 1420 | /* Before removing any session, the pomp context must be stopped 1421 | * to trigger a POMP_EVENT_DISCONNECTED event and complete any 1422 | * pending request with a RTSP_CLIENT_REQ_STATUS_ABORTED status */ 1423 | if (client->ctx != NULL) { 1424 | err = pomp_ctx_stop(client->ctx); 1425 | if (err < 0) 1426 | ULOG_ERRNO("pomp_ctx_stop", -err); 1427 | } 1428 | 1429 | rtsp_client_remove_all_sessions(client); 1430 | 1431 | if (client->ctx != NULL) { 1432 | err = pomp_ctx_destroy(client->ctx); 1433 | if (err < 0) 1434 | ULOG_ERRNO("pomp_ctx_destroy", -err); 1435 | } 1436 | 1437 | if (client->request.buf != NULL) 1438 | pomp_buffer_unref(client->request.buf); 1439 | if (client->response.buf != NULL) 1440 | pomp_buffer_unref(client->response.buf); 1441 | 1442 | free(client->request.uri); 1443 | free(client->request.content_base); 1444 | rtsp_request_header_clear(&client->request.header); 1445 | rtsp_message_clear(&client->parser_ctx.msg); 1446 | 1447 | free(client->addr); 1448 | free(client->software_name); 1449 | free(client); 1450 | 1451 | return 0; 1452 | } 1453 | 1454 | 1455 | int rtsp_client_connect(struct rtsp_client *client, const char *addr) 1456 | { 1457 | int res = 0; 1458 | char *url_tmp = NULL; 1459 | char *server_addr = NULL; 1460 | uint16_t server_port = RTSP_DEFAULT_PORT; 1461 | 1462 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1463 | ULOG_ERRNO_RETURN_ERR_IF(addr == NULL, EINVAL); 1464 | 1465 | if (client->conn_state != RTSP_CLIENT_CONN_STATE_DISCONNECTED) 1466 | return -EBUSY; 1467 | 1468 | xfree((void **)&client->addr); 1469 | 1470 | client->addr = strdup(addr); 1471 | if (client->addr == NULL) { 1472 | res = -ENOMEM; 1473 | ULOG_ERRNO("strdup", -res); 1474 | goto error; 1475 | } 1476 | 1477 | /* Parse the URL */ 1478 | url_tmp = strdup(addr); 1479 | if (url_tmp == NULL) { 1480 | res = -ENOMEM; 1481 | ULOG_ERRNO("strdup", -res); 1482 | goto error; 1483 | } 1484 | 1485 | res = rtsp_url_parse(url_tmp, &server_addr, &server_port, NULL); 1486 | if (res < 0) 1487 | goto error; 1488 | 1489 | /* Check the URL validity */ 1490 | if (!server_addr || strlen(server_addr) == 0) { 1491 | res = -EINVAL; 1492 | ULOGE("invalid server host: %s", addr); 1493 | goto error; 1494 | } 1495 | if (server_port == 0) { 1496 | res = -EINVAL; 1497 | ULOGE("invalid server port: %s", addr); 1498 | goto error; 1499 | } 1500 | 1501 | /* Set address */ 1502 | res = inet_pton(AF_INET, server_addr, &client->remote_addr_in.sin_addr); 1503 | if (res <= 0) { 1504 | res = -errno; 1505 | ULOG_ERRNO("inet_pton('%s')", -res, server_addr); 1506 | goto error; 1507 | } 1508 | client->remote_addr_in.sin_family = AF_INET; 1509 | client->remote_addr_in.sin_port = htons(server_port); 1510 | 1511 | ULOGI("connecting to address %s port %d", server_addr, server_port); 1512 | set_connection_state(client, RTSP_CLIENT_CONN_STATE_CONNECTING); 1513 | 1514 | res = pomp_ctx_connect(client->ctx, 1515 | (const struct sockaddr *)&client->remote_addr_in, 1516 | sizeof(client->remote_addr_in)); 1517 | if (res < 0) { 1518 | ULOG_ERRNO("pomp_ctx_connect", -res); 1519 | goto error; 1520 | } 1521 | 1522 | free(url_tmp); 1523 | return 0; 1524 | 1525 | error: 1526 | set_connection_state(client, RTSP_CLIENT_CONN_STATE_DISCONNECTED); 1527 | xfree((void **)&client->addr); 1528 | free(url_tmp); 1529 | return res; 1530 | } 1531 | 1532 | 1533 | int rtsp_client_disconnect(struct rtsp_client *client) 1534 | { 1535 | int res = 0; 1536 | int already_disconnected = 0; 1537 | 1538 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1539 | 1540 | if (client->conn_state == RTSP_CLIENT_CONN_STATE_DISCONNECTED) 1541 | return -EPROTO; 1542 | if (client->conn_state == RTSP_CLIENT_CONN_STATE_DISCONNECTING) 1543 | return 0; 1544 | if (client->conn_state == RTSP_CLIENT_CONN_STATE_CONNECTING) 1545 | already_disconnected = 1; 1546 | 1547 | set_connection_state(client, RTSP_CLIENT_CONN_STATE_DISCONNECTING); 1548 | 1549 | /* Before removing any session, the pomp context must be stopped 1550 | * to trigger a POMP_EVENT_DISCONNECTED event and complete any 1551 | * pending request with a RTSP_CLIENT_REQ_STATUS_ABORTED status */ 1552 | res = pomp_ctx_stop(client->ctx); 1553 | if (res < 0) { 1554 | ULOG_ERRNO("pomp_ctx_stop", -res); 1555 | return res; 1556 | } 1557 | 1558 | if (already_disconnected) { 1559 | ULOGI("client disconnected (already disconnected)"); 1560 | xfree((void **)&client->addr); 1561 | request_complete( 1562 | client, NULL, NULL, 0, RTSP_CLIENT_REQ_STATUS_ABORTED); 1563 | set_connection_state(client, 1564 | RTSP_CLIENT_CONN_STATE_DISCONNECTED); 1565 | } 1566 | 1567 | rtsp_client_remove_all_sessions(client); 1568 | 1569 | return 0; 1570 | } 1571 | 1572 | 1573 | int rtsp_client_options(struct rtsp_client *client, 1574 | const struct rtsp_header_ext *ext, 1575 | size_t ext_count, 1576 | void *req_userdata, 1577 | unsigned int timeout_ms) 1578 | { 1579 | int res = 0; 1580 | int keep_alive_in_progress = 0; 1581 | 1582 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1583 | 1584 | if (client->conn_state != RTSP_CLIENT_CONN_STATE_CONNECTED) 1585 | return -EPIPE; 1586 | 1587 | keep_alive_in_progress = clear_pending_keep_alive_timer(client); 1588 | if (keep_alive_in_progress < 0) 1589 | return keep_alive_in_progress; 1590 | 1591 | if (client->request.is_pending && !keep_alive_in_progress) 1592 | return -EBUSY; 1593 | 1594 | /* Set request header */ 1595 | rtsp_request_header_clear(&client->request.header); 1596 | client->request.userdata = req_userdata; 1597 | client->request.header.method = RTSP_METHOD_TYPE_OPTIONS; 1598 | client->request.header.uri = xstrdup("*"); 1599 | client->request.header.cseq = client->cseq; 1600 | client->request.header.user_agent = xstrdup(client->software_name); 1601 | res = rtsp_request_header_copy_ext( 1602 | &client->request.header, ext, ext_count); 1603 | if (res < 0) 1604 | return res; 1605 | 1606 | /* Send the request */ 1607 | res = send_request(client, timeout_ms); 1608 | if (res < 0) 1609 | return res; 1610 | 1611 | client->request.is_pending = 1; 1612 | client->cseq++; 1613 | return 0; 1614 | } 1615 | 1616 | 1617 | int rtsp_client_describe(struct rtsp_client *client, 1618 | const char *path, 1619 | const struct rtsp_header_ext *ext, 1620 | size_t ext_count, 1621 | void *req_userdata, 1622 | unsigned int timeout_ms) 1623 | { 1624 | int res = 0; 1625 | int keep_alive_in_progress = 0; 1626 | 1627 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1628 | ULOG_ERRNO_RETURN_ERR_IF( 1629 | (client->methods_allowed != 0) && 1630 | !(client->methods_allowed & RTSP_METHOD_FLAG_DESCRIBE), 1631 | ENOSYS); 1632 | 1633 | if (client->conn_state != RTSP_CLIENT_CONN_STATE_CONNECTED) 1634 | return -EPIPE; 1635 | 1636 | keep_alive_in_progress = clear_pending_keep_alive_timer(client); 1637 | if (keep_alive_in_progress < 0) 1638 | return keep_alive_in_progress; 1639 | 1640 | if (client->request.is_pending && !keep_alive_in_progress) 1641 | return -EBUSY; 1642 | 1643 | /* Set request header */ 1644 | rtsp_request_header_clear(&client->request.header); 1645 | client->request.userdata = req_userdata; 1646 | client->request.header.method = RTSP_METHOD_TYPE_DESCRIBE; 1647 | client->request.header.uri = make_uri(client, path); 1648 | client->request.header.cseq = client->cseq; 1649 | client->request.header.user_agent = xstrdup(client->software_name); 1650 | client->request.header.accept = xstrdup(RTSP_CONTENT_TYPE_SDP); 1651 | res = rtsp_request_header_copy_ext( 1652 | &client->request.header, ext, ext_count); 1653 | if (res < 0) 1654 | return res; 1655 | 1656 | /* Send the request */ 1657 | res = send_request(client, timeout_ms); 1658 | if (res < 0) 1659 | return res; 1660 | 1661 | client->request.is_pending = 1; 1662 | client->cseq++; 1663 | return 0; 1664 | } 1665 | 1666 | 1667 | int rtsp_client_setup(struct rtsp_client *client, 1668 | const char *content_base, 1669 | const char *resource_url, 1670 | const char *session_id, 1671 | enum rtsp_delivery delivery, 1672 | enum rtsp_lower_transport lower_transport, 1673 | uint16_t client_stream_port, 1674 | uint16_t client_control_port, 1675 | const struct rtsp_header_ext *ext, 1676 | size_t ext_count, 1677 | void *req_userdata, 1678 | unsigned int timeout_ms) 1679 | { 1680 | int res = 0; 1681 | int keep_alive_in_progress = 0; 1682 | struct rtsp_transport_header *th; 1683 | 1684 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1685 | ULOG_ERRNO_RETURN_ERR_IF(content_base == NULL, EINVAL); 1686 | ULOG_ERRNO_RETURN_ERR_IF(resource_url == NULL, EINVAL); 1687 | ULOG_ERRNO_RETURN_ERR_IF(client_stream_port == 0, EINVAL); 1688 | ULOG_ERRNO_RETURN_ERR_IF(client_control_port == 0, EINVAL); 1689 | ULOG_ERRNO_RETURN_ERR_IF( 1690 | (client->methods_allowed != 0) && 1691 | !(client->methods_allowed & RTSP_METHOD_FLAG_SETUP), 1692 | ENOSYS); 1693 | 1694 | if (client->conn_state != RTSP_CLIENT_CONN_STATE_CONNECTED) 1695 | return -EPIPE; 1696 | 1697 | keep_alive_in_progress = clear_pending_keep_alive_timer(client); 1698 | if (keep_alive_in_progress < 0) 1699 | return keep_alive_in_progress; 1700 | 1701 | /* If there is a pending request that is not a keep alive, remain in 1702 | * busy state */ 1703 | if (client->request.is_pending && !keep_alive_in_progress) 1704 | return -EBUSY; 1705 | 1706 | /* If a session id is passed, make sure that we know the session, 1707 | * and that its content_base is the right one */ 1708 | if (session_id != NULL) { 1709 | struct rtsp_client_session *session = 1710 | rtsp_client_get_session(client, session_id, 0); 1711 | if (!session) { 1712 | ULOGE("%s: session not found", __func__); 1713 | return -ENOENT; 1714 | } 1715 | if (strcmp(session->content_base, content_base) != 0) { 1716 | ULOGE("%s: invalid content base", __func__); 1717 | return -EINVAL; 1718 | } 1719 | } 1720 | 1721 | /* Set request header */ 1722 | rtsp_request_header_clear(&client->request.header); 1723 | client->request.userdata = req_userdata; 1724 | client->request.header.method = RTSP_METHOD_TYPE_SETUP; 1725 | 1726 | res = format_request_uri( 1727 | client, content_base, resource_url, &client->request.uri); 1728 | if (res < 0) 1729 | return res; 1730 | 1731 | client->request.content_base = xstrdup(content_base); 1732 | 1733 | client->request.header.uri = xstrdup(client->request.uri); 1734 | client->request.header.cseq = client->cseq; 1735 | client->request.header.user_agent = xstrdup(client->software_name); 1736 | 1737 | th = rtsp_transport_header_new(); 1738 | client->request.header.transport[0] = th; 1739 | client->request.header.transport_count = 1; 1740 | client->request.header.transport[0]->transport_protocol = 1741 | strdup(RTSP_TRANSPORT_PROTOCOL_RTP); 1742 | client->request.header.transport[0]->transport_profile = 1743 | strdup(RTSP_TRANSPORT_PROFILE_AVP); 1744 | client->request.header.transport[0]->lower_transport = lower_transport; 1745 | client->request.header.transport[0]->delivery = delivery; 1746 | client->request.header.transport[0]->dst_stream_port = 1747 | client_stream_port; 1748 | client->request.header.transport[0]->dst_control_port = 1749 | client_control_port; 1750 | client->request.header.session_id = xstrdup(session_id); 1751 | res = rtsp_request_header_copy_ext( 1752 | &client->request.header, ext, ext_count); 1753 | if (res < 0) 1754 | return res; 1755 | 1756 | /* Send the request */ 1757 | res = send_request(client, timeout_ms); 1758 | if (res < 0) 1759 | return res; 1760 | 1761 | client->request.is_pending = 1; 1762 | client->cseq++; 1763 | return 0; 1764 | } 1765 | 1766 | 1767 | int rtsp_client_play(struct rtsp_client *client, 1768 | const char *session_id, 1769 | const struct rtsp_range *range, 1770 | float scale, 1771 | const struct rtsp_header_ext *ext, 1772 | size_t ext_count, 1773 | void *req_userdata, 1774 | unsigned int timeout_ms) 1775 | { 1776 | int res = 0; 1777 | int keep_alive_in_progress = 0; 1778 | struct rtsp_client_session *session; 1779 | 1780 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1781 | ULOG_ERRNO_RETURN_ERR_IF(session_id == NULL, EINVAL); 1782 | ULOG_ERRNO_RETURN_ERR_IF(range == NULL, EINVAL); 1783 | ULOG_ERRNO_RETURN_ERR_IF( 1784 | (client->methods_allowed != 0) && 1785 | !(client->methods_allowed & RTSP_METHOD_FLAG_PLAY), 1786 | ENOSYS); 1787 | 1788 | if (client->conn_state != RTSP_CLIENT_CONN_STATE_CONNECTED) 1789 | return -EPIPE; 1790 | 1791 | keep_alive_in_progress = clear_pending_keep_alive_timer(client); 1792 | if (keep_alive_in_progress < 0) 1793 | return keep_alive_in_progress; 1794 | 1795 | /* If there is a pending request that is not a keep alive, remain in 1796 | * busy state */ 1797 | if (client->request.is_pending && !keep_alive_in_progress) 1798 | return -EBUSY; 1799 | 1800 | /* Make sure that we know the session */ 1801 | session = rtsp_client_get_session(client, session_id, 0); 1802 | if (session == NULL) { 1803 | ULOGE("%s: session not found", __func__); 1804 | return -ENOENT; 1805 | } 1806 | 1807 | /* Set request header */ 1808 | rtsp_request_header_clear(&client->request.header); 1809 | client->request.userdata = req_userdata; 1810 | client->request.header.method = RTSP_METHOD_TYPE_PLAY; 1811 | client->request.header.uri = xstrdup(session->content_base); 1812 | client->request.header.cseq = client->cseq; 1813 | client->request.header.user_agent = xstrdup(client->software_name); 1814 | client->request.header.session_id = xstrdup(session_id); 1815 | client->request.header.range = *range; 1816 | client->request.header.scale = scale; 1817 | res = rtsp_request_header_copy_ext( 1818 | &client->request.header, ext, ext_count); 1819 | if (res < 0) 1820 | return res; 1821 | 1822 | /* Send the request */ 1823 | res = send_request(client, timeout_ms); 1824 | if (res < 0) 1825 | return res; 1826 | 1827 | client->request.is_pending = 1; 1828 | client->cseq++; 1829 | return 0; 1830 | } 1831 | 1832 | 1833 | int rtsp_client_pause(struct rtsp_client *client, 1834 | const char *session_id, 1835 | const struct rtsp_range *range, 1836 | const struct rtsp_header_ext *ext, 1837 | size_t ext_count, 1838 | void *req_userdata, 1839 | unsigned int timeout_ms) 1840 | { 1841 | int res = 0; 1842 | int keep_alive_in_progress = 0; 1843 | struct rtsp_client_session *session; 1844 | 1845 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1846 | ULOG_ERRNO_RETURN_ERR_IF(session_id == NULL, EINVAL); 1847 | ULOG_ERRNO_RETURN_ERR_IF(range == NULL, EINVAL); 1848 | ULOG_ERRNO_RETURN_ERR_IF( 1849 | (client->methods_allowed != 0) && 1850 | !(client->methods_allowed & RTSP_METHOD_FLAG_PAUSE), 1851 | ENOSYS); 1852 | 1853 | if (client->conn_state != RTSP_CLIENT_CONN_STATE_CONNECTED) 1854 | return -EPIPE; 1855 | keep_alive_in_progress = clear_pending_keep_alive_timer(client); 1856 | if (keep_alive_in_progress < 0) 1857 | return keep_alive_in_progress; 1858 | 1859 | /* If there is a pending request that is not a keep alive, remain in 1860 | * busy state */ 1861 | if (client->request.is_pending && !keep_alive_in_progress) 1862 | return -EBUSY; 1863 | 1864 | /* Make sure that we know the session */ 1865 | session = rtsp_client_get_session(client, session_id, 0); 1866 | if (session == NULL) { 1867 | ULOGE("%s: session not found", __func__); 1868 | return -ENOENT; 1869 | } 1870 | 1871 | /* Set request header */ 1872 | rtsp_request_header_clear(&client->request.header); 1873 | client->request.userdata = req_userdata; 1874 | client->request.header.method = RTSP_METHOD_TYPE_PAUSE; 1875 | client->request.header.uri = xstrdup(session->content_base); 1876 | client->request.header.cseq = client->cseq; 1877 | client->request.header.user_agent = xstrdup(client->software_name); 1878 | client->request.header.session_id = xstrdup(session_id); 1879 | client->request.header.range = *range; 1880 | res = rtsp_request_header_copy_ext( 1881 | &client->request.header, ext, ext_count); 1882 | if (res < 0) 1883 | return res; 1884 | 1885 | /* Send the request */ 1886 | res = send_request(client, timeout_ms); 1887 | if (res < 0) 1888 | return res; 1889 | 1890 | client->request.is_pending = 1; 1891 | client->cseq++; 1892 | return 0; 1893 | } 1894 | 1895 | 1896 | int rtsp_client_teardown(struct rtsp_client *client, 1897 | const char *resource_url, 1898 | const char *session_id, 1899 | const struct rtsp_header_ext *ext, 1900 | size_t ext_count, 1901 | void *req_userdata, 1902 | unsigned int timeout_ms) 1903 | { 1904 | return send_teardown(client, 1905 | resource_url, 1906 | session_id, 1907 | ext, 1908 | ext_count, 1909 | req_userdata, 1910 | timeout_ms, 1911 | 0); 1912 | } 1913 | 1914 | int rtsp_client_remove_session(struct rtsp_client *client, 1915 | const char *session_id) 1916 | { 1917 | return rtsp_client_remove_session_internal( 1918 | client, session_id, RTSP_STATUS_CODE_REQUEST_TIMEOUT, 0); 1919 | } 1920 | 1921 | 1922 | int rtsp_client_cancel(struct rtsp_client *client) 1923 | { 1924 | ULOG_ERRNO_RETURN_ERR_IF(client == NULL, EINVAL); 1925 | 1926 | return request_complete( 1927 | client, NULL, NULL, 0, RTSP_CLIENT_REQ_STATUS_CANCELED); 1928 | } 1929 | --------------------------------------------------------------------------------