 | Level: Introductory Peter Seebach (developerworks@seebs.plethora.net), Freelance writer, Wind River Systems
04 Dec 2007 These
three
installments of Linux on board
show you how to get started building applications for the Nokia N800 by way of a
working example: using the camera feature to create a Webcam. In this third and
final installment, write an automatic photo-uploading routine for the photos you've
taken.
First, a quick review. The
first installment
in this three-part series showed you what's in the Nokia N800 Linux®
installation, listed its technical specs and physical parameters, and explained
how to set up and test the build environment. By the end of the
second installment,
you saw a nice program that, whenever the user pressed a button, dutifully
compressed an image into a JPEG in memory then ignored it.
Now in this third and final installment, you see how to add automatic uploading
of those JPEGs to a remote site.
Uploading files
Uploading files is a little harder than I originally hoped. The N800 simply
doesn't have much for file upload and download tools (although it does provide
curl). In any event, it would be ideal to avoid storing
a local file.
This approach favors use of libcurl directly from the
app rather than shelling out to run curl on the command
line. Libcurl, like libjpeg,
prefers to work on a stdio FILE object instead of a memory buffer.
Luckily, a GNU C library extension comes to the rescue. The
fmemopen() function gives you a stdio FILE * object
that refers to a buffer in memory. Replacing the open
of test.jpg with a single call to fmemopen solves half
the problem:
Listing 1. Using fmemopen
static unsigned char jpegdata[JPEG_SIZE];
FILE *out;
out = fmemopen(jpegdata, JPEG_SIZE, "wb");
|
The JPEG_SIZE macro was defined to 512KB, a value
arrived at through experimentation. (The largest size I ever saw in testing was
about 360KB with higher quality settings, so I figured that a little safety margin
would be plenty.)
The JPEG library calls are totally unchanged. After they're done, all that's
necessary is to use libcurl to upload the file. Here's
sample code for that:
Listing 2. Using libcurl
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_UPLOAD, TRUE);
curl_easy_setopt(curl, CURLOPT_URL,
"ftp://test:dwtest@www.example.net/public_html/test.jpeg");
curl_easy_setopt(curl, CURLOPT_READDATA, jpeg);
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) size);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
|
This works—almost. The
CURLOPT_INFILESIZE_LARGE option, which specifies the
size of the file to be uploaded, is politely ignored.
curl simply uploads the entire file—in this
case 512KB, most of it empty bytes. The curl library
assumes that the size is purely advisory. Luckily,
fmemopen comes to our rescue once again:
Listing 3. Abusing fmemopen
FILE *jpeg;
off_t size;
size = ftell(out);
jpeg = fmemopen(jpegdata, size, "rb");
|
Now, handing curl the new JPEG file handle produces
exactly the results I want—almost. Every upload after the first yields
error 18, CURLE_PARTIAL_FILE, claiming that the file
was not completely transferred. Since the size is what I expect it to be, and the
image isn't harmed, I ignore that error.
Thanks to libjpeg and
libcurl, the camera application now does the things it
most needs to do. It uploads JPEG images streamed from the camera and doesn't even
impose extra wear on the local flash disk. Obviously, if you are serious about
this, you can add configuration options to let the user specify where and how to
upload.
You might think it seems a little risky to open two FILE objects using the same
buffer. But it's not a problem since there is no path of execution where they'd be
overlapping. libjpeg finishes touching the
buffer before libcurl starts, and that fflush() that
might otherwise have seemed redundant ensures that any remaining data are
in memory. Of course, there's more to do here.
Queueing images
Giving the file the same name all the time isn't exactly optimal and once you
think about that, you might start thinking about questions like "what happens if
the network is down" or "how quickly can I take pictures." A first-pass solution
to this is to store up encoded messages while a background thread tries to upload
them. This gives us all sorts of exciting new things to deal with; in particular,
a linked list of JPEG files that are queued for uploading and some pthread code.
Here's the first data structure:
Listing 4. A list of JPEG data
typedef struct jpeg_data {
struct jpeg_data *next, *prev;
size_t size;
unsigned char *data;
} jpeg_data_t;
|
The AppData structure that gets passed around needs a pointer to
jpeg_data_t to serve as the head of the list; it also
needs a pthread mutex to ensure that the upload routine and the JPEG-creation code
never clash in their thread interactions. Here's how the JPEG code handles it,
starting from the flush of the output file:
Listing 5. Queueing for later delivery
fflush(out);
j = malloc(sizeof(jpeg_data_t));
j->prev = NULL;
j->size = ftell(out);
j->data = malloc(j->size);
memcpy(j->data, jpeg_data, j->size);
pthread_mutex_lock(&appdata->jpeg_mutex);
j->next = appdata->jpegs;
if (j->next)
j->next->prev = j;
appdata->jpegs = j;
pthread_mutex_unlock(&appdata->jpeg_mutex);
fclose(out);
jpeg_destroy_compress(&comp);
|
This just leaves us with a couple of minor bookkeeping tasks -- initializing the
mutex and starting a thread to run through the list of uploadable files. This last
bit is probably the most interesting chunk of code involved; a couple of
predictable but verbose blocks have been removed here to make the code a bit
shorter:
Listing 6. Hurry up and wait
void *
upload_jpegs(void *v) {
AppData *appdata = v;
jpeg_data_t *j = NULL;
for (;;) {
pthread_mutex_lock(&appdata->jpeg_mutex);
if (appdata->jpegs) {
/* extract last item from the list, as j */
}
pthread_mutex_unlock(&appdata->jpeg_mutex);
if (j) {
if (upload_jpeg(j)) {
free(j->data);
free(j);
j = NULL;
} else {
jpeg_data_t *list;
pthread_mutex_lock(&appdata->jpeg_mutex);
/* put j back on the list */
pthread_mutex_unlock(&appdata->jpeg_mutex);
sleep(10);
}
} else {
if (appdata->done)
pthread_exit(0);
sleep(5);
}
}
}
|
The check against appdata->done reflects a
need elsewhere in the code; when the GUI starts cleaning up because the program
has been exited, the user has no way of knowing whether there's pictures still to
be uploaded, so the cleanup code sets a flag letting the uploader know there's no
more data coming, then uses pthread_join() to wait for
the uploader.
Packaging it up
Although it took some doing (and some excellent tips from people in the #maemo
IRC channel), the camera application now does the right thing when run. The next
stage is building it into a package that can be installed on the N800. The
canonical way to do this using the dpkg tools is very flexible and powerful; it
also imposes a lot of overhead and is massive overkill for a single self-contained
binary.
In this case, I went with the much simpler strategy of just building a very
minimal package. A debian package is just an archive (produced with
ar) containing three files:
- a file called debian-binary indicating the debian package version (it should
contain only the text "2.0"),
- a file called control.tar.gz containing some metadata, and
- a file called data.tar.gz containing the files to install.
The data file really only needs two files in it. One is the actual program and
the other is a "desktop" file telling the application manager about it. The actual
program, I installed in /usr/bin; the desktop file is installed in
/usr/share/applications/hildon. I went with a very minimal desktop file:
Listing 7. The desktop file
[Desktop Entry]
Encoding=UTF-8
Version=0.1
Type=Application
Name=dW Cam
Exec=/usr/bin/dwcam
X-Window-Icon=tn-bookmarks-link
X-HildonDesk-ShowInToolbar=true
X-Osso-Type=application/x-executable
Terminal=false
|
This file tells the application manager that it should create an entry for an
application called dW Cam which is actually implemented by /usr/bin/dwcam. No
custom icon is specified; the system just uses the default icon for an
application. This is all you need to get an application on the menu. These two
files are compiled into a tarball named data.tar.gz; it should be compiled with
relative path names, so for instance, dwcam is ./usr/bin/dwcam.
What's left is the debian package file metadata. Minimally, this means a
copyright file, a changelog, and a control file; these are compiled into a tarball
named control.tar.gz. The control file is the one which tells the Debian package
code how to handle the application. Here's the control file.
Listing 8. The control file
Package: cam-app
Section: user/accessories
Priority: optional
Maintainer: Peter Seebach <seebs@seebs.net>
Build-Depends: gstreamer (>= 0.10)
Stardards-Version: 3.6.0
Architecture: armel
Version: 0.1
Depends: libatk1.0-0 (>= 1.9.0), libc6 (>= 2.3.5-1), [...]
Description: A trivial camera application.
XB-Maemo-Icon-26:
iVBORw0KGgoAAAANSUhEUgAAABoAAAAZCAAAAAAKtWG8AAAACXBIWXMAAAAnAAAAJwEqCZFP
AAAAB3RJTUUH1QkMEgEBuF+MPAAAACF0RVh0Q29tbWVudABKUEVHOmdudS1oZWFkLmpwZyAy
[...]
|
I didn't include the whole depends list, but in general this would be a list of
dependencies. If you use dpkg to build your package, these can be filled in
automatically. The XB-Maemo-Icon-26 field is a base64 representation of a small
icon.
Once the control and data tarballs are ready, all you have to do is join them
together with the debian-binary file using ar. It is
important that the debian-binary file be the first one in the archive; otherwise,
the debian tools won't even recognize the file as a debian package. Once that's
done, the resulting file can be installed, either from the command line
dpkg -i dwcam-0.1.deb or using the Application Manager,
using the menu item Application -> Install from file....
A quick check reveals that the new icon shows up in the Tools/Extras menu under
the name dW Cam and the application runs as expected.
Room for improvement
It's easy to see a lot of places where this could be improved. If there were a need
for portability to other platforms, it would probably be worth the trouble of
setting up dpkg to handle the build. The technique of just tacking together a set
of flags for the compiler is workable if you only have one target; if you're going
to have more than one, it might be a real problem. The flags being used to build
the final version of the program are pretty long:
Listing 9. C compiler flags
cc --std=c99 -g -O2 -Wall -I/usr/include/atk-1.0 \
-I/usr/include/gtk-2.0 -I/usr/include/pango-1.0 \
-I/usr/lib/gtk-2.0/include -I/usr/include/dbus-1.0 \
-I/usr/lib/dbus-1.0/include -I/usr/include/libxml2 \
-I/usr/lib/glib-2.0/include -I/usr/include/glib-2.0 \
-I/usr/include/gstreamer-0.10 -o dwcam dwcam.c \
-lgstbase-0.10 -lgstinterfaces-0.10 -losso -lgtk-x11-2.0 \
-ljpeg -lhildonbase -lhildonwidgets -lcurl
|
Different systems have different idioms for how to avoid clashes between include
files from multiple different libraries; some would have handled all of the
include paths above with a simple -I/usr/local/include.
Still, while the dpkg system looked like overkill for my project, if you were
going to continue developing and expanding the system or possibly target other
devices, it would become very useful very quickly.
Obviously, another major area of potential improvement is the use of a hardcoded
password in the binary. The application ought to have a nice user interface
allowing it to configure various settings, such as where and how to upload files,
on the fly; likewise, it ought to have settings for how often to take pictures in
the absence of user intervention. This is not an especially hard piece of code to
write; it was omitted partially for brevity and partially because the technique
has very little to do with the N800 in particular. It would fit better in a more
general discussion of Gtk development.
Resources Learn
-
"Linux on board: Developing for the Nokia N800"
(developerWorks, November 2007) introduces you to the Nokia N800 and its build
environment in preparation for this article.
-
"Linux on board: Accessing the Nokia N800 camera"
(developerWorks, November 2007) shows how to build a camera
application using gstreamer to access the Nokia N800's Webcam.
-
"Linux on board: Linux powers Nokia 770"
(developerWorks, November 2006) lets you in on the N800 predecessor, the
Linux-based Nokia 770 Internet tablet.
-
"How to use the Camera API"
is the maemo tutorial that Peter used to develop the application in this series.
-
maemo.org offers an application development
platform for Internet tablets. The site includes a
wiki of available applications,
many for the Nokia 770, downloads
(where you may get the 3.0 "bora" release for the N800), and a truly excellent
guide on how to make packages for the maemo platform.
-
"Create Debian Linux packages"
(developerWorks, July 2003) shows you how to build easy-to-distribute packages for
Linux devices (maemo stresses a familiarity with "GTK+/GNOME technologies and the
Debian tools").
- Read up on the Nokia
N800 and
N770 on Wikipedia.
-
Scratchbox is a cross-compilation toolkit
designed to make embedded Linux application development easier.
- In the
developerWorks Linux zone,
find more resources for Linux developers, and scan our
most popular articles and
tutorials.
- See all
Linux tips
and
Linux tutorials
on developerWorks.
- Stay current with
developerWorks technical events and Webcasts.
Get products and technologies
-
GStreamer is a library that allows
the construction of graphs of media-handling components, ranging from simple
Ogg/Vorbis playback to complex audio (mixing) and video (non-linear editing)
processing.
-
Order the SEK for Linux,
a two-DVD set containing the latest IBM trial software for Linux from DB2®,
Lotus®, Rational®, Tivoli®, and WebSphere®.
- With
IBM trial software,
available for download directly from developerWorks, build your next development
project on Linux.
Discuss
About the author  | 
|  | Peter Seebach collects tiny devices that run on Linux. He is sick of hearing the joke about making a beowulf cluster of them. |
Rate this page
|  |