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

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

leaudio: Add BAP Broadcaster implementation

This implements the single-instance Broadcaster entity
for creating, configuring and managing audio broadcasts.
Each registered broadcast is an independently controlled
state machine, which can be started, stopped, suspended or
destroyed. The implementation takes care of all the
advertising, ISO channel creation and audio data routing

Note, that currently all the system audio is trasfered over
the broadcast stream including all the sonification events
like clicks and notifications. Current audio session client
implementation is designed to work with either unicast or
broadcast, but not both at the same time.

Bug: 150670922
Tag: #feature
Test: atest BluetoothInstrumentationTests bluetooth_test_broadcaster bluetooth_test_broadcaster_sm bluetooth_le_audio_test
Sponsor: jpawlowski@
Change-Id: I4e9c95d4b6a4fe0a7911bb8702ee7f41ff500470
parent 94339d1b
Loading
Loading
Loading
Loading
+266 −2
Original line number Diff line number Diff line
@@ -27,11 +27,16 @@
#include "com_android_bluetooth.h"
#include "hardware/bt_le_audio.h"

using bluetooth::le_audio::BroadcastAudioProfile;
using bluetooth::le_audio::BroadcastId;
using bluetooth::le_audio::BroadcastState;
using bluetooth::le_audio::btle_audio_codec_config_t;
using bluetooth::le_audio::btle_audio_codec_index_t;
using bluetooth::le_audio::ConnectionState;
using bluetooth::le_audio::GroupNodeStatus;
using bluetooth::le_audio::GroupStatus;
using bluetooth::le_audio::LeAudioBroadcasterCallbacks;
using bluetooth::le_audio::LeAudioBroadcasterInterface;
using bluetooth::le_audio::LeAudioClientCallbacks;
using bluetooth::le_audio::LeAudioClientInterface;

@@ -299,7 +304,6 @@ static jboolean groupAddNodeNative(JNIEnv* env, jobject object, jint group_id,

static jboolean groupRemoveNodeNative(JNIEnv* env, jobject object,
                                      jint group_id, jbyteArray address) {

  if (!sLeAudioClientInterface) {
    LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface";
    return JNI_FALSE;
@@ -340,9 +344,269 @@ static JNINativeMethod sMethods[] = {
    {"groupSetActiveNative", "(I)V", (void*)groupSetActiveNative},
};

/* Le Audio Broadcaster */
static jmethodID method_onBroadcastCreated;
static jmethodID method_onBroadcastDestroyed;
static jmethodID method_onBroadcastStateChanged;
static jmethodID method_onBroadcastId;

static LeAudioBroadcasterInterface* sLeAudioBroadcasterInterface = nullptr;
static std::shared_timed_mutex sBroadcasterInterfaceMutex;

static jobject sBroadcasterCallbacksObj = nullptr;
static std::shared_timed_mutex sBroadcasterCallbacksMutex;

class LeAudioBroadcasterCallbacksImpl : public LeAudioBroadcasterCallbacks {
 public:
  ~LeAudioBroadcasterCallbacksImpl() = default;

  void OnBroadcastCreated(uint8_t instance_id, bool success) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex);
    CallbackEnv sCallbackEnv(__func__);

    if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return;
    sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj,
                                 method_onBroadcastCreated, (jint)instance_id,
                                 success ? JNI_TRUE : JNI_FALSE);
  }

  void OnBroadcastDestroyed(uint8_t instance_id) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex);
    CallbackEnv sCallbackEnv(__func__);

    if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return;
    sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj,
                                 method_onBroadcastDestroyed,
                                 (jint)instance_id);
  }

  void OnBroadcastStateChanged(uint8_t instance_id,
                               BroadcastState state) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex);
    CallbackEnv sCallbackEnv(__func__);

    if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return;
    sCallbackEnv->CallVoidMethod(
        sBroadcasterCallbacksObj, method_onBroadcastStateChanged,
        (jint)instance_id,
        (jint) static_cast<std::underlying_type<BroadcastState>::type>(state));
  }

  void OnBroadcastId(uint8_t instance_id,
                     const BroadcastId& broadcast_id) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex);
    CallbackEnv sCallbackEnv(__func__);

    // broadcast_id
    int field_size = broadcast_id.size();
    ScopedLocalRef<jbyteArray> serialized_broadcast_id(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(field_size));
    if (!serialized_broadcast_id.get()) {
      LOG(ERROR) << "Failed to allocate new jbyteArray broadcast_id for the "
                    "announcement";
      return;
    }

    sCallbackEnv->SetByteArrayRegion(serialized_broadcast_id.get(), 0,
                                     field_size, (jbyte*)broadcast_id.data());

    if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return;
    sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj, method_onBroadcastId,
                                 (jint)instance_id,
                                 serialized_broadcast_id.get());
  }
};

