Problem of stereo audio streaming using Audio Stream Broadcast custom protocol with BLE coexistence sample codes

How to modify the Audio Stream Broadcast custom protocol with BLE coexistence for stereo audio streaming from one transmitter to 2 receivers (left and right channel) using the sample codes (remote_mic_rx_coex and remote_mic_tx_coex) ?

Currently, the sample codes can only do audio streaming from one transmitter to one receiver.

To modify the TX side for stereo audio streaming to 2 receivers, the software execution flow should be modified as below.

  1. TX side establishes BLE connection to RX-LeftCh
  2. TX side exchanges custom protocol parameters with RX-LeftCh.
  3. TX side drops BLE connection to RX-LeftCh.
  4. TX side establishes BLE connection to RX-RightCh.
  5. TX side exchanges custom protocol parameters with RX-RightCh.
  6. TX side drops BLE connection to RX-RightCh.
  7. TX side starts custom protocol to stream audio to RX-LeftCh and RX-RightCh.

Based on above software execution flow, can you let me know which parts of the codes need to be modified?

BLE device address and device name of RX-LeftCh and RX-RightCh should be different (or same)?
For example,

RX-LeftCh :

BLE device address : PRIVATE_BDADDR = { 0x11, 0x11, 0x11, 0x11, 0x11, 0xC0 }.

BLE device name : APP_DFLT_DEVICE_NAME = “RemoteMic_Rx”

RX-RightCh :

BLE device address : PRIVATE_BDADDR = { 0x11, 0x11, 0x11, 0x11, 0x11, 0xD0 }.

BLE device name : APP_DFLT_DEVICE_NAME = “RemoteMic_Rx2”

Hi @NEO,

By default, the ‘remote_mic_rx_coex’ & ‘remote_mic_tx_coex’ sample applications are capable of using the single TX, unlimited RX Custom Audio Streaming Protocol.

It is important to note that this protocol is a broadcast protocol, and that it does not send audio over the BLE Standard, but instead uses a custom protocol to communicate the Audio Packets.

The ‘remote_mic_tx_coex’ sample code will broadcast both the Left and Right audio packets with a certain Stream Address, tagged with a header that differentiates between Left and Right.

The ‘remote_mic_rx_coex’ sample code will then listen for all audio packets on that Stream Address, but will only accept and process that packets tagged with the header for the channel they are listening for. Therefore, setting up two ‘remote_mic_rx_coex’ samples to act as Left and Right channels can be done by ensuring the ‘role’ and ‘access_word’ variables within the ‘rm_app.c’ ‘APP_RM_init()’ function are set as desired (ie. One set to LEFT role and one set to RIGHT role). This will result in the LEFT role only processing the audio packets tagged as LEFT, and RIGHT role only processing the audio packets tagged as RIGHT.

As for the BLE Coexistence, the timing and slot allocation to ensure the proper BLE intervals are maintained (Advertising/Connection Intervals) alongside the Remote Mic Protocol will be done automatically in these sample applications. These sample applications have all of the RMP and BLE configurations to co-exists enabled by default. As you suggest, simply changing the BLE Names and Addresses will be enough to enable both RX devices to achieve simultaneous BLE connections.

Please let us know if you have any follow-up questions, and thank you for using the Community Forums.

1 Like

The problem is on the TX side. How to make the TX connect to two RX via BLE for exchanging custom protocol parameters, before dropping the BLE connection and switch to custom protocol for starting streaming audio to the two RX?

Currently, the TX can only connect to one RX via BLE for exchanging custom protocol parameters, before dropping the BLE connection and switch to custom protocol for starting streaming audio to one RX.

Current settings of sample codes:

TX side:
app_env.rm_param.role = RM_MASTER_ROLE;
app_env.rm_param.accessword = (0x00cde629 | (0x0d << 24));

RX side:
app_env.rm_param.role = RM_SLAVE_ROLE;
app_env.rm_param.accessword = (0x00cde629 | (0x0d << 24));

The access_word settings of both the TX side and RX side are the same in the sample code. That is, for audio streaming from one TX to one RX.

In the case of two RX, what should be the correct settings for these variables for each RX?

Hi @NEO,

