Things used in this project

Hardware components:
Pi 3 02
Raspberry Pi Raspberry Pi 3 Model B
×1
ELP no distortion lens 1080p HD USB Camera Module (ELP-USBFHD05MT-FD100)
This webcam has no distortion.
×1
ELP 2.8mm wide angle lens 1080p HD USB Camera Module (ELP-USBFHD01M-L28) (optional)
This webcam has a wide angle lens with distortion.
×1
MinnowBoard Turbot Quad Core (optional)
With the MinnowBoard Turbot Quad Core, you can get HD720p (1280x720) video with 25 fps. (Please choose in the settings as video sub type "MJPG", image quality 60% and use 4 threads).
×1
Microsoft Microsoft LifeCam HD 3000 (optional)
×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.Configuration;
using HttpWebcamLiveStream.Helper;
using System;
using System.Collections.Generic;
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.Capture;
using Windows.Media.Capture.Frames;
using Windows.Storage.Streams;
using Windows.UI.Core;

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;
        
        private BitmapPropertySet _imageQuality;

        private int _threadsCount = 0;
        private int _stoppedThreads = 0;
        private bool _stopThreads = false;

        private volatile Stopwatch _lastFrameAdded = new Stopwatch();
        private volatile object _lastFrameAddedLock = new object();

        public async Task Initialize(VideoSetting videoSetting)
        {
            await CoreApplication.MainView.CoreWindow.Dispatcher.RunAndAwaitAsync(CoreDispatcherPriority.Normal, async () =>
            {
                _threadsCount = videoSetting.UsedThreads;
                _stoppedThreads = videoSetting.UsedThreads;

                _lastFrameAdded.Start();

                _imageQuality = new BitmapPropertySet();
                var imageQualityValue = new BitmapTypedValue(videoSetting.VideoQuality, 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 exposure (auto light adjustment)
                if (_mediaCapture.VideoDeviceController.Exposure.Capabilities.Supported
                    && _mediaCapture.VideoDeviceController.Exposure.Capabilities.AutoModeSupported)
                {
                    _mediaCapture.VideoDeviceController.Exposure.TrySetAuto(true);
                }

                var videoResolutionWidthHeight = VideoResolutionWidthHeight.Get(videoSetting.VideoResolution);
                var videoSubType = VideoSubtypeHelper.Get(videoSetting.VideoSubtype);

                //Set resolution, frame rate and video subtyp
                var videoFormat = mediaFrameSource.SupportedFormats.Where(sf => sf.VideoFormat.Width == videoResolutionWidthHeight.Width
                                                                                && sf.VideoFormat.Height == videoResolutionWidthHeight.Height
                                                                                && sf.Subtype == videoSubType)
                                                                    .OrderByDescending(m => m.FrameRate.Numerator / m.FrameRate.Denominator)
                                                                    .First();

                await mediaFrameSource.SetFormatAsync(videoFormat);

                _mediaFrameReader = await _mediaCapture.CreateFrameReaderAsync(mediaFrameSource);
                await _mediaFrameReader.StartAsync();
            });
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void GarbageCollectorCanWorkHere() { }

        private void ProcessFrames()
        {
            _stoppedThreads--;

            while (_stopThreads == false)
            {
                try
                {
                    GarbageCollectorCanWorkHere();
                    
                    var frame = _mediaFrameReader.TryAcquireLatestFrame();

                    var frameDuration = new Stopwatch();
                    frameDuration.Start();

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

                    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);

                                lock (_lastFrameAddedLock)
                                {
                                    if (_lastFrameAdded.Elapsed.Subtract(frameDuration.Elapsed) > TimeSpan.Zero)
                                    {
                                        Frame = image;

                                        _lastFrameAdded = frameDuration;
                                    }
                                }

                                encoder = null;
                            }
                        }
                    }
                }
                catch (ObjectDisposedException) { }
            }

            _stoppedThreads++;
        }

        public void Start()
        {
            for (int workerNumber = 0; workerNumber < _threadsCount; workerNumber++)
            {
                Task.Factory.StartNew(() =>
                {
                    ProcessFrames();

                }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)
                .AsAsyncAction()
                .AsTask();
            }
        }

        public async Task Stop()
        {
            _stopThreads = true;
            
            SpinWait.SpinUntil(() => { return _threadsCount == _stoppedThreads; });

            await _mediaFrameReader.StopAsync();

            _stopThreads = false;
        }

        public async Task<List<MediaFrameFormat>> GetMediaFrameFormatsAsync()
        {
            var mediaFrameFormats = new List<MediaFrameFormat>();

            await CoreApplication.MainView.CoreWindow.Dispatcher.RunAndAwaitAsync(CoreDispatcherPriority.Normal, async () =>
            {
                var mediaCapture = new MediaCapture();

                var settings = new MediaCaptureInitializationSettings()
                {
                    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
                    StreamingCaptureMode = StreamingCaptureMode.Video
                };

                await mediaCapture.InitializeAsync(settings);

                var mediaFrameSource = mediaCapture.FrameSources.First().Value;

                mediaFrameFormats = mediaFrameSource.SupportedFormats.ToList();

                mediaCapture.Dispose();
            });

            return mediaFrameFormats;
        }
    }
}
Camera.jsJavaScript
The camera JavaScript file. It receives the video frames (JPEG images) over a websocket from the http server.
var webSocketVideoFrame;
var frameTime;

