Comparing Pandora's and Spotify's Audio

posted on 2016-12-21 at 12:30 EST

The impetus for this article is a post by a friend that Facebook pushed into my feed. In it, his friend asserts that Pandora's audio is worse than Spotify's audio. My friend responds with each services respective streaming bitrates. While the answer isn't wrong, it isn't complete; the two services use different formats, thus making a simple bitrate comparison impossible.

Impetus for article

Bitrates

According to Spotify's support they encode their audio using Ogg Vorbis at bitrates of ~96Kbps ("normal" mobile), ~160Kbps ("normal" desktop / "high" mobile), and ~320Kbps (paid premium).

Pandora's support indicates that they encode their audio using Advanced Audio Coding (AAC) at bitrates of 64Kbps on the web for free listeners, up to and including 64Kbps for mobile, 128Kbps for home devices (e.g. Sony STR-DN1040 receiver), and 192Kbps for subscribers.

There are a few things to notice in these statements:

  1. Spotify is clearly stating that they use Variable Bitrate encoding (VBR) by prefixing their rates with a ~.
  2. Pandora doesn't give any indication they use VBR, so we can only assume they encode with Constant Bitrate encoding (CBR).
  3. They use completely different encoding algorithms.

Comparison Setup

Given the information in Bitrates, we can devise an analyical method for comparing the two services:

  1. Acquire a source sample with very high quality.
  2. Encode the source sample into the various formats at the various bitrates.
    • we will use CBR encoding to keep things simple
  3. Create a spectrogram of each sample.

As our source, we will use a sample from my 2015 release Thirty-six. This sample is a raw audio file recorded at 96Khz per second with a resolution of 24bit. The sample covers the audio range fairly well:

Source spectrogram

The full details of which are:

Input File     : 'source-sample.wav'
    Channels       : 2
    Sample Rate    : 96000
    Precision      : 24-bit
    Duration       : 00:00:12.00 = 1152000 samples ~ 900 CDDA sectors
    File Size      : 6.91M
    Bit Rate       : 4.61M
    Sample Encoding: 24-bit Signed Integer PCM
    

Additionally, we must consider that the majority of Spotify's and Pandora's source material comes from Compact Discs (CDs). Thus, their source material has a sample rate of 44.1KHz per second with a resolution of 16bit. Therefore, we will also encode our source sample to a reference sample with the same detail. The reference sample's spectrogram looks like:

Reference spectrogram

It is from this reference sample that we will generate the remaining samples.

Notice that this sample already has a lot more noise in the spectrogram when compared to the source's spectrogram. This indicates that no matter what encoder or bitrate is used, Spotify and Pandora (in the majority of cases) will never be able to stream a true representation of the music.

Utility Details

All wav and Ogg Vorbis encodings and spectrograms in this article are generated using SoX 14.4.2 as provided by MacPorts. For example, to generate the reference sample the following was used:

% sox source-sample.wav -b 16 -r 44100 reference-sample.wav
    % soxi reference-sample.wav
    
    Input File     : 'reference-sample.wav'
    Channels       : 2
    Sample Rate    : 44100
    Precision      : 16-bit
    Duration       : 00:00:12.00 = 529200 samples = 900 CDDA sectors
    File Size      : 2.12M
    Bit Rate       : 1.41M
    Sample Encoding: 16-bit Signed Integer PCM
    

It is important to note that we must use sox's -C parameter to encode into Ogg Vorbis. The details of which mean that we cannot explicitly specify sample rates. This is a limitation (feature) of the format, and is why Spotify cannot give explicit rates.

Our AAC encodings are created using FAAC because sox will not ship AAC encoding due to patent restrictions.

Normal Desktop Rates

Let's start our comparison by investigating each service's normal desktop encodings and bitrates for free listeners. We can clearly see from the spectrograms that at the normal, free tier, Spotify captures much more of the source audio than does Pandora. It's very likely you will have a hard time distinguishing the Spotify stream from their source material (the refrence sample in our tests). Whereas Pandora will be very noticeably different.

Result files:

Spotify Normal

% sox reference-sample.wav -C 4 spotify-normal.ogg
    % soxi spotify-normal.ogg
    
    Input File     : 'spotify-normal.ogg'
    Channels       : 2
    Sample Rate    : 44100
    Precision      : 16-bit
    Duration       : 00:00:12.00 = 529200 samples = 900 CDDA sectors
    File Size      : 245k
    Bit Rate       : 163k
    Sample Encoding: Vorbis
    Comment        : 'Comment=Processed by SoX'
    
    % sox spotify-normal.ogg -n spectrogram -t "Spotify Normal" -o spotify-normal-spectrogram.png
    
Spotify normal spectrogram

Pandora Normal

% faac -b 64 -o pandora-normal.m4a reference-sample.wav
    Freeware Advanced Audio Coder
    FAAC 1.28
    
    Average bitrate: 64 kbps
    Quantization quality: 100
    Bandwidth: 5604 Hz
    Object type: Low Complexity(MPEG-4) + M/S
    Container format: MPEG-4 File Format (MP4)
    Encoding reference-sample.wav to pandora-normal.m4a
       frame          | bitrate | elapsed/estim | play/CPU | ETA
      518/518   (100%)|   64.0  |    0.2/0.2    |   72.48x | 0.0
    
    % faad pandora-normal.m4a -o pandora-normal.wav
    % sox pandora-normal.wav -n spectrogram -t "Pandora Normal" -o pandora-normal-spectrogram.png
    
Pandora normal spectrogram

High Quality Rates

I this test we will look at Spotify's regular, free, "high quality" rate of ~192Kbps and Pandora's subscriber only "high quality" 192Kbps rate. This will give us our closest "apples to apples" comparision possible.

In this comparison we can see that both are capturing mostly the same amount of detail. However, the Ogg Vorbis encoding is capturing much more detail above 16KHz. This coincides very much with the average adult's range of hearing.

Result files:

Spotify High

% sox reference-sample.wav -C 5 spotify-high.ogg
    % soxi spotify-high.ogg
    Input File     : 'spotify-high.ogg'
    Channels       : 2
    Sample Rate    : 44100
    Precision      : 16-bit
    Duration       : 00:00:12.00 = 529200 samples = 900 CDDA sectors
    File Size      : 298k
    Bit Rate       : 198k
    Sample Encoding: Vorbis
    Comment        : 'Comment=Processed by SoX'
    
    % sox spotify-high.ogg -n spectrogram -t "Spotify High" -o spotify-high-spectrogram.png
    
Spotify high spectrogram

Pandora High

% faac -b 192 -o pandora-high.m4a reference-sample.wav                                                                                                       [s:0 l:3954]
    Freeware Advanced Audio Coder
    FAAC 1.28
    
    Average bitrate: 152 kbps
    Quantization quality: 100
    Bandwidth: 16000 Hz
    Object type: Low Complexity(MPEG-4) + M/S
    Container format: MPEG-4 File Format (MP4)
    Encoding reference-sample.wav to pandora-high.m4a
       frame          | bitrate | elapsed/estim | play/CPU | ETA
      518/518   (100%)|  152.3  |    0.2/0.2    |   52.24x | 0.0
    
    % faad pandora-high.m4a -o pandora-high.wav
    % sox pandora-high.wav -n spectrogram -t "Pandora High" -o pandora-high-spectrogram.png
    
Pandora high spectrogram

Conclusion

If we assume that I have come within an acceptable degree of modeling the encoding environments of both Spotify and Pandora, we can only conclude that Spotify streams the better audio. However, since sox does not support AAC files at all, we can't be too sure of our results. The sox and faac tools are very different, and would be even if sox supported AAC.

What is most concerning to me is the fact that faac seems to be using a low pass filter set at 16KHz for its upper threshold. If that is the case, then the Pandora results are not completely accurate. And I have hard time believing that Pandora would stream with such a cutoff.