You are correct that by default the TX Coex sample application only supports a single BLE connection by default. We do not recommend connecting to two BLE devices simultaneously, as this can limit the amount of time allocated to processing the Remote Mic Protocol while the BLE maintains both Connection Intervals. It is possible to update the code execution to support two simultaneous connections, but in this situation you should ensure the Connection Intervals are set to ~x2 the default value to allow for sufficient timing slots. If you would like further direction towards this approach, please let us know.

Another option is to perform the connections and parameter exchanges in a sequential fashion. By first: connecting to one of the RX Coex device over BLE, exchanging an Indication/Notification containing the parameters, confirming their validity, and then disconnecting, you can then perform the same process with the second RX Coex device to establish the second set of parameters. Again, if you would like more details on this approach, please let us know.

As for the Dual Remote Mic Streaming, there was actually an error in my previous response. Within RX Coex you are looking for the ‘accessword’ & ‘audioChnl’ variables. The default ‘audioChnl’ variable is defined in ‘app.h’ as ‘#define APP_RM_AUDIO_CHANNEL’, but this can also be changed at runtime by using the DIO5 button on our QFN and SiP evaluation boards. In this situation, 0-LEFT & 1-RIGHT. If it is simpler, you can also set these directly in the ‘APP_RM_init()’ function if your devices do not need the ability to switch channels at runtime.

Please let us know if you have any further questions or concerns.

Do you have the sample codes and documents for this sequential fashion approach?

Can this parallel fashion approach allow the TX to stream stereo audio to two RXs while simultaneously maintaining BLE connection with them?

This ensures the TX can continue to exchange some other control function parameters with the two RXs via BLE connection while simultaneously streaming audio to them using custom protocol.

I would like to have more details on these two approaches.
Please let me know. Thanks.

Hi @NEO,

Unfortunately, we do not have documentation or sample code the pertains to these specific situations.

In theory, you are correct that setting up the 2 parallel BLE connections should allow simultaneous Remote Mic streaming and BLE connectivity between a single TX and 2 RX devices, but we have avoided this setup in our samples to ensure the two protocols both have sufficient time-slots to maintain the connection/streaming. If you are attempting this, it is suggested to ensure the Connection Interval is negotiate to be twice the value of the single BLE setup. This should allow sufficient time for all 3 connection (2 BLE, 1 RMP) to be maintained.

There are several considerations while adding a second simultaneous BLE connection to this setup:

  1. The ‘GAPM_START_CONNECTION_CMD’ sent within ‘ble_std.c’/‘Connection_SendStartCmd()’ should be updated to use the ‘GAPM_CONNECTION_AUTO’ in the ‘code’ variable (GAPM_CONNECTION_DIRECT only supports 1 connection) and a second address and address type should be added to the ‘peers’ variable.
  2. You must use the ‘GAPC_CONNECTION_REQ_IND’ and ‘GAPC_DISCONNECT_IND’ message handlers to keep track of how many connections are currently established. On Connection Request, increment the value, and on Disconnect Indication, decrement the value.
  3. Following a ‘GAPC_CONNECTION_REQ_IND’ you must call the ‘Connection_SendStartCmd()’ again if the maximum number of connections has not been reached (in this case <2).
  4. The Central’s proposed Connection Interval should be increased to x2 the original value. This can be done in ‘ble_std.h’ under the ‘CON_INTERVAL_MIN/MAX’ variable define. (Note that this variable uses 1.25ms step size [ie 6 * 1.25 = 7.5ms]).

For a better demonstration of this dual Central Connection setup, please take a look at the ‘central_client’ sample application, as it supports 4 simultaneous connections.

I can’t find the ‘central_client’ sample code.
I am using 3.4.2-4 pack.
I can only find ‘central_client_uart’ sample code. However, the Connection_SendStartCmd() is exactly same as that used in ‘remote_mic_tx_coex’ sample code.
I can’t see how this function can support 4 simultaneous connections.

Is ‘central_client_uart’ sample code same as ‘central_client’ sample code?

Required Code Changes:
cmd->op.code = GAPM_CONNECTION_AUTO;

