diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index c03ba8c264e6f59e41a4104ffd2463399b9b2b18..83009268d4ee89dac8137af82a587c6a1f5aeb7b 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -102,6 +102,7 @@ cc_library_static {
         "le_audio/broadcaster/state_machine.cc",
         "le_audio/client.cc",
         "le_audio/client_parser.cc",
+        "le_audio/codec_interface.cc",
         "le_audio/codec_manager.cc",
         "le_audio/content_control_id_keeper.cc",
         "le_audio/devices.cc",
@@ -697,6 +698,7 @@ cc_test {
         "le_audio/le_audio_types.cc",
         "le_audio/le_audio_types_test.cc",
         "le_audio/metrics_collector_linux.cc",
+        "le_audio/mock_codec_interface.cc",
         "le_audio/mock_codec_manager.cc",
         "le_audio/mock_iso_manager.cc",
         "le_audio/state_machine.cc",
@@ -775,6 +777,7 @@ cc_test {
         "le_audio/le_audio_utils.cc",
         "le_audio/metrics_collector.cc",
         "le_audio/metrics_collector_test.cc",
+        "le_audio/mock_codec_interface.cc",
         "le_audio/mock_codec_manager.cc",
         "le_audio/mock_iso_manager.cc",
         "le_audio/mock_state_machine.cc",
@@ -805,7 +808,6 @@ cc_test {
         "libevent",
         "libflatbuffers-cpp",
         "libgmock",
-        "liblc3",
         "libosi",
     ],
     data: [
@@ -907,6 +909,7 @@ cc_test {
         "le_audio/broadcaster/state_machine.cc",
         "le_audio/broadcaster/state_machine_test.cc",
         "le_audio/le_audio_types.cc",
+        "le_audio/mock_codec_interface.cc",
         "le_audio/mock_codec_manager.cc",
         "le_audio/mock_iso_manager.cc",
     ],
@@ -919,7 +922,6 @@ cc_test {
         "libbt-common",
         "libchrome",
         "libgmock",
-        "liblc3",
     ],
     sanitize: {
         cfi: true,
@@ -964,6 +966,7 @@ cc_test {
         "le_audio/le_audio_types.cc",
         "le_audio/le_audio_utils.cc",
         "le_audio/metrics_collector_linux.cc",
+        "le_audio/mock_codec_interface.cc",
         "le_audio/mock_codec_manager.cc",
         "le_audio/mock_iso_manager.cc",
         "test/common/mock_controller.cc",
@@ -983,7 +986,6 @@ cc_test {
         "libchrome",
         "libevent",
         "libgmock",
-        "liblc3",
         "libosi",
     ],
     sanitize: {
diff --git a/system/bta/le_audio/broadcaster/broadcaster.cc b/system/bta/le_audio/broadcaster/broadcaster.cc
index b06fd2d6e028ebeecbadda96915b9c5daf90d9d4..19b7a14d283da09d59fb3ae4261a5321f1137776 100644
--- a/system/bta/le_audio/broadcaster/broadcaster.cc
+++ b/system/bta/le_audio/broadcaster/broadcaster.cc
@@ -22,12 +22,12 @@
 #include "bta/include/bta_le_audio_api.h"
 #include "bta/include/bta_le_audio_broadcaster_api.h"
 #include "bta/le_audio/broadcaster/state_machine.h"
+#include "bta/le_audio/codec_interface.h"
 #include "bta/le_audio/content_control_id_keeper.h"
 #include "bta/le_audio/le_audio_types.h"
 #include "bta/le_audio/le_audio_utils.h"
 #include "bta/le_audio/metrics_collector.h"
 #include "device/include/controller.h"
-#include "embdrv/lc3/include/lc3.h"
 #include "gd/common/strings.h"
 #include "internal_include/stack_config.h"
 #include "osi/include/log.h"
@@ -956,31 +956,21 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
 
     void CheckAndReconfigureEncoders() {
       auto const& codec_id = codec_wrapper_.GetLeAudioCodecId();
-      if (codec_id.coding_format != kLeAudioCodingFormatLC3) {
-        LOG_ERROR("Invalid codec ID: [%d:%d:%d]", codec_id.coding_format,
-                  codec_id.vendor_company_id, codec_id.vendor_codec_id);
-        return;
-      }
-
-      if (enc_audio_buffers_.size() != codec_wrapper_.GetNumChannels()) {
-        enc_audio_buffers_.resize(codec_wrapper_.GetNumChannels());
-      }
-
-      const int dt_us = codec_wrapper_.GetDataIntervalUs();
-      const int sr_hz = codec_wrapper_.GetSampleRate();
-      const auto encoder_bytes = lc3_encoder_size(dt_us, sr_hz);
-      const auto channel_bytes = codec_wrapper_.GetMaxSduSizePerChannel();
-
       /* TODO: We should act smart and reuse current configurations */
-      encoders_.clear();
-      encoders_mem_.clear();
-      while (encoders_.size() < codec_wrapper_.GetNumChannels()) {
-        auto& encoder_buf = enc_audio_buffers_.at(encoders_.size());
-        encoder_buf.resize(channel_bytes);
-
-        encoders_mem_.emplace_back(malloc(encoder_bytes), &std::free);
-        encoders_.emplace_back(
-            lc3_setup_encoder(dt_us, sr_hz, 0, encoders_mem_.back().get()));
+      sw_enc_.clear();
+      while (sw_enc_.size() != codec_wrapper_.GetNumChannels()) {
+        auto codec = le_audio::CodecInterface::CreateInstance(codec_id);
+
+        auto codec_status =
+            codec->InitEncoder(codec_wrapper_.GetLeAudioCodecConfiguration(),
+                               codec_wrapper_.GetLeAudioCodecConfiguration());
+        if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) {
+          LOG_ERROR("Channel %d codec setup failed with err: %d",
+                    (uint32_t)sw_enc_.size(), codec_status);
+          return;
+        }
+
+        sw_enc_.emplace_back(std::move(codec));
       }
     }
 
@@ -992,23 +982,9 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
       codec_wrapper_ = config;
     }
 
-    void encodeLc3Channel(lc3_encoder_t encoder,
-                          std::vector<uint8_t>& out_buffer,
-                          const std::vector<uint8_t>& data,
-                          int initial_channel_offset, int pitch_samples,
-                          int num_channels) {
-      auto encoder_status =
-          lc3_encode(encoder, LC3_PCM_FORMAT_S16,
-                     (int16_t*)(data.data() + initial_channel_offset),
-                     pitch_samples, out_buffer.size(), out_buffer.data());
-      if (encoder_status != 0) {
-        LOG_ERROR("Encoding error=%d", encoder_status);
-      }
-    }
-
     static void sendBroadcastData(
         const std::unique_ptr<BroadcastStateMachine>& broadcast,
-        std::vector<std::vector<uint8_t>>& encoded_channels) {
+        std::vector<std::unique_ptr<le_audio::CodecInterface>>& encoders) {
       auto const& config = broadcast->GetBigConfig();
       if (config == std::nullopt) {
         LOG_ERROR(
@@ -1019,15 +995,16 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
         return;
       }
 
-      if (config->connection_handles.size() < encoded_channels.size()) {
+      if (config->connection_handles.size() < encoders.size()) {
         LOG_ERROR("Not enough BIS'es to broadcast all channels!");
         return;
       }
 
-      for (uint8_t chan = 0; chan < encoded_channels.size(); ++chan) {
-        IsoManager::GetInstance()->SendIsoData(config->connection_handles[chan],
-                                               encoded_channels[chan].data(),
-                                               encoded_channels[chan].size());
+      for (uint8_t chan = 0; chan < encoders.size(); ++chan) {
+        IsoManager::GetInstance()->SendIsoData(
+            config->connection_handles[chan],
+            (const uint8_t*)encoders[chan]->GetDecodedSamples().data(),
+            encoders[chan]->GetDecodedSamples().size() * 2);
       }
     }
 
@@ -1042,9 +1019,9 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
 
       /* Prepare encoded data for all channels */
       for (uint8_t chan = 0; chan < num_channels; ++chan) {
-        /* TODO: Use encoder agnostic wrapper */
-        encodeLc3Channel(encoders_[chan], enc_audio_buffers_[chan], data,
-                         chan * bytes_per_sample, num_channels, num_channels);
+        auto initial_channel_offset = chan * bytes_per_sample;
+        sw_enc_[chan]->Encode(data.data() + initial_channel_offset,
+                              num_channels, codec_wrapper_.GetFrameLen());
       }
 
       /* Currently there is no way to broadcast multiple distinct streams.
@@ -1056,7 +1033,7 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
         if ((broadcast->GetState() ==
              BroadcastStateMachine::State::STREAMING) &&
             !broadcast->IsMuted())
-          sendBroadcastData(broadcast, enc_audio_buffers_);
+          sendBroadcastData(broadcast, sw_enc_);
       }
       LOG_VERBOSE("All data sent.");
     }
@@ -1103,9 +1080,7 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
 
    private:
     BroadcastCodecWrapper codec_wrapper_;
-    std::vector<lc3_encoder_t> encoders_;
-    std::vector<std::unique_ptr<void, decltype(&std::free)>> encoders_mem_;
-    std::vector<std::vector<uint8_t>> enc_audio_buffers_;
+    std::vector<std::unique_ptr<le_audio::CodecInterface>> sw_enc_;
   } audio_receiver_;
 
   static class QueuedBroadcast {
diff --git a/system/bta/le_audio/broadcaster/broadcaster_types.cc b/system/bta/le_audio/broadcaster/broadcaster_types.cc
index e2a5dc8ca45bdd220c03425915e079cb56cae7d2..6c1dd50b5a465e2deeb38d578466177d7de463dc 100644
--- a/system/bta/le_audio/broadcaster/broadcaster_types.cc
+++ b/system/bta/le_audio/broadcaster/broadcaster_types.cc
@@ -22,7 +22,6 @@
 #include "bt_types.h"
 #include "bta_le_audio_broadcaster_api.h"
 #include "btm_ble_api_types.h"
-#include "embdrv/lc3/include/lc3.h"
 #include "internal_include/stack_config.h"
 #include "osi/include/properties.h"
 
@@ -374,10 +373,8 @@ types::LeAudioLtvMap BroadcastCodecWrapper::GetSubgroupCodecSpecData() const {
   };
 
   if (codec_id.coding_format == kLeAudioCodecIdLc3.coding_format) {
-    uint16_t bc =
-        lc3_frame_bytes(source_codec_config.data_interval_us, codec_bitrate);
     codec_spec_ltvs[codec_spec_conf::kLeAudioCodecLC3TypeOctetPerFrame] =
-        UINT16_TO_VEC_UINT8(bc);
+        UINT16_TO_VEC_UINT8(codec_frame_len);
   }
 
   if (source_codec_config.num_channels == 1) {
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index b18bfa3ef38aa0f3dc165d4268afd5217b41ed08..54d2da3a4922bf285a0145b2aebc809c38428338 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -34,12 +34,12 @@
 #include "btif_profile_storage.h"
 #include "btm_iso_api.h"
 #include "client_parser.h"
+#include "codec_interface.h"
 #include "codec_manager.h"
 #include "common/time_util.h"
 #include "content_control_id_keeper.h"
 #include "device/include/controller.h"
 #include "devices.h"
-#include "embdrv/lc3/include/lc3.h"
 #include "gatt/bta_gattc_int.h"
 #include "gd/common/strings.h"
 #include "internal_include/stack_config.h"
@@ -166,23 +166,6 @@ static void le_audio_health_status_callback(const RawAddress& addr,
                                             int group_id,
                                             LeAudioHealthBasedAction action);
 
-inline uint8_t bits_to_bytes_per_sample(uint8_t bits_per_sample) {
-  // 24 bit audio stream is sent as unpacked, each sample takes 4 bytes.
-  if (bits_per_sample == 24) return 4;
-
-  return bits_per_sample / 8;
-}
-
-inline lc3_pcm_format bits_to_lc3_bits(uint8_t bits_per_sample) {
-  if (bits_per_sample == 16) return LC3_PCM_FORMAT_S16;
-
-  if (bits_per_sample == 24) return LC3_PCM_FORMAT_S24;
-
-  LOG_ALWAYS_FATAL("Encoder/decoder don't know how to handle %d",
-                   bits_per_sample);
-  return LC3_PCM_FORMAT_S16;
-}
-
 class LeAudioClientImpl;
 LeAudioClientImpl* instance;
 std::mutex instance_mutex;
@@ -248,12 +231,6 @@ class LeAudioClientImpl : public LeAudioClient {
         in_voip_call_(false),
         current_source_codec_config({0, 0, 0, 0}),
         current_sink_codec_config({0, 0, 0, 0}),
-        lc3_encoder_left_mem(nullptr),
-        lc3_encoder_right_mem(nullptr),
-        lc3_decoder_left_mem(nullptr),
-        lc3_decoder_right_mem(nullptr),
-        lc3_decoder_left(nullptr),
-        lc3_decoder_right(nullptr),
         le_audio_source_hal_client_(nullptr),
         le_audio_sink_hal_client_(nullptr),
         close_vbc_timeout_(alarm_new("LeAudioCloseVbcTimeout")),
@@ -3061,27 +3038,12 @@ class LeAudioClientImpl : public LeAudioClient {
   void PrepareAndSendToTwoCises(
       const std::vector<uint8_t>& data,
       struct le_audio::stream_configuration* stream_conf) {
-    uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
     uint16_t left_cis_handle = 0;
     uint16_t right_cis_handle = 0;
-    uint16_t number_of_required_samples_per_channel;
-
-    int dt_us = current_source_codec_config.data_interval_us;
-    int af_hz = audio_framework_source_config.sample_rate;
-    number_of_required_samples_per_channel = lc3_frame_samples(dt_us, af_hz);
-
-    lc3_pcm_format bits_per_sample =
-        bits_to_lc3_bits(audio_framework_source_config.bits_per_sample);
-    uint8_t bytes_per_sample =
-        bits_to_bytes_per_sample(audio_framework_source_config.bits_per_sample);
-
-    for (auto [cis_handle, audio_location] : stream_conf->sink_streams) {
-      if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft)
-        left_cis_handle = cis_handle;
-      if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyRight)
-        right_cis_handle = cis_handle;
-    }
 
+    uint16_t number_of_required_samples_per_channel =
+        sw_enc_left->GetNumOfSamplesPerChannel();
+    uint8_t bytes_per_sample = sw_enc_left->GetNumOfBytesPerSample();
     if (data.size() < bytes_per_sample * 2 /* channels */ *
                           number_of_required_samples_per_channel) {
       LOG(ERROR) << __func__ << " Missing samples. Data size: " << +data.size()
@@ -3091,29 +3053,28 @@ class LeAudioClientImpl : public LeAudioClient {
       return;
     }
 
-    std::vector<uint8_t> chan_left_enc(byte_count, 0);
-    std::vector<uint8_t> chan_right_enc(byte_count, 0);
-
-    bool mono = (left_cis_handle == 0) || (right_cis_handle == 0);
+    for (auto [cis_handle, audio_location] : stream_conf->sink_streams) {
+      if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft)
+        left_cis_handle = cis_handle;
+      if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyRight)
+        right_cis_handle = cis_handle;
+    }
 
-    if (!mono) {
-      lc3_encode(lc3_encoder_left, bits_per_sample, data.data(), 2,
-                 chan_left_enc.size(), chan_left_enc.data());
-      lc3_encode(lc3_encoder_right, bits_per_sample,
-                 data.data() + bytes_per_sample, 2, chan_right_enc.size(),
-                 chan_right_enc.data());
-    } else {
+    uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
+    bool mix_to_mono = (left_cis_handle == 0) || (right_cis_handle == 0);
+    if (mix_to_mono) {
       std::vector<uint8_t> mono = mono_blend(
           data, bytes_per_sample, number_of_required_samples_per_channel);
       if (left_cis_handle) {
-        lc3_encode(lc3_encoder_left, bits_per_sample, mono.data(), 1,
-                   chan_left_enc.size(), chan_left_enc.data());
+        sw_enc_left->Encode(mono.data(), 1, byte_count);
       }
 
       if (right_cis_handle) {
-        lc3_encode(lc3_encoder_right, bits_per_sample, mono.data(), 1,
-                   chan_right_enc.size(), chan_right_enc.data());
+        sw_enc_left->Encode(mono.data(), 1, byte_count);
       }
+    } else {
+      sw_enc_left->Encode(data.data(), 2, byte_count);
+      sw_enc_right->Encode(data.data() + bytes_per_sample, 2, byte_count);
     }
 
     DLOG(INFO) << __func__ << " left_cis_handle: " << +left_cis_handle
@@ -3121,59 +3082,50 @@ class LeAudioClientImpl : public LeAudioClient {
     /* Send data to the controller */
     if (left_cis_handle)
       IsoManager::GetInstance()->SendIsoData(
-          left_cis_handle, chan_left_enc.data(), chan_left_enc.size());
+          left_cis_handle,
+          (const uint8_t*)sw_enc_left->GetDecodedSamples().data(),
+          sw_enc_left->GetDecodedSamples().size() * 2);
 
     if (right_cis_handle)
       IsoManager::GetInstance()->SendIsoData(
-          right_cis_handle, chan_right_enc.data(), chan_right_enc.size());
+          right_cis_handle,
+          (const uint8_t*)sw_enc_right->GetDecodedSamples().data(),
+          sw_enc_right->GetDecodedSamples().size() * 2);
   }
 
   void PrepareAndSendToSingleCis(
       const std::vector<uint8_t>& data,
       struct le_audio::stream_configuration* stream_conf) {
-    int num_channels = stream_conf->sink_num_of_channels;
-    uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
-    auto cis_handle = stream_conf->sink_streams.front().first;
-    uint16_t number_of_required_samples_per_channel;
-
-    int dt_us = current_source_codec_config.data_interval_us;
-    int af_hz = audio_framework_source_config.sample_rate;
-    number_of_required_samples_per_channel = lc3_frame_samples(dt_us, af_hz);
-    lc3_pcm_format bits_per_sample =
-        bits_to_lc3_bits(audio_framework_source_config.bits_per_sample);
-    uint8_t bytes_per_sample =
-        bits_to_bytes_per_sample(audio_framework_source_config.bits_per_sample);
-
-    if ((int)data.size() < (2 /* bytes per sample */ * num_channels *
+    uint16_t num_channels = stream_conf->sink_num_of_channels;
+    uint16_t cis_handle = stream_conf->sink_streams.front().first;
+
+    uint16_t number_of_required_samples_per_channel =
+        sw_enc_left->GetNumOfSamplesPerChannel();
+    uint8_t bytes_per_sample = sw_enc_left->GetNumOfBytesPerSample();
+    if ((int)data.size() < (bytes_per_sample * num_channels *
                             number_of_required_samples_per_channel)) {
       LOG(ERROR) << __func__ << "Missing samples";
       return;
     }
-    std::vector<uint8_t> chan_encoded(num_channels * byte_count, 0);
 
-    if (num_channels == 1) {
+    uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
+    bool mix_to_mono = (num_channels == 1);
+    if (mix_to_mono) {
       /* Since we always get two channels from framework, lets make it mono here
        */
       std::vector<uint8_t> mono = mono_blend(
           data, bytes_per_sample, number_of_required_samples_per_channel);
-
-      auto err = lc3_encode(lc3_encoder_left, bits_per_sample, mono.data(), 1,
-                            byte_count, chan_encoded.data());
-
-      if (err < 0) {
-        LOG(ERROR) << " error while encoding, error code: " << +err;
-      }
+      sw_enc_left->Encode(mono.data(), 1, byte_count);
     } else {
-      lc3_encode(lc3_encoder_left, bits_per_sample, (const int16_t*)data.data(),
-                 2, byte_count, chan_encoded.data());
-      lc3_encode(lc3_encoder_right, bits_per_sample,
-                 (const int16_t*)data.data() + 1, 2, byte_count,
-                 chan_encoded.data() + byte_count);
+      sw_enc_left->Encode((const uint8_t*)data.data(), 2, byte_count);
+      // Output to the left channel buffer with `byte_count` offset
+      sw_enc_right->Encode((const uint8_t*)data.data() + 2, 2, byte_count,
+                           &sw_enc_left->GetDecodedSamples(), byte_count);
     }
 
-    /* Send data to the controller */
-    IsoManager::GetInstance()->SendIsoData(cis_handle, chan_encoded.data(),
-                                           chan_encoded.size());
+    IsoManager::GetInstance()->SendIsoData(
+        cis_handle, (const uint8_t*)sw_enc_left->GetDecodedSamples().data(),
+        sw_enc_left->GetDecodedSamples().size() * 2);
   }
 
   const struct le_audio::stream_configuration* GetStreamSinkConfiguration(
@@ -3208,20 +3160,19 @@ class LeAudioClientImpl : public LeAudioClient {
       return;
     }
 
-    if (stream_conf.sink_num_of_devices == 2) {
-      PrepareAndSendToTwoCises(data, &stream_conf);
-    } else if (stream_conf.sink_streams.size() == 2) {
-      /* Streaming to one device but 2 CISes */
+    if ((stream_conf.sink_num_of_devices == 2) ||
+        (stream_conf.sink_streams.size() == 2)) {
+      /* Streaming to two devices or one device with 2 CISes */
       PrepareAndSendToTwoCises(data, &stream_conf);
     } else {
+      /* Streaming to one device and 1 CIS */
       PrepareAndSendToSingleCis(data, &stream_conf);
     }
   }
 
   void CleanCachedMicrophoneData() {
-    cached_channel_data_.clear();
     cached_channel_timestamp_ = 0;
-    cached_channel_is_left_ = false;
+    cached_channel_ = nullptr;
   }
 
   /* Handles audio data packets coming from the controller */
@@ -3239,11 +3190,10 @@ class LeAudioClientImpl : public LeAudioClient {
       return;
     }
 
-    auto stream_conf = group->stream_conf;
-
     uint16_t left_cis_handle = 0;
     uint16_t right_cis_handle = 0;
-    for (auto [cis_handle, audio_location] : stream_conf.source_streams) {
+    for (auto [cis_handle, audio_location] :
+         group->stream_conf.source_streams) {
       if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft) {
         left_cis_handle = cis_handle;
       }
@@ -3253,97 +3203,43 @@ class LeAudioClientImpl : public LeAudioClient {
       }
     }
 
-    bool is_left = true;
+    auto decoder = sw_dec_left.get();
     if (cis_conn_hdl == left_cis_handle) {
-      is_left = true;
+      decoder = sw_dec_left.get();
     } else if (cis_conn_hdl == right_cis_handle) {
-      is_left = false;
+      decoder = sw_dec_right.get();
     } else {
       LOG_ERROR("Received data for unknown handle: %04x", cis_conn_hdl);
       return;
     }
 
-    uint16_t required_for_channel_byte_count =
-        stream_conf.source_octets_per_codec_frame;
-
-    int dt_us = current_sink_codec_config.data_interval_us;
-    int af_hz = audio_framework_sink_config.sample_rate;
-    lc3_pcm_format bits_per_sample =
-        bits_to_lc3_bits(audio_framework_sink_config.bits_per_sample);
-
-    int pcm_size;
-    if (dt_us == 10000) {
-      if (af_hz == 44100)
-        pcm_size = 480;
-      else
-        pcm_size = af_hz / 100;
-    } else if (dt_us == 7500) {
-      if (af_hz == 44100)
-        pcm_size = 360;
-      else
-        pcm_size = (af_hz * 3) / 400;
-    } else {
-      LOG(ERROR) << "BAD dt_us: " << dt_us;
-      return;
-    }
-
-    std::vector<int16_t> pcm_data_decoded(pcm_size, 0);
-
-    int err = 0;
-
-    if (required_for_channel_byte_count != size) {
-      LOG(INFO) << "Insufficient data for decoding and send, required: "
-                << int(required_for_channel_byte_count)
-                << ", received: " << int(size) << ", will do PLC";
-      size = 0;
-      data = nullptr;
-    }
-
-    lc3_decoder_t decoder_to_use =
-        is_left ? lc3_decoder_left : lc3_decoder_right;
-
-    err = lc3_decode(decoder_to_use, data, size, bits_per_sample,
-                     pcm_data_decoded.data(), 1 /* pitch */);
-
-    if (err < 0) {
-      LOG(ERROR) << " bad decoding parameters: " << static_cast<int>(err);
-      return;
-    }
-
-    /* AF == Audio Framework */
-    bool af_is_stereo = (audio_framework_sink_config.num_channels == 2);
-
     if (!left_cis_handle || !right_cis_handle) {
       /* mono or just one device connected */
-      SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo,
-                        &pcm_data_decoded, nullptr);
+      decoder->Decode(data, size);
+      SendAudioDataToAF(&decoder->GetDecodedSamples());
       return;
     }
     /* both devices are connected */
 
-    if (cached_channel_timestamp_ == 0 && cached_channel_data_.empty()) {
+    if (cached_channel_ == nullptr ||
+        cached_channel_->GetDecodedSamples().empty()) {
       /* First packet received, cache it. We need both channel data to send it
        * to AF. */
-      cached_channel_data_ = pcm_data_decoded;
+      decoder->Decode(data, size);
       cached_channel_timestamp_ = timestamp;
-      cached_channel_is_left_ = is_left;
+      cached_channel_ = decoder;
       return;
     }
 
     /* We received either data for the other audio channel, or another
      * packet for same channel */
-
-    if (cached_channel_is_left_ != is_left) {
+    if (cached_channel_ != decoder) {
       /* It's data for the 2nd channel */
       if (timestamp == cached_channel_timestamp_) {
         /* Ready to mix data and send out to AF */
-        if (is_left) {
-          SendAudioDataToAF(true /* bt_got_stereo */, af_is_stereo,
-                            &cached_channel_data_, &pcm_data_decoded);
-        } else {
-          SendAudioDataToAF(true /* bt_got_stereo */, af_is_stereo,
-                            &pcm_data_decoded, &cached_channel_data_);
-        }
+        decoder->Decode(data, size);
+        SendAudioDataToAF(&sw_dec_left->GetDecodedSamples(),
+                          &sw_dec_right->GetDecodedSamples());
 
         CleanCachedMicrophoneData();
         return;
@@ -3352,18 +3248,11 @@ class LeAudioClientImpl : public LeAudioClient {
       /* 2nd Channel is in the future compared to the cached data.
        Send the cached data to AF, and keep the new channel data in cache.
        This should happen only during stream setup */
+      SendAudioDataToAF(&decoder->GetDecodedSamples());
 
-      if (cached_channel_is_left_) {
-        SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo,
-                          &cached_channel_data_, nullptr);
-      } else {
-        SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, nullptr,
-                          &cached_channel_data_);
-      }
-
-      cached_channel_data_ = pcm_data_decoded;
+      decoder->Decode(data, size);
       cached_channel_timestamp_ = timestamp;
-      cached_channel_is_left_ = is_left;
+      cached_channel_ = decoder;
       return;
     }
 
@@ -3371,25 +3260,22 @@ class LeAudioClientImpl : public LeAudioClient {
      * data */
 
     /* Send the cached data out */
-    if (cached_channel_is_left_) {
-      SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo,
-                        &cached_channel_data_, nullptr);
-    } else {
-      SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, nullptr,
-                        &cached_channel_data_);
-    }
+    SendAudioDataToAF(&decoder->GetDecodedSamples());
 
     /* Cache the data in case 2nd channel connects */
-    cached_channel_data_ = pcm_data_decoded;
+    decoder->Decode(data, size);
     cached_channel_timestamp_ = timestamp;
-    cached_channel_is_left_ = is_left;
+    cached_channel_ = decoder;
   }
 
-  void SendAudioDataToAF(bool bt_got_stereo, bool af_is_stereo,
-                         std::vector<int16_t>* left,
-                         std::vector<int16_t>* right) {
+  void SendAudioDataToAF(std::vector<int16_t>* left,
+                         std::vector<int16_t>* right = nullptr) {
     uint16_t to_write = 0;
     uint16_t written = 0;
+
+    bool af_is_stereo = (audio_framework_sink_config.num_channels == 2);
+    bool bt_got_stereo = (left != nullptr) & (right != nullptr);
+
     if (!af_is_stereo) {
       if (!bt_got_stereo) {
         std::vector<int16_t>* mono = left ? left : right;
@@ -3480,26 +3366,26 @@ class LeAudioClientImpl : public LeAudioClient {
         group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink);
     if (CodecManager::GetInstance()->GetCodecLocation() ==
         le_audio::types::CodecLocation::HOST) {
-      if (lc3_encoder_left_mem) {
+      if (sw_enc_left || sw_enc_right) {
         LOG(WARNING)
             << " The encoder instance should have been already released.";
-        free(lc3_encoder_left_mem);
-        lc3_encoder_left_mem = nullptr;
-        free(lc3_encoder_right_mem);
-        lc3_encoder_right_mem = nullptr;
       }
-      int dt_us = current_source_codec_config.data_interval_us;
-      int sr_hz = current_source_codec_config.sample_rate;
-      int af_hz = audio_framework_source_config.sample_rate;
-      unsigned enc_size = lc3_encoder_size(dt_us, af_hz);
-
-      lc3_encoder_left_mem = malloc(enc_size);
-      lc3_encoder_right_mem = malloc(enc_size);
+      sw_enc_left = le_audio::CodecInterface::CreateInstance(stream_conf->id);
+      auto codec_status = sw_enc_left->InitEncoder(
+          audio_framework_source_config, current_source_codec_config);
+      if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) {
+        LOG_ERROR("Left channel codec setup failed with err: %d", codec_status);
+        return false;
+      }
 
-      lc3_encoder_left =
-          lc3_setup_encoder(dt_us, sr_hz, af_hz, lc3_encoder_left_mem);
-      lc3_encoder_right =
-          lc3_setup_encoder(dt_us, sr_hz, af_hz, lc3_encoder_right_mem);
+      sw_enc_right = le_audio::CodecInterface::CreateInstance(stream_conf->id);
+      codec_status = sw_enc_right->InitEncoder(audio_framework_source_config,
+                                               current_source_codec_config);
+      if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) {
+        LOG_ERROR("Right channel codec setup failed with err: %d",
+                  codec_status);
+        return false;
+      }
     }
 
     le_audio_source_hal_client_->UpdateRemoteDelay(remote_delay_ms);
@@ -3547,26 +3433,26 @@ class LeAudioClientImpl : public LeAudioClient {
 
     if (CodecManager::GetInstance()->GetCodecLocation() ==
         le_audio::types::CodecLocation::HOST) {
-      if (lc3_decoder_left_mem) {
+      if (sw_dec_left.get() || sw_dec_right.get()) {
         LOG(WARNING)
             << " The decoder instance should have been already released.";
-        free(lc3_decoder_left_mem);
-        lc3_decoder_left_mem = nullptr;
-        free(lc3_decoder_right_mem);
-        lc3_decoder_right_mem = nullptr;
+      }
+      sw_dec_left = le_audio::CodecInterface::CreateInstance(stream_conf->id);
+      auto codec_status = sw_dec_left->InitDecoder(current_sink_codec_config,
+                                                   audio_framework_sink_config);
+      if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) {
+        LOG_ERROR("Left channel codec setup failed with err: %d", codec_status);
+        return;
       }
 
-      int dt_us = current_sink_codec_config.data_interval_us;
-      int sr_hz = current_sink_codec_config.sample_rate;
-      int af_hz = audio_framework_sink_config.sample_rate;
-      unsigned dec_size = lc3_decoder_size(dt_us, af_hz);
-      lc3_decoder_left_mem = malloc(dec_size);
-      lc3_decoder_right_mem = malloc(dec_size);
-
-      lc3_decoder_left =
-          lc3_setup_decoder(dt_us, sr_hz, af_hz, lc3_decoder_left_mem);
-      lc3_decoder_right =
-          lc3_setup_decoder(dt_us, sr_hz, af_hz, lc3_decoder_right_mem);
+      sw_dec_right = le_audio::CodecInterface::CreateInstance(stream_conf->id);
+      codec_status = sw_dec_right->InitDecoder(current_sink_codec_config,
+                                               audio_framework_sink_config);
+      if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) {
+        LOG_ERROR("Right channel codec setup failed with err: %d",
+                  codec_status);
+        return;
+      }
     }
     le_audio_sink_hal_client_->UpdateRemoteDelay(remote_delay_ms);
     ConfirmLocalAudioSinkStreamingRequest();
@@ -3584,19 +3470,11 @@ class LeAudioClientImpl : public LeAudioClient {
   void SuspendAudio(void) {
     CancelStreamingRequest();
 
-    if (lc3_encoder_left_mem) {
-      free(lc3_encoder_left_mem);
-      lc3_encoder_left_mem = nullptr;
-      free(lc3_encoder_right_mem);
-      lc3_encoder_right_mem = nullptr;
-    }
-
-    if (lc3_decoder_left_mem) {
-      free(lc3_decoder_left_mem);
-      lc3_decoder_left_mem = nullptr;
-      free(lc3_decoder_right_mem);
-      lc3_decoder_right_mem = nullptr;
-    }
+    if (sw_enc_left) sw_enc_left.reset();
+    if (sw_enc_right) sw_enc_right.reset();
+    if (sw_dec_left) sw_dec_left.reset();
+    if (sw_dec_right) sw_dec_right.reset();
+    CleanCachedMicrophoneData();
   }
 
   void StopAudio(void) { SuspendAudio(); }
@@ -5398,17 +5276,11 @@ class LeAudioClientImpl : public LeAudioClient {
       .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us,
   };
 
-  void* lc3_encoder_left_mem;
-  void* lc3_encoder_right_mem;
-
-  lc3_encoder_t lc3_encoder_left;
-  lc3_encoder_t lc3_encoder_right;
-
-  void* lc3_decoder_left_mem;
-  void* lc3_decoder_right_mem;
+  std::unique_ptr<le_audio::CodecInterface> sw_enc_left;
+  std::unique_ptr<le_audio::CodecInterface> sw_enc_right;
 
-  lc3_decoder_t lc3_decoder_left;
-  lc3_decoder_t lc3_decoder_right;
+  std::unique_ptr<le_audio::CodecInterface> sw_dec_left;
+  std::unique_ptr<le_audio::CodecInterface> sw_dec_right;
 
   std::vector<uint8_t> encoded_data;
   std::unique_ptr<LeAudioSourceAudioHalClient> le_audio_source_hal_client_;
@@ -5422,9 +5294,8 @@ class LeAudioClientImpl : public LeAudioClient {
   alarm_t* disable_timer_;
   static constexpr uint64_t kDeviceAttachDelayMs = 500;
 
-  std::vector<int16_t> cached_channel_data_;
   uint32_t cached_channel_timestamp_ = 0;
-  uint32_t cached_channel_is_left_;
+  le_audio::CodecInterface* cached_channel_ = nullptr;
 
   base::WeakPtrFactory<LeAudioClientImpl> weak_factory_{this};
 
diff --git a/system/bta/le_audio/codec_interface.cc b/system/bta/le_audio/codec_interface.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ad49c3aa7dc125c90aad23958243a7410240a4fb
--- /dev/null
+++ b/system/bta/le_audio/codec_interface.cc
@@ -0,0 +1,296 @@
+/******************************************************************************
+ *
+ * Copyright (c) 2023 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.
+ *
+ ******************************************************************************/
+
+#include "codec_interface.h"
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "embdrv/lc3/include/lc3.h"
+#include "osi/include/log.h"
+
+namespace le_audio {
+
+struct CodecInterface::Impl {
+  Impl(const types::LeAudioCodecId& codec_id) : codec_id_(codec_id) {}
+  ~Impl() { Cleanup(); }
+
+  bool IsReady() { return pcm_config_.has_value(); };
+
+  CodecInterface::Status InitEncoder(
+      const LeAudioCodecConfiguration& pcm_config,
+      const LeAudioCodecConfiguration& codec_config) {
+    // Output codec configuration
+    bt_codec_config_ = codec_config;
+
+    // TODO: For now only blocks_per_sdu = 1 is supported
+    if (codec_id_.coding_format == types::kLeAudioCodingFormatLC3) {
+      if (pcm_config_.has_value()) {
+        Cleanup();
+      }
+      pcm_config_ = pcm_config;
+
+      lc3_.pcm_format_ = (pcm_config_->bits_per_sample == 24)
+                             ? LC3_PCM_FORMAT_S24
+                             : LC3_PCM_FORMAT_S16;
+
+      // Prepare the encoder
+      const auto encoder_size = lc3_encoder_size(
+          bt_codec_config_.data_interval_us, pcm_config_->sample_rate);
+      lc3_.codec_mem_.reset(malloc(encoder_size));
+      lc3_.encoder_ = lc3_setup_encoder(
+          bt_codec_config_.data_interval_us, bt_codec_config_.sample_rate,
+          pcm_config_->sample_rate, lc3_.codec_mem_.get());
+
+      return Status::STATUS_OK;
+    }
+
+    LOG_ERROR("Invalid codec ID: [%d:%d:%d]", codec_id_.coding_format,
+              codec_id_.vendor_company_id, codec_id_.vendor_codec_id);
+    return Status::STATUS_ERR_INVALID_CODEC_ID;
+  }
+
+  CodecInterface::Status InitDecoder(
+      const LeAudioCodecConfiguration& codec_config,
+      const LeAudioCodecConfiguration& pcm_config) {
+    // Input codec configuration
+    bt_codec_config_ = codec_config;
+
+    // TODO: For now only blocks_per_sdu = 1 is supported
+    if (codec_id_.coding_format == types::kLeAudioCodingFormatLC3) {
+      if (pcm_config_.has_value()) {
+        Cleanup();
+      }
+      pcm_config_ = pcm_config;
+
+      lc3_.pcm_format_ = (pcm_config_->bits_per_sample == 24)
+                             ? LC3_PCM_FORMAT_S24
+                             : LC3_PCM_FORMAT_S16;
+
+      // Prepare the decoded output buffer
+      output_channel_samples_ = lc3_frame_samples(
+          bt_codec_config_.data_interval_us, pcm_config_->sample_rate);
+      adjustOutputBufferSizeIfNeeded(&output_channel_data_);
+
+      // Prepare the decoder
+      const auto decoder_size = lc3_decoder_size(
+          bt_codec_config_.data_interval_us, pcm_config_->sample_rate);
+      lc3_.codec_mem_.reset(malloc(decoder_size));
+      lc3_.decoder_ = lc3_setup_decoder(
+          bt_codec_config_.data_interval_us, bt_codec_config_.sample_rate,
+          pcm_config_->sample_rate, lc3_.codec_mem_.get());
+
+      return Status::STATUS_OK;
+    }
+
+    LOG_ERROR("Invalid codec ID: [%d:%d:%d]", codec_id_.coding_format,
+              codec_id_.vendor_company_id, codec_id_.vendor_codec_id);
+    return Status::STATUS_ERR_INVALID_CODEC_ID;
+  }
+
+  std::vector<int16_t>& GetDecodedSamples() { return output_channel_data_; }
+  CodecInterface::Status Decode(uint8_t* data, uint16_t size) {
+    if (!IsReady()) {
+      LOG_ERROR("decoder not ready");
+      return Status::STATUS_ERR_CODEC_NOT_READY;
+    }
+
+    // For now only LC3 is supported
+    if (codec_id_.coding_format == types::kLeAudioCodingFormatLC3) {
+      adjustOutputBufferSizeIfNeeded(&output_channel_data_);
+      auto err = lc3_decode(lc3_.decoder_, data, size, lc3_.pcm_format_,
+                            output_channel_data_.data(), 1 /* stride */);
+      if (err < 0) {
+        LOG(ERROR) << " bad decoding parameters: " << static_cast<int>(err);
+        return Status::STATUS_ERR_CODING_ERROR;
+      }
+
+      return Status::STATUS_OK;
+    }
+
+    LOG_ERROR("Invalid codec ID: [%d:%d:%d]", codec_id_.coding_format,
+              codec_id_.vendor_company_id, codec_id_.vendor_codec_id);
+    return Status::STATUS_ERR_INVALID_CODEC_ID;
+  }
+
+  CodecInterface::Status Encode(const uint8_t* data, int stride,
+                                uint16_t out_size,
+                                std::vector<int16_t>* out_buffer = nullptr,
+                                uint16_t out_offset = 0) {
+    if (!IsReady()) {
+      LOG_ERROR("decoder not ready");
+      return Status::STATUS_ERR_CODEC_NOT_READY;
+    }
+
+    if (out_size == 0) {
+      LOG_ERROR("out_size cannot be 0");
+      return Status::STATUS_ERR_CODING_ERROR;
+    }
+
+    // For now only LC3 is supported
+    if (codec_id_.coding_format == types::kLeAudioCodingFormatLC3) {
+      // Prepare the encoded output buffer
+      if (out_buffer == nullptr) {
+        out_buffer = &output_channel_data_;
+      }
+
+      // We have two bytes per sample in the buffer, while out_size and
+      // out_offset are in bytes
+      size_t channel_samples = (out_offset + out_size) / 2;
+      if (output_channel_samples_ < channel_samples) {
+        output_channel_samples_ = channel_samples;
+      }
+      adjustOutputBufferSizeIfNeeded(out_buffer);
+
+      // Encode
+      auto err =
+          lc3_encode(lc3_.encoder_, lc3_.pcm_format_, data, stride, out_size,
+                     ((uint8_t*)out_buffer->data()) + out_offset);
+      if (err < 0) {
+        LOG(ERROR) << " bad encoding parameters: " << static_cast<int>(err);
+        return Status::STATUS_ERR_CODING_ERROR;
+      }
+
+      return Status::STATUS_OK;
+    }
+
+    LOG_ERROR("Invalid codec ID: [%d:%d:%d]", codec_id_.coding_format,
+              codec_id_.vendor_company_id, codec_id_.vendor_codec_id);
+    return Status::STATUS_ERR_INVALID_CODEC_ID;
+  }
+
+  void Cleanup() {
+    pcm_config_ = std::nullopt;
+    if (codec_id_.coding_format == types::kLeAudioCodingFormatLC3) {
+      lc3_.Cleanup();
+    }
+    output_channel_data_.clear();
+    output_channel_samples_ = 0;
+  }
+
+  uint16_t GetNumOfSamplesPerChannel() {
+    if (!IsReady()) {
+      LOG_ERROR("decoder not ready");
+      return 0;
+    }
+
+    if (codec_id_.coding_format == types::kLeAudioCodingFormatLC3) {
+      return lc3_frame_samples(bt_codec_config_.data_interval_us,
+                               pcm_config_->sample_rate);
+    }
+
+    LOG_ERROR("Invalid codec ID: [%d:%d:%d]", codec_id_.coding_format,
+              codec_id_.vendor_company_id, codec_id_.vendor_codec_id);
+    return 0;
+  }
+
+  uint8_t GetNumOfBytesPerSample() {
+    if (codec_id_.coding_format == types::kLeAudioCodingFormatLC3) {
+      return lc3_.bits_to_bytes_per_sample(bt_codec_config_.bits_per_sample);
+    }
+
+    LOG_ERROR("Invalid codec ID: [%d:%d:%d]", codec_id_.coding_format,
+              codec_id_.vendor_company_id, codec_id_.vendor_codec_id);
+    return 0;
+  }
+
+ private:
+  inline void adjustOutputBufferSizeIfNeeded(std::vector<int16_t>* out_buffer) {
+    if (out_buffer->size() < output_channel_samples_) {
+      out_buffer->resize(output_channel_samples_);
+    }
+  }
+
+  // BT codec params set when codec is initialized
+  types::LeAudioCodecId codec_id_;
+  LeAudioCodecConfiguration bt_codec_config_;
+  std::optional<LeAudioCodecConfiguration> pcm_config_;
+
+  // Output buffer
+  std::vector<int16_t> output_channel_data_;
+  size_t output_channel_samples_ = 0;
+
+  // LC3
+  struct lc3_t {
+    static inline uint8_t bits_to_bytes_per_sample(uint8_t bits_per_sample) {
+      // 24 bit audio stream is sent as unpacked, each sample takes 4 bytes.
+      if (bits_per_sample == 24) return 4;
+      return bits_per_sample / 8;
+    }
+
+    void Cleanup() {
+      decoder_ = nullptr;
+      encoder_ = nullptr;
+      codec_mem_.reset();
+    }
+
+    lc3_t() : codec_mem_(nullptr, &std::free) {}
+    lc3_pcm_format pcm_format_;
+    union {
+      lc3_decoder_t decoder_;
+      lc3_encoder_t encoder_;
+    };
+    std::unique_ptr<void, decltype(&std::free)> codec_mem_;
+  } lc3_;
+};
+
+CodecInterface::CodecInterface(const types::LeAudioCodecId& codec_id) {
+  if (codec_id.coding_format == types::kLeAudioCodingFormatLC3) {
+    impl = new Impl(codec_id);
+  } else {
+    LOG_ERROR("Invalid codec ID: [%d:%d:%d]", codec_id.coding_format,
+              codec_id.vendor_company_id, codec_id.vendor_codec_id);
+  }
+}
+
+CodecInterface::~CodecInterface() { delete impl; }
+
+bool CodecInterface::IsReady() { return impl->IsReady(); };
+CodecInterface::Status CodecInterface::InitEncoder(
+    const LeAudioCodecConfiguration& pcm_config,
+    const LeAudioCodecConfiguration& codec_config) {
+  return impl->InitEncoder(pcm_config, codec_config);
+}
+CodecInterface::Status CodecInterface::InitDecoder(
+    const LeAudioCodecConfiguration& codec_config,
+    const LeAudioCodecConfiguration& pcm_config) {
+  return impl->InitDecoder(codec_config, pcm_config);
+}
+std::vector<int16_t>& CodecInterface::GetDecodedSamples() {
+  return impl->GetDecodedSamples();
+}
+CodecInterface::Status CodecInterface::Decode(uint8_t* data, uint16_t size) {
+  return impl->Decode(data, size);
+}
+CodecInterface::Status CodecInterface::Encode(const uint8_t* data, int stride,
+                                              uint16_t out_size,
+                                              std::vector<int16_t>* out_buffer,
+                                              uint16_t out_offset) {
+  return impl->Encode(data, stride, out_size, out_buffer, out_offset);
+}
+void CodecInterface::Cleanup() { return impl->Cleanup(); }
+
+uint16_t CodecInterface::GetNumOfSamplesPerChannel() {
+  return impl->GetNumOfSamplesPerChannel();
+};
+uint8_t CodecInterface::GetNumOfBytesPerSample() {
+  return impl->GetNumOfBytesPerSample();
+};
+
+}  // namespace le_audio
diff --git a/system/bta/le_audio/codec_interface.h b/system/bta/le_audio/codec_interface.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd2cca98c604d30eff5d6a54f02f28930a691e2c
--- /dev/null
+++ b/system/bta/le_audio/codec_interface.h
@@ -0,0 +1,73 @@
+/******************************************************************************
+ *
+ * Copyright (c) 2023 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.
+ *
+ ******************************************************************************/
+
+#pragma once
+
+#include <stdint.h>
+
+#include "audio_hal_client/audio_hal_client.h"
+#include "le_audio_types.h"
+
+namespace le_audio {
+
+/* CodecInterface provides a thin abstraction layer above the codec instance. It
+ * manages the output buffers internally and resizes them automatically when
+ * needed.
+ * Multi-channel stream encoding requires multiple CodecInterface instances, but
+ * even then it is still possible to encode the stream data into a single output
+ * buffer. Thanks to the optional parameters to the encode() method, the
+ * internal buffer of the first instance can be used as an output buffer by the
+ * second instance, as long as equal life time of both instances is guaranteed.
+ *
+ */
+class CodecInterface {
+ public:
+  enum class Status {
+    STATUS_ERR_CODEC_NOT_READY = -128,
+    STATUS_ERR_INVALID_CODEC_ID = -127,
+    STATUS_ERR_CODING_ERROR = -1,
+    STATUS_OK = 0,
+  };
+
+  CodecInterface(const types::LeAudioCodecId& codec_id);
+  virtual ~CodecInterface();
+  static std::unique_ptr<CodecInterface> CreateInstance(
+      const types::LeAudioCodecId& codec_id) {
+    return std::make_unique<CodecInterface>(codec_id);
+  }
+  virtual CodecInterface::Status InitEncoder(
+      const LeAudioCodecConfiguration& pcm_config,
+      const LeAudioCodecConfiguration& codec_config);
+  virtual CodecInterface::Status InitDecoder(
+      const LeAudioCodecConfiguration& codec_config,
+      const LeAudioCodecConfiguration& pcm_config);
+  virtual CodecInterface::Status Encode(
+      const uint8_t* data, int stride, uint16_t out_size,
+      std::vector<int16_t>* out_buffer = nullptr, uint16_t out_offset = 0);
+  virtual CodecInterface::Status Decode(uint8_t* data, uint16_t size);
+  virtual void Cleanup();
+  virtual bool IsReady();
+  virtual uint16_t GetNumOfSamplesPerChannel();
+  virtual uint8_t GetNumOfBytesPerSample();
+  virtual std::vector<int16_t>& GetDecodedSamples();
+
+ private:
+  struct Impl;
+  Impl* impl;
+};
+}  // namespace le_audio
diff --git a/system/bta/le_audio/mock_codec_interface.cc b/system/bta/le_audio/mock_codec_interface.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c16aae7b723ace89a5bb98328b55900af8af711a
--- /dev/null
+++ b/system/bta/le_audio/mock_codec_interface.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include "mock_codec_interface.h"
+
+namespace le_audio {
+
+struct CodecInterface::Impl : public MockCodecInterface {
+ public:
+  Impl(const types::LeAudioCodecId& codec_id) {
+    output_channel_data_.resize(1);
+  };
+  ~Impl() = default;
+
+  std::vector<int16_t>& GetDecodedSamples() { return output_channel_data_; }
+  std::vector<int16_t> output_channel_data_;
+};
+
+CodecInterface::CodecInterface(const types::LeAudioCodecId& codec_id) {
+  impl = new Impl(codec_id);
+}
+CodecInterface::~CodecInterface() { delete impl; }
+bool CodecInterface::IsReady() { return impl->IsReady(); };
+CodecInterface::Status CodecInterface::InitEncoder(
+    const LeAudioCodecConfiguration& pcm_config,
+    const LeAudioCodecConfiguration& codec_config) {
+  return impl->InitEncoder(pcm_config, codec_config);
+}
+CodecInterface::Status CodecInterface::InitDecoder(
+    const LeAudioCodecConfiguration& codec_config,
+    const LeAudioCodecConfiguration& pcm_config) {
+  return impl->InitDecoder(codec_config, pcm_config);
+}
+std::vector<int16_t>& CodecInterface::GetDecodedSamples() {
+  return impl->GetDecodedSamples();
+}
+CodecInterface::Status CodecInterface::Decode(uint8_t* data, uint16_t size) {
+  return impl->Decode(data, size);
+}
+CodecInterface::Status CodecInterface::Encode(const uint8_t* data, int stride,
+                                              uint16_t out_size,
+                                              std::vector<int16_t>* out_buffer,
+                                              uint16_t out_offset) {
+  return impl->Encode(data, stride, out_size, out_buffer, out_offset);
+}
+void CodecInterface::Cleanup() { return impl->Cleanup(); }
+
+uint16_t CodecInterface::GetNumOfSamplesPerChannel() {
+  return impl->GetNumOfSamplesPerChannel();
+};
+uint8_t CodecInterface::GetNumOfBytesPerSample() {
+  return impl->GetNumOfBytesPerSample();
+};
+}  // namespace le_audio
diff --git a/system/bta/le_audio/mock_codec_interface.h b/system/bta/le_audio/mock_codec_interface.h
new file mode 100644
index 0000000000000000000000000000000000000000..d2acda6f61e531fe3db74f13ca6f81d92fa1d0ff
--- /dev/null
+++ b/system/bta/le_audio/mock_codec_interface.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "codec_interface.h"
+
+class MockCodecInterface {
+ public:
+  MockCodecInterface() = default;
+  MockCodecInterface(const MockCodecInterface&) = delete;
+  MockCodecInterface& operator=(const MockCodecInterface&) = delete;
+
+  virtual ~MockCodecInterface() = default;
+
+  MOCK_METHOD((le_audio::CodecInterface::Status), InitEncoder,
+              (const le_audio::LeAudioCodecConfiguration& pcm_config,
+               const le_audio::LeAudioCodecConfiguration& codec_config));
+  MOCK_METHOD(le_audio::CodecInterface::Status, InitDecoder,
+              (const le_audio::LeAudioCodecConfiguration& codec_config,
+               const le_audio::LeAudioCodecConfiguration& pcm_config));
+  MOCK_METHOD(le_audio::CodecInterface::Status, Encode,
+              (const uint8_t* data, int stride, uint16_t out_size,
+               std::vector<int16_t>* out_buffer, uint16_t out_offset));
+  MOCK_METHOD(le_audio::CodecInterface::Status, Decode,
+              (uint8_t * data, uint16_t size));
+  MOCK_METHOD((void), Cleanup, ());
+  MOCK_METHOD((bool), IsReady, ());
+  MOCK_METHOD((uint16_t), GetNumOfSamplesPerChannel, ());
+  MOCK_METHOD((uint8_t), GetNumOfBytesPerSample, ());
+  MOCK_METHOD((std::vector<int16_t>&), GetDecodedSamples, ());
+};
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 3d0aaaa600a798dbfe07bc9cc5e49f1653ec929d..7ccedb75695eb4cafd09880967e1fffcd336ad1e 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -1165,6 +1165,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                  ? "sink"
                  : "source");
     auto* stream_conf = &group->stream_conf;
+    stream_conf->id = ase->codec_id;
+
     if (ase->direction == le_audio::types::kLeAudioDirectionSink) {
       auto iter = std::find_if(
           stream_conf->sink_streams.begin(), stream_conf->sink_streams.end(),