1 /*
2  * Copyright (C) 2014 The Android Open Source Project
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 
17 #define LOG_TAG "alsa_device_profile"
18 /*#define LOG_NDEBUG 0*/
19 /*#define LOG_PCM_PARAMS 0*/
20 
21 #include <errno.h>
22 #include <inttypes.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <cutils/properties.h>
26 
27 #include <log/log.h>
28 
29 #include "include/alsa_device_profile.h"
30 #include "include/alsa_format.h"
31 #include "include/alsa_logging.h"
32 
33 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
34 
35 #define PERIOD_DURATION_US (5 * 1000)
36 
37 #define DEFAULT_PERIOD_SIZE 1024
38 
39 static const char * const format_string_map[] = {
40     "AUDIO_FORMAT_PCM_16_BIT",      /* "PCM_FORMAT_S16_LE", */
41     "AUDIO_FORMAT_PCM_32_BIT",      /* "PCM_FORMAT_S32_LE", */
42     "AUDIO_FORMAT_PCM_8_BIT",       /* "PCM_FORMAT_S8", */
43     "AUDIO_FORMAT_PCM_8_24_BIT",    /* "PCM_FORMAT_S24_LE", */
44     "AUDIO_FORMAT_PCM_24_BIT_PACKED"/* "PCM_FORMAT_S24_3LE" */
45 };
46 
47 extern int8_t const pcm_format_value_map[50];
48 
49 /* Sort these in terms of preference (best first).
50    192 kHz is not first because it requires significant resources for possibly worse
51    quality and driver instability (depends on device).
52    The order here determines the default sample rate for the device.
53    AudioPolicyManager may not respect this ordering when picking sample rates.
54    Update MAX_PROFILE_SAMPLE_RATES after changing the array size.
55 
56    TODO: remove 32000, 22050, 12000, 11025?  Each sample rate check
57    requires opening the device which may cause pops. */
58 static const unsigned std_sample_rates[] =
59     {96000, 88200, 192000, 176400, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000};
60 
61 static void profile_reset(alsa_device_profile* profile)
62 {
63     profile->card = profile->device = -1;
64 
65     /* terminate the attribute arrays with invalid values */
66     profile->formats[0] = PCM_FORMAT_INVALID;
67     profile->sample_rates[0] = 0;
68     profile->channel_counts[0] = 0;
69 
70     profile->min_period_size = profile->max_period_size = 0;
71     profile->min_channel_count = profile->max_channel_count = DEFAULT_CHANNEL_COUNT;
72 
73     profile->is_valid = false;
74 }
75 
76 void profile_init(alsa_device_profile* profile, int direction)
77 {
78     profile->direction = direction;
79     profile_reset(profile);
80 }
81 
82 bool profile_is_initialized(const alsa_device_profile* profile)
83 {
84     return profile->card >= 0 && profile->device >= 0;
85 }
86 
87 bool profile_is_valid(const alsa_device_profile* profile) {
88     return profile->is_valid;
89 }
90 
91 bool profile_is_cached_for(const alsa_device_profile* profile, int card, int device) {
92     return card == profile->card && device == profile->device;
93 }
94 
95 void profile_decache(alsa_device_profile* profile) {
96     profile_reset(profile);
97 }
98 
99 /*
100  * Returns the supplied value rounded up to the next even multiple of 16
101  */
102 static unsigned int round_to_16_mult(unsigned int size)
103 {
104     return (size + 15) & ~15;   /* 0xFFFFFFF0; */
105 }
106 
107 /*
108  * Returns the system defined minimum period size based on the supplied sample rate.
109  */
110 unsigned profile_calc_min_period_size(const alsa_device_profile* profile, unsigned sample_rate)
111 {
112     ALOGV("profile_calc_min_period_size(%p, rate:%d)", profile, sample_rate);
113     if (profile == NULL) {
114         return DEFAULT_PERIOD_SIZE;
115     } else {
116         unsigned period_us = property_get_int32("ro.audio.usb.period_us", PERIOD_DURATION_US);
117         unsigned num_sample_frames = ((uint64_t)sample_rate * period_us) / 1000000;
118 
119         if (num_sample_frames < profile->min_period_size) {
120             num_sample_frames = profile->min_period_size;
121         }
122         return round_to_16_mult(num_sample_frames);
123     }
124 }
125 
126 unsigned int profile_get_period_size(const alsa_device_profile* profile, unsigned sample_rate)
127 {
128     unsigned int period_size = profile_calc_min_period_size(profile, sample_rate);
129     ALOGV("profile_get_period_size(rate:%d) = %d", sample_rate, period_size);
130     return period_size;
131 }
132 
133 /*
134  * Sample Rate
135  */
136 unsigned profile_get_default_sample_rate(const alsa_device_profile* profile)
137 {
138     /*
139      * This is probably a poor algorithm. The default sample rate should be the highest (within
140      * limits) rate that is available for both input and output. HOWEVER, the profile has only
141      * one or the other, so that will need to be done at a higher level, like in the HAL.
142      */
143     /*
144      * TODO this won't be right in general. we should store a preferred rate as we are scanning.
145      * But right now it will return the highest rate, which may be correct.
146      */
147     return profile_is_valid(profile) ? profile->sample_rates[0] : DEFAULT_SAMPLE_RATE;
148 }
149 
150 unsigned profile_get_highest_sample_rate(const alsa_device_profile* profile) {
151     /* The hightest sample rate is always stored in the first element of sample_rates.
152      * Note that profile_reset() initiaizes the first element of samples_rates to 0
153      * Which is what we want to return if the profile had not been read anyway.
154      */
155     return profile->sample_rates[0];
156 }
157 
158 bool profile_is_sample_rate_valid(const alsa_device_profile* profile, unsigned rate)
159 {
160     if (profile_is_valid(profile)) {
161         size_t index;
162         for (index = 0; profile->sample_rates[index] != 0; index++) {
163             if (profile->sample_rates[index] == rate) {
164                 return true;
165             }
166         }
167 
168         return false;
169     } else {
170         ALOGW("**** PROFILE NOT VALID!");
171         return rate == DEFAULT_SAMPLE_RATE;
172     }
173 }
174 
175 /*
176  * Format
177  */
178 enum pcm_format profile_get_default_format(const alsa_device_profile* profile)
179 {
180     /*
181      * TODO this won't be right in general. we should store a preferred format as we are scanning.
182      */
183     return profile_is_valid(profile) ? profile->formats[0] : DEFAULT_SAMPLE_FORMAT;
184 }
185 
186 bool profile_is_format_valid(const alsa_device_profile* profile, enum pcm_format fmt) {
187     if (profile_is_valid(profile)) {
188         size_t index;
189         for (index = 0; profile->formats[index] != PCM_FORMAT_INVALID; index++) {
190             if (profile->formats[index] == fmt) {
191                 return true;
192             }
193         }
194 
195         return false;
196     } else {
197         return fmt == DEFAULT_SAMPLE_FORMAT;
198     }
199 }
200 
201 /*
202  * Channels
203  */
204 unsigned profile_get_default_channel_count(const alsa_device_profile* profile)
205 {
206     return profile_is_valid(profile) ? profile->channel_counts[0] : DEFAULT_CHANNEL_COUNT;
207 }
208 
209 unsigned profile_get_closest_channel_count(const alsa_device_profile* profile, unsigned count)
210 {
211     if (profile_is_valid(profile)) {
212         if (count < profile->min_channel_count) {
213             return profile->min_channel_count;
214         } else if (count > profile->max_channel_count) {
215             return profile->max_channel_count;
216         } else {
217             return count;
218         }
219     } else {
220         return 0;
221     }
222 }
223 
224 bool profile_is_channel_count_valid(const alsa_device_profile* profile, unsigned count)
225 {
226     if (profile_is_initialized(profile)) {
227         return count >= profile->min_channel_count && count <= profile->max_channel_count;
228     } else {
229         return count == DEFAULT_CHANNEL_COUNT;
230     }
231 }
232 
233 static bool profile_test_sample_rate(const alsa_device_profile* profile, unsigned rate)
234 {
235     struct pcm_config config = profile->default_config;
236     config.rate = rate;
237 
238     bool works = false; /* let's be pessimistic */
239     struct pcm * pcm = pcm_open(profile->card, profile->device,
240                                 profile->direction, &config);
241 
242     if (pcm != NULL) {
243         works = pcm_is_ready(pcm);
244         pcm_close(pcm);
245     }
246 
247     return works;
248 }
249 
250 static unsigned profile_enum_sample_rates(alsa_device_profile* profile, unsigned min, unsigned max)
251 {
252     unsigned num_entries = 0;
253     unsigned index;
254 
255     for (index = 0; index < ARRAY_SIZE(std_sample_rates) &&
256                     num_entries < ARRAY_SIZE(profile->sample_rates) - 1;
257          index++) {
258         if (std_sample_rates[index] >= min && std_sample_rates[index] <= max
259                 && profile_test_sample_rate(profile, std_sample_rates[index])) {
260             profile->sample_rates[num_entries++] = std_sample_rates[index];
261         }
262     }
263     profile->sample_rates[num_entries] = 0; /* terminate */
264     return num_entries; /* return # of supported rates */
265 }
266 
267 static unsigned profile_enum_sample_formats(alsa_device_profile* profile, struct pcm_mask * mask)
268 {
269     const int num_slots = ARRAY_SIZE(mask->bits);
270     const int bits_per_slot = sizeof(mask->bits[0]) * 8;
271 
272     const int table_size = ARRAY_SIZE(pcm_format_value_map);
273 
274     int slot_index, bit_index, table_index;
275     table_index = 0;
276     int num_written = 0;
277     for (slot_index = 0; slot_index < num_slots && table_index < table_size;
278             slot_index++) {
279         unsigned bit_mask = 1;
280         for (bit_index = 0;
281                 bit_index < bits_per_slot && table_index < table_size;
282                 bit_index++) {
283             if ((mask->bits[slot_index] & bit_mask) != 0) {
284                 enum pcm_format format = pcm_format_value_map[table_index];
285                 /* Never return invalid (unrecognized) or 8-bit */
286                 if (format != PCM_FORMAT_INVALID && format != PCM_FORMAT_S8) {
287                     profile->formats[num_written++] = format;
288                     if (num_written == ARRAY_SIZE(profile->formats) - 1) {
289                         /* leave at least one PCM_FORMAT_INVALID at the end */
290                         goto end;
291                     }
292                 }
293             }
294             bit_mask <<= 1;
295             table_index++;
296         }
297     }
298 end:
299     profile->formats[num_written] = PCM_FORMAT_INVALID;
300     return num_written;
301 }
302 
303 static unsigned profile_enum_channel_counts(alsa_device_profile* profile, unsigned min,
304         unsigned max)
305 {
306     /* modify alsa_device_profile.h if you change the std_channel_counts[] array. */
307     static const unsigned std_channel_counts[] = {8, 7, 6, 5, 4, 3, 2, 1};
308 
309     unsigned num_counts = 0;
310     unsigned index;
311     /* TODO write a profile_test_channel_count() */
312     /* Ensure there is at least one invalid channel count to terminate the channel counts array */
313     for (index = 0; index < ARRAY_SIZE(std_channel_counts) &&
314                     num_counts < ARRAY_SIZE(profile->channel_counts) - 1;
315          index++) {
316         /* TODO Do we want a channel counts test? */
317         if (std_channel_counts[index] >= min && std_channel_counts[index] <= max /* &&
318             profile_test_channel_count(profile, channel_counts[index])*/) {
319             profile->channel_counts[num_counts++] = std_channel_counts[index];
320         }
321     }
322     // if we have no match with the standard counts, we use the largest (preferred) std count.
323     if (num_counts == 0) {
324         ALOGW("usb device does not match std channel counts, setting to %d",
325                 std_channel_counts[0]);
326         profile->channel_counts[num_counts++] = std_channel_counts[0];
327     }
328     profile->channel_counts[num_counts] = 0;
329     return num_counts; /* return # of supported counts */
330 }
331 
332 /*
333  * Reads and decodes configuration info from the specified ALSA card/device.
334  */
335 static int read_alsa_device_config(alsa_device_profile * profile, struct pcm_config * config)
336 {
337     ALOGV("usb:audio_hw - read_alsa_device_config(c:%d d:%d t:0x%X)",
338           profile->card, profile->device, profile->direction);
339 
340     if (profile->card < 0 || profile->device < 0) {
341         return -EINVAL;
342     }
343 
344     struct pcm_params * alsa_hw_params =
345         pcm_params_get(profile->card, profile->device, profile->direction);
346     if (alsa_hw_params == NULL) {
347         return -EINVAL;
348     }
349 
350     profile->min_period_size = pcm_params_get_min(alsa_hw_params, PCM_PARAM_PERIOD_SIZE);
351     profile->max_period_size = pcm_params_get_max(alsa_hw_params, PCM_PARAM_PERIOD_SIZE);
352 
353     profile->min_channel_count = pcm_params_get_min(alsa_hw_params, PCM_PARAM_CHANNELS);
354     profile->max_channel_count = pcm_params_get_max(alsa_hw_params, PCM_PARAM_CHANNELS);
355 
356     int ret = 0;
357 
358     /*
359      * This Logging will be useful when testing new USB devices.
360      */
361 #ifdef LOG_PCM_PARAMS
362     log_pcm_params(alsa_hw_params);
363 #endif
364 
365     config->channels = pcm_params_get_min(alsa_hw_params, PCM_PARAM_CHANNELS);
366     // For output devices, let's make sure we choose at least stereo
367     // (assuming the device supports it).
368     if (profile->direction == PCM_OUT &&
369         config->channels < 2 && pcm_params_get_max(alsa_hw_params, PCM_PARAM_CHANNELS) >= 2) {
370         config->channels = 2;
371     }
372     config->rate = pcm_params_get_min(alsa_hw_params, PCM_PARAM_RATE);
373     // Prefer 48K or 44.1K
374     if (config->rate < 48000 &&
375         pcm_params_get_max(alsa_hw_params, PCM_PARAM_RATE) >= 48000) {
376         config->rate = 48000;
377     } else if (config->rate < 44100 &&
378                pcm_params_get_max(alsa_hw_params, PCM_PARAM_RATE) >= 44100) {
379         config->rate = 44100;
380     }
381     config->period_size = profile_calc_min_period_size(profile, config->rate);
382     config->period_count = pcm_params_get_min(alsa_hw_params, PCM_PARAM_PERIODS);
383     config->format = get_pcm_format_for_mask(pcm_params_get_mask(alsa_hw_params, PCM_PARAM_FORMAT));
384 #ifdef LOG_PCM_PARAMS
385     log_pcm_config(config, "read_alsa_device_config");
386 #endif
387     if (config->format == PCM_FORMAT_INVALID) {
388         ret = -EINVAL;
389     }
390 
391     pcm_params_free(alsa_hw_params);
392 
393     return ret;
394 }
395 
396 bool profile_read_device_info(alsa_device_profile* profile)
397 {
398     if (!profile_is_initialized(profile)) {
399         return false;
400     }
401 
402     /* let's get some defaults */
403     read_alsa_device_config(profile, &profile->default_config);
404     ALOGV("default_config chans:%d rate:%d format:%d count:%d size:%d",
405           profile->default_config.channels, profile->default_config.rate,
406           profile->default_config.format, profile->default_config.period_count,
407           profile->default_config.period_size);
408 
409     struct pcm_params * alsa_hw_params = pcm_params_get(profile->card,
410                                                         profile->device,
411                                                         profile->direction);
412     if (alsa_hw_params == NULL) {
413         return false;
414     }
415 
416     /* Formats */
417     struct pcm_mask * format_mask = pcm_params_get_mask(alsa_hw_params, PCM_PARAM_FORMAT);
418     profile_enum_sample_formats(profile, format_mask);
419 
420     /* Channels */
421     profile_enum_channel_counts(
422             profile, pcm_params_get_min(alsa_hw_params, PCM_PARAM_CHANNELS),
423             pcm_params_get_max(alsa_hw_params, PCM_PARAM_CHANNELS));
424 
425     /* Sample Rates */
426     profile_enum_sample_rates(
427             profile, pcm_params_get_min(alsa_hw_params, PCM_PARAM_RATE),
428             pcm_params_get_max(alsa_hw_params, PCM_PARAM_RATE));
429 
430     profile->is_valid = true;
431 
432     pcm_params_free(alsa_hw_params);
433     return true;
434 }
435 
436 char * profile_get_sample_rate_strs(const alsa_device_profile* profile)
437 {
438     /* if we assume that rate strings are about 5 characters (48000 is 5), plus ~1 for a
439      * delimiter "|" this buffer has room for about 22 rate strings which seems like
440      * way too much, but it's a stack variable so only temporary.
441      */
442     char buffer[128];
443     buffer[0] = '\0';
444     size_t buffSize = ARRAY_SIZE(buffer);
445     size_t curStrLen = 0;
446 
447     char numBuffer[32];
448 
449     size_t numEntries = 0;
450     size_t index;
451     for (index = 0; profile->sample_rates[index] != 0; index++) {
452         snprintf(numBuffer, sizeof(numBuffer), "%u", profile->sample_rates[index]);
453         // account for both the null, and potentially the bar.
454         if (buffSize - curStrLen < strlen(numBuffer) + (numEntries != 0 ? 2 : 1)) {
455             /* we don't have room for another, so bail at this point rather than
456              * return a malformed rate string
457              */
458             break;
459         }
460         if (numEntries++ != 0) {
461             strlcat(buffer, "|", buffSize);
462         }
463         curStrLen = strlcat(buffer, numBuffer, buffSize);
464     }
465 
466     return strdup(buffer);
467 }
468 
469 char * profile_get_format_strs(const alsa_device_profile* profile)
470 {
471     /* if we assume that format strings are about 24 characters (AUDIO_FORMAT_PCM_16_BIT is 23),
472      * plus ~1 for a delimiter "|" this buffer has room for about 10 format strings which seems
473      *  like way too much, but it's a stack variable so only temporary.
474      */
475     char buffer[256];
476     buffer[0] = '\0';
477     size_t buffSize = ARRAY_SIZE(buffer);
478     size_t curStrLen = 0;
479 
480     size_t numEntries = 0;
481     size_t index = 0;
482     for (index = 0; profile->formats[index] != PCM_FORMAT_INVALID; index++) {
483         // account for both the null, and potentially the bar.
484         if (buffSize - curStrLen < strlen(format_string_map[profile->formats[index]])
485                                    + (numEntries != 0 ? 2 : 1)) {
486             /* we don't have room for another, so bail at this point rather than
487              * return a malformed rate string
488              */
489             break;
490         }
491         if (numEntries++ != 0) {
492             strlcat(buffer, "|", buffSize);
493         }
494         curStrLen = strlcat(buffer, format_string_map[profile->formats[index]], buffSize);
495     }
496 
497     return strdup(buffer);
498 }
499 
500 char * profile_get_channel_count_strs(const alsa_device_profile* profile)
501 {
502     // FIXME implicit fixed channel count assumption here (FCC_8).
503     // we use only the canonical even number channel position masks.
504     static const char * const out_chans_strs[] = {
505         /* 0 */"AUDIO_CHANNEL_NONE", /* will never be taken as this is a terminator */
506         /* 1 */"AUDIO_CHANNEL_OUT_MONO",
507         /* 2 */"AUDIO_CHANNEL_OUT_STEREO",
508         /* 3 */ /* "AUDIO_CHANNEL_OUT_STEREO|AUDIO_CHANNEL_OUT_FRONT_CENTER" */ NULL,
509         /* 4 */"AUDIO_CHANNEL_OUT_QUAD",
510         /* 5 */ /* "AUDIO_CHANNEL_OUT_QUAD|AUDIO_CHANNEL_OUT_FRONT_CENTER" */ NULL,
511         /* 6 */"AUDIO_CHANNEL_OUT_5POINT1",
512         /* 7 */ /* "AUDIO_CHANNEL_OUT_5POINT1|AUDIO_CHANNEL_OUT_BACK_CENTER" */ NULL,
513         /* 8 */"AUDIO_CHANNEL_OUT_7POINT1",
514         /* channel counts greater than this not considered */
515     };
516 
517     static const char * const in_chans_strs[] = {
518         /* 0 */"AUDIO_CHANNEL_NONE", /* will never be taken as this is a terminator */
519         /* 1 */"AUDIO_CHANNEL_IN_MONO",
520         /* 2 */"AUDIO_CHANNEL_IN_STEREO",
521         /* channel counts greater than this not considered */
522     };
523 
524     static const char * const index_chans_strs[] = {
525         /* 0 */"AUDIO_CHANNEL_NONE", /* will never be taken as this is a terminator */
526         /* 1 */"AUDIO_CHANNEL_INDEX_MASK_1",
527         /* 2 */"AUDIO_CHANNEL_INDEX_MASK_2",
528         /* 3 */"AUDIO_CHANNEL_INDEX_MASK_3",
529         /* 4 */"AUDIO_CHANNEL_INDEX_MASK_4",
530         /* 5 */"AUDIO_CHANNEL_INDEX_MASK_5",
531         /* 6 */"AUDIO_CHANNEL_INDEX_MASK_6",
532         /* 7 */"AUDIO_CHANNEL_INDEX_MASK_7",
533         /* 8 */"AUDIO_CHANNEL_INDEX_MASK_8",
534     };
535 
536     const bool isOutProfile = profile->direction == PCM_OUT;
537 
538     const char * const * const chans_strs = isOutProfile ? out_chans_strs : in_chans_strs;
539     const size_t chans_strs_size =
540             isOutProfile ? ARRAY_SIZE(out_chans_strs) : ARRAY_SIZE(in_chans_strs);
541 
542     /*
543      * If we assume each channel string is 26 chars ("AUDIO_CHANNEL_INDEX_MASK_8" is 26) + 1 for,
544      * the "|" delimiter, then we allocate room for 16 strings.
545      */
546     char buffer[27 * 16 + 1]; /* caution, may need to be expanded */
547     buffer[0] = '\0';
548     size_t buffSize = ARRAY_SIZE(buffer);
549     size_t curStrLen = 0;
550 
551     /* We currently support MONO and STEREO, and always report STEREO but some (many)
552      * USB Audio Devices may only announce support for MONO (a headset mic for example), or
553      * The total number of output channels. SO, if the device itself doesn't explicitly
554      * support STEREO, append to the channel config strings we are generating.
555      *
556      * The MONO and STEREO positional channel masks are provided for legacy compatibility.
557      * For multichannel (n > 2) we only expose channel index masks.
558      */
559     // Always support stereo
560     curStrLen = strlcat(buffer, chans_strs[2], buffSize);
561 
562     size_t index;
563     unsigned channel_count;
564     for (index = 0;
565          (channel_count = profile->channel_counts[index]) != 0;
566          index++) {
567 
568         /* we only show positional information for mono (stereo handled already) */
569         if (channel_count < chans_strs_size
570                 && chans_strs[channel_count] != NULL
571                 && channel_count < 2 /* positional only for fewer than 2 channels */) {
572             // account for the '|' and the '\0'
573             if (buffSize - curStrLen < strlen(chans_strs[channel_count]) + 2) {
574                 /* we don't have room for another, so bail at this point rather than
575                  * return a malformed rate string
576                  */
577                 break;
578             }
579 
580             strlcat(buffer, "|", buffSize);
581             curStrLen = strlcat(buffer, chans_strs[channel_count], buffSize);
582         }
583 
584         // handle channel index masks for both input and output
585         // +2 to account for the '|' and the '\0'
586          if (buffSize - curStrLen < strlen(index_chans_strs[channel_count]) + 2) {
587              /* we don't have room for another, so bail at this point rather than
588               * return a malformed rate string
589               */
590              break;
591          }
592 
593          strlcat(buffer, "|", buffSize);
594          curStrLen = strlcat(buffer, index_chans_strs[channel_count], buffSize);
595     }
596 
597     return strdup(buffer);
598 }
599 
600 void profile_dump(const alsa_device_profile* profile, int fd)
601 {
602     if (profile == NULL) {
603         dprintf(fd, "  %s\n", "No USB Profile");
604         return; /* bail early */
605     }
606 
607     if (!profile->is_valid) {
608         dprintf(fd, "  Profile is INVALID");
609     }
610 
611     /* card/device/direction */
612     dprintf(fd, "  card:%d, device:%d - %s\n",
613                 profile->card, profile->device, profile->direction == PCM_OUT ? "OUT" : "IN");
614 
615     /* formats */
616     dprintf(fd, "  Formats: ");
617     for (int fmtIndex = 0;
618           profile->formats[fmtIndex] != PCM_FORMAT_INVALID && fmtIndex < MAX_PROFILE_FORMATS;
619           fmtIndex++) {
620         dprintf(fd, "%d ", profile->formats[fmtIndex]);
621     }
622     dprintf(fd, "\n");
623 
624     /* sample rates */
625     dprintf(fd, "  Rates: ");
626     for (int rateIndex = 0;
627           profile->sample_rates[rateIndex] != 0 && rateIndex < MAX_PROFILE_SAMPLE_RATES;
628           rateIndex++) {
629         dprintf(fd, "%u ", profile->sample_rates[rateIndex]);
630     }
631     dprintf(fd, "\n");
632 
633     // channel counts
634     dprintf(fd, "  Channel Counts: ");
635     for (int cntIndex = 0;
636           profile->channel_counts[cntIndex] != 0 && cntIndex < MAX_PROFILE_CHANNEL_COUNTS;
637           cntIndex++) {
638         dprintf(fd, "%u ", profile->channel_counts[cntIndex]);
639     }
640     dprintf(fd, "\n");
641 
642     dprintf(fd, "  min/max period size [%u : %u]\n",
643             profile->min_period_size,profile-> max_period_size);
644     dprintf(fd, "  min/max channel count [%u : %u]\n",
645             profile->min_channel_count, profile->max_channel_count);
646 
647     // struct pcm_config default_config;
648     dprintf(fd, "  Default Config:\n");
649     dprintf(fd, "    channels: %d\n", profile->default_config.channels);
650     dprintf(fd, "    rate: %d\n", profile->default_config.rate);
651     dprintf(fd, "    period_size: %d\n", profile->default_config.period_size);
652     dprintf(fd, "    period_count: %d\n", profile->default_config.period_count);
653     dprintf(fd, "    format: %d\n", profile->default_config.format);
654 }
655