function GetVideoFrames() {

    webSocketVideoFrame = new WebSocket('ws://' + location.host + "/VideoFrame");
    webSocketVideoFrame.binaryType = "arraybuffer";

    webSocketVideoFrame.onopen = function () {
        webSocketHelper.waitUntilWebsocketReady(function () {
            webSocketVideoFrame.send(JSON.stringify({ command: "VideoFrame" }));
        }, webSocketVideoFrame, 0);
    };

    webSocketVideoFrame.onmessage = function () {

        var bytearray = new Uint8Array(event.data);

        var blob = new Blob([event.data], { type: "image/jpeg" });
        var url = createObjectURL(blob);
        document.querySelector("#videoFrame").src = url;

        webSocketHelper.waitUntilWebsocketReady(function () {
            webSocketVideoFrame.send(JSON.stringify({ command: "VideoFrame" }));
        }, webSocketVideoFrame, 0);

        frameTime = new Date().getTime();
    };
}

function createObjectURL(blob) {
    if (window.webkitURL) {
        return window.webkitURL.createObjectURL(blob);
    } else if (window.URL && window.URL.createObjectURL) {
        return window.URL.createObjectURL(blob);
    } else {
        return null;
    }
}

function KeepAliveGetVideoFrames() {

    var duration = 0;
    if (frameTime !== undefined) {
        duration = new Date().getTime() - frameTime
    }

    if (frameTime !== undefined
        && duration <= 1000) {

        setTimeout(function () {
            KeepAliveGetVideoFrames();
        }, 100);
    } else {

        if (webSocketVideoFrame !== undefined) {
            try {
                webSocketVideoFrame.close();
            } catch (e) { }
        }

        GetVideoFrames();

        setTimeout(function () {
            KeepAliveGetVideoFrames();
        }, 4000);
    }
}

