A customer asked us to integrate a new camera with
Padarn recently (the
D-Link DCS-900) to try to do a similar thing that I had done in my
PTZ camera demo. Well this camera was a little different and a whole lot easier to get "streaming" data from. I ended up writing a small wrapper class that takes the MJPG data coming from the camera, parses it into in-memory Image classes, and then those are available for use.
Once I had that I figured it would be interesting to serve up a Padarn page that did nothing but return the latest frame data from that camera - after all the camera doesn't support PTZ and just a button that takes a picture didn't seem terribly new or informative.
The problem was that the version of Padarn I was using (1.0.5020) didn't support writing a stream of bytes to the Response. Well as of 1.0.5030 Padarn now supports WriteBytes (and a few other things like CacheBehavior) and I created a page that dynamically loads the images into an on-page IFrame and allows you to adjust the refresh rate dynamically without ever having to send a full page request in again.
I can see that this could be useful for dynamic updates to a page for something like a control system gauge or for animating building automation status items.
Check out the new sample here.
The code for the DCS-900 looks like this (notice that the ICamera interface changed a little, so the PTZ camera also had to be altered. I simply stubbed out the new methods).
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using OpenNETCF.Peripherals.Camera;
namespace OpenNETCF.Peripherals
{
public class DCS900 : ICamera
{
private Image m_lastImage = null;
private volatile bool m_stopThread = true;
Thread m_grabberThread;
private IPAddress m_ip;
public DCS900()
{
}
public void Initialize(IPAddress ipAddress, string userName, string password)
{
m_ip = ipAddress;
}
public bool SupportsPanTilt
{
get { return false; }
}
private int FindFrameBoundary(byte[] data, int offset, int maxLength)
{
byte[] frameDelimiter = Encoding.ASCII.GetBytes("--video boundary--");
int n = 0;
bool match;
for (; offset < maxLength; offset++)
{
match = true;
if (data[offset] == frameDelimiter[n])
{
match &= true;
n++;
if (n >= frameDelimiter.Length)
{
// we've found the header
return offset + 1;
}
}
else
{
match = false;
n = 0;
}
}
return -1;
}
private Image ImageFromFrameData(byte[] data, int frameLength)
{
int start = 50;
// find the data start - it will be between 50 and 54 bytes in from the frame start
while (data[start] != 0xFF)
{
start++;
if (start > 54) return null;
if (data[start + 1] == 0xD9) break;
}
try
{
using (MemoryStream stream = new MemoryStream(data, start, frameLength - start))
{
Image image = new Bitmap(stream);
stream.Close();
return image;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
public void Start()
{
m_grabberThread = new Thread(GrabberThreadProc);
m_grabberThread.IsBackground = true;
m_grabberThread.Start();
}
public void Stop()
{
m_stopThread = true;
}
public bool IsRunning
{
get { return !m_stopThread; }
}
private void GrabberThreadProc()
{
lock (m_grabberThread)
{
m_stopThread = false;
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Connect(new IPEndPoint(m_ip, 80));
string header = "GET /VIDEO.CGI HTTP/1.0\r\nUser-Agent: user\r\nAuthorization: Basic YWRtaW46REVVU1Q=\r\n\r\n";
byte[] data = Encoding.ASCII.GetBytes(header);
byte[] image = new byte[32768]; // 32k buffer is more than adequate for this camera
s.Send(data);
data = new byte[2048];
int offsetStart = 0;
int offsetEnd = 0;
int ptr = 0;
int length = s.Receive(data);
do
{
offsetStart = FindFrameBoundary(data, 0, length);
} while (offsetStart < 0);
while (!m_stopThread)
{
offsetEnd = FindFrameBoundary(data, offsetStart, length);
while (offsetEnd < 0)
{