Thus, if we assume Pandora's tooling does not have this 16KHz cutoff like faac does, we can theorize that Pandora's encodings would include more detail above 16KHz just as Spotify's streams would (based on our findings). Which leads me to the conclusion that Spotify is better for free users, but either service will be roughly equal for paid users.

Full discolsure: I do not use Spotify. I am a paid Pandora subscriber.


Switching To Void Linux On My HTPC

posted on 2016-01-10 at 17:15 EST

In 2008 I built myself a HTPC. It started out running Arch Linux but switched to Ubuntu when Arch decided to force systemd. Ubuntu's Upstart didn't live up to Arch's original RC system, but it fit the bill of not being systemd. I have never liked Ubuntu so it was most certainly a stop-gap solution. My replacement for Ubuntu is Void Linux.

I discovered Void a few months ago when Debian decided to force systemd as well. Once that happened I did some digging on Distrowatch for distributions that didn't include systemd (aside: technicall I did a search for distros with a specific init system, but that doesn't seem possible at the time of this writing). After researching a few on the list it was clear to me that Void Linux would be my new distribution of choice. The release model is very much like Arch's, makes a point of not using OpenSSL by using LibreSSL instead, and uses Runit for the init system.

Regarding LibreSSL over OpenSSL: look back at the early posts of opensslrampage.org. It's very illuminating.

Runit is rather amazing in its simplicity. The flexibility of systemvinit is still present, but there's pretty much no reason to have more than 5 lines in a Runit init script; still, there are crazy people out there. The short of it is Runit doesn't fork processes. It simply starts a process and waits for it to exit. If the process does exit, Runit restarts. So a complete init script can be:

#!/bin/sh
    mkdir -p /run/samba
    exec smbd -F -S
    

That simple script is all that is necessary to start Samba. Compare that to a traditional sysv init script or a systemd script and you'll see why this is so great.

So, getting back to my HTPC. Reinstalling the base OS with Void was very easy. And installing everything I needed to run my interface (Kodi) was even easier:

$ xbps-install kodi xorg x11vnc
    

Now, at this point there's always some trickery needed to get the system to boot straight to Kodi. This time was no exception. Initially I thought I'd be able to get by with a guide on the Void wiki. But that didn't pan out: the guide assumes the user will only ever be used for logging in straight to X11. I need to SSH to the system as that user on a regular basis, so that assumption wouldn't work.

When I originally built my HTPC back in 2008 there was a display manager that supported automatic logins without much hassle (I can't recall which one). But that got replaced with SLiM. SLiM supported automatic logins, but only on the first login. If whatever program you were running, Kodi in this case, crashed then you'd be staring at login screen. Who wants to get out a keyboard and mouse to use an entertainment system? Not me. I searched for a solution and found none, so I wrote my own tool for the job. If you've read this site for a while you may have seen it listed as "mythlogin", as I originally used MythTV. Since the guide's method of automatic login wouldn't work for me I once again turned to my tool. This time I've renamed it autox; this tool will be in the official Void respository likely by the time you read this.

I originally wrote autox to be used on a sysvinit system with an inittab. When I switched to Ubuntu it turned out using autox was almost just as easy. But under Runit? It wasn't so easy:

  1. autox doesn't truly log a user in to the system. It merely sets up his regular environment with all of his PAM granted permissions, e.g. real-time clock access.
  2. simply using agetty as the guide does results in the process being launched outside of Runit's supervisor proccess. That's no good since we want Runit to manage the process.

Digging in to how Void sets up ttys I learned about a tool I hadn't heard of before -- setsid. Combining setsid with agetty did the trick. The resulting Runit script for my HTPC:

#!/bin/sh
    
    sv start wpa_supplicant
    exec 2>&1
    exec setsid -w agetty -a htpc -n -l /usr/bin/autox -o htpc tty7 38400 linux
    

Wait. What is line number three? That's how you define a dependent service under Runit. Instead of some convoluted descriptor file like Upstart and systemd want you just start the required service. In this case I need network access and my HTPC is only connected via 802.11n currently. So I need to authenticate to my access point prior to launching Kodi since Kodi uses the Internet.

There was one other problem, though. I use x11vnc to make X11 accessible from my other computers. This is handy when I need to do something with Kodi that would be a chore with just an IR remote. I had been using my .xinitrc file to launch x11vnc. I was using exec to fork it off into its own process in the background. Well, do that under this new configuration resulted in x11vnc running outside of Runit's supervisor process. Again, not good. Solution? Runit:

#!/bin/sh
    sv start kodi
    exec x11vnc -many -q -avahi -ncache 10 -passwd super_secret
    

Again, since x11vnc is dependent on X11 being already up and running I just invoke the kodi service before hand. Simple.

Finally, there is one other piece of my HTPC puzzle. I use nzbget for some things. And I let it run on my HTPC as the "htpc" user. Under the previous init systems it wasn't worth the hassle to define it as a system service. So I wrapped it in a screen script and launched it manually every time I had to reboot my HTPC (which isn't often). But there's a pretty cool feature of Runit -- user services. No more manually starting nzbget!:

