Skip to content

Conversation

@MarkWangChinese
Copy link
Contributor

The sample is based on PR #31583
The sample implement the a2dp source that can connect to one A2DP Bluetooth headset.
This is one inputting shell commands to find and connect to Bluetooth headset, it plays one 1k sin media data after connection.

@Thalley
Copy link
Contributor

Thalley commented Feb 28, 2024

@MarkWangChinese I'll review once CI is green :)

The sample implement the a2dp source that canconnect to
one A2DP Bluetooth headset. This are shell commands to
find and connect to Bluetooth headset in this sample,
it plays one 1k sin media data after connection.

Signed-off-by: Mark Wang <[email protected]>
@hermabe hermabe removed their request for review February 28, 2024 10:20
@MarkWangChinese
Copy link
Contributor Author

@MarkWangChinese I'll review once CI is green :)

Hi Emil, the current CI issues are: (1) undefined Kconfig macro. it cannot be found because the Kconfig is defined in #31583, and it is not merged; (2) invalid license: bsd-new The sample's codes logic/flow is similar with the NXP SDK bluetooth demos, the codes are bsd license in NXP SDK, so I use bsd license here too. (3) The added sample fails to compile because the sample is depend on the #31583, and it is not merged.

@Thalley
Copy link
Contributor

Thalley commented Feb 28, 2024

@MarkWangChinese I'll review once CI is green :)

Hi Emil, the current CI issues are: (1) undefined Kconfig macro. it cannot be found because the Kconfig is defined in #31583, and it is not merged; (2) invalid license: bsd-new The sample's codes logic/flow is similar with the NXP SDK bluetooth demos, the codes are bsd license in NXP SDK, so I use bsd license here too. (3) The added sample fails to compile because the sample is depend on the #31583, and it is not merged.

If this PR depends on code that is not yet merged, then please revert it to a draft, as it is not ready for review

@MarkWangChinese MarkWangChinese marked this pull request as draft February 28, 2024 12:14
@github-actions
Copy link

This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time.

@github-actions github-actions bot added the Stale label Apr 29, 2024
@github-actions github-actions bot closed this May 13, 2024
@xG3nesis
Copy link

Hello @MarkWangChinese!

It's been quite a while since you contributed that excellent example to the Bluetooth samples. I've been trying to implement it on my M5 Stack Atom Echo, which has an ESP32-PICO-D4. However, I haven't had much success so far. I've noticed that the libraries have significantly changed since then.

At this point, I can initialize the Bluetooth stack, discover nearby Bluetooth devices, connect to a Bluetooth device, and connect to the A2DP sink. However, I'm having trouble understanding the SBC streaming part. It seems like you're using a library that's not originally included in Zephyr: #include <zephyr/libsbc/sbc.h>. Is that correct?

Additionally, I'm attempting to stream data to a Linux VM, but I'm losing the connection shortly after it's established. I'm not sure why this is happening. Could you offer any advice?

Thank you!

@MarkWangChinese
Copy link
Contributor Author

Hello @MarkWangChinese!

It's been quite a while since you contributed that excellent example to the Bluetooth samples. I've been trying to implement it on my M5 Stack Atom Echo, which has an ESP32-PICO-D4. However, I haven't had much success so far. I've noticed that the libraries have significantly changed since then.

At this point, I can initialize the Bluetooth stack, discover nearby Bluetooth devices, connect to a Bluetooth device, and connect to the A2DP sink. However, I'm having trouble understanding the SBC streaming part. It seems like you're using a library that's not originally included in Zephyr: #include <zephyr/libsbc/sbc.h>. Is that correct?

Additionally, I'm attempting to stream data to a Linux VM, but I'm losing the connection shortly after it's established. I'm not sure why this is happening. Could you offer any advice?

Thank you!

