The WP7 emulator still doesn’t support camera simulation in Windows Phone Developer Tools Beta.  Several days ago, I found an interesting artcile in MSDN CODING4FUN talking about photo effect processing and camera capture on WP7.  Then I have an idea – Why don’t we use normal webcams of PC to simulate the camera on WP7 emulator.  Here is my trial.

Demo Video

In fact, it’s not difficult to get it work, we need a WebCam Gateway (WinForm application) which listens to specific HTTP port always return current captured image byte array as response when receive HTTP request.  In WP7 side, we use WebClient to send HTTP request to the “WebCam Gateway” and get a stream with image data inside, the same way as what we do in CameraCaptureTask.Completed event.

WebCam Gateway WinForm Application

WebCam Gateway has only two primary goals: capture photo from webcam and works as a HTTP server!  When it comes to webcam programming, WIA seems the easiest way. However, video content support is removed from WIA for Windows Vista. Microsoft recommends using the newer WPD API.  I know nothing about webcam, so I picked an easier way, there is an “out-of-box” project, DxSnap, in DirectShow.NET library samples.  I wrote a MicroHttpServer class using TcpListener to get request and return data, put it into DxSnap, and the WebCam Gateway is done.


Fig: Use IE to get photo from WebCam Gateway directly (click image to scale up)

CameraProxy Class

Now, since we can use any browser to get current image of webcam, it’s easy to do the same thing in WP7.   I wrote a CameraProxy class to encapsulate WebCam Gateway communication detail and try to make it’s behavior as similar as CameraCaptureTask as possible.  So, there is a Show() method, a Completed(object sender, PhotoResult e) event that  we can use the original CameraCaptureTask.Completed event handler to receive the image from WebCam Gateway.  Unfortunately, PhotoResult.ChosenPhoto property is read-only, so I can’t use it to return image data.  My solution is to declare a PhotoResultHacking class inheriting from PhotoResult and add a CamerPhotoStream property, and then we can extract the image data by detecting the argument type.

Here is the code of CameraProxy:

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.Phone.Tasks;
using System.IO;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
 
namespace Darkthread
{
    public class CameraProxy
    {
        private string gatewayUrl = null;
        private IApplicationBar appBar = null; 
        private Border mask = new Border()
        {
            Background = new SolidColorBrush(Colors.White),
            Opacity = 0.7,
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment = VerticalAlignment.Stretch,
            Visibility = Visibility.Collapsed
        };
        //Constructor, url for WebCam Gateway, page for UI blocking
        public CameraProxy(string url, PhoneApplicationPage page)
        {
            gatewayUrl = url;
            if (page != null)
            {
                appBar = page.ApplicationBar;
                //Try to get LayoutRoot element
                Panel pnl = 
                    VisualTreeHelper.GetChild(page, 0) as Panel;
                //If the LayoutRoot is a container
                if (pnl != null) //Add a mask element
                    pnl.Children.Add(mask);
                else
                    mask = null;
            }
        }
        //Completed event compatible with CameraCaptureTask
        public event Action<object, PhotoResult> Completed;
       
        public void Show()
        {
            WebClient wc = new WebClient();
            wc.OpenReadCompleted += (sender, e) =>
            {
                try
                {
                    //e.Result is the stream that contains image we need
                    PhotoResultHacking pr = new PhotoResultHacking(
                        Microsoft.Phone.Tasks.TaskResult.OK, e.Result);
                    if (Completed != null)
                        Completed(this, pr);
                }
                catch
                {
                    //If any exception, return TaskResult.Cancel
                    PhotoResult epr = new PhotoResult(TaskResult.Cancel);
                    if (Completed != null)
                        Completed(this, epr);
                }
                finally
                {
                    //Remove the blocking mask
                    if (mask != null)
                        mask.Visibility = Visibility.Collapsed;
                    //Show the ApplicationBar
                    if (appBar != null)
                        appBar.IsVisible = true;
                }
            };
            //Add a mask to block the UI
            if (mask != null)
                mask.Visibility = Visibility.Visible;
            //Hide ApplicationBar to block the UI
            if (appBar != null)
                appBar.IsVisible = false;
            wc.OpenReadAsync(new Uri(gatewayUrl));
        }
    }
    //A **compatible** PhotoResult providing writable property
    public class PhotoResultHacking : PhotoResult
    {
        public Stream CameraPhotoStream;
        public PhotoResultHacking(TaskResult result, Stream stream) :
            base(result)
        {
            CameraPhotoStream = stream;
        }
    }
}

How to Use

I modified PicFx project to show how to use CameraProxy. 

