hc
2024-12-19 9370bb92b2d16684ee45cf24e879c93c509162da
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
/*
 * BlueALSA - utils.c
 * Copyright (c) 2016-2018 Arkadiusz Bokowy
 *
 * This file is a part of bluez-alsa.
 *
 * This project is licensed under the terms of the MIT license.
 *
 */
 
#include "utils.h"
 
#include <errno.h>
#include <stdlib.h>
#include <string.h>
 
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sco.h>
 
#if ENABLE_LDAC
# include "ldacBT.h"
#endif
 
#include "a2dp-codecs.h"
#include "bluez.h"
#include "shared/log.h"
 
 
/**
 * Calculate the optimum bitpool for given parameters.
 *
 * @param freq Sampling frequency.
 * @param model Channel mode.
 * @return Coded SBC bitpool. */
int a2dp_sbc_default_bitpool(int freq, int mode) {
   switch (freq) {
   case SBC_SAMPLING_FREQ_16000:
   case SBC_SAMPLING_FREQ_32000:
       return 53;
   case SBC_SAMPLING_FREQ_44100:
       switch (mode) {
       case SBC_CHANNEL_MODE_MONO:
       case SBC_CHANNEL_MODE_DUAL_CHANNEL:
           return 31;
       case SBC_CHANNEL_MODE_STEREO:
       case SBC_CHANNEL_MODE_JOINT_STEREO:
           return 53;
       default:
           warn("Invalid channel mode: %u", mode);
           return 53;
       }
   case SBC_SAMPLING_FREQ_48000:
       switch (mode) {
       case SBC_CHANNEL_MODE_MONO:
       case SBC_CHANNEL_MODE_DUAL_CHANNEL:
           return 29;
       case SBC_CHANNEL_MODE_STEREO:
       case SBC_CHANNEL_MODE_JOINT_STEREO:
           return 51;
       default:
           warn("Invalid channel mode: %u", mode);
           return 51;
       }
   default:
       warn("Invalid sampling freq: %u", freq);
       return 53;
   }
}
 
/**
 * Get the list of all available HCI controllers.
 *
 * @param di The address to the device info structure pointer, where the list
 *     of all available devices will be stored. Allocated memory should be freed
 *     with the free().
 * @param num The address, where the number of initialized device structures
 *     will be stored.
 * @return On success this function returns 0. Otherwise, -1 is returned and
 *     errno is set to indicate the error. */
int hci_devlist(struct hci_dev_info **di, int *num) {
 
   int i;
 
   if ((*di = malloc(HCI_MAX_DEV * sizeof(**di))) == NULL)
       return -1;
 
   for (i = *num = 0; i < HCI_MAX_DEV; i++)
       if (hci_devinfo(i, &(*di)[*num]) == 0)
           (*num)++;
 
   return 0;
}
 
/**
 * Open SCO link for given Bluetooth device.
 *
 * @param di The address to the HCI device info structure for which the SCO
 *   link should be established.
 * @param ba Pointer to the Bluetooth address structure for a target device.
 * @param transparent Use transparent mode for voice transmission.
 * @return On success this function returns socket file descriptor. Otherwise,
 *   -1 is returned and errno is set to indicate the error. */
int hci_open_sco(const struct hci_dev_info *di, const bdaddr_t *ba, bool transparent) {
 
   struct sockaddr_sco addr_hci = {
       .sco_family = AF_BLUETOOTH,
       .sco_bdaddr = di->bdaddr,
   };
   struct sockaddr_sco addr_dev = {
       .sco_family = AF_BLUETOOTH,
       .sco_bdaddr = *ba,
   };
   int dd, err;
 
   if ((dd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) == -1)
       return -1;
   if (bind(dd, (struct sockaddr *)&addr_hci, sizeof(addr_hci)) == -1)
       goto fail;
 
   if (transparent) {
       struct bt_voice voice = {
           .setting = BT_VOICE_TRANSPARENT,
       };
       if (setsockopt(dd, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)) == -1)
           goto fail;
   }
 
   if (connect(dd, (struct sockaddr *)&addr_dev, sizeof(addr_dev)) == -1)
       goto fail;
 
   return dd;
 
fail:
   err = errno;
   close(dd);
   errno = err;
   return -1;
}
 
int hci_submit_cmd_wait(uint16_t ogf, uint16_t ocf, uint8_t *params,
           uint8_t plen)
{
   int fd;
   uint16_t index = 0;
   uint8_t status;
   int ret;
   struct hci_request rq;
 
   fd = hci_open_dev(index);
   if (fd < 0) {
       error("Couldn't open device: %s(%d)\n", strerror(errno), errno);
       return -1;
   }
 
   memset(&rq, 0, sizeof(rq));
   rq.ogf = ogf;
   rq.ocf = ocf;
   rq.cparam = params;
   rq.clen = plen;
   rq.rparam = &status;
   rq.rlen = 1;
 
   ret = hci_send_req(fd, &rq, 1000);
   if (status || ret < 0) {
       error("Can't send cmd for hci%d: %s (%d)\n", index,
               strerror(errno), errno);
       hci_close_dev(fd);
       return -1;
   }
 
   hci_close_dev(fd);
 
   return 0;
}
 
/**
 * Get BlueZ D-Bus object path for given profile and codec.
 *
 * @param profile Bluetooth profile.
 * @param codec Bluetooth profile codec.
 * @return This function returns BlueZ D-Bus object path. */
const char *g_dbus_get_profile_object_path(enum bluetooth_profile profile, uint16_t codec) {
   switch (profile) {
   case BLUETOOTH_PROFILE_A2DP_SOURCE:
       switch (codec) {
       case A2DP_CODEC_SBC:
           return "/A2DP/SBC/Source";
#if ENABLE_MPEG
       case A2DP_CODEC_MPEG12:
           return "/A2DP/MPEG12/Source";
#endif
#if ENABLE_AAC
       case A2DP_CODEC_MPEG24:
           return "/A2DP/MPEG24/Source";
#endif
#if ENABLE_APTX
       case A2DP_CODEC_VENDOR_APTX:
           return "/A2DP/APTX/Source";
#endif
#if ENABLE_LDAC
       case A2DP_CODEC_VENDOR_LDAC:
           return "/A2DP/LDAC/Source";
#endif
       default:
           warn("Unsupported A2DP codec: %#x", codec);
           return "/A2DP/Source";
       }
   case BLUETOOTH_PROFILE_A2DP_SINK:
       switch (codec) {
       case A2DP_CODEC_SBC:
           return "/A2DP/SBC/Sink";
#if ENABLE_MPEG
       case A2DP_CODEC_MPEG12:
           return "/A2DP/MPEG12/Sink";
#endif
#if ENABLE_AAC
       case A2DP_CODEC_MPEG24:
           return "/A2DP/MPEG24/Sink";
#endif
#if ENABLE_APTX
       case A2DP_CODEC_VENDOR_APTX:
           return "/A2DP/APTX/Sink";
#endif
#if ENABLE_LDAC
       case A2DP_CODEC_VENDOR_LDAC:
           return "/A2DP/LDAC/Sink";
#endif
       default:
           warn("Unsupported A2DP codec: %#x", codec);
           return "/A2DP/Sink";
       }
   case BLUETOOTH_PROFILE_HSP_HS:
       return "/HSP/Headset";
   case BLUETOOTH_PROFILE_HSP_AG:
       return "/HSP/AudioGateway";
   case BLUETOOTH_PROFILE_HFP_HF:
       return "/HFP/HandsFree";
   case BLUETOOTH_PROFILE_HFP_AG:
       return "/HFP/AudioGateway";
   case BLUETOOTH_PROFILE_NULL:
       break;
   }
   return "/";
}
 
/**
 * Convert BlueZ D-Bus object path into a Bluetooth profile.
 *
 * @param path BlueZ D-Bus object path.
 * @return On success this function returns Bluetooth profile. If object
 *   path cannot be recognize, NULL profile is returned. */
enum bluetooth_profile g_dbus_object_path_to_profile(const char *path) {
   if (strncmp(path, "/A2DP", 5) == 0) {
       if (strstr(path + 5, "/Source") != NULL)
           return BLUETOOTH_PROFILE_A2DP_SOURCE;
       if (strstr(path + 5, "/Sink") != NULL)
           return BLUETOOTH_PROFILE_A2DP_SINK;
   }
   if (strncmp(path, "/HSP", 4) == 0) {
       if (strcmp(path + 4, "/Headset") == 0)
           return BLUETOOTH_PROFILE_HSP_HS;
       if (strcmp(path + 4, "/AudioGateway") == 0)
           return BLUETOOTH_PROFILE_HSP_AG;
   }
   if (strncmp(path, "/HFP", 4) == 0) {
       if (strcmp(path + 4, "/HandsFree") == 0)
           return BLUETOOTH_PROFILE_HFP_HF;
       if (strcmp(path + 4, "/AudioGateway") == 0)
           return BLUETOOTH_PROFILE_HFP_AG;
   }
   return BLUETOOTH_PROFILE_NULL;
}
 
