Bluetooth® Low Energy audio - STM32WBA Public broadcast profile

1. Introduction

The public broadcast profile (PBP) is a Bluetooth® LE audio profile specified by the Bluetooth SIG. It is categorized as a "use case profile", meaning that it is a high-layer profile designed for a specific use case. The complete specification can be found on the Bluetooth SIG website[1]. Location of PBP inside BLE audio Profiles architecture

The PBP addresses the use case of a broadcast in a public space, accessible by many people simultaneously: a train station, an airport or a public TV for example. It is based on the BLE audio broadcast feature, as described in the wiki page "Introduction to Bluetooth LE audio"[2]., which is publicly advertised as "Auracast" by the Bluetooth SIG. Due to the nature of the broadcast feature, which allows sending audio streams unidirectionally without ACL connection, an unlimited number of devices can synchronize to a single broadcast source.

2. Roles

The PBP introduces three different roles:

  • Public broadcast source (PBS): Based on the BAP broadcast source, but adds additional fields to the extended advertising data payload, to facilitate discovery and synchronization of public broadcast sinks
  • Public broadcast sink (PBK): Based on the BAP broadcast sink, able to read the additional extended advertising data fields of the PBS
  • Public broadcast assistant (PBA): Based on the BAP broadcast assistant, able to control the reception of a broadcast audio stream on a PBK.

The requirements for the three roles are the following:

PBP Role CAP Roles BAP Roles GAP Roles
Public Broadcast Source Initiator Broadcast Source Broadcaster
Public Broadcast Sink Acceptor Broadcast Sink & Scan Delegator Observer & Peripheral
Public Broadcast Assistant Commander Broadcast Assistant Observer & Central

3. Public broadcast announcement

The public broadcast announcement is a service data field added by the PBS to give extra context to a source content. It provides the information to the PBK before synchronizing the periodic advertising train, if the source is compatible.

Advertising Content Description
Encryption Defines if the broadcast source is encrypted or not
Standard Quality Defines if the broadcast source uses a standard audio quality configuration (16_2 or 24_2)
High Quality Defines if the broadcast source uses a high-quality configuration (48KHz configuration)
Metadata Additional metadata field, available for custom implementation

4. Broadcast name

The public broadcast profile also requires to advertise a broadcast name AD type on the PBS. This broadcast name is a 4 to 32 character long string, encoded in UTF-8, describing the source content in a human-readable way.

Examples of broadcast name can be "Gate 3", or "Auracast_Room:2A".

5. Public broadcast profile APIs

The following section lists and describes the public broadcast source and sink APIs.

5.1. Public broadcast source APIs

The following APIs are available for the public broadcast source role:

PBP_Init Initialize the Public Broadcast Profile with the selected role(s)
PBP_PBS_BroadcastAudioStart Perform a Broadcast Audio Start procedure using PBP requirements to start a Public Broadcast Source.
PBP_PBS_BroadcastAudioStop Perform the Broadcast Audio Stop procedure to stop a Public Broadcast Source
PBP_PBS_BroadcastAudioUpdate Perform the Broadcast Audio Update Procedure to update a Public Broadcast Source's metadata
CAP_Broadcast_ReadSupportedControllerDelay Reat the supported controller delay range function of the number of BIS
CAP_Broadcast_SetupAudioDataPath Setup the data path of the chosen BISs

5.1.1. Public broadcast source initialization

1. In order to initialize and establish a public broadcast source, initialize the GATT and GAP layers using the appropriated roles

aci_gatt_init();

/* Broadcaster GAP role is required */
aci_gap_init(GAP_BROADCASTER_ROLE, PRIVACY, DEV_NAME_CHAR_HANDLE,
             &service_handle, &dev_name_char_handler, &appearance_char_handle );

2. The audio stack must be initialized

tBleStatus ret;
BleAudioInit_t BleAudioInit;

/* Initialize the Audio IP*/
BleAudioInit.NumOfLinks = CFG_BLE_NUM_LINK;
BleAudioInit.bleStartRamAddress = (uint8_t*)aAudioInitBuffer;
BleAudioInit.total_buffer_size = BLE_AUDIO_DYN_ALLOC_SIZE;
ret = BLE_AUDIO_STACK_Init(&BleAudioInit);

