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: mpf_context.c 2181 2014-09-14 04:29:38Z achaloyan@gmail.com $
17  */
18 
19 #ifdef WIN32
20 #pragma warning(disable: 4127)
21 #endif
22 #include <apr_ring.h>
23 #include "mpf_context.h"
24 #include "mpf_termination.h"
25 #include "mpf_stream.h"
26 #include "mpf_bridge.h"
27 #include "mpf_multiplier.h"
28 #include "mpf_mixer.h"
29 #include "apt_log.h"
30 
31 /** Item of the association matrix */
32 typedef struct {
33 	unsigned char on;
34 } matrix_item_t;
35 
36 /** Item of the association matrix header */
37 typedef struct {
38 	mpf_termination_t *termination;
39 	unsigned char      tx_count;
40 	unsigned char      rx_count;
41 } header_item_t;
42 
43 /** Media processing context */
44 struct mpf_context_t {
45 	/** Ring entry */
46 	APR_RING_ENTRY(mpf_context_t) link;
47 	/** Back pointer to the context factory */
48 	mpf_context_factory_t        *factory;
49 	/** Pool to allocate memory from */
50 	apr_pool_t                   *pool;
51 	/** Informative name of the context used for debugging */
52 	const char                   *name;
53 	/** External object */
54 	void                         *obj;
55 
56 	/** Max number of terminations in the context */
57 	apr_size_t                    capacity;
58 	/** Current number of terminations in the context */
59 	apr_size_t                    count;
60 	/** Header of the association matrix */
61 	header_item_t                *header;
62 	/** Association matrix, which represents the topology */
63 	matrix_item_t                **matrix;
64 
65 	/** Array of media processing objects constructed while
66 	applying topology based on association matrix */
67 	apr_array_header_t           *mpf_objects;
68 };
69 
70 /** Factory of media contexts */
71 struct mpf_context_factory_t {
72 	/** Ring head */
73 	APR_RING_HEAD(mpf_context_head_t, mpf_context_t) head;
74 };
75 
76 
77 static APR_INLINE apt_bool_t stream_direction_compatibility_check(mpf_termination_t *termination1, mpf_termination_t *termination2);
78 static mpf_object_t* mpf_context_bridge_create(mpf_context_t *context, apr_size_t i);
79 static mpf_object_t* mpf_context_multiplier_create(mpf_context_t *context, apr_size_t i);
80 static mpf_object_t* mpf_context_mixer_create(mpf_context_t *context, apr_size_t j);
81 
82 
mpf_context_factory_create(apr_pool_t * pool)83 MPF_DECLARE(mpf_context_factory_t*) mpf_context_factory_create(apr_pool_t *pool)
84 {
85 	mpf_context_factory_t *factory = apr_palloc(pool, sizeof(mpf_context_factory_t));
86 	APR_RING_INIT(&factory->head, mpf_context_t, link);
87 	return factory;
88 }
89 
mpf_context_factory_destroy(mpf_context_factory_t * factory)90 MPF_DECLARE(void) mpf_context_factory_destroy(mpf_context_factory_t *factory)
91 {
92 	mpf_context_t *context;
93 	while(!APR_RING_EMPTY(&factory->head, mpf_context_t, link)) {
94 		context = APR_RING_FIRST(&factory->head);
95 		mpf_context_destroy(context);
96 		APR_RING_REMOVE(context, link);
97 	}
98 }
99 
mpf_context_factory_process(mpf_context_factory_t * factory)100 MPF_DECLARE(apt_bool_t) mpf_context_factory_process(mpf_context_factory_t *factory)
101 {
102 	mpf_context_t *context;
103 	for(context = APR_RING_FIRST(&factory->head);
104 			context != APR_RING_SENTINEL(&factory->head, mpf_context_t, link);
105 				context = APR_RING_NEXT(context, link)) {
106 
107 		mpf_context_process(context);
108 	}
109 
110 	return TRUE;
111 }
112 
113 
mpf_context_create(mpf_context_factory_t * factory,const char * name,void * obj,apr_size_t max_termination_count,apr_pool_t * pool)114 MPF_DECLARE(mpf_context_t*) mpf_context_create(
115 								mpf_context_factory_t *factory,
116 								const char *name,
117 								void *obj,
118 								apr_size_t max_termination_count,
119 								apr_pool_t *pool)
120 {
121 	apr_size_t i,j;
122 	matrix_item_t *matrix_item;
123 	header_item_t *header_item;
124 	mpf_context_t *context = apr_palloc(pool,sizeof(mpf_context_t));
125 	APR_RING_ELEM_INIT(context,link);
126 	context->factory = factory;
127 	context->obj = obj;
128 	context->pool = pool;
129 	context->name = name;
130 	if(!context->name) {
131 		context->name = apr_psprintf(pool,"0x%pp",context);
132 	}
133 	context->capacity = max_termination_count;
134 	context->count = 0;
135 	context->mpf_objects = apr_array_make(pool,1,sizeof(mpf_object_t*));
136 	context->header = apr_palloc(pool,context->capacity * sizeof(header_item_t));
137 	context->matrix = apr_palloc(pool,context->capacity * sizeof(matrix_item_t*));
138 	for(i=0; i<context->capacity; i++) {
139 		header_item = &context->header[i];
140 		header_item->termination = NULL;
141 		header_item->tx_count = 0;
142 		header_item->rx_count = 0;
143 		context->matrix[i] = apr_palloc(pool,context->capacity * sizeof(matrix_item_t));
144 		for(j=0; j<context->capacity; j++) {
145 			matrix_item = &context->matrix[i][j];
146 			matrix_item->on = 0;
147 		}
148 	}
149 
150 	return context;
151 }
152 
mpf_context_destroy(mpf_context_t * context)153 MPF_DECLARE(apt_bool_t) mpf_context_destroy(mpf_context_t *context)
154 {
155 	apr_size_t i;
156 	mpf_termination_t *termination;
157 	for(i=0; i<context->capacity; i++){
158 		termination = context->header[i].termination;
159 		if(termination) {
160 			mpf_context_termination_subtract(context,termination);
161 			mpf_termination_subtract(termination);
162 		}
163 	}
164 	return TRUE;
165 }
166 
mpf_context_object_get(const mpf_context_t * context)167 MPF_DECLARE(void*) mpf_context_object_get(const mpf_context_t *context)
168 {
169 	return context->obj;
170 }
171 
mpf_context_termination_add(mpf_context_t * context,mpf_termination_t * termination)172 MPF_DECLARE(apt_bool_t) mpf_context_termination_add(mpf_context_t *context, mpf_termination_t *termination)
173 {
174 	apr_size_t i;
175 	header_item_t *header_item;
176 	for(i=0; i<context->capacity; i++) {
177 		header_item = &context->header[i];
178 		if(header_item->termination) {
179 			continue;
180 		}
181 		if(!context->count) {
182 			apt_log(APT_LOG_MARK,APT_PRIO_DEBUG,"Add Media Context %s",context->name);
183 			APR_RING_INSERT_TAIL(&context->factory->head,context,mpf_context_t,link);
184 		}
185 
186 		header_item->termination = termination;
187 		header_item->tx_count = 0;
188 		header_item->rx_count = 0;
189 
190 		termination->slot = i;
191 		context->count++;
192 		return TRUE;
193 	}
194 	return FALSE;
195 }
196 
mpf_context_termination_subtract(mpf_context_t * context,mpf_termination_t * termination)197 MPF_DECLARE(apt_bool_t) mpf_context_termination_subtract(mpf_context_t *context, mpf_termination_t *termination)
198 {
199 	header_item_t *header_item1;
200 	header_item_t *header_item2;
201 	matrix_item_t *item;
202 	apr_size_t j,k;
203 	apr_size_t i = termination->slot;
204 	if(i >= context->capacity) {
205 		return FALSE;
206 	}
207 	header_item1 = &context->header[i];
208 	if(header_item1->termination != termination) {
209 		return FALSE;
210 	}
211 
212 	for(j=0,k=0; j<context->capacity && k<context->count; j++) {
213 		header_item2 = &context->header[j];
214 		if(!header_item2->termination) {
215 			continue;
216 		}
217 		k++;
218 
219 		item = &context->matrix[i][j];
220 		if(item->on) {
221 			item->on = 0;
222 			header_item1->tx_count--;
223 			header_item2->rx_count--;
224 		}
225 
226 		item = &context->matrix[j][i];
227 		if(item->on) {
228 			item->on = 0;
229 			header_item2->tx_count--;
230 			header_item1->rx_count--;
231 		}
232 	}
233 	header_item1->termination = NULL;
234 
235 	termination->slot = (apr_size_t)-1;
236 	context->count--;
237 	if(!context->count) {
238 		apt_log(APT_LOG_MARK,APT_PRIO_DEBUG,"Remove Media Context %s",context->name);
239 		APR_RING_REMOVE(context,link);
240 	}
241 	return TRUE;
242 }
243 
mpf_context_association_add(mpf_context_t * context,mpf_termination_t * termination1,mpf_termination_t * termination2)244 MPF_DECLARE(apt_bool_t) mpf_context_association_add(mpf_context_t *context, mpf_termination_t *termination1, mpf_termination_t *termination2)
245 {
246 	header_item_t *header_item1;
247 	matrix_item_t *matrix_item1;
248 	header_item_t *header_item2;
249 	matrix_item_t *matrix_item2;
250 	apr_size_t i = termination1->slot;
251 	apr_size_t j = termination2->slot;
252 	if(i >= context->capacity || j >= context->capacity) {
253 		return FALSE;
254 	}
255 
256 	header_item1 = &context->header[i];
257 	header_item2 = &context->header[j];
258 
259 	if(header_item1->termination != termination1 || header_item2->termination != termination2) {
260 		return FALSE;
261 	}
262 
263 	matrix_item1 = &context->matrix[i][j];
264 	matrix_item2 = &context->matrix[j][i];
265 
266 	/* 1 -> 2 */
267 	if(!matrix_item1->on) {
268 		if(stream_direction_compatibility_check(header_item1->termination,header_item2->termination) == TRUE) {
269 			matrix_item1->on = 1;
270 			header_item1->tx_count ++;
271 			header_item2->rx_count ++;
272 		}
273 	}
274 
275 	/* 2 -> 1 */
276 	if(!matrix_item2->on) {
277 		if(stream_direction_compatibility_check(header_item2->termination,header_item1->termination) == TRUE) {
278 			matrix_item2->on = 1;
279 			header_item2->tx_count ++;
280 			header_item1->rx_count ++;
281 		}
282 	}
283 	return TRUE;
284 }
285 
mpf_context_association_remove(mpf_context_t * context,mpf_termination_t * termination1,mpf_termination_t * termination2)286 MPF_DECLARE(apt_bool_t) mpf_context_association_remove(mpf_context_t *context, mpf_termination_t *termination1, mpf_termination_t *termination2)
287 {
288 	header_item_t *header_item1;
289 	matrix_item_t *matrix_item1;
290 	header_item_t *header_item2;
291 	matrix_item_t *matrix_item2;
292 	apr_size_t i = termination1->slot;
293 	apr_size_t j = termination2->slot;
294 	if(i >= context->capacity || j >= context->capacity) {
295 		return FALSE;
296 	}
297 
298 	header_item1 = &context->header[i];
299 	header_item2 = &context->header[j];
300 
301 	if(header_item1->termination != termination1 || header_item2->termination != termination2) {
302 		return FALSE;
303 	}
304 
305 	matrix_item1 = &context->matrix[i][j];
306 	matrix_item2 = &context->matrix[j][i];
307 
308 	/* 1 -> 2 */
309 	if(matrix_item1->on == 1) {
310 		matrix_item1->on = 0;
311 		header_item1->tx_count --;
312 		header_item2->rx_count --;
313 	}
314 
315 	/* 2 -> 1 */
316 	if(matrix_item2->on == 1) {
317 		matrix_item2->on = 0;
318 		header_item2->tx_count --;
319 		header_item1->rx_count --;
320 	}
321 	return TRUE;
322 }
323 
mpf_context_associations_reset(mpf_context_t * context)324 MPF_DECLARE(apt_bool_t) mpf_context_associations_reset(mpf_context_t *context)
325 {
326 	apr_size_t i,j,k;
327 	header_item_t *header_item1;
328 	header_item_t *header_item2;
329 	matrix_item_t *item;
330 
331 	/* destroy existing topology / if any */
332 	mpf_context_topology_destroy(context);
333 
334 	/* reset assigned associations */
335 	for(i=0,k=0; i<context->capacity && k<context->count; i++) {
336 		header_item1 = &context->header[i];
337 		if(!header_item1->termination) {
338 			continue;
339 		}
340 		k++;
341 
342 		if(!header_item1->tx_count && !header_item1->rx_count) {
343 			continue;
344 		}
345 
346 		for(j=i; j<context->capacity; j++) {
347 			header_item2 = &context->header[j];
348 			if(!header_item2->termination) {
349 				continue;
350 			}
351 
352 			item = &context->matrix[i][j];
353 			if(item->on) {
354 				item->on = 0;
355 				header_item1->tx_count--;
356 				header_item2->rx_count--;
357 			}
358 
359 			item = &context->matrix[j][i];
360 			if(item->on) {
361 				item->on = 0;
362 				header_item2->tx_count--;
363 				header_item1->rx_count--;
364 			}
365 		}
366 	}
367 	return TRUE;
368 }
369 
mpf_context_object_add(mpf_context_t * context,mpf_object_t * object)370 static apt_bool_t mpf_context_object_add(mpf_context_t *context, mpf_object_t *object)
371 {
372 	if(!object) {
373 		return FALSE;
374 	}
375 
376 	APR_ARRAY_PUSH(context->mpf_objects, mpf_object_t*) = object;
377 #if 1
378 	mpf_object_trace(object);
379 #endif
380 	return TRUE;
381 }
382 
mpf_context_topology_apply(mpf_context_t * context)383 MPF_DECLARE(apt_bool_t) mpf_context_topology_apply(mpf_context_t *context)
384 {
385 	apr_size_t i,k;
386 	header_item_t *header_item;
387 	mpf_object_t *object;
388 
389 	/* first destroy existing topology / if any */
390 	mpf_context_topology_destroy(context);
391 
392 	for(i=0,k=0; i<context->capacity && k<context->count; i++) {
393 		header_item = &context->header[i];
394 		if(!header_item->termination) {
395 			continue;
396 		}
397 		k++;
398 
399 		if(header_item->tx_count > 0) {
400 			object = NULL;
401 			if(header_item->tx_count == 1) {
402 				object = mpf_context_bridge_create(context,i);
403 			}
404 			else { /* tx_count > 1 */
405 				object = mpf_context_multiplier_create(context,i);
406 			}
407 
408 			mpf_context_object_add(context,object);
409 		}
410 		if(header_item->rx_count > 1) {
411 			object = mpf_context_mixer_create(context,i);
412 			mpf_context_object_add(context,object);
413 		}
414 	}
415 
416 	return TRUE;
417 }
418 
mpf_context_topology_destroy(mpf_context_t * context)419 MPF_DECLARE(apt_bool_t) mpf_context_topology_destroy(mpf_context_t *context)
420 {
421 	if(context->mpf_objects->nelts) {
422 		int i;
423 		mpf_object_t *object;
424 		for(i=0; i<context->mpf_objects->nelts; i++) {
425 			object = APR_ARRAY_IDX(context->mpf_objects,i,mpf_object_t*);
426 			mpf_object_destroy(object);
427 		}
428 		apr_array_clear(context->mpf_objects);
429 	}
430 	return TRUE;
431 }
432 
mpf_context_process(mpf_context_t * context)433 MPF_DECLARE(apt_bool_t) mpf_context_process(mpf_context_t *context)
434 {
435 	int i;
436 	mpf_object_t *object;
437 	for(i=0; i<context->mpf_objects->nelts; i++) {
438 		object = APR_ARRAY_IDX(context->mpf_objects,i,mpf_object_t*);
439 		if(object && object->process) {
440 			object->process(object);
441 		}
442 	}
443 	return TRUE;
444 }
445 
446 
mpf_context_bridge_create(mpf_context_t * context,apr_size_t i)447 static mpf_object_t* mpf_context_bridge_create(mpf_context_t *context, apr_size_t i)
448 {
449 	header_item_t *header_item1 = &context->header[i];
450 	header_item_t *header_item2;
451 	matrix_item_t *item;
452 	apr_size_t j;
453 	for(j=0; j<context->capacity; j++) {
454 		header_item2 = &context->header[j];
455 		if(!header_item2->termination) {
456 			continue;
457 		}
458 		item = &context->matrix[i][j];
459 		if(!item->on) {
460 			continue;
461 		}
462 
463 		if(header_item2->rx_count > 1) {
464 			/* mixer will be created instead */
465 			return NULL;
466 		}
467 
468 		/* create bridge i -> j */
469 		if(header_item1->termination && header_item2->termination) {
470 			return mpf_bridge_create(
471 				header_item1->termination->audio_stream,
472 				header_item2->termination->audio_stream,
473 				header_item1->termination->codec_manager,
474 				context->name,
475 				context->pool);
476 		}
477 	}
478 	return NULL;
479 }
480 
mpf_context_multiplier_create(mpf_context_t * context,apr_size_t i)481 static mpf_object_t* mpf_context_multiplier_create(mpf_context_t *context, apr_size_t i)
482 {
483 	mpf_audio_stream_t **sink_arr;
484 	header_item_t *header_item1 = &context->header[i];
485 	header_item_t *header_item2;
486 	matrix_item_t *item;
487 	apr_size_t j,k;
488 	sink_arr = apr_palloc(context->pool,header_item1->tx_count * sizeof(mpf_audio_stream_t*));
489 	for(j=0,k=0; j<context->capacity && k<header_item1->tx_count; j++) {
490 		header_item2 = &context->header[j];
491 		if(!header_item2->termination) {
492 			continue;
493 		}
494 		item = &context->matrix[i][j];
495 		if(!item->on) {
496 			continue;
497 		}
498 		sink_arr[k] = header_item2->termination->audio_stream;
499 		k++;
500 	}
501 	return mpf_multiplier_create(
502 				header_item1->termination->audio_stream,
503 				sink_arr,
504 				header_item1->tx_count,
505 				header_item1->termination->codec_manager,
506 				context->name,
507 				context->pool);
508 }
509 
mpf_context_mixer_create(mpf_context_t * context,apr_size_t j)510 static mpf_object_t* mpf_context_mixer_create(mpf_context_t *context, apr_size_t j)
511 {
512 	mpf_audio_stream_t **source_arr;
513 	header_item_t *header_item1 = &context->header[j];
514 	header_item_t *header_item2;
515 	matrix_item_t *item;
516 	apr_size_t i,k;
517 	source_arr = apr_palloc(context->pool,header_item1->rx_count * sizeof(mpf_audio_stream_t*));
518 	for(i=0,k=0; i<context->capacity && k<header_item1->rx_count; i++) {
519 		header_item2 = &context->header[i];
520 		if(!header_item2->termination) {
521 			continue;
522 		}
523 		item = &context->matrix[i][j];
524 		if(!item->on) {
525 			continue;
526 		}
527 		source_arr[k] = header_item2->termination->audio_stream;
528 		k++;
529 	}
530 	return mpf_mixer_create(
531 				source_arr,
532 				header_item1->rx_count,
533 				header_item1->termination->audio_stream,
534 				header_item1->termination->codec_manager,
535 				context->name,
536 				context->pool);
537 }
538 
stream_direction_compatibility_check(mpf_termination_t * termination1,mpf_termination_t * termination2)539 static APR_INLINE apt_bool_t stream_direction_compatibility_check(mpf_termination_t *termination1, mpf_termination_t *termination2)
540 {
541 	mpf_audio_stream_t *source = termination1->audio_stream;
542 	mpf_audio_stream_t *sink = termination2->audio_stream;
543 	if(source && (source->direction & STREAM_DIRECTION_RECEIVE) == STREAM_DIRECTION_RECEIVE &&
544 		sink && (sink->direction & STREAM_DIRECTION_SEND) == STREAM_DIRECTION_SEND) {
545 		return TRUE;
546 	}
547 	return FALSE;
548 }
549