diff --git a/system/audio_bluetooth_hw/device_port_proxy.cc b/system/audio_bluetooth_hw/device_port_proxy.cc
index 6e7c0d6c15311656ec46b3c7170f58a19451566a..afa87426b4c3cb842e1dd2be2877da542f0337f1 100644
--- a/system/audio_bluetooth_hw/device_port_proxy.cc
+++ b/system/audio_bluetooth_hw/device_port_proxy.cc
@@ -172,6 +172,24 @@ bool BluetoothAudioPort::init_session_type(audio_devices_t device) {
                    << ")";
       session_type_ = SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH;
       break;
+    case AUDIO_DEVICE_OUT_BLE_HEADSET:
+      LOG(VERBOSE) << __func__
+                   << ": device=AUDIO_DEVICE_OUT_BLE_HEADSET (MEDIA/VOICE) ("
+                   << StringPrintf("%#x", device) << ")";
+      session_type_ = SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH;
+      break;
+    case AUDIO_DEVICE_OUT_BLE_SPEAKER:
+      LOG(VERBOSE) << __func__
+                   << ": device=AUDIO_DEVICE_OUT_BLE_SPEAKER (MEDIA) ("
+                   << StringPrintf("%#x", device) << ")";
+      session_type_ = SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH;
+      break;
+    case AUDIO_DEVICE_IN_BLE_HEADSET:
+      LOG(VERBOSE) << __func__
+                   << ": device=AUDIO_DEVICE_IN_BLE_HEADSET (VOICE) ("
+                   << StringPrintf("%#x", device) << ")";
+      session_type_ = SessionType_2_1::LE_AUDIO_SOFTWARE_DECODED_DATAPATH;
+      break;
     default:
       LOG(ERROR) << __func__ << ": unknown device=" << StringPrintf("%#x", device);
       return false;
diff --git a/system/audio_bluetooth_hw/device_port_proxy.h b/system/audio_bluetooth_hw/device_port_proxy.h
index 3541573dc3beeaee06655dcb35caf2af88598f33..9e113926fa669a98caa88b5a0f58afb373004359 100644
--- a/system/audio_bluetooth_hw/device_port_proxy.h
+++ b/system/audio_bluetooth_hw/device_port_proxy.h
@@ -69,11 +69,8 @@ class BluetoothAudioPort {
   // Called by Audio framework / HAL to stop the stream
   void Stop();
 
-  // The audio data path to the Bluetooth stack (Software encoding)
-  size_t WriteData(const void* buffer, size_t bytes) const;
-
   // Called by the Audio framework / HAL to fetch informaiton about audio frames
-  // presented to an external sink.
+  // presented to an external sink, or frames presented fror an internal sink
   bool GetPresentationPosition(uint64_t* delay_ns, uint64_t* bytes,
                                timespec* timestamp) const;
 
diff --git a/system/audio_hal_interface/Android.bp b/system/audio_hal_interface/Android.bp
index 335d12e3eca05abfeaa8b38ed3f22a97f897e8db..dafec38c94ee707b7caa7404b256252f1a4f4fc4 100644
--- a/system/audio_hal_interface/Android.bp
+++ b/system/audio_hal_interface/Android.bp
@@ -39,6 +39,7 @@ cc_library_static {
                 "client_interface.cc",
                 "codec_status.cc",
                 "hearing_aid_software_encoding.cc",
+                "le_audio_software.cc",
             ],
         },
         host: {
diff --git a/system/audio_hal_interface/client_interface.cc b/system/audio_hal_interface/client_interface.cc
index 9b7f75ca53baa823f259966237e7f4c3e0ef5556..aa7412aed2f1b10dadc2b72ba05263bc39f3fd42 100644
--- a/system/audio_hal_interface/client_interface.cc
+++ b/system/audio_hal_interface/client_interface.cc
@@ -525,7 +525,11 @@ bool BluetoothAudioClientInterface::UpdateAudioConfig_2_1(
       (transport_->GetSessionType_2_1() ==
            SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH ||
        transport_->GetSessionType_2_1() ==
-           SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH);
+           SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH ||
+       transport_->GetSessionType_2_1() ==
+           SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH ||
+       transport_->GetSessionType_2_1() ==
+           SessionType_2_1::LE_AUDIO_SOFTWARE_DECODED_DATAPATH);
   bool is_offload_session = (transport_->GetSessionType_2_1() ==
                              SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH);
   auto audio_config_discriminator = audio_config_2_1.getDiscriminator();
diff --git a/system/audio_hal_interface/client_interface_unittest.cc b/system/audio_hal_interface/client_interface_unittest.cc
index 30c09194e5cc52b210862979d36abe0916e741c3..33954c72027bd29f0667758d772dfa5f2d53600c 100644
--- a/system/audio_hal_interface/client_interface_unittest.cc
+++ b/system/audio_hal_interface/client_interface_unittest.cc
@@ -86,6 +86,15 @@ constexpr SampleRatePair kSampleRatePairs[9] = {
     {.hal_sample_rate_ = SampleRate::RATE_24000,
      .btav_sample_rate_ = BTAV_A2DP_CODEC_SAMPLE_RATE_24000}};
 
+constexpr SampleRate_2_1 kSampleRates_2_1[] = {
+    SampleRate_2_1::RATE_UNKNOWN, SampleRate_2_1::RATE_8000,
+    SampleRate_2_1::RATE_16000,   SampleRate_2_1::RATE_24000,
+    SampleRate_2_1::RATE_32000,   SampleRate_2_1::RATE_44100,
+    SampleRate_2_1::RATE_48000};
+
+constexpr uint32_t kDataIntervalUs[] = {0 /* Invalid */,
+                                        10000 /* Valid 10ms */};
+
 struct BitsPerSamplePair {
   BitsPerSample hal_bits_per_sample_;
   btav_a2dp_codec_bits_per_sample_t btav_bits_per_sample_;
@@ -796,3 +805,67 @@ TEST_F(BluetoothAudioClientInterfaceTest,
     }    // BitsPerSampple
   }      // SampleRate
 }
+
+TEST_F(BluetoothAudioClientInterfaceTest,
+       StartAndEndLeAudioEncodingSoftwareSession) {
+  test_sink_transport_ = new TestSinkTransport(
+      SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH);
+  clientif_sink_ =
+      new BluetoothAudioSinkClientInterface(test_sink_transport_, nullptr);
+  AudioConfiguration_2_1 audio_config = {};
+  PcmParameters_2_1 pcm_config = {};
+  for (auto sample_rate : kSampleRates_2_1) {
+    pcm_config.sampleRate = sample_rate;
+    for (auto bits_per_sample_pair : kBitsPerSamplePairs) {
+      pcm_config.bitsPerSample = bits_per_sample_pair.hal_bits_per_sample_;
+      for (auto channel_mode_pair : kChannelModePairs) {
+        pcm_config.channelMode = channel_mode_pair.hal_channel_mode_;
+        for (auto data_interval_us : kDataIntervalUs) {
+          pcm_config.dataIntervalUs = data_interval_us;
+          audio_config.pcmConfig(pcm_config);
+          clientif_sink_->UpdateAudioConfig_2_1(audio_config);
+          if (IsSinkSoftwarePcmParameters_2_1_Supported(pcm_config)) {
+            ASSERT_EQ(clientif_sink_->StartSession_2_1(),
+                      kClientIfReturnSuccess);
+          } else {
+            ASSERT_NE(clientif_sink_->StartSession_2_1(),
+                      kClientIfReturnSuccess);
+          }
+          ASSERT_EQ(clientif_sink_->EndSession(), kClientIfReturnSuccess);
+        }  // dataIntervalUs
+      }    // ChannelMode
+    }      // BitsPerSampple
+  }        // SampleRate
+}
+
+TEST_F(BluetoothAudioClientInterfaceTest,
+       StartAndEndLeAudioDecodedSoftwareSession) {
+  test_source_transport_ = new TestSourceTransport(
+      SessionType_2_1::LE_AUDIO_SOFTWARE_DECODED_DATAPATH);
+  clientif_source_ =
+      new BluetoothAudioSourceClientInterface(test_source_transport_, nullptr);
+  AudioConfiguration_2_1 audio_config = {};
+  PcmParameters_2_1 pcm_config = {};
+  for (auto sample_rate : kSampleRates_2_1) {
+    pcm_config.sampleRate = sample_rate;
+    for (auto bits_per_sample_pair : kBitsPerSamplePairs) {
+      pcm_config.bitsPerSample = bits_per_sample_pair.hal_bits_per_sample_;
+      for (auto channel_mode_pair : kChannelModePairs) {
+        pcm_config.channelMode = channel_mode_pair.hal_channel_mode_;
+        for (auto data_interval_us : kDataIntervalUs) {
+          pcm_config.dataIntervalUs = data_interval_us;
+          audio_config.pcmConfig(pcm_config);
+          clientif_source_->UpdateAudioConfig_2_1(audio_config);
+          if (IsSourceSoftwarePcmParameters_2_1_Supported(pcm_config)) {
+            ASSERT_EQ(clientif_source_->StartSession_2_1(),
+                      kClientIfReturnSuccess);
+          } else {
+            ASSERT_NE(clientif_source_->StartSession_2_1(),
+                      kClientIfReturnSuccess);
+          }
+          ASSERT_EQ(clientif_source_->EndSession(), kClientIfReturnSuccess);
+        }  // dataIntervalUs
+      }    // ChannelMode
+    }      // BitsPerSampple
+  }        // SampleRate
+}
diff --git a/system/audio_hal_interface/le_audio_software.cc b/system/audio_hal_interface/le_audio_software.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7abac40097219976a59971fcb200f17eb2491780
--- /dev/null
+++ b/system/audio_hal_interface/le_audio_software.cc
@@ -0,0 +1,556 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * 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 "BTAudioClientLeAudio"
+
+#include "le_audio_software.h"
+
+#include "client_interface.h"
+#include "osi/include/log.h"
+#include "osi/include/properties.h"
+
+namespace {
+
+using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
+using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
+using ::android::hardware::bluetooth::audio::V2_0::CodecType;
+using ::android::hardware::bluetooth::audio::V2_1::PcmParameters;
+using ::bluetooth::audio::BluetoothAudioCtrlAck;
+using ::bluetooth::audio::SampleRate_2_1;
+using ::bluetooth::audio::SessionType;
+using ::bluetooth::audio::SessionType_2_1;
+using ::bluetooth::audio::le_audio::LeAudioClientInterface;
+using ::bluetooth::audio::le_audio::StreamCallbacks;
+
+bluetooth::audio::BluetoothAudioSinkClientInterface*
+    le_audio_sink_hal_clientinterface = nullptr;
+bluetooth::audio::BluetoothAudioSourceClientInterface*
+    le_audio_source_hal_clientinterface = nullptr;
+
+static bool is_source_hal_enabled() {
+  return le_audio_source_hal_clientinterface != nullptr;
+}
+
+static bool is_sink_hal_enabled() {
+  return le_audio_sink_hal_clientinterface != nullptr;
+}
+
+class LeAudioTransport {
+ public:
+  LeAudioTransport(void (*flush)(void), StreamCallbacks stream_cb,
+                   PcmParameters pcm_config)
+      : flush_(std::move(flush)),
+        stream_cb_(std::move(stream_cb)),
+        remote_delay_report_ms_(0),
+        total_bytes_processed_(0),
+        data_position_({}),
+        pcm_config_(std::move(pcm_config)){};
+
+  BluetoothAudioCtrlAck StartRequest() {
+    LOG(INFO) << __func__;
+    if (stream_cb_.on_resume_(true)) {
+      return BluetoothAudioCtrlAck::SUCCESS_FINISHED;
+    }
+    return BluetoothAudioCtrlAck::FAILURE;
+  }
+
+  BluetoothAudioCtrlAck SuspendRequest() {
+    LOG(INFO) << __func__;
+    if (stream_cb_.on_suspend_()) {
+      flush_();
+      return BluetoothAudioCtrlAck::SUCCESS_FINISHED;
+    } else {
+      return BluetoothAudioCtrlAck::FAILURE;
+    }
+  }
+
+  void StopRequest() {
+    LOG(INFO) << __func__;
+    if (stream_cb_.on_suspend_()) {
+      flush_();
+    }
+  }
+
+  bool GetPresentationPosition(uint64_t* remote_delay_report_ns,
+                               uint64_t* total_bytes_processed,
+                               timespec* data_position) {
+    VLOG(2) << __func__ << ": data=" << total_bytes_processed_
+            << " byte(s), timestamp=" << data_position_.tv_sec << "."
+            << data_position_.tv_nsec
+            << "s, delay report=" << remote_delay_report_ms_ << " msec.";
+    if (remote_delay_report_ns != nullptr) {
+      *remote_delay_report_ns = remote_delay_report_ms_ * 1000000u;
+    }
+    if (total_bytes_processed != nullptr)
+      *total_bytes_processed = total_bytes_processed_;
+    if (data_position != nullptr) *data_position = data_position_;
+
+    return true;
+  }
+
+  void MetadataChanged(const source_metadata_t& source_metadata) {
+    auto track_count = source_metadata.track_count;
+    auto tracks = source_metadata.tracks;
+    LOG(INFO) << __func__ << ": " << track_count << " track(s) received";
+    while (track_count) {
+      VLOG(1) << __func__ << ": usage=" << tracks->usage
+              << ", content_type=" << tracks->content_type
+              << ", gain=" << tracks->gain;
+      --track_count;
+      ++tracks;
+    }
+  }
+
+  void ResetPresentationPosition() {
+    VLOG(2) << __func__ << ": called.";
+    remote_delay_report_ms_ = 0;
+    total_bytes_processed_ = 0;
+    data_position_ = {};
+  }
+
+  void LogBytesProcessed(size_t bytes_processed) {
+    if (bytes_processed) {
+      total_bytes_processed_ += bytes_processed;
+      clock_gettime(CLOCK_MONOTONIC, &data_position_);
+    }
+  }
+
+  void SetRemoteDelay(uint16_t delay_report_ms) {
+    LOG(INFO) << __func__ << ": delay_report=" << delay_report_ms << " msec";
+    remote_delay_report_ms_ = delay_report_ms;
+  }
+
+  const PcmParameters& LeAudioGetSelectedHalPcmConfig() { return pcm_config_; }
+
+  void LeAudioSetSelectedHalPcmConfig(SampleRate_2_1 sample_rate,
+                                      BitsPerSample bit_rate,
+                                      ChannelMode channel_mode,
+                                      uint32_t data_interval) {
+    pcm_config_.sampleRate = sample_rate;
+    pcm_config_.bitsPerSample = bit_rate;
+    pcm_config_.channelMode = channel_mode;
+    pcm_config_.dataIntervalUs = data_interval;
+  }
+
+ private:
+  void (*flush_)(void);
+  StreamCallbacks stream_cb_;
+  uint16_t remote_delay_report_ms_;
+  uint64_t total_bytes_processed_;
+  timespec data_position_;
+  PcmParameters pcm_config_;
+};
+
+static void flush_sink() {
+  if (!is_sink_hal_enabled()) return;
+
+  le_audio_sink_hal_clientinterface->FlushAudioData();
+}
+
+// Sink transport implementation for Le Audio
+class LeAudioSinkTransport
+    : public bluetooth::audio::IBluetoothSinkTransportInstance {
+ public:
+  LeAudioSinkTransport(StreamCallbacks stream_cb)
+      : IBluetoothSinkTransportInstance(
+            SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH, {}) {
+    transport_ =
+        new LeAudioTransport(flush_sink, std::move(stream_cb),
+                             {SampleRate_2_1::RATE_16000, ChannelMode::STEREO,
+                              BitsPerSample::BITS_16, 0});
+  };
+
+  ~LeAudioSinkTransport() { delete transport_; }
+
+  BluetoothAudioCtrlAck StartRequest() override {
+    return transport_->StartRequest();
+  }
+
+  BluetoothAudioCtrlAck SuspendRequest() override {
+    return transport_->SuspendRequest();
+  }
+
+  void StopRequest() override { transport_->StopRequest(); }
+
+  bool GetPresentationPosition(uint64_t* remote_delay_report_ns,
+                               uint64_t* total_bytes_read,
+                               timespec* data_position) override {
+    return transport_->GetPresentationPosition(remote_delay_report_ns,
+                                               total_bytes_read, data_position);
+  }
+
+  void MetadataChanged(const source_metadata_t& source_metadata) override {
+    transport_->MetadataChanged(source_metadata);
+  }
+
+  void ResetPresentationPosition() override {
+    transport_->ResetPresentationPosition();
+  }
+
+  void LogBytesRead(size_t bytes_read) override {
+    transport_->LogBytesProcessed(bytes_read);
+  }
+
+  void SetRemoteDelay(uint16_t delay_report_ms) {
+    transport_->SetRemoteDelay(delay_report_ms);
+  }
+
+  const PcmParameters& LeAudioGetSelectedHalPcmConfig() {
+    return transport_->LeAudioGetSelectedHalPcmConfig();
+  }
+
+  void LeAudioSetSelectedHalPcmConfig(SampleRate_2_1 sample_rate,
+                                      BitsPerSample bit_rate,
+                                      ChannelMode channel_mode,
+                                      uint32_t data_interval) {
+    transport_->LeAudioSetSelectedHalPcmConfig(sample_rate, bit_rate,
+                                               channel_mode, data_interval);
+  }
+
+ private:
+  LeAudioTransport* transport_;
+};
+
+static void flush_source() {
+  if (le_audio_source_hal_clientinterface == nullptr) return;
+
+  le_audio_source_hal_clientinterface->FlushAudioData();
+}
+
+class LeAudioSourceTransport
+    : public bluetooth::audio::IBluetoothSourceTransportInstance {
+ public:
+  LeAudioSourceTransport(StreamCallbacks stream_cb)
+      : IBluetoothSourceTransportInstance(
+            SessionType_2_1::LE_AUDIO_SOFTWARE_DECODED_DATAPATH, {}) {
+    transport_ =
+        new LeAudioTransport(flush_source, std::move(stream_cb),
+                             {SampleRate_2_1::RATE_16000, ChannelMode::MONO,
+                              BitsPerSample::BITS_16, 0});
+  };
+
+  ~LeAudioSourceTransport() { delete transport_; }
+
+  BluetoothAudioCtrlAck StartRequest() override {
+    return transport_->StartRequest();
+  }
+
+  BluetoothAudioCtrlAck SuspendRequest() override {
+    return transport_->SuspendRequest();
+  }
+
+  void StopRequest() override { transport_->StopRequest(); }
+
+  bool GetPresentationPosition(uint64_t* remote_delay_report_ns,
+                               uint64_t* total_bytes_written,
+                               timespec* data_position) override {
+    return transport_->GetPresentationPosition(
+        remote_delay_report_ns, total_bytes_written, data_position);
+  }
+
+  void MetadataChanged(const source_metadata_t& source_metadata) override {
+    transport_->MetadataChanged(source_metadata);
+  }
+
+  void ResetPresentationPosition() override {
+    transport_->ResetPresentationPosition();
+  }
+
+  void LogBytesWritten(size_t bytes_written) override {
+    transport_->LogBytesProcessed(bytes_written);
+  }
+
+  void SetRemoteDelay(uint16_t delay_report_ms) {
+    transport_->SetRemoteDelay(delay_report_ms);
+  }
+
+  const PcmParameters& LeAudioGetSelectedHalPcmConfig() {
+    return transport_->LeAudioGetSelectedHalPcmConfig();
+  }
+
+  void LeAudioSetSelectedHalPcmConfig(SampleRate_2_1 sample_rate,
+                                      BitsPerSample bit_rate,
+                                      ChannelMode channel_mode,
+                                      uint32_t data_interval) {
+    transport_->LeAudioSetSelectedHalPcmConfig(sample_rate, bit_rate,
+                                               channel_mode, data_interval);
+  }
+
+ private:
+  LeAudioTransport* transport_;
+};
+
+// Instance of Le Audio to provide call-in APIs for Bluetooth Audio Hal
+LeAudioSinkTransport* le_audio_sink = nullptr;
+LeAudioSourceTransport* le_audio_source = nullptr;
+}  // namespace
+
+namespace bluetooth {
+namespace audio {
+namespace le_audio {
+
+LeAudioClientInterface* LeAudioClientInterface::interface = nullptr;
+LeAudioClientInterface* LeAudioClientInterface::Get() {
+  if (osi_property_get_bool(BLUETOOTH_AUDIO_HAL_PROP_DISABLED, false)) {
+    LOG(ERROR) << __func__ << ": BluetoothAudio HAL is disabled";
+    return nullptr;
+  }
+
+  if (LeAudioClientInterface::interface == nullptr)
+    LeAudioClientInterface::interface = new LeAudioClientInterface();
+
+  return LeAudioClientInterface::interface;
+}
+
+static SampleRate_2_1 le_audio_sample_rate2audio_hal(uint32_t sample_rate_2_1) {
+  switch (sample_rate_2_1) {
+    case 8000:
+      return SampleRate_2_1::RATE_8000;
+    case 16000:
+      return SampleRate_2_1::RATE_16000;
+    case 24000:
+      return SampleRate_2_1::RATE_24000;
+    case 32000:
+      return SampleRate_2_1::RATE_32000;
+    case 44100:
+      return SampleRate_2_1::RATE_44100;
+    case 48000:
+      return SampleRate_2_1::RATE_48000;
+    case 88200:
+      return SampleRate_2_1::RATE_88200;
+    case 96000:
+      return SampleRate_2_1::RATE_96000;
+    case 176400:
+      return SampleRate_2_1::RATE_176400;
+    case 192000:
+      return SampleRate_2_1::RATE_192000;
+  };
+  return SampleRate_2_1::RATE_UNKNOWN;
+}
+
+static BitsPerSample le_audio_bit_rate2audio_hal(uint8_t bits_per_sample) {
+  switch (bits_per_sample) {
+    case 16:
+      return BitsPerSample::BITS_16;
+    case 24:
+      return BitsPerSample::BITS_24;
+    case 32:
+      return BitsPerSample::BITS_32;
+  };
+  return BitsPerSample::BITS_UNKNOWN;
+}
+
+static ChannelMode le_audio_channel_mode2audio_hal(uint8_t channels_count) {
+  switch (channels_count) {
+    case 1:
+      return ChannelMode::MONO;
+    case 2:
+      return ChannelMode::STEREO;
+  }
+  return ChannelMode::UNKNOWN;
+}
+
+void LeAudioClientInterface::Sink::Cleanup() {
+  LOG(INFO) << __func__;
+  StopSession();
+  delete le_audio_sink_hal_clientinterface;
+  le_audio_sink_hal_clientinterface = nullptr;
+  delete le_audio_sink;
+  le_audio_sink = nullptr;
+}
+
+void LeAudioClientInterface::Sink::SetPcmParameters(
+    const PcmParameters& params) {
+  le_audio_sink->LeAudioSetSelectedHalPcmConfig(
+      le_audio_sample_rate2audio_hal(params.sample_rate),
+      le_audio_bit_rate2audio_hal(params.bits_per_sample),
+      le_audio_channel_mode2audio_hal(params.channels_count),
+      params.data_interval_us);
+}
+
+// Update Le Audio delay report to BluetoothAudio HAL
+void LeAudioClientInterface::Sink::SetRemoteDelay(uint16_t delay_report_ms) {
+  LOG(INFO) << __func__ << ": delay_report_ms=" << delay_report_ms << " ms";
+  le_audio_sink->SetRemoteDelay(delay_report_ms);
+}
+
+void LeAudioClientInterface::Sink::StartSession() {
+  LOG(INFO) << __func__;
+  AudioConfiguration_2_1 audio_config;
+  audio_config.pcmConfig(le_audio_sink->LeAudioGetSelectedHalPcmConfig());
+  if (!le_audio_sink_hal_clientinterface->UpdateAudioConfig_2_1(audio_config)) {
+    LOG(ERROR) << __func__ << ": cannot update audio config to HAL";
+    return;
+  }
+  le_audio_sink_hal_clientinterface->StartSession_2_1();
+}
+
+void LeAudioClientInterface::Sink::StopSession() {
+  LOG(INFO) << __func__;
+  le_audio_sink_hal_clientinterface->EndSession();
+}
+
+size_t LeAudioClientInterface::Sink::Read(uint8_t* p_buf, uint32_t len) {
+  return le_audio_sink_hal_clientinterface->ReadAudioData(p_buf, len);
+}
+
+void LeAudioClientInterface::Source::Cleanup() {
+  LOG(INFO) << __func__;
+  StopSession();
+  delete le_audio_source_hal_clientinterface;
+  le_audio_source_hal_clientinterface = nullptr;
+  delete le_audio_source;
+  le_audio_source = nullptr;
+}
+
+void LeAudioClientInterface::Source::SetPcmParameters(
+    const PcmParameters& params) {
+  le_audio_source->LeAudioSetSelectedHalPcmConfig(
+      le_audio_sample_rate2audio_hal(params.sample_rate),
+      le_audio_bit_rate2audio_hal(params.bits_per_sample),
+      le_audio_channel_mode2audio_hal(params.channels_count),
+      params.data_interval_us);
+}
+
+void LeAudioClientInterface::Source::SetRemoteDelay(uint16_t delay_report_ms) {
+  LOG(INFO) << __func__ << ": delay_report_ms=" << delay_report_ms << " ms";
+  le_audio_source->SetRemoteDelay(delay_report_ms);
+}
+
+void LeAudioClientInterface::Source::StartSession() {
+  LOG(INFO) << __func__;
+  if (!is_source_hal_enabled()) return;
+  AudioConfiguration_2_1 audio_config;
+  audio_config.pcmConfig(le_audio_source->LeAudioGetSelectedHalPcmConfig());
+  if (!le_audio_source_hal_clientinterface->UpdateAudioConfig_2_1(
+          audio_config)) {
+    LOG(ERROR) << __func__ << ": cannot update audio config to HAL";
+    return;
+  }
+  le_audio_source_hal_clientinterface->StartSession_2_1();
+}
+
+void LeAudioClientInterface::Source::StopSession() {
+  LOG(INFO) << __func__;
+  le_audio_source_hal_clientinterface->EndSession();
+}
+
+size_t LeAudioClientInterface::Source::Write(const uint8_t* p_buf,
+                                             uint32_t len) {
+  return le_audio_source_hal_clientinterface->WriteAudioData(p_buf, len);
+}
+
+LeAudioClientInterface::Sink* LeAudioClientInterface::GetSink(
+    StreamCallbacks stream_cb,
+    bluetooth::common::MessageLoopThread* message_loop) {
+  if (sink_ == nullptr) {
+    sink_ = new Sink();
+  } else {
+    LOG(WARNING) << __func__ << ", Sink is already acquired";
+    return nullptr;
+  }
+
+  LOG(INFO) << __func__;
+
+  le_audio_sink = new LeAudioSinkTransport(std::move(stream_cb));
+  le_audio_sink_hal_clientinterface =
+      new bluetooth::audio::BluetoothAudioSinkClientInterface(le_audio_sink,
+                                                              message_loop);
+  if (!le_audio_sink_hal_clientinterface->IsValid()) {
+    LOG(WARNING) << __func__
+                 << ": BluetoothAudio HAL for Le Audio is invalid?!";
+    delete le_audio_sink_hal_clientinterface;
+    le_audio_sink_hal_clientinterface = nullptr;
+    delete le_audio_sink;
+    le_audio_sink = nullptr;
+    delete sink_;
+    sink_ = nullptr;
+
+    return nullptr;
+  }
+
+  return sink_;
+}
+
+bool LeAudioClientInterface::IsSinkAcquired() { return sink_ != nullptr; }
+
+bool LeAudioClientInterface::ReleaseSink(LeAudioClientInterface::Sink* sink) {
+  if (sink != sink_) {
+    LOG(WARNING) << __func__ << ", can't release not acquired sink";
+    return false;
+  }
+
+  if (le_audio_sink_hal_clientinterface && le_audio_sink) sink->Cleanup();
+
+  delete (sink_);
+  sink_ = nullptr;
+
+  return true;
+}
+
+LeAudioClientInterface::Source* LeAudioClientInterface::GetSource(
+    StreamCallbacks stream_cb,
+    bluetooth::common::MessageLoopThread* message_loop) {
+  if (source_ == nullptr) {
+    source_ = new Source();
+  } else {
+    LOG(WARNING) << __func__ << ", Source is already acquired";
+    return nullptr;
+  }
+
+  LOG(INFO) << __func__;
+
+  le_audio_source = new LeAudioSourceTransport(std::move(stream_cb));
+  le_audio_source_hal_clientinterface =
+      new bluetooth::audio::BluetoothAudioSourceClientInterface(le_audio_source,
+                                                                message_loop);
+  if (!le_audio_source_hal_clientinterface->IsValid()) {
+    LOG(WARNING) << __func__
+                 << ": BluetoothAudio HAL for Le Audio is invalid?!";
+    delete le_audio_source_hal_clientinterface;
+    le_audio_source_hal_clientinterface = nullptr;
+    delete le_audio_source;
+    le_audio_source = nullptr;
+    delete source_;
+    source_ = nullptr;
+
+    return nullptr;
+  }
+
+  return source_;
+}
+
+bool LeAudioClientInterface::IsSourceAcquired() { return source_ != nullptr; }
+
+bool LeAudioClientInterface::ReleaseSource(
+    LeAudioClientInterface::Source* source) {
+  if (source != source_) {
+    LOG(WARNING) << __func__ << ", can't release not acquired source";
+    return false;
+  }
+
+  if (le_audio_source_hal_clientinterface && le_audio_source) source->Cleanup();
+
+  delete (source_);
+  source_ = nullptr;
+
+  return true;
+}
+
+}  // namespace le_audio
+}  // namespace audio
+}  // namespace bluetooth
diff --git a/system/audio_hal_interface/le_audio_software.h b/system/audio_hal_interface/le_audio_software.h
new file mode 100644
index 0000000000000000000000000000000000000000..51584e6f5feb32cd483e7623152f37914dff634a
--- /dev/null
+++ b/system/audio_hal_interface/le_audio_software.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2021 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 <functional>
+
+#include "common/message_loop_thread.h"
+
+namespace bluetooth {
+namespace audio {
+namespace le_audio {
+
+struct StreamCallbacks {
+  std::function<bool(bool start_media_task)> on_resume_;
+  std::function<bool(void)> on_suspend_;
+};
+
+class LeAudioClientInterface {
+ public:
+  struct PcmParameters {
+    uint32_t data_interval_us;
+    uint32_t sample_rate;
+    uint8_t bits_per_sample;
+    uint8_t channels_count;
+  };
+
+ private:
+  class IClientInterfaceEndpoint {
+   public:
+    virtual ~IClientInterfaceEndpoint() = default;
+    virtual void Cleanup() = 0;
+    virtual void SetPcmParameters(const PcmParameters& params) = 0;
+    virtual void SetRemoteDelay(uint16_t delay_report_ms) = 0;
+    virtual void StartSession() = 0;
+    virtual void StopSession() = 0;
+  };
+
+ public:
+  class Sink : public IClientInterfaceEndpoint {
+   public:
+    virtual ~Sink() = default;
+
+    void Cleanup() override;
+    void SetPcmParameters(const PcmParameters& params) override;
+    void SetRemoteDelay(uint16_t delay_report_ms) override;
+    void StartSession() override;
+    void StopSession() override;
+
+    // Read the stream of bytes sinked to us by the upper layers
+    size_t Read(uint8_t* p_buf, uint32_t len);
+  };
+  class Source : public IClientInterfaceEndpoint {
+   public:
+    virtual ~Source() = default;
+
+    void Cleanup() override;
+    void SetPcmParameters(const PcmParameters& params) override;
+    void SetRemoteDelay(uint16_t delay_report_ms) override;
+    void StartSession() override;
+    void StopSession() override;
+
+    // Source the given stream of bytes to be sinked into the upper layers
+    size_t Write(const uint8_t* p_buf, uint32_t len);
+  };
+
+  // Get LE Audio sink client interface if it's not previously acquired and not
+  // yet released.
+  Sink* GetSink(StreamCallbacks stream_cb,
+                bluetooth::common::MessageLoopThread* message_loop);
+  // This should be called before trying to get sink interface
+  bool IsSinkAcquired();
+  // Release sink interface if belongs to LE audio client interface
+  bool ReleaseSink(Sink* sink);
+
+  // Get LE Audio source client interface if it's not previously acquired and
+  // not yet released.
+  Source* GetSource(StreamCallbacks stream_cb,
+                    bluetooth::common::MessageLoopThread* message_loop);
+  // This should be called before trying to get source interface
+  bool IsSourceAcquired();
+  // Release source interface if belongs to LE audio client interface
+  bool ReleaseSource(Source* source);
+
+  // Get interface, if previously not initialized - it'll initialize singleton.
+  static LeAudioClientInterface* Get();
+
+ private:
+  static LeAudioClientInterface* interface;
+  Sink* sink_ = nullptr;
+  Source* source_ = nullptr;
+};
+
+}  // namespace le_audio
+}  // namespace audio
+}  // namespace bluetooth