2013-07-25 10:22 AM
Hello everyone,
I'm currently working on a USB application on a STM32F103, using the Audio Class, and after some work and extensive reading (USB specification and Audio Class specification of course, + several examples and discussions), I am stuck. First, my system : it's a custom board, with an ADC/DAC, a DSP and an MCU (STM32F103). The analog audio is converted by the ADC (clocked by a local oscillator on the board), treated by the DSP, and sent to the DAC and the MCU. The audio format is 48 kHz, 24 bits, single channel (mono). All of this works fine. In fact, all of this has been working fine for a few years now, this is an old board already in production. The USB audio will be a new feature on an old product. What I want is to source this audio stream into my computer. I want my board to be recognized as a standard soundcard by my OS (Windows on my workstation, but Mac OS compatibility will be needed in the future), using the standard audio class driver. I programmed a set of descriptors that seems ok. The device is recognized by Windows, the driver installs nicely, and I can listen to the audio stream on my speakers. I experimented a little with alternate settings, a few different bit reslution, sample rate, everything went ok. I started with a synchronous endpoint, and I got the sound right. But of course the drift between the USB clock and my local clock resulted in audio clicks every few seconds. So I tried setting the endpoint as asynchronous. But so far, without success. My endpoint is an ''asynchonous source'', so I should provide ''implicit feedforward'' by dynamically changing the number of samples I send to the host. That's what I'm currently trying to do. Using 2 timers, I'm getting the period of the the local and USB (SOF) clock. By comparing them, I know if the local clock is late or early with respect to the USB SOF clock. I implemented a mechanism that usually sends 48 samples, and, depending on the clock relationship, sometimes send 47 or 49 samples. This mechanism seems to work, I observed the ''47 samples'' or ''49 samples'' event occuring more or less frequently as I moved the local clock around the USB clock. But that's it : the audio is still the same, drifting slowly until I get clicks. ---- From my point of view, this asynchronous behaviour requests an ''asynchronous sample rate converter'' (ASRC) to be dealt with correctly, and on some level, I doubt there's such a thing in the windows driver. But it seems the usbaudio.sys driver actually does support this. Would it be of any help if I tried the explicit feedback approach, which is needed for an async sink ? Does anyone have a code example (not necessarily on a ST chip) implementing such a function ? I only found sink examples (USB Speakers usually, including the one provided by ST), or source example working with synchronous endpoint (or claiming to be asynchronous but really working as synchronous). Do you know a small software that would allow me to check my descriptors from my computer ? To check the host does get them right. Anyway, I would be thankful for any input on the subject. I can provide specific parts of my code if needed. #audio-usb-asynchronous2013-07-25 01:25 PM
Dear bassmati,
Intersting subject and complex, as far as I know Windows asynchronous audio implementation is not reliable and sometimes it hangs while not understanding to send 47 or 49 samples instead of 48 in dynamic way. We have an implementation on our STM32F4 devices, but not sure If it helps you. good luck and let me know if you have a solution, I can share it with the community. STOne-322013-07-25 11:25 PM
If I understand correctly you produce samples with ADC and simultaneously play it on different codec asynchronously.
Yes, it would produce clicks without sample rate conversion.You can use some simplistic SRC like discarding one sample when you have 49 to send or copy one sample when you only have 47, it's very poor conversion but I doubt you will be able to hear any clicks and you always have 48 samples so you can use synchronous mode.2013-07-26 12:55 AM
Thanks for your answers.
I'm using ASRCs on other projects, it's not something trivial... Plus, I understood Windows did not support asynchronous endpoints until Vista. So in a way, I would not be surprised if their implementation was not ideal. But I'll have to make that work first if I want to test it. I also will have to test on Mac OS. The second option would indeed be to use a synchronous endpoint and make the conversion myself. More burden for me, but at least I would have predictable, reliable and OS-independant results. I don't think I could use the ''+/-1 sample'' approach for my final application, but it could be a simple way to start. For now I have a technical question : I started with the ''Audio_Speaker'' example of the ''STM32_USB-FS-Device_Lib_V3.3.0'' library. I entirely rewrote the descriptors, but the actual code that handles the USB transfers is more or less a translation, getting all the ''OUT'' parts to ''IN''. But I'm starting to suspect something could be wrong here. Especially, I'm not sure when exactly the callback happens. Here is the example code :void EP1_OUT_Callback(void)
{ uint16_t Data_Len; /* data length*/ if (GetENDPOINT(ENDP1) & EP_DTOG_TX) { /*read from ENDP1_BUF0Addr buffer*/ Data_Len = GetEPDblBuf0Count(ENDP1); PMAToUserBufferCopy(Stream_Buff, ENDP1_BUF0Addr, Data_Len); } else { /*read from ENDP1_BUF1Addr buffer*/ Data_Len = GetEPDblBuf1Count(ENDP1); PMAToUserBufferCopy(Stream_Buff, ENDP1_BUF1Addr, Data_Len); } FreeUserBuffer(ENDP1, EP_DBUF_OUT); In_Data_Offset += Data_Len; } It deals with the double buffering, getting the length of the transfer and the data from the PMA memory (which, by the way, is quite small... for my 48 kHz 24bits signal, I need more than half of it, I couldn't even get a stereo signal if I wanted). So I guess this callback happens after the actual USB transaction. Translated for my code, for an IN endpoint, I get that :void EP3_IN_Callback(void){
[...] if(GetENDPOINT(ENDP3) & EP_DTOG_TX){ SetEPDblBuf0Count(ENDP3, EP_DBUF_IN, nb_bytes_to_send); UserToPMABufferCopy(Stream_Buff_to_PC, ENDP3_BUF0Addr, nb_bytes_to_send); } else{ SetEPDblBuf1Count(ENDP3, EP_DBUF_IN, nb_bytes_to_send); UserToPMABufferCopy(Stream_Buff_to_PC, ENDP3_BUF1Addr, nb_bytes_to_send); } FreeUserBuffer(ENDP3, EP_DBUF_IN); } But I'm not sure it's ok.2013-07-26 06:21 AM
I'm not really familiar with USB devices.
If I understand the USB specs correctly, you tried to create async source and you expected adaptive sink on the other side but it didn't work.It could mean windows only supports async sink with explicit feedback, who knows.If this is the case you need to resample anyway(adaptive source) and it's no better than synchronous.2013-07-31 12:17 AM
I found a bunch of discussion all over the internet from people trying to make the asynchronous audio USB work, and it's almost always by using a sync endpoint for explicit feedback. So yeah, I think it's my next move.