#!/bin/sh
    exec 2>&1
    exec /bin/nzbget --server
    

With that run script and /home/htpc/{sv,service} I can let Runit take care of starting and stopping it. All while not having to jump through a bunch of hoops to start it as a specific user. This is something I'd love to use at work, but I'm stuck with RedHat and I'm not going to put another init system on top of an existing one (maybe).

Anyway, the point of this post was mainly to highlight Runit and Void Linux. They are a great combination for an appliance system like an HTPC. Such a system doesn't need a lot of resources, but it is better to give the actual application the majority of the resources. With Void and Runit your application gets almost all of the system resources. I'll end this post with the stats on my HTPC's currently used resources:

% free -h                                                                                                                                                   [s:127 l:385]
                  total        used        free      shared  buff/cache   available
    Mem:           7.7G        475M        2.8G         57M        4.4G        7.1G
    Swap:            0B          0B          0B
    
    % ps_mem                                                                                                                                                 [s:1 l:392]
     Private  +   Shared  =  RAM used    Program
    
     92.0 KiB +  23.5 KiB = 115.5 KiB    nanoklogd
    100.0 KiB +  26.0 KiB = 126.0 KiB    socklog
    124.0 KiB +  38.0 KiB = 162.0 KiB    uuidd
    132.0 KiB +  71.5 KiB = 203.5 KiB    kodi
    180.0 KiB +  38.5 KiB = 218.5 KiB    acpid
    176.0 KiB +  73.0 KiB = 249.0 KiB    runsvdir (2)
    192.0 KiB + 132.0 KiB = 324.0 KiB    sh (2)
    216.0 KiB + 169.0 KiB = 385.0 KiB    autox (2)
    200.0 KiB + 236.0 KiB = 436.0 KiB    xinit
    448.0 KiB + 166.0 KiB = 614.0 KiB    svlogd (5)
    448.0 KiB + 219.0 KiB = 667.0 KiB    agetty (4)
    704.0 KiB +   4.0 KiB = 708.0 KiB    runit
    740.0 KiB + 272.0 KiB =   1.0 MiB    login (2)
    932.0 KiB + 132.5 KiB =   1.0 MiB    sudo
      1.0 MiB +  90.5 KiB =   1.1 MiB    udevd
      1.4 MiB + 506.5 KiB =   1.9 MiB    runsv (19)
      1.6 MiB + 395.0 KiB =   1.9 MiB    wpa_supplicant
      2.3 MiB + 109.5 KiB =   2.4 MiB    most
      2.6 MiB + 461.5 KiB =   3.1 MiB    mandoc
      2.9 MiB + 437.5 KiB =   3.3 MiB    nmbd
      1.2 MiB +   2.7 MiB =   3.9 MiB    sshd (5)
      4.2 MiB +   4.3 MiB =   8.5 MiB    smbd (2)
      7.6 MiB +   1.3 MiB =   8.9 MiB    mosh-server (2)
     12.2 MiB + 562.5 KiB =  12.8 MiB    x11vnc
     11.1 MiB +   2.4 MiB =  13.6 MiB    zsh (6)
     14.9 MiB + 811.0 KiB =  15.7 MiB    nzbget
     29.2 MiB +   1.8 MiB =  31.0 MiB    Xorg
    411.8 MiB +   5.2 MiB = 417.0 MiB    kodi.bin
    ---------------------------------
                            531.1 MiB
    =================================
    
    % pstree                                                                                                                                                      [s:0 l:386]
    runit─┬─2*[mosh-server───zsh]
          └─runsvdir─┬─runsv─┬─socklog
                     │       └─svlogd
                     ├─4*[runsv───agetty]
                     ├─runsv───sshd─┬─sshd───sshd───zsh
                     │              └─sshd───sshd───zsh───pstree
                     ├─runsv───uuidd
                     ├─runsv───login───zsh
                     ├─runsv───smbd───smbd
                     ├─runsv───nanoklogd
                     ├─runsv─┬─svlogd
                     │       └─wpa_supplicant
                     ├─runsv─┬─mythlogin───autox───sh───xinit─┬─Xorg───{Xorg}
                     │       │                                └─sh───kodi───kodi.bin─┬─{AESink}
                     │       │                                                       ├─{ActiveAE}
                     │       │                                                       ├─{AirPlayServer}
                     │       │                                                       ├─{EventServer}
                     │       │                                                       ├─{FDEventMonitor}
                     │       │                                                       ├─23*[{LanguageInvoker}]
                     │       │                                                       ├─{PeripBusUSBUdev}
                     │       │                                                       ├─{TCPServer}
                     │       │                                                       ├─17*[{kodi.bin}]
                     │       │                                                       └─2*[{libmicrohttpd}]
                     │       └─svlogd
                     ├─runsv───login───zsh───man───most
                     ├─runsv───nmbd
                     ├─runsv───udevd
                     ├─runsv───acpid
                     ├─runsv─┬─svlogd
                     │       └─x11vnc
                     └─runsv───runsvdir───runsv─┬─nzbget───6*[{nzbget}]
                                                └─svlogd
    

Goodbye Wordpress!

posted on 2015-11-29 at 17:15 EST

For the last five years this site has been generated by Wordpress. The decision to move to Wordpress was based primarily on the ammount of spam that was being posted through the comment system I had written. Wordpress provides some great tools for fighting comment spam. But comments on weblog posts are becoming more irrelvant by the day; or rather, no one does it anymore. So I don't have need of that feature any longer.

But that's not why I have dumped Wordpress. I have dumped Wordpress because it is one giant security hole:

I could keep linking stories of its vulnerabilities all day. Suffice it to say, it is foolish to continue using Wordpress.

Given that fact, I decided to forego a dynamically generated website altogether. This site is now completely static. This site is written in nothing more than plain old HTML, CSS, and JavaScript. That used to come at a cost of maintainability. It was far easier to use a dynamic content generator for a site of any size if you wanted to be able to maintain it. Nowadays that isn't the case. There are many, many, tools for generating static websites. I even wrote one at one point (you probably shouldn't use it).

The tool I settled on using is Metalsmith. It's a very simple tool with a lot of flexibility. I won't go over it in detail here. You can read about in detail elsewhere. If you are curious about the code to generate this site, you can peruse the git repository. At the time of this writing the project is just enough to get going.

If you're using Wordpress, and want to migrate off it, then I have written a tool you might want to use -- wp-to-static. I had been wanting to do this migration since early 2015, but it took me a while to finish writing that tool (mostly due to laziness). I'm a firm believer in the 301 code. As a result, all of my old content is still available; even my old old content.

Anyway, I have been holding off writing new posts because I didn't want to add any more content to Wordpress. Now that I've moved on to this setup I will maybe write more frequently. My current goal, though, is to come up with some sort of better template/design.

Finally, I may consider adding Disqus comment system. But it's unlikely. If you have something to say about a post, you can mention @jsumners79 on Twitter or +JamesSumners on Google+.