Hi @xG3nesis, Good to know that you are working on the a2dp too. The basic a2dp profile implementation is already merged by (#31583), but this draft pr is old and need updating because this example pr is implemented before #31583 is merged. And this example pr depend on SBC encoder/decoder request that is approve-pending (#67705). Without SBC encoder/decoder, the example can't work and I need to keep it as draft.

@xG3nesis
Copy link

xG3nesis commented Jun 27, 2024

Thank you for your prompt response @MarkWangChinese.

I will be manually incorporating the work you completed on #70531 and making some modifications to successfully implement A2DP streaming. I also need to integrate your SBC repository (https://github.com/MarkWangChinese/libsbc/tree/main) into my Zephyr stack. Your efforts are greatly appreciated and have been incredibly helpful.

Thank you again!😄

@xG3nesis
Copy link

xG3nesis commented Jun 29, 2024

Hello @MarkWangChinese!

Thanks to the example you wrote two years ago, I'm currently attempting to implement A2DP streaming on my ESP32 using the new a2dp and a2dp_codec_sbc libraries you developed. I've successfully added the SBC encoding library, but there's something I can't figure out:

In your old code, I noticed you implemented a structure called sbc_hdr to initialize the header of your SBC frame:

static void a2dp_playback_timeout_handler(struct k_timer *timer) {
[...]
   struct bt_a2dp_codec_sbc_media_packet_hdr *sbc_hdr;
[...]
   buf = bt_a2dp_media_buf_alloc(NULL);
   if (buf == NULL) {
	   return;
   }
   sbc_hdr = net_buf_add(buf, sizeof(struct bt_a2dp_codec_sbc_media_packet_hdr));
   memset(sbc_hdr, 0, sizeof(struct bt_a2dp_codec_sbc_media_packet_hdr));
[...]
   sbc_hdr->number_of_sbc_frames = frame_num;
[...]
}

I'm unsure how to achieve the same functionality with the new a2dp_codec_sbc.h library.

This is what my main.c looks like for the moment (there most be a lot of mistakes aha, i don't grasp everything in the libraries for the moment) :

#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr/libsbc/sbc.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/classic/sdp.h>
#include <zephyr/bluetooth/classic/a2dp.h>
#include <zephyr/bluetooth/classic/rfcomm.h>
#include <zephyr/bluetooth/classic/a2dp_codec_sbc.h>
#include "app_connect.h"
#include "a2dp_media_48KHz_1ksin.h"

#define SDP_CLIENT_USER_BUF_LEN		512U
NET_BUF_POOL_FIXED_DEFINE(app_sdp_client_pool, CONFIG_BT_MAX_CONN,
			  SDP_CLIENT_USER_BUF_LEN, 8, NULL);

static uint8_t app_sdp_a2sink_user(struct bt_conn *conn, struct bt_sdp_client_result *result);

static void a2dp_playback_timeout_handler(struct k_timer *timer);
K_TIMER_DEFINE(a2dp_player_timer, a2dp_playback_timeout_handler, NULL);

#define APPL_A2DP_MTU   (672U)
#define DEFAULT_BIT_RATE (328u)

static uint32_t a2dp_src_sf;

static int64_t ref_time;
static uint32_t a2dp_src_missed_count;
static volatile uint8_t a2dp_src_playback;
static int tone_index;
uint8_t a2dp_src_nc;

#define A2DP_SBC_BLOCK_MAX (512U)
uint32_t audio_time_interval; /* ms */
uint32_t audio_frame_sample_count;
uint8_t a2dp_pcm_buffer[1920u]; /* 10ms max packet pcm data size. the max is 480 * 2 * 2 */
uint8_t a2dp_sbc_encode_buffer_frame[A2DP_SBC_BLOCK_MAX];
struct sbc_encoder encoder;
uint32_t send_samples_count;
uint16_t send_count;

#define A2DP_SRC_PERIOD_MS	10

struct bt_a2dp *default_a2dp;
struct bt_a2dp_ep *default_a2dp_endpoint;
struct bt_a2dp_stream *bt_a2dp_stream_handle;
BT_A2DP_SBC_SOURCE_EP_DEFAULT(a2dp_source_ep);


static void stream_configured(struct bt_a2dp_stream *stream) {
	printk("Streaming successfully configurated!");
}

static void stream_etablished(struct bt_a2dp_stream *stream) {
	printk("Streaming pipe successfully etablished!");
}

static void stream_released(struct bt_a2dp_stream *stream) {
	printk("Streaming pipe successfully released!");
}

static void stream_started(struct bt_a2dp_stream *stream) {
	printk("Streaming pipe successfully started");
}

struct bt_a2dp_stream_ops stream_ops = {
	.configured = stream_configured,
	.established = stream_etablished,
	.released = stream_released,
	.started = stream_started,
};


static uint8_t *a2dp_pl_produce_media(uint32_t a2dp_src_num_samples)
{
	uint8_t *media = NULL;
	uint16_t  medialen;

	/* Music Audio is Stereo */
	medialen = (a2dp_src_num_samples * a2dp_src_nc * 2);

	/* For mono or dual configuration, skip alternative samples */
	if (1 == a2dp_src_nc) {
		uint16_t index;

		media = (uint8_t *)&a2dp_pcm_buffer[0];

		for (index = 0; index < a2dp_src_num_samples; index++) {
			media[(2 * index)] = *((uint8_t *)beethoven + tone_index);
			media[(2 * index) + 1] = *((uint8_t *)beethoven + tone_index + 1);
			/* Update the tone index */
			tone_index += 4u;
			if (tone_index >= sizeof(beethoven)) {
				tone_index = 0U;
			}
		}
	} else {
		if ((tone_index + (a2dp_src_num_samples << 2)) > sizeof(beethoven)) {
			media = (uint8_t *)&a2dp_pcm_buffer[0];
			memcpy(media, ((uint8_t *)beethoven + tone_index),
				sizeof(beethoven) - tone_index);
			memcpy(&media[sizeof(beethoven) - tone_index],
				((uint8_t *)beethoven),
				((a2dp_src_num_samples << 2) - (sizeof(beethoven) - tone_index)));
			/* Update the tone index */
			tone_index = ((a2dp_src_num_samples << 2) -
				(sizeof(beethoven) - tone_index));
		} else {
			media = ((uint8_t *)beethoven + tone_index);
			/* Update the tone index */
			tone_index += (a2dp_src_num_samples << 2);
			if (tone_index >= sizeof(beethoven)) {
				tone_index = 0U;
			}
		}
	}

	return media;
}

static void a2dp_playback_timeout_handler(struct k_timer *timer)
{
	int64_t period_ms;
	uint32_t a2dp_src_num_samples;
	uint8_t *pcm_data;
	uint8_t index;
	uint32_t pcm_frame_size;
	uint32_t pcm_frame_samples;
	uint32_t encoded_frame_size;
	uint8_t *net_buffer;
	struct net_buf *buf;
	uint32_t sample_count = 0;
	uint8_t frame_num = 0;
	int ret;
	// struct bt_a2dp_codec_sbc_media_packet_hdr *sbc_hdr;
	uint8_t *sbc_hdr;
	int err;

	/* If stopped then return */
	if (0U == a2dp_src_playback) {
		return;
	}

	period_ms = k_uptime_delta(&ref_time);

	NET_BUF_POOL_DEFINE(pool_buf, 0, 1000, 0, NULL);
	buf = net_buf_alloc(&pool_buf, K_NO_WAIT);
	if (buf == NULL) {
		return;
	}
	sbc_hdr = net_buf_add(buf, sizeof(uint8_t));
	/* Get the number of samples */
	a2dp_src_num_samples = (uint16_t)((period_ms * a2dp_src_sf) / 1000);
	a2dp_src_missed_count += (uint32_t)((period_ms * a2dp_src_sf) % 1000);

	/* Raw adjust for the drift */
	while (a2dp_src_missed_count >= (1000 * audio_frame_sample_count)) {
		a2dp_src_num_samples += audio_frame_sample_count;
		a2dp_src_missed_count -= (1000 * audio_frame_sample_count);
	}

	pcm_data = a2dp_pl_produce_media(a2dp_src_num_samples);
	if (pcm_data == NULL) {
		return;
	}

	pcm_frame_size = sbc_frame_bytes(&encoder);
	pcm_frame_samples = sbc_frame_samples(&encoder);
	encoded_frame_size = sbc_frame_encoded_bytes(&encoder);
	for (index = 0; index < (a2dp_src_num_samples / audio_frame_sample_count); index++) {
		net_buffer = net_buf_tail(buf);
		if (buf->len + encoded_frame_size > bt_a2dp_get_mtu(bt_a2dp_stream_handle)) {
			printk("mtu error");
			return;
		}

		err = sbc_encode(&encoder,
				(uint8_t *)&pcm_data[index * pcm_frame_size],
				encoded_frame_size, &net_buffer[0]);
		if (err) {
			printk("sbc encode fail");
			continue;
		}
		buf->len += encoded_frame_size;
		sample_count += pcm_frame_samples;
		frame_num++;
	}

	// BT_A2DP_SBC_MEDIA_HDR_NUM_FRAMES_SET(sbc_hdr, frame_num);
	*sbc_hdr = BT_A2DP_SBC_MEDIA_HDR_ENCODE(frame_num, 0, 0, 0);

	ret = bt_a2dp_stream_send(bt_a2dp_stream_handle, buf, send_count, send_samples_count);
	if (ret < 0) {
		printk("Failed to send SBC audio data on streams(%d)\n", ret);
		net_buf_unref(buf);
	} else {
		send_count++;
		send_samples_count += sample_count;
	}
}

static void music_control_a2dp_start(void)
{
	/* Start Audio Source */
	a2dp_src_playback = 1U;
	audio_frame_sample_count = sbc_frame_samples(&encoder);

	/* calculate the interval that contains multiple of frame. default is 10ms */
	audio_time_interval = ((10 * a2dp_src_sf / 1000) / audio_frame_sample_count);
	audio_time_interval = audio_time_interval * audio_frame_sample_count * 1000 / a2dp_src_sf;
	k_uptime_delta(&ref_time);
	k_timer_start(&a2dp_player_timer, K_MSEC(audio_time_interval), K_MSEC(audio_time_interval));
}

// void app_endpoint_configured(struct bt_a2dp_endpoint_configure_result *result)
// {
// 	if (result->err == 0) {
// 		default_a2dp_endpoint = &sbc_endpoint;
// 		struct bt_a2dp_codec_sbc_params *config =
// 			(struct bt_a2dp_codec_sbc_params *)
// 			&result->config.media_config->codec_ie[0];

// 		a2dp_src_sf = bt_a2dp_sbc_get_sampling_frequency(config);
// 		a2dp_src_nc = bt_a2dp_sbc_get_channel_num(config);

// 		sbc_setup_encoder(&encoder, a2dp_src_sf, bt_a2dp_sbc_get_channel_mode(config),
// 			bt_a2dp_sbc_get_block_length(config), bt_a2dp_sbc_get_subband_num(config),
// 			bt_a2dp_sbc_get_allocation_method(config), DEFAULT_BIT_RATE);
// 		bt_a2dp_start(default_a2dp_endpoint);
// 		printk("a2dp start playing\r\n");
// 	}
// }


static struct bt_sdp_discover_params discov_a2dp_sink = {
	.uuid = BT_UUID_DECLARE_16(BT_SDP_AUDIO_SINK_SVCLASS),
	.func = app_sdp_a2sink_user,
	.pool = &app_sdp_client_pool,
};

static uint8_t app_sdp_a2sink_user(struct bt_conn *conn,
			   struct bt_sdp_client_result *result)
{
	uint16_t param;
	int res;

	if ((result) && (result->resp_buf)) {
		printk("sdp success callback\r\n");
		res = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_L2CAP, &param);
		if (res < 0) {
			printk("PSM is not found\r\n");
			return BT_SDP_DISCOVER_UUID_CONTINUE;
		}
		if (param == BT_UUID_AVDTP_VAL) {
			printk("A2DP Service found. Connecting ...\n");
			default_a2dp = bt_a2dp_connect(default_conn);
			if (default_a2dp == NULL) {
				printk("fail to connect a2dp\r\n");
			}
			return BT_SDP_DISCOVER_UUID_STOP;
		}
		return BT_SDP_DISCOVER_UUID_CONTINUE;
	}

	printk("sdp fail callback\r\n");
	return BT_SDP_DISCOVER_UUID_CONTINUE;
}

void app_sdp_discover_a2dp_sink(void)
{
	int res;

	res = bt_sdp_discover(default_conn, &discov_a2dp_sink);
	if (res) {
		printk("SDP discovery failed: result\r\n");
	} else {
		printk("SDP discovery started\r\n");
	}
}

static void a2dp_connected (struct bt_a2dp *a2dp, int err) {
	if(!err) {
		printk("[i] Successfully connected to A2DP profile!\n");
		
		struct bt_a2dp_stream bt_a2dp_stream_handle = {
			.local_ep = &a2dp_source_ep,
			.remote_ep = default_a2dp_endpoint,
			.remote_ep_id = 0,
			.ops = &stream_ops,
			.a2dp = default_a2dp,
			.codec_config = bt_a2dp_ep_cap_iea2dp_source_ep
		};
		
		int ret;

		ret = bt_a2dp_stream_establish(&bt_a2dp_stream_handle);

		if(ret){
			printk("Unable to send AVDTP_OPEN command");
		} else {
			music_control_a2dp_start();
		}
 
	} else {
		if (default_a2dp != NULL) {
			default_a2dp = NULL;
		}
		printk("[E] Unable to connect to A2DP profile (Error: %d).\n", err);
	}
}

static void a2dp_disconnected(struct bt_a2dp *a2dp) {
	int ret;

	printk("[i] Device disconnected from A2DP profile!\n");

	a2dp_src_playback = 0U;
	/* stop timer */
	k_timer_stop(&a2dp_player_timer);
	printk("a2dp disconnected\r\n");

	ret = bt_a2dp_stream_release(bt_a2dp_stream_handle);

	if(ret){
		printk("Unable to send AVDTP_OPEN command");
	}
}


static struct bt_a2dp_cb bt_a2dp_callbacks = {
	.connected = a2dp_connected,
	.disconnected = a2dp_disconnected,
};

static void app_a2dp_init(void) {
	

	bt_a2dp_register_ep(&a2dp_source_ep, BT_AVDTP_AUDIO, BT_AVDTP_SOURCE);
	bt_a2dp_register_cb(&bt_a2dp_callbacks);
}

static void bt_ready(int err)
{
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return;
	}

	printk("Bluetooth initialized\n");

	app_connect_init();
	app_a2dp_init();
}

