USB Audio
USB audio is a technology that allows high-quality digital audio input and output by use of a USB interface for the transmission of audio data. It is often utilized in gadgets like microphones and headphones to offer superior sound quality and easy connectivity. Plug-and-play ease of use, robust interoperability, and versatility make USB audio devices ideal for a range of uses, including communication, professional recording, and personal pleasure. The purpose of this document is to give an overview of the USB audio application. The USB audio project implements a very simple USB audio device. The USB User Guide has more details on these files and USB.
Requirements
The sample supports the following development kits:
Hardware Platforms |
Board Name |
Build Target |
---|---|---|
RTL87x3E HDK |
RTL87x3E EVB |
usb_audio_4M_dual_bank0 usb_audio_16M_dual_bank0 |
RTL87x3D HDK |
RTL87x3D EVB |
usb_audio_8M_bank0 usb_audio_16M_bank0 usb_audio_cs_16M_bank0 |
This sample project can be found under board\evb\usb_demo
in SDK folder structure. Developers can choose the project according to the Board Name
and choose the Build Target
according to the flash map.
When built for an xxx_4M_xxx
build target, the sample is configured to build and run with a 4M flash map.
When built for an xxx_8M_xxx
build target, the sample is configured to build and run with an 8M flash map.
When built for an xxx_16M_xxx
build target, the sample is configured to build and run with a 16M flash map.
The sample also requires a device running a USB audio function.
To quickly set up the development environment, please refer to the detailed instructions provided in Quick Start.
Configurations
APP configurable functions are defined in app_flags.h
or Keil project.
#define F_APP_USB_AUDIO_SUPPORT 1
#define F_APP_USB_HID_SUPPORT 1
The user can easily change the value of the macro definition to switch the function.
Building and Downloading
Take the project rtl87x3e_usb_audio.uvprojx
and target usb_audio_4M_dual_bank0
as an example, to build and run the sample with the Keil development environment, follow the steps listed below:
After a successful build, the APP bin file usb_audio_bank0_MP-v0.0.0.0-xxx.bin
will be generated in the directory usb_demo\bin\rtl87x3e\flash_4M_dualbank\bank0
.
Download APP bin into EVB board.
Press the reset button on the EVB board, and it will start the USB function.
Experimental Verification
After programming the sample to the EVB board, users can verify that the USB audio is successfully enumerated, turn on the EVB, and connect it to the host via a USB cable. Make sure that the USB audio functionality is implemented on the EVB and the system is properly configured. This may require software development and configuration on the EVB. Connect the EVB to the host using a USB cable. Ensure that the USB cable can transfer data correctly and that the EVB is recognized by the host.
Open Device Manager (Windows) or use the command-line tool (Linux) on the host to check if the connected USB device is enumerated correctly. In Device Manager, the USB device related to the EVB should be visible. If the EVB is successfully enumerated, proceed to the next step to test the USB audio functionality. This requires running appropriate audio testing software, such as an audio player, on the host. Make sure that the host is set to the correct audio output device. Open the audio player on the host and select the USB audio device as the output device. Play a piece of music or test audio and confirm if the audio is played from the audio output port on the EVB.
Code Overview
The USB audio application overview will be introduced according to the following parts:
The USB audio sample project overview will be introduced in the chapter Source Code Directory.
The USB audio sample project source code overview will be introduced in the chapter Source Code Overview.
Source Code Directory
This section describes the project directory and project structure. The reference files directory is as follows:
Project directory:
\sdk\board\evb\usb_demo
.Project source code directory:
\sdk\src\sample\usb_demo
.
Source files in the sample project are currently categorized into several groups as below.
└── Project: rtl87x3x_usb_audio
├── include ROM UUID header files. Users do not need to modify it
└── lib includes all binary symbol files that user application is built on
├── startup_rtl.c
└── system_rtl.c
├── cmsis The cmsis source code. Users do not need to modify it
└── usb The USB profiles source code
├── usb_audio_stream.c usb audio manage API
├── usb_audio1.c usb audio descriptor and some API
├── usb_dev.c usb device descriptor
└── usb_hid.c usb hid descriptor and some API
└── usb_demo The application source code
├── app_main.c
├── app_io_msg.c
├── app_ipc.c
├── app_usb.c
├── app_usb_audio.c
└── app_usb_audio_mmi.c
Source Code Overview
The following sections describe important parts of this application. The following sequence diagram shows how to use the audio class, and more details will be discussed in a later chapter:
USB audio sequence diagram is shown in the figure.
Initialization
The main function is invoked when the application is powered on or the chip is reset, and it performs the following initialization functions:
int main(void)
{
.....
os_msg_queue_create(&audio_io_queue_handle, "ioQ", MAX_NUMBER_OF_IO_MESSAGE, sizeof(T_IO_MSG));
os_msg_queue_create(&audio_evt_queue_handle, "evtQ", MAX_NUMBER_OF_RX_EVENT, sizeof(unsigned char));
app_init_timer(audio_evt_queue_handle, MAX_NUMBER_OF_APP_TIMER_MODULE);
board_init();
framework_init();
app_usb_init();
os_task_create(&app_task_handle, "app_task", app_task, NULL, 1024 * 3, 1);
os_sched_start();
return 0;
}
Please refer to app_main.c
for initialization function declaration.
USB Device Configuration Descriptor
A device descriptor describes general information about a USB device. It includes information that applies globally to the device and all of the device’s configurations. A USB device has only one device descriptor.
The device descriptor of a high-speed capable device has a version number of 2.0 (0200H). The bcdUSB field contains a BCD version number. The value of the bcdUSB field is 0xJJMN for version JJ.M.N (JJ: major version number, M: minor version number, N: sub-minor version number), e.g., version 2.1.3 is represented with the value 0x0213 and version 2.0 is represented with a value of 0x0200.
Field |
Description |
---|---|
bcdUSB |
USB Specification Release Number in Binary-Coded Decimal (2.10 is 210H) |
bDeviceClass |
Class code (assigned by the USB-IF) |
bDeviceSubClass |
Subclass code (assigned by the USB-IF) |
bDeviceProtocol |
Protocol code (assigned by the USB-IF) |
bMaxPacketSize0 |
Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid) |
idVendor |
Vendor ID (assigned by the USB-IF) |
idProduct |
Product ID (assigned by the manufacturer) |
bcdDevice |
Device release number in binary-coded decimal |
iManufacturer |
Index of string descriptor describing manufacturer |
iProduct |
Index of string descriptor describing product |
iSerialNumber |
Index of string descriptor describing the device’s serial number |
bNumConfigurations |
Number of possible configurations |
USB device application can configure the descriptor.
typedef enum
{
STRING_ID_UNDEFINED,
STRING_ID_MANUFACTURER,
STRING_ID_PRODUCT,
STRING_ID_SERIALNUM,
} T_STRING_ID;
static const T_USB_DEVICE_DESC usb_dev_desc =
{
......
.bMaxPacketSize0 = 0x40,
.idVendor = 0x0bda,
.idProduct = 0x8773,
.bcdDevice = 0x0200,
.iManufacturer = STRING_ID_MANUFACTURER,
.iProduct = STRING_ID_PRODUCT,
.iSerialNumber = STRING_ID_SERIALNUM,
.bNumConfigurations = 1,
}
static T_STRING dev_strings[] =
{
[0] =
{
.id = STRING_ID_MANUFACTURER,
.s = "RealTek",
},
[1] =
{
.id = STRING_ID_PRODUCT,
.s = "USB Audio Device",
},
[2] =
{
.id = STRING_ID_SERIALNUM,
.s = "0123456789A",
},
}
static T_USB_CONFIG_DESC usb_cfg_desc =
{
......
.bmAttributes = 0x80,
.bMaxPower = 0x32
};
void usb_dev_init(void)
{
usb_dev_driver_dev_desc_register((void *)&usb_dev_desc);
usb_dev_driver_cfg_desc_register((void *)&usb_cfg_desc);
usb_dev_driver_string_desc_register((void *)dev_stringtabs);
}
USB Audio Configuration Descriptor List
This application exposes the following features:
USB Audio Interface Descriptor
Need to configure revision of class specification - 1.0 and number of streaming interfaces.
Audio Interface Descriptor
Configure AudioControl Interface Descriptors.
Configure AudioStreaming Interface Descriptors.
Audio Endpoint Descriptor
AudioControl Endpoint Descriptors.
AudioStreaming Endpoint Descriptors.
Feature Unit Control
Volume Control.
Mute Control.
USB Audio Configuration Descriptor
In analogy to the device descriptor, an audio configuration descriptor is applicable only in the case of audio-only devices.
static const T_UAC1_OT_DESC output_terminal_desc0 =
{
.....
.bDescriptorSubtype = UAC1_OUTPUT_TERMINAL,
.bTerminalID = ID_OUTPUT_TERMINAL1,
.wTerminalType = UAC1_OUTPUT_TERMINAL_SPEAKER,
.....
};
static const T_UAC1_FMT_TYPE_I_DESC format_type_i_desc1 =
{
.bLength = sizeof(format_type_i_desc1),
.bDescriptorType = UAC1_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC1_FORMAT_TYPE,
.bFormatType = UAC1_FORMAT_TYPE_I_PCM,
.bNrChannels = UAC1_SPK_CHAN_NUM,
.bSubframeSize = UAC1_SPK_BIT_RES / 8,
.bBitResolution = UAC1_SPK_BIT_RES,
.bSamFreqType = UAC1_SPK_SAM_RATE_NUM,
.tSamFreq =
{
[0] = {[0] = 0x80, [1] = 0xBB, [2] = 0x00},
},
};
static T_UAC1_STD_ISO_EP_DESC out_ep_desc_hs =
{
.bLength = sizeof(T_UAC1_STD_ISO_EP_DESC),
.bDescriptorType = UAC1_DT_ENDPOINT,
.bEndpointAddress = UAC1_ISO_OUT_ENDPOINT_ADDRESS,
.bmAttributes = UAC1_EP_XFER_ISOC | UAC1_EP_SYNC_ADAPTIVE,
.wMaxPacketSize = 96 * 3 * 2, //TODO
.bInterval = 4,
.bRefresh = 0,
.bSynchAddress = 0,
};
static T_UAC1_STD_ISO_EP_DESC out_ep_desc_fs =
{
.bLength = sizeof(T_UAC1_STD_ISO_EP_DESC),
.bDescriptorType = UAC1_DT_ENDPOINT,
.bEndpointAddress = UAC1_ISO_OUT_ENDPOINT_ADDRESS,
.bmAttributes = UAC1_EP_XFER_ISOC | UAC1_EP_SYNC_ADAPTIVE,
.wMaxPacketSize = 96 * 3 * 2, //TODO
.bInterval = 1,
.bRefresh = 0,
.bSynchAddress = 0,
};
static int32_t vol_attr_set_spk(void *ctrl, uint8_t cmd, int value)
{
......
}
static int32_t vol_attr_get_spk(void *ctrl, uint8_t cmd)
{
......
}
static int32_t mute_attr_set(void *ctrl, uint8_t cmd, int value)
{
......
}
static int32_t mute_attr_get(void *ctrl, uint8_t cmd)
{
int32_t data = ((int32_t *)(*(int32_t *)ctrl))[cmd - 1];
return data;
}
static T_CTRL_ATTR vol_attr_spk =
{
.attr =
{
.cur = UAC1_SPK_VOL_CUR,
.min = UAC1_SPK_VOL_MIN,
.max = UAC1_SPK_VOL_MAX,
.res = UAC1_SPK_VOL_RES,
}
};
static T_CTRL_ATTR mute_attr_spk =
{
.attr =
{
.cur = 0,
.min = 0,
.max = 1,
.res = 1,
}
};
static const T_USB_AUDIO_DRIVER_CTRL vol_ctrl_spk =
{
.type = UAC1_FU_VOLUME_CONTROL,
.attr = {.data = (void *) &vol_attr_spk, .len = sizeof(vol_attr_spk)},
.get = (T_USB_AUDIO_CTRL_GET_FUNC)vol_attr_get_spk,
.set = (T_USB_AUDIO_CTRL_SET_FUNC)vol_attr_set_spk,
};
static const T_USB_AUDIO_DRIVER_CTRL mute_ctrl_spk =
{
.type = UAC1_FU_MUTE_CONTROL,
.attr = {.data = (void *) &mute_attr_spk, .len = sizeof(mute_attr_spk)},
.get = (T_USB_AUDIO_CTRL_GET_FUNC)mute_attr_get,
.set = (T_USB_AUDIO_CTRL_SET_FUNC)mute_attr_set,
};
USB Audio Event
This code sample shows how to use the USB API to get notifications for events in USB audio, like changes in data stream state and volume:
void usb_audio_stream_evt_handle(uint8_t evt, uint32_t param)
{
T_USB_AUDIO_STREAM_EVT_INFO stream_evt = {.d8 = evt};
uint8_t evt_type = stream_evt.u.evt_type;
uint8_t stream_type = stream_evt.u.dir;
T_USB_AUDIO_STREAM *stream = usb_audio_stream_get_by_type((T_USB_AUDIO_STREAM_TYPE)stream_type);
switch (evt_type)
{
case USB_AUDIO_STREAM_EVT_ACTIVE:
{
uint8_t bit_width = ((uint8_t)(param >> 8) == 0) ? 16 : 24;
uint8_t sr_idx = (uint8_t)(param >> 16);
uint8_t chann_num = (uint8_t)(param >> 24);
T_STREAM_ATTR attr = {.sample_rate = sample_rate_table[sr_idx], .chann_num = chann_num, .bit_width = bit_width};
usb_audio_stream_create(stream, attr);
app_ipc_publish(USB_IPC_TOPIC, USB_IPC_EVT_AUDIO_DS_START, NULL);
}
break;
case USB_AUDIO_STREAM_EVT_DEACTIVE:
{
usb_audio_stream_ctrl(stream, CTRL_CMD_STOP, 0);
app_ipc_publish(USB_IPC_TOPIC, USB_IPC_EVT_AUDIO_DS_STOP, NULL);
usb_audio_stream_release(stream);
}
break;
case USB_AUDIO_STREAM_EVT_DATA_XMIT:
{
......
app_ipc_publish(USB_IPC_TOPIC, USB_IPC_EVT_AUDIO_DS_START, NULL);
}
break;
case USB_AUDIO_STREAM_EVT_VOL_GET:
......
break;
case USB_AUDIO_STREAM_EVT_VOL_SET:
{
T_UAS_VOL vol = {0,};
vol.cur = stream->ctrl.vol.cur = param & 0x0000FFFF;
vol.range = stream->ctrl.vol.range;
usb_audio_stream_ctrl(stream, CTRL_CMD_VOL_CHG, &vol);
}
break;
case USB_AUDIO_STREAM_EVT_MUTE_SET:
{
stream->ctrl.mute = param;
usb_audio_stream_ctrl(stream, CTRL_CMD_MUTE, ¶m);
}
break;
default:
break;
}
}
static bool usb_audio_ual_spk_ctrl(void *handle, uint8_t cmd, void *param)
{
switch (cmd)
{
case CTRL_CMD_VOL_CHG:
{
T_UAS_VOL *vol = (T_UAS_VOL *)param;
usb_audio_ual_spk_vol_ctrl(handle, vol);
}
break;
case CTRL_CMD_MUTE:
......
break;
case CTRL_CMD_STOP:
{
app_usb_audio_stop();
}
break;
default:
break;
}
return true;
}
USB Control
In the APP layer code, relevant functions can be registered and some parameters initialized to use the USB functionality.
void app_usb_init(void)
{
memset(&app_usb_db, 0, sizeof(T_APP_USB_DB));
usb_audio_stream_init();
app_usb_audio_init();
app_ipc_subscribe(USB_IPC_TOPIC, app_usb_ipc_cback);
app_usb_audio_mmi_init();
usb_hid_init();
usb_dev_init();
T_USB_CORE_CONFIG config = {.speed = USB_SPEED_FULL, .class_set = {.hid_enable = 0, .uac_enable = 0}};
config.class_set.uac_enable = 1;
config.class_set.hid_enable = 1;
usb_dm_core_init(config);
app_usb_start();
}
When this API is executed, USB will start up, and all its features will be successfully listed.
static void app_usb_start(void)
{
uint32_t actual_mhz = 0;
sys_hall_auto_sleep_in_idle(false);
app_usb_db.store.pre_cpu_clk = pm_cpu_freq_get();
pm_cpu_freq_req(&app_usb_freq_handle, pm_cpu_max_freq_get(), &actual_mhz);
pm_dvfs_set_supreme();
#if (TARGET_RTL8773DO == 1 || TARGET_RTL8773DFL == 1)
pmu_vcore3_pon_domain_enable(PMU_USB);
#endif
usb_dm_start(false);
app_ipc_publish(USB_IPC_TOPIC, USB_IPC_EVT_PLUG, NULL);
}
USB lib layer will notify the app layer of some USB data stream status. Here is a code example:
static void app_usb_ipc_cback(uint32_t id, void *msg)
{
switch (id)
{
case USB_IPC_EVT_AUDIO_DS_START:
{
if (app_usb_db.ds_playing == false)
{
app_usb_audio_start(USB_AUDIO_SCENARIO_PLAYBACK);
app_usb_db.ds_playing = true;
}
}
break;
case USB_IPC_EVT_AUDIO_DS_STOP:
{
app_usb_db.ds_playing = false;
}
break;
case USB_IPC_EVT_AUDIO_US_START:
{
app_usb_audio_start(USB_AUDIO_SCENARIO_CAPTURE);
}
break;
case USB_IPC_EVT_AUDIO_US_STOP:
{
app_usb_audio_stop(USB_AUDIO_SCENARIO_CAPTURE);
}
break;
default:
break;
}
}
HID consumer control is supported by the USB HID report descriptor.
The USB API can be used to send commands such as play, pause, volume up, and volume down to the host in order to control the host’s state.
Here is a code example demonstrating how to use the USB API to send these commands to the host:
void app_usb_audio_mmi_handle_action(uint8_t action)
{
uint8_t bit_pos = 0;
uint8_t report[2] = {HID_REPORT_ID_AUDIO_CONTROL, 0};
if (hid_mmi_handle == NULL)
{
hid_mmi_handle = usb_hid_data_pipe_open(HID_INT_IN_EP_1);
}
APP_PRINT_INFO1("app_usb_audio_mmi_handle_action, action:0x%x", action);
switch (action)
{
case MMI_SPK_VOL_UP:
......
break;
case MMI_SPK_VOL_DOWN:
......
break;
case MMI_AV_PLAY_PAUSE:
......
break;
case MMI_AV_FWD:
......
break;
case MMI_AV_BWD:
......
break;
default:
{
return;
}
}
report[1] = 1 << bit_pos;
usb_hid_data_pipe_send(hid_mmi_handle, report, 2, NULL);
report[1] = 0;
usb_hid_data_pipe_send(hid_mmi_handle, report, 2, NULL);
}
Troubleshooting
Can RTL87x3E and RTL87x3D Enter DLPS in Suspend State?
RTL87x3D can enter DLPS.
RTL87x3E can not enter DLPS, but it can enter LPS mode in suspend state.