From be1851c4465c3c5fa6dc8fd58c1338a0513f9eaa Mon Sep 17 00:00:00 2001
From: Pavlin Radoslavov <pavlin@google.com>
Date: Tue, 1 Nov 2016 05:45:40 -0700
Subject: [PATCH] Integration of the LDAC codec for A2DP source

The codec can be used if the encoding shared library is installed
on the device:
 libldacBT_enc.so

Also, increased the maximum number of SEPs AVDT_NUM_SEPS from 3
to 6 to accomodate Sink devices that support more than 3 codecs.

Test: A2DP streaming to LDAC headsets
Bug: 30958229
Change-Id: I114c3e6be2ebe1ac8aece62a3146637d37cd938e
---
 system/include/bt_target.h                    |    2 +-
 system/stack/Android.mk                       |    2 +
 system/stack/a2dp/a2dp_codec_config.cc        |    5 +
 system/stack/a2dp/a2dp_vendor.cc              |   77 ++
 system/stack/a2dp/a2dp_vendor_ldac.cc         | 1102 +++++++++++++++++
 system/stack/a2dp/a2dp_vendor_ldac_encoder.cc |  807 ++++++++++++
 system/stack/include/a2dp_vendor_ldac.h       |  155 +++
 .../include/a2dp_vendor_ldac_constants.h      |   53 +
 .../stack/include/a2dp_vendor_ldac_encoder.h  |   71 ++
 9 files changed, 2273 insertions(+), 1 deletion(-)
 create mode 100644 system/stack/a2dp/a2dp_vendor_ldac.cc
 create mode 100644 system/stack/a2dp/a2dp_vendor_ldac_encoder.cc
 create mode 100644 system/stack/include/a2dp_vendor_ldac.h
 create mode 100644 system/stack/include/a2dp_vendor_ldac_constants.h
 create mode 100644 system/stack/include/a2dp_vendor_ldac_encoder.h

diff --git a/system/include/bt_target.h b/system/include/bt_target.h
index fa2d6aa5a83..eef12f67314 100644
--- a/system/include/bt_target.h
+++ b/system/include/bt_target.h
@@ -1100,7 +1100,7 @@
 
 /* Number of simultaneous stream endpoints. */
 #ifndef AVDT_NUM_SEPS
-#define AVDT_NUM_SEPS 3
+#define AVDT_NUM_SEPS 6
 #endif
 
 /* Number of transport channels setup by AVDT for all media streams */
diff --git a/system/stack/Android.mk b/system/stack/Android.mk
index 0b1615f5fd9..146c4b460ff 100644
--- a/system/stack/Android.mk
+++ b/system/stack/Android.mk
@@ -43,6 +43,8 @@ LOCAL_SRC_FILES := \
     ./a2dp/a2dp_vendor_aptx_hd.cc \
     ./a2dp/a2dp_vendor_aptx_encoder.cc \
     ./a2dp/a2dp_vendor_aptx_hd_encoder.cc \
+    ./a2dp/a2dp_vendor_ldac.cc \
+    ./a2dp/a2dp_vendor_ldac_encoder.cc \
     ./avct/avct_api.cc \
     ./avct/avct_bcb_act.cc \
     ./avct/avct_ccb.cc \
diff --git a/system/stack/a2dp/a2dp_codec_config.cc b/system/stack/a2dp/a2dp_codec_config.cc
index a0182c37929..69659ced7cf 100644
--- a/system/stack/a2dp/a2dp_codec_config.cc
+++ b/system/stack/a2dp/a2dp_codec_config.cc
@@ -28,6 +28,7 @@
 #include "a2dp_vendor.h"
 #include "a2dp_vendor_aptx.h"
 #include "a2dp_vendor_aptx_hd.h"
+#include "a2dp_vendor_ldac.h"
 #include "osi/include/log.h"
 
 /* The Media Type offset within the codec info byte array */
@@ -90,6 +91,10 @@ A2dpCodecConfig* A2dpCodecConfig::createCodec(
     case BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_HD:
       codec_config = new A2dpCodecConfigAptxHd();
       break;
+    case BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC:
+      codec_config = new A2dpCodecConfigLdac();
+      break;
+    // Add a switch statement for each vendor-specific codec
     case BTAV_A2DP_CODEC_INDEX_MAX:
       break;
   }
diff --git a/system/stack/a2dp/a2dp_vendor.cc b/system/stack/a2dp/a2dp_vendor.cc
index 72241400171..a49d76f30f8 100644
--- a/system/stack/a2dp/a2dp_vendor.cc
+++ b/system/stack/a2dp/a2dp_vendor.cc
@@ -23,6 +23,7 @@
 #include "a2dp_vendor.h"
 #include "a2dp_vendor_aptx.h"
 #include "a2dp_vendor_aptx_hd.h"
+#include "a2dp_vendor_ldac.h"
 #include "bt_target.h"
 #include "osi/include/log.h"
 #include "osi/include/osi.h"