int main(void)
{
	int err;

	err = bt_enable(bt_ready);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
	}

	return err;
}

@xG3nesis
Copy link

xG3nesis commented Jul 1, 2024

To keep you updated @MarkWangChinese, I'm building my path step by step through the different libraries. I'm able to compile my code but this is the error that i'm running into after connecting to my device.

image

Down below, you'll find my updated main.c :

#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr/libsbc/sbc.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/classic/sdp.h>
#include <zephyr/bluetooth/classic/a2dp.h>
#include <zephyr/bluetooth/classic/rfcomm.h>
#include <zephyr/bluetooth/classic/a2dp_codec_sbc.h>
#include "app_connect.h"
#include "a2dp_media_48KHz_1ksin.h"

#define SDP_CLIENT_USER_BUF_LEN		512U
NET_BUF_POOL_FIXED_DEFINE(app_sdp_client_pool, CONFIG_BT_MAX_CONN,
			  SDP_CLIENT_USER_BUF_LEN, 8, NULL);

static uint8_t app_sdp_a2sink_user(struct bt_conn *conn, struct bt_sdp_client_result *result);

static void a2dp_playback_timeout_handler(struct k_timer *timer);
K_TIMER_DEFINE(a2dp_player_timer, a2dp_playback_timeout_handler, NULL);