3. The common audio profile (CAP) must then be initialized using the CAP initiator and BAP broadcast source roles. Other profiles and roles are not required for a public broadcast source.

CAP_Config_t PBPAPP_CAP_Config = {0};
BAP_Config_t PBPAPP_BAP_Config = {0};
CCP_Config_t PBPAPP_CCP_Config = {0};
MCP_Config_t PBPAPP_MCP_Config = {0};
VCP_Config_t PBPAPP_VCP_Config = {0};
MICP_Config_t PBPAPP_MICP_Config = {0};
CSIP_Config_t PBPAPP_CSIP_Config = {0};

/* Configure CAP */
PBPAPP_CAP_Config.Role = CAP_ROLE_INITIATOR;
PBPAPP_CAP_Config.MaxNumLinks = CFG_BLE_NUM_LINK;
PBPAPP_CAP_Config.pStartRamAddr = (uint8_t *)&aCAPMemBuffer;
PBPAPP_CAP_Config.RamSize = CAP_DYN_ALLOC_SIZE;

/* Configure BAP */
PBPAPP_BAP_Config.Role = BAP_ROLE_BROADCAST_SOURCE;
PBPAPP_BAP_Config.MaxNumBleLinks = CFG_BLE_NUM_LINK;
PBPAPP_BAP_Config.ISOChnlConfig.MaxNumCIG = 0;
PBPAPP_BAP_Config.ISOChnlConfig.MaxNumCISPerCIG = 0;
PBPAPP_BAP_Config.ISOChnlConfig.MaxNumBIG = MAX_NUM_BIG;
PBPAPP_BAP_Config.ISOChnlConfig.MaxNumBISPerBIG = MAX_NUM_BIS_PER_BIG;
PBPAPP_BAP_Config.ISOChnlConfig.pStartRamAddr = 0;
PBPAPP_BAP_Config.ISOChnlConfig.RamSize = 0u;

/* Initialize the common audio profile*/
ret = CAP_Init(&PBPAPP_CAP_Config,
			   &PBPAPP_BAP_Config,
			   &PBPAPP_VCP_Config,
			   &PBPAPP_MICP_Config,
			   &PBPAPP_CCP_Config,
			   &PBPAPP_MCP_Config,
			   &PBPAPP_CSIP_Config);

4. Lastly, the public broadcast profile is initialized using the public broadcast source role

PBP_Init(PBP_ROLE_PUBLIC_BROADCAST_SOURCE);

5.1.2. Start a public broadcast source

Public broadcast source state machine:

PBP Source State Machine

1. To start a public broadcast source, use the PBP_PBS_broadcastaudiostart

PBP_PBS_BroadcastAudioStart(&pbp_audio_start_params);

2. During the public broadcast source start procedure, the CAP_AUDIO_CLOCK_REQ_EVT event is called in the CAP callback when the sampling frequency is configured and so audio clock subsystem should be updated to support submitted audio sample frequency. Use the sampling frequency to initialize the Audio Clocks.

static void CAP_App_Notification(CAP_Notification_Evt_t *pNotification)
{
  switch (pNotification->EvtOpcode)
  {
    case CAP_AUDIO_CLOCK_REQ_EVT:
      {
	    /* Init Audio Clock with configured Sampling Frequency */
        Sampling_Freq_t *freq = (Sampling_Freq_t *)pNotification->pInfo;
        AudioClock_Init(*freq);
      }
      break;
  }
}

3. The PBP_PBS_BROADCAST_AUDIOSTARTED_EVT event is generated upon BIG creation. Use this event to setup the data path and the audio peripherals. The PBP_BROADCAST_AUDIO_UP_EVT event is then generated to indicate that the audio path is up.

void PBP_Notification(PBP_Notification_Evt_t *pNotification)
{
  switch(pNotification->EvtOpcode)
  {
    case PBP_PBS_BROADCAST_AUDIOSTARTED_EVT:
    {
      /* Register Start Rx callback */
      CODEC_RegisterTriggerClbk(1,0,&start_audio_source);

      /* Initialize Audio peripherals */
      MX_AudioInit(role,
                   sampling_freq,
                   frame_duration,
                   0,
                   (uint8_t *) aPlayBuff,
                   (AudioDriverConfig) PBPAPP_Context.audio_driver_config);

      /* Read Controller Delay */
      CAP_Broadcast_ReadSupportedControllerDelay(NUM_BIS, DATA_PATH_INPUT,
                                                a_codec_id,
                                                &controller_delay_min,
                                                &controller_delay_max);

      /* Setup Audio Data Path */
      CAP_Broadcast_SetupAudioDataPath(NUM_BIS, bis_conn_handle, direction, 
                                       codec_id, controller_delay, 
                                       DATA_PATH_CIRCULAR_BUF, 
                                       datapath_param_len, &datapath_param);
      break;
    }
    case PBP_BROADCAST_AUDIO_UP_EVT:
    {
      /* Audio is UP */
      break;
    }
  }
}