@@ -43,6 +44,11 @@ bool A2DP_IsVendorSourceCodecValid(const uint8_t* p_codec_info) {
     return A2DP_IsVendorSourceCodecValidAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_IsVendorSourceCodecValidLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return false;
@@ -85,6 +91,11 @@ bool A2DP_IsVendorPeerSinkCodecValid(const uint8_t* p_codec_info) {
     return A2DP_IsVendorPeerSinkCodecValidAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_IsVendorPeerSinkCodecValidLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return false;
@@ -159,6 +170,12 @@ bool A2DP_VendorUsesRtpHeader(bool content_protection_enabled,
                                           p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorUsesRtpHeaderLdac(content_protection_enabled,
+                                        p_codec_info);
+  }
+
   // Add checks based on <content_protection_enabled, vendor_id, codec_id>
 
   return true;
@@ -180,6 +197,11 @@ const char* A2DP_VendorCodecName(UNUSED_ATTR const uint8_t* p_codec_info) {
     return A2DP_VendorCodecNameAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorCodecNameLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return "UNKNOWN VENDOR CODEC";
@@ -214,6 +236,11 @@ bool A2DP_VendorCodecTypeEquals(const uint8_t* p_codec_info_a,
     return A2DP_VendorCodecTypeEqualsAptxHd(p_codec_info_a, p_codec_info_b);
   }
 
+  // Check for LDAC
+  if (vendor_id_a == A2DP_LDAC_VENDOR_ID && codec_id_a == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorCodecTypeEqualsLdac(p_codec_info_a, p_codec_info_b);
+  }
+
   // OPTIONAL: Add extra vendor-specific checks based on the
   // vendor-specific data stored in "p_codec_info_a" and "p_codec_info_b".
 
@@ -249,6 +276,11 @@ bool A2DP_VendorCodecEquals(const uint8_t* p_codec_info_a,
     return A2DP_VendorCodecEqualsAptxHd(p_codec_info_a, p_codec_info_b);
   }
 
+  // Check for LDAC
+  if (vendor_id_a == A2DP_LDAC_VENDOR_ID && codec_id_a == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorCodecEqualsLdac(p_codec_info_a, p_codec_info_b);
+  }
+
   // Add extra vendor-specific checks based on the
   // vendor-specific data stored in "p_codec_info_a" and "p_codec_info_b".
 
@@ -271,6 +303,11 @@ int A2DP_VendorGetTrackSampleRate(const uint8_t* p_codec_info) {
     return A2DP_VendorGetTrackSampleRateAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorGetTrackSampleRateLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return -1;
@@ -292,6 +329,11 @@ int A2DP_VendorGetTrackBitsPerSample(const uint8_t* p_codec_info) {
     return A2DP_VendorGetTrackBitsPerSampleAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorGetTrackBitsPerSampleLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return -1;
@@ -313,6 +355,11 @@ int A2DP_VendorGetTrackChannelCount(const uint8_t* p_codec_info) {
     return A2DP_VendorGetTrackChannelCountAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorGetTrackChannelCountLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return -1;
@@ -360,6 +407,11 @@ bool A2DP_VendorGetPacketTimestamp(const uint8_t* p_codec_info,
                                                p_timestamp);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorGetPacketTimestampLdac(p_codec_info, p_data, p_timestamp);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return false;
@@ -384,6 +436,12 @@ bool A2DP_VendorBuildCodecHeader(const uint8_t* p_codec_info, BT_HDR* p_buf,
                                              frames_per_packet);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorBuildCodecHeaderLdac(p_codec_info, p_buf,
+                                           frames_per_packet);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return false;
@@ -406,6 +464,11 @@ const tA2DP_ENCODER_INTERFACE* A2DP_VendorGetEncoderInterface(
     return A2DP_VendorGetEncoderInterfaceAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorGetEncoderInterfaceLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return NULL;
@@ -427,6 +490,11 @@ bool A2DP_VendorAdjustCodec(uint8_t* p_codec_info) {
     return A2DP_VendorAdjustCodecAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorAdjustCodecLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return false;
@@ -449,6 +517,11 @@ btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndex(
     return A2DP_VendorSourceCodecIndexAptxHd(p_codec_info);
   }
 
+  // Check for LDAC
+  if (vendor_id == A2DP_LDAC_VENDOR_ID && codec_id == A2DP_LDAC_CODEC_ID) {
+    return A2DP_VendorSourceCodecIndexLdac(p_codec_info);
+  }
+
   // Add checks based on <vendor_id, codec_id>
 
   return BTAV_A2DP_CODEC_INDEX_MAX;
@@ -464,6 +537,8 @@ const char* A2DP_VendorCodecIndexStr(btav_a2dp_codec_index_t codec_index) {
       return A2DP_VendorCodecIndexStrAptx();
     case BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_HD:
       return A2DP_VendorCodecIndexStrAptxHd();
+    case BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC:
+      return A2DP_VendorCodecIndexStrLdac();
     // Add a switch statement for each vendor-specific codec
     case BTAV_A2DP_CODEC_INDEX_MAX:
       break;
@@ -483,6 +558,8 @@ bool A2DP_VendorInitCodecConfig(btav_a2dp_codec_index_t codec_index,
       return A2DP_VendorInitCodecConfigAptx(p_cfg);
     case BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_HD:
       return A2DP_VendorInitCodecConfigAptxHd(p_cfg);
+    case BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC:
+      return A2DP_VendorInitCodecConfigLdac(p_cfg);
     // Add a switch statement for each vendor-specific codec
     case BTAV_A2DP_CODEC_INDEX_MAX:
       break;
diff --git a/system/stack/a2dp/a2dp_vendor_ldac.cc b/system/stack/a2dp/a2dp_vendor_ldac.cc
new file mode 100644
index 00000000000..2f496ba3564
--- /dev/null
+++ b/system/stack/a2dp/a2dp_vendor_ldac.cc
@@ -0,0 +1,1102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/******************************************************************************
+ *
+ *  Utility functions to help build and parse the LDAC Codec Information
+ *  Element and Media Payload.
+ *
+ ******************************************************************************/
+
+#define LOG_TAG "a2dp_vendor_ldac"
+
+#include "bt_target.h"
+
+#include "a2dp_vendor_ldac.h"
+
+#include <string.h>
+
+#include <base/logging.h>
+#include "a2dp_vendor.h"
+#include "a2dp_vendor_ldac_encoder.h"
+#include "bt_utils.h"
+#include "osi/include/log.h"
+#include "osi/include/osi.h"
+
+// data type for the LDAC Codec Information Element */
+// NOTE: bits_per_sample is only needs for LDAC encoder initialization.
+typedef struct {
+  uint32_t vendorId;
+  uint16_t codecId;    /* Codec ID for LDAC */
+  uint8_t sampleRate;  /* Sampling Frequency */
+  uint8_t channelMode; /* STEREO/DUAL/MONO */
+  btav_a2dp_codec_bits_per_sample_t bits_per_sample;
+} tA2DP_LDAC_CIE;
+
+/* LDAC Source codec capabilities */
+static const tA2DP_LDAC_CIE a2dp_ldac_caps = {
+    A2DP_LDAC_VENDOR_ID,  // vendorId
+    A2DP_LDAC_CODEC_ID,   // codecId
+    // sampleRate
+    (A2DP_LDAC_SAMPLING_FREQ_44100 | A2DP_LDAC_SAMPLING_FREQ_48000 |
+     A2DP_LDAC_SAMPLING_FREQ_88200 | A2DP_LDAC_SAMPLING_FREQ_96000),
+    // channelMode
+    (A2DP_LDAC_CHANNEL_MODE_MONO | A2DP_LDAC_CHANNEL_MODE_DUAL |
+     A2DP_LDAC_CHANNEL_MODE_STEREO),
+    // bits_per_sample
+    (BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16 | BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24 |
+     BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32)};
+
+/* Default LDAC codec configuration */
+static const tA2DP_LDAC_CIE a2dp_ldac_default_config = {
+    A2DP_LDAC_VENDOR_ID,                // vendorId
+    A2DP_LDAC_CODEC_ID,                 // codecId
+    A2DP_LDAC_SAMPLING_FREQ_96000,      // sampleRate
+    A2DP_LDAC_CHANNEL_MODE_STEREO,      // channelMode
+    BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32  // bits_per_sample
+};
+
+static const tA2DP_ENCODER_INTERFACE a2dp_encoder_interface_ldac = {
+    a2dp_vendor_ldac_encoder_init,  a2dp_vendor_ldac_encoder_cleanup,
+    a2dp_vendor_ldac_feeding_init,  a2dp_vendor_ldac_feeding_reset,
+    a2dp_vendor_ldac_feeding_flush, a2dp_vendor_ldac_get_encoder_interval_ms,
+    a2dp_vendor_ldac_send_frames,   a2dp_vendor_ldac_debug_codec_dump};
+
+UNUSED_ATTR static tA2DP_STATUS A2DP_CodecInfoMatchesCapabilityLdac(
+    const tA2DP_LDAC_CIE* p_cap, const uint8_t* p_codec_info,
+    bool is_peer_codec_info);
+
+// Builds the LDAC Media Codec Capabilities byte sequence beginning from the
+// LOSC octet. |media_type| is the media type |AVDT_MEDIA_TYPE_*|.
+// |p_ie| is a pointer to the LDAC Codec Information Element information.
+// The result is stored in |p_result|. Returns A2DP_SUCCESS on success,
+// otherwise the corresponding A2DP error status code.
+static tA2DP_STATUS A2DP_BuildInfoLdac(uint8_t media_type,
+                                       const tA2DP_LDAC_CIE* p_ie,
+                                       uint8_t* p_result) {
+  tA2DP_STATUS status;
+
+  if (p_ie == NULL || p_result == NULL) {
+    status = A2DP_INVALID_PARAMS;
+  } else {
+    status = A2DP_SUCCESS;
+    *p_result++ = A2DP_LDAC_CODEC_LEN;
+    *p_result++ = media_type;
+    *p_result++ = A2DP_MEDIA_CT_NON_A2DP;
+
+    // Vendor ID and Codec ID
+    *p_result++ = (uint8_t)(p_ie->vendorId & 0x000000FF);
+    *p_result++ = (uint8_t)((p_ie->vendorId & 0x0000FF00) >> 8);
+    *p_result++ = (uint8_t)((p_ie->vendorId & 0x00FF0000) >> 16);
+    *p_result++ = (uint8_t)((p_ie->vendorId & 0xFF000000) >> 24);
+    *p_result++ = (uint8_t)(p_ie->codecId & 0x00FF);
+    *p_result++ = (uint8_t)((p_ie->codecId & 0xFF00) >> 8);
+
+    // Sampling Frequency
+    *p_result = (uint8_t)(p_ie->sampleRate & A2DP_LDAC_SAMPLING_FREQ_MASK);
+    if (*p_result == 0) return A2DP_INVALID_PARAMS;
+    p_result++;
+
+    // Channel Mode
+    *p_result = (uint8_t)(p_ie->channelMode & A2DP_LDAC_CHANNEL_MODE_MASK);
+    if (*p_result == 0) return A2DP_INVALID_PARAMS;
+  }
+  return status;
+}
+
+// Parses the LDAC Media Codec Capabilities byte sequence beginning from the
+// LOSC octet. The result is stored in |p_ie|. The byte sequence to parse is
+// |p_codec_info|. If |is_capability| is true, the byte sequence is
+// codec capabilities, otherwise is codec configuration.
+// Returns A2DP_SUCCESS on success, otherwise the corresponding A2DP error
+// status code.
+static tA2DP_STATUS A2DP_ParseInfoLdac(tA2DP_LDAC_CIE* p_ie,
+                                       const uint8_t* p_codec_info,
+                                       bool is_capability) {
+  tA2DP_STATUS status = A2DP_SUCCESS;
+  uint8_t losc;
+  uint8_t media_type;
+  tA2DP_CODEC_TYPE codec_type;
+
+  if (p_ie == NULL || p_codec_info == NULL) return A2DP_INVALID_PARAMS;
+
+  // Check the codec capability length
+  losc = *p_codec_info++;
+  if (losc != A2DP_LDAC_CODEC_LEN) return A2DP_WRONG_CODEC;
+
+  media_type = (*p_codec_info++) >> 4;
+  codec_type = *p_codec_info++;
+  /* Check the Media Type and Media Codec Type */
+  if (media_type != AVDT_MEDIA_TYPE_AUDIO ||
+      codec_type != A2DP_MEDIA_CT_NON_A2DP) {
+    return A2DP_WRONG_CODEC;
+  }
+
+  // Check the Vendor ID and Codec ID */
+  p_ie->vendorId = (*p_codec_info & 0x000000FF) |
+                   (*(p_codec_info + 1) << 8 & 0x0000FF00) |
+                   (*(p_codec_info + 2) << 16 & 0x00FF0000) |
+                   (*(p_codec_info + 3) << 24 & 0xFF000000);
+  p_codec_info = p_codec_info + 4;
+  p_ie->codecId =
+      (*p_codec_info & 0x00FF) | (*(p_codec_info + 1) << 8 & 0xFF00);
+  p_codec_info = p_codec_info + 2;
+  if (p_ie->vendorId != A2DP_LDAC_VENDOR_ID ||
+      p_ie->codecId != A2DP_LDAC_CODEC_ID) {
+    return A2DP_WRONG_CODEC;
+  }
+
+  p_ie->sampleRate = *p_codec_info++ & A2DP_LDAC_SAMPLING_FREQ_MASK;
+  p_ie->channelMode = *p_codec_info++ & A2DP_LDAC_CHANNEL_MODE_MASK;
+
+  status = A2DP_SUCCESS;
+
+  if (is_capability) return status;
+
+  if (A2DP_BitsSet(p_ie->sampleRate) != A2DP_SET_ONE_BIT)
+    status = A2DP_BAD_SAMP_FREQ;
+  if (A2DP_BitsSet(p_ie->channelMode) != A2DP_SET_ONE_BIT)
+    status = A2DP_BAD_CH_MODE;
+
+  return status;
+}
+
+// Build the LDAC Media Payload Header.
+// |p_dst| points to the location where the header should be written to.
+// If |frag| is true, the media payload frame is fragmented.
+// |start| is true for the first packet of a fragmented frame.
+// |last| is true for the last packet of a fragmented frame.
+// If |frag| is false, |num| is the number of number of frames in the packet,
+// otherwise is the number of remaining fragments (including this one).
+static void A2DP_BuildMediaPayloadHeaderLdac(uint8_t* p_dst, bool frag,
+                                             bool start, bool last,
+                                             uint8_t num) {
+  if (p_dst == NULL) return;
+
+  *p_dst = 0;
+  if (frag) *p_dst |= A2DP_LDAC_HDR_F_MSK;
+  if (start) *p_dst |= A2DP_LDAC_HDR_S_MSK;
+  if (last) *p_dst |= A2DP_LDAC_HDR_L_MSK;
+  *p_dst |= (A2DP_LDAC_HDR_NUM_MSK & num);
+}
+
+bool A2DP_IsVendorSourceCodecValidLdac(const uint8_t* p_codec_info) {
+  tA2DP_LDAC_CIE cfg_cie;
+
+  /* Use a liberal check when parsing the codec info */
+  return (A2DP_ParseInfoLdac(&cfg_cie, p_codec_info, false) == A2DP_SUCCESS) ||
+         (A2DP_ParseInfoLdac(&cfg_cie, p_codec_info, true) == A2DP_SUCCESS);
+}
+
+bool A2DP_IsVendorPeerSinkCodecValidLdac(const uint8_t* p_codec_info) {
+  tA2DP_LDAC_CIE cfg_cie;
+
+  /* Use a liberal check when parsing the codec info */
+  return (A2DP_ParseInfoLdac(&cfg_cie, p_codec_info, false) == A2DP_SUCCESS) ||
+         (A2DP_ParseInfoLdac(&cfg_cie, p_codec_info, true) == A2DP_SUCCESS);
+}
+
+// Checks whether A2DP LDAC codec configuration matches with a device's codec
+// capabilities. |p_cap| is the LDAC codec configuration. |p_codec_info| is
+// the device's codec capabilities.
+// If |is_capability| is true, the byte sequence is codec capabilities,
+// otherwise is codec configuration.
+// |p_codec_info| contains the codec capabilities for a peer device that
+// is acting as an A2DP source.
+// Returns A2DP_SUCCESS if the codec configuration matches with capabilities,
+// otherwise the corresponding A2DP error status code.
+static tA2DP_STATUS A2DP_CodecInfoMatchesCapabilityLdac(
+    const tA2DP_LDAC_CIE* p_cap, const uint8_t* p_codec_info,
+    bool is_capability) {
+  tA2DP_STATUS status;
+  tA2DP_LDAC_CIE cfg_cie;
+
+  /* parse configuration */
+  status = A2DP_ParseInfoLdac(&cfg_cie, p_codec_info, is_capability);
+  if (status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: parsing failed %d", __func__, status);
+    return status;
+  }
+
+  /* verify that each parameter is in range */
+
+  LOG_DEBUG(LOG_TAG, "FREQ peer: 0x%x, capability 0x%x", cfg_cie.sampleRate,
+            p_cap->sampleRate);
+  LOG_DEBUG(LOG_TAG, "CH_MODE peer: 0x%x, capability 0x%x", cfg_cie.channelMode,
+            p_cap->channelMode);
+
+  /* sampling frequency */
+  if ((cfg_cie.sampleRate & p_cap->sampleRate) == 0) return A2DP_NS_SAMP_FREQ;
+
+  /* channel mode */
+  if ((cfg_cie.channelMode & p_cap->channelMode) == 0) return A2DP_NS_CH_MODE;
+
+  return A2DP_SUCCESS;
+}
+
+bool A2DP_VendorUsesRtpHeaderLdac(UNUSED_ATTR bool content_protection_enabled,
+                                  UNUSED_ATTR const uint8_t* p_codec_info) {
+  // TODO: Is this correct? The RTP header is always included?
+  return true;
+}
+
+const char* A2DP_VendorCodecNameLdac(UNUSED_ATTR const uint8_t* p_codec_info) {
+  return "LDAC";
+}
+
+bool A2DP_VendorCodecTypeEqualsLdac(const uint8_t* p_codec_info_a,
+                                    const uint8_t* p_codec_info_b) {
+  tA2DP_LDAC_CIE ldac_cie_a;
+  tA2DP_LDAC_CIE ldac_cie_b;
+
+  // Check whether the codec info contains valid data
+  tA2DP_STATUS a2dp_status =
+      A2DP_ParseInfoLdac(&ldac_cie_a, p_codec_info_a, true);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: cannot decode codec information: %d", __func__,
+              a2dp_status);
+    return false;
+  }
+  a2dp_status = A2DP_ParseInfoLdac(&ldac_cie_b, p_codec_info_b, true);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: cannot decode codec information: %d", __func__,
+              a2dp_status);
+    return false;
+  }
+
+  return true;
+}
+
+bool A2DP_VendorCodecEqualsLdac(const uint8_t* p_codec_info_a,
+                                const uint8_t* p_codec_info_b) {
+  tA2DP_LDAC_CIE ldac_cie_a;
+  tA2DP_LDAC_CIE ldac_cie_b;
+
+  // Check whether the codec info contains valid data
+  tA2DP_STATUS a2dp_status =
+      A2DP_ParseInfoLdac(&ldac_cie_a, p_codec_info_a, true);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: cannot decode codec information: %d", __func__,
+              a2dp_status);
+    return false;
+  }
+  a2dp_status = A2DP_ParseInfoLdac(&ldac_cie_b, p_codec_info_b, true);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: cannot decode codec information: %d", __func__,
+              a2dp_status);
+    return false;
+  }
+
+  return (ldac_cie_a.sampleRate == ldac_cie_b.sampleRate) &&
+         (ldac_cie_a.channelMode == ldac_cie_b.channelMode);
+}
+
+int A2DP_VendorGetTrackSampleRateLdac(const uint8_t* p_codec_info) {
+  tA2DP_LDAC_CIE ldac_cie;
+
+  // Check whether the codec info contains valid data
+  tA2DP_STATUS a2dp_status = A2DP_ParseInfoLdac(&ldac_cie, p_codec_info, false);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: cannot decode codec information: %d", __func__,
+              a2dp_status);
+    return -1;
+  }
+
+  switch (ldac_cie.sampleRate) {
+    case A2DP_LDAC_SAMPLING_FREQ_44100:
+      return 44100;
+    case A2DP_LDAC_SAMPLING_FREQ_48000:
+      return 48000;
+    case A2DP_LDAC_SAMPLING_FREQ_88200:
+      return 88200;
+    case A2DP_LDAC_SAMPLING_FREQ_96000:
+      return 96000;
+    case A2DP_LDAC_SAMPLING_FREQ_176400:
+      return 176400;
+    case A2DP_LDAC_SAMPLING_FREQ_192000:
+      return 192000;
+  }
+
+  return -1;
+}
+
+int A2DP_VendorGetTrackBitsPerSampleLdac(const uint8_t* p_codec_info) {
+  tA2DP_LDAC_CIE ldac_cie;
+
+  // Check whether the codec info contains valid data
+  tA2DP_STATUS a2dp_status = A2DP_ParseInfoLdac(&ldac_cie, p_codec_info, false);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: cannot decode codec information: %d", __func__,
+              a2dp_status);
+    return -1;
+  }
+
+  switch (a2dp_ldac_caps.bits_per_sample) {
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+      return 16;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+      return 24;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+      return 32;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+      break;
+  }
+  return -1;
+}
+
+int A2DP_VendorGetTrackChannelCountLdac(const uint8_t* p_codec_info) {
+  tA2DP_LDAC_CIE ldac_cie;
+
+  // Check whether the codec info contains valid data
+  tA2DP_STATUS a2dp_status = A2DP_ParseInfoLdac(&ldac_cie, p_codec_info, false);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: cannot decode codec information: %d", __func__,
+              a2dp_status);
+    return -1;
+  }
+
+  switch (ldac_cie.channelMode) {
+    case A2DP_LDAC_CHANNEL_MODE_MONO:
+      return 1;
+    case A2DP_LDAC_CHANNEL_MODE_DUAL:
+      return 2;
+    case A2DP_LDAC_CHANNEL_MODE_STEREO:
+      return 2;
+  }
+
+  return -1;
+}
+
+int A2DP_VendorGetChannelModeCodeLdac(const uint8_t* p_codec_info) {
+  tA2DP_LDAC_CIE ldac_cie;
+
+  // Check whether the codec info contains valid data
+  tA2DP_STATUS a2dp_status = A2DP_ParseInfoLdac(&ldac_cie, p_codec_info, false);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: cannot decode codec information: %d", __func__,
+              a2dp_status);
+    return -1;
+  }
+
+  switch (ldac_cie.channelMode) {
+    case A2DP_LDAC_CHANNEL_MODE_MONO:
+    case A2DP_LDAC_CHANNEL_MODE_DUAL:
+    case A2DP_LDAC_CHANNEL_MODE_STEREO:
+      return ldac_cie.channelMode;
+    default:
+      break;
+  }
+
+  return -1;
+}
+
+bool A2DP_VendorGetPacketTimestampLdac(const uint8_t* p_codec_info,
+                                       const uint8_t* p_data,
+                                       uint32_t* p_timestamp) {
+  // TODO: Is this function really codec-specific?
+  *p_timestamp = *(const uint32_t*)p_data;
+  return true;
+}
+
+bool A2DP_VendorBuildCodecHeaderLdac(UNUSED_ATTR const uint8_t* p_codec_info,
+                                     BT_HDR* p_buf,
+                                     uint16_t frames_per_packet) {
+  uint8_t* p;
+
+  p_buf->offset -= A2DP_LDAC_MPL_HDR_LEN;
+  p = (uint8_t*)(p_buf + 1) + p_buf->offset;
+  p_buf->len += A2DP_LDAC_MPL_HDR_LEN;
+  A2DP_BuildMediaPayloadHeaderLdac(p, false, false, false,
+                                   (uint8_t)frames_per_packet);
+
+  return true;
+}
+
+void A2DP_VendorDumpCodecInfoLdac(const uint8_t* p_codec_info) {
+  tA2DP_STATUS a2dp_status;
+  tA2DP_LDAC_CIE ldac_cie;
+
+  LOG_DEBUG(LOG_TAG, "%s", __func__);
+
+  a2dp_status = A2DP_ParseInfoLdac(&ldac_cie, p_codec_info, true);
+  if (a2dp_status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: A2DP_ParseInfoLdac fail:%d", __func__, a2dp_status);
+    return;
+  }
+
+  LOG_DEBUG(LOG_TAG, "\tsamp_freq: 0x%x", ldac_cie.sampleRate);
+  if (ldac_cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_44100) {
+    LOG_DEBUG(LOG_TAG, "\tsamp_freq: (44100)");
+  }
+  if (ldac_cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_48000) {
+    LOG_DEBUG(LOG_TAG, "\tsamp_freq: (48000)");
+  }
+  if (ldac_cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_88200) {
+    LOG_DEBUG(LOG_TAG, "\tsamp_freq: (88200)");
+  }
+  if (ldac_cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_96000) {
+    LOG_DEBUG(LOG_TAG, "\tsamp_freq: (96000)");
+  }
+  if (ldac_cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_176400) {
+    LOG_DEBUG(LOG_TAG, "\tsamp_freq: (176400)");
+  }
+  if (ldac_cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_192000) {
+    LOG_DEBUG(LOG_TAG, "\tsamp_freq: (192000)");
+  }
+
+  LOG_DEBUG(LOG_TAG, "\tch_mode: 0x%x", ldac_cie.channelMode);
+  if (ldac_cie.channelMode == A2DP_LDAC_CHANNEL_MODE_MONO) {
+    LOG_DEBUG(LOG_TAG, "\tch_mode: (Mono)");
+  }
+  if (ldac_cie.channelMode == A2DP_LDAC_CHANNEL_MODE_DUAL) {
+    LOG_DEBUG(LOG_TAG, "\tch_mode: (Dual)");
+  }
+  if (ldac_cie.channelMode == A2DP_LDAC_CHANNEL_MODE_STEREO) {
+    LOG_DEBUG(LOG_TAG, "\tch_mode: (Stereo)");
+  }
+}
+
+const tA2DP_ENCODER_INTERFACE* A2DP_VendorGetEncoderInterfaceLdac(
+    const uint8_t* p_codec_info) {
+  if (!A2DP_IsVendorSourceCodecValidLdac(p_codec_info)) return NULL;
+
+  return &a2dp_encoder_interface_ldac;
+}
+
+bool A2DP_VendorAdjustCodecLdac(uint8_t* p_codec_info) {
+  tA2DP_LDAC_CIE cfg_cie;
+
+  // Nothing to do: just verify the codec info is valid
+  if (A2DP_ParseInfoLdac(&cfg_cie, p_codec_info, true) != A2DP_SUCCESS)
+    return false;
+
+  return true;
+}
+
+btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndexLdac(
+    UNUSED_ATTR const uint8_t* p_codec_info) {
+  return BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC;
+}
+
+const char* A2DP_VendorCodecIndexStrLdac(void) { return "LDAC"; }
+
+bool A2DP_VendorInitCodecConfigLdac(tAVDT_CFG* p_cfg) {
+  if (A2DP_BuildInfoLdac(AVDT_MEDIA_TYPE_AUDIO, &a2dp_ldac_caps,
+                         p_cfg->codec_info) != A2DP_SUCCESS) {
+    return false;
+  }
+
+#if (BTA_AV_CO_CP_SCMS_T == TRUE)
+  /* Content protection info - support SCMS-T */
+  uint8_t* p = p_cfg->protect_info;
+  *p++ = AVDT_CP_LOSC;
+  UINT16_TO_STREAM(p, AVDT_CP_SCMS_T_ID);
+  p_cfg->num_protect = 1;
+#endif
+
+  return true;
+}
+
+UNUSED_ATTR static void build_codec_config(const tA2DP_LDAC_CIE& config_cie,
+                                           btav_a2dp_codec_config_t* result) {
+  if (config_cie.sampleRate & A2DP_LDAC_SAMPLING_FREQ_44100)
+    result->sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_44100;
+  if (config_cie.sampleRate & A2DP_LDAC_SAMPLING_FREQ_48000)
+    result->sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_48000;
+  if (config_cie.sampleRate & A2DP_LDAC_SAMPLING_FREQ_88200)
+    result->sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_88200;
+  if (config_cie.sampleRate & A2DP_LDAC_SAMPLING_FREQ_96000)
+    result->sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_96000;
+
+  result->bits_per_sample = config_cie.bits_per_sample;
+
+  if (config_cie.channelMode & A2DP_LDAC_CHANNEL_MODE_MONO)
+    result->channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_MONO;
+  if (config_cie.channelMode &
+      (A2DP_LDAC_CHANNEL_MODE_DUAL | A2DP_LDAC_CHANNEL_MODE_STEREO)) {
+    result->channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+  }
+}
+
+A2dpCodecConfigLdac::A2dpCodecConfigLdac()
+    : A2dpCodecConfig(BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC, "LDAC") {}
+
+A2dpCodecConfigLdac::~A2dpCodecConfigLdac() {}
+
+bool A2dpCodecConfigLdac::init() {
+  if (!isValid()) return false;
+
+  // Load the encoder
+  if (!A2DP_VendorLoadEncoderLdac()) {
+    LOG_ERROR(LOG_TAG, "%s: cannot load the encoder", __func__);
+    return false;
+  }
+
+  return true;
+}
+
+//
+// Selects the best sample rate from |sampleRate|.
+// The result is stored in |p_result| and |p_codec_config|.
+// Returns true if a selection was made, otherwise false.
+//
+static bool select_best_sample_rate(uint8_t sampleRate,
+                                    tA2DP_LDAC_CIE* p_result,
+                                    btav_a2dp_codec_config_t* p_codec_config) {
+  if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_192000) {
+    p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_192000;
+    p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_192000;
+    return true;
+  }
+  if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_176400) {
+    p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_176400;
+    p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_176400;
+    return true;
+  }
+  if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_96000) {
+    p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_96000;
+    p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_96000;
+    return true;
+  }
+  if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_88200) {
+    p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_88200;
+    p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_88200;
+    return true;
+  }
+  if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_48000) {
+    p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_48000;
+    p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_48000;
+    return true;
+  }
+  if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_44100) {
+    p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_44100;
+    p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_44100;
+    return true;
+  }
+  return false;
+}
+
+//
+// Selects the audio sample rate from |p_codec_audio_config|.
+// |sampleRate| contains the capability.
+// The result is stored in |p_result| and |p_codec_config|.
+// Returns true if a selection was made, otherwise false.
+//
+static bool select_audio_sample_rate(
+    const btav_a2dp_codec_config_t* p_codec_audio_config, uint8_t sampleRate,
+    tA2DP_LDAC_CIE* p_result, btav_a2dp_codec_config_t* p_codec_config) {
+  switch (p_codec_audio_config->sample_rate) {
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_44100) {
+        p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_44100;
+        p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_44100;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_48000) {
+        p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_48000;
+        p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_48000;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_88200) {
+        p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_88200;
+        p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_88200;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_96000) {
+        p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_96000;
+        p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_96000;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_176400) {
+        p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_176400;
+        p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_176400;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_192000) {
+        p_result->sampleRate = A2DP_LDAC_SAMPLING_FREQ_192000;
+        p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_192000;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
+      break;
+  }
+  return false;
+}
+
+//
+// Selects the best bits per sample from |bits_per_sample|.
+// |bits_per_sample| contains the capability.
+// The result is stored in |p_result| and |p_codec_config|.
+// Returns true if a selection was made, otherwise false.
+//
+static bool select_best_bits_per_sample(
+    btav_a2dp_codec_bits_per_sample_t bits_per_sample, tA2DP_LDAC_CIE* p_result,
+    btav_a2dp_codec_config_t* p_codec_config) {
+  if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32) {
+    p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32;
+    p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32;
+    return true;
+  }
+  if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24) {
+    p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24;
+    p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24;
+    return true;
+  }
+  if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16) {
+    p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+    p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+    return true;
+  }
+  return false;
+}
+
+//
+// Selects the audio bits per sample from |p_codec_audio_config|.
+// |bits_per_sample| contains the capability.
+// The result is stored in |p_result| and |p_codec_config|.
+// Returns true if a selection was made, otherwise false.
+//
+static bool select_audio_bits_per_sample(
+    const btav_a2dp_codec_config_t* p_codec_audio_config,
+    btav_a2dp_codec_bits_per_sample_t bits_per_sample, tA2DP_LDAC_CIE* p_result,
+    btav_a2dp_codec_config_t* p_codec_config) {
+  switch (p_codec_audio_config->bits_per_sample) {
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+      if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16) {
+        p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+        p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+      if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24) {
+        p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24;
+        p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+      if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32) {
+        p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32;
+        p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+      break;
+  }
+  return false;
+}
+
+//
+// Selects the best channel mode from |channelMode|.
+// The result is stored in |p_result| and |p_codec_config|.
+// Returns true if a selection was made, otherwise false.
+//
+static bool select_best_channel_mode(uint8_t channelMode,
+                                     tA2DP_LDAC_CIE* p_result,
+                                     btav_a2dp_codec_config_t* p_codec_config) {
+  if (channelMode & A2DP_LDAC_CHANNEL_MODE_STEREO) {
+    p_result->channelMode = A2DP_LDAC_CHANNEL_MODE_STEREO;
+    p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+    return true;
+  }
+  if (channelMode & A2DP_LDAC_CHANNEL_MODE_DUAL) {
+    p_result->channelMode = A2DP_LDAC_CHANNEL_MODE_DUAL;
+    p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+    return true;
+  }
+  if (channelMode & A2DP_LDAC_CHANNEL_MODE_MONO) {
+    p_result->channelMode = A2DP_LDAC_CHANNEL_MODE_MONO;
+    p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_MONO;
+    return true;
+  }
+  return false;
+}
+
+//
+// Selects the audio channel mode from |p_codec_audio_config|.
+// |channelMode| contains the capability.
+// The result is stored in |p_result| and |p_codec_config|.
+// Returns true if a selection was made, otherwise false.
+//
+static bool select_audio_channel_mode(
+    const btav_a2dp_codec_config_t* p_codec_audio_config, uint8_t channelMode,
+    tA2DP_LDAC_CIE* p_result, btav_a2dp_codec_config_t* p_codec_config) {
+  switch (p_codec_audio_config->channel_mode) {
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
+      if (channelMode & A2DP_LDAC_CHANNEL_MODE_MONO) {
+        p_result->channelMode = A2DP_LDAC_CHANNEL_MODE_MONO;
+        p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_MONO;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
+      if (channelMode & A2DP_LDAC_CHANNEL_MODE_STEREO) {
+        p_result->channelMode = A2DP_LDAC_CHANNEL_MODE_STEREO;
+        p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+        return true;
+      }
+      if (channelMode & A2DP_LDAC_CHANNEL_MODE_DUAL) {
+        p_result->channelMode = A2DP_LDAC_CHANNEL_MODE_DUAL;
+        p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+        return true;
+      }
+      break;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
+      break;
+  }
+
+  return false;
+}
+
+bool A2dpCodecConfigLdac::setCodecConfig(const uint8_t* p_peer_codec_info,
+                                         bool is_capability,
+                                         uint8_t* p_result_codec_config) {
+  std::lock_guard<std::recursive_mutex> lock(codec_mutex_);
+  tA2DP_LDAC_CIE sink_info_cie;
+  tA2DP_LDAC_CIE result_config_cie;
+  uint8_t channelMode;
+  uint8_t sampleRate;
+  btav_a2dp_codec_bits_per_sample_t bits_per_sample;
+
+  // Save the internal state
+  btav_a2dp_codec_config_t saved_codec_config = codec_config_;
+  btav_a2dp_codec_config_t saved_codec_capability = codec_capability_;
+  btav_a2dp_codec_config_t saved_codec_user_config = codec_user_config_;
+  btav_a2dp_codec_config_t saved_codec_audio_config = codec_audio_config_;
+  uint8_t saved_ota_codec_config[AVDT_CODEC_SIZE];
+  uint8_t saved_ota_codec_peer_capability[AVDT_CODEC_SIZE];
+  uint8_t saved_ota_codec_peer_config[AVDT_CODEC_SIZE];
+  memcpy(saved_ota_codec_config, ota_codec_config_, sizeof(ota_codec_config_));
+  memcpy(saved_ota_codec_peer_capability, ota_codec_peer_capability_,
+         sizeof(ota_codec_peer_capability_));
+  memcpy(saved_ota_codec_peer_config, ota_codec_peer_config_,
+         sizeof(ota_codec_peer_config_));
+
+  tA2DP_STATUS status =
+      A2DP_ParseInfoLdac(&sink_info_cie, p_peer_codec_info, is_capability);
+  if (status != A2DP_SUCCESS) {
+    LOG_ERROR(LOG_TAG, "%s: can't parse peer's Sink capabilities: error = %d",
+              __func__, status);
+    goto fail;
+  }
+
+  //
+  // Build the preferred configuration
+  //
+  memset(&result_config_cie, 0, sizeof(result_config_cie));
+  result_config_cie.vendorId = a2dp_ldac_caps.vendorId;
+  result_config_cie.codecId = a2dp_ldac_caps.codecId;
+
+  //
+  // Select the sample frequency
+  //
+  sampleRate = a2dp_ldac_caps.sampleRate & sink_info_cie.sampleRate;
+  codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
+  switch (codec_user_config_.sample_rate) {
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_44100) {
+        result_config_cie.sampleRate = A2DP_LDAC_SAMPLING_FREQ_44100;
+        codec_capability_.sample_rate = codec_user_config_.sample_rate;
+        codec_config_.sample_rate = codec_user_config_.sample_rate;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_48000) {
+        result_config_cie.sampleRate = A2DP_LDAC_SAMPLING_FREQ_48000;
+        codec_capability_.sample_rate = codec_user_config_.sample_rate;
+        codec_config_.sample_rate = codec_user_config_.sample_rate;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_88200) {
+        result_config_cie.sampleRate = A2DP_LDAC_SAMPLING_FREQ_88200;
+        codec_capability_.sample_rate = codec_user_config_.sample_rate;
+        codec_config_.sample_rate = codec_user_config_.sample_rate;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_96000) {
+        result_config_cie.sampleRate = A2DP_LDAC_SAMPLING_FREQ_96000;
+        codec_capability_.sample_rate = codec_user_config_.sample_rate;
+        codec_config_.sample_rate = codec_user_config_.sample_rate;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_176400) {
+        result_config_cie.sampleRate = A2DP_LDAC_SAMPLING_FREQ_176400;
+        codec_capability_.sample_rate = codec_user_config_.sample_rate;
+        codec_config_.sample_rate = codec_user_config_.sample_rate;
+      }
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+      if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_192000) {
+        result_config_cie.sampleRate = A2DP_LDAC_SAMPLING_FREQ_192000;
+        codec_capability_.sample_rate = codec_user_config_.sample_rate;
+        codec_config_.sample_rate = codec_user_config_.sample_rate;
+      }
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
+      codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
+      codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
+      break;
+  }
+
+  // Select the sample frequency if there is no user preference
+  do {
+    if (codec_config_.sample_rate != BTAV_A2DP_CODEC_SAMPLE_RATE_NONE) break;
+
+    // Compute the common capability
+    if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_44100)
+      codec_capability_.sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_44100;
+    if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_48000)
+      codec_capability_.sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_48000;
+    if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_88200)
+      codec_capability_.sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_88200;
+    if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_96000)
+      codec_capability_.sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_96000;
+    if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_176400)
+      codec_capability_.sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_176400;
+    if (sampleRate & A2DP_LDAC_SAMPLING_FREQ_192000)
+      codec_capability_.sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_192000;
+
+    // No user preference - try the codec audio config
+    if (select_audio_sample_rate(&codec_audio_config_, sampleRate,
+                                 &result_config_cie, &codec_config_)) {
+      break;
+    }
+
+    // No user preference - try the default config
+    if (select_best_sample_rate(
+            a2dp_ldac_default_config.sampleRate & sink_info_cie.sampleRate,
+            &result_config_cie, &codec_config_)) {
+      break;
+    }
+
+    // No user preference - use the best match
+    if (select_best_sample_rate(sampleRate, &result_config_cie,
+                                &codec_config_)) {
+      break;
+    }
+  } while (false);
+  if (codec_config_.sample_rate == BTAV_A2DP_CODEC_SAMPLE_RATE_NONE) {
+    LOG_ERROR(LOG_TAG,
+              "%s: cannot match sample frequency: source caps = 0x%x "
+              "sink info = 0x%x",
+              __func__, a2dp_ldac_caps.sampleRate, sink_info_cie.sampleRate);
+    goto fail;
+  }
+
+  //
+  // Select the bits per sample
+  //
+  // NOTE: this information is NOT included in the LDAC A2DP codec description
+  // that is sent OTA.
+  bits_per_sample = a2dp_ldac_caps.bits_per_sample;
+  codec_config_.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
+  switch (codec_user_config_.bits_per_sample) {
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+      if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16) {
+        result_config_cie.bits_per_sample = codec_user_config_.bits_per_sample;
+        codec_capability_.bits_per_sample = codec_user_config_.bits_per_sample;
+        codec_config_.bits_per_sample = codec_user_config_.bits_per_sample;
+      }
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+      if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24) {
+        result_config_cie.bits_per_sample = codec_user_config_.bits_per_sample;
+        codec_capability_.bits_per_sample = codec_user_config_.bits_per_sample;
+        codec_config_.bits_per_sample = codec_user_config_.bits_per_sample;
+      }
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+      if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32) {
+        result_config_cie.bits_per_sample = codec_user_config_.bits_per_sample;
+        codec_capability_.bits_per_sample = codec_user_config_.bits_per_sample;
+        codec_config_.bits_per_sample = codec_user_config_.bits_per_sample;
+      }
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+      result_config_cie.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
+      codec_capability_.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
+      codec_config_.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
+      break;
+  }
+
+  // Select the bits per sample if there is no user preference
+  do {
+    if (codec_config_.bits_per_sample != BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE)
+      break;
+
+    // Compute the common capability
+    codec_capability_.bits_per_sample = bits_per_sample;
+
+    // No user preference - the the codec audio config
+    if (select_audio_bits_per_sample(&codec_audio_config_,
+                                     a2dp_ldac_caps.bits_per_sample,
+                                     &result_config_cie, &codec_config_)) {
+      break;
+    }
+
+    // No user preference - try the default config
+    if (select_best_bits_per_sample(a2dp_ldac_default_config.bits_per_sample,
+                                    &result_config_cie, &codec_config_)) {
+      break;
+    }
+
+    // No user preference - use the best match
+    if (select_best_bits_per_sample(a2dp_ldac_caps.bits_per_sample,
+                                    &result_config_cie, &codec_config_)) {
+      break;
+    }
+  } while (false);
+  if (codec_config_.bits_per_sample == BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE) {
+    LOG_ERROR(LOG_TAG,
+              "%s: cannot match bits per sample: default = 0x%x "
+              "user preference = 0x%x",
+              __func__, a2dp_ldac_default_config.bits_per_sample,
+              codec_user_config_.bits_per_sample);
+    goto fail;
+  }
+
+  //
+  // Select the channel mode
+  //
+  channelMode = a2dp_ldac_caps.channelMode & sink_info_cie.channelMode;
+  codec_config_.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE;
+  switch (codec_user_config_.channel_mode) {
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
+      if (channelMode & A2DP_LDAC_CHANNEL_MODE_MONO) {
+        result_config_cie.channelMode = A2DP_LDAC_CHANNEL_MODE_MONO;
+        codec_capability_.channel_mode = codec_user_config_.channel_mode;
+        codec_config_.channel_mode = codec_user_config_.channel_mode;
+      }
+      break;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
+      if (channelMode & A2DP_LDAC_CHANNEL_MODE_STEREO) {
+        result_config_cie.channelMode = A2DP_LDAC_CHANNEL_MODE_STEREO;
+        codec_capability_.channel_mode = codec_user_config_.channel_mode;
+        codec_config_.channel_mode = codec_user_config_.channel_mode;
+        break;
+      }
+      if (channelMode & A2DP_LDAC_CHANNEL_MODE_DUAL) {
+        result_config_cie.channelMode = A2DP_LDAC_CHANNEL_MODE_DUAL;
+        codec_capability_.channel_mode = codec_user_config_.channel_mode;
+        codec_config_.channel_mode = codec_user_config_.channel_mode;
+        break;
+      }
+      break;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
+      codec_capability_.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE;
+      codec_config_.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE;
+      break;
+  }
+
+  // Select the channel mode if there is no user preference
+  do {
+    if (codec_config_.channel_mode != BTAV_A2DP_CODEC_CHANNEL_MODE_NONE) break;
+
+    // Compute the common capability
+    if (channelMode & A2DP_LDAC_CHANNEL_MODE_MONO)
+      codec_capability_.channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_MONO;
+    if (channelMode &
+        (A2DP_LDAC_CHANNEL_MODE_STEREO | A2DP_LDAC_CHANNEL_MODE_DUAL)) {
+      codec_capability_.channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+    }
+
+    // No user preference - try the codec audio config
+    if (select_audio_channel_mode(&codec_audio_config_, channelMode,
+                                  &result_config_cie, &codec_config_)) {
+      break;
+    }
+
+    // No user preference - try the default config
+    if (select_best_channel_mode(
+            a2dp_ldac_default_config.channelMode & sink_info_cie.channelMode,
+            &result_config_cie, &codec_config_)) {
+      break;
+    }
+
+    // No user preference - use the best match
+    if (select_best_channel_mode(channelMode, &result_config_cie,
+                                 &codec_config_)) {
+      break;
+    }
+  } while (false);
+  if (codec_config_.channel_mode == BTAV_A2DP_CODEC_CHANNEL_MODE_NONE) {
+    LOG_ERROR(LOG_TAG,
+              "%s: cannot match channel mode: source caps = 0x%x "
+              "sink info = 0x%x",
+              __func__, a2dp_ldac_caps.channelMode, sink_info_cie.channelMode);
+    goto fail;
+  }
+
+  if (A2DP_BuildInfoLdac(AVDT_MEDIA_TYPE_AUDIO, &result_config_cie,
+                         p_result_codec_config) != A2DP_SUCCESS) {
+    goto fail;
+  }
+
+  //
+  // Copy the codec-specific fields if they are not zero
+  //
+  if (codec_user_config_.codec_specific_1 != 0)
+    codec_config_.codec_specific_1 = codec_user_config_.codec_specific_1;
+  if (codec_user_config_.codec_specific_2 != 0)
+    codec_config_.codec_specific_2 = codec_user_config_.codec_specific_2;
+  if (codec_user_config_.codec_specific_3 != 0)
+    codec_config_.codec_specific_3 = codec_user_config_.codec_specific_3;
+  if (codec_user_config_.codec_specific_4 != 0)
+    codec_config_.codec_specific_4 = codec_user_config_.codec_specific_4;
+
+  // Create a local copy of the peer codec capability, and the
+  // result codec config.
+  if (is_capability) {
+    status = A2DP_BuildInfoLdac(AVDT_MEDIA_TYPE_AUDIO, &sink_info_cie,
+                                ota_codec_peer_capability_);
+  } else {
+    status = A2DP_BuildInfoLdac(AVDT_MEDIA_TYPE_AUDIO, &sink_info_cie,
+                                ota_codec_peer_config_);
+  }
+  CHECK(status == A2DP_SUCCESS);
+  status = A2DP_BuildInfoLdac(AVDT_MEDIA_TYPE_AUDIO, &result_config_cie,
+                              ota_codec_config_);
+  CHECK(status == A2DP_SUCCESS);
+  return true;
+
+fail:
+  // Restore the internal state
+  codec_config_ = saved_codec_config;
+  codec_capability_ = saved_codec_capability;
+  codec_user_config_ = saved_codec_user_config;
+  codec_audio_config_ = saved_codec_audio_config;
+  memcpy(ota_codec_config_, saved_ota_codec_config, sizeof(ota_codec_config_));
+  memcpy(ota_codec_peer_capability_, saved_ota_codec_peer_capability,
+         sizeof(ota_codec_peer_capability_));
+  memcpy(ota_codec_peer_config_, saved_ota_codec_peer_config,
+         sizeof(ota_codec_peer_config_));
+  return false;
+}
diff --git a/system/stack/a2dp/a2dp_vendor_ldac_encoder.cc b/system/stack/a2dp/a2dp_vendor_ldac_encoder.cc
new file mode 100644
index 00000000000..70456b490e7
--- /dev/null
+++ b/system/stack/a2dp/a2dp_vendor_ldac_encoder.cc
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "a2dp_vendor_ldac_encoder"
+
+#include "a2dp_vendor_ldac_encoder.h"
+
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "a2dp_vendor.h"
+#include "a2dp_vendor_ldac.h"
+#include "bt_common.h"
+#include "osi/include/log.h"
+#include "osi/include/osi.h"
+
+//
+// Encoder for LDAC Source Codec
+//
+
+//
+// TODO: Local typedefs and defines to avoid including the LDAC encoder
+// header file.
+//
+typedef void* HANDLE_LDAC_BT;
+typedef enum {
+  LDACBT_SMPL_FMT_S16 = 0x2,
+  LDACBT_SMPL_FMT_S24 = 0x3,
+  LDACBT_SMPL_FMT_S32 = 0x4,
+  LDACBT_SMPL_FMT_F32 = 0x5,
+} LDACBT_SMPL_FMT_T;
+
+enum {
+  LDACBT_EQMID_HQ = 0,
+  LDACBT_EQMID_SQ,
+  LDACBT_EQMID_MQ,
+};
+
+#define LDACBT_ENC_LSU 128
+#define LDACBT_MAX_LSU 512
+#define LDACBT_API_ERR(err) ((err >> 20) & 0x0FFF)
+#define LDACBT_HANDLE_ERR(err) ((err >> 10) & 0x03FF)
+#define LDACBT_BLOCK_ERR(err) (err & 0x03FF)
+
+//
+// The LDAC encoder shared library, and the functions to use
+//
+static const char* LDAC_ENCODER_LIB_NAME = "libldacBT_enc.so";
+static void* ldac_encoder_lib_handle = NULL;
+
+static const char* LDAC_GET_HANDLE_NAME = "ldacBT_get_handle";
+typedef HANDLE_LDAC_BT (*tLDAC_GET_HANDLE)(void);
+
+static const char* LDAC_FREE_HANDLE_NAME = "ldacBT_free_handle";
+typedef void (*tLDAC_FREE_HANDLE)(HANDLE_LDAC_BT hLdacParam);
+
+static const char* LDAC_CLOSE_HANDLE_NAME = "ldacBT_close_handle";
+typedef void (*tLDAC_CLOSE_HANDLE)(HANDLE_LDAC_BT hLdacParam);
+
+static const char* LDAC_GET_VERSION_NAME = "ldacBT_get_version";
+typedef int (*tLDAC_GET_VERSION)(void);
+
+static const char* LDAC_GET_BITRATE_NAME = "ldacBT_get_bitrate";
+typedef int (*tLDAC_GET_BITRATE)(HANDLE_LDAC_BT hLdacParam);
+
+static const char* LDAC_GET_SAMPLING_FREQ_NAME = "ldacBT_get_sampling_freq";
+typedef int (*tLDAC_GET_SAMPLING_FREQ)(HANDLE_LDAC_BT hLdacParam);
+
+static const char* LDAC_INIT_HANDLE_ENCODE_NAME = "ldacBT_init_handle_encode";
+typedef int (*tLDAC_INIT_HANDLE_ENCODE)(HANDLE_LDAC_BT hLdacParam, int mtu,
+                                        int eqmid, int channel_mode,
+                                        LDACBT_SMPL_FMT_T fmt,
+                                        int sampling_freq);
+
+static const char* LDAC_ENCODE_NAME = "ldacBT_encode";
+typedef int (*tLDAC_ENCODE)(HANDLE_LDAC_BT hLdacParam, void* p_pcm,
+                            int* p_pcm_encoded_byte, unsigned char* p_stream,
+                            int* pframe_length_wrote, int* pframe_num);
+
+static const char* LDAC_SET_EQMID_NAME = "ldacBT_set_eqmid";
+typedef int (*tLDAC_SET_EQMID)(HANDLE_LDAC_BT hLdacParam, int eqmid);
+
+static const char* LDAC_ALTER_EQMID_PRIORITY_NAME =
+    "ldacBT_alter_eqmid_priority";
+typedef int (*tLDAC_ALTER_EQMID_PRIORITY)(HANDLE_LDAC_BT hLdacParam,
+                                          int priority);
+
+static const char* LDAC_GET_EQMID_NAME = "ldacBT_get_eqmid";
+typedef int (*tLDAC_GET_EQMID)(HANDLE_LDAC_BT hLdacParam);
+
+static const char* LDAC_GET_ERROR_CODE_NAME = "ldacBT_get_error_code";
+typedef int (*tLDAC_GET_ERROR_CODE)(HANDLE_LDAC_BT hLdacParam);
+
+static tLDAC_GET_HANDLE ldac_get_handle_func;
+static tLDAC_FREE_HANDLE ldac_free_handle_func;
+static tLDAC_CLOSE_HANDLE ldac_close_handle_func;
+static tLDAC_GET_VERSION ldac_get_version_func;
+static tLDAC_GET_BITRATE ldac_get_bitrate_func;
+static tLDAC_GET_SAMPLING_FREQ ldac_get_sampling_freq_func;
+static tLDAC_INIT_HANDLE_ENCODE ldac_init_handle_encode_func;
+static tLDAC_ENCODE ldac_encode_func;
+static tLDAC_SET_EQMID ldac_set_eqmid_func;
+static tLDAC_ALTER_EQMID_PRIORITY ldac_alter_eqmid_priority_func;
+static tLDAC_GET_EQMID ldac_get_eqmid_func;
+static tLDAC_GET_ERROR_CODE ldac_get_error_code_func;
+
+#define STATS_UPDATE_MAX(current_value_storage, new_value) \
+  do {                                                     \
+    if ((new_value) > (current_value_storage))             \
+      (current_value_storage) = (new_value);               \
+  } while (0)
+
+// A2DP LDAC encoder interval in milliseconds
+#define A2DP_LDAC_ENCODER_INTERVAL_MS 20
+#define A2DP_LDAC_MEDIA_BYTES_PER_FRAME 128
+
+// offset
+#if (BTA_AV_CO_CP_SCMS_T == TRUE)
+#define A2DP_LDAC_OFFSET (AVDT_MEDIA_OFFSET + A2DP_LDAC_MPL_HDR_LEN + 1)
+#else
+#define A2DP_LDAC_OFFSET (AVDT_MEDIA_OFFSET + A2DP_LDAC_MPL_HDR_LEN)
+#endif
+
+typedef struct {
+  uint32_t sample_rate;
+  uint8_t channel_mode;
+  uint8_t bits_per_sample;
+  int quality_mode_index;
+  int pcm_wlength;
+  LDACBT_SMPL_FMT_T pcm_fmt;
+} tA2DP_LDAC_ENCODER_PARAMS;
+
+typedef struct {
+  uint32_t counter;
+  uint32_t bytes_per_tick; /* pcm bytes read each media task tick */
+  uint64_t last_frame_us;
+} tA2DP_LDAC_FEEDING_STATE;
+
+typedef struct {
+  uint64_t session_start_us;
+
+  size_t media_read_total_expected_packets;
+  size_t media_read_total_expected_reads_count;
+  size_t media_read_total_expected_read_bytes;
+
+  size_t media_read_total_dropped_packets;
+  size_t media_read_total_actual_reads_count;
+  size_t media_read_total_actual_read_bytes;
+} a2dp_ldac_encoder_stats_t;
+
+typedef struct {
+  a2dp_source_read_callback_t read_callback;
+  a2dp_source_enqueue_callback_t enqueue_callback;
+  uint16_t TxAaMtuSize;
+
+  bool use_SCMS_T;
+  bool is_peer_edr;          // True if the peer device supports EDR
+  bool peer_supports_3mbps;  // True if the peer device supports 3Mbps EDR
+  uint16_t peer_mtu;         // MTU of the A2DP peer
+  uint32_t timestamp;        // Timestamp for the A2DP frames
+
+  HANDLE_LDAC_BT ldac_handle;
+  bool has_ldac_handle;  // True if ldac_handle is valid
+
+  tA2DP_FEEDING_PARAMS feeding_params;
+  tA2DP_LDAC_ENCODER_PARAMS ldac_encoder_params;
+  tA2DP_LDAC_FEEDING_STATE ldac_feeding_state;
+
+  a2dp_ldac_encoder_stats_t stats;
+} tA2DP_LDAC_ENCODER_CB;
+
+static tA2DP_LDAC_ENCODER_CB a2dp_ldac_encoder_cb;
+
+static void a2dp_vendor_ldac_encoder_update(uint16_t peer_mtu,
+                                            A2dpCodecConfig* a2dp_codec_config,
+                                            bool* p_restart_input,
+                                            bool* p_restart_output,
+                                            bool* p_config_updated);
+static void a2dp_ldac_get_num_frame_iteration(uint8_t* num_of_iterations,
+                                              uint8_t* num_of_frames,
+                                              uint64_t timestamp_us);
+static void a2dp_ldac_encode_frames(uint8_t nb_frame);
+static bool a2dp_ldac_read_feeding(uint8_t* read_buffer);
+
+static void* load_func(const char* func_name) {
+  void* func_ptr = dlsym(ldac_encoder_lib_handle, func_name);
+  if (func_ptr == NULL) {
+    LOG_ERROR(LOG_TAG,
+              "%s: cannot find function '%s' in the encoder library: %s",
+              __func__, func_name, dlerror());
+    A2DP_VendorUnloadEncoderLdac();
+    return NULL;
+  }
+  return func_ptr;
+}
+
+bool A2DP_VendorLoadEncoderLdac(void) {
+  if (ldac_encoder_lib_handle != NULL) return true;  // Already loaded
+
+  // Initialize the control block
+  memset(&a2dp_ldac_encoder_cb, 0, sizeof(a2dp_ldac_encoder_cb));
+
+  // Open the encoder library
+  ldac_encoder_lib_handle = dlopen(LDAC_ENCODER_LIB_NAME, RTLD_NOW);
+  if (ldac_encoder_lib_handle == NULL) {
+    LOG_ERROR(LOG_TAG, "%s: cannot open LDAC encoder library %s: %s", __func__,
+              LDAC_ENCODER_LIB_NAME, dlerror());
+    return false;
+  }
+
+  // Load all functions
+  ldac_get_handle_func = (tLDAC_GET_HANDLE)load_func(LDAC_GET_HANDLE_NAME);
+  if (ldac_get_handle_func == NULL) return false;
+  ldac_free_handle_func = (tLDAC_FREE_HANDLE)load_func(LDAC_FREE_HANDLE_NAME);
+  if (ldac_free_handle_func == NULL) return false;
+  ldac_close_handle_func =
+      (tLDAC_CLOSE_HANDLE)load_func(LDAC_CLOSE_HANDLE_NAME);
+  if (ldac_close_handle_func == NULL) return false;
+  ldac_get_version_func = (tLDAC_GET_VERSION)load_func(LDAC_GET_VERSION_NAME);
+  if (ldac_get_version_func == NULL) return false;
+  ldac_get_bitrate_func = (tLDAC_GET_BITRATE)load_func(LDAC_GET_BITRATE_NAME);
+  if (ldac_get_bitrate_func == NULL) return false;
+  ldac_get_sampling_freq_func =
+      (tLDAC_GET_SAMPLING_FREQ)load_func(LDAC_GET_SAMPLING_FREQ_NAME);
+  if (ldac_get_sampling_freq_func == NULL) return false;
+  ldac_init_handle_encode_func =
+      (tLDAC_INIT_HANDLE_ENCODE)load_func(LDAC_INIT_HANDLE_ENCODE_NAME);
+  if (ldac_init_handle_encode_func == NULL) return false;
+  ldac_encode_func = (tLDAC_ENCODE)load_func(LDAC_ENCODE_NAME);
+  if (ldac_encode_func == NULL) return false;
+  ldac_set_eqmid_func = (tLDAC_SET_EQMID)load_func(LDAC_SET_EQMID_NAME);
+  if (ldac_set_eqmid_func == NULL) return false;
+  ldac_alter_eqmid_priority_func =
+      (tLDAC_ALTER_EQMID_PRIORITY)load_func(LDAC_ALTER_EQMID_PRIORITY_NAME);
+  if (ldac_alter_eqmid_priority_func == NULL) return false;
+  ldac_get_eqmid_func = (tLDAC_GET_EQMID)load_func(LDAC_GET_EQMID_NAME);
+  if (ldac_get_eqmid_func == NULL) return false;
+  ldac_get_error_code_func =
+      (tLDAC_GET_ERROR_CODE)load_func(LDAC_GET_ERROR_CODE_NAME);
+  if (ldac_get_error_code_func == NULL) return false;
+
+  return true;
+}
+
+void A2DP_VendorUnloadEncoderLdac(void) {
+  // Cleanup any LDAC-related state
+  if (a2dp_ldac_encoder_cb.has_ldac_handle && ldac_free_handle_func != NULL)
+    ldac_free_handle_func(a2dp_ldac_encoder_cb.ldac_handle);
+  memset(&a2dp_ldac_encoder_cb, 0, sizeof(a2dp_ldac_encoder_cb));
+
+  ldac_get_handle_func = NULL;
+  ldac_free_handle_func = NULL;
+  ldac_close_handle_func = NULL;
+  ldac_get_version_func = NULL;
+  ldac_get_bitrate_func = NULL;
+  ldac_get_sampling_freq_func = NULL;
+  ldac_init_handle_encode_func = NULL;
+  ldac_encode_func = NULL;
+  ldac_set_eqmid_func = NULL;
+  ldac_alter_eqmid_priority_func = NULL;
+  ldac_get_eqmid_func = NULL;
+  ldac_get_error_code_func = NULL;
+
+  if (ldac_encoder_lib_handle != NULL) {
+    dlclose(ldac_encoder_lib_handle);
+    ldac_encoder_lib_handle = NULL;
+  }
+}
+
+void a2dp_vendor_ldac_encoder_init(
+    const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params,
+    A2dpCodecConfig* a2dp_codec_config,
+    a2dp_source_read_callback_t read_callback,
+    a2dp_source_enqueue_callback_t enqueue_callback) {
+  if (a2dp_ldac_encoder_cb.has_ldac_handle)
+    ldac_free_handle_func(a2dp_ldac_encoder_cb.ldac_handle);
+  memset(&a2dp_ldac_encoder_cb, 0, sizeof(a2dp_ldac_encoder_cb));
+
+  a2dp_ldac_encoder_cb.stats.session_start_us = time_get_os_boottime_us();
+
+  a2dp_ldac_encoder_cb.read_callback = read_callback;
+  a2dp_ldac_encoder_cb.enqueue_callback = enqueue_callback;
+  a2dp_ldac_encoder_cb.is_peer_edr = p_peer_params->is_peer_edr;
+  a2dp_ldac_encoder_cb.peer_supports_3mbps = p_peer_params->peer_supports_3mbps;
+  a2dp_ldac_encoder_cb.peer_mtu = p_peer_params->peer_mtu;
+  a2dp_ldac_encoder_cb.timestamp = 0;
+
+  a2dp_ldac_encoder_cb.use_SCMS_T = false;  // TODO: should be a parameter
+#if (BTA_AV_CO_CP_SCMS_T == TRUE)
+  a2dp_ldac_encoder_cb.use_SCMS_T = true;
+#endif
+
+  a2dp_ldac_encoder_cb.ldac_handle = ldac_get_handle_func();
+  if (a2dp_ldac_encoder_cb.ldac_handle == NULL) {
+    LOG_ERROR(LOG_TAG, "%s: Cannot get LDAC encoder handle", __func__);
+    return;  // TODO: Return an error?
+  }
+  a2dp_ldac_encoder_cb.has_ldac_handle = true;
+
+  // NOTE: Ignore the restart_input / restart_output flags - this initization
+  // happens when the connection is (re)started.
+  bool restart_input = false;
+  bool restart_output = false;
+  bool config_updated = false;
+  a2dp_vendor_ldac_encoder_update(a2dp_ldac_encoder_cb.peer_mtu,
+                                  a2dp_codec_config, &restart_input,
+                                  &restart_output, &config_updated);
+}
+
+bool A2dpCodecConfigLdac::updateEncoderUserConfig(
+    const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, bool* p_restart_input,
+    bool* p_restart_output, bool* p_config_updated) {
+  a2dp_ldac_encoder_cb.is_peer_edr = p_peer_params->is_peer_edr;
+  a2dp_ldac_encoder_cb.peer_supports_3mbps = p_peer_params->peer_supports_3mbps;
+  a2dp_ldac_encoder_cb.peer_mtu = p_peer_params->peer_mtu;
+  a2dp_ldac_encoder_cb.timestamp = 0;
+
+  if (a2dp_ldac_encoder_cb.peer_mtu == 0) {
+    LOG_ERROR(LOG_TAG,
+              "%s: Cannot update the codec encoder for %s: "
+              "invalid peer MTU",
+              __func__, name().c_str());
+    return false;
+  }
+
+  a2dp_vendor_ldac_encoder_update(a2dp_ldac_encoder_cb.peer_mtu, this,
+                                  p_restart_input, p_restart_output,
+                                  p_config_updated);
+  return true;
+}
+
+// Update the A2DP LDAC encoder.
+// |peer_mtu| is the peer MTU.
+// |a2dp_codec_config| is the A2DP codec to use for the update.
+static void a2dp_vendor_ldac_encoder_update(uint16_t peer_mtu,
+                                            A2dpCodecConfig* a2dp_codec_config,
+                                            bool* p_restart_input,
+                                            bool* p_restart_output,
+                                            bool* p_config_updated) {
+  tA2DP_LDAC_ENCODER_PARAMS* p_encoder_params =
+      &a2dp_ldac_encoder_cb.ldac_encoder_params;
+  uint8_t codec_info[AVDT_CODEC_SIZE];
+
+  *p_restart_input = false;
+  *p_restart_output = false;
+  *p_config_updated = false;
+  if (!a2dp_codec_config->copyOutOtaCodecConfig(codec_info)) {
+    LOG_ERROR(LOG_TAG,
+              "%s: Cannot update the codec encoder for %s: "
+              "invalid codec config",
+              __func__, a2dp_codec_config->name().c_str());
+    return;
+  }
+  const uint8_t* p_codec_info = codec_info;
+  btav_a2dp_codec_config_t codec_config = a2dp_codec_config->getCodecConfig();
+
+  // The feeding parameters
+  a2dp_ldac_encoder_cb.feeding_params.sample_rate =
+      A2DP_VendorGetTrackSampleRateLdac(p_codec_info);
+  a2dp_ldac_encoder_cb.feeding_params.bits_per_sample =
+      a2dp_codec_config->getAudioBitsPerSample();
+  a2dp_ldac_encoder_cb.feeding_params.channel_count =
+      A2DP_VendorGetTrackChannelCountLdac(p_codec_info);
+
+  // The codec parameters
+  p_encoder_params->sample_rate =
+      a2dp_ldac_encoder_cb.feeding_params.sample_rate;
+  p_encoder_params->channel_mode =
+      A2DP_VendorGetChannelModeCodeLdac(p_codec_info);
+
+  uint16_t mtu_size =
+      BT_DEFAULT_BUFFER_SIZE - A2DP_LDAC_OFFSET - sizeof(BT_HDR);
+  if (mtu_size < peer_mtu) {
+    a2dp_ldac_encoder_cb.TxAaMtuSize = mtu_size;
+  } else {
+    a2dp_ldac_encoder_cb.TxAaMtuSize = peer_mtu;
+  }
+
+  // Set the quality mode index
+  int old_quality_mode_index = p_encoder_params->quality_mode_index;
+  if (codec_config.codec_specific_1 != 0) {
+    p_encoder_params->quality_mode_index = codec_config.codec_specific_1 % 10;
+    LOG_DEBUG(LOG_TAG, "%s: setting quality index to %d", __func__,
+              p_encoder_params->quality_mode_index);
+  } else {
+    p_encoder_params->quality_mode_index = LDACBT_EQMID_HQ;
+    LOG_DEBUG(LOG_TAG, "%s: setting quality index to default value of %d",
+              __func__, p_encoder_params->quality_mode_index);
+  }
+  if (p_encoder_params->quality_mode_index != old_quality_mode_index)
+    *p_config_updated = true;
+
+  p_encoder_params->pcm_wlength =
+      a2dp_ldac_encoder_cb.feeding_params.bits_per_sample >> 3;
+  // Set the Audio format from pcm_wlength
+  p_encoder_params->pcm_fmt = LDACBT_SMPL_FMT_S16;
+  if (p_encoder_params->pcm_wlength == 2)
+    p_encoder_params->pcm_fmt = LDACBT_SMPL_FMT_S16;
+  else if (p_encoder_params->pcm_wlength == 3)
+    p_encoder_params->pcm_fmt = LDACBT_SMPL_FMT_S24;
+  else if (p_encoder_params->pcm_wlength == 4)
+    p_encoder_params->pcm_fmt = LDACBT_SMPL_FMT_S32;
+
+  LOG_DEBUG(LOG_TAG, "%s: MTU=%d, peer_mtu=%d", __func__,
+            a2dp_ldac_encoder_cb.TxAaMtuSize, peer_mtu);
+  LOG_DEBUG(LOG_TAG,
+            "%s: sample_rate: %d channel_mode: %d "
+            "quality_mode_index: %d pcm_wlength: %d pcm_fmt: %d",
+            __func__, p_encoder_params->sample_rate,
+            p_encoder_params->channel_mode,
+            p_encoder_params->quality_mode_index, p_encoder_params->pcm_wlength,
+            p_encoder_params->pcm_fmt);
+
+  // Initialize the encoder.
+  // NOTE: MTU in the initialization must include the AVDT media header size.
+  int result = ldac_init_handle_encode_func(
+      a2dp_ldac_encoder_cb.ldac_handle,
+      a2dp_ldac_encoder_cb.TxAaMtuSize + AVDT_MEDIA_HDR_SIZE,
+      p_encoder_params->quality_mode_index, p_encoder_params->channel_mode,
+      p_encoder_params->pcm_fmt, p_encoder_params->sample_rate);
+  if (result != 0) {
+    LOG_ERROR(LOG_TAG, "%s: error initializing the LDAC encoder: %d", __func__,
+              result);
+  }
+}
+
+void a2dp_vendor_ldac_encoder_cleanup(void) {
+  if (a2dp_ldac_encoder_cb.has_ldac_handle)
+    ldac_free_handle_func(a2dp_ldac_encoder_cb.ldac_handle);
+  memset(&a2dp_ldac_encoder_cb, 0, sizeof(a2dp_ldac_encoder_cb));
+}
+
+void a2dp_vendor_ldac_feeding_init(
+    const tA2DP_FEEDING_PARAMS* p_feeding_params) {
+  tA2DP_LDAC_ENCODER_PARAMS* p_encoder_params =
+      &a2dp_ldac_encoder_cb.ldac_encoder_params;
+  bool reconfig_needed = false;
+
+  LOG_DEBUG(LOG_TAG,
+            "%s: PCM feeding: sample_rate:%d bits_per_sample:%d "
+            "channel_count:%d",
+            __func__, p_feeding_params->sample_rate,
+            p_feeding_params->bits_per_sample, p_feeding_params->channel_count);
+
+  /* Save the feeding information */
+  memcpy(&a2dp_ldac_encoder_cb.feeding_params, p_feeding_params,
+         sizeof(tA2DP_FEEDING_PARAMS));
+
+  // Check the PCM feeding sample_rate
+  switch (p_feeding_params->sample_rate) {
+    case 44100:
+    case 48000:
+    case 88200:
+    case 96000:
+    case 176400:
+    case 192000:
+      break;
+    default:
+      LOG_WARN(LOG_TAG, "%s: feeding PCM sample_rate %u is not supported",
+               __func__, p_feeding_params->sample_rate);
+      return;
+  }
+  if (p_encoder_params->sample_rate != p_feeding_params->sample_rate) {
+    LOG_DEBUG(LOG_TAG,
+              "%s: codec reconfiguration: feeding PCM sample_rate "
+              "from %u to %u",
+              __func__, p_encoder_params->sample_rate,
+              p_feeding_params->sample_rate);
+    reconfig_needed = true;
+    p_encoder_params->sample_rate = p_feeding_params->sample_rate;
+  }
+
+  // Check the bits per sample
+  switch (p_feeding_params->bits_per_sample) {
+    case 16:
+    case 24:
+    case 32:
+      break;
+    default:
+      LOG_WARN(LOG_TAG, "%s: feeding PCM bits_per_sample %u is not supported",
+               __func__, p_feeding_params->bits_per_sample);
+      return;
+  }
+  if (p_encoder_params->bits_per_sample != p_feeding_params->bits_per_sample) {
+    LOG_DEBUG(LOG_TAG,
+              "%s: LDAC reconfiguration needed: "
+              "feeding PCM bits_per_sample from %u to %u",
+              __func__, p_encoder_params->bits_per_sample,
+              p_feeding_params->bits_per_sample);
+    p_encoder_params->bits_per_sample = p_feeding_params->bits_per_sample;
+    // NOTE: A2DP reconfigure is not necessary in this case. Re-initializing
+    // just the LDAC encoder is sufficient.
+    reconfig_needed = true;
+  }
+
+  // Check the number of channels
+  switch (p_feeding_params->channel_count) {
+    case 1:  // Mono
+    case 2:  // Stereo
+      break;
+    default:
+      LOG_WARN(LOG_TAG, "%s: feeding PCM channel_count %u is not supported",
+               __func__, p_feeding_params->channel_count);
+      return;
+  }
+
+  if ((p_feeding_params->channel_count == 1) &&
+      (p_encoder_params->channel_mode != A2DP_LDAC_CHANNEL_MODE_MONO)) {
+    LOG_DEBUG(LOG_TAG,
+              "%s: codec reconfiguration: feeding PCM channel_count "
+              "from 2 to %u",
+              __func__, p_feeding_params->channel_count);
+    reconfig_needed = true;
+    p_encoder_params->channel_mode = A2DP_LDAC_CHANNEL_MODE_MONO;
+  }
+  if ((p_feeding_params->channel_count == 2) &&
+      (p_encoder_params->channel_mode != A2DP_LDAC_CHANNEL_MODE_STEREO) &&
+      (p_encoder_params->channel_mode != A2DP_LDAC_CHANNEL_MODE_DUAL)) {
+    LOG_DEBUG(LOG_TAG,
+              "%s: codec reconfiguration: feeding PCM channel_count "
+              "from 1 to %u",
+              __func__, p_feeding_params->channel_count);
+    reconfig_needed = true;
+    p_encoder_params->channel_mode = A2DP_LDAC_CHANNEL_MODE_STEREO;
+  }
+
+  p_encoder_params->pcm_wlength = p_encoder_params->bits_per_sample >> 3;
+  // Set the Audio format from pcm_wlength
+  p_encoder_params->pcm_fmt = LDACBT_SMPL_FMT_S16;
+  if (p_encoder_params->pcm_wlength == 2)
+    p_encoder_params->pcm_fmt = LDACBT_SMPL_FMT_S16;
+  else if (p_encoder_params->pcm_wlength == 3)
+    p_encoder_params->pcm_fmt = LDACBT_SMPL_FMT_S24;
+  else if (p_encoder_params->pcm_wlength == 4)
+    p_encoder_params->pcm_fmt = LDACBT_SMPL_FMT_S32;
+
+  if (!reconfig_needed) {
+    LOG_DEBUG(LOG_TAG, "%s: no LDAC reconfiguration needed", __func__);
+    return;
+  }
+
+  LOG_DEBUG(LOG_TAG, "%s: sample_rate=%u bits_per_sample=%u channel_count=%u",
+            __func__, p_feeding_params->sample_rate,
+            p_feeding_params->bits_per_sample, p_feeding_params->channel_count);
+
+  if (a2dp_ldac_encoder_cb.has_ldac_handle) {
+    ldac_free_handle_func(a2dp_ldac_encoder_cb.ldac_handle);
+    a2dp_ldac_encoder_cb.has_ldac_handle = false;
+  }
+  a2dp_ldac_encoder_cb.ldac_handle = ldac_get_handle_func();
+  if (a2dp_ldac_encoder_cb.ldac_handle == NULL) {
+    LOG_ERROR(LOG_TAG, "%s: Cannot get LDAC encoder handle", __func__);
+    return;  // TODO: Return an error?
+  }
+  a2dp_ldac_encoder_cb.has_ldac_handle = true;
+
+  LOG_DEBUG(LOG_TAG,
+            "%s: sample_rate: %d channel_mode: %d "
+            "quality_mode_index: %d pcm_wlength: %d pcm_fmt: %d",
+            __func__, p_encoder_params->sample_rate,
+            p_encoder_params->channel_mode,
+            p_encoder_params->quality_mode_index, p_encoder_params->pcm_wlength,
+            p_encoder_params->pcm_fmt);
+
+  // Initialize the encoder.
+  // NOTE: MTU in the initialization must include the AVDT media header size.
+  int result = ldac_init_handle_encode_func(
+      a2dp_ldac_encoder_cb.ldac_handle,
+      a2dp_ldac_encoder_cb.TxAaMtuSize + AVDT_MEDIA_HDR_SIZE,
+      p_encoder_params->quality_mode_index, p_encoder_params->channel_mode,
+      p_encoder_params->pcm_fmt, p_encoder_params->sample_rate);
+  if (result != 0) {
+    LOG_ERROR(LOG_TAG, "%s: error initializing the LDAC encoder: %d", __func__,
+              result);
+  }
+}
+
+void a2dp_vendor_ldac_feeding_reset(void) {
+  /* By default, just clear the entire state */
+  memset(&a2dp_ldac_encoder_cb.ldac_feeding_state, 0,
+         sizeof(a2dp_ldac_encoder_cb.ldac_feeding_state));
+
+  a2dp_ldac_encoder_cb.ldac_feeding_state.bytes_per_tick =
+      (a2dp_ldac_encoder_cb.feeding_params.sample_rate *
+       a2dp_ldac_encoder_cb.feeding_params.bits_per_sample / 8 *
+       a2dp_ldac_encoder_cb.feeding_params.channel_count *
+       A2DP_LDAC_ENCODER_INTERVAL_MS) /
+      1000;
+
+  LOG_DEBUG(LOG_TAG, "%s: PCM bytes per tick %u", __func__,
+            a2dp_ldac_encoder_cb.ldac_feeding_state.bytes_per_tick);
+}
+
+void a2dp_vendor_ldac_feeding_flush(void) {
+  a2dp_ldac_encoder_cb.ldac_feeding_state.counter = 0;
+}
+
+period_ms_t a2dp_vendor_ldac_get_encoder_interval_ms(void) {
+  return A2DP_LDAC_ENCODER_INTERVAL_MS;
+}
+
+void a2dp_vendor_ldac_send_frames(uint64_t timestamp_us) {
+  uint8_t nb_frame = 0;
+  uint8_t nb_iterations = 0;
+
+  a2dp_ldac_get_num_frame_iteration(&nb_iterations, &nb_frame, timestamp_us);
+  LOG_VERBOSE(LOG_TAG, "%s: Sending %d frames per iteration, %d iterations",
+              __func__, nb_frame, nb_iterations);
+  if (nb_frame == 0) return;
+
+  for (uint8_t counter = 0; counter < nb_iterations; counter++) {
+    // Transcode frame and enqueue
+    a2dp_ldac_encode_frames(nb_frame);
+  }
+}
+
+// Obtains the number of frames to send and number of iterations
+// to be used. |num_of_iterations| and |num_of_frames| parameters
+// are used as output param for returning the respective values.
+static void a2dp_ldac_get_num_frame_iteration(uint8_t* num_of_iterations,
+                                              uint8_t* num_of_frames,
+                                              uint64_t timestamp_us) {
+  uint32_t result = 0;
+  uint8_t nof = 0;
+  uint8_t noi = 1;
+
+  uint32_t pcm_bytes_per_frame =
+      A2DP_LDAC_MEDIA_BYTES_PER_FRAME *
+      a2dp_ldac_encoder_cb.feeding_params.channel_count *
+      a2dp_ldac_encoder_cb.feeding_params.bits_per_sample / 8;
+  LOG_VERBOSE(LOG_TAG, "%s: pcm_bytes_per_frame %u", __func__,
+              pcm_bytes_per_frame);
+
+  uint32_t us_this_tick = A2DP_LDAC_ENCODER_INTERVAL_MS * 1000;
+  uint64_t now_us = timestamp_us;
+  if (a2dp_ldac_encoder_cb.ldac_feeding_state.last_frame_us != 0)
+    us_this_tick =
+        (now_us - a2dp_ldac_encoder_cb.ldac_feeding_state.last_frame_us);
+  a2dp_ldac_encoder_cb.ldac_feeding_state.last_frame_us = now_us;
+
+  a2dp_ldac_encoder_cb.ldac_feeding_state.counter +=
+      a2dp_ldac_encoder_cb.ldac_feeding_state.bytes_per_tick * us_this_tick /
+      (A2DP_LDAC_ENCODER_INTERVAL_MS * 1000);
+
+  result =
+      a2dp_ldac_encoder_cb.ldac_feeding_state.counter / pcm_bytes_per_frame;
+  a2dp_ldac_encoder_cb.ldac_feeding_state.counter -=
+      result * pcm_bytes_per_frame;
+  nof = result;
+
+  LOG_VERBOSE(LOG_TAG, "%s: effective num of frames %u, iterations %u",
+              __func__, nof, noi);
+
+  *num_of_frames = nof;
+  *num_of_iterations = noi;
+}
+
+static void a2dp_ldac_encode_frames(uint8_t nb_frame) {
+  tA2DP_LDAC_ENCODER_PARAMS* p_encoder_params =
+      &a2dp_ldac_encoder_cb.ldac_encoder_params;
+  uint8_t remain_nb_frame = nb_frame;
+  uint16_t ldac_frame_size;
+  uint8_t read_buffer[LDACBT_MAX_LSU * 4 /* byte/sample */ * 2 /* ch */];
+
+  switch (p_encoder_params->sample_rate) {
+    case 176400:
+    case 192000:
+      ldac_frame_size = 512;  // sample/ch
+      break;
+    case 88200:
+    case 96000:
+      ldac_frame_size = 256;  // sample/ch
+      break;
+    case 44100:
+    case 48000:
+    default:
+      ldac_frame_size = 128;  // sample/ch
+      break;
+  }
+
+  uint32_t count;
+  int32_t encode_count = 0;
+  int32_t out_frames = 0;
+  int written = 0;
+  while (nb_frame) {
+    BT_HDR* p_buf = (BT_HDR*)osi_malloc(BT_DEFAULT_BUFFER_SIZE);
+
+    /* Init buffer */
+    p_buf->offset = A2DP_LDAC_OFFSET;
+    p_buf->len = 0;
+    p_buf->layer_specific = 0;
+
+    count = 0;
+    do {
+      /* Read PCM data */
+      if (a2dp_ldac_read_feeding(read_buffer)) {
+        uint8_t* packet = (uint8_t*)(p_buf + 1) + p_buf->offset + p_buf->len;
+        if (a2dp_ldac_encoder_cb.ldac_handle == NULL) {
+          LOG_ERROR(LOG_TAG, "%s: invalid LDAC handle", __func__);
+          osi_free(p_buf);
+          return;
+        }
+        int result = ldac_encode_func(
+            a2dp_ldac_encoder_cb.ldac_handle, read_buffer, (int*)&encode_count,
+            packet + count, (int*)&written, (int*)&out_frames);
+        if (result != 0) {
+          int err_code =
+              ldac_get_error_code_func(a2dp_ldac_encoder_cb.ldac_handle);
+          LOG_ERROR(LOG_TAG,
+                    "%s: LDAC encoding error: %d api_error = %d "
+                    "handle_error = %d block_error = %d",
+                    __func__, result, LDACBT_API_ERR(err_code),
+                    LDACBT_HANDLE_ERR(err_code), LDACBT_BLOCK_ERR(err_code));
+          osi_free(p_buf);
+          return;
+        }
+        count += written;
+        p_buf->len += written;
+        nb_frame--;
+        p_buf->layer_specific += out_frames;  // added a frame to the buffer
+      } else {
+        LOG_WARN(LOG_TAG, "%s: underflow %d", __func__, nb_frame);
+        a2dp_ldac_encoder_cb.ldac_feeding_state.counter +=
+            nb_frame * LDACBT_ENC_LSU *
+            a2dp_ldac_encoder_cb.feeding_params.channel_count *
+            a2dp_ldac_encoder_cb.feeding_params.bits_per_sample / 8;
+
+        // no more pcm to read
+        nb_frame = 0;
+      }
+    } while ((written == 0) && nb_frame);
+
+    if (p_buf->len) {
+      /*
+       * Timestamp of the media packet header represent the TS of the
+       * first frame, i.e the timestamp before including this frame.
+       */
+      *((uint32_t*)(p_buf + 1)) = a2dp_ldac_encoder_cb.timestamp;
+
+      a2dp_ldac_encoder_cb.timestamp += p_buf->layer_specific * ldac_frame_size;
+
+      uint8_t done_nb_frame = remain_nb_frame - nb_frame;
+      remain_nb_frame = nb_frame;
+      if (!a2dp_ldac_encoder_cb.enqueue_callback(p_buf, done_nb_frame)) return;
+    } else {
+      osi_free(p_buf);
+    }
+  }
+}
+
+static bool a2dp_ldac_read_feeding(uint8_t* read_buffer) {
+  uint32_t read_size = LDACBT_ENC_LSU *
+                       a2dp_ldac_encoder_cb.feeding_params.channel_count *
+                       a2dp_ldac_encoder_cb.feeding_params.bits_per_sample / 8;
+
+  /* Read Data from UIPC channel */
+  uint32_t nb_byte_read =
+      a2dp_ldac_encoder_cb.read_callback(read_buffer, read_size);
+
+  if (nb_byte_read < read_size) {
+    if (nb_byte_read == 0) return false;
+
+    /* Fill the unfilled part of the read buffer with silence (0) */
+    memset(((uint8_t*)read_buffer) + nb_byte_read, 0, read_size - nb_byte_read);
+    nb_byte_read = read_size;
+  }
+  return true;
+}
+
+void a2dp_vendor_ldac_debug_codec_dump(int fd) {
+  a2dp_ldac_encoder_stats_t* stats = &a2dp_ldac_encoder_cb.stats;
+
+  dprintf(fd, "\nA2DP LDAC State:\n");
+
+  dprintf(fd,
+          "  Packets expected/dropped                                : %zu / "
+          "%zu\n",
+          stats->media_read_total_expected_packets,
+          stats->media_read_total_dropped_packets);
+
+  dprintf(fd,
+          "  PCM reads count expected/actual                         : %zu / "
+          "%zu\n",
+          stats->media_read_total_expected_reads_count,
+          stats->media_read_total_actual_reads_count);
+
+  dprintf(fd,
+          "  PCM read bytes expected/actual                          : %zu / "
+          "%zu\n",
+          stats->media_read_total_expected_read_bytes,
+          stats->media_read_total_actual_read_bytes);
+}
diff --git a/system/stack/include/a2dp_vendor_ldac.h b/system/stack/include/a2dp_vendor_ldac.h
new file mode 100644
index 00000000000..4bb71c4bd47
--- /dev/null
+++ b/system/stack/include/a2dp_vendor_ldac.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// A2DP Codec API for LDAC
+//
+
+#ifndef A2DP_VENDOR_LDAC_H
+#define A2DP_VENDOR_LDAC_H
+
+#include "a2dp_codec_api.h"
+#include "a2dp_vendor_ldac_constants.h"
+#include "avdt_api.h"
+
+class A2dpCodecConfigLdac : public A2dpCodecConfig {
+ public:
+  A2dpCodecConfigLdac();
+  virtual ~A2dpCodecConfigLdac();
+
+  bool init() override;
+  bool setCodecConfig(const uint8_t* p_peer_codec_info, bool is_capability,
+                      uint8_t* p_result_codec_config) override;
+
+ private:
+  bool updateEncoderUserConfig(
+      const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params,
+      bool* p_restart_input, bool* p_restart_output,
+      bool* p_config_updated) override;
+};
+
+// Checks whether the codec capabilities contain a valid A2DP LDAC Source
+// codec.
+// NOTE: only codecs that are implemented are considered valid.
+// Returns true if |p_codec_info| contains information about a valid LDAC
+// codec, otherwise false.
+bool A2DP_IsVendorSourceCodecValidLdac(const uint8_t* p_codec_info);
+
+// Checks whether the codec capabilities contain a valid peer A2DP LDAC Sink
+// codec.
+// NOTE: only codecs that are implemented are considered valid.
+// Returns true if |p_codec_info| contains information about a valid LDAC
+// codec, otherwise false.
+bool A2DP_IsVendorPeerSinkCodecValidLdac(const uint8_t* p_codec_info);
+
+// Checks whether the A2DP data packets should contain RTP header.
+// |content_protection_enabled| is true if Content Protection is
+// enabled. |p_codec_info| contains information about the codec capabilities.
+// Returns true if the A2DP data packets should contain RTP header, otherwise
+// false.
+bool A2DP_VendorUsesRtpHeaderLdac(bool content_protection_enabled,
+                                  const uint8_t* p_codec_info);
+
+// Gets the A2DP LDAC codec name for a given |p_codec_info|.
+const char* A2DP_VendorCodecNameLdac(const uint8_t* p_codec_info);
+
+// Checks whether two A2DP LDAC codecs |p_codec_info_a| and |p_codec_info_b|
+// have the same type.
+// Returns true if the two codecs have the same type, otherwise false.
+bool A2DP_VendorCodecTypeEqualsLdac(const uint8_t* p_codec_info_a,
+                                    const uint8_t* p_codec_info_b);
+
+// Checks whether two A2DP LDAC codecs |p_codec_info_a| and |p_codec_info_b|
+// are exactly the same.
+// Returns true if the two codecs are exactly the same, otherwise false.
+// If the codec type is not LDAC, the return value is false.
+bool A2DP_VendorCodecEqualsLdac(const uint8_t* p_codec_info_a,
+                                const uint8_t* p_codec_info_b);
+
+// Gets the track sample rate value for the A2DP LDAC codec.
+// |p_codec_info| is a pointer to the LDAC codec_info to decode.
+// Returns the track sample rate on success, or -1 if |p_codec_info|
+// contains invalid codec information.
+int A2DP_VendorGetTrackSampleRateLdac(const uint8_t* p_codec_info);
+
+// Gets the bits per audio sample for the A2DP LDAC codec.
+// |p_codec_info| is a pointer to the LDAC codec_info to decode.
+// Returns the bits per audio sample on success, or -1 if |p_codec_info|
+// contains invalid codec information.
+int A2DP_VendorGetTrackBitsPerSampleLdac(const uint8_t* p_codec_info);
+
+// Gets the channel count for the A2DP LDAC codec.
+// |p_codec_info| is a pointer to the LDAC codec_info to decode.
+// Returns the channel count on success, or -1 if |p_codec_info|
+// contains invalid codec information.
+int A2DP_VendorGetTrackChannelCountLdac(const uint8_t* p_codec_info);
+
+// Gets the channel mode code for the A2DP LDAC codec.
+// The actual value is codec-specific.
+// |p_codec_info| is a pointer to the LDAC codec_info to decode.
+// Returns the channel mode code on success, or -1 if |p_codec_info|
+// contains invalid codec information.
+int A2DP_VendorGetChannelModeCodeLdac(const uint8_t* p_codec_info);
+
+// Gets the A2DP LDAC audio data timestamp from an audio packet.
+// |p_codec_info| contains the codec information.
+// |p_data| contains the audio data.
+// The timestamp is stored in |p_timestamp|.
+// Returns true on success, otherwise false.
+bool A2DP_VendorGetPacketTimestampLdac(const uint8_t* p_codec_info,
+                                       const uint8_t* p_data,
+                                       uint32_t* p_timestamp);
+
+// Builds A2DP LDAC codec header for audio data.
+// |p_codec_info| contains the codec information.
+// |p_buf| contains the audio data.
+// |frames_per_packet| is the number of frames in this packet.
+// Returns true on success, otherwise false.
+bool A2DP_VendorBuildCodecHeaderLdac(const uint8_t* p_codec_info, BT_HDR* p_buf,
+                                     uint16_t frames_per_packet);
+
+// Decodes and displays LDAC codec info (for debugging).
+// |p_codec_info| is a pointer to the LDAC codec_info to decode and display.
+void A2DP_VendorDumpCodecInfoLdac(const uint8_t* p_codec_info);
+
+// Gets the A2DP LDAC encoder interface that can be used to encode and prepare
+// A2DP packets for transmission - see |tA2DP_ENCODER_INTERFACE|.
+// |p_codec_info| contains the codec information.
+// Returns the A2DP LDAC encoder interface if the |p_codec_info| is valid and
+// supported, otherwise NULL.
+const tA2DP_ENCODER_INTERFACE* A2DP_VendorGetEncoderInterfaceLdac(
+    const uint8_t* p_codec_info);
+
+// Adjusts the A2DP LDAC codec, based on local support and Bluetooth
+// specification.
+// |p_codec_info| contains the codec information to adjust.
+// Returns true if |p_codec_info| is valid and supported, otherwise false.
+bool A2DP_VendorAdjustCodecLdac(uint8_t* p_codec_info);
+
+// Gets the A2DP LDAC Source codec index for a given |p_codec_info|.
+// Returns the corresponding |btav_a2dp_codec_index_t| on success,
+// otherwise |BTAV_A2DP_CODEC_INDEX_MAX|.
+btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndexLdac(
+    const uint8_t* p_codec_info);
+
+// Gets the A2DP LDAC Source codec name.
+const char* A2DP_VendorCodecIndexStrLdac(void);
+
+// Initializes A2DP LDAC Source codec information into |tAVDT_CFG|
+// configuration entry pointed by |p_cfg|.
+bool A2DP_VendorInitCodecConfigLdac(tAVDT_CFG* p_cfg);
+
+#endif  // A2DP_VENDOR_LDAC_H
diff --git a/system/stack/include/a2dp_vendor_ldac_constants.h b/system/stack/include/a2dp_vendor_ldac_constants.h
new file mode 100644
index 00000000000..7d041fb1457
--- /dev/null
+++ b/system/stack/include/a2dp_vendor_ldac_constants.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// A2DP constants for LDAC codec
+//
+
+#ifndef A2DP_VENDOR_LDAC_CONSTANTS_H
+#define A2DP_VENDOR_LDAC_CONSTANTS_H
+
+// Length of the LDAC Media Payload header
+#define A2DP_LDAC_MPL_HDR_LEN 1
+
+// LDAC Media Payload Header
+#define A2DP_LDAC_HDR_F_MSK 0x80
+#define A2DP_LDAC_HDR_S_MSK 0x40
+#define A2DP_LDAC_HDR_L_MSK 0x20
+#define A2DP_LDAC_HDR_NUM_MSK 0x0F
+
+// LDAC codec specific settings
+#define A2DP_LDAC_CODEC_LEN 10
+// [Octet 0-3] Vendor ID
+#define A2DP_LDAC_VENDOR_ID 0x0000012D
+// [Octet 4-5] Vendor Specific Codec ID
+#define A2DP_LDAC_CODEC_ID 0x00AA
+// [Octet 6], [Bits 0-5] Sampling Frequency
+#define A2DP_LDAC_SAMPLING_FREQ_MASK 0x3F
+#define A2DP_LDAC_SAMPLING_FREQ_44100 0x20
+#define A2DP_LDAC_SAMPLING_FREQ_48000 0x10
+#define A2DP_LDAC_SAMPLING_FREQ_88200 0x08
+#define A2DP_LDAC_SAMPLING_FREQ_96000 0x04
+#define A2DP_LDAC_SAMPLING_FREQ_176400 0x02
+#define A2DP_LDAC_SAMPLING_FREQ_192000 0x01
+// [Octet 7], [Bits 0-2] Channel Mode
+#define A2DP_LDAC_CHANNEL_MODE_MASK 0x07
+#define A2DP_LDAC_CHANNEL_MODE_MONO 0x04
+#define A2DP_LDAC_CHANNEL_MODE_DUAL 0x02
+#define A2DP_LDAC_CHANNEL_MODE_STEREO 0x01
+
+#endif  // A2DP_VENDOR_LDAC_CONSTANTS_H
diff --git a/system/stack/include/a2dp_vendor_ldac_encoder.h b/system/stack/include/a2dp_vendor_ldac_encoder.h
new file mode 100644
index 00000000000..393ba1b174a
--- /dev/null
+++ b/system/stack/include/a2dp_vendor_ldac_encoder.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Interface to the A2DP LDAC Encoder
+//
+
+#ifndef A2DP_VENDOR_LDAC_ENCODER_H
+#define A2DP_VENDOR_LDAC_ENCODER_H
+
+#include "a2dp_codec_api.h"
+#include "osi/include/time.h"
+
+// Loads the A2DP LDAC encoder.
+// Return true on success, otherwise false.
+bool A2DP_VendorLoadEncoderLdac(void);
+
+// Unloads the A2DP LDAC encoder.
+void A2DP_VendorUnloadEncoderLdac(void);
+
+// Initialize the A2DP LDAC encoder.
+// |p_peer_params| contains the A2DP peer information
+// The current A2DP codec config is in |a2dp_codec_config|.
+// |read_callback| is the callback for reading the input audio data.
+// |enqueue_callback| is the callback for enqueueing the encoded audio data.
+void a2dp_vendor_ldac_encoder_init(
+    const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params,
+    A2dpCodecConfig* a2dp_codec_config,
+    a2dp_source_read_callback_t read_callback,
+    a2dp_source_enqueue_callback_t enqueue_callback);
+
+// Cleanup the A2DP LDAC encoder.
+void a2dp_vendor_ldac_encoder_cleanup(void);
+
+// Initialize the feeding for the A2DP LDAC encoder.
+// The feeding initialization parameters are in |p_feeding_params|.
+void a2dp_vendor_ldac_feeding_init(
+    const tA2DP_FEEDING_PARAMS* p_feeding_params);
+
+// Reset the feeding for the A2DP LDAC encoder.
+void a2dp_vendor_ldac_feeding_reset(void);
+
+// Flush the feeding for the A2DP LDAC encoder.
+void a2dp_vendor_ldac_feeding_flush(void);
+
+// Get the A2DP LDAC encoder interval (in milliseconds).
+period_ms_t a2dp_vendor_ldac_get_encoder_interval_ms(void);
+
+// Prepare and send A2DP LDAC encoded frames.
+// |timestamp_us| is the current timestamp (in microseconds).
+void a2dp_vendor_ldac_send_frames(uint64_t timestamp_us);
+
+// Dump LDAC codec-related statistics.
+// |fd| is the file descriptor to use to dump the statistics information
+// in user-friendly test format.
+void a2dp_vendor_ldac_debug_codec_dump(int fd);
+
+#endif  // A2DP_VENDOR_LDAC_ENCODER_H
-- 
GitLab