Статьи

Просто для удовольствия: веб-камера вашего ПК показывает изображения на вашем Windows Phone 7


Вы когда-нибудь задумывались о подключении дополнительного оборудования к вашему телефону?
Я сделал, и не потому, что в телефоне нет этого конкретного оборудования, а скорее из любопытства — что будет, если он будет работать? Так что есть идея — показать изображение с веб-камеры прямо на устройстве Windows Phone 7. Для этого типа приложений может быть несколько применений — во-первых, вы всегда можете отслеживать, что происходит в определенном месте. Кроме того, у вас есть видео-чат, построенный вокруг этой идеи, но это другая история.

Итак, начнем. Прежде всего, посмотрите, как будет организовано приложение. Есть три основных компонента:

  • Клиентское приложение — работает на компьютере, на котором установлена ​​веб-камера. Это будет приложение, которое будет генерировать основные данные для передачи.
  • Служба WCF — это будет промежуточная ссылка, которая будет собирать данные из клиентского приложения и передавать их в приложение WP7.
  • Приложение WP7 — актуальное мобильное приложение, которое будет отображать данные.

Клиентское приложение построено в точности так, как я описал в одном из моих руководств по созданию WPF-приложения для просмотра веб-камеры. Вам нужно создать это приложение, так как я собираюсь использовать его и в этой статье. Если у вас есть готовое клиентское приложение (в данный момент никаких изменений не требуется), давайте приступим к созданию сервиса.

Создайте новое приложение службы WCF и просто добавьте этот код в основной класс обслуживания:

[ServiceContract]
public class Service1
{
static byte[] imageData;

[OperationContract]
public byte[] GetData()
{
return imageData;
}

[OperationContract]
public void SetData(byte[] data)
{
imageData = data;
}
}

Так просто, как только можно. SetData получает байты изображения из приложения, а GetData возвращает существующие байты запрашивающему приложению. Заметьте, однако, что imageData (байтовый массив) установлен в static. Как настольные, так и мобильные приложения будут содержать ссылку на сервисный клиент. Если я не собираюсь отмечать это поле как статическое, каждый новый экземпляр служебного клиента получит совершенно новый массив, и это не то, что мы хотим здесь. Данные должны быть сохранены и доступны независимо от того, какой экземпляр их вызывает.

Теперь вернемся к клиентскому приложению. У нас уже есть данные с веб-камеры, возвращенные в качестве дескриптора —  deviceHandle . Но это IntPtr, а нам нужен байтовый массив. Фактически, у нас есть объект данных, и мы можем скопировать его в буфер обмена и затем выполнить необходимые преобразования.

Итак, в приложении WPF я создал новый метод StartSendingData, который будет обрабатывать данные. Что я делаю в первую очередь, я создаю экземпляр IDataObject и Image (System.Drawing) для хранения изображения данных.

IDataObject dataObject;
System.Drawing.Image image;

Теперь мне нужно передать объект данных, сохраненный дескриптором, в системный буфер обмена. Для этого я вызываю SendMessage с константой WM_CAP_EDIT_COPY, переданной в качестве параметра — это сохранит текущие данные из deviceHandle в буфере обмена.

SendMessage(deviceHandle, WM_CAP_EDIT_COPY, IntPtr.Zero, IntPtr.Zero);

Чтобы получить объект из буфера обмена, я могу использовать это:

dataObject = Clipboard.GetDataObject();

Существует небольшая вероятность, что между моими операциями пользователь изменит объект данных в буфере обмена, поэтому мне нужно проверить, можно ли преобразовать объект в буфере обмена в растровое изображение, прежде чем идти дальше:

if (dataObject.GetDataPresent(typeof(System.Drawing.Bitmap)))
{

}

Если проверка пройдена, я могу преобразовать объект данных в экземпляр Image (еще раз, System.Drawing):

image = (System.Drawing.Image)dataObject.GetData(typeof(System.Drawing.Bitmap));

