首先,让我们快速回顾一下。在这个分三部分的系列的 第 1 期 中,演示了 Nokia N800 Linux® 的内部结构,列出了它的 技术规范和物理参数,并阐述了如何设置和测试构建环境。在 第 2 期 的末尾,展示了一个程序,只要用户按下一个按钮,它就会将一幅图像压缩为 JPEG 文件,并将其保存在内存中。
现在,在第 3 期也是最后一期文章中,您将会看到如何将这些 JPEG 文件自动上传到远程站点。
上传文件比我最初所希望的稍微困难一些。N800 没有提供很多文件上传和下载工具(尽管它提供了
curl)。无论如何,应该避免将文件保存在本地。
此方法从应用程序直接使用 libcurl,而不是在命令行运行 curl。与 libjpeg 一样,Libcurl 用于处理 stdio FILE 对象,而不是内存缓冲区。
幸运的是,通过扩展 GNU C 库,可以改变这种现状。
fmemopen() 函数提供了一个 stdio FILE * 对象,该对象表示内存中的一个缓冲区。通过调用 fmemopen 取代 test.jpg 的 open,问题就解决一半了:
清单 1. 使用 fmemopen
static unsigned char jpegdata[JPEG_SIZE];
FILE *out;
out = fmemopen(jpegdata, JPEG_SIZE, "wb");
|
JPEG_SIZE 宏被定义为 512KB,通过试验可以达到这个值(在保证较高质量的前提下,我在测试中所见过的最大大小为 360KB 左右,因此我认为少量的安全冗余已经足够了)。
JPEG 库调用总体上没有什么变化。完成这些调用后,所需的只是使用 libcurl 上传文件。以下是上传文件的示例代码:
清单 2. 使用 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);
|
在大多数情况下,此代码都能发挥作用。我们忽略了 CURLOPT_INFILESIZE_LARGE 选项,它用于指定要上传的文件大小。
curl 上传整个文件 — 在 512KB 的情况下,大部分都是空字节。curl 库假定该大小是所建议的大小。幸运的是,我们可以再次使用
fmemopen 来解决:
清单 3. 再次使用 fmemopen
FILE *jpeg;
off_t size;
size = ftell(out);
jpeg = fmemopen(jpegdata, size, "rb");
|
现在,把新的 JPEG 文件句柄交给 curl,结果几乎与我所期望的一样。第一次以后的每次上传都会产生错误 18,CURLE_PARTIAL_FILE,提示文件传输没有全部完成。因为文件大小是我所期望的大小,并且不会影响到图像,所以我忽略了这个错误。
借助 libjpeg 和
libcurl,摄像机应用程序基本上能够实现所需的功能了。它上传来自摄像机的图像流,因此不会消耗太多的本地磁盘空间。当然,如果很注重这项功能,可以添加配置选项,允许用户指定文件上传位置和上传方式。
您可能觉得用同一个缓冲区打开两个 FILE 对象会有风险。其实不用担心,因为两个对象重叠的部分并不会被执行。在 libcurl 开始之前,libjpeg 就已经完成对缓冲区的处理。fflush() 看起来有些多余,但是可以确保剩余的数据仍在内存中。当然,还有更多的工作要做。
始终为同一个文件分配相同的名称并不是最佳方法,而且如果这样做,就得考虑一些问题,比如 “如果网络连接断开会发生什么”、“拍照的速度有多快”。初步的解决方案是,当后台线程试图上传编码消息时对这些消息进行存储。这会导致需要处理一些新的事情,特别是,针对上传进行排列的 JPEG 文件链接列表和一些 pthread 代码。
以下是第一个数据结构:
清单 4. JPEG 数据列表
typedef struct jpeg_data {
struct jpeg_data *next, *prev;
size_t size;
unsigned char *data;
} jpeg_data_t;
|
传送的 AppData 结构需要一个指向 jpeg_data_t 的指针作为列表标题;还需要一个 pthread 互斥锁来确保在线程交互中上传例程和 JPEG 创建代码不会冲突。以下是具体的 JPEG 代码,从输出文件的刷新开始:
清单 5. 排队以便稍后传递
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);
|
现在只剩下两个次要的任务 —— 初始化互斥锁和启动线程运行可上传的文件的列表。最后的任务可能是最有趣的代码块;此处删除了一些可预知但是冗长的代码块,以使代码更加简洁:
清单 6. 忙碌与等待
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);
}
}
}
|
检查 appdata->done 可以发现代码的另外一处需求;当 GUI 开始清除时,由于程序已经退出,用户无法知道是否还有图片等待上传,因此清除代码设置了一个标记,让上传程序知道没有需要处理的数据,然后使用 pthread_join() 等待上传程序。
执行了诸多操作后(也借鉴了一些来自 #maemo IRC 频道的优秀技巧),摄像机应用程序现在能够正常运行了。下一步是将其打包,以便在 N800 上安装。标准方法是使用 dpkg 工具进行打包,这种方法不仅灵活而且功能强大;它也需要较大的开销,而且对于单个独立的二进制文件来说有些大材小用。
在本例中,我使用比较简单的策略,即构建一个非常小的包。一个 debian 包是一个包含 3 个文件的归档文件(通过 ar 命令生成):
- 名为 debian-binary 的文件表示 debian 包版本(它只能包含文本 “2.0”),
- 名为 control.tar.gz 的文件包含一些元数据,
- 名为 data.tar.gz 的文件包含将要安装的文件。
数据文件事实上只需包含两个文件。一个是实际的程序,另一个为 “desktop” 文件,该文件告诉应用程序管理器关于数据文件的信息。我把实际的程序安装在 /usr/bin 中,把 desktop 文件安装在 /usr/share/applications/hildon 中。我使用了一个非常小的 desktop 文件:
清单 7. desktop 文件
[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
|
该文件告诉应用程序管理器应该为应用程序创建一个条目,条目名为 dW Cam,该条目实际上是通过 /usr/bin/dwcam 来实现的。没有指定定制图标;系统只为应用程序使用默认的图标。可通过该图标在菜单上获取应用程序。将这两个文件编译为一个称作 data.tar.gz 的 tarball 文件;需要使用相对路径名进行编译,例如 dwcam 的路径名为 ./usr/bin/dwcam。
现在需要处理 debian 包文件元数据。该元数据至少包含一个版权文件、一个 changelog 和一个控制文件;它们将被编译为一个称为 control.tar.gz 的 tarball 文件。控制文件告诉 Debian 包代码如何处理应用程序。以下是这个控制文件。
清单 8. 控制文件
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
[...]
|
我并没有包含全部的依赖列表,但是通常情况下这应该是一个依赖关系列表。如果使用 dpkg 来构建包,这些依赖项将被自动填充。XB-Maemo-Icon-26 字段是一个小图标的 base64 表示。
一旦生成了控制和数据 tarball 文件,您需要使用 ar 将它们和 debian-binary 文件连接在一起。将 debian-binary 文件作为归档文件中的第一个文件,这一点很重要;否则,debian 工具不会将文件识别为一个 debian 包。连接完成之后,就可以安装生成的文件了,可以从命令行通过
dpkg -i dwcam-0.1.deb 命令安装,或者使用应用程序管理器,使用菜单项 Application -> Install from file... 安装。
快速检查表明,新图标已经出现在 Tools/Extras 菜单的 dW Cam 名称下面,而且应用程序能够正常运行。
很容易发现,这个功能还有很大改进空间。如果需要移植到其他平台,也许有必要设置 dpkg 以处理不同版本。如果只有一个目标平台,那么可以将编译器的标记连接起来;如果需要多个目标平台,则有点困难。构建最终程序所使用的标记非常长:
清单 9. C 编译器标记
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
|
对于如何避免来自不同库的包含文件之间的冲突,不同系统具有不同的编程习惯;一些系统使用 -I/usr/local/include 处理上述的所有包含路径。尽管如此,虽然 dpkg 系统对于我的项目来说有些大材小用,如果想要继续开发
和扩展系统或其他设备,dpkg 系统非常有用而且速度很快。
显然,可以进行改进的另一个主要区域是二进制文件中硬编码密码的使用。应用程序应该有一个不错的用户界面,允许配置各种设置,比如如何动态地上传文件,上传到哪里;同样地,应用程序也应该可以设置在缺乏用户指示时拍照的频率。实现这个任务的代码并不很复杂;由于该技术与 N800 没有多大关系,也为了使代码更简洁,我们省略了一部分代码。这些代码更适合针对 Gtk 开发的更全面讨论。
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
-
“Linux on board: Developing for the Nokia N800”(developerWorks,2007 年 11 月)介绍了 Nokia N800 及其构建环境,可以作为本文的准备知识。
-
“Linux 的魅力:访问 Nokia N800 摄像机”(developerWorks,2007 11 月)演示了如何使用 gstreamer 构建摄像机应用程序,以访问 Nokia N800 的 Webcam。
-
“Linux on board: Linux powers Nokia 770”(developerWorks,2006 年 11 月)介绍了 N800 的前身,基于 Linux 的 Nokia 770 Internet tablet。
-
“How to use the Camera API” 是一个 maemo 教程,Peter 在本文中使用该教程开发应用程序。
-
maemo.org 为
Internet tablet 提供了应用程序开发平台。该站点包含 可用的应用程序的 wiki,其中很多程序针对 Nokia 770,还有一些 下载(可以从该处获得 N800 的 3.0 “bora” 发布)和一本非常好的 平台软件包制作指南。
-
“创建 Debian Linux 软件包”(developerWorks,2003 年 7 月)展示了如何构建关于 Linux 设备的易于发布的软件包(maemo 强调熟悉 “GTK+/GNOME 技术和 Debian 工具”)。
- 阅读 Wikipedia 上关于 Nokia
N800 和
N770 的文章。
-
Scratchbox 是交叉编译工具包,目的是使嵌入式 Linux 应用程序开发起来更轻松。
- 在
developerWorks Linux 专区 查找关于 Linux 开发人员的更多资源,浏览
最流行的文章和教程。
- 请参阅 developerWorks 上的所有
Linux 技巧
和
Linux 教程。
- 随时关注
developerWorks 技术活动和网络广播。
获得产品和技术
-
GStreamer 是一个库,允许构造媒体处理组件图,包括简单 Ogg/Vorbis 回放和复杂音频(混合)和视频(非线性编辑)处理。
-
订购 SEK for Linux,共包含两张 DVD,其中有用于 Linux 的最新 IBM 试用软件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。
- 使用
IBM 试用软件,构建您的下一个 Linux 开发项目,可以直接从 developerWorks 下载。
讨论
- 通过
新 developerWorks 空间 中的博客、论坛、podcast 和社区主题加入
developerWorks 社区。