KeepAliveGetVideoFrames();
HttpServer.csC#
The C# http server. It provides the html, css and javascript files. It also sends the current video frame (jpeg image) to the WebSocket managed in the Camera.js JavaScript file.
using HttpWebcamLiveStream.Configuration;
using HttpWebcamLiveStream.Devices;
using HttpWebcamLiveStream.Helper;
using System;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
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)
        {
            try
            {
                var socket = eventArgs.Socket;

                //Read request
                var request = await ReadRequest(socket);

                //Write Response
                await WriteResponse(request, socket);

                socket.InputStream.Dispose();
                socket.OutputStream.Dispose();
                socket.Dispose();
            }
            catch (Exception) { }
        }

        private async Task<HttpServerRequest> ReadRequest(StreamSocket socket)
        {
            var request = string.Empty;
            var error = false;

            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))
                {
                    error = true;
                    return new HttpServerRequest(null, true);
                }

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

                request += Encoding.UTF8.GetString(data, 0, (int)inputStreamReadTask.AsTask().Result.Length);
            }

            return new HttpServerRequest(request, error);
        }

        private async Task WriteResponse(HttpServerRequest request, StreamSocket socket)
        {
            var relativeUrlLower = request.Url.ToLowerInvariant();
            var outputStream = socket.OutputStream;

            //Get javascript files
            if (relativeUrlLower.StartsWith("/javascript"))
            {
                await HttpServerResponse.WriteResponseFile(ToFolderPath(request.Url), HttpContentType.JavaScript, outputStream);
            }
            //Get css style files
            else if (relativeUrlLower.StartsWith("/styles"))
            {
                await HttpServerResponse.WriteResponseFile(ToFolderPath(request.Url), HttpContentType.Css, outputStream);
            }
            //Get video setting
            else if (relativeUrlLower.StartsWith("/videosetting"))
            {
                HttpServerResponse.WriteResponseJson(ConfigurationFile.VideoSetting.Stringify(), outputStream);
            }
            //Get supported video settings
            else if (relativeUrlLower.StartsWith("/supportedvideosettings"))
            {
                HttpServerResponse.WriteResponseJson(ConfigurationFile.VideoSettingsSupported.Stringify(), outputStream);
            }
            //Set video settings
            else if (relativeUrlLower.StartsWith("/savevideosetting"))
            {
                await _camera.Stop();

                var videoSetting = new VideoSetting
                {
                    VideoSubtype = VideoSubtypeHelper.Get(request.Body["VideoSubtype"].GetString()),
                    VideoResolution = (VideoResolution)request.Body["VideoResolution"].GetNumber(),
                    VideoQuality = request.Body["VideoQuality"].GetNumber(),
                    UsedThreads = (int)request.Body["UsedThreads"].GetNumber()
                };

                await ConfigurationFile.Write(videoSetting);
                await _camera.Initialize(videoSetting);
                _camera.Start();

                HttpServerResponse.WriteResponseOk(outputStream);
            }
            //Get current camera frame
            else if (relativeUrlLower.StartsWith("/videoframe"))
            {
                if (_camera.Frame != null)
                {
                    var webSocket = new WebSocket(socket, request, _camera);
                    await webSocket.Start();
                }
                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.IgnoreCase);
            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

Contact

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

Physical Computing - Scratch 2.0 for Raspberry Pi
Easy
  • 103
  • 5

Full instructions

This is my 3rd tutorial with a focus on Scratch and Physical Computing using RPi. Here we will explore the new version of Scratch, the 2.0.

Break your heart - IoT Project - IoT Blocky
Easy
  • 14
  • 1

Full instructions

Using this project to control the LEDs on the Raspberry Pi Sense HAT

Hey Mycroft, Where Is the International Space Station?
Easy
  • 1,881
  • 37

Full instructions

Developing my first skill with the Mycroft.ai open source voice assistant on a Raspberry Pi 3 - aka Picroft!

Raspberry Pi RGB Console with SwishPi pHAT
Easy
  • 46
  • 1

Full instructions

Build a Python single page app to control an RGB LED using the SwishPI pHAT.

Sound Detector with Visual Alerts
Easy
  • 2,183
  • 3

Full instructions

Create a WiFi-enabled sound detector that sends your phone notifications using Prowl and Python!

Measure Plant Growth
Easy
  • 1,175
  • 16

Find out how much plants grow without killing the plant.

ProjectsCommunitiesTopicsContestsLiveAppsBetaFree StoreBlogAdd projectSign up / Login