High definition audio on a Raspberry Pi with a DAC (some notes)

Hi all,

this post is a follow-up to cong314159’s post that got me interested in playing high definition audio to a DAC.

It is intended to be a partial success story for anyone wishing to achieve the same.

My Raspberry Pi 3 B v1.2 is the client (player) in this scenario, on which I tried various incarnations of Plex.
I purchased a Topping E30 USB DAC for AUD200 after some investigation. I decided against the D50S due to price (I wasn’t sure it would work) and I didn’t need the bluetooth audio feature. That plus a trusted reviewer (Review) could barely tell the difference.

I recommend using a decent (Apple or other big brand) USB switching power supplies for both the DAC and the Pi (USB chargers tested). Definitely the 10W/2A model for the Pi. Sure you could spend AUD500 on a linear DC supply for the DAC but read Do you need a linear power supply… first.

Anyway the good news was that the Pi saw the connected DAC without any issues; no drivers or kernel tweaking necessary.

Plex on a Pi presents the DAC in a strange way, quite unlike on a Mac for example. The short of it is if you want high-def audio you must choose the S/PDIF device (also known as iec958). I’ve no idea why it’s called that. Firstly it’s USB (not coax or fibre) and secondly the supported bitrates of the device by far exceed those of S/PDIF. You also get a ton of other devices to ignore (E30 front speakers, E30 stereo, E30 4.0, E30 4.1, …). If you’re only doing this for music, don’t bother with passthrough (unless you have some DTS audio, I guess. I didn’t test this as my setup is stereo, and I’m not sure if the E30 can decode it anyway).

All of my high-def music is either PCM or DSD, stored in FLAC containers (aka, DSD over PCM, or DoP). See @cong314159’s post above for sticking DSD into FLAC.

The key point to getting decent high-def out of a Pi is preventing transcoding at the server, and filtering/resampling at the client. Transcoding shouldn’t be happening in a local network with FLAC audio but just in case verify that by looking at the server’s dashboard when playing a track. I did see transcoding when playing a DSD file (.dff I think) which is why you need DSD in FLAC.

Plex turns on filters at the client for various reasons - the main two are:

  • software volume: as soon as the volume is anything other than 100% (loudest) on the player Plex turns on the volume filter. This attenuates the PCM audio in software - it changes the bitstream for PCM and, more importantly, BREAKS DSD. This is because DSD over PCM, or DoP, is not PCM, but rather just looks like PCM until it reaches something that can distinguish it (the DAC). If you’re playing DoP and press the Minus key or drag the volume slider the filter destroys the DSD and all you’ll get is white noise (which is deliberate and part of the DoP spec.). Anyway the main point is: leave the volume unattenuated and control it with your amplifier (which I assume you DAC is connected).

  • mismatched audio properties of the source and the DAC’s capabilities: I found this out when trying to send a 5.1 DoP track to the DAC (that only supports stereo). Plex tried to do the right thing and down-mixed it but again, DoP isn’t PCM, and the stream was destroyed. A 5.1 PCM stream will probably work, but because I’m stereo only I only use 2 channel audio. Also the aim here is to get your high-def audio to the DAC unadulterated. Other reasons mismatches occur are bit rates, sample sizes, integer and float types, etc.

Here the Plex log file is your friend. SSH over to the Pi and look at /storage/.local/share/plexmediaplayer/logs/plexmediaplayer.log (that’s for PMP - for RasPlex/OpenPHT it’s somewhere else).

Here’s a ripped CD being played:

PlayerComponent.cpp @ 594 - af: Audio filter chain: 
PlayerComponent.cpp @ 594 - af:   [in] 44100Hz stereo 2ch s16 
PlayerComponent.cpp @ 594 - af:   [out] 44100Hz stereo 2ch s16 
PlayerComponent.cpp @ 594 - af:   [ao] 44100Hz stereo 2ch s16 
PlayerComponent.cpp @ 594 - ao: Trying audio driver 'alsa' 
PlayerComponent.cpp @ 594 - ao: Using preferred device 'iec958:CARD=E30,DEV=0' 
PlayerComponent.cpp @ 594 - ao/alsa: requested format: 44100 Hz, stereo channels, s16
...
PlayerComponent.cpp @ 594 - ao/alsa: using soft-buffer of 11025 samples. 
PlayerComponent.cpp @ 596 - cplayer: AO: [alsa] 44100Hz stereo 2ch s32 
PlayerComponent.cpp @ 594 - cplayer: AO: Description: ALSA audio output 
PlayerComponent.cpp @ 594 - af: Adding filter lavrresample  
PlayerComponent.cpp @ 594 - af: Setting option 'normalize' = 'no' (flags = 0) 
PlayerComponent.cpp @ 594 - af: Setting option 'o' = 'surround_mix_level=1' (flags = 0)
PlayerComponent.cpp @ 594 - af: Audio filter chain: 
PlayerComponent.cpp @ 594 - af:   [in] 44100Hz stereo 2ch s16 
PlayerComponent.cpp @ 594 - af:   [lavrresample] 44100Hz stereo 2ch s32 [a] 
PlayerComponent.cpp @ 594 - af:   [out] 44100Hz stereo 2ch s32 
PlayerComponent.cpp @ 594 - af:   [ao] 44100Hz stereo 2ch s32
PlayerComponent.cpp @ 594 - cplayer: starting audio playback

The keen-eyed reader will note that this is being resampled. Why? It appears the DAC can’t do 16-bit samples (s16) or Plex is insisting on sending 32-bit samples. I need to investigate this further. In any case the shifting 16 bits into 32 bits is a trivial, lossless calculation and so has no effect on the audio.

For your high-def audio however, lavrresample lines in the Audio filter chain are to be avoided where possible for PCM, and always for DoP.

Here’s a 96Khz, 32 bit PCM track:

PlayerComponent.cpp @ 594 - af: Audio filter chain: 
PlayerComponent.cpp @ 594 - af:   [in] 96000Hz stereo 2ch s32 
PlayerComponent.cpp @ 594 - af:   [out] 96000Hz stereo 2ch s32 
PlayerComponent.cpp @ 594 - af:   [ao] 96000Hz stereo 2ch s32 
PlayerComponent.cpp @ 594 - ao: Trying audio driver 'alsa' 
PlayerComponent.cpp @ 594 - ao: Using preferred device 'iec958:CARD=E30,DEV=0' 
PlayerComponent.cpp @ 594 - ao/alsa: requested format: 96000 Hz, stereo channels, s32
...
PlayerComponent.cpp @ 596 - cplayer: AO: [alsa] 96000Hz stereo 2ch s32 
PlayerComponent.cpp @ 594 - cplayer: AO: Description: ALSA audio output
PlayerComponent.cpp @ 594 - cplayer: starting audio playback

and the same track in DSD (DoP):

PlayerComponent.cpp @ 594 - af: Audio filter chain:            
PlayerComponent.cpp @ 594 - af:   [in] 176400Hz stereo 2ch s32   
PlayerComponent.cpp @ 594 - af:   [out] 176400Hz stereo 2ch s32  
PlayerComponent.cpp @ 594 - af:   [ao] 176400Hz stereo 2ch s32                     
PlayerComponent.cpp @ 594 - ao: Trying audio driver 'alsa'                            
PlayerComponent.cpp @ 594 - ao: Using preferred device 'iec958:CARD=E30,DEV=0'        
PlayerComponent.cpp @ 594 - ao/alsa: requested format: 176400 Hz, stereo channels, s32
...
PlayerComponent.cpp @ 596 - cplayer: AO: [alsa] 176400Hz stereo 2ch s32 
PlayerComponent.cpp @ 594 - cplayer: AO: Description: ALSA audio output 
PlayerComponent.cpp @ 594 - cplayer: starting audio playback

If I change the volume:

PlayerComponent.cpp @ 594 - cplayer: Set property: volume=63 -> 1 
...
PlayerComponent.cpp @ 594 - cplayer: Inserting volume filter.
PlayerComponent.cpp @ 594 - af: Adding filter volume                                   
...
PlayerComponent.cpp @ 594 - af: Audio filter chain:
PlayerComponent.cpp @ 594 - af:   [in] 176400Hz stereo 2ch s32  
PlayerComponent.cpp @ 594 - af:   [lavrresample] 176400Hz stereo 2ch float [a]
PlayerComponent.cpp @ 594 - af:   [volume] "softvol" 176400Hz stereo 2ch float
PlayerComponent.cpp @ 594 - af:   [lavrresample] 176400Hz stereo 2ch s32 [a]
PlayerComponent.cpp @ 594 - af:   [out] 176400Hz stereo 2ch s32
PlayerComponent.cpp @ 594 - af:   [ao] 176400Hz stereo 2ch s32
PlayerComponent.cpp @ 594 - volume: volume gain: 0.238328
PlayerComponent.cpp @ 594 - cplayer: Set property: volume=62 -> 1

The audio changes to white noise (as expected, this is DoP). Note the insertion of the soft volume filter.

As for software, here’s the condensed story:

  • RasPlex/OpenPHT: for everything other than DoP it worked well other than the occasional crash. The bonus was gapless playback. Dark Side of the Moon sounded great. On some, if not all, DoP tracks I got audible crackles and pops which ruined the listening experience (during track one of Amused to Death, for example). I tried a better power supply to the Pi but that didn’t help.

  • PlexMediaPlayer Embedded community build (4.29.3): flawless audio, but of course, no gapless playback. Which makes the experience of crystal clear SACD audio of Dark Side or Amused simply awful.

  • PlexAmp v2.0.0 beta 2: Not being proficient at Linux and the dearth of documentation for PlexAmp has me at a disadvantage here. I’ve yet to get any audio at all as the wrong default device is being selected. I think I’ve discovered how to remedy this. I need to test and report back.

Finding a player that is modern, supported and provides gapless playback is the holy trinity that I am yet to find. I won’t turn this into a rant about Plex as I am loathe to overly criticise free software, nor would I be to first to request gapless playback, but I don’t quite understand Plex’s silence on the matter. The product line-up is also confusing to me. I understand the server, but there is (was) PHT, and there’s Plex (the app), Plex Media Player, PlexAmp (audio only). They each have different features, supported platforms, and pricing (PlexAmp is now PP only I believe). If Plex wanted to take a good chunk of Roon’s space it would be relatively easy to have an embedded, high-def audio solution that supported gapless playback. The code is all there. (Obviously bells and whistles such a sweet fades would be a no-no for the high-def music).

Anyway I hope this post helps some people, and if you can suggest any solutions to the missing pieces I’d appreciate it.

Scott

2 Likes

It always boggles my brain how plex can pass through video-audio (ie ac3/dts/etc), but not audio-audio.

1 Like

It’s likely a combination of history (XBMC), use cases, or both. If you convert everything to PCM as XBMC used to do, then you can play everything, both locally on the device, and to a PCM receiver.

If you could convert but something else did it better (like a Dolby/DTS capable receiver) then it made sense to pass it through. Multichannel PCM had to wait for a fast enough transmission like HDMI before it could be passed through or handed off.

Plex made it even tricker by adding an additional abstraction between the now remote server and the client, allowing for more transcoding. Plex’s direct play is really just passthrough from a client/server perspective. Much like XBMC locked the client and server together (eliminating most transcoding), Plex locked the client and rendering device together (without much thought to devices like DACs and receivers). They each took approaches to make most people’s lives easier. I’m sure that’s oversimplified but that’s how I see it. Anyway back to the high-def audio…

P.S. I could have saved myself a ton of time and just called this post: “+1 for gapless playback” :slight_smile:

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.