Add API call to determine if format capability structure is "empty".
[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         parse = ast_strdupa(list);
323         while ((this = strsep(&parse, ","))) {
324                 int framems = 0;
325                 struct ast_format *format = NULL;
326
327                 iter_allowing = allowing;
328                 if (*this == '!') {
329                         this++;
330                         iter_allowing = !allowing;
331                 }
332                 if ((psize = strrchr(this, ':'))) {
333                         *psize++ = '\0';
334                         ast_debug(1, "Packetization for codec: %s is %s\n", this, psize);
335                         if (!sscanf(psize, "%30d", &framems) || (framems < 0)) {
336                                 framems = 0;
337                                 res = -1;
338                                 ast_log(LOG_WARNING, "Bad packetization value for codec %s\n", this);
339                                 continue;
340                         }
341                 }
342                 all = strcasecmp(this, "all") ? 0 : 1;
343
344                 if (!all && !(format = ast_format_cache_get(this))) {
345                         ast_log(LOG_WARNING, "Cannot %s unknown format '%s'\n", iter_allowing ? "allow" : "disallow", this);
346                         res = -1;
347                         continue;
348                 }
349
350                 if (cap) {
351                         if (iter_allowing) {
352                                 if (all) {
353                                         ast_format_cap_append_by_type(cap, AST_MEDIA_TYPE_UNKNOWN);
354                                 } else {
355                                         ast_format_cap_append(cap, format, framems);
356                                 }
357                         } else {
358                                 if (all) {
359                                         ast_format_cap_remove_by_type(cap, AST_MEDIA_TYPE_UNKNOWN);
360                                 } else {
361                                         ast_format_cap_remove(cap, format);
362                                 }
363                         }
364                 }
365
366                 ao2_cleanup(format);
367         }
368         return res;
369 }
370
371 size_t ast_format_cap_count(const struct ast_format_cap *cap)
372 {
373         return AST_VECTOR_SIZE(&cap->preference_order);
374 }
375
376 struct ast_format *ast_format_cap_get_format(const struct ast_format_cap *cap, int position)
377 {
378         struct format_cap_framed *framed;
379
380         ast_assert(position < AST_VECTOR_SIZE(&cap->preference_order));
381
382         if (position >= AST_VECTOR_SIZE(&cap->preference_order)) {
383                 return NULL;
384         }
385
386         framed = AST_VECTOR_GET(&cap->preference_order, position);
387
388         ast_assert(framed->format != ast_format_none);
389         ao2_ref(framed->format, +1);
390         return framed->format;
391 }
392
393 struct ast_format *ast_format_cap_get_best_by_type(const struct ast_format_cap *cap, enum ast_media_type type)
394 {
395         int i;
396
397         if (type == AST_MEDIA_TYPE_UNKNOWN) {
398                 return ast_format_cap_get_format(cap, 0);
399         }
400
401         for (i = 0; i < AST_VECTOR_SIZE(&cap->preference_order); i++) {
402                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap->preference_order, i);
403
404                 if (ast_format_get_type(framed->format) == type) {
405                         ao2_ref(framed->format, +1);
406                         ast_assert(framed->format != ast_format_none);
407                         return framed->format;
408                 }
409         }
410
411         return NULL;
412 }
413
414 unsigned int ast_format_cap_get_framing(const struct ast_format_cap *cap)
415 {
416         return (cap->framing != UINT_MAX) ? cap->framing : 0;
417 }
418
419 unsigned int ast_format_cap_get_format_framing(const struct ast_format_cap *cap, const struct ast_format *format)
420 {
421         unsigned int framing;
422         struct format_cap_framed_list *list;
423         struct format_cap_framed *framed, *result = NULL;
424
425         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
426                 return 0;
427         }
428
429         framing = cap->framing != UINT_MAX ? cap->framing : ast_format_get_default_ms(format);
430         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
431
432         AST_LIST_TRAVERSE(list, framed, entry) {
433                 enum ast_format_cmp_res res = ast_format_cmp(format, framed->format);
434
435                 if (res == AST_FORMAT_CMP_NOT_EQUAL) {
436                         continue;
437                 }
438
439                 result = framed;
440
441                 if (res == AST_FORMAT_CMP_EQUAL) {
442                         break;
443                 }
444         }
445
446         if (result && result->framing) {
447                 framing = result->framing;
448         }
449
450         return framing;
451 }
452
453 /*!
454  * \brief format_cap_framed comparator for AST_VECTOR_REMOVE_CMP_ORDERED()
455  *
456  * \param elem Element to compare against
457  * \param value Value to compare with the vector element.
458  *
459  * \return 0 if element does not match.
460  * \return Non-zero if element matches.
461  */
462 #define FORMAT_CAP_FRAMED_ELEM_CMP(elem, value) ((elem)->format == (value))
463
464 /*!
465  * \brief format_cap_framed vector element cleanup.
466  *
467  * \param elem Element to cleanup
468  *
469  * \return Nothing
470  */
471 #define FORMAT_CAP_FRAMED_ELEM_CLEANUP(elem)  ao2_cleanup((elem))
472
473 int ast_format_cap_remove(struct ast_format_cap *cap, struct ast_format *format)
474 {
475         struct format_cap_framed_list *list;
476         struct format_cap_framed *framed;
477
478         ast_assert(format != NULL);
479
480         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
481                 return -1;
482         }
483
484         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
485
486         AST_LIST_TRAVERSE_SAFE_BEGIN(list, framed, entry) {
487                 if (!FORMAT_CAP_FRAMED_ELEM_CMP(framed, format)) {
488                         continue;
489                 }
490
491                 AST_LIST_REMOVE_CURRENT(entry);
492                 FORMAT_CAP_FRAMED_ELEM_CLEANUP(framed);
493                 break;
494         }
495         AST_LIST_TRAVERSE_SAFE_END;
496
497         return AST_VECTOR_REMOVE_CMP_ORDERED(&cap->preference_order, format,
498                 FORMAT_CAP_FRAMED_ELEM_CMP, FORMAT_CAP_FRAMED_ELEM_CLEANUP);
499 }
500
501 void ast_format_cap_remove_by_type(struct ast_format_cap *cap, enum ast_media_type type)
502 {
503         int idx;
504
505         for (idx = 0; idx < AST_VECTOR_SIZE(&cap->formats); ++idx) {
506                 struct format_cap_framed_list *list = AST_VECTOR_GET_ADDR(&cap->formats, idx);
507                 struct format_cap_framed *framed;
508
509                 AST_LIST_TRAVERSE_SAFE_BEGIN(list, framed, entry) {
510                         if ((type != AST_MEDIA_TYPE_UNKNOWN) &&
511                                 ast_format_get_type(framed->format) != type) {
512                                 continue;
513                         }
514
515                         AST_LIST_REMOVE_CURRENT(entry);
516                         AST_VECTOR_REMOVE_CMP_ORDERED(&cap->preference_order, framed->format,
517                                 FORMAT_CAP_FRAMED_ELEM_CMP, FORMAT_CAP_FRAMED_ELEM_CLEANUP);
518                         ao2_ref(framed, -1);
519                 }
520                 AST_LIST_TRAVERSE_SAFE_END;
521         }
522 }
523
524 struct ast_format *ast_format_cap_get_compatible_format(const struct ast_format_cap *cap, const struct ast_format *format)
525 {
526         struct format_cap_framed_list *list;
527         struct format_cap_framed *framed;
528         struct ast_format *result = NULL;
529
530         ast_assert(format != NULL);
531
532         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
533                 return NULL;
534         }
535
536         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
537
538         AST_LIST_TRAVERSE(list, framed, entry) {
539                 enum ast_format_cmp_res res = ast_format_cmp(format, framed->format);
540
541                 if (res == AST_FORMAT_CMP_NOT_EQUAL) {
542                         continue;
543                 }
544
545                 /* Replace any current result, this one will also be a subset OR an exact match */
546                 ao2_cleanup(result);
547
548                 result = ast_format_joint(format, framed->format);
549
550                 /* If it's a match we can do no better so return asap */
551                 if (res == AST_FORMAT_CMP_EQUAL) {
552                         break;
553                 }
554         }
555
556         return result;
557 }
558
559 enum ast_format_cmp_res ast_format_cap_iscompatible_format(const struct ast_format_cap *cap,
560         const struct ast_format *format)
561 {
562         enum ast_format_cmp_res res = AST_FORMAT_CMP_NOT_EQUAL;
563         struct format_cap_framed_list *list;
564         struct format_cap_framed *framed;
565
566         ast_assert(format != NULL);
567
568         if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) {
569                 return AST_FORMAT_CMP_NOT_EQUAL;
570         }
571
572         list = AST_VECTOR_GET_ADDR(&cap->formats, ast_format_get_codec_id(format));
573
574         AST_LIST_TRAVERSE(list, framed, entry) {
575                 enum ast_format_cmp_res cmp = ast_format_cmp(format, framed->format);
576
577                 if (cmp == AST_FORMAT_CMP_NOT_EQUAL) {
578                         continue;
579                 }
580
581                 res = cmp;
582
583                 if (res == AST_FORMAT_CMP_EQUAL) {
584                         break;
585                 }
586         }
587
588         return res;
589 }
590
591 int ast_format_cap_has_type(const struct ast_format_cap *cap, enum ast_media_type type)
592 {
593         int idx;
594
595         for (idx = 0; idx < AST_VECTOR_SIZE(&cap->preference_order); ++idx) {
596                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap->preference_order, idx);
597
598                 if (ast_format_get_type(framed->format) == type) {
599                         return 1;
600                 }
601         }
602
603         return 0;
604 }
605
606 int ast_format_cap_get_compatible(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2,
607         struct ast_format_cap *result)
608 {
609         int idx, res = 0;
610
611         for (idx = 0; idx < AST_VECTOR_SIZE(&cap1->preference_order); ++idx) {
612                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap1->preference_order, idx);
613                 struct ast_format *format;
614
615                 format = ast_format_cap_get_compatible_format(cap2, framed->format);
616                 if (!format) {
617                         continue;
618                 }
619
620                 res = ast_format_cap_append(result, format, framed->framing);
621                 ao2_ref(format, -1);
622
623                 if (res) {
624                         break;
625                 }
626         }
627
628         return res;
629 }
630
631 int ast_format_cap_iscompatible(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
632 {
633         int idx;
634
635         for (idx = 0; idx < AST_VECTOR_SIZE(&cap1->preference_order); ++idx) {
636                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap1->preference_order, idx);
637
638                 if (ast_format_cap_iscompatible_format(cap2, framed->format) != AST_FORMAT_CMP_NOT_EQUAL) {
639                         return 1;
640                 }
641         }
642
643         return 0;
644 }
645
646 static int internal_format_cap_identical(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
647 {
648         int idx;
649         struct ast_format *tmp;
650
651         for (idx = 0; idx < AST_VECTOR_SIZE(&cap1->preference_order); ++idx) {
652                 tmp = ast_format_cap_get_format(cap1, idx);
653
654                 if (ast_format_cap_iscompatible_format(cap2, tmp) != AST_FORMAT_CMP_EQUAL) {
655                         ao2_ref(tmp, -1);
656                         return 0;
657                 }
658
659                 ao2_ref(tmp, -1);
660         }
661
662         return 1;
663 }
664
665 int ast_format_cap_identical(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
666 {
667         if (AST_VECTOR_SIZE(&cap1->preference_order) != AST_VECTOR_SIZE(&cap2->preference_order)) {
668                 return 0; /* if they are not the same size, they are not identical */
669         }
670
671         if (!internal_format_cap_identical(cap1, cap2)) {
672                 return 0;
673         }
674
675         return internal_format_cap_identical(cap2, cap1);
676 }
677
678 const char *ast_format_cap_get_names(struct ast_format_cap *cap, struct ast_str **buf)
679 {
680         int i;
681
682         ast_str_set(buf, 0, "(");
683
684         if (!AST_VECTOR_SIZE(&cap->preference_order)) {
685                 ast_str_append(buf, 0, "nothing)");
686                 return ast_str_buffer(*buf);
687         }
688
689         for (i = 0; i < AST_VECTOR_SIZE(&cap->preference_order); ++i) {
690                 int res;
691                 struct format_cap_framed *framed = AST_VECTOR_GET(&cap->preference_order, i);
692
693                 res = ast_str_append(buf, 0, "%s%s", ast_format_get_name(framed->format),
694                         i < AST_VECTOR_SIZE(&cap->preference_order) - 1 ? "|" : "");
695                 if (res < 0) {
696                         break;
697                 }
698         }
699         ast_str_append(buf, 0, ")");
700
701         return ast_str_buffer(*buf);
702 }
703
704 int ast_format_cap_empty(struct ast_format_cap *cap)
705 {
706         int count = ast_format_cap_count(cap);
707
708         if (count > 1) {
709                 return 0;
710         }
711
712         if (count == 0 || AST_VECTOR_GET(&cap->preference_order, 0)->format == ast_format_none) {
713                 return 1;
714         }
715
716         return 0;
717 }