The qemu sound system got a bunch of improvements in 2018 and 2019.

New in qemu 3.0

The hda emulation uses a high resolution timer now to better emulate the timing-sensitive dma transfer of sound samples. Credits for this implementation go to Martin Schrodt.

Unfortunaly this is incompatible with older qemu versions, so it is only enabled for 3.0+ machine type versions. So upgrading qemu is not enough to get this, you also have to make sure you are using a new enough machine type (qemu -M command line switch).

libvirt stores the machine type in the domain xml when the guest is created. It is never updated automatically. So have a look at your domain configuration (using virsh edit domain-name for example) and check the version is 3.0 or newer:

[ ... ]
    <type arch='x86_64' machine='pc-q35-3.0'>hvm</type>
[ ... ]

New in qemu 3.1

The pulseaudio backend got fixes in 3.1, so if you are using pulse you should upgrade to at least qemu version 3.1.

New in qemu upcoming 4.0

Yet another pulseaudio bugfix.

Initial support for the -audiodev command line switch was finally merged. So audio support is not the odd kid any more which is configured in a completely different way, using environment variables instead of command line switches. Credits for this go to Kővágó, Zoltán.

In the pipeline

There are more -audiodev improvements in the pipeline, they are expected to land upstream in the 4.1 or 4.2 devel cycle.

Latency tuning

While being at it one final note:

Bugs in qemu sound device emulation and audio backends are not the only possible root cause for bad sound quality. Crackling sound -- typically caused by buffer underruns -- can also be caused by latency problems elsewhere in qemu.

One known offender is disk I/O, specifically the linux aio support which isn't as async as it should be and blocks now and then. linux aio support is configured with io=native for block device backends.

Better choice is io=threads. In libvirt xml:

[ ... ]
    <disk type='...' device='disk'>
      <driver name='qemu' type='...' cache='none' io='threads'/>
[ ... ]

Another known issue is spice audio compression, so better turn that off when using spice:

[ ... ]
    <graphics type='spice'>
      [ ... ]
      <playback compression='off'/>
[ ... ]