News Archive (1999-2012) | 2013-current at LinuxGizmos | Current Tech News Portal |    About   

Roku Labs HD1000 Digital Media Player SDK review

Mar 1, 2004 — by LinuxDevices Staff — from the LinuxDevices Archive — 11 views

These days there are numerous embedded devices that use Linux as their foundation. From the manufacturer's viewpoint, this makes economic sence. Unless the quantities are quite small, any time a manufacturer can trade a one time engineering cost (such as software development) for a bill of material cost (such as an OS License) they'll do it. Even if the OS cost is only one dollar, when the manufacturer sells the millionth units, that's a million dollars of extra revenue. You can buy a lot of Linux software development for a million dollars.

However, the GPL puts the manufacturer in an interesting position. He must make all of the GPL'd code he modified available to anyone who asks in one form or another. Some manufacturers are simply ignoring this requirement. Shame on them. Others are making the code available, but not supporting it in any way. While they are technically following the letter of the GPL, and that's good, they are not realizing the full benefit that they could be gaining by actively supporting the code. The smartest manufacturers, such as Sharp, Roku, etc. are not only making their GPL'd code available, they are actively trying to build a developer base for their product, thereby increasing not only the potential user base, but the breadth of applications available for their product. At LinuxDevices.com, we applaud those manufacturers who take the time and energy to not only build great products using Linux and all the tools available for it, but also build software development kits for those products so that hobbyist and professional developers can use those manufactured products in new an unique ways.

This article is the first of an ongoing series of articles that examines those software development kits that hardware manufacturers make available for their products. Specifically, this article will look at Roku's SDK for their HD1000 Digital Media Player. LinuxDevices.com has already reviewed the HD1000 at the application level, so this article will focus specifically on the SDK, which was announced along with a coding contest in January, 2004.

The Hardware

The HD1000 is billed as a Digital Media Player. Basically, you can hook the HD1000 between your fancy new HDTV and the audio/video source and it will allow you to display digital media through your HDTV and the sound system it's hooked into. This lets you play all those MP3s and MPG movies and JPGs you have floating around your network on you HDTV with the little remote that comes with the unit. Pretty cool. Also, in passthru mode, it will act as a screensaver for your HDTV — any image that sits on the screen for too long (a paused DVD perhaps), will be replaced by a screen saver of your choosing — or creation!!

However, if you look at the back of the unit, you'll see that the posibilities are endless.


Roku, back view
(Click for larger view)

From left to right you have:

  • RCA AUDIO: Right In
  • RCA AUDIO: Left In
  • RCA AUDIO: Right Out
  • RCA AUDIO: Left Out
  • RS232
  • S-Video In
  • Composite Y In
  • Composite Pb In
  • Composite Pr In
  • S-Video Out
  • Composite Y Out
  • Composite Pb Out
  • Composite Pr Out
  • VGA Out
  • USB
  • S/PDIF Digital Audio Out
  • Ethernet
  • Power (Not shown)

The front of the Unit is also impressive:


Roku, front view
(Click for larger view)

Again from left to right:

  • Power. Actually, this just controls the power to the video output. The unit stays on all the time. Press it long enough and the unit will reboot.
  • Complact Flash slot
  • SD/MMC Slot
  • Memory Stick slot
  • SmartMedia slot
  • Busy light
  • Menu button
  • Exit button
  • Select button
  • 4 way directional button.

The unit also comes with a remote that contains the following buttons:

  • Menu
  • Power
  • Exit
  • 4 way directional
  • Select
  • |
  • >||
  • >>|
  • rotate
  • info
  • zoom in/out

Internally, the Roku has:

  • MIPS 300mhz CPU
  • 16 MB flash (mostly used)
  • 64 MB RAM
  • 2D/3D graphics engine
  • Hardware MPEG2 Decode engine

For more details, about the Roku itself, see our complete device profile.

The Possibilities

All in all, the Roku is an impressive hardware package with some interesting expansion possibilities. The various flash cards on the front are there so that you can view your camera's pictures directly from the card. However a developer can use these slots to greatly expand the amout of “disk” space and “memory” that is available on the machine. This is especially important when you want to compile large and complex applications because currently the only way to compile C/C++ applications is directly on the HD1000. More about that later.

