Audio library, USB audio interface, volume setting

Status
Not open for further replies.

PaulS

Well-known member
Hooked up a PCM5102 board to a Teensy 3.1 to form a USB audio interface.

PCM5102 DAC board.jpg

With the code below, my windows PC sees a USB Teensy Audio device and outputs audio via the PCM5102 board as expected.
Code:
// PCM5102 bd   Teensy
// VCC          5V
// GND          GND
// LRCK         23
// DATA         22
// BCK          9

// set Tools > USB Type to Audio (+ Serial if needed)

#include <Audio.h>

AudioInputUSB            usb1;
AudioAmplifier           amp2;
AudioAmplifier           amp1;
AudioOutputI2S           i2s1;
AudioConnection          patchCord1(usb1, 0, amp1, 0);
AudioConnection          patchCord2(usb1, 1, amp2, 0);
AudioConnection          patchCord3(amp2, 0, i2s1, 1);
AudioConnection          patchCord4(amp1, 0, i2s1, 0);

void setup()
{
  Serial.begin(9600);
  AudioMemory(12);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
}

void loop()
{
  float vol = usb1.volume();  // read PC volume setting
  amp1.gain(vol);             // set gain according to PC volume
  amp2.gain(vol);
  delay(100);
  Serial.println(vol);
}

To control the volume of the audio I use the windows volume slider:

Windows audio volume 50%.png

Now the interesting part is that when the slider is at 50%, the value of variable "vol" = 0.63. I expected 0.50.
When I checked other slider settings, I see the following values for slider percentages:

Slider vs volume value.PNG

My questions now are: where [and how] is this conversion done? And is there a reason it isn't linear?
I have been digging through the audio library sources but could not find it.

Any help and/or explanation is appreciated!

Thanks,
Paul
 
After further digging I found a number of references to USB volume in C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3\usb_audio.h & usb_audio.c.
But I'm still in the dark where the non-linear conversion takes place.

Is it perhaps in C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\Audio\mixer.h and mixer.cpp? If I understand these files correctly, I see a conversion from float to int32_t. Could that be the issue?

Regards,
Paul
 
Questions answered

While printing member "volume" of structure "usb_audio_features_struct" [in C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3\usb_audio.h] to the serial monitor, I noticed the same non-linearity between Windows slider percentage and the value of "volume".

Since member "volume" is directly obtained from the USB audio interface Volume feature descriptor, I can only conclude that Windows does the non-linear conversion from slider percentage to USB volume. Apparently Windows 7 tries to make a kind of logarithmic scaled volume.

Regards,
Paul
 
Hi Paul,


Yes logarithmic volume sliders are better than linear. Interesting that windows does this that way, but it's a good thing for audio. Our ears don't hear linearly so they setup the volume sliders in the same way.

It would be nice if the teensy audio library mixers were also logarithmic...

Thanks for the code, I was looking for this. Next step for me is to figure out the USB volume descriptor for the microphones. I'll want control over them via the windows mixer.

Jay
 
I haven't used the USB volume parameter. It was a contribution I merged without testing, since it seemed like a low risk. Looks like it just delivers whatever number the USB host sends.

If there's a problem in need of fixing (in the Teensy core lib), please let me know?
 
I haven't used the USB volume parameter. It was a contribution I merged without testing, since it seemed like a low risk. Looks like it just delivers whatever number the USB host sends.

If there's a problem in need of fixing (in the Teensy core lib), please let me know?

No it appears that Paul found out the USB volume is logarithmic... Your library is fine, anybody's application can control your library in a logarithmic way. The volumes on the library could have the feature, but it isn't broken.
 
@PaulStoffregen: Indeed, nothing to fix here.
Out of curiosity I checked the slider versus volume values on Windows 10.
Microsoft updated the conversion. It now looks more like the correct logarithmic behaviour:

Capture.PNG

Paul
 
Hello,

I'm looking to control the input volume similarly to the way the output volume is being controlled by the usb1.volume(); funtion. Here is an image showing the volume slider I'm looking to monitor in my application.

usb-audio-input-volume.png


I was looking over the USB Audio Spec to try and figure it out. It looks like it's a descriptor called "Input Terminal Audio Class Descriptor". Any quick hints on how I could add functionality to the library to get a usb1.inputVolume(); function?

Any help is appreciated. :)

Jay
 
I wonder if it is possible at all to tell the source to attenuate the output signal before sending out.
In your case you would like to send out a control signal to the Teensy to attenuate the mic output. But I think the actual attenuation is always happening at the receiver side [in your case the host PC]. I know that for analog signals the volume attenuation ["levels" setting] is done at the receiver side.
Similarly, in case of the usb1.volume control in my sketch on top, the receiving Teensy attenuates the full-scale signal from the source [the host PC] before sending it further out to the DAC.

Paul
 
To your point... It might be class compliant to send the full un-attenuated input from the input into the USB1 object. That can be arranged in the Audio Design Tool project. But what I'm looking to control is the "listening volume" that goes through the "DSP" and out the speakers...

Just to be clear, I'm not looking for the windows "listen to this device" feature. That feature will rout the USB input back out the output, and introduce lag.
 
Many people may not know... Motherboard sound cards have an analog audio path from the input to the output. So when you turn down the volume on that slider (the one here), the motherboard attenuates the analog volume of the input and sends it directly out of the speakers. This is the functionality that I'm trying to replicate, inside the Teensy. This is why any regular USB Audio device doesn't work for my use-case. We need the DSP, to have low-latency input to output because we are in the digital domain. The reason it works on motherboards is because of that analog signal path.... In comparison to the Windows "listen to this device" which will take the USB input and push it back down the USB to the output, introducing lag.
 
Last edited:
Here's another example, from a different computer. This one has specified a "microphone boost" parameter (aka "gain"). I also want to see about getting access to that slider. We probably have to define it somewhere so when the device is installed windows knows what options to display.

usb-audio-input-volume2.png

But this slider definitely controls the input "listen" level.


We probably also can define multiple inputs (although I'm aware Teensy is only currently 2i2o). This soundcard has two mics...

usb-audio-input-volume3.png

Anyway, just defining my situation while researching.
 
How can I return the current features.volume?

I have an AudioInputUSB object called usb1 and an AudioOutputUSB object called usb2.

So this code works for the "output" AudioInputUSB object.

float vol = usb1.volume();

Can I get this to work for the AudioOutputUSB object too?

float inputVol = usb2.volume();

I'm trying to figure it out in the library code if is there. The usb_audio.cpp file has this code. Can I get the usb2 reading from that?

Code:
int usb_audio_get_feature(void *stp, uint8_t *data, uint32_t *datalen)
{
	struct setup_struct setup = *((struct setup_struct *)stp);
	if (setup.bmRequestType==0xA1) { // should check bRequest, bChannel, and UnitID
			if (setup.bCS==0x01) { // mute
				data[0] = AudioInputUSB::features.mute;  // 1=mute, 0=unmute
				*datalen = 1;
				return 1;
			}
			else if (setup.bCS==0x02) { // volume
				if (setup.bRequest==0x81) { // GET_CURR
					data[0] = AudioInputUSB::features.volume & 0xFF;
					data[1] = (AudioInputUSB::features.volume>>8) & 0xFF;
				}
				else if (setup.bRequest==0x82) { // GET_MIN
					//serial_print("vol get_min\n");
					data[0] = 0;     // min level is 0
					data[1] = 0;
				}
				else if (setup.bRequest==0x83) { // GET_MAX
					data[0] = FEATURE_MAX_VOLUME & 0xFF;  // max level, for range of 0 to MAX
					data[1] = (FEATURE_MAX_VOLUME>>8) & 0x0F;
				}
				else if (setup.bRequest==0x84) { // GET_RES
					data[0] = 1; // increment vol by by 1
					data[1] = 0;
				}
				else { // pass over SET_MEM, etc.
					return 0;
				}
				*datalen = 2;
				return 1;
			}
	}
	return 0;
}

Am I looking in the right place? I don't notice an "AudioOutputUSB", I wonder where I could add it to get that value?

Jay
 
Hi Jay,
I've been digging further on your wanted USB input volume control.
What you are looking for is the "Feature Unit Descriptor" [para 4.3.2.5 of the USB spec for Audio Devices].
I googled for "Feature Unit Descriptor" and mostly you see this Feature Unit Descriptor mentioned for a USB Output Terminal, not for the Input Terminal that you want.
For example see para "4.1. Descriptors" of this document or para "2.3.3.4 Feature Unit Descriptor" of this document.
However, this document talks about a "Microphone - Feature Unit Descriptor" [page 30/31].
So theoretically it looks like there is a Feature Unit Descriptor for an Input Terminal but I'm not sure how often this is actually implemented [either by Windows and/or a recording capable USB audio device].
Perhaps Rev 2.0 of the Universal Serial Bus Device Class Definition for Audio Devices is more informative. Figure 3-2 shows a Feature Unit Descriptor for an analog input.

Cheers,
Paul
 
Thanks, I think you are right about this version 2.0 spec. It's the Input Terminal Descriptor in section 4.7.2.4



4.7.2.4 Input Terminal Descriptor
The Input Terminal descriptor (ITD) provides information to the Host that is related to the functional
aspects of the Input Terminal.
The Input Terminal is uniquely identified by the value in the bTerminalID field. No other Unit or
Terminal within the AudioControl interface may have the same ID. This value must be passed in the Entity
ID field (part of the wIndex field) of each request that is directed to the Terminal.
The wTerminalType field provides pertinent information about the physical entity that the Input Terminal
represents. This could be a USB OUT endpoint, an external Line In connection, a microphone, etc. A
complete list of Terminal Type codes is provided in a separate document, USB Audio Terminal Types that
is considered part of this specification.
The bAssocTerminal field is used to associate an Output Terminal to this Input Terminal, effectively
implementing a bi-directional Terminal pair. If no association exists, the bAssocTerminal field must be set
to zero.
The Host software can treat the associated Terminals as being physically related. In many cases, one
Terminal cannot exist without the other. A typical example of such a Terminal pair is an Input Terminal,
which represents the microphone, and an Output Terminal, which represents the earpiece of a headset.
The bCSourceID contains a constant indicating to which Clock Entity the Clock Input Pin of this Input
Terminal is connected.
The bNrChannels, bmChannelConfig and iChannelNames fields together constitute the cluster
descriptor. They characterize the cluster that leaves the Input Terminal over the single Output Pin
(‘downstream’ connection). For a detailed description of the cluster descriptor, see Section 4.1, “Audio
Channel Cluster Descriptor”.
The bmControls field contains a set of bit pairs, indicating which Controls are present and what their
capabilities are. If a Control is present, it must be Host readable. If a certain Control is not present then the
bit pair must be set to 0b00. If a Control is present but read-only, the bit pair must be set to 0b01. If a
Control is also Host programmable, the bit pair must be set to 0b11. The value 0b10 is not allowed.
An index to a string descriptor is provided to further describe the Input Terminal.


A table of the elements in the descriptor.

usb_Input_terminal_descriptor.png

And a list of Terminal Types here https://www.usb.org/sites/default/files/termt10.pdf

usb_Input_terminal_types.png
 
Input Terminal (IT) = Receptacle for audio information flowing into the audio function.

Feature Unit (FU) = Provides basic audio manipulation on the incoming logical audio channels.


View attachment 21119

It appears that one would outline the full functionality of the audio device to the host, although the processing is done within the audio device. This allows the host to control the device. This chart reminds me of the Teensy Audio Design Tool.

Figure 3-2: Inside the Audio Function

View attachment 21118

As an example, consider a Volume Control inside a Feature Unit. By issuing the appropriate Get requests, the Host software can obtain values for the Volume Control’s attributes and, for instance, use them to correctly display the Control on the screen. Setting the Volume Control’s current attribute allows the Host software to change the volume setting of the Volume Control.


After the Teensy Processes the input signal, it will send it to an "output terminal" may would go back up the USB into the host PC, or possibly out of some speaker or line output. So the Teensy can process things but the processing can be manipulated by the user via the host.

Interesting stuff, now I guess I need to figure out how to write and get values from the feature unit. Where is the Input Feature Unit described? Is it described? It shows up in the control panel as an input...

attachment.php
 
Last edited:
Hi Jay,
Both your attachments return this when clicked on:
Capture.PNG

On topic: I think we are getting closer. What you want seems theoretically possible, but the question is how to realize it.
I don't know yet but will keep thinking about it.
Thanks for correcting the link to the Rev 2 spec.

Paul
 
Hi Jay,
Both your attachments return this when clicked on:
View attachment 21123


Ah, that was the meat and potatoes! In the spec there is this chart which shows how the descriptors relate to eachother. You were asking originally about why that volume control is useful, suggesting that input volume is adjusted on the host, not on the USB device. This chart shows why I'm seeking access to those descriptors.

usb_descriptor_flow_chart.jpg

My custom ADC module has 4 microphones, and each mic has a gain setting. So I believe that's what I'm going to attempt to define in the descriptors. The result will be the ability to control the volume and the gain of each microphone from within windows, and within windows applications that understand what we are giving them control over.

usb_feature_unit.png

On topic: I think we are getting closer. What you want seems theoretically possible, but the question is how to realize it.
I don't know yet but will keep thinking about it.
Thanks for correcting the link to the Rev 2 spec.

Paul

Yes thanks for your correction as well. I'm looking deeper to see how the current implementation is being described, and how I can modify those descriptors to include my input controls. I will report with any progress. Thanks!
 
Hello,

@PaulStoffregen, I'm wondering if you could help and/or take a bounty for this. I have some notes above, but long story short is that I would like to monitor the "usb1.inputVolume();" in the same way that we monitor "usb1.volume();".

Sure, it would be nice to also get access to a) define more inputs (even though they wouldn't be actual "USB" inputs just inputs on the device, and b) define other features such as tone, gain, eq, etc. However, this is not ultimately that important to me at this point. The only thing I really want to do at this point is monitor this level. This level is clearly already defined in the code somewhere, because it exists, the computer knows about it. But it's not clear to me how I can monitor its value.

attachment.php



I've spoken with the audio focused users on this forum (Chip, Pauls, Bob, etc) nobody can help. Those who are interested in the possibility of helping aren't able due to contractual obligations. I've tried posting on other forums, freelancers, etc, but this is a really high-level black hole for most people. So do you have any advice for me (us) as to where I can dive into the code to monitor this level?

Thanks for whatever it is you can offer, if anything! :)

Jay
 
Sorry guys, I have to put a shameful bump on this. My project is starting to round out now and if I can do this I can really finish the prototype. PaulS, sorry for taking over your originally very simple thread. But I'm all in on this now. :)

TLDR; JayShoe really wants to understand what the volume(); function is doing and how it works because he really wants to monitor the "input volume" on the PC in the same exact way.
 
Hi Frank,

Yes.

It happens here, I think?

https://github.com/PaulStoffregen/cores/blob/master/teensy4/usb_audio.cpp
line 473

Code:
			else if (setup.bCS==0x02) { // volume
				if (setup.bRequest==0x81) { // GET_CURR
					data[0] = AudioInputUSB::features.volume & 0xFF;
					data[1] = (AudioInputUSB::features.volume>>8) & 0xFF;
				}

I think I need to add another else if statement that would correspond to whatever the address for the input volume?

Also here: https://github.com/PaulStoffregen/cores/blob/master/teensy4/usb_audio.h


Code:
// audio features supported
struct usb_audio_features_struct {
  int change;  // set to 1 when any value is changed
  int mute;    // 1=mute, 0=unmute
  int volume;  // volume from 0 to FEATURE_MAX_VOLUME, maybe should be float from 0.0 to 1.0
};

I think I also need an int pcInputVolume.
 
I upgraded to Platformio and it has a function to right click and "go to definition" you can also find all references. This functionality is very useful. I'm starting to understand it a *little* more. Maybe if I continue to read it I'll stumble over it but it's massively confusing. There are some struct definitions that get the features, somehow in usb.c. And then somehow usb_audio.cpp.... I don't know, ugh it's nuts. I will continue to bang head on keyboard.
 
I am working on another hardware platform but also struggling to make base boost work. However finally it is proven not supported on windows 10. It only support sub set of feature units like Vol/Mute/AGC. The microphone boost on windows 10 is not on Usb Audio Card, but on PC.

https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/usb-2-0-audio-drivers
Entity Control GET CUR SET CUR GET RANGE INTERRUPT
Clock Source Sampling Frequency Control x x x
Clock Selector Clock Selector Control x
Clock Multiplier Numerator Control x
Denominator Control x
Terminal Connector Control x x
Mixer Unit Mixer Control x x x
Selector Unit Selector Control x x
Feature Unit Mute Control x x x
Volume Control x x x x
Automatic Gain Control x x
Effect Unit –
Processing Unit –
Extension Unit –
 
Status
Not open for further replies.
Back
Top