/* First RX device BD address and type */
cmd->peers[0].addr_type = DIRECT_PEER_BD_ADDRESS_TYPE;
memcpy(&cmd->peers[0].addr.addr[0], &peerAddress0[0], BD_ADDR_LEN);

/* Second RX device BD address and type */
cmd->peers[1].addr_type = DIRECT_PEER_BD_ADDRESS_TYPE;
memcpy(&cmd->peers[1].addr.addr[0], &peerAddress1[0], BD_ADDR_LEN);

/* Prepare the GAPM_START_CONNECTION_CMD message */
cmd = KE_MSG_ALLOC_DYN(GAPM_START_CONNECTION_CMD, TASK_GAPM, TASK_APP,
gapm_start_connection_cmd,
(2 * sizeof(struct gap_bdaddr)));
Note: length is changed to 2 times of sizeof(struct gap_bdaddr) because of two peers (peers[2]).

cmd->nb_peers = 1; => should this value be changed to 2, i.e. for two RX?

Any example of modified ‘GAPC_CONNECTION_REQ_IND’ and ‘GAPC_DISCONNECT_IND’ ?

Can you show me how to modify ‘GAPC_CONNECTION_REQ_IND’ and ‘GAPC_DISCONNECT_IND’ ?

TX Side BLE Connection Interval:
/* Set the connection interval to 7.5ms and slave latency to zero */
#define CON_INTERVAL_MIN 6 // 6 * 1.25ms = 7.5ms => changed to 12 (=15ms)
#define CON_INTERVAL_MAX 6 // 6 * 1.25ms = 7.5ms => changed to 12 (=15ms)
#define CON_SLAVE_LATENCY 0

RX Side BLE Connection Interval:
/* Slave preferred connection parameters */
#define PREF_SLV_MIN_CON_INTERVAL 8 => should this value be changed to 12 to match TX?
#define PREF_SLV_MAX_CON_INTERVAL 10 => should this value be changed to 12 to match TX?

RMP Connection Interval:
Transmission interval: 10ms
Re-transmission interval: 5ms

What is the rationale for increasing the connection interval to be twice the value of the single BLE setup?

By increasing the connection interval to twice the original value, can all 3 connection (2 BLE and 1 RMP) be maintained simultaneously? That is, TX can stream audio to 2 RX using RMP and simultaneously maintain BLE connection with the 2 RX.

Can you show me the simultaneous coexist RF connection interval for all 3 connection (2 BLE, 1RMP) with an illustration diagram?

Can you show me the Message Sequence Chart for TX and 2 Rx for all 3 connection (2 BLE, 1 RMP) simultaneously ?

Is below modification correct?

void Connection_SendStartCmd(void)
{
uint8_t peerAddress0[BD_ADDR_LEN] = DIRECT_PEER_BD_ADDRESS;
// By NEO
uint8_t peerAddress1[BD_ADDR_LEN] = DIRECT_PEER_BD_ADDRESS_RX2;
struct gapm_start_connection_cmd *cmd;

PRINTF("__START CONNECTION\n");
/* Prepare the GAPM_START_CONNECTION_CMD message  */
// By NEO
/*
cmd = KE_MSG_ALLOC_DYN(GAPM_START_CONNECTION_CMD, TASK_GAPM, TASK_APP,
                       gapm_start_connection_cmd,
                       (sizeof(struct gap_bdaddr)));
*/
cmd = KE_MSG_ALLOC_DYN(GAPM_START_CONNECTION_CMD, TASK_GAPM, TASK_APP,
                       gapm_start_connection_cmd,
                       2 * (sizeof(struct gap_bdaddr)));

// By NEO
//cmd->op.code       = GAPM_CONNECTION_DIRECT;
cmd->op.code       = GAPM_CONNECTION_AUTO;
cmd->op.addr_src   = GAPM_STATIC_ADDR;
cmd->op.state      = 0;

/* Set scan interval to 62.5ms and scan window to 50% of the interval */
cmd->scan_interval = SCAN_INERVAL;
cmd->scan_window   = SCAN_WINDOW;

/* Set the connection interval to 7.5ms */
cmd->con_intv_min  = CON_INTERVAL_MIN;
cmd->con_intv_max  = CON_INTERVAL_MAX;
cmd->con_latency   = CON_SLAVE_LATENCY;

/* Set supervisory timeout to 3s */
cmd->superv_to     = CON_SUP_TIMEOUT;

// By NEO
//cmd->nb_peers      = 1;
cmd->nb_peers      = 2;  // for two RX

/* Address Type: Private Address */
cmd->peers[0].addr_type = DIRECT_PEER_BD_ADDRESS_TYPE;
memcpy(&cmd->peers[0].addr.addr[0], &peerAddress0[0], BD_ADDR_LEN);
// By NEO
cmd->peers[1].addr_type = DIRECT_PEER_BD_ADDRESS_TYPE;
memcpy(&cmd->peers[1].addr.addr[0], &peerAddress1[0], BD_ADDR_LEN);

/* Send the message */
ke_msg_send(cmd);

ble_env.state = APPM_CONNECTING;

}