#define APPL_A2DP_MTU   (672U)
#define DEFAULT_BIT_RATE (328u)

static uint32_t a2dp_src_sf;

static int64_t ref_time;
static uint32_t a2dp_src_missed_count;
static volatile uint8_t a2dp_src_playback;
static int tone_index;
uint8_t a2dp_src_nc;

#define A2DP_SBC_BLOCK_MAX (512U)
uint32_t audio_time_interval; /* ms */
uint32_t audio_frame_sample_count;
uint8_t a2dp_pcm_buffer[1920u]; /* 10ms max packet pcm data size. the max is 480 * 2 * 2 */
uint8_t a2dp_sbc_encode_buffer_frame[A2DP_SBC_BLOCK_MAX];
struct sbc_encoder encoder;
uint32_t send_samples_count;
uint16_t send_count;

#define A2DP_SRC_PERIOD_MS	10

struct bt_a2dp *source_a2dp;
struct bt_a2dp_ep *remote_a2dp_endpoint;
struct bt_a2dp_stream *bt_a2dp_stream_handle;
BT_A2DP_SBC_SOURCE_EP_DEFAULT(a2dp_source_ep);


static void stream_configured(struct bt_a2dp_stream *stream) {
	printk("Streaming successfully configurated!");
}

static void stream_etablished(struct bt_a2dp_stream *stream) {
	printk("Streaming pipe successfully etablished!");
}

