Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Odroid N2 | Significant audio distortion with USB DAC #5949

Closed
Kreeblah opened this issue Dec 4, 2022 · 18 comments
Closed

Odroid N2 | Significant audio distortion with USB DAC #5949

Kreeblah opened this issue Dec 4, 2022 · 18 comments

Comments

@Kreeblah
Copy link

Kreeblah commented Dec 4, 2022

Creating a bug report/issue

Required Information

  • DietPi version | 8.11.2
  • Distro version | bullseye
  • Kernel version | Linux DietPi 5.19.17-meson64 #22.11.1 SMP PREEMPT Wed Nov 30 11:05:42 UTC 2022 aarch64 GNU/Linux
  • SBC model | Odroid N2 (aarch64) - Actually an N2+, if it matters
  • Power supply used | 12V/2A from AmeriDroid
  • SD card used | 8GB eMMC

Additional Information (if applicable)

  • Software title | Roon Bridge
  • Was the software title installed freshly or updated/migrated? Freshly installed
  • Can this issue be replicated on a fresh installation of DietPi? Yes.
  • Bug report ID | 89a3073f-311b-421b-8bd3-162dc9172e46
  • USB DAC | Hidizs S9 Pro

Steps to reproduce

  1. Connect the USB DAC
  2. Set ALSA to use either "any USB DAC" or the specific hardware ID for the USB DAC (both yield the same results)
  3. Play something from Roon

Expected behaviour

  • The audio should be clear

Actual behaviour

  • There's significant audio distortion

Extra details

  • This same hardware setup works fine with the official ODROID minimal images. I don't experience audio distortion with those.
@MichaIng
Copy link
Owner

MichaIng commented Dec 4, 2022

Many thanks for your report.

Probably a limitation of the mainline kernel, but I didn't hear of this until now.

3.5mm jack and HDMI audio work fine? And you did try all USB ports, whether it makes a difference?

I can test it when I find time, having N2+ and a (cheap) USB DAC here as well.

@MichaIng MichaIng changed the title Significant audio distortion with USB DAC Odroid N1 | Significant audio distortion with USB DAC Dec 4, 2022
@MichaIng MichaIng changed the title Odroid N1 | Significant audio distortion with USB DAC Odroid N2 | Significant audio distortion with USB DAC Dec 4, 2022
@Kreeblah
Copy link
Author

Kreeblah commented Dec 5, 2022

I did some more testing based on your question, and I have a more specific condition for this to happen. While I don't have a good way of testing the HDMI audio (so I don't know whether there's an issue there or not), I was able to test the internal 3.5mm output, and it worked fine. So did my USB DAC afterwards.

I was able to replicate this on another fresh install, too. It doesn't matter which USB port I have the DAC plugged into. It sounds extremely distorted until I first use dietpi-config to switch to the 3.5mm output and then back to the DAC. I don't even have to play anything through the 3.5mm jack. Just switch to it and back to the USB DAC. After that, my DAC sounds fine on any USB port, and it persists across reboots (whereas prior to doing that, the distortion persisted across reboots).

Is it possible that something might not be initialized properly except when switching to the internal 3.5mm output?

@MichaIng
Copy link
Owner

MichaIng commented Dec 5, 2022

Probably it's related to hardware interrupts all done on the same CPU. E.g. on legacy kernel C2 images we apply SMP affinities. Can you show:

cat /proc/interrupts

Here is what is shows in my case (Odroid N2+):