/**
 * Convert BlueZ D-Bus device path into a bdaddr_t structure.
 *
 * @param path BlueZ D-Bus device path.
 * @param addr Address where the parsed address will be stored.
 * @return On success this function returns 0. Otherwise, -1 is returned. */
int g_dbus_device_path_to_bdaddr(const char *path, bdaddr_t *addr) {
 
   char *tmp, *p;
   int ret;
 
   if ((path = strrchr(path, '/')) == NULL)
       return -1;
   if ((path = strstr(path, "dev_")) == NULL)
       return -1;
   if ((tmp = strdup(path + 4)) == NULL)
       return -1;
 
   for (p = tmp; *p != '\0'; p++)
       if (*p == '_')
           *p = ':';
 
   ret = str2ba(tmp, addr);
 
   free(tmp);
   return ret;
}
 
/**
 * Get a property of a given D-Bus interface.
 *
 * @param conn D-Bus connection handler.
 * @param name Valid D-Bus name or NULL.
 * @param path Valid D-Bus object path.
 * @param interface Interface with the given property.
 * @param property The property name.
 * @return On success this function returns variant containing property value.
 *   Otherwise, NULL is returned. */
GVariant *g_dbus_get_property(GDBusConnection *conn, const char *name,
       const char *path, const char *interface, const char *property) {
 
   GDBusMessage *msg = NULL, *rep = NULL;
   GVariant *value = NULL;
   GError *err = NULL;
 
   msg = g_dbus_message_new_method_call(name, path, "org.freedesktop.DBus.Properties", "Get");
   g_dbus_message_set_body(msg, g_variant_new("(ss)", interface, property));
 
   if ((rep = g_dbus_connection_send_message_with_reply_sync(conn, msg,
                   G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL)
       goto fail;
 
   if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) {
       g_dbus_message_to_gerror(rep, &err);
       goto fail;
   }
 
   g_variant_get(g_dbus_message_get_body(rep), "(v)", &value);
 
fail:
 
   if (msg != NULL)
       g_object_unref(msg);
   if (rep != NULL)
       g_object_unref(rep);
   if (err != NULL) {
       warn("Couldn't get property: %s", err->message);
       g_error_free(err);
   }
 
   return value;
}
 
/**
 * Set a property of a given D-Bus interface.
 *
 * @param conn D-Bus connection handler.
 * @param name Valid D-Bus name or NULL.
 * @param path Valid D-Bus object path.
 * @param interface Interface with the given property.
 * @param property The property name.
 * @param value Variant containing property value.
 * @return On success this function returns TRUE. Otherwise, FALSE. */
gboolean g_dbus_set_property(GDBusConnection *conn, const char *name,
       const char *path, const char *interface, const char *property,
       const GVariant *value) {
 
   GDBusMessage *msg = NULL, *rep = NULL;
   GError *err = NULL;
 
   msg = g_dbus_message_new_method_call(name, path, "org.freedesktop.DBus.Properties", "Set");
   g_dbus_message_set_body(msg, g_variant_new("(ssv)", interface, property, value));
 
   if ((rep = g_dbus_connection_send_message_with_reply_sync(conn, msg,
                   G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL)
       goto fail;
 
   if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) {
       g_dbus_message_to_gerror(rep, &err);
       goto fail;
   }
 
fail:
 
   if (msg != NULL)
       g_object_unref(msg);
   if (rep != NULL)
       g_object_unref(rep);
   if (err != NULL) {
       warn("Couldn't set property: %s", err->message);
       g_error_free(err);
       return FALSE;
   }
 
   return TRUE;
}
 
/**
 * Convert Bluetooth profile into a human-readable string.
 *
 * @param profile Bluetooth profile.
 * @return Human-readable string. */
const char *bluetooth_profile_to_string(enum bluetooth_profile profile) {
   switch (profile) {
   case BLUETOOTH_PROFILE_NULL:
       return "N/A";
   case BLUETOOTH_PROFILE_A2DP_SOURCE:
       return "A2DP Source";
   case BLUETOOTH_PROFILE_A2DP_SINK:
       return "A2DP Sink";
   case BLUETOOTH_PROFILE_HSP_HS:
       return "HSP Headset";
   case BLUETOOTH_PROFILE_HSP_AG:
       return "HSP Audio Gateway";
   case BLUETOOTH_PROFILE_HFP_HF:
       return "HFP Hands-Free";
   case BLUETOOTH_PROFILE_HFP_AG:
       return "HFP Audio Gateway";
   }
   return "N/A";
}
 
/**
 * Convert Bluetooth A2DP codec into a human-readable string.
 *
 * @param codec Bluetooth A2DP audio codec.
 * @return Human-readable string. */
const char *bluetooth_a2dp_codec_to_string(uint16_t codec) {
   switch (codec) {
   case A2DP_CODEC_SBC:
       return "SBC";
#if ENABLE_MPEG
   case A2DP_CODEC_MPEG12:
       return "MPEG";
#endif
#if ENABLE_AAC
   case A2DP_CODEC_MPEG24:
       return "AAC";
#endif
#if ENABLE_APTX
   case A2DP_CODEC_VENDOR_APTX:
       return "APT-X";
#endif
#if ENABLE_APTX_HD
   case A2DP_CODEC_VENDOR_APTX_HD:
       return "APT-X HD";
#endif
#if ENABLE_LDAC
   case A2DP_CODEC_VENDOR_LDAC:
       return "LDAC";
#endif
   }
   return "N/A";
}
 
/**
 * Convert Bluetooth address into a human-readable string.
 *
 * This function returns statically allocated buffer. It is not by any means
 * thread safe. This function should be used for debugging purposes only.
 *
 * For debugging purposes, one could use the batostr() function provided by
 * the bluez library. However, this function converts the Bluetooth address
 * to the string with a incorrect (reversed) bytes order...
 *
 * @param ba Pointer to the Bluetooth address structure.
 * @return On success this function returns statically allocated buffer with
 *   human-readable Bluetooth address. On error, it returns NULL. */
const char *batostr_(const bdaddr_t *ba) {
   static char addr[18];
   if (ba2str(ba, addr) > 0)
       return addr;
   return NULL;
}
 
/**
 * Scale PCM signal stored in the buffer.
 *
 * Neutral value for scaling factor is 1.0. It is possible to increase
 * signal gain by using scaling factor values greater than 1, however
 * clipping will most certainly occur.
 *
 * @param buffer Address to the buffer where the PCM signal is stored.
 * @param size The number of samples in the buffer.
 * @param channels The number of channels in the buffer.
 * @param ch1_scale The scaling factor for 1st channel.
 * @param ch1_scale The scaling factor for 2nd channel. */
void snd_pcm_scale_s16le(int16_t *buffer, size_t size, int channels,
       double ch1_scale, double ch2_scale) {
   switch (channels) {
   case 1:
       if (ch1_scale != 1.0)
           while (size--)
               buffer[size] = buffer[size] * ch1_scale;
       break;
   case 2:
       if (ch1_scale != 1.0 || ch2_scale != 1.0)
           while (size--) {
               double scale = size % 2 == 0 ? ch1_scale : ch2_scale;
               buffer[size] = buffer[size] * scale;
           }
       break;
   }
}
 
#if ENABLE_AAC
/**
 * Get string representation of the FDK-AAC decoder error code.
 *
 * @param error FDK-AAC decoder error code.
 * @return Human-readable string. */
const char *aacdec_strerror(AAC_DECODER_ERROR err) {
   switch (err) {
   case AAC_DEC_OK:
       return "Success";
   case AAC_DEC_OUT_OF_MEMORY:
       return "Out of memory";
   case AAC_DEC_TRANSPORT_SYNC_ERROR:
       return "Transport sync error";
   case AAC_DEC_NOT_ENOUGH_BITS:
       return "Not enough bits";
   case AAC_DEC_INVALID_HANDLE:
       return "Invalid handle";
   case AAC_DEC_UNSUPPORTED_AOT:
       return "Unsupported AOT";
   case AAC_DEC_UNSUPPORTED_FORMAT:
       return "Unsupported format";
   case AAC_DEC_UNSUPPORTED_ER_FORMAT:
       return "Unsupported ER format";
   case AAC_DEC_UNSUPPORTED_EPCONFIG:
       return "Unsupported EP format";
   case AAC_DEC_UNSUPPORTED_MULTILAYER:
       return "Unsupported multilayer";
   case AAC_DEC_UNSUPPORTED_CHANNELCONFIG:
       return "Unsupported channels";
   case AAC_DEC_UNSUPPORTED_SAMPLINGRATE:
       return "Unsupported sample rate";
   case AAC_DEC_INVALID_SBR_CONFIG:
       return "Unsupported SBR";
   case AAC_DEC_SET_PARAM_FAIL:
       return "Unsupported parameter";
   case AAC_DEC_NEED_TO_RESTART:
       return "Restart required";
   case AAC_DEC_TRANSPORT_ERROR:
       return "Transport error";
   case AAC_DEC_PARSE_ERROR:
       return "Parse error";
   case AAC_DEC_UNSUPPORTED_EXTENSION_PAYLOAD:
       return "Unsupported extension payload";
   case AAC_DEC_DECODE_FRAME_ERROR:
       return "Bitstream corrupted";
   case AAC_DEC_CRC_ERROR:
       return "CRC mismatch";
   case AAC_DEC_INVALID_CODE_BOOK:
       return "Invalid codebook";
   case AAC_DEC_UNSUPPORTED_PREDICTION:
       return "Unsupported prediction";
   case AAC_DEC_UNSUPPORTED_CCE:
       return "Unsupported CCE";
   case AAC_DEC_UNSUPPORTED_LFE:
       return "Unsupported LFE";
   case AAC_DEC_UNSUPPORTED_GAIN_CONTROL_DATA:
       return "Unsupported gain control data";
   case AAC_DEC_UNSUPPORTED_SBA:
       return "Unsupported SBA";
   case AAC_DEC_TNS_READ_ERROR:
       return "TNS read error";
   case AAC_DEC_RVLC_ERROR:
       return "RVLC decode error";
   case AAC_DEC_ANC_DATA_ERROR:
       return "Ancillary data error";
   case AAC_DEC_TOO_SMALL_ANC_BUFFER:
       return "Too small ancillary buffer";
   case AAC_DEC_TOO_MANY_ANC_ELEMENTS:
       return "Too many ancillary elements";
   default:
       debug("Unknown error code: %#x", err);
       return "Unknown error";
   }
}
#endif
 
#if ENABLE_AAC
/**
 * Get string representation of the FDK-AAC encoder error code.
 *
 * @param error FDK-AAC encoder error code.
 * @return Human-readable string. */
const char *aacenc_strerror(AACENC_ERROR err) {
   switch (err) {
   case AACENC_OK:
       return "Success";
   case AACENC_INVALID_HANDLE:
       return "Invalid handle";
   case AACENC_MEMORY_ERROR:
       return "Out of memory";
   case AACENC_UNSUPPORTED_PARAMETER:
       return "Unsupported parameter";
   case AACENC_INVALID_CONFIG:
       return "Invalid config";
   case AACENC_INIT_ERROR:
       return "Initialization error";
   case AACENC_INIT_AAC_ERROR:
       return "AAC library initialization error";
   case AACENC_INIT_SBR_ERROR:
       return "SBR library initialization error";
   case AACENC_INIT_TP_ERROR:
       return "Transport library initialization error";
   case AACENC_INIT_META_ERROR:
       return "Metadata library initialization error";
   case AACENC_ENCODE_ERROR:
       return "Encoding error";
   case AACENC_ENCODE_EOF:
       return "End of file";
   default:
       debug("Unknown error code: %#x", err);
       return "Unknown error";
   }
}
#endif
 
#if ENABLE_LDAC
/**
 * Get string representation of the LDAC error code.
 *
 * @param error LDAC error code.
 * @return Human-readable string. */
const char *ldacBT_strerror(int err) {
   switch (LDACBT_API_ERR(err)) {
   case LDACBT_ERR_NONE:
       return "Success";
   case LDACBT_ERR_ASSERT_SAMPLING_FREQ:
   case LDACBT_ERR_ASSERT_SUP_SAMPLING_FREQ:
   case LDACBT_ERR_CHECK_SAMPLING_FREQ:
       return "Invalid sample rate";
   case LDACBT_ERR_ASSERT_CHANNEL_CONFIG:
   case LDACBT_ERR_CHECK_CHANNEL_CONFIG:
       return "Invalid channel config";
   case LDACBT_ERR_ASSERT_FRAME_LENGTH:
   case LDACBT_ERR_ASSERT_SUP_FRAME_LENGTH:
   case LDACBT_ERR_ASSERT_FRAME_STATUS:
       return "Invalid frame status";
   case LDACBT_ERR_ASSERT_NSHIFT:
       return "Invalid N-shift";
   case LDACBT_ERR_ASSERT_CHANNEL_MODE:
       return "Invalid channel mode";
   case LDACBT_ERR_ALTER_EQMID_LIMITED:
       return "EQMID limited";
   case LDACBT_ERR_HANDLE_NOT_INIT:
       return "Invalid handle";
   case LDACBT_ERR_ILL_EQMID:
       return "Unsupported EQMID";
   case LDACBT_ERR_ILL_SAMPLING_FREQ:
       return "Unsupported sample rate";
   case LDACBT_ERR_ILL_NUM_CHANNEL:
       return "Unsupported channels";
   case LDACBT_ERR_ILL_MTU_SIZE:
       return "Unsupported MTU";
   default:
       debug("Unknown error code: %#x", err);
       return "Unknown error";
   }
}
#endif