static void stream_released(struct bt_a2dp_stream *stream) {
	printk("Streaming pipe successfully released!");
}

static void stream_started(struct bt_a2dp_stream *stream) {
	printk("Streaming pipe successfully started");
}

struct bt_a2dp_stream_ops stream_ops = {
	.configured = stream_configured,
	.established = stream_etablished,
	.released = stream_released,
	.started = stream_started,
};


static uint8_t *a2dp_pl_produce_media(uint32_t a2dp_src_num_samples)
{
	uint8_t *media = NULL;
	uint16_t  medialen;

	/* Music Audio is Stereo */
	medialen = (a2dp_src_num_samples * a2dp_src_nc * 2);

	/* For mono or dual configuration, skip alternative samples */
	if (1 == a2dp_src_nc) {
		uint16_t index;

		media = (uint8_t *)&a2dp_pcm_buffer[0];

		for (index = 0; index < a2dp_src_num_samples; index++) {
			media[(2 * index)] = *((uint8_t *)beethoven + tone_index);
			media[(2 * index) + 1] = *((uint8_t *)beethoven + tone_index + 1);
			/* Update the tone index */
			tone_index += 4u;
			if (tone_index >= sizeof(beethoven)) {
				tone_index = 0U;
			}
		}
	} else {
		if ((tone_index + (a2dp_src_num_samples << 2)) > sizeof(beethoven)) {
			media = (uint8_t *)&a2dp_pcm_buffer[0];
			memcpy(media, ((uint8_t *)beethoven + tone_index),
				sizeof(beethoven) - tone_index);
			memcpy(&media[sizeof(beethoven) - tone_index],
				((uint8_t *)beethoven),
				((a2dp_src_num_samples << 2) - (sizeof(beethoven) - tone_index)));
			/* Update the tone index */
			tone_index = ((a2dp_src_num_samples << 2) -
				(sizeof(beethoven) - tone_index));
		} else {
			media = ((uint8_t *)beethoven + tone_index);
			/* Update the tone index */
			tone_index += (a2dp_src_num_samples << 2);
			if (tone_index >= sizeof(beethoven)) {
				tone_index = 0U;
			}
		}
	}

	return media;
}