root@OdroidN2:~# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5
  9:          0          0          0          0          0          0     GICv2  25 Level     vgic
 11:       1880       1663       4531       2093       1961       1717     GICv2  30 Level     arch_timer
 12:          0          0          0          0          0          0     GICv2  27 Level     kvm guest vtimer
 14:        250          0          0          0          0          0     GICv2 225 Edge      ttyAML0
 15:         24          0          0          0          0          0     GICv2  71 Edge      ffd1c000.i2c
 16:        573          0          0          0          0          0     GICv2  35 Edge      meson
 17:         15          0          0          0          0          0     GICv2  89 Edge      dw_hdmi_top_irq, ff600000.hdmi-tx
 18:          0          0          0          0          0          0     GICv2  63 Level     ff400000.usb, ff400000.usb
 19:      30760          0          0          0          0          0     GICv2  62 Level     xhci-hcd:usb1
 20:          1          0          0          0          0          0     GICv2  48 Level     ffe09000.usb
 21:          0          0          0          0          0          0     GICv2 222 Edge      ffe05000.sd
 22:       5077          0          0          0          0          0     GICv2 223 Edge      ffe07000.mmc
 27:          2          0          0          0          0          0     GICv2 228 Edge      ff808000.ir
 28:          2          0          0          0          0          0     GICv2 232 Edge      ff809000.adc
 29:          4          0          0          0          0          0     GICv2 192 Level     panfrost-gpu
 30:          0          0          0          0          0          0     GICv2 193 Level     panfrost-mmu
 31:          0          0          0          0          0          0     GICv2 194 Level     panfrost-job
 32:          0          0          0          0          0          0     GICv2  76 Edge      vdec
 33:          0          0          0          0          0          0     GICv2  64 Edge      esparserirq
IPI0:        98        102        491        181        480        342       Rescheduling interrupts
IPI1:      1636       1892       4033      18074       3087       7800       Function call interrupts
IPI2:         0          0          0          0          0          0       CPU stop interrupts
IPI3:         0          0          0          0          0          0       CPU stop (for crash dump) interrupts
IPI4:         0          0          0          0          0          0       Timer broadcast interrupts
IPI5:        68         22         42         89         70         58       IRQ work interrupts
IPI6:         0          0          0          0          0          0       CPU wake-up interrupts
Err:          0

If the IDs are/stay the same, USB-related interrupts can be moved to a different core. In my case they stay the same across reboots, so the interrupts can be done by the first A73 core instead:

root@OdroidN2:~# echo 4 > /proc/irq/18/smp_affinity
root@OdroidN2:~# echo 4 > /proc/irq/19/smp_affinity
root@OdroidN2:~# echo 4 > /proc/irq/20/smp_affinity
root@OdroidN2:~# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5
 18:          0          0          0          0          0          0     GICv2  63 Level     ff400000.usb, ff400000.usb
 19:      32392          0       2664          0          0          0     GICv2  62 Level     xhci-hcd:usb1
 20:          1          0          0          0          0          0     GICv2  48 Level     ffe09000.usb

As you can see, now xhci-hcd:usb1 interrupts start to be processed by CPU2. Probably this helps with USB audio.

@Kreeblah
Copy link
Author

Kreeblah commented Dec 5, 2022

root@DietPi:~# cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       
  9:          0          0          0          0          0          0     GICv2  25 Level     vgic
 11:      21971       7326      14605      13965      12332      10759     GICv2  30 Level     arch_timer
 12:          0          0          0          0          0          0     GICv2  27 Level     kvm guest vtimer
 15:         60          0          0          0          0          0     GICv2  71 Edge      ffd1c000.i2c
 16:        867          0          0          0          0          0     GICv2  35 Edge      meson
 17:         15          0          0          0          0          0     GICv2  89 Edge      dw_hdmi_top_irq, ff600000.hdmi-tx
 18:          0          0          0          0          0          0     GICv2  63 Level     ff400000.usb, ff400000.usb
 19:      56914          0          0          0          0          0     GICv2  62 Level     xhci-hcd:usb1
 20:          1          0          0          0          0          0     GICv2  48 Level     ffe09000.usb
 21:          0          0          0          0          0          0     GICv2 222 Edge      ffe05000.sd
 22:       5472          0          0          0          0          0     GICv2 223 Edge      ffe07000.mmc
 24:       7807          0          0          0          0          0     GICv2  40 Level     eth0
 25:          1          0          0          0          0          0  meson-gpio-irqchip  26 Level     mdio_mux-0.0:00
 27:          2          0          0          0          0          0     GICv2 232 Edge      ff809000.adc
 28:          0          0          0          0          0          0     GICv2 228 Edge      ff808000.ir
 32:          0          0          0          0          0          0     GICv2  76 Edge      vdec
 33:          0          0          0          0          0          0     GICv2  64 Edge      esparserirq
 37:          4          0          0          0          0          0     GICv2 192 Level     panfrost-gpu
 38:          0          0          0          0          0          0     GICv2 193 Level     panfrost-mmu
 39:          0          0          0          0          0          0     GICv2 194 Level     panfrost-job