static LeAudioBroadcasterCallbacksImpl sLeAudioBroadcasterCallbacks;

static void BroadcasterClassInitNative(JNIEnv* env, jclass clazz) {
  method_onBroadcastCreated =
      env->GetMethodID(clazz, "onBroadcastCreated", "(IZ)V");
  method_onBroadcastDestroyed =
      env->GetMethodID(clazz, "onBroadcastDestroyed", "(I)V");
  method_onBroadcastStateChanged =
      env->GetMethodID(clazz, "onBroadcastStateChanged", "(II)V");
  method_onBroadcastId = env->GetMethodID(clazz, "onBroadcastId", "(I[B)V");
}

static void BroadcasterInitNative(JNIEnv* env, jobject object) {
  std::unique_lock<std::shared_timed_mutex> interface_lock(
      sBroadcasterInterfaceMutex);
  std::unique_lock<std::shared_timed_mutex> callbacks_lock(
      sBroadcasterCallbacksMutex);

  const bt_interface_t* btInf = getBluetoothInterface();
  if (btInf == nullptr) {
    LOG(ERROR) << "Bluetooth module is not loaded";
    return;
  }

  if (sBroadcasterCallbacksObj != nullptr) {
    LOG(INFO) << "Cleaning up LeAudio Broadcaster callback object";
    env->DeleteGlobalRef(sBroadcasterCallbacksObj);
    sBroadcasterCallbacksObj = nullptr;
  }

  if ((sBroadcasterCallbacksObj = env->NewGlobalRef(object)) == nullptr) {
    LOG(ERROR)
        << "Failed to allocate Global Ref for LeAudio Broadcaster Callbacks";
    return;
  }

  sLeAudioBroadcasterInterface =
      (LeAudioBroadcasterInterface*)btInf->get_profile_interface(
          BT_PROFILE_LE_AUDIO_BROADCASTER_ID);
  if (sLeAudioBroadcasterInterface == nullptr) {
    LOG(ERROR) << "Failed to get Bluetooth LeAudio Broadcaster Interface";
    return;
  }

  sLeAudioBroadcasterInterface->Initialize(&sLeAudioBroadcasterCallbacks);
}

static void BroadcasterStopNative(JNIEnv* env, jobject object) {
  std::unique_lock<std::shared_timed_mutex> interface_lock(
      sBroadcasterInterfaceMutex);

  const bt_interface_t* btInf = getBluetoothInterface();
  if (btInf == nullptr) {
    LOG(ERROR) << "Bluetooth module is not loaded";
    return;
  }

  if (sLeAudioBroadcasterInterface != nullptr)
    sLeAudioBroadcasterInterface->Stop();
}

static void BroadcasterCleanupNative(JNIEnv* env, jobject object) {
  std::unique_lock<std::shared_timed_mutex> interface_lock(
      sBroadcasterInterfaceMutex);
  std::unique_lock<std::shared_timed_mutex> callbacks_lock(
      sBroadcasterCallbacksMutex);

  const bt_interface_t* btInf = getBluetoothInterface();
  if (btInf == nullptr) {
    LOG(ERROR) << "Bluetooth module is not loaded";
    return;
  }

  if (sLeAudioBroadcasterInterface != nullptr) {
    sLeAudioBroadcasterInterface->Cleanup();
    sLeAudioBroadcasterInterface = nullptr;
  }

  if (sBroadcasterCallbacksObj != nullptr) {
    env->DeleteGlobalRef(sBroadcasterCallbacksObj);
    sBroadcasterCallbacksObj = nullptr;
  }
}

static void CreateBroadcastNative(JNIEnv* env, jobject object,
                                  jbyteArray metadata, jint audio_profile,
                                  jbyteArray broadcast_code) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex);
  if (!sLeAudioBroadcasterInterface) return;

  std::array<uint8_t, 16> code_array;
  if (broadcast_code)
    env->GetByteArrayRegion(broadcast_code, 0, 16, (jbyte*)code_array.data());

  jbyte* meta = env->GetByteArrayElements(metadata, nullptr);
  sLeAudioBroadcasterInterface->CreateBroadcast(
      std::vector<uint8_t>(meta, meta + env->GetArrayLength(metadata)),
      static_cast<BroadcastAudioProfile>(audio_profile),
      broadcast_code ? std::optional<std::array<uint8_t, 16>>(code_array)
                     : std::nullopt);
  env->ReleaseByteArrayElements(metadata, meta, 0);
}

static void UpdateMetadataNative(JNIEnv* env, jobject object, jint instance_id,
                                 jbyteArray metadata) {
  jbyte* meta = env->GetByteArrayElements(metadata, nullptr);
  sLeAudioBroadcasterInterface->UpdateMetadata(
      instance_id,
      std::vector<uint8_t>(meta, meta + env->GetArrayLength(metadata)));
  env->ReleaseByteArrayElements(metadata, meta, 0);
}