Hi @NEO,

Please see my feedback outlined below:


Is ‘central_client_uart’ sample code same as ‘central_client’ sample code?

Apologies, there was a mistake in my previous feedback. The Central sample application that offers 4 simultaneous connections is ‘ble_central_client_bond’. It uses out BLE Abstraction to make the exchange more high-level, but the Abstraction source code is provided (ble_gap.c / ble_gatt.c) so it can offer the necessary steps to perform the multiple connection logic.


Can you show me how to modify ‘GAPC_CONNECTION_REQ_IND’ and ‘GAPC_DISCONNECT_IND’ ?

This indexing of the number of existing connections is also available in the Connection Request Indication and Disconnect Indication handlers of the ‘ble_central_client_bond’ sample application. It is essentially just a counter that will keep track of how many connections are currently active, to prevent an addition Connection Attempt once both peers are connected.


By increasing the connection interval to twice the original value, can all 3 connection (2 BLE and 1 RMP) be maintained simultaneously?

In theory, this should allow enough time for both peer Connection Events and the RMP exchange to occur. The graphic below will offer more insight into this. This has not been tested on our end as it is not part of the default sample code, so I cannot say decisively if this will offer the necessary flexibility to support both features, but I am also not aware of any limitations that will prevent it.


Can you show me the Message Sequence Chart for TX and 2 Rx for all 3 connection (2 BLE, 1 RMP) simultaneously?

To be noted, the time-scale does not align with what is configured in the RMP samples (default Connection Interval of 7.5ms, Transmission Interval of 10ms, Retransmission Interval of 5ms). It is simply intended to demonstrate the theoretical concept that if the Connection Interval is doubled across 2 simultaneous BLE Connection, the frequency of Connection Events will stay the same.


Is below modification correct?

This looks entirely correct to me and should implement the Automatic Connection procedure for both peer devices as expected. This can also be compared with the ‘gapm_start_connection_cmd’ variable, defined within the ‘ble_central_client_bond’ sample, to further verify that it is configured as expected.


1 Like

Hi Brandon,

Are below modifications correct?

int GAPC_ConnectionReqInd(ke_msg_id_t const msg_id,
struct gapc_connection_req_ind const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
struct gapc_connection_cfm *cfm;

ble_env.conidx = KE_IDX_GET(src_id);

PRINTF("__GAPC_CONNECTION_REQ_IND\n");
/* Check if the received connection handle was valid */
if (ble_env.conidx != GAP_INVALID_CONIDX)
{

  PRINTF("__APPM_CONNECTED\n");
  ble_env.state  = APPM_CONNECTED;

  /* Retrieve the connection info from the parameters */
  ble_env.conidx = param->conhdl;

  /* Save the connection parameters */
  ble_env.con_interval = param->con_interval;
  ble_env.con_latency  = param->con_latency;
  ble_env.time_out     = param->sup_to;

  /* Send connection confirmation */
  cfm = KE_MSG_ALLOC(GAPC_CONNECTION_CFM,
  				   KE_BUILD_ID(TASK_GAPC, ble_env.conidx), TASK_APP,
  				   gapc_connection_cfm);

  cfm->pairing_lvl = GAP_AUTH_REQ_NO_MITM_NO_BOND;

  cfm->svc_changed_ind_enable = 0;

  /* Send the message */
  ke_msg_send(cfm);

  /* Start enabling client services */
  BLE_SetServiceState(true, ble_env.conidx);

  connectionCount++;  // By NEO

  // By NEO
  /* If not yet connected to all peers, keep scanning */
  if(connectionCount < APP_NB_PEERS) {
  	Connection_SendStartCmd();
  }
}
else
{
    Connection_SendStartCmd();
}

return (KE_MSG_CONSUMED);

}