IPI0:       311        313        598        749        648        590       Rescheduling interrupts
IPI1:      4145       6238       8207       7969       7402       6376       Function call interrupts
IPI2:         0          0          0          0          0          0       CPU stop interrupts
IPI3:         0          0          0          0          0          0       CPU stop (for crash dump) interrupts
IPI4:         0          0          0          0          0          0       Timer broadcast interrupts
IPI5:       328        175        802        601        280        398       IRQ work interrupts
IPI6:         0          0          0          0          0          0       CPU wake-up interrupts
Err:          0
root@DietPi:~# 

@Kreeblah
Copy link
Author

Kreeblah commented Dec 5, 2022

Seems to be 18, 19, and 20 on mine as well.

@MichaIng
Copy link
Owner

MichaIng commented Dec 5, 2022

Looks good, most IRQs are the same. The graphics/panfrost ones are different, somehow, and you do not seem to use the serial console, while I use no Ethernet (a USB WiFi adapter instead). Not sure whether the IDs depend on the order the devices are detected by the kernel, e.g. ff808000.ir and ff809000.adc have switched IDs in our cases.

Test whether changing the affinity helps to solve the USB sound quirks. If so, I'll think about how to implement this into our Odroid N2 images OOTB.

@Kreeblah
Copy link
Author

So, I've been trying to do some testing to see whether changing the CPU affinity helps, but I actually can't replicate this issue any more, even on a fresh install. I'm absolutely baffled, since I was able to replicate it for months (and swapped to the ODROID Ubuntu distribution on this device for a bit, assuming it was a temporary bug), but . . . it doesn't seem to be happening for me now that I want to test the affinity settings you suggested. I'm following exactly the same steps I was before from the same image, on the same hardware, with a different result.

I'm absolutely mystified, but I guess it works for me out of the box now?

@MichaIng
Copy link
Owner

Strange indeed. A I see you used Linux 5.19. Probably you switched to edge kernel to solve or test another issue? Then with edge Linux the issue may still exist.

Yours is Linux 5.10, right?

uname -a

@Kreeblah
Copy link
Author

Kreeblah commented Dec 18, 2022

No, I'm actually on 5.19.17.

# uname -a
Linux DietPi 5.19.17-meson64 #22.11.1 SMP PREEMPT Wed Nov 30 11:05:42 UTC 2022 aarch64 GNU/Linux

This is just from the DietPi image on the dietpi.com site with whatever updates it does (so, no switching kernels or branches) and installing the Roon bridge and dependencies/enabling the USB DAC. No other changes.

I just double-checked the image I've been using to make sure it's the same as what's currently, and it's the same. Both have a SHA256 sum of 8b6bb04307086d91bba4966025272637d15b71a9e6ce060dbfeba66ab6614a58 for the 7zip file.

@MichaIng
Copy link
Owner

Ah okay, I just didn't recognise yet that the "current" kernel branch for meson64 obviously moved to Linux 5.19 already. Seems to have been done with Armbian v22.11.

Let me keep this issue open, so when I'm back home from holiday I'll check whether/how Armbian's boot scripts do tweak SMP affinities on meson64/Odroid N2 and how. I actually also read somewhere else that it doesn't have any benefit nowadays, but it doesn't sound logic to me to let a single core dealing with all hardware interrupts.

@MichaIng
Copy link
Owner

Okay, lacking a better method, this should do it:

cat << '_EOF_' > /var/lib/dietpi/postboot.d/smp_affinities.sh
#!/bin/dash
while read -r line
do
	[ "$skipped" ] || { skipped=1; continue; } # Skip first line
	irq=${line%%:*}
	expr "$irq" : '^[0-9]\+$' > /dev/null || break # Stop when reaching non-numeric IRQs
	case "$line" in
		*mmc*) echo 1 > "/proc/irq/$irq/smp_affinity_list";;
		*xhci-hcd*) echo 2 > "/proc/irq/$irq/smp_affinity_list";;
		*eth0*) echo 3 > "/proc/irq/$irq/smp_affinity_list";;
	esac