A quick look at the back reveals both an ethernet card and a USB slot. There are lots of things you can attach to the USB slot, including various network devices (such as ethernet adapters, 802.11 adapters, etc). Add a network device and the HD1000 becomes a simple, programmable router. Add two, and it becomes a complex router with LAN, WAN and DMZ.

Applications can be built and “productized” on a flash card so that all you have to do is plug in the card and the HD1000 will start running the application. This is probably the easiest way to build a completely fool proof presentation kiosk that there is.

Developing Applications

Roku describes two different ways you might want to program the HD1000. The simplest way is to write bash scripts to automate the built-in software, such as the MD3 player, JPG viewer, etc. The other way is to write C++ programs using Roku's proprietary Cascade windowing library. You don't have the choice of X Windows since they don't provide the libraries for you. You could try to port X, but without the source code to the video drivers it would probably be a difficult task. Currently, Roku does not support any scripting language other than the bash shell. Although they may support Java, Perl or Python at some later date.

Any executable with the .roku extension will show up on the menuing system when the media that it lives on is mounted. This gives you an easy way for users to start your applications — just pop in a flash card, navigate to it and select the application and it runs. I've also been told that an executable named “.autoexec.roku” will automatically execute when it is installed.

A “Simple” C++/Cascade Program

I wanted to write a simple C++ program to try out the Cascade library. The library itself comes with a few example programs, but it's always nice to build a little app yourself to get used to the flavor of the API. I figured it should be easy enough to teach the HD1000 how to download a little .PNG file from the Internet and display it on the screen.

What started out as a little one hour project turned into a whole day marathon that I never really finished becuase I ran out of time for the “download the .PNG from the Internet” part. I thought it would be easy because the Cascade library contains a function named CascadeBitmap::CreateFromBitmapFile() that the documentation says will load a .PNG bitmap from a file name and get it ready to paint to the screen. The trouble is there's a bug in the implementation that prevents me from giving it the type of PNG I want to give it. Here's where I lament the fact that the Cascade library is proprietary and the source is not available. It turns out that the fix would be just a few lines of code — but I can't fix it. So I have to spend a whole bunch of time figuring out the libpng library functions that I have to call directly. Oh well.

Below is an annotated listing of the finished program that displays a PNG file on the HD1000 screen..

// showpng.cpp - show a png file
//
// Copyright (c) 2004, LinuxWithin.com, LLC. All rights reserved
// This program is distributed under the terms of the GPL
//
///////////////////////////////////////////////////////////////////////////////
The libpng.so shared library file is included in the HD1000. However, you'll need to get the png.h file from the source distribution at http://www.libpng.org.
#include cascade/Cascade.h>
#include stdio.h>
#include string.h>
#include stdlib.h>
#include unistd.h>
#include sys/types.h>
#include fcntl.h>
#include png.h>
The colors are created in typical RGB fashion. Strangly, the libpng header file does not contain the PNG_HEADER in any form whatsoever.
///////////////////////////////////////////////////////////////////////////////
// module ShowPNG #defines
#define PNG_HEADER 8 // PNG header size

#define COLOR_BLACK CascadeColor(0, 0, 0)
#define COLOR_GREEN CascadeColor(0, 0xff, 0)

#define COLOR_BACKGROUND COLOR_BLACK
#define COLOR_TEXT COLOR_GREEN
This class is only required because the Cascade library function CascadeBitmap::CreateFromBitmapFile() does not work for all .PNG files. This class is the container for the shared memory region that will hold the raw bits to be painted on the screen.

///////////////////////////////////////////////////////////////////////////////
// class ShowPNGPngSharedMemZone
class ShowPNGPngSharedMemZone : public CascadeSharedMemZone {
public:
void Load( char *filename );
public:
u32 Width;
u32 Height;
bool IsLoaded;
};
The Load() function goes through the somewhat tedious process of loading the .PNG file from the disk, and drawing it out to a shared memory buffer so that the Cascade library can paint it to the screen.

As you can see, this single function is the bulk of the program. Without it (had the CreateBitmapFromFile() function worked), the program would have been a lot smaller.