static void a2dp_playback_timeout_handler(struct k_timer *timer)
{
	int64_t period_ms;
	uint32_t a2dp_src_num_samples;
	uint8_t *pcm_data;
	uint8_t index;
	uint32_t pcm_frame_size;
	uint32_t pcm_frame_samples;
	uint32_t encoded_frame_size;
	uint8_t *net_buffer;
	struct net_buf *buf;
	uint32_t sample_count = 0;
	uint8_t frame_num = 0;
	int ret;
	// struct bt_a2dp_codec_sbc_media_packet_hdr *sbc_hdr;
	uint8_t *sbc_hdr;
	int err;

	/* If stopped then return */
	if (0U == a2dp_src_playback) {
		return;
	}

	period_ms = k_uptime_delta(&ref_time);

	NET_BUF_POOL_DEFINE(pool_buf, 0, 1000, 0, NULL);
	buf = net_buf_alloc(&pool_buf, K_NO_WAIT);
	if (buf == NULL) {
		return;
	}
	sbc_hdr = net_buf_add(buf, sizeof(uint8_t));
	/* Get the number of samples */
	a2dp_src_num_samples = (uint16_t)((period_ms * a2dp_src_sf) / 1000);
	a2dp_src_missed_count += (uint32_t)((period_ms * a2dp_src_sf) % 1000);

	/* Raw adjust for the drift */
	while (a2dp_src_missed_count >= (1000 * audio_frame_sample_count)) {
		a2dp_src_num_samples += audio_frame_sample_count;
		a2dp_src_missed_count -= (1000 * audio_frame_sample_count);
	}

	pcm_data = a2dp_pl_produce_media(a2dp_src_num_samples);
	if (pcm_data == NULL) {
		return;
	}

	pcm_frame_size = sbc_frame_bytes(&encoder);
	pcm_frame_samples = sbc_frame_samples(&encoder);
	encoded_frame_size = sbc_frame_encoded_bytes(&encoder);
	for (index = 0; index < (a2dp_src_num_samples / audio_frame_sample_count); index++) {
		net_buffer = net_buf_tail(buf);
		if (buf->len + encoded_frame_size > bt_a2dp_get_mtu(bt_a2dp_stream_handle)) {
			printk("mtu error");
			return;
		}

		err = sbc_encode(&encoder,
				(uint8_t *)&pcm_data[index * pcm_frame_size],
				encoded_frame_size, &net_buffer[0]);
		if (err) {
			printk("sbc encode fail");
			continue;
		}
		buf->len += encoded_frame_size;
		sample_count += pcm_frame_samples;
		frame_num++;
	}

	// BT_A2DP_SBC_MEDIA_HDR_NUM_FRAMES_SET(sbc_hdr, frame_num);
	*sbc_hdr = BT_A2DP_SBC_MEDIA_HDR_ENCODE(frame_num, 0, 0, 0);

	ret = bt_a2dp_stream_send(bt_a2dp_stream_handle, buf, send_count, send_samples_count);
	if (ret < 0) {
		printk("Failed to send SBC audio data on streams(%d)\n", ret);
		net_buf_unref(buf);
	} else {
		send_count++;
		send_samples_count += sample_count;
	}
}

static void music_control_a2dp_start(void)
{
	/* Start Audio Source */
	a2dp_src_playback = 1U;
	audio_frame_sample_count = sbc_frame_samples(&encoder);

	/* calculate the interval that contains multiple of frame. default is 10ms */
	audio_time_interval = ((10 * a2dp_src_sf / 1000) / audio_frame_sample_count);
	audio_time_interval = audio_time_interval * audio_frame_sample_count * 1000 / a2dp_src_sf;
	k_uptime_delta(&ref_time);
	k_timer_start(&a2dp_player_timer, K_MSEC(audio_time_interval), K_MSEC(audio_time_interval));
}

static struct bt_sdp_discover_params discov_a2dp_sink = {
	.uuid = BT_UUID_DECLARE_16(BT_SDP_AUDIO_SINK_SVCLASS),
	.func = app_sdp_a2sink_user,
	.pool = &app_sdp_client_pool,
};

static uint8_t app_sdp_a2sink_user(struct bt_conn *conn,
			   struct bt_sdp_client_result *result)
{
	uint16_t param;
	int res;

	if ((result) && (result->resp_buf)) {
		printk("sdp success callback\r\n");
		res = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_L2CAP, &param);
		if (res < 0) {
			printk("PSM is not found\r\n");
			return BT_SDP_DISCOVER_UUID_CONTINUE;
		}
		if (param == BT_UUID_AVDTP_VAL) {
			printk("A2DP Service found. Connecting ...\n");
			source_a2dp = bt_a2dp_connect(default_conn);
			if (source_a2dp == NULL) {
				printk("fail to connect a2dp\r\n");
			}
			return BT_SDP_DISCOVER_UUID_STOP;
		}
		return BT_SDP_DISCOVER_UUID_CONTINUE;
	}

	printk("sdp fail callback\r\n");
	return BT_SDP_DISCOVER_UUID_CONTINUE;
}

void app_sdp_discover_a2dp_sink(void)
{
	int res;

	res = bt_sdp_discover(default_conn, &discov_a2dp_sink);
	if (res) {
		printk("SDP discovery failed: result\r\n");
	} else {
		printk("SDP discovery started\r\n");
	}
}

