Linux на борту: Автоматическая загрузка фотографий Nokia N800

Перенесите свои изображения

Три этих выпуска Linux на борту показывают, как начать создавать приложения для Nokia N800, на рабочем примере: использовании встроенной камеры для создания Web-камеры. В этом третьем и последнем выпуске мы напишем процедуру автоматической выгрузки полученных изображений.

Питер Сибах, автор, Независимый

Питер Сибах (Peter Seebach) работает с компьютерами много лет и постепенно приспособился. Хотя он до сих пор не понимает, почему мышку надо чистить так часто.



05.02.2008

Для начала - краткое повторение. В первом выпуске этой серии из трёх частей мы показали конфигурацию Linux на Nokia N800 ® , привели технические спецификации и физические характеристики этого устройства, а также объяснили, как установить и настроить среду разработки. К концу второго выпуска мы создали неплохую программу, которая по нажатию кнопки пользователем добросовестно сжимает изображение в формат JPEG, записывает его в память и забывает о нем.

Сейчас, в третьем и последнем выпуске, мы покажем, как добавить автоматическую выгрузку этих JPEG на удалённый сервер.

Выгрузка файлов

Другие статьи developerWorks по разработке для Nokia N770 и N800

Задача загрузки файлов на сервер оказалась сложнее, чем я надеялся изначально. На N800 просто нет нужных инструментов загрузки и выгрузки файлов (хотя на нем есть curl). В любом случае было бы замечательно избежать локального хранения файлов.

Такой подход благоприятствует использованию libcurl напрямую из приложения - вместо возврата в командный процессор и запуска curl из командной строки. Libcurl, как и libjpeg, предпочитает работу с объектом stdio FILE, а не с буфером в памяти.

К счастью, нас выручает одно из расширений библиотеки GNU C. Функция fmemopen() предоставляет объект stdio FILE *, ссылающийся на буфер, расположенный в памяти. Замена вызова open файла test.jpg одним вызовом fmemopen решает половину задачи:

Листинг 1. Использование fmemopen
static unsigned char jpegdata[JPEG_SIZE];
FILE *out;

out = fmemopen(jpegdata, JPEG_SIZE, "wb");

Макрос JPEG_SIZE был определен как 512KB, это значение было получено экспериментальным путём. (Самый большой размер файла, встречавшийся мне при тестировании, составлял около 360 КБ при максимальном качестве, поэтому я предположил, что этого небольшого запаса будет достаточно.)

Вызовы библиотеки 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просто выгружает файл целиком — в данном случае все 512 КБ, большинство из которых пустые. Библиотека curl предполагает, что размер приводится исключительно в информационных целях. К счастью, снова приходит на помощь fmemopen:

Использование 3. Нагружаем fmemopen
FILE *jpeg;
off_t size;
size = ftell(out);
jpeg = fmemopen(jpegdata, size, "rb");

Теперь curl, обрабатывая новый файл JPEG, формирует точно такой результат, который нужен мне — почти. Каждая выгрузка, выполняемая после первой, приводит к возникновению ошибки 18 CURLE_PARTIAL_FILE, которая говорит о том, что файл передан не полностью. Поскольку размер файла точно такой, какой нужен мне, и изображение не повреждено, я игнорирую эту ошибку.

Теперь, благодаря libjpeg и libcurl, приложение камеры делает то, что должно. Оно загружает изображения JPEG, полученные с камеры, при этом не накладывая дополнительной нагрузки на локальный flash-диск. Конечно же, если подходить к этой задаче серьезно, можно добавить параметры конфигурации, предоставив пользователю возможность указать, куда и как выгружать изображения.

Может показаться, что открывать два объекта FILE с использованием одного буфера несколько рискованно. Однако в этом нет никакой проблемы, поскольку при выполнении программы ситуация, когда они будут пересекаться, невозможна. libjpegзаканчивает работу с буфером до запуска libcurl, и функция 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; когда графический интерфейс начинает сворачиваться при выходе из программы, пользователь не может знать, остались ли ещё изображения, подлежащие выгрузке, поэтому в коде завершения работы устанавливается флаг, который сообщает процедуре выгрузки о том, что данных больше не будет, и ждёт завершения её работы, используя pthread_join().


