Linux 的魅力: 自动上传 Nokia N800 照片

来源:互联网 发布:网络证券销售怎么样 编辑:程序博客网 时间:2024/06/05 10:27

文档选项将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Peter Seebach, 作家, 自由作家

2008 年 1 月 14 日

Linux 的魅力 的 3 期文章用实际例子演示了如何着手构建 Nokia N800 应用程序:使用摄像机功能创建 Webcam。本文是第 3 期,也是最后一期。本文将编写一个自动照片上传例程,用于上传所拍照片。

首先,让我们快速回顾一下。在这个分三部分的系列的 第 1 期 中,演示了 Nokia N800 Linux® 的内部结构,列出了它的 技术规范和物理参数,并阐述了如何设置和测试构建环境。在 第 2 期 的末尾,展示了一个程序,只要用户按下一个按钮,它就会将一幅图像压缩为 JPEG 文件,并将其保存在内存中。

现在,在第 3 期也是最后一期文章中,您将会看到如何将这些 JPEG 文件自动上传到远程站点。

上传文件

关于开发 Nokia N770 和 N800 的其他 developerWorks 文章
  • Linux 助力 Nokia 770
  • 开发 Nokia N800
  • 访问 Nokia N800 摄像机
  • developerWorks 上所有的 Linux的魅力 系列文章

上传文件比我最初所希望的稍微困难一些。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,提示文件传输没有全部完成。因为文件大小是我所期望的大小,并且不会影响到图像,所以我忽略了这个错误。

借助 libjpeglibcurl,摄像机应用程序基本上能够实现所需的功能了。它上传来自摄像机的图像流,因此不会消耗太多的本地磁盘空间。当然,如果很注重这项功能,可以添加配置选项,允许用户指定文件上传位置和上传方式。

您可能觉得用同一个缓冲区打开两个 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 开发的更全面讨论。