void
ShowPNGPngSharedMemZone::Load( char * fn ){

png_byte header[PNG_HEADER];
png_structp pngp;
png_infop infop, endp;
png_bytep *rowp;

IsLoaded = false;

//
// Open the file
//
FILE *fp = fopen( fn, "r");
if (!fp){
fprintf(stderr,"Can't open %sn",fn);
return;
}

//
// Read the header & verify it's a png file
//
fread(header, 1, PNG_HEADER, fp);
if (png_sig_cmp(header, 0, PNG_HEADER)){
fprintf(stderr,"%s is not a png.n",fn);
fclose(fp);
return;
}

//
// Setup the png library data structures
//
if (!(pngp=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL))){
fprintf(stderr,"Couldn't allocate pngp!n");
fclose(fp);
return;
}

if (!(infop=png_create_info_struct(pngp))){
fprintf(stderr,"Couldn't allocate infop!n");
fclose(fp);
return;
}

if (!(endp=png_create_info_struct(pngp))){
fprintf(stderr,"Couldn't allocate endp!n");
fclose(fp);
return;
}

//
// Setup the jump back here on png library internal error
//
if (setjmp(png_jmpbuf(pngp))) {
png_destroy_read_struct(&pngp, &infop, &endp);
fclose(fp);
return;
}

//
// More png lib setup
//
png_init_io(pngp, fp); // Tell the library about the fp
png_set_sig_bytes(pngp, PNG_HEADER); // Tell the lib we have the header

//
// Make png lib do most of the bit twiddling when it loads the picture
//
int png_transforms =
PNG_TRANSFORM_EXPAND |
PNG_TRANSFORM_STRIP_ALPHA |
PNG_TRANSFORM_BGR |
0;

//
// Finally, we get to actually load the picture
//
png_read_png(
pngp,
infop,
PNG_TRANSFORM_EXPAND | // Expand colors to 8 bits
PNG_TRANSFORM_STRIP_ALPHA | // strip the transpaency
PNG_TRANSFORM_BGR | // Swap the colors around
0,
NULL
);

//
// Get the rows
//
rowp = png_get_rows(pngp, infop);

//
// Grab the info necessary to paint the picture
//
int color_depth;
int color_type;
int interlace_type;
int compression_type;
int filter_method;
png_get_IHDR(
pngp,
infop,
&this->Width,
&this->Height,
&color_depth,
&color_type,
&interlace_type,
&compression_type,
&filter_method
);

#if 0
printf(
"Image info: width: %d height: %d depth: %d ctype=%xn",
this->Width,
this->Height,
color_depth,
color_type
);
#endif

//
// Ok, now that we have the info, let's paint the pic in memory
//
this->Open(
"showpng",
this->Width*this->Height*4, // 4 bytes/color (RGBA)
true
);

unsigned char *d = (unsigned char *) this->MapLock();
for(int i=0; ithis->Height; i++){
unsigned char *s = (unsigned char *) rowp[i];
for(int j=0;jthis->Width; j++){
*d++ = *s++; // Red
*d++ = *s++; // Green
*d++ = *s++; // Blue
*d++ = 0xff; // Alpha
}
}
this->Unlock();
png_destroy_read_struct(&pngp, &infop, &endp);
fclose(fp);
IsLoaded = true;
return;
}
This is the “Window” class for the Cascade library. Mostly it's a way to get to the OnPaint function which tells the application how to paint itself to the screen.
///////////////////////////////////////////////////////////////////////////////
// class ShowPNGWindow
class ShowPNGWindow : public CascadeWindow {
public:
ShowPNGWindow(char *filename);
protected:
virtual void OnPaint(CascadeBitmap & winBitmap);
virtual void OnVanish();
protected:
ShowPNGPngSharedMemZone picture;
};
Since the application just shows one picture at a time on the screen, we load it in the window's constructor. The SetRectAbsolute() function is necessary here or the program won't work at all.
ShowPNGWindow::ShowPNGWindow( char *filename ) {
CascadeScreen screen;
SetRectAbsolute(screen.GetRect());
picture.Load( filename );
}
The OnPaint() function shows how to actually output some graphics to the screen. C++ makes this look almost easy. The steps are:

  1. Fill the screen with the background color
  2. Decide if we even have a picture (picture.IsLoaded).
  3. Blit() the picture to the screen
void
ShowPNGWindow::OnPaint(CascadeBitmap &winBitmap) {
CascadeRect r = GetRectAbsolute();

//
// Paint the background
//
winBitmap.FillRect( r, COLOR_BACKGROUND );

//
// Paint the graphic
//
if ( picture.IsLoaded ){
winBitmap.Blit(
CascadePoint(
r.w/2-(r.w-picture.Width)/2,
r.h/2-(r.h-picture.Height)/2
),
picture,
0,
picture.Width,
picture.Height,
32,
CascadeRect(0,0,picture.Width,picture.Height)
);
}
}
This is here so that the the applicatoin terminates when the window vanishes.
void
ShowPNGWindow::OnVanish() {
CascadeApp::GetApp()->Terminate(0);
}
The App class is where the application spends most of its time. Hidden within is the main event loop. As application events happen, the appropriate OnXXX() function is called.

Here we have a null OnAppInit() that parses the command line arguments and a OnKeyDown() to detect the Exit key.

///////////////////////////////////////////////////////////////////////////////
// class ShowPNGApp
class ShowPNGApp : public CascadeApp {
public:
ShowPNGApp() { }
virtual ~ShowPNGApp() { }
protected:
virtual void OnAppInit();
virtual void OnKeyDown(u32 nKey);
private:
ShowPNGWindow *Window;
};
The Exit key makes the window vanish, which in-turn makes the application terminate.
void
ShowPNGApp::OnKeyDown( u32 nKey ){
if (CK_EXIT == nKey) Window->Vanish();
}
This code lets the command line user tell the program which picture to paint on the screen. The code is a bit buggy — there's a seg fault in here somewhere.
void
ShowPNGApp::OnAppInit() {
int argc = GetArgc();
char *file = NULL;
char **argv = (char **) GetArgv();
if (argc){
argc--; argv++; // Consume the command
while(--argc){
char *arg = *argv++;
if (!strncmp("-f", arg, 2)){
file = *argv++;
} else {
fprintf(stderr,"Unknown option: %sn", arg);
Terminate(1);
return;
}
}
if (file){
Window = new ShowPNGWindow( file );
Window->Materialize();
Window->SetFocus();
} else {
fprintf(stderr,"You must specify a file namen");
Terminate(1);
return;
}
}
}
The main() function is the picture of simplicity. Instantiate the app class and run it.
///////////////////////////////////////////////////////////////////////////////
// let 'er rip
int main(int argc, const char ** argv) {
ShowPNGApp app;
return app.Run(argc, argv);
}

Developing C++ Applications for the HD1000

The way you develop applications for the HD1000 is quite interesting. Instead of using a cross compiler on your own Linux or Windows machine, Roku has you telnet over to the HD1000 and compile your applications there. The nice part about this is you can get started with nothing but a PC. Heck, you could use a serial terminal to do your development on the HD1000. The only problem is the environment of the HD1000 isn't really adequate for software development. Sure you have VIM on the box so you can edit your program, but what you really need is a lot more RAM and a much faster processor. Roku has you get around the RAM problem by setting up a flash card as swap space. While this does mostly work, it does slow the already slow compiler down quite a bit, so the compile time for even a simple program is about 20 seconds. This doesn't sound like much, but when you develop incrementally like I do it can be a serious drawback.

The other problem is it only mostly works. I was able to compile several open source packages (thttpd, libpng, zlib, etc), but I was unable to compile the one big one I tried — ImageMagick. The documentation says you need a 64mb swap space, but I was only able to fit a 58mb swap space on my 64mb card — so that could have been my problem, but I doubt it.

Anyway, for the things you can compile, the process is quite simple. The best way for me was to share a directory on my Windows box out so that the Roku automatically mounted it. Then to put all of the development software and my source code on that share. That way, I was able to use my winodows editor to edit the source code to the program and the compiler on the HD1000 to compile. All in all it works pretty well — except for the long compile time.

These are the scripts I use to enable and disable the swap space on the Roku:

StartSwap.roku StopSwap.roku
#!/bin/sh
SWAPDIR=`dirname $0`
SWAPFILE=$SWAPDIR/swapfile

# Remount the flash RW
DEV=`mount | grep $SWAPDIR | awk '{print $1}'`
MNT=`mount | grep $SWAPDIR | awk '{print $3}'`
mount -o remount,rw $DEV $MNT

