Wednesday, May 8, 2013

Blurays, DRM and other unforgivable sins

So back in February this year I ordered some Blurays on amazon.com and imported them from the US. Even with custom fees they were way cheaper than buying them in Germany and thus I thought: "Well, I'm sure a friend of mine will be nice enough to create proper copies of the discs."
A few months later I happened to acquire a notebook with a Bluray drive, so that was out of question and I was excited for my Blurays to arrive.
Fast forward, two days ago:
I received my package with the DVD/BD combo pack of Spice and Wolf released by FUNimation in the US, and I was immediately let down when I found out that it is not possible to play back Blurays out of the box. To that day I thought the big companies had figured out that using DRM is essentially useless and only sets the hurdle a little higher. I never bothered reading about Bluray DRM before, neither had I ever really heard about it. So I started reading the Archlinux Bluray Wiki-Entry, which already helped a lot, but I wasn't able to play my legally purchased Blurays until after I ran into many errors and compiled software many, many times.
The first keyword to drop is AACS. With it goes aacskeys and libaacs. After trying to mess with aacskeys I gave up a little and used DVDFab9 to rip one of the Blurays and I was shocked how easily it decrypted the disc. By this time I already spend quite some hours on this, but I was somewhat happy that I could play the decrypted files. The next day I started reading about this again and learned about libaacs. I downloaded the sources, compiled it and with aacs_info I was able to gain the VID, I don't know how it works, but feel free to dig through the code. Now, with the VID I could use aacskeys with the included Host Certificates and Private Keys to get the VUK (see: http://en.wikipedia.org/wiki/Advanced_Access_Content_System#Decryption_process). Now once you have the VUK you don't need anything else to finally decrypt your disc, so better save it somewhere, in fact you are needed to save the VUK for libaacs and libaacskeys in the directory: ~/.cache/aacs/vuk/. This should happen automagically, and it did once I messed around long enough with it on my laptop with the Bluray drive. You can use VLC, mplayer or even mplayer2 to play back your Bluray. I took this one step further and mounted the mounted Bluray (yes mounted twice!) via sshfs on my notebook without a Bluray drive and was able to play it, too. I installed libaacs and libaacskeys and copied the files in ~/.cache/aacs/vuk/ in the same directory. The files in that directory are named after the DiscID, which is also output by aacskeys and contain the VUK.

So after messing with this for almost two days I was finally able to watch my Blurays. Once again I spent a lot of time on something that shouldn't be. The bad thing is the big companies don't even care. I made my purchase, they made money, I gained nothing. At least now I can say I really own these discs, because before I was able to decrypt them, I felt betrayed and robbed. I felt like I wasted my money on unreadable discs.

Friday, March 15, 2013

mp3server

A few weeks ago I started writing a server to re-stream an mp3-stream. It all started on IRC (like everything on the internet does):

* klaxa is listening to millie - Dreaming Forest
<Sean_McG> klaxa: you play good music -- do you stream it at all?
<klaxa> no, should i?
* Sean_McG would listen to it
<klaxa> it wouldn't be up all the time, but i guess i can set something up

So I thought of ways how to do it:

<klaxa> i have mpd running, i'd try to add some output that i grab locally via http, have something connect to my server which then encodes to mulitple formats and streams
<klaxa> the hardest part i see right now is the streaming part
<klaxa> the rest shouldn't be too hard, i could even do it with netcat
<klaxa> if i knew how robust mp3 is against starting to stream from just anywhere in the bytestream i could write my own server

Well, turns out mp3 isn't robust  against starting "just anywhere in the bytestream" AT ALL. Therefore I read the mp3 header specification and started implementing a simple program to split an mp3 stream into frames. This alone took some hours of coding, but it was totally worth it. After about a week in total I had written some naive code that could re-stream an mp3-stream to some clients. However, because of the design at the time, all the sockets were blocking and not multiplexed. One night I went to the library (I love working late in the evening) and started to rewrite my code to use select() to multiplex through the clients. Shit worked and we did some first tests which were kinda passed.
Later on Sean_McG started to contribute code, at first it was only to make the server IPv6 compatible, but later he added autotools support and provided many fixes for my poorly written code.

What motivated me to do this was the fact that most streaming software I used until now (shoutcast and mpd) are resource clogging monsters. What I wanted was an extremely lightweight application, which did nothing but data redirection at its core, and this is what evolved from it.

Today and yesterday I did some benchmarks with people from IRC (Thanks Kabaka, LordV, Phase4, Hans_Henrik and Reiuji) to see how well it scales.
Yesterday's test results were basically:
<klaxa> Sean_McG: i got some people benchmark the stream with me and it's at least able to saturate 100 mbit/s
Today I decided to test my server on a different box with more bandwidth. The test results can be boiled down to:
<klaxa> okay so what broke it was probably the filedescriptor limit of 1024
The bandwidth usage of todays test maxed out at about 40 MB/s so circa 320 Mbit/s, serving almost 1000 clients.

A bit insight on the technical details:
The server opens a port (currently hardcoded port 8080) and waits for a client to connect. This client is taken as the source for the mp3-stream (There is no authentication method or anything similar as of now). Each client that connects now will be treated as a "real" client, which wants to listen to our stream.
Now this is where it gets interesting: Whereas mpd uses a buffer for every client and uses some multi-threaded model to push out the stream over HTTP, I implemented a ringbuffer for the stream which every client can access. "Client" in this case is a struct storing the client's filedescriptor, a counter of how many bytes have already been written other data for various things and pointers to the previous and next client struct. In the main-loop select() checks whether or not a new frame from the source can be read, new clients want to connect, or existing clients can be written. In the first case we just remove the oldest frame from the ringbuffer and replace it with the new one. In the second case a new client struct is created and added at the end of the doubly linked client list. In the third case we traverse the client list and write as many frames as possible, in most cases this should be one frame. It is possible that a client lags behind and new frames got added to the ringbuffer without new frames being written to this particular client. In that case upon the next iteration in the main-loop the server will try to write all frames to the client that are needed to be up-to-date with the stream. If clients lag behind one round in the ringbuffer they will experience skips in the stream, if they lag behind two rounds in the ringbuffer they are declared dead and dropped.

My future plans entail:
  • Implement icy-metadata
  • Fork project and rewrite it to work with the most popular audio codecs.
  • Become a real low-resource alternative to icecast.