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

No comments: