1. Introduction
The Bluetooth® LE Audio Capture and Rendering Control section encompasses
- the Volume Control Profile (VCP)
- the Volume Control Service (VCS)
- the Volume Offset Control Service (VOCS)
- the Audio Input Control Service (AICS)
- the Microphone Control Profile (MICP)
- the Microphone Control Service (MICS)
The VCP, VCS and VOCS are used to control audio rendering, the MICP and MICS are used to control audio capture. AICS can be used for both capture and rendering to control audio inputs.
2. Volume Control Profile (VCP)
The Volume Control Profile allows to remotely control and monitor the volume of a device rendering audio. It's been specified by the Bluetooth® SIG and the full specification is accessible on their website[1].
The VCP relies on 3 different services:
- the Volume Control Service (VCS)
- the Volume Offset Control Service (VOCS)
- the Audio Input Control Service (AICS).
2.1. VCP Roles
The VCP introduces 2 roles:
- Volume Renderer: which receives and renders audio streams. Example of Volume Renderer devices are Headsets, Speakers, Earbuds, TV or Hearing Aids
- Volume Controller: which connects to Volume Renderers and controls their volume. This role is typically handled by a smartphone
The Volume Renderer is a VCS Server and optionally a VOCS and/or AICS Server.
2.2. Volume Control Service (VCS)
The Volume Control Service is used by devices to expose the general volume of a device. It contains the absolute volume value (0-255), and the mute value (0-1). The specification of the VCS is accessible on the Bluetooth website[2].
2.3. Volume Offset Control Service (VOCS)
The Volume Offset Control Service is used by devices to expose the volume offset of an audio output. An instance of the VOCS can be created for each distinct audio output, permitting, for example, to set a higher volume on one of the outputs. The specification of the VOCS is accessible on the Bluetooth website[3]
2.4. Audio Input Control Service (AICS)
The Audio Input Control Service exposes the state of an audio input. An instance of the AICS can be created for each distinct audio input on a device. It contains a gain value (0-255), a mute value (0-1) and a gain mode value (Manual/Automatic). The specification of the AICS is accessible on the Bluetooth website[4]
2.5. Volume Control topologies examples
Below are two examples of topologies using VCP inside an audio device.
The first topology only uses VCS to allow the control of the volume of the two speakers in headphones.
The second topology uses
- VCS to control the global volume of the two speakers
- VOCS to adjust the offset volume of each headset, permitting for example to increase the gain of one of the speakers
- AICS to control the audio input sources of the headphones, permitting to mute unwanted inputs and adjust the gain of each input
3. Microphone Control Profile (MICP)
The Microphone Control Profile allows to remotely control and monitor the state of a microphone. It's been specified by the Bluetooth® SIG and the full specification is accessible on their website[5].
The MICP relies on 2 different services:
- the Microphone Control Service (MICS)
- the Audio Input Control Service (AICS).
3.1. MICP Roles
The MICP introduces 2 roles:
- Microphone Device: captures audio and sends it over audio streams. Example of Microphone Devices are Headsets, Earbuds, Microphones or Hearing Aids
- Microphone Controller: connects to Microphone Devices and controls their microphone state. This role is typically handled by a smartphone
The Microphone Device is a MICS Server and optionally a AICS Server.
3.2. Microphone Control Service (MICS)
The Microphone Control Service exposes the microphone status of a device. It contains only a mute value (0-1), allowing a remote device to mute or unmute the microphone. The specification of the MICS is accessible on the Bluetooth website[6].
3.3. Audio Input Control Service (AICS)
When used along the MICP, the Audio Input Control Service exposes the state of a microphone. An instance of the AICS can be created for each distinct microphone on a device. It contains a gain value (0-255), a mute value (0-1) and a gain mode value (Manual/Automatic). On devices containing multiple microphones, it permits to mute and adjust the gain value on each microphone separately. The specification of the AICS is accessible on the Bluetooth website[7].
3.4. Microphone Control topologies examples
Below are two examples of topologies using MICP inside an audio device.
The first topology only uses MICS to allow control of the mute status of the microphone.
The second topology uses
- MICS to control the mute status of both microphones
- AICS to control the each microphone gain and mute status independently
4. Local Capture and Rendering
4.1. Local Volume Control
The following section explains how to use the VCP on a Volume Renderer to inform remote devices of the volume state of the device.
4.1.1. Initialize a VCP Renderer device
1. First of all, initialize a VCP_Config_t structure with generic VCP parameters: role, number of links, initial volume and mute settings, volume step size, number of AIC and VOC instances and RAM allocation (see STM32WBA Architecture and Integration wiki page[8]).
VCP_Config_t APP_VCP_Config = {0};
/* Configure VCP */
APP_VCP_Config.Role = VCP_ROLE_RENDERER;
APP_VCP_Config.MaxNumBleLinks = CFG_BLE_NUM_LINK;
APP_VCP_Config.Renderer.InitialMuteState = 0u;
APP_VCP_Config.Renderer.InitialVolumeSetting = 20u;
APP_VCP_Config.Renderer.VolumeStepSize = 1u;
APP_VCP_Config.Renderer.NumAICInst = 2;
APP_VCP_Config.Renderer.NumVOCInst = 2;
APP_VCP_Config.Renderer.pStartRamAddr = (uint8_t*)aRenderMemBuffer;
APP_VCP_Config.Renderer.RamSize = BLE_VCP_RDR_DYN_ALLOC_SIZE;
2. (Optional) Configure the AIC instances. In this example 2 AIC instances are configured: Bluetooth and Line In
/* Initialize and configure 2 Audio Input Control (AIC) Instances: Bluetooth and Line In */
VCP_AIC_InitInst_t aAICInst[2];
for (uint8_t inst = 0u ; inst < 2 ; inst++)
{
aAICInst[inst].InstID = inst;
aAICInst[inst].Status = 0x00u;
aAICInst[inst].State.GainSetting = 0;
aAICInst[inst].State.Mute = VCP_AIC_MUTE_DISABLED;
aAICInst[inst].State.GainMode = VCP_AIC_GAIN_MODE_MANUAL_ONLY;
aAICInst[inst].Prop.GainSettingUnits = 10u;
aAICInst[inst].Prop.GainSettingMin = -20;
aAICInst[inst].Prop.GainSettingMax = 10;
aAICInst[inst].MaxDescriptionLength = APP_VCP_RDR_AIC_DESCRIPTION_LENGTH;
if (inst == 0u)
{
aAICInst[inst].AudioInputType = AUDIO_INPUT_BLUETOOTH;
aAICInst[inst].pDescription = (uint8_t *)"Bluetooth";
aAICInst[inst].DescriptionLength = 9u;
}
else
{
aAICInst[inst].AudioInputType = AUDIO_INPUT_ANALOG;
aAICInst[inst].pDescription = (uint8_t *)"Line In";
aAICInst[inst].DescriptionLength = 7u;
}
}
APP_VCP_Config.Renderer.pAICInst = &aAICInst[0u];
3. (Optional) Configure the VOC instances. In this example, 2 VOC instances are configured for the left and right speakers
/* Initialize and configure 2 Volume Offset (VOC) Instances: Right Speaker and Left Speaker */
VCP_VOC_InitInst_t aVOCInst[2];
for (uint8_t inst = 0u ; inst < 2 ; inst++)
{
aVOCInst[inst].InstID = inst;
if (inst == 0u)
{
aVOCInst[inst].VolumeOffset = -23;
aVOCInst[inst].AudioLocation = FRONT_LEFT;
aVOCInst[inst].MaxDescriptionLength = APP_VCP_RDR_VOC_DESCRIPTION_LENGTH;
aVOCInst[inst].pDescription = (uint8_t *)"Left Speaker";
aVOCInst[inst].DescriptionLength = 12u;
}
else
{
aVOCInst[inst].VolumeOffset = -23;
aVOCInst[inst].AudioLocation = FRONT_RIGHT;
aVOCInst[inst].MaxDescriptionLength = APP_VCP_RDR_VOC_DESCRIPTION_LENGTH;
aVOCInst[inst].pDescription = (uint8_t *)"Right Speaker";
aVOCInst[inst].DescriptionLength = 13u;
}
}
APP_VCP_Config.Renderer.pVOCInst = &aVOCInst[0u];
4. Finally, initialize the VCP through the CAP, along with other audio profiles
/* Initialize CAP */
CAP_Init(&APP_CAP_Config,
&APP_BAP_Config,
&APP_VCP_Config,
&APP_MICP_Config,
&APP_CCP_Config,
&APP_MCP_Config,
&APP_CSIP_Config);
4.1.2. Control a local VCP Renderer
To control a VCP Renderer role initialized locally, multiple APIs can be found in vcp.h. Following are examples of commands that may be frequently used:
/* Set the absolute volume value */
VCP_RENDER_SetAbsVolume(0x7F);
/* Set Mute State */
VCP_RENDER_SetMuteState(0);
/* Increase Volume by one step size*/
VCP_RENDER_IncreaseVolume();
/* Decrease Volume by one step size */
VCP_RENDER_DecreaseVolume();
VCP Events are handled via Meta Events through the CAP Notification function. Handle the CAP Notification CAP_VCP_META_EVT and decode it depending on the VCP Event Code.
/* CAP Notification handler */
void APP_CAPNotification(CAP_Notification_Evt_t *pNotification)
{
switch(pNotification->EvtOpcode)
{
case CAP_VCP_META_EVT:
{
VCP_Notification_Evt_t *p_vcp_evt = (VCP_Notification_Evt_t *)pNotification->pInfo;
VCP_MetaEvt_Notification(p_vcp_evt);
break;
}
}
}
/* Function decoding a VCP Notification */
void VCP_MetaEvt_Notification(VCP_Notification_Evt_t *pNotification)
{
switch(pNotification->EvtOpcode)
{
case VCP_RENDERER_UPDATED_VOLUME_STATE_EVT:
{
VCP_VolumeState_Evt_t *p_info = (VCP_VolumeState_Evt_t *)pNotification->pInfo;
LOG_INFO_APP("Updated Volume State :\n");
LOG_INFO_APP(" Volume Setting : %d\n",p_info->VolSetting);
LOG_INFO_APP(" Mute : %d\n",p_info->Mute);
LOG_INFO_APP(" Change Counter : %d\n",p_info->ChangeCounter);
break;
}
case VCP_RENDERER_UPDATED_AUDIO_INPUT_STATE_EVT:
{
VCP_AudioInputState_Evt_t *p_info = (VCP_AudioInputState_Evt_t *)pNotification->pInfo;
LOG_INFO_APP("Updated Audio Input State :\n");
LOG_INFO_APP(" Instance ID : %d\n",p_info->AICInst);
LOG_INFO_APP(" Gain Setting : %d\n",p_info->State.GainSetting);
LOG_INFO_APP(" Mute : %d\n",p_info->State.Mute);
LOG_INFO_APP(" Gain Mode : %d\n",p_info->State.GainMode);
LOG_INFO_APP(" Change Counter : %d\n",p_info->ChangeCounter);
break;
}
case VCP_RENDERER_UPDATED_AUDIO_INPUT_DESCRIPTION_EVT:
{
VCP_AudioDescription_Evt_t *p_info = (VCP_AudioDescription_Evt_t *)pNotification->pInfo;
LOG_INFO_APP("Updated Audio Input Description :\n");
LOG_INFO_APP(" Instance ID : %d\n",p_info->Inst);
LOG_INFO_APP(" Description : ");
Print_String(p_info->pData, p_info->DataLength);
LOG_INFO_APP("\n");
break;
}
case VCP_RENDERER_UPDATED_VOLUME_OFFSET_STATE_EVT:
{
VCP_VolumeOffsetState_Evt_t *p_info = (VCP_VolumeOffsetState_Evt_t *)pNotification->pInfo;
LOG_INFO_APP("[VOC %d] Updated Volume Offset State :\n",p_info->VOCInst);
LOG_INFO_APP(" Volume Offset: %d \n",p_info->VolumeOffset);
LOG_INFO_APP(" Change Counter : %d\n",p_info->ChangeCounter);
break;
}
case VCP_RENDERER_UPDATED_AUDIO_OUTPUT_LOCATION_EVT:
{
VCP_AudioLocation_Evt_t *p_info = (VCP_AudioLocation_Evt_t *)pNotification->pInfo;
LOG_INFO_APP("[VOC %d] Updated Audio Location : 0x%08X\n",p_info->VOCInst,p_info->AudioLocation);
break;
}
case VCP_RENDERER_UPDATED_AUDIO_OUTPUT_DESCRIPTION_EVT:
{
VCP_AudioDescription_Evt_t *p_info = (VCP_AudioDescription_Evt_t *)pNotification->pInfo;
LOG_INFO_APP("Updated Audio Output Description :\n");
LOG_INFO_APP(" Instance ID : %d\n",p_info->Inst);
LOG_INFO_APP(" Description : ");
Print_String(p_info->pData, p_info->DataLength);
LOG_INFO_APP("\n");
break;
}
default:
break;
}
}
4.2. Local Microphone Control
The following section explains how to use the MICP on a Microphone Device to inform remote devices of the microphone state of the device.
4.2.1. Initialize a MICP Device
1. First of all, initialize a MICP_Config_t structure with generic MICP parameters: role, number of links, initial mute settings, number of AIC and instances and RAM allocation (see STM32WBA Architecture and Integration wiki page[8]).
/* Configure MICP */
MICP_Config_t APP_MICP_Config = {0};
APP_MICP_Config.Role = MICP_ROLE_DEVICE;
APP_MICP_Config.MaxNumBleLinks = CFG_BLE_NUM_LINK;
APP_MICP_Config.Device.InitialMuteState = 0u;
APP_MICP_Config.Device.NumAICInst = 2;
APP_MICP_Config.Device.pStartRamAddr = (uint8_t*)aMicDevMemBuffer;
APP_MICP_Config.Device.RamSize = BLE_MICP_DEV_DYN_ALLOC_SIZE;
2. (Optional) Configure the AIC instances. In this example 2 AIC instances are configured: Microphone 1 and Microphone 2
/* Initialize and configure 2 Audio Input Control (AIC) Instances: Microphone 1 and Microphone 2 */
MICP_AIC_InitInst_t aMicAICInst[2];
for (uint8_t inst = 0u ; inst < APP_MICP_DEV_NUM_AIC_INSTANCES ; inst++)
{
aMicAICInst[inst].InstID = inst;
aMicAICInst[inst].Status = 0x00u;
aMicAICInst[inst].State.GainSetting = 0;
aMicAICInst[inst].State.Mute = MICP_AIC_MUTE_DISABLED;
aMicAICInst[inst].State.GainMode = MICP_AIC_GAIN_MODE_MANUAL_ONLY;
aMicAICInst[inst].Prop.GainSettingUnits = 10u;
aMicAICInst[inst].Prop.GainSettingMin = -20;
aMicAICInst[inst].Prop.GainSettingMax = 10;
aMicAICInst[inst].AudioInputType = AUDIO_INPUT_MICROPHONE;
aMicAICInst[inst].MaxDescriptionLength = APP_MICP_DEV_AIC_DESCRIPTION_LENGTH;
if (inst == 0u)
{
aMicAICInst[inst].pDescription = (uint8_t *)"Microphone 1";
aMicAICInst[inst].DescriptionLength = 12u;
}
else
{
aMicAICInst[inst].pDescription = (uint8_t *)"Microphone 2";
aMicAICInst[inst].DescriptionLength = 12u;
}
}
APP_MICP_Config.Device.pAICInst = &aMicAICInst[0u];
3. Finally, initialize the MICP through the CAP, along with other audio profiles
/* Initialize CAP */
CAP_Init(&APP_CAP_Config,
&APP_BAP_Config,
&APP_VCP_Config,
&APP_MICP_Config,
&APP_CCP_Config,
&APP_MCP_Config,
&APP_CSIP_Config);
4.2.2. Control a local MICP Device
To control a MICP Device role initialized locally, multiple APIs can be found in micp.h. The main API to use is the Set Mute API:
MICP_DEVICE_SetMute(MICROPHONE_MUTE_MUTED);
MICP Events are handled via Meta Events through the CAP Notification function. Handle the CAP Notification CAP_MICP_META_EVT and decode it depending on the MICP Event Code.
/* CAP Notification handler */
void APP_CAPNotification(CAP_Notification_Evt_t *pNotification)
{
switch(pNotification->EvtOpcode)
{
case CAP_MICP_META_EVT:
{
MICP_Notification_Evt_t *p_micp_evt = (MICP_Notification_Evt_t *)pNotification->pInfo;
MICP_MetaEvt_Notification(p_micp_evt);
break;
}
}
}
/* Function decoding a MICP Notification */
void MICP_MetaEvt_Notification(MICP_Notification_Evt_t *pNotification)
{
switch(pNotification->EvtOpcode)
{
case MICP_DEVICE_UPDATED_MUTE_EVT:
{
LOG_INFO_APP("Received new MICP Mute value: %d\n", pNotification->pInfo[0u]);
break;
}
case MICP_DEVICE_UPDATED_AUDIO_INPUT_STATE_EVT:
{
MICP_AudioInputState_Evt_t *p_info = (MICP_AudioInputState_Evt_t *)pNotification->pInfo;
APP_DBG_MSG("Updated Audio Input State :\n");
APP_DBG_MSG(" Instance ID : %d\n",p_info->AICInst);
APP_DBG_MSG(" Gain Setting : %d\n",p_info->State.GainSetting);
APP_DBG_MSG(" Mute : %d\n",p_info->State.Mute);
APP_DBG_MSG(" Gain Mode : %d\n",p_info->State.GainMode);
APP_DBG_MSG(" Change Counter : %d\n",p_info->ChangeCounter);
break;
}
case MICP_DEVICE_UPDATED_AUDIO_INPUT_DESCRIPTION_EVT:
{
MICP_AudioDescription_Evt_t *p_info = (MICP_AudioDescription_Evt_t *)pNotification->pInfo;
APP_DBG_MSG("Updated Audio Input Description :\n");
APP_DBG_MSG(" Instance ID : %d\n",p_info->Inst);
APP_DBG_MSG(" Description : %s\n",p_info->pData);
break;
}
default:
break;
}
}
5. Capture and rendering control procedures
The procedures associated to the capture and rendering control are specified in the section 7.3.2 of the Common audio profile[9]
5.1. Volume control procedures
The volume control procedures (Change volume procedure, Change volume offset procedure and Change volume mute state procedure), described in the CAP specification, are performed by the CAP Commander in volume controller role to manage the volume on the Acceptors of a coordinated set acting in the VCP renderer role. The procedures are initiated by the specific CAP functions listed in Table 4.9.
Note that the Generic Audio Framework offers volume control functions, declared in vcp.h header file, which could be used in the application layer to perform operations not specified in the procedure defined in the common audio profile specification:
Functions | Description | Header File |
---|---|---|
CAP_VolumeController_StartSetVolumeProcedure() | Start procedure to change the volume level on all the Acceptors of the coordinated set acting in the VCP volume renderer role. (Section 7.3.2.2[9]) | cap.h |
CAP_VolumeController_StartSetVolumeMuteStateProcedure() | Start the procedure to change the (Volume) mute state on all the Acceptors of the coordinated set acting in the VCP volume renderer role (section 7.3.2.4[9]) | cap.h |
CAP_VolumeController_StartSetVolumeOffsetProcedure() * | Start procedure to set the volume offset relative to the volume level set by the change volume procedure on the Acceptors of the coordinated set acting in the VCP renderer role. (Section 7.3.2.3[9]) | cap.h |
Table 4.9 Functions for Volume control procedure management
(*) Not yet available
During the volume control procedures, the CAP Commander receives volume control events, defined invcp_types.h file, through the CAP_VCP_META_EVT event, and, once the procedure is complete, a dedicated CAP event defined in cap_types.h file. The Table 4.10 describes the events received by the CAP Commander during the CAP volume control procedures.
Functions | Description | Header File |
---|---|---|
VCP_CONTROLLER_UPDATED_VOLUME_STATE_EVT | Volume state has changed on a CAP Acceptor of the coordinated set Note: This event occurs during the change volume procedure and the change volume mute state procedure. |
vcp_types.h |
CAP_SET_VOLUME_PROCEDURE_COMPLETE_EVT | CAP change volume procedure is complete for all the CAP Acceptors of the coordinated set | cap_types.h |
CAP_SET_VOLUME_MUTE_STATE_PROCEDURE_COMPLETE_EVT | CAP change volume mute state procedure is complete for all the CAP Acceptors of the coordinated set | cap_types.h |
VCP_CONTROLLER_VOLUME_OFFSET_STATE_EVT | Volume offset state has changed on a volume offset instance of a CAP Acceptor of the coordinated set Note: This event occurs during the change volume offset procedure. |
vcp_types.h |
CAP_SET_VOLUME_OFFSET_PROCEDURE_COMPLETE_EVT* | CAP set volume offset procedure is complete for all the CAP Acceptors of the coordinated set. | cap_types.h |
Table 4.10 Events of volume control procedures
(*) Not yet available
TheFigure 4.6 shows the sequence of CAP change volume procedure initiated by a CAP Initiator with a coordinated set composed of two set members, CAP Acceptor 1 and CAP Acceptor 2. Note that the CAP_VolumeController_StartSetVolumeProcedure() is called with the connection handle of the CAP Acceptor 1 and the Generic Audio Framework is responsible to send the change volume operation request to the other set members of the coordinated set.
Figure 4.6 CAP change volume procedure sequence diagram |
5.2. Microphone control procedures
The microphone control procedures (Change microphone volume mute state procedure and Change microphone gain setting procedure), described in the CAP specification, are performed by the CAP Commander in the microphone controller role to manage the microphone volume on the Acceptors of a coordinated set acting in the MICP device role. The procedures are initiated by the specific CAP functions listed in Table 4.11.
Note that the Generic Audio Framework offers microphone control functions, declared in micp.h header file, which could be used in the application layer to perform operations not specified in the procedure defined in the common audio profile specification:
Functions | Description | Header File |
---|---|---|
CAP_MicrophoneController_StartSetMuteProcedure() | Start the procedure to change the microphone mute value on all the Acceptors of the coordinated set acting in the MICP microphone device role. (Section 7.3.2.5[9]) | cap.h |
CAP_MicrophoneController_StartSetGainSettingProcedure() | Start procedure to set the microphone gain setting on the Acceptors of the coordinated set acting in the MICP device role. (Section 7.3.2.6[9]) | cap.h |
Table 4.11 Functions for microphone control procedure management
During the microphone control procedures, the CAP Commander receives microphone control events defined inmicp_types.h file, through the CAP_MICP_META_EVT event, and, once the procedure is complete, a dedicated CAP event defined in cap_types.h file. The Table 4.12 describes the events received by the CAP Commander during the CAP microphone control procedures.
Event | Description | Header File |
---|---|---|
MICP_CONTROLLER_UPDATED_MUTE_EVT | Microphone state has changed on a CAP Acceptor of the coordinated set Note: This event occurs during the change microphone volume mute state procedure. |
micp_types.h |
CAP_SET_MICROPHONE_MUTE_COMPLETE_EVT | CAP change microphone volume mute state procedure is complete for all the CAP Acceptors of the coordinated set | cap_types.h |
MICP_DEVICE_UPDATED_AUDIO_INPUT_STATE_EVT | Audio input state of an audio input instance has changed on a CAP Acceptor of the coordinated set Note: This event occurs during the change gain setting procedure. |
micp_types.h |
CAP_SET_MICROPHONE_GAIN_SETTING_COMPLETE_EVT | CAP change gain setting procedure is complete for all the CAP Acceptors of the coordinated set | cap_types.h |
Table 4.11 Events of microphone control procedures
TheFigure 4.7 shows the sequence of CAP change microphone volume mute state procedure initiated by a CAP Initiator with a coordinated set composed of two set members, CAP Acceptor 1 and CAP Acceptor 2. Note that the CAP_MicrophoneController_StartSetMuteProcedure() is called with the connection handle of the CAP Acceptor 1 and the Generic Audio Framework is responsible to send the change microphone volume mute state operation request to the other set members of the coordinated set.
Figure 4.7 CAP change microphone volume mute state procedure sequence diagram |
6. References