mikebrady / shairport-sync

AirPlay and AirPlay 2 audio player
Other
7.29k stars 574 forks source link

Audio corruption (not sound card related) #109

Closed timjosten closed 8 years ago

timjosten commented 9 years ago

Apparently, there's long standing bug in most Shairport forks. Be it the old Shairport, or Shairport-sync, or even Shairport4w, all of them are affected. I have tested them all on different platforms and experienced the same audio corruption. If I use any other receiver (like Airfoil), there's no corruption. The corruption is a noise appended to sound. I can clearly hear it in quiet passages, for example, I have one track which starts with electronic piano, it is clean when listening on local device (pc or phone), but on Shairport it is crackling like vinyl record. Not loud too. I did some data dumps and figured out what is causing the corruption. When going thru Shairport, audio data is corrupted. Every few kilobytes there are 4 bytes lost. I compared the original raw audio and the one outputted by shairport. Interestingly, it is always 4 bytes which get lost (1 stereo sample). They just drop out from the stream. This is causing slight but hearable "rales". It happens each 1-2 kilobytes. What is wrong? Any thoughts?

I have attached 2 audio dumps, they are renamed to GIFs as this forum doesn't allow other file types. The first one is original audio, the second is dumped from shairport pipe output. Open both in hex editor and compare page by page. You will see those 4 bytes gets dropped on each page.

original raw shairport raw

joerg-krause commented 9 years ago

The corruption is a noise appended to sound. I can clearly hear it in quiet passages, for example, I have one track which starts with electronic piano, it is clean when listening on local device (pc or phone), but on Shairport it is crackling like vinyl record. Not loud too.

It's not a bug, it's the "Sounds-like-good-old-vinyl"-feature :smile:

But seriously, very interesting! I've a patch in mind which I find at a fork of the orginal shairport: https://github.com/yenchee1970/shairport/commit/387fa01f2318f6502f77a07747d1ef5b693b1c9b

I'm not sure if it somehow related or if the patch from @yenchee1970 really fixes something. Do you mind to test it with this patch applied again?

timjosten commented 9 years ago

I am struggling with setting up OpenWrt build environment on a netbook. Have to wait an eternity if something fails to build.

joerg-krause commented 9 years ago

I see!

Just for clarification: shairport does stuffing by inserting or deleting frames. You can turn this almost of if you set drift to a large value, eg 65535.

Which version of shairport-sync did you used for testing?

timjosten commented 9 years ago

2.1.15-1

What about original shairport, or shairport for windows? Do they stuff too? They are affected too

joerg-krause commented 9 years ago

Of course, you're right! I just want to be sure that no stuffing is done at all.

I hexdumped both files and compared at which positions the four bytes are dropped: 0x5b8 0xd90 0x1042. So they are not dropped at a regular interval...

Maybe @mikebrady has an idea...

joerg-krause commented 9 years ago

Which command line settings did you used for testing?

timjosten commented 9 years ago

I used nothing for the command line options. I simply haven't found anything interesting there to try (except for stuffing, which resulted in heavy cpu load if soxr is enabled).

Off topic: sometimes, the root of such problems (when something is incomplete) is when using incorrect comparator inside for() or while() cycles. For example < instead of <=. Or ++i instead of i++.

mikebrady commented 9 years ago

Thanks for the posts. I'll have a look at the files and that patch, though it doesn't look like it could be the case of the problem. One thing that is completely unaltered in Shairport Sync is the code that decompressed the incoming Apple Lossless data. It wold be really nice to see if that is the cause of the glitches.

timjosten commented 9 years ago

Well, for my application I need no stuffing at all. So that patch in a stuff_buffer function is a good hint (yay!). I can even binary patch the code to not bother with recompiling. But... The problem for other users is there anyway. There's corruption even if streaming from localhost to localhost. For example, I tried Airfoil streaming to Shairport4w on the very same system, and there's corruption. There couldn't be any delays in transmission on localhost. :)

mikebrady commented 8 years ago

Hi there @timjosten. Would there be possibility of reposting those two files please?

[Update] It's okay, thanks – I've got em.

mikebrady commented 8 years ago

Different question for you, @timjosten, please: how did you generate the "original" audio file?

timjosten commented 8 years ago

Hi @mikebrady! The original audio file is just a FLAC decompressed to WAV, then converted to RAW. I know what stuffing is, it is needed of course because different systems tend to playback audio at slightly different speeds (hardware timers are not atomic precision). But why stuffing is there when capturing and playing on localhost? I have enabled soxr and I don't hear any noises anymore.

P.S. Are two separate computers are so out of sync in terms of timers that Shairport is stuffing so often? I thought it should stuff several samples in each 10-20 seconds to be in sync. But it is doing it thousands of times per second (so it is hearable)!

mikebrady commented 8 years ago

