Эта статья о приложении под названием CyberNanny, которое я недавно создал, чтобы видеть свою дочку, Миранду, из любого места в любое время. Приложение написано на Visual C++ (MFC) и объединяет в себе такие разные технологии, как Kinect и его SDK, Windows Azure, веб-сервисы и автоматизация Office через Outlook. Проект размещен на CodePlex (cybernanny.codeplex.com), где можно посмотреть его код или внести в него свой вклад.
Прежде чем рассказать об основных элементах этого приложения, я кратко поясню технологии, использованные для его создания.
C++ был — и остается — рабочей лошадкой во многих компаниях, разрабатывающих ПО. Стандарт C++ 11 выводит этот язык на новый уровень. Его можно описать тремя прилагательными: современный, элегантный и крайне быстрый. Кроме того, MFC все еще используется, и Microsoft обновляет ее при каждом выпуске очередной версии своего компилятора Visual C++.
Технология Kinect великолепна, если не сказать больше; она меняет то, как мы взаимодействуем с играми и компьютерами. А благодаря тому, что Microsoft снабдила разработчиков Kinect SDK, перед ними открылся мир новых возможностей в создании приложений, требующих взаимодействия с человеком. Любопытно, что Kinect SDK основан на COM (равно как и новая модель программирования в Windows 8, названная Windows Runtime и часто сокращаемая до WinRT). Этот SDK также доступен в языках Microsoft .NET Framework.
Одно из требований к CyberNanny заключалось в надежной доставке сообщений через очередь с высокой доступностью, и Windows Azure реализует это требование.
Windows Azure — это предложение Microsoft Platform as a Service (PaaS), которое существует уже несколько лет. Эта платформа предоставляет множество сервисов, позволяющих создавать решения поверх них (например, Compute и Storage). Одно из требований к CyberNanny заключалось в надежной доставке сообщений через очередь с высокой доступностью, и Windows Azure реализует это требование.
Использование веб-сервисов из неуправляемого кода возможно через Windows Web Services API (WWSAPI), введенный в Windows 7. У меня есть публикация в блоге (bit.ly/LiygQY), где я описал WPF-приложение, реализующее неуправляемый компонент, который использует WWSAPI. Важно упомянуть, что WWSAPI встроен в ОС, поэтому нет нужды что-либо скачивать или устанавливать, кроме Windows SDK (чтобы получить заголовочные и lib-файлы).
Зачем заново изобретать колесо? Одним из требований к CyberNanny была возможность отправлять сообщения по электронной почте с прикрепленными картинками, поэтому, чтобы не писать собственный класс для обработки электронной почты, я предпочел для этой задачи задействовать функциональность Outlook. Это позволило мне сосредоточиться на главной цели: создании распределенного приложения для присмотра за моей дочкой.
Эта статья состоит из четырех главных разделов:
- обзор общей архитектуры решения;
- архитектура Kinect;
- локально развертываемые компоненты (неуправляемые);
- размещаемые в облаке компоненты (управляемые).
Обзор общей архитектуры решения
Концепция CyberNanny проста (рис. 1), но в ней есть некоторые движущиеся части. Ее можно кратко описать как толстый клиент, написанный на Visual C++, который захватывает кадры через датчик Kinect. Эти кадры потом можно использовать как снимок, прикрепляемый к новому сообщению электронной почты, формируемому в Outlook через механизм автоматизации. Приложение уведомляется о необработанных запросах следующим образом: по таймеру порождается новый поток, который опрашивает очередь, размещенную в Windows Azure. Запросы ставятся в очередь через веб-страницу ASP.NET.
Рис. 1. Архитектура CyberNanny
Kinect for Xbox 360 | Kinect for Xbox 360 |
Capture Frames | Захват кадров |
Polling Queue | Опрос очереди |
Windows Azure Web Role (2 Cores) WCF Service Queue | Windows Azure Веб-роль (2 узла) Очередь WCF-сервиса |
Smartphone | Смартфон |
Request | Запрос |
Tablet | Планшет |
Happy Dad | Счастливый папочка |
Laptop | Лэптоп |
Windows 7 Laptop | Лэптоп с Windows 7 |
If there's a request, take picture, compose e-mail, attach picture and send | Если есть запрос, делаем снимок, формируем почтовое сообщение, прикрепляем снимок и отправляем |
Send an E-mail | Отправка электронной почты |
Для запуска и тестирования этого решения у вас должны быть:
- датчик Kinect (я использовал версию для Xbox 360);
- подписка Windows Azure;
- Kinect SDK.
Архитектура Kinect
Хорошее понимание того, как устроены вещи и как их можно реализовать, крайне важно в любом проекте разработки, и случай с Kinect — не исключение. Microsoft предоставила SDK для разработчиков, использующих как управляемый, так и неуправляемый код. Я опишу архитектуру Kinect, показанную на рис. 2.
Рис. 2. Архитектура Kinect for Windows
Applications | Приложения |
NUI API | NUI API |
Windows Core Audio and Speech APIs | Windows Core Audio API и Speech API |
DMO Codec for Mic Array | DMO-кодек для микрофонной решетки |
Device Setup | Настройка устройства |
Device Access | Доступ к устройству |
Video Stream Control | Управление видеопотоком |
Audio Stream Control | Управление аудиопотоком |
User Mode | Пользовательский режим |
WinUSB Device Stack | Стек устройств WinUSB |
WinUSB Camera Stack | Стек камер WinUSB |
USBAudio Audio Stack | Стек аудиоустройств USBAudio |
Kernel Mode | Режим ядра |
Kernel-Mode Drivers for Kinect for Windows | Драйверы режима ядра для Kinect for Windows |
USB Hub | USB-концентратор |
Hardware | Аппаратный уровень |
Motor | Мотор |
Cameras | Камеры |
Audio Mic Array | Микрофонная решетка |
Kinect Sensor | Датчик Kinect |
Kinect for Windows SDK | Kinect for Windows SDK |
Windows Components | Windows-компоненты |
User-Created Components | Компоненты, создаваемые пользователем |
Цифры в кружках на рис. 2 соответствуют следующему.
- Аппаратная часть Kinect Аппаратный компоненты, включая Kinect и USB-концентратор, через который этот датчик подключается к компьютеру.
- Драйверы Kinect Windows-драйверы для Kinect, устанавливаемые в процессе установки SDK, как описывается в этой статье. Драйверы Kinect поддерживают:
- микрофонную решетку (microphone array) Kinect как аудиоустройство режима ядра, к которому можно обращаться через стандартные аудио-API в Windows;
- управление потоковыми аудио и видео (цвет, глубина и скелет);
- функции перечисления устройств, позволяющие приложению использовать более одного датчика Kinect.
Хорошее понимание того, как устроены вещи и как их можно реализовать, крайне важно в любом проекте разработки, и случай с Kinect — не исключение.
- Аудио- и видеокомпоненты Kinect Natural User Interface (NUI) для отслеживания скелета, аудио, цвета и глубины изображения.
- DirectX Media Object (DMO) Необходим для формирования диаграммы направленности микрофонной решетки и локализации источника звука.
- Стандартные API в Windows 7 API-интерфейсы для звука, речи и медиа в Windows 7, описанные в Windows 7 SDK и Microsoft Speech SDK.
Я продемонстрирую, как я использовал видеокомпонент для захвата кадров, которые потом сохранялись как JPEG-файлы для отправки по электронной почте. Рендеринг захваченных кадров осуществляется средствами Direct2D.
Класс Nui_Core Я написал класс Nui_Core, который инкапсулирует функциональность, нужную мне от датчика Kinect. В приложении создается только один экземпляр этого класса. Приложение взаимодействует с датчиком через член типа INuiSensor, представляющий физическое устройство, подключенное к компьютеру. Важно помнить, что Kinect SDK основан на COM, поэтому вышеупомянутый интерфейс — равно как и все остальные COM-интерфейсы, широко применяемые в этом приложении, — управляются смарт-указателями (например, CComPtr<INuiSensor> m_pSensor;).
Захват кадров с датчика осуществляется следующим образом.
- Проверяем, доступен ли датчик; для этого вызываем NuiGetSensorCount.
- Создаем экземпляр датчика Kinect вызовом NuiCreateSensorByIndex.
- Создаем объект фабрики вызовом D2D1CreateFactory (для создания Direct2D-ресурсов).
- Создаем события для каждого потока данных (stream), необходимого приложению.
- Открываем потоки данных вызовом NuiImageStreamOpen.
- Обрабатываем захваченные данные (кадр).
Подготовив экземпляр Nui_Core, вы можете легко сделать снимок по требованию, вызвав метод TakePicture, показанный на рис. 3.
Рис. 3. Метод TakePicture
void Nui_Core::TakePicture(std::shared_ptr<BYTE>& imageBytes, int& bytesCount) {
byte *bytes;
NUI_IMAGE_FRAME imageFrame;
NUI_LOCKED_RECT LockedRect;
if (SUCCEEDED(m_pSensor->NuiImageStreamGetNextFrame(m_hVideoStream,
m_millisecondsToWait, &imageFrame))) {
auto pTexture = imageFrame.pFrameTexture;
pTexture->LockRect(0, &LockedRect, NULL, 0);
if (LockedRect.Pitch != 0) {
bytes = static_cast<BYTE *>(LockedRect.pBits);
m_pDrawColor->Draw(bytes, LockedRect.size);
}
pTexture->UnlockRect(0);
imageBytes.reset(new BYTE[LockedRect.size]);
memcpy(imageBytes.get(), bytes, LockedRect.size);
bytesCount = LockedRect.size;
m_pSensor->NuiImageStreamReleaseFrame(m_hVideoStream, &imageFrame);
}
}
Заметьте, что вы передаете смарт-указатель, чтобы сохранить байты изображения и количество байтов, скопированных в него, а затем эта информация используется для создания битовой карты.
Не забывайте по окончании работы с датчиком выключать его вызовом NuiShutdown; кроме того, нужно освобождать описатели, использовавшиеся вами.
Класс DrawDevice Как упоминалось ранее, средства рендеринга предоставляются Direct2D; вот почему для использования Nui_Core требуется другой вспомогательный класс. Этот класс отвечает за обеспечение доступности ресурсов для захваченного кадра, в данном случае — битовой карты.
Три основных метода класса DrawDevice: Initialize, Draw и EnsureResources. Я опишу каждый из них.
Initialize Отвечает за настройку трех членов типа DrawDevice. В приложении имеется элемент управления «вкладки» с тремя вкладками, поэтому для каждой из них предусмотрен свой член (определяют режимы просмотра Color, Skeletal и Depth). Каждая вкладка — это окно, в котором визуализируется соответствующий кадр. InitializeColorView, показанный в следующем коде, — хороший пример вызова метода Initialize:
bool Nui_Core::InitializeColorView() {
auto width = m_rect.Width();
auto height = m_rect.Height();
m_pDrawColor = std::shared_ptr<DrawDevice>(new DrawDevice());
return (m_pDrawColor.get()->Initialize(m_views[TAB_VIEW_1]->m_hWnd,
m_pD2DFactory.p, 640, 320, NULL));
}
Draw Визуализирует кадр на соответствующей вкладке. Принимает аргумент типа Byte*, захваченный датчиком. Как и в видеороликах, эффект анимации достигается последовательной визуализацией статических кадров.
EnsureResources Отвечает за создание битовой карты по запросу метода Draw.
Локально развертываемые компоненты (неуправляемые)
Проект CyberNanny состоит из следующих компонентов.
- Приложение:
- CCyberNannyApp (наследуется от CWinApp). В приложении один член типа Nui_Core для взаимодействия с датчиком.
- UI-элементы:
- CCyberNannyDlg (основное окно, наследуется от CDialogEx);
- CAboutDlg (диалог About, наследуется от CDialogEx).
- Клиент веб-сервиса:
- файлы, автоматически генерируемые после выполнения WSUTIL применительно к сервису, имеют формат Web Services Description Language (WSDL). Эти файлы содержат сообщения, структуры и методы, предоставляемые веб-сервисом на основе WCF.
- Объекты Outlook:
- чтобы манипулировать какими-либо объектами Outlook, вы должны импортировать их в свой проект, выбрав Add MFC Class в окне ActiveX Control Wizard. Объекты, используемые в этом приложении, — Application, Attachment, Mail-Item и Namespace.
- Прокси:
- это собственный класс, который инкапсулирует процедуру создания объектов, нужных для взаимодействия с WWSAPI.
- Вспомогательные классы:
- используются для поддержки такой функциональности приложения, как преобразование битовой карты в JPEG для уменьшения размера файла, предоставление оболочки для отправки сообщений по электронной почте и взаимодействия с Outlook и др.
При запуске приложения происходят следующие события.
- Вызовом RegisterWindowMessage определяется новое оконное сообщение. Это необходимо для добавления элементов в список событий при обработке запросов. Дело в том, что вы не можете напрямую модифицировать UI-элементы из любого потока, отличного от UI-потока, — иначе вы получите исключение, связанное с недопустимым вызовом между потоками. Все это управляется MFC-инфраструктурой обработки сообщений.
- Вы инициализируете член Nui_Core и настраиваете пару таймеров (один из них обновляет текущее время в строке состояния, а другой запускает поток для опроса очереди, чтобы проверить наличие в ней необработанного запроса).
- Датчик Kinect начинает захват кадров, но приложение не получает снимок, если в очереди нет запроса. Метод ProcessRequest отвечает за получение снимка, его сериализацию на диск, запись в средство просмотра событий и запуск механизма автоматизации Outlook, как показано на рис. 4.
Рис. 4. Вызов метода ProcessRequest
void CCyberNannyDlg::ProcessRequest(_request request) {
if (!request.IsEmpty) {
auto byteCount = 0;
ImageFile imageFile;
std::shared_ptr<BYTE> bytes;
m_Kinect.TakePicture(bytes, byteCount);
imageFile.SerializeImage(bytes, byteCount);
EventLogHelper::LogRequest(request);
m_emailer.ComposeAndSend(request.EmailRecipient,
imageFile.ImageFilePath_get());
imageFile.DeleteFile();
}
}
Кадр, изначально захваченный Kinect, является битовой картой, которая занимает примерно 1,7 Мб (что неудобно для отправки по почте, а значит, битовую карту нужно конвертировать в JPEG-изображение). Кроме того, она перевернута, поэтому требуется поворот изображения на 180°. С этой целью выполняется пара вызовов GDI+. Эта функциональность инкапсулирована в класс ImageFile.
Класс ImageFile служит оболочкой для операций с GDI+. Два основных метода этого класса представляют собой следующее.
- SerializeImage Этот метод принимает shared_ptr<BYTE>, который содержит байты захваченного кадра, подлежащие сериализации в изображение, а также счетчик байтов. Кроме того, изображение поворачивается вызовом метода RotateFlip.
- GetEncoderClsid Как упоминалось, файл изображения слишком велик для использования в качестве вложения в почтовом сообщении, поэтому его нужно кодировать в более компактный формат (JPEG, например). GDI+ предоставляет функцию GetImageEncoders, позволяющую выяснить, какие конвертеры доступны в системе.
До сих пор я рассматривал, как приложение использует датчик Kinect и как из захваченных кадров создается картинка, отправляемая по электронной почте. Теперь я покажу, как вызывать WCF-сервис, размещенный в Windows Azure.
Кадр, изначально захваченный Kinect, является битовой картой, которая занимает примерно 1,7 Мб (что неудобно для отправки по почте, а значит, битовую карту нужно конвертировать в JPEG-изображение). Кроме того, она перевернута, поэтому требуется поворот изображения на 180°.
WWSAPI, введенный в Windows 7, позволяет разработчикам, использующим неуправляемый код, легко работать с веб- или WCF-сервисами, не беспокоясь о деталях коммуникационного взаимодействия (сокетах). Первый шаг в использовании сервиса — получение WSDL, который применяется с WSUTIL и в свою очередь генерирует код на C для прокси сервиса, который представляет собой структуры данных, необходимые сервису. Существует альтернатива под названием Casablanca (bit.ly/JLletJ) — она поддерживает клиент-серверное взаимодействие в облаке для неуправляемого кода, но ее не было на момент написания CyberNanny.
Как правило, полученный WSDL сохраняется на диске, а затем WSDL-файл вместе со связанными файлами схемы используется как ввод для WSUTIL. Учитывайте один аспект в работе со схемами. Их нужно скачивать наряду с WSDL, а иначе WSUTIL будет жаловаться в процессе генерации файлов. Определить нужные схемы можно, проверив параметр .xsd в разделе схемы в WSDL-файле:
wsutil /wsdl:cybernanny.wsdl /xsd:cybernanny0.xsd
cybernanny1.xsd cybernanny2.xsd cybernanny3.xsd
/string:WS_STRING
В CyberNanny имеется только веб-роль, которая занимает два узла, чтобы обеспечить высокую доступность.
Конечные файлы можно добавить в решение, а затем вызывать сервис через эти файлы. Для работы с WWSAPI требуются четыре основных объекта:
- WS_HEAP;
- WS_ERROR;
- WS_SERVICE_PROXY;
- WS_CHANNEL_PROPERTY.
Эти объекты обеспечивают взаимодействие между клиентом и сервисом. Функциональность вызова сервиса я поместил в класс Proxy.
Большинство WWSAPI-функций возвращает HRESULT, поэтому отладка и устранение ошибок могут оказаться весьма трудной задачей. Но не бойтесь: вы можете включить трассировку из Windows Event Viewer и увидеть точную причину сбоя данной функции. Чтобы включить трассировку, выберите Applications and Services Logs | Microsoft | WebServices | Tracing (щелкните правой кнопкой мыши для ее включения).
Это в основном исчерпывает описание неуправляемых компонентов решения. Более подробную информацию можно извлечь из исходного кода, размещенного на сайте CodePlex. Следующий раздел посвящен последнему компоненту решения — Windows Azure.
Размещаемые в облаке компоненты (управляемые)
Пожалуйста, обратите внимание, что это не учебное пособие по Windows Azure, а просто описание компонентов Windows Azure, применяемых в CyberNanny. За более глубокой и детальной информацией обращайтесь на веб-сайт Windows Azure windowsazure.com. Платформа Windows Azure (рис. 5) включает следующие сервисы:
- Windows Azure Compute;
- Windows Azure Storage;
- Windows Azure SQL Database;
- Windows Azure AppFabric;
- Windows Azure Marketplace;
- Windows Azure Virtual Network.
Рис. 5. Сервисы платформы Windows Azure
Cloud Applications | Облачные приложения |
Web/Worker Roles | Веб- и рабочие роли |
Business Analytics | Бизнес-аналитика |
SQL Azure Reporting | Отчетность SQL Azure |
Networking | Сетевые сервисы |
Traffic Manager Connect | Traffic Manager Connect |
HPC | HPC |
HPC Scheduler | Планировщик HPC |
Data Management | Управление данными |
SQL Azure Tables Blobs | SQL Azure Таблицы Двоичные объекты |
Messaging | Обмен сообщениями |
Queues Service Bus | Очереди Шина сервисов |
Caching | Кеширование |
In-Memory CDN | В памяти CDN |
Commerce | Коммерция |
Marketplace | Marketplace |
Identity | Идентификация |
Windows Azure Active Directory | Windows Azure Active Directory |
Visual Studio | Visual Studio |
Eclipse | Eclipse |
.NET | .NET |
Java | Java |
PHP | PHP |
Node.js | Node.js |
SDKs | SDK |
В CyberNanny имеется только веб-роль, которая занимает два узла, чтобы обеспечить высокую доступность. Если один из узлов выйдет из строя, платформа переключится на работоспособный узел. Веб-роль — это приложение ASP.NET, и оно лишь вставляет элементы сообщений в очередь. Эти сообщения потом поступают от CyberNanny. Кроме того, имеется WCF-сервис, который является частью веб-роли, отвечающей за обработку очереди.
Заметьте, что роль в Windows Azure — это индивидуальный компонент, выполняемый в облаке, где каждый экземпляр облака соответствует экземпляру виртуальной машины (VM). В случае CyberNanny я использую две VM.
В CyberNanny имеется веб-роль, представляющая собой веб-приложение (не важно, состоит оно только из ASPX-страниц или является набором WCF-сервисов), которое выполняется в IIS. Она доступна через конечные точки HTTP/HTTPS. Существует и другой тип роли — рабочая (Worker Role). Это приложение фоновой обработки (например, для финансовых расчетов), и оно способно предоставлять внешние (доступные из Интернета) и внутренние конечные точки.
Это приложение также использует очередь, предоставляемую Windows Azure Storage, которое обеспечивает надежное хранение и доставку сообщений. Изящество этой очереди в том, что вам не приходится писать никакого специализированного кода для ее использования. От вас не требуется и структуризации хранилища данных так, чтобы оно напоминало очередь, потому что вся эта функциональность обеспечивается самой платформой.
Помимо высокой доступности и масштабируемости, еще одно преимущество платформы Windows Azure — унификация в выполнении разнообразных процессов, например разработки, тестирования и развертывания решений на основе Windows Azure из Visual Studio, а также применение .NET в качестве общепринятого языка при построении решений.
Это приложение также использует очередь, предоставляемую Windows Azure Storage, которое обеспечивает надежное хранение и доставку сообщений.
Есть и некоторые другие интересные функции, которые я хотел бы добавить в CyberNanny, например обнаружение движения и распознавание речи. Если вы хотите использовать это ПО или внести свой вклад в данный проект, пожалуйста, не стесняйтесь. Задействованные технологии теперь вполне доступны, и, хотя они выглядят «разными», они способны взаимодействовать и отлично сочетаются друг с другом.
Удачи в кодировании!