Skip to main content

skip to main content

developerWorks  >  Linux  >

Linux on board: Auto-uploading Nokia N800 photos

Move your pictures around

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


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

Other developerWorks articles on developing for the Nokia N770 and N800

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.



Back to top


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.



Back to top


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.



Back to top


Room for improvement

Share this...

digg Digg this story
del.icio.us Post to del.icio.us
Slashdot Slashdot it!

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

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

Author photo

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


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top


DB2, Lotus, Rational, Tivoli, and WebSphere are trademarks of IBM Corporation in the United States, other countries, or both. Linux is a trademark of Linus Torvalds in the United States, other countries, or both. UNIX is a registered trademark of The Open Group in the United States and other countries. Other company, product, or service names may be trademarks or service marks of others.