177652efbdbd22b6fdaa7d6e91a02d70a0e6c5cc
[asterisk/asterisk.git] / main / format_cap.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2014, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Format Capabilities API
22  *
23  * \author Joshua Colp <jcolp@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include "asterisk/logger.h"
35 #include "asterisk/format.h"
36 #include "asterisk/format_cap.h"
37 #include "asterisk/format_cache.h"
38 #include "asterisk/codec.h"
39 #include "asterisk/astobj2.h"
40 #include "asterisk/strings.h"
41 #include "asterisk/vector.h"
42 #include "asterisk/linkedlists.h"
43 #include "asterisk/utils.h"
44
45 /*! \brief Structure used for capability formats, adds framing */
46 struct format_cap_framed {
47         /*! \brief A pointer to the format */
48         struct ast_format *format;
49         /*! \brief The format framing size */
50         unsigned int framing;
51         /*! \brief Linked list information */
52         AST_LIST_ENTRY(format_cap_framed) entry;
53 };
54
55 /*! \brief Format capabilities structure, holds formats + preference order + etc */
56 struct ast_format_cap {
57         /*! \brief Vector of formats, indexed using the codec identifier */
58         AST_VECTOR(, struct format_cap_framed_list) formats;
59         /*! \brief Vector of formats, added in preference order */
60         AST_VECTOR(, struct format_cap_framed *) preference_order;
61         /*! \brief Global framing size, applies to all formats if no framing present on format */
62         unsigned int framing;
63 };
64
65 /*! \brief Linked list for formats */
66 AST_LIST_HEAD_NOLOCK(format_cap_framed_list, format_cap_framed);
67
68 /*! \brief Dummy empty list for when we are inserting a new list */
69 static const struct format_cap_framed_list format_cap_framed_list_empty = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
70
71 /*! \brief Destructor for format capabilities structure */
72 static void format_cap_destroy(void *obj)
73 {
74         struct ast_format_cap *cap = obj;
75         int idx;
76
77         for (idx = 0; idx < AST_VECTOR_SIZE(&cap->formats); idx++) {
78                 struct format_cap_framed_list *list = AST_VECTOR_GET_ADDR(&cap->formats, idx);
79                 struct format_cap_framed *framed;
80
81                 while ((framed = AST_LIST_REMOVE_HEAD(list, entry))) {
82                         ao2_ref(framed, -1);
83                 }
84         }
85         AST_VECTOR_FREE(&cap->formats);
86
87         for (idx = 0; idx < AST_VECTOR_SIZE(&cap->preference_order); idx++) {
88                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap->preference_order, idx);
89
90                 /* This will always be non-null, unlike formats */
91                 ao2_ref(framed, -1);
92         }
93         AST_VECTOR_FREE(&cap->preference_order);
94 }
95
96 static inline void format_cap_init(struct ast_format_cap *cap, enum ast_format_cap_flags flags)
97 {
98         AST_VECTOR_INIT(&cap->formats, 0);
99
100         /* TODO: Look at common usage of this and determine a good starting point */
101         AST_VECTOR_INIT(&cap->preference_order, 5);
102
103         cap->framing = UINT_MAX;
104 }
105
106 struct ast_format_cap *__ast_format_cap_alloc(enum ast_format_cap_flags flags)
107 {
108         struct ast_format_cap *cap;
109
110         cap = ao2_alloc_options(sizeof(*cap), format_cap_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
111         if (!cap) {
112                 return NULL;
113         }
114
115         format_cap_init(cap, flags);
116
117         return cap;
118 }
119
120 struct ast_format_cap *__ast_format_cap_alloc_debug(enum ast_format_cap_flags flags, const char *tag, const char *file, int line, const char *func)
121 {
122         struct ast_format_cap *cap;
123
124         cap = __ao2_alloc_debug(sizeof(*cap), format_cap_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK, S_OR(tag, "ast_format_cap_alloc"), file, line, func, 1);
125         if (!cap) {
126                 return NULL;
127         }
128
129         format_cap_init(cap, flags);
130
131         return cap;
132 }
133
134 void ast_format_cap_set_framing(struct ast_format_cap *cap, unsigned int framing)
135 {
136         cap->framing = framing;
137 }
138
139 /*! \brief Destructor for format capabilities framed structure */
140 static void format_cap_framed_destroy(void *obj)
141 {
142         struct format_cap_framed *framed = obj;
143
144         ao2_cleanup(framed->format);
145 }
146
147 static inline int format_cap_framed_init(struct format_cap_framed *framed, struct ast_format_cap *cap, struct ast_format *format, unsigned int framing)
148 {
149         struct format_cap_framed_list *list;
150
151         framed->framing = framing;
152
153         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
154                 if (AST_VECTOR_INSERT(&cap->formats, ast_format_get_codec_id(format), format_cap_framed_list_empty)) {
155                         ao2_ref(framed, -1);
156                         return -1;
157                 }
158         }
159         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
160
161         /* Order doesn't matter for formats, so insert at the head for performance reasons */
162         ao2_ref(framed, +1);
163         AST_LIST_INSERT_HEAD(list, framed, entry);
164
165         /* This takes the allocation reference */
166         AST_VECTOR_APPEND(&cap->preference_order, framed);
167
168         cap->framing = MIN(cap->framing, framing ? framing : ast_format_get_default_ms(format));
169
170         return 0;
171 }
172
173 /*! \internal \brief Determine if \c format is in \c cap */
174 static int format_in_format_cap(struct ast_format_cap *cap, struct ast_format *format)
175 {
176         struct format_cap_framed *framed;
177         int i;
178
179         for (i = 0; i < AST_VECTOR_SIZE(&cap->preference_order); i++) {
180                 framed = AST_VECTOR_GET(&cap->preference_order, i);
181
182                 if (ast_format_get_codec_id(format) == ast_format_get_codec_id(framed->format)) {
183                         return 1;
184                 }
185         }
186
187         return 0;
188 }
189
190 int __ast_format_cap_append(struct ast_format_cap *cap, struct ast_format *format, unsigned int framing)
191 {
192         struct format_cap_framed *framed;
193
194         ast_assert(format != NULL);
195
196         if (format_in_format_cap(cap, format)) {
197                 return 0;
198         }
199
200         framed = ao2_alloc_options(sizeof(*framed), format_cap_framed_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
201         if (!framed) {
202                 return -1;
203         }
204         framed->format = ao2_bump(format);
205
206         return format_cap_framed_init(framed, cap, format, framing);
207 }
208
209 int __ast_format_cap_append_debug(struct ast_format_cap *cap, struct ast_format *format, unsigned int framing, const char *tag, const char *file, int line, const char *func)
210 {
211         struct format_cap_framed *framed;
212
213         ast_assert(format != NULL);
214
215         if (format_in_format_cap(cap, format)) {
216                 return 0;
217         }
218
219         framed = ao2_alloc_options(sizeof(*framed), format_cap_framed_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
220         if (!framed) {
221                 return -1;
222         }
223
224         __ao2_ref_debug(format, +1, S_OR(tag, "ast_format_cap_append"), file, line, func);
225         framed->format = format;
226
227         return format_cap_framed_init(framed, cap, format, framing);
228 }
229
230 int ast_format_cap_append_by_type(struct ast_format_cap *cap, enum ast_media_type type)
231 {
232         int id;
233
234         for (id = 1; id < ast_codec_get_max(); ++id) {
235                 struct ast_codec *codec = ast_codec_get_by_id(id);
236                 struct ast_format *format;
237                 int res;
238
239                 if (!codec) {
240                         continue;
241                 }
242
243                 if ((type != AST_MEDIA_TYPE_UNKNOWN) && codec->type != type) {
244                         ao2_ref(codec, -1);
245                         continue;
246                 }
247
248                 format = ast_format_create(codec);
249                 ao2_ref(codec, -1);
250
251                 if (!format) {
252                         return -1;
253                 }
254
255                 /* Use the global framing or default framing of the codec */
256                 res = ast_format_cap_append(cap, format, 0);
257                 ao2_ref(format, -1);
258
259                 if (res) {
260                         return -1;
261                 }
262         }
263
264         return 0;
265 }
266
267 int ast_format_cap_append_from_cap(struct ast_format_cap *dst, const struct ast_format_cap *src,
268         enum ast_media_type type)
269 {
270         int idx, res = 0;
271
272         for (idx = 0; (idx < AST_VECTOR_SIZE(&src->preference_order)) && !res; ++idx) {
273                 struct format_cap_framed *framed = AST_VECTOR_GET(&src->preference_order, idx);
274
275                 if (type == AST_MEDIA_TYPE_UNKNOWN || ast_format_get_type(framed->format) == type) {
276                         res = ast_format_cap_append(dst, framed->format, framed->framing);
277                 }
278         }
279
280         return res;
281 }
282
283 static int format_cap_replace(struct ast_format_cap *cap, struct ast_format *format, unsigned int framing)
284 {
285         struct format_cap_framed *framed;
286         int i;
287
288         ast_assert(format != NULL);
289
290         for (i = 0; i < AST_VECTOR_SIZE(&cap->preference_order); i++) {
291                 framed = AST_VECTOR_GET(&cap->preference_order, i);
292
293                 if (ast_format_get_codec_id(format) == ast_format_get_codec_id(framed->format)) {
294                         ao2_t_replace(framed->format, format, "replacing with new format");
295                         framed->framing = framing;
296                         return 0;
297                 }
298         }
299
300         return -1;
301 }
302
303 void ast_format_cap_replace_from_cap(struct ast_format_cap *dst, const struct ast_format_cap *src,
304         enum ast_media_type type)
305 {
306         int idx;
307
308         for (idx = 0; (idx < AST_VECTOR_SIZE(&src->preference_order)); ++idx) {
309                 struct format_cap_framed *framed = AST_VECTOR_GET(&src->preference_order, idx);
310
311                 if (type == AST_MEDIA_TYPE_UNKNOWN || ast_format_get_type(framed->format) == type) {
312                         format_cap_replace(dst, framed->format, framed->framing);
313                 }
314         }
315 }
316
317 int ast_format_cap_update_by_allow_disallow(struct ast_format_cap *cap, const char *list, int allowing)
318 {
319         int res = 0, all = 0, iter_allowing;
320         char *parse = NULL, *this = NULL, *psize = NULL;
321
322         if (!allowing && ast_strlen_zero(list)) {
323                 return 0;
324         }
325
326         parse = ast_strdupa(list);
327         while ((this = strsep(&parse, ","))) {
328                 int framems = 0;
329                 struct ast_format *format = NULL;
330
331                 iter_allowing = allowing;
332                 if (*this == '!') {
333                         this++;
334                         iter_allowing = !allowing;
335                 }
336                 if ((psize = strrchr(this, ':'))) {
337                         *psize++ = '\0';
338                         ast_debug(1, "Packetization for codec: %s is %s\n", this, psize);
339                         if (!sscanf(psize, "%30d", &framems) || (framems < 0)) {
340                                 framems = 0;
341                                 res = -1;
342                                 ast_log(LOG_WARNING, "Bad packetization value for codec %s\n", this);
343                                 continue;
344                         }
345                 }
346                 all = strcasecmp(this, "all") ? 0 : 1;
347
348                 if (!all && !(format = ast_format_cache_get(this))) {
349                         ast_log(LOG_WARNING, "Cannot %s unknown format '%s'\n", iter_allowing ? "allow" : "disallow", this);
350                         res = -1;
351                         continue;
352                 }
353
354                 if (cap) {
355                         if (iter_allowing) {
356                                 if (all) {
357                                         ast_format_cap_append_by_type(cap, AST_MEDIA_TYPE_UNKNOWN);
358                                 } else {
359                                         ast_format_cap_append(cap, format, framems);
360                                 }
361                         } else {
362                                 if (all) {
363                                         ast_format_cap_remove_by_type(cap, AST_MEDIA_TYPE_UNKNOWN);
364                                 } else {
365                                         ast_format_cap_remove(cap, format);
366                                 }
367                         }
368                 }
369
370                 ao2_cleanup(format);
371         }
372         return res;
373 }
374
375 size_t ast_format_cap_count(const struct ast_format_cap *cap)
376 {
377         return AST_VECTOR_SIZE(&cap->preference_order);
378 }
379
380 struct ast_format *ast_format_cap_get_format(const struct ast_format_cap *cap, int position)
381 {
382         struct format_cap_framed *framed;
383
384         ast_assert(position < AST_VECTOR_SIZE(&cap->preference_order));
385
386         if (position >= AST_VECTOR_SIZE(&cap->preference_order)) {
387                 return NULL;
388         }
389
390         framed = AST_VECTOR_GET(&cap->preference_order, position);
391
392         ast_assert(framed->format != ast_format_none);
393         ao2_ref(framed->format, +1);
394         return framed->format;
395 }
396
397 struct ast_format *ast_format_cap_get_best_by_type(const struct ast_format_cap *cap, enum ast_media_type type)
398 {
399         int i;
400
401         if (type == AST_MEDIA_TYPE_UNKNOWN) {
402                 return ast_format_cap_get_format(cap, 0);
403         }
404
405         for (i = 0; i < AST_VECTOR_SIZE(&cap->preference_order); i++) {
406                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap->preference_order, i);
407
408                 if (ast_format_get_type(framed->format) == type) {
409                         ao2_ref(framed->format, +1);
410                         ast_assert(framed->format != ast_format_none);
411                         return framed->format;
412                 }
413         }
414
415         return NULL;
416 }
417
418 unsigned int ast_format_cap_get_framing(const struct ast_format_cap *cap)
419 {
420         return (cap->framing != UINT_MAX) ? cap->framing : 0;
421 }
422
423 unsigned int ast_format_cap_get_format_framing(const struct ast_format_cap *cap, const struct ast_format *format)
424 {
425         unsigned int framing;
426         struct format_cap_framed_list *list;
427         struct format_cap_framed *framed, *result = NULL;
428
429         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
430                 return 0;
431         }
432
433         framing = cap->framing != UINT_MAX ? cap->framing : ast_format_get_default_ms(format);
434         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
435
436         AST_LIST_TRAVERSE(list, framed, entry) {
437                 enum ast_format_cmp_res res = ast_format_cmp(format, framed->format);
438
439                 if (res == AST_FORMAT_CMP_NOT_EQUAL) {
440                         continue;
441                 }
442
443                 result = framed;
444
445                 if (res == AST_FORMAT_CMP_EQUAL) {
446                         break;
447                 }
448         }
449
450         if (result && result->framing) {
451                 framing = result->framing;
452         }
453
454         return framing;
455 }
456
457 /*!
458  * \brief format_cap_framed comparator for AST_VECTOR_REMOVE_CMP_ORDERED()
459  *
460  * \param elem Element to compare against
461  * \param value Value to compare with the vector element.
462  *
463  * \return 0 if element does not match.
464  * \return Non-zero if element matches.
465  */
466 #define FORMAT_CAP_FRAMED_ELEM_CMP(elem, value) ((elem)->format == (value))
467
468 /*!
469  * \brief format_cap_framed vector element cleanup.
470  *
471  * \param elem Element to cleanup
472  *
473  * \return Nothing
474  */
475 #define FORMAT_CAP_FRAMED_ELEM_CLEANUP(elem)  ao2_cleanup((elem))
476
477 int ast_format_cap_remove(struct ast_format_cap *cap, struct ast_format *format)
478 {
479         struct format_cap_framed_list *list;
480         struct format_cap_framed *framed;
481
482         ast_assert(format != NULL);
483
484         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
485                 return -1;
486         }
487
488         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
489
490         AST_LIST_TRAVERSE_SAFE_BEGIN(list, framed, entry) {
491                 if (!FORMAT_CAP_FRAMED_ELEM_CMP(framed, format)) {
492                         continue;
493                 }
494
495                 AST_LIST_REMOVE_CURRENT(entry);
496                 FORMAT_CAP_FRAMED_ELEM_CLEANUP(framed);
497                 break;
498         }
499         AST_LIST_TRAVERSE_SAFE_END;
500
501         return AST_VECTOR_REMOVE_CMP_ORDERED(&cap->preference_order, format,
502                 FORMAT_CAP_FRAMED_ELEM_CMP, FORMAT_CAP_FRAMED_ELEM_CLEANUP);
503 }
504
505 void ast_format_cap_remove_by_type(struct ast_format_cap *cap, enum ast_media_type type)
506 {
507         int idx;
508
509         for (idx = 0; idx < AST_VECTOR_SIZE(&cap->formats); ++idx) {
510                 struct format_cap_framed_list *list = AST_VECTOR_GET_ADDR(&cap->formats, idx);
511                 struct format_cap_framed *framed;
512
513                 AST_LIST_TRAVERSE_SAFE_BEGIN(list, framed, entry) {
514                         if ((type != AST_MEDIA_TYPE_UNKNOWN) &&
515                                 ast_format_get_type(framed->format) != type) {
516                                 continue;
517                         }
518
519                         AST_LIST_REMOVE_CURRENT(entry);
520                         AST_VECTOR_REMOVE_CMP_ORDERED(&cap->preference_order, framed->format,
521                                 FORMAT_CAP_FRAMED_ELEM_CMP, FORMAT_CAP_FRAMED_ELEM_CLEANUP);
522                         ao2_ref(framed, -1);
523                 }
524                 AST_LIST_TRAVERSE_SAFE_END;
525         }
526 }
527
528 struct ast_format *ast_format_cap_get_compatible_format(const struct ast_format_cap *cap, const struct ast_format *format)
529 {
530         struct format_cap_framed_list *list;
531         struct format_cap_framed *framed;
532         struct ast_format *result = NULL;
533
534         ast_assert(format != NULL);
535
536         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
537                 return NULL;
538         }
539
540         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
541
542         AST_LIST_TRAVERSE(list, framed, entry) {
543                 enum ast_format_cmp_res res = ast_format_cmp(format, framed->format);
544
545                 if (res == AST_FORMAT_CMP_NOT_EQUAL) {
546                         continue;
547                 }
548
549                 /* Replace any current result, this one will also be a subset OR an exact match */
550                 ao2_cleanup(result);
551
552                 result = ast_format_joint(format, framed->format);
553
554                 /* If it's a match we can do no better so return asap */
555                 if (res == AST_FORMAT_CMP_EQUAL) {
556                         break;
557                 }
558         }
559
560         return result;
561 }
562
563 enum ast_format_cmp_res ast_format_cap_iscompatible_format(const struct ast_format_cap *cap,
564         const struct ast_format *format)
565 {
566         enum ast_format_cmp_res res = AST_FORMAT_CMP_NOT_EQUAL;
567         struct format_cap_framed_list *list;
568         struct format_cap_framed *framed;
569
570         ast_assert(format != NULL);
571
572         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
573                 return AST_FORMAT_CMP_NOT_EQUAL;
574         }
575
576         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
577
578         AST_LIST_TRAVERSE(list, framed, entry) {
579                 enum ast_format_cmp_res cmp = ast_format_cmp(format, framed->format);
580
581                 if (cmp == AST_FORMAT_CMP_NOT_EQUAL) {
582                         continue;
583                 }
584
585                 res = cmp;
586
587                 if (res == AST_FORMAT_CMP_EQUAL) {
588                         break;
589                 }
590         }
591
592         return res;
593 }
594
595 int ast_format_cap_has_type(const struct ast_format_cap *cap, enum ast_media_type type)
596 {
597         int idx;
598
599         for (idx = 0; idx < AST_VECTOR_SIZE(&cap->preference_order); ++idx) {
600                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap->preference_order, idx);
601
602                 if (ast_format_get_type(framed->format) == type) {
603                         return 1;
604                 }
605         }
606
607         return 0;
608 }
609
610 int ast_format_cap_get_compatible(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2,
611         struct ast_format_cap *result)
612 {
613         int idx, res = 0;
614
615         for (idx = 0; idx < AST_VECTOR_SIZE(&cap1->preference_order); ++idx) {
616                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap1->preference_order, idx);
617                 struct ast_format *format;
618
619                 format = ast_format_cap_get_compatible_format(cap2, framed->format);
620                 if (!format) {
621                         continue;
622                 }
623
624                 res = ast_format_cap_append(result, format, framed->framing);
625                 ao2_ref(format, -1);
626
627                 if (res) {
628                         break;
629                 }
630         }
631
632         return res;
633 }
634
635 int ast_format_cap_iscompatible(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
636 {
637         int idx;
638
639         for (idx = 0; idx < AST_VECTOR_SIZE(&cap1->preference_order); ++idx) {
640                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap1->preference_order, idx);
641
642                 if (ast_format_cap_iscompatible_format(cap2, framed->format) != AST_FORMAT_CMP_NOT_EQUAL) {
643                         return 1;
644                 }
645         }
646
647         return 0;
648 }
649
650 static int internal_format_cap_identical(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
651 {
652         int idx;
653         struct ast_format *tmp;
654
655         for (idx = 0; idx < AST_VECTOR_SIZE(&cap1->preference_order); ++idx) {
656                 tmp = ast_format_cap_get_format(cap1, idx);
657
658                 if (ast_format_cap_iscompatible_format(cap2, tmp) != AST_FORMAT_CMP_EQUAL) {
659                         ao2_ref(tmp, -1);
660                         return 0;
661                 }
662
663                 ao2_ref(tmp, -1);
664         }
665
666         return 1;
667 }
668
669 int ast_format_cap_identical(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
670 {
671         if (AST_VECTOR_SIZE(&cap1->preference_order) != AST_VECTOR_SIZE(&cap2->preference_order)) {
672                 return 0; /* if they are not the same size, they are not identical */
673         }
674
675         if (!internal_format_cap_identical(cap1, cap2)) {
676                 return 0;
677         }
678
679         return internal_format_cap_identical(cap2, cap1);
680 }
681
682 const char *ast_format_cap_get_names(struct ast_format_cap *cap, struct ast_str **buf)
683 {
684         int i;
685
686         ast_str_set(buf, 0, "(");
687
688         if (!AST_VECTOR_SIZE(&cap->preference_order)) {
689                 ast_str_append(buf, 0, "nothing)");
690                 return ast_str_buffer(*buf);
691         }
692
693         for (i = 0; i < AST_VECTOR_SIZE(&cap->preference_order); ++i) {
694                 int res;
695                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap->preference_order, i);
696
697                 res = ast_str_append(buf, 0, "%s%s", ast_format_get_name(framed->format),
698                         i < AST_VECTOR_SIZE(&cap->preference_order) - 1 ? "|" : "");
699                 if (res < 0) {
700                         break;
701                 }
702         }
703         ast_str_append(buf, 0, ")");
704
705         return ast_str_buffer(*buf);
706 }
707
708 int ast_format_cap_empty(struct ast_format_cap *cap)
709 {
710         int count = ast_format_cap_count(cap);
711
712         if (count > 1) {
713                 return 0;
714         }
715
716         if (count == 0 || AST_VECTOR_GET(&cap->preference_order, 0)->format == ast_format_none) {
717                 return 1;
718         }
719
720         return 0;
721 }