static void StartBroadcastNative(JNIEnv* env, jobject object,
                                 jint instance_id) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex);
  if (!sLeAudioBroadcasterInterface) return;
  sLeAudioBroadcasterInterface->StartBroadcast(instance_id);
}

static void StopBroadcastNative(JNIEnv* env, jobject object, jint instance_id) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex);
  if (!sLeAudioBroadcasterInterface) return;
  sLeAudioBroadcasterInterface->StopBroadcast(instance_id);
}

static void PauseBroadcastNative(JNIEnv* env, jobject object,
                                 jint instance_id) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex);
  if (!sLeAudioBroadcasterInterface) return;
  sLeAudioBroadcasterInterface->PauseBroadcast(instance_id);
}

static void DestroyBroadcastNative(JNIEnv* env, jobject object,
                                   jint instance_id) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex);
  if (!sLeAudioBroadcasterInterface) return;
  sLeAudioBroadcasterInterface->DestroyBroadcast(instance_id);
}

static void GetBroadcastIdNative(JNIEnv* env, jobject object,
                                 jint instance_id) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex);
  if (!sLeAudioBroadcasterInterface) return;
  sLeAudioBroadcasterInterface->GetBroadcastId(instance_id);
}

static void GetAllBroadcastStatesNative(JNIEnv* env, jobject object) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex);
  if (!sLeAudioBroadcasterInterface) return;
  sLeAudioBroadcasterInterface->GetAllBroadcastStates();
}

static JNINativeMethod sBroadcasterMethods[] = {
    {"classInitNative", "()V", (void*)BroadcasterClassInitNative},
    {"initNative", "()V", (void*)BroadcasterInitNative},
    {"stopNative", "()V", (void*)BroadcasterStopNative},
    {"cleanupNative", "()V", (void*)BroadcasterCleanupNative},
    {"createBroadcastNative", "([BI[B)V", (void*)CreateBroadcastNative},
    {"updateMetadataNative", "(I[B)V", (void*)UpdateMetadataNative},
    {"startBroadcastNative", "(I)V", (void*)StartBroadcastNative},
    {"stopBroadcastNative", "(I)V", (void*)StopBroadcastNative},
    {"pauseBroadcastNative", "(I)V", (void*)PauseBroadcastNative},
    {"destroyBroadcastNative", "(I)V", (void*)DestroyBroadcastNative},
    {"getBroadcastIdNative", "(I)V", (void*)GetBroadcastIdNative},
    {"getAllBroadcastStatesNative", "()V", (void*)GetAllBroadcastStatesNative},
};

int register_com_android_bluetooth_le_audio(JNIEnv* env) {
  return jniRegisterNativeMethods(
  int register_success = jniRegisterNativeMethods(
      env, "com/android/bluetooth/le_audio/LeAudioNativeInterface", sMethods,
      NELEM(sMethods));
  return register_success &
         jniRegisterNativeMethods(
             env,
             "com/android/bluetooth/le_audio/LeAudioBroadcasterNativeInterface",
             sBroadcasterMethods, NELEM(sBroadcasterMethods));
}
}  // namespace android
+260 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

/*
 * Defines the native interface that is used by state machine/service to
 * send or receive messages from the native stack. This file is registered
 * for the native methods in the corresponding JNI C++ file.
 */
package com.android.bluetooth.le_audio;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

/**
 * LeAudio Native Interface to/from JNI.
 */
public class LeAudioBroadcasterNativeInterface {
    private static final String TAG = "LeAudioBroadcasterNativeInterface";
    private static final boolean DBG = true;
    private BluetoothAdapter mAdapter;

    @GuardedBy("INSTANCE_LOCK")
    private static LeAudioBroadcasterNativeInterface sInstance;
    private static final Object INSTANCE_LOCK = new Object();

    static {
        classInitNative();
    }