# Create the swap file
if [ ! -r $SWAPFILE ]; then
dd if=/dev/zero of=$SWAPFILE bs=1024 count=60000
mkswap $SWAPFILE
fi
swapon $SWAPFILE

#!/bin/sh
SWAPDIR=`dirname $0`
SWAPFILE=$SWAPDIR/swapfile

swapoff $SWAPFILE

You also need to mount the rokudev.cramfs file on the HD1000's file system so you'll have access to the compiler and bintools. Roku's documentation shows how to put this on your flash drive. I think it works better to just put this on your SMB share with your source code. I used Roku's mount/unmount scripts as a basis for the following scripts to mount and unmount the Roku's development software. Put these scripts in the root of your SMB share along with the rokudev.cramfs file and you'll have access to the compiler.

MountSDK.roku UmountSDK.roku
#!/bin/sh
SDKDIR=`dirname $0`
LOOP=0
CRAMFS=rokudev.cramfs
MOUNTPOINT=/usr/share/rokudev
# Mount the SDK
losetup /dev/loop/$LOOP $SDKDIR/$CRAMFS
mount /dev/loop/$LOOP $MOUNTPOINT -o ro

#!/bin/sh
SDKDIR=`dirname $0`
. $SDKDIR/config

# UMount the SDK
umount /dev/loop/$LOOP

Scripting HD1000 applications with ECP

Writing simple applications for the HD1000 is relatively easy using the Linux BASH shell and “ecp” program within the HD1000. The ecp program allows the script writer to send various commands to Cascade based applications. Roku has a nice 15 page document that details the ins and the outs of building an ecp script, so I won't try to duplicate that here. Instead, I'll provide you with a simple script that you can put in the root directory of your various flash cards that will cause the HD1000 to automatically generate a slide show as soon as you plug in the card. To run, the script must be named .autoexec.roku and must be executable.

.autoexec.roku
#!/bin/sh
######################################################################
# .autoexec.roku: This program automatically displays any pictures
# that happen to be on the card.
#
# Note: it sure would be nice if there was a little app that allowed
# me to put an error message on the screen.
######################################################################

LOG="/tmp/autoexec.log"
DIR=`dirname $0` # Find this script's dir

PATH=$PATH:/usr/local/bin # Use short app names

echo "-----------------------------" >> $LOG
echo `date` >> $LOG
echo "photo -p starting..." >> $LOG

photo -p 2>&1 >> $LOG & # Launch photo app
# In playlist mode (-p),
# the photo app will
# simply wait for ecp cmds.

sleep 2 # Give app time to startup

ERR=`ecp photoApp IMAGEDIR "$DIR"` # Tell it the dir
echo "ecp photoApp IMAGEDIR $DIR returned: $ERR" >> $LOG

if [ "$ERR" = "photoApp: ok" ]; then
ERR=`ecp photoApp SLIDESHOW "$DIR"` # Start showing apps
echo "ecp photoApp SLIDESHOW $DIR returned: $ERR" >> $LOG
else
echo "failed" >> $LOG # User never sees this
ecp photoApp QUIT
fi
echo "exiting" >> $LOG

# vim: shiftwidth=4 tabstop=4

In Conclusion

The Cascade library is really the only way to build a robust application that uses the screen for the HD1000. The library is young — the API is not yet completely solidified. Code that you write today will probably compile tomorrow, but there may be small differences. The documentation for the library is good but not yet great. And there are still a few kinks to work out in the code. All that said, the library looks very promising. It's well thought out and with a few more revisions and a lot more sample code will be solid enough for good C++ programmers to write some interesting code quickly. The HD1000 is a capable device that not only delivers what it promises to the consumer, but enables C++ programmers to develop very interesting applications that can be displayed directly on your HDTV.


About the author: — John Lombardo is the author of Embedded Linux, the first book published on the subject. He's been working with Linux since the “0.9” days. However, he does remember downloading a very early version and thinking: “Yeah, right — how is this Linux thing going to compete with Coherent” (an early 1990's Unix clone from The Mark Williams Company). John's been involved with several embedded Linux projects at the developer/architect level. Currently he's looking for his next project. You can contact him at John At Lombardos Period ORG.


 
This article was originally published on LinuxDevices.com and has been donated to the open source community by QuinStreet Inc. Please visit LinuxToday.com for up-to-date news and articles about Linux and open source.



Comments are closed.