24 September, 2009

Mplayer Swiss Army Knife

Mplayer adopts the 'can do' attitude as a media player. Generally, this utility will play most anything, employs most standard codecs and many that you'll likely never need. If you have a video file, likely you can play it with Mplayer...including raw data.

A recent program I was working on grabbed raw LVDS data streams from a Flir infared camera. First task, confirm the video output is reasonable. Cue investigation of how to play raw data streams without requiring encoding. Mplayer met this need quite nicely. For fun, I thought I'd post some of our findings, hopefully you can find them useful.

Lets start with generating our own raw video stream:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <fcntl.h>
#include <assert.h>

int main()
{
static const int NumFrames = 10;
static const int Width = 640;
static const int Height = 480;

printf("(%s:%d) main process initializing\n",__FILE__,__LINE__);
std::string fileName="/tmp/video.raw";
printf("(%s:%d) fileName='%s'\n",__FILE__,__LINE__,fileName.c_str());
int fp=open(fileName.c_str(),O_CREAT|O_WRONLY|O_TRUNC);

for (int i=0; i<NumFrames; ++i)
{
unsigned char frame[Width*Height];

// generate frame; white box, framed with black lines
memset (frame,255,Width*Height);
for(int c=0; c<Width; ++c) frame[c+Width*0]=0;
for(int c=0; c<Width; ++c) frame[c+Width*(Height-1)]=0;
for(int h=0; h<Height; ++h) frame[0+Width*h]=0;
for(int h=0; h<Height; ++h) frame[0+Width*h+(Width-1)]=0;

// write frame contents to file
for(int k=0; k<Width*Height; ++k)
{
void* tmp=(void*)(frame+k);
int ret=write(fp,tmp,sizeof(unsigned char));
assert(ret==sizeof(unsigned char));
}
}

close(fp);

printf("(%s:%d) main process terminating\n",__FILE__,__LINE__);
return EXIT_SUCCESS;
}


The above code simply generates a sequence of 10 640x480 8-bit grayscale frames, the content of each frame a white background framed with a black one-pixel wide border.

You can play the video contents of this file by issuing the following Mplayer command:

$ mplayer -demuxer rawvideo -rawvideo w=640:h=480:y8:size=307200:fps=1 /tmp/video.raw

A few things worth mentioning; first, since this is a raw video the frame size must be specified (ie. w=640:h=480), the frame rate (fps=1) must be specified as well or it will default to 24fps, giving us little time to review the contents of the video. Lastly, the size=307200 (640*480) is optional at for this format, but will become relevant in later examples. By default, the size will be defined by the width*height, so for this example you could simply not specify the value and all would still be well.

That was fun, but let's give it a little more snap. Suppose you wanted to add some frame metadata following each frame. We'll modify our above example a bit, each frame will be followed by a fixed 28-byte character string identifying the frame number.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <fcntl.h>
#include <assert.h>

int main()
{
static const int NumFrames = 10;
static const int Width = 640;
static const int Height = 480;

printf("(%s:%d) main process initializing\n",__FILE__,__LINE__);
std::string fileName="/tmp/video.raw";
printf("(%s:%d) fileName='%s'\n",__FILE__,__LINE__,fileName.c_str());
int fp=open(fileName.c_str(),O_CREAT|O_WRONLY|O_TRUNC);

for (int i=0; i<NumFrames; ++i)
{
unsigned char frame[Width*Height];

// generate frame; white box, framed with black lines
memset (frame,255,Width*Height);
for(int c=0; c<Width; ++c) frame[c+Width*0]=0;
for(int c=0; c<Width; ++c) frame[c+Width*(Height-1)]=0;
for(int h=0; h<Height; ++h) frame[0+Width*h]=0;
for(int h=0; h<Height; ++h) frame[0+Width*h+(Width-1)]=0;

// write frame contents to file
for(int k=0; k<Width*Height; ++k)
{
void* tmp=(void*)(frame+k);
int ret=write(fp,tmp,sizeof(unsigned char));
assert(ret==sizeof(unsigned char));
}
//write some metadata
char metaData[28];
sprintf(metaData,"frame%04d",i);
assert(write(fp,metaData,sizeof(metaData))==sizeof(metaData));
}

close(fp);

printf("(%s:%d) main process terminating\n",__FILE__,__LINE__);
return EXIT_SUCCESS;
}
Now the frame size becomes a required argument, since the frame size must now account for the metadata, otherwise the metadata would be interpreted as video and you'd have a mess. You can play the above generated video by issuing the command as follows:

$ mplayer -demuxer rawvideo -rawvideo w=640:h=480:y8:size=307228:fps=1 /tmp/video.raw

Ok, now we've learned how to play video with metadata. Our last trick will be playing video with a initial video header.

Let's start by adding some video metadata at the beginning of the video, a 200-byte string.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <fcntl.h>
#include <assert.h>