done < /proc/interrupts
_EOF_
  • MMC to CPU1
  • USB3 to CPU2
  • Ethernet to CPU3

@MichaIng MichaIng added this to the v8.15 milestone Feb 12, 2023
@MichaIng
Copy link
Owner

@Kreeblah
Are you still bugged with the issue and able to test the above script?

@MichaIng MichaIng modified the milestones: v8.15, v8.16 Mar 11, 2023
@MichaIng
Copy link
Owner

I tested it here with a cheap LogiLink USB DAC and test OGG and MP3 audio files and could not face any audio distortion on different ports with a USB WiFi adapter attached as well. Hence I cannot test whether above SMP affinity tweak is helping.

We are talking about the digital audio stream to the DAC, so the USB data transfer must be significantly disturbed for such effect, but if anyone can verify it, and that the above tweak helps, I'll add this OOTB to our Odroid N2 images, as other USB-related tasks would then benefit as well.

@Kreeblah
Copy link
Author

Adding that script to set affinities works fine for me, though there was a point at which I stopped being able to replicate this issue on my hardware without it, and I'm still not sure why, or what was causing it originally.

Still, it's probably worth including this in the OOTB images since the N2 has several cores to work with anyway.

@MichaIng
Copy link
Owner

Okay thanks for reporting back. I'll reread current information about SMP affinities and whether they really make still sense with current Linux. I remember some discussion which stated it wouldn't have benefits today, and that this is also a reason why it isn't possible on Raspberry Pi (the handles are write-protected). We shouldn't add a script if it has no actual benefit 😉.

@Kreeblah
Copy link
Author

Kreeblah commented Mar 15, 2023

Now that I've had that script running for a few days, I've noticed something else. Up until a few days ago, using the volume slider in Roon would sometimes cause one channel (left or right) to cut out until I changed the volume again. I figured that was just an issue with my DAC, but it hasn't happened since then.

I'll need to do some more testing, though, since I updated to v8.15 shortly after it became available, so I'll need to isolate whether it's a result of the new version or the CPU affinity script.

@MichaIng MichaIng added Enhancement 💨 and removed Solution available 🥂 Definite solution has been done labels Apr 1, 2023
@Kreeblah
Copy link
Author

Kreeblah commented Apr 2, 2023

I took a more in-depth look, and it doesn't seem that the CPU affinity script is what improved things there. Removing the script doesn't seem to make a difference. I think the new version resolved things for me.

It turns out I do still occasionally get some odd behavior when adjusting the volume (occasionally it'll drop to 0 instead of the value it was supposed to go to), but that seems to be extremely rare. I've only gotten it to happen a couple of times despite trying to make it happen. That's a separate issue, though, so if it ends up being a big thing, I'll open a new one. As far as what I originally reported here, though, I haven't been able to replicate it in a while, so I think it's solved out of the box now.

@MichaIng MichaIng removed this from the v8.16 milestone Apr 3, 2023
@MichaIng
Copy link
Owner

MichaIng commented Apr 3, 2023

Okay, thanks for testing. I was reading a little about SMP IRQ affinities and generally it is possible that this enhances performance or lower latency on high load systems or real-time applications. Sometimes it is not about balancing IRQs between CPU cores but to have interrupts for a hardware device and the related process on the same core for cache sharing.

The cache is btw also a reason why letting multiple cores handling the same interrupt is usually not beneficial. Also power saving considerations play a role as you do not want all core to wake up regularly if actually one core would be able to deal with all interrupts. However, there is a daemon for balancing IRQs among cores which is interesting to have a look at when looking deeper into this topic:

I think the cases where this makes a real difference are low, otherwise the default wouldn't be that CPU0 handles all hardware interrupts by default. Marking this issue as closed but added to archive to remember and in case have another look when we find evidence that it helps.

@MichaIng MichaIng closed this as completed Apr 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants