Update 2009-07-28: In the final project another solution was pointed out to me, which ended up being better in most cases, see the commands here: http://code.google.com/p/wiidiaplayer/source/browse/trunk/wiidiaserver/rtmp/flvstreamprovider/convertvideoffmpeg.sh

In my previous blog I explained the format I want the flash video in (and the reasons why), however, getting my video in that format did pose some problems, most of which I hope I have tackled by now.

The actual command for converting the video itself is not so complicated:

 /usr/bin/mencoder "$1"
        -of lavf -lavfopts format=flv
        -af resample=44100:0:1 -af channels=2 -oac mp3lame -lameopts cbr:br=128 -mc 0
        -ovc lavc -lavcopts vcodec=flv:vbitrate=2500:autoaspect:vratetol=1000:keyint=1 -ofps $FPS
        -o "$2"

On the first line, it shows the command and inputfile, the second line defines handles the output container format; The third line handles all audio (44.1kHz (the next two numbers define the conversion type), use two channels, convert with lame to a constant bitrate of 128kbps. The final option disallows any shifting between video and audio frames.

The forth line defines the video options (codec flv, bitrate (max) 2500kbps (max 1000kbps tolerance), per 1 frame 1 keyframe (ie: use only keyframes), and the required framerate), the final line sets the output filename.

Now, before we continue, it’s necessary to note that I have installed 3 different versions of mplayer in the last week, and each version needed a slightly different command. Currently I’m using MPlayer SVN-r24130, as provided by Gentoo ebuild media-video/mplayer-1.0.20070824, if you have a different version, you might need to adjust some parameters. I’m using some unversioned version of ffmpeg, which is provided by media-video/ffmpeg-0.4.9_p20070330; however my experiences with ffmpeg is that it is much more stable and compatible between versions, so any recent version should probably do.

The problem with the command above is that it only works on certain video files, and obviously we would like to convert any video file that mplayer can play. It seems that there are several bugs in mplayer/mencoder that disallow this (try the command above on your average avi file and mencoder will crash with a segfault), so we have to take a work-around. We will convert the video in two steps: first convert the source to an intermediary format, next convert this format with the command above to the final result. To avoid quality loss and extra CPU load, the intermediary format is the raw, uncompressed video format. This format takes about 4.5MB per second, so we don’t want to store it on disk, but pipe it directly from one mencoder into the next. To accomplish this we need to redirect any other output from mencoder to somewhere else (so that only the audio/video data reaches the next mencoder, not the status information). Furthermore, we need to keep the first mencoder from wanting to seek in the file it writes; When writing avi-files, mencoder updates the avi header every couple of hundred megabytes, by seeking to the beginning of the file and writing the header – this obviously cannot be done with a pipe. The -noodml tag fixes this problem, but another problem persists: the second mencoder (behind the pipe) expects the maximum avi size to be 4GB (which is reached in 15 minutes), so after 15 minutes encoding stops. The only solution I’ve found to this is not using the avi container, but the asf container (the mpeg container and other containers have other problems, only asf seems to work).

So the encoding part of the file looks like this:

/usr/bin/mencoder "$1"
                -of lavf -lavfopts format=asf
                -oac pcm -af resample=44100:0:1
                -ovc raw -vf scale=400:224 -ofps $FPS
                -o /dev/fd/3 3>&1 >/var/log/mencoder/1 2>&1 |
        /usr/bin/mencoder /dev/stdin
                -of lavf -lavfopts format=flv
                -af resample=44100:0:1 -af channels=2 -oac mp3lame -lameopts cbr:br=128 -mc 0
                -ovc lavc -lavcopts vcodec=flv:vbitrate=2500:autoaspect:vratetol=1000:keyint=1 -ofps $FPS
                -o "$2" > /var/log/mencoder/2 2>&1

The first mencoder sets the correct resoltion, framerate and audio options, and outputs it to a filedescriptor called /dev/fd/3. The little filedescriptor magic afterwards redirects this filedescriptor 3 to stdout, while directing stdout en stderr to a logfile. Both the input and the output are files. For the wiidiaplayer all sources are (for now) files, and I render to a temporary directory as well, to improve caching and enable seeking in the video.

To this we add some extra padding that lowers the priority of the encoders (to avoid the computer locking up when coding), and some code that makes sure all child processes end when the parent process is killed, and that’s the full conversion script:

#!/bin/bash

function getmencoderchildids {
    ps --ppid $$ | awk '$4=="mencoder" {print $1}'
}

function stop_encoding {
 echo "now stopping"
 PIDS="$(getmencoderchildids)"
 for mypid in $PIDS; do
 	kill $mypid > /dev/null 2&>1;
 done
 sleep 2;
 PIDS="$(getmencoderchildids)"
 for mypid in $PIDS; do
 	kill -9 $mypid > /dev/null 2&>1;
 done
}

trap stop_encoding TERM;
trap stop_encoding EXIT;

FPS=18

nice -n 2 /usr/bin/mencoder "$1"
	        -of lavf -lavfopts format=asf
                -oac pcm -af resample=44100:0:1
                -ovc raw -vf scale=400:224 -ofps $FPS
                -o /dev/fd/3 3>&1 >/var/log/mencoder/1 2>&1 |
        nice -n 1 /usr/bin/mencoder /dev/stdin
        		-of lavf -lavfopts format=flv
        		-af resample=44100:0:1 -af channels=2 -oac mp3lame -lameopts cbr:br=128 -mc 0
        		-ovc lavc -lavcopts vcodec=flv:vbitrate=2500:autoaspect:vratetol=1000:keyint=1 -ofps $FPS
        		-o "$2" > /var/log/mencoder/2 2>&1 &
while [ -n "$(getmencoderchildids)" ]; do
 echo "$(getmencoderchildids)"
 sleep 1;
done

The Wiidiaserver

August 5, 2007

The Wiidiaserver is the servercomponent of the Wiidiaplayer. It has a number of jobs:

  • Serve over http the html page and the swf file that contain the clientside Wiidiaplayer (and the accompanying images in the near future)
  • Serve over http dynamic requests from the player on directory structure (this may change in the future to RTMP requests)
  • Stream the media file to the clientside Wiidiaplayer
  • (in the future) serve by http pictures for a slideshow.

The streaming of the media file happens over a RTMP connection. RTMP has only been reverse-engineered last year, and when I started on the Wiidiaplayer, only 4 implementations existed that I knew of. There is of course the official Adobe implementation, which will cost you at least a couple of thousand dollars to use, just as the Wowza Media Server. Then there is the Red5 server, which seemed like a lot of overkill, and since they hinted at their homepage that it would not be able to stream flash video yet, it didn’t seem worth the trouble to try. Then there was a implementation in HaXe. This last server actually had me streaming my first video within 5 minutes, however, although the idea behind HaXe is interesting, the language seemed too young. Furthermore, I didn’t like the idea of having to ask people to install apache and the Neko VM. Since the whole HaXe implementation was only a couple of hundred lines, I figured I could port it to twisted without too much trouble.

The Wiidiaserver has grown by now beyond the port of the HaXe code. Although the initial idea was to create a class that could be used in general RTMP projects in twisted, I gave up on it. For one, I don’t have enough experience with twisted to know what one would expect from such a class, but also, trying to develop a too general implementation only leads to more code, more bugs, more problems. Right now the RTMPy project is developing just such an implementation, in case someone is looking for it, and TAPE is a server developed on it (although, at the time of writing it doesn’t do video streaming yet)

The Wiidiaserver component is developing in a way specific to the Wiidiaplayer project. The FLV files that it streams coming from flv providers. These are classes that will get de FLV data from different sources. The simplest is a class that just serves an flv file that’s on disk. The more complicated ones create the flv file on the fly, and in the near future I’ll implement classes that will get the flv data from youtube, or a video4linux source. The server has extra functionality to handle such files. For instance, when seeking, it’s possible that the requested data is not available yet. It will then do repeated RTMP calls to the Wiidiaplayer to inform the client of the delay and how long it will take.

As with all: the server is in development, and might not be stable. The code can be found at http://code.google.com/p/wiidiaplayer/ . The server needs root-permissions to run, and will retain these while running (this will change in the near future as well). This is not advisable on any system, especially those connected to the outside world. Doing so is therefore obviously at own risk.

To run the server, you need python, twisted and twisted-web; converting makes use of mplayer(mencoder) for video and ffmpeg for audio.