int GAPC_DisconnectInd(ke_msg_id_t const msg_id,
struct gapc_disconnect_ind const *param,
ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
connectionCount–; // By NEO

PRINTF("__GAPC_DISCONNECT_IND\n");
/* Go to the ready state */
ble_env.state = APPM_READY;

BLE_SetServiceState(false, ble_env.conidx);

/* When the link is lost, it sends connection start command again to put
 * it in the state that accepts advertisements from peer device
 * and can connect to it */
Connection_SendStartCmd();

return (KE_MSG_CONSUMED);

}

int GATTC_CmpEvt(ke_msg_id_t const msg_id, struct gattc_cmp_evt
const *param,
ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
uint8_t length;
uint8_t * *value;

/* Check application state and status of service and characteristic
 * discovery for custom service and if it is unsuccessful we can disconnect
 * the link although it is possible to go to enable state and let the
 * battery service works */
if (param->status != GAP_ERR_NO_ERROR)
{
    if (param->operation == GATTC_DISC_BY_UUID_SVC &&
        param->status == ATT_ERR_ATTRIBUTE_NOT_FOUND &&
        cs_env.state != CS_SERVICE_DISCOVERD
        && ble_env.state != APPM_CONNECTED)
    {
        /* Enable pending client services to be enable */
        ServiceEnable(ble_env.conidx);
    }
    else if (param->operation == GATTC_DISC_ALL_CHAR &&
             param->status == ATT_ERR_ATTRIBUTE_NOT_FOUND &&
             cs_env.state == CS_SERVICE_DISCOVERD)
    {
        /* Enable pending client services to be enable */
        ServiceEnable(ble_env.conidx);
    }
}
else
{
    if (param->operation == GATTC_WRITE)
    {
        if (cs_env.state == CS_CONFIGURING)
        {
  		cs_env.config_num++;

  		if (cs_env.config_num < CS_IDX_NB)
  		{
  			length = CustomProtocol_SelectAttributeValue(
  				cs_env.config_num, (uint8_t * *)&value);

  			CustomSrvice_SendWrite(ble_env.conidx, (uint8_t *)value,
  								   cs_env.disc_att[cs_env.config_num].
  								   pointer_hdl, 0,
  								   length, GATTC_WRITE);
  		}
  		else
  		{
  			cs_env.state = CS_PEER_CONFIGURED;
  		}
        }
        else if (cs_env.state == CS_PEER_CONFIGURED)
        {
    		// By NEO
    		/* If not yet connected to all peers, skip RMP */
    		if(connectionCount < APP_NB_PEERS) {
    			cs_env.state = CS_INIT;
    			ble_env.state = APPM_INIT;
    		}
    		else {
  			APP_RM_Init(ear_side);
  			RF_SwitchToCPMode();
  			NVIC_DisableIRQ(BLE_FINETGTIM_IRQn);
  			RM_Enable(1000);
    		}
        }
    }
}

return (KE_MSG_CONSUMED);

}

After connecting to the RX-1 via BLE, TX exchanges RMP parameters with RX-1, set cs_env.state = CS_PEER_CONFIGURED and calls RF_SwitchToCPMode().

Once switch to RMP and drop BLE connection, TX cannot connect to RX-2 via BLE.

How to modify the codes to achieve below execution flow?

  1. TX connects to RX-1 via BLE

  2. TX exchanges RMP parameters with RX-1 via BLE

  3. TX connects to RX-2 via BLE

  4. TX exchanges RMP parameters with RX-2 via BLE

  5. TX enables RMP by calling RF_SwitchToCPMode(), and starts stereo audio streaming.

  6. If possible, TX maintains simultaneous BLE and RMP connection by calling RF_SwitchToBLEMode(). So that, TX can continue to exchange other function control parameters with RX-1 and RX-2 via BLE connection while streaming audio via RMP connection.