5.1.3. Stop a public broadcast source

1. To stop a public broadcast source, use the PBP_PBS_broadcastaudiostop

/* Use 1 as release parameter to stop the related Extended and Periodic Advertising, 0 to keep them */
PBP_PBS_BroadcastAudioStop(BIG_HANDLE, 1);

2. The Audio Data Path is automatically removed and the event PBP_BROADCAST_AUDIO_DOWN_EVT is called

void PBP_Notification(PBP_Notification_Evt_t *pNotification)
{
  switch(pNotification->EvtOpcode)
  {
    case PBP_BROADCAST_AUDIO_DOWN_EVT:
    {
      /* Audio is DOWN */
      /* Deinit Audio peripherals*/
      MX_AudioDeInit();
      break;
    }
  }
}

5.2. Public broadcast sink APIs

The following APIs are available for the public broadcast sink role:

PBP_Init Initialize the Public Broadcast Profile with the selected role(s)
PBP_PBK_StartAdvReportParsing Start the parsing of Extended Advertising Reports to look for PBP UUIDs and generate PBP_PBK_BROADCAST_SOURCE_ADV_REPORT_EVT events
PBP_PBK_StopAdvReportParsing Stops the parsing of Extended Advertising Reports
PBP_PBK_StartPASync Synchronize to the periodic advertising train of the given Public Broadcast Source to discover more details about the related audio stream
PBP_PBK_StopPASync Stop the synchronization to a periodic advertising train
PBP_PBK_StartBIGSync Synchronize to a BIG and start receiving audio data from it
PBP_PBK_StopBIGSync Terminate the synchronization to a BIG
CAP_Broadcast_ReadSupportedControllerDelay Reat the supported controller delay range function of the number of BIS
CAP_Broadcast_SetupAudioDataPath Setup the data path of the chosen BISs

5.2.1. Public broadcast sink initialization

1. To initialize and establish a public broadcast source, initialize the GATT and GAP layers using the appropriated roles

aci_gatt_init();

/* Observer and Peripheral GAP roles are required */
aci_gap_init(GAP_OBSERVER_ROLE | GAP_PERIPHERAL_ROLE, PRIVACY, DEV_NAME_CHAR_HANDLE,
             &service_handle, &dev_name_char_handler, &appearance_char_handle );

2. The audio stack should also be initialized

tBleStatus ret;
BleAudioInit_t BleAudioInit;

/* Initialize the Audio IP*/
BleAudioInit.NumOfLinks = CFG_BLE_NUM_LINK;
BleAudioInit.bleStartRamAddress = (uint8_t*)aAudioInitBuffer;
BleAudioInit.total_buffer_size = BLE_AUDIO_DYN_ALLOC_SIZE;
ret = BLE_AUDIO_STACK_Init(&BleAudioInit);

3. The common audio profile (CAP) must then be initialized using the CAP acceptor, the BAP broadcast sink and the BAP scan delegator roles. Other profiles and roles are not required for a public broadcast sink.

CAP_Config_t PBPAPP_CAP_Config = {0};
BAP_Config_t PBPAPP_BAP_Config = {0};
CCP_Config_t PBPAPP_CCP_Config = {0};
MCP_Config_t PBPAPP_MCP_Config = {0};
VCP_Config_t PBPAPP_VCP_Config = {0};
MICP_Config_t PBPAPP_MICP_Config = {0};
CSIP_Config_t PBPAPP_CSIP_Config = {0};

/* Configure CAP */
PBPAPP_CAP_Config.Role = CAP_ROLE_ACCEPTOR;
PBPAPP_CAP_Config.MaxNumLinks = CFG_BLE_NUM_LINK;
PBPAPP_CAP_Config.pStartRamAddr = (uint8_t *)&aCAPMemBuffer;
PBPAPP_CAP_Config.RamSize = CAP_DYN_ALLOC_SIZE;

