Вы когда-нибудь задумывались о подключении дополнительного оборудования к вашему телефону? Я сделал, и не потому, что в телефоне нет этого конкретного оборудования, а скорее из любопытства — что будет, если он будет работать? Так что есть идея — показать изображение с веб-камеры прямо на устройстве 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):