В зависимости от установленной веб-камеры изображение может быть очень большим (как по разрешению, так и по размеру). По умолчанию для службы WCF лимит передачи данных для каждого запроса равен 16384 байта. Конечно, вы можете отрегулировать этот лимит (на самом деле, увеличить его), изменив привязку по умолчанию, но я обычно не рекомендую делать это в этом случае.

Имейте в виду, что вы передаете данные на мобильное устройство, которое может получать данные через сотовую сеть, где каждый дополнительный мегабайт будет стоить. Поэтому перед передачей фактического байтового массива в службу я минимизирую изображение:

System.Drawing.Image targetImage = image.GetThumbnailImage(image.Width / 2, image.Height / 2, null, IntPtr.Zero);

Теперь я могу работать над механизмом передачи. Прежде всего мне нужен поток для записи данных в:

using (MemoryStream stream = new MemoryStream())
{

}

Здесь я могу сохранить изображение непосредственно в поток и преобразовать его в байтовый массив:

targetImage.Save(stream, ImageFormat.Jpeg);

byte[] b = stream.ToArray();

Теперь убедитесь, что служба, которую вы разработали, работает. Добавьте ссылку на него в настольном приложении (конечно, в качестве ссылки на сервис):

В области того же MemoryStream создайте экземпляр BackgroundWorker, который будет выполнять передачу данных в фоновом потоке:

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync(b);

Обратите внимание, что я передаю байтовый массив в качестве параметра в RunWorkerAsync — таким образом, я могу получить его значение из вторичного потока.

DoWork обработчик событий выглядит следующим образом :

void worker_DoWork(object sender, DoWorkEventArgs e)
{
ImageService.Service1Client client = new ImageService.Service1Client();
client.SetData((byte[])e.Argument);
}

Все, что я делаю, это создаю экземпляр клиента службы и передаю аргумент из RunWorkerAsync в виде байтового массива (обратите внимание на явное приведение) в SetData .

Пока все хорошо, но как сделать этот процесс непрерывным? Для этого есть RunWorkerCompleted:

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
StartSendingData();
}

Когда передача данных будет завершена, она будет отправлена ​​снова и так далее. Вот и все для клиентского приложения. Итак, что я сделал в самом приложении, я добавил две кнопки в главном окне — одну для запуска процесса захвата и другую для начала передачи данных:

Кнопка захвата выполняет то же действие для получения потока изображения, как было описано в статье о веб-камере . Кнопка отправки выполняет одно действие:

private void SendButton_Click(object sender, RoutedEventArgs e)
{
StartSendingData();
}

Теперь вы можете запустить клиентское приложение — сначала захватить, а затем отправить.

В мобильном приложении добавьте ту же ссылку на службу, которую вы добавили для клиентского приложения, поскольку эта же служба используется для извлечения двоичного содержимого.

Я реализовал метод с именем ServiceCall, который будет создавать экземпляр клиента службы и запускать процесс получения двоичных данных:

void ServiceCall()
{
ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
client.GetDataCompleted += new EventHandler<ServiceReference1.GetDataCompletedEventArgs>(client_GetDataCompleted);
client.GetDataAsync();
}

Когда передача завершится, я могу установить двоичные данные на изображение:

void client_GetDataCompleted(object sender, ServiceReference1.GetDataCompletedEventArgs e)
{
using (MemoryStream stream = new MemoryStream(e.Result))
{
BitmapImage image = new BitmapImage();
image.SetSource(stream);
image1.Source = image;
}

ServiceCall();
}

Он также настроен таким образом, что он будет работать непрерывно, вызывая один и тот же метод по завершении.

Убедитесь, что вы вызываете ServiceCall где-нибудь в своем приложении WP7, и после запуска всех трех компонентов (клиентское приложение должно быть настроено на отправку данных) вы должны получить результаты, подобные следующим:

Веб-камера на Windows Phone 7 [YouTube Video]

Конечно, в некоторой степени будет отставание (в зависимости от качества изображения и скорости соединения), но, тем не менее, это работает. Это, безусловно, проект из категории «просто для удовольствия», но я определенно вижу места, где его можно применить.

Вы можете скачать исходный код здесь (ZIP-архивы — разработаны в Visual Studio 2010):