static void a2dp_connected(struct bt_a2dp *a2dp, int err) {
	if(!err) {
		printk("[i] Successfully connected to A2DP profile!\n");
		
		struct bt_a2dp_stream bt_a2dp_stream_handle = {
			.local_ep = &a2dp_source_ep,
			.remote_ep = remote_a2dp_endpoint,
			.remote_ep_id = 0,
			.ops = &stream_ops,
			.a2dp = source_a2dp,
			.codec_config = bt_a2dp_ep_cap_iea2dp_source_ep
		};
		
		int ret;

		ret = bt_a2dp_stream_establish(&bt_a2dp_stream_handle);

		if(ret){
			printk("Unable to send AVDTP_OPEN command");
		} else {
			music_control_a2dp_start();
		}
 
	} else {
		if (source_a2dp != NULL) {
			source_a2dp = NULL;
		}
		printk("[E] Unable to connect to A2DP profile (Error: %d).\n", err);
	}
}

static void a2dp_disconnected(struct bt_a2dp *a2dp) {
	int ret;

	printk("[i] Device disconnected from A2DP profile!\n");

	a2dp_src_playback = 0U;
	/* stop timer */
	k_timer_stop(&a2dp_player_timer);
	printk("a2dp disconnected\r\n");

	ret = bt_a2dp_stream_release(bt_a2dp_stream_handle);

	if(ret){
		printk("Unable to send AVDTP_OPEN command");
	}
}

static int a2dp_configuration(struct bt_a2dp *a2dp, struct bt_a2dp_ep *ep, struct bt_a2dp_codec_cfg *codec_cfg, struct bt_a2dp_stream **stream, uint8_t *rsp_err_code) {
	struct bt_a2dp_codec_sbc_params *config = (struct bt_a2dp_codec_sbc_params *) &codec_cfg->codec_config->codec_ie[0];

	a2dp_src_sf = bt_a2dp_sbc_get_sampling_frequency(config);
	a2dp_src_nc = bt_a2dp_sbc_get_channel_num(config);

	sbc_setup_encoder(&encoder, a2dp_src_sf, BT_A2DP_SBC_CHAN_MODE(config), BT_A2DP_SBC_BLK_LEN(config), BT_A2DP_SBC_SUB_BAND(config), BT_A2DP_SBC_ALLOC_MTHD(config), DEFAULT_BIT_RATE);
	bt_a2dp_stream_start(bt_a2dp_stream_handle);
	printk("a2dp start playing\r\n");

}

static struct bt_a2dp_cb bt_a2dp_callbacks = {
	.connected = a2dp_connected,
	.disconnected = a2dp_disconnected,
	.config_req = a2dp_configuration,
};

static void app_a2dp_init(void) {
	bt_a2dp_register_ep(&a2dp_source_ep, BT_AVDTP_AUDIO, BT_AVDTP_SOURCE);
	bt_a2dp_register_cb(&bt_a2dp_callbacks);
}

static void bt_ready(int err)
{
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return;
	}

	printk("Bluetooth initialized\n");

	app_connect_init();
	app_a2dp_init();
}

int main(void)
{
	int err;

	err = bt_enable(bt_ready);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
	}

	return err;
}

If you could bring some assistance, I would really appreciate it !
Thank you in advance 😃.

@MarkWangChinese
Copy link
Contributor Author

Hi @xG3nesis , I create another draft pr #76102 because this draft pr is closed and https://github.com/nxp-zephyr/zephyr is not suggested to use now in NXP. You can reference the new draft pr (#76102), I think you need to resolve some build errors in your side, but the function works in my side based on (#76101)

@Thalley Thalley removed their request for review July 19, 2024 13:28
@xG3nesis
Copy link

xG3nesis commented Jul 19, 2024

Hi @MarkWangChinese !
I've just tested your code, but I'm unable to compile it. Have you modified any libraries?
tempsnip

@MarkWangChinese
Copy link
Contributor Author

Hi @xG3nesis , you can reference to #76149. Because the sbc encoder/decoder is pending in #67705, and this pr has dependency with sbc encoder/decoder module, I can't create formal pr, so the pr is draft.

@xG3nesis
Copy link

Hi @MarkWangChinese,

Everything is compiling fine now, thank you!

However, I'm encountering an issue while trying to connect my device to my Bluetooth Ubuntu VM, which has an A2DP sink endpoint. It seems that the endpoint with the SBC codec is not being detected.

image

How do you typically test your device in your setup?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants