nRF5 SDK v17.0.0
USB audio class module
This information applies to the following SoCs: nRF52820, nRF52833, and nRF52840.

This module allows to create and handle USB audio class instances. For detailed information on the USB audio class, refer to these specification documents:

Defining audio class descriptors

A typical audio class has one control interface and one streaming interface. Before you can declare a class instance, you must first properly define the audio class descriptors:

Example 1: Layout of headphone descriptors:

AUDIO_CONTROL_INTERFACE
AUDIO_CONTROL_HEADER
INPUT_TERMINAL
FEATURE_UNIT
OUTPUT_TERMINAL
AUDIO_STREAMING_INTERFACE_ALTERNATE0
AUDIO_STREAMING_INTERFACE_ALTERNATE1
AS_FORMAT_III
EP_GENERAL
ISO_EP_OUT

Example 2: Layout of microphone descriptors:

AUDIO_CONTROL_INTERFACE
AUDIO_CONTROL_HEADER
INPUT_TERMINAL
FEATURE_UNIT
OUTPUT_TERMINAL
AUDIO_STREAMING_INTERFACE_ALTERNATE0
AUDIO_STREAMING_INTERFACE_ALTERNATE1
AS_FORMAT_I
EP_GENERAL
ISO_EP_IN

The difference between headphones and microphone descriptors is in the direction of the isochronous endpoint descriptor. Microphone has the IN direction defined (from the USB device to the host). For headphones, the isochronous endpoint direction is set to OUT (from the host to the USB device). Both of these classes have two audio streaming descriptors:

Example: Descriptors for an audio class (Headphones: 2 channels, Fs = 48KHz, Format 16bit PCM):

/* Headphones descriptors */
/**
@brief Audio class specific format III descriptor
*/
APP_USBD_AUDIO_AS_FORMAT_III_DSC( /* Format type 3 descriptor */
2, /* Number of channels */
2, /* Subframe size */
16, /* Bit resolution */
1, /* Frequency type */
APP_USBD_U24_TO_RAW_DSC(48000)) /* Frequency */
);
/**
@brief Audio class input terminal descriptor
*/
1, /* Terminal ID */
2, /* Number of channels */
HP_TERMINAL_CH_CONFIG()) /* Channels config */
);
/**
@brief Audio class output terminal descriptor
*/
3, /* Terminal ID */
2) /* Source ID */
);
/**
@brief Audio class feature unit descriptor
*/
2, /* Unit ID */
1, /* Source ID */
HP_FEATURE_CONTROLS()) /* List of controls */
);

Defining audio classes

When all descriptors are correctly defined, you can define the audio class:

#define HP_INTERFACES_CONFIG() APP_USBD_AUDIO_CONFIG_OUT(0, 1)
APP_USBD_AUDIO_GLOBAL_DEF(m_app_audio_headphone,
HP_INTERFACES_CONFIG(),
hp_audio_user_ev_handler,
&m_hp_form_desc,
&m_hp_inp_desc,
&m_hp_out_desc,
&m_hp_fea_desc,
0,
192,
1
);

If you wish to skip an optional descriptor, use NULL:

APP_USBD_AUDIO_GLOBAL_DEF(m_app_audio_headphone,
HP_INTERFACES_CONFIG(),
hp_audio_user_ev_handler,
&m_hp_form_desc,
&m_hp_inp_desc,
NULL,
&m_hp_fea_desc,
0,
192,
1
);

Using SOF events to start transfers in the audio class

To work with isochronous endpoints, you must use a SOF event. This means that the SOF event must be activated when calling the app_usbd_init function:

static void usbd_user_ev_handler(app_usbd_event_type_t event)
{
// ... State processing
}
int main(void)
{
static const app_usbd_config_t usbd_config = {
.ev_state_proc = usbd_user_ev_handler,
.enable_sof = true
};
// ... Some hardware initiation
ret = app_usbd_init(&usbd_config);
// ... Rest of the code
}

When timing is critical, it is possible to prepare for processing the SOF event directly in the interrupt - before the event queue is processed:

static void usbd_user_ev_handler(app_usbd_event_type_t event)
{
// ... State processing
}
void usbd_user_isr_handler(app_usbd_internal_evt_t const * const p_event, bool queued);
{
// ... Events processed directly in interrupt
}
int main(void)
{
static const app_usbd_config_t usbd_config = {
.ev_isr_handler = usbd_user_isr_handler,
.ev_state_proc = usbd_user_ev_handler,
.enable_sof = true
};
// ... Some hardware initiation
ret = app_usbd_init(&usbd_config);
// ... Rest of the code
}

When the SOF event is processed, transfer may be started in any direction inside this event. The following example code shows transferring of audio data from the headphones to the buffer:

static void usbd_user_ev_handler(app_usbd_event_type_t event)
{
switch(event)
{
{
{
break;
}
size_t rx_size = app_usbd_audio_class_rx_size_get(&m_app_audio_headphone.base);
m_temp_buffer_size = rx_size;
if (rx_size > 0)
{
ret = app_usbd_audio_class_rx_start(&m_app_audio_headphone.base, m_temp_buffer, rx_size);
if (NRF_SUCCESS != ret)
{
// Error handling
}
}
break;
}
// ... Rest of events
}
}

Event handling in the audio class

Audio class defines 3 events:

You can pass an event handler function to the class instance definition. The following is an example of a correct audio class user event handler prototype:

void audio_user_ev_handler(app_usbd_class_inst_t const * p_inst,

In this example code, the APP_USBD_AUDIO_USER_EVT_RX_DONE event is used to start the transfer of the data received from headphone output to the microphone input:

static void hp_audio_user_ev_handler(app_usbd_class_inst_t const * p_inst,
{
app_usbd_audio_t const * p_audio = app_usbd_audio_class_get(p_inst);
switch (event)
{
// ... Other events
{
/* Block from headphones copied into buffer, send it into microphone input */
ret = app_usbd_audio_class_tx_start(&m_app_audio_microphone.base, m_temp_buffer, m_temp_buffer_size);
if (NRF_SUCCESS == ret)
{
bsp_board_led_invert(LED_AUDIO_RX);
}
break;
}
}
}

Registering an audio class to the USBD library

After the whole class instance is defined, you must register the new class to the USBD library:

ret = app_usbd_init();
ASSERT(ret == NRF_SUCCESS);
app_usbd_class_inst_t const * class_inst_hp = app_usbd_audio_class_inst_get(&m_app_audio_headphone);
ret = app_usbd_controller_class_append(class_inst_hp);
ASSERT(ret == NRF_SUCCESS);

Documentation feedback | Developer Zone | Subscribe | Updated