/* * BlueALSA - a2dpconf.c * SPDX-FileCopyrightText: 2020-2025 BlueALSA developers * SPDX-License-Identifier: MIT */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "shared/a2dp-codecs.h" #include "shared/defs.h" #include "shared/hex.h" static uint32_t get_codec(const char *s) { char buffer[32] = { 0 }; char *tmp; strncpy(buffer, s, sizeof(buffer) - 1); if ((tmp = strchr(buffer, ':')) != NULL) tmp[0] = '\0'; if (strcasecmp(buffer, "vendor") == 0) return A2DP_CODEC_VENDOR; return a2dp_codecs_codec_id_from_string(buffer); } static ssize_t get_codec_blob(const char *s, void *dest, size_t n) { const char *tmp; if ((tmp = strchr(s, ':')) != NULL) s = tmp + 1; size_t len; if ((len = strlen(s)) % 2 != 0) { fprintf(stderr, "Invalid blob: Size not a multiple of 2: %zd\n", len); return -1; } if (n == 0) return len / 2; if (n * 2 < len) { fprintf(stderr, "Invalid blob buffer size: %zd < %zd\n", n * 2, len); return -1; } return hex2bin(s, dest, len); } static char *bintohex(const void *src, size_t n) { char *hex = calloc(1, n * 2 + 1); bin2hex(src, hex, n); return hex; } static int check_blob_size(size_t size, size_t value) { if (value == size) return 0; fprintf(stderr, "Invalid codec blob size: %zd != %zd\n", value, size); return -1; } static void print_bits(const void *bstream, size_t offset, size_t n, size_t size) { char mask[] = ".... .... .... .... .... .... .... ...."; size_t mask_spaces = size == 0 ? 0 : (size - 1) / 4; mask[size + mask_spaces] = '\0'; for (size_t i = offset; i < offset + n; i++) { size_t spaces = i == 0 ? 0 : i / 4; uint8_t byte = ((uint8_t *)bstream)[i / 8]; uint8_t bit = 1 << (8 - (i % 8) - 1); mask[i + spaces] = byte & bit ? '1' : '0'; } printf(" %s", mask); } struct bitfield { uint32_t value; const char *label; }; static bool verbose = false; static void print_bitfield(const char *name, const void *bstream, size_t offset, size_t n, size_t size, uint32_t value, const struct bitfield *fields) { if (!verbose) { printf(" %s (", name); int elems = 0; if (fields == NULL) printf(" %s", value ? "true" : "false"); else for (const struct bitfield *f = fields; f->label != NULL; f++) if (f->value == 0 || value & f->value) printf("%s%s", elems++ == 0 ? " " : " | ", f->label); printf(" )\n"); } else { for (size_t i = 0; i < n; i++) { uint32_t bit = 1 << (n - i - 1); print_bits(bstream, offset + i, 1, size); printf(" = %s", name); if (fields != NULL) for (const struct bitfield *f = fields; f->label != NULL; f++) if (bit & f->value) printf(" %s", f->label); printf(": %s\n", bit & value ? "true" : "false"); } } } static void print_value(const char *name, const void *bstream, size_t offset, size_t n, size_t size, const char *format, ...) { va_list ap; va_start(ap, format); if (!verbose) { printf(" %s ( ", name); vprintf(format, ap); printf(" )\n"); } else { print_bits(bstream, offset, n, size); printf(" = %s: ", name); vprintf(format, ap); printf("\n"); } va_end(ap); } #define print_bitfield8(name, bstream, offset, n, value, fields) \ print_bitfield(name, bstream, offset, n, 8, value, fields) #define print_bitfield16(name, bstream, offset, n, value, fields) \ print_bitfield(name, bstream, offset, n, 16, value, fields) #define print_bitfield24(name, bstream, offset, n, value, fields) \ print_bitfield(name, bstream, offset, n, 24, value, fields) #define print_bitfield32(name, bstream, offset, n, value, fields) \ print_bitfield(name, bstream, offset, n, 32, value, fields) #define print_bool8(name, bstream, offset, n, value) \ print_bitfield(name, bstream, offset, n, 8, value, NULL) #define print_bool16(name, bstream, offset, n, value) \ print_bitfield(name, bstream, offset, n, 16, value, NULL) #define print_bool24(name, bstream, offset, n, value) \ print_bitfield(name, bstream, offset, n, 24, value, NULL) #define print_bool32(name, bstream, offset, n, value) \ print_bitfield(name, bstream, offset, n, 32, value, NULL) #define print_value8(name, bstream, offset, n, format, ...) \ print_value(name, bstream, offset, n, 8, format, __VA_ARGS__) #define print_value16(name, bstream, offset, n, format, ...) \ print_value(name, bstream, offset, n, 16, format, __VA_ARGS__) #define print_value24(name, bstream, offset, n, format, ...) \ print_value(name, bstream, offset, n, 24, format, __VA_ARGS__) #define print_value32(name, bstream, offset, n, format, ...) \ print_value(name, bstream, offset, n, 32, format, __VA_ARGS__) static void dump_sbc(const void *blob, size_t size) { const a2dp_sbc_t *sbc = blob; if (check_blob_size(sizeof(*sbc), size) == -1) return; static const struct bitfield ch_modes[] = { { SBC_CHANNEL_MODE_MONO, "Mono" }, { SBC_CHANNEL_MODE_DUAL_CHANNEL, "Dual Channel" }, { SBC_CHANNEL_MODE_STEREO, "Stereo" }, { SBC_CHANNEL_MODE_JOINT_STEREO, "Joint Stereo" }, { 0 }, }; static const struct bitfield rates[] = { { SBC_SAMPLING_FREQ_16000, "16000 Hz" }, { SBC_SAMPLING_FREQ_32000, "32000 Hz" }, { SBC_SAMPLING_FREQ_44100, "44100 Hz" }, { SBC_SAMPLING_FREQ_48000, "48000 Hz" }, { 0 }, }; static const struct bitfield blocks[] = { { SBC_BLOCK_LENGTH_4, "4" }, { SBC_BLOCK_LENGTH_8, "8" }, { SBC_BLOCK_LENGTH_12, "12" }, { SBC_BLOCK_LENGTH_16, "16" }, { 0 }, }; static const struct bitfield bands[] = { { SBC_SUBBANDS_4, "4" }, { SBC_SUBBANDS_8, "8" }, { 0 }, }; static const struct bitfield allocs[] = { { SBC_ALLOCATION_SNR, "SNR" }, { SBC_ALLOCATION_LOUDNESS, "Loudness" }, { 0 }, }; const uint8_t *bstream = blob; printf("SBC {\n", bintohex(sbc, sizeof(*sbc))); print_bitfield8("Sample Rate", bstream, 0, 4, sbc->sampling_freq, rates); print_bitfield8("Channel Mode", bstream, 4, 4, sbc->channel_mode, ch_modes); print_bitfield8("Block Length", bstream += 1, 0, 4, sbc->block_length, blocks); print_bitfield8("Sub-bands", bstream, 4, 2, sbc->subbands, bands); print_bitfield8("Allocation Method", bstream, 6, 2, sbc->allocation_method, allocs); print_value8("Min Bit-pool", bstream += 1, 0, 8, "%u", sbc->min_bitpool); print_value8("Max Bit-pool", bstream += 1, 0, 8, "%u", sbc->max_bitpool); printf("}\n"); } static void dump_mpeg(const void *blob, size_t size) { const a2dp_mpeg_t *mpeg = blob; if (check_blob_size(sizeof(*mpeg), size) == -1) return; static const struct bitfield layers[] = { { MPEG_LAYER_MP1, "MP1" }, { MPEG_LAYER_MP2, "MP2" }, { MPEG_LAYER_MP3, "MP3" }, { 0 }, }; static const struct bitfield ch_modes[] = { { MPEG_CHANNEL_MODE_MONO, "Mono" }, { MPEG_CHANNEL_MODE_DUAL_CHANNEL, "Dual Channel" }, { MPEG_CHANNEL_MODE_STEREO, "Stereo" }, { MPEG_CHANNEL_MODE_JOINT_STEREO, "Joint Stereo" }, { 0 }, }; static const struct bitfield rates[] = { { MPEG_SAMPLING_FREQ_16000, "16000 Hz" }, { MPEG_SAMPLING_FREQ_22050, "22050 Hz" }, { MPEG_SAMPLING_FREQ_24000, "24000 Hz" }, { MPEG_SAMPLING_FREQ_32000, "32000 Hz" }, { MPEG_SAMPLING_FREQ_44100, "44100 Hz" }, { MPEG_SAMPLING_FREQ_48000, "48000 Hz" }, { 0 }, }; static const struct bitfield mpfs[] = { { 0, "MPF-1" }, { 1, "MPF-2" }, { 0 }, }; static const struct bitfield indexes[] = { { MPEG_BITRATE_INDEX_0, "0" }, { MPEG_BITRATE_INDEX_1, "1" }, { MPEG_BITRATE_INDEX_2, "2" }, { MPEG_BITRATE_INDEX_3, "3" }, { MPEG_BITRATE_INDEX_4, "4" }, { MPEG_BITRATE_INDEX_5, "5" }, { MPEG_BITRATE_INDEX_6, "6" }, { MPEG_BITRATE_INDEX_7, "7" }, { MPEG_BITRATE_INDEX_8, "8" }, { MPEG_BITRATE_INDEX_9, "9" }, { MPEG_BITRATE_INDEX_10, "10" }, { MPEG_BITRATE_INDEX_11, "11" }, { MPEG_BITRATE_INDEX_12, "12" }, { MPEG_BITRATE_INDEX_13, "13" }, { MPEG_BITRATE_INDEX_14, "14" }, { 0 }, }; static const struct bitfield indexes1[] = { { MPEG_BITRATE_INDEX_8 >> 8, "8" }, { MPEG_BITRATE_INDEX_9 >> 8, "9" }, { MPEG_BITRATE_INDEX_10 >> 8, "10" }, { MPEG_BITRATE_INDEX_11 >> 8, "11" }, { MPEG_BITRATE_INDEX_12 >> 8, "12" }, { MPEG_BITRATE_INDEX_13 >> 8, "13" }, { MPEG_BITRATE_INDEX_14 >> 8, "14" }, { 0 }, }; const uint8_t *bstream = blob; printf("MPEG-1,2 Audio {\n", bintohex(mpeg, sizeof(*mpeg))); print_bitfield8("Layer", bstream, 0, 3, mpeg->layer, layers); print_bool8("CRC", bstream, 3, 1, mpeg->crc); print_bitfield8("Channel Mode", bstream, 4, 4, mpeg->channel_mode, ch_modes); print_value8("RFA", bstream += 1, 0, 1, "%u", mpeg->rfa); print_bitfield8("Media Payload Format", bstream, 1, 1, mpeg->mpf, mpfs); print_bitfield8("Sample Rate", bstream, 2, 6, mpeg->sampling_freq, rates); print_bool8("VBR", bstream += 1, 0, 1, mpeg->vbr); if (!verbose) { const uint16_t mpeg_bitrate = A2DP_MPEG_GET_BITRATE(*mpeg); print_bitfield16("Bitrate Index", bstream, 1, 15, mpeg_bitrate, indexes); } else { print_bitfield8("Bitrate Index", bstream, 1, 7, mpeg->bitrate1, indexes1); print_bitfield8("Bitrate Index", bstream += 1, 0, 8, mpeg->bitrate2, indexes); } printf("}\n"); } static void dump_aac(const void *blob, size_t size) { const a2dp_aac_t *aac = blob; if (check_blob_size(sizeof(*aac), size) == -1) return; static const struct bitfield objects[] = { { AAC_OBJECT_TYPE_MPEG2_LC, "MPEG2-LC" }, { AAC_OBJECT_TYPE_MPEG4_LC, "MPEG4-LC" }, { AAC_OBJECT_TYPE_MPEG4_LTP, "MPEG4-LTP" }, { AAC_OBJECT_TYPE_MPEG4_SCA, "MPEG4-SCA" }, { AAC_OBJECT_TYPE_MPEG4_HE, "MPEG4-HE" }, { AAC_OBJECT_TYPE_MPEG4_HE2, "MPEG4-HE2" }, { AAC_OBJECT_TYPE_MPEG4_ELD2, "MPEG4-ELD2" }, { 0 }, }; static const struct bitfield ch_modes[] = { { AAC_CHANNEL_MODE_MONO, "Mono" }, { AAC_CHANNEL_MODE_STEREO, "Stereo" }, { AAC_CHANNEL_MODE_5_1, "Surround-5.1" }, { AAC_CHANNEL_MODE_7_1, "Surround-7.1" }, { 0 }, }; static const struct bitfield rates[] = { { AAC_SAMPLING_FREQ_8000, "8000 Hz" }, { AAC_SAMPLING_FREQ_11025, "11025 Hz" }, { AAC_SAMPLING_FREQ_12000, "12000 Hz" }, { AAC_SAMPLING_FREQ_16000, "16000 Hz" }, { AAC_SAMPLING_FREQ_22050, "22050 Hz" }, { AAC_SAMPLING_FREQ_24000, "24000 Hz" }, { AAC_SAMPLING_FREQ_32000, "32000 Hz" }, { AAC_SAMPLING_FREQ_44100, "44100 Hz" }, { AAC_SAMPLING_FREQ_48000, "48000 Hz" }, { AAC_SAMPLING_FREQ_64000, "64000 Hz" }, { AAC_SAMPLING_FREQ_88200, "88200 Hz" }, { AAC_SAMPLING_FREQ_96000, "96000 Hz" }, { 0 }, }; static const struct bitfield rates1[] = { { AAC_SAMPLING_FREQ_8000 >> 4, "8000 Hz" }, { AAC_SAMPLING_FREQ_11025 >> 4, "11025 Hz" }, { AAC_SAMPLING_FREQ_12000 >> 4, "12000 Hz" }, { AAC_SAMPLING_FREQ_16000 >> 4, "16000 Hz" }, { AAC_SAMPLING_FREQ_22050 >> 4, "22050 Hz" }, { AAC_SAMPLING_FREQ_24000 >> 4, "24000 Hz" }, { AAC_SAMPLING_FREQ_32000 >> 4, "32000 Hz" }, { AAC_SAMPLING_FREQ_44100 >> 4, "44100 Hz" }, { 0 }, }; const uint8_t *bstream = blob; printf("MPEG-2,4 AAC {\n", bintohex(aac, sizeof(*aac))); print_bitfield8("Object Type", bstream, 0, 7, aac->object_type, objects); print_bool8("Dynamic Range Control", bstream, 7, 1, aac->drc); if (!verbose) { const uint16_t aac_sampling_freq = A2DP_AAC_GET_SAMPLING_FREQ(*aac); print_bitfield16("Sample Rate", bstream += 1, 0, 12, aac_sampling_freq, rates); bstream += 1; } else { print_bitfield8("Sample Rate", bstream += 1, 0, 8, aac->sampling_freq1, rates1); print_bitfield8("Sample Rate", bstream += 1, 0, 4, aac->sampling_freq2, rates); } print_bitfield8("Channel Mode", bstream, 4, 4, aac->channel_mode, ch_modes); print_bool24("VBR", bstream += 1, 0, 1, aac->vbr); print_value24("Bitrate", bstream, 1, 23, "%u", A2DP_AAC_GET_BITRATE(*aac)); printf("}\n"); } static void dump_usac(const void *blob, size_t size) { const a2dp_usac_t *usac = blob; if (check_blob_size(sizeof(*usac), size) == -1) return; static const struct bitfield objects[] = { { USAC_OBJECT_TYPE_MPEGD_DRC, "MPEG-D-DRC" }, { 1 << 0, "RFA" }, { 0 }, }; static const struct bitfield ch_modes[] = { { USAC_CHANNEL_MODE_MONO, "Mono" }, { USAC_CHANNEL_MODE_STEREO, "Stereo" }, { 1 << 1, "RFA" }, { 1 << 0, "RFA" }, { 0 }, }; static const struct bitfield rates[] = { { USAC_SAMPLING_FREQ_7350, "7350 Hz" }, { USAC_SAMPLING_FREQ_8000, "8000 Hz" }, { USAC_SAMPLING_FREQ_8820, "8820 Hz" }, { USAC_SAMPLING_FREQ_9600, "9600 Hz" }, { USAC_SAMPLING_FREQ_11025, "11025 Hz" }, { USAC_SAMPLING_FREQ_11760, "11760 Hz" }, { USAC_SAMPLING_FREQ_12000, "12000 Hz" }, { USAC_SAMPLING_FREQ_12800, "12800 Hz" }, { USAC_SAMPLING_FREQ_14700, "14700 Hz" }, { USAC_SAMPLING_FREQ_16000, "16000 Hz" }, { USAC_SAMPLING_FREQ_17640, "17640 Hz" }, { USAC_SAMPLING_FREQ_19200, "19200 Hz" }, { USAC_SAMPLING_FREQ_22050, "22050 Hz" }, { USAC_SAMPLING_FREQ_24000, "24000 Hz" }, { USAC_SAMPLING_FREQ_29400, "29400 Hz" }, { USAC_SAMPLING_FREQ_32000, "32000 Hz" }, { USAC_SAMPLING_FREQ_35280, "35280 Hz" }, { USAC_SAMPLING_FREQ_38400, "38400 Hz" }, { USAC_SAMPLING_FREQ_44100, "44100 Hz" }, { USAC_SAMPLING_FREQ_48000, "48000 Hz" }, { USAC_SAMPLING_FREQ_58800, "58800 Hz" }, { USAC_SAMPLING_FREQ_64000, "64000 Hz" }, { USAC_SAMPLING_FREQ_70560, "70560 Hz" }, { USAC_SAMPLING_FREQ_76800, "76800 Hz" }, { USAC_SAMPLING_FREQ_88200, "88200 Hz" }, { USAC_SAMPLING_FREQ_96000, "96000 Hz" }, { 0 }, }; const uint8_t *bstream = blob; printf("MPEG-D USAC {\n", bintohex(usac, sizeof(*usac))); print_bitfield32("Object Type", bstream, 0, 2, usac->object_type, objects); const uint32_t usac_sampling_freq = A2DP_USAC_GET_SAMPLING_FREQ(*usac); print_bitfield32("Sample Rate", bstream, 2, 26, usac_sampling_freq, rates); print_bitfield32("Channel Mode", bstream, 28, 4, usac->channel_mode, ch_modes); print_bool24("VBR", bstream += 4, 0, 1, usac->vbr); print_value24("Bitrate", bstream, 1, 23, "%u", A2DP_USAC_GET_BITRATE(*usac)); printf("}\n"); } static void dump_atrac(const void *blob, size_t size) { const a2dp_atrac_t *atrac = blob; if (check_blob_size(sizeof(*atrac), size) == -1) return; static const struct bitfield ch_modes[] = { { ATRAC_CHANNEL_MODE_MONO, "Mono" }, { ATRAC_CHANNEL_MODE_DUAL_CHANNEL, "Dual Channel" }, { ATRAC_CHANNEL_MODE_JOINT_STEREO, "Joint Stereo" }, { 0 }, }; static const struct bitfield rates[] = { { ATRAC_SAMPLING_FREQ_44100, "44100 Hz" }, { ATRAC_SAMPLING_FREQ_48000, "48000 Hz" }, { 0 }, }; static const struct bitfield indexes[] = { { 1 << 18, "0" }, { 1 << 17, "1" }, { 1 << 16, "2" }, { 1 << 15, "3" }, { 1 << 14, "4" }, { 1 << 13, "5" }, { 1 << 12, "6" }, { 1 << 11, "7" }, { 1 << 10, "8" }, { 1 << 9, "9" }, { 1 << 8, "10" }, { 1 << 7, "11" }, { 1 << 6, "12" }, { 1 << 5, "13" }, { 1 << 4, "14" }, { 1 << 3, "15" }, { 1 << 2, "16" }, { 1 << 1, "17" }, { 1 << 0, "18" }, { 0 }, }; const uint8_t *bstream = blob; printf("ATRAC {\n", bintohex(atrac, sizeof(*atrac))); print_value8("Version", bstream, 0, 3, "%u", atrac->version); print_bitfield8("Channel Mode", bstream, 3, 3, atrac->channel_mode, ch_modes); print_value8("RFA", bstream, 6, 2, "%#x", atrac->rfa1); print_value24("RFA", ++bstream, 0, 2, "%#x", atrac->rfa2); print_bitfield24("Sample Rate", bstream, 2, 2, atrac->sampling_freq, rates); print_bool24("VBR", bstream, 4, 1, atrac->vbr); const uint32_t atrac_bitrate = A2DP_ATRAC_GET_BITRATE(*atrac); print_bitfield24("Bitrate Index", bstream, 5, 19, atrac_bitrate, indexes); const uint16_t atrac_max_sul = A2DP_ATRAC_GET_MAX_SUL(*atrac); print_value16("Max Sound Unit Length", bstream += 3, 0, 16, "%u", atrac_max_sul); print_value8("RFA", bstream += 2, 0, 8, "%#x", atrac->rfa3); printf("}\n"); } static void print_vendor(const a2dp_vendor_info_t *info) { const uint32_t vendor_id = A2DP_VENDOR_INFO_GET_VENDOR_ID(*info); const char *vendor_name = bt_compidtostr(vendor_id); print_value32("Vendor ID", &info->vendor_id, 0, 32, "%#010x [%s]", vendor_id, vendor_name); const uint16_t codec_id = A2DP_VENDOR_INFO_GET_CODEC_ID(*info); print_value16("Vendor Codec ID", &info->codec_id, 0, 16, "%#06x", codec_id); } static void dump_vendor(const void *blob, size_t size) { const a2dp_vendor_info_t *info = blob; if (size <= sizeof(*info)) return; printf(" {\n", bintohex(blob, size)); print_vendor(info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); printf(" Data ( hex:%s )\n", bintohex(bstream, size - sizeof(*info))); printf("}\n"); } static const struct bitfield aptx_ch_modes[] = { { APTX_CHANNEL_MODE_MONO, "Mono" }, { APTX_CHANNEL_MODE_STEREO, "Stereo" }, { APTX_CHANNEL_MODE_TWS, "TWS" }, { 0 }, }; static const struct bitfield aptx_rates[] = { { APTX_SAMPLING_FREQ_16000, "16000 Hz" }, { APTX_SAMPLING_FREQ_32000, "32000 Hz" }, { APTX_SAMPLING_FREQ_44100, "44100 Hz" }, { APTX_SAMPLING_FREQ_48000, "48000 Hz" }, { 0 }, }; static void dump_aptx(const void *blob, size_t size) { const a2dp_aptx_t *aptx = blob; if (check_blob_size(sizeof(*aptx), size) == -1) return; printf("aptX {\n", bintohex(blob, size)); print_vendor(&aptx->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_bitfield8("Sample Rate", bstream, 0, 4, aptx->sampling_freq, aptx_rates); print_bitfield8("Channel Mode", bstream, 4, 4, aptx->channel_mode, aptx_ch_modes); printf("}\n"); } static void dump_aptx_tws(const void *blob, size_t size) { const a2dp_aptx_t *aptx = blob; if (check_blob_size(sizeof(*aptx), size) == -1) return; printf("aptX-TWS {\n", bintohex(blob, size)); print_vendor(&aptx->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_bitfield8("Sample Rate", bstream, 0, 4, aptx->sampling_freq, aptx_rates); print_bitfield8("Channel Mode", bstream, 4, 4, aptx->channel_mode, aptx_ch_modes); printf("}\n"); } static void dump_aptx_ad(const void *blob, size_t size) { const a2dp_aptx_ad_t *aptx = blob; if (check_blob_size(sizeof(*aptx), size) == -1) return; static const struct bitfield ch_modes[] = { { APTX_AD_CHANNEL_MODE_MONO, "Mono" }, { APTX_AD_CHANNEL_MODE_STEREO, "Stereo" }, { APTX_AD_CHANNEL_MODE_TWS, "TWS" }, { APTX_AD_CHANNEL_MODE_JOINT_STEREO, "Joint Stereo" }, { APTX_AD_CHANNEL_MODE_TWS_MONO, "TWS-Mono" }, { 0 }, }; static const struct bitfield rates[] = { { APTX_AD_SAMPLING_FREQ_44100, "44100 Hz" }, { APTX_AD_SAMPLING_FREQ_48000, "48000 Hz" }, { APTX_AD_SAMPLING_FREQ_88000, "88000 Hz" }, { APTX_AD_SAMPLING_FREQ_192000, "192000 Hz" }, { 0 }, }; printf("aptX Adaptive {\n", bintohex(blob, size)); print_vendor(&aptx->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_bitfield8("Sample Rate", bstream, 0, 5, aptx->sampling_freq, rates); print_value8("RFA", bstream, 5, 3, "%#x", aptx->rfa1); print_value8("RFA", bstream += 1, 0, 3, "%#x", aptx->rfa2); print_bitfield8("Channel Mode", bstream, 3, 5, aptx->channel_mode, ch_modes); print_value8("TTP-LL Low", bstream += 1, 0, 8, "%u", aptx->ttp_ll_low); print_value8("TTP-LL High", bstream += 1, 0, 8, "%u", aptx->ttp_ll_high); print_value8("TTP-HQ Low", bstream += 1, 0, 8, "%u", aptx->ttp_hq_low); print_value8("TTP-HQ High", bstream += 1, 0, 8, "%u", aptx->ttp_hq_high); print_value8("TTP-TWS Low", bstream += 1, 0, 8, "%u", aptx->ttp_tws_low); print_value8("TTP-TWS High", bstream += 1, 0, 8, "%u", aptx->ttp_tws_high); print_value24("EOC", bstream += 1, 0, 24, "hex:%02x%02x%02x", aptx->eoc[0], aptx->eoc[1], aptx->eoc[2]); printf("}\n"); } static void dump_aptx_hd(const void *blob, size_t size) { const a2dp_aptx_hd_t *aptx = blob; if (check_blob_size(sizeof(*aptx), size) == -1) return; printf("aptX HD {\n", bintohex(blob, size)); print_vendor(&aptx->aptx.info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_bitfield8("Sample Rate", bstream, 0, 4, aptx->aptx.sampling_freq, aptx_rates); print_bitfield8("Channel Mode", bstream, 4, 4, aptx->aptx.channel_mode, aptx_ch_modes); print_value32("RFA", bstream += 1, 0, 32, "%#010x", aptx->rfa); printf("}\n"); } static void dump_aptx_ll(const void *blob, size_t size) { const a2dp_aptx_ll_t *aptx = blob; const a2dp_aptx_ll_new_t *aptx_new_caps = blob; size_t conf_size = sizeof(*aptx); if (size >= sizeof(*aptx) && aptx->has_new_caps) conf_size = sizeof(*aptx_new_caps); if (check_blob_size(conf_size, size) == -1) return; printf("aptX LL (Sprint) {\n", bintohex(blob, size)); print_vendor(&aptx->aptx.info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_bitfield8("Sample Rate", bstream, 0, 4, aptx->aptx.sampling_freq, aptx_rates); print_bitfield8("Channel Mode", bstream, 4, 4, aptx->aptx.channel_mode, aptx_ch_modes); print_value8("RFA", bstream += 1, 0, 6, "%#x", aptx->reserved); print_bool8("Has New Capabilities", bstream, 6, 1, aptx->has_new_caps); print_bool8("Bidirectional Link", bstream, 7, 1, aptx->bidirect_link); if (aptx->has_new_caps) { print_value8("RFA", bstream += 1, 0, 8, "%#x", aptx_new_caps->reserved); const uint16_t aptx_target_level = A2DP_APTX_LL_GET_TARGET_CODEC_LEVEL(*aptx_new_caps); print_value16("Target Codec Level", bstream += 1, 0, 16, "%u", aptx_target_level); const uint16_t aptx_initial_level = A2DP_APTX_LL_GET_INITIAL_CODEC_LEVEL(*aptx_new_caps); print_value16("Initial Codec Level", bstream += 2, 0, 16, "%u", aptx_initial_level); print_value8("SRA Max Rate", bstream += 2, 0, 8, "%u", aptx_new_caps->sra_max_rate); print_value8("SRA Avg Time", bstream += 1, 0, 8, "%u", aptx_new_caps->sra_avg_time); const uint16_t aptx_working_level = A2DP_APTX_LL_GET_GOOD_WORKING_LEVEL(*aptx_new_caps); print_value16("Good Working Level", bstream += 1, 0, 16, "%u", aptx_working_level); } printf("}\n"); } static void dump_faststream(const void *blob, size_t size) { const a2dp_faststream_t *fs = blob; if (check_blob_size(sizeof(*fs), size) == -1) return; static const struct bitfield directions[] = { { FASTSTREAM_DIRECTION_MUSIC, "Music" }, { FASTSTREAM_DIRECTION_VOICE, "Voice" }, { 0 }, }; static const struct bitfield rates_music[] = { { FASTSTREAM_SAMPLING_FREQ_MUSIC_48000, "48000 Hz" }, { FASTSTREAM_SAMPLING_FREQ_MUSIC_44100, "44100 Hz" }, { 0 }, }; static const struct bitfield rates_voice[] = { { FASTSTREAM_SAMPLING_FREQ_VOICE_16000, "16000 Hz" }, { 0 }, }; printf("FastStream {\n", bintohex(blob, size)); print_vendor(&fs->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_value8("RFA", bstream, 0, 6, "%#x", fs->direction >> 2); print_bitfield8("Direction", bstream, 6, 2, fs->direction, directions); print_bitfield8("Sample Rate Voice", bstream += 1, 0, 4, fs->sampling_freq_voice, rates_voice); print_bitfield8("Sample Rate Music", bstream, 4, 4, fs->sampling_freq_music, rates_music); printf("}\n"); } static void dump_lc3plus(const void *blob, size_t size) { const a2dp_lc3plus_t *lc3plus = blob; if (check_blob_size(sizeof(*lc3plus), size) == -1) return; static const struct bitfield durations[] = { { LC3PLUS_FRAME_DURATION_025, "2.5 ms" }, { LC3PLUS_FRAME_DURATION_050, "5 ms" }, { LC3PLUS_FRAME_DURATION_100, "10 ms" }, { 0 }, }; static const struct bitfield ch_modes[] = { { LC3PLUS_CHANNEL_MODE_MONO, "Mono" }, { LC3PLUS_CHANNEL_MODE_STEREO, "Stereo" }, { 0 }, }; static const struct bitfield rates[] = { { LC3PLUS_SAMPLING_FREQ_48000, "48000 Hz" }, { LC3PLUS_SAMPLING_FREQ_96000, "96000 Hz" }, { 0 }, }; printf("LC3plus {\n", bintohex(blob, size)); print_vendor(&lc3plus->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_bitfield8("Frame Duration", bstream, 0, 4, lc3plus->frame_duration, durations); print_value8("RFA", bstream, 4, 4, "%#x", lc3plus->rfa); print_bitfield8("Channel Mode", bstream += 1, 0, 8, lc3plus->channel_mode, ch_modes); const uint16_t lc3plus_sampling_freq = A2DP_LC3PLUS_GET_SAMPLING_FREQ(*lc3plus); print_bitfield16("Sample Rate", bstream += 1, 0, 16, lc3plus_sampling_freq, rates); printf("}\n"); } static void dump_ldac(const void *blob, size_t size) { const a2dp_ldac_t *ldac = blob; if (check_blob_size(sizeof(*ldac), size) == -1) return; static const struct bitfield ch_modes[] = { { LDAC_CHANNEL_MODE_MONO, "Mono" }, { LDAC_CHANNEL_MODE_DUAL, "Dual Channel" }, { LDAC_CHANNEL_MODE_STEREO, "Stereo" }, { 0 }, }; static const struct bitfield rates[] = { { LDAC_SAMPLING_FREQ_44100, "44100 Hz" }, { LDAC_SAMPLING_FREQ_48000, "48000 Hz" }, { LDAC_SAMPLING_FREQ_88200, "88200 Hz" }, { LDAC_SAMPLING_FREQ_96000, "96000 Hz" }, { LDAC_SAMPLING_FREQ_176400, "176400 Hz" }, { LDAC_SAMPLING_FREQ_192000, "192000 Hz" }, { 0 }, }; printf("LDAC {\n", bintohex(blob, size)); print_vendor(&ldac->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_value8("RFA", bstream, 0, 2, "%#x", ldac->rfa1); print_bitfield8("Sample Rate", bstream, 2, 6, ldac->sampling_freq, rates); print_value8("RFA", bstream += 1, 0, 5, "%#x", ldac->rfa2); print_bitfield8("Channel Mode", bstream, 5, 3, ldac->channel_mode, ch_modes); printf("}\n"); } static const struct bitfield lhdc_rates[] = { { LHDC_SAMPLING_FREQ_44100, "44100 Hz" }, { LHDC_SAMPLING_FREQ_48000, "48000 Hz" }, { LHDC_SAMPLING_FREQ_88200, "88200 Hz" }, { LHDC_SAMPLING_FREQ_96000, "96000 Hz" }, { 0 }, }; static const struct bitfield lhdc_bit_depths[] = { { LHDC_BIT_DEPTH_16, "16 bits" }, { LHDC_BIT_DEPTH_24, "24 bits" }, { 0 }, }; static const struct bitfield lhdc_versions[] = { { LHDC_VER3, "v3" }, { 0 }, }; static const struct bitfield lhdc_ch_split_modes[] = { { LHDC_CH_SPLIT_MODE_NONE, "None" }, { LHDC_CH_SPLIT_MODE_TWS, "TWS" }, { LHDC_CH_SPLIT_MODE_TWS_PLUS, "TWS+" }, { 0 }, }; static int lhdc_get_max_bitrate(unsigned int value) { switch (value) { case LHDC_MAX_BITRATE_400K: return 400; case LHDC_MAX_BITRATE_500K: return 500; case LHDC_MAX_BITRATE_900K: return 900; default: return -1; } } static void dump_lhdc_v1(const void *blob, size_t size) { const a2dp_lhdc_v1_t *lhdc = blob; if (check_blob_size(sizeof(*lhdc), size) == -1) return; printf("LHDC v1 {\n", bintohex(blob, size)); print_vendor(&lhdc->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_value8("RFA", bstream, 0, 1, "%#x", lhdc->rfa); print_bool8("Channel Separation", bstream, 1, 1, lhdc->ch_separation); print_bitfield8("Bit Depth", bstream, 2, 2, lhdc->bit_depth, lhdc_bit_depths); print_bitfield8("Sample Rate", bstream, 4, 4, lhdc->sampling_freq, lhdc_rates); printf("}\n"); } static void dump_lhdc_v2(const void *blob, size_t size) { const a2dp_lhdc_v2_t *lhdc = blob; if (check_blob_size(sizeof(*lhdc), size) == -1) return; printf("LHDC v2 {\n", bintohex(blob, size)); print_vendor(&lhdc->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_value8("RFA", bstream, 0, 2, "%#x", lhdc->rfa1); print_bitfield8("Bit Depth", bstream, 2, 2, lhdc->bit_depth, lhdc_bit_depths); print_bitfield8("Sample Rate", bstream, 4, 4, lhdc->sampling_freq, lhdc_rates); print_bool8("Low Latency", bstream += 1, 0, 1, lhdc->low_latency); const uint16_t lhdc_max_bitrate = lhdc_get_max_bitrate(lhdc->max_bitrate); print_value8("Max Bitrate", bstream, 1, 3, "%#x [%u kbps]", lhdc->max_bitrate, lhdc_max_bitrate); print_bitfield8("Version", bstream, 4, 4, lhdc->version, lhdc_versions); print_value8("RFA", bstream += 1, 0, 4, "%#x", lhdc->rfa2); print_bitfield8("Channel Split Mode", bstream, 4, 4, lhdc->ch_split_mode, lhdc_ch_split_modes); printf("}\n"); } static void dump_lhdc_v3(const void *blob, size_t size) { const a2dp_lhdc_v3_t *lhdc = blob; if (check_blob_size(sizeof(*lhdc), size) == -1) return; printf("LHDC v3 {\n", bintohex(blob, size)); print_vendor(&lhdc->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_bool8("AR", bstream, 0, 1, lhdc->ar); print_bool8("JAS", bstream, 1, 1, lhdc->jas); print_bitfield8("Bit Depth", bstream, 2, 2, lhdc->bit_depth, lhdc_bit_depths); print_bitfield8("Sample Rate", bstream, 4, 4, lhdc->sampling_freq, lhdc_rates); print_bool8("LLAC", bstream += 1, 0, 1, lhdc->llac); print_bool8("Low Latency", bstream, 1, 1, lhdc->low_latency); const uint16_t lhdc_max_bitrate = lhdc_get_max_bitrate(lhdc->max_bitrate); print_value8("Max Bitrate", bstream, 2, 2, "%#x [%u kbps]", lhdc->max_bitrate, lhdc_max_bitrate); print_bitfield8("Version", bstream, 4, 4, lhdc->version, lhdc_versions); print_bool8("LHDC v4", bstream += 1, 0, 1, lhdc->lhdc_v4); print_bool8("LARC", bstream, 1, 1, lhdc->larc); print_bool8("Min Bitrate", bstream, 2, 1, lhdc->min_bitrate); print_bool8("Meta", bstream, 3, 1, lhdc->meta); print_bitfield8("Channel Split Mode", bstream, 4, 4, lhdc->ch_split_mode, lhdc_ch_split_modes); printf("}\n"); } static void dump_lhdc_v5(const void *blob, size_t size) { const a2dp_lhdc_v5_t *lhdc = blob; if (check_blob_size(sizeof(*lhdc), size) == -1) return; printf("LHDC v5 {\n", bintohex(blob, size)); print_vendor(&lhdc->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_value8("RFA", bstream, 0, 3, "%#x", lhdc->rfa1); print_bitfield8("Sample Rate", bstream, 3, 5, lhdc->sampling_freq, lhdc_rates); print_value8("Min Bitrate", bstream += 1, 0, 2, "%#x", lhdc->min_bitrate); const uint16_t lhdc_max_bitrate = lhdc_get_max_bitrate(lhdc->max_bitrate); print_value8("Max Bitrate", bstream, 2, 2, "%#x [%u kbps]", lhdc->max_bitrate, lhdc_max_bitrate); print_value8("RFA", bstream, 4, 1, "%#x", lhdc->rfa2); print_bitfield8("Bit Depth", bstream, 5, 3, lhdc->bit_depth, lhdc_bit_depths); print_value8("RFA", bstream += 1, 0, 3, "%#x", lhdc->rfa3); print_bool8("Frame Length 5ms", bstream, 3, 1, lhdc->frame_len_5ms); print_bitfield8("Version", bstream, 4, 4, lhdc->version, lhdc_versions); print_bool8("RFA", bstream += 1, 0, 1, lhdc->reserved); print_bool8("Low Latency", bstream, 1, 1, lhdc->low_latency); print_value8("RFA", bstream, 2, 3, "%#x", lhdc->rfa4); print_bool8("Meta", bstream, 5, 1, lhdc->meta); print_bool8("JAS", bstream, 6, 1, lhdc->jas); print_bool8("AR", bstream, 7, 1, lhdc->ar); print_value8("RFA", bstream += 1, 0, 7, "%#x", lhdc->rfa5); print_bool8("AR On", bstream, 7, 1, lhdc->ar_on); printf("}\n"); } static void dump_opus(const void *blob, size_t size) { const a2dp_opus_t *opus = blob; if (check_blob_size(sizeof(*opus), size) == -1) return; static const struct bitfield ch_modes[] = { { OPUS_CHANNEL_MODE_STEREO, "Stereo" }, { OPUS_CHANNEL_MODE_DUAL, "Dual Channel" }, { OPUS_CHANNEL_MODE_MONO, "Mono" }, { 0 }, }; static const struct bitfield rates[] = { { OPUS_SAMPLING_FREQ_48000, "48000 Hz" }, { OPUS_SAMPLING_FREQ_24000, "24000 Hz" }, { OPUS_SAMPLING_FREQ_16000, "16000 Hz" }, { 0 }, }; static const struct bitfield durations[] = { { OPUS_FRAME_DURATION_100, "10 ms" }, { OPUS_FRAME_DURATION_200, "20 ms" }, { 0 }, }; printf("Opus {\n", bintohex(blob, size)); print_vendor(&opus->info); const uint8_t *bstream = ((uint8_t *)blob) + sizeof(a2dp_vendor_info_t); print_bitfield8("Sample Rate", bstream, 0, 3, opus->sampling_freq, rates); print_bitfield8("Frame Duration", bstream, 3, 2, opus->frame_duration, durations); print_bitfield8("Channel Mode", bstream, 5, 3, opus->channel_mode, ch_modes); printf("}\n"); } static void dump_opus_pw(const void *blob, size_t size) { const a2dp_opus_pw_t *opus = blob; if (check_blob_size(sizeof(*opus), size) == -1) return; static const struct bitfield durations[] = { { OPUS_PW_FRAME_DURATION_025, "2.5 ms" }, { OPUS_PW_FRAME_DURATION_050, "5 ms" }, { OPUS_PW_FRAME_DURATION_100, "10 ms" }, { OPUS_PW_FRAME_DURATION_200, "20 ms" }, { OPUS_PW_FRAME_DURATION_400, "40 ms" }, { 0 }, }; printf("Opus (PipeWire) {\n", bintohex(blob, size)); print_vendor(&opus->info); const uint8_t *bstream; bstream = (uint8_t *)&opus->music; print_value8("Music Channels", bstream, 0, 8, "%u", opus->music.channels); print_value8("Music Coupled Streams", bstream += 1, 0, 8, "%u", opus->music.coupled_streams); const uint32_t opus_music_location = A2DP_OPUS_PW_GET_LOCATION(opus->music); print_value32("Music Location", bstream += 1, 0, 32, "%#x", opus_music_location); print_bitfield8("Music Frame Duration", bstream += 4, 0, 8, opus->music.frame_duration, durations); const uint32_t opus_music_bitrate = A2DP_OPUS_PW_GET_BITRATE(opus->music); print_value16("Music Bitrate", bstream += 1, 0, 16, "%u [%u kbps]", opus_music_bitrate, opus_music_bitrate * 1024); bstream = (uint8_t *)&opus->voice; print_value8("Voice Channels", bstream, 0, 8, "%u", opus->voice.channels); print_value8("Voice Coupled Streams", bstream += 1, 0, 8, "%u", opus->voice.coupled_streams); const uint32_t opus_voice_location = A2DP_OPUS_PW_GET_LOCATION(opus->voice); print_value32("Voice Location", bstream += 1, 0, 32, "%#x", opus_voice_location); print_bitfield8("Voice Frame Duration", bstream += 4, 0, 8, opus->voice.frame_duration, durations); const uint32_t opus_voice_bitrate = A2DP_OPUS_PW_GET_BITRATE(opus->voice); print_value16("Voice Bitrate", bstream += 1, 0, 16, "%u [%u kbps]", opus_voice_bitrate, opus_voice_bitrate * 1024); printf("}\n"); } static const struct { uint32_t codec_id; size_t blob_size; void (*dump)(const void *, size_t); } dumps[] = { { A2DP_CODEC_SBC, sizeof(a2dp_sbc_t), dump_sbc }, { A2DP_CODEC_MPEG12, sizeof(a2dp_mpeg_t), dump_mpeg }, { A2DP_CODEC_MPEG24, sizeof(a2dp_aac_t), dump_aac }, { A2DP_CODEC_MPEGD, sizeof(a2dp_usac_t), dump_usac }, { A2DP_CODEC_ATRAC, sizeof(a2dp_atrac_t), dump_atrac }, { A2DP_CODEC_VENDOR_ID(APTX_VENDOR_ID, APTX_CODEC_ID), sizeof(a2dp_aptx_t), dump_aptx }, { A2DP_CODEC_VENDOR_ID(APTX_TWS_VENDOR_ID, APTX_TWS_CODEC_ID), sizeof(a2dp_aptx_t), dump_aptx_tws }, { A2DP_CODEC_VENDOR_ID(APTX_AD_VENDOR_ID, APTX_AD_CODEC_ID), sizeof(a2dp_aptx_ad_t), dump_aptx_ad }, { A2DP_CODEC_VENDOR_ID(APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID), sizeof(a2dp_aptx_hd_t), dump_aptx_hd }, { A2DP_CODEC_VENDOR_ID(APTX_LL_VENDOR_ID, APTX_LL_CODEC_ID), sizeof(a2dp_aptx_ll_t), dump_aptx_ll }, { A2DP_CODEC_VENDOR_ID(APTX_LL_VENDOR_ID, APTX_LL_CODEC_ID), sizeof(a2dp_aptx_ll_new_t), dump_aptx_ll }, { A2DP_CODEC_VENDOR_ID(FASTSTREAM_VENDOR_ID, FASTSTREAM_CODEC_ID), sizeof(a2dp_faststream_t), dump_faststream }, { A2DP_CODEC_VENDOR_ID(LC3PLUS_VENDOR_ID, LC3PLUS_CODEC_ID), sizeof(a2dp_lc3plus_t), dump_lc3plus }, { A2DP_CODEC_VENDOR_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID), sizeof(a2dp_ldac_t), dump_ldac }, { A2DP_CODEC_VENDOR_ID(LHDC_V1_VENDOR_ID, LHDC_V1_CODEC_ID), sizeof(a2dp_lhdc_v1_t), dump_lhdc_v1 }, { A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID), sizeof(a2dp_lhdc_v2_t), dump_lhdc_v2 }, { A2DP_CODEC_VENDOR_ID(LHDC_V3_VENDOR_ID, LHDC_V3_CODEC_ID), sizeof(a2dp_lhdc_v3_t), dump_lhdc_v3 }, { A2DP_CODEC_VENDOR_ID(LHDC_V5_VENDOR_ID, LHDC_V5_CODEC_ID), sizeof(a2dp_lhdc_v5_t), dump_lhdc_v5 }, { A2DP_CODEC_VENDOR_ID(LHDC_LL_VENDOR_ID, LHDC_LL_CODEC_ID), -1, dump_vendor }, { A2DP_CODEC_VENDOR_ID(OPUS_VENDOR_ID, OPUS_CODEC_ID), sizeof(a2dp_opus_t), dump_opus }, { A2DP_CODEC_VENDOR_ID(OPUS_PW_VENDOR_ID, OPUS_PW_CODEC_ID), sizeof(a2dp_opus_pw_t), dump_opus_pw }, { A2DP_CODEC_VENDOR_ID(SAMSUNG_HD_VENDOR_ID, SAMSUNG_HD_CODEC_ID), -1, dump_vendor }, { A2DP_CODEC_VENDOR_ID(SAMSUNG_SC_VENDOR_ID, SAMSUNG_SC_CODEC_ID), -1, dump_vendor }, }; int dump(const char *config, bool detect) { uint32_t codec_id = get_codec(config); int rv = -1; ssize_t blob_size; if ((blob_size = get_codec_blob(config, NULL, 0)) == -1) return -1; void *blob = malloc(blob_size); if (get_codec_blob(config, blob, blob_size) == -1) goto final; if (codec_id == A2DP_CODEC_VENDOR && (size_t)blob_size >= sizeof(a2dp_vendor_info_t)) codec_id = a2dp_codecs_vendor_codec_id(blob); rv = 0; for (size_t i = 0; i < ARRAYSIZE(dumps); i++) if (dumps[i].codec_id == codec_id) { dumps[i].dump(blob, blob_size); goto final; } if (detect) { for (size_t i = 0; i < ARRAYSIZE(dumps); i++) if (dumps[i].blob_size == (size_t)blob_size) dumps[i].dump(blob, blob_size); dump_vendor(blob, blob_size); goto final; } fprintf(stderr, "Couldn't detect codec type: %s\n", config); rv = -1; final: free(blob); return rv; } int main(int argc, char *argv[]) { int opt; const char *opts = "hVvx"; const struct option longopts[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "verbose", no_argument, NULL, 'v' }, { "auto-detect", no_argument, NULL, 'x' }, { 0, 0, 0, 0 }, }; int rv = EXIT_SUCCESS; bool detect = false; while ((opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1) switch (opt) { case 'h' /* --help */ : usage: printf("Usage:\n" " %s [OPTION]... ...\n" "\nOptions:\n" " -h, --help\t\tprint this help and exit\n" " -V, --version\t\tprint version and exit\n" " -v, --verbose\t\tshow verbose bit-stream details\n" " -x, --auto-detect\ttry to auto-detect codec\n" "\nExamples:\n" " %s sbc:ffff0235\n" " %s vendor:4f0000000100ff\n", argv[0], argv[0], argv[0]); return EXIT_SUCCESS; case 'V' /* --version */ : printf("%s\n", PACKAGE_VERSION); return EXIT_SUCCESS; case 'v' /* --verbose */ : verbose = true; break; case 'x' /* --auto-detect */ : detect = true; break; default: fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); return EXIT_FAILURE; } if (argc - optind < 1) goto usage; for (int i = optind; i < argc; i++) if (dump(argv[i], detect) == -1) rv = EXIT_FAILURE; return rv; }