int main()
{
static const int NumFrames = 10;
static const int Width = 640;
static const int Height = 480;

printf("(%s:%d) main process initializing\n",__FILE__,__LINE__);
std::string fileName="/tmp/video.raw";
printf("(%s:%d) fileName='%s'\n",__FILE__,__LINE__,fileName.c_str());
int fp=open(fileName.c_str(),O_CREAT|O_WRONLY|O_TRUNC);

char videoMetaData[200];
sprintf(videoMetaData,"some cool video");
assert(write(fp,videoMetaData,sizeof(videoMetaData))==sizeof(videoMetaData));

for (int i=0; i<NumFrames; ++i)
{
unsigned char frame[Width*Height];

// generate frame; white box, framed with black lines
memset (frame,255,Width*Height);
for(int c=0; c<Width; ++c) frame[c+Width*0]=0;
for(int c=0; c<Width; ++c) frame[c+Width*(Height-1)]=0;
for(int h=0; h<Height; ++h) frame[0+Width*h]=0;
for(int h=0; h<Height; ++h) frame[0+Width*h+(Width-1)]=0;

// write frame contents to file
for(int k=0; k<Width*Height; ++k)
{
void* tmp=(void*)(frame+k);
int ret=write(fp,tmp,sizeof(unsigned char));
assert(ret==sizeof(unsigned char));
}
//write some metadata
char metaData[28];
sprintf(metaData,"frame%04d",i);
assert(write(fp,metaData,sizeof(metaData))==sizeof(metaData));
}

close(fp);

printf("(%s:%d) main process terminating\n",__FILE__,__LINE__);
return EXIT_SUCCESS;
}


You play this form of video by specifying a start byte offset of 200 and you're set.

$ mplayer -demuxer rawvideo -rawvideo w=640:h=480:y8:size=307228:fps=1 -sb 200 /tmp/video.raw

20 September, 2009

Streaming with FFMpeg

For work I've been playing with VLC to support some streaming experiments for the program I'm working on. In the process of installing VLC it became obvious the vast number of package dependencies it's reliant on.

So, for fun, I played with streaming via FFMpeg at home. Below are my findings.

Assuming you have FFMpeg already installed on your machine, there are 3 steps:

1) start the ffserver application with appropriate configuration file.
2) start ffmpeg to redirect the input to the stream
3) view with video client

FFServer Setup
The ffserver utility is installed as part of the FFMpeg package, no need to install it explicitly. You do however need to author an appropriate configuration file. Below is the configuration file I've used as a starting point.

$ cat ffserver.conf
Port 8090
# bind to all IPs aliased or not
BindAddress 0.0.0.0
# max number of simultaneous clients
MaxClients 10
# max bandwidth per-client (kb/s)
MaxBandwidth 10000
# Suppress that if you want to launch ffserver as a daemon.
NoDaemon


File /tmp/feed1.ffm
FileMaxSize 5M


# FLV output - good for streaming

# the source feed
Feed feed1.ffm
# the output stream format - FLV = FLash Video
Format flv
VideoCodec flv
# this must match the ffmpeg -r argument
VideoFrameRate 10
# generally leave this is a large number
VideoBufferSize 80000
# another quality tweak
VideoBitRate 200
# quality ranges - 1-31 (1 = best, 31 = worst)
VideoQMin 30
VideoQMax 31
VideoSize 352x288
# this sets how many seconds in past to start
PreRoll 0
# wecams don't have audio
Noaudio


# ASF output - for windows media player

# the source feed
Feed feed1.ffm
# the output stream format - ASF
Format asf
VideoCodec msmpeg4
# this must match the ffmpeg -r argument
VideoFrameRate 10
# generally leave this is a large number
VideoBufferSize 80000
# another quality tweak
VideoBitRate 200
# quality ranges - 1-31 (1 = best, 31 = worst)
VideoQMin 30
VideoQMax 31
VideoSize 352x288
# this sets how many seconds in past to start
PreRoll 0
# wecams don't have audio
Noaudio

$


Next, you need to run ffserver as follows:

$ ffserver -f ffserver.conf


FFMpeg Setup
The ffmpeg utility feeds the server. The following example uses a quick cam as the source, redirecting through the video server to be viewed by the clients. Worth noting, the ffserver configuration file specifies a frame rate, in this case 10fps, which must match the command line specification of ffmpeg.


$ ffmpeg -r 10 -s 352x288 -f video4linux -i /dev/video0 http://localhost:8090/feed1.ffm



Video Client Setup
Lastly, test the feed using a video client. For our example, we'll use mplayer as follows:

$ mplayer http://localhost:8090/test.asf

Play nice.

09 September, 2009

Redirecting Sound with rDesktop

I've been working with VirtualBox for the past few months. I've grown to love it, and consider virtualization as one of the most compelling technologies we've seen in the past decade.

Running VMs headless has quite a few advantages. For instance, we're bundling X VMs on a server and serving out to less capable processors via RDP clients. The latest challenge was redirecting sound to the client processor. Like most things, pretty simple to do...once you've done it.

Thought it was worth a quick note:

Assuming you've started a VM with remote display enabled, such as:

$ VBoxHeadless -startvm WinXp -vrdp on -vrdpport 8000


To establish an RDP connection (assuming host IP of 192.168.1.27):


$ rdesktop 192.168.1.27:8000



$ rdesktop 192.168.1.27:8000 -r sound:local:oss

08 September, 2009

Older Debian Releases

Perhaps I'm living up to my name, the fat slow kid, but if I'm honest I have a hell of a time locating previous releases of Debian.

I search and I search and I search some more; after finally finding them I thought it was worth a quick post.

http://www.debian.org/releases/

For 'etch', follow the links to:
http://www.debian.org/releases/etch/debian-installer/

With the common redirects pressing you toward the latest-n-greatest sometimes its difficult to get the older version you're after.

Cheers.