Hello again. There are a number of things in your post, so I'll deal with them separately.

  1. I've just spent some time comparing two different decoders. The first decoder was made by David Hammerton and is used in Shairport Sync and, as far as I know, most of the other shairport* applications. The second decoder is the Apple Lossless Audio Codec (ALAC) from http://alac.macosforge.org. I made up a version of Shairport Sync that allows you to choose one decoder or the other. I supply the same audio from iTunes to Shairport Sync and it outputs the raw audio via the stdout backend to a file. My experiments show that the two decoders produce identical results – bit for bit, the outputs are identical. I haven't yet found a way to generate the raw audio from the source in a way that I can be sure is accurate, but the fact that the two decoders generate exactly the same output suggests that they are both working according to specification. An alternative explanation is that they are both faulty in an identical way, but that is implausible, I think.
  2. I do not know why there is a need for stuffing on a localhost – I've never really figured it out. It may be that the DAC is running on a separate clock, or the DAC clock is inaccurate (this is a known issue with some sound cards, BTW).
  3. In my experience, clocks would rarely be out by more than 100 ppm, which would take approximately four corrections per second to fix (a single correction adds or deletes 1/44,100 second, about 22.5 ppm). If it's doing thousands per second, there's a problem somewhere. Turn on statistics in Shairport Sync to get some data. Just FYI, I have a number of situations where the difference is well under 10 ppm, so a correction is a pretty rare event.
mikebrady commented 8 years ago

[Update] I finally figured out how to do this end-to-end checking reliably.

First, the source machine is a Mac running OS X 10.11.5, i.e. the latest El Capitan, with the latest iTunes, 14.4.1.6. iTunes is running with all "Playback Preferences" unselected – so "Crossfade", "Sound Enhancer" and "Sound Check" are all unselected.

The source track is a regular CD track stored in a WAV file. The Mac utility afinfo reports its properties:

File:           <filepath>
File type ID:   WAVE
Num Tracks:     1
----
Data format:     2 ch,  44100 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer
                no channel layout.
estimated duration: 282.866667 sec
audio bytes: 49897680
audio packets: 12474420
bit rate: 1411200 bits per second
packet size upper bound: 4
maximum packet size: 4
audio data file offset: 44
optimized
source bit depth: I16
----

Note that this is how the source track is stored in iTunes. My understanding of AirPlay is that the source audio is losslessly compressed to ALAC and sent over the network to the AirPlay device where it is expanded, again losslessly, and sent to the audio output.

The only special setting at the Shairport Sync end is that the general ignore_volume_control setting is set to "yes". As mentioned before, I'm using the stdout back end, which sends the audio to standard output rather than an audio device. No "stuffing" or interpolation takes place. The command I used to invoke "Shairport Sync" was

./shairport-sync -v -o stdout > ~/Desktop/hammerton_sample.bin

In this case, Shairport Sync was picking up settings from the configuration file, but was then directing its output to stdout, which was piped, as indicated, to the file.

The track was played in iTunes to the Shairport Sync device, and Shairport Sync was terminated after the Player thread exit debug message came up. This closed the output file.

Overall, what I expected to happen was that the output from Shairport Sync would be bit-for-bit identical to the audio in the original source track.

There are slight issues with finding the start and end of the audio. The audio data file offset figure allows us to skip the first 44 bytes of metadata in the source track file, so I simply looked for the first non-zero byte thereafter. To skip the approximately two seconds of silence introduced by Shairport Sync at the start of playback, I similarly searched for the first non-zero byte in the Shairport Sync output. At the end of the track, we could similarly expect extra silence to be added, but silence would be represented by all zeros.

Having synchronized the start of the source file's audio to the start of the Shairport Sync file's audio as described above, I found that the audio output from Shairport Sync is indeed bit-for-bit identical with the audio content of the source file. This is all using the standard decoder in Shairport Sync, written by David Hammerton. There is a trailer of an extra few hundred bytes of silence at the end of the Shairport Sync file, as expected.

So, overall, I'm thinking that the problems you have are caused by something else...

timjosten commented 8 years ago

@mikebrady you're awesome! :) I did almost the same procedure a year ago. But there was stuffing involved. I don't remember if I used the pipe output from Shairport Sync... My "real" question was why was it stuffing so often? The sound was constantly distorted noticeably, like a person singing with troubled throat :))

Have you binary compared those raw files I have attached in the beginning? In my case it was skipping 4 bytes each ~1500 bytes. This turns to - each ~375 samples. So each 8 milliseconds one sample is skipped. Pretty much significant!

mikebrady commented 8 years ago

I did indeed compare the two files and it did show bytes missing but also bytes inserted.

As to why it's stuffing so often, I'm afraid I don't know. You'd have to do a thorough investigation using known-good components, at least temporarily, to isolate the issues.

mikebrady commented 8 years ago

Is it okay to close this issue? I think the main topic is resolved.

timjosten commented 8 years ago

Yeah, close it. The 'basic' stuffing is the cause - it is so simple that it causes some distortion. If timers are bad, it will stuff more often, resulting in bad sound. SOXR stuffing is the cure.

joerg-krause commented 8 years ago

I made up a version of Shairport Sync that allows you to choose one decoder or the other.

Can you provide a branch for this, please? I would like to test the Apple decoder with HTC Connect to further investigate this issue: #338