Things used in this project

Hardware components:
Pi 3 02
Raspberry Pi 3 Model B
×1
ELP 2.8mm wide angle lens 1080p HD USB Camera Module (ELP-USBFHD01M-L28)
×1
Software apps and online services:
10
Microsoft Windows 10 IoT Core
Vs2015logo
Microsoft Visual Studio 2015

Schematics

Raspberry Pi 3 Model B and ELP-USBFHD01M-L28 Webcam
Img 0479 ivhsndpmjj

Code

Camera.csC#
The C# camera class get the current frame from the webcam stream and write it as an byte array jpeg image in the Frame property.
using HttpWebcamLiveStream.Helper;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Graphics.Imaging;
using Windows.Media;
using Windows.Media.Capture;
using Windows.Media.Capture.Frames;
using Windows.Media.MediaProperties;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;

namespace HttpWebcamLiveStream.Devices
{
    /// <summary>
    /// Camera: ELP 2.8mm wide angle lens 1080p HD USB Camera Module (ELP-USBFHD01M-L28)
    /// </summary>
    public class Camera
    {
        public byte[] Frame { get; set; }
        private MediaCapture _mediaCapture;
        private MediaFrameReader _mediaFrameReader;

        //Check if camera support resolution and subtyp before change
        private const int VIDEO_WIDTH = 640;
        private const int VIDEO_HEIGHT = 480;
        private const string VIDEO_SUBTYP = "YUY2";
        private const double IMAGE_QUALITY_PERCENT = 0.8d;
        private BitmapPropertySet _imageQuality;

        public async Task Initialize()
        {
            await CoreApplication.MainView.CoreWindow.Dispatcher.RunAndAwaitAsync(CoreDispatcherPriority.Normal, async () =>
            {
                _imageQuality = new BitmapPropertySet();
                var imageQualityValue = new BitmapTypedValue(IMAGE_QUALITY_PERCENT, Windows.Foundation.PropertyType.Single);
                _imageQuality.Add("ImageQuality", imageQualityValue);

                _mediaCapture = new MediaCapture();

                var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

                var settings = new MediaCaptureInitializationSettings()
                {
                    SharingMode = MediaCaptureSharingMode.ExclusiveControl,

                    //With CPU the results contain always SoftwareBitmaps, otherwise with GPU
                    //they preferring D3DSurface
                    MemoryPreference = MediaCaptureMemoryPreference.Cpu,

                    //Capture only video, no audio
                    StreamingCaptureMode = StreamingCaptureMode.Video
                };

                await _mediaCapture.InitializeAsync(settings);

                var mediaFrameSource = _mediaCapture.FrameSources.First().Value;
                var videoDeviceController = mediaFrameSource.Controller.VideoDeviceController;

                videoDeviceController.DesiredOptimization = Windows.Media.Devices.MediaCaptureOptimization.Quality;
                videoDeviceController.PrimaryUse = Windows.Media.Devices.CaptureUse.Video;

                //Set backlight compensation to min (otherwise there are problems with strong light sources)
                if (videoDeviceController.BacklightCompensation.Capabilities.Supported)
                {
                    videoDeviceController.BacklightCompensation.TrySetValue(videoDeviceController.BacklightCompensation.Capabilities.Min);
                }

                //Set exposure (auto light adjustment)
                if (_mediaCapture.VideoDeviceController.Exposure.Capabilities.Supported
                    && _mediaCapture.VideoDeviceController.Exposure.Capabilities.AutoModeSupported)
                {
                    _mediaCapture.VideoDeviceController.Exposure.TrySetAuto(true);
                }

                //Set resolution, frame rate and video subtyp
                var videoFormat = mediaFrameSource.SupportedFormats.First(sf => sf.VideoFormat.Width == VIDEO_WIDTH
                                                                                && sf.VideoFormat.Height == VIDEO_HEIGHT
                                                                                && sf.Subtype == VIDEO_SUBTYP);

                await mediaFrameSource.SetFormatAsync(videoFormat);

                _mediaFrameReader = await _mediaCapture.CreateFrameReaderAsync(mediaFrameSource);

                //If debugger is attached you can't get frames from the camera, because the BitmapEncoder
                //has a bug and not dispose correctly. This results in an System.OutOfMemoryException
                if (!Debugger.IsAttached)
                {
                    _mediaFrameReader.FrameArrived += FrameArrived;

                    await _mediaFrameReader.StartAsync();
                }
            });
        }

        public void FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs eventArgs)
        {
            var frame = _mediaFrameReader.TryAcquireLatestFrame();

            if (frame == null
                || frame.VideoMediaFrame == null
                || frame.VideoMediaFrame.SoftwareBitmap == null)
                return;

            using (var stream = new InMemoryRandomAccessStream())
            {
                using (var bitmap = SoftwareBitmap.Convert(frame.VideoMediaFrame.SoftwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore))
                {
                    var imageTask = BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream, _imageQuality).AsTask();
                    imageTask.Wait();
                    var encoder = imageTask.Result;
                    encoder.SetSoftwareBitmap(bitmap);

                    //Rotate image 180 degrees
                    //var transform = encoder.BitmapTransform;
                    //transform.Rotation = BitmapRotation.Clockwise180Degrees;

                    var flushTask = encoder.FlushAsync().AsTask();
                    flushTask.Wait();
                    
                    using (var asStream = stream.AsStream())
                    {
                        asStream.Position = 0;

                        var image = new byte[asStream.Length];
                        asStream.Read(image, 0, image.Length);

                        Frame = image;

                        encoder = null;
                    }
                }
            }
        }
    }
}
Camera.jsJavaScript
The camera JavaScript file. The JavaScript function GetVideoFrame gets a jpeg image (the current video frame) with an ajax request and display it in the browser. The function recall itself endless.
//Maximum frames of the video stream per second. The less frames the less network traffic.
var maximumVideoFramesPerSecond = 30;

var maximumVideoFramesPerSecondTimeout = 1000.0 / maximumVideoFramesPerSecond;
var getVideoFrameTimeout = 2000;
var lastVideoFrameTime = new Date();

function GetVideoFrame() {

    lastVideoFrameTime = new Date();

    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://minwinpc/VideoFrame" + new Date().getTime().toString() + ".html", true);
    xhr.responseType = "blob";

    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            if (xhr.status === 200) {
                var urlCreator = window.URL || window.webkitURL;
                var imageUrl = urlCreator.createObjectURL(xhr.response);
                document.querySelector("#videoFrame").src = imageUrl;
            }
        }
    }

    xhr.timeout = getVideoFrameTimeout;
    xhr.ontimeout = function () {
        GetVideoFrameAfterTimeout();
    }

    xhr.onerror = function () {
        GetVideoFrameAfterTimeout();
    }

    xhr.onload = function () {
        GetVideoFrameAfterTimeout();
    }

    xhr.send();
}

function GetVideoFrameAfterTimeout() {
    var videoFrameTimeToLastFrame = new Date() - lastVideoFrameTime;

    if (videoFrameTimeToLastFrame >= maximumVideoFramesPerSecondTimeout) {
        setTimeout(function () { GetVideoFrame(); }, 0);

    } else {
        var nextVideoFrameTimeout = maximumVideoFramesPerSecondTimeout - videoFrameTimeToLastFrame;
        setTimeout(function () { GetVideoFrame(); }, nextVideoFrameTimeout);
    }
}

GetVideoFrame();
HttpServer.csC#
The C# http server. It provides the html, css and javascript files. It returns also the current video frame (jpeg image) to the ajax request thats called from the Camera.js JavaScript file.
using HttpWebcamLiveStream.Devices;
using HttpWebcamLiveStream.Helper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.Data.Json;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace HttpWebcamLiveStream.Web
{
    public sealed class HttpServer
    {
        private const uint BUFFER_SIZE = 3024;
        private readonly StreamSocketListener _listener;

        //Dependency objects
        private Camera _camera;

        public HttpServer(Camera camera)
        {
            _camera = camera;

            _listener = new StreamSocketListener();
            _listener.ConnectionReceived += ProcessRequest;
            _listener.Control.KeepAlive = false;
            _listener.Control.NoDelay = false;
            _listener.Control.QualityOfService = SocketQualityOfService.LowLatency;
        }
        
        public async void Start()
        {
            await _listener.BindServiceNameAsync(80.ToString());
        }

        private async void ProcessRequest(StreamSocketListener streamSocktetListener, StreamSocketListenerConnectionReceivedEventArgs eventArgs)
        {
            var socket = eventArgs.Socket;

            try
            {
                //Read request
                var relativeUrl = await ReadRequest(socket);

                //Write Response
                await WriteResponse(relativeUrl, socket.OutputStream);

                socket.InputStream.Dispose();
                socket.OutputStream.Dispose();
                socket.Dispose();
            }
            catch (Exception exception)
            {
                try
                {
                    HttpServerResponse.WriteResponseError(exception.Message, socket.OutputStream);
                }
                catch (Exception) { }                
            }
        }

        private async Task<string> ReadRequest(StreamSocket socket)
        {
            var request = string.Empty;
            
            using (var inputStream = socket.InputStream)
            {
                var data = new byte[BUFFER_SIZE];
                var buffer = data.AsBuffer();

                var startReadRequest = DateTime.Now;
                while (!HttpGetRequestHasUrl(request))
                {
                    if (DateTime.Now.Subtract(startReadRequest) >= TimeSpan.FromMilliseconds(5000))
                    {
                        throw new TaskCanceledException("Request timeout.");
                    }

                    var inputStreamReadTask = inputStream.ReadAsync(buffer, BUFFER_SIZE, InputStreamOptions.Partial);
                    var timeout = TimeSpan.FromMilliseconds(1000);
                    await TaskHelper.CancelTaskAfterTimeout(ct => inputStreamReadTask.AsTask(ct), timeout);

                    request += Encoding.UTF8.GetString(data, 0, data.Length);
                }
            }

            var requestMethod = request.Split('\n')[0];
            var requestParts = requestMethod.Split(' ');
            var relativeUrl = requestParts.Length > 1 ? requestParts[1] : string.Empty;

            return relativeUrl;
        }

        private async Task WriteResponse(string relativeUrl, IOutputStream outputStream)
        {
            var relativeUrlLower = relativeUrl.ToLowerInvariant();

            //Get javascript files
            if (relativeUrlLower.StartsWith("/javascript"))
            {
                await HttpServerResponse.WriteResponseFile(ToFolderPath(relativeUrl), HttpContentType.JavaScript, outputStream);
            }
            //Get css style files
            else if (relativeUrlLower.StartsWith("/styles"))
            {
                await HttpServerResponse.WriteResponseFile(ToFolderPath(relativeUrl), HttpContentType.Css, outputStream);
            }
            //Get current camera frame
            else if (relativeUrlLower.StartsWith("/videoframe"))
            {
                if (_camera.Frame != null)
                {
                    HttpServerResponse.WriteResponseFile(_camera.Frame, HttpContentType.Jpeg, outputStream);
                }
                else
                {
                    HttpServerResponse.WriteResponseError("Not camera fram available. Maybe there is an error or camera is not started.", outputStream);
                }
            }
            //Get index.html page
            else
            {
                await HttpServerResponse.WriteResponseFile(@"\Html\Index.html", HttpContentType.Html, outputStream);
            }
        }
        
        private bool HttpGetRequestHasUrl(string httpRequest)
        {
            var regex = new Regex("^.*GET.*HTTP.*\\r\\n.*$", RegexOptions.Multiline);
            return regex.IsMatch(httpRequest.ToUpper());
        }

        private string ToFolderPath(string relativeUrl)
        {
            var folderPath = relativeUrl.Replace('/', '\\');
            return folderPath;
        }
    }
}
HttpWebcamLiveStream
Simple browser webcam live stream with Windows IoT Core 14393 and Raspberry Pi 3

Credits

812263169b0ee61fc6c4b75c03df8541
Sascha

.NET Developer

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

How to use latest VNC connect with Raspberry pi
Easy
  • 61
  • 11

Protip

Remotely control your Pi using VNC connect with cloud brokered end-to-end encryption.

Pre-Bedtime Environment Analysis
Easy
  • 399
  • 6

Work in progress

Define the optimum environment for sleep by applying ML to sensor data.

DIY Smart Power Sockets
Easy
  • 1,154
  • 6

Full instructions

Automate all your lights at home for a fraction of the usual cost with this simple project (5 lights for less than $60).

DIY smart home doorbell for less than $40!
Easy
  • 2,012
  • 18

Full instructions

Make a smart doorbell that notifies you when someone is at the door w/ snapshot and let you open the door remotely, all for less than $40!

Digital output management with CoAP
Easy
  • 391
  • 8

Full instructions

The X.IP5 slip-radio manages the X.IP5-web-demo: turning LEDs on/off through CoAP.

Are you in tune?
Easy
  • 132
  • 2

Work in progress

There are many ways/apps/tools to tune an ukulele. Now there is one more,

ProjectsCommunitiesContestsLiveJobsBetaFree StoreBlogAdd projectSign up / Login
Respect project