![]() |
|
级别: 初级
Peter Seebach, 作家, 自由作家
2008 年 1 月 14 日
Linux 的魅力 的 3 期文章用实际例子演示了如何着手构建 Nokia N800 应用程序:使用摄像机功能创建 Webcam。本文是第 3 期,也是最后一期。本文将编写一个自动照片上传例程,用于上传所拍照片。
首先,让我们快速回顾一下。在这个分三部分的系列的 第 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
,问题就解决一半了:
|
JPEG_SIZE
宏被定义为 512KB,通过试验可以达到这个值(在保证较高质量的前提下,我在测试中所见过的最大大小为 360KB 左右,因此我认为少量的安全冗余已经足够了)。
JPEG 库调用总体上没有什么变化。完成这些调用后,所需的只是使用 libcurl
上传文件。以下是上传文件的示例代码:
|
在大多数情况下,此代码都能发挥作用。我们忽略了 CURLOPT_INFILESIZE_LARGE
选项,它用于指定要上传的文件大小。 curl
上传整个文件 — 在 512KB 的情况下,大部分都是空字节。curl
库假定该大小是所建议的大小。幸运的是,我们可以再次使用 fmemopen
来解决:
|
现在,把新的 JPEG 文件句柄交给 curl
,结果几乎与我所期望的一样。第一次以后的每次上传都会产生错误 18,CURLE_PARTIAL_FILE
,提示文件传输没有全部完成。因为文件大小是我所期望的大小,并且不会影响到图像,所以我忽略了这个错误。
借助 libjpeg
和 libcurl
,摄像机应用程序基本上能够实现所需的功能了。它上传来自摄像机的图像流,因此不会消耗太多的本地磁盘空间。当然,如果很注重这项功能,可以添加配置选项,允许用户指定文件上传位置和上传方式。
您可能觉得用同一个缓冲区打开两个 FILE 对象会有风险。其实不用担心,因为两个对象重叠的部分并不会被执行。在 libcurl
开始之前,libjpeg
就已经完成对缓冲区的处理。fflush()
看起来有些多余,但是可以确保剩余的数据仍在内存中。当然,还有更多的工作要做。
![]() ![]() |
![]()
|
始 终为同一个文件分配相同的名称并不是最佳方法,而且如果这样做,就得考虑一些问题,比如 “如果网络连接断开会发生什么”、“拍照的速度有多快”。初步的解决方案是,当后台线程试图上传编码消息时对这些消息进行存储。这会导致需要处理一些新的 事情,特别是,针对上传进行排列的 JPEG 文件链接列表和一些 pthread 代码。
以下是第一个数据结构:
|
传送的 AppData 结构需要一个指向 jpeg_data_t
的指针作为列表标题;还需要一个 pthread 互斥锁来确保在线程交互中上传例程和 JPEG 创建代码不会冲突。以下是具体的 JPEG 代码,从输出文件的刷新开始:
|
现在只剩下两个次要的任务 —— 初始化互斥锁和启动线程运行可上传的文件的列表。最后的任务可能是最有趣的代码块;此处删除了一些可预知但是冗长的代码块,以使代码更加简洁:
|
检查 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 文件:
|
该文件告诉应用程序管理器应该为应用程序 创建一个条目,条目名为 dW Cam,该条目实际上是通过 /usr/bin/dwcam 来实现的。没有指定定制图标;系统只为应用程序使用默认的图标。可通过该图标在菜单上获取应用程序。将这两个文件编译为一个称作 data.tar.gz 的 tarball 文件;需要使用相对路径名进行编译,例如 dwcam 的路径名为 ./usr/bin/dwcam。
现 在需要处理 debian 包文件元数据。该元数据至少包含一个版权文件、一个 changelog 和一个控制文件;它们将被编译为一个称为 control.tar.gz 的 tarball 文件。控制文件告诉 Debian 包代码如何处理应用程序。以下是这个控制文件。
|
我并没有包含全部的依赖列表,但是通常情况下这应该是一个依赖关系列表。如果使用 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 以处理不同版本。如果只有一个目标平台,那么可以将编译器的标记连接起来;如果需要多个目标平台,则有点困难。构建最终程序所使用的标记非常长:
|
对于如何避免来自不同库的包含文件之间的冲突,不同系统具有不同的编程习惯;一些系统使用 -I/usr/local/include
处理上述的所有包含路径。尽管如此,虽然 dpkg 系统对于我的项目来说有些大材小用,如果想要继续开发 和扩展系统或其他设备,dpkg 系统非常有用而且速度很快。
显 然,可以进行改进的另一个主要区域是二进制文件中硬编码密码的使用。应用程序应该有一个不错的用户界面,允许配置各种设置,比如如何动态地上传文件,上传 到哪里;同样地,应用程序也应该可以设置在缺乏用户指示时拍照的频率。实现这个任务的代码并不很复杂;由于该技术与 N800 没有多大关系,也为了使代码更简洁,我们省略了一部分代码。这些代码更适合针对 Gtk 开发的更全面讨论。