/* Configure BAP */
PBPAPP_BAP_Config.Role = BAP_ROLE_BROADCAST_SINK | BAP_ROLE_SCAN_DELEGATOR;
PBPAPP_BAP_Config.MaxNumBleLinks = CFG_BLE_NUM_LINK;
PBPAPP_BAP_Config.ISOChnlConfig.MaxNumCIG = 0;
PBPAPP_BAP_Config.ISOChnlConfig.MaxNumCISPerCIG = 0;
PBPAPP_BAP_Config.ISOChnlConfig.MaxNumBIG = MAX_NUM_BIG;
PBPAPP_BAP_Config.ISOChnlConfig.MaxNumBISPerBIG = MAX_NUM_BIS_PER_BIG;
PBPAPP_BAP_Config.ISOChnlConfig.pStartRamAddr = 0;
PBPAPP_BAP_Config.ISOChnlConfig.RamSize = 0u;

/* Initialize the Common Audio Profile*/
ret = CAP_Init(&PBPAPP_CAP_Config,
			   &PBPAPP_BAP_Config,
			   &PBPAPP_VCP_Config,
			   &PBPAPP_MICP_Config,
			   &PBPAPP_CCP_Config,
			   &PBPAPP_MCP_Config,
			   &PBPAPP_CSIP_Config);

4. The Public Broadcast Profile can finally be initialized using the public broadcast sink role

PBP_Init(PBP_ROLE_PUBLIC_BROADCAST_SINK);

5.2.2. Synchronize a public broadcast sink

The synchronization process of a public broadcast sink can be summarized like this:

PBP Broadcast Sink Sequence

1. The public broadcast sink should first enable the Extended Advertising event parsing and start a scan using the ACI Start Observation procedure

uint8_t ret;
ret = PBP_PBK_StartAdvReportParsing();

if (ret == BLE_STATUS_SUCCESS)
{
  /* Starts an Observation procedure */
  ret = aci_gap_start_observation_proc(SCAN_INTERVAL,
									   SCAN_WINDOW,
									   0x00,                        /* LE_Scan_Type */
									   0x00,                        /* Address type: Public */
									   0x00,                        /* Filter duplicates: No */
									   0x00 );                      /* Filter policy: Accept all */
}

2. Upon scanning a Public Broadcast Source, the PBP_PBK_BROADCAST_SOURCE_ADV_REPORT_EVT gets generated with the details of the public broadcast source. Start synchronizing to the periodic advertising train

void PBP_Notification(PBP_Notification_Evt_t *pNotification)
{
  switch(pNotification->EvtOpcode)
  {
    case PBP_PBK_BROADCAST_SOURCE_ADV_REPORT_EVT:
    {
      PBP_Broadcast_Source_Adv_Report_Data_t *data = (PBP_Broadcast_Source_Adv_Report_Data_t*) pNotification->pInfo;
      PBP_PBK_StartPASync(data->pBAPReport->AdvSID,
                                           data->pBAPReport->pAdvAddress,
                                           data->pBAPReport->AdvAddressType,
                                           PA_EVENT_SKIP,
                                           PA_SYNC_TIMEOUT);
      break;
    }
  }
}

3. When the periodic advertising synchronization is established, the PBP_PBK_PA_SYNC_ESTABLISHED_EVT is generated, followed by PBP_PBK_BASE_REPORT_EVT containing the broadcast source configuration information contained in the periodic advertising data and the PBP_PBK_BIGINFO_REPORT_EVT containing the details of the BIG of the broadcast source. Parse the broadcast source configuration to initialize audio clocks and initiate BIG synchronization.

