USB Audio for Teensy 3.0

Status
Not open for further replies.
I've started adapting and merging MickMad's code into the core library. Still a work in progress, but this is finally moving forward to becoming a fully supported feature on Teensy and the audio lib.
 
I've started adapting and merging MickMad's code into the core library. Still a work in progress, but this is finally moving forward to becoming a fully supported feature on Teensy and the audio lib.

For what it's worth, I did spend a few days looking at this before today and have it more-or-less working with the core git code from a few days ago. I was going to work on it a few more days and then publish it, but since you are already on top of it, I figured I should just send what I have in case it is of any use to you.

The changed files are in the attached ZIP. MickMad's code was pretty good easy to follow. Thanks for your hard work!

In addition, I made a small change to the AudioPlayMemory (change "private" to "protected" in play_memory.h) class to let me inherit it so I can play back the sound form my sample sketch.
 

Attachments

  • usb-teensy.zip
    30.1 KB · Views: 251
Last edited:
Hi all guys,

@Paul, how are we going to handle the different size of packets for Isochronous endpoints? I'm talking about lines 914 and 923 of usb_dev.c, the usb_memory_rx() function in usb_dev.c, and usb_malloc() function as well.

edit:
in my old code, I have this set for lines 914 and 923.

b->desc = BDT_DESC(MAX_PACKET_BUFFER_SIZE*MAX_PACKET_BUFFER_SAMPLE_SIZE, ((uint32_t)b & 8) ? DATA1 : DATA0);

Instead of MAX_PACKET_BUFFER_SIZE*MAX_PACKET_BUFFER_SAMPLE_SIZE we shall have 180 (45 samples*2 channels*2 bytes).

USB packets size is still fixed to 64 words*1 byte...
 
Last edited:
In addition to the problem of longer data blocks for isochronous packets, there is also the problem that setup commands on endpoint 0 also sometimes require more that 64 bytes. For example, when you get a command with

wRequestAndType=0x0122 (set the frequency)

The data parameter comes after the 64 bytes, according to the spec and my observations with Wireshark.

If you are going to allow options like multiple frequencies or channels, you'll have to implement this somehow because that's how the parameters are transmitted from the computer to the device.

I didn't get very far looking at solutions. In the K20 reference manual (section 41.4.5) it addresses oversized packets:

The packet received may be larger than the negotiated MaxPacket size. Typically, this is caused by a software bug. For DMA overrun errors due to oversized data packets, the USB specification is ambiguous. It assumes correct software drivers on both sides. NAKing the packet can result in retransmission of the already oversized packet data. Therefore, in response to oversized packets, the USB core continues ACKing the packet for non-isochronous transfers.
 
If I may also request a new feature, it would be volume control. But I think it requires solving the problem with oversized packets on EP0. I did get the host computer to request volume changes by adding the following feature descriptor:

Code:
// Feature Descriptor (Table 4.7, p. 43,  USB Device Class Definition for Audio Data Formats 2.0)
0x0A,			// bLength
0x24,			// bDescriptorType = CS_INTERFACE
0x06,			// bDescriptorSubtype = FEATURE_UNIT
0x31,			// bUnitID
0x30,			// bSourceID
0x01,			// bControlSize
0x03,			// bmaControls(0) (MASTER, VOL)
0x03,			// bmaControls(1) Left
0x03,			// bmaControls(2) Right
0x00,			// iFeature

bSourceID=0x30 is the terminal id of the "Input Terminal Descriptor". Then the "Output Terminal Descriptor" is modified to take the feature as input, instead of the previous input terminal.

Code:
// Output Terminal Descriptor (Table 4.9, p.53, USB Device Class Definition for Audio Devices 2.0)
0x09,			// bLength
0x24,			// bDescriptorType = CS_INTERFACE
0x03,			// bDescriptorSubtype = OUTPUT_TERMINAL
0x40,			// bTerminalID = OUTPUT_TERMINAL_ID
0x02, 0x03,		// wTerminalType = HEADPHONES
0x00,			// bAssocTerminal
//0x30,			// bCSourceID = INPUT_TERMINAL_ID
0x31,			// bCSourceID = FEATURE UNIT_ID
0x00,			// iTerminal
 
I've added the first code which moves streaming audio from the PC into the audio library.

https://github.com/PaulStoffregen/cores/commit/adca415331771bbf1560d7a008ccb150b0ec603a

This is still very much a work in progress. Runs with Ubuntu 14.04 and Windows 10. My Mac still doesn't like it. On both Windows and Linux, there's audible distortion once every several seconds, which I suspect is the mismatch in sample rates. The rest of the time it sounds excellent.

I had to change some headers in the core library, so an update of the audio library is also needed.

Here's a simple passthrough sketch I've using for testing:

Code:
#include <Audio.h>

AudioInputUSB            usb1;           //xy=200,69
AudioOutputI2S           i2s1;           //xy=365,94
AudioConnection          patchCord1(usb1, 0, i2s1, 0);
AudioConnection          patchCord2(usb1, 1, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=302,184

// the setup routine runs once when you press reset:
void setup() {                
  AudioMemory(12);
  Serial1.begin(115200);
  Serial1.println("*********************");
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.75);
}

int count=0;

// the loop routine runs over and over again forever:
void loop() {
  Serial.println(count++);
  delay(500);               // wait for a second
}
 
Last edited:
I've now implemented the asynchronous sample rate feedback loop. The control strategy is proportional-integral, where I did some quick fiddling but no really serious tuning effort.

I also put quite a bit of work into improving the behavior if underruns or overruns do happen. But these really shouldn't happen when the feedback loop is working.
 
I've added AudioOutputUSB, so now there's bidirectional USB audio streaming to/from the audio library.

https://github.com/PaulStoffregen/cores

Here's a little program I've been using for testing the Teensy-to-PC direction.

Code:
#include <Audio.h>
#include <SD.h>

AudioPlaySdWav           playWav1;
AudioOutputUSB           audioOutput;
AudioOutputAnalog        dac;
AudioConnection          patchCord1(playWav1, 0, audioOutput, 0);
AudioConnection          patchCord2(playWav1, 1, audioOutput, 1);

// Use these with the audio adaptor board
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

void setup() {
  Serial.begin(9600);
  Serial1.begin(115200);
  Serial1.println("=======================");

  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(20);

  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    // stop here, but print a message repetitively
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
}

void playFile(const char *filename)
{
  Serial1.print("Playing file: ");
  Serial1.println(filename);
  playWav1.play(filename);
  // A brief delay for the library read WAV info
  delay(5);
  // Simply wait for the file to finish playing.
  while (playWav1.isPlaying()) {
  }
}


void loop() {
  playFile("SDTEST1.WAV");  // filenames are always uppercase 8.3 format
  delay(500);
  playFile("SDTEST2.WAV");
  delay(500);
  playFile("SDTEST3.WAV");
  delay(500);
  playFile("SDTEST4.WAV");
  delay(1500);
}
 
Mac compatibility is still an issue. I'm working on it...

If anyone tries AudioInputUSB, where the Mac sends to Teensy, you probably also need an AudioOutputUSB running to make it work. They're supposed to be able to run independently, but Apple's doing some implicit sample rate stuff (maybe), even though we have the explicit feedback endpoint. Eventually I'm going to get to the bottom of this and make each able to run independently on Macs, but for now you probably need both if you want to try this early code with a Mac.
 
I just tried it and added
Code:
teensy31.menu.usb.audio=Audio
teensy31.menu.usb.audio.build.usbtype=USB_AUDIO
teensy31.menu.usb.audio.fake_serial=teensy_gateway

But now i get this ("Teensy MIDI" and code 10 in device manager) :
AudioCode10.png

i guess i have done something wrong.. ;)

is it a problem with this:

Code:
  #define PRODUCT_ID        0x0485
which is "midi" ?


Edit:
Ok, i unplugged the Teensy, removed "Teensy MIDI" and plugged it in again.

Now it is titled "Teensy Audio", but still code 10.

Code:
Das E/A-Gerät ist falsch konfiguriert, oder die Konfigurationsparameter für den Treiber sind falsch.

C0000182

..must be something like "I/O deivce wrong configuration or wrong parameters for the driver" in English...
 
Last edited:
Ok, I fixed several problems. I believe Mac and Windows may finally both be working.

https://github.com/PaulStoffregen/cores

Here what I just tested...

Linux PC to Teensy: working
Linux Teensy to PC: working
Mac PC to Teensy: working
Mac Teensy to PC: working
Windows PC to Teensy: working
Windows Teensy to PC: maybe working?

On Windows, my drivers are screwed up so the speaker and line-out aren't working (Teensy is the only way to get sound out right now, crazy!), so all I can do at the moment is watch the little level meter in the sound control panel to see if Teensy is sending.

So far, I haven't managed to test simultaneous input and output. I don't do a lot of serious sound work with computers, so I'm not even sure what should really be done to test this. Anyone have ideas?

The main issue on Windows was incorrect delay parameters in the descriptor, which apparently are fine when Windows believe the isync endpoint is adaptive, but causes the "code 10" error when the endpoint is asynchronous.