Создание пакета

Хотя на наше приложение для работы с камерой потребовалось немало труда (и несколько отличных советов от пользователей канала IRC #maemo), теперь оно работает так, как нужно. Следующий этап - сборка этого приложения в пакет, который может быть установлен на N800. Традиционно для этого используется мощный и гибкий инструмент dpkg – однако он создает значительную дополнительную нагрузку и слишком тяжел для одного двоичного файла, содержащего в себе всё необходимое.

Я пойду значительно более лёгким путём создания минимального пакета. Пакет debian - это просто архив (созданный с помощью ar), содержащий три файла:

  • файл debian-binary, описывающий версию пакета debian (он должен содержать только текст "2.0"),
  • файл control.tar.gz, содержащий метаданные, и
  • файл data.tar.gz, содержащий файлы, подлежащие установке.

В файле данных достаточно всего двух файлов. Первый файл - это сама программа, а второй - файл "рабочего стола", который сообщает об этой программе менеджеру приложений. Саму программу я установил в /usr/bin; файл рабочего стола устанавливается в /usr/share/applications/hildon. Файл рабочего стола в моём случае получился минимальным:

Листинг 7. Файл рабочего стола
[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; их необходимо скомпилировать с относительными путями - например, dwcam как ./usr/bin/dwcam.

Теперь нам остался только файл метаданных пакета debian. Как минимум это должен быть файл, содержащий правовую информацию, журнал изменений и файл управления; они собираются в архив control.tar.gz. Файл управления говорит коду обработки пакетов 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.

Подготовив управляющий архив и архив с данными, нам остаётся только соединить их вместе файлом debian-binary, используя ar. Важно, чтобы файл debian-binary был первым в архиве, в противном случае инструменты debian даже не смогут определить, что это пакет debian. Теперь получившийся файл можно установить - либо из командной строки dpkg -i dwcam-0.1.deb, либо с помощью Application Manager, выбрав в меню Application -> Install from file....

Быстрая проверка показывает, что иконка с именем dW Cam показывается в меню Tools/Extras, а приложение запускается как и ожидалось.


Пути усовершенствования

Несложно увидеть множество путей для улучшения нашего приложения. Если необходима переносимость приложения на другие платформы, возможно, имеет смысл настроить dpkg для создания сборки. Собирать вручную набор флагов для компилятора разумно, только если у вас одна цель; если у вас несколько целей, задача может стать очень сложной. Флаги, используемые для создания окончательной версии программы, очень длинны:

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

В разных системах применяются различные подходы к тому, как избежать конфликтов между включаемыми файлами из различных библиотек; в некоторых сборках все необходимые пути, указанные выше, обрабатываются простым -I/usr/local/include. И всё же, хотя для моего проекта система dpkg кажется стрельбой из пушек по воробьям, если вы собираетесь продолжать разработку и расширение системы или, возможно, переносить ее на другие устройства, она очень быстро оправдает себя.

Очевидно, ещё одной возможной областью улучшения является использование в двоичном коде жестко заданного пароля. Приложению подобает иметь приятный интерфейс пользователя, позволяющий на лету настроить различные параметры, такие как куда и как выгружать файлы; кроме того, хорошо бы иметь настройку частоты снимков при отсутствии вмешательства пользователя. Это не особенно сложные элементы кода; они были опущены для краткости и отчасти потому, что эти методики имеют мало общего непосредственно с N800. Они были бы более уместны в общем обсуждении разработки на Gtk.

Ресурсы

Научиться

Получить продукты и технологии

  • GStreamer - это библиотека, которая позволяет строить схемы компонентов обработки мультимедиа, от простого проигрывания Ogg/Vorbis до сложной обработки звука (микширование) и видео (нелинейное редактирование).
  • Используйте в своем следующем проекте разработки для Linux ознакомительные версии программного обеспечения IBM, которые можно скачать непосредственно с developerWorks. (EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • developerWorks Premium

    Эксклюзивные инструменты для построения вашего приложения. Узнать больше.

  • Библиотека документов

    Более трех тысяч статей, обзоров, руководств и других полезных материалов.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=287119
ArticleTitle=Linux на борту: Автоматическая загрузка фотографий Nokia N800
publish-date=02052008