void PBP_Notification(PBP_Notification_Evt_t *pNotification)
{
  switch(pNotification->EvtOpcode)
  {
    case PBP_PBK_PA_SYNC_ESTABLISHED_EVT:
    {
      /* Periodic Advertising Synchronization is established */
	  break;
    }

    case PBP_PBK_BASE_REPORT_EVT:
    {
      BAP_BASE_Report_Data_t *base_data = (BAP_BASE_Report_Data_t*) pNotification->pInfo;
      
	  /* Retrieve BASE Data */
	  /* [...] */
	  
	  uint32_t freq = LTV_GetConfiguredSamplingFrequency(PBPAPP_Context.base_subgroups[0].pCodecSpecificConf,
                                                         PBPAPP_Context.base_subgroups[0].CodecSpecificConfLength);
	  /* Init Audio Clock with Broadcast Source frequency */
	  AudioClock_Init(freq);
      break;
    }

    case PBP_PBK_BIGINFO_REPORT_EVT:
    {
      BAP_BIGInfo_Report_Data_t *data = (BAP_BIGInfo_Report_Data_t *) pNotification->pInfo;
      uint8_t bis_index[2u] = {0x01, 0x02};

      /* Start BIG Synchronization */
      status = PBP_PBK_StartBIGSync(BIG_HANDLE,
                                    data->SyncHandle,
                                    &(bis_index[0]),
                                    data->NumBIS,
                                    &(PBPAPP_Context.base_group),
                                    0u,
                                    BAP_BROADCAST_ENCRYPTION,
                                    aPBPAPP_BroadcastCode,
                                    BIG_MSE,
                                    BIG_SYNC_TIMEOUT);
      break;
    }
  }
}

4. When synchronizing with the BIG, PBP_PBK_BIG_SYNC_ESTABLISHED_EVT is generated. Stop the scan procedure and setup the data path to generate the PBP_BROADCAST_AUDIO_UP_EVT when the data path is up.

void PBP_Notification(PBP_Notification_Evt_t *pNotification)
{
  switch(pNotification->EvtOpcode)
  {
    case PBP_PBK_BIG_SYNC_ESTABLISHED_EVT:
    {
      /* BIG Sync is established */
	  
	  /* Stop Advertising Report parsing and Observation procedure */
	  PBP_PBK_StopAdvReportParsing();
	  aci_gap_terminate_gap_proc(GAP_OBSERVATION_PROC);

	   /* Read Controller Delay */
	  CAP_Broadcast_ReadSupportedControllerDelay(NUM_BIS, DATA_PATH_OUTPUT,
                                                 a_codec_id,
                                                 &controller_delay_min,
                                                 &controller_delay_max);

      /* Setup Audio Data Path */								
      CAP_Broadcast_SetupAudioDataPath(NUM_BIS, bis_conn_handle, direction, 
                                       codec_id, controller_delay, 
                                       DATA_PATH_CIRCULAR_BUF, 
                                       datapath_param_len, &datapath_param);

	  break;
    }

    case PBP_BROADCAST_AUDIO_UP_EVT:
    {
	  /* Audio data path is up */
      break;
    }
  }
}

5.2.3. Stop public broadcast sink synchronization

1. To stop the synchronization to a public broadcast source from a public broadcast sink, terminate the BIG and the periodic advertising synchronization.

PBP_PBK_StopPASync(PBPAPP_Context.PASyncHandle);
PBP_PBK_StopBIGSync(BIG_HANDLE);

2. The PBP_BROADCAST_AUDIO_DOWN_EVT is generated. Deinitialize the audio peripherals.

void PBP_Notification(PBP_Notification_Evt_t *pNotification)
{
  switch(pNotification->EvtOpcode)
  {
    case PBP_BROADCAST_AUDIO_DOWN_EVT:
    {
      /* Audio data path is down, deinit Audio Peripherals */
	  MX_AudioDeInit();
	  break;
    }
  }
}

6. PBP demonstrator using STM32WBA

The STM32WBA Cube firmware allows to easily build and deploy public broadcast sources and public broadcast sinks applications.

6.1. Hardware required

The PBP demonstrator requires the following hardware:

  • 3x STM32WBA55G-DK boards
  • 2x Music source with a 3.5mm jack output (Laptop or smartphone with a jack output). If a laptop is used as a source, use a ground loop isolator device or run the laptop on battery to avoid ground issue
  • 1x Music renderer with 3.5mm jack input (Headphones or speakers)

6.2. Setup

The setup is as follows:

PBP Demonstrator Setup
Connectivity PBP Setup.png

6.3. Project architecture

The figure below represents the firmware architecture of the public broadcast profile example projects inside the STM32CubeWBA MCU Package.

STM32WBA PBP Firmware Architecture
Connectivity PBP Project Architecture.png

6.4. PBP source configuration

Following is a summary of the main changes possible on the PBP_Source project

PBP_Source project configuration
Connectivity PBP Source Config.png


Setting File Description
BROADCAST_SOURCE_BAP_CONFIG pbp_app.c Configuration index in BroadcastQoSConf array. 0 corresponds to 8_2_1 configuration, 1 to 8_2_2, … Commonly used values are 3 (16_2_1 config), 5 (24_2_1 config), and 13 (48_4_1 config). For more details about the configurations, refer to the Introduction to Bluetooth LE audio wiki page.
BROADCAST_SOURCE_FRAME_BLOCK_PER_SDU pbp_app.c Number of concatenated codec frames sent at the same time. May be 1 or 2. Setting it to 2 reduces the number of events needed to send the packets which can improve reliability but increase the latency.
BROADCAST_SOURCE_NUM_BIS pbp_app.c Number of BIS used for the Broadcast Source. May be 1 or 2. Setting it to 1 reduces the number of events needed to send the packets which can improve reliability but a value of 2 reduces the strain on Sink devices synchronizing to mono audio.
BROADCAST_SOURCE_CHANNEL_ALLOC_1 pbp_app.c Audio Channel Allocation of BIS #1. Bitfield of Audio Location values. Commonly used values are 0x01 for "Front Left" location or 0x03 for "Front Left + Front Right" location.
BROADCAST_SOURCE_CHANNEL_ALLOC_2 pbp_app.c Audio Channel Allocation of BIS #2. Bitfield of Audio Location values. Commonly used value is 0x02 for "Front Right". Ignored if "BROADCAST_SOURCE_NUM_BIS" is 1.
BROADCAST_CONTROLLER_DELAY pbp_app.c Controller delay value. Refer to "STM32WBA LC3 codec and audio data path" wiki page[3]
BAP_BROADCAST_MAX_TRANSPORT_LATENCY pbp_app.c Maximum Transport Latency value. Refer to "STM32WBA LC3 codec and audio data path" wiki page[3]
BAP_BROADCAST_ENCRYPTION pbp_app.c 1 to enable broadcast encryption, 0 to disable it
BIG_HANDLE pbp_app.c Handle of the BIG used for the Broadcast
STREAMING_AUDIO_CONTEXT pbp_app.c Audio Context Type of the Broadcast Source. Common values are AUDIO_CONTEXT_MEDIA (0x0004) and AUDIO_CONTEXT_CONVERSATIONAL (0x0002). Refer to Assigned Numbers section 6.12.3 for more details[4]
APPEARANCE pbp_app.c Appearance value of the Broadcast. Describe the physical appearance of the device. Refer to Assigned Number section 2.6 for more details[4]
BROADCAST_NAME_LENGTH pbp_app.c Length of the Broadcast Name
aPBPAPP_BroadcastCode pbp_app.c Broadcast Code used when BAP_BROADCAST_ENCRYPTION is set to 1. Broadcast Sinks will have to know the Broadcast Code in order to decrypt the stream
aPBPAPP_BroadcastName pbp_app.c Broadcast Name as described in 1.3
BAP_BROADCAST_SOURCE_ID pbp_app.h ID of the Broadcast Source. This value is ST specific and permits the PBP Sink to identify the PBP Source with a custom static identifier. Not to be confused with the Broadcast ID which is generated randomly by the BAP layer at Broadcast Source creation.

6.5. PBP sink configuration

Following is a summary of the main modification possible on the PBP_Sink project

PBP_Sink project configuration
Connectivity PBP Sink Config.png


Setting File Description
BROADCAST_CONTROLLER_DELAY pbp_app.c Controller delay value. Refer to "STM32WBA LC3 codec and audio data path" wiki page[3]
BAP_BROADCAST_ENCRYPTION pbp_app.c 1 to enable broadcast decryption, 0 to disable it
BIG_HANDLE pbp_app.c Handle of the BIG used for the Broadcast
aPBPAPP_BroadcastCode pbp_app.c Broadcast Code used when BAP_BROADCAST_ENCRYPTION is set to 1. Broadcast Code must match the one set on the PBP Source
aSourceIdList pbp_app.c List of Source IDs to cycle through using the joystick

6.6. PBP source advertising details

The following schematic details the content of a public broadcast source advertising data.

Detail of PBP Source Advertising Data
Connectivity PBP Source Advertising.png

7. References