First, we create CameraProxy object in Initialize() and useCompleted event handler of CameraCaptureTask.

        //**CameraProxy**
        Darkthread.CameraProxy cameraProxy;
 
        private void Initialize()
        {
            // Init tasks
            cameraCaptureTask = new CameraCaptureTask();
            cameraCaptureTask.Completed += PhotoProviderTaskCompleted;
 
            //**CameraProxy**
            //Create CameraProxy object and shared the same event handler
            //with cameraCaptureTask.Completed
            //PS: 192.168.1.136 is the local IP address of host
            cameraProxy = new Darkthread.CameraProxy(
                "http://192.168.1.136:1688/", this);
            cameraProxy.Completed += PhotoProviderTaskCompleted;

In orginal project, the Camera button is disabled in emulator mode, now let’s turn it on. 

            if (btn != null)
            {
                //**CameraProxy**
                //btn.IsEnabled = Microsoft.Devices.Environment.DeviceType 
                //                != DeviceType.Emulator;
                btn.IsEnabled = true;
            }

When camera button is clicked, either cameraProxy.Show() or cameraCaptureTaskShow() is called depending on EmulatorMode flag.  Event handler PhotoProviderTaskCompleted is shared among cameraProxy, cameraCaptureTask, and photoChooserTask, so inside the event we have to detect if the result is returned by cameraProxy to cast the result parameter to PhotoResultHacking class, so we get image data correctly from extra CameraPhotoStream property. 

Final part is ugly but seems unavoidable.  CameraCaptureTask and PhotoChooser are handled by operation system, they will terminate the application and launch it again after task, but I don’t think it’s possible to simulate the same behavior by our code.  In original design, some job after image captured is arranged in Initialize() and LayoutUpdated(),   So only thing I can do is to move these necessary images preparing logic to Completed event.

        //**CameraProxy** Add a flag to identify if it's in emulator mode
        private bool EmulatorMode
        {
            get
            {
                return Microsoft.Devices.Environment.DeviceType 
                       == DeviceType.Emulator;
            }
        }
 
        private void ApplicationBarIconCameraButton_Click(object sender, EventArgs e)
        {
            //Trigger CameraCaptureTask or CameraProxy depends on EmulatorMode flag
            if (EmulatorMode)
                cameraProxy.Show();
            else
                cameraCaptureTask.Show();
        }
 
        private void PhotoProviderTaskCompleted(object sender, PhotoResult e)
        {
            // Load the photo from the task result
            if (e != null && e.TaskResult == TaskResult.OK)
            {
                var bmpi = new BitmapImage();
                //**CameraProxy** add branch code for CameraProxy
                bool extCamera = (EmulatorMode && 
                                  e is Darkthread.PhotoResultHacking);
                if (extCamera)
                    bmpi.SetSource(
                        ((Darkthread.PhotoResultHacking)e).CameraPhotoStream);
                else 
                    bmpi.SetSource(e.ChosenPhoto);
                original = new WriteableBitmap(bmpi);
                //**CameraProxy**
                //Real CameraCaptureTask will terminate the application
                //and cause Loaded event, but CameraProxy won't,
                //so we have to add the logic after camera capture here.
                //Ugly, but seems no better way
                if (extCamera)
                {
                    ShowImage(original);
                    ResizeAndShowImage(original);
                }
            }
        }

That’s all!  Now we can use the WP7 emulator to **take a shot**, have fun!

You can download the WebCam Gateway source code with CameraProxy.cs, modified MainPage.xaml.cs from here

Any feedback is welcome.


Comments

# by 瓦牛

怎麼變西文了 :3

# by support@sassytoll.com

Thanks for posting, this really helped me out. Thanks, keep up the great work

# by russ

hi, getting a weird problem. the first time I call Show WebCam gateway app sees the OpenReadAsync and waits for you to click the 'shutter' button. the second time I call it OpenReadAsync runs, nothing happens in the gateway app, and the code goes into OpenReadCompleted with the photo I took the first time! sounds like a bug in the emulator; i'll pull it apart and see if I can reproduce it with the minimal code. Russ

# by russ

my problem appears to be a caching issue. if i attach a random guid to the URL everything works fine. I've replaced the OpenReadAsync line with: wc.OpenReadAsync(new Uri(gatewayUrl + "/" + Guid.NewGuid().ToString()));

# by Jeffrey

to russ, Good Job! Glad to see you find the cause and thanks for your sharing.

# by my

it doesn't work with the CAmeraProxy and the Modified Main.xaml.cs

# by #

there is missing effects, viewmodels & CompositeEffects

# by ukcctvshop12@gmail.com

I am happy to find so many useful information here in the post, we need develop more strategies in this regard, thanks for sharing.

Post a comment