    private LeAudioBroadcasterNativeInterface() {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mAdapter == null) {
            Log.wtfStack(TAG, "No Bluetooth Adapter Available");
        }
    }

    /**
     * Get singleton instance.
     */
    public static LeAudioBroadcasterNativeInterface getInstance() {
        synchronized (INSTANCE_LOCK) {
            if (sInstance == null) {
                sInstance = new LeAudioBroadcasterNativeInterface();
            }
            return sInstance;
        }
    }

    private void sendMessageToService(LeAudioStackEvent event) {
        LeAudioService service = LeAudioService.getLeAudioService();
        if (service != null) {
            service.messageFromNative(event);
        } else {
            Log.e(TAG, "Event ignored, service not available: " + event);
        }
    }

    @VisibleForTesting
    public BluetoothDevice getDevice(byte[] address) {
        return mAdapter.getRemoteDevice(address);
    }

    // Callbacks from the native stack back into the Java framework.
    @VisibleForTesting
    public void onBroadcastCreated(int instanceId, boolean success) {
        if (DBG) {
            Log.d(TAG, "onBroadcastCreated: instanceId=" + instanceId);
        }
        LeAudioStackEvent event =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED);

        event.valueInt1 = instanceId;
        event.valueBool1 = success;
        sendMessageToService(event);
    }

    @VisibleForTesting
    public void onBroadcastDestroyed(int instanceId) {
        if (DBG) {
            Log.d(TAG, "onBroadcastDestroyed: instanceId=" + instanceId);
        }
        LeAudioStackEvent event =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_DESTROYED);

        event.valueInt1 = instanceId;
        sendMessageToService(event);
    }

    @VisibleForTesting
    public void onBroadcastStateChanged(int instanceId, int state) {
        if (DBG) {
            Log.d(TAG, "onBroadcastStateChanged: instanceId=" + instanceId + " state=" + state);
        }
        LeAudioStackEvent event =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE);

        /* NOTICE: This is a fake device to satisfy Audio Manager in the upper
         * layers which needs a device instance to route audio streams to the
         * proper module (here it's Bluetooth). Broadcast has no concept of a
         * destination or peer device therefore this fake device was created.
         * For now it's only important that this device is a Bluetooth device.
         */
        event.device = getDevice(Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF"));
        event.valueInt1 = instanceId;
        event.valueInt2 = state;
        sendMessageToService(event);
    }

    @VisibleForTesting
    public void onBroadcastId(int instanceId, byte[] broadcastId) {
        LeAudioStackEvent event =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_ID);
        event.valueInt1 = instanceId;
        event.valueByte1 = broadcastId;
        if (DBG) {
            Log.d(TAG, "onBroadcastId: " + event);
        }
        sendMessageToService(event);
    }

    /**
     * Initializes the native interface.
     *
     * priorities to configure.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void init() {
        initNative();
    }

    /**
     * Stop the Broadcast Service.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void stop() {
        stopNative();
    }

    /**
     * Cleanup the native interface.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void cleanup() {
        cleanupNative();
    }

    /**
     * Creates LeAudio Broadcast instance.
     *
     * @param metadata metadata buffer with TLVs
     * @param audioProfile broadcast audio profile
     * @param broadcastCode optional code if broadcast should be encrypted
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void createBroadcast(byte[] metadata, int audioProfile, byte[] broadcastCode) {
        createBroadcastNative(metadata, audioProfile, broadcastCode);
    }

    /**
     * Update LeAudio Broadcast instance metadata.
     *
     * @param instanceId broadcast instance identifier
     * @param metadata metadata buffer with TLVs
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void updateMetadata(int instanceId, byte[] metadata) {
        updateMetadataNative(instanceId, metadata);
    }

    /**
     * Start LeAudio Broadcast instance.
     *
     * @param instanceId broadcast instance identifier
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void startBroadcast(int instanceId) {
        startBroadcastNative(instanceId);
    }

    /**
     * Stop LeAudio Broadcast instance.
     *
     * @param instanceId broadcast instance identifier
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void stopBroadcast(int instanceId) {
        stopBroadcastNative(instanceId);
    }

    /**
     * Pause LeAudio Broadcast instance.
     *
     * @param instanceId broadcast instance identifier
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void pauseBroadcast(int instanceId) {
        pauseBroadcastNative(instanceId);
    }

    /**
     * Destroy LeAudio Broadcast instance.
     *
     * @param instanceId broadcast instance identifier
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void destroyBroadcast(int instanceId) {
        destroyBroadcastNative(instanceId);
    }

    /**
     * Get LeAudio Broadcast instance advertising address.
     *
     * @param instanceId broadcast instance identifier
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void getBroadcastId(int instanceId) {
        getBroadcastIdNative(instanceId);
    }

    /**
     * Get all LeAudio Broadcast instance states.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void getAllBroadcastStates() {
        getAllBroadcastStatesNative();
    }

    // Native methods that call into the JNI interface
    private static native void classInitNative();
    private native void initNative();
    private native void stopNative();
    private native void cleanupNative();
    private native void createBroadcastNative(byte[] metadata, int profile, byte[] broadcastCode);
    private native void updateMetadataNative(int instanceId, byte[] metadata);
    private native void startBroadcastNative(int instanceId);
    private native void stopBroadcastNative(int instanceId);
    private native void pauseBroadcastNative(int instanceId);
    private native void destroyBroadcastNative(int instanceId);
    private native void getBroadcastIdNative(int instanceId);
    private native void getAllBroadcastStatesNative();
}
+217 −0

File changed.

Preview size limit exceeded, changes collapsed.

+97 −0

File changed.

Preview size limit exceeded, changes collapsed.

+244 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading