Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cff48f93 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski Committed by Łukasz Rymanowski
Browse files

LeAudio: Introduce codec wrapper interface

This extracts the encoding logic from client.cc,
for better code separation and maintainance.

Bug: 295972694
Bug: 262481440
Bug: 262481609
Test: atest bluetooth_le_audio_client_test bluetooth_le_audio_test bluetooth_test_broadcaster_state_machine bluetooth_test_broadcaster
Change-Id: Iee2d1a53c0a0955a8ff7bdd0e9e705ad17159cd9
parent 4b8c7779
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -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: {
+27 −52
Original line number Diff line number Diff line
@@ -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);
      /* TODO: We should act smart and reuse current configurations */
      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;
        }

      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_.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 {
+1 −4
Original line number Diff line number Diff line
@@ -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) {
+116 −245

File changed.

Preview size limit exceeded, changes collapsed.

+296 −0
Original line number Diff line number Diff line
/******************************************************************************
 *
 * 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
Loading