How to use a single cs_env.state variable in TX to exchange RMP parameters with RX-1 and RX-2 ?

How to modify the codes to achieve below execution flow?

  1. TX connects to RX-1 via BLE
  2. TX exchanges RMP parameters with RX-1 via BLE
  3. TX enables RMP by calling RF_SwitchToCPMode(), and starts stereo audio streaming. At the same time, keep BLE connection. RX-1 starts to receive audio streaming.
  4. TX continue to scan for RX-2 via BLE.
  5. TX connects to RX-2 via BLE
  6. TX exchanges RMP parameters with RX-2 via BLE
  7. RX-2 starts to receive audio streaming.

What modification should be done on the below state variables and their flow?

  1. ble_env.state
  2. cs_env.state
  3. app_env

RX Side:

int GATTC_WriteReqInd(ke_msg_id_t const msg_id,
struct gattc_write_req_ind const *param,
ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
:
:
if ((oldValue_onoff != app_env.RM_on_off) &&
(attnum == CS_REMPRO_IDX_ONOFF_VALUE_VAL))
{
if (app_env.RM_on_off)
{
callback.trx_event = RM_Callback_TRX;
callback.status_update = RM_Callback_StatusUpdate;

        RM_Configure(&app_env.rm_param, callback);
        RF_SwitchToCPMode();
        RM_Enable(1000);
    }
    else
    {
        BBIF_COEX_CTRL->RX_ALIAS = 0;
        BBIF_COEX_CTRL->TX_ALIAS = 0;
        RM_Disable();
        RF_SwitchToBLEMode();
    }
}

}

How does RX side achieve simultaneous BLE and RMP connection by switching between RMP mode and BLE mode, as shown in above code? Can you explain how this switching mechanism work? Can this switching mechanism applied to TX side?

TX Side:

int APP_Timer(ke_msg_id_t const msg_id,
void const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
:
:
if ((ble_env.state == APPM_CONNECTED) && (cs_env.state ==
CS_PEER_CONFIGURED))
{
app_env.RM_on_off = 1;
CustomSrvice_SendWrite(ble_env.conidx, &app_env.RM_on_off,
cs_env.disc_att[CS_REMPRO_IDX_ONOFF].pointer_hdl,
0,
1, GATTC_WRITE);
}
}

int GATTC_CmpEvt(ke_msg_id_t const msg_id, struct gattc_cmp_evt
const param,
ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
:
:
else if (cs_env.state == CS_PEER_CONFIGURED)
{
// By NEO
/
If not yet connected to all peers, skip RMP */
if(connectionCount < APP_NB_PEERS) {
cs_env.state = CS_INIT;
//ble_env.state = APPM_INIT;
//App_Env_Initialize();
}
else {
APP_RM_Init(ear_side);
RF_SwitchToCPMode();
NVIC_DisableIRQ(BLE_FINETGTIM_IRQn);
RM_Enable(1000);
}
}
}

Below modification at TX side does not work.

int GATTC_CmpEvt(ke_msg_id_t const msg_id, struct gattc_cmp_evt
const *param,
ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
:
:
if(connectionCount < APP_NB_PEERS) {
APP_RM_Init(ear_side);
RF_SwitchToCPMode();
//NVIC_DisableIRQ(BLE_FINETGTIM_IRQn);
RM_Enable(1000);
cs_env.state = CS_INIT;
}
:
:
}

At TX side, how to create 2 instances of APP Task, BLE connection and Custom Service configuration?

For remote_mic_tx_coex sample code:
/* Number of APP Task Instances */
#define APP_IDX_MAX 2 //1 By NEO

With reference from ble_central_client_bond sample code:
#define APP_IDX_MAX BLE_CONNECTION_MAX /* Number of APP Task Instances */

By the way, ble_central_client_bond is using abstract code for BLE stack, which is different from remote_mic_tx_coex.

How to port remote_mic_tx_coex sample code to use abstract code for BLE stack, just like what ble_central_client_bond sample code did?