The main problem on Mac was not transmitting (silence) on the unused input endpoint when it's enabled. Apparently Apple expects data to be arriving if it's enabled the input endpoint, and even if you're not using it, OSX refuses to send to the output endpoint. Seems strange, but it's true.

If anyone has good sound setup with Windows, please give this a try. Windows is the system I use the least, so I really depend on help finding Windows issues.
 
Works !!!

To use it with the prop-shield:

Code:
#include <Audio.h>

AudioInputUSB            usb1;           //xy=200,69
AudioMixer4              mixer1;         //xy=324,161
AudioOutputAnalog        dac1;           //xy=365,94
AudioConnection          patchCord1(usb1, 0, mixer1, 0);
AudioConnection          patchCord2(usb1, 1, mixer1, 1);
AudioConnection          patchCord3(mixer1, dac1);

#define PROP_AMP_ENABLE    5

// the setup routine runs once when you press reset:
void setup() { 

  //Start Amplifier
  pinMode(PROP_AMP_ENABLE, OUTPUT);
  digitalWrite(PROP_AMP_ENABLE, HIGH); 
  
  AudioMemory(12);
  Serial1.begin(115200);
  Serial1.println("*********************");

}

int count=0;

// the loop routine runs over and over again forever:
void loop() {
  Serial.println(count++);
  delay(500);               // wait for a second
}
 
Works !!!

To use it with the prop-shield:

Code:
#include <Audio.h>

AudioInputUSB            usb1;           //xy=200,69
AudioMixer4              mixer1;         //xy=324,161
AudioOutputAnalog        dac1;           //xy=365,94
AudioConnection          patchCord1(usb1, 0, mixer1, 0);
AudioConnection          patchCord2(usb1, 1, mixer1, 1);
AudioConnection          patchCord3(mixer1, dac1);

This is for output. Now, add also AudioCard and add SGTL5000 AudioInput.
(sorry, I could not resist. I know prop card and audiocard do not work together (I2S-RXD0 is occupied by SPI-CLK), or is there a workaround?)
 
Isn't is possible to use pin 14 for the prop shield ? Ok, requires a short wire instead of a header pin.. but that should'nt be a problem.
Perhaps Paul can add a jumper for a future "REV 2" of the prop-shield ?
 
The main problem on Mac was not transmitting (silence) on the unused input endpoint when it's enabled. Apparently Apple expects data to be arriving if it's enabled the input endpoint, and even if you're not using it, OSX refuses to send to the output endpoint. Seems strange, but it's true.

I also observed this when I worked on MickMad's code. If the Mac turns on the microphone (sets the microphone's alternate interface to 1) it expects data or else it will hang.

If anyone has good sound setup with Windows, please give this a try. Windows is the system I use the least, so I really depend on help finding Windows issues.

I can confirm that the code from today works on my PC tested using Audacity. Both input and output seem to work just fine with the sketch below. I also tested recording and playing back at the same time (using Audacity's "Software Playthrough" setting in the Recording settings) and it worked on my Mac, Linux and PC. Curiously, the Mac also has a "Hardware Playthrough" setting not found on Linux or the PC, but it doesn't work.

Code:
#include <Audio.h>

AudioSynthWaveformSine   wav1;
AudioOutputUSB           audioOutput; 
AudioConnection          patchCord5(wav1, 0, audioOutput, 0);

AudioInputUSB            usbin;    
AudioOutputI2S           out1;
AudioConnection          patchCord1(usbin, 0, out1, 0);
AudioConnection          patchCord2(usbin, 1, out1, 1);
AudioControlSGTL5000     sgtl5000_1;  

int led = 13;

// the setup routine runs once when you press reset:
void setup() {
  AudioMemory(12);
  pinMode(led, OUTPUT);
  wav1.frequency(220);
  wav1.amplitude(1);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.50);
}

int count=0;
int ledon = 0;

// the loop routine runs over and over again forever:
void loop() {
  ledon ^= 1; // flip bit
  digitalWrite(led, ledon);
  delay(500);
}
 
Isn't is possible to use pin 14 for the prop shield ? Ok, requires a short wire instead of a header pin.. but that should'nt be a problem.
Perhaps Paul can add a jumper for a future "REV 2" of the prop-shield ?

I still have hope that T4 will be compatible with prop shield and use of I2S
 
Last edited:
Yeah, but since audio-shield and prop-shield use the same pin...how should that work ?
As said above, it needs only a wire... even with the existing T 3.2
 
Status
Not open for further replies.
Back
Top