1 /*
2  * Copyright 2008-2014 Arsen Chaloyan
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * $Id: mrcp_sdp.c 2136 2014-07-04 06:33:36Z achaloyan@gmail.com $
17  */
18 
19 #include <stdlib.h>
20 #include <apr_general.h>
21 #include <sofia-sip/sdp.h>
22 #include "mrcp_sdp.h"
23 #include "mrcp_session_descriptor.h"
24 #include "mrcp_control_descriptor.h"
25 #include "mpf_rtp_attribs.h"
26 #include "mpf_rtp_pt.h"
27 #include "apt_text_stream.h"
28 #include "apt_log.h"
29 
30 #if _MSC_VER >= 1900
31 #pragma warning(disable: 4477)
32 // 'snprintf' : format string '%d' requires an argument of type 'int', but variadic argument 1 has type 'apr_size_t' 264
33 // 'snprintf' : format string '%d' requires an argument of type 'int', but variadic argument 1 has type 'const apr_size_t' 198
34 #endif
35 
36 static apr_size_t sdp_rtp_media_generate(char *buffer, apr_size_t size, const mrcp_session_descriptor_t *descriptor, const mpf_rtp_media_descriptor_t *audio_descriptor);
37 static apr_size_t sdp_control_media_generate(char *buffer, apr_size_t size, const mrcp_session_descriptor_t *descriptor, const mrcp_control_descriptor_t *control_media, apt_bool_t offer);
38 
39 static apt_bool_t mpf_rtp_media_generate(mpf_rtp_media_descriptor_t *rtp_media, const sdp_media_t *sdp_media, const apt_str_t *ip, apr_pool_t *pool);
40 static apt_bool_t mrcp_control_media_generate(mrcp_control_descriptor_t *mrcp_media, const sdp_media_t *sdp_media, const apt_str_t *ip, apr_pool_t *pool);
41 
42 /** Generate SDP string by MRCP descriptor */
sdp_string_generate_by_mrcp_descriptor(char * buffer,apr_size_t size,const mrcp_session_descriptor_t * descriptor,apt_bool_t offer)43 MRCP_DECLARE(apr_size_t) sdp_string_generate_by_mrcp_descriptor(char *buffer, apr_size_t size, const mrcp_session_descriptor_t *descriptor, apt_bool_t offer)
44 {
45 	apr_size_t i;
46 	apr_size_t count;
47 	apr_size_t audio_index = 0;
48 	mpf_rtp_media_descriptor_t *audio_media;
49 	apr_size_t video_index = 0;
50 	mpf_rtp_media_descriptor_t *video_media;
51 	apr_size_t control_index = 0;
52 	mrcp_control_descriptor_t *control_media;
53 	apr_size_t offset = 0;
54 	const char *ip = descriptor->ext_ip.buf ? descriptor->ext_ip.buf : (descriptor->ip.buf ? descriptor->ip.buf : "0.0.0.0");
55 	buffer[0] = '\0';
56 	offset += snprintf(buffer+offset,size-offset,
57 			"v=0\r\n"
58 			"o=%s 0 0 IN IP4 %s\r\n"
59 			"s=-\r\n"
60 			"c=IN IP4 %s\r\n"
61 			"t=0 0\r\n",
62 			descriptor->origin.buf ? descriptor->origin.buf : "-",
63 			ip,
64 			ip);
65 	count = mrcp_session_media_count_get(descriptor);
66 	for(i=0; i<count; i++) {
67 		audio_media = mrcp_session_audio_media_get(descriptor,audio_index);
68 		if(audio_media && audio_media->id == i) {
69 			/* generate audio media */
70 			audio_index++;
71 			offset += sdp_rtp_media_generate(buffer+offset,size-offset,descriptor,audio_media);
72 			continue;
73 		}
74 		video_media = mrcp_session_video_media_get(descriptor,video_index);
75 		if(video_media && video_media->id == i) {
76 			/* generate video media */
77 			video_index++;
78 			offset += sdp_rtp_media_generate(buffer+offset,size-offset,descriptor,video_media);
79 			continue;
80 		}
81 		control_media = mrcp_session_control_media_get(descriptor,control_index);
82 		if(control_media && control_media->id == i) {
83 			/** generate mrcp control media */
84 			control_index++;
85 			offset += sdp_control_media_generate(buffer+offset,size-offset,descriptor,control_media,offer);
86 			continue;
87 		}
88 	}
89 	return offset;
90 }
91 
92 /** Generate MRCP descriptor by SDP session */
mrcp_descriptor_generate_by_sdp_session(mrcp_session_descriptor_t * descriptor,const sdp_session_t * sdp,const char * force_destination_ip,apr_pool_t * pool)93 MRCP_DECLARE(apt_bool_t) mrcp_descriptor_generate_by_sdp_session(mrcp_session_descriptor_t* descriptor, const sdp_session_t *sdp, const char *force_destination_ip, apr_pool_t *pool)
94 {
95 	sdp_media_t *sdp_media;
96 
97 	if(!sdp) {
98 		apt_log(APT_LOG_MARK,APT_PRIO_WARNING,"Invalid SDP Message");
99 		return FALSE;
100 	}
101 
102 	if(force_destination_ip) {
103 		apt_string_assign(&descriptor->ip,force_destination_ip,pool);
104 	}
105 	else if(sdp->sdp_connection) {
106 		apt_string_assign(&descriptor->ip,sdp->sdp_connection->c_address,pool);
107 	}
108 
109 	for(sdp_media=sdp->sdp_media; sdp_media; sdp_media=sdp_media->m_next) {
110 		switch(sdp_media->m_type) {
111 			case sdp_media_audio:
112 			{
113 				mpf_rtp_media_descriptor_t *media = apr_palloc(pool,sizeof(mpf_rtp_media_descriptor_t));
114 				mpf_rtp_media_descriptor_init(media);
115 				media->id = mrcp_session_audio_media_add(descriptor,media);
116 				mpf_rtp_media_generate(media,sdp_media,&descriptor->ip,pool);
117 				break;
118 			}
119 			case sdp_media_video:
120 			{
121 				mpf_rtp_media_descriptor_t *media = apr_palloc(pool,sizeof(mpf_rtp_media_descriptor_t));
122 				mpf_rtp_media_descriptor_init(media);
123 				media->id = mrcp_session_video_media_add(descriptor,media);
124 				mpf_rtp_media_generate(media,sdp_media,&descriptor->ip,pool);
125 				break;
126 			}
127 			case sdp_media_application:
128 			{
129 				mrcp_control_descriptor_t *control_media = mrcp_control_descriptor_create(pool);
130 				control_media->id = mrcp_session_control_media_add(descriptor,control_media);
131 				mrcp_control_media_generate(control_media,sdp_media,&descriptor->ip,pool);
132 				break;
133 			}
134 			default:
135 				apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Not Supported SDP Media [%s]", sdp_media->m_type_name);
136 				break;
137 		}
138 	}
139 	return TRUE;
140 }
141 
142 /** Generate SDP media by RTP media descriptor */
sdp_rtp_media_generate(char * buffer,apr_size_t size,const mrcp_session_descriptor_t * descriptor,const mpf_rtp_media_descriptor_t * audio_media)143 static apr_size_t sdp_rtp_media_generate(char *buffer, apr_size_t size, const mrcp_session_descriptor_t *descriptor, const mpf_rtp_media_descriptor_t *audio_media)
144 {
145 	apr_size_t offset = 0;
146 	if(audio_media->state == MPF_MEDIA_ENABLED) {
147 		int codec_count = 0;
148 		int i;
149 		mpf_codec_descriptor_t *codec_descriptor;
150 		apr_array_header_t *descriptor_arr = audio_media->codec_list.descriptor_arr;
151 		const apt_str_t *direction_str;
152 		if(!descriptor_arr) {
153 			return 0;
154 		}
155 
156 		offset += snprintf(buffer+offset,size-offset,"m=audio %d RTP/AVP",audio_media->port);
157 		for(i=0; i<descriptor_arr->nelts; i++) {
158 			codec_descriptor = &APR_ARRAY_IDX(descriptor_arr,i,mpf_codec_descriptor_t);
159 			if(codec_descriptor->enabled == TRUE) {
160 				offset += snprintf(buffer+offset,size-offset," %d",codec_descriptor->payload_type);
161 				codec_count++;
162 			}
163 		}
164 		if(!codec_count){
165 			/* SDP m line should have at least one media format listed; use a reserved RTP payload type */
166 			offset += snprintf(buffer+offset,size-offset," %d",RTP_PT_RESERVED);
167 		}
168 		offset += snprintf(buffer+offset,size-offset,"\r\n");
169 
170 		if(descriptor->ip.length && audio_media->ip.length &&
171 			apt_string_compare(&descriptor->ip,&audio_media->ip) != TRUE) {
172 			const char *media_ip = audio_media->ext_ip.buf ? audio_media->ext_ip.buf : audio_media->ip.buf;
173 			offset += snprintf(buffer+offset,size-offset,"c=IN IP4 %s\r\n",media_ip);
174 		}
175 
176 		for(i=0; i<descriptor_arr->nelts; i++) {
177 			codec_descriptor = &APR_ARRAY_IDX(descriptor_arr,i,mpf_codec_descriptor_t);
178 			if(codec_descriptor->enabled == TRUE && codec_descriptor->name.buf) {
179 				offset += snprintf(buffer+offset,size-offset,"a=rtpmap:%d %s/%d\r\n",
180 					codec_descriptor->payload_type,
181 					codec_descriptor->name.buf,
182 					codec_descriptor->sampling_rate);
183 				if(codec_descriptor->format.buf) {
184 					offset += snprintf(buffer+offset,size-offset,"a=fmtp:%d %s\r\n",
185 						codec_descriptor->payload_type,
186 						codec_descriptor->format.buf);
187 				}
188 			}
189 		}
190 
191 		direction_str = mpf_rtp_direction_str_get(audio_media->direction);
192 		if(direction_str) {
193 			offset += snprintf(buffer+offset,size-offset,"a=%s\r\n",direction_str->buf);
194 		}
195 
196 		if(audio_media->ptime) {
197 			offset += snprintf(buffer+offset,size-offset,"a=ptime:%hu\r\n",audio_media->ptime);
198 		}
199 	}
200 	else {
201 		offset += snprintf(buffer+offset,size-offset,"m=audio 0 RTP/AVP %d\r\n",RTP_PT_RESERVED);
202 	}
203 
204 	offset += snprintf(buffer+offset,size-offset,"a=mid:%"APR_SIZE_T_FMT"\r\n",audio_media->mid);
205 	return offset;
206 }
207 
208 /** Generate SDP media by MRCP control media descriptor */
sdp_control_media_generate(char * buffer,apr_size_t size,const mrcp_session_descriptor_t * descriptor,const mrcp_control_descriptor_t * control_media,apt_bool_t offer)209 static apr_size_t sdp_control_media_generate(char *buffer, apr_size_t size, const mrcp_session_descriptor_t *descriptor, const mrcp_control_descriptor_t *control_media, apt_bool_t offer)
210 {
211 	int i;
212 	apr_size_t offset = 0;
213 	const apt_str_t *proto;
214 	const apt_str_t *setup_type;
215 	const apt_str_t *connection_type;
216 	proto = mrcp_proto_get(control_media->proto);
217 	setup_type = mrcp_setup_type_get(control_media->setup_type);
218 	connection_type = mrcp_connection_type_get(control_media->connection_type);
219 	if(offer == TRUE) { /* offer */
220 		if(control_media->port) {
221 			offset += snprintf(buffer+offset,size-offset,
222 				"m=application %d %s 1\r\n"
223 				"a=setup:%s\r\n"
224 				"a=connection:%s\r\n"
225 				"a=resource:%s\r\n",
226 				control_media->port,
227 				proto ? proto->buf : "",
228 				setup_type ? setup_type->buf : "",
229 				connection_type ? connection_type->buf : "",
230 				control_media->resource_name.buf);
231 
232 		}
233 		else {
234 			offset += snprintf(buffer+offset,size-offset,
235 				"m=application %d %s 1\r\n"
236 				"a=resource:%s\r\n",
237 				control_media->port,
238 				proto ? proto->buf : "",
239 				control_media->resource_name.buf);
240 		}
241 	}
242 	else { /* answer */
243 		if(control_media->port) {
244 			offset += snprintf(buffer+offset,size-offset,
245 				"m=application %d %s 1\r\n"
246 				"a=setup:%s\r\n"
247 				"a=connection:%s\r\n"
248 				"a=channel:%s@%s\r\n",
249 				control_media->port,
250 				proto ? proto->buf : "",
251 				setup_type ? setup_type->buf : "",
252 				connection_type ? connection_type->buf : "",
253 				control_media->session_id.buf,
254 				control_media->resource_name.buf);
255 		}
256 		else {
257 			offset += snprintf(buffer+offset,size-offset,
258 				"m=application %d %s 1\r\n"
259 				"a=channel:%s@%s\r\n",
260 				control_media->port,
261 				proto ? proto->buf : "",
262 				control_media->session_id.buf,
263 				control_media->resource_name.buf);
264 		}
265 	}
266 
267 	for(i=0; i<control_media->cmid_arr->nelts; i++) {
268 		offset += snprintf(buffer+offset,size-offset,
269 			"a=cmid:%"APR_SIZE_T_FMT"\r\n",
270 			APR_ARRAY_IDX(control_media->cmid_arr,i,apr_size_t));
271 	}
272 
273 	return offset;
274 }
275 
276 /** Generate RTP media descriptor by SDP media */
mpf_rtp_media_generate(mpf_rtp_media_descriptor_t * rtp_media,const sdp_media_t * sdp_media,const apt_str_t * ip,apr_pool_t * pool)277 static apt_bool_t mpf_rtp_media_generate(mpf_rtp_media_descriptor_t *rtp_media, const sdp_media_t *sdp_media, const apt_str_t *ip, apr_pool_t *pool)
278 {
279 	mpf_rtp_attrib_e id;
280 	apt_str_t name;
281 	sdp_attribute_t *attrib = NULL;
282 	sdp_rtpmap_t *map;
283 	mpf_codec_descriptor_t *codec;
284 	for(attrib = sdp_media->m_attributes; attrib; attrib=attrib->a_next) {
285 		apt_string_set(&name,attrib->a_name);
286 		id = mpf_rtp_attrib_id_find(&name);
287 		switch(id) {
288 			case RTP_ATTRIB_MID:
289 				rtp_media->mid = atoi(attrib->a_value);
290 				break;
291 			case RTP_ATTRIB_PTIME:
292 				rtp_media->ptime = (apr_uint16_t)atoi(attrib->a_value);
293 				break;
294 			default:
295 				break;
296 		}
297 	}
298 
299 	mpf_codec_list_init(&rtp_media->codec_list,5,pool);
300 	for(map = sdp_media->m_rtpmaps; map; map = map->rm_next) {
301 		codec = mpf_codec_list_add(&rtp_media->codec_list);
302 		if(codec) {
303 			codec->payload_type = (apr_byte_t)map->rm_pt;
304 			apt_string_assign(&codec->name,map->rm_encoding,pool);
305 			codec->sampling_rate = (apr_uint16_t)map->rm_rate;
306 			codec->channel_count = 1;
307 		}
308 	}
309 
310 	switch(sdp_media->m_mode) {
311 		case sdp_inactive:
312 			rtp_media->direction = STREAM_DIRECTION_NONE;
313 			break;
314 		case sdp_sendonly:
315 			rtp_media->direction = STREAM_DIRECTION_SEND;
316 			break;
317 		case sdp_recvonly:
318 			rtp_media->direction = STREAM_DIRECTION_RECEIVE;
319 			break;
320 		case sdp_sendrecv:
321 			rtp_media->direction = STREAM_DIRECTION_DUPLEX;
322 			break;
323 	}
324 
325 	if(sdp_media->m_connections) {
326 		apt_string_assign(&rtp_media->ip,sdp_media->m_connections->c_address,pool);
327 	}
328 	else {
329 		rtp_media->ip = *ip;
330 	}
331 	if(sdp_media->m_port) {
332 		rtp_media->port = (apr_port_t)sdp_media->m_port;
333 		rtp_media->state = MPF_MEDIA_ENABLED;
334 	}
335 	else {
336 		rtp_media->state = MPF_MEDIA_DISABLED;
337 	}
338 	return TRUE;
339 }
340 
341 /** Generate MRCP control media by SDP media */
mrcp_control_media_generate(mrcp_control_descriptor_t * control_media,const sdp_media_t * sdp_media,const apt_str_t * ip,apr_pool_t * pool)342 static apt_bool_t mrcp_control_media_generate(mrcp_control_descriptor_t *control_media, const sdp_media_t *sdp_media, const apt_str_t *ip, apr_pool_t *pool)
343 {
344 	mrcp_attrib_e id;
345 	apt_str_t name;
346 	apt_str_t value;
347 	sdp_attribute_t *attrib = NULL;
348 	apt_string_set(&name,sdp_media->m_proto_name);
349 	control_media->proto = mrcp_proto_find(&name);
350 	if(control_media->proto != MRCP_PROTO_TCP) {
351 		apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Not supported SDP Proto [%s], expected [%s]",sdp_media->m_proto_name,mrcp_proto_get(MRCP_PROTO_TCP)->buf);
352 		return FALSE;
353 	}
354 
355 	for(attrib = sdp_media->m_attributes; attrib; attrib=attrib->a_next) {
356 		apt_string_set(&name,attrib->a_name);
357 		id = mrcp_attrib_id_find(&name);
358 		switch(id) {
359 			case MRCP_ATTRIB_SETUP:
360 				apt_string_set(&value,attrib->a_value);
361 				control_media->setup_type = mrcp_setup_type_find(&value);
362 				break;
363 			case MRCP_ATTRIB_CONNECTION:
364 				apt_string_set(&value,attrib->a_value);
365 				control_media->connection_type = mrcp_connection_type_find(&value);
366 				break;
367 			case MRCP_ATTRIB_RESOURCE:
368 				apt_string_assign(&control_media->resource_name,attrib->a_value,pool);
369 				break;
370 			case MRCP_ATTRIB_CHANNEL:
371 				apt_string_set(&value,attrib->a_value);
372 				apt_id_resource_parse(&value,'@',&control_media->session_id,&control_media->resource_name,pool);
373 				break;
374 			case MRCP_ATTRIB_CMID:
375 				mrcp_cmid_add(control_media->cmid_arr,atoi(attrib->a_value));
376 				break;
377 			default:
378 				break;
379 		}
380 	}
381 
382 	if(sdp_media->m_connections) {
383 		apt_string_assign(&control_media->ip,sdp_media->m_connections->c_address,pool);
384 	}
385 	else {
386 		control_media->ip = *ip;
387 	}
388 	control_media->port = (apr_port_t)sdp_media->m_port;
389 	return TRUE;
390 }
391 
392 /** Generate SDP resource discovery string */
sdp_resource_discovery_string_generate(const char * ip,const char * origin,char * buffer,apr_size_t size)393 MRCP_DECLARE(apr_size_t) sdp_resource_discovery_string_generate(const char *ip, const char *origin, char *buffer, apr_size_t size)
394 {
395 	apr_size_t offset = 0;
396 	if(!ip) {
397 		ip = "0.0.0.0";
398 	}
399 	if(!origin) {
400 		origin = "-";
401 	}
402 	buffer[0] = '\0';
403 	offset += snprintf(buffer+offset,size-offset,
404 			"v=0\r\n"
405 			"o=%s 0 0 IN IP4 %s\r\n"
406 			"s=-\r\n"
407 			"c=IN IP4 %s\r\n"
408 			"t=0 0\r\n"
409 			"m=application 0 TCP/MRCPv2 1\r\n"
410 			"a=resource:speechsynth\r\n"
411 			"a=resource:speechrecog\r\n"
412 			"m=audio 0 RTP/AVP 0 8 96 101\r\n"
413 			"a=rtpmap:0 PCMU/8000\r\n"
414 			"a=rtpmap:8 PCMA/8000\r\n"
415 			"a=rtpmap:96 L16/8000\r\n"
416 			"a=rtpmap:101 telephone-event/8000\r\n",
417 			origin,
418 			ip,
419 			ip);
420 	return offset;
421 }
422