Native DSD for linux, stereo and multichannel

Technical questions regarding the XTC tools and programming with XMOS.
Post Reply
User avatar
fabriceo
XCore Addict
Posts: 181
Joined: Mon Jan 08, 2018 4:14 pm

Native DSD for linux, stereo and multichannel

Post by fabriceo »

Hi Guys,

I ve been struggling for a while with playing native DSD files in stereo and multichannel 5.1 format on linux with a standard XMOS USB application configured as 8 output channels. Having found a working solution I thought it might be beneficial to share the principles with the community.
This can be tested on a breadboard like the one sold by diyinhk and a raspberypi with volumio (eg v2.873 dated feb 19, 2021) for example.

problem statement:
if you compile the XMOS USB application with say 8 channels output and including DSD with these flags in the makefile:
-DNUM_USB_CHAN_OUT=8 -DDSD_CHANS_DAC=8
you will get a device descriptor with 3 "alternate setting" for interface 1, the last "altset" being configured with the format "UAC_FORMAT_TYPEI_RAW_DATA" and 8 channels of 4 bytes. This is fine to describe the DSD capability to the host UAC2 driver (windows thesycon asio or linux).

when playing a standard wav file on linux with the very basic command
aplay -v -D hw:x,0 file.wav (where x is the card number for the USB-audio device created at boot),
you always get an error due to the mismatch between the number of channels in the stereo wav file and the fact that the "altset" shows 8 channels.
the easy way to overcome this is to use a "plug" which will accommodate the difference and provide the missing channels, for example:
aplay -v -D plughw:x,0 file.wav
-v option will show the changes made on the original number of channels and bit format.

playing DSD stream stored in DSF files requires another player.
MPD (v0.20.18) is provided with Volumio for raspberypi, or in the ubuntu 18.04 image provided by NVidia for Jetson Nano.
Just configure the alsa output device to point to your "hw:x,0" card in /etc/mpd.conf and you are ready to play dsd stereo or dsd multichannel files with any mpd client (command line mpc or volumio GUI)
for PCM music file, MPD will automatically convert the bit format and number of channels to accommodate our usb-audio device, no need to go trough a "plughw" device.
But unfortunately, for DSD stream, the conversion of channels is not implemented, and then MPD cannot use the "dsd altset" as it is pointing for 8 channels. Then MPD decide to convert DSD to PCM and then resample the channels with a rate compatible with our usb device. So you never get a chance to play native dsd.

solution:
the way to overcome this limitation is to force the number of channels shown in the last "altset" to 2 instead of the original NUM_USB_CHAN_OUT :

Code: Select all

    /* Class Specific AS Interface Descriptor */
    .Audio_Out_ClassStreamInterface_3 =
    {
        0x10,                             /* 0  bLength: 16 */
        UAC_CS_DESCTYPE_INTERFACE,        /* 1  bDescriptorType: 0x24 */
        UAC_CS_AS_INTERFACE_SUBTYPE_AS_GENERAL, /* 2  bDescriptorSubType */
        ID_IT_USB,                        /* 3  bTerminalLink (Linked to USB input terminal) */
        0x00,                             /* 4  bmControls */
        UAC_FORMAT_TYPE_I,                /* 5  bFormatType */
        STREAM_FORMAT_OUTPUT_3_DATAFORMAT,/* 6:10  bmFormats (note this is a bitmap) */
        2 /* NUM_USB_CHAN_OUT */ ,	  /* 11 bNrChannels */
        0x00000000,                       /* 12:14: bmChannelConfig */
        .iChannelNames                 = offsetof(StringDescTable_t, outputChanStr_1)/sizeof(char *),
    },
so patching the filed "11 bNrChannels" with value 2

as a result MPD is now able to send native DSD stereo.

okay, but if you want to play a 5.1 multichannel file showing 6 channels, you still have the same problem!
the way to solve it is to patch the devicedefines.h so that the STREAM_FORMAT_OUTPUT_2_DATAFORMAT is also flagged as DSD (UAC_FORMAT_TYPEI_RAW_DATA), and the corresponding descriptor "Audio_Out_ClassStreamInterface_2" patched with value 6 instead of NUM_USB_CHAN_OUT.

and voila MPD is ok to send DSD native for stereo and multichannel files.

final tweaks:
but still there is a problem, the sound is crakling and strange.
The reason is in the endpoint0.c file, in the treatment of the first case which inform the usb-buffer and decouple task about the stream configuration change :

Code: Select all

/* Send format of data onto buffering */
outuint(c_audioControl, SET_STREAM_FORMAT_OUT);
outuint(c_audioControl, g_dataFormat_Out[sp.wValue-1]);        /* Data format (PCM/DSD) */

if(g_curUsbSpeed == XUD_SPEED_HS)
{
outuint(c_audioControl, NUM_USB_CHAN_OUT);                 /* Channel count */
outuint(c_audioControl, g_subSlot_Out_HS[sp.wValue-1]);    /* Subslot */
outuint(c_audioControl, g_sampRes_Out_HS[sp.wValue-1]);    /* Resolution */
}
here you have to change the default value NUM_USB_CHAN_OUT by a formula based on the alternateset number which is in sp.wValue, with something like
((sp.wValue==2)? 6 : (sp.wValue==3) ? 2 : NUM_USB_CHAN_OUT)

now everything works like a charm.

yes, but:
still a side effect remains, when trying to play a stereo PCM stream through the "plughw" device, this one will no more find a suitable alternateset, because it is jeopardized by the existence of the altset 3 showing 2 channels, but in a format not compatible with PCM...

The way to overcome this is to create an additional altset, and to show it as 2 channels PCM.
the default code in devicedefines.h is made for max 3 altset, so this will require some further modification here, and in the descriptor and in endpoint0.c, searching in the workspace for the keyword OUTPUT_FORMAT_COUNT and creating entries to cope with a total of 4 altset and STREAMs.

I m not documenting this but this is relatively straight foward, and in the end it works perfectly, my usb-audio device is now showing 4 altset in the linux console:

Code: Select all

volumio@volumio:~$ cat /proc/asound/card5/stream0
OKTO RESEARCH DAC8PRO at usb-0000:01:00.0-1.2, high speed : USB Audio

Playback:
  Status: Stop
  Interface 1
    Altset 1
    Format: S32_LE
    Channels: 8
    Endpoint: 1 OUT (ASYNC)
    Rates: 44100, 48000, 88200, 96000, 176400, 192000
    Data packet interval: 125 us
  Interface 1
    Altset 2
    Format: S32_LE
    Channels: 2
    Endpoint: 1 OUT (ASYNC)
    Rates: 44100, 48000, 88200, 96000, 176400, 192000
    Data packet interval: 125 us
  Interface 1
    Altset 3
    Format: SPECIAL DSD_U32_BE
    Channels: 2
    Endpoint: 1 OUT (ASYNC)
    Rates: 44100, 48000, 88200, 96000, 176400, 192000
    Data packet interval: 125 us
  Interface 1
    Altset 4
    Format: SPECIAL DSD_U32_BE
    Channels: 6
    Endpoint: 1 OUT (ASYNC)
    Rates: 44100, 48000, 88200, 96000, 176400, 192000
    Data packet interval: 125 us
 
remark, this new usb application with an enhanced descriptor has been tested also on Mac osx and on windows with a thesycon driver and this looks ok.


Post Reply